From b3d0f20a0010dd930e4f403848d72164f32b294e Mon Sep 17 00:00:00 2001 From: yardasol Date: Wed, 27 Jul 2022 13:15:10 -0500 Subject: [PATCH 1/7] added add_nuclides method --- openmc/material.py | 14 ++++++++++++++ tests/unit_tests/test_material.py | 28 +++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/openmc/material.py b/openmc/material.py index a837b1f17d..cd181539c0 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -403,6 +403,20 @@ class Material(IDManagerMixin): self._nuclides.append(NuclideTuple(nuclide, percent, percent_type)) + def add_nuclides(self, nuclides: dict): + """ Add multiple nuclides to a material + + Parameters + ---------- + nuclides : dict of str to tuple + Dictionary mapping nuclide names to a tuple containing their + atom or weight percent. + + """ + + for nuclide, (percent, percent_type) in nuclides.items(): + self.add_nuclide(nuclide, percent, percent_type) + def remove_nuclide(self, nuclide: str): """Remove a nuclide from the material diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index fd0e4f6247..8dde304856 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -25,6 +25,32 @@ def test_add_nuclide(): with pytest.raises(ValueError): m.add_nuclide('H1', 1.0, 'oa') +def test_add_nuclides(): + """Test adding multipe nuclides at once""" + m = openmc.Material() + nuclides = {'H1': (2.0, 'ao'), + 'O16': (1.0, 'wo')} + m.add_nuclides(nuclides) + + # wt-% + m = openmc.Material() + nuclides = {'H1': (2.0, 'wo'), + 'O16': (1.0, 'wo')} + m.add_nuclides(nuclides) + + # mixed + m = openmc.Material() + nuclides = {'H1': (2.0, 'ao'), + 'O16': (1.0, 'wo')} + m.add_nuclides(nuclides) + + with pytest.raises(TypeError): + m.add_nuclides({'H1': ('1.0', 'ao')}) + with pytest.raises(TypeError): + m.add_nuclides({1.0: ('H1', 'wo')}) + with pytest.raises(ValueError): + m.add_nuclides({'H1': (1.0, 'oa')}) + def test_remove_nuclide(): """Test removing nuclides.""" @@ -441,7 +467,7 @@ def test_activity_of_tritium(): m1.add_nuclide("H3", 1) m1.set_density('g/cm3', 1) m1.volume = 1 - assert pytest.approx(m1.activity) == 3.559778e14 + assert pytest.approx(m1.activity) == 3.559778e14 def test_activity_of_metastable(): From 9ae0c6bd55e045681b1cef213fd6081f9b694534 Mon Sep 17 00:00:00 2001 From: Olek <45364492+yardasol@users.noreply.github.com> Date: Thu, 28 Jul 2022 08:50:48 -0500 Subject: [PATCH 2/7] add `add_elements` Co-authored-by: Jonathan Shimwell --- openmc/material.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openmc/material.py b/openmc/material.py index cd181539c0..40b4958839 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -417,6 +417,18 @@ class Material(IDManagerMixin): for nuclide, (percent, percent_type) in nuclides.items(): self.add_nuclide(nuclide, percent, percent_type) + def add_elements(self, elements: dict): + """ Add multiple elements to a material + + Parameters + ---------- + elements : dict of str to tuple + Dictionary mapping element names to a tuple containing their + atom or weight percent. + """ + + for element, (percent, percent_type) in elements.items(): + self.add_element(element, percent, percent_type) def remove_nuclide(self, nuclide: str): """Remove a nuclide from the material From 623658870d3a51320465aabed254acd64c7671f6 Mon Sep 17 00:00:00 2001 From: yardasol Date: Thu, 28 Jul 2022 10:58:08 -0500 Subject: [PATCH 3/7] merged add_nuclides and add_elements into one function --- openmc/material.py | 45 ++++++++++++++---------- tests/unit_tests/test_material.py | 57 +++++++++++++++++-------------- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 40b4958839..1463c7fa1a 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -403,32 +403,41 @@ class Material(IDManagerMixin): self._nuclides.append(NuclideTuple(nuclide, percent, percent_type)) - def add_nuclides(self, nuclides: dict): - """ Add multiple nuclides to a material + def add_elements_or_nuclides(self, components: dict): + """ Add multiple elements or nuclides to a material Parameters ---------- - nuclides : dict of str to tuple - Dictionary mapping nuclide names to a tuple containing their - atom or weight percent. + components : dict of str to tuple + Dictionary mapping element or nuclide names to a tuple containing + their atom or weight percent, as well as any other arguments. + + Examples + -------- + >> mat = openmc.Material() + >> components = {'Li': (1.0, {'enrichment': 60.0, 'enrichment_target': 'Li7'}), + 'Fl': 1.0, + 'Be6': (0.5, 'wo')} + >> mat.add_elements_or_nuclides(components) """ - for nuclide, (percent, percent_type) in nuclides.items(): - self.add_nuclide(nuclide, percent, percent_type) + for component, args in components.items(): + cv.check_type('component', component, str) + percent_type = 'ao' + if isinstance(args, float): + args = (args,) - def add_elements(self, elements: dict): - """ Add multiple elements to a material + ## check if nuclide + if str.isdigit(component[-1]): + self.add_nuclide(component, *args) + else: # is element + kwargs = {} + if isinstance(args[-1], dict): + kwargs = args[-1] + args = args[:-1] + self.add_element(component, *args, **kwargs) - Parameters - ---------- - elements : dict of str to tuple - Dictionary mapping element names to a tuple containing their - atom or weight percent. - """ - - for element, (percent, percent_type) in elements.items(): - self.add_element(element, percent, percent_type) def remove_nuclide(self, nuclide: str): """Remove a nuclide from the material diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 8dde304856..69ca87f2e6 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -25,31 +25,39 @@ def test_add_nuclide(): with pytest.raises(ValueError): m.add_nuclide('H1', 1.0, 'oa') -def test_add_nuclides(): - """Test adding multipe nuclides at once""" +def test_add_elements_or_nuclides(): + """Test adding multipe elements or nuclides at once""" m = openmc.Material() - nuclides = {'H1': (2.0, 'ao'), - 'O16': (1.0, 'wo')} - m.add_nuclides(nuclides) - - # wt-% - m = openmc.Material() - nuclides = {'H1': (2.0, 'wo'), - 'O16': (1.0, 'wo')} - m.add_nuclides(nuclides) - - # mixed - m = openmc.Material() - nuclides = {'H1': (2.0, 'ao'), - 'O16': (1.0, 'wo')} - m.add_nuclides(nuclides) - - with pytest.raises(TypeError): - m.add_nuclides({'H1': ('1.0', 'ao')}) - with pytest.raises(TypeError): - m.add_nuclides({1.0: ('H1', 'wo')}) + components = {'H1': 2.0, + 'O16': (1.0, 'wo'), + 'Zr': 1.0, + 'O': (1.0, 'wo'), + 'U': (1.0, {'enrichment': 4.5}), + 'Li': (1.0, 'ao', {'enrichment': 60.0, 'enrichment_target': 'Li7'}), + 'H': (1.0, 'ao', {'enrichment': 50.0, 'enrichment_target': 'H2', 'enrichment_type': 'wo'})} + m.add_elements_or_nuclides(components) with pytest.raises(ValueError): - m.add_nuclides({'H1': (1.0, 'oa')}) + m.add_elements_or_nuclides({'U': (1.0, 'ao', {'enrichment': 100.0})}) + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'Pu': (1.0, 'ao', {'enrichment': 3.0})}) + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'U': (1.0, 'ao', {'enrichment': 70.0, 'enrichment_target': 'U235'})}) + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'He': (1.0, 'ao', {'enrichment': 17.0, 'enrichment_target': 'He6'})}) + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'li': (1.0, 'ao')}) # should fail as 1st char is lowercase + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'LI': (1.0, 'ao')}) # should fail as 2nd char is uppercase + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'Xx': (1.0, 'ao')}) # should fail as Xx is not an element + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'n': (1.0, 'ao')}) # check to avoid n for neutron being accepted + with pytest.raises(TypeError): + m.add_elements_or_nuclides({'H1': ('1.0', 'ao')}) + with pytest.raises(TypeError): + m.add_elements_or_nuclides({1.0: ('H1', 'wo')}) + with pytest.raises(ValueError): + m.add_elements_or_nuclides({'H1': (1.0, 'oa')}) def test_remove_nuclide(): @@ -75,7 +83,7 @@ def test_remove_elements(): assert m.nuclides[0].percent == 1.0 -def test_elements(): +def test_add_element(): """Test adding elements.""" m = openmc.Material() m.add_element('Zr', 1.0) @@ -100,7 +108,6 @@ def test_elements(): with pytest.raises(ValueError): m.add_element('n', 1.0) # check to avoid n for neutron being accepted - def test_elements_by_name(): """Test adding elements by name""" m = openmc.Material() From 9317724a5ba3ff2b6a8ea15eb34a1e3628828c41 Mon Sep 17 00:00:00 2001 From: yardasol Date: Thu, 28 Jul 2022 16:03:42 -0500 Subject: [PATCH 4/7] fix examples formatting --- openmc/material.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 1463c7fa1a..8fee0585c9 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -414,11 +414,11 @@ class Material(IDManagerMixin): Examples -------- - >> mat = openmc.Material() - >> components = {'Li': (1.0, {'enrichment': 60.0, 'enrichment_target': 'Li7'}), - 'Fl': 1.0, - 'Be6': (0.5, 'wo')} - >> mat.add_elements_or_nuclides(components) + >>> mat = openmc.Material() + >>> components = {'Li': (1.0, {'enrichment': 60.0, 'enrichment_target': 'Li7'}), + >>> 'Fl': 1.0, + >>> 'Be6': (0.5, 'wo')} + >>> mat.add_elements_or_nuclides(components) """ From 6351b49ea2acf13f90914ef756c6fbf89a296d0a Mon Sep 17 00:00:00 2001 From: yardasol Date: Thu, 28 Jul 2022 16:06:19 -0500 Subject: [PATCH 5/7] add reference to add_elements_or_nuclides in the docstring for Material --- openmc/material.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/material.py b/openmc/material.py index 8fee0585c9..14bec12f33 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -34,7 +34,9 @@ class Material(IDManagerMixin): To create a material, one should create an instance of this class, add nuclides or elements with :meth:`Material.add_nuclide` or :meth:`Material.add_element`, respectively, and set the total material - density with :meth:`Material.set_density()`. The material can then be + density with :meth:`Material.set_density()`. Alternatively, you can + use :meth:`Material.add_elements_or_nuclides()` to pass a dictionary + containing all the component information. The material can then be assigned to a cell using the :attr:`Cell.fill` attribute. Parameters From effc35b8eed3c33c63a56fdead9120143f5551ed Mon Sep 17 00:00:00 2001 From: yardasol Date: Fri, 29 Jul 2022 18:21:06 -0500 Subject: [PATCH 6/7] add_elements_or_nuclides -> add_components - changed the assumptions the function makes about the 'components' parameter - added 'percent_type' parameter - passed changes to unit test --- openmc/material.py | 48 ++++++++++++++++++----------- tests/unit_tests/test_material.py | 50 +++++++++++++++++++------------ 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 14bec12f33..9ba7360ff3 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -35,7 +35,7 @@ class Material(IDManagerMixin): nuclides or elements with :meth:`Material.add_nuclide` or :meth:`Material.add_element`, respectively, and set the total material density with :meth:`Material.set_density()`. Alternatively, you can - use :meth:`Material.add_elements_or_nuclides()` to pass a dictionary + use :meth:`Material.add_components()` to pass a dictionary containing all the component information. The material can then be assigned to a cell using the :attr:`Cell.fill` attribute. @@ -405,41 +405,55 @@ class Material(IDManagerMixin): self._nuclides.append(NuclideTuple(nuclide, percent, percent_type)) - def add_elements_or_nuclides(self, components: dict): + def add_components(self, components: dict, percent_type: str = 'ao'): """ Add multiple elements or nuclides to a material Parameters ---------- - components : dict of str to tuple - Dictionary mapping element or nuclide names to a tuple containing - their atom or weight percent, as well as any other arguments. + 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'} + 'ao' for atom percent and 'wo' for weight percent Examples -------- >>> mat = openmc.Material() - >>> components = {'Li': (1.0, {'enrichment': 60.0, 'enrichment_target': 'Li7'}), - >>> 'Fl': 1.0, - >>> 'Be6': (0.5, 'wo')} - >>> mat.add_elements_or_nuclides(components) + >>> components = {'Li': {'percent': 1.0, + >>> 'enrichment': 60.0, + >>> 'enrichment_target': 'Li7'}, + >>> 'Fl': 1.0, + >>> 'Be6': 0.5} + >>> mat.add_components(components) """ - for component, args in components.items(): + for component, params in components.items(): cv.check_type('component', component, str) - percent_type = 'ao' - if isinstance(args, float): - args = (args,) + if isinstance(params, float): + params = {'percent': params} + + else: + cv.check_type('params', params, dict) + if 'percent' not in params: + raise ValueError("An entry in the dictionary does not have " + "a required key: 'percent'") + + percent = params.pop('percent') + args = (percent, percent_type) ## check if nuclide if str.isdigit(component[-1]): self.add_nuclide(component, *args) else: # is element - kwargs = {} - if isinstance(args[-1], dict): - kwargs = args[-1] - args = args[:-1] + kwargs = params self.add_element(component, *args, **kwargs) + params['percent'] = percent + def remove_nuclide(self, nuclide: str): """Remove a nuclide from the material diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 69ca87f2e6..2c5cc2874c 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -25,39 +25,51 @@ def test_add_nuclide(): with pytest.raises(ValueError): m.add_nuclide('H1', 1.0, 'oa') -def test_add_elements_or_nuclides(): +def test_add_components(): """Test adding multipe elements or nuclides at once""" m = openmc.Material() components = {'H1': 2.0, - 'O16': (1.0, 'wo'), - 'Zr': 1.0, - 'O': (1.0, 'wo'), - 'U': (1.0, {'enrichment': 4.5}), - 'Li': (1.0, 'ao', {'enrichment': 60.0, 'enrichment_target': 'Li7'}), - 'H': (1.0, 'ao', {'enrichment': 50.0, 'enrichment_target': 'H2', 'enrichment_type': 'wo'})} - m.add_elements_or_nuclides(components) + 'O16': 1.0, + 'Zr': 1.0, + 'O': 1.0, + 'U': {'percent': 1.0, + 'enrichment': 4.5}, + 'Li': {'percent': 1.0, + 'enrichment': 60.0, + 'enrichment_target': 'Li7'}, + 'H': {'percent': 1.0, + 'enrichment': 50.0, + 'enrichment_target': 'H2', + 'enrichment_type': 'wo'}} + m.add_components(components) with pytest.raises(ValueError): - m.add_elements_or_nuclides({'U': (1.0, 'ao', {'enrichment': 100.0})}) + m.add_components({'U': {'percent': 1.0, + 'enrichment': 100.0}}) with pytest.raises(ValueError): - m.add_elements_or_nuclides({'Pu': (1.0, 'ao', {'enrichment': 3.0})}) + m.add_components({'Pu': {'percent': 1.0, + 'enrichment': 3.0}}) with pytest.raises(ValueError): - m.add_elements_or_nuclides({'U': (1.0, 'ao', {'enrichment': 70.0, 'enrichment_target': 'U235'})}) + m.add_components({'U': {'percent': 1.0, + 'enrichment': 70.0, + 'enrichment_target':'U235'}}) with pytest.raises(ValueError): - m.add_elements_or_nuclides({'He': (1.0, 'ao', {'enrichment': 17.0, 'enrichment_target': 'He6'})}) + m.add_components({'He': {'percent': 1.0, + 'enrichment': 17.0, + 'enrichment_target': 'He6'}}) with pytest.raises(ValueError): - m.add_elements_or_nuclides({'li': (1.0, 'ao')}) # should fail as 1st char is lowercase + m.add_components({'li': 1.0}) # should fail as 1st char is lowercase with pytest.raises(ValueError): - m.add_elements_or_nuclides({'LI': (1.0, 'ao')}) # should fail as 2nd char is uppercase + m.add_components({'LI': 1.0}) # should fail as 2nd char is uppercase with pytest.raises(ValueError): - m.add_elements_or_nuclides({'Xx': (1.0, 'ao')}) # should fail as Xx is not an element + m.add_components({'Xx': 1.0}) # should fail as Xx is not an element with pytest.raises(ValueError): - m.add_elements_or_nuclides({'n': (1.0, 'ao')}) # check to avoid n for neutron being accepted + m.add_components({'n': 1.0}) # check to avoid n for neutron being accepted with pytest.raises(TypeError): - m.add_elements_or_nuclides({'H1': ('1.0', 'ao')}) + m.add_components({'H1': '1.0'}) with pytest.raises(TypeError): - m.add_elements_or_nuclides({1.0: ('H1', 'wo')}) + m.add_components({1.0: 'H1'}, percent_type = 'wo') with pytest.raises(ValueError): - m.add_elements_or_nuclides({'H1': (1.0, 'oa')}) + m.add_components({'H1': 1.0}, percent_type = 'oa') def test_remove_nuclide(): From 0dd8f70939d8103e11b205b559c2e71a254e4f22 Mon Sep 17 00:00:00 2001 From: yardasol Date: Mon, 1 Aug 2022 09:22:10 -0500 Subject: [PATCH 7/7] add paulromano's 2nd round of suggestions --- openmc/material.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 9ba7360ff3..6c26416801 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -408,6 +408,8 @@ class Material(IDManagerMixin): def add_components(self, components: dict, percent_type: str = 'ao'): """ Add multiple elements or nuclides to a material + .. versionadded:: 0.13.1 + Parameters ---------- components : dict of str to float or dict @@ -442,17 +444,14 @@ class Material(IDManagerMixin): raise ValueError("An entry in the dictionary does not have " "a required key: 'percent'") - percent = params.pop('percent') - args = (percent, percent_type) + params['percent_type'] = percent_type ## check if nuclide if str.isdigit(component[-1]): - self.add_nuclide(component, *args) + self.add_nuclide(component, **params) else: # is element kwargs = params - self.add_element(component, *args, **kwargs) - - params['percent'] = percent + self.add_element(component, **params) def remove_nuclide(self, nuclide: str): """Remove a nuclide from the material