Source code for cfspopcon.formulas.plasma_profiles.plasma_profiles

"""Estimate 1D plasma profiles of density and temperature."""

import numpy as np
import xarray as xr

from ...algorithm_class import Algorithm, CompositeAlgorithm
from .numerical_profile_fits import evaluate_density_and_temperature_profile_fits


def _calc_profile_grid_edge_nudge(npoints: int) -> float:
    """Return the resolution-dependent LCFS offset used to regularize the grid.

    Hollow analytic profiles are singular exactly at ``rho = 1``. Rather than
    using one fixed epsilon, the nudge is scaled to the grid spacing so the last
    trapezoid stays well behaved across different resolutions.
    """
    if npoints <= 1:
        return 0.0

    # Choose the endpoint offset so it is one tenth of the induced grid
    # spacing: nudge = 0.1 * drho, drho = (1 - nudge) / (npoints - 1).
    return 0.1 / (npoints - 1 + 0.1)


[docs] def build_rho_grid(npoints: int = 50) -> np.ndarray: """Build the radial rho grid used to construct the 1D profiles. The grid stops about one tenth of a grid spacing inside the LCFS so the analytic hollow-profile form stays finite at the edge, while still extending essentially to ``rho = 1`` for the volume integral. Args: npoints: number of points in the grid Returns: rho [~] :term:`glossary link<rho>` """ edge_nudge = _calc_profile_grid_edge_nudge(npoints) return np.linspace(0.0, 1.0 - edge_nudge, num=npoints)
[docs] @Algorithm.register_algorithm(return_keys=["rho"]) def define_radial_grid(n_points_for_confined_region_profiles: int = 50) -> np.ndarray: """Define the radial grid for profiles. The grid is nudged just inside the LCFS (see :func:`build_rho_grid`) rather than dropping the endpoint, so the last point stays essentially at rho = 1 while the analytic hollow-profile form remains finite at the edge. """ x = build_rho_grid(n_points_for_confined_region_profiles) return xr.DataArray(x, coords=dict(dim_rho=x))
[docs] @Algorithm.register_algorithm( return_keys=["electron_density_profile", "fuel_ion_density_profile", "electron_temp_profile", "ion_temp_profile"] ) def calc_analytic_profiles( rho: np.ndarray, average_electron_density: float, average_electron_temp: float, average_ion_temp: float, electron_density_peaking: float, ion_density_peaking: float, temperature_peaking: float, dilution: float, ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """Estimate density and temperature profiles using a simple analytic fit. Args: rho : [~] :term:`glossary link<rho>` average_electron_density: [1e19 m^-3] :term:`glossary link<average_electron_density>` average_electron_temp: [keV] :term:`glossary link<average_electron_temp>` average_ion_temp: [keV] :term:`glossary link<average_ion_temp>` electron_density_peaking: [~] :term:`glossary link<electron_density_peaking>` ion_density_peaking: [~] :term:`glossary link<ion_density_peaking>` temperature_peaking: [~] :term:`glossary link<temperature_peaking>` dilution: dilution of main ions [~] Returns: :term:`electron_density_profile` [1e19 m^-3], fuel_ion_density_profile [1e19 m^-3], :term:`electron_temp_profile` [keV], :term:`ion_temp_profile` [keV] """ electron_density_profile = average_electron_density * electron_density_peaking * ((1.0 - rho**2.0) ** (electron_density_peaking - 1.0)) fuel_ion_density_profile = ( average_electron_density * dilution * (ion_density_peaking) * ((1.0 - rho**2.0) ** (ion_density_peaking - 1.0)) ) electron_temp_profile = average_electron_temp * temperature_peaking * ((1.0 - rho**2.0) ** (temperature_peaking - 1.0)) ion_temp_profile = average_ion_temp * temperature_peaking * ((1.0 - rho**2.0) ** (temperature_peaking - 1.0)) return electron_density_profile, fuel_ion_density_profile, electron_temp_profile, ion_temp_profile
[docs] @Algorithm.register_algorithm( return_keys=["electron_density_profile", "fuel_ion_density_profile", "electron_temp_profile", "ion_temp_profile"] ) def calc_prf_profiles( rho: np.ndarray, average_electron_density: float, average_electron_temp: float, average_ion_temp: float, electron_density_peaking: float, ion_density_peaking: float, temperature_peaking: float, dilution: float, normalized_inverse_temp_scale_length: float, ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """Estimate density and temperature profiles using profiles from Pablo Rodriguez-Fernandez. Args: rho: [~] :term:`glossary link<rho>` average_electron_density: [1e19 m^-3] :term:`glossary link<average_electron_density>` average_electron_temp: [keV] :term:`glossary link<average_electron_temp>` average_ion_temp: [keV] :term:`glossary link<average_ion_temp>` electron_density_peaking: [~] :term:`glossary link<electron_density_peaking>` ion_density_peaking: [~] :term:`glossary link<ion_density_peaking>` temperature_peaking: [~] :term:`glossary link<temperature_peaking>` dilution: dilution of main ions [~] normalized_inverse_temp_scale_length: [~] :term:`glossary link<normalized_inverse_temp_scale_length>` Returns: :term:`electron_density_profile` [1e19 m^-3], fuel_ion_density_profile [1e19 m^-3], :term:`electron_temp_profile` [keV], :term:`ion_temp_profile` [keV] """ electron_temp_profile, electron_density_profile = evaluate_density_and_temperature_profile_fits( rho=rho, T_avol=average_electron_temp, n_avol=average_electron_density, temperature_peaking=temperature_peaking, nu_n=electron_density_peaking, aLT=normalized_inverse_temp_scale_length, dataset="PRF", ) ion_temp_profile, fuel_ion_density_profile = evaluate_density_and_temperature_profile_fits( rho=rho, T_avol=average_ion_temp, n_avol=average_electron_density * dilution, temperature_peaking=temperature_peaking, nu_n=ion_density_peaking, aLT=normalized_inverse_temp_scale_length, dataset="PRF", ) return electron_density_profile, fuel_ion_density_profile, electron_temp_profile, ion_temp_profile
calc_peak_electron_temp = Algorithm.from_single_function( lambda average_electron_temp, temperature_peaking: average_electron_temp * temperature_peaking, return_keys=["peak_electron_temp"], name="calc_peak_electron_temp", ) calc_peak_ion_temp = Algorithm.from_single_function( lambda average_ion_temp, temperature_peaking: average_ion_temp * temperature_peaking, return_keys=["peak_ion_temp"], name="calc_peak_ion_temp", ) calc_peaking_and_analytic_profiles = CompositeAlgorithm( algorithms=[ Algorithm.get_algorithm(alg) for alg in [ "calc_effective_collisionality", "calc_ion_density_peaking", "calc_electron_density_peaking", "calc_peak_electron_temp", "calc_peak_ion_temp", "define_radial_grid", "calc_analytic_profiles", ] ], name="calc_peaking_and_analytic_profiles", register=True, ) calc_peaking_and_prf_profiles = CompositeAlgorithm( algorithms=[ Algorithm.get_algorithm(alg) for alg in [ "calc_effective_collisionality", "calc_ion_density_peaking", "calc_electron_density_peaking", "calc_peak_electron_temp", "calc_peak_ion_temp", "define_radial_grid", "calc_prf_profiles", ] ], name="calc_peaking_and_prf_profiles", register=True, )
[docs] @Algorithm.register_algorithm(return_keys=[]) def calc_peaked_profiles(): """Deprecated entry point for setting up profiles.""" # TODO: remove in a later release raise NotImplementedError("calc_peaked_profiles is deprecated.")