Skip to content

Commit ae8a8fa

Browse files
authored
Add AffineMap (#34)
* Add AffineMap * add tests to AffineMap, igetindex * Update Project.toml
1 parent bce7aaf commit ae8a8fa

File tree

4 files changed

+91
-32
lines changed

4 files changed

+91
-32
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
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.0.3"
3+
version = "0.1"
44

55
[deps]
66
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
@@ -14,7 +14,7 @@ QuasiArrays = "c4ea9172-b204-11e9-377d-29865faadc5c"
1414
BandedMatrices = "0.14.1"
1515
FillArrays = "0.8.2"
1616
IntervalSets = "0.3.2"
17-
LazyArrays = "0.14.7"
17+
LazyArrays = "0.14.7, 0.15"
1818
QuasiArrays = "0.0.6"
1919
julia = "1.1"
2020

src/ContinuumArrays.jl

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ module ContinuumArrays
22
using IntervalSets, LinearAlgebra, LazyArrays, FillArrays, BandedMatrices, QuasiArrays
33
import Base: @_inline_meta, @_propagate_inbounds_meta, axes, getindex, convert, prod, *, /, \, +, -, ==,
44
IndexStyle, IndexLinear, ==, OneTo, tail, similar, copyto!, copy, diff,
5-
first, last, show, isempty, findfirst, findlast, findall, Slice, union, minimum, maximum, sum, _sum
5+
first, last, show, isempty, findfirst, findlast, findall, Slice, union, minimum, maximum, sum, _sum,
6+
getproperty
67
import Base.Broadcast: materialize, BroadcastStyle, broadcasted
78
import LazyArrays: MemoryLayout, Applied, ApplyStyle, flatten, _flatten, colsupport,
89
adjointlayout, LdivApplyStyle, arguments, _arguments, call, broadcastlayout, lazy_getindex,
@@ -16,7 +17,7 @@ import QuasiArrays: cardinality, checkindex, QuasiAdjoint, QuasiTranspose, Inclu
1617
ApplyQuasiArray, ApplyQuasiMatrix, LazyQuasiArrayApplyStyle, AbstractQuasiArrayApplyStyle,
1718
LazyQuasiArray, LazyQuasiVector, LazyQuasiMatrix, LazyLayout, LazyQuasiArrayStyle, quasildivapplystyle
1819

19-
export Spline, LinearSpline, HeavisideSpline, DiracDelta, Derivative, fullmaterialize, ℵ₁, Inclusion, Basis, WeightedBasis, grid, transform
20+
export Spline, LinearSpline, HeavisideSpline, DiracDelta, Derivative, fullmaterialize, ℵ₁, Inclusion, Basis, WeightedBasis, grid, transform, affine
2021

2122
####
2223
# Interval indexing support
@@ -70,7 +71,9 @@ BroadcastStyle(::Type{<:QuasiTranspose{<:Any,<:Inclusion{<:Any,<:AbstractInterva
7071
###
7172

7273
# Affine map represents A*x .+ b
73-
struct AffineQuasiVector{T,AA,X,B} <: AbstractQuasiVector{T}
74+
abstract type AbstractAffineQuasiVector{T,AA,X,B} <: AbstractQuasiVector{T} end
75+
76+
struct AffineQuasiVector{T,AA,X,B} <: AbstractAffineQuasiVector{T,AA,X,B}
7477
A::AA
7578
x::X
7679
b::B
@@ -84,22 +87,23 @@ AffineQuasiVector(x) = AffineQuasiVector(one(eltype(x)), x)
8487

8588
AffineQuasiVector(A, x::AffineQuasiVector, b) = AffineQuasiVector(A*x.A, x.x, A*x.b .+ b)
8689

87-
axes(A::AffineQuasiVector) = axes(A.x)
88-
getindex(A::AffineQuasiVector, k::Number) = A.A*A.x[k] .+ A.b
89-
function getindex(A::AffineQuasiVector, k::Inclusion)
90+
axes(A::AbstractAffineQuasiVector) = axes(A.x)
91+
affine_getindex(A, k) = A.A*A.x[k] .+ A.b
92+
getindex(A::AbstractAffineQuasiVector, k::Number) = affine_getindex(A, k)
93+
function getindex(A::AbstractAffineQuasiVector, k::Inclusion)
9094
@boundscheck A.x[k] # throws bounds error if k ≠ x
9195
A
9296
end
9397

94-
getindex(A::AffineQuasiVector, ::Colon) = copy(A)
98+
getindex(A::AbstractAffineQuasiVector, ::Colon) = copy(A)
9599

96-
copy(A::AffineQuasiVector) = A
100+
copy(A::AbstractAffineQuasiVector) = A
97101

98-
inbounds_getindex(A::AffineQuasiVector{<:Any,<:Any,<:Inclusion}, k::Number) = A.A*k .+ A.b
99-
isempty(A::AffineQuasiVector) = isempty(A.x)
100-
==(a::AffineQuasiVector, b::AffineQuasiVector) = a.A == b.A && a.x == b.x && a.b == b.b
102+
inbounds_getindex(A::AbstractAffineQuasiVector{<:Any,<:Any,<:Inclusion}, k::Number) = A.A*k .+ A.b
103+
isempty(A::AbstractAffineQuasiVector) = isempty(A.x)
104+
==(a::AbstractAffineQuasiVector, b::AbstractAffineQuasiVector) = a.A == b.A && a.x == b.x && a.b == b.b
101105

102-
BroadcastStyle(::Type{<:AffineQuasiVector}) = LazyQuasiArrayStyle{1}()
106+
BroadcastStyle(::Type{<:AbstractAffineQuasiVector}) = LazyQuasiArrayStyle{1}()
103107

104108
for op in(:*, :\, :+, :-)
105109
@eval broadcasted(::LazyQuasiArrayStyle{1}, ::typeof($op), a::Number, x::Inclusion) = broadcast($op, a, AffineQuasiVector(x))
@@ -108,31 +112,74 @@ for op in(:/, :+, :-)
108112
@eval broadcasted(::LazyQuasiArrayStyle{1}, ::typeof($op), x::Inclusion, a::Number) = broadcast($op, AffineQuasiVector(x), a)
109113
end
110114

111-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(*), a::Number, x::AffineQuasiVector) = AffineQuasiVector(a, x)
112-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(\), a::Number, x::AffineQuasiVector) = AffineQuasiVector(inv(a), x)
113-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(/), x::AffineQuasiVector, a::Number) = AffineQuasiVector(inv(a), x)
114-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(+), a::Number, x::AffineQuasiVector) = AffineQuasiVector(one(eltype(x)), x, a)
115-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(+), x::AffineQuasiVector, b::Number) = AffineQuasiVector(one(eltype(x)), x, b)
116-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(-), a::Number, x::AffineQuasiVector) = AffineQuasiVector(-one(eltype(x)), x, a)
117-
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(-), x::AffineQuasiVector, b::Number) = AffineQuasiVector(one(eltype(x)), x, -b)
115+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(*), a::Number, x::AbstractAffineQuasiVector) = AffineQuasiVector(a, x)
116+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(\), a::Number, x::AbstractAffineQuasiVector) = AffineQuasiVector(inv(a), x)
117+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(/), x::AbstractAffineQuasiVector, a::Number) = AffineQuasiVector(inv(a), x)
118+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(+), a::Number, x::AbstractAffineQuasiVector) = AffineQuasiVector(one(eltype(x)), x, a)
119+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(+), x::AbstractAffineQuasiVector, b::Number) = AffineQuasiVector(one(eltype(x)), x, b)
120+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(-), a::Number, x::AbstractAffineQuasiVector) = AffineQuasiVector(-one(eltype(x)), x, a)
121+
broadcasted(::LazyQuasiArrayStyle{1}, ::typeof(-), x::AbstractAffineQuasiVector, b::Number) = AffineQuasiVector(one(eltype(x)), x, -b)
118122

