mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-27 21:55:41 -04:00
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>
This commit is contained in:
parent
d06fdee93a
commit
3681d7c205
4 changed files with 103 additions and 5 deletions
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2703,8 +2703,12 @@ class Model:
|
|||
|
||||
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
|
||||
|
|
@ -2772,6 +2776,18 @@ class Model:
|
|||
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
|
||||
|
|
@ -2788,7 +2804,7 @@ class Model:
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue