Skip to main content

qualia_core_db/solvers/linear_algebra/
mod.rs

1//! Linear Algebra & Matrix Solvers - Zero-Allocation Implementation
2//!
3//! This module provides fixed-size stack-based linear algebra solvers for
4//! eigenvalue problems, linear systems, and tensor operations suitable for
5//! the #![no_std] environment of Qualia-DB.
6
7use crate::solvers::SolversError as ExecutionError;
8use crate::solvers::{SolverConfig, SolverResult, SolverState};
9
10/// Dynamic-size, caller-owned-buffer decompositions (nalgebra-parity, zero-heap).
11pub mod cholesky;
12/// Symmetric eigendecomposition — closed-form 3×3 + general Jacobi (caller-owned).
13pub mod eigen;
14/// Dynamic-size general matrix multiply (caller-owned, zero-heap) — the canonical
15/// dense-GEMM core the specialized libs and the GPU `coop_gemv` backend share.
16pub mod gemm;
17/// Dynamic LU decomposition (partial pivoting) + determinant — canonical `n×n` LU.
18pub mod lu;
19/// Householder QR factorisation + least-squares solve (caller-owned, zero-heap).
20pub mod qr;
21/// Matrix-spectral bridge: characteristic polynomial + general (non-symmetric) eigenvalues.
22pub mod spectral;
23/// Thin singular value decomposition `A = U·Σ·Vᵀ` (via `AᵀA` eigendecomposition).
24pub mod svd;
25/// Element-wise vector ops (add / Hadamard / scale / axpy) — residual stream + gated activations.
26pub mod vector;
27
28/// Fixed-size 4x4 matrix for stack-based operations
29#[repr(C)]
30#[derive(Clone, Copy)]
31pub struct Matrix4x4 {
32    /// Matrix elements in row-major order
33    pub data: [[f64; 4]; 4],
34}
35
36/// Fixed-size 4-element vector
37#[repr(C)]
38#[derive(Clone, Copy)]
39pub struct Vector4 {
40    /// Vector elements
41    pub data: [f64; 4],
42}
43
44/// Fixed-size 3x3x3 tensor
45#[repr(C)]
46#[derive(Clone, Copy)]
47pub struct Tensor3x3x3 {
48    /// Tensor elements
49    pub data: [[[f64; 3]; 3]; 3],
50}
51
52/// Lanczos eigensolver for finding lowest eigenvalues
53#[repr(C)]
54pub struct FixedLanczosEigensolver {
55    /// Current iteration count
56    pub iteration: u32,
57    /// Tridiagonal matrix elements
58    pub alpha: [f64; 100],
59    pub beta: [f64; 100],
60    /// Lanczos vectors (only store 3 at a time)
61    pub vectors: [Vector4; 3],
62    /// Eigenvalues
63    pub eigenvalues: [f64; 4],
64    /// Solver configuration
65    pub config: SolverConfig,
66    /// Solver state
67    pub solver_state: SolverState,
68}
69
70/// Static LU decomposition solver
71#[repr(C)]
72pub struct StaticLuDecomposition {
73    /// Matrix being decomposed (overwritten with L and U)
74    pub matrix: Matrix4x4,
75    /// Permutation vector
76    pub permutation: [usize; 4],
77    /// Determinant sign
78    pub parity: i32,
79    /// Solver configuration
80    pub config: SolverConfig,
81    /// Solver state
82    pub solver_state: SolverState,
83}
84
85/// Constant tensor contraction solver
86#[repr(C)]
87pub struct ConstTensorContractor {
88    /// Input tensor A
89    pub tensor_a: Tensor3x3x3,
90    /// Input tensor B
91    pub tensor_b: Tensor3x3x3,
92    /// Result tensor
93    pub result: Tensor3x3x3,
94    /// Contraction indices
95    pub contraction_indices: [(usize, usize); 3],
96    /// Solver configuration
97    pub config: SolverConfig,
98    /// Solver state
99    pub solver_state: SolverState,
100}
101
102impl Matrix4x4 {
103    /// Create new zero matrix
104    pub const fn zero() -> Self {
105        Self {
106            data: [[0.0; 4]; 4],
107        }
108    }
109
110    /// Create identity matrix
111    pub const fn identity() -> Self {
112        Self {
113            data: [
114                [1.0, 0.0, 0.0, 0.0],
115                [0.0, 1.0, 0.0, 0.0],
116                [0.0, 0.0, 1.0, 0.0],
117                [0.0, 0.0, 0.0, 1.0],
118            ],
119        }
120    }
121
122    /// Get element at (i, j)
123    pub fn get(&self, i: usize, j: usize) -> f64 {
124        self.data[i][j]
125    }
126
127    /// Set element at (i, j)
128    pub fn set(&mut self, i: usize, j: usize, value: f64) {
129        self.data[i][j] = value;
130    }
131
132    /// Matrix-vector multiplication
133    pub fn multiply_vector(&self, v: &Vector4) -> Vector4 {
134        let mut result = Vector4::zero();
135
136        for i in 0..4 {
137            let mut sum = 0.0;
138            for j in 0..4 {
139                sum += self.data[i][j] * v.data[j];
140            }
141            result.data[i] = sum;
142        }
143
144        result
145    }
146
147    /// Matrix-matrix multiplication
148    pub fn multiply_matrix(&self, other: &Matrix4x4) -> Matrix4x4 {
149        let mut result = Matrix4x4::zero();
150
151        for i in 0..4 {
152            for j in 0..4 {
153                let mut sum = 0.0;
154                for k in 0..4 {
155                    sum += self.data[i][k] * other.data[k][j];
156                }
157                result.data[i][j] = sum;
158            }
159        }
160
161        result
162    }
163
164    /// Transpose matrix
165    pub fn transpose(&self) -> Matrix4x4 {
166        let mut result = Matrix4x4::zero();
167
168        for i in 0..4 {
169            for j in 0..4 {
170                result.data[i][j] = self.data[j][i];
171            }
172        }
173
174        result
175    }
176
177    /// Calculate determinant
178    pub fn determinant(&self) -> f64 {
179        // Use cofactor expansion for 4x4
180        let mut det = 0.0;
181
182        for i in 0..4 {
183            let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
184            let minor = self.minor(0, i);
185            det += sign * self.data[0][i] * minor.determinant_3x3();
186        }
187
188        det
189    }
190
191    /// Calculate 3x3 minor
192    fn minor(&self, row: usize, col: usize) -> Matrix4x4 {
193        let mut result = Matrix4x4::zero();
194        let mut r = 0;
195
196        for i in 0..4 {
197            if i == row {
198                continue;
199            }
200            let mut c = 0;
201            for j in 0..4 {
202                if j == col {
203                    continue;
204                }
205                result.data[r][c] = self.data[i][j];
206                c += 1;
207            }
208            r += 1;
209        }
210
211        result
212    }
213
214    /// Calculate 3x3 determinant
215    fn determinant_3x3(&self) -> f64 {
216        self.data[0][0] * (self.data[1][1] * self.data[2][2] - self.data[1][2] * self.data[2][1])
217            - self.data[0][1]
218                * (self.data[1][0] * self.data[2][2] - self.data[1][2] * self.data[2][0])
219            + self.data[0][2]
220                * (self.data[1][0] * self.data[2][1] - self.data[1][1] * self.data[2][0])
221    }
222}
223
224impl Vector4 {
225    /// Create zero vector
226    pub const fn zero() -> Self {
227        Self { data: [0.0; 4] }
228    }
229
230    /// Create vector from array
231    pub const fn from_array(data: [f64; 4]) -> Self {
232        Self { data }
233    }
234
235    /// Get element
236    pub fn get(&self, i: usize) -> f64 {
237        self.data[i]
238    }
239
240    /// Set element
241    pub fn set(&mut self, i: usize, value: f64) {
242        self.data[i] = value;
243    }
244
245    /// Vector dot product
246    pub fn dot(&self, other: &Vector4) -> f64 {
247        let mut sum = 0.0;
248        for i in 0..4 {
249            sum += self.data[i] * other.data[i];
250        }
251        sum
252    }
253
254    /// Vector norm (L2)
255    pub fn norm(&self) -> f64 {
256        self.dot(self).sqrt()
257    }
258
259    /// Normalize vector
260    pub fn normalize(&self) -> Vector4 {
261        let norm = self.norm();
262        if norm > 1e-10 {
263            Vector4::from_array([
264                self.data[0] / norm,
265                self.data[1] / norm,
266                self.data[2] / norm,
267                self.data[3] / norm,
268            ])
269        } else {
270            *self
271        }
272    }
273
274    /// Vector addition
275    pub fn add(&self, other: &Vector4) -> Vector4 {
276        Vector4::from_array([
277            self.data[0] + other.data[0],
278            self.data[1] + other.data[1],
279            self.data[2] + other.data[2],
280            self.data[3] + other.data[3],
281        ])
282    }
283
284    /// Vector subtraction
285    pub fn subtract(&self, other: &Vector4) -> Vector4 {
286        Vector4::from_array([
287            self.data[0] - other.data[0],
288            self.data[1] - other.data[1],
289            self.data[2] - other.data[2],
290            self.data[3] - other.data[3],
291        ])
292    }
293
294    /// Scalar multiplication
295    pub fn scale(&self, scalar: f64) -> Vector4 {
296        Vector4::from_array([
297            self.data[0] * scalar,
298            self.data[1] * scalar,
299            self.data[2] * scalar,
300            self.data[3] * scalar,
301        ])
302    }
303}
304
305impl Tensor3x3x3 {
306    /// Create zero tensor
307    pub const fn zero() -> Self {
308        Self {
309            data: [[[0.0; 3]; 3]; 3],
310        }
311    }
312
313    /// Get element at (i, j, k)
314    pub fn get(&self, i: usize, j: usize, k: usize) -> f64 {
315        self.data[i][j][k]
316    }
317
318    /// Set element at (i, j, k)
319    pub fn set(&mut self, i: usize, j: usize, k: usize, value: f64) {
320        self.data[i][j][k] = value;
321    }
322
323    /// Contract with another tensor
324    pub fn contract(&self, other: &Tensor3x3x3, indices: &[(usize, usize); 3]) -> Tensor3x3x3 {
325        let mut result = Tensor3x3x3::zero();
326
327        // Perform contraction along specified indices
328        for i in 0..3 {
329            for j in 0..3 {
330                for k in 0..3 {
331                    let mut sum = 0.0;
332                    for (idx_a, idx_b) in indices {
333                        sum += self.get(i, j, *idx_a) * other.get(*idx_b, j, k);
334                    }
335                    result.set(i, j, k, sum);
336                }
337            }
338        }
339
340        result
341    }
342}
343
344impl FixedLanczosEigensolver {
345    /// Create new Lanczos eigensolver
346    pub fn new(config: SolverConfig) -> Self {
347        Self {
348            iteration: 0,
349            alpha: [0.0; 100],
350            beta: [0.0; 100],
351            vectors: [Vector4::zero(); 3],
352            eigenvalues: [0.0; 4],
353            config,
354            solver_state: SolverState::default(),
355        }
356    }
357
358    /// Find lowest eigenvalues of symmetric matrix
359    pub fn find_lowest_eigenvalues(
360        &mut self,
361        matrix: &Matrix4x4,
362        num_eigenvalues: usize,
363    ) -> SolverResult<[f64; 4]> {
364        self.iteration = 0;
365        self.solver_state.converged = false;
366
367        // Initialize with random vector
368        self.vectors[0] = Vector4::from_array([1.0, 0.0, 0.0, 0.0]);
369        self.vectors[0] = self.vectors[0].normalize();
370
371        // Perform Lanczos iterations
372        while self.iteration < self.config.max_iterations.min(100) {
373            // Compute matrix-vector product
374            let w = matrix.multiply_vector(&self.vectors[0]);
375
376            // Compute alpha = v_i^T * A * v_i
377            let alpha_i = self.vectors[0].dot(&w);
378            self.alpha[self.iteration as usize] = alpha_i;
379
380            // Compute w = w - alpha_i * v_i - beta_{i-1} * v_{i-1}
381            let mut w_new = w.subtract(&self.vectors[0].scale(alpha_i));
382            if self.iteration > 0 {
383                w_new = w_new
384                    .subtract(&self.vectors[1].scale(self.beta[(self.iteration - 1) as usize]));
385            }
386
387            // Compute beta_i = ||w||
388            let beta_i = w_new.norm();
389            self.beta[self.iteration as usize] = beta_i;
390
391            // Check convergence
392            self.iteration += 1;
393
394            if beta_i < self.config.tolerance {
395                self.solver_state.converged = true;
396                break;
397            }
398
399            // Normalize and update vectors
400            self.vectors[2] = self.vectors[1];
401            self.vectors[1] = self.vectors[0];
402            self.vectors[0] = w_new.normalize();
403        }
404
405        // Extract eigenvalues from tridiagonal matrix
406        self.extract_eigenvalues_from_tridiagonal(num_eigenvalues)?;
407
408        Ok(self.eigenvalues)
409    }
410
411    /// Extract eigenvalues from tridiagonal matrix using QR algorithm
412    fn extract_eigenvalues_from_tridiagonal(&mut self, num_eigenvalues: usize) -> SolverResult<()> {
413        let n = self.iteration as usize;
414        if n == 0 {
415            return Err(ExecutionError::InvalidParameters);
416        }
417        if n > 100 {
418            return Err(ExecutionError::InvalidDimension);
419        }
420
421        // Construct dense n x n matrix for the tridiagonal system
422        let mut tridiag = [0.0; 100 * 100];
423        for i in 0..n {
424            tridiag[i * n + i] = self.alpha[i];
425            if i < n - 1 {
426                tridiag[i * n + i + 1] = self.beta[i];
427                tridiag[(i + 1) * n + i] = self.beta[i];
428            }
429        }
430
431        let mut eigvecs = [0.0; 100 * 100];
432
433        // solve eigensystem for the n x n block
434        crate::solvers::linear_algebra::eigen::symmetric_eigen(
435            n,
436            &mut tridiag[..n * n],
437            &mut eigvecs[..n * n],
438        )?;
439
440        // Extract eigenvalues from diagonal
441        let mut eigs = [0.0; 100];
442        for i in 0..n {
443            eigs[i] = tridiag[i * n + i];
444        }
445
446        // Sort ascending to get lowest eigenvalues
447        eigs[..n].sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
448
449        for i in 0..num_eigenvalues.min(4).min(n) {
450            self.eigenvalues[i] = eigs[i];
451        }
452
453        Ok(())
454    }
455}
456
457impl StaticLuDecomposition {
458    /// Create new LU decomposition solver
459    pub fn new(config: SolverConfig) -> Self {
460        Self {
461            matrix: Matrix4x4::zero(),
462            permutation: [0, 1, 2, 3],
463            parity: 1,
464            config,
465            solver_state: SolverState::default(),
466        }
467    }
468
469    /// Decompose matrix and solve linear system Ax = b
470    pub fn solve(&mut self, matrix: &Matrix4x4, b: &Vector4) -> SolverResult<Vector4> {
471        // Copy matrix for decomposition
472        self.matrix = *matrix;
473
474        // Perform LU decomposition with partial pivoting
475        self.lu_decompose()?;
476
477        // Solve using forward/backward substitution
478        self.solve_lu(b)
479    }
480
481    /// Perform LU decomposition with partial pivoting
482    fn lu_decompose(&mut self) -> SolverResult<()> {
483        self.parity = 1;
484
485        for i in 0..4 {
486            // Find pivot
487            let pivot_row = self.find_pivot(i)?;
488
489            // Swap rows if necessary
490            if pivot_row != i {
491                self.swap_rows(i, pivot_row);
492                self.parity = -self.parity;
493            }
494
495            // Eliminate column
496            for j in i + 1..4 {
497                let multiplier = self.matrix.data[j][i] / self.matrix.data[i][i];
498                self.matrix.data[j][i] = multiplier;
499
500                for k in i + 1..4 {
501                    self.matrix.data[j][k] -= multiplier * self.matrix.data[i][k];
502                }
503            }
504        }
505
506        Ok(())
507    }
508
509    /// Find pivot row
510    fn find_pivot(&self, col: usize) -> SolverResult<usize> {
511        let mut max_row = col;
512        let mut max_val = self.matrix.data[col][col].abs();
513
514        for i in col + 1..4 {
515            let val = self.matrix.data[i][col].abs();
516            if val > max_val {
517                max_val = val;
518                max_row = i;
519            }
520        }
521
522        if max_val < 1e-10 {
523            return Err(ExecutionError::SingularMatrix);
524        }
525
526        Ok(max_row)
527    }
528
529    /// Swap two rows
530    fn swap_rows(&mut self, i: usize, j: usize) {
531        for k in 0..4 {
532            let temp = self.matrix.data[i][k];
533            self.matrix.data[i][k] = self.matrix.data[j][k];
534            self.matrix.data[j][k] = temp;
535        }
536
537        // Update permutation
538        self.permutation.swap(i, j);
539    }
540
541    /// Solve using LU decomposition
542    fn solve_lu(&self, b: &Vector4) -> SolverResult<Vector4> {
543        let mut x = *b;
544
545        // Forward substitution (solve Ly = Pb)
546        for i in 0..4 {
547            let mut sum = 0.0;
548            for j in 0..i {
549                sum += self.matrix.data[i][j] * x.data[j];
550            }
551            x.data[i] -= sum;
552        }
553
554        // Backward substitution (solve Ux = y)
555        for i in (0..4).rev() {
556            let mut sum = 0.0;
557            for j in i + 1..4 {
558                sum += self.matrix.data[i][j] * x.data[j];
559            }
560            x.data[i] = (x.data[i] - sum) / self.matrix.data[i][i];
561        }
562
563        Ok(x)
564    }
565
566    /// Calculate determinant from LU decomposition
567    pub fn determinant(&self) -> f64 {
568        let mut det = 1.0;
569        for i in 0..4 {
570            det *= self.matrix.data[i][i];
571        }
572        det * self.parity as f64
573    }
574}
575
576impl ConstTensorContractor {
577    /// Create new tensor contractor
578    pub fn new(config: SolverConfig) -> Self {
579        Self {
580            tensor_a: Tensor3x3x3::zero(),
581            tensor_b: Tensor3x3x3::zero(),
582            result: Tensor3x3x3::zero(),
583            contraction_indices: [(0, 0), (1, 1), (2, 2)],
584            config,
585            solver_state: SolverState::default(),
586        }
587    }
588
589    /// Contract two tensors
590    pub fn contract(
591        &mut self,
592        tensor_a: &Tensor3x3x3,
593        tensor_b: &Tensor3x3x3,
594        indices: &[(usize, usize); 3],
595    ) -> SolverResult<Tensor3x3x3> {
596        self.tensor_a = *tensor_a;
597        self.tensor_b = *tensor_b;
598        self.contraction_indices = *indices;
599
600        // Perform contraction
601        self.result = self
602            .tensor_a
603            .contract(&self.tensor_b, &self.contraction_indices);
604
605        self.solver_state.converged = true;
606
607        Ok(self.result)
608    }
609
610    /// Get result tensor
611    pub fn get_result(&self) -> Tensor3x3x3 {
612        self.result
613    }
614}
615
616impl Default for Matrix4x4 {
617    fn default() -> Self {
618        Self::identity()
619    }
620}
621
622impl Default for Vector4 {
623    fn default() -> Self {
624        Self::zero()
625    }
626}
627
628impl Default for Tensor3x3x3 {
629    fn default() -> Self {
630        Self::zero()
631    }
632}
633
634impl Default for FixedLanczosEigensolver {
635    fn default() -> Self {
636        Self::new(SolverConfig::default())
637    }
638}
639
640impl Default for StaticLuDecomposition {
641    fn default() -> Self {
642        Self::new(SolverConfig::default())
643    }
644}
645
646impl Default for ConstTensorContractor {
647    fn default() -> Self {
648        Self::new(SolverConfig::default())
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655
656    #[test]
657    fn test_matrix4x4_operations() {
658        let mut m = Matrix4x4::identity();
659        m.set(0, 1, 2.0);
660        m.set(1, 0, 3.0);
661
662        let v = Vector4::from_array([1.0, 2.0, 3.0, 4.0]);
663        let result = m.multiply_vector(&v);
664
665        assert_eq!(result.data[0], 1.0 + 2.0 * 2.0); // 1 + 4 = 5
666        assert_eq!(result.data[1], 3.0 * 1.0 + 2.0); // 3 + 2 = 5
667    }
668
669    #[test]
670    fn test_vector_operations() {
671        let v1 = Vector4::from_array([1.0, 2.0, 3.0, 4.0]);
672        let v2 = Vector4::from_array([2.0, 3.0, 4.0, 5.0]);
673
674        let dot = v1.dot(&v2);
675        assert_eq!(dot, 1.0 * 2.0 + 2.0 * 3.0 + 3.0 * 4.0 + 4.0 * 5.0);
676
677        let norm = v1.norm();
678        assert!((norm - (1.0_f64 * 1.0 + 2.0 * 2.0 + 3.0 * 3.0 + 4.0 * 4.0).sqrt()).abs() < 1e-10);
679    }
680
681    #[test]
682    fn test_lu_decomposition() {
683        let mut lu = StaticLuDecomposition::new(SolverConfig::default());
684
685        // Test matrix: [[2, 1], [1, 2]] extended to 4x4
686        let mut m = Matrix4x4::identity();
687        m.set(0, 0, 2.0);
688        m.set(0, 1, 1.0);
689        m.set(1, 0, 1.0);
690        m.set(1, 1, 2.0);
691
692        let b = Vector4::from_array([3.0, 3.0, 0.0, 0.0]);
693        let result = lu.solve(&m, &b);
694
695        assert!(result.is_ok());
696        let x = result.unwrap();
697        assert!((x.data[0] - 1.0).abs() < 1e-10);
698        assert!((x.data[1] - 1.0).abs() < 1e-10);
699    }
700
701    #[test]
702    fn test_tensor_contraction() {
703        let mut contractor = ConstTensorContractor::new(SolverConfig::default());
704
705        let mut tensor_a = Tensor3x3x3::zero();
706        let mut tensor_b = Tensor3x3x3::zero();
707
708        // Set some values
709        tensor_a.set(0, 0, 0, 1.0);
710        tensor_a.set(1, 1, 1, 2.0);
711        tensor_b.set(0, 0, 0, 3.0);
712        tensor_b.set(1, 1, 1, 4.0);
713
714        let indices = [(0, 0), (1, 1), (2, 2)];
715        let result = contractor.contract(&tensor_a, &tensor_b, &indices);
716
717        assert!(result.is_ok());
718    }
719
720    #[test]
721    fn test_zero_allocation_guarantee() {
722        // assert_eq!(core::mem::size_of::<Matrix4x4>(), ...);
723        // assert_eq!(core::mem::size_of::<Vector4>(), ...);
724        // assert_eq!(core::mem::size_of::<Tensor3x3x3>(), ...);
725        // assert_eq!(core::mem::size_of::<FixedLanczosEigensolver>(), ...);
726        // assert_eq!(core::mem::size_of::<StaticLuDecomposition>(), ...);
727        // assert_eq!(core::mem::size_of::<ConstTensorContractor>(), ...);
728    }
729}