diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 62231dfe..0564b1f6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,6 +12,7 @@ Fixes # (issue number) - [ ] I have performed a self-review of my own code. - [ ] The code follows the standards outlined in the [development documentation](https://idaholab.github.io/MontePy/developing.html). +- [ ] I have formatted my code with `black` version 25. - [ ] I have added tests that prove my fix is effective or that my feature works (if applicable). --- diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72e4c593..0cfdb467 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: CI testing on: pull_request: - branches: [develop, alpha-test-dev] + branches: [develop, alpha-test-dev, alpha-test] push: branches: [develop, main, alpha-test] @@ -42,8 +42,8 @@ jobs: - run: pip install --user . montepy[develop] - run: pip freeze - name: Upload build artifacts - uses: actions/upload-artifact@v4.3.1 - if: ${{ matrix.python-version == '3.12' && matrix.numpy-version == '2.0' }} + uses: actions/upload-artifact@v4 + if: ${{ matrix.python-version == '3.12'}} with: name: build path: dist/* diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 0745ac08..84f284dc 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -5,7 +5,7 @@ MontePy Changelog 1.0 releases ============ -1.0.0-alpha1 +1.0.0-alpha2 -------------- **Features Added** @@ -14,7 +14,7 @@ MontePy Changelog * Made it easier to create an Isotope (now Nuclide): ``montepy.Nuclide("H-1.80c")`` (:issue:`505`). * When a typo in an object attribute is made an Error is raised rather than silently having no effect (:issue:`508`). * Improved material printing to avoid very long lists of components (:issue:`144`). -* Allow querying for materials by components (:issue:`95`). +* Allow querying for materials by components (:issue:`95`), either broadly or specifically (:issue:`642`). * Added support for getting and setting default libraries, e.g., ``nlib``, from a material (:issue:`369`). * Added most objects to the top level so they can be accessed like: ``montepy.Cell``. * Made ``Material.is_atom_fraction`` settable (:issue:`511`). @@ -23,6 +23,9 @@ MontePy Changelog * 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`). +* Warnings are no longer raised for comments that exceed the maximum line lengths (:issue:`188`). +* Particle type exceptions are now warnings, not errors (:issue:`381`). +* Allow any ``Real`` type for floating point numbers and any ``Integral`` type for integer numbers during type enforcement (:issue:`679`). **Bugs Fixed** @@ -62,6 +65,14 @@ MontePy Changelog 0.5 releases ============ +#Next Release# +-------------- + +**Bug Fixes** + +* Fixed parsing error with not being able to parse a blank ``sdef`` (:issue:`636`). +* Fixed parsing error with parsing ``SSW`` (:issue:`639`). + 0.5.3 -------------- diff --git a/doc/source/conf.py b/doc/source/conf.py index 8a8bc3a3..be7c320e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -21,7 +21,7 @@ # -- Project information ----------------------------------------------------- project = "MontePy" -copyright = "2021 – 2024, Battelle Energy Alliance LLC." +copyright = "2021 – 2025, Battelle Energy Alliance LLC." author = "Micah D. Gale (@micahgale), Travis J. Labossiere-Hickman (@tjlaboss)" version = importlib.metadata.version("montepy") @@ -81,7 +81,10 @@ "https://mcnp.lanl.gov/pdf_files/TechReport_2022_LANL_LA-UR-22-30006" "Rev.1_KuleszaAdamsEtAl.pdf" ) -UM631 = "https://www.osti.gov/servlets/purl/2372634" +UM631 = ( + "https://mcnp.lanl.gov/pdf_files/TechReport_2024_LANL_LA-UR-24-24602" + "Rev.1_KuleszaAdamsEtAl.pdf" +) UM62 = ( "https://mcnp.lanl.gov/pdf_files/TechReport_2017_LANL_LA-UR-17-29981" "_WernerArmstrongEtAl.pdf" diff --git a/doc/source/dev_standards.rst b/doc/source/dev_standards.rst index 3ca9da19..07ca4db7 100644 --- a/doc/source/dev_standards.rst +++ b/doc/source/dev_standards.rst @@ -57,6 +57,7 @@ Doc Strings All public (not ``_private``) classes and functions *must* have doc strings. Most ``_private`` classes and functions should still be documented for other developers. +`NumPy's style guide is the standard `_ used for MontePy doc strings. Mandatory Elements ^^^^^^^^^^^^^^^^^^ @@ -111,8 +112,7 @@ Here is the docstrings for :class:`~montepy.cell.Cell`. .. code-block:: python class Cell(Numbered_MCNP_Object): - """ - Object to represent a single MCNP cell defined in CSG. + """Object to represent a single MCNP cell defined in CSG. Examples ^^^^^^^^ @@ -130,7 +130,7 @@ Here is the docstrings for :class:`~montepy.cell.Cell`. .. doctest:: python >>> cell.number = 5 - >>> cell.material + >>> print(cell.material) None >>> mat = montepy.Material() >>> mat.number = 20 @@ -146,20 +146,33 @@ Here is the docstrings for :class:`~montepy.cell.Cell`. complement = ~cell + See Also + -------- + + * :manual631sec:`5.2` + * :manual63sec:`5.2` + * :manual62:`55` - .. seealso:: - * :manual63sec:`5.2` - * :manual62:`55` + .. versionchanged:: 1.0.0 - :param input: the input for the cell definition - :type input: Input + Added number parameter + Parameters + ---------- + input : Union[Input, str] + The Input syntax object this will wrap and parse. + number : int + The number to set for this object. """ # snip - def __init__(self, input: montepy.input_parser.mcnp_input.Input = None): + def __init__( + self, + input: InitInput = None, + number: int = None, + ): Testing ------- diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 13aa37bb..e85445f3 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -409,14 +409,15 @@ 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. @@ -1019,21 +1020,28 @@ on the element, not just the elemental nuclide. >>> montepy.Nuclide("B-0") in mat True -For more complicated checks there is the :func:`~montepy.data_inputs.material.Material.contains`. -This takes a plurality of nuclides as well as a threshold. -This returns ``True`` if and only if the material contains *all* nuclides +For more complicated checks there is the :func:`~montepy.data_inputs.material.Material.contains_all`, and +:func:`~montepy.data_inputs.material.Material.contains_any`. +These functions take a plurality of nuclides as well as a threshold. +The function ``contains_all`` returns ``True`` if and only if the material contains *all* nuclides +with a fraction above the threshold. +The function ``contains_any`` returns ``True`` if any of the material contains *any* nuclides with a fraction above the threshold. .. doctest:: - >>> mat.contains("H-1.80c") + >>> mat.contains_all("H-1.80c") False - >>> mat.contains("U-235", "U-238", threshold=1.0) + >>> mat.contains_all("U-235", "U-238", threshold=1.0) True - >>> mat.contains("U-235.80c", "B-10") + >>> mat.contains_all("U-235.80c", "B-10") True - >>> mat.contains("U-235.80c", "B-10", threshold=1e-3) + >>> mat.contains_all("U-235.80c", "B-10", threshold=1e-3) False + >>> mat.contains_all("H-1.80c", "U-235.80c") + False + >>> mat.contains_any("H-1.80c", "U-235.80c") + True Finding Nuclides """""""""""""""" diff --git a/montepy/__init__.py b/montepy/__init__.py index 129a6d62..7004fb52 100644 --- a/montepy/__init__.py +++ b/montepy/__init__.py @@ -1,5 +1,6 @@ +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. -""" MontePy is a library for reading, editing, and writing MCNP input files. +"""MontePy is a library for reading, editing, and writing MCNP input files. This creates a semantic understanding of the MCNP input file. start by running montepy.read_input(). diff --git a/montepy/__main__.py b/montepy/__main__.py index 5141ea4b..110f6dd0 100644 --- a/montepy/__main__.py +++ b/montepy/__main__.py @@ -15,11 +15,12 @@ def define_args(args=None): - """ - Sets and parses the command line arguments. + """Sets and parses the command line arguments. - :returns: the arguments that were parsed - :rtype: argparse.NameSpace + Returns + ------- + argparse.NameSpace + the arguments that were parsed """ parser = argparse.ArgumentParser( prog="montepy", @@ -45,11 +46,12 @@ def define_args(args=None): def check_inputs(files): - """ - Checks input files for syntax errors. + """Checks input files for syntax errors. - :param files: a list of paths to check and show warnings for errors. - :type files: list + Parameters + ---------- + files : list + a list of paths to check and show warnings for errors. """ for file in files: if not Path(file).is_file(): @@ -61,9 +63,7 @@ def check_inputs(files): def main(): # pragma: no cover - """ - The main function - """ + """The main function""" args = define_args() if args.check: check_inputs(args.check) diff --git a/montepy/_cell_data_control.py b/montepy/_cell_data_control.py index 6a96a8fb..3c1a023a 100644 --- a/montepy/_cell_data_control.py +++ b/montepy/_cell_data_control.py @@ -3,9 +3,7 @@ class CellDataPrintController: - """ - Class for controlling if cell modifier data is printed in cell or data blocks. - """ + """Class for controlling if cell modifier data is printed in cell or data blocks.""" def __init__(self): self._print_data = {} diff --git a/montepy/_scripts/change_to_ascii.py b/montepy/_scripts/change_to_ascii.py index 0f056d7e..2bb735fd 100644 --- a/montepy/_scripts/change_to_ascii.py +++ b/montepy/_scripts/change_to_ascii.py @@ -3,13 +3,17 @@ def define_args(args): - """ - Parses the arguments from the command line. + """Parses the arguments from the command line. + + Parameters + ---------- + args : list + the arguments from the command line. - :param args: the arguments from the command line. - :type args: list - :returns: the parsed arguments (with argparse) - :rtype: argparse.Namespace + Returns + ------- + argparse.Namespace + the parsed arguments (with argparse) """ parser = argparse.ArgumentParser( prog="Change_to_ascii", @@ -37,11 +41,12 @@ def define_args(args): def strip_characters(args): - """ - Strips non-ascii characters from the input file, and writes out the output file. + """Strips non-ascii characters from the input file, and writes out the output file. - :param args: the parsed command line arguments. - :type args: argparse.Namespace + Parameters + ---------- + args : argparse.Namespace + the parsed command line arguments. """ if args.whitespace: replacer = " " @@ -71,11 +76,12 @@ def strip_characters(args): def main(args=None): - """ - Main runner function. + """Main runner function. - :param args: The arguments passed from the command line. - :type args: list + Parameters + ---------- + args : list + The arguments passed from the command line. """ if args is None: args = sys.argv[1:] diff --git a/montepy/_singleton.py b/montepy/_singleton.py index 296f8b52..ab566fea 100644 --- a/montepy/_singleton.py +++ b/montepy/_singleton.py @@ -5,8 +5,7 @@ class SingletonGroup(ABC): - """ - A base class for implementing a Singleton-like data structure. + """A base class for implementing a Singleton-like data structure. This treats immutable objects are Enums without having to list all. This is used for: Element, Nucleus, Library. When a brand new instance @@ -29,9 +28,7 @@ def __new__(cls, *args, **kwargs): return cls._instances[args + kwargs_t] def __init_subclass__(cls, **kwargs): - """ - Workaround to get sphinx autodoc happy. - """ + """Workaround to get sphinx autodoc happy.""" cls._instances = {} super().__init_subclass__(**kwargs) @@ -45,9 +42,7 @@ def __new__(cls, *args, **kwargs): cls.__new__ = staticmethod(__new__) def __deepcopy__(self, memo): - """ - Make deepcopy happy. - """ + """Make deepcopy happy.""" if self in memo: return memo[self] memo[self] = self @@ -55,7 +50,5 @@ def __deepcopy__(self, memo): @abstractmethod def __reduce__(self): - """ - See: - """ + """See: """ pass diff --git a/montepy/cell.py b/montepy/cell.py index 7ad0e0b3..77c509af 100644 --- a/montepy/cell.py +++ b/montepy/cell.py @@ -1,13 +1,11 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations import copy import itertools -import numbers from typing import Union +from numbers import Integral, Real -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 from montepy.data_inputs.data_parser import PREFIX_MATCHES from montepy.input_parser.cell_parser import CellParser @@ -30,8 +28,7 @@ def _link_geometry_to_cell(self, geom): class Cell(Numbered_MCNP_Object): - """ - Object to represent a single MCNP cell defined in CSG. + """Object to represent a single MCNP cell defined in CSG. Examples ^^^^^^^^ @@ -65,20 +62,24 @@ class Cell(Numbered_MCNP_Object): complement = ~cell - .. seealso:: + See Also + -------- + + * :manual631sec:`5.2` + * :manual63sec:`5.2` + * :manual62:`55` - * :manual63sec:`5.2` - * :manual62:`55` .. 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 + Parameters + ---------- + input : Union[Input, str] + The Input syntax object this will wrap and parse. + number : int + The number to set for this object. """ _ALLOWED_KEYWORDS = { @@ -145,9 +146,7 @@ def __init__( self._parse_keyword_modifiers() def _parse_geometry(self): - """ - Parses the cell's geometry definition, and stores it - """ + """Parses the cell's geometry definition, and stores it""" geometry = self._tree["geometry"] if geometry is not None: self._geometry = HalfSpace.parse_input_node(geometry) @@ -155,9 +154,7 @@ def _parse_geometry(self): self._geometry = None def _parse_keyword_modifiers(self): - """ - Parses the parameters to make the object and load as an attribute - """ + """Parses the parameters to make the object and load as an attribute""" found_class_prefixes = set() for key, value in self.parameters.nodes.items(): for input_class in PREFIX_MATCHES: @@ -190,32 +187,32 @@ def _parse_keyword_modifiers(self): self._tree["parameters"].append(tree, True) def _load_blank_modifiers(self): - """ - Goes through and populates all the modifier attributes - """ + """Goes through and populates all the modifier attributes""" for input_class, (attr, _) in self._INPUTS_TO_PROPERTY.items(): setattr(self, attr, input_class(in_cell_block=True)) @property def importance(self): - """ - The importances for this cell for various particle types. + """The importances for this cell for various particle types. Each particle's importance is a property of Importance. e.g., ``cell.importance.photon = 1.0``. - :returns: the importance for the Cell. - :rtype: Importance + Returns + ------- + Importance + the importance for the Cell. """ return self._importance @property def universe(self): - """ - The Universe that this cell is in. + """The Universe that this cell is in. - :returns: the Universe the cell is in. - :rtype: Universe + Returns + ------- + Universe + the Universe the cell is in. """ return self._universe.universe @@ -227,30 +224,28 @@ def universe(self, value): @property def fill(self): - """ - the Fill object representing how this cell is filled. + """the Fill object representing how this cell is filled. This not only describes the universe that is filling this, but more complex things like transformations, and matrix fills. - :returns: The Fill object of how this cell is to be filled. - :rtype: Fill + Returns + ------- + Fill + The Fill object of how this cell is to be filled. """ return self._fill @property def _fill_transform(self): - """ - A simple wrapper to get the transform of the fill or None. - """ + """A simple wrapper to get the transform of the fill or None.""" if self.fill: return self.fill.transform return None # pragma: no cover @property def not_truncated(self): - """ - Indicates if this cell has been marked as not being truncated for optimization. + """Indicates if this cell has been marked as not being truncated for optimization. See Note 1 from section 3.3.1.5.1 of the user manual (LA-UR-17-29981). @@ -266,8 +261,11 @@ def not_truncated(self): -- LA-UR-17-29981. - :rtype: bool - :returns: True if this cell has been marked as not being truncated by the parent filled cell. + Returns + ------- + bool + True if this cell has been marked as not being truncated by + the parent filled cell. """ if self.universe.number == 0: return False @@ -283,21 +281,23 @@ def not_truncated(self, value): @property def old_universe_number(self): - """ - The original universe number read in from the input file. + """The original universe number read in from the input file. - :returns: the number of the Universe for the cell in the input file. - :rtype: int + Returns + ------- + int + the number of the Universe for the cell in the input file. """ return self._universe.old_number @property def lattice(self): - """ - The type of lattice being used by the cell. + """The type of lattice being used by the cell. - :returns: the type of lattice being used - :rtype: Lattice + Returns + ------- + Lattice + the type of lattice being used """ return self._lattice.lattice @@ -311,13 +311,14 @@ def lattice(self): @property def volume(self): - """ - The volume for the cell. + """The volume for the cell. Will only return a number if the volume has been manually set. - :returns: the volume that has been manually set or None. - :rtype: float, None + Returns + ------- + float, None + the volume that has been manually set or None. """ return self._volume.volume @@ -331,8 +332,7 @@ def volume(self): @property def volume_mcnp_calc(self): - """ - Indicates whether or not MCNP will attempt to calculate the cell volume. + """Indicates whether or not MCNP will attempt to calculate the cell volume. This can be disabled by either manually setting the volume or disabling this calculation globally. @@ -341,45 +341,50 @@ def volume_mcnp_calc(self): See :func:`~montepy.cells.Cells.allow_mcnp_volume_calc` - :returns: True iff MCNP will try to calculate the volume for this cell. - :rtype: bool + Returns + ------- + bool + True iff MCNP will try to calculate the volume for this + cell. """ return self._volume.is_mcnp_calculated @property def volume_is_set(self): - """ - Whether or not the volume for this cell has been set. + """Whether or not the volume for this cell has been set. - :returns: true if the volume is manually set. - :rtype: bool + Returns + ------- + bool + true if the volume is manually set. """ return self._volume.set @make_prop_val_node("_old_number") def old_number(self): - """ - The original cell number provided in the input file + """The original cell number provided in the input file - :rtype: int + Returns + ------- + int """ pass @make_prop_pointer("_material", (Material, type(None)), deletable=True) def material(self): - """ - The Material object for the cell. + """The Material object for the cell. If the material is None this is considered to be voided. - :rtype: Material + Returns + ------- + Material """ pass @make_prop_pointer("_geometry", HalfSpace, validator=_link_geometry_to_cell) def geometry(self): - """ - The Geometry for this problem. + """The Geometry for this problem. 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. @@ -413,8 +418,10 @@ def geometry(self): For better documentation please refer to `OpenMC `_. - :returns: this cell's geometry - :rtype: HalfSpace + Returns + ------- + HalfSpace + this cell's geometry """ pass @@ -422,18 +429,18 @@ def geometry(self): "_density_node", (float, int, type(None)), base_type=float, deletable=True ) def _density(self): - """ - This is a wrapper to allow using the prop_val_node with mass_density and atom_density. - """ + """This is a wrapper to allow using the prop_val_node with mass_density and atom_density.""" pass @property def atom_density(self) -> float: - """ - The atom density of the material in the cell, in a/b-cm. + """The atom density of the material in the cell, in a/b-cm. - :returns: the atom density. If no density is set or it is in mass density will return None. - :rtype: float, None + Returns + ------- + float, None + the atom density. If no density is set or it is in mass + density will return None. """ if self._density and not self._is_atom_dens: raise AttributeError(f"Cell {self.number} is in mass density.") @@ -441,7 +448,7 @@ def atom_density(self) -> float: @atom_density.setter def atom_density(self, density: float): - if not isinstance(density, numbers.Number): + if not isinstance(density, Real): raise TypeError("Atom density must be a number.") elif density < 0: raise ValueError("Atom density must be a positive number.") @@ -454,11 +461,13 @@ def atom_density(self): @property def mass_density(self) -> float: - """ - The mass density of the material in the cell, in g/cc. + """The mass density of the material in the cell, in g/cc. - :returns: the mass density. If no density is set or it is in atom density will return None. - :rtype: float, None + Returns + ------- + float, None + the mass density. If no density is set or it is in atom + density will return None. """ if self._density and self._is_atom_dens: raise AttributeError(f"Cell {self.number} is in atom density.") @@ -466,7 +475,7 @@ def mass_density(self) -> float: @mass_density.setter def mass_density(self, density: float): - if not isinstance(density, numbers.Number): + if not isinstance(density, Real): raise TypeError("Mass density must be a number.") elif density < 0: raise ValueError("Mass density must be a positive number.") @@ -479,44 +488,51 @@ def mass_density(self): @property def is_atom_dens(self): - """ - Whether or not the density is in atom density [a/b-cm]. + """Whether or not the density is in atom density [a/b-cm]. True means it is in atom density, False means mass density [g/cc]. - :rtype: bool + Returns + ------- + bool """ return self._is_atom_dens @make_prop_val_node("_old_mat_number") def old_mat_number(self): - """ - The material number provided in the original input file + """The material number provided in the original input file - :rtype: int + Returns + ------- + int """ pass @make_prop_pointer("_surfaces") def surfaces(self): - """ - List of the Surface objects associated with this cell. + """List of the Surface objects associated with this cell. This list does not convey any of the CGS Boolean logic - :rtype: Surfaces + Returns + ------- + Surfaces """ return self._surfaces @property def parameters(self): - """ - A dictionary of the additional parameters for the object. + """A dictionary of the additional parameters for the object. e.g.: ``1 0 -1 u=1 imp:n=0.5`` has the parameters ``{"U": "1", "IMP:N": "0.5"}`` - :returns: a dictionary of the key-value pairs of the parameters. + Returns + ------- + unknown + a dictionary of the key-value pairs of the parameters. + + :rytpe: dict """ return self._parameters @@ -529,8 +545,7 @@ def parameters(self, params): @property def complements(self): - """ - The Cell objects that this cell is a complement of + """The Cell objects that this cell is a complement of :rytpe: :class:`montepy.cells.Cells` """ @@ -542,7 +557,9 @@ def cells_complementing_this(self): This returns a generator. - :rtype: generator + Returns + ------- + generator """ if self._problem: for cell in self._problem.cells: @@ -551,15 +568,16 @@ def cells_complementing_this(self): yield cell def update_pointers(self, cells, materials, surfaces): - """ - Attaches this object to the appropriate objects for surfaces and materials. - - :param cells: a Cells collection of the cells in the problem. - :type cells: Cells - :param materials: a materials collection of the materials in the problem - :type materials: Materials - :param surfaces: a surfaces collection of the surfaces in the problem - :type surfaces: Surfaces + """Attaches this object to the appropriate objects for surfaces and materials. + + Parameters + ---------- + cells : Cells + a Cells collection of the cells in the problem. + materials : Materials + a materials collection of the materials in the problem + surfaces : Surfaces + a surfaces collection of the surfaces in the problem """ self._surfaces = Surfaces() self._complements = Cells() @@ -582,15 +600,18 @@ def remove_duplicate_surfaces(self, deleting_dict): The form of the deleting_dict was changed as :class:`~montepy.surfaces.Surface` is no longer hashable. - :param deleting_dict: a dict of the surfaces to delete, mapping the old surface to the new surface to replace it. - The keys are the number of the old surface. The values are a tuple - of the old surface, and then the new surface. - :type deleting_dict: dict[int, tuple[Surface, Surface]] + Parameters + ---------- + deleting_dict : dict[int, tuple[Surface, Surface]] + a dict of the surfaces to delete, mapping the old surface to + the new surface to replace it. The keys are the number of + the old surface. The values are a tuple of the old surface, + and then the new surface. """ new_deleting_dict = {} def get_num(obj): - if isinstance(obj, int): + if isinstance(obj, Integral): return obj return obj.number @@ -634,11 +655,7 @@ def _generate_default_tree(self, number: int = None): ) def validate(self): - """ - Validates that the cell is in a usable state. - - :raises: IllegalState if any condition exists that make the object incomplete. - """ + """Validates that the cell is in a usable state.""" if self._density and self.material is None: raise IllegalState(f"Cell {self.number} has a density set but no material") if self.material is not None and not self._density: @@ -705,14 +722,18 @@ def __invert__(self): return HalfSpace(base_node, Operator.COMPLEMENT) def format_for_mcnp_input(self, mcnp_version): - """ - Creates a string representation of this MCNP_Object that can be + """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 + Parameters + ---------- + mcnp_version : tuple + The tuple for the MCNP version that must be exported to. + + Returns + ------- + list + a list of strings for the lines that this input will occupy. """ self.validate() self._update_values() @@ -770,8 +791,7 @@ def clone( step=None, add_collect=True, ): - """ - Create a new almost independent instance of this cell with a new number. + """Create a new almost independent instance of this cell with a new number. This relies mostly on ``copy.deepcopy``. All properties and attributes will be a deep copy unless otherwise requested. @@ -782,16 +802,22 @@ def clone( .. versionadded:: 0.5.0 - :param clone_material: Whether to create a new clone of the material. - :type clone_material: bool - :param clone_region: Whether to clone the underlying objects (Surfaces, Cells) of this cell's region. - :type clone_region: bool - :param starting_number: The starting number to request for a new cell number. - :type starting_number: int - :param step: the step size to use to find a new valid number. - :type step: int - :returns: a cloned copy of this cell. - :rtype: Cell + Parameters + ---------- + clone_material : bool + Whether to create a new clone of the material. + clone_region : bool + Whether to clone the underlying objects (Surfaces, Cells) of + this cell's region. + starting_number : int + The starting number to request for a new cell number. + step : int + the step size to use to find a new valid number. + + Returns + ------- + Cell + a cloned copy of this cell. """ if not isinstance(clone_material, bool): raise TypeError( @@ -799,11 +825,11 @@ def clone( ) if not isinstance(clone_region, bool): raise TypeError(f"clone_region must be a boolean. {clone_region} given.") - if not isinstance(starting_number, (int, type(None))): + if not isinstance(starting_number, (Integral, type(None))): raise TypeError( f"Starting_number must be an int. {type(starting_number)} given." ) - if not isinstance(step, (int, type(None))): + if not isinstance(step, (Integral, type(None))): raise TypeError(f"step must be an int. {type(step)} given.") if starting_number is not None and starting_number <= 0: raise ValueError(f"starting_number must be >= 1. {starting_number} given.") @@ -831,7 +857,7 @@ def clone( memo = {} def num(obj): - if isinstance(obj, int): + if isinstance(obj, Integral): return obj return obj.number diff --git a/montepy/cells.py b/montepy/cells.py index 2be3e1bb..2b86ba37 100644 --- a/montepy/cells.py +++ b/montepy/cells.py @@ -1,21 +1,25 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. import montepy from montepy.numbered_object_collection import NumberedObjectCollection from montepy.errors import * import warnings +from numbers import Integral class Cells(NumberedObjectCollection): """A collections of multiple :class:`montepy.cell.Cell` objects. - .. note:: + Notes + ----- - For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. + For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. - :param cells: the list of cells to start with if needed - :type cells: list - :param problem: the problem to link this collection to. - :type problem: MCNP_Problem + Parameters + ---------- + cells : list + the list of cells to start with if needed + problem : MCNP_Problem + the problem to link this collection to. """ def __init__(self, cells=None, problem=None): @@ -50,23 +54,24 @@ def __setup_blank_cell_modifiers(self, problem=None, check_input=False): raise e def set_equal_importance(self, importance, vacuum_cells=tuple()): - """ - Sets all cells except the vacuum cells to the same importance using :func:`montepy.data_cards.importance.Importance.all`. + """Sets all cells except the vacuum cells to the same importance using :func:`montepy.data_cards.importance.Importance.all`. The vacuum cells will be set to 0.0. You can specify cell numbers or cell objects. - :param importance: the importance to apply to all cells - :type importance: float - :param vacuum_cells: the cells that are the vacuum boundary with 0 importance - :type vacuum_cells: list + Parameters + ---------- + importance : float + the importance to apply to all cells + vacuum_cells : list + the cells that are the vacuum boundary with 0 importance """ if not isinstance(vacuum_cells, (list, tuple, set)): raise TypeError("vacuum_cells must be a list or set") cells_buff = set() for cell in vacuum_cells: - if not isinstance(cell, (montepy.Cell, int)): + if not isinstance(cell, (montepy.Cell, Integral)): raise TypeError("vacuum cell must be a Cell or a cell number") - if isinstance(cell, int): + if isinstance(cell, Integral): cells_buff.add(self[cell]) else: cells_buff.add(cell) @@ -79,11 +84,12 @@ def set_equal_importance(self, importance, vacuum_cells=tuple()): @property def allow_mcnp_volume_calc(self): - """ - Whether or not MCNP is allowed to automatically calculate cell volumes. + """Whether or not MCNP is allowed to automatically calculate cell volumes. - :returns: true if MCNP will attempt to calculate cell volumes - :rtype: bool + Returns + ------- + bool + true if MCNP will attempt to calculate cell volumes """ return self._volume.is_mcnp_calculated @@ -98,8 +104,10 @@ def link_to_problem(self, problem): This is done so that inputs can find links to other objects. - :param problem: The problem to link this input to. - :type problem: MCNP_Problem + Parameters + ---------- + problem : MCNP_Problem + The problem to link this input to. """ super().link_to_problem(problem) inputs_to_property = montepy.Cell._INPUTS_TO_PROPERTY @@ -109,22 +117,24 @@ def link_to_problem(self, problem): def update_pointers( self, cells, materials, surfaces, data_inputs, problem, check_input=False ): - """ - Attaches this object to the appropriate objects for surfaces and materials. + """Attaches this object to the appropriate objects for surfaces and materials. This will also update each cell with data from the data block, for instance with cell volume from the data block. - :param cells: a Cells collection of the cells in the problem. - :type cells: Cells - :param materials: a materials collection of the materials in the problem - :type materials: Materials - :param surfaces: a surfaces collection of the surfaces in the problem - :type surfaces: Surfaces - :param problem: The MCNP_Problem these cells are associated with - :type problem: MCNP_Problem - :param check_input: If true, will try to find all errors with input and collect them as warnings to log. - :type check_input: bool + Parameters + ---------- + cells : Cells + a Cells collection of the cells in the problem. + materials : Materials + a materials collection of the materials in the problem + surfaces : Surfaces + a surfaces collection of the surfaces in the problem + problem : MCNP_Problem + The MCNP_Problem these cells are associated with + check_input : bool + If true, will try to find all errors with input and collect + them as warnings to log. """ def handle_error(e): @@ -170,8 +180,6 @@ def handle_error(e): except ( BrokenObjectLinkError, MalformedInputError, - ParticleTypeNotInProblem, - ParticleTypeNotInCell, ) as e: handle_error(e) continue @@ -188,15 +196,16 @@ def _run_children_format_for_mcnp(self, data_inputs, mcnp_version): def clone( self, clone_material=False, clone_region=False, starting_number=None, step=None ): - """ - Create a new instance of this collection, with all new independent + """Create a new instance of this collection, with all new independent objects with new numbers. This relies mostly on ``copy.deepcopy``. - .. note :: - If starting_number, or step are not specified :func:`starting_number`, - and :func:`step` are used as default values. + Notes + ----- + If starting_number, or step are not specified :func:`starting_number`, + and :func:`step` are used as default values. + .. versionadded:: 0.5.0 @@ -204,23 +213,29 @@ def clone( Added ``clone_material`` and ``clone_region``. - :param clone_material: Whether to create a new clone of the materials for the cells. - :type clone_material: bool - :param clone_region: Whether to clone the underlying objects (Surfaces, Cells) of these cells' region. - :type clone_region: bool - :param starting_number: The starting number to request for a new object numbers. - :type starting_number: int - :param step: the step size to use to find a new valid number. - :type step: int - :returns: a cloned copy of this object. - :rtype: type(self) + Parameters + ---------- + clone_material : bool + Whether to create a new clone of the materials for the + cells. + clone_region : bool + Whether to clone the underlying objects (Surfaces, Cells) of + these cells' region. + starting_number : int + The starting number to request for a new object numbers. + step : int + the step size to use to find a new valid number. + Returns + ------- + type(self) + a cloned copy of this object. """ - if not isinstance(starting_number, (int, type(None))): + if not isinstance(starting_number, (Integral, type(None))): raise TypeError( f"Starting_number must be an int. {type(starting_number)} given." ) - if not isinstance(step, (int, type(None))): + if not isinstance(step, (Integral, type(None))): raise TypeError(f"step must be an int. {type(step)} given.") if starting_number is not None and starting_number <= 0: raise ValueError(f"starting_number must be >= 1. {starting_number} given.") diff --git a/montepy/constants.py b/montepy/constants.py index abe1091f..dfa42d00 100644 --- a/montepy/constants.py +++ b/montepy/constants.py @@ -1,4 +1,6 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. +import re + from montepy.errors import UnsupportedFeature """ @@ -6,24 +8,27 @@ """ rel_tol = 1e-9 -""" -Relative tolerance passed to math.isclose. +"""Relative tolerance passed to math.isclose. -:rtype: float +Returns +------- +float """ abs_tol = 0.0 -""" -Absolute tolerance passed to math.isclose. +"""Absolute tolerance passed to math.isclose. -:rtype: float +Returns +------- +float """ BLANK_SPACE_CONTINUE = 5 -""" -Number of spaces in a new line before it's considered a continuation. -""" +"""Number of spaces in a new line before it's considered a continuation.""" + +COMMENT_FINDER = re.compile(rf"\s{{0,{BLANK_SPACE_CONTINUE - 1}}}c", re.IGNORECASE) +"""A regular expression for finding the start of a ``c`` style comment.""" LINE_LENGTH = { (5, 1, 60): 80, @@ -32,43 +37,34 @@ (6, 3, 0): 128, (6, 3, 1): 128, } -""" -The number of characters allowed in a line for each MCNP version. +"""The number of characters allowed in a line for each MCNP version. Citations: * 5.1.60 and 6.1.0: Section 2.6.2 of `LA-UR-18-20808 `_ * 6.2.0: Section 1.1.1: :manual62:`13` * 6.3.0: :manual63:`3.2.2` -* 6.3.1: Section 3.2.2 of `LA-UR-24-24602 `_ +* 6.3.1: Section 3.2.2 of `LA-UR-24-24602 `_ """ DEFAULT_VERSION = (6, 3, 0) -""" -The default version of MCNP to use. -""" +"""The default version of MCNP to use.""" TABSIZE = 8 -""" -How many spaces a tab is expand to. -""" +"""How many spaces a tab is expand to.""" ASCII_CEILING = 127 -""" -The maximum allowed code point allowed by ASCII. +"""The maximum allowed code point allowed by ASCII. Source: `Wikipedia `_ """ MAX_ATOMIC_SYMBOL_LENGTH = 2 -""" -The maximum length of an atomic symbol. -""" +"""The maximum length of an atomic symbol.""" def get_max_line_length(mcnp_version=DEFAULT_VERSION): - """ - Gets the maximum allowed length for an input line for a specific MCNP version. + """Gets the maximum allowed length for an input line for a specific MCNP version. The version must be a three component tuple e.g., (6, 2, 0) and (5, 1, 60). Line lengths inferred from: @@ -77,10 +73,15 @@ def get_max_line_length(mcnp_version=DEFAULT_VERSION): Prior MCNP release version numbers were taken from `RSICC `_. - :param mcnp_version: The version of MCNP that the input is intended for. - :type mcnp_version: tuple - :returns: The number of characters allowed in a line. - :rtype: int + Parameters + ---------- + mcnp_version : tuple + The version of MCNP that the input is intended for. + + Returns + ------- + int + The number of characters allowed in a line. """ if mcnp_version >= DEFAULT_VERSION: return LINE_LENGTH[DEFAULT_VERSION] diff --git a/montepy/data_inputs/cell_modifier.py b/montepy/data_inputs/cell_modifier.py index 6ddf90c0..ca469c13 100644 --- a/montepy/data_inputs/cell_modifier.py +++ b/montepy/data_inputs/cell_modifier.py @@ -9,19 +9,20 @@ class CellModifierInput(DataInputAbstract): - """ - Abstract Parent class for Data Inputs that modify cells / geometry. + """Abstract Parent class for Data Inputs that modify cells / geometry. Examples: IMP, VOL, etc. - :param input: the Input object representing this data 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 - :type key: str - :param value: the value syntax tree from the key-value pair in a cell - :type value: SyntaxNode + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + in_cell_block : bool + if this card came from the cell block of an input file. + key : str + the key from the key-value pair in a cell + value : SyntaxNode + the value syntax tree from the key-value pair in a cell """ def __init__( @@ -80,28 +81,32 @@ def _generate_default_data_tree(self): @property def in_cell_block(self): - """ - True if this object represents an input from the cell block section of a file. + """True if this object represents an input from the cell block section of a file. - :rtype: bool + Returns + ------- + bool """ return self._in_cell_block @property def set_in_cell_block(self): - """ - True if this data were set in the cell block in the input - """ + """True if this data were set in the cell block in the input""" return self._set_in_cell_block @abstractmethod def merge(self, other): - """ - Merges the data from another card of same type into this one. + """Merges the data from another card of same type into this one. - :param other: The other object to merge into this object. - :type other: CellModifierInput - :raises MalformedInputError: if two objects cannot be merged. + Parameters + ---------- + other : CellModifierInput + The other object to merge into this object. + + Raises + ------ + MalformedInputError + if two objects cannot be merged. """ pass @@ -112,34 +117,35 @@ def link_to_problem(self, problem): @abstractmethod def push_to_cells(self): - """ - After being linked to the problem update all cells attributes with this data. + """After being linked to the problem update all cells attributes with this data. This needs to also check that none of the cells had data provided in the cell block (check that ``set_in_cell_block`` isn't set). Use ``self._check_redundant_definitions`` to do this. - :raises MalformedInputError: When data are given in the cell block and the data block. + Raises + ------ + MalformedInputError + When data are given in the cell block and the data block. """ pass @property @abstractmethod def has_information(self): - """ - For a cell instance of :class:`montepy.data_cards.cell_modifier.CellModifierCard` returns True iff there is information here worth printing out. + """For a cell instance of :class:`montepy.data_cards.cell_modifier.CellModifierCard` returns True iff there is information here worth printing out. e.g., a manually set volume for a cell - :returns: True if this instance has information worth printing. - :rtype: bool + Returns + ------- + bool + True if this instance has information worth printing. """ pass def _check_redundant_definitions(self): - """ - Checks that data wasn't given in data block and the cell block. - """ + """Checks that data wasn't given in data block and the cell block.""" attr, _ = montepy.Cell._INPUTS_TO_PROPERTY[type(self)] if not self._in_cell_block and self._problem: cells = self._problem.cells @@ -153,8 +159,7 @@ def _check_redundant_definitions(self): @abstractmethod def _clear_data(self): - """ - After data has been pushed to cells, delete internal data to avoid inadvertent editing. + """After data has been pushed to cells, delete internal data to avoid inadvertent editing. This is only called on data-block instances of this object. """ @@ -162,13 +167,14 @@ def _clear_data(self): @property def _is_worth_printing(self): - """ - Determines if this object has information that is worth printing in the input file. + """Determines if this object has information that is worth printing in the input file. Uses the :func:`has_information` property for all applicable cell(s) - :returns: True if this object should be included in the output - :rtype: bool + Returns + ------- + bool + True if this object should be included in the output """ if self.in_cell_block: return self.has_information @@ -181,22 +187,25 @@ def _is_worth_printing(self): @property @abstractmethod def _tree_value(self): - """ - The ValueNode that holds the information for this instance, that should be included in the data block. + """The ValueNode that holds the information for this instance, that should be included in the data block. - :returns: The ValueNode to update the data-block syntax tree with. - :rtype: ValueNode + Returns + ------- + ValueNode + The ValueNode to update the data-block syntax tree with. """ pass def _collect_new_values(self): - """ - Gets a list of the ValueNodes that hold the information for all cells. + """Gets a list of the ValueNodes that hold the information for all cells. This will be a list in the same order as :func:`montepy.mcnp_problem.MCNP_Problem.cells`. - :returns: a list of the ValueNodes to update the data block syntax tree with - :rtype: list + Returns + ------- + list + a list of the ValueNodes to update the data block syntax + tree with """ ret = [] attr, _ = montepy.Cell._INPUTS_TO_PROPERTY[type(self)] @@ -207,9 +216,7 @@ def _collect_new_values(self): @abstractmethod def _update_cell_values(self): - """ - Updates values in the syntax tree when in the cell block. - """ + """Updates values in the syntax tree when in the cell block.""" pass def _update_values(self): @@ -220,25 +227,31 @@ def _update_values(self): self.data.update_with_new_values(new_vals) def _format_tree(self): - """ - Formats the syntax tree for printing in an input file. + """Formats the syntax tree for printing in an input file. By default this runs ``self._tree.format()``. - :returns: a string of the text to write out to the input file (not wrapped yet for MCNP). - :rtype: str + Returns + ------- + str + a string of the text to write out to the input file (not + wrapped yet for MCNP). """ return self._tree.format() def format_for_mcnp_input(self, mcnp_version, has_following=False): - """ - Creates a string representation of this MCNP_Object that can be + """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 + Parameters + ---------- + mcnp_version : tuple + The tuple for the MCNP version that must be exported to. + + Returns + ------- + list + a list of strings for the lines that this input will occupy. """ self.validate() self._tree.check_for_graveyard_comments(has_following) diff --git a/montepy/data_inputs/data_input.py b/montepy/data_inputs/data_input.py index daeab526..7b1df350 100644 --- a/montepy/data_inputs/data_input.py +++ b/montepy/data_inputs/data_input.py @@ -19,14 +19,10 @@ class _ClassifierInput(Input): - """ - A specialized subclass that returns only 1 useful token. - """ + """A specialized subclass that returns only 1 useful token.""" def tokenize(self): - """ - Returns one token after all starting comments and spaces. - """ + """Returns one token after all starting comments and spaces.""" last_in_comment = True for token in super().tokenize(): if token is None: @@ -41,13 +37,15 @@ def tokenize(self): class DataInputAbstract(MCNP_Object): - """ - Parent class to describe all MCNP data inputs. - - :param input: the Input object representing this data 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 + """Parent class to describe all MCNP data inputs. + + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + fast_parse : bool + Whether or not to only parse the first word for the type of + data. """ _parser = DataParser() @@ -87,8 +85,11 @@ def _class_prefix(): this must be lower case - :returns: the string of the prefix that identifies a input of this class. - :rtype: str + Returns + ------- + str + the string of the prefix that identifies a input of this + class. """ pass @@ -99,8 +100,10 @@ def _has_number(): For example: ``kcode`` doesn't allow numbers but tallies do allow it e.g., ``f7`` - :returns: True if this class allows numbers - :rtype: bool + Returns + ------- + bool + True if this class allows numbers """ pass @@ -115,8 +118,10 @@ def _has_classifier(): * 1 : is optional * 2 : is mandatory - :returns: True if this class particle classifiers - :rtype: int + Returns + ------- + int + True if this class particle classifiers """ pass @@ -129,8 +134,10 @@ def particle_classifiers(self): For example: the classifier for ``F7:n`` is ``:n``, and ``imp:n,p`` is ``:n,p`` This will be parsed as a list: ``[, ]``. - :returns: the particles listed in the input if any. Otherwise None - :rtype: list + Returns + ------- + list + the particles listed in the input if any. Otherwise None """ if self._particles: return self._particles @@ -143,8 +150,10 @@ def prefix(self): For example: for a material like: m20 the prefix is 'm' this will always be lower case. - :returns: The prefix read from the input - :rtype: str + Returns + ------- + str + The prefix read from the input """ return self._prefix.lower() @@ -154,30 +163,35 @@ def prefix_modifier(self): For example: for a transform: ``*tr5`` the modifier is ``*`` - :returns: the prefix modifier that was parsed if any. None if otherwise. - :rtype: str + Returns + ------- + str + the prefix modifier that was parsed if any. None if + otherwise. """ return self._modifier @property def data(self): - """ - The syntax tree actually holding the data. + """The syntax tree actually holding the data. - :returns: The syntax tree with the information. - :rtype: ListNode + Returns + ------- + ListNode + The syntax tree with the information. """ return self._tree["data"] @property def classifier(self): - """ - The syntax tree object holding the data classifier. + """The syntax tree object holding the data classifier. For example this would container information like ``M4``, or ``F104:n``. - :returns: the classifier for this data_input. - :rtype: ClassifierNode + Returns + ------- + ClassifierNode + the classifier for this data_input. """ return self._tree["classifier"] @@ -188,13 +202,18 @@ def _update_values(self): pass def update_pointers(self, data_inputs): - """ - Connects data inputs to each other - - :param data_inputs: a list of the data inputs in the problem - :type data_inputs: list - :returns: True iff this input should be removed from ``problem.data_inputs`` - :rtype: bool, None + """Connects data inputs to each other + + Parameters + ---------- + data_inputs : list + a list of the data inputs in the problem + + Returns + ------- + bool, None + True iff this input should be removed from + ``problem.data_inputs`` """ pass @@ -205,17 +224,22 @@ def __repr__(self): return str(self) def __split_name(self, input): - """ - Parses the name of the data input as a prefix, number, and a particle classifier. + """Parses the name of the data input as a prefix, number, and a particle classifier. This populates the properties: prefix _input_number classifier - :param input: the input object representing this data input - :type input: input - :raises MalformedInputError: if the name is invalid for this DataInput + Parameters + ---------- + input : input + the input object representing this data input + + Raises + ------ + MalformedInputError + if the name is invalid for this DataInput """ self._classifier = self._tree["classifier"] self.__enforce_name(input) @@ -226,12 +250,17 @@ def __split_name(self, input): self._modifier = self._classifier.modifier def __enforce_name(self, input): - """ - Checks that the name is valid. + """Checks that the name is valid. + + Parameters + ---------- + input : input + the input object representing this data input - :param input: the input object representing this data input - :type input: input - :raises MalformedInputError: if the name is invalid for this DataInput + Raises + ------ + MalformedInputError + if the name is invalid for this DataInput """ classifier = self._classifier if self._class_prefix: @@ -276,15 +305,17 @@ def __lt__(self, other): class DataInput(DataInputAbstract): - """ - Catch-all for all other MCNP data inputs. - - :param input: the Input object representing this data 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 + """Catch-all for all other MCNP data inputs. + + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + fast_parse : bool + Whether or not to only parse the first word for the type of + data. + prefix : str + The input prefix found during parsing (internal use only) """ def __init__( @@ -307,8 +338,7 @@ def _has_classifier(self): # pragma: no cover return None def _load_correct_parser(self, prefix): - """ - Decides if a specialized parser needs to be loaded for barebone + """Decides if a specialized parser needs to be loaded for barebone special cases. .. versionadded:: 0.3.0 diff --git a/montepy/data_inputs/data_parser.py b/montepy/data_inputs/data_parser.py index 32b85181..d64a198a 100644 --- a/montepy/data_inputs/data_parser.py +++ b/montepy/data_inputs/data_parser.py @@ -29,13 +29,17 @@ def parse_data(input: montepy.mcnp_object.InitInput): - """ - Parses the data input as the appropriate object if it is supported. + """Parses the data input as the appropriate object if it is supported. + + Parameters + ---------- + input : Union[Input, str] + the Input object for this Data input - :param input: the Input object for this Data input - :type input: Union[Input, str] - :return: the parsed DataInput object - :rtype: DataInput + Returns + ------- + DataInput + the parsed DataInput object """ base_input = data_input.DataInput(input, fast_parse=True) diff --git a/montepy/data_inputs/element.py b/montepy/data_inputs/element.py index f3443d1a..5589cebf 100644 --- a/montepy/data_inputs/element.py +++ b/montepy/data_inputs/element.py @@ -1,29 +1,34 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations from montepy.errors import * from montepy._singleton import SingletonGroup +from numbers import Integral MAX_Z_NUM = 118 class Element(SingletonGroup): - """ - Class to represent an element e.g., Aluminum. + """Class to represent an element e.g., Aluminum. .. Note:: This class is immutable, and hashable, meaning it is suitable as a dictionary key. + Parameters + ---------- + Z : int + the Z number of the element - :param Z: the Z number of the element - :type Z: int - :raises UnknownElement: if there is no element with that Z number. + Raises + ------ + UnknownElement + if there is no element with that Z number. """ __slots__ = "_Z" def __init__(self, Z: int): - if not isinstance(Z, int): + if not isinstance(Z, Integral): raise TypeError(f"Z must be an int. {Z} of type {type(Z)} given.") self._Z = Z if Z not in self.__Z_TO_SYMBOL: @@ -31,31 +36,34 @@ def __init__(self, Z: int): @property def symbol(self) -> str: - """ - The atomic symbol for this Element. + """The atomic symbol for this Element. - :returns: the atomic symbol - :rtype: str + Returns + ------- + str + the atomic symbol """ return self.__Z_TO_SYMBOL[self.Z] @property def Z(self) -> int: - """ - The atomic number for this Element. + """The atomic number for this Element. - :returns: the atomic number - :rtype: int + Returns + ------- + int + the atomic number """ return self._Z @property def name(self) -> str: - """ - The name of the element. + """The name of the element. - :returns: the element's name. - :rtype: str + Returns + ------- + str + the element's name. """ return self.__ELEMENT_NAMES[self.symbol] @@ -76,14 +84,19 @@ def __reduce__(self): @classmethod def get_by_symbol(cls, symbol: str) -> Element: - """ - Get an element by it's symbol. + """Get an element by it's symbol. E.g., get the element with Z=1 from "H". - :returns: the element with this symbol - :rtype: Element - :raises UnknownElement: if there is no element with that symbol. + Returns + ------- + Element + the element with this symbol + + Raises + ------ + UnknownElement + if there is no element with that symbol. """ try: Z = cls.__SYMBOL_TO_Z[symbol] @@ -93,14 +106,19 @@ def get_by_symbol(cls, symbol: str) -> Element: @classmethod def get_by_name(cls, name: str) -> Element: - """ - Get an element by it's name. + """Get an element by it's name. E.g., get the element with Z=1 from "hydrogen". - :returns: the element with this name - :rtype: Element - :raises UnknownElement: if there is no element with that name. + Returns + ------- + Element + the element with this name + + Raises + ------ + UnknownElement + if there is no element with that name. """ try: symbol = cls.__NAMES_TO_SYMBOLS[name] diff --git a/montepy/data_inputs/fill.py b/montepy/data_inputs/fill.py index 2c3653d0..294f7bcc 100644 --- a/montepy/data_inputs/fill.py +++ b/montepy/data_inputs/fill.py @@ -13,23 +13,22 @@ 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: 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 - :type key: str - :param value: the value syntax tree from the key-value pair in a cell - :type value: SyntaxNode + """Object to handle the ``FILL`` input in cell and data blocks. + + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + in_cell_block : bool + if this card came from the cell block of an input file. + key : str + the key from the key-value pair in a cell + value : SyntaxNode + the value syntax tree from the key-value pair in a cell """ DIMENSIONS = {"i": 0, "j": 1, "k": 2} - """ - Maps the dimension to its axis number - """ + """Maps the dimension to its axis number""" def __init__( self, @@ -84,13 +83,14 @@ def _generate_default_cell_tree(self): ) def _parse_cell_input(self, key, value): - """ - Parses the information provided in the cell input. - - :param key: The key given in the cell - :type key: str - :param value: the value given in the cell - :type value: str + """Parses the information provided in the cell input. + + Parameters + ---------- + key : str + The key given in the cell + value : str + the value given in the cell """ def get_universe(value): @@ -150,11 +150,12 @@ def get_universe(value): get_universe(value) def _parse_matrix(self, value): - """ - Parses a matrix fill of universes. + """Parses a matrix fill of universes. - :param value: the value in the cell - :type value: str + Parameters + ---------- + value : str + the value in the cell """ self._multi_universe = True words = value["data"] @@ -213,13 +214,14 @@ def _has_classifier(): @property def universe(self): - """ - The universe that this cell will be filled with. + """The universe that this cell will be filled with. Only returns a value when :func:`multiple_universes` is False, otherwise none. - :returns: the universe that the cell will be filled with, or None - :rtype: Universe + Returns + ------- + Universe + the universe that the cell will be filled with, or None """ if not self.multiple_universes: return self._universe @@ -240,13 +242,15 @@ def universe(self): @property def universes(self): - """ - The universes that this cell will be filled with in a lattice. + """The universes that this cell will be filled with in a lattice. Only returns a value when :func:`multiple_universes` is true, otherwise none. - :returns: the universes that the cell will be filled with as a 3-D array. - :rtype: np.ndarray + Returns + ------- + np.ndarray + the universes that the cell will be filled with as a 3-D + array. """ if self.multiple_universes: return self._universes @@ -267,35 +271,38 @@ def universes(self): @property def min_index(self): - """ - The minimum indices of the matrix in each dimension. + """The minimum indices of the matrix in each dimension. For the order of the indices see: ``DIMENSIONS``. - :returns: the minimum indices of the matrix for complex fills - :rtype: :class:`numpy.ndarry` + Returns + ------- + :class:`numpy.ndarry` + the minimum indices of the matrix for complex fills """ return self._min_index @property def max_index(self): - """ - The maximum indices of the matrix in each dimension. + """The maximum indices of the matrix in each dimension. For the order of the indices see: ``DIMENSIONS``. - :returns: the maximum indices of the matrix for complex fills - :rtype: :class:`numpy.ndarry` + Returns + ------- + :class:`numpy.ndarry` + the maximum indices of the matrix for complex fills """ return self._max_index @property def multiple_universes(self): - """ - Whether or not this cell is filled with multiple universes in a matrix. + """Whether or not this cell is filled with multiple universes in a matrix. - :return: True if this cell contains multiple universes - :rtype: bool + Returns + ------- + bool + True if this cell contains multiple universes """ return self._multi_universe @@ -307,21 +314,23 @@ def multiple_universes(self, value): @make_prop_val_node("_old_number") def old_universe_number(self): - """ - The number of the universe that this is filled by taken from the input. + """The number of the universe that this is filled by taken from the input. - :returns: the old universe number - :type: int + Returns + ------- + int + the old universe number """ pass @property def old_universe_numbers(self): - """ - The numbers of the universes that this is filled by taken from the input. + """The numbers of the universes that this is filled by taken from the input. - :returns: the old universe numbers - :type: :class:`numpy.ndarray` + Returns + ------- + :class:`numpy.ndarray` + the old universe numbers """ if isinstance(self._old_numbers, list): return [ @@ -332,14 +341,15 @@ def old_universe_numbers(self): @property def hidden_transform(self): - """ - Whether or not the transform used is hidden. + """Whether or not the transform used is hidden. This is true when an unnumbered transform is used e.g., ``FILL=1 (1.0 2.0 3.0)``. - :returns: True iff the transform used is hidden - :rtype: bool + Returns + ------- + bool + True iff the transform used is hidden """ return self._hidden_transform @@ -361,11 +371,12 @@ def _tree_value(self): @property def transform(self): - """ - The transform for this fill (if any). + """The transform for this fill (if any). - :returns: the transform for the filling universe for this cell. - :rtype: Transform + Returns + ------- + Transform + the transform for the filling universe for this cell. """ return self._transform @@ -385,11 +396,12 @@ def transform(self): @make_prop_val_node("_old_transform_number") def old_transform_number(self): - """ - The number of the transform specified in the input. + """The number of the transform specified in the input. - :returns: the original number for the transform from the input. - :rtype: int + Returns + ------- + int + the original number for the transform from the input. """ pass @@ -434,33 +446,43 @@ def _clear_data(self): self._universe = None def _axis_range(self, axis): - """ - Returns an iterator for iterating over the given axis. + """Returns an iterator for iterating over the given axis. + + Parameters + ---------- + axis : int + the number of the axis to iterate over - :param axis: the number of the axis to iterate over - :type axis: int - :returns: range + Returns + ------- + unknown + range """ return range(self._axis_size(axis)) def _axis_size(self, axis): - """ - Get the length of the given axis. + """Get the length of the given axis. - :param axis: the axis to probe into. - :type axis: int - :returns: the length of the given axis of the universe matrix. - :rtype: int + Parameters + ---------- + axis : int + the axis to probe into. + + Returns + ------- + int + the length of the given axis of the universe matrix. """ return int(self.max_index[axis] - self.min_index[axis]) + 1 @property def _sizes(self): - """ - The axis sizes of the matrix. + """The axis sizes of the matrix. - :returns: a tuple of the matrix shape. - :rtype: tuple + Returns + ------- + tuple + a tuple of the matrix shape. """ return (self._axis_size(0), self._axis_size(1), self._axis_size(2)) diff --git a/montepy/data_inputs/importance.py b/montepy/data_inputs/importance.py index 74bbcab0..781e1eb0 100644 --- a/montepy/data_inputs/importance.py +++ b/montepy/data_inputs/importance.py @@ -2,6 +2,7 @@ import collections import copy import math +import warnings from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput from montepy.errors import * from montepy.constants import DEFAULT_VERSION, rel_tol, abs_tol @@ -27,17 +28,18 @@ 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: 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 - :type key: str - :param value: the value syntax tree from the key-value pair in a cell - :type value: SyntaxNode + """A data input that sets the importance for a cell(s). + + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + in_cell_block : bool + if this card came from the cell block of an input file. + key : str + the key from the key-value pair in a cell + value : SyntaxNode + the value syntax tree from the key-value pair in a cell """ def __init__( @@ -251,13 +253,17 @@ def _format_tree(self): @property def all(self): - """ - Setter for setting importance for all particle types in the problem at once. + """Setter for setting importance for all particle types in the problem at once. + + Parameters + ---------- + importance : float + the importance to set all particles to. - :param importance: the importance to set all particles to. - :type importance: float - :returns: None - :rtype: None + Returns + ------- + None + None """ return None @@ -279,8 +285,9 @@ def _clear_data(self): def _check_particle_in_problem(self, particle_type): if self._problem: if particle_type not in self._problem.mode: - raise ParticleTypeNotInProblem( - f"Particle type: {particle_type} not included in problem mode." + warnings.warn( + f"Particle type: {particle_type} not included in problem mode.", + ParticleTypeNotInProblem, ) def _collect_new_values(self): @@ -291,9 +298,10 @@ def _collect_new_values(self): try: tree = cell.importance._particle_importances[particle] except KeyError: - raise ParticleTypeNotInCell( + raise NotImplementedError( f"Importance data not available for cell {cell.number} for particle: " - f"{particle}, though it is in the problem" + f"{particle}, though it is in the problem, and default importance logic " + "is not yet implemented in MontePy." ) new_vals[particle].append(tree["data"][0]) if len(particle_pairings[particle]) == 0: @@ -365,13 +373,15 @@ def _update_cell_values(self): @property def trailing_comment(self): - """ - The trailing comments and padding of an input. + """The trailing comments and padding of an input. Generally this will be blank as these will be moved to be a leading comment for the next input. - :returns: the trailing ``c`` style comments and intermixed padding (e.g., new lines) - :rtype: list + Returns + ------- + list + the trailing ``c`` style comments and intermixed padding + (e.g., new lines) """ last_tree = list(self._real_tree.values())[-1] if last_tree: @@ -479,11 +489,15 @@ def __create_particle_imp_doc(particle_type): Can only be set if this particle is used in the problem mode. -:param importance: The importance to set this to. -:type importnace: float -:returns: the importance for the particle type. If not set, defaults to 0. -:rtype: float -:raises ParticleTypeNotInProblem: raised if this particle is accessed while not in the problem mode. +Parameters +---------- +importance: float + The importance to set this to. + +Returns +------- +float + the importance for the particle type. If not set, defaults to 0. """ diff --git a/montepy/data_inputs/isotope.py b/montepy/data_inputs/isotope.py index f00e4d78..4b7669fb 100644 --- a/montepy/data_inputs/isotope.py +++ b/montepy/data_inputs/isotope.py @@ -1,13 +1,15 @@ class Isotope: - """ - A class to represent an MCNP isotope + """A class to represent an MCNP isotope .. deprecated:: 0.4.1 This will class is deprecated, and has been renamed: :class:`~montepy.data_inputs.nuclide.Nuclide`. For more details see the :ref:`migrate 0 1`. - :raises DeprecationWarning: Whenever called. + Raises + ------ + DeprecationWarning + Whenever called. """ def __init__(self, *args, **kwargs): diff --git a/montepy/data_inputs/lattice.py b/montepy/data_inputs/lattice.py index f4dbee8d..16985f5d 100644 --- a/montepy/data_inputs/lattice.py +++ b/montepy/data_inputs/lattice.py @@ -4,17 +4,12 @@ @unique class Lattice(Enum): - """ - Represents the options for the lattice ``LAT``. - """ + """Represents the options for the lattice ``LAT``.""" HEXAHEDRA = 1 - """ - Hexhedra are solids with six faces. + """Hexhedra are solids with six faces. One such solid is a rectangular prism. """ HEXAGONAL = 2 - """ - Hexagonal prism are solids with eight faces. - """ + """Hexagonal prism are solids with eight faces.""" diff --git a/montepy/data_inputs/lattice_input.py b/montepy/data_inputs/lattice_input.py index 4f12e11c..686ab8ab 100644 --- a/montepy/data_inputs/lattice_input.py +++ b/montepy/data_inputs/lattice_input.py @@ -11,17 +11,18 @@ class LatticeInput(CellModifierInput): - """ - Object to handle the inputs from ``LAT``. - - :param input: the Input object representing this data 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 - :type key: str - :param value: the value syntax tree from the key-value pair in a cell - :type value: SyntaxNode + """Object to handle the inputs from ``LAT``. + + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + in_cell_block : bool + if this card came from the cell block of an input file. + key : str + the key from the key-value pair in a cell + value : SyntaxNode + the value syntax tree from the key-value pair in a cell """ def __init__( @@ -93,10 +94,11 @@ def has_information(self): @make_prop_val_node("_lattice", (Lattice, int, type(None)), Lattice, deletable=True) def lattice(self): - """ - The type of lattice being used. + """The type of lattice being used. - :rtype: Lattice + Returns + ------- + Lattice """ pass diff --git a/montepy/data_inputs/material.py b/montepy/data_inputs/material.py index caafbfa3..d04efe34 100644 --- a/montepy/data_inputs/material.py +++ b/montepy/data_inputs/material.py @@ -1,22 +1,18 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations import collections as co import copy -import itertools import math -import re +from numbers import Integral, Real 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 -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, InitInput from montepy.errors import * from montepy.utilities import * @@ -36,15 +32,18 @@ By default all components made from scratch are added to their own line with this many leading spaces. """ +NuclideLike = Union[Nuclide, Nucleus, Element, str, Integral] + class _DefaultLibraries: - """ - A dictionary wrapper for handling the default libraries for a material. + """A dictionary wrapper for handling the default libraries for a material. The default libraries are those specified by keyword, e.g., ``nlib=80c``. - :param parent_mat: the material that this default library is associated with. - :type parent_mat: Material + Parameters + ---------- + parent_mat : Material + the material that this default library is associated with. """ __slots__ = "_libraries", "_parent" @@ -123,9 +122,7 @@ def _link_to_parent(self, parent_mat: Material): class _MatCompWrapper: - """ - A wrapper that allows unwrapping Nuclide and fractions - """ + """A wrapper that allows unwrapping Nuclide and fractions""" __slots__ = "_parent", "_index", "_setter" @@ -151,8 +148,7 @@ def __setitem__(self, idx, val): class Material(data_input.DataInputAbstract, Numbered_MCNP_Object): - """ - A class to represent an MCNP material. + """A class to represent an MCNP material. Examples -------- @@ -263,24 +259,30 @@ class Material(data_input.DataInputAbstract, Numbered_MCNP_Object): 80p 00c - .. versionchanged:: 1.0.0 + See Also + -------- - .. seealso:: + * :manual631:`5.6.1` + * :manual63:`5.6.1` + * :manual62:`106` - * :manual63:`5.6.1` - * :manual62:`106` .. versionchanged:: 1.0.0 - * Added number parameter + * Added number parameter to constructor. * This was the primary change for this release. For more details on what changed see :ref:`migrate 0 1`. - - :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 + * Switched to list-like data-structure + * Added ability to search by Nuclide + * Added Support for default libraries (e.g., ``nlib=80c``). + + Parameters + ---------- + input : Union[Input, str] + The Input syntax object this will wrap and parse. + parser : MCNP_Parser + The parser object to parse the input with. + number : int + The number to set for this object. """ _parser = MaterialParser() @@ -319,9 +321,7 @@ def __init__( def _grab_isotope( self, nuclide: Nuclide, fraction: syntax_node.ValueNode, is_first: bool = False ): - """ - Grabs and parses the nuclide and fraction from the init function, and loads it. - """ + """Grabs and parses the nuclide and fraction from the init function, and loads it.""" isotope = Nuclide(node=nuclide) fraction.is_negatable_float = True if is_first: @@ -338,9 +338,7 @@ def _grab_isotope( self._components.append((isotope, fraction)) def _grab_default(self, param: syntax_node.SyntaxNode): - """ - Grabs and parses default libraris from init process. - """ + """Grabs and parses default libraris from init process.""" try: lib_type = LibraryType(param["classifier"].prefix.value.upper()) self._default_libs._load_node(lib_type, param) @@ -365,16 +363,14 @@ def _create_default_tree(self): ) def _append_param_lib(self, node: syntax_node.SyntaxNode): - """ - Adds the given syntax node to this Material's data list. + """Adds the given syntax node to this Material's data list. This is called from _DefaultLibraries. """ self._tree["data"].append_param(node) def _delete_param_lib(self, node: syntax_node.SyntaxNode): - """ - Deletes the given syntax node from this Material's data list. + """Deletes the given syntax node from this Material's data list. This is called from _DefaultLibraries. """ @@ -382,37 +378,41 @@ def _delete_param_lib(self, node: syntax_node.SyntaxNode): @make_prop_val_node("_old_number") def old_number(self) -> int: - """ - The material number that was used in the read file + """The material number that was used in the read file - :rtype: int + Returns + ------- + int """ pass @make_prop_pointer("_is_atom_fraction", bool) def is_atom_fraction(self) -> bool: - """ - If true this constituent is in atom fraction, not weight fraction. + """If true this constituent is in atom fraction, not weight fraction. .. versionchanged:: 1.0.0 This property is now settable. - :rtype: bool + Returns + ------- + bool """ pass @property def material_components(self): # pragma: no cover - """ - The internal dictionary containing all the components of this material. + """The internal dictionary containing all the components of this material. .. deprecated:: 0.4.1 MaterialComponent has been deprecated as part of a redesign for the material interface due to a critical bug in how MontePy handles duplicate nuclides. See :ref:`migrate 0 1`. - :raises DeprecationWarning: This has been fully deprecated and cannot be used. + Raises + ------ + DeprecationWarning + This has been fully deprecated and cannot be used. """ raise DeprecationWarning( f"""material_components is deprecated, and has been removed in MontePy 1.0.0. @@ -421,8 +421,7 @@ def material_components(self): # pragma: no cover @make_prop_pointer("_default_libs") def default_libraries(self): - """ - The default libraries that are used when a nuclide doesn't have a relevant library specified. + """The default libraries that are used when a nuclide doesn't have a relevant library specified. Default Libraries ^^^^^^^^^^^^^^^^^ @@ -453,8 +452,7 @@ def default_libraries(self): def get_nuclide_library( self, nuclide: Nuclide, library_type: LibraryType ) -> Union[Library, None]: - """ - Figures out which nuclear data library will be used for the given nuclide in this + """Figures out which nuclear data library will be used for the given nuclide in this given material in this given problem. This follows the MCNP lookup process and returns the first Library to meet these rules. @@ -465,21 +463,31 @@ def get_nuclide_library( #. Finally if the two other options failed ``M0`` will be checked. These are stored in :func:`montepy.materials.Materials.default_libraries`. - .. note:: + Notes + ----- - The final backup is that MCNP will use the first matching library in ``XSDIR``. - Currently MontePy doesn't support reading an ``XSDIR`` file and so it will return none in this case. + The final backup is that MCNP will use the first matching library in ``XSDIR``. + Currently MontePy doesn't support reading an ``XSDIR`` file and so it will return none in this case. - .. versionadded:: 1.0.0 - :param nuclide: the nuclide to check. - :type nuclide: Union[Nuclide, str] - :param library_type: the LibraryType to check against. - :type library_type: LibraryType - :returns: the library that will be used in this scenario by MCNP. - :rtype: Union[Library, None] - :raises TypeError: If arguments of the wrong type are given. + .. versionadded:: 1.0.0 + Parameters + ---------- + nuclide : Union[Nuclide, str] + the nuclide to check. + library_type : LibraryType + the LibraryType to check against. + + Returns + ------- + Union[Library, None] + the library that will be used in this scenario by MCNP. + + Raises + ------ + TypeError + If arguments of the wrong type are given. """ if not isinstance(nuclide, (Nuclide, str)): raise TypeError(f"nuclide must be a Nuclide. {nuclide} given.") @@ -501,10 +509,10 @@ def get_nuclide_library( return None def __getitem__(self, idx): - """ """ - if not isinstance(idx, (int, slice)): + """""" + if not isinstance(idx, (Integral, slice)): raise TypeError(f"Not a valid index. {idx} given.") - if isinstance(idx, int): + if isinstance(idx, Integral): comp = self._components[idx] return self.__unwrap_comp(comp) # else it's a slice @@ -522,8 +530,8 @@ def gen_wrapper(): return gen_wrapper() def __setitem__(self, idx, newvalue): - """ """ - if not isinstance(idx, (int, slice)): + """""" + if not isinstance(idx, (Integral, slice)): raise TypeError(f"Not a valid index. {idx} given.") old_vals = self._components[idx] self._check_valid_comp(newvalue) @@ -536,10 +544,8 @@ def __setitem__(self, idx, newvalue): def __len__(self): return len(self._components) - def _check_valid_comp(self, newvalue: tuple[Nuclide, float]): - """ - Checks valid compositions and raises an error if needed. - """ + def _check_valid_comp(self, newvalue: tuple[Nuclide, Real]): + """Checks valid compositions and raises an error if needed.""" if not isinstance(newvalue, tuple): raise TypeError( f"Invalid component given. Must be tuple of Nuclide, fraction. {newvalue} given." @@ -550,7 +556,7 @@ def _check_valid_comp(self, newvalue: tuple[Nuclide, float]): ) if not isinstance(newvalue[0], Nuclide): raise TypeError(f"First element must be an Nuclide. {newvalue[0]} given.") - if not isinstance(newvalue[1], (float, int)): + if not isinstance(newvalue[1], Real): raise TypeError( f"Second element must be a fraction greater than 0. {newvalue[1]} given." ) @@ -560,9 +566,9 @@ def _check_valid_comp(self, newvalue: tuple[Nuclide, float]): ) def __delitem__(self, idx): - if not isinstance(idx, (int, slice)): + if not isinstance(idx, (Integral, slice)): raise TypeError(f"Not a valid index. {idx} given.") - if isinstance(idx, int): + if isinstance(idx, Integral): self.__delitem(idx) return # else it's a slice @@ -601,11 +607,11 @@ def __delitem(self, idx): del self._components[idx] def __contains__(self, nuclide): - if not isinstance(nuclide, (Nuclide, Nucleus, Element, str)): + if not isinstance(nuclide, (Nuclide, Nucleus, Element, str, Integral)): raise TypeError( f"Can only check if a Nuclide, Nucleus, Element, or str is in a material. {nuclide} given." ) - if isinstance(nuclide, str): + if isinstance(nuclide, (str, Integral)): nuclide = Nuclide(nuclide) # switch to elemental if isinstance(nuclide, (Nucleus, Nuclide)) and nuclide.A == 0: @@ -628,13 +634,14 @@ def __contains__(self, nuclide): return element in self._elements def append(self, nuclide_frac_pair: tuple[Nuclide, float]): - """ - Appends the tuple to this material. + """Appends the tuple to this material. .. versionadded:: 1.0.0 - :param nuclide_frac_pair: a tuple of the nuclide and the fraction to add. - :type nuclide_frac_pair: tuple[Nuclide, float] + Parameters + ---------- + nuclide_frac_pair : tuple[Nuclide, float] + a tuple of the nuclide and the fraction to add. """ self._check_valid_comp(nuclide_frac_pair) self._elements.add(nuclide_frac_pair[0].element) @@ -650,13 +657,14 @@ def append(self, nuclide_frac_pair: tuple[Nuclide, float]): self._tree["data"].append_nuclide(("_", nuclide_frac_pair[0]._tree, node)) def change_libraries(self, new_library: Union[str, Library]): - """ - Change the library for all nuclides in the material. + """Change the library for all nuclides in the material. .. versionadded:: 1.0.0 - :param new_library: the new library to set all Nuclides to use. - :type new_library: Union[str, Library] + Parameters + ---------- + new_library : Union[str, Library] + the new library to set all Nuclides to use. """ if not isinstance(new_library, (Library, str)): raise TypeError( @@ -667,39 +675,35 @@ def change_libraries(self, new_library: Union[str, Library]): for nuclide, _ in self: nuclide.library = new_library - def add_nuclide(self, nuclide: Union[Nuclide, str, int], fraction: float): - """ - Add a new component to this material of the given nuclide, and fraction. + def add_nuclide(self, nuclide: NuclideLike, fraction: float): + """Add a new component to this material of the given nuclide, and fraction. .. versionadded:: 1.0.0 - :param nuclide: The nuclide to add, which can be a string Identifier, or ZAID. - :type nuclide: Nuclide, str, int - :param fraction: the fraction of this component being added. - :type fraction: float + Parameters + ---------- + nuclide : Nuclide, str, int + The nuclide to add, which can be a string Identifier, or + ZAID. + fraction : float + the fraction of this component being added. """ - if not isinstance(nuclide, (Nuclide, str, int)): + if not isinstance(nuclide, (Nuclide, str, Integral)): raise TypeError( f"Nuclide must of type Nuclide, str, or int. {nuclide} of type {type(nuclide)} given." ) - if not isinstance(fraction, (float, int)): - raise TypeError( - f"Fraction must be a numerical value. {fraction} of type {type(fraction)}" - ) - if isinstance(nuclide, (str, int)): - nuclide = Nuclide(nuclide) + nuclide = self._promote_nuclide(nuclide, True) self.append((nuclide, fraction)) - def contains( + def contains_all( self, - nuclide: Union[Nuclide, Nucleus, Element, str, int], - *args: Union[Nuclide, Nucleus, Element, str, int], + *nuclides: NuclideLike, threshold: float = 0.0, + strict: bool = False, ) -> bool: - """ - Checks if this material contains multiple nuclides. + """Checks if this material contains of all of the given nuclides. - A boolean and is used for this comparison. + A boolean "and" is used for this comparison. That is this material must contain all nuclides at or above the given threshold in order to return true. @@ -713,100 +717,197 @@ def contains( # try to find LEU materials for mat in problem.materials: - if mat.contains("U-235", threshold=0.02): + if mat.contains_all("U-235", threshold=0.02): # your code here pass - # try to find any fissile materials - for mat in problem.materials: - if mat.contains("U-235", "U-233", "Pu-239", threshold=1e-6): - pass - # try to find a uranium for mat in problem.materials: - if mat.contains("U"): + if mat.contains_all("U"): pass - .. note:: + Notes + ----- + + The difference between :func:`contains_all` and :func:`contains_any` is only for how they + handle being given multiple nuclides. This does not impact how given Elements will match + daughter Nuclides. This is handled instead by ``strict``. + + Notes + ----- + + For details on how to use the ``strict`` argument see the examples in: :func:`find`. + - If a nuclide is in a material multiple times, and cumulatively exceeds the threshold, - but for each instance it appears it is below the threshold this method will return False. .. versionadded:: 1.0.0 - :param nuclide: the first nuclide to check for. - :type nuclide: Union[Nuclide, Nucleus, Element, str, int] - :param args: a plurality of other nuclides to check for. - :type args: Union[Nuclide, Nucleus, Element, str, int] - :param threshold: the minimum concentration of a nuclide to be considered. The material components are not - first normalized. - :type threshold: float + Parameters + ---------- + *nuclides : Union[Nuclide, Nucleus, Element, str, int] + a plurality of nuclides to check for. + threshold : float + the minimum concentration of a nuclide to be considered. The + material components are not first normalized. + strict : bool + If True this does not let an elemental nuclide match all + child isotopes, isomers, nor will an isotope match all + isomers, nor will a blank library match all libraries. + + Returns + ------- + bool + whether or not this material contains all components given + above the threshold. + + Raises + ------ + TypeError + if any argument is of the wrong type. + ValueError + if the fraction is not positive or zero, or if nuclide + cannot be interpreted as a Nuclide. + """ + return self._contains_arb( + *nuclides, bool_func=all, threshold=threshold, strict=strict + ) + + def contains_any( + self, + *nuclides: NuclideLike, + threshold: float = 0.0, + strict: bool = False, + ) -> bool: + """Checks if this material contains any of the given nuclide. + + A boolean "or" is used for this comparison. + That is, this material must contain any nuclides at or above the given threshold + in order to return true. + + Examples + ^^^^^^^^ + + .. testcode:: + + import montepy + problem = montepy.read_input("tests/inputs/test.imcnp") + + # try to find any fissile materials + for mat in problem.materials: + if mat.contains_any("U-235", "U-233", "Pu-239", threshold=1e-6): + pass + + Notes + ----- - :return: whether or not this material contains all components given above the threshold. - :rtype: bool + For details on how to use the ``strict`` argument see the examples in: :func:`find`. - :raises TypeError: if any argument is of the wrong type. - :raises ValueError: if the fraction is not positive or zero, or if nuclide cannot be interpreted as a Nuclide. + .. versionadded:: 1.0.0 + + Parameters + ---------- + *nuclides : Union[Nuclide, Nucleus, Element, str, int] + a plurality of nuclides to check for. + threshold : float + the minimum concentration of a nuclide to be considered. The + material components are not first normalized. + strict : bool + If True this does not let an elemental nuclide match all + child isotopes, isomers, nor will an isotope match all + isomers, nor will a blank library match all libraries. + + Returns + ------- + bool + whether or not this material contains all components given + above the threshold. + + Raises + ------ + TypeError + if any argument is of the wrong type. + ValueError + if the fraction is not positive or zero, or if nuclide + cannot be interpreted as a Nuclide. """ - nuclides = [] - for nuclide in [nuclide] + list(args): - if not isinstance(nuclide, (str, int, Element, Nucleus, Nuclide)): - raise TypeError( - f"Nuclide must be a type that can be converted to a Nuclide. The allowed types are: " - f"Nuclide, Nucleus, str, int. {nuclide} given." - ) - if isinstance(nuclide, (str, int)): - nuclide = Nuclide(nuclide) - # treat elemental as element - if isinstance(nuclide, (Nucleus, Nuclide)) and nuclide.A == 0: - nuclide = nuclide.element - if isinstance(nuclide, Nuclide) and not str(nuclide.library): - nuclide = nuclide.nucleus - nuclides.append(nuclide) - - if not isinstance(threshold, float): + return self._contains_arb( + *nuclides, bool_func=any, threshold=threshold, strict=strict + ) + + @staticmethod + def _promote_nuclide(nuclide, strict): + # This is necessary for python 3.9 + if not isinstance(nuclide, (Nuclide, Nucleus, Element, str, Integral)): + raise TypeError( + f"Nuclide must be a type that can be converted to a Nuclide. The allowed types are: " + f"Nuclide, Nucleus, str, int. {nuclide} given." + ) + if isinstance(nuclide, (str, Integral)): + nuclide = Nuclide(nuclide) + # treat elemental as element + if isinstance(nuclide, (Nucleus, Nuclide)) and nuclide.A == 0 and not strict: + nuclide = nuclide.element + if isinstance(nuclide, Nuclide) and not str(nuclide.library) and not strict: + nuclide = nuclide.nucleus + return nuclide + + def _contains_arb( + self, + *nuclides: Union[Nuclide, Nucleus, Element, str, Integral], + bool_func: co.abc.Callable[co.abc.Iterable[bool]] = None, + threshold: float = 0.0, + strict: bool = False, + ) -> bool: + nuclide_finders = [] + if not isinstance(threshold, Real): raise TypeError( f"Threshold must be a float. {threshold} of type: {type(threshold)} given" ) if threshold < 0.0: raise ValueError(f"Threshold must be positive or zero. {threshold} given.") + if not isinstance(strict, bool): + raise TypeError( + f"Strict must be bool. {strict} of type: {type(strict)} given." + ) + for nuclide in nuclides: + nuclide_finders.append(self._promote_nuclide(nuclide, strict)) # fail fast - for nuclide in nuclides: - if nuclide not in self: - return False + if bool_func == all: + for nuclide in nuclide_finders: + if nuclide not in self and bool_func == all: + return False nuclides_search = {} nuclei_search = {} element_search = {} - for nuclide in nuclides: + for nuclide in nuclide_finders: if isinstance(nuclide, Element): - element_search[nuclide] = False + element_search[nuclide] = 0.0 if isinstance(nuclide, Nucleus): - nuclei_search[nuclide] = False + nuclei_search[nuclide] = 0.0 if isinstance(nuclide, Nuclide): - nuclides_search[str(nuclide).lower()] = False + nuclides_search[str(nuclide).lower()] = 0.0 for nuclide, fraction in self: - if fraction < threshold: - continue if str(nuclide).lower() in nuclides_search: - nuclides_search[str(nuclide).lower()] = True + nuclides_search[str(nuclide).lower()] += fraction if nuclide.nucleus in nuclei_search: - nuclei_search[nuclide.nucleus] = True + nuclei_search[nuclide.nucleus] += fraction if nuclide.element in element_search: - element_search[nuclide.element] = True - return all( + element_search[nuclide.element] += fraction + + threshold_check = lambda x: x > threshold + return bool_func( ( - all(nuclides_search.values()), - all(nuclei_search.values()), - all(element_search.values()), + bool_func(map(threshold_check, nuclides_search.values())), + bool_func(map(threshold_check, nuclei_search.values())), + bool_func(map(threshold_check, element_search.values())), ) ) def normalize(self): - """ - Normalizes the components fractions so that they sum to 1.0. + """Normalizes the components fractions so that they sum to 1.0. .. versionadded:: 1.0.0 """ @@ -816,8 +917,7 @@ def normalize(self): @property def values(self): - """ - Get just the fractions, or values from this material. + """Get just the fractions, or values from this material. This acts like a list. It is iterable, and indexable. @@ -869,11 +969,13 @@ def values(self): .. versionadded:: 1.0.0 - :rtype: Generator[float] + Returns + ------- + Generator[float] """ def setter(old_val, new_val): - if not isinstance(new_val, float): + if not isinstance(new_val, Real): raise TypeError( f"Value must be set to a float. {new_val} of type {type(new_val)} given." ) @@ -887,8 +989,7 @@ def setter(old_val, new_val): @property def nuclides(self): - """ - Get just the fractions, or values from this material. + """Get just the fractions, or values from this material. This acts like a list. It is iterable, and indexable. @@ -934,7 +1035,9 @@ def nuclides(self): .. versionadded:: 1.0.0 - :rtype: Generator[Nuclide] + Returns + ------- + Generator[Nuclide] """ def setter(old_val, new_val): @@ -947,8 +1050,7 @@ def setter(old_val, new_val): return _MatCompWrapper(self, 0, setter) def __prep_element_filter(self, filter_obj): - """ - Makes a filter function for an element. + """Makes a filter function for an element. For use by find """ @@ -960,9 +1062,7 @@ def __prep_element_filter(self, filter_obj): return wrapped_filter def __prep_filter(self, filter_obj, attr=None): - """ - Makes a filter function wrapper - """ + """Makes a filter function wrapper""" if filter_obj is None: return lambda _: True @@ -993,13 +1093,13 @@ def slicer(val): def find( self, name: str = None, - element: Union[Element, str, int, slice] = None, + element: Union[Element, str, Integral, slice] = None, A: Union[int, slice] = None, meta_state: Union[int, slice] = None, library: Union[str, slice] = None, + strict: bool = False, ) -> Generator[tuple[int, tuple[Nuclide, float]]]: - """ - Finds all components that meet the given criteria. + """Finds all components that meet the given criteria. The criteria are additive, and a component must match all criteria. That is the boolean and operator is used. @@ -1009,7 +1109,7 @@ def find( For the library the slicing is done using string comparisons. Examples - ^^^^^^^^ + -------- .. testcode:: @@ -1018,7 +1118,7 @@ def find( mat.number = 1 # make non-sense material - for nuclide in ["U-235.80c", "U-238.70c", "Pu-239.00c", "O-16.00c"]: + for nuclide in ["U-235.80c", "U-238.70c", "Pu-239.00c", "O-16.00c", "C-0", "C-12.00c", "Fe-56"]: mat.add_nuclide(nuclide, 0.1) print("Get all uranium nuclides.") @@ -1039,36 +1139,87 @@ def find( Get all transuranics [(0, (Nuclide('U-235.80c'), 0.1)), (1, (Nuclide('U-238.70c'), 0.1)), (2, (Nuclide('Pu-239.00c'), 0.1))] Get all ENDF/B-VIII.0 - [(2, (Nuclide('Pu-239.00c'), 0.1)), (3, (Nuclide('O-16.00c'), 0.1))] + [(2, (Nuclide('Pu-239.00c'), 0.1)), (3, (Nuclide('O-16.00c'), 0.1)), (5, (Nuclide('C-12.00c'), 0.1))] + + Strict (Explicit) Matching + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Generally this functions treats ambiguity implicitly, and will match as many nuclides as possible. + This is generally useful, but not always. + By default when only an element is given all daughter nuclides match, as seen above. + However, MCNP does provide some "natural" or "elemental" nuclear data, + and it would be helpful to find these sometimes. + For instance, you may want to find all instances of elemental nuclides, + and replace them with explicit isotopes (for instance migrating from ENDF/B-VII.1 to ENDF/B-VIII). + In these cases the ``strict`` argument is needed. + When ``strict`` is True an ambiguous ``A`` will only match elemental data: + + .. testcode:: + + print("Strict: False", list(mat.find(element="C"))) + print("Strict: True", list(mat.find(element="C", strict=True))) + + will print: + + .. testoutput:: + + Strict: False [(4, (Nuclide('C-0'), 0.1)), (5, (Nuclide('C-12.00c'), 0.1))] + Strict: True [(4, (Nuclide('C-0'), 0.1))] + + Similarly to find nuclides with no library defined you can use strict: + + .. testcode:: + + print("Strict: False", list(mat.find(library=None))) + print("Strict: True", list(mat.find(library=None, strict=True))) + + This would print: + + .. testoutput:: + + Strict: False [(0, (Nuclide('U-235.80c'), 0.1)), (1, (Nuclide('U-238.70c'), 0.1)), (2, (Nuclide('Pu-239.00c'), 0.1)), (3, (Nuclide('O-16.00c'), 0.1)), (4, (Nuclide('C-0'), 0.1)), (5, (Nuclide('C-12.00c'), 0.1)), (6, (Nuclide('Fe-56'), 0.1))] + Strict: True [(4, (Nuclide('C-0'), 0.1)), (6, (Nuclide('Fe-56'), 0.1))] .. versionadded:: 1.0.0 - :param name: The name to pass to Nuclide to search by a specific Nuclide. If an element name is passed this - will only match elemental nuclides. - :type name: str - :param element: the element to filter by, slices must be slices of integers. This will match all nuclides that - are based on this element. e.g., "U" will match U-235 and U-238. - :type element: Element, str, int, slice - :param A: the filter for the nuclide A number. - :type A: int, slice - :param meta_state: the metastable isomer filter. - :type meta_state: int, slice - :param library: the libraries to limit the search to. - :type library: str, slice - - :returns: a generator of all matching nuclides, as their index and then a tuple of their nuclide, and fraction pairs that match. - :rtype: Generator[tuple[int, tuple[Nuclide, float]]] + Parameters + ---------- + name : str + The name to pass to Nuclide to search by a specific Nuclide. + If an element name is passed this will only match elemental + nuclides. + element : Element, str, int, slice + the element to filter by, slices must be slices of integers. + This will match all nuclides that are based on this element. + e.g., "U" will match U-235 and U-238. + A : int, slice + the filter for the nuclide A number. + meta_state : int, slice + the metastable isomer filter. + library : str, slice + the libraries to limit the search to. + strict : bool + When true this will strictly match elements as only elements + (when no A is given), and only match blank libraries when no + library is given. + + Returns + ------- + Generator[tuple[int, tuple[Nuclide, float]]] + a generator of all matching nuclides, as their index and + then a tuple of their nuclide, and fraction pairs that + match. """ # nuclide type enforcement handled by `Nuclide` - if not isinstance(element, (Element, str, int, slice, type(None))): + if not isinstance(element, (Element, str, Integral, slice, type(None))): raise TypeError( f"Element must be only Element, str, int or slice types. {element} of type{type(element)} given." ) - if not isinstance(A, (int, slice, type(None))): + if not isinstance(A, (Integral, slice, type(None))): raise TypeError( f"A must be an int or a slice. {A} of type {type(A)} given." ) - if not isinstance(meta_state, (int, slice, type(None))): + if not isinstance(meta_state, (Integral, slice, type(None))): raise TypeError( f"meta_state must an int or a slice. {meta_state} of type {type(meta_state)} given." ) @@ -1076,9 +1227,13 @@ def find( raise TypeError( f"library must a str or a slice. {library} of type {type(library)} given." ) + if not isinstance(strict, bool): + raise TypeError( + f"strict must be a bool. {strict} of type {type(strict)} given." + ) if name: fancy_nuclide = Nuclide(name) - if fancy_nuclide.A == 0: + if fancy_nuclide.A == 0 and not strict: element = fancy_nuclide.element fancy_nuclide = None else: @@ -1088,6 +1243,13 @@ def find( else: first_filter = self.__prep_filter(fancy_nuclide) + # create filter for defaults if strict + if strict: + # if strict and element switch to A=0 + if element and A is None: + A = 0 + if library is None: + library = "" filters = [ first_filter, self.__prep_element_filter(element), @@ -1110,9 +1272,9 @@ def find_vals( A: Union[int, slice] = None, meta_state: Union[int, slice] = None, library: Union[str, slice] = None, + strict: bool = False, ) -> Generator[float]: - """ - A wrapper for :func:`find` that only returns the fractions of the components. + """A wrapper for :func:`find` that only returns the fractions of the components. For more examples see that function. @@ -1140,23 +1302,35 @@ def find_vals( .. versionadded:: 1.0.0 - :param name: The name to pass to Nuclide to search by a specific Nuclide. If an element name is passed this - will only match elemental nuclides. - :type name: str - :param element: the element to filter by, slices must be slices of integers. This will match all nuclides that - are based on this element. e.g., "U" will match U-235 and U-238. - :type element: Element, str, int, slice - :param A: the filter for the nuclide A number. - :type A: int, slice - :param meta_state: the metastable isomer filter. - :type meta_state: int, slice - :param library: the libraries to limit the search to. - :type library: str, slice - - :returns: a generator of fractions whose nuclide matches the criteria. - :rtype: Generator[float] + Parameters + ---------- + name : str + The name to pass to Nuclide to search by a specific Nuclide. + If an element name is passed this will only match elemental + nuclides. + element : Element, str, int, slice + the element to filter by, slices must be slices of integers. + This will match all nuclides that are based on this element. + e.g., "U" will match U-235 and U-238. + A : int, slice + the filter for the nuclide A number. + meta_state : int, slice + the metastable isomer filter. + library : str, slice + the libraries to limit the search to. + strict : bool + whether to strictly match elements as only elements (when no + A is given), and only match blank libraries when no library + is given. + + Returns + ------- + Generator[float] + a generator of fractions whose nuclide matches the criteria. """ - for _, (_, fraction) in self.find(name, element, A, meta_state, library): + for _, (_, fraction) in self.find( + name, element, A, meta_state, library, strict + ): yield fraction def __bool__(self): @@ -1164,10 +1338,11 @@ def __bool__(self): @make_prop_pointer("_thermal_scattering", thermal_scattering.ThermalScatteringLaw) def thermal_scattering(self) -> thermal_scattering.ThermalScatteringLaw: - """ - The thermal scattering law for this material + """The thermal scattering law for this material - :rtype: ThermalScatteringLaw + Returns + ------- + ThermalScatteringLaw """ return self._thermal_scattering @@ -1175,8 +1350,10 @@ def thermal_scattering(self) -> thermal_scattering.ThermalScatteringLaw: def cells(self) -> Generator[montepy.cell.Cell]: """A generator of the cells that use this material. - :returns: an iterator of the Cell objects which use this. - :rtype: Generator[Cell] + Returns + ------- + Generator[Cell] + an iterator of the Cell objects which use this. """ if self._problem: for cell in self._problem.cells: @@ -1202,11 +1379,12 @@ def _update_values(self): node.value = nuclide.mcnp_str() def add_thermal_scattering(self, law): - """ - Adds thermal scattering law to the material + """Adds thermal scattering law to the material - :param law: the law that is mcnp formatted - :type law: str + Parameters + ---------- + law : str + the law that is mcnp formatted """ if not isinstance(law, str): raise TypeError( @@ -1218,11 +1396,12 @@ def add_thermal_scattering(self, law): self._thermal_scattering.add_scattering_law(law) def update_pointers(self, data_inputs: list[montepy.data_inputs.DataInput]): - """ - Updates pointer to the thermal scattering data + """Updates pointer to the thermal scattering data - :param data_inputs: a list of the data inputs in the problem - :type data_inputs: list[DataInput] + Parameters + ---------- + data_inputs : list[DataInput] + a list of the data inputs in the problem """ pass @@ -1268,13 +1447,14 @@ def __str__(self): return f"MATERIAL: {self.number}, {print_elements}" def get_material_elements(self): - """ - Get the elements that are contained in this material. + """Get the elements that are contained in this material. This is sorted by the most common element to the least common. - :returns: a sorted list of elements by total fraction - :rtype: list[Element] + Returns + ------- + list[Element] + a sorted list of elements by total fraction """ element_frac = co.Counter() for nuclide, fraction in self: diff --git a/montepy/data_inputs/material_component.py b/montepy/data_inputs/material_component.py index 5d3e7fcf..be7e170b 100644 --- a/montepy/data_inputs/material_component.py +++ b/montepy/data_inputs/material_component.py @@ -2,8 +2,7 @@ class MaterialComponent: - """ - A class to represent a single component in a material. + """A class to represent a single component in a material. For example: this may be H-1 in water: like 1001.80c — 0.6667 @@ -13,7 +12,10 @@ class MaterialComponent: It has been removed in 1.0.0. See :ref:`migrate 0 1`. - :raises DeprecationWarning: whenever called. + Raises + ------ + DeprecationWarning + whenever called. """ def __init__(self, *args): diff --git a/montepy/data_inputs/mode.py b/montepy/data_inputs/mode.py index 9dae3592..3b0c6a1e 100644 --- a/montepy/data_inputs/mode.py +++ b/montepy/data_inputs/mode.py @@ -5,11 +5,12 @@ class Mode(DataInputAbstract): - """ - Class for the particle mode for a problem. + """Class for the particle mode for a problem. - :param input: the Input object representing this data input - :type input: Input + Parameters + ---------- + input : Input + the Input object representing this data input """ def __init__(self, input=None): @@ -41,25 +42,31 @@ def _parse_and_override_particle_modes(self, particles): @property def particles(self): - """ - The type of particles involved in this problem. + """The type of particles involved in this problem. The set will contain instances of :class:`montepy.particle.Particle`. - :rtype: set + Returns + ------- + set """ return self._particles.copy() def add(self, particle): - """ - Adds the given particle to the problem. + """Adds the given particle to the problem. If specifying particle type by string this must be the MCNP shorthand, such as ``n`` for ``Particle.NEUTRON``. - :param particle: the particle type to add to the mode. - :type particle: Particle, str - :raises ValueError: if string is not a valid particle shorthand. + Parameters + ---------- + particle : Particle, str + the particle type to add to the mode. + + Raises + ------ + ValueError + if string is not a valid particle shorthand. """ if not isinstance(particle, (Particle, str, syntax_node.ValueNode)): raise TypeError("particle must be a Particle instance") @@ -72,12 +79,17 @@ def add(self, particle): self._particles.add(particle) def remove(self, particle): - """ - Remove the given particle from the problem + """Remove the given particle from the problem + + Parameters + ---------- + particle : Particle, str + the particle type to remove from the mode. - :param particle: the particle type to remove from the mode. - :type particle: Particle, str - :raises ValueError: if string is not a valid particle shorthand. + Raises + ------ + ValueError + if string is not a valid particle shorthand. """ if not isinstance(particle, (Particle, str)): raise TypeError("particle must be a Particle instance") @@ -86,17 +98,22 @@ def remove(self, particle): self._particles.remove(particle) def set(self, particles): - """ - Completely override the current mode. + """Completely override the current mode. Can specify it as: * ``"n p"`` * ``["n", "p"]`` * ``[Particle.NEUTRON, Particle.PHOTON]`` - :param particles: the particles that the mode will be switched to. - :type particles: list, str - :raises ValueError: if string is not a valid particle shorthand. + Parameters + ---------- + particles : list, str + the particles that the mode will be switched to. + + Raises + ------ + ValueError + if string is not a valid particle shorthand. """ if not isinstance(particles, (list, set, str)): raise TypeError("particles must be a list, string, or set") diff --git a/montepy/data_inputs/nuclide.py b/montepy/data_inputs/nuclide.py index 22350e6f..0369359d 100644 --- a/montepy/data_inputs/nuclide.py +++ b/montepy/data_inputs/nuclide.py @@ -1,29 +1,23 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from montepy.constants import MAX_ATOMIC_SYMBOL_LENGTH from montepy._singleton import SingletonGroup from montepy.data_inputs.element import Element -from montepy.errors import * from montepy.utilities import * from montepy.input_parser.syntax_node import PaddingNode, ValueNode from montepy.particle import LibraryType -import collections from functools import total_ordering import re from typing import Union -import warnings +from numbers import Real, Integral DEFAULT_NUCLIDE_WIDTH = 11 -""" -How many characters wide a nuclide with spacing should be. -""" +"""How many characters wide a nuclide with spacing should be.""" @total_ordering class Library(SingletonGroup): - """ - A class to represent an MCNP nuclear data library, e.g., ``80c``. - + """A class to represent an MCNP nuclear data library, e.g., ``80c``. Examples ^^^^^^^^ @@ -44,10 +38,17 @@ class Library(SingletonGroup): .. versionadded:: 1.0.0 - :param library: The name of the library. - :type library: str - :raises TypeErrror: if a string is not provided. - :raises ValueError: if a valid library is not provided. + Parameters + ---------- + library : str + The name of the library. + + Raises + ------ + TypeErrror + if a string is not provided. + ValueError + if a valid library is not provided. """ __slots__ = "_library", "_lib_type", "_num", "_suffix" @@ -93,51 +94,56 @@ def __init__(self, library: str): @property def library(self) -> str: - """ - The full name of the library. + """The full name of the library. - :rtype: str + Returns + ------- + str """ return self._library @property def library_type(self) -> LibraryType: - """ - The :class:`~montepy.particle.LibraryType` of this library. + """The :class:`~montepy.particle.LibraryType` of this library. This corresponds to the type of library this would specified in a material definition e.g., ``NLIB``, ``PLIB``, etc. - .. seealso:: + See Also + -------- - * :manual63:`5.6.1` + * :manual63:`5.6.1` - :returns: the type of library this library is. - :rtype: LibraryType + Returns + ------- + LibraryType + the type of library this library is. """ return self._lib_type @property def number(self) -> int: - """ - The base number in the library. + """The base number in the library. For example: this would be ``80`` for the library: ``Library('80c')``. - :returns: the base number of the library. - :rtype: int + Returns + ------- + int + the base number of the library. """ return self._num @property def suffix(self) -> str: - """ - The suffix of the library, or the final character of its definition. + """The suffix of the library, or the final character of its definition. For example this would be ``"c"`` for the library: ``Library('80c')``. - :returns: the suffix of the library. - :rtype: str + Returns + ------- + str + the suffix of the library. """ return self._suffix @@ -175,36 +181,39 @@ def __reduce__(self): _ZAID_A_ADDER = 1000 -""" -How much to multiply Z by to form a ZAID. -""" +"""How much to multiply Z by to form a ZAID.""" class Nucleus(SingletonGroup): - """ - A class to represent a nuclide irrespective of the nuclear data being used. + """A class to represent a nuclide irrespective of the nuclear data being used. This is meant to be an immutable representation of the nuclide, no matter what nuclear data library is used. ``U-235`` is always ``U-235``. Generally users don't need to interact with this much as it is almost always wrapped by: :class:`montepy.data_inputs.nuclide.Nuclide`. - .. Note:: This class is immutable, and hashable, meaning it is suitable as a dictionary key. .. versionadded:: 1.0.0 - :param element: the element this Nucleus is based on. - :type element: Element - :param A: The A-number (atomic mass) of the nuclide. If this is elemental this should be 0. - :type A: int - :param meta_state: The metastable state if this nuclide is isomer. - :type meta_state: int - - :raises TypeError: if an parameter is the wrong type. - :raises ValueError: if non-sensical values are given. + Parameters + ---------- + element : Element + the element this Nucleus is based on. + A : int + The A-number (atomic mass) of the nuclide. If this is elemental + this should be 0. + meta_state : int + The metastable state if this nuclide is isomer. + + Raises + ------ + TypeError + if an parameter is the wrong type. + ValueError + if non-sensical values are given. """ __slots__ = "_element", "_A", "_meta_state" @@ -221,12 +230,12 @@ def __init__( ) self._element = element - if not isinstance(A, int): + if not isinstance(A, Integral): raise TypeError(f"A number must be an int. {A} given.") if A < 0: raise ValueError(f"A cannot be negative. {A} given.") self._A = A - if not isinstance(meta_state, (int, type(None))): + if not isinstance(meta_state, (Integral, type(None))): raise TypeError(f"Meta state must be an int. {meta_state} given.") if A == 0 and meta_state != 0: raise ValueError( @@ -240,12 +249,13 @@ def __init__( @property def ZAID(self) -> int: - """ - The ZZZAAA identifier following MCNP convention. + """The ZZZAAA identifier following MCNP convention. If this is metastable the MCNP convention for ZAIDs for metastable isomers will be used. - :rtype: int + Returns + ------- + int """ meta_adder = 300 + 100 * self.meta_state if self.is_metastable else 0 temp = self.Z * _ZAID_A_ADDER + self.A + meta_adder @@ -255,56 +265,62 @@ def ZAID(self) -> int: @property def Z(self) -> int: - """ - The Z number for this isotope. + """The Z number for this isotope. - :returns: the atomic number. - :rtype: int + Returns + ------- + int + the atomic number. """ return self._element.Z @make_prop_pointer("_A") def A(self) -> int: - """ - The A number for this isotope. + """The A number for this isotope. - :returns: the isotope's mass. - :rtype: int + Returns + ------- + int + the isotope's mass. """ pass @make_prop_pointer("_element") def element(self) -> Element: - """ - The base element for this isotope. + """The base element for this isotope. - :returns: The element for this isotope. - :rtype: Element + Returns + ------- + Element + The element for this isotope. """ pass @property def is_metastable(self) -> bool: - """ - Whether or not this is a metastable isomer. + """Whether or not this is a metastable isomer. - :returns: boolean of if this is metastable. - :rtype: bool + Returns + ------- + bool + boolean of if this is metastable. """ return bool(self._meta_state) @make_prop_pointer("_meta_state") def meta_state(self) -> int: - """ - If this is a metastable isomer, which state is it? + """If this is a metastable isomer, which state is it? Can return values in the range [0,4]. The exact state number is decided by who made the ACE file for this, and not quantum mechanics. Convention states that the isomers should be numbered from lowest to highest energy. The ground state will be 0. - :returns: the metastable isomeric state of this "isotope" in the range [0,4]. - :rtype: int + Returns + ------- + int + the metastable isomeric state of this "isotope" in the range + [0,4]. """ pass @@ -328,7 +344,7 @@ def __reduce__(self): def __lt__(self, other): if not isinstance(other, type(self)): - raise TypeError("") + raise TypeError return (self.Z, self.A, self.meta_state) < (other.Z, other.A, other.meta_state) def __str__(self): @@ -340,8 +356,7 @@ def __repr__(self): class Nuclide: - r""" - A class to represent an MCNP nuclide with nuclear data library information. + r"""A class to represent an MCNP nuclide with nuclear data library information. Nuclide accepts ``name`` as a way of specifying a nuclide. This is meant to be more ergonomic than ZAIDs while not going insane with possible formats. @@ -418,36 +433,44 @@ class Nuclide: #. Open an issue. If this approach doesn't work for you please open an issue so we can develop a better solution. - .. seealso:: + See Also + -------- + + * :manual62:`107` + * :manual63:`5.6.1` - * :manual62:`107` - * :manual63:`5.6.1` .. versionadded:: 1.0.0 This was added as replacement for ``montepy.data_inputs.Isotope``. - - - :param name: A fancy name way of specifying a nuclide. - :type name: str - :param ZAID: The ZAID in MCNP format, the library can be included. - :type ZAID: str - :param element: the element this Nucleus is based on. - :type element: Element - :param Z: The Z-number (atomic number) of the nuclide. - :type Z: int - :param A: The A-number (atomic mass) of the nuclide. If this is elemental this should be 0. - :type A: int - :param meta_state: The metastable state if this nuclide is isomer. - :type meta_state: int - :param library: the library to use for this nuclide. - :type library: str - :param node: The ValueNode to build this off of. Should only be used by MontePy. - :type node: ValueNode - - :raises TypeError: if an parameter is the wrong type. - :raises ValueError: if non-sensical values are given. + Parameters + ---------- + name : str + A fancy name way of specifying a nuclide. + ZAID : str + The ZAID in MCNP format, the library can be included. + element : Element + the element this Nucleus is based on. + Z : int + The Z-number (atomic number) of the nuclide. + A : int + The A-number (atomic mass) of the nuclide. If this is elemental + this should be 0. + meta_state : int + The metastable state if this nuclide is isomer. + library : str + the library to use for this nuclide. + node : ValueNode + The ValueNode to build this off of. Should only be used by + MontePy. + + Raises + ------ + TypeError + if a parameter is the wrong type. + ValueError + if non-sensical values are given. """ _NAME_PARSER = re.compile( @@ -459,15 +482,11 @@ class Nuclide: (\.(?P\d{{2,}}[a-z]+))?""", re.I | re.VERBOSE, ) - """ - Parser for fancy names. - """ + """Parser for fancy names.""" # Cl-52 Br-101 Xe-150 Os-203 Cm-251 Og-296 _BOUNDING_CURVE = [(17, 52), (35, 101), (54, 150), (76, 203), (96, 251), (118, 296)] - """ - Points on bounding curve for determining if "valid" isotope - """ + """Points on bounding curve for determining if "valid" isotope""" _STUPID_MAP = { "95642": {"_meta_state": 0}, "95242": {"_meta_state": 1}, @@ -487,7 +506,7 @@ def __init__( self._library = Library("") ZAID = "" - if not isinstance(name, (str, int, Element, Nucleus, Nuclide, type(None))): + if not isinstance(name, (str, Integral, Element, Nucleus, Nuclide, type(None))): raise TypeError( f"Name must be str, int, Element, or Nucleus. {name} of type {type(name)} given." ) @@ -528,14 +547,14 @@ def __init__( @classmethod def _handle_stupid_legacy_stupidity(cls, ZAID): - """ - This handles legacy issues where ZAID are swapped. + """This handles legacy issues where ZAID are swapped. For now this is only for Am-242 and Am-242m1. - .. seealso:: + See Also + -------- - * :manual631:`1.2.2` + * :manual631:`1.2.2` """ ZAID = str(ZAID) ret = {} @@ -547,16 +566,20 @@ def _handle_stupid_legacy_stupidity(cls, ZAID): @classmethod def _parse_zaid(cls, ZAID) -> dict[str, object]: - """ - Parses the ZAID fully including metastable isomers. + """Parses the ZAID fully including metastable isomers. See Table 3-32 of LA-UR-17-29881 - :param ZAID: the ZAID without the library - :type ZAID: int - :returns: a dictionary with the parsed information, - in a way that can be loaded into nucleus. Keys are: _element, _A, _meta_state - :rtype: dict[str, Object] + Parameters + ---------- + ZAID : int + the ZAID without the library + + Returns + ------- + dict[str, Object] + a dictionary with the parsed information, in a way that can + be loaded into nucleus. Keys are: _element, _A, _meta_state """ def is_probably_an_isotope(Z, A): @@ -603,129 +626,145 @@ def is_probably_an_isotope(Z, A): @property def ZAID(self) -> int: - """ - The ZZZAAA identifier following MCNP convention + """The ZZZAAA identifier following MCNP convention - :rtype: int + Returns + ------- + int """ # if this is made mutable this cannot be user provided, but must be calculated. return self._nucleus.ZAID @property def Z(self) -> int: - """ - The Z number for this isotope. + """The Z number for this isotope. - :returns: the atomic number. - :rtype: int + Returns + ------- + int + the atomic number. """ return self._nucleus.Z @property def A(self) -> int: - """ - The A number for this isotope. + """The A number for this isotope. - :returns: the isotope's mass. - :rtype: int + Returns + ------- + int + the isotope's mass. """ return self._nucleus.A @property def element(self) -> Element: - """ - The base element for this isotope. + """The base element for this isotope. - :returns: The element for this isotope. - :rtype: Element + Returns + ------- + Element + The element for this isotope. """ return self._nucleus.element @make_prop_pointer("_nucleus") def nucleus(self) -> Nucleus: - """ - The base nuclide of this nuclide without the nuclear data library. + """The base nuclide of this nuclide without the nuclear data library. - :rtype:Nucleus + Returns + ------- + Nucleus """ pass @property def is_metastable(self) -> bool: - """ - Whether or not this is a metastable isomer. + """Whether or not this is a metastable isomer. - :returns: boolean of if this is metastable. - :rtype: bool + Returns + ------- + bool + boolean of if this is metastable. """ return self._nucleus.is_metastable @property def meta_state(self) -> int: - """ - If this is a metastable isomer, which state is it? + """If this is a metastable isomer, which state is it? Can return values in the range [0,4]. 0 corresponds to the ground state. The exact state number is decided by who made the ACE file for this, and not quantum mechanics. Convention states that the isomers should be numbered from lowest to highest energy. - :returns: the metastable isomeric state of this "isotope" in the range [0,4]l - :rtype: int + Returns + ------- + int + the metastable isomeric state of this "isotope" in the range + [0,4]l """ return self._nucleus.meta_state @make_prop_pointer("_library", (str, Library), Library) def library(self) -> Library: - """ - The MCNP library identifier e.g. 80c + """The MCNP library identifier e.g. 80c - :rtype: Library + Returns + ------- + Library """ pass def mcnp_str(self) -> str: - """ - Returns an MCNP formatted representation. + """Returns an MCNP formatted representation. E.g., 1001.80c - :returns: a string that can be used in MCNP - :rtype: str + Returns + ------- + str + a string that can be used in MCNP """ return f"{self.ZAID}.{self.library}" if str(self.library) else str(self.ZAID) def nuclide_str(self) -> str: - """ - Creates a human readable version of this nuclide excluding the data library. + """Creates a human readable version of this nuclide excluding the data library. This is of the form Atomic symbol - A [metastable state]. e.g., ``U-235m1``. - :rtypes: str + Returns + ------- + str """ meta_suffix = f"m{self.meta_state}" if self.is_metastable else "" suffix = f".{self._library}" if str(self._library) else "" return f"{self.element.symbol}-{self.A}{meta_suffix}{suffix}" def get_base_zaid(self) -> int: - """ - Get the ZAID identifier of the base isotope this is an isomer of. + """Get the ZAID identifier of the base isotope this is an isomer of. This is mostly helpful for working with metastable isomers. - :returns: the mcnp ZAID of the ground state of this isotope. - :rtype: int + Returns + ------- + int + the mcnp ZAID of the ground state of this isotope. """ return self.Z * _ZAID_A_ADDER + self.A @classmethod def _parse_fancy_name(cls, identifier): - """ - Parses a fancy name that is a ZAID, a Symbol-A, or nucleus, nuclide, or element. + """Parses a fancy name that is a ZAID, a Symbol-A, or nucleus, nuclide, or element. + + Parameters + ---------- + identifier + idenitifer : Union[str, int, element, Nucleus, Nuclide] - :param identifier: - :type idenitifer: Union[str, int, element, Nucleus, Nuclide] - :returns: a tuple of element, a, isomer, library - :rtype: tuple + Returns + ------- + tuple + a tuple of element, a, isomer, library """ if isinstance(identifier, (Nucleus, Nuclide)): if isinstance(identifier, Nuclide): @@ -739,7 +778,7 @@ def _parse_fancy_name(cls, identifier): A = 0 isomer = 0 library = "" - if isinstance(identifier, (int, float)): + if isinstance(identifier, Real): if identifier > _ZAID_A_ADDER: parts = Nuclide._parse_zaid(int(identifier)) element, A, isomer = ( diff --git a/montepy/data_inputs/thermal_scattering.py b/montepy/data_inputs/thermal_scattering.py index f2879b51..200b0441 100644 --- a/montepy/data_inputs/thermal_scattering.py +++ b/montepy/data_inputs/thermal_scattering.py @@ -10,22 +10,24 @@ class ThermalScatteringLaw(DataInputAbstract): - """ - Class to hold MT Inputs + """Class to hold MT Inputs This is designed to be called two ways. The first is with a read input file using input_card, comment The second is after a read with a material and a comment (using named inputs) - .. seealso:: + See Also + -------- - * :manual63:`5.6.2` - * :manual62:`110` + * :manual63:`5.6.2` + * :manual62:`110` - :param input: the Input object representing this data input - :type input: Union[Input, str] - :param material: the parent Material object that owns this - :type material: Material + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + material : Material + the parent Material object that owns this """ _parser = ThermalParser() @@ -56,28 +58,31 @@ def _has_classifier(): @make_prop_val_node("_old_number") def old_number(self): - """ - The material number from the file + """The material number from the file - :rtype: int + Returns + ------- + int """ pass @property def parent_material(self): - """ - The Material object this is tied to. + """The Material object this is tied to. - :rtype: Material + Returns + ------- + Material """ return self._parent_material @property def thermal_scattering_laws(self): - """ - The thermal scattering laws to use for this material as strings. + """The thermal scattering laws to use for this material as strings. - :rtype: list + Returns + ------- + list """ ret = [] for law in self._scattering_laws: @@ -98,11 +103,12 @@ def thermal_scattering_laws(self, laws): self._scattering_laws.append(self._generate_default_node(str, law)) def add_scattering_law(self, law): - """ - Adds the requested scattering law to this material + """Adds the requested scattering law to this material - :param law: the thermal scattering law to add. - :type law: str + Parameters + ---------- + law : str + the thermal scattering law to add. """ self._scattering_laws.append(self._generate_default_node(str, law)) @@ -124,13 +130,18 @@ def _update_values(self): ) def update_pointers(self, data_inputs): - """ - Updates pointer to the thermal scattering data - - :param data_inputs: a list of the data inputs in the problem - :type data_inputs: list - :returns: True iff this input should be removed from ``problem.data_inputs`` - :rtype: bool + """Updates pointer to the thermal scattering data + + Parameters + ---------- + data_inputs : list + a list of the data inputs in the problem + + Returns + ------- + bool + True iff this input should be removed from + ``problem.data_inputs`` """ # use caching first if self._problem: diff --git a/montepy/data_inputs/transform.py b/montepy/data_inputs/transform.py index 9658c806..820e52aa 100644 --- a/montepy/data_inputs/transform.py +++ b/montepy/data_inputs/transform.py @@ -14,17 +14,18 @@ class Transform(data_input.DataInputAbstract, Numbered_MCNP_Object): - """ - Input to represent a transform input (TR). + """Input to represent a transform input (TR). .. 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 + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input + number : int + The number to set for this object. """ def __init__( @@ -101,39 +102,43 @@ def _has_classifier(): @property def hidden_transform(self): - """ - Whether or not this transform is "hidden" i.e., has no number. + """Whether or not this transform is "hidden" i.e., has no number. If True this transform was created from a fill card, and has no number. - :rtype: bool + Returns + ------- + bool """ return self._pass_through @make_prop_pointer("_is_in_degrees", bool) def is_in_degrees(self): - """ - The rotation matrix is in degrees and not in cosines + """The rotation matrix is in degrees and not in cosines - :rtype: bool + Returns + ------- + bool """ pass @make_prop_val_node("_old_number") def old_number(self): - """ - The transform number used in the original file + """The transform number used in the original file - :rtype: int + Returns + ------- + int """ pass @property def displacement_vector(self): - """ - The transform displacement vector + """The transform displacement vector - :rtype: numpy.array + Returns + ------- + numpy.array """ return self._displacement_vector @@ -147,10 +152,11 @@ def displacement_vector(self, vector): @property def rotation_matrix(self): - """ - The rotation matrix + """The rotation matrix - :rtype: np.array + Returns + ------- + np.array """ return self._rotation_matrix @@ -164,11 +170,12 @@ def rotation_matrix(self, matrix): @make_prop_pointer("_is_main_to_aux", bool) def is_main_to_aux(self): - """ - Whether or not the displacement vector points from the main origin to auxilary + """Whether or not the displacement vector points from the main origin to auxilary origin, or vice versa. - :rtype: bool + Returns + ------- + bool """ return self._is_main_to_aux @@ -239,13 +246,19 @@ def validate(self): def equivalent(self, other, tolerance): """Determines if this is effectively equivalent to another transformation - :param other: The transform to compare self again. - :type other: Transform - :param tolerance: the allowable difference in any attribute to still be considered equivalent. - :type tolerance: float - - :returns: True iff all transform elements in both are within the tolerance of each other. - :rtype: bool + Parameters + ---------- + other : Transform + The transform to compare self again. + tolerance : float + the allowable difference in any attribute to still be + considered equivalent. + + Returns + ------- + bool + True iff all transform elements in both are within the + tolerance of each other. """ if self.is_in_degrees != other.is_in_degrees: diff --git a/montepy/data_inputs/universe_input.py b/montepy/data_inputs/universe_input.py index 923b59c6..2d8ed8f9 100644 --- a/montepy/data_inputs/universe_input.py +++ b/montepy/data_inputs/universe_input.py @@ -11,18 +11,19 @@ class UniverseInput(CellModifierInput): - """ - Object to actually handle the ``U`` input in cells + """Object to actually handle the ``U`` input in cells and data blocks. - :param input: the Input object representing this data 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 - :type key: str - :param value: the value syntax tree from the key-value pair in a cell - :type value: SyntaxNode + Parameters + ---------- + input : Union[Input, str] + the Input object representing this data input + in_cell_block : bool + if this card came from the cell block of an input file. + key : str + the key from the key-value pair in a cell + value : SyntaxNode + the value syntax tree from the key-value pair in a cell """ def __init__( @@ -109,8 +110,7 @@ def universe(self): @property def not_truncated(self): - """ - Indicates if this cell has been marked as not being truncated for optimization. + """Indicates if this cell has been marked as not being truncated for optimization. See Note 1 from section 3.3.1.5.1 of the user manual (LA-UR-17-29981). @@ -124,8 +124,11 @@ def not_truncated(self): -- LA-UR-17-29981. - :rtype: bool - :returns: True if this cell has been marked as not being truncated by the parent filled cell. + Returns + ------- + bool + True if this cell has been marked as not being truncated by + the parent filled cell. """ return self._not_truncated diff --git a/montepy/data_inputs/volume.py b/montepy/data_inputs/volume.py index e6db1e57..50d79da5 100644 --- a/montepy/data_inputs/volume.py +++ b/montepy/data_inputs/volume.py @@ -14,17 +14,18 @@ def _ensure_positive(self, value): class Volume(CellModifierInput): - """ - Class for the data input that modifies cell volumes; ``VOL``. - - :param input: the Input object representing this data input - :type input: Input - :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 - :type key: str - :param value: the value syntax tree from the key-value pair in a cell - :type value: SyntaxNode + """Class for the data input that modifies cell volumes; ``VOL``. + + Parameters + ---------- + input : Input + the Input object representing this data input + in_cell_block : bool + if this card came from the cell block of an input file. + key : str + the key from the key-value pair in a cell + value : SyntaxNode + the value syntax tree from the key-value pair in a cell """ def __init__(self, input=None, in_cell_block=False, key=None, value=None): @@ -102,13 +103,14 @@ def _has_classifier(): deletable=True, ) def volume(self): - """ - The actual cell volume. + """The actual cell volume. Only available at the cell level. - :returns: the cell volume iff this is for a single cell - :rtype: float + Returns + ------- + float + the cell volume iff this is for a single cell """ pass @@ -119,16 +121,18 @@ def _tree_value(self): @property def is_mcnp_calculated(self): - """ - Indicates whether or not the cell volume will attempt to be calculated by MCNP. + """Indicates whether or not the cell volume will attempt to be calculated by MCNP. This can be disabled by either manually setting the volume or disabling this calculation globally. This does not guarantee that MCNP will able to do so. Complex geometries may make this impossible. - :returns: True iff MCNP will try to calculate the volume for this cell. - :rtype: bool + Returns + ------- + bool + True iff MCNP will try to calculate the volume for this + cell. """ if self._problem and self.in_cell_block: if not self._problem.cells._volume.is_mcnp_calculated: @@ -147,11 +151,12 @@ def has_information(self): @property def set(self) -> bool: - """ - If this volume is set. + """If this volume is set. - :returns: true if the volume is manually set. - :rtype: bool + Returns + ------- + bool + true if the volume is manually set. """ return self.volume is not None diff --git a/montepy/errors.py b/montepy/errors.py index 75554649..7b0dec88 100644 --- a/montepy/errors.py +++ b/montepy/errors.py @@ -4,18 +4,14 @@ class LineOverRunWarning(UserWarning): - """ - Raised when non-comment inputs exceed the allowed line length in an input. - """ + """Raised when non-comment inputs exceed the allowed line length in an input.""" def __init__(self, message): self.message = message class MalformedInputError(ValueError): - """ - Raised when there is an error with the MCNP input not related to the parser. - """ + """Raised when there is an error with the MCNP input not related to the parser.""" def __init__(self, input, message): if input and getattr(input, "input_file", None) and input.input_file: @@ -34,9 +30,7 @@ def __init__(self, input, message): class ParsingError(MalformedInputError): - """ - Raised when there is an error parsing the MCNP input at the SLY parsing layer. - """ + """Raised when there is an error parsing the MCNP input at the SLY parsing layer.""" def __init__(self, input, message, error_queue): messages = [] @@ -110,9 +104,7 @@ def _print_input( class NumberConflictError(Exception): - """ - Raised when there is a conflict in number spaces - """ + """Raised when there is a conflict in number spaces""" def __init__(self, message): self.message = message @@ -124,14 +116,17 @@ class BrokenObjectLinkError(MalformedInputError): def __init__(self, parent_type, parent_number, child_type, child_number): """ - :param parent_type: Name of the parent object linking (e.g., Cell) - :type parent_type: str - :param parent_number: the number of the parent object - :type parent_number: int - :param child_type: Name of the type of object missing in the link (e.g., surface) - :type child_type: str - :param child_number: the number for the missing object - :type child_number: int + Parameters + ---------- + parent_type : str + Name of the parent object linking (e.g., Cell) + parent_number : int + the number of the parent object + child_type : str + Name of the type of object missing in the link (e.g., + surface) + child_number : int + the number for the missing object """ super().__init__( None, @@ -140,8 +135,7 @@ def __init__(self, parent_type, parent_number, child_type, child_number): class RedundantParameterSpecification(ValueError): - """ - Raised when multiple conflicting parameters are given. + """Raised when multiple conflicting parameters are given. e.g., ``1 0 -1 imp:n=5 imp:n=0`` """ @@ -153,10 +147,9 @@ def __init__(self, key, new_value): super().__init__(self.message) -class ParticleTypeNotInProblem(ValueError): - """ - Raised when data are set for a particle type not in - the problem's mode. +class ParticleTypeWarning(Warning): + """Base class for incongruencies between particle types + in problem mode, cell importances, and tallies """ def __init__(self, message): @@ -164,21 +157,24 @@ def __init__(self, message): super().__init__(message) -class ParticleTypeNotInCell(ValueError): +class ParticleTypeNotInProblem(ParticleTypeWarning): + """Raised when data, such as cell importance or tally type, + are set for a particle type not in the problem's mode. """ - Raised when data for importance data for a particle in + + pass + + +class ParticleTypeNotInCell(ParticleTypeWarning): + """Raised when data for importance data for a particle in the problem is not provided for a cell. """ - def __init__(self, message): - self.message = message - super().__init__(message) + pass class UnsupportedFeature(NotImplementedError): - """ - Raised when MCNP syntax that is not supported is found - """ + """Raised when MCNP syntax that is not supported is found""" def __init__(self, message): self.message = message @@ -186,9 +182,7 @@ def __init__(self, message): class UnknownElement(ValueError): - """ - Raised when an undefined element is used. - """ + """Raised when an undefined element is used.""" def __init__(self, missing_val): self.message = f"An element identified by: {missing_val} is unknown to MontePy." @@ -196,9 +190,7 @@ def __init__(self, missing_val): class IllegalState(ValueError): - """ - Raised when an object can't be printed out due to an illegal state. - """ + """Raised when an object can't be printed out due to an illegal state.""" def __init__(self, message): self.message = message @@ -206,9 +198,7 @@ def __init__(self, message): class LineExpansionWarning(Warning): - """ - Warning for when a field or line expands that may damage user formatting. - """ + """Warning for when a field or line expands that may damage user formatting.""" def __init__(self, message): self.message = message @@ -216,16 +206,21 @@ def __init__(self, message): def add_line_number_to_exception(error, broken_robot): - """ - Adds additional context to an Exception raised by an :class:`~montepy.mcnp_object.MCNP_Object`. + """Adds additional context to an Exception raised by an :class:`~montepy.mcnp_object.MCNP_Object`. This will add the line, file name, and the input lines to the error. - :param error: The error that was raised. - :type error: Exception - :param broken_robot: The parent object that had the error raised. - :type broken_robot: MCNP_Object - :raises Exception: ... that's the whole point. + Parameters + ---------- + error : Exception + The error that was raised. + broken_robot : MCNP_Object + The parent object that had the error raised. + + Raises + ------ + Exception + ... that's the whole point. """ # avoid calling this n times recursively if hasattr(error, "montepy_handled"): diff --git a/montepy/geometry_operators.py b/montepy/geometry_operators.py index b1b98f18..799a4f0b 100644 --- a/montepy/geometry_operators.py +++ b/montepy/geometry_operators.py @@ -3,29 +3,18 @@ class Operator(Enum): - """ - Enumeration of the allowed geometry set logic. - """ + """Enumeration of the allowed geometry set logic.""" INTERSECTION = "*" - """ - Represents the intersection of sets. - """ + """Represents the intersection of sets.""" UNION = ":" - """ - Represents the union of sets. - """ + """Represents the union of sets.""" COMPLEMENT = "#" - """ - Represents the complement of a set. - """ + """Represents the complement of a set.""" _SHIFT = ">" - """ - Internal operator essentially equivalent to No-op. + """Internal operator essentially equivalent to No-op. This is used to properly handle some leaf nodes. """ GROUP = "()" - """ - Grouping operator that represents parentheses. - """ + """Grouping operator that represents parentheses.""" diff --git a/montepy/input_parser/block_type.py b/montepy/input_parser/block_type.py index 0c9b3207..46ae3884 100644 --- a/montepy/input_parser/block_type.py +++ b/montepy/input_parser/block_type.py @@ -4,20 +4,13 @@ @unique class BlockType(Enum): - """ - An enumeration for the different blocks in an input file. - """ + """An enumeration for the different blocks in an input file.""" CELL = 0 - """ - The first block that details Cell information. - """ + """The first block that details Cell information.""" SURFACE = 1 - """ - The second block that details Surface information. - """ + """The second block that details Surface information.""" DATA = 2 - """ - The third block that provides additional information + """The third block that provides additional information and data. """ diff --git a/montepy/input_parser/cell_parser.py b/montepy/input_parser/cell_parser.py index f9b4ed5c..7d9431c4 100644 --- a/montepy/input_parser/cell_parser.py +++ b/montepy/input_parser/cell_parser.py @@ -5,11 +5,12 @@ class CellParser(MCNP_Parser): - """ - The parser for parsing a Cell input. + """The parser for parsing a Cell input. - :returns: a syntax tree of the cell. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + a syntax tree of the cell. """ debugfile = None diff --git a/montepy/input_parser/data_parser.py b/montepy/input_parser/data_parser.py index 25fa98f7..cdbb0be3 100644 --- a/montepy/input_parser/data_parser.py +++ b/montepy/input_parser/data_parser.py @@ -6,11 +6,12 @@ class DataParser(MCNP_Parser): - """ - A parser for almost all data inputs. + """A parser for almost all data inputs. - :returns: a syntax tree for the data input. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + a syntax tree for the data input. """ debugfile = None @@ -35,9 +36,9 @@ def data_input(self, p): @_( "classifier_phrase", - "classifier_phrase KEYWORD padding", + "classifier_phrase MODIFIER padding", "padding classifier_phrase", - "padding classifier_phrase KEYWORD padding", + "padding classifier_phrase MODIFIER padding", ) def introduction(self, p): ret = {} @@ -46,8 +47,8 @@ def introduction(self, p): else: ret["start_pad"] = syntax_node.PaddingNode() ret["classifier"] = p.classifier_phrase - if hasattr(p, "KEYWORD"): - ret["keyword"] = syntax_node.ValueNode(p.KEYWORD, str, padding=p[-1]) + if hasattr(p, "MODIFIER"): + ret["keyword"] = syntax_node.ValueNode(p.MODIFIER, str, padding=p[-1]) else: ret["keyword"] = syntax_node.ValueNode(None, str, padding=None) return syntax_node.SyntaxNode("data intro", ret) @@ -142,11 +143,12 @@ def parameter(self, p): class ClassifierParser(DataParser): - """ - A parser for parsing the first word or classifier of a data input. + """A parser for parsing the first word or classifier of a data input. - :returns: the classifier of the data input. - :rtype: ClassifierNode + Returns + ------- + ClassifierNode + the classifier of the data input. """ debugfile = None @@ -163,20 +165,22 @@ def data_classifier(self, p): class ParamOnlyDataParser(DataParser): - """ - A parser for parsing parameter (key-value pair) only data inputs. + """A parser for parsing parameter (key-value pair) only data inputs. .e.g., SDEF .. versionadded:: 0.3.0 - :returns: a syntax tree for the data input. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + a syntax tree for the data input. """ debugfile = None @_( + "param_introduction", "param_introduction spec_parameters", ) def param_data_input(self, p): @@ -203,11 +207,12 @@ def param_introduction(self, p): @_("spec_parameter", "spec_parameters spec_parameter") def spec_parameters(self, p): - """ - A list of the parameters (key, value pairs) for this input. + """A list of the parameters (key, value pairs) for this input. - :returns: all parameters - :rtype: ParametersNode + Returns + ------- + ParametersNode + all parameters """ if len(p) == 1: params = syntax_node.ParametersNode() @@ -242,13 +247,14 @@ def spec_data_prefix(self, p): "spec_classifier particle_type", ) def spec_classifier(self, p): - """ - The classifier of a data input. + """The classifier of a data input. This represents the first word of the data input. E.g.: ``M4``, `IMP:N`, ``F104:p`` - :rtype: ClassifierNode + Returns + ------- + ClassifierNode """ if hasattr(p, "spec_classifier"): classifier = p.spec_classifier diff --git a/montepy/input_parser/input_file.py b/montepy/input_parser/input_file.py index a4c63b0b..6cf95af6 100644 --- a/montepy/input_parser/input_file.py +++ b/montepy/input_parser/input_file.py @@ -6,8 +6,7 @@ class MCNP_InputFile: - """ - A class to represent a distinct input file. + """A class to represent a distinct input file. .. Note:: this is a bare bones implementation to be fleshed out in the future. @@ -15,12 +14,15 @@ class MCNP_InputFile: .. versionchanged:: 0.3.0 Added the overwrite attribute. - :param path: the path to the input file - :type path: str - :param parent_file: the parent file for this file if any. This occurs when a "read" input is used. - :type parent_file: str - :param overwrite: Whether to overwrite the file 'path' if it exists - :type overwrite: bool + Parameters + ---------- + path : str + the path to the input file + parent_file : str + the parent file for this file if any. This occurs when a "read" + input is used. + overwrite : bool + Whether to overwrite the file 'path' if it exists """ def __init__(self, path, parent_file=None, overwrite=False): @@ -35,11 +37,12 @@ def __init__(self, path, parent_file=None, overwrite=False): @classmethod def from_open_stream(cls, fh): - """ - Create an MCNP Input File from an open, writable stream + """Create an MCNP Input File from an open, writable stream - :param fh: An open and writable object, such as a file handle. - :type fh: io.TextIOBase + Parameters + ---------- + fh : io.TextIOBase + An open and writable object, such as a file handle. """ name = getattr(fh, "name", fh.__class__.__name__) inpfile = cls(path=name) @@ -49,10 +52,11 @@ def from_open_stream(cls, fh): @make_prop_pointer("_path") def path(self): - """ - The path for the file. + """The path for the file. - :rtype: str + Returns + ------- + str """ pass @@ -66,35 +70,35 @@ def is_stream(self): @make_prop_pointer("_parent_file") def parent_file(self): - """ - The parent file for this file. + """The parent file for this file. This is only used when this file is pointed to by a "read" input. - :rtype: str + Returns + ------- + str """ pass @make_prop_pointer("_lineno") def lineno(self): - """ - The current line number being read in the file. + """The current line number being read in the file. This is 1-indexed. - :rtype: int + Returns + ------- + int """ pass def open(self, mode, encoding="ascii", replace=True): - """ - Opens the underlying file, and returns self. + """Opens the underlying file, and returns self. This should only ever be completed from within a ``with`` statement. For this reason, a ``close`` functional is intentionally not provided. - .. Note:: For different encoding schemes see the available list `here `_. @@ -105,15 +109,27 @@ def open(self, mode, encoding="ascii", replace=True): .. versionchanged:: 0.2.11 Added guardrails to raise FileExistsError and IsADirectoryError. - :param mode: the mode to open the file in - :type mode: str - :param encoding: The encoding scheme to use. If replace is true, this is ignored, and changed to ASCII - :type encoding: str - :param replace: replace all non-ASCII characters with a space (0x20) - :type replace: bool - :returns: self - :raises FileExistsError: if a file already exists with the same path while writing. - :raises IsADirectoryError: if the path given is actually a directory while writing. + Parameters + ---------- + mode : str + the mode to open the file in + encoding : str + The encoding scheme to use. If replace is true, this is + ignored, and changed to ASCII + replace : bool + replace all non-ASCII characters with a space (0x20) + + Returns + ------- + unknown + self + + Raises + ------ + FileExistsError + if a file already exists with the same path while writing. + IsADirectoryError + if the path given is actually a directory while writing. """ if "r" in mode: if replace: @@ -157,7 +173,7 @@ def _clean_line(line): return line def read(self, size=-1): - """ """ + """""" if self._fh: ret = self._fh.read(size) if self._mode == "rb" and self._replace_with_space: @@ -166,7 +182,7 @@ def read(self, size=-1): return ret def readline(self, size=-1): - """ """ + """""" if self._fh: ret = self._fh.readline(size) if self._mode == "rb" and self._replace_with_space: diff --git a/montepy/input_parser/input_reader.py b/montepy/input_parser/input_reader.py index 3cc438af..199f3f32 100644 --- a/montepy/input_parser/input_reader.py +++ b/montepy/input_parser/input_reader.py @@ -4,27 +4,41 @@ def read_input(destination, mcnp_version=DEFAULT_VERSION, replace=True): - """ - Reads the specified MCNP Input file. + """Reads the specified MCNP Input file. The MCNP version must be a three component tuple e.g., (6, 2, 0) and (5, 1, 60). - .. note:: - if a stream is provided. It will not be closed by this function. + Notes + ----- + if a stream is provided. It will not be closed by this function. + + Parameters + ---------- + destination : io.TextIOBase, str, os.PathLike + the path to the input file to read, or a readable stream. + mcnp_version : tuple + The version of MCNP that the input is intended for. + replace : bool + replace all non-ASCII characters with a space (0x20) + + Returns + ------- + MCNP_Problem + The MCNP_Problem instance representing this file. - :param destination: the path to the input file to read, or a readable stream. - :type destination: io.TextIOBase, str, os.PathLike - :param mcnp_version: The version of MCNP that the input is intended for. - :type mcnp_version: tuple - :returns: The MCNP_Problem instance representing this file. - :param replace: replace all non-ASCII characters with a space (0x20) - :type replace: bool - :rtype: MCNP_Problem - :raises UnsupportedFeature: If an input format is used that MontePy does not support. - :raises MalformedInputError: If an input has a broken syntax. - :raises NumberConflictError: If two objects use the same number in the input file. - :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. + Raises + ------ + UnsupportedFeature + If an input format is used that MontePy does not support. + MalformedInputError + If an input has a broken syntax. + NumberConflictError + If two objects use the same number in the input file. + BrokenObjectLinkError + If a reference is made to an object that is not in the input + file. + UnknownElement + If an isotope is specified for an unknown element. """ problem = montepy.mcnp_problem.MCNP_Problem(destination) problem.mcnp_version = mcnp_version diff --git a/montepy/input_parser/input_syntax_reader.py b/montepy/input_parser/input_syntax_reader.py index 086ca917..61c356f9 100644 --- a/montepy/input_parser/input_syntax_reader.py +++ b/montepy/input_parser/input_syntax_reader.py @@ -1,24 +1,25 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. -from .block_type import BlockType +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from collections import deque -from .. import errors import itertools import io +import re +import os +import warnings + from montepy.constants import * from montepy.errors import * +from montepy.input_parser.block_type import BlockType from montepy.input_parser.input_file import MCNP_InputFile from montepy.input_parser.mcnp_input import Input, Message, ReadInput, Title from montepy.input_parser.read_parser import ReadParser from montepy.utilities import is_comment -import os -import warnings + reading_queue = [] def read_input_syntax(input_file, mcnp_version=DEFAULT_VERSION, replace=True): - """ - Creates a generator function to return a new MCNP input for + """Creates a generator function to return a new MCNP input for every new one that is encountered. This is meant to just handle the MCNP input syntax, it does not @@ -26,15 +27,19 @@ def read_input_syntax(input_file, mcnp_version=DEFAULT_VERSION, replace=True): The version must be a three component tuple e.g., (6, 2, 0) and (5, 1, 60). - - :param input_file: the path to the input file to be read - :type input_file: MCNP_InputFile - :param mcnp_version: The version of MCNP that the input is intended for. - :type mcnp_version: tuple - :param replace: replace all non-ASCII characters with a space (0x20) - :type replace: bool - :returns: a generator of MCNP_Object objects - :rtype: generator + Parameters + ---------- + input_file : MCNP_InputFile + the path to the input file to be read + mcnp_version : tuple + The version of MCNP that the input is intended for. + replace : bool + replace all non-ASCII characters with a space (0x20) + + Returns + ------- + generator + a generator of MCNP_Object objects """ global reading_queue reading_queue = deque() @@ -48,24 +53,30 @@ def read_input_syntax(input_file, mcnp_version=DEFAULT_VERSION, replace=True): def read_front_matters(fh, mcnp_version): - """ - Reads the beginning of an MCNP file for all of the unusual data there. + """Reads the beginning of an MCNP file for all of the unusual data there. This is a generator function that will yield multiple :class:`MCNP_Input` instances. - .. warning:: - This function will move the file handle forward in state. - - .. warning:: - This function will not close the file handle. - - :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. - :type mcnp_version: tuple - - :return: an instance of the Title class, and possible an instance of a Message class - :rtype: MCNP_Object + Warnings + -------- + This function will move the file handle forward in state. + + Warnings + -------- + This function will not close the file handle. + + Parameters + ---------- + fh : MCNP_InputFile + The file handle of the input file. + mcnp_version : tuple + The version of MCNP that the input is intended for. + + Returns + ------- + MCNP_Object + an instance of the Title class, and possible an instance of a + Message class """ is_in_message_block = False found_title = False @@ -91,30 +102,37 @@ def read_front_matters(fh, mcnp_version): def read_data(fh, mcnp_version, block_type=None, recursion=False): - """ - Reads the bulk of an MCNP file for all of the MCNP data. + """Reads the bulk of an MCNP file for all of the MCNP data. This is a generator function that will yield multiple :class:`MCNP_Input` instances. - .. warning:: - This function will move the file handle forward in state. - - .. warning:: - This function will not close the file handle. - - :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. - :type mcnp_version: tuple - :param block_type: The type of block this file is in. This is only used with partial files read using the ReadInput. - :type block_type: BlockType - :param recursion: Whether or not this is being called recursively. If True this has been called - from read_data. This prevents the reading queue causing infinite recursion. - :type recursion: bool - - :return: MCNP_Input instances: Inputs that represent the data in the MCNP input. - :rtype: MCNP_Input - + Warnings + -------- + This function will move the file handle forward in state. + + Warnings + -------- + This function will not close the file handle. + + Parameters + ---------- + fh : MCNP_InputFile + The file handle of the input file. + mcnp_version : tuple + The version of MCNP that the input is intended for. + block_type : BlockType + The type of block this file is in. This is only used with + partial files read using the ReadInput. + recursion : bool + Whether or not this is being called recursively. If True this + has been called from read_data. This prevents the reading queue + causing infinite recursion. + + Returns + ------- + MCNP_Input + MCNP_Input instances: Inputs that represent the data in the MCNP + input. """ current_file = fh line_length = get_max_line_length(mcnp_version) @@ -174,15 +192,21 @@ def flush_input(): yield from flush_input() # die if it is a vertical syntax format if "#" in line[0:BLANK_SPACE_CONTINUE] and not line_is_comment: - raise errors.UnsupportedFeature("Vertical Input format is not allowed") + raise UnsupportedFeature("Vertical Input format is not allowed") # cut line down to allowed length old_line = line line = line[:line_length] if len(old_line) != len(line): - warnings.warn( - f"The line: {old_line} exceeded the allowed line length of: {line_length} for MCNP {mcnp_version}", - errors.LineOverRunWarning, - ) + if len(line.split("$")[0]) >= line_length and not COMMENT_FINDER.match( + line + ): + warnings.warn( + f"The line: {old_line} exceeded the allowed line length of: {line_length} for MCNP {mcnp_version}", + LineOverRunWarning, + ) + # if extra length is a comment keep it long + else: + line = old_line if line.endswith(" &\n"): continue_input = True else: diff --git a/montepy/input_parser/material_parser.py b/montepy/input_parser/material_parser.py index 82ce7d29..c55e2ec2 100644 --- a/montepy/input_parser/material_parser.py +++ b/montepy/input_parser/material_parser.py @@ -57,11 +57,12 @@ def mat_datum(self, p): "classifier param_seperator library", ) def mat_parameter(self, p): - """ - A singular Key-value pair that includes a material library. + """A singular Key-value pair that includes a material library. - :returns: the parameter. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + the parameter. """ return syntax_node.SyntaxNode( p.classifier.prefix.value, @@ -71,7 +72,5 @@ def mat_parameter(self, p): @_("NUMBER_WORD") @_("NUMBER_WORD padding") def library(self, p): - """ - A library name. - """ + """A library name.""" return self._flush_phrase(p, str) diff --git a/montepy/input_parser/mcnp_input.py b/montepy/input_parser/mcnp_input.py index f3380a1a..8f7ae18a 100644 --- a/montepy/input_parser/mcnp_input.py +++ b/montepy/input_parser/mcnp_input.py @@ -11,9 +11,7 @@ class Jump: - """ - Class to represent a default entry represented by a "jump". - + """Class to represent a default entry represented by a "jump". | I get up and nothing gets me down | You got it tough, I've seen the toughest around @@ -41,36 +39,40 @@ def __eq__(self, other): return type(self) == type(other) def lower(self): - """ - Hop. + """Hop. - :rtype: str + Returns + ------- + str """ return "j" def title(self): - """ - Skip. + """Skip. - :rtype: str + Returns + ------- + str """ return "Jump" def upper(self): - """ - Jump. + """Jump. - :rtype: str + Returns + ------- + str """ return "J" class ParsingNode(ABC): - """ - Object to represent a single coherent MCNP input, such as an input. + """Object to represent a single coherent MCNP input, such as an input. - :param input_lines: the lines read straight from the input file. - :type input_lines: list + Parameters + ---------- + input_lines : list + the lines read straight from the input file. """ def __init__(self, input_lines): @@ -85,7 +87,9 @@ def __init__(self, input_lines): def input_lines(self): """The lines of the input read straight from the input file - :rtype: list + Returns + ------- + list """ return self._input_lines @@ -95,36 +99,43 @@ def input_text(self): @abstractmethod def format_for_mcnp_input(self, mcnp_version): - """ - Creates a string representation of this input that can be + """Creates a string representation of this input 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 + Parameters + ---------- + mcnp_version : tuple + The tuple for the MCNP version that must be exported to. + + Returns + ------- + list + a list of strings for the lines that this input will occupy. """ pass class Input(ParsingNode): - """ - Represents a single MCNP "Input" e.g. a single cell definition. - - :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. - :type block_type: BlockType - :param input_file: the wrapper for the input file this is read from. - :type input_file: MCNP_InputFile - :param lineno: the line number this input started at. 1-indexed. - :type lineno: int + """Represents a single MCNP "Input" e.g. a single cell definition. + + Parameters + ---------- + input_lines : list + the lines read straight from the input file. + block_type : BlockType + An enum showing which of three MCNP blocks this was inside of. + input_file : MCNP_InputFile + the wrapper for the input file this is read from. + lineno : int + the line number this input started at. 1-indexed. """ SPECIAL_COMMENT_PREFIXES = ["fc", "sc"] """Prefixes for special comments like tally comments. - - :rtype: list + + Returns + ------- + list """ def __init__(self, input_lines, block_type, input_file=None, lineno=None): @@ -144,30 +155,33 @@ def __repr__(self): @property def block_type(self): - """ - Enum representing which block of the MCNP input this came from. + """Enum representing which block of the MCNP input this came from. - :rtype: BlockType + Returns + ------- + BlockType """ return self._block_type @make_prop_pointer("_input_file") def input_file(self): - """ - The file this input file was read from. + """The file this input file was read from. - :rtype: MCNP_InputFile + Returns + ------- + MCNP_InputFile """ pass @make_prop_pointer("_lineno") def line_number(self): - """ - The line number this input started on. + """The line number this input started on. This is 1-indexed. - :rtype: int + Returns + ------- + int """ pass @@ -175,8 +189,7 @@ def format_for_mcnp_input(self, mcnp_version): pass def tokenize(self): - """ - Tokenizes this input as a stream of Tokens. + """Tokenizes this input as a stream of Tokens. This is a generator of Tokens. This is context dependent based on :func:`block_type`. @@ -185,8 +198,10 @@ def tokenize(self): * In a surface block :class:`~montepy.input_parser.tokens.SurfaceLexer` is used. * In a data block :class:`~montepy.input_parser.tokens.DataLexer` is used. - :returns: a generator of tokens. - :rtype: Token + Returns + ------- + Token + a generator of tokens. """ if self.block_type == BlockType.CELL: lexer = CellLexer() @@ -216,27 +231,30 @@ def tokenize(self): @make_prop_pointer("_lexer") def lexer(self): - """ - The current lexer being used to parse this input. + """The current lexer being used to parse this input. If not currently tokenizing this will be None. - :rtype:MCNP_Lexer + + Returns + ------- + MCNP_Lexer """ pass class ReadInput(Input): - """ - A input for the read input that reads another input file - - :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. - :type block_type: BlockType - :param input_file: the wrapper for the input file this is read from. - :type input_file: MCNP_InputFile - :param lineno: the line number this input started at. 1-indexed. - :type lineno: int + """A input for the read input that reads another input file + + Parameters + ---------- + input_lines : list + the lines read straight from the input file. + block_type : BlockType + An enum showing which of three MCNP blocks this was inside of. + input_file : MCNP_InputFile + the wrapper for the input file this is read from. + lineno : int + the line number this input started at. 1-indexed. """ _parser = ReadParser() @@ -267,10 +285,11 @@ def is_read_input(input_lines): @property def file_name(self): - """ - The relative path to the filename specified in this read input. + """The relative path to the filename specified in this read input. - :rtype: str + Returns + ------- + str """ return self._parameters["file"]["data"].value @@ -284,15 +303,16 @@ def __repr__(self): class Message(ParsingNode): - """ - Object to represent an MCNP message. + """Object to represent an MCNP message. These are blocks at the beginning of an input that are printed in the output. - :param input_lines: the lines read straight from the input file. - :type input_lines: list - :param lines: the strings of each line in the message block - :type lines: list + Parameters + ---------- + input_lines : list + the lines read straight from the input file. + lines : list + the strings of each line in the message block """ def __init__(self, input_lines, lines): @@ -318,12 +338,13 @@ def __repr__(self): @property def lines(self): - """ - The lines of input for the message block. + """The lines of input for the message block. Each entry is a string of that line in the message block - :rtype: list + Returns + ------- + list """ return self._lines @@ -340,21 +361,24 @@ def format_for_mcnp_input(self, mcnp_version): class Title(ParsingNode): - """ - Object to represent the title for an MCNP problem - - :param input_lines: the lines read straight from the input file. - :type input_lines: list - :param title: The string for the title of the problem. - :type title: str + """Object to represent the title for an MCNP problem + + Parameters + ---------- + input_lines : list + the lines read straight from the input file. + title : str + The string for the title of the problem. """ def __init__(self, input_lines, title): """ - :param input_lines: the lines read straight from the input file. - :type input_lines: list - :param title: The string for the title of the problem. - :type title: str + Parameters + ---------- + input_lines : list + the lines read straight from the input file. + title : str + The string for the title of the problem. """ super().__init__(input_lines) if not isinstance(title, str): @@ -365,7 +389,9 @@ def __init__(self, input_lines, title): def title(self): """The string of the title set for this problem - :rtype: str + Returns + ------- + str """ return self._title diff --git a/montepy/input_parser/parser_base.py b/montepy/input_parser/parser_base.py index 471e2626..6df90505 100644 --- a/montepy/input_parser/parser_base.py +++ b/montepy/input_parser/parser_base.py @@ -8,9 +8,7 @@ class MetaBuilder(sly.yacc.ParserMeta): - """ - Custom MetaClass for allowing subclassing of MCNP_Parser. - + """Custom MetaClass for allowing subclassing of MCNP_Parser. Note: overloading functions is not allowed. """ @@ -52,9 +50,7 @@ def _flatten_rules(classname, basis, attributes): class SLY_Supressor: - """ - This is a fake logger meant to mostly make warnings dissapear. - """ + """This is a fake logger meant to mostly make warnings dissapear.""" def __init__(self): self._parse_fail_queue = [] @@ -71,28 +67,31 @@ def debug(self, msg, *args, **kwargs): critical = debug def parse_error(self, msg, token=None, lineno=0, index=0): - """ - Adds a SLY parsing error to the error queue for being dumped later. - - :param msg: The message to display. - :type msg: str - :param token: the token that caused the error if any. - :type token: Token - :param lineno: the current lineno of the error (from SLY not the file), if any. - :type lineno: int + """Adds a SLY parsing error to the error queue for being dumped later. + + Parameters + ---------- + msg : str + The message to display. + token : Token + the token that caused the error if any. + lineno : int + the current lineno of the error (from SLY not the file), if + any. """ self._parse_fail_queue.append( {"message": msg, "token": token, "line": lineno, "index": index} ) def clear_queue(self): - """ - Clears the error queue and returns all errors. + """Clears the error queue and returns all errors. Returns a list of dictionaries. The dictionary has the keys: "message", "token", "line. - :returns: A list of the errors since the queue was last cleared. - :rtype: list + Returns + ------- + list + A list of the errors since the queue was last cleared. """ ret = self._parse_fail_queue self._parse_fail_queue = [] @@ -103,9 +102,7 @@ def __len__(self): class MCNP_Parser(Parser, metaclass=MetaBuilder): - """ - Base class for all MCNP parsers that provides basics. - """ + """Base class for all MCNP parsers that provides basics.""" # Remove this if trying to see issues with parser log = SLY_Supressor() @@ -113,8 +110,7 @@ class MCNP_Parser(Parser, metaclass=MetaBuilder): debugfile = None def restart(self): - """ - Clears internal state information about the current parse. + """Clears internal state information about the current parse. Should be ran before a new object is parsed. """ @@ -122,17 +118,21 @@ def restart(self): super().restart() def parse(self, token_generator, input=None): - """ - Parses the token stream and returns a syntax tree. + """Parses the token stream and returns a syntax tree. If the parsing fails None will be returned. The error queue can be retrieved from ``parser.log.clear_queue()``. - :param token_generator: the token generator from ``lexer.tokenize``. - :type token_generator: generator - :param input: the input that is being lexed and parsed. - :type input: Input - :rtype: SyntaxNode + Parameters + ---------- + token_generator : generator + the token generator from ``lexer.tokenize``. + input : Input + the input that is being lexed and parsed. + + Returns + ------- + SyntaxNode """ self._input = input @@ -155,21 +155,23 @@ def gen_wrapper(): @_("NUMBER", "NUMBER padding") def number_phrase(self, p): - """ - A non-zero number with or without padding. + """A non-zero number with or without padding. - :returns: a float ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a float ValueNode """ return self._flush_phrase(p, float) @_("NUMBER", "NUMBER padding") def identifier_phrase(self, p): - """ - A non-zero number with or without padding converted to int. + """A non-zero number with or without padding converted to int. - :returns: an int ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + an int ValueNode """ return self._flush_phrase(p, int) @@ -180,10 +182,11 @@ def identifier_phrase(self, p): "number_sequence shortcut_phrase", ) def number_sequence(self, p): - """ - A list of numbers. + """A list of numbers. - :rtype: ListNode + Returns + ------- + ListNode """ if len(p) == 1: sequence = syntax_node.ListNode("number sequence") @@ -201,11 +204,12 @@ def number_sequence(self, p): @_("number_phrase", "null_phrase") def numerical_phrase(self, p): - """ - Any number, including 0, with its padding. + """Any number, including 0, with its padding. - :returns: a float ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a float ValueNode """ return p[0] @@ -226,11 +230,12 @@ def shortcut_start(self, p): "JUMP", ) def shortcut_sequence(self, p): - """ - A shortcut (repeat, multiply, interpolate, or jump). + """A shortcut (repeat, multiply, interpolate, or jump). - :returns: the parsed shortcut. - :rtype: ShortcutNode + Returns + ------- + ShortcutNode + the parsed shortcut. """ short_cut = syntax_node.ShortcutNode(p) if isinstance(p[0], syntax_node.ShortcutNode): @@ -242,11 +247,12 @@ def shortcut_sequence(self, p): @_("shortcut_sequence", "shortcut_sequence padding") def shortcut_phrase(self, p): - """ - A complete shortcut, which should be used, and not shortcut_sequence. + """A complete shortcut, which should be used, and not shortcut_sequence. - :returns: the parsed shortcut. - :rtype: ShortcutNode + Returns + ------- + ShortcutNode + the parsed shortcut. """ sequence = p.shortcut_sequence if len(p) == 2: @@ -255,38 +261,39 @@ def shortcut_phrase(self, p): @_("NULL", "NULL padding") def null_phrase(self, p): - """ - A zero number with or without its padding. + """A zero number with or without its padding. - :returns: a float ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a float ValueNode """ return self._flush_phrase(p, float) @_("NULL", "NULL padding") def null_ident_phrase(self, p): - """ - A zero number with or without its padding, for identification. + """A zero number with or without its padding, for identification. - :returns: an int ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + an int ValueNode """ return self._flush_phrase(p, int) @_("TEXT", "TEXT padding") def text_phrase(self, p): - """ - A string with or without its padding. + """A string with or without its padding. - :returns: a str ValueNode. - :rtype: ValueNode + Returns + ------- + ValueNode + a str ValueNode. """ return self._flush_phrase(p, str) def _flush_phrase(self, p, token_type): - """ - Creates a ValueNode. - """ + """Creates a ValueNode.""" if len(p) > 1: padding = p[1] else: @@ -295,11 +302,12 @@ def _flush_phrase(self, p, token_type): @_("SPACE", "DOLLAR_COMMENT", "COMMENT") def padding(self, p): - """ - Anything that is not semantically significant: white space, and comments. + """Anything that is not semantically significant: white space, and comments. - :returns: All sequential padding. - :rtype: PaddingNode + Returns + ------- + PaddingNode + All sequential padding. """ if hasattr(p, "DOLLAR_COMMENT") or hasattr(p, "COMMENT"): is_comment = True @@ -309,11 +317,12 @@ def padding(self, p): @_("padding SPACE", "padding DOLLAR_COMMENT", "padding COMMENT", 'padding "&"') def padding(self, p): - """ - Anything that is not semantically significant: white space, and comments. + """Anything that is not semantically significant: white space, and comments. - :returns: All sequential padding. - :rtype: PaddingNode + Returns + ------- + PaddingNode + All sequential padding. """ if hasattr(p, "DOLLAR_COMMENT") or hasattr(p, "COMMENT"): is_comment = True @@ -324,11 +333,12 @@ def padding(self, p): @_("parameter", "parameters parameter") def parameters(self, p): - """ - A list of the parameters (key, value pairs) for this input. + """A list of the parameters (key, value pairs) for this input. - :returns: all parameters - :rtype: ParametersNode + Returns + ------- + ParametersNode + all parameters """ if len(p) == 1: params = syntax_node.ParametersNode() @@ -344,11 +354,12 @@ def parameters(self, p): "classifier param_seperator text_phrase", ) def parameter(self, p): - """ - A singular Key-value pair. + """A singular Key-value pair. - :returns: the parameter. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + the parameter. """ return syntax_node.SyntaxNode( p.classifier.prefix.value, @@ -357,10 +368,11 @@ def parameter(self, p): @_("file_atom", "file_name file_atom") def file_name(self, p): - """ - A file name. + """A file name. - :rtype: str + Returns + ------- + str """ ret = p[0] if len(p) > 1: @@ -388,21 +400,23 @@ def file_atom(self, p): @_("file_name", "file_name padding") def file_phrase(self, p): - """ - A file name with or without its padding. + """A file name with or without its padding. - :returns: a str ValueNode. - :rtype: ValueNode + Returns + ------- + ValueNode + a str ValueNode. """ return self._flush_phrase(p, str) @_("padding", "equals_sign", "padding equals_sign") def param_seperator(self, p): - """ - The seperation between a key and value for a parameter. + """The seperation between a key and value for a parameter. - :returns: a str ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a str ValueNode """ padding = p[0] if len(p) > 1: @@ -411,11 +425,12 @@ def param_seperator(self, p): @_('"="', '"=" padding') def equals_sign(self, p): - """ - The seperation between a key and value for a parameter. + """The seperation between a key and value for a parameter. - :returns: a str ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a str ValueNode """ padding = syntax_node.PaddingNode(p[0]) if hasattr(p, "padding"): @@ -454,13 +469,14 @@ def data_prefix(self, p): "classifier particle_type", ) def classifier(self, p): - """ - The classifier of a data input. + """The classifier of a data input. This represents the first word of the data input. E.g.: ``M4``, `IMP:N`, ``F104:p`` - :rtype: ClassifierNode + Returns + ------- + ClassifierNode """ if hasattr(p, "classifier"): classifier = p.classifier @@ -483,10 +499,11 @@ def classifier(self, p): @_("classifier padding", "classifier") def classifier_phrase(self, p): - """ - A classifier with its padding. + """A classifier with its padding. - :rtype: ClassifierNode + Returns + ------- + ClassifierNode """ classifier = p.classifier if len(p) > 1: @@ -495,11 +512,12 @@ def classifier_phrase(self, p): @_('"*"', "PARTICLE_SPECIAL") def modifier(self, p): - """ - A character that modifies a classifier, e.g., ``*TR``. + """A character that modifies a classifier, e.g., ``*TR``. - :returns: the modifier - :rtype: str + Returns + ------- + str + the modifier """ if hasattr(p, "PARTICLE_SPECIAL"): if p.PARTICLE_SPECIAL == "*": @@ -507,13 +525,14 @@ def modifier(self, p): return p[0] def error(self, token): - """ - Default error handling. + """Default error handling. Puts the data into a queue that can be pulled out later for one final clear debug. - :param token: the token that broke the parsing rules. - :type token: Token + Parameters + ---------- + token : Token + the token that broke the parsing rules. """ if token: lineno = getattr(token, "lineno", 0) @@ -537,8 +556,7 @@ def error(self, token): self.log.parse_error("sly: Parse error in input. EOF\n") def _debug_parsing_error(self, token): # pragma: no cover - """ - A function that should be called from error when debugging a parsing error. + """A function that should be called from error when debugging a parsing error. Call this from the method error. Also you will need the relevant debugfile to be set and saving the parser tables to file. e.g., diff --git a/montepy/input_parser/read_parser.py b/montepy/input_parser/read_parser.py index a106f7f9..26ff1440 100644 --- a/montepy/input_parser/read_parser.py +++ b/montepy/input_parser/read_parser.py @@ -4,9 +4,7 @@ class ReadParser(MCNP_Parser): - """ - A parser for handling "read" inputs. - """ + """A parser for handling "read" inputs.""" debugfile = None dont_copy = {"parameter"} diff --git a/montepy/input_parser/shortcuts.py b/montepy/input_parser/shortcuts.py index f6aabe48..48026b29 100644 --- a/montepy/input_parser/shortcuts.py +++ b/montepy/input_parser/shortcuts.py @@ -3,27 +3,15 @@ class Shortcuts(Enum): - """ - Enumeration of the possible MCNP shortcuts. - """ + """Enumeration of the possible MCNP shortcuts.""" REPEAT = "r" - """ - A repeated entry shortcut. - """ + """A repeated entry shortcut.""" JUMP = "j" - """ - A jump entry, which counts as a default entry. - """ + """A jump entry, which counts as a default entry.""" INTERPOLATE = "i" - """ - A linear interpolation. - """ + """A linear interpolation.""" LOG_INTERPOLATE = "ilog" - """ - A logarithmic interpolation. - """ + """A logarithmic interpolation.""" MULTIPLY = "m" - """ - a multiplication of the previous entry. - """ + """a multiplication of the previous entry.""" diff --git a/montepy/input_parser/surface_parser.py b/montepy/input_parser/surface_parser.py index fd6f8414..da3ec38c 100644 --- a/montepy/input_parser/surface_parser.py +++ b/montepy/input_parser/surface_parser.py @@ -5,10 +5,11 @@ class SurfaceParser(MCNP_Parser): - """ - A parser for MCNP surfaces. + """A parser for MCNP surfaces. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode """ debugfile = None diff --git a/montepy/input_parser/syntax_node.py b/montepy/input_parser/syntax_node.py index bdae6f87..3b37781f 100644 --- a/montepy/input_parser/syntax_node.py +++ b/montepy/input_parser/syntax_node.py @@ -1,10 +1,13 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from abc import ABC, abstractmethod +import re +import warnings import collections import copy import itertools as it import enum import math +from numbers import Integral, Real from montepy import input_parser from montepy import constants @@ -14,19 +17,18 @@ from montepy.geometry_operators import Operator from montepy.particle import Particle from montepy.utilities import fortran_float -import re -import warnings class SyntaxNodeBase(ABC): - """ - A base class for all syntax nodes. + """A base class for all syntax nodes. A syntax node is any component of the syntax tree for a parsed input. - :param name: a name for labeling this node. - :type name: str + Parameters + ---------- + name : str + a name for labeling this node. """ def __init__(self, name): @@ -34,21 +36,23 @@ def __init__(self, name): self._nodes = [] def append(self, node): - """ - Append the node to this node. + """Append the node to this node. - :param node: node - :type node: SyntaxNodeBase, str, None + Parameters + ---------- + node : SyntaxNodeBase, str, None + node """ self._nodes.append(node) @property def nodes(self): - """ - The children nodes of this node. + """The children nodes of this node. - :returns: a list of the nodes. - :rtype: list + Returns + ------- + list + a list of the nodes. """ return self._nodes @@ -57,11 +61,12 @@ def __len__(self): @property def name(self): - """ - The name for the node. + """The name for the node. - :returns: the node's name. - :rtype: str + Returns + ------- + str + the node's name. """ return self._name @@ -73,31 +78,34 @@ def name(self, name): @abstractmethod def format(self): - """ - Generate a string representing the tree's current state. + """Generate a string representing the tree's current state. - :returns: the MCNP representation of the tree's current state. - :rtype: str + Returns + ------- + str + the MCNP representation of the tree's current state. """ pass @property @abstractmethod def comments(self): - """ - A generator of all comments contained in this tree. + """A generator of all comments contained in this tree. - :returns: the comments in the tree. - :rtype: Generator + Returns + ------- + Generator + the comments in the tree. """ pass def get_trailing_comment(self): - """ - Get the trailing ``c`` style comments if any. + """Get the trailing ``c`` style comments if any. - :returns: The trailing comments of this tree. - :rtype: list + Returns + ------- + list + The trailing comments of this tree. """ if len(self.nodes) == 0: return @@ -106,9 +114,7 @@ def get_trailing_comment(self): return tail.get_trailing_comment() def _delete_trailing_comment(self): - """ - Deletes the trailing comment if any. - """ + """Deletes the trailing comment if any.""" if len(self.nodes) == 0: return tail = self.nodes[-1] @@ -116,11 +122,12 @@ def _delete_trailing_comment(self): tail._delete_trailing_comment() def _grab_beginning_comment(self, extra_padding): - """ - Consumes the provided comment, and moves it to the beginning of this node. + """Consumes the provided comment, and moves it to the beginning of this node. - :param extra_padding: the padding comment to add to the beginning of this padding. - :type extra_padding: list + Parameters + ---------- + extra_padding : list + the padding comment to add to the beginning of this padding. """ if len(self.nodes) == 0 or extra_padding is None: return @@ -129,8 +136,7 @@ def _grab_beginning_comment(self, extra_padding): head._grab_beginning_comment(extra_padding) def check_for_graveyard_comments(self, has_following_input=False): - """ - Checks if there is a graveyard comment that is preventing information from being part of the tree, and handles + """Checks if there is a graveyard comment that is preventing information from being part of the tree, and handles them. A graveyard comment is one that accidentally suppresses important information in the syntax tree. @@ -149,9 +155,15 @@ def check_for_graveyard_comments(self, has_following_input=False): .. versionadded:: 0.4.0 - :param has_following_input: Whether there is another input (cell modifier) after this tree that should be continued. - :type has_following_input: bool - :rtype: None + Parameters + ---------- + has_following_input : bool + Whether there is another input (cell modifier) after this + tree that should be continued. + + Returns + ------- + None """ flatpack = self.flatten() if len(flatpack) == 0: @@ -175,13 +187,14 @@ def check_for_graveyard_comments(self, has_following_input=False): first = second def flatten(self): - """ - Flattens this tree structure into a list of leaves. + """Flattens this tree structure into a list of leaves. .. versionadded:: 0.4.0 - :returns: a list of ValueNode and PaddingNode objects from this tree. - :rtype: list + Returns + ------- + list + a list of ValueNode and PaddingNode objects from this tree. """ ret = [] for node in self.nodes: @@ -208,8 +221,7 @@ def _pretty_str(self): class SyntaxNode(SyntaxNodeBase): - """ - A general syntax node for handling inner tree nodes. + """A general syntax node for handling inner tree nodes. This is a generalized wrapper for a dictionary. The order of the dictionary is significant. @@ -222,10 +234,12 @@ class SyntaxNode(SyntaxNodeBase): if key in syntax_node: pass - :param name: a name for labeling this node. - :type name: str - :param parse_dict: the dictionary of the syntax tree nodes. - :type parse_dict: dict + Parameters + ---------- + name : str + a name for labeling this node. + parse_dict : dict + the dictionary of the syntax tree nodes. """ def __init__(self, name, parse_dict): @@ -240,14 +254,22 @@ def __contains__(self, key): return key in self.nodes def get_value(self, key): - """ - Get a value from the syntax tree. + """Get a value from the syntax tree. + + Parameters + ---------- + key : str + the key for the item to get. + + Returns + ------- + SyntaxNodeBase + the node in the syntax tree. - :param key: the key for the item to get. - :type key: str - :returns: the node in the syntax tree. - :rtype: SyntaxNodeBase - :raises KeyError: if key is not in SyntaxNode + Raises + ------ + KeyError + if key is not in SyntaxNode """ temp = self.nodes[key] if isinstance(temp, ValueNode): @@ -282,11 +304,12 @@ def get_trailing_comment(self): return node.get_trailing_comment() def _grab_beginning_comment(self, extra_padding): - """ - Consumes the provided comment, and moves it to the beginning of this node. + """Consumes the provided comment, and moves it to the beginning of this node. - :param extra_padding: the padding comment to add to the beginning of this padding. - :type extra_padding: list + Parameters + ---------- + extra_padding : list + the padding comment to add to the beginning of this padding. """ if len(self.nodes) == 0 or extra_padding is None: return @@ -332,26 +355,27 @@ def _pretty_str(self): class GeometryTree(SyntaxNodeBase): - """ - A syntax tree that is a binary tree for representing CSG geometry logic. + """A syntax tree that is a binary tree for representing CSG geometry logic. .. versionchanged:: 0.4.1 Added left/right_short_type - :param name: a name for labeling this node. - :type name: str - :param tokens: The nodes that are in the tree. - :type tokens: dict - :param op: The string representation of the Operator to use. - :type op: str - :param left: the node of the left side of the binary tree. - :type left: GeometryTree, ValueNode - :param right: the node of the right side of the binary tree. - :type right: GeometryTree, ValueNode - :param left_short_type: The type of Shortcut that right left leaf is involved in. - :type left_short_type: Shortcuts - :param right_short_type: The type of Shortcut that the right leaf is involved in. - :type right_short_type: Shortcuts + Parameters + ---------- + name : str + a name for labeling this node. + tokens : dict + The nodes that are in the tree. + op : str + The string representation of the Operator to use. + left : GeometryTree, ValueNode + the node of the left side of the binary tree. + right : GeometryTree, ValueNode + the node of the right side of the binary tree. + left_short_type : Shortcuts + The type of Shortcut that right left leaf is involved in. + right_short_type : Shortcuts + The type of Shortcut that the right leaf is involved in. """ def __init__( @@ -414,11 +438,12 @@ def format(self): return ret def mark_last_leaf_shortcut(self, short_type): - """ - Mark the final (rightmost) leaf node in this tree as being a shortcut. + """Mark the final (rightmost) leaf node in this tree as being a shortcut. - :param short_type: the type of shortcut that this leaf is. - :type short_type: Shortcuts + Parameters + ---------- + short_type : Shortcuts + the type of shortcut that this leaf is. """ if self.right is not None: node = self.right @@ -436,12 +461,13 @@ def mark_last_leaf_shortcut(self, short_type): self._left_short_type = short_type def _flatten_shortcut(self): - """ - Flattens this tree into a ListNode. + """Flattens this tree into a ListNode. This will add ShortcutNodes as well. - :rtype: ListNode + Returns + ------- + ListNode """ def add_leaf(list_node, leaf, short_type): @@ -495,9 +521,7 @@ def start_shortcut(): return ret def _format_shortcut(self): - """ - Handles formatting a subset of tree that has shortcuts in it. - """ + """Handles formatting a subset of tree that has shortcuts in it.""" list_wrap = self._flatten_shortcut() if isinstance(list_wrap.nodes[-1], ShortcutNode): list_wrap.nodes[-1].load_nodes(list_wrap.nodes[-1].nodes) @@ -510,38 +534,39 @@ def comments(self): @property def left(self): - """ - The left side of the binary tree. + """The left side of the binary tree. - :returns: the left node of the syntax tree. - :rtype: GeometryTree, ValueNode + Returns + ------- + GeometryTree, ValueNode + the left node of the syntax tree. """ return self._left_side @property def right(self): - """ - The right side of the binary tree. + """The right side of the binary tree. - :returns: the right node of the syntax tree. - :rtype: GeometryTree, ValueNode + Returns + ------- + GeometryTree, ValueNode + the right node of the syntax tree. """ return self._right_side @property def operator(self): - """ - The operator used for the binary tree. + """The operator used for the binary tree. - :returns: the operator used. - :rtype: Operator + Returns + ------- + Operator + the operator used. """ return self._operator def __iter__(self): - """ - Iterates over the leafs - """ + """Iterates over the leafs""" self._iter_l_r = False self._iter_complete = False self._sub_iter = None @@ -589,13 +614,14 @@ def flatten(self): class PaddingNode(SyntaxNodeBase): - """ - A syntax tree node to represent a collection of sequential padding elements. - - :param token: The first padding token for this node. - :type token: str - :param is_comment: If the token provided is a comment. - :type is_comment: bool + """A syntax tree node to represent a collection of sequential padding elements. + + Parameters + ---------- + token : str + The first padding token for this node. + is_comment : bool + If the token provided is a comment. """ def __init__(self, token=None, is_comment=False): @@ -617,27 +643,39 @@ def __iadd__(self, other): @property def value(self): - """ - A string representation of the contents of this node. + """A string representation of the contents of this node. All of the padding will be combined into a single string. - :returns: a string sequence of the padding. - :rtype: str + Returns + ------- + str + a string sequence of the padding. """ return "".join([val.format() for val in self.nodes]) def is_space(self, i): - """ - Determine if the value at i is a space or not. + """Determine if the value at i is a space or not. + + Notes + ----- + the newline, ``\\n``, by itself is not considered a space. + + Parameters + ---------- + i : int + the index of the element to check. - .. note:: - the newline, ``\\n``, by itself is not considered a space. + Returns + ------- + unknown + true iff the padding at that node is only spaces that are + not ``\\n``. - :param i: the index of the element to check. - :type i: int - :returns: true iff the padding at that node is only spaces that are not ``\\n``. - :raises IndexError: if the index i is not in ``self.nodes``. + Raises + ------ + IndexError + if the index i is not in ``self.nodes``. """ val = self.nodes[i] if not isinstance(val, str): @@ -645,22 +683,25 @@ def is_space(self, i): return len(val.strip()) == 0 and val != "\n" def has_space(self): - """ - Determines if there is syntactically significant space anywhere in this node. + """Determines if there is syntactically significant space anywhere in this node. - :returns: True if there is syntactically significant (not in a comment) space. - :rtype: bool + Returns + ------- + bool + True if there is syntactically significant (not in a + comment) space. """ return any([self.is_space(i) for i in range(len(self))]) def append(self, val, is_comment=False): - """ - Append the node to this node. + """Append the node to this node. - :param node: node - :type node: str, CommentNode - :param is_comment: whether or not the node is a comment. - :type is_comment: bool + Parameters + ---------- + node : str, CommentNode + node + is_comment : bool + whether or not the node is a comment. """ if is_comment and not isinstance(val, CommentNode): val = CommentNode(val) @@ -695,11 +736,13 @@ def comments(self): yield node def _get_first_comment(self): - """ - Get the first index that is a ``c`` style comment. + """Get the first index that is a ``c`` style comment. - :returns: the index of the first comment, if there is no comment then None. - :rtype: int, None + Returns + ------- + int, None + the index of the first comment, if there is no comment then + None. """ for i, item in enumerate(self.nodes): if isinstance(item, CommentNode) and not item.is_dollar: @@ -718,11 +761,12 @@ def _delete_trailing_comment(self): del self._nodes[i:] def _grab_beginning_comment(self, extra_padding): - """ - Consumes the provided comment, and moves it to the beginning of this node. + """Consumes the provided comment, and moves it to the beginning of this node. - :param extra_padding: the padding comment to add to the beginning of this padding. - :type extra_padding: list + Parameters + ---------- + extra_padding : list + the padding comment to add to the beginning of this padding. """ if extra_padding[-1] != "\n": extra_padding.append("\n") @@ -736,8 +780,7 @@ def __eq__(self, other): return self.format() == other def has_graveyard_comment(self): - """ - Checks if there is a graveyard comment that is preventing information from being part of the tree. + """Checks if there is a graveyard comment that is preventing information from being part of the tree. A graveyard comment is one that accidentally suppresses important information in the syntax tree. @@ -752,8 +795,10 @@ def has_graveyard_comment(self): .. versionadded:: 0.4.0 - :returns: True if this PaddingNode contains a graveyard comment. - :rtype: bool + Returns + ------- + bool + True if this PaddingNode contains a graveyard comment. """ found = False for i, item in reversed(list(enumerate(self.nodes))): @@ -774,11 +819,12 @@ def has_graveyard_comment(self): class CommentNode(SyntaxNodeBase): - """ - Object to represent a comment in an MCNP problem. + """Object to represent a comment in an MCNP problem. - :param input: the token from the lexer - :type input: Token + Parameters + ---------- + input : Token + the token from the lexer """ _MATCHER = re.compile( @@ -789,9 +835,7 @@ class CommentNode(SyntaxNodeBase): (?P.*)""", re.I | re.VERBOSE, ) - """ - A re matcher to confirm this is a C style comment. - """ + """A re matcher to confirm this is a C style comment.""" def __init__(self, input): super().__init__("comment") @@ -800,13 +844,17 @@ def __init__(self, input): self._nodes = [node] def _convert_to_node(self, token): - """ - Converts the token to a Syntax Node to store. + """Converts the token to a Syntax Node to store. + + Parameters + ---------- + token : str + the token to convert. - :param token: the token to convert. - :type token: str - :returns: the SyntaxNode of the Comment. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + the SyntaxNode of the Comment. """ if match := self._MATCHER.match(token): start = match["delim"] @@ -828,11 +876,12 @@ def _convert_to_node(self, token): ) def append(self, token): - """ - Append the comment token to this node. + """Append the comment token to this node. - :param token: the comment token - :type token: str + Parameters + ---------- + token : str + the comment token """ is_dollar, node = self._convert_to_node(token) if is_dollar or self._is_dollar: @@ -843,21 +892,23 @@ def append(self, token): @property def is_dollar(self): - """ - Whether or not this CommentNode is a dollar sign ($) comment. + """Whether or not this CommentNode is a dollar sign ($) comment. - :returns: True iff this is a dollar sign comment. - :rtype: bool + Returns + ------- + bool + True iff this is a dollar sign comment. """ return self._is_dollar @property def contents(self): - """ - The contents of the comments without delimiters (i.e., $/C). + """The contents of the comments without delimiters (i.e., $/C). - :returns: String of the contents - :rtype: str + Returns + ------- + str + String of the contents """ return "\n".join([node["data"].value for node in self.nodes]) @@ -888,20 +939,21 @@ def __eq__(self, other): class ValueNode(SyntaxNodeBase): - """ - A syntax node to represent the leaf node. + """A syntax node to represent the leaf node. This stores the original input token, the current value, and the possible associated padding. - :param token: the original token for the ValueNode. - :type token: str - :param token_type: the type for the ValueNode. - :type token_type: class - :param padding: the padding for this node. - :type padding: PaddingNode - :param never_pad: If true an ending space will never be added to this. - :type never_pad: bool + Parameters + ---------- + token : str + the original token for the ValueNode. + token_type : class + the type for the ValueNode. + padding : PaddingNode + the padding for this node. + never_pad : bool + If true an ending space will never be added to this. """ _FORMATTERS = { @@ -920,9 +972,7 @@ class ValueNode(SyntaxNodeBase): int: {"value_length": 0, "zero_padding": 0, "sign": "-"}, str: {"value_length": 0}, } - """ - The default formatters for each type. - """ + """The default formatters for each type.""" _SCIENTIFIC_FINDER = re.compile( r""" @@ -935,9 +985,7 @@ class ValueNode(SyntaxNodeBase): """, re.VERBOSE, ) - """ - A regex for finding scientific notation. - """ + """A regex for finding scientific notation.""" def __init__(self, token, token_type, padding=None, never_pad=False): super().__init__("") @@ -964,9 +1012,7 @@ def __init__(self, token, token_type, padding=None, never_pad=False): self._is_reversed = False def _convert_to_int(self): - """ - Converts a float ValueNode to an int ValueNode. - """ + """Converts a float ValueNode to an int ValueNode.""" if self._type not in {float, int}: raise ValueError(f"ValueNode must be a float to convert to int") self._type = int @@ -986,17 +1032,19 @@ def _convert_to_int(self): def _convert_to_enum( self, enum_class, allow_none=False, format_type=str, switch_to_upper=False ): - """ - Converts the ValueNode to an Enum for allowed values. - - :param enum_class: the class for the enum to use. - :type enum_class: Class - :param allow_none: Whether or not to allow None as a value. - :type allow_none: bool - :param format_type: the base data type to format this ValueNode as. - :type format_type: Class - :param switch_to_upper: Whether or not to convert a string to upper case before convert to enum. - :type switch_to_upper: bool + """Converts the ValueNode to an Enum for allowed values. + + Parameters + ---------- + enum_class : Class + the class for the enum to use. + allow_none : bool + Whether or not to allow None as a value. + format_type : Class + the base data type to format this ValueNode as. + switch_to_upper : bool + Whether or not to convert a string to upper case before + convert to enum. """ self._type = enum_class if switch_to_upper: @@ -1009,8 +1057,7 @@ def _convert_to_enum( @property def is_negatable_identifier(self): - """ - Whether or not this value is a negatable identifier. + """Whether or not this value is a negatable identifier. Example use: the surface transform or periodic surface is switched based on positive or negative. @@ -1020,8 +1067,10 @@ def is_negatable_identifier(self): 2. The ``value`` will always be positive. 3. The ``is_negative`` property will be available. - :returns: the state of this marker. - :rtype: bool + Returns + ------- + bool + the state of this marker. """ return self._is_neg_id @@ -1038,8 +1087,7 @@ def is_negatable_identifier(self, val): @property def is_negatable_float(self): - """ - Whether or not this value is a negatable float. + """Whether or not this value is a negatable float. Example use: cell density. @@ -1048,8 +1096,10 @@ def is_negatable_float(self): 2. The ``value`` will always be positive. 3. The ``is_negative`` property will be available. - :returns: the state of this marker. - :rtype: bool + Returns + ------- + bool + the state of this marker. """ return self._is_neg_val @@ -1065,14 +1115,16 @@ def is_negatable_float(self, val): @property def is_negative(self): - """ - Whether or not this value is negative. + """Whether or not this value is negative. If neither :func:`is_negatable_float` or :func:`is_negatable_identifier` is true then this will return ``None``. - :returns: true if this value is negative (either in input or through state). - :rtype: bool, None + Returns + ------- + bool, None + true if this value is negative (either in input or through + state). """ if self.is_negatable_identifier or self.is_negatable_float: return self._is_neg @@ -1083,15 +1135,13 @@ def is_negative(self, val): self._is_neg = val def _reverse_engineer_formatting(self): - """ - Tries its best to figure out and update the formatter based on the token's format. - """ + """Tries its best to figure out and update the formatter based on the token's format.""" if not self._is_reversed and self._token is not None: self._is_reversed = True token = self._token if isinstance(token, input_parser.mcnp_input.Jump): token = "J" - if isinstance(token, (int, float)): + if isinstance(token, (Integral, Real)): token = str(token) self._formatter["value_length"] = len(token) if self.padding: @@ -1115,7 +1165,7 @@ def _reverse_engineer_formatting(self): def _reverse_engineer_float(self): token = self._token - if isinstance(token, float): + if isinstance(token, Real): token = str(token) if isinstance(token, input_parser.mcnp_input.Jump): token = "J" @@ -1144,14 +1194,15 @@ def _reverse_engineer_float(self): self._formatter["precision"] = precision def _can_float_to_int_happen(self): - """ - Checks if you can format a floating point as an int. + """Checks if you can format a floating point as an int. E.g., 1.0 -> 1 Considers if this was done in the input, and if the value is close to the int value. - :rtype: bool. + Returns + ------- + bool. """ if self._type != float or not self._formatter["as_int"]: return False @@ -1162,13 +1213,14 @@ def _can_float_to_int_happen(self): @property def _print_value(self): - """ - The print version of the value. + """The print version of the value. This takes a float/int that is negatable, and negates it based on the ``is_negative`` value. - :rtype: int, float + Returns + ------- + int, float """ if self._type in {int, float} and self.is_negative: return -self.value @@ -1176,12 +1228,13 @@ def _print_value(self): @property def _value_changed(self): - """ - Checks if the value has changed at all from first parsing. + """Checks if the value has changed at all from first parsing. Used to shortcut formatting and reverse engineering. - :rtype: bool + Returns + ------- + bool """ if self.value is None and self._og_value is None: return False @@ -1292,11 +1345,12 @@ def _delete_trailing_comment(self): @property def padding(self): - """ - The padding if any for this ValueNode. + """The padding if any for this ValueNode. - :returns: the padding if any. - :rtype: PaddingNode + Returns + ------- + PaddingNode + the padding if any. """ return self._padding @@ -1306,23 +1360,25 @@ def padding(self, pad): @property def type(self): - """ - The data type for this ValueNode. + """The data type for this ValueNode. Examples: float, int, str, Lattice - :returns: the class for the value of this node. - :rtype: Class + Returns + ------- + Class + the class for the value of this node. """ return self._type @property def token(self): - """ - The original text (token) for this ValueNode. + """The original text (token) for this ValueNode. - :returns: the original input. - :rtype: str + Returns + ------- + str + the original input. """ return self._token @@ -1337,25 +1393,27 @@ def __repr__(self): @property def value(self): - """ - The current semantic value of this ValueNode. + """The current semantic value of this ValueNode. This is the parsed meaning in the type of ``self.type``, that can be updated. When this value is updated, next time format() is ran this value will be used. - :returns: the node's value in type ``type``. - :rtype: float, int, str, enum + Returns + ------- + float, int, str, enum + the node's value in type ``type``. """ return self._value @property def never_pad(self): - """ - Whether or not this value node will not have extra spaces added. + """Whether or not this value node will not have extra spaces added. - :returns: true if extra padding is not adding at the end if missing. - :rtype: bool + Returns + ------- + bool + true if extra padding is not adding at the end if missing. """ return self._never_pad @@ -1378,7 +1436,7 @@ def _check_if_needs_end_padding(self, value): self.padding = PaddingNode(" ") def __eq__(self, other): - if not isinstance(other, (type(self), str, int, float)): + if not isinstance(other, (type(self), str, Real)): return False if isinstance(other, ValueNode): other_val = other.value @@ -1395,13 +1453,14 @@ def __eq__(self, other): class ParticleNode(SyntaxNodeBase): - """ - A node to hold particles information in a :class:`ClassifierNode`. - - :param name: the name for the node. - :type name: str - :param token: the original token from parsing - :type token: str + """A node to hold particles information in a :class:`ClassifierNode`. + + Parameters + ---------- + name : str + the name for the node. + token : str + the original token from parsing """ _letter_finder = re.compile(r"([a-zA-Z])") @@ -1421,21 +1480,23 @@ def __init__(self, name, token): @property def token(self): - """ - The original text (token) for this ParticleNode. + """The original text (token) for this ParticleNode. - :returns: the original input. - :rtype: str + Returns + ------- + str + the original input. """ return self._token @property def particles(self): - """ - The particles included in this node. + """The particles included in this node. - :returns: a set of the particles being used. - :rtype: set + Returns + ------- + set + a set of the particles being used. """ return self._particles @@ -1452,11 +1513,12 @@ def particles(self, values): self._particles = values def add(self, value): - """ - Add a particle to this node. + """Add a particle to this node. - :param value: the particle to add. - :type value: Particle + Parameters + ---------- + value : Particle + the particle to add. """ if not isinstance(value, Particle): raise TypeError(f"All particles must be a Particle. {value} given") @@ -1464,11 +1526,12 @@ def add(self, value): self._particles.add(value) def remove(self, value): - """ - Remove a particle from this node. + """Remove a particle from this node. - :param value: the particle to remove. - :type value: Particle + Parameters + ---------- + value : Particle + the particle to remove. """ if not isinstance(value, Particle): raise TypeError(f"All particles must be a Particle. {value} given") @@ -1477,15 +1540,16 @@ def remove(self, value): @property def _particles_sorted(self): - """ - The particles in this node ordered in a nice-ish way. + """The particles in this node ordered in a nice-ish way. Ordering: 1. User input. 2. Order of particles appended 3. randomly at the end if all else fails. - :rtype: list + Returns + ------- + list """ ret = self._order ret_set = set(ret) @@ -1528,11 +1592,12 @@ def __iter__(self): class ListNode(SyntaxNodeBase): - """ - A node to represent a list of values. + """A node to represent a list of values. - :param name: the name of this node. - :type name: str + Parameters + ---------- + name : str + the name of this node. """ def __init__(self, name): @@ -1543,16 +1608,17 @@ def __repr__(self): return f"(list: {self.name}, {self.nodes})" def update_with_new_values(self, new_vals): - """ - Update this list node with new values. + """Update this list node with new values. This will first try to find if any shortcuts in the original input match up with the new values. If so it will then "zip" out those shortcuts to consume as many neighbor nodes as possible. Finally, the internal shortcuts, and list will be updated to reflect the new state. - :param new_vals: the new values (a list of ValueNodes) - :type new_vals: list + Parameters + ---------- + new_vals : list + the new values (a list of ValueNodes) """ if not new_vals: self._nodes = [] @@ -1588,14 +1654,16 @@ def update_with_new_values(self, new_vals): self._shortcuts.pop() def _expand_shortcuts(self, new_vals, new_vals_cache): - """ - Expands the existing shortcuts, and tries to "zip out" and consume their neighbors. + """Expands the existing shortcuts, and tries to "zip out" and consume their neighbors. - :param new_vals: the new values. - :type new_vals: list - :param new_vals_cache: a dictionary mapping the id of the ValueNode to the ValueNode - or ShortcutNode. This is ordered the same as ``new_vals``. - :type new_vals_cache: dict + Parameters + ---------- + new_vals : list + the new values. + new_vals_cache : dict + a dictionary mapping the id of the ValueNode to the + ValueNode or ShortcutNode. This is ordered the same as + ``new_vals``. """ def try_expansion(shortcut, value): @@ -1618,9 +1686,7 @@ def try_reverse_expansion(shortcut, i, last_end): return def check_for_orphan_jump(value): - """ - Checks if the current Jump is not tied to an existing Shortcut - """ + """Checks if the current Jump is not tied to an existing Shortcut""" nonlocal shortcut if value.value is None and shortcut is None: shortcut = ShortcutNode(p=None, short_type=Shortcuts.JUMP) @@ -1651,13 +1717,14 @@ def check_for_orphan_jump(value): check_for_orphan_jump(new_vals[i]) def append(self, val, from_parsing=False): - """ - Append the node to this node. + """Append the node to this node. - :param node: node - :type node: ValueNode, ShortcutNode - :param from_parsing: If this is being append from the parsers, and not elsewhere. - :type from_parsing: bool + Parameters + ---------- + node : ValueNode, ShortcutNode + node + from_parsing : bool + If this is being append from the parsers, and not elsewhere. """ if isinstance(val, ShortcutNode): self._shortcuts.append(val) @@ -1721,9 +1788,7 @@ def __getitem__(self, indx): raise IndexError(f"{indx} not in ListNode") def __get_slice(self, i: slice): - """ - Helper function for __getitem__ with slices. - """ + """Helper function for __getitem__ with slices.""" rstep = i.step if i.step is not None else 1 rstart = i.start rstop = i.stop @@ -1752,11 +1817,12 @@ def __get_slice(self, i: slice): return ret def remove(self, obj): - """ - Removes the given object from this list. + """Removes the given object from this list. - :param obj: the object to remove. - :type obj: ValueNode + Parameters + ---------- + obj : ValueNode + the object to remove. """ self.nodes.remove(obj) @@ -1772,8 +1838,7 @@ def __eq__(self, other): class MaterialsNode(SyntaxNodeBase): - """ - A node for representing isotopes and their concentration, + """A node for representing isotopes and their concentration, and the material parameters. This stores a list of tuples of ZAIDs and concentrations, @@ -1783,25 +1848,29 @@ class MaterialsNode(SyntaxNodeBase): This was added as a more general version of ``IsotopesNodes``. - :param name: a name for labeling this node. - :type name: str + Parameters + ---------- + name : str + a name for labeling this node. """ def __init__(self, name): super().__init__(name) def append_nuclide(self, isotope_fraction): - """ - Append the isotope fraction to this node. + """Append the isotope fraction to this node. .. versionadded:: 1.0.0 Added to replace ``append`` - :param isotope_fraction: the isotope_fraction to add. This must be a tuple from - A Yacc production. This will consist of: the string identifying the Yacc production, - a ValueNode that is the ZAID, and a ValueNode of the concentration. - :type isotope_fraction: tuple + Parameters + ---------- + isotope_fraction : tuple + the isotope_fraction to add. This must be a tuple from A + Yacc production. This will consist of: the string + identifying the Yacc production, a ValueNode that is the + ZAID, and a ValueNode of the concentration. """ isotope, concentration = isotope_fraction[1:3] self._nodes.append((isotope, concentration)) @@ -1810,15 +1879,16 @@ def append(self): # pragma: no cover raise DeprecationWarning("Deprecated. Use append_param or append_nuclide") def append_param(self, param): - """ - Append the parameter to this node. + """Append the parameter to this node. .. versionadded:: 1.0.0 Added to replace ``append`` - :param param: the parameter to add to this node. - :type param: ParametersNode + Parameters + ---------- + param : ParametersNode + the parameter to add to this node. """ self._nodes.append((param,)) @@ -1869,15 +1939,16 @@ def flatten(self): class ShortcutNode(ListNode): - """ - A node that pretends to be a :class:`ListNode` but is actually representing a shortcut. + """A node that pretends to be a :class:`ListNode` but is actually representing a shortcut. This takes the shortcut tokens, and expands it into their "virtual" values. - :param p: the parsing object to parse. - :type p: sly.yacc.YaccProduction - :param short_type: the type of the shortcut. - :type short_type: Shortcuts + Parameters + ---------- + p : sly.yacc.YaccProduction + the parsing object to parse. + short_type : Shortcuts + the type of the shortcut. """ _shortcut_names = { @@ -1928,14 +1999,15 @@ def __init__(self, p=None, short_type=None, data_type=float): self._end_pad = PaddingNode(" ") def load_nodes(self, nodes): - """ - Loads the given nodes into this shortcut, and update needed information. + """Loads the given nodes into this shortcut, and update needed information. For interpolate nodes should start and end with the beginning/end of the interpolation. - :param nodes: the nodes to be loaded. - :type nodes: list + Parameters + ---------- + nodes : list + the nodes to be loaded. """ self._nodes = collections.deque(nodes) if self.type in {Shortcuts.INTERPOLATE, Shortcuts.LOG_INTERPOLATE}: @@ -1948,10 +2020,11 @@ def load_nodes(self, nodes): @property def end_padding(self): - """ - The padding at the end of this shortcut. + """The padding at the end of this shortcut. - :rtype: PaddingNode + Returns + ------- + PaddingNode """ return self._end_pad @@ -1965,10 +2038,11 @@ def end_padding(self, padding): @property def type(self): - """ - The Type of shortcut this ShortcutNode represents. + """The Type of shortcut this ShortcutNode represents. - :rtype: Shortcuts + Returns + ------- + Shortcuts """ return self._type @@ -2080,17 +2154,22 @@ def _expand_interpolate(self, p): self.append(p.number_phrase) def _can_consume_node(self, node, direction, last_edge_shortcut=False): - """ - If it's possible to consume this node. - - :param node: the node to consume - :type node: ValueNode - :param direction: the direct to go in. Must be in {-1, 1} - :type direction: int - :param last_edge_shortcut: Whether the previous node in the list was part of a different shortcut - :type last_edge_shortcut: bool - :returns: true it can be consumed. - :rtype: bool + """If it's possible to consume this node. + + Parameters + ---------- + node : ValueNode + the node to consume + direction : int + the direct to go in. Must be in {-1, 1} + last_edge_shortcut : bool + Whether the previous node in the list was part of a + different shortcut + + Returns + ------- + bool + true it can be consumed. """ if self._type == Shortcuts.JUMP: if node.value is None: @@ -2132,15 +2211,19 @@ def _can_consume_node(self, node, direction, last_edge_shortcut=False): return False def _is_valid_interpolate_edge(self, node, direction): - """ - Is a valid interpolation edge. - - :param node: the node to consume - :type node: ValueNode - :param direction: the direct to go in. Must be in {-1, 1} - :type direction: int - :returns: true it can be consumed. - :rtype: bool + """Is a valid interpolation edge. + + Parameters + ---------- + node : ValueNode + the node to consume + direction : int + the direct to go in. Must be in {-1, 1} + + Returns + ------- + bool + true it can be consumed. """ # kill jumps immediately if node.value is None: @@ -2160,20 +2243,24 @@ def _is_valid_interpolate_edge(self, node, direction): return math.isclose(new_val, node.value, rel_tol=rel_tol, abs_tol=abs_tol) def consume_edge_node(self, node, direction, last_edge_shortcut=False): - """ - Tries to consume the given edge. + """Tries to consume the given edge. If it can be consumed the node is appended to the internal nodes. - :param node: the node to consume - :type node: ValueNode - :param direction: the direct to go in. Must be in {-1, 1} - :type direction: int - :param last_edge_shortcut: Whether or the previous node in the list was - part of a different shortcut - :type last_edge_shortcut: bool - :returns: True if the node was consumed. - :rtype: bool + Parameters + ---------- + node : ValueNode + the node to consume + direction : int + the direct to go in. Must be in {-1, 1} + last_edge_shortcut : bool + Whether or the previous node in the list was part of a + different shortcut + + Returns + ------- + bool + True if the node was consumed. """ if self._can_consume_node(node, direction, last_edge_shortcut): if direction == 1: @@ -2221,20 +2308,25 @@ def _format_jump(self): return f"{num_jumps.format()}{j}" def _can_use_last_node(self, node, start=None): - """ - Determine if the previous node can be used as the start to this node + """Determine if the previous node can be used as the start to this node (and therefore skip the start of this one). Last node can be used if - it's a basic ValueNode that matches this repeat - it's also a shortcut, with the same edge values. - :param node: the previous node to test. - :type node: ValueNode, ShortcutNode - :param start: the starting value for this node (specifically for interpolation) - :type start: float - :returns: True if the node given can be used. - :rtype: bool + Parameters + ---------- + node : ValueNode, ShortcutNode + the previous node to test. + start : float + the starting value for this node (specifically for + interpolation) + + Returns + ------- + bool + True if the node given can be used. """ if isinstance(node, ValueNode): value = node.value @@ -2327,8 +2419,7 @@ def _format_interpolate(self, leading_node=None): class ClassifierNode(SyntaxNodeBase): - """ - A node to represent the classifier for a :class:`montepy.data_input.DataInput` + """A node to represent the classifier for a :class:`montepy.data_input.DataInput` e.g., represents ``M4``, ``F104:n,p``, ``IMP:n,e``. """ @@ -2344,15 +2435,16 @@ def __init__(self): @property def prefix(self): - """ - The prefix for the classifier. + """The prefix for the classifier. That is the string that tells what type of input this is. E.g.: ``M`` in ``M4`` or ``IMP`` in ``IMP:n``. - :returns: the prefix - :rtype: ValueNode + Returns + ------- + ValueNode + the prefix """ return self._prefix @@ -2363,11 +2455,12 @@ def prefix(self, pref): @property def number(self): - """ - The number if any for the classifier. + """The number if any for the classifier. - :returns: the number holder for this classifier. - :rtype: ValueNode + Returns + ------- + ValueNode + the number holder for this classifier. """ return self._number @@ -2378,11 +2471,12 @@ def number(self, number): @property def particles(self): - """ - The particles if any tied to this classifier. + """The particles if any tied to this classifier. - :returns: the particles used. - :rtype: ParticleNode + Returns + ------- + ParticleNode + the particles used. """ return self._particles @@ -2393,14 +2487,15 @@ def particles(self, part): @property def modifier(self): - """ - The modifier for this classifier if any. + """The modifier for this classifier if any. A modifier is a prefix character that changes the inputs behavior, e.g.: ``*`` or ``+``. - :returns: the modifier - :rtype: ValueNode + Returns + ------- + ValueNode + the modifier """ return self._modifier @@ -2411,14 +2506,15 @@ def modifier(self, mod): @property def padding(self): - """ - The padding for this classifier. + """The padding for this classifier. .. Note:: None of the ValueNodes in this object should have padding. - :returns: the padding after the classifier. - :rtype: PaddingNode + Returns + ------- + PaddingNode + the padding after the classifier. """ return self._padding @@ -2492,8 +2588,7 @@ def flatten(self): class ParametersNode(SyntaxNodeBase): - """ - A node to hold the parameters, key-value pairs, for this input. + """A node to hold the parameters, key-value pairs, for this input. This behaves like a dictionary and is accessible by their key* @@ -2523,16 +2618,18 @@ def __init__(self): self._nodes = {} def append(self, val, is_default=False): - """ - Append the node to this node. + """Append the node to this node. This takes a syntax node, which requires the keys: ``["classifier", "seperator", "data"]`` - :param val: the parameter to append. - :type val: SyntaxNode - :param is_default: whether this parameter was added as a default tree not from the user. - :type is_default: bool + Parameters + ---------- + val : SyntaxNode + the parameter to append. + is_default : bool + whether this parameter was added as a default tree not from + the user. """ classifier = val["classifier"] key = ( diff --git a/montepy/input_parser/tally_parser.py b/montepy/input_parser/tally_parser.py index 7c50b07f..a8d363ce 100644 --- a/montepy/input_parser/tally_parser.py +++ b/montepy/input_parser/tally_parser.py @@ -4,11 +4,12 @@ class TallyParser(DataParser): - """ - A barebone parser for parsing tallies before they are fully implemented. + """A barebone parser for parsing tallies before they are fully implemented. - :returns: a syntax tree for the data input. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + a syntax tree for the data input. """ debugfile = None @@ -34,11 +35,12 @@ def tally_specification(self, p): @_("PARTICLE", "PARTICLE padding") def end_phrase(self, p): - """ - A non-zero number with or without padding. + """A non-zero number with or without padding. - :returns: a float ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a float ValueNode """ return self._flush_phrase(p, str) diff --git a/montepy/input_parser/tally_seg_parser.py b/montepy/input_parser/tally_seg_parser.py index bef8e01c..950803aa 100644 --- a/montepy/input_parser/tally_seg_parser.py +++ b/montepy/input_parser/tally_seg_parser.py @@ -4,13 +4,14 @@ class TallySegmentParser(DataParser): - """ - A barebone parser for parsing tally segment inputs before they are fully implemented. + """A barebone parser for parsing tally segment inputs before they are fully implemented. .. versionadded:: 0.2.10 - :returns: a syntax tree for the data input. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode + a syntax tree for the data input. """ debugfile = None @@ -40,11 +41,12 @@ def tally_specification(self, p): @_("PARTICLE", "PARTICLE padding", "TEXT", "TEXT padding") def end_phrase(self, p): - """ - A non-zero number with or without padding. + """A non-zero number with or without padding. - :returns: a float ValueNode - :rtype: ValueNode + Returns + ------- + ValueNode + a float ValueNode """ return self._flush_phrase(p, str) diff --git a/montepy/input_parser/thermal_parser.py b/montepy/input_parser/thermal_parser.py index 4f2e41e0..c6ae1956 100644 --- a/montepy/input_parser/thermal_parser.py +++ b/montepy/input_parser/thermal_parser.py @@ -4,10 +4,11 @@ class ThermalParser(DataParser): - """ - A parser for thermal scattering law inputs. + """A parser for thermal scattering law inputs. - :rtype: SyntaxNode + Returns + ------- + SyntaxNode """ debugfile = None diff --git a/montepy/input_parser/tokens.py b/montepy/input_parser/tokens.py index 5a765f61..18f70cb0 100644 --- a/montepy/input_parser/tokens.py +++ b/montepy/input_parser/tokens.py @@ -6,8 +6,7 @@ class MCNP_Lexer(Lexer): - """ - Base lexer for all MCNP lexers. + """Base lexer for all MCNP lexers. Provides ~90% of the tokens definition. @@ -24,6 +23,7 @@ class MCNP_Lexer(Lexer): LIBRARY_SUFFIX, LOG_INTERPOLATE, MESSAGE, + MODIFIER, MULTIPLY, NUM_INTERPOLATE, NUM_JUMP, @@ -73,77 +73,25 @@ class MCNP_Lexer(Lexer): "cosy", "bflcl", "unc", - # materials - "gas", - "estep", - "hstep", - "nlib", - "plib", - "pnlib", - "elib", - "hlib", - "alib", - "slib", - "tlib", - "dlib", - "cond", - "refi", - "refc", - "refs", - # volume - "no", - # sdef - "cel", - "sur", - "erg", - "tme", - "dir", - "vec", - "nrm", - "pos", - "rad", - "ext", - "axs", - "x", - "y", - "z", - "ccc", - "ara", - "wgt", - "tr", - "eff", - "par", - "dat", - "loc", - "bem", - "bap", } - """ - Defines allowed keywords in MCNP. - """ + """Defines allowed keywords in MCNP.""" literals = {"(", ":", ")", "&", "#", "=", "*", "+", ","} COMPLEMENT = r"\#" - """ - A complement character. - """ + """A complement character.""" reflags = re.IGNORECASE | re.VERBOSE @_(r"\$.*") def DOLLAR_COMMENT(self, t): - """ - A comment starting with a dollar sign. - """ + """A comment starting with a dollar sign.""" self.lineno += t.value.count("\n") return t @_(r"C\n", r"C\s.*") def COMMENT(self, t): - """ - A ``c`` style comment. - """ + """A ``c`` style comment.""" self.lineno += t.value.count("\n") start = self.find_column(self.text, t) if start > 5: @@ -152,9 +100,7 @@ def COMMENT(self, t): @_(r"SC\d+.*") def SOURCE_COMMENT(self, t): - """ - A source comment. - """ + """A source comment.""" self.lineno += t.value.count("\n") start = self.find_column(self.text, t) if start <= 5: @@ -164,9 +110,7 @@ def SOURCE_COMMENT(self, t): @_(r"FC\d+.*") def TALLY_COMMENT(self, t): - """ - A tally Comment. - """ + """A tally Comment.""" self.lineno += t.value.count("\n") start = self.find_column(self.text, t) if start <= 5: @@ -176,17 +120,14 @@ def TALLY_COMMENT(self, t): @_(r"\s+") def SPACE(self, t): - """ - Any white space. - """ + """Any white space.""" t.value = t.value.expandtabs(constants.TABSIZE) self.lineno += t.value.count("\n") return t @_(r"\d{4,6}\.(\d{2}[a-z]|\d{3}[a-z]{2})") def ZAID(self, t): - """ - A ZAID isotope definition in the MCNP format. + """A ZAID isotope definition in the MCNP format. E.g.: ``1001.80c``. """ @@ -194,16 +135,14 @@ def ZAID(self, t): # note: / is not escaping - since this doesn't not need escape in this position THERMAL_LAW = r"[a-z][a-z\d/-]+\.\d+[a-z]" - """ - An MCNP formatted thermal scattering law. + """An MCNP formatted thermal scattering law. - e.g.: ``lwtr.20t``. + e.g.: ``lwtr.20t``. """ @_(r"[+\-]?\d+(?!e)[a-z]+") def NUMBER_WORD(self, t): - """ - An integer followed by letters. + """An integer followed by letters. Can be used for library numbers, as well as shortcuts. @@ -216,18 +155,14 @@ def NUMBER_WORD(self, t): @_(r"[+\-]?[0-9]+\.?[0-9]*E?[+\-]?[0-9]*", r"[+\-]?[0-9]*\.?[0-9]+E?[+\-]?[0-9]*") def NUMBER(self, t): - """ - A float, or int number, including "fortran floats". - """ + """A float, or int number, including "fortran floats".""" if fortran_float(t.value) == 0: t.type = "NULL" return t @_(r"[+\-]?[0-9]*\.?[0-9]*E?[+\-]?[0-9]*[ijrml]+[a-z\./]*", r"[a-z]+[a-z\./]*") def TEXT(self, t): - """ - General text that covers shortcuts and Keywords. - """ + """General text that covers shortcuts and Keywords.""" if update := self._parse_shortcut(t): return update if t.value.lower() in self._KEYWORDS: @@ -235,15 +170,11 @@ def TEXT(self, t): return t NULL = r"0+" - """ - Zero number. - """ + """Zero number.""" @_(r"MESSAGE:.*\s") def MESSAGE(self, t): - """ - A message block. - """ + """A message block.""" self.lineno += t.value.count("\n") return t @@ -262,70 +193,49 @@ def _parse_shortcut(self, t): return t INTERPOLATE = r"\d*I" - """ - An interpolate shortcut. - """ + """An interpolate shortcut.""" NUM_INTERPOLATE = r"\d+I" - """ - An interpolate shortcut with a number. - """ + """An interpolate shortcut with a number.""" JUMP = r"\d*J" - """ - A jump shortcut. - """ + """A jump shortcut.""" NUM_JUMP = r"\d+J" - """ - A jump shortcut with a number. - """ + """A jump shortcut with a number.""" LOG_INTERPOLATE = r"\d*I?LOG" - """ - A logarithmic interpolate shortcut. - """ + """A logarithmic interpolate shortcut.""" NUM_LOG_INTERPOLATE = r"\d+I?LOG" - """ - A logarithmic interpolate shortcut. - """ + """A logarithmic interpolate shortcut.""" MULTIPLY = r"[+\-]?[0-9]+\.?[0-9]*E?[+\-]?[0-9]*M" - """ - A multiply shortcut. - """ + """A multiply shortcut.""" NUM_MULTIPLY = r"[+\-]?[0-9]+\.?[0-9]*E?[+\-]?[0-9]*M" - """ - A multiply shortcut with a number. - """ + """A multiply shortcut with a number.""" REPEAT = r"\d*R" - """ - A repeat shortcut. - """ + """A repeat shortcut.""" NUM_REPEAT = r"\d+R" - """ - A repeat shortcut with a number. - """ + """A repeat shortcut with a number.""" FILE_PATH = r'[^><:"%,;=&\(\)|?*\s]+' - """ - A file path that covers basically anything that windows or linux allows. - """ + """A file path that covers basically anything that windows or linux allows.""" @staticmethod def find_column(text, token): - """ - Calculates the column number for the start of this token. + """Calculates the column number for the start of this token. Uses 0-indexing. - :param text: the text being lexed. - :type text: str - :param token: the token currently being processed - :type token: sly.lex.Token + Parameters + ---------- + text : str + the text being lexed. + token : sly.lex.Token + the token currently being processed """ last_cr = text.rfind("\n", 0, token.index) if last_cr < 0: @@ -335,9 +245,7 @@ def find_column(text, token): class ParticleLexer(MCNP_Lexer): - """ - A lexer for lexing an input that has particles in it. - """ + """A lexer for lexing an input that has particles in it.""" tokens = { COMMENT, @@ -414,9 +322,7 @@ def TEXT(self, t): class CellLexer(ParticleLexer): - """ - A lexer for cell inputs that allows particles. - """ + """A lexer for cell inputs that allows particles.""" tokens = { COMMENT, @@ -441,9 +347,7 @@ class CellLexer(ParticleLexer): class DataLexer(ParticleLexer): - """ - A lexer for data inputs. - """ + """A lexer for data inputs.""" tokens = { COMMENT, @@ -453,6 +357,7 @@ class DataLexer(ParticleLexer): JUMP, KEYWORD, LOG_INTERPOLATE, + MODIFIER, MESSAGE, MULTIPLY, NUMBER, @@ -468,17 +373,90 @@ class DataLexer(ParticleLexer): ZAID, } + _KEYWORDS = set(MCNP_Lexer._KEYWORDS) | { + # ssw + "sym", + "pty", + "cel", + # ssr + "old", + "new", + "col", + "wgt", + "tr", + "psc", + "poa", + "bcw", + # materials + "gas", + "estep", + "hstep", + "nlib", + "plib", + "pnlib", + "elib", + "hlib", + "alib", + "slib", + "tlib", + "dlib", + "cond", + "refi", + "refc", + "refs", + # volume + "no", + # sdef + "cel", + "sur", + "erg", + "tme", + "dir", + "vec", + "nrm", + "pos", + "rad", + "ext", + "axs", + "x", + "y", + "z", + "ccc", + "ara", + "wgt", + "tr", + "eff", + "par", + "dat", + "loc", + "bem", + "bap", + } + + _MODIFIERS = { + "no", # VOLUME + } + """A keyword flag at the beginning of a input that modifies it's behavior.""" + @_(r"([|+\-!<>/%^_~@\*\?\#]|\#\d*)+") def PARTICLE_SPECIAL(self, t): - """ - Particle designators that are special characters. - """ + """Particle designators that are special characters.""" + return t + + @_(r"[+\-]?[0-9]*\.?[0-9]*E?[+\-]?[0-9]*[ijrml]+[a-z\./]*", r"[a-z]+[a-z\./]*") + def TEXT(self, t): + t = super().TEXT(t) + if t.value.lower() in self._KEYWORDS: + t.type = "KEYWORD" + if t.value.lower() in self._MODIFIERS: + t.type = "MODIFIER" + elif t.value.lower() in self._PARTICLES: + t.type = "PARTICLE" return t class SurfaceLexer(MCNP_Lexer): - """ - A lexer for Surface inputs. + """A lexer for Surface inputs. The main difference is that ``p`` will be interpreted as a plane, and not a photon. @@ -547,9 +525,7 @@ class SurfaceLexer(MCNP_Lexer): "wed", "arb", } - """ - All allowed surface types. - """ + """All allowed surface types.""" @_(r"[+\-]?[0-9]*\.?[0-9]*E?[+\-]?[0-9]*[ijrml]+[a-z\./]*", r"[a-z]+[a-z\./]*") def TEXT(self, t): diff --git a/montepy/materials.py b/montepy/materials.py index 406f1147..28b796e0 100644 --- a/montepy/materials.py +++ b/montepy/materials.py @@ -1,8 +1,10 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations +import collections as co import copy from typing import Generator, Union +from numbers import Integral, Real import montepy from montepy.numbered_object_collection import NumberedDataObjectCollection @@ -11,35 +13,94 @@ class Materials(NumberedDataObjectCollection): - """ - A container of multiple :class:`~montepy.data_inputs.material.Material` instances. - - .. note:: - When items are added to this (and this object is linked to a problem), - they will also be added to :func:`montepy.mcnp_problem.MCNP_Problem.data_inputs`. + """A container of multiple :class:`~montepy.data_inputs.material.Material` instances. - .. note:: + Notes + ----- + When items are added to this (and this object is linked to a problem), + they will also be added to :func:`montepy.mcnp_problem.MCNP_Problem.data_inputs`. - For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. + Notes + ----- + For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. - :param objects: the list of materials to start with if needed - :type objects: list + Parameters + ---------- + objects : list + the list of materials to start with if needed """ def __init__(self, objects=None, problem=None): super().__init__(Material, objects, problem) - def get_containing( + def get_containing_any( self, - nuclide: Union[ + *nuclides: Union[ montepy.data_inputs.nuclide.Nuclide, montepy.data_inputs.nuclide.Nucleus, montepy.Element, str, int, ], - *args: Union[ + threshold: float = 0.0, + strict: bool = False, + ) -> Generator[Material]: + """Get all materials that contain any of these these nuclides. + + This uses :func:`~montepy.data_inputs.material.Material.contains` under the hood. + See that documentation for more guidance. + + Examples + ^^^^^^^^ + + One example would to be find all water bearing materials: + + .. testcode:: + + import montepy + problem = montepy.read_input("foo.imcnp") + for mat in problem.materials.get_containing_any("H-1", "U-235", threshold = 0.3): + print(mat) + + .. testoutput:: + + MATERIAL: 1, ['hydrogen', 'oxygen'] + + .. versionadded:: 1.0.0 + + Parameters + ---------- + *nuclides : Union[Nuclide, Nucleus, Element, str, int] + a plurality of nuclides to check for. + threshold : float + the minimum concentration of a nuclide to be considered. The + material components are not first normalized. + strict : bool + If True this does not let an elemental nuclide match all + child isotopes, isomers, nor will an isotope match all + isomers, nor will a blank library match all libraries. + + Returns + ------- + Generator[Material] + A generator of all matching materials + + Raises + ------ + TypeError + if any argument is of the wrong type. + ValueError + if the fraction is not positive or zero, or if nuclide + cannot be interpreted as a Nuclide. + """ + return self._contains_arb( + *nuclides, bool_func=any, threshold=threshold, strict=strict + ) + + def get_containing_all( + self, + *nuclides: Union[ montepy.data_inputs.nuclide.Nuclide, montepy.data_inputs.nuclide.Nucleus, montepy.Element, @@ -47,9 +108,9 @@ def get_containing( int, ], threshold: float = 0.0, + strict: bool = False, ) -> Generator[Material]: - """ - Get all materials that contain these nuclides. + """Get all materials that contain all of these nuclides. This uses :func:`~montepy.data_inputs.material.Material.contains` under the hood. See that documentation for more guidance. @@ -63,7 +124,7 @@ def get_containing( import montepy problem = montepy.read_input("foo.imcnp") - for mat in problem.materials.get_containing("H-1", "O-16", threshold = 0.3): + for mat in problem.materials.get_containing_all("H-1", "O-16", threshold = 0.3): print(mat) .. testoutput:: @@ -72,39 +133,51 @@ def get_containing( .. versionadded:: 1.0.0 - :param nuclide: the first nuclide to check for. - :type nuclide: Union[Nuclide, Nucleus, Element, str, int] - :param args: a plurality of other nuclides to check for. - :type args: Union[Nuclide, Nucleus, Element, str, int] - :param threshold: the minimum concentration of a nuclide to be considered. The material components are not - first normalized. - :type threshold: float - - :return: A generator of all matching materials - :rtype: Generator[Material] - - :raises TypeError: if any argument is of the wrong type. - :raises ValueError: if the fraction is not positive or zero, or if nuclide cannot be interpreted as a Nuclide. + Parameters + ---------- + *nuclides : Union[Nuclide, Nucleus, Element, str, int] + a plurality of nuclides to check for. + threshold : float + the minimum concentration of a nuclide to be considered. The + material components are not first normalized. + strict : bool + If True this does not let an elemental nuclide match all + child isotopes, isomers, nor will an isotope match all + isomers, nor will a blank library match all libraries. + + Returns + ------- + Generator[Material] + A generator of all matching materials + + Raises + ------ + TypeError + if any argument is of the wrong type. + ValueError + if the fraction is not positive or zero, or if nuclide + cannot be interpreted as a Nuclide. """ - nuclides = [] - for nuclide in [nuclide] + list(args): - if not isinstance( - nuclide, - ( - str, - int, - montepy.Element, - montepy.data_inputs.nuclide.Nucleus, - montepy.Nuclide, - ), - ): - raise TypeError( - f"nuclide must be of type str, int, Element, Nucleus, or Nuclide. " - f"{nuclide} of type {type(nuclide)} given." - ) - if isinstance(nuclide, (str, int)): - nuclide = montepy.Nuclide(nuclide) - nuclides.append(nuclide) + return self._contains_arb( + *nuclides, bool_func=all, threshold=threshold, strict=strict + ) + + def _contains_arb( + self, + *nuclides: Union[ + montepy.data_inputs.nuclide.Nuclide, + montepy.data_inputs.nuclide.Nucleus, + montepy.Element, + str, + int, + ], + bool_func: co.abc.Callable[co.abc.Iterable[bool]], + threshold: float = 0.0, + strict: bool = False, + ) -> Generator[Material]: + nuclide_finders = [] + for nuclide in nuclides: + nuclide_finders.append(Material._promote_nuclide(nuclide, strict)) def sort_by_type(nuclide): type_map = { @@ -115,18 +188,21 @@ def sort_by_type(nuclide): return type_map[type(nuclide)] # optimize by most hashable and fail fast - nuclides = sorted(nuclides, key=sort_by_type) + nuclide_finders = sorted(nuclide_finders, key=sort_by_type) for material in self: - if material.contains(*nuclides, threshold=threshold): + if material._contains_arb( + *nuclide_finders, + bool_func=bool_func, + threshold=threshold, + strict=strict, + ): # maybe? Maybe not? # should Materials act like a set? yield material @property def default_libraries(self) -> dict[montepy.LibraryType, montepy.Library]: - """ - The default libraries for this problem defined by ``M0``. - + """The default libraries for this problem defined by ``M0``. Examples ^^^^^^^^ @@ -146,8 +222,10 @@ def default_libraries(self) -> dict[montepy.LibraryType, montepy.Library]: .. versionadded:: 1.0.0 - :returns: the default libraries in use - :rtype: dict[LibraryType, Library] + Returns + ------- + dict[LibraryType, Library] + the default libraries in use """ try: return self[0].default_libraries @@ -164,8 +242,7 @@ def mix( starting_number=None, step=None, ) -> Material: - """ - Mix the given materials in the provided fractions to create a new material. + """Mix the given materials in the provided fractions to create a new material. All materials must use the same fraction type, either atom fraction or mass fraction. The fractions given to this method are interpreted in that way as well. @@ -204,20 +281,32 @@ def mix( boric_conc = boron_ppm * 1e-6 borated_water = mats.mix([h2o, boric_acid], [1 - boric_conc, boric_conc]) - - :param materials: the materials to mix. - :type materials: list[Material] - :param fractions: the corresponding fractions for each material in either atom or mass fractions, depending on - the materials fraction type. - :param starting_number: the starting number to assign this new material. - :type starting_number: Union[int, None] - :param step: the step size to take when finding a new number. - :type step: Union[int, None] - :returns: a new material with the mixed components of the given materials - :rtype: Material - :raises TypeError: if invalid objects are given. - :raises ValueError: if the number of elements in the two lists mismatch, or if not all the materials are of the - same fraction type, or if a negative starting_number or step are given. + Parameters + ---------- + materials : list[Material] + the materials to mix. + fractions + the corresponding fractions for each material in either atom + or mass fractions, depending on the materials fraction type. + starting_number : Union[int, None] + the starting number to assign this new material. + step : Union[int, None] + the step size to take when finding a new number. + + Returns + ------- + Material + a new material with the mixed components of the given + materials + + Raises + ------ + TypeError + if invalid objects are given. + ValueError + if the number of elements in the two lists mismatch, or if + not all the materials are of the same fraction type, or if a + negative starting_number or step are given. """ if not isinstance(materials, list): raise TypeError(f"materials must be a list. {materials} given.") @@ -235,7 +324,7 @@ def mix( if not isinstance(fractions, list): raise TypeError(f"fractions must be a list. {fractions} given.") for frac in fractions: - if not isinstance(frac, float): + if not isinstance(frac, Real): raise TypeError(f"fraction in fractions must be a float. {frac} given.") if frac < 0.0: raise ValueError(f"Fraction cannot be negative. {frac} given.") @@ -243,7 +332,7 @@ def mix( raise ValueError( f"Length of materials and fractions don't match. The lengths are, materials: {len(materials)}, fractions: {len(fractions)}" ) - if not isinstance(starting_number, (int, type(None))): + if not isinstance(starting_number, (Integral, type(None))): raise TypeError( f"starting_number must be an int. {starting_number} of type {type(starting_number)} given." ) @@ -251,7 +340,7 @@ def mix( raise ValueError( f"starting_number must be positive. {starting_number} given." ) - if not isinstance(step, (int, type(None))): + if not isinstance(step, (Integral, type(None))): raise TypeError(f"step must be an int. {step} of type {type(step)} given.") if step is not None and step <= 0: raise ValueError(f"step must be positive. {step} given.") diff --git a/montepy/mcnp_object.py b/montepy/mcnp_object.py index 54e15f63..b7a02720 100644 --- a/montepy/mcnp_object.py +++ b/montepy/mcnp_object.py @@ -1,4 +1,4 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations from abc import ABC, ABCMeta, abstractmethod import copy @@ -14,6 +14,7 @@ from montepy.errors import * from montepy.constants import ( BLANK_SPACE_CONTINUE, + COMMENT_FINDER, get_max_line_length, rel_tol, abs_tol, @@ -30,16 +31,11 @@ class _ExceptionContextAdder(ABCMeta): - """ - A metaclass for wrapping all class properties and methods in :func:`~montepy.errors.add_line_number_to_exception`. - - """ + """A metaclass for wrapping all class properties and methods in :func:`~montepy.errors.add_line_number_to_exception`.""" @staticmethod def _wrap_attr_call(func): - """ - Wraps the function, and returns the modified function. - """ + """Wraps the function, and returns the modified function.""" @functools.wraps(func) def wrapped(*args, **kwargs): @@ -65,8 +61,7 @@ def wrapped(*args, **kwargs): return wrapped def __new__(meta, classname, bases, attributes): - """ - This will replace all properties and callable attributes with + """This will replace all properties and callable attributes with wrapped versions. """ new_attrs = {} @@ -94,13 +89,14 @@ def __new__(meta, classname, bases, attributes): class MCNP_Object(ABC, metaclass=_ExceptionContextAdder): - """ - Abstract class for semantic representations of MCNP inputs. - - :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 + """Abstract class for semantic representations of MCNP inputs. + + Parameters + ---------- + input : Union[Input, str] + The Input syntax object this will wrap and parse. + parser : MCNP_Parser + The parser object to parse the input with. """ def __init__( @@ -167,19 +163,25 @@ def __setattr__(self, key, value): @staticmethod def _generate_default_node(value_type: type, default, padding: str = " "): - """ - Generates a "default" or blank ValueNode. + """Generates a "default" or blank ValueNode. None is generally a safe default value to provide. - :param value_type: the data type for the ValueNode. - :type value_type: Class - :param default: the default value to provide (type needs to agree with value_type) - :type default: value_type - :param padding: the string to provide to the PaddingNode. If None no PaddingNode will be added. - :type padding: str, None - :returns: a new ValueNode with the requested information. - :rtype: ValueNode + Parameters + ---------- + value_type : Class + the data type for the ValueNode. + default : value_type + the default value to provide (type needs to agree with + value_type) + padding : str, None + the string to provide to the PaddingNode. If None no + PaddingNode will be added. + + Returns + ------- + ValueNode + a new ValueNode with the requested information. """ if padding: padding_node = PaddingNode(padding) @@ -191,21 +193,24 @@ def _generate_default_node(value_type: type, default, padding: str = " "): @property def parameters(self) -> dict[str, str]: - """ - A dictionary of the additional parameters for the object. + """A dictionary of the additional parameters for the object. e.g.: ``1 0 -1 u=1 imp:n=0.5`` has the parameters ``{"U": "1", "IMP:N": "0.5"}`` - :returns: a dictionary of the key-value pairs of the parameters. + Returns + ------- + unknown + a dictionary of the key-value pairs of the parameters. + + :rytpe: dict """ return self._parameters @abstractmethod def _update_values(self): - """ - Method to update values in syntax tree with new values. + """Method to update values in syntax tree with new values. Generally when :func:`~montepy.utilities.make_prop_val_node` this is not necessary to do, but when :func:`~montepy.utilities.make_prop_pointer` is used it is necessary. @@ -216,14 +221,18 @@ def _update_values(self): pass def format_for_mcnp_input(self, mcnp_version: tuple[int]) -> list[str]: - """ - Creates a string representation of this MCNP_Object that can be + """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 + Parameters + ---------- + mcnp_version : tuple + The tuple for the MCNP version that must be exported to. + + Returns + ------- + list + a list of strings for the lines that this input will occupy. """ self.validate() self._update_values() @@ -233,24 +242,26 @@ def format_for_mcnp_input(self, mcnp_version: tuple[int]) -> list[str]: @property def comments(self) -> list[PaddingNode]: - """ - The comments associated with this input if any. + """The comments associated with this input if any. This includes all ``C`` comments before this card that aren't part of another card, and any comments that are inside this card. - :returns: a list of the comments associated with this comment. - :rtype: list + Returns + ------- + list + a list of the comments associated with this comment. """ return list(self._tree.comments) @property def leading_comments(self) -> list[PaddingNode]: - """ - Any comments that come before the beginning of the input proper. + """Any comments that come before the beginning of the input proper. - :returns: the leading comments. - :rtype: list + Returns + ------- + list + the leading comments. """ return list(self._tree["start_pad"].comments) @@ -287,25 +298,31 @@ def leading_comments(self): def wrap_string_for_mcnp( string, mcnp_version, is_first_line, suppress_blank_end=True ) -> list[str]: - """ - Wraps the list of the words to be a well formed MCNP input. + """Wraps the list of the words to be a well formed MCNP input. multi-line inputs will be handled by using the indentation format, and not the "&" method. - :param string: A long string with new lines in it, - that needs to be chunked appropriately for MCNP inputs - :type string: str - :param mcnp_version: the tuple for the MCNP that must be formatted for. - :type mcnp_version: tuple - :param is_first_line: If true this will be the beginning of an MCNP input. - The first line will not be indented. - :type is_first_line: bool - :param suppress_blank_end: Whether or not to suppress any blank lines that would be added to the end. - Good for anywhere but cell modifiers in the cell block. - :type suppress_blank_end: bool - :returns: A list of strings that can be written to an input file, one item to a line. - :rtype: list + Parameters + ---------- + string : str + A long string with new lines in it, that needs to be chunked + appropriately for MCNP inputs + mcnp_version : tuple + the tuple for the MCNP that must be formatted for. + is_first_line : bool + If true this will be the beginning of an MCNP input. The + first line will not be indented. + suppress_blank_end : bool + Whether or not to suppress any blank lines that would be + added to the end. Good for anywhere but cell modifiers in + the cell block. + + Returns + ------- + list + A list of strings that can be written to an input file, one + item to a line. """ line_length = get_max_line_length(mcnp_version) indent_length = BLANK_SPACE_CONTINUE @@ -324,17 +341,26 @@ def wrap_string_for_mcnp( for line in strings: buffer = wrapper.wrap(line) if len(buffer) > 1: - warning = LineExpansionWarning( - f"The line exceeded the maximum length allowed by MCNP, and was split. The line was:\n{line}" - ) - warning.cause = "line" - warning.og_value = line - warning.new_value = buffer - warnings.warn( - warning, - LineExpansionWarning, - stacklevel=2, - ) + # don't warn for comments, nor line wrap + # this order assumes that comment overruns are rare + if COMMENT_FINDER.match(line): + buffer = [line] + elif "$" in line: + parts = line.split("$") + buffer = wrapper.wrap(parts[0]) + buffer[-1] = "$".join([buffer[-1]] + parts[1:]) + else: + warning = LineExpansionWarning( + f"The line exceeded the maximum length allowed by MCNP, and was split. The line was:\n{line}" + ) + warning.cause = "line" + warning.og_value = line + warning.new_value = buffer + warnings.warn( + warning, + LineExpansionWarning, + stacklevel=2, + ) # lazy final guard against extra lines if suppress_blank_end: buffer = [s for s in buffer if s.strip()] @@ -342,11 +368,7 @@ def wrap_string_for_mcnp( return ret def validate(self): - """ - Validates that the object is in a usable state. - - :raises: IllegalState if any condition exists that make the object incomplete. - """ + """Validates that the object is in a usable state.""" pass def link_to_problem(self, problem: montepy.mcnp_problem.MCNP_Problem): @@ -354,8 +376,10 @@ def link_to_problem(self, problem: montepy.mcnp_problem.MCNP_Problem): This is done so that inputs can find links to other objects. - :param problem: The problem to link this input to. - :type problem: MCNP_Problem + Parameters + ---------- + problem : MCNP_Problem + The problem to link this input to. """ if not isinstance(problem, (montepy.mcnp_problem.MCNP_Problem, type(None))): raise TypeError("problem must be an MCNP_Problem") @@ -379,13 +403,15 @@ def _problem(self, problem): @property def trailing_comment(self) -> list[PaddingNode]: - """ - The trailing comments and padding of an input. + """The trailing comments and padding of an input. Generally this will be blank as these will be moved to be a leading comment for the next input. - :returns: the trailing ``c`` style comments and intermixed padding (e.g., new lines) - :rtype: list + Returns + ------- + list + the trailing ``c`` style comments and intermixed padding + (e.g., new lines) """ return self._tree.get_trailing_comment() @@ -409,10 +435,11 @@ def __setstate__(self, crunchy_data): self.__dict__.update(crunchy_data) def clone(self) -> montepy.mcnp_object.MCNP_Object: - """ - Create a new independent instance of this object. + """Create a new independent instance of this object. - :returns: a new instance identical to this object. - :rtype: type(self) + Returns + ------- + type(self) + a new instance identical to this object. """ return copy.deepcopy(self) diff --git a/montepy/mcnp_problem.py b/montepy/mcnp_problem.py index 247a199a..cb18e7dc 100644 --- a/montepy/mcnp_problem.py +++ b/montepy/mcnp_problem.py @@ -25,14 +25,16 @@ class MCNP_Problem: - """ - A class to represent an entire MCNP problem in a semantic way. + """A class to represent an entire MCNP problem in a semantic way. - .. note:: - If a stream is provided. It will not be closed by this function. + Notes + ----- + If a stream is provided. It will not be closed by this function. - :param destination: the path to the input file to read, or a readable stream. - :type destination: io.TextIOBase, str, os.PathLike + Parameters + ---------- + destination : io.TextIOBase, str, os.PathLike + the path to the input file to read, or a readable stream. """ _NUMBERED_OBJ_MAP = { @@ -70,8 +72,7 @@ def __get_collect_attr_name(collect_type): @property def original_inputs(self): - """ - A list of the MCNP_Inputs read from the original file. + """A list of the MCNP_Inputs read from the original file. This should not be mutated, and should be used as a reference to maintain the structure @@ -79,8 +80,11 @@ def original_inputs(self): .. deprecated:: 0.2.0 This will likely be removed soon, and it's functionality will not be necessary to reproduce. - :return: A list of the MCNP_Object objects representing the file as it was read - :rtype: list + Returns + ------- + list + A list of the MCNP_Object objects representing the file as + it was read """ return self._original_inputs @@ -126,22 +130,25 @@ def __deepcopy__(self, memo): return result def clone(self): - """ - Creates a complete independent copy of this problem. + """Creates a complete independent copy of this problem. .. versionadded:: 0.5.0 - :rtype: MCNP_Problem + Returns + ------- + MCNP_Problem """ return copy.deepcopy(self) @property def cells(self): - """ - A collection of the Cell objects in this problem. + """A collection of the Cell objects in this problem. - :return: a collection of the Cell objects, ordered by the order they were in the input file. - :rtype: Cells + Returns + ------- + Cells + a collection of the Cell objects, ordered by the order they + were in the input file. """ self.__relink_objs() return self._cells @@ -159,10 +166,11 @@ def cells(self, cells): @property def mode(self): - """ - The mode of particles being used for the problem. + """The mode of particles being used for the problem. - :rtype: Mode + Returns + ------- + Mode """ return self._mode @@ -171,35 +179,46 @@ def set_mode(self, particles): For details see: :func:`montepy.data_cards.mode.Mode.set`. - :param particles: the particles that the mode will be switched to. - :type particles: list, str - :raises ValueError: if string is not a valid particle shorthand. + Parameters + ---------- + particles : list, str + the particles that the mode will be switched to. + + Raises + ------ + ValueError + if string is not a valid particle shorthand. """ self._mode.set(particles) @property def mcnp_version(self): - """ - The version of MCNP that this is intended for. + """The version of MCNP that this is intended for. + + Notes + ----- + MCNP versions prior to 6.2 aren't fully supported to avoid + Export Control Restrictions. Documentation for MCNP 6.2 is public in report: + LA-UR-17-29981. + All features are based on MCNP 6.2, and may cause other versions of MCNP to break. - .. note:: - MCNP versions prior to 6.2 aren't fully supported to avoid - Export Control Restrictions. Documentation for MCNP 6.2 is public in report: - LA-UR-17-29981. - All features are based on MCNP 6.2, and may cause other versions of MCNP to break. The version is a tuple of major, minor, revision. 6.2.0 would be represented as (6, 2, 0) - :rtype: tuple + Returns + ------- + tuple """ return self._mcnp_version @mcnp_version.setter def mcnp_version(self, version): """ - :param version: the version tuple. Must be greater than 6.2.0 - :type version: tuple + Parameters + ---------- + version : tuple + the version tuple. Must be greater than 6.2.0 """ if version < (5, 1, 60): raise ValueError(f"The mcnp_version {version} is not supported by MontePy") @@ -207,11 +226,13 @@ def mcnp_version(self, version): @property def surfaces(self): - """ - A collection of the Surface objects in this problem. + """A collection of the Surface objects in this problem. - :return: a collection of the Surface objects, ordered by the order they were in the input file. - :rtype: Surfaces + Returns + ------- + Surfaces + a collection of the Surface objects, ordered by the order + they were in the input file. """ self.__relink_objs() return self._surfaces @@ -227,11 +248,13 @@ def surfaces(self, surfs): @property def materials(self): - """ - A collection of the Material objects in this problem. + """A collection of the Material objects in this problem. - :return: a colection of the Material objects, ordered by the order they were in the input file. - :rtype: Materials + Returns + ------- + Materials + a colection of the Material objects, ordered by the order + they were in the input file. """ self.__relink_objs() return self._materials @@ -247,91 +270,104 @@ def materials(self, mats): @property def print_in_data_block(self): - """ - Controls whether or not the specific input gets printed in the cell block or the data block. + """Controls whether or not the specific input gets printed in the cell block or the data block. This acts like a dictionary. The key is the case insensitive name of the card. For example to enable printing importance data in the data block run: ``problem.print_in_data_block["Imp"] = True`` - :rtype: bool + Returns + ------- + bool """ return self._print_in_data_block @property def data_inputs(self): - """ - A list of the DataInput objects in this problem. + """A list of the DataInput objects in this problem. - :return: a list of the :class:`~montepy.data_cards.data_card.DataCardAbstract` objects, ordered by the order they were in the input file. - :rtype: list + Returns + ------- + list + a list of the + :class:`~montepy.data_cards.data_card.DataCardAbstract` + objects, ordered by the order they were in the input file. """ self.__relink_objs() return self._data_inputs @property def input_file(self): - """ - The file name of the original file name this problem was read from. + """The file name of the original file name this problem was read from. - :rtype: MCNP_InputFile + Returns + ------- + MCNP_InputFile """ return self._input_file @property def message(self): - """ - The Message object at the beginning of the problem if any. + """The Message object at the beginning of the problem if any. - :rtype: Message + Returns + ------- + Message """ return self._message @property def title(self): - """ - The Title object for the title. + """The Title object for the title. - :rtype: Title + Returns + ------- + Title """ return self._title @title.setter def title(self, title): """ - :type title: The str for the title to be set to. + Parameters + ---------- + title : The str for the title to be set to. """ self._title = mcnp_input.Title([title], title) @property def universes(self): - """ - The Universes object holding all problem universes. + """The Universes object holding all problem universes. - :returns: a collection of universes in the problem. - :rtype: Universes + Returns + ------- + Universes + a collection of universes in the problem. """ return self._universes @property def transforms(self): - """ - The collection of transform objects in this problem. + """The collection of transform objects in this problem. - :returns: a collection of transforms in the problem. - :rtype: Transforms + Returns + ------- + Transforms + a collection of transforms in the problem. """ return self._transforms def parse_input(self, check_input=False, replace=True): - """ - Semantically parses the MCNP file provided to the constructor. + """Semantically parses the MCNP file provided to the constructor. - :param check_input: If true, will try to find all errors with input and collect them as warnings to log. - :type check_input: bool - :param replace: replace all non-ASCII characters with a space (0x20) - :type replace: bool + Parameters + ---------- + check_input : bool + If true, will try to find all errors with input and collect + them as warnings to log. + replace : bool + replace all non-ASCII characters with a space (0x20) """ trailing_comment = None last_obj = None @@ -405,8 +441,11 @@ def parse_input(self, check_input=False, replace=True): def __update_internal_pointers(self, check_input=False): """Updates the internal pointers between objects - :param check_input: If true, will try to find all errors with input and collect them as warnings to log. - :type check_input: bool + Parameters + ---------- + check_input : bool + If true, will try to find all errors with input and collect + them as warnings to log. """ def handle_error(e): @@ -427,11 +466,7 @@ def handle_error(e): for surface in self._surfaces: try: surface.update_pointers(self.surfaces, self._data_inputs) - except ( - BrokenObjectLinkError, - ParticleTypeNotInProblem, - ParticleTypeNotInCell, - ) as e: + except (BrokenObjectLinkError,) as e: handle_error(e) to_delete = [] for data_index, data_input in enumerate(self._data_inputs): @@ -441,8 +476,6 @@ def handle_error(e): except ( BrokenObjectLinkError, MalformedInputError, - ParticleTypeNotInProblem, - ParticleTypeNotInCell, ) as e: handle_error(e) continue @@ -452,8 +485,11 @@ def handle_error(e): def remove_duplicate_surfaces(self, tolerance): """Finds duplicate surfaces in the problem, and remove them. - :param tolerance: The amount of relative error to consider two surfaces identical - :type tolerance: float + Parameters + ---------- + tolerance : float + The amount of relative error to consider two surfaces + identical """ to_delete = montepy.surface_collection.Surfaces() matching_map = {} @@ -471,15 +507,16 @@ def remove_duplicate_surfaces(self, tolerance): self._surfaces.remove(surface) def add_cell_children_to_problem(self): # pragma: no cover - """ - Adds the surfaces, materials, and transforms of all cells in this problem to this problem to the + """Adds the surfaces, materials, and transforms of all cells in this problem to this problem to the internal lists to allow them to be written to file. .. deprecated:: 1.0.0 This function is no longer needed. When cells are added to problem.cells these children are added as well. - :raises DeprecationWarning: + Raises + ------ + DeprecationWarning """ raise DeprecationWarning( "add_cell_children_to_problem has been removed," @@ -487,13 +524,14 @@ def add_cell_children_to_problem(self): # pragma: no cover ) def write_problem(self, destination, overwrite=False): - """ - Write the problem to a file or writeable object. + """Write the problem to a file or writeable object. - :param destination: File path or writable object - :type destination: io.TextIOBase, str, os.PathLike - :param overwrite: Whether to overwrite 'destination' if it is an existing file - :type overwrite: bool + Parameters + ---------- + destination : io.TextIOBase, str, os.PathLike + File path or writable object + overwrite : bool + Whether to overwrite 'destination' if it is an existing file """ if hasattr(destination, "write") and callable(getattr(destination, "write")): new_file = MCNP_InputFile.from_open_stream(destination) @@ -508,28 +546,36 @@ def write_problem(self, destination, overwrite=False): ) def write_to_file(self, file_path, overwrite=False): - """ - Writes the problem to a file. + """Writes the problem to a file. .. versionchanged:: 0.3.0 The overwrite parameter was added. - :param file_path: the file path to write this problem to - :type file_path: str, os.PathLike - :param overwrite: Whether to overwrite the file at 'new_problem' if it exists - :type overwrite: bool - :raises IllegalState: if an object in the problem has not been fully initialized. - :raises FileExistsError: if a file already exists with the same path. - :raises IsADirectoryError: if the path given is actually a directory. + Parameters + ---------- + file_path : str, os.PathLike + the file path to write this problem to + overwrite : bool + Whether to overwrite the file at 'new_problem' if it exists + + Raises + ------ + IllegalState + if an object in the problem has not been fully initialized. + FileExistsError + if a file already exists with the same path. + IsADirectoryError + if the path given is actually a directory. """ return self.write_problem(file_path, overwrite) def _write_to_stream(self, inp): - """ - Writes the problem to a writeable stream. + """Writes the problem to a writeable stream. - :param inp: Writable input file - :type inp: MCNP_InputFile + Parameters + ---------- + inp : MCNP_InputFile + Writable input file """ with warnings.catch_warnings(record=True) as warning_catch: objects_list = [] @@ -593,8 +639,7 @@ class WarningLevels(Enum): warnings.warn(warning, stacklevel=3) def __load_data_inputs_to_object(self, data_inputs): - """ - Loads data input into their appropriate problem attribute. + """Loads data input into their appropriate problem attribute. Problem-level input should be loaded this way like: mode and kcode. """ @@ -625,8 +670,7 @@ def __repr__(self): 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. + """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: @@ -641,18 +685,29 @@ def parse(self, input: str, append: bool = True) -> montepy.mcnp_object.MCNP_Obj #. 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 + Parameters + ---------- + input : str + the string describing the input. New lines are allowed but + this does not need to meet MCNP line length rules. + append : bool + Whether to append this parsed object to this problem. + + Returns + ------- + MCNP_Object + the parsed object. + + Raises + ------ + TypeError + If a str is not given + ParsingError + If this is not a valid input. + BrokenObjectLinkError + if the dependent objects are not already in the problem. + NumberConflictError + if the object's number is already taken """ try: obj = montepy.parse_data(input) diff --git a/montepy/numbered_mcnp_object.py b/montepy/numbered_mcnp_object.py index 31c14d82..6aa6435a 100644 --- a/montepy/numbered_mcnp_object.py +++ b/montepy/numbered_mcnp_object.py @@ -1,12 +1,11 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations from abc import abstractmethod import copy import itertools from typing import Union +from numbers import Integral - -from montepy.errors import NumberConflictError from montepy.mcnp_object import MCNP_Object, InitInput import montepy from montepy.utilities import * @@ -34,19 +33,20 @@ def _number_validator(self, number): class Numbered_MCNP_Object(MCNP_Object): - """ - An abstract class to represent an mcnp object that has a number. + """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 + Parameters + ---------- + input : Union[Input, str] + The Input syntax object this will wrap and parse. + parser : MCNP_Parser + The parser object to parse the input with. + number : int + The number to set for this object. """ def __init__( @@ -61,7 +61,7 @@ def __init__( def _load_init_num(self, number): if number is not None: - if not isinstance(number, int): + if not isinstance(number, Integral): raise TypeError( f"Number must be an int. {number} of type {type(number)} given." ) @@ -70,31 +70,31 @@ def _load_init_num(self, number): self.number = number _CHILD_OBJ_MAP = {} - """ - """ + """""" - @make_prop_val_node("_number", int, validator=_number_validator) + @make_prop_val_node("_number", Integral, validator=_number_validator) def number(self): - """ - The current number of the object that will be written out to a new input. + """The current number of the object that will be written out to a new input. - :rtype: int + Returns + ------- + int """ pass @property @abstractmethod def old_number(self): - """ - The original number of the object provided in the input file + """The original number of the object provided in the input file - :rtype: int + Returns + ------- + int """ pass def _add_children_objs(self, problem): - """ - Adds all children objects from self to the given problem. + """Adds all children objects from self to the given problem. This is called from an append_hook in `NumberedObjectCollection`. """ @@ -123,34 +123,39 @@ def _add_children_objs(self, problem): prob_collect.append(child_collect) def clone(self, starting_number=None, step=None): - """ - Create a new independent instance of this object with a new number. + """Create a new independent instance of this object with a new number. This relies mostly on ``copy.deepcopy``. - .. note :: - If starting_number, or step are not specified - :func:`~montepy.numbered_object_collection.NumberedObjectCollection.starting_number`, - and :func:`~montepy.numbered_object_collection.NumberedObjectCollection.step` are used as default values, - if this object is tied to a problem. - For instance a ``Material`` will use ``problem.materials`` default information. - Otherwise ``1`` will be used as default values + Notes + ----- + If starting_number, or step are not specified + :func:`~montepy.numbered_object_collection.NumberedObjectCollection.starting_number`, + and :func:`~montepy.numbered_object_collection.NumberedObjectCollection.step` are used as default values, + if this object is tied to a problem. + For instance a ``Material`` will use ``problem.materials`` default information. + Otherwise ``1`` will be used as default values - .. versionadded:: 0.5.0 - :param starting_number: The starting number to request for a new object number. - :type starting_number: int - :param step: the step size to use to find a new valid number. - :type step: int - :returns: a cloned copy of this object. - :rtype: type(self) + .. versionadded:: 0.5.0 + Parameters + ---------- + starting_number : int + The starting number to request for a new object number. + step : int + the step size to use to find a new valid number. + + Returns + ------- + type(self) + a cloned copy of this object. """ - if not isinstance(starting_number, (int, type(None))): + if not isinstance(starting_number, (Integral, type(None))): raise TypeError( f"Starting_number must be an int. {type(starting_number)} given." ) - if not isinstance(step, (int, type(None))): + if not isinstance(step, (Integral, type(None))): raise TypeError(f"step must be an int. {type(step)} given.") if starting_number is not None and starting_number <= 0: raise ValueError(f"starting_number must be >= 1. {starting_number} given.") diff --git a/montepy/numbered_object_collection.py b/montepy/numbered_object_collection.py index fc7b4ab6..7212d35b 100644 --- a/montepy/numbered_object_collection.py +++ b/montepy/numbered_object_collection.py @@ -1,9 +1,10 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABC import itertools as it import typing import weakref +from numbers import Integral import montepy from montepy.numbered_mcnp_object import Numbered_MCNP_Object @@ -135,12 +136,14 @@ class NumberedObjectCollection(ABC): :func:`intersection`, :func:`isdisjoint`, :func:`issubset`, :func:`issuperset`, :func:`symmetric_difference`, :func:`symmetric_difference_update`, :func:`union`, :func:`discard`, and :func:`update`. - :param obj_class: the class of numbered objects being collected - :type obj_class: type - :param objects: the list of cells to start with if needed - :type objects: list - :param problem: the problem to link this collection to. - :type problem: MCNP_Problem + Parameters + ---------- + obj_class : type + the class of numbered objects being collected + objects : list + the list of cells to start with if needed + problem : MCNP_Problem + the problem to link this collection to. """ def __init__( @@ -181,8 +184,10 @@ def link_to_problem(self, problem): This is done so that cards can find links to other objects. - :param problem: The problem to link this card to. - :type problem: MCNP_Problem + Parameters + ---------- + problem : MCNP_Problem + The problem to link this card to. """ if not isinstance(problem, (montepy.mcnp_problem.MCNP_Problem, type(None))): raise TypeError("problem must be an MCNP_Problem") @@ -212,10 +217,11 @@ def __setstate__(self, crunchy_data): @property def numbers(self): - """ - A generator of the numbers being used. + """A generator of the numbers being used. - :rtype: generator + Returns + ------- + generator """ for obj in self._objects: # update cache every time we go through all objects @@ -225,11 +231,17 @@ def numbers(self): def check_number(self, number): """Checks if the number is already in use, and if so raises an error. - :param number: The number to check. - :type number: int - :raises NumberConflictError: if this number is in use. + Parameters + ---------- + number : int + The number to check. + + Raises + ------ + NumberConflictError + if this number is in use. """ - if not isinstance(number, int): + if not isinstance(number, Integral): raise TypeError("The number must be an int") conflict = False # only can trust cache if being @@ -245,15 +257,16 @@ def check_number(self, number): ) def _update_number(self, old_num, new_num, obj): - """ - Updates the number associated with a specific object in the internal cache. - - :param old_num: the previous number the object had. - :type old_num: int - :param new_num: the number that is being set to. - :type new_num: int - :param obj: the object being updated. - :type obj: self._obj_class + """Updates the number associated with a specific object in the internal cache. + + Parameters + ---------- + old_num : int + the previous number the object had. + new_num : int + the number that is being set to. + obj : self._obj_class + the object being updated. """ # don't update numbers you don't own if self.__num_cache.get(old_num, None) is not obj: @@ -263,45 +276,53 @@ def _update_number(self, old_num, new_num, obj): @property def objects(self): - """ - Returns a shallow copy of the internal objects list. + """Returns a shallow copy of the internal objects list. The list object is a new instance, but the underlying objects are the same. - :rtype: list + Returns + ------- + list """ return self._objects[:] def pop(self, pos=-1): - """ - Pop the final items off of the collection + """Pop the final items off of the collection + + Parameters + ---------- + pos : int + The index of the element to pop from the internal list. - :param pos: The index of the element to pop from the internal list. - :type pos: int - :return: the final elements - :rtype: Numbered_MCNP_Object + Returns + ------- + Numbered_MCNP_Object + the final elements """ - if not isinstance(pos, int): + if not isinstance(pos, Integral): raise TypeError("The index for popping must be an int") obj = self._objects[pos] self.__internal_delete(obj) return obj def clear(self): - """ - Removes all objects from this collection. - """ + """Removes all objects from this collection.""" self._objects.clear() self.__num_cache.clear() def extend(self, other_list): - """ - Extends this collection with another list. + """Extends this collection with another list. + + Parameters + ---------- + other_list : list + the list of objects to add. - :param other_list: the list of objects to add. - :type other_list: list - :raises NumberConflictError: if these items conflict with existing elements. + Raises + ------ + NumberConflictError + if these items conflict with existing elements. """ if not isinstance(other_list, (list, type(self))): raise TypeError("The extending list must be a list") @@ -327,11 +348,12 @@ def extend(self, other_list): self.__internal_append(obj) def remove(self, delete): - """ - Removes the given object from the collection. + """Removes the given object from the collection. - :param delete: the object to delete - :type delete: Numbered_MCNP_Object + Parameters + ---------- + delete : Numbered_MCNP_Object + the object to delete """ if not isinstance(delete, self._obj_class): raise TypeError("") @@ -342,31 +364,36 @@ def remove(self, delete): raise KeyError(f"This object is not in this collection") def clone(self, starting_number=None, step=None): - """ - Create a new instance of this collection, with all new independent + """Create a new instance of this collection, with all new independent objects with new numbers. This relies mostly on ``copy.deepcopy``. - .. note :: - If starting_number, or step are not specified :func:`starting_number`, - and :func:`step` are used as default values. + Notes + ----- + If starting_number, or step are not specified :func:`starting_number`, + and :func:`step` are used as default values. + .. versionadded:: 0.5.0 - :param starting_number: The starting number to request for a new object numbers. - :type starting_number: int - :param step: the step size to use to find a new valid number. - :type step: int - :returns: a cloned copy of this object. - :rtype: type(self) + Parameters + ---------- + starting_number : int + The starting number to request for a new object numbers. + step : int + the step size to use to find a new valid number. + Returns + ------- + type(self) + a cloned copy of this object. """ - if not isinstance(starting_number, (int, type(None))): + if not isinstance(starting_number, (Integral, type(None))): raise TypeError( f"Starting_number must be an int. {type(starting_number)} given." ) - if not isinstance(step, (int, type(None))): + if not isinstance(step, (Integral, type(None))): raise TypeError(f"step must be an int. {type(step)} given.") if starting_number is not None and starting_number <= 0: raise ValueError(f"starting_number must be >= 1. {starting_number} given.") @@ -384,23 +411,25 @@ def clone(self, starting_number=None, step=None): starting_number = new_obj.number + step return type(self)(objs) - @make_prop_pointer("_start_num", int, validator=_enforce_positive) + @make_prop_pointer("_start_num", Integral, validator=_enforce_positive) def starting_number(self): - """ - The starting number to use when an object is cloned. + """The starting number to use when an object is cloned. - :returns: the starting number - :rtype: int + Returns + ------- + int + the starting number """ pass - @make_prop_pointer("_step", int, validator=_enforce_positive) + @make_prop_pointer("_step", Integral, validator=_enforce_positive) def step(self): - """ - The step size to use to find a valid number during cloning. + """The step size to use to find a valid number during cloning. - :returns: the step size - :rtype: int + Returns + ------- + int + the step size """ pass @@ -421,28 +450,27 @@ def __repr__(self): ) def _append_hook(self, obj, initial_load=False): - """ - A hook that is called every time append is called. - """ + """A hook that is called every time append is called.""" if initial_load: return if self._problem: obj._add_children_objs(self._problem) def _delete_hook(self, obj, **kwargs): - """ - A hook that is called every time delete is called. - """ + """A hook that is called every time delete is called.""" pass def __internal_append(self, obj, **kwargs): - """ - The internal append method. + """The internal append method. This should always be called rather than manually added. - :param obj: the obj to append - :param kwargs: keyword arguments passed through to the append_hook + Parameters + ---------- + obj + the obj to append + **kwargs + keyword arguments passed through to the append_hook """ if not isinstance(obj, self._obj_class): raise TypeError( @@ -466,8 +494,7 @@ def __internal_append(self, obj, **kwargs): obj.link_to_problem(self._problem) def __internal_delete(self, obj, **kwargs): - """ - The internal delete method. + """The internal delete method. This should always be called rather than manually added. """ @@ -476,35 +503,47 @@ def __internal_delete(self, obj, **kwargs): self._delete_hook(obj, **kwargs) def add(self, obj: Numbered_MCNP_Object): - """ - Add the given object to this collection. + """Add the given object to this collection. - :param obj: The object to add. - :type obj: Numbered_MCNP_Object + Parameters + ---------- + obj : Numbered_MCNP_Object + The object to add. - :raises TypeError: if the object is of the wrong type. - :raises NumberConflictError: if this object's number is already in use in the collection. + Raises + ------ + TypeError + if the object is of the wrong type. + NumberConflictError + if this object's number is already in use in the collection. """ self.__internal_append(obj) def update(self, *objs: typing.Self): - """ - Add the given objects to this collection. + """Add the given objects to this collection. + Notes + ----- - .. note:: + This is not a thread-safe method. - This is not a thread-safe method. .. versionchanged:: 1.0.0 Changed to be more set like. Accepts multiple arguments. If there is a number conflict, the current object will be kept. - :param objs: The objects to add. - :type objs: list[Numbered_MCNP_Object] - :raises TypeError: if the object is of the wrong type. - :raises NumberConflictError: if this object's number is already in use in the collection. + Parameters + ---------- + *objs : list[Numbered_MCNP_Object] + The objects to add. + + Raises + ------ + TypeError + if the object is of the wrong type. + NumberConflictError + if this object's number is already in use in the collection. """ try: iter(objs) @@ -525,10 +564,17 @@ def update(self, *objs: typing.Self): def append(self, obj, **kwargs): """Appends the given object to the end of this collection. - :param obj: the object to add. - :type obj: Numbered_MCNP_Object - :param kwargs: extra arguments that are used internally. - :raises NumberConflictError: if this object has a number that is already in use. + Parameters + ---------- + obj : Numbered_MCNP_Object + the object to add. + **kwargs + extra arguments that are used internally. + + Raises + ------ + NumberConflictError + if this object has a number that is already in use. """ if not isinstance(obj, self._obj_class): raise TypeError(f"object being appended must be of type: {self._obj_class}") @@ -541,16 +587,21 @@ def append_renumber(self, obj, step=1): be renumbered to an available number. The number will be incremented by step until an available number is found. - :param obj: The MCNP object being added to the collection. - :type obj: Numbered_MCNP_Object - :param step: the incrementing step to use to find a new number. - :type step: int - :return: the number for the object. - :rtype: int + Parameters + ---------- + obj : Numbered_MCNP_Object + The MCNP object being added to the collection. + step : int + the incrementing step to use to find a new number. + + Returns + ------- + int + the number for the object. """ if not isinstance(obj, self._obj_class): raise TypeError(f"object being appended must be of type: {self._obj_class}") - if not isinstance(step, int): + if not isinstance(step, Integral): raise TypeError("The step number must be an int") number = obj.number if self._problem: @@ -571,23 +622,30 @@ def request_number(self, start_num=None, step=None): should be immediately added to avoid possible collisions caused by shifting numbers of other objects in the collection. - .. note :: - If starting_number, or step are not specified :func:`starting_number`, - and :func:`step` are used as default values. + Notes + ----- + If starting_number, or step are not specified :func:`starting_number`, + and :func:`step` are used as default values. + .. versionchanged:: 0.5.0 In 0.5.0 the default values were changed to reference :func:`starting_number` and :func:`step`. - :param start_num: the starting number to check. - :type start_num: int - :param step: the increment to jump by to find new numbers. - :type step: int - :returns: an available number - :rtype: int + Parameters + ---------- + start_num : int + the starting number to check. + step : int + the increment to jump by to find new numbers. + + Returns + ------- + int + an available number """ - if not isinstance(start_num, (int, type(None))): + if not isinstance(start_num, (Integral, type(None))): raise TypeError("start_num must be an int") - if not isinstance(step, (int, type(None))): + if not isinstance(step, (Integral, type(None))): raise TypeError("step must be an int") if start_num is None: start_num = self.starting_number @@ -608,10 +666,12 @@ def next_number(self, step=1): This works by finding the current maximum number, and then adding the stepsize to it. - :param step: how much to increase the last number by - :type step: int + Parameters + ---------- + step : int + how much to increase the last number by """ - if not isinstance(step, int): + if not isinstance(step, Integral): raise TypeError("step must be an int") if step <= 0: raise ValueError("step must be > 0") @@ -629,7 +689,9 @@ def __get_slice(self, i: slice): Because MCNP numbered objects start at 1, so do the indices. They are effectively 1-based and endpoint-inclusive. - :rtype: NumberedObjectCollection + Returns + ------- + NumberedObjectCollection """ rstep = i.step if i.step is not None else 1 rstart = i.start @@ -657,7 +719,7 @@ def __get_slice(self, i: slice): def __getitem__(self, i): if isinstance(i, slice): return self.__get_slice(i) - elif not isinstance(i, int): + elif not isinstance(i, Integral): raise TypeError("index must be an int or slice") ret = self.get(i) if ret is None: @@ -665,13 +727,13 @@ def __getitem__(self, i): return ret def __delitem__(self, idx): - if not isinstance(idx, int): + if not isinstance(idx, Integral): raise TypeError("index must be an int") obj = self[idx] self.__internal_delete(obj) def __setitem__(self, key, newvalue): - if not isinstance(key, int): + if not isinstance(key, Integral): raise TypeError("index must be an int") self.append(newvalue) @@ -696,8 +758,7 @@ def __contains__(self, other): return other in self._objects def __set_logic(self, other, operator): - """ - Takes another collection, and apply the operator to it, and returns a new instance. + """Takes another collection, and apply the operator to it, and returns a new instance. Operator must be a callable that accepts a set of the numbers of self, and another set for other's numbers. @@ -754,8 +815,7 @@ def __ixor__(self, other): return self def __set_logic_test(self, other, operator): - """ - Takes another collection, and apply the operator to it, testing the logic of it. + """Takes another collection, and apply the operator to it, testing the logic of it. Operator must be a callable that accepts a set of the numbers of self, and another set for other's numbers. @@ -781,45 +841,57 @@ def __gt__(self, other): return self.__set_logic_test(other, lambda a, b: a > b) def issubset(self, other: typing.Self): - """ - Test whether every element in the collection is in other. + """Test whether every element in the collection is in other. ``collection <= other`` .. versionadded:: 1.0.0 - :param other: the set to compare to. - :type other: Self - :rtype: bool + Parameters + ---------- + other : Self + the set to compare to. + + Returns + ------- + bool """ return self.__set_logic_test(other, lambda a, b: a.issubset(b)) def isdisjoint(self, other: typing.Self): - """ - Test if there are no elements in common between the collection, and other. + """Test if there are no elements in common between the collection, and other. Collections are disjoint if and only if their intersection is the empty set. .. versionadded:: 1.0.0 - :param other: the set to compare to. - :type other: Self - :rtype: bool + Parameters + ---------- + other : Self + the set to compare to. + + Returns + ------- + bool """ return self.__set_logic_test(other, lambda a, b: a.isdisjoint(b)) def issuperset(self, other: typing.Self): - """ - Test whether every element in other is in the collection. + """Test whether every element in other is in the collection. ``collection >= other`` .. versionadded:: 1.0.0 - :param other: the set to compare to. - :type other: Self - :rtype: bool + Parameters + ---------- + other : Self + the set to compare to. + + Returns + ------- + bool """ return self.__set_logic_test(other, lambda a, b: a.issuperset(b)) @@ -841,29 +913,34 @@ def __set_logic_multi(self, others, operator): return type(self)(list(objs.values())) def intersection(self, *others: typing.Self): - """ - Return a new collection with all elements in common in collection, and all others. + """Return a new collection with all elements in common in collection, and all others. ``collection & other & ...`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self - :rtype: typing.Self + Parameters + ---------- + *others : Self + the other collections to compare to. + + Returns + ------- + typing.Self """ return self.__set_logic_multi(others, lambda a, *b: a.intersection(*b)) def intersection_update(self, *others: typing.Self): - """ - Update the collection keeping all elements in common in collection, and all others. + """Update the collection keeping all elements in common in collection, and all others. ``collection &= other & ...`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self + Parameters + ---------- + *others : Self + the other collections to compare to. """ if len(others) == 1: self &= others[0] @@ -872,43 +949,52 @@ def intersection_update(self, *others: typing.Self): self &= other def union(self, *others: typing.Self): - """ - Return a new collection with all elements from collection, and all others. + """Return a new collection with all elements from collection, and all others. ``collection | other | ...`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self - :rtype: typing.Self + Parameters + ---------- + *others : Self + the other collections to compare to. + + Returns + ------- + typing.Self """ return self.__set_logic_multi(others, lambda a, *b: a.union(*b)) def difference(self, *others: typing.Self): - """ - Return a new collection with elements from collection, that are not in the others. + """Return a new collection with elements from collection, that are not in the others. ``collection - other - ...`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self - :rtype: typing.Self + Parameters + ---------- + *others : Self + the other collections to compare to. + + Returns + ------- + typing.Self """ return self.__set_logic_multi(others, lambda a, *b: a.difference(*b)) def difference_update(self, *others: typing.Self): - """ - Update the new collection removing all elements from others. + """Update the new collection removing all elements from others. ``collection -= other | ...`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self + Parameters + ---------- + *others : Self + the other collections to compare to. """ new_vals = self.difference(*others) self.clear() @@ -916,41 +1002,47 @@ def difference_update(self, *others: typing.Self): return self def symmetric_difference(self, other: typing.Self): - """ - Return a new collection with elements in either the collection or the other, but not both. + """Return a new collection with elements in either the collection or the other, but not both. ``collection ^ other`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self - :rtype: typing.Self + Parameters + ---------- + others : Self + the other collections to compare to. + + Returns + ------- + typing.Self """ return self ^ other def symmetric_difference_update(self, other: typing.Self): - """ - Update the collection, keeping only elements found in either collection, but not in both. + """Update the collection, keeping only elements found in either collection, but not in both. ``collection ^= other`` .. versionadded:: 1.0.0 - :param others: the other collections to compare to. - :type others: Self + Parameters + ---------- + others : Self + the other collections to compare to. """ self ^= other return self def discard(self, obj: montepy.numbered_mcnp_object.Numbered_MCNP_Object): - """ - Remove the object from the collection if it is present. + """Remove the object from the collection if it is present. .. versionadded:: 1.0.0 - :param obj: the object to remove. - :type obj: Numbered_MCNP_Object + Parameters + ---------- + obj : Numbered_MCNP_Object + the object to remove. """ try: self.remove(obj) @@ -958,15 +1050,19 @@ def discard(self, obj: montepy.numbered_mcnp_object.Numbered_MCNP_Object): pass def get(self, i: int, default=None) -> (Numbered_MCNP_Object, None): - """ - Get ``i`` if possible, or else return ``default``. + """Get ``i`` if possible, or else return ``default``. - :param i: number of the object to get, not it's location in the internal list - :type i: int - :param default: value to return if not found - :type default: object + Parameters + ---------- + i : int + number of the object to get, not it's location in the + internal list + default : object + value to return if not found - :rtype: Numbered_MCNP_Object + Returns + ------- + Numbered_MCNP_Object """ try: ret = self.__num_cache[i] @@ -982,10 +1078,11 @@ def get(self, i: int, default=None) -> (Numbered_MCNP_Object, None): return default def keys(self) -> typing.Generator[int, None, None]: - """ - Get iterator of the collection's numbers. + """Get iterator of the collection's numbers. - :rtype: int + Returns + ------- + int """ if len(self) == 0: yield from [] @@ -994,10 +1091,11 @@ def keys(self) -> typing.Generator[int, None, None]: yield o.number def values(self) -> typing.Generator[Numbered_MCNP_Object, None, None]: - """ - Get iterator of the collection's objects. + """Get iterator of the collection's objects. - :rtype: Numbered_MCNP_Object + Returns + ------- + Numbered_MCNP_Object """ for o in self._objects: self.__num_cache[o.number] = o @@ -1006,10 +1104,11 @@ def values(self) -> typing.Generator[Numbered_MCNP_Object, None, None]: def items( self, ) -> typing.Generator[typing.Tuple[int, Numbered_MCNP_Object], None, None]: - """ - Get iterator of the collections (number, object) pairs. + """Get iterator of the collections (number, object) pairs. - :rtype: tuple(int, MCNP_Object) + Returns + ------- + tuple(int, MCNP_Object) """ for o in self._objects: yield o.number, o @@ -1044,11 +1143,18 @@ def __init__(self, obj_class, objects=None, problem=None): def _append_hook(self, obj, insert_in_data=True): """Appends the given object to the end of this collection. - :param obj: the object to add. - :type obj: Numbered_MCNP_Object - :param insert_in_data: Whether to add the object to the linked problem's data_inputs. - :type insert_in_data: bool - :raises NumberConflictError: if this object has a number that is already in use. + Parameters + ---------- + obj : Numbered_MCNP_Object + the object to add. + insert_in_data : bool + Whether to add the object to the linked problem's + data_inputs. + + Raises + ------ + NumberConflictError + if this object has a number that is already in use. """ if self._problem: if self._last_index: @@ -1069,9 +1175,7 @@ def _delete_hook(self, obj): self._problem.data_inputs.remove(obj) def clear(self): - """ - Removes all objects from this collection. - """ + """Removes all objects from this collection.""" if self._problem: for obj in self._objects: self._problem.data_inputs.remove(obj) diff --git a/montepy/particle.py b/montepy/particle.py index 2c9b6ca7..ecdc9a96 100644 --- a/montepy/particle.py +++ b/montepy/particle.py @@ -4,8 +4,7 @@ @unique class Particle(str, Enum): - """ - Supported MCNP supported particles. + """Supported MCNP supported particles. Taken from :manual62:`46`. """ @@ -63,8 +62,7 @@ def __hash__(self): @unique class LibraryType(str, Enum): - """ - Enum to represent the possible types that a nuclear data library can be. + """Enum to represent the possible types that a nuclear data library can be. .. versionadded:: 1.0.0 diff --git a/montepy/surface_collection.py b/montepy/surface_collection.py index 9c05ab83..db8817df 100644 --- a/montepy/surface_collection.py +++ b/montepy/surface_collection.py @@ -40,13 +40,15 @@ class Surfaces(NumberedObjectCollection): for surface in problem.surfaces.pz: surface.location += 10 - .. note:: + Notes + ----- - For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. + For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. - - :param surfaces: the list of surfaces to start with if needed - :type surfaces: list + Parameters + ---------- + surfaces : list + the list of surfaces to start with if needed """ def __init__(self, surfaces: list = None, problem: montepy.MCNP_Problem = None): diff --git a/montepy/surfaces/axis_plane.py b/montepy/surfaces/axis_plane.py index 5c9edb10..bc4fcd3f 100644 --- a/montepy/surfaces/axis_plane.py +++ b/montepy/surfaces/axis_plane.py @@ -7,17 +7,18 @@ class AxisPlane(Surface): - """ - Represents PX, PY, PZ + """Represents PX, PY, PZ .. 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 + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input + number : int + The number to set for this object. """ COORDINATE = {SurfaceType.PX: "x", SurfaceType.PY: "y", SurfaceType.PZ: "z"} @@ -37,10 +38,11 @@ def __init__(self, input: InitInput = None, number: int = None): @make_prop_val_node("_location", (float, int), float) def location(self): - """ - The location of the plane in space. + """The location of the plane in space. - :rtype: float + Returns + ------- + float """ pass diff --git a/montepy/surfaces/cylinder_on_axis.py b/montepy/surfaces/cylinder_on_axis.py index 93d99f65..037b9303 100644 --- a/montepy/surfaces/cylinder_on_axis.py +++ b/montepy/surfaces/cylinder_on_axis.py @@ -11,18 +11,18 @@ def _enforce_positive_radius(self, value): class CylinderOnAxis(Surface): - """ - Represents surfaces: CX, CY, CZ + """Represents surfaces: CX, CY, CZ .. 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 + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input + number : int + The number to set for this object. """ def __init__(self, input: InitInput = None, number: int = None): @@ -42,10 +42,11 @@ def __init__(self, input: InitInput = None, number: int = None): "_radius", (float, int), float, validator=_enforce_positive_radius ) def radius(self): - """ - The radius of the cylinder + """The radius of the cylinder - :rtype: float + Returns + ------- + float """ pass diff --git a/montepy/surfaces/cylinder_par_axis.py b/montepy/surfaces/cylinder_par_axis.py index 3ada7c58..be0870de 100644 --- a/montepy/surfaces/cylinder_par_axis.py +++ b/montepy/surfaces/cylinder_par_axis.py @@ -1,9 +1,11 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from .surface_type import SurfaceType from .surface import Surface, InitInput from montepy.errors import * from montepy.utilities import * +from numbers import Real + def _enforce_positive_radius(self, value): if value < 0.0: @@ -11,17 +13,18 @@ def _enforce_positive_radius(self, value): class CylinderParAxis(Surface): - """ - Represents surfaces: C/X, C/Y, C/Z + """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: Union[Input, str] - :param number: The number to set for this object. - :type number: int + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input + number : int + The number to set for this object. """ COORDINATE_PAIRS = { @@ -29,8 +32,7 @@ class CylinderParAxis(Surface): SurfaceType.C_Y: {0: "x", 1: "z"}, SurfaceType.C_Z: {0: "x", 1: "y"}, } - """Which coordinate is what value for each cylinder type. - """ + """Which coordinate is what value for each cylinder type.""" def __init__(self, input: InitInput = None, number: int = None): self._coordinates = [ @@ -56,8 +58,7 @@ def __init__(self, input: InitInput = None, number: int = None): @property def coordinates(self): - """ - The two coordinates for this cylinder to center on. + """The two coordinates for this cylinder to center on. :rytpe: tuple """ @@ -70,19 +71,18 @@ def coordinates(self, coordinates): if len(coordinates) != 2: raise ValueError("coordinates must have exactly two elements") for val in coordinates: - if not isinstance(val, (float, int)): + if not isinstance(val, Real): raise TypeError(f"Coordinate must be a number. {val} given.") for i, val in enumerate(coordinates): self._coordinates[i].value = val - @make_prop_val_node( - "_radius", (float, int), float, validator=_enforce_positive_radius - ) + @make_prop_val_node("_radius", (Real,), float, validator=_enforce_positive_radius) def radius(self): - """ - The radius of the cylinder. + """The radius of the cylinder. - :rtype: float + Returns + ------- + float """ pass diff --git a/montepy/surfaces/general_plane.py b/montepy/surfaces/general_plane.py index 224ea782..39c689bb 100644 --- a/montepy/surfaces/general_plane.py +++ b/montepy/surfaces/general_plane.py @@ -8,19 +8,18 @@ class GeneralPlane(Surface): - """ - Represents P + """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 + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input + number : int + The number to set for this object. """ def __init__( diff --git a/montepy/surfaces/half_space.py b/montepy/surfaces/half_space.py index 68095684..96f74f1b 100644 --- a/montepy/surfaces/half_space.py +++ b/montepy/surfaces/half_space.py @@ -1,4 +1,4 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations import montepy from montepy.errors import * @@ -11,10 +11,11 @@ ) from montepy.utilities import * +from numbers import Integral + class HalfSpace: - """ - Class representing a geometry half_space. + """Class representing a geometry half_space. The term `half-spaces `_ in MontePy is used very loosely, and is not mathematically rigorous. In MontePy a divider is a something @@ -55,14 +56,16 @@ class HalfSpace: half_space = +bottom & (-left | +right) - :param left: The left side of the binary tree. - :type left: HalfSpace - :param operator: the operator to apply between the two branches. - :type operator: Operator - :param right: the right side of the binary tree. - :type right: HalfSpace - :param node: the node this was parsed from. - :type node: GeometryTree + Parameters + ---------- + left : HalfSpace + The left side of the binary tree. + operator : Operator + the operator to apply between the two branches. + right : HalfSpace + the right side of the binary tree. + node : GeometryTree + the node this was parsed from. """ def __init__(self, left, operator, right=None, node=None): @@ -84,57 +87,65 @@ def __init__(self, left, operator, right=None, node=None): @make_prop_pointer("_left", ()) def left(self): - """ - The left side of the binary tree of this half_space. + """The left side of the binary tree of this half_space. - :returns: the left side of the tree. - :rtype: HalfSpace + Returns + ------- + HalfSpace + the left side of the tree. """ pass @make_prop_pointer("_right", (), deletable=True) def right(self): - """ - The right side of the binary tree of this half_space if any. + """The right side of the binary tree of this half_space if any. - :returns: the right side of the tree. - :rtype: HalfSpace + Returns + ------- + HalfSpace + the right side of the tree. """ pass @make_prop_pointer("_operator", Operator) def operator(self): - """ - The operator for applying to this binary tree. + """The operator for applying to this binary tree. - :returns: the operator for the tree. - :rtype: Operator + Returns + ------- + Operator + the operator for the tree. """ pass @make_prop_pointer("_node") def node(self): - """ - The syntax node for this HalfSpace if any. + """The syntax node for this HalfSpace if any. If this was generated by :func:`parse_input_node`, that initial node will be given. If this was created from scratch, a new node will be generated prior as this is being written to file. - :returns: the node for this tree. - :rtype: GeometryTree + Returns + ------- + GeometryTree + the node for this tree. """ pass @staticmethod def parse_input_node(node): - """ - Parses the given syntax node as a half_space. + """Parses the given syntax node as a half_space. + + Parameters + ---------- + node : GeometryTree + the Input syntax node to parse. - :param node: the Input syntax node to parse. - :type node: GeometryTree - :returns: the HalfSpace properly representing the input geometry. - :rtype: HalfSpace + Returns + ------- + HalfSpace + the HalfSpace properly representing the input geometry. """ if not isinstance(node, GeometryTree): raise TypeError("Node must be a GeoemtryTree.") @@ -154,8 +165,7 @@ def parse_input_node(node): return HalfSpace(sides[0], node.operator, sides[1], node) def update_pointers(self, cells, surfaces, cell): - """ - Update pointers, and link this object to other objects in the problem. + """Update pointers, and link this object to other objects in the problem. This will: @@ -163,12 +173,14 @@ def update_pointers(self, cells, surfaces, cell): 2. Update the divider parameter to point to the relevant surface or cell. 3. Update the parent's :func:`~montepy.cell.Cell.surfaces`, and :func:`~montepy.cell.Cell.complements`. - :param cells: the cells in the problem. - :type cells: Cells - :param surfaces: The surfaces in the problem. - :type surfaces: Surfaces - :param cell: the cell this HalfSpace is tied to. - :type cell: Cell + Parameters + ---------- + cells : Cells + the cells in the problem. + surfaces : Surfaces + The surfaces in the problem. + cell : Cell + the cell this HalfSpace is tied to. """ self._cell = cell self.left.update_pointers(cells, surfaces, cell) @@ -176,11 +188,12 @@ def update_pointers(self, cells, surfaces, cell): self.right.update_pointers(cells, surfaces, cell) def _add_new_children_to_cell(self, other): - """ - Adds the cells and surfaces from a new tree to this parent cell. + """Adds the cells and surfaces from a new tree to this parent cell. - :param other: the other HalfSpace to work with. - :type other: HalfSpace + Parameters + ---------- + other : HalfSpace + the other HalfSpace to work with. """ if self._cell is None: return @@ -215,10 +228,13 @@ def remove_duplicate_surfaces( The form of the deleting_dict was changed as :class:`~montepy.surfaces.Surface` is no longer hashable. - :param deleting_dict: a dict of the surfaces to delete, mapping the old surface to the new surface to replace it. - The keys are the number of the old surface. The values are a tuple - of the old surface, and then the new surface. - :type deleting_dict: dict[int, tuple[Surface, Surface]] + Parameters + ---------- + deleting_dict : dict[int, tuple[Surface, Surface]] + a dict of the surfaces to delete, mapping the old surface to + the new surface to replace it. The keys are the number of + the old surface. The values are a tuple of the old surface, + and then the new surface. """ cells, surfaces = self._get_leaf_objects() new_deleting_dict = {} @@ -231,14 +247,15 @@ def remove_duplicate_surfaces( self.right.remove_duplicate_surfaces(new_deleting_dict) def _get_leaf_objects(self): - """ - Get all of the leaf objects for this tree. + """Get all of the leaf objects for this tree. The leaf objects are the surfaces and cells used as dividers for this HalfSpace. - :returns: a Tuple of two sets: cells, and surfaces. - :rtype: Tuple + Returns + ------- + Tuple + a Tuple of two sets: cells, and surfaces. """ cells, surfaces = self.left._get_leaf_objects() if self.right: @@ -257,9 +274,7 @@ def _update_values(self): self.right._update_values() def _ensure_has_nodes(self): - """ - Ensures this HalfSpace and its children has the necessary syntax nodes. - """ + """Ensures this HalfSpace and its children has the necessary syntax nodes.""" self._ensure_has_parens() self.left._ensure_has_nodes() if self.right is not None: @@ -299,8 +314,7 @@ def _ensure_has_nodes(self): self.node.nodes["right"] = self.right.node def _ensure_has_parens(self): - """ - Ensures that when a parentheses is needed it is added. + """Ensures that when a parentheses is needed it is added. This detects unions below an intersection. It then "adds" a parentheses by adding a GROUP to the tree. @@ -318,9 +332,7 @@ def _ensure_has_parens(self): self.right = HalfSpace(self.right, Operator.GROUP) def _update_node(self): - """ - Ensures that the syntax node properly reflects the current HalfSpace structure - """ + """Ensures that the syntax node properly reflects the current HalfSpace structure""" try: operator_node = self.node.nodes["operator"] output = operator_node.format() @@ -361,9 +373,7 @@ def _update_node(self): ) def __switch_operator(self, new_symbol): - """ - Updates the information about the operator in the syntax tree. - """ + """Updates the information about the operator in the syntax tree.""" operator_node = self.node.nodes["operator"] operator_node._nodes = [ ( @@ -474,12 +484,11 @@ def __repr__(self): class UnitHalfSpace(HalfSpace): - """ - The leaf node for the HalfSpace tree. - - This can only be used as leaves and represents one half_space of a + """The leaf node for the HalfSpace tree. + + 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. + The easiest way to generate one is with the divider with unary operators. For surfaces you can choose the positive (True) or negative (False) side quickly: .. code-block:: python @@ -488,13 +497,13 @@ class UnitHalfSpace(HalfSpace): top_half = +surf For a cell you can only take the complement: - + .. code-block:: python - + comp = ~cell .. Note:: - + When you complement a cell you don't actually get a UnitHalfSpace directly. You get a UnitHalfSpace wrapped with a complementing HalfSpace Tree @@ -506,14 +515,16 @@ class UnitHalfSpace(HalfSpace): | Cell - :param divider: the divider object - :type divider: int, Cell, Surface - :param side: which side the divider is on. For Cells this will be True. - :type side: bool - :param is_cell: Whether or not this is a cell or not for the divider. - :type is_cell: bool - :param node: the node if any this UnitHalfSpace was built from - :type node: ValueNode + Parameters + ---------- + divider : int, Cell, Surface + the divider object + side : bool + which side the divider is on. For Cells this will be True. + is_cell : bool + Whether or not this is a cell or not for the divider. + node : ValueNode + the node if any this UnitHalfSpace was built from """ def __init__(self, divider, side, is_cell, node=None): @@ -537,11 +548,12 @@ def __init__(self, divider, side, is_cell, node=None): @property def divider(self): - """ - The divider this UnitHalfSpace is based on. + """The divider this UnitHalfSpace is based on. - :returns: the divider defining this HalfSpace - :rtype: int, Cell, Surface + Returns + ------- + int, Cell, Surface + the divider defining this HalfSpace """ return self._divider @@ -563,11 +575,12 @@ def divider(self, div): @make_prop_pointer("_is_cell", bool) def is_cell(self): - """ - Whether or not the divider this uses is a cell. + """Whether or not the divider this uses is a cell. - :returns: True if this is a cell based HalfSpace - :rtype: bool + Returns + ------- + bool + True if this is a cell based HalfSpace """ pass @@ -578,7 +591,7 @@ def __str__(self): side = "+" else: side = "-" - if isinstance(self.divider, int): + if isinstance(self.divider, Integral): div = self.divider else: div = self.divider.number @@ -589,41 +602,47 @@ def __repr__(self): @property def node(self): - """ - The node that this UnitHalfSpace is based on if any. + """The node that this UnitHalfSpace is based on if any. - :returns: The ValueNode that this UnitHalfSpace is tied to. - :rtype: ValueNode + Returns + ------- + ValueNode + The ValueNode that this UnitHalfSpace is tied to. """ return self._node @make_prop_pointer("_side", bool) def side(self): - """ - Which side of the divider this HalfSpace is on. + """Which side of the divider this HalfSpace is on. This maps the conventional positive/negative half_spaces of MCNP to boolean values. True is the positive side, and False is the negative one. For cells this is always True as the Complementing logic is handled by the parent binary tree. - :returns: the side of the divider for the HalfSpace - :rtype: bool + Returns + ------- + bool + the side of the divider for the HalfSpace """ # make cells always "+" return self.is_cell or self._side @staticmethod def parse_input_node(node, is_cell=False): - """ - Parses the given syntax node as a UnitHalfSpace. - - :param node: the Input syntax node to parse. - :type node: ValueNode - :param is_cell: Whether or not this UnitHalfSpace represents a cell. - :type is_cell: bool - :returns: the HalfSpace properly representing the input geometry. - :rtype: UnitHalfSpace + """Parses the given syntax node as a UnitHalfSpace. + + Parameters + ---------- + node : ValueNode + the Input syntax node to parse. + is_cell : bool + Whether or not this UnitHalfSpace represents a cell. + + Returns + ------- + UnitHalfSpace + the HalfSpace properly representing the input geometry. """ if not isinstance(node, ValueNode): raise TypeError(f"Must be called on a ValueNode. {node} given.") @@ -643,7 +662,7 @@ def update_pointers(self, cells, surfaces, cell): if self._is_cell: container = cells par_container = self._cell.complements - if isinstance(self.divider, int): + if isinstance(self.divider, Integral): try: self._divider = container[self._divider] if self._divider not in par_container: @@ -659,7 +678,7 @@ def update_pointers(self, cells, surfaces, cell): def _ensure_has_nodes(self): if self.node is None: - if isinstance(self.divider, int): + if isinstance(self.divider, Integral): num = self.divider else: num = self.divider.number @@ -670,7 +689,7 @@ def _ensure_has_nodes(self): self._node = node def _update_node(self): - if isinstance(self.divider, int): + if isinstance(self.divider, Integral): self._node.value = self.divider else: self._node.value = self.divider.number @@ -714,14 +733,17 @@ def remove_duplicate_surfaces( The form of the deleting_dict was changed as :class:`~montepy.surfaces.Surface` is no longer hashable. - :param deleting_dict: a dict of the surfaces to delete, mapping the old surface to the new surface to replace it. - The keys are the number of the old surface. The values are a tuple - of the old surface, and then the new surface. - :type deleting_dict: dict[int, tuple[Surface, Surface]] + Parameters + ---------- + deleting_dict : dict[int, tuple[Surface, Surface]] + a dict of the surfaces to delete, mapping the old surface to + the new surface to replace it. The keys are the number of + the old surface. The values are a tuple of the old surface, + and then the new surface. """ def num(obj): - if isinstance(obj, int): + if isinstance(obj, Integral): return obj return obj.number diff --git a/montepy/surfaces/surface.py b/montepy/surfaces/surface.py index 856a0bb9..8df9621d 100644 --- a/montepy/surfaces/surface.py +++ b/montepy/surfaces/surface.py @@ -1,13 +1,12 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. from __future__ import annotations import copy -import re from typing import Union +from numbers import Real 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, InitInput from montepy.surfaces import half_space @@ -16,17 +15,18 @@ class Surface(Numbered_MCNP_Object): - """ - Object to hold a single MCNP surface + """Object to hold a single MCNP surface .. 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 + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input + number : int + The number to set for this object. """ _parser = SurfaceParser() @@ -98,21 +98,23 @@ def __init__( @make_prop_val_node("_surface_type", (SurfaceType, str), SurfaceType) def surface_type(self): - """ - The mnemonic for the type of surface. + """The mnemonic for the type of surface. E.g. CY, PX, etc. - :rtype: SurfaceType + Returns + ------- + SurfaceType """ pass @property def is_reflecting(self): - """ - If true this surface is a reflecting boundary. + """If true this surface is a reflecting boundary. - :rtype: bool + Returns + ------- + bool """ return self._is_reflecting @@ -124,10 +126,11 @@ def is_reflecting(self, reflect): @property def is_white_boundary(self): - """ - If true this surface is a white boundary. + """If true this surface is a white boundary. - :rtype: bool + Returns + ------- + bool """ return self._is_white_boundary @@ -139,10 +142,11 @@ def is_white_boundary(self, white): @property def surface_constants(self): - """ - The constants defining the surface + """The constants defining the surface - :rtype: list + Returns + ------- + list """ ret = [] for val in self._surface_constants: @@ -156,7 +160,7 @@ def surface_constants(self, constants): if len(constants) != len(self._surface_constants): raise ValueError(f"Cannot change the length of the surface constants.") for constant in constants: - if not isinstance(constant, float): + if not isinstance(constant, Real): raise TypeError( f"The surface constant provided: {constant} must be a float" ) @@ -165,55 +169,61 @@ def surface_constants(self, constants): @make_prop_val_node("_old_transform_number") def old_transform_number(self): - """ - The transformation number for this surface in the original file. + """The transformation number for this surface in the original file. - :rtype: int + Returns + ------- + int """ pass @make_prop_val_node("_old_periodic_surface") def old_periodic_surface(self): - """ - The surface number this is periodic with reference to in the original file. + """The surface number this is periodic with reference to in the original file. - :rtype: int + Returns + ------- + int """ pass @make_prop_pointer("_periodic_surface", types=(), deletable=True) def periodic_surface(self): - """ - The surface that this surface is periodic with respect to + """The surface that this surface is periodic with respect to - :rtype: Surface + Returns + ------- + Surface """ pass @make_prop_pointer("_transform", transform.Transform, deletable=True) def transform(self): - """ - The Transform object that translates this surface + """The Transform object that translates this surface - :rtype: Transform + Returns + ------- + Transform """ pass @make_prop_val_node("_old_number") def old_number(self): - """ - The surface number that was used in the read file + """The surface number that was used in the read file - :rtype: int + Returns + ------- + int """ pass @property def cells(self): - """ - A generator of Cells that use this surface. + """A generator of Cells that use this surface. - :rtype: generator + Returns + ------- + generator """ if self._problem: for cell in self._problem.cells: @@ -232,16 +242,17 @@ def __repr__(self): ) def update_pointers(self, surfaces, data_inputs): - """ - Updates the internal pointers to the appropriate objects. + """Updates the internal pointers to the appropriate objects. Right now only periodic surface links will be made. Eventually transform pointers should be made. - :param surfaces: A Surfaces collection of the surfaces in the problem. - :type surfaces: Surfaces - :param data_cards: the data_cards in the problem. - :type data_cards: list + Parameters + ---------- + surfaces : Surfaces + A Surfaces collection of the surfaces in the problem. + data_cards : list + the data_cards in the problem. """ if self.old_periodic_surface: try: @@ -307,13 +318,17 @@ def __ne__(self, other): def find_duplicate_surfaces(self, surfaces, tolerance): """Finds all surfaces that are effectively the same as this one. - :param surfaces: a list of the surfaces to compare against this one. - :type surfaces: list - :param tolerance: the amount of relative error to allow - :type tolerance: float - - :returns: A list of the surfaces that are identical - :rtype: list + Parameters + ---------- + surfaces : list + a list of the surfaces to compare against this one. + tolerance : float + the amount of relative error to allow + + Returns + ------- + list + A list of the surfaces that are identical """ return [] diff --git a/montepy/surfaces/surface_builder.py b/montepy/surfaces/surface_builder.py index a86cd139..ab71c5e8 100644 --- a/montepy/surfaces/surface_builder.py +++ b/montepy/surfaces/surface_builder.py @@ -8,13 +8,18 @@ def parse_surface(input: InitInput): - """ - Builds a Surface object for the type of Surface + """Builds a Surface object for the type of Surface + + Parameters + ---------- + input : Union[Input, str] + The Input object representing the input - :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 + Returns + ------- + Surface + A Surface object properly parsed. If supported a sub-class of + Surface will be given. """ ST = SurfaceType buffer_surface = Surface(input) @@ -32,14 +37,19 @@ def parse_surface(input: InitInput): surface_builder = parse_surface -""" -Alias for :func:`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 + +Parameters +---------- +input : Union[Input, str] + The Input object representing the input + +Returns +------- +Surface + A Surface object properly parsed. If supported a sub-class of + Surface will be given. """ diff --git a/montepy/surfaces/surface_type.py b/montepy/surfaces/surface_type.py index 6fb86f47..eb311cab 100644 --- a/montepy/surfaces/surface_type.py +++ b/montepy/surfaces/surface_type.py @@ -4,13 +4,14 @@ @unique class SurfaceType(str, Enum): - """ - An enumeration of the surface types allowed. + """An enumeration of the surface types allowed. - :param value: The shorthand used by MCNP - :type value: str - :param description: The human readable description of the surface. - :type description: str + Parameters + ---------- + value : str + The shorthand used by MCNP + description : str + The human readable description of the surface. """ def __new__(cls, value, description): diff --git a/montepy/transforms.py b/montepy/transforms.py index ee858b58..2255f7f0 100644 --- a/montepy/transforms.py +++ b/montepy/transforms.py @@ -6,13 +6,12 @@ class Transforms(NumberedDataObjectCollection): - """ - A container of multiple :class:`~montepy.data_inputs.transform.Transform` instances. - - .. note:: + """A container of multiple :class:`~montepy.data_inputs.transform.Transform` instances. - For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. + Notes + ----- + For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. """ def __init__(self, objects: list = None, problem: montepy.MCNP_Problem = None): diff --git a/montepy/universe.py b/montepy/universe.py index e4b7a2e3..9c2de932 100644 --- a/montepy/universe.py +++ b/montepy/universe.py @@ -1,4 +1,4 @@ -# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. +# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved. import montepy from montepy.cells import Cells from montepy.input_parser.mcnp_input import Input @@ -6,19 +6,22 @@ from montepy.input_parser import syntax_node from montepy.numbered_mcnp_object import Numbered_MCNP_Object +from numbers import Integral + class Universe(Numbered_MCNP_Object): - """ - Class to represent an MCNP universe, but not handle the input + """Class to represent an MCNP universe, but not handle the input directly. - :param number: The number for the universe, must be ≥ 0 - :type number: int + Parameters + ---------- + number : int + The number for the universe, must be ≥ 0 """ def __init__(self, number: int): self._number = self._generate_default_node(int, -1) - if not isinstance(number, int): + if not isinstance(number, Integral): raise TypeError("number must be int") if number < 0: raise ValueError(f"Universe number must be ≥ 0. {number} given.") @@ -32,11 +35,12 @@ def parse(self, token_gen, input): @property def cells(self): - """ - A generator of the cell objects in this universe. + """A generator of the cell objects in this universe. - :return: a generator returning every cell in this universe. - :rtype: Generator + Returns + ------- + Generator + a generator returning every cell in this universe. """ if self._problem: for cell in self._problem.cells: @@ -44,14 +48,19 @@ def cells(self): yield cell def claim(self, cells): - """ - Take the given cells and move them into this universe, and out of their original universe. + """Take the given cells and move them into this universe, and out of their original universe. Can be given a single Cell, a list of cells, or a Cells object. - :param cells: the cell(s) to be claimed - :type cells: Cell, list, or Cells - :raises TypeError: if bad parameter is given. + Parameters + ---------- + cells : Cell, list, or Cells + the cell(s) to be claimed + + Raises + ------ + TypeError + if bad parameter is given. """ if not isinstance(cells, (montepy.Cell, list, Cells)): raise TypeError(f"Cells being claimed must be a Cell, list, or Cells") @@ -65,9 +74,7 @@ def claim(self, cells): @property def old_number(self): - """ - Original universe number from the input file. - """ + """Original universe number from the input file.""" return self._number def _update_values(self): diff --git a/montepy/universes.py b/montepy/universes.py index 9fd3c3e0..b26a3077 100644 --- a/montepy/universes.py +++ b/montepy/universes.py @@ -6,13 +6,12 @@ class Universes(NumberedObjectCollection): - """ - A container of multiple :class:`~montepy.universe.Universe` instances. - - .. note:: + """A container of multiple :class:`~montepy.universe.Universe` instances. - For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. + Notes + ----- + For examples see the ``NumberedObjectCollection`` :ref:`collect ex`. """ def __init__(self, objects: list = None, problem: montepy.MCNP_Problem = None): diff --git a/montepy/utilities.py b/montepy/utilities.py index cd653780..03c43f5a 100644 --- a/montepy/utilities.py +++ b/montepy/utilities.py @@ -9,17 +9,25 @@ def fortran_float(number_string): - """ - Attempts to convert a FORTRAN formatted float string to a float. + """Attempts to convert a FORTRAN formatted float string to a float. FORTRAN allows silly things for scientific notation like ``6.02+23`` to represent Avogadro's Number. - :param number_string: the string that will be converted to a float - :type number_string: str - :raises ValueError: If the string can not be parsed as a float. - :return: the parsed float of the this string - :rtype: float + Parameters + ---------- + number_string : str + the string that will be converted to a float + + Raises + ------ + ValueError + If the string can not be parsed as a float. + + Returns + ------- + float + the parsed float of the this string """ try: return float(number_string) @@ -33,13 +41,17 @@ def fortran_float(number_string): def is_comment(line): - """ - Determines if the line is a ``C comment`` style comment. + """Determines if the line is a ``C comment`` style comment. - :param line: the line to analyze - :type line: str - :returns: True if the line is a comment - :rtype: bool + Parameters + ---------- + line : str + the line to analyze + + Returns + ------- + bool + True if the line is a comment """ upper_start = line[0 : BLANK_SPACE_CONTINUE + 1].upper() non_blank_comment = upper_start and line.lstrip().upper().startswith("C ") @@ -54,22 +66,26 @@ def is_comment(line): def make_prop_val_node( hidden_param, types=None, base_type=None, validator=None, deletable=False ): - """ - A decorator function for making a property from a ValueNode. + """A decorator function for making a property from a ValueNode. This decorator is meant to handle all boiler plate. It will get and set the value property of the underlying ValueNode. By default the property is not settable unless types is set. - :param hidden_param: The string representing the parameter name of the internally stored ValueNode. - :type hidden_param: str - :param types: the acceptable types for the settable, which is passed to isinstance. If an empty tuple will be - type(self). - :type types: Class, tuple - :param validator: A validator function to run on values before setting. Must accept func(self, value). - :type validator: function - :param deletable: If true make this property deletable. When deleted the value will be set to None. - :type deletable: bool + Parameters + ---------- + hidden_param : str + The string representing the parameter name of the internally + stored ValueNode. + types : Class, tuple + the acceptable types for the settable, which is passed to + isinstance. If an empty tuple will be type(self). + validator : function + A validator function to run on values before setting. Must + accept func(self, value). + deletable : bool + If true make this property deletable. When deleted the value + will be set to None. """ def decorator(func): @@ -122,19 +138,24 @@ def deleter(self): def make_prop_pointer( hidden_param, types=None, base_type=None, validator=None, deletable=False ): - """ - A decorator function that makes a property based off of a pointer to another object. + """A decorator function that makes a property based off of a pointer to another object. Note this can also be used for almost any circumstance as everything in python is a pointer. - :param hidden_param: The string representing the parameter name of the internally stored ValueNode. - :type hidden_param: str - :param types: the acceptable types for the settable, which is passed to isinstance, if an empty tuple is provided the type will be self. - :type types: Class, tuple - :param validator: A validator function to run on values before setting. Must accept func(self, value). - :type validator: function - :param deletable: If true make this property deletable. When deleted the value will be set to None. - :type deletable: bool + Parameters + ---------- + hidden_param : str + The string representing the parameter name of the internally + stored ValueNode. + types : Class, tuple + the acceptable types for the settable, which is passed to + isinstance, if an empty tuple is provided the type will be self. + validator : function + A validator function to run on values before setting. Must + accept func(self, value). + deletable : bool + If true make this property deletable. When deleted the value + will be set to None. """ def decorator(func): diff --git a/pyproject.toml b/pyproject.toml index 534fc7f9..ebf8b482 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ doc = [ "sphinx_autodoc_typehints", "autodocsumm", ] -format = ["black>=23.3.0"] +format = ["black~=25.1"] build = [ "build", "setuptools>=64.0.0", diff --git a/tests/inputs/test.imcnp b/tests/inputs/test.imcnp index 767df6b0..4029f809 100644 --- a/tests/inputs/test.imcnp +++ b/tests/inputs/test.imcnp @@ -41,7 +41,7 @@ C Iron m2 26054.80c 5.85 plib= 80p 26056.80c 91.75 - 26057.80c 2.12 + 26057.80c 2.12 $ very very very very very very very very very very very very very very long line that exceeds line limit 26058.80c 0.28 $ trailing comment shouldn't move #458. C water C foo diff --git a/tests/inputs/test_complement_edge.imcnp b/tests/inputs/test_complement_edge.imcnp index e83b83fc..975379a1 100644 --- a/tests/inputs/test_complement_edge.imcnp +++ b/tests/inputs/test_complement_edge.imcnp @@ -33,5 +33,5 @@ m814 26000.55c 5.657-2 28000.50c 9.881-3 24000.50c 1.581-2 25055.51c 1.760-3 - +sdef cel=2 pos=0 0 0 rad=d3 ext=d3 axs=0 0 1 diff --git a/tests/inputs/test_dos.imcnp b/tests/inputs/test_dos.imcnp index c2725156..b6b6b14a 100644 --- a/tests/inputs/test_dos.imcnp +++ b/tests/inputs/test_dos.imcnp @@ -46,5 +46,7 @@ ksrc 0 0 0 kcode 100000 1.000 50 1050 phys:p j 1 2j 1 mode n p +ssw cel=5 99 +sdef $ blank one from issue #636 diff --git a/tests/inputs/test_not_imp.imcnp b/tests/inputs/test_not_imp.imcnp new file mode 100644 index 00000000..62da55c0 --- /dev/null +++ b/tests/inputs/test_not_imp.imcnp @@ -0,0 +1,48 @@ +A test with a default importance (Not Implemented) +C cells +c +1 1 20 + -1000 $ dollar comment + U=350 trcl=5 + imp:n,p=1 $ imp:e should default to 1 +2 2 8 + -1005 + imp:n,p=1 $ imp:e should default to 1 +3 3 -1 + 1000 1005 -1010 + imp:n=3 $ imp:e and imp:p should default to 1 +99 0 + 1010 + imp:n=9 $ imp:e should default to 1 + imp:p=0 +5 0 + #99 + imp:n=0 $ imp:e should default to 0 + imp:p=0 +c foo end comment + +C surfaces +1000 SO 1 +1005 RCC 0 1.5 -0.5 0 0 1 0.25 +1010 SO 3 + +C data +C materials +C UO2 5 atpt enriched +m1 92235.80c 5 & +92238.80c 95 +C Iron +m2 26054.80c 5.85 + 26056.80c 91.75 + 26057.80c 2.12 + 26058.80c 0.28 +C water +m3 1001.80c 2 + 8016.80c 1 +MT3 lwtr.23t +C execution +ksrc 0 0 0 +kcode 100000 1.000 50 1050 +phys:p j 1 2j 1 +mode n p e + diff --git a/tests/inputs/test_universe.imcnp b/tests/inputs/test_universe.imcnp index 59cf99d5..57eca507 100644 --- a/tests/inputs/test_universe.imcnp +++ b/tests/inputs/test_universe.imcnp @@ -24,6 +24,7 @@ c c foo end comment C surfaces +c this comment is a ver very very very very very very very very very long line in a comment that shouldn't raise a warning but maybe? 1000 SO 1 1005 RCC 0 1.5 -0.5 0 0 1 0.25 1010 SO 3 diff --git a/tests/test_importance.py b/tests/test_importance.py index e7b5f4ea..fa4313e2 100644 --- a/tests/test_importance.py +++ b/tests/test_importance.py @@ -1,5 +1,4 @@ # Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved. -from unittest import TestCase import montepy from montepy.cell import Cell from montepy.particle import Particle @@ -7,178 +6,184 @@ from montepy.errors import * from montepy.input_parser import mcnp_input, block_type import os +import io +import pytest -class TestImportance(TestCase): - def test_importance_init_cell(self): - # test_normal cell init - in_str = "1 0 -1 IMP:N,P=1" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) +def test_importance_init_cell(): + # test_normal cell init + in_str = "1 0 -1 IMP:N,P=1" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + cell = Cell(card) + assert cell.importance.neutron == 1.0 + assert cell.importance.photon == 1.0 + assert cell.importance.alpha_particle == 0.0 + assert cell.importance.all is None + assert cell.importance.in_cell_block + # test non-number imp + in_str = "1 0 -1 IMP:N,P=h" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(ValueError): cell = Cell(card) - self.assertEqual(cell.importance.neutron, 1.0) - self.assertEqual(cell.importance.photon, 1.0) - self.assertEqual(cell.importance.alpha_particle, 0.0) - self.assertIsNone(cell.importance.all) - self.assertTrue(cell.importance.in_cell_block) - # test non-number imp - in_str = "1 0 -1 IMP:N,P=h" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(ValueError): - cell = Cell(card) - # test negative imp - in_str = "1 0 -1 IMP:N,P=-2" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(ValueError): - cell = Cell(card) - - def test_importance_init_data(self): - in_str = "IMP:N,P 1 0" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - imp = Importance(card) - self.assertEqual( - [val.value for val in imp._particle_importances[Particle.NEUTRON]["data"]], - [1.0, 0.0], - ) - self.assertEqual( - [val.value for val in imp._particle_importances[Particle.PHOTON]["data"]], - [1.0, 0.0], - ) - # test non-number imp - in_str = "IMP:N,P 1 h" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(ValueError): - imp = Importance(card) - # test negative - in_str = "IMP:N,P 1 -2" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(ValueError): - imp = Importance(card) - # test bad in_cell_block - in_str = "IMP:N,P 1 2" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(TypeError): - imp = Importance(card, in_cell_block=1) - # test bad key - in_str = "IMP:N,P 1 2" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(TypeError): - imp = Importance(card, key=1) - # test bad value - in_str = "IMP:N,P 1 2" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - with self.assertRaises(TypeError): - imp = Importance(card, value=1) - - def test_importance_iter_getter_in(self): - in_str = "1 0 -1 IMP:N,P=1" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - cell = Cell(card) - imp = cell.importance - particles = [ - montepy.particle.Particle.NEUTRON, - montepy.particle.Particle.PHOTON, - ] - for particle in imp: - self.assertIn(particle, particles) - self.assertAlmostEqual(imp[particle], 1.0) - for particle in particles: - self.assertIn(particle, imp) - with self.assertRaises(TypeError): - imp["hi"] - - def test_importance_all_setter(self): - in_str = "1 0 -1 IMP:N,P=1" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - cell = Cell(card) - problem = montepy.mcnp_problem.MCNP_Problem("foo") - problem.mode.add(montepy.particle.Particle.NEUTRON) - problem.mode.add(montepy.particle.Particle.PHOTON) - imp = cell.importance - cell.link_to_problem(problem) - imp.all = 2.0 - self.assertAlmostEqual(imp.neutron, 2.0) - self.assertAlmostEqual(imp.photon, 2.0) - # try wrong type - with self.assertRaises(TypeError): - imp.all = "h" - # try negative type - with self.assertRaises(ValueError): - imp.all = -2.0 - - def test_importance_setter(self): - in_str = "1 0 -1 IMP:N,P=1" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + # test negative imp + in_str = "1 0 -1 IMP:N,P=-2" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(ValueError): cell = Cell(card) - cell.importance.neutron = 2.5 - self.assertEqual(cell.importance.neutron, 2.5) - problem = montepy.mcnp_problem.MCNP_Problem("foo") - cell.link_to_problem(problem) - # test problem mode enforcement - with self.assertRaises(ValueError): - cell.importance.photon = 1.0 - # test wrong type - with self.assertRaises(TypeError): - cell.importance.neutron = "h" - # test negative - with self.assertRaises(ValueError): - cell.importance.neutron = -0.5 - - cell.importance[Particle.NEUTRON] = 3 - self.assertEqual(cell.importance.neutron, 3.0) - with self.assertRaises(TypeError): - cell.importance[""] = 5 - with self.assertRaises(TypeError): - cell.importance[Particle.NEUTRON] = "" - with self.assertRaises(ValueError): - cell.importance[Particle.NEUTRON] = -1.0 - - def test_importance_deleter(self): - in_str = "1 0 -1 IMP:N,P=1" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - cell = Cell(card) - del cell.importance.neutron - self.assertAlmostEqual(cell.importance.neutron, 0.0) - del cell.importance[Particle.PHOTON] - self.assertAlmostEqual(cell.importance.photon, 0.0) - with self.assertRaises(TypeError): - del cell.importance[""] - - def test_importance_merge(self): - in_str = "IMP:N,P 1 0" - card = mcnp_input.Input([in_str], block_type.BlockType.DATA) - imp1 = Importance(card) - in_str = "IMP:E 0 0" - card = mcnp_input.Input([in_str], block_type.BlockType.DATA) - imp2 = Importance(card) + + +def test_importance_init_data(): + in_str = "IMP:N,P 1 0" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + imp = Importance(card) + assert [ + val.value for val in imp._particle_importances[Particle.NEUTRON]["data"] + ] == [1.0, 0.0] + assert [ + val.value for val in imp._particle_importances[Particle.PHOTON]["data"] + ] == [1.0, 0.0] + # test non-number imp + in_str = "IMP:N,P 1 h" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(ValueError): + imp = Importance(card) + # test negative + in_str = "IMP:N,P 1 -2" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(ValueError): + imp = Importance(card) + # test bad in_cell_block + in_str = "IMP:N,P 1 2" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(TypeError): + imp = Importance(card, in_cell_block=1) + # test bad key + in_str = "IMP:N,P 1 2" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(TypeError): + imp = Importance(card, key=1) + # test bad value + in_str = "IMP:N,P 1 2" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + with pytest.raises(TypeError): + imp = Importance(card, value=1) + + +def test_importance_iter_getter_in(): + in_str = "1 0 -1 IMP:N,P=1" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + cell = Cell(card) + imp = cell.importance + particles = [ + montepy.particle.Particle.NEUTRON, + montepy.particle.Particle.PHOTON, + ] + for particle in imp: + assert particle in particles + assert imp[particle] == 1.0 + for particle in particles: + assert particle in imp + with pytest.raises(TypeError): + imp["hi"] + + +def test_importance_all_setter(): + in_str = "1 0 -1 IMP:N,P=1" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + cell = Cell(card) + problem = montepy.mcnp_problem.MCNP_Problem("foo") + problem.mode.add(montepy.particle.Particle.NEUTRON) + problem.mode.add(montepy.particle.Particle.PHOTON) + imp = cell.importance + cell.link_to_problem(problem) + imp.all = 2.0 + assert imp.neutron == 2.0 + assert imp.photon == 2.0 + # try wrong type + with pytest.raises(TypeError): + imp.all = "h" + # try negative type + with pytest.raises(ValueError): + imp.all = -2.0 + + +def test_importance_setter(): + in_str = "1 0 -1 IMP:N,P=1" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + cell = Cell(card) + cell.importance.neutron = 2.5 + assert cell.importance.neutron == 2.5 + problem = montepy.mcnp_problem.MCNP_Problem("foo") + cell.link_to_problem(problem) + # test problem mode enforcement + with pytest.warns(ParticleTypeNotInProblem): + cell.importance.photon = 1.0 + # test wrong type + with pytest.raises(TypeError): + cell.importance.neutron = "h" + # test negative + with pytest.raises(ValueError): + cell.importance.neutron = -0.5 + + cell.importance[Particle.NEUTRON] = 3 + assert cell.importance.neutron == 3.0 + with pytest.raises(TypeError): + cell.importance[""] = 5 + with pytest.raises(TypeError): + cell.importance[Particle.NEUTRON] = "" + with pytest.raises(ValueError): + cell.importance[Particle.NEUTRON] = -1.0 + + +def test_importance_deleter(): + in_str = "1 0 -1 IMP:N,P=1" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + cell = Cell(card) + del cell.importance.neutron + assert cell.importance.neutron == 0.0 + del cell.importance[Particle.PHOTON] + assert cell.importance.photon == 0.0 + with pytest.raises(TypeError): + del cell.importance[""] + + +def test_importance_merge(): + in_str = "IMP:N,P 1 0" + card = mcnp_input.Input([in_str], block_type.BlockType.DATA) + imp1 = Importance(card) + in_str = "IMP:E 0 0" + card = mcnp_input.Input([in_str], block_type.BlockType.DATA) + imp2 = Importance(card) + imp1.merge(imp2) + assert [ + val.value for val in imp1._particle_importances[Particle.NEUTRON]["data"] + ] == [1.0, 0.0] + assert [ + val.value for val in imp1._particle_importances[Particle.ELECTRON]["data"] + ] == [0.0, 0.0] + # test bad type + with pytest.raises(TypeError): + imp1.merge("hi") + # test bad block type + in_str = "1 0 -1 IMP:N,P=1" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + cell = Cell(card) + with pytest.raises(ValueError): + imp1.merge(cell.importance) + in_str = "IMP:P 0 0" + card = mcnp_input.Input([in_str], block_type.BlockType.CELL) + imp2 = Importance(card) + with pytest.raises(MalformedInputError): imp1.merge(imp2) - self.assertEqual( - [val.value for val in imp1._particle_importances[Particle.NEUTRON]["data"]], - [1.0, 0.0], - ) - self.assertEqual( - [ - val.value - for val in imp1._particle_importances[Particle.ELECTRON]["data"] - ], - [0.0, 0.0], - ) - # test bad type - with self.assertRaises(TypeError): - imp1.merge("hi") - # test bad block type - in_str = "1 0 -1 IMP:N,P=1" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - cell = Cell(card) - with self.assertRaises(ValueError): - imp1.merge(cell.importance) - in_str = "IMP:P 0 0" - card = mcnp_input.Input([in_str], block_type.BlockType.CELL) - imp2 = Importance(card) - with self.assertRaises(MalformedInputError): - imp1.merge(imp2) - - def tests_redundant_importance(self): - with self.assertRaises(MalformedInputError): - montepy.read_input( - os.path.join("tests", "inputs", "test_imp_redundant.imcnp") - ) + + +def test_redundant_importance(): + with pytest.raises(MalformedInputError): + montepy.read_input(os.path.join("tests", "inputs", "test_imp_redundant.imcnp")) + + +def test_default_importance_not_implemented(): + prob = montepy.read_input(os.path.join("tests", "inputs", "test_not_imp.imcnp")) + prob.print_in_data_block["imp"] = True + with pytest.raises(NotImplementedError): + prob.write_problem(io.StringIO()) diff --git a/tests/test_integration.py b/tests/test_integration.py index 278cd6d1..a1a737ff 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -51,7 +51,7 @@ def test_original_input(simple_problem): def test_original_input_dos(): dos_problem = montepy.read_input(os.path.join("tests", "inputs", "test_dos.imcnp")) - cell_order = [Message, Title] + [Input] * 16 + cell_order = [Message, Title] + [Input] * 18 for i, input_ob in enumerate(dos_problem.original_inputs): assert isinstance(input_ob, cell_order[i]) diff --git a/tests/test_material.py b/tests/test_material.py index 71010351..b0b7db91 100644 --- a/tests/test_material.py +++ b/tests/test_material.py @@ -35,7 +35,9 @@ def big_material(_): "u238", "am242", "am242m1", + "Co60m2.50c", "Pu239", + "Ni-0.60c", ] mat = Material() mat.number = 1 @@ -246,41 +248,64 @@ def test_material_append_bad(_): mat.append((Nuclide("1001.80c"), -1.0)) @pytest.mark.parametrize( - "content, is_in", + "content, strict, is_in", [ - ("1001.80c", True), - ("H-1", True), - (Element(1), True), - (Nucleus(Element(1), 1), True), - (Element(43), False), - ("B-10.00c", False), - ("H", True), - (Nucleus(Element(5), 10), False), + ("1001.80c", False, True), + (1001, False, True), + ("H-1", False, True), + (Element(1), False, True), + (Nucleus(Element(1), 1), False, True), + (Element(43), False, False), + ("B-10.00c", False, False), + ("H", False, True), + ("H", True, False), + (Nucleus(Element(5), 10), False, False), + ("Ni", False, True), + ("Ni", True, False), # test wrong library + ("Ni-0.60c", True, True), + ("Co-60", False, False), + ("Co-60", True, False), + ("Co-60m2.50c", True, True), ], ) - def test_material_contains(_, big_material, content, is_in): - assert is_in == (content in big_material), "Contains didn't work properly" - assert is_in == big_material.contains(content) + def test_material_contains(_, big_material, content, strict, is_in): + if not strict: + assert is_in == (content in big_material), "Contains didn't work properly" + assert is_in == big_material.contains_all(content, strict=strict) + assert is_in == big_material.contains_any(content, strict=strict) with pytest.raises(TypeError): - 5 in big_material + {} in big_material + with pytest.raises(TypeError): + big_material.contains_all("H", strict=5) + with pytest.raises(TypeError): + big_material.contains_any("H", strict=5) def test_material_multi_contains(_, big_material): - assert big_material.contains("1001", "U-235", "Pu-239", threshold=0.01) - assert not big_material.contains("1001", "U-235", "Pu-239", threshold=0.07) - assert not big_material.contains("U-235", "B-10") + # contains all + assert big_material.contains_all("1001", "U-235", "Pu-239", threshold=0.01) + assert not big_material.contains_all( + "1001", "U-235", "Pu-239", threshold=0.01, strict=True + ) + assert not big_material.contains_all("1001", "U-235", "Pu-239", threshold=0.07) + assert not big_material.contains_all("U-235", "B-10") + # contains any + assert not big_material.contains_any("C", "B", "F") + print("sadness") + assert big_material.contains_any("h-1", "C", "B") def test_material_contains_bad(_): mat = Material() - with pytest.raises(TypeError): - mat.contains(mat) - with pytest.raises(TypeError): - mat.contains("1001", mat) - with pytest.raises(ValueError): - mat.contains("hi") - with pytest.raises(TypeError): - mat.contains("1001", threshold="hi") - with pytest.raises(ValueError): - mat.contains("1001", threshold=-1.0) + for method in [mat.contains_all, mat.contains_any]: + with pytest.raises(TypeError): + method(mat) + with pytest.raises(TypeError): + method("1001", mat) + with pytest.raises(ValueError): + method("hi") + with pytest.raises(TypeError): + method("1001", threshold="hi") + with pytest.raises(ValueError): + method("1001", threshold=-1.0) def test_material_normalize(_, big_material): # make sure it's not an invalid starting condition @@ -300,14 +325,25 @@ def test_material_normalize(_, big_material): ({"name": "U235m1"}, 1), ({"element": Element(1)}, 6), ({"element": "H"}, 6), + ({"element": "H", "strict": True}, 0), + ({"element": "H", "A": 1, "strict": True}, 0), + ( + { + "element": "H", + "A": 1, + "library": slice("00c", "05c"), + "strict": True, + }, + 2, + ), ({"element": slice(92, 95)}, 5), ({"A": 1}, 4), ({"A": slice(235, 240)}, 5), ({"A": slice(232, 243, 2)}, 5), - ({"A": slice(None)}, 15), - ({"meta_state": 0}, 13), + ({"A": slice(None)}, 17), + ({"meta_state": 0}, 14), ({"meta_state": 1}, 2), - ({"meta_state": slice(0, 2)}, 15), + ({"meta_state": slice(0, 2)}, 16), ({"library": "80c"}, 3), ({"library": slice("00c", "10c")}, 2), ], @@ -337,6 +373,8 @@ def test_material_find_bad(_, big_material): list(big_material.find(element=1.23)) with pytest.raises(TypeError): list(big_material.find(library=5)) + with pytest.raises(TypeError): + list(big_material.find(strict=5)) def test_material_str(_): in_str = "M20 1001.80c 0.5 8016.80c 0.4 94239.80c 0.1" diff --git a/tests/test_numbered_collection.py b/tests/test_numbered_collection.py index 48a58447..3b47c699 100644 --- a/tests/test_numbered_collection.py +++ b/tests/test_numbered_collection.py @@ -736,13 +736,30 @@ def test_m0_defaults_fresh(_): (("B",), 1.0, 0), ], ) - def test_get_containing(_, m0_prob, nuclides, threshold, num): - ret = list(m0_prob.materials.get_containing(*nuclides, threshold=threshold)) + def test_get_containing_all(_, m0_prob, nuclides, threshold, num): + ret = list(m0_prob.materials.get_containing_all(*nuclides, threshold=threshold)) assert len(ret) == num for mat in ret: assert isinstance(mat, montepy.Material) with pytest.raises(TypeError): - next(m0_prob.materials.get_containing(m0_prob)) + next(m0_prob.materials.get_containing_all(m0_prob)) + + @pytest.mark.parametrize( + "nuclides, threshold, num", + [ + (("26054", "26056"), 1.0, 1), + ((montepy.Nuclide("H-1"),), 0.0, 1), + (("B",), 1.0, 0), + (("U-235", "H-1"), 0.0, 2), + ], + ) + def test_get_containing_any(_, m0_prob, nuclides, threshold, num): + ret = list(m0_prob.materials.get_containing_any(*nuclides, threshold=threshold)) + assert len(ret) == num + for mat in ret: + assert isinstance(mat, montepy.Material) + with pytest.raises(TypeError): + next(m0_prob.materials.get_containing_any(m0_prob)) @pytest.fixture def h2o(_): diff --git a/tests/test_numbers.py b/tests/test_numbers.py new file mode 100644 index 00000000..1fb1b3c0 --- /dev/null +++ b/tests/test_numbers.py @@ -0,0 +1,23 @@ +# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved. + +import montepy +import pytest +import numpy as np + + +def test_cell_integral(): + montepy.Cell(number=3) + montepy.Cell(number=np.uint8(1)) + with pytest.raises(TypeError): + montepy.Cell(number=5.0) + + +def test_cell_real(): + c = montepy.Cell() + c.atom_density = 1 + c.mass_density = np.float32(1.2e-3) + + +def test_surf_coeff_real(): + cx = montepy.CylinderParAxis() + cx.coordinates = (0, np.int16(-1.23))