From 3a0619ea8ec3f7265e64aaa1839c1f40677d1687 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 26 Sep 2024 13:43:30 -0700 Subject: [PATCH 01/41] Add option for partially heterogeneous 1D XS model. --- .../neutronics/crossSectionSettings.py | 8 +++ .../latticePhysics/latticePhysicsWriter.py | 1 + armi/reactor/converters/blockConverters.py | 60 +++++++++++++++---- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 22a58cc14..b2725041e 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -57,6 +57,7 @@ CONF_COMPONENT_AVERAGING = "averageByComponent" CONF_XS_MAX_ATOM_NUMBER = "xsMaxAtomNumber" CONF_MIN_DRIVER_DENSITY = "minDriverDensity" +CONF_PARTIALLY_HETEROGENEOUS = "partiallyHeterogeneous" class XSGeometryTypes(Enum): @@ -149,6 +150,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_PRIORITY, CONF_XS_MAX_ATOM_NUMBER, CONF_MIN_DRIVER_DENSITY, + CONF_PARTIALLY_HETEROGENEOUS, }, XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): { CONF_XSID, @@ -433,6 +435,11 @@ class XSModelingOptions: The minimum number density for nuclides included in driver material for a 1D lattice physics model. + partiallyHeterogeneous : bool + This is a lattice physics configuration option used to enable a partially + heterogeneous approximation for a 1D cylindrical model. Everything inside of the + duct will be treated as homogeneous. + Notes ----- Not all default attributes may be useful for your specific application and you may @@ -674,6 +681,7 @@ def setDefaults(self, blockRepresentation, validBlockTypes): CONF_HOMOGBLOCK: False, CONF_BLOCK_REPRESENTATION: crossSectionGroupManager.CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION, CONF_BLOCKTYPES: validBlockTypes, + CONF_PARTIALLY_HETEROGENEOUS: False, } elif self.geometry == XSGeometryTypes.getStr( XSGeometryTypes.TWO_DIMENSIONAL_HEX diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 85c2fe3ef..f57e7e187 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -113,6 +113,7 @@ def __init__( self.driverXsID = self.xsSettings.driverID self.numExternalRings = self.xsSettings.numExternalRings self.criticalBucklingSearchActive = self.xsSettings.criticalBuckling + self.partiallyHeterogeneous = self.xsSettings.partiallyHeterogeneous self.executeExclusive = self.xsSettings.xsExecuteExclusive self.priority = self.xsSettings.xsPriority diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index d78d1c48d..56cccd420 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -507,6 +507,11 @@ class HexComponentsToCylConverter(BlockAvgToCylConverter): duct/intercoolant pinComponentsRing1 | coolant | pinComponentsRing2 | coolant | ... | nonpins ... + The ``partiallyHeterogeneous`` option allows the user to treat everything inside the duct + as a single homogenized composition. This will significantly reduce the memory and runtime + required for MC2, and also provide an alternative approximation for the spatial self-shielding + effect on microscopic cross sections. + This converter expects the ``sourceBlock`` and ``driverFuelBlock`` to defined and for the ``sourceBlock`` to have a spatial grid defined. Additionally, both the ``sourceBlock`` and ``driverFuelBlock`` must be instances of HexBlocks. @@ -518,6 +523,7 @@ def __init__( driverFuelBlock=None, numExternalRings=None, mergeIntoClad=None, + partiallyHeterogeneous=False, ): BlockAvgToCylConverter.__init__( self, @@ -547,6 +553,7 @@ def __init__( ) self.pinPitch = sourceBlock.getPinPitch() self.mergeIntoClad = mergeIntoClad or [] + self.partiallyHeterogeneous = partiallyHeterogeneous self.interRingComponent = sourceBlock.getComponent(Flags.COOLANT, exact=True) self._remainingCoolantFillArea = self.interRingComponent.getArea() if not self.interRingComponent: @@ -581,10 +588,13 @@ def convert(self): numRings = self._sourceBlock.spatialGrid.getMinimumRings( self._sourceBlock.getNumPins() ) - pinComponents, nonPins = self._classifyComponents() - self._buildFirstRing(pinComponents) - for ring in range(2, numRings + 1): - self._buildNthRing(pinComponents, ring) + if self.partiallyHeterogeneous: + self._buildInsideDuct() + else: + pinComponents, nonPins = self._classifyComponents() + self._buildFirstRing(pinComponents) + for ring in range(2, numRings + 1): + self._buildNthRing(pinComponents, ring) self._buildNonPinRings(nonPins) self._addDriverFuelRings() @@ -644,6 +654,35 @@ def _classifyComponents(self): return list(sorted(pinComponents)), nonPins + def _buildInsideDuct(self): + """Build a homogenized material of the components inside the duct.""" + blockType = self._sourceBlock.getType() + blockName = f"Homogenized {blockType}" + newBlock = copy.deepcopy(self._sourceBlock) + + removeComponents = False + for c in sorted(newBlock.getComponents()): + if c.hasFlags(Flags.DUCT): + removeComponents = True + if removeComponents: + newBlock.remove(c) + else: + compFlags = compFlags | c.p.flags + + outerDiam = getOuterDiamFromIDAndArea(0.0, newBlock.getArea()) + + circle = components.Circle( + blockName, + "_Mixture", + newBlock.getAverageTempInC(), + newBlock.getAverageTempInC(), + id=0.0, + od=outerDiam, + mult=1, + ) + circle.setNumberDensities(newBlock.getNumberDensities()) + circle.p.flags = compFlags + def _buildFirstRing(self, pinComponents): """Add first ring of components to new block.""" for oldC in pinComponents: @@ -692,12 +731,13 @@ def _buildNonPinRings(self, nonPins): Also needs to add final coolant layer between the outer pins and the non-pins. Will crash if there are things that are not circles or hexes. """ - # fill in the last ring of coolant using the rest - coolInnerDiam = self.convertedBlock[-1].getDimension("od") - coolantOD = getOuterDiamFromIDAndArea( - coolInnerDiam, self._remainingCoolantFillArea - ) - self._addCoolantRing(coolantOD, " outer") + if not self.partiallyHeterogeneous: + # fill in the last ring of coolant using the rest + coolInnerDiam = self.convertedBlock[-1].getDimension("od") + coolantOD = getOuterDiamFromIDAndArea( + coolInnerDiam, self._remainingCoolantFillArea + ) + self._addCoolantRing(coolantOD, " outer") innerDiameter = coolantOD for i, hexagon in enumerate(sorted(nonPins)): From 3f5225352523874203a54296f1fb1c26c1c0f9f8 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 26 Sep 2024 15:00:45 -0700 Subject: [PATCH 02/41] Add partiallyHeterogeneous to settings validator --- armi/physics/neutronics/crossSectionSettings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index b2725041e..3ccfaa7df 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -196,6 +196,7 @@ def getStr(cls, typeSpec: Enum): vol.Optional(CONF_XS_MAX_ATOM_NUMBER): vol.Coerce(int), vol.Optional(CONF_MIN_DRIVER_DENSITY): vol.Coerce(float), vol.Optional(CONF_COMPONENT_AVERAGING): bool, + vol.Optional(CONF_PARTIALLY_HETEROGENEOUS): bool, } ) From 2da66ce60196874e6d64623f751f0e8bfb640b5b Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 26 Sep 2024 15:05:17 -0700 Subject: [PATCH 03/41] Add partiallyHeterogeneous to XSModelingOptions --- armi/physics/neutronics/crossSectionSettings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 3ccfaa7df..5b5b422c0 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -472,6 +472,7 @@ def __init__( xsMaxAtomNumber=None, averageByComponent=False, minDriverDensity=0.0, + partiallyHeterogeneous=False, ): self.xsID = xsID self.geometry = geometry @@ -494,6 +495,7 @@ def __init__( self.xsMaxAtomNumber = xsMaxAtomNumber self.minDriverDensity = minDriverDensity self.averageByComponent = averageByComponent + self.partiallyHeterogeneous = partiallyHeterogeneous # these are related to execution self.xsExecuteExclusive = xsExecuteExclusive self.xsPriority = xsPriority From 7336b6c3c0f1918de1627602555a7e84178077b2 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 26 Sep 2024 16:02:59 -0700 Subject: [PATCH 04/41] Update homogenization of inside duct area. --- armi/reactor/converters/blockConverters.py | 32 +++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 56cccd420..c784e20fa 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -588,10 +588,10 @@ def convert(self): numRings = self._sourceBlock.spatialGrid.getMinimumRings( self._sourceBlock.getNumPins() ) + pinComponents, nonPins = self._classifyComponents() if self.partiallyHeterogeneous: self._buildInsideDuct() else: - pinComponents, nonPins = self._classifyComponents() self._buildFirstRing(pinComponents) for ring in range(2, numRings + 1): self._buildNthRing(pinComponents, ring) @@ -660,17 +660,26 @@ def _buildInsideDuct(self): blockName = f"Homogenized {blockType}" newBlock = copy.deepcopy(self._sourceBlock) - removeComponents = False - for c in sorted(newBlock.getComponents()): + compFlags = newBlock.geFirstComponent().p.flags + for c in sorted(newBlock.getComponents(), reverse=True): + newBlock.remove(c, recomputeAreaFractions=False) if c.hasFlags(Flags.DUCT): - removeComponents = True - if removeComponents: - newBlock.remove(c) - else: - compFlags = compFlags | c.p.flags + ductIP = c.getDimension("ip") + break + + # add pitch defining component + newBlock.add( + components.Hexagon( + "pitchComponent", + "Void", + self._sourceBlock.getAverageTempInC(), + self._sourceBlock.getAverageTempInC(), + ip=ductIP, + op=ductIP, + ) + ) outerDiam = getOuterDiamFromIDAndArea(0.0, newBlock.getArea()) - circle = components.Circle( blockName, "_Mixture", @@ -682,6 +691,7 @@ def _buildInsideDuct(self): ) circle.setNumberDensities(newBlock.getNumberDensities()) circle.p.flags = compFlags + self.convertedBlock.add(circle) def _buildFirstRing(self, pinComponents): """Add first ring of components to new block.""" @@ -738,8 +748,10 @@ def _buildNonPinRings(self, nonPins): coolInnerDiam, self._remainingCoolantFillArea ) self._addCoolantRing(coolantOD, " outer") + innerDiameter = coolantOD + else: + innerDiameter = self.convertedBlock[-1].getDimension("od") - innerDiameter = coolantOD for i, hexagon in enumerate(sorted(nonPins)): outerDiam = getOuterDiamFromIDAndArea( innerDiameter, hexagon.getArea() From 18dbb7cf598e3ee12267eb1cdd91e4f8031081ca Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Fri, 27 Sep 2024 14:10:06 -0700 Subject: [PATCH 05/41] Add partiallyHeterogeneous to unit tests. --- .../neutronics/tests/test_crossSectionSettings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index 631af79c1..ddcc923ab 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -118,6 +118,7 @@ def test_homogeneousXsDefaultSettingAssignment(self): self.assertNotIn("YA", xsModel) self.assertEqual(xsModel["YA"].geometry, "0D") self.assertEqual(xsModel["YA"].criticalBuckling, True) + self.assertEqual(xsModel["YA"].partiallyHeterogeneous, False) def test_setDefaultSettingsByLowestBuGroupHomogeneous(self): # Initialize some micro suffix in the cross sections @@ -177,7 +178,12 @@ def test_optionalKey(self): """Test that optional key shows up with default value.""" cs = settings.Settings() xsModel = XSSettings() - da = XSModelingOptions("DA", geometry="1D cylinder", meshSubdivisionsPerCm=1.0) + da = XSModelingOptions( + "DA", + geometry="1D cylinder", + meshSubdivisionsPerCm=1.0, + partiallyHeterogeneous=True, + ) xsModel["DA"] = da xsModel.setDefaults( cs[CONF_XS_BLOCK_REPRESENTATION], @@ -185,6 +191,7 @@ def test_optionalKey(self): ) self.assertEqual(xsModel["DA"].mergeIntoClad, ["gap"]) self.assertEqual(xsModel["DA"].meshSubdivisionsPerCm, 1.0) + self.assertEqual(xsModel["DA"].partiallyHeterogeneous, True) def test_badCrossSections(self): with self.assertRaises(TypeError): From 8c9b098296b28d02a710ca2259a18fe45e44d160 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Fri, 27 Sep 2024 14:32:17 -0700 Subject: [PATCH 06/41] Fix typo / bugs --- armi/reactor/converters/blockConverters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index c784e20fa..6580c9535 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -660,14 +660,14 @@ def _buildInsideDuct(self): blockName = f"Homogenized {blockType}" newBlock = copy.deepcopy(self._sourceBlock) - compFlags = newBlock.geFirstComponent().p.flags + compFlags = newBlock.getComponents()[0].p.flags for c in sorted(newBlock.getComponents(), reverse=True): newBlock.remove(c, recomputeAreaFractions=False) if c.hasFlags(Flags.DUCT): ductIP = c.getDimension("ip") break - # add pitch defining component + # add pitch defining component with no area newBlock.add( components.Hexagon( "pitchComponent", From def556daa9e52405b07d5f90123479df2143ff05 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Mon, 30 Sep 2024 14:19:06 -0700 Subject: [PATCH 07/41] Assign flags from all components inside duct to homogeneous mixture. --- armi/reactor/converters/blockConverters.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 6580c9535..db441c6cd 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -660,12 +660,16 @@ def _buildInsideDuct(self): blockName = f"Homogenized {blockType}" newBlock = copy.deepcopy(self._sourceBlock) - compFlags = newBlock.getComponents()[0].p.flags + compFlags = newBlock.getComponent(Flags.COOLANT).p.flags + outsideDuct = True for c in sorted(newBlock.getComponents(), reverse=True): - newBlock.remove(c, recomputeAreaFractions=False) - if c.hasFlags(Flags.DUCT): - ductIP = c.getDimension("ip") - break + if outsideDuct: + newBlock.remove(c, recomputeAreaFractions=False) + if c.hasFlags(Flags.DUCT): + ductIP = c.getDimension("ip") + outsideDuct = False + else: + compFlags = compFlags | c.p.flags # add pitch defining component with no area newBlock.add( From e8544822ae584fca0553c533f9bcef371f48c3e4 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 10:46:06 -0700 Subject: [PATCH 08/41] Fix typo --- armi/nuclearDataIO/nuclearFileMetadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/nuclearDataIO/nuclearFileMetadata.py b/armi/nuclearDataIO/nuclearFileMetadata.py index 3726c305b..2e1ddb89a 100644 --- a/armi/nuclearDataIO/nuclearFileMetadata.py +++ b/armi/nuclearDataIO/nuclearFileMetadata.py @@ -252,4 +252,4 @@ def _getSkippedKeys(self, other, selfContainer, otherContainer, mergedData): class NuclideMetadata(_Metadata): - """Simple dictionary for providing metadata about how to read/write a nuclde to/from a file.""" + """Simple dictionary for providing metadata about how to read/write a nuclide to/from a file.""" From f1018d4c0c355e6c3b8f8fb00102807a364d82a5 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 26 Sep 2024 23:01:44 +0000 Subject: [PATCH 09/41] Update partially heterogeneous homogenization. --- armi/reactor/converters/blockConverters.py | 32 +++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 56cccd420..6580c9535 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -588,10 +588,10 @@ def convert(self): numRings = self._sourceBlock.spatialGrid.getMinimumRings( self._sourceBlock.getNumPins() ) + pinComponents, nonPins = self._classifyComponents() if self.partiallyHeterogeneous: self._buildInsideDuct() else: - pinComponents, nonPins = self._classifyComponents() self._buildFirstRing(pinComponents) for ring in range(2, numRings + 1): self._buildNthRing(pinComponents, ring) @@ -660,17 +660,26 @@ def _buildInsideDuct(self): blockName = f"Homogenized {blockType}" newBlock = copy.deepcopy(self._sourceBlock) - removeComponents = False - for c in sorted(newBlock.getComponents()): + compFlags = newBlock.getComponents()[0].p.flags + for c in sorted(newBlock.getComponents(), reverse=True): + newBlock.remove(c, recomputeAreaFractions=False) if c.hasFlags(Flags.DUCT): - removeComponents = True - if removeComponents: - newBlock.remove(c) - else: - compFlags = compFlags | c.p.flags + ductIP = c.getDimension("ip") + break + + # add pitch defining component with no area + newBlock.add( + components.Hexagon( + "pitchComponent", + "Void", + self._sourceBlock.getAverageTempInC(), + self._sourceBlock.getAverageTempInC(), + ip=ductIP, + op=ductIP, + ) + ) outerDiam = getOuterDiamFromIDAndArea(0.0, newBlock.getArea()) - circle = components.Circle( blockName, "_Mixture", @@ -682,6 +691,7 @@ def _buildInsideDuct(self): ) circle.setNumberDensities(newBlock.getNumberDensities()) circle.p.flags = compFlags + self.convertedBlock.add(circle) def _buildFirstRing(self, pinComponents): """Add first ring of components to new block.""" @@ -738,8 +748,10 @@ def _buildNonPinRings(self, nonPins): coolInnerDiam, self._remainingCoolantFillArea ) self._addCoolantRing(coolantOD, " outer") + innerDiameter = coolantOD + else: + innerDiameter = self.convertedBlock[-1].getDimension("od") - innerDiameter = coolantOD for i, hexagon in enumerate(sorted(nonPins)): outerDiam = getOuterDiamFromIDAndArea( innerDiameter, hexagon.getArea() From 43c3e2b43db80938c2026f6d2ca84db65a3cc8b5 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Mon, 30 Sep 2024 21:15:46 +0000 Subject: [PATCH 10/41] Put all flags into homogenized inside duct component. --- armi/reactor/converters/blockConverters.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 6580c9535..72cee6038 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -660,13 +660,16 @@ def _buildInsideDuct(self): blockName = f"Homogenized {blockType}" newBlock = copy.deepcopy(self._sourceBlock) - compFlags = newBlock.getComponents()[0].p.flags + compFlags = newBlock.getComponent(Flags.COOLANT).p.flags + outsideDuct = True for c in sorted(newBlock.getComponents(), reverse=True): - newBlock.remove(c, recomputeAreaFractions=False) - if c.hasFlags(Flags.DUCT): - ductIP = c.getDimension("ip") - break - + if outsideDuct: + newBlock.remove(c, recomputeAreaFractions=False) + if c.hasFlags(Flags.DUCT): + ductIP = c.getDimension("ip") + outsideDuct = False + else: + compFlags = compFlags | c.p.flags # add pitch defining component with no area newBlock.add( components.Hexagon( From c2417f66ad992267efb8ad95f708d4c3f2d0fe47 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 23:28:44 +0000 Subject: [PATCH 11/41] Introduce new Cylindrical block collection for partially het models --- .../neutronics/crossSectionGroupManager.py | 55 +++++++++++++ .../latticePhysics/latticePhysicsWriter.py | 8 +- armi/reactor/converters/blockConverters.py | 82 +++++++++++++------ 3 files changed, 118 insertions(+), 27 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 0131d625a..e8f82f683 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -63,6 +63,7 @@ from armi.physics.neutronics.const import CONF_CROSS_SECTION from armi.reactor import flags from armi.reactor.components import basicShapes +from armi.reactor.geometryConverters.blockConverters import stripComponents from armi.reactor.flags import Flags from armi.utils.units import TRACE_NUMBER_DENSITY @@ -648,6 +649,52 @@ def _getNucTempHelper(self): return nvt, nv +class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( + CylindricalComponentsAverageBlockCollection +): + """ + Creates a representative block for the purpose of cross section generation with a one- + dimensional cylindrical model where all material inside the duct is homogenized. + + .. impl:: Create partially heterogeneous representative blocks. + :id: I_ARMI_XSGM_CREATE_REPR_BLOCKS2 + :implements: R_ARMI_XSGM_CREATE_REPR_BLOCKS + + This class constructs representative blocks based on a volume-weighted average using + cylindrical blocks from an existing block list. Inheriting functionality from the abstract + :py:class:`Reactor ` + object, this class will construct representative blocks using averaged parameters of all + blocks in the given collection. Number density averages are computed at a component level. + Nuclide temperatures from a median block-average temperature are used and the average burnup + is evaluated across all blocks in the block list. + + The average nuclide temperatures are calculated only for the homogenized region inside of + the duct. For the non-homogenized regions, the MC2 writer uses the component temperatures. + + Notes + ----- + The representative block for this collection is the same as the parent. The only difference between + the two collection types is that this collection calculates average nuclide temperatures based only + on the components that are inside of the duct. + """ + + def _getNucTempHelper(self): + """All candidate blocks are used in the average.""" + nvt = np.zeros(len(self.allNuclidesInProblem)) + nv = np.zeros(len(self.allNuclidesInProblem)) + for block in self.getCandidateBlocks(): + wt = self.getWeight(block) + # remove the duct and intercoolant from the block before + # calculating average nuclide temps + newBlock, _mixtureFlags = stripComponents(block, Flags.DUCT) + nvtBlock, nvBlock = getBlockNuclideTemperatureAvgTerms( + newBlock, self.allNuclidesInProblem + ) + nvt += nvtBlock * wt + nv += nvBlock * wt + return nvt, nv + + class SlabComponentsAverageBlockCollection(BlockCollection): """ Creates a representative 1D slab block. @@ -1520,6 +1567,9 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION = "FluxWeightedAverage" SLAB_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DSlab" CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DCylinder" +CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = ( + "ComponentAverage1DCylinderPartiallyHeterogeneous" +) # Mapping between block collection string constants and their # respective block collection classes. @@ -1529,12 +1579,17 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION: FluxWeightedAverageBlockCollection, SLAB_COMPONENTS_BLOCK_COLLECTION: SlabComponentsAverageBlockCollection, CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION: CylindricalComponentsAverageBlockCollection, + CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION: CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection, } def blockCollectionFactory(xsSettings, allNuclidesInProblem): """Build a block collection based on user settings and input.""" blockRepresentation = xsSettings.blockRepresentation + if ( + blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION + ) and xsSettings.partiallyHeterogeneous: + blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION validBlockTypes = xsSettings.validBlockTypes averageByComponent = xsSettings.averageByComponent return BLOCK_COLLECTIONS[blockRepresentation]( diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index f57e7e187..5c56c573c 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -267,8 +267,12 @@ def _getAllNuclidesByCategory(self, component=None): continue # skip LFPs here but add individual FPs below. if isinstance(subjectObject, components.Component): - # Heterogeneous number densities and temperatures - nucTemperatureInC = subjectObject.temperatureInC + if self.partiallyHeterogeneous and "Homogenized" in subjectObject.name: + # Nuclide temperatures representing heterogeneous model component temperatures + nucTemperatureInC = self._getAvgNuclideTemperatureInC(nucName) + else: + # Heterogeneous number densities and temperatures + nucTemperatureInC = subjectObject.temperatureInC else: # Homogeneous number densities and temperatures nucTemperatureInC = self._getAvgNuclideTemperatureInC(nucName) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 72cee6038..6fc3b2155 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -658,30 +658,7 @@ def _buildInsideDuct(self): """Build a homogenized material of the components inside the duct.""" blockType = self._sourceBlock.getType() blockName = f"Homogenized {blockType}" - newBlock = copy.deepcopy(self._sourceBlock) - - compFlags = newBlock.getComponent(Flags.COOLANT).p.flags - outsideDuct = True - for c in sorted(newBlock.getComponents(), reverse=True): - if outsideDuct: - newBlock.remove(c, recomputeAreaFractions=False) - if c.hasFlags(Flags.DUCT): - ductIP = c.getDimension("ip") - outsideDuct = False - else: - compFlags = compFlags | c.p.flags - # add pitch defining component with no area - newBlock.add( - components.Hexagon( - "pitchComponent", - "Void", - self._sourceBlock.getAverageTempInC(), - self._sourceBlock.getAverageTempInC(), - ip=ductIP, - op=ductIP, - ) - ) - + newBlock, mixtureFlags = stripComponents(self._sourceBlock, Flags.DUCT) outerDiam = getOuterDiamFromIDAndArea(0.0, newBlock.getArea()) circle = components.Circle( blockName, @@ -693,7 +670,7 @@ def _buildInsideDuct(self): mult=1, ) circle.setNumberDensities(newBlock.getNumberDensities()) - circle.p.flags = compFlags + circle.p.flags = mixtureFlags self.convertedBlock.add(circle) def _buildFirstRing(self, pinComponents): @@ -878,3 +855,58 @@ def radiiFromRingOfRods(distToRodCenter, numRods, rodRadii, layout="hexagon"): rLast, bigRLast = rodRadius, distFromCenterComp return sorted(radiiFromRodCenter) + + +def stripComponents(block, compFlags): + """ + Remove all components from a block outside of the first component that matches compFlags. + + Parameters + ---------- + block : armi.reactor.blocks.Block + Source block from which to produce a modified copy + compFlags : armi.reactor.flags.Flags + Component flags to indicate which components to strip from the + block. All components outside of the first one that matches + compFlags are stripped. + + Returns + ------- + newBlock : armi.reactor.blocks.Block + Copy of source block with specified components stripped off + + Notes + ----- + This is often used for creating a partially heterogeneous representation + of a block. For example, one might want to treat everything inside of a + specific component (such as the duct) as homogenized, while keeping a + heterogeneous representation of the remaining components. + """ + newBlock = copy.deepcopy(block) + mixtureFlags = newBlock.getComponent(Flags.COOLANT).p.flags + innerMostComp = next( + i for i, c in enumerate(block.getComponents()) if c.hasFlags(compFlags) + ) + outsideComp = True + indexedComponents = [(i, c) for i, c in enumerate(sorted(block.getComponents()))] + for i, c in sorted(indexedComponents, reverse=True): + if outsideComp: + block.remove(c, recomputeAreaFractions=False) + if i == innerMostCompt: + ductIP = c.getDimension("ip") + outsideComp = False + else: + mixtureFlags = mixtureFlags | c.p.flags + + # add pitch defining component with no area + newBlock.add( + components.Hexagon( + "pitchComponent", + "Void", + self._sourceBlock.getAverageTempInC(), + self._sourceBlock.getAverageTempInC(), + ip=ductIP, + op=ductIP, + ) + ) + return newBlock, mixtureFlags From 9b2088e7418cd7b424eeb3517c66641cee1432ba Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 23:39:28 +0000 Subject: [PATCH 12/41] Fix import. --- .../neutronics/crossSectionGroupManager.py | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index e8f82f683..854834fce 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -63,7 +63,7 @@ from armi.physics.neutronics.const import CONF_CROSS_SECTION from armi.reactor import flags from armi.reactor.components import basicShapes -from armi.reactor.geometryConverters.blockConverters import stripComponents +from armi.reactor.converters.blockConverters import stripComponents from armi.reactor.flags import Flags from armi.utils.units import TRACE_NUMBER_DENSITY @@ -648,10 +648,7 @@ def _getNucTempHelper(self): nv += nvBlock * wt return nvt, nv - -class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( - CylindricalComponentsAverageBlockCollection -): +class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection(CylindricalComponentsAverageBlockCollection): """ Creates a representative block for the purpose of cross section generation with a one- dimensional cylindrical model where all material inside the duct is homogenized. @@ -668,13 +665,13 @@ class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( Nuclide temperatures from a median block-average temperature are used and the average burnup is evaluated across all blocks in the block list. - The average nuclide temperatures are calculated only for the homogenized region inside of + The average nuclide temperatures are calculated only for the homogenized region inside of the duct. For the non-homogenized regions, the MC2 writer uses the component temperatures. Notes ----- The representative block for this collection is the same as the parent. The only difference between - the two collection types is that this collection calculates average nuclide temperatures based only + the two collection types is that this collection calculates average nuclide temperatures based only on the components that are inside of the duct. """ @@ -684,7 +681,7 @@ def _getNucTempHelper(self): nv = np.zeros(len(self.allNuclidesInProblem)) for block in self.getCandidateBlocks(): wt = self.getWeight(block) - # remove the duct and intercoolant from the block before + # remove the duct and intercoolant from the block before # calculating average nuclide temps newBlock, _mixtureFlags = stripComponents(block, Flags.DUCT) nvtBlock, nvBlock = getBlockNuclideTemperatureAvgTerms( @@ -1567,9 +1564,7 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION = "FluxWeightedAverage" SLAB_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DSlab" CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DCylinder" -CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = ( - "ComponentAverage1DCylinderPartiallyHeterogeneous" -) +CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = "ComponentAverage1DCylinderPartiallyHeterogeneous" # Mapping between block collection string constants and their # respective block collection classes. @@ -1586,10 +1581,8 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): def blockCollectionFactory(xsSettings, allNuclidesInProblem): """Build a block collection based on user settings and input.""" blockRepresentation = xsSettings.blockRepresentation - if ( - blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION - ) and xsSettings.partiallyHeterogeneous: - blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION + if (blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION) and xsSettings.partiallyHeterogeneous: + blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION validBlockTypes = xsSettings.validBlockTypes averageByComponent = xsSettings.averageByComponent return BLOCK_COLLECTIONS[blockRepresentation]( From c87c3d5d7675b63300eb98c95cbd9fbdfbf82065 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 23:43:44 +0000 Subject: [PATCH 13/41] Fix a typo. --- armi/reactor/converters/blockConverters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 6fc3b2155..418134fa9 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -892,7 +892,7 @@ def stripComponents(block, compFlags): for i, c in sorted(indexedComponents, reverse=True): if outsideComp: block.remove(c, recomputeAreaFractions=False) - if i == innerMostCompt: + if i == innerMostComp: ductIP = c.getDimension("ip") outsideComp = False else: From 102e7c12e8d111376e59adf30892cfdd327193a0 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 23:45:49 +0000 Subject: [PATCH 14/41] Fix one more bug. --- armi/reactor/converters/blockConverters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 418134fa9..e8ceb8044 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -903,8 +903,8 @@ def stripComponents(block, compFlags): components.Hexagon( "pitchComponent", "Void", - self._sourceBlock.getAverageTempInC(), - self._sourceBlock.getAverageTempInC(), + block.getAverageTempInC(), + block.getAverageTempInC(), ip=ductIP, op=ductIP, ) From ab062f9a550c5a6914296b588f9b2aa91fe57ba2 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 16:53:22 -0700 Subject: [PATCH 15/41] Black formatting. --- .../neutronics/crossSectionGroupManager.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 854834fce..ea91b9d00 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -648,7 +648,10 @@ def _getNucTempHelper(self): nv += nvBlock * wt return nvt, nv -class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection(CylindricalComponentsAverageBlockCollection): + +class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( + CylindricalComponentsAverageBlockCollection +): """ Creates a representative block for the purpose of cross section generation with a one- dimensional cylindrical model where all material inside the duct is homogenized. @@ -665,13 +668,13 @@ class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection(Cylindri Nuclide temperatures from a median block-average temperature are used and the average burnup is evaluated across all blocks in the block list. - The average nuclide temperatures are calculated only for the homogenized region inside of + The average nuclide temperatures are calculated only for the homogenized region inside of the duct. For the non-homogenized regions, the MC2 writer uses the component temperatures. Notes ----- The representative block for this collection is the same as the parent. The only difference between - the two collection types is that this collection calculates average nuclide temperatures based only + the two collection types is that this collection calculates average nuclide temperatures based only on the components that are inside of the duct. """ @@ -681,7 +684,7 @@ def _getNucTempHelper(self): nv = np.zeros(len(self.allNuclidesInProblem)) for block in self.getCandidateBlocks(): wt = self.getWeight(block) - # remove the duct and intercoolant from the block before + # remove the duct and intercoolant from the block before # calculating average nuclide temps newBlock, _mixtureFlags = stripComponents(block, Flags.DUCT) nvtBlock, nvBlock = getBlockNuclideTemperatureAvgTerms( @@ -1564,7 +1567,9 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION = "FluxWeightedAverage" SLAB_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DSlab" CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DCylinder" -CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = "ComponentAverage1DCylinderPartiallyHeterogeneous" +CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = ( + "ComponentAverage1DCylinderPartiallyHeterogeneous" +) # Mapping between block collection string constants and their # respective block collection classes. @@ -1581,8 +1586,10 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): def blockCollectionFactory(xsSettings, allNuclidesInProblem): """Build a block collection based on user settings and input.""" blockRepresentation = xsSettings.blockRepresentation - if (blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION) and xsSettings.partiallyHeterogeneous: - blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION + if ( + blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION + ) and xsSettings.partiallyHeterogeneous: + blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION validBlockTypes = xsSettings.validBlockTypes averageByComponent = xsSettings.averageByComponent return BLOCK_COLLECTIONS[blockRepresentation]( From fdb2772899b86fd2cc21326251930382a0eb5e42 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 16:56:53 -0700 Subject: [PATCH 16/41] Fix a bug. --- armi/reactor/converters/blockConverters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index e8ceb8044..8de4ffdbb 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -891,7 +891,7 @@ def stripComponents(block, compFlags): indexedComponents = [(i, c) for i, c in enumerate(sorted(block.getComponents()))] for i, c in sorted(indexedComponents, reverse=True): if outsideComp: - block.remove(c, recomputeAreaFractions=False) + newBlock.remove(c, recomputeAreaFractions=False) if i == innerMostComp: ductIP = c.getDimension("ip") outsideComp = False From 716a7e211e7be1a818907041273074a14d262ad2 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Tue, 1 Oct 2024 23:45:49 +0000 Subject: [PATCH 17/41] Fix one more bug. --- .../neutronics/crossSectionGroupManager.py | 21 ++++++++++++------- armi/reactor/converters/blockConverters.py | 11 +++++----- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 854834fce..ea91b9d00 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -648,7 +648,10 @@ def _getNucTempHelper(self): nv += nvBlock * wt return nvt, nv -class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection(CylindricalComponentsAverageBlockCollection): + +class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( + CylindricalComponentsAverageBlockCollection +): """ Creates a representative block for the purpose of cross section generation with a one- dimensional cylindrical model where all material inside the duct is homogenized. @@ -665,13 +668,13 @@ class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection(Cylindri Nuclide temperatures from a median block-average temperature are used and the average burnup is evaluated across all blocks in the block list. - The average nuclide temperatures are calculated only for the homogenized region inside of + The average nuclide temperatures are calculated only for the homogenized region inside of the duct. For the non-homogenized regions, the MC2 writer uses the component temperatures. Notes ----- The representative block for this collection is the same as the parent. The only difference between - the two collection types is that this collection calculates average nuclide temperatures based only + the two collection types is that this collection calculates average nuclide temperatures based only on the components that are inside of the duct. """ @@ -681,7 +684,7 @@ def _getNucTempHelper(self): nv = np.zeros(len(self.allNuclidesInProblem)) for block in self.getCandidateBlocks(): wt = self.getWeight(block) - # remove the duct and intercoolant from the block before + # remove the duct and intercoolant from the block before # calculating average nuclide temps newBlock, _mixtureFlags = stripComponents(block, Flags.DUCT) nvtBlock, nvBlock = getBlockNuclideTemperatureAvgTerms( @@ -1564,7 +1567,9 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION = "FluxWeightedAverage" SLAB_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DSlab" CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DCylinder" -CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = "ComponentAverage1DCylinderPartiallyHeterogeneous" +CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = ( + "ComponentAverage1DCylinderPartiallyHeterogeneous" +) # Mapping between block collection string constants and their # respective block collection classes. @@ -1581,8 +1586,10 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): def blockCollectionFactory(xsSettings, allNuclidesInProblem): """Build a block collection based on user settings and input.""" blockRepresentation = xsSettings.blockRepresentation - if (blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION) and xsSettings.partiallyHeterogeneous: - blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION + if ( + blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION + ) and xsSettings.partiallyHeterogeneous: + blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION validBlockTypes = xsSettings.validBlockTypes averageByComponent = xsSettings.averageByComponent return BLOCK_COLLECTIONS[blockRepresentation]( diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 418134fa9..c102c398a 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -883,15 +883,16 @@ def stripComponents(block, compFlags): heterogeneous representation of the remaining components. """ newBlock = copy.deepcopy(block) + avgBlockTemp = block.getAverageTempInC() mixtureFlags = newBlock.getComponent(Flags.COOLANT).p.flags innerMostComp = next( - i for i, c in enumerate(block.getComponents()) if c.hasFlags(compFlags) + i for i, c in enumerate(newBlock.getComponents()) if c.hasFlags(compFlags) ) outsideComp = True - indexedComponents = [(i, c) for i, c in enumerate(sorted(block.getComponents()))] + indexedComponents = [(i, c) for i, c in enumerate(sorted(newBlock.getComponents()))] for i, c in sorted(indexedComponents, reverse=True): if outsideComp: - block.remove(c, recomputeAreaFractions=False) + newBlock.remove(c, recomputeAreaFractions=False) if i == innerMostComp: ductIP = c.getDimension("ip") outsideComp = False @@ -903,8 +904,8 @@ def stripComponents(block, compFlags): components.Hexagon( "pitchComponent", "Void", - self._sourceBlock.getAverageTempInC(), - self._sourceBlock.getAverageTempInC(), + avgBlockTemp, + avgBlockTemp, ip=ductIP, op=ductIP, ) From 752b256ad17e214d1eeec416daaeb74748998bf5 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Wed, 2 Oct 2024 00:01:33 +0000 Subject: [PATCH 18/41] Fix one more bug. --- armi/reactor/converters/blockConverters.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 8de4ffdbb..c102c398a 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -883,12 +883,13 @@ def stripComponents(block, compFlags): heterogeneous representation of the remaining components. """ newBlock = copy.deepcopy(block) + avgBlockTemp = block.getAverageTempInC() mixtureFlags = newBlock.getComponent(Flags.COOLANT).p.flags innerMostComp = next( - i for i, c in enumerate(block.getComponents()) if c.hasFlags(compFlags) + i for i, c in enumerate(newBlock.getComponents()) if c.hasFlags(compFlags) ) outsideComp = True - indexedComponents = [(i, c) for i, c in enumerate(sorted(block.getComponents()))] + indexedComponents = [(i, c) for i, c in enumerate(sorted(newBlock.getComponents()))] for i, c in sorted(indexedComponents, reverse=True): if outsideComp: newBlock.remove(c, recomputeAreaFractions=False) @@ -903,8 +904,8 @@ def stripComponents(block, compFlags): components.Hexagon( "pitchComponent", "Void", - block.getAverageTempInC(), - block.getAverageTempInC(), + avgBlockTemp, + avgBlockTemp, ip=ductIP, op=ductIP, ) From 9806e7876ec4154e5209a7ae7d2f269814967152 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Wed, 2 Oct 2024 00:45:30 +0000 Subject: [PATCH 19/41] Calculate average nuclide temperatures for partially het cylindrical block collection --- armi/physics/neutronics/crossSectionGroupManager.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index ea91b9d00..0a5d51deb 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -678,6 +678,16 @@ class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( on the components that are inside of the duct. """ + def _getNewBlock(self): + newBlock = copy.deepcopy(self._selectCandidateBlock()) + newBlock.name = "1D_CYL_PART_HET_AVG_" + newBlock.getMicroSuffix() + return newBlock + + def _makeRepresentativeBlock(self): + """Build a representative fuel block based on component number densities.""" + self.calcAvgNuclideTemperatures() + return CylindricalComponentsAverageBlockCollection._makeRepresentativeBlock(self) + def _getNucTempHelper(self): """All candidate blocks are used in the average.""" nvt = np.zeros(len(self.allNuclidesInProblem)) From 69d5acfb79bbf91f1bda246a0b60f6e698dd16c3 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Wed, 2 Oct 2024 00:45:30 +0000 Subject: [PATCH 20/41] Calculate average nuclide temperatures for partially het cylindrical block collection --- armi/physics/neutronics/crossSectionGroupManager.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index ea91b9d00..0a5d51deb 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -678,6 +678,16 @@ class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( on the components that are inside of the duct. """ + def _getNewBlock(self): + newBlock = copy.deepcopy(self._selectCandidateBlock()) + newBlock.name = "1D_CYL_PART_HET_AVG_" + newBlock.getMicroSuffix() + return newBlock + + def _makeRepresentativeBlock(self): + """Build a representative fuel block based on component number densities.""" + self.calcAvgNuclideTemperatures() + return CylindricalComponentsAverageBlockCollection._makeRepresentativeBlock(self) + def _getNucTempHelper(self): """All candidate blocks are used in the average.""" nvt = np.zeros(len(self.allNuclidesInProblem)) From 391901fed16d1f927a973779a4c123559070faea Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 3 Oct 2024 03:29:24 +0000 Subject: [PATCH 21/41] Add mergeIntoFuel option. --- armi/physics/neutronics/crossSectionSettings.py | 12 ++++++++++++ .../latticePhysics/latticePhysicsWriter.py | 1 + armi/reactor/converters/blockConverters.py | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 5b5b422c0..887381865 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -49,6 +49,7 @@ CONF_HOMOGBLOCK = "useHomogenizedBlockComposition" CONF_INTERNAL_RINGS = "numInternalRings" CONF_MERGE_INTO_CLAD = "mergeIntoClad" +CONF_MERGE_INTO_FUEL = "mergeIntoFuel" CONF_MESH_PER_CM = "meshSubdivisionsPerCm" CONF_REACTION_DRIVER = "nuclideReactionDriver" CONF_XSID = "xsID" @@ -137,6 +138,7 @@ def getStr(cls, typeSpec: Enum): CONF_XSID, CONF_GEOM, CONF_MERGE_INTO_CLAD, + CONF_MERGE_INTO_FUEL, CONF_DRIVER, CONF_HOMOGBLOCK, CONF_INTERNAL_RINGS, @@ -188,6 +190,7 @@ def getStr(cls, typeSpec: Enum): vol.Optional(CONF_INTERNAL_RINGS): vol.Coerce(int), vol.Optional(CONF_EXTERNAL_RINGS): vol.Coerce(int), vol.Optional(CONF_MERGE_INTO_CLAD): [str], + vol.Optional(CONF_MERGE_INTO_FUEL): [str], vol.Optional(CONF_XS_FILE_LOCATION): [str], vol.Optional(CONF_EXTERNAL_FLUX_FILE_LOCATION): str, vol.Optional(CONF_MESH_PER_CM): vol.Coerce(float), @@ -413,6 +416,12 @@ class XSModelingOptions: and is sometimes used to merge a "gap" or low-density region into a "clad" region to avoid numerical issues. + mergeIntoFuel : list of str + This is a lattice physics configuration option that is a list of component + names to merge into a "fuel" component. This is highly-design specific + and is sometimes used to merge a "gap" or low-density region into + a "fuel" region to avoid numerical issues. + meshSubdivisionsPerCm : float This is a lattice physics configuration option that can be used to control subregion meshing of the representative block in 1D problems. @@ -466,6 +475,7 @@ def __init__( numInternalRings=None, numExternalRings=None, mergeIntoClad=None, + mergeIntoFuel=None, meshSubdivisionsPerCm=None, xsExecuteExclusive=None, xsPriority=None, @@ -491,6 +501,7 @@ def __init__( self.numInternalRings = numInternalRings self.numExternalRings = numExternalRings self.mergeIntoClad = mergeIntoClad + self.mergeIntoFuel = mergeIntoFuel self.meshSubdivisionsPerCm = meshSubdivisionsPerCm self.xsMaxAtomNumber = xsMaxAtomNumber self.minDriverDensity = minDriverDensity @@ -678,6 +689,7 @@ def setDefaults(self, blockRepresentation, validBlockTypes): CONF_GEOM: self.geometry, CONF_DRIVER: "", CONF_MERGE_INTO_CLAD: ["gap"], + CONF_MERGE_INTO_FUEL: [], CONF_MESH_PER_CM: 1.0, CONF_INTERNAL_RINGS: 0, CONF_EXTERNAL_RINGS: 1, diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 5c56c573c..8e0ead4f3 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -110,6 +110,7 @@ def __init__( self.xsId = representativeBlock.getMicroSuffix() self.xsSettings = self.cs[CONF_CROSS_SECTION][self.xsId] self.mergeIntoClad = self.xsSettings.mergeIntoClad + self.mergeIntoFuel = self.xsSettings.mergeIntoFuel self.driverXsID = self.xsSettings.driverID self.numExternalRings = self.xsSettings.numExternalRings self.criticalBucklingSearchActive = self.xsSettings.criticalBuckling diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index c102c398a..97de38ca9 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -523,6 +523,7 @@ def __init__( driverFuelBlock=None, numExternalRings=None, mergeIntoClad=None, + mergeIntoFuel=None, partiallyHeterogeneous=False, ): BlockAvgToCylConverter.__init__( @@ -553,6 +554,7 @@ def __init__( ) self.pinPitch = sourceBlock.getPinPitch() self.mergeIntoClad = mergeIntoClad or [] + self.mergeIntoFuel = mergeIntoFuel or [] self.partiallyHeterogeneous = partiallyHeterogeneous self.interRingComponent = sourceBlock.getComponent(Flags.COOLANT, exact=True) self._remainingCoolantFillArea = self.interRingComponent.getArea() @@ -619,6 +621,10 @@ def _dissolveComponents(self): for componentName in self.mergeIntoClad: self.dissolveComponentIntoComponent(componentName, "clad") + # do user-input merges + for componentName in self.mergeIntoFuel: + self.dissolveComponentIntoComponent(componentName, "fuel") + def _classifyComponents(self): """ Figure out which components are in each pin ring and which are not. From c979b40f1134d550547fcb34faf8ea7529d84d21 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 3 Oct 2024 04:44:09 +0000 Subject: [PATCH 22/41] Update XSGM to work when components have zero area. --- armi/physics/neutronics/crossSectionGroupManager.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 0a5d51deb..fcba86d1d 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -624,7 +624,11 @@ def _getAverageComponentNucs(self, components, bWeights): weight = bWeight * c.getArea() totalWeight += weight densities += weight * np.array(c.getNuclideNumberDensities(allNucNames)) - return allNucNames, densities / totalWeight + if totalWeight > 0.0: + weightedDensities = densities / totalWeight + else: + weightedDensities = np.zeros_like(densities) + return allNucNames, weightedDensities def _orderComponentsInGroup(self, repBlock): """Order the components based on dimension and material type within the representative @@ -846,7 +850,11 @@ def _getAverageComponentNucs(self, components, bWeights): weight = bWeight * c.getArea() totalWeight += weight densities += weight * np.array(c.getNuclideNumberDensities(allNucNames)) - return allNucNames, densities / totalWeight + if totalWeight > 0.0: + weightedDensities = densities / totalWeight + else: + weightedDensities = np.zeros_like(densities) + return allNucNames, weightedDensities def _orderComponentsInGroup(self, repBlock): """Order the components based on dimension and material type within the representative block.""" From f511b5b50d29735570758e8dd74bac08c2f3ae3f Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 3 Oct 2024 21:11:03 +0000 Subject: [PATCH 23/41] Fix a component sorting issue. --- armi/reactor/converters/blockConverters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 97de38ca9..669339f0e 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -892,16 +892,16 @@ def stripComponents(block, compFlags): avgBlockTemp = block.getAverageTempInC() mixtureFlags = newBlock.getComponent(Flags.COOLANT).p.flags innerMostComp = next( - i for i, c in enumerate(newBlock.getComponents()) if c.hasFlags(compFlags) + i for i, c in enumerate(sorted(newBlock.getComponents())) if c.hasFlags(compFlags) ) outsideComp = True indexedComponents = [(i, c) for i, c in enumerate(sorted(newBlock.getComponents()))] for i, c in sorted(indexedComponents, reverse=True): if outsideComp: - newBlock.remove(c, recomputeAreaFractions=False) if i == innerMostComp: ductIP = c.getDimension("ip") outsideComp = False + newBlock.remove(c, recomputeAreaFractions=False) else: mixtureFlags = mixtureFlags | c.p.flags From cb39d04505388de43fedc487418e2f222d83f40f Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 3 Oct 2024 15:03:05 -0700 Subject: [PATCH 24/41] Fix a component sorting issue --- armi/reactor/converters/blockConverters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index c102c398a..022aa0dc1 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -886,7 +886,7 @@ def stripComponents(block, compFlags): avgBlockTemp = block.getAverageTempInC() mixtureFlags = newBlock.getComponent(Flags.COOLANT).p.flags innerMostComp = next( - i for i, c in enumerate(newBlock.getComponents()) if c.hasFlags(compFlags) + i for i, c in enumerate(sorted(newBlock.getComponents())) if c.hasFlags(compFlags) ) outsideComp = True indexedComponents = [(i, c) for i, c in enumerate(sorted(newBlock.getComponents()))] From 9b7165071e3f1175aad575fb67cb6a33419138a5 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 3 Oct 2024 15:03:36 -0700 Subject: [PATCH 25/41] Black formatting --- armi/physics/neutronics/crossSectionGroupManager.py | 4 +++- armi/reactor/converters/blockConverters.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 0a5d51deb..dd46f8fbd 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -686,7 +686,9 @@ def _getNewBlock(self): def _makeRepresentativeBlock(self): """Build a representative fuel block based on component number densities.""" self.calcAvgNuclideTemperatures() - return CylindricalComponentsAverageBlockCollection._makeRepresentativeBlock(self) + return CylindricalComponentsAverageBlockCollection._makeRepresentativeBlock( + self + ) def _getNucTempHelper(self): """All candidate blocks are used in the average.""" diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 022aa0dc1..2ff76e9b2 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -886,7 +886,9 @@ def stripComponents(block, compFlags): avgBlockTemp = block.getAverageTempInC() mixtureFlags = newBlock.getComponent(Flags.COOLANT).p.flags innerMostComp = next( - i for i, c in enumerate(sorted(newBlock.getComponents())) if c.hasFlags(compFlags) + i + for i, c in enumerate(sorted(newBlock.getComponents())) + if c.hasFlags(compFlags) ) outsideComp = True indexedComponents = [(i, c) for i, c in enumerate(sorted(newBlock.getComponents()))] From f641447a8a8ec9a81c3085735eac1203a73ee1b0 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Mon, 7 Oct 2024 16:00:26 -0700 Subject: [PATCH 26/41] Fix linting error. --- armi/physics/neutronics/crossSectionGroupManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 09d005062..e139d3f39 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -653,7 +653,7 @@ def _getNucTempHelper(self): return nvt, nv -class CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection( +class CylindricalComponentsPartHetAverageBlockCollection( CylindricalComponentsAverageBlockCollection ): """ @@ -1599,7 +1599,7 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION: FluxWeightedAverageBlockCollection, SLAB_COMPONENTS_BLOCK_COLLECTION: SlabComponentsAverageBlockCollection, CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION: CylindricalComponentsAverageBlockCollection, - CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION: CylindricalComponentsPartiallyHeterogeneousAverageBlockCollection, + CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION: CylindricalComponentsPartHetAverageBlockCollection, } From bc357664d8192b242a384fe2a95caa2101bba527 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Mon, 7 Oct 2024 16:07:15 -0700 Subject: [PATCH 27/41] Update comments and docstrings. --- armi/reactor/converters/blockConverters.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 67876613b..603b8e17c 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -509,9 +509,9 @@ class HexComponentsToCylConverter(BlockAvgToCylConverter): nonpins ... The ``partiallyHeterogeneous`` option allows the user to treat everything inside the duct - as a single homogenized composition. This will significantly reduce the memory and runtime - required for MC2, and also provide an alternative approximation for the spatial self-shielding - effect on microscopic cross sections. + as a single homogenized composition. This could significantly reduce the memory and runtime + required for the lattice physics solver, and also provide an alternative approximation for + the spatial self-shielding effect on microscopic cross sections. This converter expects the ``sourceBlock`` and ``driverFuelBlock`` to defined and for the ``sourceBlock`` to have a spatial grid defined. Additionally, both the ``sourceBlock`` @@ -618,11 +618,11 @@ def _dissolveComponents(self): ) self._remainingCoolantFillArea = self.interRingComponent.getArea() - # do user-input merges + # do user-input merges into cladding for componentName in self.mergeIntoClad: self.dissolveComponentIntoComponent(componentName, "clad") - # do user-input merges + # do user-input merges into fuel for componentName in self.mergeIntoFuel: self.dissolveComponentIntoComponent(componentName, "fuel") From 2211c3b28f2074de5eb3acfaaf75a8c03b127208 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Mon, 7 Oct 2024 16:09:57 -0700 Subject: [PATCH 28/41] Update a unit test. --- armi/physics/neutronics/tests/test_crossSectionSettings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index ddcc923ab..d7578df5f 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -192,6 +192,7 @@ def test_optionalKey(self): self.assertEqual(xsModel["DA"].mergeIntoClad, ["gap"]) self.assertEqual(xsModel["DA"].meshSubdivisionsPerCm, 1.0) self.assertEqual(xsModel["DA"].partiallyHeterogeneous, True) + self.assertEqual(xsModel["DA"].mergeIntoFuel, []) def test_badCrossSections(self): with self.assertRaises(TypeError): From 3fd64bf1e66b53dc7c93a33ffee0efdd9b3f686c Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Wed, 9 Oct 2024 16:05:55 -0700 Subject: [PATCH 29/41] Rename variable. --- armi/reactor/converters/blockConverters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 603b8e17c..99ff3d666 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -510,7 +510,7 @@ class HexComponentsToCylConverter(BlockAvgToCylConverter): The ``partiallyHeterogeneous`` option allows the user to treat everything inside the duct as a single homogenized composition. This could significantly reduce the memory and runtime - required for the lattice physics solver, and also provide an alternative approximation for + required for the lattice physics solver, and also provide an alternative approximation for the spatial self-shielding effect on microscopic cross sections. This converter expects the ``sourceBlock`` and ``driverFuelBlock`` to defined and for @@ -902,7 +902,7 @@ def stripComponents(block, compFlags): for i, c in sorted(indexedComponents, reverse=True): if outsideComp: if i == innerMostComp: - ductIP = c.getDimension("ip") + compIP = c.getDimension("ip") outsideComp = False newBlock.remove(c, recomputeAreaFractions=False) else: @@ -915,8 +915,8 @@ def stripComponents(block, compFlags): "Void", avgBlockTemp, avgBlockTemp, - ip=ductIP, - op=ductIP, + ip=compIP, + op=compIP, ) ) return newBlock, mixtureFlags From 5bf38fe73f230f62469fd311829ad3b013fe0cc7 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 10 Oct 2024 12:06:37 -0700 Subject: [PATCH 30/41] Add option to split trace isotopes from 1D to 0D. --- armi/physics/neutronics/crossSectionSettings.py | 10 ++++++++++ .../neutronics/latticePhysics/latticePhysicsWriter.py | 1 + 2 files changed, 11 insertions(+) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 887381865..8ea7d5dc5 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -59,6 +59,7 @@ CONF_XS_MAX_ATOM_NUMBER = "xsMaxAtomNumber" CONF_MIN_DRIVER_DENSITY = "minDriverDensity" CONF_PARTIALLY_HETEROGENEOUS = "partiallyHeterogeneous" +CONF_SPLIT_TRACE_ISOTOPES = "splitTraceIsotopes" class XSGeometryTypes(Enum): @@ -450,6 +451,12 @@ class XSModelingOptions: heterogeneous approximation for a 1D cylindrical model. Everything inside of the duct will be treated as homogeneous. + splitTraceIsotopes : bool + This is a lattice physics configuration option used to enable a separate 0D fuel + cross section calculation for trace fission products when using a 1D cross section + model. This can significantly reduce the memory and run time required for the 1D + model. + Notes ----- Not all default attributes may be useful for your specific application and you may @@ -483,6 +490,7 @@ def __init__( averageByComponent=False, minDriverDensity=0.0, partiallyHeterogeneous=False, + splitTraceIsotopes=False, ): self.xsID = xsID self.geometry = geometry @@ -507,6 +515,7 @@ def __init__( self.minDriverDensity = minDriverDensity self.averageByComponent = averageByComponent self.partiallyHeterogeneous = partiallyHeterogeneous + self.splitTraceIsotopes = splitTraceIsotopes # these are related to execution self.xsExecuteExclusive = xsExecuteExclusive self.xsPriority = xsPriority @@ -697,6 +706,7 @@ def setDefaults(self, blockRepresentation, validBlockTypes): CONF_BLOCK_REPRESENTATION: crossSectionGroupManager.CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION, CONF_BLOCKTYPES: validBlockTypes, CONF_PARTIALLY_HETEROGENEOUS: False, + CONF_SPLIT_TRACE_ISOTOPES: False, } elif self.geometry == XSGeometryTypes.getStr( XSGeometryTypes.TWO_DIMENSIONAL_HEX diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 8e0ead4f3..3699c2f4c 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -115,6 +115,7 @@ def __init__( self.numExternalRings = self.xsSettings.numExternalRings self.criticalBucklingSearchActive = self.xsSettings.criticalBuckling self.partiallyHeterogeneous = self.xsSettings.partiallyHeterogeneous + self.splitTraceIsotopes = self.xsSettings.splitTraceIsotopes self.executeExclusive = self.xsSettings.xsExecuteExclusive self.priority = self.xsSettings.xsPriority From be1fd4e8f075e6334ca782da893dcd01bed4e4a3 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 10 Oct 2024 12:17:44 -0700 Subject: [PATCH 31/41] Add splitTraceIsotopes as a valid input option. --- armi/physics/neutronics/crossSectionSettings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 8ea7d5dc5..a86771a07 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -154,6 +154,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_MAX_ATOM_NUMBER, CONF_MIN_DRIVER_DENSITY, CONF_PARTIALLY_HETEROGENEOUS, + CONF_SPLIT_TRACE_ISOTOPES, }, XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): { CONF_XSID, @@ -201,6 +202,7 @@ def getStr(cls, typeSpec: Enum): vol.Optional(CONF_MIN_DRIVER_DENSITY): vol.Coerce(float), vol.Optional(CONF_COMPONENT_AVERAGING): bool, vol.Optional(CONF_PARTIALLY_HETEROGENEOUS): bool, + vol.Optional(CONF_SPLIT_TRACE_ISOTOPES): bool, } ) From 4c50fac249b39596ba7d3f2552e48cd57e59a892 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 10 Oct 2024 14:15:13 -0700 Subject: [PATCH 32/41] Add splitTraceIsotopes to unit test --- armi/physics/neutronics/tests/test_crossSectionSettings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index d7578df5f..61df3f238 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -119,6 +119,7 @@ def test_homogeneousXsDefaultSettingAssignment(self): self.assertEqual(xsModel["YA"].geometry, "0D") self.assertEqual(xsModel["YA"].criticalBuckling, True) self.assertEqual(xsModel["YA"].partiallyHeterogeneous, False) + self.assertEqual(xsModel["YA"].splitTraceIsotopes, False) def test_setDefaultSettingsByLowestBuGroupHomogeneous(self): # Initialize some micro suffix in the cross sections @@ -183,6 +184,7 @@ def test_optionalKey(self): geometry="1D cylinder", meshSubdivisionsPerCm=1.0, partiallyHeterogeneous=True, + splitTraceIsotopes=True, ) xsModel["DA"] = da xsModel.setDefaults( @@ -192,6 +194,7 @@ def test_optionalKey(self): self.assertEqual(xsModel["DA"].mergeIntoClad, ["gap"]) self.assertEqual(xsModel["DA"].meshSubdivisionsPerCm, 1.0) self.assertEqual(xsModel["DA"].partiallyHeterogeneous, True) + self.assertEqual(xsModel["DA"].splitTraceIsotopes, True) self.assertEqual(xsModel["DA"].mergeIntoFuel, []) def test_badCrossSections(self): From e8b0efc1b803f516822096dcf808215447075f3f Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Fri, 11 Oct 2024 10:46:33 -0700 Subject: [PATCH 33/41] Check for lower-case XS IDs --- armi/nuclearDataIO/xsLibraries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/nuclearDataIO/xsLibraries.py b/armi/nuclearDataIO/xsLibraries.py index 126437761..f38bbaa65 100644 --- a/armi/nuclearDataIO/xsLibraries.py +++ b/armi/nuclearDataIO/xsLibraries.py @@ -127,7 +127,7 @@ def getISOTXSLibrariesToMerge(xsLibrarySuffix, xsLibFileNames): isosWithSuffix = [ iso for iso in isosToMerge - if re.match(f".*ISO[A-Z]{{2}}F?{xsLibrarySuffix}$", iso) + if re.match(f".*ISO[A-Za-z]{{2}}F?{xsLibrarySuffix}$", iso) ] isosToMerge = [ iso @@ -193,7 +193,7 @@ def mergeXSLibrariesInWorkingDirectory( for xsLibFilePath in sorted(xsLibFiles): try: # get XS ID from the cross section library name - xsID = re.search("ISO([A-Z0-9]{2})", xsLibFilePath).group(1) + xsID = re.search("ISO([A-Z0-9a-z]{2})", xsLibFilePath).group(1) except AttributeError: # if glob has matched something that is not actually an ISOXX file, # the .group() call will fail From 92fe1a67cece9469f585cde39f9e346e521c6743 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Fri, 11 Oct 2024 16:17:13 -0700 Subject: [PATCH 34/41] Update unit test. --- armi/nuclearDataIO/tests/test_xsLibraries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/nuclearDataIO/tests/test_xsLibraries.py b/armi/nuclearDataIO/tests/test_xsLibraries.py index 324206e9e..ae4b55229 100644 --- a/armi/nuclearDataIO/tests/test_xsLibraries.py +++ b/armi/nuclearDataIO/tests/test_xsLibraries.py @@ -127,7 +127,7 @@ def test_mergeFailsWithNonIsotxsFiles(self): os.remove(dummyFileName) with TemporaryDirectoryChanger(): - dummyFileName = "ISOtopics.txt" + dummyFileName = "ISO[]" with open(dummyFileName, "w") as file: file.write( "This is a file that starts with the letters 'ISO' but will" From 0bcfa76359a485fc87cb587be3989c95fb47c2de Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Mon, 14 Oct 2024 12:00:29 -0700 Subject: [PATCH 35/41] Add missing parameter to XSModelingOptions docstring. --- armi/physics/neutronics/crossSectionSettings.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index a86771a07..9cf602834 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -434,7 +434,7 @@ class XSModelingOptions: no others will allocate to it. This is useful for time balancing when you have one task that takes much longer than the others. - xsPriority: + xsPriority: int The priority of the mpi tasks that results from this xsID. Lower priority will execute first. starting longer jobs first is generally more efficient. @@ -444,6 +444,15 @@ class XSModelingOptions: (e.g., fission products) as a depletion product of an isotope with a much smaller atomic number. + averageByComponent: bool + Controls whether the representative block averaging is performed on a + component-by-component basis or on the block as a whole. If True, the + resulting representative block will have component compositions that + largely reflect those of the underlying blocks in the collection. If + False, the number densities of some nuclides in the individual + components may not be reflective of those of the underlying components + due to the block number density "dehomogenization". + minDriverDensity: float The minimum number density for nuclides included in driver material for a 1D lattice physics model. From e592fa0f01194a92824c71e33fb7c11e90706576 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Wed, 16 Oct 2024 14:52:38 -0700 Subject: [PATCH 36/41] Add partially heterogeneous example to gallery. --- doc/gallery-src/analysis/run_hexBlockToRZConversion.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/gallery-src/analysis/run_hexBlockToRZConversion.py b/doc/gallery-src/analysis/run_hexBlockToRZConversion.py index 78b7cebe5..fd4649930 100644 --- a/doc/gallery-src/analysis/run_hexBlockToRZConversion.py +++ b/doc/gallery-src/analysis/run_hexBlockToRZConversion.py @@ -50,6 +50,7 @@ _o, r = test_reactors.loadTestReactor() +# fully heterogeneous bFuel = r.core.getBlocks(Flags.FUEL)[0] bControl = r.core.getBlocks(Flags.CONTROL)[0] converter = blockConverters.HexComponentsToCylConverter( @@ -57,3 +58,10 @@ ) converter.convert() converter.plotConvertedBlock() + +# partially heterogeneous +converter = blockConverters.HexComponentsToCylConverter( + sourceBlock=bFuel, partiallyHeterogeneous=True +) +converter.convert() +converter.plotConvertedBlock() From 1726569bd175616bc1c2a1bf3ddc10d2de16d6ec Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 17 Oct 2024 14:59:45 -0700 Subject: [PATCH 37/41] Change setting name. --- armi/physics/neutronics/crossSectionSettings.py | 17 +++++++++-------- .../latticePhysics/latticePhysicsWriter.py | 2 +- .../tests/test_crossSectionSettings.py | 6 +++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 9cf602834..0dee5c06e 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -59,7 +59,7 @@ CONF_XS_MAX_ATOM_NUMBER = "xsMaxAtomNumber" CONF_MIN_DRIVER_DENSITY = "minDriverDensity" CONF_PARTIALLY_HETEROGENEOUS = "partiallyHeterogeneous" -CONF_SPLIT_TRACE_ISOTOPES = "splitTraceIsotopes" +CONF_TRACE_ISOTOPE_THRESHOLD = "traceIsotopeThreshold" class XSGeometryTypes(Enum): @@ -154,7 +154,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_MAX_ATOM_NUMBER, CONF_MIN_DRIVER_DENSITY, CONF_PARTIALLY_HETEROGENEOUS, - CONF_SPLIT_TRACE_ISOTOPES, + CONF_TRACE_ISOTOPE_THRESHOLD, }, XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): { CONF_XSID, @@ -202,7 +202,7 @@ def getStr(cls, typeSpec: Enum): vol.Optional(CONF_MIN_DRIVER_DENSITY): vol.Coerce(float), vol.Optional(CONF_COMPONENT_AVERAGING): bool, vol.Optional(CONF_PARTIALLY_HETEROGENEOUS): bool, - vol.Optional(CONF_SPLIT_TRACE_ISOTOPES): bool, + vol.Optional(CONF_TRACE_ISOTOPE_THRESHOLD): vol.Coerce(float), } ) @@ -462,11 +462,12 @@ class XSModelingOptions: heterogeneous approximation for a 1D cylindrical model. Everything inside of the duct will be treated as homogeneous. - splitTraceIsotopes : bool + traceIsotopeThreshold : float This is a lattice physics configuration option used to enable a separate 0D fuel cross section calculation for trace fission products when using a 1D cross section model. This can significantly reduce the memory and run time required for the 1D - model. + model. The setting takes a float value that represents the number density cutoff + for isotopes to be considered "trace". If no value is provided, the default is 0.0. Notes ----- @@ -501,7 +502,7 @@ def __init__( averageByComponent=False, minDriverDensity=0.0, partiallyHeterogeneous=False, - splitTraceIsotopes=False, + traceIsotopeThreshold=0.0, ): self.xsID = xsID self.geometry = geometry @@ -526,7 +527,7 @@ def __init__( self.minDriverDensity = minDriverDensity self.averageByComponent = averageByComponent self.partiallyHeterogeneous = partiallyHeterogeneous - self.splitTraceIsotopes = splitTraceIsotopes + self.traceIsotopeThreshold = traceIsotopeThreshold # these are related to execution self.xsExecuteExclusive = xsExecuteExclusive self.xsPriority = xsPriority @@ -717,7 +718,7 @@ def setDefaults(self, blockRepresentation, validBlockTypes): CONF_BLOCK_REPRESENTATION: crossSectionGroupManager.CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION, CONF_BLOCKTYPES: validBlockTypes, CONF_PARTIALLY_HETEROGENEOUS: False, - CONF_SPLIT_TRACE_ISOTOPES: False, + CONF_TRACE_ISOTOPE_THRESHOLD: 0.0, } elif self.geometry == XSGeometryTypes.getStr( XSGeometryTypes.TWO_DIMENSIONAL_HEX diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 3699c2f4c..0009eae2e 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -115,7 +115,7 @@ def __init__( self.numExternalRings = self.xsSettings.numExternalRings self.criticalBucklingSearchActive = self.xsSettings.criticalBuckling self.partiallyHeterogeneous = self.xsSettings.partiallyHeterogeneous - self.splitTraceIsotopes = self.xsSettings.splitTraceIsotopes + self.traceIsotopeThreshold = self.xsSettings.traceIsotopeThreshold self.executeExclusive = self.xsSettings.xsExecuteExclusive self.priority = self.xsSettings.xsPriority diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index 61df3f238..4022f6aff 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -119,7 +119,7 @@ def test_homogeneousXsDefaultSettingAssignment(self): self.assertEqual(xsModel["YA"].geometry, "0D") self.assertEqual(xsModel["YA"].criticalBuckling, True) self.assertEqual(xsModel["YA"].partiallyHeterogeneous, False) - self.assertEqual(xsModel["YA"].splitTraceIsotopes, False) + self.assertEqual(xsModel["YA"].traceIsotopeThreshold, 0.0) def test_setDefaultSettingsByLowestBuGroupHomogeneous(self): # Initialize some micro suffix in the cross sections @@ -184,7 +184,7 @@ def test_optionalKey(self): geometry="1D cylinder", meshSubdivisionsPerCm=1.0, partiallyHeterogeneous=True, - splitTraceIsotopes=True, + traceIsotopeThreshold=1.0e-5, ) xsModel["DA"] = da xsModel.setDefaults( @@ -194,7 +194,7 @@ def test_optionalKey(self): self.assertEqual(xsModel["DA"].mergeIntoClad, ["gap"]) self.assertEqual(xsModel["DA"].meshSubdivisionsPerCm, 1.0) self.assertEqual(xsModel["DA"].partiallyHeterogeneous, True) - self.assertEqual(xsModel["DA"].splitTraceIsotopes, True) + self.assertEqual(xsModel["DA"].traceIsotopeThreshold, 1.0e-5) self.assertEqual(xsModel["DA"].mergeIntoFuel, []) def test_badCrossSections(self): From ed2f318c3808f32d1663d90a07b79e177efbae9e Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 17 Oct 2024 15:14:27 -0700 Subject: [PATCH 38/41] Change another setting name. --- .../physics/neutronics/crossSectionGroupManager.py | 14 +++++++------- armi/physics/neutronics/crossSectionSettings.py | 14 +++++++------- .../latticePhysics/latticePhysicsWriter.py | 4 ++-- .../neutronics/tests/test_crossSectionSettings.py | 6 +++--- armi/reactor/converters/blockConverters.py | 10 +++++----- .../analysis/run_hexBlockToRZConversion.py | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index e139d3f39..9e8972c6f 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -653,7 +653,7 @@ def _getNucTempHelper(self): return nvt, nv -class CylindricalComponentsPartHetAverageBlockCollection( +class CylindricalComponentsDuctHetAverageBlockCollection( CylindricalComponentsAverageBlockCollection ): """ @@ -684,7 +684,7 @@ class CylindricalComponentsPartHetAverageBlockCollection( def _getNewBlock(self): newBlock = copy.deepcopy(self._selectCandidateBlock()) - newBlock.name = "1D_CYL_PART_HET_AVG_" + newBlock.getMicroSuffix() + newBlock.name = "1D_CYL_DUCT_HET_AVG_" + newBlock.getMicroSuffix() return newBlock def _makeRepresentativeBlock(self): @@ -1587,8 +1587,8 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION = "FluxWeightedAverage" SLAB_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DSlab" CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DCylinder" -CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION = ( - "ComponentAverage1DCylinderPartiallyHeterogeneous" +CYLINDRICAL_COMPONENTS_DUCT_HET_BLOCK_COLLECTION = ( + "ComponentAverage1DCylinderDuctHeterogeneous" ) # Mapping between block collection string constants and their @@ -1599,7 +1599,7 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION: FluxWeightedAverageBlockCollection, SLAB_COMPONENTS_BLOCK_COLLECTION: SlabComponentsAverageBlockCollection, CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION: CylindricalComponentsAverageBlockCollection, - CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION: CylindricalComponentsPartHetAverageBlockCollection, + CYLINDRICAL_COMPONENTS_DUCT_HET_BLOCK_COLLECTION: CylindricalComponentsDuctHetAverageBlockCollection, } @@ -1608,8 +1608,8 @@ def blockCollectionFactory(xsSettings, allNuclidesInProblem): blockRepresentation = xsSettings.blockRepresentation if ( blockRepresentation == CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION - ) and xsSettings.partiallyHeterogeneous: - blockRepresentation = CYLINDRICAL_COMPONENTS_PARTIALLY_HET_BLOCK_COLLECTION + ) and xsSettings.ductHeterogeneous: + blockRepresentation = CYLINDRICAL_COMPONENTS_DUCT_HET_BLOCK_COLLECTION validBlockTypes = xsSettings.validBlockTypes averageByComponent = xsSettings.averageByComponent return BLOCK_COLLECTIONS[blockRepresentation]( diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 0dee5c06e..14a4bc537 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -58,7 +58,7 @@ CONF_COMPONENT_AVERAGING = "averageByComponent" CONF_XS_MAX_ATOM_NUMBER = "xsMaxAtomNumber" CONF_MIN_DRIVER_DENSITY = "minDriverDensity" -CONF_PARTIALLY_HETEROGENEOUS = "partiallyHeterogeneous" +CONF_DUCT_HETEROGENEOUS = "ductHeterogeneous" CONF_TRACE_ISOTOPE_THRESHOLD = "traceIsotopeThreshold" @@ -153,7 +153,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_PRIORITY, CONF_XS_MAX_ATOM_NUMBER, CONF_MIN_DRIVER_DENSITY, - CONF_PARTIALLY_HETEROGENEOUS, + CONF_DUCT_HETEROGENEOUS, CONF_TRACE_ISOTOPE_THRESHOLD, }, XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): { @@ -201,7 +201,7 @@ def getStr(cls, typeSpec: Enum): vol.Optional(CONF_XS_MAX_ATOM_NUMBER): vol.Coerce(int), vol.Optional(CONF_MIN_DRIVER_DENSITY): vol.Coerce(float), vol.Optional(CONF_COMPONENT_AVERAGING): bool, - vol.Optional(CONF_PARTIALLY_HETEROGENEOUS): bool, + vol.Optional(CONF_DUCT_HETEROGENEOUS): bool, vol.Optional(CONF_TRACE_ISOTOPE_THRESHOLD): vol.Coerce(float), } ) @@ -457,7 +457,7 @@ class XSModelingOptions: The minimum number density for nuclides included in driver material for a 1D lattice physics model. - partiallyHeterogeneous : bool + ductHeterogeneous : bool This is a lattice physics configuration option used to enable a partially heterogeneous approximation for a 1D cylindrical model. Everything inside of the duct will be treated as homogeneous. @@ -501,7 +501,7 @@ def __init__( xsMaxAtomNumber=None, averageByComponent=False, minDriverDensity=0.0, - partiallyHeterogeneous=False, + ductHeterogeneous=False, traceIsotopeThreshold=0.0, ): self.xsID = xsID @@ -526,7 +526,7 @@ def __init__( self.xsMaxAtomNumber = xsMaxAtomNumber self.minDriverDensity = minDriverDensity self.averageByComponent = averageByComponent - self.partiallyHeterogeneous = partiallyHeterogeneous + self.ductHeterogeneous = ductHeterogeneous self.traceIsotopeThreshold = traceIsotopeThreshold # these are related to execution self.xsExecuteExclusive = xsExecuteExclusive @@ -717,7 +717,7 @@ def setDefaults(self, blockRepresentation, validBlockTypes): CONF_HOMOGBLOCK: False, CONF_BLOCK_REPRESENTATION: crossSectionGroupManager.CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION, CONF_BLOCKTYPES: validBlockTypes, - CONF_PARTIALLY_HETEROGENEOUS: False, + CONF_DUCT_HETEROGENEOUS: False, CONF_TRACE_ISOTOPE_THRESHOLD: 0.0, } elif self.geometry == XSGeometryTypes.getStr( diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 0009eae2e..68da4be98 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -114,7 +114,7 @@ def __init__( self.driverXsID = self.xsSettings.driverID self.numExternalRings = self.xsSettings.numExternalRings self.criticalBucklingSearchActive = self.xsSettings.criticalBuckling - self.partiallyHeterogeneous = self.xsSettings.partiallyHeterogeneous + self.ductHeterogeneous = self.xsSettings.ductHeterogeneous self.traceIsotopeThreshold = self.xsSettings.traceIsotopeThreshold self.executeExclusive = self.xsSettings.xsExecuteExclusive @@ -269,7 +269,7 @@ def _getAllNuclidesByCategory(self, component=None): continue # skip LFPs here but add individual FPs below. if isinstance(subjectObject, components.Component): - if self.partiallyHeterogeneous and "Homogenized" in subjectObject.name: + if self.ductHeterogeneous and "Homogenized" in subjectObject.name: # Nuclide temperatures representing heterogeneous model component temperatures nucTemperatureInC = self._getAvgNuclideTemperatureInC(nucName) else: diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index 4022f6aff..2106ba9b7 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -118,7 +118,7 @@ def test_homogeneousXsDefaultSettingAssignment(self): self.assertNotIn("YA", xsModel) self.assertEqual(xsModel["YA"].geometry, "0D") self.assertEqual(xsModel["YA"].criticalBuckling, True) - self.assertEqual(xsModel["YA"].partiallyHeterogeneous, False) + self.assertEqual(xsModel["YA"].ductHeterogeneous, False) self.assertEqual(xsModel["YA"].traceIsotopeThreshold, 0.0) def test_setDefaultSettingsByLowestBuGroupHomogeneous(self): @@ -183,7 +183,7 @@ def test_optionalKey(self): "DA", geometry="1D cylinder", meshSubdivisionsPerCm=1.0, - partiallyHeterogeneous=True, + ductHeterogeneous=True, traceIsotopeThreshold=1.0e-5, ) xsModel["DA"] = da @@ -193,7 +193,7 @@ def test_optionalKey(self): ) self.assertEqual(xsModel["DA"].mergeIntoClad, ["gap"]) self.assertEqual(xsModel["DA"].meshSubdivisionsPerCm, 1.0) - self.assertEqual(xsModel["DA"].partiallyHeterogeneous, True) + self.assertEqual(xsModel["DA"].ductHeterogeneous, True) self.assertEqual(xsModel["DA"].traceIsotopeThreshold, 1.0e-5) self.assertEqual(xsModel["DA"].mergeIntoFuel, []) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 99ff3d666..0e6205a48 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -508,7 +508,7 @@ class HexComponentsToCylConverter(BlockAvgToCylConverter): duct/intercoolant pinComponentsRing1 | coolant | pinComponentsRing2 | coolant | ... | nonpins ... - The ``partiallyHeterogeneous`` option allows the user to treat everything inside the duct + The ``ductHeterogeneous`` option allows the user to treat everything inside the duct as a single homogenized composition. This could significantly reduce the memory and runtime required for the lattice physics solver, and also provide an alternative approximation for the spatial self-shielding effect on microscopic cross sections. @@ -525,7 +525,7 @@ def __init__( numExternalRings=None, mergeIntoClad=None, mergeIntoFuel=None, - partiallyHeterogeneous=False, + ductHeterogeneous=False, ): BlockAvgToCylConverter.__init__( self, @@ -556,7 +556,7 @@ def __init__( self.pinPitch = sourceBlock.getPinPitch() self.mergeIntoClad = mergeIntoClad or [] self.mergeIntoFuel = mergeIntoFuel or [] - self.partiallyHeterogeneous = partiallyHeterogeneous + self.ductHeterogeneous = ductHeterogeneous self.interRingComponent = sourceBlock.getComponent(Flags.COOLANT, exact=True) self._remainingCoolantFillArea = self.interRingComponent.getArea() if not self.interRingComponent: @@ -592,7 +592,7 @@ def convert(self): self._sourceBlock.getNumPins() ) pinComponents, nonPins = self._classifyComponents() - if self.partiallyHeterogeneous: + if self.ductHeterogeneous: self._buildInsideDuct() else: self._buildFirstRing(pinComponents) @@ -728,7 +728,7 @@ def _buildNonPinRings(self, nonPins): Also needs to add final coolant layer between the outer pins and the non-pins. Will crash if there are things that are not circles or hexes. """ - if not self.partiallyHeterogeneous: + if not self.ductHeterogeneous: # fill in the last ring of coolant using the rest coolInnerDiam = self.convertedBlock[-1].getDimension("od") coolantOD = getOuterDiamFromIDAndArea( diff --git a/doc/gallery-src/analysis/run_hexBlockToRZConversion.py b/doc/gallery-src/analysis/run_hexBlockToRZConversion.py index fd4649930..2f06455a8 100644 --- a/doc/gallery-src/analysis/run_hexBlockToRZConversion.py +++ b/doc/gallery-src/analysis/run_hexBlockToRZConversion.py @@ -61,7 +61,7 @@ # partially heterogeneous converter = blockConverters.HexComponentsToCylConverter( - sourceBlock=bFuel, partiallyHeterogeneous=True + sourceBlock=bFuel, ductHeterogeneous=True ) converter.convert() converter.plotConvertedBlock() From 72151b6dfec5a5f90e3c9398a19c0c3c8f21b326 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Thu, 24 Oct 2024 09:06:42 -0700 Subject: [PATCH 39/41] Update docstring. --- armi/reactor/converters/blockConverters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/armi/reactor/converters/blockConverters.py b/armi/reactor/converters/blockConverters.py index 0e6205a48..b72804f2f 100644 --- a/armi/reactor/converters/blockConverters.py +++ b/armi/reactor/converters/blockConverters.py @@ -880,7 +880,9 @@ def stripComponents(block, compFlags): Returns ------- newBlock : armi.reactor.blocks.Block - Copy of source block with specified components stripped off + Copy of source block with specified components stripped off. + mixtureFlags : TypeSpec + Combination of all component flags within newBlock. Notes ----- From c9dcecc7bfceef9b1fb3e4157067592a8e9eefd0 Mon Sep 17 00:00:00 2001 From: Michael Jarrett Date: Wed, 30 Oct 2024 11:57:10 -0700 Subject: [PATCH 40/41] Update release notes. --- doc/release/0.4.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index 1269218d7..5d7e862c5 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -25,6 +25,7 @@ New Features #. Provide ``Block.getInputHeight`` for determining the height of a block from blueprints. (`PR#1927 `_) #. Improve performance by changing the lattice physics interface so that cross sections are not updated on ``everyNode`` calls during coupled calculations (`PR#1963 `_) #. Improve efficiency of reaction rate calculations. (`PR#1887 `_) +#. Add some new options for simplification of 1D cross section modeling. (`PR#1949 `_) #. TBD API Changes From 4d5631be61d741bae5dea20ccfda8fa99d346575 Mon Sep 17 00:00:00 2001 From: John Stilley <1831479+john-science@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:02:22 -0700 Subject: [PATCH 41/41] Update doc/release/0.4.rst --- doc/release/0.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index 5d7e862c5..84bd6f38a 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -25,7 +25,7 @@ New Features #. Provide ``Block.getInputHeight`` for determining the height of a block from blueprints. (`PR#1927 `_) #. Improve performance by changing the lattice physics interface so that cross sections are not updated on ``everyNode`` calls during coupled calculations (`PR#1963 `_) #. Improve efficiency of reaction rate calculations. (`PR#1887 `_) -#. Add some new options for simplification of 1D cross section modeling. (`PR#1949 `_) +#. Adding new options for simplifying 1D cross section modeling. (`PR#1949 `_) #. TBD API Changes