ionics-fits API

Common

class ionics_fits.common.Fitter(x: float | List | ndarray, y: float | ndarray, model: Model)

Base class for fitters.

Fitters perform maximum likelihood parameter estimation on a dataset under the assumption of a certain model and statistics (normal, binomial, etc) and store the results as attributes.

For details about the various fitter subclasses provided by ionics_fits, see Fitters.

Usage

Basic usage:

import numpy as np
from matplotlib import pyplot as plt

from ionics_fits.models.polynomial import Line
from ionics_fits.normal import NormalFitter

a = 3.2
y0 = -9

x = np.linspace(-10, 10)
y = a * x + y0

fit = NormalFitter(x, y, model=Line())
print(f"Fitted: y = {fit.values['a']:.3f} * x + {fit.values['y0']:.3f}")

plt.plot(x, y)
plt.plot(*fit.evaluate())
plt.show()

The fit may be configured by modifying the Model‘s parameters dictionary. This allows one to:

  • set upper and lower bounds for each parameter

  • control which parameters are fixed / floated

  • provide user estimates to be used instead of the Model‘s heuristics

As an example, let’s fit a sinusoid, whose frequency is already known:

import numpy as np
from matplotlib import pyplot as plt

from ionics_fits.models.sinusoid import Sinusoid
from ionics_fits.normal import NormalFitter

omega = 2 * np.pi
model = Sinusoid()
model.parameters["omega"].fixed_to = omega

params = {
    "a": 2,
    "omega": omega,
    "phi": 0.5 * np.pi,
    "y0": 0,
    "x0": 0,
    "tau": np.inf,
}

x = np.linspace(-3, 3, 100)
y = model(x, True, **params)

fit = NormalFitter(x, y, model=model)
print(f"Amplitude: dataset = {params['a']:.3f}, fit = {fit.values['a']:.3f}")
print(f"Phase: dataset = {params['phi']:.3f}, fit = {fit.values['phi']:.3f}")

plt.plot(*fit.evaluate(None, True), '-.o', label="fit")
plt.plot(x, y, label="data")
plt.grid()
plt.legend()
plt.show()
Amplitude: dataset = 2.000, fit = 2.000
Phase: dataset = 1.571, fit = 1.571

ionics_fits supports fitting datasets with arbitrary x-axis and y-axis dimensions. The transformations module provides a number of classes which allow higher-dimensional models to be constructed from lower-dimension models.

Example of fitting a dataset with a 2D x-axis:

import numpy as np
from matplotlib import pyplot as plt

from ionics_fits.models.multi_x import Gaussian2D
from ionics_fits.normal import NormalFitter

omega = 2 * np.pi  # we know the frequency
model = Gaussian2D()

params = {
    "x0_x0": 0,
    "x0_x1": 3,
    "sigma_x0": 2,
    "sigma_x1": 3,
    "y0": 0,
    "a": 9,
}

x_0_ax = np.linspace(-3, 3, 50)
x_1_ax = np.linspace(-10, 10, 100)
x_0_mesh, x_1_mesh = np.meshgrid(x_0_ax, x_1_ax)
x_shape = x_0_mesh.shape

x = np.vstack((x_0_mesh.ravel(), x_1_mesh.ravel()))
y = model(x, **params)

fit = NormalFitter(x, y, model=model)

_, y_fit = fit.evaluate(x)
y_fit = y_fit.reshape(x_shape)

_, axs = plt.subplots(2, 1)
axs[0].pcolormesh(x_0_mesh, x_1_mesh, y.reshape(x_shape))
axs[1].pcolormesh(x_0_mesh, x_1_mesh, y_fit)
axs[0].title.set_text("Model")
axs[1].title.set_text("Fit")
axs[0].grid()
axs[1].grid()
plt.show()

As an example of fitting a dataset with a 2D y-axis, here’s how to fit Rabi flopping on a pair of qubits. We’ll assume all parameters are the same for the two qubits, other than the Rabi frequencies:

import pprint
import numpy as np
from matplotlib import pyplot as plt

from ionics_fits.models.rabi import RabiFlopTime
from ionics_fits.models.transformations.repeated_model import RepeatedModel
from ionics_fits.normal import NormalFitter

params = {
    "omega_0": 2 * np.pi * 1,
    "omega_1": 2 * np.pi * 2,
    "delta": 0,
    "P_readout_e": 1,
    "P_readout_g": 0
}

rabi_model = RabiFlopTime(start_excited=False)
model = RepeatedModel(
    model=rabi_model,
    num_repetitions=2,
    common_params=[
        param_name for param_name in rabi_model.parameters.keys()
        if param_name != "omega"
    ]
)

t = np.linspace(0, 3, 100)
y = model(t, **params)

fit = NormalFitter(t, y, model)

pprint.pprint(fit.values)

plt.plot(t, y.T, ".")
plt.legend(("qubit 0", "qubit 1"))
plt.gca().set_prop_cycle(None)
plt.plot(*fit.evaluate(None, True))
plt.grid()
plt.show()
{'P_readout_e': np.float64(0.9999999999),
 'P_readout_g': np.float64(1e-10),
 'delta': np.float64(0.0),
 'omega_0': np.float64(6.283185307179586),
 'omega_1': np.float64(12.56637061436225),
 't_dead': np.float64(0.0),
 'tau': np.float64(inf)}

Class Attributes

x

x-axis data. The input data is sorted along the x-axis dimensions and filtered to contain only the “valid” points where x and y are finite.

Type:

float | List | numpy.ndarray

y

y-axis data. The input data is sorted along the x-axis dimensions x and filtered to contain only the “valid” points where x and y are finite.

Type:

float | numpy.ndarray

sigma

standard errors for each point. This is stored as an array with the same shape as y.

Type:

float | numpy.ndarray | None

values

dictionary mapping model parameter names to their fitted values

Type:

Dict[str, float]

uncertainties

dictionary mapping model parameter names to their fit uncertainties. For sufficiently large datasets, well-formed problems and ignoring covariances these are the 1-sigma confidence intervals (roughly: there is a 1/3 chance that the true parameter values differ from their fitted values by more than this much). These uncertainties are generally only useful when the covariances are small.

Type:

Dict[str, float]

derived_values

dictionary mapping names of derived parameters (parameters which are not part of the fit, but are calculated by the model from the fitted parameter values) to their values

Type:

Dict[str, float]

derived_uncertainties

dictionary mapping names of derived parameters to their fit uncertainties

Type:

Dict[str, float]

initial_values

dictionary mapping model parameter names to the initial values used to seed the fit.

Type:

Dict[str, float]

model

the fit model

Type:

ionics_fits.common.Model

covariances

matrix of covariances between the floated parameters. The ordering of parameters in the covariance matrix matches free_parameters. The fit uncertainties are calculated as the square root of the diagonals of the covariance matrix.

Type:

numpy.ndarray

free_parameters

list of names of the model parameters floated during the fit

Type:

List[str]

x_scales

the applied x-axis scale factors

Type:

numpy.ndarray

y_scales

the applied y-axis scale factors

Type:

numpy.ndarray

__init__(x: float | List | ndarray, y: float | ndarray, model: Model)

Fits a model to a dataset and stores the results.

Parameters:
  • x – x-axis data. For models with more than one x-axis dimension, x should be in the form (num_x_axes, num_samples).

  • y – y-axis data.For models with more than one y-axis dimension, y should be in the form (num_y_axes, num_samples).

  • model – the model function to fit to. The model’s parameter dictionary is used to configure the fit (set parameter bounds etc). Modify this before fitting to change the fit behaviour from the model class’ defaults. The model is (deep) copied and stored as an attribute.

_fit(x: float | List | ndarray, y: float | ndarray, parameters: Dict[str, ModelParameter], free_func: Callable[[...], float | ndarray]) Tuple[Dict[str, float], Dict[str, float], ndarray]

Implementation of the parameter estimation.

Fitter implementations must override this method to provide a fit with appropriate statistics.

Parameters:
  • x – rescaled x-axis data, must be a 1D array

  • y – rescaled y-axis data

  • parameters – dictionary of rescaled model parameters

  • free_func – convenience wrapper for the model function, taking only values for the fit’s free parameters

Returns:

tuple of dictionaries mapping model parameter names to their fitted values and uncertainties.

calc_sigma() float | ndarray | None

Returns an array of standard error values for each y-axis data point

Subclasses must override this.

evaluate(x_fit: float | List | ndarray | None = None, transpose_and_squeeze=False) Tuple[float | List | ndarray, float | ndarray]

Evaluates the model function using the fitted parameter set.

Parameters:
  • x_fit – optional x-axis points to evaluate the model at. If None, we use the stored value of x.

  • transpose_and_squeeze – if True, the results arrays are transposed and squeezed prior to being returned. This is intended to be used for plotting, since matplotlib requires different y-series to be stored as columns.

Returns:

tuple of x-axis values used and corresponding y-axis found by evaluating the model.

residuals() float | ndarray

Returns an array of fit residuals.

