Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -17,6 +17,11 @@
|
|
| 17 |
# - Turn speed range: 470–520 kt
|
| 18 |
# - Southern segment speed range: 430–510 kt (per segment)
|
| 19 |
# - Waypoints at ping times are driven to ping rings for those timestamps with measured BTO.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
import os
|
| 22 |
import re
|
|
@@ -127,6 +132,11 @@ BTO_TARGET_TIGHT_US = 1.0 # purely informational
|
|
| 127 |
# HARD BTO labels list (will be AUTO-FILTERED to those with measured BTO)
|
| 128 |
HARD_BTO_LABELS = ["18:40:00", "19:40:00", "20:40:00", "21:40:00", "22:40:00", "24:10:00", "24:20:00"]
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
# Search area colors (optional)
|
| 131 |
SEARCH_AREA_COLORS = {
|
| 132 |
"Ocean Infinity": "#ff3b3b",
|
|
@@ -1223,6 +1233,120 @@ def _build_south_legs_piecewise(h_south: float, speeds: list[float], minutes: li
|
|
| 1223 |
return legs
|
| 1224 |
|
| 1225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1226 |
# ================= Guided Solver =================
|
| 1227 |
def solve_guided_straight(
|
| 1228 |
method, alt_ft, vs_fpm,
|
|
@@ -1256,9 +1380,10 @@ def solve_guided_straight(
|
|
| 1256 |
heading_final_deg=float(h_south),
|
| 1257 |
)
|
| 1258 |
pts_turn, _, _ = simulate_path(RADAR_FIX_LAT, RADAR_FIX_LON, turn_legs, method)
|
| 1259 |
-
|
| 1260 |
|
| 1261 |
-
# Fit south segments sequentially (per segment
|
|
|
|
| 1262 |
south_speeds = []
|
| 1263 |
south_errs = []
|
| 1264 |
for mins, end_lbl in zip(GUIDED_SOUTH_SEG_MIN, GUIDED_SOUTH_SEG_LABELS_END):
|
|
@@ -1281,10 +1406,24 @@ def solve_guided_straight(
|
|
| 1281 |
south_errs.append(float(err_seg))
|
| 1282 |
lat_cur, lon_cur = float(lat1), float(lon1)
|
| 1283 |
|
| 1284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1285 |
full_legs = clip_legs_to_end_dt(turn_legs + south_legs, start_dt, end_dt)
|
| 1286 |
|
| 1287 |
-
# Compute BTO errors at enforced timestamps
|
| 1288 |
bto_errs = []
|
| 1289 |
for dt_k in enforced_dts:
|
| 1290 |
meas = _lookup_measured_bto(dt_k)
|
|
@@ -1299,27 +1438,29 @@ def solve_guided_straight(
|
|
| 1299 |
if not bto_errs:
|
| 1300 |
return None
|
| 1301 |
|
| 1302 |
-
|
| 1303 |
-
|
| 1304 |
-
|
|
|
|
| 1305 |
|
| 1306 |
details = {
|
| 1307 |
"h_south": float(h_south),
|
| 1308 |
"v_turn": float(v_turn),
|
| 1309 |
-
"south_speeds":
|
| 1310 |
-
"south_errs_us": south_errs,
|
| 1311 |
"turn_info": turn_info,
|
| 1312 |
"enforced_labels": enforced_labels,
|
| 1313 |
-
"max_abs_err_us": max_abs,
|
| 1314 |
-
"sum_abs_err_us": sum_abs,
|
| 1315 |
-
"rmse_bto_us": rmse,
|
|
|
|
| 1316 |
}
|
| 1317 |
return (max_abs, sum_abs, rmse), full_legs, details
|
| 1318 |
|
| 1319 |
def best_for_heading(h_south: float):
|
| 1320 |
h_south = float(h_south)
|
| 1321 |
|
| 1322 |
-
# Coarse scan turn speed
|
| 1323 |
coarse_turns = list(np.arange(TURN_SPEED_MIN_KT, TURN_SPEED_MAX_KT + 1e-9, 5.0))
|
| 1324 |
best_key = None
|
| 1325 |
best_legs = None
|
|
@@ -1346,7 +1487,6 @@ def solve_guided_straight(
|
|
| 1346 |
if out2 is None:
|
| 1347 |
return 1e9
|
| 1348 |
(max_abs, sum_abs, _rmse), _legs, _det = out2
|
| 1349 |
-
# primary max abs, secondary sum abs
|
| 1350 |
return float(max_abs) + 1e-3 * float(sum_abs)
|
| 1351 |
|
| 1352 |
v_best, _ = _golden_min_abs(obj, v_lo, v_hi, iters=22)
|
|
@@ -1387,21 +1527,24 @@ def solve_guided_straight(
|
|
| 1387 |
|
| 1388 |
# Reporting
|
| 1389 |
south_speeds = global_det["south_speeds"]
|
| 1390 |
-
|
|
|
|
| 1391 |
|
| 1392 |
report_lines = [
|
| 1393 |
"**Guided mode: Straight + computed turn (18:22→18:40), clipped to 00:20**",
|
| 1394 |
-
"- Solver mode: **BTO-first ring-fit (fast)**",
|
| 1395 |
f"- Best southern heading: **{global_det['h_south']:.2f}°**",
|
| 1396 |
f"- Turn speed range: {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f} kt | Best: **{global_det['v_turn']:.2f} kt**",
|
| 1397 |
"",
|
|
|
|
|
|
|
| 1398 |
"**Southern per-segment speeds (each constrained to 430–510 kt):**",
|
| 1399 |
-
f"- 18:40→19:40: **{south_speeds[0]:.2f} kt** (
|
| 1400 |
-
f"- 19:40→20:40: **{south_speeds[1]:.2f} kt** (
|
| 1401 |
-
f"- 20:40→21:40: **{south_speeds[2]:.2f} kt** (
|
| 1402 |
-
f"- 21:40→22:40: **{south_speeds[3]:.2f} kt** (
|
| 1403 |
-
f"- 22:40→00:10: **{south_speeds[4]:.2f} kt** (
|
| 1404 |
-
f"- 00:10→00:20: **{south_speeds[5]:.2f} kt** (
|
| 1405 |
"",
|
| 1406 |
f"- Enforced BTO timestamps (measured available): **{', '.join(global_det['enforced_labels'])}**",
|
| 1407 |
f"- Max |BTO error| at enforced times: **{global_det['max_abs_err_us']:.2f} µs**",
|
|
@@ -1507,7 +1650,8 @@ with gr.Blocks() as demo:
|
|
| 1507 |
f"- **Final resting point:** **00:20:00Z** (trajectory clipped exactly at this time)\n\n"
|
| 1508 |
f"**Turn speed bounds:** {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f} kt\n"
|
| 1509 |
f"**Southern speed bounds:** {SOUTH_SPEED_MIN_KT:.0f}–{SOUTH_SPEED_MAX_KT:.0f} kt (per segment)\n"
|
| 1510 |
-
f"**Ring-fit goal:** match BTO at ping times where measured BTO exists (auto-detected from satLoc)."
|
|
|
|
| 1511 |
)
|
| 1512 |
|
| 1513 |
with gr.Row():
|
|
@@ -1530,6 +1674,7 @@ with gr.Blocks() as demo:
|
|
| 1530 |
gr.Markdown(
|
| 1531 |
"- Guided mode has **no speed inputs**.\n"
|
| 1532 |
"- Solver uses **fast BTO-first ring-fit** (turn speed + per-segment southern speeds).\n"
|
|
|
|
| 1533 |
"- If 18:40 has no measured BTO in your satLoc, it is **automatically skipped** for enforcement."
|
| 1534 |
)
|
| 1535 |
|
|
|
|
| 17 |
# - Turn speed range: 470–520 kt
|
| 18 |
# - Southern segment speed range: 430–510 kt (per segment)
|
| 19 |
# - Waypoints at ping times are driven to ping rings for those timestamps with measured BTO.
|
| 20 |
+
#
|
| 21 |
+
# NEW (per your request):
|
| 22 |
+
# - Adds a fast "global polish" step over the 6 southern segment speeds ONLY
|
| 23 |
+
# (coordinate descent), to reduce max |BTO error| at enforced endpoints.
|
| 24 |
+
# This helps when greedy segment-fit hits the speed floor (430 kt) but still leaves residual error.
|
| 25 |
|
| 26 |
import os
|
| 27 |
import re
|
|
|
|
| 132 |
# HARD BTO labels list (will be AUTO-FILTERED to those with measured BTO)
|
| 133 |
HARD_BTO_LABELS = ["18:40:00", "19:40:00", "20:40:00", "21:40:00", "22:40:00", "24:10:00", "24:20:00"]
|
| 134 |
|
| 135 |
+
# ===== Global polish (southern speeds only) =====
|
| 136 |
+
POLISH_PASSES = 5 # small number keeps it fast
|
| 137 |
+
POLISH_WINDOW_KT = 6.0 # explore +/- window around current speed
|
| 138 |
+
POLISH_STEP_KT = 1.0 # 1 kt grid within window (fast + robust)
|
| 139 |
+
|
| 140 |
# Search area colors (optional)
|
| 141 |
SEARCH_AREA_COLORS = {
|
| 142 |
"Ocean Infinity": "#ff3b3b",
|
|
|
|
| 1233 |
return legs
|
| 1234 |
|
| 1235 |
|
| 1236 |
+
def _simulate_south_endpoints(
|
| 1237 |
+
method: str,
|
| 1238 |
+
lat0: float,
|
| 1239 |
+
lon0: float,
|
| 1240 |
+
hdg: float,
|
| 1241 |
+
speeds: list[float],
|
| 1242 |
+
minutes: list[float],
|
| 1243 |
+
):
|
| 1244 |
+
lat, lon = float(lat0), float(lon0)
|
| 1245 |
+
endpoints = []
|
| 1246 |
+
for v, m in zip(speeds, minutes):
|
| 1247 |
+
lat, lon = _simulate_one_leg_end(method, lat, lon, float(hdg), float(v), float(m))
|
| 1248 |
+
endpoints.append((float(lat), float(lon)))
|
| 1249 |
+
return endpoints
|
| 1250 |
+
|
| 1251 |
+
|
| 1252 |
+
def _bto_errors_at_south_endpoints(
|
| 1253 |
+
method: str,
|
| 1254 |
+
endpoints: list[tuple[float, float]],
|
| 1255 |
+
alt_ft: float,
|
| 1256 |
+
vs_fpm: float,
|
| 1257 |
+
start_dt: datetime,
|
| 1258 |
+
descent_start_dt: datetime | None,
|
| 1259 |
+
):
|
| 1260 |
+
errs = []
|
| 1261 |
+
labels_used = []
|
| 1262 |
+
for (lat, lon), end_lbl in zip(endpoints, GUIDED_SOUTH_SEG_LABELS_END):
|
| 1263 |
+
dt_end = _dt_from_label(end_lbl)
|
| 1264 |
+
meas = _lookup_measured_bto(dt_end)
|
| 1265 |
+
if meas is None:
|
| 1266 |
+
continue
|
| 1267 |
+
bto = _bto_calc(method, lat, lon, dt_end, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 1268 |
+
errs.append(float(bto - meas))
|
| 1269 |
+
labels_used.append(end_lbl)
|
| 1270 |
+
return labels_used, errs
|
| 1271 |
+
|
| 1272 |
+
|
| 1273 |
+
def _bto_obj_from_errs(errs: list[float]):
|
| 1274 |
+
if not errs:
|
| 1275 |
+
return None
|
| 1276 |
+
arr = np.array(errs, dtype=float)
|
| 1277 |
+
max_abs = float(np.max(np.abs(arr)))
|
| 1278 |
+
sum_abs = float(np.sum(np.abs(arr)))
|
| 1279 |
+
rmse = float(np.sqrt(np.mean(arr * arr)))
|
| 1280 |
+
# primary max abs, secondary sum abs
|
| 1281 |
+
score = float(max_abs) + 1e-3 * float(sum_abs)
|
| 1282 |
+
return score, max_abs, sum_abs, rmse
|
| 1283 |
+
|
| 1284 |
+
|
| 1285 |
+
def _polish_south_speeds(
|
| 1286 |
+
method: str,
|
| 1287 |
+
lat0: float,
|
| 1288 |
+
lon0: float,
|
| 1289 |
+
hdg: float,
|
| 1290 |
+
speeds_init: list[float],
|
| 1291 |
+
alt_ft: float,
|
| 1292 |
+
vs_fpm: float,
|
| 1293 |
+
start_dt: datetime,
|
| 1294 |
+
descent_start_dt: datetime | None,
|
| 1295 |
+
):
|
| 1296 |
+
speeds = [float(v) for v in speeds_init]
|
| 1297 |
+
best_endpoints = _simulate_south_endpoints(method, lat0, lon0, hdg, speeds, GUIDED_SOUTH_SEG_MIN)
|
| 1298 |
+
_lbls, errs = _bto_errors_at_south_endpoints(method, best_endpoints, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 1299 |
+
obj0 = _bto_obj_from_errs(errs)
|
| 1300 |
+
if obj0 is None:
|
| 1301 |
+
return speeds, errs, {"polish_passes": 0, "improved": False}
|
| 1302 |
+
|
| 1303 |
+
best_score = obj0[0]
|
| 1304 |
+
improved_any = False
|
| 1305 |
+
|
| 1306 |
+
for _pass in range(int(POLISH_PASSES)):
|
| 1307 |
+
improved_this_pass = False
|
| 1308 |
+
|
| 1309 |
+
for i in range(len(speeds)):
|
| 1310 |
+
v0 = float(speeds[i])
|
| 1311 |
+
v_lo = _clamp(v0 - POLISH_WINDOW_KT, SOUTH_SPEED_MIN_KT, SOUTH_SPEED_MAX_KT)
|
| 1312 |
+
v_hi = _clamp(v0 + POLISH_WINDOW_KT, SOUTH_SPEED_MIN_KT, SOUTH_SPEED_MAX_KT)
|
| 1313 |
+
|
| 1314 |
+
cand_vs = _frange(v_lo, v_hi, POLISH_STEP_KT)
|
| 1315 |
+
if not cand_vs:
|
| 1316 |
+
continue
|
| 1317 |
+
|
| 1318 |
+
local_best_v = v0
|
| 1319 |
+
local_best_score = best_score
|
| 1320 |
+
|
| 1321 |
+
# try each candidate on this coordinate
|
| 1322 |
+
for v_try in cand_vs:
|
| 1323 |
+
tmp = speeds[:]
|
| 1324 |
+
tmp[i] = float(v_try)
|
| 1325 |
+
endpoints = _simulate_south_endpoints(method, lat0, lon0, hdg, tmp, GUIDED_SOUTH_SEG_MIN)
|
| 1326 |
+
_lbls2, errs2 = _bto_errors_at_south_endpoints(method, endpoints, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 1327 |
+
obj2 = _bto_obj_from_errs(errs2)
|
| 1328 |
+
if obj2 is None:
|
| 1329 |
+
continue
|
| 1330 |
+
score2 = obj2[0]
|
| 1331 |
+
if score2 + 1e-12 < local_best_score:
|
| 1332 |
+
local_best_score = score2
|
| 1333 |
+
local_best_v = float(v_try)
|
| 1334 |
+
|
| 1335 |
+
if local_best_v != v0:
|
| 1336 |
+
speeds[i] = local_best_v
|
| 1337 |
+
best_score = local_best_score
|
| 1338 |
+
improved_this_pass = True
|
| 1339 |
+
improved_any = True
|
| 1340 |
+
|
| 1341 |
+
if not improved_this_pass:
|
| 1342 |
+
break
|
| 1343 |
+
|
| 1344 |
+
# final errors for reporting
|
| 1345 |
+
final_endpoints = _simulate_south_endpoints(method, lat0, lon0, hdg, speeds, GUIDED_SOUTH_SEG_MIN)
|
| 1346 |
+
_lbls3, final_errs = _bto_errors_at_south_endpoints(method, final_endpoints, alt_ft, vs_fpm, start_dt, descent_start_dt)
|
| 1347 |
+
return speeds, final_errs, {"polish_passes": int(POLISH_PASSES), "improved": bool(improved_any)}
|
| 1348 |
+
|
| 1349 |
+
|
| 1350 |
# ================= Guided Solver =================
|
| 1351 |
def solve_guided_straight(
|
| 1352 |
method, alt_ft, vs_fpm,
|
|
|
|
| 1380 |
heading_final_deg=float(h_south),
|
| 1381 |
)
|
| 1382 |
pts_turn, _, _ = simulate_path(RADAR_FIX_LAT, RADAR_FIX_LON, turn_legs, method)
|
| 1383 |
+
lat_turn_end, lon_turn_end = pts_turn[-1]
|
| 1384 |
|
| 1385 |
+
# Fit south segments sequentially (greedy per segment)
|
| 1386 |
+
lat_cur, lon_cur = float(lat_turn_end), float(lon_turn_end)
|
| 1387 |
south_speeds = []
|
| 1388 |
south_errs = []
|
| 1389 |
for mins, end_lbl in zip(GUIDED_SOUTH_SEG_MIN, GUIDED_SOUTH_SEG_LABELS_END):
|
|
|
|
| 1406 |
south_errs.append(float(err_seg))
|
| 1407 |
lat_cur, lon_cur = float(lat1), float(lon1)
|
| 1408 |
|
| 1409 |
+
# ---- NEW: global polish on southern segment speeds only ----
|
| 1410 |
+
south_speeds_pol, enforced_errs_pol, polish_info = _polish_south_speeds(
|
| 1411 |
+
method=method,
|
| 1412 |
+
lat0=float(lat_turn_end),
|
| 1413 |
+
lon0=float(lon_turn_end),
|
| 1414 |
+
hdg=float(h_south),
|
| 1415 |
+
speeds_init=south_speeds,
|
| 1416 |
+
alt_ft=alt_ft,
|
| 1417 |
+
vs_fpm=vs_fpm,
|
| 1418 |
+
start_dt=start_dt,
|
| 1419 |
+
descent_start_dt=descent_start_dt,
|
| 1420 |
+
)
|
| 1421 |
+
|
| 1422 |
+
# Build full legs with polished speeds
|
| 1423 |
+
south_legs = _build_south_legs_piecewise(float(h_south), south_speeds_pol, GUIDED_SOUTH_SEG_MIN)
|
| 1424 |
full_legs = clip_legs_to_end_dt(turn_legs + south_legs, start_dt, end_dt)
|
| 1425 |
|
| 1426 |
+
# Compute BTO errors at enforced timestamps (from full legs for safety)
|
| 1427 |
bto_errs = []
|
| 1428 |
for dt_k in enforced_dts:
|
| 1429 |
meas = _lookup_measured_bto(dt_k)
|
|
|
|
| 1438 |
if not bto_errs:
|
| 1439 |
return None
|
| 1440 |
|
| 1441 |
+
obj = _bto_obj_from_errs(bto_errs)
|
| 1442 |
+
if obj is None:
|
| 1443 |
+
return None
|
| 1444 |
+
_score, max_abs, sum_abs, rmse = obj
|
| 1445 |
|
| 1446 |
details = {
|
| 1447 |
"h_south": float(h_south),
|
| 1448 |
"v_turn": float(v_turn),
|
| 1449 |
+
"south_speeds": [float(v) for v in south_speeds_pol],
|
| 1450 |
+
"south_errs_us": south_errs, # greedy segment-end err (may include nan if missing meas)
|
| 1451 |
"turn_info": turn_info,
|
| 1452 |
"enforced_labels": enforced_labels,
|
| 1453 |
+
"max_abs_err_us": float(max_abs),
|
| 1454 |
+
"sum_abs_err_us": float(sum_abs),
|
| 1455 |
+
"rmse_bto_us": float(rmse),
|
| 1456 |
+
"polish": polish_info,
|
| 1457 |
}
|
| 1458 |
return (max_abs, sum_abs, rmse), full_legs, details
|
| 1459 |
|
| 1460 |
def best_for_heading(h_south: float):
|
| 1461 |
h_south = float(h_south)
|
| 1462 |
|
| 1463 |
+
# Coarse scan turn speed
|
| 1464 |
coarse_turns = list(np.arange(TURN_SPEED_MIN_KT, TURN_SPEED_MAX_KT + 1e-9, 5.0))
|
| 1465 |
best_key = None
|
| 1466 |
best_legs = None
|
|
|
|
| 1487 |
if out2 is None:
|
| 1488 |
return 1e9
|
| 1489 |
(max_abs, sum_abs, _rmse), _legs, _det = out2
|
|
|
|
| 1490 |
return float(max_abs) + 1e-3 * float(sum_abs)
|
| 1491 |
|
| 1492 |
v_best, _ = _golden_min_abs(obj, v_lo, v_hi, iters=22)
|
|
|
|
| 1527 |
|
| 1528 |
# Reporting
|
| 1529 |
south_speeds = global_det["south_speeds"]
|
| 1530 |
+
greedy_seg_end_errs = global_det["south_errs_us"]
|
| 1531 |
+
polish_info = global_det.get("polish", {}) or {}
|
| 1532 |
|
| 1533 |
report_lines = [
|
| 1534 |
"**Guided mode: Straight + computed turn (18:22→18:40), clipped to 00:20**",
|
| 1535 |
+
"- Solver mode: **BTO-first ring-fit (fast) + global polish (southern speeds)**",
|
| 1536 |
f"- Best southern heading: **{global_det['h_south']:.2f}°**",
|
| 1537 |
f"- Turn speed range: {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f} kt | Best: **{global_det['v_turn']:.2f} kt**",
|
| 1538 |
"",
|
| 1539 |
+
f"**Polish:** passes={POLISH_PASSES}, window=±{POLISH_WINDOW_KT:.0f} kt, step={POLISH_STEP_KT:.0f} kt | improved={polish_info.get('improved', False)}",
|
| 1540 |
+
"",
|
| 1541 |
"**Southern per-segment speeds (each constrained to 430–510 kt):**",
|
| 1542 |
+
f"- 18:40→19:40: **{south_speeds[0]:.2f} kt** (greedy end err {greedy_seg_end_errs[0]:+.2f} µs)",
|
| 1543 |
+
f"- 19:40→20:40: **{south_speeds[1]:.2f} kt** (greedy end err {greedy_seg_end_errs[1]:+.2f} µs)",
|
| 1544 |
+
f"- 20:40→21:40: **{south_speeds[2]:.2f} kt** (greedy end err {greedy_seg_end_errs[2]:+.2f} µs)",
|
| 1545 |
+
f"- 21:40→22:40: **{south_speeds[3]:.2f} kt** (greedy end err {greedy_seg_end_errs[3]:+.2f} µs)",
|
| 1546 |
+
f"- 22:40→00:10: **{south_speeds[4]:.2f} kt** (greedy end err {greedy_seg_end_errs[4]:+.2f} µs)",
|
| 1547 |
+
f"- 00:10→00:20: **{south_speeds[5]:.2f} kt** (greedy end err {greedy_seg_end_errs[5]:+.2f} µs)",
|
| 1548 |
"",
|
| 1549 |
f"- Enforced BTO timestamps (measured available): **{', '.join(global_det['enforced_labels'])}**",
|
| 1550 |
f"- Max |BTO error| at enforced times: **{global_det['max_abs_err_us']:.2f} µs**",
|
|
|
|
| 1650 |
f"- **Final resting point:** **00:20:00Z** (trajectory clipped exactly at this time)\n\n"
|
| 1651 |
f"**Turn speed bounds:** {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f} kt\n"
|
| 1652 |
f"**Southern speed bounds:** {SOUTH_SPEED_MIN_KT:.0f}–{SOUTH_SPEED_MAX_KT:.0f} kt (per segment)\n"
|
| 1653 |
+
f"**Ring-fit goal:** match BTO at ping times where measured BTO exists (auto-detected from satLoc).\n"
|
| 1654 |
+
f"**Global polish:** {POLISH_PASSES} passes, ±{POLISH_WINDOW_KT:.0f} kt window, {POLISH_STEP_KT:.0f} kt step (southern speeds only)."
|
| 1655 |
)
|
| 1656 |
|
| 1657 |
with gr.Row():
|
|
|
|
| 1674 |
gr.Markdown(
|
| 1675 |
"- Guided mode has **no speed inputs**.\n"
|
| 1676 |
"- Solver uses **fast BTO-first ring-fit** (turn speed + per-segment southern speeds).\n"
|
| 1677 |
+
"- Then runs a **global polish** step to reduce max |BTO error| across enforced endpoints.\n"
|
| 1678 |
"- If 18:40 has no measured BTO in your satLoc, it is **automatically skipped** for enforcement."
|
| 1679 |
)
|
| 1680 |
|