Repository: idaholab/MontePy

Issue:
Relax 'float' type enforcement to any real number and 'int' type enforcement to any integral
**Is your feature request related to a problem? Please describe.**
Numeric variables come in many different types. Virtually all places where `float` is enforced, any real number is valid instead. An `int`, `numpy.uint8`, or `np.float64` would fail the test.

Similarly, `int` enforcement disallows types such as `numpy.uint8` and `np.int64`.

**Describe the solution you'd like**

Use `numbers.Real` for enforcement, instead.

```python
# Replace all of this:
isinstance(num, float)
# with this:
isinstance(num, numbers.Real)
```

Similarly, any place where `int` is enforced, use `numbers.Integral` instead.

```python
# Replace all of this:
isinstance(num, int)
# with this:
isinstance(num, numbers.Integral)
```


**Describe alternatives you've considered**
 - Do not enforce type and allow it to fail naturally. @MicahGale does not like this, because the error message will be less helpful.
 - `try` to cast to `float` or `int`. This is more verbose and annoying to do. In the case of a `float`, we definitely don't want to cast to `int` in most places (an edge case would be for normally "very large" parameters such as `nps` handling floats such as `1e10`).
 - Use `numbers.Number`. This allows complex numbers, which I don't think we ever want to do.



Discussion:

Patch:
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index 6c16d227..2b6d4450 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -25,6 +25,7 @@ MontePy Changelog
 * 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**
diff --git a/montepy/cell.py b/montepy/cell.py
index e119790e..88231a61 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
@@ -442,7 +440,7 @@ class Cell(Numbered_MCNP_Object):
 
     @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.")
@@ -467,7 +465,7 @@ class Cell(Numbered_MCNP_Object):
 
     @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.")
@@ -591,7 +589,7 @@ class Cell(Numbered_MCNP_Object):
         new_deleting_dict = {}
 
         def get_num(obj):
-            if isinstance(obj, int):
+            if isinstance(obj, Integral):
                 return obj
             return obj.number
 
@@ -800,11 +798,11 @@ class Cell(Numbered_MCNP_Object):
             )
         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.")
@@ -832,7 +830,7 @@ class Cell(Numbered_MCNP_Object):
         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 a6f77993..89502831 100644
--- a/montepy/cells.py
+++ b/montepy/cells.py
@@ -1,8 +1,9 @@
-# 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):
@@ -64,9 +65,9 @@ class Cells(NumberedObjectCollection):
             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)
@@ -214,11 +215,11 @@ class Cells(NumberedObjectCollection):
         :rtype: type(self)
 
         """
-        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/data_inputs/element.py b/montepy/data_inputs/element.py
index f3443d1a..a9b480ca 100644
--- a/montepy/data_inputs/element.py
+++ b/montepy/data_inputs/element.py
@@ -1,7 +1,8 @@
-# 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
 
@@ -23,7 +24,7 @@ class Element(SingletonGroup):
     __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:
diff --git a/montepy/data_inputs/material.py b/montepy/data_inputs/material.py
index e295c405..ffbf9018 100644
--- a/montepy/data_inputs/material.py
+++ b/montepy/data_inputs/material.py
@@ -1,23 +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 numbers
-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 *
@@ -37,7 +32,7 @@ This is used for adding new material components.
 By default all components made from scratch are added to their own line with this many leading spaces.
 """
 
-NuclideLike = Union[Nuclide, Nucleus, Element, str, numbers.Integral]
+NuclideLike = Union[Nuclide, Nucleus, Element, str, Integral]
 
 
 class _DefaultLibraries:
@@ -507,9 +502,9 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
 
     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
@@ -528,7 +523,7 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
 
     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)
@@ -541,7 +536,7 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
     def __len__(self):
         return len(self._components)
 
-    def _check_valid_comp(self, newvalue: tuple[Nuclide, float]):
+    def _check_valid_comp(self, newvalue: tuple[Nuclide, Real]):
         """
         Checks valid compositions and raises an error if needed.
         """
@@ -555,7 +550,7 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
             )
         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."
             )
@@ -565,9 +560,9 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
             )
 
     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
@@ -606,11 +601,11 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
         del self._components[idx]
 
     def __contains__(self, nuclide):
-        if not isinstance(nuclide, (Nuclide, Nucleus, Element, str, numbers.Integral)):
+        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, numbers.Integral)):
+        if isinstance(nuclide, (str, Integral)):
             nuclide = Nuclide(nuclide)
         # switch to elemental
         if isinstance(nuclide, (Nucleus, Nuclide)) and nuclide.A == 0:
@@ -683,7 +678,7 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
         :param fraction: the fraction of this component being added.
         :type fraction: float
         """
-        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."
             )
@@ -808,12 +803,12 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
     @staticmethod
     def _promote_nuclide(nuclide, strict):
         # This is necessary for python 3.9
-        if not isinstance(nuclide, (Nuclide, Nucleus, Element, str, numbers.Integral)):
+        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, int)):
+        if isinstance(nuclide, (str, Integral)):
             nuclide = Nuclide(nuclide)
         # treat elemental as element
         if isinstance(nuclide, (Nucleus, Nuclide)) and nuclide.A == 0 and not strict:
@@ -824,13 +819,13 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
 
     def _contains_arb(
         self,
-        *nuclides: Union[Nuclide, Nucleus, Element, str, int],
+        *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, numbers.Real):
+        if not isinstance(threshold, Real):
             raise TypeError(
                 f"Threshold must be a float. {threshold} of type: {type(threshold)} given"
             )
@@ -946,7 +941,7 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
         """
 
         def setter(old_val, new_val):
