Repository: terrapower/armi
Pull request #1766 (2024-07-05 03:15:15)

Add timestep keyword to interface headers in stdout
## What is the change?

Remove `_expandCycleAndTimeNodeArgs` as a static method to get cycle/node info from the class. This is done to avoid massive API changes to `_interactAll` and `interactCoupled`. Add `timestep` keyword to printouts.

## Why is the change being made?

The context is in #1748. I wanted a reliable keyword to search for so I can quickly check where I'm at in a simulation, even during coupled iterations

---

## Checklist

<!--
    You (the pull requester) should put an `x` in the boxes below you have completed.

    (If a checkbox doesn't apply to your PR, check it anyway.)
-->

- [x] This PR has only [one purpose or idea](https://terrapower.github.io/armi/developer/tooling.html#one-idea-one-pr).
- [x] [Tests](https://terrapower.github.io/armi/developer/tooling.html#test-it) have been added/updated to verify any new/changed code.

<!-- Check the code quality -->

- [x] The code style follows [good practices](https://terrapower.github.io/armi/developer/standards_and_practices.html).
- [x] The commit message(s) follow [good practices](https://terrapower.github.io/armi/developer/tooling.html).

<!-- Check the project-level cruft -->

- [x] The [release notes](https://terrapower.github.io/armi/developer/tooling.html#add-release-notes) have been updated if necessary.
- [x] The [documentation](https://terrapower.github.io/armi/developer/tooling.html#document-it) is still up-to-date in the `doc` folder.
- [x] The dependencies are still up-to-date in `pyproject.toml`.

opotowsky: @john-science @albeanth let me know what you think the the approach I chose -- I chose it b/c  it limited the downstream updates needed to all `interactCoupled` methods

Patch:
diff --git a/armi/operators/operator.py b/armi/operators/operator.py
index 10da03c4..166f1650 100644
--- a/armi/operators/operator.py
+++ b/armi/operators/operator.py
@@ -487,9 +487,7 @@ class Operator:
 
         halt = False
 
-        cycleNodeTag = self._expandCycleAndTimeNodeArgs(
-            *args, interactionName=interactionName
-        )
+        cycleNodeTag = self._expandCycleAndTimeNodeArgs(interactionName)
         runLog.header(
             "===========  Triggering {} Event ===========".format(
                 interactionName + cycleNodeTag
@@ -497,9 +495,7 @@ class Operator:
         )
 
         for statePointIndex, interface in enumerate(activeInterfaces, start=1):
-            self.printInterfaceSummary(
-                interface, interactionName, statePointIndex, *args
-            )
+            self.printInterfaceSummary(interface, interactionName, statePointIndex)
 
             # maybe make this a context manager
             if printMemUsage:
@@ -546,41 +542,42 @@ class Operator:
         """
         pass
 
-    def printInterfaceSummary(self, interface, interactionName, statePointIndex, *args):
+    def printInterfaceSummary(self, interface, interactionName, statePointIndex):
         """
         Log which interaction point is about to be executed.
 
         This looks better as multiple lines but it's a lot easier to grep as one line.
         We leverage newlines instead of long banners to save disk space.
         """
-        nodeInfo = self._expandCycleAndTimeNodeArgs(
-            *args, interactionName=interactionName
-        )
+        nodeInfo = self._expandCycleAndTimeNodeArgs(interactionName)
         line = "=========== {:02d} - {:30s} {:15s} ===========".format(
             statePointIndex, interface.name, interactionName + nodeInfo
         )
         runLog.header(line)
 
-    @staticmethod
-    def _expandCycleAndTimeNodeArgs(*args, interactionName):
+    def _expandCycleAndTimeNodeArgs(self, interactionName):
         """Return text annotating information for current run event.
 
         Notes
         -----
         - Init, BOL, EOL: empty
-        - Everynode: (cycle, time node)
-        - BOC: cycle number
-        - Coupling: iteration number
+        - Everynode: cycle, time node
+        - BOC, EOC: cycle number
+        - Coupled: cycle, time node, iteration number
         """
-        cycleNodeInfo = ""
-        if args:
-            if len(args) == 1:
-                if interactionName == "Coupled":
-                    cycleNodeInfo = f" - iteration {args[0]}"
-                elif interactionName in ("BOC", "EOC"):
-                    cycleNodeInfo = f" - cycle {args[0]}"
-            else:
-                cycleNodeInfo = f" - cycle {args[0]}, node {args[1]}"
+        if interactionName == "Coupled":
+            cycleNodeInfo = (
+                f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode}"
+                f" - iteration {self.r.core.p.coupledIteration}"
+            )
+        elif interactionName in ("BOC", "EOC"):
+            cycleNodeInfo = f" - timestep: cycle {self.r.p.cycle}"
+        elif interactionName in ("Init", "BOL", "EOL"):
+            cycleNodeInfo = ""
+        else:
+            cycleNodeInfo = (
+                f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode}"
+            )
         return cycleNodeInfo
 
     def _debugDB(self, interactionName, interfaceName, statePointIndex=0):
diff --git a/doc/release/0.3.rst b/doc/release/0.3.rst
index f79e3240..d3e40f31 100644
--- a/doc/release/0.3.rst
+++ b/doc/release/0.3.rst
@@ -50,6 +50,7 @@ API Changes
 #. Removing the parameters ``outsideFuelRing`` and ``outsideFuelRingFluxFr``. (`PR#1700 <https://github.com/terrapower/armi/pull/1700>`_)
 #. Removing the setting ``doOrificedTH``. (`PR#1706 <https://github.com/terrapower/armi/pull/1706>`_)
 #. Changing the Doppler constant params to ``VOLUME_INTEGRATED``. (`PR#1659 <https://github.com/terrapower/armi/pull/1659>`_)
+#. Change ``Operator._expandCycleAndTimeNodeArgs`` to be a non-static method so it no longer needs to take arbitrary args (`PR#1766 <https://github.com/terrapower/armi/pull/1766>`_)
 #. TBD
 
 Bug Fixes


Test patch:
diff --git a/armi/cases/tests/test_cases.py b/armi/cases/tests/test_cases.py
index 6b94b49c..095cb9b6 100644
--- a/armi/cases/tests/test_cases.py
+++ b/armi/cases/tests/test_cases.py
@@ -234,7 +234,10 @@ class TestArmiCase(unittest.TestCase):
 
                 self.assertIn("Triggering BOL Event", mock.getStdout())
                 self.assertIn("xsGroups", mock.getStdout())
-                self.assertIn("Completed EveryNode - cycle 0", mock.getStdout())
+                self.assertIn(
+                    "Completed EveryNode - timestep: cycle 0, node 0 Event",
+                    mock.getStdout(),
+                )
 
     def test_clone(self):
         testTitle = "CLONE_TEST"
diff --git a/armi/operators/tests/test_operators.py b/armi/operators/tests/test_operators.py
index 2b2ab931..8d7ebe8b 100644
--- a/armi/operators/tests/test_operators.py
+++ b/armi/operators/tests/test_operators.py
@@ -543,32 +543,44 @@ settings:
 
 
 class TestInterfaceAndEventHeaders(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.o, cls.r = test_reactors.loadTestReactor(
+            inputFileName="smallestTestReactor/armiRunSmallest.yaml",
+            customSettings={CONF_TIGHT_COUPLING: True},
+        )
+        cls.r.p.cycle = 0
+        cls.r.p.timeNode = 1
+        cls.r.core.p.coupledIteration = 7
+
     def test_expandCycleAndTimeNodeArgs_Empty(self):
-        """When *args are empty, cycleNodeInfo should be an empty string."""
+        """When cycleNodeInfo should be an empty string."""
         for task in ["Init", "BOL", "EOL"]:
             self.assertEqual(
-                Operator._expandCycleAndTimeNodeArgs(interactionName=task), ""
+                self.o._expandCycleAndTimeNodeArgs(interactionName=task), ""
             )
 
-    def test_expandCycleAndTimeNodeArgs_OneArg(self):
-        """When *args is a single value, cycleNodeInfo should return the right string."""
-        cycle = 0
+    def test_expandCycleAndTimeNodeArgs_Cycle(self):
+        """When cycleNodeInfo should return only the cycle."""
         for task in ["BOC", "EOC"]:
             self.assertEqual(
-                Operator._expandCycleAndTimeNodeArgs(cycle, interactionName=task),
-                f" - cycle {cycle}",
+                self.o._expandCycleAndTimeNodeArgs(interactionName=task),
+                f" - timestep: cycle {self.r.p.cycle}",
             )
+
+    def test_expandCycleAndTimeNodeArgs_EveryNode(self):
+        """When cycleNodeInfo should return the cycle and node."""
         self.assertEqual(
-            Operator._expandCycleAndTimeNodeArgs(cycle, interactionName="Coupled"),
-            f" - iteration {cycle}",
+            self.o._expandCycleAndTimeNodeArgs(interactionName="EveryNode"),
+            f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode}",
         )
 
-    def test_expandCycleAndTimeNodeArgs_TwoArg(self):
-        """When *args is two values, cycleNodeInfo should return the right string."""
-        cycle, timeNode = 0, 0
+    def test_expandCycleAndTimeNodeArgs_Coupled(self):
+        """When cycleNodeInfo should return the cycle, node, and iteration number."""
         self.assertEqual(
-            Operator._expandCycleAndTimeNodeArgs(
-                cycle, timeNode, interactionName="EveryNode"
+            self.o._expandCycleAndTimeNodeArgs(interactionName="Coupled"),
+            (
+                f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode} "
+                f"- iteration {self.r.core.p.coupledIteration}"
             ),
-            f" - cycle {cycle}, node {timeNode}",
         )