diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index b26afbba3..a9ae9c9e2 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -387,6 +387,113 @@ def getNumberDensityWithTrace(component, nucName): return nvt, nv +class CylindricalComponentsAverageBlockCollection(BlockCollection): + """ + Creates a representative block for the purpose of cross section generation with a one-dimensional + cylindrical model. + + Notes + ----- + When generating the representative block within this collection, the geometry is checked + against all other blocks to ensure that the number of components are consistent. This implementation + is intended to be opinionated, so if a user attempts to put blocks that have geometric differences + then this will fail. + + This selects a representative block based on the collection of candidates based on the + median block average temperatures as an assumption. + """ + + def _getNewBlock(self): + newBlock = copy.deepcopy(self._selectCandidateBlock()) + newBlock.name = "1D_CYL_AVG_" + newBlock.getMicroSuffix() + return newBlock + + def _selectCandidateBlock(self): + """Selects the candidate block with the median block-average temperature.""" + info = [] + for b in self.getCandidateBlocks(): + info.append((b.getAverageTempInC(), b.getName(), b)) + info.sort() + medianBlockData = info[len(info) // 2] + return medianBlockData[-1] + + def _makeRepresentativeBlock(self): + """Build a representative fuel block based on component number densities.""" + repBlock = self._getNewBlock() + bWeights = [self.getWeight(b) for b in self.getCandidateBlocks()] + componentsInOrder = self._orderComponentsInGroup(repBlock) + + for c, allSimilarComponents in zip(repBlock, componentsInOrder): + allNucsNames, densities = self._getAverageComponentNucs( + allSimilarComponents, bWeights + ) + for nuc, aDensity in zip(allNucsNames, densities): + c.setNumberDensity(nuc, aDensity) + return repBlock + + @staticmethod + def _getAllNucs(components): + """Iterate through components and get all unique nuclides.""" + nucs = set() + for c in components: + nucs = nucs.union(c.getNuclides()) + return sorted(list(nucs)) + + @staticmethod + def _checkComponentConsistency(b, repBlock): + """ + Verify that all components being homogenized have same multiplicity and nuclides + + Raises + ------ + ValueError + When the components in a candidate block do not align with + the components in the representative block. This check includes component area, component multiplicity, + and nuclide composition. + """ + if len(b) != len(repBlock): + raise ValueError( + f"Blocks {b} and {repBlock} have differing number " + f"of components and cannot be homogenized" + ) + for c, repC in zip(b, repBlock): + compString = ( + f"Component {repC} in block {repBlock} and component {c} in block {b}" + ) + if c.p.mult != repC.p.mult: + raise ValueError( + f"{compString} must have the same multiplicity, but they have." + f"{repC.p.mult} and {c.p.mult}, respectively." + ) + + theseNucs = set(c.getNuclides()) + thoseNucs = set(repC.getNuclides()) + diffNucs = theseNucs.symmetric_difference(thoseNucs) + if diffNucs: + raise ValueError( + f"{compString} are in the same location, but nuclides " + f"differ by {diffNucs}. \n{theseNucs} \n{thoseNucs}" + ) + + def _getAverageComponentNucs(self, components, bWeights): + """Compute average nuclide densities by block weights and component area fractions.""" + allNucNames = self._getAllNucs(components) + densities = numpy.zeros(len(allNucNames)) + totalWeight = 0.0 + for c, bWeight in zip(components, bWeights): + weight = bWeight * c.getArea() + totalWeight += weight + densities += weight * numpy.array(c.getNuclideNumberDensities(allNucNames)) + return allNucNames, densities / totalWeight + + def _orderComponentsInGroup(self, repBlock): + """Order the components based on dimension and material type within the representative block.""" + for b in self.getCandidateBlocks(): + self._checkComponentConsistency(b, repBlock) + componentLists = [list(b) for b in self.getCandidateBlocks()] + return [list(comps) for comps in zip(*componentLists)] + + class SlabComponentsAverageBlockCollection(BlockCollection): """ Creates a representative 1D slab block. @@ -402,6 +509,11 @@ class SlabComponentsAverageBlockCollection(BlockCollection): """ + def _getNewBlock(self): + newBlock = copy.deepcopy(self.getCandidateBlocks()[0]) + newBlock.name = "1D_SLAB_AVG_" + newBlock.getMicroSuffix() + return newBlock + def _makeRepresentativeBlock(self): """Build a representative fuel block based on component number densities.""" repBlock = self._getNewBlock() @@ -409,7 +521,7 @@ def _makeRepresentativeBlock(self): componentsInOrder = self._orderComponentsInGroup(repBlock) for c, allSimilarComponents in zip(repBlock, componentsInOrder): - allNucsNames, densities = self._getAverageComponantNucs( + allNucsNames, densities = self._getAverageComponentNucs( allSimilarComponents, bWeights ) for nuc, aDensity in zip(allNucsNames, densities): @@ -510,7 +622,7 @@ def _removeLatticeComponents(repBlock): repBlock.remove(c) return repBlock - def _getAverageComponantNucs(self, components, bWeights): + def _getAverageComponentNucs(self, components, bWeights): """Compute average nuclide densities by block weights and component area fractions.""" allNucNames = self._getAllNucs(components) densities = numpy.zeros(len(allNucNames)) @@ -1160,11 +1272,21 @@ def updateNuclideTemperatures(self, blockCollectionByXsGroup=None): runLog.extra("XS ID: {}, Collection: {}".format(xsID, collection)) +# String constants +MEDIAN_BLOCK_COLLECTION = "Median" +AVERAGE_BLOCK_COLLECTION = "Average" +FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION = "FluxWeightedAverage" +SLAB_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DSlab" +CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION = "ComponentAverage1DCylinder" + +# Mapping between block collection string constants and their +# respective block collection classes. BLOCK_COLLECTIONS = { - "Median": MedianBlockCollection, - "Average": AverageBlockCollection, - "ComponentAverage1DSlab": SlabComponentsAverageBlockCollection, - "FluxWeightedAverage": FluxWeightedAverageBlockCollection, + MEDIAN_BLOCK_COLLECTION: MedianBlockCollection, + AVERAGE_BLOCK_COLLECTION: AverageBlockCollection, + FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION: FluxWeightedAverageBlockCollection, + SLAB_COMPONENTS_BLOCK_COLLECTION: SlabComponentsAverageBlockCollection, + CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION: CylindricalComponentsAverageBlockCollection, } diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index e7ea0dabe..f294fff08 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -26,11 +26,13 @@ See detailed docs in `:doc: Lattice Physics `. """ +from enum import Enum from typing import Dict, Union import voluptuous as vol from armi import runLog +from armi.physics.neutronics import crossSectionGroupManager from armi.physics.neutronics.crossSectionGroupManager import BLOCK_COLLECTIONS from armi.settings import Setting from armi import context @@ -53,19 +55,55 @@ CONF_XS_EXECUTE_EXCLUSIVE = "xsExecuteExclusive" CONF_XS_PRIORITY = "xsPriority" -# These may be used as arguments to ``latticePhysicsInterface._getGeomDependentWriters``. -# This could be an ENUM later. + +class XSGeometryTypes(Enum): + """ + Data structure for storing the available geometry options + within the framework. + """ + + ZERO_DIMENSIONAL = 1 + ONE_DIMENSIONAL_SLAB = 2 + ONE_DIMENSIONAL_CYLINDER = 4 + TWO_DIMENSIONAL_HEX = 8 + + @classmethod + def _mapping(cls): + mapping = { + cls.ZERO_DIMENSIONAL: "0D", + cls.ONE_DIMENSIONAL_SLAB: "1D slab", + cls.ONE_DIMENSIONAL_CYLINDER: "1D cylinder", + cls.TWO_DIMENSIONAL_HEX: "2D hex", + } + return mapping + + @classmethod + def getStr(cls, typeSpec: Enum): + """ + Return a string representation of the given ``typeSpec``. + + Examples + -------- + XSGeometryTypes.getStr(XSGeometryTypes.ZERO_DIMENSIONAL) == "0D" + XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX) == "2D hex" + """ + geometryTypes = list(cls) + if typeSpec not in geometryTypes: + raise TypeError(f"{typeSpec} not in {geometryTypes}") + return cls._mapping()[cls[typeSpec.name]] + + XS_GEOM_TYPES = { - "0D", - "1D slab", - "1D cylinder", - "2D hex", + XSGeometryTypes.getStr(XSGeometryTypes.ZERO_DIMENSIONAL), + XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_SLAB), + XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_CYLINDER), + XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX), } # This dictionary defines the valid set of inputs based on # the geometry type within the ``XSModelingOptions`` _VALID_INPUTS_BY_GEOMETRY_TYPE = { - "0D": { + XSGeometryTypes.getStr(XSGeometryTypes.ZERO_DIMENSIONAL): { CONF_XSID, CONF_GEOM, CONF_BUCKLING, @@ -76,7 +114,7 @@ CONF_XS_EXECUTE_EXCLUSIVE, CONF_XS_PRIORITY, }, - "1D slab": { + XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_SLAB): { CONF_XSID, CONF_GEOM, CONF_MESH_PER_CM, @@ -86,7 +124,7 @@ CONF_XS_EXECUTE_EXCLUSIVE, CONF_XS_PRIORITY, }, - "1D cylinder": { + XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_CYLINDER): { CONF_XSID, CONF_GEOM, CONF_MERGE_INTO_CLAD, @@ -101,7 +139,7 @@ CONF_XS_EXECUTE_EXCLUSIVE, CONF_XS_PRIORITY, }, - "2D hex": { + XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): { CONF_XSID, CONF_GEOM, CONF_BUCKLING, @@ -263,7 +301,9 @@ def _getDefault(self, xsID): "before attempting to add a new XS ID." ) - xsOpt = XSModelingOptions(xsID, geometry="0D") + xsOpt = XSModelingOptions( + xsID, geometry=XSGeometryTypes.getStr(XSGeometryTypes.ZERO_DIMENSIONAL) + ) xsOpt.setDefaults(self._blockRepresentation, self._validBlockTypes) xsOpt.validate() return xsOpt @@ -551,43 +591,70 @@ def setDefaults(self, blockRepresentation, validBlockTypes): defaults = {} if self.xsIsPregenerated: + allowableBlockCollections = [ + crossSectionGroupManager.MEDIAN_BLOCK_COLLECTION, + crossSectionGroupManager.AVERAGE_BLOCK_COLLECTION, + crossSectionGroupManager.FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION, + ] defaults = { CONF_XS_FILE_LOCATION: self.xsFileLocation, CONF_BLOCK_REPRESENTATION: blockRepresentation, } - elif self.geometry == "0D": + elif self.geometry == XSGeometryTypes.getStr(XSGeometryTypes.ZERO_DIMENSIONAL): + allowableBlockCollections = [ + crossSectionGroupManager.MEDIAN_BLOCK_COLLECTION, + crossSectionGroupManager.AVERAGE_BLOCK_COLLECTION, + crossSectionGroupManager.FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION, + ] bucklingSearch = False if self.fluxIsPregenerated else True defaults = { - CONF_GEOM: "0D", + CONF_GEOM: self.geometry, CONF_BUCKLING: bucklingSearch, CONF_DRIVER: "", CONF_BLOCK_REPRESENTATION: blockRepresentation, CONF_BLOCKTYPES: validBlockTypes, CONF_EXTERNAL_FLUX_FILE_LOCATION: self.fluxFileLocation, } - elif self.geometry == "1D slab": + elif self.geometry == XSGeometryTypes.getStr( + XSGeometryTypes.ONE_DIMENSIONAL_SLAB + ): + allowableBlockCollections = [ + crossSectionGroupManager.SLAB_COMPONENTS_BLOCK_COLLECTION, + ] defaults = { - CONF_GEOM: "1D slab", + CONF_GEOM: self.geometry, CONF_MESH_PER_CM: 1.0, - CONF_BLOCK_REPRESENTATION: blockRepresentation, + CONF_BLOCK_REPRESENTATION: crossSectionGroupManager.SLAB_COMPONENTS_BLOCK_COLLECTION, CONF_BLOCKTYPES: validBlockTypes, } - elif self.geometry == "1D cylinder": + elif self.geometry == XSGeometryTypes.getStr( + XSGeometryTypes.ONE_DIMENSIONAL_CYLINDER + ): + allowableBlockCollections = [ + crossSectionGroupManager.CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION + ] defaults = { - CONF_GEOM: "1D cylinder", + CONF_GEOM: self.geometry, CONF_DRIVER: "", CONF_MERGE_INTO_CLAD: ["gap"], CONF_MESH_PER_CM: 1.0, CONF_INTERNAL_RINGS: 0, CONF_EXTERNAL_RINGS: 1, CONF_HOMOGBLOCK: False, - CONF_BLOCK_REPRESENTATION: blockRepresentation, + CONF_BLOCK_REPRESENTATION: crossSectionGroupManager.CYLINDRICAL_COMPONENTS_BLOCK_COLLECTION, CONF_BLOCKTYPES: validBlockTypes, } - elif self.geometry == "2D hex": + elif self.geometry == XSGeometryTypes.getStr( + XSGeometryTypes.TWO_DIMENSIONAL_HEX + ): + allowableBlockCollections = [ + crossSectionGroupManager.MEDIAN_BLOCK_COLLECTION, + crossSectionGroupManager.AVERAGE_BLOCK_COLLECTION, + crossSectionGroupManager.FLUX_WEIGHTED_AVERAGE_BLOCK_COLLECTION, + ] defaults = { - CONF_GEOM: "2D hex", + CONF_GEOM: self.geometry, CONF_BUCKLING: False, CONF_EXTERNAL_DRIVER: True, CONF_DRIVER: "", @@ -603,6 +670,14 @@ def setDefaults(self, blockRepresentation, validBlockTypes): currentValue = getattr(self, attrName) if currentValue is None: setattr(self, attrName, defaultValue) + else: + if attrName == CONF_BLOCK_REPRESENTATION: + if currentValue not in allowableBlockCollections: + raise ValueError( + f"Invalid block collection type `{currentValue}` assigned " + f"for {self.xsID}. Expected one of the " + f"following: {allowableBlockCollections}" + ) self.validate() diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsInterface.py b/armi/physics/neutronics/latticePhysics/latticePhysicsInterface.py index 137f7aaa2..085cb3e1c 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsInterface.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsInterface.py @@ -81,13 +81,6 @@ def __init__(self, r, cs): # Set to True by default, but should be disabled when perturbed cross sections are generated. self._updateBlockNeutronVelocities = True - # Geometry options available through the lattice physics interfaces - self._ZERO_DIMENSIONAL_GEOM = "0D" - self._ONE_DIMENSIONAL_GEOM = "1D" - self._TWO_DIMENSIONAL_GEOM = "2D" - self._SLAB_MODEL = " slab" - self._CYLINDER_MODEL = " cylinder" - self._HEX_MODEL = " hex" self._burnupTolerance = self.cs[CONF_TOLERATE_BURNUP_CHANGE] self._oldXsIdsAndBurnup = {} self.executablePath = self._getExecutablePath() diff --git a/armi/physics/neutronics/latticePhysics/tests/test_latticeInterface.py b/armi/physics/neutronics/latticePhysics/tests/test_latticeInterface.py index 5304c5f27..7f80a6f19 100644 --- a/armi/physics/neutronics/latticePhysics/tests/test_latticeInterface.py +++ b/armi/physics/neutronics/latticePhysics/tests/test_latticeInterface.py @@ -87,10 +87,9 @@ def setUp(self): def test_LatticePhysicsInterface(self): """Super basic test of the LatticePhysicsInterface""" - self.assertEqual(self.latticeInterface._HEX_MODEL.strip(), "hex") + self.assertEqual(self.latticeInterface._updateBlockNeutronVelocities, True) self.assertEqual(self.latticeInterface.executablePath, "/tmp/fake_path") self.assertEqual(self.latticeInterface.executableRoot, "/tmp") - self.latticeInterface.updateXSLibrary(0) self.assertEqual(len(self.latticeInterface._oldXsIdsAndBurnup), 0) diff --git a/armi/physics/neutronics/tests/test_crossSectionManager.py b/armi/physics/neutronics/tests/test_crossSectionManager.py index 9c9c693ef..6e5d600a1 100644 --- a/armi/physics/neutronics/tests/test_crossSectionManager.py +++ b/armi/physics/neutronics/tests/test_crossSectionManager.py @@ -160,7 +160,7 @@ def setUp(self): }, # Steel ] # later sorted by density so less massive block first - self.expectedBlockDensites = [ + self.expectedBlockDensities = [ expectedRepBlanketBlock, expectedRepFuelBlock, expectedRepFuelBlock, @@ -191,9 +191,9 @@ def test_ComponentAverageRepBlock(self): representativeBlockList = list(xsgm.representativeBlocks.values()) representativeBlockList.sort(key=lambda repB: repB.getMass() / repB.getVolume()) - assert len(representativeBlockList) == len(self.expectedBlockDensites) + assert len(representativeBlockList) == len(self.expectedBlockDensities) for b, componentDensities, areas in zip( - representativeBlockList, self.expectedBlockDensites, self.expectedAreas + representativeBlockList, self.expectedBlockDensities, self.expectedAreas ): assert len(b) == len(componentDensities) and len(b) == len(areas) for c, compDensity, compArea in zip(b, componentDensities, areas): @@ -209,6 +209,117 @@ def test_ComponentAverageRepBlock(self): ) +class TestBlockCollectionComponentAverage1DCylinder(unittest.TestCase): + r""" + tests for 1D cylinder XS gen cases + """ + + def setUp(self): + r""" + First part of setup same as test_Cartesian. + Second part of setup builds lists/dictionaries of expected values to compare to. + has expected values for component isotopic atom density and component area + """ + self.o, self.r = test_reactors.loadTestReactor(TEST_ROOT) + + sodiumDensity = {"NA23": 0.022166571826233578} + steelDensity = { + "C": 0.0007685664978992269, + "V": 0.0002718224847461385, + "SI28": 0.0003789374369638149, + "SI29": 1.924063709833714e-05, + "SI30": 1.268328992580968e-05, + "CR50": 0.0004532023742335746, + "CR52": 0.008739556775111474, + "CR53": 0.0009909955713678232, + "CR54": 0.000246679773317009, + "MN55": 0.0004200803669857142, + "FE54": 0.004101496663229472, + "FE56": 0.06438472483061823, + "FE57": 0.0014869241111006412, + "FE58": 0.00019788230265709334, + "NI58": 0.0002944487657779742, + "NI60": 0.00011342053328927859, + "NI61": 4.930763373747379e-06, + "NI62": 1.571788956157717e-05, + "NI64": 4.005163933412346e-06, + "MO92": 7.140180476114493e-05, + "MO94": 4.4505841916481845e-05, + "MO95": 7.659816252004227e-05, + "MO96": 8.02548587207478e-05, + "MO97": 4.594927462728666e-05, + "MO98": 0.00011610009956095838, + "MO100": 4.6334190016834624e-05, + "W182": 3.663619370317025e-05, + "W183": 1.9783544599711936e-05, + "W184": 4.235973352562047e-05, + "W186": 3.9304414603061506e-05, + } + linerAdjustment = 1.014188527784268 + cladDensity = { + nuc: dens * linerAdjustment for nuc, dens in steelDensity.items() + } + fuelDensity = { + "AM241": 2.3605999999999997e-05, + "PU238": 3.7387e-06, + "PU239": 0.0028603799999999996, + "PU240": 0.000712945, + "PU241": 9.823120000000004e-05, + "PU242": 2.02221e-05, + "U235": 0.00405533, + "U238": 0.0134125, + } + self.expectedComponentDensities = [ + fuelDensity, + sodiumDensity, + cladDensity, + steelDensity, + sodiumDensity, + steelDensity, + sodiumDensity, + ] + self.expectedComponentAreas = [ + 99.54797488948871, + 29.719913442616843, + 30.07759373476877, + 1.365897776727751, + 63.184097853691235, + 17.107013842808822, + 1.9717608091694139, + ] + + def test_ComponentAverage1DCylinder(self): + r""" + tests that the XS group manager calculates the expected component atom density + and component area correctly. Order of components is also checked since in + 1D cases the order of the components matters. + """ + xsgm = self.o.getInterface("xsGroups") + + xsgm.interactBOL() + + # Check that the correct defaults are propagated after the interactBOL + # from the cross section group manager is called. + xsOpt = self.o.cs[CONF_CROSS_SECTION]["ZA"] + self.assertEqual(xsOpt.blockRepresentation, "ComponentAverage1DCylinder") + + xsgm.createRepresentativeBlocks() + representativeBlockList = list(xsgm.representativeBlocks.values()) + representativeBlockList.sort(key=lambda repB: repB.getMass() / repB.getVolume()) + reprBlock = xsgm.representativeBlocks["ZA"] + self.assertEqual(reprBlock.name, "1D_CYL_AVG_ZA") + + for c, compDensity, compArea in zip( + reprBlock, self.expectedComponentDensities, self.expectedComponentAreas + ): + self.assertEqual(compArea, c.getArea()) + cNucs = c.getNuclides() + for nuc in cNucs: + self.assertAlmostEqual( + c.getNumberDensity(nuc), compDensity.get(nuc, 0.0) + ) + + class TestBlockCollectionFluxWeightedAverage(unittest.TestCase): def setUp(self): fpFactory = test_lumpedFissionProduct.getDummyLFPFile() diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index ebe8fa713..21028efbb 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -153,7 +153,7 @@ def test_setDefaultSettingsByLowestBuGroupOneDimensional(self): rq = XSModelingOptions( "RQ", geometry="1D cylinder", - blockRepresentation="Average", + blockRepresentation="ComponentAverage1DCylinder", meshSubdivisionsPerCm=1.0, ) xsModel["RQ"] = rq diff --git a/armi/tests/armiRun.yaml b/armi/tests/armiRun.yaml index 3b765e300..9b1407cb0 100644 --- a/armi/tests/armiRun.yaml +++ b/armi/tests/armiRun.yaml @@ -25,6 +25,16 @@ settings: YA: geometry: 0D fluxFileLocation: rzmflxYA + ZA: + geometry: 1D cylinder + blockRepresentation: ComponentAverage1DCylinder + validBlockTypes: + - fuel + externalDriver: false + mergeIntoClad: + - gap + numInternalRings: 1 + numExternalRings: 1 cycleLength: 2000.0 db: false detailAssemLocationsBOL: diff --git a/armi/tests/refSmallReactorBase.yaml b/armi/tests/refSmallReactorBase.yaml index 9bb083c92..963c63b1f 100644 --- a/armi/tests/refSmallReactorBase.yaml +++ b/armi/tests/refSmallReactorBase.yaml @@ -237,6 +237,14 @@ blocks: isotopics: MOX mult: 169.0 od: 0.86602 + bond: &component_fuel_bond2 + shape: Circle + material: Sodium + Tinput: 450.0 + Thot: 450.0 + id: fuel.od + mult: fuel.mult + od: liner1.id clad: *component_fuel_clad liner1: &component_fuel2_liner1 shape: Circle @@ -256,7 +264,6 @@ blocks: mergeWith: clad mult: 169.0 od: 0.99 - bond: *component_fuel_bond wire: *component_fuel_wire coolant: *component_fuel_coolant duct: *component_fuel_duct @@ -377,7 +384,7 @@ assemblies: blocks: [*block_grid_plate, *block_fuel2, *block_fuel2, *block_fuel2, *block_plenum] height: *standard_heights axial mesh points: *standard_axial_mesh_points - xs types: *igniter_fuel_xs_types + xs types: &middle_fuel_xs_types [Z, Z, Z, Z, Z] annular fuel: specifier: AF blocks: [*block_grid_plate, *block_fuel3, *block_fuel3, *block_fuel3, *block_plenum] diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst index 3efc2cd6b..b116ecf6d 100644 --- a/doc/release/0.2.rst +++ b/doc/release/0.2.rst @@ -20,7 +20,8 @@ What's new in ARMI #. 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 `_) #. Use ``minimumNuclideDensity`` setting when generating macroscopic XS. (`PR#1248 `_) -#. Improve support for single component axial expansion and general cleanup of axial expansion unit tests. (`PR#1230 `_) +#. Improve support for single component axial expansion and general cleanup of axial expansion unit tests. (`PR#1230 `_) +#. New cross section group representative block type for 1D cylindrical models. (`PR#1238 `_) #. TBD Bug fixes