11#!/usr/bin/env python3
2- """Patch the build copy of amica15 for a portable gfortran build (epic #165) .
2+ """Patch the build copy of amica15 for a portable, seedable gfortran build.
33
4- Applies sccn/amica PR #53's portable random_seed fix: the tracked source passes a
5- fixed size-2 PUT array (only ifort accepts that); gfortran needs an array of its
6- own SIZE. Idempotent and verified -- if the anchor lines are absent (source
7- drift), it fails loudly rather than building a subtly wrong binary.
4+ Applies sccn/amica PR #54 (supersedes #53): (1) size the ``random_seed(PUT=...)``
5+ array via ``random_seed(SIZE=...)`` so it builds under gfortran, not only ifort;
6+ (2) wire a ``seed`` ``input.param`` option so a run's random initialization is
7+ reproducible when set, and clock-random otherwise. Idempotent and verified -- if
8+ an anchor is absent (source drift) it fails loudly rather than building a subtly
9+ wrong binary.
810
9- Only the build copy is patched; the tracked amica15.f90 stays the read-only
10- parity reference.
11+ Only the build copy is patched; the tracked `` amica15.f90`` stays the read-only
12+ parity reference (== sccn/amica master until PR #54 merges) .
1113"""
1214
1315import sys
1416
17+ # (1) header: SIZE-based seed array + the `seed` param state.
18+ HDR_ANCHOR = "integer :: ii, jj, kk, c0, c1, c2"
19+ HDR_ADD = (
20+ HDR_ANCHOR
21+ + "\n INTEGER :: nseed, input_seed = 0"
22+ + "\n LOGICAL :: use_seed = .false."
23+ + "\n INTEGER, ALLOCATABLE :: seedvec(:)"
24+ )
25+
26+ # (2) portable + reproducible seeding.
1527OLD_SEED = "call random_seed(PUT = c1 * (myrank+1) * (seed+myrank+1))"
1628NEW_SEED = """\
17- ! Portable RNG seeding (sccn/amica PR #53): gfortran's random_seed(PUT=...)
18- ! requires an array of the compiler-defined size (query with SIZE=); the original
19- ! passed a fixed size-2 array, which only compiles with ifort. Fill the full-size
20- ! array from the clock, rank, and fixed seed so per-rank streams still differ .
29+ ! Portable + reproducible RNG seeding (sccn/amica PR #54, supersedes #53):
30+ ! random_seed(PUT=...) needs an array of the compiler SIZE (query with SIZE=);
31+ ! the original size-2 array only compiled under ifort. With `seed` set in the
32+ ! param file, seed deterministically (reproducible); otherwise clock-seed .
2133call random_seed(size = nseed)
2234allocate(seedvec(nseed))
23- do jj = 1, nseed
24- seedvec(jj) = c1 + (jj-1) + (myrank+1)*(seed(mod(jj-1,2)+1) + myrank + 1)
25- end do
35+ if (use_seed) then
36+ do jj = 1, nseed
37+ seedvec(jj) = input_seed + 1009*myrank + 37*(jj-1)
38+ end do
39+ else
40+ do jj = 1, nseed
41+ seedvec(jj) = c1 + 1009*(myrank+1)*jj + 37*(jj-1)
42+ end do
43+ end if
2644call random_seed(PUT = seedvec)
2745deallocate(seedvec)"""
2846
29- HDR_ANCHOR = "integer :: ii, jj, kk, c0, c1, c2"
30- HDR_ADD = HDR_ANCHOR + "\n INTEGER :: nseed\n INTEGER, ALLOCATABLE :: seedvec(:)"
47+ # (3) broadcast the new param state to all ranks.
48+ OLD_BCAST = "call MPI_BCAST(rholratefact,1,MPI_DOUBLE_PRECISION,0,seg_comm,ierr)"
49+ NEW_BCAST = (
50+ OLD_BCAST
51+ + "\n call MPI_BCAST(input_seed,1,MPI_INTEGER,0,seg_comm,ierr)"
52+ + "\n call MPI_BCAST(use_seed,1,MPI_LOGICAL,0,seg_comm,ierr)"
53+ )
54+
55+ # (4) parse the `seed` option from input.param (appended after the `indir` case).
56+ OLD_PARSE = " case('indir')\n read(tmparg,'(a)') indirparam"
57+ NEW_PARSE = (
58+ OLD_PARSE
59+ + "\n case('seed')"
60+ + "\n read(tmparg,'(i12)') input_seed"
61+ + "\n use_seed = .true."
62+ + "\n print *, 'seed = ', input_seed; call flush(6)"
63+ )
3164
3265
3366def patch (path , old , new , marker , label ):
@@ -50,17 +83,22 @@ def main():
5083
5184 new_seed = NEW_SEED
5285 if pin_seed :
53- # Determinism for validate_shim.sh: drop the clock term (c1) so the shim
54- # and mpif90 builds seed identically and can be compared bit-for-bit.
55- # Scoped to the seed formula only -- the clock is still read for timing.
56- if "c1 +" not in NEW_SEED :
57- sys .exit ("ERROR: --pin-seed anchor 'c1 +' not found in NEW_SEED" )
86+ # Determinism for validate_shim.sh: drop the clock term (c1) from the
87+ # default (unseeded) branch so the shim and mpif90 builds seed identically
88+ # and can be compared bit-for-bit. Scoped to that one formula.
89+ if "seedvec(jj) = c1 +" not in NEW_SEED :
90+ sys .exit (
91+ "ERROR: --pin-seed anchor 'seedvec(jj) = c1 +' not found in NEW_SEED"
92+ )
5893 new_seed = NEW_SEED .replace ("seedvec(jj) = c1 +" , "seedvec(jj) = 987654321 +" )
5994
6095 patch (header , HDR_ANCHOR , HDR_ADD , "INTEGER :: nseed" , "header seedvec decl" )
6196 patch (amica15 , OLD_SEED , new_seed , "call random_seed(size = nseed)" , "random_seed" )
97+ patch (amica15 , OLD_BCAST , NEW_BCAST , "MPI_BCAST(use_seed" , "seed BCAST" )
98+ patch (amica15 , OLD_PARSE , NEW_PARSE , "case('seed')" , "seed param parser" )
6299 print (
63- f"patched: portable random_seed + seedvec declarations{ ' (pinned)' if pin_seed else '' } "
100+ "patched: portable+reproducible random_seed, seed param, BCAST"
101+ f"{ ' (pinned)' if pin_seed else '' } "
64102 )
65103
66104
0 commit comments