Skip to content

Commit 3284de8

Browse files
committed
type promotion for composite maps
1 parent 3e8dcd4 commit 3284de8

File tree

13 files changed

+253
-99
lines changed

13 files changed

+253
-99
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "FunctionMaps"
22
uuid = "a85aefff-f8ca-4649-a888-c8e5398bc76c"
33
authors = ["Daan Huybrechs <[email protected]> and contributors"]
4-
version = "0.1.1"
4+
version = "0.1.2"
55

66
[deps]
77
CompositeTypes = "b152e2b5-7a66-4b01-a709-34e65c35f657"

src/FunctionMaps.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import CompositeTypes: component, components
1212

1313
# Exhaustive list of exports:
1414

15+
# from CompositeTypes
16+
export component, components, ncomponents
17+
1518
# from util/common.jl
1619
export prectype, numtype
1720

src/concrete/coordinates.jl

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# This file contains a few specific maps, included mainly to test
32
# functionality on non-trivial maps
43

@@ -12,6 +11,9 @@ end
1211

1312
CartToPolarMap() = CartToPolarMap{Float64}()
1413

14+
similarmap(::CartToPolarMap, ::Type{SVector{2,T}}) where {T<:Number} = CartToPolarMap{T}()
15+
isequalmap(m1::CartToPolarMap, m2::CartToPolarMap) = true
16+
1517
mapsize(m::CartToPolarMap) = (2,2)
1618

1719
applymap(map::CartToPolarMap{T}, x) where {T} =
@@ -28,10 +30,6 @@ inverse(m::CartToPolarMap, x) = inverse(m)(x)
2830

2931
isrealmap(m::CartToPolarMap) = true
3032

31-
convert(::Type{Map{SVector{2,T}}}, ::CartToPolarMap) where {T} = CartToPolarMap{T}()
32-
33-
isequalmap(m1::CartToPolarMap, m2::CartToPolarMap) = true
34-
3533

3634
"""
3735
A Polar to Cartesian map. The angle is mapped to the second dimension,
@@ -43,6 +41,9 @@ end
4341

4442
PolarToCartMap() = PolarToCartMap{Float64}()
4543

44+
similarmap(::PolarToCartMap, ::Type{SVector{2,T}}) where {T<:Number} = PolarToCartMap{T}()
45+
isequalmap(m1::PolarToCartMap, m2::PolarToCartMap) = true
46+
4647
mapsize(m::PolarToCartMap) = (2,2)
4748

4849
applymap(map::PolarToCartMap{T}, x) where {T} = SVector{2,T}((x[1]+1)/2*cos(pi*x[2]), (x[1]+1)/2*sin(pi*x[2]))
@@ -55,10 +56,6 @@ inverse(m::PolarToCartMap, x) = inverse(m)(x)
5556

5657
isrealmap(m::PolarToCartMap) = true
5758

58-
convert(::Type{Map{SVector{2,T}}}, ::PolarToCartMap) where {T} = PolarToCartMap{T}()
59-
60-
isequalmap(m1::PolarToCartMap, m2::PolarToCartMap) = true
61-
6259

6360
"""
6461
The map `[cos(2πt), sin(2πt)]` from `[0,1]` to the unit circle in `ℝ^2`.
@@ -67,6 +64,9 @@ struct UnitCircleMap{T} <: Map{T} end
6764

6865
UnitCircleMap() = UnitCircleMap{Float64}()
6966

67+
similarmap(::UnitCircleMap, ::Type{T}) where {T<:Number} = UnitCircleMap{T}()
68+
isequalmap(::UnitCircleMap, ::UnitCircleMap) = true
69+
7070
mapsize(m::UnitCircleMap) = (2,)
7171

7272
applymap(m::UnitCircleMap{T}, t) where {T} = SVector(cos(2*T(pi)*t), sin(2*T(pi)*t))
@@ -89,6 +89,9 @@ end
8989

9090
AngleMap() = AngleMap{Float64}()
9191

