Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix units and unit-conversion factors #2891

Merged
merged 9 commits into from
Jun 4, 2024
42 changes: 32 additions & 10 deletions bluemira/base/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ def _add_contexts(self, contexts: Optional[List[Context]] = None):
Add new contexts to registry
"""
if not self._contexts_added:
self.contexts = [self._energy_temperature_context(), self._flow_context()]
self.contexts = [
self._energy_temperature_context(),
self._mass_energy_context(),
self._flow_context(),
]

for c in self.contexts:
self.add_context(c)
Expand Down Expand Up @@ -107,6 +111,31 @@ def _energy_temperature_context(self):
lambda _, x: x / conversion,
)

def _mass_energy_context(self):
"""
Converter between mass and energy

energy = mass * speed-of-light^2

Returns
-------
pint context
"""
m_to_e = Context("Mass_to_Energy")

m_units = "[mass]"
e_units = "[energy]"

conversion = self.Quantity("c^2")

return self._transform(
m_to_e,
m_units,
e_units,
lambda _, x: x * conversion,
lambda _, x: x / conversion,
)

@property
def flow_conversion(self):
"""Gas flow conversion factor R * T"""
Expand Down Expand Up @@ -257,6 +286,8 @@ def _transform(
# Vacuum permittivity
EPS_0 = ureg.Quantity("eps_0").to_base_units().magnitude # [A^2.s^4/kg/m^3]

# absolute charge of an electron
ELEMENTARY_CHARGE = ureg.Quantity("e").to_base_units().magnitude # [e]

# Commonly used..
MU_0_2PI = 2e-7 # [T.m/A] or [V.s/(A.m)]
Expand Down Expand Up @@ -337,15 +368,6 @@ def _transform(
# Conversions
# =============================================================================

# Electron-volts to Joules
EV_TO_J = ureg.Quantity(1, ureg.eV).to(ureg.joule).magnitude

# Joules to Electron-volts
J_TO_EV = ureg.Quantity(1, ureg.joule).to(ureg.eV).magnitude

# Atomic mass units to kilograms
AMU_TO_KG = ureg.Quantity(1, ureg.amu).to(ureg.kg).magnitude

# Years to seconds
YR_TO_S = ureg.Quantity(1, ureg.year).to(ureg.second).magnitude

Expand Down
11 changes: 6 additions & 5 deletions bluemira/fuel_cycle/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,11 @@
m_T = m_T_0 * np.ones(len(self.DEMO_t))
for i in range(1, len(self.DEMO_t)):
dt = self.DEMO_t[i] - self.DEMO_t[i - 1]
t_bred = TBR * self.brate[i] * (YR_TO_S * dt)
t_bred += self.prate[i] * (YR_TO_S * dt)
t_burnt = self.brate[i] * (YR_TO_S * dt)
t_DD = self.prate[i] * (YR_TO_S * dt)
dts = dt * YR_TO_S
t_bred = TBR * self.brate[i] * dts
t_bred += self.prate[i] * dts
t_burnt = self.brate[i] * dts
t_DD = self.prate[i] * dts

Check warning on line 193 in bluemira/fuel_cycle/cycle.py

View check run for this annotation

Codecov / codecov/patch

bluemira/fuel_cycle/cycle.py#L189-L193

Added lines #L189 - L193 were not covered by tests
m_T[i] = (m_T[i - 1]) * np.exp(-T_LAMBDA * dt) - t_burnt + t_bred + t_DD
return m_T

Expand Down Expand Up @@ -350,7 +351,7 @@

m_T_out = self.plasma(self.params.eta_iv, self.params.I_miv, flows=flows)
# Resolution - Not used everywhere for speed
n_ts = int(round((YR_TO_S * self.DEMO_t[-1]) / self.timestep))
n_ts = int(round(self.DEMO_t[-1] * YR_TO_S / self.timestep))

Check warning on line 354 in bluemira/fuel_cycle/cycle.py

View check run for this annotation

Codecov / codecov/patch

bluemira/fuel_cycle/cycle.py#L354

Added line #L354 was not covered by tests
self.t, m_pellet_in = discretise_1d(self.DEMO_t, self.m_T_in, n_ts)

# Flow out of the vacuum vessel
Expand Down
2 changes: 1 addition & 1 deletion bluemira/fuel_cycle/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
self.t_on_total = self.fpy * YR_TO_S # [s] total fusion time
tf_ins_life_dose = tf_ins_nflux * self.t_on_total / self.params.tf_fluence
if tf_ins_life_dose > 1:
self.tf_lifeend = round(self.params.tf_fluence / tf_ins_nflux / YR_TO_S, 2)
self.tf_lifeend = round(self.params.tf_fluence / tf_ins_nflux * S_TO_YR, 2)

Check warning on line 162 in bluemira/fuel_cycle/lifecycle.py

View check run for this annotation

Codecov / codecov/patch

bluemira/fuel_cycle/lifecycle.py#L162

Added line #L162 was not covered by tests
tflifeperc = round(100 * self.tf_lifeend / self.fpy, 1)
bluemira_warn(
f"TF coil insulation fried after {self.tf_lifeend:.2f} full-power years"
Expand Down
3 changes: 1 addition & 2 deletions bluemira/fuel_cycle/timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,7 @@
self.ft = np.zeros(len(self.t))
for i in fuse_indices[1::2]:
self.ft[i] = self.t[i] - self.t[i - 1]
self.ft = np.cumsum(self.ft)
self.ft *= S_TO_YR
self.ft = np.cumsum(self.ft) * S_TO_YR

Check warning on line 388 in bluemira/fuel_cycle/timeline.py

View check run for this annotation

Codecov / codecov/patch

bluemira/fuel_cycle/timeline.py#L388

Added line #L388 was not covered by tests
self.t *= S_TO_YR
self.plant_life = self.t[-1] # total plant lifetime [calendar]

Expand Down
14 changes: 6 additions & 8 deletions bluemira/fuel_cycle/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ def delay_decay(t: np.ndarray, m_t_flow: np.ndarray, tt_delay: float) -> np.ndar
The time vector
m_t_flow:
The mass flow vector
t_delay:
The delay duration [s]
tt_delay:
The delay duration [yr]

Returns
-------
Expand Down Expand Up @@ -575,7 +575,7 @@ def _fountain_linear_sink(
if dt == 0:
return m_flow, inventory, sum_in, decayed

m_in = m_flow * YR_TO_S # kg/yr
m_in = m_flow * YR_TO_S # converts kg/s to kg/yr
dts = dt * YR_TO_S
mass_in = m_flow * dts
sum_in += mass_in
Expand Down Expand Up @@ -731,13 +731,12 @@ def _linear_thresh_sink(
decayed:
Accountancy parameter to calculate the total value of decayed T in a sink
"""
years = 365 * 24 * 3600
dt = t_out - t_in
if dt == 0:
return m_flow, inventory, sum_in, decayed

