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(¶meter_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(¶m_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}