Skip to content

transport

Transport data structures.

DT_5050_MIXTURE = FractionalFuelComposition(D=0.5, T=0.5, He3=0.0) module-attribute

Fractional fuel composition dataclass.

Notes

Fuel fractions are taken to be constant along the profile. Note that the D-He-3 reaction is aneutronic, but dilutes the fuel in the case that it is included in the fuel density profile.

annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216) module-attribute

FractionalFuelComposition

Fractional fuel composition dataclass.

Notes

Fuel fractions are taken to be constant along the profile. Note that the D-He-3 reaction is aneutronic, but dilutes the fuel in the case that it is included in the fuel density profile.

Source code in tokamak_neutron_source/transport.py
@dataclass
class FractionalFuelComposition:
    """
    Fractional fuel composition dataclass.

    Notes
    -----
    Fuel fractions are taken to be constant along the profile.
    Note that the D-He-3 reaction is aneutronic, but dilutes the fuel
    in the case that it is included in the fuel density profile.
    """

    """Deuterium fuel fraction"""
    D: float

    """Tritium fuel fraction"""
    T: float

    """Helium-3 fuel fraction"""
    He3: float = 0.0

    def __post_init__(self):
        """Force fractions to sum to 1.0."""
        if not np.equal(sum(astuple(self)), 1.0):
            norm = sum(astuple(self))
            self.D, self.T, self.He3 = self.D / norm, self.T / norm, self.He3 / norm
            logger.warning(
                f"Fuel fraction has been renormalized to: {self}",
                stacklevel=1,
            )

He3 = 0.0 class-attribute

Convert a string or number to a floating point number, if possible.

TransportInformation

Transport information.

Source code in tokamak_neutron_source/transport.py
@dataclass
class TransportInformation:
    """Transport information."""

    deuterium_density_profile: PlasmaProfile  # [1/m^3]
    tritium_density_profile: PlasmaProfile  # [1/m^3]
    helium3_density_profile: PlasmaProfile  # [1/m^3]
    temperature_profile: PlasmaProfile  # [keV]
    rho_profile: npt.NDArray  # [0..1]

    @classmethod
    def from_profiles(
        cls,
        ion_temperature_profile: np.ndarray,
        fuel_density_profile: np.ndarray,
        rho_profile: np.ndarray,
        fuel_composition: FractionalFuelComposition = DT_5050_MIXTURE,
    ) -> TransportInformation:
        """
        Instantiate TransportInformation from profile arrays.

        Parameters
        ----------
        ion_temperature_profile:
            Ion temperature profile [keV]
        fuel_density_profile:
            Fuel density profile [1/m^3]
        rho_profile:
            Normalised radial coordinate profile
        fuel_composition
            Fractional fuel composition (constant fraction across profile)

        """  # noqa: DOC201
        return cls(
            DataProfile(
                fuel_composition.D * fuel_density_profile,
                rho_profile,
            ),
            DataProfile(
                fuel_composition.T * fuel_density_profile,
                rho_profile,
            ),
            DataProfile(
                fuel_composition.He3 * fuel_density_profile,
                rho_profile,
            ),
            DataProfile(ion_temperature_profile, rho_profile),
            np.asarray(rho_profile),
        )

    @classmethod
    def from_parameterisations(
        cls,
        ion_temperature_profile: PlasmaProfile,
        fuel_density_profile: PlasmaProfile,
        rho_profile: npt.NDArray,
        fuel_composition: FractionalFuelComposition = DT_5050_MIXTURE,
    ) -> TransportInformation:
        """
        Instantiate TransportInformation from profile parameterisations.

        Parameters
        ----------
        ion_temperature_profile:
            Ion temperature profile parameterisation
        fuel_density_profile:
            Fuel density profile parameterisation
        rho_profile:
            Noramlised radial coordinate profile
        fuel_composition
            Fractional fuel composition (constant fraction across profile)

        """  # noqa: DOC201
        d_profile = deepcopy(fuel_density_profile)
        d_profile.set_scale(fuel_composition.D)
        t_profile = deepcopy(fuel_density_profile)
        t_profile.set_scale(fuel_composition.T)
        he3_profile = deepcopy(fuel_density_profile)
        he3_profile.set_scale(fuel_composition.He3)

        return cls(
            d_profile,
            t_profile,
            he3_profile,
            ion_temperature_profile,
            rho_profile,
        )

    @classmethod
    def from_jetto(cls, jsp_file: str, frame_number: int = -1) -> TransportInformation:
        """
        Instantiate TransportInformation from JETTO file.

        Parameters
        ----------
        jsp_file:
            Path to the JETTO .jsp file
        frame_number:
            The specific time-slice of the JETTO run that we want to investigate.
            This ensures that all of the extracted quantities are describing the same
            point in time.
        """  # noqa: DOC201
        data = load_jsp(jsp_file, frame_number)

        return cls(
            DataProfile(
                data.d_density,
                data.rho,
            ),
            DataProfile(
                data.t_density,
                data.rho,
            ),
            DataProfile(
                data.he3_density,
                data.rho,
            ),
            DataProfile(data.ion_temperature, data.rho),
            data.rho,
        )

    def plot(self) -> tuple[plt.Figure, plt.Axes]:
        """
        Plot the TransportInformation

        Returns
        -------
        f:
            Matplotlib Figure object
        ax:
            Matplotlib Axes object
        """
        f, ax = plt.subplots()

        d_d = self.deuterium_density_profile.value(self.rho_profile)
        d_t = self.tritium_density_profile.value(self.rho_profile)
        d_he3 = self.helium3_density_profile.value(self.rho_profile)

        for d, label, ls in zip(
            [d_d, d_t, d_he3], ["D", "T", "³He"], ["-.", "--", "."], strict=True
        ):
            if not np.allclose(d, 0.0):
                ax.plot(self.rho_profile, d, ls=ls, label=label)
        ax.set_xlabel(r"$\rho$")
        ax.set_ylabel(r"$n$ [1/m$^{3}$]")
        ax.legend(loc="lower left")
        ax2 = ax.twinx()
        ax2.plot(
            self.rho_profile,
            self.temperature_profile.value(self.rho_profile),
            label=r"$T_{i}$",
            color="r",
        )
        ax2.set_ylabel(r"$T_{i}$ [keV]")
        ax2.legend(loc="upper right")
        plt.show()
        return f, ax

