FA-69596 / Orbital propagation / Open access
Hyperbolic Kepler solver: Hyperbolic residual has the elliptic sign convention · case 01
Outbound hyperbolic arcs are reported as inbound.
ROOT CAUSE
The residual is written H - e sinh H - M, which solves for -H.
VERIFIED REPAIR
Solve e sinh H - H = M.
Unsuccessful approach: Using e sinh H + H mixes the elliptic sign into the hyperbolic equation.
Case contract
Input [M, e] with e>1 (else None). Solve e sinh H - H = M by Newton from H=asinh(M/e) (60 iterations max, stop when step<1e-13). Return [H, nu, nu_inf] rounded to 9 with nu=2 atan(sqrt((e+1)/(e-1)) tanh(H/2)) and asymptote nu_inf=acos(-1/e).
Why this case matters
Orbit determination and mission planning chain many small conversions; one wrong branch or unit silently moves a spacecraft by kilometres.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
M,e=x
if e<=1: return None
H=math.asinh(M/e)
for _ in range(60):
d=(H-e*math.sinh(H)-M)/(1-e*math.cosh(H))
H-=d
if abs(d)<1e-13: break
nu=2*math.atan(math.sqrt((e+1)/(e-1))*math.tanh(H/2))
return [round(H,9),round(nu,9),round(math.acos(-1/e),9)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582])], [('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297])], [('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297]), ('hyperbolic kepler solver [0.8, 10.0]', [0.8, 10.0], [0.088759344, 0.097984305, 1.670963748]), ('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983])], [('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.8, 10.0]', [0.8, 10.0], [0.088759344, 0.097984305, 1.670963748]), ('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014])], [('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| hyperbolic kepler solver [0.5, 1.2] | [-1.097223034, -2.055391897, 2.55590711] | [1.097223034, 2.055391897, 2.55590711] | Failed |
| hyperbolic kepler solver [2.0, 1.5] | [-1.61268581, -1.961096791, 2.300523983] | [1.61268581, 1.961096791, 2.300523983] | Failed |
| hyperbolic kepler solver [10.0, 2.5] | [-2.296335107, -1.790713502, 1.982313173] | [2.296335107, 1.790713502, 1.982313173] | Failed |
| hyperbolic kepler solver [-3.0, 1.1] | [2.270719072, 2.617037695, 2.711892987] | [-2.270719072, -2.617037695, 2.711892987] | Failed |
| hyperbolic kepler solver [0.0, 3.0] | [0.0, 0.0, 1.910633236] | [0.0, 0.0, 1.910633236] | Passed |
| hyperbolic kepler solver [50.0, 1.05] | [-4.645312748, -2.825835675, 2.831748014] | [4.645312748, 2.825835675, 2.831748014] | Failed |
| hyperbolic kepler solver [-0.2, 4.0] | [0.066601003, 0.085896903, 1.823476582] | [-0.066601003, -0.085896903, 1.823476582] | Failed |
SHA-256 / 3cf07ae02a25fc221b989e1b3c00355f8d7b346108dcc1a66c15b8d574f25658
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
M,e=x
if e<=1: return None
H=math.asinh(M/e)
for _ in range(60):
d=(e*math.sinh(H)+H-M)/(e*math.cosh(H)+1)
H-=d
if abs(d)<1e-13: break
nu=2*math.atan(math.sqrt((e+1)/(e-1))*math.tanh(H/2))
return [round(H,9),round(nu,9),round(math.acos(-1/e),9)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582])], [('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297])], [('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297]), ('hyperbolic kepler solver [0.8, 10.0]', [0.8, 10.0], [0.088759344, 0.097984305, 1.670963748]), ('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983])], [('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.8, 10.0]', [0.8, 10.0], [0.088759344, 0.097984305, 1.670963748]), ('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014])], [('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| hyperbolic kepler solver [0.5, 1.2] | [0.226217617, 0.714993852, 2.55590711] | [1.097223034, 2.055391897, 2.55590711] | Failed |
| hyperbolic kepler solver [2.0, 1.5] | [0.755610229, 1.357714298, 2.300523983] | [1.61268581, 1.961096791, 2.300523983] | Failed |
| hyperbolic kepler solver [10.0, 2.5] | [1.89259975, 1.690487009, 1.982313173] | [2.296335107, 1.790713502, 1.982313173] | Failed |
| hyperbolic kepler solver [-3.0, 1.1] | [-1.24605993, -2.390183774, 2.711892987] | [-2.270719072, -2.617037695, 2.711892987] | Failed |
| hyperbolic kepler solver [0.0, 3.0] | [0.0, 0.0, 1.910633236] | [0.0, 0.0, 1.910633236] | Passed |
| hyperbolic kepler solver [50.0, 1.05] | [4.463014768, 2.824640259, 2.831748014] | [4.645312748, 2.825835675, 2.831748014] | Failed |
| hyperbolic kepler solver [-0.2, 4.0] | [-0.039991471, -0.051610429, 1.823476582] | [-0.066601003, -0.085896903, 1.823476582] | Failed |
SHA-256 / a2ffdd3d0e3605d2a55a217f0af4acca4da5a4107079e779c27c16b588c402c1
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
M,e=x
if e<=1: return None
H=math.asinh(M/e)
for _ in range(60):
d=(e*math.sinh(H)-H-M)/(e*math.cosh(H)-1)
H-=d
if abs(d)<1e-13: break
nu=2*math.atan(math.sqrt((e+1)/(e-1))*math.tanh(H/2))
return [round(H,9),round(nu,9),round(math.acos(-1/e),9)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582])], [('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297])], [('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297]), ('hyperbolic kepler solver [0.8, 10.0]', [0.8, 10.0], [0.088759344, 0.097984305, 1.670963748]), ('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983])], [('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.8, 10.0]', [0.8, 10.0], [0.088759344, 0.097984305, 1.670963748]), ('hyperbolic kepler solver [0.5, 1.2]', [0.5, 1.2], [1.097223034, 2.055391897, 2.55590711]), ('hyperbolic kepler solver [2.0, 1.5]', [2.0, 1.5], [1.61268581, 1.961096791, 2.300523983]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014])], [('hyperbolic kepler solver [50.0, 1.05]', [50.0, 1.05], [4.645312748, 2.825835675, 2.831748014]), ('hyperbolic kepler solver [10.0, 2.5]', [10.0, 2.5], [2.296335107, 1.790713502, 1.982313173]), ('hyperbolic kepler solver [-3.0, 1.1]', [-3.0, 1.1], [-2.270719072, -2.617037695, 2.711892987]), ('hyperbolic kepler solver [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('hyperbolic kepler solver [-0.2, 4.0]', [-0.2, 4.0], [-0.066601003, -0.085896903, 1.823476582]), ('hyperbolic kepler solver [1.0, 1.0]', [1.0, 1.0], None), ('hyperbolic kepler solver [5.0, 1.8]', [5.0, 1.8], [2.078164038, 1.937063662, 2.159827297])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| hyperbolic kepler solver [0.5, 1.2] | [1.097223034, 2.055391897, 2.55590711] | [1.097223034, 2.055391897, 2.55590711] | Passed |
| hyperbolic kepler solver [2.0, 1.5] | [1.61268581, 1.961096791, 2.300523983] | [1.61268581, 1.961096791, 2.300523983] | Passed |
| hyperbolic kepler solver [10.0, 2.5] | [2.296335107, 1.790713502, 1.982313173] | [2.296335107, 1.790713502, 1.982313173] | Passed |
| hyperbolic kepler solver [-3.0, 1.1] | [-2.270719072, -2.617037695, 2.711892987] | [-2.270719072, -2.617037695, 2.711892987] | Passed |
| hyperbolic kepler solver [0.0, 3.0] | [0.0, 0.0, 1.910633236] | [0.0, 0.0, 1.910633236] | Passed |
| hyperbolic kepler solver [50.0, 1.05] | [4.645312748, 2.825835675, 2.831748014] | [4.645312748, 2.825835675, 2.831748014] | Passed |
| hyperbolic kepler solver [-0.2, 4.0] | [-0.066601003, -0.085896903, 1.823476582] | [-0.066601003, -0.085896903, 1.823476582] | Passed |
SHA-256 / e8e240ce6014f9c36cd2de39558f1de0a5b7eadfaed83c877949fdeb1283945e
Verification & scope
A deterministic toy two-body model with stipulated constants and conventions; not flight dynamics software or a validated SGP4 implementation. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:48:12.844065+00:00.
Case digest / 89558ea2c4cab368cabf6038e490229822b89d497f47054deb19e53415e3e62d