Repository: terrapower/armi
Pull request #1347 (2023-07-17 20:41:47)

Relax Error Check for 1D cylindrical collection
## What is the change?

The `CylindricalComponentsAverageBlockCollection` was recently added: https://github.com/terrapower/armi/pull/1238

This collection is in some ways modeled after the `SlabComponentsAverageBlockCollection`, which is much older. The `_checkComponentConsistency` concept is similar, where each analogous component of each block in the collection is checked for consistency before homogenizing multiple blocks into one representative block. However, the new implementation checked that two components being homogenized into one have _exactly_ the same nuclides. The old implementation just checked key nuclides like U-235, Pu-239, Fe-56, and Na-23 to verify that components were similiar; i.e., structure being merged with structure (Fe-56), fuel with fuel (U-235/Pu-239), coolant with coolant (Na-23).

## Why is the change being made?

The new implementation was excessively picky about what can be homogenized together. Two similar assembly types where one has some extra trace isotopes in the cladding would throw a `ValueError` instead of just graciously being homogenized together.

This PR revises the implementation to make it functionally equivalent to the similar, older method.

Note: using Fe-56 as a proxy for structure and Na-23 for coolant is undesirably SFR-centric, but I'm not going to address that in this PR.

---

## 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 important changes.
- [x] The documentation is still up-to-date in the `doc` folder.
- [x] The dependencies are still up-to-date in `setup.py`.


keckler: @tjayasa FYI, I think your work was one of the motivations for this PR?
keckler: > Note: using Fe-56 as a proxy for structure and Na-23 for coolant is undesirably SFR-centric, but I'm not going to address that in this PR.

I'd sugget putting a comment in the code saying as much, for future readers.

Patch:
diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py
index cd475fcf..5ba6e91b 100644
--- a/armi/physics/neutronics/crossSectionGroupManager.py
+++ b/armi/physics/neutronics/crossSectionGroupManager.py
@@ -468,6 +468,9 @@ class CylindricalComponentsAverageBlockCollection(BlockCollection):
                 f"Blocks {b} and {repBlock} have differing number "
                 f"of components and cannot be homogenized"
             )
+        # Using Fe-56 as a proxy for structure and Na-23 as proxy for coolant is undesirably SFR-centric
+        # This should be generalized in the future, if possible
+        consistentNucs = {"PU239", "U238", "U235", "U234", "FE56", "NA23", "O16"}
         for c, repC in zip(sorted(b), sorted(repBlock)):
             compString = (
                 f"Component {repC} in block {repBlock} and component {c} in block {b}"
@@ -480,7 +483,10 @@ class CylindricalComponentsAverageBlockCollection(BlockCollection):
 
             theseNucs = set(c.getNuclides())
             thoseNucs = set(repC.getNuclides())
-            diffNucs = theseNucs.symmetric_difference(thoseNucs)
+            # check for any differences between which `consistentNucs` the components have
+            diffNucs = theseNucs.symmetric_difference(thoseNucs).intersection(
+                consistentNucs
+            )
             if diffNucs:
                 raise ValueError(
                     f"{compString} are in the same location, but nuclides "
diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst
index 9a48e6de..f796a64a 100644
--- a/doc/release/0.2.rst
+++ b/doc/release/0.2.rst
@@ -13,6 +13,7 @@ What's new in ARMI
 #. Removed redundant ``Material.name`` variable. (`PR#1335 <https://github.com/terrapower/armi/pull/1335>`_)
 #. Added SHA1 hashes of XS control files to the welcome text. (`PR#1334 <https://github.com/terrapower/armi/pull/1334>`_)
 #. Put back ``avgFuelTemp`` block parameter. (`PR#1362 <https://github.com/terrapower/armi/pull/1362>`_)
+#. Make cylindrical component block collection less strict about pre-homogenization checks. (`PR#1347 <https://github.com/terrapower/armi/pull/1347>`_)
 #. TBD
 
 Bug fixes


Test patch:
diff --git a/armi/physics/neutronics/tests/test_crossSectionManager.py b/armi/physics/neutronics/tests/test_crossSectionManager.py
index 21897222..af284386 100644
--- a/armi/physics/neutronics/tests/test_crossSectionManager.py
+++ b/armi/physics/neutronics/tests/test_crossSectionManager.py
@@ -376,8 +376,28 @@ class TestBlockCollectionComponentAverage1DCylinder(unittest.TestCase):
         nucDiffBlock.add(clad)
         nucDiffBlock.add(coolant)
 
+        # additional non-important nuclides
+        negligibleNucDiffBlock = HexBlock("blockNegligibleNucDiff")
+        negligibleNuc = {"N14": 1.0e-5}
+        modControl = baseComponents[0].getNumberDensities()
+        modClad = baseComponents[2].getNumberDensities()
+        modCoolant = baseComponents[4].getNumberDensities()
+        modControl.update(negligibleNuc)
+        modClad.update(negligibleNuc)
+        modCoolant.update(negligibleNuc)
+        mixedDensities = {
+            "control": modControl,
+            "clad": modClad,
+            "coolant": modCoolant,
+        }
+        control, clad, coolant = self._makeComponents(7, mixedDensities)
+        negligibleNucDiffBlock.add(control)
+        negligibleNucDiffBlock.add(clad)
+        negligibleNucDiffBlock.add(coolant)
+
         blockCollection._checkComponentConsistency(refBlock, matchingBlock)
         blockCollection._checkComponentConsistency(refBlock, unsortedBlock)
+        blockCollection._checkComponentConsistency(refBlock, negligibleNucDiffBlock)
         for b in (nonMatchingMultBlock, nonMatchingLengthBlock, nucDiffBlock):
             with self.assertRaises(ValueError):
                 blockCollection._checkComponentConsistency(refBlock, b)