Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions docs/source/usersguide/random_ray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -650,8 +650,8 @@ model to use these multigroup cross sections. An example is given below::
)

The most important parameter to set is the ``method`` parameter, which can be
either "stochastic_slab", "material_wise", or "infinite_medium". An overview
of these methods is given below:
one of "material_wise", "cell_wise", "stochastic_slab", or
"infinite_medium". An overview of these methods is given below:

.. list-table:: Comparison of Automatic MGXS Generation Methods
:header-rows: 1
Expand All @@ -665,14 +665,31 @@ of these methods is given below:
- * Higher Fidelity
* Runs a CE simulation with the original geometry and source, tallying
cross sections with a material filter.
- * Typically the most accurate of the three methods
- * Typically more accurate than the surrogate-geometry methods
(``stochastic_slab`` and ``infinite_medium``)
* Accurately captures (averaged over the full problem domain)
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. This
can be mitigated by supplying weight windows via ``weight_windows_file``
(see :ref:`mgxs_bootstrap`).
* - ``cell_wise``
- * Highest Fidelity
* Like ``material_wise``, but clones the material in each cell so every
cell gets its own cross sections (each material-filled cell is assigned a
distinct macroscopic).
- * Resolves spatial variation between distinct cells that share a material,
which ``material_wise`` averages away (e.g. a shield wall modelled as
several cells of one material, each covering a different depth)
* Captures spatial self shielding between cells filled with the same material
- * Most expensive (one cross section set per cell) and a larger library
* Fidelity is limited by the cell definitions, not the source region mesh:
a single large cell uses one cross section set throughout, so a steep
gradient is only resolved if the geometry is split into several cells
* Same far-from-source limitation as ``material_wise``: a cell that is not
tallied to yields zero cross sections for that cell (the same weight
window mitigation applies)
* - ``stochastic_slab``
- * Medium Fidelity
* Runs a CE simulation with a greatly simplified geometry, where materials
Expand All @@ -691,6 +708,13 @@ of these methods is given below:
between materials)
* May hang if a material has a k-infinity greater than 1.0

.. note::
The ``cell_wise`` method generates one cross section set per cell definition,
not per cell instance. A cell that appears in several locations of a lattice
therefore shares a single cross section set across all of those locations. If
you need distinct cross sections per instance, subdivide the geometry into
separate cells.

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 ``correction``
Expand Down Expand Up @@ -769,12 +793,12 @@ the fidelity of the generated MGXS data.
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
The ``"material_wise"`` and ``"cell_wise"`` methods run a continuous energy
simulation of the original geometry, so they produce the highest fidelity cross
sections of the available methods. However, they have 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.

Expand All @@ -800,8 +824,8 @@ described in the :ref:`FW-CADIS user guide <usersguide_fw_cadis>`. The resulting
model.convert_to_multigroup(
weight_windows_file="weight_windows.h5", overwrite_mgxs_library=True)

