@danielsimonjr/mathts-autograd 0.3.13 → 0.3.14
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/dual-tensor.d.ts +132 -0
- package/dist/dual-tensor.d.ts.map +1 -0
- package/dist/forward-grad.d.ts +45 -0
- package/dist/forward-grad.d.ts.map +1 -0
- package/dist/grad.d.ts +58 -0
- package/dist/grad.d.ts.map +1 -0
- package/dist/index.d.ts +14 -653
- package/dist/index.d.ts.map +1 -0
- package/dist/reverse-grad.d.ts +25 -0
- package/dist/reverse-grad.d.ts.map +1 -0
- package/dist/tape.d.ts +444 -0
- package/dist/tape.d.ts.map +1 -0
- package/package.json +58 -58
package/dist/index.d.ts
CHANGED
|
@@ -1,659 +1,20 @@
|
|
|
1
|
-
import { Tensor, Index } from '@danielsimonjr/mathts-tensor';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the wrapped Tensor. Arithmetic follows the dual-number rules:
|
|
7
|
-
* (a, a') + (b, b') = (a+b, a'+b')
|
|
8
|
-
* (a, a') * (b, b') = (a·b, a·b' + a'·b)
|
|
9
|
-
* (a, a') / (b, b') = (a/b, (a'·b − a·b')/b²)
|
|
10
|
-
* scale((a, a'), k) = (k·a, k·a')
|
|
11
|
-
* Elementwise unless noted. Reductions and contractions add their own rules.
|
|
2
|
+
* @danielsimonjr/mathts-autograd — forward + reverse-mode automatic
|
|
3
|
+
* differentiation for the MathTS rank-N Tensor type.
|
|
12
4
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
5
|
+
* v0.1.0 ships:
|
|
6
|
+
* - forwardGrad(fn, x): dual-number AD; returns { value, jacobian }.
|
|
7
|
+
* - reverseGrad(fn, x, cotangent?): tape-based reverse-mode AD;
|
|
8
|
+
* returns { value, gradient }.
|
|
15
9
|
*
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
declare class DualTensor {
|
|
20
|
-
readonly shape: ReadonlyArray<number>;
|
|
21
|
-
readonly primal: Float64Array;
|
|
22
|
-
readonly tangent: Float64Array;
|
|
23
|
-
constructor(shape: ReadonlyArray<number>, primal: Float64Array, tangent: Float64Array);
|
|
24
|
-
/**
|
|
25
|
-
* S5 fix: existing engine ops (e.g. lower, pderiv, contract) reach into
|
|
26
|
-
* `.data`. The getter returns the primal so those ops still work when a
|
|
27
|
-
* DualTensor flows through them as a structurally-compatible Tensor.
|
|
28
|
-
* (AD-aware ops branch on `'tangent' in arg` before reaching here.)
|
|
29
|
-
*/
|
|
30
|
-
get data(): Float64Array;
|
|
31
|
-
/** Lift a Tensor to a DualTensor with zero tangent. */
|
|
32
|
-
static fromTensor(t: Tensor): DualTensor;
|
|
33
|
-
/** Lift a Tensor to a DualTensor with a unit tangent at flat-index `i`. */
|
|
34
|
-
static unitAt(t: Tensor, i: number): DualTensor;
|
|
35
|
-
/** Extract the primal as a plain Tensor. */
|
|
36
|
-
toPrimalTensor(): Tensor;
|
|
37
|
-
/** Extract the tangent as a plain Tensor. */
|
|
38
|
-
toTangentTensor(): Tensor;
|
|
39
|
-
/** Elementwise addition following dual-number rule: (a+b, a'+b'). */
|
|
40
|
-
add(other: DualTensor): DualTensor;
|
|
41
|
-
/** Elementwise subtraction following dual-number rule: (a-b, a'-b'). */
|
|
42
|
-
sub(other: DualTensor): DualTensor;
|
|
43
|
-
/**
|
|
44
|
-
* Elementwise multiplication following dual-number rule: (a·b, a·b' + a'·b).
|
|
45
|
-
*
|
|
46
|
-
* I3 fix: explicit alias check. When `this === other` (self-multiplication),
|
|
47
|
-
* use the specialised rule (a·a)' = 2·a·a' directly. The general rule also
|
|
48
|
-
* degenerates correctly, but the explicit branch is more readable and aligns
|
|
49
|
-
* the forward-mode implementation with the reverse-mode alias fix.
|
|
50
|
-
*/
|
|
51
|
-
mul(other: DualTensor): DualTensor;
|
|
52
|
-
/** Scalar multiplication following dual-number rule: (k·a, k·a'). */
|
|
53
|
-
scale(k: number): DualTensor;
|
|
54
|
-
/**
|
|
55
|
-
* Elementwise division following the dual-number quotient rule:
|
|
56
|
-
* (a, a') / (b, b') = (a/b, (a'·b − a·b') / b²).
|
|
57
|
-
* The aliased case a.divide(a) is (1, 0).
|
|
58
|
-
*/
|
|
59
|
-
divide(other: DualTensor): DualTensor;
|
|
60
|
-
private _unaryElementwise;
|
|
61
|
-
/**
|
|
62
|
-
* Apply a shared elementary rule from core's `DUAL_UNARY_RULES` element-wise.
|
|
63
|
-
* Sourcing `primal`/`deriv` from the canonical table (rather than inline
|
|
64
|
-
* `Math.*`) keeps this AD path and the scalar `Dual` in lock-step and lets a
|
|
65
|
-
* future accelerated `primal` in the standard layer propagate here for free —
|
|
66
|
-
* the loop still calls plain scalar functions, so it stays allocation-free.
|
|
67
|
-
*/
|
|
68
|
-
private _rule;
|
|
69
|
-
/** Exponential. f'(x) = eˣ = y. */
|
|
70
|
-
exp(): DualTensor;
|
|
71
|
-
/** Natural logarithm. f'(x) = 1/x. */
|
|
72
|
-
log(): DualTensor;
|
|
73
|
-
/** Sine. f'(x) = cos(x). */
|
|
74
|
-
sin(): DualTensor;
|
|
75
|
-
/** Cosine. f'(x) = −sin(x). */
|
|
76
|
-
cos(): DualTensor;
|
|
77
|
-
/** Tangent. f'(x) = 1/cos²(x). */
|
|
78
|
-
tan(): DualTensor;
|
|
79
|
-
/** Square root. f'(x) = 1/(2·y) where y = √x. */
|
|
80
|
-
sqrt(): DualTensor;
|
|
81
|
-
/** Square (x²). f'(x) = 2x. */
|
|
82
|
-
square(): DualTensor;
|
|
83
|
-
/** Reciprocal (1/x). f'(x) = −1/x². */
|
|
84
|
-
reciprocal(): DualTensor;
|
|
85
|
-
/** Absolute value. f'(x) = sign(x) (subgradient 0 at exact zero). */
|
|
86
|
-
abs(): DualTensor;
|
|
87
|
-
/** Fixed-exponent power x^k. f'(x) = k·x^(k−1). */
|
|
88
|
-
pow(k: number): DualTensor;
|
|
89
|
-
/** Hyperbolic sine. f'(x) = cosh(x). */
|
|
90
|
-
sinh(): DualTensor;
|
|
91
|
-
/** Hyperbolic cosine. f'(x) = sinh(x). */
|
|
92
|
-
cosh(): DualTensor;
|
|
93
|
-
/** Hyperbolic tangent. f'(x) = 1 − tanh²(x) = 1 − y². */
|
|
94
|
-
tanh(): DualTensor;
|
|
95
|
-
/** Arcsine. f'(x) = 1/√(1 − x²). */
|
|
96
|
-
asin(): DualTensor;
|
|
97
|
-
/** Arccosine. f'(x) = −1/√(1 − x²). */
|
|
98
|
-
acos(): DualTensor;
|
|
99
|
-
/** Arctangent. f'(x) = 1/(1 + x²). */
|
|
100
|
-
atan(): DualTensor;
|
|
101
|
-
/** Inverse hyperbolic sine. f'(x) = 1/√(x² + 1). */
|
|
102
|
-
asinh(): DualTensor;
|
|
103
|
-
/** Inverse hyperbolic cosine (x ≥ 1). f'(x) = 1/√(x² − 1). */
|
|
104
|
-
acosh(): DualTensor;
|
|
105
|
-
/** Inverse hyperbolic tangent (|x| < 1). f'(x) = 1/(1 − x²). */
|
|
106
|
-
atanh(): DualTensor;
|
|
107
|
-
/** Base-2 logarithm. f'(x) = 1/(x·ln2). */
|
|
108
|
-
log2(): DualTensor;
|
|
109
|
-
/** Base-10 logarithm. f'(x) = 1/(x·ln10). */
|
|
110
|
-
log10(): DualTensor;
|
|
111
|
-
/** log(1 + x). f'(x) = 1/(1 + x). */
|
|
112
|
-
log1p(): DualTensor;
|
|
113
|
-
/** exp(x) − 1. f'(x) = eˣ = y + 1. */
|
|
114
|
-
expm1(): DualTensor;
|
|
115
|
-
/** Cube root. f'(x) = 1/(3·y²) where y = x^(1/3) (→∞ at x = 0). */
|
|
116
|
-
cbrt(): DualTensor;
|
|
117
|
-
/** Sign (−1/0/+1). f'(x) = 0 almost everywhere. */
|
|
118
|
-
sign(): DualTensor;
|
|
119
|
-
/**
|
|
120
|
-
* Two-argument arctangent: atan2(this, other), this = y, other = x.
|
|
121
|
-
* Tangent: (b·a' − a·b') / (a² + b²), with a = this, b = other.
|
|
122
|
-
*/
|
|
123
|
-
atan2(other: DualTensor): DualTensor;
|
|
124
|
-
private checkSameShape;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Forward-mode AD via dual numbers. Returns the value and the full Jacobian
|
|
129
|
-
* of fn at x, shape [...value.shape, ...x.shape] (row-major flatten).
|
|
130
|
-
*
|
|
131
|
-
* Implementation: for each flat-index k in x, build a DualTensor with a unit
|
|
132
|
-
* tangent at position k, run fn (which operates on Tensors — see "interop"
|
|
133
|
-
* below), extract the tangent of the result. The tangent is the k-th column
|
|
134
|
-
* of the Jacobian (in row-major-of-output-flattened form).
|
|
135
|
-
*
|
|
136
|
-
* Interop with the Tensor type: fn is typed `(x: Tensor) => Tensor`, so the
|
|
137
|
-
* tracing requires fn's body to be implementable on either Tensor OR
|
|
138
|
-
* DualTensor. v0.1.0 ships a documented "trace mode" — if fn uses only the
|
|
139
|
-
* core ops that DualTensor implements (add/sub/mul/scale), the
|
|
140
|
-
* implementation auto-traces by detecting DualTensor inputs via a thin
|
|
141
|
-
* wrapper. The actual mechanism in v0.1.0: caller writes fn as if Tensor's
|
|
142
|
-
* methods accept either Tensor or DualTensor (TypeScript's structural typing
|
|
143
|
-
* makes this work for the implemented ops). v0.2.0 may broaden via decorator
|
|
144
|
-
* instrumentation.
|
|
145
|
-
*
|
|
146
|
-
* For UPT v0.4.0's call-sites (metric functions in christoffel lowering),
|
|
147
|
-
* fn uses only add/sub/mul/scale → safe.
|
|
10
|
+
* Tasks 6/7 populate this barrel. Task 5 scaffolds the package.
|
|
148
11
|
*
|
|
149
12
|
* @packageDocumentation
|
|
150
13
|
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
* compatible with Tensor) and must return a DualTensor.
|
|
159
|
-
* @param x - Input tensor at which to differentiate.
|
|
160
|
-
* @returns `{ value, jacobian }` where:
|
|
161
|
-
* - `value` has the output shape of fn applied to x
|
|
162
|
-
* - `jacobian` has shape `[...value.shape, ...x.shape]` row-major.
|
|
163
|
-
* `jacobian.data[kY * xSize + kX]` = ∂y[kY] / ∂x[kX].
|
|
164
|
-
* Verified by Adam+Eve review 2026-05-15 (E13): kY outer, kX inner,
|
|
165
|
-
* stride xSize between successive y-rows IS row-major [...y.shape, ...x.shape].
|
|
166
|
-
*/
|
|
167
|
-
declare function forwardGrad(fn: (x: Tensor) => Tensor, x: Tensor): {
|
|
168
|
-
value: Tensor;
|
|
169
|
-
jacobian: Tensor;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Tape — records the sequence of ops during a forward pass so we can
|
|
174
|
-
* replay them in reverse to compute the vector-Jacobian product.
|
|
175
|
-
*
|
|
176
|
-
* Each TapedTensor wraps a primal Tensor + a tape-node id. Ops on
|
|
177
|
-
* TapedTensors record their backward closures into a shared Tape.
|
|
178
|
-
* After the forward pass, calling Tape.backward(outputGrad) walks the
|
|
179
|
-
* tape in reverse, accumulating gradients into each input slot.
|
|
180
|
-
*
|
|
181
|
-
* v0.1.0 supports the same ops as DualTensor: add, sub, mul, scale.
|
|
182
|
-
* v0.2.0 adds: contract (named-index), matmul (batched rank-N).
|
|
183
|
-
*/
|
|
184
|
-
|
|
185
|
-
type BackwardFn = (outputGrad: Float64Array) => void;
|
|
186
|
-
declare class Tape {
|
|
187
|
-
private nodes;
|
|
188
|
-
private inputGradSlots;
|
|
189
|
-
private nextOpId;
|
|
190
|
-
private nextInputId;
|
|
191
|
-
/** Allocate a fresh id for an input or intermediate. */
|
|
192
|
-
allocate(size: number): {
|
|
193
|
-
id: number;
|
|
194
|
-
gradSlot: Float64Array;
|
|
195
|
-
};
|
|
196
|
-
record(inputIds: ReadonlyArray<number>, outputSize: number, backward: BackwardFn): {
|
|
197
|
-
id: number;
|
|
198
|
-
gradSlot: Float64Array;
|
|
199
|
-
};
|
|
200
|
-
/** Seed the final output's gradient slot, then replay in reverse. */
|
|
201
|
-
backward(outputId: number, outputGrad: Float64Array): void;
|
|
202
|
-
getInputGrad(id: number): Float64Array | undefined;
|
|
203
|
-
}
|
|
204
|
-
declare class TapedTensor {
|
|
205
|
-
readonly shape: ReadonlyArray<number>;
|
|
206
|
-
readonly primal: Float64Array;
|
|
207
|
-
readonly tape: Tape;
|
|
208
|
-
readonly id: number;
|
|
209
|
-
/**
|
|
210
|
-
* Optional per-axis Index labels. When set, enables `contract`.
|
|
211
|
-
* Must have the same length as `shape` when present.
|
|
212
|
-
*/
|
|
213
|
-
readonly axisLabels?: ReadonlyArray<Index>;
|
|
214
|
-
constructor(shape: ReadonlyArray<number>, primal: Float64Array, tape: Tape, id: number, axisLabels?: ReadonlyArray<Index>);
|
|
215
|
-
/**
|
|
216
|
-
* S5 fix: existing engine ops (e.g. lower, pderiv, contract) reach into
|
|
217
|
-
* `.data`. The getter returns the primal so those ops still work when a
|
|
218
|
-
* TapedTensor flows through them as a structurally-compatible Tensor.
|
|
219
|
-
* (AD-aware ops branch on `'tape' in arg` before reaching here.)
|
|
220
|
-
*/
|
|
221
|
-
get data(): Float64Array;
|
|
222
|
-
static fromTensorAsInput(t: Tensor, tape: Tape): TapedTensor;
|
|
223
|
-
toPrimalTensor(): Tensor;
|
|
224
|
-
/**
|
|
225
|
-
* Reconstruct the Tensor primal with the given axisLabels (used internally
|
|
226
|
-
* when the primal was computed from an op that produces labelled output).
|
|
227
|
-
*/
|
|
228
|
-
private toPrimalTensorWith;
|
|
229
|
-
add(other: TapedTensor): TapedTensor;
|
|
230
|
-
sub(other: TapedTensor): TapedTensor;
|
|
231
|
-
/**
|
|
232
|
-
* Elementwise division: this / other.
|
|
233
|
-
*
|
|
234
|
-
* Adjoints (quotient rule):
|
|
235
|
-
* dA = dY / b
|
|
236
|
-
* dB = −dY · a / b²
|
|
237
|
-
*
|
|
238
|
-
* The alias case (a.divide(a)) is handled explicitly: the combined gradient
|
|
239
|
-
* is dA + dB = dY/a − dY·a/a² = dY/a − dY/a = 0. This is correct since
|
|
240
|
-
* a/a = 1 everywhere and d(1)/da = 0.
|
|
241
|
-
*/
|
|
242
|
-
divide(other: TapedTensor): TapedTensor;
|
|
243
|
-
mul(other: TapedTensor): TapedTensor;
|
|
244
|
-
scale(k: number): TapedTensor;
|
|
245
|
-
/**
|
|
246
|
-
* Reverse-mode AD over `Tensor.contract`.
|
|
247
|
-
*
|
|
248
|
-
* Both operands must carry `axisLabels`; the resulting TapedTensor inherits
|
|
249
|
-
* the contracted-output axisLabels (non-shared axis concatenation).
|
|
250
|
-
*
|
|
251
|
-
* Adjoint derivation (T-notation):
|
|
252
|
-
* Let Y = A.contract(B) (contraction over shared indices S).
|
|
253
|
-
* dA = dY.contract(B') where B' = B with its free axes re-labelled to
|
|
254
|
-
* match the shared positions in A. Equivalently: for each element of A,
|
|
255
|
-
* dA[...a_free, ...s] = Σ_{...b_free} dY[...a_free, ...b_free] · B[...s, ...b_free]
|
|
256
|
-
* dB = A'.contract(dY) symmetrically.
|
|
257
|
-
*
|
|
258
|
-
* Implementation: because `Tensor.contract` matches by Index identity, we
|
|
259
|
-
* build the backward contraction by re-labelling the free axes of dY with
|
|
260
|
-
* the Index objects from the other operand — so `Tensor.contract` automatically
|
|
261
|
-
* finds the right shared axes.
|
|
262
|
-
*/
|
|
263
|
-
contract(other: TapedTensor): TapedTensor;
|
|
264
|
-
/**
|
|
265
|
-
* Reverse-mode AD over batched matmul.
|
|
266
|
-
*
|
|
267
|
-
* Accepts operands of rank ≥ 2. Convention: trailing 2 axes are the matrix
|
|
268
|
-
* dims (rows × cols); all leading axes are batch dims (must be broadcast-
|
|
269
|
-
* compatible). For rank-2 inputs this is classical matrix multiplication.
|
|
270
|
-
*
|
|
271
|
-
* Adjoint derivation (classical matrix calc, extended to batched):
|
|
272
|
-
* Y = A @ B (A: ...×m×k, B: ...×k×n → Y: ...×m×n)
|
|
273
|
-
* dA = dY @ Bᵀ (dA: ...×m×k)
|
|
274
|
-
* dB = Aᵀ @ dY (dB: ...×k×n)
|
|
275
|
-
*
|
|
276
|
-
* Implemented via `Tensor.einsum` with a dynamically-built spec:
|
|
277
|
-
* forward: '...ik,...kj->...ij'
|
|
278
|
-
* dA: '...ij,...kj->...ik' (contract dY with B on j)
|
|
279
|
-
* dB: '...ki,...kj->...ij' (contract A on k with dY on k)
|
|
280
|
-
*/
|
|
281
|
-
matmul(other: TapedTensor): TapedTensor;
|
|
282
|
-
private checkSameShape;
|
|
283
|
-
/**
|
|
284
|
-
* Sum elements along the given axis/axes (or all axes if omitted).
|
|
285
|
-
*
|
|
286
|
-
* Adjoint: dX[...] = dY[reduced(idx)] broadcast back to input shape.
|
|
287
|
-
* Each input element receives the output-gradient entry from its reduced
|
|
288
|
-
* counterpart (the non-reduced coordinates select the dY element; the
|
|
289
|
-
* reduced coordinates are collapsed to 0 in the keepDims=false case).
|
|
290
|
-
*/
|
|
291
|
-
sum(axis?: number | ReadonlyArray<number>, opts?: {
|
|
292
|
-
keepDims?: boolean;
|
|
293
|
-
}): TapedTensor;
|
|
294
|
-
/**
|
|
295
|
-
* Arithmetic mean along the given axis/axes (or all axes if omitted).
|
|
296
|
-
*
|
|
297
|
-
* Adjoint: dX[...] = dY[reduced(idx)] / N, broadcast back to input shape.
|
|
298
|
-
* N = product of reduced-axis dimensions.
|
|
299
|
-
*/
|
|
300
|
-
mean(axis?: number | ReadonlyArray<number>, opts?: {
|
|
301
|
-
keepDims?: boolean;
|
|
302
|
-
}): TapedTensor;
|
|
303
|
-
/**
|
|
304
|
-
* Product of elements along the given axis/axes (or all axes if omitted).
|
|
305
|
-
*
|
|
306
|
-
* Adjoint: dX_i = dY * (prod_over_axes(x) / x_i) per element.
|
|
307
|
-
*
|
|
308
|
-
* Zero-element corners:
|
|
309
|
-
* - Exactly one x_i = 0 in the reduced group: d/dx_i = product of all others
|
|
310
|
-
* (which is the full product / x_i evaluated via alternate product),
|
|
311
|
-
* and d/dx_j = 0 for all j ≠ i where x_j ≠ 0.
|
|
312
|
-
* - Two or more zeros in the reduced group: gradient is 0 everywhere for that
|
|
313
|
-
* group (because changing any single zero cannot change a product that is
|
|
314
|
-
* zero due to another zero).
|
|
315
|
-
*
|
|
316
|
-
* Implementation: uses prefix/suffix products to handle zeros robustly.
|
|
317
|
-
*/
|
|
318
|
-
prod(axis?: number | ReadonlyArray<number>, opts?: {
|
|
319
|
-
keepDims?: boolean;
|
|
320
|
-
}): TapedTensor;
|
|
321
|
-
/**
|
|
322
|
-
* Maximum value along the given axis/axes (or all axes if omitted).
|
|
323
|
-
*
|
|
324
|
-
* Adjoint: dY is scattered to the argmax position(s).
|
|
325
|
-
* Tie-breaking: "first-wins" — the gradient flows to the first (smallest
|
|
326
|
-
* flat-index) element among those that attain the maximum.
|
|
327
|
-
*/
|
|
328
|
-
max(axis?: number | ReadonlyArray<number>, opts?: {
|
|
329
|
-
keepDims?: boolean;
|
|
330
|
-
}): TapedTensor;
|
|
331
|
-
/**
|
|
332
|
-
* Minimum value along the given axis/axes (or all axes if omitted).
|
|
333
|
-
*
|
|
334
|
-
* Adjoint: dY is scattered to the argmin position(s).
|
|
335
|
-
* Tie-breaking: "first-wins" — gradient flows to the first (smallest
|
|
336
|
-
* flat-index) element that attains the minimum.
|
|
337
|
-
*/
|
|
338
|
-
min(axis?: number | ReadonlyArray<number>, opts?: {
|
|
339
|
-
keepDims?: boolean;
|
|
340
|
-
}): TapedTensor;
|
|
341
|
-
/**
|
|
342
|
-
* p-norm of the tensor.
|
|
343
|
-
*
|
|
344
|
-
* Supported p values: 1, 2, 'fro', 'inf'. Default p = 2.
|
|
345
|
-
* When `opts.axis` is given, reduces along that axis; otherwise reduces all axes.
|
|
346
|
-
*
|
|
347
|
-
* Adjoints:
|
|
348
|
-
* - p=2 / p='fro': dX = dY · x / ‖x‖₂ (Frobenius is the 2-norm of the flattened tensor)
|
|
349
|
-
* - p=1: dX = dY · sign(x) (subgradient = 0 at exact zero)
|
|
350
|
-
* - p='inf': dX scattered to the element(s) of max absolute value;
|
|
351
|
-
* tie-breaking: first-wins. Sign of the scattered gradient
|
|
352
|
-
* matches sign(x_max).
|
|
353
|
-
*/
|
|
354
|
-
norm(opts?: {
|
|
355
|
-
p?: 1 | 2 | 'fro' | 'inf';
|
|
356
|
-
axis?: number;
|
|
357
|
-
keepDims?: boolean;
|
|
358
|
-
}): TapedTensor;
|
|
359
|
-
/**
|
|
360
|
-
* Elementwise natural logarithm.
|
|
361
|
-
*
|
|
362
|
-
* Adjoint: dX = dY / x
|
|
363
|
-
*/
|
|
364
|
-
log(): TapedTensor;
|
|
365
|
-
/**
|
|
366
|
-
* Elementwise exponential.
|
|
367
|
-
*
|
|
368
|
-
* Adjoint: dX = dY · y where y = exp(x). Primal output is cached.
|
|
369
|
-
*/
|
|
370
|
-
exp(): TapedTensor;
|
|
371
|
-
/**
|
|
372
|
-
* Elementwise sine.
|
|
373
|
-
*
|
|
374
|
-
* Adjoint: dX = dY · cos(x)
|
|
375
|
-
*/
|
|
376
|
-
sin(): TapedTensor;
|
|
377
|
-
/**
|
|
378
|
-
* Elementwise cosine.
|
|
379
|
-
*
|
|
380
|
-
* Adjoint: dX = −dY · sin(x)
|
|
381
|
-
*/
|
|
382
|
-
cos(): TapedTensor;
|
|
383
|
-
/**
|
|
384
|
-
* Elementwise tangent.
|
|
385
|
-
*
|
|
386
|
-
* Adjoint: dX = dY / cos²(x) (= dY · sec²(x))
|
|
387
|
-
*/
|
|
388
|
-
tan(): TapedTensor;
|
|
389
|
-
/**
|
|
390
|
-
* Elementwise square root.
|
|
391
|
-
*
|
|
392
|
-
* Adjoint: dX = dY / (2·y) where y = sqrt(x). Primal output is cached.
|
|
393
|
-
*/
|
|
394
|
-
sqrt(): TapedTensor;
|
|
395
|
-
/**
|
|
396
|
-
* Elementwise square (x²).
|
|
397
|
-
*
|
|
398
|
-
* Adjoint: dX = dY · 2x
|
|
399
|
-
*/
|
|
400
|
-
square(): TapedTensor;
|
|
401
|
-
/**
|
|
402
|
-
* Elementwise power: x^k.
|
|
403
|
-
*
|
|
404
|
-
* `k` may be a fixed `number` (constant exponent) or a `TapedTensor`
|
|
405
|
-
* (variable exponent, both inputs on the tape).
|
|
406
|
-
*
|
|
407
|
-
* Adjoints, for y = a^b:
|
|
408
|
-
* - fixed exponent k: dA = dY · k · a^(k−1)
|
|
409
|
-
* - variable exponent: dA = dY · b · a^(b−1), dB = dY · a^b · ln(a)
|
|
410
|
-
* - aliased a^a: dA = dY · a^a · (ln a + 1)
|
|
411
|
-
*
|
|
412
|
-
* The dB term needs ln(a); for a ≤ 0 it is mathematically undefined and
|
|
413
|
-
* yields NaN/−Inf, honestly signalling non-differentiability w.r.t. the
|
|
414
|
-
* exponent rather than masking it.
|
|
415
|
-
*/
|
|
416
|
-
pow(k: number | TapedTensor): TapedTensor;
|
|
417
|
-
/**
|
|
418
|
-
* Elementwise reciprocal: 1 / x.
|
|
419
|
-
*
|
|
420
|
-
* Adjoint: dX = −dY / x²
|
|
421
|
-
*/
|
|
422
|
-
reciprocal(): TapedTensor;
|
|
423
|
-
/**
|
|
424
|
-
* Elementwise absolute value: |x|.
|
|
425
|
-
*
|
|
426
|
-
* Adjoint: dX = dY · sign(x)
|
|
427
|
-
* Subgradient at exact zero is defined as 0 (rather than undefined).
|
|
428
|
-
*/
|
|
429
|
-
abs(): TapedTensor;
|
|
430
|
-
/**
|
|
431
|
-
* Build a reverse-mode node for an elementwise unary op.
|
|
432
|
-
* @param primalFn forward value y = f(x)
|
|
433
|
-
* @param localGrad local derivative dy/dx, given x and the computed y
|
|
434
|
-
*/
|
|
435
|
-
private _unaryElementwise;
|
|
436
|
-
/** Hyperbolic sine. Adjoint: dX = dY · cosh(x). */
|
|
437
|
-
sinh(): TapedTensor;
|
|
438
|
-
/** Hyperbolic cosine. Adjoint: dX = dY · sinh(x). */
|
|
439
|
-
cosh(): TapedTensor;
|
|
440
|
-
/** Hyperbolic tangent. Adjoint: dX = dY · (1 − tanh²(x)) = dY · (1 − y²). */
|
|
441
|
-
tanh(): TapedTensor;
|
|
442
|
-
/** Arcsine. Adjoint: dX = dY / √(1 − x²). */
|
|
443
|
-
asin(): TapedTensor;
|
|
444
|
-
/** Arccosine. Adjoint: dX = −dY / √(1 − x²). */
|
|
445
|
-
acos(): TapedTensor;
|
|
446
|
-
/** Arctangent. Adjoint: dX = dY / (1 + x²). */
|
|
447
|
-
atan(): TapedTensor;
|
|
448
|
-
/** Inverse hyperbolic sine. Adjoint: dX = dY / √(x² + 1). */
|
|
449
|
-
asinh(): TapedTensor;
|
|
450
|
-
/** Inverse hyperbolic cosine (x ≥ 1). Adjoint: dX = dY / √(x² − 1). */
|
|
451
|
-
acosh(): TapedTensor;
|
|
452
|
-
/** Inverse hyperbolic tangent (|x| < 1). Adjoint: dX = dY / (1 − x²). */
|
|
453
|
-
atanh(): TapedTensor;
|
|
454
|
-
/** Base-2 logarithm. Adjoint: dX = dY / (x · ln2). */
|
|
455
|
-
log2(): TapedTensor;
|
|
456
|
-
/** Base-10 logarithm. Adjoint: dX = dY / (x · ln10). */
|
|
457
|
-
log10(): TapedTensor;
|
|
458
|
-
/** log(1 + x) (accurate near 0). Adjoint: dX = dY / (1 + x). */
|
|
459
|
-
log1p(): TapedTensor;
|
|
460
|
-
/** exp(x) − 1 (accurate near 0). Adjoint: dX = dY · eˣ = dY · (y + 1). */
|
|
461
|
-
expm1(): TapedTensor;
|
|
462
|
-
/** Cube root. Adjoint: dX = dY / (3 · y²) where y = x^(1/3) (→∞ at x = 0). */
|
|
463
|
-
cbrt(): TapedTensor;
|
|
464
|
-
/** Sign of x (−1/0/+1). Adjoint: 0 almost everywhere. */
|
|
465
|
-
sign(): TapedTensor;
|
|
466
|
-
/**
|
|
467
|
-
* Two-argument arctangent: atan2(this, other), with this = y, other = x.
|
|
468
|
-
*
|
|
469
|
-
* Adjoints (a = this, b = other, d = a² + b²):
|
|
470
|
-
* dA = dY · b / d
|
|
471
|
-
* dB = −dY · a / d
|
|
472
|
-
*
|
|
473
|
-
* The aliased case atan2(a, a) is locally constant (±π/4 or ±3π/4), so its
|
|
474
|
-
* gradient is 0 — produced naturally by contributing nothing when aliased
|
|
475
|
-
* (dA + dB = dY · (b − a)/d = 0 at a = b).
|
|
476
|
-
*/
|
|
477
|
-
atan2(other: TapedTensor): TapedTensor;
|
|
478
|
-
/**
|
|
479
|
-
* Reverse-mode AD over `Tensor.tensordot`.
|
|
480
|
-
*
|
|
481
|
-
* `axes[i] = [a, b]` contracts axis `a` of `this` with axis `b` of `other`.
|
|
482
|
-
* The result shape is `this`'s non-contracted axes (in original order)
|
|
483
|
-
* followed by `other`'s non-contracted axes (in original order).
|
|
484
|
-
*
|
|
485
|
-
* Adjoint derivation (NumPy/PyTorch tensordot backward, see Townsend 2016 §6,
|
|
486
|
-
* and the canonical PyTorch implementation `TensorDotBackward0` in
|
|
487
|
-
* `torch/csrc/autograd/generated/Functions.cpp`):
|
|
488
|
-
*
|
|
489
|
-
* Z = tensordot(A, B, axes)
|
|
490
|
-
* dA = tensordot(dZ, B, [axes_of_dZ_corresponding_to_B's_free, B's_free])
|
|
491
|
-
* then permute back into A's original axis order.
|
|
492
|
-
* dB = tensordot(A, dZ, [A's_free, axes_of_dZ_corresponding_to_A's_free])
|
|
493
|
-
* then permute back into B's original axis order.
|
|
494
|
-
*
|
|
495
|
-
* The axis-permutation bookkeeping is the trickiest part: Tensor.tensordot
|
|
496
|
-
* produces output axes in the order [A's free axes (original A order),
|
|
497
|
-
* B's free axes (original B order)] — and after the backward contractions
|
|
498
|
-
* the survivors come out in B's original (resp. A's original) axis order
|
|
499
|
-
* for the contracted side, which then needs to be permuted into pair order
|
|
500
|
-
* (so axis k of the contracted block matches the kth pair) and finally
|
|
501
|
-
* scattered back into A's (resp. B's) full original axis order.
|
|
502
|
-
*
|
|
503
|
-
* For the rank-2 × rank-2 single-axis case (i.e. ordinary matmul A·B with
|
|
504
|
-
* axes = [[1, 0]]), this reduces to dA = dZ · Bᵀ, dB = Aᵀ · dZ — the same
|
|
505
|
-
* adjoint as `TapedTensor.matmul`.
|
|
506
|
-
*/
|
|
507
|
-
tensordot(other: TapedTensor, axes: ReadonlyArray<readonly [number, number]>): TapedTensor;
|
|
508
|
-
/**
|
|
509
|
-
* Reverse-mode AD over the full SVD of a rank-2 matrix.
|
|
510
|
-
*
|
|
511
|
-
* Forward: `A = U · diag(S) · Vt`, where for input shape [m, n], k = min(m, n):
|
|
512
|
-
* - U has shape [m, k]
|
|
513
|
-
* - S has shape [k]
|
|
514
|
-
* - Vt has shape [k, n] (Vt is V^T in the standard A = U Σ V^T convention,
|
|
515
|
-
* i.e. its rows are right-singular-vector components)
|
|
516
|
-
*
|
|
517
|
-
* Returned TapedTensors share a single backward closure. When backward()
|
|
518
|
-
* runs, it pulls dU, dS, dV from each output's gradient slot, assembles
|
|
519
|
-
* dA, and writes to the input's gradient slot.
|
|
520
|
-
*
|
|
521
|
-
* Adjoint (real, distinct nonzero singular values, m = n square case;
|
|
522
|
-
* extended to rectangular below). Derived directly from the forward
|
|
523
|
-
* Jacobian; equivalent to PyTorch's `svd_backward`
|
|
524
|
-
* (`aten/src/ATen/native/BatchLinearAlgebra.cpp`) and Townsend (2016)
|
|
525
|
-
* "Differentiating the Singular Value Decomposition" §3:
|
|
526
|
-
*
|
|
527
|
-
* Let α = skew(U^T · dU), β = skew(V^T · dV) (k×k, antisymmetric)
|
|
528
|
-
* Build C (k×k):
|
|
529
|
-
* C[i,i] = dS[i]
|
|
530
|
-
* C[i,j] = (α[i,j] + β[i,j]) / (s_j − s_i)
|
|
531
|
-
* + (α[i,j] − β[i,j]) / (s_j + s_i) for i ≠ j
|
|
532
|
-
* dA_in = U · C · V^T (m×n in-subspace part)
|
|
533
|
-
*
|
|
534
|
-
* Rectangular correction (when m > k, i.e. m > n):
|
|
535
|
-
* dA += (I − U U^T) · dU · diag(1/s) · V^T
|
|
536
|
-
* Rectangular correction (when n > k, i.e. n > m):
|
|
537
|
-
* dA += U · diag(1/s) · dV^T · (I − V V^T)
|
|
538
|
-
*
|
|
539
|
-
* Regularisation at repeated/near-zero singular values (PyTorch-equivalent
|
|
540
|
-
* subgradient choice): the (i,j) entry of C is masked to 0 whenever
|
|
541
|
-
* `|s_j − s_i| < REL_TOL · max(|s|)` (the "difference" denominator) or
|
|
542
|
-
* `|s_j + s_i| < REL_TOL · max(|s|)` (the "sum" denominator, only relevant
|
|
543
|
-
* when both are ~0). REL_TOL = 1e-10. This makes the gradient a subgradient
|
|
544
|
-
* at exact degeneracy — finite, but not the unique true derivative (which
|
|
545
|
-
* does not exist at degeneracies). The rectangular correction also masks
|
|
546
|
-
* 1/s_i when |s_i| < REL_TOL · max(|s|).
|
|
547
|
-
*
|
|
548
|
-
* Throws if input is not rank-2. For rank > 2 inputs the user should
|
|
549
|
-
* reshape first.
|
|
550
|
-
*/
|
|
551
|
-
svd(): {
|
|
552
|
-
U: TapedTensor;
|
|
553
|
-
S: TapedTensor;
|
|
554
|
-
V: TapedTensor;
|
|
555
|
-
};
|
|
556
|
-
/**
|
|
557
|
-
* Reverse-mode AD over the eigendecomposition of a rank-2 matrix.
|
|
558
|
-
*
|
|
559
|
-
* Symmetric path (`symmetric: true`):
|
|
560
|
-
* Forward: `A = U · diag(Λ) · U^T` for symmetric A (n×n).
|
|
561
|
-
* Adjoint (Magnus & Neudecker 1999 §10.6.6; PyTorch `linalg_eigh_backward`):
|
|
562
|
-
* F[i,j] = 1 / (λ_j − λ_i) for i ≠ j, 0 otherwise (with degeneracy mask)
|
|
563
|
-
* dA_raw = U · (diag(dΛ) + F ∘ (U^T · dU)) · U^T
|
|
564
|
-
* dA = (dA_raw + dA_raw^T) / 2 (symmetrise)
|
|
565
|
-
*
|
|
566
|
-
* Non-symmetric path (`symmetric: false`):
|
|
567
|
-
* Forward: `A = V · diag(λ) · V^{-1}` (V columns are right eigenvectors).
|
|
568
|
-
* Adjoint (Magnus & Neudecker 1999 §10.6 / Giles 2008 §3.2 / Townsend 2016 §4;
|
|
569
|
-
* cross-check: PyTorch `linalg_eig_backward`):
|
|
570
|
-
* E[i,j] = 1 / (λ_j − λ_i) for i ≠ j, 0 otherwise (with degeneracy mask)
|
|
571
|
-
* dA = V^{-T} · ( E ∘ (V^T · dV) + diag(dλ) ) · V^T
|
|
572
|
-
*
|
|
573
|
-
* Restrictions (all enforced — throw a clear error otherwise):
|
|
574
|
-
* 1. Eigenvalues must be real. The underlying matrix-eig primitive returns
|
|
575
|
-
* placeholder eigenvectors (not actual complex vectors) when complex
|
|
576
|
-
* eigenvalues arise, so the adjoint formula cannot be evaluated. Real-
|
|
577
|
-
* Schur differentiation would require complex arithmetic infrastructure
|
|
578
|
-
* throughout the Tape/TapedTensor stack, which is out of scope.
|
|
579
|
-
* 2. A must be diagonalisable (non-defective). The adjoint assumes V is
|
|
580
|
-
* invertible; defective inputs have algebraic > geometric multiplicity
|
|
581
|
-
* so V is rank-deficient. Detected by cond_∞(V) > 1e14.
|
|
582
|
-
*
|
|
583
|
-
* Regularisation at repeated eigenvalues (subgradient choice): mask
|
|
584
|
-
* F/E[i,j] = 0 when `|λ_i − λ_j| < REL_TOL · max(|λ|)`. REL_TOL = 1e-10.
|
|
585
|
-
*
|
|
586
|
-
* Throws if input is not rank-2 or square, if `symmetric` is missing, or
|
|
587
|
-
* (non-symmetric path) on complex eigenvalues / defective input.
|
|
588
|
-
*/
|
|
589
|
-
eig(opts: {
|
|
590
|
-
symmetric: boolean;
|
|
591
|
-
}): {
|
|
592
|
-
eigvals: TapedTensor;
|
|
593
|
-
eigvecs: TapedTensor;
|
|
594
|
-
};
|
|
595
|
-
private _eigSymmetric;
|
|
596
|
-
private _eigGeneral;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Reverse-mode AD via tape. Returns the value and the vector-Jacobian product
|
|
601
|
-
* (gradient = ∂(cotangent · value) / ∂x). gradient.shape = x.shape.
|
|
602
|
-
*
|
|
603
|
-
* Default cotangent: ones-like(value) — required for scalar outputs, useful
|
|
604
|
-
* for VJP defaults. For non-scalar value, cotangent's shape must match value.shape.
|
|
605
|
-
*/
|
|
606
|
-
|
|
607
|
-
declare function reverseGrad(fn: (x: Tensor) => Tensor, x: Tensor, cotangent?: Tensor): {
|
|
608
|
-
value: Tensor;
|
|
609
|
-
gradient: Tensor;
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* GC15 — ergonomic, number-friendly autodiff bridge.
|
|
614
|
-
*
|
|
615
|
-
* The low-level building blocks (`Tape`, `TapedTensor`, `reverseGrad`) require
|
|
616
|
-
* manual tape management and `Tensor` marshalling. This is the JAX-style
|
|
617
|
-
* convenience layer: write a function of a single `TapedTensor` (the AD-aware
|
|
618
|
-
* surface — `.sin()`, `.mul()`, `.sum()`, `.matmul()`, … all record adjoints)
|
|
619
|
-
* and differentiate it against plain JS numbers / arrays.
|
|
620
|
-
*
|
|
621
|
-
* grad((x) => x.sin())(1) // [cos(1)]
|
|
622
|
-
* valueAndGrad((x) => x.pow(2).sum())([1, 2, 3]) // { value: 14, grad: [2,4,6] }
|
|
623
|
-
* derivative((x) => x.sin().mul(x), 2) // sin(2) + 2cos(2)
|
|
624
|
-
*
|
|
625
|
-
* `f` must use TapedTensor operations (not the plain `functions/` ops, which are
|
|
626
|
-
* not AD-aware). Differentiating arbitrary `functions/` ops would require
|
|
627
|
-
* overloading them onto dual numbers / the tape — a separate, larger effort.
|
|
628
|
-
*/
|
|
629
|
-
|
|
630
|
-
type ScalarFn = (x: TapedTensor) => TapedTensor;
|
|
631
|
-
type NumericInput = number | readonly number[] | Float64Array;
|
|
632
|
-
/**
|
|
633
|
-
* Forward value and reverse-mode gradient of a scalar-valued function.
|
|
634
|
-
*
|
|
635
|
-
* @param fn maps a TapedTensor (shape `[n]`) to a scalar TapedTensor (length 1).
|
|
636
|
-
* @param x the point — a number, number[], or Float64Array.
|
|
637
|
-
* @returns `{ value, grad }` where `grad` has the same length as `x`.
|
|
638
|
-
* @throws if `fn` does not return a length-1 (scalar) result.
|
|
639
|
-
*/
|
|
640
|
-
declare function valueAndGrad(fn: ScalarFn, x: NumericInput): {
|
|
641
|
-
value: number;
|
|
642
|
-
grad: Float64Array;
|
|
643
|
-
};
|
|
644
|
-
/**
|
|
645
|
-
* Gradient function of a scalar-valued function: `grad(f)(x)` returns ∇f(x).
|
|
646
|
-
*/
|
|
647
|
-
declare function grad(fn: ScalarFn): (x: NumericInput) => Float64Array;
|
|
648
|
-
/**
|
|
649
|
-
* Scalar convenience: derivative of `f: ℝ → ℝ` at `x0`.
|
|
650
|
-
*/
|
|
651
|
-
declare function derivative(fn: ScalarFn, x0: number): number;
|
|
652
|
-
/**
|
|
653
|
-
* Jacobian of a vector-valued function `f: ℝⁿ → ℝᵐ`, one reverse pass per output
|
|
654
|
-
* row. Returns an `m × n` array. (For tall outputs forward-mode is cheaper; this
|
|
655
|
-
* favours wide-input / few-output cases, the common ML gradient shape.)
|
|
656
|
-
*/
|
|
657
|
-
declare function jacobian(fn: ScalarFn, x: NumericInput): number[][];
|
|
658
|
-
|
|
659
|
-
export { DualTensor, type NumericInput, type ScalarFn, Tape, TapedTensor, derivative, forwardGrad, grad, jacobian, reverseGrad, valueAndGrad };
|
|
14
|
+
export { DualTensor } from './dual-tensor.js';
|
|
15
|
+
export { forwardGrad } from './forward-grad.js';
|
|
16
|
+
export { Tape, TapedTensor } from './tape.js';
|
|
17
|
+
export { reverseGrad } from './reverse-grad.js';
|
|
18
|
+
export { grad, valueAndGrad, derivative, jacobian } from './grad.js';
|
|
19
|
+
export type { ScalarFn, NumericInput } from './grad.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
|