#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::packed::{PackedMat, PackedMut, PackedRef, TriangularKind};
use crate::{Mat, MatMut, MatRef};
use num_complex::Complex;
use num_traits::{One, Zero};
use oxiblas_core::scalar::Scalar;
#[derive(Clone)]
pub struct SymmetricMat<T: Scalar> {
packed: PackedMat<T>,
}
impl<T: Scalar> SymmetricMat<T> {
pub fn zeros(n: usize, uplo: TriangularKind) -> Self
where
T: bytemuck::Zeroable,
{
SymmetricMat {
packed: PackedMat::zeros(n, uplo),
}
}
pub fn filled(n: usize, uplo: TriangularKind, value: T) -> Self {
SymmetricMat {
packed: PackedMat::filled(n, uplo, value),
}
}
#[inline]
pub fn from_packed(packed: PackedMat<T>) -> Self {
SymmetricMat { packed }
}
pub fn from_dense(mat: &MatRef<'_, T>, uplo: TriangularKind) -> Self
where
T: bytemuck::Zeroable,
{
assert!(mat.is_square(), "Matrix must be square");
SymmetricMat {
packed: PackedMat::from_dense(mat, uplo),
}
}
pub fn from_dense_checked(
mat: &MatRef<'_, T>,
uplo: TriangularKind,
tol: T::Real,
) -> Option<Self>
where
T: bytemuck::Zeroable,
{
assert!(mat.is_square(), "Matrix must be square");
let n = mat.nrows();
for j in 0..n {
for i in 0..j {
let diff = mat[(i, j)] - mat[(j, i)];
if diff.abs() > tol {
return None;
}
}
}
Some(Self::from_dense(mat, uplo))
}
#[inline]
pub fn dim(&self) -> usize {
self.packed.dim()
}
#[inline]
pub fn shape(&self) -> (usize, usize) {
let n = self.dim();
(n, n)
}
#[inline]
pub fn uplo(&self) -> TriangularKind {
self.packed.kind()
}
#[inline]
pub fn len(&self) -> usize {
self.packed.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.packed.is_empty()
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
}
}
#[inline]
pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get_mut(row, col)
} else {
self.packed.get_mut(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get_mut(row, col)
} else {
self.packed.get_mut(col, row)
}
}
}
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
let n = self.dim();
assert!(row < n && col < n, "Index out of bounds");
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed
.set(row, col, value)
.expect("row <= col is within the stored upper triangle");
} else {
self.packed
.set(col, row, value)
.expect("row > col swapped to (col, row) is within the upper triangle");
}
}
TriangularKind::Lower => {
if row >= col {
self.packed
.set(row, col, value)
.expect("row >= col is within the stored lower triangle");
} else {
self.packed
.set(col, row, value)
.expect("row < col swapped to (col, row) is within the lower triangle");
}
}
}
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.packed.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.packed.as_mut_ptr()
}
#[inline]
pub fn as_slice(&self) -> &[T] {
self.packed.as_slice()
}
#[inline]
pub fn as_slice_mut(&mut self) -> &mut [T] {
self.packed.as_slice_mut()
}
#[inline]
pub fn as_packed(&self) -> &PackedMat<T> {
&self.packed
}
#[inline]
pub fn as_packed_mut(&mut self) -> &mut PackedMat<T> {
&mut self.packed
}
pub fn to_dense(&self) -> Mat<T>
where
T: bytemuck::Zeroable,
{
let n = self.dim();
let mut mat = Mat::zeros(n, n);
for j in 0..n {
for i in 0..n {
if let Some(&val) = self.get(i, j) {
mat[(i, j)] = val;
}
}
}
mat
}
#[inline]
pub fn diagonal(&self) -> Vec<T> {
self.packed.diagonal()
}
#[inline]
pub fn set_diagonal(&mut self, diag: &[T]) {
self.packed.set_diagonal(diag);
}
#[inline]
pub fn fill(&mut self, value: T) {
self.packed.fill(value);
}
#[inline]
pub fn scale(&mut self, alpha: T) {
self.packed.scale(alpha);
}
pub fn add_diagonal(&mut self, alpha: T) {
let n = self.dim();
for i in 0..n {
if let Some(val) = self.get_mut(i, i) {
*val += alpha;
}
}
}
}
impl<T: Scalar> SymmetricMat<T> {
pub fn frobenius_norm_squared(&self) -> T::Real {
let n = self.dim();
let two = T::Real::one() + T::Real::one();
let mut sum = T::Real::zero();
for j in 0..n {
for i in 0..n {
let in_stored_triangle = match self.uplo() {
TriangularKind::Upper => i <= j,
TriangularKind::Lower => i >= j,
};
if !in_stored_triangle {
continue;
}
if let Some(&val) = self.get(i, j) {
if i == j {
sum += val.abs_sq();
} else {
sum += two * val.abs_sq();
}
}
}
}
sum
}
}
impl<T: Scalar + core::fmt::Debug> core::fmt::Debug for SymmetricMat<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let n = self.dim();
writeln!(f, "SymmetricMat {}×{} ({:?}) {{", n, n, self.uplo())?;
let max_dim = 8.min(n);
for i in 0..max_dim {
write!(f, " [")?;
for j in 0..max_dim {
if j > 0 {
write!(f, ", ")?;
}
if let Some(v) = self.get(i, j) {
write!(f, "{:8.4?}", v)?;
} else {
write!(f, " * ")?;
}
}
if n > max_dim {
write!(f, ", ...")?;
}
writeln!(f, "]")?;
}
if n > max_dim {
writeln!(f, " ...")?;
}
write!(f, "}}")
}
}
#[derive(Clone, Copy)]
pub struct SymmetricView<'a, T: Scalar> {
inner: MatRef<'a, T>,
uplo: TriangularKind,
}
impl<'a, T: Scalar> SymmetricView<'a, T> {
#[inline]
pub fn new(mat: MatRef<'a, T>, uplo: TriangularKind) -> Self {
assert!(mat.is_square(), "Symmetric matrix must be square");
SymmetricView { inner: mat, uplo }
}
#[inline]
pub fn dim(&self) -> usize {
self.inner.nrows()
}
#[inline]
pub fn uplo(&self) -> TriangularKind {
self.uplo
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
let n = self.dim();
if row >= n || col >= n {
return None;
}
match self.uplo {
TriangularKind::Upper => {
if row <= col {
self.inner.get(row, col)
} else {
self.inner.get(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.inner.get(row, col)
} else {
self.inner.get(col, row)
}
}
}
}
pub fn to_packed(&self) -> SymmetricMat<T>
where
T: bytemuck::Zeroable,
{
SymmetricMat::from_dense(&self.inner, self.uplo)
}
}
pub struct SymmetricViewMut<'a, T: Scalar> {
inner: MatMut<'a, T>,
uplo: TriangularKind,
}
impl<'a, T: Scalar> SymmetricViewMut<'a, T> {
#[inline]
pub fn new(mat: MatMut<'a, T>, uplo: TriangularKind) -> Self {
assert!(mat.is_square(), "Symmetric matrix must be square");
SymmetricViewMut { inner: mat, uplo }
}
#[inline]
pub fn dim(&self) -> usize {
self.inner.nrows()
}
#[inline]
pub fn uplo(&self) -> TriangularKind {
self.uplo
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
let n = self.dim();
if row >= n || col >= n {
return None;
}
match self.uplo {
TriangularKind::Upper => {
if row <= col {
self.inner.get(row, col)
} else {
self.inner.get(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.inner.get(row, col)
} else {
self.inner.get(col, row)
}
}
}
}
pub fn set(&mut self, row: usize, col: usize, value: T) {
let n = self.dim();
assert!(row < n && col < n, "Index out of bounds");
self.inner.set(row, col, value);
if row != col {
self.inner.set(col, row, value);
}
}
#[inline]
pub fn rb(&self) -> SymmetricView<'_, T> {
SymmetricView {
inner: self.inner.rb(),
uplo: self.uplo,
}
}
}
#[derive(Clone)]
pub struct HermitianMat<T: Scalar> {
packed: PackedMat<T>,
}
impl<T: Scalar> HermitianMat<T> {
pub fn zeros(n: usize, uplo: TriangularKind) -> Self
where
T: bytemuck::Zeroable,
{
HermitianMat {
packed: PackedMat::zeros(n, uplo),
}
}
#[inline]
pub fn from_packed(packed: PackedMat<T>) -> Self {
HermitianMat { packed }
}
#[inline]
pub fn dim(&self) -> usize {
self.packed.dim()
}
#[inline]
pub fn shape(&self) -> (usize, usize) {
let n = self.dim();
(n, n)
}
#[inline]
pub fn uplo(&self) -> TriangularKind {
self.packed.kind()
}
#[inline]
pub fn len(&self) -> usize {
self.packed.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.packed.is_empty()
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
}
}
#[inline]
pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get_mut(row, col)
} else {
self.packed.get_mut(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get_mut(row, col)
} else {
self.packed.get_mut(col, row)
}
}
}
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
let n = self.dim();
assert!(row < n && col < n, "Index out of bounds");
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed
.set(row, col, value)
.expect("row <= col is within the stored upper triangle");
} else {
self.packed
.set(col, row, value)
.expect("row > col swapped to (col, row) is within the upper triangle");
}
}
TriangularKind::Lower => {
if row >= col {
self.packed
.set(row, col, value)
.expect("row >= col is within the stored lower triangle");
} else {
self.packed
.set(col, row, value)
.expect("row < col swapped to (col, row) is within the lower triangle");
}
}
}
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.packed.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.packed.as_mut_ptr()
}
#[inline]
pub fn as_slice(&self) -> &[T] {
self.packed.as_slice()
}
#[inline]
pub fn as_slice_mut(&mut self) -> &mut [T] {
self.packed.as_slice_mut()
}
#[inline]
pub fn as_packed(&self) -> &PackedMat<T> {
&self.packed
}
#[inline]
pub fn as_packed_mut(&mut self) -> &mut PackedMat<T> {
&mut self.packed
}
#[inline]
pub fn diagonal(&self) -> Vec<T> {
self.packed.diagonal()
}
#[inline]
pub fn set_diagonal(&mut self, diag: &[T]) {
self.packed.set_diagonal(diag);
}
#[inline]
pub fn fill(&mut self, value: T) {
self.packed.fill(value);
}
#[inline]
pub fn scale(&mut self, alpha: T::Real) {
self.packed.scale(T::from_real(alpha));
}
}
impl HermitianMat<Complex<f32>> {
pub fn to_dense_f32(&self) -> Mat<Complex<f32>> {
let n = self.dim();
let mut mat = Mat::filled(n, n, Complex::new(0.0f32, 0.0f32));
for j in 0..n {
for i in 0..n {
let in_stored_triangle = match self.uplo() {
TriangularKind::Upper => i <= j,
TriangularKind::Lower => i >= j,
};
if !in_stored_triangle {
continue;
}
if let Some(&val) = self.get(i, j) {
if i == j {
mat[(i, j)] = val;
} else {
mat[(i, j)] = val;
mat[(j, i)] = val.conj();
}
}
}
}
mat
}
pub fn from_dense_checked_f32(
mat: &MatRef<'_, Complex<f32>>,
uplo: TriangularKind,
tol: f32,
) -> Option<Self> {
assert!(mat.is_square(), "Matrix must be square");
let n = mat.nrows();
for j in 0..n {
for i in 0..j {
let a_ij = mat[(i, j)];
let a_ji = mat[(j, i)];
let diff = a_ij - a_ji.conj();
if diff.norm() > tol {
return None;
}
}
let diag = mat[(j, j)];
if diag.im.abs() > tol {
return None;
}
}
let packed = PackedMat::from_dense(mat, uplo);
Some(HermitianMat { packed })
}
}
impl HermitianMat<Complex<f64>> {
pub fn to_dense(&self) -> Mat<Complex<f64>> {
let n = self.dim();
let mut mat = Mat::filled(n, n, Complex::new(0.0f64, 0.0f64));
for j in 0..n {
for i in 0..n {
let in_stored_triangle = match self.uplo() {
TriangularKind::Upper => i <= j,
TriangularKind::Lower => i >= j,
};
if !in_stored_triangle {
continue;
}
if let Some(&val) = self.get(i, j) {
if i == j {
mat[(i, j)] = val;
} else {
mat[(i, j)] = val;
mat[(j, i)] = val.conj();
}
}
}
}
mat
}
pub fn from_dense_checked(
mat: &MatRef<'_, Complex<f64>>,
uplo: TriangularKind,
tol: f64,
) -> Option<Self> {
assert!(mat.is_square(), "Matrix must be square");
let n = mat.nrows();
for j in 0..n {
for i in 0..j {
let a_ij = mat[(i, j)];
let a_ji = mat[(j, i)];
let diff = a_ij - a_ji.conj();
if diff.norm() > tol {
return None;
}
}
let diag = mat[(j, j)];
if diag.im.abs() > tol {
return None;
}
}
let packed = PackedMat::from_dense(mat, uplo);
Some(HermitianMat { packed })
}
}
impl<T: Scalar + core::fmt::Debug> core::fmt::Debug for HermitianMat<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let n = self.dim();
writeln!(f, "HermitianMat {}×{} ({:?}) {{", n, n, self.uplo())?;
let max_dim = 6.min(n);
for i in 0..max_dim {
write!(f, " [")?;
for j in 0..max_dim {
if j > 0 {
write!(f, ", ")?;
}
if let Some(v) = self.get(i, j) {
write!(f, "{:?}", v)?;
} else {
write!(f, "*")?;
}
}
if n > max_dim {
write!(f, ", ...")?;
}
writeln!(f, "]")?;
}
if n > max_dim {
writeln!(f, " ...")?;
}
write!(f, "}}")
}
}
#[derive(Clone, Copy)]
pub struct SymmetricRef<'a, T: Scalar> {
packed: PackedRef<'a, T>,
}
impl<'a, T: Scalar> SymmetricRef<'a, T> {
#[inline]
pub fn new(packed: PackedRef<'a, T>) -> Self {
SymmetricRef { packed }
}
#[inline]
pub fn from_slice(data: &'a [T], n: usize, uplo: TriangularKind) -> Self {
SymmetricRef {
packed: PackedRef::from_slice(data, n, uplo),
}
}
#[inline]
pub fn dim(&self) -> usize {
self.packed.dim()
}
#[inline]
pub fn uplo(&self) -> TriangularKind {
self.packed.kind()
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
}
}
#[inline]
pub fn as_packed(&self) -> PackedRef<'a, T> {
self.packed
}
}
pub struct SymmetricMut<'a, T: Scalar> {
packed: PackedMut<'a, T>,
}
impl<'a, T: Scalar> SymmetricMut<'a, T> {
#[inline]
pub fn new(packed: PackedMut<'a, T>) -> Self {
SymmetricMut { packed }
}
#[inline]
pub fn from_slice(data: &'a mut [T], n: usize, uplo: TriangularKind) -> Self {
SymmetricMut {
packed: PackedMut::from_slice(data, n, uplo),
}
}
#[inline]
pub fn dim(&self) -> usize {
self.packed.dim()
}
#[inline]
pub fn uplo(&self) -> TriangularKind {
self.packed.kind()
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get(row, col)
} else {
self.packed.get(col, row)
}
}
}
}
#[inline]
pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed.get_mut(row, col)
} else {
self.packed.get_mut(col, row)
}
}
TriangularKind::Lower => {
if row >= col {
self.packed.get_mut(row, col)
} else {
self.packed.get_mut(col, row)
}
}
}
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
let n = self.dim();
assert!(row < n && col < n, "Index out of bounds");
match self.uplo() {
TriangularKind::Upper => {
if row <= col {
self.packed
.set(row, col, value)
.expect("row <= col is within the stored upper triangle");
} else {
self.packed
.set(col, row, value)
.expect("row > col swapped to (col, row) is within the upper triangle");
}
}
TriangularKind::Lower => {
if row >= col {
self.packed
.set(row, col, value)
.expect("row >= col is within the stored lower triangle");
} else {
self.packed
.set(col, row, value)
.expect("row < col swapped to (col, row) is within the lower triangle");
}
}
}
}
#[inline]
pub fn rb(&self) -> SymmetricRef<'_, T> {
SymmetricRef {
packed: self.packed.rb(),
}
}
#[inline]
pub fn rb_mut(&mut self) -> SymmetricMut<'_, T> {
SymmetricMut {
packed: self.packed.rb_mut(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_complex::Complex64;
#[test]
fn test_symmetric_basic() {
let mut s: SymmetricMat<f64> = SymmetricMat::zeros(3, TriangularKind::Upper);
s.set(0, 0, 1.0);
s.set(0, 1, 2.0);
s.set(0, 2, 3.0);
s.set(1, 1, 4.0);
s.set(1, 2, 5.0);
s.set(2, 2, 6.0);
assert_eq!(s.get(0, 0), Some(&1.0));
assert_eq!(s.get(0, 1), Some(&2.0));
assert_eq!(s.get(1, 0), Some(&2.0)); assert_eq!(s.get(2, 0), Some(&3.0)); assert_eq!(s.get(1, 2), Some(&5.0));
assert_eq!(s.get(2, 1), Some(&5.0));
let diag = s.diagonal();
assert_eq!(diag, vec![1.0, 4.0, 6.0]);
}
#[test]
fn test_symmetric_lower() {
let mut s: SymmetricMat<f64> = SymmetricMat::zeros(3, TriangularKind::Lower);
s.set(0, 0, 1.0);
s.set(1, 0, 2.0);
s.set(1, 1, 3.0);
s.set(2, 0, 4.0);
s.set(2, 1, 5.0);
s.set(2, 2, 6.0);
assert_eq!(s.get(0, 0), Some(&1.0));
assert_eq!(s.get(1, 0), Some(&2.0));
assert_eq!(s.get(0, 1), Some(&2.0)); assert_eq!(s.get(0, 2), Some(&4.0)); }
#[test]
fn test_symmetric_to_dense() {
let mut s: SymmetricMat<f64> = SymmetricMat::zeros(3, TriangularKind::Upper);
s.set(0, 0, 1.0);
s.set(0, 1, 2.0);
s.set(0, 2, 3.0);
s.set(1, 1, 4.0);
s.set(1, 2, 5.0);
s.set(2, 2, 6.0);
let dense = s.to_dense();
assert_eq!(dense[(0, 0)], 1.0);
assert_eq!(dense[(0, 1)], 2.0);
assert_eq!(dense[(0, 2)], 3.0);
assert_eq!(dense[(1, 1)], 4.0);
assert_eq!(dense[(1, 2)], 5.0);
assert_eq!(dense[(2, 2)], 6.0);
assert_eq!(dense[(1, 0)], 2.0);
assert_eq!(dense[(2, 0)], 3.0);
assert_eq!(dense[(2, 1)], 5.0);
}
#[test]
fn test_symmetric_from_dense() {
let dense = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[2.0, 4.0, 5.0], &[3.0, 5.0, 6.0]]);
let s = SymmetricMat::from_dense(&dense.as_ref(), TriangularKind::Upper);
assert_eq!(s.get(0, 0), Some(&1.0));
assert_eq!(s.get(0, 1), Some(&2.0));
assert_eq!(s.get(1, 0), Some(&2.0));
assert_eq!(s.get(2, 2), Some(&6.0));
}
#[test]
fn test_symmetric_from_dense_checked() {
let symmetric = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[2.0, 4.0, 5.0], &[3.0, 5.0, 6.0]]);
let result =
SymmetricMat::from_dense_checked(&symmetric.as_ref(), TriangularKind::Upper, 1e-10);
assert!(result.is_some());
let non_symmetric = Mat::from_rows(&[
&[1.0, 2.0, 3.0],
&[4.0, 5.0, 6.0], &[3.0, 5.0, 6.0],
]);
let result =
SymmetricMat::from_dense_checked(&non_symmetric.as_ref(), TriangularKind::Upper, 1e-10);
assert!(result.is_none());
}
#[test]
fn test_symmetric_operations() {
let mut s: SymmetricMat<f64> = SymmetricMat::filled(3, TriangularKind::Upper, 1.0);
s.scale(2.0);
assert_eq!(s.get(0, 0), Some(&2.0));
assert_eq!(s.get(0, 1), Some(&2.0));
s.add_diagonal(1.0);
assert_eq!(s.get(0, 0), Some(&3.0));
assert_eq!(s.get(1, 1), Some(&3.0));
assert_eq!(s.get(0, 1), Some(&2.0)); }
#[test]
fn test_symmetric_view() {
let dense = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[2.0, 4.0, 5.0], &[3.0, 5.0, 6.0]]);
let view = SymmetricView::new(dense.as_ref(), TriangularKind::Upper);
assert_eq!(view.get(0, 1), Some(&2.0));
assert_eq!(view.get(1, 0), Some(&2.0)); }
#[test]
fn test_symmetric_view_mut() {
let mut dense: Mat<f64> = Mat::zeros(3, 3);
{
let mut view = SymmetricViewMut::new(dense.as_mut(), TriangularKind::Upper);
view.set(0, 1, 5.0);
}
assert_eq!(dense[(0, 1)], 5.0);
assert_eq!(dense[(1, 0)], 5.0);
}
#[test]
fn test_hermitian_basic() {
let mut h: HermitianMat<Complex64> = HermitianMat::zeros(2, TriangularKind::Upper);
h.set(0, 0, Complex64::new(1.0, 0.0));
h.set(1, 1, Complex64::new(2.0, 0.0));
h.set(0, 1, Complex64::new(3.0, 4.0));
assert_eq!(h.get(0, 0), Some(&Complex64::new(1.0, 0.0)));
assert_eq!(h.get(1, 1), Some(&Complex64::new(2.0, 0.0)));
assert_eq!(h.get(0, 1), Some(&Complex64::new(3.0, 4.0)));
assert_eq!(h.get(1, 0), Some(&Complex64::new(3.0, 4.0))); }
#[test]
fn test_hermitian_to_dense() {
let mut h: HermitianMat<Complex64> = HermitianMat::zeros(2, TriangularKind::Upper);
h.set(0, 0, Complex64::new(1.0, 0.0));
h.set(1, 1, Complex64::new(2.0, 0.0));
h.set(0, 1, Complex64::new(3.0, 4.0));
let dense = h.to_dense();
assert_eq!(dense[(0, 0)], Complex64::new(1.0, 0.0));
assert_eq!(dense[(1, 1)], Complex64::new(2.0, 0.0));
assert_eq!(dense[(0, 1)], Complex64::new(3.0, 4.0));
assert_eq!(dense[(1, 0)], Complex64::new(3.0, -4.0)); }
#[test]
fn test_hermitian_from_dense_checked() {
let hermitian = Mat::from_rows(&[
&[Complex64::new(1.0, 0.0), Complex64::new(3.0, 4.0)],
&[Complex64::new(3.0, -4.0), Complex64::new(2.0, 0.0)],
]);
let result =
HermitianMat::from_dense_checked(&hermitian.as_ref(), TriangularKind::Upper, 1e-10);
assert!(result.is_some());
let non_hermitian = Mat::from_rows(&[
&[Complex64::new(1.0, 0.5), Complex64::new(3.0, 4.0)],
&[Complex64::new(3.0, -4.0), Complex64::new(2.0, 0.0)],
]);
let result =
HermitianMat::from_dense_checked(&non_hermitian.as_ref(), TriangularKind::Upper, 1e-10);
assert!(result.is_none());
}
#[test]
fn test_symmetric_ref_mut() {
let mut data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut smut = SymmetricMut::from_slice(&mut data, 3, TriangularKind::Upper);
assert_eq!(smut.get(0, 0), Some(&1.0));
assert_eq!(smut.get(0, 1), Some(&2.0));
assert_eq!(smut.get(1, 0), Some(&2.0));
smut.set(0, 1, 10.0);
assert_eq!(smut.get(0, 1), Some(&10.0));
assert_eq!(smut.get(1, 0), Some(&10.0)); }
#[test]
fn test_hermitian_to_dense_lower_roundtrip() {
let original = Mat::from_rows(&[
&[
Complex64::new(1.0, 0.0),
Complex64::new(3.0, 4.0),
Complex64::new(5.0, -2.0),
],
&[
Complex64::new(3.0, -4.0),
Complex64::new(2.0, 0.0),
Complex64::new(6.0, 1.0),
],
&[
Complex64::new(5.0, 2.0),
Complex64::new(6.0, -1.0),
Complex64::new(7.0, 0.0),
],
]);
let herm =
HermitianMat::from_dense_checked(&original.as_ref(), TriangularKind::Lower, 1e-12)
.expect("matrix is Hermitian by construction");
let dense = herm.to_dense();
for i in 0..3 {
for j in 0..3 {
assert_eq!(
dense[(i, j)],
original[(i, j)],
"mismatch at ({i}, {j}): Lower-storage to_dense must reproduce A, not conj(A)"
);
}
}
assert_eq!(dense[(1, 0)], Complex64::new(3.0, -4.0));
assert_eq!(dense[(0, 1)], Complex64::new(3.0, 4.0));
}
#[test]
fn test_hermitian_to_dense_f32_lower_roundtrip() {
let original = Mat::from_rows(&[
&[Complex::new(1.0f32, 0.0), Complex::new(3.0, 4.0)],
&[Complex::new(3.0, -4.0), Complex::new(2.0, 0.0)],
]);
let herm =
HermitianMat::from_dense_checked_f32(&original.as_ref(), TriangularKind::Lower, 1e-6)
.expect("matrix is Hermitian by construction");
let dense = herm.to_dense_f32();
for i in 0..2 {
for j in 0..2 {
assert_eq!(dense[(i, j)], original[(i, j)], "mismatch at ({i}, {j})");
}
}
assert_eq!(dense[(1, 0)], Complex::new(3.0f32, -4.0));
assert_eq!(dense[(0, 1)], Complex::new(3.0f32, 4.0));
}
#[test]
fn test_hermitian_to_dense_upper_roundtrip() {
let original = Mat::from_rows(&[
&[Complex64::new(1.0, 0.0), Complex64::new(3.0, 4.0)],
&[Complex64::new(3.0, -4.0), Complex64::new(2.0, 0.0)],
]);
let herm =
HermitianMat::from_dense_checked(&original.as_ref(), TriangularKind::Upper, 1e-12)
.expect("matrix is Hermitian by construction");
let dense = herm.to_dense();
for i in 0..2 {
for j in 0..2 {
assert_eq!(dense[(i, j)], original[(i, j)], "mismatch at ({i}, {j})");
}
}
}
#[test]
fn test_symmetric_frobenius_norm_squared_generic() {
let mut s: SymmetricMat<f64> = SymmetricMat::zeros(3, TriangularKind::Upper);
s.set(0, 0, 1.0);
s.set(0, 1, 2.0);
s.set(0, 2, 3.0);
s.set(1, 1, 4.0);
s.set(1, 2, 5.0);
s.set(2, 2, 6.0);
assert_eq!(s.frobenius_norm_squared(), 129.0);
let mut c: SymmetricMat<Complex64> = SymmetricMat::zeros(2, TriangularKind::Upper);
c.set(0, 0, Complex64::new(1.0, 1.0));
c.set(0, 1, Complex64::new(2.0, 0.0));
c.set(1, 1, Complex64::new(3.0, -1.0));
let norm_sq: f64 = c.frobenius_norm_squared();
assert!((norm_sq - 20.0).abs() < 1e-12, "got {norm_sq}");
let mut c_lower: SymmetricMat<Complex64> = SymmetricMat::zeros(2, TriangularKind::Lower);
c_lower.set(0, 0, Complex64::new(1.0, 1.0));
c_lower.set(1, 0, Complex64::new(2.0, 0.0));
c_lower.set(1, 1, Complex64::new(3.0, -1.0));
let norm_sq_lower: f64 = c_lower.frobenius_norm_squared();
assert!((norm_sq_lower - 20.0).abs() < 1e-12, "got {norm_sq_lower}");
}
#[test]
fn test_hermitian_scale_real_preserves_hermitian() {
let mut h: HermitianMat<Complex64> = HermitianMat::zeros(2, TriangularKind::Upper);
h.set(0, 0, Complex64::new(1.0, 0.0));
h.set(1, 1, Complex64::new(2.0, 0.0));
h.set(0, 1, Complex64::new(3.0, 4.0));
h.scale(2.0);
let dense = h.to_dense();
assert_eq!(dense[(0, 0)], Complex64::new(2.0, 0.0));
assert_eq!(dense[(1, 1)], Complex64::new(4.0, 0.0));
assert_eq!(dense[(0, 1)], Complex64::new(6.0, 8.0));
assert_eq!(dense[(1, 0)], dense[(0, 1)].conj());
assert_eq!(dense[(1, 0)], Complex64::new(6.0, -8.0));
}
}