Skip to content

Commit 91f5008

Browse files
authored
Merge pull request #668 from idaholab/material_clear
Implemented clear for material
2 parents 823c901 + b419f89 commit 91f5008

17 files changed

+896
-673
lines changed

doc/source/changelog.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ MontePy Changelog
2424
* Added function: :func:`~montepy.mcnp_problem.MCNP_Problem.parse` to parse arbitrary MCNP object (:issue:`88`).
2525
* An error is now raised when typos in object attributes are used, e.g., ``cell.nubmer`` (:issue:`508`).
2626
* Warnings are no longer raised for comments that exceed the maximum line lengths (:issue:`188`).
27-
* Particle type exceptions are now warnings, not errors (:issue:`381`).
27+
* Particle type exceptions are now warnings, not errors (:issue:`381`).
28+
* Added :func:`~montepy.data_inputs.material.Material.clear` to ``Material`` to clear out all nuclides (:issue:`665`).
2829
* Allow any ``Real`` type for floating point numbers and any ``Integral`` type for integer numbers during type enforcement (:issue:`679`).
2930

30-
3131
**Bugs Fixed**
3232

3333
* Made it so that a material created from scratch can be written to file (:issue:`512`).

montepy/cell.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import itertools
55
from typing import Union
66
from numbers import Integral, Real
7+
import warnings
78

89
from montepy.cells import Cells
910
from montepy.data_inputs import importance, fill, lattice_input, universe_input, volume
@@ -752,34 +753,40 @@ def cleanup_last_line(ret):
752753
return ret
753754