from_profiles(ion_temperature_profile, fuel_density_profile, rho_profile, fuel_composition=FractionalFuelComposition(D=0.5, T=0.5, He3=0.0)) classmethod

Instantiate TransportInformation from profile arrays.

Parameters:

Name Type Description Default
ion_temperature_profile ndarray

Ion temperature profile [keV]

required
fuel_density_profile ndarray

Fuel density profile [1/m^3]

required
rho_profile ndarray

Normalised radial coordinate profile

required
fuel_composition FractionalFuelComposition

Fractional fuel composition (constant fraction across profile)

FractionalFuelComposition(D=0.5, T=0.5, He3=0.0)
Source code in tokamak_neutron_source/transport.py
@classmethod
def from_profiles(
    cls,
    ion_temperature_profile: np.ndarray,
    fuel_density_profile: np.ndarray,
    rho_profile: np.ndarray,
    fuel_composition: FractionalFuelComposition = DT_5050_MIXTURE,
) -> TransportInformation:
    """
    Instantiate TransportInformation from profile arrays.

    Parameters
    ----------
    ion_temperature_profile:
        Ion temperature profile [keV]
    fuel_density_profile:
        Fuel density profile [1/m^3]
    rho_profile:
        Normalised radial coordinate profile
    fuel_composition
        Fractional fuel composition (constant fraction across profile)

    """  # noqa: DOC201
    return cls(
        DataProfile(
            fuel_composition.D * fuel_density_profile,
            rho_profile,
        ),
        DataProfile(
            fuel_composition.T * fuel_density_profile,
            rho_profile,
        ),
        DataProfile(
            fuel_composition.He3 * fuel_density_profile,
            rho_profile,
        ),
        DataProfile(ion_temperature_profile, rho_profile),
        np.asarray(rho_profile),
    )

from_parameterisations(ion_temperature_profile, fuel_density_profile, rho_profile, fuel_composition=FractionalFuelComposition(D=0.5, T=0.5, He3=0.0)) classmethod

Instantiate TransportInformation from profile parameterisations.

Parameters:

Name Type Description Default
ion_temperature_profile PlasmaProfile

Ion temperature profile parameterisation

required
fuel_density_profile PlasmaProfile

Fuel density profile parameterisation

required
rho_profile NDArray

Noramlised radial coordinate profile

required
fuel_composition FractionalFuelComposition

Fractional fuel composition (constant fraction across profile)

