FusionSyntheticDiagnostics.jl

Installation

First install Julia, then:

using Pkg
Pkg.add("FusionSyntheticDiagnostics")

Synthetic Diagnostics

Bolometer

Synthetic bolometer can be added using IMAS compatible JSON file that describes the metadata and the line of sight of chords with information on apertures and detectors. On computation, the bolomter uses radiation data in the IMAS IDS to numerically integrate total radiation falling on each detector taking into account the apertures in the path.

FusionSyntheticDiagnostics.add_bolometer!Function
add_bolometer!(
    config::Union{String, Dict{Symbol, Any}}=default_bolometer,
    @nospecialize(ids::IMAS.dd)=IMAS.dd();
    overwrite::Bool=false, kwargs...,
)::IMAS.dd

Add bolometer to IMAS structure using a JSON file or Julia Dict and compute the bolometer outputs. kwargs are passed to compute_bolometer!.

source
FusionSyntheticDiagnostics.compute_bolometer!Function
compute_bolometer!(

    @nospecialize(ids::IMAS.dd);

    rtol::Float64=1e-3,
    n_e_gsi::Int=5,
)

Computed the line integrated electron density from the bolometer data present in IDS structure for all the chords. The computation is based on the edge profile data and core profile data present in the IDS structure.

source

Several useful geometrical functions are defined and used here.

FusionSyntheticDiagnostics.FoVType
FoV

A structure to represent the field of view of a bolometer channel. It holds the half angle of the conical field of view and the transformation to go from the field of view frame to the (X, Y, Z) frame. In the field of view frame, the z-axis is along the conical axis and the origin is the apex of the cone. The positive z direction is away from the detector and towards the plasma.

Constructors:

FoV(ha::Float64, fov2XYZ::AffineMap{T, U})
 where {T <: Rotation{3, Float64}, U <: SVector{3, Float64}}

FoV(
    ha::Float64,
    vertex::SVector{3, Float64},
    direction::SVector{3, Float64},
)::FoV

Convinience constructor of field of view from a given half-angle ha, field of view vertex in XYZ frame and unit vectore direction along field of view.

source
FusionSyntheticDiagnostics.get_FoVFunction
get_FoV(
    ch::IMAS.bolometer__channel{T},
    ap2XYZ::AffineMap{RotMatrix3{Float64}, SVector{3, Float64}},
    det2XYZ::AffineMap{RotMatrix3{Float64}, SVector{3, Float64}},
)::FoV where {T <: Real}

Assuming aperture and detector are arrays of points described in 3D space on some

coordinate axes and that the array of points for aperture lie in a plane and the array of points for the detector lie in a plane. This function computes the field of view of the detector from the aperture and returns a tuple of the half angle of the field of view and the transformation that maps from field of view coordinates to the input coordinate frame. In the field of view coordinate frame, the field of view is a cone with the vertex at the origin and the axis along the z-axis, and half angle that is returned.

First, two circles are created, centered at aperture and detector and covering entire area of the aperture and detector in detector frame. The frame is rotated about the z-axis to get the aperture normal to lie in x-z plane. In this frame, at y=0 plane, the aperture and detector become two line segments. The extremeties of the two line segments are joined to create 4 extreme rays. The pair of extreme rays creating the the largest angle between them is then chosen as the extreme rays that would form the cone of field of view (FoV). The angle bisector is calculated to get the axis of FoV and half-angle of FoV is calculated. These two are then returned as FoV object which comprises of half-angle and the transformation required to go from FoV frame to XYZ frame. In FoV frame, the conical axis is along the z axis.

source
FusionSyntheticDiagnostics.get_lineFunction
get_line(
    p1::SVector{2, Float64},
    p2::SVector{2, Float64},
)::Union{SVector{3, Float64}, Nothing}

Compute the line equation from two points in 2D space. The line is represented by the equation $a x + b y + c = 0$ and stored as SVector{3, Float64}(a, b, c). Returns nothing if the two points are the same.

source
FusionSyntheticDiagnostics.get_angleFunction
get_angle(l1::SVector{3, Float64}, l2::SVector{3, Float64})::Float64

