Skip to main content

embedded_dsp/
matrix.rs

1//! Matrix operations (addition, subtraction, multiplication, scale, transpose, inverse, complex matrix multiplication).
2
3use crate::types::*;
4
5/// Matrix structure wrapping a slice of data in row-major order.
6#[derive(Debug, Clone, Copy)]
7pub struct MatrixInstance<'a, T> {
8    pub num_rows: u16,
9    pub num_cols: u16,
10    pub data: &'a [T],
11}
12
13impl<'a, T> MatrixInstance<'a, T> {
14    pub fn new(num_rows: u16, num_cols: u16, data: &'a [T]) -> Self {
15        Self {
16            num_rows,
17            num_cols,
18            data,
19        }
20    }
21}
22
23/// Mutable Matrix structure wrapping a mutable slice of data in row-major order.
24#[derive(Debug)]
25pub struct MatrixInstanceMut<'a, T> {
26    pub num_rows: u16,
27    pub num_cols: u16,
28    pub data: &'a mut [T],
29}
30
31impl<'a, T> MatrixInstanceMut<'a, T> {
32    pub fn new(num_rows: u16, num_cols: u16, data: &'a mut [T]) -> Self {
33        Self {
34            num_rows,
35            num_cols,
36            data,
37        }
38    }
39}
40
41// --- Matrix Addition ---
42
43pub fn mat_add_f32(
44    a: &MatrixInstance<f32>,
45    b: &MatrixInstance<f32>,
46    out: &mut MatrixInstanceMut<f32>,
47) -> Status {
48    if a.num_rows != b.num_rows
49        || a.num_cols != b.num_cols
50        || a.num_rows != out.num_rows
51        || a.num_cols != out.num_cols
52    {
53        return Status::SizeMismatch;
54    }
55    let total = (a.num_rows as usize) * (a.num_cols as usize);
56    if a.data.len() < total || b.data.len() < total || out.data.len() < total {
57        return Status::LengthError;
58    }
59    for i in 0..total {
60        out.data[i] = a.data[i] + b.data[i];
61    }
62    Status::Success
63}
64
65pub fn mat_add_q31(
66    a: &MatrixInstance<q31>,
67    b: &MatrixInstance<q31>,
68    out: &mut MatrixInstanceMut<q31>,
69) -> Status {
70    if a.num_rows != b.num_rows
71        || a.num_cols != b.num_cols
72        || a.num_rows != out.num_rows
73        || a.num_cols != out.num_cols
74    {
75        return Status::SizeMismatch;
76    }
77    let total = (a.num_rows as usize) * (a.num_cols as usize);
78    if a.data.len() < total || b.data.len() < total || out.data.len() < total {
79        return Status::LengthError;
80    }
81    for i in 0..total {
82        out.data[i] = a.data[i].saturating_add(b.data[i]);
83    }
84    Status::Success
85}
86
87pub fn mat_add_q15(
88    a: &MatrixInstance<q15>,
89    b: &MatrixInstance<q15>,
90    out: &mut MatrixInstanceMut<q15>,
91) -> Status {
92    if a.num_rows != b.num_rows
93        || a.num_cols != b.num_cols
94        || a.num_rows != out.num_rows
95        || a.num_cols != out.num_cols
96    {
97        return Status::SizeMismatch;
98    }
99    let total = (a.num_rows as usize) * (a.num_cols as usize);
100    if a.data.len() < total || b.data.len() < total || out.data.len() < total {
101        return Status::LengthError;
102    }
103    for i in 0..total {
104        out.data[i] = a.data[i].saturating_add(b.data[i]);
105    }
106    Status::Success
107}
108
109// --- Matrix Subtraction ---
110
111pub fn mat_sub_f32(
112    a: &MatrixInstance<f32>,
113    b: &MatrixInstance<f32>,
114    out: &mut MatrixInstanceMut<f32>,
115) -> Status {
116    if a.num_rows != b.num_rows
117        || a.num_cols != b.num_cols
118        || a.num_rows != out.num_rows
119        || a.num_cols != out.num_cols
120    {
121        return Status::SizeMismatch;
122    }
123    let total = (a.num_rows as usize) * (a.num_cols as usize);
124    if a.data.len() < total || b.data.len() < total || out.data.len() < total {
125        return Status::LengthError;
126    }
127    for i in 0..total {
128        out.data[i] = a.data[i] - b.data[i];
129    }
130    Status::Success
131}
132
133pub fn mat_sub_q31(
134    a: &MatrixInstance<q31>,
135    b: &MatrixInstance<q31>,
136    out: &mut MatrixInstanceMut<q31>,
137) -> Status {
138    if a.num_rows != b.num_rows
139        || a.num_cols != b.num_cols
140        || a.num_rows != out.num_rows
141        || a.num_cols != out.num_cols
142    {
143        return Status::SizeMismatch;
144    }
145    let total = (a.num_rows as usize) * (a.num_cols as usize);
146    if a.data.len() < total || b.data.len() < total || out.data.len() < total {
147        return Status::LengthError;
148    }
149    for i in 0..total {
150        out.data[i] = a.data[i].saturating_sub(b.data[i]);
151    }
152    Status::Success
153}
154
155pub fn mat_sub_q15(
156    a: &MatrixInstance<q15>,
157    b: &MatrixInstance<q15>,
158    out: &mut MatrixInstanceMut<q15>,
159) -> Status {
160    if a.num_rows != b.num_rows
161        || a.num_cols != b.num_cols
162        || a.num_rows != out.num_rows
163        || a.num_cols != out.num_cols
164    {
165        return Status::SizeMismatch;
166    }
167    let total = (a.num_rows as usize) * (a.num_cols as usize);
168    if a.data.len() < total || b.data.len() < total || out.data.len() < total {
169        return Status::LengthError;
170    }
171    for i in 0..total {
172        out.data[i] = a.data[i].saturating_sub(b.data[i]);
173    }
174    Status::Success
175}
176
177// --- Matrix Multiplication ---
178
179pub fn mat_mult_f32(
180    a: &MatrixInstance<f32>,
181    b: &MatrixInstance<f32>,
182    out: &mut MatrixInstanceMut<f32>,
183) -> Status {
184    if a.num_cols != b.num_rows || a.num_rows != out.num_rows || b.num_cols != out.num_cols {
185        return Status::SizeMismatch;
186    }
187    let rows_a = a.num_rows as usize;
188    let cols_a = a.num_cols as usize;
189    let cols_b = b.num_cols as usize;
190
191    for r in 0..rows_a {
192        for c in 0..cols_b {
193            let mut sum = 0.0f32;
194            for k in 0..cols_a {
195                sum += a.data[r * cols_a + k] * b.data[k * cols_b + c];
196            }
197            out.data[r * cols_b + c] = sum;
198        }
199    }
200    Status::Success
201}
202
203pub fn mat_mult_q31(
204    a: &MatrixInstance<q31>,
205    b: &MatrixInstance<q31>,
206    out: &mut MatrixInstanceMut<q31>,
207) -> Status {
208    if a.num_cols != b.num_rows || a.num_rows != out.num_rows || b.num_cols != out.num_cols {
209        return Status::SizeMismatch;
210    }
211    let rows_a = a.num_rows as usize;
212    let cols_a = a.num_cols as usize;
213    let cols_b = b.num_cols as usize;
214
215    for r in 0..rows_a {
216        for c in 0..cols_b {
217            let mut sum: i64 = 0;
218            for k in 0..cols_a {
219                sum +=
220                    (a.data[r * cols_a + k].to_bits() as i64
221                        * b.data[k * cols_b + c].to_bits() as i64)
222                        >> 31;
223            }
224            out.data[r * cols_b + c] =
225                q31::from_bits(sum.clamp(i32::MIN as i64, i32::MAX as i64) as i32);
226        }
227    }
228    Status::Success
229}
230
231pub fn mat_mult_q15(
232    a: &MatrixInstance<q15>,
233    b: &MatrixInstance<q15>,
234    out: &mut MatrixInstanceMut<q15>,
235) -> Status {
236    if a.num_cols != b.num_rows || a.num_rows != out.num_rows || b.num_cols != out.num_cols {
237        return Status::SizeMismatch;
238    }
239    let rows_a = a.num_rows as usize;
240    let cols_a = a.num_cols as usize;
241    let cols_b = b.num_cols as usize;
242
243    for r in 0..rows_a {
244        for c in 0..cols_b {
245            let mut sum: i32 = 0;
246            for k in 0..cols_a {
247                sum += (a.data[r * cols_a + k].to_bits() as i32
248                    * b.data[k * cols_b + c].to_bits() as i32)
249                    >> 15;
250            }
251            out.data[r * cols_b + c] =
252                q15::from_bits(sum.clamp(i16::MIN as i32, i16::MAX as i32) as i16);
253        }
254    }
255    Status::Success
256}
257
258// --- Matrix Scale ---
259
260pub fn mat_scale_f32(
261    src: &MatrixInstance<f32>,
262    scale: f32,
263    out: &mut MatrixInstanceMut<f32>,
264) -> Status {
265    if src.num_rows != out.num_rows || src.num_cols != out.num_cols {
266        return Status::SizeMismatch;
267    }
268    let total = (src.num_rows as usize) * (src.num_cols as usize);
269    for i in 0..total {
270        out.data[i] = src.data[i] * scale;
271    }
272    Status::Success
273}
274
275pub fn mat_scale_q31(
276    src: &MatrixInstance<q31>,
277    scale_fract: q31,
278    shift: i8,
279    out: &mut MatrixInstanceMut<q31>,
280) -> Status {
281    if src.num_rows != out.num_rows || src.num_cols != out.num_cols {
282        return Status::SizeMismatch;
283    }
284    let total = (src.num_rows as usize) * (src.num_cols as usize);
285    crate::basic_math::scale_q31(
286        &src.data[..total],
287        scale_fract,
288        shift,
289        &mut out.data[..total],
290    );
291    Status::Success
292}
293
294pub fn mat_scale_q15(
295    src: &MatrixInstance<q15>,
296    scale_fract: q15,
297    shift: i8,
298    out: &mut MatrixInstanceMut<q15>,
299) -> Status {
300    if src.num_rows != out.num_rows || src.num_cols != out.num_cols {
301        return Status::SizeMismatch;
302    }
303    let total = (src.num_rows as usize) * (src.num_cols as usize);
304    crate::basic_math::scale_q15(
305        &src.data[..total],
306        scale_fract,
307        shift,
308        &mut out.data[..total],
309    );
310    Status::Success
311}
312
313// --- Matrix Transpose ---
314
315pub fn mat_trans_f32(src: &MatrixInstance<f32>, out: &mut MatrixInstanceMut<f32>) -> Status {
316    if src.num_rows != out.num_cols || src.num_cols != out.num_rows {
317        return Status::SizeMismatch;
318    }
319    let rows = src.num_rows as usize;
320    let cols = src.num_cols as usize;
321
322    for r in 0..rows {
323        for c in 0..cols {
324            out.data[c * rows + r] = src.data[r * cols + c];
325        }
326    }
327    Status::Success
328}
329
330pub fn mat_trans_q31(src: &MatrixInstance<q31>, out: &mut MatrixInstanceMut<q31>) -> Status {
331    if src.num_rows != out.num_cols || src.num_cols != out.num_rows {
332        return Status::SizeMismatch;
333    }
334    let rows = src.num_rows as usize;
335    let cols = src.num_cols as usize;
336
337    for r in 0..rows {
338        for c in 0..cols {
339            out.data[c * rows + r] = src.data[r * cols + c];
340        }
341    }
342    Status::Success
343}
344
345pub fn mat_trans_q15(src: &MatrixInstance<q15>, out: &mut MatrixInstanceMut<q15>) -> Status {
346    if src.num_rows != out.num_cols || src.num_cols != out.num_rows {
347        return Status::SizeMismatch;
348    }
349    let rows = src.num_rows as usize;
350    let cols = src.num_cols as usize;
351
352    for r in 0..rows {
353        for c in 0..cols {
354            out.data[c * rows + r] = src.data[r * cols + c];
355        }
356    }
357    Status::Success
358}
359
360// --- Matrix Inverse (f32 Gauss-Jordan Elimination with partial pivoting) ---
361
362pub fn mat_inverse_f32(src: &MatrixInstance<f32>, out: &mut MatrixInstanceMut<f32>) -> Status {
363    if src.num_rows != src.num_cols || out.num_rows != out.num_cols || src.num_rows != out.num_rows
364    {
365        return Status::SizeMismatch;
366    }
367    let n = src.num_rows as usize;
368    if n == 0 {
369        return Status::SizeMismatch;
370    }
371
372    // Stack-allocated scratch buffer for n <= 16, or array for n x 2n augmented matrix
373    // Gauss-Jordan elimination
374    let mut aug = [0.0f32; 16 * 32];
375    if n > 16 {
376        return Status::ArgumentError; // Limit to 16x16 without heap allocation in no_std
377    }
378
379    for r in 0..n {
380        for c in 0..n {
381            aug[r * 2 * n + c] = src.data[r * n + c];
382            aug[r * 2 * n + n + c] = if r == c { 1.0 } else { 0.0 };
383        }
384    }
385
386    for i in 0..n {
387        // Pivot selection
388        let mut max_row = i;
389        let mut max_val = aug[i * 2 * n + i].abs();
390        for r in (i + 1)..n {
391            let val = aug[r * 2 * n + i].abs();
392            if val > max_val {
393                max_val = val;
394                max_row = r;
395            }
396        }
397
398        if max_val < 1e-12 {
399            return Status::Singular;
400        }
401
402        // Swap rows
403        if max_row != i {
404            for c in 0..(2 * n) {
405                aug.swap(i * 2 * n + c, max_row * 2 * n + c);
406            }
407        }
408
409        let pivot = aug[i * 2 * n + i];
410        for c in 0..(2 * n) {
411            aug[i * 2 * n + c] /= pivot;
412        }
413
414        for r in 0..n {
415            if r != i {
416                let factor = aug[r * 2 * n + i];
417                for c in 0..(2 * n) {
418                    let sub = factor * aug[i * 2 * n + c];
419                    aug[r * 2 * n + c] -= sub;
420                }
421            }
422        }
423    }
424
425    for r in 0..n {
426        for c in 0..n {
427            out.data[r * n + c] = aug[r * 2 * n + n + c];
428        }
429    }
430
431    Status::Success
432}
433
434// --- Polynomial Least Squares Regression & Curve Fitting ---
435
436/// Fits a polynomial of degree `degree` ($y = c_0 + c_1 x + c_2 x^2 + \dots + c_d x^d$) to data points $(x_i, y_i)$
437/// using weighted linear least-squares regression.
438///
439/// `x` and `y`: slices of input coordinates (must have equal non-zero length $N \ge \text{degree} + 1$).
440/// `weights`: optional slice of weights $w_i \ge 0$ for each point. If `None`, uniform weights $w_i = 1$ are used.
441/// `degree`: order of the polynomial fit (e.g. 1 for linear, 2 for quadratic). Maximum supported degree is 15.
442/// `out_coeffs`: destination slice of length at least `degree + 1`, populated with $[c_0, c_1, \dots, c_d]$.
443pub fn polynomial_least_squares_fit(
444    x: &[f32],
445    y: &[f32],
446    weights: Option<&[f32]>,
447    degree: usize,
448    out_coeffs: &mut [f32],
449) -> Status {
450    let n = x.len();
451    let m = degree + 1; // Number of coefficients
452
453    if n == 0 || y.len() != n || out_coeffs.len() < m || n < m {
454        return Status::LengthError;
455    }
456    if let Some(w) = weights {
457        if w.len() != n {
458            return Status::LengthError;
459        }
460    }
461    if degree > 15 {
462        return Status::ArgumentError; // Limit for stack-allocated matrix
463    }
464
465    // Build normal equations H * c = v where H is M x M and v is M x 1
466    // Augmented matrix aug of size M x (M + 1)
467    let mut aug = [0.0f32; 16 * 17];
468    let cols = m + 1;
469
470    for i in 0..n {
471        let w = if let Some(weights_slice) = weights {
472            weights_slice[i]
473        } else {
474            1.0f32
475        };
476        let xi = x[i];
477        let yi = y[i];
478
479        // Precompute powers of xi: xi^0, xi^1, ..., xi^(2*degree)
480        let mut x_powers = [1.0f32; 32];
481        for p in 1..(2 * m) {
482            x_powers[p] = x_powers[p - 1] * xi;
483        }
484
485        for j in 0..m {
486            let w_xi_j = w * x_powers[j];
487            for k in 0..m {
488                aug[j * cols + k] += w_xi_j * x_powers[k];
489            }
490            aug[j * cols + m] += w_xi_j * yi;
491        }
492    }
493
494    // Solve via Gauss-Jordan elimination
495    for i in 0..m {
496        // Partial pivot
497        let mut max_row = i;
498        let mut max_val = aug[i * cols + i].abs();
499        for r in (i + 1)..m {
500            let val = aug[r * cols + i].abs();
501            if val > max_val {
502                max_val = val;
503                max_row = r;
504            }
505        }
506
507        if max_val < 1e-12 {
508            return Status::Singular;
509        }
510
511        if max_row != i {
512            for c in 0..cols {
513                aug.swap(i * cols + c, max_row * cols + c);
514            }
515        }
516
517        let pivot = aug[i * cols + i];
518        for c in 0..cols {
519            aug[i * cols + c] /= pivot;
520        }
521
522        for r in 0..m {
523            if r != i {
524                let factor = aug[r * cols + i];
525                for c in 0..cols {
526                    let sub = factor * aug[i * cols + c];
527                    aug[r * cols + c] -= sub;
528                }
529            }
530        }
531    }
532
533    for i in 0..m {
534        out_coeffs[i] = aug[i * cols + m];
535    }
536
537    Status::Success
538}
539
540/// Evaluates a polynomial $P(x) = c_0 + c_1 x + c_2 x^2 + \dots + c_d x^d$ using Horner's method.
541pub fn polynomial_eval_f32(coeffs: &[f32], x: f32) -> f32 {
542    if coeffs.is_empty() {
543        return 0.0;
544    }
545    let mut result = coeffs[coeffs.len() - 1];
546    for &c in coeffs[..coeffs.len() - 1].iter().rev() {
547        result = result * x + c;
548    }
549    result
550}