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