universal-physics-tensor 1.0.4 → 2.0.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.
Files changed (168) hide show
  1. package/README.md +6 -8
  2. package/dist/atlas/chain-pipeline.d.ts +37 -0
  3. package/dist/atlas/chain-pipeline.d.ts.map +1 -1
  4. package/dist/atlas/chain-pipeline.js +95 -79
  5. package/dist/atlas/chain-pipeline.js.map +1 -1
  6. package/dist/atlas/proof-target.d.ts +28 -2
  7. package/dist/atlas/proof-target.d.ts.map +1 -1
  8. package/dist/atlas/proof-target.js +25 -16
  9. package/dist/atlas/proof-target.js.map +1 -1
  10. package/dist/cli/commands/_atlas-route.d.ts +1 -1
  11. package/dist/cli/commands/atlas.d.ts.map +1 -1
  12. package/dist/cli/commands/atlas.js +1 -2
  13. package/dist/cli/commands/atlas.js.map +1 -1
  14. package/dist/cli/commands/eval.d.ts.map +1 -1
  15. package/dist/cli/commands/eval.js +7 -10
  16. package/dist/cli/commands/eval.js.map +1 -1
  17. package/dist/cli/commands/evaluate.d.ts +7 -3
  18. package/dist/cli/commands/evaluate.d.ts.map +1 -1
  19. package/dist/cli/commands/evaluate.js +53 -32
  20. package/dist/cli/commands/evaluate.js.map +1 -1
  21. package/dist/cli/commands/map.d.ts +1 -1
  22. package/dist/cli/commands/map.d.ts.map +1 -1
  23. package/dist/cli/commands/map.js.map +1 -1
  24. package/dist/cli/commands/metric.d.ts.map +1 -1
  25. package/dist/cli/commands/metric.js +10 -11
  26. package/dist/cli/commands/metric.js.map +1 -1
  27. package/dist/cli/commands/path.d.ts +2 -2
  28. package/dist/cli/commands/path.d.ts.map +1 -1
  29. package/dist/cli/commands/path.js +10 -11
  30. package/dist/cli/commands/path.js.map +1 -1
  31. package/dist/cli/commands/recover.d.ts.map +1 -1
  32. package/dist/cli/commands/recover.js +1 -2
  33. package/dist/cli/commands/recover.js.map +1 -1
  34. package/dist/cli/commands/regime.d.ts +2 -2
  35. package/dist/cli/commands/regime.d.ts.map +1 -1
  36. package/dist/cli/commands/regime.js +3 -4
  37. package/dist/cli/commands/regime.js.map +1 -1
  38. package/dist/cli/eval-numbers.d.ts +2 -2
  39. package/dist/cli/eval-numbers.js +2 -2
  40. package/dist/cli/version.d.ts +6 -2
  41. package/dist/cli/version.d.ts.map +1 -1
  42. package/dist/cli/version.js +11 -3
  43. package/dist/cli/version.js.map +1 -1
  44. package/dist/cli-api.d.ts +50 -0
  45. package/dist/cli-api.d.ts.map +1 -1
  46. package/dist/cli-api.js +42 -0
  47. package/dist/cli-api.js.map +1 -1
  48. package/dist/composition/chain-result.d.ts +55 -0
  49. package/dist/composition/chain-result.d.ts.map +1 -0
  50. package/dist/composition/chain-result.js +57 -0
  51. package/dist/composition/chain-result.js.map +1 -0
  52. package/dist/composition/compose-symbolic.d.ts.map +1 -1
  53. package/dist/composition/compose-symbolic.js +4 -39
  54. package/dist/composition/compose-symbolic.js.map +1 -1
  55. package/dist/composition/compose.d.ts.map +1 -1
  56. package/dist/composition/compose.js +11 -4
  57. package/dist/composition/compose.js.map +1 -1
  58. package/dist/composition/enumerate.d.ts +46 -5
  59. package/dist/composition/enumerate.d.ts.map +1 -1
  60. package/dist/composition/enumerate.js +37 -8
  61. package/dist/composition/enumerate.js.map +1 -1
  62. package/dist/composition/expr-simplify.d.ts.map +1 -1
  63. package/dist/composition/expr-simplify.js +3 -11
  64. package/dist/composition/expr-simplify.js.map +1 -1
  65. package/dist/composition/mathts-scalar-symbols.d.ts +48 -0
  66. package/dist/composition/mathts-scalar-symbols.d.ts.map +1 -0
  67. package/dist/composition/mathts-scalar-symbols.js +150 -0
  68. package/dist/composition/mathts-scalar-symbols.js.map +1 -0
  69. package/dist/composition/probe/study.js +1 -1
  70. package/dist/composition/probe/study.js.map +1 -1
  71. package/dist/composition/uncertainty.d.ts +2 -2
  72. package/dist/composition/uncertainty.d.ts.map +1 -1
  73. package/dist/composition/uncertainty.js +14 -13
  74. package/dist/composition/uncertainty.js.map +1 -1
  75. package/dist/diff/bridge-gradient.d.ts +5 -8
  76. package/dist/diff/bridge-gradient.d.ts.map +1 -1
  77. package/dist/diff/bridge-gradient.js +5 -8
  78. package/dist/diff/bridge-gradient.js.map +1 -1
  79. package/dist/dimensional/buckingham.d.ts.map +1 -1
  80. package/dist/dimensional/buckingham.js +19 -91
  81. package/dist/dimensional/buckingham.js.map +1 -1
  82. package/dist/dimensional/units.d.ts +13 -0
  83. package/dist/dimensional/units.d.ts.map +1 -1
  84. package/dist/dimensional/units.js +78 -1
  85. package/dist/dimensional/units.js.map +1 -1
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +4 -4
  89. package/dist/index.js.map +1 -1
  90. package/dist/numerical/binding-value.d.ts +3 -3
  91. package/dist/numerical/binding-value.d.ts.map +1 -1
  92. package/dist/numerical/binding-value.js +60 -31
  93. package/dist/numerical/binding-value.js.map +1 -1
  94. package/dist/numerical/einstein-equation.js +3 -3
  95. package/dist/numerical/einstein-equation.js.map +1 -1
  96. package/dist/numerical/engine-registry.d.ts +12 -27
  97. package/dist/numerical/engine-registry.d.ts.map +1 -1
  98. package/dist/numerical/engine-registry.js +10 -65
  99. package/dist/numerical/engine-registry.js.map +1 -1
  100. package/dist/numerical/errors.d.ts +1 -2
  101. package/dist/numerical/errors.d.ts.map +1 -1
  102. package/dist/numerical/errors.js +1 -2
  103. package/dist/numerical/errors.js.map +1 -1
  104. package/dist/numerical/formula-contract.d.ts +60 -0
  105. package/dist/numerical/formula-contract.d.ts.map +1 -0
  106. package/dist/numerical/formula-contract.js +107 -0
  107. package/dist/numerical/formula-contract.js.map +1 -0
  108. package/dist/numerical/formula-dimension.d.ts +33 -10
  109. package/dist/numerical/formula-dimension.d.ts.map +1 -1
  110. package/dist/numerical/formula-dimension.js +48 -35
  111. package/dist/numerical/formula-dimension.js.map +1 -1
  112. package/dist/numerical/formula-mathts.d.ts +15 -17
  113. package/dist/numerical/formula-mathts.d.ts.map +1 -1
  114. package/dist/numerical/formula-mathts.js +34 -36
  115. package/dist/numerical/formula-mathts.js.map +1 -1
  116. package/dist/numerical/formula-registry.d.ts +11 -19
  117. package/dist/numerical/formula-registry.d.ts.map +1 -1
  118. package/dist/numerical/formula-registry.js +16 -79
  119. package/dist/numerical/formula-registry.js.map +1 -1
  120. package/dist/numerical/geodesic-integrator.d.ts.map +1 -1
  121. package/dist/numerical/geodesic-integrator.js +19 -31
  122. package/dist/numerical/geodesic-integrator.js.map +1 -1
  123. package/dist/numerical/gl4-integrator.d.ts +12 -51
  124. package/dist/numerical/gl4-integrator.d.ts.map +1 -1
  125. package/dist/numerical/gl4-integrator.js +103 -565
  126. package/dist/numerical/gl4-integrator.js.map +1 -1
  127. package/dist/numerical/index.d.ts +0 -2
  128. package/dist/numerical/index.d.ts.map +1 -1
  129. package/dist/numerical/index.js +0 -2
  130. package/dist/numerical/index.js.map +1 -1
  131. package/dist/numerical/kretschmann.d.ts +2 -2
  132. package/dist/numerical/kretschmann.js +2 -2
  133. package/dist/numerical/mathts-engine.d.ts +7 -8
  134. package/dist/numerical/mathts-engine.d.ts.map +1 -1
  135. package/dist/numerical/mathts-engine.js +19 -27
  136. package/dist/numerical/mathts-engine.js.map +1 -1
  137. package/dist/numerical/null-ray-integrator.d.ts.map +1 -1
  138. package/dist/numerical/null-ray-integrator.js +11 -28
  139. package/dist/numerical/null-ray-integrator.js.map +1 -1
  140. package/dist/numerical/quadrature.d.ts +2 -17
  141. package/dist/numerical/quadrature.d.ts.map +1 -1
  142. package/dist/numerical/quadrature.js +9 -20
  143. package/dist/numerical/quadrature.js.map +1 -1
  144. package/dist/numerical/strides.d.ts +1 -2
  145. package/dist/numerical/strides.d.ts.map +1 -1
  146. package/dist/numerical/strides.js +1 -2
  147. package/dist/numerical/strides.js.map +1 -1
  148. package/dist/numerical/tensor-engine.d.ts +2 -3
  149. package/dist/numerical/tensor-engine.d.ts.map +1 -1
  150. package/dist/numerical/tensor-engine.js +1 -2
  151. package/dist/numerical/tensor-engine.js.map +1 -1
  152. package/dist/relations/category.d.ts +8 -2
  153. package/dist/relations/category.d.ts.map +1 -1
  154. package/dist/relations/category.js +10 -2
  155. package/dist/relations/category.js.map +1 -1
  156. package/dist/relations/regime-vocabularies.d.ts +40 -0
  157. package/dist/relations/regime-vocabularies.d.ts.map +1 -0
  158. package/dist/relations/regime-vocabularies.js +19 -0
  159. package/dist/relations/regime-vocabularies.js.map +1 -0
  160. package/package.json +12 -46
  161. package/dist/numerical/float64-engine.d.ts +0 -55
  162. package/dist/numerical/float64-engine.d.ts.map +0 -1
  163. package/dist/numerical/float64-engine.js +0 -675
  164. package/dist/numerical/float64-engine.js.map +0 -1
  165. package/dist/numerical/formula.d.ts +0 -106
  166. package/dist/numerical/formula.d.ts.map +0 -1
  167. package/dist/numerical/formula.js +0 -348
  168. package/dist/numerical/formula.js.map +0 -1
