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

geo-ant / varpro / 14804370657

02 May 2025 10:26PM UTC coverage: 78.389% (-2.9%) from 81.338%
14804370657

push

github

web-flow
Feature/cleanup refactorings (#45)

* rip out parallel code

* remove more remnants of the parallel computations

* rename levmar problem builder to separable problem builder

* rename levmar problem to separable problem

* factor out separable problem into its own module

* refactor problem and builder to own module

* performance gains for MRHS through reordering matrix computations

* prepare typestate mrhs

* more rhs typestate refactor

* fix  tests and benches

* fix warnings

* docs are overindented: jail. Docs are underindented, believe it or not... jail

* fix more and more doctests

* fix doctests

* remove mentions of levmar problem

* documentation updates

* more doc updates

* factor out fit result into own module

* remove unused imports

* update changelog

* update toml and update changelog

* update readme

* doc updates

* fmt

* fix clippy lints

* add integration test for uncovered code path

* rewrite some things

79 of 117 new or added lines in 9 files covered. (67.52%)

33 existing lines in 4 files now uncovered.

798 of 1018 relevant lines covered (78.39%)

1296.79 hits per line

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

71.43
/src/problem.rs
1
use crate::prelude::*;
2
use crate::util::Weights;
3
use nalgebra::{
4
    ComplexField, DMatrix, DefaultAllocator, DimMin, MatrixView, SVD, Scalar, VectorView,
5
};
6
use nalgebra::{Dim, Dyn};
7

8
mod builder;
9

10
pub use builder::LevMarBuilderError;
11
pub use builder::SeparableProblemBuilder;
12

13
/// trait describing the type of right hand side for the problem, meaning either
14
/// a single right hand side or multiple right hand sides. The latter implies
15
/// global fitting.
16
pub trait RhsType {}
17

18
/// This type indicates that the associated problem has a single (vector) right hand side.
19
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
20
pub struct SingleRhs;
21

22
/// This type indicates that the associated problem has multiple right hand sides
23
/// and thus performs global fitting for the nonlinear parameters.
24
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
25
pub struct MultiRhs;
26

27
impl RhsType for MultiRhs {}
28
impl RhsType for SingleRhs {}
29

30
/// This is a the problem of fitting the separable model to data in a form that the
31
/// [levenberg_marquardt](https://crates.io/crates/levenberg-marquardt) crate can use it to
32
/// perform the least squares fit.
33
///
34
/// # Construction
35
///
36
/// Use the [SeparableProblemBuilder](self::builder::SeparableProblemBuilder) to create an instance of a
37
/// levmar problem.
38
///
39
/// # Usage
40
///
41
/// After obtaining an instance of `SeparableProblem` we can pass it to the [LevenbergMarquardt](levenberg_marquardt::LevenbergMarquardt)
42
/// structure of the levenberg_marquardt crate for minimization. Refer to the documentation of the
43
/// [levenberg_marquardt](https://crates.io/crates/levenberg-marquardt) for an overview. A usage example
44
/// is provided in this crate documentation as well. The [LevenbergMarquardt](levenberg_marquardt::LevenbergMarquardt)
45
/// solver is reexported by this module as [LevMarSolver](self::LevMarSolver) for naming consistency.
46
///
47
/// # `MRHS`: Multiple Right Hand Sides
48
///
49
/// The problem generic on the boolean `MRHS` which indicates whether the
50
/// problem fits a single (`MRHS == false`) or multiple (`MRHS == true`) right
51
/// hand sides. This is decided during the building process. The underlying
52
/// math does not change, but the interface changes to use vectors for coefficients
53
/// and data in case of a single right hand side. For multiple right hand sides,
54
/// the coefficients and the data are matrices corresponding to columns of
55
/// coefficient vectors and data vectors respectively.
56
#[derive(Clone)]
57
#[allow(non_snake_case)]
58
pub struct SeparableProblem<Model, Rhs: RhsType>
59
where
60
    Model: SeparableNonlinearModel,
61
    Model::ScalarType: Scalar + ComplexField + Copy,
62
{
63
    /// the *weighted* data matrix to which to fit the model `$\boldsymbol{Y}_w$`.
64
    /// It is a matrix so it can accomodate multiple right hand sides. If
65
    /// the problem has only a single right hand side (MRHS = false), this is just
66
    /// a matrix with one column. The underlying math does not change in either case.
67
    /// **Attention** the data matrix is weighted with the weights if some weights
68
    /// where provided (otherwise it is unweighted)
69
    pub(crate) Y_w: DMatrix<Model::ScalarType>,
70
    /// a reference to the separable model we are trying to fit to the data
71
    pub(crate) model: Model,
72
    /// truncation epsilon for SVD below which all singular values are assumed zero
73
    pub(crate) svd_epsilon: <Model::ScalarType as ComplexField>::RealField,
74
    /// the weights of the data. If none are given, the data is not weighted
75
    /// If weights were provided, the builder has checked that the weights have the
76
    /// correct dimension for the data
77
    pub(crate) weights: Weights<Model::ScalarType, Dyn>,
78
    /// the currently cached calculations belonging to the currently set model parameters
79
    /// those are updated on set_params. If this is None, then it indicates some error that
80
    /// is propagated on to the levenberg-marquardt crate by also returning None results
81
    /// by residuals() and/or jacobian()
82
    pub(crate) cached: Option<CachedCalculations<Model::ScalarType, Dyn, Dyn>>,
83
    phantom: std::marker::PhantomData<Rhs>,
84
}
85

86
/// helper structure that stores the cached calculations,
87
/// which are carried out by the SeparableProblem on setting the parameters
88
#[derive(Debug, Clone)]
89
pub(crate) struct CachedCalculations<ScalarType, ModelDim, OutputDim>
90
where
91
    ScalarType: Scalar + ComplexField,
92
    ModelDim: Dim,
93
    OutputDim: Dim + nalgebra::DimMin<ModelDim>,
94
    DefaultAllocator: nalgebra::allocator::Allocator<ModelDim>,
95
    DefaultAllocator: nalgebra::allocator::Allocator<OutputDim>,
96
    DefaultAllocator:
97
        nalgebra::allocator::Allocator<<OutputDim as DimMin<ModelDim>>::Output, ModelDim>,
98
    DefaultAllocator:
99
        nalgebra::allocator::Allocator<OutputDim, <OutputDim as DimMin<ModelDim>>::Output>,
100
    DefaultAllocator: nalgebra::allocator::Allocator<<OutputDim as DimMin<ModelDim>>::Output>,
101
{
102
    /// The current residual matrix of model function values belonging to the current parameters
103
    pub(crate) current_residuals: DMatrix<ScalarType>,
104
    /// Singular value decomposition of the current function value matrix
105
    pub(crate) current_svd: SVD<ScalarType, OutputDim, ModelDim>,
106
    /// the linear coefficients `$\boldsymbol C$` providing the current best fit
107
    pub(crate) linear_coefficients: DMatrix<ScalarType>,
108
}
109

110
impl<Model, Rhs: RhsType> std::fmt::Debug for SeparableProblem<Model, Rhs>
111
where
112
    Model: SeparableNonlinearModel,
113
    Model::ScalarType: Scalar + ComplexField + Copy,
114
{
115
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
116
        f.debug_struct("SeparableProblem")
1✔
117
            .field("y_w", &self.Y_w)
1✔
118
            .field("model", &"/* omitted */")
1✔
119
            .field("svd_epsilon", &self.svd_epsilon)
1✔
120
            .field("weights", &self.weights)
1✔
121
            .field("cached", &self.cached)
1✔
122
            .finish()
123
    }
124
}
125