Compute the angle in radians between two lines in 2D space. The lines are represented by the equation $a x + b y + c = 0$ and stored as SVector{3, Float64}(a, b, c).

source
FusionSyntheticDiagnostics.get_angle_bisectorFunction
get_angle_bisector(
    l1::SVector{3, Float64},
    l2::SVector{3, Float64},
)::Tuple{SVector{3, Float64}, SVector{3, Float64}}

Compute the angle bisector of two lines in 2D space. The lines are represented by the equation $a x + b y + c = 0$ and stored as SVector{3, Float64}(a, b, c). Returns Tuple of the anglie bisector equations in same format.

source
FusionSyntheticDiagnostics.compute_intersectionFunction
compute_intersection(
    line1::SVector{3, Float64},
    line2::SVector{3, Float64},

)::SVector{2, Float64}

Compute intersection point of two infinite lines. Return nothing if they are parallel. The lines are represented by the equation $a x + b y + c = 0$ and stored as SVector{3, Float64}(a, b, c).

source
compute_intersection(
    p1::SVector{2, Float64},
    p2::SVector{2, Float64},
    line::SVector{3, Float64},

)::SVector{2, Float64}

Compute intersection point of a finite line segment and an infinite line. Return nothing if they are parallel or the intersection point is outside the line segment. The line segment is represented by two points and the infinite line is represented by the equation $a x + b y + c = 0$ and stored as SVector{3, Float64}(a, b, c).

source
compute_intersection(
    p1::SVector{2, Float64},
    p2::SVector{2, Float64},
    line_0::SVector{3, Float64},
    line_dir::SVector{3, Float64},
)::SVector{2, SVector{3, Float64}}

Compute intersection point of a finite surface and an infinite line. Return SVector{2, Float64}(NaN, NaN) for no intersection. The finite surface is represented by two points p1 and p2 on x-z plane and created by rotating the line segment formed between p1 and p2 around the z-axis.

Thus, finite 2D surface in 3 dimensions is given by points:

x = (p1[1] * t + p2[1] * (1 - t)) * cos(ϕ)
y = (p1[1] * t + p2[1] * (1 - t)) * sin(ϕ)
z = p1[2] * t + p2[2] * (1 - t)

where t is in [0, 1] and ϕ is in [0, 2 * pi).

The infinite line is represented by an initial point line_0 and direction line_dir. The line is given by the equation:

x = line_0[1] + line_dir[1] * s
y = line_0[2] + line_dir[2] * s
z = line_0[3] + line_dir[3] * s

where s is a real number.

It uses computeintersections to compute the intersection points and returns the XYZ coordinates of the intersection points.

source

Interferometer

Synthetic interferometer can be added using IMAS compatible JSON file that describes the metadata and the line of sight of chords. On computation, the interferometer uses edge profiles and core profiles data in the IMAS IDS to numerically integrate electron density along the line of sight and returns data in IMAS IDS interferometer object.

FusionSyntheticDiagnostics.add_interferometer!Function
add_interferometer!(
    config::Union{String, Dict{Symbol, Any}}=default_ifo,
    @nospecialize(ids::IMAS.dd)=IMAS.dd();
    overwrite::Bool=false, kwargs...,
)::IMAS.dd

Add interferometer to IMAS structure using a JSON file or Julia Dict and compute the line integrated electron density if not present. kwargs are passed to compute_interferometer!.

source
FusionSyntheticDiagnostics.compute_interferometer!Function
compute_interferometer!(
    @nospecialize(ids::IMAS.dd);
    rtol::Float64=1e-3,
    n_e_gsi::Int=5,
)

Computed the line integrated electron density from the interferometer data present in IDS structure for all the chords. The computation is based on the edge profile data and core profile data present in the IDS structure. rtol is the relative tolerance for the numerical integration and n_e_gsi is the gridsubsetindex that stores the electron density data in the edge profile.

source

Langmuir Probes

Langmuir probes can be added using IMAS compatible JSON file that describes the metadata and positions of embedded or reciprocating probes. Computation is currently supported for embedded langmuir probes only which uses the edge profiles data to report the edge electron and average ion temperature and electron density. If plasma potential and probe biasing information is available, it will use a langmuir probe current model to also calculate the ion saturaton current and current density as reported by a typical probe in IMAS data format.

