Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0a066b1
Lazily replicated bug #892 as a test case.
MicahGale Mar 5, 2026
5dad2f5
Fixed bug that explicitly set was ignored.
MicahGale Mar 8, 2026
01cc4a1
Allowed importance.all without a problem.
MicahGale Mar 8, 2026
b1b99e5
Ignore default implicit importance, and change to caps in tests.
MicahGale Mar 8, 2026
7f1254f
Filter printing by particles in the problem.
MicahGale Mar 8, 2026
b7381f5
Updated test for default importance printing change.
MicahGale Mar 8, 2026
7a94701
Added #892 to the changelog.
MicahGale Mar 8, 2026
a7f1011
Merge branch 'develop' into 892-default-importances-are-not-printed-i…
MicahGale Mar 9, 2026
90165d8
Added photon importance back in with feedback.
MicahGale Mar 10, 2026
ac8c9ba
Added general mechanism to force all cells to print their cell mods.
MicahGale Mar 14, 2026
4da56e0
Made mechanism to set all or nothing for importances.
MicahGale Mar 14, 2026
84e1d44
Ensured that default values are also printed if necessary.
MicahGale Mar 14, 2026
024bc48
Made importance more dict like.
MicahGale Mar 14, 2026
6fd62ff
Updated test inputs to be valid for number of importances.
MicahGale Mar 14, 2026
ff330cd
Ensured that multiple importances don't collide when new ones are added.
MicahGale Mar 14, 2026
98d1e75
Prevented default importances from going to a new line.
MicahGale Mar 14, 2026
b3bc628
Updated complements_edge to test for skipping printing default import…
MicahGale Mar 14, 2026
bcb1a79
Updated to black 26
MicahGale Mar 14, 2026
285393c
Document keys, values, items for importance.
MicahGale Mar 14, 2026
2696c2d
updated annotation to use the propery namespace.
MicahGale Mar 14, 2026
fc4e3c8
Tested keys values items.
MicahGale Mar 15, 2026
a53566d
Moved problem tests to an importance integration test file.
MicahGale Mar 15, 2026
457c1de
Added test to replicate #913.
MicahGale Mar 15, 2026
6dee424
Updated URL for permanent redirect.
MicahGale Mar 16, 2026
4234880
Ignore Zenodo URLs due to possible GHA banning.
MicahGale Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ MontePy Changelog
**Bugs Fixed**

* Fixed a bug where ``append_renumber`` raised a ``TypeError`` when called with an object whose ``number`` is ``None`` (e.g. an object created with no arguments) (:issue:`880`).
* Fixed a bug where the importance of cells made from scratch are usually not printed to the output file (:pull:`921`).

**Feature Added**

* Added ``extend_renumber`` to ``NumberedObjectCollection`` with related test cases (:issue:`881`).
* Made :class:`montepy.data_inputs.Importance` more ``dict``-like with ``keys``, ``values``, and ``items`` functions (:pull:`921`).

1.3.0
--------------
Expand Down
9 changes: 9 additions & 0 deletions montepy/_cell_data_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class CellDataPrintController:

def __init__(self):
self._print_data = {}
self._all_or_nothing = {}

def __getitem__(self, key):
if not isinstance(key, str):
Expand All @@ -26,5 +27,13 @@ def __setitem__(self, key, value):
else:
raise KeyError(f"{key} is not a supported cell modifier in MCNP")

def _set_all_or_none(self, key, all_in=True):
""" """
self._all_or_nothing[key.upper()] = all_in

def _get_all_or_none(self, key):
""" """
return self._all_or_nothing.get(key.upper(), False)

def __str__(self):
return f"Print data in data block: {self._print_data}"
9 changes: 9 additions & 0 deletions montepy/data_inputs/cell_modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ def _is_worth_printing(self):
True if this object should be included in the output
"""
if self.in_cell_block:
# if has to be all or nothing printed
if (
hasattr(self, "_ALL_OR_NOTHING")
and self._problem
and self._problem.print_in_data_block._get_all_or_none(
self._class_prefix()
)
):
return True
return self.has_information
attr, _ = montepy.Cell._INPUTS_TO_PROPERTY[type(self)]
for cell in self._problem.cells:
Expand Down
87 changes: 81 additions & 6 deletions montepy/data_inputs/importance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import copy
import math
import warnings

import montepy
from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput
from montepy.exceptions import *
from montepy.constants import DEFAULT_VERSION, rel_tol, abs_tol
Expand All @@ -25,6 +27,12 @@
# * _real_tree : holds unique trees for every particle type. This is used in data block formatting.
# * _particle_importances : a dictionary of ParameterNodes that maps a particle to it's ParameterNode
# * _part_combos : a list of ParticleNode that show which particles were combined on the original input
#
# Defaults and all or nothing
#
# The default value is stored in _DEFAULT_IMP
#
#


class Importance(CellModifierInput):
Expand All @@ -44,6 +52,11 @@ class Importance(CellModifierInput):

_DEFAULT_IMP = 1.0

_ALL_OR_NOTHING = True
"""
Marks that if one cell has a value all cells must have values, no matter the default.
"""

def __init__(
self,
input: InitInput = None,
Expand All @@ -54,7 +67,7 @@ def __init__(
self._particle_importances = {}
self._real_tree = {}
self._part_combos = []
self._explicitly_set = False
self.__explicitly_set = False
super().__init__(input, in_cell_block, key, value)
if self.in_cell_block:
if key:
Expand Down Expand Up @@ -96,8 +109,6 @@ def _generate_default_cell_tree(self, particle=None):
particle = Particle.NEUTRON
else:
particles = syntax_node.ParticleNode("imp particle", particle.value.lower())
if self._problem:
particles.particles = self._problem.mode.particles
classifier.particles = particles
list_node = syntax_node.ListNode("imp data")
list_node.append(self._generate_default_node(float, self._DEFAULT_IMP))
Expand Down Expand Up @@ -128,12 +139,39 @@ def _has_number():
def _has_classifier():
return 2

def keys(self) -> collections.abc.Generator[Particle, None, None]:
"""
Returns a generator of all of the particles that have set values for this importance.