126
impl<Model> SeparableProblem<Model, MultiRhs>
127
where
128
    Model: SeparableNonlinearModel,
129
    Model::ScalarType: Scalar + ComplexField + Copy,
130
{
131
    /// Get the linear coefficients for the current problem. After a successful pass of the solver,
132
    /// this contains a value with the best fitting linear coefficients
133
    ///
134
    /// # Returns
135
    ///
136
    /// Either the current best estimate coefficients or None, if none were calculated or the solver
137
    /// encountered an error. After the solver finished, this is the least squares best estimate
138
    /// for the linear coefficients of the base functions.
139
    ///
140
    /// Since this method is for fitting a single right hand side, the coefficients
141
    /// are a single column vector.
NEW
142
    pub fn linear_coefficients(&self) -> Option<MatrixView<Model::ScalarType, Dyn, Dyn>> {
×
NEW
143
        self.cached
×
144
            .as_ref()
NEW
145
            .map(|cache| cache.linear_coefficients.as_view())
×
146
    }
147

148
    /// the weighted data matrix`$\boldsymbol{Y}_w$` to which to fit the model. Note
149
    /// that the weights are already applied to the data matrix and this
150
    /// is not the original data vector.
151
    ///
152
    /// This method is for fitting a multiple right hand sides, hence the data
153
    /// matrix is a matrix that contains the right hand sides as columns.
NEW
154
    pub fn weighted_data(&self) -> MatrixView<Model::ScalarType, Dyn, Dyn> {
×
NEW
155
        self.Y_w.as_view()
×
156
    }
157
}
158

