Skip to main content

numeris/
nalgebra_interop.rs

1//! Conversions between numeris and nalgebra matrix/vector types.
2//!
3//! Enabled by the `nalgebra` cargo feature. Provides zero-copy borrows
4//! via `as_slice` where possible, and cheap `memcpy`-level owned conversions
5//! for fixed-size and dynamic matrices.
6//!
7//! # Layout compatibility
8//!
9//! Both numeris and nalgebra use column-major contiguous storage:
10//! - `Matrix<T, M, N>` stores `[[T; M]; N]` — N columns of M rows
11//! - `nalgebra::SMatrix<T, R, C>` stores `[T; R*C]` column-major
12//!
13//! Owned conversions go through `from_column_slice` / `as_slice`, which is a
14//! single `memcpy` for fixed-size types (≤ 288 bytes for a 6×6 f64 matrix).
15//!
16//! # Vector convention
17//!
18//! numeris `Vector<T, N>` is `Matrix<T, N, 1>` — an N×1 column vector,
19//! matching nalgebra's `SVector<T, N>` = `SMatrix<T, N, 1>` exactly.
20//! The `From` impls for `Matrix<T, M, N>` cover vectors automatically.
21//!
22//! # Examples
23//!
24//! ```
25//! # #[cfg(feature = "nalgebra")] {
26//! use numeris::Matrix;
27//!
28//! let nm = Matrix::new([[1.0_f64, 2.0], [3.0, 4.0]]);
29//! let na: nalgebra::SMatrix<f64, 2, 2> = nm.into();
30//! assert_eq!(na[(0, 0)], 1.0);
31//! assert_eq!(na[(1, 0)], 3.0);
32//!
33//! let back: Matrix<f64, 2, 2> = na.into();
34//! assert_eq!(back, nm);
35//! # }
36//! ```
37
38use crate::traits::{MatrixMut, MatrixRef, Scalar};
39use crate::Matrix;
40
41// ── Fixed-size Matrix ↔ SMatrix ────────────────────────────────────
42
43impl<T: Scalar + nalgebra::Scalar, const M: usize, const N: usize>
44    From<Matrix<T, M, N>> for nalgebra::SMatrix<T, M, N>
45where
46    nalgebra::Const<M>: nalgebra::DimName,
47    nalgebra::Const<N>: nalgebra::DimName,
48{
49    #[inline]
50    fn from(m: Matrix<T, M, N>) -> Self {
51        nalgebra::SMatrix::from_column_slice(m.as_slice())
52    }
53}
54
55impl<T: Scalar + nalgebra::Scalar, const M: usize, const N: usize>
56    From<&Matrix<T, M, N>> for nalgebra::SMatrix<T, M, N>
57where
58    nalgebra::Const<M>: nalgebra::DimName,
59    nalgebra::Const<N>: nalgebra::DimName,
60{
61    #[inline]
62    fn from(m: &Matrix<T, M, N>) -> Self {
63        nalgebra::SMatrix::from_column_slice(m.as_slice())
64    }
65}
66
67impl<T: Scalar + nalgebra::Scalar, const M: usize, const N: usize>
68    From<nalgebra::SMatrix<T, M, N>> for Matrix<T, M, N>
69where
70    nalgebra::Const<M>: nalgebra::DimName,
71    nalgebra::Const<N>: nalgebra::DimName,
72{
73    #[inline]
74    fn from(m: nalgebra::SMatrix<T, M, N>) -> Self {
75        Matrix::from_slice(m.as_slice())
76    }
77}
78
79impl<T: Scalar + nalgebra::Scalar, const M: usize, const N: usize>
80    From<&nalgebra::SMatrix<T, M, N>> for Matrix<T, M, N>
81where
82    nalgebra::Const<M>: nalgebra::DimName,
83    nalgebra::Const<N>: nalgebra::DimName,
84{
85    #[inline]
86    fn from(m: &nalgebra::SMatrix<T, M, N>) -> Self {
87        Matrix::from_slice(m.as_slice())
88    }
89}
90
91// ── Vector ↔ SVector ───────────────────────────────────────────────
92//
93// Vector<T,N> = Matrix<T,N,1> and SVector<T,N> = SMatrix<T,N,1>:
94// already covered by the Matrix<T,M,N> ↔ SMatrix<T,M,N> impls above.
95// No special convenience methods needed.
96
97// ── MatrixRef / MatrixMut for nalgebra::SMatrix ────────────────────
98
99impl<T: Scalar + nalgebra::Scalar, const M: usize, const N: usize>
100    MatrixRef<T> for nalgebra::SMatrix<T, M, N>
101where
102    nalgebra::Const<M>: nalgebra::DimName,
103    nalgebra::Const<N>: nalgebra::DimName,
104{
105    #[inline]
106    fn nrows(&self) -> usize {
107        M
108    }
109
110    #[inline]
111    fn ncols(&self) -> usize {
112        N
113    }
114
115    #[inline]
116    fn get(&self, row: usize, col: usize) -> &T {
117        &self[(row, col)]
118    }
119
120    #[inline]
121    fn col_as_slice(&self, col: usize, row_start: usize) -> &[T] {
122        let start = col * M + row_start;
123        let end = (col + 1) * M;
124        &self.as_slice()[start..end]
125    }
126}
127
128impl<T: Scalar + nalgebra::Scalar, const M: usize, const N: usize>
129    MatrixMut<T> for nalgebra::SMatrix<T, M, N>
130where
131    nalgebra::Const<M>: nalgebra::DimName,
132    nalgebra::Const<N>: nalgebra::DimName,
133{
134    #[inline]
135    fn get_mut(&mut self, row: usize, col: usize) -> &mut T {
136        &mut self[(row, col)]
137    }
138
139    #[inline]
140    fn col_as_mut_slice(&mut self, col: usize, row_start: usize) -> &mut [T] {
141        let start = col * M + row_start;
142        let end = (col + 1) * M;
143        &mut self.as_mut_slice()[start..end]
144    }
145}
146
147// ── Dynamic: DynMatrix ↔ DMatrix, DynVector ↔ DVector ─────────────
148
149#[cfg(feature = "alloc")]
150mod dyn_interop {
151    use crate::dynmatrix::{DynMatrix, DynVector};
152    use crate::traits::{MatrixMut, MatrixRef, Scalar};
153
154    impl<T: Scalar + nalgebra::Scalar> From<DynMatrix<T>> for nalgebra::DMatrix<T> {
155        #[inline]
156        fn from(m: DynMatrix<T>) -> Self {
157            nalgebra::DMatrix::from_column_slice(m.nrows(), m.ncols(), m.as_slice())
158        }
159    }
160
161    impl<T: Scalar + nalgebra::Scalar> From<&DynMatrix<T>> for nalgebra::DMatrix<T> {
162        #[inline]
163        fn from(m: &DynMatrix<T>) -> Self {
164            nalgebra::DMatrix::from_column_slice(m.nrows(), m.ncols(), m.as_slice())
165        }
166    }
167
168    impl<T: Scalar + nalgebra::Scalar> From<nalgebra::DMatrix<T>> for DynMatrix<T> {
169        #[inline]
170        fn from(m: nalgebra::DMatrix<T>) -> Self {
171            DynMatrix::from_slice(m.nrows(), m.ncols(), m.as_slice())
172        }
173    }
174
175    impl<T: Scalar + nalgebra::Scalar> From<&nalgebra::DMatrix<T>> for DynMatrix<T> {
176        #[inline]
177        fn from(m: &nalgebra::DMatrix<T>) -> Self {
178            DynMatrix::from_slice(m.nrows(), m.ncols(), m.as_slice())
179        }
180    }
181
182    impl<T: Scalar + nalgebra::Scalar> From<DynVector<T>> for nalgebra::DVector<T> {
183        #[inline]
184        fn from(v: DynVector<T>) -> Self {
185            nalgebra::DVector::from_column_slice(v.as_slice())
186        }
187    }
188
189    impl<T: Scalar + nalgebra::Scalar> From<&DynVector<T>> for nalgebra::DVector<T> {
190        #[inline]
191        fn from(v: &DynVector<T>) -> Self {
192            nalgebra::DVector::from_column_slice(v.as_slice())
193        }
194    }
195
196    impl<T: Scalar + nalgebra::Scalar> From<nalgebra::DVector<T>> for DynVector<T> {
197        #[inline]
198        fn from(v: nalgebra::DVector<T>) -> Self {
199            DynVector::from_slice(v.as_slice())
200        }
201    }
202
203    impl<T: Scalar + nalgebra::Scalar> From<&nalgebra::DVector<T>> for DynVector<T> {
204        #[inline]
205        fn from(v: &nalgebra::DVector<T>) -> Self {
206            DynVector::from_slice(v.as_slice())
207        }
208    }
209
210    // ── MatrixRef / MatrixMut for nalgebra::DMatrix ────────────────
211
212    impl<T: Scalar + nalgebra::Scalar> MatrixRef<T> for nalgebra::DMatrix<T> {
213        #[inline]
214        fn nrows(&self) -> usize {
215            self.nrows()
216        }
217
218        #[inline]
219        fn ncols(&self) -> usize {
220            self.ncols()
221        }
222
223        #[inline]
224        fn get(&self, row: usize, col: usize) -> &T {
225            &self[(row, col)]
226        }
227
228        #[inline]
229        fn col_as_slice(&self, col: usize, row_start: usize) -> &[T] {
230            let nrows = self.nrows();
231            let start = col * nrows + row_start;
232            let end = (col + 1) * nrows;
233            &self.as_slice()[start..end]
234        }
235    }
236
237    impl<T: Scalar + nalgebra::Scalar> MatrixMut<T> for nalgebra::DMatrix<T> {
238        #[inline]
239        fn get_mut(&mut self, row: usize, col: usize) -> &mut T {
240            &mut self[(row, col)]
241        }
242
243        #[inline]
244        fn col_as_mut_slice(&mut self, col: usize, row_start: usize) -> &mut [T] {
245            let nrows = self.nrows();
246            let start = col * nrows + row_start;
247            let end = (col + 1) * nrows;
248            &mut self.as_mut_slice()[start..end]
249        }
250    }
251}
252
253// ── Tests ──────────────────────────────────────────────────────────
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn matrix_to_smatrix_roundtrip() {
261        let nm = Matrix::new([[1.0_f64, 2.0], [3.0, 4.0]]);
262        let na: nalgebra::SMatrix<f64, 2, 2> = nm.into();
263        assert_eq!(na[(0, 0)], 1.0);
264        assert_eq!(na[(0, 1)], 2.0);
265        assert_eq!(na[(1, 0)], 3.0);
266        assert_eq!(na[(1, 1)], 4.0);
267        let back: Matrix<f64, 2, 2> = na.into();
268        assert_eq!(back, nm);
269    }
270
271    #[test]
272    fn matrix_ref_roundtrip() {
273        let nm = Matrix::new([[1.0_f64, 2.0, 3.0], [4.0, 5.0, 6.0]]);
274        let na: nalgebra::SMatrix<f64, 2, 3> = (&nm).into();
275        let back: Matrix<f64, 2, 3> = (&na).into();
276        assert_eq!(back, nm);
277    }
278
279    #[test]
280    fn vector_to_svector_roundtrip() {
281        // Vector<T,N> = Matrix<T,N,1> and SVector<T,N> = SMatrix<T,N,1>:
282        // From impls work directly, no transpose needed.
283        use crate::Vector;
284        let nv = Vector::from_array([1.0_f64, 2.0, 3.0]);
285        let na: nalgebra::SVector<f64, 3> = nv.into();
286        assert_eq!(na[0], 1.0);
287        assert_eq!(na[1], 2.0);
288        assert_eq!(na[2], 3.0);
289        assert_eq!(na.nrows(), 3);
290        assert_eq!(na.ncols(), 1);
291        let back: Vector<f64, 3> = na.into();
292        assert_eq!(back, nv);
293    }
294
295    #[test]
296    fn vector_ref_roundtrip() {
297        use crate::Vector;
298        let nv = Vector::from_array([10.0_f32, 20.0, 30.0, 40.0]);
299        let na: nalgebra::SMatrix<f32, 4, 1> = (&nv).into();
300        let back: Vector<f32, 4> = (&na).into();
301        assert_eq!(back, nv);
302    }
303
304    #[test]
305    fn smatrix_matrix_ref_trait() {
306        let na = nalgebra::SMatrix::<f64, 3, 3>::new(
307            1.0, 2.0, 3.0,
308            4.0, 5.0, 6.0,
309            7.0, 8.0, 9.0,
310        );
311        assert_eq!(na.nrows(), 3);
312        assert_eq!(na.ncols(), 3);
313        assert_eq!(*MatrixRef::get(&na, 0, 0), 1.0);
314        assert_eq!(*MatrixRef::get(&na, 1, 0), 4.0);
315        assert_eq!(*MatrixRef::get(&na, 0, 1), 2.0);
316
317        let col1 = MatrixRef::col_as_slice(&na, 1, 0);
318        assert_eq!(col1, &[2.0, 5.0, 8.0]);
319
320        let col2_from1 = MatrixRef::col_as_slice(&na, 2, 1);
321        assert_eq!(col2_from1, &[6.0, 9.0]);
322    }
323
324    #[test]
325    fn smatrix_matrix_mut_trait() {
326        let mut na = nalgebra::SMatrix::<f64, 2, 2>::zeros();
327        *MatrixMut::get_mut(&mut na, 0, 1) = 42.0;
328        assert_eq!(na[(0, 1)], 42.0);
329
330        let col = MatrixMut::col_as_mut_slice(&mut na, 0, 0);
331        col[0] = 10.0;
332        col[1] = 20.0;
333        assert_eq!(na[(0, 0)], 10.0);
334        assert_eq!(na[(1, 0)], 20.0);
335    }
336
337    #[test]
338    fn lu_on_nalgebra_smatrix() {
339        use crate::linalg::lu::lu_in_place;
340
341        let mut na = nalgebra::SMatrix::<f64, 3, 3>::new(
342            2.0, 1.0, -1.0,
343            -3.0, -1.0, 2.0,
344            -2.0, 1.0, 2.0,
345        );
346        let mut perm = [0usize; 3];
347        let result = lu_in_place(&mut na, &mut perm);
348        assert!(result.is_ok());
349    }
350
351    #[test]
352    fn cholesky_on_nalgebra_smatrix() {
353        use crate::linalg::cholesky::cholesky_in_place;
354
355        // SPD matrix: [[4, 2], [2, 3]]
356        let mut na = nalgebra::SMatrix::<f64, 2, 2>::new(4.0, 2.0, 2.0, 3.0);
357        let result = cholesky_in_place(&mut na);
358        assert!(result.is_ok());
359    }
360
361    #[test]
362    fn nonsquare_matrix_roundtrip() {
363        let nm = Matrix::new([[1.0_f64, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], [9.0, 10.0, 11.0, 12.0]]);
364        let na: nalgebra::SMatrix<f64, 3, 4> = nm.into();
365        let back: Matrix<f64, 3, 4> = na.into();
366        assert_eq!(back, nm);
367    }
368
369    #[test]
370    fn i32_matrix_roundtrip() {
371        let nm = Matrix::new([[1_i32, 2], [3, 4]]);
372        let na: nalgebra::SMatrix<i32, 2, 2> = nm.into();
373        let back: Matrix<i32, 2, 2> = na.into();
374        assert_eq!(back, nm);
375    }
376
377    #[cfg(feature = "alloc")]
378    mod dyn_tests {
379        use crate::dynmatrix::{DynMatrix, DynVector};
380        use crate::traits::{MatrixMut, MatrixRef};
381
382        #[test]
383        fn dynmatrix_to_dmatrix_roundtrip() {
384            let dm = DynMatrix::from_rows(2, 3, &[1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0]);
385            let na: nalgebra::DMatrix<f64> = (&dm).into();
386            assert_eq!(na.nrows(), 2);
387            assert_eq!(na.ncols(), 3);
388            assert_eq!(na[(0, 0)], 1.0);
389            assert_eq!(na[(0, 2)], 3.0);
390            assert_eq!(na[(1, 0)], 4.0);
391            let back: DynMatrix<f64> = na.into();
392            assert_eq!(back, dm);
393        }
394
395        #[test]
396        fn dynvector_to_dvector_roundtrip() {
397            let dv = DynVector::from_slice(&[1.0_f64, 2.0, 3.0]);
398            let na: nalgebra::DVector<f64> = (&dv).into();
399            assert_eq!(na.len(), 3);
400            assert_eq!(na[0], 1.0);
401            assert_eq!(na[2], 3.0);
402            let back: DynVector<f64> = na.into();
403            assert_eq!(back, dv);
404        }
405
406        #[test]
407        fn dmatrix_matrix_ref_trait() {
408            let na = nalgebra::DMatrix::from_row_slice(2, 3, &[
409                1.0_f64, 2.0, 3.0,
410                4.0, 5.0, 6.0,
411            ]);
412            assert_eq!(MatrixRef::nrows(&na), 2);
413            assert_eq!(MatrixRef::ncols(&na), 3);
414            assert_eq!(*MatrixRef::get(&na, 0, 0), 1.0);
415            assert_eq!(*MatrixRef::get(&na, 1, 0), 4.0);
416            assert_eq!(*MatrixRef::get(&na, 0, 2), 3.0);
417
418            let col1 = MatrixRef::col_as_slice(&na, 1, 0);
419            assert_eq!(col1, &[2.0, 5.0]);
420        }
421
422        #[test]
423        fn dmatrix_matrix_mut_trait() {
424            let mut na = nalgebra::DMatrix::zeros(3, 3);
425            *MatrixMut::get_mut(&mut na, 1, 2) = 99.0;
426            assert_eq!(na[(1, 2)], 99.0);
427
428            let col = MatrixMut::col_as_mut_slice(&mut na, 0, 1);
429            col[0] = 10.0;
430            col[1] = 20.0;
431            assert_eq!(na[(1, 0)], 10.0);
432            assert_eq!(na[(2, 0)], 20.0);
433        }
434
435        #[test]
436        fn lu_on_nalgebra_dmatrix() {
437            use crate::linalg::lu::lu_in_place;
438
439            let mut na = nalgebra::DMatrix::from_row_slice(3, 3, &[
440                2.0_f64, 1.0, -1.0,
441                -3.0, -1.0, 2.0,
442                -2.0, 1.0, 2.0,
443            ]);
444            let mut perm = [0usize; 3];
445            let result = lu_in_place(&mut na, &mut perm);
446            assert!(result.is_ok());
447        }
448    }
449}