universal-physics-tensor 0.4.6 → 0.5.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.
Files changed (73) hide show
  1. package/dist/bridges/equations/be-37-shapiro-delay.d.ts.map +1 -1
  2. package/dist/bridges/equations/be-37-shapiro-delay.js +7 -9
  3. package/dist/bridges/equations/be-37-shapiro-delay.js.map +1 -1
  4. package/dist/bridges/gravitational-lensing.d.ts +13 -2
  5. package/dist/bridges/gravitational-lensing.d.ts.map +1 -1
  6. package/dist/bridges/gravitational-lensing.js +17 -6
  7. package/dist/bridges/gravitational-lensing.js.map +1 -1
  8. package/dist/bridges/perihelion-precession.d.ts +18 -3
  9. package/dist/bridges/perihelion-precession.d.ts.map +1 -1
  10. package/dist/bridges/perihelion-precession.js +22 -8
  11. package/dist/bridges/perihelion-precession.js.map +1 -1
  12. package/dist/core/constants.d.ts +50 -0
  13. package/dist/core/constants.d.ts.map +1 -0
  14. package/dist/core/constants.js +50 -0
  15. package/dist/core/constants.js.map +1 -0
  16. package/dist/dimensional/connection-validators.d.ts +60 -2
  17. package/dist/dimensional/connection-validators.d.ts.map +1 -1
  18. package/dist/dimensional/connection-validators.js +76 -2
  19. package/dist/dimensional/connection-validators.js.map +1 -1
  20. package/dist/dimensional/curvature.d.ts +361 -0
  21. package/dist/dimensional/curvature.d.ts.map +1 -0
  22. package/dist/dimensional/curvature.js +299 -0
  23. package/dist/dimensional/curvature.js.map +1 -0
  24. package/dist/dimensional/validator.d.ts +5 -3
  25. package/dist/dimensional/validator.d.ts.map +1 -1
  26. package/dist/dimensional/validator.js +58 -1
  27. package/dist/dimensional/validator.js.map +1 -1
  28. package/dist/index.d.ts +9 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +23 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/numerical/be37-covariant-eikonal.d.ts +112 -39
  33. package/dist/numerical/be37-covariant-eikonal.d.ts.map +1 -1
  34. package/dist/numerical/be37-covariant-eikonal.js +219 -40
  35. package/dist/numerical/be37-covariant-eikonal.js.map +1 -1
  36. package/dist/numerical/curvature-lowering-helpers.d.ts +189 -0
  37. package/dist/numerical/curvature-lowering-helpers.d.ts.map +1 -0
  38. package/dist/numerical/curvature-lowering-helpers.js +412 -0
  39. package/dist/numerical/curvature-lowering-helpers.js.map +1 -0
  40. package/dist/numerical/engine-registry.js +4 -4
  41. package/dist/numerical/engine-registry.js.map +1 -1
  42. package/dist/numerical/errors.d.ts +16 -0
  43. package/dist/numerical/errors.d.ts.map +1 -1
  44. package/dist/numerical/errors.js +20 -0
  45. package/dist/numerical/errors.js.map +1 -1
  46. package/dist/numerical/gl4-integrator.d.ts +213 -0
  47. package/dist/numerical/gl4-integrator.d.ts.map +1 -0
  48. package/dist/numerical/gl4-integrator.js +307 -0
  49. package/dist/numerical/gl4-integrator.js.map +1 -0
  50. package/dist/numerical/index.d.ts +8 -0
  51. package/dist/numerical/index.d.ts.map +1 -1
  52. package/dist/numerical/index.js +4 -0
  53. package/dist/numerical/index.js.map +1 -1
  54. package/dist/numerical/lowering.d.ts.map +1 -1
  55. package/dist/numerical/lowering.js +180 -3
  56. package/dist/numerical/lowering.js.map +1 -1
  57. package/dist/numerical/mathts-engine.js +8 -8
  58. package/dist/numerical/mathts-engine.js.map +1 -1
  59. package/dist/numerical/metric-inverse.d.ts.map +1 -1
  60. package/dist/numerical/metric-inverse.js +28 -0
  61. package/dist/numerical/metric-inverse.js.map +1 -1
  62. package/dist/numerical/null-ray-integrator.d.ts.map +1 -1
  63. package/dist/numerical/null-ray-integrator.js +6 -3
  64. package/dist/numerical/null-ray-integrator.js.map +1 -1
  65. package/dist/numerical/pderiv.d.ts +27 -3
  66. package/dist/numerical/pderiv.d.ts.map +1 -1
  67. package/dist/numerical/pderiv.js +44 -12
  68. package/dist/numerical/pderiv.js.map +1 -1
  69. package/dist/numerical/perihelion-finder.d.ts +111 -0
  70. package/dist/numerical/perihelion-finder.d.ts.map +1 -0
  71. package/dist/numerical/perihelion-finder.js +288 -0
  72. package/dist/numerical/perihelion-finder.js.map +1 -0
  73. package/package.json +1 -1
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Curvature-derived helpers — Ricci, Einstein, Bianchi (v0.5.0 Phase 1d).
3
+ *
4
+ * Module hosts the layer of GR objects derived by contraction of a
5
+ * `RiemannTensorNode`:
6
+ *
7
+ * - `ricci(R)` → R_μν = R^λ_{λμν} (this file, Task 7)
8
+ * - `einstein(R)` → G_μν = R_μν − ½ g_μν R (Task 8 — TBD)
9
+ * - `bianchiResidual(R)` → ∇_λ G^{λμ} (Task 9 — TBD)
10
+ *
11
+ * Separation rationale (Design §3 Task 1d): the bare `RiemannTensorNode`
12
+ * stays in `connection-validators.ts` next to `CovariantDerivativeNode`
13
+ * (both are *primary* curvature objects: connection + first-derivative).
14
+ * Everything *contracted from* Riemann lives here — keeps the module
15
+ * graph: tensor → metric → connection-validators → curvature.
16
+ *
17
+ * @module dimensional/curvature
18
+ */
19
+ import type { Dimension } from './types.js';
20
+ import type { ExprNode } from './validator.js';
21
+ import type { RiemannTensorNode } from './connection-validators.js';
22
+ import type { MetricTensorNode } from './metric-validators.js';
23
+ import type { TensorEngine } from '../numerical/tensor-engine.js';
24
+ import type { NumericalInputs, NestedArray } from '../numerical/types.js';
25
+ /**
26
+ * v0.5.0 Task 7: Ricci tensor AST node R_μν.
27
+ *
28
+ * Internally wraps a RiemannTensorNode and represents the contraction
29
+ *
30
+ * R_μν = R^λ_{μλν} (Carroll Eq. 3.91)
31
+ *
32
+ * Index-map on the wrapped Riemann (R^ρ_σμν per RiemannTensorNode):
33
+ *
34
+ * R^ρ σ μ ν
35
+ * │ │ │ │
36
+ * │ │ └── lowerIndices[1] = the dummy contraction slot (λ)
37
+ * │ │ contracts against upperIndex.label (ρ ↔ λ)
38
+ * │ └── lowerIndices[0] = free output index #1 (μ_out, lower)
39
+ * └── upperIndex (ρ) — contracts against lowerIndices[1]
40
+ * lowerIndices[2] = free output index #2 (ν_out, lower)
41
+ *
42
+ * After contraction: R_μν free indices = {μ_out: lower, ν_out: lower}, dim {L: -2}.
43
+ *
44
+ * NOTE (post-S1 correction, commit `76628c4`): prior to v0.5.0 Task 7 this
45
+ * docstring incorrectly said `lowerIndices[0]` was the dummy slot. Carroll
46
+ * Eq. 3.91 contracts on the SECOND lower (the μ slot in R^ρ_σμν), giving
47
+ * R_σν after the contraction. The implementation in `validateRicciTensor`
48
+ * below (lines ~87-101) is the authoritative reference. See memory entry
49
+ * `feedback_v0_5_0_ricci_contraction_bug.md` for the bug history.
50
+ *
51
+ * The node is its own validation arm in validator.ts and its own lowering
52
+ * arm in numerical/lowering.ts — same pattern as RiemannTensorNode (no
53
+ * AST-rewrite; the contraction is a single primitive walked directly).
54
+ *
55
+ * @public
56
+ */
57
+ export interface RicciTensorNode {
58
+ readonly kind: 'ricci-tensor';
59
+ /** The Riemann tensor whose first two slots are contracted. */
60
+ readonly riemann: RiemannTensorNode;
61
+ }
62
+ /**
63
+ * Result of validating a RicciTensorNode.
64
+ * @public
65
+ */
66
+ export interface RicciTensorValidationResult {
67
+ readonly dim: Dimension;
68
+ readonly freeIndices: Map<string, {
69
+ upper: number;
70
+ lower: number;
71
+ }>;
72
+ }
73
+ /**
74
+ * Validate a `ricci-tensor` node.
75
+ *
76
+ * Throws:
77
+ * - IndexLabelCollisionError if the embedded Riemann's two free output
78
+ * labels (the σ slot — lowerIndices[0] — and the ν slot —
79
+ * lowerIndices[2]) collide. (The contracted middle slot
80
+ * lowerIndices[1] is invisible to this check; any label is allowed
81
+ * there because it's a dummy.)
82
+ *
83
+ * The Riemann sub-node is validated structurally by re-entering its own
84
+ * validator via the `validateRiemannChild` callback (so its signature
85
+ * checks fire), but its free indices are NOT propagated — the ρ slot
86
+ * collapses into the contraction with the μ slot (Carroll Eq. 3.91:
87
+ * `R_μν = R^λ_{μλν}`), and the surviving σ / ν slots become the new
88
+ * free indices of the Ricci tensor.
89
+ */
90
+ export declare function validateRicciTensor(node: RicciTensorNode, validateRiemannChild: (child: RiemannTensorNode) => {
91
+ dim: Dimension;
92
+ freeIndices: Map<string, {
93
+ upper: number;
94
+ lower: number;
95
+ }>;
96
+ }): RicciTensorValidationResult;
97
+ /**
98
+ * Build the Ricci tensor R_μν = R^λ_{μλν} as a composite ExprNode.
99
+ *
100
+ * **Convention (Carroll Eq. 3.91).** Contract upper-ρ against the SECOND
101
+ * lower slot — the μ position in R^ρ_σμν, i.e., `lowerIndices[1]` in the
102
+ * RiemannTensorNode storage. The surviving free indices are the σ slot
103
+ * (`lowerIndices[0]`) → Ricci's first free output μ_out, and the ν slot
104
+ * (`lowerIndices[2]`) → Ricci's second free output ν_out.
105
+ *
106
+ * Index mapping after contraction:
107
+ * - upperIndex (ρ) → contracted with lowerIndices[1] (the μ slot, λ dummy)
108
+ * - lowerIndices[0] (σ) → Ricci's free output index 0 (μ_out)
109
+ * - lowerIndices[2] (ν) → Ricci's free output index 1 (ν_out)
110
+ * Result: R_μν with free indices {σ_label: lower, ν_label: lower}
111
+ *
112
+ * Index-map diagram:
113
+ * R^ρ _ σ _ μ _ ν (RiemannTensorNode slots)
114
+ * ↑ ↑
115
+ * upper lower[1] <- contract these two (ρ = μ = λ)
116
+ * lower[0] lower[2] <- become R_μν free indices
117
+ *
118
+ * **Why not "upper ↔ lowerIndices[0]" (the first-lower trace)?** The trace
119
+ * `R^λ_{λμν}` over the first pair (ρ ↔ σ) is identically zero by the
120
+ * first-pair antisymmetry of the (lowered) Riemann tensor — for any
121
+ * metric, including non-vacuum solutions like de Sitter. The
122
+ * constant-curvature identity `R_μν = (n-1) K g_μν` (Carroll §8.1,
123
+ * `R = 4Λ` in n=4) only matches the upper↔second-lower contraction.
124
+ * The de-Sitter Ricci-scalar test in `tests/dimensional/ricci.test.ts`
125
+ * is the discriminating fixture that pins this convention.
126
+ *
127
+ * The returned tree is a single `ricci-tensor` node wrapping the supplied
128
+ * Riemann. Validator and numerical lowering each have a dedicated arm —
129
+ * no AST rewrite into a `tensor-product`-of-Riemann happens here (the
130
+ * RiemannTensorNode is not contractable in the v0.3.5 tensor-product
131
+ * einsum sense; the contraction is a primitive operation walked directly).
132
+ *
133
+ * @public
134
+ */
135
+ export declare function ricci(R: RiemannTensorNode): ExprNode;
136
+ /**
137
+ * v0.5.0 Task 8: Einstein tensor AST node G_μν = R_μν − ½ R g_μν.
138
+ *
139
+ * Wraps a `RiemannTensorNode` (contracted via the same Carroll-Eq.-3.91
140
+ * convention as `RicciTensorNode` — see ricci()'s JSDoc) plus the metric
141
+ * pair needed for the scalar trace `R = g^μν R_μν` and the `½ R g_μν`
142
+ * subtraction term. Storage mirrors the `RicciTensorNode` pattern: own
143
+ * validator + lowering arms, no AST rewrite into op('-', ricci, scale·g).
144
+ *
145
+ * Free indices match `R.lowerIndices[0]` and `R.lowerIndices[2]` — same as
146
+ * ricci(R), with the same Carroll-Eq.-3.91 contraction; the embedded
147
+ * Riemann's middle slot lowerIndices[1] is the dummy λ.
148
+ *
149
+ * Dim: {L: -2} — inherited from Riemann/Ricci (both `R_μν` and `R · g_μν`
150
+ * carry 1/L²; subtraction preserves dim).
151
+ *
152
+ * @public
153
+ */
154
+ export interface EinsteinTensorNode {
155
+ readonly kind: 'einstein-tensor';
156
+ /** The Riemann tensor whose contraction yields the inner Ricci R_μν. */
157
+ readonly riemann: RiemannTensorNode;
158
+ /** Lower metric g_μν — supplies the `½ R g_μν` subtraction tensor. */
159
+ readonly gLower: MetricTensorNode;
160
+ /** Upper metric g^μν — supplies the scalar trace `R = g^μν R_μν`. */
161
+ readonly gInverse: MetricTensorNode;
162
+ }
163
+ /**
164
+ * Result of validating an EinsteinTensorNode.
165
+ * @public
166
+ */
167
+ export interface EinsteinTensorValidationResult {
168
+ readonly dim: Dimension;
169
+ readonly freeIndices: Map<string, {
170
+ upper: number;
171
+ lower: number;
172
+ }>;
173
+ }
174
+ /**
175
+ * Validate an `einstein-tensor` node.
176
+ *
177
+ * Delegates structural checks to the embedded Riemann via the
178
+ * `validateRiemannChild` callback (same pattern as `validateRicciTensor`)
179
+ * and reuses `validateRicciTensor` for the surviving free-index labels —
180
+ * an Einstein tensor's free indices are exactly the Ricci tensor's free
181
+ * indices (subtracting `½ R g_μν` with matching {μ_out, ν_out} labels
182
+ * preserves the index structure).
183
+ *
184
+ * The `gLower` / `gInverse` sub-nodes are deliberately NOT propagated as
185
+ * free indices — they are consumed internally by the scalar-trace
186
+ * contraction and the `½ R g_μν` multiplication (same H1 discipline as
187
+ * RiemannTensorNode for its gLower/gInverse fields).
188
+ *
189
+ * Throws:
190
+ * - Everything `validateRicciTensor` throws (IndexLabelCollisionError on
191
+ * surviving free-index collision; MetricSignatureError /
192
+ * PartialDerivativeIndexVarianceError from the inner Riemann).
193
+ */
194
+ export declare function validateEinsteinTensor(node: EinsteinTensorNode, validateRiemannChild: (child: RiemannTensorNode) => {
195
+ dim: Dimension;
196
+ freeIndices: Map<string, {
197
+ upper: number;
198
+ lower: number;
199
+ }>;
200
+ }): EinsteinTensorValidationResult;
201
+ /**
202
+ * Build the Einstein tensor G_μν = R_μν − ½ R g_μν as a composite ExprNode.
203
+ *
204
+ * **Convention.** The Einstein tensor adds the scalar-trace subtraction
205
+ * term `½ R g_μν` to Ricci — it does NOT introduce a new contraction
206
+ * convention. The inner Ricci `R_μν` inherits Carroll Eq. 3.91 from
207
+ * `ricci(R)` (contraction on `lowerIndices[1]`, the middle/μ slot in
208
+ * R^ρ_σμν; see RicciTensorNode JSDoc for the index-map diagram). The
209
+ * scalar trace is `R = g^μν R_μν`. The result `G_μν` shares free indices
210
+ * {μ_out, ν_out} with `ricci(R)`.
211
+ *
212
+ * **Algebra (sanity check).**
213
+ * - de Sitter (n=4): `R_μν = Λ g_μν`, `R = 4Λ` ⇒
214
+ * `G_μν = Λ g_μν − ½·4Λ·g_μν = −Λ g_μν`. Vacuum Einstein equation
215
+ * `G_μν + Λ g_μν = 0` holds.
216
+ * - Schwarzschild (vacuum): `R_μν ≡ 0`, `R = 0` ⇒ `G_μν ≡ 0`.
217
+ * - Trace identity (any metric, n=4): `g^μν G_μν = R − ½·R·4 = −R`.
218
+ *
219
+ * **AST shape.** Single `einstein-tensor` node wrapping `R`, `gLower`,
220
+ * `gInverse`. Validator and numerical lowering each have a dedicated arm;
221
+ * no AST rewrite into `ricci(R) − ½ R g_μν` happens (the subtraction term
222
+ * would need a tensor-valued scalar-multiply that the v0.3.5 tensor-product
223
+ * einsum does not support directly). Same walk-directly pattern as Ricci.
224
+ *
225
+ * **Matter coupling is out of scope.** The vacuum / cosmological-constant
226
+ * tests pin G_μν only; `G_μν = κ T_μν` (Einstein field equations with a
227
+ * stress-energy source) is deferred to v0.6.0+.
228
+ *
229
+ * @public
230
+ */
231
+ export declare function einstein(R: RiemannTensorNode, g: MetricTensorNode, gInverse: MetricTensorNode): ExprNode;
232
+ /**
233
+ * v0.5.0 Task 9: Second-Bianchi-identity residual AST node.
234
+ *
235
+ * Represents the cyclic-over-first-three-indices sum that vanishes by the
236
+ * second Bianchi identity (Carroll Eq. 3.95):
237
+ *
238
+ * B_{λμνρσ} = ∇_λ R_{μνρσ} + ∇_μ R_{νλρσ} + ∇_ν R_{λμρσ} ≡ 0
239
+ *
240
+ * Storage: wraps the originating RiemannTensorNode. Lowering computes B
241
+ * numerically by:
242
+ * 1. Lowering the upper-ρ of the Riemann tensor JS-side via g_{aρ}.
243
+ * 2. Computing ∂_λ R_{αβγδ} via a 4th-order centered FD (one MORE layer
244
+ * on top of the Riemann lowering's already-FD-laden Γ + ∂Γ stack).
245
+ * 3. Adding Christoffel-correction terms to form ∇_λ R_{μνρσ} (Approach 1:
246
+ * canonical, not partial-only).
247
+ * 4. Summing cyclically over (λ, μ, ν) with (ρ, σ) fixed.
248
+ *
249
+ * **Dim L⁻³.** R_{μνρσ} has dim L⁻² (inherited from R^ρ_{σμν}, since lowering
250
+ * with the dimensionless metric preserves dim per our convention). One ∂/∂x
251
+ * divides by L, giving L⁻³ on ∇R and on the cyclic sum B.
252
+ *
253
+ * **Free indices (5 lower):** λ, μ, ν, ρ, σ — taken from the same axis
254
+ * convention as RiemannTensorNode.lowerIndices plus a fresh λ slot. To avoid
255
+ * label collisions we synthesise `lambda` as the 5th index and reuse
256
+ * R.lowerIndices[0..2] for {μ_out, ν_out, ρ_out}; the 4th wrapped slot
257
+ * surfaces as `sigma_out`. (See `validateBianchiResidual` below for the
258
+ * label-collision rule.)
259
+ *
260
+ * **Why not lower via the v0.3.0 `lower()` AST function?** lower() returns a
261
+ * `tensor-product` node referencing the metric and operand by label/free-
262
+ * index merge, intended for compositional expressions. The lowered Riemann
263
+ * here is consumed point-wise by the FD stencil; building an AST product +
264
+ * re-validating + re-lowering on every of the 25 FD samples is needless
265
+ * machinery for a closed JS-side contraction. Matches the walk-directly
266
+ * philosophy of Tasks 6, 7, 8.
267
+ *
268
+ * @public
269
+ */
270
+ export interface BianchiResidualNode {
271
+ readonly kind: 'bianchi-residual';
272
+ /** The Riemann tensor whose cyclic-derivative identity is checked. */
273
+ readonly riemann: RiemannTensorNode;
274
+ }
275
+ /**
276
+ * Result of validating a BianchiResidualNode.
277
+ * @public
278
+ */
279
+ export interface BianchiResidualValidationResult {
280
+ readonly dim: Dimension;
281
+ readonly freeIndices: Map<string, {
282
+ upper: number;
283
+ lower: number;
284
+ }>;
285
+ }
286
+ /**
287
+ * Validate a `bianchi-residual` node.
288
+ *
289
+ * Inherits all structural checks from the embedded Riemann via
290
+ * `validateRiemannChild`. The output freeIndices are 5 lower labels:
291
+ *
292
+ * - `lambda` — the synthesised FIRST cyclic-derivative axis (a new label;
293
+ * must not collide with the wrapped Riemann's free labels)
294
+ * - The Riemann's `lowerIndices[0]` label → μ_out
295
+ * - The Riemann's `lowerIndices[1]` label → ν_out
296
+ * - The Riemann's `lowerIndices[2]` label → ρ_out
297
+ * - A synthesised `sigma_out` — the fourth Riemann index after lowering ρ.
298
+ *
299
+ * Throws:
300
+ * - IndexLabelCollisionError if the synthesised `lambda` / `sigma_out`
301
+ * labels collide with any wrapped-Riemann label.
302
+ */
303
+ export declare function validateBianchiResidual(node: BianchiResidualNode, validateRiemannChild: (child: RiemannTensorNode) => {
304
+ dim: Dimension;
305
+ freeIndices: Map<string, {
306
+ upper: number;
307
+ lower: number;
308
+ }>;
309
+ }): BianchiResidualValidationResult;
310
+ /**
311
+ * Imported lazily inside `bianchiResidual()` to avoid pulling the numerical
312
+ * lowering layer into the dimensional module's import graph at module load.
313
+ * Mirrors the runtime contract — `bianchiResidual()` returns closures that
314
+ * call `evaluateNumerical`, but the function itself is pure-symbolic until
315
+ * the closures are invoked.
316
+ *
317
+ * v0.5.1 TS-1: `engine`/`inputs`/return are now strictly typed via
318
+ * `import type` (no runtime import into curvature.ts), so the public API
319
+ * surface no longer leaks `any` through the bianchi return shape.
320
+ */
321
+ type LazyEvaluator = (engine: TensorEngine, inputs: NumericalInputs) => Promise<NestedArray>;
322
+ /**
323
+ * Build the second-Bianchi-identity residual `B_{λμνρσ}` as a composite
324
+ * object with both an AST representation and evaluator closures.
325
+ *
326
+ * **Return shape (deviates from `ricci()`/`einstein()`'s plain-ExprNode
327
+ * return):** Bianchi is a 5-index residual tensor whose primary purpose is
328
+ * to be EVALUATED and reduced to its max-absolute value. Callers that just
329
+ * want the scalar self-consistency check use `evaluateMax`; callers that
330
+ * want to inspect per-component residual structure use `evaluate`. The
331
+ * underlying `residual: ExprNode` is exposed for downstream symbolic
332
+ * consumers (validator, equation-homogeneity checks).
333
+ *
334
+ * **Convention.** Carroll Eq. 3.95 cyclic form on the first three lower
335
+ * indices of the all-lower Riemann:
336
+ *
337
+ * B_{λμνρσ} = ∇_λ R_{μνρσ} + ∇_μ R_{νλρσ} + ∇_ν R_{λμρσ} = 0
338
+ *
339
+ * **Implementation (Approach 1 — full ∇, not raw ∂).** Lowering computes
340
+ * each `∇_λ R_{μνρσ}` term with full Christoffel corrections (one per lower
341
+ * index of R), then sums cyclically. The lowered Riemann itself is computed
342
+ * by lowering the upper-ρ of R^ρ_{σμν} on the JS side after the Riemann
343
+ * lowering pipeline (Task 6) — no v0.3.0 `lower()` AST round-trip per FD
344
+ * sample.
345
+ *
346
+ * **Numerical-noise discussion.** The cyclic sum involves one extra
347
+ * coordinate-derivative on R_{μνρσ}, which itself sits on a ∂g→Γ→∂Γ→R FD
348
+ * stack. Schwarzschild + de Sitter empirical residuals are reported in
349
+ * `tests/dimensional/bianchi-residual.test.ts`; expected per-component
350
+ * noise floor is ~1e-7 to 1e-8 — much looser than the Task 6 Riemann floor
351
+ * (~8e-10) because of the extra FD layer.
352
+ *
353
+ * @public
354
+ */
355
+ export declare function bianchiResidual(R: RiemannTensorNode): {
356
+ residual: ExprNode;
357
+ evaluate: LazyEvaluator;
358
+ evaluateMax: (engine: TensorEngine, inputs: NumericalInputs) => Promise<number>;
359
+ };
360
+ export {};
361
+ //# sourceMappingURL=curvature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curvature.d.ts","sourceRoot":"","sources":["../../src/dimensional/curvature.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Curvature-derived helpers — Ricci, Einstein, Bianchi (v0.5.0 Phase 1d).
3
+ *
4
+ * Module hosts the layer of GR objects derived by contraction of a
5
+ * `RiemannTensorNode`:
6
+ *
7
+ * - `ricci(R)` → R_μν = R^λ_{λμν} (this file, Task 7)
8
+ * - `einstein(R)` → G_μν = R_μν − ½ g_μν R (Task 8 — TBD)
9
+ * - `bianchiResidual(R)` → ∇_λ G^{λμ} (Task 9 — TBD)
10
+ *
11
+ * Separation rationale (Design §3 Task 1d): the bare `RiemannTensorNode`
12
+ * stays in `connection-validators.ts` next to `CovariantDerivativeNode`
13
+ * (both are *primary* curvature objects: connection + first-derivative).
14
+ * Everything *contracted from* Riemann lives here — keeps the module
15
+ * graph: tensor → metric → connection-validators → curvature.
16
+ *
17
+ * @module dimensional/curvature
18
+ */
19
+ import { IndexLabelCollisionError } from './errors.js';
20
+ /**
21
+ * Validate a `ricci-tensor` node.
22
+ *
23
+ * Throws:
24
+ * - IndexLabelCollisionError if the embedded Riemann's two free output
25
+ * labels (the σ slot — lowerIndices[0] — and the ν slot —
26
+ * lowerIndices[2]) collide. (The contracted middle slot
27
+ * lowerIndices[1] is invisible to this check; any label is allowed
28
+ * there because it's a dummy.)
29
+ *
30
+ * The Riemann sub-node is validated structurally by re-entering its own
31
+ * validator via the `validateRiemannChild` callback (so its signature
32
+ * checks fire), but its free indices are NOT propagated — the ρ slot
33
+ * collapses into the contraction with the μ slot (Carroll Eq. 3.91:
34
+ * `R_μν = R^λ_{μλν}`), and the surviving σ / ν slots become the new
35
+ * free indices of the Ricci tensor.
36
+ */
37
+ export function validateRicciTensor(node, validateRiemannChild) {
38
+ // Re-validate the embedded Riemann so its signature checks (upperIndex /
39
+ // lowerIndices variance, gLower / gInverse signature, free-index
40
+ // disjointness) fire here too. We discard its freeIndices map: the ρ slot
41
+ // and the μ slot (lowerIndices[1]) are the contraction; the surviving σ
42
+ // (lowerIndices[0]) and ν (lowerIndices[2]) are reconstructed below from
43
+ // the authoritative axis source.
44
+ const rResult = validateRiemannChild(node.riemann);
45
+ // Dim is inherited 1:1 from the Riemann (contraction does not change
46
+ // per-component units — multiplying by δ^ρ_μ is dimensionless).
47
+ const dim = rResult.dim;
48
+ // Surviving free indices: Ricci's μ_out ← lowerIndices[0] (σ slot),
49
+ // Ricci's ν_out ← lowerIndices[2] (ν slot). Contracted middle slot
50
+ // (lowerIndices[1]) is dummied and not surfaced as free.
51
+ const muOutIdx = node.riemann.lowerIndices[0];
52
+ const nuOutIdx = node.riemann.lowerIndices[2];
53
+ if (muOutIdx.label === nuOutIdx.label) {
54
+ throw new IndexLabelCollisionError(muOutIdx.label, 2, ['ricci-tensor']);
55
+ }
56
+ const freeIndices = new Map();
57
+ freeIndices.set(muOutIdx.label, { upper: 0, lower: 1 });
58
+ freeIndices.set(nuOutIdx.label, { upper: 0, lower: 1 });
59
+ return { dim, freeIndices };
60
+ }
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ // ricci() — user-facing constructor
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+ /**
65
+ * Build the Ricci tensor R_μν = R^λ_{μλν} as a composite ExprNode.
66
+ *
67
+ * **Convention (Carroll Eq. 3.91).** Contract upper-ρ against the SECOND
68
+ * lower slot — the μ position in R^ρ_σμν, i.e., `lowerIndices[1]` in the
69
+ * RiemannTensorNode storage. The surviving free indices are the σ slot
70
+ * (`lowerIndices[0]`) → Ricci's first free output μ_out, and the ν slot
71
+ * (`lowerIndices[2]`) → Ricci's second free output ν_out.
72
+ *
73
+ * Index mapping after contraction:
74
+ * - upperIndex (ρ) → contracted with lowerIndices[1] (the μ slot, λ dummy)
75
+ * - lowerIndices[0] (σ) → Ricci's free output index 0 (μ_out)
76
+ * - lowerIndices[2] (ν) → Ricci's free output index 1 (ν_out)
77
+ * Result: R_μν with free indices {σ_label: lower, ν_label: lower}
78
+ *
79
+ * Index-map diagram:
80
+ * R^ρ _ σ _ μ _ ν (RiemannTensorNode slots)
81
+ * ↑ ↑
82
+ * upper lower[1] <- contract these two (ρ = μ = λ)
83
+ * lower[0] lower[2] <- become R_μν free indices
84
+ *
85
+ * **Why not "upper ↔ lowerIndices[0]" (the first-lower trace)?** The trace
86
+ * `R^λ_{λμν}` over the first pair (ρ ↔ σ) is identically zero by the
87
+ * first-pair antisymmetry of the (lowered) Riemann tensor — for any
88
+ * metric, including non-vacuum solutions like de Sitter. The
89
+ * constant-curvature identity `R_μν = (n-1) K g_μν` (Carroll §8.1,
90
+ * `R = 4Λ` in n=4) only matches the upper↔second-lower contraction.
91
+ * The de-Sitter Ricci-scalar test in `tests/dimensional/ricci.test.ts`
92
+ * is the discriminating fixture that pins this convention.
93
+ *
94
+ * The returned tree is a single `ricci-tensor` node wrapping the supplied
95
+ * Riemann. Validator and numerical lowering each have a dedicated arm —
96
+ * no AST rewrite into a `tensor-product`-of-Riemann happens here (the
97
+ * RiemannTensorNode is not contractable in the v0.3.5 tensor-product
98
+ * einsum sense; the contraction is a primitive operation walked directly).
99
+ *
100
+ * @public
101
+ */
102
+ export function ricci(R) {
103
+ return { kind: 'ricci-tensor', riemann: R };
104
+ }
105
+ /**
106
+ * Validate an `einstein-tensor` node.
107
+ *
108
+ * Delegates structural checks to the embedded Riemann via the
109
+ * `validateRiemannChild` callback (same pattern as `validateRicciTensor`)
110
+ * and reuses `validateRicciTensor` for the surviving free-index labels —
111
+ * an Einstein tensor's free indices are exactly the Ricci tensor's free
112
+ * indices (subtracting `½ R g_μν` with matching {μ_out, ν_out} labels
113
+ * preserves the index structure).
114
+ *
115
+ * The `gLower` / `gInverse` sub-nodes are deliberately NOT propagated as
116
+ * free indices — they are consumed internally by the scalar-trace
117
+ * contraction and the `½ R g_μν` multiplication (same H1 discipline as
118
+ * RiemannTensorNode for its gLower/gInverse fields).
119
+ *
120
+ * Throws:
121
+ * - Everything `validateRicciTensor` throws (IndexLabelCollisionError on
122
+ * surviving free-index collision; MetricSignatureError /
123
+ * PartialDerivativeIndexVarianceError from the inner Riemann).
124
+ */
125
+ export function validateEinsteinTensor(node, validateRiemannChild) {
126
+ // Delegate to Ricci validation — the surviving free-index labels and dim
127
+ // are identical (R_μν has the same {μ_out, ν_out} as G_μν, dim {L:-2}).
128
+ const ricciNode = { kind: 'ricci-tensor', riemann: node.riemann };
129
+ const r = validateRicciTensor(ricciNode, validateRiemannChild);
130
+ // Defense in depth: the `½ R g_μν` term involves g_μν, but its free
131
+ // indices are NOT propagated — they are the same {μ_out, ν_out} as Ricci
132
+ // by construction (we contract them down to a 4×4 in the same axis order
133
+ // during lowering). Mirrors H1: gLower / gInverse on RiemannTensorNode.
134
+ return { dim: r.dim, freeIndices: r.freeIndices };
135
+ }
136
+ // ─────────────────────────────────────────────────────────────────────────────
137
+ // einstein() — user-facing constructor
138
+ // ─────────────────────────────────────────────────────────────────────────────
139
+ /**
140
+ * Build the Einstein tensor G_μν = R_μν − ½ R g_μν as a composite ExprNode.
141
+ *
142
+ * **Convention.** The Einstein tensor adds the scalar-trace subtraction
143
+ * term `½ R g_μν` to Ricci — it does NOT introduce a new contraction
144
+ * convention. The inner Ricci `R_μν` inherits Carroll Eq. 3.91 from
145
+ * `ricci(R)` (contraction on `lowerIndices[1]`, the middle/μ slot in
146
+ * R^ρ_σμν; see RicciTensorNode JSDoc for the index-map diagram). The
147
+ * scalar trace is `R = g^μν R_μν`. The result `G_μν` shares free indices
148
+ * {μ_out, ν_out} with `ricci(R)`.
149
+ *
150
+ * **Algebra (sanity check).**
151
+ * - de Sitter (n=4): `R_μν = Λ g_μν`, `R = 4Λ` ⇒
152
+ * `G_μν = Λ g_μν − ½·4Λ·g_μν = −Λ g_μν`. Vacuum Einstein equation
153
+ * `G_μν + Λ g_μν = 0` holds.
154
+ * - Schwarzschild (vacuum): `R_μν ≡ 0`, `R = 0` ⇒ `G_μν ≡ 0`.
155
+ * - Trace identity (any metric, n=4): `g^μν G_μν = R − ½·R·4 = −R`.
156
+ *
157
+ * **AST shape.** Single `einstein-tensor` node wrapping `R`, `gLower`,
158
+ * `gInverse`. Validator and numerical lowering each have a dedicated arm;
159
+ * no AST rewrite into `ricci(R) − ½ R g_μν` happens (the subtraction term
160
+ * would need a tensor-valued scalar-multiply that the v0.3.5 tensor-product
161
+ * einsum does not support directly). Same walk-directly pattern as Ricci.
162
+ *
163
+ * **Matter coupling is out of scope.** The vacuum / cosmological-constant
164
+ * tests pin G_μν only; `G_μν = κ T_μν` (Einstein field equations with a
165
+ * stress-energy source) is deferred to v0.6.0+.
166
+ *
167
+ * @public
168
+ */
169
+ export function einstein(R, g, gInverse) {
170
+ return { kind: 'einstein-tensor', riemann: R, gLower: g, gInverse };
171
+ }
172
+ /**
173
+ * Validate a `bianchi-residual` node.
174
+ *
175
+ * Inherits all structural checks from the embedded Riemann via
176
+ * `validateRiemannChild`. The output freeIndices are 5 lower labels:
177
+ *
178
+ * - `lambda` — the synthesised FIRST cyclic-derivative axis (a new label;
179
+ * must not collide with the wrapped Riemann's free labels)
180
+ * - The Riemann's `lowerIndices[0]` label → μ_out
181
+ * - The Riemann's `lowerIndices[1]` label → ν_out
182
+ * - The Riemann's `lowerIndices[2]` label → ρ_out
183
+ * - A synthesised `sigma_out` — the fourth Riemann index after lowering ρ.
184
+ *
185
+ * Throws:
186
+ * - IndexLabelCollisionError if the synthesised `lambda` / `sigma_out`
187
+ * labels collide with any wrapped-Riemann label.
188
+ */
189
+ export function validateBianchiResidual(node, validateRiemannChild) {
190
+ // Re-validate the embedded Riemann for its own signature/index checks.
191
+ validateRiemannChild(node.riemann);
192
+ // Build the 5 free-index labels. The Riemann's lowerIndices[0..2] become
193
+ // the μ_out, ν_out, ρ_out of the all-lower Riemann (after lowering ρ to a
194
+ // synthesised 'sigma_out'... wait — the convention here is:
195
+ // ∇_λ R_{αβγδ} has 5 indices: λ + 4 from the lowered Riemann.
196
+ // The 4 from the lowered Riemann are: the freshly-lowered ρ (renamed
197
+ // from `upperIndex.label`) + lowerIndices[0..2].
198
+ // We use literal label `lambda` for the cyclic-deriv axis. The freshly-
199
+ // lowered ρ takes a literal `alpha_lower` label.
200
+ //
201
+ // Collision rule: `lambda` and `alpha_lower` must not collide with any of
202
+ // {upperIndex.label, lowerIndices[0..2].label}.
203
+ const wrappedLabels = new Set([
204
+ node.riemann.upperIndex.label,
205
+ node.riemann.lowerIndices[0].label,
206
+ node.riemann.lowerIndices[1].label,
207
+ node.riemann.lowerIndices[2].label,
208
+ ]);
209
+ const synthesised = ['lambda', 'alpha_lower'];
210
+ for (const s of synthesised) {
211
+ if (wrappedLabels.has(s)) {
212
+ throw new IndexLabelCollisionError(s, 2, ['bianchi-residual']);
213
+ }
214
+ }
215
+ // Per Design §3 Task 1f / F8 / I3: Bianchi residual carries 1/L³ (one
216
+ // covariant derivative on R, which is 1/L²).
217
+ const dim = { L: -3, M: 0, T: 0, I: 0, Theta: 0, N: 0, J: 0 };
218
+ // 5 free indices, all lower.
219
+ const freeIndices = new Map();
220
+ freeIndices.set('lambda', { upper: 0, lower: 1 });
221
+ freeIndices.set('alpha_lower', { upper: 0, lower: 1 });
222
+ freeIndices.set(node.riemann.lowerIndices[0].label, { upper: 0, lower: 1 });
223
+ freeIndices.set(node.riemann.lowerIndices[1].label, { upper: 0, lower: 1 });
224
+ freeIndices.set(node.riemann.lowerIndices[2].label, { upper: 0, lower: 1 });
225
+ return { dim, freeIndices };
226
+ }
227
+ /**
228
+ * Build the second-Bianchi-identity residual `B_{λμνρσ}` as a composite
229
+ * object with both an AST representation and evaluator closures.
230
+ *
231
+ * **Return shape (deviates from `ricci()`/`einstein()`'s plain-ExprNode
232
+ * return):** Bianchi is a 5-index residual tensor whose primary purpose is
233
+ * to be EVALUATED and reduced to its max-absolute value. Callers that just
234
+ * want the scalar self-consistency check use `evaluateMax`; callers that
235
+ * want to inspect per-component residual structure use `evaluate`. The
236
+ * underlying `residual: ExprNode` is exposed for downstream symbolic
237
+ * consumers (validator, equation-homogeneity checks).
238
+ *
239
+ * **Convention.** Carroll Eq. 3.95 cyclic form on the first three lower
240
+ * indices of the all-lower Riemann:
241
+ *
242
+ * B_{λμνρσ} = ∇_λ R_{μνρσ} + ∇_μ R_{νλρσ} + ∇_ν R_{λμρσ} = 0
243
+ *
244
+ * **Implementation (Approach 1 — full ∇, not raw ∂).** Lowering computes
245
+ * each `∇_λ R_{μνρσ}` term with full Christoffel corrections (one per lower
246
+ * index of R), then sums cyclically. The lowered Riemann itself is computed
247
+ * by lowering the upper-ρ of R^ρ_{σμν} on the JS side after the Riemann
248
+ * lowering pipeline (Task 6) — no v0.3.0 `lower()` AST round-trip per FD
249
+ * sample.
250
+ *
251
+ * **Numerical-noise discussion.** The cyclic sum involves one extra
252
+ * coordinate-derivative on R_{μνρσ}, which itself sits on a ∂g→Γ→∂Γ→R FD
253
+ * stack. Schwarzschild + de Sitter empirical residuals are reported in
254
+ * `tests/dimensional/bianchi-residual.test.ts`; expected per-component
255
+ * noise floor is ~1e-7 to 1e-8 — much looser than the Task 6 Riemann floor
256
+ * (~8e-10) because of the extra FD layer.
257
+ *
258
+ * @public
259
+ */
260
+ export function bianchiResidual(R) {
261
+ const residual = { kind: 'bianchi-residual', riemann: R };
262
+ const evaluate = async (engine, inputs) => {
263
+ // Dynamic import to keep the dimensional module's load-time graph clean
264
+ // (a static import here would create a dimensional→numerical→dimensional
265
+ // runtime cycle: numerical/index.ts imports validator.ts). The TS-1
266
+ // type-tightening above uses `import type` only, so this dynamic import
267
+ // does not weaken the static type story.
268
+ const mod = await import('../numerical/index.js');
269
+ const result = await mod.evaluateNumerical(residual, inputs, { engine });
270
+ return result.value;
271
+ };
272
+ const evaluateMax = async (engine, inputs) => {
273
+ const value = await evaluate(engine, inputs);
274
+ // Walk the 5-deep nested array (or any depth) and return max |x|.
275
+ // v0.5.1 TS-3: signature accepts `NestedArray` (the public type from
276
+ // evaluate()) plus a typed-array escape hatch; unexpected shapes throw
277
+ // rather than being silently ignored.
278
+ let max = 0;
279
+ const walk = (v) => {
280
+ if (typeof v === 'number') {
281
+ const a = Math.abs(v);
282
+ if (a > max)
283
+ max = a;
284
+ return;
285
+ }
286
+ if (Array.isArray(v)) {
287
+ for (const c of v)
288
+ walk(c);
289
+ return;
290
+ }
291
+ throw new Error(`bianchiResidual.evaluateMax: unexpected value shape — expected `
292
+ + `number | NestedArray, got ${typeof v}`);
293
+ };
294
+ walk(value);
295
+ return max;
296
+ };
297
+ return { residual: residual, evaluate, evaluateMax };
298
+ }
299
+ //# sourceMappingURL=curvature.js.map