• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-ant / varpro / 16667578290

01 Aug 2025 05:59AM UTC coverage: 76.836% (-1.6%) from 78.389%
16667578290

Pull #50

github

web-flow
Merge bd3d004fd into f242b6e14
Pull Request #50: Feature/documentation improvements 2

5 of 5 new or added lines in 1 file covered. (100.0%)

34 existing lines in 7 files now uncovered.

743 of 967 relevant lines covered (76.84%)

2656.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.0
/src/model/builder/mod.rs
1
use nalgebra::{DVector, Scalar};
2

3
use crate::basis_function::BasisFunction;
4
use crate::model::builder::modelfunction_builder::ModelBasisFunctionBuilder;
5
use crate::model::detail::check_parameter_names;
6
use crate::model::model_basis_function::ModelBasisFunction;
7
use crate::model::SeparableModel;
8
use error::ModelBuildError;
9

10
mod modelfunction_builder;
11

12
#[cfg(any(test, doctest))]
13
mod test;
14

15
/// contains the error for the model builder
16
pub 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"]
252
pub enum SeparableModelBuilder<ScalarType>
253
where
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)]
277
pub 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
289
impl<ScalarType> From<ModelBuildError> for SeparableModelBuilder<ScalarType>
290
where
291
    ScalarType: Scalar,
292
{
293
    fn from(err: ModelBuildError) -> Self {
×
294
        Self::Error(err)
×
295
    }
296
}
297

298
impl<ScalarType> From<UnfinishedModel<ScalarType>> for SeparableModelBuilder<ScalarType>
299
where
300
    ScalarType: Scalar,
301
{
302
    #[inline]
303
    fn from(model: UnfinishedModel<ScalarType>) -> Self {
71✔
304
        Self::Normal(model)
71✔
305
    }
306
}
307

308
/// create a SeparableModelBuilder with the given result variant
309
impl<ScalarType> From<Result<UnfinishedModel<ScalarType>, ModelBuildError>>
310
    for SeparableModelBuilder<ScalarType>
311
where
312
    ScalarType: Scalar,
313
{
314
    #[inline]
315
    fn from(model_result: Result<UnfinishedModel<ScalarType>, ModelBuildError>) -> Self {
38✔
316
        match model_result {
38✔
317
            Ok(model) => Self::Normal(model),
37✔
318
            Err(err) => Self::Error(err),
2✔
319
        }
320
    }
321
}
322

323
impl<ScalarType> SeparableModelBuilder<ScalarType>
324
where
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
24✔
339
    where
340
        StrCollection: IntoIterator,
341
        StrCollection::Item: AsRef<str>,