92+
similarmap(::AngleMap, ::Type{SVector{2,T}}) where {T<:Number} = AngleMap{T}()
93+
isequalmap(::AngleMap, ::AngleMap) = true
94+
9295
function applymap(m::AngleMap{T}, x) where {T}
9396
twopi = 2*convert(T, pi)
9497
θ = atan(x[2],x[1])
@@ -124,6 +127,9 @@ struct UnitDiskMap{T} <: Map{SVector{2,T}} end
124127

125128
UnitDiskMap() = UnitDiskMap{Float64}()
126129

130+
similarmap(::UnitDiskMap, ::Type{SVector{2,T}}) where {T<:Number} = UnitDiskMap{T}()
131+
isequalmap(::UnitDiskMap, ::UnitDiskMap) = true
132+
127133
mapsize(m::UnitDiskMap) = (2,2)
128134

129135
applymap(m::UnitDiskMap{T}, x) where {T} =

src/generic/composite.jl

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
1+
# ensure that the domaintype of each map in the sequence agrees with the codomain type of the
2+
# preceding map
3+
function match_domain_codomain_types(::Type{T}, map, maps...) where {T}
4+
Y = promote_type(T, domaintype(map))
5+
m1 = convert_domaintype(Y, map)
6+
(m1, match_domain_codomain_types(codomaintype(m1), maps...)...)
7+
end
8+
9+
match_domain_codomain_types(::Type{T}, map) where {T} = (convert_domaintype(T, map),)
10+
11+
"""
12+
ComposedMap{T,MAPS}
113
2-
"The composition of several maps."
14+
The composition of several maps.
15+
16+
The `components` of a `ComposedMap` are the maps in the order that they are applied
17+
to the input.
18+
"""
319
struct ComposedMap{T,MAPS} <: CompositeLazyMap{T}
420
maps :: MAPS
521
end
622

7-
ComposedMap(maps...) = ComposedMap{domaintype(maps[1])}(maps...)
8-
ComposedMap{T}(maps...) where {T} = ComposedMap{T,typeof(maps)}(maps)
23+
function ComposedMap(map1, maps...)
24+
P = prectype(map1, maps...)
25+
if P == Any
26+
# don't try to promote types
27+
_ComposedMap(domaintype(map1), map1, maps...)
28+
else
29+
T = to_prectype(P, domaintype(map1))
30+
_ComposedMap(T, match_domain_codomain_types(T, map1, maps...)...)
31+
end
32+
end
33+
ComposedMap{T}(map1, maps...) where {T} = _ComposedMap(T, match_domain_codomain_types(T, map1, maps...)...)
34+
_ComposedMap(::Type{T}, maps...) where {T} = ComposedMap{T,typeof(maps)}(maps)
935

10-
# TODO: make proper conversion
11-
similarmap(m::ComposedMap, ::Type{T}) where {T} = ComposedMap{T}(m.maps...)
36+
similarmap(m::ComposedMap, ::Type{T}) where {T} = ComposedMap{T}(components(m)...)
1237

13-
codomaintype(m::ComposedMap) = codomaintype(m.maps[end])
38+
codomaintype(m::ComposedMap) = codomaintype(last(m.maps))
1439

1540
# Maps are applied in the order that they appear in m.maps
1641
applymap(m::ComposedMap, x) = applymap_rec(x, m.maps...)
@@ -19,7 +44,7 @@ applymap_rec(x, map1, maps...) = applymap_rec(applymap(map1, x), maps...)
1944

