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

geo-ant / varpro / 4842785308

pending completion
4842785308

push

github

Unknown Committer
Unknown Commit Message

349 of 349 new or added lines in 14 files covered. (100.0%)

592 of 669 relevant lines covered (88.49%)

966.01 hits per line

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

92.86
/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>(
45✔
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
45✔
60
            .into_iter()
61
            .map(|s| s.as_ref().to_string())
150✔
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) {
47✔
66
            return Self {
2✔
67
                model_function_result: Err(err),
2✔
68
                model_parameters,
2✔
69
                function_parameters,
2✔
70
            };
71
        }
72

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

81
        Self {
82
            model_function_result,
83
            model_parameters,
84
            function_parameters: function_parameters.to_vec(),
43✔
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
101✔
102
            .model_parameters
51✔
103
            .iter()
104
            .enumerate()
105
            .filter(|(_idx, model_param)| self.function_parameters.contains(model_param))
203✔
106
            .find(|(_idx, model_param)| model_param == &parameter)
166✔
107
        {
108
            if let Ok(model_function) = self.model_function_result.as_mut() {
100✔
109
                match create_wrapped_basis_function(
50✔
110
                    &self.model_parameters,
50✔
111
                    &self.function_parameters,
50✔
112
                    derivative,
50✔
113
                ) {
114
                    Ok(deriv) => {
50✔
115
                        // push derivative and check that the derivative was not already in the set
50✔
116
                        if model_function
50✔
117
                            .derivatives
50✔
118
                            .insert(deriv_index_in_model, deriv)
50✔
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
50✔
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()?;
47✔
150
        self.model_function_result
43✔
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> {
45✔
159
        match self.model_function_result.as_ref() {
45✔
160
            Ok(modelfunction) => {
39✔
161
                // this should not go wrong, but to be safe
39✔
162
                check_parameter_names(self.model_parameters.as_slice())?;
39✔
163
                check_parameter_names(self.function_parameters.as_slice())?;
39✔
164
                // create the index mapping
165
                let index_mapping = create_index_mapping(
166
                    self.model_parameters.as_slice(),
39✔
167
                    self.function_parameters.as_slice(),
39✔
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())
88✔
171
                {
172
                    if !modelfunction.derivatives.contains_key(index) {
49✔
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() {
37✔
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
37✔
185
                Ok(())
37✔
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