use ndarray::{Array1, Array2};
use smartcore::decomposition::pca::PCA;
use smartcore::decomposition::svd::SVD;
use smartcore::linalg::basic::arrays::Array;
use smartcore::linalg::basic::matrix::DenseMatrix;
use smartcore::linear::elastic_net::ElasticNet;
use smartcore::linear::lasso::Lasso;
use smartcore::linear::linear_regression::LinearRegression;
use smartcore::linear::logistic_regression::LogisticRegression;
use smartcore::linear::ridge_regression::RidgeRegression;
use crate::canonical::{AffineModel, LinearModelWeights, LogisticModelWeights};
use crate::Result;
pub type DenseLinearRegression = LinearRegression<f64, f64, DenseMatrix<f64>, Vec<f64>>;
pub type DenseLogisticRegression = LogisticRegression<f64, i32, DenseMatrix<f64>, Vec<i32>>;
pub type DenseRidgeRegression = RidgeRegression<f64, f64, DenseMatrix<f64>, Vec<f64>>;
pub type DenseLasso = Lasso<f64, f64, DenseMatrix<f64>, Vec<f64>>;
pub type DenseElasticNet = ElasticNet<f64, f64, DenseMatrix<f64>, Vec<f64>>;
pub type DensePca = PCA<f64, DenseMatrix<f64>>;
pub type DenseSvd = SVD<f64, DenseMatrix<f64>>;
fn ndarray_matrix(matrix: &DenseMatrix<f64>) -> Array2<f64> {
let (rows, columns) = matrix.shape();
Array2::from_shape_fn((rows, columns), |(row, column)| *matrix.get((row, column)))
}
fn column_weights(coefficients: &DenseMatrix<f64>, intercept: f64) -> LinearModelWeights {
let (rows, columns) = coefficients.shape();
debug_assert_eq!(columns, 1);
LinearModelWeights::new(
Array1::from_iter((0..rows).map(|row| *coefficients.get((row, 0)))),
intercept,
)
}
#[must_use]
pub fn linear_weights(model: &DenseLinearRegression) -> LinearModelWeights {
column_weights(model.coefficients(), *model.intercept())
}
pub fn logistic_weights(model: &DenseLogisticRegression) -> Result<LogisticModelWeights> {
let coefficients = model.coefficients();
let intercept = model.intercept();
let (rows, columns) = coefficients.shape();
let coefficient_values =
(0..rows).flat_map(|row| (0..columns).map(move |column| *coefficients.get((row, column))));
LogisticModelWeights::new(
Array2::from_shape_vec((rows, columns), coefficient_values.collect())
.expect("matrix shape and iteration count agree"),
Array1::from_iter((0..rows).map(|row| *intercept.get((row, 0)))),
model.classes().len(),
)
}
#[must_use]
pub fn logistic_classes(model: &DenseLogisticRegression) -> &[i32] {
model.classes()
}
#[must_use]
pub fn ridge_weights(model: &DenseRidgeRegression) -> LinearModelWeights {
column_weights(model.coefficients(), *model.intercept())
}
#[must_use]
pub fn lasso_weights(model: &DenseLasso) -> LinearModelWeights {
column_weights(model.coefficients(), *model.intercept())
}
#[must_use]
pub fn elastic_net_weights(model: &DenseElasticNet) -> LinearModelWeights {
column_weights(model.coefficients(), *model.intercept())
}
pub fn pca_transform(model: &DensePca) -> Result<AffineModel> {
let matrix = ndarray_matrix(model.components());
let zero = DenseMatrix::new(1, matrix.nrows(), vec![0.0; matrix.nrows()], false)
.map_err(|error| crate::Error::InvalidModel(error.to_string()))?;
let bias_matrix = model
.transform(&zero)
.map_err(|error| crate::Error::InvalidModel(error.to_string()))?;
let bias = Array1::from_iter((0..matrix.ncols()).map(|column| *bias_matrix.get((0, column))));
AffineModel::new(matrix, bias)
}
pub fn svd_transform(model: &DenseSvd) -> Result<AffineModel> {
let matrix = ndarray_matrix(model.components());
let bias = Array1::zeros(matrix.ncols());
AffineModel::new(matrix, bias)
}