Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add BandedSylvesterRecurrence #7

Merged
merged 9 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ authors = ["Tianyi Pu <[email protected]> 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"
Expand All @@ -12,11 +13,16 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

[compat]
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 = "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"
StaticArrays = "0.8, 0.9, 0.10, 0.11, 0.12, 1"
Test = "1"
julia = "1.3"

[extras]
Expand Down
29 changes: 7 additions & 22 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
80 changes: 80 additions & 0 deletions src/BandedSylvesters.jl
Original file line number Diff line number Diff line change
@@ -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.
"""
BandedSylvesterRecurrence

@static if VERSION < v"1.8"
include("fragments/BandedSylvesterRecurrence1.jl")
else
include("fragments/BandedSylvesterRecurrence2.jl")
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

1 change: 1 addition & 0 deletions src/PseudostableRecurrences.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/Utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ iscircular(::Array) = false
iscircular(::CircularArray) = true
iscircular(A::AbstractArray) = iscircular(parent(A))

tocircular(A::AbstractArray) = iscircular(A) ? A : CircularArray(A)
tocircular(A::AbstractArray) = iscircular(A) ? A : CircularArray(A)

# not to be confused with LinearAlgebra.BLAS.dotu
_dotu(x, y) = mapreduce(*, +, x, y)
8 changes: 8 additions & 0 deletions src/fragments/BandedSylvesterRecurrence1.jl
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions src/fragments/BandedSylvesterRecurrence2.jl
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading