diff --git a/armi/reactor/converters/axialExpansionChanger.py b/armi/reactor/converters/axialExpansionChanger.py index 2991d68e3..e9a9842cb 100644 --- a/armi/reactor/converters/axialExpansionChanger.py +++ b/armi/reactor/converters/axialExpansionChanger.py @@ -14,11 +14,13 @@ """Enable component-wise axial expansion for assemblies and/or a reactor.""" from statistics import mean -from numpy import array +from typing import List + from armi import runLog from armi.materials import material -from armi.reactor.flags import Flags from armi.reactor.components import UnshapedComponent +from armi.reactor.flags import Flags +from numpy import array TARGET_FLAGS_IN_PREFERRED_ORDER = [ Flags.FUEL, @@ -154,7 +156,6 @@ def performThermalAxialExpansion( tempGrid: list, tempField: list, setFuel: bool = True, - updateNDensForRadialExp: bool = True, ): """Perform thermal expansion for an assembly given an axial temperature grid and field. @@ -169,21 +170,9 @@ def performThermalAxialExpansion( setFuel : boolean, optional Boolean to determine whether or not fuel blocks should have their target components set This is useful when target components within a fuel block need to be determined on-the-fly. - updateNDensForRadialExp: optional, bool - boolean to determine whether or not the component number densities should be updated - to account for radial expansion/contraction - - Notes - ----- - - Setting updateNDensForRadialExp to False isolates the number density changes due to the - temp change to just the axial dim. This is useful for testing. However, in practical use - updateNDensForRadialExp should be set to True to capture radial expansion/contraction - effects associated with updating the component temperature. """ self.setAssembly(a, setFuel) - self.expansionData.updateComponentTempsBy1DTempField( - tempGrid, tempField, updateNDensForRadialExp - ) + self.expansionData.updateComponentTempsBy1DTempField(tempGrid, tempField) self.expansionData.computeThermalExpansionFactors() self.axiallyExpandAssembly() @@ -261,13 +250,10 @@ def axiallyExpandAssembly(self): b.p.zbottom = self.linked.linkedBlocks[b][0].p.ztop isDummyBlock = ib == (numOfBlocks - 1) if not isDummyBlock: - for c in _getSolidComponents(b): + for c in getSolidComponents(b): growFrac = self.expansionData.getExpansionFactor(c) runLog.debug(msg=f" Component {c}, growFrac = {growFrac:.4e}") - if growFrac >= 0.0: - c.height = (1.0 + growFrac) * blockHeight - else: - c.height = (1.0 / (1.0 - growFrac)) * blockHeight + c.height = growFrac * blockHeight # align linked components if ib == 0: c.zbottom = 0.0 @@ -281,21 +267,26 @@ def axiallyExpandAssembly(self): # the top of the block below it c.zbottom = self.linked.linkedBlocks[b][0].p.ztop c.ztop = c.zbottom + c.height + # update component number densities + newNumberDensities = { + nuc: c.getNumberDensity(nuc) / growFrac + for nuc in c.getNuclides() + } + c.setNumberDensities(newNumberDensities) # redistribute block boundaries if on the target component if self.expansionData.isTargetComponent(c): b.p.ztop = c.ztop + b.p.height = b.p.ztop - b.p.zbottom + else: + b.p.height = b.p.ztop - b.p.zbottom - # see also b.setHeight() - # - the above not chosen due to call to calculateZCoords - oldComponentVolumes = [c.getVolume() for c in b] - oldHeight = b.getHeight() - b.p.height = b.p.ztop - b.p.zbottom + b.p.z = b.p.zbottom + b.getHeight() / 2.0 _checkBlockHeight(b) - self._conserveComponentDensity(b, oldHeight, oldComponentVolumes) - # set block mid point and redo mesh - # - functionality based on assembly.calculateZCoords() - b.p.z = b.p.zbottom + b.getHeight() / 2.0 + # call component.clearCache to update the component volume, and therefore the masses, of all solid components. + for c in getSolidComponents(b): + c.clearCache() + # redo mesh -- functionality based on assembly.calculateZCoords() mesh.append(b.p.ztop) b.spatialLocator = self.linked.a.spatialGrid[0, 0, ib] @@ -330,35 +321,8 @@ def manageCoreMesh(self, r): for old, new in zip(oldMesh, r.core.p.axialMesh): runLog.extra(f"{old:.6e}\t{new:.6e}") - def _conserveComponentDensity(self, b, oldHeight, oldVolume): - """Update block height dependent component parameters. - - 1) update component volume for all materials (used to compute block volume) - 2) update number density for solid materials only (no fluid) - - Parameters - ---------- - oldHeight : list of floats - list containing block heights pre-expansion - oldVolume : list of floats - list containing component volumes pre-expansion - """ - solidComponents = _getSolidComponents(b) - for ic, c in enumerate(b): - c.p.volume = oldVolume[ic] * b.getHeight() / oldHeight - if c in solidComponents: - growFrac = self.expansionData.getExpansionFactor(c) - if growFrac >= 0.0: - growth = 1.0 + growFrac - else: - growth = 1.0 / (1.0 - growFrac) - newNumberDensities = { - nuc: c.getNumberDensity(nuc) / growth for nuc in c.getNuclides() - } - c.setNumberDensities(newNumberDensities) - -def _getSolidComponents(b): +def getSolidComponents(b): """ Return list of components in the block that have solid material. @@ -616,40 +580,41 @@ def __init__(self, a, setFuel): self._componentDeterminesBlockHeight = {} self._setTargetComponents(setFuel) - def setExpansionFactors(self, componentLst, percents): - """Sets user defined expansion factors. + def setExpansionFactors(self, componentLst: List, expFrac: List): + """Sets user defined expansion fractions. Parameters ---------- - componentLst : list[:py:class:`Component `] + componentLst : List[:py:class:`Component `] list of Components to have their heights changed - percents : list[float] - list of height changes in percent that are to be applied to componentLst + expFrac : List[float] + list of L1/L0 height changes that are to be applied to componentLst Raises ------ RuntimeError - If componentLst and percents are different lengths - - Notes - ----- - - requires that the length of componentLst and percents be the same + If componentLst and expFrac are different lengths """ - if len(componentLst) != len(percents): + if len(componentLst) != len(expFrac): runLog.error( - "Number of components and percent changes must be the same!\n\ - len(componentLst) = {0:d}\n\ - len(percents) = {1:d}".format( - len(componentLst), len(percents) - ) + f"Number of components and expansion fractions must be the same!\n" + f" len(componentLst) = {len(componentLst)}\n" + f" len(expFrac) = {len(expFrac)}" ) raise RuntimeError - for c, p in zip(componentLst, percents): + if 0.0 in expFrac: + msg = "An expansion fraction, L1/L0, equal to 0.0, is not physical. Expansion fractions should be greater than 0.0." + runLog.error(msg) + raise RuntimeError(msg) + for exp in expFrac: + if exp < 0.0: + msg = "A negative expansion fraction, L1/L0, is not physical. Expansion fractions should be greater than 0.0." + runLog.error(msg) + raise RuntimeError(msg) + for c, p in zip(componentLst, expFrac): self._expansionFactors[c] = p - def updateComponentTempsBy1DTempField( - self, tempGrid, tempField, updateNDensForRadialExp: bool = True - ): + def updateComponentTempsBy1DTempField(self, tempGrid, tempField): """Assign a block-average axial temperature to components. Parameters @@ -658,19 +623,12 @@ def updateComponentTempsBy1DTempField( 1D axial temperature grid (i.e., physical locations where temp is stored) tempField : numpy array temperature values along grid - updateNDensForRadialExp: optional, bool - boolean to determine whether or not the component number densities should be updated - to account for radial expansion/contraction Notes ----- - given a 1D axial temperature grid and distribution, searches for temperatures that fall within the bounds of a block, and averages them - this average temperature is then passed to self.updateComponentTemp() - - Setting updateNDensForRadialExp to False isolates the number density changes due to the - temp change to just the axial dim. This is useful for testing. However, in practical use - updateNDensForRadialExp should be set to True to capture radial expansion/contraction - effects associated with updating the component temperature. Raises ------ @@ -694,72 +652,49 @@ def updateComponentTempsBy1DTempField( if len(tmpMapping) == 0: raise ValueError( - "Block {0:s} has no temperature points within it! \ - Likely need to increase the refinement of the temperature grid.".format( - str(b.name) - ) + f"{b} has no temperature points within it!" + "Likely need to increase the refinement of the temperature grid." ) blockAveTemp = mean(tmpMapping) for c in b: - self.updateComponentTemp(b, c, blockAveTemp, updateNDensForRadialExp) + self.updateComponentTemp(c, blockAveTemp) - def updateComponentTemp( - self, b, c, temp: float, updateNDensForRadialExp: bool = True - ): + def updateComponentTemp(self, c, temp: float): """Update component temperatures with a provided temperature. Parameters ---------- - b : :py:class:`Block ` - parent block for c c : :py:class:`Component ` component to which the temperature, temp, is to be applied temp : float new component temperature in C - updateNDensForRadialExp : bool - boolean to determine whether or not the component number densities should be updated - to account for the radial expansion/contraction associated with the new temperature Notes ----- - "reference" height and temperature are the current states; i.e. before 1) the new temperature, temp, is applied to the component, and 2) the component is axially expanded - - Setting updateNDensForRadialExp to False isolates the number density changes due to the - temp change to just the axial dim. This is useful for testing. However, in practical use - updateNDensForRadialExp should be set to True to capture radial expansion/contraction - effects associated with updating the component temperature. """ self.componentReferenceTemperature[c] = c.temperatureInC - if not updateNDensForRadialExp: - # Update component temp manually to avoid the call to changeNDensByFactor(f) within c.setTemperature(). - # This isolates the number density changes due to the temp change to just the axial dim. - # This is useful for testing. - c.temperatureInC = temp - c.p.volume = c.getArea(cold=True) * b.getHeight() - else: - c.setTemperature(temp) - c.p.volume = c.getArea(cold=False) * b.getHeight() + c.setTemperature(temp) def computeThermalExpansionFactors(self): """Computes expansion factors for all components via thermal expansion.""" for b in self._a: - for c in b: + for c in getSolidComponents(b): if c in self.componentReferenceTemperature: - self._expansionFactors[c] = ( - c.getThermalExpansionFactor( - T0=self.componentReferenceTemperature[c] - ) - - 1.0 + growFrac = c.getThermalExpansionFactor( + T0=self.componentReferenceTemperature[c] ) + self._expansionFactors[c] = growFrac elif self.componentReferenceTemperature: # we want expansion factors relative to componentReferenceTemperature not Tinput. # But for this component there isn't a componentReferenceTemperature, - # so we'll assume that the expansion factor is 0.0. - self._expansionFactors[c] = 0.0 + # so we'll assume that the expansion factor is 1.0. + self._expansionFactors[c] = 1.0 else: - self._expansionFactors[c] = c.getThermalExpansionFactor() - 1.0 + self._expansionFactors[c] = c.getThermalExpansionFactor() def getExpansionFactor(self, c): """Retrieves expansion factor for c. @@ -772,7 +707,7 @@ def getExpansionFactor(self, c): if c in self._expansionFactors: value = self._expansionFactors[c] else: - value = 0.0 + value = 1.0 return value def _setTargetComponents(self, setFuel): diff --git a/armi/reactor/converters/tests/test_axialExpansionChanger.py b/armi/reactor/converters/tests/test_axialExpansionChanger.py index 506ebfc35..81b8b2e6d 100644 --- a/armi/reactor/converters/tests/test_axialExpansionChanger.py +++ b/armi/reactor/converters/tests/test_axialExpansionChanger.py @@ -13,34 +13,29 @@ # limitations under the License. """Test axialExpansionChanger.""" -from statistics import mean +import collections import os import unittest +from statistics import mean -from numpy import linspace, array, vstack, zeros - -from armi.materials import material -from armi.reactor.assemblies import grids -from armi.reactor.assemblies import HexAssembly +from armi import materials +from armi.materials import _MATERIAL_NAMESPACE_ORDER, custom +from armi.reactor.assemblies import HexAssembly, grids from armi.reactor.blocks import HexBlock from armi.reactor.components import DerivedShape, UnshapedComponent -from armi.reactor.tests.test_reactors import loadTestReactor, reduceTestReactorRings -from armi.tests import TEST_ROOT -from armi.reactor.components.basicShapes import ( - Circle, - Hexagon, - Rectangle, -) +from armi.reactor.components.basicShapes import Circle, Hexagon, Rectangle from armi.reactor.components.complexShapes import Helix from armi.reactor.converters.axialExpansionChanger import ( AxialExpansionChanger, ExpansionData, _determineLinked, + getSolidComponents, ) -from armi import materials -from armi.materials import custom, _MATERIAL_NAMESPACE_ORDER from armi.reactor.flags import Flags +from armi.reactor.tests.test_reactors import loadTestReactor, reduceTestReactorRings +from armi.tests import TEST_ROOT from armi.utils import units +from numpy import array, linspace, zeros class AxialExpansionTestBase(unittest.TestCase): @@ -59,9 +54,10 @@ class AxialExpansionTestBase(unittest.TestCase): def setUp(self): self.obj = AxialExpansionChanger() - self.massAndDens = {} - self.steelMass = [] - self.blockHeights = {} + self.componentMass = collections.defaultdict(list) + self.componentDensity = collections.defaultdict(list) + self.totalAssemblySteelMass = [] + self.blockZtop = collections.defaultdict(list) self.origNameSpace = _MATERIAL_NAMESPACE_ORDER # set namespace order for materials so that fake HT9 material can be found materials.setMaterialNamespaceOrder( @@ -84,38 +80,21 @@ def _getConservationMetrics(self, a): 2. mass of assembly steel 3. block heights """ - mass = 0.0 + totalSteelMass = 0.0 for b in a: - for c in b: - # store mass and density of target component - if self.obj.expansionData.isTargetComponent(c): - self._storeTargetComponentMassAndDensity(c) + # store block ztop + self.blockZtop[b].append(b.p.ztop) + for c in getSolidComponents(b): + # store mass and density of component + self.componentMass[c].append(c.getMass()) + self.componentDensity[c].append( + c.material.getProperty("density", c.temperatureInK) + ) # store steel mass for assembly if c.p.flags in self.Steel_Component_Lst: - mass += c.getMass() - - # store block heights - tmp = array([b.p.zbottom, b.p.ztop, b.p.height, b.getVolume()]) - if b.name not in self.blockHeights: - self.blockHeights[b.name] = tmp - else: - self.blockHeights[b.name] = vstack((self.blockHeights[b.name], tmp)) + totalSteelMass += c.getMass() - self.steelMass.append(mass) - - def _storeTargetComponentMassAndDensity(self, c): - tmp = array( - [ - c.getMass(), - c.material.getProperty("density", c.temperatureInK), - ] - ) - if c.parent.name not in self.massAndDens: - self.massAndDens[c.parent.name] = tmp - else: - self.massAndDens[c.parent.name] = vstack( - (self.massAndDens[c.parent.name], tmp) - ) + self.totalAssemblySteelMass.append(totalSteelMass) class Temperature: @@ -189,13 +168,11 @@ def setUp(self): self._generateComponentWiseExpectedHeight() # do the axial expansion - self.axialMeshLocs = zeros((self.temp.tempSteps, len(self.a))) for idt in range(self.temp.tempSteps): self.obj.performThermalAxialExpansion( self.a, self.temp.tempGrid, self.temp.tempField[idt, :], setFuel=True ) self._getConservationMetrics(self.a) - self.axialMeshLocs[idt, :] = self.a.getAxialMesh() def tearDown(self): AxialExpansionTestBase.tearDown(self) @@ -206,30 +183,9 @@ def test_AssemblyAxialExpansionHeight(self): for ib, b in enumerate(self.a): self.assertAlmostEqual( self.trueZtop[ib, idt], - self.blockHeights[b.name][idt][1], + self.blockZtop[b][idt], places=7, - msg="Block height is not correct.\ - Temp Step = {0:d}, Block ID = {1:}.".format( - idt, b.name - ), - ) - - def test_AxialMesh(self): - """Test that mesh aligns with block tops for component-based expansion.""" - for idt in range(self.temp.tempSteps): - for ib, b in enumerate(self.a): - self.assertEqual( - self.axialMeshLocs[idt][ib], - self.blockHeights[b.name][idt][1], - msg="\ - Axial mesh and block top do not align and invalidate the axial mesh.\ - Block ID = {0:s},\n\ - Top = {1:.12e}\n\ - Mesh Loc = {2:.12e}".format( - str(b.name), - self.blockHeights[b.name][idt][1], - self.axialMeshLocs[idt][ib], - ), + msg=f"Block height is not correct. {b}; Temp Step = {idt}", ) def _generateComponentWiseExpectedHeight(self): @@ -282,114 +238,117 @@ def tearDown(self): AxialExpansionTestBase.tearDown(self) def expandAssemForMassConservationTest(self): - """Initialize class variables for mass conservation checks.""" - self.oldMass = {} - for b in self.a: - self.oldMass[b.name] = 0.0 - - # do the expansion and store mass and density info - self.temp = Temperature( - self.a.getTotalHeight(), coldTemp=1.0, hotInletTemp=1000.0 - ) - for idt in range(self.temp.tempSteps): + """do the thermal expansion and store conservation metrics of interest""" + # create a semi-realistic/physical variable temperature grid over the assembly + temp = Temperature(self.a.getTotalHeight(), numTempGridPts=11, tempSteps=10) + for idt in range(temp.tempSteps): self.obj.performThermalAxialExpansion( self.a, - self.temp.tempGrid, - self.temp.tempField[idt, :], - setFuel=True, - updateNDensForRadialExp=False, + temp.tempGrid, + temp.tempField[idt, :], ) self._getConservationMetrics(self.a) - def test_ColdThermalExpansionContractionConservation(self): - """Thermally expand and then contract to ensure original state is recovered. + def test_ThermalExpansionContractionConservation_Simple(self): + r"""Thermally expand and then contract to ensure original state is recovered. Notes ----- - Temperature field is isothermal and initially at 25 C. + Temperature field is always isothermal and initially at 25 C. """ - isothermalTempList = [20.0, 25.0, 30.0] - a = buildTestAssemblyWithFakeMaterial(name="FakeMat") - originalMesh = a.getAxialMesh() + isothermalTempList = [100.0, 350.0, 250.0, 25.0] + a = buildTestAssemblyWithFakeMaterial(name="HT9") + origMesh = a.getAxialMesh()[:-1] + origMasses, origNDens = self._getComponentMassAndNDens(a) axialExpChngr = AxialExpansionChanger(detailedAxialExpansion=True) tempGrid = linspace(0.0, a.getHeight()) for temp in isothermalTempList: - # Set hot isothermal temp and expand - tempField = array([temp] * len(tempGrid)) - axialExpChngr.performThermalAxialExpansion( - a, tempGrid, tempField, updateNDensForRadialExp=False + # compute expected change in number densities + c = a[0][0] + radialGrowthFrac = c.material.getThermalExpansionDensityReduction( + prevTempInC=c.temperatureInC, newTempInC=temp ) - if temp == 25.0: - for new, old in zip( - a.getAxialMesh()[:-1], originalMesh[:-1] - ): # skip dummy block - self.assertAlmostEqual( - new, - old, - msg="At original temp (250 C) block height is {0:.5f}. " - "Current temp is {1:.5f} and block height is {2:.5f}".format( - old, temp, new - ), - places=3, - ) - else: - for new, old in zip( - a.getAxialMesh()[:-1], originalMesh[:-1] - ): # skip dummy block - self.assertNotEqual( - new, - old, - msg="At original temp (250 C) block height is {0:.5f}. " - "Current temp is {1:.5f} and block height is {2:.5f}".format( - old, temp, new - ), - ) - - def test_HotThermalExpansionContractionConservation(self): + axialGrowthFrac = c.getThermalExpansionFactor(T0=c.temperatureInC, Tc=temp) + totGrowthFrac = axialGrowthFrac / radialGrowthFrac + # Set new isothermal temp and expand + tempField = array([temp] * len(tempGrid)) + oldMasses, oldNDens = self._getComponentMassAndNDens(a) + axialExpChngr.performThermalAxialExpansion(a, tempGrid, tempField) + newMasses, newNDens = self._getComponentMassAndNDens(a) + self._checkMass(oldMasses, newMasses) + self._checkNDens(oldNDens, newNDens, totGrowthFrac) + + # make sure that the assembly returned to the original state + for orig, new in zip(origMesh, a.getAxialMesh()): + self.assertAlmostEqual(orig, new, places=12) + self._checkMass(origMasses, newMasses) + self._checkNDens(origNDens, newNDens, 1.0) + + def test_ThermalExpansionContractionConservation_Complex(self): r"""Thermally expand and then contract to ensure original state is recovered. Notes ----- - Temperature field is isothermal and initially at 250 C. + Assemblies with liners are not supported and not considered for conservation testing. """ - isothermalTempList = [200.0, 250.0, 300.0] - a = buildTestAssemblyWithFakeMaterial(name="FakeMat", hot=True) - originalMesh = a.getAxialMesh() + _oCold, rCold = loadTestReactor( + os.path.join(TEST_ROOT, "detailedAxialExpansion"), + customSettings={"inputHeightsConsideredHot": False}, + ) + assems = list(rCold.blueprints.assemblies.values()) + for a in assems: + if a.hasFlags([Flags.MIDDLE, Flags.ANNULAR, Flags.TEST]): + # assemblies with the above flags have liners and conservation + # of such assemblies is not currently supported + continue + self.complexConservationTest(a) + + def complexConservationTest(self, a): + origMesh = a.getAxialMesh()[:-1] + origMasses, origNDens = self._getComponentMassAndNDens(a) axialExpChngr = AxialExpansionChanger(detailedAxialExpansion=True) - - tempGrid = linspace(0.0, a.getHeight()) - for temp in isothermalTempList: - # Set hot isothermal temp and expand - tempField = array([temp] * len(tempGrid)) - axialExpChngr.performThermalAxialExpansion( - a, tempGrid, tempField, updateNDensForRadialExp=False - ) - if temp == 250.0: - for new, old in zip( - a.getAxialMesh()[:-1], originalMesh[:-1] - ): # skip dummy block - self.assertAlmostEqual( - new, - old, - msg="At original temp (250 C) block height is {0:.5f}. " - "Current temp is {1:.5f} and block height is {2:.5f}".format( - old, temp, new - ), - places=1, - ) - else: - for new, old in zip( - a.getAxialMesh()[:-1], originalMesh[:-1] - ): # skip dummy block - self.assertNotEqual( - new, - old, - msg="At original temp (250 C) block height is {0:.5f}. " - "Current temp is {1:.5f} and block height is {2:.5f}".format( - old, temp, new - ), + axialExpChngr.setAssembly(a) + tempAdjust = [50.0, 50.0, -50.0, -50.0] + for temp in tempAdjust: + # adjust component temperatures by temp + for b in a: + for c in getSolidComponents(b): + axialExpChngr.expansionData.updateComponentTemp( + c, c.temperatureInC + temp ) + # get U235/B10 and FE56 mass pre-expansion + prevFE56Mass = a.getMass("FE56") + prevMass = self._getMass(a) + # compute thermal expansion coeffs and expand + axialExpChngr.expansionData.computeThermalExpansionFactors() + axialExpChngr.axiallyExpandAssembly() + # ensure that total U235/B10 and FE56 mass is conserved post-expansion + newFE56Mass = a.getMass("FE56") + newMass = self._getMass(a) + self.assertAlmostEqual( + newFE56Mass / prevFE56Mass, 1.0, places=14, msg=f"{a}" + ) + if newMass: + self.assertAlmostEqual(newMass / prevMass, 1.0, places=14, msg=f"{a}") + + newMasses, newNDens = self._getComponentMassAndNDens(a) + # make sure that the assembly returned to the original state + for orig, new in zip(origMesh, a.getAxialMesh()): + self.assertAlmostEqual(orig, new, places=12, msg=f"{a}") + self._checkMass(origMasses, newMasses) + self._checkNDens(origNDens, newNDens, 1.0) + + @staticmethod + def _getMass(a): + """get the mass of an assembly. The conservation of HT9 pins in shield assems + are accounted for in FE56 conservation checks.""" + newMass = None + if a.hasFlags(Flags.FUEL): + newMass = a.getMass("U235") + elif a.hasFlags(Flags.CONTROL): + newMass = a.getMass("B10") + return newMass def test_PrescribedExpansionContractionConservation(self): """Expand all components and then contract back to original state. @@ -397,75 +356,81 @@ def test_PrescribedExpansionContractionConservation(self): Notes ----- - uniform expansion over all components within the assembly - - 10 total expansion steps: 5 at +1%, and 5 at -1% - - assertion on if original axial mesh matches the final axial mesh + - 10 total expansion steps: 5 at +1.01 L1/L0, and 5 at -(1.01^-1) L1/L0 """ a = buildTestAssemblyWithFakeMaterial(name="FakeMat") - obj = AxialExpansionChanger() - oldMesh = a.getAxialMesh() - componentLst = [c for b in a for c in b] + axExpChngr = AxialExpansionChanger() + origMesh = a.getAxialMesh() + origMasses, origNDens = self._getComponentMassAndNDens(a) + componentLst = [c for b in a for c in getSolidComponents(b)] + expansionGrowthFrac = 1.01 + contractionGrowthFrac = 1.0 / expansionGrowthFrac for i in range(0, 10): - # get the percentage change if i < 5: - percents = 0.01 + zeros(len(componentLst)) + growthFrac = expansionGrowthFrac + fracLst = growthFrac + zeros(len(componentLst)) else: - percents = -0.01 + zeros(len(componentLst)) - # set the expansion factors - oldMasses = [ - c.getMass() - for b in a - for c in b - if not isinstance(c.material, material.Fluid) - ] + growthFrac = contractionGrowthFrac + fracLst = growthFrac + zeros(len(componentLst)) + oldMasses, oldNDens = self._getComponentMassAndNDens(a) # do the expansion - obj.performPrescribedAxialExpansion(a, componentLst, percents, setFuel=True) - newMasses = [ - c.getMass() - for b in a - for c in b - if not isinstance(c.material, material.Fluid) - ] - for old, new in zip(oldMasses, newMasses): - self.assertAlmostEqual(old, new) - - self.assertEqual( - oldMesh, - a.getAxialMesh(), - msg="Axial mesh is not the same after the expansion and contraction!", - ) + axExpChngr.performPrescribedAxialExpansion( + a, componentLst, fracLst, setFuel=True + ) + newMasses, newNDens = self._getComponentMassAndNDens(a) + self._checkMass(oldMasses, newMasses) + self._checkNDens(oldNDens, newNDens, growthFrac) + + # make sure that the assembly returned to the original state + for orig, new in zip(origMesh, a.getAxialMesh()): + self.assertAlmostEqual(orig, new, places=13) + self._checkMass(origMasses, newMasses) + self._checkNDens(origNDens, newNDens, 1.0) + + def _checkMass(self, prevMass, newMass): + for prev, new in zip(prevMass.values(), newMass.values()): + # scaling helps check the assertion closer to machine precision + ave = (new + prev) / 2.0 + prevScaled = prev / ave + newScaled = new / ave + self.assertAlmostEqual(prevScaled, newScaled, places=14) + + def _checkNDens(self, prevNDen, newNDens, ratio): + for prevComp, newComp in zip(prevNDen.values(), newNDens.values()): + for prev, new in zip(prevComp.values(), newComp.values()): + if prev: + self.assertAlmostEqual(prev / new, ratio, msg=f"{prev} / {new}") + + @staticmethod + def _getComponentMassAndNDens(a): + masses = {} + nDens = {} + for b in a: + for c in getSolidComponents(b): + masses[c] = c.getMass() + nDens[c] = c.getNumberDensities() + return masses, nDens def test_TargetComponentMassConservation(self): """Tests mass conservation for target components.""" self.expandAssemForMassConservationTest() - for idt in range(self.temp.tempSteps): - for b in self.a[:-1]: # skip the dummy sodium block - if idt != 0: - self.assertAlmostEqual( - self.oldMass[b.name], - self.massAndDens[b.name][idt][0], - places=7, - msg="Conservation of Mass Failed on time step {0:d}, block name {1:s},\ - with old mass {2:.7e}, and new mass {3:.7e}.".format( - idt, - b.name, - self.oldMass[b.name], - self.massAndDens[b.name][idt][0], - ), - ) - self.oldMass[b.name] = self.massAndDens[b.name][idt][0] + for cName, masses in self.componentMass.items(): + for i in range(1, len(masses)): + self.assertAlmostEqual( + masses[i], masses[i - 1], msg=f"{cName} mass not right" + ) - def test_SteelConservation(self): - """Tests mass conservation for total assembly steel. + for cName, density in self.componentDensity.items(): + for i in range(1, len(density)): + self.assertLess( + density[i], density[i - 1], msg=f"{cName} density not right." + ) - Component list defined by, Steel_Component_List, in GetSteelMass() - """ - self.expandAssemForMassConservationTest() - for idt in range(self.temp.tempSteps - 1): + for i in range(1, len(self.totalAssemblySteelMass)): self.assertAlmostEqual( - self.steelMass[idt], - self.steelMass[idt + 1], - places=7, - msg="Conservation of steel mass failed on time step {0:d}".format(idt), + self.totalAssemblySteelMass[i], + self.totalAssemblySteelMass[i - 1], + msg="Total assembly steel mass is not conserved.", ) def test_NoMovementACLP(self): @@ -494,8 +459,8 @@ def test_NoMovementACLP(self): # expand fuel # get fuel components cList = [c for b in assembly for c in b if c.hasFlags(Flags.FUEL)] - # 10% growth of fuel components - pList = zeros(len(cList)) + 0.1 + # 1.01 L1/L0 growth of fuel components + pList = zeros(len(cList)) + 1.01 chngr = AxialExpansionChanger() chngr.performPrescribedAxialExpansion(assembly, cList, pList, setFuel=True) @@ -525,8 +490,8 @@ def test_computeThermalExpansionFactors(self): # apply new temp to the pin and clad components of each block for b in self.a: for c in b[0:2]: - stdThermExpFactor[c] = c.getThermalExpansionFactor() - 1.0 - self.obj.expansionData.updateComponentTemp(b, c, newTemp) + stdThermExpFactor[c] = c.getThermalExpansionFactor() + self.obj.expansionData.updateComponentTemp(c, newTemp) self.obj.expansionData.computeThermalExpansionFactors() @@ -542,7 +507,7 @@ def test_computeThermalExpansionFactors(self): else: self.assertEqual( self.obj.expansionData.getExpansionFactor(c), - 0.0, + 1.0, msg=f"Block {b}, Component {c}, thermExpCoeff not right.\n", ) @@ -556,11 +521,11 @@ def setUp(self): reduceTestReactorRings(self.r, o.cs, 3) self.oldAxialMesh = self.r.core.p.axialMesh - # expand refAssem by 10% + # expand refAssem by 1.01 L1/L0 componentLst = [c for b in self.r.core.refAssem for c in b] - percents = 0.01 + zeros(len(componentLst)) + expansionGrowthFracs = 1.01 + zeros(len(componentLst)) self.axialExpChngr.performPrescribedAxialExpansion( - self.r.core.refAssem, componentLst, percents, setFuel=True + self.r.core.refAssem, componentLst, expansionGrowthFracs, setFuel=True ) def test_manageCoreMesh(self): @@ -600,8 +565,22 @@ def test_isTopDummyBlockPresent(self): def test_setExpansionFactors(self): with self.assertRaises(RuntimeError) as cm: cList = self.a[0].getChildren() - percents = range(len(cList) + 1) - self.obj.expansionData.setExpansionFactors(cList, percents) + expansionGrowthFracs = range(len(cList) + 1) + self.obj.expansionData.setExpansionFactors(cList, expansionGrowthFracs) + the_exception = cm.exception + self.assertEqual(the_exception.error_code, 3) + + with self.assertRaises(RuntimeError) as cm: + cList = self.a[0].getChildren() + expansionGrowthFracs = zeros(len(cList)) + self.obj.expansionData.setExpansionFactors(cList, expansionGrowthFracs) + the_exception = cm.exception + self.assertEqual(the_exception.error_code, 3) + + with self.assertRaises(RuntimeError) as cm: + cList = self.a[0].getChildren() + expansionGrowthFracs = zeros(len(cList)) - 10.0 + self.obj.expansionData.setExpansionFactors(cList, expansionGrowthFracs) the_exception = cm.exception self.assertEqual(the_exception.error_code, 3) @@ -871,9 +850,8 @@ def test_coldAssemblyExpansion(self): 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): + for cExp in getSolidComponents(bExp): + if cExp.zbottom == bExp.p.zbottom and cExp.ztop == bExp.p.ztop: matDens = cExp.material.density(Tc=cExp.temperatureInC) compDens = cExp.density() msg = ( @@ -884,7 +862,7 @@ def test_coldAssemblyExpansion(self): self.assertAlmostEqual( matDens, compDens, - places=7, + places=12, msg=msg, ) @@ -1126,7 +1104,7 @@ def _buildTestBlock(blockType: str, name: str, hotTemp: float, height: float): blockType : string determines which type of block you're building name : string - determines which fake material to use + determines which material to use """ b = HexBlock(blockType, height=height) @@ -1179,7 +1157,7 @@ class FakeMat(materials.ht9.HT9): Notes ----- - - specifically used TestAxialExpansionHeight to verify axialExpansionChanger produces + - specifically used in TestAxialExpansionHeight to verify axialExpansionChanger produces expected heights from hand calculation - also used to verify mass and height conservation resulting from even amounts of expansion and contraction. See TestConservation. @@ -1187,9 +1165,6 @@ class FakeMat(materials.ht9.HT9): name = "FakeMat" - def __init__(self): - materials.ht9.HT9.__init__(self) - def linearExpansionPercent(self, Tk=None, Tc=None): """A fake linear expansion percent.""" Tc = units.getTc(Tc, Tk) @@ -1207,9 +1182,6 @@ class FakeMatException(materials.ht9.HT9): name = "FakeMatException" - def __init__(self): - materials.ht9.HT9.__init__(self) - def linearExpansionPercent(self, Tk=None, Tc=None): """A fake linear expansion percent.""" Tc = units.getTc(Tc, Tk) diff --git a/armi/tests/detailedAxialExpansion/refSmallReactorBase.yaml b/armi/tests/detailedAxialExpansion/refSmallReactorBase.yaml index 417347609..623b8b8b9 100644 --- a/armi/tests/detailedAxialExpansion/refSmallReactorBase.yaml +++ b/armi/tests/detailedAxialExpansion/refSmallReactorBase.yaml @@ -41,6 +41,25 @@ blocks: ip: grid.op mult: 1.0 op: 16.75 + + duct: &block_duct + coolant: *component_coolant + duct: &component_duct + shape: Hexagon + material: HT9 + Tinput: 25.0 + Thot: 450.0 + ip: 16.0 + mult: 1.0 + op: 16.6 + intercoolant: &component_intercoolant + shape: Hexagon + material: Sodium + Tinput: 25.0 + Thot: 450.0 + ip: duct.op + mult: 1.0 + op: 16.75 SodiumBlock : &block_dummy flags: dummy @@ -52,7 +71,6 @@ blocks: ip: 0.0 mult: 1.0 op: 16.75 - axial expansion target component: coolant ## ------------------------------------------------------------------------------------ ## fuel blocks @@ -69,7 +87,7 @@ blocks: shape: Circle material: Sodium Tinput: 25.0 - Thot: 450.0 + Thot: 470.0 id: shield.od mult: shield.mult od: clad.id @@ -85,29 +103,15 @@ blocks: shape: Helix material: HT9 Tinput: 25.0 - Thot: 450.0 + Thot: 470.0 axialPitch: 30.15 helixDiameter: 1.19056 id: 0.0 mult: shield.mult od: 0.10056 coolant: *component_coolant - duct: &component_fuel_duct - shape: Hexagon - material: HT9 - Tinput: 25.0 - Thot: 450.0 - ip: 16.0 - mult: 1.0 - op: 16.6 - intercoolant: &component_fuel_intercoolant - shape: Hexagon - material: Sodium - Tinput: 25.0 - Thot: 450.0 - ip: duct.op - mult: 1.0 - op: 16.75 + duct: *component_duct + intercoolant: *component_intercoolant fuel: &block_fuel fuel: &component_fuel_fuel @@ -122,7 +126,7 @@ blocks: shape: Circle material: Sodium Tinput: 25.0 - Thot: 450.0 + Thot: 470.0 id: fuel.od mult: fuel.mult od: clad.id @@ -138,15 +142,15 @@ blocks: shape: Helix material: HT9 Tinput: 25.0 - Thot: 450.0 + Thot: 470.0 axialPitch: 30.15 helixDiameter: 1.19056 id: 0.0 mult: fuel.mult od: 0.10056 coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant plenum: &block_plenum gap: &component_plenum_gap @@ -169,30 +173,23 @@ blocks: shape: Helix material: HT9 Tinput: 25.0 - Thot: 450.0 + Thot: 470.0 axialPitch: 30.15 helixDiameter: 1.19056 id: 0.0 mult: clad.mult od: 0.10056 coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant aclp plenum : &block_aclp gap: *component_plenum_gap clad: *component_plenum_clad wire: *component_plenum_wire coolant: *component_coolant - duct: - shape: Hexagon - material: HT9 - Tinput: 25.0 - Thot: 450.0 - ip: 16.0 - mult: 1.0 - op: 16.65 - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant fuel2: &block_fuel2 fuel: @@ -208,7 +205,7 @@ blocks: shape: Circle material: Sodium Tinput: 25.0 - Thot: 450.0 + Thot: 600.0 id: fuel.od mult: fuel.mult od: liner2.id @@ -233,8 +230,8 @@ blocks: clad: *component_fuel_clad wire: *component_fuel_wire coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant lta fuel a: &block_lta1_fuel fuel: *component_fuel_fuel @@ -244,8 +241,8 @@ blocks: clad: *component_fuel_clad wire: *component_fuel_wire coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant lta fuel b: &block_lta2_fuel fuel: @@ -263,8 +260,8 @@ blocks: clad: *component_fuel_clad wire: *component_fuel_wire coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant annular fuel gap: &block_fuel3 gap1: @@ -328,18 +325,18 @@ blocks: shape: Circle material: HT9 Tinput: 25.0 - Thot: 430.0 + Thot: 470.0 id: 0.900 mult: fuel.mult od: 1.000 wire: *component_fuel_wire coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant ## ------------------------------------------------------------------------------------ ## control - moveable duct: &block_duct + moveable duct: &block_ctrl_duct coolant: *component_coolant duct: &component_control_duct shape: Hexagon @@ -365,25 +362,25 @@ blocks: Tinput: 25.0 Thot: 600.0 id: 0.0 - mult: 61.0 - od: 1.286 - gap: + mult: clad.mult + od: gap.id + gap: &component_control_gap shape: Circle material: Void Tinput: 25.0 Thot: 450.0 - id: control.od - mult: control.mult + id: 1.286 + mult: clad.mult od: clad.id - clad: + clad: &component_control_clad shape: Circle material: HT9 Tinput: 25.0 Thot: 450.0 id: 1.358 - mult: control.mult + mult: 61.0 od: 1.686 - wire: + wire: &component_control_wire shape: Helix material: HT9 Tinput: 25.0 @@ -391,9 +388,9 @@ blocks: axialPitch: 50.0 helixDiameter: 1.771 id: 0.0 - mult: control.mult - od: 0.085 - innerDuct: + mult: clad.mult + od: 0.085 + innerDuct: &component_control_innerDuct shape: Hexagon material: HT9 Tinput: 25.0 @@ -406,33 +403,11 @@ blocks: intercoolant: *component_control_intercoolant moveable plenum: &block_control_plenum - gap: - shape: Circle - material: Void - Tinput: 25.0 - Thot: 600.0 - id: 0.0 - mult: clad.mult - od: clad.id - clad: - shape: Circle - material: HT9 - Tinput: 25.0 - Thot: 450.0 - id: 1.358 - mult: 61.0 - od: 1.686 - wire: - shape: Helix - material: HT9 - Tinput: 25.0 - Thot: 450.0 - axialPitch: 30.15 - helixDiameter: 1.19056 - id: 0.0 - mult: clad.mult - od: 0.10056 + gap: *component_control_gap + clad: *component_control_clad + wire: *component_control_wire coolant: *component_coolant + innderDuct: *component_control_innerDuct duct: *component_control_duct intercoolant: *component_control_intercoolant @@ -447,23 +422,23 @@ blocks: id: 0.0 mult: 169.0 od: 0.90362 - gap: + gap: &component_radial_shield_gap shape: Circle material: Void Tinput: 25.0 Thot: 450.0 - id: shield.od - mult: shield.mult + id: 0.90362 + mult: 169.0 od: clad.id - clad: + clad: &component_radial_shield_clad shape: Circle material: HT9 Tinput: 25.0 Thot: 450.0 id: 0.90562 - mult: shield.mult + mult: 169.0 od: 1.05036 - wire: + wire: &component_radial_shield_wire shape: Helix material: HT9 Tinput: 25.0 @@ -473,92 +448,63 @@ blocks: id: 0.0 mult: 169.0 od: 0.10056 - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant coolant: *component_coolant radial shield plenum: &block_shield_plenum - gap: &component_radial_shield_plenum_gap - shape: Circle - material: Void - Tinput: 25.0 - Thot: 600.0 - id: 0.0 - mult: clad.mult - od: clad.id - clad: &component_radial_shield_clad - shape: Circle - material: HT9 - Tinput: 25.0 - Thot: 450.0 - id: 0.90562 - mult: 169.0 - od: 1.05036 - wire: &component_radial_shield_plenum_wire - shape: Helix - material: HT9 - Tinput: 25.0 - Thot: 450.0 - axialPitch: 30.15 - helixDiameter: 1.19056 - id: 0.0 - mult: clad.mult - od: 0.10056 + gap: *component_radial_shield_gap + clad: *component_radial_shield_clad + wire: *component_radial_shield_wire coolant: *component_coolant - duct: *component_fuel_duct - intercoolant: *component_fuel_intercoolant - + duct: *component_duct + intercoolant: *component_intercoolant + radial shield aclp: &block_shield_aclp - gap: *component_radial_shield_plenum_gap + gap: *component_radial_shield_gap clad: *component_radial_shield_clad - wire: *component_radial_shield_plenum_wire + wire: *component_radial_shield_wire coolant: *component_coolant - duct: - shape: Hexagon - material: HT9 - Tinput: 25.0 - Thot: 450.0 - ip: 16.0 - mult: 1.0 - op: 16.65 - intercoolant: *component_fuel_intercoolant + duct: *component_duct + intercoolant: *component_intercoolant + axial expansion target component: clad # not necessary, but useful for testing coverage assemblies: - heights: &highOffset_height [25.0, 27.5, 27.5, 27.5, 27.5, 15.0, 25.0, 25.0, 17.5] - axial mesh points: &standard_axial_mesh_points [1, 1, 1, 1, 1, 1, 1, 1, 1] + heights: &highOffset_height [25.0, 27.5, 27.5, 27.5, 27.5, 15.0, 25.0, 25.0, 25.0, 17.5] + axial mesh points: &standard_axial_mesh_points [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] igniter fuel: specifier: IC - blocks: &igniter_fuel_blocks [*block_grid_plate, *block_fuel_axial_shield, *block_fuel, *block_fuel, *block_fuel, *block_plenum, *block_aclp, *block_plenum, *block_dummy] + blocks: &igniter_fuel_blocks [*block_grid_plate, *block_fuel_axial_shield, *block_fuel, *block_fuel, *block_fuel, *block_plenum, *block_aclp, *block_plenum, *block_duct, *block_dummy] height: *highOffset_height axial mesh points: *standard_axial_mesh_points material modifications: - U235_wt_frac: &igniter_fuel_u235_wt_frac ['', '', 0.11, 0.11, 0.11, '', '', '', ''] - ZR_wt_frac: &igniter_fuel_zr_wt_frac ['', '', 0.06, 0.06, 0.06, '', '', '', ''] - xs types: &igniter_fuel_xs_types [A, A, B, C, C, D, A, A, A] + U235_wt_frac: &igniter_fuel_u235_wt_frac ['', '', 0.11, 0.11, 0.11, '', '', '', '', ''] + ZR_wt_frac: &igniter_fuel_zr_wt_frac ['', '', 0.06, 0.06, 0.06, '', '', '', '', ''] + xs types: &igniter_fuel_xs_types [A, A, B, C, C, D, A, A, A, A] middle fuel: specifier: MC - blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_fuel2, *block_fuel2, *block_fuel2, *block_plenum, *block_aclp, *block_plenum, *block_dummy] - height: [25.0, 26.25, 26.25, 26.25, 26.25, 20.0, 25.0, 25.0, 17.5] + blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_fuel2, *block_fuel2, *block_fuel2, *block_plenum, *block_aclp, *block_plenum, *block_duct, *block_dummy] + height: [25.0, 26.25, 26.25, 26.25, 26.25, 20.0, 25.0, 25.0, 25.0, 17.5] axial mesh points: *standard_axial_mesh_points xs types: *igniter_fuel_xs_types annular fuel: specifier: AF - blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_fuel3, *block_fuel3, *block_fuel3, *block_plenum, *block_aclp, *block_plenum, *block_dummy] + blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_fuel3, *block_fuel3, *block_fuel3, *block_plenum, *block_aclp, *block_plenum, *block_duct, *block_dummy] height: *highOffset_height axial mesh points: *standard_axial_mesh_points xs types: *igniter_fuel_xs_types lead test fuel: specifier: LA - blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_lta1_fuel, *block_lta1_fuel, *block_lta1_fuel, *block_plenum, *block_aclp, *block_plenum, *block_dummy] + blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_lta1_fuel, *block_lta1_fuel, *block_lta1_fuel, *block_plenum, *block_aclp, *block_plenum, *block_duct, *block_dummy] height: *highOffset_height axial mesh points: *standard_axial_mesh_points material modifications: - U235_wt_frac: <a_fuel_u235_wt_frac ['', '', 0.2, 0.2, 0.2, '', '', '', ''] - ZR_wt_frac: <a_fuel_zr_wt_frac ['', '', 0.07, 0.07, 0.06, '', '', '', ''] + U235_wt_frac: <a_fuel_u235_wt_frac ['', '', 0.2, 0.2, 0.2, '', '', '', '', ''] + ZR_wt_frac: <a_fuel_zr_wt_frac ['', '', 0.07, 0.07, 0.06, '', '', '', '', ''] xs types: *igniter_fuel_xs_types lead test fuel b: specifier: LB - blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_lta2_fuel, *block_lta2_fuel, *block_lta2_fuel, *block_plenum, *block_aclp, *block_plenum, *block_dummy] + blocks: [*block_grid_plate, *block_fuel_axial_shield, *block_lta2_fuel, *block_lta2_fuel, *block_lta2_fuel, *block_plenum, *block_aclp, *block_plenum, *block_duct, *block_dummy] height: *highOffset_height axial mesh points: *standard_axial_mesh_points material modifications: @@ -568,7 +514,7 @@ assemblies: feed fuel: specifier: OC blocks: *igniter_fuel_blocks - height: [25.0, 25.625, 25.625, 25.625, 25.625, 22.5, 25.0, 25.0, 17.5] + height: [25.0, 25.625, 25.625, 25.625, 25.625, 22.5, 25.0, 25.0, 25.0, 17.5] axial mesh points: *standard_axial_mesh_points material modifications: U235_wt_frac: *igniter_fuel_u235_wt_frac @@ -576,20 +522,19 @@ assemblies: xs types: *igniter_fuel_xs_types secondary control: specifier: SC - blocks: [*block_grid_plate, *block_duct, *block_duct, *block_control, *block_control, *block_control_plenum, *block_duct, *block_duct, *block_dummy] - height: [25.0, 49.0, 49.0, 25.0, 25.0, 25.0, 1.0, 1.0, 17.5] + blocks: [*block_grid_plate, *block_ctrl_duct, *block_ctrl_duct, *block_control, *block_control, *block_control, *block_control_plenum, *block_ctrl_duct, *block_ctrl_duct, *block_dummy] + height: [25.0, 49.0, 49.0, 25.0, 25.0, 25.0, 25.0, 1.0, 1.0, 17.5] axial mesh points: *standard_axial_mesh_points xs types: *igniter_fuel_xs_types primary control: specifier: PC - blocks: [*block_grid_plate, *block_duct, *block_duct, *block_control, *block_control, *block_control_plenum, *block_duct, *block_duct, *block_dummy] - height: [25.0, 1.0, 1.0, 25.0, 25.0, 25.0, 49.0, 49.0, 17.5] + blocks: [*block_grid_plate, *block_ctrl_duct, *block_ctrl_duct, *block_control, *block_control, *block_control, *block_control_plenum, *block_ctrl_duct, *block_ctrl_duct, *block_dummy] + height: [25.0, 1.0, 1.0, 25.0, 25.0, 25.0, 25.0, 49.0, 49.0, 17.5] axial mesh points: *standard_axial_mesh_points xs types: *igniter_fuel_xs_types radial shield: specifier: SH - blocks: [*block_grid_plate, *block_radial_shield, *block_radial_shield, *block_radial_shield, *block_radial_shield, *block_shield_plenum, *block_shield_aclp, *block_shield_plenum, *block_dummy] - height: [25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 17.5] + blocks: [*block_grid_plate, *block_radial_shield, *block_radial_shield, *block_radial_shield, *block_radial_shield, *block_shield_plenum, *block_shield_aclp, *block_shield_plenum, *block_duct, *block_dummy] + height: [25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 25.0, 17.5] axial mesh points: *standard_axial_mesh_points xs types: *igniter_fuel_xs_types -