119-
function checkindex(::Type{Bool}, inds::Inclusion{<:Any,<:AbstractInterval}, r::AffineQuasiVector{<:Any,<:Any,<:Inclusion{<:Any,<:AbstractInterval}})
123+
function checkindex(::Type{Bool}, inds::Inclusion{<:Any,<:AbstractInterval}, r::AbstractAffineQuasiVector)
120124
@_propagate_inbounds_meta
121125
isempty(r) | (checkindex(Bool, inds, first(r)) & checkindex(Bool, inds, last(r)))
122126
end
123127

124-
igetindex(d::AffineQuasiVector, x) = d.A \ (x .- d.b)
128+
affine_igetindex(d, x) = d.A \ (x .- d.b)
129+
igetindex(d::AbstractAffineQuasiVector, x) = affine_igetindex(d, x)
125130

126131
for find in (:findfirst, :findlast, :findall)
127-
@eval $find(f::Base.Fix2{typeof(isequal)}, d::AffineQuasiVector) = $find(isequal(igetindex(d, f.x)), d.x)
132+
@eval $find(f::Base.Fix2{typeof(isequal)}, d::AbstractAffineQuasiVector) = $find(isequal(igetindex(d, f.x)), d.x)
133+
end
134+
135+
minimum(d::AbstractAffineQuasiVector) = signbit(d.A) ? last(d) : first(d)
136+
maximum(d::AbstractAffineQuasiVector) = signbit(d.A) ? first(d) : last(d)
137+
138+
union(d::AbstractAffineQuasiVector) = Inclusion(minimum(d)..maximum(d))
139+
140+
141+
struct AffineMap{T,D,R} <: AbstractAffineQuasiVector{T,T,D,T}
142+
domain::D
143+
range::R
144+
end
145+
146+
AffineMap(domain::AbstractQuasiVector{T}, range::AbstractQuasiVector{V}) where {T,V} =
147+
AffineMap{promote_type(T,V), typeof(domain),typeof(range)}(domain,range)
148+
149+
measure(x::Inclusion) = last(x)-first(x)
150+
151+
function getproperty(A::AffineMap, d::Symbol)
152+
d == :x && return A.domain
153+
d == :A && return measure(A.range)/measure(A.domain)
154+
d == :b && return (last(A.domain)*first(A.range) - first(A.domain)*last(A.range))/measure(A.domain)
155+
getfield(A, d)
156+
end
157+
158+
function getindex(A::AffineMap, k::Number)
159+
# ensure we exactly hit range
160+
k == first(A.domain) && return first(A.range)
161+
k == last(A.domain) && return last(A.range)
162+
affine_getindex(A, k)
128163
end
129164

