From 83b61f7db0491fca9daf9e7d0228df434761e0d5 Mon Sep 17 00:00:00 2001 From: DIGVIJAY <144053736+digvijay-y@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:22:47 +0000 Subject: [PATCH 1/5] weakref --- demo/_config.py | 2 ++ montepy/cell.py | 7 +++++ montepy/mcnp_object.py | 11 ++++++- montepy/numbered_mcnp_object.py | 42 +++++++++++++++++---------- montepy/numbered_object_collection.py | 26 ++++++++++++++--- montepy/surfaces/half_space.py | 29 ++++++++++++++++++ tests/test_numbered_collection.py | 2 ++ 7 files changed, 98 insertions(+), 21 deletions(-) diff --git a/demo/_config.py b/demo/_config.py index bdd243f4..355ad7d8 100644 --- a/demo/_config.py +++ b/demo/_config.py @@ -8,7 +8,9 @@ def IFrame(src, width=X_RES, height=Y_RES, extras=None, **kwargs): return _iframe(src, width, height, extras, **kwargs) + def install_montepy(): if "pyodide" in sys.modules: import piplite + piplite.install("montepy") diff --git a/montepy/cell.py b/montepy/cell.py index 429b32d7..297d3ea9 100644 --- a/montepy/cell.py +++ b/montepy/cell.py @@ -922,6 +922,13 @@ def num(obj): for key in keys: attr = getattr(self, key) setattr(result, key, copy.deepcopy(attr, memo)) + # Clear weakrefs so the cloned cell isn't linked to original collection/problem + # This prevents number conflict checks against the original collection + result._collection_ref = None + result._problem_ref = None + # Update the geometry's _cell references to point to result, not the deepcopied intermediate + if result._geometry is not None: + result._geometry._set_cell(result) # copy geometry for special in special_keys: new_objs = [] diff --git a/montepy/mcnp_object.py b/montepy/mcnp_object.py index 72943faf..a9b00280 100644 --- a/montepy/mcnp_object.py +++ b/montepy/mcnp_object.py @@ -107,6 +107,7 @@ def __init__( except AttributeError: self._BLOCK_TYPE = montepy.input_parser.block_type.BlockType.DATA self._problem_ref = None + self._collection_ref = None self._parameters = ParametersNode() self._input = None if input: @@ -225,6 +226,13 @@ def _update_values(self): """ pass + @property + def _collection(self): + """Returns the parent collection this object belongs to, if any.""" + if self._collection_ref is not None: + return self._collection_ref() + return None + def format_for_mcnp_input(self, mcnp_version: tuple[int]) -> list[str]: """Creates a list of strings representing this MCNP_Object that can be written to file. @@ -490,7 +498,7 @@ def _grab_beginning_comment(self, padding: list[PaddingNode], last_obj=None): def __getstate__(self): state = self.__dict__.copy() - bad_keys = {"_problem_ref", "_parser"} + bad_keys = {"_problem_ref", "_collection_ref", "_parser"} for key in bad_keys: if key in state: del state[key] @@ -498,6 +506,7 @@ def __getstate__(self): def __setstate__(self, crunchy_data): crunchy_data["_problem_ref"] = None + crunchy_data["_collection_ref"] = None self.__dict__.update(crunchy_data) def clone(self) -> montepy.mcnp_object.MCNP_Object: diff --git a/montepy/numbered_mcnp_object.py b/montepy/numbered_mcnp_object.py index 6137b1ce..043b42f9 100644 --- a/montepy/numbered_mcnp_object.py +++ b/montepy/numbered_mcnp_object.py @@ -5,6 +5,7 @@ import itertools from typing import Union from numbers import Integral +import weakref from montepy.mcnp_object import MCNP_Object, InitInput import montepy @@ -14,22 +15,31 @@ def _number_validator(self, number): if number < 0: raise ValueError("number must be >= 0") - if self._problem: - obj_map = montepy.MCNP_Problem._NUMBERED_OBJ_MAP - try: - collection_type = obj_map[type(self)] - except KeyError as e: - found = False - for obj_class in obj_map: - if isinstance(self, obj_class): - collection_type = obj_map[obj_class] - found = True - break - if not found: - raise e - collection = getattr(self._problem, collection_type.__name__.lower()) - collection.check_number(number) - collection._update_number(self.number, number, self) + + # Only validate against collection if linked to a problem + if self._problem is not None: + if self._collection is not None: + collection = self._collection + else: + # Find collection via _problem + obj_map = montepy.MCNP_Problem._NUMBERED_OBJ_MAP + collection_type = obj_map.get(type(self)) + + if collection_type is None: + # Finding via inheritance + for obj_class in obj_map: + if isinstance(self, obj_class): + collection_type = obj_map[obj_class] + break + + if collection_type is not None: + collection = getattr(self._problem, collection_type.__name__.lower()) + else: + collection = None + + if collection is not None: + collection.check_number(number) + collection._update_number(self.number, number, self) class Numbered_MCNP_Object(MCNP_Object): diff --git a/montepy/numbered_object_collection.py b/montepy/numbered_object_collection.py index 52ceb32f..1d8a087e 100644 --- a/montepy/numbered_object_collection.py +++ b/montepy/numbered_object_collection.py @@ -178,6 +178,7 @@ def __init__( ) ) self.__num_cache[obj.number] = obj + obj._collection_ref = weakref.ref(self) self._objects = objects def link_to_problem(self, problem): @@ -196,8 +197,18 @@ def link_to_problem(self, problem): self._problem_ref = None else: self._problem_ref = weakref.ref(problem) + # Rebuild the number cache to ensure it reflects current object numbers + # after deepcopy/unpickling when the cache might be stale + self.__num_cache.clear() + for obj in self._objects: + self.__num_cache[obj.number] = obj for obj in self: obj.link_to_problem(problem) + # the _collection_ref that points to the main cells collection. + if problem is not None: + existing_coll = obj._collection + if existing_coll is None or existing_coll._problem is not problem: + obj._collection_ref = weakref.ref(self) @property def _problem(self): @@ -340,11 +351,15 @@ def extend(self, other_list): "The object in the list {obj} is not of type: {self._obj_class}" ) if obj.number in nums: - raise NumberConflictError( - ( - f"When adding to {type(self).__name__} there was a number collision due to " - f"adding {obj} which conflicts with {self[obj.number]}" + try: + conflicting_obj = self[obj.number] + conflict_msg = ( + f"adding {obj} which conflicts with {conflicting_obj}" ) + except KeyError: + conflict_msg = f"adding {obj} which conflicts with existing object number {obj.number}" + raise NumberConflictError( + f"When adding to {type(self).__name__} there was a number collision due to {conflict_msg}" ) nums.add(obj.number) for obj in other_list: @@ -496,6 +511,7 @@ def __internal_append(self, obj, **kwargs): ) self.__num_cache[obj.number] = obj self._objects.append(obj) + obj._collection_ref = weakref.ref(self) self._append_hook(obj, **kwargs) if self._problem: obj.link_to_problem(self._problem) @@ -507,6 +523,7 @@ def __internal_delete(self, obj, **kwargs): """ self.__num_cache.pop(obj.number, None) self._objects.remove(obj) + obj._collection_ref = None self._delete_hook(obj, **kwargs) def add(self, obj: Numbered_MCNP_Object): @@ -613,6 +630,7 @@ def append_renumber(self, obj, step=1): number = obj.number if obj.number > 0 else 1 if self._problem: obj.link_to_problem(self._problem) + obj._collection_ref = None try: self.append(obj) except (NumberConflictError, ValueError) as e: diff --git a/montepy/surfaces/half_space.py b/montepy/surfaces/half_space.py index 9717b7eb..ee63dcaa 100644 --- a/montepy/surfaces/half_space.py +++ b/montepy/surfaces/half_space.py @@ -187,6 +187,22 @@ def update_pointers(self, cells, surfaces, cell): if self.right is not None: self.right.update_pointers(cells, surfaces, cell) + def _set_cell(self, cell): + """Sets the _cell reference for this HalfSpace and all children. + + This is used during cloning to update the _cell references to point + to the cloned cell rather than the original (or a deepcopied intermediate). + + Parameters + ---------- + cell : Cell + the cell this HalfSpace should be tied to. + """ + self._cell = cell + self.left._set_cell(cell) + if self.right is not None: + self.right._set_cell(cell) + def _add_new_children_to_cell(self, other): """Adds the cells and surfaces from a new tree to this parent cell. @@ -676,6 +692,19 @@ def update_pointers(self, cells, surfaces, cell): "Cell", self._cell.number, "Surface", self._divider ) + def _set_cell(self, cell): + """Sets the _cell reference for this UnitHalfSpace. + + This is used during cloning to update the _cell references to point + to the cloned cell rather than the original (or a deepcopied intermediate). + + Parameters + ---------- + cell : Cell + the cell this UnitHalfSpace should be tied to. + """ + self._cell = cell + def _ensure_has_nodes(self): if self.node is None: if isinstance(self.divider, Integral): diff --git a/tests/test_numbered_collection.py b/tests/test_numbered_collection.py index e5335cd4..1eb7fcde 100644 --- a/tests/test_numbered_collection.py +++ b/tests/test_numbered_collection.py @@ -86,6 +86,7 @@ def test_extend(self, cp_simple_problem): extender = copy.deepcopy(extender) for surf in extender: surf._problem = None + surf._collection_ref = None surfaces[1000].number = 1 extender[0].number = 1000 extender[1].number = 70 @@ -161,6 +162,7 @@ def test_append_renumber(self, cp_simple_problem): cells.append_renumber(cell, "hi") cell = copy.deepcopy(cell) cell._problem = None + cell._collection_ref = None cell.number = 1 cells.append_renumber(cell) assert cell.number == 4 From 3ca4d871177588f34c5da2f50c94466dc03aaec4 Mon Sep 17 00:00:00 2001 From: DIGVIJAY <144053736+digvijay-y@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:25:27 +0000 Subject: [PATCH 2/5] changes to collection_ref --- demo/0_IntroToMontePy.ipynb | 5 +-- demo/1_PinCellCorrection_inter.ipynb | 3 +- demo/2_BuildAssembly_inter.ipynb | 1 + demo/3_AddingGuideTubes_inter.ipynb | 1 + demo/4_AxialDiscretize_inter.ipynb | 1 + demo/answers/1_PinCellCorrection.ipynb | 3 +- demo/answers/2_BuildAssembly.ipynb | 3 +- demo/answers/3_AddingGuideTubes.ipynb | 1 + demo/answers/4_AxialDiscretize.ipynb | 1 + doc/source/changelog.rst | 1 + montepy/cell.py | 3 -- montepy/mcnp_object.py | 11 +------ montepy/numbered_mcnp_object.py | 28 +++++++++++++--- montepy/numbered_object_collection.py | 45 +++++++++++++++----------- montepy/surfaces/half_space.py | 29 ----------------- 15 files changed, 67 insertions(+), 69 deletions(-) diff --git a/demo/0_IntroToMontePy.ipynb b/demo/0_IntroToMontePy.ipynb index 56312f06..c267ce89 100644 --- a/demo/0_IntroToMontePy.ipynb +++ b/demo/0_IntroToMontePy.ipynb @@ -228,8 +228,9 @@ }, "outputs": [], "source": [ - "#note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", + "# note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", @@ -428,7 +429,7 @@ }, "outputs": [], "source": [ - "#note this %pip is only needed for running in jupyterlite online\n", + "# note this %pip is only needed for running in jupyterlite online\n", "%pip install ipython\n", "from _config import IFrame\n", "\n", diff --git a/demo/1_PinCellCorrection_inter.ipynb b/demo/1_PinCellCorrection_inter.ipynb index 934ff656..2909c97a 100644 --- a/demo/1_PinCellCorrection_inter.ipynb +++ b/demo/1_PinCellCorrection_inter.ipynb @@ -121,8 +121,9 @@ }, "outputs": [], "source": [ - "#note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", + "# note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "# actually needed\n", diff --git a/demo/2_BuildAssembly_inter.ipynb b/demo/2_BuildAssembly_inter.ipynb index 89db4008..766331bf 100644 --- a/demo/2_BuildAssembly_inter.ipynb +++ b/demo/2_BuildAssembly_inter.ipynb @@ -65,6 +65,7 @@ "outputs": [], "source": [ "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", diff --git a/demo/3_AddingGuideTubes_inter.ipynb b/demo/3_AddingGuideTubes_inter.ipynb index 9be2c4be..dc6af1cb 100644 --- a/demo/3_AddingGuideTubes_inter.ipynb +++ b/demo/3_AddingGuideTubes_inter.ipynb @@ -105,6 +105,7 @@ "outputs": [], "source": [ "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", diff --git a/demo/4_AxialDiscretize_inter.ipynb b/demo/4_AxialDiscretize_inter.ipynb index c1cffafa..a0241fb9 100644 --- a/demo/4_AxialDiscretize_inter.ipynb +++ b/demo/4_AxialDiscretize_inter.ipynb @@ -71,6 +71,7 @@ "outputs": [], "source": [ "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", diff --git a/demo/answers/1_PinCellCorrection.ipynb b/demo/answers/1_PinCellCorrection.ipynb index edc645f7..7485e91b 100644 --- a/demo/answers/1_PinCellCorrection.ipynb +++ b/demo/answers/1_PinCellCorrection.ipynb @@ -120,8 +120,9 @@ }, "outputs": [], "source": [ - "#note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", + "# note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "# actually needed\n", diff --git a/demo/answers/2_BuildAssembly.ipynb b/demo/answers/2_BuildAssembly.ipynb index b651fa4f..ddb3e536 100644 --- a/demo/answers/2_BuildAssembly.ipynb +++ b/demo/answers/2_BuildAssembly.ipynb @@ -64,8 +64,9 @@ }, "outputs": [], "source": [ - "#note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", + "# note this install_montepy is only necessary for jupyterlite; You don't need to use it locally\n", "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", diff --git a/demo/answers/3_AddingGuideTubes.ipynb b/demo/answers/3_AddingGuideTubes.ipynb index f21e9bfa..bcb5c3ab 100644 --- a/demo/answers/3_AddingGuideTubes.ipynb +++ b/demo/answers/3_AddingGuideTubes.ipynb @@ -104,6 +104,7 @@ "outputs": [], "source": [ "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", diff --git a/demo/answers/4_AxialDiscretize.ipynb b/demo/answers/4_AxialDiscretize.ipynb index a70f87f7..8ffe339d 100644 --- a/demo/answers/4_AxialDiscretize.ipynb +++ b/demo/answers/4_AxialDiscretize.ipynb @@ -71,6 +71,7 @@ "outputs": [], "source": [ "from _config import install_montepy\n", + "\n", "install_montepy()\n", "\n", "import montepy\n", diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 3b805cbd..47e5ab58 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -14,6 +14,7 @@ MontePy Changelog **Features Added** +* Implement _collection_ref to link objects to their NumberedObjectCollection parent (:issue:`867`). * Added checking for additional input after the ``data`` block, and raising a warning if it exists (:issue:`525`). * Allow multiple universe fills to accept 2D MNCP lattices (:issue:`719`). * Make ``LatticeType.RECTANGULAR`` and ``LatticeType.HEXAHEDRAL`` synonymous (:issue:`808`). diff --git a/montepy/cell.py b/montepy/cell.py index f85e19e9..39095400 100644 --- a/montepy/cell.py +++ b/montepy/cell.py @@ -931,9 +931,6 @@ def num(obj): # This prevents number conflict checks against the original collection result._collection_ref = None result._problem_ref = None - # Update the geometry's _cell references to point to result, not the deepcopied intermediate - if result._geometry is not None: - result._geometry._set_cell(result) # copy geometry for special in special_keys: new_objs = [] diff --git a/montepy/mcnp_object.py b/montepy/mcnp_object.py index a9b00280..72943faf 100644 --- a/montepy/mcnp_object.py +++ b/montepy/mcnp_object.py @@ -107,7 +107,6 @@ def __init__( except AttributeError: self._BLOCK_TYPE = montepy.input_parser.block_type.BlockType.DATA self._problem_ref = None - self._collection_ref = None self._parameters = ParametersNode() self._input = None if input: @@ -226,13 +225,6 @@ def _update_values(self): """ pass - @property - def _collection(self): - """Returns the parent collection this object belongs to, if any.""" - if self._collection_ref is not None: - return self._collection_ref() - return None - def format_for_mcnp_input(self, mcnp_version: tuple[int]) -> list[str]: """Creates a list of strings representing this MCNP_Object that can be written to file. @@ -498,7 +490,7 @@ def _grab_beginning_comment(self, padding: list[PaddingNode], last_obj=None): def __getstate__(self): state = self.__dict__.copy() - bad_keys = {"_problem_ref", "_collection_ref", "_parser"} + bad_keys = {"_problem_ref", "_parser"} for key in bad_keys: if key in state: del state[key] @@ -506,7 +498,6 @@ def __getstate__(self): def __setstate__(self, crunchy_data): crunchy_data["_problem_ref"] = None - crunchy_data["_collection_ref"] = None self.__dict__.update(crunchy_data) def clone(self) -> montepy.mcnp_object.MCNP_Object: diff --git a/montepy/numbered_mcnp_object.py b/montepy/numbered_mcnp_object.py index 043b42f9..94028fad 100644 --- a/montepy/numbered_mcnp_object.py +++ b/montepy/numbered_mcnp_object.py @@ -35,11 +35,12 @@ def _number_validator(self, number): if collection_type is not None: collection = getattr(self._problem, collection_type.__name__.lower()) else: - collection = None + raise TypeError( + f"Could not find collection type for {type(self).__name__} in problem." + ) - if collection is not None: - collection.check_number(number) - collection._update_number(self.number, number, self) + collection.check_number(number) + collection._update_number(self.number, number, self) class Numbered_MCNP_Object(MCNP_Object): @@ -69,6 +70,7 @@ def __init__( self._number = self._generate_default_node(int, -1) super().__init__(input, parser) self._load_init_num(number) + self._collection_ref = None def _load_init_num(self, number): if number is not None: @@ -133,6 +135,24 @@ def _add_children_objs(self, problem): except (TypeError, AssertionError): prob_collect.append(child_collect) + @property + def _collection(self): + """Returns the parent collection this object belongs to, if any.""" + if self._collection_ref is not None: + return self._collection_ref() + return None + + def __getstate__(self): + state = super().__getstate__() + # Remove _collection_ref weakref as it can't be pickled + if "_collection_ref" in state: + del state["_collection_ref"] + return state + + def __setstate__(self, crunchy_data): + crunchy_data["_collection_ref"] = None + super().__setstate__(crunchy_data) + def clone(self, starting_number=None, step=None): """Create a new independent instance of this object with a new number. diff --git a/montepy/numbered_object_collection.py b/montepy/numbered_object_collection.py index 1d8a087e..a157ac41 100644 --- a/montepy/numbered_object_collection.py +++ b/montepy/numbered_object_collection.py @@ -178,7 +178,7 @@ def __init__( ) ) self.__num_cache[obj.number] = obj - obj._collection_ref = weakref.ref(self) + self._link_to_collection(obj) self._objects = objects def link_to_problem(self, problem): @@ -197,18 +197,13 @@ def link_to_problem(self, problem): self._problem_ref = None else: self._problem_ref = weakref.ref(problem) - # Rebuild the number cache to ensure it reflects current object numbers - # after deepcopy/unpickling when the cache might be stale - self.__num_cache.clear() - for obj in self._objects: - self.__num_cache[obj.number] = obj for obj in self: obj.link_to_problem(problem) # the _collection_ref that points to the main cells collection. if problem is not None: existing_coll = obj._collection if existing_coll is None or existing_coll._problem is not problem: - obj._collection_ref = weakref.ref(self) + self._link_to_collection(obj) @property def _problem(self): @@ -216,6 +211,26 @@ def _problem(self): return self._problem_ref() return None + def _link_to_collection(self, obj): + """Links the given object to this collection via a weakref. + + Parameters + ---------- + obj : Numbered_MCNP_Object + The object to link to this collection. + """ + obj._collection_ref = weakref.ref(self) + + def _unlink_from_collection(self, obj): + """Unlinks the given object from this collection. + + Parameters + ---------- + obj : Numbered_MCNP_Object + The object to unlink from this collection. + """ + obj._collection_ref = None + def __getstate__(self): state = self.__dict__.copy() weakref_key = "_problem_ref" @@ -351,15 +366,9 @@ def extend(self, other_list): "The object in the list {obj} is not of type: {self._obj_class}" ) if obj.number in nums: - try: - conflicting_obj = self[obj.number] - conflict_msg = ( - f"adding {obj} which conflicts with {conflicting_obj}" - ) - except KeyError: - conflict_msg = f"adding {obj} which conflicts with existing object number {obj.number}" raise NumberConflictError( - f"When adding to {type(self).__name__} there was a number collision due to {conflict_msg}" + f"When adding to {type(self).__name__} there was a number collision due to " + f"adding {obj} which conflicts with existing object number {obj.number}" ) nums.add(obj.number) for obj in other_list: @@ -511,7 +520,7 @@ def __internal_append(self, obj, **kwargs): ) self.__num_cache[obj.number] = obj self._objects.append(obj) - obj._collection_ref = weakref.ref(self) + self._link_to_collection(obj) self._append_hook(obj, **kwargs) if self._problem: obj.link_to_problem(self._problem) @@ -523,7 +532,7 @@ def __internal_delete(self, obj, **kwargs): """ self.__num_cache.pop(obj.number, None) self._objects.remove(obj) - obj._collection_ref = None + self._unlink_from_collection(obj) self._delete_hook(obj, **kwargs) def add(self, obj: Numbered_MCNP_Object): @@ -630,7 +639,7 @@ def append_renumber(self, obj, step=1): number = obj.number if obj.number > 0 else 1 if self._problem: obj.link_to_problem(self._problem) - obj._collection_ref = None + self._unlink_from_collection(obj) try: self.append(obj) except (NumberConflictError, ValueError) as e: diff --git a/montepy/surfaces/half_space.py b/montepy/surfaces/half_space.py index ee63dcaa..9717b7eb 100644 --- a/montepy/surfaces/half_space.py +++ b/montepy/surfaces/half_space.py @@ -187,22 +187,6 @@ def update_pointers(self, cells, surfaces, cell): if self.right is not None: self.right.update_pointers(cells, surfaces, cell) - def _set_cell(self, cell): - """Sets the _cell reference for this HalfSpace and all children. - - This is used during cloning to update the _cell references to point - to the cloned cell rather than the original (or a deepcopied intermediate). - - Parameters - ---------- - cell : Cell - the cell this HalfSpace should be tied to. - """ - self._cell = cell - self.left._set_cell(cell) - if self.right is not None: - self.right._set_cell(cell) - def _add_new_children_to_cell(self, other): """Adds the cells and surfaces from a new tree to this parent cell. @@ -692,19 +676,6 @@ def update_pointers(self, cells, surfaces, cell): "Cell", self._cell.number, "Surface", self._divider ) - def _set_cell(self, cell): - """Sets the _cell reference for this UnitHalfSpace. - - This is used during cloning to update the _cell references to point - to the cloned cell rather than the original (or a deepcopied intermediate). - - Parameters - ---------- - cell : Cell - the cell this UnitHalfSpace should be tied to. - """ - self._cell = cell - def _ensure_has_nodes(self): if self.node is None: if isinstance(self.divider, Integral): From de5c480a0482428c208dfaecdc14840983811bd1 Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Mon, 5 Jan 2026 16:29:19 -0600 Subject: [PATCH 3/5] Update changelog.rst with release staging. --- doc/source/changelog.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 47e5ab58..a5f3ab2c 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -5,6 +5,13 @@ MontePy Changelog 1.2 releases ============ +#Next Version# +-------------- + +**Performance Improvement** + +* Implement _collection_ref to link objects to their NumberedObjectCollection parent (:issue:`867`). + 1.2.0 -------------- @@ -14,7 +21,6 @@ MontePy Changelog **Features Added** -* Implement _collection_ref to link objects to their NumberedObjectCollection parent (:issue:`867`). * Added checking for additional input after the ``data`` block, and raising a warning if it exists (:issue:`525`). * Allow multiple universe fills to accept 2D MNCP lattices (:issue:`719`). * Make ``LatticeType.RECTANGULAR`` and ``LatticeType.HEXAHEDRAL`` synonymous (:issue:`808`). From 7fbe1b789ff59a1d381e82b156a9823a25bb3fdb Mon Sep 17 00:00:00 2001 From: DIGVIJAY <144053736+digvijay-y@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:49:26 +0000 Subject: [PATCH 4/5] post-review --- demo/1_PinCellCorrection_inter.ipynb | 12 +++++-- demo/answers/1_PinCellCorrection.ipynb | 18 ++++++++--- doc/source/changelog.rst | 7 ++++ montepy/numbered_mcnp_object.py | 45 +++++++++++--------------- montepy/numbered_object_collection.py | 4 +-- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/demo/1_PinCellCorrection_inter.ipynb b/demo/1_PinCellCorrection_inter.ipynb index 2909c97a..7577050c 100644 --- a/demo/1_PinCellCorrection_inter.ipynb +++ b/demo/1_PinCellCorrection_inter.ipynb @@ -235,7 +235,9 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.MCNP_Problem.html#montepy.MCNP_Problem\")" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.MCNP_Problem.html#montepy.MCNP_Problem\"\n", + ")" ] }, { @@ -305,7 +307,9 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.Surfaces.html#montepy.Surfaces\")" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.Surfaces.html#montepy.Surfaces\"\n", + ")" ] }, { @@ -375,7 +379,9 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.Surface.html#montepy.Surface\")" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.Surface.html#montepy.Surface\"\n", + ")" ] }, { diff --git a/demo/answers/1_PinCellCorrection.ipynb b/demo/answers/1_PinCellCorrection.ipynb index 7485e91b..6d6a6274 100644 --- a/demo/answers/1_PinCellCorrection.ipynb +++ b/demo/answers/1_PinCellCorrection.ipynb @@ -234,7 +234,11 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.MCNP_Problem.html#montepy.MCNP_Problem\", 800, 600)" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.MCNP_Problem.html#montepy.MCNP_Problem\",\n", + " 800,\n", + " 600,\n", + ")" ] }, { @@ -318,7 +322,9 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.Surfaces.html#montepy.Surfaces\")" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.Surfaces.html#montepy.Surfaces\"\n", + ")" ] }, { @@ -403,7 +409,9 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.Surface.html#montepy.Surface\")" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.Surface.html#montepy.Surface\"\n", + ")" ] }, { @@ -1562,7 +1570,9 @@ }, "outputs": [], "source": [ - "IFrame(\"https://www.montepy.org/en/stable/api/generated/montepy.MCNP_Problem.html#montepy.MCNP_Problem\")" + "IFrame(\n", + " \"https://www.montepy.org/en/stable/api/generated/montepy.MCNP_Problem.html#montepy.MCNP_Problem\"\n", + ")" ] }, { diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index a5f3ab2c..cb686aff 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -2,6 +2,13 @@ MontePy Changelog ***************** +unreleased +============ + +**Features Added** + +* Implement _collection_ref to link objects to their NumberedObjectCollection parent (:issue:`867`). + 1.2 releases ============ diff --git a/montepy/numbered_mcnp_object.py b/montepy/numbered_mcnp_object.py index 94028fad..c4c6759f 100644 --- a/montepy/numbered_mcnp_object.py +++ b/montepy/numbered_mcnp_object.py @@ -16,29 +16,9 @@ def _number_validator(self, number): if number < 0: raise ValueError("number must be >= 0") - # Only validate against collection if linked to a problem - if self._problem is not None: - if self._collection is not None: - collection = self._collection - else: - # Find collection via _problem - obj_map = montepy.MCNP_Problem._NUMBERED_OBJ_MAP - collection_type = obj_map.get(type(self)) - - if collection_type is None: - # Finding via inheritance - for obj_class in obj_map: - if isinstance(self, obj_class): - collection_type = obj_map[obj_class] - break - - if collection_type is not None: - collection = getattr(self._problem, collection_type.__name__.lower()) - else: - raise TypeError( - f"Could not find collection type for {type(self).__name__} in problem." - ) - + # Only validate against collection if linked to a collection + if self._collection is not None: + collection = self._collection collection.check_number(number) collection._update_number(self.number, number, self) @@ -69,8 +49,8 @@ def __init__( if not input: self._number = self._generate_default_node(int, -1) super().__init__(input, parser) - self._load_init_num(number) self._collection_ref = None + self._load_init_num(number) def _load_init_num(self, number): if number is not None: @@ -142,11 +122,24 @@ def _collection(self): return self._collection_ref() return None + def _link_to_collection(self, collection): + """Links this object to the given collection via a weakref. + + Parameters + ---------- + collection : NumberedObjectCollection + The collection to link this object to. + """ + self._collection_ref = weakref.ref(collection) + + def _unlink_from_collection(self): + """Unlinks this object from its collection.""" + self._collection_ref = None + def __getstate__(self): state = super().__getstate__() # Remove _collection_ref weakref as it can't be pickled - if "_collection_ref" in state: - del state["_collection_ref"] + state.pop("_collection_ref", None) return state def __setstate__(self, crunchy_data): diff --git a/montepy/numbered_object_collection.py b/montepy/numbered_object_collection.py index a157ac41..24036c8a 100644 --- a/montepy/numbered_object_collection.py +++ b/montepy/numbered_object_collection.py @@ -219,7 +219,7 @@ def _link_to_collection(self, obj): obj : Numbered_MCNP_Object The object to link to this collection. """ - obj._collection_ref = weakref.ref(self) + obj._link_to_collection(self) def _unlink_from_collection(self, obj): """Unlinks the given object from this collection. @@ -229,7 +229,7 @@ def _unlink_from_collection(self, obj): obj : Numbered_MCNP_Object The object to unlink from this collection. """ - obj._collection_ref = None + obj._unlink_from_collection() def __getstate__(self): state = self.__dict__.copy() From c202b8bb6df7fdc865773b7dc2983e63e3f8981c Mon Sep 17 00:00:00 2001 From: DIGVIJAY <144053736+digvijay-y@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:57:54 +0000 Subject: [PATCH 5/5] update --- doc/source/changelog.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index cb686aff..a5f3ab2c 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -2,13 +2,6 @@ MontePy Changelog ***************** -unreleased -============ - -**Features Added** - -* Implement _collection_ref to link objects to their NumberedObjectCollection parent (:issue:`867`). - 1.2 releases ============