Repository: terrapower/armi
Pull request #1256 (2023-05-03 20:18:17)

Change b.axialExpTargetComponent to block parameter
## Description
Downstream analysis identified an issue where the blueprints specified axial expansion target component was unable to be used with database loads (i.e., restarts and snapshot runs). This PR changes the Block class instance variable, `b.axialExpTargetComponent`, to a registered block parameter that gets written to the db.

When a standard run to executed, the axial expansion target components for each block are stored as block parameters and written to the DB. When used for a DB load case, they are directly read off of the database. 

<!-- Mandatory: Please include a summary of the change and link to any related GitHub Issues.-->

---

## Checklist

<!--
    You (the pull requester) should put an `x` in the boxes below you have completed.
    If you're unsure about any of them, don't hesitate to ask. We're here to help!
    Learn what a "good PR" looks like here: https://terrapower.github.io/armi/developer/tooling.html#good-pull-requests
-->

- [x] This PR has only one purpose or idea.
- [x] Tests have been added/updated to verify that the new/changed code works.

<!-- 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/release/index.html) (location `doc/release/0.X.rst`) are up-to-date with any bug fixes or new features.
- [x] The documentation is still up-to-date in the `doc` folder.
- [x] The dependencies are still up-to-date in `setup.py`.



albeanth: Coverage failure seems to be coverage spray.... 
albeanth: Converting to draft while this waits it's turn in line within a downstream ARMI app.

Patch:
diff --git a/armi/reactor/blockParameters.py b/armi/reactor/blockParameters.py
index 2c9cb532..976a6597 100644
--- a/armi/reactor/blockParameters.py
+++ b/armi/reactor/blockParameters.py
@@ -347,6 +347,13 @@ def getBlockParameterDefinitions():
         )
 
     with pDefs.createBuilder() as pb:
