From 3cf4136cd3748ad40b880c77ed404f1e87c0933d Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 10:04:01 +0000 Subject: [PATCH 1/9] add BandedSylvesterRecurrence --- docs/src/examples.md | 29 +++--------- src/BandedSylvesters.jl | 80 ++++++++++++++++++++++++++++++++++ src/PseudostableRecurrences.jl | 1 + src/Utils.jl | 5 ++- 4 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 src/BandedSylvesters.jl diff --git a/docs/src/examples.md b/docs/src/examples.md index 4ed889a..42b478a 100644 --- a/docs/src/examples.md +++ b/docs/src/examples.md @@ -21,7 +21,7 @@ end plot(4:N, abs.(x.-sqrt(2))[4:N], label="numerical error", yaxis=:log, background_color=:transparent, foreground_color=:gray) ``` -The backward recurrence, on the other hand, will be dominated by the eigenvalue ``\frac{1}{3}`` instead. While there may be other algorithms that are stable in this case, they are not trivial and vary across different problems. +The backward recurrence, on the other hand, will be dominated by the eigenvalue ``\frac{1}{3}`` instead. While there may be other algorithms that are stable in this case, they often requires the asymptotic behavior of the sequence and vary across different problems. ## Pseudo-stablisation methods The idea of pseudo-stablisation is to use a high precision to achieve a given error tolerance despite the instability. The precision choice is smart such that no extra care is taken by the end user, resulting in a "stable" behavior. Needless to say, pseudo-stablisation only works on problems where the initial values and the recurrence coefficients can be computed to arbitrary precision. @@ -31,36 +31,21 @@ The current iteration of the strategy focuses on linear problems, where the end The test recurrence may sound like a performance trade-off, but since it is run under a low precision, the cost is negligible. ### Linear-recursive Sequence -First, we shall recognise that the linear recursive sequence is basically a 1D stencil recurrence with the stencil +First, we shall recognise that the linear recursive sequence is basically a 1D stencil recurrence. The recurrence needs to be converted to an assignment first, that is, ``x_{n}=\frac{10}{3}x_{n-1}-3x_{n-2}+\frac{2}{3}x_{n-3}``. Such a recurrence can be defined by the stencil with the coefficients ```@example 1 -stencil = (CartesianIndex(-3), CartesianIndex(-2), CartesianIndex(-1)); -``` -with the coefficients -```@example 1 -coef0(m) = 2//3 -coef1(m) = -3 -coef2(m) = 10//3 -coefs = (coef0, coef1, coef2) -``` - -They will work together to define the recurrence -```julia -# calculate x[m] -x[m] = 0 -for i in 1:3 - x[m] += coef[i](m)*x[m+stencil[i]] -end +stencil = (CartesianIndex(-3), CartesianIndex(-2), CartesianIndex(-1)) +coefs = (n -> 2//3, n -> -3, n -> 10//3) ``` -!!! note "Inhomogenious case" +!!! note "Inhomogeneous case" To define a linear inhomogeneous recurrence, the coefficient associated with the zero cartesian index is used. For example, the recurrence ```math - x_{n+3}-\frac{10}{3}x_{n+2}+3x_{n+1}-\frac{2}{3}x_n=\frac{1}{n} + x_{n}=\frac{10}{3}x_{n-1}-3x_{n-2}+\frac{2}{3}x_{n-3}+\frac{1}{n} ``` should be defined by ```julia stencil = (CartesianIndex(-3), CartesianIndex(-2), CartesianIndex(-1), CartesianIndex(0)) - coefs = (coef0, coef1, coef2, m -> 1//m) + coefs = (coef0, coef1, coef2, n -> 1//n) ``` We then need to define the initial values diff --git a/src/BandedSylvesters.jl b/src/BandedSylvesters.jl new file mode 100644 index 0000000..e829af5 --- /dev/null +++ b/src/BandedSylvesters.jl @@ -0,0 +1,80 @@ +import ArrayLayouts: colsupport, rowsupport +import BandedMatrices: lowerbandwidth + +""" + BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} + +The recurrence generated from the infinite Sylvester equation ``AX+XB+C=0``, assuming ``X`` has infinite number of columns. The upper bandwidth of ``A`` has to be finite, the lower bandwidth of ``B`` has to be positive and finite and the lower bounding band of ``B`` can't contain zero. The recurrence is basically a cross-shaped stencil recurrence, where the width of the stencil is determined by The total bandwidth of ``B`` and the height by that of ``A``. See also [`BandedSylvesterRecurrencePlan`](@ref). + +!!! note + The restriction to the bandwidths may not be optimal, but it's the boundary of our knowledge at the moment. + +# Properties +- `A::TA, B::TB, C::TC`: the matrices ``A``, ``B`` and ``C``. It's recommended to use `BandedMatrices.jl` to boost performance. Their dimensions don't have to match. Just make sure that no `BoundsError` happens during the recurrence. +- `buffer::TX`: the buffer that stores temp results. +- `sliceind::Int`: the current column index. +- `lastind::Int`: the last column index to be computed. +""" +mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} + const A::TA + const B::TB + const C::TC + const buffer::TX + sliceind::Int + const lastind::Int +end +buffer(R::BandedSylvesterRecurrence) = R.buffer + +""" + BandedSylvesterRecurrencePlan{FA<:Function, FB<:Function, FC<:Function, INIT<:Function} <: AbstractLinearRecurrencePlan + +See also [`BandedSylvesterRecurrence`](@ref). + +# Properties +- `fA::FA, fB::FB, fC::FC`: the functions that generate `A`, `B` and `C` for the `BandedSylvesterRecurrence`. The functions should have the form `f(eltype, size...)`. +- `init::INIT`: the function that generates the first few columns of ``X`` in order to start the recurrence. It should have the form `f(eltype, size...)`. +- `size::Dims{2}`: the size of ``X``. +- `bandB::NTuple{2,Int}`: the bandwidths of `B`. Although `B` can have infinite upper bandwidth, that will cause the stencil to have infinite width and hence in practice, the upper bandwidth of `B` is always limited by the finite width of ``X``. +- `firstind::Int`: the first column to be computed. The default value is `bandB[2]+1`. +""" +struct BandedSylvesterRecurrencePlan{FA<:Function, FB<:Function, FC<:Function, INIT<:Function} <: AbstractLinearRecurrencePlan + fA::FA + fB::FB + fC::FC + init::INIT + size::Dims{2} + bandB::NTuple{2,Int} + firstind::Int +end +BandedSylvesterRecurrencePlan(fA, fB, fC, init, size, bandB) = BandedSylvesterRecurrencePlan(fA, fB, fC, init, size, bandB, bandB[2]+1) +size(P::BandedSylvesterRecurrencePlan) = P.size + +function init(P::BandedSylvesterRecurrencePlan; T=Float64, init=:default) + if init == :default + buffer = tocircular(P.init(T, size(P)...)) + elseif init == :rand + buffer = CircularArray(rand(T, front(size(P))..., P.firstind)) + end + A = P.fA(T) + B = P.fB(T) + BandedSylvesterRecurrence(A, B, buffer, P.firstind, P.size[end]) +end + +function step!(R::BandedSylvesterRecurrence) + if R.sliceind > R.lastind + return nothing + end + A, B, X = R.A, R.B, R.buffer + nx = R.sliceind + n = nx - lowerbandwidth(B) + ind = axes(X, 1) + Bs = colsupport(B, n) + for m in ind + I1 = rowsupport(A, m) ∩ ind + I2 = (Base.OneTo(nx-1)) ∩ Bs + X[m, nx] = (_dotu(view(A,m,I1), view(X,I1,n)) + _dotu(view(X,m,I2), view(B,I2,n)) + C[nx,n]) / B[nx,n] + end + R.sliceind += 1 + return view(X, :, nx) +end + diff --git a/src/PseudostableRecurrences.jl b/src/PseudostableRecurrences.jl index 674e26d..11b4b5f 100644 --- a/src/PseudostableRecurrences.jl +++ b/src/PseudostableRecurrences.jl @@ -8,6 +8,7 @@ include("FillArraysExt.jl") include("BandedMatricesExt.jl") include("AbstractRecurrences.jl") include("StencilRecurrences.jl") +include("BandedSylvesters.jl") import LinearAlgebra: norm export stable_recurrence diff --git a/src/Utils.jl b/src/Utils.jl index 048757f..d045811 100644 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -29,4 +29,7 @@ iscircular(::Array) = false iscircular(::CircularArray) = true iscircular(A::AbstractArray) = iscircular(parent(A)) -tocircular(A::AbstractArray) = iscircular(A) ? A : CircularArray(A) \ No newline at end of file +tocircular(A::AbstractArray) = iscircular(A) ? A : CircularArray(A) + +# not to be confused with LinearAlgebra.BLAS.dotu +_dotu(x, y) = mapreduce(*, +, x, y) \ No newline at end of file From ec8a9b62e703a3d5e6d5855181610f6c797aa1eb Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 10:14:18 +0000 Subject: [PATCH 2/9] Update Project.toml --- Project.toml | 2 ++ test/runtests.jl | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b76d444..f040310 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Tianyi Pu <912396513@qq.com> and contributors"] version = "0.0.1" [deps] +ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" BandedMatrices = "aae01518-5342-5314-be14-df237901396f" CircularArrays = "7a955b69-7140-5f4e-a0ed-f168c5e2e749" Infinities = "e1ba4f0e-776d-440f-acd9-e1d2e9742647" @@ -12,6 +13,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] +ArrayLayouts = "1" BandedMatrices = "0.15, 0.16, 0.17, 1" CircularArrays = "1" Infinities = "0.1" diff --git a/test/runtests.jl b/test/runtests.jl index 17c58d0..365c445 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,5 +12,5 @@ end using Aqua @testset "Project quality" begin - Aqua.test_all(PseudostableRecurrences, ambiguities=false, piracies=true, deps_compat=false, unbound_args=false) + Aqua.test_all(PseudostableRecurrences, ambiguities=false, piracies=true, deps_compat=true, unbound_args=false) end \ No newline at end of file From 9eeec42bda514d28c876a7190cef454836425ca9 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 10:18:40 +0000 Subject: [PATCH 3/9] Update Project.toml --- Project.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f040310..5ca5994 100644 --- a/Project.toml +++ b/Project.toml @@ -13,11 +13,12 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] -ArrayLayouts = "1" +ArrayLayouts = "0.5, 0.6, 0.7, 0.8, 1" BandedMatrices = "0.15, 0.16, 0.17, 1" CircularArrays = "1" Infinities = "0.1" LazyArrays = "0.16, 0.17, 0.18, 0.19, 0.20, 0.21, 0.22, 1" +LinearAlgebra = "1" StaticArrays = "0.8, 0.9, 0.10, 0.11, 0.12, 1" julia = "1.3" From b604ccafbf9da2fecdf13dbaf17b4761214c9338 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 10:23:57 +0000 Subject: [PATCH 4/9] Update Project.toml --- Project.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5ca5994..c5bed9d 100644 --- a/Project.toml +++ b/Project.toml @@ -13,9 +13,11 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] -ArrayLayouts = "0.5, 0.6, 0.7, 0.8, 1" +Aqua = "0.8" +ArrayLayouts = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1" BandedMatrices = "0.15, 0.16, 0.17, 1" CircularArrays = "1" +Documenter = "1" Infinities = "0.1" LazyArrays = "0.16, 0.17, 0.18, 0.19, 0.20, 0.21, 0.22, 1" LinearAlgebra = "1" From c4aba1c9d44b892c287def534724a6a4ccb7e0e7 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 10:25:50 +0000 Subject: [PATCH 5/9] Update Project.toml --- Project.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c5bed9d..8ff4c58 100644 --- a/Project.toml +++ b/Project.toml @@ -20,8 +20,9 @@ CircularArrays = "1" Documenter = "1" Infinities = "0.1" LazyArrays = "0.16, 0.17, 0.18, 0.19, 0.20, 0.21, 0.22, 1" -LinearAlgebra = "1" +LinearAlgebra = "0, 1" StaticArrays = "0.8, 0.9, 0.10, 0.11, 0.12, 1" +Test = "1" julia = "1.3" [extras] From 2de63ef21785a0fc130e9e7232656787d75bf59b Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 10:27:53 +0000 Subject: [PATCH 6/9] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8ff4c58..e030e1f 100644 --- a/Project.toml +++ b/Project.toml @@ -17,7 +17,7 @@ Aqua = "0.8" ArrayLayouts = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1" BandedMatrices = "0.15, 0.16, 0.17, 1" CircularArrays = "1" -Documenter = "1" +Documenter = "0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 1" Infinities = "0.1" LazyArrays = "0.16, 0.17, 0.18, 0.19, 0.20, 0.21, 0.22, 1" LinearAlgebra = "0, 1" From 98d93ebf7a8b94bdc9ca3bfb1afdf04be3d96823 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 11:16:03 +0000 Subject: [PATCH 7/9] Update CI.yml --- .github/workflows/CI.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ae2ff18..4670660 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -24,6 +24,12 @@ jobs: matrix: version: - '1.3' + - '1.4' + - '1.5' + - '1.6' + - '1.7' + - '1.8' + - '1.9' - '1.10' - 'nightly' os: From ddac41a48e2a80011e6327f3c6f9b1553630d487 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 11:28:44 +0000 Subject: [PATCH 8/9] Update BandedSylvesters.jl --- src/BandedSylvesters.jl | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/BandedSylvesters.jl b/src/BandedSylvesters.jl index e829af5..1a7e272 100644 --- a/src/BandedSylvesters.jl +++ b/src/BandedSylvesters.jl @@ -15,13 +15,24 @@ The recurrence generated from the infinite Sylvester equation ``AX+XB+C=0``, ass - `sliceind::Int`: the current column index. - `lastind::Int`: the last column index to be computed. """ -mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} - const A::TA - const B::TB - const C::TC - const buffer::TX - sliceind::Int - const lastind::Int +@static if VERSION < v"1.8" + mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} + A::TA + B::TB + C::TC + buffer::TX + sliceind::Int + lastind::Int + end +else + mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} + const A::TA + const B::TB + const C::TC + const buffer::TX + sliceind::Int + const lastind::Int + end end buffer(R::BandedSylvesterRecurrence) = R.buffer From 99d1c1b82a2f938caf52af6ef93ab43db934c653 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 4 Feb 2024 11:42:15 +0000 Subject: [PATCH 9/9] try to fix --- src/BandedSylvesters.jl | 21 +++++---------------- src/fragments/BandedSylvesterRecurrence1.jl | 8 ++++++++ src/fragments/BandedSylvesterRecurrence2.jl | 8 ++++++++ 3 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 src/fragments/BandedSylvesterRecurrence1.jl create mode 100644 src/fragments/BandedSylvesterRecurrence2.jl diff --git a/src/BandedSylvesters.jl b/src/BandedSylvesters.jl index 1a7e272..5c4ec41 100644 --- a/src/BandedSylvesters.jl +++ b/src/BandedSylvesters.jl @@ -15,25 +15,14 @@ The recurrence generated from the infinite Sylvester equation ``AX+XB+C=0``, ass - `sliceind::Int`: the current column index. - `lastind::Int`: the last column index to be computed. """ +BandedSylvesterRecurrence + @static if VERSION < v"1.8" - mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} - A::TA - B::TB - C::TC - buffer::TX - sliceind::Int - lastind::Int - end + include("fragments/BandedSylvesterRecurrence1.jl") else - mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} - const A::TA - const B::TB - const C::TC - const buffer::TX - sliceind::Int - const lastind::Int - end + include("fragments/BandedSylvesterRecurrence2.jl") end + buffer(R::BandedSylvesterRecurrence) = R.buffer """ diff --git a/src/fragments/BandedSylvesterRecurrence1.jl b/src/fragments/BandedSylvesterRecurrence1.jl new file mode 100644 index 0000000..fd0ae5d --- /dev/null +++ b/src/fragments/BandedSylvesterRecurrence1.jl @@ -0,0 +1,8 @@ +mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} + A::TA + B::TB + C::TC + buffer::TX + sliceind::Int + lastind::Int +end \ No newline at end of file diff --git a/src/fragments/BandedSylvesterRecurrence2.jl b/src/fragments/BandedSylvesterRecurrence2.jl new file mode 100644 index 0000000..980ba17 --- /dev/null +++ b/src/fragments/BandedSylvesterRecurrence2.jl @@ -0,0 +1,8 @@ +mutable struct BandedSylvesterRecurrence{T, TA<:AbstractMatrix{T}, TB<:AbstractMatrix{T}, TC<:AbstractMatrix{T}, TX<:AbstractMatrix{T}} <: AbstractLinearRecurrence{slicetype(TX)} + const A::TA + const B::TB + const C::TC + const buffer::TX + sliceind::Int + const lastind::Int +end \ No newline at end of file