Ship-Ship Collision Calculations¶
This chapter describes OMRAT’s calculations for ship-ship collision risk. Four types of collision encounters are modelled: head-on, overtaking, crossing, and bend collisions, all based on the equations in Hansen (2008).
Overview¶
Ship-ship collisions occur when two vessels occupy the same space at the same time. The IWRAP methodology models this by calculating the geometric number of collision candidates – the expected number of close encounters per year assuming no evasive action – and then multiplying by a causation factor to get the actual accident frequency.
The general formula is:
Where:
\(N_G\) = geometric collision candidates per year
\(P_C\) = causation probability (IALA default values)
Pipeline orchestrator: compute/ship_collision_model.py:526 – run_ship_collision_model()
Head-On Collisions¶
Head-on collisions occur between vessels travelling in opposite directions on the same leg. This is the classical encounter type for narrow shipping lanes.
Geometry¶
Two streams of traffic travel in opposite directions. The collision probability depends on the lateral overlap of their position distributions.
Equations (Hansen Eq. 4.2–4.4)¶
The geometric number of collision candidates is:
Where:
\(Q_1, Q_2\) = traffic volume in each direction (ships/year)
\(V_1, V_2\) = vessel speeds (m/s)
\(V_{ij} = V_1 + V_2\) = relative closing speed (m/s)
\(L_w\) = leg segment length (m)
\(P_G\) = geometric collision probability
The term \(Q/V\) converts from traffic frequency (ships/year) to traffic density (ships/m), accounting for the time ships spend on the leg.
The geometric collision probability is:
Where:
\(\mu_{ij} = \mu_1 + \mu_2\) – combined mean lateral distance (head-on: positions add because ships face opposite directions)
\(\sigma_{ij} = \sqrt{\sigma_1^2 + \sigma_2^2}\) – combined standard deviation
\(B_{ij} = (B_1 + B_2) / 2\) – average vessel breadth (collision width)
\(\Phi\) = standard normal CDF
compute/basic_equations.py:46 – get_head_on_collision_candidates()
# Matching them lets us bypass scipy's frozen-distribution dispatch (~2 ms
# per call) and use C-level ``ndtr`` / a direct Weibull formula.
_NORM_FUNC_RE = re.compile(
r"\.norm\(loc\s*=\s*(?P<loc>-?\d+(?:\.\d+)?)\s*,\s*scale\s*=\s*(?P<scale>-?\d+(?:\.\d+)?)\s*\)\s*\.cdf\(x\)"
)
_WEIBULL_FUNC_RE = re.compile(
r"\.weibull_min\(c\s*=\s*(?P<c>-?\d+(?:\.\d+)?)\s*,\s*loc\s*=\s*(?P<loc>-?\d+(?:\.\d+)?)\s*,\s*scale\s*=\s*(?P<scale>-?\d+(?:\.\d+)?)\s*\)\s*\.cdf\(x\)"
)
def _repair_fn(data: dict, drift_speed: float):
"""Return a callable ``f(distance) -> P(not repaired)`` for *data*.
Caches the compiled scalar distribution callable, so the hot per-edge /
per-ship call sites don't re-parse strings or re-create distribution
objects. For the two dominant shapes (``norm.cdf(x)`` /
``weibull_min.cdf(x)``) we bypass scipy's frozen-distribution machinery
and call ``scipy.special.ndtr`` or a direct Weibull formula -- scipy's
generic ``.cdf`` has ~2 ms of Python-level dispatch per call, and
``get_not_repaired`` runs tens of thousands of times per cascade.
"""
key: tuple
if data.get('use_lognormal') == 1:
key = ('lognorm', float(data['std']), float(data['loc']),
float(data['scale']), float(drift_speed))
else:
key = ('func', str(data.get('func', '')), float(drift_speed))
fn = _REPAIR_FN_CACHE.get(key)
if fn is not None:
return fn
if data.get('use_lognormal') == 1:
# lognorm.cdf(x; s, loc, scale) = ndtr(log((x-loc)/scale) / s) for x>loc
s = float(data['std'])
loc = float(data['loc'])
scale = float(data['scale'])
def fn(distance: float, _s=s, _loc=loc, _scale=scale, _spd=drift_speed) -> float:
drift_time = distance / _spd / 3600.0
t = drift_time - _loc
if t <= 0 or _scale <= 0:
return 1.0
Default causation factor¶
Overtaking Collisions¶
Overtaking collisions occur between vessels travelling in the same direction at different speeds. The faster vessel catches up to the slower one.
Geometry¶
Both vessels travel in the same direction. The collision probability depends on the speed difference (overtaking is only possible if \(V_{\text{fast}} > V_{\text{slow}}\)) and the lateral overlap of their distributions.
Equations (Hansen Eq. 4.5)¶
The formula is structurally identical to head-on, but with different relative speed and lateral offset:
Note
If \(V_{\text{fast}} \leq V_{\text{slow}}\), overtaking is impossible and the result is zero.
The geometric probability \(P_G\), combined standard deviation \(\sigma_{ij}\), and breadth \(B_{ij}\) are calculated identically to the head-on case.
compute/basic_equations.py:147 – get_overtaking_collision_candidates()
Default causation factor¶
Crossing Collisions¶
Crossing collisions occur at intersections where two legs cross at an angle \(\theta\). This is common at traffic separation scheme junctions, port approaches, and areas where multiple routes converge.
Geometry¶
Two traffic streams cross at angle \(\theta\). The collision zone is an area around the intersection point whose size depends on vessel dimensions and the crossing angle.
Equations (Hansen Eq. 4.6)¶
Where:
\(\theta\) = crossing angle (radians)
\(D_{ij}\) = collision diameter (metres)
\(V_1, V_2\) = vessel speeds on each leg
The relative speed uses the law of cosines:
The collision diameter accounts for the projected area of both vessels at the crossing angle:
Where \(L_1, L_2\) are ship lengths and \(B_1, B_2\) are ship beams.
Note
When \(\theta \to 0\) or \(\theta \to \pi\) (parallel or anti-parallel courses), \(\sin\theta \to 0\) and the crossing formula is not applicable. Use head-on or overtaking formulas instead.
compute/basic_equations.py:238 – get_crossing_collision_candidates()
def get_head_on_collision_candidates(
Q1: float, # Traffic in direction 1 (ships/year)
Q2: float, # Traffic in direction 2 (ships/year)
V1: float, # Speed direction 1 (m/s)
V2: float, # Speed direction 2 (m/s)
mu1: float, # Mean lateral position dir 1 (m)
mu2: float, # Mean lateral position dir 2 (m)
sigma1: float, # Std dev lateral position dir 1 (m)
sigma2: float, # Std dev lateral position dir 2 (m)
B1: float, # Beam of vessel type 1 (m)
B2: float, # Beam of vessel type 2 (m)
L_w: float # Leg segment length (m)
) -> float:
"""
Calculate geometric number of head-on collision candidates.
Uses Hansen Eq. 4.2-4.4:
N_G = Q1 × Q2 × V_ij × P_G × L_w
Where:
- V_ij = V1 + V2 (relative closing speed for head-on)
- P_G = Φ((μ_ij + B_ij)/σ_ij) - Φ((μ_ij - B_ij)/σ_ij)
- μ_ij = μ1 + μ2 (mean lateral distance between vessels)
- σ_ij = √(σ1² + σ2²) (combined standard deviation)
- B_ij = (B1 + B2)/2 (average vessel breadth)
Parameters
----------
Q1 : float
Traffic in direction 1 (ships/year)
Q2 : float
Traffic in direction 2 (ships/year)
V1 : float
Speed direction 1 (m/s)
V2 : float
Speed direction 2 (m/s)
mu1 : float
Mean lateral position dir 1 (m)
mu2 : float
Mean lateral position dir 2 (m)
Default causation factor¶
How OMRAT distributes traffic between legs at a junction¶
A common question from new users:
“At a crossing or merging point, how does OMRAT decide which fraction of leg A’s traffic continues to leg B vs leg C?”
Short answer: it doesn’t. OMRAT does not model a routing distribution at junctions. Each leg has its own independent traffic table, and crossing collisions are computed pairwise from \(Q_1 \times Q_2\) for every pair of legs that share a waypoint.
That means the user is responsible for keeping leg traffic counts consistent:
If 1000 ships/year transit a fork and the user expects 60 % to take leg B and 40 % to take leg C, the Frequency (ships/year) value on leg A must be
1000, on leg B600, on leg C400— entered manually on the Traffic tab (or split by AIS in the user’s pre-processing).If you populate traffic from the AIS database (
pbUpdateAIS), OMRAT samples passages from the AIS table that intersect each leg’s passage line independently. A ship that AIS shows transiting both leg A and leg B contributes+1to leg A’s count and+1to leg B’s count — i.e. AIS already supplies a self-consistent distribution as a side-effect of how passages are counted.
The crossing-collision contribution between leg \(i\) and leg \(j\) is therefore:
with no extra weighting term for “the share of \(Q_i\) that continues toward leg \(j\)”. The same applies to merging collisions (treated as crossings with a small angle) and to bend collisions on a single leg (no neighbouring-leg contribution).
compute/ship_collision_model.py:_calc_crossing_collisions
iterates every pair (leg_i, leg_j) and skips pairs that don’t
share a waypoint or are nearly parallel (crossing_angle < 0.1
rad).
Bend Collisions¶
Bend collisions occur at waypoints where a route changes direction. A vessel that fails to make the turn continues on its original heading and may collide with vessels that did turn correctly.
Geometry¶
At a waypoint, the route changes direction by angle \(\theta\). Most vessels turn correctly, but a small fraction \(P_0\) (typically 1%) fail to turn and continue straight.
Equations¶
The bend collision is then modelled as a crossing collision between the non-turning traffic and the turning traffic:
Where \(\theta\) is the bend angle (change in heading at the waypoint) and the crossing collision formula is applied with self-interaction (same ship type on both “legs”).
compute/basic_equations.py:331 – get_bend_collision_candidates()
Default causation factor¶
Default probability of not turning:
Calculation Workflow¶
The collision calculation for each leg proceeds as follows:
Extract traffic data: For each segment, extract all ship types, frequencies, speeds, and dimensions in both directions.
Pair ship types: For head-on and overtaking, pair every ship type \(i\) in direction 1 with every ship type \(j\) in direction 2 (or same direction for overtaking).
Calculate per-pair N_G: Apply the appropriate formula based on encounter type, using the specific vessel speeds, beams, and lateral distributions.
Sum over all pairs: The total geometric candidates is the sum over all ship type pairs.
Apply causation factor: Multiply by \(P_C\) to get accident frequency.
Display results: Results are shown in the UI as annual accident frequencies per collision type.
Summary of Equations¶
Type |
Relative Speed |
Lateral Offset |
Key Parameter |
|---|---|---|---|
Head-on |
\(V_1 + V_2\) |
\(\mu_1 + \mu_2\) |
Opposite directions |
Overtaking |
\(V_f - V_s\) |
\(\mu_f - \mu_s\) |
Same direction, \(V_f > V_s\) |
Crossing |
\(\sqrt{V_1^2+V_2^2-2V_1V_2\cos\theta}\) |
\(D_{ij}\) |
Crossing angle \(\theta\) |
Bend |
(crossing formula) |
(crossing formula) |
\(P_0 = 0.01\) |