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.predictions and predictions_and_jacobians methods.

  • 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"
)