Collision Avoidance Maneuver design for conjunction assessment
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 212 lines 8.1 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2026 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Physical invariant fuzz tests for the CAM library. 7 8 Property-based tests that check physical invariants hold for random inputs. 9 Each test generates 1000 random conjunction scenarios and verifies that 10 fundamental physics properties are preserved. *) 11 12let n_cases = 1000 13 14(* Generate random inputs in physically reasonable ranges *) 15let random_miss () = Random.float 10000.0 (* 0-10000 m *) 16let random_sigma_r () = 10.0 +. Random.float 990.0 (* 10-1000 m *) 17let random_sigma_t () = 100.0 +. Random.float 9900.0 (* 100-10000 m *) 18let random_hbr () = 1.0 +. Random.float 49.0 (* 1-50 m *) 19let random_dv () = Random.float 20.0 -. 10.0 (* -10 to 10 m/s *) 20let random_dt () = Random.float 86400.0 (* 0 to 86400 s *) 21 22(* {1 Invariant 1: Pc is always in [0, 1]} *) 23 24let test_pc_bounds () = 25 Random.self_init (); 26 for i = 1 to n_cases do 27 let miss_r = random_miss () in 28 let miss_t = random_miss () in 29 let miss_n = random_miss () in 30 let sigma_r = random_sigma_r () in 31 let sigma_t = random_sigma_t () in 32 let hbr = random_hbr () in 33 let dv = random_dv () in 34 let dt = random_dt () in 35 let m : Cam.maneuver = { dt; dv; direction = `Tangential } in 36 let r = Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr m in 37 if r.pc_before < 0.0 || r.pc_before > 1.0 then 38 Alcotest.failf "case %d: pc_before=%e out of [0,1]" i r.pc_before; 39 if r.pc_after < 0.0 || r.pc_after > 1.0 then 40 Alcotest.failf "case %d: pc_after=%e out of [0,1]" i r.pc_after 41 done 42 43(* {1 Invariant 2: miss_distance_after is always >= 0} *) 44 45let test_miss_distance_positive () = 46 Random.self_init (); 47 for i = 1 to n_cases do 48 let miss_r = random_miss () in 49 let miss_t = random_miss () in 50 let miss_n = random_miss () in 51 let sigma_r = random_sigma_r () in 52 let sigma_t = random_sigma_t () in 53 let hbr = random_hbr () in 54 let dv = random_dv () in 55 let dt = random_dt () in 56 let m : Cam.maneuver = { dt; dv; direction = `Tangential } in 57 let r = Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr m in 58 if r.miss_distance_before < 0.0 then 59 Alcotest.failf "case %d: miss_distance_before=%e < 0" i 60 r.miss_distance_before; 61 if r.miss_distance_after < 0.0 then 62 Alcotest.failf "case %d: miss_distance_after=%e < 0" i 63 r.miss_distance_after 64 done 65 66(* {1 Invariant 3: Zero burn invariant — dv=0 returns identical before/after} *) 67 68let test_zero_burn_invariant () = 69 Random.self_init (); 70 for i = 1 to n_cases do 71 let miss_r = random_miss () in 72 let miss_t = random_miss () in 73 let miss_n = random_miss () in 74 let sigma_r = random_sigma_r () in 75 let sigma_t = random_sigma_t () in 76 let hbr = random_hbr () in 77 let dt = random_dt () in 78 let m : Cam.maneuver = { dt; dv = 0.0; direction = `Tangential } in 79 let r = Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr m in 80 let miss_diff = 81 Float.abs (r.miss_distance_after -. r.miss_distance_before) 82 in 83 if miss_diff > 1e-6 then 84 Alcotest.failf "case %d: zero burn changed miss by %e" i miss_diff; 85 let pc_diff = Float.abs (r.pc_after -. r.pc_before) in 86 if pc_diff > 1e-15 then 87 Alcotest.failf "case %d: zero burn changed Pc by %e" i pc_diff; 88 if Float.abs r.delta_v > 1e-15 then 89 Alcotest.failf "case %d: zero burn delta_v=%e" i r.delta_v 90 done 91 92(* {1 Invariant 4: dt=0 invariant — burn at TCA has no effect} *) 93 94let test_dt_zero_invariant () = 95 Random.self_init (); 96 for i = 1 to n_cases do 97 let miss_r = random_miss () in 98 let miss_t = random_miss () in 99 let miss_n = random_miss () in 100 let sigma_r = random_sigma_r () in 101 let sigma_t = random_sigma_t () in 102 let hbr = random_hbr () in 103 let dv = random_dv () in 104 let m : Cam.maneuver = { dt = 0.0; dv; direction = `Tangential } in 105 let r = Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr m in 106 let miss_diff = 107 Float.abs (r.miss_distance_after -. r.miss_distance_before) 108 in 109 if miss_diff > 1e-6 then 110 Alcotest.failf "case %d: dt=0 burn changed miss by %e (dv=%.3f)" i 111 miss_diff dv; 112 let pc_diff = Float.abs (r.pc_after -. r.pc_before) in 113 if pc_diff > 1e-15 then 114 Alcotest.failf "case %d: dt=0 burn changed Pc by %e (dv=%.3f)" i pc_diff 115 dv 116 done 117 118(* {1 Invariant 5: Symmetry — dv=+X and dv=-X produce same delta_v cost} *) 119 120let test_dv_cost_symmetry () = 121 Random.self_init (); 122 for i = 1 to n_cases do 123 let miss_r = random_miss () in 124 let miss_t = random_miss () in 125 let miss_n = random_miss () in 126 let sigma_r = random_sigma_r () in 127 let sigma_t = random_sigma_t () in 128 let hbr = random_hbr () in 129 let dv = 0.01 +. Random.float 9.99 in 130 let dt = 1.0 +. Random.float 86399.0 in 131 let eval dv = 132 Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr 133 { dt; dv; direction = `Tangential } 134 in 135 let r_pos = eval dv in 136 let r_neg = eval (-.dv) in 137 let diff = Float.abs (r_pos.delta_v -. r_neg.delta_v) in 138 if diff > 1e-10 then 139 Alcotest.failf "case %d: delta_v not symmetric: +dv=%.6f -dv=%.6f" i 140 r_pos.delta_v r_neg.delta_v 141 done 142 143(* {1 Invariant 6: Monotonicity — increasing burn magnitude increases miss shift} *) 144 145let test_monotonicity () = 146 Random.self_init (); 147 for i = 1 to n_cases do 148 let miss_r = random_miss () in 149 let miss_t = random_miss () in 150 let miss_n = random_miss () in 151 let sigma_r = random_sigma_r () in 152 let sigma_t = random_sigma_t () in 153 let hbr = random_hbr () in 154 let dt = 100.0 +. Random.float 86300.0 in 155 let eval dv = 156 Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr 157 { dt; dv; direction = `Tangential } 158 in 159 let dv1 = 1.0 in 160 let dv2 = 5.0 in 161 let r1 = eval dv1 in 162 let r2 = eval dv2 in 163 (* The miss distance shift should be larger for larger burn. 164 shift = |miss_after - miss_before|. For same-direction burns, the 165 absolute shift from the zero-burn baseline should increase. *) 166 let shift1 = 167 Float.abs (r1.miss_distance_after -. r1.miss_distance_before) 168 in 169 let shift2 = 170 Float.abs (r2.miss_distance_after -. r2.miss_distance_before) 171 in 172 if shift2 < shift1 -. 1e-6 then 173 Alcotest.failf 174 "case %d: larger burn (%.1f) gave smaller shift (%.1f) than smaller \ 175 burn (%.1f, shift=%.1f)" 176 i dv2 shift2 dv1 shift1 177 done 178 179(* {1 Invariant 7: Large burn safety — sufficiently large burn drives Pc down} *) 180 181let test_large_burn_safety () = 182 Random.self_init (); 183 for i = 1 to n_cases do 184 let miss_r = random_miss () in 185 let miss_t = random_miss () in 186 let miss_n = random_miss () in 187 let sigma_r = random_sigma_r () in 188 let sigma_t = random_sigma_t () in 189 let hbr = random_hbr () in 190 let dt = 3600.0 in 191 (* A 100 m/s burn at 1 hour shifts T by 360 km — should overwhelm any 192 conjunction geometry with sigmas < 10 km *) 193 let m : Cam.maneuver = { dt; dv = 100.0; direction = `Tangential } in 194 let r = Cam.evaluate ~miss_r ~miss_t ~miss_n ~sigma_r ~sigma_t ~hbr m in 195 if r.pc_after > 1e-10 then 196 Alcotest.failf 197 "case %d: 100 m/s burn at 1h should drive Pc < 1e-10, got %e" i 198 r.pc_after 199 done 200 201let suite = 202 ( "cam", 203 [ 204 Alcotest.test_case "Pc bounds" `Quick test_pc_bounds; 205 Alcotest.test_case "miss distance positive" `Quick 206 test_miss_distance_positive; 207 Alcotest.test_case "zero burn invariant" `Quick test_zero_burn_invariant; 208 Alcotest.test_case "dt=0 invariant" `Quick test_dt_zero_invariant; 209 Alcotest.test_case "delta-v cost symmetry" `Quick test_dv_cost_symmetry; 210 Alcotest.test_case "monotonicity" `Quick test_monotonicity; 211 Alcotest.test_case "large burn safety" `Quick test_large_burn_safety; 212 ] )