2045
# The size of a composite map depends on the first and the last map to be applied
2146
# We check whether they are scalar_to_vector, vector_to_vector, etcetera
22-
mapsize(m::ComposedMap) = _composed_mapsize(m, m.maps[end], m.maps[1], mapsize(m.maps[end]), mapsize(m.maps[1]))
47+
mapsize(m::ComposedMap) = _composed_mapsize(m, last(m.maps), first(m.maps), mapsize(last(m.maps)), mapsize(first(m.maps)))
2348
_composed_mapsize(m, m_end, m1, S_end::Tuple{Int,Int}, S1::Tuple{Int,Int}) = (S_end[1],S1[2])
2449
_composed_mapsize(m, m_end, m1, S_end::Tuple{Int,Int}, S1::Tuple{Int}) =
2550
is_vector_to_scalar(m_end) ? () : (S_end[1],)
@@ -109,9 +134,10 @@ struct MulMap{T,MAPS} <: CompositeLazyMap{T}
109134
maps :: MAPS
110135
end
111136

137+
MulMap(map1::Map, maps::Map...) = MulMap(promote_maps(map1, maps...)...)
112138
MulMap(map1::Map{T}, maps::Map{T}...) where {T} = MulMap{T}(map1, maps...)
113139
MulMap{T}(maps::Map{T}...) where {T} = MulMap{T,typeof(maps)}(maps)
114-
MulMap{T}(maps...) where {T} = _mulmap(T, convert.(Map{T}, maps)...)
140+
MulMap{T}(maps...) where {T} = _mulmap(T, convert_domaintype.(Ref(T), maps)...)
115141
_mulmap(::Type{T}, maps...) where {T} = MulMap{T,typeof(maps)}(maps)
116142

117143
similarmap(m::MulMap, ::Type{T}) where {T} = MulMap{T}(m.maps...)
@@ -155,9 +181,10 @@ struct SumMap{T,MAPS} <: CompositeLazyMap{T}
155181
maps :: MAPS
156182
end
157183

184+
SumMap(map1::Map, maps::Map...) = SumMap(promote_maps(map1, maps...)...)
158185
SumMap(map1::Map{T}, maps::Map{T}...) where {T} = SumMap{T}(map1, maps...)
159186
SumMap{T}(maps::Map{T}...) where {T} = SumMap{T,typeof(maps)}(maps)
160-
SumMap{T}(maps...) where {T} = _summap(T, convert.(Map{T}, maps)...)
187+
SumMap{T}(maps...) where {T} = _summap(T, convert_domaintype.(Ref(T), maps)...)
161188
_summap(::Type{T}, maps...) where {T} = SumMap{T,typeof(maps)}(maps)
162189

163190
similarmap(m::SumMap, ::Type{T}) where {T} = SumMap{T}(m.maps...)

src/generic/isomorphism.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ See also: [`VectorToNumber`](@ref).
3434
"""
3535
NumberToVector() = NumberToVector{Float64}()
3636

37+
similarmap(::NumberToVector, ::Type{T}) where {T<:Number} = NumberToVector{T}()
38+
similarmap(::VectorToNumber, ::Type{SVector{1,T}}) where {T<:Number} = VectorToNumber{T}()
39+
isequalmap(::NumberToVector, ::NumberToVector) = true
40+
isequalmap(::VectorToNumber, ::VectorToNumber) = true
41+
3742
mapsize(::VectorToNumber) = (1,1)
3843
mapsize(::NumberToVector) = (1,)
3944

@@ -77,6 +82,11 @@ See also: [`VectorToComplex`](@ref).
7782
"""
7883
ComplexToVector() = ComplexToVector{Float64}()
7984

85+
similarmap(::ComplexToVector, ::Type{Complex{T}}) where {T<:Number} = ComplexToVector{T}()
86+
similarmap(::VectorToComplex, ::Type{SVector{2,T}}) where {T<:Number} = VectorToComplex{T}()
87+
isequalmap(::ComplexToVector, ::ComplexToVector) = true
88+
isequalmap(::VectorToComplex, ::VectorToComplex) = true
89+
8090
mapsize(::VectorToComplex) = (1,2)
8191
mapsize(::ComplexToVector) = (2,)
8292

@@ -115,6 +125,12 @@ end
115125
struct FlatToNested{N,T,U,DIM} <: Isomorphism{SVector{N,T},U}
116126
end
117127

