Skip to main content

oxiz_math/
blas.rs

1//! OxiBLAS: High-Performance BLAS Operations for Large-Scale LP
2//!
3//! This module provides pure Rust implementations of BLAS (Basic Linear Algebra Subprograms)
4//! operations optimized for vectorization and cache efficiency. These operations are designed
5//! for large-scale linear programming problems with 1000+ variables.
6//!
7//! # Operations
8//!
9//! ## Level 1 BLAS (Vector-Vector)
10//! - `ddot`: Dot product of two vectors
11//! - `dnrm2`: Euclidean norm of a vector
12//! - `dscal`: Scale a vector by a scalar
13//! - `daxpy`: y = alpha * x + y
14//! - `dcopy`: Copy vector x to y
15//! - `dswap`: Swap vectors x and y
16//! - `idamax`: Index of maximum absolute value
17//!
18//! ## Level 2 BLAS (Matrix-Vector)
19//! - `dgemv`: General matrix-vector multiplication
20//! - `dtrsv`: Triangular solve with single vector
21//!
22//! ## Level 3 BLAS (Matrix-Matrix)
23//! - `dgemm`: General matrix-matrix multiplication with blocking
24//! - `dtrsm`: Triangular solve with matrix
25//!
26//! # Example
27//!
28//! ```
29//! use oxiz_math::blas::{ddot, dgemv, dgemm, Transpose};
30//!
31//! // Level 1: Dot product
32//! let x = vec![1.0, 2.0, 3.0];
33//! let y = vec![4.0, 5.0, 6.0];
34//! let dot = ddot(&x, &y);
35//! assert_eq!(dot, 32.0); // 1*4 + 2*5 + 3*6
36//!
37//! // Level 2: Matrix-vector multiplication
38//! let a = vec![1.0, 2.0, 3.0, 4.0]; // 2x2 row-major
39//! let x2 = vec![1.0, 2.0];
40//! let mut y2 = vec![0.0, 0.0];
41//! dgemv(Transpose::NoTrans, 2, 2, 1.0, &a, &x2, 0.0, &mut y2);
42//! ```
43//!
44//! # Performance Optimizations
45//!
46//! - **Cache blocking**: DGEMM uses block-wise multiplication for better cache utilization
47//! - **Loop unrolling**: Critical inner loops are unrolled for better instruction pipelining
48//! - **Memory access patterns**: Designed for row-major storage with stride-aware operations
49//! - **SIMD-friendly**: Inner loops are structured for auto-vectorization by the compiler
50
51/// Block size for cache-efficient matrix operations.
52/// Chosen to fit L1 cache (typically 32KB) for three blocks of f64s.
53#[allow(unused_imports)]
54use crate::prelude::*;
55
56const BLOCK_SIZE: usize = 64;
57
58/// Transpose option for matrix operations.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Transpose {
61    /// No transpose (use matrix as-is).
62    NoTrans,
63    /// Transpose the matrix.
64    Trans,
65}
66
67/// Side for triangular solve operations.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Side {
70    /// Matrix A is on the left: solve AX = B.
71    Left,
72    /// Matrix A is on the right: solve XA = B.
73    Right,
74}
75
76/// Upper or lower triangular indicator.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum UpLo {
79    /// Upper triangular matrix.
80    Upper,
81    /// Lower triangular matrix.
82    Lower,
83}
84
85/// Diagonal type indicator.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum Diag {
88    /// Non-unit diagonal (use actual diagonal elements).
89    NonUnit,
90    /// Unit diagonal (diagonal elements are assumed to be 1).
91    Unit,
92}
93
94// =============================================================================
95// Level 1 BLAS: Vector-Vector Operations
96// =============================================================================
97
98/// DDOT: Compute the dot product of two vectors.
99///
100/// Returns x^T * y = sum(x\[i\] * y\[i\])
101///
102/// # Arguments
103/// * `x` - First input vector
104/// * `y` - Second input vector
105///
106/// # Panics
107/// Panics if vectors have different lengths.
108///
109/// # Example
110/// ```
111/// use oxiz_math::blas::ddot;
112/// let x = vec![1.0, 2.0, 3.0];
113/// let y = vec![4.0, 5.0, 6.0];
114/// assert_eq!(ddot(&x, &y), 32.0);
115/// ```
116#[inline]
117pub fn ddot(x: &[f64], y: &[f64]) -> f64 {
118    assert_eq!(
119        x.len(),
120        y.len(),
121        "Vector lengths must match for dot product"
122    );
123
124    let n = x.len();
125    let mut sum = 0.0;
126
127    // Unroll by 4 for better pipelining
128    let chunks = n / 4;
129    let remainder = n % 4;
130
131    for i in 0..chunks {
132        let idx = i * 4;
133        sum += x[idx] * y[idx];
134        sum += x[idx + 1] * y[idx + 1];
135        sum += x[idx + 2] * y[idx + 2];
136        sum += x[idx + 3] * y[idx + 3];
137    }
138
139    // Handle remainder
140    for i in (chunks * 4)..n {
141        sum += x[i] * y[i];
142    }
143
144    // Use remainder variable to satisfy clippy (even though we use the range)
145    let _ = remainder;
146
147    sum
148}
149
150/// DNRM2: Compute the Euclidean (L2) norm of a vector.
151///
152/// Returns ||x||_2 = sqrt(sum(x\[i\]^2))
153///
154/// Uses a numerically stable algorithm to avoid overflow/underflow.
155///
156/// # Example
157/// ```
158/// use oxiz_math::blas::dnrm2;
159/// let x = vec![3.0, 4.0];
160/// assert!((dnrm2(&x) - 5.0).abs() < 1e-10);
161/// ```
162#[inline]
163pub fn dnrm2(x: &[f64]) -> f64 {
164    if x.is_empty() {
165        return 0.0;
166    }
167
168    let n = x.len();
169
170    // Find scale factor to avoid overflow/underflow
171    let mut scale = 0.0f64;
172    for &xi in x {
173        let abs_xi = xi.abs();
174        if abs_xi > scale {
175            scale = abs_xi;
176        }
177    }
178
179    if scale == 0.0 {
180        return 0.0;
181    }
182
183    // Compute scaled sum of squares
184    let mut sum = 0.0;
185    let inv_scale = 1.0 / scale;
186
187    // Unroll by 4
188    let chunks = n / 4;
189
190    for i in 0..chunks {
191        let idx = i * 4;
192        let s0 = x[idx] * inv_scale;
193        let s1 = x[idx + 1] * inv_scale;
194        let s2 = x[idx + 2] * inv_scale;
195        let s3 = x[idx + 3] * inv_scale;
196        sum += s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3;
197    }
198
199    for s in x.iter().skip(chunks * 4).take(n - chunks * 4) {
200        let s = s * inv_scale;
201        sum += s * s;
202    }
203
204    scale * sum.sqrt()
205}
206
207/// DSCAL: Scale a vector by a scalar.
208///
209/// Computes x = alpha * x
210///
211/// # Arguments
212/// * `alpha` - Scalar multiplier
213/// * `x` - Vector to scale (modified in place)
214///
215/// # Example
216/// ```
217/// use oxiz_math::blas::dscal;
218/// let mut x = vec![1.0, 2.0, 3.0];
219/// dscal(2.0, &mut x);
220/// assert_eq!(x, vec![2.0, 4.0, 6.0]);
221/// ```
222#[inline]
223pub fn dscal(alpha: f64, x: &mut [f64]) {
224    if alpha == 1.0 {
225        return;
226    }
227
228    if alpha == 0.0 {
229        x.fill(0.0);
230        return;
231    }
232
233    let n = x.len();
234    let chunks = n / 4;
235
236    for i in 0..chunks {
237        let idx = i * 4;
238        x[idx] *= alpha;
239        x[idx + 1] *= alpha;
240        x[idx + 2] *= alpha;
241        x[idx + 3] *= alpha;
242    }
243
244    for x_val in x.iter_mut().skip(chunks * 4).take(n - chunks * 4) {
245        *x_val *= alpha;
246    }
247}
248
249/// DAXPY: Compute y = alpha * x + y (vector plus scaled vector).
250///
251/// # Arguments
252/// * `alpha` - Scalar multiplier for x
253/// * `x` - Input vector
254/// * `y` - Input/output vector (modified in place)
255///
256/// # Panics
257/// Panics if vectors have different lengths.
258///
259/// # Example
260/// ```
261/// use oxiz_math::blas::daxpy;
262/// let x = vec![1.0, 2.0, 3.0];
263/// let mut y = vec![4.0, 5.0, 6.0];
264/// daxpy(2.0, &x, &mut y);
265/// assert_eq!(y, vec![6.0, 9.0, 12.0]);
266/// ```
267#[inline]
268pub fn daxpy(alpha: f64, x: &[f64], y: &mut [f64]) {
269    assert_eq!(x.len(), y.len(), "Vector lengths must match for DAXPY");
270
271    if alpha == 0.0 {
272        return;
273    }
274
275    let n = x.len();
276    let chunks = n / 4;
277
278    for i in 0..chunks {
279        let idx = i * 4;
280        y[idx] += alpha * x[idx];
281        y[idx + 1] += alpha * x[idx + 1];
282        y[idx + 2] += alpha * x[idx + 2];
283        y[idx + 3] += alpha * x[idx + 3];
284    }
285
286    for i in (chunks * 4)..n {
287        y[i] += alpha * x[i];
288    }
289}
290
291/// DCOPY: Copy vector x to vector y.
292///
293/// # Arguments
294/// * `x` - Source vector
295/// * `y` - Destination vector (modified in place)
296///
297/// # Panics
298/// Panics if vectors have different lengths.
299///
300/// # Example
301/// ```
302/// use oxiz_math::blas::dcopy;
303/// let x = vec![1.0, 2.0, 3.0];
304/// let mut y = vec![0.0, 0.0, 0.0];
305/// dcopy(&x, &mut y);
306/// assert_eq!(y, vec![1.0, 2.0, 3.0]);
307/// ```
308#[inline]
309pub fn dcopy(x: &[f64], y: &mut [f64]) {
310    assert_eq!(x.len(), y.len(), "Vector lengths must match for DCOPY");
311    y.copy_from_slice(x);
312}
313
314/// DSWAP: Swap vectors x and y.
315///
316/// # Arguments
317/// * `x` - First vector (modified in place)
318/// * `y` - Second vector (modified in place)
319///
320/// # Panics
321/// Panics if vectors have different lengths.
322///
323/// # Example
324/// ```
325/// use oxiz_math::blas::dswap;
326/// let mut x = vec![1.0, 2.0, 3.0];
327/// let mut y = vec![4.0, 5.0, 6.0];
328/// dswap(&mut x, &mut y);
329/// assert_eq!(x, vec![4.0, 5.0, 6.0]);
330/// assert_eq!(y, vec![1.0, 2.0, 3.0]);
331/// ```
332#[inline]
333pub fn dswap(x: &mut [f64], y: &mut [f64]) {
334    assert_eq!(x.len(), y.len(), "Vector lengths must match for DSWAP");
335    x.swap_with_slice(y);
336}
337
338/// IDAMAX: Find index of element with maximum absolute value.
339///
340/// Returns the index of the first element with the largest absolute value.
341/// Returns 0 for empty vectors.
342///
343/// # Example
344/// ```
345/// use oxiz_math::blas::idamax;
346/// let x = vec![1.0, -5.0, 3.0];
347/// assert_eq!(idamax(&x), 1);
348/// ```
349#[inline]
350pub fn idamax(x: &[f64]) -> usize {
351    if x.is_empty() {
352        return 0;
353    }
354
355    let mut max_idx = 0;
356    let mut max_val = x[0].abs();
357
358    for (i, &xi) in x.iter().enumerate().skip(1) {
359        let abs_xi = xi.abs();
360        if abs_xi > max_val {
361            max_val = abs_xi;
362            max_idx = i;
363        }
364    }
365
366    max_idx
367}
368
369/// DASUM: Compute the sum of absolute values of vector elements.
370///
371/// Returns sum(|x\[i\]|)
372///
373/// # Example
374/// ```
375/// use oxiz_math::blas::dasum;
376/// let x = vec![1.0, -2.0, 3.0];
377/// assert_eq!(dasum(&x), 6.0);
378/// ```
379#[inline]
380pub fn dasum(x: &[f64]) -> f64 {
381    let n = x.len();
382    let mut sum = 0.0;
383
384    let chunks = n / 4;
385
386    for i in 0..chunks {
387        let idx = i * 4;
388        sum += x[idx].abs();
389        sum += x[idx + 1].abs();
390        sum += x[idx + 2].abs();
391        sum += x[idx + 3].abs();
392    }
393
394    for x_val in x.iter().skip(chunks * 4).take(n - chunks * 4) {
395        sum += x_val.abs();
396    }
397
398    sum
399}
400
401// =============================================================================
402// Level 2 BLAS: Matrix-Vector Operations
403// =============================================================================
404
405/// DGEMV: General matrix-vector multiplication.
406///
407/// Computes y = alpha * op(A) * x + beta * y
408///
409/// where op(A) = A if trans == NoTrans, or op(A) = A^T if trans == Trans.
410///
411/// # Arguments
412/// * `trans` - Whether to transpose A
413/// * `m` - Number of rows of A
414/// * `n` - Number of columns of A
415/// * `alpha` - Scalar multiplier for A*x
416/// * `a` - Matrix A in row-major order (m x n)
417/// * `x` - Input vector (n for NoTrans, m for Trans)
418/// * `beta` - Scalar multiplier for y
419/// * `y` - Output vector (m for NoTrans, n for Trans), modified in place
420///
421/// # Panics
422/// Panics if dimensions don't match.
423///
424/// # Example
425/// ```
426/// use oxiz_math::blas::{dgemv, Transpose};
427/// let a = vec![1.0, 2.0, 3.0, 4.0]; // 2x2 row-major
428/// let x = vec![1.0, 2.0];
429/// let mut y = vec![0.0, 0.0];
430/// dgemv(Transpose::NoTrans, 2, 2, 1.0, &a, &x, 0.0, &mut y);
431/// assert_eq!(y, vec![5.0, 11.0]); // [1*1+2*2, 3*1+4*2]
432/// ```
433#[allow(clippy::too_many_arguments)]
434pub fn dgemv(
435    trans: Transpose,
436    m: usize,
437    n: usize,
438    alpha: f64,
439    a: &[f64],
440    x: &[f64],
441    beta: f64,
442    y: &mut [f64],
443) {
444    assert_eq!(a.len(), m * n, "Matrix A size must be m * n");
445
446    match trans {
447        Transpose::NoTrans => {
448            assert_eq!(x.len(), n, "Vector x length must be n for NoTrans");
449            assert_eq!(y.len(), m, "Vector y length must be m for NoTrans");
450
451            // Scale y by beta
452            if beta == 0.0 {
453                y.fill(0.0);
454            } else if beta != 1.0 {
455                dscal(beta, y);
456            }
457
458            if alpha == 0.0 {
459                return;
460            }
461
462            // y = alpha * A * x + beta * y
463            for (i, y_val) in y.iter_mut().enumerate().take(m) {
464                let row_start = i * n;
465                let mut sum = 0.0;
466
467                // Unroll inner loop
468                let chunks = n / 4;
469                for j in 0..chunks {
470                    let idx = j * 4;
471                    sum += a[row_start + idx] * x[idx];
472                    sum += a[row_start + idx + 1] * x[idx + 1];
473                    sum += a[row_start + idx + 2] * x[idx + 2];
474                    sum += a[row_start + idx + 3] * x[idx + 3];
475                }
476                for j in (chunks * 4)..n {
477                    sum += a[row_start + j] * x[j];
478                }
479
480                *y_val += alpha * sum;
481            }
482        }
483        Transpose::Trans => {
484            assert_eq!(x.len(), m, "Vector x length must be m for Trans");
485            assert_eq!(y.len(), n, "Vector y length must be n for Trans");
486
487            // Scale y by beta
488            if beta == 0.0 {
489                y.fill(0.0);
490            } else if beta != 1.0 {
491                dscal(beta, y);
492            }
493
494            if alpha == 0.0 {
495                return;
496            }
497
498            // y = alpha * A^T * x + beta * y
499            for (i, x_val) in x.iter().enumerate().take(m) {
500                let row_start = i * n;
501                let alpha_xi = alpha * x_val;
502
503                // Unroll inner loop
504                let chunks = n / 4;
505                for j in 0..chunks {
506                    let idx = j * 4;
507                    y[idx] += alpha_xi * a[row_start + idx];
508                    y[idx + 1] += alpha_xi * a[row_start + idx + 1];
509                    y[idx + 2] += alpha_xi * a[row_start + idx + 2];
510                    y[idx + 3] += alpha_xi * a[row_start + idx + 3];
511                }
512                for j in (chunks * 4)..n {
513                    y[j] += alpha_xi * a[row_start + j];
514                }
515            }
516        }
517    }
518}
519
520/// DTRSV: Triangular solve with a single vector.
521///
522/// Solves op(A) * x = b where A is triangular.
523///
524/// # Arguments
525/// * `uplo` - Whether A is upper or lower triangular
526/// * `trans` - Whether to use A or A^T
527/// * `diag` - Whether A has unit diagonal
528/// * `n` - Order of matrix A
529/// * `a` - Triangular matrix A in row-major order (n x n)
530/// * `b` - Right-hand side vector (modified in place to contain solution)
531///
532/// # Panics
533/// Panics if dimensions don't match.
534#[allow(clippy::too_many_arguments)]
535pub fn dtrsv(uplo: UpLo, trans: Transpose, diag: Diag, n: usize, a: &[f64], b: &mut [f64]) {
536    assert_eq!(a.len(), n * n, "Matrix A size must be n * n");
537    assert_eq!(b.len(), n, "Vector b length must be n");
538
539    if n == 0 {
540        return;
541    }
542
543    match (uplo, trans) {
544        (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
545            // Forward substitution
546            for i in 0..n {
547                let mut sum = b[i];
548                for j in 0..i {
549                    let a_ij = if trans == Transpose::Trans {
550                        a[j * n + i]
551                    } else {
552                        a[i * n + j]
553                    };
554                    sum -= a_ij * b[j];
555                }
556                if diag == Diag::NonUnit {
557                    // Diagonal element is same for both NoTrans and Trans
558                    let a_ii = a[i * n + i];
559                    b[i] = sum / a_ii;
560                } else {
561                    b[i] = sum;
562                }
563            }
564        }
565        (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
566            // Backward substitution
567            for i in (0..n).rev() {
568                let mut sum = b[i];
569                for j in (i + 1)..n {
570                    let a_ij = if trans == Transpose::Trans {
571                        a[j * n + i]
572                    } else {
573                        a[i * n + j]
574                    };
575                    sum -= a_ij * b[j];
576                }
577                if diag == Diag::NonUnit {
578                    // Diagonal element is same for both NoTrans and Trans
579                    let a_ii = a[i * n + i];
580                    b[i] = sum / a_ii;
581                } else {
582                    b[i] = sum;
583                }
584            }
585        }
586    }
587}
588
589// =============================================================================
590// Level 3 BLAS: Matrix-Matrix Operations
591// =============================================================================
592
593/// DGEMM: General matrix-matrix multiplication with cache blocking.
594///
595/// Computes C = alpha * op(A) * op(B) + beta * C
596///
597/// where op(X) = X if trans == NoTrans, or op(X) = X^T if trans == Trans.
598///
599/// Uses block-wise multiplication for better cache utilization on large matrices.
600///
601/// # Arguments
602/// * `trans_a` - Whether to transpose A
603/// * `trans_b` - Whether to transpose B
604/// * `m` - Number of rows of op(A) and C
605/// * `n` - Number of columns of op(B) and C
606/// * `k` - Number of columns of op(A) and rows of op(B)
607/// * `alpha` - Scalar multiplier for A*B
608/// * `a` - Matrix A
609/// * `b` - Matrix B
610/// * `beta` - Scalar multiplier for C
611/// * `c` - Output matrix C (modified in place)
612///
613/// # Panics
614/// Panics if dimensions don't match.
615///
616/// # Example
617/// ```
618/// use oxiz_math::blas::{dgemm, Transpose};
619/// let a = vec![1.0, 2.0, 3.0, 4.0]; // 2x2 row-major
620/// let b = vec![5.0, 6.0, 7.0, 8.0]; // 2x2 row-major
621/// let mut c = vec![0.0; 4];
622/// dgemm(Transpose::NoTrans, Transpose::NoTrans, 2, 2, 2, 1.0, &a, &b, 0.0, &mut c);
623/// assert_eq!(c, vec![19.0, 22.0, 43.0, 50.0]);
624/// ```
625#[allow(clippy::too_many_arguments)]
626pub fn dgemm(
627    trans_a: Transpose,
628    trans_b: Transpose,
629    m: usize,
630    n: usize,
631    k: usize,
632    alpha: f64,
633    a: &[f64],
634    b: &[f64],
635    beta: f64,
636    c: &mut [f64],
637) {
638    // Validate dimensions
639    let (a_rows, a_cols) = match trans_a {
640        Transpose::NoTrans => (m, k),
641        Transpose::Trans => (k, m),
642    };
643    let (b_rows, b_cols) = match trans_b {
644        Transpose::NoTrans => (k, n),
645        Transpose::Trans => (n, k),
646    };
647
648    assert_eq!(a.len(), a_rows * a_cols, "Matrix A size mismatch");
649    assert_eq!(b.len(), b_rows * b_cols, "Matrix B size mismatch");
650    assert_eq!(c.len(), m * n, "Matrix C size must be m * n");
651
652    // Scale C by beta
653    if beta == 0.0 {
654        c.fill(0.0);
655    } else if beta != 1.0 {
656        for ci in c.iter_mut() {
657            *ci *= beta;
658        }
659    }
660
661    if alpha == 0.0 {
662        return;
663    }
664
665    // Use blocked algorithm for large matrices
666    if m * n * k > BLOCK_SIZE * BLOCK_SIZE * BLOCK_SIZE {
667        dgemm_blocked(trans_a, trans_b, m, n, k, alpha, a, b, c);
668    } else {
669        dgemm_simple(trans_a, trans_b, m, n, k, alpha, a, b, c);
670    }
671}
672
673/// Simple (non-blocked) matrix multiplication for small matrices.
674#[allow(clippy::too_many_arguments)]
675fn dgemm_simple(
676    trans_a: Transpose,
677    trans_b: Transpose,
678    m: usize,
679    n: usize,
680    k: usize,
681    alpha: f64,
682    a: &[f64],
683    b: &[f64],
684    c: &mut [f64],
685) {
686    let (a_cols, b_cols) = match (trans_a, trans_b) {
687        (Transpose::NoTrans, Transpose::NoTrans) => (k, n),
688        (Transpose::NoTrans, Transpose::Trans) => (k, k),
689        (Transpose::Trans, Transpose::NoTrans) => (m, n),
690        (Transpose::Trans, Transpose::Trans) => (m, k),
691    };
692
693    for i in 0..m {
694        for j in 0..n {
695            let mut sum = 0.0;
696            for l in 0..k {
697                let a_il = match trans_a {
698                    Transpose::NoTrans => a[i * a_cols + l],
699                    Transpose::Trans => a[l * a_cols + i],
700                };
701                let b_lj = match trans_b {
702                    Transpose::NoTrans => b[l * b_cols + j],
703                    Transpose::Trans => b[j * b_cols + l],
704                };
705                sum += a_il * b_lj;
706            }
707            c[i * n + j] += alpha * sum;
708        }
709    }
710}
711
712/// Blocked matrix multiplication for better cache utilization.
713#[allow(clippy::too_many_arguments)]
714fn dgemm_blocked(
715    trans_a: Transpose,
716    trans_b: Transpose,
717    m: usize,
718    n: usize,
719    k: usize,
720    alpha: f64,
721    a: &[f64],
722    b: &[f64],
723    c: &mut [f64],
724) {
725    let (a_cols, b_cols) = match (trans_a, trans_b) {
726        (Transpose::NoTrans, Transpose::NoTrans) => (k, n),
727        (Transpose::NoTrans, Transpose::Trans) => (k, k),
728        (Transpose::Trans, Transpose::NoTrans) => (m, n),
729        (Transpose::Trans, Transpose::Trans) => (m, k),
730    };
731
732    // Block over all three dimensions
733    for i0 in (0..m).step_by(BLOCK_SIZE) {
734        let i1 = (i0 + BLOCK_SIZE).min(m);
735
736        for j0 in (0..n).step_by(BLOCK_SIZE) {
737            let j1 = (j0 + BLOCK_SIZE).min(n);
738
739            for l0 in (0..k).step_by(BLOCK_SIZE) {
740                let l1 = (l0 + BLOCK_SIZE).min(k);
741
742                // Multiply block
743                for i in i0..i1 {
744                    for j in j0..j1 {
745                        let mut sum = 0.0;
746                        for l in l0..l1 {
747                            let a_il = match trans_a {
748                                Transpose::NoTrans => a[i * a_cols + l],
749                                Transpose::Trans => a[l * a_cols + i],
750                            };
751                            let b_lj = match trans_b {
752                                Transpose::NoTrans => b[l * b_cols + j],
753                                Transpose::Trans => b[j * b_cols + l],
754                            };
755                            sum += a_il * b_lj;
756                        }
757                        c[i * n + j] += alpha * sum;
758                    }
759                }
760            }
761        }
762    }
763}
764
765/// DTRSM: Triangular solve with multiple right-hand sides.
766///
767/// Solves op(A) * X = alpha * B (Side::Left) or X * op(A) = alpha * B (Side::Right)
768/// where A is triangular.
769///
770/// # Arguments
771/// * `side` - Whether A is on the left or right
772/// * `uplo` - Whether A is upper or lower triangular
773/// * `trans` - Whether to use A or A^T
774/// * `diag` - Whether A has unit diagonal
775/// * `m` - Number of rows of B
776/// * `n` - Number of columns of B
777/// * `alpha` - Scalar multiplier
778/// * `a` - Triangular matrix A
779/// * `b` - Right-hand side matrix B (modified in place to contain X)
780///
781/// # Panics
782/// Panics if dimensions don't match.
783#[allow(clippy::too_many_arguments)]
784pub fn dtrsm(
785    side: Side,
786    uplo: UpLo,
787    trans: Transpose,
788    diag: Diag,
789    m: usize,
790    n: usize,
791    alpha: f64,
792    a: &[f64],
793    b: &mut [f64],
794) {
795    let a_size = match side {
796        Side::Left => m,
797        Side::Right => n,
798    };
799
800    assert_eq!(a.len(), a_size * a_size, "Matrix A must be square");
801    assert_eq!(b.len(), m * n, "Matrix B size must be m * n");
802
803    // Scale B by alpha
804    if alpha != 1.0 {
805        for bi in b.iter_mut() {
806            *bi *= alpha;
807        }
808    }
809
810    match side {
811        Side::Left => dtrsm_left(uplo, trans, diag, m, n, a, b),
812        Side::Right => dtrsm_right(uplo, trans, diag, m, n, a, b),
813    }
814}
815
816/// Triangular solve: op(A) * X = B (A on the left).
817fn dtrsm_left(
818    uplo: UpLo,
819    trans: Transpose,
820    diag: Diag,
821    m: usize,
822    n: usize,
823    a: &[f64],
824    b: &mut [f64],
825) {
826    for col in 0..n {
827        match (uplo, trans) {
828            (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
829                // Forward substitution
830                for i in 0..m {
831                    let mut sum = b[i * n + col];
832                    for j in 0..i {
833                        let a_ij = if trans == Transpose::Trans {
834                            a[j * m + i]
835                        } else {
836                            a[i * m + j]
837                        };
838                        sum -= a_ij * b[j * n + col];
839                    }
840                    if diag == Diag::NonUnit {
841                        let a_ii = a[i * m + i];
842                        b[i * n + col] = sum / a_ii;
843                    } else {
844                        b[i * n + col] = sum;
845                    }
846                }
847            }
848            (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
849                // Backward substitution
850                for i in (0..m).rev() {
851                    let mut sum = b[i * n + col];
852                    for j in (i + 1)..m {
853                        let a_ij = if trans == Transpose::Trans {
854                            a[j * m + i]
855                        } else {
856                            a[i * m + j]
857                        };
858                        sum -= a_ij * b[j * n + col];
859                    }
860                    if diag == Diag::NonUnit {
861                        let a_ii = a[i * m + i];
862                        b[i * n + col] = sum / a_ii;
863                    } else {
864                        b[i * n + col] = sum;
865                    }
866                }
867            }
868        }
869    }
870}
871
872/// Triangular solve: X * op(A) = B (A on the right).
873fn dtrsm_right(
874    uplo: UpLo,
875    trans: Transpose,
876    diag: Diag,
877    m: usize,
878    n: usize,
879    a: &[f64],
880    b: &mut [f64],
881) {
882    for row in 0..m {
883        match (uplo, trans) {
884            (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
885                // Forward substitution on columns
886                for j in 0..n {
887                    let mut sum = b[row * n + j];
888                    for k in 0..j {
889                        let a_kj = if trans == Transpose::Trans {
890                            a[j * n + k]
891                        } else {
892                            a[k * n + j]
893                        };
894                        sum -= b[row * n + k] * a_kj;
895                    }
896                    if diag == Diag::NonUnit {
897                        let a_jj = a[j * n + j];
898                        b[row * n + j] = sum / a_jj;
899                    } else {
900                        b[row * n + j] = sum;
901                    }
902                }
903            }
904            (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
905                // Backward substitution on columns
906                for j in (0..n).rev() {
907                    let mut sum = b[row * n + j];
908                    for k in (j + 1)..n {
909                        let a_kj = if trans == Transpose::Trans {
910                            a[j * n + k]
911                        } else {
912                            a[k * n + j]
913                        };
914                        sum -= b[row * n + k] * a_kj;
915                    }
916                    if diag == Diag::NonUnit {
917                        let a_jj = a[j * n + j];
918                        b[row * n + j] = sum / a_jj;
919                    } else {
920                        b[row * n + j] = sum;
921                    }
922                }
923            }
924        }
925    }
926}
927
928// =============================================================================
929// LP-Specific Operations
930// =============================================================================
931
932/// Configuration for BLAS operations in LP context.
933#[derive(Debug, Clone)]
934pub struct BlasLPConfig {
935    /// Block size for matrix operations.
936    pub block_size: usize,
937    /// Tolerance for numerical zero.
938    pub zero_tolerance: f64,
939    /// Whether to use pivoting in triangular solves.
940    pub use_pivoting: bool,
941}
942
943impl Default for BlasLPConfig {
944    fn default() -> Self {
945        Self {
946            block_size: BLOCK_SIZE,
947            zero_tolerance: 1e-12,
948            use_pivoting: true,
949        }
950    }
951}
952
953/// Compute the reduced cost for simplex pivoting.
954///
955/// reduced_cost = c - c_B * B^{-1} * A
956///
957/// where c is the objective, c_B is the basic objective, B^{-1} is the basis inverse,
958/// and A is the constraint matrix.
959#[allow(dead_code)]
960pub fn compute_reduced_cost(
961    c: &[f64],
962    c_b: &[f64],
963    b_inv_a: &[f64],
964    m: usize,
965    n: usize,
966) -> Vec<f64> {
967    let mut reduced = c.to_vec();
968
969    // reduced = c - c_B * B^{-1} * A
970    // This is essentially reduced = c - gemv(trans, c_B, B^{-1}*A)
971    for j in 0..n {
972        for i in 0..m {
973            reduced[j] -= c_b[i] * b_inv_a[i * n + j];
974        }
975    }
976
977    reduced
978}
979
980/// Solve B * x = b for the simplex basis update.
981///
982/// Uses LU factorization with partial pivoting for numerical stability.
983#[allow(dead_code)]
984pub fn solve_basis(b: &[f64], n: usize, rhs: &mut [f64], config: &BlasLPConfig) -> bool {
985    // Simple LU factorization with partial pivoting
986    let mut lu = b.to_vec();
987    let mut perm: Vec<usize> = (0..n).collect();
988
989    // LU factorization
990    for k in 0..n - 1 {
991        // Find pivot
992        if config.use_pivoting {
993            let mut max_idx = k;
994            let mut max_val = lu[k * n + k].abs();
995            for i in (k + 1)..n {
996                let val = lu[i * n + k].abs();
997                if val > max_val {
998                    max_val = val;
999                    max_idx = i;
1000                }
1001            }
1002
1003            if max_val < config.zero_tolerance {
1004                return false; // Singular matrix
1005            }
1006
1007            if max_idx != k {
1008                // Swap rows
1009                for j in 0..n {
1010                    lu.swap(k * n + j, max_idx * n + j);
1011                }
1012                perm.swap(k, max_idx);
1013            }
1014        }
1015
1016        let pivot = lu[k * n + k];
1017        if pivot.abs() < config.zero_tolerance {
1018            return false; // Singular matrix
1019        }
1020
1021        // Eliminate below diagonal
1022        for i in (k + 1)..n {
1023            let factor = lu[i * n + k] / pivot;
1024            lu[i * n + k] = factor;
1025            for j in (k + 1)..n {
1026                lu[i * n + j] -= factor * lu[k * n + j];
1027            }
1028        }
1029    }
1030
1031    // Apply permutation to RHS
1032    let mut tmp = vec![0.0; n];
1033    for i in 0..n {
1034        tmp[i] = rhs[perm[i]];
1035    }
1036    rhs.copy_from_slice(&tmp);
1037
1038    // Forward substitution (L * y = Pb)
1039    for i in 1..n {
1040        for j in 0..i {
1041            rhs[i] -= lu[i * n + j] * rhs[j];
1042        }
1043    }
1044
1045    // Backward substitution (U * x = y)
1046    for i in (0..n).rev() {
1047        for j in (i + 1)..n {
1048            rhs[i] -= lu[i * n + j] * rhs[j];
1049        }
1050        rhs[i] /= lu[i * n + i];
1051    }
1052
1053    true
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058    use super::*;
1059
1060    const EPSILON: f64 = 1e-10;
1061
1062    fn approx_eq(a: f64, b: f64) -> bool {
1063        (a - b).abs() < EPSILON
1064    }
1065
1066    fn approx_eq_vec(a: &[f64], b: &[f64]) -> bool {
1067        a.len() == b.len() && a.iter().zip(b.iter()).all(|(&ai, &bi)| approx_eq(ai, bi))
1068    }
1069
1070    // =============================================================================
1071    // Level 1 BLAS Tests
1072    // =============================================================================
1073
1074    #[test]
1075    fn test_ddot() {
1076        let x = vec![1.0, 2.0, 3.0, 4.0];
1077        let y = vec![5.0, 6.0, 7.0, 8.0];
1078        let result = ddot(&x, &y);
1079        assert!(approx_eq(result, 70.0)); // 5 + 12 + 21 + 32
1080    }
1081
1082    #[test]
1083    fn test_ddot_empty() {
1084        let x: Vec<f64> = vec![];
1085        let y: Vec<f64> = vec![];
1086        assert!(approx_eq(ddot(&x, &y), 0.0));
1087    }
1088
1089    #[test]
1090    fn test_dnrm2() {
1091        let x = vec![3.0, 4.0];
1092        assert!(approx_eq(dnrm2(&x), 5.0));
1093    }
1094
1095    #[test]
1096    fn test_dnrm2_large_values() {
1097        // Test numerical stability with large values
1098        let scale = 1e150;
1099        let x = vec![3.0 * scale, 4.0 * scale];
1100        assert!(approx_eq(dnrm2(&x), 5.0 * scale));
1101    }
1102
1103    #[test]
1104    fn test_dnrm2_small_values() {
1105        // Test numerical stability with small values
1106        let scale = 1e-150;
1107        let x = vec![3.0 * scale, 4.0 * scale];
1108        assert!(approx_eq(dnrm2(&x), 5.0 * scale));
1109    }
1110
1111    #[test]
1112    fn test_dscal() {
1113        let mut x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1114        dscal(2.0, &mut x);
1115        assert!(approx_eq_vec(&x, &[2.0, 4.0, 6.0, 8.0, 10.0]));
1116    }
1117
1118    #[test]
1119    fn test_dscal_zero() {
1120        let mut x = vec![1.0, 2.0, 3.0];
1121        dscal(0.0, &mut x);
1122        assert!(approx_eq_vec(&x, &[0.0, 0.0, 0.0]));
1123    }
1124
1125    #[test]
1126    fn test_dscal_one() {
1127        let mut x = vec![1.0, 2.0, 3.0];
1128        let original = x.clone();
1129        dscal(1.0, &mut x);
1130        assert!(approx_eq_vec(&x, &original));
1131    }
1132
1133    #[test]
1134    fn test_daxpy() {
1135        let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1136        let mut y = vec![10.0, 20.0, 30.0, 40.0, 50.0];
1137        daxpy(2.0, &x, &mut y);
1138        assert!(approx_eq_vec(&y, &[12.0, 24.0, 36.0, 48.0, 60.0]));
1139    }
1140
1141    #[test]
1142    fn test_daxpy_zero_alpha() {
1143        let x = vec![1.0, 2.0, 3.0];
1144        let mut y = vec![10.0, 20.0, 30.0];
1145        let original_y = y.clone();
1146        daxpy(0.0, &x, &mut y);
1147        assert!(approx_eq_vec(&y, &original_y));
1148    }
1149
1150    #[test]
1151    fn test_dcopy() {
1152        let x = vec![1.0, 2.0, 3.0];
1153        let mut y = vec![0.0, 0.0, 0.0];
1154        dcopy(&x, &mut y);
1155        assert!(approx_eq_vec(&y, &x));
1156    }
1157
1158    #[test]
1159    fn test_dswap() {
1160        let mut x = vec![1.0, 2.0, 3.0];
1161        let mut y = vec![4.0, 5.0, 6.0];
1162        dswap(&mut x, &mut y);
1163        assert!(approx_eq_vec(&x, &[4.0, 5.0, 6.0]));
1164        assert!(approx_eq_vec(&y, &[1.0, 2.0, 3.0]));
1165    }
1166
1167    #[test]
1168    fn test_idamax() {
1169        let x = vec![1.0, -5.0, 3.0, -2.0];
1170        assert_eq!(idamax(&x), 1);
1171    }
1172
1173    #[test]
1174    fn test_idamax_empty() {
1175        let x: Vec<f64> = vec![];
1176        assert_eq!(idamax(&x), 0);
1177    }
1178
1179    #[test]
1180    fn test_dasum() {
1181        let x = vec![1.0, -2.0, 3.0, -4.0, 5.0];
1182        assert!(approx_eq(dasum(&x), 15.0));
1183    }
1184
1185    // =============================================================================
1186    // Level 2 BLAS Tests
1187    // =============================================================================
1188
1189    #[test]
1190    fn test_dgemv_notrans() {
1191        // A = [[1, 2], [3, 4]], x = [5, 6]
1192        // y = A * x = [1*5+2*6, 3*5+4*6] = [17, 39]
1193        let a = vec![1.0, 2.0, 3.0, 4.0];
1194        let x = vec![5.0, 6.0];
1195        let mut y = vec![0.0, 0.0];
1196        dgemv(Transpose::NoTrans, 2, 2, 1.0, &a, &x, 0.0, &mut y);
1197        assert!(approx_eq_vec(&y, &[17.0, 39.0]));
1198    }
1199
1200    #[test]
1201    fn test_dgemv_trans() {
1202        // A = [[1, 2], [3, 4]], x = [5, 6]
1203        // y = A^T * x = [1*5+3*6, 2*5+4*6] = [23, 34]
1204        let a = vec![1.0, 2.0, 3.0, 4.0];
1205        let x = vec![5.0, 6.0];
1206        let mut y = vec![0.0, 0.0];
1207        dgemv(Transpose::Trans, 2, 2, 1.0, &a, &x, 0.0, &mut y);
1208        assert!(approx_eq_vec(&y, &[23.0, 34.0]));
1209    }
1210
1211    #[test]
1212    fn test_dgemv_with_beta() {
1213        let a = vec![1.0, 2.0, 3.0, 4.0];
1214        let x = vec![1.0, 1.0];
1215        let mut y = vec![10.0, 10.0];
1216        // y = 2 * A * x + 3 * y = 2 * [3, 7] + 3 * [10, 10] = [36, 44]
1217        dgemv(Transpose::NoTrans, 2, 2, 2.0, &a, &x, 3.0, &mut y);
1218        assert!(approx_eq_vec(&y, &[36.0, 44.0]));
1219    }
1220
1221    #[test]
1222    fn test_dtrsv_lower() {
1223        // L = [[2, 0], [1, 3]]
1224        // Solve L * x = [4, 5]
1225        // x[0] = 4/2 = 2
1226        // x[1] = (5 - 1*2)/3 = 1
1227        let l = vec![2.0, 0.0, 1.0, 3.0];
1228        let mut b = vec![4.0, 5.0];
1229        dtrsv(
1230            UpLo::Lower,
1231            Transpose::NoTrans,
1232            Diag::NonUnit,
1233            2,
1234            &l,
1235            &mut b,
1236        );
1237        assert!(approx_eq_vec(&b, &[2.0, 1.0]));
1238    }
1239
1240    #[test]
1241    fn test_dtrsv_upper() {
1242        // U = [[2, 1], [0, 3]]
1243        // Solve U * x = [5, 6]
1244        // x[1] = 6/3 = 2
1245        // x[0] = (5 - 1*2)/2 = 1.5
1246        let u = vec![2.0, 1.0, 0.0, 3.0];
1247        let mut b = vec![5.0, 6.0];
1248        dtrsv(
1249            UpLo::Upper,
1250            Transpose::NoTrans,
1251            Diag::NonUnit,
1252            2,
1253            &u,
1254            &mut b,
1255        );
1256        assert!(approx_eq_vec(&b, &[1.5, 2.0]));
1257    }
1258
1259    #[test]
1260    fn test_dtrsv_unit_diagonal() {
1261        // L = [[1, 0], [2, 1]] (unit diagonal)
1262        // Solve L * x = [3, 8]
1263        // x[0] = 3
1264        // x[1] = 8 - 2*3 = 2
1265        let l = vec![1.0, 0.0, 2.0, 1.0];
1266        let mut b = vec![3.0, 8.0];
1267        dtrsv(UpLo::Lower, Transpose::NoTrans, Diag::Unit, 2, &l, &mut b);
1268        assert!(approx_eq_vec(&b, &[3.0, 2.0]));
1269    }
1270
1271    // =============================================================================
1272    // Level 3 BLAS Tests
1273    // =============================================================================
1274
1275    #[test]
1276    fn test_dgemm_basic() {
1277        // A = [[1, 2], [3, 4]], B = [[5, 6], [7, 8]]
1278        // C = A * B = [[19, 22], [43, 50]]
1279        let a = vec![1.0, 2.0, 3.0, 4.0];
1280        let b = vec![5.0, 6.0, 7.0, 8.0];
1281        let mut c = vec![0.0; 4];
1282        dgemm(
1283            Transpose::NoTrans,
1284            Transpose::NoTrans,
1285            2,
1286            2,
1287            2,
1288            1.0,
1289            &a,
1290            &b,
1291            0.0,
1292            &mut c,
1293        );
1294        assert!(approx_eq_vec(&c, &[19.0, 22.0, 43.0, 50.0]));
1295    }
1296
1297    #[test]
1298    fn test_dgemm_with_transpose_a() {
1299        // A = [[1, 3], [2, 4]] (will be transposed to [[1, 2], [3, 4]])
1300        // B = [[5, 6], [7, 8]]
1301        // C = A^T * B = [[19, 22], [43, 50]]
1302        let a = vec![1.0, 3.0, 2.0, 4.0];
1303        let b = vec![5.0, 6.0, 7.0, 8.0];
1304        let mut c = vec![0.0; 4];
1305        dgemm(
1306            Transpose::Trans,
1307            Transpose::NoTrans,
1308            2,
1309            2,
1310            2,
1311            1.0,
1312            &a,
1313            &b,
1314            0.0,
1315            &mut c,
1316        );
1317        assert!(approx_eq_vec(&c, &[19.0, 22.0, 43.0, 50.0]));
1318    }
1319
1320    #[test]
1321    fn test_dgemm_with_transpose_b() {
1322        // A = [[1, 2], [3, 4]]
1323        // B = [[5, 7], [6, 8]] (will be transposed to [[5, 6], [7, 8]])
1324        // C = A * B^T = [[19, 22], [43, 50]]
1325        let a = vec![1.0, 2.0, 3.0, 4.0];
1326        let b = vec![5.0, 7.0, 6.0, 8.0];
1327        let mut c = vec![0.0; 4];
1328        dgemm(
1329            Transpose::NoTrans,
1330            Transpose::Trans,
1331            2,
1332            2,
1333            2,
1334            1.0,
1335            &a,
1336            &b,
1337            0.0,
1338            &mut c,
1339        );
1340        assert!(approx_eq_vec(&c, &[19.0, 22.0, 43.0, 50.0]));
1341    }
1342
1343    #[test]
1344    fn test_dgemm_with_alpha_beta() {
1345        let a = vec![1.0, 0.0, 0.0, 1.0]; // Identity
1346        let b = vec![1.0, 2.0, 3.0, 4.0];
1347        let mut c = vec![10.0, 20.0, 30.0, 40.0];
1348        // C = 2 * I * B + 3 * C = 2 * B + 3 * C
1349        // = [2*1+3*10, 2*2+3*20, 2*3+3*30, 2*4+3*40] = [32, 64, 96, 128]
1350        dgemm(
1351            Transpose::NoTrans,
1352            Transpose::NoTrans,
1353            2,
1354            2,
1355            2,
1356            2.0,
1357            &a,
1358            &b,
1359            3.0,
1360            &mut c,
1361        );
1362        assert!(approx_eq_vec(&c, &[32.0, 64.0, 96.0, 128.0]));
1363    }
1364
1365    #[test]
1366    fn test_dgemm_non_square() {
1367        // A = [[1, 2, 3], [4, 5, 6]] (2x3)
1368        // B = [[7, 8], [9, 10], [11, 12]] (3x2)
1369        // C = A * B (2x2)
1370        let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1371        let b = vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0];
1372        let mut c = vec![0.0; 4];
1373        dgemm(
1374            Transpose::NoTrans,
1375            Transpose::NoTrans,
1376            2,
1377            2,
1378            3,
1379            1.0,
1380            &a,
1381            &b,
1382            0.0,
1383            &mut c,
1384        );
1385        // c[0,0] = 1*7 + 2*9 + 3*11 = 58
1386        // c[0,1] = 1*8 + 2*10 + 3*12 = 64
1387        // c[1,0] = 4*7 + 5*9 + 6*11 = 139
1388        // c[1,1] = 4*8 + 5*10 + 6*12 = 154
1389        assert!(approx_eq_vec(&c, &[58.0, 64.0, 139.0, 154.0]));
1390    }
1391
1392    #[test]
1393    fn test_dtrsm_left_lower() {
1394        // L = [[2, 0], [1, 3]]
1395        // Solve L * X = [[4, 6], [5, 9]]
1396        // Column 0: [4, 5] -> [2, 1]
1397        // Column 1: [6, 9] -> [3, 2]
1398        let l = vec![2.0, 0.0, 1.0, 3.0];
1399        let mut b = vec![4.0, 6.0, 5.0, 9.0];
1400        dtrsm(
1401            Side::Left,
1402            UpLo::Lower,
1403            Transpose::NoTrans,
1404            Diag::NonUnit,
1405            2,
1406            2,
1407            1.0,
1408            &l,
1409            &mut b,
1410        );
1411        assert!(approx_eq_vec(&b, &[2.0, 3.0, 1.0, 2.0]));
1412    }
1413
1414    #[test]
1415    fn test_dtrsm_with_alpha() {
1416        // L = [[2, 0], [1, 3]]
1417        // Solve L * X = 2 * [[4, 6], [5, 9]] = [[8, 12], [10, 18]]
1418        let l = vec![2.0, 0.0, 1.0, 3.0];
1419        let mut b = vec![4.0, 6.0, 5.0, 9.0];
1420        dtrsm(
1421            Side::Left,
1422            UpLo::Lower,
1423            Transpose::NoTrans,
1424            Diag::NonUnit,
1425            2,
1426            2,
1427            2.0,
1428            &l,
1429            &mut b,
1430        );
1431        assert!(approx_eq_vec(&b, &[4.0, 6.0, 2.0, 4.0]));
1432    }
1433
1434    // =============================================================================
1435    // LP Integration Tests
1436    // =============================================================================
1437
1438    #[test]
1439    fn test_blas_lp_config() {
1440        let config = BlasLPConfig::default();
1441        assert_eq!(config.block_size, BLOCK_SIZE);
1442        assert!(config.zero_tolerance > 0.0);
1443        assert!(config.use_pivoting);
1444    }
1445
1446    #[test]
1447    fn test_solve_basis_simple() {
1448        // B = [[2, 1], [1, 3]]
1449        // Solve B * x = [5, 7]
1450        // Expected: x = [8/5, 9/5] = [1.6, 1.8]
1451        let b = vec![2.0, 1.0, 1.0, 3.0];
1452        let mut rhs = vec![5.0, 7.0];
1453        let config = BlasLPConfig::default();
1454        let success = solve_basis(&b, 2, &mut rhs, &config);
1455        assert!(success);
1456        assert!(approx_eq(rhs[0], 1.6));
1457        assert!(approx_eq(rhs[1], 1.8));
1458    }
1459
1460    #[test]
1461    fn test_identity_operations() {
1462        // Test that I * x = x
1463        // Identity matrix in column-major order (BLAS convention)
1464        let i = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
1465        let x = vec![1.0, 2.0, 3.0];
1466        let mut y = vec![0.0, 0.0, 0.0];
1467        dgemv(Transpose::NoTrans, 3, 3, 1.0, &i, &x, 0.0, &mut y);
1468        assert!(approx_eq_vec(&y, &x));
1469    }
1470
1471    #[test]
1472    fn test_large_matrix_blocked() {
1473        // Test blocked GEMM with a matrix larger than block size
1474        let n = 100;
1475        let a: Vec<f64> = (0..n * n).map(|i| (i % 7) as f64).collect();
1476        let b: Vec<f64> = (0..n * n).map(|i| ((i + 3) % 5) as f64).collect();
1477        let mut c = vec![0.0; n * n];
1478
1479        dgemm(
1480            Transpose::NoTrans,
1481            Transpose::NoTrans,
1482            n,
1483            n,
1484            n,
1485            1.0,
1486            &a,
1487            &b,
1488            0.0,
1489            &mut c,
1490        );
1491
1492        // Verify one element manually
1493        let mut expected = 0.0;
1494        for k in 0..n {
1495            expected += a[k] * b[k * n];
1496        }
1497        assert!(approx_eq(c[0], expected));
1498    }
1499}