FractionalFuelComposition(D=0.5, T=0.5, He3=0.0)
Source code in tokamak_neutron_source/transport.py
@classmethod
def from_parameterisations(
    cls,
    ion_temperature_profile: PlasmaProfile,
    fuel_density_profile: PlasmaProfile,
    rho_profile: npt.NDArray,
    fuel_composition: FractionalFuelComposition = DT_5050_MIXTURE,
) -> TransportInformation:
    """
    Instantiate TransportInformation from profile parameterisations.

    Parameters
    ----------
    ion_temperature_profile:
        Ion temperature profile parameterisation
    fuel_density_profile:
        Fuel density profile parameterisation
    rho_profile:
        Noramlised radial coordinate profile
    fuel_composition
        Fractional fuel composition (constant fraction across profile)

    """  # noqa: DOC201
    d_profile = deepcopy(fuel_density_profile)
    d_profile.set_scale(fuel_composition.D)
    t_profile = deepcopy(fuel_density_profile)
    t_profile.set_scale(fuel_composition.T)
    he3_profile = deepcopy(fuel_density_profile)
    he3_profile.set_scale(fuel_composition.He3)

    return cls(
        d_profile,
        t_profile,
        he3_profile,
        ion_temperature_profile,
        rho_profile,
    )

from_jetto(jsp_file, frame_number=-1) classmethod

Instantiate TransportInformation from JETTO file.

Parameters:

Name Type Description Default
jsp_file str

Path to the JETTO .jsp file

required
frame_number int

The specific time-slice of the JETTO run that we want to investigate. This ensures that all of the extracted quantities are describing the same point in time.

-1
Source code in tokamak_neutron_source/transport.py
@classmethod
def from_jetto(cls, jsp_file: str, frame_number: int = -1) -> TransportInformation:
    """
    Instantiate TransportInformation from JETTO file.

    Parameters
    ----------
    jsp_file:
        Path to the JETTO .jsp file
    frame_number:
        The specific time-slice of the JETTO run that we want to investigate.
        This ensures that all of the extracted quantities are describing the same
        point in time.
    """  # noqa: DOC201
    data = load_jsp(jsp_file, frame_number)

    return cls(
        DataProfile(
            data.d_density,
            data.rho,
        ),
        DataProfile(
            data.t_density,
            data.rho,
        ),
        DataProfile(
            data.he3_density,
            data.rho,
        ),
        DataProfile(data.ion_temperature, data.rho),
        data.rho,
    )

plot()

Plot the TransportInformation

Returns:

Name Type Description
f Figure

Matplotlib Figure object

ax Axes

Matplotlib Axes object

Source code in tokamak_neutron_source/transport.py
def plot(self) -> tuple[plt.Figure, plt.Axes]:
    """
    Plot the TransportInformation

    Returns
    -------
    f:
        Matplotlib Figure object
    ax:
        Matplotlib Axes object
    """
    f, ax = plt.subplots()

    d_d = self.deuterium_density_profile.value(self.rho_profile)
    d_t = self.tritium_density_profile.value(self.rho_profile)
    d_he3 = self.helium3_density_profile.value(self.rho_profile)

    for d, label, ls in zip(
        [d_d, d_t, d_he3], ["D", "T", "³He"], ["-.", "--", "."], strict=True
    ):
        if not np.allclose(d, 0.0):
            ax.plot(self.rho_profile, d, ls=ls, label=label)
    ax.set_xlabel(r"$\rho$")
    ax.set_ylabel(r"$n$ [1/m$^{3}$]")
    ax.legend(loc="lower left")
    ax2 = ax.twinx()
    ax2.plot(
        self.rho_profile,
        self.temperature_profile.value(self.rho_profile),
        label=r"$T_{i}$",
        color="r",
    )
    ax2.set_ylabel(r"$T_{i}$ [keV]")
    ax2.legend(loc="upper right")
    plt.show()
    return f, ax

DataProfile

Bases: tokamak_neutron_source.profile.PlasmaProfile

Plasma profile from data.

Notes

Normalised radius conventions are not enforced here. Linear interpolation is used.

Source code in tokamak_neutron_source/profile.py
class DataProfile(PlasmaProfile):
    """
    Plasma profile from data.

    Notes
    -----
    Normalised radius conventions are not enforced here.
    Linear interpolation is used.
    """

    def __init__(self, values: npt.NDArray, rho: npt.NDArray, *, scale: float = 1.0):
        super().__init__(scale)
        self._values = values
        self._rho = rho
        self._interpolator = interp1d(self._rho, self._values, kind="linear")

    def value(self, rho: float | Iterable) -> float | np.ndarray:
        """Calculate the value of the profile at given value(s) of rho."""  # noqa: DOC201
        rho = np.clip(rho, 0.0, 1.0)
        return self._scale * self._interpolator(rho)

value(rho)

Calculate the value of the profile at given value(s) of rho.

