import math
import random
from benchmarks.comparative.results import AccuracyComparison
from benchmarks.comparative.tasks.base import BenchmarkTask
R_EARTH = 6378137.0
def _geodetic_position_error(
ga: list[float], gb: list[float]
) -> tuple[float, float]:
lon_a, lat_a, alt_a = ga[0], ga[1], ga[2]
lon_b, lat_b, alt_b = gb[0], gb[1], gb[2]
dlon = ((lon_a - lon_b + 540.0) % 360.0) - 180.0
lat_rad = math.radians(lat_a)
dlon_m = math.radians(dlon) * R_EARTH * math.cos(lat_rad)
dlat_m = math.radians(lat_a - lat_b) * R_EARTH
dalt_m = alt_a - alt_b
max_abs = max(abs(dlon_m), abs(dlat_m), abs(dalt_m))
rms = math.sqrt((dlon_m * dlon_m + dlat_m * dlat_m + dalt_m * dalt_m) / 3.0)
return max_abs, rms
def _geocentric_position_error(
ga: list[float], gb: list[float]
) -> tuple[float, float]:
return _geodetic_position_error(ga, gb)
def _build_position_comparison(
task_name: str,
language_a: str,
language_b: str,
results_a: list,
results_b: list,
pair_error: callable,
) -> AccuracyComparison:
n = min(len(results_a), len(results_b))
if n == 0:
return AccuracyComparison(
task_name=task_name,
reference_language=language_a,
comparison_language=language_b,
max_abs_error=float("nan"),
max_rel_error=float("nan"),
rms_error=float("nan"),
)
abs_errors: list[float] = []
rms_errors: list[float] = []
for i in range(n):
ga = results_a[i]
gb = results_b[i]
if not isinstance(ga, (list, tuple)) or len(ga) < 3:
continue
if not isinstance(gb, (list, tuple)) or len(gb) < 3:
continue
max_abs, rms = pair_error(list(ga), list(gb))
abs_errors.append(max_abs)
rms_errors.append(rms)
if not abs_errors:
return AccuracyComparison(
task_name=task_name,
reference_language=language_a,
comparison_language=language_b,
max_abs_error=float("nan"),
max_rel_error=float("nan"),
rms_error=float("nan"),
)
overall_max = max(abs_errors)
overall_rms = math.sqrt(sum(e * e for e in rms_errors) / len(rms_errors))
rel = overall_max / 1.0
return AccuracyComparison(
task_name=task_name,
reference_language=language_a,
comparison_language=language_b,
max_abs_error=overall_max,
max_rel_error=rel,
rms_error=overall_rms,
)
class GeodeticToEcefTask(BenchmarkTask):
@property
def name(self) -> str:
return "coordinates.geodetic_to_ecef"
@property
def module(self) -> str:
return "coordinates"
@property
def description(self) -> str:
return "Convert geodetic coordinates (lon, lat, alt) to ECEF position vectors"
@property
def languages(self) -> list[str]:
return ["python", "rust", "java", "basilisk", "gmat", "nyx"]
def generate_params(self, seed: int) -> dict:
return self._gen_points(seed, 50)
def generate_accuracy_samples(self, seed: int, n: int) -> dict:
return self._gen_points(seed, n)
@staticmethod
def _gen_points(seed: int, n: int) -> dict:
rng = random.Random(seed)
points = []
for _ in range(n):
lon = rng.uniform(-180.0, 180.0)
lat = rng.uniform(-90.0, 90.0)
alt = rng.uniform(0.0, 1000e3) points.append([lon, lat, alt])
return {"points": points}
class EcefToGeodeticTask(BenchmarkTask):
@property
def name(self) -> str:
return "coordinates.ecef_to_geodetic"
@property
def module(self) -> str:
return "coordinates"
@property
def description(self) -> str:
return "Convert ECEF position vectors to geodetic coordinates (lon, lat, alt)"
@property
def languages(self) -> list[str]:
return ["python", "rust", "java", "basilisk", "gmat", "nyx"]
def generate_params(self, seed: int) -> dict:
return self._gen_points(seed, 50)
def generate_accuracy_samples(self, seed: int, n: int) -> dict:
return self._gen_points(seed, n)
@staticmethod
def _gen_points(seed: int, n: int) -> dict:
rng = random.Random(seed)
points = []
for _ in range(n):
lon = math.radians(rng.uniform(-180.0, 180.0))
lat = math.radians(rng.uniform(-90.0, 90.0))
alt = rng.uniform(0.0, 1000e3)
r = R_EARTH + alt
x = r * math.cos(lat) * math.cos(lon)
y = r * math.cos(lat) * math.sin(lon)
z = r * math.sin(lat)
points.append([x, y, z])
return {"points": points}
def compare_results(
self, results_a, results_b, language_a, language_b
) -> AccuracyComparison:
return _build_position_comparison(
self.name,
language_a,
language_b,
results_a,
results_b,
_geodetic_position_error,
)
class GeocentricToEcefTask(BenchmarkTask):
@property
def name(self) -> str:
return "coordinates.geocentric_to_ecef"
@property
def module(self) -> str:
return "coordinates"
@property
def description(self) -> str:
return (
"Convert geocentric coordinates (lon, lat, radius) to ECEF position vectors"
)
@property
def languages(self) -> list[str]:
return ["python", "rust", "java", "gmat"]
def generate_params(self, seed: int) -> dict:
return self._gen_points(seed, 50)
def generate_accuracy_samples(self, seed: int, n: int) -> dict:
return self._gen_points(seed, n)
@staticmethod
def _gen_points(seed: int, n: int) -> dict:
rng = random.Random(seed)
points = []
for _ in range(n):
lon = rng.uniform(-180.0, 180.0) lat = rng.uniform(-90.0, 90.0) altitude = rng.uniform(0.0, 1000e3) points.append([lon, lat, altitude])
return {"points": points}
class EcefToGeocentricTask(BenchmarkTask):
@property
def name(self) -> str:
return "coordinates.ecef_to_geocentric"
@property
def module(self) -> str:
return "coordinates"
@property
def description(self) -> str:
return (
"Convert ECEF position vectors to geocentric coordinates (lon, lat, radius)"
)
@property
def languages(self) -> list[str]:
return ["python", "rust", "java", "gmat"]
def generate_params(self, seed: int) -> dict:
return self._gen_points(seed, 50)
def generate_accuracy_samples(self, seed: int, n: int) -> dict:
return self._gen_points(seed, n)
@staticmethod
def _gen_points(seed: int, n: int) -> dict:
rng = random.Random(seed)
points = []
for _ in range(n):
lon = math.radians(rng.uniform(-180.0, 180.0))
lat = math.radians(rng.uniform(-90.0, 90.0))
radius = R_EARTH + rng.uniform(0.0, 1000e3)
x = radius * math.cos(lat) * math.cos(lon)
y = radius * math.cos(lat) * math.sin(lon)
z = radius * math.sin(lat)
points.append([x, y, z])
return {"points": points}
def compare_results(
self, results_a, results_b, language_a, language_b
) -> AccuracyComparison:
return _build_position_comparison(
self.name,
language_a,
language_b,
results_a,
results_b,
_geocentric_position_error,
)
class EcefToAzelTask(BenchmarkTask):
@property
def name(self) -> str:
return "coordinates.ecef_to_azel"
@property
def module(self) -> str:
return "coordinates"
@property
def description(self) -> str:
return (
"Convert station and satellite ECEF positions to azimuth, elevation, range"
)
@property
def languages(self) -> list[str]:
return ["python", "rust", "java", "nyx"]
def generate_params(self, seed: int) -> dict:
rng = random.Random(seed)
pairs = []
for _ in range(50):
sta_lon = rng.uniform(-180.0, 180.0) sta_lat = rng.uniform(-70.0, 70.0) sta_alt = rng.uniform(0.0, 3000.0)
sta_lon_rad = math.radians(sta_lon)
sta_lat_rad = math.radians(sta_lat)
f = 1.0 / 298.257223563 e2 = 2 * f - f * f
sin_lat = math.sin(sta_lat_rad)
cos_lat = math.cos(sta_lat_rad)
N = R_EARTH / math.sqrt(1 - e2 * sin_lat * sin_lat)
sta_x = (N + sta_alt) * cos_lat * math.cos(sta_lon_rad)
sta_y = (N + sta_alt) * cos_lat * math.sin(sta_lon_rad)
sta_z = (N * (1 - e2) + sta_alt) * sin_lat
sat_lon = sta_lon + rng.uniform(-10.0, 10.0)
sat_lat = sta_lat + rng.uniform(-10.0, 10.0)
sat_alt = rng.uniform(200e3, 1000e3)
sat_lon_rad = math.radians(sat_lon)
sat_lat_rad = math.radians(sat_lat)
sin_lat_s = math.sin(sat_lat_rad)
cos_lat_s = math.cos(sat_lat_rad)
N_s = R_EARTH / math.sqrt(1 - e2 * sin_lat_s * sin_lat_s)
sat_x = (N_s + sat_alt) * cos_lat_s * math.cos(sat_lon_rad)
sat_y = (N_s + sat_alt) * cos_lat_s * math.sin(sat_lon_rad)
sat_z = (N_s * (1 - e2) + sat_alt) * sin_lat_s
pairs.append(
{
"station_ecef": [sta_x, sta_y, sta_z],
"satellite_ecef": [sat_x, sat_y, sat_z],
"station_geodetic": [sta_lon, sta_lat, sta_alt],
}
)
return {"pairs": pairs}