#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use oxiblas_core::memory::AlignedVec;
use oxiblas_core::scalar::Scalar;
#[inline]
fn checked_ldab(kl: usize, ku: usize) -> usize {
kl.checked_add(ku)
.and_then(|s| s.checked_add(1))
.unwrap_or_else(|| {
panic!(
"BandedMat: leading dimension overflow (kl + ku + 1 exceeds usize::MAX; \
kl={kl}, ku={ku})"
)
})
}
#[inline]
fn checked_band_len(ldab: usize, ncols: usize) -> usize {
ldab.checked_mul(ncols).unwrap_or_else(|| {
panic!(
"BandedMat: band storage size overflow ({ldab} * {ncols} exceeds \
usize::MAX); requested matrix dimensions are too large to allocate"
)
})
}
#[derive(Clone)]
pub struct BandedMat<T: Scalar> {
data: AlignedVec<T>,
nrows: usize,
ncols: usize,
kl: usize,
ku: usize,
ldab: usize,
}
impl<T: Scalar> BandedMat<T> {
pub fn zeros(nrows: usize, ncols: usize, kl: usize, ku: usize) -> Self
where
T: bytemuck::Zeroable,
{
let ldab = checked_ldab(kl, ku);
let total = checked_band_len(ldab, ncols);
BandedMat {
data: AlignedVec::zeros(total),
nrows,
ncols,
kl,
ku,
ldab,
}
}
pub fn filled(nrows: usize, ncols: usize, kl: usize, ku: usize, value: T) -> Self {
let ldab = checked_ldab(kl, ku);
let total = checked_band_len(ldab, ncols);
BandedMat {
data: AlignedVec::filled(total, value),
nrows,
ncols,
kl,
ku,
ldab,
}
}
pub fn from_slice(nrows: usize, ncols: usize, kl: usize, ku: usize, data: &[T]) -> Self {
let ldab = checked_ldab(kl, ku);
let expected = checked_band_len(ldab, ncols);
assert_eq!(
data.len(),
expected,
"Slice length must equal (kl + ku + 1) * ncols = {}",
expected
);
BandedMat {
data: AlignedVec::from_slice(data),
nrows,
ncols,
kl,
ku,
ldab,
}
}
#[inline]
pub fn nrows(&self) -> usize {
self.nrows
}
#[inline]
pub fn ncols(&self) -> usize {
self.ncols
}
#[inline]
pub fn shape(&self) -> (usize, usize) {
(self.nrows, self.ncols)
}
#[inline]
pub fn kl(&self) -> usize {
self.kl
}
#[inline]
pub fn ku(&self) -> usize {
self.ku
}
#[inline]
pub fn bandwidth(&self) -> usize {
self.ldab
}
#[inline]
pub fn ldab(&self) -> usize {
self.ldab
}
#[inline]
pub fn is_empty(&self) -> bool {
self.nrows == 0 || self.ncols == 0
}
#[inline]
pub fn is_square(&self) -> bool {
self.nrows == self.ncols
}
#[inline]
pub fn in_band(&self, row: usize, col: usize) -> bool {
if row >= self.nrows || col >= self.ncols {
return false;
}
let diff = row as isize - col as isize;
diff >= -(self.ku as isize) && diff <= self.kl as isize
}
#[inline]
pub fn band_index(&self, row: usize, col: usize) -> Option<usize> {
if !self.in_band(row, col) {
return None;
}
let band_row = self.ku + row - col;
Some(band_row + col * self.ldab)
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
self.band_index(row, col).map(|idx| &self.data[idx])
}
#[inline]
pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
self.band_index(row, col).map(|idx| &mut self.data[idx])
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
let idx = self
.band_index(row, col)
.expect("Element outside bandwidth");
self.data[idx] = value;
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.data.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.data.as_mut_ptr()
}
#[inline]
pub fn as_slice(&self) -> &[T] {
self.data.as_slice()
}
#[inline]
pub fn as_slice_mut(&mut self) -> &mut [T] {
self.data.as_mut_slice()
}
#[inline]
pub fn as_banded_ref(&self) -> BandedRef<'_, T> {
unsafe {
BandedRef::new(
self.data.as_ptr(),
self.nrows,
self.ncols,
self.kl,
self.ku,
self.ldab,
)
}
}
#[inline]
pub fn as_banded_mut(&mut self) -> BandedMut<'_, T> {
let (nrows, ncols, kl, ku, ldab) = (self.nrows, self.ncols, self.kl, self.ku, self.ldab);
unsafe { BandedMut::new(self.data.as_mut_ptr(), nrows, ncols, kl, ku, ldab) }
}
pub fn get_band(&self, band_idx: isize) -> Option<(Vec<T>, usize)> {
if band_idx < -(self.kl as isize) || band_idx > self.ku as isize {
return None;
}
let storage_row = (self.ku as isize - band_idx) as usize;
let start_col = if band_idx >= 0 { band_idx as usize } else { 0 };
let start_row = if band_idx >= 0 {
0
} else {
(-band_idx) as usize
};
let len = self
.nrows
.saturating_sub(start_row)
.min(self.ncols.saturating_sub(start_col));
if len == 0 {
return Some((Vec::new(), start_col));
}
let data = self.data.as_slice();
let elements: Vec<T> = (0..len)
.map(|t| data[storage_row + (start_col + t) * self.ldab])
.collect();
Some((elements, start_col))
}
pub fn diagonal(&self) -> Vec<T> {
let len = self.nrows.min(self.ncols);
(0..len).filter_map(|i| self.get(i, i).copied()).collect()
}
pub fn set_diagonal(&mut self, diag: &[T]) {
let len = self.nrows.min(self.ncols);
assert!(
diag.len() <= len,
"Diagonal length exceeds matrix dimension"
);
for (i, &val) in diag.iter().enumerate() {
if i < len {
self.set(i, i, val);
}
}
}
pub fn to_dense(&self) -> crate::Mat<T>
where
T: bytemuck::Zeroable,
{
let mut mat = crate::Mat::zeros(self.nrows, self.ncols);
for j in 0..self.ncols {
let start_row = j.saturating_sub(self.ku);
let end_row = (j + self.kl + 1).min(self.nrows);
for i in start_row..end_row {
if let Some(&val) = self.get(i, j) {
mat[(i, j)] = val;
}
}
}
mat
}
pub fn from_dense(mat: &crate::MatRef<'_, T>, kl: usize, ku: usize) -> Self
where
T: bytemuck::Zeroable,
{
let nrows = mat.nrows();
let ncols = mat.ncols();
let mut banded = Self::zeros(nrows, ncols, kl, ku);
for j in 0..ncols {
let start_row = j.saturating_sub(ku);
let end_row = (j + kl + 1).min(nrows);
for i in start_row..end_row {
banded.set(i, j, mat[(i, j)]);
}
}
banded
}
pub fn fill(&mut self, value: T) {
for j in 0..self.ncols {
let start_row = j.saturating_sub(self.ku);
let end_row = (j + self.kl + 1).min(self.nrows);
for i in start_row..end_row {
self.set(i, j, value);
}
}
}
pub fn scale(&mut self, alpha: T) {
for j in 0..self.ncols {
let start_row = j.saturating_sub(self.ku);
let end_row = (j + self.kl + 1).min(self.nrows);
for i in start_row..end_row {
if let Some(val) = self.get_mut(i, j) {
*val *= alpha;
}
}
}
}
pub fn to_symmetric_banded(&self, uplo: super::packed::TriangularKind) -> SymmetricBandedMat<T>
where
T: bytemuck::Zeroable,
{
assert!(
self.kl == self.ku,
"Matrix must have equal number of sub- and superdiagonals for symmetric storage"
);
assert!(
self.is_square(),
"Matrix must be square for symmetric storage"
);
SymmetricBandedMat::from_banded(self, uplo)
}
}
impl<T: Scalar + core::fmt::Debug> core::fmt::Debug for BandedMat<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(
f,
"BandedMat {}×{} (kl={}, ku={}) {{",
self.nrows, self.ncols, self.kl, self.ku
)?;
let max_rows = 8.min(self.nrows);
let max_cols = 8.min(self.ncols);
for i in 0..max_rows {
write!(f, " [")?;
for j in 0..max_cols {
if j > 0 {
write!(f, ", ")?;
}
match self.get(i, j) {
Some(v) => write!(f, "{:8.4?}", v)?,
None => write!(f, " 0 ")?,
}
}
if self.ncols > max_cols {
write!(f, ", ...")?;
}
writeln!(f, "]")?;
}
if self.nrows > max_rows {
writeln!(f, " ...")?;
}
write!(f, "}}")
}
}
#[derive(Clone)]
pub struct SymmetricBandedMat<T: Scalar> {
data: AlignedVec<T>,
n: usize,
k: usize,
uplo: super::packed::TriangularKind,
ldab: usize,
}
impl<T: Scalar> SymmetricBandedMat<T> {
pub fn zeros(n: usize, k: usize, uplo: super::packed::TriangularKind) -> Self
where
T: bytemuck::Zeroable,
{
let ldab = checked_ldab(k, 0);
let total = checked_band_len(ldab, n);
SymmetricBandedMat {
data: AlignedVec::zeros(total),
n,
k,
uplo,
ldab,
}
}
pub fn from_banded(banded: &BandedMat<T>, uplo: super::packed::TriangularKind) -> Self
where
T: bytemuck::Zeroable,
{
assert!(banded.is_square(), "Matrix must be square");
assert_eq!(
banded.kl(),
banded.ku(),
"Matrix must have equal sub/superdiagonals"
);
let n = banded.nrows();
let k = banded.kl();
let mut sb = Self::zeros(n, k, uplo);
match uplo {
super::packed::TriangularKind::Upper => {
for j in 0..n {
let start_i = j.saturating_sub(k);
for i in start_i..=j {
if let Some(&val) = banded.get(i, j) {
sb.set(i, j, val);
}
}
}
}
super::packed::TriangularKind::Lower => {
for j in 0..n {
let end_i = (j + k + 1).min(n);
for i in j..end_i {
if let Some(&val) = banded.get(i, j) {
sb.set(i, j, val);
}
}
}
}
}
sb
}
#[inline]
pub fn dim(&self) -> usize {
self.n
}
#[inline]
pub fn k(&self) -> usize {
self.k
}
#[inline]
pub fn uplo(&self) -> super::packed::TriangularKind {
self.uplo
}
#[inline]
pub fn ldab(&self) -> usize {
self.ldab
}
#[inline]
pub fn in_band(&self, row: usize, col: usize) -> bool {
if row >= self.n || col >= self.n {
return false;
}
let diff = (row as isize - col as isize).unsigned_abs();
diff <= self.k
}
pub fn band_index(&self, row: usize, col: usize) -> Option<usize> {
if !self.in_band(row, col) {
return None;
}
match self.uplo {
super::packed::TriangularKind::Upper => {
let (i, j) = if row <= col { (row, col) } else { (col, row) };
let band_row = self.k + i - j;
Some(band_row + j * self.ldab)
}
super::packed::TriangularKind::Lower => {
let (i, j) = if row >= col { (row, col) } else { (col, row) };
let band_row = i - j;
Some(band_row + j * self.ldab)
}
}
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
self.band_index(row, col).map(|idx| &self.data[idx])
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
let idx = self
.band_index(row, col)
.expect("Element outside bandwidth");
self.data[idx] = value;
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.data.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.data.as_mut_ptr()
}
pub fn to_dense(&self) -> crate::Mat<T>
where
T: bytemuck::Zeroable,
{
let mut mat = crate::Mat::zeros(self.n, self.n);
for j in 0..self.n {
let start_i = j.saturating_sub(self.k);
let end_i = (j + self.k + 1).min(self.n);
for i in start_i..end_i {
if let Some(&val) = self.get(i, j) {
mat[(i, j)] = val;
if i != j {
mat[(j, i)] = val; }
}
}
}
mat
}
pub fn diagonal(&self) -> Vec<T> {
(0..self.n)
.filter_map(|i| self.get(i, i).copied())
.collect()
}
}
impl<T: Scalar + core::fmt::Debug> core::fmt::Debug for SymmetricBandedMat<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(
f,
"SymmetricBandedMat {}×{} (k={}, {:?}) {{",
self.n, self.n, self.k, self.uplo
)?;
let max_dim = 8.min(self.n);
for i in 0..max_dim {
write!(f, " [")?;
for j in 0..max_dim {
if j > 0 {
write!(f, ", ")?;
}
match self.get(i, j) {
Some(v) => write!(f, "{:8.4?}", v)?,
None => write!(f, " 0 ")?,
}
}
if self.n > max_dim {
write!(f, ", ...")?;
}
writeln!(f, "]")?;
}
if self.n > max_dim {
writeln!(f, " ...")?;
}
write!(f, "}}")
}
}
#[derive(Clone, Copy)]
pub struct BandedRef<'a, T: Scalar> {
ptr: *const T,
nrows: usize,
ncols: usize,
kl: usize,
ku: usize,
ldab: usize,
_marker: core::marker::PhantomData<&'a T>,
}
impl<'a, T: Scalar> BandedRef<'a, T> {
#[inline]
pub unsafe fn new(
ptr: *const T,
nrows: usize,
ncols: usize,
kl: usize,
ku: usize,
ldab: usize,
) -> Self {
BandedRef {
ptr,
nrows,
ncols,
kl,
ku,
ldab,
_marker: core::marker::PhantomData,
}
}
#[inline]
pub fn from_slice(data: &'a [T], nrows: usize, ncols: usize, kl: usize, ku: usize) -> Self {
let ldab = checked_ldab(kl, ku);
let expected = checked_band_len(ldab, ncols);
assert_eq!(
data.len(),
expected,
"Slice length must equal (kl + ku + 1) * ncols = {expected}"
);
unsafe { BandedRef::new(data.as_ptr(), nrows, ncols, kl, ku, ldab) }
}
#[inline]
pub fn shape(&self) -> (usize, usize) {
(self.nrows, self.ncols)
}
#[inline]
pub fn kl(&self) -> usize {
self.kl
}
#[inline]
pub fn ku(&self) -> usize {
self.ku
}
#[inline]
pub fn ldab(&self) -> usize {
self.ldab
}
#[inline]
pub fn in_band(&self, row: usize, col: usize) -> bool {
if row >= self.nrows || col >= self.ncols {
return false;
}
let diff = row as isize - col as isize;
diff >= -(self.ku as isize) && diff <= self.kl as isize
}
#[inline]
pub fn band_index(&self, row: usize, col: usize) -> Option<usize> {
if !self.in_band(row, col) {
return None;
}
let band_row = self.ku + row - col;
Some(band_row + col * self.ldab)
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
self.band_index(row, col)
.map(|idx| unsafe { &*self.ptr.add(idx) })
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.ptr
}
}
unsafe impl<'a, T: Scalar + Send> Send for BandedRef<'a, T> {}
unsafe impl<'a, T: Scalar + Sync> Sync for BandedRef<'a, T> {}
pub struct BandedMut<'a, T: Scalar> {
ptr: *mut T,
nrows: usize,
ncols: usize,
kl: usize,
ku: usize,
ldab: usize,
_marker: core::marker::PhantomData<&'a mut T>,
}
impl<'a, T: Scalar> BandedMut<'a, T> {
#[inline]
pub unsafe fn new(
ptr: *mut T,
nrows: usize,
ncols: usize,
kl: usize,
ku: usize,
ldab: usize,
) -> Self {
BandedMut {
ptr,
nrows,
ncols,
kl,
ku,
ldab,
_marker: core::marker::PhantomData,
}
}
#[inline]
pub fn from_slice(data: &'a mut [T], nrows: usize, ncols: usize, kl: usize, ku: usize) -> Self {
let ldab = checked_ldab(kl, ku);
let expected = checked_band_len(ldab, ncols);
assert_eq!(
data.len(),
expected,
"Slice length must equal (kl + ku + 1) * ncols = {expected}"
);
unsafe { BandedMut::new(data.as_mut_ptr(), nrows, ncols, kl, ku, ldab) }
}
#[inline]
pub fn shape(&self) -> (usize, usize) {
(self.nrows, self.ncols)
}
#[inline]
pub fn kl(&self) -> usize {
self.kl
}
#[inline]
pub fn ku(&self) -> usize {
self.ku
}
#[inline]
pub fn in_band(&self, row: usize, col: usize) -> bool {
if row >= self.nrows || col >= self.ncols {
return false;
}
let diff = row as isize - col as isize;
diff >= -(self.ku as isize) && diff <= self.kl as isize
}
#[inline]
pub fn band_index(&self, row: usize, col: usize) -> Option<usize> {
if !self.in_band(row, col) {
return None;
}
let band_row = self.ku + row - col;
Some(band_row + col * self.ldab)
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
self.band_index(row, col)
.map(|idx| unsafe { &*self.ptr.add(idx) })
}
#[inline]
pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
self.band_index(row, col)
.map(|idx| unsafe { &mut *self.ptr.add(idx) })
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
let idx = self
.band_index(row, col)
.expect("Element outside bandwidth");
unsafe {
*self.ptr.add(idx) = value;
}
}
#[inline]
pub fn rb(&self) -> BandedRef<'_, T> {
unsafe {
BandedRef::new(
self.ptr, self.nrows, self.ncols, self.kl, self.ku, self.ldab,
)
}
}
#[inline]
pub fn rb_mut(&mut self) -> BandedMut<'_, T> {
unsafe {
BandedMut::new(
self.ptr, self.nrows, self.ncols, self.kl, self.ku, self.ldab,
)
}
}
}
unsafe impl<'a, T: Scalar + Send> Send for BandedMut<'a, T> {}
unsafe impl<'a, T: Scalar + Sync> Sync for BandedMut<'a, T> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_banded_basic() {
let mut bm: BandedMat<f64> = BandedMat::zeros(4, 4, 1, 1);
for i in 0..4 {
bm.set(i, i, 2.0);
if i > 0 {
bm.set(i, i - 1, -1.0);
}
if i < 3 {
bm.set(i, i + 1, -1.0);
}
}
assert_eq!(bm.get(0, 0), Some(&2.0));
assert_eq!(bm.get(0, 1), Some(&-1.0));
assert_eq!(bm.get(1, 0), Some(&-1.0));
assert_eq!(bm.get(1, 1), Some(&2.0));
assert_eq!(bm.get(0, 2), None); assert_eq!(bm.get(2, 0), None);
let diag = bm.diagonal();
assert_eq!(diag, vec![2.0, 2.0, 2.0, 2.0]);
}
#[test]
fn test_banded_in_band() {
let bm: BandedMat<f64> = BandedMat::zeros(5, 5, 2, 1);
assert!(bm.in_band(0, 0)); assert!(bm.in_band(0, 1)); assert!(!bm.in_band(0, 2));
assert!(bm.in_band(1, 0)); assert!(bm.in_band(2, 0)); assert!(!bm.in_band(3, 0));
assert!(!bm.in_band(5, 0)); assert!(!bm.in_band(0, 5));
}
#[test]
fn test_banded_to_dense() {
let mut bm: BandedMat<f64> = BandedMat::zeros(3, 3, 1, 1);
bm.set(0, 0, 1.0);
bm.set(0, 1, 2.0);
bm.set(1, 0, 3.0);
bm.set(1, 1, 4.0);
bm.set(1, 2, 5.0);
bm.set(2, 1, 6.0);
bm.set(2, 2, 7.0);
let dense = bm.to_dense();
assert_eq!(dense[(0, 0)], 1.0);
assert_eq!(dense[(0, 1)], 2.0);
assert_eq!(dense[(0, 2)], 0.0);
assert_eq!(dense[(1, 0)], 3.0);
assert_eq!(dense[(1, 1)], 4.0);
assert_eq!(dense[(1, 2)], 5.0);
assert_eq!(dense[(2, 0)], 0.0);
assert_eq!(dense[(2, 1)], 6.0);
assert_eq!(dense[(2, 2)], 7.0);
}
#[test]
fn test_banded_from_dense() {
use crate::Mat;
let dense = Mat::from_rows(&[
&[1.0, 2.0, 0.0, 0.0],
&[3.0, 4.0, 5.0, 0.0],
&[0.0, 6.0, 7.0, 8.0],
&[0.0, 0.0, 9.0, 10.0],
]);
let bm = BandedMat::from_dense(&dense.as_ref(), 1, 1);
assert_eq!(bm.get(0, 0), Some(&1.0));
assert_eq!(bm.get(0, 1), Some(&2.0));
assert_eq!(bm.get(1, 0), Some(&3.0));
assert_eq!(bm.get(1, 1), Some(&4.0));
assert_eq!(bm.get(1, 2), Some(&5.0));
assert_eq!(bm.get(2, 1), Some(&6.0));
assert_eq!(bm.get(2, 2), Some(&7.0));
assert_eq!(bm.get(2, 3), Some(&8.0));
assert_eq!(bm.get(3, 2), Some(&9.0));
assert_eq!(bm.get(3, 3), Some(&10.0));
}
#[test]
fn test_banded_pentadiagonal() {
let mut bm: BandedMat<f64> = BandedMat::zeros(6, 6, 2, 2);
for i in 0..6 {
bm.set(i, i, 4.0); }
for i in 1..6 {
bm.set(i, i - 1, -1.0); bm.set(i - 1, i, -1.0); }
for i in 2..6 {
bm.set(i, i - 2, -0.5); bm.set(i - 2, i, -0.5); }
assert_eq!(bm.bandwidth(), 5);
assert_eq!(bm.get(0, 0), Some(&4.0));
assert_eq!(bm.get(2, 0), Some(&-0.5));
assert_eq!(bm.get(0, 2), Some(&-0.5));
assert_eq!(bm.get(3, 0), None); }
#[test]
fn test_get_band_matches_hand_computed_diagonals() {
let mut bm: BandedMat<f64> = BandedMat::zeros(5, 5, 1, 1);
let entries = [
(0, 0, 1.0),
(0, 1, 2.0),
(1, 0, 3.0),
(1, 1, 4.0),
(1, 2, 5.0),
(2, 1, 6.0),
(2, 2, 7.0),
(2, 3, 8.0),
(3, 2, 9.0),
(3, 3, 10.0),
(3, 4, 11.0),
(4, 3, 12.0),
(4, 4, 13.0),
];
for &(i, j, v) in &entries {
bm.set(i, j, v);
}
let (main, start) = bm.get_band(0).expect("main diagonal must exist");
assert_eq!(main, vec![1.0, 4.0, 7.0, 10.0, 13.0]);
assert_eq!(start, 0);
let (super_diag, start) = bm.get_band(1).expect("superdiagonal must exist");
assert_eq!(super_diag, vec![2.0, 5.0, 8.0, 11.0]);
assert_eq!(start, 1);
let (sub_diag, start) = bm.get_band(-1).expect("subdiagonal must exist");
assert_eq!(sub_diag, vec![3.0, 6.0, 9.0, 12.0]);
assert_eq!(start, 0);
assert!(bm.get_band(2).is_none());
assert!(bm.get_band(-2).is_none());
}
#[test]
fn test_get_band_degenerate_bandwidth_does_not_underflow() {
let bm: BandedMat<f64> = BandedMat::zeros(2, 2, 3, 0);
let (elements, start_col) = bm
.get_band(-3)
.expect("band_idx = -kl must be a representable band index");
assert!(elements.is_empty());
assert_eq!(start_col, 0);
let (main, start) = bm.get_band(0).expect("main diagonal must exist");
assert_eq!(main.len(), 2);
assert_eq!(start, 0);
}
#[test]
fn test_from_banded_honors_lower_uplo() {
use crate::packed::TriangularKind;
let mut bm: BandedMat<f64> = BandedMat::zeros(4, 4, 1, 1);
for i in 0..4 {
bm.set(i, i, 10.0 * (i as f64 + 1.0)); }
for i in 0..3 {
bm.set(i, i + 1, 100.0 * (i as f64 + 1.0)); bm.set(i + 1, i, i as f64 + 1.0); }
let sb = bm.to_symmetric_banded(TriangularKind::Lower);
assert_eq!(sb.uplo(), TriangularKind::Lower);
let mut expected: SymmetricBandedMat<f64> =
SymmetricBandedMat::zeros(4, 1, TriangularKind::Lower);
for i in 0..4 {
expected.set(i, i, 10.0 * (i as f64 + 1.0));
}
for i in 0..3 {
expected.set(i + 1, i, i as f64 + 1.0);
}
for i in 0..4 {
for j in 0..4 {
assert_eq!(sb.get(i, j), expected.get(i, j), "mismatch at ({i}, {j})");
}
}
assert_eq!(sb.get(1, 0), Some(&1.0));
assert_eq!(sb.get(0, 1), Some(&1.0)); assert_eq!(sb.get(2, 1), Some(&2.0));
assert_eq!(sb.get(3, 2), Some(&3.0));
}
#[test]
fn test_to_symmetric_banded_upper_still_works() {
use crate::packed::TriangularKind;
let mut bm: BandedMat<f64> = BandedMat::zeros(3, 3, 1, 1);
bm.set(0, 0, 1.0);
bm.set(1, 1, 2.0);
bm.set(2, 2, 3.0);
bm.set(0, 1, -1.0);
bm.set(1, 2, -2.0);
let sb = bm.to_symmetric_banded(TriangularKind::Upper);
assert_eq!(sb.uplo(), TriangularKind::Upper);
assert_eq!(sb.get(0, 1), Some(&-1.0));
assert_eq!(sb.get(1, 2), Some(&-2.0));
}
#[test]
fn test_symmetric_banded() {
use crate::packed::TriangularKind;
let mut sb: SymmetricBandedMat<f64> =
SymmetricBandedMat::zeros(4, 1, TriangularKind::Upper);
for i in 0..4 {
sb.set(i, i, 2.0);
}
for i in 0..3 {
sb.set(i, i + 1, -1.0); }
assert_eq!(sb.get(0, 1), Some(&-1.0));
assert_eq!(sb.get(1, 0), Some(&-1.0));
let dense = sb.to_dense();
assert_eq!(dense[(0, 1)], -1.0);
assert_eq!(dense[(1, 0)], -1.0);
}
#[test]
fn test_banded_scale() {
let mut bm: BandedMat<f64> = BandedMat::zeros(3, 3, 1, 1);
bm.set(0, 0, 1.0);
bm.set(0, 1, 2.0);
bm.set(1, 0, 3.0);
bm.set(1, 1, 4.0);
bm.set(1, 2, 5.0);
bm.set(2, 1, 6.0);
bm.set(2, 2, 7.0);
bm.scale(2.0);
assert_eq!(bm.get(0, 0), Some(&2.0));
assert_eq!(bm.get(1, 1), Some(&8.0));
assert_eq!(bm.get(2, 2), Some(&14.0));
}
#[test]
fn test_banded_ref() {
let mut bm: BandedMat<f64> = BandedMat::zeros(3, 3, 1, 1);
bm.set(0, 0, 1.0);
bm.set(1, 1, 2.0);
bm.set(2, 2, 3.0);
let bref = unsafe {
BandedRef::new(
bm.as_ptr(),
bm.nrows(),
bm.ncols(),
bm.kl(),
bm.ku(),
bm.ldab(),
)
};
assert_eq!(bref.get(0, 0), Some(&1.0));
assert_eq!(bref.get(1, 1), Some(&2.0));
assert_eq!(bref.get(2, 2), Some(&3.0));
}
#[test]
fn test_banded_mut() {
let mut bm: BandedMat<f64> = BandedMat::zeros(3, 3, 1, 1);
{
let (nrows, ncols, kl, ku, ldab) =
(bm.nrows(), bm.ncols(), bm.kl(), bm.ku(), bm.ldab());
let mut bmut = unsafe { BandedMut::new(bm.as_mut_ptr(), nrows, ncols, kl, ku, ldab) };
bmut.set(0, 0, 10.0);
bmut.set(1, 1, 20.0);
}
assert_eq!(bm.get(0, 0), Some(&10.0));
assert_eq!(bm.get(1, 1), Some(&20.0));
}
#[test]
fn test_banded_nonsquare() {
let mut bm: BandedMat<f64> = BandedMat::zeros(4, 6, 1, 2);
assert_eq!(bm.shape(), (4, 6));
assert!(!bm.is_square());
bm.set(0, 0, 1.0);
bm.set(0, 1, 2.0);
bm.set(0, 2, 3.0);
bm.set(1, 0, 4.0);
bm.set(1, 1, 5.0);
bm.set(1, 2, 6.0);
bm.set(1, 3, 7.0);
let dense = bm.to_dense();
assert_eq!(dense.shape(), (4, 6));
assert_eq!(dense[(0, 0)], 1.0);
assert_eq!(dense[(0, 2)], 3.0);
assert_eq!(dense[(1, 3)], 7.0);
}
#[test]
#[should_panic(expected = "band storage size overflow")]
fn test_banded_zeros_size_overflow_panics_not_wraps() {
let _: BandedMat<f64> = BandedMat::zeros(4, usize::MAX, 1, 1);
}
#[test]
#[should_panic(expected = "leading dimension overflow")]
fn test_banded_ldab_overflow_panics_not_wraps() {
let _: BandedMat<f64> = BandedMat::zeros(4, 4, usize::MAX, 1);
}
#[test]
#[should_panic(expected = "band storage size overflow")]
fn test_symmetric_banded_zeros_size_overflow_panics_not_wraps() {
let _: SymmetricBandedMat<f64> =
SymmetricBandedMat::zeros(usize::MAX, 3, super::super::packed::TriangularKind::Upper);
}
#[test]
#[should_panic(expected = "Slice length must equal")]
fn test_banded_ref_from_slice_rejects_short_slice() {
let data = [0.0f64; 4];
let _ = BandedRef::from_slice(&data, 100, 100, 1, 1);
}
#[test]
#[should_panic(expected = "Slice length must equal")]
fn test_banded_mut_from_slice_rejects_short_slice() {
let mut data = [0.0f64; 4];
let _ = BandedMut::from_slice(&mut data, 100, 100, 1, 1);
}
#[test]
fn test_banded_views_from_owned_matrix_round_trip() {
let mut bm: BandedMat<f64> = BandedMat::zeros(4, 4, 1, 1);
bm.set(1, 1, 5.0);
let bref = bm.as_banded_ref();
assert_eq!(bref.get(1, 1), Some(&5.0));
assert_eq!(bref.get(0, 3), None);
let mut bmut = bm.as_banded_mut();
bmut.set(2, 2, 9.0);
assert_eq!(bmut.get(2, 2), Some(&9.0));
assert_eq!(bm.get(2, 2), Some(&9.0));
}
#[test]
fn test_banded_from_slice_accepts_exact_length() {
let data = [1.0f64; 12];
let bref = BandedRef::from_slice(&data, 4, 4, 1, 1);
assert_eq!(bref.shape(), (4, 4));
assert_eq!(bref.ldab(), 3);
assert_eq!(bref.get(0, 0), Some(&1.0));
}
}