Code Flow: Ship-Ship Collision Model¶
This chapter walks the ship-ship collision model one function at a time, in the order the calls fire when a user presses Run Model. Pair it with Ship-Ship Collision Calculations, which derives the four collision formulas (head-on, overtaking, crossing, bend).
Entry point¶
ShipCollisionModelMixin.run_ship_collision_model() is phase 2 of
CalculationTask (see
Code Flow: From “Run Model” to Results). It takes the same data dict as the other
phases and writes
self.ship_collision_prob– total frequency (accidents/year)self.collision_report– per-leg + per-type breakdownLEPHeadOnCollision/LEPOvertakingCollision/LEPCrossingCollision/LEPMergingCollisionline-edit text
Entry: compute/ship_collision_model.py:526 – run_ship_collision_model()
Top-level call tree¶
run_ship_collision_model(data)
|
+-- for each leg:
| _calc_head_on_collisions(...)
| +-- _get_weighted_mu_sigma(seg_info, dir)
| | +-- get_distribution(seg_info, dir) # compute/data_preparation.py
| +-- get_loa_midpoint / estimate_beam
| +-- get_head_on_collision_candidates(...) # compute/basic_equations.py
|
| _calc_overtaking_collisions(...)
| +-- _get_weighted_mu_sigma(...)
| +-- get_overtaking_collision_candidates(...)
|
| _calc_bend_collisions(...)
| +-- get_bend_collision_candidates(...)
|
+-- _calc_crossing_collisions(...) # across leg pairs
| +-- _parse_point / _points_match / _calc_bearing
| +-- get_crossing_collision_candidates(...)
|
+-- aggregate into result dict
+-- update LEP* line-edits
Unlike the drifting model, the ship-collision phase has no geometric
precompute step. All heavy numerics live inside four pure-math
functions in compute.basic_equations:
get_head_on_collision_candidates(),
get_overtaking_collision_candidates(),
get_crossing_collision_candidates(),
get_bend_collision_candidates().
Everything in the mixin is bookkeeping around those.
Data pulled from data¶
Field |
Use |
|---|---|
|
Per-LOA, per-type frequency matrix. Same structure as the drifting model uses. |
|
Per-cell speed. A cell can hold a scalar or a list; the code averages lists. |
|
Per-cell beam. Falls back to |
|
Lateral distribution parameters used by the head-on and
overtaking formulas. |
|
Used only when it exceeds 5 degrees; drives the bend-collision formula. |
|
Used by crossing-collision geometry to find waypoints and crossing angles between pairs of legs. |
|
Causation factors. The per-type geometric candidate count is multiplied by these to get the accident frequency. |
|
Defines the LOA bins ( |
run_ship_collision_model(): orchestrator¶
Source: compute/ship_collision_model.py:526 – run_ship_collision_model()
Step by step:
Short-circuit if
traffic_dataorsegment_datais empty; set totals to zero and return.Read causation factors from
data['pc'](default to IALA recommended values).For every leg (outer loop is in this function), call three per-leg helpers:
_calc_head_on_collisions()- same-leg, opposite-direction._calc_overtaking_collisions()- same-leg, same-direction with different speeds._calc_bend_collisions()- same-leg geometric bend.
Each helper returns a scalar frequency for that leg. Progress is reported at 80 % of the
spatialphase because crossing collisions come next and still need the remaining 20 %.Call
_calc_crossing_collisions()once for the fullleg_keyslist - it iterates every leg pair and looks for shared waypoints. Progress is reported inside the helper (cascadephase).Build the result report:
self.collision_report = { 'totals': result, # head_on, overtaking, crossing, bend, total 'by_leg': by_leg, # per leg dict of head_on / overtaking / bend 'causation_factors': {...}, }
Write the four result line-edits in one block, swallowing
Exceptionbecause Qt widgets can disappear mid-run in pytest contexts.
_calc_head_on_collisions()¶
Source: compute/ship_collision_model.py:138 – _calc_head_on_collisions()
Structure:
Fetch direction 1 and direction 2 cells (
leg_dirs[dir1]/leg_dirs[dir2]). If only one direction exists, head-on is zero.Extract per-cell
freq,speed,beamarrays. For a cell that holds a scalar speed, that scalar is used; for a list/array, the mean is used.Get
(mu1, sigma1)and(mu2, sigma2)via_get_weighted_mu_sigma().Double loop over every
(loa_i, type_j)cell in direction 1 and every(loa_k, type_l)cell in direction 2. For each pair:n_g = get_head_on_collision_candidates( Q1=q1, Q2=q2, V1=v1_ms, V2=v2_ms, mu1=mu1_lat, mu2=mu2_lat, sigma1=sigma1_lat, sigma2=sigma2_lat, B1=b1, B2=b2, L_w=leg_length_m, ) leg_head_on += n_g * pc_headon
The nested loop runs per leg; for a busy leg with 21 ship types x 5 LOA bins per direction there are ~10 k pairs, each a handful of arithmetic ops. No shapely.
The core formula is in
compute.basic_equations.get_head_on_collision_candidates(). See
Ship-Ship Collision Calculations for the math.
_calc_overtaking_collisions()¶
Source: compute/ship_collision_model.py:239 – _calc_overtaking_collisions()
Iterates each direction independently (overtaking is same-direction by definition). For each direction:
Flatten every non-zero cell into a
ship_cellslist of(loa, type, freq, speed_ms, beam).Double loop over pairs
(fast, slow)wherev_fast > v_slow. The slower ship’s cell and the faster ship’s cell are passed toget_overtaking_collision_candidates()along with(mu_ot, sigma_ot)= the direction’s weighted lateral distribution.Multiply by
pc.overtakingand accumulate intoleg_overtaking.
_calc_bend_collisions()¶
Source: compute/ship_collision_model.py:313 – _calc_bend_collisions()
Bend collisions model a ship that fails to turn at the leg’s downstream waypoint and continues straight.
Loop the leg’s direction cells to accumulate an average frequency (
avg_freq) and average length / beam (avg_length,avg_beam). The averaging is a simple running mean weighted by non-zero cell count.Read
segment_data[leg]['bend_angle']. If <= 5 degrees, return 0 (no meaningful bend).Otherwise, call
get_bend_collision_candidates():n_g = get_bend_collision_candidates( Q=avg_freq, P_no_turn=0.01, L=avg_length, B=avg_beam, theta=bend_angle_rad, )
P_no_turnis hard-coded at 0.01 (IALA default).Multiply by
pc.bendand return.
_calc_crossing_collisions()¶
Source: compute/ship_collision_model.py:367 – _calc_crossing_collisions()
This is the only function that does cross-leg geometry.
Outer double loop over every
(leg1, leg2)pair withj > ito avoid double-counting.Parse each leg’s
Start_PointandEnd_Pointvia_parse_point().Short-circuit unless the two legs share a waypoint (
_points_match()comparing start/end pairs). This is the cheap filter that keeps the O(L^2) loop tractable.Compute each leg’s bearing from the stored
bearingfield or from_calc_bearing(start, end)().Crossing angle =
abs(bearing1 - bearing2)reduced to[0, 90]. Angles below ~0.1 rad are treated as parallel and skipped.For every
(dir1_cell, dir2_cell)pair on the two legs, callget_crossing_collision_candidates():n_g = get_crossing_collision_candidates( Q1=q1, Q2=q2, V1=v1_ms, V2=v2_ms, L1=l1, L2=l2, B1=b1, B2=b2, theta=crossing_angle_rad, )
and multiply by
pc.crossingbefore accumulating.Report progress (
cascadephase) after every pair.
The pure-math helpers¶
All collision math lives in compute.basic_equations. Each
helper is a short numpy expression with no external dependencies.
Source: compute/basic_equations.py
Function |
Formula (see Ship-Ship Collision Calculations for derivation) |
|---|---|
|
\(N_G = \frac{Q_1}{V_1}\frac{Q_2}{V_2} V_{ij} P_G L_w / \text{s/yr}\), with \(P_G\) the Gaussian lateral-overlap probability. |
|
Same structure but \(V_{ij} = |V_\mathrm{fast} - V_\mathrm{slow}|\) and only pairs with fast > slow contribute. |
|
\(N_G = \frac{Q_1 Q_2}{V_1 V_2} \frac{D}{\sin\theta} (L_1 + L_2 \sin\theta + B_1 + B_2 \cos\theta) / \text{s/yr}\). |
|
\(N_G = Q \cdot P_\mathrm{no\_turn} \cdot (L + B \tan(\theta))\), simplified from Hansen for a single-leg bend. |
sec/year = \(365.25 \times 24 \times 3600\).
Output¶
After _calc_crossing_collisions() returns, the orchestrator
assembles:
result = {
'head_on': total_head_on,
'overtaking': total_overtaking,
'crossing': total_crossing,
'bend': total_bend,
'total': sum of all four,
}
self.ship_collision_prob = result['total']
self.collision_report = {
'totals': result,
'by_leg': {leg: {'head_on': ..., 'overtaking': ..., 'bend': ...}, ...},
'causation_factors': {...},
}
The four line-edits (LEPHeadOnCollision, LEPOvertakingCollision,
LEPCrossingCollision, LEPMergingCollision) are set with
f"{result[...]:.3e}". collision_report is the input to the
ship-collision Markdown report written later by
DriftingReportMixin (despite the name, the mixin writes all
result reports).
Function reference¶
compute/ship_collision_model.py (all methods of
ShipCollisionModelMixin)
run_ship_collision_model- orchestrator._calc_head_on_collisions- per leg._calc_overtaking_collisions- per leg, per direction._calc_bend_collisions- per leg, angle > 5 deg._calc_crossing_collisions- all leg pairs with shared waypoints._get_weighted_mu_sigma- lateral distribution reduction._parse_point/_points_match/_calc_bearing- geometry helpers.get_loa_midpoint/estimate_beam- LOA/beam fallback estimates.
compute/basic_equations.py
get_head_on_collision_candidatesget_overtaking_collision_candidatesget_crossing_collision_candidatesget_bend_collision_candidates
compute/data_preparation.py
get_distribution- extract up to three superposed distributions from one segment’smean/std/weightfields.