use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::{Float, FromPrimitive, NumAssign, One, Zero};
use scirs2_core::random::{self, ChaCha8Rng, Rng, RngExt, SeedableRng};
use std::iter::Sum;
use crate::decomposition::qr;
use crate::error::LinalgResult;
#[allow(dead_code)]
pub fn uniform<F>(rows: usize, cols: usize, low: F, high: F, seed: Option<u64>) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let range = high - low;
let mut result = Array2::<F>::zeros((rows, cols));
for i in 0..rows {
for j in 0..cols {
let r: f64 = rng.random_range(0.0..1.0);
let val = low + F::from_f64(r).expect("Operation failed") * range;
result[[i, j]] = val;
}
}
result
}
#[allow(dead_code)]
pub fn random_normalmatrix<F>(shape: (usize, usize), seed: Option<u64>) -> LinalgResult<Array2<F>>
where
F: Float + Zero + One + Copy + scirs2_core::numeric::FromPrimitive + NumAssign + 'static,
{
Ok(normal(shape.0, shape.1, F::zero(), F::one(), seed))
}
#[allow(dead_code)]
pub fn normal<F>(rows: usize, cols: usize, mean: F, std: F, seed: Option<u64>) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let mut result = Array2::<F>::zeros((rows, cols));
for i in 0..rows {
for j in 0..cols {
let u1: f64 = rng.random_range(0.00001..0.99999); let u2: f64 = rng.random_range(0.0..1.0);
let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
let val = mean + F::from_f64(z0).expect("Operation failed") * std;
result[[i, j]] = val;
}
}
result
}
#[allow(dead_code)]
pub fn orthogonal<F>(n: usize, seed: Option<u64>) -> Array2<F>
where
F: Float
+ NumAssign
+ FromPrimitive
+ Clone
+ std::fmt::Debug
+ Sum
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
let a = normal(n, n, F::zero(), F::one(), seed);
let (q, _) = qr(&a.view(), None).expect("Operation failed");
q
}
#[allow(dead_code)]
pub fn spd<F>(n: usize, min_eigenval: F, maxeigenval: F, seed: Option<u64>) -> Array2<F>
where
F: Float
+ NumAssign
+ FromPrimitive
+ Clone
+ std::fmt::Debug
+ Sum
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
let a = normal(n, n, F::zero(), F::one(), seed);
let at = a.t();
let mut result = at.dot(&a);
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let mut diag_values = Array1::<F>::zeros(n);
for i in 0..n {
let r: f64 = rng.random_range(0.0..1.0);
let range = maxeigenval - min_eigenval;
diag_values[i] = min_eigenval + F::from_f64(r).expect("Operation failed") * range;
}
for i in 0..n {
result[[i, i]] += diag_values[i];
}
result
}
#[allow(dead_code)]
pub fn diagonal<F>(n: usize, low: F, high: F, seed: Option<u64>) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let range = high - low;
let mut diag = Array1::<F>::zeros(n);
for i in 0..n {
let r: f64 = rng.random_range(0.0..1.0);
diag[i] = low + F::from_f64(r).expect("Operation failed") * range;
}
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
result[[i, i]] = diag[i];
}
result
}
#[allow(dead_code)]
pub fn banded<F>(
rows: usize,
cols: usize,
lower_bandwidth: usize,
upper_bandwidth: usize,
low: F,
high: F,
seed: Option<u64>,
) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut result = Array2::<F>::zeros((rows, cols));
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let range = high - low;
for i in 0..rows {
let j_start = i.saturating_sub(lower_bandwidth);
let j_end = (i + upper_bandwidth + 1).min(cols);
for j in j_start..j_end {
let r: f64 = rng.random_range(0.0..1.0);
result[[i, j]] = low + F::from_f64(r).expect("Operation failed") * range;
}
}
result
}
#[allow(dead_code)]
pub fn sparse<F>(
rows: usize,
cols: usize,
density: f64,
low: F,
high: F,
seed: Option<u64>,
) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
if !(0.0..=1.0).contains(&density) {
panic!("Density must be between 0 and 1");
}
let mut result = Array2::<F>::zeros((rows, cols));
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let range = high - low;
for i in 0..rows {
for j in 0..cols {
let p: f64 = rng.random_range(0.0..1.0);
if p < density {
let r: f64 = rng.random_range(0.0..1.0);
result[[i, j]] = low + F::from_f64(r).expect("Operation failed") * range;
}
}
}
result
}
#[allow(dead_code)]
pub fn toeplitz<F>(n: usize, low: F, high: F, seed: Option<u64>) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let range = high - low;
let mut first_row = Array1::<F>::zeros(n);
let mut first_col = Array1::<F>::zeros(n);
for i in 0..n {
let r: f64 = rng.random_range(0.0..1.0);
first_row[i] = low + F::from_f64(r).expect("Operation failed") * range;
}
first_col[0] = first_row[0];
for i in 1..n {
let r: f64 = rng.random_range(0.0..1.0);
first_col[i] = low + F::from_f64(r).expect("Operation failed") * range;
}
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
if i <= j {
result[[i, j]] = first_row[j - i];
} else {
result[[i, j]] = first_col[i - j];
}
}
}
result
}
#[allow(dead_code)]
pub fn with_condition_number<F>(n: usize, conditionnumber: F, seed: Option<u64>) -> Array2<F>
where
F: Float
+ NumAssign
+ FromPrimitive
+ Clone
+ std::fmt::Debug
+ Sum
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
if conditionnumber < F::one() {
panic!("Condition _number must be >= 1.0");
}
let q1 = orthogonal::<F>(n, seed);
let seed2 = seed.map(|s| s.wrapping_add(1));
let q2 = orthogonal::<F>(n, seed2);
let mut d = Array2::<F>::zeros((n, n));
let min_eigenval = F::one() / conditionnumber;
let log_min = min_eigenval.ln();
let log_max = F::one().ln();
for i in 0..n {
let t = F::from_f64((i as f64) / ((n - 1) as f64)).expect("Operation failed");
let log_val = log_min * t + log_max * (F::one() - t);
d[[i, i]] = log_val.exp();
}
let temp = q1.dot(&d);
let q2t = q2.t();
temp.dot(&q2t)
}
#[allow(dead_code)]
pub fn with_eigenvalues<F>(eigenvalues: &Array1<F>, seed: Option<u64>) -> Array2<F>
where
F: Float
+ NumAssign
+ FromPrimitive
+ Clone
+ std::fmt::Debug
+ Sum
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
let n = eigenvalues.len();
let q = orthogonal::<F>(n, seed);
let mut d = Array2::<F>::zeros((n, n));
for i in 0..n {
d[[i, i]] = eigenvalues[i];
}
let temp = q.dot(&d);
let qt = q.t();
temp.dot(&qt)
}
#[allow(dead_code)]
pub fn hilbert<F>(n: usize) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
let value = F::one() / F::from_f64((i + j + 1) as f64).expect("Operation failed");
result[[i, j]] = value;
}
}
result
}
#[allow(dead_code)]
pub fn vandermonde<F>(points: &Array1<F>) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let n = points.len();
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
let x = points[i];
result[[i, 0]] = F::one();
for j in 1..n {
result[[i, j]] = result[[i, j - 1]] * x;
}
}
result
}
#[allow(dead_code)]
pub fn random_correlation<F>(n: usize, seed: Option<u64>) -> Array2<F>
where
F: Float
+ NumAssign
+ FromPrimitive
+ Clone
+ std::fmt::Debug
+ Sum
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
let k = (n / 2) + 1;
let a = normal(n, k, F::zero(), F::one(), seed);
let at = a.t();
let result = a.dot(&at);
let mut corr = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
corr[[i, j]] = result[[i, j]] / (result[[i, i]] * result[[j, j]]).sqrt();
}
}
for i in 0..n {
corr[[i, i]] = F::one();
}
corr
}
#[allow(dead_code)]
pub fn low_rank<F>(rows: usize, cols: usize, rank: usize, seed: Option<u64>) -> Array2<F>
where
F: Float
+ NumAssign
+ FromPrimitive
+ Clone
+ std::fmt::Debug
+ Sum
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ 'static,
{
if rank > rows.min(cols) {
panic!("Rank must be less than or equal to min(rows, cols)");
}
if rank == 0 {
return Array2::<F>::zeros((rows, cols));
}
let mut left = Array2::<F>::zeros((rows, rank));
let mut right = Array2::<F>::zeros((rank, cols));
if rows >= rank {
let temp = orthogonal::<F>(rows, seed);
for i in 0..rows {
for j in 0..rank {
left[[i, j]] = temp[[i, j]];
}
}
} else {
left = normal(rows, rank, F::zero(), F::one(), seed);
}
if cols >= rank {
let temp = orthogonal::<F>(cols, seed.map(|s| s.wrapping_add(1)));
let temp_t = temp.t();
for i in 0..rank {
for j in 0..cols {
right[[i, j]] = temp_t[[i, j]];
}
}
} else {
right = normal(
rank,
cols,
F::zero(),
F::one(),
seed.map(|s| s.wrapping_add(1)),
);
}
let mut result = Array2::<F>::zeros((rows, cols));
for r in 0..rank {
let scaling = F::from_f64(1.0).expect("Operation failed");
let u_col = left.column(r).to_owned();
let v_row = right.row(r).to_owned();
for i in 0..rows {
for j in 0..cols {
result[[i, j]] += scaling * u_col[i] * v_row[j];
}
}
}
result
}
#[allow(dead_code)]
pub fn permutation<F>(n: usize, seed: Option<u64>) -> Array2<F>
where
F: Float + NumAssign + FromPrimitive + Clone + 'static,
{
let mut rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => {
let mut seedarr = [0u8; 32];
scirs2_core::random::rng().fill(&mut seedarr);
ChaCha8Rng::from_seed(seedarr)
}
};
let mut result = Array2::<F>::zeros((n, n));
let mut indices: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let j = rng.random_range(0..=i);
indices.swap(i, j);
}
for (i, &j) in indices.iter().enumerate() {
result[[i, j]] = F::one();
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decomposition::{cholesky, svd};
use crate::eigen::eigvals;
use approx::assert_relative_eq;
use scirs2_core::ndarray::{array, Array2};
#[test]
fn test_uniform() {
let rows = 3;
let cols = 4;
let low = -1.0;
let high = 1.0;
let a = uniform::<f64>(rows, cols, low, high, None);
let b = uniform::<f64>(rows, cols, low, high, Some(42));
let c = uniform::<f64>(rows, cols, low, high, Some(42));
assert_eq!(a.shape(), &[rows, cols]);
assert_eq!(b.shape(), &[rows, cols]);
assert!(a.iter().all(|&x| x >= low && x <= high));
assert!(b.iter().all(|&x| x >= low && x <= high));
assert_eq!(b, c);
assert_ne!(a, b);
}
#[test]
fn test_normal() {
let rows = 3;
let cols = 4;
let mean = 0.0;
let std = 1.0;
let a = normal::<f64>(rows, cols, mean, std, None);
let b = normal::<f64>(rows, cols, mean, std, Some(42));
let c = normal::<f64>(rows, cols, mean, std, Some(42));
assert_eq!(a.shape(), &[rows, cols]);
assert_eq!(b.shape(), &[rows, cols]);
assert_eq!(b, c);
assert_ne!(a, b);
}
#[test]
fn test_orthogonal() {
let n = 4;
let a = orthogonal::<f64>(n, None);
let b = orthogonal::<f64>(n, Some(42));
assert_eq!(a.shape(), &[n, n]);
assert_eq!(b.shape(), &[n, n]);
let at = a.t();
let result_a = at.dot(&a);
let _identity = Array2::<f64>::eye(n);
for i in 0..n {
for j in 0..n {
if i == j {
assert_relative_eq!(result_a[[i, j]], 1.0, epsilon = 1e-10);
} else {
assert_relative_eq!(result_a[[i, j]], 0.0, epsilon = 1e-10);
}
}
}
let bt = b.t();
let result_b = bt.dot(&b);
for i in 0..n {
for j in 0..n {
if i == j {
assert_relative_eq!(result_b[[i, j]], 1.0, epsilon = 1e-10);
} else {
assert_relative_eq!(result_b[[i, j]], 0.0, epsilon = 1e-10);
}
}
}
}
#[test]
fn test_spd() {
let n = 3;
let min_eigenval = 1.0;
let max_eigenval = 10.0;
let a = spd::<f64>(n, min_eigenval, max_eigenval, None);
let b = spd::<f64>(n, min_eigenval, max_eigenval, Some(42));
assert_eq!(a.shape(), &[n, n]);
assert_eq!(b.shape(), &[n, n]);
let at = a.t();
for i in 0..n {
for j in 0..n {
assert_relative_eq!(a[[i, j]], at[[i, j]], epsilon = 1e-10);
}
}
let chol_a = cholesky(&a.view(), None);
assert!(chol_a.is_ok());
let chol_b = cholesky(&b.view(), None);
assert!(chol_b.is_ok());
}
#[test]
fn test_diagonal() {
let n = 3;
let low = 1.0;
let high = 10.0;
let a = diagonal::<f64>(n, low, high, None);
let b = diagonal::<f64>(n, low, high, Some(42));
assert_eq!(a.shape(), &[n, n]);
assert_eq!(b.shape(), &[n, n]);
for i in 0..n {
for j in 0..n {
if i == j {
assert!(a[[i, j]] >= low && a[[i, j]] <= high);
assert!(b[[i, j]] >= low && b[[i, j]] <= high);
} else {
assert_eq!(a[[i, j]], 0.0);
assert_eq!(b[[i, j]], 0.0);
}
}
}
}
#[test]
fn test_banded() {
let rows = 5;
let cols = 5;
let lower = 1; let upper = 1; let low = -1.0;
let high = 1.0;
let a = banded::<f64>(rows, cols, lower, upper, low, high, Some(42));
assert_eq!(a.shape(), &[rows, cols]);
for i in 0..rows {
for j in 0..cols {
if (i as isize - j as isize).abs() <= (lower as isize).max(upper as isize) {
assert!(a[[i, j]] >= low && a[[i, j]] <= high);
} else {
assert_eq!(a[[i, j]], 0.0);
}
}
}
}
#[test]
fn test_sparse() {
let rows = 10;
let cols = 10;
let density = 0.2; let low = -1.0;
let high = 1.0;
let a = sparse::<f64>(rows, cols, density, low, high, Some(42));
assert_eq!(a.shape(), &[rows, cols]);
let non_zero_count = a.iter().filter(|&&x| x != 0.0).count();
let total_elements = rows * cols;
let expected_count = (total_elements as f64 * density) as usize;
let tolerance = (total_elements as f64 * 0.1) as usize; assert!(
non_zero_count >= expected_count - tolerance
&& non_zero_count <= expected_count + tolerance,
"Expected {} non-zero elements (±{}), but got {}",
expected_count,
tolerance,
non_zero_count
);
assert!(a
.iter()
.filter(|&&x| x != 0.0)
.all(|&x| x >= low && x <= high));
}
#[test]
fn test_toeplitz() {
let n = 5;
let low = -1.0;
let high = 1.0;
let a = toeplitz::<f64>(n, low, high, Some(42));
assert_eq!(a.shape(), &[n, n]);
for k in -(n as isize - 1)..n as isize {
let mut diagonal_values = Vec::new();
for i in 0..n {
for j in 0..n {
if (j as isize) - (i as isize) == k {
diagonal_values.push(a[[i, j]]);
}
}
}
if !diagonal_values.is_empty() {
let first_val = diagonal_values[0];
for val in &diagonal_values {
assert_relative_eq!(*val, first_val, epsilon = 1e-10);
}
}
}
}
#[test]
fn test_with_condition_number() {
let n = 4;
let condition = 5.0;
let a = with_condition_number::<f64>(n, condition, Some(42));
assert_eq!(a.shape(), &[n, n]);
if let Ok((_, s, _)) = svd(&a.view(), false, None) {
assert_eq!(s.len(), n);
for i in 0..n {
assert!(s[i] > 0.0);
}
} else {
println!("SVD failed for condition number matrix, skipping detailed verification");
}
}
#[test]
fn test_with_eigenvalues() {
let eigenvalues = array![1.0, 2.0, 3.0];
let n = eigenvalues.len();
let a = with_eigenvalues(&eigenvalues, Some(42));
assert_eq!(a.shape(), &[n, n]);
let computed_eigenvalues = eigvals(&a.view(), None).expect("Operation failed");
let mut real_computed = Vec::new();
for ev in computed_eigenvalues.iter() {
real_computed.push(ev.re);
}
real_computed.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
let mut sorted_expected = eigenvalues.to_vec();
sorted_expected.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
for (expected, computed) in sorted_expected.iter().zip(real_computed.iter()) {
assert!((expected - computed).abs() < 1e-10);
}
}
#[test]
fn test_hilbert() {
let n = 4;
let h = hilbert::<f64>(n);
assert_eq!(h.shape(), &[n, n]);
for i in 0..n {
for j in 0..n {
let expected = 1.0 / (i + j + 1) as f64;
assert_relative_eq!(h[[i, j]], expected, epsilon = 1e-10);
}
}
let ht = h.t();
for i in 0..n {
for j in 0..n {
assert_relative_eq!(h[[i, j]], ht[[i, j]], epsilon = 1e-10);
}
}
let chol = cholesky(&h.view(), None);
assert!(chol.is_ok());
}
#[test]
fn test_vandermonde() {
let points = array![1.0, 2.0, 3.0, 4.0];
let n = points.len();
let v = vandermonde(&points);
assert_eq!(v.shape(), &[n, n]);
for i in 0..n {
for j in 0..n {
let expected = points[i].powi(j as i32);
assert_relative_eq!(v[[i, j]], expected, epsilon = 1e-10);
}
}
}
#[test]
fn test_random_correlation() {
let n = 5;
let c = random_correlation::<f64>(n, Some(42));
assert_eq!(c.shape(), &[n, n]);
for i in 0..n {
assert_relative_eq!(c[[i, i]], 1.0, epsilon = 1e-10);
}
for i in 0..n {
for j in 0..n {
if i != j {
assert!(c[[i, j]] >= -1.0 && c[[i, j]] <= 1.0);
}
}
}
let ct = c.t();
for i in 0..n {
for j in 0..n {
assert_relative_eq!(c[[i, j]], ct[[i, j]], epsilon = 1e-10);
}
}
let eigenvalues = eigvals(&c.view(), None).expect("Operation failed");
for ev in eigenvalues.iter() {
assert!(ev.re >= -1e-10); }
}
#[test]
fn test_low_rank() {
let rows = 6;
let cols = 5;
let rank = 3;
let a = low_rank::<f64>(rows, cols, rank, Some(42));
assert_eq!(a.shape(), &[rows, cols]);
if let Ok((_, s, _)) = svd(&a.view(), false, None) {
for i in 0..rank {
assert!(s[i] > 1e-10);
}
let significant_count = s.iter().filter(|&&sv| sv > 1e-10).count();
assert!(
significant_count >= rank,
"Matrix has fewer significant singular values ({}) than requested rank ({})",
significant_count,
rank
);
} else {
println!("SVD failed for low-rank matrix, skipping detailed rank verification");
}
let zero_rank = low_rank::<f64>(3, 3, 0, None);
assert_eq!(zero_rank.shape(), &[3, 3]);
for i in 0..3 {
for j in 0..3 {
assert_eq!(zero_rank[[i, j]], 0.0);
}
}
}
#[test]
fn test_permutation() {
let n = 5;
let p = permutation::<f64>(n, Some(42));
assert_eq!(p.shape(), &[n, n]);
for i in 0..n {
let row_sum: f64 = p.row(i).sum();
let col_sum: f64 = p.column(i).sum();
assert_relative_eq!(row_sum, 1.0, epsilon = 1e-10);
assert_relative_eq!(col_sum, 1.0, epsilon = 1e-10);
}
for i in 0..n {
for j in 0..n {
assert!(p[[i, j]] == 0.0 || p[[i, j]] == 1.0);
}
}
let pt = p.t();
let result = p.dot(&pt);
for i in 0..n {
for j in 0..n {
if i == j {
assert_relative_eq!(result[[i, j]], 1.0, epsilon = 1e-10);
} else {
assert_relative_eq!(result[[i, j]], 0.0, epsilon = 1e-10);
}
}
}
}
}