Skip to content

Commit 239f7fe

Browse files
ahman24paulromano
andauthored
Implement user-configurable random number stride (#3067)
Co-authored-by: Paul Romano <[email protected]>
1 parent e2557bb commit 239f7fe

File tree

16 files changed

+153
-3
lines changed

16 files changed

+153
-3
lines changed

docs/source/io_formats/settings.rst

+9
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,15 @@ pseudo-random number generator.
538538

539539
*Default*: 1
540540

541+
--------------------
542+
``<stride>`` Element
543+
--------------------
544+
545+
The ``stride`` element is used to specify how many random numbers are allocated
546+
for each source particle history.
547+
548+
*Default*: 152,917
549+
541550
.. _source_element:
542551

543552
--------------------

docs/source/io_formats/statepoint.rst

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The current version of the statepoint file format is 18.1.
2323
bank is present (1) or not (0).
2424

2525
:Datasets: - **seed** (*int8_t*) -- Pseudo-random number generator seed.
26+
- **stride** (*uint64_t*) -- Pseudo-random number generator stride.
2627
- **energy_mode** (*char[]*) -- Energy mode of the run, either
2728
'continuous-energy' or 'multi-group'.
2829
- **run_mode** (*char[]*) -- Run mode used, either 'eigenvalue' or

include/openmc/capi.h

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ int openmc_get_nuclide_index(const char name[], int* index);
7171
int openmc_add_unstructured_mesh(
7272
const char filename[], const char library[], int* id);
7373
int64_t openmc_get_seed();
74+
uint64_t openmc_get_stride();
7475
int openmc_get_tally_index(int32_t id, int32_t* index);
7576
void openmc_get_tally_next_id(int32_t* id);
7677
int openmc_global_tallies(double** ptr);
@@ -137,6 +138,7 @@ int openmc_reset_timers();
137138
int openmc_run();
138139
int openmc_sample_external_source(size_t n, uint64_t* seed, void* sites);
139140
void openmc_set_seed(int64_t new_seed);
141+
void openmc_set_stride(uint64_t new_stride);
140142
int openmc_set_n_batches(
141143
int32_t n_batches, bool set_max_batches, bool add_statepoint_batch);
142144
int openmc_simulation_finalize();

include/openmc/random_lcg.h

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ constexpr int STREAM_SOURCE {1};
1515
constexpr int STREAM_URR_PTABLE {2};
1616
constexpr int STREAM_VOLUME {3};
1717
constexpr int64_t DEFAULT_SEED {1};
18+
constexpr uint64_t DEFAULT_STRIDE {152917ULL};
1819

1920
//==============================================================================
2021
//! Generate a pseudo-random number using a linear congruential generator.
@@ -98,5 +99,18 @@ extern "C" int64_t openmc_get_seed();
9899

99100
extern "C" void openmc_set_seed(int64_t new_seed);
100101

102+
//==============================================================================
103+
//! Get OpenMC's stride.
104+
//==============================================================================
105+
106+
extern "C" uint64_t openmc_get_stride();
107+
108+
//==============================================================================
109+
//! Set OpenMC's stride.
110+
//! @param new_stride Stride.
111+
//==============================================================================
112+
113+
extern "C" void openmc_set_stride(uint64_t new_stride);
114+
101115
} // namespace openmc
102116
#endif // OPENMC_RANDOM_LCG_H

openmc/lib/settings.py

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
_dll.openmc_set_seed.argtypes = [c_int64]
1414
_dll.openmc_get_seed.restype = c_int64
15+
_dll.openmc_set_stride.argtypes = [c_int64]
16+
_dll.openmc_get_stride.restype = c_int64
1517
_dll.openmc_get_n_batches.argtypes = [POINTER(c_int), c_bool]
1618
_dll.openmc_get_n_batches.restype = c_int
1719
_dll.openmc_get_n_batches.errcheck = _error_handler
@@ -68,6 +70,14 @@ def seed(self):
6870
def seed(self, seed):
6971
_dll.openmc_set_seed(seed)
7072

