use super::spec::{Dimensionality, HasSpec, ProblemSpec, Properties, Reference};
pub struct SparseLeastSquares<M, V> {
pub a: M,
pub b: V,
}
impl<M, V> SparseLeastSquares<M, V> {
pub fn new(a: M, b: V) -> Self {
Self { a, b }
}
}
pub static SPARSE_LEAST_SQUARES_SPEC: ProblemSpec = ProblemSpec {
name: "Sparse least squares",
dim: Dimensionality::NDimensional { min: 1 },
properties: Properties {
smooth: true,
differentiable: true,
convex: true,
unimodal: true,
separable: false,
scalable: true,
},
references: &[Reference {
citation: "Björck (1996)",
title: "Numerical Methods for Least Squares Problems",
source: "SIAM",
doi: Some("10.1137/1.9781611971484"),
url: None,
}],
description: "Linear least-squares problem r(x) = A·x − b with a \
sparse design matrix A. Residual is exactly linear, \
so Gauss-Newton converges in one iteration. The \
S2b sparse fixture.",
};
impl<M, V> HasSpec for SparseLeastSquares<M, V> {
const SPEC: &'static ProblemSpec = &SPARSE_LEAST_SQUARES_SPEC;
}
pub struct SparseLeastSquaresBoxed<M, V> {
pub a: M,
pub b: V,
pub lower: V,
pub upper: V,
}
impl<M, V> SparseLeastSquaresBoxed<M, V> {
pub fn new(a: M, b: V, lower: V, upper: V) -> Self {
Self { a, b, lower, upper }
}
}
impl<M, V> HasSpec for SparseLeastSquaresBoxed<M, V> {
const SPEC: &'static ProblemSpec = &SPARSE_LEAST_SQUARES_SPEC;
}
#[cfg(feature = "nalgebra")]
mod nalgebra_impl {
use super::{SparseLeastSquares, SparseLeastSquaresBoxed};
use crate::core::math::{MatVec, ScaledAdd};
use crate::{BoxConstraints, CostFunction, Jacobian, Residual};
use nalgebra::DVector;
use nalgebra_sparse::CscMatrix;
impl CostFunction for SparseLeastSquares<CscMatrix<f64>, DVector<f64>> {
type Param = DVector<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
Ok(0.5 * r.iter().map(|v| v * v).sum::<f64>())
}
}
impl Residual for SparseLeastSquares<CscMatrix<f64>, DVector<f64>> {
type Param = DVector<f64>;
type Output = DVector<f64>;
type Error = std::convert::Infallible;
fn residual(&self, x: &DVector<f64>) -> Result<DVector<f64>, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
Ok(r)
}
}
impl Jacobian for SparseLeastSquares<CscMatrix<f64>, DVector<f64>> {
type Jacobian = CscMatrix<f64>;
fn jacobian(&self, _x: &DVector<f64>) -> Result<CscMatrix<f64>, std::convert::Infallible> {
Ok(self.a.clone())
}
}
impl CostFunction for SparseLeastSquaresBoxed<CscMatrix<f64>, DVector<f64>> {
type Param = DVector<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
Ok(0.5 * r.iter().map(|v| v * v).sum::<f64>())
}
}
impl Residual for SparseLeastSquaresBoxed<CscMatrix<f64>, DVector<f64>> {
type Param = DVector<f64>;
type Output = DVector<f64>;
type Error = std::convert::Infallible;
fn residual(&self, x: &DVector<f64>) -> Result<DVector<f64>, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
Ok(r)
}
}
impl Jacobian for SparseLeastSquaresBoxed<CscMatrix<f64>, DVector<f64>> {
type Jacobian = CscMatrix<f64>;
fn jacobian(&self, _x: &DVector<f64>) -> Result<CscMatrix<f64>, std::convert::Infallible> {
Ok(self.a.clone())
}
}
impl BoxConstraints for SparseLeastSquaresBoxed<CscMatrix<f64>, DVector<f64>> {
fn lower(&self) -> &DVector<f64> {
&self.lower
}
fn upper(&self) -> &DVector<f64> {
&self.upper
}
}
}
#[cfg(feature = "faer")]
mod faer_impl {
use super::{SparseLeastSquares, SparseLeastSquaresBoxed};
use crate::core::math::{MatVec, ScaledAdd};
use crate::{BoxConstraints, CostFunction, Jacobian, Residual};
use faer::sparse::SparseColMat;
use faer::Col;
impl CostFunction for SparseLeastSquares<SparseColMat<usize, f64>, Col<f64>> {
type Param = Col<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
let mut s = 0.0;
for i in 0..r.nrows() {
s += r[i] * r[i];
}
Ok(0.5 * s)
}
}
impl Residual for SparseLeastSquares<SparseColMat<usize, f64>, Col<f64>> {
type Param = Col<f64>;
type Output = Col<f64>;
type Error = std::convert::Infallible;
fn residual(&self, x: &Col<f64>) -> Result<Col<f64>, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
Ok(r)
}
}
impl Jacobian for SparseLeastSquares<SparseColMat<usize, f64>, Col<f64>> {
type Jacobian = SparseColMat<usize, f64>;
fn jacobian(
&self,
_x: &Col<f64>,
) -> Result<SparseColMat<usize, f64>, std::convert::Infallible> {
Ok(self.a.clone())
}
}
impl CostFunction for SparseLeastSquaresBoxed<SparseColMat<usize, f64>, Col<f64>> {
type Param = Col<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
let mut s = 0.0;
for i in 0..r.nrows() {
s += r[i] * r[i];
}
Ok(0.5 * s)
}
}
impl Residual for SparseLeastSquaresBoxed<SparseColMat<usize, f64>, Col<f64>> {
type Param = Col<f64>;
type Output = Col<f64>;
type Error = std::convert::Infallible;
fn residual(&self, x: &Col<f64>) -> Result<Col<f64>, std::convert::Infallible> {
let mut r = self.a.matvec(x);
r.scaled_add(-1.0, &self.b);
Ok(r)
}
}
impl Jacobian for SparseLeastSquaresBoxed<SparseColMat<usize, f64>, Col<f64>> {
type Jacobian = SparseColMat<usize, f64>;
fn jacobian(
&self,
_x: &Col<f64>,
) -> Result<SparseColMat<usize, f64>, std::convert::Infallible> {
Ok(self.a.clone())
}
}
impl BoxConstraints for SparseLeastSquaresBoxed<SparseColMat<usize, f64>, Col<f64>> {
fn lower(&self) -> &Col<f64> {
&self.lower
}
fn upper(&self) -> &Col<f64> {
&self.upper
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spec_is_wired_up_via_has_spec_trait() {
type Probe = SparseLeastSquares<(), ()>;
let spec = <Probe as HasSpec>::SPEC;
assert_eq!(spec.name, "Sparse least squares");
assert!(spec.properties.convex);
assert!(spec.properties.scalable);
assert!(matches!(spec.dim, Dimensionality::NDimensional { min: 1 }));
assert!(!spec.references.is_empty());
}
#[cfg(feature = "faer")]
#[test]
fn faer_residual_at_zero_returns_minus_b() {
use crate::Residual;
use faer::sparse::{SparseColMat, Triplet};
use faer::Col;
let triplets = [
Triplet::new(0_usize, 0_usize, 1.0),
Triplet::new(1, 1, 1.0),
Triplet::new(2, 0, 1.0),
Triplet::new(2, 1, 1.0),
];
let a = SparseColMat::<usize, f64>::try_new_from_triplets(3, 2, &triplets).unwrap();
let b = Col::<f64>::from_fn(3, |i| [1.0, 2.0, 4.0][i]);
let prob = SparseLeastSquares::new(a, b);
let r = prob.residual(&Col::<f64>::zeros(2)).unwrap();
assert_eq!(r.nrows(), 3);
assert_eq!(r[0], -1.0);
assert_eq!(r[1], -2.0);
assert_eq!(r[2], -4.0);
}
}