342
    {
343
        let parameter_names: Vec<String> = parameter_names
72✔
344
            .into_iter()
345
            .map(|s| s.as_ref().to_string())
136✔
346
            .collect();
347

348
        if let Err(parameter_error) = check_parameter_names(&parameter_names) {
26✔
349
            Self::Error(parameter_error)
×
350
        } else {
351
            let model_result = UnfinishedModel {
352
                parameter_names,
353
                basefunctions: Vec::new(),
44✔
354
                x_vector: None,
355
                initial_parameters: None,
356
            };
357
            Self::from(model_result)
44✔
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
26✔
367
    where
368
        F: Fn(&DVector<ScalarType>) -> DVector<ScalarType> + 'static + Send + Sync,
369
    {
370
        match self {
26✔
371
            SeparableModelBuilder::Error(err) => Self::from(err),
×
372
            SeparableModelBuilder::Normal(mut model) => {
14✔
373
                model
14✔
374
                    .basefunctions
14✔
375
                    .push(ModelBasisFunction::parameter_independent(function));
42✔
376
                Self::from(model)
28✔
377
            }
378
            SeparableModelBuilder::FunctionBuilding {
×
379
                model,
12✔
380
                function_builder,
12✔
UNCOV
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)
72✔
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>(
61✔
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 {
61✔
408
            SeparableModelBuilder::Error(err) => Self::from(err),
×
409
            SeparableModelBuilder::Normal(model) => {
42✔
410
                let function_builder = ModelBasisFunctionBuilder::new(
411
                    model.parameter_names.clone(),
84✔
412
                    function_params,
42✔
413
                    function,
42✔
414
                );
415
                Self::FunctionBuilding {
416
                    model,
417
                    function_builder,
418
                }
419
            }
420
            SeparableModelBuilder::FunctionBuilding {
×
421
                model,
19✔
422
                function_builder,
19✔
423
            } => Self::from(extend_model(model, function_builder))
76✔
424
                .function(function_params, function),
57✔
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>(
52✔
434
        self,
435
        parameter: StrType,
436
        derivative: F,
437
    ) -> Self
438
    where
439
        F: BasisFunction<ScalarType, ArgList> + 'static,
440
    {
441
        match self {
52✔
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,
52✔
450
                function_builder,
52✔
451
            } => Self::FunctionBuilding {
452
                model,
453
                function_builder: function_builder.partial_deriv(parameter.as_ref(), derivative),
156✔
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 {
21✔
462
        match self {
21✔
463
            SeparableModelBuilder::Error(err) => Self::from(err),
×
464
            SeparableModelBuilder::Normal(mut model) => {
18✔
465
                model.x_vector = Some(x);
36✔
466
                Self::from(model)
36✔
467
            }
468
            SeparableModelBuilder::FunctionBuilding {
×
469
                model,
3✔
470
                function_builder,
3✔
471
            } => Self::from(extend_model(model, function_builder)).independent_variable(x),
18✔
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 {
21✔
479
        match self {
21✔
480
            SeparableModelBuilder::Error(err) => Self::from(err),
×
481
            SeparableModelBuilder::Normal(mut model) => {
18✔
482
                let expected = model.parameter_names.len();
54✔
483
                if expected != initial_parameters.len() {
36✔
484
                    Self::from(Err(ModelBuildError::IncorrectParameterCount {
2✔
485
                        expected,
2✔
486
                        actual: initial_parameters.len(),
1✔
487
                    }))
488
                } else {
489
                    model.initial_parameters = Some(initial_parameters);
17✔
UNCOV
490
                    Self::from(model)
×
491
                }
492
            }
493
            SeparableModelBuilder::FunctionBuilding {
×
494
                model,
3✔
495
                function_builder,
3✔
496
            } => Self::from(extend_model(model, function_builder))
12✔
497
                .initial_parameters(initial_parameters),
6✔
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> {
24✔
517
        match self {
24✔
518
            SeparableModelBuilder::Error(err) => Err(err),
3✔
519
            SeparableModelBuilder::Normal(model) => model.try_into(),
48✔
520
            SeparableModelBuilder::FunctionBuilding {
×
521
                model,
5✔
522
                function_builder,
5✔
523
            } => extend_model(model, function_builder).and_then(TryInto::try_into),
20✔
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.
535
impl<ScalarType: Scalar> TryInto<SeparableModel<ScalarType>> for UnfinishedModel<ScalarType> {
536
    type Error = ModelBuildError;
537
    fn try_into(self) -> Result<SeparableModel<ScalarType>, ModelBuildError> {
20✔
538
        if self.basefunctions.is_empty() {
40✔
539
            Err(ModelBuildError::EmptyModel)
1✔
540
        } else if self.parameter_names.is_empty() {
19✔
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() {
107✔
548
                if !self
44✔
549
                    .basefunctions
44✔
550
                    .iter()
44✔
551
                    .any(|function| function.derivatives.contains_key(&param_index))
269✔
552
                {
553
                    return Err(ModelBuildError::UnusedParameter {
1✔
554
                        parameter: parameter_name.clone(),
1✔
555
                    });
556
                }
557
            }
558
            let x_vector = self.x_vector.ok_or(ModelBuildError::MissingX)?;
36✔
559
            let initial_parameters = self
16✔
560
                .initial_parameters
×
561
                .ok_or(ModelBuildError::MissingInitialParameters)?;
1✔
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
576
fn extend_model<ScalarType: Scalar>(
42✔
577
    mut model: UnfinishedModel<ScalarType>,
578
    function_builder: ModelBasisFunctionBuilder<ScalarType>,
579
) -> Result<UnfinishedModel<ScalarType>, ModelBuildError> {
580
    let function = function_builder.build()?;
126✔
581
    model.basefunctions.push(function);
×
582
    Ok(model)
×
583
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc