varpro/model/builder/
mod.rs

1use nalgebra::{DVector, Scalar};
2
3use crate::basis_function::BasisFunction;
4use crate::model::builder::modelfunction_builder::ModelBasisFunctionBuilder;
5use crate::model::detail::check_parameter_names;
6use crate::model::model_basis_function::ModelBasisFunction;
7use crate::model::SeparableModel;
8use error::ModelBuildError;
9
10mod modelfunction_builder;
11
12#[cfg(any(test, doctest))]
13mod test;
14
15/// contains the error for the model builder
16pub mod error;
17
18/// A builder that allows us to construct a valid [SeparableModel],
19/// which is an implementor of the [SeparableNonlinearModel](crate::model::SeparableNonlinearModel)
20/// trait.
21///
22/// # Introduction
23///
24/// In the main crate we defined a separable model as a vector valued function
25/// `$\vec{f}(\vec{\alpha},\vec{c})$`, but we are going to deviate from this
26/// definition slightly here. We want to provide an *independent variable* `$\vec{x}$`
27/// that the function depends on, to make a model usable on different supports.
28///
29/// To make the dependence on the independent variable explitict,
30/// we now writethe separable model as
31/// ```math
32///  \vec{f}(\vec{x},\vec{\alpha},\vec{c}) = \sum_{j=1}^{N_{basis}} c_j \vec{f}_j(\vec{x},S_j(\alpha))
33/// ```
34///
35/// The basis functions `$\vec{f}_j(\vec{x},S_j(\alpha))$` depend on the independent variable `$\vec{x}$`
36/// and *a subset* `$S_j(\alpha)$` of the *nonlinear* model parameters `$\vec{\alpha}$`
37/// just as in the other notation.
38///
39/// # Usage
40/// The SeparableModelBuilder is concerned with building a model from basis functions and their derivatives.
41/// This is done as a step by step process.
42///
43/// ## Constructing an Empty Builder
44/// The first step is to create an empty builder by specifying the complete set of *nonlinear* parameters that
45/// the model will be depending on. This is done by calling [SeparableModelBuilder::new](SeparableModelBuilder::new)
46/// and specifying the list of parameters of the model by name.
47///
48/// ## Adding Basis Functions to The Model
49///
50/// Basis functions come in two flavors. Those that depend on a subset of the nonlinear parameters
51/// `$\vec{\alpha}$` and those that do not. Both function types have to obey certain rules to
52/// be considered valid:
53///
54/// **Function Arguments and Output**
55///
56/// * The first argument of the function must be a reference to a `&DVector` type
57///   that accepts the independent variable (the `$\vec{x}$` values) and the other
58///   parameters must be scalars that are the nonlinear parameters that the basis
59///   function depends on.
60///
61/// So if we want to model a basis function `$\vec{f_1}(\vec{x},\vec{\alpha})$`
62/// where `$\vec{\alpha}=(\alpha_1,\alpha_2)$` we would write the function in Rust as
63///
64/// ```rust
65/// # use nalgebra::DVector;
66/// fn f1(x: &DVector<f32>, alpha1: f32, alpha2: f32) -> DVector<f32> {
67///    // e.g. for sinusoidal function with frequency alpha1 and phase alpha2
68///    // apply the function elementwise to the vector x
69///    x.map(|x| f32::sin(alpha1*x+alpha2))
70/// }
71/// ```
72/// using single precision (`f32`) floats.
73///
74/// **Linear Independence**
75///
76/// The basis functions must be linearly independent. That means adding `$\vec{f_1}(\vec{x})=\vec{x}$`
77/// and `$\vec{f_1}(\vec{x})=2\,\vec{x}$` is forbidden. Adding functions that
78/// are lineary dependent will possibly destabilize the fitting process.
79/// the calculations. Adding linearly dependent functions is also a bad idea
80/// because it adds no value due to the linear superposition of the basis functions.
81///
82/// For some models, e.g. sums of exponential decays it might happen that the basis functions become
83/// linearly dependent *for some combinations* of nonlinear model parameters. This isn't great but it is
84/// okay, since the VarPro algorithm in this crate exhibits a degree of robustness against basis functions
85/// becoming collinear (see [SeparableProblemBuilder::epsilon](crate::problem::SeparableProblemBuilder::epsilon)).
86///
87/// ### Invariant Basis Functions
88///
89/// Basis functions that do not depend on model parameters are treated specially. The library refers
90/// to them as *invariant functions* and they are added to a builder by calling
91/// [SeparableModelBuilder::invariant_function](SeparableModelBuilder::invariant_function). Since
92/// the basis function depends only on `$\vec{x}$` it can be written as `$\vec{f}_j(\vec{x})$`. In Rust
93/// this translates to a signature `Fn(&DVector<ScalarType>) -> DVector<ScalarType> + 'static` for the callable.
94///
95/// **Example**: Calling [SeparableModelBuilder::invariant_function](SeparableModelBuilder::invariant_function)
96/// adds the function to the model. These calls can be chained to add more functions.
97///
98/// ```rust
99/// use nalgebra::DVector;
100/// use varpro::prelude::SeparableModelBuilder;
101/// fn squared(x: &DVector<f64>) -> DVector<f64> {
102///     x.map(|x|x.powi(2))
103/// }
104///
105/// let builder = SeparableModelBuilder::<f64>::new(&["alpha","beta"])
106///                 // we can add an invariant function using a function pointer
107///                 .invariant_function(squared)
108///                 // or we can add it using a lambda
109///                 .invariant_function(|x|x.map(|x|(x+1.).sin()));
110///```
111/// Caveat: we cannot successfully build a model containing only invariant functions. It would
112/// make no sense to use the varpro library to fit such a model because that is purely a linear
113/// least squares problem. See the next section for adding parameter dependent functions.
114///
115/// ### Nonlinear Basis Functions
116///
117/// The core functionality of the builder is to add basis functions to the model
118/// that depend nonlinearly on some (or all) of the model parameters `$\vec{\alpha}$`.
119/// We add a basis function to a builder by calling `builder.function`. Each call must
120/// be immediately followed by calls to `partial_deriv` for each of the parameters that the basis
121/// function depends on.
122///
123/// #### Rules for Model Functions
124///
125/// There are several rules for adding model basis functions. One of them is enforced by the compiler,
126/// some of them are enforced at runtime (when trying to build the model) and others simply cannot
127/// be enforced by the library.
128///
129/// ** Rules You Must Abide By **
130///
131/// * Basis functions must be **nonlinear** in the parameters they take. If they aren't, you can always
132///   rewrite the problem so that the linear parameters go in the coefficient vector `$\vec{c}$`. This
133///   means that each partial derivative also depend on all the parameters that the basis function depends
134///   on.
135///
136/// * Derivatives must take the same parameter arguments *and in the same order* as the original
137///   basis function. This means if basis function `$\vec{f}_j$` is given as `$\vec{f}_j(\vec{x},a,b)$`,
138///   then the derivatives must also be given with the parameters `$a,b$` in the same order, i.e.
139///   `$\partial/\partial a \vec{f}_j(\vec{x},a,b)$`, `$\partial/\partial b \vec{f}_j(\vec{x},a,b)$`.
140///
141/// **Rules Enforced at Compile Time**
142///
143/// * Partial derivatives cannot be added to invariant functions.
144///
145/// **Rules Enforced at Runtime**
146///
147/// * A partial derivative must be given for each parameter that the basis function depends on.
148/// * Basis functions may only depend on the parameters that the model depends on.
149///
150///
151/// The builder allows us to provide basis functions for a separable model as a step by step process.
152///
153/// ## Example
154///
155/// Let's build a model that is the sum of an exponential decay `$\exp(-t/\tau)$`
156/// and a sine  function `$\sin(\omega t + \phi)$`. The model depends on the parameters `$\tau$`,
157/// `$\omega$` and `$\phi$`. The exponential decay depends only on `$\tau$` and the sine function
158/// depends on `$\omega$` and `$\phi$`. The model is given by
159///
160/// ```math
161/// f(t,\tau,\omega,\phi) = \exp(-t/\tau) + \sin(\omega t + \phi)
162/// ```
163/// which is a reasonable nontrivial model to demonstrate the usage of the library.
164///
165/// ```rust
166/// // exponential decay f(t,tau) = exp(-t/tau)
167/// use nalgebra::{Scalar, DVector};
168/// use num_traits::Float;
169/// use varpro::prelude::SeparableModelBuilder;
170/// pub fn exp_decay<ScalarType: Float + Scalar>(
171///     tvec: &DVector<ScalarType>,
172///     tau: ScalarType,
173/// ) -> DVector<ScalarType> {
174///     tvec.map(|t| (-t / tau).exp())
175/// }
176///
177/// // derivative of exp decay with respect to tau
178/// pub fn exp_decay_dtau<ScalarType: Scalar + Float>(
179///     tvec: &DVector<ScalarType>,
180///     tau: ScalarType,
181/// ) -> DVector<ScalarType> {
182///     tvec.map(|t| (-t / tau).exp() * t / (tau * tau))
183/// }
184///
185/// // function sin (omega*t+phi)
186/// pub fn sin_ometa_t_plus_phi<ScalarType: Scalar + Float>(
187///     tvec: &DVector<ScalarType>,
188///     omega: ScalarType,
189///     phi: ScalarType,
190/// ) -> DVector<ScalarType> {
191///     tvec.map(|t| (omega * t + phi).sin())
192/// }
193///
194/// // derivative d/d(omega) sin (omega*t+phi)
195/// pub fn sin_ometa_t_plus_phi_domega<ScalarType: Scalar + Float>(
196///     tvec: &DVector<ScalarType>,
197///     omega: ScalarType,
198///     phi: ScalarType,
199/// ) -> DVector<ScalarType> {
200///     tvec.map(|t| t * (omega * t + phi).cos())
201/// }
202///
203/// // derivative d/d(phi) sin (omega*t+phi)
204/// pub fn sin_ometa_t_plus_phi_dphi<ScalarType: Scalar + Float>(
205///     tvec: &DVector<ScalarType>,
206///     omega: ScalarType,
207///     phi: ScalarType,
208/// ) -> DVector<ScalarType> {
209///     tvec.map(|t| (omega * t + phi).cos())
210/// }
211///
212/// let x_coords = DVector::from_vec(vec![0.,1.,2.,3.,4.,5.]);
213/// let initial_guess = vec![1.,1.,1.];
214///
215/// let model = SeparableModelBuilder::<f64>::new(&["tau","omega","phi"])
216///               // the x coordintates that this model
217///               // is evaluated on
218///               .independent_variable(x_coords)
219///               // add the exp decay and all derivatives
220///               .function(&["tau"],exp_decay)
221///               .partial_deriv("tau",exp_decay_dtau)
222///               // a new call to function finalizes adding the previous function
223///               .function(&["omega","phi"],sin_ometa_t_plus_phi)
224///               .partial_deriv("phi", sin_ometa_t_plus_phi_dphi)
225///               .partial_deriv("omega",sin_ometa_t_plus_phi_domega)
226///               // we can also add invariant functions. Same as above, the
227///               // call tells the model builder that the previous function has all
228///               // the partial derivatives finished
229///               .invariant_function(|x|x.clone())
230///               // the initial nonlinear parameters
231///               // of the model
232///               .initial_parameters(initial_guess)
233///               // we build the model calling build. This returns either a valid model
234///               // or an error variant which is pretty helpful in understanding what went wrong
235///               .build().unwrap();
236/// ```
237///
238/// There is some [special macro magic](https://geo-ant.github.io/blog/2021/rust-traits-and-variadic-functions/)
239/// that allows us to pass a function `$f(\vec{x},a_1,..,a_n)$`
240/// as any item that implements the Rust trait `Fn(&DVector<ScalarType>, ScalarType,... ,ScalarType)->DVector<ScalarType> + 'static`.
241/// This allows us to write the functions in an intuitive fashion in Rust code. All nonlinear parameters `$\alpha$`
242/// are simply scalar arguments in the parameter list of the function. This works for functions
243/// taking up to 10 nonlinear arguments, but can be extended easily by modifying this crates source.
244///
245///
246/// ## Building a Model
247///
248/// The model is finalized and built using the [SeparableModelBuilder::build](SeparableModelBuilder::build)
249/// method. This method returns a valid model or an error variant doing a pretty good job of
250/// explaning why the model is invalid.
251#[must_use = "The builder should be transformed into a model using the build() method"]
252pub enum SeparableModelBuilder<ScalarType>
253where
254    ScalarType: Scalar,
255{
256    #[doc(hidden)]
257    /// builder is in an error state
258    Error(ModelBuildError),
259    /// builder is in normal state (i.e. NOT in the process of building a function and not in an error state)
260    #[doc(hidden)]
261    Normal(UnfinishedModel<ScalarType>),
262    /// builder is in the state of building a function
263    #[doc(hidden)]
264    FunctionBuilding {
265        /// the currently held model
266        model: UnfinishedModel<ScalarType>,
267        /// the function we are currently building. It is added
268        /// to the model once a builder function is called that
269        /// indicates that building this function is over
270        function_builder: ModelBasisFunctionBuilder<ScalarType>,
271    },
272}
273
274/// a helper structure that represents an unfinished separable model
275#[derive(Default)]
276#[doc(hidden)]
277pub struct UnfinishedModel<ScalarType: Scalar> {
278    /// the parameter names
279    parameter_names: Vec<String>,
280    /// the base functions
281    basefunctions: Vec<ModelBasisFunction<ScalarType>>,
282    /// the x-vector (independent variable associated with the model)
283    x_vector: Option<DVector<ScalarType>>,
284    /// the initial guesses for the parameters
285    initial_parameters: Option<Vec<ScalarType>>,
286}
287
288/// create a SeparableModelBuilder which contains an error variant
289impl<ScalarType> From<ModelBuildError> for SeparableModelBuilder<ScalarType>
290where
291    ScalarType: Scalar,
292{
293    fn from(err: ModelBuildError) -> Self {
294        Self::Error(err)
295    }
296}
297
298impl<ScalarType> From<UnfinishedModel<ScalarType>> for SeparableModelBuilder<ScalarType>
299where
300    ScalarType: Scalar,
301{
302    #[inline]
303    fn from(model: UnfinishedModel<ScalarType>) -> Self {
304        Self::Normal(model)
305    }
306}
307
308/// create a SeparableModelBuilder with the given result variant
309impl<ScalarType> From<Result<UnfinishedModel<ScalarType>, ModelBuildError>>
310    for SeparableModelBuilder<ScalarType>
311where
312    ScalarType: Scalar,
313{
314    #[inline]
315    fn from(model_result: Result<UnfinishedModel<ScalarType>, ModelBuildError>) -> Self {
316        match model_result {
317            Ok(model) => Self::Normal(model),
318            Err(err) => Self::Error(err),
319        }
320    }
321}
322
323impl<ScalarType> SeparableModelBuilder<ScalarType>
324where
325    ScalarType: Scalar,
326{
327    /// Create a new builder for a model that depends on this list of paramters
328    /// The model parameters indices correspond to the order of appearance in here.
329    /// Model parameter indices start at 0.
330    /// # Arguments
331    /// * `parameter_names` A collection containing all the nonlinear model parameters
332    /// # Requirements on the Parameters
333    /// * The list of parameters must only contain unique names
334    /// * The list of parameter names must not be empty
335    /// * Parameter names must not contain a comma. This is a precaution because
336    ///   `&["alpha,beta"]` most likely indicates a typo for `&["alpha","beta"]`. Any other form
337    ///   of punctuation is allowed.
338    pub fn new<StrCollection>(parameter_names: StrCollection) -> Self
339    where
340        StrCollection: IntoIterator,
341        StrCollection::Item: AsRef<str>,
342    {
343        let parameter_names: Vec<String> = parameter_names
344            .into_iter()
345            .map(|s| s.as_ref().to_string())
346            .collect();
347
348        if let Err(parameter_error) = check_parameter_names(&parameter_names) {
349            Self::Error(parameter_error)
350        } else {
351            let model_result = UnfinishedModel {
352                parameter_names,
353                basefunctions: Vec::new(),
354                x_vector: None,
355                initial_parameters: None,
356            };
357            Self::from(model_result)
358        }
359    }
360
361    /// Add a function `$\vec{f}(\vec{x})$` to the model. In the `varpro` library this is called
362    /// an *invariant* function because the model function is independent of the model parameters
363    /// # Usage
364    /// For usage see the documentation of the [SeparableModelBuilder]
365    /// struct documentation.
366    pub fn invariant_function<F>(self, function: F) -> Self
367    where
368        F: Fn(&DVector<ScalarType>) -> DVector<ScalarType> + 'static + Send + Sync,
369    {
370        match self {
371            SeparableModelBuilder::Error(err) => Self::from(err),
372            SeparableModelBuilder::Normal(mut model) => {
373                model
374                    .basefunctions
375                    .push(ModelBasisFunction::parameter_independent(function));
376                Self::from(model)
377            }
378            SeparableModelBuilder::FunctionBuilding {
379                model,
380                function_builder,
381            } =>
382            // this will not be an infinite recursion because it goes to either the normal
383            // or error state. This template can be used pretty much everywhere, because
384            // we just extend the model (finalize the function builder) and then invoke the
385            // called function again
386            {
387                Self::from(extend_model(model, function_builder)).invariant_function(function)
388            }
389        }
390    }
391
392    /// Add a function `$\vec{f}(\vec{x},\alpha_1,...,\alpha_n)$` to the model that depends on the
393    /// location parameter `\vec{x}` and nonlinear model parameters.
394    /// # Usage
395    /// For usage see the documentation of the [SeparableModelBuilder]
396    /// struct documentation.
397    pub fn function<F, StrCollection, ArgList>(
398        self,
399        function_params: StrCollection,
400        function: F,
401    ) -> Self
402    where
403        F: BasisFunction<ScalarType, ArgList> + 'static,
404        StrCollection: IntoIterator,
405        StrCollection::Item: AsRef<str>,
406    {
407        match self {
408            SeparableModelBuilder::Error(err) => Self::from(err),
409            SeparableModelBuilder::Normal(model) => {
410                let function_builder = ModelBasisFunctionBuilder::new(
411                    model.parameter_names.clone(),
412                    function_params,
413                    function,
414                );
415                Self::FunctionBuilding {
416                    model,
417                    function_builder,
418                }
419            }
420            SeparableModelBuilder::FunctionBuilding {
421                model,
422                function_builder,
423            } => Self::from(extend_model(model, function_builder))
424                .function(function_params, function),
425        }
426    }
427
428    /// Add a partial derivative to a function, see the example in the documentation
429    /// to this structure.
430    /// A call to this function must only occur when it follows a call to
431    /// `function` or another call to `partial_derivative`. Other cases will
432    /// lead to errors when building the model.
433    pub fn partial_deriv<StrType: AsRef<str>, F, ArgList>(
434        self,
435        parameter: StrType,
436        derivative: F,
437    ) -> Self
438    where
439        F: BasisFunction<ScalarType, ArgList> + 'static,
440    {
441        match self {
442            SeparableModelBuilder::Error(err) => Self::from(err),
443            SeparableModelBuilder::Normal(_model) => {
444                // only when we are in the process of building a function, may
445                // we call this function
446                Self::from(Err(ModelBuildError::IllegalCallToPartialDeriv))
447            }
448            SeparableModelBuilder::FunctionBuilding {
449                model,
450                function_builder,
451            } => Self::FunctionBuilding {
452                model,
453                function_builder: function_builder.partial_deriv(parameter.as_ref(), derivative),
454            },
455        }
456    }
457
458    /// Set the independent variable `$x$` which will be used when evaluating the model.
459    /// Also see the struct documentation of [SeparableModelBuilder]
460    /// for information on how to use this method.
461    pub fn independent_variable(self, x: DVector<ScalarType>) -> Self {
462        match self {
463            SeparableModelBuilder::Error(err) => Self::from(err),
464            SeparableModelBuilder::Normal(mut model) => {
465                model.x_vector = Some(x);
466                Self::from(model)
467            }
468            SeparableModelBuilder::FunctionBuilding {
469                model,
470                function_builder,
471            } => Self::from(extend_model(model, function_builder)).independent_variable(x),
472        }
473    }
474
475    /// Set the initial values for the model parameters `$\vec{\alpha}$`.
476    /// Also see the struct documentation of [SeparableModelBuilder]
477    /// for information on how to use this method.
478    pub fn initial_parameters(self, initial_parameters: Vec<ScalarType>) -> Self {
479        match self {
480            SeparableModelBuilder::Error(err) => Self::from(err),
481            SeparableModelBuilder::Normal(mut model) => {
482                let expected = model.parameter_names.len();
483                if expected != initial_parameters.len() {
484                    Self::from(Err(ModelBuildError::IncorrectParameterCount {
485                        expected,
486                        actual: initial_parameters.len(),
487                    }))
488                } else {
489                    model.initial_parameters = Some(initial_parameters);
490                    Self::from(model)
491                }
492            }
493            SeparableModelBuilder::FunctionBuilding {
494                model,
495                function_builder,
496            } => Self::from(extend_model(model, function_builder))
497                .initial_parameters(initial_parameters),
498        }
499    }
500
501    /// Build a separable model from the contents of this builder.
502    /// # Result
503    /// A valid separable model or an error indicating why a valid model could not be constructed.
504    ///
505    /// ## Valid Models
506    /// A model is valid if the following conditions where upheld during construction
507    /// * The list of parameters is valid (see [SeparableModelBuilder::new](SeparableModelBuilder::new))
508    /// * Each basis function depends on valid parameters
509    /// * Each basis function only depends on (a subset of) the parameters given on model construction
510    /// * For each parameter in the model, there is at least one function that depends on it
511    ///
512    /// # Order of the Basis Functions in the Model
513    /// **Note** The order of basis functions in the model is order in which the basis functions
514    /// where provided during the builder stage. That means the first basis functions gets index `0` in
515    /// the model, the second gets index `1` and so on.
516    pub fn build(self) -> Result<SeparableModel<ScalarType>, ModelBuildError> {
517        match self {
518            SeparableModelBuilder::Error(err) => Err(err),
519            SeparableModelBuilder::Normal(model) => model.try_into(),
520            SeparableModelBuilder::FunctionBuilding {
521                model,
522                function_builder,
523            } => extend_model(model, function_builder).and_then(TryInto::try_into),
524        }
525    }
526}
527
528/// try to convert an unfinished model into a valid model
529/// # Returns
530/// If the model is valid, then the model is returned as an ok variant, otherwise an error variant
531/// A model is valid, when
532///  * the model has at least one modelfunction, and
533///  * for each model parameter we have at least one function that depends on this
534///    parameter.
535impl<ScalarType: Scalar> TryInto<SeparableModel<ScalarType>> for UnfinishedModel<ScalarType> {
536    type Error = ModelBuildError;
537    fn try_into(self) -> Result<SeparableModel<ScalarType>, ModelBuildError> {
538        if self.basefunctions.is_empty() {
539            Err(ModelBuildError::EmptyModel)
540        } else if self.parameter_names.is_empty() {
541            Err(ModelBuildError::EmptyParameters)
542        } else {
543            // now check that all model parameters are referenced in at least one parameter of one
544            // of the given model functions. We do this by checking the indices of the derivatives
545            // because each model parameter must occur at least once as a key in at least one of the
546            // modelfunctions derivatives
547            for (param_index, parameter_name) in self.parameter_names.iter().enumerate() {
548                if !self
549                    .basefunctions
550                    .iter()
551                    .any(|function| function.derivatives.contains_key(&param_index))
552                {
553                    return Err(ModelBuildError::UnusedParameter {
554                        parameter: parameter_name.clone(),
555                    });
556                }
557            }
558            let x_vector = self.x_vector.ok_or(ModelBuildError::MissingX)?;
559            let initial_parameters = self
560                .initial_parameters
561                .ok_or(ModelBuildError::MissingInitialParameters)?;
562            // otherwise return this model
563            Ok(SeparableModel {
564                parameter_names: self.parameter_names,
565                basefunctions: self.basefunctions,
566                x_vector,
567                current_parameters: DVector::from_vec(initial_parameters),
568            })
569        }
570    }
571}
572
573/// try and extend a model with the given function in the builder
574/// if building the function in the builder fails, an error is returned,
575/// otherwise the extended model is returned
576fn extend_model<ScalarType: Scalar>(
577    mut model: UnfinishedModel<ScalarType>,
578    function_builder: ModelBasisFunctionBuilder<ScalarType>,
579) -> Result<UnfinishedModel<ScalarType>, ModelBuildError> {
580    let function = function_builder.build()?;
581    model.basefunctions.push(function);
582    Ok(model)
583}