FusionSyntheticDiagnostics.add_langmuir_probes!Function
add_langmuir_probes!(
    config::Union{String, Dict{Symbol, Any}}=default_lp,
    @nospecialize(ids::IMAS.dd)=IMAS.dd();
    overwrite::Bool=false, kwargs...,
)::IMAS.dd

Add langmuir probes positions and other parameters from JSON file or Julia Dict to ids structure and compute langmuir probe outputs using edge profiles data. kwargs are passed to compute_langmuir_probes!.

source
FusionSyntheticDiagnostics.compute_langmuir_probes!Function
compute_langmuir_probes!(
    ids::IMAS.dd;
    v_plasma::Union{Float64, Vector{Float64}, Nothing}=nothing,
    v_floating::Union{Float64, Vector{Float64}, Nothing}=nothing,
    v_probe::Union{Float64, Vector{Float64}, Nothing}=nothing,
    v_plasma_noise::Union{Noise, Nothing}=nothing,
    Z_eff::Float64=1.0,
    m_i_amu::Float64=2.014, # Deuterium
    probe_type::Symbol=:cylindrical,
    ne_noise::Union{Noise, Nothing}=nothing,
    te_noise::Union{Noise, Nothing}=nothing,
    ti_noise::Union{Noise, Nothing}=nothing,
    n_e_gsi::Int=5,
)

Compute langmuir probe outputs for all the embedded langmuir probes in the ids structure using edge profiles data. If v_plasma or v_floating are provided along with v_probe, then langmuir_probe_current() is used to compute the ion saturation current and parallel current density and stored in the IDS. Noise models can be added for electron density and temperature and ion average temperature as power spectral densities using data type Noise.

Input arguments:

  • ids: IMAS.dd object
  • v_plasma: Plasma potential (in V) or a vector of plasma potentials for each time step
  • v_floating: Floating potential (in V) or a vector of floating potentials for each time step
  • v_probe: Probe potential (in V) or a vector of probe potentials for each time step
  • v_plasma_noise: Noise object for plasma potential
  • Z_eff: Effective charge of the ion
  • m_i_amu: Ion mass in atomic mass units (amu)
  • probe_type: Type of the probe, either :cylindrical or :spherical
  • ne_noise: Noise object for electron density
  • te_noise: Noise object for electron temperature
  • ti_noise: Noise object for average ion temperature
  • n_e_gsi: Grid subset index that is used in edge profiles data for electron density, electron temperature, and average ion temperature.
source
FusionSyntheticDiagnostics.langmuir_probe_currentFunction
langmuir_probe_current(
    Δv::Float64,
    Te::Float64,
    Ti=::Float64,
    ne::Float64,
    A::Float64,
    m_i_amu=2.04,
    Z_eff::Float64=1.0;
    probe_type::Symbol=:cylindrical,
)::Float64

Langmuir Probe current model for cylindrical and spherical probes.

For Δv <= 0:

$I_{probe} = -I_{isc} (1 - \frac{Δv}{T_i})^{ex} - I_{esc} e^{\frac{Δv}{T_e}}$

For Δv > 0:

$I_{probe} = -I_{esc} (1 + \frac{Δv}{T_e})^{ex} - I_{isc} e^{-\frac{Δv}{T_i}}$

where

$I_{isc} = \frac{1}{2} A q_i n_e \sqrt{e (T_e + T_i) / m_i}$

$I_{esc} = -\frac{1}{4} A e n_e \sqrt{8 e T_e / m_e}$

where A is the effective surface area of the probe, q_i(Z_effe) is the total charge of the ion, and ex is 1 for spherical and 0.5 for cylindrical probes.

Input arguments:

  • Δv: Bias voltage (v_probe - v_plasma)
  • Te: Electron temperature (in eV)
  • Ti: Ion temperature (in eV)
  • ne: Electron density (in $m^{-3}$)
  • A: Effective surface area of the probe (in $m^{2}$)
  • m_i_amu: Ion mass in atomic mass units (amu)
  • Z_eff: Effective charge of the ion (in units of elementary charge e)
  • probe_type: Type of the probe, either :cylindrical or :spherical

