adjointly 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1390 @@
1
+ // src/primitives.ts
2
+ function unary(name, value, deriv) {
3
+ return Object.freeze({
4
+ name,
5
+ value,
6
+ deriv
7
+ });
8
+ }
9
+ function binary(name, value, da, db) {
10
+ return Object.freeze({
11
+ name,
12
+ value,
13
+ da,
14
+ db
15
+ });
16
+ }
17
+ /** Whether `a` or `b` is `NaN`: the one case where neither `a <= b` nor `a > b` holds. */
18
+ function eitherNaN(a, b) {
19
+ return Number.isNaN(a) || Number.isNaN(b);
20
+ }
21
+ /**
22
+ * Logistic sigmoid `1 / (1 + e^-x)`, evaluated without overflow for any `x`: `exp` is only ever
23
+ * called on `-|x|`.
24
+ */
25
+ function sigmoidValue(x) {
26
+ if (x >= 0) return 1 / (1 + Math.exp(-x));
27
+ const e = Math.exp(x);
28
+ return e / (1 + e);
29
+ }
30
+ /**
31
+ * `sigmoid'(x) = sigmoid(x) * sigmoid(-x) = e / (1 + e)^2` with `e = exp(-|x|)`. Computed from `x`
32
+ * rather than as `y * (1 - y)`, which loses all precision for large positive `x` where `y` rounds
33
+ * to 1.
34
+ */
35
+ function sigmoidDeriv(x) {
36
+ const e = Math.exp(-Math.abs(x));
37
+ const s = 1 + e;
38
+ return e / (s * s);
39
+ }
40
+ /**
41
+ * Every primitive, keyed by name. Subgradient conventions at points where the derivative does not
42
+ * exist are part of the contract:
43
+ *
44
+ * | primitive | convention |
45
+ * |---|---|
46
+ * | `abs` | derivative at `0` is `0` |
47
+ * | `max`, `min` | on a tie (`a === b`) the whole derivative goes to the first argument `a` |
48
+ * | `pow` | `d/db` is `0` wherever `a <= 0`; `d/da` is `0` wherever `b === 0` |
49
+ * | `sqrt` | derivative at `0` (and `-0`) is `+Infinity` (the true one-sided limit) |
50
+ * | `max`, `min` | if either argument is `NaN`, the value and both partials are `NaN`, as for `abs` |
51
+ *
52
+ * Everywhere else the partials are the ordinary analytic derivatives, and outside a function's
53
+ * domain (`log` of a negative, `sqrt` of a negative) the value and partials are whatever IEEE
54
+ * arithmetic produces, usually `NaN`.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { primitives } from 'adjointly';
59
+ * const x = 0.5;
60
+ * const y = primitives.tanh.value(x); //=> tanh(0.5)
61
+ * const dy = primitives.tanh.deriv(x, y); //=> 1 - tanh(0.5) ** 2
62
+ * ```
63
+ */
64
+ const primitives = Object.freeze({
65
+ /** `-x`. */
66
+ neg: unary("neg", (x) => -x, () => -1),
67
+ /** `a + b`. */
68
+ add: binary("add", (a, b) => a + b, () => 1, () => 1),
69
+ /** `a - b`. */
70
+ sub: binary("sub", (a, b) => a - b, () => 1, () => -1),
71
+ /** `a * b`. */
72
+ mul: binary("mul", (a, b) => a * b, (_a, b) => b, (a) => a),
73
+ /** `a / b`. */
74
+ div: binary("div", (a, b) => a / b, (_a, b) => 1 / b, (_a, b, y) => -y / b),
75
+ /** `1 / x`. */
76
+ recip: unary("recip", (x) => 1 / x, (_x, y) => -y * y),
77
+ /** `x * x`. */
78
+ square: unary("square", (x) => x * x, (x) => 2 * x),
79
+ /** `sqrt(x)`; derivative `+Infinity` at `0` and at `-0`. */
80
+ sqrt: unary("sqrt", (x) => Math.sqrt(x), (_x, y) => .5 / (y + 0)),
81
+ /** `e^x`. */
82
+ exp: unary("exp", (x) => Math.exp(x), (_x, y) => y),
83
+ /** `e^x - 1`, accurate near `0`. */
84
+ expm1: unary("expm1", (x) => Math.expm1(x), (x) => Math.exp(x)),
85
+ /** Natural logarithm. */
86
+ log: unary("log", (x) => Math.log(x), (x) => 1 / x),
87
+ /** `log(1 + x)`, accurate near `0`. */
88
+ log1p: unary("log1p", (x) => Math.log1p(x), (x) => 1 / (1 + x)),
89
+ /**
90
+ * `a ** b`, differentiable in both. `d/da = b * a^(b-1)`, taken as `0` when `b === 0` (so
91
+ * `0^0` does not produce `0 * Infinity`). `d/db = a^b * log(a)` for `a > 0` and `0` for
92
+ * `a <= 0`, where the real power is not differentiable in the exponent.
93
+ */
94
+ pow: binary("pow", (a, b) => Math.pow(a, b), (a, b) => b === 0 ? 0 : b * Math.pow(a, b - 1), (a, _b, y) => a > 0 ? y * Math.log(a) : 0),
95
+ /** `|x|`; derivative `0` at `0`. */
96
+ abs: unary("abs", (x) => Math.abs(x), (x) => x > 0 ? 1 : x < 0 ? -1 : x === 0 ? 0 : NaN),
97
+ /**
98
+ * The smaller of `a` and `b`; on a tie the derivative goes to `a`. If either is `NaN`, the value
99
+ * and both partials are `NaN`.
100
+ */
101
+ min: binary("min", (a, b) => Math.min(a, b), (a, b) => a <= b ? 1 : eitherNaN(a, b) ? NaN : 0, (a, b) => a <= b ? 0 : eitherNaN(a, b) ? NaN : 1),
102
+ /**
103
+ * The larger of `a` and `b`; on a tie the derivative goes to `a`. If either is `NaN`, the value
104
+ * and both partials are `NaN`.
105
+ */
106
+ max: binary("max", (a, b) => Math.max(a, b), (a, b) => a >= b ? 1 : eitherNaN(a, b) ? NaN : 0, (a, b) => a >= b ? 0 : eitherNaN(a, b) ? NaN : 1),
107
+ /** Sine. */
108
+ sin: unary("sin", (x) => Math.sin(x), (x) => Math.cos(x)),
109
+ /** Cosine. */
110
+ cos: unary("cos", (x) => Math.cos(x), (x) => -Math.sin(x)),
111
+ /** Hyperbolic tangent. */
112
+ tanh: unary("tanh", (x) => Math.tanh(x), (x) => {
113
+ const e = Math.exp(-2 * Math.abs(x));
114
+ const s = 1 + e;
115
+ return 4 * e / (s * s);
116
+ }),
117
+ /** Logistic sigmoid `1 / (1 + e^-x)`, stable for any `x`. */
118
+ sigmoid: unary("sigmoid", sigmoidValue, sigmoidDeriv),
119
+ /**
120
+ * Softplus `log(1 + e^x)`, stable for any `x`: computed as `x + log1p(e^-x)` for positive `x`
121
+ * and `log1p(e^x)` otherwise, so it neither overflows nor rounds to `0` for very negative `x`.
122
+ * Its derivative is `sigmoid(x)`.
123
+ */
124
+ softplus: unary("softplus", (x) => x > 0 ? x + Math.log1p(Math.exp(-x)) : Math.log1p(Math.exp(x)), (x) => sigmoidValue(x))
125
+ });
126
+ /** The names of the one-argument primitives, in table order. */
127
+ const unaryNames = [
128
+ "neg",
129
+ "recip",
130
+ "square",
131
+ "sqrt",
132
+ "exp",
133
+ "expm1",
134
+ "log",
135
+ "log1p",
136
+ "abs",
137
+ "sin",
138
+ "cos",
139
+ "tanh",
140
+ "sigmoid",
141
+ "softplus"
142
+ ];
143
+ /** The names of the two-argument primitives, in table order. */
144
+ const binaryNames = [
145
+ "add",
146
+ "sub",
147
+ "mul",
148
+ "div",
149
+ "pow",
150
+ "min",
151
+ "max"
152
+ ];
153
+ // src/arithmetic.ts
154
+ /**
155
+ * The operations with a plain-number operand, each the binary primitive `of` with one operand a
156
+ * constant: the first one when `constantFirst`, otherwise the second. Their values and partials
157
+ * are that primitive's, from the table.
158
+ */
159
+ const constantOperandOps = {
160
+ addc: {
161
+ of: "add",
162
+ constantFirst: false
163
+ },
164
+ subc: {
165
+ of: "sub",
166
+ constantFirst: false
167
+ },
168
+ csub: {
169
+ of: "sub",
170
+ constantFirst: true
171
+ },
172
+ mulc: {
173
+ of: "mul",
174
+ constantFirst: false
175
+ },
176
+ divc: {
177
+ of: "div",
178
+ constantFirst: false
179
+ },
180
+ cdiv: {
181
+ of: "div",
182
+ constantFirst: true
183
+ },
184
+ powc: {
185
+ of: "pow",
186
+ constantFirst: false
187
+ }
188
+ };
189
+ /**
190
+ * {@link Arithmetic} on ordinary numbers: computes values only, with nothing recorded. Use it to
191
+ * evaluate a differentiable function at full speed when no derivative is needed.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { plain } from 'adjointly';
196
+ * plain.add(plain.constant(1), plain.exp(0)); //=> 2
197
+ * ```
198
+ */
199
+ const plain = {
200
+ constant: (x) => x,
201
+ value: (a) => a,
202
+ neg: primitives.neg.value,
203
+ add: primitives.add.value,
204
+ sub: primitives.sub.value,
205
+ mul: primitives.mul.value,
206
+ div: primitives.div.value,
207
+ recip: primitives.recip.value,
208
+ square: primitives.square.value,
209
+ sqrt: primitives.sqrt.value,
210
+ exp: primitives.exp.value,
211
+ expm1: primitives.expm1.value,
212
+ log: primitives.log.value,
213
+ log1p: primitives.log1p.value,
214
+ pow: primitives.pow.value,
215
+ abs: primitives.abs.value,
216
+ min: primitives.min.value,
217
+ max: primitives.max.value,
218
+ sin: primitives.sin.value,
219
+ cos: primitives.cos.value,
220
+ tanh: primitives.tanh.value,
221
+ sigmoid: primitives.sigmoid.value,
222
+ softplus: primitives.softplus.value,
223
+ addc: primitives.add.value,
224
+ subc: primitives.sub.value,
225
+ csub: primitives.sub.value,
226
+ mulc: primitives.mul.value,
227
+ divc: primitives.div.value,
228
+ cdiv: primitives.div.value,
229
+ powc: primitives.pow.value
230
+ };
231
+ // src/capacity.ts
232
+ /** `capacity` if it is a positive integer, the default if it is not given; otherwise throws. */
233
+ function initialCapacity(capacity, owner) {
234
+ if (capacity === void 0) return 1024;
235
+ if (!Number.isInteger(capacity) || capacity < 1) throw new RangeError(`${owner}: capacity must be a positive integer, not ${String(capacity)}`);
236
+ return capacity;
237
+ }
238
+ // src/debug.ts
239
+ /** Indices fit in 31 bits (parents are stored in an Int32Array). */
240
+ const STRIDE = 2 ** 31;
241
+ /** Epochs run from 1 to EPOCHS - 1, so that a handle stays below 2^52, an exact integer. */
242
+ const EPOCHS = 2 ** 21;
243
+ let lastEpoch = 0;
244
+ /**
245
+ * A fresh epoch, unique among all debug tapes and Forwards until 2^21 - 1 epochs have been used,
246
+ * after which numbering wraps around; a stale handle could then be mistaken for a live one only if
247
+ * it were used exactly that many clears later, at the same index.
248
+ */
249
+ function nextEpoch() {
250
+ lastEpoch = lastEpoch % (EPOCHS - 1) + 1;
251
+ return lastEpoch;
252
+ }
253
+ /** The handles of one debug Tape or Forward: issuing them and checking them. */
254
+ var Handles = class {
255
+ epoch = 0;
256
+ /** Every epoch this instance has used, to tell its own stale handles from another's. */
257
+ own = /* @__PURE__ */ new Set();
258
+ /** The epoch each slot was last written in. */
259
+ slotEpoch = /* @__PURE__ */ new Int32Array(64);
260
+ kind;
261
+ first;
262
+ target;
263
+ constructor(kind, first, target) {
264
+ this.kind = kind;
265
+ this.first = first;
266
+ this.target = target;
267
+ this.begin();
268
+ }
269
+ /** Start a new epoch: handles issued from now on are told apart from earlier ones. */
270
+ begin() {
271
+ this.epoch = nextEpoch();
272
+ this.own.add(this.epoch);
273
+ }
274
+ /** The handle for the slot `index` just written. */
275
+ issue(index) {
276
+ while (index >= this.slotEpoch.length) {
277
+ const grown = new Int32Array(this.slotEpoch.length * 2);
278
+ grown.set(this.slotEpoch);
279
+ this.slotEpoch = grown;
280
+ }
281
+ this.slotEpoch[index] = this.epoch;
282
+ return this.epoch * STRIDE + index;
283
+ }
284
+ /**
285
+ * Throw if `value`, given where a plain number belongs, is one of this instance's variables: what
286
+ * a variable mistaken for a number looks like, typically after a `typeof x === 'number'` test,
287
+ * which every variable passes. Only this instance's handles are recognised, so that no ordinary
288
+ * number is ever refused: a handle is a whole number of 2^31 or more, encoding an epoch of this
289
+ * instance and a slot written in that epoch.
290
+ */
291
+ checkNumber(value, where) {
292
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) return;
293
+ const epoch = Math.floor(value / STRIDE);
294
+ const index = value - epoch * STRIDE;
295
+ if (this.own.has(epoch) && this.slotEpoch[index] === epoch) throw new RangeError(`${where} is a variable, where a plain number belongs. A variable is a number at run time, so typeof x === 'number' is true for it too: keep variables and numbers apart instead.`);
296
+ }
297
+ /** The slot index of `handle`, or a `RangeError` saying what is wrong with it. */
298
+ resolve(handle, where) {
299
+ if (typeof handle !== "number" || !Number.isSafeInteger(handle) || handle < 0) throw new RangeError(`${where} is not a variable (it is ${String(handle)})`);
300
+ const epoch = Math.floor(handle / STRIDE);
301
+ const index = handle - epoch * STRIDE;
302
+ if (epoch === 0) throw new RangeError(`${where} is the plain number ${String(handle)}, not a variable of this ${this.kind}. Numbers enter through constant() or input(), or as the number operand of addc(), mulc() and the like. Do not tell variables from numbers with typeof: both are numbers.`);
303
+ if (!this.own.has(epoch)) throw new RangeError(`${where} is a variable of another Tape or Forward`);
304
+ if (index >= this.first + this.target.size || this.slotEpoch[index] !== epoch) throw new RangeError(`${where} is stale: its node was discarded by clear() or rewind()`);
305
+ return index;
306
+ }
307
+ };
308
+ const arithmetic = {
309
+ constant: [["n"], "v"],
310
+ value: [["v"], "-"]
311
+ };
312
+ for (const name of unaryNames) arithmetic[name] = [["v"], "v"];
313
+ for (const name of binaryNames) arithmetic[name] = [["v", "v"], "v"];
314
+ for (const [name, { constantFirst }] of Object.entries(constantOperandOps)) arithmetic[name] = [constantFirst ? ["n", "v"] : ["v", "n"], "v"];
315
+ const signatures = {
316
+ Tape: {
317
+ ...arithmetic,
318
+ input: [["n"], "v"],
319
+ inputs: [["ns"], "vs"],
320
+ backward: [["v", "n"], "-"],
321
+ vjp: [["vs", "-"], "-"],
322
+ adjoint: [["v"], "-"],
323
+ adjoints: [["vs", "-"], "-"],
324
+ gradient: [[
325
+ "v",
326
+ "vs",
327
+ "-"
328
+ ], "-"]
329
+ },
330
+ Forward: {
331
+ ...arithmetic,
332
+ input: [["n", "n"], "v"],
333
+ tangent: [["v"], "-"]
334
+ }
335
+ };
336
+ /**
337
+ * Turn `target`, a freshly constructed Tape or Forward, into its debug version. `first` is the
338
+ * index of its first slot (1 for a Tape, whose slot 0 is reserved; 0 for a Forward).
339
+ */
340
+ function installChecks(target, kind, first) {
341
+ const handles = new Handles(kind, first, target);
342
+ const self = target;
343
+ const proto = Object.getPrototypeOf(target);
344
+ const element = (where, k) => `${where}, element ${String(k + 1)}`;
345
+ const decode = (arg, value, where) => {
346
+ switch (arg) {
347
+ case "v": return handles.resolve(value, where);
348
+ case "vs": return value.map((h, k) => handles.resolve(h, element(where, k)));
349
+ case "n":
350
+ handles.checkNumber(value, where);
351
+ return value;
352
+ case "ns":
353
+ Array.from(value, (x, k) => {
354
+ handles.checkNumber(x, element(where, k));
355
+ });
356
+ return value;
357
+ case "-": return value;
358
+ }
359
+ };
360
+ const encode = (arg, value) => arg === "v" ? handles.issue(value) : arg === "vs" ? value.map((i) => handles.issue(i)) : value;
361
+ for (const [name, [args, result]] of Object.entries(signatures[kind])) {
362
+ const run = name === "gradient" ? (output, inputs, out) => {
363
+ proto.backward.call(target, output);
364
+ return proto.adjoints.call(target, inputs, out);
365
+ } : (...decoded) => proto[name].apply(target, decoded);
366
+ self[name] = (...params) => {
367
+ const decoded = params.map((p, i) => decode(args[i] ?? "-", p, `${kind}.${name}: argument ${String(i + 1)}`));
368
+ return encode(result, run(...decoded));
369
+ };
370
+ }
371
+ self.clear = () => {
372
+ proto.clear.call(target);
373
+ handles.begin();
374
+ };
375
+ if (kind === "Tape") self.rewind = (mark) => {
376
+ proto.rewind.call(target, mark);
377
+ handles.begin();
378
+ };
379
+ }
380
+ // src/tape.ts
381
+ /**
382
+ * Slot 0 of every array is a sink, not a node: leaves name it as both parents, and unary nodes as
383
+ * their second, always with a zero partial. Every parent index is therefore a valid slot, and the
384
+ * backward sweep's zero rule never writes to the sink.
385
+ */
386
+ const SINK = 0;
387
+ /**
388
+ * Reverse-mode automatic differentiation: an {@link Arithmetic} over {@link Var} that records
389
+ * every operation as a node on a tape, then computes the derivative of one output with respect to
390
+ * every node in a single backward sweep.
391
+ *
392
+ * Each node stores its value, up to two parent indices and the local partial derivatives with
393
+ * respect to them, computed when the node is recorded, and its adjoint: six typed arrays, 40
394
+ * bytes a node. They double when full, so recording does not allocate once the tape has grown to
395
+ * the size of the computation, and a tape reused across evaluations (via {@link Tape.clear} or
396
+ * {@link Tape.rewind}) never allocates again.
397
+ *
398
+ * The backward sweep visits nodes from the output down to the first, adding
399
+ * `partial * adjoint(child)` into each parent's adjoint, under the zero rule: if the partial or
400
+ * the adjoint is exactly 0 the term is 0, even when the other is infinite or `NaN`, so an unused
401
+ * `sqrt(0)` or `log(0)` does not poison the gradient.
402
+ *
403
+ * @example
404
+ * ```ts
405
+ * import { Tape } from 'adjointly';
406
+ * const t = new Tape();
407
+ * const x = t.input(2);
408
+ * const y = t.input(3);
409
+ * const f = t.mul(x, t.sin(y)); // x * sin(y)
410
+ * t.backward(f);
411
+ * t.adjoint(x); //=> sin(3)
412
+ * t.adjoint(y); //=> 2 * cos(3)
413
+ * ```
414
+ */
415
+ var Tape = class {
416
+ val;
417
+ pa;
418
+ pb;
419
+ da;
420
+ db;
421
+ adj;
422
+ /** The next free slot; nodes occupy slots 1 to n - 1. */
423
+ n = 1;
424
+ /** An empty tape; see {@link TapeOptions}. */
425
+ constructor(options = {}) {
426
+ const cap = initialCapacity(options.capacity, "Tape") + 1;
427
+ this.val = new Float64Array(cap);
428
+ this.pa = new Int32Array(cap);
429
+ this.pb = new Int32Array(cap);
430
+ this.da = new Float64Array(cap);
431
+ this.db = new Float64Array(cap);
432
+ this.adj = new Float64Array(cap);
433
+ if (options.debug === true) installChecks(this, "Tape", 1);
434
+ }
435
+ /** The number of nodes recorded so far. */
436
+ get size() {
437
+ return this.n - 1;
438
+ }
439
+ /** The number of nodes the tape can hold before it next grows. */
440
+ get capacity() {
441
+ return this.val.length - 1;
442
+ }
443
+ /** Forget every node, keeping the storage. Every `Var` previously issued becomes invalid. */
444
+ clear() {
445
+ this.n = 1;
446
+ }
447
+ /**
448
+ * A checkpoint at the current end of the tape. {@link Tape.rewind} to it discards everything
449
+ * recorded after it and keeps everything before, so a shared prefix (inputs, parameters,
450
+ * precomputed features) is recorded once and reused across many evaluations.
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * const t = new Tape();
455
+ * const w = t.input(0.5);
456
+ * const m = t.mark();
457
+ * let grad = 0;
458
+ * for (const x of [1, 2, 3]) {
459
+ * t.rewind(m); // drop the previous sample's nodes, keep w
460
+ * const loss = t.square(t.subc(t.mulc(w, x), 1));
461
+ * t.backward(loss);
462
+ * grad += t.adjoint(w);
463
+ * }
464
+ * grad; //=> 2 * ((0.5 - 1) * 1 + (1 - 1) * 2 + (1.5 - 1) * 3)
465
+ * t.size; //=> 4
466
+ * ```
467
+ */
468
+ mark() {
469
+ return this.n;
470
+ }
471
+ /**
472
+ * Truncate the tape back to `mark`. Vars created before the mark stay valid with their values;
473
+ * Vars created after it become invalid. Throws if `mark` is beyond the current end (the tape was
474
+ * already cleared or rewound further back).
475
+ */
476
+ rewind(mark) {
477
+ if (mark > this.n || mark < 1) throw new RangeError(`Tape.rewind: mark ${mark} is outside the tape (size ${this.size})`);
478
+ this.n = mark;
479
+ }
480
+ /**
481
+ * Record an independent variable with value `x`. Identical to {@link Tape.constant} on the tape
482
+ * (both are leaves and both accumulate an adjoint); the separate name says which leaves you
483
+ * intend to read a gradient for.
484
+ */
485
+ input(x) {
486
+ return this.push(x, SINK, 0, SINK, 0);
487
+ }
488
+ inputs(xs) {
489
+ return Array.from(xs, (x) => this.push(x, SINK, 0, SINK, 0));
490
+ }
491
+ /**
492
+ * Lift a number into `T` as a constant: its derivative with respect to every input is zero.
493
+ * On a `Tape` it still appends a node (constants are leaves of the tape).
494
+ */
495
+ constant(x) {
496
+ return this.push(x, SINK, 0, SINK, 0);
497
+ }
498
+ /**
499
+ * The current numeric value of `a`. Use it to branch (`if (A.value(x) > 0)`), to log, or to
500
+ * return a result. The number it returns carries no derivative information: anything computed
501
+ * from it with ordinary arithmetic is a constant as far as differentiation is concerned.
502
+ */
503
+ value(a) {
504
+ return this.val[a];
505
+ }
506
+ /**
507
+ * Run one backward sweep from `output`, seeding its adjoint with `seed` (default 1). Afterwards
508
+ * {@link Tape.adjoint} of any node `v` is `seed * d(output)/d(v)`. Every call starts from zero
509
+ * adjoints, so call it once per output whose gradient you want; the recorded tape is not
510
+ * changed and can be swept any number of times.
511
+ */
512
+ backward(output, seed = 1) {
513
+ if (output < 1 || output >= this.n) throw new RangeError(`Tape.backward: ${output} is not a node of this tape (size ${this.size})`);
514
+ const adj = this.adj;
515
+ adj.fill(0, 0, output + 1);
516
+ adj[output] = seed;
517
+ this.sweep(output);
518
+ }
519
+ /**
520
+ * Vector-Jacobian product: one backward sweep seeded with `weights[k]` at `outputs[k]`.
521
+ * Afterwards {@link Tape.adjoint} of `v` is `sum_k weights[k] * d(outputs[k])/d(v)`. An output
522
+ * listed twice gets the sum of its weights; with no outputs every adjoint is zero.
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * const t = new Tape();
527
+ * const x = t.input(1);
528
+ * const f = t.square(x); // df/dx = 2x
529
+ * const g = t.exp(x); // dg/dx = e^x
530
+ * t.vjp([f, g], [2, 3]); // gradient of 2f + 3g in one sweep
531
+ * t.adjoint(x); //=> 2 * 2 + 3 * E
532
+ * ```
533
+ */
534
+ vjp(outputs, weights) {
535
+ if (outputs.length !== weights.length) throw new RangeError(`Tape.vjp: ${outputs.length} outputs but ${weights.length} weights`);
536
+ let top = 0;
537
+ for (const o of outputs) {
538
+ if (o < 1 || o >= this.n) throw new RangeError(`Tape.vjp: ${o} is not a node of this tape (size ${this.size})`);
539
+ top = Math.max(top, o);
540
+ }
541
+ const adj = this.adj;
542
+ if (outputs.length === 0) {
543
+ adj.fill(0);
544
+ return;
545
+ }
546
+ adj.fill(0, 0, top + 1);
547
+ outputs.forEach((o, k) => {
548
+ adj[o] = adj[o] + weights[k];
549
+ });
550
+ this.sweep(top);
551
+ }
552
+ /**
553
+ * The adjoint of `v` from the last {@link Tape.backward} or {@link Tape.vjp}: the derivative of
554
+ * the seeded output(s) with respect to `v`. Zero for a node the output does not depend on.
555
+ * Meaningless for a node recorded after the output of that sweep.
556
+ */
557
+ adjoint(v) {
558
+ return this.adj[v];
559
+ }
560
+ /**
561
+ * Read the adjoints of `vars` into `out` (allocated if not given) and return it: the gradient
562
+ * restricted to those variables, in their order.
563
+ */
564
+ adjoints(vars, out = new Float64Array(vars.length)) {
565
+ const adj = this.adj;
566
+ vars.forEach((v, k) => {
567
+ out[k] = adj[v];
568
+ });
569
+ return out;
570
+ }
571
+ /**
572
+ * `backward(output)` then `adjoints(inputs, out)`: the gradient of `output` with respect to
573
+ * `inputs`, in their order.
574
+ *
575
+ * @example
576
+ * ```ts
577
+ * const t = new Tape();
578
+ * const [x, y] = t.inputs([1, 2]);
579
+ * t.gradient(t.mul(x, y), [x, y]); //=> [2, 1]
580
+ * ```
581
+ */
582
+ gradient(output, inputs, out) {
583
+ this.backward(output);
584
+ return this.adjoints(inputs, out);
585
+ }
586
+ sweep(top) {
587
+ const adj = this.adj, pa = this.pa, pb = this.pb, da = this.da, db = this.db;
588
+ for (let i = top; i > SINK; i--) {
589
+ const g = adj[i];
590
+ if (g === 0) continue;
591
+ const dA = da[i];
592
+ if (dA !== 0) {
593
+ const a = pa[i];
594
+ adj[a] = adj[a] + dA * g;
595
+ }
596
+ const dB = db[i];
597
+ if (dB !== 0) {
598
+ const b = pb[i];
599
+ adj[b] = adj[b] + dB * g;
600
+ }
601
+ }
602
+ }
603
+ push(y, a, dA, b, dB) {
604
+ const i = this.n;
605
+ if (i === this.val.length) this.grow();
606
+ this.val[i] = y;
607
+ this.pa[i] = a;
608
+ this.da[i] = dA;
609
+ this.pb[i] = b;
610
+ this.db[i] = dB;
611
+ this.n = i + 1;
612
+ return i;
613
+ }
614
+ grow() {
615
+ const cap = this.val.length * 2;
616
+ const val = new Float64Array(cap);
617
+ val.set(this.val);
618
+ this.val = val;
619
+ const pa = new Int32Array(cap);
620
+ pa.set(this.pa);
621
+ this.pa = pa;
622
+ const pb = new Int32Array(cap);
623
+ pb.set(this.pb);
624
+ this.pb = pb;
625
+ const da = new Float64Array(cap);
626
+ da.set(this.da);
627
+ this.da = da;
628
+ const db = new Float64Array(cap);
629
+ db.set(this.db);
630
+ this.db = db;
631
+ const adj = new Float64Array(cap);
632
+ adj.set(this.adj);
633
+ this.adj = adj;
634
+ }
635
+ /** `-a`. */
636
+ neg(a) {
637
+ const x = this.val[a];
638
+ const y = primitives.neg.value(x);
639
+ return this.push(y, a, primitives.neg.deriv(x, y), SINK, 0);
640
+ }
641
+ /** `a + b`. */
642
+ add(a, b) {
643
+ const x = this.val[a];
644
+ const z = this.val[b];
645
+ const y = primitives.add.value(x, z);
646
+ return this.push(y, a, primitives.add.da(x, z, y), b, primitives.add.db(x, z, y));
647
+ }
648
+ /** `a - b`. */
649
+ sub(a, b) {
650
+ const x = this.val[a];
651
+ const z = this.val[b];
652
+ const y = primitives.sub.value(x, z);
653
+ return this.push(y, a, primitives.sub.da(x, z, y), b, primitives.sub.db(x, z, y));
654
+ }
655
+ /** `a * b`. */
656
+ mul(a, b) {
657
+ const x = this.val[a];
658
+ const z = this.val[b];
659
+ const y = primitives.mul.value(x, z);
660
+ return this.push(y, a, primitives.mul.da(x, z, y), b, primitives.mul.db(x, z, y));
661
+ }
662
+ /** `a / b`. */
663
+ div(a, b) {
664
+ const x = this.val[a];
665
+ const z = this.val[b];
666
+ const y = primitives.div.value(x, z);
667
+ return this.push(y, a, primitives.div.da(x, z, y), b, primitives.div.db(x, z, y));
668
+ }
669
+ /** `1 / a`. */
670
+ recip(a) {
671
+ const x = this.val[a];
672
+ const y = primitives.recip.value(x);
673
+ return this.push(y, a, primitives.recip.deriv(x, y), SINK, 0);
674
+ }
675
+ /** `a * a`, one node instead of a `mul` of `a` with itself. */
676
+ square(a) {
677
+ const x = this.val[a];
678
+ const y = primitives.square.value(x);
679
+ return this.push(y, a, primitives.square.deriv(x, y), SINK, 0);
680
+ }
681
+ /** `sqrt(a)`; its derivative at `0` is `+Infinity`. */
682
+ sqrt(a) {
683
+ const x = this.val[a];
684
+ const y = primitives.sqrt.value(x);
685
+ return this.push(y, a, primitives.sqrt.deriv(x, y), SINK, 0);
686
+ }
687
+ /** `e^a`. */
688
+ exp(a) {
689
+ const x = this.val[a];
690
+ const y = primitives.exp.value(x);
691
+ return this.push(y, a, primitives.exp.deriv(x, y), SINK, 0);
692
+ }
693
+ /** `e^a - 1`, accurate for small `a`. */
694
+ expm1(a) {
695
+ const x = this.val[a];
696
+ const y = primitives.expm1.value(x);
697
+ return this.push(y, a, primitives.expm1.deriv(x, y), SINK, 0);
698
+ }
699
+ /** Natural logarithm. */
700
+ log(a) {
701
+ const x = this.val[a];
702
+ const y = primitives.log.value(x);
703
+ return this.push(y, a, primitives.log.deriv(x, y), SINK, 0);
704
+ }
705
+ /** `log(1 + a)`, accurate for small `a`. */
706
+ log1p(a) {
707
+ const x = this.val[a];
708
+ const y = primitives.log1p.value(x);
709
+ return this.push(y, a, primitives.log1p.deriv(x, y), SINK, 0);
710
+ }
711
+ /**
712
+ * `a ** b`, differentiable in both arguments. The derivative with respect to `b` is `0` wherever
713
+ * `a <= 0`; the derivative with respect to `a` is `0` wherever `b === 0`. For a fixed numeric
714
+ * exponent use {@link Arithmetic.powc}; for `b = 2` prefer {@link Arithmetic.square}, for
715
+ * `b = 0.5` prefer {@link Arithmetic.sqrt}.
716
+ */
717
+ pow(a, b) {
718
+ const x = this.val[a];
719
+ const z = this.val[b];
720
+ const y = primitives.pow.value(x, z);
721
+ return this.push(y, a, primitives.pow.da(x, z, y), b, primitives.pow.db(x, z, y));
722
+ }
723
+ /** `|a|`; its derivative at `0` is `0`. */
724
+ abs(a) {
725
+ const x = this.val[a];
726
+ const y = primitives.abs.value(x);
727
+ return this.push(y, a, primitives.abs.deriv(x, y), SINK, 0);
728
+ }
729
+ /** The smaller of `a` and `b`; on a tie the derivative flows to `a`. */
730
+ min(a, b) {
731
+ const x = this.val[a];
732
+ const z = this.val[b];
733
+ const y = primitives.min.value(x, z);
734
+ return this.push(y, a, primitives.min.da(x, z, y), b, primitives.min.db(x, z, y));
735
+ }
736
+ /** The larger of `a` and `b`; on a tie the derivative flows to `a`. */
737
+ max(a, b) {
738
+ const x = this.val[a];
739
+ const z = this.val[b];
740
+ const y = primitives.max.value(x, z);
741
+ return this.push(y, a, primitives.max.da(x, z, y), b, primitives.max.db(x, z, y));
742
+ }
743
+ /** Sine. */
744
+ sin(a) {
745
+ const x = this.val[a];
746
+ const y = primitives.sin.value(x);
747
+ return this.push(y, a, primitives.sin.deriv(x, y), SINK, 0);
748
+ }
749
+ /** Cosine. */
750
+ cos(a) {
751
+ const x = this.val[a];
752
+ const y = primitives.cos.value(x);
753
+ return this.push(y, a, primitives.cos.deriv(x, y), SINK, 0);
754
+ }
755
+ /** Hyperbolic tangent. */
756
+ tanh(a) {
757
+ const x = this.val[a];
758
+ const y = primitives.tanh.value(x);
759
+ return this.push(y, a, primitives.tanh.deriv(x, y), SINK, 0);
760
+ }
761
+ /** Logistic sigmoid `1 / (1 + e^-a)`, stable for any `a`. */
762
+ sigmoid(a) {
763
+ const x = this.val[a];
764
+ const y = primitives.sigmoid.value(x);
765
+ return this.push(y, a, primitives.sigmoid.deriv(x, y), SINK, 0);
766
+ }
767
+ /** Softplus `log(1 + e^a)`, stable for any `a`; its derivative is `sigmoid(a)`. */
768
+ softplus(a) {
769
+ const x = this.val[a];
770
+ const y = primitives.softplus.value(x);
771
+ return this.push(y, a, primitives.softplus.deriv(x, y), SINK, 0);
772
+ }
773
+ /**
774
+ * `a + c` for a plain number `c`: `add(a, constant(c))` without the constant's tape node. Same
775
+ * value and derivative, bit for bit.
776
+ */
777
+ addc(a, c) {
778
+ const x = this.val[a];
779
+ const y = primitives.add.value(x, c);
780
+ return this.push(y, a, primitives.add.da(x, c, y), SINK, 0);
781
+ }
782
+ /** `a - c` for a plain number `c`; `sub(a, constant(c))` in one node. */
783
+ subc(a, c) {
784
+ const x = this.val[a];
785
+ const y = primitives.sub.value(x, c);
786
+ return this.push(y, a, primitives.sub.da(x, c, y), SINK, 0);
787
+ }
788
+ /** `c - a` for a plain number `c`; `sub(constant(c), a)` in one node. */
789
+ csub(c, a) {
790
+ const x = this.val[a];
791
+ const y = primitives.sub.value(c, x);
792
+ return this.push(y, a, primitives.sub.db(c, x, y), SINK, 0);
793
+ }
794
+ /** `a * c` for a plain number `c`; `mul(a, constant(c))` in one node. */
795
+ mulc(a, c) {
796
+ const x = this.val[a];
797
+ const y = primitives.mul.value(x, c);
798
+ return this.push(y, a, primitives.mul.da(x, c, y), SINK, 0);
799
+ }
800
+ /** `a / c` for a plain number `c`; `div(a, constant(c))` in one node. */
801
+ divc(a, c) {
802
+ const x = this.val[a];
803
+ const y = primitives.div.value(x, c);
804
+ return this.push(y, a, primitives.div.da(x, c, y), SINK, 0);
805
+ }
806
+ /** `c / a` for a plain number `c`; `div(constant(c), a)` in one node. */
807
+ cdiv(c, a) {
808
+ const x = this.val[a];
809
+ const y = primitives.div.value(c, x);
810
+ return this.push(y, a, primitives.div.db(c, x, y), SINK, 0);
811
+ }
812
+ /**
813
+ * `a ** c` for a plain number `c`; `pow(a, constant(c))` in one node, with the same conventions
814
+ * (the derivative is `0` when `c === 0`).
815
+ */
816
+ powc(a, c) {
817
+ const x = this.val[a];
818
+ const y = primitives.pow.value(x, c);
819
+ return this.push(y, a, primitives.pow.da(x, c, y), SINK, 0);
820
+ }
821
+ };
822
+ // src/forward.ts
823
+ /**
824
+ * One term of the chain rule, `d * t`, except that a zero factor makes the term zero even when the
825
+ * other factor is infinite or `NaN`. The tape's backward sweep applies the same rule.
826
+ */
827
+ function times(d, t) {
828
+ return d === 0 || t === 0 ? 0 : d * t;
829
+ }
830
+ /**
831
+ * Forward-mode automatic differentiation: an {@link Arithmetic} over {@link Dual} where every value
832
+ * carries a tangent, its derivative along one direction chosen when the inputs are created. One
833
+ * evaluation gives the directional derivative `grad f(x) . v` (a Jacobian-vector product) for every
834
+ * output at once, with no backward sweep and nothing kept but a value and a tangent per slot.
835
+ *
836
+ * Tangents propagate as `t(y) = da * t(a) + db * t(b)` using the same partials the {@link Tape}
837
+ * records, under the same zero rule: if a partial or a tangent is exactly 0 the term is 0, even when
838
+ * the other is infinite or `NaN`, so an infinite partial such as `sqrt` at `0` does not turn a zero
839
+ * tangent into `NaN`.
840
+ *
841
+ * Use forward mode when there are few inputs (or one direction) and many outputs; use the tape
842
+ * when there are many inputs and one output.
843
+ *
844
+ * @example
845
+ * ```ts
846
+ * import { Forward } from 'adjointly';
847
+ * const F = new Forward();
848
+ * const x = F.input(2, 1); // seed: direction (1, 0)
849
+ * const y = F.input(3, 0);
850
+ * const f = F.mul(x, F.sin(y)); // x * sin(y)
851
+ * F.value(f); //=> 2 * sin(3)
852
+ * F.tangent(f); //=> sin(3)
853
+ * ```
854
+ */
855
+ var Forward = class {
856
+ val;
857
+ tan;
858
+ n = 0;
859
+ /** An empty forward-mode context; see {@link ForwardOptions}. */
860
+ constructor(options = {}) {
861
+ const cap = initialCapacity(options.capacity, "Forward");
862
+ this.val = new Float64Array(cap);
863
+ this.tan = new Float64Array(cap);
864
+ if (options.debug === true) installChecks(this, "Forward", 0);
865
+ }
866
+ /** The number of values created so far. */
867
+ get size() {
868
+ return this.n;
869
+ }
870
+ /** Forget every value, keeping the storage. Every `Dual` previously issued becomes invalid. */
871
+ clear() {
872
+ this.n = 0;
873
+ }
874
+ /** An input with value `x` and tangent (seed) `tangent`: its component of the direction. */
875
+ input(x, tangent) {
876
+ return this.push(x, tangent);
877
+ }
878
+ /**
879
+ * Lift a number into `T` as a constant: its derivative with respect to every input is zero.
880
+ * On a `Tape` it still appends a node (constants are leaves of the tape).
881
+ */
882
+ constant(x) {
883
+ return this.push(x, 0);
884
+ }
885
+ /**
886
+ * The current numeric value of `a`. Use it to branch (`if (A.value(x) > 0)`), to log, or to
887
+ * return a result. The number it returns carries no derivative information: anything computed
888
+ * from it with ordinary arithmetic is a constant as far as differentiation is concerned.
889
+ */
890
+ value(a) {
891
+ return this.val[a];
892
+ }
893
+ /** The tangent of `a`: its derivative along the direction the inputs were seeded with. */
894
+ tangent(a) {
895
+ return this.tan[a];
896
+ }
897
+ push(y, t) {
898
+ const i = this.n;
899
+ if (i === this.val.length) this.grow();
900
+ this.val[i] = y;
901
+ this.tan[i] = t;
902
+ this.n = i + 1;
903
+ return i;
904
+ }
905
+ grow() {
906
+ const cap = this.val.length * 2;
907
+ const val = new Float64Array(cap);
908
+ val.set(this.val);
909
+ this.val = val;
910
+ const tan = new Float64Array(cap);
911
+ tan.set(this.tan);
912
+ this.tan = tan;
913
+ }
914
+ /** `-a`. */
915
+ neg(a) {
916
+ const x = this.val[a];
917
+ const ta = this.tan[a];
918
+ const y = primitives.neg.value(x);
919
+ return this.push(y, primitives.neg.deriv(x, y) * ta);
920
+ }
921
+ /** `a + b`. */
922
+ add(a, b) {
923
+ const x = this.val[a];
924
+ const z = this.val[b];
925
+ const ta = this.tan[a];
926
+ const tb = this.tan[b];
927
+ const y = primitives.add.value(x, z);
928
+ return this.push(y, primitives.add.da(x, z, y) * ta + primitives.add.db(x, z, y) * tb);
929
+ }
930
+ /** `a - b`. */
931
+ sub(a, b) {
932
+ const x = this.val[a];
933
+ const z = this.val[b];
934
+ const ta = this.tan[a];
935
+ const tb = this.tan[b];
936
+ const y = primitives.sub.value(x, z);
937
+ return this.push(y, primitives.sub.da(x, z, y) * ta + primitives.sub.db(x, z, y) * tb);
938
+ }
939
+ /** `a * b`. */
940
+ mul(a, b) {
941
+ const x = this.val[a];
942
+ const z = this.val[b];
943
+ const ta = this.tan[a];
944
+ const tb = this.tan[b];
945
+ const y = primitives.mul.value(x, z);
946
+ return this.push(y, times(primitives.mul.da(x, z, y), ta) + times(primitives.mul.db(x, z, y), tb));
947
+ }
948
+ /** `a / b`. */
949
+ div(a, b) {
950
+ const x = this.val[a];
951
+ const z = this.val[b];
952
+ const ta = this.tan[a];
953
+ const tb = this.tan[b];
954
+ const y = primitives.div.value(x, z);
955
+ return this.push(y, times(primitives.div.da(x, z, y), ta) + times(primitives.div.db(x, z, y), tb));
956
+ }
957
+ /** `1 / a`. */
958
+ recip(a) {
959
+ const x = this.val[a];
960
+ const ta = this.tan[a];
961
+ const y = primitives.recip.value(x);
962
+ return this.push(y, times(primitives.recip.deriv(x, y), ta));
963
+ }
964
+ /** `a * a`, one node instead of a `mul` of `a` with itself. */
965
+ square(a) {
966
+ const x = this.val[a];
967
+ const ta = this.tan[a];
968
+ const y = primitives.square.value(x);
969
+ return this.push(y, times(primitives.square.deriv(x, y), ta));
970
+ }
971
+ /** `sqrt(a)`; its derivative at `0` is `+Infinity`. */
972
+ sqrt(a) {
973
+ const x = this.val[a];
974
+ const ta = this.tan[a];
975
+ const y = primitives.sqrt.value(x);
976
+ return this.push(y, times(primitives.sqrt.deriv(x, y), ta));
977
+ }
978
+ /** `e^a`. */
979
+ exp(a) {
980
+ const x = this.val[a];
981
+ const ta = this.tan[a];
982
+ const y = primitives.exp.value(x);
983
+ return this.push(y, times(primitives.exp.deriv(x, y), ta));
984
+ }
985
+ /** `e^a - 1`, accurate for small `a`. */
986
+ expm1(a) {
987
+ const x = this.val[a];
988
+ const ta = this.tan[a];
989
+ const y = primitives.expm1.value(x);
990
+ return this.push(y, times(primitives.expm1.deriv(x, y), ta));
991
+ }
992
+ /** Natural logarithm. */
993
+ log(a) {
994
+ const x = this.val[a];
995
+ const ta = this.tan[a];
996
+ const y = primitives.log.value(x);
997
+ return this.push(y, times(primitives.log.deriv(x, y), ta));
998
+ }
999
+ /** `log(1 + a)`, accurate for small `a`. */
1000
+ log1p(a) {
1001
+ const x = this.val[a];
1002
+ const ta = this.tan[a];
1003
+ const y = primitives.log1p.value(x);
1004
+ return this.push(y, times(primitives.log1p.deriv(x, y), ta));
1005
+ }
1006
+ /**
1007
+ * `a ** b`, differentiable in both arguments. The derivative with respect to `b` is `0` wherever
1008
+ * `a <= 0`; the derivative with respect to `a` is `0` wherever `b === 0`. For a fixed numeric
1009
+ * exponent use {@link Arithmetic.powc}; for `b = 2` prefer {@link Arithmetic.square}, for
1010
+ * `b = 0.5` prefer {@link Arithmetic.sqrt}.
1011
+ */
1012
+ pow(a, b) {
1013
+ const x = this.val[a];
1014
+ const z = this.val[b];
1015
+ const ta = this.tan[a];
1016
+ const tb = this.tan[b];
1017
+ const y = primitives.pow.value(x, z);
1018
+ return this.push(y, times(primitives.pow.da(x, z, y), ta) + times(primitives.pow.db(x, z, y), tb));
1019
+ }
1020
+ /** `|a|`; its derivative at `0` is `0`. */
1021
+ abs(a) {
1022
+ const x = this.val[a];
1023
+ const ta = this.tan[a];
1024
+ const y = primitives.abs.value(x);
1025
+ return this.push(y, times(primitives.abs.deriv(x, y), ta));
1026
+ }
1027
+ /** The smaller of `a` and `b`; on a tie the derivative flows to `a`. */
1028
+ min(a, b) {
1029
+ const x = this.val[a];
1030
+ const z = this.val[b];
1031
+ const ta = this.tan[a];
1032
+ const tb = this.tan[b];
1033
+ const y = primitives.min.value(x, z);
1034
+ return this.push(y, times(primitives.min.da(x, z, y), ta) + times(primitives.min.db(x, z, y), tb));
1035
+ }
1036
+ /** The larger of `a` and `b`; on a tie the derivative flows to `a`. */
1037
+ max(a, b) {
1038
+ const x = this.val[a];
1039
+ const z = this.val[b];
1040
+ const ta = this.tan[a];
1041
+ const tb = this.tan[b];
1042
+ const y = primitives.max.value(x, z);
1043
+ return this.push(y, times(primitives.max.da(x, z, y), ta) + times(primitives.max.db(x, z, y), tb));
1044
+ }
1045
+ /** Sine. */
1046
+ sin(a) {
1047
+ const x = this.val[a];
1048
+ const ta = this.tan[a];
1049
+ const y = primitives.sin.value(x);
1050
+ return this.push(y, times(primitives.sin.deriv(x, y), ta));
1051
+ }
1052
+ /** Cosine. */
1053
+ cos(a) {
1054
+ const x = this.val[a];
1055
+ const ta = this.tan[a];
1056
+ const y = primitives.cos.value(x);
1057
+ return this.push(y, times(primitives.cos.deriv(x, y), ta));
1058
+ }
1059
+ /** Hyperbolic tangent. */
1060
+ tanh(a) {
1061
+ const x = this.val[a];
1062
+ const ta = this.tan[a];
1063
+ const y = primitives.tanh.value(x);
1064
+ return this.push(y, times(primitives.tanh.deriv(x, y), ta));
1065
+ }
1066
+ /** Logistic sigmoid `1 / (1 + e^-a)`, stable for any `a`. */
1067
+ sigmoid(a) {
1068
+ const x = this.val[a];
1069
+ const ta = this.tan[a];
1070
+ const y = primitives.sigmoid.value(x);
1071
+ return this.push(y, times(primitives.sigmoid.deriv(x, y), ta));
1072
+ }
1073
+ /** Softplus `log(1 + e^a)`, stable for any `a`; its derivative is `sigmoid(a)`. */
1074
+ softplus(a) {
1075
+ const x = this.val[a];
1076
+ const ta = this.tan[a];
1077
+ const y = primitives.softplus.value(x);
1078
+ return this.push(y, times(primitives.softplus.deriv(x, y), ta));
1079
+ }
1080
+ /**
1081
+ * `a + c` for a plain number `c`: `add(a, constant(c))` without the constant's tape node. Same
1082
+ * value and derivative, bit for bit.
1083
+ */
1084
+ addc(a, c) {
1085
+ const x = this.val[a];
1086
+ const y = primitives.add.value(x, c);
1087
+ return this.push(y, times(primitives.add.da(x, c, y), this.tan[a]));
1088
+ }
1089
+ /** `a - c` for a plain number `c`; `sub(a, constant(c))` in one node. */
1090
+ subc(a, c) {
1091
+ const x = this.val[a];
1092
+ const y = primitives.sub.value(x, c);
1093
+ return this.push(y, times(primitives.sub.da(x, c, y), this.tan[a]));
1094
+ }
1095
+ /** `c - a` for a plain number `c`; `sub(constant(c), a)` in one node. */
1096
+ csub(c, a) {
1097
+ const x = this.val[a];
1098
+ const y = primitives.sub.value(c, x);
1099
+ return this.push(y, times(primitives.sub.db(c, x, y), this.tan[a]));
1100
+ }
1101
+ /** `a * c` for a plain number `c`; `mul(a, constant(c))` in one node. */
1102
+ mulc(a, c) {
1103
+ const x = this.val[a];
1104
+ const y = primitives.mul.value(x, c);
1105
+ return this.push(y, times(primitives.mul.da(x, c, y), this.tan[a]));
1106
+ }
1107
+ /** `a / c` for a plain number `c`; `div(a, constant(c))` in one node. */
1108
+ divc(a, c) {
1109
+ const x = this.val[a];
1110
+ const y = primitives.div.value(x, c);
1111
+ return this.push(y, times(primitives.div.da(x, c, y), this.tan[a]));
1112
+ }
1113
+ /** `c / a` for a plain number `c`; `div(constant(c), a)` in one node. */
1114
+ cdiv(c, a) {
1115
+ const x = this.val[a];
1116
+ const y = primitives.div.value(c, x);
1117
+ return this.push(y, times(primitives.div.db(c, x, y), this.tan[a]));
1118
+ }
1119
+ /**
1120
+ * `a ** c` for a plain number `c`; `pow(a, constant(c))` in one node, with the same conventions
1121
+ * (the derivative is `0` when `c === 0`).
1122
+ */
1123
+ powc(a, c) {
1124
+ const x = this.val[a];
1125
+ const y = primitives.pow.value(x, c);
1126
+ return this.push(y, times(primitives.pow.da(x, c, y), this.tan[a]));
1127
+ }
1128
+ };
1129
+ // src/composites.ts
1130
+ /**
1131
+ * The sum of `xs`, added left to right; `A.constant(0)` for an empty list.
1132
+ *
1133
+ * @example
1134
+ * ```ts
1135
+ * sum(plain, [1, 2, 3]); //=> 6
1136
+ * ```
1137
+ */
1138
+ function sum(A, xs) {
1139
+ if (xs.length === 0) return A.constant(0);
1140
+ let s = xs[0];
1141
+ for (let i = 1; i < xs.length; i++) s = A.add(s, xs[i]);
1142
+ return s;
1143
+ }
1144
+ /**
1145
+ * The dot product `sum_i xs[i] * ys[i]`; `A.constant(0)` for empty lists. Throws if the lengths
1146
+ * differ.
1147
+ *
1148
+ * @example
1149
+ * ```ts
1150
+ * dot(plain, [1, 2], [3, 4]); //=> 11
1151
+ * ```
1152
+ */
1153
+ function dot(A, xs, ys) {
1154
+ if (xs.length !== ys.length) throw new RangeError(`dot: lengths differ (${xs.length} and ${ys.length})`);
1155
+ if (xs.length === 0) return A.constant(0);
1156
+ let s = A.mul(xs[0], ys[0]);
1157
+ for (let i = 1; i < xs.length; i++) s = A.add(s, A.mul(xs[i], ys[i]));
1158
+ return s;
1159
+ }
1160
+ /** The largest of the values of `xs` as a plain number; `NaN` if any of them is `NaN`. */
1161
+ function largest(A, xs) {
1162
+ let m = -Infinity;
1163
+ for (const x of xs) m = Math.max(m, A.value(x));
1164
+ return m;
1165
+ }
1166
+ /**
1167
+ * `log(sum_i exp(xs[i]))`, computed stably as `m + log(sum_i exp(xs[i] - m))` where `m` is the
1168
+ * largest value. `m` enters as a constant: the identity holds for any `m`, so its derivative
1169
+ * through `m` is zero and leaving it out keeps the gradient exact (it is `softmax(xs)`).
1170
+ * Throws on an empty list.
1171
+ *
1172
+ * Infinite inputs give the limit of the finite case. If the largest input is `+Infinity`, or every
1173
+ * input is `-Infinity`, the value is that infinity and the gradient is shared equally by the inputs
1174
+ * equal to it, `0` elsewhere; a `-Infinity` among finite inputs has weight `0`. A `NaN` input
1175
+ * makes everything `NaN`.
1176
+ *
1177
+ * @example
1178
+ * ```ts
1179
+ * // The naive log(exp(1000) + exp(1000)) overflows to Infinity.
1180
+ * logsumexp(plain, [1000, 1000]); //=> 1000 + log(2)
1181
+ * gradient((A, xs) => logsumexp(A, xs), [Infinity, 0]).gradient; //=> [1, 0]
1182
+ * ```
1183
+ */
1184
+ function logsumexp(A, xs) {
1185
+ if (xs.length === 0) throw new RangeError("logsumexp: empty list");
1186
+ const m = largest(A, xs);
1187
+ if (m === Infinity || m === -Infinity) {
1188
+ const top = xs.filter((x) => A.value(x) === m);
1189
+ return A.add(A.mul(sum(A, top), A.constant(1 / top.length)), A.constant(Math.log(top.length)));
1190
+ }
1191
+ const c = A.constant(m);
1192
+ let s = A.exp(A.sub(xs[0], c));
1193
+ for (let i = 1; i < xs.length; i++) s = A.add(s, A.exp(A.sub(xs[i], c)));
1194
+ return A.add(c, A.log(s));
1195
+ }
1196
+ /**
1197
+ * The softmax `exp(xs[i]) / sum_j exp(xs[j])`, computed stably by first subtracting the largest
1198
+ * value as a constant (softmax is invariant to the shift, so the gradient stays exact). Throws on
1199
+ * an empty list.
1200
+ *
1201
+ * Infinite inputs give the limit of the finite case. If the largest input is `+Infinity`, or every
1202
+ * input is `-Infinity`, the weights are `1/k` on the `k` inputs equal to it and `0` elsewhere,
1203
+ * returned as constants: the Jacobian there is `0`, which is also its limit. A `-Infinity` among
1204
+ * finite inputs gets weight `0`. A `NaN` input makes every weight `NaN`.
1205
+ *
1206
+ * @example
1207
+ * ```ts
1208
+ * softmax(plain, [0, Math.log(3)]); //=> [0.25, 0.75]
1209
+ * softmax(plain, [Infinity, 0, Infinity]); //=> [0.5, 0, 0.5]
1210
+ * ```
1211
+ */
1212
+ function softmax(A, xs) {
1213
+ if (xs.length === 0) throw new RangeError("softmax: empty list");
1214
+ const m = largest(A, xs);
1215
+ if (m === Infinity || m === -Infinity) {
1216
+ const k = xs.filter((x) => A.value(x) === m).length;
1217
+ return xs.map((x) => A.constant(A.value(x) === m ? 1 / k : 0));
1218
+ }
1219
+ const c = A.constant(m);
1220
+ const es = xs.map((x) => A.exp(A.sub(x, c)));
1221
+ const inv = A.recip(sum(A, es));
1222
+ return es.map((e) => A.mul(e, inv));
1223
+ }
1224
+ // src/convenience.ts
1225
+ /**
1226
+ * Evaluate `f` at `at` on plain numbers, recording nothing. The value is bit-for-bit the one
1227
+ * {@link gradient} and {@link jvp} report.
1228
+ *
1229
+ * @example
1230
+ * ```ts
1231
+ * evaluate((A, [x]) => A.exp(x), [0]); //=> 1
1232
+ * ```
1233
+ */
1234
+ function evaluate(f, at) {
1235
+ return f(plain, at);
1236
+ }
1237
+ /**
1238
+ * The value and gradient of `f` at `at`, by reverse mode: one forward pass recording a tape and
1239
+ * one backward sweep, whatever the number of inputs.
1240
+ *
1241
+ * Pass `tape` to reuse its storage across calls in a loop; it is cleared first and left holding
1242
+ * this evaluation.
1243
+ *
1244
+ * @example
1245
+ * ```ts
1246
+ * gradient((A, [x, y]) => A.mul(x, y), [3, 4]); //=> { value: 12, gradient: [4, 3] }
1247
+ * ```
1248
+ */
1249
+ function gradient(f, at, tape = new Tape()) {
1250
+ tape.clear();
1251
+ const xs = tape.inputs(at);
1252
+ const y = f(tape, xs);
1253
+ tape.backward(y);
1254
+ return {
1255
+ value: tape.value(y),
1256
+ gradient: xs.map((x) => tape.adjoint(x))
1257
+ };
1258
+ }
1259
+ /**
1260
+ * The value of `f` at `at` and its derivative along `direction` (a Jacobian-vector product), by
1261
+ * forward mode: one pass, no tape. Throws if `direction` and `at` differ in length.
1262
+ *
1263
+ * @example
1264
+ * ```ts
1265
+ * jvp((A, [x, y]) => A.mul(x, y), [3, 4], [1, 0]); //=> { value: 12, derivative: 4 }
1266
+ * ```
1267
+ */
1268
+ function jvp(f, at, direction, forward = new Forward()) {
1269
+ if (direction.length !== at.length) throw new RangeError(`jvp: ${at.length} inputs but a direction of length ${direction.length}`);
1270
+ forward.clear();
1271
+ const y = f(forward, at.map((x, i) => forward.input(x, direction[i])));
1272
+ return {
1273
+ value: forward.value(y),
1274
+ derivative: forward.tangent(y)
1275
+ };
1276
+ }
1277
+ // src/check.ts
1278
+ /**
1279
+ * Default relative step for {@link finiteDifference}: about the cube root of machine epsilon,
1280
+ * which balances the central difference's `O(h^2)` truncation error against rounding error.
1281
+ */
1282
+ const DEFAULT_STEP = 6e-6;
1283
+ /**
1284
+ * The gradient of `f` at `at` by central differences on plain numbers:
1285
+ * `(f(x + h_i e_i) - f(x - h_i e_i)) / (2 h_i)` with `h_i = h * max(1, |at[i]|)`.
1286
+ * Costs two evaluations per input; expect agreement with the true gradient to roughly 1e-8
1287
+ * relative for a smooth, well-scaled function, and nothing useful across a kink or a branch.
1288
+ *
1289
+ * @param h - relative step, default {@link DEFAULT_STEP}.
1290
+ *
1291
+ * @example
1292
+ * ```ts
1293
+ * finiteDifference((A, [x]) => A.square(x), [3]); //=> ~[6]
1294
+ * ```
1295
+ */
1296
+ function finiteDifference(f, at, h = DEFAULT_STEP) {
1297
+ return centralDifferences(f, at, h).map((d) => d.slope);
1298
+ }
1299
+ /** Units in the last place of error allowed for in each evaluation of `f`. */
1300
+ const ROUNDING_ULPS = 4;
1301
+ /**
1302
+ * Each component's central difference, and an estimate of the error that rounding in the two
1303
+ * evaluations of `f` puts into it: `ROUNDING_ULPS * eps * (|f(x + h)| + |f(x - h)|) / (2 h)`.
1304
+ */
1305
+ function centralDifferences(f, at, h) {
1306
+ const x = at.slice();
1307
+ const point = x;
1308
+ return at.map((xi, i) => {
1309
+ const step = h * Math.max(1, Math.abs(xi));
1310
+ x[i] = xi + step;
1311
+ const up = f(plain, point);
1312
+ x[i] = xi - step;
1313
+ const down = f(plain, point);
1314
+ x[i] = xi;
1315
+ const span = xi + step - (xi - step);
1316
+ return {
1317
+ slope: (up - down) / span,
1318
+ roundoff: ROUNDING_ULPS * Number.EPSILON * (Math.abs(up) + Math.abs(down)) / span
1319
+ };
1320
+ });
1321
+ }
1322
+ /**
1323
+ * Compare `f`'s reverse-mode gradient at `at` with central finite differences. Component `i`
1324
+ * passes when `|analytic - numeric| <= atol + rtol * max(|analytic|, |numeric|) + roundoff`.
1325
+ *
1326
+ * `roundoff` allows for the rounding error of the finite difference itself, which grows with
1327
+ * `|f|` and shrinks with the step: `4 * eps * (|f(x + h)| + |f(x - h)|) / (2 h)`, four units in the
1328
+ * last place for each evaluation. Without it an exact gradient of a function whose value is large
1329
+ * next to its slope (`1e5 + 1e-3 * x`) would fail.
1330
+ *
1331
+ * Use it in your own tests to verify a function written against `Arithmetic`, typically at a few
1332
+ * random points away from kinks and branch boundaries, where finite differences are meaningless.
1333
+ *
1334
+ * @example
1335
+ * ```ts
1336
+ * const loss: ScalarFunction = (A, xs) => logsumexp(A, xs.map((x) => A.square(x)));
1337
+ * const r = checkGradient(loss, [0.3, -1.2, 2]);
1338
+ * if (!r.ok) {
1339
+ * const { index, analytic, numeric } = r.worst;
1340
+ * throw new Error(`component ${String(index)}: ${String(analytic)} vs ${String(numeric)}`);
1341
+ * }
1342
+ * r.ok; //=> true
1343
+ * ```
1344
+ */
1345
+ function checkGradient(f, at, options = {}) {
1346
+ const { h = DEFAULT_STEP, rtol = 1e-5, atol = 1e-8 } = options;
1347
+ const { value, gradient: analytic } = gradient(f, at);
1348
+ const differences = centralDifferences(f, at, h);
1349
+ const numeric = differences.map((d) => d.slope);
1350
+ let ok = true;
1351
+ let worst = {
1352
+ index: -1,
1353
+ analytic: NaN,
1354
+ numeric: NaN,
1355
+ error: 0,
1356
+ allowed: 0,
1357
+ roundoff: 0
1358
+ };
1359
+ let worstRatio = -1;
1360
+ for (let i = 0; i < analytic.length; i++) {
1361
+ const a = analytic[i];
1362
+ const n = numeric[i];
1363
+ const roundoff = differences[i].roundoff;
1364
+ const error = Math.abs(a - n);
1365
+ const allowed = atol + rtol * Math.max(Math.abs(a), Math.abs(n)) + roundoff;
1366
+ const ratio = Number.isNaN(error) ? Infinity : error === 0 ? 0 : error / allowed;
1367
+ if (!(error <= allowed)) ok = false;
1368
+ if (ratio > worstRatio) {
1369
+ worstRatio = ratio;
1370
+ worst = {
1371
+ index: i,
1372
+ analytic: a,
1373
+ numeric: n,
1374
+ error,
1375
+ allowed,
1376
+ roundoff
1377
+ };
1378
+ }
1379
+ }
1380
+ return {
1381
+ ok,
1382
+ value,
1383
+ analytic,
1384
+ numeric,
1385
+ worst
1386
+ };
1387
+ }
1388
+ export { DEFAULT_STEP, Forward, Tape, binaryNames, checkGradient, dot, evaluate, finiteDifference, gradient, jvp, logsumexp, plain, primitives, softmax, sum, unaryNames };
1389
+
1390
+ //# sourceMappingURL=index.js.map