The ``weight_windows_file`` setting is only used with the
``"material_wise"`` method, as the ``"stochastic_slab"`` and
The ``weight_windows_file`` setting is only used with the ``"material_wise"``
and ``"cell_wise"`` methods, 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
Expand Down
66 changes: 43 additions & 23 deletions openmc/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2638,8 +2638,12 @@ def convert_to_multigroup(

Parameters
----------
method : {"material_wise", "stochastic_slab", "infinite_medium"}, optional
Method to generate the MGXS.
method : {"material_wise", "stochastic_slab", "infinite_medium", \
"cell_wise"}, optional
Method to generate the MGXS. "cell_wise" is like
"material_wise" but gives each cell its own cross sections. The
material in each material-filled cell is cloned, so the per-material
generation produces one cross section set per cell.
groups : openmc.mgxs.EnergyGroups, str, or sequence of float, optional
Energy group structure for the MGXS. Can be an
:class:`openmc.mgxs.EnergyGroups` object, a string name of a
Expand Down Expand Up @@ -2699,9 +2703,9 @@ def convert_to_multigroup(
also construct their own sources and always disable
``create_fission_neutrons`` so that fission is treated as capture.
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
and ``"cell_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
Expand All @@ -2710,7 +2714,8 @@ def convert_to_multigroup(
groups = openmc.mgxs.EnergyGroups(groups)

check_value('method', method,
('material_wise', 'stochastic_slab', 'infinite_medium'))
('material_wise', 'cell_wise', 'stochastic_slab',
'infinite_medium'))

# Keyword arguments are Settings attributes applied as overrides on
# the generation defaults
Expand Down Expand Up @@ -2739,10 +2744,11 @@ def convert_to_multigroup(

# 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 the user's keyword-argument overrides.
# ("material_wise"/"cell_wise") or a fresh Settings object (surrogate
# methods), then the generation defaults, then the user's
# keyword-argument overrides.
user_settings = settings
if method == 'material_wise':
if method in ('material_wise', 'cell_wise'):
settings = copy.deepcopy(self.settings)
else:
settings = openmc.Settings()
Expand All @@ -2760,7 +2766,8 @@ def convert_to_multigroup(

if user_settings is not None:
# The surrogate-geometry methods construct their own sources
if method != "material_wise" and len(user_settings.source) > 0:
if method not in ("material_wise", "cell_wise") \
and len(user_settings.source) > 0:
warnings.warn(
'The given "source" setting is ignored by the '
f'"{method}" MGXS generation method, which constructs '
Expand All @@ -2773,31 +2780,32 @@ def convert_to_multigroup(

# 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. The surrogate-geometry methods also treat fission
# as capture (nu-fission is still tallied)
if method == 'material_wise':
# so it is owned by the generation method: "material_wise" and
# "cell_wise" always take it from the model, while the
# surrogate-geometry methods always run in fixed source mode. The
# surrogate-geometry methods also treat fission as capture (nu-fission
# is still tallied)
if method in ('material_wise', 'cell_wise'):
settings.run_mode = self.settings.run_mode
else:
settings.run_mode = 'fixed source'
settings.create_fission_neutrons = False

# 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
# "material_wise" and "cell_wise" methods' 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":
method not in ("material_wise", "cell_wise"):
warnings.warn(
'The "weight_windows_file" setting is only applicable to '
'the "material_wise" MGXS generation method and will be '
f'ignored for the "{method}" method.'
'the "material_wise" and "cell_wise" MGXS generation methods '
f'and will be ignored for the "{method}" method.'
)
settings.weight_windows_file = None

Expand All @@ -2822,6 +2830,18 @@ def convert_to_multigroup(
self.settings.run_mode = original_run_mode
break

# For "cell_wise", give each cell its own cross sections by
# cloning the material in every material-filled cell. Each clone gets
# a unique id, so the per-material generation below produces (and
# assigns) one cross section set per cell.
if method == "cell_wise":
cell_materials = []
for cell in self.geometry.get_all_cells().values():
if isinstance(cell.fill, openmc.Material):
cell.fill = cell.fill.clone()
cell_materials.append(cell.fill)
self.materials = openmc.Materials(cell_materials)

# Temporarily replace each material's name with a unique, valid HDF5
# dataset name (its name plus ID) for use as its MGXS library entry
# and macroscopic. The ID keeps the name unique even when materials
Expand All @@ -2838,7 +2858,7 @@ def convert_to_multigroup(
self._generate_infinite_medium_mgxs(
groups, settings, mgxs_path, correction, tmpdir,
source_energy, temperatures)
elif method == "material_wise":
elif method in ("material_wise", "cell_wise"):
self._generate_material_wise_mgxs(
groups, settings, mgxs_path, correction, tmpdir,
temperatures)
Expand Down
40 changes: 40 additions & 0 deletions tests/unit_tests/dagmc/test_convert_to_multigroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,43 @@ def test_convert_to_multigroup_without_particles_batches(run_in_tmpdir):

# Verify the model was converted successfully
assert model.settings.energy_mode == 'multi-group'


def test_convert_to_multigroup_cell_wise(run_in_tmpdir):
"""cell_wise gives each DAGMC volume its own cross sections, so two
cells filled with the same material end up with distinct macroscopics."""
openmc.reset_auto_ids()

# dagmc.h5m has two fuel volumes (both "no-void fuel"), one water volume, a
# graveyard and an implicit complement.
u235 = openmc.Material(name="no-void fuel")
u235.add_nuclide("U235", 1.0)
u235.set_density("g/cm3", 11.0)
water = openmc.Material(name="water")
water.add_nuclide("H1", 2.0)
water.add_nuclide("O16", 1.0)
water.set_density("g/cm3", 1.0)
water.id = 41

dagmc_file = Path(__file__).parent / "dagmc.h5m"
model = openmc.Model()
model.materials = openmc.Materials([u235, water])
model.geometry = openmc.Geometry(openmc.DAGMCUniverse(dagmc_file))
model.settings = openmc.Settings()
model.settings.run_mode = "fixed source"
source = openmc.IndependentSource()
source.energy = openmc.stats.delta_function(2.0e6)
model.settings.source = source

# Pre-create the library so MGXS generation/transport is skipped; this
# exercises the per-cell material cloning for the DAGMC cells only.
Path("mgxs.h5").touch()
model.convert_to_multigroup(
method="cell_wise", groups="CASMO-2", mgxs_path="mgxs.h5")

# The three material-filled volumes (two fuel, one water) each get their own
# cloned material with a distinct macroscopic; the void cells are skipped.
assert model.settings.energy_mode == "multi-group"
assert len(model.materials) == 3
macros = [m._macroscopic for m in model.materials]
assert len(set(macros)) == 3
51 changes: 51 additions & 0 deletions tests/unit_tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,37 @@ def test_convert_to_multigroup_preserves_material_names(run_in_tmpdir):
assert len(set(macro)) == 2


def test_convert_to_multigroup_cell_wise(run_in_tmpdir):
"""cell_wise clones the material in each cell so two cells sharing one
material end up with distinct (spatially-resolved) cross sections rather than a
single shared set."""
water = openmc.Material(name="water")
water.add_element("H", 2.0)
water.add_element("O", 1.0)
water.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=water, region=-s1)
c2 = openmc.Cell(fill=water, region=+s1 & -s2) # same material, distinct cell
model = openmc.Model(openmc.Geometry([c1, c2]), openmc.Materials([water]))

# Pre-create the library so MGXS generation (and transport) is skipped; this
# exercises the per-cell material cloning and macroscopic assignment only.
Path("mgxs.h5").touch()
model.convert_to_multigroup(method="cell_wise", mgxs_path="mgxs.h5")

# Each cell now holds its own cloned material reading a distinct macroscopic.
assert len(model.materials) == 2
assert c1.fill is not c2.fill
assert c1.fill._macroscopic == f"water_{c1.fill.id}"
assert c2.fill._macroscopic == f"water_{c2.fill.id}"
assert c1.fill._macroscopic != c2.fill._macroscopic
# The user's original material object is left untouched.
assert water.name == "water"
assert model.settings.energy_mode == "multi-group"


class _GenerationCaptured(Exception):
"""Raised by the patched MGXS library builder to end generation early."""

Expand Down Expand Up @@ -1206,6 +1237,26 @@ def test_convert_to_multigroup_weight_windows(run_in_tmpdir, monkeypatch):
assert gen.weight_windows_file is None


def test_convert_to_multigroup_cell_wise_settings(run_in_tmpdir, monkeypatch):
# "cell_wise" runs the same full-geometry continuous energy simulation as
# "material_wise", so it inherits the model's settings and run mode and
# accepts a weight windows file
model = _steel_water_model()
model.settings.run_mode = 'fixed source'
model.settings.source = openmc.IndependentSource(space=openmc.stats.Point())
ww_path = Path('ww.h5').resolve()

gen = _capture_generation_settings(
monkeypatch, model, method='cell_wise', particles=777,
weight_windows_file=ww_path)

assert gen.particles == 777
assert gen.batches == 200 # generation default
assert gen.run_mode == 'fixed source'
assert len(gen.source) == 1
assert gen.weight_windows_file == ww_path


def test_convert_to_multigroup_validation(run_in_tmpdir):
model = _steel_water_model()

Expand Down
Loading