From 36e15b91d51407df07f4208d01cb10ae4eed3494 Mon Sep 17 00:00:00 2001 From: sarah <> Date: Wed, 7 Feb 2024 18:53:01 +0100 Subject: [PATCH] document sorting requirements --- faer-libs/faer-core/src/sparse.rs | 314 ++++++++++++++++++++++++++ faer-libs/faer-sparse/src/amd.rs | 2 + faer-libs/faer-sparse/src/cholesky.rs | 32 +-- faer-libs/faer-sparse/src/colamd.rs | 2 + faer-libs/faer-sparse/src/lu.rs | 73 +++--- faer-libs/faer-sparse/src/qr.rs | 21 +- 6 files changed, 391 insertions(+), 53 deletions(-) diff --git a/faer-libs/faer-core/src/sparse.rs b/faer-libs/faer-core/src/sparse.rs index 62f35a01..add57881 100644 --- a/faer-libs/faer-core/src/sparse.rs +++ b/faer-libs/faer-core/src/sparse.rs @@ -105,6 +105,15 @@ pub const fn repeat_byte(byte: u8) -> usize { /// * `nnz_per_col[j] <= col_ptrs[j+1] - col_ptrs[j]` /// * if `nnz_per_col` is `Some(_)`, elements of `row_indices[col_ptrs[j]..][..nnz_per_col[j]]` are /// less than `nrows` +/// +/// * Within each column, row indices are unique and sorted in increasing order. +/// +/// # Note +/// Some algorithms allow working with matrices containing duplicate and/or unsorted row +/// indicers per column. +/// +/// Passing such a matrix to an algorithm that does not explicitly permit this is unspecified +/// (though not undefined) behavior. #[derive(Debug)] pub struct SymbolicSparseColMatRef<'a, I> { nrows: usize, @@ -129,6 +138,15 @@ pub struct SymbolicSparseColMatRef<'a, I> { /// * `nnz_per_row[i] <= row_ptrs[i+1] - row_ptrs[i]` /// * if `nnz_per_row` is `Some(_)`, elements of `col_indices[row_ptrs[i]..][..nnz_per_row[i]]` are /// less than `ncols` +/// +/// * Within each row, column indices are unique and sorted in increasing order. +/// +/// # Note +/// Some algorithms allow working with matrices containing duplicate and/or unsorted column +/// indicers per row. +/// +/// Passing such a matrix to an algorithm that does not explicitly permit this is unspecified +/// (though not undefined) behavior. #[derive(Debug)] pub struct SymbolicSparseRowMatRef<'a, I> { nrows: usize, @@ -233,6 +251,38 @@ impl SymbolicSparseRowMat { } } + /// Creates a new symbolic matrix view from data containing duplicate and/or unsorted column + /// indices per row, after asserting its other invariants. + /// + /// # Panics + /// + /// See type level documentation. + #[inline] + #[track_caller] + pub fn new_unsorted_checked( + nrows: usize, + ncols: usize, + row_ptrs: Vec, + nnz_per_row: Option>, + col_indices: Vec, + ) -> Self { + SymbolicSparseRowMatRef::new_unsorted_checked( + nrows, + ncols, + &row_ptrs, + nnz_per_row.as_deref(), + &col_indices, + ); + + Self { + nrows, + ncols, + row_ptr: row_ptrs, + row_nnz: nnz_per_row, + col_ind: col_indices, + } + } + /// Creates a new symbolic matrix view without asserting its invariants. /// /// # Safety @@ -305,6 +355,9 @@ impl SymbolicSparseRowMat { } /// Consumes the matrix, and returns its transpose in column-major format without reallocating. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn into_transpose(self) -> SymbolicSparseColMat { SymbolicSparseColMat { @@ -317,12 +370,18 @@ impl SymbolicSparseRowMat { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> { self.as_ref().to_owned() } /// Copies the current matrix into a newly allocated matrix, with column-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_col_major(&self) -> Result, FaerError> { self.as_ref().to_col_major() @@ -331,6 +390,10 @@ impl SymbolicSparseRowMat { /// Returns the number of symbolic non-zeros in the matrix. /// /// The value is guaranteed to be less than `I::Signed::MAX`. + /// + /// # Note + /// Allows unsorted matrices, but the output is a count of all the entries, including the + /// duplicate ones. #[inline] pub fn compute_nnz(&self) -> usize { self.as_ref().compute_nnz() @@ -434,6 +497,38 @@ impl SymbolicSparseColMat { } } + /// Creates a new symbolic matrix view from data containing duplicate and/or unsorted row + /// indices per column, after asserting its other invariants. + /// + /// # Panics + /// + /// See type level documentation. + #[inline] + #[track_caller] + pub fn new_unsorted_checked( + nrows: usize, + ncols: usize, + col_ptrs: Vec, + nnz_per_col: Option>, + row_indices: Vec, + ) -> Self { + SymbolicSparseColMatRef::new_unsorted_checked( + nrows, + ncols, + &col_ptrs, + nnz_per_col.as_deref(), + &row_indices, + ); + + Self { + nrows, + ncols, + col_ptr: col_ptrs, + col_nnz: nnz_per_col, + row_ind: row_indices, + } + } + /// Creates a new symbolic matrix view without asserting its invariants. /// /// # Safety @@ -506,6 +601,9 @@ impl SymbolicSparseColMat { } /// Consumes the matrix, and returns its transpose in row-major format without reallocating. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn into_transpose(self) -> SymbolicSparseRowMat { SymbolicSparseRowMat { @@ -518,12 +616,18 @@ impl SymbolicSparseColMat { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> { self.as_ref().to_owned() } /// Copies the current matrix into a newly allocated matrix, with row-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_row_major(&self) -> Result, FaerError> { self.as_ref().to_row_major() @@ -532,6 +636,10 @@ impl SymbolicSparseColMat { /// Returns the number of symbolic non-zeros in the matrix. /// /// The value is guaranteed to be less than `I::Signed::MAX`. + /// + /// # Note + /// Allows unsorted matrices, but the output is a count of all the entries, including the + /// duplicate ones. #[inline] pub fn compute_nnz(&self) -> usize { self.as_ref().compute_nnz() @@ -628,6 +736,69 @@ impl<'a, I: Index> SymbolicSparseRowMatRef<'a, I> { } assert!(row_ptrs[ncols].zx() <= col_indices.len()); + if let Some(nnz_per_row) = nnz_per_row { + for (&nnz_i, &[c, c_next]) in zip(nnz_per_row, windows2(row_ptrs)) { + assert!(nnz_i <= c_next - c); + let col_indices = &col_indices[c.zx()..c.zx() + nnz_i.zx()]; + if !col_indices.is_empty() { + let mut j_prev = col_indices[0]; + for &j in &col_indices[1..] { + assert!(j_prev < j); + j_prev = j; + } + let ncols = I::truncate(ncols); + assert!(j_prev < ncols); + } + } + } else { + for &[c, c_next] in windows2(row_ptrs) { + let col_indices = &col_indices[c.zx()..c_next.zx()]; + if !col_indices.is_empty() { + let mut j_prev = col_indices[0]; + for &j in &col_indices[1..] { + assert!(j_prev < j); + j_prev = j; + } + let ncols = I::truncate(ncols); + assert!(j_prev < ncols); + } + } + } + + Self { + nrows, + ncols, + row_ptr: row_ptrs, + row_nnz: nnz_per_row, + col_ind: col_indices, + } + } + + /// Creates a new symbolic matrix view from data containing duplicate and/or unsorted column + /// indices per row, after asserting its other invariants. + /// + /// # Panics + /// + /// See type level documentation. + #[inline] + #[track_caller] + pub fn new_unsorted_checked( + nrows: usize, + ncols: usize, + row_ptrs: &'a [I], + nnz_per_row: Option<&'a [I]>, + col_indices: &'a [I], + ) -> Self { + assert!(all( + ncols <= I::Signed::MAX.zx(), + nrows <= I::Signed::MAX.zx(), + )); + assert!(row_ptrs.len() == nrows + 1); + for &[c, c_next] in windows2(row_ptrs) { + assert!(c <= c_next); + } + assert!(row_ptrs[ncols].zx() <= col_indices.len()); + if let Some(nnz_per_row) = nnz_per_row { for (&nnz_i, &[c, c_next]) in zip(nnz_per_row, windows2(row_ptrs)) { assert!(nnz_i <= c_next - c); @@ -706,6 +877,9 @@ impl<'a, I: Index> SymbolicSparseRowMatRef<'a, I> { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> { self.transpose() @@ -714,6 +888,9 @@ impl<'a, I: Index> SymbolicSparseRowMatRef<'a, I> { } /// Copies the current matrix into a newly allocated matrix, with column-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_col_major(&self) -> Result, FaerError> { self.transpose().to_row_major().map(|m| m.into_transpose()) @@ -722,6 +899,10 @@ impl<'a, I: Index> SymbolicSparseRowMatRef<'a, I> { /// Returns the number of symbolic non-zeros in the matrix. /// /// The value is guaranteed to be less than `I::Signed::MAX`. + /// + /// # Note + /// Allows unsorted matrices, but the output is a count of all the entries, including the + /// duplicate ones. #[inline] pub fn compute_nnz(&self) -> usize { self.transpose().compute_nnz() @@ -833,6 +1014,69 @@ impl<'a, I: Index> SymbolicSparseColMatRef<'a, I> { } assert!(col_ptrs[ncols].zx() <= row_indices.len()); + if let Some(nnz_per_col) = nnz_per_col { + for (&nnz_j, &[c, c_next]) in zip(nnz_per_col, windows2(col_ptrs)) { + assert!(nnz_j <= c_next - c); + let row_indices = &row_indices[c.zx()..c.zx() + nnz_j.zx()]; + if !row_indices.is_empty() { + let mut i_prev = row_indices[0]; + for &i in &row_indices[1..] { + assert!(i_prev < i); + i_prev = i; + } + let nrows = I::truncate(nrows); + assert!(i_prev < nrows); + } + } + } else { + for &[c, c_next] in windows2(col_ptrs) { + let row_indices = &row_indices[c.zx()..c_next.zx()]; + if !row_indices.is_empty() { + let mut i_prev = row_indices[0]; + for &i in &row_indices[1..] { + assert!(i_prev < i); + i_prev = i; + } + let nrows = I::truncate(nrows); + assert!(i_prev < nrows); + } + } + } + + Self { + nrows, + ncols, + col_ptr: col_ptrs, + col_nnz: nnz_per_col, + row_ind: row_indices, + } + } + + /// Creates a new symbolic matrix view from data containing duplicate and/or unsorted row + /// indices per column, after asserting its other invariants. + /// + /// # Panics + /// + /// See type level documentation. + #[inline] + #[track_caller] + pub fn new_unsorted_checked( + nrows: usize, + ncols: usize, + col_ptrs: &'a [I], + nnz_per_col: Option<&'a [I]>, + row_indices: &'a [I], + ) -> Self { + assert!(all( + ncols <= I::Signed::MAX.zx(), + nrows <= I::Signed::MAX.zx(), + )); + assert!(col_ptrs.len() == ncols + 1); + for &[c, c_next] in windows2(col_ptrs) { + assert!(c <= c_next); + } + assert!(col_ptrs[ncols].zx() <= row_indices.len()); + if let Some(nnz_per_col) = nnz_per_col { for (&nnz_j, &[c, c_next]) in zip(nnz_per_col, windows2(col_ptrs)) { assert!(nnz_j <= c_next - c); @@ -911,6 +1155,9 @@ impl<'a, I: Index> SymbolicSparseColMatRef<'a, I> { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> { Ok(SymbolicSparseColMat { @@ -926,6 +1173,9 @@ impl<'a, I: Index> SymbolicSparseColMatRef<'a, I> { } /// Copies the current matrix into a newly allocated matrix, with row-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_row_major(&self) -> Result, FaerError> { let mut col_ptr = try_zeroed::(self.nrows + 1)?; @@ -946,6 +1196,10 @@ impl<'a, I: Index> SymbolicSparseColMatRef<'a, I> { /// Returns the number of symbolic non-zeros in the matrix. /// /// The value is guaranteed to be less than `I::Signed::MAX`. + /// + /// # Note + /// Allows unsorted matrices, but the output is a count of all the entries, including the + /// duplicate ones. #[inline] pub fn compute_nnz(&self) -> usize { match self.col_nnz { @@ -1087,6 +1341,9 @@ impl<'a, I: Index, E: Entity> SparseRowMatMut<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> where @@ -1097,6 +1354,9 @@ impl<'a, I: Index, E: Entity> SparseRowMatMut<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix, with column-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_col_major(&self) -> Result, FaerError> where @@ -1252,6 +1512,9 @@ impl<'a, I: Index, E: Entity> SparseColMatMut<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> where @@ -1262,6 +1525,9 @@ impl<'a, I: Index, E: Entity> SparseColMatMut<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix, with row-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_row_major(&self) -> Result, FaerError> where @@ -1423,6 +1689,9 @@ impl<'a, I: Index, E: Entity> SparseRowMatRef<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> where @@ -1435,6 +1704,9 @@ impl<'a, I: Index, E: Entity> SparseRowMatRef<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix, with column-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_col_major(&self) -> Result, FaerError> where @@ -1581,6 +1853,9 @@ impl<'a, I: Index, E: Entity> SparseColMatRef<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> where @@ -1612,6 +1887,9 @@ impl<'a, I: Index, E: Entity> SparseColMatRef<'a, I, E> { } /// Copies the current matrix into a newly allocated matrix, with row-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_row_major(&self) -> Result, FaerError> where @@ -1797,6 +2075,9 @@ impl SparseColMat { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> where @@ -1807,6 +2088,9 @@ impl SparseColMat { } /// Copies the current matrix into a newly allocated matrix, with row-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_row_major(&self) -> Result, FaerError> where @@ -1859,6 +2143,9 @@ impl SparseColMat { } /// Returns a view over the transpose of `self` in row-major format. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn into_transpose(self) -> SparseRowMat { SparseRowMat { @@ -1932,6 +2219,9 @@ impl SparseRowMat { } /// Copies the current matrix into a newly allocated matrix. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn to_owned(&self) -> Result, FaerError> where @@ -1942,6 +2232,9 @@ impl SparseRowMat { } /// Copies the current matrix into a newly allocated matrix, with column-major order. + /// + /// # Note + /// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. #[inline] pub fn to_col_major(&self) -> Result, FaerError> where @@ -1994,6 +2287,9 @@ impl SparseRowMat { } /// Returns a view over the transpose of `self` in column-major format. + /// + /// # Note + /// Allows unsorted matrices, producing an unsorted output. #[inline] pub fn into_transpose(self) -> SparseColMat { SparseColMat { @@ -3230,6 +3526,9 @@ pub unsafe fn permute_hermitian_unsorted<'out, I: Index, E: ComplexField>( /// Computes the self-adjoint permutation $P A P^\top$ of the matrix `A` and returns a view over it. /// /// The result is stored in `new_col_ptrs`, `new_row_indices`. +/// +/// # Note +/// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. pub fn permute_hermitian<'out, I: Index, E: ComplexField>( new_values: GroupFor, new_col_ptrs: &'out mut [I], @@ -3436,6 +3735,9 @@ pub fn ghost_transpose<'m, 'n, 'a, I: Index, E: Entity>( /// Computes the transpose of the matrix `A` and returns a view over it. /// /// The result is stored in `new_col_ptrs`, `new_row_indices` and `new_values`. +/// +/// # Note +/// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. pub fn transpose<'a, I: Index, E: Entity>( new_col_ptrs: &'a mut [I], new_row_indices: &'a mut [I], @@ -3458,6 +3760,9 @@ pub fn transpose<'a, I: Index, E: Entity>( /// Computes the adjoint of the matrix `A` and returns a view over it. /// /// The result is stored in `new_col_ptrs`, `new_row_indices` and `new_values`. +/// +/// # Note +/// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. pub fn adjoint<'a, I: Index, E: ComplexField>( new_col_ptrs: &'a mut [I], new_row_indices: &'a mut [I], @@ -3480,6 +3785,9 @@ pub fn adjoint<'a, I: Index, E: ComplexField>( /// Computes the adjoint of the symbolic matrix `A` and returns a view over it. /// /// The result is stored in `new_col_ptrs`, `new_row_indices`. +/// +/// # Note +/// Allows unsorted matrices, producing a sorted output. Duplicate entries are kept, however. pub fn adjoint_symbolic<'a, I: Index>( new_col_ptrs: &'a mut [I], new_row_indices: &'a mut [I], @@ -3513,6 +3821,9 @@ pub mod mul { /// Multiplies a sparse matrix `lhs` by a dense matrix `rhs`, and stores the result in /// `acc`. See [`crate::mul::matmul`] for more details. + /// + /// # Note + /// Allows unsorted matrices. #[track_caller] pub fn sparse_dense_matmul< I: Index, @@ -3578,6 +3889,9 @@ pub mod mul { /// Multiplies a dense matrix `lhs` by a sparse matrix `rhs`, and stores the result in /// `acc`. See [`crate::mul::matmul`] for more details. + /// + /// # Note + /// Allows unsorted matrices. #[track_caller] pub fn dense_sparse_matmul< I: Index, diff --git a/faer-libs/faer-sparse/src/amd.rs b/faer-libs/faer-sparse/src/amd.rs index ddf39407..c84ed16a 100644 --- a/faer-libs/faer-sparse/src/amd.rs +++ b/faer-libs/faer-sparse/src/amd.rs @@ -1084,6 +1084,8 @@ pub fn order_sorted( )) } +/// # Note +/// Allows unsorted matrices. pub fn order_maybe_unsorted( perm: &mut [I], perm_inv: &mut [I], diff --git a/faer-libs/faer-sparse/src/cholesky.rs b/faer-libs/faer-sparse/src/cholesky.rs index e71a22a0..53038f50 100644 --- a/faer-libs/faer-sparse/src/cholesky.rs +++ b/faer-libs/faer-sparse/src/cholesky.rs @@ -2,6 +2,10 @@ //! matrix. See [`faer_cholesky`] for more info. //! //! The entry point in this module is [`SymbolicCholesky`] and [`factorize_symbolic_cholesky`]. +//! +//! # Note +//! The functions in this module accept unsorted input, producing a sorted decomposition factor +//! (simplicial). // implementation inspired by https://gitlab.com/hodge_star/catamari @@ -204,7 +208,7 @@ pub mod simplicial { .copied(), )?; - let _ = SymbolicSparseColMatRef::new_checked(n, n, &L_col_ptrs, None, &L_row_ind); + let _ = SymbolicSparseColMatRef::new_unsorted_checked(n, n, &L_col_ptrs, None, &L_row_ind); Ok(SymbolicSimplicialCholesky { dimension: n, @@ -4092,7 +4096,7 @@ pub(crate) mod tests { ] .map(truncate); - let A = SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind); + let A = SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind); let zero = truncate(0); let mut etree = vec![zero.to_signed(); n]; let mut col_count = vec![zero; n]; @@ -4137,7 +4141,7 @@ pub(crate) mod tests { let row_ind = &*row_ind.iter().copied().map(I).collect::>(); let amd_perm = &*amd_perm.iter().copied().map(I).collect::>(); let amd_perm_inv = &*amd_perm_inv.iter().copied().map(I).collect::>(); - let A = SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind); + let A = SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind); let perm = &mut vec![I(0); n]; let perm_inv = &mut vec![I(0); n]; @@ -4260,7 +4264,7 @@ pub(crate) mod tests { let mut dense = Mat::::zeros(n, n); let L = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked( + SymbolicSparseColMatRef::new_unsorted_checked( n, n, symbolic.col_ptrs(), @@ -4292,7 +4296,7 @@ pub(crate) mod tests { let mut dense = Mat::::zeros(n, n); let L = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked( + SymbolicSparseColMatRef::new_unsorted_checked( n, n, symbolic.col_ptrs(), @@ -4350,7 +4354,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); let zero = truncate(0); @@ -4450,7 +4454,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); let mut A_dense = sparse_to_dense(A); @@ -4575,7 +4579,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); let mut A_dense = sparse_to_dense(A); @@ -4716,7 +4720,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); let mut A_dense = sparse_to_dense(A); @@ -4865,7 +4869,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); let zero = truncate(0); @@ -4945,7 +4949,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A_upper = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); @@ -5109,7 +5113,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A_upper = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); @@ -5271,7 +5275,7 @@ pub(crate) mod tests { let values = values_mat.col_as_slice(0); let A_upper = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); @@ -5408,7 +5412,7 @@ pub(crate) mod tests { signs[..8].fill(1); let A_upper = SparseColMatRef::<'_, I, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, col_ptr, None, row_ind), + SymbolicSparseColMatRef::new_unsorted_checked(n, n, col_ptr, None, row_ind), values, ); diff --git a/faer-libs/faer-sparse/src/colamd.rs b/faer-libs/faer-sparse/src/colamd.rs index 6c086434..9610715f 100644 --- a/faer-libs/faer-sparse/src/colamd.rs +++ b/faer-libs/faer-sparse/src/colamd.rs @@ -122,6 +122,8 @@ pub fn order_req( ) } +/// # Note +/// Allows unsorted matrices. pub fn order( perm: &mut [I], perm_inv: &mut [I], diff --git a/faer-libs/faer-sparse/src/lu.rs b/faer-libs/faer-sparse/src/lu.rs index 89d70fe5..c8ced6b2 100644 --- a/faer-libs/faer-sparse/src/lu.rs +++ b/faer-libs/faer-sparse/src/lu.rs @@ -1,6 +1,10 @@ //! Computes the LU decomposition of a given sparse matrix. See [`faer_lu`] for more info. //! //! The entry point in this module is [`SymbolicLu`] and [`factorize_symbolic_lu`]. +//! +//! # Warning +//! The functions in this module accept unsorted input, and always produce unsorted decomposition +//! factors. use crate::{ cholesky::simplicial::EliminationTreeRef, @@ -114,6 +118,8 @@ impl From for LuError { } pub mod supernodal { + use crate::try_collect; + use super::*; use faer_core::assert; @@ -619,12 +625,19 @@ pub mod supernodal { nrows: usize, } impl MatU8 { - fn new(nrows: usize, ncols: usize) -> Self { + fn new() -> Self { Self { - data: alloc::vec![1u8; nrows * ncols], - nrows, + data: alloc::vec::Vec::new(), + nrows: 0, } } + + fn with_dims(nrows: usize, ncols: usize) -> Result { + Ok(Self { + data: try_collect((0..(nrows * ncols)).into_iter().map(|_| 1u8))?, + nrows, + }) + } } impl core::ops::Index<(usize, usize)> for MatU8 { type Output = u8; @@ -750,16 +763,14 @@ pub mod supernodal { let (col_perm, col_perm_inv) = col_perm.into_arrays(); - let mut contrib_work = (0..n_supernodes) - .map(|_| { - ( - E::faer_map(E::UNIT, |()| alloc::vec::Vec::>::new()), - alloc::vec::Vec::::new(), - 0usize, - MatU8::new(0, 0), - ) - }) - .collect::>(); + let mut contrib_work = try_collect((0..n_supernodes).map(|_| { + ( + E::faer_map(E::UNIT, |()| alloc::vec::Vec::>::new()), + alloc::vec::Vec::::new(), + 0usize, + MatU8::new(), + ) + }))?; let work_is_empty = |v: &GroupFor>>| { let mut is_empty = false; @@ -969,7 +980,7 @@ pub mod supernodal { work_make_empty(&mut left_contrib[d].0); left_contrib[d].1 = alloc::vec::Vec::new(); left_contrib[d].2 = 0; - left_contrib[d].3 = MatU8::new(0, 0); + left_contrib[d].3 = MatU8::new(); } } } @@ -1172,7 +1183,7 @@ pub mod supernodal { work_make_empty(&mut left_contrib[d].0); left_contrib[d].1 = alloc::vec::Vec::new(); left_contrib[d].2 = 0; - left_contrib[d].3 = MatU8::new(0, 0); + left_contrib[d].3 = MatU8::new(); } } } @@ -1191,11 +1202,16 @@ pub mod supernodal { )? .zx(), )?; + right_contrib[0] + .1 + .try_reserve_exact(s_col_index_count) + .map_err(nomem)?; right_contrib[0] .1 .resize(s_col_index_count, I(s_row_index_count - s_size)); right_contrib[0].2 = s_col_index_count; - right_contrib[0].3 = MatU8::new(s_row_index_count - s_size, s_col_index_count); + right_contrib[0].3 = + MatU8::with_dims(s_row_index_count - s_size, s_col_index_count)?; let mut s_LU = work_to_mat_mut( &mut right_contrib[0].0, @@ -1306,7 +1322,7 @@ pub mod supernodal { work_make_empty(&mut left_contrib[d].0); left_contrib[d].1 = alloc::vec::Vec::new(); left_contrib[d].2 = 0; - left_contrib[d].3 = MatU8::new(0, 0); + left_contrib[d].3 = MatU8::new(); } } } @@ -1384,7 +1400,7 @@ pub mod simplicial { } #[inline] - pub fn l_factor(&self) -> SparseColMatRef<'_, I, E> { + pub fn l_factor_unsorted(&self) -> SparseColMatRef<'_, I, E> { SparseColMatRef::<'_, I, E>::new( unsafe { SymbolicSparseColMatRef::new_unchecked( @@ -1400,7 +1416,7 @@ pub mod simplicial { } #[inline] - pub fn u_factor(&self) -> SparseColMatRef<'_, I, E> { + pub fn u_factor_unsorted(&self) -> SparseColMatRef<'_, I, E> { SparseColMatRef::<'_, I, E>::new( unsafe { SymbolicSparseColMatRef::new_unchecked( @@ -1432,8 +1448,8 @@ pub mod simplicial { let mut X = rhs; let mut temp = work; - let l = self.l_factor(); - let u = self.u_factor(); + let l = self.l_factor_unsorted(); + let u = self.u_factor_unsorted(); faer_core::permutation::permute_rows(temp.rb_mut(), X.rb(), row_perm); triangular_solve::solve_unit_lower_triangular_in_place( @@ -1470,8 +1486,8 @@ pub mod simplicial { let mut X = rhs; let mut temp = work; - let l = self.l_factor(); - let u = self.u_factor(); + let l = self.l_factor_unsorted(); + let u = self.u_factor_unsorted(); faer_core::permutation::permute_rows(temp.rb_mut(), X.rb(), col_perm); triangular_solve::solve_upper_triangular_transpose_in_place( @@ -1765,17 +1781,6 @@ pub mod simplicial { row_perm[p.zx()] = I(idx); } - faer_core::sparse::sort_indices::( - &lu.l_col_ptr, - &mut lu.l_row_ind, - lu.l_val.as_slice_mut().into_inner(), - ); - faer_core::sparse::sort_indices::( - &lu.u_col_ptr, - &mut lu.u_row_ind, - lu.u_val.as_slice_mut().into_inner(), - ); - lu.nrows = m; lu.ncols = n; diff --git a/faer-libs/faer-sparse/src/qr.rs b/faer-libs/faer-sparse/src/qr.rs index 9451acb0..bad5cfa3 100644 --- a/faer-libs/faer-sparse/src/qr.rs +++ b/faer-libs/faer-sparse/src/qr.rs @@ -1,6 +1,10 @@ //! Computes the QR decomposition of a given sparse matrix. See [`faer_qr`] for more info. //! //! The entry point in this module is [`SymbolicQr`] and [`factorize_symbolic_qr`]. +//! +//! # Warning +//! The functions in this module accept unsorted input, and always produce unsorted decomposition +//! factors. use crate::{ cholesky::{ @@ -1633,7 +1637,7 @@ pub mod simplicial { ]) } - pub fn factorize_simplicial_numeric_qr<'a, I: Index, E: ComplexField>( + pub fn factorize_simplicial_numeric_qr_unsorted<'a, I: Index, E: ComplexField>( r_col_ptrs: &'a mut [I], r_row_indices: &'a mut [I], r_values: GroupFor, @@ -1784,6 +1788,7 @@ pub mod simplicial { r_pos += 1; r_col_ptrs[j + 1] = I(r_pos); } + unsafe { SimplicialQrRef::new( symbolic, @@ -2090,7 +2095,7 @@ impl SymbolicQr { let (householder_values, values) = values.split_at(symbolic.len_householder()); let (tau_values, _) = values.split_at(n); - simplicial::factorize_simplicial_numeric_qr::( + simplicial::factorize_simplicial_numeric_qr_unsorted::( r_col_ptrs, r_row_indices, r_values.into_inner(), @@ -2321,7 +2326,7 @@ mod tests { ghost_adjoint, ghost_adjoint_symbolic, ghost_transpose, qr::{ simplicial::{ - factorize_simplicial_numeric_qr, factorize_simplicial_numeric_qr_req, + factorize_simplicial_numeric_qr_req, factorize_simplicial_numeric_qr_unsorted, factorize_simplicial_symbolic_qr, }, supernodal::{ @@ -2852,7 +2857,7 @@ mod tests { let mut householder_values = vec![E::faer_zero(); symbolic.len_householder()]; let mut tau_values = vec![E::faer_zero(); n]; - let qr = factorize_simplicial_numeric_qr::( + let qr = factorize_simplicial_numeric_qr_unsorted::( &mut r_col_ptrs, &mut r_row_indices, &mut r_values, @@ -2900,7 +2905,13 @@ mod tests { } let R = SparseColMatRef::<'_, usize, E>::new( - SymbolicSparseColMatRef::new_checked(n, n, &r_col_ptrs, None, &r_row_indices), + SymbolicSparseColMatRef::new_unsorted_checked( + n, + n, + &r_col_ptrs, + None, + &r_row_indices, + ), &r_values, ); let r = sparse_to_dense(R);