159
impl<Model> SeparableProblem<Model, SingleRhs>
160
where
161
    Model: SeparableNonlinearModel,
162
    Model::ScalarType: Scalar + ComplexField + Copy,
163
{
164
    /// Get the linear coefficients for the current problem. After a successful pass of the solver,
165
    /// this contains a value with the best fitting linear coefficients
166
    /// # Returns
167
    /// Either the current best estimate coefficients or None, if none were calculated or the solver
168
    /// encountered an error. After the solver finished, this is the least squares best estimate
169
    /// for the linear coefficients of the base functions.
170
    ///
171
    /// The linear coefficients are column vectors that are ordered
172
    /// into a matrix, where the column at index $$s$$ are the best linear
173
    /// coefficients for the member at index $$s$$ of the dataset.
174
    pub fn linear_coefficients(&self) -> Option<VectorView<Model::ScalarType, Dyn>> {
6✔
175
        self.cached
6✔
176
            .as_ref()
177
            .map(|cache|{
12✔
178
                debug_assert_eq!(cache.linear_coefficients.ncols(),1,
12✔
NEW
179
                    "coefficient matrix must have exactly one column for single right hand side. This indicates a programming error in the library.");
×
180
                cache.linear_coefficients.as_view()
6✔
181
            })
182
    }
183

184
    /// the weighted data vector `$\vec{y}_w$` to which to fit the model. Note
185
    /// that the weights are already applied to the data vector and this
186
    /// is not the original data vector.
187
    ///
188
    /// This method is for fitting a single right hand side, hence the data
189
    /// is a single column vector.
190
    pub fn weighted_data(&self) -> VectorView<Model::ScalarType, Dyn> {
6✔
191
        debug_assert_eq!(
6✔
192
            self.Y_w.ncols(),
6✔
NEW
193
            1,
×
NEW
194
            "data matrix must have exactly one column for single right hand side. This indicates a programming error in the library."
×
195
        );
196
        self.Y_w.as_view()
6✔
197
    }
198
}
199

200
impl<Model, Rhs: RhsType> SeparableProblem<Model, Rhs>
201
where
202
    Model: SeparableNonlinearModel,
203
    Model::ScalarType: Scalar + ComplexField + Copy,
204
{
205
    /// access the contained model immutably
206
    pub fn model(&self) -> &Model {
34✔
207
        &self.model
34✔
208
    }
209

210
    /// get the weights of the data for the fitting problem
211
    pub fn weights(&self) -> &Weights<Model::ScalarType, Dyn> {
6✔
212
        &self.weights
6✔
213
    }
214
}
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