diff --git a/montepy/data_inputs/isotope.py b/montepy/data_inputs/isotope.py index 5a5272a5..4303198d 100644 --- a/montepy/data_inputs/isotope.py +++ b/montepy/data_inputs/isotope.py @@ -20,20 +20,22 @@ class Isotope: def __init__(self, ZAID="", node=None): if node is not None and isinstance(node, ValueNode): + if node.type == float: + node = ValueNode(node.token, str, node.padding) self._tree = node ZAID = node.value - if "." in ZAID: - parts = ZAID.split(".") - try: - assert len(parts) == 2 - int(parts[0]) - except (AssertionError, ValueError) as e: - raise ValueError(f"ZAID: {ZAID} could not be parsed as a valid isotope") - self._ZAID = parts[0] - self.__parse_zaid() + parts = ZAID.split(".") + try: + assert len(parts) <= 2 + int(parts[0]) + except (AssertionError, ValueError) as e: + raise ValueError(f"ZAID: {ZAID} could not be parsed as a valid isotope") + self._ZAID = parts[0] + self.__parse_zaid() + if len(parts) == 2: self._library = parts[1] else: - raise ValueError(f"ZAID: {ZAID} could not be parsed as a valid isotope") + self._library = "" def __parse_zaid(self): """ diff --git a/montepy/data_inputs/material.py b/montepy/data_inputs/material.py index 022c20bc..75cd3413 100644 --- a/montepy/data_inputs/material.py +++ b/montepy/data_inputs/material.py @@ -3,6 +3,8 @@ from montepy.data_inputs import data_input, thermal_scattering from montepy.data_inputs.isotope import Isotope from montepy.data_inputs.material_component import MaterialComponent +from montepy.input_parser import syntax_node +from montepy.input_parser.material_parser import MaterialParser from montepy import mcnp_object from montepy.numbered_mcnp_object import Numbered_MCNP_Object from montepy.errors import * @@ -26,6 +28,8 @@ class Material(data_input.DataInputAbstract, Numbered_MCNP_Object): :type input: Input """ + _parser = MaterialParser() + def __init__(self, input=None): self._material_components = {} self._thermal_scattering = None @@ -37,7 +41,24 @@ def __init__(self, input=None): self._number = num set_atom_frac = False isotope_fractions = self._tree["data"] - for isotope_node, fraction in isotope_fractions: + if isinstance(isotope_fractions, syntax_node.ListNode): + # in python 3.12 this can be replaced with itertools.batched + def batch_gen(): + it = iter(isotope_fractions) + while batch := tuple(itertools.islice(it, 2)): + yield batch + + iterator = batch_gen() + elif isinstance(isotope_fractions, syntax_node.IsotopesNode): + iterator = iter(isotope_fractions) + else: # pragma: no cover + # this is a fall through error, that should never be raised, + # but is here just in case + raise MalformedInputError( + input, + f"Material definitions for material: {self.number} is not valid.", + ) + for isotope_node, fraction in iterator: isotope = Isotope(node=isotope_node) fraction.is_negatable_float = True if not set_atom_frac: @@ -116,6 +137,21 @@ def cells(self): if cell.material == self: yield cell + def format_for_mcnp_input(self, mcnp_version): + """ + Creates a string representation of this MCNP_Object that can be + written to file. + + :param mcnp_version: The tuple for the MCNP version that must be exported to. + :type mcnp_version: tuple + :return: a list of strings for the lines that this input will occupy. + :rtype: list + """ + lines = super().format_for_mcnp_input(mcnp_version) + if self.thermal_scattering is not None: + lines += self.thermal_scattering.format_for_mcnp_input(mcnp_version) + return lines + def add_thermal_scattering(self, law): """ Adds thermal scattering law to the material diff --git a/montepy/input_parser/__init__.py b/montepy/input_parser/__init__.py index 487d3fcb..a80864bb 100644 --- a/montepy/input_parser/__init__.py +++ b/montepy/input_parser/__init__.py @@ -4,6 +4,7 @@ from . import cell_parser from . import data_parser from . import input_reader +from . import material_parser from . import mcnp_input from . import parser_base from . import read_parser diff --git a/montepy/input_parser/material_parser.py b/montepy/input_parser/material_parser.py new file mode 100644 index 00000000..f53e1b4b --- /dev/null +++ b/montepy/input_parser/material_parser.py @@ -0,0 +1,31 @@ +# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +from montepy.input_parser.data_parser import DataParser +from montepy.input_parser import syntax_node + + +class MaterialParser(DataParser): + debugfile = None + + @_( + "introduction isotopes", + "introduction isotopes parameters", + ) + def material(self, p): + ret = {} + for key, node in p.introduction.nodes.items(): + ret[key] = node + ret["data"] = p.isotopes + if hasattr(p, "parameters"): + ret["parameters"] = p.parameters + return syntax_node.SyntaxNode("data", ret) + + @_("isotope_fractions", "number_sequence", "isotope_hybrid_fractions") + def isotopes(self, p): + return p[0] + + @_("number_sequence isotope_fraction", "isotope_hybrid_fractions isotope_fraction") + def isotope_hybrid_fractions(self, p): + ret = p[0] + for node in p.isotope_fraction[1:]: + ret.append(node) + return ret diff --git a/tests/test_integration.py b/tests/test_integration.py index 7da34589..c4ba5afb 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -138,6 +138,10 @@ def test_write_to_file(self): for i, data in enumerate(self.simple_problem.data_inputs): if isinstance(data, material.Material): self.assertEqual(data.number, test_problem.data_inputs[i].number) + if data.thermal_scattering is not None: + assert ( + test_problem.data_inputs[i].thermal_scattering is not None + ) elif isinstance(data, volume.Volume): self.assertEqual(str(data), str(test_problem.data_inputs[i])) else: diff --git a/tests/test_material.py b/tests/test_material.py index 56adfed9..aec22336 100644 --- a/tests/test_material.py +++ b/tests/test_material.py @@ -32,6 +32,22 @@ def test_material_init(self): for component in material.material_components: self.assertEqual(material.material_components[component].fraction, 0.5) + # test implicit library with syntax tree errors + in_str = """m1 1001 0.33 + 8016 0.666667""" + input_card = Input(in_str.split("\n"), BlockType.DATA) + material = Material(input_card) + # test implicit library + in_str = "M20 1001 0.5 2001 0.5 8016.710nc 0.5" + input_card = Input([in_str], BlockType.DATA) + material = Material(input_card) + self.assertEqual(material.number, 20) + self.assertEqual(material.old_number, 20) + self.assertTrue(material.is_atom_fraction) + for component in material.material_components: + self.assertEqual(material.material_components[component].fraction, 0.5) + + # test weight fraction in_str = "M20 1001.80c -0.5 8016.80c -0.5" input_card = Input([in_str], BlockType.DATA) material = Material(input_card) @@ -164,8 +180,6 @@ def test_isotope_init(self): Isotope("1001.80c.5") with self.assertRaises(ValueError): Isotope("hi.80c") - with self.assertRaises(ValueError): - Isotope("1001") def test_isotope_metastable_init(self): isotope = Isotope("13426.02c")