varpro::model::builder

Enum SeparableModelBuilder

Source
pub enum SeparableModelBuilder<ScalarType>
where ScalarType: Scalar,
{ // some variants omitted }
Expand description

A builder that allows us to construct a valid SeparableModel, which is an implementor of the SeparableNonlinearModel trait.

§Introduction

In the main crate we defined a separable model as a vector valued function $\vec{f}(\vec{\alpha},\vec{c})$, but we are going to deviate from this definition slightly here. We want to provide an independent variable $\vec{x}$ that the function depends on, to make a model usable on different supports.

To make the dependence on the independent variable explitict, we now writethe separable model as

 \vec{f}(\vec{x},\vec{\alpha},\vec{c}) = \sum_{j=1}^{N_{basis}} c_j \vec{f}_j(\vec{x},S_j(\alpha))

The basis functions $\vec{f}_j(\vec{x},S_j(\alpha))$ depend on the independent variable $\vec{x}$ and a subset $S_j(\alpha)$ of the nonlinear model parameters $\vec{\alpha}$ just as in the other notation.

§Usage

The SeparableModelBuilder is concerned with building a model from basis functions and their derivatives. This is done as a step by step process.

§Constructing an Empty Builder

The first step is to create an empty builder by specifying the complete set of nonlinear parameters that the model will be depending on. This is done by calling SeparableModelBuilder::new and specifying the list of parameters of the model by name.

§Adding Basis Functions to The Model

Basis functions come in two flavors. Those that depend on a subset of the nonlinear parameters $\vec{\alpha}$ and those that do not. Both function types have to obey certain rules to be considered valid:

Function Arguments and Output

  • The first argument of the function must be a reference to a &DVector type that accepts the independent variable (the $\vec{x}$ values) and the other parameters must be scalars that are the nonlinear parameters that the basis function depends on.

So if we want to model a basis function $\vec{f_1}(\vec{x},\vec{\alpha})$ where $\vec{\alpha}=(\alpha_1,\alpha_2)$ we would write the function in Rust as

fn f1(x: &DVector<f32>, alpha1: f32, alpha2: f32) -> DVector<f32> {
   // e.g. for sinusoidal function with frequency alpha1 and phase alpha2
   // apply the function elementwise to the vector x
   x.map(|x| f32::sin(alpha1*x+alpha2))
}

using single precision (f32) floats.

Linear Independence

The basis functions must be linearly independent. That means adding $\vec{f_1}(\vec{x})=\vec{x}$ and $\vec{f_1}(\vec{x})=2\,\vec{x}$ is forbidden. Adding functions that are lineary dependent will possibly destabilize the fitting process. the calculations. Adding linearly dependent functions is also a bad idea because it adds no value due to the linear superposition of the basis functions.

For some models, e.g. sums of exponential decays it might happen that the basis functions become linearly dependent for some combinations of nonlinear model parameters. This isn’t great but it is okay, since the VarPro algorithm in this crate exhibits a degree of robustness against basis functions becoming collinear (see LevMarProblemBuilder::epsilon).

§Invariant Basis Functions

Basis functions that do not depend on model parameters are treated specially. The library refers to them as invariant functions and they are added to a builder by calling SeparableModelBuilder::invariant_function. Since the basis function depends only on $\vec{x}$ it can be written as $\vec{f}_j(\vec{x})$. In Rust this translates to a signature Fn(&DVector<ScalarType>) -> DVector<ScalarType> + 'static for the callable.

Example: Calling SeparableModelBuilder::invariant_function adds the function to the model. These calls can be chained to add more functions.

use nalgebra::DVector;
use varpro::prelude::SeparableModelBuilder;
fn squared(x: &DVector<f64>) -> DVector<f64> {
    x.map(|x|x.powi(2))
}

let builder = SeparableModelBuilder::<f64>::new(&["alpha","beta"])
                // we can add an invariant function using a function pointer
                .invariant_function(squared)
                // or we can add it using a lambda
                .invariant_function(|x|x.map(|x|(x+1.).sin()));

Caveat: we cannot successfully build a model containing only invariant functions. It would make no sense to use the varpro library to fit such a model because that is purely a linear least squares problem. See the next section for adding parameter dependent functions.

§Nonlinear Basis Functions

The core functionality of the builder is to add basis functions to the model that depend nonlinearly on some (or all) of the model parameters $\vec{\alpha}$. We add a basis function to a builder by calling builder.function. Each call must be immediately followed by calls to partial_deriv for each of the parameters that the basis function depends on.

§Rules for Model Functions

There are several rules for adding model basis functions. One of them is enforced by the compiler, some of them are enforced at runtime (when trying to build the model) and others simply cannot be enforced by the library.

** Rules You Must Abide By **

  • Basis functions must be nonlinear in the parameters they take. If they aren’t, you can always rewrite the problem so that the linear parameters go in the coefficient vector $\vec{c}$. This means that each partial derivative also depend on all the parameters that the basis function depends on.

  • Derivatives must take the same parameter arguments and in the same order as the original basis function. This means if basis function $\vec{f}_j$ is given as $\vec{f}_j(\vec{x},a,b)$, then the derivatives must also be given with the parameters $a,b$ in the same order, i.e. $\partial/\partial a \vec{f}_j(\vec{x},a,b)$, $\partial/\partial b \vec{f}_j(\vec{x},a,b)$.

Rules Enforced at Compile Time

  • Partial derivatives cannot be added to invariant functions.

Rules Enforced at Runtime

  • A partial derivative must be given for each parameter that the basis function depends on.
  • Basis functions may only depend on the parameters that the model depends on.

The builder allows us to provide basis functions for a separable model as a step by step process.

§Example

Let’s build a model that is the sum of an exponential decay $\exp(-t/\tau)$ and a sine function $\sin(\omega t + \phi)$. The model depends on the parameters $\tau$, $\omega$ and $\phi$. The exponential decay depends only on $\tau$ and the sine function depends on $\omega$ and $\phi$. The model is given by

f(t,\tau,\omega,\phi) = \exp(-t/\tau) + \sin(\omega t + \phi)

which is a reasonable nontrivial model to demonstrate the usage of the library.

// exponential decay f(t,tau) = exp(-t/tau)
use nalgebra::{Scalar, DVector};
use num_traits::Float;
use varpro::prelude::SeparableModelBuilder;
pub fn exp_decay<ScalarType: Float + Scalar>(
    tvec: &DVector<ScalarType>,
    tau: ScalarType,
) -> DVector<ScalarType> {
    tvec.map(|t| (-t / tau).exp())
}

// derivative of exp decay with respect to tau
pub fn exp_decay_dtau<ScalarType: Scalar + Float>(
    tvec: &DVector<ScalarType>,
    tau: ScalarType,
) -> DVector<ScalarType> {
    tvec.map(|t| (-t / tau).exp() * t / (tau * tau))
}

// function sin (omega*t+phi)
pub fn sin_ometa_t_plus_phi<ScalarType: Scalar + Float>(
    tvec: &DVector<ScalarType>,
    omega: ScalarType,
    phi: ScalarType,
) -> DVector<ScalarType> {
    tvec.map(|t| (omega * t + phi).sin())
}

// derivative d/d(omega) sin (omega*t+phi)
pub fn sin_ometa_t_plus_phi_domega<ScalarType: Scalar + Float>(
    tvec: &DVector<ScalarType>,
    omega: ScalarType,
    phi: ScalarType,
) -> DVector<ScalarType> {
    tvec.map(|t| t * (omega * t + phi).cos())
}

// derivative d/d(phi) sin (omega*t+phi)
pub fn sin_ometa_t_plus_phi_dphi<ScalarType: Scalar + Float>(
    tvec: &DVector<ScalarType>,
    omega: ScalarType,
    phi: ScalarType,
) -> DVector<ScalarType> {
    tvec.map(|t| (omega * t + phi).cos())
}

let x_coords = DVector::from_vec(vec![0.,1.,2.,3.,4.,5.]);
let initial_guess = vec![1.,1.,1.];

let model = SeparableModelBuilder::<f64>::new(&["tau","omega","phi"])
              // the x coordintates that this model
              // is evaluated on
              .independent_variable(x_coords)
              // add the exp decay and all derivatives
              .function(&["tau"],exp_decay)
              .partial_deriv("tau",exp_decay_dtau)
              // a new call to function finalizes adding the previous function
              .function(&["omega","phi"],sin_ometa_t_plus_phi)
              .partial_deriv("phi", sin_ometa_t_plus_phi_dphi)
              .partial_deriv("omega",sin_ometa_t_plus_phi_domega)
              // we can also add invariant functions. Same as above, the
              // call tells the model builder that the previous function has all
              // the partial derivatives finished
              .invariant_function(|x|x.clone())
              // the initial nonlinear parameters
              // of the model
              .initial_parameters(initial_guess)
              // we build the model calling build. This returns either a valid model
              // or an error variant which is pretty helpful in understanding what went wrong
              .build().unwrap();

There is some special macro magic that allows us to pass a function $f(\vec{x},a_1,..,a_n)$ as any item that implements the Rust trait Fn(&DVector<ScalarType>, ScalarType,... ,ScalarType)->DVector<ScalarType> + 'static. This allows us to write the functions in an intuitive fashion in Rust code. All nonlinear parameters $\alpha$ are simply scalar arguments in the parameter list of the function. This works for functions taking up to 10 nonlinear arguments, but can be extended easily by modifying this crates source.

§Building a Model

The model is finalized and built using the SeparableModelBuilder::build method. This method returns a valid model or an error variant doing a pretty good job of explaning why the model is invalid.

Implementations§

Source§

impl<ScalarType> SeparableModelBuilder<ScalarType>
where ScalarType: Scalar,

Source

pub fn new<StrCollection>(parameter_names: StrCollection) -> Self
where StrCollection: IntoIterator, StrCollection::Item: AsRef<str>,

Create a new builder for a model that depends on this list of paramters The model parameters indices correspond to the order of appearance in here. Model parameter indices start at 0.

§Arguments
  • parameter_names A collection containing all the nonlinear model parameters
§Requirements on the Parameters
  • The list of parameters must only contain unique names
  • The list of parameter names must not be empty
  • Parameter names must not contain a comma. This is a precaution because &["alpha,beta"] most likely indicates a typo for &["alpha","beta"]. Any other form of punctuation is allowed.
Source

pub fn invariant_function<F>(self, function: F) -> Self
where F: Fn(&DVector<ScalarType>) -> DVector<ScalarType> + 'static,

Add a function $\vec{f}(\vec{x})$ to the model. In the varpro library this is called an invariant function because the model function is independent of the model parameters

§Usage

For usage see the documentation of the SeparableModelBuilder struct documentation.

Source

pub fn function<F, StrCollection, ArgList>( self, function_params: StrCollection, function: F, ) -> Self
where F: BasisFunction<ScalarType, ArgList> + 'static, StrCollection: IntoIterator, StrCollection::Item: AsRef<str>,

Add a function $\vec{f}(\vec{x},\alpha_1,...,\alpha_n)$ to the model that depends on the location parameter \vec{x} and nonlinear model parameters.

§Usage

For usage see the documentation of the SeparableModelBuilder struct documentation.

Source

pub fn partial_deriv<StrType: AsRef<str>, F, ArgList>( self, parameter: StrType, derivative: F, ) -> Self
where F: BasisFunction<ScalarType, ArgList> + 'static,

Add a partial derivative to a function, see the example in the documentation to this structure. A call to this function must only occur when it follows a call to function or another call to partial_derivative. Other cases will lead to errors when building the model.

Source

pub fn independent_variable(self, x: DVector<ScalarType>) -> Self

Set the independent variable $x$ which will be used when evaluating the model. Also see the struct documentation of SeparableModelBuilder for information on how to use this method.

Source

pub fn initial_parameters(self, initial_parameters: Vec<ScalarType>) -> Self

Set the initial values for the model parameters $\vec{\alpha}$. Also see the struct documentation of SeparableModelBuilder for information on how to use this method.

Source

pub fn build(self) -> Result<SeparableModel<ScalarType>, ModelBuildError>

Build a separable model from the contents of this builder.

§Result

A valid separable model or an error indicating why a valid model could not be constructed.

§Valid Models

A model is valid if the following conditions where upheld during construction

  • The list of parameters is valid (see SeparableModelBuilder::new)
  • Each basis function depends on valid parameters
  • Each basis function only depends on (a subset of) the parameters given on model construction
  • For each parameter in the model, there is at least one function that depends on it
§Order of the Basis Functions in the Model

Note The order of basis functions in the model is order in which the basis functions where provided during the builder stage. That means the first basis functions gets index 0 in the model, the second gets index 1 and so on.

Trait Implementations§

Source§

impl<ScalarType> From<ModelBuildError> for SeparableModelBuilder<ScalarType>
where ScalarType: Scalar,

create a SeparableModelBuilder which contains an error variant

Source§

fn from(err: ModelBuildError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl<ScalarType> Freeze for SeparableModelBuilder<ScalarType>

§

impl<ScalarType> !RefUnwindSafe for SeparableModelBuilder<ScalarType>

§

impl<ScalarType> !Send for SeparableModelBuilder<ScalarType>

§

impl<ScalarType> !Sync for SeparableModelBuilder<ScalarType>

§

impl<ScalarType> Unpin for SeparableModelBuilder<ScalarType>
where ScalarType: Unpin,

§

impl<ScalarType> !UnwindSafe for SeparableModelBuilder<ScalarType>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.