-            if not isinstance(new_val, numbers.Real):
+            if not isinstance(new_val, Real):
                 raise TypeError(
                     f"Value must be set to a float. {new_val} of type {type(new_val)} given."
                 )
@@ -1066,7 +1061,7 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
     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,
@@ -1180,15 +1175,15 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
         :rtype: Generator[tuple[int, tuple[Nuclide, float]]]
         """
         # 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."
             )
diff --git a/montepy/data_inputs/nuclide.py b/montepy/data_inputs/nuclide.py
index 22350e6f..40421d00 100644
--- a/montepy/data_inputs/nuclide.py
+++ b/montepy/data_inputs/nuclide.py
@@ -1,17 +1,15 @@
-# 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
 """
@@ -221,12 +219,12 @@ class Nucleus(SingletonGroup):
             )
         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(
@@ -328,7 +326,7 @@ class Nucleus(SingletonGroup):
 
     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):
@@ -446,7 +444,7 @@ class Nuclide:
     :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 TypeError: if a parameter is the wrong type.
     :raises ValueError: if non-sensical values are given.
     """
 
@@ -487,7 +485,7 @@ class Nuclide:
         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."
             )
@@ -739,7 +737,7 @@ class Nuclide:
         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/input_parser/syntax_node.py b/montepy/input_parser/syntax_node.py
index bdae6f87..eab96f9e 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,8 +17,6 @@ from montepy.input_parser.shortcuts import Shortcuts
 from montepy.geometry_operators import Operator
 from montepy.particle import Particle
 from montepy.utilities import fortran_float
-import re
-import warnings
 
 
 class SyntaxNodeBase(ABC):
@@ -1091,7 +1092,7 @@ class ValueNode(SyntaxNodeBase):
             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 +1116,7 @@ class ValueNode(SyntaxNodeBase):
 
     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"
@@ -1378,7 +1379,7 @@ class ValueNode(SyntaxNodeBase):
             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
diff --git a/montepy/materials.py b/montepy/materials.py
index 36281d6a..1545ed62 100644
--- a/montepy/materials.py
+++ b/montepy/materials.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
 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
@@ -292,7 +293,7 @@ class Materials(NumberedDataObjectCollection):
         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.")
@@ -300,7 +301,7 @@ class Materials(NumberedDataObjectCollection):
             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."
             )
@@ -308,7 +309,7 @@ class Materials(NumberedDataObjectCollection):
             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/numbered_mcnp_object.py b/montepy/numbered_mcnp_object.py
index 31c14d82..e46b75a3 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 *
@@ -61,7 +60,7 @@ class Numbered_MCNP_Object(MCNP_Object):
 
     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."
                 )
@@ -73,7 +72,7 @@ class Numbered_MCNP_Object(MCNP_Object):
     """
     """
 
-    @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.
@@ -146,11 +145,11 @@ class Numbered_MCNP_Object(MCNP_Object):
         :rtype: type(self)
 
         """
-        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..edb6ee07 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
@@ -229,7 +230,7 @@ class NumberedObjectCollection(ABC):
         :type number: int
         :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
@@ -282,7 +283,7 @@ class NumberedObjectCollection(ABC):
         :return: the final elements
         :rtype: Numbered_MCNP_Object
         """
-        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)
@@ -362,11 +363,11 @@ class NumberedObjectCollection(ABC):
         :rtype: type(self)
 
         """
-        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,7 +385,7 @@ class NumberedObjectCollection(ABC):
             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.
@@ -394,7 +395,7 @@ class NumberedObjectCollection(ABC):
         """
         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.
@@ -550,7 +551,7 @@ class NumberedObjectCollection(ABC):
         """
         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:
@@ -585,9 +586,9 @@ class NumberedObjectCollection(ABC):
         :returns: an available number
         :rtype: int
         """
-        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
@@ -611,7 +612,7 @@ class NumberedObjectCollection(ABC):
         :param step: how much to increase the last number by
         :type step: int
         """
-        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")
@@ -657,7 +658,7 @@ class NumberedObjectCollection(ABC):
     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 +666,13 @@ class NumberedObjectCollection(ABC):
         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)
 
diff --git a/montepy/surfaces/cylinder_par_axis.py b/montepy/surfaces/cylinder_par_axis.py
index 3ada7c58..c83ea09f 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:
@@ -70,14 +72,12 @@ class CylinderParAxis(Surface):
         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.
diff --git a/montepy/surfaces/half_space.py b/montepy/surfaces/half_space.py
index 68095684..3014c9fa 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,6 +11,8 @@ from montepy.input_parser.syntax_node import (
 )
 from montepy.utilities import *
 
+from numbers import Integral
+
 
 class HalfSpace:
     """
@@ -578,7 +580,7 @@ class UnitHalfSpace(HalfSpace):
                 side = "+"
         else:
             side = "-"
-        if isinstance(self.divider, int):
+        if isinstance(self.divider, Integral):
             div = self.divider
         else:
             div = self.divider.number
@@ -643,7 +645,7 @@ class UnitHalfSpace(HalfSpace):
         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 +661,7 @@ class UnitHalfSpace(HalfSpace):
 
     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 +672,7 @@ class UnitHalfSpace(HalfSpace):
             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
@@ -721,7 +723,7 @@ class UnitHalfSpace(HalfSpace):
         """
 
         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..14bfa904 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
@@ -156,7 +155,7 @@ class Surface(Numbered_MCNP_Object):
         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"
                 )
diff --git a/montepy/universe.py b/montepy/universe.py
index e4b7a2e3..884e80e7 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,6 +6,8 @@ from montepy.input_parser.block_type import BlockType
 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):
     """
@@ -18,7 +20,7 @@ class Universe(Numbered_MCNP_Object):
 
     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.")


Test patch:
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))