diff --git a/CSparse.Tests/CSparse.Tests.csproj b/CSparse.Tests/CSparse.Tests.csproj index 195baf0..f58e88a 100644 --- a/CSparse.Tests/CSparse.Tests.csproj +++ b/CSparse.Tests/CSparse.Tests.csproj @@ -41,9 +41,9 @@ - - - + + + diff --git a/CSparse.Tests/Ordering/TestAMD.cs b/CSparse.Tests/Ordering/TestAMD.cs new file mode 100644 index 0000000..60d3df4 --- /dev/null +++ b/CSparse.Tests/Ordering/TestAMD.cs @@ -0,0 +1,89 @@ +using CSparse.Ordering; +using CSparse.Storage; +using NUnit.Framework; + +namespace CSparse.Tests.Ordering +{ + class TestAMD + { + [Test] + public void TestAMD1() + { + int[] ap = [0, 9, 15, 21, 27, 33, 39, 48, 57, 61, 70, 76, 82, 88, 94, 100, 106, 110, 119, 128, 137, 143, 152, 156, 160]; + int[] ai = [ + /* col 0 */ 0, 5, 6, 12, 13, 17, 18, 19, 21, + /* col 1 */ 1, 8, 9, 13, 14, 17, + /* col 2 */ 2, 6, 11, 20, 21, 22, + /* col 3 */ 3, 7, 10, 15, 18, 19, + /* col 4 */ 4, 7, 9, 14, 15, 16, + /* col 5 */ 0, 5, 6, 12, 13, 17, + /* col 6 */ 0, 2, 5, 6, 11, 12, 19, 21, 23, + /* col 7 */ 3, 4, 7, 9, 14, 15, 16, 17, 18, + /* col 8 */ 1, 8, 9, 14, + /* col 9 */ 1, 4, 7, 8, 9, 13, 14, 17, 18, + /* col 10 */ 3, 10, 18, 19, 20, 21, + /* col 11 */ 2, 6, 11, 12, 21, 23, + /* col 12 */ 0, 5, 6, 11, 12, 23, + /* col 13 */ 0, 1, 5, 9, 13, 17, + /* col 14 */ 1, 4, 7, 8, 9, 14, + /* col 15 */ 3, 4, 7, 15, 16, 18, + /* col 16 */ 4, 7, 15, 16, + /* col 17 */ 0, 1, 5, 7, 9, 13, 17, 18, 19, + /* col 18 */ 0, 3, 7, 9, 10, 15, 17, 18, 19, + /* col 19 */ 0, 3, 6, 10, 17, 18, 19, 20, 21, + /* col 20 */ 2, 10, 19, 20, 21, 22, + /* col 21 */ 0, 2, 6, 10, 11, 19, 20, 21, 22, + /* col 22 */ 2, 20, 21, 22, + /* col 23 */ 6, 11, 12, 23 ]; + + var S = new SymbolicColumnStorage(24, 24, ap, ai, false); + + var p = AMD.Generate(S, ColumnOrdering.MinimumDegreeAtA); + + int[] expected = [8, 16, 4, 14, 15, 1, 7, 9, 22, 23, 2, 11, 3, 12, 13, 17, 18, 0, 10, 19, 20, 6, 21, 5, 24]; + + Assert.That(Permutation.IsValid(p), Is.True); + Assert.That(p, Is.EqualTo(expected).AsCollection); + } + + [Test] + public void TestAMD2() + { + int[] ap = [0, 9, 14, 20, 28, 33, 37, 44, 53, 58, 63, 63, 66, 69, 72, 75, 78, 82, 86, 91, 97, 101, 112, 112, 116]; + int[] ai = [ + /* col 0 */ 0, 17, 18, 21, 5, 12, 5, 0, 13, + /* col 1 */ 14, 1, 8, 13, 17, + /* col 2 */ 2, 20, 11, 6, 11, 22, + /* col 3 */ 3, 3, 10, 7, 18, 18, 15, 19, + /* col 4 */ 7, 9, 15, 14, 16, + /* col 5 */ 5, 13, 6, 17, + /* col 6 */ 5, 0, 11, 6, 12, 6, 23, + /* col 7 */ 3, 4, 9, 7, 14, 16, 15, 17, 18, + /* col 8 */ 1, 9, 14, 14, 14, + /* col 9 */ 7, 13, 8, 1, 17, + /* col 10 */ + /* col 11 */ 2, 12, 23, + /* col 12 */ 5, 11, 12, + /* col 13 */ 0, 13, 17, + /* col 14 */ 1, 9, 14, + /* col 15 */ 3, 15, 16, + /* col 16 */ 16, 4, 4, 15, + /* col 17 */ 13, 17, 19, 17, + /* col 18 */ 15, 17, 19, 9, 10, + /* col 19 */ 17, 19, 20, 0, 6, 10, + /* col 20 */ 22, 10, 20, 21, + /* col 21 */ 6, 2, 10, 19, 20, 11, 21, 22, 22, 22, 22, + /* col 22 */ + /* col 23 */ 12, 11, 12, 23 ]; + + var S = new SymbolicColumnStorage(24, 24, ap, ai, false); + + var p = AMD.Generate(S, ColumnOrdering.MinimumDegreeAtA); + + int[] expected = [10, 11, 23, 12, 2, 6, 8, 14, 15, 16, 4, 1, 9, 7, 18, 3, 5, 17, 0, 19, 20, 21, 13, 22, 24]; + + Assert.That(Permutation.IsValid(p), Is.True); + Assert.That(p, Is.EqualTo(expected).AsCollection); + } + } +} diff --git a/CSparse.Tests/Ordering/TestDulmageMendelsohn.cs b/CSparse.Tests/Ordering/TestDulmageMendelsohn.cs index 43faaf9..7dd64e0 100644 --- a/CSparse.Tests/Ordering/TestDulmageMendelsohn.cs +++ b/CSparse.Tests/Ordering/TestDulmageMendelsohn.cs @@ -1,6 +1,8 @@ namespace CSparse.Tests.Ordering { + using CSparse.Double; using CSparse.Ordering; + using CSparse.Storage; using NUnit.Framework; using System; @@ -33,5 +35,46 @@ public void TestGenerate2() Assert.That(dm.StructuralRank == n, Is.True); } + + [Test] + public void TestGenerate3() + { + var A = SparseMatrix.OfRowMajor(8, 8, + [ + 11, 12, 0, 0, 0, 0, 0, 0, + 0, 22, 23, 0, 25, 26, 0, 0, + 0, 0, 33, 34, 0, 0, 37, 0, + 0, 0, 43, 44, 0, 0, 0, 48, + 51, 0, 0, 0, 55, 56, 0, 0, + 0, 0, 0, 0, 0, 66, 67, 0, + 0, 0, 0, 0, 0, 76, 77, 0, + 0, 0, 0, 84, 0, 0, 87, 88 + ]); + + var S = SymbolicColumnStorage.Create(A); + + var dm = DulmageMendelsohn.Generate(S, 1); + + Assert.That(dm.StructuralRank, Is.EqualTo(8)); + Assert.That(dm.Blocks, Is.EqualTo(3)); + + int[] expected = [0, 1, 4, 2, 3, 7, 5, 6]; + + Assert.That(dm.RowPermutation, Is.EqualTo(expected).AsCollection); + Assert.That(dm.ColumnPermutation, Is.EqualTo(expected).AsCollection); + + expected = [0, 3, 6, 8]; + + Assert.That(dm.BlockRowPointers, Is.EqualTo(expected).AsCollection); + Assert.That(dm.BlockColumnPointers, Is.EqualTo(expected).AsCollection); + + expected = [0, 0, 8, 8, 8]; + + Assert.That(dm.CoarseRowDecomposition, Is.EqualTo(expected).AsCollection); + + expected = [0, 0, 0, 8, 8]; + + Assert.That(dm.CoarseColumnDecomposition, Is.EqualTo(expected).AsCollection); + } } } diff --git a/CSparse/CSparse.csproj b/CSparse/CSparse.csproj index 05c3d12..87c411d 100644 --- a/CSparse/CSparse.csproj +++ b/CSparse/CSparse.csproj @@ -11,10 +11,10 @@ Copyright Christian Woltering © 2012-2024 Christian Woltering - 4.1.0.0 - 4.1.0.0 + 4.2.0.0 + 4.2.0.0 math sparse matrix lu cholesky qr decomposition factorization - 4.1.0 + 4.2.0 CSparse CSparse LGPL-2.1-only @@ -22,6 +22,10 @@ https://github.com/wo80/CSparse.NET.git git + Version 4.2.0 + + * Make SymbolicColumnStorage class public and update StronglyConnectedComponents and DulmageMendelsohn decomposition accordingly. + Version 4.1.0 * Add overload for creating a sparse matrix from an enumerable of ValueTuple. diff --git a/CSparse/Ordering/AMD.cs b/CSparse/Ordering/AMD.cs index 0c71f76..8943232 100644 --- a/CSparse/Ordering/AMD.cs +++ b/CSparse/Ordering/AMD.cs @@ -25,6 +25,22 @@ public static class AMD /// public static int[] Generate(CompressedColumnStorage A, ColumnOrdering order) where T : struct, IEquatable, IFormattable + { + return Generate(SymbolicColumnStorage.Create(A), order); + } + + /// + /// Generate minimum degree ordering of A+A' (if A is symmetric) or A'A. + /// + /// Column-compressed matrix + /// Column ordering method + /// amd(A+A') if A is symmetric, or amd(A'A) otherwise, null on + /// error or for natural ordering + /// + /// See Chapter 7.1 (Fill-reducing orderings: Minimum degree ordering) in + /// "Direct Methods for Sparse Linear Systems" by Tim Davis. + /// + public static int[] Generate(SymbolicColumnStorage A, ColumnOrdering order) { int[] Cp, Ci, P, W, nv, next, head, elen, degree, w, hhead; @@ -42,7 +58,7 @@ public static int[] Generate(CompressedColumnStorage A, ColumnOrdering ord return Permutation.Create(n); } - var C = ConstructMatrix(SymbolicColumnStorage.Create(A), order); + var C = ConstructMatrix(A, order); Cp = C.ColumnPointers; cnz = Cp[n]; @@ -139,7 +155,7 @@ public static int[] Generate(CompressedColumnStorage A, ColumnOrdering ord Ci[p] = -(j + 2); // first entry is now CS_FLIP(j) } } - for (q = 0, p = 0; p < cnz; ) // scan all of memory + for (q = 0, p = 0; p < cnz;) // scan all of memory { if ((j = FLIP(Ci[p++])) >= 0) // found object j { @@ -303,7 +319,7 @@ public static int[] Generate(CompressedColumnStorage A, ColumnOrdering ord eln = elen[i]; for (p = Cp[i] + 1; p <= Cp[i] + ln - 1; p++) w[Ci[p]] = mark; jlast = i; - for (j = next[i]; j != -1; ) // compare i with all j + for (j = next[i]; j != -1;) // compare i with all j { ok = (W[j] == ln) && (elen[j] == eln); for (p = Cp[j] + 1; ok && p <= Cp[j] + ln - 1; p++) @@ -411,13 +427,13 @@ private static bool KeepOffDiag(int i, int j) private static SymbolicColumnStorage ConstructMatrix(SymbolicColumnStorage A, ColumnOrdering order) { SymbolicColumnStorage result = null; - + // Compute A' var AT = A.Transpose(); int m = A.RowCount; int n = A.ColumnCount; - + if (order == ColumnOrdering.MinimumDegreeAtPlusA) { if (n != m) @@ -444,7 +460,7 @@ private static SymbolicColumnStorage ConstructMatrix(SymbolicColumnStorage A, Co { // Column j of AT starts here. p = colptr[j]; - + // New column j starts here. colptr[j] = p2; diff --git a/CSparse/Ordering/DulmageMendelsohn.cs b/CSparse/Ordering/DulmageMendelsohn.cs index fd9fdf7..10636cd 100644 --- a/CSparse/Ordering/DulmageMendelsohn.cs +++ b/CSparse/Ordering/DulmageMendelsohn.cs @@ -22,7 +22,7 @@ public sealed class DulmageMendelsohn private int nb; // number of blocks in fine dmperm decomposition /// - /// Create a new Decomposition instance. + /// Create a new decomposition instance. /// private DulmageMendelsohn(int m, int n) { @@ -97,22 +97,29 @@ public int Singletons public int[] CoarseColumnDecomposition => cc; /// - /// Compute coarse and then fine Dulmage-Mendelsohn decomposition. seed - /// optionally selects a randomized algorithm. + /// Compute coarse and fine Dulmage-Mendelsohn decomposition. /// /// column-compressed matrix - /// 0: natural, -1: reverse, random order otherwise + /// The seed optionally selects a randomized algorithm (0 = default (natural), -1 = reverse, random order otherwise). /// Dulmage-Mendelsohn analysis public static DulmageMendelsohn Generate(CompressedColumnStorage matrix, int seed = 0) where T : struct, IEquatable, IFormattable + { + return Generate(SymbolicColumnStorage.Create(matrix), seed); + } + + /// + /// Compute coarse and fine Dulmage-Mendelsohn decomposition. + /// + /// The matrix represented by . + /// The seed optionally selects a randomized algorithm (0 = default (natural), -1 = reverse, random order otherwise). + /// Dulmage-Mendelsohn analysis + public static DulmageMendelsohn Generate(SymbolicColumnStorage A, int seed = 0) { int i, j, k, cnz, nc, nb1, nb2; int[] Cp, ps, rs; bool ok; - // We are not interested in the actual matrix values. - var A = SymbolicColumnStorage.Create(matrix); - // Maximum matching int m = A.RowCount; int n = A.ColumnCount; @@ -147,7 +154,7 @@ public static DulmageMendelsohn Generate(CompressedColumnStorage matrix, i // Fine decomposition int[] pinv = Permutation.Invert(p); // pinv=p' - var C = SymbolicColumnStorage.Create(matrix); + var C = A.Clone(); A.Permute(pinv, q, C); // C=A(p,q) (it will hold A(R2,C2)) Cp = C.ColumnPointers; diff --git a/CSparse/Ordering/StronglyConnectedComponents.cs b/CSparse/Ordering/StronglyConnectedComponents.cs index cf38d30..434be23 100644 --- a/CSparse/Ordering/StronglyConnectedComponents.cs +++ b/CSparse/Ordering/StronglyConnectedComponents.cs @@ -48,19 +48,31 @@ private StronglyConnectedComponents(int m, int n) public static StronglyConnectedComponents Generate(CompressedColumnStorage matrix) where T : struct, IEquatable, IFormattable { - return Generate(SymbolicColumnStorage.Create(matrix, false), matrix.ColumnCount); + return Generate(SymbolicColumnStorage.Create(matrix, false)); } - + /// - /// Find strongly connected components of A. + /// Compute strongly connected components of A. /// - /// - /// - /// - internal static StronglyConnectedComponents Generate(SymbolicColumnStorage A, int n) + /// The matrix represented by . + /// Strongly connected components + public static StronglyConnectedComponents Generate(SymbolicColumnStorage A) + { + return Generate(A, A.ColumnCount); + } + + /// + /// Compute strongly connected components of A. + /// + /// The matrix represented by . + /// The size of the matrix. + /// Strongly connected components + public static StronglyConnectedComponents Generate(SymbolicColumnStorage A, int size) { // matrix A temporarily modified, then restored + int n = size; + int i, k, b, nb = 0, top; int[] xi, p, r, Ap, ATp; diff --git a/CSparse/Storage/CompressedColumnStorage.cs b/CSparse/Storage/CompressedColumnStorage.cs index 47d2593..feb8169 100644 --- a/CSparse/Storage/CompressedColumnStorage.cs +++ b/CSparse/Storage/CompressedColumnStorage.cs @@ -40,10 +40,7 @@ public abstract class CompressedColumnStorage : Matrix /// /// Gets the number of non-zero entries. /// - public int NonZerosCount - { - get { return ColumnPointers[columns]; } - } + public int NonZerosCount => ColumnPointers[columns]; /// /// Initializes a new instance of the class. diff --git a/CSparse/Storage/SymbolicColumnStorage.cs b/CSparse/Storage/SymbolicColumnStorage.cs index 04715ac..c3c88dc 100644 --- a/CSparse/Storage/SymbolicColumnStorage.cs +++ b/CSparse/Storage/SymbolicColumnStorage.cs @@ -9,7 +9,7 @@ namespace CSparse.Storage /// /// Used for ordering and symbolic factorization. /// - internal class SymbolicColumnStorage + public class SymbolicColumnStorage { private int rowCount; private int columnCount; @@ -27,27 +27,26 @@ internal class SymbolicColumnStorage /// /// Gets the number of rows. /// - public int RowCount - { - get { return rowCount; } - } + public int RowCount => rowCount; /// /// Gets the number of columns. /// - public int ColumnCount - { - get { return columnCount; } - } + public int ColumnCount => columnCount; /// /// Gets the number of non-zero entries. /// - public int NonZerosCount - { - get { return ColumnPointers[columnCount]; } - } + public int NonZerosCount => ColumnPointers[columnCount]; + /// + /// Initializes a new instance of the class. + /// + /// The number of rows. + /// The number of columns. + /// The number of non-zero values. + /// If true, both and arrays will be allocated. + /// public SymbolicColumnStorage(int rowCount, int columnCount, int valueCount, bool allocate) { // Explicitly allow m or n = 0 (may occur in Dulmage-Mendelsohn decomposition). @@ -61,27 +60,77 @@ public SymbolicColumnStorage(int rowCount, int columnCount, int valueCount, bool if (allocate) { - this.ColumnPointers = new int[columnCount + 1]; - this.RowIndices = new int[valueCount]; + ColumnPointers = new int[columnCount + 1]; + RowIndices = new int[valueCount]; } } /// - /// Change the shape of the matrix (only used by Dulmage-Mendelsohn decomposition). + /// Initializes a new instance of the class. /// - /// - /// - internal void Reshape(int rowCount, int columnCount) + /// The number of rows. + /// The number of columns. + /// The number of non-zero values. + /// The number of non-zero values. + /// If true, both and arrays will be copied to new arrays. + /// + public SymbolicColumnStorage(int rowCount, int columnCount, int[] columnPointers, int[] rowIndices, bool copy) { - if (rowCount >= 0) + // Explicitly allow m or n = 0 (may occur in Dulmage-Mendelsohn decomposition). + if (rowCount < 0 || columnCount < 0) { - this.rowCount = rowCount; + throw new ArgumentOutOfRangeException(Resources.MatrixDimensionNonNegative); } - if (columnCount >= 0) + + if (columnPointers is null) { - this.columnCount = columnCount; - //Array.Resize(ref this.ColumnPointers, columnCount + 1); + throw new ArgumentNullException(nameof(columnPointers)); } + + if (rowIndices is null) + { + throw new ArgumentNullException(nameof(rowIndices)); + } + + if (columnPointers.Length < columnCount + 1) + { + throw new ArgumentOutOfRangeException(nameof(columnPointers), "Column pointers array size doesn't match given column count argument."); + } + + if (rowIndices.Length < columnPointers[columnCount]) + { + throw new ArgumentOutOfRangeException(nameof(rowIndices), "Row indices array size doesn't match non-zeros count."); + } + + this.rowCount = rowCount; + this.columnCount = columnCount; + + if (copy) + { + int valueCount = rowIndices.Length; + + ColumnPointers = new int[columnCount + 1]; + RowIndices = new int[valueCount]; + + Buffer.BlockCopy(columnPointers, 0, ColumnPointers, 0, (columnCount + 1) * Constants.SizeOfInt); + Buffer.BlockCopy(rowIndices, 0, RowIndices, 0, valueCount * Constants.SizeOfInt); + } + else + { + ColumnPointers = columnPointers; + RowIndices = rowIndices; + } + } + + /// + /// Creates a new instance of the class. + /// + /// The sparse matrix to create the from. + /// If true, both column pointers and row indices arrays of will be copied to new arrays. + public static SymbolicColumnStorage Create(CompressedColumnStorage A, bool copy = true) + where T : struct, IEquatable, IFormattable + { + return new SymbolicColumnStorage(A.RowCount, A.ColumnCount, A.ColumnPointers, A.RowIndices, copy); } /// @@ -93,16 +142,16 @@ public bool Resize(int size) { if (size <= 0) { - size = this.ColumnPointers[columnCount]; + size = ColumnPointers[columnCount]; } - Array.Resize(ref this.RowIndices, size); + Array.Resize(ref RowIndices, size); return true; } /// - /// Sort column indices using insertion sort. + /// Sort column indices. /// public void Sort() { @@ -138,16 +187,19 @@ public void Sort() } } + /// + /// Returns a copy of the . + /// + /// public SymbolicColumnStorage Clone() { - int m = this.RowCount; - int n = this.ColumnCount; - int nnz = this.NonZerosCount; + int n = ColumnCount; + int nnz = NonZerosCount; - var result = new SymbolicColumnStorage(m, n, nnz, true); + var result = new SymbolicColumnStorage(RowCount, n, nnz, true); - Buffer.BlockCopy(this.ColumnPointers, 0, result.ColumnPointers, 0, (n + 1) * Constants.SizeOfInt); - Buffer.BlockCopy(this.RowIndices, 0, result.RowIndices, 0, nnz * Constants.SizeOfInt); + Buffer.BlockCopy(ColumnPointers, 0, result.ColumnPointers, 0, (n + 1) * Constants.SizeOfInt); + Buffer.BlockCopy(RowIndices, 0, result.RowIndices, 0, nnz * Constants.SizeOfInt); return result; } @@ -162,8 +214,8 @@ public virtual SymbolicColumnStorage Transpose() { int j, k, p; - int m = this.RowCount; - int n = this.ColumnCount; + int m = RowCount; + int n = ColumnCount; var result = new SymbolicColumnStorage(n, m, 0, false); @@ -232,7 +284,7 @@ public SymbolicColumnStorage Add(SymbolicColumnStorage other) { // Column j of result starts here cp[j] = nz; - nz = this.Scatter(j, w, j + 1, ci, nz); // A(:,j) + nz = Scatter(j, w, j + 1, ci, nz); // A(:,j) nz = other.Scatter(j, w, j + 1, ci, nz); // B(:,j) } @@ -259,15 +311,15 @@ public SymbolicColumnStorage Multiply(SymbolicColumnStorage other) { int p, j, nz = 0; - if (this.columnCount != other.rowCount) + if (columnCount != other.rowCount) { throw new ArgumentException(); } - int m = this.rowCount; + int m = rowCount; int n = other.columnCount; - int anz = this.NonZerosCount; + int anz = NonZerosCount; int bnz = other.NonZerosCount; var bp = other.ColumnPointers; @@ -292,7 +344,7 @@ public SymbolicColumnStorage Multiply(SymbolicColumnStorage other) for (p = bp[j]; p < bp[j + 1]; p++) { - nz = this.Scatter(bi[p], work, j + 1, ci, nz); + nz = Scatter(bi[p], work, j + 1, ci, nz); } } @@ -319,10 +371,10 @@ public virtual void Permute(int[] pinv, int[] q, SymbolicColumnStorage result) { int i, j, k, nz = 0; - int n = this.columnCount; + int n = columnCount; - int[] ap = this.ColumnPointers; - int[] ai = this.RowIndices; + int[] ap = ColumnPointers; + int[] ai = RowIndices; // Allocate memory if needed. if (result.ColumnPointers == null) @@ -350,6 +402,24 @@ public virtual void Permute(int[] pinv, int[] q, SymbolicColumnStorage result) #endregion + /// + /// Change the shape of the matrix (only used by Dulmage-Mendelsohn decomposition). + /// + /// + /// + internal void Reshape(int rowCount, int columnCount) + { + if (rowCount >= 0) + { + this.rowCount = rowCount; + } + if (columnCount >= 0) + { + this.columnCount = columnCount; + //Array.Resize(ref this.ColumnPointers, columnCount + 1); + } + } + /// /// Drops entries from a sparse matrix /// @@ -381,7 +451,7 @@ internal int Keep(Func func) ColumnPointers[columnCount] = nz; // Remove extra space. - Array.Resize(ref this.RowIndices, nz); + Array.Resize(ref RowIndices, nz); return nz; } @@ -411,28 +481,5 @@ private int Scatter(int j, int[] work, int mark, int[] ci, int nz) return nz; } - - internal static SymbolicColumnStorage Create(CompressedColumnStorage mat, bool allocate = true) - where T : struct, IEquatable, IFormattable - { - int m = mat.RowCount; - int n = mat.ColumnCount; - int nnz = mat.NonZerosCount; - - var result = new SymbolicColumnStorage(m, n, nnz, allocate); - - if (allocate) - { - Buffer.BlockCopy(mat.ColumnPointers, 0, result.ColumnPointers, 0, (n + 1) * Constants.SizeOfInt); - Buffer.BlockCopy(mat.RowIndices, 0, result.RowIndices, 0, nnz * Constants.SizeOfInt); - } - else - { - result.ColumnPointers = mat.ColumnPointers; - result.RowIndices = mat.RowIndices; - } - - return result; - } } }