colregs-planner 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. colregs_planner-0.1.0/LICENSE +21 -0
  2. colregs_planner-0.1.0/PKG-INFO +471 -0
  3. colregs_planner-0.1.0/README.md +425 -0
  4. colregs_planner-0.1.0/colregs_planner/__init__.py +22 -0
  5. colregs_planner-0.1.0/colregs_planner/ais/__init__.py +0 -0
  6. colregs_planner-0.1.0/colregs_planner/ais/cleaning.py +166 -0
  7. colregs_planner-0.1.0/colregs_planner/ais/windows.py +52 -0
  8. colregs_planner-0.1.0/colregs_planner/colregs_graph/__init__.py +0 -0
  9. colregs_planner-0.1.0/colregs_planner/colregs_graph/colregs_classifier.py +418 -0
  10. colregs_planner-0.1.0/colregs_planner/colregs_graph/colregs_graph.py +115 -0
  11. colregs_planner-0.1.0/colregs_planner/motion_models/__init__.py +1 -0
  12. colregs_planner-0.1.0/colregs_planner/motion_models/adapters.py +386 -0
  13. colregs_planner-0.1.0/colregs_planner/motion_models/ct_model.py +191 -0
  14. colregs_planner-0.1.0/colregs_planner/motion_models/milliampere_model.py +344 -0
  15. colregs_planner-0.1.0/colregs_planner/motion_models/vikenes_model.py +404 -0
  16. colregs_planner-0.1.0/colregs_planner/nominal/__init__.py +19 -0
  17. colregs_planner-0.1.0/colregs_planner/nominal/indexer.py +166 -0
  18. colregs_planner-0.1.0/colregs_planner/nominal/library.py +144 -0
  19. colregs_planner-0.1.0/colregs_planner/nominal/retriever.py +216 -0
  20. colregs_planner-0.1.0/colregs_planner/planner_api/__init__.py +40 -0
  21. colregs_planner-0.1.0/colregs_planner/planner_api/dp_planner.py +320 -0
  22. colregs_planner-0.1.0/colregs_planner/planner_api/manoeuvre_search.py +519 -0
  23. colregs_planner-0.1.0/colregs_planner/planner_api/manoeuvres.py +48 -0
  24. colregs_planner-0.1.0/colregs_planner/planner_api/schema.py +208 -0
  25. colregs_planner-0.1.0/colregs_planner/planner_api/service.py +687 -0
  26. colregs_planner-0.1.0/colregs_planner/planner_api/tools.py +218 -0
  27. colregs_planner-0.1.0/colregs_planner/policies/__init__.py +1 -0
  28. colregs_planner-0.1.0/colregs_planner/policies/constant.py +70 -0
  29. colregs_planner-0.1.0/colregs_planner/policies/cto.py +452 -0
  30. colregs_planner-0.1.0/colregs_planner/policies/debug_types.py +79 -0
  31. colregs_planner-0.1.0/colregs_planner/policies/predictor_dp.py +52 -0
  32. colregs_planner-0.1.0/colregs_planner/policies/search_dp.py +93 -0
  33. colregs_planner-0.1.0/colregs_planner/policies/vo_analytic.py +777 -0
  34. colregs_planner-0.1.0/colregs_planner/policies/vo_common.py +387 -0
  35. colregs_planner-0.1.0/colregs_planner/policies/vo_kuwata.py +553 -0
  36. colregs_planner-0.1.0/colregs_planner/prediction/__init__.py +1 -0
  37. colregs_planner-0.1.0/colregs_planner/prediction/parallel.py +203 -0
  38. colregs_planner-0.1.0/colregs_planner/prediction/predictor.py +693 -0
  39. colregs_planner-0.1.0/colregs_planner/prediction/responding_filter.py +217 -0
  40. colregs_planner-0.1.0/colregs_planner/prediction/vo_rollout.py +621 -0
  41. colregs_planner-0.1.0/colregs_planner/scenario_config.py +165 -0
  42. colregs_planner-0.1.0/colregs_planner/seachart/__init__.py +0 -0
  43. colregs_planner-0.1.0/colregs_planner/seachart/loader.py +292 -0
  44. colregs_planner-0.1.0/colregs_planner/vessel/__init__.py +2 -0
  45. colregs_planner-0.1.0/colregs_planner/vessel/base.py +85 -0
  46. colregs_planner-0.1.0/colregs_planner/vessel/encounter.py +225 -0
  47. colregs_planner-0.1.0/colregs_planner/vessel/state.py +170 -0
  48. colregs_planner-0.1.0/colregs_planner/visualization/__init__.py +1 -0
  49. colregs_planner-0.1.0/colregs_planner/visualization/live_viz.py +2088 -0
  50. colregs_planner-0.1.0/colregs_planner/visualization/map_background.py +131 -0
  51. colregs_planner-0.1.0/colregs_planner/visualization/multi_panel.py +540 -0
  52. colregs_planner-0.1.0/colregs_planner.egg-info/PKG-INFO +471 -0
  53. colregs_planner-0.1.0/colregs_planner.egg-info/SOURCES.txt +63 -0
  54. colregs_planner-0.1.0/colregs_planner.egg-info/dependency_links.txt +1 -0
  55. colregs_planner-0.1.0/colregs_planner.egg-info/requires.txt +32 -0
  56. colregs_planner-0.1.0/colregs_planner.egg-info/top_level.txt +1 -0
  57. colregs_planner-0.1.0/pyproject.toml +56 -0
  58. colregs_planner-0.1.0/setup.cfg +4 -0
  59. colregs_planner-0.1.0/tests/test_colregs_classifier.py +240 -0
  60. colregs_planner-0.1.0/tests/test_dp_planner.py +95 -0
  61. colregs_planner-0.1.0/tests/test_manoeuvre_search.py +601 -0
  62. colregs_planner-0.1.0/tests/test_milliampere.py +441 -0
  63. colregs_planner-0.1.0/tests/test_nominal_indexer.py +64 -0
  64. colregs_planner-0.1.0/tests/test_parallel.py +152 -0
  65. colregs_planner-0.1.0/tests/test_vo_solver.py +115 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johannes Robert Skarø and Mikkel Bergstrand
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,471 @@
1
+ Metadata-Version: 2.4
2
+ Name: colregs-planner
3
+ Version: 0.1.0
4
+ Summary: COLREGs-aware multi-vessel trajectory prediction and own-ship manoeuvre planning, with a dynamic-positioning reference as output
5
+ Author: Johannes Robert Skarø, Mikkel Bergstrand
6
+ License-Expression: MIT
7
+ Keywords: COLREGs,collision avoidance,maritime,autonomous ships,trajectory prediction,velocity obstacle,motion planning
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.26
21
+ Requires-Dist: scipy>=1.11
22
+ Requires-Dist: PyYAML>=6.0
23
+ Provides-Extra: nominal
24
+ Requires-Dist: h5py>=3.10; extra == "nominal"
25
+ Requires-Dist: pyproj>=3.6; extra == "nominal"
26
+ Provides-Extra: ais
27
+ Requires-Dist: pandas>=3.0; extra == "ais"
28
+ Requires-Dist: pyarrow>=14; extra == "ais"
29
+ Requires-Dist: pyproj>=3.6; extra == "ais"
30
+ Requires-Dist: h5py>=3.10; extra == "ais"
31
+ Provides-Extra: sim
32
+ Requires-Dist: matplotlib>=3.8; extra == "sim"
33
+ Provides-Extra: seachart
34
+ Requires-Dist: pyogrio>=0.7; extra == "seachart"
35
+ Requires-Dist: shapely>=2.0; extra == "seachart"
36
+ Requires-Dist: pyproj>=3.6; extra == "seachart"
37
+ Provides-Extra: plots
38
+ Requires-Dist: matplotlib>=3.8; extra == "plots"
39
+ Requires-Dist: contextily>=1.5; extra == "plots"
40
+ Provides-Extra: all
41
+ Requires-Dist: colregs-planner[ais,nominal,plots,seachart,sim]; extra == "all"
42
+ Provides-Extra: test
43
+ Requires-Dist: pytest>=8; extra == "test"
44
+ Requires-Dist: colregs-planner[ais,nominal,sim]; extra == "test"
45
+ Dynamic: license-file
46
+
47
+ # colregs-planner
48
+
49
+ COLREGs-aware multi-vessel trajectory prediction and own-ship manoeuvre
50
+ planning. Code for **"COLREGs-Aware Multi-Vessel Trajectory Prediction via Interaction
51
+ Hypothesis Inference and Velocity-Obstacle Rollout."**
52
+
53
+ Each vessel's trajectory is predicted in two layers. A *nominal* layer predicts
54
+ where the vessel would go without other traffic. An *interaction-aware* layer
55
+ assigns COLREGs roles between vessel pairs, estimates which vessels actually
56
+ respond to their obligations, and predicts the resulting maneuvers with a
57
+ velocity-obstacle (VO) rollout.
58
+
59
+ ![Multi-vessel COLREGs-compliant encounter](docs/multi_vessel.gif)
60
+
61
+ ## Install
62
+
63
+ Python 3.11 or newer. From PyPI:
64
+
65
+ ```bash
66
+ pip install colregs-planner # prediction, planner, ColregsDPPlanner
67
+ pip install "colregs-planner[all]" # plus AIS processing, nominal library, plots, sea charts
68
+ ```
69
+
70
+ Extras: `nominal` (track library), `ais` (AIS processing and library
71
+ building), `sim` (simulation configs and plots), `seachart`, `plots`.
72
+
73
+ ```python
74
+ from colregs_planner import ColregsDPPlanner, Route # see "DP reference for an own ship"
75
+ ```
76
+
77
+ The simulation and AIS scripts live in the repository, not in the package. To
78
+ run them, install from source:
79
+
80
+ ```bash
81
+ git clone https://github.com/johannesskaro/multi-vessel-prediction.git
82
+ cd multi-vessel-prediction
83
+ python -m venv venv
84
+ venv\Scripts\activate # Windows; source venv/bin/activate on Linux/macOS
85
+ python --version # must be 3.11 or newer
86
+ pip install -e ".[all,test]"
87
+ ```
88
+
89
+ If `python --version` shows an older version, another Python comes first on your
90
+ PATH. Delete the `venv` folder and create it with a specific version instead:
91
+ `py -3.13 -m venv venv` on Windows (`py --list` shows the installed versions), or
92
+ `python3.13 -m venv venv` on Linux/macOS.
93
+
94
+ ## Quick start
95
+
96
+ The simulations need no data:
97
+
98
+ ```bash
99
+ python scripts/run_simulation.py --config scenarios/simulation/multi_vessel.yaml
100
+ ```
101
+
102
+ This is the four-vessel scenario from the paper (Fig. 2): V2 does not respond,
103
+ and the others adapt once the filter detects it. Controls in the plot window:
104
+ **Space** pause/resume, **S** save the frame to `outputs/frames/`, **Q** quit.
105
+ Useful flags: `--save` writes a GIF to `outputs/`, `--stop-time 20.5` pauses and
106
+ saves a frame at that time, `--record` saves a video.
107
+
108
+ ## Simulation scenarios
109
+
110
+ | Config | Scenario |
111
+ |---|---|
112
+ | `head_on.yaml` | two vessels head-on, both responsive |
113
+ | `crossing.yaml` | two vessels crossing, the target does not respond |
114
+ | `overtaking.yaml` | two vessels overtaking, the target does not respond |
115
+ | `overtaking_ahead.yaml` | ego overtakes a slower vessel dead ahead; the pass is far beyond a short planning horizon |
116
+ | `head_on_crossing.yaml` | head-on plus a non-responsive crossing vessel |
117
+ | `multi_crossing.yaml` | ego and three crossing vessels; ego is non-responsive, the others must avoid it |
118
+ | `multi_crossing_give_way.yaml` | the same with a responsive ego: give-way to two starboard crossers while stand-on to a port crosser |
119
+ | `multi_vessel.yaml` | four vessels, one non-responsive (paper Fig. 2) |
120
+ | `give_way_stop.yaml` | ego gives way to a close crosser from starboard while a vessel alongside blocks a starboard turn; stopping is the clean solution |
121
+
122
+ ### Changing a scenario
123
+
124
+ Copy a config in `scenarios/simulation/` and edit it. The vessels are listed
125
+ under `scenario.vessels`. The vessel named `ego` starts at the origin; every other
126
+ vessel is placed relative to it:
127
+
128
+ ```yaml
129
+ scenario:
130
+ dt: 0.5 # time step [s]
131
+ t_horizon: 75.0 # simulation length [s]
132
+ vessels:
133
+ ego:
134
+ speed: 5.0 # [m/s]
135
+ heading: 0.0 # [deg], 0 = north, 90 = east
136
+ policy: vo_analytic
137
+ sim_model: ct_decoupled_ar
138
+ intent: responsive
139
+ d_pass: 75.0 # desired passing distance [m]
140
+
141
+ target_1: # add vessels by adding blocks like this one
142
+ encounter: head_on # where it starts, relative to ego (see below)
143
+ range: 400.0 # distance from ego [m]
144
+ speed: 5.0
145
+ policy: vo_analytic
146
+ sim_model: ct_decoupled_ar
147
+ intent: non_responsive # holds its course and ignores all traffic
148
+ d_pass: 75.0
149
+ ```
150
+
151
+ - **Start position.** `encounter` and `range` place a vessel relative to ego:
152
+
153
+ | `encounter` | Start position and course |
154
+ |---|---|
155
+ | `head_on` | ahead, on the reciprocal course; `lateral_offset_m` shifts it sideways |
156
+ | `crossing_starboard` | on ego's starboard side, crossing to port |
157
+ | `crossing_port` | on ego's port side, crossing to starboard |
158
+ | `overtaking_ahead` | ahead, same course (ego overtakes), 50 m to starboard (`lateral_offset_m` overrides) |
159
+ | `overtaking_astern` | astern, same course (it overtakes ego), 50 m to starboard (`lateral_offset_m` overrides) |
160
+
161
+ Crossing vessels are timed to reach the crossing point together with ego. The
162
+ COLREGs roles are not taken from `encounter`: they are classified from the
163
+ vessel states at every step, as in the paper.
164
+ - **Responsiveness.** `intent: responsive` vessels follow their COLREGs
165
+ obligations. `intent: non_responsive` vessels keep their course whatever
166
+ happens. The filter has to discover this from their motion. Play around by setting different vessels as non_responsive to see how that changes the predictions.
167
+ A vessel planned by `predictor_dp` or `search_dp` is the own ship under our
168
+ control and always follows COLREGs, so it cannot be `non_responsive`; give a
169
+ non-responsive `ego` a plain policy such as `vo_analytic` (as in
170
+ `multi_crossing.yaml`).
171
+ - **Behavior.** Any VO parameter (see [Parameters](#parameters)) can be set per
172
+ vessel, e.g. `d_pass` or `course_budget_deg`. Names are checked, so a typo
173
+ gives an error.
174
+ - **Motion model.** `sim_model` sets how a vessel's motion is simulated:
175
+ `ct_decoupled_ar` (default in the configs), `ct_coupled_ar`, `viknes_ar`, `cv`,
176
+ or `milliampere` (see below).
177
+
178
+ ### milliAmpere own ship
179
+
180
+ `sim_model: milliampere` simulates a vessel with the 3DOF maneuvering model of
181
+ the milliAmpere autonomous ferry and its controllers (ported from liteAmpere
182
+ into `colregs_planner/motion_models/milliampere_model.py`). How the hull is driven depends
183
+ on the vessel's `policy`:
184
+
185
+ - `predictor_dp`: dynamic positioning on a moving reference. Every step the
186
+ predictor (top-1 hypothesis, the dashed line in the plot) is rolled out one
187
+ step from the current states, and the reference moves by the vessel's
188
+ predicted step and turns to its predicted course. The vessel is the own
189
+ ship under our control, so it is always responsive, not estimated. The DP law is
190
+ liteAmpere's with its damping moved onto the velocity error and the hull's
191
+ drag at the reference velocity fed forward, so the hull stays within
192
+ ~0.1 m of the reference instead of trailing it by 10–17 m.
193
+ - any VO policy (e.g. `vo_analytic`): the policy's desired velocity becomes
194
+ heading and speed commands for liteAmpere's course and speed controllers.
195
+ - `search_dp`: dynamic positioning along a plan chosen by search. Every
196
+ `replan_s` (default 5 s) the own ship scores every sequence of two 7.5 s
197
+ manoeuvres from {nominal, starboard 30°, starboard 60°, port 30°, half speed,
198
+ stop},
199
+ each rolled out with the prediction model while the other vessels react to
200
+ it, under the top-2 responsiveness hypotheses. The cost weighs passing
201
+ distance (with `bow_factor` × `d_pass`, default 2, required when crossing
202
+ ahead of a vessel; expected, and worst case over the hypotheses with at least 10 %
203
+ probability, so a stand-on own ship holds course until the filter actually
204
+ doubts the give-way vessel), COLREGs (starboard
205
+ in head-on and act early when giving way, both while the route course would
206
+ still pass inside `d_pass`, hold when standing on unless bound
207
+ to keep clear of another vessel, no port turns
208
+ toward a give-way encounter, alter course early when overtaking), deviation
209
+ from the route, manoeuvre effort,
210
+ time lost along the route, and switching away from a manoeuvre less than
211
+ one edge old. Code in
212
+ `colregs_planner/planner_api/manoeuvre_search.py`; tune with a per-vessel
213
+ `search: {depth, edge_s, plan_dt, horizon_s, top_k}` block.
214
+ A vessel being overtaken only costs a collision until the TCPA on the
215
+ route course is below the predictor's `tcpa_threshold`. From then on, the
216
+ passing distance also counts the straight-line CPA after the horizon.
217
+ While the route course would also pass inside the overtaking `d_pass`, a
218
+ plan must alter course away from the vessel on its first edge, not slow
219
+ down behind it. No edge may turn back toward it until the own ship is past.
220
+ The plot draws the own ship along its committed plan, with the others
221
+ reacting to it, and the console lists each plan made.
222
+
223
+ `--ego-policy` overrides ego's policy from the command line, for comparisons:
224
+
225
+ ```bash
226
+ python scripts/run_simulation.py --config scenarios/simulation/milliampere/crossing.yaml --ego-policy search_dp
227
+ ```
228
+
229
+ The model is identified for speeds up to about 2 m/s, so the ship-scale
230
+ scenarios are too fast for it. `scenarios/simulation/milliampere/` holds the
231
+ same scenarios with every length and speed scaled by 0.3 (ego at 1.5 m/s) and
232
+ ego on the milliAmpere model with `predictor_dp`:
233
+
234
+ ```bash
235
+ python scripts/run_simulation.py --config scenarios/simulation/milliampere/multi_vessel.yaml
236
+ python scripts/tools/scale_scenarios.py # regenerate them after editing the originals
237
+ ```
238
+
239
+ Because times are unchanged, horizons and TCPA thresholds carry over as they
240
+ are. Lengths that live outside the vessel blocks are scaled too: the COLREGs
241
+ classifier distances (`scenario.classifier: {d_u, dcpa_max, d_a}`, defaults
242
+ 150 m, 1500 m, 10 km) and the overtaking start offset (`lateral_offset_m`,
243
+ default 50 m for overtaking encounters).
244
+
245
+ The `prediction` block sets what is predicted and shown:
246
+
247
+ ```yaml
248
+ prediction:
249
+ enabled: true
250
+ horizon: 120.0 # prediction horizon [s]
251
+ use_responding_filter: true # estimate which vessels respond (2^N hypotheses)
252
+ top_k_hypotheses: 2 # number of most likely hypotheses drawn (1 = only the best)
253
+ ```
254
+
255
+ Plot options go under `viz`. `scenarios/viz_reference.yaml` lists all of them
256
+ with their defaults.
257
+
258
+ ## Real AIS data
259
+
260
+ The real-data experiments use AIS data from the Oslofjord (Moss–Horten). The data
261
+ is not in the repository; it is shared separately as a `handover` folder. Put it
262
+ in the repository root:
263
+
264
+ ```
265
+ handover/
266
+ Moss Horten Feb 2026 AIS/processed/ais_01.pkl, ais_04.pkl # encounter windows (test data)
267
+ nominal_library_jan_26.h5 # nominal track library
268
+ seacharts/moss_horten.gpkg # sea chart (optional)
269
+ ```
270
+
271
+ Run the three test windows from the paper:
272
+
273
+ ```bash
274
+ python scripts/run_AIS.py --config scenarios/real/window_079.yaml # live playback
275
+ python scripts/run_AIS.py --config scenarios/real/window_098.yaml
276
+ python scripts/run_AIS.py --config scenarios/real/window_054.yaml
277
+ ```
278
+
279
+ The sea-chart background needs `pip install -e ".[seachart]"`; without it the
280
+ plots have a plain background. `moss_horten.gpkg` is the Moss–Horten part of
281
+ Kartverket's depth data. For another area, download the full chart
282
+ (`Basisdata_0000_Norge_25833_Dybdedata_FGDB.gdb`) from Kartverket into
283
+ `seacharts/`, which is used when present, or crop it with
284
+ `scripts/tools/crop_seachart.py --bbox LAT_MIN LAT_MAX LON_MIN LON_MAX`. `scenarios/README.md` explains all config blocks.
285
+
286
+ To build the data yourself from the daily AIS files at
287
+ [Kystverket](https://ais.kystverket.no/):
288
+
289
+ ```bash
290
+ python scripts/process_raw_ais.py --data-dir "data/<test period>" --out "data/<test period>/processed/ais_01.pkl"
291
+ python scripts/build_nominal_library.py --data-dir "data/<library period>" --out data/nominal_library.h5
292
+ python scripts/tools/browse_encounters.py --data "data/<test period>/processed/ais_01.pkl" # find encounter windows
293
+ ```
294
+
295
+ and point `data.pkl_path` and `prediction.nominal.library_path` in the config to
296
+ the new files.
297
+
298
+ To use the predictor in your own code:
299
+
300
+ ```python
301
+ from colregs_planner.prediction.predictor import VesselPredictor
302
+ from colregs_planner.prediction.vo_rollout import VOAgentConfig
303
+
304
+ predictor = VesselPredictor(dt=2.0, H=300, use_responding_filter=True,
305
+ default_agent_config=VOAgentConfig(d_pass=100.0))
306
+ for states in stream: # dict: vessel id -> VesselState (NED)
307
+ p_responsive = predictor.update(states)
308
+ trajectories = predictor.predict()[0][0] # most likely hypothesis
309
+ ```
310
+
311
+ The full-horizon rollouts can run in parallel worker processes
312
+ (`colregs_planner/prediction/parallel.py`), with identical results:
313
+ `prediction.interaction.parallel_workers: N` in the real-data configs,
314
+ `prediction.parallel_workers: N` in the simulation configs, or
315
+ `VesselPredictor(..., parallel_workers=N)`. Live playback gains only when
316
+ `top_k_hypotheses > 1` (one worker per hypothesis); `--metrics` also runs the
317
+ rollouts of different frames in parallel, so it gains even with
318
+ `top_k_hypotheses: 1`. `parallel_workers: auto` picks this for you: one worker
319
+ per hypothesis live, all CPU cores but one for `--metrics`; it is the default
320
+ in both config types. Use 0 (serial) when debugging the rollout.
321
+
322
+ The planners use the same mechanism. A `search_dp` own ship in the simulation
323
+ gets a pool sized by `prediction.parallel_workers` (`auto`: one worker per
324
+ plan rollout, at most CPU cores − 1), and `PredictionService.evaluate_manoeuvres`
325
+ runs on the predictor's pool when the service config sets
326
+ `interaction.parallel_workers` (default 0 there). Results are the same either
327
+ way.
328
+
329
+ ## Planner API
330
+
331
+ `colregs_planner/planner_api` wraps the predictor for use as an information source inside
332
+ a larger system, e.g. an AI planner. `PredictionService.step()` runs the full
333
+ predictor cycle once per observation; the `get_*` queries then read the cached
334
+ result and return JSON-serializable dicts, so they can be called as often as
335
+ needed without re-running the prediction or disturbing the responding filters.
336
+
337
+ ```python
338
+ from colregs_planner.planner_api import PredictionService, TOOLS, call_tool
339
+
340
+ service = PredictionService.from_config("scenarios/real/window_079.yaml",
341
+ dt=2.0, ref_lat=REF_LAT, ref_lon=REF_LON)
342
+ for t, obs in stream: # {vessel_id: VesselState or {"north", "east", "speed", "course_deg"}}
343
+ service.step(obs, t=t)
344
+
345
+ service.get_encounters("ego") # roles, encounter type, range, DCPA/TCPA, from ego's view
346
+ service.get_responsiveness() # P(vessel follows its COLREGs obligations)
347
+ service.get_hypotheses() # responsiveness hypotheses + re-assigned roles
348
+ service.get_predicted_cpa("ego") # closest approach along the predicted trajectories
349
+ service.get_predicted_trajectory("257123000", hypothesis=0, resolution_s=30)
350
+ service.get_predicted_state("257123000", t=120)
351
+ service.get_snapshot() # everything at once
352
+ ```
353
+
354
+ `TOOLS` holds a JSON Schema definition for every query (Anthropic `tools=`
355
+ format; `tool_specs("parameters")` for frameworks that use that key), and
356
+ `call_tool(service, name, arguments)` dispatches a tool call, returning
357
+ `{"result": ...}` or `{"error": ...}`. Units: NED metres from the session
358
+ origin, degrees with 0 = north clockwise, m/s, seconds.
359
+
360
+ `python scripts/planner_api_demo.py` runs the API on a synthetic encounter
361
+ (no data needed); `--tools` prints the tool definitions.
362
+
363
+ Pass `own_id` for the own ship: its responsiveness is then fixed (it follows
364
+ COLREGs) instead of estimated, and the hypotheses only vary over the other
365
+ vessels. `service.evaluate_manoeuvre(own_id, course_change_deg, speed_factor,
366
+ duration_s)` predicts the closest approaches if the own ship starts that
367
+ manoeuvre now, with every other vessel reacting to it, under each hypothesis.
368
+
369
+ ### DP reference for an own ship
370
+
371
+ `ColregsDPPlanner` (`colregs_planner/planner_api/dp_planner.py`) is the interface for
372
+ running the planner in another vessel system: vessel states in, dynamic
373
+ positioning (DP) reference out. It runs the same pipeline as the simulator's
374
+ `search_dp` own ship: responsiveness estimated from the observations, COLREGs
375
+ roles from the classifier, a manoeuvre search every `replan_s`, and the own
376
+ ship's track along the chosen plan as the DP reference.
377
+
378
+ ```python
379
+ from colregs_planner.planner_api import ColregsDPPlanner, Route
380
+
381
+ planner = ColregsDPPlanner(
382
+ own_id = "ego",
383
+ route = Route(north=0.0, east=0.0, course_deg=0.0, speed=1.5), # straight leg
384
+ dt = 0.5, # interval between update() calls [s]
385
+ config = {"interaction": {"use_responding_filter": True, "top_k_hypotheses": 2,
386
+ "agent": {"d_pass": 30.0, "tcpa_threshold": 100.0}}},
387
+ classifier = {"d_u": 45.0, "dcpa_max": 450.0, "d_a": 3000.0},
388
+ parallel_workers = "auto", # worker processes for the rollouts
389
+ )
390
+ ref = planner.update(t, {"ego": own_state, "257123000": other_state, ...})
391
+ ref.north, ref.east, ref.heading_deg, ref.vn, ref.ve # DP reference at t
392
+ planner.reference(t_now) # sample the committed plan at any rate
393
+ planner.set_route(next_leg) # onto the next leg of the route
394
+ ```
395
+
396
+ States are `VesselState`s or dicts with `north`, `east`, `speed` and one of
397
+ `course_deg` / `heading_deg` / `psi`, in local NED metres. Call `update()` at a
398
+ fixed `dt` with every observed vessel, own ship included. Vessels slower than
399
+ 0.5 m/s (e.g. at anchor) are planned around as holding position. A replan
400
+ takes about 0.05–0.2 s on one process. `python scripts/dp_planner_demo.py` runs it
401
+ on a synthetic head-on encounter.
402
+
403
+ ## Parameters
404
+
405
+ The VO parameters and their defaults are the fields of `VOAgentConfig`
406
+ (`colregs_planner/prediction/vo_rollout.py`). They are set per vessel in the simulation
407
+ configs and under `prediction.interaction.agent` in the real-data configs.
408
+
409
+ | Parameter | Paper | Default | Meaning |
410
+ |---|---|---|---|
411
+ | `d_pass` | `D` | 100 m | desired passing distance |
412
+ | `los_lookahead` | `Δ` | 100 m | LOS lookahead back to the nominal track |
413
+ | `continuity_weight` | `λ` | 0.9 | penalty on changing the selected heading (Eq. 19) |
414
+ | `course_budget_deg` | | 70° | largest course change from the nominal course |
415
+ | `k_psi` | `k_ψ` | 0.1 s⁻¹ | turn-rate gain of the rollout motion model (Eq. 20) |
416
+ | `head_on_rvo_weight` | | 0.5 | share of the head-on maneuver each vessel takes (0.5 = RVO) |
417
+
418
+ The responding filter (`colregs_planner/prediction/responding_filter.py`) uses
419
+ `p(r₀=R) = 0.99` and `σ_ψ = 5°`. The COLREGs classifier
420
+ (`colregs_planner/colregs_graph/colregs_classifier.py`) uses `DCPA_max = 1500 m`. The nominal
421
+ predictor's `β`, search radius and history length are set under
422
+ `prediction.nominal` in the real-data configs.
423
+
424
+ ## Differences from the paper
425
+
426
+ - **VO solver.** In the paper, a vessel must keep the full passing distance `D`
427
+ from every vessel. When that is impossible, e.g. vessels already closer than
428
+ `D` or several vessels blocking each other, the paper's solver gives extreme
429
+ turns or sudden course switches. This solver instead keeps the largest passing
430
+ distance that can be reached within `course_budget_deg`. When several vessels
431
+ conflict, it reduces all passing distances together, the most for encounters
432
+ furthest ahead in time. Speed is never changed, so every maneuver is a course
433
+ change. See `solve_vo_achievable` in `colregs_planner/policies/vo_analytic.py`.
434
+ - **STAND-ON.** A responsive stand-on vessel still avoids a collision at the last
435
+ moment (COLREGs Rule 17(b)), with a small passing distance `d_coll` =
436
+ 0.1·`d_pass`. In the paper, STAND-ON has no constraint. Non-responsive vessels
437
+ ignore all traffic, as in the paper.
438
+
439
+ ## Repository layout
440
+
441
+ ```
442
+ scripts/
443
+ run_simulation.py simulations (scenarios/simulation/)
444
+ run_AIS.py real AIS playback, figures and metrics (scenarios/real/)
445
+ planner_api_demo.py planner API on a synthetic encounter
446
+ dp_planner_demo.py own-ship DP reference from vessel states (ColregsDPPlanner)
447
+ process_raw_ais.py raw AIS -> encounter windows
448
+ build_nominal_library.py
449
+ tools/ encounter browsing, sea-chart cropping, deviation analysis, profiling
450
+ colregs_planner/ the package
451
+ nominal/ nominal layer: track library and nearest-neighbor retrieval (Sec. III)
452
+ colregs_graph/ COLREGs classifier and interaction graph (Sec. IV-A, IV-B)
453
+ prediction/ responding filter, VO rollout, VesselPredictor (Sec. IV-C, V)
454
+ planner_api/ PredictionService queries and tool definitions for planners
455
+ policies/ VO solvers (vo_analytic: this work, vo_kuwata: Kuwata et al.), CTO
456
+ motion_models/ kinematic models used in simulation and rollout
457
+ vessel/, ais/, visualization/, seachart/
458
+ scenarios/ run configurations
459
+ ```
460
+
461
+ ## Development
462
+
463
+ `main` holds the code used for the paper. Make changes on your own branch and
464
+ open a pull request to merge them, instead of pushing to `main`:
465
+
466
+ ```bash
467
+ git switch main && git pull
468
+ git switch -c <your-name>/<topic> # e.g. anna/new-scenario
469
+ # ... edit, then commit
470
+ git push -u origin <your-name>/<topic>
471
+ ```