@interstellar-tools/equations 0.55.0 → 0.56.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/dist/categories/kepler/solve-kepler-high-eccentricity.d.ts +9 -3
- package/dist/categories/kepler/solve-kepler-high-eccentricity.d.ts.map +1 -1
- package/dist/categories/kepler/solve-kepler-high-eccentricity.js +19 -6
- package/dist/categories/kepler/solve-kepler-high-eccentricity.js.map +1 -1
- package/dist/categories/kepler/solve-kepler-newton-raphson.d.ts +14 -17
- package/dist/categories/kepler/solve-kepler-newton-raphson.d.ts.map +1 -1
- package/dist/categories/kepler/solve-kepler-newton-raphson.js +26 -34
- package/dist/categories/kepler/solve-kepler-newton-raphson.js.map +1 -1
- package/dist/categories/kepler/solve-kepler.d.ts +10 -5
- package/dist/categories/kepler/solve-kepler.d.ts.map +1 -1
- package/dist/categories/kepler/solve-kepler.js +23 -14
- package/dist/categories/kepler/solve-kepler.js.map +1 -1
- package/package.json +4 -4
|
@@ -27,7 +27,12 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
27
27
|
*
|
|
28
28
|
* **Solving Strategy:**
|
|
29
29
|
* 1. **Initial Guess:**
|
|
30
|
-
* -
|
|
30
|
+
* - **Elliptical** ($e < 1$): the equation is $2\pi$-periodic, so $M$ is first reduced to one
|
|
31
|
+
* turn with its sign kept ($M \bmod 2\pi$), then Danby's starting value is used:
|
|
32
|
+
* $$
|
|
33
|
+
* E_0 = M + 0.85\, e \,\operatorname{sign}(\sin M)
|
|
34
|
+
* $$
|
|
35
|
+
* - **Hyperbolic** ($e > 1$):
|
|
31
36
|
* $$
|
|
32
37
|
* E_0 = M \pm \frac{e \sin(M)}{1 - e \cos(M)}
|
|
33
38
|
* $$
|
|
@@ -51,7 +56,8 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
51
56
|
* (default tolerance is **1e-9**).
|
|
52
57
|
*
|
|
53
58
|
* 3. **Angle Wrapping (Elliptical Only):**
|
|
54
|
-
* - The result is wrapped using `wrapAngle()
|
|
59
|
+
* - The result is wrapped using `wrapAngle()`, which keeps the sign: $E \in (-2\pi, 2\pi)$ with
|
|
60
|
+
* the sign of $M$. Use `solveKepler()` for a result normalized to $[0, 2\pi)$.
|
|
55
61
|
*
|
|
56
62
|
*
|
|
57
63
|
* **Performance Considerations:**
|
|
@@ -63,7 +69,7 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
63
69
|
* @param {number} e - Orbital eccentricity ($e > 0.9$ for high-eccentricity orbits).
|
|
64
70
|
* @param {number} [maxIter=Math.max(300, Math.floor(5 + 3 * Math.log(1 + Math.abs(M))))] - Maximum number of **iterations** before failure.
|
|
65
71
|
* @param {number} [tolerance=1e-9] - Convergence criterion for stopping the iteration.
|
|
66
|
-
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (
|
|
72
|
+
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (for elliptical orbits in $(-2\pi, 2\pi)$, with the sign of $M$).
|
|
67
73
|
*
|
|
68
74
|
*
|
|
69
75
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve-kepler-high-eccentricity.d.ts","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-high-eccentricity.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve-kepler-high-eccentricity.d.ts","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-high-eccentricity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAKzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;AAEH,eAAO,MAAM,2BAA2B,GACtC,GAAG,OAAO,EACV,GAAG,MAAM,EACT,gBAAsE,EACtE,kBAAgB,KACf,OAqEF,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TWO_PI } from '@interstellar-tools/constants';
|
|
1
2
|
import { wrapAngle } from '../angle/wrap-angle.js';
|
|
2
3
|
/* node:coverage disable */
|
|
3
4
|
/**
|
|
@@ -28,7 +29,12 @@ import { wrapAngle } from '../angle/wrap-angle.js';
|
|
|
28
29
|
*
|
|
29
30
|
* **Solving Strategy:**
|
|
30
31
|
* 1. **Initial Guess:**
|
|
31
|
-
* -
|
|
32
|
+
* - **Elliptical** ($e < 1$): the equation is $2\pi$-periodic, so $M$ is first reduced to one
|
|
33
|
+
* turn with its sign kept ($M \bmod 2\pi$), then Danby's starting value is used:
|
|
34
|
+
* $$
|
|
35
|
+
* E_0 = M + 0.85\, e \,\operatorname{sign}(\sin M)
|
|
36
|
+
* $$
|
|
37
|
+
* - **Hyperbolic** ($e > 1$):
|
|
32
38
|
* $$
|
|
33
39
|
* E_0 = M \pm \frac{e \sin(M)}{1 - e \cos(M)}
|
|
34
40
|
* $$
|
|
@@ -52,7 +58,8 @@ import { wrapAngle } from '../angle/wrap-angle.js';
|
|
|
52
58
|
* (default tolerance is **1e-9**).
|
|
53
59
|
*
|
|
54
60
|
* 3. **Angle Wrapping (Elliptical Only):**
|
|
55
|
-
* - The result is wrapped using `wrapAngle()
|
|
61
|
+
* - The result is wrapped using `wrapAngle()`, which keeps the sign: $E \in (-2\pi, 2\pi)$ with
|
|
62
|
+
* the sign of $M$. Use `solveKepler()` for a result normalized to $[0, 2\pi)$.
|
|
56
63
|
*
|
|
57
64
|
*
|
|
58
65
|
* **Performance Considerations:**
|
|
@@ -64,7 +71,7 @@ import { wrapAngle } from '../angle/wrap-angle.js';
|
|
|
64
71
|
* @param {number} e - Orbital eccentricity ($e > 0.9$ for high-eccentricity orbits).
|
|
65
72
|
* @param {number} [maxIter=Math.max(300, Math.floor(5 + 3 * Math.log(1 + Math.abs(M))))] - Maximum number of **iterations** before failure.
|
|
66
73
|
* @param {number} [tolerance=1e-9] - Convergence criterion for stopping the iteration.
|
|
67
|
-
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (
|
|
74
|
+
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (for elliptical orbits in $(-2\pi, 2\pi)$, with the sign of $M$).
|
|
68
75
|
*
|
|
69
76
|
*
|
|
70
77
|
* @example
|
|
@@ -96,8 +103,14 @@ export const solveKeplerHighEccentricity = (M, e, maxIter = Math.max(300, Math.f
|
|
|
96
103
|
throw new RangeError(`Invalid eccentricity: ${e}. Must be e ≥ 0.`);
|
|
97
104
|
}
|
|
98
105
|
let E;
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
if (e < 1) {
|
|
107
|
+
// The elliptical equation is 2π-periodic: solve within one turn (sign kept),
|
|
108
|
+
// so large |M| doesn't run into the |E| > 100 divergence guard below
|
|
109
|
+
M = (M % TWO_PI);
|
|
110
|
+
// Danby's starting value, good for all 0 ≤ e < 1
|
|
111
|
+
E = M + 0.85 * e * Math.sign(Math.sin(M));
|
|
112
|
+
}
|
|
113
|
+
else if (M < 0) {
|
|
101
114
|
E = M - (e * Math.sin(M)) / (1 - e * Math.cos(M));
|
|
102
115
|
}
|
|
103
116
|
else {
|
|
@@ -137,7 +150,7 @@ export const solveKeplerHighEccentricity = (M, e, maxIter = Math.max(300, Math.f
|
|
|
137
150
|
break;
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
|
-
// **Wrap angles to
|
|
153
|
+
// **Wrap angles to (-2π, 2π), keeping the sign, for elliptical orbits**
|
|
141
154
|
if (e < 1) {
|
|
142
155
|
lastValidE = wrapAngle(lastValidE);
|
|
143
156
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve-kepler-high-eccentricity.js","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-high-eccentricity.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve-kepler-high-eccentricity.js","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-high-eccentricity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAGvD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,2BAA2B;AAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;AACH,0BAA0B;AAC1B,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,CAAU,EACV,CAAS,EACT,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACtE,SAAS,GAAG,IAAI,EACP,EAAE;IACX,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,MAAM,IAAI,UAAU,CAAC,yBAAyB,CAAC,kBAAkB,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,CAAS,CAAC;IAEd,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,6EAA6E;QAC7E,qEAAqE;QACrE,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAY,CAAC;QAC5B,iDAAiD;QACjD,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;SAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;SAAM,CAAC;QACN,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,IAAI,IAAI,GAAG,OAAO,EAAE,CAAC;QACrD,IAAI,EAAU,CAAC;QACf,IAAI,GAAW,CAAC;QAEhB,qCAAqC;QACrC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACV,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7B,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,+BAA+B;aAC1B,CAAC;YACJ,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAE3B,EAAE,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;YACvB,GAAG,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QACtB,CAAC;QAED,oCAAoC;QACpC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAErD,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC;QAEjB,yCAAyC;QACzC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QAE1D,CAAC,IAAI,KAAK,CAAC;QACX,IAAI,EAAE,CAAC;QAEP,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,UAAU,GAAG,CAAC,CAAC;QACjB,CAAC;QAED,iDAAiD;QACjD,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC;YACtB,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,UAAqB,CAAC;AAC/B,CAAC,CAAC"}
|
|
@@ -18,27 +18,23 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
18
18
|
*
|
|
19
19
|
* **Solving Strategy:**
|
|
20
20
|
* 1. **Handle Special Cases:**
|
|
21
|
-
* - If the orbit is **circular** ($e = 0$), then $E = M$ directly.
|
|
22
|
-
* - If the orbit is **nearly parabolic** ($e \geq 0.97$), a special approximation is used.
|
|
23
21
|
* - If **eccentricity is out of range** ($e < 0$ or $e \geq 1$), a `RangeError` is thrown.
|
|
22
|
+
* - Kepler's equation is $2\pi$-periodic ($M + 2\pi k \mapsto E + 2\pi k$), so the equation is
|
|
23
|
+
* solved for $M \bmod 2\pi$ and the whole turns are added back. Any finite $M$ works,
|
|
24
|
+
* including negative values and many revolutions.
|
|
24
25
|
*
|
|
25
|
-
* 2. **Initial Approximation
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* 2. **Initial Approximation** (Danby 1987, good for all $0 \leq e < 1$):
|
|
27
|
+
* $$
|
|
28
|
+
* E_0 = M + 0.85\, e \,\operatorname{sign}(\sin M)
|
|
29
|
+
* $$
|
|
29
30
|
*
|
|
30
31
|
* 3. **Newton-Raphson Iteration with Householder Acceleration:**
|
|
31
|
-
* -
|
|
32
|
+
* - With $f(E) = E - e \sin(E) - M$, $f'(E) = 1 - e \cos(E)$, $f''(E) = e \sin(E)$ and
|
|
33
|
+
* $f'''(E) = e \cos(E)$, each step refines the Newton correction to third order:
|
|
32
34
|
* $$
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* - $f(E) = E - e \sin(E) - M$
|
|
37
|
-
* - $f'(E) = 1 - e \cos(E)$
|
|
38
|
-
*
|
|
39
|
-
* - **Householder acceleration** refines the correction:
|
|
40
|
-
* $$
|
|
41
|
-
* \Delta E = \frac{f(E)}{f'(E)} \left( 1 - \frac{1}{2} \frac{f''(E)}{f'(E)} \Delta E \right)^{-1}
|
|
35
|
+
* \delta_1 = -\frac{f}{f'}, \quad
|
|
36
|
+
* \delta_2 = -\frac{f}{f' + \tfrac{1}{2} \delta_1 f''}, \quad
|
|
37
|
+
* \Delta E = -\frac{f}{f' + \tfrac{1}{2} \delta_2 f'' + \tfrac{1}{6} \delta_2^2 f'''}
|
|
42
38
|
* $$
|
|
43
39
|
*
|
|
44
40
|
* 4. **Convergence Check:**
|
|
@@ -54,7 +50,7 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
54
50
|
* ---
|
|
55
51
|
*
|
|
56
52
|
* **Performance Considerations:**
|
|
57
|
-
* - **Typically converges in 4
|
|
53
|
+
* - **Typically converges in 3-4 iterations, including $e \to 1$.**
|
|
58
54
|
* - **Time complexity:** $O(1)$ for Newton-Raphson.
|
|
59
55
|
*
|
|
60
56
|
* ---
|
|
@@ -81,6 +77,7 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
81
77
|
* @see [Kepler's Equation (Wikipedia)](https://en.wikipedia.org/wiki/Kepler%27s_equation)
|
|
82
78
|
* @see [Newton-Raphson Method (Wikipedia)](https://en.wikipedia.org/wiki/Newton%27s_method)
|
|
83
79
|
* @see [Eccentric Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly#Eccentric_anomaly)
|
|
80
|
+
* @see Danby, J. M. A. (1987). The solution of Kepler's equation, III. *Celestial Mechanics*, 40, 303–312.
|
|
84
81
|
* @group Kepler Solvers
|
|
85
82
|
*/
|
|
86
83
|
export declare const solveKeplerNewtonRaphson: (M: Radians, e: number, maxIter: number, tolerance: number) => Radians | number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve-kepler-newton-raphson.d.ts","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-newton-raphson.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve-kepler-newton-raphson.d.ts","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-newton-raphson.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH,eAAO,MAAM,wBAAwB,GACnC,GAAG,OAAO,EACV,GAAG,MAAM,EACT,SAAS,MAAM,EACf,WAAW,MAAM,KAChB,OAAO,GAAG,MAkCZ,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TWO_PI } from '@interstellar-tools/constants';
|
|
1
2
|
/**
|
|
2
3
|
* Solves **Kepler's Equation** for the **Eccentric Anomaly** ($E$) using the **Newton-Raphson method**
|
|
3
4
|
* with Householder acceleration for fast convergence.
|
|
@@ -17,27 +18,23 @@
|
|
|
17
18
|
*
|
|
18
19
|
* **Solving Strategy:**
|
|
19
20
|
* 1. **Handle Special Cases:**
|
|
20
|
-
* - If the orbit is **circular** ($e = 0$), then $E = M$ directly.
|
|
21
|
-
* - If the orbit is **nearly parabolic** ($e \geq 0.97$), a special approximation is used.
|
|
22
21
|
* - If **eccentricity is out of range** ($e < 0$ or $e \geq 1$), a `RangeError` is thrown.
|
|
22
|
+
* - Kepler's equation is $2\pi$-periodic ($M + 2\pi k \mapsto E + 2\pi k$), so the equation is
|
|
23
|
+
* solved for $M \bmod 2\pi$ and the whole turns are added back. Any finite $M$ works,
|
|
24
|
+
* including negative values and many revolutions.
|
|
23
25
|
*
|
|
24
|
-
* 2. **Initial Approximation
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* 2. **Initial Approximation** (Danby 1987, good for all $0 \leq e < 1$):
|
|
27
|
+
* $$
|
|
28
|
+
* E_0 = M + 0.85\, e \,\operatorname{sign}(\sin M)
|
|
29
|
+
* $$
|
|
28
30
|
*
|
|
29
31
|
* 3. **Newton-Raphson Iteration with Householder Acceleration:**
|
|
30
|
-
* -
|
|
32
|
+
* - With $f(E) = E - e \sin(E) - M$, $f'(E) = 1 - e \cos(E)$, $f''(E) = e \sin(E)$ and
|
|
33
|
+
* $f'''(E) = e \cos(E)$, each step refines the Newton correction to third order:
|
|
31
34
|
* $$
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* - $f(E) = E - e \sin(E) - M$
|
|
36
|
-
* - $f'(E) = 1 - e \cos(E)$
|
|
37
|
-
*
|
|
38
|
-
* - **Householder acceleration** refines the correction:
|
|
39
|
-
* $$
|
|
40
|
-
* \Delta E = \frac{f(E)}{f'(E)} \left( 1 - \frac{1}{2} \frac{f''(E)}{f'(E)} \Delta E \right)^{-1}
|
|
35
|
+
* \delta_1 = -\frac{f}{f'}, \quad
|
|
36
|
+
* \delta_2 = -\frac{f}{f' + \tfrac{1}{2} \delta_1 f''}, \quad
|
|
37
|
+
* \Delta E = -\frac{f}{f' + \tfrac{1}{2} \delta_2 f'' + \tfrac{1}{6} \delta_2^2 f'''}
|
|
41
38
|
* $$
|
|
42
39
|
*
|
|
43
40
|
* 4. **Convergence Check:**
|
|
@@ -53,7 +50,7 @@
|
|
|
53
50
|
* ---
|
|
54
51
|
*
|
|
55
52
|
* **Performance Considerations:**
|
|
56
|
-
* - **Typically converges in 4
|
|
53
|
+
* - **Typically converges in 3-4 iterations, including $e \to 1$.**
|
|
57
54
|
* - **Time complexity:** $O(1)$ for Newton-Raphson.
|
|
58
55
|
*
|
|
59
56
|
* ---
|
|
@@ -80,36 +77,31 @@
|
|
|
80
77
|
* @see [Kepler's Equation (Wikipedia)](https://en.wikipedia.org/wiki/Kepler%27s_equation)
|
|
81
78
|
* @see [Newton-Raphson Method (Wikipedia)](https://en.wikipedia.org/wiki/Newton%27s_method)
|
|
82
79
|
* @see [Eccentric Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly#Eccentric_anomaly)
|
|
80
|
+
* @see Danby, J. M. A. (1987). The solution of Kepler's equation, III. *Celestial Mechanics*, 40, 303–312.
|
|
83
81
|
* @group Kepler Solvers
|
|
84
82
|
*/
|
|
85
83
|
export const solveKeplerNewtonRaphson = (M, e, maxIter, tolerance) => {
|
|
86
84
|
if (e < 0 || e >= 1) {
|
|
87
85
|
throw new RangeError(`Invalid eccentricity: ${e}. Must be in range [0,1).`);
|
|
88
86
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else if (e < 0.97) {
|
|
95
|
-
E = M + e * Math.sin(M) * (1 + e * Math.cos(M));
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
E = (6 * M) / e; // Nearly parabolic case
|
|
99
|
-
}
|
|
87
|
+
// Kepler's equation is 2π-periodic: solve within one turn, add the turns back
|
|
88
|
+
const Mr = M % TWO_PI;
|
|
89
|
+
const turns = M - Mr;
|
|
90
|
+
// Danby's starting value, good for all 0 ≤ e < 1
|
|
91
|
+
let E = Mr + 0.85 * e * Math.sign(Math.sin(Mr));
|
|
100
92
|
let iter = 0;
|
|
101
93
|
while (iter < maxIter) {
|
|
102
|
-
const F = E - e * Math.sin(E) -
|
|
94
|
+
const F = E - e * Math.sin(E) - Mr;
|
|
103
95
|
const dF = 1 - e * Math.cos(E);
|
|
104
96
|
const d2F = e * Math.sin(E);
|
|
105
97
|
const d3F = e * Math.cos(E);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
correction
|
|
98
|
+
// Newton's step, refined to third order (Householder / Danby)
|
|
99
|
+
const delta1 = -F / dF;
|
|
100
|
+
const delta2 = -F / (dF + 0.5 * delta1 * d2F);
|
|
101
|
+
const correction = -F / (dF + 0.5 * delta2 * d2F + (1 / 6) * delta2 * delta2 * d3F);
|
|
110
102
|
E += correction;
|
|
111
103
|
if (Math.abs(correction) < tolerance) {
|
|
112
|
-
return E; // Converged
|
|
104
|
+
return (E + turns); // Converged
|
|
113
105
|
}
|
|
114
106
|
iter++;
|
|
115
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve-kepler-newton-raphson.js","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-newton-raphson.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve-kepler-newton-raphson.js","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler-newton-raphson.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,CAAU,EACV,CAAS,EACT,OAAe,EACf,SAAiB,EACC,EAAE;IACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAAC,yBAAyB,CAAC,2BAA2B,CAAC,CAAC;IAC9E,CAAC;IAED,8EAA8E;IAC9E,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;IACtB,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC;IAErB,iDAAiD;IACjD,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,OAAO,IAAI,GAAG,OAAO,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5B,8DAA8D;QAC9D,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,CAAC,CAAC;QAC9C,MAAM,UAAU,GACd,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,CAAC;QAEnE,CAAC,IAAI,UAAU,CAAC;QAEhB,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,SAAS,EAAE,CAAC;YACrC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAY,CAAC,CAAC,YAAY;QAC7C,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC;IAED,OAAO,GAAG,CAAC,CAAC,mBAAmB;AACjC,CAAC,CAAC"}
|
|
@@ -3,8 +3,8 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
3
3
|
* Solves **Kepler's Equation** for the **Eccentric Anomaly** ($E$) using an adaptive approach:
|
|
4
4
|
*
|
|
5
5
|
* - **Newton-Raphson method** for fast convergence.
|
|
6
|
-
* - **Bisection fallback** if Newton’s method fails.
|
|
7
6
|
* - **High-eccentricity solver** for extreme orbits ($e > 0.9$).
|
|
7
|
+
* - **Bisection fallback** if the selected solver doesn't return a root.
|
|
8
8
|
*
|
|
9
9
|
* ---
|
|
10
10
|
*
|
|
@@ -28,10 +28,15 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
28
28
|
* 2. **Select the Best Solver:**
|
|
29
29
|
* - **For high eccentricities ($e > 0.9$)** → Uses `solveKeplerHighEccentricity()`.
|
|
30
30
|
* - **For moderate eccentricities ($e \leq 0.9$)** → Uses `solveKeplerNewtonRaphson()`.
|
|
31
|
-
* - **
|
|
31
|
+
* - **The result is verified** against Kepler's equation ($|E - e\sin E - M| \leq$ `tolerance`,
|
|
32
|
+
* modulo $2\pi$). If the solver didn't converge (`NaN`), stopped at `maxIter`, or settled
|
|
33
|
+
* away from the root, it falls back to `solveKeplerBisection()`, which always brackets the root.
|
|
32
34
|
*
|
|
33
|
-
* 3. **
|
|
34
|
-
* -
|
|
35
|
+
* 3. **Normalization:**
|
|
36
|
+
* - Kepler's equation is $2\pi$-periodic ($M + 2\pi k \mapsto E + 2\pi k$), so $M$ is first
|
|
37
|
+
* reduced to $[0, 2\pi)$ with `norm2pi()`. This keeps the solvers in their stable range
|
|
38
|
+
* for any finite $M$, including negative values and many revolutions.
|
|
39
|
+
* - The solution is normalized to $[0, 2\pi)$ with `norm2pi()`.
|
|
35
40
|
*
|
|
36
41
|
* ---
|
|
37
42
|
*
|
|
@@ -46,7 +51,7 @@ import type { Radians } from '@interstellar-tools/types';
|
|
|
46
51
|
* @param {number} e - Orbital eccentricity ($0 \leq e < 1$).
|
|
47
52
|
* @param {number} [maxIter=50] - Maximum number of **iterations** before fallback.
|
|
48
53
|
* @param {number} [tolerance=1e-9] - Convergence criterion for stopping the iteration.
|
|
49
|
-
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (
|
|
54
|
+
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (normalized to $[0, 2\pi)$).
|
|
50
55
|
*
|
|
51
56
|
* @throws {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} If the **eccentricity ($e$) is invalid** ($e < 0$ or $e \geq 1$).
|
|
52
57
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve-kepler.d.ts","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve-kepler.d.ts","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAQzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AACH,eAAO,MAAM,WAAW,GACtB,GAAG,OAAO,EACV,GAAG,MAAM,EACT,gBAAY,EACZ,kBAAgB,KACf,OAuBF,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TWO_PI } from '@interstellar-tools/constants';
|
|
2
|
+
import { norm2pi } from '../helpers/misc.js';
|
|
2
3
|
import { solveKeplerBisection } from './solve-kepler-bisection.js';
|
|
3
4
|
import { solveKeplerHighEccentricity } from './solve-kepler-high-eccentricity.js';
|
|
4
5
|
import { solveKeplerNewtonRaphson } from './solve-kepler-newton-raphson.js';
|
|
@@ -6,8 +7,8 @@ import { solveKeplerNewtonRaphson } from './solve-kepler-newton-raphson.js';
|
|
|
6
7
|
* Solves **Kepler's Equation** for the **Eccentric Anomaly** ($E$) using an adaptive approach:
|
|
7
8
|
*
|
|
8
9
|
* - **Newton-Raphson method** for fast convergence.
|
|
9
|
-
* - **Bisection fallback** if Newton’s method fails.
|
|
10
10
|
* - **High-eccentricity solver** for extreme orbits ($e > 0.9$).
|
|
11
|
+
* - **Bisection fallback** if the selected solver doesn't return a root.
|
|
11
12
|
*
|
|
12
13
|
* ---
|
|
13
14
|
*
|
|
@@ -31,10 +32,15 @@ import { solveKeplerNewtonRaphson } from './solve-kepler-newton-raphson.js';
|
|
|
31
32
|
* 2. **Select the Best Solver:**
|
|
32
33
|
* - **For high eccentricities ($e > 0.9$)** → Uses `solveKeplerHighEccentricity()`.
|
|
33
34
|
* - **For moderate eccentricities ($e \leq 0.9$)** → Uses `solveKeplerNewtonRaphson()`.
|
|
34
|
-
* - **
|
|
35
|
+
* - **The result is verified** against Kepler's equation ($|E - e\sin E - M| \leq$ `tolerance`,
|
|
36
|
+
* modulo $2\pi$). If the solver didn't converge (`NaN`), stopped at `maxIter`, or settled
|
|
37
|
+
* away from the root, it falls back to `solveKeplerBisection()`, which always brackets the root.
|
|
35
38
|
*
|
|
36
|
-
* 3. **
|
|
37
|
-
* -
|
|
39
|
+
* 3. **Normalization:**
|
|
40
|
+
* - Kepler's equation is $2\pi$-periodic ($M + 2\pi k \mapsto E + 2\pi k$), so $M$ is first
|
|
41
|
+
* reduced to $[0, 2\pi)$ with `norm2pi()`. This keeps the solvers in their stable range
|
|
42
|
+
* for any finite $M$, including negative values and many revolutions.
|
|
43
|
+
* - The solution is normalized to $[0, 2\pi)$ with `norm2pi()`.
|
|
38
44
|
*
|
|
39
45
|
* ---
|
|
40
46
|
*
|
|
@@ -49,7 +55,7 @@ import { solveKeplerNewtonRaphson } from './solve-kepler-newton-raphson.js';
|
|
|
49
55
|
* @param {number} e - Orbital eccentricity ($0 \leq e < 1$).
|
|
50
56
|
* @param {number} [maxIter=50] - Maximum number of **iterations** before fallback.
|
|
51
57
|
* @param {number} [tolerance=1e-9] - Convergence criterion for stopping the iteration.
|
|
52
|
-
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (
|
|
58
|
+
* @returns {Radians} The **eccentric anomaly** ($E$) in **radians** (normalized to $[0, 2\pi)$).
|
|
53
59
|
*
|
|
54
60
|
* @throws {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError | RangeError} If the **eccentricity ($e$) is invalid** ($e < 0$ or $e \geq 1$).
|
|
55
61
|
*
|
|
@@ -86,15 +92,18 @@ export const solveKepler = (M, e, maxIter = 50, tolerance = 1e-9) => {
|
|
|
86
92
|
if (e < 0 || e >= 1) {
|
|
87
93
|
throw new RangeError(`Invalid eccentricity: ${e}. Must be in range [0,1).`);
|
|
88
94
|
}
|
|
95
|
+
// Kepler's equation is 2π-periodic, so solve for M in [0, 2π)
|
|
96
|
+
const Mn = norm2pi(M);
|
|
89
97
|
// **Use different solvers for high-eccentricity cases**
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
let E = e > 0.9
|
|
99
|
+
? solveKeplerHighEccentricity(Mn, e, maxIter, tolerance)
|
|
100
|
+
: solveKeplerNewtonRaphson(Mn, e, maxIter, tolerance);
|
|
101
|
+
// The iterative solvers can stop at maxIter or on a vanishing step away from
|
|
102
|
+
// the root, so verify the result and fall back to bisection if needed
|
|
103
|
+
const residual = norm2pi(E - e * Math.sin(E) - Mn);
|
|
104
|
+
if (!(Math.min(residual, TWO_PI - residual) <= tolerance)) {
|
|
105
|
+
E = solveKeplerBisection(Mn, e, maxIter, tolerance);
|
|
92
106
|
}
|
|
93
|
-
|
|
94
|
-
let E = solveKeplerNewtonRaphson(M, e, maxIter, tolerance);
|
|
95
|
-
if (isNaN(E)) {
|
|
96
|
-
E = solveKeplerBisection(M, e, maxIter, tolerance);
|
|
97
|
-
}
|
|
98
|
-
return wrapAngle(E);
|
|
107
|
+
return norm2pi(E);
|
|
99
108
|
};
|
|
100
109
|
//# sourceMappingURL=solve-kepler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve-kepler.js","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve-kepler.js","sourceRoot":"","sources":["../../../src/categories/kepler/solve-kepler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAGvD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,CAAU,EACV,CAAS,EACT,OAAO,GAAG,EAAE,EACZ,SAAS,GAAG,IAAI,EACP,EAAE;IACX,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAAC,yBAAyB,CAAC,2BAA2B,CAAC,CAAC;IAC9E,CAAC;IAED,8DAA8D;IAC9D,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAY,CAAC;IAEjC,wDAAwD;IACxD,IAAI,CAAC,GACH,CAAC,GAAG,GAAG;QACL,CAAC,CAAC,2BAA2B,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC;QACxD,CAAC,CAAC,wBAAwB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAE1D,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAEnD,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC,IAAI,SAAS,CAAC,EAAE,CAAC;QAC1D,CAAC,GAAG,oBAAoB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,OAAO,CAAC,CAAC,CAAY,CAAC;AAC/B,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interstellar-tools/equations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Curated, well-tested orbital mechanics and dynamics formulas (e.g., Newtonian gravity, vis-viva, Kepler's 3rd law, escape/circular speeds, specific energy, specific angular momentum, peri/apoapsis radii).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"interstellar",
|
|
@@ -109,9 +109,9 @@
|
|
|
109
109
|
"test:ci": "glob-bin -c \"node --import tsx --test --no-warnings --experimental-test-coverage --test-coverage-exclude='**/__tests__/helpers/**' --test-reporter=cobertura --test-reporter-destination=coverage/cobertura-coverage.xml --test-reporter=spec --test-reporter-destination=stdout\" \"**/__tests__/**/*.spec.ts\""
|
|
110
110
|
},
|
|
111
111
|
"dependencies": {
|
|
112
|
-
"@interstellar-tools/constants": "^0.
|
|
113
|
-
"@interstellar-tools/temporal": "^0.
|
|
114
|
-
"@interstellar-tools/types": "^0.
|
|
112
|
+
"@interstellar-tools/constants": "^0.56.0",
|
|
113
|
+
"@interstellar-tools/temporal": "^0.56.0",
|
|
114
|
+
"@interstellar-tools/types": "^0.56.0"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
117
|
"@types/node": "^25.9.0",
|