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.
@@ -0,0 +1,868 @@
1
+ /**
2
+ * Scalar arithmetic over a value type `T`. Write a function once against this interface, generic
3
+ * in `T`, and run it three ways: on plain numbers ({@link plain}), recording a tape for gradients
4
+ * ({@link Tape}), or carrying tangents for directional derivatives ({@link Forward}).
5
+ *
6
+ * Every operation on a `T` must go through these methods. A `T` is opaque: for `Tape` and
7
+ * `Forward` it is an index into storage, and `Math.exp(x)` or `x * y` on it computes nonsense
8
+ * (the branded types make most such mistakes compile errors). Numbers enter through
9
+ * {@link Arithmetic.constant} and leave through {@link Arithmetic.value}.
10
+ *
11
+ * The values every implementation computes come from the same {@link primitives} table, so a
12
+ * function's value is bit-for-bit identical under all of them. Subgradient conventions (`abs` at
13
+ * `0`, ties in `max`/`min`, `pow` with a non-positive base) are documented there.
14
+ *
15
+ * @typeParam T - the value type: `number` for `plain`, `Var` for `Tape`, `Dual` for `Forward`.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { type Arithmetic, plain, gradient } from 'adjointly';
20
+ *
21
+ * // f(x, y) = x * e^y + 3
22
+ * function f<T>(A: Arithmetic<T>, [x, y]: readonly [T, T]): T {
23
+ * return A.addc(A.mul(x, A.exp(y)), 3);
24
+ * }
25
+ * f(plain, [2, 0]); //=> 5
26
+ * gradient(f, [2, 0]); //=> { value: 5, gradient: [1, 2] }
27
+ * ```
28
+ */
29
+ interface Arithmetic<T> {
30
+ /**
31
+ * Lift a number into `T` as a constant: its derivative with respect to every input is zero.
32
+ * On a `Tape` it still appends a node (constants are leaves of the tape).
33
+ */
34
+ constant(x: number): T;
35
+ /**
36
+ * The current numeric value of `a`. Use it to branch (`if (A.value(x) > 0)`), to log, or to
37
+ * return a result. The number it returns carries no derivative information: anything computed
38
+ * from it with ordinary arithmetic is a constant as far as differentiation is concerned.
39
+ */
40
+ value(a: T): number;
41
+ /** `-a`. */
42
+ neg(a: T): T;
43
+ /** `a + b`. */
44
+ add(a: T, b: T): T;
45
+ /** `a - b`. */
46
+ sub(a: T, b: T): T;
47
+ /** `a * b`. */
48
+ mul(a: T, b: T): T;
49
+ /** `a / b`. */
50
+ div(a: T, b: T): T;
51
+ /** `1 / a`. */
52
+ recip(a: T): T;
53
+ /** `a * a`, one node instead of a `mul` of `a` with itself. */
54
+ square(a: T): T;
55
+ /** `sqrt(a)`; its derivative at `0` is `+Infinity`. */
56
+ sqrt(a: T): T;
57
+ /** `e^a`. */
58
+ exp(a: T): T;
59
+ /** `e^a - 1`, accurate for small `a`. */
60
+ expm1(a: T): T;
61
+ /** Natural logarithm. */
62
+ log(a: T): T;
63
+ /** `log(1 + a)`, accurate for small `a`. */
64
+ log1p(a: T): T;
65
+ /**
66
+ * `a ** b`, differentiable in both arguments. The derivative with respect to `b` is `0` wherever
67
+ * `a <= 0`; the derivative with respect to `a` is `0` wherever `b === 0`. For a fixed numeric
68
+ * exponent use {@link Arithmetic.powc}; for `b = 2` prefer {@link Arithmetic.square}, for
69
+ * `b = 0.5` prefer {@link Arithmetic.sqrt}.
70
+ */
71
+ pow(a: T, b: T): T;
72
+ /** `|a|`; its derivative at `0` is `0`. */
73
+ abs(a: T): T;
74
+ /** The smaller of `a` and `b`; on a tie the derivative flows to `a`. */
75
+ min(a: T, b: T): T;
76
+ /** The larger of `a` and `b`; on a tie the derivative flows to `a`. */
77
+ max(a: T, b: T): T;
78
+ /** Sine. */
79
+ sin(a: T): T;
80
+ /** Cosine. */
81
+ cos(a: T): T;
82
+ /** Hyperbolic tangent. */
83
+ tanh(a: T): T;
84
+ /** Logistic sigmoid `1 / (1 + e^-a)`, stable for any `a`. */
85
+ sigmoid(a: T): T;
86
+ /** Softplus `log(1 + e^a)`, stable for any `a`; its derivative is `sigmoid(a)`. */
87
+ softplus(a: T): T;
88
+ /**
89
+ * `a + c` for a plain number `c`: `add(a, constant(c))` without the constant's tape node. Same
90
+ * value and derivative, bit for bit.
91
+ */
92
+ addc(a: T, c: number): T;
93
+ /** `a - c` for a plain number `c`; `sub(a, constant(c))` in one node. */
94
+ subc(a: T, c: number): T;
95
+ /** `c - a` for a plain number `c`; `sub(constant(c), a)` in one node. */
96
+ csub(c: number, a: T): T;
97
+ /** `a * c` for a plain number `c`; `mul(a, constant(c))` in one node. */
98
+ mulc(a: T, c: number): T;
99
+ /** `a / c` for a plain number `c`; `div(a, constant(c))` in one node. */
100
+ divc(a: T, c: number): T;
101
+ /** `c / a` for a plain number `c`; `div(constant(c), a)` in one node. */
102
+ cdiv(c: number, a: T): T;
103
+ /**
104
+ * `a ** c` for a plain number `c`; `pow(a, constant(c))` in one node, with the same conventions
105
+ * (the derivative is `0` when `c === 0`).
106
+ */
107
+ powc(a: T, c: number): T;
108
+ }
109
+ /**
110
+ * The inputs of a {@link ScalarFunction}: one `T` for each number in the point `X`. For a point
111
+ * written as a literal, `[3, 4]`, that is the tuple `readonly [T, T]`, so destructured inputs need
112
+ * no non-null assertions; for a `number[]` it is `readonly T[]`.
113
+ */
114
+ type Inputs<T, X extends readonly number[]> = { readonly [K in keyof X]: T; };
115
+ /**
116
+ * A function of several inputs written once against {@link Arithmetic}, the shape the
117
+ * conveniences ({@link gradient}, {@link jvp}, {@link evaluate}, {@link checkGradient}) accept.
118
+ * The input array has exactly as many elements as the point it is evaluated at; `X` is the type of
119
+ * that point, so `ScalarFunction<[number, number]>` takes two inputs.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const rosenbrock: ScalarFunction<[number, number]> = (A, [x, y]) =>
124
+ * A.add(
125
+ * A.square(A.csub(1, x)),
126
+ * A.mulc(A.square(A.sub(y, A.square(x))), 100),
127
+ * );
128
+ * evaluate(rosenbrock, [1, 1]); //=> 0
129
+ * gradient(rosenbrock, [0, 0]).gradient; //=> [-2, 0]
130
+ * ```
131
+ */
132
+ type ScalarFunction<X extends readonly number[] = readonly number[]> = <T>(A: Arithmetic<T>, xs: Inputs<T, X>) => T;
133
+ /**
134
+ * {@link Arithmetic} on ordinary numbers: computes values only, with nothing recorded. Use it to
135
+ * evaluate a differentiable function at full speed when no derivative is needed.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * import { plain } from 'adjointly';
140
+ * plain.add(plain.constant(1), plain.exp(0)); //=> 2
141
+ * ```
142
+ */
143
+ export declare const plain: Arithmetic<number>;
144
+ /** A primitive of one argument: `y = value(x)`, `dy/dx = deriv(x, y)`. */
145
+ interface UnaryPrimitive {
146
+ /** The primitive's name, equal to its key in {@link primitives} and its method on `Arithmetic`. */
147
+ readonly name: string;
148
+ /** The value of the operation at `x`. */
149
+ readonly value: (x: number) => number;
150
+ /** The derivative `dy/dx` at `x`, given `y = value(x)`. */
151
+ readonly deriv: (x: number, y: number) => number;
152
+ }
153
+ /** A primitive of two arguments: `y = value(a, b)`, with partials `da = dy/da` and `db = dy/db`. */
154
+ interface BinaryPrimitive {
155
+ /** The primitive's name, equal to its key in {@link primitives} and its method on `Arithmetic`. */
156
+ readonly name: string;
157
+ /** The value of the operation at `(a, b)`. */
158
+ readonly value: (a: number, b: number) => number;
159
+ /** The partial derivative `dy/da` at `(a, b)`, given `y = value(a, b)`. */
160
+ readonly da: (a: number, b: number, y: number) => number;
161
+ /** The partial derivative `dy/db` at `(a, b)`, given `y = value(a, b)`. */
162
+ readonly db: (a: number, b: number, y: number) => number;
163
+ }
164
+ /**
165
+ * Every primitive, keyed by name. Subgradient conventions at points where the derivative does not
166
+ * exist are part of the contract:
167
+ *
168
+ * | primitive | convention |
169
+ * |---|---|
170
+ * | `abs` | derivative at `0` is `0` |
171
+ * | `max`, `min` | on a tie (`a === b`) the whole derivative goes to the first argument `a` |
172
+ * | `pow` | `d/db` is `0` wherever `a <= 0`; `d/da` is `0` wherever `b === 0` |
173
+ * | `sqrt` | derivative at `0` (and `-0`) is `+Infinity` (the true one-sided limit) |
174
+ * | `max`, `min` | if either argument is `NaN`, the value and both partials are `NaN`, as for `abs` |
175
+ *
176
+ * Everywhere else the partials are the ordinary analytic derivatives, and outside a function's
177
+ * domain (`log` of a negative, `sqrt` of a negative) the value and partials are whatever IEEE
178
+ * arithmetic produces, usually `NaN`.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * import { primitives } from 'adjointly';
183
+ * const x = 0.5;
184
+ * const y = primitives.tanh.value(x); //=> tanh(0.5)
185
+ * const dy = primitives.tanh.deriv(x, y); //=> 1 - tanh(0.5) ** 2
186
+ * ```
187
+ */
188
+ export declare const primitives: Readonly<{
189
+ /** `-x`. */
190
+ neg: UnaryPrimitive;
191
+ /** `a + b`. */
192
+ add: BinaryPrimitive;
193
+ /** `a - b`. */
194
+ sub: BinaryPrimitive;
195
+ /** `a * b`. */
196
+ mul: BinaryPrimitive;
197
+ /** `a / b`. */
198
+ div: BinaryPrimitive;
199
+ /** `1 / x`. */
200
+ recip: UnaryPrimitive;
201
+ /** `x * x`. */
202
+ square: UnaryPrimitive;
203
+ /** `sqrt(x)`; derivative `+Infinity` at `0` and at `-0`. */
204
+ sqrt: UnaryPrimitive;
205
+ /** `e^x`. */
206
+ exp: UnaryPrimitive;
207
+ /** `e^x - 1`, accurate near `0`. */
208
+ expm1: UnaryPrimitive;
209
+ /** Natural logarithm. */
210
+ log: UnaryPrimitive;
211
+ /** `log(1 + x)`, accurate near `0`. */
212
+ log1p: UnaryPrimitive;
213
+ /**
214
+ * `a ** b`, differentiable in both. `d/da = b * a^(b-1)`, taken as `0` when `b === 0` (so
215
+ * `0^0` does not produce `0 * Infinity`). `d/db = a^b * log(a)` for `a > 0` and `0` for
216
+ * `a <= 0`, where the real power is not differentiable in the exponent.
217
+ */
218
+ pow: BinaryPrimitive;
219
+ /** `|x|`; derivative `0` at `0`. */
220
+ abs: UnaryPrimitive;
221
+ /**
222
+ * The smaller of `a` and `b`; on a tie the derivative goes to `a`. If either is `NaN`, the value
223
+ * and both partials are `NaN`.
224
+ */
225
+ min: BinaryPrimitive;
226
+ /**
227
+ * The larger of `a` and `b`; on a tie the derivative goes to `a`. If either is `NaN`, the value
228
+ * and both partials are `NaN`.
229
+ */
230
+ max: BinaryPrimitive;
231
+ /** Sine. */
232
+ sin: UnaryPrimitive;
233
+ /** Cosine. */
234
+ cos: UnaryPrimitive;
235
+ /** Hyperbolic tangent. */
236
+ tanh: UnaryPrimitive;
237
+ /** Logistic sigmoid `1 / (1 + e^-x)`, stable for any `x`. */
238
+ sigmoid: UnaryPrimitive;
239
+ /**
240
+ * Softplus `log(1 + e^x)`, stable for any `x`: computed as `x + log1p(e^-x)` for positive `x`
241
+ * and `log1p(e^x)` otherwise, so it neither overflows nor rounds to `0` for very negative `x`.
242
+ * Its derivative is `sigmoid(x)`.
243
+ */
244
+ softplus: UnaryPrimitive;
245
+ }>;
246
+ /** Names of the one-argument primitives. */
247
+ type UnaryName = { [K in keyof typeof primitives]: (typeof primitives)[K] extends {
248
+ deriv: unknown;
249
+ } ? K : never; }[keyof typeof primitives];
250
+ /** Names of the two-argument primitives. */
251
+ type BinaryName = Exclude<keyof typeof primitives, UnaryName>;
252
+ /** The names of the one-argument primitives, in table order. */
253
+ export declare const unaryNames: readonly UnaryName[];
254
+ /** The names of the two-argument primitives, in table order. */
255
+ export declare const binaryNames: readonly BinaryName[];
256
+ declare const varBrand: unique symbol;
257
+ declare const markBrand: unique symbol;
258
+ /**
259
+ * A variable on a {@link Tape}: at run time a plain integer, the index of its node. The brand makes
260
+ * it a compile-time error to pass a raw number where a `Var` is expected, or to use a `Var` from a
261
+ * {@link Forward} where a tape's is expected; numbers enter a tape through
262
+ * {@link Tape.input} or {@link Tape.constant}.
263
+ *
264
+ * A `Var` is only meaningful on the tape that created it, and only until that tape is cleared or
265
+ * rewound past it. Neither is checked at run time.
266
+ *
267
+ * Because it is a number underneath, `x + y` on two `Var`s type-checks (TypeScript allows
268
+ * arithmetic on number subtypes) and silently adds two indices. Always use `tape.add(x, y)`.
269
+ */
270
+ type Var = number & {
271
+ readonly [varBrand]: 'adjointly.Var';
272
+ };
273
+ /** A checkpoint on a {@link Tape}, returned by {@link Tape.mark} and consumed by {@link Tape.rewind}. */
274
+ type Mark = number & {
275
+ readonly [markBrand]: 'adjointly.Mark';
276
+ };
277
+ /** Options for constructing a {@link Tape}. */
278
+ interface TapeOptions {
279
+ /**
280
+ * Initial capacity in nodes, a positive integer (default 1024; anything else throws a
281
+ * `RangeError`). The tape doubles whenever it fills, so this only matters to avoid the first few
282
+ * reallocations when the size of the computation is known.
283
+ */
284
+ readonly capacity?: number;
285
+ /**
286
+ * Check every operand (default `false`). Each method then verifies that its variables are live
287
+ * variables of this Tape, and throws a `RangeError` naming the method and the argument for a
288
+ * plain number, a variable from before `clear()` or `rewind()`, or one from another Tape or Forward.
289
+ * Off, nothing is checked and nothing is slower: a debug Tape is a separate set of methods on
290
+ * that one instance. Its variables are no longer small integers (they encode which recording
291
+ * they belong to), which is invisible unless they are used as numbers.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * const t = new Tape({ debug: true });
296
+ * t.input(5);
297
+ * let caught = '';
298
+ * try {
299
+ * t.exp(1 as Var);
300
+ * } catch (e) {
301
+ * caught = (e as Error).message.split(',')[0]!;
302
+ * }
303
+ * caught; //=> 'Tape.exp: argument 1 is the plain number 1'
304
+ * ```
305
+ */
306
+ readonly debug?: boolean;
307
+ }
308
+ /**
309
+ * Reverse-mode automatic differentiation: an {@link Arithmetic} over {@link Var} that records
310
+ * every operation as a node on a tape, then computes the derivative of one output with respect to
311
+ * every node in a single backward sweep.
312
+ *
313
+ * Each node stores its value, up to two parent indices and the local partial derivatives with
314
+ * respect to them, computed when the node is recorded, and its adjoint: six typed arrays, 40
315
+ * bytes a node. They double when full, so recording does not allocate once the tape has grown to
316
+ * the size of the computation, and a tape reused across evaluations (via {@link Tape.clear} or
317
+ * {@link Tape.rewind}) never allocates again.
318
+ *
319
+ * The backward sweep visits nodes from the output down to the first, adding
320
+ * `partial * adjoint(child)` into each parent's adjoint, under the zero rule: if the partial or
321
+ * the adjoint is exactly 0 the term is 0, even when the other is infinite or `NaN`, so an unused
322
+ * `sqrt(0)` or `log(0)` does not poison the gradient.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * import { Tape } from 'adjointly';
327
+ * const t = new Tape();
328
+ * const x = t.input(2);
329
+ * const y = t.input(3);
330
+ * const f = t.mul(x, t.sin(y)); // x * sin(y)
331
+ * t.backward(f);
332
+ * t.adjoint(x); //=> sin(3)
333
+ * t.adjoint(y); //=> 2 * cos(3)
334
+ * ```
335
+ */
336
+ export declare class Tape implements Arithmetic<Var> {
337
+ private val;
338
+ private pa;
339
+ private pb;
340
+ private da;
341
+ private db;
342
+ private adj;
343
+ /** The next free slot; nodes occupy slots 1 to n - 1. */
344
+ private n;
345
+ /** An empty tape; see {@link TapeOptions}. */
346
+ constructor(options?: TapeOptions);
347
+ /** The number of nodes recorded so far. */
348
+ get size(): number;
349
+ /** The number of nodes the tape can hold before it next grows. */
350
+ get capacity(): number;
351
+ /** Forget every node, keeping the storage. Every `Var` previously issued becomes invalid. */
352
+ clear(): void;
353
+ /**
354
+ * A checkpoint at the current end of the tape. {@link Tape.rewind} to it discards everything
355
+ * recorded after it and keeps everything before, so a shared prefix (inputs, parameters,
356
+ * precomputed features) is recorded once and reused across many evaluations.
357
+ *
358
+ * @example
359
+ * ```ts
360
+ * const t = new Tape();
361
+ * const w = t.input(0.5);
362
+ * const m = t.mark();
363
+ * let grad = 0;
364
+ * for (const x of [1, 2, 3]) {
365
+ * t.rewind(m); // drop the previous sample's nodes, keep w
366
+ * const loss = t.square(t.subc(t.mulc(w, x), 1));
367
+ * t.backward(loss);
368
+ * grad += t.adjoint(w);
369
+ * }
370
+ * grad; //=> 2 * ((0.5 - 1) * 1 + (1 - 1) * 2 + (1.5 - 1) * 3)
371
+ * t.size; //=> 4
372
+ * ```
373
+ */
374
+ mark(): Mark;
375
+ /**
376
+ * Truncate the tape back to `mark`. Vars created before the mark stay valid with their values;
377
+ * Vars created after it become invalid. Throws if `mark` is beyond the current end (the tape was
378
+ * already cleared or rewound further back).
379
+ */
380
+ rewind(mark: Mark): void;
381
+ /**
382
+ * Record an independent variable with value `x`. Identical to {@link Tape.constant} on the tape
383
+ * (both are leaves and both accumulate an adjoint); the separate name says which leaves you
384
+ * intend to read a gradient for.
385
+ */
386
+ input(x: number): Var;
387
+ /**
388
+ * Record several independent variables at once; see {@link Tape.input}. For a tuple of numbers
389
+ * the result is a tuple of the same length, so it destructures without non-null assertions.
390
+ *
391
+ * @example
392
+ * ```ts
393
+ * const t = new Tape();
394
+ * const [x, y] = t.inputs([2, 3]);
395
+ * t.value(t.mul(x, y)); //=> 6
396
+ * ```
397
+ */
398
+ inputs<const X extends readonly number[]>(xs: X): { -readonly [K in keyof X]: Var; };
399
+ inputs(xs: ArrayLike<number>): Var[];
400
+ /**
401
+ * Lift a number into `T` as a constant: its derivative with respect to every input is zero.
402
+ * On a `Tape` it still appends a node (constants are leaves of the tape).
403
+ */
404
+ constant(x: number): Var;
405
+ /**
406
+ * The current numeric value of `a`. Use it to branch (`if (A.value(x) > 0)`), to log, or to
407
+ * return a result. The number it returns carries no derivative information: anything computed
408
+ * from it with ordinary arithmetic is a constant as far as differentiation is concerned.
409
+ */
410
+ value(a: Var): number;
411
+ /**
412
+ * Run one backward sweep from `output`, seeding its adjoint with `seed` (default 1). Afterwards
413
+ * {@link Tape.adjoint} of any node `v` is `seed * d(output)/d(v)`. Every call starts from zero
414
+ * adjoints, so call it once per output whose gradient you want; the recorded tape is not
415
+ * changed and can be swept any number of times.
416
+ */
417
+ backward(output: Var, seed?: number): void;
418
+ /**
419
+ * Vector-Jacobian product: one backward sweep seeded with `weights[k]` at `outputs[k]`.
420
+ * Afterwards {@link Tape.adjoint} of `v` is `sum_k weights[k] * d(outputs[k])/d(v)`. An output
421
+ * listed twice gets the sum of its weights; with no outputs every adjoint is zero.
422
+ *
423
+ * @example
424
+ * ```ts
425
+ * const t = new Tape();
426
+ * const x = t.input(1);
427
+ * const f = t.square(x); // df/dx = 2x
428
+ * const g = t.exp(x); // dg/dx = e^x
429
+ * t.vjp([f, g], [2, 3]); // gradient of 2f + 3g in one sweep
430
+ * t.adjoint(x); //=> 2 * 2 + 3 * E
431
+ * ```
432
+ */
433
+ vjp(outputs: readonly Var[], weights: ArrayLike<number>): void;
434
+ /**
435
+ * The adjoint of `v` from the last {@link Tape.backward} or {@link Tape.vjp}: the derivative of
436
+ * the seeded output(s) with respect to `v`. Zero for a node the output does not depend on.
437
+ * Meaningless for a node recorded after the output of that sweep.
438
+ */
439
+ adjoint(v: Var): number;
440
+ /**
441
+ * Read the adjoints of `vars` into `out` (allocated if not given) and return it: the gradient
442
+ * restricted to those variables, in their order.
443
+ */
444
+ adjoints(vars: readonly Var[], out?: Float64Array): Float64Array;
445
+ /**
446
+ * `backward(output)` then `adjoints(inputs, out)`: the gradient of `output` with respect to
447
+ * `inputs`, in their order.
448
+ *
449
+ * @example
450
+ * ```ts
451
+ * const t = new Tape();
452
+ * const [x, y] = t.inputs([1, 2]);
453
+ * t.gradient(t.mul(x, y), [x, y]); //=> [2, 1]
454
+ * ```
455
+ */
456
+ gradient(output: Var, inputs: readonly Var[], out?: Float64Array): Float64Array;
457
+ private sweep;
458
+ private push;
459
+ private grow;
460
+ /** `-a`. */
461
+ neg(a: Var): Var;
462
+ /** `a + b`. */
463
+ add(a: Var, b: Var): Var;
464
+ /** `a - b`. */
465
+ sub(a: Var, b: Var): Var;
466
+ /** `a * b`. */
467
+ mul(a: Var, b: Var): Var;
468
+ /** `a / b`. */
469
+ div(a: Var, b: Var): Var;
470
+ /** `1 / a`. */
471
+ recip(a: Var): Var;
472
+ /** `a * a`, one node instead of a `mul` of `a` with itself. */
473
+ square(a: Var): Var;
474
+ /** `sqrt(a)`; its derivative at `0` is `+Infinity`. */
475
+ sqrt(a: Var): Var;
476
+ /** `e^a`. */
477
+ exp(a: Var): Var;
478
+ /** `e^a - 1`, accurate for small `a`. */
479
+ expm1(a: Var): Var;
480
+ /** Natural logarithm. */
481
+ log(a: Var): Var;
482
+ /** `log(1 + a)`, accurate for small `a`. */
483
+ log1p(a: Var): Var;
484
+ /**
485
+ * `a ** b`, differentiable in both arguments. The derivative with respect to `b` is `0` wherever
486
+ * `a <= 0`; the derivative with respect to `a` is `0` wherever `b === 0`. For a fixed numeric
487
+ * exponent use {@link Arithmetic.powc}; for `b = 2` prefer {@link Arithmetic.square}, for
488
+ * `b = 0.5` prefer {@link Arithmetic.sqrt}.
489
+ */
490
+ pow(a: Var, b: Var): Var;
491
+ /** `|a|`; its derivative at `0` is `0`. */
492
+ abs(a: Var): Var;
493
+ /** The smaller of `a` and `b`; on a tie the derivative flows to `a`. */
494
+ min(a: Var, b: Var): Var;
495
+ /** The larger of `a` and `b`; on a tie the derivative flows to `a`. */
496
+ max(a: Var, b: Var): Var;
497
+ /** Sine. */
498
+ sin(a: Var): Var;
499
+ /** Cosine. */
500
+ cos(a: Var): Var;
501
+ /** Hyperbolic tangent. */
502
+ tanh(a: Var): Var;
503
+ /** Logistic sigmoid `1 / (1 + e^-a)`, stable for any `a`. */
504
+ sigmoid(a: Var): Var;
505
+ /** Softplus `log(1 + e^a)`, stable for any `a`; its derivative is `sigmoid(a)`. */
506
+ softplus(a: Var): Var;
507
+ /**
508
+ * `a + c` for a plain number `c`: `add(a, constant(c))` without the constant's tape node. Same
509
+ * value and derivative, bit for bit.
510
+ */
511
+ addc(a: Var, c: number): Var;
512
+ /** `a - c` for a plain number `c`; `sub(a, constant(c))` in one node. */
513
+ subc(a: Var, c: number): Var;
514
+ /** `c - a` for a plain number `c`; `sub(constant(c), a)` in one node. */
515
+ csub(c: number, a: Var): Var;
516
+ /** `a * c` for a plain number `c`; `mul(a, constant(c))` in one node. */
517
+ mulc(a: Var, c: number): Var;
518
+ /** `a / c` for a plain number `c`; `div(a, constant(c))` in one node. */
519
+ divc(a: Var, c: number): Var;
520
+ /** `c / a` for a plain number `c`; `div(constant(c), a)` in one node. */
521
+ cdiv(c: number, a: Var): Var;
522
+ /**
523
+ * `a ** c` for a plain number `c`; `pow(a, constant(c))` in one node, with the same conventions
524
+ * (the derivative is `0` when `c === 0`).
525
+ */
526
+ powc(a: Var, c: number): Var;
527
+ }
528
+ declare const dualBrand: unique symbol;
529
+ /**
530
+ * A value in a {@link Forward} computation: at run time a plain integer, the index of its slot.
531
+ * Branded separately from `Var`, so a forward value cannot be passed to a `Tape` or vice versa, and
532
+ * a raw number cannot be passed where a `Dual` is expected.
533
+ *
534
+ * Valid only on the `Forward` that created it, and only until that `Forward` is cleared.
535
+ */
536
+ type Dual = number & {
537
+ readonly [dualBrand]: 'adjointly.Dual';
538
+ };
539
+ /** Options for constructing a {@link Forward}. */
540
+ interface ForwardOptions {
541
+ /**
542
+ * Initial capacity in values, a positive integer (default 1024; anything else throws a
543
+ * `RangeError`); storage doubles whenever it fills.
544
+ */
545
+ readonly capacity?: number;
546
+ /**
547
+ * Check every operand (default `false`). Each method then verifies that its variables are live
548
+ * variables of this Forward, and throws a `RangeError` naming the method and the argument for a
549
+ * plain number, a variable from before `clear()`, or one from another Tape or Forward.
550
+ * Off, nothing is checked and nothing is slower: a debug Forward is a separate set of methods on
551
+ * that one instance. Its variables are no longer small integers (they encode which recording
552
+ * they belong to), which is invisible unless they are used as numbers.
553
+ *
554
+ * @example
555
+ * ```ts
556
+ * const t = new Forward({ debug: true });
557
+ * t.input(5, 1);
558
+ * let caught = '';
559
+ * try {
560
+ * t.exp(1 as Dual);
561
+ * } catch (e) {
562
+ * caught = (e as Error).message.split(',')[0]!;
563
+ * }
564
+ * caught; //=> 'Forward.exp: argument 1 is the plain number 1'
565
+ * ```
566
+ */
567
+ readonly debug?: boolean;
568
+ }
569
+ /**
570
+ * Forward-mode automatic differentiation: an {@link Arithmetic} over {@link Dual} where every value
571
+ * carries a tangent, its derivative along one direction chosen when the inputs are created. One
572
+ * evaluation gives the directional derivative `grad f(x) . v` (a Jacobian-vector product) for every
573
+ * output at once, with no backward sweep and nothing kept but a value and a tangent per slot.
574
+ *
575
+ * Tangents propagate as `t(y) = da * t(a) + db * t(b)` using the same partials the {@link Tape}
576
+ * records, under the same zero rule: if a partial or a tangent is exactly 0 the term is 0, even when
577
+ * the other is infinite or `NaN`, so an infinite partial such as `sqrt` at `0` does not turn a zero
578
+ * tangent into `NaN`.
579
+ *
580
+ * Use forward mode when there are few inputs (or one direction) and many outputs; use the tape
581
+ * when there are many inputs and one output.
582
+ *
583
+ * @example
584
+ * ```ts
585
+ * import { Forward } from 'adjointly';
586
+ * const F = new Forward();
587
+ * const x = F.input(2, 1); // seed: direction (1, 0)
588
+ * const y = F.input(3, 0);
589
+ * const f = F.mul(x, F.sin(y)); // x * sin(y)
590
+ * F.value(f); //=> 2 * sin(3)
591
+ * F.tangent(f); //=> sin(3)
592
+ * ```
593
+ */
594
+ export declare class Forward implements Arithmetic<Dual> {
595
+ private val;
596
+ private tan;
597
+ private n;
598
+ /** An empty forward-mode context; see {@link ForwardOptions}. */
599
+ constructor(options?: ForwardOptions);
600
+ /** The number of values created so far. */
601
+ get size(): number;
602
+ /** Forget every value, keeping the storage. Every `Dual` previously issued becomes invalid. */
603
+ clear(): void;
604
+ /** An input with value `x` and tangent (seed) `tangent`: its component of the direction. */
605
+ input(x: number, tangent: number): Dual;
606
+ /**
607
+ * Lift a number into `T` as a constant: its derivative with respect to every input is zero.
608
+ * On a `Tape` it still appends a node (constants are leaves of the tape).
609
+ */
610
+ constant(x: number): Dual;
611
+ /**
612
+ * The current numeric value of `a`. Use it to branch (`if (A.value(x) > 0)`), to log, or to
613
+ * return a result. The number it returns carries no derivative information: anything computed
614
+ * from it with ordinary arithmetic is a constant as far as differentiation is concerned.
615
+ */
616
+ value(a: Dual): number;
617
+ /** The tangent of `a`: its derivative along the direction the inputs were seeded with. */
618
+ tangent(a: Dual): number;
619
+ private push;
620
+ private grow;
621
+ /** `-a`. */
622
+ neg(a: Dual): Dual;
623
+ /** `a + b`. */
624
+ add(a: Dual, b: Dual): Dual;
625
+ /** `a - b`. */
626
+ sub(a: Dual, b: Dual): Dual;
627
+ /** `a * b`. */
628
+ mul(a: Dual, b: Dual): Dual;
629
+ /** `a / b`. */
630
+ div(a: Dual, b: Dual): Dual;
631
+ /** `1 / a`. */
632
+ recip(a: Dual): Dual;
633
+ /** `a * a`, one node instead of a `mul` of `a` with itself. */
634
+ square(a: Dual): Dual;
635
+ /** `sqrt(a)`; its derivative at `0` is `+Infinity`. */
636
+ sqrt(a: Dual): Dual;
637
+ /** `e^a`. */
638
+ exp(a: Dual): Dual;
639
+ /** `e^a - 1`, accurate for small `a`. */
640
+ expm1(a: Dual): Dual;
641
+ /** Natural logarithm. */
642
+ log(a: Dual): Dual;
643
+ /** `log(1 + a)`, accurate for small `a`. */
644
+ log1p(a: Dual): Dual;
645
+ /**
646
+ * `a ** b`, differentiable in both arguments. The derivative with respect to `b` is `0` wherever
647
+ * `a <= 0`; the derivative with respect to `a` is `0` wherever `b === 0`. For a fixed numeric
648
+ * exponent use {@link Arithmetic.powc}; for `b = 2` prefer {@link Arithmetic.square}, for
649
+ * `b = 0.5` prefer {@link Arithmetic.sqrt}.
650
+ */
651
+ pow(a: Dual, b: Dual): Dual;
652
+ /** `|a|`; its derivative at `0` is `0`. */
653
+ abs(a: Dual): Dual;
654
+ /** The smaller of `a` and `b`; on a tie the derivative flows to `a`. */
655
+ min(a: Dual, b: Dual): Dual;
656
+ /** The larger of `a` and `b`; on a tie the derivative flows to `a`. */
657
+ max(a: Dual, b: Dual): Dual;
658
+ /** Sine. */
659
+ sin(a: Dual): Dual;
660
+ /** Cosine. */
661
+ cos(a: Dual): Dual;
662
+ /** Hyperbolic tangent. */
663
+ tanh(a: Dual): Dual;
664
+ /** Logistic sigmoid `1 / (1 + e^-a)`, stable for any `a`. */
665
+ sigmoid(a: Dual): Dual;
666
+ /** Softplus `log(1 + e^a)`, stable for any `a`; its derivative is `sigmoid(a)`. */
667
+ softplus(a: Dual): Dual;
668
+ /**
669
+ * `a + c` for a plain number `c`: `add(a, constant(c))` without the constant's tape node. Same
670
+ * value and derivative, bit for bit.
671
+ */
672
+ addc(a: Dual, c: number): Dual;
673
+ /** `a - c` for a plain number `c`; `sub(a, constant(c))` in one node. */
674
+ subc(a: Dual, c: number): Dual;
675
+ /** `c - a` for a plain number `c`; `sub(constant(c), a)` in one node. */
676
+ csub(c: number, a: Dual): Dual;
677
+ /** `a * c` for a plain number `c`; `mul(a, constant(c))` in one node. */
678
+ mulc(a: Dual, c: number): Dual;
679
+ /** `a / c` for a plain number `c`; `div(a, constant(c))` in one node. */
680
+ divc(a: Dual, c: number): Dual;
681
+ /** `c / a` for a plain number `c`; `div(constant(c), a)` in one node. */
682
+ cdiv(c: number, a: Dual): Dual;
683
+ /**
684
+ * `a ** c` for a plain number `c`; `pow(a, constant(c))` in one node, with the same conventions
685
+ * (the derivative is `0` when `c === 0`).
686
+ */
687
+ powc(a: Dual, c: number): Dual;
688
+ }
689
+ /**
690
+ * The sum of `xs`, added left to right; `A.constant(0)` for an empty list.
691
+ *
692
+ * @example
693
+ * ```ts
694
+ * sum(plain, [1, 2, 3]); //=> 6
695
+ * ```
696
+ */
697
+ export declare function sum<T>(A: Arithmetic<T>, xs: readonly T[]): T;
698
+ /**
699
+ * The dot product `sum_i xs[i] * ys[i]`; `A.constant(0)` for empty lists. Throws if the lengths
700
+ * differ.
701
+ *
702
+ * @example
703
+ * ```ts
704
+ * dot(plain, [1, 2], [3, 4]); //=> 11
705
+ * ```
706
+ */
707
+ export declare function dot<T>(A: Arithmetic<T>, xs: readonly T[], ys: readonly T[]): T;
708
+ /**
709
+ * `log(sum_i exp(xs[i]))`, computed stably as `m + log(sum_i exp(xs[i] - m))` where `m` is the
710
+ * largest value. `m` enters as a constant: the identity holds for any `m`, so its derivative
711
+ * through `m` is zero and leaving it out keeps the gradient exact (it is `softmax(xs)`).
712
+ * Throws on an empty list.
713
+ *
714
+ * Infinite inputs give the limit of the finite case. If the largest input is `+Infinity`, or every
715
+ * input is `-Infinity`, the value is that infinity and the gradient is shared equally by the inputs
716
+ * equal to it, `0` elsewhere; a `-Infinity` among finite inputs has weight `0`. A `NaN` input
717
+ * makes everything `NaN`.
718
+ *
719
+ * @example
720
+ * ```ts
721
+ * // The naive log(exp(1000) + exp(1000)) overflows to Infinity.
722
+ * logsumexp(plain, [1000, 1000]); //=> 1000 + log(2)
723
+ * gradient((A, xs) => logsumexp(A, xs), [Infinity, 0]).gradient; //=> [1, 0]
724
+ * ```
725
+ */
726
+ export declare function logsumexp<T>(A: Arithmetic<T>, xs: readonly T[]): T;
727
+ /**
728
+ * The softmax `exp(xs[i]) / sum_j exp(xs[j])`, computed stably by first subtracting the largest
729
+ * value as a constant (softmax is invariant to the shift, so the gradient stays exact). Throws on
730
+ * an empty list.
731
+ *
732
+ * Infinite inputs give the limit of the finite case. If the largest input is `+Infinity`, or every
733
+ * input is `-Infinity`, the weights are `1/k` on the `k` inputs equal to it and `0` elsewhere,
734
+ * returned as constants: the Jacobian there is `0`, which is also its limit. A `-Infinity` among
735
+ * finite inputs gets weight `0`. A `NaN` input makes every weight `NaN`.
736
+ *
737
+ * @example
738
+ * ```ts
739
+ * softmax(plain, [0, Math.log(3)]); //=> [0.25, 0.75]
740
+ * softmax(plain, [Infinity, 0, Infinity]); //=> [0.5, 0, 0.5]
741
+ * ```
742
+ */
743
+ export declare function softmax<T>(A: Arithmetic<T>, xs: readonly T[]): T[];
744
+ /** The result of {@link gradient}. */
745
+ interface GradientResult {
746
+ /** `f(at)`. */
747
+ readonly value: number;
748
+ /** `df/d(at[i])` for each `i`. */
749
+ readonly gradient: number[];
750
+ }
751
+ /** The result of {@link jvp}. */
752
+ interface JvpResult {
753
+ /** `f(at)`. */
754
+ readonly value: number;
755
+ /** The directional derivative `sum_i df/d(at[i]) * direction[i]`. */
756
+ readonly derivative: number;
757
+ }
758
+ /**
759
+ * Evaluate `f` at `at` on plain numbers, recording nothing. The value is bit-for-bit the one
760
+ * {@link gradient} and {@link jvp} report.
761
+ *
762
+ * @example
763
+ * ```ts
764
+ * evaluate((A, [x]) => A.exp(x), [0]); //=> 1
765
+ * ```
766
+ */
767
+ export declare function evaluate<const X extends readonly number[]>(f: ScalarFunction<X>, at: X): number;
768
+ /**
769
+ * The value and gradient of `f` at `at`, by reverse mode: one forward pass recording a tape and
770
+ * one backward sweep, whatever the number of inputs.
771
+ *
772
+ * Pass `tape` to reuse its storage across calls in a loop; it is cleared first and left holding
773
+ * this evaluation.
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * gradient((A, [x, y]) => A.mul(x, y), [3, 4]); //=> { value: 12, gradient: [4, 3] }
778
+ * ```
779
+ */
780
+ export declare function gradient<const X extends readonly number[]>(f: ScalarFunction<X>, at: X, tape?: Tape): GradientResult;
781
+ /**
782
+ * The value of `f` at `at` and its derivative along `direction` (a Jacobian-vector product), by
783
+ * forward mode: one pass, no tape. Throws if `direction` and `at` differ in length.
784
+ *
785
+ * @example
786
+ * ```ts
787
+ * jvp((A, [x, y]) => A.mul(x, y), [3, 4], [1, 0]); //=> { value: 12, derivative: 4 }
788
+ * ```
789
+ */
790
+ export declare function jvp<const X extends readonly number[]>(f: ScalarFunction<X>, at: X, direction: readonly number[], forward?: Forward): JvpResult;
791
+ /**
792
+ * Default relative step for {@link finiteDifference}: about the cube root of machine epsilon,
793
+ * which balances the central difference's `O(h^2)` truncation error against rounding error.
794
+ */
795
+ export declare const DEFAULT_STEP = 0.000006;
796
+ /**
797
+ * The gradient of `f` at `at` by central differences on plain numbers:
798
+ * `(f(x + h_i e_i) - f(x - h_i e_i)) / (2 h_i)` with `h_i = h * max(1, |at[i]|)`.
799
+ * Costs two evaluations per input; expect agreement with the true gradient to roughly 1e-8
800
+ * relative for a smooth, well-scaled function, and nothing useful across a kink or a branch.
801
+ *
802
+ * @param h - relative step, default {@link DEFAULT_STEP}.
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * finiteDifference((A, [x]) => A.square(x), [3]); //=> ~[6]
807
+ * ```
808
+ */
809
+ export declare function finiteDifference<const X extends readonly number[]>(f: ScalarFunction<X>, at: X, h?: number): number[];
810
+ /** Options for {@link checkGradient}. */
811
+ interface CheckGradientOptions {
812
+ /** Relative finite-difference step; default {@link DEFAULT_STEP}. */
813
+ readonly h?: number;
814
+ /** Relative tolerance; default `1e-5`. */
815
+ readonly rtol?: number;
816
+ /** Absolute tolerance; default `1e-8`. */
817
+ readonly atol?: number;
818
+ }
819
+ /** The result of {@link checkGradient}. */
820
+ interface GradientCheck {
821
+ /** Whether every component is within tolerance (and none is `NaN`). */
822
+ readonly ok: boolean;
823
+ /** `f(at)`. */
824
+ readonly value: number;
825
+ /** The gradient by reverse mode. */
826
+ readonly analytic: number[];
827
+ /** The gradient by central differences. */
828
+ readonly numeric: number[];
829
+ /**
830
+ * The component furthest out of tolerance relative to its allowance (`-1` index when `at` is
831
+ * empty). `error` is `|analytic - numeric|`; `roundoff` the estimated rounding error of the
832
+ * finite difference; `allowed` is `atol + rtol * max(|analytic|, |numeric|) + roundoff`.
833
+ */
834
+ readonly worst: {
835
+ readonly index: number;
836
+ readonly analytic: number;
837
+ readonly numeric: number;
838
+ readonly error: number;
839
+ readonly allowed: number;
840
+ readonly roundoff: number;
841
+ };
842
+ }
843
+ /**
844
+ * Compare `f`'s reverse-mode gradient at `at` with central finite differences. Component `i`
845
+ * passes when `|analytic - numeric| <= atol + rtol * max(|analytic|, |numeric|) + roundoff`.
846
+ *
847
+ * `roundoff` allows for the rounding error of the finite difference itself, which grows with
848
+ * `|f|` and shrinks with the step: `4 * eps * (|f(x + h)| + |f(x - h)|) / (2 h)`, four units in the
849
+ * last place for each evaluation. Without it an exact gradient of a function whose value is large
850
+ * next to its slope (`1e5 + 1e-3 * x`) would fail.
851
+ *
852
+ * Use it in your own tests to verify a function written against `Arithmetic`, typically at a few
853
+ * random points away from kinks and branch boundaries, where finite differences are meaningless.
854
+ *
855
+ * @example
856
+ * ```ts
857
+ * const loss: ScalarFunction = (A, xs) => logsumexp(A, xs.map((x) => A.square(x)));
858
+ * const r = checkGradient(loss, [0.3, -1.2, 2]);
859
+ * if (!r.ok) {
860
+ * const { index, analytic, numeric } = r.worst;
861
+ * throw new Error(`component ${String(index)}: ${String(analytic)} vs ${String(numeric)}`);
862
+ * }
863
+ * r.ok; //=> true
864
+ * ```
865
+ */
866
+ export declare function checkGradient<const X extends readonly number[]>(f: ScalarFunction<X>, at: X, options?: CheckGradientOptions): GradientCheck;
867
+ export type { Arithmetic, BinaryName, BinaryPrimitive, CheckGradientOptions, Dual, ForwardOptions, GradientCheck, GradientResult, Inputs, JvpResult, Mark, ScalarFunction, TapeOptions, UnaryName, UnaryPrimitive, Var };
868
+ //# sourceMappingURL=index.d.ts.map