130-
minimum(d::AffineQuasiVector{<:Real,<:Real,<:Inclusion}) = signbit(d.A) ? last(d) : first(d)
131-
maximum(d::AffineQuasiVector{<:Real,<:Real,<:Inclusion}) = signbit(d.A) ? first(d) : last(d)
165+
function igetindex(A::AffineMap, k::Number)
166+
# ensure we exactly hit range
167+
k == first(A.range) && return first(A.domain)
168+
k == last(A.range) && return last(A.domain)
169+
affine_igetindex(A, k)
170+
end
171+
172+
first(A::AffineMap) = first(A.range)
173+
last(A::AffineMap) = last(A.range)
174+
175+
affine(a::AbstractQuasiVector, b::AbstractQuasiVector) = AffineMap(a, b)
176+
affine(a, b::AbstractQuasiVector) = affine(Inclusion(a), b)
177+
affine(a::AbstractQuasiVector, b) = affine(a, Inclusion(b))
178+
affine(a, b) = affine(Inclusion(a), Inclusion(b))
179+
132180

133-
union(d::AffineQuasiVector{<:Real,<:Real,<:Inclusion}) = Inclusion(minimum(d)..maximum(d))
134181

135-
const QInfAxes = Union{Inclusion,AffineQuasiVector}
182+
const QInfAxes = Union{Inclusion,AbstractAffineQuasiVector}
136183

