# 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 functionalities of components within ARMI."""
import copy
import math
import random
import unittest
import numpy as np
from numpy.testing import assert_allclose, assert_equal
from armi.materials import air, alloy200
from armi.materials.material import Material
from armi.reactor import components, flags
from armi.reactor.blocks import Block
from armi.reactor.components import (
Circle,
Component,
ComponentType,
Cube,
DerivedShape,
DifferentialRadialSegment,
FilletedHexagon,
Helix,
Hexagon,
HexHoledCircle,
HoledHexagon,
HoledRectangle,
HoledSquare,
NullComponent,
RadialSegment,
Rectangle,
SolidRectangle,
Sphere,
Square,
Triangle,
UnshapedComponent,
UnshapedVolumetricComponent,
materials,
)
from armi.reactor.reactors import Reactor
from armi.testing import loadTestReactor
from armi.utils.units import getTc
[docs]
class MockCompositionDependentExpander(materials.Material):
"""Dummy material that has a composition-dependent thermal expansion coefficient."""
[docs]
def linearExpansionPercent(self, Tk: float = None, Tc: float = None) -> float:
"""
Composition-dependent linear expansion coefficient.
Parameters
----------
Tk : float, optional
Temperature in Kelvin.
Tc : float, optional
Temperature in Celsius.
"""
alpha = 1.0e-5
beta = 1.0e-5 * self.parent.getMassFrac("C")
refTemp = 20
return (alpha + beta) * getTc(Tc=Tc, Tk=Tk) * (Tc - refTemp)
[docs]
class TestComponentFactory(unittest.TestCase):
[docs]
def getCircleVoidDict(self):
return dict(
shape="circle",
name="gap",
Tinput=25,
Thot=600,
od=2.1,
id=0.0,
mult=7,
material="Void",
isotopics="",
)
[docs]
def getCircleFuelDict(self):
return dict(
shape="circle",
name="fuel",
Tinput=25,
Thot=600,
od=2.1,
id=0.0,
mult=7,
material="UZr",
isotopics="",
)
[docs]
def test_factory(self):
"""Creating and verifying void and fuel components.
.. test:: Example void and fuel components are initialized.
:id: T_ARMI_COMP_DEF0
:tests: R_ARMI_COMP_DEF
"""
voidAttrs = self.getCircleVoidDict()
voidComp = components.factory(voidAttrs.pop("shape"), [], voidAttrs)
fuelAttrs = self.getCircleFuelDict()
fuelComp = components.factory(fuelAttrs.pop("shape"), [], fuelAttrs)
self.assertIsInstance(voidComp, components.Circle)
self.assertIsInstance(voidComp.material, materials.Void)
self.assertIsInstance(fuelComp, components.Circle)
self.assertIsInstance(fuelComp.material, materials.UZr)
[docs]
def test_componentInitializationAndDuplication(self):
"""Initialize and duplicate a component, veifying the parameters.
.. test:: Verify the parameters of an initialized component.
:id: T_ARMI_COMP_DEF1
:tests: R_ARMI_COMP_DEF
"""
# populate the class/signature dict, and create a basis attrs
attrs = {
"name": "gap",
"Tinput": 25,
"Thot": 600,
"material": "Void",
"isotopics": "",
}
for i, (name, klass) in enumerate(ComponentType.TYPES.items()):
# hack together a dictionary input
thisAttrs = {k: 1.0 for k in set(klass.INIT_SIGNATURE).difference(attrs)}
if "oR" in thisAttrs:
thisAttrs["oR"] /= 20.0
if "iR" in thisAttrs:
thisAttrs["iR"] /= 20.0
del thisAttrs["components"]
thisAttrs.update(attrs)
thisAttrs["name"] = f"banana{i}"
if "modArea" in thisAttrs:
thisAttrs["modArea"] = None
component = components.factory(name, [], thisAttrs)
duped = copy.deepcopy(component)
for key, val in component.p.items():
if key in ["numberDensities", "nuclides"]:
for i in range(len(val)):
self.assertEqual(val[i], duped.p[key][i])
elif key not in ["area", "volume", "serialNum"]:
# they get recomputed
self.assertEqual(
val,
duped.p[key],
msg=f"Key: {key}, val1: {val}, val2: {duped.p[key]}",
)
[docs]
def test_factoryBadShapeName(self):
badDict = self.getCircleFuelDict()
with self.assertRaises(ValueError):
components.factory("turtle", [], badDict)
[docs]
class TestGeneralComponents(unittest.TestCase):
"""Base test for all individual component tests."""
componentCls = Component
componentMaterial = "HT9"
componentDims = {"Tinput": 25.0, "Thot": 25.0}
def setUp(self, component=None):
"""
Most of the time nothing will be passed as `component` and the result will be stored in
self, but you can also pass a component object as `component`, in which case the object will
be returned with the `parent` attribute assigned.
"""
class _Parent:
def getSymmetryFactor(self):
return 1.0
def getHeight(self):
return 1.0
def clearCache(self):
pass
def __iter__(self):
"""Act like an iterator but don't actually iterate."""
return iter(())
derivedMustUpdate = False
if component is None:
self.component = self.componentCls("TestComponent", self.componentMaterial, **self.componentDims)
self.component.parent = _Parent()
else:
component.parent = _Parent()
return component
[docs]
class TestComponentNDens(TestGeneralComponents):
"""Test component number density setting."""
componentCls = Circle
componentDims = {"Tinput": 25.0, "Thot": 25.0, "id": 0.0, "od": 0.5}
[docs]
def test_setNumberDensity(self):
"""Test setting a single number density.
.. test:: Users can set Component number density.
:id: T_ARMI_COMP_NUCLIDE_FRACS0
:tests: R_ARMI_COMP_NUCLIDE_FRACS
"""
component = self.component
self.assertAlmostEqual(component.getNumberDensity("C"), 0.000780, 6)
component.setNumberDensity("C", 0.57)
self.assertEqual(component.getNumberDensity("C"), 0.57)
[docs]
def test_setNumberDensities(self):
"""Test setting multiple number densities.
.. test:: Users can set Component number densities.
:id: T_ARMI_COMP_NUCLIDE_FRACS1
:tests: R_ARMI_COMP_NUCLIDE_FRACS
"""
component = self.component
self.assertAlmostEqual(component.getNumberDensity("MN"), 0.000426, 6)
component.setNumberDensities({"C": 1, "MN": 0.58})
self.assertEqual(component.getNumberDensity("C"), 1.0)
self.assertEqual(component.getNumberDensity("MN"), 0.58)
[docs]
def test_setNumberDensitiesWithExpansion(self):
expansionMaterial = MockCompositionDependentExpander()
expansionMaterial.parent = self.component
self.component.material = expansionMaterial
component = self.component
initialVolume = component.getVolume()
component.temperatureInC = 50
self.assertAlmostEqual(component.getNumberDensity("MN"), 0.000426, 6)
component.setNumberDensities({"C": 1, "MN": 0.58})
newVolume = component.getVolume()
expansionFactor = initialVolume / newVolume
self.assertEqual(component.getNumberDensity("C"), 1.0 * expansionFactor)
self.assertEqual(component.getNumberDensity("MN"), 0.58 * expansionFactor)
[docs]
def test_changeNDensByFactor(self):
"""Test the ability to change just the component number densities."""
referenceDensity = self.component.getNumberDensities()
self.component.p.detailedNDens = None
self.component.p.pinNDens = None
scalingFactor = random.uniform(0, 10)
self.component.changeNDensByFactor(scalingFactor)
for nuc, refDens in referenceDensity.items():
actual = self.component.getNumberDensity(nuc)
self.assertEqual(actual, refDens * scalingFactor, msg=nuc)
self.assertIsNone(self.component.p.detailedNDens)
self.assertIsNone(self.component.p.pinNDens)
[docs]
class TestComponent(TestGeneralComponents):
"""Test the base component."""
componentCls = Component
[docs]
def test_initializeComponentMaterial(self):
"""Creating component with single material.
.. test:: Components are made of one material.
:id: T_ARMI_COMP_1MAT0
:tests: R_ARMI_COMP_1MAT
"""
expectedName = "TestComponent"
actualName = self.component.getName()
expectedMaterialName = "HT9"
actualMaterialName = self.component.material.getName()
self.assertEqual(expectedName, actualName)
self.assertEqual(expectedMaterialName, actualMaterialName)
[docs]
def test_solid_material(self):
"""Determine if material is solid.
.. test:: Components have material properties.
:id: T_ARMI_COMP_MAT
:tests: R_ARMI_COMP_MAT
"""
self.assertTrue(isinstance(self.component.getProperties(), Material))
self.assertTrue(hasattr(self.component.material, "density"))
self.assertIn("HT9", str(self.component.getProperties()))
self.component.material = air.Air()
self.assertFalse(self.component.containsSolidMaterial())
self.component.material = alloy200.Alloy200()
self.assertTrue(self.component.containsSolidMaterial())
self.assertTrue(isinstance(self.component.getProperties(), Material))
self.assertTrue(hasattr(self.component.material, "density"))
self.assertIn("Alloy200", str(self.component.getProperties()))
[docs]
class TestNullComponent(TestGeneralComponents):
componentCls = NullComponent
[docs]
def test_cmp(self):
"""Test null component."""
cur = self.component
ref = DerivedShape("DerivedShape", "Material", 0, 0)
self.assertLess(cur, ref)
[docs]
def test_nonzero(self):
cur = bool(self.component)
ref = False
self.assertEqual(cur, ref)
[docs]
def test_getDimension(self):
"""Test getting empty component.
.. test:: Retrieve a null dimension.
:id: T_ARMI_COMP_DIMS0
:tests: R_ARMI_COMP_DIMS
"""
for temp in range(400, 901, 25):
self.assertEqual(self.component.getDimension("", Tc=temp), 0.0)
[docs]
class TestUnshapedComponent(TestGeneralComponents):
componentCls = UnshapedComponent
componentMaterial = "HT9"
componentDims = {"Tinput": 25.0, "Thot": 430.0, "area": math.pi}
[docs]
def test_getComponentArea(self):
# a case without thermal expansion
self.assertEqual(self.component.getComponentArea(cold=True), math.pi)
# a case with thermal expansion
self.assertEqual(
self.component.getComponentArea(cold=False),
math.pi * self.component.getThermalExpansionFactor(self.component.temperatureInC) ** 2,
)
# Passing temperature directly
self.assertEqual(
self.component.getComponentArea(cold=False),
self.component.getComponentArea(Tc=self.component.temperatureInC),
)
# show that area expansion is consistent with the density change in the material
hotDensity = self.component.density()
hotArea = self.component.getArea()
thermalExpansionFactor = self.component.getThermalExpansionFactor(self.component.temperatureInC)
coldComponent = self.setUp(
UnshapedComponent(
name="coldComponent",
material=self.componentMaterial,
Tinput=self.component.inputTemperatureInC,
Thot=self.component.inputTemperatureInC,
area=math.pi,
)
)
coldDensity = coldComponent.density()
coldArea = coldComponent.getArea()
self.assertGreater(thermalExpansionFactor, 1)
# thermalExpansionFactor accounts for density being 3D while area is 2D
self.assertAlmostEqual(
(coldDensity * coldArea),
(thermalExpansionFactor * hotDensity * hotArea),
)
[docs]
def test_getBoundingCircleOuterDiameter(self):
# a case without thermal expansion
self.assertEqual(self.component.getBoundingCircleOuterDiameter(cold=True), 2.0)
# a case with thermal expansion
self.assertEqual(
self.component.getBoundingCircleOuterDiameter(cold=False),
2.0 * self.component.getThermalExpansionFactor(self.component.temperatureInC),
)
[docs]
def test_component_less_than(self):
"""Ensure that comparisons between components properly reference bounding circle outer diameter.
.. test:: Order components by their outermost diameter
:id: T_ARMI_COMP_ORDER
:tests: R_ARMI_COMP_ORDER
"""
componentCls = UnshapedComponent
componentMaterial = "HT9"
smallDims = {"Tinput": 25.0, "Thot": 430.0, "area": 0.5 * math.pi}
sameDims = {"Tinput": 25.0, "Thot": 430.0, "area": 1.0 * math.pi}
bigDims = {"Tinput": 25.0, "Thot": 430.0, "area": 2.0 * math.pi}
smallComponent = componentCls("TestComponent", componentMaterial, **smallDims)
sameComponent = componentCls("TestComponent", componentMaterial, **sameDims)
bigComponent = componentCls("TestComponent", componentMaterial, **bigDims)
self.assertTrue(smallComponent < self.component)
self.assertFalse(bigComponent < self.component)
self.assertFalse(sameComponent < self.component)
[docs]
def test_fromComponent(self):
circle = components.Circle("testCircle", "HT9", 25, 500, 1.0)
unshaped = components.UnshapedComponent.fromComponent(circle)
self.assertEqual(circle.getComponentArea(), unshaped.getComponentArea())
[docs]
class TestShapedComponent(TestGeneralComponents):
"""Abstract class for all shaped components."""
[docs]
def test_preserveMassDuringThermalExpansion(self):
"""Test that when we thermally expand any arbitrary shape, mass is conserved."""
if not self.component.THERMAL_EXPANSION_DIMS:
return
temperatures = [25.0, 30.0, 40.0, 60.0, 80.0, 430.0]
masses = []
report = "Temperature, mass, volume, dLL\n"
for ht in temperatures:
self.component.setTemperature(ht)
mass = self.component.getMass()
masses.append(mass)
report += "{:10.1f}, {:7.5e}, {:7.5e}, {:7.5e}\n".format(
ht,
mass,
self.component.getVolume(),
self.component.getThermalExpansionFactor(),
)
for mass in masses:
self.assertNotAlmostEqual(mass, 0.0)
self.assertAlmostEqual(
masses[0],
mass,
msg="Masses are not preserved during thermal expansion of component {} at {} C. "
"Original Mass: {}, Thermally Expanded Mass: {}\n{}"
"".format(self.component, ht, masses[0], mass, report),
)
[docs]
def test_volumeAfterClearCache(self):
"""
Test volume after cache has been cleared.
.. test:: Clear cache after a dimensions updated.
:id: T_ARMI_COMP_VOL0
:tests: R_ARMI_COMP_VOL
"""
c = UnshapedVolumetricComponent("testComponent", "Custom", 0, 0, volume=1)
self.assertAlmostEqual(c.getVolume(), 1, 6)
c.clearCache()
self.assertAlmostEqual(c.getVolume(), 1, 6)
[docs]
def test_densityConsistent(self):
"""Testing the Component matches quick hand calc."""
c = self.component
# no volume defined
if isinstance(c, (DerivedShape, UnshapedVolumetricComponent)):
return
elif isinstance(c, Component):
return
# basic density sanity test
self.assertAlmostEqual(c.density(), c.getMass() / c.getVolume())
# test 2D expanding density
if c.temperatureInC == c.inputTemperatureInC:
self.assertAlmostEqual(c.density(), c.material.pseudoDensity(Tc=c.temperatureInC), delta=0.001)
if not c.is3D:
self.assertAlmostEqual(
c.getArea() * c.parent.getHeight() * c.density(),
self.component.getMass(),
)
[docs]
def test_density(self):
"""Testing the Component density gets the correct 3D material density."""
class StrangeMaterial(Material):
"""material designed to make the test easier to understand."""
def pseduoDensity(self, Tk=None, Tc=None):
return 1.0
def density(self, Tk=None, Tc=None):
return 3.0
c = Sphere(
name="strangeBall",
material=StrangeMaterial(),
Tinput=200,
Thot=500,
od=1,
id=0,
mult=1,
)
# we expect to see the 3D material density here
self.assertEqual(c.density(), 3.0)
[docs]
class TestDerivedShape(TestShapedComponent):
componentCls = DerivedShape
componentMaterial = "Sodium"
componentDims = {"Tinput": 25.0, "Thot": 400.0, "area": 1.0}
[docs]
def test_getBoundingCircleOuterDiameter(self):
self.assertGreater(self.component.getBoundingCircleOuterDiameter(cold=True), 0.0)
[docs]
def test_computeVolume(self):
"""Test the computeVolume method on a number of components in a block.
.. test:: Compute the volume of a DerivedShape inside solid shapes.
:id: T_ARMI_COMP_FLUID
:tests: R_ARMI_COMP_FLUID
"""
from armi.reactor.tests.test_blocks import buildSimpleFuelBlock
# Calculate the total volume of the block
b = buildSimpleFuelBlock()
totalVolume = b.getVolume()
# calculate the total volume by adding up all the components
c = b.getComponent(flags.Flags.COOLANT)
totalByParts = 0
for co in b.getComponents():
totalByParts += co.computeVolume()
self.assertAlmostEqual(totalByParts, totalVolume)
# test the computeVolume method on the one DerivedShape in this block
self.assertAlmostEqual(c.computeVolume(), 1386.5232044586771)
[docs]
class TestDerivedShapeGetArea(unittest.TestCase):
[docs]
def test_getAreaColdTrue(self):
"""Prove that the DerivedShape.getArea() works at cold=True."""
# load one-block test reactor
_o, r = loadTestReactor(inputFileName="smallestTestReactor/armiRunSmallest.yaml")
b = r.core[0][0]
# ensure there is a DerivedShape in this Block
shapes = set([type(c) for c in b])
self.assertIn(Circle, shapes)
self.assertIn(DerivedShape, shapes)
self.assertIn(Helix, shapes)
self.assertIn(Hexagon, shapes)
# prove that getArea works on the block level
self.assertAlmostEqual(b.getArea(cold=True), b.getArea(cold=False), delta=1e-10)
# prove that getArea preserves the sum of all the areas, even if there is a DerivedShape
totalAreaCold = sum([c.getArea(cold=True) for c in b])
totalAreaHot = sum([c.getArea(cold=False) for c in b])
self.assertAlmostEqual(totalAreaCold, totalAreaHot, delta=1e-10)
[docs]
def test_getAreaTemp(self):
"""Prove that the DerivedShape.getArea() works for an arbitrary temperature."""
# load one-block test reactor
_o, r = loadTestReactor(inputFileName="smallestTestReactor/armiRunSmallest.yaml")
b = r.core[0][0]
b.clearCache()
# ensure there is a DerivedShape in this Block
shapes = set([type(c) for c in b])
self.assertIn(Circle, shapes)
self.assertIn(DerivedShape, shapes)
self.assertIn(Helix, shapes)
self.assertIn(Hexagon, shapes)
blockArea = b.getMaxArea()
compArea = sum([c.getArea(Tc=300) for c in b if not isinstance(c, DerivedShape)])
comp = [c for c in b if isinstance(c, DerivedShape)][0]
self.assertAlmostEqual(blockArea - compArea, comp.getComponentArea(Tc=300))
[docs]
class TestComponentSort(unittest.TestCase):
def setUp(self):
self.components = []
pinComp = components.Circle("pin", "UZr", Tinput=273.0, Thot=273.0, od=0.08, mult=169.0)
gapComp = components.Circle("gap", "Sodium", Tinput=273.0, Thot=273.0, id=0.08, od=0.08, mult=169.0)
ductComp = components.Hexagon("duct", "HT9", Tinput=273.0, Thot=273.0, op=2.6, ip=2.0, mult=1.0)
cladComp = components.Circle("clad", "HT9", Tinput=273.0, Thot=273.0, id=0.08, od=0.1, mult=169.0)
wireComp = components.Helix(
"wire",
"HT9",
Tinput=273.0,
Thot=273.0,
axialPitch=10.0,
helixDiameter=0.11,
od=0.01,
mult=169.0,
)
self.components = [
wireComp,
cladComp,
ductComp,
pinComp,
gapComp,
]
[docs]
def test_sorting(self):
"""Test that components are sorted as expected."""
sortedComps = sorted(self.components)
currentMaxOd = 0.0
for c in sortedComps:
self.assertGreaterEqual(c.getBoundingCircleOuterDiameter(cold=True), currentMaxOd)
currentMaxOd = c.getBoundingCircleOuterDiameter(cold=True)
self.assertEqual(sortedComps[1].name, "gap")
self.assertEqual(sortedComps[2].name, "clad")
[docs]
class TestCircle(TestShapedComponent):
"""Test circle shaped component."""
componentCls = Circle
_id = 5.0
_od = 10
_coldTemp = 25.0
componentDims = {
"Tinput": _coldTemp,
"Thot": 25.0,
"od": _od,
"id": _id,
"mult": 1.5,
}
[docs]
def test_copy(self):
circle2 = copy.copy(self.component)
self.assertIsNot(circle2, self.component)
self.assertAlmostEqual(circle2.getDimension("id"), self.component.getDimension("id"))
self.assertAlmostEqual(circle2.getDimension("od"), self.component.getDimension("od"))
self.assertAlmostEqual(circle2.getDimension("mult"), self.component.getDimension("mult"))
[docs]
def test_circleExpansionWorks(self):
"""Test that when ARMI thermally expands a circle, mass is conserved.
.. test:: Calculate thermal expansion.
:id: T_ARMI_COMP_EXPANSION0
:tests: R_ARMI_COMP_EXPANSION
"""
hotTemp = 700.0
dLL = self.component.material.linearExpansionFactor(Tc=hotTemp, T0=self._coldTemp)
ref = 1.0 + dLL
cur = self.component.getThermalExpansionFactor(Tc=hotTemp)
self.assertAlmostEqual(cur, ref)
[docs]
def test_getDimension(self):
"""Test getting component dimension at specific temperature.
.. test:: Retrieve a dimension at a temperature.
:id: T_ARMI_COMP_DIMS1
:tests: R_ARMI_COMP_DIMS
.. test:: Calculate thermal expansion.
:id: T_ARMI_COMP_EXPANSION1
:tests: R_ARMI_COMP_EXPANSION
"""
for hotTemp in range(200, 400, 25):
ref = self._od * self.component.getThermalExpansionFactor(Tc=hotTemp)
cur = self.component.getDimension("od", Tc=hotTemp)
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a circle."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_getBoundingCircleOuterDiam(self):
ref = self._od
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getCircleInnerDiameter(self):
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertAlmostEqual(self._id, cur)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["od", "id", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
def test_getArea(self):
"""Calculate area of circle.
.. test:: Calculate area of circle.
:id: T_ARMI_COMP_VOL1
:tests: R_ARMI_COMP_VOL
"""
# show we can calculate the area once
od = self.component.getDimension("od")
idd = self.component.getDimension("id")
mult = self.component.getDimension("mult")
ref = math.pi * ((od / 2) ** 2 - (idd / 2) ** 2) * mult
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
# show we can clear the cache, change the temp, and correctly re-calc the area
for newTemp in range(500, 690, 19):
self.component.clearCache()
# re-calc area
self.component.temperatureInC = newTemp
od = self.component.getDimension("od", Tc=newTemp)
idd = self.component.getDimension("id", Tc=newTemp)
ref = math.pi * ((od / 2) ** 2 - (idd / 2) ** 2) * mult
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_compInteractionsLinkingByDims(self):
"""Tests linking of Components by dimensions.
The component ``gap``, representing the fuel-clad gap filled with Void, is defined with
dimensions that depend on the fuel outer diameter and clad inner diameter. The
:py:meth:`~armi.reactor.components.component.Component.resolveLinkedDims` method links the
gap dimensions appropriately when the Component is constructed, and the test shows the area
of the gap is calculated correctly based on the thermally-expanded dimensions of the fuel
and clad Components.
.. test:: Show the dimensions of a liquid Component can be defined to depend on the solid
Components that bound it.
:id: T_ARMI_COMP_FLUID1
:tests: R_ARMI_COMP_FLUID
"""
nPins = 217
fuelDims = {"Tinput": 25.0, "Thot": 430.0, "od": 0.9, "id": 0.0, "mult": nPins}
cladDims = {"Tinput": 25.0, "Thot": 430.0, "od": 1.1, "id": 1.0, "mult": nPins}
fuel = Circle("fuel", "UZr", **fuelDims)
clad = Circle("clad", "HT9", **cladDims)
gapDims = {
"Tinput": 25.0,
"Thot": 430.0,
"od": "clad.id",
"id": "fuel.od",
"mult": nPins,
}
gapDims["components"] = {"clad": clad, "fuel": fuel}
gap = Circle("gap", "Void", **gapDims)
mult = gap.getDimension("mult")
od = gap.getDimension("od")
idd = gap.getDimension("id")
ref = mult * math.pi * ((od / 2.0) ** 2 - (idd / 2.0) ** 2)
cur = gap.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_badComponentName(self):
"""This shows that resolveLinkedDims cannot support names with periods in them."""
nPins = 12
fuelDims = {"Tinput": 25.0, "Thot": 430.0, "od": 0.9, "id": 0.0, "mult": nPins}
cladDims = {"Tinput": 25.0, "Thot": 430.0, "od": 1.1, "id": 1.0, "mult": nPins}
fuel = Circle("fuel", "UZr", **fuelDims)
clad = Circle("clad_4.2.3", "HT9", **cladDims)
gapDims = {
"Tinput": 25.0,
"Thot": 430.0,
"od": "clad_4.2.3.id",
"id": "fuel.od",
"mult": nPins,
}
gapDims["components"] = {"clad_4.2.3": clad, "fuel": fuel}
with self.assertRaises(ValueError):
_gap = Circle("gap", "Void", **gapDims)
[docs]
def test_compInteractionsLinkingBySubt(self):
"""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)
fuelDims = {
"Tinput": 25.0,
"Thot": 430.0,
"od": 0.9,
"id": 0.0,
"mult": nPins,
"modArea": "gap.sub",
}
fuel = Circle("fuel", "UZr", components={"gap": gap}, **fuelDims)
gapArea = (
gap.getDimension("mult")
* math.pi
* ((gap.getDimension("od") / 2.0) ** 2 - (gap.getDimension("id") / 2.0) ** 2)
)
fuelArea = (
fuel.getDimension("mult")
* math.pi
* ((fuel.getDimension("od") / 2.0) ** 2 - (fuel.getDimension("id") / 2.0) ** 2)
)
ref = fuelArea - gapArea
cur = fuel.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_getNumberDensities(self):
"""Test that demonstrates that number densities can be retrieved on from component."""
self.component.p.numberDensities = np.ones(1, dtype=np.float64)
self.component.p.nuclides = np.array(["NA23"], dtype="S6")
self.assertEqual(self.component.getNumberDensity("NA23"), 1.0)
[docs]
def test_changeNumberDensities(self):
"""Test that demonstrates that the number densities on a component can be modified."""
self.component.p.numberDensities = np.ones(1, dtype=np.float64)
self.component.p.nuclides = np.array(["NA23"], dtype="S6")
self.component.p.detailedNDens = [1.0]
self.component.p.pinNDens = [1.0]
self.assertEqual(self.component.getNumberDensity("NA23"), 1.0)
self.component.changeNDensByFactor(3.0)
self.assertEqual(self.component.getNumberDensity("NA23"), 3.0)
self.assertEqual(self.component.p.detailedNDens[0], 3.0)
self.assertEqual(self.component.p.pinNDens[0], 3.0)
[docs]
def test_fuelMass(self):
nominalMass = self.component.getMass()
self.component.p.flags = flags.Flags.FUEL
self.assertEqual(self.component.getFuelMass(), nominalMass)
self.component.p.flags = flags.Flags.MODERATOR
self.assertEqual(self.component.getFuelMass(), 0.0)
[docs]
def test_theoreticalDensitySetter(self):
"""Ensure only fraction theoretical densities are supported."""
self.assertEqual(self.component.p.theoreticalDensityFrac, 1)
with self.assertRaises(ValueError):
self.component.p.theoreticalDensityFrac = 2.0
self.assertEqual(self.component.p.theoreticalDensityFrac, 1)
self.component.p.theoreticalDensityFrac = 0.2
self.assertEqual(self.component.p.theoreticalDensityFrac, 0.2)
with self.assertRaises(ValueError):
self.component.p.theoreticalDensityFrac = -1.0
self.assertEqual(self.component.p.theoreticalDensityFrac, 0.2)
self.component.p.theoreticalDensityFrac = 1.0
self.assertEqual(self.component.p.theoreticalDensityFrac, 1)
self.component.p.theoreticalDensityFrac = 0.0
self.assertEqual(self.component.p.theoreticalDensityFrac, 0)
[docs]
class TestComponentExpansion(unittest.TestCase):
tCold = 25
tWarm = 50
tHot = 500
coldOuterDiameter = 1.0
[docs]
def test_HT9Expansion(self):
self.runExpansionTests(mat="HT9", isotope="FE")
[docs]
def test_UZrExpansion(self):
self.runExpansionTests(mat="UZr", isotope="U235")
[docs]
def test_B4CExpansion(self):
self.runExpansionTests(mat="B4C", isotope="B10")
[docs]
def runExpansionTests(self, mat: str, isotope: str):
self.componentMassIndependentOfInputTemp(mat)
self.expansionConservationHotHeightDefined(mat, isotope)
self.expansionConservationColdHeightDefined(mat)
[docs]
def expansionConservationHotHeightDefined(self, mat: str, isotope: str):
"""
Demonstrate tutorial for how to expand and relationships conserved at during expansion.
Notes
-----
- height taken as hot height and show how quantity is conserved with
inputHeightsConsideredHot = True (the default)
"""
hotHeight = 1.0
circle1 = Circle("circle", mat, self.tCold, self.tWarm, self.coldOuterDiameter)
circle2 = Circle("circle", mat, self.tCold, self.tHot, self.coldOuterDiameter)
# mass density is proportional to Fe number density and derived from
# all the number densities and atomic masses
self.assertAlmostEqual(
circle1.getNumberDensity(isotope) / circle2.getNumberDensity(isotope),
circle1.density() / circle2.density(),
)
# the colder one has more because it is the same cold outer diameter but it would be taller
# at the same temperature
mass1 = circle1.density() * circle1.getArea() * hotHeight
mass2 = circle2.density() * circle2.getArea() * hotHeight
self.assertGreater(mass1, mass2)
# they are off by factor of thermal exp
self.assertAlmostEqual(
mass1 * circle1.getThermalExpansionFactor(),
mass2 * circle2.getThermalExpansionFactor(),
)
# material.pseudoDensity is the 2D density of a material
# material.density is true density and not equal in this case
for circle in [circle1, circle2]:
# 2D density is not equal after application of coldMatAxialExpansionFactor
# which happens during construction
self.assertNotAlmostEqual(
circle.density(),
circle.material.pseudoDensity(Tc=circle.temperatureInC),
)
# 2D density is off by the material thermal exp factor
percent = circle.material.linearExpansionPercent(Tc=circle.temperatureInC)
thermalExpansionFactorFromColdMatTemp = 1 + percent / 100
self.assertAlmostEqual(
circle.density() * thermalExpansionFactorFromColdMatTemp,
circle.material.pseudoDensity(Tc=circle.temperatureInC),
)
self.assertAlmostEqual(
circle.density(),
circle.material.density(Tc=circle.temperatureInC),
)
# brief 2D expansion with set temp to show mass is conserved hot height would come from
# block value
warmMass = circle1.density() * circle1.getArea() * hotHeight
circle1.setTemperature(self.tHot)
hotMass = circle1.density() * circle1.getArea() * hotHeight
self.assertAlmostEqual(warmMass, hotMass)
circle1.setTemperature(self.tWarm)
# Change temp to circle 2 temp to show equal to circle2 and then change back to show
# recoverable to original values
oldArea = circle1.getArea()
initialDens = circle1.density()
# when block.setHeight is called (which effectively changes component height)
# component.setNumberDensity is called (for solid isotopes) to adjust the number density so
# that now the 2D expansion will be approximated/expanded around the hot temp which is akin
# to these adjustments
heightFactor = circle1.getHeightFactor(self.tHot)
circle1.adjustDensityForHeightExpansion(self.tHot) # apply temp at new height
circle1.setTemperature(self.tHot)
# now its density is same as hot component
self.assertAlmostEqual(circle1.density(), circle2.density())
# show that mass is conserved after expansion
circle1NewHotHeight = hotHeight * heightFactor
self.assertAlmostEqual(mass1, circle1.density() * circle1.getArea() * circle1NewHotHeight)
self.assertAlmostEqual(
circle1.density(),
circle1.material.density(Tc=circle1.temperatureInC),
)
# change back to old temp
circle1.adjustDensityForHeightExpansion(self.tWarm)
circle1.setTemperature(self.tWarm)
# check for consistency
self.assertAlmostEqual(initialDens, circle1.density())
self.assertAlmostEqual(oldArea, circle1.getArea())
self.assertAlmostEqual(mass1, circle1.density() * circle1.getArea() * hotHeight)
[docs]
def expansionConservationColdHeightDefined(self, mat: str):
"""
Demonstrate that material is conserved at during expansion.
Notes
-----
- height taken as cold height and show how quantity is conserved with
inputHeightsConsideredHot = False
"""
coldHeight = 1.0
circle1 = Circle("circle", mat, self.tCold, self.tWarm, self.coldOuterDiameter)
circle2 = Circle("circle", mat, self.tCold, self.tHot, self.coldOuterDiameter)
# same as 1 but we will make like 2
circle1AdjustTo2 = Circle("circle", mat, self.tCold, self.tWarm, self.coldOuterDiameter)
# make it hot like 2
circle1AdjustTo2.adjustDensityForHeightExpansion(self.tHot)
circle1AdjustTo2.setTemperature(self.tHot)
# check that its like 2
self.assertAlmostEqual(circle2.density(), circle1AdjustTo2.density())
self.assertAlmostEqual(circle2.getArea(), circle1AdjustTo2.getArea())
for circle in [circle1, circle2, circle1AdjustTo2]:
self.assertAlmostEqual(
circle.density(),
circle.material.density(Tc=circle.temperatureInC),
)
# total mass consistent between hot and cold. Hot height will be taller
hotHeight = coldHeight * circle.getThermalExpansionFactor()
self.assertAlmostEqual(
coldHeight * circle.getArea(cold=True) * circle.material.density(Tc=circle.inputTemperatureInC),
hotHeight * circle.getArea() * circle.density(),
)
[docs]
class TestTriangle(TestShapedComponent):
"""Test triangle shaped component."""
componentCls = Triangle
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"base": 3.0,
"height": 2.0,
"mult": 30,
}
[docs]
def test_getArea(self):
"""Calculate area of triangle.
.. test:: Calculate area of triangle.
:id: T_ARMI_COMP_VOL2
:tests: R_ARMI_COMP_VOL
.. test:: Triangle shaped component
:id: T_ARMI_COMP_SHAPES1
:tests: R_ARMI_COMP_SHAPES
"""
b = self.component.getDimension("base")
h = self.component.getDimension("height")
mult = self.component.getDimension("mult")
ref = mult * 0.5 * b * h
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a triangle."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["base", "height", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestRectangle(TestShapedComponent):
"""Test rectangle shaped component."""
componentCls = Rectangle
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"lengthOuter": 6.0,
"lengthInner": 4.0,
"widthOuter": 5.0,
"widthInner": 3.0,
"mult": 2,
}
[docs]
def test_negativeArea(self):
dims = {
"Tinput": 25.0,
"Thot": 430.0,
"lengthOuter": 1.0,
"lengthInner": 2.0,
"widthOuter": 5.0,
"widthInner": 6.0,
"mult": 2,
}
refArea = dims["mult"] * (dims["lengthOuter"] * dims["widthOuter"] - dims["lengthInner"] * dims["widthInner"])
negativeRectangle = Rectangle("test", "Void", **dims)
self.assertAlmostEqual(negativeRectangle.getArea(), refArea)
with self.assertRaises(ArithmeticError):
negativeRectangle = Rectangle("test", "UZr", **dims)
negativeRectangle.getArea()
[docs]
def test_getBoundingCircleOuterDiam(self):
"""Get outer diameter bounding circle.
.. test:: Rectangle shaped component
:id: T_ARMI_COMP_SHAPES2
:tests: R_ARMI_COMP_SHAPES
"""
ref = math.sqrt(61.0)
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
# verify the area of the rectangle is correct
ref = self.componentDims["lengthOuter"] * self.componentDims["widthOuter"]
ref -= self.componentDims["lengthInner"] * self.componentDims["widthInner"]
ref *= self.componentDims["mult"]
cur = self.component.getArea(cold=True)
self.assertAlmostEqual(cur, ref)
[docs]
def test_getCircleInnerDiam(self):
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertAlmostEqual(math.sqrt(25.0), cur)
[docs]
def test_getArea(self):
"""Calculate area of rectangle.
.. test:: Calculate area of rectangle.
:id: T_ARMI_COMP_VOL3
:tests: R_ARMI_COMP_VOL
"""
outerL = self.component.getDimension("lengthOuter")
innerL = self.component.getDimension("lengthInner")
outerW = self.component.getDimension("widthOuter")
innerW = self.component.getDimension("widthInner")
mult = self.component.getDimension("mult")
ref = mult * (outerL * outerW - innerL * innerW)
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a rectangle."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = [
"lengthInner",
"lengthOuter",
"widthInner",
"widthOuter",
"mult",
]
ref = [True, True, True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestSolidRectangle(TestShapedComponent):
componentCls = SolidRectangle
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"lengthOuter": 5.0,
"widthOuter": 5.0,
"mult": 1,
}
[docs]
def test_getBoundingCircleOuterDiam(self):
"""Test get bounding circle of the outer diameter."""
ref = math.sqrt(50)
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getArea(self):
"""Calculate area of solid rectangle.
.. test:: Calculate area of solid rectangle.
:id: T_ARMI_COMP_VOL4
:tests: R_ARMI_COMP_VOL
"""
outerL = self.component.getDimension("lengthOuter")
outerW = self.component.getDimension("widthOuter")
mult = self.component.getDimension("mult")
ref = mult * (outerL * outerW)
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a solid rectangle."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["lengthOuter", "widthOuter", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestSquare(TestShapedComponent):
"""Test square shaped component."""
componentCls = Square
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"widthOuter": 3.0,
"widthInner": 2.0,
"mult": 1,
}
[docs]
def test_negativeArea(self):
dims = {
"Tinput": 25.0,
"Thot": 430.0,
"widthOuter": 1.0,
"widthInner": 5.0,
"mult": 1,
}
refArea = dims["mult"] * (dims["widthOuter"] * dims["widthOuter"] - dims["widthInner"] * dims["widthInner"])
negativeRectangle = Square("test", "Void", **dims)
self.assertAlmostEqual(negativeRectangle.getArea(), refArea)
with self.assertRaises(ArithmeticError):
negativeRectangle = Square("test", "UZr", **dims)
negativeRectangle.getArea()
[docs]
def test_getBoundingCircleOuterDiam(self):
"""Get bounding circle outer diameter.
.. test:: Square shaped component
:id: T_ARMI_COMP_SHAPES3
:tests: R_ARMI_COMP_SHAPES
"""
ref = math.sqrt(18.0)
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
# verify the area of the circle is correct
ref = self.componentDims["widthOuter"] ** 2 - self.componentDims["widthInner"] ** 2
cur = self.component.getComponentArea(cold=True)
self.assertAlmostEqual(cur, ref)
[docs]
def test_getCircleInnerDiam(self):
ref = math.sqrt(8.0)
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getArea(self):
"""Calculate area of square.
.. test:: Calculate area of square.
:id: T_ARMI_COMP_VOL5
:tests: R_ARMI_COMP_VOL
"""
outerW = self.component.getDimension("widthOuter")
innerW = self.component.getDimension("widthInner")
mult = self.component.getDimension("mult")
ref = mult * (outerW * outerW - innerW * innerW)
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a square."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["widthOuter", "widthInner", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestCube(TestShapedComponent):
componentCls = Cube
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"lengthOuter": 5.0,
"lengthInner": 4.0,
"widthOuter": 5.0,
"widthInner": 3.0,
"heightOuter": 20.0,
"heightInner": 10.0,
"mult": 2,
}
[docs]
def test_negativeVolume(self):
dims = {
"Tinput": 25.0,
"Thot": 430.0,
"lengthOuter": 5.0,
"lengthInner": 20.0,
"widthOuter": 5.0,
"widthInner": 30.0,
"heightOuter": 20.0,
"heightInner": 30.0,
"mult": 2,
}
refVolume = dims["mult"] * (
dims["lengthOuter"] * dims["widthOuter"] * dims["heightOuter"]
- dims["lengthInner"] * dims["widthInner"] * dims["heightInner"]
)
negativeCube = Cube("test", "Void", **dims)
self.assertAlmostEqual(negativeCube.getVolume(), refVolume)
with self.assertRaises(ArithmeticError):
negativeCube = Cube("test", "UZr", **dims)
negativeCube.getVolume()
[docs]
def test_getVolume(self):
"""Calculate area of cube.
.. test:: Calculate area of cube.
:id: T_ARMI_COMP_VOL6
:tests: R_ARMI_COMP_VOL
"""
lengthO = self.component.getDimension("lengthOuter")
widthO = self.component.getDimension("widthOuter")
heightO = self.component.getDimension("heightOuter")
lengthI = self.component.getDimension("lengthInner")
widthI = self.component.getDimension("widthInner")
heightI = self.component.getDimension("heightInner")
mult = self.component.getDimension("mult")
ref = mult * (lengthO * widthO * heightO - lengthI * widthI * heightI)
cur = self.component.getVolume()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a cube."""
self.assertFalse(self.component.THERMAL_EXPANSION_DIMS)
[docs]
class TestHexagon(TestShapedComponent):
"""Test hexagon shaped component."""
componentCls = Hexagon
componentDims = {"Tinput": 25.0, "Thot": 430.0, "op": 10.0, "ip": 5.0, "mult": 1}
[docs]
def test_getBoundingCircleOuterDiam(self):
ref = 2.0 * 10 / math.sqrt(3)
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getCircleInnerDiameter(self):
ref = 2.0 * 5.0 / math.sqrt(3)
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getArea(self):
"""Calculate area of hexagon.
.. test:: Calculate area of hexagon.
:id: T_ARMI_COMP_VOL7
:tests: R_ARMI_COMP_VOL
"""
cur = self.component.getArea()
mult = self.component.getDimension("mult")
op = self.component.getDimension("op")
ip = self.component.getDimension("ip")
ref = math.sqrt(3.0) / 2.0 * (op**2 - ip**2) * mult
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a hexagon."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["op", "ip", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestFilletedHexagon(TestShapedComponent):
"""Test FilletedHexagon shaped component."""
componentCls = FilletedHexagon
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"op": 10.0,
"ip": 5.0,
"mult": 1,
"oR": 0.2,
"iR": 0.1,
}
[docs]
def test_getBoundingCircleOuterDiameter(self):
ref = 2.0 * 10 / math.sqrt(3)
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getCircleInnerDiameter(self):
ref = 2.0 * 5.0 / math.sqrt(3)
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getComponentArea(self):
cur = self.component.getComponentArea()
op = self.component.getDimension("op")
ip = self.component.getDimension("ip")
oR = self.component.getDimension("oR")
iR = self.component.getDimension("iR")
mult = self.component.getDimension("mult")
ref = mult * (FilletedHexagon._area(op, oR) - FilletedHexagon._area(ip, iR))
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a Hexagon."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["op", "ip", "iR", "oR", "mult"]
ref = [True, True, True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
def test_filletedMatchesNormal(self):
"""Prove that if the radius of curvature is 0.0, FilletedHexagon is just a hexagon."""
for ip in np.arange(0.1, 1, 0.1):
for op in np.arange(1.1, 5, 0.4):
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"op": op,
"ip": ip,
"mult": 1.0,
}
f = FilletedHexagon("xyz", "HT9", **componentDims)
h = Hexagon("xyz", "HT9", **componentDims)
self.assertAlmostEqual(f.getComponentArea(), h.getComponentArea(), delta=1e-7)
self.assertGreaterEqual(h.getArea(), f.getArea() - 1e-7)
[docs]
def test_filletedBecomesACircle(self):
"""Prove that as the radius of curvature becomes D/2, the shape becomes a circle."""
for op in np.arange(1.0, 5.0, 0.5):
componentDims = {
"Tinput": 425.0,
"Thot": 425.0,
"op": op,
"ip": 0.0,
"oR": op / 2.0,
"iR": 0.0,
"mult": 1.0,
}
f = FilletedHexagon("circleHex", "HT9", **componentDims)
self.assertAlmostEqual(f.getComponentArea(), math.pi * (op / 2.0) ** 2, delta=1e-7)
[docs]
class TestHoledHexagon(TestShapedComponent):
"""Test holed hexagon shaped component."""
componentCls = HoledHexagon
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"op": 16.5,
"holeOD": 3.6,
"nHoles": 7,
"mult": 1.0,
}
[docs]
def test_getBoundingCircleOuterDiameter(self):
ref = 2.0 * 16.5 / math.sqrt(3)
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getCircleInnerDiameter(self):
ref = 0 # there are multiple holes, so the function should return 0
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertEqual(ref, cur)
# make and test another one with just 1 hole
simpleHoledHexagon = HoledHexagon(
"hex",
"Void",
self.componentDims["Tinput"],
self.componentDims["Thot"],
self.componentDims["op"],
self.componentDims["holeOD"],
nHoles=1,
)
self.assertEqual(
self.componentDims["holeOD"],
simpleHoledHexagon.getCircleInnerDiameter(cold=True),
)
[docs]
def test_getArea(self):
"""Calculate area of holed hexagon.
.. test:: Calculate area of holed hexagon.
:id: T_ARMI_COMP_VOL8
:tests: R_ARMI_COMP_VOL
"""
op = self.component.getDimension("op")
odHole = self.component.getDimension("holeOD")
nHoles = self.component.getDimension("nHoles")
mult = self.component.getDimension("mult")
hexarea = math.sqrt(3.0) / 2.0 * (op**2)
holeArea = nHoles * math.pi * ((odHole / 2.0) ** 2)
ref = mult * (hexarea - holeArea)
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a holed hexagon."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["op", "holeOD", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestHexHoledCircle(TestShapedComponent):
componentCls = HexHoledCircle
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"od": 16.5,
"holeOP": 3.6,
"mult": 1.0,
}
[docs]
def test_getCircleInnerDiameter(self):
simpleHexHoledCircle = HexHoledCircle(
"Circle",
"Void",
self.componentDims["Tinput"],
self.componentDims["Thot"],
self.componentDims["od"],
self.componentDims["holeOP"],
)
self.assertEqual(
self.componentDims["holeOP"],
simpleHexHoledCircle.getCircleInnerDiameter(cold=True),
)
[docs]
def test_getArea(self):
"""Calculate area of hex holed circle.
.. test:: Calculate area of hex holed circle.
:id: T_ARMI_COMP_VOL9
:tests: R_ARMI_COMP_VOL
"""
od = self.component.getDimension("od")
holeOP = self.component.getDimension("holeOP")
mult = self.component.getDimension("mult")
hexarea = math.sqrt(3.0) / 2.0 * (holeOP**2)
holeArea = math.pi * ((od / 2.0) ** 2)
ref = mult * (holeArea - hexarea)
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
"""Test that ARMI can thermally expands a holed hexagon."""
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["od", "holeOP", "mult"]
ref = [True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestHoledRectangle(TestShapedComponent):
"""Tests HoledRectangle, and provides much support for HoledSquare test."""
componentCls = HoledRectangle
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"lengthOuter": 16.0,
"widthOuter": 10.0,
"holeOD": 3.6,
"mult": 1.0,
}
dimsToTestExpansion = ["lengthOuter", "widthOuter", "holeOD", "mult"]
def setUp(self):
TestShapedComponent.setUp(self)
self.setClassDims()
[docs]
def setClassDims(self):
# This enables subclassing testing for square
self.length = self.component.getDimension("lengthOuter")
self.width = self.component.getDimension("widthOuter")
[docs]
def test_getBoundingCircleOuterDiameter(self):
# hypotenuse
ref = (self.length**2 + self.width**2) ** 0.5
cur = self.component.getBoundingCircleOuterDiameter()
self.assertAlmostEqual(ref, cur)
[docs]
def test_getCircleInnerDiameter(self):
ref = self.componentDims["holeOD"]
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertEqual(ref, cur)
[docs]
def test_getArea(self):
"""Calculate area of holed rectangle.
.. test:: Calculate area of holed rectangle.
:id: T_ARMI_COMP_VOL10
:tests: R_ARMI_COMP_VOL
"""
rectArea = self.length * self.width
odHole = self.component.getDimension("holeOD")
mult = self.component.getDimension("mult")
holeArea = math.pi * ((odHole / 2.0) ** 2)
ref = mult * (rectArea - holeArea)
cur = self.component.getArea()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
ref = [True] * len(self.dimsToTestExpansion)
ref[-1] = False # mult shouldn't expand
for i, d in enumerate(self.dimsToTestExpansion):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
class TestHoledSquare(TestHoledRectangle):
"""Test holed square shaped component."""
componentCls = HoledSquare
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"widthOuter": 16.0,
"holeOD": 3.6,
"mult": 1.0,
}
dimsToTestExpansion = ["widthOuter", "holeOD", "mult"]
[docs]
def setClassDims(self):
# This enables subclassing testing for square
self.width = self.length = self.component.getDimension("widthOuter")
[docs]
def test_thermallyExpands(self):
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_getCircleInnerDiameter(self):
ref = self.componentDims["holeOD"]
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertEqual(ref, cur)
[docs]
class TestHelix(TestShapedComponent):
"""Test helix shaped component."""
componentCls = Helix
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"od": 0.25,
"axialPitch": 1.0,
"mult": 1.5,
"helixDiameter": 2.0,
"id": 0.1,
}
[docs]
def test_getBoundingCircleOuterDiameter(self):
ref = 2.0 + 0.25
cur = self.component.getBoundingCircleOuterDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getCircleInnerDiameter(self):
ref = 2.0 - 0.25
cur = self.component.getCircleInnerDiameter(cold=True)
self.assertAlmostEqual(ref, cur)
[docs]
def test_getArea(self):
"""Calculate area of helix.
.. test:: Calculate area of helix.
:id: T_ARMI_COMP_VOL11
:tests: R_ARMI_COMP_VOL
"""
cur = self.component.getArea()
axialPitch = self.component.getDimension("axialPitch")
helixDiameter = self.component.getDimension("helixDiameter")
innerDiameter = self.component.getDimension("id")
outerDiameter = self.component.getDimension("od")
mult = self.component.getDimension("mult")
c = axialPitch / (2.0 * math.pi)
helixFactor = math.sqrt((helixDiameter / 2.0) ** 2 + c**2) / c
ref = mult * math.pi * (outerDiameter**2 / 4.0 - innerDiameter**2 / 4.0) * helixFactor
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
self.assertTrue(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_dimensionThermallyExpands(self):
expandedDims = ["od", "id", "axialPitch", "helixDiameter", "mult"]
ref = [True, True, True, True, False]
for i, d in enumerate(expandedDims):
cur = d in self.component.THERMAL_EXPANSION_DIMS
self.assertEqual(cur, ref[i])
[docs]
def test_validParameters(self):
"""Testing the Helix class performs as expected with various inputs."""
# stupid/simple inputs
h = Helix("thing", "Cu", 0, 0, 1, 1, 1)
self.assertEqual(h.getDimension("axialPitch"), 1)
# standard case / inputs ordered well
h = Helix(
"what",
"Cu",
Tinput=25.0,
Thot=425.0,
id=0.1,
od=0.35,
mult=1.0,
axialPitch=1.123,
helixDiameter=1.5,
)
self.assertTrue(1.123 < h.getDimension("axialPitch") < 1.15)
# inputs ordered crazy
h = Helix(
material="Cu",
id=0.1,
mult=1.0,
Tinput=25.0,
Thot=425.0,
axialPitch=1.123,
name="stuff",
od=0.35,
helixDiameter=1.5,
)
self.assertTrue(1.123 < h.getDimension("axialPitch") < 1.15)
# missing helixDiameter input
with self.assertRaises(TypeError):
h = Helix(
name="helix",
material="Cu",
Tinput=25.0,
Thot=425.0,
id=0.1,
od=0.35,
mult=1.0,
axialPitch=1.123,
)
[docs]
class TestSphere(TestShapedComponent):
componentCls = Sphere
componentDims = {"Tinput": 25.0, "Thot": 430.0, "od": 1.0, "id": 0.0, "mult": 3}
[docs]
def test_getVolume(self):
"""Calculate area of sphere.
.. test:: Calculate volume of sphere.
:id: T_ARMI_COMP_VOL12
:tests: R_ARMI_COMP_VOL
"""
od = self.component.getDimension("od")
idd = self.component.getDimension("id")
mult = self.component.getDimension("mult")
ref = mult * 4.0 / 3.0 * math.pi * ((od / 2.0) ** 3 - (idd / 2.0) ** 3)
cur = self.component.getVolume()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
self.assertFalse(self.component.THERMAL_EXPANSION_DIMS)
[docs]
class TestRadialSegment(TestShapedComponent):
componentCls = RadialSegment
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"inner_radius": 110,
"outer_radius": 170,
"height": 160,
"mult": 1,
}
[docs]
def test_getVolume(self):
mult = self.component.getDimension("mult")
outerRad = self.component.getDimension("outer_radius")
innerRad = self.component.getDimension("inner_radius")
outerTheta = self.component.getDimension("outer_theta")
innerTheta = self.component.getDimension("inner_theta")
height = self.component.getDimension("height")
radialArea = math.pi * (outerRad**2 - innerRad**2)
aziFraction = (outerTheta - innerTheta) / (math.pi * 2.0)
ref = mult * radialArea * aziFraction * height
cur = self.component.getVolume()
self.assertAlmostEqual(cur, ref)
[docs]
def test_thermallyExpands(self):
self.assertFalse(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_getBoundingCircleOuterDiameter(self):
self.assertEqual(self.component.getBoundingCircleOuterDiameter(cold=True), 340.0)
[docs]
class TestDifferentialRadialSegment(TestShapedComponent):
componentCls = DifferentialRadialSegment
componentDims = {
"Tinput": 25.0,
"Thot": 430.0,
"inner_radius": 110,
"radius_differential": 60,
"inner_axial": 60,
"height": 160,
}
[docs]
def test_getVolume(self):
mult = self.component.getDimension("mult")
outerRad = self.component.getDimension("outer_radius")
innerRad = self.component.getDimension("inner_radius")
outerTheta = self.component.getDimension("outer_theta")
innerTheta = self.component.getDimension("inner_theta")
height = self.component.getDimension("height")
radialArea = math.pi * (outerRad**2 - innerRad**2)
aziFraction = (outerTheta - innerTheta) / (math.pi * 2.0)
ref = mult * radialArea * aziFraction * height
cur = self.component.getVolume()
self.assertAlmostEqual(cur, ref)
[docs]
def test_updateDims(self):
"""
Test Update dimensions.
.. test:: Dimensions can be updated.
:id: T_ARMI_COMP_VOL13
:tests: R_ARMI_COMP_VOL
"""
self.assertEqual(self.component.getDimension("inner_radius"), 110)
self.assertEqual(self.component.getDimension("radius_differential"), 60)
self.component.updateDims()
self.assertEqual(self.component.getDimension("outer_radius"), 170)
self.assertEqual(self.component.getDimension("outer_axial"), 220)
self.assertEqual(self.component.getDimension("outer_theta"), 2 * math.pi)
[docs]
def test_thermallyExpands(self):
self.assertFalse(self.component.THERMAL_EXPANSION_DIMS)
[docs]
def test_getBoundingCircleOuterDiameter(self):
self.assertEqual(self.component.getBoundingCircleOuterDiameter(cold=True), 340)
[docs]
class TestMaterialAdjustments(unittest.TestCase):
"""Tests to make sure enrichment and mass fractions can be adjusted properly."""
def setUp(self):
dims = {"Tinput": 25.0, "Thot": 600.0, "od": 10.0, "id": 5.0, "mult": 1.0}
self.fuel = Circle("fuel", "UZr", **dims)
class FakeBlock:
reactor = Reactor("testMatReactor", None)
def getHeight(self): # unit height
return 1.0
def getSymmetryFactor(self):
return 1.0
def getAncestor(self, fn):
return self.reactor
self.fuel.parent = FakeBlock()
[docs]
def test_setMassFrac(self):
"""Make sure we can set a mass fraction properly."""
target35 = 0.2
self.fuel.setMassFrac("U235", target35)
self.assertAlmostEqual(self.fuel.getMassFrac("U235"), target35)
[docs]
def test_setMassFracOnComponentMaterial(self):
"""Checks for valid and invalid mass fraction assignments on a component's material."""
# Negative value is not acceptable.
with self.assertRaises(ValueError):
self.fuel.material.setMassFrac("U235", -0.1)
# Greater than 1.0 value is not acceptable.
with self.assertRaises(ValueError):
self.fuel.material.setMassFrac("U235", 1.1)
# String is not acceptable.
with self.assertRaises(TypeError):
self.fuel.material.setMassFrac("U235", "")
# `NoneType` is not acceptable.
with self.assertRaises(TypeError):
self.fuel.material.setMassFrac("U235", None)
# Zero is acceptable
self.fuel.material.setMassFrac("U235", 0.0)
self.assertAlmostEqual(self.fuel.material.getMassFrac("U235"), 0.0)
# One is acceptable
self.fuel.material.setMassFrac("U235", 1.0)
self.assertAlmostEqual(self.fuel.material.getMassFrac("U235"), 1.0)
[docs]
def test_adjustMassFrac_invalid(self):
with self.assertRaises(ValueError):
self.fuel.adjustMassFrac(nuclideToAdjust="ZR", val=-0.23)
with self.assertRaises(ValueError):
self.fuel.adjustMassFrac(nuclideToAdjust="ZR", val=1.12)
alwaysFalse = lambda a: False
self.fuel.parent = None
self.assertIsNone(self.fuel.getAncestorAndDistance(alwaysFalse))
[docs]
def test_adjustMassFrac_U235(self):
zrMass = self.fuel.getMass("ZR")
uMass = self.fuel.getMass("U")
zrFrac = zrMass / (uMass + zrMass)
enrichmentFrac = 0.3
u235Frac = enrichmentFrac * uMass / (uMass + zrMass)
u238Frac = (1.0 - enrichmentFrac) * uMass / (uMass + zrMass)
self.fuel.adjustMassFrac(nuclideToAdjust="U235", elementToHoldConstant="ZR", val=u235Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("U235"), u235Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("U238"), u238Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("ZR"), zrFrac)
[docs]
def test_adjustMassFrac_U(self):
self.fuel.adjustMassFrac(elementToAdjust="U", val=0.7)
uFrac = self.fuel.getMassFrac("U")
u235Enrichment = 0.1
u238Frac = (1.0 - u235Enrichment) * uFrac
u235Frac = u235Enrichment * uFrac
self.assertAlmostEqual(self.fuel.getMassFrac("U235"), u235Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("U238"), u238Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("ZR"), 0.30)
[docs]
def test_adjustMassFrac_clear_ZR(self):
self.fuel.adjustMassFrac(nuclideToAdjust="ZR", val=0.0)
self.assertAlmostEqual(self.fuel.getMassFrac("ZR"), 0.0)
self.assertAlmostEqual(self.fuel.getNumberDensity("ZR"), 0.0)
self.assertAlmostEqual(self.fuel.getMassFrac("U235") + self.fuel.getMassFrac("U238"), 1.0)
[docs]
def test_adjustMassFrac_set_ZR(self):
u235Enrichment = 0.1
zrFrac = 0.1
uFrac = 1.0 - zrFrac
u238Frac = (1.0 - u235Enrichment) * uFrac
u235Frac = u235Enrichment * uFrac
self.fuel.adjustMassFrac(nuclideToAdjust="ZR", val=zrFrac)
self.assertAlmostEqual(self.fuel.getMassFrac("U235"), u235Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("U238"), u238Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("ZR"), zrFrac)
[docs]
def test_adjustMassFrac_leave_same(self):
zrFrac = 0.1
u238Enrichment = 0.9
uFrac = 1.0 - zrFrac
u238Frac = uFrac * u238Enrichment
self.fuel.adjustMassFrac(nuclideToAdjust="ZR", val=zrFrac)
self.assertAlmostEqual(self.fuel.getMassFrac("U238"), u238Frac)
self.assertAlmostEqual(self.fuel.getMassFrac("ZR"), zrFrac)
[docs]
def test_adjustMassEnrichment(self):
self.fuel.adjustMassEnrichment(0.2)
self.assertAlmostEqual(self.fuel.getMassFrac("U235"), 0.18)
self.assertAlmostEqual(self.fuel.getMassFrac("U238"), 0.72)
self.assertAlmostEqual(self.fuel.getMassFrac("ZR"), 0.1)
[docs]
def test_getEnrichment(self):
self.fuel.adjustMassEnrichment(0.3)
self.assertAlmostEqual(self.fuel.getEnrichment(), 0.3)
[docs]
def test_finalizeLoadDBAdjustsTD(self):
"""Ensure component is fully loaded through finalize methods."""
tdFrac = 0.54321
comp = self.fuel
comp.p.theoreticalDensityFrac = tdFrac
comp.finalizeLoadingFromDB()
self.assertEqual(comp.material.getTD(), tdFrac)
[docs]
class TestPinQuantities(unittest.TestCase):
"""Test methods that involve retrieval of pin quantities."""
def setUp(self):
self.r = loadTestReactor(inputFileName="smallestTestReactor/armiRunSmallest.yaml")[1]
[docs]
def test_getPinMgFluxes(self):
"""Test proper retrieval of pin multigroup flux for fuel component."""
# Get a fuel block and its fuel component from the core
fuelBlock: Block = self.r.core.getFirstBlock(flags.Flags.FUEL)
fuelComponent: Component = fuelBlock.getComponent(flags.Flags.FUEL)
numPins = int(fuelComponent.p.mult)
self.assertEqual(numPins, 169)
# Set pin fluxes at block level
fuelBlock.assignPinIndices()
pinMgFluxes = np.random.rand(numPins, 33)
pinMgFluxesAdj = np.random.rand(numPins, 33)
pinMgFluxesGamma = np.random.rand(numPins, 33)
fuelBlock.setPinMgFluxes(pinMgFluxes)
fuelBlock.setPinMgFluxes(pinMgFluxesAdj, adjoint=True)
fuelBlock.setPinMgFluxes(pinMgFluxesGamma, gamma=True)
# Retrieve from component to ensure they match
simPinMgFluxes = fuelComponent.getPinMgFluxes()
simPinMgFluxesAdj = fuelComponent.getPinMgFluxes(adjoint=True)
simPinMgFluxesGamma = fuelComponent.getPinMgFluxes(gamma=True)
assert_equal(pinMgFluxes, simPinMgFluxes)
assert_equal(pinMgFluxesAdj, simPinMgFluxesAdj)
assert_equal(pinMgFluxesGamma, simPinMgFluxesGamma)
# Check assertion for adjoint gamma flux
with self.assertRaisesRegex(ValueError, "Adjoint gamma flux is currently unsupported."):
fuelComponent.getPinMgFluxes(adjoint=True, gamma=True)
# Check assertion for not-found parameter
fuelBlock.p.pinMgFluxes = None
with self.assertRaisesRegex(
ValueError,
f"Failure getting pinMgFluxes from {fuelComponent} via parent {fuelBlock}",
):
fuelComponent.getPinMgFluxes()