From f5681d7162b100fa74e290f9a0f7c5e807076a54 Mon Sep 17 00:00:00 2001 From: John Stilley <1831479+john-science@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:18:39 -0800 Subject: [PATCH] Adding requirement crumbs for Materials and Composites (#1487) --- armi/materials/tests/test_materials.py | 49 ++----------- armi/materials/tests/test_uZr.py | 99 +++++++++++++++++++++++++- armi/reactor/components/component.py | 20 ++++++ armi/reactor/composites.py | 35 ++++++++- armi/reactor/grids/grid.py | 2 - armi/reactor/tests/test_blocks.py | 25 +++++-- armi/reactor/tests/test_components.py | 26 ++++++- armi/reactor/tests/test_composites.py | 57 ++++++++++++++- armi/reactor/tests/test_reactors.py | 6 +- 9 files changed, 260 insertions(+), 59 deletions(-) diff --git a/armi/materials/tests/test_materials.py b/armi/materials/tests/test_materials.py index e146836966..f597983bc7 100644 --- a/armi/materials/tests/test_materials.py +++ b/armi/materials/tests/test_materials.py @@ -35,12 +35,7 @@ def setUp(self): self.mat = self.MAT_CLASS() def test_isPicklable(self): - """Test that all materials are picklable so we can do MPI communication of state. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES0 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test that all materials are picklable so we can do MPI communication of state.""" stream = pickle.dumps(self.mat) mat = pickle.loads(stream) @@ -50,21 +45,11 @@ def test_isPicklable(self): ) def test_density(self): - """Test that all materials produce a zero density from density. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES1 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test that all materials produce a zero density from density.""" self.assertNotEqual(self.mat.density(500), 0) def test_TD(self): - """Test the material density. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES2 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test the material density.""" self.assertEqual(self.mat.getTD(), self.mat.theoreticalDensityFrac) self.mat.clearCache() @@ -75,12 +60,7 @@ def test_TD(self): self.assertEqual(self.mat.cached, {}) def test_duplicate(self): - """Test the material duplication. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES3 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test the material duplication.""" mat = self.mat.duplicate() self.assertEqual(len(mat.massFrac), len(self.mat.massFrac)) @@ -92,12 +72,7 @@ def test_duplicate(self): self.assertEqual(mat.theoreticalDensityFrac, self.mat.theoreticalDensityFrac) def test_cache(self): - """Test the material cache. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES4 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test the material cache.""" self.mat.clearCache() self.assertEqual(len(self.mat.cached), 0) @@ -108,23 +83,13 @@ def test_cache(self): self.assertEqual(val, "Noether") def test_densityKgM3(self): - """Test the density for kg/m^3. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES5 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test the density for kg/m^3.""" dens = self.mat.density(500) densKgM3 = self.mat.densityKgM3(500) self.assertEqual(dens * 1000.0, densKgM3) def test_pseudoDensityKgM3(self): - """Test the pseudo density for kg/m^3. - - .. test:: Test the material base class. - :id: T_ARMI_MAT_PROPERTIES6 - :tests: R_ARMI_MAT_PROPERTIES - """ + """Test the pseudo density for kg/m^3.""" dens = self.mat.pseudoDensity(500) densKgM3 = self.mat.pseudoDensityKgM3(500) self.assertEqual(dens * 1000.0, densKgM3) diff --git a/armi/materials/tests/test_uZr.py b/armi/materials/tests/test_uZr.py index c1ecfaa794..d6072b4f5d 100644 --- a/armi/materials/tests/test_uZr.py +++ b/armi/materials/tests/test_uZr.py @@ -13,16 +13,109 @@ # limitations under the License. """Tests for simplified UZr material.""" -import unittest +from unittest import TestCase +import pickle -from armi.materials.tests import test_materials from armi.materials.uZr import UZr -class UZR_TestCase(test_materials._Material_Test, unittest.TestCase): +class UZR_TestCase(TestCase): + MAT_CLASS = UZr + def setUp(self): + self.mat = self.MAT_CLASS() + + def test_isPicklable(self): + """Test that materials are picklable so we can do MPI communication of state. + + .. test:: Test the material base class has temp-dependent thermal conductivity curves. + :id: T_ARMI_MAT_PROPERTIES0 + :tests: R_ARMI_MAT_PROPERTIES + """ + stream = pickle.dumps(self.mat) + mat = pickle.loads(stream) + + # check a property that is sometimes interpolated. + self.assertEqual( + self.mat.thermalConductivity(500), mat.thermalConductivity(500) + ) + + def test_TD(self): + """Test the material theoretical density. + + .. test:: Test the material base class has temp-dependent TD curves. + :id: T_ARMI_MAT_PROPERTIES2 + :tests: R_ARMI_MAT_PROPERTIES + """ + self.assertEqual(self.mat.getTD(), self.mat.theoreticalDensityFrac) + + self.mat.clearCache() + self.mat._setCache("dummy", 666) + self.assertEqual(self.mat.cached, {"dummy": 666}) + self.mat.adjustTD(0.5) + self.assertEqual(0.5, self.mat.theoreticalDensityFrac) + self.assertEqual(self.mat.cached, {}) + + def test_duplicate(self): + """Test the material duplication. + + .. test:: Materials shall calc mass fracs at init. + :id: T_ARMI_MAT_FRACS + :tests: R_ARMI_MAT_FRACS + """ + mat = self.mat.duplicate() + + self.assertEqual(len(mat.massFrac), len(self.mat.massFrac)) + for key in self.mat.massFrac: + self.assertEqual(mat.massFrac[key], self.mat.massFrac[key]) + + self.assertEqual(mat.parent, self.mat.parent) + self.assertEqual(mat.refDens, self.mat.refDens) + self.assertEqual(mat.theoreticalDensityFrac, self.mat.theoreticalDensityFrac) + + def test_cache(self): + """Test the material cache.""" + self.mat.clearCache() + self.assertEqual(len(self.mat.cached), 0) + + self.mat._setCache("Emmy", "Noether") + self.assertEqual(len(self.mat.cached), 1) + + val = self.mat._getCached("Emmy") + self.assertEqual(val, "Noether") + + def test_densityKgM3(self): + """Test the density for kg/m^3. + + .. test:: Test the material base class has temp-dependent density. + :id: T_ARMI_MAT_PROPERTIES5 + :tests: R_ARMI_MAT_PROPERTIES + """ + dens = self.mat.density(500) + densKgM3 = self.mat.densityKgM3(500) + self.assertEqual(dens * 1000.0, densKgM3) + + def test_pseudoDensityKgM3(self): + """Test the pseudo density for kg/m^3. + + .. test:: Test the material base class has temp-dependent 2D density. + :id: T_ARMI_MAT_PROPERTIES6 + :tests: R_ARMI_MAT_PROPERTIES + """ + dens = self.mat.pseudoDensity(500) + densKgM3 = self.mat.pseudoDensityKgM3(500) + self.assertEqual(dens * 1000.0, densKgM3) + def test_density(self): + """Test that all materials produce a zero density from density. + + .. test:: Test the material base class has temp-dependent density. + :id: T_ARMI_MAT_PROPERTIES1 + :tests: R_ARMI_MAT_PROPERTIES + """ + self.assertNotEqual(self.mat.density(500), 0) + cur = self.mat.density(400) ref = 15.94 delta = ref * 0.01 diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index 49a1f5c4d3..e8202a13c6 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -173,6 +173,10 @@ class Component(composites.Composite, metaclass=ComponentType): :id: I_ARMI_COMP_DEF :implements: R_ARMI_COMP_DEF + .. impl:: Order components by there outermost diameter (using the < operator). + :id: I_ARMI_COMP_ORDER + :implements: R_ARMI_COMP_ORDER + Attributes ---------- temperatureInC : float @@ -387,6 +391,10 @@ def getProperties(self): .. impl:: Material properties are retrievable. :id: I_ARMI_COMP_MAT0 :implements: R_ARMI_COMP_MAT + + .. impl:: Components have one-and-only-one material. + :id: I_ARMI_COMP_1MAT + :implements: R_ARMI_COMP_1MAT """ return self.material @@ -658,6 +666,10 @@ def setNumberDensity(self, nucName, val): """ Set heterogeneous number density. + .. impl:: Setting nuclide fractions. + :id: I_ARMI_COMP_NUCLIDE_FRACS0 + :implements: R_ARMI_COMP_NUCLIDE_FRACS + Parameters ---------- nucName : str @@ -676,6 +688,10 @@ def setNumberDensities(self, numberDensities): """ Set one or more multiple number densities. Clears out any number density not listed. + .. impl:: Setting nuclide fractions. + :id: I_ARMI_COMP_NUCLIDE_FRACS1 + :implements: R_ARMI_COMP_NUCLIDE_FRACS + Parameters ---------- numberDensities : dict @@ -888,6 +904,10 @@ def getThermalExpansionFactor(self, Tc=None, T0=None): """ Retrieves the material thermal expansion fraction. + .. impl:: Calculates radial thermal expansion factor. + :id: I_ARMI_COMP_EXPANSION + :implements: R_ARMI_COMP_EXPANSION + Parameters ---------- Tc : float, optional diff --git a/armi/reactor/composites.py b/armi/reactor/composites.py index 40cfc9c190..480e4463a3 100644 --- a/armi/reactor/composites.py +++ b/armi/reactor/composites.py @@ -624,6 +624,10 @@ def getParameterCollection(cls): created and associated. So, we use this top-level class method to dig dynamically down to the underlying parameter collection type. + .. impl:: Composites (and all ARMI objects) have parameter collections. + :id: I_ARMI_CMP_PARAMS + :implements: R_ARMI_CMP_PARAMS + See Also -------- :py:meth:`armi.reactor.parameters.parameterCollections.ParameterCollection.__reduce__` @@ -669,8 +673,8 @@ def hasFlags(self, typeID: TypeSpec, exact=False): Determine if this object is of a certain type. .. impl:: Flags can be queried. - :id: I_ARMI_CMP_HAS_FLAGS - :implements: R_ARMI_CMP_HAS_FLAGS + :id: I_ARMI_CMP_FLAG + :implements: R_ARMI_CMP_FLAG Parameters ---------- @@ -1224,6 +1228,10 @@ def getNumberDensity(self, nucName): """ Return the number density of a nuclide in atoms/barn-cm. + .. impl:: Get number density for a specific nuclide + :id: I_ARMI_CMP_NUC0 + :implements: R_ARMI_CMP_NUC + Notes ----- This can get called very frequently and has to do volume computations so should @@ -1239,7 +1247,12 @@ def getNumberDensity(self, nucName): return self.getNuclideNumberDensities([nucName])[0] def getNuclideNumberDensities(self, nucNames): - """Return a list of number densities in atoms/barn-cm for the nuc names requested.""" + """Return a list of number densities in atoms/barn-cm for the nuc names requested. + + .. impl:: Get number densities for specific nuclides. + :id: I_ARMI_CMP_NUC1 + :implements: R_ARMI_CMP_NUC + """ volumes = numpy.array( [ c.getVolume() / (c.parent.getSymmetryFactor() if c.parent else 1.0) @@ -2435,6 +2448,10 @@ def getComponentByName(self, name): """ Gets a particular component from this object, based on its name. + .. impl:: Get child component by name. + :id: I_ARMI_CMP_BY_NAME + :implements: R_ARMI_CMP_BY_NAME + Parameters ---------- name : str @@ -2646,6 +2663,10 @@ class Composite(ArmiObject): mixed with siblings in a grid. This allows mixing grid-representation with explicit representation, often useful in advanced assemblies and thermal reactors. + + .. impl:: Composites are a physical part of the reactor in a hierarchical data model. + :id: I_ARMI_CMP + :implements: R_ARMI_CMP """ def __init__(self, name): @@ -2751,6 +2772,10 @@ def getChildren( """ Return the children objects of this composite. + .. impl:: Composites have children, in the hierarchical data model. + :id: I_ARMI_CMP_CHILDREN + :implements: R_ARMI_CMP_CHILDREN + Parameters ---------- deep : boolean, optional @@ -2865,6 +2890,10 @@ def syncMpiState(self): In parallelized runs, if each process has its own copy of the entire reactor hierarchy, this method synchronizes the state of all parameters on all objects. + .. impl:: Composites can be synchronized across MPI threads. + :id: I_ARMI_CMP_MPI + :implements: R_ARMI_CMP_MPI + Returns ------- int diff --git a/armi/reactor/grids/grid.py b/armi/reactor/grids/grid.py index 7d509eda40..8e33bdec4c 100644 --- a/armi/reactor/grids/grid.py +++ b/armi/reactor/grids/grid.py @@ -183,7 +183,6 @@ def overlapsWhichSymmetryLine(self, indices: IJType) -> Optional[int]: None if not line of symmetry goes through the object at the requested index. Otherwise, some grid constants like ``BOUNDARY_CENTER`` will be returned. - """ @abstractmethod @@ -244,5 +243,4 @@ def reduce(self) -> Tuple[Hashable, ...]: Notes ----- For consistency, the second to last argument **must** be the geomType - """ diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index b24c0d42d1..a540ce4b61 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -23,7 +23,6 @@ from armi import materials, runLog, settings, tests from armi.reactor import blueprints -from armi.reactor.blueprints.tests.test_blockBlueprints import FULL_BP from armi.reactor.components import basicShapes, complexShapes from armi.nucDirectory import nucDir, nuclideBases from armi.nuclearDataIO.cccc import isotxs @@ -306,7 +305,9 @@ def getComponentData(component): class TestDetailedNDensUpdate(unittest.TestCase): - def setUp(self): + def test_updateDetailedNdens(self): + from armi.reactor.blueprints.tests.test_blockBlueprints import FULL_BP + cs = settings.Settings() with io.StringIO(FULL_BP) as stream: bps = blueprints.Blueprints.load(stream) @@ -317,7 +318,6 @@ def setUp(self): a.add(buildSimpleFuelBlock()) self.r.core.add(a) - def test_updateDetailedNdens(self): # get first block in assembly with 'fuel' key block = self.r.core[0][0] # get nuclides in first component in block @@ -1237,13 +1237,24 @@ def test_getComponentsOfMaterial(self): ) def test_getComponentByName(self): + """Test children by name. + + .. test:: Get children by name. + :id: I_ARMI_CMP_BY_NAME0 + :tests: R_ARMI_CMP_BY_NAME + """ self.assertIsNone( self.block.getComponentByName("not the droid youre looking for") ) self.assertIsNotNone(self.block.getComponentByName("annular void")) def test_getSortedComponentsInsideOfComponent(self): - """Test that components can be sorted within a block and returned in the correct order.""" + """Test that components can be sorted within a block and returned in the correct order. + + .. test:: Get children by name. + :id: I_ARMI_CMP_BY_NAME1 + :tests: R_ARMI_CMP_BY_NAME + """ expected = [ self.block.getComponentByName(c) for c in [ @@ -1262,6 +1273,12 @@ def test_getSortedComponentsInsideOfComponent(self): self.assertListEqual(actual, expected) def test_getSortedComponentsInsideOfComponentSpecifiedTypes(self): + """Test that components can be sorted within a block and returned in the correct order. + + .. test:: Get children by name. + :id: I_ARMI_CMP_BY_NAME2 + :tests: R_ARMI_CMP_BY_NAME + """ expected = [ self.block.getComponentByName(c) for c in [ diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 899deeea0a..1c16b8cb60 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -174,6 +174,10 @@ def test_initializeComponentMaterial(self): .. test:: Components are made of one material. :id: T_ARMI_COMP_1MAT0 :tests: R_ARMI_COMP_1MAT + + .. test:: Define a component. + :id: T_ARMI_COMP_DEF0 + :tests: R_ARMI_COMP_DEF """ expectedName = "TestComponent" actualName = self.component.getName() @@ -225,6 +229,12 @@ class TestNullComponent(TestGeneralComponents): componentCls = NullComponent def test_cmp(self): + """Test null component. + + .. test:: Define a component. + :id: T_ARMI_COMP_DEF1 + :tests: R_ARMI_COMP_DEF + """ cur = self.component ref = DerivedShape("DerivedShape", "Material", 0, 0) self.assertLess(cur, ref) @@ -431,6 +441,10 @@ def test_getThermalExpansionFactorConservedMassByLinearExpansionPercent(self): .. test:: Circle shaped component :id: T_ARMI_COMP_SHAPES0 :tests: R_ARMI_COMP_SHAPES + + .. test:: Calculate thermal expansion. + :id: I_ARMI_COMP_EXPANSION0 + :tests: R_ARMI_COMP_EXPANSION """ hotTemp = 700.0 dLL = self.component.material.linearExpansionFactor( @@ -446,6 +460,10 @@ def test_getDimension(self): .. test:: Retrieve a dimension at a temperature. :id: T_ARMI_COMP_DIMS1 :tests: R_ARMI_COMP_DIMS + + .. test:: Calculate thermal expansion. + :id: I_ARMI_COMP_EXPANSION1 + :tests: R_ARMI_COMP_EXPANSION """ hotTemp = 700.0 ref = self._od * self.component.getThermalExpansionFactor(Tc=hotTemp) @@ -528,7 +546,7 @@ def test_badComponentName(self): _gap = Circle("gap", "Void", **gapDims) def test_componentInteractionsLinkingBySubtraction(self): - r"""Tests linking of components by subtraction.""" + """Tests linking of components by subtraction.""" nPins = 217 gapDims = {"Tinput": 25.0, "Thot": 430.0, "od": 1.0, "id": 0.9, "mult": nPins} gap = Circle("gap", "Void", **gapDims) @@ -889,6 +907,12 @@ class TestSolidRectangle(TestShapedComponent): } def test_getBoundingCircleOuterDiameter(self): + """Test get bounding circle of the outer diameter. + + .. test:: Define a component. + :id: T_ARMI_COMP_DEF2 + :tests: R_ARMI_COMP_DEF + """ ref = math.sqrt(50) cur = self.component.getBoundingCircleOuterDiameter(cold=True) self.assertAlmostEqual(ref, cur) diff --git a/armi/reactor/tests/test_composites.py b/armi/reactor/tests/test_composites.py index 31f8d3f540..6b127d1ac8 100644 --- a/armi/reactor/tests/test_composites.py +++ b/armi/reactor/tests/test_composites.py @@ -110,7 +110,17 @@ def setUp(self): container.add(nested) self.container = container - def test_Composite(self): + def test_composite(self): + """Test basic Composite things. + + .. test:: Composites are a physical item. + :id: T_ARMI_CMP0 + :tests: R_ARMI_CMP + + .. test:: Composites are part of a hierarchical model. + :id: T_ARMI_CMP_CHILDREN0 + :tests: R_ARMI_CMP_CHILDREN + """ container = self.container children = container.getChildren() @@ -124,6 +134,16 @@ def test_iterComponents(self): self.assertIn(self.thirdGen, list(self.container.iterComponents())) def test_getChildren(self): + """Test the get children method. + + .. test:: Composites are a physical part of the reactor. + :id: T_ARMI_CMP1 + :tests: R_ARMI_CMP + + .. test:: Composites are part of a hierarchical model. + :id: T_ARMI_CMP_CHILDREN1 + :tests: R_ARMI_CMP_CHILDREN + """ # There are 5 leaves and 1 composite in container. The composite has one leaf. firstGen = self.container.getChildren() self.assertEqual(len(firstGen), 6) @@ -141,6 +161,18 @@ def test_getChildren(self): ) self.assertEqual(len(onlyLiner), 1) + def test_getName(self): + """Test the getName method. + + .. test:: Composites names should be accessible. + :id: T_ARMI_CMP_GET_NAME + :tests: R_ARMI_CMP_GET_NAME + """ + self.assertEqual(self.secondGen.getName(), "liner") + self.assertEqual(self.thirdGen.getName(), "pin 77") + self.assertEqual(self.secondGen.getName(), "liner") + self.assertEqual(self.container.getName(), "inner test fuel") + def test_sort(self): # in this case, the children should start sorted c0 = [c.name for c in self.container.getChildren()] @@ -221,8 +253,8 @@ def test_hasFlags(self): """Ensure flags are queryable. .. test:: Flags can be queried. - :id: T_ARMI_CMP_HAS_FLAGS - :tests: R_ARMI_CMP_HAS_FLAGS + :id: T_ARMI_CMP_FLAG + :tests: R_ARMI_CMP_FLAG """ self.container.setType("fuel") self.assertFalse(self.container.hasFlags(Flags.SHIELD | Flags.FUEL, exact=True)) @@ -522,6 +554,12 @@ def test_getFissileMass(self): self.assertAlmostEqual(cur, ref, places=places) def test_getMaxParam(self): + """Test getMaxParam(). + + .. test:: Composites have parameter collections. + :id: T_ARMI_CMP_PARAMS0 + :tests: R_ARMI_CMP_PARAMS + """ for ci, c in enumerate(self.Block): if isinstance(c, basicShapes.Circle): c.p.id = ci @@ -532,6 +570,12 @@ def test_getMaxParam(self): self.assertIs(comp, lastSeen) def test_getMinParam(self): + """Test getMinParam(). + + .. test:: Composites have parameter collections. + :id: T_ARMI_CMP_PARAMS1 + :tests: R_ARMI_CMP_PARAMS + """ for ci, c in reversed(list(enumerate(self.Block))): if isinstance(c, basicShapes.Circle): c.p.id = ci @@ -642,11 +686,18 @@ def test_getNumberDensities(self): .. test:: Number density of composite is retrievable. :id: T_ARMI_CMP_GET_NDENS0 :tests: R_ARMI_CMP_GET_NDENS + + .. test:: Get number densities. + :id: T_ARMI_CMP_NUC + :tests: R_ARMI_CMP_NUC """ ndens = self.obj.getNumberDensities() self.assertAlmostEqual(0.0001096, ndens["SI"], 7) self.assertAlmostEqual(0.0000368, ndens["W"], 7) + ndens = self.obj.getNumberDensity("SI") + self.assertAlmostEqual(0.0001096, ndens, 7) + def test_dimensionReport(self): report = self.obj.setComponentDimensionsReport() self.assertEqual(len(report), len(self.obj)) diff --git a/armi/reactor/tests/test_reactors.py b/armi/reactor/tests/test_reactors.py index 9a3aabac9a..2fa22a6f94 100644 --- a/armi/reactor/tests/test_reactors.py +++ b/armi/reactor/tests/test_reactors.py @@ -246,8 +246,12 @@ def test_coreSfp(self): :tests: R_ARMI_R .. test:: The reactor object includes a core and an SFP. - :id: T_ARMI_R_CHILDREN + :id: T_ARMI_R_CHILDREN1 :tests: R_ARMI_R_CHILDREN + + .. test:: Components are a physical part of the reactor. + :id: T_ARMI_CMP2 + :tests: R_ARMI_CMP """ self.assertTrue(isinstance(self.r.core, reactors.Core)) self.assertTrue(isinstance(self.r.sfp, SpentFuelPool))