diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index ee0aafb..ee21827 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,4 +2,4 @@ ko_fi: cwoltering liberapay: cwoltering -custom: ['http://wo80.bplaced.net/donate.html'] +custom: ['https://wo80.webspace.rocks/donate/'] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 893ffa2..dccce14 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..045a391 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,125 @@ +### Version 4.3.0 - 2025-11-11 + +* Add net10.0 and remove net6.0 target framework. +* Minor optimizations for matrix-vector multiplication. +* Minor optimizations checking empty Span. + +### Version 4.2.0 - 2024-09-15 + +* Make `SymbolicColumnStorage` class public and update `StronglyConnectedComponents` and `DulmageMendelsohn` decomposition accordingly. + +### Version 4.1.0 - 2024-06-14 + +* Add overload for creating a sparse matrix from an enumerable of `ValueTuple`. +* Add matrix `EnumerateIndexedAsValueTuples()` to enumerate entries as `ValueTuple`. + +### Version 4.0.0 - 2024-04-03 + +The major version change is due to the removal of obsolete methods in the `Converter` class. Visibility of that class was changed from public to internal. In case those obsolete methods were still used, please switch to the static conversion methods provided by the `SparseMatrix` class. + +Additional changes: + +* Add helper method `Helper.ValidateStorage(...)` to validate the structure of a sparse matrix. +* Update to `GetHashCode()` method of `CompressedColumnStorage` class. +* Improvements to documentation. + +### Version 3.8.1 - 2023-11-15 + +* Add overloads for permutation `Invert()` and `IsValid()` methods taking the permutation length as argument. + +### Version 3.8.0 - 2023-05-20 + +* Add overloads for the factorization `Solve()` methods taking `Span` as argument. Note that this introduces a dependency on `System.Memory` for the netstandard2.0 assembly. + +### Version 3.7.0 - 2022-05-04 + +* Add sparse matrix `OfDiagonals` static method (similar to MATLAB spdiags). + +### Version 3.6.0 - 2021-11-25 + +* Remove .NET 4.5 target framework, upgrade .NET 5.0 to 6.0. +* Add constructor that takes explicit non-zeros count to `CoordinateStorage` class. + +### Version 3.5.0 - 2021-01-14 + +* Remove .NET 4.0 target framework, add .NET 5.0. + +### Version 3.4.9 - 2020-11-06 + +* Add `CoordinateStorage` constructor that uses existing storage arrays. +* Convert `CoordinateStorage` to sparse matrix in place. + +### Version 3.4.7 - 2020-08-28 + +* BREAKING: make `SparseLDL` constructor private (use static create methods instead). +* Add complex version of `SparseLDL`. +* Add `matrix.EnumerateIndexed(action)` overload. + +### Version 3.4.6 - 2020-07-21 + +* Add `SolveTranspose` method for `SparseQR`. + +### Version 3.4.5 - 2020-06-11 + +This release introduces the static `SparseMatrix.AutoTrimStorage` option, which enables control over hidden memory allocations in matrix addition and multiplication. By default, the matrix storage will be resized to exactly fit the non-zeros count, which involves new memory allocations. If you want to avoid this, set `AutoTrimStorage` to `false`. + +Additional changes: + +* Add public helper methods `Helper.TrimStrorage(...)` and `Helper.SortIndices(...)` +* Add `DenseMatrix.OfJaggedArray(...)` + +### Version 3.4.3 - 2020-05-25 + +* Add a sparse matrix multiplication overload that accepts the result matrix as a parameter. + +### Version 3.4.2 - 2020-05-13 + +* Make CSparse.NET CLS compliant +* Mark public methods of Converter class as obsolete + +### Version 3.4.1 - 2019-10-02 + +* Improved validation of matrix constructor arguments +* Fixes an issue with `CoordinateStorage` throwing `IndexOutOfRangeException` (introduced in v3.4.0) + +### Version 3.4.0 - 2019-09-15 + +* Parallel dense and sparse matrix multiplication (by Andreas Girgensohn) +* General performance improvements for sparse matrix addition and multiplication + +### Version 3.3.0 - 2019-04-29 + +* Support more target frameworks (including netstandard2.0). +* Public access to members of Dulmage-Mendelsohn decomposition. +* Compute strongly connected components. + +### Version 3.2.3 - 2018-11-30 + +* Added matrix creation helper (e.g. call `SparseMatrix.OfIndexed(s)` to convert coordinate storage). + +### Version 3.2.2 - 2018-10-12 + +* Added MatrixMarket writer. +* BREAKING: make `IProgress interface` compatible with .NET 4.5. + +### Version 3.2.1 - 2018-09-17 + +### Version 3.2.0 - 2018-03-09 + +* Added new `DenseMatrix` type. +* BREAKING: removed deprecated `CompressedColumnStorage` type. +* BREAKING: removed deprecated `matrix.Norm(int)` method. + +### Version 3.1.10 - 2018-03-06 + +* Rename `CompressedColumnStorage` to `SparseMatrix`. +* BREAKING: `matrix.Multiply(x, y)` overwrites y (instead of update). +* BREAKING: sparse matrix `PermuteColumns` returns a new matrix (instead of update). + +### Version 3.1.9 - 2017-01-06 + +* BREAKING: use static `Create` methods (e.g. `SparseLU.Create(...)`) instead of constructors. + +### Version 3.1.4 - 2015-09-19 + +* Initial release of CSparse.NET (based on Tim Davis CSparse version 3.1.4) diff --git a/CSparse.Tests/CSparse.Tests.csproj b/CSparse.Tests/CSparse.Tests.csproj index f58e88a..a795c2b 100644 --- a/CSparse.Tests/CSparse.Tests.csproj +++ b/CSparse.Tests/CSparse.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false @@ -41,9 +41,9 @@ - - - + + + diff --git a/CSparse.Tests/Complex/Factorization/SparseLUTest.cs b/CSparse.Tests/Complex/Factorization/SparseLUTest.cs index de063ec..9ffac2d 100644 --- a/CSparse.Tests/Complex/Factorization/SparseLUTest.cs +++ b/CSparse.Tests/Complex/Factorization/SparseLUTest.cs @@ -3,6 +3,7 @@ namespace CSparse.Tests.Complex.Factorization using CSparse.Complex; using CSparse.Complex.Factorization; using NUnit.Framework; + using System; using Complex = System.Numerics.Complex; public class SparseLUTest @@ -30,6 +31,14 @@ public void TestSolve() A.Multiply(-1.0, x, 1.0, r); Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Test exceptions: + + var e1 = Assert.Throws(() => lu.Solve(b, null)); + var e2 = Assert.Throws(() => lu.Solve(null, x)); + + Assert.That(e1.ParamName, Is.EqualTo("result")); + Assert.That(e2.ParamName, Is.EqualTo("input")); } [Test] diff --git a/CSparse.Tests/Double/Factorization/SparseLUTest.cs b/CSparse.Tests/Double/Factorization/SparseLUTest.cs index 625d635..87d3bbc 100644 --- a/CSparse.Tests/Double/Factorization/SparseLUTest.cs +++ b/CSparse.Tests/Double/Factorization/SparseLUTest.cs @@ -3,6 +3,7 @@ namespace CSparse.Tests.Double.Factorization using CSparse.Double; using CSparse.Double.Factorization; using NUnit.Framework; + using System; public class SparseLUTest { @@ -29,6 +30,14 @@ public void TestSolve() A.Multiply(-1.0, x, 1.0, r); Assert.That(Vector.Norm(r.Length, r) < EPS, Is.True); + + // Test exceptions: + + var e1 = Assert.Throws(() => lu.Solve(b, null)); + var e2 = Assert.Throws(() => lu.Solve(null, x)); + + Assert.That(e1.ParamName, Is.EqualTo("result")); + Assert.That(e2.ParamName, Is.EqualTo("input")); } [Test] diff --git a/CSparse.sln b/CSparse.sln index 57511aa..7105c81 100644 --- a/CSparse.sln +++ b/CSparse.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36705.20 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSparse", "CSparse\CSparse.csproj", "{BE369FD3-02F6-4A13-8DA2-E44A819FAE3C}" EndProject @@ -9,6 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSparse.Tests", "CSparse.Te EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A01A8F54-DCC1-4E01-B6B4-1C653B382D30}" ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md README.md = README.md EndProjectSection EndProject diff --git a/CSparse/CSparse.csproj b/CSparse/CSparse.csproj index 87c411d..b5eb86b 100644 --- a/CSparse/CSparse.csproj +++ b/CSparse/CSparse.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net6.0;net8.0 + netstandard2.0;net8.0;net10.0 False True CSparse @@ -9,42 +9,47 @@ CSparse.NET provides numerical methods for sparse LU, Cholesky and QR decomposition of real and complex linear systems. CSparse.NET - Copyright Christian Woltering © 2012-2024 + Copyright Christian Woltering © 2012-2025 Christian Woltering - 4.2.0.0 - 4.2.0.0 + 4.3.0.0 + 4.3.0.0 math sparse matrix lu cholesky qr decomposition factorization - 4.2.0 + 4.3.0 CSparse CSparse LGPL-2.1-only https://github.com/wo80/CSparse.NET https://github.com/wo80/CSparse.NET.git git - - Version 4.2.0 + Version 4.3.0 - * Make SymbolicColumnStorage class public and update StronglyConnectedComponents and DulmageMendelsohn decomposition accordingly. +* Add net10.0 and remove net6.0 target framework. +* Minor optimizations for matrix-vector multiplication. +* Minor optimizations checking empty Span. - Version 4.1.0 +Version 4.2.0 - * Add overload for creating a sparse matrix from an enumerable of ValueTuple. - * Add matrix EnumerateIndexedAsValueTuples() to enumerate entries as ValueTuple. +* Make SymbolicColumnStorage class public and update StronglyConnectedComponents and DulmageMendelsohn decomposition accordingly. - Version 4.0.0 +Version 4.1.0 - The major version change is due to the removal of obsolete methods in the Converter class. Visibility of that class was changed from public to internal. In case those obsolete methods were still used, please switch to the static conversion methods provided by the SparseMatrix class. +* Add overload for creating a sparse matrix from an enumerable of ValueTuple. +* Add matrix EnumerateIndexedAsValueTuples() to enumerate entries as ValueTuple. - Other changes in this version: +Version 4.0.0 - * Addition of helper method Helper.ValidateStorage(...) to validate the structure of a sparse matrix. - * Update to GetHashCode() method of CompressedColumnStorage class. - * Improvements to documentation. - +The major version change is due to the removal of obsolete methods in the Converter class. Visibility of that class was changed from public to internal. In case those obsolete methods were still used, please switch to the static conversion methods provided by the SparseMatrix class. + +Other changes in this version: + +* Addition of helper method Helper.ValidateStorage(...) to validate the structure of a sparse matrix. +* Update to GetHashCode() method of CompressedColumnStorage class. +* Improvements to documentation. + - + diff --git a/CSparse/Complex/Factorization/SparseCholesky.cs b/CSparse/Complex/Factorization/SparseCholesky.cs index e129694..e7c7c74 100644 --- a/CSparse/Complex/Factorization/SparseCholesky.cs +++ b/CSparse/Complex/Factorization/SparseCholesky.cs @@ -120,9 +120,9 @@ public int NonZerosCount /// The left hand side vector, x. public void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = this.temp; diff --git a/CSparse/Complex/Factorization/SparseLDL.cs b/CSparse/Complex/Factorization/SparseLDL.cs index 71e72b1..01f7807 100644 --- a/CSparse/Complex/Factorization/SparseLDL.cs +++ b/CSparse/Complex/Factorization/SparseLDL.cs @@ -126,9 +126,9 @@ public int NonZerosCount /// Solution vector x. public void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = temp; diff --git a/CSparse/Complex/Factorization/SparseLU.cs b/CSparse/Complex/Factorization/SparseLU.cs index 6161654..60553bf 100644 --- a/CSparse/Complex/Factorization/SparseLU.cs +++ b/CSparse/Complex/Factorization/SparseLU.cs @@ -124,9 +124,9 @@ public int NonZerosCount /// The left hand side vector, x. public void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = this.temp; @@ -153,9 +153,9 @@ public void Solve(ReadOnlySpan input, Span result) /// The left hand side vector, x. public void SolveTranspose(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = this.temp; diff --git a/CSparse/Complex/Factorization/SparseQR.cs b/CSparse/Complex/Factorization/SparseQR.cs index e2f7b1e..0e88785 100644 --- a/CSparse/Complex/Factorization/SparseQR.cs +++ b/CSparse/Complex/Factorization/SparseQR.cs @@ -99,9 +99,9 @@ private SparseQR(int rows, int columns) /// public override void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = new Complex[S.m2]; @@ -154,9 +154,9 @@ public override void Solve(ReadOnlySpan input, Span result) /// The left hand side vector, x. public void SolveTranspose(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); int m2 = S.m2; diff --git a/CSparse/Complex/SparseMatrix.cs b/CSparse/Complex/SparseMatrix.cs index eb0ce2e..d591fed 100644 --- a/CSparse/Complex/SparseMatrix.cs +++ b/CSparse/Complex/SparseMatrix.cs @@ -171,15 +171,18 @@ public override void Multiply(ReadOnlySpan x, Span y) } int end; + Complex xj; for (int j = 0; j < columns; j++) { end = ap[j + 1]; + xj = x[j]; + // Loop over the rows for (int k = ap[j]; k < end; k++) { - y[ai[k]] += x[j] * ax[k]; + y[ai[k]] += xj * ax[k]; } } } diff --git a/CSparse/Double/Factorization/SparseCholesky.cs b/CSparse/Double/Factorization/SparseCholesky.cs index c96555b..28b07f4 100644 --- a/CSparse/Double/Factorization/SparseCholesky.cs +++ b/CSparse/Double/Factorization/SparseCholesky.cs @@ -119,9 +119,9 @@ public int NonZerosCount /// The left hand side vector, x. public void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = this.temp; diff --git a/CSparse/Double/Factorization/SparseLDL.cs b/CSparse/Double/Factorization/SparseLDL.cs index 740da8e..de2713a 100644 --- a/CSparse/Double/Factorization/SparseLDL.cs +++ b/CSparse/Double/Factorization/SparseLDL.cs @@ -125,9 +125,9 @@ public int NonZerosCount /// Solution vector x. public void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = temp; diff --git a/CSparse/Double/Factorization/SparseLU.cs b/CSparse/Double/Factorization/SparseLU.cs index 8d7d0a2..6ff2c26 100644 --- a/CSparse/Double/Factorization/SparseLU.cs +++ b/CSparse/Double/Factorization/SparseLU.cs @@ -123,9 +123,9 @@ public int NonZerosCount /// The left hand side vector, x. public void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = this.temp; @@ -152,9 +152,9 @@ public void Solve(ReadOnlySpan input, Span result) /// The left hand side vector, x. public void SolveTranspose(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = this.temp; diff --git a/CSparse/Double/Factorization/SparseQR.cs b/CSparse/Double/Factorization/SparseQR.cs index fea3394..9f88ee8 100644 --- a/CSparse/Double/Factorization/SparseQR.cs +++ b/CSparse/Double/Factorization/SparseQR.cs @@ -98,9 +98,9 @@ private SparseQR(int rows, int columns) /// public override void Solve(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); var x = new double[S.m2]; @@ -152,9 +152,9 @@ public override void Solve(ReadOnlySpan input, Span result) /// The left hand side vector, x. public void SolveTranspose(ReadOnlySpan input, Span result) { - if (input == null) throw new ArgumentNullException(nameof(input)); + if (input.IsEmpty) throw new ArgumentNullException(nameof(input)); - if (result == null) throw new ArgumentNullException(nameof(result)); + if (result.IsEmpty) throw new ArgumentNullException(nameof(result)); int m2 = S.m2; diff --git a/CSparse/Double/SparseMatrix.cs b/CSparse/Double/SparseMatrix.cs index 6537cb4..b18aabc 100644 --- a/CSparse/Double/SparseMatrix.cs +++ b/CSparse/Double/SparseMatrix.cs @@ -171,15 +171,18 @@ public override void Multiply(ReadOnlySpan x, Span y) } int end; + double xj; for (int j = 0; j < columns; j++) { end = ap[j + 1]; + xj = x[j]; + // Loop over the rows. for (int k = ap[j]; k < end; k++) { - y[ai[k]] += x[j] * ax[k]; + y[ai[k]] += xj * ax[k]; } } }