# 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.
# pylint: disable=too-many-lines, invalid-name
"""
The units module contains unit conversion functions and constants.
"""
import math
import scipy.constants
# Names
DEGC = chr(176) + "C"
MICRONS = chr(181) + "m"
NOT_APPLICABLE = "N/A"
UNITLESS = ""
# conversions
C_TO_K = 273.15
BOLTZMAN_CONSTANT = 8.6173324e-11 # boltzmann constant in MeV/K
AVOGADROS_NUMBER = 6.0221415e23
CM2_PER_BARN = 1.0e-24
MOLES_PER_CC_TO_ATOMS_PER_BARN_CM = AVOGADROS_NUMBER * CM2_PER_BARN
JOULES_PER_MeV = 1.60217646e-13
JOULES_PER_eV = JOULES_PER_MeV * 1.0e-6
SECONDS_PER_MINUTE = 60.0
MINUTES_PER_HOUR = 60.0
HOURS_PER_DAY = 24.0
SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR
SECONDS_PER_DAY = HOURS_PER_DAY * SECONDS_PER_HOUR
DAYS_PER_YEAR = 365.24219 # mean tropical year
SECONDS_PER_YEAR = 31556926.0
GAS_CONSTANT = 8.3144621 # J/mol-K
# Cut-off is taken to be any element/nuclide with an atomic number
# that is greater than Actinium (i.e., the first classified Actinide).
HEAVY_METAL_CUTOFF_Z = 89
MICRONS_PER_METER = 1.0e6
CM2_PER_M2 = 1.0e4
CM3_PER_M3 = 1.0e6
METERS_PER_CM = 0.01
WATTS_PER_MW = 1.0e6
EV_PER_MEV = 1.0e6
MM_PER_CM = 10.0
G_PER_KG = 1000.0
LITERS_PER_CUBIC_METER = 1000
CC_PER_LITER = CM3_PER_M3 / LITERS_PER_CUBIC_METER
DEG_TO_RAD = 1.0 / 180.0 * math.pi # Degrees to Radians
RAD_TO_REV = 1.0 / (2 * math.pi) # Radians to Revolutions
ATOMIC_MASS_CONSTANT_MEV = scipy.constants.physical_constants[
"atomic mass constant energy equivalent in MeV"
][0]
ABS_REACTIVITY_TO_PCM = 1.0e5
PA_PER_ATM = scipy.constants.atm
PA_PER_MMHG = 133.322368421053
PA_PER_BAR = 100000.0
CURIE_PER_BECQUEREL = 1.0 / 3.7e10
MICROCURIES_PER_BECQUEREL = CURIE_PER_BECQUEREL * 1e-6
G_PER_CM3_TO_KG_PER_M3 = 1000.0
# constants
ASCII_MIN_CHAR = 44 # First char allowed in various FORTRAN inputs
ASCII_LETTER_A = 65
ASCII_LETTER_Z = 90
ASCII_ZERO = 48
TRACE_NUMBER_DENSITY = 1e-50
MIN_FUEL_HM_MOLES_PER_CC = 1e-10
# More than 10 decimals can create floating point comparison problems in MCNP and DIF3D
FLOAT_DIMENSION_DECIMALS = 10
EFFECTIVELY_ZERO = 10.0 ** (-1 * FLOAT_DIMENSION_DECIMALS)
#
# STEFAN_BOLTZMANN_CONSTANT is for constant for radiation heat transfer [W m^-2 K^-4]
#
STEFAN_BOLTZMANN_CONSTANT = 5.67e-8 # W/m^2-K^4
#
# GRAVITY is the acceleration due to gravity at the Earths surface in [m s^-2].
#
GRAVITY = 9.80665
#
# :code:`REYNOLDS_TURBULENT` is the Reynolds number below which a duct flow will exhibit "laminar"
# conditions. Reyonlds numbers greater than :code:`REYNOLDS_TURBULENT` will involve flows that are
# "transitional" or "turbulent".
#
REYNOLDS_LAMINAR = 2100.0
#
# :code:`REYNOLDS_TURBULENT` is the Reynolds number above which a duct flow will exhibit "turbulent"
# conditions. Reynolds numbers lower than :code:`REYNOLDS_TURBULENT` will involve flows that are
# "transitional" or "laminar".
#
REYNOLDS_TURBULENT = 4000.0
[docs]def getTk(Tc=None, Tk=None):
"""
Return a temperature in Kelvin, given a temperature in Celsius or Kelvin
Returns
-------
T : float
temperature in Kelvin
Raises
------
TypeError
The temperature was not provided as an int or float.
"""
if not ((Tc is not None) ^ (Tk is not None)):
raise ValueError(
f"Cannot produce T in K from Tc={Tc} and Tk={Tk}. "
"Please supply a single temperature."
)
return float(Tk) if Tk is not None else Tc + C_TO_K
[docs]def getTc(Tc=None, Tk=None):
"""
Return a temperature in Celcius, given a temperature in Celsius or Kelvin
Returns
-------
T : float
temperature in Celsius
Raises
------
TypeError
The temperature was not provided as an int or float.
"""
if not ((Tc is not None) ^ (Tk is not None)):
raise ValueError(
f"Cannot produce T in C from Tc={Tc} and Tk={Tk}. "
"Please supply a single temperature."
)
return float(Tc) if Tc is not None else Tk - C_TO_K
[docs]def getTf(Tc=None, Tk=None):
"""
Return a temperature in Fahrenheit, given a temperature in Celsius or Kelvin
Returns
-------
T : float
temperature in Fahrenheit
Raises
------
TypeError
The temperature was not provided as an int or float.
"""
return 1.8 * getTc(Tc, Tk) + 32.0
[docs]def getTemperature(Tc=None, Tk=None, tempUnits=None):
"""
Returns the temperature in the prescribed temperature units
Parameters
----------
Tc : float
temperature in Celsius
Tk : float
temperature in Kelvin
tempUnits : str
a flag for the temperature units of the correlation 'Tk', 'K', 'Kelvin',
'Tc', 'C', or 'Celsius' are acceptable.
Returns
-------
T : float
temperature in units defined by the tempUnits flag
Raises
------
ValueError
When an invalid tempUnits input is provided.
"""
if tempUnits in ["Tk", "K", "Kelvin"]:
return getTk(Tc=Tc, Tk=Tk)
if tempUnits in ["Tc", "C", "Celsius"]:
return getTc(Tc=Tc, Tk=Tk)
raise ValueError("Invalid inputs provided. Check docstring.")
[docs]def getTmev(Tc=None, Tk=None):
Tk = getTk(Tc, Tk)
return BOLTZMAN_CONSTANT * Tk
[docs]def convertMmhgToPascal(mmhg):
"""Converts pressure from mmhg to pascal.
Parameters
----------
mmhg : float
pressure in mmhg
Returns
-------
pascal : float
pressure in pascal
"""
return mmhg * PA_PER_MMHG
[docs]def convertBarToPascal(pBar):
"""Converts pressure from bar to pascal.
Parameters
----------
pBar : float
pressure in bar
Returns
-------
pascal : float
pressure in pascal
"""
return pBar * PA_PER_BAR
[docs]def convertAtmToPascal(pAtm):
"""Converts pressure from atomspheres to pascal.
Parameters
----------
pAtm : float
pressure in atomspheres
Returns
-------
pascal : float
pressure in pascal
"""
return pAtm * PA_PER_ATM
PRESSURE_CONVERTERS = {
"Pa": lambda pa: pa,
"bar": convertBarToPascal,
"mmHg": convertMmhgToPascal,
"atm": convertAtmToPascal,
}
[docs]def sanitizeAngle(theta):
"""
Returns an angle between 0 and 2pi.
Parameters
----------
theta : float
an angle
Returns
-------
theta : float
an angle between 0 and 2*pi
"""
if theta < 0:
theta = theta + (1 + -1 * int(theta / (math.pi * 2.0))) * math.pi * 2.0
if theta > 2.0 * math.pi:
theta = theta - int(theta / (math.pi * 2.0)) * math.pi * 2.0
return theta
[docs]def getXYLineParameters(theta, x=0, y=0):
"""
returns parameters A B C D for a plane in the XY direction
Parameters
----------
theta : float
angle above x-axis in radians
x : float
x coordinate
y : float
y coordinate
Returns
-------
A : float
line coefficient
B : float
line coefficient
C : float
line coefficient
D : float
line coefficient
Notes
-----
the line is in the form of A*x + B*y + C*z - D = 0 -- this corresponds to a MCNP arbitrary line equation
"""
theta = sanitizeAngle(theta)
if (
math.fabs(theta) < 1e-10
or math.fabs(theta - math.pi) < 1e-10
or math.fabs(theta - 2.0 * math.pi) < 1e-10
):
# this is a py plane so y is always y
return 0.0, 1.0, 0.0, y
if (
math.fabs(theta - math.pi / 2.0) > 1e-10
or math.fabs(theta - 3 * math.pi / 2.0) > 1e-10
):
# this is a px plane so x is always x
return 1.0, 0.0, 0.0, x
A = -1.0 / math.cos(theta)
B = 1.0 / math.sin(theta)
C = 0.0
D = A * x + B * y
return A, B, C, D