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
26 changes: 24 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,21 @@ 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 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
Comment on lines +678 to +690

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to add a note clarifying that it is generating unique MGXS data for each cell and not for each cell instance. Basically, it would be nice to alert the user that a cell that shows up in multiple locations of a lattice would have the same MGXS data set for all locations. If this type of discussion ends up being too wordy for the table formatting, perhaps it can be included as regular text elsewhere in the section.

  • 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

I don't completely agree with, as a cell may still be very large and have a steep gradient within it. A full shield wall may easily just be defined with one cell, in which case, you still are sharing the same MGXS throughout the full wall (even if you had applied a cell-under-voxel overlay to make smaller source regions). You only get an improvement if you had manually broken the shield wall down in your geometry definition into multiple cells that cover different depths of the wall. Basically, we don't want to oversell this method in terms of fidelity.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @jtramm yes good spot, I have updated the docs as suggested

* - ``stochastic_slab``
- * Medium Fidelity
* Runs a CE simulation with a greatly simplified geometry, where materials
Expand All @@ -691,6 +706,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 ``nparticles``
Expand Down
22 changes: 19 additions & 3 deletions openmc/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2721,8 +2721,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 @@ -2790,6 +2794,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 @@ -2806,7 +2822,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 @@ -1090,3 +1090,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