-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathforward.jl
More file actions
257 lines (210 loc) · 6.64 KB
/
Copy pathforward.jl
File metadata and controls
257 lines (210 loc) · 6.64 KB
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
module forward_tests
using Diffractor
using Diffractor: TaylorBundle, ZeroBundle, DNEBundle, ∂☆
using Diffractor: first_partial, primal
using ChainRules
using ChainRulesCore
using ChainRulesCore: ZeroTangent, NoTangent, frule_via_ad, rrule_via_ad
using LinearAlgebra
using Test
# Minimal 2-nd order forward smoke test
let var"'" = Diffractor.PrimeDerivativeFwd
@test Diffractor.∂☆{2}()(ZeroBundle{2}(sin),
Diffractor.ExplicitTangentBundle{2}(1.0, (1.0, 1.0, 0.0)))[Diffractor.CanonicalTangentIndex(1)] == sin'(1.0)
end
# Simple Forward Mode tests
let var"'" = Diffractor.PrimeDerivativeFwd
recursive_sin(x) = sin(x)
ChainRulesCore.frule(∂, ::typeof(recursive_sin), x) = frule(∂, sin, x)
# Integration tests
@test recursive_sin'(1.0) == cos(1.0)
@test recursive_sin''(1.0) == -sin(1.0)
@test_broken recursive_sin'''(1.0) == -cos(1.0)
@test_broken recursive_sin''''(1.0) == sin(1.0)
@test_broken recursive_sin'''''(1.0) == cos(1.0)
@test_broken recursive_sin''''''(1.0) == -sin(1.0)
# Test the special rules for sin/cos/exp
@test sin''''''(1.0) == -sin(1.0)
@test cos''''''(1.0) == -cos(1.0)
@test exp''''''(1.0) == exp(1.0)
@test (x->prod([x, 4]))'(3) == 4
end
# Some Basic Mixed Mode tests
function sin_twice_fwd(x)
let var"'" = Diffractor.PrimeDerivativeFwd
sin''(x)
end
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test sin_twice_fwd'(1.0) == sin'''(1.0)
end
@testset "No partials" begin
primal_calls = Ref(0)
function foo(x, y)
primal_calls[]+=1
return x+y
end
frule_calls = Ref(0)
function ChainRulesCore.frule((_, ẋ, ẏ), ::typeof(foo), x, y)
frule_calls[]+=1
return x+y, ẋ+ẏ
end
# Special case if there is no derivative information at all:
@test ∂☆{1}()(ZeroBundle{1}(foo), ZeroBundle{1}(2.0), ZeroBundle{1}(3.0)) == ZeroBundle{1}(5.0)
@test frule_calls[] == 0
@test primal_calls[] == 1
end
@testset "indexing" begin
# Test to make sure that `:boundscheck` and such are properly handled
function foo(x)
t = (x, x)
return t[1] + 1
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test foo'(1.0) == 1.0
end
# Test that `@inbounds` is ignored by Diffractor
function foo_errors(x)
t = (x, x)
@inbounds return t[3] + 1
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test_throws BoundsError foo_errors'(1.0) == 1.0
end
end
@testset "map" begin
@test ==(
∂☆{1}()(ZeroBundle{1}(xs->(map(x->2*x, xs))), TaylorBundle{1}([1.0, 2.0], ([10.0, 100.0],))),
TaylorBundle{1}([2.0, 4.0], ([20.0, 200.0],))
)
# map over all closure, wrt the closed variable
mulby(x) = y->x*y
🐇 = ∂☆{1}()(
ZeroBundle{1}(x->(map(mulby(x), [2.0, 4.0]))),
TaylorBundle{1}(2.0, (10.0,))
)
@test 🐇 == TaylorBundle{1}([4.0, 8.0], ([20.0, 40.0],))
end
@testset "structs" begin
struct IDemo
x::Float64
y::Float64
end
function foo(a)
obj = IDemo(2.0, a)
return obj.x * obj.y
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test foo'(100.0) == 2.0
@test foo''(100.0) == 0.0
end
end
@testset "tuples" begin
function foo(a)
tup = (2.0, a)
return first(tup) * tup[2]
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test foo'(100.0) == 2.0
@test foo''(100.0) == 0.0
end
end
@testset "vararg" begin
function foo(a)
tup = (2.0, a)
return *(tup...)
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test foo'(100.0) == 2.0
@test foo''(100.0) == 0.0
end
end
@testset "isdefined" begin
function foo_isdefined(x)
if (@noinline rand()) < 2
a=1 # always happens
end
2*@isdefined(a)*x + @isdefined(_thing_that_is_not_defined)
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test foo_isdefined'(100.0) == 2.0
end
end
@testset "types in tuples" begin
function foo(a)
tup = (a, 2a, Int)
return tup[2]
end
let var"'" = Diffractor.PrimeDerivativeFwd
@test foo'(100.0) == 2.0
end
end
@testset "custom number types" begin
struct CustomNumber <: Number
val::Float64
end
double_and_custom_num(x) = CustomNumber(2.0*x)
let var"'" = Diffractor.PrimeDerivativeFwd
@test double_and_custom_num'(100.0) == CustomNumber(2.0)
end
end
@testset "custom array type" begin
struct MyLittleStaticVector{N, T} <: AbstractVector{T}
val::NTuple{N, T}
end
Base.size(::MyLittleStaticVector{N}) where N = (N,)
Base.getindex(x::MyLittleStaticVector, ii::Int) = x.val[ii]
once_twice_three_times(x) = MyLittleStaticVector((x, 2x, 3x))
@assert once_twice_three_times(10.0) == MyLittleStaticVector((10.0, 20.0, 30.0))
🥯 = ∂☆{1}()(DNEBundle{1}(once_twice_three_times), TaylorBundle{1}(10.0, (1.0,)))
@test primal(🥯) = MyLittleStaticVector((10.0, 20.0, 30.0))
@test first_partial(🥯) == MyLittleStaticVector((1.0, 2.0, 3.0))
end
@testset "taylor_compatible" begin
taylor_compatible = Diffractor.taylor_compatible
@test taylor_compatible(
TaylorBundle{1}(10.0, (20.0,)),
TaylorBundle{1}(20.0, (30.0,))
)
@test !taylor_compatible(
TaylorBundle{1}(10.0, (20.0,)),
TaylorBundle{1}(21.0, (30.0,))
)
@test taylor_compatible(
TaylorBundle{2}(10.0, (20.0, 30.)),
TaylorBundle{2}(20.0, (30.0, 40.))
)
@test !taylor_compatible(
TaylorBundle{2}(10.0, (20.0, 30.0)),
TaylorBundle{2}(20.0, (31.0, 40.0))
)
tuptan(args...) = Tangent{typeof(args)}(args...)
@test taylor_compatible(
TaylorBundle{1}((10.0, 20.0), (tuptan(20.0, 30.0),)),
)
@test taylor_compatible(
TaylorBundle{2}((10.0, 20.0), (tuptan(20.0, 30.0),tuptan(30.0, 40.0))),
)
@test !taylor_compatible(
TaylorBundle{1}((10.0, 20.0), (tuptan(21.0, 30.0),)),
)
@test !taylor_compatible(
TaylorBundle{2}((10.0, 20.0), (tuptan(20.0, 31.0),tuptan(30.0, 40.0))),
)
end
@testset "configured frule" begin
my_func(x) = sin(x)
frule_hits = 0
function ChainRulesCore.frule(config::RuleConfig{>:HasForwardsMode}, (_, dx), ::typeof(my_func), x)
res=my_func(x)
_, der_fwd = ChainRulesCore.frule_via_ad(config, (ChainRulesCore.NoTangent(), dx), sin, x)
frule_hits +=1
return res, der_fwd
end
let var"'" = Diffractor.PrimeDerivativeFwd
@assert frule_hits == 0
@test my_func'(1.0) == cos(1.0)
@test frule_hits == 1
end
end
end # module