m_in = m_flow * years # kg/yr
dts = dt * years
m_in = m_flow * YR_TO_S # converts kg/s to kg/yr
dts = dt * YR_TO_S
mass_in = m_flow * dts
sum_in += mass_in
j_inv0 = inventory
Expand Down Expand Up @@ -825,13 +824,12 @@ def _sqrt_thresh_sink(
not accounted for in this function. We have to add decay in the sink and
ensure this is handled when calculation the absorbtion and out-flow.
"""
years = 365 * 24 * 3600
dt = t_out - t_in
if dt == 0:
# Nothing can happen if time is zero
return m_flow, inventory, sum_in, decayed

dts = dt * years
dts = dt * YR_TO_S
mass_in = m_flow * dts
sum_in += mass_in

Expand Down
25 changes: 18 additions & 7 deletions bluemira/plasma_physics/collisions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

from bluemira.base.constants import (
ELECTRON_MASS,
ELEMENTARY_CHARGE,
EPS_0,
EV_TO_J,
H_PLANCK,
K_BOLTZMANN,
PROTON_MASS,
raw_uc,
)


Expand All @@ -35,7 +35,9 @@
-------
Debye length [m]
"""
return np.sqrt(EPS_0 * K_BOLTZMANN * temperature / (EV_TO_J**2 * density))
return np.sqrt(
EPS_0 * raw_uc(temperature, "K", "J") / (ELEMENTARY_CHARGE**2 * density)
)


def reduced_mass(mass_1: float, mass_2: float) -> float:
Expand Down Expand Up @@ -74,7 +76,10 @@
The sqrt(2) term is for a 3-dimensional system and the most probable velocity in
the particle velocity distribution.
"""
return np.sqrt(2) * np.sqrt(K_BOLTZMANN * temperature / mass)
return np.sqrt(2) * np.sqrt(
raw_uc(temperature, "K", "J") # = Joule = kg*m^2/s^2
/ mass
) # sqrt(m^2/s^2) = m/s


def de_broglie_length(velocity: float, mu_12: float) -> float:
Expand Down Expand Up @@ -110,7 +115,7 @@
-------
Perpendicular impact parameter [m]
"""
return EV_TO_J**2 / (4 * np.pi * EPS_0 * mu_12 * velocity**2)
return ELEMENTARY_CHARGE**2 / (4 * np.pi * EPS_0 * mu_12 * velocity**2)


def coulomb_logarithm(temperature: float, density: float) -> float:
Expand Down Expand Up @@ -146,7 +151,8 @@
Z_eff:
Effective charge [a.m.u.]
T_e:
Electron temperature on axis [eV]
Electron temperature on axis [keV]
The equation takes in temperature as [eV], so an in-line conversion is used here.
ln_lambda:
Coulomb logarithm value

Expand All @@ -160,4 +166,9 @@

\t:math:`\\sigma = 1.92e4 (2-Z_{eff}^{-1/3}) \\dfrac{T_{e}^{3/2}}{Z_{eff}ln\\Lambda}`
"""
return 1.92e4 * (2 - Z_eff ** (-1 / 3)) * T_e**1.5 / (Z_eff * ln_lambda)
return (

Check warning on line 169 in bluemira/plasma_physics/collisions.py

View check run for this annotation

Codecov / codecov/patch

bluemira/plasma_physics/collisions.py#L169

Added line #L169 was not covered by tests
1.92e4
* (2 - Z_eff ** (-1 / 3))
* raw_uc(T_e, "keV", "eV") ** 1.5
/ (Z_eff * ln_lambda)
)
12 changes: 4 additions & 8 deletions bluemira/plasma_physics/reactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@
import numpy.typing as npt

from bluemira.base.constants import (
AMU_TO_KG,
C_LIGHT,
D_MOLAR_MASS,
ELECTRON_MOLAR_MASS,
EV_TO_J,
HE3_MOLAR_MASS,
HE_MOLAR_MASS,
J_TO_EV,
NEUTRON_MOLAR_MASS,
N_AVOGADRO,
PROTON_MOLAR_MASS,
Expand Down Expand Up @@ -59,7 +55,7 @@ def E_DT_fusion() -> float:
\\Delta E = \\Delta m c^2
"""
delta_m = (D_MOLAR_MASS + T_MOLAR_MASS) - (HE_MOLAR_MASS + NEUTRON_MOLAR_MASS)
return delta_m * C_LIGHT**2 * AMU_TO_KG * J_TO_EV
return raw_uc(delta_m, "amu", "eV")


def E_DD_fusion() -> float:
Expand Down Expand Up @@ -88,7 +84,7 @@ def E_DD_fusion() -> float:
]
)
delta_m = np.average(delta_m)
return delta_m * C_LIGHT**2 * AMU_TO_KG * J_TO_EV
return raw_uc(delta_m, "amu", "eV")