Source code in tokamak_neutron_source/profile.py
def value(self, rho: float | Iterable) -> float | np.ndarray:
    """Calculate the value of the profile at given value(s) of rho."""  # noqa: DOC201
    rho = np.clip(rho, 0.0, 1.0)
    return self._scale * self._interpolator(rho)

PlasmaProfile

Bases: abc.ABC

Abstract base class for plasma profiles.

Source code in tokamak_neutron_source/profile.py
class PlasmaProfile(ABC):
    """Abstract base class for plasma profiles."""

    def __init__(self, scale: float = 1.0):
        self._scale = scale

    @abstractmethod
    def value(self, rho: float | Iterable) -> float | np.ndarray:
        """Calculate the value of the profile at given value(s) of rho."""

    def set_scale(self, scale: float = 1.0):
        """Set the scale of the rho value. Multiplies existing scale."""
        self._scale *= scale

set_scale(scale=1.0)

Set the scale of the rho value. Multiplies existing scale.

Source code in tokamak_neutron_source/profile.py
def set_scale(self, scale: float = 1.0):
    """Set the scale of the rho value. Multiplies existing scale."""
    self._scale *= scale

value(rho)

Calculate the value of the profile at given value(s) of rho.

Source code in tokamak_neutron_source/profile.py
@abstractmethod
def value(self, rho: float | Iterable) -> float | np.ndarray:
    """Calculate the value of the profile at given value(s) of rho."""

load_jsp(file, frame_number=-1)

Load a JETTO JSP binary file.

Parameters:

Name Type Description Default
file str | Path

File to read

required
frame_number int

Frame number to read

-1

Returns:

Type Description
SimpleJETTOOutput

Simplified JETTO output

Raises:

Type Description
ValueError

If the specified frame number is invalid.

Notes

For details, refer to https://users.euro-fusion.org/pages/data-cmg/wiki/JETTO_ppfjsp.html

The core values (rho = 0.0) are not provided by JETTO. Here we extrapolate them.

JETTO presently does not provide Helium-3 densities. These are taken to be 0.0.

JETTO presently does not provide D-D fusion power or reaction rates, or some files may potentially do some but only for one of the channels.

Source code in tokamak_neutron_source/tools.py
def load_jsp(file: str | Path, frame_number: int = -1) -> SimpleJETTOOutput:
    """
    Load a JETTO JSP binary file.

    Parameters
    ----------
    file:
        File to read
    frame_number:
        Frame number to read

    Returns
    -------
    :
        Simplified JETTO output

    Raises
    ------
    ValueError
        If the specified frame number is invalid.

    Notes
    -----
    For details, refer to
    https://users.euro-fusion.org/pages/data-cmg/wiki/JETTO_ppfjsp.html

    The core values (rho = 0.0) are not provided by JETTO. Here we extrapolate
    them.

    JETTO presently does not provide Helium-3 densities. These are taken to
    be 0.0.

    JETTO presently does not provide D-D fusion power or reaction rates, or
    some files may potentially do some but only for one of the channels.
    """
    from jetto_tools.binary import read_binary_file  # noqa: PLC0415

    jsp = read_binary_file(file)

    time_stamps = jsp["TIME"][:, 0, 0]  # times when the snapshots are made

    if frame_number < -1 or frame_number > len(time_stamps) - 1:
        raise ValueError(f"This JETTO file does not have a frame number: {frame_number}")
    t_index = len(time_stamps) - 1 if frame_number == -1 else frame_number

    rho = jsp["XPSQ"][t_index, :]  # Sqrt(poloidal magnetic flux)
    ion_temperature = jsp["TI"][t_index, :]
    d_density = jsp["NID"][t_index, :]
    t_density = jsp["NIT"][t_index, :]
    he3_density = np.zeros_like(rho)  # JETTO does not provide 3-He density

    # Here we treat the core, as JETTO at present does not provide data at rho = 0.0
    rho = np.insert(rho, 0, 0.0)
    ion_temperature = np.insert(ion_temperature, 0, ion_temperature[0])
    d_density = np.insert(d_density, 0, d_density[0])
    t_density = np.insert(t_density, 0, t_density[0])
    he3_density = np.insert(he3_density, 0, he3_density[0])

    ion_temperature = raw_uc(ion_temperature, "eV", "keV")

    # Cumulative vectors for fusion power and neutron rate
    dt_neutron_rate = jsp["R00"][t_index, -1]

    return SimpleJETTOOutput(
        rho=rho,
        ion_temperature=ion_temperature,
        d_density=d_density,
        t_density=t_density,
        he3_density=he3_density,
        dt_neutron_rate=dt_neutron_rate,
    )