Repository: terrapower/armi
Pull request #1653 (2024-02-22 22:26:31)

Inherit XS Settings when creating a new XS ID
## What is the change?

When the cross section group manager creates a new XS ID that is based on a previous XS ID within `CrossSectionGroupManager._getModifiedReprBlocks()`, the new settings will now be inherited from the settings for the original XS group.

## Why is the change being made?

Previously, a new XS ID would just be initialized with default cross section settings. Typically, when a user is creating a modified representative block with a new XS ID, they would want to keep the cross section settings from the original XS group.

---

## 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!
    (If a checkbox requires no action for this PR, put an `x` in the box.)
-->

- [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 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](https://terrapower.github.io/armi/developer/tooling.html#document-it) is still up-to-date in the `doc` folder.
- [x] If any [requirements](https://terrapower.github.io/armi/developer/tooling.html#watch-for-requirements) were affected, mention it in the [release notes](https://terrapower.github.io/armi/release/index.html).
- [x] The dependencies are still up-to-date in `pyproject.toml`.


jakehader: Hi @mgjarrett - I might be misremembering but I think that we need to handle the case where the original XS settings are a link to an external cross section file or files. In that case we might need to default to 0D. Maybe I am misrememberinf why I implemented it this way.
mgjarrett: > Hi @mgjarrett - I might be misremembering but I think that we need to handle the case where the original XS settings are a link to an external cross section file or files. In that case we might need to default to 0D. Maybe I am misrememberinf why I implemented it this way.

We shouldn't be trying to make a modified representative block based on a XS group that uses pregenerated cross sections. There is a warning logged in that situation:

https://github.com/terrapower/armi/blob/6163e942abe873129533b84a6f2efa632f260b47/armi/physics/neutronics/crossSectionGroupManager.py#L1255-L1261

No error is thrown, and you could even end up not getting the warning, depending on the arguments passed into `_getModifiedReprBlocks`. Maybe we need stronger controls around this. I can't tell if the intention is actually to prevent the user from passing in pregenerated XS to this method, or just to warn and allow it anyway.

Patch:
diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py
index dc7f2cbd..c0cfb808 100644
--- a/armi/physics/neutronics/crossSectionGroupManager.py
+++ b/armi/physics/neutronics/crossSectionGroupManager.py
@@ -58,14 +58,13 @@ import string
 
 import numpy
 
-from armi import context
-from armi import interfaces
-from armi import runLog
+from armi import context, interfaces, runLog
+from armi.physics.neutronics import LatticePhysicsFrequency
 from armi.physics.neutronics.const import CONF_CROSS_SECTION
+from armi.reactor import flags
 from armi.reactor.components import basicShapes
 from armi.reactor.flags import Flags
 from armi.utils.units import TRACE_NUMBER_DENSITY
-from armi.physics.neutronics import LatticePhysicsFrequency
 
 ORDER = interfaces.STACK_ORDER.BEFORE + interfaces.STACK_ORDER.CROSS_SECTIONS
 
@@ -874,11 +873,11 @@ class CrossSectionGroupManager(interfaces.Interface):
             reactor state.
         """
         # now that all cs settings are loaded, apply defaults to compound XS settings
-        from armi.physics.neutronics.settings import CONF_XS_BLOCK_REPRESENTATION
         from armi.physics.neutronics.settings import (
             CONF_DISABLE_BLOCK_TYPE_EXCLUSION_IN_XS_GENERATION,
+            CONF_LATTICE_PHYSICS_FREQUENCY,
+            CONF_XS_BLOCK_REPRESENTATION,
         )
-        from armi.physics.neutronics.settings import CONF_LATTICE_PHYSICS_FREQUENCY
 
         self.cs[CONF_CROSS_SECTION].setDefaults(
             self.cs[CONF_XS_BLOCK_REPRESENTATION],
@@ -1233,8 +1232,19 @@ class CrossSectionGroupManager(interfaces.Interface):
         for newXSID in modifiedReprBlocks:
             oldXSID = origXSIDsFromNew[newXSID]
             oldBlockCollection = blockCollectionByXsGroup[oldXSID]
+
+            # create a new block collection that inherits all of the properties
+            # and settings from oldBlockCollection.
+            if len(oldBlockCollection._validRepresentativeBlockTypes) > 0:
+                validBlockTypes = []
+                for flag in oldBlockCollection._validRepresentativeBlockTypes:
+                    validBlockTypes.append(flags._toString(Flags, flag))
+            else:
+                validBlockTypes = None
             newBlockCollection = oldBlockCollection.__class__(
-                oldBlockCollection.allNuclidesInProblem
+                oldBlockCollection.allNuclidesInProblem,
+                validBlockTypes=validBlockTypes,
+                averageByComponent=oldBlockCollection.averageByComponent,
             )
             newBlockCollectionsByXsGroup[newXSID] = newBlockCollection
         return newBlockCollectionsByXsGroup, modifiedReprBlocks, origXSIDsFromNew
@@ -1293,6 +1303,12 @@ class CrossSectionGroupManager(interfaces.Interface):
                 if b.getMicroSuffix() == origXSID:
                     b.p.xsType = newXSType
 
+            # copy XS settings to new XS ID
+            self.cs[CONF_CROSS_SECTION][newXSID] = copy.deepcopy(
+                self.cs[CONF_CROSS_SECTION][origXSID]
+            )
+            self.cs[CONF_CROSS_SECTION][newXSID].xsID = newXSID
+
         return modifiedReprBlocks, origXSIDsFromNew
 
     def getNextAvailableXsTypes(self, howMany=1, excludedXSTypes=None):
diff --git a/doc/release/0.3.rst b/doc/release/0.3.rst
index cbeac212..3151bdf8 100644
--- a/doc/release/0.3.rst
+++ b/doc/release/0.3.rst
@@ -52,6 +52,8 @@ Bug Fixes
 ---------
 #. Fixed four bugs with "corners up" hex grids. (`PR#1649 <https://github.com/terrapower/armi/pull/1649>`_)
 #. Fixed ``safeCopy`` to work on both Windows and Linux with strict permissions (`PR#1691 <https://github.com/terrapower/armi/pull/1691>`_)
+#. When creating a new XS group, inherit settings from initial group. (`PR#1653 <https://github.com/terrapower/armi/pull/1653>`_)
+
 #. TBD
 
 Quality Work


Test patch:
diff --git a/armi/physics/neutronics/tests/test_crossSectionManager.py b/armi/physics/neutronics/tests/test_crossSectionManager.py
index b574b319..f73b57fe 100644
--- a/armi/physics/neutronics/tests/test_crossSectionManager.py
+++ b/armi/physics/neutronics/tests/test_crossSectionManager.py
@@ -17,10 +17,10 @@ Test the cross section manager.
 
 :py:mod:`armi.physics.neutronics.crossSectionGroupManager`
 """
-from io import BytesIO
 import copy
 import os
 import unittest
+from io import BytesIO
 from unittest.mock import MagicMock
 
 from six.moves import cPickle
@@ -29,28 +29,25 @@ from armi import settings
 from armi.physics.neutronics import crossSectionGroupManager
 from armi.physics.neutronics.const import CONF_CROSS_SECTION
 from armi.physics.neutronics.crossSectionGroupManager import (
+    AverageBlockCollection,
     BlockCollection,
+    CrossSectionGroupManager,
     FluxWeightedAverageBlockCollection,
-)
-from armi.physics.neutronics.crossSectionGroupManager import (
     MedianBlockCollection,
-    AverageBlockCollection,
 )
-from armi.physics.neutronics.crossSectionGroupManager import CrossSectionGroupManager
+from armi.physics.neutronics.crossSectionSettings import XSModelingOptions
 from armi.physics.neutronics.fissionProductModel.tests import test_lumpedFissionProduct
 from armi.physics.neutronics.settings import (
-    CONF_XS_BLOCK_REPRESENTATION,
     CONF_LATTICE_PHYSICS_FREQUENCY,
+    CONF_XS_BLOCK_REPRESENTATION,
 )
 from armi.reactor.blocks import HexBlock
 from armi.reactor.flags import Flags
-from armi.reactor.tests import test_reactors, test_blocks
-from armi.tests import TEST_ROOT
-from armi.tests import mockRunLogs
+from armi.reactor.tests import test_blocks, test_reactors
+from armi.tests import TEST_ROOT, mockRunLogs
 from armi.utils import units
 from armi.utils.directoryChangers import TemporaryDirectoryChanger
 
-
 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
@@ -829,7 +826,25 @@ class TestCrossSectionGroupManager(unittest.TestCase):
         This tests that the XS ID of the new representative block is correct and that the
         compositions are identical between the original and the new representative blocks.
         """
-        _o, r = test_reactors.loadTestReactor(TEST_ROOT)
+        o, r = test_reactors.loadTestReactor(TEST_ROOT)
+        # set a few random non-default settings on AA to be copied to the new BA group
+        o.cs[CONF_CROSS_SECTION].update(
+            {
+                "AA": XSModelingOptions(
+                    "AA",
+                    geometry="0D",
+                    averageByComponent=True,
+                    xsMaxAtomNumber=60,
+                    criticalBuckling=False,
+                    xsPriority=2,
+                )
+            }
+        )
+        o.cs[CONF_CROSS_SECTION].setDefaults(
+            crossSectionGroupManager.AVERAGE_BLOCK_COLLECTION, ["fuel"]
+        )
+        aaSettings = o.cs[CONF_CROSS_SECTION]["AA"]
+        self.csm.cs = copy.deepcopy(o.cs)
         self.csm.createRepresentativeBlocks()
         unperturbedReprBlocks = copy.deepcopy(self.csm.representativeBlocks)
         self.assertNotIn("BA", unperturbedReprBlocks)
@@ -852,6 +867,14 @@ class TestCrossSectionGroupManager(unittest.TestCase):
         )
         self.assertEqual(origXSIDsFromNew["BA"], "AA")
 
+        # check that settings were copied correctly
+        baSettings = self.csm.cs[CONF_CROSS_SECTION]["BA"]
+        self.assertEqual(baSettings.xsID, "BA")
+        for setting, baSettingValue in baSettings.__dict__.items():
+            if setting == "xsID":
+                continue
+            self.assertEqual(baSettingValue, aaSettings.__dict__[setting])
+
     def test_interactBOL(self):
         """Test `BOL` lattice physics update frequency.