Adding diagnostics to a MIDAS analysis
To include a diagnostic in a MIDAS analysis, we need to create a DiagnosticLikelihood object. MIDAS abstracts the definition of a diagnostic likelihood into two parts:
A DiagnosticModel object, which implements (or calls) the forward-model for the diagnostic, and specifies what information is required to evaluate the model predictions (e.g. the values of plasma fields like temperature or density at specific coordinates).
A LikelihoodFunction object, which holds the experimental measurements and uncertainties, and specifies a distribution used to model the uncertainties (e.g. Gaussian, logistic etc.)
Specifying diagnostic models
For a diagnostic forward model to evaluate its predictions, it will require information about the physical state of the system it is measuring, and potentially information regarding the diagnostic itself, such as calibration values or background levels.
In MIDAS, the information required by models is grouped into two categories: ‘parameters’ and ‘fields’.
Requesting field values
‘fields’ are the values of physical quantities at particular spatial coordinates. For example, a model of a Thomson scattering diagnostic may require the values of both the electron temperature and density at a set of \((R, z)\) coordinates. To specify which field values are required by a diagnostic, we create instances of the FieldRequest class for each required field, and then pass those instances as arguments to the Fields class:
from numpy import linspace, full
from midas import FieldRequest, Fields
# example measurement positions for a Thomson-scattering diagnostic
R_ts = linspace(0.3, 1.6, 131)
z_ts = full(131, fill_value=0.01)
# Request the electron temperature and density field values at these positions
fields = Fields(
FieldRequest(name="T_e", coordinates={"radius": R_ts, "z": z_ts}),
FieldRequest(name="n_e", coordinates={"radius": R_ts, "z": z_ts}),
)
Specifying required parameters
‘parameters’ capture any other information required to evaluate the diagnostic model, but are typically used to specify properties of the instrument itself. The parameters required by a model are specified by creating instances of the ParameterVector class, which are then passed as arguments to the Parameters class:
from midas import Parameters, ParameterVector
parameters = Parameters(
ParameterVector(name="calibration_value", size=1),
ParameterVector(name="background_line_coeffs", size=2),
)
Defining a diagnostic
Diagnostic models in midas are defined as classes, and must meet three
requirements:
The class must inherit from the DiagnosticModel abstract base-class, and therefore implement the required
midas.models.DiagnosticModel.predictionsandpredictions_and_jacobiansmethods.Instances of the class must have a parameters instance attribute, which is an instance of the Parameters class.
Instances of the class must have a fields instance attribute, which is an instance of the Fields class.
For example, a simple straight-line model would not require any field values, but would require parameters to define the gradient and offset:
from numpy import ndarray, ones_like
from midas.models import DiagnosticModel
from midas import Parameters, ParameterVector, Fields
class StraightLine(DiagnosticModel):
def __init__(self, x_axis: ndarray):
self.x = x_axis
self.parameters = Parameters(
ParameterVector(name="gradient", size=1),
ParameterVector(name="y_intercept", size=1),
)
self.fields = Fields()
def predictions(self, gradient: float, y_intercept: float) -> ndarray:
return gradient * self.x + y_intercept
def predictions_and_jacobians(self, gradient: float, y_intercept: float) -> tuple:
predictions = gradient * self.x + y_intercept
jacobians = {"gradient": self.x, "y_intercept": ones_like(self.x)}
return predictions, jacobians
Specifying likelihood functions
Likelihood functions are models for the uncertainties associated with the diagnostic measurements. Classes for commonly used likelihood functions, such as the Gaussian and logistic distributions, are available in the midas.likelihoods module.
Likelihood function classes encapsulate the experimental measurements and their associated uncertainties, so these data must be passed as arguments. For example, creating an instance of GaussianLikelihood could look like this:
from midas.likelihoods import GaussianLikelihood
gaussian_likelihood = GaussianLikelihood(
y_data=measurement_values,
sigma=measurement_uncertainties,
)
Creating a DiagnosticLikelihood
Combining the previous examples of a straight-line model and a Gaussian likelihood, we can create an instance of DiagnosticLikelihood:
from midas.likelihoods import DiagnosticLikelihood
straight_line_model = StraightLine(x_axis=measurement_positions)
straight_line_likelihood = DiagnosticLikelihood(
diagnostic_model=straight_line_model,
likelihood=gaussian_likelihood,
name="straight_line"
)