use super::{LevMarProblem, LinearSolver};
use crate::{model::SeparableNonlinearModel, problem::RhsType, util::to_vector};
use levenberg_marquardt::LeastSquaresProblem;
use nalgebra::{
ComplexField, DMatrix, DefaultAllocator, Dyn, Matrix, MatrixViewMut, Owned, Scalar,
UninitMatrix, Vector, SVD,
};
use num_traits::{Float, FromPrimitive, Zero};
use std::ops::Mul;
#[derive(Debug, Clone)]
pub struct SvdLinearSolver<ScalarType>
where
ScalarType: Scalar + ComplexField,
{
pub(crate) current_residuals: DMatrix<ScalarType>,
pub(crate) decomposition: SVD<ScalarType, Dyn, Dyn>,
pub(crate) linear_coefficients: DMatrix<ScalarType>,
}
impl<ScalarType> LinearSolver for SvdLinearSolver<ScalarType>
where
ScalarType: Scalar + ComplexField,
{
type ScalarType = ScalarType;
fn linear_coefficients_matrix(self) -> DMatrix<Self::ScalarType> {
self.linear_coefficients
}
}
impl<Model, Rhs: RhsType> LeastSquaresProblem<Model::ScalarType, Dyn, Dyn>
for LevMarProblem<Model, Rhs, SvdLinearSolver<Model::ScalarType>>
where
Model::ScalarType: Scalar + ComplexField + Copy,
<Model::ScalarType as ComplexField>::RealField: Float + Zero,
<<Model as SeparableNonlinearModel>::ScalarType as ComplexField>::RealField:
Mul<Model::ScalarType, Output = Model::ScalarType> + Float,
Model: SeparableNonlinearModel,
DefaultAllocator: nalgebra::allocator::Allocator<Dyn>,
{
type ResidualStorage = Owned<Model::ScalarType, Dyn>;
type JacobianStorage = Owned<Model::ScalarType, Dyn, Dyn>;
type ParameterStorage = Owned<Model::ScalarType, Dyn>;
#[allow(non_snake_case)]
fn set_params(&mut self, params: &Vector<Model::ScalarType, Dyn, Self::ParameterStorage>) {
if self
.separable_problem
.model
.set_params(params.clone())
.is_err()
{
self.cached = None;
return;
}
let Ok(Phi) = self.separable_problem.model.eval() else {
self.cached = None;
return;
};
let Phi_w = &self.separable_problem.weights * Phi;
let max_dim_phiw = Phi_w.ncols().max(Phi_w.nrows());
let current_svd = Phi_w.clone().svd(true, true);
let svd_epsilon = current_svd.singular_values.max()
* <Model::ScalarType as ComplexField>::RealField::from_usize(max_dim_phiw)
.expect("integer size out of floating point bounds")
* <Model::ScalarType as ComplexField>::RealField::epsilon();
let Ok(linear_coefficients) =
current_svd.solve(&self.separable_problem.Y_w, svd_epsilon.real())
else {
self.cached = None;
return;
};
let current_residuals = &self.separable_problem.Y_w - Phi_w * &linear_coefficients;
self.cached = Some(SvdLinearSolver {
current_residuals,
decomposition: current_svd,
linear_coefficients,
})
}
fn params(&self) -> Vector<Model::ScalarType, Dyn, Self::ParameterStorage> {
self.separable_problem.model.params()
}
fn residuals(&self) -> Option<Vector<Model::ScalarType, Dyn, Self::ResidualStorage>> {
self.cached
.as_ref()
.map(|cached| to_vector(cached.current_residuals.clone()))
}
#[allow(non_snake_case)]
fn jacobian(&self) -> Option<Matrix<Model::ScalarType, Dyn, Dyn, Self::JacobianStorage>> {
let SvdLinearSolver {
current_residuals: _,
decomposition,
linear_coefficients,
} = self.cached.as_ref()?;
let data_cols = self.separable_problem.Y_w.ncols();
let parameter_count = self.separable_problem.model.parameter_count();
let mut jacobian_matrix = unsafe {
UninitMatrix::uninit(
Dyn(self.separable_problem.model.output_len() * data_cols),
Dyn(parameter_count),
)
.assume_init()
};
let U = decomposition.u.as_ref()?; let U_t = U.transpose();
let one = Model::ScalarType::from_i8(1).unwrap();
let result: Result<Vec<()>, Model::Error> = jacobian_matrix
.column_iter_mut()
.enumerate()
.map(|(k, mut jacobian_col)| {
let Dk = &self.separable_problem.weights
* self.separable_problem.model.eval_partial_deriv(k)?;
let view: MatrixViewMut<Model::ScalarType, Dyn, Dyn, _, _> =
jacobian_col.as_view_mut();
let mut dkc_shaped_jacobian: Matrix<Model::ScalarType, Dyn, Dyn, _> = view
.reshape_generic::<Dyn, Dyn>(
Dk.shape_generic().0,
linear_coefficients.shape_generic().1,
);
if data_cols <= parameter_count {
Dk.mul_to(linear_coefficients, &mut dkc_shaped_jacobian);
let Ut_DkC = &U_t * &dkc_shaped_jacobian;
dkc_shaped_jacobian.gemm(one, U, &Ut_DkC, -one);
} else {
let Ut_Dk = &U_t * &Dk;
let mut Dk = Dk;
Dk.gemm(one, U, &Ut_Dk, -one);
Dk.mul_to(linear_coefficients, &mut dkc_shaped_jacobian);
};
Ok(())
})
.collect::<Result<_, _>>();
result.ok()?;
Some(jacobian_matrix)
}
}