use crate::matrix::{DenseDesignMatrix, DenseDesignOperator, DesignMatrix, LinearOperator};
use gam_runtime::resource::MatrixMaterializationError;
use ndarray::{Array1, Array2, Axis, s};
use std::ops::Range;
use std::sync::Arc;
#[derive(Clone)]
struct NoDensifyOperator {
dense: Array2<f64>,
}
impl LinearOperator for NoDensifyOperator {
fn nrows(&self) -> usize {
self.dense.nrows()
}
fn ncols(&self) -> usize {
self.dense.ncols()
}
fn apply(&self, vector: &Array1<f64>) -> Array1<f64> {
self.dense.dot(vector)
}
fn apply_transpose(&self, vector: &Array1<f64>) -> Array1<f64> {
self.dense.t().dot(vector)
}
fn diag_xtw_x(&self, weights: &Array1<f64>) -> Result<Array2<f64>, String> {
if weights.len() != self.nrows() {
return Err(format!(
"NoDensifyOperator weight length mismatch: weights={}, nrows={}",
weights.len(),
self.nrows()
));
}
let weighted = &self.dense * &weights.view().insert_axis(Axis(1));
Ok(self.dense.t().dot(&weighted))
}
}
impl DenseDesignOperator for NoDensifyOperator {
fn row_chunk_into(
&self,
rows: Range<usize>,
mut out: ndarray::ArrayViewMut2<'_, f64>,
) -> Result<(), MatrixMaterializationError> {
out.assign(&self.dense.slice(s![rows, ..]));
Ok(())
}
fn to_dense(&self) -> Array2<f64> {
panic!("NoDensifyOperator must stay lazy")
}
}
pub fn no_densify_design(dense: Array2<f64>) -> DesignMatrix {
DesignMatrix::from(DenseDesignMatrix::from(Arc::new(NoDensifyOperator { dense })))
}
#[cfg(test)]
mod tests {
use super::no_densify_design;
use ndarray::array;
#[test]
fn no_densify_design_is_operator_backed_and_stays_lazy() {
let design = no_densify_design(array![[1.0, 2.0], [3.0, 4.0]]);
assert!(design.as_dense_ref().is_none(), "must not be materialized");
assert!(!design.is_materialized_dense());
assert!(design.is_operator_backed());
assert_eq!(design.nrows(), 2);
assert_eq!(design.ncols(), 2);
let beta = array![1.0, -1.0];
let got = design.dot(&beta);
assert!((got[0] - (-1.0)).abs() < 1e-12); assert!((got[1] - (-1.0)).abs() < 1e-12); let chunk = design
.try_row_chunk(0..2)
.expect("row chunk must stay lazy, not densify");
assert_eq!(chunk, array![[1.0, 2.0], [3.0, 4.0]]);
}
#[test]
#[should_panic(expected = "operator-backed design")]
fn no_densify_design_rejects_materialization() {
let design = no_densify_design(array![[1.0, 2.0], [3.0, 4.0]]);
let _ = design.as_dense_cow();
}
}