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

geo-ant / varpro / 6526131140

15 Oct 2023 07:49PM UTC coverage: 84.275% (-4.2%) from 88.49%
6526131140

push

github

web-flow
Feature/uncertainties of fit parameters (#24)



    Deprecate the minimize function of the LevMarSolver and
    replace it with fit, with a slightly different API.
    Add a function fit_with_statistics and a statistics module
    that calculates additional statistical information about the fit
    (if successful). It allows us to extract the estimated standard
    errors of the model parameters (both linear and nonlinear) but also
    provides more complete information like the covariance matrix and
    the correlation matrix.
    add more exhaustive tests
    add original varpro matlab code from DP O'Leary and BW Rust
    with permission
    bump benchmarking dependencies

209 of 209 new or added lines in 4 files covered. (100.0%)

686 of 814 relevant lines covered (84.28%)

767.31 hits per line

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

77.36
/src/model/builder/modelfunction_builder/mod.rs
1
#[cfg(test)]
2
mod test;
3

4
use nalgebra::base::Scalar;
5

6
use crate::basis_function::BasisFunction;
7
use crate::model::builder::error::ModelBuildError;
8
use crate::model::detail::{
9
    check_parameter_names, create_index_mapping, create_wrapped_basis_function,
10
};
11
use crate::model::model_basis_function::ModelBasisFunction;
12

13
/// This is a library internal helper that allows us to construct basis functions with
14
/// derivatives for a model. It makes sure that only valid model functions can be built.
15
/// Such a model function is valid when
16
/// * the model parameters are unique and non-empty
17
/// * the function parameters are unique, non-empty and a subset of the model params
18
/// * a derivative is provided for each parameter that the function depends on
19
/// (all other derivatives are zero, because the function does not depends on other params)
20
///
21
#[doc(hidden)]
22
pub(crate) struct ModelBasisFunctionBuilder<ScalarType>
23
where
24
    ScalarType: Scalar,
25
{
26
    /// the model parameters for the model this function belongs to
27
    model_parameters: Vec<String>,
28
    /// the parameters that the function depends on. Must be a subset of the model parameters
29
    function_parameters: Vec<String>,
30
    /// the current result of the building process of the model function
31
    model_function_result: Result<ModelBasisFunction<ScalarType>, ModelBuildError>,
32
}
33

34
impl<ScalarType> ModelBasisFunctionBuilder<ScalarType>
35
where
36
    ScalarType: Scalar,
37
{
38
    /// begin constructing a modelfunction for a specific model. The modelfunction must take
39
    /// a subset of the model parameters. This is the first step in creating a function, because
40
    /// the modelfunction must have partial derivatives specified for each parameter it takes.
41
    /// # Arguments
42
    /// * `model_parameters`: the model parameters of the model to which this function belongs. This is important
43
    /// so the builder understands how the parameters of the function relate to the parameters of the model.
44
    /// * `function_parameters`: the parameters that the function takes. Those must be in the order
45
    /// of the parameter vector. The paramters must not be empty, nor may they contain duplicates
46
    /// * `function`: the actual function.
47
    /// # Result
48
    /// A model builder that can be used to add derivatives.
49
    pub fn new<FuncType, StrCollection, ArgList>(
47✔
50
        model_parameters: Vec<String>,
51
        function_parameters: StrCollection,
52
        function: FuncType,
53
    ) -> Self
54
    where
55
        FuncType: BasisFunction<ScalarType, ArgList> + 'static,
56
        StrCollection: IntoIterator,
57
        StrCollection::Item: AsRef<str>,
58
    {
59
        let function_parameters: Vec<String> = function_parameters
47✔
60
            .into_iter()
61
            .map(|s| s.as_ref().to_string())
158✔
62
            .collect();
63
        // check that the function parameter list is valid, otherwise continue with an
64
        // internal error state
65
        if let Err(err) = check_parameter_names(&function_parameters) {
49✔
66
            return Self {
×
67
                model_function_result: Err(err),
×
68
                model_parameters,
×
69
                function_parameters,
×
70
            };
71
        }
72

73
        let model_function_result =
45✔
74
            create_wrapped_basis_function(&model_parameters, &function_parameters, function).map(
45✔
75
                |function| ModelBasisFunction {
88✔
76
                    function,
43✔
77
                    derivatives: Default::default(),
43✔
78
                },
79
            );
80

81
        Self {
82
            model_function_result,
83
            model_parameters,
84
            function_parameters: function_parameters.to_vec(),
45✔
85
        }
86
    }
87

88
    /// Add a derivative for the function with respect to the given parameter.
89
    /// # Arguments
90
    /// * `parameter`: the parameter with respect to which the derivative is taken.
91
    /// The parameter must be inside the set of model parameters. Furthermore the
92
    /// * `derivative`: the partial derivative of the function with which the
93
    /// builder was created.
94
    pub fn partial_deriv<FuncType, ArgList>(mut self, parameter: &str, derivative: FuncType) -> Self
95
    where
96
        FuncType: BasisFunction<ScalarType, ArgList> + 'static,
97
    {
98
        //this makes sure that the index of the derivative is calculated with respect to the
99
        //model parameter list while also making sure that the given derivative exists in the function
100
        //parameters
101
        if let Some((deriv_index_in_model, _)) = self
109✔
102
            .model_parameters
55✔
103
            .iter()
104
            .enumerate()
105
            .filter(|(_idx, model_param)| self.function_parameters.contains(model_param))
219✔
106
            .find(|(_idx, model_param)| model_param == &parameter)
180✔
107
        {
108
            if let Ok(model_function) = self.model_function_result.as_mut() {
54✔
109
                match create_wrapped_basis_function(
×
110
                    &self.model_parameters,
×
111
                    &self.function_parameters,
×
112
                    derivative,
×
113
                ) {
114
                    Ok(deriv) => {
54✔
115
                        // push derivative and check that the derivative was not already in the set
116
                        if model_function
54✔
117
                            .derivatives
54✔
118
                            .insert(deriv_index_in_model, deriv)
54✔
119
                            .is_some()
120
                        {
121
                            self.model_function_result =
1✔
122
                                Err(ModelBuildError::DuplicateDerivative {
1✔
123
                                    parameter: parameter.into(),
1✔
124
                                });
125
                        }
126
                    }
127
                    Err(error) => {
×
128
                        self.model_function_result = Err(error);
×
129
                    }
130
                }
131
            }
132
            self
54✔
133
        } else {
134
            Self {
135
                model_function_result: Err(ModelBuildError::InvalidDerivative {
1✔
136
                    parameter: parameter.into(),
137
                    function_parameters: self.function_parameters.clone(),
138
                }),
139
                ..self
140
            }
141
        }
142
    }
143

144
    /// Build a modelfunction with derivatives from the contents of this builder
145
    /// # Result
146
    /// A modelfunction containing the given function and derivatives or an error
147
    /// variant if an error occurred during the building process
148
    pub fn build(self) -> Result<ModelBasisFunction<ScalarType>, ModelBuildError> {
×
149
        self.check_completion()?;
49✔
150
        self.model_function_result
45✔
151
    }
152

153
    /// Helper function to check if the function builder carries a complete and valid function
154
    /// If the modelfunction builder is carrying an Error result, this function returns Ok(()).
155
    /// If it carries an ok variant, then this function checks that the modelfunction
156
    /// has all necessary derivatives provided and if so returns Ok(()), otherwise it returns
157
    /// an error indicating that there are missing derivatives.
158
    fn check_completion(&self) -> Result<(), ModelBuildError> {
47✔
159
        match self.model_function_result.as_ref() {
47✔
160
            Ok(modelfunction) => {
41✔
161
                // this should not go wrong, but to be safe
162
                check_parameter_names(self.model_parameters.as_slice())?;
41✔
163
                check_parameter_names(self.function_parameters.as_slice())?;
41✔
164
                // create the index mapping
165
                let index_mapping = create_index_mapping(
166
                    self.model_parameters.as_slice(),
41✔
167
                    self.function_parameters.as_slice(),
41✔
168
                )?;
169
                // now make sure that the derivatives are provided for all indices of that mapping
170
                for (index, parameter) in index_mapping.iter().zip(self.function_parameters.iter())
53✔
171
                {
172
                    if !modelfunction.derivatives.contains_key(index) {
53✔
173
                        return Err(ModelBuildError::MissingDerivative {
2✔
174
                            missing_parameter: parameter.clone(),
2✔
175
                            function_parameters: self.function_parameters.clone(),
2✔
176
                        });
177
                    }
178
                }
179
                // this is a sanity check. if this came this far, there should not be an error here
180
                if index_mapping.len() != modelfunction.derivatives.len() {
39✔
181
                    // this also should never be the case and indicates a programming error in the library
182
                    panic!("Incorrect number of derivatives in set. This indicates a logic error in this library.");
×
183
                }
184
                // otherwise
185
                Ok(())
39✔
186
            }
187
            Err(_) => Ok(()),
6✔
188
        }
189
    }
190
}
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