@@ -1,675 +0,0 @@
1
- /**
2
- * Float64ReferenceEngine — the pure-TypeScript, Float64Array-backed
3
- * TensorEngine implementation. v0.3.5's default engine. Zero runtime
4
- * dependencies. Naive O(n) algorithms: a correctness baseline, not a
5
- * performance target (v0.3.5-Design.md §13). MathTSEngine (Task 11) is
6
- * the performance answer.
7
- *
8
- * @module numerical/float64-engine
9
- */
10
- import { NumericalBackendError } from './errors.js';
11
- import { rowMajorStrides, flatIndex, sameShape } from './strides.js';
12
- // ---------------------------------------------------------------------------
13
- // Private: forward-mode AD via dual numbers
14
- // ---------------------------------------------------------------------------
15
- /**
16
- * EngineDualTensor — a primal Float64Array + per-element tangent, both of
17
- * the same length. Implements the dual-number rules:
18
- * (a, a') + (b, b') = (a+b, a'+b')
19
- * (a, a') - (b, b') = (a-b, a'-b')
20
- * (a, a') * (b, b') = (a·b, a·b' + a'·b) [alias case: 2a·a']
21
- * scale((a, a'), k) = (k·a, k·a')
22
- *
23
- * S5 fix: `.data` returns the primal so that engine ops that reach into
24
- * `EngineTensor.data` still work structurally (AD-aware branches detect
25
- * `'tangent' in arg` BEFORE falling through to primal paths).
26
- *
27
- * @internal
28
- */
29
- class EngineDualTensor {
30
- shape;
31
- primal;
32
- tangent;
33
- constructor(shape, primal, tangent) {
34
- this.shape = shape;
35
- this.primal = primal;
36
- this.tangent = tangent;
37
- }
38
- /** S5 fix: primal acts as `.data` for structurally-compatible dispatch. */
39
- get data() { return this.primal; }
40
- add(other) {
41
- const p = new Float64Array(this.primal.length);
42
- const t = new Float64Array(this.tangent.length);
43
- for (let i = 0; i < p.length; i++) {
44
- p[i] = this.primal[i] + other.primal[i];
45
- t[i] = this.tangent[i] + other.tangent[i];
46
- }
47
- return new EngineDualTensor(this.shape, p, t);
48
- }
49
- sub(other) {
50
- const p = new Float64Array(this.primal.length);
51
- const t = new Float64Array(this.tangent.length);
52
- for (let i = 0; i < p.length; i++) {
53
- p[i] = this.primal[i] - other.primal[i];
54
- t[i] = this.tangent[i] - other.tangent[i];
55
- }
56
- return new EngineDualTensor(this.shape, p, t);
57
- }
58
- /**
59
- * Elementwise mul with I3 alias check: `if (this === other)` applies
60
- * (a·a)' = 2·a·a' directly instead of the split path.
61
- */
62
- mul(other) {
63
- const p = new Float64Array(this.primal.length);
64
- const t = new Float64Array(this.tangent.length);
65
- if (this === other) {
66
- for (let i = 0; i < p.length; i++) {
67
- p[i] = this.primal[i] * this.primal[i];
68
- t[i] = 2 * this.primal[i] * this.tangent[i];
69
- }
70
- }
71
- else {
72
- for (let i = 0; i < p.length; i++) {
73
- p[i] = this.primal[i] * other.primal[i];
74
- t[i] = this.tangent[i] * other.primal[i] + this.primal[i] * other.tangent[i];
75
- }
76
- }
77
- return new EngineDualTensor(this.shape, p, t);
78
- }
79
- scale(k) {
80
- const p = new Float64Array(this.primal.length);
81
- const t = new Float64Array(this.tangent.length);
82
- for (let i = 0; i < p.length; i++) {
83
- p[i] = this.primal[i] * k;
84
- t[i] = this.tangent[i] * k;
85
- }
86
- return new EngineDualTensor(this.shape, p, t);
87
- }
88
- }
89
- /**
90
- * EngineTape — records op backward closures during the forward pass.
91
- *
92
- * S3 fix: disjoint ID namespaces. `allocate()` returns `nextInputId--`
93
- * (negatives ← inputs); `record()` returns `nextOpId++` (non-negatives ← ops).
94
- * Eliminates the collision that arises when id = nodes.length + some constant
95
- * eventually wraps around with op ids.
96
- *
97
- * E19 fix: `backward()` uses `slot.set(outputGrad)` to overwrite the seed slot
98
- * (not +=), so re-invoking backward() is idempotent and avoids double-counting.
99
- *
100
- * @internal
101
- */
102
- class EngineTape {
103
- nodes = [];
104
- inputGradSlots = new Map();
105
- nextOpId = 0;
106
- nextInputId = -1; // negatives = inputs; non-negatives = ops
107
- allocate(size) {
108
- const id = this.nextInputId--;
109
- const gradSlot = new Float64Array(size);
110
- this.inputGradSlots.set(id, gradSlot);
111
- return { id, gradSlot };
112
- }
113
- record(inputIds, outputSize, backward) {
114
- const outputGradSlot = new Float64Array(outputSize);
115
- const id = this.nextOpId++;
116
- this.nodes.push({ inputIds, backward, outputGradSlot });
117
- this.inputGradSlots.set(id, outputGradSlot);
118
- return { id, gradSlot: outputGradSlot };
119
- }
120
- /** E19 fix: overwrite (not +=) the seed slot, then walk tape in reverse. */
121
- backward(outputId, outputGrad) {
122
- const slot = this.inputGradSlots.get(outputId);
123
- if (!slot) {
124
- throw new NumericalBackendError(`EngineTape.backward: unknown outputId ${outputId}`);
125
- }
126
- slot.set(outputGrad); // E19 fix: overwrite, not accumulate
127
- for (let n = this.nodes.length - 1; n >= 0; n--) {
128
- this.nodes[n].backward(this.nodes[n].outputGradSlot);
129
- }
130
- }
131
- getInputGrad(id) {
132
- return this.inputGradSlots.get(id);
133
- }
134
- }
135
- /**
136
- * EngineTapedTensor — a primal Float64Array + tape reference + node id.
137
- * Arithmetic methods register backward closures on the shared tape.
138
- *
139
- * S5 fix: `.data` returns the primal for structural compatibility with ops
140
- * that reach into `EngineTensor.data`.
141
- *
142
- * I3 fix: `mul()` checks `if (this === other)` and accumulates
143
- * `2·outputGrad·primal` instead of the aliasing-accident path.
144
- *
145
- * @internal
146
- */
147
- class EngineTapedTensor {
148
- shape;
149
- primal;
150
- tape;
151
- id;
152
- constructor(shape, primal, tape, id) {
153
- this.shape = shape;
154
- this.primal = primal;
155
- this.tape = tape;
156
- this.id = id;
157
- }
158
- /** S5 fix: primal acts as `.data` for structurally-compatible dispatch. */
159
- get data() { return this.primal; }
160
- static fromInput(t, tape) {
161
- const { id } = tape.allocate(t.data.length);
162
- return new EngineTapedTensor(t.shape, new Float64Array(t.data), tape, id);
163
- }
164
- add(other) {
165
- const out = new Float64Array(this.primal.length);
166
- for (let i = 0; i < out.length; i++)
167
- out[i] = this.primal[i] + other.primal[i];
168
- const thisGradSlot = this.tape.getInputGrad(this.id);
169
- const otherGradSlot = this.tape.getInputGrad(other.id);
170
- const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
171
- for (let i = 0; i < outputGrad.length; i++) {
172
- thisGradSlot[i] += outputGrad[i];
173
- otherGradSlot[i] += outputGrad[i];
174
- }
175
- });
176
- return new EngineTapedTensor(this.shape, out, this.tape, id);
177
- }
178
- sub(other) {
179
- const out = new Float64Array(this.primal.length);
180
- for (let i = 0; i < out.length; i++)
181
- out[i] = this.primal[i] - other.primal[i];
182
- const thisGradSlot = this.tape.getInputGrad(this.id);
183
- const otherGradSlot = this.tape.getInputGrad(other.id);
184
- const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
185
- for (let i = 0; i < outputGrad.length; i++) {
186
- thisGradSlot[i] += outputGrad[i];
187
- otherGradSlot[i] -= outputGrad[i];
188
- }
189
- });
190
- return new EngineTapedTensor(this.shape, out, this.tape, id);
191
- }
192
- /**
193
- * I3 fix: explicit alias check. When `this === other`, accumulate
194
- * `2·outputGrad·primal` (d(a²)/da = 2a via VJP) instead of the
195
- * split-path that relies on aliasing accidents.
196
- */
197
- mul(other) {
198
- const out = new Float64Array(this.primal.length);
199
- for (let i = 0; i < out.length; i++)
200
- out[i] = this.primal[i] * other.primal[i];
201
- const thisPrimal = this.primal;
202
- const otherPrimal = other.primal;
203
- const thisGradSlot = this.tape.getInputGrad(this.id);
204
- const otherGradSlot = this.tape.getInputGrad(other.id);
205
- const isAliased = this === other;
206
- const { id } = this.tape.record([this.id, other.id], out.length, (outputGrad) => {
207
- for (let i = 0; i < outputGrad.length; i++) {
208
- if (isAliased) {
209
- thisGradSlot[i] += 2 * outputGrad[i] * thisPrimal[i];
210
- }
211
- else {
212
- thisGradSlot[i] += outputGrad[i] * otherPrimal[i];
213
- otherGradSlot[i] += outputGrad[i] * thisPrimal[i];
214
- }
215
- }
216
- });
217
- return new EngineTapedTensor(this.shape, out, this.tape, id);
218
- }
219
- scale(k) {
220
- const out = new Float64Array(this.primal.length);
221
- for (let i = 0; i < out.length; i++)
222
- out[i] = this.primal[i] * k;
223
- const thisGradSlot = this.tape.getInputGrad(this.id);
224
- const { id } = this.tape.record([this.id], out.length, (outputGrad) => {
225
- for (let i = 0; i < outputGrad.length; i++) {
226
- thisGradSlot[i] += outputGrad[i] * k;
227
- }
228
- });
229
- return new EngineTapedTensor(this.shape, out, this.tape, id);
230
- }
231
- }
232
- /** Row-major Float64Array-backed tensor. `strides[k]` is the flat-index
233
- * step for axis k. Rank-0 has shape [] and a length-1 data array.
234
- * @internal — concrete EngineTensor of Float64ReferenceEngine; consumers
235
- * operate on the opaque `EngineTensor` handle, not this class. */
236
- class Float64Tensor {
237
- shape;
238
- data;
239
- constructor(shape, data) {
240
- this.shape = shape;
241
- this.data = data;
242
- }
243
- static sizeOf(shape) {
244
- return shape.reduce((a, b) => a * b, 1);
245
- }
246
- }
247
- function flatten(data, shape) {
248
- const size = Float64Tensor.sizeOf(shape);
249
- const out = new Float64Array(size);
250
- let cursor = 0;
251
- const walk = (node, depth) => {
252
- if (depth === shape.length) {
253
- if (typeof node !== 'number') {
254
- throw new NumericalBackendError(`fromNested: expected a number at depth ${depth}, got ${typeof node}`);
255
- }
256
- out[cursor++] = node;
257
- return;
258
- }
259
- if (!Array.isArray(node) || node.length !== shape[depth]) {
260
- throw new NumericalBackendError(`fromNested: shape mismatch at depth ${depth} — expected length ${shape[depth]}`);
261
- }
262
- for (const child of node)
263
- walk(child, depth + 1);
264
- };
265
- walk(data, 0);
266
- if (cursor !== size) {
267
- throw new NumericalBackendError(`fromNested: filled ${cursor} of ${size} elements`);
268
- }
269
- return out;
270
- }
271
- function rebuild(t) {
272
- if (t.shape.length === 0)
273
- return t.data[0];
274
- const strides = rowMajorStrides(t.shape);
275
- const build = (depth, offset) => {
276
- if (depth === t.shape.length - 1) {
277
- const row = [];
278
- for (let i = 0; i < t.shape[depth]; i++)
279
- row.push(t.data[offset + i]);
280
- return row;
281
- }
282
- const out = [];
283
- for (let i = 0; i < t.shape[depth]; i++) {
284
- out.push(build(depth + 1, offset + i * strides[depth]));
285
- }
286
- return out;
287
- };
288
- return build(0, 0);
289
- }
290
- function asF64(t, op) {
291
- if (!(t instanceof Float64Tensor)) {
292
- throw new NumericalBackendError(`Float64ReferenceEngine.${op}: operand is not a Float64Tensor`);
293
- }
294
- return t;
295
- }
296
- function elementwise(a, b, op, f) {
297
- if (!sameShape(a.shape, b.shape)) {
298
- throw new NumericalBackendError(`Float64ReferenceEngine.${op}: shape mismatch [${a.shape}] vs [${b.shape}]`);
299
- }
300
- const out = new Float64Array(a.data.length);
301
- for (let i = 0; i < a.data.length; i++)
302
- out[i] = f(a.data[i], b.data[i]);
303
- return new Float64Tensor(a.shape, out);
304
- }
305
- /** Iterate every multi-index of `shape` in row-major order, calling `visit`
306
- * with a fresh index array each step. */
307
- function forEachIndex(shape, visit) {
308
- if (shape.length === 0) {
309
- visit([]);
310
- return;
311
- }
312
- const idx = new Array(shape.length).fill(0);
313
- const total = Float64Tensor.sizeOf(shape);
314
- for (let n = 0; n < total; n++) {
315
- visit(idx);
316
- for (let k = shape.length - 1; k >= 0; k--) {
317
- if (++idx[k] < shape[k])
318
- break;
319
- idx[k] = 0;
320
- }
321
- }
322
- }
323
- /**
324
- * Shared AD dispatch for the elementwise binary ops (`add`/`sub`/`mul`):
325
- * forward-mode (dual) and reverse-mode (tape) operands route to the tracing
326
- * classes' own same-named method; everything else falls through to the primal
327
- * Float64 kernel. `instanceof` is safe — both AD classes are private to this
328
- * module and cannot be confused with unrelated types (TS-3). The dual/tape
329
- * `add`/`sub`/`mul` share one signature, so the `a[method](b)` call is type-safe.
330
- */
331
- function adBinary(a, b, method, prim) {
332
- if (a instanceof EngineDualTensor && b instanceof EngineDualTensor) {
333
- return a[method](b);
334
- }
335
- if (a instanceof EngineTapedTensor && b instanceof EngineTapedTensor) {
336
- return a[method](b);
337
- }
338
- return elementwise(asF64(a, method), asF64(b, method), method, prim);
339
- }
340
- /**
341
- * The pure-TypeScript, zero-dependency `TensorEngine` — the correctness
342
- * baseline and conformance reference. Fallback engine in v0.4.0+: active
343
- * when @danielsimonjr/mathts-tensor is not installed (see engine-registry.ts).
344
- * Was the default in v0.3.5 before MathTSEngine was introduced.
345
- * @public
346
- */
347
- export class Float64ReferenceEngine {
348
- name = 'Float64ReferenceEngine';
349
- fromNested(data, shape) {
350
- return new Float64Tensor(shape, flatten(data, shape));
351
- }
352
- toNested(t) {
353
- return rebuild(asF64(t, 'toNested'));
354
- }
355
- add(a, b) {
356
- return adBinary(a, b, 'add', (x, y) => x + y);
357
- }
358
- sub(a, b) {
359
- return adBinary(a, b, 'sub', (x, y) => x - y);
360
- }
361
- mul(a, b) {
362
- return adBinary(a, b, 'mul', (x, y) => x * y);
363
- }
364
- scale(t, k) {
365
- // AD dispatch: dual path (forward-mode)
366
- if (t instanceof EngineDualTensor) {
367
- return t.scale(k);
368
- }
369
- // AD dispatch: tape path (reverse-mode)
370
- if (t instanceof EngineTapedTensor) {
371
- return t.scale(k);
372
- }
373
- const f = asF64(t, 'scale');
374
- const out = new Float64Array(f.data.length);
375
- for (let i = 0; i < f.data.length; i++)
376
- out[i] = f.data[i] * k;
377
- return new Float64Tensor(f.shape, out);
378
- }
379
- identity(n) {
380
- const out = new Float64Array(n * n);
381
- for (let i = 0; i < n; i++)
382
- out[i * n + i] = 1;
383
- return new Float64Tensor([n, n], out);
384
- }
385
- normInf(t) {
386
- const f = asF64(t, 'normInf');
387
- let max = 0;
388
- for (let i = 0; i < f.data.length; i++) {
389
- const a = Math.abs(f.data[i]);
390
- if (a > max)
391
- max = a;
392
- }
393
- return max;
394
- }
395
- reshape(t, shape) {
396
- const f = asF64(t, 'reshape');
397
- if (Float64Tensor.sizeOf(shape) !== f.data.length) {
398
- throw new NumericalBackendError(`reshape: size mismatch — [${f.shape}] (${f.data.length}) -> [${shape}]`);
399
- }
400
- // Row-major storage is order-preserving: same data buffer, new shape.
401
- return new Float64Tensor([...shape], f.data.slice());
402
- }
403
- transpose(t, perm) {
404
- const f = asF64(t, 'transpose');
405
- const rank = f.shape.length;
406
- let p = perm;
407
- if (!p) {
408
- const pArr = new Array(rank);
409
- for (let i = 0; i < rank; i++) {
410
- pArr[i] = rank - 1 - i;
411
- }
412
- p = pArr;
413
- }
414
- if (p.length !== rank) {
415
- throw new NumericalBackendError(`transpose: perm length ${p.length} != rank ${rank}`);
416
- }
417
- const outShape = p.map((axis) => f.shape[axis]);
418
- const inStrides = rowMajorStrides(f.shape);
419
- const out = new Float64Array(f.data.length);
420
- const outStrides = rowMajorStrides(outShape);
421
- // Bolt: Hoist array allocations out of the iteration loop and use explicit while/for loops
422
- // to track multi-index odometers. This completely bypasses the function call closure overhead
423
- // of `forEachIndex` and eliminates `new Array` dynamic allocations during traversal.
424
- const outIdx = new Array(rank).fill(0);
425
- const inIdx = new Array(rank).fill(0);
426
- const total = f.data.length;
427
- for (let n = 0; n < total; n++) {
428
- // Map back to input index
429
- for (let k = 0; k < rank; k++)
430
- inIdx[p[k]] = outIdx[k];
431
- // Bolt: out[n] is mathematically equivalent to out[flatIndex(outIdx, outStrides)]
432
- // because outIdx increments monotonically in row-major order alongside n.
433
- out[n] = f.data[flatIndex(inIdx, inStrides)];
434
- // Increment multi-index odometer
435
- for (let k = rank - 1; k >= 0; k--) {
436
- if (++outIdx[k] < outShape[k])
437
- break;
438
- outIdx[k] = 0;
439
- }
440
- }
441
- return new Float64Tensor(outShape, out);
442
- }
443
- matMul(a, b) {
444
- const fa = asF64(a, 'matMul');
445
- const fb = asF64(b, 'matMul');
446
- if (fa.shape.length !== 2 || fb.shape.length !== 2) {
447
- throw new NumericalBackendError(`matMul: both operands must be rank-2 — got [${fa.shape}], [${fb.shape}]`);
448
- }
449
- const [m, k] = fa.shape;
450
- const [k2, n] = fb.shape;
451
- if (k !== k2) {
452
- throw new NumericalBackendError(`matMul: inner dimension mismatch ${k} != ${k2}`);
453
- }
454
- const out = new Float64Array(m * n);
455
- for (let i = 0; i < m; i++) {
456
- for (let j = 0; j < n; j++) {
457
- let sum = 0;
458
- for (let p = 0; p < k; p++)
459
- sum += fa.data[i * k + p] * fb.data[p * n + j];
460
- out[i * n + j] = sum;
461
- }
462
- }
463
- return new Float64Tensor([m, n], out);
464
- }
465
- einsum(spec, ...operands) {
466
- const ops = operands.map((o, i) => asF64(o, `einsum (operand ${i})`));
467
- const inStrides = ops.map((o) => rowMajorStrides(o.shape));
468
- // Each free axis -> one output index variable; each contraction -> one
469
- // summed index variable. Determine the size of every variable and the
470
- // (operand, axis) sites that read it.
471
- const freeSizes = spec.free.map((fa) => {
472
- const op = ops[fa.operand];
473
- if (!op)
474
- throw new NumericalBackendError(`einsum: free axis references missing operand ${fa.operand}`);
475
- return op.shape[fa.axis];
476
- });
477
- const contractSizes = spec.contractions.map((c) => {
478
- const [[oa, axa], [ob, axb]] = c.pair;
479
- const sa = ops[oa]?.shape[axa];
480
- const sb = ops[ob]?.shape[axb];
481
- if (sa === undefined || sb === undefined || sa !== sb) {
482
- throw new NumericalBackendError(`einsum: contraction pair size mismatch — (${oa},${axa})=${sa} vs (${ob},${axb})=${sb}`);
483
- }
484
- return sa;
485
- });
486
- const outShape = freeSizes;
487
- const outStrides = rowMajorStrides(outShape);
488
- const out = new Float64Array(Float64Tensor.sizeOf(outShape));
489
- // Guard (finding #2): every axis of a rank-≥1 operand MUST appear in the
490
- // spec as either a free axis or a contracted axis. An unreferenced axis
491
- // would silently read index 0 on every iteration — a wrong result. A
492
- // rank-0 operand legitimately participates in nothing: it contributes its
493
- // scalar value data[0] as a factor to every output element. That is the
494
- // documented, intended behaviour — only rank-≥1 operands are guarded.
495
- for (let o = 0; o < ops.length; o++) {
496
- if (ops[o].shape.length === 0)
497
- continue; // rank-0 scalar factor — allowed
498
- const covered = new Set();
499
- spec.free.forEach((fa) => { if (fa.operand === o)
500
- covered.add(fa.axis); });
501
- spec.contractions.forEach((c) => {
502
- const [[oa, axa], [ob, axb]] = c.pair;
503
- if (oa === o)
504
- covered.add(axa);
505
- if (ob === o)
506
- covered.add(axb);
507
- });
508
- if (covered.size !== ops[o].shape.length) {
509
- throw new NumericalBackendError(`einsum: operand ${o} (rank ${ops[o].shape.length}) has axes not referenced `
510
- + `by the spec — every rank-≥1 operand axis must be a free or contracted axis`);
511
- }
512
- }
513
- const freeAxesByOp = new Array(ops.length);
514
- const contractAxesByOp = new Array(ops.length);
515
- for (let i = 0; i < ops.length; i++) {
516
- freeAxesByOp[i] = [];
517
- contractAxesByOp[i] = [];
518
- }
519
- spec.free.forEach((fa, v) => {
520
- freeAxesByOp[fa.operand].push({ varIdx: v, axis: fa.axis });
521
- });
522
- spec.contractions.forEach((c, v) => {
523
- const [[oa, axa], [ob, axb]] = c.pair;
524
- contractAxesByOp[oa].push({ varIdx: v, axis: axa });
525
- contractAxesByOp[ob].push({ varIdx: v, axis: axb });
526
- });
527
- // For a given assignment of (free vars, contract vars), compute the flat
528
- // index into each operand using the precomputed per-operand axis maps.
529
- const freeVals = new Array(outShape.length).fill(0);
530
- const contractVals = new Array(contractSizes.length).fill(0);
531
- const outTotal = outShape.length === 0 ? 1 : Float64Tensor.sizeOf(outShape);
532
- const contractTotal = contractSizes.length === 0 ? 1 : Float64Tensor.sizeOf(contractSizes);
533
- // Bolt: Hoist array allocations and flatIndex math out of the innermost loop.
534
- // freeBaseIndices[o] tracks the base flat index of operand o for the current freeVals.
535
- const freeBaseIndices = new Int32Array(ops.length);
536
- const contractBaseIndices = new Int32Array(ops.length);
537
- for (let i = 0; i < outTotal; i++) {
538
- let acc = 0;
539
- for (let o = 0; o < ops.length; o++) {
540
- let f = 0;
541
- const fa = freeAxesByOp[o];
542
- const s = inStrides[o];
543
- for (let m = 0; m < fa.length; m++) {
544
- f += freeVals[fa[m].varIdx] * s[fa[m].axis];
545
- }
546
- freeBaseIndices[o] = f;
547
- }
548
- for (let j = 0; j < contractVals.length; j++)
549
- contractVals[j] = 0;
550
- for (let j = 0; j < contractTotal; j++) {
551
- // Pre-compute contract indices for this iteration of contractVals
552
- for (let o = 0; o < ops.length; o++) {
553
- let f = freeBaseIndices[o];
554
- const ca = contractAxesByOp[o];
555
- const s = inStrides[o];
556
- for (let m = 0; m < ca.length; m++) {
557
- f += contractVals[ca[m].varIdx] * s[ca[m].axis];
558
- }
559
- contractBaseIndices[o] = f;
560
- }
561
- let product = 1;
562
- for (let o = 0; o < ops.length; o++) {
563
- product *= ops[o].data[contractBaseIndices[o]];
564
- }
565
- acc += product;
566
- for (let k = contractSizes.length - 1; k >= 0; k--) {
567
- if (++contractVals[k] < contractSizes[k])
568
- break;
569
- contractVals[k] = 0;
570
- }
571
- }
572
- out[flatIndex(freeVals, outStrides)] = acc;
573
- for (let k = outShape.length - 1; k >= 0; k--) {
574
- if (++freeVals[k] < outShape[k])
575
- break;
576
- freeVals[k] = 0;
577
- }
578
- }
579
- return new Float64Tensor(outShape, out);
580
- }
581
- // -------------------------------------------------------------------------
582
- // Forward-mode AD (Jacobian-vector product via dual numbers)
583
- // -------------------------------------------------------------------------
584
- /**
585
- * Compute the value and full Jacobian of `fn` at point `x` using
586
- * forward-mode automatic differentiation (dual numbers).
587
- *
588
- * Returns `{ value, jacobian }` where `jacobian.shape = [...value.shape, ...x.shape]`
589
- * (row-major). `jacobian.data[kY * xSize + kX] = ∂y[kY] / ∂x[kX]`.
590
- *
591
- * Guard: if `fn` returns a non-EngineDualTensor, throws a clear error
592
- * ("AD-traceable" guard, reconciliation fix S6/I2).
593
- */
594
- async forwardGrad(fn, x) {
595
- const xData = x.data;
596
- const xSize = xData.length;
597
- // Probe with zero tangent to learn the output shape.
598
- const xDualZero = new EngineDualTensor(x.shape, new Float64Array(xData), new Float64Array(xSize));
599
- const yProbeRaw = fn(xDualZero);
600
- if (!('tangent' in yProbeRaw)) {
601
- throw new NumericalBackendError('Float64ReferenceEngine.forwardGrad: fn must be AD-traceable — its return must ' +
602
- 'propagate through EngineDualTensor arithmetic (use engine.add/sub/mul/scale on ' +
603
- 'the argument). A plain-tensor return loses the tangent and corrupts the Jacobian.');
604
- }
605
- const yProbe = yProbeRaw;
606
- const ySize = yProbe.primal.length;
607
- const jacobianShape = [...yProbe.shape, ...x.shape];
608
- const jacobianData = new Float64Array(jacobianShape.reduce((a, b) => a * b, 1));
609
- // Sweep each input flat-index kX. Build a unit-tangent dual, run fn,
610
- // scatter the tangent into the Jacobian column.
611
- for (let kX = 0; kX < xSize; kX++) {
612
- const tan = new Float64Array(xSize);
613
- tan[kX] = 1;
614
- const xDualUnit = new EngineDualTensor(x.shape, new Float64Array(xData), tan);
615
- const yDualRaw = fn(xDualUnit);
616
- if (!('tangent' in yDualRaw)) {
617
- throw new NumericalBackendError('Float64ReferenceEngine.forwardGrad: fn lost AD trace mid-sweep (returned non-dual tensor)');
618
- }
619
- const yDual = yDualRaw;
620
- for (let kY = 0; kY < ySize; kY++) {
621
- jacobianData[kY * xSize + kX] = yDual.tangent[kY];
622
- }
623
- }
624
- return {
625
- value: new Float64Tensor(yProbe.shape, new Float64Array(yProbe.primal)),
626
- jacobian: new Float64Tensor(jacobianShape, jacobianData),
627
- };
628
- }
629
- // -------------------------------------------------------------------------
630
- // Reverse-mode AD (vector-Jacobian product via tape)
631
- // -------------------------------------------------------------------------
632
- /**
633
- * Compute the value and gradient (VJP) of `fn` at point `x` using
634
- * reverse-mode automatic differentiation (tape).
635
- *
636
- * `cotangent` defaults to ones-like(value). For non-scalar outputs,
637
- * cotangent.shape must match value.shape.
638
- *
639
- * Guard: if `fn` returns a non-EngineTapedTensor, throws a clear error.
640
- */
641
- async reverseGrad(fn, x, cotangent) {
642
- const tape = new EngineTape();
643
- const xF64 = x;
644
- const xTaped = EngineTapedTensor.fromInput(xF64, tape);
645
- const yRaw = fn(xTaped);
646
- if (!('tape' in yRaw)) {
647
- throw new NumericalBackendError('Float64ReferenceEngine.reverseGrad: fn must be AD-traceable — its return must ' +
648
- 'propagate through EngineTapedTensor arithmetic (use engine.add/sub/mul/scale on ' +
649
- 'the argument). A plain-tensor return loses the tape and corrupts the gradient.');
650
- }
651
- const yTaped = yRaw;
652
- const value = new Float64Tensor(yTaped.shape, new Float64Array(yTaped.primal));
653
- // Resolve cotangent.
654
- let ctData;
655
- if (cotangent === undefined) {
656
- ctData = new Float64Array(yTaped.primal.length).fill(1);
657
- }
658
- else {
659
- if (cotangent.shape.length !== value.shape.length ||
660
- !cotangent.shape.every((v, i) => v === value.shape[i])) {
661
- throw new NumericalBackendError(`Float64ReferenceEngine.reverseGrad: cotangent shape [${cotangent.shape}] ` +
662
- `!= value shape [${value.shape}]`);
663
- }
664
- ctData = new Float64Array(cotangent.data);
665
- }
666
- // E19 fix: backward() uses slot.set(outputGrad) — see EngineTape.backward.
667
- tape.backward(yTaped.id, ctData);
668
- const xGrad = tape.getInputGrad(xTaped.id);
669
- return {
670
- value,
671
- gradient: new Float64Tensor(x.shape, new Float64Array(xGrad)),
672
- };
673
- }
674
- }
675
- //# sourceMappingURL=float64-engine.js.map