FA-69601 / Orbital propagation / Open access
Hyperbolic Kepler solver: Circular tangent is used on the hyperbolic anomaly · case 01
True anomaly can exceed the asymptote.
ROOT CAUSE
The true anomaly uses tan(H/2) instead of tanh(H/2).
VERIFIED REPAIR
Use tanh(H/2).
Unsuccessful approach: Using sinh(H/2) without the cosh denominator is still unbounded.
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=(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.tan(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.225024696, 2.55590711] | [1.097223034, 2.055391897, 2.55590711] | Failed |
| hyperbolic kepler solver [2.0, 1.5] | [1.61268581, 2.331318606, 2.300523983] | [1.61268581, 1.961096791, 2.300523983] | Failed |
| hyperbolic kepler solver [10.0, 2.5] | [2.296335107, 2.568938915, 1.982313173] | [2.296335107, 1.790713502, 1.982313173] | Failed |
| hyperbolic kepler solver [-3.0, 1.1] | [-2.270719072, -2.939248157, 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.851573781, 2.831748014] | [4.645312748, 2.825835675, 2.831748014] | Failed |
| hyperbolic kepler solver [-0.2, 4.0] | [-0.066601003, -0.085960351, 1.823476582] | [-0.066601003, -0.085896903, 1.823476582] | Failed |
SHA-256 / d9dc2de9aab239c03786cf919184368901a72cbe92212cfa73c7b5d9082d55ad
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.sinh(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.177900764, 2.55590711] | [1.097223034, 2.055391897, 2.55590711] | Failed |
| hyperbolic kepler solver [2.0, 1.5] | [1.61268581, 2.216243435, 2.300523983] | [1.61268581, 1.961096791, 2.300523983] | Failed |
| hyperbolic kepler solver [10.0, 2.5] | [2.296335107, 2.276337206, 1.982313173] | [2.296335107, 1.790713502, 1.982313173] | Failed |
| hyperbolic kepler solver [-3.0, 1.1] | [-2.270719072, -2.831358361, 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, 3.079790276, 2.831748014] | [4.645312748, 2.825835675, 2.831748014] | Failed |
| hyperbolic kepler solver [-0.2, 4.0] | [-0.066601003, -0.085944476, 1.823476582] | [-0.066601003, -0.085896903, 1.823476582] | Failed |
SHA-256 / c6d507ed8dab878e224a298ed12a08b8a111cab2f764f7f5ebf2d4a83035b336
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.854753+00:00.
Case digest / 1daf4bf58cd6b4d81b69880ae381e4f99e9c5ae9b9c7959e3ab01bcfd1c44130