-
Notifications
You must be signed in to change notification settings - Fork 99
/
Maps.jl
298 lines (233 loc) · 6.74 KB
/
Maps.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
"""
Abstract type representing a function (mapping) that provides a cache and an in-place
evaluation for performance. This is the type to be used in the [`lazy_map`](@ref) function.
Derived types must implement the following method:
- [`evaluate!(cache,k,x...)`](@ref)
and optionally these ones:
- [`return_cache(k,x...)`](@ref)
- [`return_type(k,x...)`](@ref)
The mapping interface can be tested with the [`test_map`](@ref) function.
Note that most of the functionality implemented in terms of this interface
relies in duck typing. That is, it is not strictly needed to work with types
that inherit from `Map`. This is specially useful in order to accommodate
existing types into this framework without the need to implement a wrapper type
that inherits from `Map`. For instance, a default implementation is available
for `Function` objects. However, we recommend that new types inherit from `Map`.
"""
abstract type Map <: GridapType end
"""
return_cache(f,x...)
Returns the `cache` needed to lazy_map mapping `f` with arguments
of the same type as the objects in `x`.
This function returns `nothing` by default, i.e., no cache.
"""
return_cache(f,x...) = nothing
"""
evaluate!(cache,f,x...)
Applies the mapping `f` at the arguments `x...` using
the scratch data provided in the given `cache` object. The `cache` object
is built with the [`return_cache`](@ref) function using arguments of the same type as in `x`.
In general, the returned value `y` can share some part of its state with the `cache` object.
If the result of two or more calls to this function need to be accessed simultaneously
(e.g., in multi-threading), create and use several `cache` objects (e.g., one cache
per thread).
"""
@noinline function evaluate!(cache,f,x...)
@abstractmethod """
Method
Gridap.Arrays.evaluate!(cache,f,x...)
has not been defined for the requested types (see stack trace).
"""
end
"""
return_type(f,x...)
Returns the type of the result of calling mapping `f` with
arguments of the types of the objects `x`.
"""
return_type(f,x...) = typeof(return_value(f,x...))
return_value(f,x...) = evaluate(f,testargs(f,x...)...)
"""
testargs(f,x...)
The default implementation of this function is `testargs(f,x...) = x`.
One can overload it in order to use `lazy_map` with 0-length array
and maps with non-trivial domains.
"""
testargs(f,x...) = x
"""
evaluate(f,x...)
evaluates the mapping `f` at the arguments in `x` by creating a temporary cache
internally. This functions is equivalent to
```jl
cache = return_cache(f,x...)
evaluate!(cache,f,x...)
```
"""
function evaluate(f,x...)
c = return_cache(f,x...)
y = evaluate!(c,f,x...)
end
(m::Map)(x...) = evaluate(m,x...)
# Default implementation for Function
evaluate!(cache,f::Function,x...) = f(x...)
# Default implementation for constructors
evaluate!(cache,::Type{f},x...) where f = f(x...)
# Testing the interface
"""
test_map(y,f,x...;cmp=(==))
Function used to test if the mapping `f` has been
implemented correctly. `f` is a `Map` sub-type, `x` is a tuple in the domain of the
mapping and `y` is the expected result. Function `cmp` is used to compare
the computed result with the expected one. The checks are done with the `@test`
macro.
"""
function test_map(y,f,x...;cmp=(==))
z = evaluate(f,x...)
@test cmp(z,y)
@test typeof(z) == return_type(f,x...)
cache = return_cache(f,x...)
z = evaluate!(cache,f,x...)
@test cmp(z,y)
z = evaluate!(cache,f,x...)
@test cmp(z,y)
true
end
# Broadcast Functions
"""
Broadcasting(f)
Returns a mapping that represents the "broadcasted" version of the
function `f`.
# Example
```jldoctest
using Gridap.Arrays
a = [3,2]
b = [2,1]
bm = Broadcasting(+)
c = evaluate(bm,a,b)
println(c)
# output
[5, 3]
```
"""
struct Broadcasting{F} <: Map
f::F
end
return_cache(f::Broadcasting,x...) = nothing
evaluate!(cache,f::Broadcasting,x...) = broadcast(f.f,x...)
function return_value(f::Broadcasting,x...)
broadcast( (y...) -> f.f(testargs(f.f,y...)...), x... )
end
function evaluate!(cache,f::Broadcasting,x::Union{Number,AbstractArray{<:Number}}...)
r = _prepare_cache!(cache,x...)
a = r.array
broadcast!(f.f,a,x...)
a
end
function evaluate!(cache,f::Broadcasting,x::AbstractArray{<:Number})
setsize!(cache,size(x))
a = cache.array
@inbounds for i in eachindex(x)
a[i] = f.f(x[i])
end
a
end
function evaluate!(cache,f::Broadcasting,args::Number...)
f.f(args...)
end
function return_value(f::Broadcasting,x::Number...)
return_value(f.f,x...)
end
function return_cache(f::Broadcasting,x::Number...)
nothing
end
function return_value(f::Broadcasting,x::Union{Number,AbstractArray{<:Number}}...)
s = map(_size_zero,x)
bs = Base.Broadcast.broadcast_shape(s...)
T = return_type(f.f,map(testitem,x)...)
r = fill(testvalue(T),bs)
r
end
function _size_zero(a)
s = size(a)
if length(a) == 0
r = map(i-> (i==0 ? 1 : i) ,s)
else
r = s
end
r
end
_size_zero(a::Number) = (1,)
function return_cache(f::Broadcasting,x::Union{Number,AbstractArray{<:Number}}...)
s = map(_size,x)
bs = Base.Broadcast.broadcast_shape(s...)
T = return_type(f.f,map(testitem,x)...)
r = fill(testvalue(T),bs)
cache = CachedArray(r)
_prepare_cache!(cache,x...)
cache
end
function _prepare_cache!(c,x...)
s = map(_size,x)
bs = Base.Broadcast.broadcast_shape(s...)
if bs != size(c)
setsize!(c,bs)
end
c
end
_size(a) = size(a)
_size(a::Number) = (1,)
"""
OperationMap(f,args)
Returns a mapping that represents the result of applying the function `f`
to the arguments in the tuple `args`.
That is, `OperationMap(f,args)(x...)` is formally defined as
`f(map(a->a(x...),args)...)`
"""
struct OperationMap{K,L} <: Map
k::K
l::L
function OperationMap(k,l)
new{typeof(k),typeof(l)}(k,l)
end
end
function return_cache(c::OperationMap,x...)
cl = map(fi -> return_cache(fi,x...),c.l)
lx = map(fi -> return_value(fi,x...),c.l)
ck = return_cache(c.k,lx...)
ck, cl
end
function evaluate!(cache,c::OperationMap,x...)
ck, cf = cache
lx = map((ci,fi) -> evaluate!(ci,fi,x...),cf,c.l)
evaluate!(ck,c.k,lx...)
end
# Operations
"""
Operation(op)
Returns the map that results after applying an operation `f` over a set of map(s) `args`.
That is `Operation(f)(args)(x...)` is formally defined as
`f(map(a->a(x...),args)...)`.
# Example
```jldoctest
using Gridap.Arrays
fa(x) = x.*x
fb(x) = sqrt.(x)
x = collect(0:5)
fab = Operation(fa)(fb)
c = evaluate(fab,x)
println(c)
# output
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]
```
"""
struct Operation{T} <: Map
op::T
end
evaluate!(cache,op::Operation,args::Map...) = OperationMap(op.op,args)
evaluate!(cache,op::Operation,args::Function...) = OperationMap(op.op,args)
"""
"""
function inverse_map(f)
@unreachable """\n
Function inverse_map is not implemented yet for objects of type $(typeof(f))
"""
end