diff --git a/armi/reactor/blueprints/__init__.py b/armi/reactor/blueprints/__init__.py index bbc9617c9..edf39cda1 100644 --- a/armi/reactor/blueprints/__init__.py +++ b/armi/reactor/blueprints/__init__.py @@ -338,7 +338,7 @@ def _prepConstruction(self, cs): ) if not cs["inputHeightsConsideredHot"]: runLog.header( - "=========== Axially expanding all assemblies (except control) from Tinput to Thot ===========" + "=========== Axially expanding all assemblies from Tinput to Thot ===========" ) # expand axial heights from cold to hot so dims and masses are consistent # with specified component hot temperatures. diff --git a/armi/reactor/converters/axialExpansionChanger.py b/armi/reactor/converters/axialExpansionChanger.py index a735cab13..78fda338c 100644 --- a/armi/reactor/converters/axialExpansionChanger.py +++ b/armi/reactor/converters/axialExpansionChanger.py @@ -74,26 +74,18 @@ def expandColdDimsToHot(assems, isDetailedAxialExpansion, referenceAssembly=None referenceAssembly = getDefaultReferenceAssem(assems) axialExpChanger = AxialExpansionChanger(isDetailedAxialExpansion) for a in assems: - if not a.hasFlags(Flags.CONTROL): - axialExpChanger.setAssembly(a) - # this doesn't get applied to control assems, so CR will be interpreted - # as hot. This should be conservative because the control rods will - # be modeled as slightly shorter with the correct hot density. Density - # is more important than height, so we are forcing density to be correct - # since we can't do axial expansion (yet) - axialExpChanger.applyColdHeightMassIncrease() - axialExpChanger.expansionData.computeThermalExpansionFactors() - axialExpChanger.axiallyExpandAssembly() + axialExpChanger.setAssembly(a) + axialExpChanger.applyColdHeightMassIncrease() + axialExpChanger.expansionData.computeThermalExpansionFactors() + axialExpChanger.axiallyExpandAssembly() if not isDetailedAxialExpansion: for a in assems: - if not a.hasFlags(Flags.CONTROL): - a.setBlockMesh(referenceAssembly.getAxialMesh()) + a.setBlockMesh(referenceAssembly.getAxialMesh()) # update block BOL heights to reflect hot heights for a in assems: - if not a.hasFlags(Flags.CONTROL): - for b in a: - b.p.heightBOL = b.getHeight() - b.completeInitialLoading() + for b in a: + b.p.heightBOL = b.getHeight() + b.completeInitialLoading() class AxialExpansionChanger: diff --git a/armi/reactor/converters/tests/test_axialExpansionChanger.py b/armi/reactor/converters/tests/test_axialExpansionChanger.py index a25b0787e..20dc49c46 100644 --- a/armi/reactor/converters/tests/test_axialExpansionChanger.py +++ b/armi/reactor/converters/tests/test_axialExpansionChanger.py @@ -831,7 +831,7 @@ def setUp(self): os.path.join(TEST_ROOT, "detailedAxialExpansion"), customSettings={"inputHeightsConsideredHot": True}, ) - reduceTestReactorRings(r, o.cs, 3) + reduceTestReactorRings(r, o.cs, 5) self.stdAssems = [a for a in r.core.getAssemblies()] @@ -839,7 +839,7 @@ def setUp(self): os.path.join(TEST_ROOT, "detailedAxialExpansion"), customSettings={"inputHeightsConsideredHot": False}, ) - reduceTestReactorRings(rCold, oCold.cs, 3) + reduceTestReactorRings(rCold, oCold.cs, 5) self.testAssems = [a for a in rCold.core.getAssemblies()] @@ -853,9 +853,6 @@ def test_coldAssemblyExpansion(self): 2. in armi.tests.detailedAxialExpansion.refSmallReactorBase.yaml, Thot > Tinput resulting in a non-zero DeltaT. Each block in the expanded case should therefore be a different height than that of the standard case. - - The one exception is for control assemblies. These designs can be unique from regular - pin type assemblies by allowing downward expansion. Because of this, they are skipped - for axial expansion. """ for aStd, aExp in zip(self.stdAssems, self.testAssems): self.assertAlmostEqual( @@ -869,37 +866,48 @@ def test_coldAssemblyExpansion(self): hasCustomMaterial = any( isinstance(c.material, custom.Custom) for c in bStd ) - if (aStd.hasFlags(Flags.CONTROL)) or (hasCustomMaterial): + if hasCustomMaterial: checkColdBlockHeight(bStd, bExp, self.assertAlmostEqual, "the same") else: checkColdBlockHeight(bStd, bExp, self.assertNotEqual, "different") if bStd.hasFlags(Flags.FUEL): - # fuel mass should grow because heights are considered cold heights - # and a cold 1 cm column has more mass than a hot 1 cm column - if not isinstance( - bStd.getComponent(Flags.FUEL).material, custom.Custom - ): - # custom materials don't expand - self.assertGreater(bExp.getMass("U235"), bStd.getMass("U235")) - - if not aStd.hasFlags(Flags.CONTROL) and not aStd.hasFlags(Flags.TEST): - if not hasCustomMaterial: - # skip blocks of custom material where liner is merged with clad - for cExp in bExp: - if not isinstance(cExp.material, custom.Custom): - matDens = cExp.material.density(Tc=cExp.temperatureInC) - compDens = cExp.getMassDensity() - msg = ( - f"{cExp} {cExp.material} in {bExp} was not at correct density. \n" - + f"expansion = {bExp.p.height / bStd.p.height} \n" - + f"density = {matDens}, component density = {compDens} \n" - ) - self.assertAlmostEqual( - matDens, - compDens, - places=7, - msg=msg, - ) + self.checkColdHeightBlockMass(bStd, bExp, Flags.FUEL, "U235") + elif bStd.hasFlags(Flags.CONTROL): + self.checkColdHeightBlockMass(bStd, bExp, Flags.CONTROL, "B10") + + if not aStd.hasFlags(Flags.TEST) and not hasCustomMaterial: + # skip blocks of custom material where liner is merged with clad + for cExp in bExp: + if not isinstance(cExp.material, custom.Custom): + matDens = cExp.material.density(Tc=cExp.temperatureInC) + compDens = cExp.getMassDensity() + msg = ( + f"{cExp} {cExp.material} in {bExp} was not at correct density. \n" + + f"expansion = {bExp.p.height / bStd.p.height} \n" + + f"density = {matDens}, component density = {compDens} \n" + ) + self.assertAlmostEqual( + matDens, + compDens, + places=7, + msg=msg, + ) + + def checkColdHeightBlockMass( + self, bStd: HexBlock, bExp: HexBlock, flagType: Flags, nuclide: str + ): + """checks that nuclide masses for blocks with input cold heights and "inputHeightsConsideredHot": True are underpredicted + + Notes + ----- + If blueprints have cold blocks heights with "inputHeightsConsideredHot": True in the inputs, then + the nuclide densities are thermally expanded but the block height is not. This ultimately results in + nuclide masses being underpredicted relative to the case where both nuclide densities and block heights + are thermally expanded. + """ + # custom materials don't expand + if not isinstance(bStd.getComponent(flagType).material, custom.Custom): + self.assertGreater(bExp.getMass(nuclide), bStd.getMass(nuclide)) def checkColdBlockHeight(bStd, bExp, assertType, strForAssertion): diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst index 52efe20b2..4c2868a90 100644 --- a/doc/release/0.2.rst +++ b/doc/release/0.2.rst @@ -14,6 +14,7 @@ What's new in ARMI #. Use TemporaryDirectoryChanger for executer.run() so dirs are cleaned up during run. (`PR#1219 `_) #. New option ``copyOutput`` for globalFluxInterface to not copy output back to working directory. (`PR#1218 `_, `PR#1227 `_) #. `Executer` class has a ``dcType`` attribute to define the type of ``DirectoryChanger`` it will use. (`PR#1228 `_) +#. Enabling one-way (upwards) axial expansion of control assemblies. (`PR#1226 `_) #. Implement control rod decusping option for uniform mesh converter. (`PR#1229 `_) #. createRepresentativeBlocksFromExistingBlocks() now returns the mapping of original to new XS IDs. (`PR#1217 `_) #. Added a capability to prioritize mpi action execution and exclusivity. (`PR#1237 `_)