class ionics_fits.common.Model(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Base class for fit models.

A model groups a function to be fitted with associated metadata (parameter names, default bounds etc) and heuristics. It is agnostic about the method of fitting or the data statistics.

Models may be used either as part of a fit (see Fitter) or as a standalone function (see __call__()).

Class Attributes

parameters

dictionary mapping parameter names to ModelParameter s. The parameters may be modified to alter their properties (bounds, user estimates, etc.).

Type:

Dict[str, ionics_fits.common.ModelParameter]

internal_parameters

list of “internal” model parameters. These are not directly used during the fit, but are rescaled in the same way as regular model parameters. These are used, for example, by transformations models.

Type:

List[ionics_fits.common.ModelParameter]

__call__(x: float | List | ndarray, transpose_and_squeeze=False, **kwargs: float) float | ndarray

Evaluates the model at a given set of x-axis points and with a given set of parameter values and returns the results.

Example:

import numpy as np
from matplotlib import pyplot as plt
from ionics_fits.models.sinusoid import Sinusoid

model = Sinusoid()
x = np.linspace(0, 1)
y = model(x, True, a=1, omega=2*np.pi, phi=0, y0=0)
plt.plot(x, y)
Parameters:
  • x – x-axis data. For models with more than one x-axis, the data should be shaped (num_x_axes, num_samples). For models with a single x-axis dimension, a 1D array may be used instead.

  • transpose_and_squeeze – if True, the results arrays are transposed and squeezed proior to being returned. This is intended to be used for plotting, since matplotlib requires different y-series to be stored as columns.

  • **kwargs – values for model parameters. All model parameters which are not fixed_to a value must be specified. Any parameters which are not specified default to their fixed values.

Returns:

the model function values

__init__(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)
Parameters:
  • parameters – optional dictionary mapping parameter names to ModelParameters. This should be None (default) if the model has a static set of parameters, in which case the parameter dictionary is generated from the call signature of _func(). The model parameters are stored as parameters and may be modified after construction to change the model behaviour during fitting (e.g. to change the bounds, fixed parameters, etc).

  • internal_parameters – optional list of “internal” model parameters, which are not exposed to the user as arguments of func(). Internal parameters are rescaled in the same way as regular model parameters, but are not otherwise used by Model. These are used by transformations models, which modify the behaviour of other models.

_func(x: float | List | ndarray) float | ndarray

Evaluates the model at a given set of x-axis points and with a given set of parameter values and returns the results.

Overload this in preference to func() unless the Model takes a dynamic set of parameters.

ModelParameters should be used as the type annotations for the parameters arguments to define the model’s parameters. These are used in the construction to generate the parameters dictionary:

from ionics_fits.utils import scale_x, scale_y

def _func(
    self,
    x,
    a: ModelParameter(lower_bound=-1., scale_func=scale_y()),
    x0: ModelParameter(fixed_to=0, scale_func=scale_x())
):
    ...
Parameters:

x – x-axis data

Returns:

array of model values

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Returns dictionaries of values and uncertainties for the derived model parameters (parameters which are calculated from the fit results rather than being directly part of the fit) based on values of the fitted parameters and their uncertainties.

Parameters:
  • x – x-axis data

  • y – y-axis data

  • fitted_params – dictionary mapping model parameter names to their fitted values.

  • fit_uncertainties – dictionary mapping model parameter names to their fit uncertainties.

Returns:

tuple of dictionaries containing the derived parameter values and uncertainties.

can_rescale() Tuple[List[bool], List[bool]]

Returns a tuple of lists of bools specifying whether the model can be rescaled along each x- and y-axes dimension.

clear_heuristics()

Clear the heuristics for all model parameters (both exposed and internal).

This is mainly used in transformations-type models where the parameter estimator my be run multiple times for the same model instance.

estimate_parameters(x: float | List | ndarray, y: float | ndarray)

Set heuristic values for model parameters.

Typically called by Fitter.

Implementations of this method must ensure that all parameters have an initial value set (at least one of fixed_to, user_estimate or heuristic must not be None for each parameter).

Implementations should aim to make use of all information supplied by the user (bounds, user estimates, fixed values) to provide the best initial guesses for all parameters.

The x and y data is sorted along the x-axis dimensions and is filtered to remove points with non-finite x or y values and are is rescaled if supported by the model.

Parameters:
  • x – x-axis data

  • y – y-axis data

func(x: float | List | ndarray, param_values: Dict[str, float]) float | ndarray

Evaluates the model at a given set of x-axis points and with a given set of parameter values and returns the result.

To use the model as a function outside of a fit, __call__() generally provides a more convenient interface.

Overload this to provide a model function with a dynamic set of parameters, otherwise prefer to override _func().

Parameters:
  • x – x-axis data

  • param_values – dictionary of parameter values

Returns:

array of model values

get_num_x_axes() int

Returns the number of x-axis dimensions the model has.

get_num_y_axes() int

Returns the number of y-axis dimensions the model has.

rescale(x_scales: ndarray, y_scales: ndarray)

Rescales the model parameters based on the specified x and y data scale factors.

All parameters and internal_parameters are rescaled.

Parameters:
  • x_scales – array of x-axis scale factors

  • y_scales – array of y-axis scale factors

unscale()

Disables rescaling of the model parameters.

class ionics_fits.common.ModelParameter(scale_func: Callable[[ndarray, ndarray], float], lower_bound: float = -inf, upper_bound: float = inf, fixed_to: float | None = None, user_estimate: float | None = None, heuristic: float | None = None)

Represents a model parameter.

scale_func

callable returning a scale factor which the parameter must be multiplied by if it was fitted using x and y data that has been multiplied by the given scale factors. Scale factors are used to improve numerical stability by avoiding asking the optimizer to work with very large or very small values of x and y. The callable takes the x-axis and y-axis scale factors as arguments. A number of default scale functions are provided for convenience in ionics_fits.utils.

Type:

Callable[[numpy.ndarray, numpy.ndarray], float]

lower_bound

lower bound for the parameter. Fitted values are guaranteed to be greater than or equal to the lower bound. Parameter bounds may be used by fit heuristics to help find good starting points for the optimizer.

Type:

float

upper_bound

upper bound for the parameter. Fitted values are guaranteed to be lower than or equal to the upper bound. Parameter bounds may be used by fit heuristics to help find good starting points for the optimizer.

Type:

float

fixed_to

if not None, the model parameter is fixed to this value during fitting instead of being floated. This value may additionally be used by the heuristics to help find good initial values for other model parameters. The value of fixed_to must lie within the bounds of the parameter.

Type:

float | None

user_estimate

if not None and the parameter is not fixed, this value is used as an initial value during fitting rather than obtaining a value from the heuristics. This value may additionally be used by the heuristics to help find good initial values for other model parameters. The value of user_estimate must lie within the bounds of the parameter.

Type:

float | None

heuristic

if both of fixed_to and user_estimate are None, this value is used as an initial value during fitting. It is set by the Model’ s estimate_parameters() method and should not be set by the user.

Type:

float | None

__init__(scale_func: Callable[[ndarray, ndarray], float], lower_bound: float = -inf, upper_bound: float = inf, fixed_to: float | None = None, user_estimate: float | None = None, heuristic: float | None = None) None
clip(value: float) float

Clips a value to lie between the parameter’s lower and upper bounds.

Parameters:

value – value to be clipped

Returns:

clipped value

get_initial_value(default: float | None = None) float

Returns the parameter’s initial value.

For fixed parameters, this is the value the parameter is fixed to. For floated parameters, it is the value used to seed the fit. In the latter case, the initial value is retrieved from the user_estimate if available, otherwise the heuristic is used.

Parameters:

default – optional value to use if no other value is available

has_initial_value() bool

Returns True if the parameter is fixed, has a user estimate or a heuristic.

has_user_initial_value() bool

Returns True if the parameter is fixed or has a user estimate

rescale(x_scales: ndarray, y_scales: ndarray)

Rescales the parameter metadata based on the specified x and y data scale factors.

Rescaling affects the values of lower_bound, upper_bound, fixed_to, user_estimate, and heuristic.

Parameters:
  • x_scales – array of x-axis scale factors

  • y_scales – array of y-axis scale factors

unscale()

Disables rescaling of the parameter metadata

Utils

class ionics_fits.utils.Array

Subclass of numpy’s NDArray used purely for type annotation convenience.

Type annotations can have arbitrary subscripts, e.g. Array[(4,), "float64"].

class ionics_fits.utils.ArrayLike

Used for types that can be a numpy array or a list that’s then converted to an array

ionics_fits.utils.scale_invariant(x_scales: ndarray, y_scales: ndarray) float

Scale function for ModelParameters whose value is invariant under rescaling of the x- and y-axes

Parameters:
  • x_scales – array of x-axis scale factors

  • y_scales – array of y-axis scale factors

Returns:

1

ionics_fits.utils.scale_no_rescale(x_scales: ndarray, y_scales: ndarray) float

Scale function for ModelParameters which cannot be rescaled.

Raises a RuntimeError if any of the x-axis or y-axis scale factors are not equal to 1.

Parameters:
  • x_scales – array of x-axis scale factors

  • y_scales – array of y-axis scale factors

Returns:

1.

ionics_fits.utils.scale_power(x_power: int, y_power: int, x_axis: int = 0, y_axis: int = 0) Callable[[ndarray, ndarray], float]

Returns a scale function for ModelParameters whose value scales as a function of one x-axis and one y-axis dimension.

The parameter scale factor is calculated as:

scale_factor = (x_scales[x_axis] ** x_power) * (y_scales[y_axis] ** y_power)
Parameters:
  • x_power – x-axis power

  • y_power – y-axis power

  • x_axis – index of the x-axis dimension the parameter scales with

  • y_axis – index of the y-axis dimension the parameter scales with

Returns:

scale function

ionics_fits.utils.scale_undefined(x_scales: ndarray, y_scales: ndarray) float

Scale function for ModelParameters whose scaling is not known yet.

This scale function is typically used for parameters whose scale factor is not known until runtime.

Parameters:
  • x_scales – array of x-axis scale factors

  • y_scales – array of y-axis scale factors

ionics_fits.utils.scale_x(x_axis: int = 0) Callable[[ndarray, ndarray], float]

Returns a scale function for ModelParameters whose value scales linearly with one x-axis dimension.

Parameters:

x_axis – index of the x-axis dimension the parameter scales with

Returns:

scale function

ionics_fits.utils.scale_x_inv(x_axis: int = 0) Callable[[ndarray, ndarray], float]

Returns a scale function for ModelParameters whose value scales inversely with one x-axis dimension.

Parameters:

x_axis – index of the x-axis dimension the parameter scales with

Returns:

scale function

ionics_fits.utils.scale_y(y_axis: int = 0) Callable[[ndarray, ndarray], float]

Returns a scale function for ModelParameters whose value scales linearly with one y-axis dimension.

Parameters:

y_axis – index of the y-axis dimension the parameter scales with

Returns:

scale function

Fitters

class ionics_fits.normal.NormalFitter(x: float | List | ndarray, y: float | ndarray, model: Model, sigma: float | ndarray | None = None, curve_fit_args: Dict | None = None)

Fitter for Normally-distributed data.

We use least-squares fitting as a maximum-likelihood parameter estimator for normally distributed data. For data that is close to normal this is usually a pretty good approximation of a true MLE estimator.

See Fitter for further details.

__init__(x: float | List | ndarray, y: float | ndarray, model: Model, sigma: float | ndarray | None = None, curve_fit_args: Dict | None = None)

Fits a model to a dataset and stores the results.

Parameters:
  • x – x-axis data

  • y – y-axis data

  • sigma – optional y-axis standard deviations.

  • model – the model function to fit to. The model’s parameter dictionary is used to configure the fit (set parameter bounds etc). Modify this before fitting to change the fit behaviour from the model class’ defaults. The model is (deep) copied and stored as an attribute.

  • curve_fit_args – optional dictionary of keyword arguments to be passed into scipy.curve_fit.

calc_sigma() float | List | ndarray | None

Returns an array of standard error values for each y-axis data point if available.

chi_squared(x: float | List | ndarray, y: float | ndarray, sigma: float | ndarray) float

Returns the Chi-squared fit significance for the fitted model compared to a given dataset as a number between 0 and 1.

The significance gives the probability that fit residuals as large as the ones we observe could have arisen through chance given our assumed statistics and assuming that the fitted model perfectly represents the probability distribution.

A value of 1 indicates a perfect fit (all data points lie on the fitted curve) a value close to 0 indicates super-statistical deviations of the dataset from the fitted model.

class ionics_fits.MLE.MLEFitter(x: float | List | ndarray, y: float | ndarray, model: Model, step_size: float = 0.0001, minimizer_args: Dict | None = None)

Base class for maximum Likelihood Parameter Estimation fitters.

Implementations should override the log_likelihood() and calc_sigma() methods.

See Fitter for further details.

__init__(x: float | List | ndarray, y: float | ndarray, model: Model, step_size: float = 0.0001, minimizer_args: Dict | None = None)

Fits a model to a dataset and stores the results.

Parameters:
  • x – x-axis data

  • y – y-axis data

  • model – the model function to fit to. The model’s parameter dictionary is used to configure the fit (set parameter bounds etc). Modify this before fitting to change the fit behaviour from the model class’ defaults. The model is (deep) copied and stored as an attribute.

  • step_size – step size used when calculating the log likelihood’s Hessian as part of finding the fitted parameter standard errors. Where finite parameter bounds are provided, they are used to scale the step size appropriately for each parameter.

  • minimizer_args – optional dictionary of keyword arguments to be passed into scipy.optimize.minimize. By default we set maxls to 100.

log_likelihood(free_param_values: ndarray, x: float | List | ndarray, y: float | ndarray, free_func: Callable[[...], float | ndarray]) float

Returns the negative log-likelihood of a given dataset

Parameters:
  • free_param_values – array of floated parameter values

  • x – x-axis data

  • y – y-axis data

  • free_func – convenience wrapper for the model function, taking only values for the fit’s free parameters

class ionics_fits.binomial.BinomialFitter(x: float | List | ndarray, y: float | ndarray, num_trials: int, model: Model, step_size: float = 0.0001, minimizer_args: Dict | None = None)

Maximum-likelihood parameter estimator for Binomially-distributed data.

The model is interpreted as giving the success probability for a Bernoulli trial under a given set of parameters: p = M(x; params).

The y-axis data is interpreted as the success fraction, such that the total number of successes is equal to k = y * num_trails.

See Fitter and MLEFitter for further details.

__init__(x: float | List | ndarray, y: float | ndarray, num_trials: int, model: Model, step_size: float = 0.0001, minimizer_args: Dict | None = None)

Fits a model to a dataset and stores the results.

Parameters:
  • x – x-axis data

  • y – y-axis data

  • model – the model function to fit to. The model’s parameter dictionary is used to configure the fit (set parameter bounds etc). Modify this before fitting to change the fit behaviour from the model class’ defaults. The model is (deep) copied and stored as an attribute.

  • num_trials – number of Bernoulli trails for each sample

  • step_size – see MLEFitter.

  • minimizer_args – optional dictionary of keyword arguments to be passed into scipy.optimize.minimize.

calc_sigma() float | ndarray

Return an array of standard error values for each y-axis data point.

log_likelihood(free_param_values: ndarray, x: float | List | ndarray, y: float | ndarray, free_func: Callable[[...], float | ndarray]) float

Returns the negative log-likelihood of a given dataset

Parameters:
  • free_param_values – array of floated parameter values

  • x – x-axis data

  • y – y-axis data

  • free_func – convenience wrapper for the model function, taking only values for the fit’s free parameters

Validators

It’s not enough to just fit data, we want to know whether we can trust the fit results. This is where validators come in.

There are two distinct aspects to the validation problem:

  • did the fit find the model parameters which best match the data (as opposed to getting stuck in a local minimum in parameter space far from the global optimum)?

  • Are the fitted parameter values consistent with our prior knowledge of the system (e.g. we know that a fringe contrast must lie within certain bounds).

The approach taken in ionics_fits is as follows. First any prior knowledge about the system should be encoded into the ModelParameter configuration by setting parameter bounds, fixing parameters, etc.

After that, the fit is validated using a FitValidator. Validators provide a flexible and extensible framework for using statistical tests to validate fits.

class ionics_fits.validators.FitValidator

Base class for fit validators

validate(fit_results: Fitter) Tuple[bool, float]

Validates the fit.

Subclasses must override this method

Returns:

tuple specifying whether the fit succeeded and the fit significance as a number between 0 (complete failure) and 1 (perfect fit).

window_fun(fit_results: Fitter)

Returns a boolean array of x-axis points which should be included in fit validation. This implementation includes all x-axis data points; override to only include a subset.

class ionics_fits.validators.NSigmaValidator(n_sigma: float = 3.0, significance_threshold: float = 0.75)
__init__(n_sigma: float = 3.0, significance_threshold: float = 0.75)

Fit validator which checks that at least significance_threshold of points lie within n_sigma standard errors of the fitted value.

This is a relatively forgiving (easy to configure in a way that gives minimal “good” fits which fail validation) general-purpose fit validator.

Parameters:
  • n_sigma – number of standard errors that points allowed to differ from the model.

  • significance_threshold – fraction of points which must lie within n_sigma of the model for the fit to be considered successful.

validate(fit_results: Fitter) Tuple[bool, float]

Validates the fit.

Subclasses must override this method

Returns:

tuple specifying whether the fit succeeded and the fit significance as a number between 0 (complete failure) and 1 (perfect fit).

Models

Benchmarking

class ionics_fits.models.benchmarking.Benchmarking(num_qubits)

Benchmarking success probability decay model according to:

y = (y0 - y_inf)*p^x + y_inf

where x is the sequence length (number of Clifford operations).

See _func() for parameter details.

__init__(num_qubits)
Parameters:

num_qubits – The number of qubits involved in the benchmarking sequence.

_func(x: float | ~typing.List | ~numpy.ndarray, p: <ModelParameter(lower_bound=0.0, upper_bound=1.0, scale_func=scale_no_rescale)>, y0: <ModelParameter(lower_bound=0.0, upper_bound=1.0, scale_func=scale_no_rescale)>, y_inf: <ModelParameter(lower_bound=0, upper_bound=1, scale_func=scale_no_rescale)>) float | ndarray

Fit parameters

Parameters:
  • p – depolarisation parameter

  • y0 – SPAM fidelity estimate

  • y_inf – depolarisation offset (y-axis asymptote) (fixed to 1/2^n by default)

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • e: error per Clifford e = (1 - p) / alpha_n where alpha_n = 2^n / (2^n - 1)

  • e_spam: estimated SPAM error e_spam = 1 - y0

Cone

class ionics_fits.models.cone.ConeSlice(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Slice through a cone according to:

z = sign * sqrt( (k_x * (x - x0))**2 + (k_y * (y - y0)) ** 2)) + z0

This model represents a slice through the cone with fixed y, given by:

z = sign(k_x) * sqrt( (k_x * (x - x0))**2 + alpha ** 2 ) + z0

where:

  • alpha = k_y * (y - y0)

  • we use the sign of k_x to set the sign for the cone

Floating z0 and alpha without a user-estimate for either may result in an unreliable fit.

See _func() for parameter details.

_func(x: float | ~typing.List | ~numpy.ndarray, x0: <ModelParameter(scale_func=scale_no_rescale)>, z0: <ModelParameter(fixed_to=0, scale_func=scale_no_rescale)>, k: <ModelParameter(scale_func=scale_no_rescale)>, alpha: <ModelParameter(lower_bound=0, scale_func=scale_no_rescale)>) float | ndarray
Parameters:
  • x0 – x-axis offset

  • z0 – vertical offset to the cone

  • k – slope along x

  • alpha – offset due to being off-centre in the y-axis

Exponential

class ionics_fits.models.exponential.Exponential(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Exponential function according to:

y(x < x_dead) = y0
y(x >= x_dead) = y0 + (y_inf-y0)*(1-exp(-(x-x_dead)/tau))

See _func() for parameter details.

_func(x: float | ~typing.List | ~numpy.ndarray, x_dead: <ModelParameter(lower_bound=0, fixed_to=0, scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, y_inf: <ModelParameter(scale_func=scale_y)>, tau: <ModelParameter(lower_bound=0, scale_func=scale_x)>) float | ndarray
Parameters:
  • x_dead – x-axis “dead time” (fixed to 0 by default)

  • y0 – initial (x = x_dead) y-axis offset

  • y_inf – y-axis asymptote (i.e. y(x - x_dead >> tau) => y_inf)

  • tau – decay constant

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • x_1_e: x-axis value for 1/e decay including dead time (x_1_e = x_dead + tau)

Gaussian

class ionics_fits.models.gaussian.Gaussian(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Gaussian model according to:

y = a / (sigma * sqrt(2*pi)) * exp(-0.5*((x-x0)/(sigma))^2) + y0

See _func() for parameter details.

_func(x: float | ~typing.List | ~numpy.ndarray, x0: <ModelParameter(scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, a: <ModelParameter(scale_func=scale_power)>, sigma: <ModelParameter(lower_bound=0, scale_func=scale_x)>) float | ndarray
Parameters:
  • x0 – x-axis offset

  • y0 – y-axis offset

  • a – y-axis scale factor. The Gaussian is normalized such that its integral is equal to a.

  • sigma – distribution half-width at 1/e of maximum height is 2*sigma (sigma is the 1/sqrt(e) radius).

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • FWHMH: full width at half-maximum height

  • peak: peak height above y0

  • w0: full width at 1/e max height. For Gaussian beams this is the beam waist

Heuristics

Helper functions for writing robust heuristics.

ionics_fits.models.heuristics.find_x_offset_fft(x: float | List | ndarray, omega: List | ndarray, spectrum: List | ndarray, omega_cut_off: float) float

Finds the x-axis offset of a dataset from the phase of an FFT.

This function uses the FFT shift theorem to extract the offset from the phase slope of an FFT.

This heuristic does not require any model parameters to have value estimates. It has good noise robustness (e.g. it’s not thrown off by a small number of outliers). It is generally accurate when the dataset is roughly regularly sampled along x, when the model is roughly zero outside of the dataset and when the model’s spectral content is below the dataset’s Nyquist frequency.

This heuristic supports datasets with a single x- and y-axis dimension.

See also get_spectrum().

Parameters:
  • omega – FFT frequency axis

  • spectrum – complex FFT data

  • omega_cut_off – highest value of omega to use in offset estimation

Returns:

an estimate of the x-axis offset

ionics_fits.models.heuristics.find_x_offset_sampling(model: Model, x: float | List | ndarray, y: float | ndarray, width: float, x_offset_param_name: str = 'x0') float

Finds the x-axis offset of a dataset by stepping through a range of potential offset values and picking the one that gives the lowest residuals.

This function takes a more brute-force approach by evaluating the model at a range of offset values, picking the one that gives the lowest residuals. This may be appropriate where one needs the estimate to be highly robust in the face of noisy, irregularly sampled data.

Parameters:
  • x – x-axis data

  • y – y-axis data

  • width – width of the feature we’re trying to find (e.g. FWHMH). Used to pick the spacing between offset values to try.

  • x_offset_param_name – name of the x-axis offset parameter

Returns:

an estimate of the x-axis offset

ionics_fits.models.heuristics.find_x_offset_sym_peak_fft(model: Model, x: float | List | ndarray, y: float | ndarray, omega: List | ndarray, spectrum: List | ndarray, omega_cut_off: float, test_pts: List | ndarray | None = None, x_offset_param_name: str = 'x0', y_offset_param_name: str = 'y0', defaults: Dict[str, float] | None = None)

Finds the x-axis offset for symmetric, peaked (maximum deviation from the baseline occurs at the symmetry point) functions.

This heuristic combines the following heuristics, picking the one that gives the best fit (in the “lowest sum squared residuals” sense):

A limitation of this heuristic is that all other parameters must already have a known value (fixed value, user estimate, heuristic or via the defaults argument).

This heuristic supports datasets with a single x- and y-axis dimension.

  • find_x_offset_fft()

  • Tests all points in the top quartile of deviation from the baseline

  • Optionally, user-provided “test points”, taken from another heuristic. This allows the developer to combine the general-purpose heuristics here with other heuristics which make use of more model-specific assumptions

Parameters:
  • model – the fit model

  • x – x-axis data

  • y – y-axis data

  • omega – FFT frequency axis

  • spectrum – complex FFT data

  • omega_cut_off – highest value of omega to use in offset estimation

  • test_pts – optional array of x-axis points to test

  • x_offset_param_name – name of the x-axis offset model parameter

  • y_offset_param_name – name of the y-axis offset model parameter

  • defaults – optional dictionary of fallback values to use for parameters with no initial value specified

Returns:

an estimate of the x-axis offset

ionics_fits.models.heuristics.get_pgram(x: ndarray, y: ndarray) Tuple[ndarray, ndarray]

Returns a Lombe-Scargle periodogram for a dataset, converted into amplitude units.

This function supports datasets with a single x- and y-axis dimension.

Parameters:
  • x – x-axis data

  • y – y-axis data

Returns:

tuple with the frequency axis (angular units) and the periodogram

ionics_fits.models.heuristics.get_spectrum(x: float | List | ndarray, y: float | ndarray, trim_dc: bool = False) Tuple[ndarray, ndarray]

Returns the frequency spectrum (Fourier transform) of a dataset.

NB the returned spectrum will only match the continuos Fourier transform of the model function in the limit where the model function is zero outside of the sampling window.

NB for narrow-band signals the peak amplitude depends on where the signal frequency lies compared to the frequency bins.

This function supports datasets with a single x- and y-axis dimension.

Parameters:
  • x – 1D ndarray of shape (num_samples,) containing x-axis data

  • y – 1D ndarray of shape (num_samples,) containing y-axis data

  • trim_dc – if True we do not return the DC component.

Returns:

tuple of (angular freq, fft)

ionics_fits.models.heuristics.get_sym_x(x: float | List | ndarray, y: float | ndarray) float

Returns x_0 such that y(x-x_0) is maximally symmetric.

This heuristic does not require any model parameters to have value estimates.

This heuristic supports arbitrary numbers y-axis dimensions, but only a single x-axis dimension.

Limitations of the current implementation:

  • it can struggle with datasets which have significant amounts of data “in the wings” of the model function. Because it doesn’t know anything about the model function (to avoid needing estimates for parameters) it can’t tell if the symmetry point it has found is really just an area of the distribution which is featureless. This could potentially be fixed by changing how we divide the data up into “windows”

  • it currently assumes the data is on a roughly regularly sampled grid

Parameters:
  • x – x-axis data

  • y – y-axis data

Returns:

the value of x about which y is maximally symmetric

ionics_fits.models.heuristics.param_min_sqrs(model: Model, x: float | List | ndarray, y: float | ndarray, scanned_param: str, scanned_param_values: List | ndarray, defaults: Dict[str, float] | None = None) Tuple[float, float]

Scans one model parameter while holding the others fixed to find the value that gives the best fit to the data (in the minimum sum-squared residuals sense).

A limitation of this heuristic is that all other parameters must already have a known value (fixed value, user estimate, heuristic or via the defaults argument).

This heuristic supports arbitrary numbers of x- and y-axis dimensions.

Parameters:
  • x – x-axis data

  • y – y-axis data

  • scanned_param – name of parameter to optimize

  • scanned_param_values – array of scanned parameter values to test

  • defaults – optional dictionary of fallback values to use for non-scanned parameters, which don’t have an in initial value (fixed value, user estimate or heuristic) set

Returns:

tuple with the value from scanned_param_values which gives the best fit and the root-sum-squared residuals for that value (“cost”).

Laser Rabi

class ionics_fits.models.laser_rabi.LaserFlop(distribution_fun: Callable[[...], ndarray], start_excited: bool, sideband_index: int, n_max: int = 30)

Base class for damped Rabi flopping with finite Lamb-Dicke parameter.

This model calculates measurement outcomes for systems containing two internal states and moving in a 1D harmonic potential that undergo damped Rabi oscillations, defined by:

P = P_readout_g + (P_readout_e - P_readout_g) * P_e

where P_e is the (time-dependent) population in the excited state and P_readout_g and P_readout_e are the readout levels (measurement outcomes when the qubit is in one state).

This class does not support fitting directly; use one of the subclasses instead. Subclasses must inherit from this class and a suitable RabiFlop subclass, such as RabiFlopFreq or RabiFlopTime.

The model requires that the spin state of the system starts out entirely in one of the ground or excited states, specified using __init__()'s start_excited parameter. It further assumes that the motional part of the system starts out in a distribution over different Fock states, described by the specified distribution_fun.

Independent variables:

  • t_pulse: duration of driving pulse including dead time. The duration of the interaction is given by t = max(0, t_pulse - t_dead).

  • w: frequency of driving pulse relative to the reference frequency w_0, given by delta = w - w_0

The model additionally gains any parameters associated with the specified Fock state distribution function.

Derived parameters are inherited from RabiFlop's calculate_derived_params() method.

All frequencies are in angular units.

See also RabiFlop.

__init__(distribution_fun: Callable[[...], ndarray], start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

_func(x: ~typing.Tuple[float | ~typing.List | ~numpy.ndarray, float | ~typing.List | ~numpy.ndarray], P_readout_e: <ModelParameter(lower_bound=0.0,upper_bound=1.0,scale_func=scale_y)>, P_readout_g: <ModelParameter(lower_bound=0.0,upper_bound=1.0,scale_func=scale_y)>, eta: <ModelParameter(lower_bound=0.0,scale_func=scale_invariant)>, omega: <ModelParameter(lower_bound=0.0,scale_func=scale_undefined)>, tau: <ModelParameter(lower_bound=0.0,fixed_to=inf,scale_func=scale_undefined)>, t_dead: <ModelParameter(lower_bound=0.0,fixed_to=0.0,scale_func=scale_undefined)>, w_0: <ModelParameter(scale_func=scale_undefined)>, **kwargs: ~ionics_fits.common.ModelParameter) float | ndarray

Return measurement probability.

Parameters:
  • x – tuple of (t_pulse, w). Subclasses should override func to map this onto the appropriate input data.

  • P_readout_e – excited state readout level

  • P_readout_g – ground state readout level

  • eta – Lamb-Dicke parameter

  • omega – carrier Rabi frequency

  • tau – decay time constant

  • t_dead – dead time

  • w_0 – resonance frequency offset

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]
Derived parameters:
  • t_pi: Pi-time, calculated as t_pi = pi / omega

  • t_pi_2: Pi/2-time, calculated as t_pi_2 = t_pi / 2

  • f_0: Offset of resonance from zero of frequency variable in linear units

class ionics_fits.models.laser_rabi.LaserFlopFreqCoherent(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse detuning scans when the motional degree of freedom starts in a coherent state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopFreqDisplacedThermal(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse detuning scans when the motional degree of freedom starts in a displaced thermal state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopFreqSqueezed(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse detuning scans when the motional degree of freedom starts in a squeezed state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopFreqThermal(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse detuning scans when the motional degree of freedom starts in a thermal state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopTimeCoherent(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse duration scans when the motional degree of freedom starts in a coherent state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopTimeDisplacedThermal(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse duration scans when the motional degree of freedom starts in a displaced thermal state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopTimeSqueezed(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse duration scans when the motional degree of freedom starts in a squeezed state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

class ionics_fits.models.laser_rabi.LaserFlopTimeThermal(start_excited: bool, sideband_index: int, n_max: int = 30)

Fit model for Rabi flopping pulse duration scans when the motional degree of freedom starts in a thermal state.

__init__(start_excited: bool, sideband_index: int, n_max: int = 30)
Parameters:
  • distribution_fun – function returning an array of Fock state occupation probabilities. The distribution function’s first argument should be the maximum Fock state to include in the simulation (the returned array has n_max + 1 elements). Subsequent arguments should be ModelParameters used to parametrise the distribution.

  • start_excited – if True the qubit starts in the excited state

  • sideband_index – change in motional state due to a pi-pulse starting from the spin ground-state.

  • n_max – maximum Fock state used in the simulation

Lorentzian

class ionics_fits.models.lorentzian.Lorentzian(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Lorentzian model according to:

y = a * (0.5 * fwhmh)^2 / ((x - x0)^2 + (0. 5 * fwhmh)^2) + y0

See _func() for parameter details.

_func(x: float | ~typing.List | ~numpy.ndarray, x0: <ModelParameter(scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, a: <ModelParameter(scale_func=scale_y)>, fwhmh: <ModelParameter(lower_bound=0, scale_func=scale_x)>) float | ndarray
Parameters:
  • x0 – x-axis offset

  • y0 – y-axis offset

  • a – peak value of the function above y0

  • fwhmh – full width at half maximum height of the function

Mølmer–Sørensen

class ionics_fits.models.molmer_sorensen.MolmerSorensen(num_qubits: int, start_excited: bool, walsh_idx: int)

Base class for Mølmer–Sørensen interactions.

This model calculates the time-dependent populations for one or two qubits coupled to a single motional mode undergoing a Mølmer–Sørensen type interaction.

It requires that the initial spin states of all qubits are the same and either |g> or |e> - different initial states for each qubit or initial states which are superpositions of spin eigenstates are are not supported.

For single-qubit interactions, the model has one y-axis dimension, giving the excited-state population at the end of the interaction.

For two-qubit interactions, the model has three y-axis dimensions - P_gg, P_1e, P_ee - giving the probabilities of 0, 1 or 2 ions being in the excited state at the end of the interaction duration.

Modulation of the sign of the spin-dependent force according to a Walsh function is supported.

The motion’s initial state must be a thermal distribution.

This class does not support fitting directly; use one of its subclasses instead.

Independent variables:

  • t_pulse: total interaction duration.

  • w: detuning of red/blue sideband tones relative to reference frequency w_0. The interaction detuning is given by delta = w - w_0.

All frequencies are in angular units unless stated otherwise.

__init__(num_qubits: int, start_excited: bool, walsh_idx: int)
Parameters:
  • num_qubits – number of qubits (must be 1 or 2)

  • walsh_idx – Index of Walsh function

  • start_excited – If True, all qubits start in |e>, otherwise they start in |g>.

_func(x: float | ~typing.List | ~numpy.ndarray, omega: <ModelParameter(lower_bound=0.0, scale_func=scale_undefined)>, w_0: <ModelParameter(scale_func=scale_undefined)>, n_bar: <ModelParameter(lower_bound=0.0, fixed_to=0.0, scale_func=scale_invariant)>) float | ndarray

Return measurement probabilities for the states P_gg, P_1, and P_ee.

Parameters:
  • omega – sideband Rabi frequency

  • w_0 – angular resonance frequency offset

  • n_bar – average initial occupancy of the motional mode

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • f_0: resonance frequency offset (Hz)

class ionics_fits.models.molmer_sorensen.MolmerSorensenFreq(num_qubits: int, start_excited: bool, walsh_idx: int)

Fit model for Mølmer–Sørensen detuning scans.

This model calculates the populations for Mølmer–Sørensen interactions when the gate duration is kept fixed and only the interaction detuning is varied. The pulse duration is specified using a new t_pulse model parameter.

__init__(num_qubits: int, start_excited: bool, walsh_idx: int)
Parameters:
  • num_qubits – number of qubits (must be 1 or 2)

  • walsh_idx – Index of Walsh function

  • start_excited – If True, all qubits start in |e>, otherwise they start in |g>.

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • f_0: resonance frequency offset (Hz)

  • f_loop_{n}_{i}: frequency offset of nth loop closure (Hz) for n = [1, 5] at “plus” (i = p) or “minus” (i = m) detuning

class ionics_fits.models.molmer_sorensen.MolmerSorensenTime(num_qubits: int, start_excited: bool, walsh_idx: int)

Fit model for Mølmer–Sørensen pulse duration scans.

This model calculates the populations for Mølmer–Sørensen interactions when the interaction detuning is kept fixed and only the pulse duration is varied.

Since the detuning is not scanned as an independent variable, we replace w_0 with a new model parameter delta, defined by delta = |w - w_0|.

__init__(num_qubits: int, start_excited: bool, walsh_idx: int)
Parameters:
  • num_qubits – number of qubits (must be 1 or 2)

  • walsh_idx – Index of Walsh function

  • start_excited – If True, all qubits start in |e>, otherwise they start in |g>.

Multi-X

Models with more than one x-axis degree of freedom, which have been created from 1D models using Model2D.

class ionics_fits.models.multi_x.Cone2D

2D Cone Model.

Parameters are:

  • x0_x0

  • x0_x1

  • k_x0

  • k_x1

  • y0

Parameters with an _x0 suffix inherit from ConeSlice, parameters with an _x1 suffix inherit from Triangle.

class ionics_fits.models.multi_x.Gaussian2D

2D Gaussian according to:

y = (
    a / ((sigma_x0 * sqrt(2*pi)) * (sigma_x1 * sqrt(2*pi)))
    * exp(-0.5*((x0-x0_x0)/(sigma_x0))^2 -0.5*((x1-x0_x1)/(sigma_x1))^2) + y0

Parameters are:

  • a

  • x0_x0

  • x0_x1

  • sigma_x0

  • sigma_x1

  • y0

Derived results are:
  • FWHMH_x0

  • FWHMH_x1

  • w0_x0

  • w0_x1

  • peak

See Gaussian for details.

class ionics_fits.models.multi_x.Parabola2D

2D Parabola according to:

y = k_x0 * (x0 - x0_x0)^2 + k_x1 *(x1 - x0_x1) + y0

Parameters are:

  • x0_x0

  • x0_x1

  • k_x0

  • k_x1

  • y0

See Parabola for details.

Polynomial

class ionics_fits.models.polynomial.Line

Straight line fit according to:

y = a * x + y0

Fit parameters (all floated by default unless stated otherwise):

  • y0: y-axis intercept

  • a: slope

class ionics_fits.models.polynomial.Parabola

Parabola fit according to: y = k * (x - x0)^2 + y0

Fit parameters (all floated by default unless stated otherwise):
  • x0: x-axis offset

  • y0: y-axis intercept

  • k: curvature

Derived parameters:

None

estimate_parameters(x: float | List | ndarray, y: float | ndarray)

If x0 is floated, we map the Polynomial a_1 coefficient onto a value for x0 according to:

y = a_0 + a_2 * x^2
x -> x - x0: y = a_0 + a_2 * (x - x0)^2
y = a_0 + a_2*x^2 + a_2 * x0^2 + 2*a_2*x*x0
y = (a_0 + a_2 * x0^2) + 2*a_2*x*x0 + a_2*x^2

a_0 -> a_0 + a_2 * x0^2
a_1 -> 2*a_2*x0 => x0 = a_1/(2*a_2)
a_2 -> a_2
class ionics_fits.models.polynomial.Polynomial(poly_degree=10)

Polynomial fit model according to:

y = sum(a_n*(x-x0)^n) for n ={0...poly_degree}

Model parameters:

  • a_0a_{poly_degree}: polynomial coefficients

  • x0: x-axis offset (fixed to 0 by default). Floating x0 as well as polynomial coefficients results in an under-defined problem.

can_rescale() Tuple[List[bool], List[bool]]

Returns a tuple of lists of bools specifying whether the model can be rescaled along each x- and y-axes dimension.

estimate_parameters(x: float | List | ndarray, y: float | ndarray)

Set heuristic values for model parameters.

Typically called by Fitter.

Implementations of this method must ensure that all parameters have an initial value set (at least one of fixed_to, user_estimate or heuristic must not be None for each parameter).

Implementations should aim to make use of all information supplied by the user (bounds, user estimates, fixed values) to provide the best initial guesses for all parameters.

The x and y data is sorted along the x-axis dimensions and is filtered to remove points with non-finite x or y values and are is rescaled if supported by the model.

Parameters:
  • x – x-axis data

  • y – y-axis data

func(x: float | List | ndarray, param_values: Dict[str, float]) float | ndarray

Evaluates the model at a given set of x-axis points and with a given set of parameter values and returns the result.

To use the model as a function outside of a fit, __call__() generally provides a more convenient interface.

Overload this to provide a model function with a dynamic set of parameters, otherwise prefer to override _func().

Parameters:
  • x – x-axis data

  • param_values – dictionary of parameter values

Returns:

array of model values

get_num_x_axes() int

Returns the number of x-axis dimensions the model has.

get_num_y_axes() int

Returns the number of y-axis dimensions the model has.

class ionics_fits.models.polynomial.Power(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Single-power fit according to:

y = a*(x-x0)^n + y0

x - x0 must always be strictly greater than 0. This is because n can take non-integral values (for integer coefficients use Polynomial instead) and this function’s return is real-valued.

The fit will often struggle when both y0 and n are floated if the dataset doesn’t contain some asymptotic values where y ~ y0. The more you can help it out by bounding parameters and providing initial guesses the better.

The fit will generally struggle to converge if both a and y0 are floated unless it is given some guidance (e.g. initial values).

_func(x: float | ~typing.List | ~numpy.ndarray, a: <ModelParameter(fixed_to=1, scale_func=scale_undefined)>, x0: <ModelParameter(fixed_to=0, scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, n: <ModelParameter(scale_func=scale_invariant)>) float | ndarray
  • a: y-axis scale factor

  • x0: x-axis offset

  • y0: y-axis offset

  • n: power

Quantum Physics

ionics_fits.models.quantum_phys.coherent_state_probs(n_max: int, alpha: <ModelParameter(lower_bound=0, scale_func=scale_invariant)>) ndarray

Coherent state probability distribution.

A coherent state is defined as:

|α> = exp(α a_dag - α* a) |0>

where a_dag and a denote the harmonic-oscillator creation and annihilation operators. The mean Fock state occupancy is given by n_bar = |α|^2

Parameters:
  • n_max – the distribution is truncated at a maximum Fock state of |n_max>

  • alpha – Complex displacement parameter

Returns:

array of Fock state occupation probabilities

ionics_fits.models.quantum_phys.displaced_thermal_state_probs(n_max: int, n_bar: <ModelParameter(lower_bound=0, scale_func=scale_invariant)>, alpha: <ModelParameter(lower_bound=0, scale_func=scale_invariant)>) ndarray

Displaced thermal probability distribution.

For an ion initially in a thermal distribution characterised by an average phonon number n_bar, we calculate the new probability distribution after applying a displacement operator D(α).

Formula taken from equation (7) of Ramm, M., Pruttivarasin, T. and Häffner, H., 2014. Energy transport in trapped ion chains. New Journal of Physics, 16(6), p.063062. https://iopscience.iop.org/article/10.1088/1367-2630/16/6/063062/pdf

Parameters:
  • n_max – the distribution is truncated at a maximum Fock state of |n_max>

  • n_bar – the mean thermal Fock state occupation before displacement

  • alpha – Complex displacement parameter

Returns:

array of Fock state occupation probabilities

ionics_fits.models.quantum_phys.squeezed_state_probs(n_max: int, zeta: <ModelParameter(lower_bound=0, scale_func=scale_invariant)>) ndarray

Squeezed state probability distribution.

A pure squeezed state is defined as:

|ζ> = exp[1 / 2 *  (ζ* a^2 - ζ a_dag^2)] |0>,

where a_dag and a denote the harmonic-oscillator creation and annihilation operators.

Parameters:
  • n_max – the distribution is truncated at a maximum Fock state of |n_max>

  • zeta – Complex squeezing parameter

Returns:

array of Fock state occupation probabilities

ionics_fits.models.quantum_phys.thermal_state_probs(n_max: int, n_bar: <ModelParameter(lower_bound=0, scale_func=scale_invariant)>) ndarray

Thermal state probability distribution.

Parameters:
  • n_max – the distribution is truncated at a maximum Fock state of |n_max>

  • n_bar – the mean Fock state occupation

Returns:

array of Fock state occupation probabilities

Rabi

class ionics_fits.models.rabi.RabiFlop(start_excited: bool)

Base class for damped Rabi flopping.

This model calculates measurement outcomes for two-state systems undergoing damped Rabi oscillations, defined by:

P = P_readout_g + (P_readout_e - P_readout_g) * P_e

where P_e is the (time-dependent) population in the excited state and P_readout_g and P_readout_e are the readout levels (measurement outcomes when the qubit is in one state).

This class does not support fitting directly; use one of the subclasses instead.

The model requires that the system starts out entirely in one of the ground or excited states, specified using __init__()'s start_excited parameter.

The probability of transition from one state to the other is calculated as:

P_trans = 1 / 2 * omega^2 / W^2 * [1 - exp(-t / tau) * cos(W * t)]
where:
  • t is the duration of interaction between qubit and driving field

  • W = sqrt(omega^2 + delta^2)

  • delta is the detuning of the driving field from resonance

  • omega is the Rabi frequency

  • tau is the decay time constant.

Independent variables:
  • t_pulse: duration of driving pulse including dead time. The duration of the interaction is given by t = max(0, t_pulse - t_dead).

  • w: frequency of driving pulse relative to the reference frequency w_0, given by delta = w - w_0

All frequencies are in angular units.

__init__(start_excited: bool)
Parameters:

start_excited – if True the system is assumed to start in the excited state, other wise it is assumed to start in the ground state.

_func(x: ~typing.Tuple[float | ~typing.List | ~numpy.ndarray, float | ~typing.List | ~numpy.ndarray], P_readout_e: <ModelParameter(lower_bound=0.0,upper_bound=1.0,scale_func=scale_y)>, P_readout_g: <ModelParameter(lower_bound=0.0,upper_bound=1.0,scale_func=scale_y)>, omega: <ModelParameter(lower_bound=0.0,scale_func=scale_undefined)>, tau: <ModelParameter(lower_bound=0.0,fixed_to=inf,scale_func=scale_undefined)>, t_dead: <ModelParameter(lower_bound=0.0,fixed_to=0.0,scale_func=scale_undefined)>, w_0: <ModelParameter(scale_func=scale_undefined)>) float | ndarray

Return measurement probability.

Parameters:
  • x – tuple of (t_pulse, w). Subclasses should override func to map this onto the appropriate input data.

  • P_readout_e – excited state readout level

  • P_readout_g – ground state readout level

  • omega – Rabi frequency

  • tau – decay time constant (fixed to infinity by default)

  • t_dead – dead time (fixed to 0 by default)

  • w_0 – resonance frequency offset

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]
Derived parameters:
  • t_pi: Pi-time, calculated as t_pi = pi / omega

  • t_pi_2: Pi/2-time, calculated as t_pi_2 = t_pi / 2

  • f_0: Offset of resonance from zero of frequency variable in linear units

class ionics_fits.models.rabi.RabiFlopFreq(start_excited: bool)

Fit model for Rabi flopping frequency scans.

This model calculates the measurement outcomes for damped Rabi flops when the pulse duration is kept fixed and only its frequency is varied. The pulse duration is specified using a new t_pulse model parameter.

See also RabiFlop.

__init__(start_excited: bool)
Parameters:

start_excited – if True the system is assumed to start in the excited state, other wise it is assumed to start in the ground state.

class ionics_fits.models.rabi.RabiFlopTime(start_excited: bool)

Fit model for Rabi flopping pulse duration scans.

This model calculates the measurement outcome for damped Rabi flops when the frequency of the pulse is kept fixed and only its duration is varied.

Since the detuning is not scanned as an independent variable, we replace w_0 with a new model parameter delta, defined by: delta = |w - w_0|.

See also RabiFlop.

__init__(start_excited: bool)
Parameters:

start_excited – if True the system is assumed to start in the excited state, other wise it is assumed to start in the ground state.

Ramsey

class ionics_fits.models.ramsey.Ramsey(start_excited: bool)

Fit model for detuning scans of Ramsey experiments (for time scans, use the Sinusoid model).

This model calculates the measurement outcomes for Ramsey experiments, defined by:

P = P_readout_g + (P_readout_e - P_readout_g) * P_e

where P_e is the (time-dependent) population in the excited state and P_readout_g and P_readout_e are the readout levels (measurement outcomes when the qubit is in one state).

The model requires that the system starts out entirely in one of the ground or excited states, specified using :meth:__init__'s start_excited parameter.

All frequencies are in angular units.

_func(x: float | ~typing.List | ~numpy.ndarray, P_readout_e: <ModelParameter(lower_bound=0.0, upper_bound=1.0, scale_func=scale_y)>, P_readout_g: <ModelParameter(lower_bound=0.0, upper_bound=1.0, scale_func=scale_y)>, t: <ModelParameter(lower_bound=0.0, scale_func=scale_x_inv)>, t_pi_2: <ModelParameter(lower_bound=0.0, scale_func=scale_x_inv)>, w_0: <ModelParameter(scale_func=scale_x)>, phi: <PeriodicModelParameter(scale_func=scale_invariant, period=6.283, offset=-3.142)>, tau: <ModelParameter(lower_bound=0.0, fixed_to=inf, scale_func=scale_x_inv)>)
Parameters:
  • P_readout_e – excited state readout level

  • P_readout_g – ground state readout level

  • t – Ramsey delay

  • t_pi_2 – duration of the pi/2 pulses. The pi/2 pulses are assumed to be ideal pi/2 pulses with a corresponding Rabi frequency of Omega = np.pi / (2 * t_pi_2)

  • w_0 – resonance frequency offset, defined such that the Ramsey detuning is given by delta = x - w_0

  • phi – phase of the second pi/2 pulse relative to the first pi/2 pulse

  • tau – decay time constant (fixed to infinity by default)

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • f_0: resonance frequency offset in linear units, given by w_0 / (2 * np.pi)

Rectangle

class ionics_fits.models.rectangle.Rectangle(thresh: float = 0.5)

Rectangle function according to:

x <= x_l: y = y0
x >= x_r: y = y0
x_r > x > x_l: y0 + a

For x_l = y0 = 0, x_r = inf this is a Heaviside step function.

__init__(thresh: float = 0.5)
Parameters:

thresh – threshold used to configure the parameter estimator, which attempts to find the edges of the rectangle by applying this threshold to the peak height above the baseline to determine which points are inside / outside the rectangle.

_func(x: float | ~typing.List | ~numpy.ndarray, a: <ModelParameter(scale_func=scale_y)>, y0: <ModelParameter(scale_func=scale_y)>, x_l: <ModelParameter(scale_func=scale_x)>, x_r: <ModelParameter(scale_func=scale_x)>) float | ndarray
Parameters:
  • a – rectangle height above the baseline

  • y0 – y-axis offset

  • x_l – left transition point

Param- x_r:

right transition point

Sigmoid

class ionics_fits.models.sigmoid.LogisticFunction(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Logistic function model according to:

y = a / (1 + exp(-k*(x - x0))) + y0

See _func() for parameters.

_func(x: float | ~typing.List | ~numpy.ndarray, a: <ModelParameter(scale_func=scale_y)>, y0: <ModelParameter(scale_func=scale_y)>, x0: <ModelParameter(scale_func=scale_x)>, k: <ModelParameter(lower_bound=0, scale_func=scale_x_inv)>) float | ndarray
Parameters:
  • a – y-axis scale factor

  • y0 – y-axis offset

  • x0 – x-axis offset

  • k – logistic growth rate (steepness of the curve)

Sinc

class ionics_fits.models.sinc.Sinc(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Sinc function according to:

y = a * sin(w * (x - x0)) / (w * (x - x0)) + y0

See _func() for parameters.

_func(x: float | ~typing.List | ~numpy.ndarray, x0: <ModelParameter(scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, a: <ModelParameter(scale_func=scale_y)>, w: <ModelParameter(lower_bound=0, scale_func=scale_x_inv)>) float | ndarray
Parameters:
  • x0 – x-axis offset

  • y0 – y-axis offset

  • a – amplitude

  • w – x scale factor

class ionics_fits.models.sinc.Sinc2(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Sinc-squared function according to:

y = a * (sin(w * (x - x0)) / (w * (x - x0)))^2 + y0

See _func() for parameters.

_func(x: float | ~typing.List | ~numpy.ndarray, x0: <ModelParameter(scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, a: <ModelParameter(scale_func=scale_y)>, w: <ModelParameter(lower_bound=0, scale_func=scale_x_inv)>) float | ndarray
Parameters:
  • x0 – x-axis offset

  • y0 – y-axis offset

  • a – amplitude

  • w – x scale factor

Sinusoid

class ionics_fits.models.sinusoid.Sine2(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Sine-squared fit according to:

y = Gamma * a * [sin(omega * (x - x0) + phi)]**2 + y0
Gamma = np.exp(-x / tau)

See also Sinusoid.

_func(x: float | ~typing.List | ~numpy.ndarray, a: <ModelParameter(lower_bound=0, scale_func=scale_y)>, omega: <ModelParameter(lower_bound=0, scale_func=scale_x_inv)>, phi: <PeriodicModelParameter(scale_func=scale_invariant, period=6.283, offset=-3.142)>, y0: <ModelParameter(scale_func=scale_y)>, x0: <ModelParameter(fixed_to=0, scale_func=scale_x)>, tau: <ModelParameter(lower_bound=0, fixed_to=inf, scale_func=scale_x)>) float | ndarray
Parameters:
  • a – initial (x = 0) amplitude of the sinusoid

  • omega – angular frequency

  • phi – phase offset

  • y0 – y-axis offset

  • x0 – x-axis offset

  • tau – decay/growth constant

class ionics_fits.models.sinusoid.SineMinMax

Sinusoid parametrised by minimum / maximum values instead of offset / amplitude:

y = Gamma * a * sin[omega * (x - x0) + phi] + y0

This class is equivalent to Sinusoid except that the a and y0 parameters are replaced with new min and max parameters defined by:

min = y0 - a
max = y0 + a

See Sinusoid for further details.

class ionics_fits.models.sinusoid.Sinusoid(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Generalised sinusoid fit according to:

y = Gamma * a * sin[omega * (x - x0) + phi] + y0
Gamma = exp(-x / tau).

All phases are in radians, frequencies are in angular units.

x0 and phi0 are equivalent parametrisations for the phase offset, but in some cases it works out convenient to have access to both (e.g. one as a fixed offset, the other floated). At most one of them should be floated at once. By default, x0 is fixed at 0 and phi0 is floated.

_func(x: float | ~typing.List | ~numpy.ndarray, a: <ModelParameter(lower_bound=0, scale_func=scale_y)>, omega: <ModelParameter(lower_bound=0, scale_func=scale_x_inv)>, phi: <PeriodicModelParameter(scale_func=scale_invariant, period=6.283, offset=-3.142)>, y0: <ModelParameter(scale_func=scale_y)>, x0: <ModelParameter(fixed_to=0, scale_func=scale_x)>, tau: <ModelParameter(lower_bound=0, fixed_to=inf, scale_func=scale_x)>) float | ndarray
Parameters:
  • a – initial (x = 0) amplitude of the sinusoid

  • omega – angular frequency

  • phi – phase offset

  • y0 – y-axis offset

  • x0 – x-axis offset

  • tau – decay/growth constant

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]
  • f: frequency

  • phi_cosine: cosine phase (phi + pi/2)

  • contrast: peak-to-peak amplitude of the pure sinusoid

  • min/max: min / max values of the pure sinusoid

  • period: period of oscillation

TODO: peak values of the damped sinusoid as well as x value that the peak occurs at.

Triangle

class ionics_fits.models.triangle.Triangle(parameters: Dict[str, ModelParameter] | None = None, internal_parameters: List[ModelParameter] | None = None)

Triangle function according to:

y(x>=x0) = k_p*|x-x0| + y0
y(x<x0) = k_m*|x-x0| + y0
y = max(y, y_min)
y = min(y, m_max)
k_p = (1 + sym) * k
k_m = (1 - sym) * k

See _func() for parameter details.

_func(x: float | ~typing.List | ~numpy.ndarray, x0: <ModelParameter(scale_func=scale_x)>, y0: <ModelParameter(scale_func=scale_y)>, k: <ModelParameter(scale_func=scale_power)>, sym: <ModelParameter(lower_bound=-1, upper_bound=1, fixed_to=0, scale_func=scale_invariant)>, y_min: <ModelParameter(fixed_to=-inf, scale_func=scale_y)>, y_max: <ModelParameter(fixed_to=inf, scale_func=scale_y)>) float | ndarray
Parameters:
  • x0 – x-axis offset

  • y0 – y-axis offset

  • k – average slope

  • sym – symmetry parameter (fixed to 0 by default)

  • y_min – minimum value of y (bound to -inf by default)

  • y_max – maximum value of y (bound to +inf by default)

calculate_derived_params(x: float | List | ndarray, y: float | ndarray, fitted_params: Dict[str, float], fit_uncertainties: Dict[str, float]) Tuple[Dict[str, float], Dict[str, float]]

Derived parameters:

  • k_m: slope for x < x0

  • k_p: slope for x >= x0

Utils

class ionics_fits.models.utils.PeriodicModelParameter(scale_func: Callable[[ndarray, ndarray], float], fixed_to: float | None = None, user_estimate: float | None = None, heuristic: float | None = None, period: float = 1, offset: float = 0)

Represents a model parameter whose value is periodic.

Parameter values are clipped to lie within:

((value - offset) % period) + offset

PeriodicModelParameters do not support bounds.

period

the period (default = 1)

Type:

float

offset

the offset (default = 0)

Type:

float

ionics_fits.models.utils.param_like(template_param: ModelParameter, overrides: Dict[str, Any] | None = None) ModelParameter

Returns a new parameter based on a template.

Parameters:
  • template_param – the returned parameter is a (deep) copy of the template parameter.

  • overrides – optional dictionary of attributes of the template parameter to replace.

Returns:

the new ModelParameter

Transformations

Models that change the behaviour of other models.

Aggregate Model

class ionics_fits.models.transformations.aggregate_model.AggregateModel(models: Dict[str, Model], common_params: Dict[str, Tuple[ModelParameter, List[Tuple[str, str]]]] | None = None)

Model formed by combining one or more models along the y-axis to produce a new model, whose number of y-axis dimensions is the sum of the y-dimensionalities of the aggregated models.

When aggregating a number of identical models, use a RepeatedModel instead.

Aggregate models allow multiple data sets to be fit jointly, with some parameters treated as “common” - their values forced to be the same for all of the aggregated models.

Example usage:

from pprint import pprint

from ionics_fits.models.laser_rabi import LaserFlopTimeThermal
from ionics_fits.models.transformations.aggregate_model import AggregateModel

rsb = LaserFlopTimeThermal(start_excited=False, sideband_index=-1)
bsb = LaserFlopTimeThermal(start_excited=False, sideband_index=+1)

model = AggregateModel(
    models={"rsb": rsb, "bsb": bsb},
    common_params={
        param: (rsb.parameters[param], [("rsb", param), ("bsb", param)])
        for param in rsb.parameters.keys()
    },
)

pprint(list(model.parameters.keys()))
['P_readout_e',
 'P_readout_g',
 'eta',
 'omega',
 'tau',
 't_dead',
 'delta',
 'n_bar']

This creates an AggregateModel, which models Rabi flopping on the blue and red sidebands of a pair of spins coupled to a motional mode starting in a thermal state. In this example, all parameters for the two sideband models are fit jointly.

The first y-axis dimension (y[0, :]) from the AggregateModel stores the red sideband with the second dimension storing the blue sideband.

At present this class only supports models with a single y-axis dimension. This is just because no one got around to implementing it yet rather than any fundamental difficulty.

__init__(models: Dict[str, Model], common_params: Dict[str, Tuple[ModelParameter, List[Tuple[str, str]]]] | None = None)
Parameters:
  • models

    The models to be aggregated. This should be a dictionary mapping model names to model instances. The model names are used as suffixes for names of model parameters and derived results. For example, if one of the aggregated models named model has a parameter param, the aggregate model will have a parameter param_model. The same applies to the derived results.

    The order of the models in this dictionary defines the order of the y-axis dimensions for the AggregateModel.

    The passed-in models are considered “owned” by the AggregateModel and should not be used / modified elsewhere.

  • common_params

    Optional dictionary specifying “common” model parameters. This feature allows multiple parameters (which can be from the same or different models) to be fit jointly to a single value. The common parameters are replaced with a new parameter, which is introduced to expose the common value to the user.

    The parameter metadata (limits, fixed_to, user_estimate, etc.) from the new parameter replaces the metadata for all parameters bound to it. Metadata set on the bound parameters is disregarded.

    The common_params dictionary keys are the names of the new model parameters.

    The dictionary values should be tuples containing the new model template parameter and a list of parameters to bind to the new parameter. The bound parameter lists should be lists of tuples, comprised of a pair of strings specifying the name of the model which owns the common parameter, and the name of the model parameter to make common.

    The new model parameters inherit their metadata (limits etc.) from the template parameters, which are (deep) copied and are not modified.

Mapped Model

class ionics_fits.models.transformations.mapped_model.MappedModel(model: Model, param_mapping: Dict[str, str], fixed_params: Dict[str, float] | None = None, derived_result_mapping: Dict[str, str | None] | None = None)

Wraps a Model, allowing parameters to be renamed or replaced with fixed values.

See also ReparametrizedModel

__init__(model: Model, param_mapping: Dict[str, str], fixed_params: Dict[str, float] | None = None, derived_result_mapping: Dict[str, str | None] | None = None)
Parameters:
  • model – The model to be wrapped. This model is considered “owned” by the MappedModel and should not be modified / used elsewhere.

  • param_mapping – dictionary mapping names of parameters in the WrappedModel to names of parameters in the model being wrapped.

  • fixed_params – dictionary mapping names of WrappedModel parameters to values they sould be fixed to. Fixed parameters are not parameters of the WrappedModel.

  • derived_result_mapping – optional dictionary mapping names of derived result in the WrappedModel to names of derived results in the model being wrapped. Derived results may be renamed to None to exclude them from the WrappedModel.

Model2D

class ionics_fits.models.transformations.model_2d.Model2D(models: Tuple[Model, Model], result_params: Tuple[str], model_names: Tuple[str, str] | None = None)

Combines a pair of Model's, each of which is a function of a single x-dimension, to make a new Model, which is a function of 2 x-axis dimensions.

All y-axis data is generated from the output of the first model; the output from the second model provides the values of certain “result” parameters used by the first model. In other words:

model_0 = models[0] = f(x_axis_0)
model_1 = models[1] = g(x_axis_1)
y(x_0, x_1) = model_0(x_0 | result_params = model_1(x_1))

The 2D models generated using this class are separable into functions of the two x-axes. As a result, it can only generate axis-aligned models.

This model provides a quick and convenient means of extending the x-axis dimensionality of existing models. The design aim is to maximise flexibility while making minimal assumptions about the underlying Models.

Model parameters and results:

  • All parameters from the two models - apart from the first model’s result parameters - are parameters of the 2D model.

  • All derived results from the two models are included in the Model2D's derived results.

  • A parameter/derived result named param from a model named model is exposed as a parameter / result of the Model2D named param_model.

  • A MappedModel can be used to provide custom naming schemes

Example usage:

from ionics_fits.models.gaussian import Gaussian
from ionics_Fits.models.transformations.model_2d import Model2D

Gaussian2D = Model2D(
    models=(Gaussian(), Gaussian),
    result_params=("a_x0",),
)

This fits a Gaussian model along the first x-axis dimension. It then takes the amplitudes of those Gaussians at each value of the second x-axis dimension and fits those to a Gaussian to produce a 2D Gaussian fit.

At present this class aggregates a pair of 1D models to make a 2D model. It would be easy to extend it to the more general case of aggregating an arbitrary number of models, each with arbitrary x-axis dimensionality. This has not been done yet because there has not been a use-case.

See also multi_x.

__init__(models: Tuple[Model, Model], result_params: Tuple[str], model_names: Tuple[str, str] | None = None)
Parameters:
  • models – Tuple containing the two Models to be combined to make the 2D model. The model instances are considered “owned” by the 2D model (they are not copied). They should not be referenced externally.

  • result_params – tuple of names of “result parameters” of the first model, whose value is found by evaluating the second model. The order of parameter names in this tuple must match the order of y-axis dimensions for the second model.

  • model_names – optional tuple of names for the two models. These are used when generating names for fit results and derived parameters. Empty strings are allowed so long as the two models do not share any parameter names. If this argument is None the model names default to x0 and x1 respectively. If a model name is an empty string, the trailing underscore is omitted in parameter / result names.

wrap_scale_funcs()

Called during __init__() to make sure that each ModelParameter uses the scale factor from the appropriate x-axis when rescaling.

This method wraps the scale_funcs for each parameter of the 1D models to use the x-axis scale factor for their x-axis dimension when rescaling.

This is appropriate where each model parameter scales with just that the x-axis associated with its model. If this is not the case, this method should be overridden. See Gaussian2D for an example of this.

Reparametrized Model

class ionics_fits.models.transformations.reparametrized_model.ReparametrizedModel(model: Model, new_params: Dict[str, ModelParameter], bound_params: List[str])

Model formed by reparametrizing an existing Model.

ionics_fits aims to provide convenient and flexible model parametrisations, however sometimes the default parametrisation won’t be convenient for your application. For these cases ReparametrizedModels provide a convenient way of changing and extending the parametrisation of an existing model.

Reparametrizing a model involves replacing “bound” parameters with “new” parameters, whose values the bound parameter values are calculated from.

All non-bound parameters of the original model as well as all “new” parameters are parameters of the ReparametrizedModel (bound parameters are internal parameters of the new model, but are not directly exposed to the user). All derived results from the original model are derived results from the ReparametrizedModel (override calculate_derived_params to change this behaviour).

Values and uncertainties for the bound parameters are exposed as derived results.

Subclasses must override bound_param_values(), bound_param_uncertainties() and bound_param_uncertainties() to specify the mapping between “new” and “bound” parameters.

Example usage converting a sinusoid parameterised by offset and amplitude into one parametrised by minimum and maximum values:

from typing import Dict

from ionics_fits.models.sinusoid import Sinusoid
from ionics_fits.models.transformations.reparametrized_model import (
    ReparametrizedModel
)

class SineMinMax(ReparametrizedModel):
    def __init__(self):
        super().__init__(
            model=Sinusoid(),
            new_params={
                "min": ModelParameter(scale_func=scale_y()),
                "max": ModelParameter(scale_func=scale_y()),
            },
            bound_params=["a", "y0"],
        )

    @staticmethod
    def bound_param_values(param_values: Dict[str, float]) -> Dict[str, float]:
        return {
            "a": 0.5 * (param_values["max"] - param_values["min"]),
            "y0": 0.5 * (param_values["max"] + param_values["min"]),
        }

    @staticmethod
    def bound_param_uncertainties(
        param_values: Dict[str, float], param_uncertainties: Dict[str, float]
    ) -> Dict[str, float]:
        err = 0.5 * np.sqrt(
            param_uncertainties["max"] ** 2 + param_uncertainties["min"] ** 2
        )
        return {"a": err, "y0": err}

    @staticmethod
    def new_param_values(model_param_values: Dict[str, float]
    ) -> Dict[str, float]:
        return {
            "max": (model_param_values["y0"] + model_param_values["a"]),
            "min": (model_param_values["y0"] - model_param_values["a"]),
        }

See also MappedModel.

__init__(model: Model, new_params: Dict[str, ModelParameter], bound_params: List[str])
Parameters:
  • model – The model to be reparametrized. This model is considered “owned” by the ReparametrizedModel and should not be used / modified elsewhere.

  • new_params – dictionary of new parameters of the ReparametrizedModel

  • bound_params – list of parameters of the Model to bound. These parameters are not exposed as parameters of the ReparametrizedModel.

static bound_param_uncertainties(param_values: Dict[str, float], param_uncertainties: Dict[str, float]) Dict[str, float]

Returns a dictionary of uncertainties for the model’s bound parameters.

This method must be overridden to specify the mapping from parameter uncertainties for the ReparameterizedModel to bound parameter uncertainties.

Parameters:
  • param_values – dictionary of values for parameters of the ReparameterizedModel.

  • param_uncertainties – dictionary of uncertainties for parameters of the ReparameterizedModel.

Returns:

dictionary of values for the bound parameters of the original model.

static bound_param_values(param_values: Dict[str, float]) Dict[str, float]

Returns a dictionary of values of the model’s bound parameters.

This method must be overridden to specify the mapping from parameters of the ReparameterizedModel to values of the bound parameters.

Parameters:

new_param_values – dictionary of parameter values for the ReparameterizedModel.

Returns:

dictionary of values for the bound parameters of the original model.

static new_param_values(model_param_values: Dict[str, float]) Dict[str, float]

Returns a dictionary of values of the model’s “new” parameters.

This method must be overridden to specify the mapping from values of the original model to values of the ReparametrizedModel's “new” parameters.

This is used to find estimates for the new parameters from the original model’s parameter estimates.

Parameters:

model_param_values – dictionary of parameter values for the original model

Returns:

dictionary of values for the “new” parameters of the ReparametrizedModel.

Repeated Model

class ionics_fits.models.transformations.repeated_model.RepeatedModel(model: Model, common_params: List[str] | None = None, num_repetitions: int = 2, aggregate_results=False)

Model formed by repeating a Model one or more models along the y-axis to produce a new model, with greater y-axis dimensionality.

The number of y-axis dimensions in the RepeatedModel is the product of the number of y-axis dimensions form the original model and the number of repetitions.

Repeated models allow multiple datasets to be analysed simultaneously. This is useful, for example, when doing joint fits to datasets (using common parameters).

The RepeatedModel has the following parameters and fit results:

  • all common parameters of the repeated model are parameters of the new model

  • for each independent (not common) parameter of the repeated model, param, the RepeatedModel has parameters param_{n} for n in [0, .., num_repitions-1]

  • for each independent parameter, param, the RepeatedModel model has additional fit results representing statistics between the repetitions. For each independent parameter param, the results dictionary will have additional quantities param_mean and param_peak_peak.

If aggregate_results is enabled the rules for fit results are modified as follows:

  • no statistical quantities are calculated for fit parameters or derived results

  • all derived results whose values and uncertainties are the same for all repetitions are aggregated together to give a single result. These results have the same name as the original model’s results, with no suffix.

  • all derived results whose value is not the same for al repetitions are omitted.

__init__(model: Model, common_params: List[str] | None = None, num_repetitions: int = 2, aggregate_results=False)
Parameters:
  • model – The Model to be repeated. The implementation of model will be used to generate data for all y axes. This model is considered owned by the RepeatedModel and should not be used / modified elsewhere.

  • common_params – optional list of names of model parameters, whose value is common to all y axes. All other model parameters are allowed to vary independently between the y axes

  • num_repetitions – the number of times the model is to be repeated

  • aggregate_results – determines whether derived results are aggregated or not. The default behaviour is to not aggregate results. This is generally suitable when one wants access to the values of non-common parameters from the various repetitions. Aggregating results can be useful, for example, when all parameters are common across the repetitions and one wants a single set of values reported.

Scaled Model

class ionics_fits.models.transformations.scaled_model.ScaledModel(model: Model, x_scale: float, x_offset: float = 0.0)

Model with rescaled x-axis.

A common use-case for ScaledModels is converting models between linear and angular units.

__init__(model: Model, x_scale: float, x_offset: float = 0.0)
Parameters:
  • model – model to rescale. This model is considered “owned” by the ScaledModel and should not be used/modified elsewhere.

  • x_scale – multiplicative x-axis scale factor. To convert a model that takes x in angular units and convert to one that takes x in linear units use x_scale = 2 * np.pi

  • x_offset – additive x-axis offset