@guinetik/gcanvas 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rank-2 Tensor class for general relativity calculations.
|
|
3
|
+
* Provides immutable tensor operations following the Complex class pattern.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* // Create a Schwarzschild metric at r = 10, rs = 2
|
|
7
|
+
* const g = Tensor.schwarzschild(10, 2);
|
|
8
|
+
* console.log(g.get(0, 0)); // g_tt component
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Create from components
|
|
12
|
+
* const metric = new Tensor([
|
|
13
|
+
* [-1, 0, 0, 0],
|
|
14
|
+
* [0, 1, 0, 0],
|
|
15
|
+
* [0, 0, 1, 0],
|
|
16
|
+
* [0, 0, 0, 1]
|
|
17
|
+
* ]);
|
|
18
|
+
*/
|
|
19
|
+
export class Tensor {
|
|
20
|
+
#components;
|
|
21
|
+
#dimension;
|
|
22
|
+
#name;
|
|
23
|
+
#signature;
|
|
24
|
+
#coordinates;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a new rank-2 tensor from a 2D array of components.
|
|
28
|
+
* @param {number[][]} components - 2D array of tensor components
|
|
29
|
+
* @param {Object} [options={}] - Optional metadata
|
|
30
|
+
* @param {string} [options.name] - Name of the tensor (e.g., 'Schwarzschild')
|
|
31
|
+
* @param {number[]} [options.signature] - Metric signature (e.g., [-1, 1, 1, 1])
|
|
32
|
+
* @param {string[]} [options.coordinates] - Coordinate names (e.g., ['t', 'r', 'θ', 'φ'])
|
|
33
|
+
*/
|
|
34
|
+
constructor(components, options = {}) {
|
|
35
|
+
// Deep copy to ensure immutability
|
|
36
|
+
this.#components = components.map((row) => [...row]);
|
|
37
|
+
this.#dimension = components.length;
|
|
38
|
+
this.#name = options.name || "";
|
|
39
|
+
this.#signature = options.signature || null;
|
|
40
|
+
this.#coordinates = options.coordinates || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
// STATIC FACTORY METHODS
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a Minkowski (flat spacetime) metric tensor.
|
|
49
|
+
* @returns {Tensor} Minkowski metric with signature (-,+,+,+)
|
|
50
|
+
*/
|
|
51
|
+
static minkowski() {
|
|
52
|
+
return new Tensor(
|
|
53
|
+
[
|
|
54
|
+
[-1, 0, 0, 0],
|
|
55
|
+
[0, 1, 0, 0],
|
|
56
|
+
[0, 0, 1, 0],
|
|
57
|
+
[0, 0, 0, 1],
|
|
58
|
+
],
|
|
59
|
+
{
|
|
60
|
+
name: "Minkowski",
|
|
61
|
+
signature: [-1, 1, 1, 1],
|
|
62
|
+
coordinates: ["t", "x", "y", "z"],
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a Schwarzschild metric tensor at a given radial position.
|
|
69
|
+
* Uses geometrized units where G = c = 1.
|
|
70
|
+
*
|
|
71
|
+
* @param {number} r - Radial coordinate (must be > rs)
|
|
72
|
+
* @param {number} rs - Schwarzschild radius (2GM/c²)
|
|
73
|
+
* @param {number} [theta=Math.PI/2] - Polar angle (default: equatorial plane)
|
|
74
|
+
* @returns {Tensor} Schwarzschild metric tensor
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* Create a Schwarzschild metric tensor at a given radial position.
|
|
78
|
+
* Uses geometrized units where G = c = 1.
|
|
79
|
+
*
|
|
80
|
+
* @param {number} r - Radial coordinate (must be > rs)
|
|
81
|
+
* @param {number} rs - Schwarzschild radius (2GM/c²)
|
|
82
|
+
* @param {number} [theta=Math.PI/2] - Polar angle (default: equatorial plane)
|
|
83
|
+
* @returns {Tensor} Schwarzschild metric tensor
|
|
84
|
+
*/
|
|
85
|
+
static schwarzschild(r, rs, theta = Math.PI / 2) {
|
|
86
|
+
if (r <= rs) {
|
|
87
|
+
// Inside event horizon - metric components swap signature
|
|
88
|
+
// For visualization purposes, we clamp to a small value outside
|
|
89
|
+
r = rs * 1.001;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const factor = 1 - rs / r;
|
|
93
|
+
const sinTheta = Math.sin(theta);
|
|
94
|
+
|
|
95
|
+
return new Tensor(
|
|
96
|
+
[
|
|
97
|
+
[-factor, 0, 0, 0],
|
|
98
|
+
[0, 1 / factor, 0, 0],
|
|
99
|
+
[0, 0, r * r, 0],
|
|
100
|
+
[0, 0, 0, r * r * sinTheta * sinTheta],
|
|
101
|
+
],
|
|
102
|
+
{
|
|
103
|
+
name: "Schwarzschild",
|
|
104
|
+
signature: [-1, 1, 1, 1],
|
|
105
|
+
coordinates: ["t", "r", "θ", "φ"],
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a contravariant (inverse) Schwarzschild metric tensor.
|
|
112
|
+
* @param {number} r - Radial coordinate
|
|
113
|
+
* @param {number} rs - Schwarzschild radius
|
|
114
|
+
* @param {number} [theta=Math.PI/2] - Polar angle
|
|
115
|
+
* @returns {Tensor} Inverse Schwarzschild metric
|
|
116
|
+
*/
|
|
117
|
+
static schwarzschildContravariant(r, rs, theta = Math.PI / 2) {
|
|
118
|
+
if (r <= rs) r = rs * 1.001;
|
|
119
|
+
|
|
120
|
+
const factor = 1 - rs / r;
|
|
121
|
+
const sinTheta = Math.sin(theta);
|
|
122
|
+
|
|
123
|
+
return new Tensor(
|
|
124
|
+
[
|
|
125
|
+
[-1 / factor, 0, 0, 0],
|
|
126
|
+
[0, factor, 0, 0],
|
|
127
|
+
[0, 0, 1 / (r * r), 0],
|
|
128
|
+
[0, 0, 0, 1 / (r * r * sinTheta * sinTheta)],
|
|
129
|
+
],
|
|
130
|
+
{
|
|
131
|
+
name: "Schwarzschild (Contravariant)",
|
|
132
|
+
signature: [-1, 1, 1, 1],
|
|
133
|
+
coordinates: ["t", "r", "θ", "φ"],
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create a Kerr metric tensor at a given position.
|
|
140
|
+
* Describes spacetime around a rotating (spinning) black hole.
|
|
141
|
+
* Uses Boyer-Lindquist coordinates and geometrized units (G = c = 1).
|
|
142
|
+
*
|
|
143
|
+
* The Kerr metric has an OFF-DIAGONAL g_tφ term representing frame dragging.
|
|
144
|
+
*
|
|
145
|
+
* @param {number} r - Radial coordinate
|
|
146
|
+
* @param {number} theta - Polar angle (0 to π)
|
|
147
|
+
* @param {number} M - Mass parameter
|
|
148
|
+
* @param {number} a - Spin parameter (0 ≤ a ≤ M, a=M is extremal)
|
|
149
|
+
* @returns {Tensor} Kerr metric tensor (non-diagonal)
|
|
150
|
+
*/
|
|
151
|
+
static kerr(r, theta, M, a) {
|
|
152
|
+
// Clamp spin to valid range [0, M]
|
|
153
|
+
a = Math.min(Math.abs(a), M);
|
|
154
|
+
|
|
155
|
+
const a2 = a * a;
|
|
156
|
+
const r2 = r * r;
|
|
157
|
+
const cosTheta = Math.cos(theta);
|
|
158
|
+
const sinTheta = Math.sin(theta);
|
|
159
|
+
const sin2Theta = sinTheta * sinTheta;
|
|
160
|
+
const cos2Theta = cosTheta * cosTheta;
|
|
161
|
+
|
|
162
|
+
// Fundamental Kerr quantities
|
|
163
|
+
const Sigma = r2 + a2 * cos2Theta;
|
|
164
|
+
const Delta = r2 - 2 * M * r + a2;
|
|
165
|
+
|
|
166
|
+
// Outer event horizon radius
|
|
167
|
+
const rPlus = M + Math.sqrt(Math.max(0, M * M - a2));
|
|
168
|
+
|
|
169
|
+
// Clamp to just outside horizon to avoid coordinate singularity
|
|
170
|
+
if (r <= rPlus) {
|
|
171
|
+
r = rPlus * 1.001;
|
|
172
|
+
const r2New = r * r;
|
|
173
|
+
const SigmaNew = r2New + a2 * cos2Theta;
|
|
174
|
+
const DeltaNew = r2New - 2 * M * r + a2;
|
|
175
|
+
return Tensor._buildKerrMetric(r, theta, M, a, SigmaNew, DeltaNew, sin2Theta);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return Tensor._buildKerrMetric(r, theta, M, a, Sigma, Delta, sin2Theta);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create a contravariant (inverse) Kerr metric tensor.
|
|
183
|
+
* Uses analytical block-inversion for the t-φ coupling.
|
|
184
|
+
*
|
|
185
|
+
* @param {number} r - Radial coordinate
|
|
186
|
+
* @param {number} theta - Polar angle
|
|
187
|
+
* @param {number} M - Mass parameter
|
|
188
|
+
* @param {number} a - Spin parameter
|
|
189
|
+
* @returns {Tensor} Inverse Kerr metric
|
|
190
|
+
*/
|
|
191
|
+
static kerrContravariant(r, theta, M, a) {
|
|
192
|
+
a = Math.min(Math.abs(a), M);
|
|
193
|
+
const a2 = a * a;
|
|
194
|
+
const r2 = r * r;
|
|
195
|
+
const cosTheta = Math.cos(theta);
|
|
196
|
+
const sinTheta = Math.sin(theta);
|
|
197
|
+
const sin2Theta = sinTheta * sinTheta;
|
|
198
|
+
|
|
199
|
+
const Sigma = r2 + a2 * cosTheta * cosTheta;
|
|
200
|
+
const Delta = r2 - 2 * M * r + a2;
|
|
201
|
+
|
|
202
|
+
const rPlus = M + Math.sqrt(Math.max(0, M * M - a2));
|
|
203
|
+
if (r <= rPlus) r = rPlus * 1.001;
|
|
204
|
+
|
|
205
|
+
// Direct analytical inverse components for Kerr metric
|
|
206
|
+
// g^tt = -( (r²+a²)² - a²Δsin²θ ) / (ΣΔ)
|
|
207
|
+
const g_inv_tt = -(Math.pow(r2 + a2, 2) - a2 * Delta * sin2Theta) / (Sigma * Delta);
|
|
208
|
+
|
|
209
|
+
// g^rr = Δ / Σ
|
|
210
|
+
const g_inv_rr = Delta / Sigma;
|
|
211
|
+
|
|
212
|
+
// g^θθ = 1 / Σ
|
|
213
|
+
const g_inv_thth = 1 / Sigma;
|
|
214
|
+
|
|
215
|
+
// g^φφ = (Δ - a²sin²θ) / (ΣΔsin²θ)
|
|
216
|
+
const g_inv_phph = (Delta - a2 * sin2Theta) / (Sigma * Delta * sin2Theta);
|
|
217
|
+
|
|
218
|
+
// g^tφ = -(2Mar) / (ΣΔ)
|
|
219
|
+
const g_inv_tph = -(2 * M * a * r) / (Sigma * Delta);
|
|
220
|
+
|
|
221
|
+
return new Tensor(
|
|
222
|
+
[
|
|
223
|
+
[g_inv_tt, 0, 0, g_inv_tph],
|
|
224
|
+
[0, g_inv_rr, 0, 0],
|
|
225
|
+
[0, 0, g_inv_thth, 0],
|
|
226
|
+
[g_inv_tph, 0, 0, g_inv_phph],
|
|
227
|
+
],
|
|
228
|
+
{
|
|
229
|
+
name: "Kerr (Contravariant)",
|
|
230
|
+
signature: [-1, 1, 1, 1],
|
|
231
|
+
coordinates: ["t", "r", "θ", "φ"],
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Internal helper to build Kerr metric components.
|
|
238
|
+
* @private
|
|
239
|
+
*/
|
|
240
|
+
static _buildKerrMetric(r, theta, M, a, Sigma, Delta, sin2Theta) {
|
|
241
|
+
const a2 = a * a;
|
|
242
|
+
const r2 = r * r;
|
|
243
|
+
|
|
244
|
+
// Metric components in Boyer-Lindquist coordinates
|
|
245
|
+
// g_tt = -(1 - 2Mr/Σ)
|
|
246
|
+
const g_tt = -(1 - (2 * M * r) / Sigma);
|
|
247
|
+
|
|
248
|
+
// g_rr = Σ/Δ
|
|
249
|
+
const g_rr = Sigma / Delta;
|
|
250
|
+
|
|
251
|
+
// g_θθ = Σ
|
|
252
|
+
const g_thth = Sigma;
|
|
253
|
+
|
|
254
|
+
// g_φφ = (r² + a² + 2Ma²r sin²θ/Σ) sin²θ
|
|
255
|
+
const g_phph = (r2 + a2 + (2 * M * a2 * r * sin2Theta) / Sigma) * sin2Theta;
|
|
256
|
+
|
|
257
|
+
// OFF-DIAGONAL: g_tφ = g_φt = -2Mar sin²θ / Σ
|
|
258
|
+
// This is the FRAME DRAGGING term!
|
|
259
|
+
const g_tph = -(2 * M * a * r * sin2Theta) / Sigma;
|
|
260
|
+
|
|
261
|
+
return new Tensor(
|
|
262
|
+
[
|
|
263
|
+
[g_tt, 0, 0, g_tph],
|
|
264
|
+
[0, g_rr, 0, 0],
|
|
265
|
+
[0, 0, g_thth, 0],
|
|
266
|
+
[g_tph, 0, 0, g_phph],
|
|
267
|
+
],
|
|
268
|
+
{
|
|
269
|
+
name: "Kerr",
|
|
270
|
+
signature: [-1, 1, 1, 1],
|
|
271
|
+
coordinates: ["t", "r", "θ", "φ"],
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Calculate Kerr horizon radii.
|
|
278
|
+
* r± = M ± √(M² - a²)
|
|
279
|
+
*
|
|
280
|
+
* @param {number} M - Mass parameter
|
|
281
|
+
* @param {number} a - Spin parameter
|
|
282
|
+
* @param {boolean} [inner=false] - Return inner (Cauchy) horizon if true
|
|
283
|
+
* @returns {number} Horizon radius (outer by default, NaN if a > M)
|
|
284
|
+
*/
|
|
285
|
+
static kerrHorizonRadius(M, a, inner = false) {
|
|
286
|
+
const discriminant = M * M - a * a;
|
|
287
|
+
if (discriminant < 0) return NaN; // Naked singularity (a > M)
|
|
288
|
+
const sqrtDisc = Math.sqrt(discriminant);
|
|
289
|
+
return inner ? M - sqrtDisc : M + sqrtDisc;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Calculate ergosphere radius (static limit surface).
|
|
294
|
+
* r_ergo(θ) = M + √(M² - a²cos²θ)
|
|
295
|
+
*
|
|
296
|
+
* At poles (θ=0,π): r_ergo = r+ (touches horizon)
|
|
297
|
+
* At equator (θ=π/2): r_ergo = 2M (maximum extent)
|
|
298
|
+
*
|
|
299
|
+
* @param {number} M - Mass parameter
|
|
300
|
+
* @param {number} a - Spin parameter
|
|
301
|
+
* @param {number} theta - Polar angle
|
|
302
|
+
* @returns {number} Ergosphere radius at given theta
|
|
303
|
+
*/
|
|
304
|
+
static kerrErgosphereRadius(M, a, theta) {
|
|
305
|
+
const cosTheta = Math.cos(theta);
|
|
306
|
+
const discriminant = M * M - a * a * cosTheta * cosTheta;
|
|
307
|
+
if (discriminant < 0) return NaN;
|
|
308
|
+
return M + Math.sqrt(discriminant);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Calculate ISCO radius for Kerr metric.
|
|
313
|
+
* Different for prograde (co-rotating) vs retrograde (counter-rotating) orbits.
|
|
314
|
+
*
|
|
315
|
+
* Prograde ISCO: approaches M as a → M (can orbit closer)
|
|
316
|
+
* Retrograde ISCO: approaches 9M as a → M (must orbit farther)
|
|
317
|
+
*
|
|
318
|
+
* Uses the exact Bardeen formula.
|
|
319
|
+
*
|
|
320
|
+
* @param {number} M - Mass parameter
|
|
321
|
+
* @param {number} a - Spin parameter
|
|
322
|
+
* @param {boolean} [prograde=true] - Prograde orbit if true
|
|
323
|
+
* @returns {number} ISCO radius
|
|
324
|
+
*/
|
|
325
|
+
static kerrISCO(M, a, prograde = true) {
|
|
326
|
+
const aM = a / M; // Dimensionless spin
|
|
327
|
+
const sign = prograde ? 1 : -1;
|
|
328
|
+
|
|
329
|
+
// Bardeen's formula for Kerr ISCO
|
|
330
|
+
const Z1 = 1 + Math.cbrt(1 - aM * aM) * (Math.cbrt(1 + aM) + Math.cbrt(1 - aM));
|
|
331
|
+
const Z2 = Math.sqrt(3 * aM * aM + Z1 * Z1);
|
|
332
|
+
|
|
333
|
+
return M * (3 + Z2 - sign * Math.sqrt((3 - Z1) * (3 + Z1 + 2 * Z2)));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Calculate frame-dragging angular velocity (omega).
|
|
338
|
+
* This is the angular velocity at which spacetime itself rotates.
|
|
339
|
+
*
|
|
340
|
+
* ω = -g_tφ / g_φφ
|
|
341
|
+
*
|
|
342
|
+
* At large r: ω ≈ 2Ma/r³ (Lense-Thirring precession)
|
|
343
|
+
*
|
|
344
|
+
* @param {number} r - Radial coordinate
|
|
345
|
+
* @param {number} theta - Polar angle
|
|
346
|
+
* @param {number} M - Mass parameter
|
|
347
|
+
* @param {number} a - Spin parameter
|
|
348
|
+
* @returns {number} Frame dragging angular velocity
|
|
349
|
+
*/
|
|
350
|
+
static kerrFrameDraggingOmega(r, theta, M, a) {
|
|
351
|
+
const metric = Tensor.kerr(r, theta, M, a);
|
|
352
|
+
const g_tph = metric.get(0, 3);
|
|
353
|
+
const g_phph = metric.get(3, 3);
|
|
354
|
+
|
|
355
|
+
if (Math.abs(g_phph) < 1e-10) return 0;
|
|
356
|
+
return -g_tph / g_phph;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Calculate effective potential for Kerr geodesics (equatorial plane).
|
|
361
|
+
*
|
|
362
|
+
* @param {number} M - Mass parameter
|
|
363
|
+
* @param {number} a - Spin parameter
|
|
364
|
+
* @param {number} E - Energy per unit mass
|
|
365
|
+
* @param {number} L - Angular momentum per unit mass
|
|
366
|
+
* @param {number} r - Radial coordinate
|
|
367
|
+
* @returns {number} Effective potential value
|
|
368
|
+
*/
|
|
369
|
+
static kerrEffectivePotential(M, a, E, L, r) {
|
|
370
|
+
if (r <= 0) return Infinity;
|
|
371
|
+
|
|
372
|
+
const r2 = r * r;
|
|
373
|
+
const a2 = a * a;
|
|
374
|
+
const Delta = r2 - 2 * M * r + a2;
|
|
375
|
+
|
|
376
|
+
// Simplified effective potential for equatorial orbits
|
|
377
|
+
const term1 = -M / r;
|
|
378
|
+
const term2 = (L * L - a2 * (E * E - 1)) / (2 * r2);
|
|
379
|
+
const term3 = -M * Math.pow(L - a * E, 2) / (r2 * r);
|
|
380
|
+
|
|
381
|
+
return term1 + term2 + term3;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Create a diagonal tensor from an array of values.
|
|
386
|
+
* @param {number[]} values - Diagonal values
|
|
387
|
+
* @param {Object} [options={}] - Optional metadata
|
|
388
|
+
* @returns {Tensor} Diagonal tensor
|
|
389
|
+
*/
|
|
390
|
+
static diagonal(values, options = {}) {
|
|
391
|
+
const n = values.length;
|
|
392
|
+
const components = [];
|
|
393
|
+
for (let i = 0; i < n; i++) {
|
|
394
|
+
components[i] = [];
|
|
395
|
+
for (let j = 0; j < n; j++) {
|
|
396
|
+
components[i][j] = i === j ? values[i] : 0;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return new Tensor(components, options);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Create an identity tensor of given dimension.
|
|
404
|
+
* @param {number} [n=4] - Dimension
|
|
405
|
+
* @returns {Tensor} Identity tensor
|
|
406
|
+
*/
|
|
407
|
+
static identity(n = 4) {
|
|
408
|
+
const values = new Array(n).fill(1);
|
|
409
|
+
return Tensor.diagonal(values, { name: "Identity" });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Create a zero tensor of given dimension.
|
|
414
|
+
* @param {number} [n=4] - Dimension
|
|
415
|
+
* @returns {Tensor} Zero tensor
|
|
416
|
+
*/
|
|
417
|
+
static zero(n = 4) {
|
|
418
|
+
const components = [];
|
|
419
|
+
for (let i = 0; i < n; i++) {
|
|
420
|
+
components[i] = new Array(n).fill(0);
|
|
421
|
+
}
|
|
422
|
+
return new Tensor(components, { name: "Zero" });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
426
|
+
// COMPONENT ACCESS
|
|
427
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Get a component at the specified indices.
|
|
431
|
+
* @param {number} i - Row index
|
|
432
|
+
* @param {number} j - Column index
|
|
433
|
+
* @returns {number} Component value
|
|
434
|
+
*/
|
|
435
|
+
get(i, j) {
|
|
436
|
+
return this.#components[i][j];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Return a new tensor with the specified component changed.
|
|
441
|
+
* @param {number} i - Row index
|
|
442
|
+
* @param {number} j - Column index
|
|
443
|
+
* @param {number} value - New value
|
|
444
|
+
* @returns {Tensor} New tensor with updated component
|
|
445
|
+
*/
|
|
446
|
+
set(i, j, value) {
|
|
447
|
+
const newComponents = this.#components.map((row) => [...row]);
|
|
448
|
+
newComponents[i][j] = value;
|
|
449
|
+
return new Tensor(newComponents, {
|
|
450
|
+
name: this.#name,
|
|
451
|
+
signature: this.#signature,
|
|
452
|
+
coordinates: this.#coordinates,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get the diagonal components as an array.
|
|
458
|
+
* @returns {number[]} Diagonal values
|
|
459
|
+
*/
|
|
460
|
+
getDiagonal() {
|
|
461
|
+
const diag = [];
|
|
462
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
463
|
+
diag.push(this.#components[i][i]);
|
|
464
|
+
}
|
|
465
|
+
return diag;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get the dimension of the tensor.
|
|
470
|
+
* @returns {number} Dimension (n for n×n tensor)
|
|
471
|
+
*/
|
|
472
|
+
get dimension() {
|
|
473
|
+
return this.#dimension;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get the tensor name.
|
|
478
|
+
* @returns {string} Name
|
|
479
|
+
*/
|
|
480
|
+
get name() {
|
|
481
|
+
return this.#name;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get the metric signature.
|
|
486
|
+
* @returns {number[]|null} Signature array or null
|
|
487
|
+
*/
|
|
488
|
+
get signature() {
|
|
489
|
+
return this.#signature;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Get the coordinate names.
|
|
494
|
+
* @returns {string[]|null} Coordinate names or null
|
|
495
|
+
*/
|
|
496
|
+
get coordinates() {
|
|
497
|
+
return this.#coordinates;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
501
|
+
// TENSOR OPERATIONS
|
|
502
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Add another tensor to this one.
|
|
506
|
+
* @param {Tensor} other - Tensor to add
|
|
507
|
+
* @returns {Tensor} Sum of tensors
|
|
508
|
+
*/
|
|
509
|
+
add(other) {
|
|
510
|
+
const result = [];
|
|
511
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
512
|
+
result[i] = [];
|
|
513
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
514
|
+
result[i][j] = this.#components[i][j] + other.get(i, j);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return new Tensor(result);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Subtract another tensor from this one.
|
|
522
|
+
* @param {Tensor} other - Tensor to subtract
|
|
523
|
+
* @returns {Tensor} Difference of tensors
|
|
524
|
+
*/
|
|
525
|
+
subtract(other) {
|
|
526
|
+
const result = [];
|
|
527
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
528
|
+
result[i] = [];
|
|
529
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
530
|
+
result[i][j] = this.#components[i][j] - other.get(i, j);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return new Tensor(result);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Multiply this tensor by a scalar.
|
|
538
|
+
* @param {number} scalar - Scalar multiplier
|
|
539
|
+
* @returns {Tensor} Scaled tensor
|
|
540
|
+
*/
|
|
541
|
+
scale(scalar) {
|
|
542
|
+
const result = [];
|
|
543
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
544
|
+
result[i] = [];
|
|
545
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
546
|
+
result[i][j] = this.#components[i][j] * scalar;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return new Tensor(result);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Matrix multiply this tensor with another.
|
|
554
|
+
* @param {Tensor} other - Tensor to multiply with
|
|
555
|
+
* @returns {Tensor} Product tensor
|
|
556
|
+
*/
|
|
557
|
+
multiply(other) {
|
|
558
|
+
const result = [];
|
|
559
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
560
|
+
result[i] = [];
|
|
561
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
562
|
+
let sum = 0;
|
|
563
|
+
for (let k = 0; k < this.#dimension; k++) {
|
|
564
|
+
sum += this.#components[i][k] * other.get(k, j);
|
|
565
|
+
}
|
|
566
|
+
result[i][j] = sum;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return new Tensor(result);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Transpose this tensor (swap indices).
|
|
574
|
+
* @returns {Tensor} Transposed tensor
|
|
575
|
+
*/
|
|
576
|
+
transpose() {
|
|
577
|
+
const result = [];
|
|
578
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
579
|
+
result[i] = [];
|
|
580
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
581
|
+
result[i][j] = this.#components[j][i];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return new Tensor(result, {
|
|
585
|
+
name: this.#name ? `${this.#name}ᵀ` : "",
|
|
586
|
+
signature: this.#signature,
|
|
587
|
+
coordinates: this.#coordinates,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Compute the inverse of this tensor (for rank-2).
|
|
593
|
+
* Uses Gaussian elimination with partial pivoting.
|
|
594
|
+
* @returns {Tensor} Inverse tensor
|
|
595
|
+
*/
|
|
596
|
+
inverse() {
|
|
597
|
+
const n = this.#dimension;
|
|
598
|
+
|
|
599
|
+
// Fast path for diagonal tensors: O(n)
|
|
600
|
+
if (this.isDiagonal()) {
|
|
601
|
+
const diag = this.getDiagonal();
|
|
602
|
+
const invDiag = diag.map((v) => {
|
|
603
|
+
if (Math.abs(v) < 1e-15) {
|
|
604
|
+
throw new Error("Diagonal matrix is singular, cannot compute inverse");
|
|
605
|
+
}
|
|
606
|
+
return 1 / v;
|
|
607
|
+
});
|
|
608
|
+
return Tensor.diagonal(invDiag, {
|
|
609
|
+
name: this.#name ? `${this.#name}⁻¹` : "",
|
|
610
|
+
signature: this.#signature,
|
|
611
|
+
coordinates: this.#coordinates,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Create augmented matrix [A|I]
|
|
616
|
+
const aug = [];
|
|
617
|
+
for (let i = 0; i < n; i++) {
|
|
618
|
+
aug[i] = [];
|
|
619
|
+
for (let j = 0; j < n; j++) {
|
|
620
|
+
aug[i][j] = this.#components[i][j];
|
|
621
|
+
}
|
|
622
|
+
for (let j = 0; j < n; j++) {
|
|
623
|
+
aug[i][n + j] = i === j ? 1 : 0;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Gaussian elimination with partial pivoting
|
|
628
|
+
for (let col = 0; col < n; col++) {
|
|
629
|
+
// Find pivot
|
|
630
|
+
let maxRow = col;
|
|
631
|
+
for (let row = col + 1; row < n; row++) {
|
|
632
|
+
if (Math.abs(aug[row][col]) > Math.abs(aug[maxRow][col])) {
|
|
633
|
+
maxRow = row;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Swap rows
|
|
638
|
+
[aug[col], aug[maxRow]] = [aug[maxRow], aug[col]];
|
|
639
|
+
|
|
640
|
+
// Check for singular matrix
|
|
641
|
+
if (Math.abs(aug[col][col]) < 1e-10) {
|
|
642
|
+
throw new Error("Matrix is singular, cannot compute inverse");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Scale pivot row
|
|
646
|
+
const pivot = aug[col][col];
|
|
647
|
+
for (let j = 0; j < 2 * n; j++) {
|
|
648
|
+
aug[col][j] /= pivot;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Eliminate column
|
|
652
|
+
for (let row = 0; row < n; row++) {
|
|
653
|
+
if (row !== col) {
|
|
654
|
+
const factor = aug[row][col];
|
|
655
|
+
for (let j = 0; j < 2 * n; j++) {
|
|
656
|
+
aug[row][j] -= factor * aug[col][j];
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Extract inverse from augmented matrix
|
|
663
|
+
const result = [];
|
|
664
|
+
for (let i = 0; i < n; i++) {
|
|
665
|
+
result[i] = [];
|
|
666
|
+
for (let j = 0; j < n; j++) {
|
|
667
|
+
result[i][j] = aug[i][n + j];
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return new Tensor(result, {
|
|
672
|
+
name: this.#name ? `${this.#name}⁻¹` : "",
|
|
673
|
+
signature: this.#signature,
|
|
674
|
+
coordinates: this.#coordinates,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
679
|
+
// DERIVED QUANTITIES
|
|
680
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Compute the determinant of this tensor.
|
|
684
|
+
* Uses LU decomposition for efficiency.
|
|
685
|
+
* @returns {number} Determinant
|
|
686
|
+
*/
|
|
687
|
+
determinant() {
|
|
688
|
+
const n = this.#dimension;
|
|
689
|
+
|
|
690
|
+
// Fast path for diagonal tensors: O(n)
|
|
691
|
+
if (this.isDiagonal()) {
|
|
692
|
+
return this.getDiagonal().reduce((acc, v) => acc * v, 1);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// For small matrices, use direct formulas
|
|
696
|
+
if (n === 2) {
|
|
697
|
+
return (
|
|
698
|
+
this.#components[0][0] * this.#components[1][1] -
|
|
699
|
+
this.#components[0][1] * this.#components[1][0]
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (n === 3) {
|
|
704
|
+
const m = this.#components;
|
|
705
|
+
return (
|
|
706
|
+
m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) -
|
|
707
|
+
m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) +
|
|
708
|
+
m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// For 4x4 and larger, use LU decomposition
|
|
713
|
+
const lu = this.#components.map((row) => [...row]);
|
|
714
|
+
let det = 1;
|
|
715
|
+
let swaps = 0;
|
|
716
|
+
|
|
717
|
+
for (let col = 0; col < n; col++) {
|
|
718
|
+
// Find pivot
|
|
719
|
+
let maxRow = col;
|
|
720
|
+
for (let row = col + 1; row < n; row++) {
|
|
721
|
+
if (Math.abs(lu[row][col]) > Math.abs(lu[maxRow][col])) {
|
|
722
|
+
maxRow = row;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (maxRow !== col) {
|
|
727
|
+
[lu[col], lu[maxRow]] = [lu[maxRow], lu[col]];
|
|
728
|
+
swaps++;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (Math.abs(lu[col][col]) < 1e-10) {
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
det *= lu[col][col];
|
|
736
|
+
|
|
737
|
+
for (let row = col + 1; row < n; row++) {
|
|
738
|
+
lu[row][col] /= lu[col][col];
|
|
739
|
+
for (let j = col + 1; j < n; j++) {
|
|
740
|
+
lu[row][j] -= lu[row][col] * lu[col][j];
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return swaps % 2 === 0 ? det : -det;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Compute the trace (sum of diagonal elements).
|
|
750
|
+
* @returns {number} Trace
|
|
751
|
+
*/
|
|
752
|
+
trace() {
|
|
753
|
+
let sum = 0;
|
|
754
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
755
|
+
sum += this.#components[i][i];
|
|
756
|
+
}
|
|
757
|
+
return sum;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Check if the tensor is diagonal (off-diagonal elements near zero).
|
|
762
|
+
* @param {number} [tolerance=1e-10] - Tolerance for zero comparison
|
|
763
|
+
* @returns {boolean} True if diagonal
|
|
764
|
+
*/
|
|
765
|
+
isDiagonal(tolerance = 1e-10) {
|
|
766
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
767
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
768
|
+
if (i !== j && Math.abs(this.#components[i][j]) > tolerance) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Check if the tensor is symmetric.
|
|
778
|
+
* @param {number} [tolerance=1e-10] - Tolerance for comparison
|
|
779
|
+
* @returns {boolean} True if symmetric
|
|
780
|
+
*/
|
|
781
|
+
isSymmetric(tolerance = 1e-10) {
|
|
782
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
783
|
+
for (let j = i + 1; j < this.#dimension; j++) {
|
|
784
|
+
if (
|
|
785
|
+
Math.abs(this.#components[i][j] - this.#components[j][i]) > tolerance
|
|
786
|
+
) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
795
|
+
// GR-SPECIFIC STATIC UTILITIES
|
|
796
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Compute Christoffel symbols (connection coefficients) for a metric.
|
|
800
|
+
* Uses numerical differentiation by default, but switches to analytical
|
|
801
|
+
* forms for recognized metrics (e.g., Schwarzschild).
|
|
802
|
+
*
|
|
803
|
+
* @param {Function} metricFn - Function(position: number[]) => Tensor
|
|
804
|
+
* @param {number[]} position - Position [t, r, θ, φ] at which to evaluate
|
|
805
|
+
* @param {number} [delta=0.001] - Step size for numerical differentiation
|
|
806
|
+
* @returns {number[][][]} Christoffel symbols Γ[lambda][mu][nu]
|
|
807
|
+
*/
|
|
808
|
+
static christoffel(metricFn, position, delta = 0.001) {
|
|
809
|
+
const g = metricFn(position);
|
|
810
|
+
|
|
811
|
+
// Fast path for Schwarzschild metric (analytical)
|
|
812
|
+
if (g.name === "Schwarzschild") {
|
|
813
|
+
const rs = position._rs || 2; // Expecting rs attached to position or default
|
|
814
|
+
return Tensor.schwarzschildChristoffel(position[1], rs, position[2]);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const dim = 4;
|
|
818
|
+
const result = [];
|
|
819
|
+
const gInv = g.inverse();
|
|
820
|
+
|
|
821
|
+
// Calculate partial derivatives ∂_ρ g_μν
|
|
822
|
+
const dg = []; // dg[rho][mu][nu] = ∂_ρ g_μν
|
|
823
|
+
for (let rho = 0; rho < dim; rho++) {
|
|
824
|
+
const posPlus = [...position];
|
|
825
|
+
const posMinus = [...position];
|
|
826
|
+
posPlus[rho] += delta;
|
|
827
|
+
posMinus[rho] -= delta;
|
|
828
|
+
|
|
829
|
+
const gPlus = metricFn(posPlus);
|
|
830
|
+
const gMinus = metricFn(posMinus);
|
|
831
|
+
|
|
832
|
+
dg[rho] = [];
|
|
833
|
+
for (let mu = 0; mu < dim; mu++) {
|
|
834
|
+
dg[rho][mu] = [];
|
|
835
|
+
for (let nu = 0; nu < dim; nu++) {
|
|
836
|
+
dg[rho][mu][nu] = (gPlus.get(mu, nu) - gMinus.get(mu, nu)) / (2 * delta);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Compute Christoffel symbols using Einstein notation logic
|
|
842
|
+
// Γ^λ_μν = ½ g^λσ (∂_μ g_νσ + ∂_ν g_μσ - ∂_σ g_μν)
|
|
843
|
+
for (let lambda = 0; lambda < dim; lambda++) {
|
|
844
|
+
result[lambda] = [];
|
|
845
|
+
for (let mu = 0; mu < dim; mu++) {
|
|
846
|
+
result[lambda][mu] = [];
|
|
847
|
+
for (let nu = 0; nu < dim; nu++) {
|
|
848
|
+
let sum = 0;
|
|
849
|
+
for (let sigma = 0; sigma < dim; sigma++) {
|
|
850
|
+
const g_inv_ls = gInv.get(lambda, sigma);
|
|
851
|
+
if (Math.abs(g_inv_ls) < 1e-15) continue; // Skip zero terms
|
|
852
|
+
|
|
853
|
+
sum +=
|
|
854
|
+
(g_inv_ls *
|
|
855
|
+
(dg[mu][nu][sigma] + dg[nu][mu][sigma] - dg[sigma][mu][nu])) /
|
|
856
|
+
2;
|
|
857
|
+
}
|
|
858
|
+
result[lambda][mu][nu] = sum;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Analytical Christoffel symbols for Schwarzschild metric.
|
|
868
|
+
* @param {number} r - Radial coordinate
|
|
869
|
+
* @param {number} rs - Schwarzschild radius
|
|
870
|
+
* @param {number} theta - Polar angle
|
|
871
|
+
* @returns {number[][][]} Christoffel symbols
|
|
872
|
+
*/
|
|
873
|
+
static schwarzschildChristoffel(r, rs, theta) {
|
|
874
|
+
const dim = 4;
|
|
875
|
+
const G = []; // Gamma[lambda][mu][nu]
|
|
876
|
+
for (let i = 0; i < dim; i++) {
|
|
877
|
+
G[i] = [];
|
|
878
|
+
for (let j = 0; j < dim; j++) {
|
|
879
|
+
G[i][j] = new Array(dim).fill(0);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const factor = 1 - rs / r;
|
|
884
|
+
if (Math.abs(factor) < 1e-10) return G; // singularity
|
|
885
|
+
|
|
886
|
+
const rs_2r2 = rs / (2 * r * r);
|
|
887
|
+
const cotTheta = 1 / Math.tan(theta);
|
|
888
|
+
const sinTheta = Math.sin(theta);
|
|
889
|
+
const cosTheta = Math.cos(theta);
|
|
890
|
+
|
|
891
|
+
// Gamma^t_tr = Gamma^t_rt
|
|
892
|
+
G[0][0][1] = G[0][1][0] = rs_2r2 / factor;
|
|
893
|
+
|
|
894
|
+
// Gamma^r_tt
|
|
895
|
+
G[1][0][0] = (rs * factor) / (2 * r * r);
|
|
896
|
+
|
|
897
|
+
// Gamma^r_rr
|
|
898
|
+
G[1][1][1] = -rs_2r2 / factor;
|
|
899
|
+
|
|
900
|
+
// Gamma^r_thth
|
|
901
|
+
G[1][2][2] = -(r - rs);
|
|
902
|
+
|
|
903
|
+
// Gamma^r_phph
|
|
904
|
+
G[1][3][3] = -(r - rs) * sinTheta * sinTheta;
|
|
905
|
+
|
|
906
|
+
// Gamma^th_rth = Gamma^th_thr
|
|
907
|
+
G[2][1][2] = G[2][2][1] = 1 / r;
|
|
908
|
+
|
|
909
|
+
// Gamma^th_phph
|
|
910
|
+
G[2][3][3] = -sinTheta * cosTheta;
|
|
911
|
+
|
|
912
|
+
// Gamma^ph_rph = Gamma^ph_phr
|
|
913
|
+
G[3][1][3] = G[3][3][1] = 1 / r;
|
|
914
|
+
|
|
915
|
+
// Gamma^ph_thph = Gamma^ph_phth
|
|
916
|
+
G[3][2][3] = G[3][3][2] = cotTheta;
|
|
917
|
+
|
|
918
|
+
return G;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Compute the effective potential for geodesic motion in Schwarzschild spacetime.
|
|
923
|
+
*
|
|
924
|
+
* V_eff = -M/r + L²/(2r²) - ML²/r³
|
|
925
|
+
*
|
|
926
|
+
* @param {number} M - Mass (Schwarzschild radius / 2)
|
|
927
|
+
* @param {number} L - Angular momentum per unit mass
|
|
928
|
+
* @param {number} r - Radial coordinate
|
|
929
|
+
* @returns {number} Effective potential value
|
|
930
|
+
*/
|
|
931
|
+
static effectivePotential(M, L, r) {
|
|
932
|
+
if (r <= 0) return Infinity;
|
|
933
|
+
const L2 = L * L;
|
|
934
|
+
return -M / r + L2 / (2 * r * r) - (M * L2) / (r * r * r);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Find the ISCO (Innermost Stable Circular Orbit) radius.
|
|
939
|
+
* For Schwarzschild: r_ISCO = 6M = 3rs
|
|
940
|
+
*
|
|
941
|
+
* @param {number} rs - Schwarzschild radius
|
|
942
|
+
* @returns {number} ISCO radius
|
|
943
|
+
*/
|
|
944
|
+
static iscoRadius(rs) {
|
|
945
|
+
return 3 * rs;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Find the photon sphere radius.
|
|
950
|
+
* For Schwarzschild: r_photon = 3M = 1.5rs
|
|
951
|
+
*
|
|
952
|
+
* @param {number} rs - Schwarzschild radius
|
|
953
|
+
* @returns {number} Photon sphere radius
|
|
954
|
+
*/
|
|
955
|
+
static photonSphereRadius(rs) {
|
|
956
|
+
return 1.5 * rs;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
960
|
+
// DISPLAY/UTILITY
|
|
961
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Get a flat array of all components (row-major order).
|
|
965
|
+
* @returns {number[]} Flat array
|
|
966
|
+
*/
|
|
967
|
+
toArray() {
|
|
968
|
+
const flat = [];
|
|
969
|
+
for (let i = 0; i < this.#dimension; i++) {
|
|
970
|
+
for (let j = 0; j < this.#dimension; j++) {
|
|
971
|
+
flat.push(this.#components[i][j]);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return flat;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Get a 2D array copy of the components.
|
|
979
|
+
* @returns {number[][]} 2D array
|
|
980
|
+
*/
|
|
981
|
+
toMatrix() {
|
|
982
|
+
return this.#components.map((row) => [...row]);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Get a string representation of the tensor.
|
|
987
|
+
* @param {number} [precision=3] - Decimal precision
|
|
988
|
+
* @returns {string} String representation
|
|
989
|
+
*/
|
|
990
|
+
toString(precision = 3) {
|
|
991
|
+
const header = this.#name ? `${this.#name} Tensor:\n` : "";
|
|
992
|
+
const rows = this.#components.map(
|
|
993
|
+
(row) => `[ ${row.map((v) => v.toFixed(precision).padStart(10)).join(" ")} ]`
|
|
994
|
+
);
|
|
995
|
+
return header + rows.join("\n");
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Get a LaTeX representation of the tensor.
|
|
1000
|
+
* @param {number} [precision=3] - Decimal precision
|
|
1001
|
+
* @returns {string} LaTeX string
|
|
1002
|
+
*/
|
|
1003
|
+
toLatex(precision = 3) {
|
|
1004
|
+
const rows = this.#components.map((row) =>
|
|
1005
|
+
row.map((v) => v.toFixed(precision)).join(" & ")
|
|
1006
|
+
);
|
|
1007
|
+
return `\\begin{pmatrix}\n${rows.join(" \\\\\n")}\n\\end{pmatrix}`;
|
|
1008
|
+
}
|
|
1009
|
+
}
|