754755
ret = ""
755-
for key, node in self._tree.nodes.items():
756-
if key != "parameters":
757-
ret += node.format()
758-
else:
759-
printed_importance = False
760-
final_param = next(reversed(node.nodes.values()))
761-
for param in node.nodes.values():
762-
if param["classifier"].prefix.value.lower() in modifier_keywords:
763-
cls = modifier_keywords[
756+
with warnings.catch_warnings(record=True) as ws:
757+
758+
for key, node in self._tree.nodes.items():
759+
if key != "parameters":
760+
ret += node.format()
761+
else:
762+
printed_importance = False
763+
final_param = next(reversed(node.nodes.values()))
764+
for param in node.nodes.values():
765+
if (
764766
param["classifier"].prefix.value.lower()
765-
]
766-
attr, _ = self._INPUTS_TO_PROPERTY[cls]
767-
if attr == "_importance":
768-
if printed_importance:
769-
continue
770-
printed_importance = True
771-
# add trailing space to comment if necessary
772-
ret = cleanup_last_line(ret)
773-
ret += "\n".join(
774-
getattr(self, attr).format_for_mcnp_input(
775-
mcnp_version, param is not final_param
767+
in modifier_keywords
768+
):
769+
cls = modifier_keywords[
770+
param["classifier"].prefix.value.lower()
771+
]
772+
attr, _ = self._INPUTS_TO_PROPERTY[cls]
773+
if attr == "_importance":
774+
if printed_importance:
775+
continue
776+
printed_importance = True
777+
# add trailing space to comment if necessary
778+
ret = cleanup_last_line(ret)
779+
ret += "\n".join(
780+
getattr(self, attr).format_for_mcnp_input(
781+
mcnp_version, param is not final_param
782+
)
776783
)
777-
)
778-
else:
779-
# add trailing space to comment if necessary
780-
ret = cleanup_last_line(ret)
781-
ret += param.format()
782-
# check for accidental empty lines from subsequent cell modifiers that didn't print
784+
else:
785+
# add trailing space to comment if necessary
786+
ret = cleanup_last_line(ret)
787+
ret += param.format()
788+
# check for accidental empty lines from subsequent cell modifiers that didn't print
789+
self._flush_line_expansion_warning(ret, ws)
783790
ret = "\n".join([l for l in ret.splitlines() if l.strip()])
784791
return self.wrap_string_for_mcnp(ret, mcnp_version, True)
785792

montepy/data_inputs/material.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __setitem__(self, key, value):
6868
try:
6969
node = self._libraries[key]
7070
except KeyError:
71-
node = self._generate_default_node(key)
71+
node = self._generate_default_node(key, str(value))
7272
self._parent()._append_param_lib(node)
7373
self._libraries[key] = node
7474
node["data"].value = str(value)
@@ -79,7 +79,7 @@ def __delitem__(self, key):
7979
self._parent()._delete_param_lib(node)
8080

8181
def __str__(self):
82-
return str(self._libraries)
82+
return "\n".join([f"{key} = {value}" for key, value in self.items()])
8383

8484
def __iter__(self):
8585
return iter(self._libraries)
@@ -97,13 +97,13 @@ def _validate_key(key):
9797
return key
9898

9999
@staticmethod
100-
def _generate_default_node(key: LibraryType):
100+
def _generate_default_node(key: LibraryType, val: str):
101101
classifier = syntax_node.ClassifierNode()
102102
classifier.prefix = key.value
103103
ret = {
104104
"classifier": classifier,
105105
"seperator": syntax_node.PaddingNode(" = "),
106-
"data": syntax_node.ValueNode("", str),
106+
"data": syntax_node.ValueNode(val, str),
107107
}
108108
return syntax_node.SyntaxNode("mat library", ret)
109109

@@ -279,13 +279,12 @@ class Material(data_input.DataInputAbstract, Numbered_MCNP_Object):
279279
----------
280280
input : Union[Input, str]
281281
The Input syntax object this will wrap and parse.
282-
parser : MCNP_Parser
283-
The parser object to parse the input with.
284282
number : int
285283
The number to set for this object.
286284
"""
287285

288286
_parser = MaterialParser()
287+
_NEW_LINE_STR = "\n" + " " * DEFAULT_INDENT
289288

290289
def __init__(
291290
self,
@@ -367,6 +366,7 @@ def _append_param_lib(self, node: syntax_node.SyntaxNode):
367366
368367
This is called from _DefaultLibraries.
369368
"""
369+
self._ensure_has_ending_padding()
370370
self._tree["data"].append_param(node)
371371

372372
def _delete_param_lib(self, node: syntax_node.SyntaxNode):
@@ -535,9 +535,9 @@ def __setitem__(self, idx, newvalue):
535535
raise TypeError(f"Not a valid index. {idx} given.")
536536
old_vals = self._components[idx]
537537
self._check_valid_comp(newvalue)
538+
node_idx = self._tree["data"].nodes.index((old_vals[0]._tree, old_vals[1]), idx)
538539
# grab fraction
539540
old_vals[1].value = newvalue[1]
540-
node_idx = self._tree["data"].nodes.index((old_vals[0]._tree, old_vals[1]), idx)
541541
self._tree["data"].nodes[node_idx] = (newvalue[0]._tree, old_vals[1])
542542
self._components[idx] = (newvalue[0], old_vals[1])
543543

@@ -646,16 +646,41 @@ def append(self, nuclide_frac_pair: tuple[Nuclide, float]):
646646
self._check_valid_comp(nuclide_frac_pair)
647647
self._elements.add(nuclide_frac_pair[0].element)
648648
self._nuclei.add(nuclide_frac_pair[0].nucleus)
649+
# node for fraction
649650
node = self._generate_default_node(
650-
float, str(nuclide_frac_pair[1]), "\n" + " " * DEFAULT_INDENT
651+
float, str(nuclide_frac_pair[1]), self._NEW_LINE_STR
651652
)
652653
syntax_node.ValueNode(str(nuclide_frac_pair[1]), float)
653654
node.is_negatable_float = True
654655
nuclide_frac_pair = (nuclide_frac_pair[0], node)
655656
node.is_negative = not self._is_atom_fraction
656657
self._components.append(nuclide_frac_pair)
658+
self._ensure_has_ending_padding()
657659
self._tree["data"].append_nuclide(("_", nuclide_frac_pair[0]._tree, node))
658660

661+
def _ensure_has_ending_padding(self):
662+
def get_last_val_node():
663+
last_vals = self._tree["data"].nodes[-1][-1]
664+
if isinstance(last_vals, syntax_node.ValueNode):
665+
return last_vals
666+
return last_vals["data"]
667+
668+
if len(self._tree["data"]) == 0:
669+
return
670+
padding = get_last_val_node().padding
671+
672+
def add_new_line_padding():
673+
if padding is None:
674+
get_last_val_node().padding = syntax_node.PaddingNode(
675+
self._NEW_LINE_STR
676+
)
677+
else:
678+
padding.append(self._NEW_LINE_STR)
679+
680+
if padding:
681+
padding.check_for_graveyard_comments(True)
682+
add_new_line_padding()
683+
659684
def change_libraries(self, new_library: Union[str, Library]):
660685
"""Change the library for all nuclides in the material.
661686
@@ -906,6 +931,14 @@ def _contains_arb(
906931
)
907932
)
908933

934+
def clear(self):
935+
"""Clears all nuclide components from this material.
936+
937+
.. versionadded:: 1.0.0
938+
"""
939+
for _ in range(len(self)):
940+
del self[0]
941+
909942
def normalize(self):
910943
"""Normalizes the components fractions so that they sum to 1.0.
911944

montepy/errors.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,7 @@ def __init__(self, message):
200200
class LineExpansionWarning(Warning):
201201
"""Warning for when a field or line expands that may damage user formatting."""
202202

203-
def __init__(self, message):
204-
self.message = message
205-
super().__init__(self.message)
203+
pass
206204

207205

208206
def add_line_number_to_exception(error, broken_robot):

montepy/input_parser/input_syntax_reader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from montepy.utilities import is_comment
1616

1717

18-
reading_queue = []
18+
reading_queue = deque()
1919

2020

2121
def read_input_syntax(input_file, mcnp_version=DEFAULT_VERSION, replace=True):

montepy/input_parser/material_parser.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ def batch_gen():
4646
yield batch
4747

4848
for group in batch_gen():
49+
if group[0].type != str:
50+
group[0]._convert_to_str()
4951
new_list.append(("foo", *group))
5052
return new_list
5153

52-
@_("isotope_fraction", "number_sequence", "parameter", "mat_parameter")
54+
@_("isotope_fraction", "even_number_sequence", "parameter", "mat_parameter")
5355
def mat_datum(self, p):
5456
return p[0]
5557

montepy/input_parser/mcnp_input.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ def __str__(self):
3232
def __repr__(self):
3333
return f"Jump: {hex(id(self))}"
3434

35+
def __format__(self, spec):
36+
return format(str(self), spec)
37+
3538
def __bool__(self):
3639
raise TypeError("Jump doesn't have a truthiness or falsiness")
3740

montepy/input_parser/parser_base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,28 @@ def number_sequence(self, p):
202202
sequence.append(p[1])
203203
return sequence
204204

205+
@_(
206+
"numerical_phrase numerical_phrase",
207+
"shortcut_phrase",
208+
"even_number_sequence numerical_phrase numerical_phrase",
209+
"even_number_sequence shortcut_phrase",
210+
)
211+
def even_number_sequence(self, p):
212+
"""
213+
A list of numbers with an even number of elements*.
214+
215+
* shortcuts will break this.
216+
"""
217+
if not hasattr(p, "even_number_sequence"):
218+
sequence = syntax_node.ListNode("number sequence")
219+
sequence.append(p[0])
220+
else:
221+
sequence = p[0]
222+
if len(p) > 1:
223+
for idx in range(1, len(p)):
224+
sequence.append(p[idx])
225+
return sequence
226+
205227
@_("number_phrase", "null_phrase")
206228
def numerical_phrase(self, p):
207229
"""Any number, including 0, with its padding.

montepy/input_parser/syntax_node.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,17 @@ def _convert_to_enum(
10551055
self._value = enum_class(value)
10561056
self._formatter = self._FORMATTERS[format_type].copy()
10571057

1058+
def _convert_to_str(self):
1059+
"""Converts this ValueNode to being a string type.
1060+
1061+
.. versionadded:: 1.0.0
1062+
1063+
"""
1064+
self._type = str
1065+
self._value = str(self._token)
1066+
self._og_value = self._token
1067+
self._formatter = self._FORMATTERS[str].copy()
1068+
10581069
@property
10591070
def is_negatable_identifier(self):
10601071
"""Whether or not this value is a negatable identifier.
@@ -1292,7 +1303,14 @@ def format(self):
12921303
)
12931304
else:
12941305
temp = str(value)
1306+
end_line_padding = False
12951307
if self.padding:
1308+
for node in self.padding.nodes:
1309+
if node == "\n":
1310+
end_line_padding = True
1311+
break
1312+
if isinstance(node, CommentNode):
1313+
break
12961314
if self.padding.is_space(0):
12971315
# if there was and end space, and we ran out of space, and there isn't
12981316
# a saving space later on
@@ -1313,16 +1331,24 @@ def format(self):
13131331
buffer = "{temp:<{value_length}}{padding}".format(
13141332
temp=temp, padding=pad_str, **self._formatter
13151333
)
1316-
if len(buffer) > self._formatter["value_length"] and self._token is not None:
1317-
warning = LineExpansionWarning(
1318-
f"The value has expanded, and may change formatting. The original value was {self._token}, new value is {temp}."
1319-
)
1334+
"""
1335+
If:
1336+
1. expanded
1337+
2. had an original value
1338+
3. and value doesn't end in a new line (without a comment)
1339+
"""
1340+
if (
1341+
len(buffer) > self._formatter["value_length"]
1342+
and self._token is not None
1343+
and not end_line_padding
1344+
):
1345+
warning = LineExpansionWarning("")
13201346
warning.cause = "value"
13211347
warning.og_value = self._token
13221348
warning.new_value = temp
13231349
warnings.warn(
13241350
warning,
1325-
stacklevel=2,
1351+
category=LineExpansionWarning,
13261352
)
13271353
return buffer + extra_pad_str
13281354