+        pb.defParam(
+            "axialExpTargetComponent",
+            units="",
+            description="The name of the target component used for axial expansion and contraction of solid components.",
+            default="",
+            saveToDB=True,
+        )
 
         pb.defParam(
             "topIndex",
diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py
index ca2bc57a..bf66d900 100644
--- a/armi/reactor/blocks.py
+++ b/armi/reactor/blocks.py
@@ -97,7 +97,6 @@ class Block(composites.Composite):
 
         self.points = []
         self.macros = None
-        self.axialExpTargetComponent = None
 
         # flag to indicated when DerivedShape children must be updated.
         self.derivedMustUpdate = False
@@ -1505,7 +1504,7 @@ class Block(composites.Composite):
         --------
         armi.reactor.converters.axialExpansionChanger.py::ExpansionData::_setTargetComponents
         """
-        self.axialExpTargetComponent = targetComponent
+        self.p.axialExpTargetComponent = targetComponent.name
 
     def getPinCoordinates(self):
         """
diff --git a/armi/reactor/blueprints/blockBlueprint.py b/armi/reactor/blueprints/blockBlueprint.py
index b7d9771e..24d90186 100644
--- a/armi/reactor/blueprints/blockBlueprint.py
+++ b/armi/reactor/blueprints/blockBlueprint.py
@@ -216,12 +216,10 @@ class BlockBlueprint(yamlize.KeyedList):
                 b.setAxialExpTargetComp(components[self.axialExpTargetComponent])
             except KeyError as noMatchingComponent:
                 raise RuntimeError(
-                    "Block {0} --> axial expansion target component {1} specified in the blueprints does not "
-                    "match any component names. Revise axial expansion target component in blueprints "
-                    "to match the name of a component and retry.".format(
-                        b,
-                        self.axialExpTargetComponent,
-                    )
+                    f"Block {b} --> axial expansion target component {self.axialExpTargetComponent} "
+                    "specified in the blueprints does not match any component names. "
+                    "Revise axial expansion target component in blueprints "
+                    "to match the name of a component and retry."
                 ) from noMatchingComponent
 
         for c in components.values():
diff --git a/armi/reactor/converters/axialExpansionChanger.py b/armi/reactor/converters/axialExpansionChanger.py
index 131d5164..c919028b 100644
--- a/armi/reactor/converters/axialExpansionChanger.py
+++ b/armi/reactor/converters/axialExpansionChanger.py
@@ -283,14 +283,6 @@ class AxialExpansionChanger:
                     c.ztop = c.zbottom + c.height
                     # redistribute block boundaries if on the target component
                     if self.expansionData.isTargetComponent(c):
-                        if b.axialExpTargetComponent is None:
-                            runLog.debug(
-                                f"      Component {c} is target component (inferred)"
-                            )
-                        else:
-                            runLog.debug(
-                                f"      Component {c} is target component (blueprints defined)"
-                            )
                         b.p.ztop = c.ztop
 
             # see also b.setHeight()
@@ -799,8 +791,10 @@ class ExpansionData:
             target components should be determined on the fly.
         """
         for b in self._a:
-            if b.axialExpTargetComponent is not None:
-                self._componentDeterminesBlockHeight[b.axialExpTargetComponent] = True
+            if b.p.axialExpTargetComponent:
+                self._componentDeterminesBlockHeight[
+                    b.getComponentByName(b.p.axialExpTargetComponent)
+                ] = True
             elif b.hasFlags(Flags.PLENUM) or b.hasFlags(Flags.ACLP):
                 self.determineTargetComponent(b, Flags.CLAD)
             elif b.hasFlags(Flags.DUMMY):
@@ -811,7 +805,7 @@ class ExpansionData:
                 self.determineTargetComponent(b)
 
     def determineTargetComponent(self, b, flagOfInterest=None):
-        """appends target component to self._componentDeterminesBlockHeight
+        """determines target component, stores it on the block, and appends it to self._componentDeterminesBlockHeight
 
         Parameters
         ----------
@@ -860,6 +854,7 @@ class ExpansionData:
                 f"Block {b}\nflagOfInterest {flagOfInterest}\nComponents {componentWFlag}"
             )
         self._componentDeterminesBlockHeight[componentWFlag[0]] = True
+        b.p.axialExpTargetComponent = componentWFlag[0].name
 
     def _isFuelLocked(self, b):
         """physical/realistic implementation reserved for ARMI plugin
@@ -883,6 +878,7 @@ class ExpansionData:
         if c is None:
             raise RuntimeError(f"No fuel component within {b}!")
         self._componentDeterminesBlockHeight[c] = True
+        b.p.axialExpTargetComponent = c.name
 
     def isTargetComponent(self, c):
         """returns bool if c is a target component
diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst
index ec276380..5cdd58a5 100644
--- a/doc/release/0.2.rst
+++ b/doc/release/0.2.rst
@@ -22,6 +22,7 @@ What's new in ARMI
 #. Use ``minimumNuclideDensity`` setting when generating macroscopic XS.  (`PR#1248 <https://github.com/terrapower/armi/pull/1248>`_)
 #. Improve support for single component axial expansion and general cleanup of axial expansion unit tests. (`PR#1230 <https://github.com/terrapower/armi/pull/1230>`_)
 #. New cross section group representative block type for 1D cylindrical models. (`PR#1238 <https://github.com/terrapower/armi/pull/1238>`_)
+#. Store the axial expansion target component name as a block parameter. (`PR#1256 https://github.com/terrapower/armi/pull/1256`_) 
 #. When using non-uniform mesh, detailed fission/activation products have cross sections generated to avoid blocks without xs data. (`PR#1257 <https://github.com/terrapower/armi/pull/1257>`_)
 #. TBD
 


Test patch:
diff --git a/armi/reactor/converters/tests/test_axialExpansionChanger.py b/armi/reactor/converters/tests/test_axialExpansionChanger.py
index e1206d81..dc4d63cd 100644
--- a/armi/reactor/converters/tests/test_axialExpansionChanger.py
+++ b/armi/reactor/converters/tests/test_axialExpansionChanger.py
@@ -630,7 +630,7 @@ class TestExceptions(AxialExpansionTestBase, unittest.TestCase):
     def test_AssemblyAxialExpansionException(self):
         """test that negative height exception is caught"""
         # manually set axial exp target component for code coverage
-        self.a[0].axialExpTargetComponent = self.a[0][0]
+        self.a[0].p.axialExpTargetComponent = self.a[0][0].name
         temp = Temperature(self.a.getTotalHeight(), numTempGridPts=11, tempSteps=10)
         with self.assertRaises(ArithmeticError) as cm:
             for idt in range(temp.tempSteps):
@@ -705,12 +705,19 @@ class TestDetermineTargetComponent(AxialExpansionTestBase, unittest.TestCase):
         b.add(fuel)
         b.add(clad)
         b.add(self.coolant)
+        # make sure that b.p.axialExpTargetComponent is empty initially
+        self.assertFalse(b.p.axialExpTargetComponent)
         # call method, and check that target component is correct
         self.expData.determineTargetComponent(b)
         self.assertTrue(
             self.expData.isTargetComponent(fuel),
             msg=f"determineTargetComponent failed to recognize intended component: {fuel}",
         )
+        self.assertEqual(
+            b.p.axialExpTargetComponent,
+            fuel.name,
+            msg=f"determineTargetComponent failed to recognize intended component: {fuel}",
+        )
 
     def test_determineTargetComponentBlockWithMultipleFlags(self):
         """provides coverage for searching TARGET_FLAGS_IN_PREFERRED_ORDER with multiple flags"""
@@ -800,12 +807,14 @@ class TestDetermineTargetComponent(AxialExpansionTestBase, unittest.TestCase):
         # manually set target component
         b.setAxialExpTargetComp(duct)
         self.assertEqual(
-            b.axialExpTargetComponent,
-            duct,
+            b.p.axialExpTargetComponent,
+            duct.name,
         )
 
         # check that target component is stored on expansionData object correctly
-        self.expData._componentDeterminesBlockHeight[b.axialExpTargetComponent] = True
+        self.expData._componentDeterminesBlockHeight[
+            b.getComponentByName(b.p.axialExpTargetComponent)
+        ] = True
         self.assertTrue(self.expData.isTargetComponent(duct))