Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
| 4 |
# Guided scenarios (ACTIVE):
|
| 5 |
# A) Straight heading
|
| 6 |
# B) Variable heading & speed (beam search, BTO-first)
|
|
|
|
| 7 |
#
|
| 8 |
# Key behavior:
|
| 9 |
# - Last radar spot FIXED: 18:22:00Z @ 6.578N, 96.341E
|
|
@@ -21,6 +22,7 @@
|
|
| 21 |
# PERFORMANCE:
|
| 22 |
# - Straight heading mode keeps Top-K polish.
|
| 23 |
# - Variable heading & speed uses beam search and skips polish by default (much faster).
|
|
|
|
| 24 |
|
| 25 |
import os
|
| 26 |
import re
|
|
@@ -138,6 +140,12 @@ VAR_HEADING_DELTA_SET = [-6, -3, 0, 3, 6] # candidate changes per segment (deg)
|
|
| 138 |
VAR_BEAM_WIDTH = 12 # keep top-N partial paths each segment
|
| 139 |
VAR_SMOOTH_PENALTY_US_PER_DEG = 1.5 # small penalty to avoid wiggles (still BTO-first)
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
# Search area colors (optional)
|
| 142 |
SEARCH_AREA_COLORS = {
|
| 143 |
"Ocean Infinity": "#ff3b3b",
|
|
@@ -1570,7 +1578,17 @@ def solve_guided_straight(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1570 |
|
| 1571 |
|
| 1572 |
# ================= Guided Solver: Variable heading & speed (FAST beam search) =================
|
| 1573 |
-
def solve_guided_variable(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1574 |
start_dt = _guided_start_dt()
|
| 1575 |
end_dt = _dt_from_label(GUIDED_FINAL_LABEL)
|
| 1576 |
|
|
@@ -1589,12 +1607,8 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1589 |
turn_speeds = list(np.arange(TURN_SPEED_MIN_KT, TURN_SPEED_MAX_KT + 1e-9, VAR_TURN_SPEED_STEP))
|
| 1590 |
|
| 1591 |
# initial (first south heading) candidates (coarse)
|
| 1592 |
-
first_headings = _frange(
|
| 1593 |
|
| 1594 |
-
# Beam state:
|
| 1595 |
-
# {
|
| 1596 |
-
# lat, lon, headings[], speeds[], v_turn, max_abs, sum_abs, smooth_sum
|
| 1597 |
-
# }
|
| 1598 |
best_global = None
|
| 1599 |
best_global_legs = None
|
| 1600 |
best_global_info = None
|
|
@@ -1615,7 +1629,6 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1615 |
pts_turn, _, _ = simulate_path(RADAR_FIX_LAT, RADAR_FIX_LON, turn_legs, method)
|
| 1616 |
lat0, lon0 = float(pts_turn[-1][0]), float(pts_turn[-1][1])
|
| 1617 |
|
| 1618 |
-
# initialize beam after turn
|
| 1619 |
beam = [{
|
| 1620 |
"lat": lat0,
|
| 1621 |
"lon": lon0,
|
|
@@ -1631,7 +1644,6 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1631 |
"seg_end_errs": [],
|
| 1632 |
}]
|
| 1633 |
|
| 1634 |
-
# walk segments with beam search
|
| 1635 |
for mins, end_lbl in zip(GUIDED_SOUTH_SEG_MIN, GUIDED_SOUTH_SEG_LABELS_END):
|
| 1636 |
dt_end = _dt_from_label(end_lbl)
|
| 1637 |
meas = _lookup_measured_bto(dt_end)
|
|
@@ -1639,13 +1651,12 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1639 |
|
| 1640 |
for st in beam:
|
| 1641 |
h_prev = float(st["h_prev"])
|
| 1642 |
-
|
| 1643 |
cand_h = []
|
| 1644 |
-
for d in
|
| 1645 |
h_try = h_prev + float(d)
|
| 1646 |
-
h_try = _clamp(h_try,
|
| 1647 |
cand_h.append(h_try)
|
| 1648 |
-
# unique small set
|
| 1649 |
cand_h = sorted(list({round(x, 6): x for x in cand_h}.values()))
|
| 1650 |
|
| 1651 |
for h_try in cand_h:
|
|
@@ -1664,7 +1675,6 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1664 |
vmax=SOUTH_SPEED_MAX_KT,
|
| 1665 |
)
|
| 1666 |
|
| 1667 |
-
# BTO-first objective: update errors only if measured exists
|
| 1668 |
abs_err = 0.0 if meas is None or (err_seg != err_seg) else abs(float(err_seg))
|
| 1669 |
smooth_pen = abs(float(h_try) - float(h_prev)) * VAR_SMOOTH_PENALTY_US_PER_DEG
|
| 1670 |
|
|
@@ -1687,7 +1697,6 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1687 |
"seg_end_errs": st["seg_end_errs"] + [float(err_seg)],
|
| 1688 |
})
|
| 1689 |
|
| 1690 |
-
# prune beam
|
| 1691 |
if not next_beam:
|
| 1692 |
beam = []
|
| 1693 |
break
|
|
@@ -1698,7 +1707,6 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1698 |
if not beam:
|
| 1699 |
continue
|
| 1700 |
|
| 1701 |
-
# Evaluate final beam states using enforced timestamps (full recompute for safety)
|
| 1702 |
for st in beam:
|
| 1703 |
south_legs = _build_south_legs_variable(st["headings"], st["speeds"], GUIDED_SOUTH_SEG_MIN)
|
| 1704 |
full_legs = clip_legs_to_end_dt(st["turn_legs"] + south_legs, start_dt, end_dt)
|
|
@@ -1723,20 +1731,25 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1723 |
"max_abs_err_us": float(max_abs),
|
| 1724 |
"sum_abs_err_us": float(sum_abs),
|
| 1725 |
"rmse_bto_us": float(rmse),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1726 |
}
|
| 1727 |
|
| 1728 |
if best_global_legs is None:
|
| 1729 |
-
return None, "Guided solve:
|
| 1730 |
|
| 1731 |
hs = best_global_info["headings"]
|
| 1732 |
vs = best_global_info["speeds"]
|
| 1733 |
es = best_global_info["seg_end_errs"]
|
| 1734 |
|
| 1735 |
report_lines = [
|
| 1736 |
-
"**Guided mode:
|
| 1737 |
"- Solver mode: **BTO-first ring-fit (beam search)**",
|
|
|
|
| 1738 |
f"- Turn speed: **{best_global_info['v_turn']:.2f} kt** (range {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f})",
|
| 1739 |
-
f"- Beam width: **{VAR_BEAM_WIDTH}** | Heading deltas: **{
|
| 1740 |
"",
|
| 1741 |
"**Per-segment heading & speed:**",
|
| 1742 |
f"- 18:40→19:40: **{hs[0]:.2f}°**, **{vs[0]:.2f} kt** (end err {es[0]:+.2f} µs)",
|
|
@@ -1755,6 +1768,34 @@ def solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice):
|
|
| 1755 |
return best_global_legs, "\n".join(report_lines), start_dt
|
| 1756 |
|
| 1757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1758 |
# ================= RUNNERS =================
|
| 1759 |
def _run_common(start_lat, start_lon, legs, method, start_dt, alt_ft, vs_fpm, descent_start_choice):
|
| 1760 |
if start_dt == RADAR_FIX_TIME_UTC:
|
|
@@ -1797,8 +1838,10 @@ def run_sim_manual(start_lat, start_lon, legs_df, method, start_time_str, alt_ft
|
|
| 1797 |
def run_guided(guided_mode, start_lat, start_lon, method, alt_ft, vs_fpm, descent_start_choice):
|
| 1798 |
if guided_mode == "Straight heading":
|
| 1799 |
legs, report_md, start_dt = solve_guided_straight(method, alt_ft, vs_fpm, descent_start_choice)
|
|
|
|
|
|
|
| 1800 |
else:
|
| 1801 |
-
legs, report_md, start_dt =
|
| 1802 |
|
| 1803 |
if legs is None:
|
| 1804 |
fig = go.Figure()
|
|
@@ -1846,11 +1889,12 @@ with gr.Blocks() as demo:
|
|
| 1846 |
f"**Turn speed bounds:** {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f} kt\n"
|
| 1847 |
f"**Southern speed bounds:** {SOUTH_SPEED_MIN_KT:.0f}–{SOUTH_SPEED_MAX_KT:.0f} kt\n"
|
| 1848 |
f"**Straight mode:** Top-K polish (top-{TOPK_POLISH})\n"
|
| 1849 |
-
f"**Variable mode:** beam search (width {VAR_BEAM_WIDTH})
|
|
|
|
| 1850 |
)
|
| 1851 |
|
| 1852 |
guided_mode = gr.Dropdown(
|
| 1853 |
-
["Straight heading", "Variable heading & speed"],
|
| 1854 |
value="Straight heading",
|
| 1855 |
label="Guided scenario",
|
| 1856 |
)
|
|
|
|
| 4 |
# Guided scenarios (ACTIVE):
|
| 5 |
# A) Straight heading
|
| 6 |
# B) Variable heading & speed (beam search, BTO-first)
|
| 7 |
+
# C) Variable heading & speed (SE) (beam search, BTO-first, heading constrained <180)
|
| 8 |
#
|
| 9 |
# Key behavior:
|
| 10 |
# - Last radar spot FIXED: 18:22:00Z @ 6.578N, 96.341E
|
|
|
|
| 22 |
# PERFORMANCE:
|
| 23 |
# - Straight heading mode keeps Top-K polish.
|
| 24 |
# - Variable heading & speed uses beam search and skips polish by default (much faster).
|
| 25 |
+
# - SE mode is the same beam search but constrains headings to 150–179° (towards SE-ish, avoids 190° SW).
|
| 26 |
|
| 27 |
import os
|
| 28 |
import re
|
|
|
|
| 140 |
VAR_BEAM_WIDTH = 12 # keep top-N partial paths each segment
|
| 141 |
VAR_SMOOTH_PENALTY_US_PER_DEG = 1.5 # small penalty to avoid wiggles (still BTO-first)
|
| 142 |
|
| 143 |
+
# ===== SE constrained mode knobs (avoid SW headings like 190°) =====
|
| 144 |
+
VAR_SE_HEADING_MIN = 150.0
|
| 145 |
+
VAR_SE_HEADING_MAX = 179.0
|
| 146 |
+
VAR_SE_FIRST_HEADING_STEP = 4.0
|
| 147 |
+
VAR_SE_HEADING_DELTA_SET = [-6, -3, 0, 3, 6] # same style, but clamped into 150–179
|
| 148 |
+
|
| 149 |
# Search area colors (optional)
|
| 150 |
SEARCH_AREA_COLORS = {
|
| 151 |
"Ocean Infinity": "#ff3b3b",
|
|
|
|
| 1578 |
|
| 1579 |
|
| 1580 |
# ================= Guided Solver: Variable heading & speed (FAST beam search) =================
|
| 1581 |
+
def solve_guided_variable(
|
| 1582 |
+
method,
|
| 1583 |
+
alt_ft,
|
| 1584 |
+
vs_fpm,
|
| 1585 |
+
descent_start_choice,
|
| 1586 |
+
heading_min: float,
|
| 1587 |
+
heading_max: float,
|
| 1588 |
+
first_heading_step: float,
|
| 1589 |
+
heading_delta_set: list[float],
|
| 1590 |
+
label_prefix: str,
|
| 1591 |
+
):
|
| 1592 |
start_dt = _guided_start_dt()
|
| 1593 |
end_dt = _dt_from_label(GUIDED_FINAL_LABEL)
|
| 1594 |
|
|
|
|
| 1607 |
turn_speeds = list(np.arange(TURN_SPEED_MIN_KT, TURN_SPEED_MAX_KT + 1e-9, VAR_TURN_SPEED_STEP))
|
| 1608 |
|
| 1609 |
# initial (first south heading) candidates (coarse)
|
| 1610 |
+
first_headings = _frange(float(heading_min), float(heading_max), float(first_heading_step))
|
| 1611 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1612 |
best_global = None
|
| 1613 |
best_global_legs = None
|
| 1614 |
best_global_info = None
|
|
|
|
| 1629 |
pts_turn, _, _ = simulate_path(RADAR_FIX_LAT, RADAR_FIX_LON, turn_legs, method)
|
| 1630 |
lat0, lon0 = float(pts_turn[-1][0]), float(pts_turn[-1][1])
|
| 1631 |
|
|
|
|
| 1632 |
beam = [{
|
| 1633 |
"lat": lat0,
|
| 1634 |
"lon": lon0,
|
|
|
|
| 1644 |
"seg_end_errs": [],
|
| 1645 |
}]
|
| 1646 |
|
|
|
|
| 1647 |
for mins, end_lbl in zip(GUIDED_SOUTH_SEG_MIN, GUIDED_SOUTH_SEG_LABELS_END):
|
| 1648 |
dt_end = _dt_from_label(end_lbl)
|
| 1649 |
meas = _lookup_measured_bto(dt_end)
|
|
|
|
| 1651 |
|
| 1652 |
for st in beam:
|
| 1653 |
h_prev = float(st["h_prev"])
|
| 1654 |
+
|
| 1655 |
cand_h = []
|
| 1656 |
+
for d in heading_delta_set:
|
| 1657 |
h_try = h_prev + float(d)
|
| 1658 |
+
h_try = _clamp(h_try, float(heading_min), float(heading_max))
|
| 1659 |
cand_h.append(h_try)
|
|
|
|
| 1660 |
cand_h = sorted(list({round(x, 6): x for x in cand_h}.values()))
|
| 1661 |
|
| 1662 |
for h_try in cand_h:
|
|
|
|
| 1675 |
vmax=SOUTH_SPEED_MAX_KT,
|
| 1676 |
)
|
| 1677 |
|
|
|
|
| 1678 |
abs_err = 0.0 if meas is None or (err_seg != err_seg) else abs(float(err_seg))
|
| 1679 |
smooth_pen = abs(float(h_try) - float(h_prev)) * VAR_SMOOTH_PENALTY_US_PER_DEG
|
| 1680 |
|
|
|
|
| 1697 |
"seg_end_errs": st["seg_end_errs"] + [float(err_seg)],
|
| 1698 |
})
|
| 1699 |
|
|
|
|
| 1700 |
if not next_beam:
|
| 1701 |
beam = []
|
| 1702 |
break
|
|
|
|
| 1707 |
if not beam:
|
| 1708 |
continue
|
| 1709 |
|
|
|
|
| 1710 |
for st in beam:
|
| 1711 |
south_legs = _build_south_legs_variable(st["headings"], st["speeds"], GUIDED_SOUTH_SEG_MIN)
|
| 1712 |
full_legs = clip_legs_to_end_dt(st["turn_legs"] + south_legs, start_dt, end_dt)
|
|
|
|
| 1731 |
"max_abs_err_us": float(max_abs),
|
| 1732 |
"sum_abs_err_us": float(sum_abs),
|
| 1733 |
"rmse_bto_us": float(rmse),
|
| 1734 |
+
"heading_min": float(heading_min),
|
| 1735 |
+
"heading_max": float(heading_max),
|
| 1736 |
+
"deltas": [float(x) for x in heading_delta_set],
|
| 1737 |
+
"label_prefix": str(label_prefix),
|
| 1738 |
}
|
| 1739 |
|
| 1740 |
if best_global_legs is None:
|
| 1741 |
+
return None, f"Guided solve: {label_prefix} found no solution within bounds.", None
|
| 1742 |
|
| 1743 |
hs = best_global_info["headings"]
|
| 1744 |
vs = best_global_info["speeds"]
|
| 1745 |
es = best_global_info["seg_end_errs"]
|
| 1746 |
|
| 1747 |
report_lines = [
|
| 1748 |
+
f"**Guided mode: {best_global_info['label_prefix']} + computed turn (18:22→18:40), clipped to 00:20**",
|
| 1749 |
"- Solver mode: **BTO-first ring-fit (beam search)**",
|
| 1750 |
+
f"- Heading bounds: **{best_global_info['heading_min']:.1f}–{best_global_info['heading_max']:.1f}°**",
|
| 1751 |
f"- Turn speed: **{best_global_info['v_turn']:.2f} kt** (range {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f})",
|
| 1752 |
+
f"- Beam width: **{VAR_BEAM_WIDTH}** | Heading deltas: **{best_global_info['deltas']}°** | Smooth penalty: **{VAR_SMOOTH_PENALTY_US_PER_DEG:.2f} µs/deg**",
|
| 1753 |
"",
|
| 1754 |
"**Per-segment heading & speed:**",
|
| 1755 |
f"- 18:40→19:40: **{hs[0]:.2f}°**, **{vs[0]:.2f} kt** (end err {es[0]:+.2f} µs)",
|
|
|
|
| 1768 |
return best_global_legs, "\n".join(report_lines), start_dt
|
| 1769 |
|
| 1770 |
|
| 1771 |
+
def solve_guided_variable_default(method, alt_ft, vs_fpm, descent_start_choice):
|
| 1772 |
+
return solve_guided_variable(
|
| 1773 |
+
method=method,
|
| 1774 |
+
alt_ft=alt_ft,
|
| 1775 |
+
vs_fpm=vs_fpm,
|
| 1776 |
+
descent_start_choice=descent_start_choice,
|
| 1777 |
+
heading_min=VAR_HEADING_MIN,
|
| 1778 |
+
heading_max=VAR_HEADING_MAX,
|
| 1779 |
+
first_heading_step=VAR_FIRST_HEADING_STEP,
|
| 1780 |
+
heading_delta_set=VAR_HEADING_DELTA_SET,
|
| 1781 |
+
label_prefix="Variable heading & speed",
|
| 1782 |
+
)
|
| 1783 |
+
|
| 1784 |
+
|
| 1785 |
+
def solve_guided_variable_se(method, alt_ft, vs_fpm, descent_start_choice):
|
| 1786 |
+
return solve_guided_variable(
|
| 1787 |
+
method=method,
|
| 1788 |
+
alt_ft=alt_ft,
|
| 1789 |
+
vs_fpm=vs_fpm,
|
| 1790 |
+
descent_start_choice=descent_start_choice,
|
| 1791 |
+
heading_min=VAR_SE_HEADING_MIN,
|
| 1792 |
+
heading_max=VAR_SE_HEADING_MAX,
|
| 1793 |
+
first_heading_step=VAR_SE_FIRST_HEADING_STEP,
|
| 1794 |
+
heading_delta_set=VAR_SE_HEADING_DELTA_SET,
|
| 1795 |
+
label_prefix="Variable heading & speed (SE)",
|
| 1796 |
+
)
|
| 1797 |
+
|
| 1798 |
+
|
| 1799 |
# ================= RUNNERS =================
|
| 1800 |
def _run_common(start_lat, start_lon, legs, method, start_dt, alt_ft, vs_fpm, descent_start_choice):
|
| 1801 |
if start_dt == RADAR_FIX_TIME_UTC:
|
|
|
|
| 1838 |
def run_guided(guided_mode, start_lat, start_lon, method, alt_ft, vs_fpm, descent_start_choice):
|
| 1839 |
if guided_mode == "Straight heading":
|
| 1840 |
legs, report_md, start_dt = solve_guided_straight(method, alt_ft, vs_fpm, descent_start_choice)
|
| 1841 |
+
elif guided_mode == "Variable heading & speed":
|
| 1842 |
+
legs, report_md, start_dt = solve_guided_variable_default(method, alt_ft, vs_fpm, descent_start_choice)
|
| 1843 |
else:
|
| 1844 |
+
legs, report_md, start_dt = solve_guided_variable_se(method, alt_ft, vs_fpm, descent_start_choice)
|
| 1845 |
|
| 1846 |
if legs is None:
|
| 1847 |
fig = go.Figure()
|
|
|
|
| 1889 |
f"**Turn speed bounds:** {TURN_SPEED_MIN_KT:.0f}–{TURN_SPEED_MAX_KT:.0f} kt\n"
|
| 1890 |
f"**Southern speed bounds:** {SOUTH_SPEED_MIN_KT:.0f}–{SOUTH_SPEED_MAX_KT:.0f} kt\n"
|
| 1891 |
f"**Straight mode:** Top-K polish (top-{TOPK_POLISH})\n"
|
| 1892 |
+
f"**Variable mode:** beam search (width {VAR_BEAM_WIDTH})\n"
|
| 1893 |
+
f"**SE mode:** beam search with heading constrained to {VAR_SE_HEADING_MIN:.0f}–{VAR_SE_HEADING_MAX:.0f}° (avoids 190° SW)"
|
| 1894 |
)
|
| 1895 |
|
| 1896 |
guided_mode = gr.Dropdown(
|
| 1897 |
+
["Straight heading", "Variable heading & speed", "Variable heading & speed (SE)"],
|
| 1898 |
value="Straight heading",
|
| 1899 |
label="Guided scenario",
|
| 1900 |
)
|