From 11524aad0a4501f54f1347e65cdae05a0985a6fc Mon Sep 17 00:00:00 2001 From: Jon Shimwell Date: Fri, 21 Nov 2025 18:22:43 +0100 Subject: [PATCH 1/8] building mat from constructor --- openmc/material.py | 52 +++++++++++++++++++++++++++++-- tests/unit_tests/test_material.py | 32 +++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 1609da05a36..f48f6016f0c 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -60,6 +60,18 @@ class Material(IDManagerMixin): temperature : float, optional Temperature of the material in Kelvin. If not specified, the material inherits the default temperature applied to the model. + density : float, optional + Density of the material. If not specified, the density can be set later + using :meth:`Material.set_density()`. + density_units : {'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'}, optional + Units for the density. Defaults to 'g/cm3' if density is provided but + units are not specified. + depletable : bool, optional + Indicate whether the material is depletable. Defaults to False. + volume : float, optional + Volume of the material in cm^3. If not specified, the volume can be set later using :meth:`Material.add_volume_information()` or by assigning to the volume attribute. + nuclides : list of tuple, optional + List of tuples, each containing (nuclide: str, percent: float, percent_type: str = 'ao'). If provided, nuclides are added to the material at construction. Attributes ---------- @@ -111,17 +123,27 @@ class Material(IDManagerMixin): next_id = 1 used_ids = set() - def __init__(self, material_id=None, name='', temperature=None): + def __init__( + self, + material_id: int | None = None, + name: str = "", + temperature: float | None = None, + density: float | None = None, + density_units: str | None = None, + depletable: bool | None = False, + volume: float | None = None, + nuclides: list[tuple[str, float, str]] | None = None, + ): # Initialize class attributes self.id = material_id self.name = name self.temperature = temperature self._density = None self._density_units = 'sum' - self._depletable = False + self._depletable = depletable self._paths = None self._num_instances = None - self._volume = None + self._volume = volume self._atoms = {} self._isotropic = [] self._ncrystal_cfg = None @@ -136,6 +158,30 @@ def __init__(self, material_id=None, name='', temperature=None): # If specified, a list of table names self._sab = [] + # Set density if provided + if (density is not None and density_units is None) or ( + density is None and density_units is not None + ): + raise ValueError( + "Both density and density_units must be provided together." + ) + elif density is not None and density_units is not None: + self.set_density(density_units, density) + + # Add nuclides if provided + if nuclides is not None: + for entry in nuclides: + if len(entry) == 2: + nuclide, percent = entry + percent_type = "ao" + elif len(entry) == 3: + nuclide, percent, percent_type = entry + else: + raise ValueError( + "Each nuclide tuple must have 2 or 3 elements: (nuclide, percent, [percent_type])" + ) + self.add_nuclide(nuclide, percent, percent_type) + def __repr__(self) -> str: string = 'Material\n' string += '{: <16}=\t{}\n'.format('\tID', self._id) diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 2e37242720b..50fab2c1c38 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -767,3 +767,35 @@ def test_mean_free_path(): mat2.add_nuclide('Pb208', 1.0) mat2.set_density('g/cm3', 11.34) assert mat2.mean_free_path(energy=14e6) == pytest.approx(5.65, abs=1e-2) + + +def test_material_from_constructor(): + + mat1 = openmc.Material( + **{ + "material_id": 1, + "name": "neutron_star", + "density": 1e17, + "density_units": "kg/m3", + } + ) + assert mat1.id == 1 + assert mat1.name == "neutron_star" + assert mat1._density == 1e17 + assert mat1._density_units == "kg/m3" + assert mat1.nuclides == [] + + mat2 = openmc.Material( + material_id=42, + name="plasma", + temperature=None, + density=1e-7, + density_units="g/cm3", + nuclides=[("H1", 0.1, "ao"), ("H2", 0.2, "wo"), ("H3", 0.3)], + ) + assert mat2.id == 42 + assert mat2.name == "plasma" + assert mat2.temperature is None + assert mat2.density == 1e-7 + assert mat2.density_units == "g/cm3" + assert mat2.nuclides == [("H1", 0.1, "ao"), ("H2", 0.2, "wo"), ("H3", 0.3, "ao")] From a164f452938ed5f861d3437b7d1670a9322ca3b6 Mon Sep 17 00:00:00 2001 From: shimwell Date: Fri, 21 Nov 2025 18:29:02 +0100 Subject: [PATCH 2/8] doc str order matches existing docs str --- openmc/material.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index f48f6016f0c..31e355e5bb3 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -58,20 +58,23 @@ class Material(IDManagerMixin): Name of the material. If not specified, the name will be the empty string. temperature : float, optional - Temperature of the material in Kelvin. If not specified, the material - inherits the default temperature applied to the model. + Temperature of the material in Kelvin. density : float, optional - Density of the material. If not specified, the density can be set later - using :meth:`Material.set_density()`. - density_units : {'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'}, optional - Units for the density. Defaults to 'g/cm3' if density is provided but - units are not specified. + Density of the material (units defined separately) + density_units : str + Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/m3', + 'atom/b-cm', 'atom/cm3', 'sum', or 'macro'. The 'macro' unit only + applies in the case of a multi-group calculation. depletable : bool, optional Indicate whether the material is depletable. Defaults to False. - volume : float, optional - Volume of the material in cm^3. If not specified, the volume can be set later using :meth:`Material.add_volume_information()` or by assigning to the volume attribute. nuclides : list of tuple, optional - List of tuples, each containing (nuclide: str, percent: float, percent_type: str = 'ao'). If provided, nuclides are added to the material at construction. + List in which each item is a namedtuple consisting of a nuclide string, + the percent density, and the percent type ('ao' or 'wo'). The namedtuple + has field names ``name``, ``percent``, and ``percent_type``. + volume : float, optional + Volume of the material in cm^3. This can either be set manually or + calculated in a stochastic volume calculation and added via the + :meth:`Material.add_volume_information` method. Attributes ---------- @@ -131,8 +134,8 @@ def __init__( density: float | None = None, density_units: str | None = None, depletable: bool | None = False, - volume: float | None = None, nuclides: list[tuple[str, float, str]] | None = None, + volume: float | None = None, ): # Initialize class attributes self.id = material_id From ffcf4bdc83fe1126f8d4a64cfa25e6d80e3983f2 Mon Sep 17 00:00:00 2001 From: shimwell Date: Fri, 21 Nov 2025 18:30:40 +0100 Subject: [PATCH 3/8] min diff --- openmc/material.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openmc/material.py b/openmc/material.py index 31e355e5bb3..f5a33b7130b 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -58,7 +58,8 @@ class Material(IDManagerMixin): Name of the material. If not specified, the name will be the empty string. temperature : float, optional - Temperature of the material in Kelvin. + Temperature of the material in Kelvin. If not specified, the material + inherits the default temperature applied to the model. density : float, optional Density of the material (units defined separately) density_units : str From 30eb7fcbc6a41a5520ed96921186ce1415cb77b9 Mon Sep 17 00:00:00 2001 From: shimwell Date: Mon, 24 Nov 2025 09:32:36 +0100 Subject: [PATCH 4/8] removed nuclides from constructor --- openmc/material.py | 18 ------------------ tests/unit_tests/test_material.py | 3 +-- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index f5a33b7130b..cd0cb6c9f62 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -68,10 +68,6 @@ class Material(IDManagerMixin): applies in the case of a multi-group calculation. depletable : bool, optional Indicate whether the material is depletable. Defaults to False. - nuclides : list of tuple, optional - List in which each item is a namedtuple consisting of a nuclide string, - the percent density, and the percent type ('ao' or 'wo'). The namedtuple - has field names ``name``, ``percent``, and ``percent_type``. volume : float, optional Volume of the material in cm^3. This can either be set manually or calculated in a stochastic volume calculation and added via the @@ -135,7 +131,6 @@ def __init__( density: float | None = None, density_units: str | None = None, depletable: bool | None = False, - nuclides: list[tuple[str, float, str]] | None = None, volume: float | None = None, ): # Initialize class attributes @@ -172,19 +167,6 @@ def __init__( elif density is not None and density_units is not None: self.set_density(density_units, density) - # Add nuclides if provided - if nuclides is not None: - for entry in nuclides: - if len(entry) == 2: - nuclide, percent = entry - percent_type = "ao" - elif len(entry) == 3: - nuclide, percent, percent_type = entry - else: - raise ValueError( - "Each nuclide tuple must have 2 or 3 elements: (nuclide, percent, [percent_type])" - ) - self.add_nuclide(nuclide, percent, percent_type) def __repr__(self) -> str: string = 'Material\n' diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 50fab2c1c38..6c1430fa913 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -791,11 +791,10 @@ def test_material_from_constructor(): temperature=None, density=1e-7, density_units="g/cm3", - nuclides=[("H1", 0.1, "ao"), ("H2", 0.2, "wo"), ("H3", 0.3)], ) assert mat2.id == 42 assert mat2.name == "plasma" assert mat2.temperature is None assert mat2.density == 1e-7 assert mat2.density_units == "g/cm3" - assert mat2.nuclides == [("H1", 0.1, "ao"), ("H2", 0.2, "wo"), ("H3", 0.3, "ao")] + assert mat2.nuclides == [] From 97a81825c4105485eb9505106a1730f9aed144e8 Mon Sep 17 00:00:00 2001 From: shimwell Date: Sat, 29 Nov 2025 23:56:14 +0100 Subject: [PATCH 5/8] default to g/cm3 --- openmc/material.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index cd0cb6c9f62..c83adc1a542 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -65,7 +65,7 @@ class Material(IDManagerMixin): density_units : str Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', or 'macro'. The 'macro' unit only - applies in the case of a multi-group calculation. + applies in the case of a multi-group calculation. Defaults to 'g/cm3'. depletable : bool, optional Indicate whether the material is depletable. Defaults to False. volume : float, optional @@ -129,7 +129,7 @@ def __init__( name: str = "", temperature: float | None = None, density: float | None = None, - density_units: str | None = None, + density_units: str = "g/cm3", depletable: bool | None = False, volume: float | None = None, ): @@ -138,7 +138,7 @@ def __init__( self.name = name self.temperature = temperature self._density = None - self._density_units = 'sum' + self._density_units = density_units self._depletable = depletable self._paths = None self._num_instances = None @@ -158,13 +158,7 @@ def __init__( self._sab = [] # Set density if provided - if (density is not None and density_units is None) or ( - density is None and density_units is not None - ): - raise ValueError( - "Both density and density_units must be provided together." - ) - elif density is not None and density_units is not None: + if density is not None: self.set_density(density_units, density) From 3bb1b9edf2c729fc624f89667390ba2f97ff8e09 Mon Sep 17 00:00:00 2001 From: shimwell Date: Sun, 30 Nov 2025 00:06:11 +0100 Subject: [PATCH 6/8] add compound --- openmc/material.py | 6 ++++++ tests/unit_tests/test_material.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/openmc/material.py b/openmc/material.py index c83adc1a542..f650da5a771 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -132,6 +132,8 @@ def __init__( density_units: str = "g/cm3", depletable: bool | None = False, volume: float | None = None, + components: dict | None = None, + percent_type: str = "ao", ): # Initialize class attributes self.id = material_id @@ -161,6 +163,10 @@ def __init__( if density is not None: self.set_density(density_units, density) + # Add components if provided + if components is not None: + self.add_components(components, percent_type=percent_type) + def __repr__(self) -> str: string = 'Material\n' diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 6c1430fa913..93abb74bfc4 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -770,6 +770,26 @@ def test_mean_free_path(): def test_material_from_constructor(): + # Test that components and percent_type work in the constructor + components = { + 'Li': {'percent': 0.5, 'enrichment': 60.0, 'enrichment_target': 'Li7'}, + 'O16': 1.0, + 'Be': 0.5 + } + mat = openmc.Material( + material_id=123, + name="test-mat", + components=components, + percent_type="ao" + ) + # Check that nuclides were added + nuclide_names = [nuc.name for nuc in mat.nuclides] + assert 'O16' in nuclide_names + assert 'Be9' in nuclide_names + assert 'Li7' in nuclide_names + assert 'Li6' in nuclide_names + assert mat.id == 123 + assert mat.name == "test-mat" mat1 = openmc.Material( **{ From 75317403d4b15c1405e86934c5991c833fe6879a Mon Sep 17 00:00:00 2001 From: shimwell Date: Mon, 1 Dec 2025 17:08:37 +0100 Subject: [PATCH 7/8] changed default density units to previous default --- openmc/material.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index f650da5a771..56266b66da6 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -65,7 +65,7 @@ class Material(IDManagerMixin): density_units : str Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', or 'macro'. The 'macro' unit only - applies in the case of a multi-group calculation. Defaults to 'g/cm3'. + applies in the case of a multi-group calculation. Defaults to 'sum'. depletable : bool, optional Indicate whether the material is depletable. Defaults to False. volume : float, optional @@ -129,7 +129,7 @@ def __init__( name: str = "", temperature: float | None = None, density: float | None = None, - density_units: str = "g/cm3", + density_units: str = "sum", depletable: bool | None = False, volume: float | None = None, components: dict | None = None, From f58aa7d0a40dd050ae3a151f02661263d56684d0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 8 Dec 2025 06:32:37 -0600 Subject: [PATCH 8/8] Update docstring for Material --- openmc/material.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openmc/material.py b/openmc/material.py index 56266b66da6..735a0574326 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -72,6 +72,14 @@ class Material(IDManagerMixin): Volume of the material in cm^3. This can either be set manually or calculated in a stochastic volume calculation and added via the :meth:`Material.add_volume_information` method. + components : dict of str to float or dict + Dictionary mapping element or nuclide names to their atom or weight + percent. To specify enrichment of an element, the entry of + ``components`` for that element must instead be a dictionary containing + the keyword arguments as well as a value for ``'percent'`` + percent_type : {'ao', 'wo'} + Whether the values in `components` should be interpreted as atom percent + ('ao') or weight percent ('wo'). Attributes ----------