Skip to main content

oxiblas_ndarray/
blas.rs

1//! BLAS operations on ndarray types.
2//!
3//! This module provides BLAS Level 1, 2, and 3 operations directly on
4//! ndarray types, using OxiBLAS as the backend.
5
6use crate::conversions::array2_to_mat;
7use ndarray::{Array1, Array2, ArrayView1, ShapeBuilder};
8use num_complex::{Complex32, Complex64};
9use oxiblas_blas::level1::{asum, axpy, dot, dotc_c32, dotc_c64, dotu_c32, dotu_c64, nrm2, scal};
10use oxiblas_blas::level2::{GemvTrans, gemv as blas_gemv};
11use oxiblas_blas::level3::{GemmKernel, gemm as blas_gemm};
12use oxiblas_core::scalar::Field;
13use oxiblas_matrix::Mat;
14
15// =============================================================================
16// BLAS Level 1: Vector-Vector Operations
17// =============================================================================
18
19/// Computes the dot product of two 1D arrays.
20///
21/// # Arguments
22/// * `x` - First vector
23/// * `y` - Second vector
24///
25/// # Returns
26/// The dot product x·y
27///
28/// # Panics
29/// Panics if vectors have different lengths.
30pub fn dot_ndarray<T: Field>(x: &Array1<T>, y: &Array1<T>) -> T {
31    assert_eq!(x.len(), y.len(), "Vector lengths must match");
32
33    // Try to get contiguous slices for efficient computation
34    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice()) {
35        dot(x_slice, y_slice)
36    } else {
37        // Non-contiguous: convert to contiguous first
38        let x_vec: Vec<T> = x.iter().cloned().collect();
39        let y_vec: Vec<T> = y.iter().cloned().collect();
40        dot(&x_vec, &y_vec)
41    }
42}
43
44/// Computes the dot product of two array views.
45pub fn dot_view<T: Field>(x: &ArrayView1<T>, y: &ArrayView1<T>) -> T {
46    assert_eq!(x.len(), y.len(), "Vector lengths must match");
47
48    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice()) {
49        dot(x_slice, y_slice)
50    } else {
51        let x_vec: Vec<T> = x.iter().cloned().collect();
52        let y_vec: Vec<T> = y.iter().cloned().collect();
53        dot(&x_vec, &y_vec)
54    }
55}
56
57// =============================================================================
58// Complex Dot Products
59// =============================================================================
60
61/// Computes the conjugate dot product of two Complex64 vectors (ZDOTC).
62///
63/// x^H · y = Σ conj(x\[i\]) * y\[i\]
64///
65/// This is the standard inner product for complex vector spaces.
66///
67/// # Arguments
68/// * `x` - First complex vector (will be conjugated)
69/// * `y` - Second complex vector
70///
71/// # Returns
72/// The conjugate dot product
73///
74/// # Panics
75/// Panics if vectors have different lengths.
76///
77/// # Example
78/// ```
79/// use oxiblas_ndarray::blas::dotc_c64_ndarray;
80/// use ndarray::array;
81/// use num_complex::Complex64;
82///
83/// let x = array![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
84/// let y = array![Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)];
85/// let result = dotc_c64_ndarray(&x, &y);
86/// // conj(1+2i)*(5+6i) + conj(3+4i)*(7+8i) = (1-2i)(5+6i) + (3-4i)(7+8i)
87/// // = (5+12) + (6-10)i + (21+32) + (24-28)i = 70 - 8i
88/// assert!((result.re - 70.0).abs() < 1e-10);
89/// assert!((result.im - (-8.0)).abs() < 1e-10);
90/// ```
91pub fn dotc_c64_ndarray(x: &Array1<Complex64>, y: &Array1<Complex64>) -> Complex64 {
92    assert_eq!(x.len(), y.len(), "Vector lengths must match");
93
94    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice()) {
95        dotc_c64(x_slice, y_slice)
96    } else {
97        let x_vec: Vec<Complex64> = x.iter().copied().collect();
98        let y_vec: Vec<Complex64> = y.iter().copied().collect();
99        dotc_c64(&x_vec, &y_vec)
100    }
101}
102
103/// Computes the conjugate dot product of two Complex32 vectors (CDOTC).
104///
105/// x^H · y = Σ conj(x\[i\]) * y\[i\]
106///
107/// # Panics
108/// Panics if vectors have different lengths.
109pub fn dotc_c32_ndarray(x: &Array1<Complex32>, y: &Array1<Complex32>) -> Complex32 {
110    assert_eq!(x.len(), y.len(), "Vector lengths must match");
111
112    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice()) {
113        dotc_c32(x_slice, y_slice)
114    } else {
115        let x_vec: Vec<Complex32> = x.iter().copied().collect();
116        let y_vec: Vec<Complex32> = y.iter().copied().collect();
117        dotc_c32(&x_vec, &y_vec)
118    }
119}
120
121/// Computes the unconjugated dot product of two Complex64 vectors (ZDOTU).
122///
123/// x · y = Σ x\[i\] * y\[i\]
124///
125/// Note: This is the bilinear form, not the standard inner product.
126/// For the standard inner product (sesquilinear), use `dotc_c64_ndarray`.
127///
128/// # Panics
129/// Panics if vectors have different lengths.
130pub fn dotu_c64_ndarray(x: &Array1<Complex64>, y: &Array1<Complex64>) -> Complex64 {
131    assert_eq!(x.len(), y.len(), "Vector lengths must match");
132
133    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice()) {
134        dotu_c64(x_slice, y_slice)
135    } else {
136        let x_vec: Vec<Complex64> = x.iter().copied().collect();
137        let y_vec: Vec<Complex64> = y.iter().copied().collect();
138        dotu_c64(&x_vec, &y_vec)
139    }
140}
141
142/// Computes the unconjugated dot product of two Complex32 vectors (CDOTU).
143///
144/// x · y = Σ x\[i\] * y\[i\]
145///
146/// # Panics
147/// Panics if vectors have different lengths.
148pub fn dotu_c32_ndarray(x: &Array1<Complex32>, y: &Array1<Complex32>) -> Complex32 {
149    assert_eq!(x.len(), y.len(), "Vector lengths must match");
150
151    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice()) {
152        dotu_c32(x_slice, y_slice)
153    } else {
154        let x_vec: Vec<Complex32> = x.iter().copied().collect();
155        let y_vec: Vec<Complex32> = y.iter().copied().collect();
156        dotu_c32(&x_vec, &y_vec)
157    }
158}
159
160/// Computes the Euclidean norm of a Complex64 vector.
161///
162/// ||x||_2 = sqrt(Σ |x\[i\]|²) = sqrt(Σ (x\[i\].re² + x\[i\].im²))
163///
164/// This is equivalent to sqrt(x^H · x).
165///
166/// The real and imaginary components are treated as a flat real vector and
167/// passed to the numerically stable, scaled (LASSQ-style) [`nrm2`] used by
168/// the BLAS Level-1 norm routines, so this never overflows to infinity for
169/// vectors whose true norm is representable but whose naive sum of squares
170/// would overflow (e.g. entries with magnitude around `1e200`).
171pub fn nrm2_c64_ndarray(x: &Array1<Complex64>) -> f64 {
172    let mut components = Vec::with_capacity(x.len() * 2);
173    for xi in x.iter() {
174        components.push(xi.re);
175        components.push(xi.im);
176    }
177    nrm2(&components)
178}
179
180/// Computes the Euclidean norm of a Complex32 vector.
181///
182/// ||x||_2 = sqrt(Σ |x\[i\]|²)
183///
184/// See [`nrm2_c64_ndarray`]: uses the same scaled (LASSQ-style) algorithm
185/// via [`nrm2`] to avoid overflow/underflow for extreme-magnitude entries.
186pub fn nrm2_c32_ndarray(x: &Array1<Complex32>) -> f32 {
187    let mut components = Vec::with_capacity(x.len() * 2);
188    for xi in x.iter() {
189        components.push(xi.re);
190        components.push(xi.im);
191    }
192    nrm2(&components)
193}
194
195/// Computes the L1 norm of a Complex64 vector (sum of absolute values).
196///
197/// ||x||_1 = Σ |x\[i\]|
198pub fn asum_c64_ndarray(x: &Array1<Complex64>) -> f64 {
199    let mut sum = 0.0f64;
200    for xi in x.iter() {
201        sum += xi.norm();
202    }
203    sum
204}
205
206/// Computes the L1 norm of a Complex32 vector (sum of absolute values).
207///
208/// ||x||_1 = Σ |x\[i\]|
209pub fn asum_c32_ndarray(x: &Array1<Complex32>) -> f32 {
210    let mut sum = 0.0f32;
211    for xi in x.iter() {
212        sum += xi.norm();
213    }
214    sum
215}
216
217/// Computes the Euclidean (L2) norm of a vector.
218///
219/// ||x||_2 = sqrt(sum(x_i^2))
220pub fn nrm2_ndarray<T: Field + oxiblas_core::scalar::Real>(x: &Array1<T>) -> T {
221    if let Some(slice) = x.as_slice() {
222        nrm2(slice)
223    } else {
224        let vec: Vec<T> = x.iter().cloned().collect();
225        nrm2(&vec)
226    }
227}
228
229/// Computes the L1 norm (sum of absolute values) of a vector.
230///
231/// ||x||_1 = sum(|x_i|)
232pub fn asum_ndarray<T: Field + oxiblas_core::scalar::Real>(x: &Array1<T>) -> T {
233    if let Some(slice) = x.as_slice() {
234        asum(slice)
235    } else {
236        let vec: Vec<T> = x.iter().cloned().collect();
237        asum(&vec)
238    }
239}
240
241/// Computes y = α·x + y (AXPY operation).
242///
243/// # Arguments
244/// * `alpha` - Scalar multiplier
245/// * `x` - Input vector
246/// * `y` - Output vector (modified in place)
247pub fn axpy_ndarray<T: Field>(alpha: T, x: &Array1<T>, y: &mut Array1<T>) {
248    assert_eq!(x.len(), y.len(), "Vector lengths must match");
249
250    if let (Some(x_slice), Some(y_slice)) = (x.as_slice(), y.as_slice_mut()) {
251        axpy(alpha, x_slice, y_slice);
252    } else {
253        // Non-contiguous: element-wise
254        for (yi, xi) in y.iter_mut().zip(x.iter()) {
255            *yi = alpha * (*xi) + *yi;
256        }
257    }
258}
259
260/// Scales a vector: x = α·x
261pub fn scal_ndarray<T: Field>(alpha: T, x: &mut Array1<T>) {
262    if let Some(slice) = x.as_slice_mut() {
263        scal(alpha, slice);
264    } else {
265        for xi in x.iter_mut() {
266            *xi = alpha * (*xi);
267        }
268    }
269}
270
271// =============================================================================
272// BLAS Level 2: Matrix-Vector Operations
273// =============================================================================
274
275/// Transpose options for matrix-vector operations.
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277pub enum Transpose {
278    /// No transpose
279    NoTrans,
280    /// Transpose
281    Trans,
282    /// Conjugate transpose (for complex types)
283    ConjTrans,
284}
285
286impl From<Transpose> for GemvTrans {
287    fn from(t: Transpose) -> Self {
288        match t {
289            Transpose::NoTrans => GemvTrans::NoTrans,
290            Transpose::Trans => GemvTrans::Trans,
291            Transpose::ConjTrans => GemvTrans::ConjTrans,
292        }
293    }
294}
295
296/// General matrix-vector multiplication: y = α·op(A)·x + β·y
297///
298/// # Arguments
299/// * `trans` - Whether to transpose A
300/// * `alpha` - Scalar multiplier for A·x
301/// * `a` - The matrix (m×n)
302/// * `x` - Input vector
303/// * `beta` - Scalar multiplier for y
304/// * `y` - Output vector (modified in place)
305///
306/// # Panics
307/// Panics if dimensions don't match.
308pub fn gemv_ndarray<T: Field + Clone>(
309    trans: Transpose,
310    alpha: T,
311    a: &Array2<T>,
312    x: &Array1<T>,
313    beta: T,
314    y: &mut Array1<T>,
315) where
316    T: bytemuck::Zeroable,
317{
318    let a_mat = array2_to_mat(a);
319    let (m, n) = a.dim();
320
321    // Determine expected dimensions
322    let (x_len, y_len) = match trans {
323        Transpose::NoTrans => (n, m),
324        Transpose::Trans | Transpose::ConjTrans => (m, n),
325    };
326
327    assert_eq!(x.len(), x_len, "x dimension mismatch");
328    assert_eq!(y.len(), y_len, "y dimension mismatch");
329
330    // Convert vectors to slices
331    let x_vec: Vec<T> = x.iter().cloned().collect();
332
333    if let Some(y_slice) = y.as_slice_mut() {
334        blas_gemv(trans.into(), alpha, a_mat.as_ref(), &x_vec, beta, y_slice);
335    } else {
336        let mut y_vec: Vec<T> = y.iter().cloned().collect();
337        blas_gemv(
338            trans.into(),
339            alpha,
340            a_mat.as_ref(),
341            &x_vec,
342            beta,
343            &mut y_vec,
344        );
345        for (yi, val) in y.iter_mut().zip(y_vec) {
346            *yi = val;
347        }
348    }
349}
350
351/// Matrix-vector multiplication: y = A·x
352///
353/// Simplified version of gemv with alpha=1, beta=0.
354pub fn matvec<T: Field + Clone>(a: &Array2<T>, x: &Array1<T>) -> Array1<T>
355where
356    T: bytemuck::Zeroable,
357{
358    let (m, _n) = a.dim();
359    let mut y = Array1::zeros(m);
360    gemv_ndarray(Transpose::NoTrans, T::one(), a, x, T::zero(), &mut y);
361    y
362}
363
364/// Transposed matrix-vector multiplication: y = A^T·x
365pub fn matvec_t<T: Field + Clone>(a: &Array2<T>, x: &Array1<T>) -> Array1<T>
366where
367    T: bytemuck::Zeroable,
368{
369    let (_m, n) = a.dim();
370    let mut y = Array1::zeros(n);
371    gemv_ndarray(Transpose::Trans, T::one(), a, x, T::zero(), &mut y);
372    y
373}
374
375// =============================================================================
376// BLAS Level 3: Matrix-Matrix Operations
377// =============================================================================
378
379/// General matrix-matrix multiplication: C = α·A·B + β·C
380///
381/// # Arguments
382/// * `alpha` - Scalar multiplier for A·B
383/// * `a` - Left matrix (m×k)
384/// * `b` - Right matrix (k×n)
385/// * `beta` - Scalar multiplier for C
386/// * `c` - Output matrix (m×n), modified in place
387///
388/// # Panics
389/// Panics if matrix dimensions are incompatible.
390pub fn gemm_ndarray<T: Field + GemmKernel>(
391    alpha: T,
392    a: &Array2<T>,
393    b: &Array2<T>,
394    beta: T,
395    c: &mut Array2<T>,
396) where
397    T: bytemuck::Zeroable + Clone,
398{
399    let a_mat = array2_to_mat(a);
400    let b_mat = array2_to_mat(b);
401
402    let (m, n) = c.dim();
403    let mut c_mat: Mat<T> = Mat::zeros(m, n);
404
405    // Copy existing C values if beta != 0
406    if beta != T::zero() {
407        for i in 0..m {
408            for j in 0..n {
409                c_mat[(i, j)] = c[[i, j]];
410            }
411        }
412    }
413
414    blas_gemm(alpha, a_mat.as_ref(), b_mat.as_ref(), beta, c_mat.as_mut());
415
416    // Copy result back
417    for i in 0..m {
418        for j in 0..n {
419            c[[i, j]] = c_mat[(i, j)];
420        }
421    }
422}
423
424/// Matrix multiplication: C = A·B
425///
426/// Simplified version that allocates a new output matrix.
427pub fn matmul<T: Field + GemmKernel>(a: &Array2<T>, b: &Array2<T>) -> Array2<T>
428where
429    T: bytemuck::Zeroable + Clone,
430{
431    let (m, k1) = a.dim();
432    let (k2, n) = b.dim();
433    assert_eq!(k1, k2, "Inner dimensions must match: {} vs {}", k1, k2);
434
435    let a_mat = array2_to_mat(a);
436    let b_mat = array2_to_mat(b);
437    let mut c_mat: Mat<T> = Mat::zeros(m, n);
438
439    blas_gemm(
440        T::one(),
441        a_mat.as_ref(),
442        b_mat.as_ref(),
443        T::zero(),
444        c_mat.as_mut(),
445    );
446
447    // Create output in column-major order for efficiency
448    Array2::from_shape_fn((m, n).f(), |(i, j)| c_mat[(i, j)])
449}
450
451/// Matrix multiplication returning row-major output.
452pub fn matmul_c<T: Field + GemmKernel>(a: &Array2<T>, b: &Array2<T>) -> Array2<T>
453where
454    T: bytemuck::Zeroable + Clone,
455{
456    let (m, k1) = a.dim();
457    let (k2, n) = b.dim();
458    assert_eq!(k1, k2, "Inner dimensions must match");
459
460    let a_mat = array2_to_mat(a);
461    let b_mat = array2_to_mat(b);
462    let mut c_mat: Mat<T> = Mat::zeros(m, n);
463
464    blas_gemm(
465        T::one(),
466        a_mat.as_ref(),
467        b_mat.as_ref(),
468        T::zero(),
469        c_mat.as_mut(),
470    );
471
472    // Row-major output
473    Array2::from_shape_fn((m, n), |(i, j)| c_mat[(i, j)])
474}
475
476/// In-place matrix multiplication: C = A·B (C is reallocated)
477pub fn matmul_into<T: Field + GemmKernel>(a: &Array2<T>, b: &Array2<T>, c: &mut Array2<T>)
478where
479    T: bytemuck::Zeroable + Clone,
480{
481    gemm_ndarray(T::one(), a, b, T::zero(), c);
482}
483
484// =============================================================================
485// Matrix Norms
486// =============================================================================
487
488/// Computes the Frobenius norm of a matrix.
489///
490/// ||A||_F = sqrt(sum(a_ij^2))
491///
492/// Delegates to the numerically stable, scaled (LASSQ-style) [`nrm2`] used
493/// by the BLAS Level-1 norm routines (tracks a running scale factor and a
494/// sum-of-squares relative to that scale, combining them at the end as
495/// `scale * sqrt(sumsq)`), so this never overflows to infinity for matrices
496/// whose true Frobenius norm is representable but whose naive sum of
497/// squares would overflow.
498pub fn frobenius_norm<T: Field + oxiblas_core::scalar::Real>(a: &Array2<T>) -> T {
499    if let Some(slice) = a.as_slice() {
500        nrm2(slice)
501    } else {
502        // Non-contiguous (e.g. transposed/strided) layout: flatten first.
503        let vec: Vec<T> = a.iter().copied().collect();
504        nrm2(&vec)
505    }
506}
507
508/// Computes the 1-norm (maximum column sum) of a matrix.
509///
510/// For real types where `Real = T`.
511pub fn norm_1(a: &Array2<f64>) -> f64 {
512    let (nrows, ncols) = a.dim();
513    let mut max_sum = 0.0f64;
514
515    for j in 0..ncols {
516        let mut col_sum = 0.0f64;
517        for i in 0..nrows {
518            col_sum += a[[i, j]].abs();
519        }
520        if col_sum > max_sum {
521            max_sum = col_sum;
522        }
523    }
524
525    max_sum
526}
527
528/// Computes the infinity-norm (maximum row sum) of a matrix.
529///
530/// For real types where `Real = T`.
531pub fn norm_inf(a: &Array2<f64>) -> f64 {
532    let (nrows, ncols) = a.dim();
533    let mut max_sum = 0.0f64;
534
535    for i in 0..nrows {
536        let mut row_sum = 0.0f64;
537        for j in 0..ncols {
538            row_sum += a[[i, j]].abs();
539        }
540        if row_sum > max_sum {
541            max_sum = row_sum;
542        }
543    }
544
545    max_sum
546}
547
548/// Computes the maximum absolute element of a matrix.
549///
550/// For real types where `Real = T`.
551pub fn norm_max(a: &Array2<f64>) -> f64 {
552    let mut max_val = 0.0f64;
553    for val in a.iter() {
554        let abs_val = val.abs();
555        if abs_val > max_val {
556            max_val = abs_val;
557        }
558    }
559    max_val
560}
561
562// =============================================================================
563// Additional Operations
564// =============================================================================
565
566/// Computes the trace of a square matrix.
567pub fn trace<T: Field>(a: &Array2<T>) -> T {
568    let (nrows, ncols) = a.dim();
569    assert_eq!(nrows, ncols, "Matrix must be square for trace");
570
571    let mut sum = T::zero();
572    for i in 0..nrows {
573        sum += a[[i, i]];
574    }
575    sum
576}
577
578/// Transposes a matrix.
579pub fn transpose<T: Clone>(a: &Array2<T>) -> Array2<T> {
580    a.t().to_owned()
581}
582
583/// Creates an identity matrix.
584pub fn eye<T: Field>(n: usize) -> Array2<T>
585where
586    T: Clone,
587{
588    let mut result = Array2::zeros((n, n));
589    for i in 0..n {
590        result[[i, i]] = T::one();
591    }
592    result
593}
594
595/// Creates an identity matrix in column-major order.
596pub fn eye_f<T: Field>(n: usize) -> Array2<T>
597where
598    T: Clone,
599{
600    let mut result: Array2<T> = Array2::from_shape_fn((n, n).f(), |_| T::zero());
601    for i in 0..n {
602        result[[i, i]] = T::one();
603    }
604    result
605}
606
607// =============================================================================
608// Complex Matrix Operations
609// =============================================================================
610
611/// Computes the Hermitian (conjugate) transpose of a Complex64 matrix.
612///
613/// Returns A^H where (A^H)\[i,j\] = conj(A\[j,i\])
614///
615/// # Example
616/// ```
617/// use oxiblas_ndarray::blas::conj_transpose_c64;
618/// use ndarray::array;
619/// use num_complex::Complex64;
620///
621/// let a = array![
622///     [Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
623///     [Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)]
624/// ];
625/// let ah = conj_transpose_c64(&a);
626/// // ah[0,0] = conj(a[0,0]) = 1 - 2i
627/// assert!((ah[[0, 0]].re - 1.0).abs() < 1e-10);
628/// assert!((ah[[0, 0]].im - (-2.0)).abs() < 1e-10);
629/// // ah[0,1] = conj(a[1,0]) = 5 - 6i
630/// assert!((ah[[0, 1]].re - 5.0).abs() < 1e-10);
631/// assert!((ah[[0, 1]].im - (-6.0)).abs() < 1e-10);
632/// ```
633pub fn conj_transpose_c64(a: &Array2<Complex64>) -> Array2<Complex64> {
634    let (m, n) = a.dim();
635    Array2::from_shape_fn((n, m), |(i, j)| a[[j, i]].conj())
636}
637
638/// Computes the Hermitian (conjugate) transpose of a Complex32 matrix.
639///
640/// Returns A^H where (A^H)\[i,j\] = conj(A\[j,i\])
641pub fn conj_transpose_c32(a: &Array2<Complex32>) -> Array2<Complex32> {
642    let (m, n) = a.dim();
643    Array2::from_shape_fn((n, m), |(i, j)| a[[j, i]].conj())
644}
645
646/// Computes the Frobenius norm of a Complex64 matrix.
647///
648/// ||A||_F = sqrt(Σ |a\[i,j\]|²) = sqrt(Σ (a\[i,j\].re² + a\[i,j\].im²))
649///
650/// This is equivalent to sqrt(trace(A^H * A)).
651///
652/// Same scaled (LASSQ-style) [`nrm2`] delegation as [`nrm2_c64_ndarray`], to
653/// avoid overflow for matrices with extreme-magnitude entries.
654pub fn frobenius_norm_c64(a: &Array2<Complex64>) -> f64 {
655    let mut components = Vec::with_capacity(a.len() * 2);
656    for val in a.iter() {
657        components.push(val.re);
658        components.push(val.im);
659    }
660    nrm2(&components)
661}
662
663/// Computes the Frobenius norm of a Complex32 matrix.
664///
665/// ||A||_F = sqrt(Σ |a\[i,j\]|²)
666///
667/// Same scaled (LASSQ-style) [`nrm2`] delegation as [`nrm2_c32_ndarray`], to
668/// avoid overflow for matrices with extreme-magnitude entries.
669pub fn frobenius_norm_c32(a: &Array2<Complex32>) -> f32 {
670    let mut components = Vec::with_capacity(a.len() * 2);
671    for val in a.iter() {
672        components.push(val.re);
673        components.push(val.im);
674    }
675    nrm2(&components)
676}
677
678/// Computes the 1-norm (maximum column sum of absolute values) of a Complex64 matrix.
679///
680/// ||A||_1 = max_j Σ_i |a\[i,j\]|
681pub fn norm_1_c64(a: &Array2<Complex64>) -> f64 {
682    let (nrows, ncols) = a.dim();
683    let mut max_sum = 0.0f64;
684
685    for j in 0..ncols {
686        let mut col_sum = 0.0f64;
687        for i in 0..nrows {
688            col_sum += a[[i, j]].norm();
689        }
690        if col_sum > max_sum {
691            max_sum = col_sum;
692        }
693    }
694
695    max_sum
696}
697
698/// Computes the 1-norm of a Complex32 matrix.
699pub fn norm_1_c32(a: &Array2<Complex32>) -> f32 {
700    let (nrows, ncols) = a.dim();
701    let mut max_sum = 0.0f32;
702
703    for j in 0..ncols {
704        let mut col_sum = 0.0f32;
705        for i in 0..nrows {
706            col_sum += a[[i, j]].norm();
707        }
708        if col_sum > max_sum {
709            max_sum = col_sum;
710        }
711    }
712
713    max_sum
714}
715
716/// Computes the infinity-norm (maximum row sum of absolute values) of a Complex64 matrix.
717///
718/// ||A||_∞ = max_i Σ_j |a\[i,j\]|
719pub fn norm_inf_c64(a: &Array2<Complex64>) -> f64 {
720    let (nrows, ncols) = a.dim();
721    let mut max_sum = 0.0f64;
722
723    for i in 0..nrows {
724        let mut row_sum = 0.0f64;
725        for j in 0..ncols {
726            row_sum += a[[i, j]].norm();
727        }
728        if row_sum > max_sum {
729            max_sum = row_sum;
730        }
731    }
732
733    max_sum
734}
735
736/// Computes the infinity-norm of a Complex32 matrix.
737pub fn norm_inf_c32(a: &Array2<Complex32>) -> f32 {
738    let (nrows, ncols) = a.dim();
739    let mut max_sum = 0.0f32;
740
741    for i in 0..nrows {
742        let mut row_sum = 0.0f32;
743        for j in 0..ncols {
744            row_sum += a[[i, j]].norm();
745        }
746        if row_sum > max_sum {
747            max_sum = row_sum;
748        }
749    }
750
751    max_sum
752}
753
754/// Computes the maximum absolute element of a Complex64 matrix.
755///
756/// max |a\[i,j\]|
757pub fn norm_max_c64(a: &Array2<Complex64>) -> f64 {
758    let mut max_val = 0.0f64;
759    for val in a.iter() {
760        let abs_val = val.norm();
761        if abs_val > max_val {
762            max_val = abs_val;
763        }
764    }
765    max_val
766}
767
768/// Computes the maximum absolute element of a Complex32 matrix.
769pub fn norm_max_c32(a: &Array2<Complex32>) -> f32 {
770    let mut max_val = 0.0f32;
771    for val in a.iter() {
772        let abs_val = val.norm();
773        if abs_val > max_val {
774            max_val = abs_val;
775        }
776    }
777    max_val
778}
779
780/// Computes the trace of a Complex64 square matrix.
781pub fn trace_c64(a: &Array2<Complex64>) -> Complex64 {
782    let (nrows, ncols) = a.dim();
783    assert_eq!(nrows, ncols, "Matrix must be square for trace");
784
785    let mut sum = Complex64::new(0.0, 0.0);
786    for i in 0..nrows {
787        sum += a[[i, i]];
788    }
789    sum
790}
791
792/// Computes the trace of a Complex32 square matrix.
793pub fn trace_c32(a: &Array2<Complex32>) -> Complex32 {
794    let (nrows, ncols) = a.dim();
795    assert_eq!(nrows, ncols, "Matrix must be square for trace");
796
797    let mut sum = Complex32::new(0.0, 0.0);
798    for i in 0..nrows {
799        sum += a[[i, i]];
800    }
801    sum
802}
803
804/// Scales a Complex64 vector: x = α·x
805pub fn scal_c64_ndarray(alpha: Complex64, x: &mut Array1<Complex64>) {
806    for xi in x.iter_mut() {
807        *xi = alpha * (*xi);
808    }
809}
810
811/// Scales a Complex32 vector: x = α·x
812pub fn scal_c32_ndarray(alpha: Complex32, x: &mut Array1<Complex32>) {
813    for xi in x.iter_mut() {
814        *xi = alpha * (*xi);
815    }
816}
817
818/// AXPY operation for Complex64: y = α·x + y
819pub fn axpy_c64_ndarray(alpha: Complex64, x: &Array1<Complex64>, y: &mut Array1<Complex64>) {
820    assert_eq!(x.len(), y.len(), "Vector lengths must match");
821
822    for (yi, xi) in y.iter_mut().zip(x.iter()) {
823        *yi = alpha * (*xi) + *yi;
824    }
825}
826
827/// AXPY operation for Complex32: y = α·x + y
828pub fn axpy_c32_ndarray(alpha: Complex32, x: &Array1<Complex32>, y: &mut Array1<Complex32>) {
829    assert_eq!(x.len(), y.len(), "Vector lengths must match");
830
831    for (yi, xi) in y.iter_mut().zip(x.iter()) {
832        *yi = alpha * (*xi) + *yi;
833    }
834}
835
836/// Creates a Complex64 identity matrix.
837pub fn eye_c64(n: usize) -> Array2<Complex64> {
838    let mut result: Array2<Complex64> = Array2::from_elem((n, n), Complex64::new(0.0, 0.0));
839    for i in 0..n {
840        result[[i, i]] = Complex64::new(1.0, 0.0);
841    }
842    result
843}
844
845/// Creates a Complex32 identity matrix.
846pub fn eye_c32(n: usize) -> Array2<Complex32> {
847    let mut result: Array2<Complex32> = Array2::from_elem((n, n), Complex32::new(0.0, 0.0));
848    for i in 0..n {
849        result[[i, i]] = Complex32::new(1.0, 0.0);
850    }
851    result
852}
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857    use ndarray::array;
858
859    #[test]
860    fn test_dot_ndarray() {
861        let x = array![1.0f64, 2.0, 3.0];
862        let y = array![4.0f64, 5.0, 6.0];
863        let d = dot_ndarray(&x, &y);
864        assert!((d - 32.0).abs() < 1e-10);
865    }
866
867    #[test]
868    fn test_nrm2_ndarray() {
869        let x = array![3.0f64, 4.0];
870        let norm = nrm2_ndarray(&x);
871        assert!((norm - 5.0).abs() < 1e-10);
872    }
873
874    #[test]
875    fn test_asum_ndarray() {
876        let x = array![-1.0f64, 2.0, -3.0];
877        let sum = asum_ndarray(&x);
878        assert!((sum - 6.0).abs() < 1e-10);
879    }
880
881    #[test]
882    fn test_axpy_ndarray() {
883        let x = array![1.0f64, 2.0, 3.0];
884        let mut y = array![4.0f64, 5.0, 6.0];
885        axpy_ndarray(2.0, &x, &mut y);
886        assert!((y[0] - 6.0).abs() < 1e-10);
887        assert!((y[1] - 9.0).abs() < 1e-10);
888        assert!((y[2] - 12.0).abs() < 1e-10);
889    }
890
891    #[test]
892    fn test_scal_ndarray() {
893        let mut x = array![1.0f64, 2.0, 3.0];
894        scal_ndarray(2.0, &mut x);
895        assert!((x[0] - 2.0).abs() < 1e-10);
896        assert!((x[1] - 4.0).abs() < 1e-10);
897        assert!((x[2] - 6.0).abs() < 1e-10);
898    }
899
900    #[test]
901    fn test_gemv_notrans() {
902        let a = Array2::from_shape_fn((2, 3), |(i, j)| (i * 3 + j + 1) as f64);
903        let x = array![1.0f64, 1.0, 1.0];
904        let mut y = array![0.0f64, 0.0];
905
906        gemv_ndarray(Transpose::NoTrans, 1.0, &a, &x, 0.0, &mut y);
907
908        // y[0] = 1 + 2 + 3 = 6
909        // y[1] = 4 + 5 + 6 = 15
910        assert!((y[0] - 6.0).abs() < 1e-10);
911        assert!((y[1] - 15.0).abs() < 1e-10);
912    }
913
914    #[test]
915    fn test_gemv_trans() {
916        let a = Array2::from_shape_fn((2, 3), |(i, j)| (i * 3 + j + 1) as f64);
917        let x = array![1.0f64, 1.0];
918        let mut y = array![0.0f64, 0.0, 0.0];
919
920        gemv_ndarray(Transpose::Trans, 1.0, &a, &x, 0.0, &mut y);
921
922        // y[0] = 1 + 4 = 5
923        // y[1] = 2 + 5 = 7
924        // y[2] = 3 + 6 = 9
925        assert!((y[0] - 5.0).abs() < 1e-10);
926        assert!((y[1] - 7.0).abs() < 1e-10);
927        assert!((y[2] - 9.0).abs() < 1e-10);
928    }
929
930    #[test]
931    fn test_matvec() {
932        let a = Array2::from_shape_fn((2, 3), |(i, j)| (i * 3 + j + 1) as f64);
933        let x = array![1.0f64, 2.0, 3.0];
934        let y = matvec(&a, &x);
935
936        // y[0] = 1*1 + 2*2 + 3*3 = 14
937        // y[1] = 4*1 + 5*2 + 6*3 = 32
938        assert!((y[0] - 14.0).abs() < 1e-10);
939        assert!((y[1] - 32.0).abs() < 1e-10);
940    }
941
942    #[test]
943    fn test_matmul() {
944        let a = Array2::from_shape_fn((2, 3), |_| 1.0f64);
945        let b = Array2::from_shape_fn((3, 2), |_| 2.0f64);
946        let c = matmul(&a, &b);
947
948        assert_eq!(c.dim(), (2, 2));
949        for i in 0..2 {
950            for j in 0..2 {
951                assert!((c[[i, j]] - 6.0).abs() < 1e-10);
952            }
953        }
954    }
955
956    #[test]
957    fn test_gemm_ndarray() {
958        let a = Array2::from_shape_fn((2, 3), |_| 1.0f64);
959        let b = Array2::from_shape_fn((3, 2), |_| 2.0f64);
960        let mut c = Array2::from_shape_fn((2, 2), |_| 1.0f64);
961
962        gemm_ndarray(1.0, &a, &b, 1.0, &mut c);
963
964        // C = 1 * A * B + 1 * C = 6 + 1 = 7
965        for i in 0..2 {
966            for j in 0..2 {
967                assert!((c[[i, j]] - 7.0).abs() < 1e-10);
968            }
969        }
970    }
971
972    #[test]
973    fn test_frobenius_norm() {
974        let a = array![[1.0f64, 2.0], [3.0, 4.0]];
975        let norm = frobenius_norm(&a);
976        // sqrt(1 + 4 + 9 + 16) = sqrt(30)
977        assert!((norm - 30.0f64.sqrt()).abs() < 1e-10);
978    }
979
980    #[test]
981    fn test_frobenius_norm_overflow_prevention() {
982        // Squaring 1e200 gives 1e400, which overflows f64::MAX (1.8e308),
983        // but the true Frobenius norm (2e200) is well within range. The
984        // naive sum-of-squares implementation would return `inf` here.
985        let large = 1e200f64;
986        let a = array![[large, large], [large, large]];
987        let norm = frobenius_norm(&a);
988        let expected = 2.0 * large; // sqrt(4) * large
989        assert!(norm.is_finite(), "norm should be finite, got {}", norm);
990        assert!(
991            (norm - expected).abs() / expected < 1e-10,
992            "expected {}, got {}",
993            expected,
994            norm
995        );
996    }
997
998    #[test]
999    fn test_norm_1() {
1000        let a = array![[1.0f64, 2.0], [3.0, 4.0]];
1001        let norm = norm_1(&a);
1002        // max(1+3, 2+4) = max(4, 6) = 6
1003        assert!((norm - 6.0).abs() < 1e-10);
1004    }
1005
1006    #[test]
1007    fn test_norm_inf() {
1008        let a = array![[1.0f64, 2.0], [3.0, 4.0]];
1009        let norm = norm_inf(&a);
1010        // max(1+2, 3+4) = max(3, 7) = 7
1011        assert!((norm - 7.0).abs() < 1e-10);
1012    }
1013
1014    #[test]
1015    fn test_trace() {
1016        let a = array![[1.0f64, 2.0], [3.0, 4.0]];
1017        let tr = trace(&a);
1018        assert!((tr - 5.0).abs() < 1e-10);
1019    }
1020
1021    #[test]
1022    fn test_eye() {
1023        let id: Array2<f64> = eye(3);
1024        for i in 0..3 {
1025            for j in 0..3 {
1026                if i == j {
1027                    assert!((id[[i, j]] - 1.0).abs() < 1e-15);
1028                } else {
1029                    assert!(id[[i, j]].abs() < 1e-15);
1030                }
1031            }
1032        }
1033    }
1034
1035    #[test]
1036    fn test_transpose() {
1037        let a = array![[1.0f64, 2.0, 3.0], [4.0, 5.0, 6.0]];
1038        let at = transpose(&a);
1039        assert_eq!(at.dim(), (3, 2));
1040        assert!((at[[0, 0]] - 1.0).abs() < 1e-15);
1041        assert!((at[[2, 1]] - 6.0).abs() < 1e-15);
1042    }
1043
1044    // =========================================================================
1045    // Complex Number Tests
1046    // =========================================================================
1047
1048    #[test]
1049    fn test_dotc_c64_ndarray() {
1050        // x = [1+2i, 3+4i], y = [5+6i, 7+8i]
1051        // conj(x) * y = (1-2i)(5+6i) + (3-4i)(7+8i)
1052        //             = (5+12) + (6-10)i + (21+32) + (24-28)i
1053        //             = 70 - 8i
1054        let x = array![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
1055        let y = array![Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)];
1056
1057        let result = dotc_c64_ndarray(&x, &y);
1058        assert!((result.re - 70.0).abs() < 1e-10);
1059        assert!((result.im - (-8.0)).abs() < 1e-10);
1060    }
1061
1062    #[test]
1063    fn test_dotc_c32_ndarray() {
1064        let x = array![Complex32::new(1.0, 2.0), Complex32::new(3.0, 4.0)];
1065        let y = array![Complex32::new(5.0, 6.0), Complex32::new(7.0, 8.0)];
1066
1067        let result = dotc_c32_ndarray(&x, &y);
1068        assert!((result.re - 70.0).abs() < 1e-5);
1069        assert!((result.im - (-8.0)).abs() < 1e-5);
1070    }
1071
1072    #[test]
1073    fn test_dotu_c64_ndarray() {
1074        // x = [1+2i, 3+4i], y = [5+6i, 7+8i]
1075        // x * y = (1+2i)(5+6i) + (3+4i)(7+8i)
1076        //       = (5-12) + (6+10)i + (21-32) + (24+28)i
1077        //       = -18 + 68i
1078        let x = array![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
1079        let y = array![Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)];
1080
1081        let result = dotu_c64_ndarray(&x, &y);
1082        assert!((result.re - (-18.0)).abs() < 1e-10);
1083        assert!((result.im - 68.0).abs() < 1e-10);
1084    }
1085
1086    #[test]
1087    fn test_dotu_c32_ndarray() {
1088        let x = array![Complex32::new(1.0, 2.0), Complex32::new(3.0, 4.0)];
1089        let y = array![Complex32::new(5.0, 6.0), Complex32::new(7.0, 8.0)];
1090
1091        let result = dotu_c32_ndarray(&x, &y);
1092        assert!((result.re - (-18.0)).abs() < 1e-5);
1093        assert!((result.im - 68.0).abs() < 1e-5);
1094    }
1095
1096    #[test]
1097    fn test_dotc_c64_self_inner_product() {
1098        // x^H * x should be real and equal to ||x||^2
1099        let x = array![
1100            Complex64::new(1.0, 2.0),
1101            Complex64::new(3.0, 4.0),
1102            Complex64::new(5.0, 6.0)
1103        ];
1104
1105        let result = dotc_c64_ndarray(&x, &x);
1106
1107        // Should be purely real
1108        assert!(result.im.abs() < 1e-10);
1109
1110        // Should equal sum of |x_i|^2 = (1+4) + (9+16) + (25+36) = 5 + 25 + 61 = 91
1111        assert!((result.re - 91.0).abs() < 1e-10);
1112    }
1113
1114    #[test]
1115    fn test_nrm2_c64_ndarray() {
1116        // ||x||_2 = sqrt(sum(|x_i|^2))
1117        let x = array![Complex64::new(3.0, 4.0)]; // |3+4i| = 5
1118        let norm = nrm2_c64_ndarray(&x);
1119        assert!((norm - 5.0).abs() < 1e-10);
1120
1121        let x = array![Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0)];
1122        let norm = nrm2_c64_ndarray(&x);
1123        // sqrt(1 + 1) = sqrt(2)
1124        assert!((norm - 2.0f64.sqrt()).abs() < 1e-10);
1125    }
1126
1127    #[test]
1128    fn test_nrm2_c32_ndarray() {
1129        let x = array![Complex32::new(3.0, 4.0)];
1130        let norm = nrm2_c32_ndarray(&x);
1131        assert!((norm - 5.0).abs() < 1e-5);
1132    }
1133
1134    #[test]
1135    fn test_nrm2_c64_ndarray_overflow_prevention() {
1136        // |x_i|^2 for a component around 1e200 would overflow when squared
1137        // naively (1e400 > f64::MAX), even though the true norm (2e200) is
1138        // representable.
1139        let large = 1e200f64;
1140        let x = array![Complex64::new(large, 0.0), Complex64::new(0.0, large)];
1141        let norm = nrm2_c64_ndarray(&x);
1142        let expected = 2.0f64.sqrt() * large;
1143        assert!(norm.is_finite(), "norm should be finite, got {}", norm);
1144        assert!(
1145            (norm - expected).abs() / expected < 1e-10,
1146            "expected {}, got {}",
1147            expected,
1148            norm
1149        );
1150    }
1151
1152    #[test]
1153    fn test_nrm2_c32_ndarray_overflow_prevention() {
1154        let large = 1e30f32; // near f32::MAX (3.4e38) once squared and summed
1155        let x = array![Complex32::new(large, 0.0), Complex32::new(0.0, large)];
1156        let norm = nrm2_c32_ndarray(&x);
1157        let expected = 2.0f32.sqrt() * large;
1158        assert!(norm.is_finite(), "norm should be finite, got {}", norm);
1159        assert!(
1160            (norm - expected).abs() / expected < 1e-5,
1161            "expected {}, got {}",
1162            expected,
1163            norm
1164        );
1165    }
1166
1167    #[test]
1168    fn test_asum_c64_ndarray() {
1169        // sum of |x_i|
1170        let x = array![Complex64::new(3.0, 4.0), Complex64::new(5.0, 12.0)];
1171        // |3+4i| = 5, |5+12i| = 13
1172        let sum = asum_c64_ndarray(&x);
1173        assert!((sum - 18.0).abs() < 1e-10);
1174    }
1175
1176    #[test]
1177    fn test_asum_c32_ndarray() {
1178        let x = array![Complex32::new(3.0, 4.0), Complex32::new(5.0, 12.0)];
1179        let sum = asum_c32_ndarray(&x);
1180        assert!((sum - 18.0).abs() < 1e-5);
1181    }
1182
1183    #[test]
1184    fn test_conj_transpose_c64() {
1185        let a = array![
1186            [Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
1187            [Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)]
1188        ];
1189
1190        let ah = conj_transpose_c64(&a);
1191        assert_eq!(ah.dim(), (2, 2));
1192
1193        // ah[0,0] = conj(a[0,0]) = 1-2i
1194        assert!((ah[[0, 0]].re - 1.0).abs() < 1e-10);
1195        assert!((ah[[0, 0]].im - (-2.0)).abs() < 1e-10);
1196
1197        // ah[0,1] = conj(a[1,0]) = 5-6i
1198        assert!((ah[[0, 1]].re - 5.0).abs() < 1e-10);
1199        assert!((ah[[0, 1]].im - (-6.0)).abs() < 1e-10);
1200
1201        // ah[1,0] = conj(a[0,1]) = 3-4i
1202        assert!((ah[[1, 0]].re - 3.0).abs() < 1e-10);
1203        assert!((ah[[1, 0]].im - (-4.0)).abs() < 1e-10);
1204
1205        // ah[1,1] = conj(a[1,1]) = 7-8i
1206        assert!((ah[[1, 1]].re - 7.0).abs() < 1e-10);
1207        assert!((ah[[1, 1]].im - (-8.0)).abs() < 1e-10);
1208    }
1209
1210    #[test]
1211    fn test_conj_transpose_c64_rectangular() {
1212        let a = array![
1213            [
1214                Complex64::new(1.0, 1.0),
1215                Complex64::new(2.0, 2.0),
1216                Complex64::new(3.0, 3.0)
1217            ],
1218            [
1219                Complex64::new(4.0, 4.0),
1220                Complex64::new(5.0, 5.0),
1221                Complex64::new(6.0, 6.0)
1222            ]
1223        ];
1224
1225        let ah = conj_transpose_c64(&a);
1226        assert_eq!(ah.dim(), (3, 2));
1227
1228        // ah[2,1] = conj(a[1,2]) = 6-6i
1229        assert!((ah[[2, 1]].re - 6.0).abs() < 1e-10);
1230        assert!((ah[[2, 1]].im - (-6.0)).abs() < 1e-10);
1231    }
1232
1233    #[test]
1234    fn test_frobenius_norm_c64() {
1235        let a = array![
1236            [Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0)],
1237            [Complex64::new(0.0, 1.0), Complex64::new(1.0, 0.0)]
1238        ];
1239        // |1|^2 + |i|^2 + |i|^2 + |1|^2 = 1 + 1 + 1 + 1 = 4
1240        let norm = frobenius_norm_c64(&a);
1241        assert!((norm - 2.0).abs() < 1e-10);
1242    }
1243
1244    #[test]
1245    fn test_frobenius_norm_c32() {
1246        let a = array![
1247            [Complex32::new(3.0, 4.0)] // |3+4i| = 5, |3+4i|^2 = 25
1248        ];
1249        let norm = frobenius_norm_c32(&a);
1250        assert!((norm - 5.0).abs() < 1e-5);
1251    }
1252
1253    #[test]
1254    fn test_frobenius_norm_c64_overflow_prevention() {
1255        let large = 1e200f64;
1256        let a = array![[Complex64::new(large, 0.0), Complex64::new(0.0, large)]];
1257        let norm = frobenius_norm_c64(&a);
1258        let expected = 2.0f64.sqrt() * large;
1259        assert!(norm.is_finite(), "norm should be finite, got {}", norm);
1260        assert!(
1261            (norm - expected).abs() / expected < 1e-10,
1262            "expected {}, got {}",
1263            expected,
1264            norm
1265        );
1266    }
1267
1268    #[test]
1269    fn test_frobenius_norm_c32_overflow_prevention() {
1270        let large = 1e30f32;
1271        let a = array![[Complex32::new(large, 0.0), Complex32::new(0.0, large)]];
1272        let norm = frobenius_norm_c32(&a);
1273        let expected = 2.0f32.sqrt() * large;
1274        assert!(norm.is_finite(), "norm should be finite, got {}", norm);
1275        assert!(
1276            (norm - expected).abs() / expected < 1e-5,
1277            "expected {}, got {}",
1278            expected,
1279            norm
1280        );
1281    }
1282
1283    #[test]
1284    fn test_norm_1_c64() {
1285        let a = array![
1286            [Complex64::new(3.0, 4.0), Complex64::new(0.0, 1.0)],
1287            [Complex64::new(0.0, 0.0), Complex64::new(5.0, 12.0)]
1288        ];
1289        // col 0: |3+4i| + |0| = 5 + 0 = 5
1290        // col 1: |i| + |5+12i| = 1 + 13 = 14
1291        let norm = norm_1_c64(&a);
1292        assert!((norm - 14.0).abs() < 1e-10);
1293    }
1294
1295    #[test]
1296    fn test_norm_inf_c64() {
1297        let a = array![
1298            [Complex64::new(3.0, 4.0), Complex64::new(0.0, 1.0)],
1299            [Complex64::new(0.0, 0.0), Complex64::new(5.0, 12.0)]
1300        ];
1301        // row 0: |3+4i| + |i| = 5 + 1 = 6
1302        // row 1: |0| + |5+12i| = 0 + 13 = 13
1303        let norm = norm_inf_c64(&a);
1304        assert!((norm - 13.0).abs() < 1e-10);
1305    }
1306
1307    #[test]
1308    fn test_norm_max_c64() {
1309        let a = array![
1310            [Complex64::new(1.0, 0.0), Complex64::new(3.0, 4.0)],
1311            [Complex64::new(5.0, 12.0), Complex64::new(0.0, 1.0)]
1312        ];
1313        // max(|1|, |3+4i|, |5+12i|, |i|) = max(1, 5, 13, 1) = 13
1314        let max = norm_max_c64(&a);
1315        assert!((max - 13.0).abs() < 1e-10);
1316    }
1317
1318    #[test]
1319    fn test_trace_c64() {
1320        let a = array![
1321            [Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
1322            [Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)]
1323        ];
1324        // trace = (1+2i) + (7+8i) = 8 + 10i
1325        let tr = trace_c64(&a);
1326        assert!((tr.re - 8.0).abs() < 1e-10);
1327        assert!((tr.im - 10.0).abs() < 1e-10);
1328    }
1329
1330    #[test]
1331    fn test_trace_c32() {
1332        let a = array![
1333            [Complex32::new(1.0, 2.0), Complex32::new(3.0, 4.0)],
1334            [Complex32::new(5.0, 6.0), Complex32::new(7.0, 8.0)]
1335        ];
1336        let tr = trace_c32(&a);
1337        assert!((tr.re - 8.0).abs() < 1e-5);
1338        assert!((tr.im - 10.0).abs() < 1e-5);
1339    }
1340
1341    #[test]
1342    fn test_scal_c64_ndarray() {
1343        let mut x = array![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
1344        let alpha = Complex64::new(2.0, 0.0);
1345        scal_c64_ndarray(alpha, &mut x);
1346
1347        assert!((x[0].re - 2.0).abs() < 1e-10);
1348        assert!((x[0].im - 4.0).abs() < 1e-10);
1349        assert!((x[1].re - 6.0).abs() < 1e-10);
1350        assert!((x[1].im - 8.0).abs() < 1e-10);
1351    }
1352
1353    #[test]
1354    fn test_scal_c64_ndarray_complex_alpha() {
1355        let mut x = array![Complex64::new(1.0, 0.0)];
1356        let alpha = Complex64::new(0.0, 1.0); // i
1357        scal_c64_ndarray(alpha, &mut x);
1358
1359        // i * 1 = i
1360        assert!((x[0].re - 0.0).abs() < 1e-10);
1361        assert!((x[0].im - 1.0).abs() < 1e-10);
1362    }
1363
1364    #[test]
1365    fn test_axpy_c64_ndarray() {
1366        let x = array![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
1367        let mut y = array![Complex64::new(5.0, 6.0), Complex64::new(7.0, 8.0)];
1368        let alpha = Complex64::new(2.0, 0.0);
1369
1370        axpy_c64_ndarray(alpha, &x, &mut y);
1371
1372        // y = 2*x + y = 2*(1+2i) + (5+6i) = (2+4i) + (5+6i) = 7+10i
1373        assert!((y[0].re - 7.0).abs() < 1e-10);
1374        assert!((y[0].im - 10.0).abs() < 1e-10);
1375
1376        // y = 2*(3+4i) + (7+8i) = (6+8i) + (7+8i) = 13+16i
1377        assert!((y[1].re - 13.0).abs() < 1e-10);
1378        assert!((y[1].im - 16.0).abs() < 1e-10);
1379    }
1380
1381    #[test]
1382    fn test_axpy_c32_ndarray() {
1383        let x = array![Complex32::new(1.0, 2.0)];
1384        let mut y = array![Complex32::new(3.0, 4.0)];
1385        let alpha = Complex32::new(0.0, 1.0); // i
1386
1387        axpy_c32_ndarray(alpha, &x, &mut y);
1388
1389        // y = i*(1+2i) + (3+4i) = (i - 2) + (3+4i) = (1) + (5i)
1390        assert!((y[0].re - 1.0).abs() < 1e-5);
1391        assert!((y[0].im - 5.0).abs() < 1e-5);
1392    }
1393
1394    #[test]
1395    fn test_eye_c64() {
1396        let id = eye_c64(3);
1397        assert_eq!(id.dim(), (3, 3));
1398
1399        for i in 0..3 {
1400            for j in 0..3 {
1401                if i == j {
1402                    assert!((id[[i, j]].re - 1.0).abs() < 1e-10);
1403                    assert!(id[[i, j]].im.abs() < 1e-10);
1404                } else {
1405                    assert!(id[[i, j]].re.abs() < 1e-10);
1406                    assert!(id[[i, j]].im.abs() < 1e-10);
1407                }
1408            }
1409        }
1410    }
1411
1412    #[test]
1413    fn test_eye_c32() {
1414        let id = eye_c32(2);
1415        assert_eq!(id.dim(), (2, 2));
1416        assert!((id[[0, 0]].re - 1.0).abs() < 1e-5);
1417        assert!((id[[1, 1]].re - 1.0).abs() < 1e-5);
1418        assert!(id[[0, 1]].re.abs() < 1e-5);
1419        assert!(id[[1, 0]].re.abs() < 1e-5);
1420    }
1421
1422    #[test]
1423    fn test_dotc_c64_large() {
1424        // Test with larger arrays to verify SIMD path
1425        let n = 1000;
1426        let x: Array1<Complex64> =
1427            Array1::from_shape_fn(n, |i| Complex64::new(i as f64, (i as f64) * 0.5));
1428        let y: Array1<Complex64> =
1429            Array1::from_shape_fn(n, |i| Complex64::new(1.0, 0.1 * i as f64));
1430
1431        let result = dotc_c64_ndarray(&x, &y);
1432
1433        // Verify against manual computation
1434        let expected: Complex64 = x.iter().zip(y.iter()).map(|(xi, yi)| xi.conj() * yi).sum();
1435        assert!((result.re - expected.re).abs() < 1e-6);
1436        assert!((result.im - expected.im).abs() < 1e-6);
1437    }
1438
1439    #[test]
1440    fn test_dotu_c64_large() {
1441        let n = 1000;
1442        let x: Array1<Complex64> = Array1::from_shape_fn(n, |i| {
1443            Complex64::new((i % 100) as f64, ((i + 50) % 100) as f64)
1444        });
1445        let y: Array1<Complex64> = Array1::from_shape_fn(n, |i| {
1446            Complex64::new(((i + 25) % 100) as f64, ((i + 75) % 100) as f64)
1447        });
1448
1449        let result = dotu_c64_ndarray(&x, &y);
1450
1451        let expected: Complex64 = x.iter().zip(y.iter()).map(|(xi, yi)| xi * yi).sum();
1452        assert!((result.re - expected.re).abs() < 1e-6);
1453        assert!((result.im - expected.im).abs() < 1e-6);
1454    }
1455
1456    #[test]
1457    fn test_hermitian_property() {
1458        // For a Hermitian matrix A = A^H, verify property holds
1459        let a = array![
1460            [Complex64::new(2.0, 0.0), Complex64::new(1.0, 1.0)],
1461            [Complex64::new(1.0, -1.0), Complex64::new(3.0, 0.0)]
1462        ];
1463
1464        let ah = conj_transpose_c64(&a);
1465
1466        // A should equal A^H for Hermitian matrix
1467        for i in 0..2 {
1468            for j in 0..2 {
1469                assert!((a[[i, j]].re - ah[[i, j]].re).abs() < 1e-10);
1470                assert!((a[[i, j]].im - ah[[i, j]].im).abs() < 1e-10);
1471            }
1472        }
1473    }
1474}