Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
=======
History
=======
2025.9.22: Bugfix: fixed floating-point precision issues in For loops
* This fix addresses floating-point precision issues in For loops that caused early
termination and generated malformed directory names (e.g., "3.00000000002" or
"2.999999999998"). The fix calculates the number of decimal places from loop
parameters and applies rounding to maintain precision throughout loop iterations.

2025.7.23: Bugfix: options not properly set if the loop is in a subflowchart
* The options of the steps in the loop were not set correctly if the loop was in a
subflowchart in e.g. a substep step or training step.
Expand Down
38 changes: 27 additions & 11 deletions loop_step/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
printer = printing.getPrinter("loop")


def _get_decimal_places(value):
"""Get the number of decimal places in a float."""
if not isinstance(value, float):
return 0
parts = str(value).split(".")
if len(parts) == 2:
return len(parts[1])
return 0


class BreakLoop(Exception):
"""Indicates that SEAMM should break from the loop"""

Expand Down Expand Up @@ -217,30 +227,33 @@ def run(self):
# Some local variables need each iteration

# See if loop variables are all integers
integers = True
start = P["start"]
if isinstance(start, str):
start = float(start)
if start.is_integer():
start = int(start)
else:
integers = False

step = P["step"]
if isinstance(step, str):
step = float(step)
if step.is_integer():
step = int(step)
else:
integers = False

end = P["end"]
if isinstance(end, str):
end = float(end)
if end.is_integer():
end = int(end)
else:
integers = False

if integers:
# If floating point, remove nasty extra digits
ndigits = max(
_get_decimal_places(start),
_get_decimal_places(step),
_get_decimal_places(end),
)
if ndigits > 0:
fmt = f".{ndigits}f"
else:
fmt = f"0{len(str(end))}d"

if self._loop_value is None:
Expand All @@ -261,6 +274,8 @@ def run(self):
while tmp <= end:
count += 1
tmp += step
if ndigits > 0:
tmp = round(tmp, ndigits)
self._loop_length = count
printer.important(
__(
Expand Down Expand Up @@ -520,12 +535,13 @@ def run(self):
self._loop_count += 1
if self._loop_count > 1:
self._loop_value += step
if ndigits > 0:
self._loop_value = round(self._loop_value, ndigits)

self.set_variable(P["variable"], self._loop_value)

# For integer loops, we can use the value for the directory names
if integers:
self._custom_directory_name = f"iter_{self._loop_value:{fmt}}"
# Use the value for the directory names
self._custom_directory_name = f"iter_{self._loop_value:{fmt}}"

# Set up the index variables
tmp = self.get_variable("_loop_indices")
Expand Down
Loading