# 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.
"""3-dimensional shapes."""
import math
from armi.reactor.components import componentParameters
from armi.reactor.components import ShapedComponent
[docs]class Sphere(ShapedComponent):
"""A spherical component."""
is3D = True
THERMAL_EXPANSION_DIMS = {}
# Just usurp the Circle parameters. This may lead to issues at some point in things like the DB
# interface, but for now, they are the same params, so why not?
pDefs = componentParameters.getCircleParameterDefinitions()
def __init__(
self,
name,
material,
Tinput,
Thot,
od=None,
id=None,
mult=None,
modArea=None,
isotopics=None,
mergeWith=None,
components=None,
):
ShapedComponent.__init__(
self,
name,
material,
Tinput,
Thot,
isotopics=isotopics,
mergeWith=mergeWith,
components=components,
)
self._linkAndStoreDimensions(
components, od=od, id=id, mult=mult, modArea=modArea
)
[docs] def getComponentArea(self, cold=False):
raise NotImplementedError("Cannot compute area of a sphere component.")
[docs] def getComponentVolume(self):
"""Computes the volume of the sphere in cm^3."""
od = self.getDimension("od")
iD = self.getDimension("id")
mult = self.getDimension("mult")
vol = mult * 4.0 / 3.0 * math.pi * ((od / 2.0) ** 3 - (iD / 2.0) ** 3)
return vol
[docs]class Cube(ShapedComponent):
"""
More correctly, a rectangular cuboid
"""
is3D = True
THERMAL_EXPANSION_DIMS = {}
pDefs = componentParameters.getCubeParameterDefinitions()
def __init__(
self,
name,
material,
Tinput,
Thot,
lengthOuter=None,
lengthInner=None,
widthOuter=None,
widthInner=None,
heightOuter=None,
heightInner=None,
mult=None,
modArea=None,
isotopics=None,
mergeWith=None,
components=None,
):
ShapedComponent.__init__(
self,
name,
material,
Tinput,
Thot,
isotopics=isotopics,
mergeWith=mergeWith,
components=components,
)
self._linkAndStoreDimensions(
components,
lengthOuter=lengthOuter,
lengthInner=lengthInner,
widthOuter=widthOuter,
widthInner=widthInner,
heightOuter=heightOuter,
heightInner=heightInner,
mult=mult,
modArea=modArea,
)
[docs] def getComponentArea(self, cold=False):
raise NotImplementedError("Cannot compute area of a cube component.")
[docs] def getComponentVolume(self):
r"""
Computes the volume of the cube in cm^3.
"""
lengthO = self.getDimension("lengthOuter")
widthO = self.getDimension("widthOuter")
heightO = self.getDimension("heightOuter")
lengthI = self.getDimension("lengthInner")
widthI = self.getDimension("widthInner")
heightI = self.getDimension("heightInner")
mult = self.getDimension("mult")
vol = mult * (lengthO * widthO * heightO - lengthI * widthI * heightI)
return vol
[docs]class Torus(ShapedComponent):
r"""Theta defines the extent the radial segment is rotated around the Z-axis
phi defines the extent around the major radius (i.e. a half torus is from 0 to pi)
Notes
-----
p0 - inner minor radius
p1 - outer minor radius
p2 - major radius
p3 - multiplier
p4 - inner theta (optional) 0 (default)
p5 - outer theta (optional) 2pi (default)
p6 - inner phi (optional) 0 (default)
p7 - outer phi (optional) 2pi (default)
p8 - height (optional) <set as outer minor radius > (default)
p9 - reference volume (optional)
Z
|
|
|
| - ^ -
| / | minor radius
|-----------------------> major radius, R
| \ /
| - - -
|
Y
^ -
| -
| - \
| - \ \
| theta | |
ZX-----------------------> major radius, X
|
|
|
"""
is3D = True
THERMAL_EXPANSION_DIMS = {}
pDefs = componentParameters.getTorusParameterDefinitions()
def __init__(
self,
name,
material,
Tinput,
Thot,
inner_minor_radius=None,
outer_minor_radius=None,
major_radius=None,
mult=1,
inner_theta=0.0,
outer_theta=math.pi * 2,
inner_phi=0,
outer_phi=math.pi * 2,
height=None,
reference_volume=None,
inner_radius=None,
outer_radius=None,
isotopics=None,
mergeWith=None,
components=None,
):
ShapedComponent.__init__(
self,
name,
material,
Tinput,
Thot,
isotopics=isotopics,
mergeWith=mergeWith,
components=components,
)
height = 2 * outer_minor_radius if height is None else height
inner_radius = (
major_radius - outer_minor_radius if inner_radius is None else inner_radius
)
outer_radius = (
major_radius + outer_minor_radius if outer_radius is None else outer_radius
)
self._linkAndStoreDimensions(
components,
inner_minor_radius=inner_minor_radius,
outer_minor_radius=outer_minor_radius,
major_radius=major_radius,
mult=mult,
inner_theta=inner_theta,
outer_theta=outer_theta,
inner_phi=inner_phi,
outer_phi=outer_phi,
height=height,
reference_volume=reference_volume,
inner_radius=inner_radius,
outer_radius=outer_radius,
)
[docs] def getComponentArea(
self, refVolume=None, refArea=None, refHeight=None, cold=False
):
r"""Computes the volume averaged area of the torus component.
Parameters
----------
RefVolume - float
This is the volume to use when normalizing area
RefArea - float
This is the area to use when normalizing area
RefHeight - floats
This is the height to use to estimate volume if a reference volume
is not given
Notes
-----
Since area fractions are being used as a proxy for volume fractions, this method returns the reference
area normalized to the volume ratio of the torus within reference volume
"""
refPhi = self.getDimension("reference_phi", cold=cold)
height = self.getDimension("height", cold=cold)
if refArea is None:
# assume the footprint of the assembly is the footprint of a half torus
if "reference_phi" in self.p and "height" in self.p:
# reference volume and reference height defined in the component
refArea = refPhi / height
else:
majorRad = self.getDimension("major_radius", cold=cold)
outerMinorRad = self.getDimension("outer_minor_radius", cold=cold)
outerTh = self.getDimension("outer_theta")
innerTh = self.getDimension("inner_theta")
refArea = (
math.pi
* (
(majorRad + outerMinorRad) ** 2.0
- (majorRad - outerMinorRad) ** 2.0
)
* (outerTh - innerTh)
/ (2 * math.pi)
)
if refVolume is None:
if refPhi:
refVolume = refPhi
elif refHeight is None:
refVolume = refArea * height
else:
refVolume = refArea * refHeight
return self.getVolume() * refArea / refVolume
[docs] def getComponentVolume(self):
"""Computes the volume of the torus in cm^3.
Notes
-----
The exact solution is the solution to integrating the volume:
dV ~ (dr)*((R+cos(Ph)*r)*dTh)*(r*dPh)
Solution from WolframAlpha:
integrate (m*(R + cos(phi)*r)*r) dr dphi dtheta, theta=t1...t2, r=r1...r2, phi=p1...p2
"""
r1 = self.getDimension("inner_minor_radius")
r2 = self.getDimension("outer_minor_radius")
R = self.getDimension("major_radius")
mult = self.getDimension("mult")
t1 = self.getDimension("inner_theta")
t2 = self.getDimension("outer_theta")
p1 = self.getDimension("inner_phi")
p2 = self.getDimension("outer_phi")
dTh = t2 - t1
dPhi = p1 - p2
dRad = r1 - r2
totRad = r1 + r2
dAngle = math.sin(p1) - math.sin(p2)
vol = (
mult
* dTh
* (3 * R * dPhi * dRad * totRad + 2 * (r1 ** 3 - r2 ** 3) * dAngle)
/ 6.0
)
return vol
[docs]class RadialSegment(ShapedComponent):
is3D = True
THERMAL_EXPANSION_DIMS = {}
pDefs = componentParameters.getRadialSegmentParameterDefinitions()
def __init__(
self,
name,
material,
Tinput,
Thot,
inner_radius=None,
outer_radius=None,
height=None,
mult=None,
inner_theta=0,
outer_theta=math.pi * 2,
isotopics=None,
mergeWith=None,
components=None,
):
ShapedComponent.__init__(
self,
name,
material,
Tinput,
Thot,
isotopics=isotopics,
mergeWith=mergeWith,
components=components,
)
self._linkAndStoreDimensions(
components,
inner_radius=inner_radius,
outer_radius=outer_radius,
height=height,
mult=mult,
inner_theta=inner_theta,
outer_theta=outer_theta,
)
[docs] def getComponentArea(self, refVolume=None, refHeight=None, cold=False):
if refHeight:
return (
(self.getDimension("height", cold=cold) / refHeight)
* self.getDimension("mult")
* (
math.pi
* (
self.getDimension("outer_radius", cold=cold) ** 2
- self.getDimension("inner_radius", cold=cold) ** 2
)
* (
(
self.getDimension("outer_theta", cold=cold)
- self.getDimension("inner_theta", cold=cold)
)
/ (math.pi * 2.0)
)
)
)
if refVolume:
return (self.getComponentVolume() / refVolume) / self.getDimension("height")
else:
return self.getComponentVolume() / self.getDimension("height")
[docs] def getComponentVolume(self):
mult = self.getDimension("mult")
outerRad = self.getDimension("outer_radius")
innerRad = self.getDimension("inner_radius")
outerTheta = self.getDimension("outer_theta")
innerTheta = self.getDimension("inner_theta")
height = self.getDimension("height")
radialArea = math.pi * (outerRad ** 2 - innerRad ** 2)
aziFraction = (outerTheta - innerTheta) / (math.pi * 2.0)
vol = mult * radialArea * aziFraction * height
return vol
[docs] def getBoundingCircleOuterDiameter(self, Tc=None, cold=False):
return self.getDimension("outer_radius", Tc, cold)
[docs]class DifferentialRadialSegment(RadialSegment):
"""
This component class represents a volume element with thicknesses in the
azimuthal, radial and axial directions. Furthermore it has dependent
dimensions: (outer theta, outer radius, outer axial) that can be updated
depending on the 'differential' in the corresponding directions
This component class is super useful for defining ThRZ reactors and
perturbing its dimensions using the optimization modules
See Also
--------
geometry purturbation:
armi.physics.optimize.OptimizationInterface.modifyCase (ThRZReflectorThickness,ThRZActiveHeight,ThRZActiveRadius)
mesh updating:
armi.reactor.reactors.Reactor.importGeom
"""
is3D = True
THERMAL_EXPANSION_DIMS = {}
def __init__(
self,
name,
material,
Tinput,
Thot,
inner_radius=None,
radius_differential=None,
inner_axial=None,
height=None,
inner_theta=0,
azimuthal_differential=2 * math.pi,
mult=1,
isotopics=None,
mergeWith=None,
components=None,
):
ShapedComponent.__init__(
self,
name,
material,
Tinput,
Thot,
isotopics=isotopics,
mergeWith=mergeWith,
components=components,
)
self._linkAndStoreDimensions(
components,
inner_radius=inner_radius,
radius_differential=radius_differential,
inner_axial=inner_axial,
height=height,
inner_theta=inner_theta,
azimuthal_differential=azimuthal_differential,
mult=mult,
)
self.updateDims()
[docs] def updateDims(self, key="", val=None):
"""
Update the dimensions of differential radial segment component.
Notes
-----
Can be used to update any dimension on the component, but outer_radius, outer_axial, and outer_theta are
always updated.
See Also
--------
armi.reactor.blocks.Block.updateComponentDims
"""
self.setDimension(key, val)
self.setDimension(
"outer_radius",
self.getDimension("inner_radius")
+ self.getDimension("radius_differential"),
)
self.setDimension(
"outer_axial",
self.getDimension("inner_axial") + self.getDimension("height"),
)
self.setDimension(
"outer_theta",
self.getDimension("inner_theta")
+ self.getDimension("azimuthal_differential"),
)
[docs] def getComponentArea(self, refVolume=None, refHeight=None, cold=False):
self.updateDims()
return RadialSegment.getComponentArea(
self, refVolume=None, refHeight=None, cold=False
)
[docs] def getComponentVolume(self):
self.updateDims()
return RadialSegment.getComponentVolume(self)