universal-physics-tensor 1.0.4 → 2.0.1
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/README.md +11 -13
- package/dist/atlas/catalog-formal-ref.js +1 -1
- package/dist/atlas/catalog-formal-ref.js.map +1 -1
- package/dist/atlas/chain-pipeline.d.ts +37 -0
- package/dist/atlas/chain-pipeline.d.ts.map +1 -1
- package/dist/atlas/chain-pipeline.js +95 -79
- package/dist/atlas/chain-pipeline.js.map +1 -1
- package/dist/atlas/derive-evidence.d.ts +6 -4
- package/dist/atlas/derive-evidence.d.ts.map +1 -1
- package/dist/atlas/derive-evidence.js +12 -4
- package/dist/atlas/derive-evidence.js.map +1 -1
- package/dist/atlas/physjs-ref.d.ts +2 -2
- package/dist/atlas/physjs-ref.d.ts.map +1 -1
- package/dist/atlas/physjs-ref.js +33 -3
- package/dist/atlas/physjs-ref.js.map +1 -1
- package/dist/atlas/proof-target.d.ts +28 -2
- package/dist/atlas/proof-target.d.ts.map +1 -1
- package/dist/atlas/proof-target.js +25 -16
- package/dist/atlas/proof-target.js.map +1 -1
- package/dist/bridges/be64-eddington-luminosity.d.ts.map +1 -1
- package/dist/bridges/be64-eddington-luminosity.js +1 -3
- package/dist/bridges/be64-eddington-luminosity.js.map +1 -1
- package/dist/bridges/be66-radiation-pressure.d.ts +30 -0
- package/dist/bridges/be66-radiation-pressure.d.ts.map +1 -0
- package/dist/bridges/be66-radiation-pressure.js +37 -0
- package/dist/bridges/be66-radiation-pressure.js.map +1 -0
- package/dist/bridges/be67-alfven-speed.d.ts +54 -0
- package/dist/bridges/be67-alfven-speed.d.ts.map +1 -0
- package/dist/bridges/be67-alfven-speed.js +49 -0
- package/dist/bridges/be67-alfven-speed.js.map +1 -0
- package/dist/bridges/be68-tolman-ehrenfest.d.ts +49 -0
- package/dist/bridges/be68-tolman-ehrenfest.d.ts.map +1 -0
- package/dist/bridges/be68-tolman-ehrenfest.js +46 -0
- package/dist/bridges/be68-tolman-ehrenfest.js.map +1 -0
- package/dist/bridges/bridge-equations.d.ts +6 -0
- package/dist/bridges/bridge-equations.d.ts.map +1 -1
- package/dist/bridges/bridge-equations.js +6 -0
- package/dist/bridges/bridge-equations.js.map +1 -1
- package/dist/bridges/evaluators.d.ts +3 -3
- package/dist/bridges/evaluators.d.ts.map +1 -1
- package/dist/bridges/evaluators.js +25 -4
- package/dist/bridges/evaluators.js.map +1 -1
- package/dist/bridges/index.d.ts +10 -4
- package/dist/bridges/index.d.ts.map +1 -1
- package/dist/bridges/index.js +74 -4
- package/dist/bridges/index.js.map +1 -1
- package/dist/cli/commands/_atlas-route.d.ts +1 -1
- package/dist/cli/commands/atlas.d.ts.map +1 -1
- package/dist/cli/commands/atlas.js +6 -2
- package/dist/cli/commands/atlas.js.map +1 -1
- package/dist/cli/commands/chain.d.ts.map +1 -1
- package/dist/cli/commands/chain.js +8 -3
- package/dist/cli/commands/chain.js.map +1 -1
- package/dist/cli/commands/derive.js +1 -1
- package/dist/cli/commands/eval.d.ts.map +1 -1
- package/dist/cli/commands/eval.js +31 -15
- package/dist/cli/commands/eval.js.map +1 -1
- package/dist/cli/commands/evaluate.d.ts +7 -3
- package/dist/cli/commands/evaluate.d.ts.map +1 -1
- package/dist/cli/commands/evaluate.js +53 -32
- package/dist/cli/commands/evaluate.js.map +1 -1
- package/dist/cli/commands/explain.d.ts.map +1 -1
- package/dist/cli/commands/explain.js +119 -15
- package/dist/cli/commands/explain.js.map +1 -1
- package/dist/cli/commands/map.d.ts +1 -1
- package/dist/cli/commands/map.d.ts.map +1 -1
- package/dist/cli/commands/map.js.map +1 -1
- package/dist/cli/commands/metric.d.ts.map +1 -1
- package/dist/cli/commands/metric.js +10 -11
- package/dist/cli/commands/metric.js.map +1 -1
- package/dist/cli/commands/path.d.ts +2 -2
- package/dist/cli/commands/path.d.ts.map +1 -1
- package/dist/cli/commands/path.js +10 -11
- package/dist/cli/commands/path.js.map +1 -1
- package/dist/cli/commands/recover.d.ts.map +1 -1
- package/dist/cli/commands/recover.js +1 -2
- package/dist/cli/commands/recover.js.map +1 -1
- package/dist/cli/commands/regime.d.ts +2 -2
- package/dist/cli/commands/regime.d.ts.map +1 -1
- package/dist/cli/commands/regime.js +3 -4
- package/dist/cli/commands/regime.js.map +1 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +8 -3
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/symbolic.d.ts.map +1 -1
- package/dist/cli/commands/symbolic.js +5 -0
- package/dist/cli/commands/symbolic.js.map +1 -1
- package/dist/cli/eval-numbers.d.ts +6 -4
- package/dist/cli/eval-numbers.d.ts.map +1 -1
- package/dist/cli/eval-numbers.js +11 -5
- package/dist/cli/eval-numbers.js.map +1 -1
- package/dist/cli/map-evidence.d.ts +2 -1
- package/dist/cli/map-evidence.d.ts.map +1 -1
- package/dist/cli/map-evidence.js +2 -1
- package/dist/cli/map-evidence.js.map +1 -1
- package/dist/cli/search-index.d.ts +8 -0
- package/dist/cli/search-index.d.ts.map +1 -1
- package/dist/cli/search-index.js +18 -1
- package/dist/cli/search-index.js.map +1 -1
- package/dist/cli/version.d.ts +6 -2
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +11 -3
- package/dist/cli/version.js.map +1 -1
- package/dist/cli-api.d.ts +52 -0
- package/dist/cli-api.d.ts.map +1 -1
- package/dist/cli-api.js +44 -0
- package/dist/cli-api.js.map +1 -1
- package/dist/composition/bridge-analysis.d.ts.map +1 -1
- package/dist/composition/bridge-analysis.js +18 -1
- package/dist/composition/bridge-analysis.js.map +1 -1
- package/dist/composition/canonical-graph.d.ts +9 -4
- package/dist/composition/canonical-graph.d.ts.map +1 -1
- package/dist/composition/canonical-graph.js +113 -8
- package/dist/composition/canonical-graph.js.map +1 -1
- package/dist/composition/catalog-graph.d.ts +5 -4
- package/dist/composition/catalog-graph.d.ts.map +1 -1
- package/dist/composition/catalog-graph.js +7 -4
- package/dist/composition/catalog-graph.js.map +1 -1
- package/dist/composition/chain-result.d.ts +55 -0
- package/dist/composition/chain-result.d.ts.map +1 -0
- package/dist/composition/chain-result.js +57 -0
- package/dist/composition/chain-result.js.map +1 -0
- package/dist/composition/compose-symbolic.d.ts.map +1 -1
- package/dist/composition/compose-symbolic.js +4 -39
- package/dist/composition/compose-symbolic.js.map +1 -1
- package/dist/composition/compose.d.ts.map +1 -1
- package/dist/composition/compose.js +11 -4
- package/dist/composition/compose.js.map +1 -1
- package/dist/composition/edges/applied-physicist.d.ts +43 -0
- package/dist/composition/edges/applied-physicist.d.ts.map +1 -0
- package/dist/composition/edges/applied-physicist.js +128 -0
- package/dist/composition/edges/applied-physicist.js.map +1 -0
- package/dist/composition/enumerate.d.ts +46 -5
- package/dist/composition/enumerate.d.ts.map +1 -1
- package/dist/composition/enumerate.js +37 -8
- package/dist/composition/enumerate.js.map +1 -1
- package/dist/composition/expr-simplify.d.ts.map +1 -1
- package/dist/composition/expr-simplify.js +3 -11
- package/dist/composition/expr-simplify.js.map +1 -1
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.d.ts.map +1 -1
- package/dist/composition/index.js +2 -0
- package/dist/composition/index.js.map +1 -1
- package/dist/composition/mathts-scalar-symbols.d.ts +48 -0
- package/dist/composition/mathts-scalar-symbols.d.ts.map +1 -0
- package/dist/composition/mathts-scalar-symbols.js +150 -0
- package/dist/composition/mathts-scalar-symbols.js.map +1 -0
- package/dist/composition/probe/study.js +1 -1
- package/dist/composition/probe/study.js.map +1 -1
- package/dist/composition/quantities/applied-physicist.d.ts +34 -0
- package/dist/composition/quantities/applied-physicist.d.ts.map +1 -0
- package/dist/composition/quantities/applied-physicist.js +82 -0
- package/dist/composition/quantities/applied-physicist.js.map +1 -0
- package/dist/composition/quantities.d.ts +1 -0
- package/dist/composition/quantities.d.ts.map +1 -1
- package/dist/composition/quantities.js +1 -0
- package/dist/composition/quantities.js.map +1 -1
- package/dist/composition/uncertainty.d.ts +2 -2
- package/dist/composition/uncertainty.d.ts.map +1 -1
- package/dist/composition/uncertainty.js +14 -13
- package/dist/composition/uncertainty.js.map +1 -1
- package/dist/core/constants.d.ts +16 -0
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +16 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/diff/bridge-gradient.d.ts +5 -8
- package/dist/diff/bridge-gradient.d.ts.map +1 -1
- package/dist/diff/bridge-gradient.js +5 -8
- package/dist/diff/bridge-gradient.js.map +1 -1
- package/dist/dimensional/bridge-check.d.ts +1 -1
- package/dist/dimensional/bridge-check.d.ts.map +1 -1
- package/dist/dimensional/bridge-check.js +5 -2
- package/dist/dimensional/bridge-check.js.map +1 -1
- package/dist/dimensional/buckingham.d.ts.map +1 -1
- package/dist/dimensional/buckingham.js +19 -91
- package/dist/dimensional/buckingham.js.map +1 -1
- package/dist/dimensional/dimension-spec.d.ts.map +1 -1
- package/dist/dimensional/dimension-spec.js +1 -0
- package/dist/dimensional/dimension-spec.js.map +1 -1
- package/dist/dimensional/formula-names.d.ts.map +1 -1
- package/dist/dimensional/formula-names.js +9 -1
- package/dist/dimensional/formula-names.js.map +1 -1
- package/dist/dimensional/units.d.ts +13 -0
- package/dist/dimensional/units.d.ts.map +1 -1
- package/dist/dimensional/units.js +81 -3
- package/dist/dimensional/units.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/numerical/binding-value.d.ts +21 -3
- package/dist/numerical/binding-value.d.ts.map +1 -1
- package/dist/numerical/binding-value.js +106 -33
- package/dist/numerical/binding-value.js.map +1 -1
- package/dist/numerical/einstein-equation.js +3 -3
- package/dist/numerical/einstein-equation.js.map +1 -1
- package/dist/numerical/engine-registry.d.ts +12 -27
- package/dist/numerical/engine-registry.d.ts.map +1 -1
- package/dist/numerical/engine-registry.js +10 -65
- package/dist/numerical/engine-registry.js.map +1 -1
- package/dist/numerical/errors.d.ts +1 -2
- package/dist/numerical/errors.d.ts.map +1 -1
- package/dist/numerical/errors.js +1 -2
- package/dist/numerical/errors.js.map +1 -1
- package/dist/numerical/formula-contract.d.ts +60 -0
- package/dist/numerical/formula-contract.d.ts.map +1 -0
- package/dist/numerical/formula-contract.js +107 -0
- package/dist/numerical/formula-contract.js.map +1 -0
- package/dist/numerical/formula-dimension.d.ts +33 -10
- package/dist/numerical/formula-dimension.d.ts.map +1 -1
- package/dist/numerical/formula-dimension.js +48 -35
- package/dist/numerical/formula-dimension.js.map +1 -1
- package/dist/numerical/formula-mathts.d.ts +15 -17
- package/dist/numerical/formula-mathts.d.ts.map +1 -1
- package/dist/numerical/formula-mathts.js +34 -36
- package/dist/numerical/formula-mathts.js.map +1 -1
- package/dist/numerical/formula-registry.d.ts +11 -19
- package/dist/numerical/formula-registry.d.ts.map +1 -1
- package/dist/numerical/formula-registry.js +16 -79
- package/dist/numerical/formula-registry.js.map +1 -1
- package/dist/numerical/geodesic-integrator.d.ts.map +1 -1
- package/dist/numerical/geodesic-integrator.js +19 -31
- package/dist/numerical/geodesic-integrator.js.map +1 -1
- package/dist/numerical/gl4-integrator.d.ts +12 -51
- package/dist/numerical/gl4-integrator.d.ts.map +1 -1
- package/dist/numerical/gl4-integrator.js +103 -565
- package/dist/numerical/gl4-integrator.js.map +1 -1
- package/dist/numerical/index.d.ts +0 -2
- package/dist/numerical/index.d.ts.map +1 -1
- package/dist/numerical/index.js +0 -2
- package/dist/numerical/index.js.map +1 -1
- package/dist/numerical/kretschmann.d.ts +2 -2
- package/dist/numerical/kretschmann.js +2 -2
- package/dist/numerical/mathts-engine.d.ts +7 -8
- package/dist/numerical/mathts-engine.d.ts.map +1 -1
- package/dist/numerical/mathts-engine.js +19 -27
- package/dist/numerical/mathts-engine.js.map +1 -1
- package/dist/numerical/null-ray-integrator.d.ts.map +1 -1
- package/dist/numerical/null-ray-integrator.js +11 -28
- package/dist/numerical/null-ray-integrator.js.map +1 -1
- package/dist/numerical/quadrature.d.ts +2 -17
- package/dist/numerical/quadrature.d.ts.map +1 -1
- package/dist/numerical/quadrature.js +9 -20
- package/dist/numerical/quadrature.js.map +1 -1
- package/dist/numerical/strides.d.ts +1 -2
- package/dist/numerical/strides.d.ts.map +1 -1
- package/dist/numerical/strides.js +1 -2
- package/dist/numerical/strides.js.map +1 -1
- package/dist/numerical/tensor-engine.d.ts +2 -3
- package/dist/numerical/tensor-engine.d.ts.map +1 -1
- package/dist/numerical/tensor-engine.js +1 -2
- package/dist/numerical/tensor-engine.js.map +1 -1
- package/dist/relations/category.d.ts +8 -2
- package/dist/relations/category.d.ts.map +1 -1
- package/dist/relations/category.js +10 -2
- package/dist/relations/category.js.map +1 -1
- package/dist/relations/regime-vocabularies.d.ts +40 -0
- package/dist/relations/regime-vocabularies.d.ts.map +1 -0
- package/dist/relations/regime-vocabularies.js +19 -0
- package/dist/relations/regime-vocabularies.js.map +1 -0
- package/package.json +12 -46
- package/dist/numerical/float64-engine.d.ts +0 -55
- package/dist/numerical/float64-engine.d.ts.map +0 -1
- package/dist/numerical/float64-engine.js +0 -675
- package/dist/numerical/float64-engine.js.map +0 -1
- package/dist/numerical/formula.d.ts +0 -106
- package/dist/numerical/formula.d.ts.map +0 -1
- package/dist/numerical/formula.js +0 -348
- 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
|