73+
@property
74+
def stride(self):
75+
return _dll.openmc_get_stride()
76+
77+
@stride.setter
78+
def stride(self, stride):
79+
_dll.openmc_set_stride(stride)
80+
7181
def set_batches(self, n_batches, set_max_batches=True, add_sp_batch=True):
7282
"""Set number of batches or maximum number of batches
7383

openmc/settings.py

+25
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ class Settings:
193193
The type of calculation to perform (default is 'eigenvalue')
194194
seed : int
195195
Seed for the linear congruential pseudorandom number generator
196+
stride : int
197+
Number of random numbers allocated for each source particle history
196198
source : Iterable of openmc.SourceBase
197199
Distribution of source sites in space, angle, and energy
198200
sourcepoint : dict
@@ -338,6 +340,7 @@ def __init__(self, **kwargs):
338340
self._ptables = None
339341
self._uniform_source_sampling = None
340342
self._seed = None
343+
self._stride = None
341344
self._survival_biasing = None
342345

343346
# Shannon entropy mesh
@@ -614,6 +617,16 @@ def seed(self, seed: int):
614617
cv.check_greater_than('random number generator seed', seed, 0)
615618
self._seed = seed
616619

620+
@property
621+
def stride(self) -> int:
622+
return self._stride
623+
624+
@stride.setter
625+
def stride(self, stride: int):
626+
cv.check_type('random number generator stride', stride, Integral)
627+
cv.check_greater_than('random number generator stride', stride, 0)
628+
self._stride = stride
629+
617630
@property
618631
def survival_biasing(self) -> bool:
619632
return self._survival_biasing
@@ -1336,6 +1349,11 @@ def _create_seed_subelement(self, root):
13361349
element = ET.SubElement(root, "seed")
13371350
element.text = str(self._seed)
13381351

1352+
def _create_stride_subelement(self, root):
1353+
if self._stride is not None:
1354+
element = ET.SubElement(root, "stride")
1355+
element.text = str(self._stride)
1356+
13391357
def _create_survival_biasing_subelement(self, root):
13401358
if self._survival_biasing is not None:
13411359
element = ET.SubElement(root, "survival_biasing")
@@ -1763,6 +1781,11 @@ def _seed_from_xml_element(self, root):
17631781
if text is not None:
17641782
self.seed = int(text)
17651783

1784+
def _stride_from_xml_element(self, root):
1785+
text = get_text(root, 'stride')
1786+
if text is not None:
1787+
self.stride = int(text)
1788+
17661789
def _survival_biasing_from_xml_element(self, root):
17671790
text = get_text(root, 'survival_biasing')
17681791
if text is not None:
@@ -2014,6 +2037,7 @@ def to_xml_element(self, mesh_memo=None):
20142037
self._create_plot_seed_subelement(element)
20152038
self._create_ptables_subelement(element)
20162039
self._create_seed_subelement(element)
2040+
self._create_stride_subelement(element)
20172041
self._create_survival_biasing_subelement(element)
20182042
self._create_cutoff_subelement(element)
20192043
self._create_entropy_mesh_subelement(element, mesh_memo)
@@ -2122,6 +2146,7 @@ def from_xml_element(cls, elem, meshes=None):
21222146
settings._plot_seed_from_xml_element(elem)
21232147
settings._ptables_from_xml_element(elem)
21242148
settings._seed_from_xml_element(elem)
2149+
settings._stride_from_xml_element(elem)
21252150
settings._survival_biasing_from_xml_element(elem)
21262151
settings._cutoff_from_xml_element(elem)
21272152
settings._entropy_mesh_from_xml_element(elem, meshes)

openmc/statepoint.py

+6
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class StatePoint:
9999
and whose values are time values in seconds.
100100
seed : int
101101
Pseudorandom number generator seed
102+
stride : int
103+
Number of random numbers allocated for each particle history
102104
source : numpy.ndarray of compound datatype
103105
Array of source sites. The compound datatype has fields 'r', 'u',
104106
'E', 'wgt', 'delayed_group', 'surf_id', and 'particle', corresponding to
@@ -356,6 +358,10 @@ def runtime(self):
356358
def seed(self):
357359
return self._f['seed'][()]
358360

361+
@property
362+
def stride(self):
363+
return self._f['stride'][()]
364+
359365
@property
360366
def source(self):
361367
return self._f['source_bank'][()] if self.source_present else None

src/finalize.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ int openmc_finalize()
159159
model::root_universe = -1;
160160
model::plotter_seed = 1;
161161
openmc::openmc_set_seed(DEFAULT_SEED);
162+
openmc::openmc_set_stride(DEFAULT_STRIDE);
162163

163164
// Deallocate arrays
164165
free_memory();
@@ -221,5 +222,6 @@ int openmc_hard_reset()
221222

222223
// Reset the random number generator state
223224
openmc::openmc_set_seed(DEFAULT_SEED);
225+
openmc::openmc_set_stride(DEFAULT_STRIDE);
224226
return 0;
225227
}

src/initialize.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ int openmc_init(int argc, char* argv[], const void* intracomm)
9999
}
100100
#endif
101101

102-
// Initialize random number generator -- if the user specifies a seed, it
103-
// will be re-initialized later
102+
// Initialize random number generator -- if the user specifies a seed and/or
103+
// stride, it will be re-initialized later
104104
openmc::openmc_set_seed(DEFAULT_SEED);
105+
openmc::openmc_set_stride(DEFAULT_STRIDE);
105106

106107
// Copy previous locale and set locale to C. This is a workaround for an issue
107108
// whereby when openmc_init is called from the plotter, the Qt application

src/random_lcg.cpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ int64_t master_seed {1};
1010
// LCG parameters
1111
constexpr uint64_t prn_mult {6364136223846793005ULL}; // multiplication
1212
constexpr uint64_t prn_add {1442695040888963407ULL}; // additive factor, c
13-
constexpr uint64_t prn_stride {152917LL}; // stride between particles
13+
uint64_t prn_stride {DEFAULT_STRIDE}; // stride between particles
1414

1515
//==============================================================================
1616
// PRN
@@ -133,4 +133,14 @@ extern "C" void openmc_set_seed(int64_t new_seed)
133133
master_seed = new_seed;
134134
}
135135

136+
extern "C" uint64_t openmc_get_stride()
137+
{
138+
return prn_stride;
139+
}
140+
141+
extern "C" void openmc_set_stride(uint64_t new_stride)
142+
{
143+
prn_stride = new_stride;
144+
}
145+
136146
} // namespace openmc

src/settings.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,12 @@ void read_settings_xml(pugi::xml_node root)
514514
openmc_set_seed(seed);
515515
}
516516

517+
// Copy random number stride if specified
518+
if (check_for_node(root, "stride")) {
519+
auto stride = std::stoull(get_node_value(root, "stride"));
520+
openmc_set_stride(stride);
521+
}
522+
517523
// Check for electron treatment
518524
if (check_for_node(root, "electron_treatment")) {
519525
auto temp_str = get_node_value(root, "electron_treatment", true, true);

src/state_point.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ extern "C" int openmc_statepoint_write(const char* filename, bool* write_source)
8989
// Write out random number seed
9090
write_dataset(file_id, "seed", openmc_get_seed());
9191

92+
// Write out random number stride
93+
write_dataset(file_id, "stride", openmc_get_stride());
94+
9295
// Write run information
9396
write_dataset(file_id, "energy_mode",
9497
settings::run_CE ? "continuous-energy" : "multi-group");
@@ -399,6 +402,11 @@ extern "C" int openmc_statepoint_load(const char* filename)
399402
read_dataset(file_id, "seed", seed);
400403
openmc_set_seed(seed);
401404

405+
// Read and overwrite random number stride
406+
uint64_t stride;
407+
read_dataset(file_id, "stride", stride);
408+
openmc_set_stride(stride);
409+
402410
// It is not impossible for a state point to be generated from a CE run but
403411
// to be loaded in to an MG run (or vice versa), check to prevent that.
404412
read_dataset(file_id, "energy_mode", word);

tests/regression_tests/stride/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version='1.0' encoding='utf-8'?>
2+
<model>
3+
<materials>
4+
<material depletable="true" id="1">
5+
<density units="g/cm3" value="4.5"/>
6+
<nuclide ao="1.0" name="U235"/>
7+
</material>
8+
</materials>
9+
<geometry>
10+
<cell id="1" material="1" region="-1" universe="1"/>
11+
<surface boundary="vacuum" coeffs="0.0 0.0 0.0 10.0" id="1" type="sphere"/>
12+
</geometry>
13+
<settings>
14+
<run_mode>eigenvalue</run_mode>
15+
<particles>1000</particles>
16+
<batches>10</batches>
17+
<inactive>5</inactive>
18+
<source particle="neutron" strength="1.0" type="independent">
19+
<space type="box">
20+
<parameters>-4 -4 -4 4 4 4</parameters>
21+
</space>
22+
</source>
23+
<stride>1529170</stride>
24+
</settings>
25+
</model>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
k-combined:
2+
2.978080E-01 6.106774E-03

tests/regression_tests/stride/test.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pytest
2+
import openmc
3+
4+
from tests.testing_harness import PyAPITestHarness
5+
6+
7+
@pytest.fixture
8+
def model():
9+
u = openmc.Material()
10+
u.add_nuclide('U235', 1.0)
11+
u.set_density('g/cm3', 4.5)
12+
sph = openmc.Sphere(r=10.0, boundary_type='vacuum')
13+
cell = openmc.Cell(fill=u, region=-sph)
14+
model = openmc.Model()
15+
model.geometry = openmc.Geometry([cell])
16+
17+
model.settings.batches = 10
18+
model.settings.inactive = 5
19+
model.settings.particles = 1000
20+
model.settings.stride = 1_529_170
21+
model.settings.source = openmc.IndependentSource(
22+
space=openmc.stats.Box([-4, -4, -4], [4, 4, 4])
23+
)
24+
return model
25+
26+
27+
def test_seed(model):
28+
harness = PyAPITestHarness('statepoint.10.h5', model)
29+
harness.main()

0 commit comments

Comments
 (0)