diff --git a/armi/reactor/components/__init__.py b/armi/reactor/components/__init__.py index 516cdae81..a7c5b1f6c 100644 --- a/armi/reactor/components/__init__.py +++ b/armi/reactor/components/__init__.py @@ -186,6 +186,23 @@ def getBoundingCircleOuterDiameter(self, Tc=None, cold=False): """ return 2 * math.sqrt(self.getComponentArea(cold=cold) / math.pi) + def getCircleInnerDiameter(self, Tc=None, cold=False): + """ + Component is unshaped; assume it is circular and there is no ID (return 0.0). + + Parameters + ---------- + Tc : float, optional + Ignored for this component + cold : bool, optional + Ignored for this component + + Notes + ----- + Tc is not used in this method for this particular component. + """ + return 0.0 + @staticmethod def fromComponent(otherComponent): """ diff --git a/armi/reactor/components/basicShapes.py b/armi/reactor/components/basicShapes.py index 4e395009b..27994d681 100644 --- a/armi/reactor/components/basicShapes.py +++ b/armi/reactor/components/basicShapes.py @@ -20,8 +20,8 @@ """ import math -from armi.reactor.components import ShapedComponent -from armi.reactor.components import componentParameters +from armi.reactor.components.component import ShapedComponent +from armi.reactor.components.component import componentParameters class Circle(ShapedComponent): diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index 2a64da275..0564bc1f3 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -273,19 +273,34 @@ def __lt__(self, other): """ True if a circle encompassing this object has a smaller diameter than one encompassing another component. + If the bounding circles for both components have identical size, then revert to checking the inner + diameter of each component for sorting. + This allows sorting because the Python sort functions only use this method. """ thisOD = self.getBoundingCircleOuterDiameter(cold=True) thatOD = other.getBoundingCircleOuterDiameter(cold=True) try: - return thisOD < thatOD - except Exception: - raise ValueError( - "Components 1 ({} with OD {}) and 2 ({} and OD {}) cannot be ordered because their " - "bounding circle outer diameters are not comparable.".format( - self, thisOD, other, thatOD + if thisOD == thatOD: + thisID = self.getCircleInnerDiameter(cold=True) + thatID = other.getCircleInnerDiameter(cold=True) + return thisID < thatID + else: + return thisOD < thatOD + except (NotImplementedError, Exception) as e: + if isinstance(e, NotImplementedError): + raise NotImplementedError( + "getCircleInnerDiameter not implemented for at least one of {}, {}".format( + self, other + ) + ) + else: + raise ValueError( + "Components 1 ({} with OD {}) and 2 ({} and OD {}) cannot be ordered because their " + "bounding circle outer diameters are not comparable.".format( + self, thisOD, other, thatOD + ) ) - ) def __setstate__(self, state): composites.Composite.__setstate__(self, state) diff --git a/armi/reactor/components/volumetricShapes.py b/armi/reactor/components/volumetricShapes.py index 329c537b1..7386b6276 100644 --- a/armi/reactor/components/volumetricShapes.py +++ b/armi/reactor/components/volumetricShapes.py @@ -250,7 +250,10 @@ def getComponentVolume(self): return vol def getBoundingCircleOuterDiameter(self, Tc=None, cold=False): - return self.getDimension("outer_radius", Tc, cold) + return 2.0 * self.getDimension("outer_radius", Tc, cold) + + def getCircleInnerDiameter(self, Tc=None, cold=False): + return 2.0 * self.getDimension("inner_radius", Tc, cold) class DifferentialRadialSegment(RadialSegment): diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index fc6569bb3..9ae0b5488 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -44,7 +44,7 @@ UnshapedVolumetricComponent, ComponentType, ) -from armi.reactor.components import materials +from armi import materials from armi.reactor.tests.test_reactors import loadTestReactor @@ -508,6 +508,52 @@ def test_getAreaColdTrue(self): self.assertAlmostEqual(totalAreaCold, totalAreaHot, delta=1e-10) +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, + ] + + 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") + + class TestCircle(TestShapedComponent): """Test circle shaped component.""" @@ -1613,7 +1659,7 @@ def test_thermallyExpands(self): def test_getBoundingCircleOuterDiameter(self): self.assertEqual( - self.component.getBoundingCircleOuterDiameter(cold=True), 170.0 + self.component.getBoundingCircleOuterDiameter(cold=True), 340.0 ) @@ -1660,7 +1706,7 @@ def test_thermallyExpands(self): self.assertFalse(self.component.THERMAL_EXPANSION_DIMS) def test_getBoundingCircleOuterDiameter(self): - self.assertEqual(self.component.getBoundingCircleOuterDiameter(cold=True), 170) + self.assertEqual(self.component.getBoundingCircleOuterDiameter(cold=True), 340) class TestMaterialAdjustments(unittest.TestCase): diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index 700d90bdc..14fa35810 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -14,7 +14,8 @@ New Features #. Provide utilities for determining location of a rotated object in a hexagonal lattice: :func:`armi.utils.hexagon.getIndexOfRotatedCell` (`PR#1846 `_) +#. Allow merging a component with zero area into another component. (`PR#1858 `_) +#. Use inner diameter for sorting components when outer diameter is identical. (`PR#1882 `_) #. TBD API Changes