This repository implements a framework for simulating and evaluating autonomous ship collision avoidance (COLAV) control strategies. The initial framework prototype is described in the CCTA2023 paper. As of September 2023, the simulation framework has been wrapped to be compatible with Gymnasium and Stable Baselines, such that you can now use it as a gym for training RL-agents.
The main functionality is contained in the Simulator class of simulator.py, which runs through fully defined scenarios generated by the ScenarioGenerator, which takes in scenario configuration files in .yaml format (examples under scenarios/). One can visualize the results underway and output the results for use in downstream applications. These main modules have default configurations specified under config/, change according to your own needs. The seacharts package is used to provide usage of Electronic Navigational Charts for visualization and anti-grounding purposes. See the examples and tests for simple tutorials on how to use the repository. The rrt-rs package enables grounding-free trajectory planning and behavior generations for vessels involved in a simulation. Lastly, the vimmjipda package can be used for enabling Multi-Target Tracking in the simulation.
The COLAVEnvironment represents the Gymnasium compatible environment wrapper around the above mentioned functionality that can be used to train reinforcement learning agents. Many example action and observation types exist under this colav_simulator/gym umbrella.
The framework uses the North-East coordinate system with values in meters, speed in meters per second, angles in radians, and angular velocities in radians per second.
Developed as part of the Autoship Centre for Research-based Innovation (SFI), and tested under a Unix-based operating system (Linux, macOS). Open sourced in the autumn of 2025.

Example visualization of a DRL-based MPC algorithm run in multiple evaluation episodes using the COLAVEnvironment Gymnasium functionality.

