FA-69381 / Orbital propagation / Open access
True anomaly from eccentric anomaly: True anomaly is left in (-pi, pi] · case 01
Descending-branch anomalies are reported as negative angles.
ROOT CAUSE
The atan2 result is not reduced into [0,2pi).
VERIFIED REPAIR
Reduce modulo 2pi.
Unsuccessful approach: Adding only pi to negative values lands on the opposite side of the orbit.
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=2*math.atan2(math.sqrt(1+e)*math.sin(E/2),math.sqrt(1-e)*math.cos(E/2))
pass
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 [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]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])], [('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('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 [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 [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('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 [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 [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('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 [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 [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]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])]]
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 |
|---|---|---|---|
| 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 [4.0, 0.3] | [3.789582293, 1.196093086] | [3.789582293, 1.196093086] | 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 / 1b7fac7e85a04a05562fdcbe0f5950db510af735f08b4eeaeaff1360f356f651
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=2*math.atan2(math.sqrt(1+e)*math.sin(E/2),math.sqrt(1-e)*math.cos(E/2))
nu=nu+math.pi if nu<0 else nu
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 [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]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])], [('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('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 [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 [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('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 [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 [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('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 [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 [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]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])]]
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 |
|---|---|---|---|
| true anomaly from eccentric anomaly [7.0, 0.2] | [-2.28134345, 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 [4.0, 0.3] | [3.789582293, 1.196093086] | [3.789582293, 1.196093086] | 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 / a5f545ccc4221f0ceffbba77e307e8e2b9f0b1c4a44b3a53f1db004d0bf1291c
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 [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]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])], [('true anomaly from eccentric anomaly [-1.0, 0.4]', [-1.0, 0.4], [4.892343298, 0.783879078]), ('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 [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 [-4.0, 0.6]', [-4.0, 0.6], [2.691681202, 1.392186173]), ('true anomaly from eccentric anomaly [7.0, 0.2]', [7.0, 0.2], [0.860249203, 0.849219549]), ('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 [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 [12.0, 0.15]', [12.0, 0.15], [5.630422415, 0.873421906]), ('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 [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 [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]), ('true anomaly from eccentric anomaly [0.0, 0.9]', [0.0, 0.9], [0.0, 0.1])]]
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 |
|---|---|---|---|
| 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 [4.0, 0.3] | [3.789582293, 1.196093086] | [3.789582293, 1.196093086] | 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 / 6d128010433a89a0fbe648a2aa97b59f3b8efd766bd0ecff51a5010e6ddd39bc
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.878423+00:00.
Case digest / 12c9dd6129b4698f47c0ccc42e602a723b6ca9731f23873696cd61b4bd1a8364