diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 3b1b0b88..8d810620 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -2,8 +2,8 @@ MontePy Changelog ***************** -1.0.0 releases -============== +1.0 releases +============ #Next Version# -------------- @@ -20,8 +20,11 @@ MontePy Changelog * Made ``Material.is_atom_fraction`` settable (:issue:`511`). * Made NumberedObjectCollections act like a set (:issue:`138`). * Automatically added children objects, e.g., the surfaces in a cell, to the problem when the cell is added to the problem (:issue:`63`). +* Added ability to parse all MCNP objects from a string (:issue:`88`). +* Added function: :func:`~montepy.mcnp_problem.MCNP_Problem.parse` to parse arbitrary MCNP object (:issue:`88`). * An error is now raised when typos in object attributes are used, e.g., ``cell.nubmer`` (:issue:`508`). + **Bugs Fixed** * Made it so that a material created from scratch can be written to file (:issue:`512`). diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 5bc82903..f0873b28 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -273,8 +273,7 @@ The ``NumberedObjectCollection`` has various mechanisms internally to avoid numb import montepy prob = montepy.read_input("tests/inputs/test.imcnp") - cell = montepy.Cell() - cell.number = 2 + cell = montepy.Cell(number = 2) prob.cells.append(cell) .. testoutput:: @@ -340,21 +339,23 @@ Using the generators in this way does not cause any issues, but there are ways t by making "stale" information. This can be done by making a copy of it with ``list()``. ->>> for num in problem.cells.numbers: -... print(num) -1 -2 -3 -99 -5 ->>> numbers = list(problem.cells.numbers) ->>> numbers -[1, 2, 3, 99, 5] ->>> problem.cells[1].number = 1000 ->>> 1000 in problem.cells.numbers -True ->>> 1000 in numbers -False +.. doctest:: + + >>> for num in problem.cells.numbers: + ... print(num) + 1 + 2 + 3 + 99 + 5 + >>> numbers = list(problem.cells.numbers) + >>> numbers + [1, 2, 3, 99, 5] + >>> problem.cells[1].number = 1000 + >>> 1000 in problem.cells.numbers + True + >>> 1000 in numbers + False Oh no! When we made a list of the numbers we broke the link, and the new list won't update when the numbers of the cells change, and you can cause issues this way. @@ -370,6 +371,69 @@ If a ``Cell`` or a group of ``Cells`` are cloned their numbers will be to change However, if a whole :class:`~montepy.mcnp_problem.MCNP_Problem` is cloned these objects will not have their numbers changed. For an example for how to clone a numbered object see :ref:`Cloning a Cell`. +Creating Objects from a String +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sometimes its more convenient to create an MCNP object from its input string for MCNP, rather than setting a lot of properties, +or the object you need isn't supported by MontePy yet. +In this case there are a few ways to generate this object. +First all :class:`~montepy.mcnp_object.MCNP_Object` constructors can take a string: + +.. doctest:: + + >>> cell = montepy.Cell("1 0 -2 imp:n=1") + >>> cell.number + 1 + >>> cell.importance[montepy.Particle.NEUTRON] + 1.0 + >>> # surfaces + >>> surf = montepy.AxisPlane("5 PZ 10") + >>> surf.number + 5 + >>> surf.location + 10.0 + >>> # materials + >>> mat = montepy.Material("M1 1001.80c 2 8016.80c 1") + >>> mat.number + 1 + >>> thermal_scat = montepy.ThermalScatteringLaw("MT1 lwrt.40t") + >>> thermal_scat.old_number + 1 + >>> #object linking hasn't occuring + >>> print(thermal_scat.parent_material) + None + +For data inputs and surfaces there are some helper functions that help parse all objects of that type, +and return the appropriate object. +For surfaces this is: :func:`~montepy.surfaces.surface_builder.parse_surface`, +and for data inputs this is :func:`~montepy.data_inputs.data_parser.parse_data`. + +.. doctest:: + >>> surf = montepy.parse_surface("1 cz 5.0") + >>> type(surf) + foo + >>> surf.radius + 5.0 + >>> mat = montepy.parse_data("m1 1001.80c 1") + >>> type(mat) + foo + + +This object is still unlinked from other objects, and won't be kept with a problem. +So there is also :func:`~montepy.mcnp_problem.MCNP_Problem.parse`. +This takes a string, and then creates the MCNP object, +links it to the problem, +links it to its other objects (e.g., surfaces, materials, etc.), +and appends it to necessary collections (if requested): + +.. testcode:: + + cell = problem.parse("123 0 -1005") + assert cell in problem.cells + assert cell.surfaces[1005] is problem.surfaces[1005] + cell = problem.parse("124 0 -1005", append=False) + assert cell not in problem.cells + Surfaces -------- @@ -596,24 +660,18 @@ Order of precedence and grouping is automatically handled by Python so you can e .. testcode:: # build blank surfaces - bottom_plane = montepy.AxisPlane() + bottom_plane = montepy.AxisPlane(number=1) bottom_plane.location = 0.0 - top_plane = montepy.AxisPlane() + top_plane = montepy.AxisPlane(number=2) top_plane.location = 10.0 - fuel_cylinder = montepy.CylinderOnAxis() + fuel_cylinder = montepy.CylinderOnAxis(number=3) fuel_cylinder.radius = 1.26 / 2 - clad_cylinder = montepy.CylinderOnAxis() + clad_cylinder = montepy.CylinderOnAxis( number=4) clad_cylinder.radius = (1.26 / 2) + 1e-3 # fuel, gap, cladding - clad_od = montepy.surfaces.CylinderOnAxis() + clad_od = montepy.CylinderOnAxis(number=5) clad_od.radius = clad_cylinder.radius + 0.1 # add thickness - other_fuel = montepy.surfaces.CylinderOnAxis() + other_fuel = montepy.CylinderOnAxis(number=6) other_fuel.radius = 3.0 - other_fuel.number = 10 - bottom_plane.number = 1 - top_plane.number = 2 - fuel_cylinder.number = 3 - clad_cylinder.number = 4 - clad_od.number = 5 #make weird truncated fuel sample slug_half_space = +bottom_plane & -top_plane & -fuel_cylinder @@ -808,8 +866,7 @@ You can also easy apply a transform to the filling universe with: .. testcode:: import numpy as np - transform = montepy.data_inputs.transform.Transform() - transform.number = 5 + transform = montepy.data_inputs.transform.Transform(number=5) transform.displacement_vector = np.array([1, 2, 0]) cell.fill.transform = transform diff --git a/montepy/__init__.py b/montepy/__init__.py index ad83b5e1..129a6d62 100644 --- a/montepy/__init__.py +++ b/montepy/__init__.py @@ -7,6 +7,7 @@ You will receive an MCNP_Problem object that you will interact with. """ +from . import data_inputs from . import input_parser from . import constants import importlib.metadata @@ -17,6 +18,8 @@ from montepy.data_inputs.transform import Transform from montepy.data_inputs.nuclide import Library, Nuclide from montepy.data_inputs.element import Element +from montepy.data_inputs.thermal_scattering import ThermalScatteringLaw +from montepy.data_inputs.data_parser import parse_data # geometry from montepy.geometry_operators import Operator diff --git a/montepy/cell.py b/montepy/cell.py index 4d7123ff..7ad0e0b3 100644 --- a/montepy/cell.py +++ b/montepy/cell.py @@ -1,10 +1,11 @@ -# 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations - import copy import itertools import numbers +from typing import Union +import montepy from montepy.cells import Cells from montepy.constants import BLANK_SPACE_CONTINUE from montepy.data_inputs import importance, fill, lattice_input, universe_input, volume @@ -12,7 +13,7 @@ from montepy.input_parser.cell_parser import CellParser from montepy.input_parser import syntax_node from montepy.errors import * -from montepy.numbered_mcnp_object import Numbered_MCNP_Object +from montepy.numbered_mcnp_object import Numbered_MCNP_Object, InitInput from montepy.data_inputs.material import Material from montepy.geometry_operators import Operator from montepy.surfaces.half_space import HalfSpace, UnitHalfSpace @@ -64,15 +65,20 @@ class Cell(Numbered_MCNP_Object): complement = ~cell - .. seealso:: * :manual63sec:`5.2` * :manual62:`55` - :param input: the input for the cell definition - :type input: Input + .. versionchanged:: 1.0.0 + + Added number parameter + + :param input: The Input syntax object this will wrap and parse. + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ _ALLOWED_KEYWORDS = { @@ -105,7 +111,12 @@ class Cell(Numbered_MCNP_Object): _parser = CellParser() - def __init__(self, input: montepy.input_parser.mcnp_input.Input = None): + def __init__( + self, + input: InitInput = None, + number: int = None, + ): + self._BLOCK_TYPE = montepy.input_parser.block_type.BlockType.CELL self._CHILD_OBJ_MAP = { "material": Material, "surfaces": Surface, @@ -119,10 +130,9 @@ def __init__(self, input: montepy.input_parser.mcnp_input.Input = None): self._density_node = self._generate_default_node(float, None) self._surfaces = Surfaces() self._complements = Cells() - self._number = self._generate_default_node(int, -1) - super().__init__(input, self._parser) + super().__init__(input, self._parser, number) if not input: - self._generate_default_tree() + self._generate_default_tree(number) self._old_number = copy.deepcopy(self._tree["cell_num"]) self._number = self._tree["cell_num"] mat_tree = self._tree["material"] @@ -371,9 +381,6 @@ def geometry(self): """ The Geometry for this problem. - .. versionadded:: 0.2.0 - Added with the new ability to represent true CSG geometry logic. - The HalfSpace tree that is able to represent this cell's geometry. MontePy's geometry is based upon dividers, which includes both Surfaces, and cells. A half-space is created by choosing one side of the divider. @@ -607,7 +614,7 @@ def _update_values(self): for input_class, (attr, _) in self._INPUTS_TO_PROPERTY.items(): getattr(self, attr)._update_values() - def _generate_default_tree(self): + def _generate_default_tree(self, number: int = None): material = syntax_node.SyntaxNode( "material", { @@ -619,7 +626,7 @@ def _generate_default_tree(self): self._tree = syntax_node.SyntaxNode( "cell", { - "cell_num": self._generate_default_node(int, None), + "cell_num": self._generate_default_node(int, number), "material": material, "geometry": None, "parameters": syntax_node.ParametersNode(), diff --git a/montepy/data_inputs/cell_modifier.py b/montepy/data_inputs/cell_modifier.py index 230e44e5..6ddf90c0 100644 --- a/montepy/data_inputs/cell_modifier.py +++ b/montepy/data_inputs/cell_modifier.py @@ -1,7 +1,7 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. from abc import abstractmethod import montepy -from montepy.data_inputs.data_input import DataInputAbstract +from montepy.data_inputs.data_input import DataInputAbstract, InitInput from montepy.input_parser import syntax_node from montepy.input_parser.block_type import BlockType from montepy.input_parser.mcnp_input import Input, Jump @@ -15,7 +15,7 @@ class CellModifierInput(DataInputAbstract): Examples: IMP, VOL, etc. :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param in_cell_block: if this card came from the cell block of an input file. :type in_cell_block: bool :param key: the key from the key-value pair in a cell @@ -24,7 +24,13 @@ class CellModifierInput(DataInputAbstract): :type value: SyntaxNode """ - def __init__(self, input=None, in_cell_block=False, key=None, value=None): + def __init__( + self, + input: InitInput = None, + in_cell_block: bool = False, + key: str = None, + value: syntax_node.SyntaxNode = None, + ): fast_parse = False if key and value: input = Input([key], BlockType.DATA) @@ -178,8 +184,6 @@ def _tree_value(self): """ The ValueNode that holds the information for this instance, that should be included in the data block. - .. versionadded:: 0.2.0 - :returns: The ValueNode to update the data-block syntax tree with. :rtype: ValueNode """ @@ -191,8 +195,6 @@ def _collect_new_values(self): This will be a list in the same order as :func:`montepy.mcnp_problem.MCNP_Problem.cells`. - .. versionadded:: 0.2.0 - :returns: a list of the ValueNodes to update the data block syntax tree with :rtype: list """ @@ -207,8 +209,6 @@ def _collect_new_values(self): def _update_cell_values(self): """ Updates values in the syntax tree when in the cell block. - - .. versionadded:: 0.2.0 """ pass diff --git a/montepy/data_inputs/data_input.py b/montepy/data_inputs/data_input.py index ed7cab19..daeab526 100644 --- a/montepy/data_inputs/data_input.py +++ b/montepy/data_inputs/data_input.py @@ -1,4 +1,5 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +from __future__ import annotations from abc import abstractmethod import copy @@ -11,9 +12,10 @@ ) from montepy.input_parser.mcnp_input import Input from montepy.particle import Particle -from montepy.mcnp_object import MCNP_Object +from montepy.mcnp_object import MCNP_Object, InitInput import re +from typing import Union class _ClassifierInput(Input): @@ -43,7 +45,7 @@ class DataInputAbstract(MCNP_Object): Parent class to describe all MCNP data inputs. :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param fast_parse: Whether or not to only parse the first word for the type of data. :type fast_parse: bool """ @@ -52,17 +54,29 @@ class DataInputAbstract(MCNP_Object): _classifier_parser = ClassifierParser() - def __init__(self, input=None, fast_parse=False): + def __init__( + self, + input: InitInput = None, + fast_parse=False, + ): self._particles = None if not fast_parse: super().__init__(input, self._parser) if input: self.__split_name(input) else: - input = copy.copy(input) - input.__class__ = _ClassifierInput + if input: + if isinstance(input, str): + input = _ClassifierInput( + input.split("\n"), + montepy.input_parser.block_type.BlockType.DATA, + ) + else: + input = copy.copy(input) + input.__class__ = _ClassifierInput super().__init__(input, self._classifier_parser) - self.__split_name(input) + if input: + self.__split_name(input) @staticmethod @abstractmethod @@ -266,14 +280,16 @@ class DataInput(DataInputAbstract): Catch-all for all other MCNP data inputs. :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param fast_parse: Whether or not to only parse the first word for the type of data. :type fast_parse: bool :param prefix: The input prefix found during parsing (internal use only) :type prefix: str """ - def __init__(self, input=None, fast_parse=False, prefix=None): + def __init__( + self, input: InitInput = None, fast_parse: bool = False, prefix: str = None + ): if prefix: self._load_correct_parser(prefix) super().__init__(input, fast_parse) diff --git a/montepy/data_inputs/data_parser.py b/montepy/data_inputs/data_parser.py index 7d0ca9a2..32b85181 100644 --- a/montepy/data_inputs/data_parser.py +++ b/montepy/data_inputs/data_parser.py @@ -1,4 +1,6 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. + +import montepy from montepy.data_inputs import ( data_input, fill, @@ -26,15 +28,12 @@ } -def parse_data(input): +def parse_data(input: montepy.mcnp_object.InitInput): """ Parses the data input as the appropriate object if it is supported. - .. versionchanged:: 0.2.0 - Removed the ``comment`` parameter, as it's in the syntax tree directly now. - :param input: the Input object for this Data input - :type input: Input + :type input: Union[Input, str] :return: the parsed DataInput object :rtype: DataInput """ diff --git a/montepy/data_inputs/fill.py b/montepy/data_inputs/fill.py index 1801a70b..2c3653d0 100644 --- a/montepy/data_inputs/fill.py +++ b/montepy/data_inputs/fill.py @@ -1,6 +1,6 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. import itertools as it -from montepy.data_inputs.cell_modifier import CellModifierInput +from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput from montepy.data_inputs.transform import Transform from montepy.errors import * from montepy.input_parser.block_type import BlockType @@ -17,7 +17,7 @@ class Fill(CellModifierInput): Object to handle the ``FILL`` input in cell and data blocks. :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param in_cell_block: if this card came from the cell block of an input file. :type in_cell_block: bool :param key: the key from the key-value pair in a cell @@ -31,7 +31,13 @@ class Fill(CellModifierInput): Maps the dimension to its axis number """ - def __init__(self, input=None, in_cell_block=False, key=None, value=None): + def __init__( + self, + input: InitInput = None, + in_cell_block: bool = False, + key: str = None, + value: syntax_node.SyntaxNode = None, + ): self._old_number = self._generate_default_node(int, None) self._old_numbers = None self._universe = None diff --git a/montepy/data_inputs/importance.py b/montepy/data_inputs/importance.py index 0315aefc..74bbcab0 100644 --- a/montepy/data_inputs/importance.py +++ b/montepy/data_inputs/importance.py @@ -2,7 +2,7 @@ import collections import copy import math -from montepy.data_inputs.cell_modifier import CellModifierInput +from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput from montepy.errors import * from montepy.constants import DEFAULT_VERSION, rel_tol, abs_tol from montepy.input_parser import syntax_node @@ -31,7 +31,7 @@ class Importance(CellModifierInput): A data input that sets the importance for a cell(s). :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param in_cell_block: if this card came from the cell block of an input file. :type in_cell_block: bool :param key: the key from the key-value pair in a cell @@ -40,7 +40,13 @@ class Importance(CellModifierInput): :type value: SyntaxNode """ - def __init__(self, input=None, in_cell_block=False, key=None, value=None): + def __init__( + self, + input: InitInput = None, + in_cell_block: bool = False, + key: str = None, + value: syntax_node.SyntaxNode = None, + ): self._particle_importances = {} self._real_tree = {} self._part_combos = [] diff --git a/montepy/data_inputs/lattice_input.py b/montepy/data_inputs/lattice_input.py index 69bc69be..4f12e11c 100644 --- a/montepy/data_inputs/lattice_input.py +++ b/montepy/data_inputs/lattice_input.py @@ -1,6 +1,7 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. import itertools -from montepy.data_inputs.cell_modifier import CellModifierInput + +from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput from montepy.data_inputs.lattice import Lattice from montepy.errors import * from montepy.input_parser.mcnp_input import Jump @@ -14,7 +15,7 @@ class LatticeInput(CellModifierInput): Object to handle the inputs from ``LAT``. :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param in_cell_block: if this card came from the cell block of an input file. :type in_cell_block: bool :param key: the key from the key-value pair in a cell @@ -23,7 +24,13 @@ class LatticeInput(CellModifierInput): :type value: SyntaxNode """ - def __init__(self, input=None, in_cell_block=False, key=None, value=None): + def __init__( + self, + input: InitInput = None, + in_cell_block: bool = False, + key: str = None, + value: syntax_node.SyntaxNode = None, + ): super().__init__(input, in_cell_block, key, value) self._lattice = self._generate_default_node(int, None) self._lattice._convert_to_enum(Lattice, True, int) diff --git a/montepy/data_inputs/material.py b/montepy/data_inputs/material.py index 610c5791..caafbfa3 100644 --- a/montepy/data_inputs/material.py +++ b/montepy/data_inputs/material.py @@ -1,13 +1,15 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations - -import copy import collections as co +import copy import itertools import math +import re from typing import Generator, Union +import warnings import weakref +import montepy from montepy.data_inputs import data_input, thermal_scattering from montepy.data_inputs.nuclide import Library, Nucleus, Nuclide from montepy.data_inputs.element import Element @@ -15,14 +17,10 @@ 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.numbered_mcnp_object import Numbered_MCNP_Object, InitInput from montepy.errors import * from montepy.utilities import * from montepy.particle import LibraryType -import montepy - -import re -import warnings MAX_PRINT_ELEMENTS: int = 5 @@ -265,6 +263,7 @@ class Material(data_input.DataInputAbstract, Numbered_MCNP_Object): 80p 00c + .. versionchanged:: 1.0.0 .. seealso:: @@ -273,15 +272,24 @@ class Material(data_input.DataInputAbstract, Numbered_MCNP_Object): .. versionchanged:: 1.0.0 - This was the primary change for this release. For more details on what changed see :ref:`migrate 0 1`. + * Added number parameter + * This was the primary change for this release. For more details on what changed see :ref:`migrate 0 1`. - :param input: the input that contains the data for this material - :type input: Input + :param input: The Input syntax object this will wrap and parse. + :type input: Union[Input, str] + :param parser: The parser object to parse the input with. + :type parser: MCNP_Parser + :param number: The number to set for this object. + :type number: int """ _parser = MaterialParser() - def __init__(self, input: montepy.input_parser.mcnp_input.Input = None): + def __init__( + self, + input: InitInput = None, + number: int = None, + ): self._components = [] self._thermal_scattering = None self._is_atom_fraction = True @@ -291,6 +299,7 @@ def __init__(self, input: montepy.input_parser.mcnp_input.Input = None): self._nuclei = set() self._default_libs = _DefaultLibraries(self) super().__init__(input) + self._load_init_num(number) if input: num = self._input_number self._old_number = copy.deepcopy(num) @@ -342,6 +351,7 @@ def _grab_default(self, param: syntax_node.SyntaxNode): def _create_default_tree(self): classifier = syntax_node.ClassifierNode() classifier.number = self._number + classifier.number.never_pad = True classifier.prefix = syntax_node.ValueNode("M", str, never_pad=True) classifier.padding = syntax_node.PaddingNode(" ") mats = syntax_node.MaterialsNode("mat stuff") diff --git a/montepy/data_inputs/thermal_scattering.py b/montepy/data_inputs/thermal_scattering.py index 11d6974c..f2879b51 100644 --- a/montepy/data_inputs/thermal_scattering.py +++ b/montepy/data_inputs/thermal_scattering.py @@ -1,10 +1,12 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. -from montepy.data_inputs.data_input import DataInputAbstract +from __future__ import annotations + +import montepy +from montepy.data_inputs.data_input import DataInputAbstract, InitInput from montepy.input_parser.thermal_parser import ThermalParser from montepy import mcnp_object from montepy.errors import * from montepy.utilities import * -import montepy class ThermalScatteringLaw(DataInputAbstract): @@ -21,14 +23,14 @@ class ThermalScatteringLaw(DataInputAbstract): * :manual62:`110` :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param material: the parent Material object that owns this :type material: Material """ _parser = ThermalParser() - def __init__(self, input="", material=None): + def __init__(self, input: InitInput = "", material: montepy.Material = None): self._old_number = self._generate_default_node(int, -1) self._parent_material = None self._scattering_laws = [] diff --git a/montepy/data_inputs/transform.py b/montepy/data_inputs/transform.py index d4917642..9658c806 100644 --- a/montepy/data_inputs/transform.py +++ b/montepy/data_inputs/transform.py @@ -1,31 +1,46 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +from __future__ import annotations import copy +import numpy as np +import re +from typing import Union + +import montepy from montepy import mcnp_object from montepy.data_inputs import data_input from montepy.errors import * -from montepy.numbered_mcnp_object import Numbered_MCNP_Object +from montepy.numbered_mcnp_object import Numbered_MCNP_Object, InitInput from montepy.utilities import * -import numpy as np -import re class Transform(data_input.DataInputAbstract, Numbered_MCNP_Object): """ Input to represent a transform input (TR). - :param input: The Input syntax object this will wrap and parse. - :type input: Input + .. versionchanged:: 1.0.0 + + Added number parameter + + :param input: The Input object representing the input + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ - def __init__(self, input=None, pass_through=False): + def __init__( + self, + input: InitInput = None, + pass_through: bool = False, + number: int = None, + ): self._pass_through = pass_through - self._number = self._generate_default_node(int, -1) self._old_number = self._generate_default_node(int, -1) self._displacement_vector = np.array([]) self._rotation_matrix = np.array([]) self._is_in_degrees = False self._is_main_to_aux = True super().__init__(input) + self._load_init_num(number) if input: words = self._tree["data"] i = 0 diff --git a/montepy/data_inputs/universe_input.py b/montepy/data_inputs/universe_input.py index f189f058..923b59c6 100644 --- a/montepy/data_inputs/universe_input.py +++ b/montepy/data_inputs/universe_input.py @@ -1,6 +1,6 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. import itertools -from montepy.data_inputs.cell_modifier import CellModifierInput +from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput from montepy.errors import * from montepy.constants import DEFAULT_VERSION from montepy.input_parser.mcnp_input import Jump @@ -16,7 +16,7 @@ class UniverseInput(CellModifierInput): and data blocks. :param input: the Input object representing this data input - :type input: Input + :type input: Union[Input, str] :param in_cell_block: if this card came from the cell block of an input file. :type in_cell_block: bool :param key: the key from the key-value pair in a cell @@ -25,7 +25,13 @@ class UniverseInput(CellModifierInput): :type value: SyntaxNode """ - def __init__(self, input=None, in_cell_block=False, key=None, value=None): + def __init__( + self, + input: InitInput = None, + in_cell_block: bool = False, + key: str = None, + value: syntax_node.SyntaxNode = None, + ): self._universe = None self._old_numbers = [] self._old_number = self._generate_default_node(int, Jump()) @@ -42,10 +48,7 @@ def __init__(self, input=None, in_cell_block=False, key=None, value=None): for node in self.data: try: node.is_negatable_identifier = True - if node.value is not None: - self._old_numbers.append(node) - else: - self._old_numbers.append(node) + self._old_numbers.append(node) except ValueError: raise MalformedInputError( input, diff --git a/montepy/input_parser/cell_parser.py b/montepy/input_parser/cell_parser.py index 65d347e6..f9b4ed5c 100644 --- a/montepy/input_parser/cell_parser.py +++ b/montepy/input_parser/cell_parser.py @@ -8,9 +8,6 @@ class CellParser(MCNP_Parser): """ The parser for parsing a Cell input. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :returns: a syntax tree of the cell. :rtype: SyntaxNode """ diff --git a/montepy/input_parser/data_parser.py b/montepy/input_parser/data_parser.py index 497e8e7b..25fa98f7 100644 --- a/montepy/input_parser/data_parser.py +++ b/montepy/input_parser/data_parser.py @@ -9,9 +9,6 @@ class DataParser(MCNP_Parser): """ A parser for almost all data inputs. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :returns: a syntax tree for the data input. :rtype: SyntaxNode """ @@ -148,9 +145,6 @@ class ClassifierParser(DataParser): """ A parser for parsing the first word or classifier of a data input. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :returns: the classifier of the data input. :rtype: ClassifierNode """ diff --git a/montepy/input_parser/input_reader.py b/montepy/input_parser/input_reader.py index 4c508a01..3cc438af 100644 --- a/montepy/input_parser/input_reader.py +++ b/montepy/input_parser/input_reader.py @@ -1,5 +1,5 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. -from montepy import mcnp_problem +import montepy from montepy.constants import DEFAULT_VERSION @@ -26,7 +26,7 @@ def read_input(destination, mcnp_version=DEFAULT_VERSION, replace=True): :raises BrokenObjectLinkError: If a reference is made to an object that is not in the input file. :raises UnknownElement: If an isotope is specified for an unknown element. """ - problem = mcnp_problem.MCNP_Problem(destination) + problem = montepy.mcnp_problem.MCNP_Problem(destination) problem.mcnp_version = mcnp_version problem.parse_input(replace=replace) return problem diff --git a/montepy/input_parser/input_syntax_reader.py b/montepy/input_parser/input_syntax_reader.py index 86a7a129..086ca917 100644 --- a/montepy/input_parser/input_syntax_reader.py +++ b/montepy/input_parser/input_syntax_reader.py @@ -59,9 +59,6 @@ def read_front_matters(fh, mcnp_version): .. warning:: This function will not close the file handle. - .. versionchanged:: 0.2.0 - ``fh`` was changed to be an MCNP_InputFile to hold more information. - :param fh: The file handle of the input file. :type fh: MCNP_InputFile :param mcnp_version: The version of MCNP that the input is intended for. @@ -105,9 +102,6 @@ def read_data(fh, mcnp_version, block_type=None, recursion=False): .. warning:: This function will not close the file handle. - .. versionchanged:: 0.2.0 - ``file_wrapper`` was added to better track which file is being read. - :param fh: The file handle of the input file. :type fh: MCNP_InputFile :param mcnp_version: The version of MCNP that the input is intended for. diff --git a/montepy/input_parser/mcnp_input.py b/montepy/input_parser/mcnp_input.py index 6f6fb9f4..f3380a1a 100644 --- a/montepy/input_parser/mcnp_input.py +++ b/montepy/input_parser/mcnp_input.py @@ -69,9 +69,6 @@ class ParsingNode(ABC): """ Object to represent a single coherent MCNP input, such as an input. - .. versionadded:: 0.2.0 - This was added as part of the parser rework. - :param input_lines: the lines read straight from the input file. :type input_lines: list """ @@ -114,10 +111,6 @@ class Input(ParsingNode): """ Represents a single MCNP "Input" e.g. a single cell definition. - .. versionadded:: 0.2.0 - This was added as part of the parser rework, and rename. - This was a replacement for :class:`Card`. - :param input_lines: the lines read straight from the input file. :type input_lines: list :param block_type: An enum showing which of three MCNP blocks this was inside of. diff --git a/montepy/input_parser/parser_base.py b/montepy/input_parser/parser_base.py index d725cc24..471e2626 100644 --- a/montepy/input_parser/parser_base.py +++ b/montepy/input_parser/parser_base.py @@ -12,9 +12,6 @@ class MetaBuilder(sly.yacc.ParserMeta): Custom MetaClass for allowing subclassing of MCNP_Parser. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - Note: overloading functions is not allowed. """ @@ -57,9 +54,6 @@ def _flatten_rules(classname, basis, attributes): class SLY_Supressor: """ This is a fake logger meant to mostly make warnings dissapear. - - .. versionadded:: 0.2.0 - This was added with the major parser rework. """ def __init__(self): @@ -111,9 +105,6 @@ def __len__(self): class MCNP_Parser(Parser, metaclass=MetaBuilder): """ Base class for all MCNP parsers that provides basics. - - .. versionadded:: 0.2.0 - This was added with the major parser rework. """ # Remove this if trying to see issues with parser diff --git a/montepy/input_parser/read_parser.py b/montepy/input_parser/read_parser.py index ab46c302..a106f7f9 100644 --- a/montepy/input_parser/read_parser.py +++ b/montepy/input_parser/read_parser.py @@ -6,9 +6,6 @@ class ReadParser(MCNP_Parser): """ A parser for handling "read" inputs. - - .. versionadded:: 0.2.0 - This was added with the major parser rework. """ debugfile = None diff --git a/montepy/input_parser/shortcuts.py b/montepy/input_parser/shortcuts.py index 14237101..f6aabe48 100644 --- a/montepy/input_parser/shortcuts.py +++ b/montepy/input_parser/shortcuts.py @@ -5,9 +5,6 @@ class Shortcuts(Enum): """ Enumeration of the possible MCNP shortcuts. - - .. versionadded:: 0.2.0 - This was added with the major parser rework. """ REPEAT = "r" diff --git a/montepy/input_parser/surface_parser.py b/montepy/input_parser/surface_parser.py index f4fc7d3c..fd6f8414 100644 --- a/montepy/input_parser/surface_parser.py +++ b/montepy/input_parser/surface_parser.py @@ -8,9 +8,6 @@ class SurfaceParser(MCNP_Parser): """ A parser for MCNP surfaces. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :rtype: SyntaxNode """ diff --git a/montepy/input_parser/syntax_node.py b/montepy/input_parser/syntax_node.py index 7d8843dd..af8d6c78 100644 --- a/montepy/input_parser/syntax_node.py +++ b/montepy/input_parser/syntax_node.py @@ -25,9 +25,6 @@ class SyntaxNodeBase(ABC): A syntax node is any component of the syntax tree for a parsed input. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :param name: a name for labeling this node. :type name: str """ @@ -196,6 +193,19 @@ def flatten(self): ret += node.flatten() return ret + def _pretty_str(self): + INDENT = 2 + if not self.nodes: + return f"" + ret = f"" def __repr__(self): return str(self) @@ -311,14 +318,23 @@ def flatten(self): ret += node.flatten() return ret + def _pretty_str(self): + INDENT = 2 + ret = f"" ) + def _pretty_str(self): + INDENT = 2 + ret = f"" def __repr__(self): return str(self) @@ -743,9 +777,6 @@ class CommentNode(SyntaxNodeBase): """ Object to represent a comment in an MCNP problem. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :param input: the token from the lexer :type input: Token """ @@ -843,6 +874,9 @@ def comments(self): def __str__(self): return self.format() + def _pretty_str(self): + return str(self) + def __repr__(self): ret = f"COMMENT: " for node in self.nodes: @@ -860,9 +894,6 @@ class ValueNode(SyntaxNodeBase): This stores the original input token, the current value, and the possible associated padding. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :param token: the original token for the ValueNode. :type token: str :param token_type: the type for the ValueNode. @@ -1296,7 +1327,10 @@ def token(self): return self._token def __str__(self): - return f"(Value, {self._value}, padding: {self._padding})" + return f"" + + def _pretty_str(self): + return str(self) def __repr__(self): return str(self) @@ -1366,9 +1400,6 @@ class ParticleNode(SyntaxNodeBase): """ A node to hold particles information in a :class:`ClassifierNode`. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :param name: the name for the node. :type name: str :param token: the original token from parsing @@ -1502,9 +1533,6 @@ class ListNode(SyntaxNodeBase): """ A node to represent a list of values. - .. versionadded:: 0.2.0 - This was added with the major parser rework. - :param name: the name of this node. :type name: str """ @@ -1805,7 +1833,18 @@ def format(self): return ret def __repr__(self): - return f"(Isotopes: {self.nodes})" + return f"(Materials: {self.nodes})" + + def _pretty_str(self): + INDENT = 2 + ret = f" +""" + @property def comments(self): if self.padding is not None: @@ -2457,9 +2501,6 @@ class ParametersNode(SyntaxNodeBase): This behaves like a dictionary and is accessible by their key* - .. versionadded:: 0.2.0 - This was added with the major parser rework. - .. Note:: How to access values. @@ -2509,11 +2550,23 @@ def append(self, val, is_default=False): self._nodes[key] = val def __str__(self): - return f"(Parameters, {self.nodes})" + return f"" def __repr__(self): return str(self) + def _pretty_str(self): + INDENT = 2 + ret = f" list[PaddingNode]: """ Any comments that come before the beginning of the input proper. - .. versionadded:: 0.2.0 - :returns: the leading comments. :rtype: list """ diff --git a/montepy/mcnp_problem.py b/montepy/mcnp_problem.py index 3f36de8c..247a199a 100644 --- a/montepy/mcnp_problem.py +++ b/montepy/mcnp_problem.py @@ -19,7 +19,7 @@ from montepy.data_inputs import parse_data from montepy.input_parser import input_syntax_reader, block_type, mcnp_input from montepy.input_parser.input_file import MCNP_InputFile -from montepy.universes import Universes +from montepy.universes import Universe, Universes from montepy.transforms import Transforms import montepy @@ -40,7 +40,7 @@ class MCNP_Problem: surface.Surface: Surfaces, Material: Materials, transform.Transform: Transforms, - montepy.universe.Universe: Universes, + Universe: Universes, } def __init__(self, destination): @@ -339,7 +339,7 @@ def parse_input(self, check_input=False, replace=True): OBJ_MATCHER = { block_type.BlockType.CELL: (Cell, self._cells), block_type.BlockType.SURFACE: ( - surface_builder.surface_builder, + surface_builder.parse_surface, self._surfaces, ), block_type.BlockType.DATA: (parse_data, self._data_inputs), @@ -623,3 +623,60 @@ def __repr__(self): ret += f"{obj}\n" ret += "\n" return ret + + def parse(self, input: str, append: bool = True) -> montepy.mcnp_object.MCNP_Object: + """ + Parses the MCNP object given by the string, and links it adds it to this problem. + + This attempts to identify the input type by trying to parse it in the following order: + + #. Data Input + #. Surface + #. Cell + + This is done mostly for optimization to go from easiest parsing to hardest. + This will: + + #. Parse the input + #. Link it to other objects in the problem. Note: this will raise an error if those objects don't exist. + #. Append it to the appropriate collection + + :param input: the string describing the input. New lines are allowed but this does not need to meet MCNP line + length rules. + :type input: str + :param append: Whether to append this parsed object to this problem. + :type append: bool + :returns: the parsed object. + :rtype: MCNP_Object + + :raises TypeError: If a str is not given + :raises ParsingError: If this is not a valid input. + :raises BrokenObjectLinkError: if the dependent objects are not already in the problem. + :raises NumberConflictError: if the object's number is already taken + """ + try: + obj = montepy.parse_data(input) + except ParsingError: + try: + obj = montepy.parse_surface(input) + except ParsingError: + obj = montepy.Cell(input) + # let final parsing error bubble up + obj.link_to_problem(self) + if isinstance(obj, montepy.Cell): + obj.update_pointers(self.cells, self.materials, self.surfaces) + if append: + self.cells.append(obj) + elif isinstance(obj, montepy.surfaces.surface.Surface): + obj.update_pointers(self.surfaces, self.data_inputs) + if append: + self.surfaces.append(obj) + else: + obj.update_pointers(self.data_inputs) + if append: + self.data_inputs.append(obj) + if isinstance(obj, Material): + self._materials.append(obj, insert_in_data=False) + if isinstance(obj, transform.Transform): + self._transforms.append(obj, insert_in_data=False) + return obj diff --git a/montepy/numbered_mcnp_object.py b/montepy/numbered_mcnp_object.py index 9b7c4da3..31c14d82 100644 --- a/montepy/numbered_mcnp_object.py +++ b/montepy/numbered_mcnp_object.py @@ -1,9 +1,13 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +from __future__ import annotations from abc import abstractmethod import copy import itertools +from typing import Union + + from montepy.errors import NumberConflictError -from montepy.mcnp_object import MCNP_Object +from montepy.mcnp_object import MCNP_Object, InitInput import montepy from montepy.utilities import * @@ -30,6 +34,40 @@ def _number_validator(self, number): class Numbered_MCNP_Object(MCNP_Object): + """ + An abstract class to represent an mcnp object that has a number. + + .. versionchanged:: 1.0.0 + + Added number parameter + + :param input: The Input syntax object this will wrap and parse. + :type input: Union[Input, str] + :param parser: The parser object to parse the input with. + :type parser: MCNP_Parser + :param number: The number to set for this object. + :type number: int + """ + + def __init__( + self, + input: InitInput, + parser: montepy.input_parser.parser_base.MCNP_Parser, + number: int = None, + ): + self._number = self._generate_default_node(int, -1) + super().__init__(input, parser) + self._load_init_num(number) + + def _load_init_num(self, number): + if number is not None: + if not isinstance(number, int): + raise TypeError( + f"Number must be an int. {number} of type {type(number)} given." + ) + if number < 0: + raise ValueError(f"Number must be 0 or greater. {number} given.") + self.number = number _CHILD_OBJ_MAP = {} """ diff --git a/montepy/surfaces/__init__.py b/montepy/surfaces/__init__.py index e2e29558..872f4aac 100644 --- a/montepy/surfaces/__init__.py +++ b/montepy/surfaces/__init__.py @@ -13,3 +13,6 @@ from .half_space import HalfSpace, UnitHalfSpace from .surface import Surface from .surface_type import SurfaceType + +# promote functions +from .surface_builder import parse_surface diff --git a/montepy/surfaces/axis_plane.py b/montepy/surfaces/axis_plane.py index eae04f42..5c9edb10 100644 --- a/montepy/surfaces/axis_plane.py +++ b/montepy/surfaces/axis_plane.py @@ -1,6 +1,7 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. + from .surface_type import SurfaceType -from .surface import Surface +from .surface import Surface, InitInput from montepy.errors import * from montepy.utilities import * @@ -9,15 +10,21 @@ class AxisPlane(Surface): """ Represents PX, PY, PZ + .. versionchanged:: 1.0.0 + + Added number parameter + :param input: The Input object representing the input - :type input: Input + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ COORDINATE = {SurfaceType.PX: "x", SurfaceType.PY: "y", SurfaceType.PZ: "z"} - def __init__(self, input=None): + def __init__(self, input: InitInput = None, number: int = None): self._location = self._generate_default_node(float, None) - super().__init__(input) + super().__init__(input, number) ST = SurfaceType if input: if self.surface_type not in [ST.PX, ST.PY, ST.PZ]: diff --git a/montepy/surfaces/cylinder_on_axis.py b/montepy/surfaces/cylinder_on_axis.py index 0ade7c00..93d99f65 100644 --- a/montepy/surfaces/cylinder_on_axis.py +++ b/montepy/surfaces/cylinder_on_axis.py @@ -1,6 +1,6 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. from .surface_type import SurfaceType -from .surface import Surface +from .surface import Surface, InitInput from montepy.errors import * from montepy.utilities import * @@ -14,13 +14,20 @@ class CylinderOnAxis(Surface): """ Represents surfaces: CX, CY, CZ + .. versionchanged:: 1.0.0 + + Added number parameter + + :param input: The Input object representing the input - :type input: Input + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ - def __init__(self, input=None): + def __init__(self, input: InitInput = None, number: int = None): self._radius = self._generate_default_node(float, None) - super().__init__(input) + super().__init__(input, number) ST = SurfaceType if input: if self.surface_type not in [ST.CX, ST.CY, ST.CZ]: diff --git a/montepy/surfaces/cylinder_par_axis.py b/montepy/surfaces/cylinder_par_axis.py index 85f52270..3ada7c58 100644 --- a/montepy/surfaces/cylinder_par_axis.py +++ b/montepy/surfaces/cylinder_par_axis.py @@ -1,6 +1,6 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. from .surface_type import SurfaceType -from .surface import Surface +from .surface import Surface, InitInput from montepy.errors import * from montepy.utilities import * @@ -14,8 +14,14 @@ class CylinderParAxis(Surface): """ Represents surfaces: C/X, C/Y, C/Z + .. versionchanged:: 1.0.0 + + Added number parameter + :param input: The Input object representing the input - :type input: Input + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ COORDINATE_PAIRS = { @@ -26,13 +32,13 @@ class CylinderParAxis(Surface): """Which coordinate is what value for each cylinder type. """ - def __init__(self, input=None): + def __init__(self, input: InitInput = None, number: int = None): self._coordinates = [ self._generate_default_node(float, None), self._generate_default_node(float, None), ] self._radius = self._generate_default_node(float, None) - super().__init__(input) + super().__init__(input, number) ST = SurfaceType if input: if self.surface_type not in [ST.C_X, ST.C_Y, ST.C_Z]: diff --git a/montepy/surfaces/general_plane.py b/montepy/surfaces/general_plane.py index 9bf118ea..224ea782 100644 --- a/montepy/surfaces/general_plane.py +++ b/montepy/surfaces/general_plane.py @@ -1,19 +1,34 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +from typing import Union + +import montepy from montepy.errors import * from montepy.surfaces.surface_type import SurfaceType -from montepy.surfaces.surface import Surface +from montepy.surfaces.surface import Surface, InitInput class GeneralPlane(Surface): """ Represents P + .. versionchanged:: 1.0.0 + + Added number parameter + :param input: The Input object representing the input :type input: Input + :param input: The Input object representing the input + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ - def __init__(self, input=None): - super().__init__(input) + def __init__( + self, + input: InitInput = None, + number: int = None, + ): + super().__init__(input, number) if input: if self.surface_type != SurfaceType.P: raise ValueError("A GeneralPlane must be a surface of type P") diff --git a/montepy/surfaces/half_space.py b/montepy/surfaces/half_space.py index e571b6d1..68095684 100644 --- a/montepy/surfaces/half_space.py +++ b/montepy/surfaces/half_space.py @@ -16,9 +16,6 @@ class HalfSpace: """ Class representing a geometry half_space. - .. versionadded:: 0.2.0 - This was added as the core of the rework to how MCNP geometries are implemented. - The term `half-spaces `_ in MontePy is used very loosely, and is not mathematically rigorous. In MontePy a divider is a something that splits a space (R\\ :sup:`3` ) into two half-spaces. At the simplest this would @@ -480,9 +477,6 @@ class UnitHalfSpace(HalfSpace): """ The leaf node for the HalfSpace tree. - .. versionadded:: 0.2.0 - This was added as the core of the rework to how MCNP geometries are implemented. - This can only be used as leaves and represents one half_space of a a divider. The easiest way to generate one is with the divider with unary operators. diff --git a/montepy/surfaces/surface.py b/montepy/surfaces/surface.py index a340f7be..856a0bb9 100644 --- a/montepy/surfaces/surface.py +++ b/montepy/surfaces/surface.py @@ -1,32 +1,48 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +from __future__ import annotations import copy +import re +from typing import Union + +import montepy from montepy.errors import * from montepy.data_inputs import transform from montepy.input_parser import syntax_node from montepy.input_parser.surface_parser import SurfaceParser -from montepy.numbered_mcnp_object import Numbered_MCNP_Object +from montepy.numbered_mcnp_object import Numbered_MCNP_Object, InitInput from montepy.surfaces import half_space from montepy.surfaces.surface_type import SurfaceType from montepy.utilities import * -import re class Surface(Numbered_MCNP_Object): """ Object to hold a single MCNP surface + .. versionchanged:: 1.0.0 + + Added number parameter + :param input: The Input object representing the input - :type input: Input + :type input: Union[Input, str] + :param number: The number to set for this object. + :type number: int """ _parser = SurfaceParser() - def __init__(self, input=None): - super().__init__(input, self._parser) + def __init__( + self, + input: InitInput = None, + number: int = None, + ): self._CHILD_OBJ_MAP = { "periodic_surface": Surface, "transform": transform.Transform, } + self._BLOCK_TYPE = montepy.input_parser.block_type.BlockType.SURFACE + self._number = self._generate_default_node(int, -1) + super().__init__(input, self._parser, number) self._periodic_surface = None self._old_periodic_surface = self._generate_default_node(int, None) self._transform = None @@ -35,7 +51,6 @@ def __init__(self, input=None): self._is_white_boundary = False self._surface_constants = [] self._surface_type = self._generate_default_node(str, None) - self._number = self._generate_default_node(int, -1) self._modifier = self._generate_default_node(str, None) # surface number if input: diff --git a/montepy/surfaces/surface_builder.py b/montepy/surfaces/surface_builder.py index d72fa99d..a86cd139 100644 --- a/montepy/surfaces/surface_builder.py +++ b/montepy/surfaces/surface_builder.py @@ -1,21 +1,18 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. from montepy.surfaces.axis_plane import AxisPlane -from montepy.surfaces.surface import Surface +from montepy.surfaces.surface import Surface, InitInput from montepy.surfaces.surface_type import SurfaceType from montepy.surfaces.cylinder_on_axis import CylinderOnAxis from montepy.surfaces.cylinder_par_axis import CylinderParAxis from montepy.surfaces.general_plane import GeneralPlane -def surface_builder(input): +def parse_surface(input: InitInput): """ Builds a Surface object for the type of Surface - .. versionchanged:: 0.2.0 - The ``comments`` argument has been removed with the simpler init function. - :param input: The Input object representing the input - :type input: Input + :type input: Union[Input, str] :returns: A Surface object properly parsed. If supported a sub-class of Surface will be given. :rtype: Surface """ @@ -32,3 +29,17 @@ def surface_builder(input): return GeneralPlane(input) else: return buffer_surface + + +surface_builder = parse_surface +""" +Alias for :func:`parse_surface`. + +:deprecated: 1.0.0 + Renamed to be :func:`parse_surface` to be more pythonic. + +:param input: The Input object representing the input +:type input: Union[Input, str] +:returns: A Surface object properly parsed. If supported a sub-class of Surface will be given. +:rtype: Surface +""" diff --git a/montepy/universe.py b/montepy/universe.py index 3a17552a..e4b7a2e3 100644 --- a/montepy/universe.py +++ b/montepy/universe.py @@ -16,7 +16,7 @@ class Universe(Numbered_MCNP_Object): :type number: int """ - def __init__(self, number): + def __init__(self, number: int): self._number = self._generate_default_node(int, -1) if not isinstance(number, int): raise TypeError("number must be int") @@ -28,7 +28,7 @@ class Parser: def parse(self, token_gen, input): return syntax_node.SyntaxNode("fake universe", {}) - super().__init__(Input(["U"], BlockType.DATA), Parser()) + super().__init__(Input(["U"], BlockType.DATA), Parser(), number) @property def cells(self): diff --git a/tests/test_cell_problem.py b/tests/test_cell_problem.py index 0a02a422..4d2b1673 100644 --- a/tests/test_cell_problem.py +++ b/tests/test_cell_problem.py @@ -12,7 +12,7 @@ class TestCellClass(TestCase): def test_bad_init(self): with self.assertRaises(TypeError): - Cell("5") + Cell(5) # TODO test updating cell geometry once done def test_cell_validator(self): @@ -29,9 +29,7 @@ def test_cell_validator(self): # TODO test geometry stuff def test_number_setter(self): - in_str = "1 0 2" - card = Input([in_str], BlockType.CELL) - cell = Cell(card) + cell = Cell("1 0 2") cell.number = 5 self.assertEqual(cell.number, 5) with self.assertRaises(TypeError): @@ -160,6 +158,15 @@ def test_init(line, is_void, mat_number, density, atom_dens, parameters): assert cell.parameters[parameter]["data"][0].value == pytest.approx(value) +def test_blank_num_init(): + cell = Cell(number=5) + assert cell.number == 5 + with pytest.raises(TypeError): + Cell(number="hi") + with pytest.raises(ValueError): + Cell(number=-1) + + @pytest.mark.parametrize("line", ["foo", "foo bar", "1 foo", "1 1 foo"]) def test_malformed_init(line): with pytest.raises(montepy.errors.MalformedInputError): diff --git a/tests/test_integration.py b/tests/test_integration.py index e03d27b9..278cd6d1 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1191,3 +1191,23 @@ def test_read_write_cycle(file): ) else: raise e + + +def test_arbitrary_parse(simple_problem): + simple_problem = simple_problem.clone() + for append in [False, True]: + cell = simple_problem.parse("20 0 -1005", append) + assert (cell in simple_problem.cells) == append + assert cell.number == 20 + assert cell.surfaces[1005] in simple_problem.surfaces + surf = simple_problem.parse("5 SO 7.5", append) + assert (surf in simple_problem.surfaces) == append + assert surf.number == 5 + mat = simple_problem.parse("m123 1001.80c 1.0 8016.80c 2.0", append) + assert (mat in simple_problem.materials) == append + assert (mat in simple_problem.data_inputs) == append + assert mat.number == 123 + transform = simple_problem.parse("tr25 0 0 1", append) + assert (transform in simple_problem.transforms) == append + with pytest.raises(ParsingError): + simple_problem.parse("123 hello this is invalid") diff --git a/tests/test_material.py b/tests/test_material.py index 64e753d5..71010351 100644 --- a/tests/test_material.py +++ b/tests/test_material.py @@ -101,8 +101,7 @@ def test_material_parameter_parsing(_): "M20 1001.80c 1.0 gas = 0 nlib = 00c", "M120 nlib=80c 1001 1.0", ]: - input = Input([line], BlockType.DATA) - material = Material(input) + material = Material(line) def test_material_validator(_): material = Material() @@ -495,6 +494,10 @@ def test_mat_clone_bad(_, args, error): with pytest.raises(error): mat.clone(*args) + def test_mat_num_init(_): + mat = Material(number=5) + assert mat.number == 5 + @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) @given( lib_num=st.integers(0, 99), diff --git a/tests/test_surfaces.py b/tests/test_surfaces.py index 9d22ef8b..d20dee31 100644 --- a/tests/test_surfaces.py +++ b/tests/test_surfaces.py @@ -17,9 +17,7 @@ class testSurfaces(TestCase): def test_surface_init(self): - in_str = "1 PZ 0.0" - card = Input([in_str], BlockType.SURFACE) - surf = Surface(card) + surf = Surface("1 PZ 0.0") self.assertEqual(surf.number, 1) self.assertEqual(surf.old_number, 1) self.assertEqual(len(surf.surface_constants), 1) @@ -79,6 +77,8 @@ def test_surface_init(self): card = Input([in_str], BlockType.SURFACE) with self.assertRaises(MalformedInputError): Surface(card) + surf = Surface(number=5) + assert surf.number == 5 def test_validator(self): surf = Surface() @@ -236,6 +236,8 @@ def test_axis_plane_init(self): surf = montepy.surfaces.axis_plane.AxisPlane( Input([bad_input], BlockType.SURFACE) ) + surf = montepy.surfaces.axis_plane.AxisPlane(number=5) + assert surf.number == 5 def test_cylinder_on_axis_init(self): bad_inputs = ["1 P 0.0", "1 CZ 0.0 10.0"] @@ -244,6 +246,8 @@ def test_cylinder_on_axis_init(self): surf = montepy.surfaces.cylinder_on_axis.CylinderOnAxis( Input([bad_input], BlockType.SURFACE) ) + surf = montepy.surfaces.cylinder_on_axis.CylinderOnAxis(number=5) + assert surf.number == 5 def test_cylinder_par_axis_init(self): bad_inputs = ["1 P 0.0", "1 C/Z 0.0"] @@ -252,6 +256,8 @@ def test_cylinder_par_axis_init(self): surf = montepy.surfaces.cylinder_par_axis.CylinderParAxis( Input([bad_input], BlockType.SURFACE) ) + surf = montepy.surfaces.cylinder_par_axis.CylinderParAxis(number=5) + assert surf.number == 5 def test_gen_plane_init(self): bad_inputs = ["1 PZ 0.0", "1 P 0.0"] @@ -260,6 +266,8 @@ def test_gen_plane_init(self): surf = montepy.surfaces.general_plane.GeneralPlane( Input([bad_input], BlockType.SURFACE) ) + surf = montepy.surfaces.general_plane.GeneralPlane(number=5) + assert surf.number == 5 def test_axis_plane_location_setter(self): in_str = "1 PZ 0.0" diff --git a/tests/test_syntax_parsing.py b/tests/test_syntax_parsing.py index cbd80e82..67c5e5fa 100644 --- a/tests/test_syntax_parsing.py +++ b/tests/test_syntax_parsing.py @@ -440,6 +440,7 @@ def test_syntax_trailing_comments(self): def test_syntax_str(self): str(self.test_node) repr(self.test_node) + self.test_node._pretty_str() class TestGeometryTree(TestCase): @@ -478,6 +479,7 @@ def test_geometry_str(self): test = self.test_tree str(test) repr(test) + test._pretty_str() def test_geometry_comments(self): test = copy.deepcopy(self.test_tree) @@ -696,6 +698,7 @@ def test_list_str(self): list_node.append(syntax_node.ValueNode("1.0", float)) str(list_node) repr(list_node) + list_node._pretty_str() def test_list_slicing(self): list_node = syntax_node.ListNode("list") @@ -793,6 +796,7 @@ def test_isotopes_str(self): isotopes.append_nuclide(("isotope_fraction", zaid, concentration)) str(isotopes) repr(isotopes) + isotopes._pretty_str() def test_isotopes_iter(self): isotopes = syntax_node.MaterialsNode("test") @@ -1573,6 +1577,7 @@ def test_parameter_dict(self): def test_parameter_str(self): str(self.param) repr(self.param) + self.param._pretty_str() def test_parameter_format(self): self.assertEqual(self.param.format(), "vol=1.0") diff --git a/tests/test_transform.py b/tests/test_transform.py index 421530a1..b1d59574 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -48,6 +48,8 @@ def test_transform_init(self): with self.assertRaises(MalformedInputError): card = Input(["TR5:n,p 0.0 0.0 0.0"], BlockType.DATA) Transform(card) + transform = Transform(number=5) + assert transform.number == 5 # test vanilla case in_str = "tr5 " + "1.0 " * 3 + "0.0 " * 9