# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for the composite pattern."""
import itertools
import logging
import unittest
from copy import deepcopy
from armi import nuclearDataIO, runLog, settings, utils
from armi.nucDirectory import nucDir, nuclideBases
from armi.physics.neutronics.fissionProductModel.tests.test_lumpedFissionProduct import (
getDummyLFPFile,
)
from armi.reactor import assemblies, components, composites, grids, parameters
from armi.reactor.blueprints import assemblyBlueprint
from armi.reactor.components import basicShapes
from armi.reactor.composites import getReactionRateDict
from armi.reactor.flags import Flags, TypeSpec
from armi.reactor.tests.test_blocks import loadTestBlock
from armi.testing import loadTestReactor
from armi.tests import ISOAA_PATH, mockRunLogs
[docs]
class MockBP:
allNuclidesInProblem = set(nuclideBases.byName.keys())
""":meta hide-value:"""
activeNuclides = allNuclidesInProblem
""":meta hide-value:"""
inactiveNuclides = set()
elementsToExpand = set()
customIsotopics = {}
[docs]
def getDummyParamDefs():
dummyDefs = parameters.ParameterDefinitionCollection()
with dummyDefs.createBuilder() as pb:
pb.defParam("type", units=utils.units.UNITLESS, description="Fake type")
return dummyDefs
_testGrid = grids.CartesianGrid.fromRectangle(0.01, 0.01)
[docs]
class DummyComposite(composites.Composite):
pDefs = getDummyParamDefs()
def __init__(self, name, i=0):
composites.Composite.__init__(self, name)
self.p.type = name
self.spatialLocator = grids.IndexLocation(i, i, i, _testGrid)
[docs]
class DummyLeaf(composites.Composite):
pDefs = getDummyParamDefs()
def __init__(self, name, i=0):
composites.Composite.__init__(self, name)
self.p.type = name
self.spatialLocator = grids.IndexLocation(i, i, i, _testGrid)
# Some special material attribute for testing getChildren(includeMaterials=True)
self.material = ("hello", "world")
[docs]
def getChildren(self, deep=False, generationNum=1, includeMaterials=False, predicate=None):
"""Return empty list, representing that this object has no children."""
return []
[docs]
def getChildrenWithFlags(self, typeSpec: TypeSpec, exactMatch=True):
"""Return empty list, representing that this object has no children."""
return []
[docs]
def getBoundingCircleOuterDiameter(self, Tc=None, cold=False):
return 1.0
[docs]
def iterComponents(self, typeSpec=None, exact=False):
if self.hasFlags(typeSpec, exact):
yield self
[docs]
class TestCompositePattern(unittest.TestCase):
def setUp(self):
self.cs = settings.Settings()
runLog.setVerbosity("error")
container = DummyComposite("inner test fuel", 99)
for i in range(5):
leaf = DummyLeaf(f"duct {i}", i + 100)
leaf.setType("duct")
container.add(leaf)
nested = DummyComposite("clad", 98)
nested.setType("clad")
self.cladChild = nested
self.secondGen = DummyComposite("liner", 97)
self.thirdGen = DummyLeaf("pin 77", 33)
self.secondGen.add(self.thirdGen)
nested.add(self.secondGen)
container.add(nested)
self.container = container
# Composite tree structure in list of lists for testing
# tree[i] contains the children at "generation" or "depth" i
self.tree: list[list[composites.Composite]] = [
[self.container],
list(self.container),
[self.secondGen],
[self.thirdGen],
]
[docs]
def test_composite(self):
"""Test basic Composite things.
.. test:: Composites are part of a hierarchical model.
:id: T_ARMI_CMP0
:tests: R_ARMI_CMP
"""
container = self.container
children = container.getChildren()
for child in children:
self.assertEqual(child.parent, container)
allChildren = container.getChildren(deep=True)
self.assertEqual(len(allChildren), 8)
[docs]
def test_printContents(self):
with mockRunLogs.BufferLog() as mock:
self.assertEqual("", mock.getStdout())
testName = "test_printContents"
runLog.LOG.startLog(testName)
runLog.LOG.setVerbosity(logging.IMPORTANT)
self.container.printContents(includeNuclides=True)
logMsg = mock.getStdout()
self.assertIn("DummyComposite", logMsg)
self.assertIn("DummyLeaf", logMsg)
[docs]
def test_iterComponents(self):
self.assertIn(self.thirdGen, list(self.container.iterComponents()))
[docs]
def test_getChildren(self):
"""Test the get children method.
.. test:: Composites are part of a hierarchical model.
:id: T_ARMI_CMP1
:tests: R_ARMI_CMP
"""
firstGen = self.container.getChildren()
self.assertEqual(firstGen, self.tree[1])
secondGen = self.container.getChildren(generationNum=2)
self.assertEqual(secondGen, self.tree[2])
self.assertIs(secondGen[0], self.secondGen)
third = self.container.getChildren(generationNum=3)
self.assertEqual(third, self.tree[3])
self.assertIs(third[0], self.thirdGen)
allC = self.container.getChildren(deep=True)
expected = self.tree[1] + self.tree[2] + self.tree[3]
self.assertTrue(
all(a is e for a, e in itertools.zip_longest(allC, expected)),
msg=f"Deep traversal differs: {allC=} != {expected=}",
)
onlyLiner = self.container.getChildren(deep=True, predicate=lambda o: o.p.type == "liner")
self.assertEqual(len(onlyLiner), 1)
self.assertIs(onlyLiner[0], self.secondGen)
[docs]
def test_getChildrenWithMaterials(self):
"""Test the ability for getChildren to place the material after the object."""
withMaterials = self.container.getChildren(deep=True, includeMaterials=True)
# Grab the iterable so we can control the progression
items = iter(withMaterials)
for item in items:
expectedMat = getattr(item, "material", None)
if expectedMat is None:
continue
# Material should be the next item in the list
actualMat = next(items)
self.assertIs(actualMat, expectedMat)
break
else:
raise RuntimeError("No materials found with includeMaterials=True")
[docs]
def test_iterChildren(self):
"""Detailed testing on Composite.iterChildren."""
def compareIterables(actual, expected: list[composites.Composite]):
for e in expected:
a = next(actual)
self.assertIs(a, e)
# Ensure we've consumed the actual iterator and there's nothing left
with self.assertRaises(StopIteration):
next(actual)
compareIterables(self.container.iterChildren(), self.tree[1])
compareIterables(self.container.iterChildren(generationNum=2), self.tree[2])
compareIterables(self.container.iterChildren(generationNum=3), self.tree[3])
compareIterables(
self.container.iterChildren(deep=True),
self.tree[1] + self.tree[2] + self.tree[3],
)
[docs]
def test_iterAndGetChildren(self):
"""Compare that iter children and get children are consistent."""
self._compareIterGetChildren()
self._compareIterGetChildren(deep=True)
self._compareIterGetChildren(generationNum=2)
# Some wacky predicate just to check we can use that too
self._compareIterGetChildren(deep=True, predicate=lambda c: len(c.name) % 3)
def _compareIterGetChildren(self, **kwargs):
fromIter = self.container.iterChildren(**kwargs)
fromGetter = self.container.getChildren(**kwargs)
msg = repr(kwargs)
# Use zip longest just in case one iterator comes up short
for count, (it, gt) in enumerate(itertools.zip_longest(fromIter, fromGetter)):
self.assertIs(it, gt, msg=f"{count=} :: {msg}")
[docs]
def test_simpleIterChildren(self):
"""Test that C.iterChildren() is identical to iter(C)."""
for count, (fromNative, fromIterChildren) in enumerate(
itertools.zip_longest(self.container, self.container.iterChildren())
):
self.assertIs(fromIterChildren, fromNative, msg=count)
[docs]
def test_iterChildrenWithMaterials(self):
"""Test that C.iterChildrenWithMaterials gets materials following their parent component."""
items = iter(self.container.iterChildrenWithMaterials(deep=True))
for item in items:
if isinstance(item, components.Component):
mat = next(items)
self.assertIs(mat, item.material)
[docs]
def test_getName(self):
"""Test the getName method."""
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")
[docs]
def test_sort(self):
# in this case, the children should start sorted
c0 = [c.name for c in self.container]
self.container.sort()
c1 = [c.name for c in self.container]
self.assertNotEqual(c0, c1)
# verify repeated sorting behave
for _ in range(3):
self.container.sort()
ci = [c.name for c in self.container]
self.assertEqual(c1, ci)
# break the order
children = self.container.getChildren()
self.container._children = children[2:] + children[:2]
c2 = [c.name for c in self.container]
self.assertNotEqual(c1, c2)
# verify the sort order
self.container.sort()
c3 = [c.name for c in self.container]
self.assertEqual(c1, c3)
[docs]
def test_areChildernOfType(self):
expectedResults = [False, False, False, False, False, True]
for i, b in enumerate(self.container.doChildrenHaveFlags(Flags.CLAD)):
self.assertEqual(b, expectedResults[i])
[docs]
def test_containsAtLeastOneChildOfType(self):
c = self.container
self.assertTrue(c.containsAtLeastOneChildWithFlags(Flags.DUCT))
self.assertTrue(c.containsAtLeastOneChildWithFlags(Flags.CLAD))
[docs]
def test_containsOnlyChildrenOfType(self):
c = self.container
for b in c:
b.setType("bond")
self.assertTrue(c.containsOnlyChildrenWithFlags(Flags.BOND))
[docs]
def test_nameContains(self):
c = self.container
c.setName("test one two three")
self.assertTrue(c.nameContains("one"))
self.assertTrue(c.nameContains("One"))
self.assertTrue(c.nameContains("THREE"))
self.assertFalse(c.nameContains("nope"))
self.assertFalse(c.nameContains(["nope"]))
self.assertTrue(c.nameContains(["one", "TWO", "three"]))
self.assertTrue(c.nameContains(["nope", "dope", "three"]))
[docs]
def test_nucSpec(self):
self.assertEqual(self.container._getNuclidesFromSpecifier("U235"), ["U235"])
uNucs = self.container._getNuclidesFromSpecifier("U")
self.assertIn("U235", uNucs)
self.assertIn("U241", uNucs)
self.assertIn("U227", uNucs)
self.assertEqual(self.container._getNuclidesFromSpecifier(["U238", "U235"]), ["U235", "U238"])
uzr = self.container._getNuclidesFromSpecifier(["U238", "U235", "ZR"])
self.assertIn("U235", uzr)
self.assertIn("ZR92", uzr)
self.assertNotIn("ZR", uzr)
puIsos = self.container._getNuclidesFromSpecifier(["PU"]) # PU is special because it has no natural isotopics
self.assertIn("PU239", puIsos)
self.assertNotIn("PU", puIsos)
self.assertEqual(self.container._getNuclidesFromSpecifier(["FE", "FE56"]).count("FE56"), 1)
[docs]
def test_hasFlags(self):
"""Ensure flags are queryable.
.. test:: Flags can be queried.
: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))
self.assertTrue(self.container.hasFlags(Flags.FUEL))
self.assertTrue(self.container.hasFlags(None))
[docs]
def test_hasFlagsSubstring(self):
"""Make sure typespecs with the same word in them no longer match."""
self.container.setType("intercoolant")
self.assertFalse(self.container.hasFlags(Flags.COOLANT))
self.assertFalse(self.container.hasFlags(Flags.COOLANT, exact=True))
self.assertTrue(self.container.hasFlags(Flags.INTERCOOLANT, exact=True))
self.container.setType("innerduct")
self.assertFalse(self.container.hasFlags(Flags.DUCT, exact=True))
[docs]
def test_hasFlagsNoTypeSpecified(self):
self.container.setType("fuel")
types = [None, [], [None]]
for t in types:
self.assertTrue(self.container.hasFlags(t))
self.assertFalse(self.container.hasFlags(t, exact=True))
[docs]
def test_calcTotalParam(self):
minSerialNumberCount = 21.0
kids = self.container.getChildren()
tot = self.container.calcTotalParam("serialNum", kids)
self.assertGreaterEqual(tot, minSerialNumberCount)
tot = self.container.calcTotalParam("serialNum", kids, calcBasedOnFullObj=True)
self.assertGreaterEqual(tot, minSerialNumberCount)
tot = self.container.calcTotalParam("serialNum", kids, typeSpec=Flags.FUEL)
self.assertEqual(tot, 0.0)
with self.assertRaises(ValueError):
self.container.calcTotalParam(
"power", self.container.getChildren(), addSymmetricPositions=True, calcBasedOnFullObj=True
)
[docs]
def test_getBoundingCirlceOuterDiameter(self):
od = self.container.getBoundingCircleOuterDiameter()
self.assertAlmostEqual(od, len(list(self.container.iterComponents())))
[docs]
def test_getParamNames(self):
params = self.container.getParamNames()
self.assertEqual(len(params), 3)
self.assertIn("flags", params)
self.assertIn("serialNum", params)
self.assertIn("type", params)
[docs]
def test_updateVolume(self):
self.assertAlmostEqual(self.container.getVolume(), 0)
self.container._updateVolume()
self.assertAlmostEqual(self.container.getVolume(), 0)
[docs]
def test_expandLFPs(self):
# simple test, with no lumped fission product mappings
numDens = {"NA23": 1.0}
numDens = self.container._expandLFPs(numDens)
self.assertEqual(len(numDens), 1)
# set the lumped fission product mapping
fpd = getDummyLFPFile()
lfps = fpd.createLFPsFromFile()
self.container.setLumpedFissionProducts(lfps)
# get back the lumped fission product mapping, just to check
lfp = self.container.getLumpedFissionProductCollection()
self.assertEqual(len(lfp), 3)
self.assertIn("LFP35", lfp)
self.assertIn("LFP38", lfp)
self.assertIn("LFP39", lfp)
# quick test WITH some lumped fission products in the mix
numDens = {"NA23": 1.0, "LFP35": 2.0}
numDens = self.container._expandLFPs(numDens)
self.assertEqual(len(numDens), 9)
self.assertEqual(numDens["MO99"], 0)
[docs]
def test_setChildrenLumpedFissionProducts(self):
# build a lumped fission product collection
fpd = getDummyLFPFile()
lfps = fpd.createLFPsFromFile()
# validate that the LFP collection is None
self.container.setChildrenLumpedFissionProducts(None)
for c in self.container:
self.assertIsNone(c._lumpedFissionProducts)
# validate that the LFP collection is not None
self.container.setChildrenLumpedFissionProducts(lfps)
for c in self.container:
self.assertIsNotNone(c._lumpedFissionProducts)
[docs]
def test_requiresLumpedFissionProds(self):
# build a lumped fission product collection
fpd = getDummyLFPFile()
lfps = fpd.createLFPsFromFile()
self.container.setChildrenLumpedFissionProducts(lfps)
# test the null case
result = self.container.requiresLumpedFissionProducts(None)
self.assertFalse(result)
# test the usual case
result = self.container.requiresLumpedFissionProducts(set())
self.assertFalse(result)
# test a positive case
result = self.container.requiresLumpedFissionProducts(["LFP35"])
self.assertTrue(result)
[docs]
def test_getLumpedFissionProdsIfNullCase(self):
# build a lumped fission product collection
fpd = getDummyLFPFile()
lfps = fpd.createLFPsFromFile()
self.container.setChildrenLumpedFissionProducts(lfps)
# test the null case
result = self.container.getLumpedFissionProductsIfNecessary(None)
self.assertEqual(len(result), 0)
# test a positive case
result = self.container.getLumpedFissionProductsIfNecessary(["LFP35"])
self.assertGreater(len(result), 0)
[docs]
def test_getIntegratedMgFlux(self):
mgFlux = self.container.getIntegratedMgFlux()
self.assertEqual(mgFlux, [0.0])
[docs]
def test_getReactionRates(self):
# test the null case
rRates = self.container.getReactionRates("U235")
self.assertEqual(len(rRates), 6)
self.assertEqual(sum([r for r in rRates.values()]), 0)
# init reactor
_o, r = loadTestReactor(inputFileName="smallestTestReactor/armiRunSmallest.yaml")
lib = nuclearDataIO.isotxs.readBinary(ISOAA_PATH)
r.core.lib = lib
# test on a Component
b = r.core.getFirstAssembly().getFirstBlock()
b.p.mgFlux = 1
c = b.getComponents()[0]
rRatesComp = c.getReactionRates("U235")
self.assertEqual(len(rRatesComp), 6)
self.assertGreater(sum([r for r in rRatesComp.values()]), 0)
# test on a Block
rRatesBlock = b.getReactionRates("U235")
self.assertEqual(len(rRatesBlock), 6)
self.assertGreater(sum([r for r in rRatesBlock.values()]), 0)
# test on an Assembly
assem = r.core.getFirstAssembly()
rRatesAssem = assem.getReactionRates("U235")
self.assertEqual(len(rRatesAssem), 6)
self.assertGreater(sum([r for r in rRatesAssem.values()]), 0)
# test on a Core
rRatesCore = r.core.getReactionRates("U235")
self.assertEqual(len(rRatesCore), 6)
self.assertGreater(sum([r for r in rRatesCore.values()]), 0)
# test on a Reactor
rRatesReactor = r.getReactionRates("U235")
self.assertEqual(len(rRatesReactor), 6)
self.assertGreater(sum([r for r in rRatesReactor.values()]), 0)
# test that all different levels of the hierarchy have the same reaction rates
for key, val in rRatesBlock.items():
self.assertAlmostEqual(rRatesAssem[key], val)
self.assertAlmostEqual(rRatesCore[key], val)
self.assertAlmostEqual(rRatesReactor[key], val)
[docs]
def test_getFirstComponent(self):
c = self.container.getComponents()[0]
c0 = self.container.getFirstComponent()
self.assertIs(c, c0)
self.assertIsInstance(c0, composites.Composite)
c = self.cladChild.getComponents()[0]
c0 = self.cladChild.getFirstComponent()
self.assertIs(c, c0)
self.assertIsInstance(c0, composites.Composite)
c = self.secondGen.getComponents()[0]
c0 = self.secondGen.getFirstComponent()
self.assertIs(c, c0)
self.assertIsInstance(c0, composites.Composite)
b = loadTestBlock()
c = b.getComponents()[0]
c0 = b.getFirstComponent()
self.assertIs(c, c0)
self.assertIsInstance(c0, composites.Composite)
# covering edge case: someone passes in a flag that doesn't exist on on the object
with self.assertRaises(ValueError):
b.getFirstComponent(typeSpec=Flags.POISON)
[docs]
def test_syncParameters(self):
data = [{"serialNum": 123}, {"flags": "FAKE"}]
numSynced = self.container._syncParameters(data, {})
self.assertEqual(numSynced, 2)
[docs]
def test_iterChildrenWithFlags(self):
expectedChildren = {c for c in self.container if c.hasFlags(Flags.DUCT)}
found = set()
for c in self.container.iterChildrenWithFlags(Flags.DUCT):
self.assertIn(c, expectedChildren)
found.add(c)
self.assertSetEqual(found, expectedChildren)
[docs]
def test_iterChildrenOfType(self):
clads = self.container.iterChildrenOfType("clad")
first = next(clads)
self.assertIs(first, self.cladChild)
with self.assertRaises(StopIteration):
next(clads)
[docs]
def test_removeAll(self):
"""Test the ability to remove all children of a composite."""
self.container.removeAll()
self.assertEqual(len(self.container), 0)
# Nothing to iterate over
items = iter(self.container)
with self.assertRaises(StopIteration):
next(items)
for child in self.tree[1]:
self.assertIsNone(child.parent)
[docs]
def test_setChildren(self):
"""Test the ability to override children on a composite."""
newChildren = self.tree[2] + self.tree[3]
oldChildren = list(self.container)
self.container.setChildren(newChildren)
self.assertEqual(len(self.container), len(newChildren))
for old in oldChildren:
self.assertIsNone(old.parent)
for actualNew, expectedNew in zip(newChildren, self.container):
self.assertIs(actualNew, expectedNew)
[docs]
def test_add(self):
# get the size of the container at the start
lenContainer = len(self.container)
# add a dummy leaf to the container
leaf = DummyLeaf("duct 9", 99)
leaf.setType("duct")
self.container.add(leaf)
# verify the container's size has increased by one
self.assertEqual(len(self.container), lenContainer + 1)
[docs]
def test_extend(self):
# generate a list of elements to add to this container
elements = []
lenElements = 5
for i in range(lenElements):
leaf = DummyLeaf(f"duct {i}", i + 100)
leaf.setType("duct")
elements.append(leaf)
# extend the container by the above list
lenContainer = len(self.container)
self.container.extend(elements)
self.assertEqual(len(self.container), lenContainer + lenElements)
# show all the composites in the block have the block as the parent
for c in self.container:
self.assertIs(c.parent, self.container)
[docs]
class TestCompositeTree(unittest.TestCase):
blueprintYaml = """
name: test assembly
height: [1, 1] # 2 blocks
axial mesh points: [1, 1]
xs types: [A, A]
specifier: AA
blocks:
- &block_metal_fuel
name: metal fuel
fuel: &component_metal_fuel_fuel
shape: Circle
material: UZr
Tinput: 500
Thot: 500.0
id: 0.0
od: 1.0
mult: 7
clad: &component_metal_fuel_clad
shape: Circle
material: HT9
Tinput: 450.0
Thot: 450.0
id: 1.09
od: 1.1
mult: 7
bond: &component_metal_fuel_bond
shape: Circle
material: Sodium
Tinput: 450.0
Thot: 450.0
id: fuel.od
od: clad.id
mult: 7
coolant: &component_metal_fuel_coolant
shape: DerivedShape
material: Sodium
Tinput: 450.0
Thot: 450.0
duct: &component_metal_fuel_duct
shape: Hexagon
material: HT9
Tinput: 25.0
Thot: 450.0
ip: 16.0
mult: 1.0
op: 16.6
- &block_oxide_fuel
name: mox fuel
fuel:
<<: *component_metal_fuel_fuel
material: MOX
clad: *component_metal_fuel_clad
bond: *component_metal_fuel_bond
coolant: *component_metal_fuel_coolant
duct: *component_metal_fuel_duct
"""
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.block = None
self.r = None
def setUp(self):
self.block = loadTestBlock()
self.r = self.block.core.r
self.block.setHeight(100.0)
self.refDict = {
"U235": 0.00275173784234,
"U238": 0.0217358415457,
"W182": 1.09115150103e-05,
"W183": 5.89214392093e-06,
"W184": 1.26159558164e-05,
"W186": 1.17057432664e-05,
"V": 2e-2,
"NA23": 2e-2,
"ZR": 0.00709003962772,
}
self.block.setNumberDensities(self.refDict)
[docs]
def test_ordering(self):
a = assemblies.Assembly("dummy")
a.spatialGrid = grids.AxialGrid.fromNCells(2, armiObject=a)
otherBlock = deepcopy(self.block)
a.add(self.block)
a.add(otherBlock)
self.assertTrue(self.block < otherBlock)
locator = self.block.spatialLocator
self.block.spatialLocator = otherBlock.spatialLocator
otherBlock.spatialLocator = locator
self.assertTrue(otherBlock < self.block)
# test some edge cases
otherBlock.spatialLocator._grid = None
with self.assertRaises(ValueError):
otherBlock < self.block
otherBlock.spatialLocator = None
with self.assertRaises(ValueError):
otherBlock < self.block
[docs]
def test_getAncestorWithFlags(self):
# this test block is not part of an assembly, so it should not have a parent/ancestor
parent = self.block.getAncestorWithFlags(Flags.FUEL)
self.assertIsNone(parent)
# pick a component that is not part of a fuel composite, so it should not have a fuel ancestor
grandchild = self.block.getFirstComponent()
child = grandchild.getAncestorWithFlags(Flags.FUEL)
self.assertIsNone(child)
# test the usual case: get a ancestor with the fuel flag
child = self.block.getChildrenWithFlags(Flags.FUEL)[0]
grandchild = child.getFirstComponent()
child1 = grandchild.getAncestorWithFlags(Flags.FUEL)
self.assertEqual(child1, grandchild)
# default case: the only ancestor with the fuel flag is the composite itself, so return that
child2 = child.getAncestorWithFlags(Flags.FUEL)
self.assertEqual(child2, child)
[docs]
def test_changeNDensByFactor(self):
b = deepcopy(self.block.getChildrenWithFlags(Flags.FUEL)[0])
# test inital state
dens = b.getNumberDensities()
zrDens = dens["ZR"]
u235Dens = dens["U235"]
u238Dens = dens["U238"]
b.changeNDensByFactor(0.5)
# test new state
dens = b.getNumberDensities()
self.assertAlmostEqual(dens["ZR"], zrDens / 2, delta=1e-6)
self.assertAlmostEqual(dens["U235"], u235Dens / 2, delta=1e-6)
self.assertAlmostEqual(dens["U238"], u238Dens / 2, delta=1e-6)
[docs]
def test_summing(self):
a = assemblies.Assembly("dummy")
a.spatialGrid = grids.AxialGrid.fromNCells(2, armiObject=a)
otherBlock = deepcopy(self.block)
a.add(self.block)
a.add(otherBlock)
b = self.block + otherBlock
self.assertEqual(len(b), 26)
self.assertFalse(b[0].is3D)
self.assertIn("Circle", str(b[0]))
self.assertFalse(b[-1].is3D)
self.assertIn("Hexagon", str(b[-1]))
[docs]
def test_constituentReport(self):
runLog.info(self.r.core.constituentReport())
runLog.info(self.r.core.getFirstAssembly().constituentReport())
runLog.info(self.r.core.getFirstBlock().constituentReport())
runLog.info(self.r.core.getFirstBlock().getComponents()[0].constituentReport())
[docs]
def test_getNuclides(self):
"""
The getNuclides should return all keys that have ever been in this block, including values
that are at trace.
"""
cur = self.block.getNuclides()
ref = self.refDict.keys()
for key in ref:
self.assertIn(key, cur)
self.assertIn("FE", cur) # this is in at trace value.
[docs]
def test_getFuelMass(self):
"""
This test creates a dummy assembly and ensures that the assembly, block, and fuel component
masses are consistent. `getFuelMass` ensures that the fuel component is used to `getMass`.
"""
cs = settings.Settings()
assemDesign = assemblyBlueprint.AssemblyBlueprint.load(self.blueprintYaml)
a = assemDesign.construct(cs, MockBP)
fuelMass = 0.0
for b in a:
fuel = b.getComponent(Flags.FUEL)
fuelMass += fuel.getMass()
self.assertEqual(b.getFuelMass(), fuel.getMass())
self.assertEqual(fuelMass, a.getFuelMass())
[docs]
def test_getChildrenIncludeMaterials(self):
"""Test that the ``StateRetainer`` retains material properties when they are modified."""
cs = settings.Settings()
assemDesign = assemblyBlueprint.AssemblyBlueprint.load(self.blueprintYaml)
a = assemDesign.construct(cs, MockBP)
component = a[0][0]
referenceDensity = component.material.pseudoDensity(Tc=200)
self.assertEqual(component.material.pseudoDensity(Tc=200), referenceDensity)
[docs]
def test_getHMMass(self):
fuelDims = {"Tinput": 273.0, "Thot": 273.0, "od": 0.76, "id": 0.0, "mult": 1.0}
self.fuelComponent = components.Circle("fuel", "UZr", **fuelDims)
self.block.add(self.fuelComponent)
self.block.clearNumberDensities()
self.refDict = {
"U235": 0.00275173784234,
"U238": 0.0217358415457,
"W182": 1.09115150103e-05,
"W183": 5.89214392093e-06,
"W184": 1.26159558164e-05,
"W186": 1.17057432664e-05,
"V": 3e-2,
"NA23": 2e-2,
"ZR": 0.00709003962772,
}
self.block.setNumberDensities(self.refDict)
cur = self.block.getHMMass()
mass = 0.0
for nucName in self.refDict.keys():
if nucDir.isHeavyMetal(nucName):
mass += self.block.getMass(nucName)
places = 6
self.assertAlmostEqual(cur, mass, places=places)
[docs]
def test_getFPMass(self):
fuelDims = {"Tinput": 273.0, "Thot": 273.0, "od": 0.76, "id": 0.0, "mult": 1.0}
self.fuelComponent = components.Circle("fuel", "UZr", **fuelDims)
self.fuelComponent.material.setMassFrac("LFP38", 0.25)
self.block.add(self.fuelComponent)
refDict = {"LFP35": 0.1, "LFP38": 0.05, "LFP39": 0.7}
self.fuelComponent.setNumberDensities(refDict)
cur = self.block.getFPMass()
mass = 0.0
for nucName in refDict.keys():
mass += self.block.getMass(nucName)
ref = mass
places = 6
self.assertAlmostEqual(cur, ref, places=places)
[docs]
def test_setMassFrac(self):
# build test component
c = DummyComposite("test_setMassFrac")
c.getHeight = lambda: 1.0
fuelDims = {"Tinput": 273.0, "Thot": 273.0, "od": 0.76, "id": 0.0, "mult": 1.0}
fuelComponent = components.Circle("fuel", "UZr", **fuelDims)
c.add(fuelComponent)
# test initial state
self.assertEqual(c.getFPMass(), 0.0)
self.assertAlmostEqual(c.getHMMass(), 6.468105962375698, delta=1e-6)
self.assertAlmostEqual(c.getMass(), 7.186784402639664, delta=1e-6)
# use setMassFrac
c.setMassFrac("U235", 0.99)
c.setMassFrac("U238", 0.01)
# test new state
self.assertEqual(c.getFPMass(), 0.0)
self.assertAlmostEqual(c.getHMMass(), 7.178895593948443, delta=1e-6)
self.assertAlmostEqual(c.getMass(), 7.186784402639666, delta=1e-6)
# test edge case were zero density
c.setNumberDensities({})
with self.assertRaises(ValueError):
c.setMassFrac("U235", 0.98)
[docs]
def test_getFissileMass(self):
cur = self.block.getFissileMass()
mass = 0.0
for nucName in self.refDict.keys():
if nucName in nuclideBases.NuclideBase.fissile:
mass += self.block.getMass(nucName)
ref = mass
places = 6
self.assertAlmostEqual(cur, ref, places=places)
[docs]
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
lastSeen = c
lastIndex = ci
cMax, comp = self.block.getMaxParam("id", returnObj=True)
self.assertEqual(cMax, lastIndex)
self.assertIs(comp, lastSeen)
[docs]
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
lastSeen = c
lastIndex = ci
cMax, comp = self.block.getMinParam("id", returnObj=True)
self.assertEqual(cMax, lastIndex)
self.assertIs(comp, lastSeen)
[docs]
class TestFlagSerializer(unittest.TestCase):
[docs]
class TestFlagsA(utils.Flag):
A = utils.flags.auto()
B = utils.flags.auto()
C = utils.flags.auto()
D = utils.flags.auto()
[docs]
class TestFlagsB(utils.Flag):
A = utils.flags.auto()
B = utils.flags.auto()
BPRIME = utils.flags.auto()
C = utils.flags.auto()
D = utils.flags.auto()
[docs]
def test_flagSerialization(self):
data = [
Flags.FUEL,
Flags.FUEL | Flags.INNER,
Flags.A | Flags.B | Flags.CONTROL,
]
flagsArray, attrs = composites.FlagSerializer.pack(data)
data2 = composites.FlagSerializer.unpack(flagsArray, composites.FlagSerializer.version, attrs)
self.assertEqual(data, data2)
# discrepant versions
with self.assertRaises(ValueError):
data2 = composites.FlagSerializer.unpack(flagsArray, "0", attrs)
# missing flags in current version Flags
attrs["flag_order"].append("NONEXISTANTFLAG")
with mockRunLogs.BufferLog() as mock:
self.assertEqual("", mock.getStdout())
testName = "test_flagSerialization"
runLog.LOG.startLog(testName)
runLog.LOG.setVerbosity(logging.WARNING)
data2 = composites.FlagSerializer.unpack(flagsArray, composites.FlagSerializer.version, attrs)
flagLog = mock.getStdout()
self.assertIn("The set of flags", flagLog)
self.assertIn("NONEXISTANTFLAG", flagLog)
[docs]
def test_flagConversion(self):
data = [
self.TestFlagsA.A,
self.TestFlagsA.A | self.TestFlagsA.C,
self.TestFlagsA.A | self.TestFlagsA.C | self.TestFlagsA.D,
]
serialized, attrs = composites.FlagSerializer._packImpl(data, self.TestFlagsA)
data2 = composites.FlagSerializer._unpackImpl(
serialized, composites.FlagSerializer.version, attrs, self.TestFlagsB
)
expected = [
self.TestFlagsB.A,
self.TestFlagsB.A | self.TestFlagsB.C,
self.TestFlagsB.A | self.TestFlagsB.C | self.TestFlagsB.D,
]
self.assertEqual(data2, expected)
[docs]
class TestMiscMethods(unittest.TestCase):
"""
Test a variety of methods on the composite.
these may get moved to composted classes in the future.
"""
def setUp(self):
self.obj = loadTestBlock()
[docs]
def test_setMass(self):
"""Test setting and retrieving mass.
.. test:: Mass of a composite is retrievable.
:id: T_ARMI_CMP_GET_MASS
:tests: R_ARMI_CMP_GET_MASS
"""
masses = {"U235": 5.0, "U238": 3.0}
self.obj.setMasses(masses)
self.assertAlmostEqual(self.obj.getMass("U235"), 5.0)
self.assertAlmostEqual(self.obj.getMass("U238"), 3.0)
self.assertAlmostEqual(self.obj.getMass(), 8.0)
self.obj.addMasses(masses)
self.assertAlmostEqual(self.obj.getMass("U238"), 6.0)
# make sure it works with groups of groups
group = composites.Composite("group")
group.add(self.obj)
group.add(loadTestBlock())
group.setMass("U235", 5)
self.assertAlmostEqual(group.getMass("U235"), 5)
# ad a second block, and confirm it works
group.add(loadTestBlock())
self.assertGreater(group.getMass("U235"), 5)
self.assertAlmostEqual(group.getMass("U235"), 1364.28376185)
[docs]
def test_getNumberDensities(self):
"""Get number densities from composite.
.. test:: Number density of composite is retrievable.
:id: T_ARMI_CMP_GET_NDENS0
:tests: R_ARMI_CMP_GET_NDENS
"""
# verify the number densities from the composite
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)
# sum nuc densities from children components
totalVolume = self.obj.getVolume()
childDensities = {}
for o in self.obj:
m = o.getVolume()
d = o.getNumberDensities()
for nuc, val in d.items():
if nuc not in childDensities:
childDensities[nuc] = val * (m / totalVolume)
else:
childDensities[nuc] += val * (m / totalVolume)
# verify the children match this composite
for nuc in ["FE", "SI"]:
self.assertAlmostEqual(self.obj.getNumberDensity(nuc), childDensities[nuc], 4, msg=nuc)
[docs]
def test_getNumDensWithExpandedFissProds(self):
"""Get number densities from composite.
.. test:: Get number densities.
:id: T_ARMI_CMP_NUC
:tests: R_ARMI_CMP_NUC
"""
# verify the number densities from the composite
ndens = self.obj.getNumberDensities(expandFissionProducts=True)
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)
# set the lumped fission product mapping
fpd = getDummyLFPFile()
lfps = fpd.createLFPsFromFile()
self.obj.setLumpedFissionProducts(lfps)
# sum nuc densities from children components
totalVolume = self.obj.getVolume()
childDensities = {}
for o in self.obj:
# get the number densities with and without fission products
d0 = o.getNumberDensities(expandFissionProducts=False)
d = o.getNumberDensities(expandFissionProducts=True)
# prove that the expanded fission products have more isotopes
if len(d0) > 0:
self.assertGreater(len(d), len(d0))
# sum the child nuclide densites (weighted by mass fraction)
m = o.getVolume()
for nuc, val in d.items():
if nuc not in childDensities:
childDensities[nuc] = val * (m / totalVolume)
else:
childDensities[nuc] += val * (m / totalVolume)
# verify the children match this composite
for nuc in ["FE", "SI"]:
self.assertAlmostEqual(self.obj.getNumberDensity(nuc), childDensities[nuc], 4, msg=nuc)
[docs]
def test_dimensionReport(self):
report = self.obj.setComponentDimensionsReport()
self.assertEqual(len(report), len(self.obj))
[docs]
def test_getAtomicWeight(self):
weight = self.obj.getAtomicWeight()
self.assertTrue(50 < weight < 100)
[docs]
def test_copyParamsToChildren(self):
self.obj.p.percentBu = 5
self.obj.copyParamsToChildren(["percentBu"])
for child in self.obj:
self.assertEqual(child.p.percentBu, self.obj.p.percentBu)
[docs]
def test_copyParamsFrom(self):
obj2 = loadTestBlock()
obj2.p.percentBu = 15.2
self.obj.copyParamsFrom(obj2)
self.assertEqual(obj2.p.percentBu, self.obj.p.percentBu)
[docs]
class TestGetReactionRateDict(unittest.TestCase):
[docs]
def test_getReactionRateDict(self):
lib = nuclearDataIO.isotxs.readBinary(ISOAA_PATH)
rxRatesDict = getReactionRateDict(nucName="PU239", lib=lib, xsSuffix="AA", mgFlux=1, nDens=1)
self.assertEqual(rxRatesDict["nG"], sum(lib["PU39AA"].micros.nGamma))