use scirs2_core::ndarray::{Array1, Array2, ArrayView2, ScalarOperand};
use scirs2_core::numeric::{Complex, Float, NumAssign, One};
use std::iter::Sum;
use crate::error::{LinalgError, LinalgResult};
use crate::lapack::{cholesky as lapack_cholesky, lu_factor, qr_factor, svd as lapack_svd};
use crate::validation::validate_decomposition;
use scirs2_core::linalg::{
cholesky_ndarray, eig_ndarray, eig_symmetric, qr_ndarray, svd_ndarray, Eigenvalue,
};
#[allow(dead_code)]
type QZResult<F> = LinalgResult<(Array2<F>, Array2<F>, Array2<F>, Array2<F>)>;
#[allow(dead_code)]
type CODResult<F> = LinalgResult<(Array2<F>, Array2<F>, Array2<F>)>;
type EigResult = LinalgResult<(Array1<Complex<f64>>, Array2<Complex<f64>>)>;
#[allow(dead_code)]
pub fn cholesky<F>(a: &ArrayView2<F>, workers: Option<usize>) -> LinalgResult<Array2<F>>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
validate_decomposition(a, "Cholesky decomposition", true)?;
let use_work_stealing = if let Some(num_workers) = workers {
num_workers > 1 && a.nrows() > 100
} else {
false
};
if use_work_stealing {
use crate::parallel::parallel_cholesky_work_stealing;
parallel_cholesky_work_stealing(a, workers.expect("Operation failed"))
} else {
if let Some(num_workers) = workers {
std::env::set_var("OMP_NUM_THREADS", num_workers.to_string());
}
match lapack_cholesky(a) {
Ok(result) => Ok(result),
Err(e) => {
match e {
LinalgError::NonPositiveDefiniteError(_) => {
Err(LinalgError::non_positive_definite_with_suggestions(
"Cholesky decomposition",
a.dim(),
None,
))
}
LinalgError::SingularMatrixError(_) => {
Err(LinalgError::singularmatrix_with_suggestions(
"Cholesky decomposition",
a.dim(),
None,
))
}
_ => Err(e),
}
}
}
}
}
#[allow(dead_code)]
pub fn lu<F>(
a: &ArrayView2<F>,
workers: Option<usize>,
) -> LinalgResult<(Array2<F>, Array2<F>, Array2<F>)>
where
F: Float + NumAssign + One + Sum + Send + Sync + ScalarOperand + 'static,
{
if a.is_empty() {
return Err(LinalgError::ShapeError(
"LU decomposition failed: Input matrix cannot be empty".to_string(),
));
}
for &val in a.iter() {
if !val.is_finite() {
return Err(LinalgError::InvalidInputError(
"LU decomposition failed: Matrix contains non-finite values".to_string(),
));
}
}
let use_work_stealing = if let Some(num_workers) = workers {
num_workers > 1 && a.nrows() > 100
} else {
false
};
if use_work_stealing {
if let Some(num_workers) = workers {
use crate::parallel::parallel_lu_work_stealing;
let (l, u, piv) = parallel_lu_work_stealing(a, num_workers)?;
let n = a.nrows();
let mut p = Array2::<F>::zeros((n, n));
for (i, &piv_index) in piv.iter().enumerate() {
p[[i, piv_index]] = F::one();
}
return Ok((p, l, u));
}
}
if let Some(num_workers) = workers {
std::env::set_var("OMP_NUM_THREADS", num_workers.to_string());
}
let lu_result = match lu_factor(a) {
Ok(result) => result,
Err(e) => {
match e {
LinalgError::SingularMatrixError(_) => {
return Err(LinalgError::singularmatrix_with_suggestions(
"LU decomposition",
a.dim(),
None,
));
}
_ => return Err(e),
}
}
};
let n = a.nrows();
let m = a.ncols();
let mut p = Array2::<F>::zeros((n, n));
for (i, &piv) in lu_result.piv.iter().enumerate() {
p[[i, piv]] = F::one();
}
let mut l = Array2::<F>::zeros((n, n.min(m)));
for i in 0..n {
for j in 0..i.min(m) {
l[[i, j]] = lu_result.lu[[i, j]];
}
if i < m {
l[[i, i]] = F::one(); }
}
let mut u = Array2::<F>::zeros((n.min(m), m));
for i in 0..n.min(m) {
for j in i..m {
u[[i, j]] = lu_result.lu[[i, j]];
}
}
Ok((p, l, u))
}
#[allow(dead_code)]
pub fn qr<F>(a: &ArrayView2<F>, workers: Option<usize>) -> LinalgResult<(Array2<F>, Array2<F>)>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
if a.is_empty() {
return Err(LinalgError::ShapeError(
"QR decomposition failed: Input matrix cannot be empty".to_string(),
));
}
for &val in a.iter() {
if !val.is_finite() {
return Err(LinalgError::InvalidInputError(
"QR decomposition failed: Matrix contains non-finite values".to_string(),
));
}
}
let use_work_stealing = if let Some(num_workers) = workers {
num_workers > 1 && a.nrows() > 100
} else {
false
};
if use_work_stealing {
use crate::parallel::parallel_qr_work_stealing;
return parallel_qr_work_stealing(a, workers.expect("Operation failed"));
}
if let Some(num_workers) = workers {
std::env::set_var("OMP_NUM_THREADS", num_workers.to_string());
}
match qr_factor(a) {
Ok(qr_result) => Ok((qr_result.q, qr_result.r)),
Err(e) => {
match e {
LinalgError::SingularMatrixError(_) => Err(
LinalgError::singularmatrix_with_suggestions("QR decomposition", a.dim(), None),
),
_ => Err(e),
}
}
}
}
#[allow(dead_code)]
pub fn svd<F>(
a: &ArrayView2<F>,
full_matrices: bool,
workers: Option<usize>,
) -> LinalgResult<(Array2<F>, Array1<F>, Array2<F>)>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
if a.is_empty() {
return Err(LinalgError::ShapeError(
"SVD computation failed: Input matrix cannot be empty".to_string(),
));
}
for &val in a.iter() {
if !val.is_finite() {
return Err(LinalgError::InvalidInputError(
"SVD computation failed: Matrix contains non-finite values".to_string(),
));
}
}
let use_work_stealing = if let Some(num_workers) = workers {
num_workers > 1 && a.nrows() > 100
} else {
false
};
if use_work_stealing {
use crate::parallel::parallel_svd_work_stealing;
return parallel_svd_work_stealing(a, workers.expect("Operation failed"));
}
if let Some(num_workers) = workers {
std::env::set_var("OMP_NUM_THREADS", num_workers.to_string());
}
match lapack_svd(a, full_matrices) {
Ok(svd_result) => Ok((svd_result.u, svd_result.s, svd_result.vt)),
Err(e) => {
match e {
LinalgError::ConvergenceError(_) => {
Err(LinalgError::ConvergenceError(format!(
"SVD computation failed to converge\nMatrix shape: {}×{}\nSuggestions:\n1. Try different SVD algorithm or increase iteration limit\n2. Check matrix conditioning - use condition number estimation\n3. Consider rank-revealing QR decomposition for rank-deficient _matrices",
a.nrows(), a.ncols()
)))
}
_ => Err(e),
}
}
}
}
#[allow(dead_code)]
pub fn cholesky_default<F>(a: &ArrayView2<F>) -> LinalgResult<Array2<F>>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
cholesky(a, None)
}
#[allow(dead_code)]
pub fn lu_default<F>(a: &ArrayView2<F>) -> LinalgResult<(Array2<F>, Array2<F>, Array2<F>)>
where
F: Float + NumAssign + One + Sum + Send + Sync + ScalarOperand + 'static,
{
lu(a, None)
}
#[allow(dead_code)]
pub fn qr_default<F>(a: &ArrayView2<F>) -> LinalgResult<(Array2<F>, Array2<F>)>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
qr(a, None)
}
#[allow(dead_code)]
pub fn svd_default<F>(
a: &ArrayView2<F>,
full_matrices: bool,
) -> LinalgResult<(Array2<F>, Array1<F>, Array2<F>)>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
svd(a, full_matrices, None)
}
#[allow(dead_code)]
pub fn schur<F>(a: &ArrayView2<F>) -> LinalgResult<(Array2<F>, Array2<F>)>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for Schur decomposition, got shape {:?}",
a.shape()
)));
}
let n = a.nrows();
let mut t = a.to_owned();
let mut z = Array2::eye(n);
let max_iter = 100;
for _ in 0..max_iter {
let (q, r) = qr(&t.view(), None)?;
t = r.dot(&q); z = z.dot(&q); }
Ok((z, t))
}
#[allow(dead_code)]
pub fn qz<F>(a: &ArrayView2<F>, b: &ArrayView2<F>) -> QZResult<F>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
if a.nrows() != a.ncols() || b.nrows() != b.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrices must be square for QZ decomposition, got shapes {:?} and {:?}",
a.shape(),
b.shape()
)));
}
if a.nrows() != b.nrows() {
return Err(LinalgError::ShapeError(format!(
"Matrices must have the same dimensions for QZ decomposition, got shapes {:?} and {:?}",
a.shape(),
b.shape()
)));
}
let n = a.nrows();
let mut q = Array2::eye(n);
let mut z = Array2::eye(n);
let mut a_temp = a.to_owned();
let mut b_temp = b.to_owned();
let max_iter = 30;
for _ in 0..max_iter {
let (q1, r1) = qr(&b_temp.view(), None)?;
let q1t = q1.t();
let a1 = q1t.dot(&a_temp);
let b1 = r1;
let (q2t, r2t) = qr(&a1.t().view(), None)?;
let q2 = q2t.t();
let r2 = r2t.t().to_owned();
a_temp = r2;
b_temp = b1.dot(&q2);
q = q.dot(&q1);
z = z.dot(&q2);
}
let a_decomp = a_temp;
let mut b_decomp = b_temp;
for i in 1..n {
for j in 0..i {
b_decomp[[i, j]] = F::zero();
}
}
Ok((q, a_decomp, b_decomp, z))
}
#[allow(dead_code)]
pub fn complete_orthogonal_decomposition<F>(a: &ArrayView2<F>) -> CODResult<F>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
let n_rows = a.nrows();
let n_cols = a.ncols();
let mut a_copy = a.to_owned();
let mut p = Array2::eye(n_cols);
let mut col_norms = vec![F::zero(); n_cols];
for (j, norm) in col_norms.iter_mut().enumerate().take(n_cols) {
let col = a.column(j);
*norm = col.iter().fold(F::zero(), |acc, &x| acc + x * x).sqrt();
}
let min_dim = n_rows.min(n_cols);
let mut q = Array2::eye(n_rows);
for k in 0..min_dim {
let mut max_norm = F::zero();
let mut max_idx = k;
for (j, &norm) in col_norms.iter().enumerate().skip(k).take(n_cols - k) {
if norm > max_norm {
max_norm = norm;
max_idx = j;
}
}
if max_norm < F::epsilon() {
break;
}
if k != max_idx {
for i in 0..n_rows {
let temp = a_copy[[i, k]];
a_copy[[i, k]] = a_copy[[i, max_idx]];
a_copy[[i, max_idx]] = temp;
}
for i in 0..n_cols {
let temp = p[[i, k]];
p[[i, k]] = p[[i, max_idx]];
p[[i, max_idx]] = temp;
}
col_norms.swap(k, max_idx);
}
let mut x = Array1::zeros(n_rows - k);
for i in k..n_rows {
x[i - k] = a_copy[[i, k]];
}
let x_norm = x.iter().fold(F::zero(), |acc, &val| acc + val * val).sqrt();
if x_norm > F::epsilon() {
let alpha = if x[0] >= F::zero() { -x_norm } else { x_norm };
let mut v = x.clone();
v[0] -= alpha;
let v_norm = v.iter().fold(F::zero(), |acc, &val| acc + val * val).sqrt();
if v_norm > F::epsilon() {
for i in 0..v.len() {
v[i] /= v_norm;
}
for j in k..n_cols {
let mut column = Array1::zeros(n_rows - k);
for i in k..n_rows {
column[i - k] = a_copy[[i, j]];
}
let dot_product = v
.iter()
.zip(column.iter())
.fold(F::zero(), |acc, (&vi, &ci)| acc + vi * ci);
let two = F::from(2.0).ok_or_else(|| {
LinalgError::NumericalError(
"Failed to convert 2.0 to target type".to_string(),
)
})?;
for i in k..n_rows {
a_copy[[i, j]] -= two * v[i - k] * dot_product;
}
}
let mut q_sub = Array2::zeros((n_rows, n_rows));
for i in 0..n_rows {
q_sub[[i, i]] = F::one();
}
let two = F::from(2.0).ok_or_else(|| {
LinalgError::NumericalError("Failed to convert 2.0 to target type".to_string())
})?;
for i in k..n_rows {
for j in k..n_rows {
q_sub[[i, j]] -= two * v[i - k] * v[j - k];
}
}
let q_new = q.dot(&q_sub);
q = q_new;
}
}
for j in (k + 1)..n_cols {
col_norms[j] = F::zero();
for i in (k + 1)..n_rows {
col_norms[j] += a_copy[[i, j]] * a_copy[[i, j]];
}
col_norms[j] = col_norms[j].sqrt();
}
}
let mut rank = 0;
for i in 0..min_dim {
if a_copy[[i, i]].abs() > F::epsilon() {
rank += 1;
} else {
break;
}
}
if rank < min_dim {
for k in (0..rank).rev() {
let mut x = Array1::zeros(n_cols - k);
for j in k..n_cols {
x[j - k] = a_copy[[k, j]];
}
let x_norm = x.iter().fold(F::zero(), |acc, &val| acc + val * val).sqrt();
if x_norm > F::epsilon() {
let alpha = if x[0] >= F::zero() { -x_norm } else { x_norm };
let mut v = x.clone();
v[0] -= alpha;
let v_norm = v.iter().fold(F::zero(), |acc, &val| acc + val * val).sqrt();
if v_norm > F::epsilon() {
for i in 0..v.len() {
v[i] /= v_norm;
}
for i in 0..=k {
let mut row = Array1::zeros(n_cols - k);
for j in k..n_cols {
row[j - k] = a_copy[[i, j]];
}
let dot_product = v
.iter()
.zip(row.iter())
.fold(F::zero(), |acc, (&vi, &ri)| acc + vi * ri);
let two = F::from(2.0).ok_or_else(|| {
LinalgError::NumericalError(
"Failed to convert 2.0 to target type".to_string(),
)
})?;
for j in k..n_cols {
a_copy[[i, j]] -= two * v[j - k] * dot_product;
}
}
let mut p_sub = Array2::zeros((n_cols, n_cols));
for i in 0..n_cols {
p_sub[[i, i]] = F::one();
}
for i in k..n_cols {
for j in k..n_cols {
let two = F::from(2.0).ok_or_else(|| {
LinalgError::NumericalError(
"Failed to convert 2.0 to target type".to_string(),
)
})?;
p_sub[[i, j]] -= two * v[i - k] * v[j - k];
}
}
p = p.dot(&p_sub);
}
}
}
}
Ok((q, a_copy, p))
}
pub fn qr_f64_lapack(a: &ArrayView2<f64>) -> LinalgResult<(Array2<f64>, Array2<f64>)> {
let qr_result = qr_ndarray(&a.to_owned())
.map_err(|e| LinalgError::ComputationError(format!("OxiBLAS QR failed: {:?}", e)))?;
Ok((qr_result.q, qr_result.r))
}
#[allow(unused_variables)]
pub fn svd_f64_lapack(
a: &ArrayView2<f64>,
full_matrices: bool,
) -> LinalgResult<(Array2<f64>, Array1<f64>, Array2<f64>)> {
let svd_result = svd_ndarray(&a.to_owned())
.map_err(|e| LinalgError::ComputationError(format!("OxiBLAS SVD failed: {:?}", e)))?;
Ok((svd_result.u, svd_result.s, svd_result.vt))
}
pub fn cholesky_f64_lapack(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for Cholesky: got {}×{}",
a.nrows(),
a.ncols()
)));
}
let result = cholesky_ndarray(&a.to_owned())
.map_err(|e| LinalgError::ComputationError(format!("OxiBLAS Cholesky failed: {:?}", e)))?;
Ok(result.l)
}
pub fn eigh_f64_lapack(a: &ArrayView2<f64>) -> LinalgResult<(Array1<f64>, Array2<f64>)> {
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for eigenvalue decomposition: got {}×{}",
a.nrows(),
a.ncols()
)));
}
let result = eig_symmetric(&a.to_owned())
.map_err(|e| LinalgError::ComputationError(format!("OxiBLAS eigh failed: {:?}", e)))?;
Ok((result.eigenvalues, result.eigenvectors))
}
pub fn eig_f64_lapack(a: &ArrayView2<f64>) -> EigResult {
let n = a.nrows();
if n != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for eigenvalue decomposition: got {}×{}",
a.nrows(),
a.ncols()
)));
}
let result = eig_ndarray(&a.to_owned())
.map_err(|e| LinalgError::ComputationError(format!("OxiBLAS eig failed: {:?}", e)))?;
let eigvals: Array1<Complex<f64>> = Array1::from_iter(
result
.eigenvalues
.iter()
.map(|e| Complex::new(e.real, e.imag)),
);
let eigvecs_real = result
.eigenvectors_real
.unwrap_or_else(|| Array2::zeros((n, n)));
let eigvecs_imag = result
.eigenvectors_imag
.unwrap_or_else(|| Array2::zeros((n, n)));
let mut eigvecs: Array2<Complex<f64>> = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
eigvecs[[i, j]] = Complex::new(eigvecs_real[[i, j]], eigvecs_imag[[i, j]]);
}
}
Ok((eigvals, eigvecs))
}
pub fn lu_f64_lapack(a: &ArrayView2<f64>) -> LinalgResult<(Array2<f64>, Array2<f64>, Array2<f64>)> {
crate::lu(a, None)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_cholesky_2x2() {
let a = array![[4.0, 2.0], [2.0, 5.0]];
let l = cholesky(&a.view(), None).expect("Operation failed");
assert!((l[[0, 0]] - 2.0).abs() < 1e-10);
assert!((l[[0, 1]] - 0.0).abs() < 1e-10);
assert!((l[[1, 0]] - 1.0).abs() < 1e-10);
assert!((l[[1, 1]] - 2.0).abs() < 1e-10);
let l_t = l.t().to_owned();
let a_reconstructed = l.dot(&l_t);
for i in 0..2 {
for j in 0..2 {
assert!((a_reconstructed[[i, j]] - a[[i, j]]).abs() < 1e-10);
}
}
}
#[test]
fn test_lu() {
let a = array![[2.0, 1.0], [4.0, 3.0]];
let (p, l, u) = lu(&a.view(), None).expect("Operation failed");
let pa = p.dot(&a);
let lu = l.dot(&u);
assert_relative_eq!(pa[[0, 0]], lu[[0, 0]], epsilon = 1e-10);
assert_relative_eq!(pa[[0, 1]], lu[[0, 1]], epsilon = 1e-10);
assert_relative_eq!(pa[[1, 0]], lu[[1, 0]], epsilon = 1e-10);
assert_relative_eq!(pa[[1, 1]], lu[[1, 1]], epsilon = 1e-10);
}
#[test]
fn test_qr() {
let a = array![[1.0, 2.0], [3.0, 4.0]];
let (q, r) = qr(&a.view(), None).expect("Operation failed");
let qt = q.t();
let q_orthogonal = q.dot(&qt);
assert_relative_eq!(q_orthogonal[[0, 0]], 1.0, epsilon = 1e-5);
assert_relative_eq!(q_orthogonal[[0, 1]], 0.0, epsilon = 1e-5);
assert_relative_eq!(q_orthogonal[[1, 0]], 0.0, epsilon = 1e-5);
assert_relative_eq!(q_orthogonal[[1, 1]], 1.0, epsilon = 1e-5);
let qr = q.dot(&r);
assert_relative_eq!(qr[[0, 0]], a[[0, 0]], epsilon = 1e-5);
assert_relative_eq!(qr[[0, 1]], a[[0, 1]], epsilon = 1e-5);
assert_relative_eq!(qr[[1, 0]], a[[1, 0]], epsilon = 1e-5);
assert_relative_eq!(qr[[1, 1]], a[[1, 1]], epsilon = 1e-5);
}
#[test]
fn test_schur() {
let a = array![[1.0, 2.0], [3.0, 4.0]];
let (z, t) = schur(&a.view()).expect("Operation failed");
let zt = z.t();
let z_orthogonal = z.dot(&zt);
assert_relative_eq!(z_orthogonal[[0, 0]], 1.0, epsilon = 1e-5);
assert_relative_eq!(z_orthogonal[[0, 1]], 0.0, epsilon = 1e-5);
assert_relative_eq!(z_orthogonal[[1, 0]], 0.0, epsilon = 1e-5);
assert_relative_eq!(z_orthogonal[[1, 1]], 1.0, epsilon = 1e-5);
let ztzt = z.dot(&t).dot(&zt);
assert_relative_eq!(ztzt[[0, 0]], a[[0, 0]], epsilon = 1e-5);
assert_relative_eq!(ztzt[[0, 1]], a[[0, 1]], epsilon = 1e-5);
assert_relative_eq!(ztzt[[1, 0]], a[[1, 0]], epsilon = 1e-5);
assert_relative_eq!(ztzt[[1, 1]], a[[1, 1]], epsilon = 1e-5);
assert!(t[[1, 0]].abs() < 1e-5);
}
#[test]
fn test_qz() {
let a = array![[1.0, 2.0], [3.0, 4.0]];
let b = array![[5.0, 6.0], [7.0, 8.0]];
let (q, a_decomp, b_decomp, z) = qz(&a.view(), &b.view()).expect("Operation failed");
let qt = q.t();
let q_orthogonal = q.dot(&qt);
assert_relative_eq!(q_orthogonal[[0, 0]], 1.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[0, 1]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[1, 0]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[1, 1]], 1.0, epsilon = 1e-3);
let zt = z.t();
let z_orthogonal = z.dot(&zt);
assert_relative_eq!(z_orthogonal[[0, 0]], 1.0, epsilon = 1e-3);
assert_relative_eq!(z_orthogonal[[0, 1]], 0.0, epsilon = 1e-3);
assert_relative_eq!(z_orthogonal[[1, 0]], 0.0, epsilon = 1e-3);
assert_relative_eq!(z_orthogonal[[1, 1]], 1.0, epsilon = 1e-3);
assert!(b_decomp[[1, 0]].abs() < 1e-3);
}
#[test]
fn test_complete_orthogonal_decomposition() {
let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let (q, r, p) = complete_orthogonal_decomposition(&a.view()).expect("Operation failed");
let qt = q.t();
let q_orthogonal = q.dot(&qt);
assert_relative_eq!(q_orthogonal[[0, 0]], 1.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[0, 1]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[0, 2]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[1, 0]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[1, 1]], 1.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[1, 2]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[2, 0]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[2, 1]], 0.0, epsilon = 1e-3);
assert_relative_eq!(q_orthogonal[[2, 2]], 1.0, epsilon = 1e-3);
let pt = p.t();
let p_orthogonal = p.dot(&pt);
assert_relative_eq!(p_orthogonal[[0, 0]], 1.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[0, 1]], 0.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[0, 2]], 0.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[1, 0]], 0.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[1, 1]], 1.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[1, 2]], 0.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[2, 0]], 0.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[2, 1]], 0.0, epsilon = 1e-3);
assert_relative_eq!(p_orthogonal[[2, 2]], 1.0, epsilon = 1e-3);
for i in 1..3 {
for j in 0..i {
assert!(r[[i, j]].abs() < 1e-3);
}
}
for i in 1..3 {
for j in 0..i {
assert!(r[[i, j]].abs() < 0.5);
}
}
}
}