boss_bench

Source code for boss_bench.confinement

"""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)