pub fn daxpy(alpha: f64, x: &[f64], y: &mut [f64]) {
assert_eq!(x.len(), y.len(), "daxpy: vectors must have the same length");
for i in 0..y.len() {
y[i] += alpha * x[i];
}
}
pub fn dscal(alpha: f64, x: &mut [f64]) {
for v in x.iter_mut() {
*v *= alpha;
}
}
pub fn dcopy(x: &[f64], y: &mut [f64]) {
assert_eq!(x.len(), y.len(), "dcopy: vectors must have the same length");
y.copy_from_slice(x);
}
pub fn ddot(x: &[f64], y: &[f64]) -> f64 {
x.iter().zip(y.iter()).map(|(a, b)| a * b).sum()
}
pub fn dnrm2(x: &[f64]) -> f64 {
let mut scale = 0.0f64;
for &v in x {
let a = v.abs();
if a > scale {
scale = a;
}
}
if scale == 0.0 || !scale.is_finite() {
return x.iter().map(|v| v * v).sum::<f64>().sqrt();
}
let inv = 1.0 / scale;
let ss: f64 = x.iter().map(|v| (v * inv) * (v * inv)).sum();
scale * ss.sqrt()
}
pub fn idamax(x: &[f64]) -> usize {
let mut best = 0usize;
let mut best_abs = f64::NEG_INFINITY;
for (i, &v) in x.iter().enumerate() {
let a = v.abs();
if a > best_abs {
best_abs = a;
best = i;
}
}
best
}
pub fn dasum(x: &[f64]) -> f64 {
x.iter().map(|v| v.abs()).sum()
}
pub fn dgemv(alpha: f64, a: &[f64], x: &[f64], beta: f64, y: &mut [f64], m: usize, n: usize) {
assert_eq!(a.len(), m * n, "dgemv: a must be m*n");
assert_eq!(x.len(), n, "dgemv: x must have n entries");
assert_eq!(y.len(), m, "dgemv: y must have m entries");
if beta == 0.0 {
for v in y.iter_mut() {
*v = 0.0;
}
} else if beta != 1.0 {
for v in y.iter_mut() {
*v *= beta;
}
}
for j in 0..n {
let xj = x[j];
if xj == 0.0 {
continue;
}
let c = alpha * xj;
let col = &a[j * m..(j + 1) * m];
for i in 0..m {
y[i] += c * col[i];
}
}
}
pub fn dgemv_trans(alpha: f64, a: &[f64], x: &[f64], beta: f64, y: &mut [f64], m: usize, n: usize) {
assert_eq!(a.len(), m * n, "dgemv_trans: a must be m*n");
assert_eq!(x.len(), m, "dgemv_trans: x must have m entries");
assert_eq!(y.len(), n, "dgemv_trans: y must have n entries");
for j in 0..n {
let col = &a[j * m..(j + 1) * m];
let mut sum = 0.0;
for i in 0..m {
sum += col[i] * x[i];
}
y[j] = if beta == 0.0 { alpha * sum } else { alpha * sum + beta * y[j] };
}
}
pub fn dger(alpha: f64, x: &[f64], y: &[f64], a: &mut [f64], m: usize, n: usize) {
assert_eq!(a.len(), m * n, "dger: a must be m*n");
assert_eq!(x.len(), m, "dger: x must have m entries");
assert_eq!(y.len(), n, "dger: y must have n entries");
for j in 0..n {
let c = alpha * y[j];
if c == 0.0 {
continue;
}
let col = &mut a[j * m..(j + 1) * m];
for i in 0..m {
col[i] += c * x[i];
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Trans {
No,
Yes,
}
pub fn dgemm(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), m * k, "dgemm: a must be m*k");
assert_eq!(b.len(), k * n, "dgemm: b must be k*n");
assert_eq!(c.len(), m * n, "dgemm: c must be m*n");
gemm_core(alpha, a, b, beta, c, m, n, k, Trans::No, Trans::No);
}
pub fn dgemm_tn(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), k * m, "dgemm_tn: a must be k*m");
assert_eq!(b.len(), k * n, "dgemm_tn: b must be k*n");
assert_eq!(c.len(), m * n, "dgemm_tn: c must be m*n");
gemm_core(alpha, a, b, beta, c, m, n, k, Trans::Yes, Trans::No);
}
pub fn dgemm_nt(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), m * k, "dgemm_nt: a must be m*k");
assert_eq!(b.len(), n * k, "dgemm_nt: b must be n*k");
assert_eq!(c.len(), m * n, "dgemm_nt: c must be m*n");
gemm_core(alpha, a, b, beta, c, m, n, k, Trans::No, Trans::Yes);
}
pub fn dgemm_tt(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), k * m, "dgemm_tt: a must be k*m");
assert_eq!(b.len(), n * k, "dgemm_tt: b must be n*k");
assert_eq!(c.len(), m * n, "dgemm_tt: c must be m*n");
gemm_core(alpha, a, b, beta, c, m, n, k, Trans::Yes, Trans::Yes);
}
#[allow(clippy::too_many_arguments)]
fn gemm_core(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize, trans_a: Trans, trans_b: Trans) {
assert!(a.len() >= m * k, "gemm: a is too short");
assert!(b.len() >= k * n, "gemm: b is too short");
assert!(c.len() >= m * n, "gemm: c is too short");
if m == 0 || n == 0 {
return;
}
match beta {
0.0 => c.fill(0.0),
1.0 => {}
_ => {
for x in c.iter_mut() {
*x *= beta;
}
}
}
if k == 0 || alpha == 0.0 {
return;
}
let ada = if trans_a == Trans::No { m } else { k };
let adb = if trans_b == Trans::No { k } else { n };
const MC: usize = 128;
const KC: usize = 256;
const NC: usize = 256;
let mut ap = vec![0.0f64; MC * KC];
let mut bp = vec![0.0f64; KC * NC];
#[cfg(all(target_arch = "x86_64", not(target_arch = "wasm32")))]
let use_avx = has_avx2_fma();
#[cfg(not(all(target_arch = "x86_64", not(target_arch = "wasm32"))))]
let use_avx = false;
for jc in (0..n).step_by(NC) {
let nc = NC.min(n - jc);
for pc in (0..k).step_by(KC) {
let kc = KC.min(k - pc);
match trans_b {
Trans::No => {
for j in 0..nc {
let b_col = &b[pc + (jc + j) * adb..pc + kc + (jc + j) * adb];
let dst = &mut bp[j * kc..j * kc + kc];
dst.copy_from_slice(b_col);
}
}
Trans::Yes => {
for p in 0..kc {
for j in 0..nc {
bp[j * kc + p] = b[jc + j + (pc + p) * adb];
}
}
}
}
for ic in (0..m).step_by(MC) {
let mc = MC.min(m - ic);
match trans_a {
Trans::No => {
for p in 0..kc {
let a_col = &a[ic + (pc + p) * ada..ic + mc + (pc + p) * ada];
let dst = &mut ap[p * MC..p * MC + mc];
if alpha == 1.0 {
dst.copy_from_slice(a_col);
} else {
for (d, s) in dst.iter_mut().zip(a_col) {
*d = alpha * s;
}
}
}
}
Trans::Yes => {
for i in 0..mc {
for p in 0..kc {
let v = a[pc + p + (ic + i) * ada];
ap[p * MC + i] = if alpha == 1.0 { v } else { alpha * v };
}
}
}
}
gemm_panel(&ap, &bp, &mut c[ic + jc * m..], m, mc, nc, kc, MC, kc, use_avx);
}
}
}
}
#[cfg(all(target_arch = "x86_64", not(target_arch = "wasm32")))]
fn has_avx2_fma() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma"))
}
#[allow(clippy::too_many_arguments)]
fn gemm_panel(ap: &[f64], bp: &[f64], c: &mut [f64], ldc: usize, mc: usize, nc: usize, kc: usize, lda: usize, ldb: usize, use_avx: bool) {
let mut j = 0;
while j < nc {
let mut i = 0;
if j + 4 <= nc && use_avx {
#[cfg(all(target_arch = "x86_64", not(target_arch = "wasm32")))]
{
while i + 4 <= mc {
unsafe {
gemm_micro_4x4(
ap.as_ptr().add(i),
lda,
bp.as_ptr().add(j * ldb),
ldb,
c.as_mut_ptr().add(i + j * ldc),
ldc,
kc,
);
}
i += 4;
}
}
while i < mc {
for jj in j..j + 4 {
let mut sum = 0.0;
for p in 0..kc {
sum += ap[i + p * lda] * bp[p + jj * ldb];
}
c[i + jj * ldc] += sum;
}
i += 1;
}
j += 4;
} else {
for jj in j..nc {
for ii in 0..mc {
let mut sum = 0.0;
for p in 0..kc {
sum += ap[ii + p * lda] * bp[p + jj * ldb];
}
c[ii + jj * ldc] += sum;
}
}
j = nc;
}
}
}
#[cfg(all(target_arch = "x86_64", not(target_arch = "wasm32")))]
#[target_feature(enable = "avx2,fma")]
unsafe fn gemm_micro_4x4(ap: *const f64, lda: usize, bp: *const f64, ldb: usize, c: *mut f64, ldc: usize, kc: usize) {
use core::arch::x86_64::{_mm256_add_pd, _mm256_broadcast_sd, _mm256_fmadd_pd, _mm256_loadu_pd, _mm256_setzero_pd, _mm256_storeu_pd};
unsafe {
let mut c0 = _mm256_setzero_pd();
let mut c1 = _mm256_setzero_pd();
let mut c2 = _mm256_setzero_pd();
let mut c3 = _mm256_setzero_pd();
let b0 = bp;
let b1 = bp.add(ldb);
let b2 = bp.add(2 * ldb);
let b3 = bp.add(3 * ldb);
for p in 0..kc {
let av = _mm256_loadu_pd(ap.add(p * lda));
c0 = _mm256_fmadd_pd(av, _mm256_broadcast_sd(&*b0.add(p)), c0);
c1 = _mm256_fmadd_pd(av, _mm256_broadcast_sd(&*b1.add(p)), c1);
c2 = _mm256_fmadd_pd(av, _mm256_broadcast_sd(&*b2.add(p)), c2);
c3 = _mm256_fmadd_pd(av, _mm256_broadcast_sd(&*b3.add(p)), c3);
}
let mut t = [0.0f64; 4];
_mm256_storeu_pd(t.as_mut_ptr(), _mm256_add_pd(_mm256_loadu_pd(c), c0));
core::ptr::copy_nonoverlapping(t.as_ptr(), c, 4);
_mm256_storeu_pd(t.as_mut_ptr(), _mm256_add_pd(_mm256_loadu_pd(c.add(ldc)), c1));
core::ptr::copy_nonoverlapping(t.as_ptr(), c.add(ldc), 4);
_mm256_storeu_pd(t.as_mut_ptr(), _mm256_add_pd(_mm256_loadu_pd(c.add(2 * ldc)), c2));
core::ptr::copy_nonoverlapping(t.as_ptr(), c.add(2 * ldc), 4);
_mm256_storeu_pd(t.as_mut_ptr(), _mm256_add_pd(_mm256_loadu_pd(c.add(3 * ldc)), c3));
core::ptr::copy_nonoverlapping(t.as_ptr(), c.add(3 * ldc), 4);
}
}
pub fn dsyrk(alpha: f64, a: &[f64], beta: f64, c: &mut [f64], n: usize, k: usize, upper: bool) {
assert_eq!(a.len(), n * k, "dsyrk: a must be n*k");
assert_eq!(c.len(), n * n, "dsyrk: c must be n*n");
for j in 0..n {
let i_range = if upper { 0..j + 1 } else { j..n };
for i in i_range {
let mut sum = 0.0;
for l in 0..k {
sum += a[i + l * n] * a[j + l * n];
}
let idx = i + j * n;
c[idx] = if beta == 0.0 { alpha * sum } else { alpha * sum + beta * c[idx] };
}
}
}
pub fn dlange(norm: char, a: &[f64], m: usize, n: usize) -> f64 {
assert_eq!(a.len(), m * n, "dlange: a must be m*n");
match norm {
'M' | 'm' => a.iter().fold(0.0f64, |acc, v| acc.max(v.abs())),
'1' | 'O' | 'o' => {
(0..n)
.map(|j| a[j * m..(j + 1) * m].iter().map(|v| v.abs()).sum::<f64>())
.fold(0.0f64, f64::max)
}
'I' | 'i' => {
(0..m).map(|i| (0..n).map(|j| a[i + j * m].abs()).sum::<f64>()).fold(0.0f64, f64::max)
}
'F' | 'f' | 'E' | 'e' => dnrm2(a),
other => panic!("dlange: unrecognized norm '{other}'; expected one of 1, O, I, F, E, M"),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn a23() -> Vec<f64> {
vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]
}
#[test]
fn level1_operations() {
let x = vec![1.0, 2.0, 3.0];
let mut y = vec![10.0, 20.0, 30.0];
daxpy(2.0, &x, &mut y);
assert_eq!(y, vec![12.0, 24.0, 36.0]);
dscal(0.5, &mut y);
assert_eq!(y, vec![6.0, 12.0, 18.0]);
let mut z = vec![0.0; 3];
dcopy(&y, &mut z);
assert_eq!(z, y);
assert_eq!(ddot(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]), 32.0);
assert_eq!(ddot(&[1.0, 2.0, 3.0], &[4.0, 5.0]), 14.0);
assert_eq!(dnrm2(&[3.0, 4.0]), 5.0);
assert_eq!(dnrm2(&[]), 0.0);
assert_eq!(dasum(&[-1.0, 2.0, -3.0]), 6.0);
}
#[test]
fn dnrm2_does_not_overflow_or_underflow() {
let big = vec![1e200, 1e200];
let n = dnrm2(&big);
assert!(n.is_finite(), "dnrm2 overflowed: {n}");
assert!((n / (1e200 * 2.0f64.sqrt()) - 1.0).abs() < 1e-12);
let tiny = vec![1e-200, 1e-200];
let n = dnrm2(&tiny);
assert!(n > 0.0, "dnrm2 underflowed to zero");
}
#[test]
fn dgemv_matches_hand_computation() {
let a = a23();
let x = vec![1.0, 1.0, 1.0];
let mut y = vec![0.0; 2];
dgemv(1.0, &a, &x, 0.0, &mut y, 2, 3);
assert_eq!(y, vec![6.0, 15.0]);
let mut y2 = vec![1.0, 1.0];
dgemv(2.0, &a, &x, 3.0, &mut y2, 2, 3);
assert_eq!(y2, vec![2.0 * 6.0 + 3.0, 2.0 * 15.0 + 3.0]);
}
fn naive_dgemm(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize) {
for j in 0..n {
for i in 0..m {
let mut sum = 0.0;
for l in 0..k {
sum += a[i + l * m] * b[l + j * k];
}
let idx = i + j * m;
c[idx] = if beta == 0.0 { alpha * sum } else { alpha * sum + beta * c[idx] };
}
}
}
#[test]
fn dgemm_blocked_matches_naive_on_every_branch() {
let shapes = [
(1, 1, 1),
(1, 5, 1),
(5, 1, 1),
(1, 1, 5),
(2, 3, 4),
(3, 3, 3),
(4, 4, 4),
(5, 5, 5),
(7, 9, 11),
(8, 8, 8),
(13, 6, 17),
(128, 8, 8),
(129, 8, 8),
(8, 128, 8),
(8, 129, 8),
(8, 8, 256),
(8, 8, 257),
(200, 300, 260),
(260, 130, 300),
];
for (m, n, k) in shapes {
let a: Vec<f64> = (0..m * k).map(|i| ((i * 7 % 19) as f64) - 9.0).collect();
let b: Vec<f64> = (0..k * n).map(|i| ((i * 11 % 23) as f64) - 11.0).collect();
let c0: Vec<f64> = (0..m * n).map(|i| ((i % 5) as f64) * 0.25).collect();
for (alpha, beta) in [(1.0, 1.0), (1.0, 0.0), (2.5, -1.5), (0.0, 3.0)] {
let mut want = c0.clone();
naive_dgemm(alpha, &a, &b, beta, &mut want, m, n, k);
let mut got = c0.clone();
dgemm(alpha, &a, &b, beta, &mut got, m, n, k);
for idx in 0..m * n {
let scale = want[idx].abs().max(1.0);
assert!(
(got[idx] - want[idx]).abs() / scale < 1e-11,
"m={m} n={n} k={k} alpha={alpha} beta={beta}: element {idx} is {} not {}",
got[idx],
want[idx]
);
}
}
}
}
#[test]
fn dgemm_matches_hand_computation() {
let a = a23();
let b = vec![7.0, 9.0, 11.0, 8.0, 10.0, 12.0]; let mut c = vec![0.0; 4];
dgemm(1.0, &a, &b, 0.0, &mut c, 2, 2, 3);
assert_eq!(c, vec![58.0, 139.0, 64.0, 154.0]);
}
#[test]
fn dgemm_beta_scales_c() {
let a = a23();
let b = vec![7.0, 9.0, 11.0, 8.0, 10.0, 12.0];
let mut c = vec![1.0, 1.0, 1.0, 1.0];
dgemm(1.0, &a, &b, 2.0, &mut c, 2, 2, 3);
assert_eq!(c, vec![58.0 + 2.0, 139.0 + 2.0, 64.0 + 2.0, 154.0 + 2.0]);
}
#[test]
fn dsyrk_computes_both_triangles_and_leaves_the_other_alone() {
let a = vec![1.0, 3.0, 2.0, 4.0];
let n = 2;
let k = 2;
let mut lower = vec![0.0; 4];
dsyrk(1.0, &a, 0.0, &mut lower, n, k, false);
assert_eq!(lower[0], 5.0);
assert_eq!(lower[1], 11.0);
assert_eq!(lower[3], 25.0);
assert_eq!(lower[2], 0.0, "the upper triangle must be left alone");
let mut upper = vec![0.0; 4];
dsyrk(1.0, &a, 0.0, &mut upper, n, k, true);
assert_eq!(upper[0], 5.0);
assert_eq!(upper[2], 11.0);
assert_eq!(upper[3], 25.0);
assert_eq!(upper[1], 0.0, "the lower triangle must be left alone");
}
#[test]
fn dlange_norms() {
let a = a23(); assert_eq!(dlange('M', &a, 2, 3), 6.0);
assert_eq!(dlange('1', &a, 2, 3), 9.0);
assert_eq!(dlange('I', &a, 2, 3), 15.0);
assert!((dlange('F', &a, 2, 3) - 91.0f64.sqrt()).abs() < 1e-12);
assert_eq!(dlange('E', &a, 2, 3), dlange('F', &a, 2, 3));
}
#[test]
#[should_panic(expected = "unrecognized norm")]
fn dlange_rejects_an_unknown_norm() {
dlange('X', &[1.0], 1, 1);
}
}
use num_complex::Complex64;
pub fn zscal(alpha: Complex64, x: &mut [Complex64]) {
for v in x.iter_mut() {
*v = alpha * *v;
}
}
pub fn zaxpy(alpha: Complex64, x: &[Complex64], y: &mut [Complex64]) {
assert_eq!(x.len(), y.len(), "zaxpy: vectors must have the same length");
for i in 0..y.len() {
y[i] += alpha * x[i];
}
}
pub fn zcopy(x: &[Complex64], y: &mut [Complex64]) {
assert_eq!(x.len(), y.len(), "zcopy: vectors must have the same length");
y.copy_from_slice(x);
}
pub fn dznrm2(x: &[Complex64]) -> f64 {
let mut scale = 0.0f64;
for v in x {
let a = v.norm();
if a > scale {
scale = a;
}
}
if scale == 0.0 || !scale.is_finite() {
return x.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt();
}
let inv = 1.0 / scale;
let ss: f64 = x.iter().map(|v| (v.norm() * inv) * (v.norm() * inv)).sum();
scale * ss.sqrt()
}
pub fn zger(alpha: Complex64, x: &[Complex64], y: &[Complex64], a: &mut [Complex64], m: usize, n: usize) {
assert_eq!(a.len(), m * n, "zger: a must be m*n");
assert_eq!(x.len(), m, "zger: x must have m entries");
assert_eq!(y.len(), n, "zger: y must have n entries");
for j in 0..n {
let c = alpha * y[j];
if c == Complex64::new(0.0, 0.0) {
continue;
}
let col = &mut a[j * m..(j + 1) * m];
for i in 0..m {
col[i] += c * x[i];
}
}
}
pub fn zgemv(alpha: Complex64, a: &[Complex64], x: &[Complex64], beta: Complex64, y: &mut [Complex64], m: usize, n: usize) {
assert_eq!(a.len(), m * n, "zgemv: a must be m*n");
assert_eq!(x.len(), n, "zgemv: x must have n entries");
assert_eq!(y.len(), m, "zgemv: y must have m entries");
for v in y.iter_mut() {
*v = beta * *v;
}
for j in 0..n {
let c = alpha * x[j];
if c == Complex64::new(0.0, 0.0) {
continue;
}
let col = &a[j * m..(j + 1) * m];
for i in 0..m {
y[i] += c * col[i];
}
}
}
pub fn zgemm(alpha: Complex64, a: &[Complex64], b: &[Complex64], beta: Complex64, c: &mut [Complex64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), m * k, "zgemm: a must be m*k");
assert_eq!(b.len(), k * n, "zgemm: b must be k*n");
assert_eq!(c.len(), m * n, "zgemm: c must be m*n");
zgemm_core(alpha, a, b, beta, c, m, n, k, Trans::No, Trans::No);
}
pub fn zgemm_tn(alpha: Complex64, a: &[Complex64], b: &[Complex64], beta: Complex64, c: &mut [Complex64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), k * m, "zgemm_tn: a must be k*m");
assert_eq!(b.len(), k * n, "zgemm_tn: b must be k*n");
assert_eq!(c.len(), m * n, "zgemm_tn: c must be m*n");
zgemm_core(alpha, a, b, beta, c, m, n, k, Trans::Yes, Trans::No);
}
pub fn zgemm_nt(alpha: Complex64, a: &[Complex64], b: &[Complex64], beta: Complex64, c: &mut [Complex64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), m * k, "zgemm_nt: a must be m*k");
assert_eq!(b.len(), n * k, "zgemm_nt: b must be n*k");
assert_eq!(c.len(), m * n, "zgemm_nt: c must be m*n");
zgemm_core(alpha, a, b, beta, c, m, n, k, Trans::No, Trans::Yes);
}
pub fn zgemm_tt(alpha: Complex64, a: &[Complex64], b: &[Complex64], beta: Complex64, c: &mut [Complex64], m: usize, n: usize, k: usize) {
assert_eq!(a.len(), k * m, "zgemm_tt: a must be k*m");
assert_eq!(b.len(), n * k, "zgemm_tt: b must be n*k");
assert_eq!(c.len(), m * n, "zgemm_tt: c must be m*n");
zgemm_core(alpha, a, b, beta, c, m, n, k, Trans::Yes, Trans::Yes);
}
#[allow(clippy::too_many_arguments)]
fn zgemm_core(
alpha: Complex64,
a: &[Complex64],
b: &[Complex64],
beta: Complex64,
c: &mut [Complex64],
m: usize,
n: usize,
k: usize,
trans_a: Trans,
trans_b: Trans,
) {
let ada = if trans_a == Trans::No { m } else { k };
let adb = if trans_b == Trans::No { k } else { n };
for j in 0..n {
for i in 0..m {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
let av = match trans_a {
Trans::No => a[i + l * ada],
Trans::Yes => a[l + i * ada],
};
let bv = match trans_b {
Trans::No => b[l + j * adb],
Trans::Yes => b[j + l * adb],
};
sum += av * bv;
}
let idx = i + j * m;
c[idx] = alpha * sum + beta * c[idx];
}
}
}
pub fn zherk(alpha: f64, a: &[Complex64], beta: f64, c: &mut [Complex64], n: usize, k: usize, upper: bool) {
assert_eq!(a.len(), n * k, "zherk: a must be n*k");
assert_eq!(c.len(), n * n, "zherk: c must be n*n");
for j in 0..n {
let i_range = if upper { 0..j + 1 } else { j..n };
for i in i_range {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[i + l * n] * a[j + l * n].conj();
}
let idx = i + j * n;
let mut v = Complex64::new(alpha * sum.re, alpha * sum.im) + Complex64::new(beta * c[idx].re, beta * c[idx].im);
if i == j {
v.im = 0.0;
}
c[idx] = v;
}
}
}
pub fn zsyrk(alpha: Complex64, a: &[Complex64], beta: Complex64, c: &mut [Complex64], n: usize, k: usize, upper: bool) {
assert_eq!(a.len(), n * k, "zsyrk: a must be n*k");
assert_eq!(c.len(), n * n, "zsyrk: c must be n*n");
for j in 0..n {
let i_range = if upper { 0..j + 1 } else { j..n };
for i in i_range {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[i + l * n] * a[j + l * n];
}
let idx = i + j * n;
c[idx] = alpha * sum + beta * c[idx];
}
}
}
pub fn zlange(norm: char, a: &[Complex64], m: usize, n: usize) -> f64 {
assert_eq!(a.len(), m * n, "zlange: a must be m*n");
match norm {
'M' | 'm' => a.iter().fold(0.0f64, |acc, v| acc.max(v.norm())),
'1' | 'O' | 'o' => {
(0..n)
.map(|j| a[j * m..(j + 1) * m].iter().map(|v| v.norm()).sum::<f64>())
.fold(0.0f64, f64::max)
}
'I' | 'i' => {
(0..m).map(|i| (0..n).map(|j| a[i + j * m].norm()).sum::<f64>()).fold(0.0f64, f64::max)
}
'F' | 'f' | 'E' | 'e' => {
let sum: f64 = a.iter().map(|v| v.norm_sqr()).sum();
sum.sqrt()
}
other => panic!("zlange: unrecognized norm '{other}'; expected one of 1, O, I, F, E, M"),
}
}
#[allow(clippy::too_many_arguments)]
pub fn dsyrk_rank(alpha: f64, a: &[f64], beta: f64, c: &mut [f64], n: usize, k: usize, upper: bool, transposed: bool) {
if !transposed {
dsyrk(alpha, a, beta, c, n, k, upper);
return;
}
assert_eq!(a.len(), k * n, "dsyrk_rank: a must be k*n");
assert_eq!(c.len(), n * n, "dsyrk_rank: c must be n*n");
for j in 0..n {
let i_range = if upper { 0..j + 1 } else { j..n };
for i in i_range {
let mut sum = 0.0;
for l in 0..k {
sum += a[l + i * k] * a[l + j * k];
}
let idx = i + j * n;
c[idx] = if beta == 0.0 { alpha * sum } else { alpha * sum + beta * c[idx] };
}
}
}
pub fn zgemv_trans(alpha: Complex64, a: &[Complex64], x: &[Complex64], beta: Complex64, y: &mut [Complex64], m: usize, n: usize) {
assert_eq!(a.len(), m * n, "zgemv_trans: a must be m*n");
assert_eq!(x.len(), m, "zgemv_trans: x must have m entries");
assert_eq!(y.len(), n, "zgemv_trans: y must have n entries");
for j in 0..n {
let col = &a[j * m..(j + 1) * m];
let mut sum = Complex64::new(0.0, 0.0);
for i in 0..m {
sum += col[i] * x[i];
}
y[j] = alpha * sum + beta * y[j];
}
}
pub fn zsyrk_rank(alpha: Complex64, a: &[Complex64], beta: Complex64, c: &mut [Complex64], n: usize, k: usize, upper: bool) {
assert_eq!(a.len(), k * n, "zsyrk_rank: a must be k*n");
assert_eq!(c.len(), n * n, "zsyrk_rank: c must be n*n");
for j in 0..n {
let i_range = if upper { 0..j + 1 } else { j..n };
for i in i_range {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[l + i * k] * a[l + j * k];
}
let idx = i + j * n;
c[idx] = alpha * sum + beta * c[idx];
}
}
}
pub fn zherk_rank(alpha: f64, a: &[Complex64], beta: f64, c: &mut [Complex64], n: usize, k: usize, upper: bool) {
assert_eq!(a.len(), k * n, "zherk_rank: a must be k*n");
assert_eq!(c.len(), n * n, "zherk_rank: c must be n*n");
for j in 0..n {
let i_range = if upper { 0..j + 1 } else { j..n };
for i in i_range {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[l + i * k].conj() * a[l + j * k];
}
let idx = i + j * n;
let mut v = Complex64::new(alpha * sum.re, alpha * sum.im) + Complex64::new(beta * c[idx].re, beta * c[idx].im);
if i == j {
v.im = 0.0;
}
c[idx] = v;
}
}
}
pub fn dtrsv(a: &[f64], x: &mut [f64], n: usize, lower: bool, unit: bool) {
assert_eq!(a.len(), n * n, "dtrsv: a must be n*n");
assert_eq!(x.len(), n, "dtrsv: x must be n");
tri_solve_f64(a, x, n, 1, lower, unit, false);
}
pub fn dtrsv_transpose(a: &[f64], x: &mut [f64], n: usize, lower: bool, unit: bool) {
assert_eq!(a.len(), n * n, "dtrsv_transpose: a must be n*n");
assert_eq!(x.len(), n, "dtrsv_transpose: x must be n");
tri_solve_f64(a, x, n, 1, lower, unit, true);
}
pub fn dtrsm(alpha: f64, a: &[f64], b: &mut [f64], m: usize, n: usize, left: bool, lower: bool, unit: bool) {
assert_eq!(b.len(), m * n, "dtrsm: b must be m*n");
if left {
assert_eq!(a.len(), m * m, "dtrsm: a must be m*m for the left side");
for j in 0..n {
let x = &mut b[j * m..(j + 1) * m];
for v in x.iter_mut() {
*v *= alpha;
}
tri_solve_f64(a, x, m, 1, lower, unit, false);
}
} else {
assert_eq!(a.len(), n * n, "dtrsm: a must be n*n for the right side");
for i in 0..m {
let x = &mut b[i..];
for k in 0..n {
x[k * m] *= alpha;
}
tri_solve_f64(a, x, n, m, lower, unit, true);
}
}
}
fn tri_solve_f64(a: &[f64], x: &mut [f64], n: usize, stride: usize, lower: bool, unit: bool, trans: bool) {
let effective_lower = if trans { !lower } else { lower };
if effective_lower {
for i in 0..n {
let mut sum = x[i * stride];
for k in 0..i {
let a_ik = if trans { a[k + i * n] } else { a[i + k * n] };
sum -= a_ik * x[k * stride];
}
if !unit {
sum /= a[i + i * n];
}
x[i * stride] = sum;
}
} else {
for i in (0..n).rev() {
let mut sum = x[i * stride];
for k in i + 1..n {
let a_ik = if trans { a[k + i * n] } else { a[i + k * n] };
sum -= a_ik * x[k * stride];
}
if !unit {
sum /= a[i + i * n];
}
x[i * stride] = sum;
}
}
}
pub fn ztrsv(a: &[Complex64], x: &mut [Complex64], n: usize, lower: bool, unit: bool) {
assert_eq!(a.len(), n * n, "ztrsv: a must be n*n");
assert_eq!(x.len(), n, "ztrsv: x must be n");
tri_solve_z(a, x, n, 1, lower, unit, false);
}
pub fn ztrsv_transpose(a: &[Complex64], x: &mut [Complex64], n: usize, lower: bool, unit: bool) {
assert_eq!(a.len(), n * n, "ztrsv_transpose: a must be n*n");
assert_eq!(x.len(), n, "ztrsv_transpose: x must be n");
tri_solve_z(a, x, n, 1, lower, unit, true);
}
pub fn ztrsm(alpha: Complex64, a: &[Complex64], b: &mut [Complex64], m: usize, n: usize, left: bool, lower: bool, unit: bool) {
assert_eq!(b.len(), m * n, "ztrsm: b must be m*n");
if left {
assert_eq!(a.len(), m * m, "ztrsm: a must be m*m for the left side");
for j in 0..n {
let x = &mut b[j * m..(j + 1) * m];
for v in x.iter_mut() {
*v *= alpha;
}
tri_solve_z(a, x, m, 1, lower, unit, false);
}
} else {
assert_eq!(a.len(), n * n, "ztrsm: a must be n*n for the right side");
for i in 0..m {
let x = &mut b[i..];
for k in 0..n {
x[k * m] *= alpha;
}
tri_solve_z(a, x, n, m, lower, unit, true);
}
}
}
fn tri_solve_z(a: &[Complex64], x: &mut [Complex64], n: usize, stride: usize, lower: bool, unit: bool, trans: bool) {
let effective_lower = if trans { !lower } else { lower };
if effective_lower {
for i in 0..n {
let mut sum = x[i * stride];
for k in 0..i {
let a_ik = if trans { a[k + i * n] } else { a[i + k * n] };
sum -= a_ik * x[k * stride];
}
if !unit {
sum /= a[i + i * n];
}
x[i * stride] = sum;
}
} else {
for i in (0..n).rev() {
let mut sum = x[i * stride];
for k in i + 1..n {
let a_ik = if trans { a[k + i * n] } else { a[i + k * n] };
sum -= a_ik * x[k * stride];
}
if !unit {
sum /= a[i + i * n];
}
x[i * stride] = sum;
}
}
}
#[cfg(test)]
mod complex_tests {
use super::*;
fn complex_2x4() -> Vec<Complex64> {
vec![
Complex64::new(1.0, -1.0),
Complex64::new(3.0, 1.0), Complex64::new(2.0, 0.0),
Complex64::new(1.0, 0.0), Complex64::new(1.0, 0.0),
Complex64::new(3.0, 0.0), Complex64::new(1.0, 0.0),
Complex64::new(1.0, 2.0), ]
}
fn assert_close(got: Complex64, want: Complex64, what: &str) {
assert!((got - want).norm() < 1e-13, "{what}: got {got} but the reference says {want}");
}
#[test]
fn zherk_rank_conjugates_the_first_index() {
let a = complex_2x4();
let (k, n) = (2usize, 4usize);
let mut c = vec![Complex64::new(0.0, 0.0); n * n];
zherk_rank(1.0, &a, 0.0, &mut c, n, k, false);
#[rustfmt::skip]
let lower = [
(0, 0, 12.0, 0.0),
(1, 0, 5.0, -1.0),
(2, 0, 10.0, 2.0),
(3, 0, 6.0, -6.0),
(1, 1, 5.0, 0.0),
(2, 1, 5.0, 0.0),
(3, 1, 3.0, -2.0),
(2, 2, 10.0, 0.0),
(3, 2, 4.0, -6.0),
(3, 3, 6.0, 0.0),
];
for (i, j, re, im) in lower {
assert_close(c[i + j * n], Complex64::new(re, im), &format!("({i},{j})"));
}
for j in 1..n {
for i in 0..j {
assert_eq!(c[i + j * n], Complex64::new(0.0, 0.0), "({i},{j}) must be left alone");
}
}
assert_eq!(c[1].im, -1.0, "the off-diagonal imaginary part must not be negated");
}
#[test]
fn zherk_rank_upper_triangle() {
let a = complex_2x4();
let (k, n) = (2usize, 4usize);
let mut c = vec![Complex64::new(0.0, 0.0); n * n];
zherk_rank(1.0, &a, 0.0, &mut c, n, k, true);
assert_close(c[0 + 1 * n], Complex64::new(5.0, 1.0), "(0,1)");
assert_close(c[1 + 2 * n], Complex64::new(5.0, 0.0), "(1,2)");
assert_eq!(c[1], Complex64::new(0.0, 0.0));
}
#[test]
fn zherk_rank_applies_beta_and_clears_the_diagonal() {
let a = complex_2x4();
let (k, n) = (2usize, 4usize);
let mut c = vec![Complex64::new(0.0, 0.0); n * n];
for v in c.iter_mut() {
*v = Complex64::new(1.0, 1.0);
}
zherk_rank(2.0, &a, 3.0, &mut c, n, k, false);
assert_close(c[0], Complex64::new(27.0, 0.0), "(0,0)");
assert_close(c[1], Complex64::new(13.0, 1.0), "(1,0)");
}
}
#[cfg(test)]
mod reference_tests {
use super::*;
use crate::cpx;
fn assert_close(got: f64, want: f64, what: &str) {
let scale = want.abs().max(1.0);
assert!((got - want).abs() / scale < 1e-13, "{what}: got {got} but the reference says {want}");
}
fn assert_close_cpx(got: Complex64, want: Complex64, what: &str) {
let scale = want.norm().max(1.0);
assert!((got - want).norm() / scale < 1e-13, "{what}: got {got} but the reference says {want}");
}
#[test]
fn dgemm_tn_matches_cblas() {
let a: &[f64] = &[
-266.66666666666669,
298.33333333333331,
-21.0,
286.66666666666669,
-39.666666666666664,
104.66666666666667,
-117.33333333333333,
-180.33333333333334,
-221.66666666666666,
100.0,
243.33333333333334,
103.33333333333333,
-14.0,
4.333333333333333,
188.66666666666666,
];
let b: &[f64] = &[
39.666666666666664,
-154.33333333333334,
-316.66666666666669,
-141.33333333333334,
-16.666666666666668,
252.66666666666666,
272.0,
-70.666666666666671,
-96.666666666666671,
-67.666666666666671,
-315.66666666666669,
-222.0,
-9.0,
-296.0,
-27.0,
-152.33333333333334,
-323.33333333333331,
-23.333333333333332,
219.0,
-93.666666666666671,
];
let c0: &[f64] = &[
188.0,
-183.33333333333334,
-303.66666666666669,
-45.333333333333336,
69.0,
190.0,
-173.0,
20.666666666666668,
162.0,
-106.33333333333333,
198.0,
-211.0,
];
let want: &[f64] = &[
-89825.0,
109028.0,
-5619.1111111111113,
-9774.111111111124,
21935.777777777781,
77392.888888888891,
-65645.555555555533,
57544.555555555547,
-106002.88888888891,
11146.555555555573,
-31710.333333333336,
-86875.0,
];
let want_ab: &[f64] = &[
-224844.5,
272845.0,
-13592.277777777777,
-24367.27777777781,
54735.944444444453,
193197.22222222222,
-163854.38888888882,
143830.38888888888,
-265250.22222222225,
28025.888888888934,
-79572.833333333343,
-216871.0,
];
let (m, n, k) = (3usize, 4usize, 5usize);
let mut c = c0.to_vec();
dgemm_tn(1.0, a, b, 0.0, &mut c, m, n, k);
for i in 0..m * n {
assert_close(c[i], want[i], &format!("dgemm_tn [{i}]"));
}
let mut c = c0.to_vec();
dgemm_tn(2.5, a, b, -1.5, &mut c, m, n, k);
for i in 0..m * n {
assert_close(c[i], want_ab[i], &format!("dgemm_tn alpha/beta [{i}]"));
}
}
#[test]
fn dgemm_nt_matches_cblas() {
let a: &[f64] = &[
242.66666666666666,
55.0,
287.66666666666669,
144.0,
-254.33333333333334,
284.0,
105.0,
78.666666666666671,
-87.666666666666671,
-48.333333333333336,
-295.33333333333331,
-227.33333333333334,
80.0,
-188.33333333333334,
-198.0,
];
let b: &[f64] = &[
113.66666666666667,
26.0,
23.333333333333332,
240.33333333333334,
221.0,
221.33333333333334,
49.333333333333336,
-219.66666666666666,
171.33333333333334,
-87.0,
-59.0,
-216.33333333333334,
179.0,
-14.0,
-133.33333333333334,
-96.666666666666671,
289.66666666666669,
-286.0,
114.33333333333333,
-228.0,
];
let c0: &[f64] = &[
-15.333333333333334,
-3.0,
-241.0,
-330.66666666666669,
306.66666666666669,
-118.66666666666667,
271.33333333333331,
-128.33333333333334,
-237.0,
-126.0,
-212.0,
72.0,
];
let want: &[f64] = &[
91918.777777777766,
-143896.33333333334,
-17604.777777777785,
6843.0000000000036,
-3708.4444444444521,
137775.66666666669,
22162.333333333336,
1939.8888888888862,
33568.333333333336,
-9593.8888888888869,
123557.55555555555,
92835.333333333343,
];
let want_ab: &[f64] = &[
229819.94444444441,
-359736.33333333337,
-43650.44444444446,
17603.500000000007,
-9731.1111111111295,
344617.16666666674,
54998.833333333343,
5042.2222222222154,
84276.333333333343,
-23795.722222222219,
309211.88888888888,
231980.33333333337,
];
let (m, n, k) = (3usize, 4usize, 5usize);
let mut c = c0.to_vec();
dgemm_nt(1.0, a, b, 0.0, &mut c, m, n, k);
for i in 0..m * n {
assert_close(c[i], want[i], &format!("dgemm_nt [{i}]"));
}
let mut c = c0.to_vec();
dgemm_nt(2.5, a, b, -1.5, &mut c, m, n, k);
for i in 0..m * n {
assert_close(c[i], want_ab[i], &format!("dgemm_nt alpha/beta [{i}]"));
}
}
#[test]
fn dgemm_tt_matches_cblas() {
let a: &[f64] = &[
-192.33333333333334,
-188.33333333333334,
319.0,
1.3333333333333333,
198.0,
73.333333333333329,
327.0,
-329.66666666666669,
-231.0,
80.666666666666671,
-167.0,
109.0,
-103.33333333333333,
286.33333333333331,
82.333333333333329,
];
let b: &[f64] = &[
-321.33333333333331,
-217.33333333333334,
54.666666666666664,
97.666666666666671,
6.333333333333333,
-266.66666666666669,
271.66666666666669,
39.333333333333336,
28.0,
-235.33333333333334,
69.666666666666671,
120.0,
-4.0,
70.666666666666671,
147.0,
-207.33333333333334,
-71.0,
-222.33333333333334,
200.66666666666666,
233.0,
];
let c0: &[f64] = &[
59.0,
177.66666666666666,
99.0,
51.0,
-122.66666666666667,
-150.33333333333334,
-228.66666666666666,
-277.33333333333331,
-246.0,
-145.33333333333334,
45.0,
77.333333333333329,
];
let want: &[f64] = &[
55479.0,
-35527.444444444445,
44468.666666666664,
-26976.444444444445,
-59815.111111111109,
33474.555555555555,
473.55555555555287,
52107.222222222219,
71896.0,
57945.222222222219,
47153.555555555555,
-64605.777777777774,
];
let want_ab: &[f64] = &[
138609.0,
-89085.111111111109,
111023.16666666666,
-67517.611111111109,
-149353.77777777778,
83911.888888888891,
1526.8888888888821,
130684.05555555555,
180109.0,
145081.05555555556,
117816.38888888889,
-161630.44444444444,
];
let (m, n, k) = (3usize, 4usize, 5usize);
let mut c = c0.to_vec();
dgemm_tt(1.0, a, b, 0.0, &mut c, m, n, k);
for i in 0..m * n {
assert_close(c[i], want[i], &format!("dgemm_tt [{i}]"));
}
let mut c = c0.to_vec();
dgemm_tt(2.5, a, b, -1.5, &mut c, m, n, k);
for i in 0..m * n {
assert_close(c[i], want_ab[i], &format!("dgemm_tt alpha/beta [{i}]"));
}
}
#[test]
fn dgemm_tn_matches_cblas_exactly_on_integers() {
let a: &[f64] = &[-3.0, 0.0, 3.0, -1.0, 2.0, -2.0, 1.0, -3.0, 0.0, 3.0, -1.0, 2.0, -2.0, 1.0, -3.0];
let b: &[f64] = &[
-4.0, 1.0, -3.0, 2.0, -2.0, 3.0, -1.0, 4.0, 0.0, -4.0, 1.0, -3.0, 2.0, -2.0, 3.0, -1.0, 4.0, 0.0, -4.0, 1.0,
];
let want: &[f64] = &[-3.0, 12.0, 20.0, -5.0, -31.0, -1.0, 11.0, -2.0, -22.0, 9.0, 9.0, 2.0];
let (m, n, k) = (3usize, 4usize, 5usize);
let mut c = vec![0.0; m * n];
dgemm_tn(1.0, a, b, 0.0, &mut c, m, n, k);
assert_eq!(c.as_slice(), want, "dgemm_tn must be exact on integer data");
}
#[test]
fn zgemm_tn_matches_cblas() {
let a: &[Complex64] = &[
cpx!(-266.66666666666669, -59.166666666666664),
cpx!(298.33333333333331, 134.66666666666666),
cpx!(-21.0, -4.0),
cpx!(286.66666666666669, -142.0),
cpx!(-39.666666666666664, -115.66666666666667),
cpx!(104.66666666666667, 21.0),
cpx!(-117.33333333333333, -86.333333333333329),
cpx!(-180.33333333333334, 94.166666666666671),
];
let b: &[Complex64] = &[
cpx!(-192.33333333333334, -123.66666666666667),
cpx!(-188.33333333333334, 120.16666666666667),
cpx!(319.0, -136.16666666666666),
cpx!(1.3333333333333333, -94.0),
cpx!(198.0, -72.833333333333329),
cpx!(73.333333333333329, -149.0),
cpx!(327.0, 24.5),
cpx!(-329.66666666666669, 139.83333333333334),
cpx!(-231.0, 148.0),
cpx!(80.666666666666671, -127.5),
cpx!(-167.0, 163.16666666666666),
cpx!(109.0, -132.16666666666666),
];
let c0: &[Complex64] = &[
cpx!(-160.66666666666666, 60.666666666666664),
cpx!(-108.66666666666667, 13.75),
cpx!(27.333333333333332, 71.916666666666671),
cpx!(48.833333333333336, 36.0),
cpx!(3.1666666666666665, -63.583333333333336),
cpx!(-133.33333333333334, 71.0),
];
let want: &[Complex64] = &[
cpx!(-68015.111111111095, 107181.2222222222),
cpx!(-97738.770833333314, 152039.93055555556),
cpx!(-134964.09027777781, 213039.76388888893),
cpx!(-112307.58333333333, -247393.70833333334),
cpx!(147381.42361111112, -346457.55555555562),
cpx!(166229.97222222222, 13265.805555555547),
];
let (m, n, k) = (2usize, 3usize, 4usize);
let alpha = cpx!(2.0, -1.0);
let beta = cpx!(0.5, 0.25);
let mut c = c0.to_vec();
zgemm_tn(alpha, a, b, beta, &mut c, m, n, k);
for i in 0..m * n {
assert_close_cpx(c[i], want[i], &format!("zgemm_tn [{i}]"));
}
}
#[test]
fn zgemm_nt_matches_cblas() {
let a: &[Complex64] = &[
cpx!(39.666666666666664, 94.0),
cpx!(-154.33333333333334, -91.666666666666671),
cpx!(-316.66666666666669, -151.83333333333334),
cpx!(-141.33333333333334, -22.666666666666668),
cpx!(-16.666666666666668, 34.5),
cpx!(252.66666666666666, 95.0),
cpx!(272.0, -86.5),
cpx!(-70.666666666666671, 10.333333333333334),
];
let b: &[Complex64] = &[
cpx!(113.66666666666667, -7.666666666666667),
cpx!(26.0, -1.5),
cpx!(23.333333333333332, -120.5),
cpx!(240.33333333333334, -165.33333333333334),
cpx!(221.0, 153.33333333333334),
cpx!(221.33333333333334, -59.333333333333336),
cpx!(49.333333333333336, 135.66666666666666),
cpx!(-219.66666666666666, -64.166666666666671),
cpx!(171.33333333333334, -118.5),
cpx!(-87.0, -63.0),
cpx!(-59.0, -106.0),
cpx!(-216.33333333333334, 36.0),
];
let c0: &[Complex64] = &[
cpx!(-160.66666666666666, 60.666666666666664),
cpx!(-108.66666666666667, 13.75),
cpx!(27.333333333333332, 71.916666666666671),
cpx!(48.833333333333336, 36.0),
cpx!(3.1666666666666665, -63.583333333333336),
cpx!(-133.33333333333334, 71.0),
];
let want: &[Complex64] = &[
cpx!(-245210.5, 162737.38888888891),
cpx!(-48025.215277777796, 151967.04166666666),
cpx!(-239712.14583333334, -155012.73611111112),
cpx!(-211119.41666666669, -41983.347222222219),
cpx!(-223677.24305555562, 159284.97222222225),
cpx!(45678.305555555562, -19420.722222222219),
];
let (m, n, k) = (2usize, 3usize, 4usize);
let alpha = cpx!(2.0, -1.0);
let beta = cpx!(0.5, 0.25);
let mut c = c0.to_vec();
zgemm_nt(alpha, a, b, beta, &mut c, m, n, k);
for i in 0..m * n {
assert_close_cpx(c[i], want[i], &format!("zgemm_nt [{i}]"));
}
}
#[test]
fn zgemm_tt_matches_cblas() {
let a: &[Complex64] = &[
cpx!(-173.0, -96.166666666666671),
cpx!(-246.0, -94.166666666666671),
cpx!(-209.66666666666666, 159.5),
cpx!(193.66666666666666, 0.66666666666666663),
cpx!(92.0, 99.0),
cpx!(-329.33333333333331, 36.666666666666664),
cpx!(216.33333333333334, 163.5),
cpx!(130.66666666666666, -164.83333333333334),
];
let b: &[Complex64] = &[
cpx!(59.0, -160.66666666666666),
cpx!(177.66666666666666, -108.66666666666667),
cpx!(99.0, 27.333333333333332),
cpx!(51.0, 48.833333333333336),
cpx!(-122.66666666666667, 3.1666666666666665),
cpx!(-150.33333333333334, -133.33333333333334),
cpx!(-228.66666666666666, 135.83333333333334),
cpx!(-277.33333333333331, 19.666666666666668),
cpx!(-246.0, 14.0),
cpx!(-145.33333333333334, -117.66666666666667),
cpx!(45.0, 34.833333333333336),
cpx!(77.333333333333329, 60.0),
];
let c0: &[Complex64] = &[
cpx!(-160.66666666666666, 60.666666666666664),
cpx!(-108.66666666666667, 13.75),
cpx!(27.333333333333332, 71.916666666666671),
cpx!(48.833333333333336, 36.0),
cpx!(3.1666666666666665, -63.583333333333336),
cpx!(-133.33333333333334, 71.0),
];
let want: &[Complex64] = &[
cpx!(-153416.0, -129677.22222222225),
cpx!(-237261.88194444444, 62147.180555555577),
cpx!(76879.965277777781, -111139.90277777778),
cpx!(-10298.583333333325, -99573.763888888876),
cpx!(150643.70138888888, -69828.0),
cpx!(59107.805555555562, -8056.1666666666533),
];
let (m, n, k) = (2usize, 3usize, 4usize);
let alpha = cpx!(2.0, -1.0);
let beta = cpx!(0.5, 0.25);
let mut c = c0.to_vec();
zgemm_tt(alpha, a, b, beta, &mut c, m, n, k);
for i in 0..m * n {
assert_close_cpx(c[i], want[i], &format!("zgemm_tt [{i}]"));
}
}
fn transpose(src: &[f64], rows: usize, cols: usize) -> Vec<f64> {
let mut dst = vec![0.0; rows * cols];
for j in 0..cols {
for i in 0..rows {
dst[j + i * cols] = src[i + j * rows];
}
}
dst
}
fn transpose_cpx(src: &[Complex64], rows: usize, cols: usize) -> Vec<Complex64> {
let mut dst = vec![Complex64::new(0.0, 0.0); rows * cols];
for j in 0..cols {
for i in 0..rows {
dst[j + i * cols] = src[i + j * rows];
}
}
dst
}
fn naive_mat_mul(alpha: f64, a: &[f64], b: &[f64], beta: f64, c: &mut [f64], m: usize, n: usize, k: usize) {
for j in 0..n {
for i in 0..m {
let mut sum = 0.0;
for l in 0..k {
sum += a[i + l * m] * b[l + j * k];
}
let idx = i + j * m;
c[idx] = if beta == 0.0 { alpha * sum } else { alpha * sum + beta * c[idx] };
}
}
}
fn naive_zmat_mul(alpha: Complex64, a: &[Complex64], b: &[Complex64], beta: Complex64, c: &mut [Complex64], m: usize, n: usize, k: usize) {
for j in 0..n {
for i in 0..m {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[i + l * m] * b[l + j * k];
}
let idx = i + j * m;
c[idx] = alpha * sum + beta * c[idx];
}
}
}
type RealGemm = fn(f64, &[f64], &[f64], f64, &mut [f64], usize, usize, usize);
type ComplexGemm = fn(Complex64, &[Complex64], &[Complex64], Complex64, &mut [Complex64], usize, usize, usize);
#[test]
fn transposed_dgemm_matches_the_definition_on_every_branch() {
let shapes = [
(1, 1, 1),
(1, 5, 1),
(5, 1, 1),
(1, 1, 5),
(2, 3, 4),
(3, 3, 3),
(4, 4, 4),
(5, 5, 5),
(7, 9, 11),
(8, 8, 8),
(13, 6, 17),
(128, 8, 8),
(129, 8, 8),
(8, 128, 8),
(8, 129, 8),
(8, 8, 256),
(8, 8, 257),
(200, 300, 260),
(260, 130, 300),
];
let variants: [(bool, bool, RealGemm); 3] = [(true, false, dgemm_tn), (false, true, dgemm_nt), (true, true, dgemm_tt)];
for (m, n, k) in shapes {
let a_op: Vec<f64> = (0..m * k).map(|i| ((i * 7 % 19) as f64) - 9.0).collect();
let b_op: Vec<f64> = (0..k * n).map(|i| ((i * 11 % 23) as f64) - 11.0).collect();
let a_t = transpose(&a_op, m, k);
let b_t = transpose(&b_op, k, n);
for (ta, tb, kernel) in variants {
let a_stored = if ta { &a_t } else { &a_op };
let b_stored = if tb { &b_t } else { &b_op };
let c0: Vec<f64> = (0..m * n).map(|i| ((i % 5) as f64) * 0.25).collect();
for (alpha, beta) in [(1.0, 1.0), (1.0, 0.0), (2.5, -1.5), (0.0, 3.0)] {
let mut want = c0.clone();
naive_mat_mul(alpha, &a_op, &b_op, beta, &mut want, m, n, k);
let mut got = c0.clone();
kernel(alpha, a_stored, b_stored, beta, &mut got, m, n, k);
for idx in 0..m * n {
let scale = want[idx].abs().max(1.0);
assert!(
(got[idx] - want[idx]).abs() / scale < 1e-11,
"ta={ta} tb={tb} m={m} n={n} k={k} alpha={alpha} beta={beta}: element {idx} is {} not {}",
got[idx],
want[idx]
);
}
}
}
}
}
#[test]
fn transposed_zgemm_matches_the_definition_on_every_shape() {
let shapes = [(1, 1, 1), (1, 4, 1), (3, 2, 5), (2, 3, 4), (5, 5, 5), (7, 11, 13)];
let variants: [(bool, bool, ComplexGemm); 3] = [(true, false, zgemm_tn), (false, true, zgemm_nt), (true, true, zgemm_tt)];
let alpha = cpx!(0.5, -1.5);
let beta = cpx!(2.0, 0.25);
for (m, n, k) in shapes {
let a_op: Vec<Complex64> = (0..m * k).map(|i| cpx!((i % 3) as f64 - 1.0, (i % 5) as f64 - 2.0)).collect();
let b_op: Vec<Complex64> = (0..k * n).map(|i| cpx!((i % 4) as f64 - 1.5, (i % 7) as f64 - 3.0)).collect();
let a_t = transpose_cpx(&a_op, m, k);
let b_t = transpose_cpx(&b_op, k, n);
for (ta, tb, kernel) in variants {
let a_stored = if ta { &a_t } else { &a_op };
let b_stored = if tb { &b_t } else { &b_op };
let c0: Vec<Complex64> = (0..m * n).map(|i| cpx!((i % 2) as f64, 0.5)).collect();
let mut want = c0.clone();
naive_zmat_mul(alpha, &a_op, &b_op, beta, &mut want, m, n, k);
let mut got = c0.clone();
kernel(alpha, a_stored, b_stored, beta, &mut got, m, n, k);
for idx in 0..m * n {
assert_close_cpx(got[idx], want[idx], &format!("ta={ta} tb={tb} m={m} n={n} k={k} [{idx}]"));
}
}
}
}
}
#[cfg(test)]
mod definition_tests {
use super::*;
use crate::cpx;
fn complex_matrix(m: usize, n: usize) -> Vec<Complex64> {
(0..m * n)
.map(|idx| {
let (i, j) = (idx % m, idx / m);
cpx!(i as f64 + 0.5 * j as f64, 1.0 - 2.0 * i as f64 + 0.25 * j as f64)
})
.collect()
}
fn real_matrix(m: usize, n: usize) -> Vec<f64> {
(0..m * n).map(|idx| ((idx * 7 % 19) as f64) - 9.0 + idx as f64 * 0.125).collect()
}
fn assert_close(got: f64, want: f64, what: &str) {
let scale = want.abs().max(1.0);
assert!((got - want).abs() / scale < 1e-13, "{what}: got {got} but the definition says {want}");
}
fn assert_close_cpx(got: Complex64, want: Complex64, what: &str) {
let scale = want.norm().max(1.0);
assert!((got - want).norm() / scale < 1e-13, "{what}: got {got} but the definition says {want}");
}
#[test]
fn complex_level1_operations() {
let x = [cpx!(1.0, 1.0), cpx!(2.0, -1.0)];
let mut y = [cpx!(3.0, 0.0), cpx!(0.0, 1.0)];
zaxpy(cpx!(0.0, 1.0), &x, &mut y);
assert_eq!(y, [cpx!(2.0, 1.0), cpx!(1.0, 3.0)]);
zscal(cpx!(2.0, 0.0), &mut y);
assert_eq!(y, [cpx!(4.0, 2.0), cpx!(2.0, 6.0)]);
let mut z = [cpx!(0.0, 0.0); 2];
zcopy(&y, &mut z);
assert_eq!(z, y);
}
#[test]
fn dgemv_trans_matches_the_definition() {
let (m, n) = (2usize, 3usize);
let a = real_matrix(m, n);
let x = real_matrix(m, 1);
for (alpha, beta) in [(1.0, 0.0), (-2.5, 1.75)] {
let y0 = real_matrix(1, n);
let mut want = y0.clone();
for j in 0..n {
let mut sum = 0.0;
for i in 0..m {
sum += a[i + j * m] * x[i];
}
want[j] = alpha * sum + beta * want[j];
}
let mut got = y0.clone();
dgemv_trans(alpha, &a, &x, beta, &mut got, m, n);
for j in 0..n {
assert_close(got[j], want[j], &format!("dgemv_trans alpha={alpha} beta={beta} [{j}]"));
}
}
}
#[test]
fn dger_matches_the_definition() {
let (m, n) = (2usize, 3usize);
let x = real_matrix(m, 1);
let y = real_matrix(1, n);
for (label, y) in [("generic", y.clone()), ("one column zero", vec![y[0], 0.0, y[2]])] {
let a0 = real_matrix(m, n);
let mut want = a0.clone();
for j in 0..n {
for i in 0..m {
want[i + j * m] += 1.5 * x[i] * y[j];
}
}
let mut got = a0.clone();
dger(1.5, &x, &y, &mut got, m, n);
for k in 0..m * n {
assert_close(got[k], want[k], &format!("dger {label} [{k}]"));
}
}
}
#[test]
fn zgemv_and_zgemv_trans_match_the_definition() {
let (m, n) = (2usize, 3usize);
let a = complex_matrix(m, n);
let alpha = cpx!(0.5, -1.5);
let beta = cpx!(2.0, 0.25);
let x = complex_matrix(n, 1);
let y0 = complex_matrix(m, 1);
let mut want = y0.clone();
for i in 0..m {
let mut sum = Complex64::new(0.0, 0.0);
for j in 0..n {
sum += a[i + j * m] * x[j];
}
want[i] = alpha * sum + beta * want[i];
}
let mut got = y0.clone();
zgemv(alpha, &a, &x, beta, &mut got, m, n);
for i in 0..m {
assert_close_cpx(got[i], want[i], &format!("zgemv [{i}]"));
}
let xt = complex_matrix(m, 1);
let yt0 = complex_matrix(n, 1);
let mut want = yt0.clone();
for j in 0..n {
let mut sum = Complex64::new(0.0, 0.0);
for i in 0..m {
sum += a[i + j * m] * xt[i];
}
want[j] = alpha * sum + beta * want[j];
}
let mut got = yt0.clone();
zgemv_trans(alpha, &a, &xt, beta, &mut got, m, n);
for j in 0..n {
assert_close_cpx(got[j], want[j], &format!("zgemv_trans [{j}]"));
}
}
#[test]
fn zger_matches_the_definition() {
let (m, n) = (2usize, 3usize);
let x = complex_matrix(m, 1);
let alpha = cpx!(1.0, -0.5);
let y = complex_matrix(1, n);
for (label, y) in [("generic", y.clone()), ("one column zero", vec![y[0], cpx!(0.0, 0.0), y[2]])] {
let a0 = complex_matrix(m, n);
let mut want = a0.clone();
for j in 0..n {
for i in 0..m {
want[i + j * m] += alpha * x[i] * y[j];
}
}
let mut got = a0.clone();
zger(alpha, &x, &y, &mut got, m, n);
for k in 0..m * n {
assert_close_cpx(got[k], want[k], &format!("zger {label} [{k}]"));
}
}
}
#[test]
fn zgemm_matches_the_definition() {
let (m, n, k) = (2usize, 3usize, 4usize);
let a = complex_matrix(m, k);
let b = complex_matrix(k, n);
let alpha = cpx!(0.5, -1.5);
let beta = cpx!(-2.0, 0.25);
let c0 = complex_matrix(m, n);
let mut want = c0.clone();
for j in 0..n {
for i in 0..m {
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[i + l * m] * b[l + j * k];
}
let idx = i + j * m;
want[idx] = alpha * sum + beta * want[idx];
}
}
let mut got = c0.clone();
zgemm(alpha, &a, &b, beta, &mut got, m, n, k);
for idx in 0..m * n {
assert_close_cpx(got[idx], want[idx], &format!("zgemm [{idx}]"));
}
}
#[test]
fn zsyrk_routines_match_the_definition() {
let (n, k) = (3usize, 4usize);
let alpha = cpx!(2.5, -1.0);
let beta = cpx!(-1.5, 0.25);
let c0 = complex_matrix(n, n);
for transposed in [false, true] {
let a = complex_matrix(if transposed { k } else { n }, if transposed { n } else { k });
for upper in [false, true] {
let mut want = c0.clone();
for j in 0..n {
for i in 0..n {
let in_triangle = if upper { i <= j } else { i >= j };
if !in_triangle {
continue;
}
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += if transposed {
a[l + i * k] * a[l + j * k]
} else {
a[i + l * n] * a[j + l * n]
};
}
let idx = i + j * n;
want[idx] = alpha * sum + beta * want[idx];
}
}
let mut got = c0.clone();
if transposed {
zsyrk_rank(alpha, &a, beta, &mut got, n, k, upper);
} else {
zsyrk(alpha, &a, beta, &mut got, n, k, upper);
}
for j in 0..n {
for i in 0..n {
let idx = i + j * n;
assert_close_cpx(got[idx], want[idx], &format!("zsyrk transposed={transposed} upper={upper} ({i},{j})"));
let in_triangle = if upper { i <= j } else { i >= j };
if !in_triangle {
assert_eq!(got[idx], c0[idx], "the untaken triangle must be left alone");
}
}
}
}
}
}
#[test]
fn dsyrk_rank_transposed_matches_the_definition() {
let (n, k) = (3usize, 4usize);
let a = real_matrix(k, n);
let (alpha, beta) = (2.5, -1.5);
let c0 = real_matrix(n, n);
for upper in [false, true] {
let mut want = c0.clone();
for j in 0..n {
for i in 0..n {
let in_triangle = if upper { i <= j } else { i >= j };
if !in_triangle {
continue;
}
let mut sum = 0.0;
for l in 0..k {
sum += a[l + i * k] * a[l + j * k];
}
let idx = i + j * n;
want[idx] = alpha * sum + beta * want[idx];
}
}
let mut got = c0.clone();
dsyrk_rank(alpha, &a, beta, &mut got, n, k, upper, true);
for idx in 0..n * n {
assert_close(got[idx], want[idx], &format!("dsyrk_rank upper={upper} [{idx}]"));
}
let mut via_rank = c0.clone();
let mut via_syrk = c0.clone();
let a_nk = real_matrix(n, k);
dsyrk_rank(alpha, &a_nk, beta, &mut via_rank, n, k, upper, false);
dsyrk(alpha, &a_nk, beta, &mut via_syrk, n, k, upper);
assert_eq!(via_rank, via_syrk, "dsyrk_rank(transposed = false) must forward to dsyrk");
}
}
#[test]
fn zherk_matches_the_definition() {
let (n, k) = (3usize, 4usize);
let a = complex_matrix(n, k);
let (alpha, beta) = (2.5, -1.5);
let c0 = complex_matrix(n, n);
for upper in [false, true] {
let mut want = c0.clone();
for j in 0..n {
for i in 0..n {
let in_triangle = if upper { i <= j } else { i >= j };
if !in_triangle {
continue;
}
let mut sum = Complex64::new(0.0, 0.0);
for l in 0..k {
sum += a[i + l * n] * a[j + l * n].conj();
}
let idx = i + j * n;
let mut v = Complex64::new(alpha * sum.re, alpha * sum.im) + Complex64::new(beta * want[idx].re, beta * want[idx].im);
if i == j {
v.im = 0.0;
}
want[idx] = v;
}
}
let mut got = c0.clone();
zherk(alpha, &a, beta, &mut got, n, k, upper);
for j in 0..n {
for i in 0..n {
let idx = i + j * n;
let in_triangle = if upper { i <= j } else { i >= j };
if in_triangle {
assert_close_cpx(got[idx], want[idx], &format!("zherk upper={upper} ({i},{j})"));
if i == j {
assert_eq!(got[idx].im, 0.0, "the Hermitian diagonal must be exactly real");
}
} else {
assert_eq!(got[idx], c0[idx], "the untaken triangle must be left alone");
}
}
}
}
}
#[test]
fn zlange_matches_the_definition() {
let (m, n) = (2usize, 3usize);
let a = complex_matrix(m, n);
let one = (0..n).map(|j| (0..m).map(|i| a[i + j * m].norm()).sum::<f64>()).fold(0.0f64, f64::max);
let inf = (0..m).map(|i| (0..n).map(|j| a[i + j * m].norm()).sum::<f64>()).fold(0.0f64, f64::max);
let frob = a.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt();
let max = a.iter().map(|v| v.norm()).fold(0.0f64, f64::max);
for norm in ['1', 'O', 'o', 'I', 'i', 'F', 'f', 'E', 'e', 'M', 'm'] {
let want = match norm {
'1' | 'O' | 'o' => one,
'I' | 'i' => inf,
'F' | 'f' | 'E' | 'e' => frob,
_ => max,
};
assert_close(zlange(norm, &a, m, n), want, &format!("zlange '{norm}'"));
}
}
#[test]
#[should_panic(expected = "unrecognized norm")]
fn zlange_rejects_an_unknown_norm() {
zlange('X', &[cpx!(1.0, 0.0)], 1, 1);
}
}
#[cfg(test)]
mod triangular_tests {
use super::*;
fn assert_close(got: &[f64], want: &[f64], tol: f64) {
assert_eq!(got.len(), want.len());
for (i, (g, w)) in got.iter().zip(want.iter()).enumerate() {
assert!((g - w).abs() <= tol * (1.0 + w.abs()), "entry {i}: {g} vs {w}");
}
}
fn matvec(a: &[f64], x: &[f64], n: usize) -> Vec<f64> {
(0..n).map(|i| (0..n).map(|k| a[i + k * n] * x[k]).sum()).collect()
}
fn matmul(alpha: f64, a: &[f64], b: &[f64], m: usize, n: usize, k: usize) -> Vec<f64> {
let mut c = vec![0.0; m * n];
for j in 0..n {
for i in 0..m {
for l in 0..k {
c[i + j * m] += alpha * a[i + l * m] * b[l + j * k];
}
}
}
c
}
fn lower3() -> Vec<f64> {
vec![2.0, 1.0, 4.0, 0.0, 3.0, 5.0, 0.0, 0.0, 6.0]
}
fn upper3() -> Vec<f64> {
vec![2.0, 0.0, 0.0, 1.0, 3.0, 0.0, 4.0, 5.0, 6.0]
}
#[test]
fn dtrsv_solves_lower_and_upper() {
let x_exact = [1.0, -2.0, 0.5];
let mut b = matvec(&lower3(), &x_exact, 3);
dtrsv(&lower3(), &mut b, 3, true, false);
assert_close(&b, &x_exact, 1e-14);
let mut b = matvec(&upper3(), &x_exact, 3);
dtrsv(&upper3(), &mut b, 3, false, false);
assert_close(&b, &x_exact, 1e-14);
}
#[test]
fn dtrsv_ignores_a_unit_diagonal() {
let a = [7.0, 1.0, 4.0, 0.0, 7.0, 5.0, 0.0, 0.0, 7.0];
let x_exact = [1.0, -2.0, 0.5];
let unit_a = [1.0, 1.0, 4.0, 0.0, 1.0, 5.0, 0.0, 0.0, 1.0];
let mut b = matvec(&unit_a, &x_exact, 3);
dtrsv(&a, &mut b, 3, true, true);
assert_close(&b, &x_exact, 1e-14);
}
#[test]
fn dtrsv_transpose_solves_the_transposed_system() {
let x_exact = [1.0, -2.0, 0.5];
let at = {
let a = lower3();
let mut at = vec![0.0; 9];
for j in 0..3 {
for i in 0..3 {
at[i + j * 3] = a[j + i * 3];
}
}
at
};
let mut b = matvec(&at, &x_exact, 3);
dtrsv_transpose(&lower3(), &mut b, 3, true, false);
assert_close(&b, &x_exact, 1e-14);
}
#[test]
fn dtrsm_solves_both_sides() {
let alpha = 2.0;
let a = lower3();
let x_exact = [1.0, 2.0, 3.0, -1.0, 0.5, 2.0];
let mut b = matmul(1.0, &a, &x_exact, 3, 2, 3);
dtrsm(alpha, &a, &mut b, 3, 2, true, true, false);
let want: Vec<f64> = x_exact.iter().map(|v| alpha * v).collect();
assert_close(&b, &want, 1e-14);
let x_exact = [1.0, 4.0, 2.0, 5.0, 3.0, 6.0];
let mut b = matmul(1.0, &x_exact, &a, 2, 3, 3);
dtrsm(alpha, &a, &mut b, 2, 3, false, true, false);
let want: Vec<f64> = x_exact.iter().map(|v| alpha * v).collect();
assert_close(&b, &want, 1e-14);
}
#[test]
fn ztrsv_and_ztrsm_solve_complex_systems() {
let a = [
Complex64::new(1.0, 1.0),
Complex64::new(2.0, -1.0),
Complex64::new(0.0, 0.0),
Complex64::new(3.0, 0.5),
];
let x_exact = [Complex64::new(1.0, -2.0), Complex64::new(0.5, 1.0)];
let mut b = [Complex64::new(0.0, 0.0); 2];
for i in 0..2 {
b[i] = (0..2).map(|k| a[i + k * 2] * x_exact[k]).sum();
}
ztrsv(&a, &mut b, 2, true, false);
for i in 0..2 {
assert!((b[i] - x_exact[i]).norm() < 1e-14);
}
let alpha = Complex64::new(1.5, -0.5);
let x_exact = [
Complex64::new(1.0, 0.0),
Complex64::new(2.0, 1.0),
Complex64::new(-1.0, 0.5),
Complex64::new(0.0, 2.0),
];
let mut b = [Complex64::new(0.0, 0.0); 4];
for j in 0..2 {
for i in 0..2 {
b[i + j * 2] = (0..2).map(|k| a[i + k * 2] * x_exact[k + j * 2]).sum();
}
}
ztrsm(alpha, &a, &mut b, 2, 2, true, true, false);
for i in 0..4 {
assert!((b[i] - alpha * x_exact[i]).norm() < 1e-14);
}
}
#[test]
fn empty_systems_are_noops() {
dtrsv(&[], &mut [], 0, true, false);
dtrsv(&[], &mut [], 0, false, false);
dtrsv_transpose(&[], &mut [], 0, true, false);
dtrsm(1.0, &[], &mut [], 0, 0, true, true, false);
ztrsv(&[], &mut [], 0, true, false);
ztrsm(Complex64::new(1.0, 0.0), &[], &mut [], 0, 0, true, true, false);
}
#[test]
#[should_panic(expected = "dtrsv: x must be n")]
fn dtrsv_rejects_a_wrong_rhs_length() {
dtrsv(&[1.0], &mut [1.0, 2.0], 1, true, false);
}
}