128+
# TODO: not all type combinations make sense
129+
similarmap(::NestedToFlat{N,T,U,DIM}, ::Type{V}) where {N,T,U,DIM,V} = NestedToFlat{N,T,V,DIM}()
130+
similarmap(::FlatToNested{N,T,U,DIM}, ::Type{SVector{N,V}}) where {N,T,U,DIM,V<:Number} = FlatToNested{N,V,U,DIM}()
131+
isequalmap(::NestedToFlat{N,T,U,DIM}, ::NestedToFlat{N,S,V,DIM}) where {N,T,U,DIM,S,V} = true
132+
isequalmap(::FlatToNested{N,T,U,DIM}, ::FlatToNested{N,S,V,DIM}) where {N,T,U,DIM,S,V} = true
133+
118134
inverse(::NestedToFlat{N,T,U,DIM}) where {N,T,U,DIM} = FlatToNested{N,T,U,DIM}()
119135
inverse(::FlatToNested{N,T,U,DIM}) where {N,T,U,DIM} = NestedToFlat{N,T,U,DIM}()
120136
inverse(m::NestedToFlat, x) = inverse(m)(x)

src/generic/jacobian.jl

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
21
"A lazy Jacobian `J` stores a map `m` and returns `J(x) = jacobian(m, x)`."
32
struct LazyJacobian{T,M} <: SimpleLazyMap{T}
43
map :: M
54
end
65

7-
LazyJacobian(m) = LazyJacobian{domaintype(m),typeof(m)}(m)
6+
LazyJacobian(m) = LazyJacobian{domaintype(m)}(m)
7+
LazyJacobian{T}(m) where T = _LazyJacobian(T, convert_domaintype(T, m))
8+
_LazyJacobian(::Type{T}, m) where T = LazyJacobian{T,typeof(m)}(m)
9+
810
applymap(m::LazyJacobian, x) = jacobian(supermap(m), x)
911

1012
mapsize(m::LazyJacobian) = mapsize(supermap(m))
@@ -29,9 +31,8 @@ struct DeterminantMap{T,M} <: SimpleLazyMap{T}
2931
end
3032

3133
DeterminantMap(m) = DeterminantMap{domaintype(m)}(m)
32-
DeterminantMap{T}(m) where {T} = DeterminantMap{T,typeof(m)}(m)
33-
DeterminantMap{T}(m::Map{T}) where {T} = DeterminantMap{T,typeof(m)}(m)
34-
DeterminantMap{T}(m::Map{S}) where {S,T} = DeterminantMap{T}(convert(Map{T}, m))
34+
DeterminantMap{T}(m) where T = _DeterminantMap(T, convert_domaintype(T, m))
35+
_DeterminantMap(::Type{T}, m) where T = DeterminantMap{T,typeof(m)}(m)
3536

3637
determinantmap(m) = DeterminantMap(m)
3738

@@ -56,9 +57,8 @@ struct AbsMap{T,M} <: SimpleLazyMap{T}
5657
end
5758

5859
AbsMap(m) = AbsMap{domaintype(m)}(m)
59-
AbsMap{T}(m) where {T} = AbsMap{T,typeof(m)}(m)
60-
AbsMap{T}(m::Map{T}) where {T} = AbsMap{T,typeof(m)}(m)
61-
AbsMap{T}(m::Map{S}) where {S,T} = AbsMap{T}(convert(Map{T}, m))
60+
AbsMap{T}(m) where T = _AbsMap(T, convert_domaintype(T, m))
61+
_AbsMap(::Type{T}, m) where T = AbsMap{T,typeof(m)}(m)
6262

6363
absmap(m) = AbsMap(m)
6464

@@ -73,9 +73,8 @@ struct LazyDiffVolume{T,M} <: SimpleLazyMap{T}
7373
end
7474

7575
LazyDiffVolume(m) = LazyDiffVolume{domaintype(m)}(m)
76-
LazyDiffVolume{T}(m) where {T} = LazyDiffVolume{T,typeof(m)}(m)
77-
LazyDiffVolume{T}(m::Map{T}) where {T} = LazyDiffVolume{T,typeof(m)}(m)
78-
LazyDiffVolume{T}(m::Map{S}) where {S,T} = LazyDiffVolume{T}(convert(Map{T}, m))
76+
LazyDiffVolume{T}(m) where T = _LazyDiffVolume(T, convert_domaintype(T, m))
77+
_LazyDiffVolume(::Type{T}, m) where T = LazyDiffVolume{T,typeof(m)}(m)
7978

8079
applymap(m::LazyDiffVolume, x) = diffvolume(supermap(m), x)
8180

src/generic/map.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,28 @@ isvectorvalued_type(::Type{T}) where {T} = false
159159
isvectorvalued(m) =
160160
isvectorvalued_type(domaintype(m)) && isvectorvalued_type(codomaintype(m))
161161

162+
"""
163+
mapsize(m[, i])
164+
165+
The size of a vectorvalued map is the size of its jacobian matrix.
166+
167+
A map with size (3,5) maps vectors of length 5 to vectors of length 3. Its jacobian matrix
168+
is a 3-by-5 matrix.
169+
170+
Optionally, similar to the `size` function, a second argument `i` can be provided. Choose `i`
171+
to be `1` or `2` to return only the corresponding element of the size. The dimension of the
172+
domain type is `mapsize(m, 2)`, while that of the codomain type is `mapsize(m, 1)`.
173+
174+
If a map is scalar valued, its `mapsize` equals `()`. This reflects the convention in Julia that
175+
the size of a number is an empty tuple: `size(5) == ()`. If a map is scalar-to-vector, its `mapsize`
176+
is the tuple `(m,)` of length one. In this case the jacobian is a vector with size `(m,)`. If a map
177+
is vector-to-scalar, its `mapsize` is `(1,n)`.
178+
179+
Thus, in all cases of scalarvalued and vectorvalued maps, the `mapsize` of a map agrees with the
180+
size of the Jacobian matrix.
181+
"""
182+
function mapsize end
183+
162184
# mapsize should be defined for vector valued maps
163185
# The size of a map equals the size of its jacobian
164186
# The jacobian can be a number, a vector, an adjoint vector, or a matrix

src/generic/product.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,6 @@ function VcatMap{T}(maps...) where T
114114
VcatMap{T,M,N}(maps...)
115115
end
116116

117-
mapdim(map) = mapsize(map,2)
118-
119117
VcatMap{T,M,N}(maps::Union{Tuple,Vector}) where {T,M,N} = VcatMap{T,M,N}(maps...)
120118
function VcatMap{T,M,N}(maps...) where {T,M,N}
121119
DIM1 = map(t->mapsize(t,1), maps)

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ include("test_basic.jl")
1515
include("test_affine.jl")
1616
include("test_canonical.jl")
1717
include("test_product.jl")
18+
include("test_composite.jl")
19+
1820
include("test_maps.jl")
1921
include("test_arithmetics.jl")

