mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 22:26:08 -04:00
Merge feef8b1495 into 61c8a59cff
This commit is contained in:
commit
340bb127e7
23 changed files with 875 additions and 194 deletions
|
|
@ -11,6 +11,7 @@ Simple Models
|
|||
:template: myfunction.rst
|
||||
|
||||
openmc.examples.slab_mg
|
||||
openmc.examples.sphere_with_shielded_pocket
|
||||
|
||||
Reactor Models
|
||||
--------------
|
||||
|
|
|
|||
|
|
@ -642,13 +642,11 @@ model to use these multigroup cross sections. An example is given below::
|
|||
model.convert_to_multigroup(
|
||||
method="material_wise",
|
||||
groups="CASMO-2",
|
||||
nparticles=2000,
|
||||
overwrite_mgxs_library=False,
|
||||
mgxs_path="mgxs.h5",
|
||||
correction=None,
|
||||
source_energy=None,
|
||||
temperatures=None,
|
||||
temperature_settings=None
|
||||
temperatures=None
|
||||
)
|
||||
|
||||
The most important parameter to set is the ``method`` parameter, which can be
|
||||
|
|
@ -672,7 +670,9 @@ of these methods is given below:
|
|||
both spatial and resonance self shielding effects
|
||||
- * Potentially slower as the full geometry must be run
|
||||
* If a material is only present far from the source and doesn't get tallied
|
||||
to in the CE simulation, the MGXS will be zero for that material.
|
||||
to in the CE simulation, the MGXS will be zero for that material. This
|
||||
can be mitigated by supplying weight windows via the generation
|
||||
settings (see :ref:`mgxs_bootstrap`).
|
||||
* - ``stochastic_slab``
|
||||
- * Medium Fidelity
|
||||
* Runs a CE simulation with a greatly simplified geometry, where materials
|
||||
|
|
@ -693,12 +693,20 @@ of these methods is given below:
|
|||
|
||||
When selecting a non-default energy group structure, you can manually define
|
||||
group boundaries or specify the name of a known group structure (a list of which
|
||||
can be found at :data:`openmc.mgxs.GROUP_STRUCTURES`). The ``nparticles``
|
||||
parameter can be adjusted upward to improve the fidelity of the generated cross
|
||||
section library. The ``correction`` parameter can be set to ``"P0"`` to enable
|
||||
P0 transport correction. The ``overwrite_mgxs_library`` parameter can be set to
|
||||
``True`` to overwrite an existing MGXS library file, or ``False`` to skip
|
||||
generation and use an existing library file.
|
||||
can be found at :data:`openmc.mgxs.GROUP_STRUCTURES`). The ``correction``
|
||||
parameter can be set to ``"P0"`` to enable P0 transport correction. The
|
||||
``overwrite_mgxs_library`` parameter can be set to ``True`` to overwrite an
|
||||
existing MGXS library file, or ``False`` to skip generation and use an existing
|
||||
library file.
|
||||
|
||||
The continuous energy simulations used to generate the cross section library
|
||||
can be customized via the ``settings`` parameter. Only the attributes that are
|
||||
populated on the passed :class:`openmc.Settings` object override the
|
||||
generation defaults, so a sparse object adjusts just the fields you set. For
|
||||
example, the number of particles per batch (2,000 by default) can be increased
|
||||
to improve the fidelity of the generated cross section library as::
|
||||
|
||||
model.convert_to_multigroup(settings=openmc.Settings(particles=100_000))
|
||||
|
||||
.. note::
|
||||
MGXS transport correction (via setting the ``correction`` parameter in the
|
||||
|
|
@ -739,12 +747,10 @@ The ``temperatures`` parameter can be provided if temperature-dependent
|
|||
multi-group cross sections are desired for multi-physics simulations. An
|
||||
individual cross section generation calculation is run for each temperature
|
||||
provided, where the materials in the model are set to the temperature. The
|
||||
temperature settings used during cross section generation can be specified with the
|
||||
``temperature_settings`` parameter. If no ``temperature_settings`` are provided,
|
||||
the settings contained in the model will be used. The valid keys and values in the
|
||||
``temperature_settings`` dictionary are identical to
|
||||
:attr:`openmc.Settings.temperature_settings`; more information can be found in
|
||||
:class:`openmc.Settings` . This approach yields isothermal cross section interpolation
|
||||
temperature settings used during cross section generation default to those
|
||||
contained in the model and can be customized by setting
|
||||
:attr:`openmc.Settings.temperature` on the object passed via the ``settings``
|
||||
parameter. This approach yields isothermal cross section interpolation
|
||||
tables, which can be inaccurate for systems with large differences between temperatures
|
||||
in each material (often the case in fission reactors). If a more sophisticated
|
||||
temperature-dependence is required, we recommend generating cross sections manually.
|
||||
|
|
@ -757,6 +763,50 @@ simulation, and if more fidelity is needed the user may wish to follow the
|
|||
instructions below or experiment with transport correction techniques to improve
|
||||
the fidelity of the generated MGXS data.
|
||||
|
||||
.. _mgxs_bootstrap:
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Bootstrapping Material-Wise MGXS with Weight Windows
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``"material_wise"`` method runs a continuous energy simulation of the
|
||||
original geometry, so it produces the highest fidelity cross sections of the
|
||||
three methods. However, it has a notable weakness: if a material only appears
|
||||
far from the source (for example, a detector or structural material located
|
||||
outside a thick shield), an analog continuous energy simulation may be unable to
|
||||
transport any particles to that material. No tallies are scored there, and the
|
||||
resulting cross sections for that material are zero. This situation is common in
|
||||
shielding problems.
|
||||
|
||||
This limitation can be overcome by "bootstrapping" the cross section generation
|
||||
with weight windows. The idea is to first cheaply produce a set of weight
|
||||
windows that cover the entire problem and then reuse them to push particles into
|
||||
the far regions during the higher fidelity ``"material_wise"`` solve. The weight
|
||||
windows are generated using the ``"stochastic_slab"`` method (which produces
|
||||
cross sections for *all* materials regardless of their location) together with
|
||||
the random ray solver and a :class:`~openmc.WeightWindowGenerator`, exactly as
|
||||
described in the :ref:`FW-CADIS user guide <usersguide_fw_cadis>`. The resulting
|
||||
``weight_windows.h5`` file is then supplied to a second, higher fidelity
|
||||
``"material_wise"`` cross section generation by setting ``weight_windows_file``
|
||||
on the generation settings::
|
||||
|
||||
# First, generate weight windows with the stochastic slab method and random
|
||||
# ray (see the FW-CADIS user guide), producing a weight_windows.h5 file.
|
||||
...
|
||||
|
||||
# Then, bootstrap a higher fidelity material-wise library, applying those
|
||||
# weight windows during the continuous energy solve so that particles can
|
||||
# reach materials far from the source.
|
||||
settings = openmc.Settings(weight_windows_file="weight_windows.h5")
|
||||
model.convert_to_multigroup(settings=settings, overwrite_mgxs_library=True)
|
||||
|
||||
A weight windows file on the generation settings is only used with the
|
||||
``"material_wise"`` method, as the ``"stochastic_slab"`` and
|
||||
``"infinite_medium"`` methods use simplified surrogate geometries that are
|
||||
incompatible with a weight window mesh defined over the original geometry (and
|
||||
do not need weight windows, since they already tally all materials). A warning
|
||||
is issued and the file is ignored if it is supplied to another method.
|
||||
|
||||
~~~~~~~~~~~~
|
||||
The Hard Way
|
||||
~~~~~~~~~~~~
|
||||
|
|
@ -1075,9 +1125,9 @@ The adjoint flux random ray solver mode can be enabled as::
|
|||
|
||||
settings.random_ray['adjoint'] = True
|
||||
|
||||
When enabled, OpenMC will first run a forward transport simulation if there are
|
||||
no user-specified adjoint sources present, followed by an adjoint transport
|
||||
simulation. Fixed adjoint sources can be specified on the
|
||||
When enabled, OpenMC will first run a forward transport simulation if there are
|
||||
no user-specified adjoint sources present, followed by an adjoint transport
|
||||
simulation. Fixed adjoint sources can be specified on the
|
||||
:attr:`openmc.Settings.random_ray` dictionary as follows::
|
||||
|
||||
# Geometry definition
|
||||
|
|
@ -1090,21 +1140,21 @@ simulation. Fixed adjoint sources can be specified on the
|
|||
energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths)
|
||||
|
||||
adj_source = openmc.IndependentSource(
|
||||
energy=energy_distribution,
|
||||
energy=energy_distribution,
|
||||
constraints={'domains': [detector_cell]}
|
||||
)
|
||||
|
||||
# Add to random_ray dict
|
||||
settings.random_ray['adjoint_source'] = adj_source
|
||||
|
||||
The same constraints apply to the user-defined adjoint source as to the forward
|
||||
source, described in the :ref:`Fixed Source and Eigenvalue section
|
||||
<usersguide_random_ray_run_modes>`. If this source is not provided, a forward
|
||||
solve must take place to compute the adjoint external source when a forward
|
||||
external source is present in the problem. Simulation settings (e.g., number of
|
||||
rays, batches, etc.) will be identical for both calculations. At the
|
||||
conclusion of the run, all results (e.g., tallies, plots, etc.) will be
|
||||
derived from the adjoint flux rather than the forward flux but are not labeled
|
||||
The same constraints apply to the user-defined adjoint source as to the forward
|
||||
source, described in the :ref:`Fixed Source and Eigenvalue section
|
||||
<usersguide_random_ray_run_modes>`. If this source is not provided, a forward
|
||||
solve must take place to compute the adjoint external source when a forward
|
||||
external source is present in the problem. Simulation settings (e.g., number of
|
||||
rays, batches, etc.) will be identical for both calculations. At the
|
||||
conclusion of the run, all results (e.g., tallies, plots, etc.) will be
|
||||
derived from the adjoint flux rather than the forward flux but are not labeled
|
||||
any differently. When an initial forward solve is performed (i.e., when no
|
||||
user-specified adjoint source is present), its output files are also written to
|
||||
disk with a ``forward`` infix, so they are not overwritten by the subsequent
|
||||
|
|
@ -1116,10 +1166,10 @@ generating FW-CADIS weight windows, no weight window file is written for the
|
|||
forward solve, as only the final adjoint-derived weight windows are meaningful.
|
||||
|
||||
.. note::
|
||||
Use of the automated
|
||||
:ref:`FW-CADIS weight window generator<usersguide_fw_cadis>` is not
|
||||
currently compatible with user-defined adjoint sources. Instead, the
|
||||
initial forward calculation is used to assign "forward-weighted" adjoint
|
||||
Use of the automated
|
||||
:ref:`FW-CADIS weight window generator<usersguide_fw_cadis>` is not
|
||||
currently compatible with user-defined adjoint sources. Instead, the
|
||||
initial forward calculation is used to assign "forward-weighted" adjoint
|
||||
sources to the tally regions of interest.
|
||||
|
||||
---------------------------------------
|
||||
|
|
|
|||
|
|
@ -75,6 +75,19 @@ constexpr double ZERO_FLUX_CUTOFF {1e-22};
|
|||
// value will be converted to pure void.
|
||||
constexpr double MINIMUM_MACRO_XS {1e-6};
|
||||
|
||||
// Relative dead band applied to weight window comparisons: particles split
|
||||
// only above upper * (1 + tol) and roulette only below lower * (1 - tol).
|
||||
// Weight window arithmetic can land a particle's weight exactly back on a
|
||||
// bound value (e.g., a roulette survivor is assigned survival_ratio * lower
|
||||
// and a later split divides that back down), in which case the branch taken
|
||||
// would be decided by the last ulp of the bound. Since window data carries
|
||||
// ulp-level noise from non-associative parallel reductions in the solver that
|
||||
// generated it, transport results would otherwise be chaotically sensitive to
|
||||
// bit-level differences in the weight window file. Treating weights within
|
||||
// the band as inside the window is statistically negligible, and weight
|
||||
// window games are unbiased regardless of where the thresholds sit.
|
||||
constexpr double WEIGHT_WINDOW_REL_TOL {1e-9};
|
||||
|
||||
// ============================================================================
|
||||
// MATH AND PHYSICAL CONSTANTS
|
||||
|
||||
|
|
|
|||
|
|
@ -1314,17 +1314,17 @@ def random_ray_three_region_cube() -> openmc.Model:
|
|||
def random_ray_three_region_cube_with_detectors() -> openmc.Model:
|
||||
"""Create a three region cube model with two external tally regions.
|
||||
|
||||
This is an adaptation of the simple monoenergetic problem of a cube with
|
||||
three concentric cubic regions. The innermost region is near void (with
|
||||
Sigma_t around 10^-5) and contains an external isotropic source term, the
|
||||
middle region is a mild scatterer (with Sigma_t around 10^-3), and the
|
||||
outer region of the cube is a scatterer and absorber (with Sigma_t around
|
||||
This is an adaptation of the simple monoenergetic problem of a cube with
|
||||
three concentric cubic regions. The innermost region is near void (with
|
||||
Sigma_t around 10^-5) and contains an external isotropic source term, the
|
||||
middle region is a mild scatterer (with Sigma_t around 10^-3), and the
|
||||
outer region of the cube is a scatterer and absorber (with Sigma_t around
|
||||
1).
|
||||
|
||||
Two cubic "detector" regions are found outside this geometry, one along the
|
||||
y-axis near z=0, and the other in the upper right corner of the system.
|
||||
The size of each detector is scaled to be equal to that of the source
|
||||
region. The model returned by this function contains cell tallies on each
|
||||
Two cubic "detector" regions are found outside this geometry, one along the
|
||||
y-axis near z=0, and the other in the upper right corner of the system.
|
||||
The size of each detector is scaled to be equal to that of the source
|
||||
region. The model returned by this function contains cell tallies on each
|
||||
detector.
|
||||
|
||||
Returns
|
||||
|
|
@ -1498,29 +1498,29 @@ def random_ray_three_region_cube_with_detectors() -> openmc.Model:
|
|||
fill=absorber_mat,
|
||||
region=detector2_region
|
||||
)
|
||||
|
||||
|
||||
external_x = (
|
||||
+x_high & +y_low & +z_low & -x_outer &
|
||||
+x_high & +y_low & +z_low & -x_outer &
|
||||
((-y_outer & -z_high) | (-y_high & +z_high & -z_outer))
|
||||
)
|
||||
external_y = (
|
||||
+y_high & -y_outer &
|
||||
+y_high & -y_outer &
|
||||
(
|
||||
(+detector1_right & -x_high & +z_low & -z_outer) |
|
||||
(-detector1_right & +x_low & +detector1_top & -z_outer) |
|
||||
(+detector1_right & -x_high & +z_low & -z_outer) |
|
||||
(-detector1_right & +x_low & +detector1_top & -z_outer) |
|
||||
(+x_high & -x_outer & +z_low & -z_high)
|
||||
)
|
||||
)
|
||||
external_z = (
|
||||
+x_low & +y_low & +z_high & -z_outer &
|
||||
+x_low & +y_low & +z_high & -z_outer &
|
||||
((-y_outer & -x_high) | (-y_high & +x_high & -x_outer))
|
||||
)
|
||||
external_cell = openmc.Cell(fill=cavity_mat,
|
||||
region=(external_x | external_y | external_z),
|
||||
external_cell = openmc.Cell(fill=cavity_mat,
|
||||
region=(external_x | external_y | external_z),
|
||||
name='outside cube')
|
||||
|
||||
root = openmc.Universe(
|
||||
name='root universe',
|
||||
name='root universe',
|
||||
cells=[cube_domain, detector1, detector2, external_cell]
|
||||
)
|
||||
|
||||
|
|
@ -1604,8 +1604,8 @@ def random_ray_three_region_cube_with_detectors() -> openmc.Model:
|
|||
source_tally.estimator = estimator
|
||||
|
||||
# Instantiate a Tallies collection and export to XML
|
||||
tallies = openmc.Tallies([detector1_tally,
|
||||
detector2_tally,
|
||||
tallies = openmc.Tallies([detector1_tally,
|
||||
detector2_tally,
|
||||
absorber_tally,
|
||||
cavity_tally,
|
||||
source_tally])
|
||||
|
|
@ -1619,3 +1619,116 @@ def random_ray_three_region_cube_with_detectors() -> openmc.Model:
|
|||
model.tallies = tallies
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def sphere_with_shielded_pocket() -> openmc.Model:
|
||||
"""Create a continuous energy deep-shielding model with a far detector pocket.
|
||||
|
||||
A concrete sphere is centered at the origin. A 2 MeV isotropic neutron
|
||||
source sits in a small air cavity just inside the sphere surface on the -x
|
||||
side, and a small steel pocket is embedded flush with the surface on the
|
||||
+x axis, so roughly a meter of concrete separates the source from the
|
||||
pocket while only a few centimeters of concrete back the cavity. The
|
||||
sphere is enclosed in a vacuum-bounded box, with a void gap in between, so
|
||||
that solvers which sample uniformly over a rectangular domain (e.g.,
|
||||
random ray) can be applied directly. The geometry is designed for testing
|
||||
weight window and variance reduction workflows:
|
||||
|
||||
- The probability that an analog source neutron reaches the steel pocket is
|
||||
~4e-5 (the product of the concrete attenuation and the pocket's small
|
||||
solid angle), so an analog simulation with a few hundred histories
|
||||
essentially never tallies the steel, while even crude global weight
|
||||
windows allow particles to reach it reliably.
|
||||
- Because the cavity sits near the surface, deep shielding (and thus a wide
|
||||
weight window dynamic range) exists only within the small solid angle
|
||||
subtended by the pocket, which keeps weight window splitting cheap and
|
||||
convergent and the whole model fast enough for regression testing.
|
||||
|
||||
Returns
|
||||
-------
|
||||
model : openmc.Model
|
||||
A deep-shielding model with a steel pocket behind a thick concrete
|
||||
shield
|
||||
|
||||
"""
|
||||
model = openmc.Model()
|
||||
|
||||
###########################################################################
|
||||
# Materials (few nuclides, to keep data loading cheap in multi-solve tests)
|
||||
|
||||
air = openmc.Material(name='Air')
|
||||
air.set_density('g/cm3', 0.001225)
|
||||
air.add_nuclide('N14', 0.79, 'ao')
|
||||
air.add_nuclide('O16', 0.21, 'ao')
|
||||
|
||||
concrete = openmc.Material(name='Concrete')
|
||||
concrete.set_density('g/cm3', 2.3)
|
||||
concrete.add_nuclide('H1', 0.168759, 'ao')
|
||||
concrete.add_nuclide('O16', 0.562489, 'ao')
|
||||
concrete.add_nuclide('Si28', 0.203031, 'ao')
|
||||
concrete.add_nuclide('Ca40', 0.044849, 'ao')
|
||||
concrete.add_nuclide('Al27', 0.020872, 'ao')
|
||||
|
||||
steel = openmc.Material(name='Steel')
|
||||
steel.set_density('g/cm3', 7.87)
|
||||
steel.add_nuclide('Fe56', 1.0)
|
||||
|
||||
###########################################################################
|
||||
# Geometry
|
||||
|
||||
# ~92 cm of concrete separates the cavity from the pocket face, while only
|
||||
# ~6 cm of concrete backs the cavity on the -x side, so deep shielding is
|
||||
# confined to the solid angle subtended by the pocket.
|
||||
r_sphere = 66.0
|
||||
box_half_width = 70.0
|
||||
cavity_center_x = -54.0
|
||||
cavity_half_width = 6.0
|
||||
pocket_inner_face = 44.0
|
||||
pocket_half_width = 4.0
|
||||
|
||||
sphere = openmc.Sphere(r=r_sphere)
|
||||
outer_box = openmc.model.RectangularParallelepiped(
|
||||
-box_half_width, box_half_width,
|
||||
-box_half_width, box_half_width,
|
||||
-box_half_width, box_half_width, boundary_type='vacuum')
|
||||
cavity_box = openmc.model.RectangularParallelepiped(
|
||||
cavity_center_x - cavity_half_width, cavity_center_x + cavity_half_width,
|
||||
-cavity_half_width, cavity_half_width,
|
||||
-cavity_half_width, cavity_half_width)
|
||||
# The pocket box extends past the sphere surface and is clipped by it, so
|
||||
# the pocket sits flush with (and just inside) the outer surface.
|
||||
pocket_box = openmc.model.RectangularParallelepiped(
|
||||
pocket_inner_face, r_sphere + 1.0,
|
||||
-pocket_half_width, pocket_half_width,
|
||||
-pocket_half_width, pocket_half_width)
|
||||
|
||||
cavity_cell = openmc.Cell(name='cavity', fill=air, region=-cavity_box)
|
||||
pocket_cell = openmc.Cell(name='pocket', fill=steel,
|
||||
region=-pocket_box & -sphere)
|
||||
concrete_cell = openmc.Cell(
|
||||
name='concrete', fill=concrete,
|
||||
region=-sphere & +cavity_box & ~(-pocket_box & -sphere))
|
||||
void_cell = openmc.Cell(name='void', region=+sphere & -outer_box)
|
||||
|
||||
model.geometry = openmc.Geometry(
|
||||
[cavity_cell, pocket_cell, concrete_cell, void_cell])
|
||||
|
||||
###########################################################################
|
||||
# Source and settings
|
||||
|
||||
source = openmc.IndependentSource()
|
||||
source.space = openmc.stats.Box(
|
||||
[cavity_center_x - cavity_half_width,
|
||||
-cavity_half_width, -cavity_half_width],
|
||||
[cavity_center_x + cavity_half_width,
|
||||
cavity_half_width, cavity_half_width])
|
||||
source.constraints = {'domains': [cavity_cell]}
|
||||
source.angle = openmc.stats.Isotropic()
|
||||
source.energy = openmc.stats.delta_function(2.0e6)
|
||||
|
||||
model.settings.run_mode = 'fixed source'
|
||||
model.settings.source = source
|
||||
model.settings.particles = 1000
|
||||
model.settings.batches = 10
|
||||
|
||||
return model
|
||||
|
|
|
|||
|
|
@ -1898,12 +1898,10 @@ class Model:
|
|||
|
||||
Parameters
|
||||
----------
|
||||
model : openmc.Model
|
||||
The model to generate the MGXS library from.
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
mgxs_path : str
|
||||
Filename for the MGXS HDF5 file.
|
||||
correction : str
|
||||
Transport correction to apply to the MGXS. Options are None and
|
||||
"P0".
|
||||
|
|
@ -2055,11 +2053,10 @@ class Model:
|
|||
def _isothermal_infinite_media_mgxs(
|
||||
material: openmc.Material,
|
||||
groups: openmc.mgxs.EnergyGroups,
|
||||
nparticles: int,
|
||||
settings: openmc.Settings,
|
||||
correction: str | None,
|
||||
directory: PathLike,
|
||||
source: openmc.IndependentSource,
|
||||
temperature_settings: dict,
|
||||
temperature: float | None = None,
|
||||
) -> openmc.XSdata:
|
||||
"""Generate a single MGXS set for one material, where the geometry is an
|
||||
|
|
@ -2071,8 +2068,9 @@ class Model:
|
|||
The material to generate MGXS for
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
settings : openmc.Settings
|
||||
Settings for the generation run, used verbatim except for the
|
||||
fields owned by the infinite medium method.
|
||||
correction : str
|
||||
Transport correction to apply to the MGXS. Options are None and
|
||||
"P0".
|
||||
|
|
@ -2080,10 +2078,6 @@ class Model:
|
|||
Directory to run the simulation in, so as to contain XML files.
|
||||
source : openmc.IndependentSource
|
||||
Source to use when generating MGXS.
|
||||
temperature_settings : dict
|
||||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
temperature : float, optional
|
||||
The isothermal temperature value to apply to the material. If not specified,
|
||||
defaults to the temperature in the material.
|
||||
|
|
@ -2100,18 +2094,13 @@ class Model:
|
|||
if temperature is not None:
|
||||
model.materials[-1].temperature = temperature
|
||||
|
||||
# Settings
|
||||
model.settings.batches = 100
|
||||
model.settings.particles = nparticles
|
||||
|
||||
# The provided settings are used verbatim, except for the fields
|
||||
# owned by the infinite medium method
|
||||
model.settings = copy.deepcopy(settings)
|
||||
model.settings.source = source
|
||||
|
||||
model.settings.run_mode = 'fixed source'
|
||||
model.settings.create_fission_neutrons = False
|
||||
|
||||
model.settings.output = {'summary': True, 'tallies': False}
|
||||
model.settings.temperature = temperature_settings
|
||||
|
||||
# Geometry
|
||||
box = openmc.model.RectangularPrism(
|
||||
100000.0, 100000.0, boundary_type='reflective')
|
||||
|
|
@ -2133,13 +2122,12 @@ class Model:
|
|||
def _generate_infinite_medium_mgxs(
|
||||
self,
|
||||
groups: openmc.mgxs.EnergyGroups,
|
||||
nparticles: int,
|
||||
settings: openmc.Settings,
|
||||
mgxs_path: PathLike,
|
||||
correction: str | None,
|
||||
directory: PathLike,
|
||||
source_energy: openmc.stats.Univariate | None = None,
|
||||
temperatures: Sequence[float] | None = None,
|
||||
temperature_settings: dict | None = None,
|
||||
) -> None:
|
||||
"""Generate a MGXS library by running multiple OpenMC simulations, each
|
||||
representing an infinite medium simulation of a single isolated
|
||||
|
|
@ -2168,8 +2156,9 @@ class Model:
|
|||
----------
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
settings : openmc.Settings
|
||||
Settings for the generation run(s), used verbatim except for the
|
||||
fields owned by the infinite medium method.
|
||||
mgxs_path : str
|
||||
Filename for the MGXS HDF5 file.
|
||||
correction : str
|
||||
|
|
@ -2184,10 +2173,6 @@ class Model:
|
|||
A list of temperatures to generate MGXS at. Each infinite material region
|
||||
is isothermal at a given temperature data point for cross
|
||||
section generation.
|
||||
temperature_settings : dict, optional
|
||||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
"""
|
||||
|
||||
src = self._create_mgxs_sources(
|
||||
|
|
@ -2196,23 +2181,16 @@ class Model:
|
|||
source_energy=source_energy
|
||||
)
|
||||
|
||||
temp_settings = {}
|
||||
if temperature_settings is None:
|
||||
temp_settings = self.settings.temperature
|
||||
else:
|
||||
temp_settings = temperature_settings
|
||||
|
||||
if temperatures is None:
|
||||
mgxs_sets = []
|
||||
for material in self.materials:
|
||||
xs_data = Model._isothermal_infinite_media_mgxs(
|
||||
material,
|
||||
groups,
|
||||
nparticles,
|
||||
settings,
|
||||
correction,
|
||||
directory,
|
||||
src,
|
||||
temp_settings
|
||||
src
|
||||
)
|
||||
mgxs_sets.append(xs_data)
|
||||
|
||||
|
|
@ -2230,11 +2208,10 @@ class Model:
|
|||
xs_data = Model._isothermal_infinite_media_mgxs(
|
||||
material,
|
||||
groups,
|
||||
nparticles,
|
||||
settings,
|
||||
correction,
|
||||
directory,
|
||||
src,
|
||||
temp_settings,
|
||||
temperature
|
||||
)
|
||||
raw_mgxs_sets[temperature].append(xs_data)
|
||||
|
|
@ -2332,11 +2309,10 @@ class Model:
|
|||
def _isothermal_stochastic_slab_mgxs(
|
||||
stoch_geom: openmc.Geometry,
|
||||
groups: openmc.mgxs.EnergyGroups,
|
||||
nparticles: int,
|
||||
settings: openmc.Settings,
|
||||
correction: str | None,
|
||||
directory: PathLike,
|
||||
source: openmc.IndependentSource,
|
||||
temperature_settings: dict,
|
||||
temperature: float | None = None,
|
||||
) -> dict[str, openmc.XSdata]:
|
||||
"""Generate MGXS assuming a stochastic "sandwich" of materials in a layered
|
||||
|
|
@ -2349,8 +2325,9 @@ class Model:
|
|||
The stochastic slab geometry.
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
settings : openmc.Settings
|
||||
Settings for the generation run, used verbatim except for the
|
||||
fields owned by the stochastic slab method.
|
||||
correction : str
|
||||
Transport correction to apply to the MGXS. Options are None and
|
||||
"P0".
|
||||
|
|
@ -2358,10 +2335,6 @@ class Model:
|
|||
Directory to run the simulation in, so as to contain XML files.
|
||||
source : openmc.IndependentSource
|
||||
Source to use when generating MGXS.
|
||||
temperature_settings : dict
|
||||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
temperature : float, optional
|
||||
The isothermal temperature value to apply to the materials in the
|
||||
slab. If not specified, defaults to the temperature in the materials.
|
||||
|
|
@ -2379,21 +2352,13 @@ class Model:
|
|||
for material in model.geometry.get_all_materials().values():
|
||||
material.temperature = temperature
|
||||
|
||||
# Settings
|
||||
model.settings.batches = 200
|
||||
model.settings.inactive = 100
|
||||
model.settings.particles = nparticles
|
||||
model.settings.output = {'summary': True, 'tallies': False}
|
||||
model.settings.temperature = temperature_settings
|
||||
|
||||
# Define the sources
|
||||
# The provided settings are used verbatim, except for the fields
|
||||
# owned by the stochastic slab method
|
||||
model.settings = copy.deepcopy(settings)
|
||||
model.settings.source = source
|
||||
|
||||
model.settings.run_mode = 'fixed source'
|
||||
model.settings.create_fission_neutrons = False
|
||||
|
||||
model.settings.output = {'summary': True, 'tallies': False}
|
||||
|
||||
# Generate MGXS
|
||||
mgxs_lib = Model._auto_generate_mgxs_lib(
|
||||
model, groups, correction, directory)
|
||||
|
|
@ -2414,13 +2379,12 @@ class Model:
|
|||
def _generate_stochastic_slab_mgxs(
|
||||
self,
|
||||
groups: openmc.mgxs.EnergyGroups,
|
||||
nparticles: int,
|
||||
settings: openmc.Settings,
|
||||
mgxs_path: PathLike,
|
||||
correction: str | None,
|
||||
directory: PathLike,
|
||||
source_energy: openmc.stats.Univariate | None = None,
|
||||
temperatures: Sequence[float] | None = None,
|
||||
temperature_settings: dict | None = None,
|
||||
) -> None:
|
||||
"""Generate MGXS assuming a stochastic "sandwich" of materials in a layered
|
||||
slab geometry. While geometry-specific spatial shielding effects are not
|
||||
|
|
@ -2438,8 +2402,9 @@ class Model:
|
|||
----------
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
settings : openmc.Settings
|
||||
Settings for the generation run(s), used verbatim except for the
|
||||
fields owned by the stochastic slab method.
|
||||
mgxs_path : str
|
||||
Filename for the MGXS HDF5 file.
|
||||
correction : str
|
||||
|
|
@ -2468,10 +2433,6 @@ class Model:
|
|||
A list of temperatures to generate MGXS at. Each infinite material region
|
||||
is isothermal at a given temperature data point for cross
|
||||
section generation.
|
||||
temperature_settings : dict, optional
|
||||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
"""
|
||||
|
||||
# Stochastic slab geometry
|
||||
|
|
@ -2484,21 +2445,14 @@ class Model:
|
|||
source_energy=source_energy
|
||||
)
|
||||
|
||||
temp_settings = {}
|
||||
if temperature_settings is None:
|
||||
temp_settings = self.settings.temperature
|
||||
else:
|
||||
temp_settings = temperature_settings
|
||||
|
||||
if temperatures is None:
|
||||
mgxs_sets = Model._isothermal_stochastic_slab_mgxs(
|
||||
geo,
|
||||
groups,
|
||||
nparticles,
|
||||
settings,
|
||||
correction,
|
||||
directory,
|
||||
src,
|
||||
temp_settings
|
||||
src
|
||||
).values()
|
||||
|
||||
# Write the file to disk.
|
||||
|
|
@ -2513,11 +2467,10 @@ class Model:
|
|||
raw_mgxs_sets[temperature] = Model._isothermal_stochastic_slab_mgxs(
|
||||
geo,
|
||||
groups,
|
||||
nparticles,
|
||||
settings,
|
||||
correction,
|
||||
directory,
|
||||
src,
|
||||
temp_settings,
|
||||
temperature
|
||||
)
|
||||
|
||||
|
|
@ -2539,10 +2492,9 @@ class Model:
|
|||
def _isothermal_materialwise_mgxs(
|
||||
input_model: openmc.Model,
|
||||
groups: openmc.mgxs.EnergyGroups,
|
||||
nparticles: int,
|
||||
settings: openmc.Settings,
|
||||
correction: str | None,
|
||||
directory: PathLike,
|
||||
temperature_settings: dict,
|
||||
temperature: float | None = None,
|
||||
) -> dict[str, openmc.XSdata]:
|
||||
"""Generate a material-wise MGXS library for the model by running the
|
||||
|
|
@ -2559,17 +2511,13 @@ class Model:
|
|||
The model to use when computing material-wise MGXS.
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
settings : openmc.Settings
|
||||
Settings for the generation run, used verbatim.
|
||||
correction : str
|
||||
Transport correction to apply to the MGXS. Options are None and
|
||||
"P0".
|
||||
directory : str
|
||||
Directory to run the simulation in, so as to contain XML files.
|
||||
temperature_settings : dict
|
||||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
temperature : float, optional
|
||||
The isothermal temperature value to apply to the materials in the
|
||||
input model. If not specified, defaults to the temperatures in the
|
||||
|
|
@ -2587,12 +2535,8 @@ class Model:
|
|||
for material in model.geometry.get_all_materials().values():
|
||||
material.temperature = temperature
|
||||
|
||||
# Settings
|
||||
model.settings.batches = 200
|
||||
model.settings.inactive = 100
|
||||
model.settings.particles = nparticles
|
||||
model.settings.output = {'summary': True, 'tallies': False}
|
||||
model.settings.temperature = temperature_settings
|
||||
# The provided settings are used verbatim
|
||||
model.settings = copy.deepcopy(settings)
|
||||
|
||||
# Generate MGXS
|
||||
mgxs_lib = Model._auto_generate_mgxs_lib(
|
||||
|
|
@ -2614,12 +2558,11 @@ class Model:
|
|||
def _generate_material_wise_mgxs(
|
||||
self,
|
||||
groups: openmc.mgxs.EnergyGroups,
|
||||
nparticles: int,
|
||||
settings: openmc.Settings,
|
||||
mgxs_path: PathLike,
|
||||
correction: str | None,
|
||||
directory: PathLike,
|
||||
temperatures: Sequence[float] | None = None,
|
||||
temperature_settings: dict | None = None,
|
||||
) -> None:
|
||||
"""Generate a material-wise MGXS library for the model by running the
|
||||
original continuous energy OpenMC simulation of the full material
|
||||
|
|
@ -2635,8 +2578,8 @@ class Model:
|
|||
----------
|
||||
groups : openmc.mgxs.EnergyGroups
|
||||
Energy group structure for the MGXS.
|
||||
nparticles : int
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
settings : openmc.Settings
|
||||
Settings for the generation run(s), used verbatim.
|
||||
mgxs_path : PathLike
|
||||
Filename for the MGXS HDF5 file.
|
||||
correction : str
|
||||
|
|
@ -2648,26 +2591,10 @@ class Model:
|
|||
A list of temperatures to generate MGXS at. Each infinite material region
|
||||
is isothermal at a given temperature data point for cross
|
||||
section generation.
|
||||
temperature_settings : dict, optional
|
||||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
"""
|
||||
temp_settings = {}
|
||||
if temperature_settings is None:
|
||||
temp_settings = self.settings.temperature
|
||||
else:
|
||||
temp_settings = temperature_settings
|
||||
|
||||
if temperatures is None:
|
||||
mgxs_sets = Model._isothermal_materialwise_mgxs(
|
||||
self,
|
||||
groups,
|
||||
nparticles,
|
||||
correction,
|
||||
directory,
|
||||
temp_settings
|
||||
).values()
|
||||
self, groups, settings, correction, directory).values()
|
||||
|
||||
# Write the file to disk.
|
||||
mgxs_file = openmc.MGXSLibrary(energy_groups=groups)
|
||||
|
|
@ -2679,14 +2606,7 @@ class Model:
|
|||
raw_mgxs_sets = {}
|
||||
for temperature in temperatures:
|
||||
raw_mgxs_sets[temperature] = Model._isothermal_materialwise_mgxs(
|
||||
self,
|
||||
groups,
|
||||
nparticles,
|
||||
correction,
|
||||
directory,
|
||||
temp_settings,
|
||||
temperature
|
||||
)
|
||||
self, groups, settings, correction, directory, temperature)
|
||||
|
||||
# Unpack the isothermal XSData objects and build a single XSData object per material.
|
||||
mgxs_sets = []
|
||||
|
|
@ -2706,13 +2626,14 @@ class Model:
|
|||
self,
|
||||
method: str = "material_wise",
|
||||
groups: str | Sequence[float] | openmc.mgxs.EnergyGroups = "CASMO-2",
|
||||
nparticles: int = 2000,
|
||||
nparticles: int | None = None,
|
||||
overwrite_mgxs_library: bool = False,
|
||||
mgxs_path: PathLike = "mgxs.h5",
|
||||
correction: str | None = None,
|
||||
source_energy: openmc.stats.Univariate | None = None,
|
||||
temperatures: Sequence[float] | None = None,
|
||||
temperature_settings: dict | None = None,
|
||||
settings: openmc.Settings | None = None,
|
||||
):
|
||||
"""Convert all materials from continuous energy to multigroup.
|
||||
|
||||
|
|
@ -2732,6 +2653,11 @@ class Model:
|
|||
Defaults to ``"CASMO-2"``.
|
||||
nparticles : int, optional
|
||||
Number of particles to simulate per batch when generating MGXS.
|
||||
Defaults to 2000.
|
||||
|
||||
.. deprecated:: 0.15.4
|
||||
Set ``particles`` on the object passed via the ``settings``
|
||||
argument instead.
|
||||
overwrite_mgxs_library : bool, optional
|
||||
Whether to overwrite an existing MGXS library file.
|
||||
mgxs_path : str, optional
|
||||
|
|
@ -2765,10 +2691,119 @@ class Model:
|
|||
A dictionary of temperature settings to use when generating MGXS.
|
||||
Valid entries for temperature_settings are the same as the valid
|
||||
entries in openmc.Settings.temperature_settings.
|
||||
|
||||
.. deprecated:: 0.15.4
|
||||
Set ``temperature`` on the object passed via the ``settings``
|
||||
argument instead.
|
||||
settings : openmc.Settings, optional
|
||||
Settings overrides for the continuous energy simulation(s) used
|
||||
to generate the MGXS library. Attributes populated on this
|
||||
object take precedence over the generation defaults, so a
|
||||
sparse object such as ``openmc.Settings(particles=100_000)``
|
||||
adjusts just that field (see :meth:`openmc.Settings.update`).
|
||||
The run mode cannot be overridden, as it is determined by the
|
||||
generation method. A ``weight_windows_file`` is applied during
|
||||
``"material_wise"`` generation and ignored with a warning by
|
||||
the other methods; see the user guide for the weight window
|
||||
"bootstrapping" workflow this enables. Cannot be combined with
|
||||
the deprecated ``nparticles`` or ``temperature_settings``
|
||||
arguments.
|
||||
|
||||
.. versionadded:: 0.15.4
|
||||
"""
|
||||
if not isinstance(groups, openmc.mgxs.EnergyGroups):
|
||||
groups = openmc.mgxs.EnergyGroups(groups)
|
||||
|
||||
check_value('method', method,
|
||||
('material_wise', 'stochastic_slab', 'infinite_medium'))
|
||||
if settings is not None:
|
||||
check_type('settings', settings, openmc.Settings)
|
||||
|
||||
# The model may reference its materials only through the geometry.
|
||||
# The materials are converted in place and library-wide attributes
|
||||
# like cross_sections must persist on the model afterwards, so
|
||||
# populate the explicit collection if it is empty (sorted by ID for
|
||||
# reproducibility, since geometry traversal order is arbitrary)
|
||||
if not self.materials:
|
||||
self.materials = openmc.Materials(sorted(
|
||||
self.geometry.get_all_materials().values(),
|
||||
key=lambda mat: mat.id))
|
||||
|
||||
if nparticles is not None or temperature_settings is not None:
|
||||
warnings.warn(
|
||||
'The "nparticles" and "temperature_settings" arguments are '
|
||||
'deprecated. Pass a Settings object with the desired '
|
||||
'attributes via the "settings" argument instead.',
|
||||
FutureWarning)
|
||||
if settings is not None:
|
||||
raise ValueError(
|
||||
'The deprecated "nparticles" and "temperature_settings" '
|
||||
'arguments cannot be combined with the "settings" '
|
||||
'argument.')
|
||||
|
||||
# Resolve the settings for the MGXS generation run(s) in three
|
||||
# layers, with later layers taking precedence: the model's own
|
||||
# settings ("material_wise") or a fresh Settings object (surrogate
|
||||
# methods), then the generation defaults, then any attributes the
|
||||
# user populated on the provided settings.
|
||||
user_settings = settings
|
||||
if method == 'material_wise':
|
||||
settings = copy.deepcopy(self.settings)
|
||||
else:
|
||||
settings = openmc.Settings()
|
||||
settings.temperature = copy.deepcopy(self.settings.temperature)
|
||||
# The surrogate-geometry methods treat fission as capture
|
||||
# (nu-fission is still tallied)
|
||||
settings.create_fission_neutrons = False
|
||||
|
||||
settings.batches = 100 if method == 'infinite_medium' else 200
|
||||
if method != 'infinite_medium':
|
||||
settings.inactive = 100
|
||||
settings.particles = 2000
|
||||
settings.output = {'summary': True, 'tallies': False}
|
||||
if nparticles is not None:
|
||||
settings.particles = nparticles
|
||||
if temperature_settings is not None:
|
||||
settings.temperature = temperature_settings
|
||||
|
||||
if user_settings is not None:
|
||||
# The surrogate-geometry methods construct their own sources
|
||||
if method != "material_wise" and len(user_settings.source) > 0:
|
||||
warnings.warn(
|
||||
'The sources defined in "settings" are ignored by the '
|
||||
f'"{method}" MGXS generation method, which constructs '
|
||||
'its own sources.')
|
||||
settings.update(user_settings)
|
||||
|
||||
# The run mode is the one attribute that cannot be detected as
|
||||
# user-populated (a fresh Settings object defaults it to
|
||||
# 'eigenvalue'), so it is owned by the generation method:
|
||||
# "material_wise" always takes it from the model, while the
|
||||
# surrogate-geometry methods always run in fixed source mode.
|
||||
if method == 'material_wise':
|
||||
settings.run_mode = self.settings.run_mode
|
||||
else:
|
||||
settings.run_mode = 'fixed source'
|
||||
|
||||
# A weight windows file on the generation settings is loaded and
|
||||
# applied (specifying a file turns weight windows on) during the
|
||||
# "material_wise" method's continuous energy simulation of the
|
||||
# original geometry, allowing materials far from the source --
|
||||
# which an analog simulation may struggle to reach -- to still be
|
||||
# tallied, and thus obtain nonzero cross sections. The
|
||||
# "stochastic_slab" and "infinite_medium" methods use simplified
|
||||
# surrogate geometries for which weight windows defined over the
|
||||
# original geometry are neither applicable nor needed.
|
||||
if settings.weight_windows_file is not None and \
|
||||
method != "material_wise":
|
||||
warnings.warn(
|
||||
'The weight windows file set on the generation settings '
|
||||
'is only applicable to the "material_wise" MGXS '
|
||||
f'generation method and will be ignored for the '
|
||||
f'"{method}" method.'
|
||||
)
|
||||
settings.weight_windows_file = None
|
||||
|
||||
# Do all work (including MGXS generation) in a temporary directory
|
||||
# to avoid polluting the working directory with residual XML files
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
|
|
@ -2804,16 +2839,16 @@ class Model:
|
|||
if not Path(mgxs_path).is_file() or overwrite_mgxs_library:
|
||||
if method == "infinite_medium":
|
||||
self._generate_infinite_medium_mgxs(
|
||||
groups, nparticles, mgxs_path, correction, tmpdir, source_energy,
|
||||
temperatures, temperature_settings)
|
||||
groups, settings, mgxs_path, correction, tmpdir,
|
||||
source_energy, temperatures)
|
||||
elif method == "material_wise":
|
||||
self._generate_material_wise_mgxs(
|
||||
groups, nparticles, mgxs_path, correction, tmpdir,
|
||||
temperatures, temperature_settings)
|
||||
groups, settings, mgxs_path, correction, tmpdir,
|
||||
temperatures)
|
||||
elif method == "stochastic_slab":
|
||||
self._generate_stochastic_slab_mgxs(
|
||||
groups, nparticles, mgxs_path, correction, tmpdir, source_energy,
|
||||
temperatures, temperature_settings)
|
||||
groups, settings, mgxs_path, correction, tmpdir,
|
||||
source_energy, temperatures)
|
||||
else:
|
||||
raise ValueError(
|
||||
f'MGXS generation method "{method}" not recognized')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from collections.abc import Iterable, Mapping, MutableSequence, Sequence
|
||||
import copy
|
||||
from enum import Enum
|
||||
import itertools
|
||||
from math import ceil
|
||||
|
|
@ -514,6 +515,51 @@ class Settings:
|
|||
warnings.warn(msg, stacklevel=2)
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def update(self, other: 'Settings'):
|
||||
"""Update this object with all populated attributes of another instance.
|
||||
|
||||
Every attribute of `other` that has been populated -- i.e., that is
|
||||
not None and not an empty collection -- is copied to this object,
|
||||
overwriting any existing value. Attributes that were never set on
|
||||
`other` leave the corresponding attribute of this object untouched.
|
||||
This allows a sparsely-populated Settings object to be applied as a
|
||||
set of overrides on top of fully-populated defaults.
|
||||
|
||||
Note that :attr:`run_mode` always carries a value, so an explicitly
|
||||
assigned 'eigenvalue' run mode cannot be distinguished from the
|
||||
default; the run mode is therefore only copied from `other` when it
|
||||
differs from 'eigenvalue'.
|
||||
|
||||
.. versionadded:: 0.15.4
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : openmc.Settings
|
||||
Settings object whose populated attributes are applied to this
|
||||
object.
|
||||
|
||||
"""
|
||||
cv.check_type('other', other, Settings)
|
||||
for name, value in vars(other).items():
|
||||
# run_mode defaults to 'eigenvalue' rather than to an "unset"
|
||||
# state, so only a non-default value is detectable (see note in
|
||||
# the docstring).
|
||||
if name == '_run_mode':
|
||||
if value is not RunMode.EIGENVALUE:
|
||||
self._run_mode = value
|
||||
continue
|
||||
|
||||
# None or an empty collection means the attribute was never set
|
||||
# on `other` -- the same convention to_xml_element() relies on
|
||||
# to decide which elements to export.
|
||||
if value is None or (hasattr(value, '__len__') and len(value) == 0):
|
||||
continue
|
||||
|
||||
# Values on `other` were already validated by its property
|
||||
# setters; deepcopy so later mutations of `other` cannot leak
|
||||
# into this object.
|
||||
setattr(self, name, copy.deepcopy(value))
|
||||
|
||||
@property
|
||||
def run_mode(self) -> str:
|
||||
return self._run_mode.value
|
||||
|
|
|
|||
|
|
@ -392,6 +392,11 @@ void RandomRaySimulation::simulate()
|
|||
// Begin main simulation timer
|
||||
simulation::time_total.start();
|
||||
|
||||
// Reset per-solve accumulators, as simulate() may run more than once on the
|
||||
// same object (e.g. forward then adjoint when generating weight windows)
|
||||
avg_miss_rate_ = 0.0;
|
||||
total_geometric_intersections_ = 0;
|
||||
|
||||
// Random ray power iteration loop
|
||||
while (simulation::current_batch < settings::n_batches) {
|
||||
// Initialize the current batch
|
||||
|
|
|
|||
|
|
@ -946,6 +946,11 @@ void apply_weight_windows(Particle& p)
|
|||
if (!settings::weight_windows_on)
|
||||
return;
|
||||
|
||||
// Random ray rays are not Monte Carlo particles and must not be biased by
|
||||
// weight windows; the solver generates weight windows but never applies them
|
||||
if (settings::solver_type == SolverType::RANDOM_RAY)
|
||||
return;
|
||||
|
||||
// WW on photon and neutron only
|
||||
if (!p.type().is_neutron() && !p.type().is_photon())
|
||||
return;
|
||||
|
|
@ -1004,14 +1009,25 @@ void apply_weight_window(Particle& p, WeightWindow weight_window)
|
|||
if (p.ww_factor() > 1.0)
|
||||
weight_window.scale(p.ww_factor());
|
||||
|
||||
// if particle's weight is above the weight window split until they are within
|
||||
// the window
|
||||
if (weight > weight_window.upper_weight) {
|
||||
// If the particle's weight is above the weight window, split it until the
|
||||
// resulting particles are within the window. The comparisons use a relative
|
||||
// dead band so that the branch taken is insensitive to bit-level differences
|
||||
// in the window bounds (see WEIGHT_WINDOW_REL_TOL).
|
||||
if (weight > weight_window.upper_weight * (1.0 + WEIGHT_WINDOW_REL_TOL)) {
|
||||
// do not further split the particle if above the limit
|
||||
if (p.n_split() >= settings::max_history_splits)
|
||||
return;
|
||||
|
||||
double n_split = std::ceil(weight / weight_window.upper_weight);
|
||||
// The (1 - WEIGHT_WINDOW_REL_TOL) factor keeps the number of splits
|
||||
// stable when the weight-to-bound ratio sits within rounding of an exact
|
||||
// integer, which the weight window arithmetic itself can produce (e.g., a
|
||||
// roulette survivor assigned weight * max_split, later split against an
|
||||
// upper bound that is an exact multiple of the same lower bound). Ratios
|
||||
// within the dead band of an integer consistently round down; the lower
|
||||
// clamp of 2 preserves the guarantee that the split branch always splits.
|
||||
double n_split =
|
||||
std::max(2.0, std::ceil((1.0 - WEIGHT_WINDOW_REL_TOL) * weight /
|
||||
weight_window.upper_weight));
|
||||
double max_split = weight_window.max_split;
|
||||
n_split = std::min(n_split, max_split);
|
||||
|
||||
|
|
@ -1025,7 +1041,8 @@ void apply_weight_window(Particle& p, WeightWindow weight_window)
|
|||
// remaining weight is applied to current particle
|
||||
p.wgt() = weight / n_split;
|
||||
|
||||
} else if (weight <= weight_window.lower_weight) {
|
||||
} else if (weight <
|
||||
weight_window.lower_weight * (1.0 - WEIGHT_WINDOW_REL_TOL)) {
|
||||
// if the particle weight is below the window, play Russian roulette
|
||||
double weight_survive =
|
||||
std::min(weight * weight_window.max_split, weight_window.survival_weight);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ def test_random_ray_auto_convert(method):
|
|||
|
||||
# Convert to a multi-group model
|
||||
model.convert_to_multigroup(
|
||||
method=method, groups='CASMO-2', nparticles=100,
|
||||
method=method, groups='CASMO-2',
|
||||
settings=openmc.Settings(particles=100),
|
||||
overwrite_mgxs_library=False, mgxs_path="mgxs.h5"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<model>
|
||||
<materials>
|
||||
<material id="1" name="Air">
|
||||
<density value="0.001225" units="g/cm3"/>
|
||||
<nuclide name="N14" ao="0.79"/>
|
||||
<nuclide name="O16" ao="0.21"/>
|
||||
</material>
|
||||
<material id="2" name="Concrete">
|
||||
<density value="2.3" units="g/cm3"/>
|
||||
<nuclide name="H1" ao="0.168759"/>
|
||||
<nuclide name="O16" ao="0.562489"/>
|
||||
<nuclide name="Si28" ao="0.203031"/>
|
||||
<nuclide name="Ca40" ao="0.044849"/>
|
||||
<nuclide name="Al27" ao="0.020872"/>
|
||||
</material>
|
||||
<material id="3" name="Steel">
|
||||
<density value="7.87" units="g/cm3"/>
|
||||
<nuclide name="Fe56" ao="1.0"/>
|
||||
</material>
|
||||
</materials>
|
||||
<geometry>
|
||||
<cell id="1" name="cavity" material="1" region="-9 8 -11 10 -13 12" universe="1"/>
|
||||
<cell id="2" name="pocket" material="3" region="-15 14 -17 16 -19 18 -1" universe="1"/>
|
||||
<cell id="3" name="concrete" material="2" region="-1 (9 | -8 | 11 | -10 | 13 | -12) (15 | -14 | 17 | -16 | 19 | -18 | 1)" universe="1"/>
|
||||
<cell id="4" name="void" material="void" region="1 -3 2 -5 4 -7 6" universe="1"/>
|
||||
<surface id="1" type="sphere" coeffs="0.0 0.0 0.0 66.0"/>
|
||||
<surface id="2" type="x-plane" boundary="vacuum" coeffs="-70.0"/>
|
||||
<surface id="3" type="x-plane" boundary="vacuum" coeffs="70.0"/>
|
||||
<surface id="4" type="y-plane" boundary="vacuum" coeffs="-70.0"/>
|
||||
<surface id="5" type="y-plane" boundary="vacuum" coeffs="70.0"/>
|
||||
<surface id="6" type="z-plane" boundary="vacuum" coeffs="-70.0"/>
|
||||
<surface id="7" type="z-plane" boundary="vacuum" coeffs="70.0"/>
|
||||
<surface id="8" type="x-plane" coeffs="-60.0"/>
|
||||
<surface id="9" type="x-plane" coeffs="-48.0"/>
|
||||
<surface id="10" type="y-plane" coeffs="-6.0"/>
|
||||
<surface id="11" type="y-plane" coeffs="6.0"/>
|
||||
<surface id="12" type="z-plane" coeffs="-6.0"/>
|
||||
<surface id="13" type="z-plane" coeffs="6.0"/>
|
||||
<surface id="14" type="x-plane" coeffs="44.0"/>
|
||||
<surface id="15" type="x-plane" coeffs="67.0"/>
|
||||
<surface id="16" type="y-plane" coeffs="-4.0"/>
|
||||
<surface id="17" type="y-plane" coeffs="4.0"/>
|
||||
<surface id="18" type="z-plane" coeffs="-4.0"/>
|
||||
<surface id="19" type="z-plane" coeffs="4.0"/>
|
||||
</geometry>
|
||||
<settings>
|
||||
<run_mode>fixed source</run_mode>
|
||||
<particles>20</particles>
|
||||
<batches>10</batches>
|
||||
<source type="independent" strength="1.0" particle="neutron">
|
||||
<space type="box">
|
||||
<parameters>-60.0 -6.0 -6.0 -48.0 6.0 6.0</parameters>
|
||||
</space>
|
||||
<angle type="isotropic"/>
|
||||
<energy type="discrete">
|
||||
<parameters>2000000.0 1.0</parameters>
|
||||
</energy>
|
||||
<constraints>
|
||||
<domain_type>cell</domain_type>
|
||||
<domain_ids>1</domain_ids>
|
||||
</constraints>
|
||||
</source>
|
||||
<weight_windows_file>weight_windows.h5</weight_windows_file>
|
||||
<weight_window_checkpoints>
|
||||
<collision>true</collision>
|
||||
<surface>true</surface>
|
||||
</weight_window_checkpoints>
|
||||
<max_history_splits>100000</max_history_splits>
|
||||
</settings>
|
||||
<tallies>
|
||||
<filter id="193" type="cell">
|
||||
<bins>1 2 3 4</bins>
|
||||
</filter>
|
||||
<tally id="405" name="flux">
|
||||
<filters>193</filters>
|
||||
<scores>flux</scores>
|
||||
</tally>
|
||||
</tallies>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
tally 1:
|
||||
9.500337E+01
|
||||
9.709273E+02
|
||||
2.417257E-04
|
||||
1.153221E-08
|
||||
9.046900E+02
|
||||
8.904339E+04
|
||||
1.034722E+02
|
||||
1.134807E+03
|
||||
103
tests/regression_tests/random_ray_auto_convert_bootstrap/test.py
Normal file
103
tests/regression_tests/random_ray_auto_convert_bootstrap/test.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import copy
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import openmc
|
||||
import openmc.mgxs
|
||||
from openmc.examples import sphere_with_shielded_pocket
|
||||
|
||||
from tests.testing_harness import TolerantPyAPITestHarness
|
||||
|
||||
|
||||
GROUPS = 'CASMO-4'
|
||||
|
||||
|
||||
class MGXSTestHarness(TolerantPyAPITestHarness):
|
||||
def _cleanup(self):
|
||||
super()._cleanup()
|
||||
for f in ('mgxs.h5', 'weight_windows.h5'):
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
|
||||
|
||||
def generate_weight_windows(model, mesh):
|
||||
"""Convert a multigroup model to random ray and run a FW-CADIS weight
|
||||
window generation solve, producing weight_windows.h5."""
|
||||
model.convert_to_random_ray()
|
||||
rr = model.settings.random_ray
|
||||
rr['source_region_meshes'] = [(mesh, [model.geometry.root_universe])]
|
||||
rr['distance_inactive'] = 100.0
|
||||
rr['distance_active'] = 400.0
|
||||
rr['source_shape'] = 'flat'
|
||||
rr['sample_method'] = 'halton'
|
||||
rr['volume_estimator'] = 'hybrid'
|
||||
model.settings.particles = 1000
|
||||
model.settings.batches = 20
|
||||
model.settings.inactive = 15
|
||||
model.settings.weight_window_generators = openmc.WeightWindowGenerator(
|
||||
method='fw_cadis', mesh=mesh, max_realizations=20,
|
||||
energy_bounds=openmc.mgxs.EnergyGroups(GROUPS).group_edges)
|
||||
model.run()
|
||||
|
||||
|
||||
def test_random_ray_auto_convert_bootstrap():
|
||||
# Tests the weight window bootstrapped MGXS generation workflow, which
|
||||
# consists of five OpenMC runs: a stochastic_slab MGXS generation, a random
|
||||
# ray FW-CADIS weight window generation, a material_wise MGXS generation
|
||||
# using those weight windows (which reach the steel pocket that an analog
|
||||
# solve essentially never tallies), a second FW-CADIS generation using the
|
||||
# bootstrapped library, and a final continuous energy Monte Carlo solve
|
||||
# using the improved weight windows, whose tallies are compared against
|
||||
# the reference results.
|
||||
openmc.reset_auto_ids()
|
||||
model = sphere_with_shielded_pocket()
|
||||
|
||||
# Used by the continuous energy solves below; these have no effect on the
|
||||
# random ray solves (random ray generates weight windows but never applies
|
||||
# them).
|
||||
model.settings.weight_window_checkpoints = {
|
||||
'collision': True, 'surface': True}
|
||||
model.settings.max_history_splits = 100_000
|
||||
|
||||
# Overlay a ~10 cm source region / weight window mesh on the geometry
|
||||
bbox = model.geometry.bounding_box
|
||||
mesh = openmc.RegularMesh()
|
||||
mesh.dimension = np.round(
|
||||
(np.asarray(bbox.upper_right) - np.asarray(bbox.lower_left)) / 10.0
|
||||
).astype(int)
|
||||
mesh.lower_left = bbox.lower_left
|
||||
mesh.upper_right = bbox.upper_right
|
||||
|
||||
# Generate weight windows covering the whole problem with the
|
||||
# stochastic_slab method and a random ray FW-CADIS solve
|
||||
slab_model = copy.deepcopy(model)
|
||||
slab_model.convert_to_multigroup(
|
||||
method='stochastic_slab', groups=GROUPS,
|
||||
settings=openmc.Settings(particles=50),
|
||||
overwrite_mgxs_library=True, mgxs_path='mgxs.h5')
|
||||
generate_weight_windows(slab_model, mesh)
|
||||
|
||||
# Bootstrap the material-wise MGXS generation with those weight windows,
|
||||
# then regenerate the weight windows from the higher-fidelity library
|
||||
boot_model = copy.deepcopy(model)
|
||||
boot_settings = openmc.Settings(particles=1)
|
||||
boot_settings.weight_windows_file = Path('weight_windows.h5').resolve()
|
||||
boot_model.convert_to_multigroup(
|
||||
groups=GROUPS, settings=boot_settings,
|
||||
overwrite_mgxs_library=True, mgxs_path='mgxs.h5')
|
||||
generate_weight_windows(boot_model, mesh)
|
||||
|
||||
# Run the continuous energy model with the improved weight windows,
|
||||
# tallying the flux in every region
|
||||
model.settings.weight_windows_file = 'weight_windows.h5'
|
||||
model.settings.particles = 20
|
||||
model.settings.batches = 10
|
||||
tally = openmc.Tally(name='flux')
|
||||
tally.filters = [
|
||||
openmc.CellFilter(list(model.geometry.get_all_cells().values()))]
|
||||
tally.scores = ['flux']
|
||||
model.tallies = openmc.Tallies([tally])
|
||||
|
||||
harness = MGXSTestHarness('statepoint.10.h5', model)
|
||||
harness.main()
|
||||
|
|
@ -27,7 +27,8 @@ def test_random_ray_auto_convert(method):
|
|||
|
||||
# Convert to a multi-group model
|
||||
model.convert_to_multigroup(
|
||||
method=method, groups='CASMO-2', nparticles=100,
|
||||
method=method, groups='CASMO-2',
|
||||
settings=openmc.Settings(particles=100),
|
||||
overwrite_mgxs_library=False, mgxs_path="mgxs.h5"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ def test_random_ray_auto_convert_source_energy(method, source_type):
|
|||
|
||||
# Convert to a multi-group model
|
||||
model.convert_to_multigroup(
|
||||
method=method, groups='CASMO-8', nparticles=100,
|
||||
method=method, groups='CASMO-8',
|
||||
settings=openmc.Settings(particles=100),
|
||||
overwrite_mgxs_library=False, mgxs_path="mgxs.h5",
|
||||
source_energy=source_energy
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ def test_random_ray_auto_convert(method):
|
|||
|
||||
# Convert to a multi-group model
|
||||
model.convert_to_multigroup(
|
||||
method=method, groups='CASMO-2', nparticles=100,
|
||||
method=method, groups='CASMO-2',
|
||||
settings=openmc.Settings(particles=100, temperature=temp_settings),
|
||||
overwrite_mgxs_library=False, mgxs_path="mgxs.h5",
|
||||
temperatures=[294.0, 394.0], temperature_settings=temp_settings
|
||||
temperatures=[294.0, 394.0]
|
||||
)
|
||||
|
||||
# Convert to a random ray model
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
|
||||
import openmc
|
||||
from openmc.examples import pwr_pin_cell
|
||||
from openmc import RegularMesh
|
||||
|
||||
|
|
@ -23,7 +24,8 @@ def test_random_ray_diagonal_stabilization():
|
|||
# MGXS data with some negatives on the diagonal, in order
|
||||
# to trigger diagonal correction.
|
||||
model.convert_to_multigroup(
|
||||
method='material_wise', groups='CASMO-70', nparticles=13,
|
||||
method='material_wise', groups='CASMO-70',
|
||||
settings=openmc.Settings(particles=13),
|
||||
overwrite_mgxs_library=True, mgxs_path="mgxs.h5", correction='P0'
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
a78972fe3c0dadfc256cfb139c5ca8c7b634738e1895ad2d6286ed5e2567c6dc518e499363908e9058432684148a706a179a39110f703d5322491184a1d0c3e4
|
||||
41d8ba482783b724c61bf058a67ff701109076ac460f0be69b3a33c7a9e3e2aa5b5773442d33561b71c69500a9535f1ffb0f5e81f6ce364288c5094bbbc0a292
|
||||
|
|
@ -45,7 +45,7 @@ def test_convert_to_multigroup_without_particles_batches(run_in_tmpdir):
|
|||
model.convert_to_multigroup(
|
||||
method='material_wise',
|
||||
groups='CASMO-2',
|
||||
nparticles=10,
|
||||
settings=openmc.Settings(particles=10),
|
||||
overwrite_mgxs_library=True
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1090,3 +1090,177 @@ def test_convert_to_multigroup_preserves_material_names(run_in_tmpdir):
|
|||
macro = [m._macroscopic for m in model.materials]
|
||||
assert macro == [f"Steel_Plate__1_{a.id}", f"Steel_Plate__1_{b.id}"]
|
||||
assert len(set(macro)) == 2
|
||||
|
||||
|
||||
class _GenerationCaptured(Exception):
|
||||
"""Raised by the patched MGXS library builder to end generation early."""
|
||||
|
||||
|
||||
def _steel_water_model():
|
||||
a = openmc.Material(name='steel')
|
||||
a.add_element('Fe', 1.0)
|
||||
a.set_density('g/cm3', 7.9)
|
||||
b = openmc.Material(name='water')
|
||||
b.add_element('H', 2.0)
|
||||
b.add_element('O', 1.0)
|
||||
b.set_density('g/cm3', 1.0)
|
||||
s1 = openmc.Sphere(r=1.0)
|
||||
s2 = openmc.Sphere(r=2.0, boundary_type='vacuum')
|
||||
c1 = openmc.Cell(fill=a, region=-s1)
|
||||
c2 = openmc.Cell(fill=b, region=+s1 & -s2)
|
||||
return openmc.Model(openmc.Geometry([c1, c2]), openmc.Materials([a, b]))
|
||||
|
||||
|
||||
def _capture_generation_settings(monkeypatch, model, **kwargs):
|
||||
"""Return the Settings of the MGXS generation run by capturing the
|
||||
generation model just before transport would start."""
|
||||
captured = {}
|
||||
|
||||
def fake_auto_generate(gen_model, groups, correction, directory):
|
||||
captured['settings'] = gen_model.settings
|
||||
raise _GenerationCaptured()
|
||||
|
||||
monkeypatch.setattr(openmc.Model, '_auto_generate_mgxs_lib',
|
||||
fake_auto_generate)
|
||||
with pytest.raises(_GenerationCaptured):
|
||||
model.convert_to_multigroup(**kwargs)
|
||||
return captured['settings']
|
||||
|
||||
|
||||
def test_convert_to_multigroup_settings_material_wise(run_in_tmpdir, monkeypatch):
|
||||
model = _steel_water_model()
|
||||
model.settings.run_mode = 'fixed source'
|
||||
model.settings.source = openmc.IndependentSource(space=openmc.stats.Point())
|
||||
model.settings.photon_transport = True
|
||||
model.settings.batches = 1200 # tuned for the final multigroup solve
|
||||
|
||||
user = openmc.Settings(particles=12345, seed=7)
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='material_wise', settings=user)
|
||||
|
||||
# User-populated attributes override the generation defaults...
|
||||
assert gen.particles == 12345
|
||||
assert gen.seed == 7
|
||||
# ...the generation defaults override the model's own settings...
|
||||
assert gen.batches == 200
|
||||
assert gen.inactive == 100
|
||||
assert gen.output == {'summary': True, 'tallies': False}
|
||||
# ...and everything else is inherited from the model
|
||||
assert gen.run_mode == 'fixed source'
|
||||
assert gen.photon_transport is True
|
||||
assert len(gen.source) == 1
|
||||
# The caller's object is never mutated
|
||||
assert user.batches is None
|
||||
|
||||
# The run mode is owned by the generation method: material_wise always
|
||||
# takes it from the model, even when set on the provided settings
|
||||
model.settings.run_mode = 'eigenvalue'
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='material_wise',
|
||||
settings=openmc.Settings(run_mode='fixed source'))
|
||||
assert gen.run_mode == 'eigenvalue'
|
||||
|
||||
|
||||
def test_convert_to_multigroup_settings_stochastic_slab(run_in_tmpdir, monkeypatch):
|
||||
model = _steel_water_model()
|
||||
|
||||
user = openmc.Settings(particles=999, batches=50, max_history_splits=42)
|
||||
user.source = openmc.IndependentSource(space=openmc.stats.Point())
|
||||
|
||||
with pytest.warns(UserWarning, match='constructs its own'):
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='stochastic_slab', settings=user)
|
||||
|
||||
assert gen.particles == 999
|
||||
assert gen.batches == 50
|
||||
assert gen.max_history_splits == 42
|
||||
# The method constructs its own source and run mode; the user's source
|
||||
# was discarded with a warning
|
||||
assert gen.run_mode == 'fixed source'
|
||||
assert gen.create_fission_neutrons is False
|
||||
assert len(gen.source) > 0
|
||||
assert all(s is not user.source[0] for s in gen.source)
|
||||
# The caller's object is never mutated
|
||||
assert len(user.source) == 1
|
||||
|
||||
|
||||
def test_convert_to_multigroup_settings_weight_windows(run_in_tmpdir, monkeypatch):
|
||||
model = _steel_water_model()
|
||||
ww_path = Path('ww.h5').resolve()
|
||||
|
||||
user = openmc.Settings(weight_windows_file=ww_path)
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='material_wise', settings=user)
|
||||
|
||||
# A weight windows file on the generation settings is passed through to
|
||||
# the "material_wise" generation run (specifying a file turns weight
|
||||
# windows on at run time)
|
||||
assert gen.weight_windows_file == ww_path
|
||||
# The caller's object is never mutated
|
||||
assert user.weight_windows_file == ww_path
|
||||
|
||||
# An explicit weight_windows_on=False rides along with the file and
|
||||
# overrides the run-time file-implied enable
|
||||
user = openmc.Settings(weight_windows_file=ww_path,
|
||||
weight_windows_on=False)
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='material_wise', settings=user)
|
||||
assert gen.weight_windows_file == ww_path
|
||||
assert gen.weight_windows_on is False
|
||||
|
||||
# The surrogate-geometry methods ignore the file with a warning
|
||||
with pytest.warns(UserWarning, match='material_wise'):
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='stochastic_slab', settings=user)
|
||||
assert gen.weight_windows_file is None
|
||||
assert user.weight_windows_file == ww_path
|
||||
|
||||
|
||||
def test_convert_to_multigroup_settings_validation(run_in_tmpdir):
|
||||
model = _steel_water_model()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
model.convert_to_multigroup(settings={'particles': 100})
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
model.convert_to_multigroup(method='not_a_method')
|
||||
|
||||
# The deprecated arguments cannot be combined with settings
|
||||
with pytest.raises(ValueError, match='deprecated'), \
|
||||
pytest.warns(FutureWarning):
|
||||
model.convert_to_multigroup(nparticles=1000,
|
||||
settings=openmc.Settings())
|
||||
with pytest.raises(ValueError, match='deprecated'), \
|
||||
pytest.warns(FutureWarning):
|
||||
model.convert_to_multigroup(
|
||||
temperature_settings={'method': 'interpolation'},
|
||||
settings=openmc.Settings())
|
||||
|
||||
|
||||
def test_convert_to_multigroup_deprecated_args(run_in_tmpdir, monkeypatch):
|
||||
# The deprecated arguments still work, with a FutureWarning
|
||||
model = _steel_water_model()
|
||||
with pytest.warns(FutureWarning, match='deprecated'):
|
||||
gen = _capture_generation_settings(
|
||||
monkeypatch, model, method='stochastic_slab', nparticles=1234,
|
||||
temperature_settings={'method': 'interpolation'})
|
||||
assert gen.particles == 1234
|
||||
assert gen.temperature == {'method': 'interpolation'}
|
||||
assert gen.batches == 200 # generation default unchanged
|
||||
|
||||
|
||||
def test_convert_to_multigroup_materials_from_geometry(run_in_tmpdir, monkeypatch):
|
||||
# A model whose materials are only referenced through the geometry gets
|
||||
# its materials collection populated (sorted by ID) for the conversion
|
||||
model = _steel_water_model()
|
||||
model.materials = openmc.Materials()
|
||||
ids = sorted(model.geometry.get_all_materials())
|
||||
|
||||
def fake_auto_generate(gen_model, groups, correction, directory):
|
||||
raise _GenerationCaptured()
|
||||
|
||||
monkeypatch.setattr(openmc.Model, '_auto_generate_mgxs_lib',
|
||||
fake_auto_generate)
|
||||
with pytest.raises(_GenerationCaptured):
|
||||
model.convert_to_multigroup(method='stochastic_slab')
|
||||
assert [m.id for m in model.materials] == ids
|
||||
|
|
|
|||
|
|
@ -192,6 +192,35 @@ def test_export_to_xml(run_in_tmpdir):
|
|||
assert s.free_gas_threshold == 800.0
|
||||
|
||||
|
||||
def test_update():
|
||||
base = openmc.Settings(particles=100, batches=10,
|
||||
output={'tallies': False})
|
||||
other = openmc.Settings(particles=500)
|
||||
other.source = openmc.IndependentSource(space=openmc.stats.Point())
|
||||
|
||||
base.update(other)
|
||||
|
||||
# Populated attributes of `other` overwrite those of `base`...
|
||||
assert base.particles == 500
|
||||
assert len(base.source) == 1
|
||||
# ...while unset attributes leave `base` untouched
|
||||
assert base.batches == 10
|
||||
assert base.output == {'tallies': False}
|
||||
# Copied values are independent of `other`
|
||||
assert base.source[0] is not other.source[0]
|
||||
|
||||
# run_mode always carries a value, so only a non-default ('eigenvalue')
|
||||
# run mode is detectable and copied
|
||||
base.run_mode = 'fixed source'
|
||||
base.update(openmc.Settings())
|
||||
assert base.run_mode == 'fixed source'
|
||||
base.update(openmc.Settings(run_mode='volume'))
|
||||
assert base.run_mode == 'volume'
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
base.update({'particles': 5})
|
||||
|
||||
|
||||
def test_properties_file_load(tmp_path, mpi_intracomm):
|
||||
model = openmc.examples.pwr_assembly()
|
||||
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ def test_photon_heating(run_in_tmpdir, shared_secondary):
|
|||
|
||||
model.settings.run_mode = 'fixed source'
|
||||
model.settings.batches = 5
|
||||
model.settings.particles = 101
|
||||
model.settings.particles = 100
|
||||
model.settings.shared_secondary_bank = shared_secondary
|
||||
|
||||
tally = openmc.Tally()
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ def test_ww_generation_with_dagmc(run_in_tmpdir):
|
|||
rr_model.convert_to_multigroup(
|
||||
method="stochastic_slab",
|
||||
overwrite_mgxs_library=True,
|
||||
nparticles=10,
|
||||
settings=openmc.Settings(particles=10),
|
||||
groups="CASMO-2"
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue