Contact Graph Routing for time-varying satellite networks
0
fork

Configure Feed

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

fix(lint): resolve E205, E331, E340 redundant prefixes and error patterns

Remove redundant function prefixes (get_, find_, make_, create_, bundle_)
across bpsec, btree, bundle, cbort, cfdp, cgr, claudeio, and dependent
packages. Replace Printf.sprintf with Fmt.failwith, add err_eio/err_msg
helpers in skills.ml.

+69 -71
+3 -3
README.md
··· 49 49 let plan = Contact_plan.of_list contacts 50 50 51 51 let () = 52 - match find_route plan ~src:earth ~dst:mars ~time:0. with 52 + match route plan ~src:earth ~dst:mars ~time:0. with 53 53 | None -> print_endline "No route available" 54 54 | Some route -> 55 55 Format.printf "Route found: %a@." Route.pp route; ··· 77 77 78 78 ### Routing 79 79 80 - - `find_route plan ~src ~dst ~time` - Find best route (earliest arrival) 81 - - `find_routes plan ~src ~dst ~time ~max` - Find multiple alternative routes 80 + - `route plan ~src ~dst ~time` - Find best route (earliest arrival) 81 + - `routes plan ~src ~dst ~time ~max` - Find multiple alternative routes 82 82 83 83 ### Routes 84 84
+18 -20
example/tle_contacts.ml
··· 168 168 let tle_file = "ocaml-cgr/example/starlink.tle" in 169 169 let tle_text = 170 170 try load_tle_file tle_file 171 - with _ -> 171 + with Sys_error _ -> 172 172 Printf.eprintf "Could not load %s, using embedded TLEs\n" tle_file; 173 173 {|STARLINK-1008 174 174 1 44714U 19074B 26034.10858321 -.00000474 00000+0 -36567-5 0 9992 ··· 228 228 Printf.printf "\nRouting analysis:\n"; 229 229 let total_pairs = ref 0 in 230 230 let routable_pairs = ref 0 in 231 - List.iter 232 - (fun src -> 233 - List.iter 234 - (fun dst -> 235 - if not (Cgr.Node.equal src dst) then begin 236 - incr total_pairs; 237 - match Cgr.find_route plan ~src ~dst ~time:start_time with 238 - | Some route -> 239 - incr routable_pairs; 240 - Printf.printf " %s -> %s: %d hops, latency %.3f s\n" 241 - (Cgr.Node.name src) (Cgr.Node.name dst) 242 - (List.length (Cgr.Route.hops route)) 243 - (Cgr.Route.latency route) 244 - | None -> 245 - Printf.printf " %s -> %s: NO ROUTE\n" (Cgr.Node.name src) 246 - (Cgr.Node.name dst) 247 - end) 248 - nodes) 249 - nodes; 231 + let check_route src dst = 232 + if Cgr.Node.equal src dst then () 233 + else begin 234 + incr total_pairs; 235 + match Cgr.route plan ~src ~dst ~time:start_time with 236 + | Some route -> 237 + incr routable_pairs; 238 + Printf.printf " %s -> %s: %d hops, latency %.3f s\n" 239 + (Cgr.Node.name src) (Cgr.Node.name dst) 240 + (List.length (Cgr.Route.hops route)) 241 + (Cgr.Route.latency route) 242 + | None -> 243 + Printf.printf " %s -> %s: NO ROUTE\n" (Cgr.Node.name src) 244 + (Cgr.Node.name dst) 245 + end 246 + in 247 + List.iter (fun src -> List.iter (check_route src) nodes) nodes; 250 248 251 249 Printf.printf "\nRoutability: %d/%d pairs (%.1f%%)\n" !routable_pairs 252 250 !total_pairs
+6 -6
fuzz/fuzz_cgr.ml
··· 30 30 (* Property: If a route is found, arrival time is finite *) 31 31 let test_route_arrival_finite contacts src dst time = 32 32 let plan = Contact_plan.of_list contacts in 33 - match find_route plan ~src ~dst ~time with 33 + match route plan ~src ~dst ~time with 34 34 | None -> () 35 35 | Some route -> 36 36 let arrival = Route.arrival_time route in ··· 39 39 (* Property: Route endpoints match query *) 40 40 let test_route_endpoints contacts src dst time = 41 41 let plan = Contact_plan.of_list contacts in 42 - match find_route plan ~src ~dst ~time with 42 + match route plan ~src ~dst ~time with 43 43 | None -> () 44 44 | Some route -> 45 45 check (Node.equal (Route.src route) src); ··· 48 48 (* Property: Route arrival time >= query time *) 49 49 let test_route_arrival_after_start contacts src dst time = 50 50 let plan = Contact_plan.of_list contacts in 51 - match find_route plan ~src ~dst ~time with 51 + match route plan ~src ~dst ~time with 52 52 | None -> () 53 53 | Some route -> 54 54 let arrival = Route.arrival_time route in ··· 57 57 (* Property: Route hops form a valid path *) 58 58 let test_route_path_valid contacts src dst time = 59 59 let plan = Contact_plan.of_list contacts in 60 - match find_route plan ~src ~dst ~time with 60 + match route plan ~src ~dst ~time with 61 61 | None -> () 62 62 | Some route -> ( 63 63 match Route.hops route with ··· 81 81 (* Property: Multiple routes have different first hops *) 82 82 let test_routes_different_first_hops contacts src dst time = 83 83 let plan = Contact_plan.of_list contacts in 84 - let routes = find_routes plan ~src ~dst ~time ~max:5 in 84 + let routes = routes plan ~src ~dst ~time ~max:5 in 85 85 let first_hops = 86 86 List.filter_map 87 87 (fun r -> match Route.hops r with c :: _ -> Some c | [] -> None) ··· 97 97 (* Property: Routes are ordered by arrival time *) 98 98 let test_routes_ordered contacts src dst time = 99 99 let plan = Contact_plan.of_list contacts in 100 - let routes = find_routes plan ~src ~dst ~time ~max:5 in 100 + let routes = routes plan ~src ~dst ~time ~max:5 in 101 101 let arrivals = List.map Route.arrival_time routes in 102 102 let rec is_sorted = function 103 103 | [] | [ _ ] -> true
+1 -1
gen/cgr_gen.ml
··· 122 122 ground_stations; 123 123 !contacts 124 124 125 - let make_nodes n = List.init n (fun i -> Cgr.Node.v (Printf.sprintf "N%d" i)) 125 + let nodes n = List.init n (fun i -> Cgr.Node.v (Printf.sprintf "N%d" i))
+2 -2
gen/cgr_gen.mli
··· 5 5 6 6 (** Synthetic contact plan generation for testing and simulation. *) 7 7 8 - val make_nodes : int -> Cgr.Node.t list 9 - (** [make_nodes n] creates [n] nodes named "N0", "N1", ..., "N{n-1}". *) 8 + val nodes : int -> Cgr.Node.t list 9 + (** [nodes n] creates [n] nodes named "N0", "N1", ..., "N{n-1}". *) 10 10 11 11 val ring : 12 12 nodes:Cgr.Node.t list -> duration:float -> rate:float -> Cgr.Contact.t list
+3 -3
lib/cgr.ml
··· 263 263 264 264 (* High-level routing *) 265 265 266 - let find_route plan ~src ~dst ~time = 266 + let route plan ~src ~dst ~time = 267 267 let state = Dijkstra.init plan ~src ~time in 268 268 Dijkstra.extract_route state ~dst 269 269 270 - let find_routes plan ~src ~dst ~time ~max = 270 + let routes plan ~src ~dst ~time ~max = 271 271 (* Find multiple routes by suppressing initial contacts of found routes *) 272 272 let rec loop plan count acc = 273 273 if count >= max then List.rev acc 274 274 else 275 - match find_route plan ~src ~dst ~time with 275 + match route plan ~src ~dst ~time with 276 276 | None -> List.rev acc 277 277 | Some route -> ( 278 278 match Route.hops route with
+7 -7
lib/cgr.mli
··· 37 37 38 38 (* Create contact plan and find route *) 39 39 let plan = Contact_plan.of_list contacts 40 - let route = find_route plan ~src:earth ~dst:mars ~time:0. 40 + let route = route plan ~src:earth ~dst:mars ~time:0. 41 41 ]} 42 42 43 43 {2 References} ··· 206 206 207 207 (** {1 Routing} *) 208 208 209 - val find_route : 209 + val route : 210 210 Contact_plan.t -> src:Node.t -> dst:Node.t -> time:float -> Route.t option 211 - (** [find_route plan ~src ~dst ~time] finds the best route from [src] to [dst] 211 + (** [route plan ~src ~dst ~time] finds the best route from [src] to [dst] 212 212 starting no earlier than [time]. 213 213 214 214 Returns [None] if no route exists. The "best" route minimizes arrival time 215 215 at the destination (earliest delivery). *) 216 216 217 - val find_routes : 217 + val routes : 218 218 Contact_plan.t -> 219 219 src:Node.t -> 220 220 dst:Node.t -> 221 221 time:float -> 222 222 max:int -> 223 223 Route.t list 224 - (** [find_routes plan ~src ~dst ~time ~max] finds up to [max] routes. 224 + (** [routes plan ~src ~dst ~time ~max] finds up to [max] routes. 225 225 226 226 Routes are returned in order of preference (earliest arrival first). 227 227 Subsequent routes use different initial contacts to provide redundancy. *) ··· 231 231 module Dijkstra : sig 232 232 (** Low-level access to the Dijkstra computation. 233 233 234 - Most users should use {!find_route} instead. This module exposes internals 235 - for debugging, visualization, or custom routing strategies. *) 234 + Most users should use {!route} instead. This module exposes internals for 235 + debugging, visualization, or custom routing strategies. *) 236 236 237 237 type state 238 238 (** Internal state of a Dijkstra computation. *)
+29 -29
test/test_cgr.ml
··· 32 32 Contact.v ~from:earth ~to_:mars ~start:0. ~stop:100. ~rate:1000. () 33 33 in 34 34 let plan = Contact_plan.of_list [ contact ] in 35 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 35 + let route = route plan ~src:earth ~dst:mars ~time:0. in 36 36 Alcotest.(check bool) "route exists" true (Option.is_some route); 37 37 let route = Option.get route in 38 38 Alcotest.(check node) "src" earth (Route.src route); ··· 52 52 Contact.v ~from:relay ~to_:mars ~start:50. ~stop:150. ~rate:1000. () 53 53 in 54 54 let plan = Contact_plan.of_list [ c1; c2 ] in 55 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 55 + let route = route plan ~src:earth ~dst:mars ~time:0. in 56 56 Alcotest.(check bool) "route exists" true (Option.is_some route); 57 57 let route = Option.get route in 58 58 Alcotest.(check int) "hops" 2 (List.length (Route.hops route)); ··· 68 68 Contact.v ~from:earth ~to_:relay ~start:0. ~stop:100. ~rate:1000. () 69 69 in 70 70 let plan = Contact_plan.of_list [ contact ] in 71 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 71 + let route = route plan ~src:earth ~dst:mars ~time:0. in 72 72 Alcotest.(check bool) "no route" true (Option.is_none route) 73 73 74 74 (* Test: Choose earliest arrival when multiple paths exist *) ··· 86 86 Contact.v ~from:relay ~to_:mars ~start:10. ~stop:60. ~rate:1000. () 87 87 in 88 88 let plan = Contact_plan.of_list [ slow; c1; c2 ] in 89 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 89 + let route = route plan ~src:earth ~dst:mars ~time:0. in 90 90 Alcotest.(check bool) "route exists" true (Option.is_some route); 91 91 let route = Option.get route in 92 92 (* Fast path arrives at t=10 (via relay), slow path at t=100 *) ··· 103 103 Contact.v ~from:earth ~to_:mars ~start:50. ~stop:100. ~rate:1000. () 104 104 in 105 105 let plan = Contact_plan.of_list [ contact ] in 106 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 106 + let route = route plan ~src:earth ~dst:mars ~time:0. in 107 107 Alcotest.(check bool) "route exists" true (Option.is_some route); 108 108 let route = Option.get route in 109 109 (* Must wait until t=50 to transmit *) ··· 120 120 Contact.v ~from:earth ~to_:mars ~start:0. ~stop:1000. ~rate:1000. ~owlt () 121 121 in 122 122 let plan = Contact_plan.of_list [ contact ] in 123 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 123 + let route = route plan ~src:earth ~dst:mars ~time:0. in 124 124 Alcotest.(check bool) "route exists" true (Option.is_some route); 125 125 let route = Option.get route in 126 126 (* Arrival = transmit time + OWLT = 0 + 600 *) ··· 138 138 (* But the mars contact ends before we could use it *) 139 139 let c2 = Contact.v ~from:relay ~to_:mars ~start:0. ~stop:1. ~rate:1000. () in 140 140 let plan = Contact_plan.of_list [ c1; c2 ] in 141 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 141 + let route = route plan ~src:earth ~dst:mars ~time:0. in 142 142 (* We arrive at relay at t=0, but c2 ends at t=1 - should still work 143 143 since we arrive before end *) 144 144 Alcotest.(check bool) "route exists" true (Option.is_some route) 145 145 146 - (* Test: find_routes returns multiple alternatives *) 146 + (* Test: routes returns multiple alternatives *) 147 147 148 - let test_find_routes () = 148 + let test_routes () = 149 149 (* Two parallel paths *) 150 150 let via_relay = 151 151 [ ··· 160 160 ] 161 161 in 162 162 let plan = Contact_plan.of_list (via_relay @ via_moon) in 163 - let routes = find_routes plan ~src:earth ~dst:mars ~time:0. ~max:3 in 163 + let routes = routes plan ~src:earth ~dst:mars ~time:0. ~max:3 in 164 164 Alcotest.(check int) "found 2 routes" 2 (List.length routes); 165 165 (* First route should be fastest (via relay, arrives at 10) *) 166 166 let first = List.hd routes in ··· 206 206 in 207 207 let c2 = Contact.v ~from:relay ~to_:mars ~start:0. ~stop:50. ~rate:500. () in 208 208 let plan = Contact_plan.of_list [ c1; c2 ] in 209 - let route = find_route plan ~src:earth ~dst:mars ~time:0. in 209 + let route = route plan ~src:earth ~dst:mars ~time:0. in 210 210 let route = Option.get route in 211 211 (* Capacity is min of: c1 (100*1000=100000) and c2 (50*500=25000) *) 212 212 Alcotest.(check bool) ··· 270 270 let test_hdtn_cgr_tutorial () = 271 271 let n1 = Node.v "1" and n3 = Node.v "3" and n5 = Node.v "5" in 272 272 let plan = Contact_plan.of_list hdtn_cgr_tutorial_contacts in 273 - let route = find_route plan ~src:n1 ~dst:n5 ~time:0. in 273 + let route = route plan ~src:n1 ~dst:n5 ~time:0. in 274 274 Alcotest.(check bool) "route exists" true (Option.is_some route); 275 275 let route = Option.get route in 276 276 Alcotest.(check int) "3 hops" 3 (List.length (Route.hops route)); ··· 289 289 let n1 = Node.v "1" and n5 = Node.v "5" in 290 290 let plan = Contact_plan.of_list hdtn_cgr_tutorial_contacts in 291 291 (* At t=25: 1->3 active, 3->4 active (ends at 30), but 4->5 gap until t=30 *) 292 - let route = find_route plan ~src:n1 ~dst:n5 ~time:25. in 292 + let route = route plan ~src:n1 ~dst:n5 ~time:25. in 293 293 Alcotest.(check bool) "route exists" true (Option.is_some route); 294 294 let route = Option.get route in 295 295 (* Path: 1->3 (arrive 26), 3->4 (arrive 27), wait for 4->5 at t=30, arrive 31 *) ··· 329 329 let test_hdtn_routing_basic () = 330 330 let n1 = Node.v "1" and n2 = Node.v "2" and n4 = Node.v "4" in 331 331 let plan = Contact_plan.of_list hdtn_routing_test_contacts in 332 - let route = find_route plan ~src:n1 ~dst:n4 ~time:0. in 332 + let route = route plan ~src:n1 ~dst:n4 ~time:0. in 333 333 Alcotest.(check bool) "route exists" true (Option.is_some route); 334 334 let route = Option.get route in 335 335 Alcotest.(check int) "2 hops" 2 (List.length (Route.hops route)); ··· 346 346 let test_hdtn_routing_no_reverse () = 347 347 let n1 = Node.v "1" and n4 = Node.v "4" in 348 348 let plan = Contact_plan.of_list hdtn_routing_test_contacts in 349 - let route = find_route plan ~src:n4 ~dst:n1 ~time:0. in 349 + let route = route plan ~src:n4 ~dst:n1 ~time:0. in 350 350 Alcotest.(check bool) "no reverse route" true (Option.is_none route) 351 351 352 352 (* Test: HDTN routing test - alternative path via node 3 ··· 359 359 let test_hdtn_routing_choose_faster () = 360 360 let n1 = Node.v "1" and n2 = Node.v "2" and n4 = Node.v "4" in 361 361 let plan = Contact_plan.of_list hdtn_routing_test_contacts in 362 - let route = find_route plan ~src:n1 ~dst:n4 ~time:15. in 362 + let route = route plan ~src:n1 ~dst:n4 ~time:15. in 363 363 Alcotest.(check bool) "route exists" true (Option.is_some route); 364 364 let route = Option.get route in 365 365 (* Should choose 1->2->4 path *) ··· 375 375 ========================================================================== *) 376 376 377 377 let test_ring_10 () = 378 - let nodes = Cgr_gen.make_nodes 10 in 378 + let nodes = Cgr_gen.nodes 10 in 379 379 let contacts = Cgr_gen.ring ~nodes ~duration:1000. ~rate:1_000_000. in 380 380 let plan = Contact_plan.of_list contacts in 381 381 Alcotest.(check int) "10 contacts" 10 (List.length contacts); 382 382 (* Route from N0 to N5 should exist (5 hops clockwise) *) 383 383 let n0 = Node.v "N0" and n5 = Node.v "N5" in 384 - let route = find_route plan ~src:n0 ~dst:n5 ~time:0. in 384 + let route = route plan ~src:n0 ~dst:n5 ~time:0. in 385 385 Alcotest.(check bool) "route exists" true (Option.is_some route); 386 386 let route = Option.get route in 387 387 Alcotest.(check int) "5 hops" 5 (List.length (Route.hops route)) 388 388 389 389 let test_ring_100 () = 390 - let nodes = Cgr_gen.make_nodes 100 in 390 + let nodes = Cgr_gen.nodes 100 in 391 391 let contacts = Cgr_gen.ring ~nodes ~duration:1000. ~rate:1_000_000. in 392 392 let plan = Contact_plan.of_list contacts in 393 393 Alcotest.(check int) "100 contacts" 100 (List.length contacts); 394 394 (* Route from N0 to N50 should exist (50 hops clockwise) *) 395 395 let n0 = Node.v "N0" and n50 = Node.v "N50" in 396 - let route = find_route plan ~src:n0 ~dst:n50 ~time:0. in 396 + let route = route plan ~src:n0 ~dst:n50 ~time:0. in 397 397 Alcotest.(check bool) "route exists" true (Option.is_some route); 398 398 let route = Option.get route in 399 399 Alcotest.(check int) "50 hops" 50 (List.length (Route.hops route)) 400 400 401 401 let test_mesh_10 () = 402 - let nodes = Cgr_gen.make_nodes 10 in 402 + let nodes = Cgr_gen.nodes 10 in 403 403 let contacts = Cgr_gen.mesh ~nodes ~duration:1000. ~rate:1_000_000. in 404 404 let plan = Contact_plan.of_list contacts in 405 405 (* Full mesh: n*(n-1) = 10*9 = 90 directed edges *) 406 406 Alcotest.(check int) "90 contacts" 90 (List.length contacts); 407 407 (* Direct route between any two nodes *) 408 408 let n0 = Node.v "N0" and n9 = Node.v "N9" in 409 - let route = find_route plan ~src:n0 ~dst:n9 ~time:0. in 409 + let route = route plan ~src:n0 ~dst:n9 ~time:0. in 410 410 Alcotest.(check bool) "route exists" true (Option.is_some route); 411 411 let route = Option.get route in 412 412 (* In full mesh, shortest path is always 1 hop *) 413 413 Alcotest.(check int) "1 hop" 1 (List.length (Route.hops route)) 414 414 415 415 let test_mesh_20 () = 416 - let nodes = Cgr_gen.make_nodes 20 in 416 + let nodes = Cgr_gen.nodes 20 in 417 417 let contacts = Cgr_gen.mesh ~nodes ~duration:1000. ~rate:1_000_000. in 418 418 let plan = Contact_plan.of_list contacts in 419 419 (* Full mesh: 20*19 = 380 directed edges *) 420 420 Alcotest.(check int) "380 contacts" 380 (List.length contacts); 421 421 let n0 = Node.v "N0" and n19 = Node.v "N19" in 422 - let route = find_route plan ~src:n0 ~dst:n19 ~time:0. in 422 + let route = route plan ~src:n0 ~dst:n19 ~time:0. in 423 423 Alcotest.(check bool) "route exists" true (Option.is_some route); 424 424 let route = Option.get route in 425 425 Alcotest.(check int) "1 hop" 1 (List.length (Route.hops route)) 426 426 427 427 let test_time_varying () = 428 - let nodes = Cgr_gen.make_nodes 10 in 428 + let nodes = Cgr_gen.nodes 10 in 429 429 let contacts = 430 430 Cgr_gen.time_varying ~nodes ~intervals:5 ~rate:1_000_000. ~seed:42 431 431 in ··· 434 434 Alcotest.(check bool) "has contacts" true (List.length contacts > 0); 435 435 (* Try routing at different times *) 436 436 let n0 = Node.v "N0" and n5 = Node.v "N5" in 437 - let route_early = find_route plan ~src:n0 ~dst:n5 ~time:0. in 438 - let route_late = find_route plan ~src:n0 ~dst:n5 ~time:500. in 437 + let route_early = route plan ~src:n0 ~dst:n5 ~time:0. in 438 + let route_late = route plan ~src:n0 ~dst:n5 ~time:500. in 439 439 (* At least one should succeed *) 440 440 Alcotest.(check bool) 441 441 "at least one route" true ··· 456 456 Total: 80 *) 457 457 Alcotest.(check bool) "many contacts" true (List.length contacts > 50); 458 458 (* Route from GS1 to GS2 should exist (via satellites) *) 459 - let route = find_route plan ~src:gs1 ~dst:gs2 ~time:0. in 459 + let route = route plan ~src:gs1 ~dst:gs2 ~time:0. in 460 460 Alcotest.(check bool) "GS-to-GS route exists" true (Option.is_some route); 461 461 let route = Option.get route in 462 462 (* Route should have multiple hops (GS1 -> sat -> ... -> sat -> GS2) *) ··· 476 476 Alcotest.test_case "wait for contact" `Quick test_wait_for_contact; 477 477 Alcotest.test_case "propagation delay" `Quick test_propagation_delay; 478 478 Alcotest.test_case "contact expires" `Quick test_contact_expires; 479 - Alcotest.test_case "find routes" `Quick test_find_routes; 479 + Alcotest.test_case "find routes" `Quick test_routes; 480 480 Alcotest.test_case "contact plan operations" `Quick test_contact_plan; 481 481 Alcotest.test_case "route capacity" `Quick test_route_capacity; 482 482 Alcotest.test_case "hdtn cgr tutorial" `Quick test_hdtn_cgr_tutorial;