Skip to content

Commit 3681d7c

Browse files
Add cell_wise MGXS generation method
Add method="cell_wise" to Model.convert_to_multigroup: like material_wise, but gives each cell its own multigroup cross sections. The material in every material-filled cell is cloned (each clone gets a unique id), then the standard per-material generation runs, so per material becomes per cell. This captures the intra-material spatial-spectrum variation that material_wise averages away when one material spans a strong gradient. The implementation reuses the material_wise path entirely; the only new code is the per-cell cloning step in convert_to_multigroup plus the dispatch entry. Adds unit tests (CSG and DAGMC: two cells sharing a material get distinct macroscopics) and a user guide entry in the MGXS methods table. Builds on the name+id library keying from #3984 (now in develop). Co-authored-by: jon-proxima <jon@proximafusion.com>
1 parent d06fdee commit 3681d7c

4 files changed

Lines changed: 103 additions & 5 deletions

File tree

docs/source/usersguide/random_ray.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -652,8 +652,8 @@ model to use these multigroup cross sections. An example is given below::
652652
)
653653

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

658658
.. list-table:: Comparison of Automatic MGXS Generation Methods
659659
:header-rows: 1
@@ -673,6 +673,17 @@ of these methods is given below:
673673
- * Potentially slower as the full geometry must be run
674674
* If a material is only present far from the source and doesn't get tallied
675675
to in the CE simulation, the MGXS will be zero for that material.
676+
* - ``cell_wise``
677+
- * Highest Fidelity
678+
* Like ``material_wise``, but clones the material in each cell so every
679+
cell gets its own cross sections (each material-filled cell is assigned a
680+
distinct macroscopic).
681+
- * Resolves intra-material spatial variation that ``material_wise`` averages
682+
away, e.g. a thick shield or a steep flux gradient within a single material
683+
* Captures spatial self shielding between cells filled with the same material
684+
- * Most expensive (one cross section set per cell) and a larger library
685+
* Same far-from-source limitation as ``material_wise``: a cell that is not
686+
tallied to yields zero cross sections for that cell
676687
* - ``stochastic_slab``
677688
- * Medium Fidelity
678689
* Runs a CE simulation with a greatly simplified geometry, where materials