.. versionadded:: 1.4.0
"""
yield from self._particle_importances.keys()

def values(self) -> collections.abc.Generator[float, None, None]:
"""Returns a generator of the importance values set for this cell in the order in which they were set.

.. versionadded:: 1.4.0
"""
for tree in self._particle_importances.values():
yield tree["data"][0].value

def items(
self,
) -> collections.abc.Generator[tuple[Particle, float], None, None]:
"""Returns a generator of the particle and the importance value for all importances set for this cell.

.. versionadded:: 1.4.0
"""
for part in self:
yield (part, self._particle_importances[part]["data"][0].value)

@property
def has_information(self):
has_info = []
for part in self:
has_info.append(
not math.isclose(
self._explicitly_set
or not math.isclose(
self[part], self._DEFAULT_IMP, rel_tol=rel_tol, abs_tol=abs_tol
)
)
Expand Down Expand Up @@ -240,11 +278,21 @@ def push_to_cells(self):
data.nodes.append(value)

def _format_tree(self):
def ensure_has_end_space(ret, strip_new_lines=False):
if strip_new_lines:
ret = ret.rstrip("\n")
if ret and ret[-1] != " ":
# remove new lines that exist
return ret.rstrip() + " "
return ret

if self.in_cell_block:
particles_printed = set()
ret = ""
for particle in self:
if particle in particles_printed:
if particle in particles_printed or (
self._problem and particle not in self._problem.mode
):
continue
other_particles = self._particle_importances[particle][
"classifier"
Expand All @@ -263,8 +311,17 @@ def _format_tree(self):
to_remove.add(other_part)
for removee in to_remove:
other_particles.remove(removee)
ret = ensure_has_end_space(ret)
ret += self._particle_importances[particle].format()
particles_printed.add(particle)
# catch default values
if self._problem:
missed_defaults = self._problem.mode.particles - particles_printed
for particle in missed_defaults:
# trigger adding syntax tree
self[particle] = self._DEFAULT_IMP
ret = ensure_has_end_space(ret, strip_new_lines=True)
ret += self._particle_importances[particle].format()
return ret
else:
printed_parts = set()
Expand Down Expand Up @@ -299,10 +356,13 @@ def all(self, value):
value = float(value)
if value < 0.0:
raise ValueError("Importance must be ≥ 0.0")
self._explicitly_set = True
if self._problem:
self._explicitly_set = True
for particle in self._problem.mode:
self._particle_importances[particle]["data"][0].value = value
else:
for particle in montepy.Particle:
self[particle] = value

def _clear_data(self):
if not self.in_cell_block:
Expand Down Expand Up @@ -467,6 +527,21 @@ def _grab_beginning_comment(self, new_padding, last_obj=None):
new_padding
)

@property
def _explicitly_set(self):
return self.__explicitly_set

@_explicitly_set.setter
def _explicitly_set(self, value):
if value and self._problem:
self._problem.print_in_data_block._set_all_or_none(self._class_prefix())
self.__explicitly_set = value

def link_to_problem(self, problem):
super().link_to_problem(problem)
if problem and self._explicitly_set:
self._problem.print_in_data_block._set_all_or_none(self._class_prefix())


def _generate_default_data_tree(particle):
list_node = syntax_node.ListNode("number sequence")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ doc = [
]
format = ["black>=26.1,<27.0"]
develop = [
"montepy[test,doc,format]",
"montepy[test,doc,format,build]",
]
demos = ["jupyter", "ipykernel>=6,<8"]
demo-test = ["montepy[demos]", "papermill"]
Expand Down
1 change: 1 addition & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"bad_encoding.imcnp",
"unicode.imcnp",
"file2read.imcnp",
"test_not_imp.imcnp",
}

BAD_ENCODING_FILES = {
Expand Down
8 changes: 4 additions & 4 deletions tests/inputs/test_universe_data.imcnp
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ MCNP Test Model for MOAA
C cells
c
1 1 20
-1000 $ dollar comment
-1000 imp:n,p=1 $ dollar comment
2 2 8
-1005
imp:n=1
imp:p=0.5 $ graveyard
3 3 -1
1000 1005 -1010
1000 1005 -1010 imp:n,p=1
c
c foo end comment
c
99 0 1000:-1005 1010 $graveyard
99 0 1000:-1005 1010 imp:n,p=1$graveyard
5 0
#99
#99 imp:n,p=1

C surfaces
1000 SO 1
Expand Down
11 changes: 11 additions & 0 deletions tests/test_importance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import montepy
import os
import io
from pathlib import Path
import pytest
from montepy.cell import Cell
from montepy.particle import Particle
Expand Down Expand Up @@ -194,6 +195,16 @@ def test_importance_iter_getter_in(self, cell_with_importance):
for particle in imp:
assert particle in particles
assert imp[particle] == pytest.approx(1.0)
for part in imp.keys():
assert particle in particles
assert set(imp.keys()) == set(particles)
for val in imp.values():
assert val == pytest.approx(1.0)
assert len(list(imp.values())) == len(particles)
for part, val in imp.items():
assert particle in particles
assert val == pytest.approx(1.0)
assert len(list(imp.items())) == len(particles)
for particle in particles:
assert particle in imp
with pytest.raises(TypeError):
Expand Down
91 changes: 91 additions & 0 deletions tests/test_importance_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import montepy

import io
from pathlib import Path
import pytest


@pytest.fixture
def problem():
return montepy.read_input(Path("tests") / "inputs" / "test.imcnp")


def test_print_explicit_default_imp(problem): # based on #892

# Modify an existing cell. Importance remains.
problem.cells[1].geometry &= +problem.surfaces[1000]
# Cell 2 should give "imp:n=2.0 imp:p=2.0"
c2 = montepy.Cell(number=4)
c2.geometry = -problem.surfaces[1000] & +problem.surfaces[1005]
c2.importance.all = 2.0
problem.cells.append(c2)
# Cell 3 should give "imp:n=1.0 imp:p=1.0"
c3 = montepy.Cell(number=6)
c3.geometry = -problem.surfaces[1005] & +problem.surfaces[1010]
c3.importance.neutron = 1.0
c3.importance.photon = 1.0
problem.cells.append(c3)
# Cell 4 should give "imp:n=4.0 imp:p=1.0"
c4 = montepy.Cell(number=7)
c4.geometry = -problem.surfaces[1010]
c4.importance.neutron = 4.0
problem.cells.append(c4)
# Cell 5 DOES give "imp:n=5.0 imp:p=1.0"
c5 = montepy.Cell(number=8)
c5.geometry = -problem.surfaces[1015]
c5.importance.neutron = 5.0
c5.importance.photon = 1.0
problem.cells.append(c5)
# Ensure that this is set:
# Ensure that this is set:
problem.print_in_data_block["imp"] = False

stream = io.StringIO()
problem.write_problem(stream)
stream.seek(0)
for line in stream:
print(line.rstrip())
stream.seek(0)
new_problem = montepy.read_input(stream)
stream.close()
cells = new_problem.cells
for cell_num, imp_str in {
4: "IMP:n=2.0 IMP:p=2.0",
6: "IMP:n=1.0 IMP:p=1.0",
7: "IMP:n=4.0 IMP:p=1.0",
8: "IMP:n=5.0 IMP:p=1.0",
}.items():
print(cell_num, imp_str)
assert imp_str in cells[cell_num]._input.input_text


def test_splitting_part_combos(problem): # based on #913
# Works, but produces invalid MCNP
c2 = montepy.Cell(number=4)
c2.geometry = -problem.surfaces[1000] & +problem.surfaces[1005]
problem.cells.append(c2)
c2.importance.photon = 2.0
c2.importance.neutron = 2.0

# Crashes MontePy
c3 = montepy.Cell(number=6)
c3.geometry = -problem.surfaces[1015] & +problem.surfaces[1010]
problem.cells.append(c3)
c3.importance.photon = 3.0
c3.importance.neutron = 4.0

with io.StringIO() as stream:
problem.write_problem(stream)
stream.seek(0)
print(stream.read())
stream.seek(0)
new_problem = montepy.read_input(stream)
stream.close()

cells = new_problem.cells
for cell_num, imp_str in {
4: "IMP:n=2.0 IMP:p=2.0",
6: "IMP:n=4.0 IMP:p=3.0",
}.items():
print(cell_num, imp_str)
assert imp_str in cells[cell_num]._input.input_text
Loading
Loading