montepy/mcnp_object.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import copy
55
import functools
66
import itertools as it
7-
import numpy as np
87
import sys
98
import textwrap
109
from typing import Union
@@ -237,9 +236,36 @@ def format_for_mcnp_input(self, mcnp_version: tuple[int]) -> list[str]:
237236
self.validate()
238237
self._update_values()
239238
self._tree.check_for_graveyard_comments()
240-
lines = self.wrap_string_for_mcnp(self._tree.format(), mcnp_version, True)
239+
message = None
240+
with warnings.catch_warnings(record=True) as ws:
241+
lines = self.wrap_string_for_mcnp(self._tree.format(), mcnp_version, True)
242+
self._flush_line_expansion_warning(lines, ws)
241243
return lines
242244

245+
def _flush_line_expansion_warning(self, lines, ws):
246+
if not ws:
247+
return
248+
message = f"""The input had a value expand that may change formatting.
249+
The new input was:\n\n"""
250+
for line in lines:
251+
message += f" {line}"
252+
width = 15
253+
message += f"\n\n {'old value': ^{width}s} {'new value': ^{width}s}"
254+
message += f"\n {'':-^{width}s} {'':-^{width}s}\n"
255+
olds = []
256+
news = []
257+
for w in ws:
258+
warning = w.message
259+
formatter = f" {{w.og_value: >{width}}} {{w.new_value: >{width}}}\n"
260+
message += formatter.format(w=warning)
261+
olds.append(warning.og_value)
262+
news.append(warning.new_value)
263+
if message is not None:
264+
warning = LineExpansionWarning(message)
265+
warning.olds = olds
266+
warning.news = news
267+
warnings.warn(warning, stacklevel=4)
268+
243269
@property
244270
def comments(self) -> list[PaddingNode]:
245271
"""The comments associated with this input if any.

0 commit comments

Comments
 (0)