Skip to content

Commit 1f88f60

Browse files
authored
Feature/bump dependency versions 2 (#15)
* depend on newest levmar and nalgebra versions * fix deprecation warnings * add a test for diag matrix creation * update changelog, bump version
1 parent fd0d2ad commit 1f88f60

File tree

8 files changed

+56
-34
lines changed

8 files changed

+56
-34
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This is the changelog for the `varpro` library. See also here for a [version history](https://crates.io/crates/varpro/versions).
44

5+
## 0.5.0
6+
- Upgrade nalgebra and levenberg_marquardt dependencies to current versions
7+
- Fix deprecation warnings and expand test coverage slightly
8+
59
## 0.4.1
610
- Remove snafu dependency and replace by `thiserror`
711
- Use uninitialized matrices instead of zero initialisation for places where contents will be overwritten anyways

Cargo.toml

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "varpro"
3-
version = "0.4.1"
3+
version = "0.5.0"
44
authors = ["geo-ant"]
55
edition = "2021"
66
license = "MIT"
@@ -15,12 +15,13 @@ keywords = ["nonlinear","least","squares","fitting","regression"]
1515

1616
[dependencies]
1717
thiserror = "1"
18-
levenberg-marquardt = "0.12"
19-
nalgebra = "0.30"
18+
levenberg-marquardt = "0.13"
19+
nalgebra = "0.32"
2020
num-traits = "0.2"
2121

2222
[dev-dependencies]
2323
approx = "0.5"
24+
num-complex = "0.4"
2425

2526
[package.metadata.docs.rs]
2627
# To build locally use

src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![deny(missing_docs)]
2-
//! The varpro crate enables nonlinear least squares fitting for separable models using the Variable Projection (VarPro) algorithm.
31
//!
42
//! # Introduction
53
//!

src/linalg_helpers/mod.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[cfg(test)]
22
mod test;
33

4-
use nalgebra::{ClosedMul, ComplexField, DMatrix, DVector, Dynamic, Scalar};
4+
use nalgebra::{ClosedMul, ComplexField, DMatrix, DVector, Dyn, Scalar};
55
use std::ops::Mul;
66

77
/// A square diagonal matrix with dynamic dimension. Off-diagonal entries are assumed zero.
@@ -74,9 +74,8 @@ where
7474
// with valid values below. Ideally we would first call the
7575
// copy_from functionality below, but we can't because the Scalar
7676
// trait bounds will screw us. See https://github.com/dimforge/nalgebra/pull/949
77-
let mut result_matrix = unsafe {
78-
DMatrix::uninit(Dynamic::new(self.nrows()), Dynamic::new(rhs.ncols())).assume_init()
79-
};
77+
let mut result_matrix =
78+
unsafe { DMatrix::uninit(Dyn(self.nrows()), Dyn(rhs.ncols())).assume_init() };
8079

8180
for (col_idx, mut col) in result_matrix.column_iter_mut().enumerate() {
8281
col.copy_from(&self.diagonal.component_mul(&rhs.column(col_idx)));

src/linalg_helpers/test.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::linalg_helpers::DiagDMatrix;
2-
use nalgebra::{DMatrix, DVector};
2+
use approx::assert_relative_eq;
3+
use nalgebra::{ComplexField, DMatrix, DVector};
34

45
#[test]
56
#[allow(non_snake_case)]
@@ -74,3 +75,26 @@ fn diagonal_matrix_multiplication_must_panic_for_rhs_dimensions_too_large() {
7475
let A = DMatrix::from_element(ndiag - 1, 1, 1.);
7576
let _ = &D * &A;
7677
}
78+
79+
#[test]
80+
#[allow(non_snake_case)]
81+
fn diagonal_matrix_test_from_real_field() {
82+
use num_complex::Complex;
83+
let diagonal = DVector::from(vec![1., 2., 3.]);
84+
let D = DiagDMatrix::<Complex<f64>>::from_real_field(&diagonal);
85+
let ones = DVector::from(vec![
86+
Complex::new(1., 5.),
87+
Complex::new(1., 5.),
88+
Complex::new(1., 5.),
89+
]);
90+
let result = &D * &ones;
91+
92+
assert_relative_eq!(
93+
result.map(ComplexField::real),
94+
DVector::from(vec![1., 2., 3.])
95+
);
96+
assert_relative_eq!(
97+
result.map(ComplexField::imaginary),
98+
DVector::from(vec![5., 10., 15.])
99+
);
100+
}

src/model/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::model::errors::ModelError;
22
use crate::model::model_basis_function::ModelBasisFunction;
33
use nalgebra::base::Scalar;
4-
use nalgebra::{DMatrix, DVector, Dynamic};
4+
use nalgebra::{DMatrix, DVector, Dyn};
55
use num_traits::Zero;
66

77
mod detail;
@@ -152,7 +152,7 @@ where
152152
// this pattern is not great, but the trait bounds in copy_from still
153153
// prevent us from doing something better
154154
let mut function_value_matrix =
155-
unsafe { DMatrix::uninit(Dynamic::new(nrows), Dynamic::new(ncols)).assume_init() };
155+
unsafe { DMatrix::uninit(Dyn(nrows), Dyn(ncols)).assume_init() };
156156

157157
for (basefunc, mut column) in self
158158
.basefunctions

src/solvers/levmar/mod.rs

+12-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::model::SeparableModel;
22
use levenberg_marquardt::LeastSquaresProblem;
33
use nalgebra::storage::Owned;
4-
use nalgebra::{ComplexField, DMatrix, DVector, Dynamic, Matrix, Scalar, Vector, SVD};
4+
use nalgebra::{ComplexField, DMatrix, DVector, Dyn, Matrix, Scalar, Vector, SVD};
55

66
mod builder;
77
#[cfg(test)]
@@ -21,7 +21,7 @@ struct CachedCalculations<ScalarType: Scalar + ComplexField> {
2121
/// The current residual of model function values belonging to the current parameters
2222
current_residuals: DVector<ScalarType>,
2323
/// Singular value decomposition of the current function value matrix
24-
current_svd: SVD<ScalarType, Dynamic, Dynamic>,
24+
current_svd: SVD<ScalarType, Dyn, Dyn>,
2525
/// the linear coefficients `$\vec{c}$` providing the current best fit
2626
linear_coefficients: DVector<ScalarType>,
2727
}
@@ -80,22 +80,21 @@ impl<'a, ScalarType: Scalar + ComplexField + Copy> LevMarProblem<'a, ScalarType>
8080
}
8181
}
8282

83-
impl<'a, ScalarType> LeastSquaresProblem<ScalarType, Dynamic, Dynamic>
84-
for LevMarProblem<'a, ScalarType>
83+
impl<'a, ScalarType> LeastSquaresProblem<ScalarType, Dyn, Dyn> for LevMarProblem<'a, ScalarType>
8584
where
8685
ScalarType: Scalar + ComplexField + Copy,
8786
ScalarType::RealField: Mul<ScalarType, Output = ScalarType> + Float,
8887
{
89-
type ResidualStorage = Owned<ScalarType, Dynamic>;
90-
type JacobianStorage = Owned<ScalarType, Dynamic, Dynamic>;
91-
type ParameterStorage = Owned<ScalarType, Dynamic>;
88+
type ResidualStorage = Owned<ScalarType, Dyn>;
89+
type JacobianStorage = Owned<ScalarType, Dyn, Dyn>;
90+
type ParameterStorage = Owned<ScalarType, Dyn>;
9291

9392
#[allow(non_snake_case)]
9493
/// Set the (nonlinear) model parameters `$\vec{\alpha}$` and update the internal state of the
9594
/// problem accordingly. The parameters are expected in the same order that the parameter
9695
/// names were provided in at model creation. So if we gave `&["tau","beta"]` as parameters at
9796
/// model creation, the function expects the layout of the parameter vector to be `$\vec{\alpha}=(\tau,\beta)^T$`.
98-
fn set_params(&mut self, params: &Vector<ScalarType, Dynamic, Self::ParameterStorage>) {
97+
fn set_params(&mut self, params: &Vector<ScalarType, Dyn, Self::ParameterStorage>) {
9998
self.model_parameters = params.iter().cloned().collect();
10099
// matrix of weighted model function values
101100
let Phi_w = self
@@ -135,7 +134,7 @@ where
135134
/// names given on model creation. E.g. if the parameters at model creation where given as
136135
/// `&["tau","beta"]`, then the returned vector is `$\vec{\alpha} = (\tau,\beta)^T$`, i.e.
137136
/// the value of parameter `$\tau$` is at index `0` and the value of `$\beta$` at index `1`.
138-
fn params(&self) -> Vector<ScalarType, Dynamic, Self::ParameterStorage> {
137+
fn params(&self) -> Vector<ScalarType, Dyn, Self::ParameterStorage> {
139138
DVector::from(self.model_parameters.clone())
140139
}
141140

@@ -146,7 +145,7 @@ where
146145
/// algorithm calculates `$\vec{c}(\vec{\alpha})$` as the coefficients that provide the best linear least squares
147146
/// fit, given the current `$\vec{\alpha}$`. For more info on the math of VarPro, see
148147
/// e.g. [here](https://geo-ant.github.io/blog/2020/variable-projection-part-1-fundamentals/).
149-
fn residuals(&self) -> Option<Vector<ScalarType, Dynamic, Self::ResidualStorage>> {
148+
fn residuals(&self) -> Option<Vector<ScalarType, Dyn, Self::ResidualStorage>> {
150149
self.cached
151150
.as_ref()
152151
.map(|cached| cached.current_residuals.clone())
@@ -156,7 +155,7 @@ where
156155
/// Calculate the Jacobian matrix of the *weighted* residuals `$\vec{r}_w(\vec{\alpha})$`.
157156
/// For more info on how the Jacobian is calculated in the VarPro algorithm, see
158157
/// e.g. [here](https://geo-ant.github.io/blog/2020/variable-projection-part-1-fundamentals/).
159-
fn jacobian(&self) -> Option<Matrix<ScalarType, Dynamic, Dynamic, Self::JacobianStorage>> {
158+
fn jacobian(&self) -> Option<Matrix<ScalarType, Dyn, Dyn, Self::JacobianStorage>> {
160159
// TODO (Performance): make this more efficient by parallelizing
161160

162161
if let Some(CachedCalculations {
@@ -168,11 +167,8 @@ where
168167
// this is not a great pattern, but the trait bounds on copy_from
169168
// as of now prevent us from doing something more idiomatic
170169
let mut jacobian_matrix = unsafe {
171-
DMatrix::uninit(
172-
Dynamic::new(self.y_w.len()),
173-
Dynamic::new(self.model.parameter_count()),
174-
)
175-
.assume_init()
170+
DMatrix::uninit(Dyn(self.y_w.len()), Dyn(self.model.parameter_count()))
171+
.assume_init()
176172
};
177173

178174
let U = current_svd.u.as_ref()?; // will return None if this was not calculated

tests/integration_tests/levmar.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::common::linspace;
22
use levenberg_marquardt::{differentiate_numerically, LeastSquaresProblem};
33
use nalgebra::storage::Owned;
4-
use nalgebra::{DVector, Dynamic, Matrix, Vector, Vector5, U5};
4+
use nalgebra::{DVector, Dyn, Matrix, Vector, Vector5, U5};
55

66
use approx::assert_relative_eq;
77

@@ -44,10 +44,10 @@ impl DoubleExponentialDecayFittingWithOffset {
4444
}
4545
}
4646

47-
impl LeastSquaresProblem<f64, Dynamic, U5> for DoubleExponentialDecayFittingWithOffset {
47+
impl LeastSquaresProblem<f64, Dyn, U5> for DoubleExponentialDecayFittingWithOffset {
4848
type ParameterStorage = Owned<f64, U5>;
49-
type ResidualStorage = Owned<f64, Dynamic>;
50-
type JacobianStorage = Owned<f64, Dynamic, U5>;
49+
type ResidualStorage = Owned<f64, Dyn>;
50+
type JacobianStorage = Owned<f64, Dyn, U5>;
5151

5252
fn set_params(&mut self, params: &Vector<f64, U5, Self::ParameterStorage>) {
5353
self.params = *params;
@@ -80,7 +80,7 @@ impl LeastSquaresProblem<f64, Dynamic, U5> for DoubleExponentialDecayFittingWith
8080
Some(&f - &self.y)
8181
}
8282

83-
fn jacobian(&self) -> Option<Matrix<f64, Dynamic, U5, Self::JacobianStorage>> {
83+
fn jacobian(&self) -> Option<Matrix<f64, Dyn, U5, Self::JacobianStorage>> {
8484
// get parameters from internal param storage
8585
let tau1 = self.params[0];
8686
let tau2 = self.params[1];
@@ -89,7 +89,7 @@ impl LeastSquaresProblem<f64, Dynamic, U5> for DoubleExponentialDecayFittingWith
8989
// populate jacobian
9090
//let ncols = 5;
9191
let nrows = self.x.len();
92-
let mut jacobian = Matrix::<f64, Dynamic, U5, Self::JacobianStorage>::zeros(nrows);
92+
let mut jacobian = Matrix::<f64, Dyn, U5, Self::JacobianStorage>::zeros(nrows);
9393

9494
jacobian.set_column(
9595
0,

0 commit comments

Comments
 (0)