-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathadRev.js
286 lines (258 loc) · 8.57 KB
/
adRev.js
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
"use strict";
/**
Todo:
- hyperbolic and inv hyperbolic functions
- performance testing
**/
var primops = require('./primops')
var _e_ = 0
var lt_e = function(e1, e2) { return e1 < e2 }
var S_tape = function(epsilon, primal, factors, tapes, fanout, sensitivity) {
this.epsilon = epsilon;
this.primal = primal;
this.factors = factors;
this.tapes = tapes;
this.fanout = fanout;
this.sensitivity = sensitivity;
}
var makeTape = function(epsilon, primal, factors, tapes, fanout, sensitivity) {
return new S_tape(epsilon, primal, factors, tapes, fanout, sensitivity);
};
var tape = function(e, primal, factors, tapes) {
return makeTape(e, primal, factors, tapes, 0, 0.0);
};
var isTape = function(t) { return t instanceof S_tape; };
var makeTapifier = function() {
_e_ += 1;
return (function() {
var eThis = _e_;
return function(p) {return tape(eThis, p, [], []);};
}())
}
var tapify = makeTapifier();
var untapify = function(x) {
if (isTape(x)) {
return untapify(x.primal);
} else if (Array.isArray(x)) {
return x.map(untapify);
} else {
return x;
}
}
var lift_real_to_real = function(f, df_dx) {
var fn = function(x1) {
if (isTape(x1))
return tape(x1.epsilon, fn(x1.primal), [df_dx(x1.primal)], [x1]);
else
return f(x1);
}
return fn;
};
var lift_realreal_to_real = function(f, df_dx1, df_dx2) {
var fn = function(x_1, x_2) {
if (isTape(x_1)) {
if (isTape(x_2))
if (x_1.epsilon < x_2.epsilon)
return tape(x_2.epsilon, fn(x_1, x_2.primal), [df_dx2(x_1, x_2.primal)], [x_2])
else if (x_2.epsilon < x_1.epsilon)
return tape(x_1.epsilon, fn(x_1.primal, x_2), [df_dx1(x_1.primal, x_2)], [x_1])
else
return tape(x_1.epsilon,
fn(x_1.primal, x_2.primal),
[df_dx1(x_1.primal, x_2.primal), df_dx2(x_1.primal, x_2.primal)],
[x_1, x_2])
else
return tape(x_1.epsilon, fn(x_1.primal, x_2), [df_dx1(x_1.primal, x_2)], [x_1])
} else {
if (isTape(x_2))
return tape(x_2.epsilon, fn(x_1, x_2.primal), [df_dx2(x_1, x_2.primal)], [x_2])
else
return f(x_1, x_2)
}
};
return fn;
};
/** Lifting operations **/
/** helpers **/
// this might need primal* if cmp operators are used with &rest
var overloader_2cmp = function(baseF) {
var fn = function(x1, x2) {
if (isTape(x1))
return fn(x1.primal, x2);
else if (isTape(x2))
return fn(x1, x2.primal);
else
return baseF(x1, x2);
}
return fn;
};
var div2F = function(x1, x2){return d_div(1,x2);};
var divNF = function(x1, x2){return d_div(d_sub(0,x1), d_mul(x2, x2));};
/** lifted functions (overloaded) **/
var d_add = lift_realreal_to_real(primops.add, primops.oneF, primops.oneF);
var d_sub = lift_realreal_to_real(primops.sub, primops.oneF, primops.m_oneF);
var d_mul = lift_realreal_to_real(primops.mul, primops.secondF, primops.firstF);
var d_div = lift_realreal_to_real(primops.div, div2F, divNF);
var d_mod = function(a, b) {return d_sub(a, d_mul(a, d_floor(d_div(a, b))));};
var d_eq = overloader_2cmp(primops.eq);
var d_neq = overloader_2cmp(primops.neq);
var d_peq = overloader_2cmp(primops.peq);
var d_pneq = overloader_2cmp(primops.pneq);
var d_gt = overloader_2cmp(primops.gt);
var d_lt = overloader_2cmp(primops.lt);
var d_geq = overloader_2cmp(primops.geq);
var d_leq = overloader_2cmp(primops.leq);
// Chen-Harker-Kanzow-Smale smoothing
var _tolSq_ = 1e-10
var p_extrF = function(a, b, t2) {
return d_add(d_sqrt(d_add(d_pow(d_sub(a, b), 2), t2)), d_add(a, b));
};
var d_max = function(a, b) {return d_mul(p_extrF(a, b, _tolSq_), 0.5);};
var d_min = function(a, b) {return d_mul(p_extrF(d_sub(0.0, a), d_sub(0.0, b), _tolSq_), -0.5);};
var d_sqrt = lift_real_to_real(Math.sqrt, function(x){return d_div(1, d_mul(2.0, d_sqrt(x)))})
var d_exp = lift_real_to_real(Math.exp, function(x){return d_exp(x)});
var d_log = lift_real_to_real(Math.log, function(x){return d_div(1,x)});
// Note: derivatives of floor and ceil are undefined at integral values
var d_floor = lift_real_to_real(Math.floor, primops.zeroF);
var d_ceil = lift_real_to_real(Math.ceil, primops.zeroF);
// Note: better representation for abs?
var d_abs = function(x) {return d_gt(x, 0.0) ? x : d_sub(0.0, x)}
var d_pow = lift_realreal_to_real(Math.pow,
function(x1, x2){return d_mul(x2, d_pow(x1, d_sub(x2, 1)));},
function(x1, x2){return d_mul(d_log(x1), d_pow(x1, x2));});
var d_sin = lift_real_to_real(Math.sin, function(x){return d_cos(x)});
var d_cos = lift_real_to_real(Math.cos, function(x){return d_sub(0, d_sin(x))});
var d_atanF = lift_realreal_to_real(Math.atan2,
function(x1, x2){return d_div(x2, d_add(d_mul(x1,x1), d_mul(x2,x2)));},
function(x1, x2){return d_div(d_sub(0,x1), d_add(d_mul(x1,x1), d_mul(x2,x2)));});
var d_atan = function(x1, x2) {
x2 = x2 === undefined ? 1 : x2; // just atan, not atan2
return d_atanF(x1, x2);
};
var d_Math = {};
Object.getOwnPropertyNames(Math).forEach(function(n) {d_Math[n] = Math[n]});
d_Math.floor = d_floor;
d_Math.ceil = d_ceil;
d_Math.abs = d_abs;
d_Math.sqrt = d_sqrt;
d_Math.exp = d_exp;
d_Math.log = d_log;
d_Math.pow = d_pow;
d_Math.sin = d_sin;
d_Math.cos = d_cos;
d_Math.atan = d_atan;
d_Math.atan2 = d_atan;
d_Math.max = d_max;
d_Math.min = d_min;
/** derivatives and gradients **/
var determineFanout = function(tape) {
tape.fanout += 1;
if (tape.fanout === 1) {
var n = tape.tapes.length;
while (n--) determineFanout(tape.tapes[n]);
}
}
var initializeSensitivity = function(tape) {
tape.sensitivity = 0.0;
tape.fanout -= 1;
if (tape.fanout === 0) {
var n = tape.tapes.length;
while (n--) initializeSensitivity(tape.tapes[n]);
}
}
var reversePhase = function(sensitivity, tape) {
tape.sensitivity = d_add(tape.sensitivity, sensitivity);
tape.fanout -= 1;
if (tape.fanout === 0) {
var sens = tape.sensitivity;
var n = tape.factors.length;
while (n--) reversePhase(d_mul(sens, tape.factors[n]), tape.tapes[n]);
}
}
var gradientR = function(f) {
return function(x) {
_e_ += 1;
var new_x = x.map( function(xi) { return tape(_e_, xi, [], []) } )
var y = f(new_x);
if (isTape(y) && !lt_e(y.epsilon, _e_)) {
determineFanout(y);
reversePhase(1.0, y);
}
_e_ -= 1;
return new_x.map(function(v){return v.sensitivity})
}
}
var derivativeR = function(f) {
return function(x) {
var r = gradientR( function(x1) {return f(x1[0])} )([x])
return r[0]
}
}
var yGradientR = function(yReverse) {
// propogate sensitivities from y backwards
var ySensitivity = 1.0;
var thisE = tapify(0.0).epsilon;
if (isTape(yReverse) && !lt_e(yReverse.epsilon, thisE)) {
determineFanout(yReverse);
initializeSensitivity(yReverse);
}
if (isTape(yReverse) && !lt_e(yReverse.epsilon, thisE)) {
determineFanout(yReverse);
reversePhase(ySensitivity, yReverse);
}
}
var xyGradientR = function(mapIndependent, xReverse, yReverse) {
var mapDependent = function(f, yReverse) {return f(yReverse);};
var forEachDependent1 = function(f, yReverse) {return f(yReverse);};
var forEachDependent2 = function(f, yReverse, ySensitivity) {
return f(yReverse, ySensitivity);
};
// propogate sensitivities from y backwards
var ySensitivities = [1.0];
var thisE = tapify(0.0).epsilon;
var xSensitivities = _.map(ySensitivities, function(ySensitivity) {
forEachDependent1(function(yReverse) {
if (isTape(yReverse) && !lt_e(yReverse.epsilon, thisE)) {
determineFanout(yReverse);
initializeSensitivity(yReverse);
}
}, yReverse);
forEachDependent2(function(yReverse, ySensitivity) {
if (isTape(yReverse) && !lt_e(yReverse.epsilon, thisE)) {
determineFanout(yReverse);
reversePhase(ySensitivity, yReverse);
}
}, yReverse, ySensitivity);
return mapIndependent(function(tape) {return tape.sensitivity}, xReverse);
}, ySensitivities);
// return untapified y and the x derivatives
return [mapDependent(function(yReverse) {
return !isTape(yReverse) || lt_e(yReverse.epsilon, thisE) ?
yReverse :
yReverse.primal;
}, yReverse),
xSensitivities];
}
module.exports = {
add: d_add,
sub: d_sub,
mul: d_mul,
div: d_div,
mod: d_mod,
eq: d_eq,
neq: d_neq,
peq: d_peq,
pneq: d_pneq,
gt: d_gt,
lt: d_lt,
geq: d_geq,
leq: d_leq,
maths: d_Math,
derivativeR: derivativeR,
gradientR: gradientR,
yGradientR: yGradientR,
xyGradientR: xyGradientR,
makeTapifier: makeTapifier,
tapify: tapify,
untapify: untapify
}