FAILURE MAP
← Case archive

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.

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

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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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 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 / 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