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!
— Functionadd_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!
.
FusionSyntheticDiagnostics.compute_bolometer!
— Functioncompute_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.
Several useful geometrical functions are defined and used here.
FusionSyntheticDiagnostics.FoV
— TypeFoV
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.
FusionSyntheticDiagnostics.get_FoV
— Functionget_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.
FusionSyntheticDiagnostics.get_line
— Functionget_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.
FusionSyntheticDiagnostics.get_angle
— Functionget_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)
.
FusionSyntheticDiagnostics.get_angle_bisector
— Functionget_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.
FusionSyntheticDiagnostics.compute_intersection
— Functioncompute_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)
.
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)
.
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.
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!
— Functionadd_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!
.
FusionSyntheticDiagnostics.compute_interferometer!
— Functioncompute_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.
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!
— Functionadd_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!
.
FusionSyntheticDiagnostics.compute_langmuir_probes!
— Functioncompute_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 objectv_plasma
: Plasma potential (in V) or a vector of plasma potentials for each time stepv_floating
: Floating potential (in V) or a vector of floating potentials for each time stepv_probe
: Probe potential (in V) or a vector of probe potentials for each time stepv_plasma_noise
: Noise object for plasma potentialZ_eff
: Effective charge of the ionm_i_amu
: Ion mass in atomic mass units (amu)probe_type
: Type of the probe, either :cylindrical or :sphericalne_noise
: Noise object for electron densityte_noise
: Noise object for electron temperatureti_noise
: Noise object for average ion temperaturen_e_gsi
: Grid subset index that is used in edge profiles data for electron density, electron temperature, and average ion temperature.
FusionSyntheticDiagnostics.langmuir_probe_current
— Functionlangmuir_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_eff
e) 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)
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!
— Functionadd_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!
.
FusionSyntheticDiagnostics.compute_magnetics!
— Functioncompute_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.
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.
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.
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!
— Functionadd_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.
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.
FusionSyntheticDiagnostics.compute_gas_injection
— Functioncompute_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.
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.
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.
FusionSyntheticDiagnostics.compute_gas_injection!
— Functioncompute_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.
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.
FusionSyntheticDiagnostics.get_lpf
— Functionget_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
.
FusionSyntheticDiagnostics.dribble
— Functiondribble(
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
.
FusionSyntheticDiagnostics.downsample_smooth
— Functiondownsample_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.
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.
FusionSyntheticDiagnostics.find_delay
— Functionfind_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.
FusionSyntheticDiagnostics.get_gas_injection_response
— Functionget_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
, andcmd_tt
are the command sent in Volts and the corresponding time axis.P_ves
andP_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.
FusionSyntheticDiagnostics.gi_model
— Functiongi_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
FusionSyntheticDiagnostics.int_gi_model
— Functionint_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.
FusionSyntheticDiagnostics.get_required_gas_cmd
— Functionget_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.
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.
Noise model
FusionSyntheticDiagnostics.Noise
— TypeNoise
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.
FusionSyntheticDiagnostics.generate_noise
— Functiongenerate_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.
FusionSyntheticDiagnostics.generate_noise!
— Functiongenerate_noise!(n::Noise, t::Vector{Float64})::Noise
In-place version of generate_noise()
.