extern crate nalgebra as na;
use std::error::Error;
use std::fmt;
use std::time::Instant;
use linfa::prelude::*;
use linfa_elasticnet::ElasticNet;
use nalgebra_lapack::Cholesky;
use ndarray::{Array, Array1};
#[derive(Debug)]
pub struct LassoError {
message: String,
}
impl LassoError {
pub fn from_string(message: String) -> Self {
LassoError { message }
}
}
impl fmt::Display for LassoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for LassoError {}
pub fn find_p_star_linfa(a: &na::DMatrix<f32>, b: &na::DMatrix<f32>, lambda: f32) -> f32 {
println!("Finding p* using linfa");
let start_time = Instant::now();
let m = a.nrows();
let n = a.ncols();
let a_ndarray = Array::from_shape_fn((m, n), |(i, j)| a[(i, j)]);
let b_ndarray = Array1::from_vec(b.as_slice().to_vec());
let dataset = Dataset::new(a_ndarray.clone(), b_ndarray.clone());
let model = ElasticNet::lasso()
.penalty(lambda / m as f32) .with_intercept(false)
.fit(&dataset)
.expect("Failed to fit ElasticNet model");
let x_star = model.hyperplane().to_owned();
let ax_b = a_ndarray.dot(&x_star) - b_ndarray;
let sq_norm = ax_b.mapv(|x| x.powi(2)).sum();
let x_l1 = x_star.mapv(|x| x.abs()).sum();
println!(
"Fit model w/ linfa and calculated p* in {:?}",
start_time.elapsed()
);
return 0.5 * sq_norm + lambda * x_l1;
}
pub fn fast_lasso_inverse(a: &na::DMatrix<f32>, rho: f32) -> na::DMatrix<f32> {
let m = a.nrows();
let n = a.ncols();
let eye_n = na::DMatrix::<f32>::identity(n, n);
return if m < n / 2 {
let rho_inv = 1.0 / rho;
let eye_m = na::DMatrix::<f32>::identity(m, m);
let i_aat = a * a.transpose() * rho_inv + &eye_m;
let i_aat_inv = i_aat
.try_inverse()
.ok_or(format!("Fast matrix inversion failed"))
.unwrap();
eye_n * rho_inv - (a.transpose() * i_aat_inv * a) * (rho_inv * rho_inv)
} else {
let mut ata = a.transpose() * a;
ata = ata + eye_n * rho;
let l = Cholesky::new(ata).expect("Failed to compute Cholesky decomposition");
l.inverse().expect("Failed to compute inverse")
};
}
pub fn fused_lasso_factor(
a: &na::DMatrix<f32>,
b: &na::DMatrix<f32>,
rho: f32,
) -> (na::DMatrix<f32>, na::DMatrix<f32>) {
let m = a.nrows();
let n = a.ncols();
let eye_n = na::DMatrix::<f32>::identity(n, n);
if m < n / 2 {
let rho_inv = 1.0 / rho;
let i_aat = a.transpose() * a * rho_inv + &eye_n;
let i_aat_inv = i_aat
.try_inverse()
.ok_or(format!("Fast matrix inversion failed"))
.unwrap();
(
eye_n * rho_inv - (a.transpose() * i_aat_inv * a) * (rho_inv * rho_inv),
a.transpose() * b,
)
} else {
let ata = a.transpose() * a + eye_n * rho;
let atb = a.transpose() * b;
let l = Cholesky::new(ata).expect("Failed to compute Cholesky decomposition");
(l.inverse().expect("Failed to compute inverse"), atb)
}
}