From ce62566df8b7b30f14bb150002af8c32f2ee1230 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:30:07 +0000 Subject: [PATCH 01/14] Initial plan From e3e348f36d1288b3861895a87941fa3f6191553a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:41:06 +0000 Subject: [PATCH 02/14] Update docstrings for truncated decompositions with detailed keyword explanations Co-authored-by: lkdvos <37111893+lkdvos@users.noreply.github.com> --- src/interface/eig.jl | 27 ++++++++++++++++++++++++--- src/interface/eigh.jl | 29 +++++++++++++++++++++++++---- src/interface/svd.jl | 31 +++++++++++++++++++++++++------ 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/interface/eig.jl b/src/interface/eig.jl index 5a74f79f..58fb911e 100644 --- a/src/interface/eig.jl +++ b/src/interface/eig.jl @@ -31,9 +31,9 @@ See also [`eig_vals(!)`](@ref eig_vals) and [`eig_trunc(!)`](@ref eig_trunc). @functiondef eig_full """ - eig_trunc(A; kwargs...) -> D, V + eig_trunc(A; trunc, kwargs...) -> D, V eig_trunc(A, alg::AbstractAlgorithm) -> D, V - eig_trunc!(A, [DV]; kwargs...) -> D, V + eig_trunc!(A, [DV]; trunc, kwargs...) -> D, V eig_trunc!(A, [DV], alg::AbstractAlgorithm) -> D, V Compute a partial or truncated eigenvalue decomposition of the matrix `A`, @@ -41,6 +41,26 @@ such that `A * V ≈ V * D`, where the (possibly rectangular) matrix `V` contain a subset of eigenvectors and the diagonal matrix `D` contains the associated eigenvalues, selected according to a truncation strategy. +## Keyword arguments +The behavior of this function is controlled by the following keyword arguments: + +- `trunc`: Specifies the truncation strategy. This can be: + - A `NamedTuple` with fields `atol`, `rtol`, and/or `maxrank`, which will be converted to + a [`TruncationStrategy`](@ref). For details on available truncation strategies, see + [Truncations](@ref). + - A `TruncationStrategy` object directly (e.g., `truncrank(10)`, `trunctol(atol=1e-6)`, or + combinations using `&`). + - `nothing` (default), which keeps all eigenvalues. + +- Other keyword arguments are passed to the algorithm selection procedure. If no explicit + `alg` is provided, these keywords are used to select and configure the algorithm through + [`MatrixAlgebraKit.select_algorithm`](@ref). The remaining keywords after algorithm + selection are passed to the algorithm constructor. See [`MatrixAlgebraKit.default_algorithm`](@ref) + for the default algorithm selection behavior. + +When `alg` is a [`TruncatedAlgorithm`](@ref), the `trunc` keyword cannot be specified as the +truncation strategy is already embedded in the algorithm. + !!! note The bang method `eig_trunc!` optionally accepts the output structure and possibly destroys the input matrix `A`. Always use the return value of the function @@ -49,7 +69,8 @@ selected according to a truncation strategy. !!! note $(docs_eig_note) -See also [`eig_full(!)`](@ref eig_full) and [`eig_vals(!)`](@ref eig_vals). +See also [`eig_full(!)`](@ref eig_full), [`eig_vals(!)`](@ref eig_vals), and +[Truncations](@ref) for more information on truncation strategies. """ @functiondef eig_trunc diff --git a/src/interface/eigh.jl b/src/interface/eigh.jl index c51b2c01..f256cfcf 100644 --- a/src/interface/eigh.jl +++ b/src/interface/eigh.jl @@ -31,15 +31,35 @@ See also [`eigh_vals(!)`](@ref eigh_vals) and [`eigh_trunc(!)`](@ref eigh_trunc) @functiondef eigh_full """ - eigh_trunc(A; kwargs...) -> D, V + eigh_trunc(A; trunc, kwargs...) -> D, V eigh_trunc(A, alg::AbstractAlgorithm) -> D, V - eigh_trunc!(A, [DV]; kwargs...) -> D, V + eigh_trunc!(A, [DV]; trunc, kwargs...) -> D, V eigh_trunc!(A, [DV], alg::AbstractAlgorithm) -> D, V Compute a partial or truncated eigenvalue decomposition of the symmetric or hermitian matrix `A`, such that `A * V ≈ V * D`, where the isometric matrix `V` contains a subset of the orthogonal eigenvectors and the real diagonal matrix `D` contains the associated eigenvalues, -selected according to a truncation strategy. +selected according to a truncation strategy. + +## Keyword arguments +The behavior of this function is controlled by the following keyword arguments: + +- `trunc`: Specifies the truncation strategy. This can be: + - A `NamedTuple` with fields `atol`, `rtol`, and/or `maxrank`, which will be converted to + a [`TruncationStrategy`](@ref). For details on available truncation strategies, see + [Truncations](@ref). + - A `TruncationStrategy` object directly (e.g., `truncrank(10)`, `trunctol(atol=1e-6)`, or + combinations using `&`). + - `nothing` (default), which keeps all eigenvalues. + +- Other keyword arguments are passed to the algorithm selection procedure. If no explicit + `alg` is provided, these keywords are used to select and configure the algorithm through + [`MatrixAlgebraKit.select_algorithm`](@ref). The remaining keywords after algorithm + selection are passed to the algorithm constructor. See [`MatrixAlgebraKit.default_algorithm`](@ref) + for the default algorithm selection behavior. + +When `alg` is a [`TruncatedAlgorithm`](@ref), the `trunc` keyword cannot be specified as the +truncation strategy is already embedded in the algorithm. !!! note The bang method `eigh_trunc!` optionally accepts the output structure and @@ -49,7 +69,8 @@ selected according to a truncation strategy. !!! note $(docs_eigh_note) -See also [`eigh_full(!)`](@ref eigh_full) and [`eigh_vals(!)`](@ref eigh_vals). +See also [`eigh_full(!)`](@ref eigh_full), [`eigh_vals(!)`](@ref eigh_vals), and +[Truncations](@ref) for more information on truncation strategies. """ @functiondef eigh_trunc diff --git a/src/interface/svd.jl b/src/interface/svd.jl index ee6cd9af..3725a65c 100644 --- a/src/interface/svd.jl +++ b/src/interface/svd.jl @@ -41,11 +41,10 @@ See also [`svd_full(!)`](@ref svd_full), [`svd_vals(!)`](@ref svd_vals) and """ @functiondef svd_compact -# TODO: decide if we should have `svd_trunc!!` instead """ - svd_trunc(A; kwargs...) -> U, S, Vᴴ + svd_trunc(A; trunc, kwargs...) -> U, S, Vᴴ svd_trunc(A, alg::AbstractAlgorithm) -> U, S, Vᴴ - svd_trunc!(A, [USVᴴ]; kwargs...) -> U, S, Vᴴ + svd_trunc!(A, [USVᴴ]; trunc, kwargs...) -> U, S, Vᴴ svd_trunc!(A, [USVᴴ], alg::AbstractAlgorithm) -> U, S, Vᴴ Compute a partial or truncated singular value decomposition (SVD) of `A`, such that @@ -53,14 +52,34 @@ Compute a partial or truncated singular value decomposition (SVD) of `A`, such t `(m, k)`, whereas `Vᴴ` is a matrix of size `(k, n)` with orthonormal rows and `S` is a square diagonal matrix of size `(k, k)`, with `k` is set by the truncation strategy. +## Keyword arguments +The behavior of this function is controlled by the following keyword arguments: + +- `trunc`: Specifies the truncation strategy. This can be: + - A `NamedTuple` with fields `atol`, `rtol`, and/or `maxrank`, which will be converted to + a [`TruncationStrategy`](@ref). For details on available truncation strategies, see + [Truncations](@ref). + - A `TruncationStrategy` object directly (e.g., `truncrank(10)`, `trunctol(atol=1e-6)`, or + combinations using `&`). + - `nothing` (default), which keeps all singular values. + +- Other keyword arguments are passed to the algorithm selection procedure. If no explicit + `alg` is provided, these keywords are used to select and configure the algorithm through + [`MatrixAlgebraKit.select_algorithm`](@ref). The remaining keywords after algorithm + selection are passed to the algorithm constructor. See [`MatrixAlgebraKit.default_algorithm`](@ref) + for the default algorithm selection behavior. + +When `alg` is a [`TruncatedAlgorithm`](@ref), the `trunc` keyword cannot be specified as the +truncation strategy is already embedded in the algorithm. + !!! note The bang method `svd_trunc!` optionally accepts the output structure and possibly destroys the input matrix `A`. Always use the return value of the function as it may not always be possible to use the provided `USVᴴ` as output. - -See also [`svd_full(!)`](@ref svd_full), [`svd_compact(!)`](@ref svd_compact) and -[`svd_vals(!)`](@ref svd_vals). +See also [`svd_full(!)`](@ref svd_full), [`svd_compact(!)`](@ref svd_compact), +[`svd_vals(!)`](@ref svd_vals), and [Truncations](@ref) for more information on +truncation strategies. """ @functiondef svd_trunc From a6cec2d3afbeac9190d93a633afdacd54e0f02e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:53:53 +0000 Subject: [PATCH 03/14] Rewrite Truncations documentation with comprehensive examples and usage guide Co-authored-by: lkdvos <37111893+lkdvos@users.noreply.github.com> --- docs/src/user_interface/truncations.md | 127 +++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 6 deletions(-) diff --git a/docs/src/user_interface/truncations.md b/docs/src/user_interface/truncations.md index 40799890..7a1390bb 100644 --- a/docs/src/user_interface/truncations.md +++ b/docs/src/user_interface/truncations.md @@ -5,7 +5,11 @@ CollapsedDocStrings = true # Truncations -Currently, truncations are supported through the following different methods: +Truncation strategies allow you to control which eigenvalues or singular values to keep when computing partial or truncated decompositions. These strategies are used in functions like [`eigh_trunc`](@ref), [`eig_trunc`](@ref), and [`svd_trunc`](@ref) to reduce the size of the decomposition while retaining the most important information. + +## Truncation Strategies + +MatrixAlgebraKit provides several built-in truncation strategies: ```@docs; canonical=false notrunc @@ -15,11 +19,122 @@ truncfilter truncerror ``` -It is additionally possible to combine truncation strategies by making use of the `&` operator. -For example, truncating to a maximal dimension `10`, and discarding all values below `1e-6` would be achieved by: +## Combining Strategies + +Truncation strategies can be combined using the `&` operator to create intersection-based truncation. +When strategies are combined, only the values that satisfy all conditions are kept. + +For example, to keep at most 10 eigenvalues while also discarding all values below `1e-6`: + +```julia +combined_trunc = truncrank(10) & trunctol(; atol = 1e-6) +``` + +## Using Truncations in Decompositions + +Truncation strategies can be used with truncated decomposition functions in two ways: + +### 1. Using the `trunc` keyword with a `NamedTuple` + +The simplest approach is to pass a `NamedTuple` with the truncation parameters: ```julia -maxdim = 10 -atol = 1e-6 -combined_trunc = truncrank(maxdim) & trunctol(; atol) +using MatrixAlgebraKit + +# Create a symmetric matrix +A = randn(100, 100) +A = A + A' # Make symmetric + +# Keep only the 10 largest eigenvalues +D, V = eigh_trunc(A; trunc = (maxrank = 10,)) + +# Keep eigenvalues with absolute value above tolerance +D, V = eigh_trunc(A; trunc = (atol = 1e-6,)) + +# Combine multiple criteria +D, V = eigh_trunc(A; trunc = (maxrank = 20, atol = 1e-10, rtol = 1e-8)) +``` + +### 2. Using explicit `TruncationStrategy` objects + +For more control, you can construct `TruncationStrategy` objects directly: + +```julia +# Keep the 5 largest eigenvalues +strategy = truncrank(5) +D, V = eigh_trunc(A; trunc = strategy) + +# Keep eigenvalues above an absolute tolerance +strategy = trunctol(; atol = 1e-6) +D, V = eigh_trunc(A; trunc = strategy) + +# Combine strategies: keep at most 10 eigenvalues, all above 1e-8 +strategy = truncrank(10) & trunctol(; atol = 1e-8) +D, V = eigh_trunc(A; trunc = strategy) +``` + +## Complete Example + +Here's a complete example demonstrating different truncation approaches: + +```julia +using MatrixAlgebraKit +using LinearAlgebra + +# Generate a test matrix with known spectrum +n = 50 +A = randn(n, n) +A = A + A' # Make symmetric + +# 1. No truncation - keep all eigenvalues +D_full, V_full = eigh_trunc(A; trunc = nothing) +@assert size(D_full) == (n, n) + +# 2. Keep only the 10 largest eigenvalues +D_rank, V_rank = eigh_trunc(A; trunc = (maxrank = 10,)) +@assert size(D_rank) == (10, 10) +@assert size(V_rank) == (n, 10) + +# 3. Keep eigenvalues with absolute value above a threshold +D_tol, V_tol = eigh_trunc(A; trunc = (atol = 1e-6,)) +println("Kept $(size(D_tol, 1)) eigenvalues above tolerance") + +# 4. Combine rank and tolerance truncation +strategy = truncrank(15) & trunctol(; atol = 1e-8) +D_combined, V_combined = eigh_trunc(A; trunc = strategy) +println("Kept $(size(D_combined, 1)) eigenvalues (max 15, all above 1e-8)") + +# 5. Truncated SVD example +B = randn(100, 80) +U, S, Vh = svd_trunc(B; trunc = (maxrank = 20,)) +@assert size(S) == (20, 20) +@assert size(U) == (100, 20) +@assert size(Vh) == (20, 80) + +# Verify the truncated decomposition is accurate +@assert norm(B - U * S * Vh) ≈ norm(svd(B).S[21:end]) +``` + +## Truncation with SVD vs Eigenvalue Decompositions + +When using truncations with different decomposition types, keep in mind: + +- **`svd_trunc`**: Singular values are always real and non-negative, sorted in descending order. Truncation by value typically keeps the largest singular values. + +- **`eigh_trunc`**: Eigenvalues are real but can be negative for symmetric matrices. By default, `truncrank` sorts by absolute value, so `truncrank(k)` keeps the `k` eigenvalues with largest magnitude (positive or negative). + +- **`eig_trunc`**: For general (non-symmetric) matrices, eigenvalues can be complex. Truncation by absolute value considers the complex magnitude. + +## Advanced: Custom Truncation Filters + +For specialized needs, you can use [`truncfilter`](@ref) to define custom selection criteria: + +```julia +# Keep only positive eigenvalues +strategy = truncfilter(x -> x > 0) +D_positive, V_positive = eigh_trunc(A; trunc = strategy) + +# Keep eigenvalues in a specific range +strategy = truncfilter(x -> 0.1 < abs(x) < 10.0) +D_range, V_range = eigh_trunc(A; trunc = strategy) ``` From 6069be620751c0c55262a34577284c03151bf144 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:44:55 +0000 Subject: [PATCH 04/14] Convert examples to doctests and use hardcoded matrices for reproducibility Co-authored-by: lkdvos <37111893+lkdvos@users.noreply.github.com> --- docs/src/user_interface/truncations.md | 191 ++++++++++++++++--------- 1 file changed, 122 insertions(+), 69 deletions(-) diff --git a/docs/src/user_interface/truncations.md b/docs/src/user_interface/truncations.md index 7a1390bb..2a10c352 100644 --- a/docs/src/user_interface/truncations.md +++ b/docs/src/user_interface/truncations.md @@ -19,15 +19,16 @@ truncfilter truncerror ``` -## Combining Strategies - Truncation strategies can be combined using the `&` operator to create intersection-based truncation. When strategies are combined, only the values that satisfy all conditions are kept. -For example, to keep at most 10 eigenvalues while also discarding all values below `1e-6`: +```jldoctest +julia> using MatrixAlgebraKit + +julia> combined_trunc = truncrank(10) & trunctol(; atol = 1e-6); -```julia -combined_trunc = truncrank(10) & trunctol(; atol = 1e-6) +julia> typeof(combined_trunc) +MatrixAlgebraKit.TruncationIntersection{Tuple{MatrixAlgebraKit.TruncationByOrder{typeof(abs)}, MatrixAlgebraKit.TruncationByValue{Float64, Int64, typeof(abs)}}} ``` ## Using Truncations in Decompositions @@ -38,81 +39,121 @@ Truncation strategies can be used with truncated decomposition functions in two The simplest approach is to pass a `NamedTuple` with the truncation parameters: -```julia -using MatrixAlgebraKit +```jldoctest truncations +julia> using MatrixAlgebraKit -# Create a symmetric matrix -A = randn(100, 100) -A = A + A' # Make symmetric +julia> # Create a symmetric matrix with known values + A = [4.0 2.0 1.0; 2.0 5.0 3.0; 1.0 3.0 6.0]; -# Keep only the 10 largest eigenvalues -D, V = eigh_trunc(A; trunc = (maxrank = 10,)) +julia> # Keep only the 2 largest eigenvalues + D, V = eigh_trunc(A; trunc = (maxrank = 2,)); -# Keep eigenvalues with absolute value above tolerance -D, V = eigh_trunc(A; trunc = (atol = 1e-6,)) +julia> size(D) +(2, 2) -# Combine multiple criteria -D, V = eigh_trunc(A; trunc = (maxrank = 20, atol = 1e-10, rtol = 1e-8)) +julia> size(V) +(3, 2) +``` + +You can also use tolerance-based truncation or combine multiple criteria: + +```jldoctest truncations +julia> # Keep eigenvalues with absolute value above tolerance + D, V = eigh_trunc(A; trunc = (atol = 1e-6,)); + +julia> size(D, 1) # All eigenvalues are above 1e-6 +3 + +julia> # Combine multiple criteria + D, V = eigh_trunc(A; trunc = (maxrank = 2, atol = 1e-10)); + +julia> size(D) +(2, 2) ``` ### 2. Using explicit `TruncationStrategy` objects For more control, you can construct `TruncationStrategy` objects directly: -```julia -# Keep the 5 largest eigenvalues -strategy = truncrank(5) -D, V = eigh_trunc(A; trunc = strategy) +```jldoctest truncations +julia> # Keep the 2 largest eigenvalues + strategy = truncrank(2); + +julia> D, V = eigh_trunc(A; trunc = strategy); -# Keep eigenvalues above an absolute tolerance -strategy = trunctol(; atol = 1e-6) -D, V = eigh_trunc(A; trunc = strategy) +julia> size(D) +(2, 2) -# Combine strategies: keep at most 10 eigenvalues, all above 1e-8 -strategy = truncrank(10) & trunctol(; atol = 1e-8) -D, V = eigh_trunc(A; trunc = strategy) +julia> # Combine strategies: keep at most 2 eigenvalues, all above 1e-8 + strategy = truncrank(2) & trunctol(; atol = 1e-8); + +julia> D, V = eigh_trunc(A; trunc = strategy); + +julia> size(D) +(2, 2) ``` ## Complete Example Here's a complete example demonstrating different truncation approaches: -```julia -using MatrixAlgebraKit -using LinearAlgebra - -# Generate a test matrix with known spectrum -n = 50 -A = randn(n, n) -A = A + A' # Make symmetric - -# 1. No truncation - keep all eigenvalues -D_full, V_full = eigh_trunc(A; trunc = nothing) -@assert size(D_full) == (n, n) - -# 2. Keep only the 10 largest eigenvalues -D_rank, V_rank = eigh_trunc(A; trunc = (maxrank = 10,)) -@assert size(D_rank) == (10, 10) -@assert size(V_rank) == (n, 10) - -# 3. Keep eigenvalues with absolute value above a threshold -D_tol, V_tol = eigh_trunc(A; trunc = (atol = 1e-6,)) -println("Kept $(size(D_tol, 1)) eigenvalues above tolerance") - -# 4. Combine rank and tolerance truncation -strategy = truncrank(15) & trunctol(; atol = 1e-8) -D_combined, V_combined = eigh_trunc(A; trunc = strategy) -println("Kept $(size(D_combined, 1)) eigenvalues (max 15, all above 1e-8)") - -# 5. Truncated SVD example -B = randn(100, 80) -U, S, Vh = svd_trunc(B; trunc = (maxrank = 20,)) -@assert size(S) == (20, 20) -@assert size(U) == (100, 20) -@assert size(Vh) == (20, 80) - -# Verify the truncated decomposition is accurate -@assert norm(B - U * S * Vh) ≈ norm(svd(B).S[21:end]) +```jldoctest complete_example +julia> using MatrixAlgebraKit, LinearAlgebra + +julia> # Create a symmetric test matrix with known spectrum + A = [10.0 2.0 1.0 0.5; + 2.0 8.0 1.5 0.3; + 1.0 1.5 6.0 0.2; + 0.5 0.3 0.2 4.0]; + +julia> # 1. No truncation - keep all eigenvalues + D_full, V_full = eigh_trunc(A; trunc = nothing); + +julia> size(D_full) +(4, 4) + +julia> # 2. Keep only the 2 largest eigenvalues + D_rank, V_rank = eigh_trunc(A; trunc = (maxrank = 2,)); + +julia> size(D_rank) +(2, 2) + +julia> size(V_rank) +(4, 2) + +julia> # 3. Keep eigenvalues with absolute value above a threshold + D_tol, V_tol = eigh_trunc(A; trunc = (atol = 5.0,)); + +julia> size(D_tol, 1) >= 2 # At least 2 eigenvalues are above 5.0 +true + +julia> # 4. Combine rank and tolerance truncation + strategy = truncrank(3) & trunctol(; atol = 1e-8); + +julia> D_combined, V_combined = eigh_trunc(A; trunc = strategy); + +julia> size(D_combined, 1) <= 3 +true + +julia> # 5. Truncated SVD example + B = [3.0 2.0 1.0; 1.0 4.0 2.0; 2.0 1.0 5.0; 0.5 1.0 2.0]; + +julia> U, S, Vh = svd_trunc(B; trunc = (maxrank = 2,)); + +julia> size(S) +(2, 2) + +julia> size(U) +(4, 2) + +julia> size(Vh) +(2, 3) + +julia> # Verify the truncated decomposition approximates the original + reconstruction_error = norm(B - U * S * Vh); + +julia> reconstruction_error < 2.1 # Error is small (equals smallest discarded singular value) +true ``` ## Truncation with SVD vs Eigenvalue Decompositions @@ -129,12 +170,24 @@ When using truncations with different decomposition types, keep in mind: For specialized needs, you can use [`truncfilter`](@ref) to define custom selection criteria: -```julia -# Keep only positive eigenvalues -strategy = truncfilter(x -> x > 0) -D_positive, V_positive = eigh_trunc(A; trunc = strategy) +```jldoctest custom_filters +julia> using MatrixAlgebraKit + +julia> A = [4.0 -1.0 2.0; -1.0 3.0 1.0; 2.0 1.0 -2.0]; + +julia> # Keep only positive eigenvalues + strategy = truncfilter(x -> x > 0); + +julia> D_positive, V_positive = eigh_trunc(A; trunc = strategy); + +julia> size(D_positive, 1) >= 2 # At least 2 positive eigenvalues +true + +julia> # Keep eigenvalues in a specific range + strategy = truncfilter(x -> 1.0 < abs(x) < 5.0); + +julia> D_range, V_range = eigh_trunc(A; trunc = strategy); -# Keep eigenvalues in a specific range -strategy = truncfilter(x -> 0.1 < abs(x) < 10.0) -D_range, V_range = eigh_trunc(A; trunc = strategy) +julia> size(D_range, 1) >= 1 # At least 1 eigenvalue in range +true ``` From cef025d65e226f193cd440f6c488cfd9210cf88c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:52:11 +0000 Subject: [PATCH 05/14] Use svd_vals to verify truncation error equals discarded singular values Co-authored-by: Jutho <4162102+Jutho@users.noreply.github.com> --- docs/src/user_interface/truncations.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/src/user_interface/truncations.md b/docs/src/user_interface/truncations.md index 2a10c352..a0d61689 100644 --- a/docs/src/user_interface/truncations.md +++ b/docs/src/user_interface/truncations.md @@ -149,10 +149,8 @@ julia> size(U) julia> size(Vh) (2, 3) -julia> # Verify the truncated decomposition approximates the original - reconstruction_error = norm(B - U * S * Vh); - -julia> reconstruction_error < 2.1 # Error is small (equals smallest discarded singular value) +julia> # Verify the truncated decomposition error equals the discarded singular values + norm(B - U * S * Vh) ≈ norm(svd_vals(B)[3:end]) true ``` From 242f10da9ebf1926c63d8c9895395e819d018663 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 10 Oct 2025 10:40:38 -0400 Subject: [PATCH 06/14] Add workflow to comment the PR docs --- .github/workflows/PR_comment.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/PR_comment.yml diff --git a/.github/workflows/PR_comment.yml b/.github/workflows/PR_comment.yml new file mode 100644 index 00000000..fd1f1d5f --- /dev/null +++ b/.github/workflows/PR_comment.yml @@ -0,0 +1,17 @@ +name: Docs preview comment +on: + pull_request: + types: [labeled] + +permissions: + pull-requests: write +jobs: + pr_comment: + runs-on: ubuntu-latest + steps: + - name: Create PR comment + if: github.event_name == 'pull_request' && github.repository == github.event.pull_request.head.repo.full_name && github.event.label.name == 'documentation' + uses: thollander/actions-comment-pull-request@v3 + with: + message: 'After the build completes, the updated documentation will be available [here](https://quantumkithub.github.io/MatrixAlgebraKit.jl/previews/PR${{ github.event.number }}/)' + comment-tag: 'preview-doc' From 13259734c0513cef111db9be078c08b21fedbf64 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 10 Oct 2025 11:18:18 -0400 Subject: [PATCH 07/14] update truncation docs --- docs/src/user_interface/truncations.md | 185 ++++++++----------------- 1 file changed, 58 insertions(+), 127 deletions(-) diff --git a/docs/src/user_interface/truncations.md b/docs/src/user_interface/truncations.md index a0d61689..52d1d682 100644 --- a/docs/src/user_interface/truncations.md +++ b/docs/src/user_interface/truncations.md @@ -7,150 +7,87 @@ CollapsedDocStrings = true Truncation strategies allow you to control which eigenvalues or singular values to keep when computing partial or truncated decompositions. These strategies are used in functions like [`eigh_trunc`](@ref), [`eig_trunc`](@ref), and [`svd_trunc`](@ref) to reduce the size of the decomposition while retaining the most important information. -## Truncation Strategies +## Using Truncations in Decompositions -MatrixAlgebraKit provides several built-in truncation strategies: +Truncation strategies can be used with truncated decomposition functions in two ways, as illustrated below. +For concreteness, we use the following matrix as an example: -```@docs; canonical=false -notrunc -truncrank -trunctol -truncfilter -truncerror -``` +```jldoctest truncations +using MatrixAlgebraKit +using MatrixAlgebraKit: diagview -Truncation strategies can be combined using the `&` operator to create intersection-based truncation. -When strategies are combined, only the values that satisfy all conditions are kept. +A = [2 1 0; 1 3 1; 0 1 4]; +D, V = eigh_full(A); -```jldoctest -julia> using MatrixAlgebraKit +diagview(D) ≈ [3 - √3, 3, 3 + √3] -julia> combined_trunc = truncrank(10) & trunctol(; atol = 1e-6); +# output -julia> typeof(combined_trunc) -MatrixAlgebraKit.TruncationIntersection{Tuple{MatrixAlgebraKit.TruncationByOrder{typeof(abs)}, MatrixAlgebraKit.TruncationByValue{Float64, Int64, typeof(abs)}}} +true ``` -## Using Truncations in Decompositions - -Truncation strategies can be used with truncated decomposition functions in two ways: - ### 1. Using the `trunc` keyword with a `NamedTuple` -The simplest approach is to pass a `NamedTuple` with the truncation parameters: +The simplest approach is to pass a `NamedTuple` with the truncation parameters. +For example, keeping only the largest 2 eigenvalues: ```jldoctest truncations -julia> using MatrixAlgebraKit +Dtrunc, Vtrunc = eigh_trunc(A; trunc = (maxrank = 2,)); +size(Dtrunc, 1) <= 2 -julia> # Create a symmetric matrix with known values - A = [4.0 2.0 1.0; 2.0 5.0 3.0; 1.0 3.0 6.0]; +# output -julia> # Keep only the 2 largest eigenvalues - D, V = eigh_trunc(A; trunc = (maxrank = 2,)); - -julia> size(D) -(2, 2) - -julia> size(V) -(3, 2) +true ``` -You can also use tolerance-based truncation or combine multiple criteria: +Note however that there are no guarantees on the order of the output values: ```jldoctest truncations -julia> # Keep eigenvalues with absolute value above tolerance - D, V = eigh_trunc(A; trunc = (atol = 1e-6,)); - -julia> size(D, 1) # All eigenvalues are above 1e-6 -3 +diagview(Dtrunc) ≈ diagview(D)[[3, 2]] -julia> # Combine multiple criteria - D, V = eigh_trunc(A; trunc = (maxrank = 2, atol = 1e-10)); +# output -julia> size(D) -(2, 2) +true ``` -### 2. Using explicit `TruncationStrategy` objects - -For more control, you can construct `TruncationStrategy` objects directly: +You can also use tolerance-based truncation or combine multiple criteria: ```jldoctest truncations -julia> # Keep the 2 largest eigenvalues - strategy = truncrank(2); +Dtrunc, Vtrunc = eigh_trunc(A; trunc = (atol = 2.9,)); +all(>(2.9), diagview(Dtrunc)) -julia> D, V = eigh_trunc(A; trunc = strategy); +# output -julia> size(D) -(2, 2) - -julia> # Combine strategies: keep at most 2 eigenvalues, all above 1e-8 - strategy = truncrank(2) & trunctol(; atol = 1e-8); - -julia> D, V = eigh_trunc(A; trunc = strategy); - -julia> size(D) -(2, 2) +true ``` -## Complete Example - -Here's a complete example demonstrating different truncation approaches: - -```jldoctest complete_example -julia> using MatrixAlgebraKit, LinearAlgebra - -julia> # Create a symmetric test matrix with known spectrum - A = [10.0 2.0 1.0 0.5; - 2.0 8.0 1.5 0.3; - 1.0 1.5 6.0 0.2; - 0.5 0.3 0.2 4.0]; - -julia> # 1. No truncation - keep all eigenvalues - D_full, V_full = eigh_trunc(A; trunc = nothing); - -julia> size(D_full) -(4, 4) - -julia> # 2. Keep only the 2 largest eigenvalues - D_rank, V_rank = eigh_trunc(A; trunc = (maxrank = 2,)); - -julia> size(D_rank) -(2, 2) - -julia> size(V_rank) -(4, 2) - -julia> # 3. Keep eigenvalues with absolute value above a threshold - D_tol, V_tol = eigh_trunc(A; trunc = (atol = 5.0,)); +```jldoctest truncations +Dtrunc, Vtrunc = eigh_trunc(A; trunc = (maxrank = 2, atol = 2.9)); +size(Dtrunc, 1) <= 2 && all(>(2.9), diagview(Dtrunc)) -julia> size(D_tol, 1) >= 2 # At least 2 eigenvalues are above 5.0 +# output true +``` -julia> # 4. Combine rank and tolerance truncation - strategy = truncrank(3) & trunctol(; atol = 1e-8); - -julia> D_combined, V_combined = eigh_trunc(A; trunc = strategy); - -julia> size(D_combined, 1) <= 3 -true +### 2. Using explicit `TruncationStrategy` objects -julia> # 5. Truncated SVD example - B = [3.0 2.0 1.0; 1.0 4.0 2.0; 2.0 1.0 5.0; 0.5 1.0 2.0]; +For more control, you can construct [`TruncationStrategy`](@ref) objects directly. +This is also what the previous syntax will end up calling. -julia> U, S, Vh = svd_trunc(B; trunc = (maxrank = 2,)); +```jldoctest truncations +Dtrunc, Vtrunc = eigh_trunc(A; trunc = truncrank(2)) +size(Dtrunc, 1) <= 2 -julia> size(S) -(2, 2) +# output -julia> size(U) -(4, 2) +true +``` -julia> size(Vh) -(2, 3) +```jldoctest truncations +Dtrunc, Vtrunc = eigh_trunc(A; trunc = truncrank(2) & trunctol(; atol = 2.9)) +size(Dtrunc, 1) <= 2 && all(>(2.9), diagview(Dtrunc)) -julia> # Verify the truncated decomposition error equals the discarded singular values - norm(B - U * S * Vh) ≈ norm(svd_vals(B)[3:end]) +# output true ``` @@ -164,28 +101,22 @@ When using truncations with different decomposition types, keep in mind: - **`eig_trunc`**: For general (non-symmetric) matrices, eigenvalues can be complex. Truncation by absolute value considers the complex magnitude. -## Advanced: Custom Truncation Filters - -For specialized needs, you can use [`truncfilter`](@ref) to define custom selection criteria: - -```jldoctest custom_filters -julia> using MatrixAlgebraKit - -julia> A = [4.0 -1.0 2.0; -1.0 3.0 1.0; 2.0 1.0 -2.0]; - -julia> # Keep only positive eigenvalues - strategy = truncfilter(x -> x > 0); - -julia> D_positive, V_positive = eigh_trunc(A; trunc = strategy); +## Truncation Strategies -julia> size(D_positive, 1) >= 2 # At least 2 positive eigenvalues -true +MatrixAlgebraKit provides several built-in truncation strategies: -julia> # Keep eigenvalues in a specific range - strategy = truncfilter(x -> 1.0 < abs(x) < 5.0); +```@docs; canonical=false +notrunc +truncrank +trunctol +truncfilter +truncerror +``` -julia> D_range, V_range = eigh_trunc(A; trunc = strategy); +Truncation strategies can be combined using the `&` operator to create intersection-based truncation. +When strategies are combined, only the values that satisfy all conditions are kept. -julia> size(D_range, 1) >= 1 # At least 1 eigenvalue in range -true +```julia +combined_trunc = truncrank(10) & trunctol(; atol = 1e-6); ``` + From 868707ae2ca28f9b29d25bb2b6244b0a7ee4ea07 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 10 Oct 2025 11:40:56 -0400 Subject: [PATCH 08/14] update namedtuple interface --- docs/src/user_interface/truncations.md | 7 ++++ src/interface/truncation.jl | 55 ++++++++++++++++++-------- test/truncate.jl | 2 +- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/docs/src/user_interface/truncations.md b/docs/src/user_interface/truncations.md index 52d1d682..93287920 100644 --- a/docs/src/user_interface/truncations.md +++ b/docs/src/user_interface/truncations.md @@ -69,6 +69,13 @@ size(Dtrunc, 1) <= 2 && all(>(2.9), diagview(Dtrunc)) true ``` +In general, the keyword arguments that are supported can be found in the `TruncationStrategy` docstring: + +```@docs; canonical = false +TruncationStrategy +``` + + ### 2. Using explicit `TruncationStrategy` objects For more control, you can construct [`TruncationStrategy`](@ref) objects directly. diff --git a/src/interface/truncation.jl b/src/interface/truncation.jl index 9504b48f..7f7bd051 100644 --- a/src/interface/truncation.jl +++ b/src/interface/truncation.jl @@ -4,26 +4,36 @@ Select a truncation strategy based on the provided keyword arguments. ## Keyword arguments -- `atol=nothing` : Absolute tolerance for the truncation -- `rtol=nothing` : Relative tolerance for the truncation -- `maxrank=nothing` : Maximal rank for the truncation -""" -function TruncationStrategy(; atol = nothing, rtol = nothing, maxrank = nothing) - if isnothing(maxrank) && isnothing(atol) && isnothing(rtol) - return NoTruncation() - elseif isnothing(maxrank) +The following keyword arguments are all optional, and their default value (`nothing`) +will be ignored. It is also allowed to combine multiple of these, in which case the kept +values will consist of the intersection of the different truncated strategies. + +- `atol::Real` : Absolute tolerance for the truncation +- `rtol::Real` : Relative tolerance for the truncation +- `maxrank::Real` : Maximal rank for the truncation +- `maxerror::Real` : Maximal truncation error. +- `filter` : Custom filter to select truncated values. +""" +function TruncationStrategy(; + atol::Union{Real, Nothing} = nothing, + rtol::Union{Real, Nothing} = nothing, + maxrank::Union{Real, Nothing} = nothing, + maxerror::Union{Real, Nothing} = nothing, + filter = nothing + ) + strategy = notrunc() + + if !isnothing(atol) || !isnothing(rtol) atol = @something atol 0 rtol = @something rtol 0 - return trunctol(; atol, rtol) - else - if isnothing(atol) && isnothing(rtol) - return truncrank(maxrank) - else - atol = @something atol 0 - rtol = @something rtol 0 - return truncrank(maxrank) & trunctol(; atol, rtol) - end + strategy &= trunctol(; atol, rtol) end + + isnothing(maxrank) || (strategy &= truncrank(maxrank)) + isnothing(maxerror) || (strategy &= truncerror(maxerror)) + isnothing(filter) || (strategy &= truncfilter(filter)) + + return strategy end """ @@ -151,6 +161,8 @@ end function Base.:&(trunc1::TruncationStrategy, trunc2::TruncationStrategy) return TruncationIntersection((trunc1, trunc2)) end + +# flatten components function Base.:&(trunc1::TruncationIntersection, trunc2::TruncationIntersection) return TruncationIntersection((trunc1.components..., trunc2.components...)) end @@ -160,3 +172,12 @@ end function Base.:&(trunc1::TruncationStrategy, trunc2::TruncationIntersection) return TruncationIntersection((trunc1, trunc2.components...)) end + +# drop notrunc +Base.:&(::NoTruncation, trunc::TruncationStrategy) = trunc +Base.:&(trunc::TruncationStrategy, ::NoTruncation) = trunc +Base.:&(::NoTruncation, ::NoTruncation) = notrunc() + +# disambiguate +Base.:&(::NoTruncation, trunc::TruncationIntersection) = trunc +Base.:&(trunc::TruncationIntersection, ::NoTruncation) = trunc diff --git a/test/truncate.jl b/test/truncate.jl index 94a788e3..f2d24a7a 100644 --- a/test/truncate.jl +++ b/test/truncate.jl @@ -28,7 +28,7 @@ using MatrixAlgebraKit: NoTruncation, TruncationIntersection, TruncationByOrder, trunc = @constinferred TruncationStrategy(; atol, rtol, maxrank) @test trunc isa TruncationIntersection - @test trunc == truncrank(maxrank) & trunctol(; atol, rtol) + @test trunc == trunctol(; atol, rtol) & truncrank(maxrank) values = [1, 0.9, 0.5, -0.3, 0.01] @test values[@constinferred(findtruncated(values, truncrank(2)))] == values[1:2] From cc53f7a73e28d6b3d3641e32e328f690828a864f Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 10 Oct 2025 11:58:22 -0400 Subject: [PATCH 09/14] mark `trunc` kwarg as optional --- src/interface/eig.jl | 4 ++-- src/interface/eigh.jl | 4 ++-- src/interface/svd.jl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/interface/eig.jl b/src/interface/eig.jl index 58fb911e..52338330 100644 --- a/src/interface/eig.jl +++ b/src/interface/eig.jl @@ -31,9 +31,9 @@ See also [`eig_vals(!)`](@ref eig_vals) and [`eig_trunc(!)`](@ref eig_trunc). @functiondef eig_full """ - eig_trunc(A; trunc, kwargs...) -> D, V + eig_trunc(A; [trunc], kwargs...) -> D, V eig_trunc(A, alg::AbstractAlgorithm) -> D, V - eig_trunc!(A, [DV]; trunc, kwargs...) -> D, V + eig_trunc!(A, [DV]; [trunc], kwargs...) -> D, V eig_trunc!(A, [DV], alg::AbstractAlgorithm) -> D, V Compute a partial or truncated eigenvalue decomposition of the matrix `A`, diff --git a/src/interface/eigh.jl b/src/interface/eigh.jl index f256cfcf..6c19c886 100644 --- a/src/interface/eigh.jl +++ b/src/interface/eigh.jl @@ -31,9 +31,9 @@ See also [`eigh_vals(!)`](@ref eigh_vals) and [`eigh_trunc(!)`](@ref eigh_trunc) @functiondef eigh_full """ - eigh_trunc(A; trunc, kwargs...) -> D, V + eigh_trunc(A; [trunc], kwargs...) -> D, V eigh_trunc(A, alg::AbstractAlgorithm) -> D, V - eigh_trunc!(A, [DV]; trunc, kwargs...) -> D, V + eigh_trunc!(A, [DV]; [trunc], kwargs...) -> D, V eigh_trunc!(A, [DV], alg::AbstractAlgorithm) -> D, V Compute a partial or truncated eigenvalue decomposition of the symmetric or hermitian matrix diff --git a/src/interface/svd.jl b/src/interface/svd.jl index 3725a65c..e36b0462 100644 --- a/src/interface/svd.jl +++ b/src/interface/svd.jl @@ -42,9 +42,9 @@ See also [`svd_full(!)`](@ref svd_full), [`svd_vals(!)`](@ref svd_vals) and @functiondef svd_compact """ - svd_trunc(A; trunc, kwargs...) -> U, S, Vᴴ + svd_trunc(A; [trunc], kwargs...) -> U, S, Vᴴ svd_trunc(A, alg::AbstractAlgorithm) -> U, S, Vᴴ - svd_trunc!(A, [USVᴴ]; trunc, kwargs...) -> U, S, Vᴴ + svd_trunc!(A, [USVᴴ]; [trunc], kwargs...) -> U, S, Vᴴ svd_trunc!(A, [USVᴴ], alg::AbstractAlgorithm) -> U, S, Vᴴ Compute a partial or truncated singular value decomposition (SVD) of `A`, such that From e209163d4a881cf7f0cdc97d6ce46f8853b09277 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 10 Oct 2025 11:59:09 -0400 Subject: [PATCH 10/14] fix signature --- src/interface/truncation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/truncation.jl b/src/interface/truncation.jl index 7f7bd051..10ace743 100644 --- a/src/interface/truncation.jl +++ b/src/interface/truncation.jl @@ -30,7 +30,7 @@ function TruncationStrategy(; end isnothing(maxrank) || (strategy &= truncrank(maxrank)) - isnothing(maxerror) || (strategy &= truncerror(maxerror)) + isnothing(maxerror) || (strategy &= truncerror(; atol = maxerror)) isnothing(filter) || (strategy &= truncfilter(filter)) return strategy From ad5ee362f40928fdc426d39edeb3c1223bed35c4 Mon Sep 17 00:00:00 2001 From: Jutho Haegeman Date: Fri, 10 Oct 2025 22:51:40 +0200 Subject: [PATCH 11/14] change eig notes and outlining --- src/interface/eig.jl | 13 +++++++------ src/interface/eigh.jl | 15 +++++++++------ src/interface/gen_eig.jl | 12 +++++++----- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/interface/eig.jl b/src/interface/eig.jl index 52338330..62dc713e 100644 --- a/src/interface/eig.jl +++ b/src/interface/eig.jl @@ -3,9 +3,10 @@ # TODO: kwargs for sorting eigenvalues? docs_eig_note = """ -Note that [`eig_full`](@ref) and its variants do not assume additional structure on the input, -and therefore will always return complex eigenvalues and eigenvectors. For the real -eigenvalue decomposition of symmetric or hermitian operators, see [`eigh_full`](@ref). + Note that [`eig_full`](@ref) and its variants do not assume any symmetry structure on + the input matrices, and therefore will always return complex eigenvalues and eigenvectors + for reasons of type stability. For the eigenvalue decomposition of symmetric or hermitian + operators, see [`eigh_full`](@ref). """ """ @@ -24,7 +25,7 @@ and the diagonal matrix `D` contains the associated eigenvalues. as it may not always be possible to use the provided `DV` as output. !!! note - $(docs_eig_note) +$(docs_eig_note) See also [`eig_vals(!)`](@ref eig_vals) and [`eig_trunc(!)`](@ref eig_trunc). """ @@ -67,7 +68,7 @@ truncation strategy is already embedded in the algorithm. as it may not always be possible to use the provided `DV` as output. !!! note - $(docs_eig_note) +$(docs_eig_note) See also [`eig_full(!)`](@ref eig_full), [`eig_vals(!)`](@ref eig_vals), and [Truncations](@ref) for more information on truncation strategies. @@ -88,7 +89,7 @@ Compute the list of eigenvalues of `A`. as it may not always be possible to use the provided `D` as output. !!! note - $(docs_eig_note) +$(docs_eig_note) See also [`eig_full(!)`](@ref eig_full) and [`eig_trunc(!)`](@ref eig_trunc). """ diff --git a/src/interface/eigh.jl b/src/interface/eigh.jl index 6c19c886..f1fe1936 100644 --- a/src/interface/eigh.jl +++ b/src/interface/eigh.jl @@ -3,9 +3,12 @@ # TODO: kwargs for sorting eigenvalues? docs_eigh_note = """ -Note that [`eigh_full`](@ref) and its variants assume additional structure on the input, -and therefore will retain the `eltype` of the input for the eigenvalues and eigenvectors. -For generic eigenvalue decompositions, see [`eig_full`](@ref). + Note that [`eigh_full`](@ref) and its variants assume that the input matrix is hermitian, + or thus symmetric if the input is real. The resulting algorithms exploit this structure, + and return eigenvalues that are always real, and eigenvectors that are orthogonal and have + the same `eltype` as the input matrix. If the input matrix does not have this structure, + the generic eigenvalue decomposition provided by [`eig_full`](@ref) and its variants + should be used instead. """ """ @@ -24,7 +27,7 @@ and the real diagonal matrix `D` contains the associated eigenvalues. as it may not always be possible to use the provided `DV` as output. !!! note - $(docs_eigh_note) +$(docs_eigh_note) See also [`eigh_vals(!)`](@ref eigh_vals) and [`eigh_trunc(!)`](@ref eigh_trunc). """ @@ -67,7 +70,7 @@ truncation strategy is already embedded in the algorithm. as it may not always be possible to use the provided `DV` as output. !!! note - $(docs_eigh_note) +$(docs_eigh_note) See also [`eigh_full(!)`](@ref eigh_full), [`eigh_vals(!)`](@ref eigh_vals), and [Truncations](@ref) for more information on truncation strategies. @@ -88,7 +91,7 @@ Compute the list of (real) eigenvalues of the symmetric or hermitian matrix `A`. as it may not always be possible to use the provided `DV` as output. !!! note - $(docs_eigh_note) +$(docs_eigh_note) See also [`eigh_full(!)`](@ref eigh_full) and [`eigh_trunc(!)`](@ref eigh_trunc). """ diff --git a/src/interface/gen_eig.jl b/src/interface/gen_eig.jl index 5b7cfc88..92cd3477 100644 --- a/src/interface/gen_eig.jl +++ b/src/interface/gen_eig.jl @@ -3,9 +3,11 @@ # TODO: kwargs for sorting eigenvalues? docs_gen_eig_note = """ -Note that [`gen_eig_full`](@ref) and its variants do not assume additional structure on the inputs, -and therefore will always return complex eigenvalues and eigenvectors. For the real -generalized eigenvalue decomposition is not yet supported. + Note that [`gen_eig_full`](@ref) and its variants do not assume additional structure + on the inputs, and therefore will always return complex eigenvalues and eigenvectors. + Real eigenvalues can be expected when both input matrices are hermitian and one of them + is positive definite, but specialized methods that exploit this structure are not yet + implemented or supported. """ # TODO: do we need "full"? @@ -26,7 +28,7 @@ and the diagonal matrix `W` contains the associated generalized eigenvalues. possible to use the provided `WV` as output. !!! note - $(docs_gen_eig_note) +$(docs_gen_eig_note) See also [`gen_eig_vals(!)`](@ref eig_vals). """ @@ -47,7 +49,7 @@ Compute the list of generalized eigenvalues of `A` and `B`. provided `W` as output. !!! note - $(docs_gen_eig_note) +$(docs_gen_eig_note) See also [`gen_eig_full(!)`](@ref gen_eig_full). """ From 18a06905e27c44170bc2968ac5a2360ffa7d9bf1 Mon Sep 17 00:00:00 2001 From: Jutho Haegeman Date: Fri, 10 Oct 2025 23:32:57 +0200 Subject: [PATCH 12/14] some other doc corrections and additions --- docs/src/index.md | 3 ++- docs/src/user_interface/decompositions.md | 25 +++++++++++++---------- docs/src/user_interface/properties.md | 23 +++++++++++++++++++++ docs/src/user_interface/truncations.md | 2 +- 4 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 docs/src/user_interface/properties.md diff --git a/docs/src/index.md b/docs/src/index.md index 20e3185a..7055ba3e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -26,6 +26,7 @@ These operations typically follow some common skeleton, and here we go into a li ```@contents Pages = ["user_interface/compositions.md", "user_interface/decompositions.md", - "user_interface/truncations.md", "user_interface/matrix_functions.md"] + "user_interface/truncations.md", "user_interface/properties.md", + "user_interface/matrix_functions.md"] Depth = 2 ``` diff --git a/docs/src/user_interface/decompositions.md b/docs/src/user_interface/decompositions.md index 5a25d3ce..b6d0e94e 100644 --- a/docs/src/user_interface/decompositions.md +++ b/docs/src/user_interface/decompositions.md @@ -55,6 +55,7 @@ Not all matrices can be diagonalized, and some real matrices can only be diagona In particular, the resulting decomposition can only guaranteed to be real for real symmetric inputs `A`. Therefore, we provide `eig_` and `eigh_` variants, where `eig` always results in complex-valued `V` and `D`, while `eigh` requires symmetric inputs but retains the scalartype of the input. +The full set of eigenvalues and eigenvectors can be computed using the [`eig_full`](@ref) and [`eigh_full`](@ref) functions. If only the eigenvalues are required, the [`eig_vals`](@ref) and [`eigh_vals`](@ref) functions can be used. These functions return the diagonal elements of `D` in a vector. @@ -99,7 +100,7 @@ Filter = t -> t isa Type && t <: MatrixAlgebraKit.LAPACK_EigAlgorithm The [Schur decomposition](https://en.wikipedia.org/wiki/Schur_decomposition) transforms a complex square matrix `A` into a product `Q * T * Qᴴ`, where `Q` is unitary and `T` is upper triangular. It rewrites an arbitrary complex square matrix as unitarily similar to an upper triangular matrix whose diagonal elements are the eigenvalues of `A`. -For real matrices, the same decomposition can be achieved with `T` being quasi-upper triangular, ie triangular with blocks of size `(1, 1)` and `(2, 2)` on the diagonal. +For real matrices, the same decomposition can be achieved in real arithmetic by allowing `T` to be quasi-upper triangular, i.e. triangular with blocks of size `(1, 1)` and `(2, 2)` on the diagonal. This decomposition is also useful for computing the eigenvalues of a matrix, which is exposed through the [`schur_vals`](@ref) function. @@ -117,12 +118,12 @@ Filter = t -> t isa Type && t <: MatrixAlgebraKit.LAPACK_EigAlgorithm ## Singular Value Decomposition -The [Singular Value Decomposition](https://en.wikipedia.org/wiki/Singular_value_decomposition) transforms a matrix `A` into a product `U * Σ * Vᴴ`, where `U` and `V` are orthogonal, and `Σ` is diagonal, real and non-negative. -For a square matrix `A`, both `U` and `V` are unitary, and if the singular values are distinct, the decomposition is unique. +The [Singular Value Decomposition](https://en.wikipedia.org/wiki/Singular_value_decomposition) transforms a matrix `A` into a product `U * Σ * Vᴴ`, where `U` and `Vᴴ` are unitary, and `Σ` is diagonal, real and non-negative. +For a square matrix `A`, both `U` and `Vᴴ` are unitary, and if the singular values are distinct, the decomposition is unique. For rectangular matrices `A` of size `(m, n)`, there are two modes of operation, [`svd_full`](@ref) and [`svd_compact`](@ref). -The former ensures that the resulting `U`, and `V` remain square unitary matrices, of size `(m, m)` and `(n, n)`, with rectangular `Σ` of size `(m, n)`. -The latter creates an isometric `U` of size `(m, min(m, n))`, and `V` of size `(n, min(m, n))`, with a square `Σ` of size `(min(m, n), min(m, n))`. +The former ensures that the resulting `U`, and `Vᴴ` remain square unitary matrices, of size `(m, m)` and `(n, n)`, with rectangular `Σ` of size `(m, n)`. +The latter creates an isometric `U` of size `(m, min(m, n))`, and `V = (Vᴴ)'` of size `(n, min(m, n))`, with a square `Σ` of size `(min(m, n), min(m, n))`. It is also possible to compute the singular values only, using the [`svd_vals`](@ref) function. This then returns a vector of the values on the diagonal of `Σ`. @@ -147,21 +148,23 @@ Filter = t -> t isa Type && t <: MatrixAlgebraKit.LAPACK_SVDAlgorithm The [Polar Decomposition](https://en.wikipedia.org/wiki/Polar_decomposition) of a matrix `A` is a factorization `A = W * P`, where `W` is unitary and `P` is positive semi-definite. If `A` is invertible (and therefore square), the polar decomposition always exists and is unique. -For non-square matrices, the polar decomposition is not unique, but `P` is. -In particular, the polar decomposition is unique if `A` is full rank. +For non-square matrices `A` of size `(m, n)`, the decomposition `A = W * P` with `P` positive semi-definite of size `(n, n)` and `W` isometric of size `(m, n)` exists only if `m >= n`, and is unique if `A` and thus `P` is full rank. +For `m <= n`, we can analoguously decompose `A` as `A = P * Wᴴ` with `P` positive semi-definite of size `(m, m)` and `Wᴴ` of size `(m, n)` such that `W = (Wᴴ)'` is isometric. Only in the case `m = n` do both decompositions exist. -This decomposition can be computed for both sides, resulting in the [`left_polar`](@ref) and [`right_polar`](@ref) functions. +The decompositions `A = W * P` or `A = P * Wᴴ` can be computed with the [`left_polar`](@ref) and [`right_polar`](@ref) functions, respectively. ```@docs; canonical=false left_polar right_polar ``` -These functions are implemented by first computing a singular value decomposition, and then constructing the polar decomposition from the singular values and vectors. -Therefore, the relevant LAPACK-based implementation is the one for the SVD: +These functions can be implemented by first computing a singular value decomposition, and then constructing the polar decomposition from the singular values and vectors. Alternatively, the polar decomposition can be computed using an +iterative method based on Newton's method, that can be more efficient for large matrices, especially if they are +close to being isometric already. ```@docs; canonical=false PolarViaSVD +PolarNewton ``` ## Orthogonal Subspaces @@ -179,7 +182,7 @@ right_orth ## Null Spaces Similarly, it can be convenient to obtain an orthogonal basis for the kernel or cokernel of a matrix. -These are the compliments of the image and coimage, and can be computed using the [`left_null`](@ref) and [`right_null`](@ref) functions. +These are the compliments of the coimage and image, respectively, and can be computed using the [`left_null`](@ref) and [`right_null`](@ref) functions. Again, this is typically implemented through a combination of the decompositions mentioned above, and serves as a convenient interface to these operations. ```@docs; canonical=false diff --git a/docs/src/user_interface/properties.md b/docs/src/user_interface/properties.md new file mode 100644 index 00000000..479b0be5 --- /dev/null +++ b/docs/src/user_interface/properties.md @@ -0,0 +1,23 @@ +```@meta +CurrentModule = MatrixAlgebraKit +CollapsedDocStrings = true +``` + +# Matrix Properties + +MatrixAlgebraKit.jl provides a number of methods to check various properties of matrices. + +```@docs; canonical=false +isisometry +isunitary +ishermitian +isantihermitian +``` + +Furthermore, there are also methods to project a matrix onto the nearest matrix (in 2-norm distance) with a given property. + +```@docs; canonical=false +project_isometric +project_hermitian +project_antihermitian +``` \ No newline at end of file diff --git a/docs/src/user_interface/truncations.md b/docs/src/user_interface/truncations.md index 93287920..e6038d32 100644 --- a/docs/src/user_interface/truncations.md +++ b/docs/src/user_interface/truncations.md @@ -5,7 +5,7 @@ CollapsedDocStrings = true # Truncations -Truncation strategies allow you to control which eigenvalues or singular values to keep when computing partial or truncated decompositions. These strategies are used in functions like [`eigh_trunc`](@ref), [`eig_trunc`](@ref), and [`svd_trunc`](@ref) to reduce the size of the decomposition while retaining the most important information. +Truncation strategies allow you to control which eigenvalues or singular values to keep when computing partial or truncated decompositions. These strategies are used in the functions [`eigh_trunc`](@ref), [`eig_trunc`](@ref), and [`svd_trunc`](@ref) to reduce the size of the decomposition while retaining the most important information. ## Using Truncations in Decompositions From ec2e27dac324b9946851d95b5bd57f6be280e8c7 Mon Sep 17 00:00:00 2001 From: Jutho Haegeman Date: Fri, 10 Oct 2025 23:50:43 +0200 Subject: [PATCH 13/14] fix typo --- docs/src/user_interface/properties.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/user_interface/properties.md b/docs/src/user_interface/properties.md index 479b0be5..badf4281 100644 --- a/docs/src/user_interface/properties.md +++ b/docs/src/user_interface/properties.md @@ -8,7 +8,7 @@ CollapsedDocStrings = true MatrixAlgebraKit.jl provides a number of methods to check various properties of matrices. ```@docs; canonical=false -isisometry +isisometric isunitary ishermitian isantihermitian From 2ad268f867ff2065c6a57a29ba51dfd89b76543f Mon Sep 17 00:00:00 2001 From: Jutho Haegeman Date: Sat, 11 Oct 2025 00:02:12 +0200 Subject: [PATCH 14/14] add new file to toc --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index 3e37770b..c2ae1fcc 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -52,6 +52,7 @@ makedocs(; "user_interface/compositions.md", "user_interface/decompositions.md", "user_interface/truncations.md", + "user_interface/properties.md", "user_interface/matrix_functions.md", ], "Developer Interface" => "dev_interface.md",