Skip to main content

oximedia_virtual/math/
matrix.rs

1//! Matrix types for 3D geometry and Kalman filtering.
2
3use super::vector::{Vector3, Vector6};
4use serde::{Deserialize, Serialize};
5use std::ops::{Add, AddAssign, Index, IndexMut, Mul, Sub};
6
7// ---------------------------------------------------------------------------
8// Matrix3  (row-major 3x3)
9// ---------------------------------------------------------------------------
10
11/// A 3x3 matrix stored in row-major order.
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct Matrix3<T> {
14    /// `data[row][col]`
15    pub data: [[T; 3]; 3],
16}
17
18impl<T: Default + Copy> Matrix3<T> {
19    /// All-zero matrix.
20    #[must_use]
21    pub fn zeros() -> Self {
22        Self {
23            data: [[T::default(); 3]; 3],
24        }
25    }
26}
27
28impl Matrix3<f64> {
29    /// Identity.
30    #[must_use]
31    pub fn identity() -> Self {
32        let mut m = Self::zeros();
33        m.data[0][0] = 1.0;
34        m.data[1][1] = 1.0;
35        m.data[2][2] = 1.0;
36        m
37    }
38
39    /// Build from three column vectors (nalgebra compat).
40    #[must_use]
41    pub fn from_columns(c0: &Vector3<f64>, c1: &Vector3<f64>, c2: &Vector3<f64>) -> Self {
42        let mut m = Self::zeros();
43        m.data[0][0] = c0.x;
44        m.data[1][0] = c0.y;
45        m.data[2][0] = c0.z;
46        m.data[0][1] = c1.x;
47        m.data[1][1] = c1.y;
48        m.data[2][1] = c1.z;
49        m.data[0][2] = c2.x;
50        m.data[1][2] = c2.y;
51        m.data[2][2] = c2.z;
52        m
53    }
54
55    /// Build from a column-major iterator (nalgebra compat: column-major flat).
56    #[must_use]
57    pub fn from_iterator<I: IntoIterator<Item = f64>>(iter: I) -> Self {
58        let elems: Vec<f64> = iter.into_iter().collect();
59        let mut m = Self::zeros();
60        // nalgebra stores column-major
61        for (idx, val) in elems.iter().enumerate() {
62            let col = idx / 3;
63            let row = idx % 3;
64            if row < 3 && col < 3 {
65                m.data[row][col] = *val;
66            }
67        }
68        m
69    }
70
71    /// Transpose.
72    #[must_use]
73    pub fn transpose(&self) -> Self {
74        let mut m = Self::zeros();
75        for r in 0..3 {
76            for c in 0..3 {
77                m.data[c][r] = self.data[r][c];
78            }
79        }
80        m
81    }
82
83    /// Determinant.
84    #[must_use]
85    pub fn determinant(&self) -> f64 {
86        let d = &self.data;
87        d[0][0] * (d[1][1] * d[2][2] - d[1][2] * d[2][1])
88            - d[0][1] * (d[1][0] * d[2][2] - d[1][2] * d[2][0])
89            + d[0][2] * (d[1][0] * d[2][1] - d[1][1] * d[2][0])
90    }
91
92    /// Try to invert.
93    #[must_use]
94    pub fn try_inverse(&self) -> Option<Self> {
95        let det = self.determinant();
96        if det.abs() < 1e-15 {
97            return None;
98        }
99        let inv_det = 1.0 / det;
100        let d = &self.data;
101        let mut m = Self::zeros();
102        m.data[0][0] = (d[1][1] * d[2][2] - d[1][2] * d[2][1]) * inv_det;
103        m.data[0][1] = (d[0][2] * d[2][1] - d[0][1] * d[2][2]) * inv_det;
104        m.data[0][2] = (d[0][1] * d[1][2] - d[0][2] * d[1][1]) * inv_det;
105        m.data[1][0] = (d[1][2] * d[2][0] - d[1][0] * d[2][2]) * inv_det;
106        m.data[1][1] = (d[0][0] * d[2][2] - d[0][2] * d[2][0]) * inv_det;
107        m.data[1][2] = (d[0][2] * d[1][0] - d[0][0] * d[1][2]) * inv_det;
108        m.data[2][0] = (d[1][0] * d[2][1] - d[1][1] * d[2][0]) * inv_det;
109        m.data[2][1] = (d[0][1] * d[2][0] - d[0][0] * d[2][1]) * inv_det;
110        m.data[2][2] = (d[0][0] * d[1][1] - d[0][1] * d[1][0]) * inv_det;
111        Some(m)
112    }
113
114    /// SVD decomposition. Returns `(u, sigma, v_t)` where each is `Option<Matrix3>` for
115    /// compatibility with nalgebra's SVD API.  Uses Jacobi one-sided SVD.
116    #[must_use]
117    pub fn svd(&self, compute_u: bool, compute_v: bool) -> Svd3 {
118        svd_3x3(self, compute_u, compute_v)
119    }
120
121    /// Column as Vector3.
122    #[must_use]
123    pub fn column(&self, c: usize) -> Vector3<f64> {
124        Vector3::new(self.data[0][c], self.data[1][c], self.data[2][c])
125    }
126
127    /// Multiply by Vector3.
128    #[must_use]
129    pub fn mul_vec(&self, v: &Vector3<f64>) -> Vector3<f64> {
130        Vector3::new(
131            self.data[0][0] * v.x + self.data[0][1] * v.y + self.data[0][2] * v.z,
132            self.data[1][0] * v.x + self.data[1][1] * v.y + self.data[1][2] * v.z,
133            self.data[2][0] * v.x + self.data[2][1] * v.y + self.data[2][2] * v.z,
134        )
135    }
136}
137
138impl Index<(usize, usize)> for Matrix3<f64> {
139    type Output = f64;
140    fn index(&self, (r, c): (usize, usize)) -> &f64 {
141        &self.data[r][c]
142    }
143}
144
145impl IndexMut<(usize, usize)> for Matrix3<f64> {
146    fn index_mut(&mut self, (r, c): (usize, usize)) -> &mut f64 {
147        &mut self.data[r][c]
148    }
149}
150
151impl Mul for Matrix3<f64> {
152    type Output = Self;
153    fn mul(self, rhs: Self) -> Self {
154        let mut m = Self::zeros();
155        for r in 0..3 {
156            for c in 0..3 {
157                for k in 0..3 {
158                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
159                }
160            }
161        }
162        m
163    }
164}
165
166impl Mul<f64> for Matrix3<f64> {
167    type Output = Self;
168    fn mul(self, rhs: f64) -> Self {
169        let mut m = self;
170        for r in 0..3 {
171            for c in 0..3 {
172                m.data[r][c] *= rhs;
173            }
174        }
175        m
176    }
177}
178
179impl Mul<Matrix3<f64>> for f64 {
180    type Output = Matrix3<f64>;
181    fn mul(self, rhs: Matrix3<f64>) -> Matrix3<f64> {
182        rhs * self
183    }
184}
185
186impl Add for Matrix3<f64> {
187    type Output = Self;
188    fn add(self, rhs: Self) -> Self {
189        let mut m = self;
190        for r in 0..3 {
191            for c in 0..3 {
192                m.data[r][c] += rhs.data[r][c];
193            }
194        }
195        m
196    }
197}
198
199impl AddAssign for Matrix3<f64> {
200    fn add_assign(&mut self, rhs: Self) {
201        for r in 0..3 {
202            for c in 0..3 {
203                self.data[r][c] += rhs.data[r][c];
204            }
205        }
206    }
207}
208
209/// `Matrix3 * Vector3 → Vector3`
210impl Mul<Vector3<f64>> for Matrix3<f64> {
211    type Output = Vector3<f64>;
212    fn mul(self, rhs: Vector3<f64>) -> Vector3<f64> {
213        self.mul_vec(&rhs)
214    }
215}
216
217/// `Matrix3 * Point3 → Point3` (transform point).
218impl Mul<super::vector::Point3<f64>> for Matrix3<f64> {
219    type Output = super::vector::Point3<f64>;
220    fn mul(self, rhs: super::vector::Point3<f64>) -> super::vector::Point3<f64> {
221        let v = self.mul_vec(&rhs.coords());
222        super::vector::Point3::new(v.x, v.y, v.z)
223    }
224}
225
226// ---------------------------------------------------------------------------
227// SVD for 3x3 (Jacobi)
228// ---------------------------------------------------------------------------
229
230/// SVD result for 3x3.
231pub struct Svd3 {
232    pub u: Option<Matrix3<f64>>,
233    pub singular_values: [f64; 3],
234    pub v_t: Option<Matrix3<f64>>,
235}
236
237/// Compute SVD of a 3x3 matrix via one-sided Jacobi rotations.
238fn svd_3x3(a: &Matrix3<f64>, compute_u: bool, compute_v: bool) -> Svd3 {
239    // We compute A^T A, find its eigenvalues/vectors, then derive U.
240    let ata = a.transpose() * *a;
241
242    // Jacobi eigendecomposition of symmetric 3x3 matrix.
243    let (eigenvalues, eigenvectors) = jacobi_eigen_3x3(&ata);
244
245    // Singular values are sqrt of eigenvalues.
246    let mut sigma = [0.0f64; 3];
247    for i in 0..3 {
248        sigma[i] = eigenvalues[i].max(0.0).sqrt();
249    }
250
251    // V = eigenvectors (columns of V).
252    let v = eigenvectors;
253    let v_t = v.transpose();
254
255    // U = A * V * Sigma^-1
256    let u_mat = if compute_u {
257        let mut u = Matrix3::zeros();
258        for col in 0..3 {
259            if sigma[col] > 1e-14 {
260                let v_col = v.column(col);
261                let av = a.mul_vec(&v_col);
262                let inv_s = 1.0 / sigma[col];
263                u.data[0][col] = av.x * inv_s;
264                u.data[1][col] = av.y * inv_s;
265                u.data[2][col] = av.z * inv_s;
266            }
267        }
268        Some(u)
269    } else {
270        None
271    };
272
273    Svd3 {
274        u: u_mat,
275        singular_values: sigma,
276        v_t: if compute_v { Some(v_t) } else { None },
277    }
278}
279
280/// Jacobi eigendecomposition of a symmetric 3x3 matrix.
281/// Returns (eigenvalues sorted descending, eigenvector matrix with columns = eigenvectors).
282fn jacobi_eigen_3x3(m: &Matrix3<f64>) -> ([f64; 3], Matrix3<f64>) {
283    let mut a = *m;
284    let mut v = Matrix3::identity();
285
286    for _ in 0..100 {
287        // Find largest off-diagonal.
288        let mut max_val = 0.0f64;
289        let mut p = 0;
290        let mut q = 1;
291        for i in 0..3 {
292            for j in (i + 1)..3 {
293                if a.data[i][j].abs() > max_val {
294                    max_val = a.data[i][j].abs();
295                    p = i;
296                    q = j;
297                }
298            }
299        }
300        if max_val < 1e-14 {
301            break;
302        }
303
304        // Compute Jacobi rotation angle.
305        let app = a.data[p][p];
306        let aqq = a.data[q][q];
307        let apq = a.data[p][q];
308        let theta = if (app - aqq).abs() < 1e-15 {
309            std::f64::consts::FRAC_PI_4
310        } else {
311            0.5 * (2.0 * apq / (app - aqq)).atan()
312        };
313
314        let c = theta.cos();
315        let s = theta.sin();
316
317        // Apply rotation to A: A' = G^T A G
318        let mut new_a = a;
319        // Update rows/cols p and q.
320        for i in 0..3 {
321            if i != p && i != q {
322                new_a.data[i][p] = c * a.data[i][p] + s * a.data[i][q];
323                new_a.data[p][i] = new_a.data[i][p];
324                new_a.data[i][q] = -s * a.data[i][p] + c * a.data[i][q];
325                new_a.data[q][i] = new_a.data[i][q];
326            }
327        }
328        new_a.data[p][p] = c * c * app + 2.0 * c * s * apq + s * s * aqq;
329        new_a.data[q][q] = s * s * app - 2.0 * c * s * apq + c * c * aqq;
330        new_a.data[p][q] = 0.0;
331        new_a.data[q][p] = 0.0;
332        a = new_a;
333
334        // Update eigenvector matrix: V' = V * G
335        let mut new_v = v;
336        for i in 0..3 {
337            new_v.data[i][p] = c * v.data[i][p] + s * v.data[i][q];
338            new_v.data[i][q] = -s * v.data[i][p] + c * v.data[i][q];
339        }
340        v = new_v;
341    }
342
343    // Sort eigenvalues descending.
344    let mut eigenvalues = [a.data[0][0], a.data[1][1], a.data[2][2]];
345    let mut indices = [0usize, 1, 2];
346    // Simple sort.
347    for i in 0..3 {
348        for j in (i + 1)..3 {
349            if eigenvalues[j] > eigenvalues[i] {
350                eigenvalues.swap(i, j);
351                indices.swap(i, j);
352            }
353        }
354    }
355
356    // Reorder columns of V.
357    let mut v_sorted = Matrix3::zeros();
358    for col in 0..3 {
359        let src = indices[col];
360        for row in 0..3 {
361            v_sorted.data[row][col] = v.data[row][src];
362        }
363    }
364
365    (eigenvalues, v_sorted)
366}
367
368// ---------------------------------------------------------------------------
369// Matrix4  (row-major 4x4)
370// ---------------------------------------------------------------------------
371
372/// A 4x4 matrix stored in row-major order.
373#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
374pub struct Matrix4<T> {
375    pub data: [[T; 4]; 4],
376}
377
378impl<T: Default + Copy> Matrix4<T> {
379    /// All-zero matrix.
380    #[must_use]
381    pub fn zeros() -> Self {
382        Self {
383            data: [[T::default(); 4]; 4],
384        }
385    }
386}
387
388impl Matrix4<f64> {
389    /// 4x4 identity.
390    #[must_use]
391    pub fn identity() -> Self {
392        let mut m = Self::zeros();
393        for i in 0..4 {
394            m.data[i][i] = 1.0;
395        }
396        m
397    }
398
399    /// Transpose.
400    #[must_use]
401    pub fn transpose(&self) -> Self {
402        let mut m = Self::zeros();
403        for r in 0..4 {
404            for c in 0..4 {
405                m.data[c][r] = self.data[r][c];
406            }
407        }
408        m
409    }
410
411    /// Try to invert (Gauss-Jordan).
412    #[must_use]
413    pub fn try_inverse(&self) -> Option<Self> {
414        let mut aug = [[0.0f64; 8]; 4];
415        for r in 0..4 {
416            for c in 0..4 {
417                aug[r][c] = self.data[r][c];
418                aug[r][c + 4] = if r == c { 1.0 } else { 0.0 };
419            }
420        }
421        for col in 0..4 {
422            // Partial pivot.
423            let mut max_row = col;
424            let mut max_val = aug[col][col].abs();
425            for row in (col + 1)..4 {
426                if aug[row][col].abs() > max_val {
427                    max_row = row;
428                    max_val = aug[row][col].abs();
429                }
430            }
431            if max_val < 1e-15 {
432                return None;
433            }
434            aug.swap(col, max_row);
435            let pivot = aug[col][col];
436            for k in 0..8 {
437                aug[col][k] /= pivot;
438            }
439            for row in 0..4 {
440                if row == col {
441                    continue;
442                }
443                let factor = aug[row][col];
444                for k in 0..8 {
445                    aug[row][k] -= factor * aug[col][k];
446                }
447            }
448        }
449        let mut m = Self::zeros();
450        for r in 0..4 {
451            for c in 0..4 {
452                m.data[r][c] = aug[r][c + 4];
453            }
454        }
455        Some(m)
456    }
457
458    /// Fixed 3x3 sub-view (read-only) at (r0, c0). Returns Matrix3.
459    #[must_use]
460    pub fn fixed_view_3x3(&self, r0: usize, c0: usize) -> Matrix3<f64> {
461        let mut m = Matrix3::zeros();
462        for r in 0..3 {
463            for c in 0..3 {
464                m.data[r][c] = self.data[r0 + r][c0 + c];
465            }
466        }
467        m
468    }
469
470    /// Copy a Matrix3 into the 3x3 sub-block at (r0, c0).
471    pub fn set_block_3x3(&mut self, r0: usize, c0: usize, src: &Matrix3<f64>) {
472        for r in 0..3 {
473            for c in 0..3 {
474                self.data[r0 + r][c0 + c] = src.data[r][c];
475            }
476        }
477    }
478
479    /// Copy a Vector3 into the 3x1 sub-block at (r0, c0).
480    pub fn set_block_3x1(&mut self, r0: usize, c0: usize, src: &Vector3<f64>) {
481        self.data[r0][c0] = src.x;
482        self.data[r0 + 1][c0] = src.y;
483        self.data[r0 + 2][c0] = src.z;
484    }
485
486    /// Get 3x1 sub-block as Vector3.
487    #[must_use]
488    pub fn get_block_3x1(&self, r0: usize, c0: usize) -> Vector3<f64> {
489        Vector3::new(
490            self.data[r0][c0],
491            self.data[r0 + 1][c0],
492            self.data[r0 + 2][c0],
493        )
494    }
495
496    /// Multiply this matrix by a 4-element homogeneous vector.
497    #[must_use]
498    pub fn mul_homogeneous(&self, v: &[f64; 4]) -> [f64; 4] {
499        let mut out = [0.0f64; 4];
500        for r in 0..4 {
501            for c in 0..4 {
502                out[r] += self.data[r][c] * v[c];
503            }
504        }
505        out
506    }
507}
508
509impl Index<(usize, usize)> for Matrix4<f64> {
510    type Output = f64;
511    fn index(&self, (r, c): (usize, usize)) -> &f64 {
512        &self.data[r][c]
513    }
514}
515
516impl IndexMut<(usize, usize)> for Matrix4<f64> {
517    fn index_mut(&mut self, (r, c): (usize, usize)) -> &mut f64 {
518        &mut self.data[r][c]
519    }
520}
521
522impl Mul for Matrix4<f64> {
523    type Output = Self;
524    fn mul(self, rhs: Self) -> Self {
525        let mut m = Self::zeros();
526        for r in 0..4 {
527            for c in 0..4 {
528                for k in 0..4 {
529                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
530                }
531            }
532        }
533        m
534    }
535}
536
537/// `Matrix4 * [f64; 4]` — homogeneous transform.
538impl Mul<[f64; 4]> for Matrix4<f64> {
539    type Output = [f64; 4];
540    fn mul(self, rhs: [f64; 4]) -> [f64; 4] {
541        self.mul_homogeneous(&rhs)
542    }
543}
544
545// ---------------------------------------------------------------------------
546// Matrix6  (row-major 6x6)
547// ---------------------------------------------------------------------------
548
549/// A 6x6 matrix (used for Kalman covariance).
550#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
551pub struct Matrix6<T> {
552    pub data: [[T; 6]; 6],
553}
554
555impl<T: Default + Copy> Matrix6<T> {
556    /// All-zero matrix.
557    #[must_use]
558    pub fn zeros() -> Self {
559        Self {
560            data: [[T::default(); 6]; 6],
561        }
562    }
563}
564
565impl Matrix6<f64> {
566    /// 6x6 identity.
567    #[must_use]
568    pub fn identity() -> Self {
569        let mut m = Self::zeros();
570        for i in 0..6 {
571            m.data[i][i] = 1.0;
572        }
573        m
574    }
575
576    /// Transpose.
577    #[must_use]
578    pub fn transpose(&self) -> Self {
579        let mut m = Self::zeros();
580        for r in 0..6 {
581            for c in 0..6 {
582                m.data[c][r] = self.data[r][c];
583            }
584        }
585        m
586    }
587}
588
589impl Index<(usize, usize)> for Matrix6<f64> {
590    type Output = f64;
591    fn index(&self, (r, c): (usize, usize)) -> &f64 {
592        &self.data[r][c]
593    }
594}
595
596impl IndexMut<(usize, usize)> for Matrix6<f64> {
597    fn index_mut(&mut self, (r, c): (usize, usize)) -> &mut f64 {
598        &mut self.data[r][c]
599    }
600}
601
602/// `Matrix6 * f64`
603impl Mul<f64> for Matrix6<f64> {
604    type Output = Self;
605    fn mul(self, rhs: f64) -> Self {
606        let mut m = self;
607        for r in 0..6 {
608            for c in 0..6 {
609                m.data[r][c] *= rhs;
610            }
611        }
612        m
613    }
614}
615
616/// `Matrix6 * Matrix6`
617impl Mul for Matrix6<f64> {
618    type Output = Self;
619    fn mul(self, rhs: Self) -> Self {
620        let mut m = Self::zeros();
621        for r in 0..6 {
622            for c in 0..6 {
623                for k in 0..6 {
624                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
625                }
626            }
627        }
628        m
629    }
630}
631
632/// `Matrix6 * Vector6`
633impl Mul<Vector6<f64>> for Matrix6<f64> {
634    type Output = Vector6<f64>;
635    fn mul(self, rhs: Vector6<f64>) -> Vector6<f64> {
636        let mut out = Vector6::zeros();
637        for r in 0..6 {
638            for c in 0..6 {
639                out.data[r] += self.data[r][c] * rhs.data[c];
640            }
641        }
642        out
643    }
644}
645
646impl Add for Matrix6<f64> {
647    type Output = Self;
648    fn add(self, rhs: Self) -> Self {
649        let mut m = self;
650        for r in 0..6 {
651            for c in 0..6 {
652                m.data[r][c] += rhs.data[r][c];
653            }
654        }
655        m
656    }
657}
658
659impl Sub for Matrix6<f64> {
660    type Output = Self;
661    fn sub(self, rhs: Self) -> Self {
662        let mut m = self;
663        for r in 0..6 {
664            for c in 0..6 {
665                m.data[r][c] -= rhs.data[r][c];
666            }
667        }
668        m
669    }
670}
671
672// ---------------------------------------------------------------------------
673// Matrix3x6  (3 rows, 6 cols) — for Kalman observation matrix
674// ---------------------------------------------------------------------------
675
676/// A 3x6 matrix (Kalman observation / measurement matrix).
677#[derive(Debug, Clone, Copy)]
678pub struct Matrix3x6 {
679    pub data: [[f64; 6]; 3],
680}
681
682impl Matrix3x6 {
683    /// Build from a column-major flat iterator (nalgebra compat).
684    #[must_use]
685    pub fn from_iterator<I: IntoIterator<Item = f64>>(iter: I) -> Self {
686        let elems: Vec<f64> = iter.into_iter().collect();
687        let mut m = Self {
688            data: [[0.0; 6]; 3],
689        };
690        // nalgebra column-major: index = col * nrows + row
691        for (idx, val) in elems.iter().enumerate() {
692            let col = idx / 3;
693            let row = idx % 3;
694            if row < 3 && col < 6 {
695                m.data[row][col] = *val;
696            }
697        }
698        m
699    }
700
701    /// Multiply by Vector6 → Vector3.
702    #[must_use]
703    pub fn mul_vec6(&self, v: &Vector6<f64>) -> Vector3<f64> {
704        let mut out = [0.0f64; 3];
705        for r in 0..3 {
706            for c in 0..6 {
707                out[r] += self.data[r][c] * v.data[c];
708            }
709        }
710        Vector3::new(out[0], out[1], out[2])
711    }
712
713    /// Transpose → 6x3 stored as `Matrix6x3`.
714    #[must_use]
715    pub fn transpose(&self) -> Matrix6x3 {
716        let mut m = Matrix6x3 {
717            data: [[0.0; 3]; 6],
718        };
719        for r in 0..3 {
720            for c in 0..6 {
721                m.data[c][r] = self.data[r][c];
722            }
723        }
724        m
725    }
726}
727
728/// `Matrix3x6 * Vector6 → Vector3`
729impl Mul<Vector6<f64>> for Matrix3x6 {
730    type Output = Vector3<f64>;
731    fn mul(self, rhs: Vector6<f64>) -> Vector3<f64> {
732        self.mul_vec6(&rhs)
733    }
734}
735
736/// `Matrix3x6 * Matrix6 → Matrix3x6` (H * P)
737impl Mul<Matrix6<f64>> for Matrix3x6 {
738    type Output = Self;
739    fn mul(self, rhs: Matrix6<f64>) -> Self {
740        let mut m = Self {
741            data: [[0.0; 6]; 3],
742        };
743        for r in 0..3 {
744            for c in 0..6 {
745                for k in 0..6 {
746                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
747                }
748            }
749        }
750        m
751    }
752}
753
754/// `Matrix3x6 * Matrix6x3 → Matrix3` (H * P * H^T)
755impl Mul<Matrix6x3> for Matrix3x6 {
756    type Output = Matrix3<f64>;
757    fn mul(self, rhs: Matrix6x3) -> Matrix3<f64> {
758        let mut m = Matrix3::zeros();
759        for r in 0..3 {
760            for c in 0..3 {
761                for k in 0..6 {
762                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
763                }
764            }
765        }
766        m
767    }
768}
769
770// ---------------------------------------------------------------------------
771// Matrix6x3  (6 rows, 3 cols) — transpose of Matrix3x6
772// ---------------------------------------------------------------------------
773
774/// A 6x3 matrix (transpose of Kalman observation matrix).
775#[derive(Debug, Clone, Copy)]
776pub struct Matrix6x3 {
777    pub data: [[f64; 3]; 6],
778}
779
780/// `Matrix6x3 * Matrix3 → Matrix6x3` (P * H^T * S^{-1})
781impl Mul<Matrix3<f64>> for Matrix6x3 {
782    type Output = Self;
783    fn mul(self, rhs: Matrix3<f64>) -> Self {
784        let mut m = Self {
785            data: [[0.0; 3]; 6],
786        };
787        for r in 0..6 {
788            for c in 0..3 {
789                for k in 0..3 {
790                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
791                }
792            }
793        }
794        m
795    }
796}
797
798/// `Matrix6<f64> * Matrix6x3 → Matrix6x3` (P * H^T)
799impl Mul<Matrix6x3> for Matrix6<f64> {
800    type Output = Matrix6x3;
801    fn mul(self, rhs: Matrix6x3) -> Matrix6x3 {
802        let mut m = Matrix6x3 {
803            data: [[0.0; 3]; 6],
804        };
805        for r in 0..6 {
806            for c in 0..3 {
807                for k in 0..6 {
808                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
809                }
810            }
811        }
812        m
813    }
814}
815
816// ---------------------------------------------------------------------------
817// KalmanGain (6x3) — helper type for Kalman gain K
818// ---------------------------------------------------------------------------
819
820/// Kalman gain is a 6x3 matrix.  We reuse `Matrix6x3` but provide extra operations.
821impl Matrix6x3 {
822    /// `K * innovation(3x1) → Vector6`
823    #[must_use]
824    pub fn mul_vec3(&self, v: &Vector3<f64>) -> Vector6<f64> {
825        let mut out = Vector6::zeros();
826        let arr = [v.x, v.y, v.z];
827        for r in 0..6 {
828            for c in 0..3 {
829                out.data[r] += self.data[r][c] * arr[c];
830            }
831        }
832        out
833    }
834
835    /// `K(6x3) * H(3x6) → Matrix6`
836    #[must_use]
837    pub fn mul_3x6(&self, rhs: &Matrix3x6) -> Matrix6<f64> {
838        let mut m = Matrix6::zeros();
839        for r in 0..6 {
840            for c in 0..6 {
841                for k in 0..3 {
842                    m.data[r][c] += self.data[r][k] * rhs.data[k][c];
843                }
844            }
845        }
846        m
847    }
848}
849
850// ---------------------------------------------------------------------------
851// Tests
852// ---------------------------------------------------------------------------
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857
858    #[test]
859    fn test_matrix3_identity_determinant() {
860        let m = Matrix3::identity();
861        assert!((m.determinant() - 1.0).abs() < 1e-10);
862    }
863
864    #[test]
865    fn test_matrix3_inverse() {
866        let m = Matrix3::identity();
867        let inv = m.try_inverse().expect("should succeed in test");
868        assert!((inv.data[0][0] - 1.0).abs() < 1e-10);
869    }
870
871    #[test]
872    fn test_matrix4_inverse() {
873        let m = Matrix4::identity();
874        let inv = m.try_inverse().expect("should succeed in test");
875        for i in 0..4 {
876            assert!((inv.data[i][i] - 1.0).abs() < 1e-10);
877        }
878    }
879
880    #[test]
881    fn test_matrix6_mul_identity() {
882        let m = Matrix6::identity();
883        let result = m * m;
884        for i in 0..6 {
885            assert!((result.data[i][i] - 1.0).abs() < 1e-10);
886        }
887    }
888
889    #[test]
890    fn test_svd_identity() {
891        let m = Matrix3::identity();
892        let svd = m.svd(true, true);
893        for i in 0..3 {
894            assert!((svd.singular_values[i] - 1.0).abs() < 1e-6);
895        }
896    }
897
898    #[test]
899    fn test_matrix3x6_mul_vec6() {
900        // Column-major layout: col0=[1,0,0], col1=[0,1,0], col2=[0,0,1], cols 3-5 = zeros
901        // → identity block [[1,0,0,0,0,0], [0,1,0,0,0,0], [0,0,1,0,0,0]]
902        let h = Matrix3x6::from_iterator([
903            1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
904            0.0,
905        ]);
906        let v = Vector6 {
907            data: [10.0, 20.0, 30.0, 40.0, 50.0, 60.0],
908        };
909        let result = h * v;
910        assert!((result.x - 10.0).abs() < 1e-10);
911        assert!((result.y - 20.0).abs() < 1e-10);
912        assert!((result.z - 30.0).abs() < 1e-10);
913    }
914}