Example run of an anti-grounding tracking NMPC controller in the simulator.
Downstream packages compatible with the colav-simulator for ship RRT and NMPC-based trajectory planning, target tracking and reinforcement learning include:
- psbmpc: https://github.com/ntnu-itk-autonomous-ship-lab/pybind_im_and_psbmpc
- rrt-rs: https://github.com/ntnu-itk-autonomous-ship-lab/rrt-rs.
- rlmpc: https://github.com/ntnu-itk-autonomous-ship-lab/rlmpc
- vimmjipda: https://github.com/ntnu-itk-autonomous-ship-lab/vimmjipda
Are all outlined in the pyproject.toml file. The git modules are the following:
- seacharts: https://github.com/trymte/seacharts for considering ENC grounding hazards and beautiful map visualizations.
- rrt-rs: https://github.com/ntnu-itk-autonomous-ship-lab/rrt-rs optionally for ship behavior generation.
- vimmjipda: https://github.com/ntnu-itk-autonomous-ship-lab/vimmjipda optionally for Multi-Target Tracking functionality.
Install uv (https://docs.astral.sh/uv/getting-started/installation/):
curl -LsSf https://astral.sh/uv/install.sh | shInstall project:
uv syncTo use seacharts in the simulator for map visualizations and grounding hazard considerations, you need to download .gdb files from https://kartkatalog.geonorge.no in UTM 32 or 33 (see https://github.com/trymte/seacharts for instructions), and put the unzipped .gdb-files e.g. in an enc_data/ folder of your choice. By default, the ScenarioGenerator will expand relative map data file paths to data/enc" unless absolute.
If you get troubles installing gdal, this might be due to:
- The native
gdallibrary not being installed, see e.g. OSGeo/gdal#2166 - It not being installed correctly. Try to install from source or fix the gdal-version (see e.g. https://stackoverflow.com/questions/34408699/having-trouble-installing-gdal-for-python or OSGeo/gdal#2827). An issue on the topic is found on trymte/seacharts#4.
- Incompatible python and gdal package versions.
To also install the rrt-star-lib and vimmjipda optional packages, do
uv sync --group optionalTest the installation by running any of the files under tests/, e.g.
uv run pytest tests/test_simulator.pyuse these and the examples to get familiar with the simulator.
If you are using the colav_simulator in your work, please use the following citation:
@Article{Tengesdal2023sfse,
author = {Trym Tengesdal and Tor A. Johansen},
journal = {7th IEEE Conference on Control Technology and Applications (CCTA)},
title = {Simulation Framework and Software Environment for Evaluating Automatic Ship Collision Avoidance Algorithms},
year={2023},
volume={},
number={},
pages={186-193},
doi={10.1109/CCTA54093.2023.10252863},
}If you are using RRTs from rrt-rs for ship behavior generation in your work based on A Comparative Study of Rapidly-exploring Random Tree Algorithms Applied to Ship Trajectory Planning and Behavior Generation, please also use the following citation:
@article{tengesdal2025comparative,
title={A Comparative Study of Rapidly-exploring Random Tree Algorithms Applied to Ship Trajectory Planning and Behavior Generation},
author={Tengesdal, Trym and Pedersen, Tom Arne and Johansen, Tor Arne},
journal={Journal of Intelligent \& Robotic Systems},
volume={111},
number={1},
pages={1--19},
year={2025},
publisher={Springer}
}The main branch shall always be working. This means that:
- All of its features/modules shall be properly documented through quality code + descriptive text where appropriate, and the CI pipeline should pass before any PR can be merged into main.
- The
ScenarioGeneratoris successfully able to generate any scenario from valid config files. - The
Simulatoris successfully able to run through any number of scenarios, either generated or loaded from file. Furthermore, it should be problem free to save and load scenarios to/from (valid) scenario files. - The
Visualizeris successfully able to visualize each of these scenarios live, if toggled on. - Subsystems added to the
Shipclass properly adheres to their interfaces (prefixed withIin front of the class name, e.g. interfaceIModelfor the ship model interface).
For students who use this repository for their project/master work, we want you to be free to experiment with the code. In order to enable this, such work shall be performed in branches prepended with project/<year>/. For example, if Per in 2023 is working on his master thesis creating a deep reinforcement learning-based trajectory tracking controller, he should do so by branching out from main into his own branch called e.g. project/2023/drl-controller. This makes browsing the branches later easier.
A lot of the work being done on code repositories is adding needed features, or improving the platform in general.
The branches used to develop these features shall be prepended with feature/, e.g.
feature/improved_ship_configuration.
It is also a good idea to keep the features small, so that they are easy to test, and can quickly be merged into main.
When you're developing a feature based on the simulator, the workflow begins by checking out the main branch, and creating a new branch from there.
cd colav_simulator
git checkout main
git pull
# If you need to rebase because you have uncommited (non-important) changes in your working directory, you can run:
git reset --hard origin/mainA new branch for new features is created like this:
git checkout -b feature/name_of_featureNow that you have a separate branch, you can code, commit, run experiments and cooperate in that branch.
# Do some work
git add <files>
git commit
git push -u origin feature/name_of_feature # -u flag with origin only needed first time to set upstream.After doing some development and thorough testing, it might be time to merge the feature into the main branch.
Before doing that, make sure that all checkpoints under the heading Main branch are satisfied.
To make sure new issues don't arise when merging into main later (because of other changes to main), it is important to merge main into feature/name_of_feature first:
git checkout main # make sure we have the latest
git pull # version of main locally
git checkout feature/name_of_feature
git pull # in case you or someone else made changes remotely
git merge main # This attempts to merge `main` into `feature/name_of_feature`
# At this point conflicts might arise which you have to solve.Now you can perform the tests.
After the tests have been completed, you can initiate a pull request on github, where eventually the changes will be merged to main.
Go to the colav_simulator repo on https://github.com and navigate to your branch (feature/name_of_feature).
In the status bar above the commit message there is a link for Pull request.
Click that and describe your feature.
Assign people to it to review the changes, and create the pull request.
The pull request should now be reviewed by someone, and if it is ok, it will be merged into main.
Each main module mostly have their own test files, to enable easier debug/fixing and also for making yourself familiar with the code. When developing new modules, you are encouraged to simultaneously develop test files for these, such that yourself and others more conveniently can fix/debug and test the modules separately.
A core concept is the usage of Cerberus for configuration validation, where the validation schemas are used to verify configuration keys and fail fast if erroneous settings are provided. Furthermore, another core concept is the usage of dataclasses for keeping parameters and configuration objects. Each scope (models, guidances, ship, etc..) have its own Config object containing its local configuration parameters. This leads to better readability of the code by keeping all aspects of a given subsystem in its own scope, as opposed to passing global configuration objects everywhere. Dataclasses also increases readability and reduces bugs related to specifying wrong dictionary string keys that would happen when only using dictionaries for configuration settings.
The following describes the main modules superficially. Rely mainly on the code itself for the documentation.
Gymnasium-wrapper for the simulation framework. See the source code for available rewards, actions and observations that can be used.
The simulator runs through a set of scenarios, each consisting of n_episodes specified from the config, visualizes these and saves the results. The scenarios can be generated through a ScenarioConfig object, or loaded from file using an existing scenario definition (examples under scenarios/).
It is recommended to pass the COLAV planning algorithm you want to test on the own-ship at run-time to the run(...) function in the simulator (see its documentation and an example for this in tests/test_simulator.py). The planning algorithm must inherit/adhere to the ICOLAV-interface under colav_simulator/core/colav/colav_interface.py (same principle goes for all the Ship subsystems).
The simulator is configured using the simulator.yaml config file.
The scenario generator is used by the simulator to create new scenarios for COLAV testing with 1+ ships. The main method is the generate() function, which generates a scenario from a scenario config file, which is converted into a ScenarioConfig object. An Electronic Navigational Chart object (from Seacharts) is used to define the environment. The n_episodes (defaults to 1) parameter is used to facilitate Monte Carlo simulation when using random data, and one can use an EpisodeGenerationConfig for further MC specifications.
Scenarios are configured through a scenario .yaml file as e.g. the example head_on.yaml file under scenarios in the root folder. Look at predefined scenario files and the schemas folder under the package source code for further clues constraints/tips on how to write a new scenario config files.
Seacharts is used to provide access to Electronic Navigational Charts, and an ENC object is used inside the ScenarioGenerator class for this. One must here make sure that the seacharts package is properly setup with .gdb data in the data/external folder of the package, with correctly matching UTM zone for the chart data. An example default seacharts.yamlconfig file for the module is found under config/. One can specify map data, map origin, map size etc. for the ENC object from the scenario .yamlconfig file.
Troubles with "freezing" when you generate a scenario? Check if you have specified new_load_of_map_data=Truein the scenario configuration file. If this is false, and the map data is not loaded/wrong data is used, errors can happen.
In addition to random waypoint generation and/or straight line motion generation for the own-ship and/or target ships through the BehaviorGenerator class, the rrt-rs library can optionally be used for generating random ship behaviors, where Rapidly-exploring Random Trees (RRTs) are built for each ship initial state. See the source code for the BehaviorGenerator for more information.
NOTE: The random generation of scenarios (poses, waypoints, speed plans etc.) are not guaranteed to succeed every time, especially since a finite number of iterations are considered in sampling procedures (for time and robustness reasons). Many edge cases can occur that makes a particular ship-ship encounter scenario unrealistic or not interesting. This makes it important that you check the generated scenarios.
Class responsible for visualizing scenarios run through by the Simulator, and visualizing/saving the results from these. A basic live plotting feature when simulating scenarios is available. The class can, as most other main modules, be configured from the example simulator configuration file under config/.
Note that the visualizer uses Matplotlib, and scales badly with a large number of vessels and plot data. Use with care. See Roadmap below.
The Ship class simulates the behaviour of an individual ship and must adhere to the IShip interface, which necessitates that the ship class provides among others, a:
forward(self, dt: float, w = Optional[DisturbanceData] = None) -> Tuple[np.ndarray, np.ndarray, np.ndarray]function that allows simple forward simulation of the vessel, with disturbance consideration if data is available. Returns the new state, inputs to get there and the references used.plan(self, t: float, dt: float, do_list: List[Tuple[int, np.ndarray, np.ndarray, float, float]], enc: Optional[ENC] = None, w: Optional[DisturbanceData] = None) -> np.ndarrayfunction that plans a new trajectory/generates new references for the ship, either using the guidance system or COLAV system. A list of dynamic obstacle data, possibly Electronic Navigational Chart (ENC) object and disturbance information can be used by the planner.track_obstacles(self, t: float, dt: float, true_do_states: List[Tuple[int, np.ndarray, float, float]]) -> Tuple[List[Tuple[int, np.ndarray, np.ndarray, float, float]], List[Tuple[int, np.ndarray]]]:function that tracks nearby dynamic obstacles.
Standardized input/output formats are used for the interfaces to make the code for each subsystem easy to switch in/out. For the Guidance, Navigation and Control (GNC) system, we consider interface input/output dimensions based on a typical 3DOF surface vessel model (see Fossen, 2011 for reference):
- State of dimension
nx = 6x 1 consisting of[x, y, psi, u, v, r]^Tfor the typical 3DOF model case (^Tfor transposed vectors). For kinematic models or other models with reduced state dimension, the first entries are filled out. For the kinematic model in the framework this equates to setting the state as[x, y, chi, U, 0, 0]^Twherechiis the course over ground andUthe speed over ground. - Inputs of dimension
nu = 3x 1 consisting of the generalized forces and moments[X, Y, N]^Tfor the 3DOF ship model. When using e.g. a kinematic model that has different state and input dimensions, the ship controller will nominally just feed references (typically in course and speed) directly forward to the ship model (Must be configured. See below for examples). - References are of dimension 9 x N, where N is the number of trajectory samples, consisting of 3DOF reference poses, velocities and accelerations in general. E.g. for LOS-guidance the references will be populated as
[0, 0, chi_ref, U_ref, 0, 0, 0, 0, 0]^Tand passed to the ship low-level controller.
If you need to implement a model which has state, input or reference dimensions larger than the aforementioned, make an issue on the topic and pull request with the necessary interface changes.
The ship can be configured to use different combinations of collision avoidance algorithms, guidance systems, controllers, estimators, sensors, and models. The key element here is that each subsystem provides a standard inferface, which any external module using the subsystem must adhere to. See the source code and test files for more in depth info on the functionality. Check out the ship_list entry under the schemas/scenario.yaml to get a clue on what you can configure for the ship. NOTE: The own-ship must always have ID=0 and is always the first ship to configure under ship_list.
If you want to expand the Ship object by adding support for a new model, controller, guidance law etc., a rough recipe is the following (in this example for a new model, but the procedure will be the same for any subsystem or module in the framework):
- 1: Implement the model under
core/models.pyor in your own repository, inherit from theIModelinterface and implement the required interface functions. - 2: Add a parameter class for the model, and add this parameter class entry to the
models.Configclass to add support for parsing the model from configuration files. This also entails that you implement thefrom_dict(config_dict: dict) -> ModelParamsandto_dict() -> dict:functions, to allow for easy parsing of dictionaries into dataclasses. However, note that some of the implemented ship models existing in the framework have fixed and non-configurable parameters. In this case, the configuration entry scheme for the model is an empty string (see e.g. forViknesundermodelsin the scenario schema). - 3: Use the new model parameter class as input to the new model object constructor.
- 4: Add support for the new model by adding its parameter object class to the
modelspart of theschemas/scenario.yamlfiles undership_list. - 5: Test the new model in a scenario where you specify the model parameters in a scenario configuration file, or directly during run-time (as in
test_ship.py).
In all these steps, adhere to the used code style and docstring format.
Some common configurations of the ship subsystems are detailed below.
In case you are employing an RL-agent through the COLAVEnvironment, the remote_actor parameter inside the simulator step(..)-function is set to true. In this case, the RL-agent sets the ship references (pose, velocity and acceleration / inputs) externally.
The scenarios/head_on.yaml scenario contains a typical GNC/autopilot-configuration of the own-ship, where LOS-guidance (given waypoints and a speed plan) provides course and speed references to a low-level controller (in this case a feedback-linearizing surge-heading controller).
The ship plan step will then only entail that the configured guidance object LOS algorithm computes course+speed references, which are provided to the onboard controller during the forward call. The onboard controller then computes the required force vector to track the reference setpoints.
The scenarios/ais_scenario1.yaml scenario contains a typical GNC/autopilot-configuration of the own-ship, where LOS-guidance (given waypoints and a speed plan) provides course and speed references that are directly passed through to a simple kinematic ship model. Here, a PassThroughCS "controller" is used to allow for passing the guidance system course and speed references straight through the controller step and directly to the ship model.
The ship plan step will then only entail that the configured guidance object LOS algorithm computes course+speed references, which are provided to the onboard controller and directly forwarded during the forward call. The ship model will then directly use the guidance system references as inputs.
In case you want to develop a motion planning algorithm that provides low-level inputs (e.g. generalized force inputs) to the ship instead of the standard 9-entry pose, velocity, acceleration reference format, you can specify a PassThroughInputs type of controller. This "controller" essentially lets the input references (which are now low-level inputs from your algorithm) go straight through, such that the controller is in practice disabled. An example of this is found in the scenarios/simple_planning_example.yaml, where a rudder-propeller mapping with a specificed lever arm is used to map forces in x and y to include the yaw moment as well.
The ship plan step will then entail that you use your wrapped colav system, that provide references that are low-level inputs. When these are passed to the controller object during the forward call, they will pass straight through and go into the ship model object.
If you want to test your planning algorithm with perfect knowledge on nearby vessels, you can specify the GodTracker to be used under tracker in the scenario configuration file. As this object has no parameters, the configuration entry is an empty string god_tracker: '' (see schemas/scenario.yaml for clues).
The standard support for target tracking in the simulator is to use a Kalman Filter for estimating the states of nearby vessels. Most of the scenario files have examples on how to configure this tracker. Tune the measurement covariance (R) through the sensor configuration, and adjust the scenario configuration based on whether or not you want to consider AIS-measurements, Radar-measurements or both. See the tests/test_simulator.py for example usage and tests/test_radar.py for the Radar class functionality.
An interface at https://github.com/ntnu-itk-autonomous-ship-lab/vimmjipda exist for coupling the Visibility Interacting Multiple Models Joint Integrated Probabilistic Data Association (VIMMJIPDA) Multi-Target Tracker (MTT) with the simulator, and can be used with the Radar sensor (no AIS consideration implemented yet) for tracking multiple ships with simple clutter noise. See the tests/test_simulator_jipda.pyfor example usage.
The colav_interface.py provides an interface for arbitrary COLAV planning algorithms and hierarchys within. See the file for examples/inspiration on how to wrap your own COLAV-planner to make it adhere to the interface. Alternatively, you can provide your own COLAV system through the ownship_colav_system input to the simulator run(.) function. In any case, the COLAV algorithm should adhere to the ICOLAV interface (see colav_interface.py). This enables the usage of both internally developed COLAV planners in addition to third-party ones.
- Mandate unittesting of all modules and their core functionality.
- Improve random generation of vessel COLREGS scenarios. E.g. use AIS data to sample "realistic" vessel trajectories based on a fitted distribution for historical vessel positions and velocities.
- Improve live-visualization in the simulator w.r.t. code readability and run-time. Switch out matplotlib for a faster backend. Matplotlib is known to leak memory, so switching it for e.g. PyGtGraph https://www.pyqtgraph.org/ or VisPy https://vispy.org/ is promising.
- Add functionality for saving simulation results to file.
- Separate the large
schemas/scenario.yamlvalidation schema into multiple sub-schemas for easier readability. - Create IsaacGym-wrapper for GPU-enabled parallelized RL in the environment.