diff --git a/CSparse.Tests/Complex/Factorization/SparseCholeskyTest.cs b/CSparse.Tests/Complex/Factorization/SparseCholeskyTest.cs index 37edccc..c355929 100644 --- a/CSparse.Tests/Complex/Factorization/SparseCholeskyTest.cs +++ b/CSparse.Tests/Complex/Factorization/SparseCholeskyTest.cs @@ -54,5 +54,34 @@ public void TestEmptyFactorize() Assert.That(chol, Is.Not.Null); Assert.That(chol.NonZerosCount == 0, Is.True); } + + [Test] + public void TestRefactorize() + { + var A = ResourceLoader.Get("hermitian-40-spd.mat"); + + var chol = SparseCholesky.Create(A, ColumnOrdering.MinimumDegreeAtPlusA); + + // Same sparsity pattern, different values (B = 2*A stays Hermitian SPD) : + // the numeric refactorization must reuse the cached symbolic analysis. + var B = A.Clone(); + var bv = B.Values; + for (int i = 0; i < bv.Length; i++) bv[i] *= 2.0; + + chol.Refactorize(B); + + var x = Helper.CreateTestVector(B.ColumnCount); + var b = Helper.Multiply(B, x); + var r = Vector.Clone(b); + + chol.Solve(b, x); + B.Multiply(-1.0, x, 1.0, r); + + Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Dimension guard. + var small = new SparseMatrix(3, 3, 0); + Assert.Throws(() => chol.Refactorize(small)); + } } } diff --git a/CSparse.Tests/Complex/Factorization/SparseLDLTest.cs b/CSparse.Tests/Complex/Factorization/SparseLDLTest.cs index 6b3a456..677b617 100644 --- a/CSparse.Tests/Complex/Factorization/SparseLDLTest.cs +++ b/CSparse.Tests/Complex/Factorization/SparseLDLTest.cs @@ -1,6 +1,7 @@ namespace CSparse.Tests.Complex.Factorization { + using System; using CSparse.Complex; using CSparse.Complex.Factorization; using NUnit.Framework; @@ -54,5 +55,34 @@ public void TestSolveNonSpd() Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); } + + [Test] + public void TestRefactorize() + { + var A = ResourceLoader.Get("hermitian-40-spd.mat"); + + var ldl = SparseLDL.Create(A, ColumnOrdering.MinimumDegreeAtPlusA); + + // Same sparsity pattern, different values (B = 2*A) : the numeric + // refactorization must reuse the cached symbolic analysis. + var B = A.Clone(); + var bv = B.Values; + for (int i = 0; i < bv.Length; i++) bv[i] *= 2.0; + + ldl.Refactorize(B); + + var x = Helper.CreateTestVector(B.ColumnCount); + var b = Helper.Multiply(B, x); + var r = Vector.Clone(b); + + ldl.Solve(b, x); + B.Multiply(-1.0, x, 1.0, r); + + Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Dimension guard. + var small = new SparseMatrix(3, 3, 0); + Assert.Throws(() => ldl.Refactorize(small)); + } } } diff --git a/CSparse.Tests/Complex/Factorization/SparseLUTest.cs b/CSparse.Tests/Complex/Factorization/SparseLUTest.cs index 9ffac2d..6d89bca 100644 --- a/CSparse.Tests/Complex/Factorization/SparseLUTest.cs +++ b/CSparse.Tests/Complex/Factorization/SparseLUTest.cs @@ -76,5 +76,34 @@ public void TestEmptyFactorize() Assert.That(lu, Is.Not.Null); Assert.That(lu.NonZerosCount == 0, Is.True); } + + [Test] + public void TestRefactorize() + { + var A = ResourceLoader.Get("general-40x40.mat"); + + var lu = SparseLU.Create(A, ColumnOrdering.MinimumDegreeAtPlusA, 1.0); + + // Same sparsity pattern, different values (B = 2*A) : numeric refactorization + // must reuse the cached symbolic ordering and still solve correctly. + var B = A.Clone(); + var bv = B.Values; + for (int i = 0; i < bv.Length; i++) bv[i] *= 2.0; + + lu.Refactorize(B, 1.0); + + var x = Helper.CreateTestVector(B.ColumnCount); + var b = Helper.Multiply(B, x); + var r = Vector.Clone(b); + + lu.Solve(b, x); + B.Multiply(-1.0, x, 1.0, r); + + Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Dimension guard. + var small = new SparseMatrix(3, 3, 0); + Assert.Throws(() => lu.Refactorize(small, 1.0)); + } } } diff --git a/CSparse.Tests/Double/Factorization/SparseCholeskyTest.cs b/CSparse.Tests/Double/Factorization/SparseCholeskyTest.cs index 1072bfc..8788688 100644 --- a/CSparse.Tests/Double/Factorization/SparseCholeskyTest.cs +++ b/CSparse.Tests/Double/Factorization/SparseCholeskyTest.cs @@ -53,5 +53,34 @@ public void TestEmptyFactorize() Assert.That(chol, Is.Not.Null); Assert.That(chol.NonZerosCount == 0, Is.True); } + + [Test] + public void TestRefactorize() + { + var A = ResourceLoader.Get("symmetric-40-spd.mat"); + + var chol = SparseCholesky.Create(A, ColumnOrdering.MinimumDegreeAtPlusA); + + // Same sparsity pattern, different values (B = 2*A stays SPD) : the numeric + // refactorization must reuse the cached symbolic analysis and still solve. + var B = A.Clone(); + var bv = B.Values; + for (int i = 0; i < bv.Length; i++) bv[i] *= 2.0; + + chol.Refactorize(B); + + var x = Helper.CreateTestVector(B.ColumnCount); + var b = Helper.Multiply(B, x); + var r = Vector.Clone(b); + + chol.Solve(b, x); + B.Multiply(-1.0, x, 1.0, r); + + Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Dimension guard. + var small = new SparseMatrix(3, 3, 0); + Assert.Throws(() => chol.Refactorize(small)); + } } } diff --git a/CSparse.Tests/Double/Factorization/SparseLDLTest.cs b/CSparse.Tests/Double/Factorization/SparseLDLTest.cs index da1aabe..464ff6e 100644 --- a/CSparse.Tests/Double/Factorization/SparseLDLTest.cs +++ b/CSparse.Tests/Double/Factorization/SparseLDLTest.cs @@ -53,5 +53,34 @@ public void TestSolveNonSpd() Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); } + + [Test] + public void TestRefactorize() + { + var A = ResourceLoader.Get("symmetric-40-spd.mat"); + + var ldl = SparseLDL.Create(A, ColumnOrdering.MinimumDegreeAtPlusA); + + // Same sparsity pattern, different values (B = 2*A) : the numeric + // refactorization must reuse the cached symbolic analysis and still solve. + var B = A.Clone(); + var bv = B.Values; + for (int i = 0; i < bv.Length; i++) bv[i] *= 2.0; + + ldl.Refactorize(B); + + var x = Helper.CreateTestVector(B.ColumnCount); + var b = Helper.Multiply(B, x); + var r = Vector.Clone(b); + + ldl.Solve(b, x); + B.Multiply(-1.0, x, 1.0, r); + + Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Dimension guard. + var small = new SparseMatrix(3, 3, 0); + Assert.Throws(() => ldl.Refactorize(small)); + } } } diff --git a/CSparse.Tests/Double/Factorization/SparseLUTest.cs b/CSparse.Tests/Double/Factorization/SparseLUTest.cs index 87d3bbc..889d3ab 100644 --- a/CSparse.Tests/Double/Factorization/SparseLUTest.cs +++ b/CSparse.Tests/Double/Factorization/SparseLUTest.cs @@ -75,5 +75,39 @@ public void TestEmptyFactorize() Assert.That(lu, Is.Not.Null); Assert.That(lu.NonZerosCount == 0, Is.True); } + + [Test] + public void TestRefactorize() + { + // Load matrix from a file. + var A = ResourceLoader.Get("general-40x40.mat"); + + // Symbolic + numeric factorization of A. + var lu = SparseLU.Create(A, ColumnOrdering.MinimumDegreeAtPlusA, 1.0); + + // Same sparsity pattern, different values (B = 2*A) : numeric refactorization + // must reuse the cached symbolic ordering and still solve correctly. + var B = A.Clone(); + var bv = B.Values; + for (int i = 0; i < bv.Length; i++) bv[i] *= 2.0; + + lu.Refactorize(B, 1.0); + + var x = Helper.CreateTestVector(B.ColumnCount); + var b = Helper.Multiply(B, x); + var r = Vector.Clone(b); + + // Solve Bx = b with the refactorized decomposition. + lu.Solve(b, x); + + // Residual r = b - Bx. + B.Multiply(-1.0, x, 1.0, r); + + Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Dimension guard. + var small = new SparseMatrix(3, 3, 0); + Assert.Throws(() => lu.Refactorize(small, 1.0)); + } } } diff --git a/CSparse/Complex/Factorization/SparseCholesky.cs b/CSparse/Complex/Factorization/SparseCholesky.cs index e7c7c74..5629060 100644 --- a/CSparse/Complex/Factorization/SparseCholesky.cs +++ b/CSparse/Complex/Factorization/SparseCholesky.cs @@ -106,6 +106,32 @@ public int NonZerosCount get { return L.NonZerosCount; } } + /// + /// Numerically re-factorizes a Hermitian positive definite matrix that has the + /// same sparsity pattern as the one passed to Create, reusing the + /// cached symbolic analysis (ordering, elimination tree and column counts). + /// + /// Column-compressed matrix, same size and pattern as in Create. + /// + /// Intended for Newton iterations and time-stepping loops where the matrix + /// values change but its pattern does not: the fill-reducing ordering and the + /// symbolic analysis are skipped, keeping only the numeric work. The matrix + /// must keep the pattern used in Create (the symbolic structure is + /// reused) and stay positive definite. + /// + public void Refactorize(CompressedColumnStorage A) + { + Check.NotNull(A, "A"); + Check.SquareMatrix(A, "A"); + + if (A.ColumnCount != n) + { + throw new ArgumentException("Matrix dimensions don't match the factorization.", "A"); + } + + Factorize(A, null); + } + /// /// Solves a system of linear equations, Ax = b. /// diff --git a/CSparse/Complex/Factorization/SparseLDL.cs b/CSparse/Complex/Factorization/SparseLDL.cs index 01f7807..8f6a1e4 100644 --- a/CSparse/Complex/Factorization/SparseLDL.cs +++ b/CSparse/Complex/Factorization/SparseLDL.cs @@ -112,6 +112,31 @@ public int NonZerosCount get { return L.NonZerosCount; } } + /// + /// Numerically re-factorizes a matrix that has the same sparsity pattern + /// as the one passed to Create, reusing the cached symbolic analysis + /// (ordering and elimination tree). + /// + /// Column-compressed matrix, same size and pattern as in Create. + /// + /// Intended for Newton iterations and time-stepping loops where the matrix + /// values change but its pattern does not: the fill-reducing ordering and the + /// symbolic analysis are skipped, keeping only the numeric work. The matrix + /// must keep the pattern used in Create (the symbolic structure is reused). + /// + public void Refactorize(CompressedColumnStorage A) + { + Check.NotNull(A, "A"); + Check.SquareMatrix(A, "A"); + + if (A.ColumnCount != n) + { + throw new ArgumentException("Matrix dimensions don't match the factorization.", "A"); + } + + Factorize(A, null); + } + /// /// Solves a linear system Ax=b, where A is symmetric positive definite. /// diff --git a/CSparse/Complex/Factorization/SparseLU.cs b/CSparse/Complex/Factorization/SparseLU.cs index 60553bf..513e11a 100644 --- a/CSparse/Complex/Factorization/SparseLU.cs +++ b/CSparse/Complex/Factorization/SparseLU.cs @@ -110,6 +110,37 @@ public int NonZerosCount get { return (L.NonZerosCount + U.NonZerosCount - n); } } + /// + /// Numerically re-factorizes a matrix that has the same sparsity pattern + /// as the one passed to Create, reusing the cached symbolic ordering. + /// + /// Column-compressed matrix, same size as the one used in Create. + /// Partial pivoting tolerance (from 0.0 to 1.0). + /// + /// Intended for Newton iterations and time-stepping loops where the matrix + /// values change but its pattern does not: the fill-reducing column ordering + /// and symbolic analysis are skipped, keeping only the (dominant) numeric work. + /// The column ordering is a permutation, so a different pattern still factorizes + /// correctly, but the fill may then be sub-optimal. + /// + public void Refactorize(CompressedColumnStorage A, double tol = 1.0) + { + Check.NotNull(A, "A"); + Check.SquareMatrix(A, "A"); + Check.NotNaN(tol, "tol"); + + if (A.ColumnCount != n) + { + throw new ArgumentException("Matrix dimensions don't match the factorization.", "A"); + } + + // Ensure tol is in range. + tol = Math.Min(Math.Max(tol, 0.0), 1.0); + + // Reuse the cached symbolic ordering (S); recompute L, U and the pivoting. + Factorize(A, tol, null); + } + /// /// Solves a system of linear equations, Ax = b. /// diff --git a/CSparse/Double/Factorization/SparseCholesky.cs b/CSparse/Double/Factorization/SparseCholesky.cs index 28b07f4..e9f8563 100644 --- a/CSparse/Double/Factorization/SparseCholesky.cs +++ b/CSparse/Double/Factorization/SparseCholesky.cs @@ -105,6 +105,32 @@ public int NonZerosCount get { return L.NonZerosCount; } } + /// + /// Numerically re-factorizes a symmetric positive definite matrix that has the + /// same sparsity pattern as the one passed to Create, reusing the + /// cached symbolic analysis (ordering, elimination tree and column counts). + /// + /// Column-compressed matrix, same size and pattern as in Create. + /// + /// Intended for Newton iterations and time-stepping loops where the matrix + /// values change but its pattern does not: the fill-reducing ordering and the + /// symbolic analysis are skipped, keeping only the numeric work. The matrix + /// must keep the pattern used in Create (the symbolic structure is + /// reused) and stay positive definite. + /// + public void Refactorize(CompressedColumnStorage A) + { + Check.NotNull(A, "A"); + Check.SquareMatrix(A, "A"); + + if (A.ColumnCount != n) + { + throw new ArgumentException("Matrix dimensions don't match the factorization.", "A"); + } + + Factorize(A, null); + } + /// /// Solves a system of linear equations, Ax = b. /// diff --git a/CSparse/Double/Factorization/SparseLDL.cs b/CSparse/Double/Factorization/SparseLDL.cs index de2713a..d9df329 100644 --- a/CSparse/Double/Factorization/SparseLDL.cs +++ b/CSparse/Double/Factorization/SparseLDL.cs @@ -111,6 +111,31 @@ public int NonZerosCount get { return L.NonZerosCount; } } + /// + /// Numerically re-factorizes a matrix that has the same sparsity pattern + /// as the one passed to Create, reusing the cached symbolic analysis + /// (ordering and elimination tree). + /// + /// Column-compressed matrix, same size and pattern as in Create. + /// + /// Intended for Newton iterations and time-stepping loops where the matrix + /// values change but its pattern does not: the fill-reducing ordering and the + /// symbolic analysis are skipped, keeping only the numeric work. The matrix + /// must keep the pattern used in Create (the symbolic structure is reused). + /// + public void Refactorize(CompressedColumnStorage A) + { + Check.NotNull(A, "A"); + Check.SquareMatrix(A, "A"); + + if (A.ColumnCount != n) + { + throw new ArgumentException("Matrix dimensions don't match the factorization.", "A"); + } + + Factorize(A, null); + } + /// /// Solves a linear system Ax=b, where A is symmetric positive definite. /// diff --git a/CSparse/Double/Factorization/SparseLU.cs b/CSparse/Double/Factorization/SparseLU.cs index 6ff2c26..2e08801 100644 --- a/CSparse/Double/Factorization/SparseLU.cs +++ b/CSparse/Double/Factorization/SparseLU.cs @@ -109,6 +109,37 @@ public int NonZerosCount get { return (L.NonZerosCount + U.NonZerosCount - n); } } + /// + /// Numerically re-factorizes a matrix that has the same sparsity pattern + /// as the one passed to Create, reusing the cached symbolic ordering. + /// + /// Column-compressed matrix, same size as the one used in Create. + /// Partial pivoting tolerance (from 0.0 to 1.0). + /// + /// Intended for Newton iterations and time-stepping loops where the matrix + /// values change but its pattern does not: the fill-reducing column ordering + /// and symbolic analysis are skipped, keeping only the (dominant) numeric work. + /// The column ordering is a permutation, so a different pattern still factorizes + /// correctly, but the fill may then be sub-optimal. + /// + public void Refactorize(CompressedColumnStorage A, double tol = 1.0) + { + Check.NotNull(A, "A"); + Check.SquareMatrix(A, "A"); + Check.NotNaN(tol, "tol"); + + if (A.ColumnCount != n) + { + throw new ArgumentException("Matrix dimensions don't match the factorization.", "A"); + } + + // Ensure tol is in range. + tol = Math.Min(Math.Max(tol, 0.0), 1.0); + + // Reuse the cached symbolic ordering (S); recompute L, U and the pivoting. + Factorize(A, tol, null); + } + /// /// Solves a system of linear equations, Ax = b. ///