Skip to content

Commit c42fedf

Browse files
authored
Refine tensor transform interface (#170)
* Refine tensor transform interface * work on tests * Make plan_transform and grid only overloads * tests pass * coverage * add tests * add docs * Update index.md * Update bases.jl * Update bases.jl * Update test_splines.jl * fix tests * increase cov * plan_transform(P, Block(K,J)) * plan_grid_transform with Block(K,J) * Update bases.jl
1 parent 3c174df commit c42fedf

File tree

11 files changed

+242
-66
lines changed

11 files changed

+242
-66
lines changed

.github/workflows/documentation.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Documentation
2+
3+
on:
4+
push:
5+
branches:
6+
- master # update to match your development branch (master, main, dev, trunk, ...)
7+
tags: '*'
8+
pull_request:
9+
10+
jobs:
11+
build:
12+
permissions:
13+
contents: write
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v2
17+
- uses: julia-actions/setup-julia@v1
18+
with:
19+
version: '1.9'
20+
- name: Install dependencies
21+
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
22+
- name: Build and deploy
23+
env:
24+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token
25+
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key
26+
run: julia --project=docs/ docs/make.jl

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ContinuumArrays"
22
uuid = "7ae1f121-cc2c-504b-ac30-9b923412ae5c"
3-
version = "0.16.4"
3+
version = "0.17"
44

55
[deps]
66
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ A package for representing quasi arrays with continuous dimensions
33

44
[![Build Status](https://github.com/JuliaApproximation/ContinuumArrays.jl/workflows/CI/badge.svg)](https://github.com/JuliaApproximation/ContinuumArrays.jl/actions)
55
[![codecov](https://codecov.io/gh/JuliaApproximation/ContinuumArrays.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaApproximation/ContinuumArrays.jl)
6+
[![docs](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaApproximation.github.io/ContinuumArrays.jl/dev)
67

78

89
A quasi array as implemented in [QuasiArrays.jl](https://github.com/JuliaApproximation/QuasiArrays.jl) is a

docs/Project.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[deps]
2+
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
4+
[compat]
5+
Documenter = "1"

docs/make.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Documenter, ContinuumArrays
2+
3+
makedocs(
4+
modules = [ContinuumArrays],
5+
sitename="ContinuumArrays.jl",
6+
pages = Any[
7+
"Home" => "index.md"])
8+
9+
deploydocs(
10+
repo = "github.com/JuliaApproximation/ContinuumArrays.jl.git",
11+
push_preview = true
12+
)

docs/src/index.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# ContinuumArrays.jl
2+
A Julia package for working with bases as quasi-arrays
3+
4+
## The Basis interface
5+
6+
To add your own bases, subtype `Basis` and overload the following routines:
7+
8+
1. `axes(::MyBasis) = (Inclusion(mydomain), OneTo(mylength))`
9+
2. `grid(::MyBasis, ::Integer)`
10+
3. `getindex(::MyBasis, x, ::Integer)`
11+
4. `==(::MyBasis, ::MyBasis)`
12+
13+
Optional overloads are:
14+
15+
1. `plan_transform(::MyBasis, sizes::NTuple{N,Int}, region)` to plan a transform, for a tensor
16+
product of the specified sizes, similar to `plan_fft`.
17+
2. `diff(::MyBasis, dims=1)` to support differentiation and `Derivative`.
18+
3. `grammatrix(::MyBasis)` to support `Q'Q`.
19+
4. `ContinuumArrays._sum(::MyBasis, dims)` and `ContinuumArrays._cumsum(::MyBasis, dims)` to support definite and indefinite integeration.
20+
21+
22+
## Routines
23+
24+
25+
```@docs
26+
Derivative
27+
```
28+
```@docs
29+
transform
30+
```
31+
```@docs
32+
expand
33+
```
34+
```@docs
35+
grid
36+
```
37+
38+
39+
## Interal Routines
40+
41+
```@docs
42+
ContinuumArrays.TransformFactorization
43+
```
44+
45+
```@docs
46+
ContinuumArrays.AbstractConcatBasis
47+
```
48+
```@docs
49+
ContinuumArrays.MulPlan
50+
```
51+
```@docs
52+
ContinuumArrays.PiecewiseBasis
53+
```
54+
```@docs
55+
ContinuumArrays.MappedFactorization
56+
```
57+
```@docs
58+
ContinuumArrays.basis
59+
```
60+
```@docs
61+
ContinuumArrays.InvPlan
62+
```
63+
```@docs
64+
ContinuumArrays.VcatBasis
65+
```
66+
```@docs
67+
ContinuumArrays.WeightedFactorization
68+
```
69+
```@docs
70+
ContinuumArrays.HvcatBasis
71+
```
72+
```@docs
73+
ContinuumArrays.ProjectionFactorization
74+
```
75+
```

src/bases/bases.jl

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -153,50 +153,68 @@ copy(L::Ldiv{<:AbstractBasisLayout,BroadcastLayout{typeof(*)}}) = _broadcast_mul
153153
copy(L::Ldiv{<:MappedBasisLayouts,BroadcastLayout{typeof(*)}}) = _broadcast_mul_ldiv(map(MemoryLayout,arguments(L.B)), L.A, L.B)
154154

155155

156-
# expansion
157-
grid_layout(_, P, n...) = error("Overload Grid")
158156

159-
grid_layout(::MappedBasisLayout, P, n...) = invmap(parentindices(P)[1])[grid(demap(P), n...)]
160-
grid_layout(::SubBasisLayout, P::AbstractQuasiMatrix, n) = grid(parent(P), maximum(parentindices(P)[2][n]))
161-
grid_layout(::SubBasisLayout, P::AbstractQuasiMatrix) = grid(parent(P), maximum(parentindices(P)[2]))
162-
grid_layout(::WeightedBasisLayouts, P, n...) = grid(unweighted(P), n...)
163157

164158

165159
"""
166160
grid(P, n...)
167161
168162
Creates a grid of points. if `n` is unspecified it will
169163
be sufficient number of points to determine `size(P,2)`
170-
coefficients. Otherwise its enough points to determine `n`
171-
coefficients.
164+
coefficients. If `n` is an integer or `Block` its enough points to determine `n`
165+
coefficients. If `n` is a tuple then it returns a tuple of grids corresponding to a
166+
tensor-product. That is, a 5⨱6 2D transform would be
167+
```julia
168+
(x,y) = grid(P, (5,6))
169+
plan_transform(P, (5,6)) * f.(x, y')
170+
```
171+
and a 5×6×7 3D transform would be
172+
```julia
173+
(x,y,z) = grid(P, (5,6,7))
174+
plan_transform(P, (5,6,7)) * f.(x, y', reshape(z,1,1,:))
175+
```
172176
"""
173-
grid(P, n...) = grid_layout(MemoryLayout(P), P, n...)
177+
grid(P, n::Block{1}) = grid_layout(MemoryLayout(P), P, n...)
178+
grid(P, n::Integer) = grid_layout(MemoryLayout(P), P, n...)
179+
grid(L, B::Block) = grid(L, Block.(B.n)) # grid(L, Block(2,3)) == grid(L, (Block(2), Block(3))
180+
grid(L, ns::Tuple) = grid.(Ref(L), ns)
181+
grid(L) = grid(L, size(L,2))
174182

183+
grid_layout(_, P, n) = grid_axis(axes(P,2), P, n)
175184

176-
# values(f) =
185+
grid_axis(::OneTo, P, n::Block) = grid(P, size(P,2))
177186

187+
grid_layout(::MappedBasisLayout, P, n) = invmap(parentindices(P)[1])[grid(demap(P), n)]
188+
grid_layout(::SubBasisLayout, P::AbstractQuasiMatrix, n) = grid(parent(P), parentindices(P)[2][n])
189+
grid_layout(::WeightedBasisLayouts, P, n) = grid(unweighted(P), n)
178190

179191

180-
function plan_grid_transform_layout(lay, L, szs::NTuple{N,Int}, dims=1:N) where N
181-
p = grid(L)
182-
p, InvPlan(factorize(L[p,:]), dims)
183-
end
192+
# Default transform is just solve least squares on a grid
193+
# note this computes the grid an extra time.
194+
mapfactorize(L, n::Integer) = factorize(L[grid(L,n),OneTo(n)])
195+
mapfactorize(L, n::Block{1}) = factorize(L[grid(L,n),Block.(OneTo(Int(n)))])
184196

185-
function plan_grid_transform_layout(::MappedBasisLayout, L, szs::NTuple{N,Int}, dims=1:N) where N
186-
x,F = plan_grid_transform(demap(L), szs, dims)
187-
invmap(parentindices(L)[1])[x], F
197+
mapfactorize(L, ns) = map(n -> mapfactorize(L,n), ns)
198+
function plan_transform_layout(lay, L, szs::NTuple{N,Union{Int,Block{1}}}, dims=ntuple(identity,Val(N))) where N
199+
dimsz = getindex.(Ref(szs), dims) # get the sizes of transformed dimensions
200+
InvPlan(mapfactorize(L, dimsz), dims)
188201
end
202+
plan_transform_layout(::MappedBasisLayout, L, szs::NTuple{N,Union{Int,Block{1}}}, dims=ntuple(identity,Val(N))) where N = plan_transform(demap(L), szs, dims)
203+
plan_transform(L, szs::NTuple{N,Union{Int,Block{1}}}, dims=ntuple(identity,Val(N))) where N = plan_transform_layout(MemoryLayout(L), L, szs, dims)
189204

190-
plan_grid_transform(L, szs::NTuple{N,Int}, dims=1:N) where N = plan_grid_transform_layout(MemoryLayout(L), L, szs, dims)
205+
plan_transform(L, arr::AbstractArray, dims...) = plan_transform(L, size(arr), dims...)
206+
plan_transform(L, lng::Union{Integer,Block{1}}, dims...) = plan_transform(L, (lng,), dims...)
207+
plan_transform(L) = plan_transform(L, size(L,2))
191208

192-
plan_grid_transform(L, arr::AbstractArray, dims...) = plan_grid_transform(L, size(arr), dims...)
193-
plan_grid_transform(L, lng::Union{Integer,Block{1}}, dims...) = plan_grid_transform(L, (lng,), dims...)
209+
plan_transform(L, B::Block, dims...) = plan_transform(L, Block.(B.n), dims...) # grid(L, Block(2,3)) == grid(L, (Block(2), Block(3))
210+
194211

195-
plan_transform(P, szs, dims...) = plan_grid_transform(P, szs, dims...)[2]
196212

197-
_factorize(::AbstractBasisLayout, L, dims...; kws...) =
198-
TransformFactorization(plan_grid_transform(L, (size(L,2), dims...), 1)...)
213+
plan_grid_transform(P, szs::NTuple{N,Union{Integer,Block{1}}}, dims=ntuple(identity,Val(N))) where N = grid(P, getindex.(Ref(szs), dims)), plan_transform(P, szs, dims)
214+
plan_grid_transform(P, lng::Union{Integer,Block{1}}, dims=1) = plan_grid_transform(P, (lng,), dims)
215+
plan_grid_transform(P, B::Block{N}, dims=ntuple(identity,Val(N))) where N = plan_grid_transform(P, Block.(B.n), dims)
199216

217+
_factorize(::AbstractBasisLayout, L, dims...; kws...) = TransformFactorization(plan_grid_transform(L, (size(L,2), dims...), 1)...)
200218

201219

202220
"""

src/bases/splines.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ function getindex(B::HeavisideSpline{T}, x::Number, k::Int) where T
4747
return zero(T)
4848
end
4949

50-
grid(L::HeavisideSpline, n...) = L.points[1:end-1] .+ diff(L.points)/2
51-
grid(L::LinearSpline, n...) = L.points
50+
# Splines sample same number of points regardless of length.
51+
grid(L::HeavisideSpline, ::Integer) = L.points[1:end-1] .+ diff(L.points)/2
52+
grid(L::LinearSpline, ::Integer) = L.points
5253

5354
## Sub-bases
5455

src/plans.jl

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,52 +31,53 @@ end
3131
3232
Takes a factorization and supports it applied to different dimensions.
3333
"""
34-
struct InvPlan{T, Fact, Dims} <: Plan{T}
35-
factorization::Fact
34+
struct InvPlan{T, Facts<:Tuple, Dims} <: Plan{T}
35+
factorizations::Facts
3636
dims::Dims
3737
end
3838

39-
InvPlan(fact, dims) = InvPlan{eltype(fact), typeof(fact), typeof(dims)}(fact, dims)
39+
InvPlan(fact::Tuple, dims) = InvPlan{eltype(fact), typeof(fact), typeof(dims)}(fact, dims)
40+
InvPlan(fact, dims) = InvPlan((fact,), dims)
4041

41-
size(F::InvPlan, k...) = size(F.factorization, k...)
42+
size(F::InvPlan) = size.(F.factorizations, 1)
4243

4344

44-
function *(P::InvPlan{<:Any,<:Any,Int}, x::AbstractVector)
45+
function *(P::InvPlan{<:Any,<:Tuple,Int}, x::AbstractVector)
4546
@assert P.dims == 1
46-
P.factorization \ x
47+
only(P.factorizations) \ x # Only a single factorization when dims isa Int
4748
end
4849

49-
function *(P::InvPlan{<:Any,<:Any,Int}, X::AbstractMatrix)
50+
function *(P::InvPlan{<:Any,<:Tuple,Int}, X::AbstractMatrix)
5051
if P.dims == 1
51-
P.factorization \ X
52+
only(P.factorizations) \ X # Only a single factorization when dims isa Int
5253
else
5354
@assert P.dims == 2
54-
permutedims(P.factorization \ permutedims(X))
55+
permutedims(only(P.factorizations) \ permutedims(X))
5556
end
5657
end
5758

58-
function *(P::InvPlan{<:Any,<:Any,Int}, X::AbstractArray{<:Any,3})
59+
function *(P::InvPlan{<:Any,<:Tuple,Int}, X::AbstractArray{<:Any,3})
5960
Y = similar(X)
6061
if P.dims == 1
6162
for j in axes(X,3)
62-
Y[:,:,j] = P.factorization \ X[:,:,j]
63+
Y[:,:,j] = only(P.factorizations) \ X[:,:,j]
6364
end
6465
elseif P.dims == 2
6566
for k in axes(X,1)
66-
Y[k,:,:] = P.factorization \ X[k,:,:]
67+
Y[k,:,:] = only(P.factorizations) \ X[k,:,:]
6768
end
6869
else
6970
@assert P.dims == 3
7071
for k in axes(X,1), j in axes(X,2)
71-
Y[k,j,:] = P.factorization \ X[k,j,:]
72+
Y[k,j,:] = only(P.factorizations) \ X[k,j,:]
7273
end
7374
end
7475
Y
7576
end
7677

7778
function *(P::InvPlan, X::AbstractArray)
7879
for d in P.dims
79-
X = InvPlan(P.factorization, d) * X
80+
X = InvPlan(P.factorizations[d], d) * X
8081
end
8182
X
8283
end
@@ -87,54 +88,55 @@ end
8788
8889
Takes a matrix and supports it applied to different dimensions.
8990
"""
90-
struct MulPlan{T, Fact, Dims} <: Plan{T}
91-
matrix::Fact
91+
struct MulPlan{T, Fact<:Tuple, Dims} <: Plan{T}
92+
matrices::Fact
9293
dims::Dims
9394
end
9495

95-
MulPlan(fact, dims) = MulPlan{eltype(fact), typeof(fact), typeof(dims)}(fact, dims)
96+
MulPlan(mats::Tuple, dims) = MulPlan{eltype(mats), typeof(mats), typeof(dims)}(mats, dims)
97+
MulPlan(mats::AbstractMatrix, dims) = MulPlan((mats,), dims)
9698

97-
function *(P::MulPlan{<:Any,<:Any,Int}, x::AbstractVector)
99+
function *(P::MulPlan{<:Any,<:Tuple,Int}, x::AbstractVector)
98100
@assert P.dims == 1
99-
P.matrix * x
101+
only(P.matrices) * x
100102
end
101103

102-
function *(P::MulPlan{<:Any,<:Any,Int}, X::AbstractMatrix)
104+
function *(P::MulPlan{<:Any,<:Tuple,Int}, X::AbstractMatrix)
103105
if P.dims == 1
104-
P.matrix * X
106+
only(P.matrices) * X
105107
else
106108
@assert P.dims == 2
107-
permutedims(P.matrix * permutedims(X))
109+
permutedims(only(P.matrices) * permutedims(X))
108110
end
109111
end
110112

111-
function *(P::MulPlan{<:Any,<:Any,Int}, X::AbstractArray{<:Any,3})
113+
function *(P::MulPlan{<:Any,<:Tuple,Int}, X::AbstractArray{<:Any,3})
112114
Y = similar(X)
113115
if P.dims == 1
114116
for j in axes(X,3)
115-
Y[:,:,j] = P.matrix * X[:,:,j]
117+
Y[:,:,j] = only(P.matrices) * X[:,:,j]
116118
end
117119
elseif P.dims == 2
118120
for k in axes(X,1)
119-
Y[k,:,:] = P.matrix * X[k,:,:]
121+
Y[k,:,:] = only(P.matrices) * X[k,:,:]
120122
end
121123
else
122124
@assert P.dims == 3
123125
for k in axes(X,1), j in axes(X,2)
124-
Y[k,j,:] = P.matrix * X[k,j,:]
126+
Y[k,j,:] = only(P.matrices) * X[k,j,:]
125127
end
126128
end
127129
Y
128130
end
129131

130132
function *(P::MulPlan, X::AbstractArray)
131133
for d in P.dims
132-
X = MulPlan(P.matrix, d) * X
134+
X = MulPlan(P.matrices[d], d) * X
133135
end
134136
X
135137
end
136138

137-
*(A::AbstractMatrix, P::MulPlan) = MulPlan(A*P.matrix, P.dims)
139+
*(A::AbstractMatrix, P::MulPlan) = MulPlan(Ref(A) .* P.matrices, P.dims)
138140

139-
inv(P::MulPlan) = InvPlan(factorize(P.matrix), P.dims)
140-
inv(P::InvPlan) = MulPlan(P.factorization, P.dims)
141+
inv(P::MulPlan) = InvPlan(map(factorize,P.matrices), P.dims)
142+
inv(P::InvPlan) = MulPlan(convert.(Matrix,P.factorizations), P.dims)

0 commit comments

Comments
 (0)