Ref: L. Conde, "An introduction to Langmuir probe diagnostics of plasmas" (2011)

source

Magnetics

Magnetics can be added using IMAS compatible JSON file that describes the metadata and positions of Magntic field probes for poloidal and toroidal fields, flux loops, Rogowski coils, and shunts. Computation is currently supported for poloidal magnetic field probes and flux loops only which uses IMAS physics flux-surfaces functions to compute the fields and report them. As such, these diagnostic implementations are only reporting stored IMAS data in the format a real diagnostic would do, but they do not involve physical modeling or noise of the diagnostic as of now.

FusionSyntheticDiagnostics.add_magnetics!Function
add_magnetics!(
    config::Union{String, Dict{Symbol, Any}},
    @nospecialize(ids::IMAS.dd)=IMAS.dd();
    overwrite::Bool=false,
    kwargs...,
)::IMAS.dd

Add magnetic diagnostics to IMAS structure using a JSON file or Julia Dict and compute their outputs kwargs are passed to compute_magnetics!.

source
FusionSyntheticDiagnostics.compute_magnetics!Function
compute_magnetics!(@nospecialize(ids::IMAS.dd)=IMAS.dd();
    equilibrium::Union{Nothing, IMAS.equilibrium}=nothing,
    kwargs...,
)

Convinience function to compute all magnetic probes if required arguments are provided in keywords.

For b_field_pol_probe and flux_loops, PSI_interpolant::AbstractInterpolation is required which can be obtained by calling IMAS.ψ_interpolant

Alternatively, if an entire equilibrium is provided in keyword argument, then this function will compute PSI_interpolant for each time slice and compute the probes for each time step.

source
compute_magnetics!(
    probes::IMAS.IDSvector{IMAS.magnetics__b_field_pol_probe{T}};
    PSI_interpolant::Union{Nothing, AbstractInterpolation}=nothing,
    time::Union{Nothing, T}=nothing,
) where {T <: Float64}

Synthetic diagnostics for the poloidal magnetic probes saved in the data structure. Returns a vector with, for each probe, the measured component of Bpol aligned with the probe. Also, updates the data structure with time and synthetic data. Data is saved at global time if time is not provided.

source
compute_magnetics!(
    loops::IMAS.IDSvector{IMAS.magnetics__flux_loop{T}};
    PSI_interpolant::Union{Nothing, AbstractInterpolation}=nothing,
    time::Union{Nothing, T}=nothing,
) where {T <: Float64}

Synthetic diagnostics for the flux loops saved in the data structure. Returns a vector with, for each loop, the measured poloidal flux. Also, updates the data structure with time and synthetic data. Data is saved at global time if time is not provided.

source

Synthetic Actuators

Gas Injection

Gas valves can be added using IMAS compatible JSON file that describes the metadata, response curve, and positions of gas valves. If ids.gas_injection.valve[:].voltage.data is present, the gas output flow rate is calculated. Additionally, if a valve model dictionary is passed, more realistic actuation can be modeled that includes second order low pass effect, latency, and dribble effect.

FusionSyntheticDiagnostics.add_gas_injection!Function
add_gas_injection!(
    config::String=default_gas_injection,
    @nospecialize(ids::IMAS.dd)=IMAS.dd();
    overwrite::Bool=false,
)::IMAS.dd

Add gas valves from a JSON file and compute the gas flow rate based on the command signal in the gas valves.

source
add_gas_injection!(
    config::Dict{Symbol, Any},
    @nospecialize(ids::IMAS.dd)=IMAS.dd();
    overwrite::Bool=false,
)::IMAS.dd

Add gas valves from a dictionary and compute the gas flow rate based on the command signal in the gas valves.

source
FusionSyntheticDiagnostics.compute_gas_injectionFunction
compute_gas_injection(
    tt::Vector{Float64},
    cmd_voltage::Vector{Float64},
    response_curve_voltage::Vector{Float64},
    response_curve_flow_rate::Vector{Float64};
    valve_model::Dict{Symbol, Any}=Dict{Symbol, Any}(),
    global_latency::Float64=0.0,
)::Tuple{Vector{Float64}, Vector{Float64}, Union{Nothing, Vector{Float64}}}

Lowest level function to compute gas flow rate based on the command voltage data and response cruve data all provided in base Julia types.

source
compute_gas_injection(
    tt::Vector{Float64},
    cmd_voltage::Vector{Float64},
    response_curve::IMAS.gas_injection__valve___response_curve;
    valve_model::Dict{Symbol, Any}=Dict{Symbol, Any}(),
    global_latency::Float64=0.0,
)::Tuple{Vector{Float64}, Vector{Float64}, Union{Nothing, Vector{Float64}}}

Convinience function format where response curve is provided as the ids type but everything else is provided in base Julia types.

source
compute_gas_injection(
    valve::IMAS.gas_injection__valve;
    valve_model::Dict{Symbol, Any}=Dict{Symbol, Any}(),
    global_latency::Float64=0.0,
)::Tuple{Vector{Float64}, Vector{Float64}, Union{Nothing, Vector{Float64}}}

Top most level function to compute gas flow rate for a single valve.

source
FusionSyntheticDiagnostics.compute_gas_injection!Function
compute_gas_injection!(
    ids::IMAS.dd;
    valves::Dict{String, Dict{Symbol, Any}}=Dict{String, Dict{Symbol, Any}}(),
)::Array{Vector{Float64}}

Compute the gas flow rate based on the command signal in the all gas valves.

source
compute_gas_injection!(
    valve::IMAS.gas_injection__valve;
    valve_model::Dict{Symbol, Any}=Dict{Symbol, Any}(),
    global_latency::Float64=0.0,
)::Union{Nothing, Vector{Float64}}

In-place version of compute_gas_injection function for a single valve.

source
FusionSyntheticDiagnostics.get_lpfFunction
get_lpf(
    fs::Float64,
    tau::Float64,
    damping::Float64,
    gain::Float64,
)::SecondOrderSections

Create a second order filter with the given parameters. The filter is created in the s-domain and then converted to the z-domain using bilinear transform. In s-domain, the filter transfer function is:

$H(s) = k \frac{ωₙ^2}{ s^2 + 2 \gamma ωₙ s + ωₙ^2}$

where ωₙ = 2π / tau, $\gamma$ is damping and k is gain.

source
FusionSyntheticDiagnostics.dribbleFunction
dribble(
    data::Vector{Float64},
    decay_time_constant::Float64,
    fs::Float64,
)::Vector{Float64}

Function to model dribble effect when gas command falls too sharply due to remaining gas in the pipe. The function compares change in data(ΔD) at every time step with

$ΔD < -\frac{1}{τ f_s} e^{ - \left( \frac{1}{τ f_s} \right) }$

where τ(decay_time_constant) is the decay time constant and $f_s$(f_s) is the sampling frequency. If the above condition is found true, the function forces data to decay exponentially with the decay time constant untill the condition is false. The function returns the modified data.

source
FusionSyntheticDiagnostics.downsample_smoothFunction
downsample_smooth(
    tt::Vector{Float64},
    data::Vector{Float64},
    dt_res::Float64;
    default_start=0.0,
)::Vector{Float64}

Downsample and smooth the data to a new time resolution. The time axis does not need to be equally spaced in time. The function creates a new time axis with spacing given by dt_res and then averages the data points that fall within the new time bin. If no data points fall within the new time bin, the function uses the last data point to fill the new time bin. default_start is used to fill first time bin if no data is present in the first time bin.

source
downsample_smooth(
    tt::Vector{Float64},
    data::Vector{Float64},
    tt_res::Vector{Float64};
    default_start=0.0,
)::Vector{Float64}

Downsample and smooth the data to a new time resolution. The time axis does not need to be equally spaced in time. The function uses tt_res as the new resampled time axis and averages the data points that fall within the new time bin. If no data points fall within the new time bin, the function uses the last data point to fill the new time bin. default_start is used to fill first time bin if no data is present in the first time bin. Note that tt_res need not be equally spaced in time either.

