Chris773 commited on
Commit
0850996
·
verified ·
1 Parent(s): 3401409

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -21
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(method, alt_ft, vs_fpm, descent_start_choice):
 
 
 
 
 
 
 
 
 
 
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(VAR_HEADING_MIN, VAR_HEADING_MAX, VAR_FIRST_HEADING_STEP)
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
- # candidate headings around previous
1643
  cand_h = []
1644
- for d in VAR_HEADING_DELTA_SET:
1645
  h_try = h_prev + float(d)
1646
- h_try = _clamp(h_try, VAR_HEADING_MIN, VAR_HEADING_MAX)
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: Variable mode found no solution within bounds.", None
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: Variable heading & speed + computed turn (18:22→18:40), clipped to 00:20**",
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: **{VAR_HEADING_DELTA_SET}°** | Smooth penalty: **{VAR_SMOOTH_PENALTY_US_PER_DEG:.2f} µs/deg**",
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 = solve_guided_variable(method, alt_ft, vs_fpm, descent_start_choice)
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}) for speed"
 
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
  )