Skip to main content

oxiblas_matrix/
triangular.rs

1//! Triangular matrix types.
2//!
3//! Triangular matrices are square matrices where all elements above (lower)
4//! or below (upper) the main diagonal are zero. They arise frequently in
5//! matrix decompositions (LU, Cholesky, QR) and are important in BLAS.
6//!
7//! # Storage
8//!
9//! This module provides two storage options:
10//! - Full storage: Uses a standard dense matrix, treating the non-stored
11//!   triangle as zeros implicitly.
12//! - Packed storage: Uses `PackedMat` to store only the triangular portion.
13//!
14//! # Unit Triangular
15//!
16//! A unit triangular matrix has ones on the diagonal. This is common in LU
17//! decomposition where L is unit lower triangular.
18
19#[cfg(not(feature = "std"))]
20use alloc::{vec, vec::Vec};
21
22use crate::packed::{PackedMat, PackedMut, PackedRef, TriangularKind};
23use crate::{Mat, MatMut, MatRef};
24use oxiblas_core::scalar::Scalar;
25
26/// Diagonal type for triangular matrices.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum DiagonalKind {
29    /// Non-unit diagonal (general values on diagonal).
30    NonUnit,
31    /// Unit diagonal (ones on diagonal, not stored).
32    Unit,
33}
34
35/// A triangular matrix view over dense storage.
36///
37/// This is a logical view that treats the non-stored triangle as zeros.
38/// The underlying storage is a full dense matrix, but operations only
39/// access the stored triangle.
40///
41/// # Example
42///
43/// ```
44/// use oxiblas_matrix::{Mat, triangular::{TriangularView, DiagonalKind}};
45/// use oxiblas_matrix::packed::TriangularKind;
46///
47/// let mut m: Mat<f64> = Mat::eye(3);
48/// m[(0, 1)] = 2.0;
49/// m[(0, 2)] = 3.0;
50/// m[(1, 2)] = 4.0;
51///
52/// // Create upper triangular view
53/// let tri = TriangularView::new(m.as_ref(), TriangularKind::Upper, DiagonalKind::NonUnit);
54///
55/// // Access upper triangle
56/// assert_eq!(tri.get(0, 1), Some(&2.0));
57/// // Lower triangle returns zero
58/// assert_eq!(tri.get(1, 0), None);
59/// ```
60#[derive(Clone, Copy)]
61pub struct TriangularView<'a, T: Scalar> {
62    /// Underlying matrix view.
63    inner: MatRef<'a, T>,
64    /// Upper or lower triangular.
65    uplo: TriangularKind,
66    /// Unit or non-unit diagonal.
67    diag: DiagonalKind,
68}
69
70impl<'a, T: Scalar> TriangularView<'a, T> {
71    /// Creates a new triangular view over a matrix.
72    ///
73    /// # Panics
74    /// Panics if the matrix is not square.
75    #[inline]
76    pub fn new(mat: MatRef<'a, T>, uplo: TriangularKind, diag: DiagonalKind) -> Self {
77        assert!(mat.is_square(), "Triangular matrix must be square");
78        TriangularView {
79            inner: mat,
80            uplo,
81            diag,
82        }
83    }
84
85    /// Returns the matrix dimension.
86    #[inline]
87    pub fn dim(&self) -> usize {
88        self.inner.nrows()
89    }
90
91    /// Returns the shape (n, n).
92    #[inline]
93    pub fn shape(&self) -> (usize, usize) {
94        self.inner.shape()
95    }
96
97    /// Returns the triangular kind (upper/lower).
98    #[inline]
99    pub fn uplo(&self) -> TriangularKind {
100        self.uplo
101    }
102
103    /// Returns the diagonal kind (unit/non-unit).
104    #[inline]
105    pub fn diag(&self) -> DiagonalKind {
106        self.diag
107    }
108
109    /// Returns true if element (row, col) is in the stored triangle.
110    #[inline]
111    pub fn in_triangle(&self, row: usize, col: usize) -> bool {
112        match self.uplo {
113            TriangularKind::Upper => row <= col,
114            TriangularKind::Lower => row >= col,
115        }
116    }
117
118    /// Returns a reference to the element at (row, col).
119    ///
120    /// Returns `None` if the element is in the non-stored triangle.
121    /// For unit triangular matrices, diagonal elements return `None`
122    /// (they are implicitly one).
123    #[inline]
124    pub fn get(&self, row: usize, col: usize) -> Option<&T> {
125        if !self.in_triangle(row, col) {
126            return None;
127        }
128
129        if self.diag == DiagonalKind::Unit && row == col {
130            return None; // Diagonal is implicitly one
131        }
132
133        self.inner.get(row, col)
134    }
135
136    /// Returns the underlying matrix reference.
137    #[inline]
138    pub fn as_inner(&self) -> MatRef<'a, T> {
139        self.inner
140    }
141
142    /// Returns a pointer to the matrix data.
143    #[inline]
144    pub fn as_ptr(&self) -> *const T {
145        self.inner.as_ptr()
146    }
147
148    /// Returns the row stride.
149    #[inline]
150    pub fn row_stride(&self) -> usize {
151        self.inner.row_stride()
152    }
153
154    /// Converts to a full dense matrix.
155    pub fn to_dense(&self) -> Mat<T>
156    where
157        T: bytemuck::Zeroable,
158    {
159        let n = self.dim();
160        let mut mat = Mat::zeros(n, n);
161
162        for j in 0..n {
163            for i in 0..n {
164                if self.in_triangle(i, j) {
165                    if self.diag == DiagonalKind::Unit && i == j {
166                        mat[(i, j)] = T::one();
167                    } else {
168                        mat[(i, j)] = self.inner[(i, j)];
169                    }
170                }
171            }
172        }
173
174        mat
175    }
176
177    /// Converts to packed storage.
178    pub fn to_packed(&self) -> PackedMat<T>
179    where
180        T: bytemuck::Zeroable,
181    {
182        PackedMat::from_dense(&self.inner, self.uplo)
183    }
184}
185
186/// A mutable triangular matrix view over dense storage.
187pub struct TriangularViewMut<'a, T: Scalar> {
188    /// Underlying mutable matrix view.
189    inner: MatMut<'a, T>,
190    /// Upper or lower triangular.
191    uplo: TriangularKind,
192    /// Unit or non-unit diagonal.
193    diag: DiagonalKind,
194}
195
196impl<'a, T: Scalar> TriangularViewMut<'a, T> {
197    /// Creates a new mutable triangular view over a matrix.
198    #[inline]
199    pub fn new(mat: MatMut<'a, T>, uplo: TriangularKind, diag: DiagonalKind) -> Self {
200        assert!(mat.is_square(), "Triangular matrix must be square");
201        TriangularViewMut {
202            inner: mat,
203            uplo,
204            diag,
205        }
206    }
207
208    /// Returns the matrix dimension.
209    #[inline]
210    pub fn dim(&self) -> usize {
211        self.inner.nrows()
212    }
213
214    /// Returns the shape (n, n).
215    #[inline]
216    pub fn shape(&self) -> (usize, usize) {
217        self.inner.shape()
218    }
219
220    /// Returns the triangular kind.
221    #[inline]
222    pub fn uplo(&self) -> TriangularKind {
223        self.uplo
224    }
225
226    /// Returns the diagonal kind.
227    #[inline]
228    pub fn diag(&self) -> DiagonalKind {
229        self.diag
230    }
231
232    /// Returns true if element is in stored triangle.
233    #[inline]
234    pub fn in_triangle(&self, row: usize, col: usize) -> bool {
235        match self.uplo {
236            TriangularKind::Upper => row <= col,
237            TriangularKind::Lower => row >= col,
238        }
239    }
240
241    /// Returns a reference to element.
242    #[inline]
243    pub fn get(&self, row: usize, col: usize) -> Option<&T> {
244        if !self.in_triangle(row, col) {
245            return None;
246        }
247        if self.diag == DiagonalKind::Unit && row == col {
248            return None;
249        }
250        self.inner.get(row, col)
251    }
252
253    /// Returns a mutable reference to element.
254    #[inline]
255    pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
256        if !self.in_triangle(row, col) {
257            return None;
258        }
259        if self.diag == DiagonalKind::Unit && row == col {
260            return None;
261        }
262        self.inner.get_mut(row, col)
263    }
264
265    /// Sets an element in the stored triangle.
266    ///
267    /// # Panics
268    /// Panics if the element is outside the stored triangle or on the
269    /// diagonal for unit triangular matrices.
270    #[inline]
271    pub fn set(&mut self, row: usize, col: usize, value: T) {
272        assert!(
273            self.in_triangle(row, col),
274            "Element outside stored triangle"
275        );
276        assert!(
277            !(self.diag == DiagonalKind::Unit && row == col),
278            "Cannot set diagonal of unit triangular matrix"
279        );
280        self.inner.set(row, col, value);
281    }
282
283    /// Creates an immutable reborrow.
284    #[inline]
285    pub fn rb(&self) -> TriangularView<'_, T> {
286        TriangularView {
287            inner: self.inner.rb(),
288            uplo: self.uplo,
289            diag: self.diag,
290        }
291    }
292
293    /// Creates a mutable reborrow.
294    #[inline]
295    pub fn rb_mut(&mut self) -> TriangularViewMut<'_, T> {
296        TriangularViewMut {
297            inner: self.inner.rb_mut(),
298            uplo: self.uplo,
299            diag: self.diag,
300        }
301    }
302
303    /// Returns a mutable pointer to the data.
304    #[inline]
305    pub fn as_mut_ptr(&mut self) -> *mut T {
306        self.inner.as_mut_ptr()
307    }
308
309    /// Fills the stored triangle with a value.
310    ///
311    /// Does not modify the diagonal for unit triangular matrices.
312    pub fn fill(&mut self, value: T) {
313        let n = self.dim();
314        for j in 0..n {
315            for i in 0..n {
316                if self.in_triangle(i, j) {
317                    if self.diag == DiagonalKind::Unit && i == j {
318                        continue;
319                    }
320                    self.inner.set(i, j, value);
321                }
322            }
323        }
324    }
325
326    /// Scales the stored triangle by a scalar.
327    pub fn scale(&mut self, alpha: T) {
328        let n = self.dim();
329        for j in 0..n {
330            for i in 0..n {
331                if self.in_triangle(i, j) {
332                    if self.diag == DiagonalKind::Unit && i == j {
333                        continue;
334                    }
335                    if let Some(val) = self.inner.get(i, j) {
336                        self.inner.set(i, j, *val * alpha);
337                    }
338                }
339            }
340        }
341    }
342
343    /// Clears the non-stored triangle to zero.
344    pub fn zero_non_triangle(&mut self)
345    where
346        T: num_traits::Zero,
347    {
348        let n = self.dim();
349        for j in 0..n {
350            for i in 0..n {
351                if !self.in_triangle(i, j) {
352                    self.inner.set(i, j, T::zero());
353                }
354            }
355        }
356    }
357}
358
359/// Triangular matrix using packed storage.
360///
361/// This is a wrapper around `PackedMat` that provides triangular matrix
362/// semantics with efficient packed storage.
363#[derive(Clone)]
364pub struct TriangularMat<T: Scalar> {
365    /// Packed storage.
366    packed: PackedMat<T>,
367    /// Unit or non-unit diagonal.
368    diag: DiagonalKind,
369}
370
371impl<T: Scalar> TriangularMat<T> {
372    /// Creates a new triangular matrix filled with zeros.
373    pub fn zeros(n: usize, uplo: TriangularKind, diag: DiagonalKind) -> Self
374    where
375        T: bytemuck::Zeroable,
376    {
377        TriangularMat {
378            packed: PackedMat::zeros(n, uplo),
379            diag,
380        }
381    }
382
383    /// Creates a unit triangular matrix (identity-like).
384    ///
385    /// The diagonal is implicitly one and not stored.
386    pub fn unit_zeros(n: usize, uplo: TriangularKind) -> Self
387    where
388        T: bytemuck::Zeroable,
389    {
390        Self::zeros(n, uplo, DiagonalKind::Unit)
391    }
392
393    /// Creates from a packed matrix.
394    #[inline]
395    pub fn from_packed(packed: PackedMat<T>, diag: DiagonalKind) -> Self {
396        TriangularMat { packed, diag }
397    }
398
399    /// Creates from a dense matrix view.
400    pub fn from_dense(mat: &MatRef<'_, T>, uplo: TriangularKind, diag: DiagonalKind) -> Self
401    where
402        T: bytemuck::Zeroable,
403    {
404        TriangularMat {
405            packed: PackedMat::from_dense(mat, uplo),
406            diag,
407        }
408    }
409
410    /// Returns the matrix dimension.
411    #[inline]
412    pub fn dim(&self) -> usize {
413        self.packed.dim()
414    }
415
416    /// Returns the shape (n, n).
417    #[inline]
418    pub fn shape(&self) -> (usize, usize) {
419        let n = self.dim();
420        (n, n)
421    }
422
423    /// Returns the triangular kind.
424    #[inline]
425    pub fn uplo(&self) -> TriangularKind {
426        self.packed.kind()
427    }
428
429    /// Returns the diagonal kind.
430    #[inline]
431    pub fn diag(&self) -> DiagonalKind {
432        self.diag
433    }
434
435    /// Returns the packed storage length.
436    #[inline]
437    pub fn len(&self) -> usize {
438        self.packed.len()
439    }
440
441    /// Returns true if empty.
442    #[inline]
443    pub fn is_empty(&self) -> bool {
444        self.packed.is_empty()
445    }
446
447    /// Returns true if element is in the stored triangle.
448    #[inline]
449    pub fn in_triangle(&self, row: usize, col: usize) -> bool {
450        self.packed.packed_index(row, col).is_some()
451    }
452
453    /// Returns a reference to element.
454    ///
455    /// For unit triangular matrices, returns `None` for diagonal elements.
456    #[inline]
457    pub fn get(&self, row: usize, col: usize) -> Option<&T> {
458        if self.diag == DiagonalKind::Unit && row == col {
459            return None;
460        }
461        self.packed.get(row, col)
462    }
463
464    /// Returns a mutable reference to element.
465    #[inline]
466    pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
467        if self.diag == DiagonalKind::Unit && row == col {
468            return None;
469        }
470        self.packed.get_mut(row, col)
471    }
472
473    /// Sets an element.
474    ///
475    /// # Panics
476    /// Panics if `(row, col)` is outside the stored triangle, or if
477    /// setting the diagonal on a unit triangular matrix.
478    #[inline]
479    pub fn set(&mut self, row: usize, col: usize, value: T) {
480        assert!(
481            !(self.diag == DiagonalKind::Unit && row == col),
482            "Cannot set diagonal of unit triangular matrix"
483        );
484        assert!(
485            self.in_triangle(row, col),
486            "Element outside stored triangle"
487        );
488        // Safety of the `expect`: `in_triangle` above is exactly the
489        // condition `PackedMat::set` uses to decide success, so having
490        // just checked it makes the error case unreachable here.
491        self.packed
492            .set(row, col, value)
493            .expect("in_triangle check above guarantees this index is valid");
494    }
495
496    /// Returns a pointer to the packed data.
497    #[inline]
498    pub fn as_ptr(&self) -> *const T {
499        self.packed.as_ptr()
500    }
501
502    /// Returns a mutable pointer to the packed data.
503    #[inline]
504    pub fn as_mut_ptr(&mut self) -> *mut T {
505        self.packed.as_mut_ptr()
506    }
507
508    /// Returns the packed data as a slice.
509    #[inline]
510    pub fn as_slice(&self) -> &[T] {
511        self.packed.as_slice()
512    }
513
514    /// Returns the packed data as a mutable slice.
515    #[inline]
516    pub fn as_slice_mut(&mut self) -> &mut [T] {
517        self.packed.as_slice_mut()
518    }
519
520    /// Returns a reference to the underlying packed matrix.
521    #[inline]
522    pub fn as_packed(&self) -> &PackedMat<T> {
523        &self.packed
524    }
525
526    /// Returns a mutable reference to the underlying packed matrix.
527    #[inline]
528    pub fn as_packed_mut(&mut self) -> &mut PackedMat<T> {
529        &mut self.packed
530    }
531
532    /// Converts to a full dense matrix.
533    pub fn to_dense(&self) -> Mat<T>
534    where
535        T: bytemuck::Zeroable,
536    {
537        let n = self.dim();
538        let mut mat = Mat::zeros(n, n);
539
540        for j in 0..n {
541            for i in 0..n {
542                if let Some(&val) = self.packed.get(i, j) {
543                    if self.diag == DiagonalKind::Unit && i == j {
544                        mat[(i, j)] = T::one();
545                    } else {
546                        mat[(i, j)] = val;
547                    }
548                } else if self.diag == DiagonalKind::Unit && i == j {
549                    mat[(i, j)] = T::one();
550                }
551            }
552        }
553
554        mat
555    }
556
557    /// Returns the diagonal elements.
558    ///
559    /// For unit triangular matrices, returns a vector of ones.
560    pub fn diagonal(&self) -> Vec<T> {
561        let n = self.dim();
562        if self.diag == DiagonalKind::Unit {
563            vec![T::one(); n]
564        } else {
565            self.packed.diagonal()
566        }
567    }
568
569    /// Sets the diagonal elements.
570    ///
571    /// # Panics
572    /// Panics for unit triangular matrices.
573    pub fn set_diagonal(&mut self, diag: &[T]) {
574        assert!(
575            self.diag != DiagonalKind::Unit,
576            "Cannot set diagonal of unit triangular matrix"
577        );
578        self.packed.set_diagonal(diag);
579    }
580
581    /// Fills the stored triangle with a value.
582    pub fn fill(&mut self, value: T) {
583        if self.diag == DiagonalKind::Unit {
584            // Fill off-diagonal only
585            let n = self.dim();
586            for j in 0..n {
587                for i in 0..n {
588                    if self.in_triangle(i, j) && i != j {
589                        // Safety of the `expect`: guarded by `in_triangle` above.
590                        self.packed
591                            .set(i, j, value)
592                            .expect("in_triangle check above guarantees this index is valid");
593                    }
594                }
595            }
596        } else {
597            self.packed.fill(value);
598        }
599    }
600
601    /// Scales the stored triangle by a scalar.
602    pub fn scale(&mut self, alpha: T) {
603        if self.diag == DiagonalKind::Unit {
604            // Scale off-diagonal only
605            let n = self.dim();
606            for j in 0..n {
607                for i in 0..n {
608                    if self.in_triangle(i, j) && i != j {
609                        if let Some(val) = self.packed.get_mut(i, j) {
610                            *val *= alpha;
611                        }
612                    }
613                }
614            }
615        } else {
616            self.packed.scale(alpha);
617        }
618    }
619
620    /// Returns the transpose (flips upper/lower).
621    pub fn transpose(&self) -> Self
622    where
623        T: bytemuck::Zeroable,
624    {
625        TriangularMat {
626            packed: self.packed.transpose(),
627            diag: self.diag,
628        }
629    }
630}
631
632impl<T: Scalar + core::fmt::Debug> core::fmt::Debug for TriangularMat<T> {
633    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
634        let n = self.dim();
635        writeln!(
636            f,
637            "TriangularMat {}×{} ({:?}, {:?}) {{",
638            n,
639            n,
640            self.uplo(),
641            self.diag
642        )?;
643
644        let max_dim = 8.min(n);
645        for i in 0..max_dim {
646            write!(f, "  [")?;
647            for j in 0..max_dim {
648                if j > 0 {
649                    write!(f, ", ")?;
650                }
651                if self.in_triangle(i, j) {
652                    if self.diag == DiagonalKind::Unit && i == j {
653                        write!(f, "{:8.4?}", T::one())?;
654                    } else if let Some(v) = self.packed.get(i, j) {
655                        write!(f, "{:8.4?}", v)?;
656                    } else {
657                        write!(f, "      * ")?;
658                    }
659                } else {
660                    write!(f, "      0 ")?;
661                }
662            }
663            if n > max_dim {
664                write!(f, ", ...")?;
665            }
666            writeln!(f, "]")?;
667        }
668        if n > max_dim {
669            writeln!(f, "  ...")?;
670        }
671        write!(f, "}}")
672    }
673}
674
675/// A reference to triangular packed data.
676#[derive(Clone, Copy)]
677pub struct TriangularRef<'a, T: Scalar> {
678    /// Packed reference.
679    packed: PackedRef<'a, T>,
680    /// Diagonal kind.
681    diag: DiagonalKind,
682}
683
684impl<'a, T: Scalar> TriangularRef<'a, T> {
685    /// Creates a new triangular reference.
686    #[inline]
687    pub fn new(packed: PackedRef<'a, T>, diag: DiagonalKind) -> Self {
688        TriangularRef { packed, diag }
689    }
690
691    /// Creates from a slice.
692    #[inline]
693    pub fn from_slice(data: &'a [T], n: usize, uplo: TriangularKind, diag: DiagonalKind) -> Self {
694        TriangularRef {
695            packed: PackedRef::from_slice(data, n, uplo),
696            diag,
697        }
698    }
699
700    /// Returns the dimension.
701    #[inline]
702    pub fn dim(&self) -> usize {
703        self.packed.dim()
704    }
705
706    /// Returns the triangular kind.
707    #[inline]
708    pub fn uplo(&self) -> TriangularKind {
709        self.packed.kind()
710    }
711
712    /// Returns the diagonal kind.
713    #[inline]
714    pub fn diag(&self) -> DiagonalKind {
715        self.diag
716    }
717
718    /// Returns element at (row, col).
719    #[inline]
720    pub fn get(&self, row: usize, col: usize) -> Option<&T> {
721        if self.diag == DiagonalKind::Unit && row == col {
722            return None;
723        }
724        self.packed.get(row, col)
725    }
726
727    /// Returns the underlying packed reference.
728    #[inline]
729    pub fn as_packed(&self) -> PackedRef<'a, T> {
730        self.packed
731    }
732}
733
734/// A mutable reference to triangular packed data.
735pub struct TriangularMut<'a, T: Scalar> {
736    /// Packed mutable reference.
737    packed: PackedMut<'a, T>,
738    /// Diagonal kind.
739    diag: DiagonalKind,
740}
741
742impl<'a, T: Scalar> TriangularMut<'a, T> {
743    /// Creates a new mutable triangular reference.
744    #[inline]
745    pub fn new(packed: PackedMut<'a, T>, diag: DiagonalKind) -> Self {
746        TriangularMut { packed, diag }
747    }
748
749    /// Creates from a mutable slice.
750    #[inline]
751    pub fn from_slice(
752        data: &'a mut [T],
753        n: usize,
754        uplo: TriangularKind,
755        diag: DiagonalKind,
756    ) -> Self {
757        TriangularMut {
758            packed: PackedMut::from_slice(data, n, uplo),
759            diag,
760        }
761    }
762
763    /// Returns the dimension.
764    #[inline]
765    pub fn dim(&self) -> usize {
766        self.packed.dim()
767    }
768
769    /// Returns the triangular kind.
770    #[inline]
771    pub fn uplo(&self) -> TriangularKind {
772        self.packed.kind()
773    }
774
775    /// Returns the diagonal kind.
776    #[inline]
777    pub fn diag(&self) -> DiagonalKind {
778        self.diag
779    }
780
781    /// Returns element at (row, col).
782    #[inline]
783    pub fn get(&self, row: usize, col: usize) -> Option<&T> {
784        if self.diag == DiagonalKind::Unit && row == col {
785            return None;
786        }
787        self.packed.get(row, col)
788    }
789
790    /// Returns mutable element at (row, col).
791    #[inline]
792    pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
793        if self.diag == DiagonalKind::Unit && row == col {
794            return None;
795        }
796        self.packed.get_mut(row, col)
797    }
798
799    /// Sets element at (row, col).
800    ///
801    /// # Panics
802    /// Panics if `(row, col)` is outside the stored triangle, or if
803    /// setting the diagonal on a unit triangular matrix.
804    #[inline]
805    pub fn set(&mut self, row: usize, col: usize, value: T) {
806        assert!(
807            !(self.diag == DiagonalKind::Unit && row == col),
808            "Cannot set diagonal of unit triangular matrix"
809        );
810        assert!(
811            self.packed.packed_index(row, col).is_some(),
812            "Element outside stored triangle"
813        );
814        // Safety of the `expect`: the `packed_index` check above is exactly
815        // the condition `PackedMut::set` uses to decide success.
816        self.packed
817            .set(row, col, value)
818            .expect("packed_index check above guarantees this index is valid");
819    }
820
821    /// Creates an immutable reborrow.
822    #[inline]
823    pub fn rb(&self) -> TriangularRef<'_, T> {
824        TriangularRef {
825            packed: self.packed.rb(),
826            diag: self.diag,
827        }
828    }
829
830    /// Creates a mutable reborrow.
831    #[inline]
832    pub fn rb_mut(&mut self) -> TriangularMut<'_, T> {
833        TriangularMut {
834            packed: self.packed.rb_mut(),
835            diag: self.diag,
836        }
837    }
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843
844    #[test]
845    fn test_triangular_view_upper() {
846        let mut m: Mat<f64> = Mat::zeros(3, 3);
847        m[(0, 0)] = 1.0;
848        m[(0, 1)] = 2.0;
849        m[(0, 2)] = 3.0;
850        m[(1, 1)] = 4.0;
851        m[(1, 2)] = 5.0;
852        m[(2, 2)] = 6.0;
853        // Set some values below diagonal (should be ignored)
854        m[(1, 0)] = 99.0;
855        m[(2, 0)] = 99.0;
856        m[(2, 1)] = 99.0;
857
858        let tri = TriangularView::new(m.as_ref(), TriangularKind::Upper, DiagonalKind::NonUnit);
859
860        // Upper triangle
861        assert_eq!(tri.get(0, 0), Some(&1.0));
862        assert_eq!(tri.get(0, 1), Some(&2.0));
863        assert_eq!(tri.get(0, 2), Some(&3.0));
864        assert_eq!(tri.get(1, 1), Some(&4.0));
865        assert_eq!(tri.get(1, 2), Some(&5.0));
866        assert_eq!(tri.get(2, 2), Some(&6.0));
867
868        // Lower triangle returns None
869        assert_eq!(tri.get(1, 0), None);
870        assert_eq!(tri.get(2, 0), None);
871        assert_eq!(tri.get(2, 1), None);
872
873        // Test to_dense
874        let dense = tri.to_dense();
875        assert_eq!(dense[(0, 0)], 1.0);
876        assert_eq!(dense[(0, 1)], 2.0);
877        assert_eq!(dense[(1, 0)], 0.0); // Zero, not 99
878        assert_eq!(dense[(2, 1)], 0.0);
879    }
880
881    #[test]
882    fn test_triangular_view_unit() {
883        let mut m: Mat<f64> = Mat::zeros(3, 3);
884        m[(0, 0)] = 99.0; // Should be treated as 1
885        m[(0, 1)] = 2.0;
886        m[(0, 2)] = 3.0;
887        m[(1, 1)] = 99.0; // Should be treated as 1
888        m[(1, 2)] = 4.0;
889        m[(2, 2)] = 99.0; // Should be treated as 1
890
891        let tri = TriangularView::new(m.as_ref(), TriangularKind::Upper, DiagonalKind::Unit);
892
893        // Diagonal returns None (implicitly one)
894        assert_eq!(tri.get(0, 0), None);
895        assert_eq!(tri.get(1, 1), None);
896        assert_eq!(tri.get(2, 2), None);
897
898        // Off-diagonal upper elements
899        assert_eq!(tri.get(0, 1), Some(&2.0));
900        assert_eq!(tri.get(0, 2), Some(&3.0));
901        assert_eq!(tri.get(1, 2), Some(&4.0));
902
903        // to_dense should have ones on diagonal
904        let dense = tri.to_dense();
905        assert_eq!(dense[(0, 0)], 1.0);
906        assert_eq!(dense[(1, 1)], 1.0);
907        assert_eq!(dense[(2, 2)], 1.0);
908        assert_eq!(dense[(0, 1)], 2.0);
909    }
910
911    #[test]
912    fn test_triangular_view_lower() {
913        let mut m: Mat<f64> = Mat::zeros(3, 3);
914        m[(0, 0)] = 1.0;
915        m[(1, 0)] = 2.0;
916        m[(1, 1)] = 3.0;
917        m[(2, 0)] = 4.0;
918        m[(2, 1)] = 5.0;
919        m[(2, 2)] = 6.0;
920
921        let tri = TriangularView::new(m.as_ref(), TriangularKind::Lower, DiagonalKind::NonUnit);
922
923        // Lower triangle
924        assert_eq!(tri.get(0, 0), Some(&1.0));
925        assert_eq!(tri.get(1, 0), Some(&2.0));
926        assert_eq!(tri.get(1, 1), Some(&3.0));
927        assert_eq!(tri.get(2, 0), Some(&4.0));
928        assert_eq!(tri.get(2, 1), Some(&5.0));
929        assert_eq!(tri.get(2, 2), Some(&6.0));
930
931        // Upper triangle returns None
932        assert_eq!(tri.get(0, 1), None);
933        assert_eq!(tri.get(0, 2), None);
934        assert_eq!(tri.get(1, 2), None);
935    }
936
937    #[test]
938    fn test_triangular_mat_packed() {
939        let mut tri: TriangularMat<f64> =
940            TriangularMat::zeros(3, TriangularKind::Upper, DiagonalKind::NonUnit);
941
942        tri.set(0, 0, 1.0);
943        tri.set(0, 1, 2.0);
944        tri.set(0, 2, 3.0);
945        tri.set(1, 1, 4.0);
946        tri.set(1, 2, 5.0);
947        tri.set(2, 2, 6.0);
948
949        assert_eq!(tri.get(0, 0), Some(&1.0));
950        assert_eq!(tri.get(0, 1), Some(&2.0));
951        assert_eq!(tri.get(1, 2), Some(&5.0));
952
953        // Cannot access lower triangle
954        assert_eq!(tri.get(1, 0), None);
955
956        let diag = tri.diagonal();
957        assert_eq!(diag, vec![1.0, 4.0, 6.0]);
958    }
959
960    #[test]
961    fn test_triangular_mat_unit() {
962        let mut tri: TriangularMat<f64> = TriangularMat::unit_zeros(3, TriangularKind::Lower);
963
964        // Off-diagonal elements
965        tri.set(1, 0, 2.0);
966        tri.set(2, 0, 3.0);
967        tri.set(2, 1, 4.0);
968
969        // Diagonal returns None (implicit one)
970        assert_eq!(tri.get(0, 0), None);
971        assert_eq!(tri.get(1, 1), None);
972        assert_eq!(tri.get(2, 2), None);
973
974        // Off-diagonal elements
975        assert_eq!(tri.get(1, 0), Some(&2.0));
976        assert_eq!(tri.get(2, 0), Some(&3.0));
977        assert_eq!(tri.get(2, 1), Some(&4.0));
978
979        // Diagonal should be ones
980        let diag = tri.diagonal();
981        assert_eq!(diag, vec![1.0, 1.0, 1.0]);
982
983        // to_dense
984        let dense = tri.to_dense();
985        assert_eq!(dense[(0, 0)], 1.0);
986        assert_eq!(dense[(1, 1)], 1.0);
987        assert_eq!(dense[(2, 2)], 1.0);
988        assert_eq!(dense[(1, 0)], 2.0);
989        assert_eq!(dense[(0, 1)], 0.0);
990    }
991
992    #[test]
993    fn test_triangular_mat_transpose() {
994        let mut tri: TriangularMat<f64> =
995            TriangularMat::zeros(3, TriangularKind::Upper, DiagonalKind::NonUnit);
996
997        tri.set(0, 0, 1.0);
998        tri.set(0, 1, 2.0);
999        tri.set(0, 2, 3.0);
1000        tri.set(1, 1, 4.0);
1001        tri.set(1, 2, 5.0);
1002        tri.set(2, 2, 6.0);
1003
1004        let tri_t = tri.transpose();
1005        assert_eq!(tri_t.uplo(), TriangularKind::Lower);
1006
1007        // Elements should be transposed
1008        assert_eq!(tri_t.get(0, 0), Some(&1.0));
1009        assert_eq!(tri_t.get(1, 0), Some(&2.0)); // Was (0, 1)
1010        assert_eq!(tri_t.get(2, 0), Some(&3.0)); // Was (0, 2)
1011        assert_eq!(tri_t.get(1, 1), Some(&4.0));
1012        assert_eq!(tri_t.get(2, 1), Some(&5.0)); // Was (1, 2)
1013        assert_eq!(tri_t.get(2, 2), Some(&6.0));
1014    }
1015
1016    #[test]
1017    fn test_triangular_view_mut() {
1018        let mut m: Mat<f64> = Mat::zeros(3, 3);
1019        let mut view =
1020            TriangularViewMut::new(m.as_mut(), TriangularKind::Upper, DiagonalKind::NonUnit);
1021
1022        view.set(0, 0, 1.0);
1023        view.set(0, 1, 2.0);
1024        view.set(1, 1, 3.0);
1025
1026        assert_eq!(view.get(0, 0), Some(&1.0));
1027        assert_eq!(view.get(0, 1), Some(&2.0));
1028        assert_eq!(view.get(1, 1), Some(&3.0));
1029
1030        // zero_non_triangle
1031        view.zero_non_triangle();
1032        // Check that lower triangle is zeroed (it was already zero)
1033
1034        // Fill
1035        view.fill(5.0);
1036        assert_eq!(view.get(0, 0), Some(&5.0));
1037        assert_eq!(view.get(0, 1), Some(&5.0));
1038    }
1039
1040    #[test]
1041    fn test_triangular_from_dense() {
1042        let m = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
1043
1044        let tri =
1045            TriangularMat::from_dense(&m.as_ref(), TriangularKind::Upper, DiagonalKind::NonUnit);
1046
1047        assert_eq!(tri.get(0, 0), Some(&1.0));
1048        assert_eq!(tri.get(0, 1), Some(&2.0));
1049        assert_eq!(tri.get(0, 2), Some(&3.0));
1050        assert_eq!(tri.get(1, 1), Some(&5.0));
1051        assert_eq!(tri.get(1, 2), Some(&6.0));
1052        assert_eq!(tri.get(2, 2), Some(&9.0));
1053
1054        // Lower part is not stored
1055        assert_eq!(tri.get(1, 0), None);
1056        assert_eq!(tri.get(2, 0), None);
1057        assert_eq!(tri.get(2, 1), None);
1058    }
1059
1060    #[test]
1061    fn test_triangular_ref_mut() {
1062        let mut data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1063        let mut tmut =
1064            TriangularMut::from_slice(&mut data, 3, TriangularKind::Upper, DiagonalKind::NonUnit);
1065
1066        assert_eq!(tmut.get(0, 0), Some(&1.0));
1067        assert_eq!(tmut.get(0, 1), Some(&2.0));
1068
1069        tmut.set(0, 0, 10.0);
1070        assert_eq!(tmut.get(0, 0), Some(&10.0));
1071        assert_eq!(data[0], 10.0);
1072    }
1073}