FAILURE MAP
← Case archive

FA-69366 / Orbital propagation / Open access

True anomaly from eccentric anomaly: Cosine formula loses the descending half of the orbit · case 01

After apoapsis the true anomaly folds back instead of continuing past pi.

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

ROOT CAUSE

The anomaly is recovered with acos((cosE-e)/(1-e cosE)), which cannot distinguish E from -E.

VERIFIED REPAIR

Use the two-argument half-angle form.

Unsuccessful approach: Reflecting when E>pi without first reducing E modulo 2pi still fails for negative or multi-revolution E.

Case contract

Input [E, e] (radians, 0<=e<1, E any real). Return [nu, r/a] with nu = 2*atan2(sqrt(1+e) sin(E/2), sqrt(1-e) cos(E/2)) reduced to [0,2pi) and r/a = 1-e cos E, both rounded to 9 decimals.

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):
    E,e=x
    nu=math.acos((math.cos(E)-e)/(1-e*math.cos(E)))
    nu%=2*math.pi
    return [round(nu,9),round(1-e*math.cos(E),9)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])], [('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0])], [('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0]), ('true anomaly from eccentric anomaly [3.5, 0.95]', [3.5, 0.95], [3.199589845, 1.889633853]), ('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901])], [('true anomaly from eccentric anomaly [3.5, 0.95]', [3.5, 0.95], [3.199589845, 1.889633853]), ('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0]), ('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086])], [('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086]), ('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5])]]
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
true anomaly from eccentric anomaly [4.0, 0.3][2.493603015, 1.196093086][3.789582293, 1.196093086]Failed
true anomaly from eccentric anomaly [7.0, 0.2][0.860249203, 0.849219549][0.860249203, 0.849219549]Passed
true anomaly from eccentric anomaly [0.4, 0.1][0.440923623, 0.907893901][0.440923623, 0.907893901]Passed
true anomaly from eccentric anomaly [2.5, 0.3][2.663281246, 1.240343085][2.663281246, 1.240343085]Passed
true anomaly from eccentric anomaly [5.9, 0.7][0.865156876, 0.350765098][5.418028431, 0.350765098]Failed
true anomaly from eccentric anomaly [3.141592653589793, 0.5][3.141592654, 1.5][3.141592654, 1.5]Passed
true anomaly from eccentric anomaly [0.0, 0.9][0.0, 0.1][0.0, 0.1]Passed

SHA-256 / 0e6ffcf9343cdfcad2e58a8cb2e86a934581dbd023d3df974bd266e85adb13f7

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    E,e=x
    nu=math.acos((math.cos(E)-e)/(1-e*math.cos(E)))
    if E>math.pi: nu=2*math.pi-nu
    nu%=2*math.pi
    return [round(nu,9),round(1-e*math.cos(E),9)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])], [('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0])], [('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0]), ('true anomaly from eccentric anomaly [3.5, 0.95]', [3.5, 0.95], [3.199589845, 1.889633853]), ('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901])], [('true anomaly from eccentric anomaly [3.5, 0.95]', [3.5, 0.95], [3.199589845, 1.889633853]), ('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0]), ('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086])], [('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086]), ('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5])]]
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
true anomaly from eccentric anomaly [4.0, 0.3][3.789582293, 1.196093086][3.789582293, 1.196093086]Passed
true anomaly from eccentric anomaly [7.0, 0.2][5.422936104, 0.849219549][0.860249203, 0.849219549]Failed
true anomaly from eccentric anomaly [0.4, 0.1][0.440923623, 0.907893901][0.440923623, 0.907893901]Passed
true anomaly from eccentric anomaly [2.5, 0.3][2.663281246, 1.240343085][2.663281246, 1.240343085]Passed
true anomaly from eccentric anomaly [5.9, 0.7][5.418028431, 0.350765098][5.418028431, 0.350765098]Passed
true anomaly from eccentric anomaly [3.141592653589793, 0.5][3.141592654, 1.5][3.141592654, 1.5]Passed
true anomaly from eccentric anomaly [0.0, 0.9][0.0, 0.1][0.0, 0.1]Passed

SHA-256 / f1bc114c3c4deb01cf61931641c86b8a4849a96df887f93a19f8b096807ffdcf

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    E,e=x
    nu=2*math.atan2(math.sqrt(1+e)*math.sin(E/2),math.sqrt(1-e)*math.cos(E/2))
    nu%=2*math.pi
    return [round(nu,9),round(1-e*math.cos(E),9)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])], [('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0])], [('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0]), ('true anomaly from eccentric anomaly [3.5, 0.95]', [3.5, 0.95], [3.199589845, 1.889633853]), ('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901])], [('true anomaly from eccentric anomaly [3.5, 0.95]', [3.5, 0.95], [3.199589845, 1.889633853]), ('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('true anomaly from eccentric anomaly [1.2, 0.0]', [1.2, 0.0], [1.2, 1.0]), ('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086])], [('true anomaly from eccentric anomaly [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('true anomaly from eccentric anomaly [0.4, 0.1]', [0.4, 0.1], [0.440923623, 0.907893901]), ('true anomaly from eccentric anomaly [2.5, 0.3]', [2.5, 0.3], [2.663281246, 1.240343085]), ('true anomaly from eccentric anomaly [4.0, 0.3]', [4.0, 0.3], [3.789582293, 1.196093086]), ('true anomaly from eccentric anomaly [5.9, 0.7]', [5.9, 0.7], [5.418028431, 0.350765098]), ('true anomaly from eccentric anomaly [3.141592653589793, 0.5]', [3.141592653589793, 0.5], [3.141592654, 1.5])]]
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
true anomaly from eccentric anomaly [4.0, 0.3][3.789582293, 1.196093086][3.789582293, 1.196093086]Passed
true anomaly from eccentric anomaly [7.0, 0.2][0.860249203, 0.849219549][0.860249203, 0.849219549]Passed
true anomaly from eccentric anomaly [0.4, 0.1][0.440923623, 0.907893901][0.440923623, 0.907893901]Passed
true anomaly from eccentric anomaly [2.5, 0.3][2.663281246, 1.240343085][2.663281246, 1.240343085]Passed
true anomaly from eccentric anomaly [5.9, 0.7][5.418028431, 0.350765098][5.418028431, 0.350765098]Passed
true anomaly from eccentric anomaly [3.141592653589793, 0.5][3.141592654, 1.5][3.141592654, 1.5]Passed
true anomaly from eccentric anomaly [0.0, 0.9][0.0, 0.1][0.0, 0.1]Passed

SHA-256 / 6bf872a9d68d5f5f45a463cb4e083def7f0ee553ccd3a0bda73aae33e11f02c3

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

Case digest / 5d349839d1d25187a9d160cc10b0a1a201f7028ea435c8c5a3c8e596ca99ab6d