test/test_composite.jl

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
function test_composite_maps()
2+
T = Float64
3+
a = T(0)
4+
b = T(1)
5+
c = T(2)
6+
d = T(3)
7+
ma = IdentityMap{T}()
8+
mb = interval_map(a, b, c, d)
9+
10+
r = suitable_point_to_map(ma)
11+
m1 = ComposedMap(mb, ma)
12+
test_generic_map(m1)
13+
@test m1(r) ma(mb(r))
14+
m2 = ComposedMap(mb, m1)
15+
test_generic_map(m2)
16+
17+
@test ncomponents(composedmap(m1,m1)) == 4
18+
19+
m5 = ComposedMap(LinearMap(rand(T,2,2)), AffineMap(rand(T,2,2),rand(T,2)))
20+
test_generic_map(m5)
21+
@test jacobian(m5) isa ConstantMap
22+
@test m5[Component(1)] isa LinearMap
23+
@test m5[Component(2)] isa AffineMap
24+
@test ComposedMap(m5[Component(1:2)]...) == m5
25+
@test_throws BoundsError m5[Component(3)]
26+
27+
m6 = multiply_map(ma,ma)
28+
@test m6(one(T)/2) == one(T)/4
29+
@test jacobian(m6) isa SumMap
30+
@test jacobian(m6)(one(T)) == 2
31+
@test jacobian(m6, one(T)) == 2
32+
33+
m7 = sum_map(ma,ma)
34+
@test m7(one(T)) == 2
35+
@test jacobian(m7) isa ConstantMap
36+
@test jacobian(m7, one(T)) == 2
37+
@test sum_map() == ()
38+
@test sum_map(ma) == ma
39+
@test sum_map(ma, mb, ma) isa SumMap{T,Tuple{X,Y,Z}} where X where Y where Z
40+
@test sum_map(SumMap(ma, mb), ma) isa SumMap{T,Tuple{X,Y,Z}} where X where Y where Z
41+
@test sum_map(ma, SumMap(ma, mb)) isa SumMap{T,Tuple{X,Y,Z}} where X where Y where Z
42+
@test sum_map(SumMap(ma, mb), SumMap(ma,mb)) isa SumMap{T,Tuple{W,X,Y,Z}} where W where X where Y where Z
43+
@test FunctionMaps.SumMap{Float64}(LinearMap(2), LinearMap(2.0)) isa FunctionMaps.SumMap{Float64}
44+
@test allequal(map(domaintype,components(FunctionMaps.SumMap{Float64}(LinearMap(2), LinearMap(2.0)))))
45+
46+
@test mapsize(ComposedMap(LinearMap(2),LinearMap(rand(T,2)),LinearMap(rand(T,2,2)))) == (2,)
47+
@test mapsize(ComposedMap(LinearMap(2),LinearMap(rand(T,2)))) == (2,)
48+
@test mapsize(ComposedMap(LinearMap(rand(T,2)),LinearMap(rand(T,2)'),LinearMap(rand(T,2)))) == (2,)
49+
@test mapsize(ComposedMap(LinearMap(rand(T,2,2)),LinearMap(rand(T,2)'),LinearMap(2))) == (1,2)
50+
@test mapsize(ComposedMap(LinearMap(one(T)),LinearMap(one(T)))) == ()
51+
52+
@test composedmap() == ()
53+
@test composedmap(ma) == ma
54+
@test composedmap(ma,ma) == ma
55+
@test composedmap(ma,ma,ma) == ma
56+
57+
@test composite_jacobian(ma) == jacobian(ma)
58+
59+
@test multiply_map() == ()
60+
@test multiply_map(ma) == ma
61+
@test multiply_map(ma,ma)(2*one(T)) == 4
62+
@test multiply_map(ma,ma,ma)(2*one(T)) == 8
63+
@test FunctionMaps.mul_jacobian() == ()
64+
@test FunctionMaps.mul_jacobian(ma) == jacobian(ma)
65+
@test jacobian(multiply_map(ma, ma, ma)) isa FunctionMaps.SumMap
66+
67+
@test ncomponents(multiply_map(multiply_map(ma,ma),multiply_map(ma,ma))) == 4
68+
@test multiply_map(LinearMap(2), LinearMap(2.0)) isa FunctionMaps.MulMap{Float64}
69+
@test FunctionMaps.MulMap{Float64}(LinearMap(2), LinearMap(2.0)) isa FunctionMaps.MulMap{Float64}
70+
@test allequal(map(domaintype,components(FunctionMaps.MulMap{Float64}(LinearMap(2), LinearMap(2.0)))))
71+
72+
@test sum_jacobian() == ()
73+
@test sum_jacobian(ma) == jacobian(ma)
74+
75+
@test convert_domaintype(Float64, ComposedMap(LinearMap(2), LinearMap(2))) isa Map{Float64}
76+
@test allequal(map(domaintype,components(convert_domaintype(Float64, ComposedMap(LinearMap(2), LinearMap(2))))))
77+
end

0 commit comments

Comments
 (0)