openmc/model/model.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2703,8 +2703,12 @@ def convert_to_multigroup(
27032703
27042704
Parameters
27052705
----------
2706-
method : {"material_wise", "stochastic_slab", "infinite_medium"}, optional
2707-
Method to generate the MGXS.
2706+
method : {"material_wise", "stochastic_slab", "infinite_medium", \
2707+
"cell_wise"}, optional
2708+
Method to generate the MGXS. "cell_wise" is like
2709+
"material_wise" but gives each cell its own cross sections. The
2710+
material in each material-filled cell is cloned, so the per-material
2711+
generation produces one cross section set per cell.
27082712
groups : openmc.mgxs.EnergyGroups, str, or sequence of float, optional
27092713
Energy group structure for the MGXS. Can be an
27102714
:class:`openmc.mgxs.EnergyGroups` object, a string name of a
@@ -2772,6 +2776,18 @@ def convert_to_multigroup(
27722776
self.settings.run_mode = original_run_mode
27732777
break
27742778

2779+
# For "cell_wise", give each cell its own cross sections by
2780+
# cloning the material in every material-filled cell. Each clone gets
2781+
# a unique id, so the per-material generation below produces (and
2782+
# assigns) one cross section set per cell.
2783+
if method == "cell_wise":
2784+
cell_materials = []
2785+
for cell in self.geometry.get_all_cells().values():
2786+
if isinstance(cell.fill, openmc.Material):
2787+
cell.fill = cell.fill.clone()
2788+
cell_materials.append(cell.fill)
2789+
self.materials = openmc.Materials(cell_materials)
2790+
27752791
# Temporarily replace each material's name with a unique, valid HDF5
27762792
# dataset name (its name plus ID) for use as its MGXS library entry
27772793
# and macroscopic. The ID keeps the name unique even when materials
@@ -2788,7 +2804,7 @@ def convert_to_multigroup(
27882804
self._generate_infinite_medium_mgxs(
27892805
groups, nparticles, mgxs_path, correction, tmpdir, source_energy,
27902806
temperatures, temperature_settings)
2791-
elif method == "material_wise":
2807+
elif method in ("material_wise", "cell_wise"):
27922808
self._generate_material_wise_mgxs(
27932809
groups, nparticles, mgxs_path, correction, tmpdir,
27942810
temperatures, temperature_settings)

tests/unit_tests/dagmc/test_convert_to_multigroup.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,43 @@ def test_convert_to_multigroup_without_particles_batches(run_in_tmpdir):
5151

5252
# Verify the model was converted successfully
5353
assert model.settings.energy_mode == 'multi-group'
54+
55+
56+
def test_convert_to_multigroup_cell_wise(run_in_tmpdir):
57+
"""cell_wise gives each DAGMC volume its own cross sections, so two
58+
cells filled with the same material end up with distinct macroscopics."""
59+
openmc.reset_auto_ids()
60+
61+
# dagmc.h5m has two fuel volumes (both "no-void fuel"), one water volume, a
62+
# graveyard and an implicit complement.
63+
u235 = openmc.Material(name="no-void fuel")
64+
u235.add_nuclide("U235", 1.0)
65+
u235.set_density("g/cm3", 11.0)
66+
water = openmc.Material(name="water")
67+
water.add_nuclide("H1", 2.0)
68+
water.add_nuclide("O16", 1.0)
69+
water.set_density("g/cm3", 1.0)
70+
water.id = 41
71+
72+
dagmc_file = Path(__file__).parent / "dagmc.h5m"
73+
model = openmc.Model()
74+
model.materials = openmc.Materials([u235, water])
75+
model.geometry = openmc.Geometry(openmc.DAGMCUniverse(dagmc_file))
76+
model.settings = openmc.Settings()
77+
model.settings.run_mode = "fixed source"
78+
source = openmc.IndependentSource()
79+
source.energy = openmc.stats.delta_function(2.0e6)
80+
model.settings.source = source
81+
82+
# Pre-create the library so MGXS generation/transport is skipped; this
83+
# exercises the per-cell material cloning for the DAGMC cells only.
84+
Path("mgxs.h5").touch()
85+
model.convert_to_multigroup(
86+
method="cell_wise", groups="CASMO-2", mgxs_path="mgxs.h5")
87+
88+
# The three material-filled volumes (two fuel, one water) each get their own
89+
# cloned material with a distinct macroscopic; the void cells are skipped.
90+
assert model.settings.energy_mode == "multi-group"
91+
assert len(model.materials) == 3
92+
macros = [m._macroscopic for m in model.materials]
93+
assert len(set(macros)) == 3

tests/unit_tests/test_model.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,3 +1067,34 @@ def test_convert_to_multigroup_preserves_material_names(run_in_tmpdir):
10671067
macro = [m._macroscopic for m in model.materials]
10681068
assert macro == [f"Steel_Plate__1_{a.id}", f"Steel_Plate__1_{b.id}"]
10691069
assert len(set(macro)) == 2
1070+
1071+
1072+
def test_convert_to_multigroup_cell_wise(run_in_tmpdir):
1073+
"""cell_wise clones the material in each cell so two cells sharing one
1074+
material end up with distinct (spatially-resolved) cross sections rather than a
1075+
single shared set."""
1076+
water = openmc.Material(name="water")
1077+
water.add_element("H", 2.0)
1078+
water.add_element("O", 1.0)
1079+
water.set_density("g/cm3", 1.0)
1080+
1081+
s1 = openmc.Sphere(r=1.0)
1082+
s2 = openmc.Sphere(r=2.0, boundary_type="vacuum")
1083+
c1 = openmc.Cell(fill=water, region=-s1)
1084+
c2 = openmc.Cell(fill=water, region=+s1 & -s2) # same material, distinct cell
1085+
model = openmc.Model(openmc.Geometry([c1, c2]), openmc.Materials([water]))
1086+
1087+
# Pre-create the library so MGXS generation (and transport) is skipped; this
1088+
# exercises the per-cell material cloning and macroscopic assignment only.
1089+
Path("mgxs.h5").touch()
1090+
model.convert_to_multigroup(method="cell_wise", mgxs_path="mgxs.h5")
1091+
1092+
# Each cell now holds its own cloned material reading a distinct macroscopic.
1093+
assert len(model.materials) == 2
1094+
assert c1.fill is not c2.fill
1095+
assert c1.fill._macroscopic == f"water_{c1.fill.id}"
1096+
assert c2.fill._macroscopic == f"water_{c2.fill.id}"
1097+
assert c1.fill._macroscopic != c2.fill._macroscopic
1098+
# The user's original material object is left untouched.
1099+
assert water.name == "water"
1100+
assert model.settings.energy_mode == "multi-group"

0 commit comments

Comments
 (0)