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
15 changes: 13 additions & 2 deletions docs/source/usersguide/random_ray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,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 @@ -673,6 +673,17 @@ of these methods is given below:
- * 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.
* - ``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 intra-material spatial variation that ``material_wise`` averages
away, e.g. a thick shield or a steep flux gradient within a single material
* Captures spatial self shielding between cells filled with the same material
- * Most expensive (one cross section set per cell) and a larger library
* Same far-from-source limitation as ``material_wise``: a cell that is not
tallied to yields zero cross sections for that cell
* - ``stochastic_slab``
- * Medium Fidelity
* Runs a CE simulation with a greatly simplified geometry, where materials
Expand Down
22 changes: 19 additions & 3 deletions openmc/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2703,8 +2703,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 @@ -2772,6 +2776,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 @@ -2788,7 +2804,7 @@ def convert_to_multigroup(
self._generate_infinite_medium_mgxs(
groups, nparticles, mgxs_path, correction, tmpdir, source_energy,
temperatures, temperature_settings)
elif method == "material_wise":
elif method in ("material_wise", "cell_wise"):
self._generate_material_wise_mgxs(
groups, nparticles, mgxs_path, correction, tmpdir,
temperatures, temperature_settings)
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
31 changes: 31 additions & 0 deletions tests/unit_tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1067,3 +1067,34 @@ 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


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"
Loading