use oxiblas_core::scalar::Scalar;
use oxiblas_matrix::Mat;
pub struct MatBuilder<T: Scalar> {
_marker: std::marker::PhantomData<T>,
}
impl<T: Scalar> MatBuilder<T> {
#[inline]
pub fn zeros(nrows: usize, ncols: usize) -> Mat<T>
where
T: bytemuck::Zeroable,
{
Mat::zeros(nrows, ncols)
}
#[inline]
pub fn ones(nrows: usize, ncols: usize) -> Mat<T>
where
T: num_traits::One,
{
Mat::filled(nrows, ncols, T::one())
}
#[inline]
pub fn filled(nrows: usize, ncols: usize, value: T) -> Mat<T> {
Mat::filled(nrows, ncols, value)
}
pub fn identity(n: usize) -> Mat<T>
where
T: bytemuck::Zeroable + num_traits::One,
{
let mut m = Mat::zeros(n, n);
for i in 0..n {
m[(i, i)] = T::one();
}
m
}
pub fn eye(nrows: usize, ncols: usize) -> Mat<T>
where
T: bytemuck::Zeroable + num_traits::One,
{
let mut m = Mat::zeros(nrows, ncols);
let min_dim = nrows.min(ncols);
for i in 0..min_dim {
m[(i, i)] = T::one();
}
m
}
pub fn from_fn<F>(nrows: usize, ncols: usize, mut f: F) -> Mat<T>
where
F: FnMut(usize, usize) -> T,
{
let mut m = Mat::filled(nrows, ncols, f(0, 0));
for j in 0..ncols {
for i in 0..nrows {
m[(i, j)] = f(i, j);
}
}
m
}
pub fn diagonal(diag: &[T]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
let n = diag.len();
let mut m = Mat::zeros(n, n);
for (i, &val) in diag.iter().enumerate() {
m[(i, i)] = val;
}
m
}
pub fn tridiagonal(sub: &[T], main: &[T], sup: &[T]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
let n = main.len();
assert_eq!(
sub.len(),
n.saturating_sub(1),
"sub-diagonal must have length n-1"
);
assert_eq!(
sup.len(),
n.saturating_sub(1),
"super-diagonal must have length n-1"
);
let mut m = Mat::zeros(n, n);
for (i, &val) in main.iter().enumerate() {
m[(i, i)] = val;
}
for (i, &val) in sub.iter().enumerate() {
m[(i + 1, i)] = val;
}
for (i, &val) in sup.iter().enumerate() {
m[(i, i + 1)] = val;
}
m
}
pub fn banded<I>(n: usize, diagonals: I) -> Mat<T>
where
T: bytemuck::Zeroable,
I: IntoIterator<Item = (isize, Vec<T>)>,
{
let mut m = Mat::zeros(n, n);
for (offset, values) in diagonals {
let (row_start, col_start) = if offset >= 0 {
(0, offset as usize)
} else {
((-offset) as usize, 0)
};
for (k, &val) in values.iter().enumerate() {
let i = row_start + k;
let j = col_start + k;
if i < n && j < n {
m[(i, j)] = val;
}
}
}
m
}
pub fn col_vec(data: &[T]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
let n = data.len();
let mut m = Mat::zeros(n, 1);
for (i, &val) in data.iter().enumerate() {
m[(i, 0)] = val;
}
m
}
pub fn row_vec(data: &[T]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
let n = data.len();
let mut m = Mat::zeros(1, n);
for (j, &val) in data.iter().enumerate() {
m[(0, j)] = val;
}
m
}
pub fn block_diagonal(blocks: &[oxiblas_matrix::MatRef<'_, T>]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
let total_rows: usize = blocks.iter().map(|b| b.nrows()).sum();
let total_cols: usize = blocks.iter().map(|b| b.ncols()).sum();
let mut m = Mat::zeros(total_rows, total_cols);
let mut row_offset = 0;
let mut col_offset = 0;
for block in blocks {
for j in 0..block.ncols() {
for i in 0..block.nrows() {
m[(row_offset + i, col_offset + j)] = block[(i, j)];
}
}
row_offset += block.nrows();
col_offset += block.ncols();
}
m
}
pub fn vstack(matrices: &[oxiblas_matrix::MatRef<'_, T>]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
if matrices.is_empty() {
return Mat::zeros(0, 0);
}
let ncols = matrices[0].ncols();
for m in matrices {
assert_eq!(
m.ncols(),
ncols,
"All matrices must have the same number of columns"
);
}
let total_rows: usize = matrices.iter().map(|m| m.nrows()).sum();
let mut result = Mat::zeros(total_rows, ncols);
let mut row_offset = 0;
for mat in matrices {
for j in 0..ncols {
for i in 0..mat.nrows() {
result[(row_offset + i, j)] = mat[(i, j)];
}
}
row_offset += mat.nrows();
}
result
}
pub fn hstack(matrices: &[oxiblas_matrix::MatRef<'_, T>]) -> Mat<T>
where
T: bytemuck::Zeroable,
{
if matrices.is_empty() {
return Mat::zeros(0, 0);
}
let nrows = matrices[0].nrows();
for m in matrices {
assert_eq!(
m.nrows(),
nrows,
"All matrices must have the same number of rows"
);
}
let total_cols: usize = matrices.iter().map(|m| m.ncols()).sum();
let mut result = Mat::zeros(nrows, total_cols);
let mut col_offset = 0;
for mat in matrices {
for j in 0..mat.ncols() {
for i in 0..nrows {
result[(i, col_offset + j)] = mat[(i, j)];
}
}
col_offset += mat.ncols();
}
result
}
}
impl MatBuilder<f64> {
pub fn hilbert(n: usize) -> Mat<f64> {
Self::from_fn(n, n, |i, j| 1.0 / ((i + j + 1) as f64))
}
pub fn vandermonde(v: &[f64], ncols: Option<usize>) -> Mat<f64> {
let nrows = v.len();
let ncols = ncols.unwrap_or(nrows);
Self::from_fn(nrows, ncols, |i, j| v[i].powi(j as i32))
}
pub fn toeplitz(col: &[f64], row: &[f64]) -> Mat<f64> {
let nrows = col.len();
let ncols = row.len();
Self::from_fn(
nrows,
ncols,
|i, j| {
if j >= i { row[j - i] } else { col[i - j] }
},
)
}
pub fn hankel(col: &[f64], row: &[f64]) -> Mat<f64> {
let nrows = col.len();
let ncols = row.len();
Self::from_fn(nrows, ncols, |i, j| {
let sum = i + j;
if sum < nrows {
col[sum]
} else {
row[sum - nrows + 1]
}
})
}
pub fn companion(coeffs: &[f64]) -> Mat<f64> {
let n = coeffs.len();
if n == 0 {
return Mat::zeros(0, 0);
}
let mut m = Mat::zeros(n, n);
for i in 1..n {
m[(i, i - 1)] = 1.0;
}
for (i, &c) in coeffs.iter().enumerate() {
m[(i, n - 1)] = -c;
}
m
}
pub fn circulant(v: &[f64]) -> Mat<f64> {
let n = v.len();
Self::from_fn(n, n, |i, j| {
let idx = (j + n - i) % n;
v[idx]
})
}
pub fn cauchy(x: &[f64], y: &[f64]) -> Mat<f64> {
let nrows = x.len();
let ncols = y.len();
Self::from_fn(nrows, ncols, |i, j| 1.0 / (x[i] - y[j]))
}
pub fn random(nrows: usize, ncols: usize, seed: u64) -> Mat<f64> {
let mut state = seed;
Self::from_fn(nrows, ncols, |_, _| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
(state >> 11) as f64 / (1u64 << 53) as f64
})
}
pub fn random_spd(n: usize, seed: u64) -> Mat<f64> {
let a = Self::random(n, n, seed);
let mut result = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += a[(i, k)] * a[(j, k)];
}
result[(i, j)] = sum;
}
}
for i in 0..n {
result[(i, i)] += n as f64;
}
result
}
pub fn linspace(nrows: usize, ncols: usize, start: f64, end: f64) -> Mat<f64> {
let total = nrows * ncols;
if total == 0 {
return Mat::zeros(nrows, ncols);
}
if total == 1 {
return Mat::filled(nrows, ncols, start);
}
let step = (end - start) / ((total - 1) as f64);
let mut m = Mat::zeros(nrows, ncols);
let mut idx = 0;
for j in 0..ncols {
for i in 0..nrows {
m[(i, j)] = start + (idx as f64) * step;
idx += 1;
}
}
m
}
}
impl MatBuilder<f32> {
pub fn hilbert(n: usize) -> Mat<f32> {
Self::from_fn(n, n, |i, j| 1.0 / ((i + j + 1) as f32))
}
pub fn random(nrows: usize, ncols: usize, seed: u64) -> Mat<f32> {
let mut state = seed;
Self::from_fn(nrows, ncols, |_, _| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
((state >> 11) as f64 / (1u64 << 53) as f64) as f32
})
}
pub fn random_spd(n: usize, seed: u64) -> Mat<f32> {
let a = Self::random(n, n, seed);
let mut result = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
let mut sum = 0.0f32;
for k in 0..n {
sum += a[(i, k)] * a[(j, k)];
}
result[(i, j)] = sum;
}
}
for i in 0..n {
result[(i, i)] += n as f32;
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zeros() {
let m = MatBuilder::<f64>::zeros(3, 4);
assert_eq!(m.nrows(), 3);
assert_eq!(m.ncols(), 4);
for i in 0..3 {
for j in 0..4 {
assert_eq!(m[(i, j)], 0.0);
}
}
}
#[test]
fn test_ones() {
let m = MatBuilder::<f64>::ones(2, 3);
for i in 0..2 {
for j in 0..3 {
assert_eq!(m[(i, j)], 1.0);
}
}
}
#[test]
fn test_identity() {
let eye = MatBuilder::<f64>::identity(4);
for i in 0..4 {
for j in 0..4 {
if i == j {
assert_eq!(eye[(i, j)], 1.0);
} else {
assert_eq!(eye[(i, j)], 0.0);
}
}
}
}
#[test]
fn test_from_fn() {
let m = MatBuilder::from_fn(3, 3, |i, j| (i * 10 + j) as f64);
assert_eq!(m[(0, 0)], 0.0);
assert_eq!(m[(1, 2)], 12.0);
assert_eq!(m[(2, 1)], 21.0);
}
#[test]
fn test_diagonal() {
let d = MatBuilder::diagonal(&[1.0, 2.0, 3.0]);
assert_eq!(d[(0, 0)], 1.0);
assert_eq!(d[(1, 1)], 2.0);
assert_eq!(d[(2, 2)], 3.0);
assert_eq!(d[(0, 1)], 0.0);
assert_eq!(d[(1, 0)], 0.0);
}
#[test]
fn test_tridiagonal() {
let t = MatBuilder::tridiagonal(&[1.0, 2.0], &[4.0, 5.0, 6.0], &[7.0, 8.0]);
assert_eq!(t[(0, 0)], 4.0);
assert_eq!(t[(0, 1)], 7.0);
assert_eq!(t[(1, 0)], 1.0);
assert_eq!(t[(1, 1)], 5.0);
assert_eq!(t[(1, 2)], 8.0);
assert_eq!(t[(2, 1)], 2.0);
assert_eq!(t[(2, 2)], 6.0);
}
#[test]
fn test_hilbert() {
let h = MatBuilder::<f64>::hilbert(4);
assert!((h[(0, 0)] - 1.0).abs() < 1e-10);
assert!((h[(0, 1)] - 0.5).abs() < 1e-10);
assert!((h[(1, 0)] - 0.5).abs() < 1e-10);
assert!((h[(1, 1)] - 1.0 / 3.0).abs() < 1e-10);
}
#[test]
fn test_vandermonde() {
let v = MatBuilder::<f64>::vandermonde(&[1.0, 2.0, 3.0], None);
assert_eq!(v[(0, 0)], 1.0); assert_eq!(v[(0, 1)], 1.0); assert_eq!(v[(0, 2)], 1.0); assert_eq!(v[(1, 0)], 1.0); assert_eq!(v[(1, 1)], 2.0); assert_eq!(v[(1, 2)], 4.0); assert_eq!(v[(2, 2)], 9.0); }
#[test]
fn test_toeplitz() {
let t = MatBuilder::<f64>::toeplitz(&[1.0, 2.0, 3.0], &[1.0, 4.0, 5.0]);
assert_eq!(t[(0, 0)], 1.0);
assert_eq!(t[(0, 1)], 4.0);
assert_eq!(t[(0, 2)], 5.0);
assert_eq!(t[(1, 0)], 2.0);
assert_eq!(t[(1, 1)], 1.0);
assert_eq!(t[(1, 2)], 4.0);
assert_eq!(t[(2, 0)], 3.0);
assert_eq!(t[(2, 1)], 2.0);
assert_eq!(t[(2, 2)], 1.0);
}
#[test]
fn test_circulant() {
let c = MatBuilder::<f64>::circulant(&[1.0, 2.0, 3.0]);
assert_eq!(c[(0, 0)], 1.0);
assert_eq!(c[(0, 1)], 2.0);
assert_eq!(c[(0, 2)], 3.0);
assert_eq!(c[(1, 0)], 3.0);
assert_eq!(c[(1, 1)], 1.0);
assert_eq!(c[(1, 2)], 2.0);
assert_eq!(c[(2, 0)], 2.0);
assert_eq!(c[(2, 1)], 3.0);
assert_eq!(c[(2, 2)], 1.0);
}
#[test]
fn test_random() {
let r = MatBuilder::<f64>::random(5, 5, 42);
for i in 0..5 {
for j in 0..5 {
assert!(r[(i, j)] >= 0.0 && r[(i, j)] < 1.0);
}
}
}
#[test]
fn test_random_spd() {
let spd = MatBuilder::<f64>::random_spd(4, 42);
for i in 0..4 {
for j in 0..4 {
assert!((spd[(i, j)] - spd[(j, i)]).abs() < 1e-10);
}
}
for i in 0..4 {
assert!(spd[(i, i)] > 0.0);
}
}
#[test]
fn test_vstack() {
let a: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[3.0, 4.0], &[5.0, 6.0]]);
let stacked = MatBuilder::vstack(&[a.as_ref(), b.as_ref()]);
assert_eq!(stacked.nrows(), 3);
assert_eq!(stacked.ncols(), 2);
assert_eq!(stacked[(0, 0)], 1.0);
assert_eq!(stacked[(1, 0)], 3.0);
assert_eq!(stacked[(2, 1)], 6.0);
}
#[test]
fn test_hstack() {
let a: Mat<f64> = Mat::from_rows(&[&[1.0], &[2.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[3.0, 4.0], &[5.0, 6.0]]);
let stacked = MatBuilder::hstack(&[a.as_ref(), b.as_ref()]);
assert_eq!(stacked.nrows(), 2);
assert_eq!(stacked.ncols(), 3);
assert_eq!(stacked[(0, 0)], 1.0);
assert_eq!(stacked[(0, 1)], 3.0);
assert_eq!(stacked[(1, 2)], 6.0);
}
#[test]
fn test_block_diagonal() {
let a: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0], &[3.0, 4.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[5.0]]);
let block_diag = MatBuilder::block_diagonal(&[a.as_ref(), b.as_ref()]);
assert_eq!(block_diag.nrows(), 3);
assert_eq!(block_diag.ncols(), 3);
assert_eq!(block_diag[(0, 0)], 1.0);
assert_eq!(block_diag[(1, 1)], 4.0);
assert_eq!(block_diag[(2, 2)], 5.0);
assert_eq!(block_diag[(0, 2)], 0.0);
assert_eq!(block_diag[(2, 0)], 0.0);
}
#[test]
fn test_linspace() {
let m = MatBuilder::<f64>::linspace(2, 3, 0.0, 5.0);
assert!((m[(0, 0)] - 0.0).abs() < 1e-10);
assert!((m[(1, 0)] - 1.0).abs() < 1e-10);
assert!((m[(0, 1)] - 2.0).abs() < 1e-10);
assert!((m[(1, 1)] - 3.0).abs() < 1e-10);
assert!((m[(0, 2)] - 4.0).abs() < 1e-10);
assert!((m[(1, 2)] - 5.0).abs() < 1e-10);
}
#[test]
fn test_companion() {
let c = MatBuilder::<f64>::companion(&[2.0, -3.0]);
assert_eq!(c.nrows(), 2);
assert_eq!(c[(0, 1)], -2.0);
assert_eq!(c[(1, 0)], 1.0);
assert_eq!(c[(1, 1)], 3.0);
}
#[test]
fn test_col_vec() {
let v = MatBuilder::col_vec(&[1.0, 2.0, 3.0]);
assert_eq!(v.nrows(), 3);
assert_eq!(v.ncols(), 1);
assert_eq!(v[(0, 0)], 1.0);
assert_eq!(v[(1, 0)], 2.0);
assert_eq!(v[(2, 0)], 3.0);
}
#[test]
fn test_row_vec() {
let v = MatBuilder::row_vec(&[1.0, 2.0, 3.0]);
assert_eq!(v.nrows(), 1);
assert_eq!(v.ncols(), 3);
assert_eq!(v[(0, 0)], 1.0);
assert_eq!(v[(0, 1)], 2.0);
assert_eq!(v[(0, 2)], 3.0);
}
}