def n_DT_reactions(p_fus: float) -> float:
Expand All @@ -108,7 +104,7 @@ def n_DT_reactions(p_fus: float) -> float:
Number of D-T reactions per second [1/s]
"""
e_dt = E_DT_fusion()
return raw_uc(p_fus, "MW", "W") / (e_dt * EV_TO_J)
return raw_uc(p_fus, "MW", "W") / raw_uc(e_dt, "eV", "J")


def n_DD_reactions(p_fus: float) -> float:
Expand All @@ -128,7 +124,7 @@ def n_DD_reactions(p_fus: float) -> float:
Number of D-D reactions per second [1/s]
"""
e_dd = E_DD_fusion()
return p_fus / (e_dd * EV_TO_J)
return p_fus / raw_uc(e_dd, "eV", "J")


def r_T_burn(p_fus: float) -> float: # noqa: N802
Expand Down
6 changes: 3 additions & 3 deletions bluemira/plasma_physics/rules_of_thumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import numpy as np

from bluemira.base.constants import EV_TO_J, K_BOLTZMANN, MU_0
from bluemira.base.constants import MU_0, raw_uc
from bluemira.plasma_physics.collisions import coulomb_logarithm, spitzer_conductivity


Expand All @@ -29,7 +29,7 @@
Z_eff:
Effective charge [a.m.u.]
T_e:
Electron temperature on axis [eV]
Electron temperature on axis [keV]
n_e:
Electron density [1/m^3]
q_0:
Expand All @@ -54,7 +54,7 @@

There is no neo-classical resistivity on axis because there are no trapped particles
""" # noqa: W505, E501
ln_lambda = coulomb_logarithm(T_e * EV_TO_J / K_BOLTZMANN, n_e)
ln_lambda = coulomb_logarithm(raw_uc(T_e, "keV", "K"), n_e)

Check warning on line 57 in bluemira/plasma_physics/rules_of_thumb.py

View check run for this annotation

Codecov / codecov/patch

bluemira/plasma_physics/rules_of_thumb.py#L57

Added line #L57 was not covered by tests
sigma = spitzer_conductivity(Z_eff, T_e, ln_lambda)

# Current density on axis
Expand Down
Loading