FAILURE MAP
← Case archive

FA-69611 / Orbital propagation / Open access

Hyperbolic Kepler solver: Asymptote angle uses +1/e · case 01

The reported departure asymptote lies on the wrong side of the latus rectum.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

nu_inf is computed as acos(1/e).

VERIFIED REPAIR

Use acos(-1/e).

Unsuccessful approach: atan(sqrt(e^2-1)) is the same supplementary angle as acos(1/e).

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.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 [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('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 [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])]]
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 fixtureActualExpectedOutcome
hyperbolic kepler solver [0.5, 1.2][1.097223034, 2.055391897, 0.585685543][1.097223034, 2.055391897, 2.55590711]Failed
hyperbolic kepler solver [2.0, 1.5][1.61268581, 1.961096791, 0.841068671][1.61268581, 1.961096791, 2.300523983]Failed
hyperbolic kepler solver [10.0, 2.5][2.296335107, 1.790713502, 1.159279481][2.296335107, 1.790713502, 1.982313173]Failed
hyperbolic kepler solver [-3.0, 1.1][-2.270719072, -2.617037695, 0.429699666][-2.270719072, -2.617037695, 2.711892987]Failed
hyperbolic kepler solver [0.0, 3.0][0.0, 0.0, 1.230959417][0.0, 0.0, 1.910633236]Failed
hyperbolic kepler solver [50.0, 1.05][4.645312748, 2.825835675, 0.30984464][4.645312748, 2.825835675, 2.831748014]Failed
hyperbolic kepler solver [-0.2, 4.0][-0.066601003, -0.085896903, 1.318116072][-0.066601003, -0.085896903, 1.823476582]Failed

SHA-256 / a8357803b1b87bfe65c837c2a0bafcf26bcc9f4e055ce049ad34e7648fd414e9

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.atan(math.sqrt(e*e-1)),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 [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('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 [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])]]
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 fixtureActualExpectedOutcome
hyperbolic kepler solver [0.5, 1.2][1.097223034, 2.055391897, 0.585685543][1.097223034, 2.055391897, 2.55590711]Failed
hyperbolic kepler solver [2.0, 1.5][1.61268581, 1.961096791, 0.841068671][1.61268581, 1.961096791, 2.300523983]Failed
hyperbolic kepler solver [10.0, 2.5][2.296335107, 1.790713502, 1.159279481][2.296335107, 1.790713502, 1.982313173]Failed
hyperbolic kepler solver [-3.0, 1.1][-2.270719072, -2.617037695, 0.429699666][-2.270719072, -2.617037695, 2.711892987]Failed
hyperbolic kepler solver [0.0, 3.0][0.0, 0.0, 1.230959417][0.0, 0.0, 1.910633236]Failed
hyperbolic kepler solver [50.0, 1.05][4.645312748, 2.825835675, 0.30984464][4.645312748, 2.825835675, 2.831748014]Failed
hyperbolic kepler solver [-0.2, 4.0][-0.066601003, -0.085896903, 1.318116072][-0.066601003, -0.085896903, 1.823476582]Failed

SHA-256 / e076de9358c8ea05ae06131de74a614634920d33b3f258dd2f4c8791de6da584

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 [0.0, 3.0]', [0.0, 3.0], [0.0, 0.0, 1.910633236]), ('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 [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])]]
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 fixtureActualExpectedOutcome
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 / 3882ba5d23cea8394af7ce18307113e24e9a467019cc7d905b486c8ecb4b9d5e

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:13.111757+00:00.

Case digest / 86667ef2276b505351cf239a1858a7121e5df2520590dfffed15250c82b23466