source
FusionSyntheticDiagnostics.find_delayFunction
find_delay(
    data1::Vector{Float64},
    data2::Vector{Float64},
    dt::Float64,
)::Float64

Find the delay between two signals using cross-correlation. The function returns the delay in seconds. The function assumes that data1 is the reference signal and data2 is the signal that is delayed with respect to data1. The funciton assumes that data1 and data2 are sampled at the same rate and are of same length. dt is the time resolution of the data.

source
FusionSyntheticDiagnostics.get_gas_injection_responseFunction
get_gas_injection_response(
    cmd::Vector{Float64},
    cmd_tt::Vector{Float64},
    P_ves::Vector{Float64},
    P_ves_tt::Vector{Float64},
    V_ves::Float64;
    resample_dt::Float64=0.02,
    gi_fit_guess::Vector{Float64}=[1.0, 1.0],
    response_curve_voltage::Vector{Float64}=collect(0:0.001:11),
)::Tuple{IMAS.gas_injection__valve___response_curve, Dict{Symbol, Any}}

Function to fit a gas calibration shot data to a gas injection model. It requires 2 set of data:

  • cmd, and cmd_tt are the command sent in Volts and the corresponding time axis.
  • P_ves and P_ves_tt are the pressure in the vessel and the corresponding time axis.

V_ves is the volume of the vessel. The function returns a tuple of response_curve object and a valve model dictionary that can be used in compute_gas_injection! or compute_gas_injection functions.

Optional keyword arguments:

  • resample_dt: Time resolution to resample the data.
  • gi_fit_guess: Initial guess for the gas injection model parameters.
  • response_curve_voltage: Voltage axis for storing the response curve.
source
FusionSyntheticDiagnostics.gi_modelFunction
gi_model(x::Vector{Float64}, p::Vector{Float64})::Vector{Float64}

Non linear gas injection model. The model is given by:

$\Gamma = p_1 (\sqrt{x^2 p_2^2 + 1} - 1)$

where x is the input voltage in Volts and $\Gamma$ is the flow rate in Pa m³/ s

source
FusionSyntheticDiagnostics.int_gi_modelFunction
int_gi_model(x::Vector{Float64}, p::Vector{Float64})::Vector{Float64}

Cumulative sum of gas injection model. This function models the accumulated gas in the vessel volume. Note that this is a numerical cumulative sum and does not change the units of output. Returned output is in Pa m³/ s.

source
FusionSyntheticDiagnostics.get_required_gas_cmdFunction
get_required_gas_cmd(
        required_flow_rate::Float64,
        response_curve::IMAS.gas_injection__valve___response_curve,
    )::Float64

Function to get the required gas command voltage to achieve the required flow rate.

source
get_required_gas_cmd(
    required_flow_rate::Vector{Float64},
    response_curve::IMAS.gas_injection__valve___response_curve,
)::Vector{Float64}

Function to get the required gas command voltage to achieve the required flow rate.

source

Noise model

FusionSyntheticDiagnostics.NoiseType
Noise

A structure to represent a noise model. It holds power spectral density of noise and/or the time series of noise. The power spectral density is stored as a DSP.Periodograms.Periodogram object.

Constructors:

Noise(time::Vector{Float64}, signal::Vector{Float64}, binwidth::Float64)

Create a noise model from a time series of noise. The power spectral density is calculated and stored for regenaeration of noise in future.

Noise(pgram::DSP.Periodograms.Periodogram)

Create a noise model from a power spectral density data given as a DSP.Periodograms.Periodogram object.

Noise(power_spectrum::Vector{Float64}, freq::AbstractRange)

Create a noise model from a power spectral density data given as a vector along with frequency values. The power spectral density is stored as a DSP.Periodograms.Periodogram calculated from the input data.

source
FusionSyntheticDiagnostics.generate_noiseFunction
generate_noise(n::Noise, t::Vector{Float64})::Vector{Float64}

Generate a noise signal based on the power spectral density stored in the noise model that corresponds to the time axis t. The noise signal is generated by summing up the cosine waves with uniformly distributed random phase and amplitude based on the power spectral density.

source