137184
sub_materialize(_, V::AbstractQuasiArray, ::Tuple{QInfAxes}) = V
138185
sub_materialize(_, V::AbstractQuasiArray, ::Tuple{QInfAxes,QInfAxes}) = V

src/bases/bases.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function copy(M::QMul2{<:Derivative,<:SubQuasiArray{<:Any,2,<:AbstractQuasiMatri
133133
(Derivative(axes(P,1))*P)[parentindices(B)...]
134134
end
135135

136-
function copy(M::QMul2{<:Derivative,<:SubQuasiArray{<:Any,2,<:AbstractQuasiMatrix,<:Tuple{<:AffineQuasiVector,<:Any}}})
136+
function copy(M::QMul2{<:Derivative,<:SubQuasiArray{<:Any,2,<:AbstractQuasiMatrix,<:Tuple{<:AbstractAffineQuasiVector,<:Any}}})
137137
A, B = M.args
138138
P = parent(B)
139139
kr,jr = parentindices(B)
@@ -155,7 +155,7 @@ end
155155

156156
# we represent as a Mul with a banded matrix
157157
sublayout(::AbstractBasisLayout, ::Type{<:Tuple{<:Inclusion,<:AbstractUnitRange}}) = SubBasisLayout()
158-
sublayout(::BasisLayout, ::Type{<:Tuple{<:AffineQuasiVector,<:AbstractUnitRange}}) = MappedBasisLayout()
158+
sublayout(::BasisLayout, ::Type{<:Tuple{<:AbstractAffineQuasiVector,<:AbstractUnitRange}}) = MappedBasisLayout()
159159

160160
@inline sub_materialize(::AbstractBasisLayout, V::AbstractQuasiArray) = V
161161
@inline sub_materialize(::AbstractBasisLayout, V::AbstractArray) = V
@@ -208,7 +208,7 @@ end
208208

209209
function __sum(::MappedBasisLayout, V::AbstractQuasiArray, dims)
210210
kr, jr = parentindices(V)
211-
@assert kr isa AffineQuasiVector
211+
@assert kr isa AbstractAffineQuasiVector
212212
sum(demap(V); dims=dims)/kr.A
213213
end
214214

test/runtests.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using ContinuumArrays, QuasiArrays, LazyArrays, IntervalSets, FillArrays, LinearAlgebra, BandedMatrices, Test
2-
import ContinuumArrays: ℵ₁, materialize, SimplifyStyle, AffineQuasiVector, BasisLayout, AdjointBasisLayout, SubBasisLayout, MappedBasisLayout
2+
import ContinuumArrays: ℵ₁, materialize, SimplifyStyle, AffineQuasiVector, BasisLayout, AdjointBasisLayout, SubBasisLayout, MappedBasisLayout, igetindex
33
import QuasiArrays: SubQuasiArray, MulQuasiMatrix, Vec, Inclusion, QuasiDiagonal, LazyQuasiArrayApplyStyle, LazyQuasiArrayStyle
44
import LazyArrays: MemoryLayout, ApplyStyle, Applied, colsupport, arguments, ApplyLayout, LdivApplyStyle
55

@@ -328,6 +328,18 @@ end
328328

329329
@test !isempty(z)
330330
@test z == z
331+
332+
@testset "AffineMap" begin
333+
a = affine(-2..3, -1..1)
334+
@test a[0.1] == -0.16
335+
@test_throws BoundsError a[-3]
336+
@test a[-2] == first(a) == -1
337+
@test a[3] == last(a) == 1
338+
@test igetindex(a,-0.16) 0.1
339+
@test igetindex(a,1) == 3
340+
@test igetindex(a,-1) == -2
341+
@test union(a) == Inclusion(-1..1)
342+
end
331343
end
332344

333345
@testset "Change-of-variables" begin

0 commit comments

Comments
 (0)