"""Alpha-particle losses, computed over the particles born on one surface."""
import numpy as np
from ._checks import require_finite
E_ALPHA_MEV = 3.5
LOSS_TOL = 1e-14
S_INITS = (0.1, 0.25, 0.5) # birth surfaces of the traced particles
[docs]
def is_lost(confinement_time, t_max):
r"""Flag the particles that left the plasma before the end of the simulation.
Particle :math:`i` is lost if its confinement time is shorter than the simulation time,
:math:`t_i < t_\text{max}`, up to round-off.
Args:
confinement_time (np.ndarray): confinement times [s], shape ``(..., n)``.
t_max (float or np.ndarray): simulation time [s], a scalar or shape ``(...)``.
Returns:
np.ndarray: ``True`` for the lost particles, shape ``(..., n)``.
Raises:
ValueError: if an input contains NaN or inf.
"""
require_finite(confinement_time=confinement_time, t_max=t_max)
return confinement_time < np.asarray(t_max)[..., None] - LOSS_TOL
[docs]
def loss_fraction(confinement_time, t_max, s_init, s0=0.1):
"""Compute the fraction of the particles born on one surface that are lost.
Args:
confinement_time (np.ndarray): confinement times [s], shape ``(..., n)``.
t_max (float or np.ndarray): simulation time [s], a scalar or shape ``(...)``.
s_init (np.ndarray): birth surfaces, shape ``(..., n)``.
s0 (float): the birth surface :math:`s_0` to average over.
Returns:
float or np.ndarray: the loss fraction, shape ``(...)``.
Raises:
ValueError: if an input contains NaN or inf.
"""
return _mean_on_surface(is_lost(confinement_time, t_max), s_init, s0)
[docs]
def energy_loss(confinement_time, t_max, s_init, s0=0.1):
r"""Compute the mean energy lost per particle born on one surface.
.. math::
E = \frac{1}{N} \sum_i e^{-2 t_i / t_\text{max}} \cdot 3.5\ \text{MeV},
where the sum runs over the :math:`N` particles born on :math:`s_0`, and the slowing-down
time in the exponent is approximated by :math:`t_\text{max} = 0.1` s. A confined particle
(:math:`t_i = t_\text{max}`) counts :math:`3.5\,e^{-2}` MeV.
Args:
confinement_time (np.ndarray): confinement times [s], shape ``(..., n)``.
t_max (float or np.ndarray): simulation time [s], a scalar or shape ``(...)``.
s_init (np.ndarray): birth surfaces, shape ``(..., n)``.
s0 (float): the birth surface :math:`s_0` to average over.
Returns:
float or np.ndarray: the energy loss [MeV], shape ``(...)``.
Raises:
ValueError: if an input contains NaN or inf.
"""
require_finite(confinement_time=confinement_time, t_max=t_max)
tau = confinement_time / np.asarray(t_max)[..., None]
return _mean_on_surface(E_ALPHA_MEV * np.exp(-2 * tau), s_init, s0)
[docs]
def normalized_energy_loss(confinement_time, t_max, s_init, s0=0.1):
r"""Compute the energy loss, rescaled to :math:`[0, 1]`.
.. math::
\bar E = \frac{1}{N} \sum_i \frac{e^{-2 t_i / t_\text{max}} - e^{-2}}{1 - e^{-2}},
where the sum runs over the :math:`N` particles born on :math:`s_0`, so :math:`\bar E` is
0 if every particle is confined and 1 if every particle is lost at once.
Args:
confinement_time (np.ndarray): confinement times [s], shape ``(..., n)``.
t_max (float or np.ndarray): simulation time [s], a scalar or shape ``(...)``.
s_init (np.ndarray): birth surfaces, shape ``(..., n)``.
s0 (float): the birth surface :math:`s_0` to average over.
Returns:
float or np.ndarray: the normalized energy loss, shape ``(...)``.
Raises:
ValueError: if an input contains NaN or inf.
"""
require_finite(confinement_time=confinement_time, t_max=t_max)
tau = confinement_time / np.asarray(t_max)[..., None]
e = (np.exp(-2 * tau) - np.exp(-2)) / (1 - np.exp(-2))
return _mean_on_surface(e, s_init, s0)
[docs]
def mean_confinement_time(confinement_time, t_max, s_init, s0=0.1):
r"""Compute the mean confinement time of the particles born on one surface.
.. math::
\bar t = \frac{1}{N} \sum_i t_i,
where the sum runs over the :math:`N` particles born on :math:`s_0`, so a confined
particle counts :math:`t_i = t_\text{max}`, and :math:`\bar t` is at most
:math:`t_\text{max}`.
Args:
confinement_time (np.ndarray): confinement times [s], shape ``(..., n)``.
t_max (float or np.ndarray): simulation time [s], a scalar or shape ``(...)``.
s_init (np.ndarray): birth surfaces, shape ``(..., n)``.
s0 (float): the birth surface :math:`s_0` to average over.
Returns:
float or np.ndarray: the mean confinement time [s], shape ``(...)``.
Raises:
ValueError: if an input contains NaN or inf.
"""
require_finite(confinement_time=confinement_time, t_max=t_max)
t = np.minimum(confinement_time, np.asarray(t_max)[..., None])
return _mean_on_surface(t, s_init, s0)
def _mean_on_surface(values, s_init, s0):
"""Average a per-particle quantity over the particles born on one surface.
Args:
values (np.ndarray): per-particle values, shape ``(..., n)``.
s_init (np.ndarray): particle birth surfaces, the shape of values.
s0 (float): the birth surface to average over.
Returns:
float or np.ndarray: the mean over the last axis of the particles with ``s_init == s0``.
Raises:
ValueError: if s_init contains NaN or inf.
"""
require_finite(s_init=s_init)
born = np.isclose(s_init, s0)
return (values * born).sum(axis=-1) / born.sum(axis=-1)