#[derive(Debug, Clone)]
pub struct DenseMatrix {
pub rows: usize,
pub cols: usize,
pub data: Vec<f64>,
}
impl DenseMatrix {
pub fn from_col_major(rows: usize, cols: usize, col_major: Vec<f64>) -> Self {
assert_eq!(col_major.len(), rows * cols);
let mut data = vec![0.0; rows * cols];
for c in 0..cols {
for r in 0..rows {
data[r * cols + c] = col_major[c * rows + r];
}
}
Self { rows, cols, data }
}
#[inline]
pub fn get(&self, r: usize, c: usize) -> f64 {
self.data[r * self.cols + c]
}
#[inline]
pub fn set(&mut self, r: usize, c: usize, val: f64) {
self.data[r * self.cols + c] = val;
}
pub fn transpose(&self) -> Self {
let mut data = vec![0.0; self.rows * self.cols];
for r in 0..self.rows {
for c in 0..self.cols {
data[c * self.rows + r] = self.data[r * self.cols + c];
}
}
Self {
rows: self.cols,
cols: self.rows,
data,
}
}
pub fn mul_vec(&self, v: &[f64]) -> Vec<f64> {
assert_eq!(v.len(), self.cols);
let mut out = vec![0.0; self.rows];
for r in 0..self.rows {
let row_start = r * self.cols;
let mut sum = 0.0;
for c in 0..self.cols {
sum += self.data[row_start + c] * v[c];
}
out[r] = sum;
}
out
}
pub fn qr_q(&self) -> Self {
assert_eq!(
self.rows, self.cols,
"QR only implemented for square matrices"
);
let n = self.rows;
let mut r = self.data.clone();
let mut q = vec![0.0; n * n];
for i in 0..n {
q[i * n + i] = 1.0;
}
for k in 0..n {
let m = n - k;
let mut v = vec![0.0; m];
for i in 0..m {
v[i] = r[(i + k) * n + k];
}
let mut sigma = 0.0f64;
let mut comp = 0.0f64;
for i in 1..m {
let y = v[i] * v[i] - comp;
let t = sigma + y;
comp = (t - sigma) - y;
sigma = t;
}
if sigma.abs() < 1e-30 && v[0].abs() < 1e-30 {
continue; }
let x_norm = (v[0] * v[0] + sigma).sqrt();
if v[0] >= 0.0 {
v[0] += x_norm;
} else {
v[0] -= x_norm;
}
let v_norm_sq: f64 = v.iter().map(|x| x * x).sum();
if v_norm_sq < 1e-30 {
continue;
}
let tau = 2.0 / v_norm_sq;
for j in k..n {
let mut dot = 0.0;
for i in 0..m {
dot += v[i] * r[(i + k) * n + j];
}
let scale = tau * dot;
for i in 0..m {
r[(i + k) * n + j] -= scale * v[i];
}
}
for j in 0..n {
let mut dot = 0.0;
for i in 0..m {
dot += v[i] * q[j * n + (i + k)];
}
let scale = tau * dot;
for i in 0..m {
q[j * n + (i + k)] -= scale * v[i];
}
}
}
Self {
rows: n,
cols: n,
data: q,
}
}
#[cfg(test)]
pub fn frobenius_norm(&self) -> f64 {
self.data.iter().map(|v| v * v).sum::<f64>().sqrt()
}
#[cfg(test)]
pub fn sub(&self, other: &Self) -> Self {
assert_eq!(self.rows, other.rows);
assert_eq!(self.cols, other.cols);
Self {
rows: self.rows,
cols: self.cols,
data: self
.data
.iter()
.zip(&other.data)
.map(|(a, b)| a - b)
.collect(),
}
}
#[cfg(test)]
pub fn identity(n: usize) -> Self {
let mut data = vec![0.0; n * n];
for i in 0..n {
data[i * n + i] = 1.0;
}
Self {
rows: n,
cols: n,
data,
}
}
#[cfg(test)]
pub fn mul_mat(&self, other: &Self) -> Self {
assert_eq!(self.cols, other.rows);
let mut data = vec![0.0; self.rows * other.cols];
for r in 0..self.rows {
for c in 0..other.cols {
let mut sum = 0.0;
for k in 0..self.cols {
sum += self.get(r, k) * other.get(k, c);
}
data[r * other.cols + c] = sum;
}
}
Self {
rows: self.rows,
cols: other.cols,
data,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qr_orthogonal_small() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0];
let m = DenseMatrix::from_col_major(3, 3, data);
let q = m.qr_q();
let qt = q.transpose();
let product = qt.mul_mat(&q);
let identity = DenseMatrix::identity(3);
let diff = product.sub(&identity).frobenius_norm();
assert!(diff < 1e-10, "Q^T * Q should be identity, diff = {diff}");
}
#[test]
fn test_qr_orthogonal_128() {
use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
let mut rng = ChaCha8Rng::seed_from_u64(42);
let data: Vec<f64> = (0..128 * 128).map(|_| rng.random::<f64>() - 0.5).collect();
let m = DenseMatrix::from_col_major(128, 128, data);
let q = m.qr_q();
let qt = q.transpose();
let product = qt.mul_mat(&q);
let identity = DenseMatrix::identity(128);
let diff = product.sub(&identity).frobenius_norm();
assert!(diff < 1e-8, "Q^T * Q should be identity, diff = {diff}");
}
#[test]
fn test_mul_vec() {
let m = DenseMatrix {
rows: 2,
cols: 2,
data: vec![1.0, 2.0, 3.0, 4.0],
};
let result = m.mul_vec(&[5.0, 6.0]);
assert!((result[0] - 17.0).abs() < 1e-10);
assert!((result[1] - 39.0).abs() < 1e-10);
}
#[test]
fn test_transpose() {
let m = DenseMatrix {
rows: 2,
cols: 3,
data: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
};
let t = m.transpose();
assert_eq!(t.rows, 3);
assert_eq!(t.cols, 2);
assert!((t.get(0, 0) - 1.0).abs() < 1e-10);
assert!((t.get(1, 0) - 2.0).abs() < 1e-10);
assert!((t.get(0, 1) - 4.0).abs() < 1e-10);
}
}