Maritime Graph Weight System Documentation¶
Version: 1.1 Date: 2026-02-19 Project: Nautical Graph Toolkit Target Location: docs/reference/weights_system.md
Table of Contents¶
- Overview
- Visual Architecture Diagram
- Decision Trees
- Architecture Flow
- Feature Absorption (ft_* columns)
- Static Weights (wt_static_, wt_layer_)
- Directional Weights (dir_*)
- Dynamic Weights (wt_dynamic_*)
- Weight Aggregation Formula
- Column Naming Convention
- PyTorch ML Support
- Migration Guide: Weights → WeightsOpen
- Configuration Files
- File Reference
- Example Workflow
Overview¶
The maritime graph weight system transforms raw S-57 ENC data into a weighted navigable graph optimized for maritime routing. The system uses a four-stage pipeline:
S-57 ENCs → Feature Extraction → Static Weights → Directional → Dynamic Weights → Final Graph
(ft_*) (wt_static_*) (dir_*) (blocking/penalty (adjusted_weight)
/bonus_factor)
Processing Order: 1. Feature Extraction (ft_*) — Raw S-57 data extraction 2. Static Weights (wt_static_*) — Distance-based, layer-dependent weights 3. Directional (dir_*, wt_dir) — Traffic flow alignment (uses ft_orient, ft_trafic) 4. Dynamic Weights (blocking_factor, penalty_factor, bonus_factor) — Vessel-specific constraints (UKC, clearance). wt_dynamic_* per-factor columns are internal in Weights; planned as edge columns in WeightsOpen.
Key Design Principles¶
| Principle | Description |
|---|---|
| Separation of Concerns | Features → Static → Directional → Dynamic are independent stages |
| Human & Machine Readable | Columns support both manual inspection and PyTorch tensor export |
| Traceability | WeightsOpen preserves individual layer contributions for explainability |
| Configurable | All thresholds, bands, and multipliers defined in YAML/CSV |
Visual Architecture Diagram¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ MARITIME GRAPH WEIGHT SYSTEM │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────┐ ┌───────────────────┐ ┌─────────────────┐
│ S-57 ENCs │────▶│ Feature Extraction │────▶│ Enriched Graph │
│ │ │ (ft_* columns) │ │ │
└─────────────┘ └───────────────────┘ └────────┬────────┘
│
┌─────────────────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Static Weights │ │ Directional │ │ Dynamic Weights │
│ (wt_static_*) │ │ (dir_*, wt_dir) │ │ (wt_dynamic_*) │
│ │ │ │ │ │
│ • Distance │ │ • Angle Bands │ │ • UKC Bands │
│ • NavClass │ │ • Traffic Flow │ │ • Clearance │
│ • Degradation │ │ • ft_orient │ │ • Vessel Specs │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└─────────────────────────────────┼─────────────────────────┘
│
▼
┌─────────────────────┐
│ Weight Formula │
│ │
│ adjusted_weight = │
│ base × block × pen │
│ × bonus × dir │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Final Graph │
│ │
│ • adjusted_weight │
│ • Human readable │
│ • PyTorch ready │
└─────────────────────┘
Decision Trees¶
Static Weight Decision Tree¶
┌─────────────────┐
│ Feature Present │
│ on Edge? │
└────────┬────────┘
│
┌─────────┴─────────┐
│ Yes │ No
▼ ▼
┌──────────────┐ ┌──────────┐
│ Get NavClass │ │ Skip │
│ & Distance │ │(wt=1.0) │
└──────┬───────┘ └──────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DANGEROUS │ │ CAUTION │ │ SAFE │
│ (NavClass=3)│ │ (NavClass=2)│ │ (NavClass=1)│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ ┌─────┴─────┐ │
│ │ Within │ │
│ │ Buffer? │ │
│ └─────┬─────┘ │
│ ┌─────┴─────┐ │
│ │Yes │No │ │
│ ▼ ▼ │ │
│ ┌──────┐ ┌────┐│ │
│ │Block │ │Pen ││ │
│ │×10 │ │×1 ││ │
│ └───┬──┘ └─┬──┘│ │
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────┐
│ OUTPUT TO TIER │
│ blocking: MAX of all DANGEROUS + degraded │
│ CAUTION within buffer: ×10 → blocking │
│ penalty: PRODUCT of CAUTION penalties │
│ bonus: PRODUCT of SAFE bonuses │
└──────────────────────────────────────────────────────┘
Legend — degradation multipliers in the CAUTION branch: -
×1= baserisk_multiplierfrom the classifier (e.g., 5.0 for RESARE). "×1" means no additional degradation factor is applied; the penalty is still the classifier'srisk_multiplier, not 1.0. -×10=risk_multiplierescalated byCAUTION_DEGRADE_FACTOR(10.0) when the edge is within the feature buffer → the result exceedsBLOCKING_THRESHOLD, converting the CAUTION penalty into a blocking constraint.
Directional Weight Decision Tree¶
┌─────────────────┐
│ Has ft_orient? │
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ Yes │ No
▼ ▼
┌──────────────┐ ┌──────────┐
│ Calculate │ │ wt_dir=1.0│
│ dir_diff = │ │ (neutral) │
│ |edge- │ └──────────┘
│ feature| │
└──────┬───────┘
│
┌────┴────┐
│trafic=4?│
└────┬────┘
│
┌────┴────┐
│Yes │No
▼ ▼
┌────────────┐ ┌────────────┐
│Check reverse│ │Use forward │
│orientation │ │diff only │
└──────┬─────┘ └──────┬─────┘
│ │
└──────┬───────┘
▼
┌──────────────┐
│ Find Angle │
│ Band │
└──────┬───────┘
│
┌───────────┼───────────┼───────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│≤30° │ │≤60° │ │≤85° │ │≤95° │
│wt=0.9│ │wt=1.3 │ │wt=5.0 │ │wt=20.0 │
└──────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
└───────────┼───────────┴───────────┘
▼
┌──────────────┐
│≤180° │
│wt=99.0 │
└──────────────┘
Dynamic UKC Decision Tree¶
┌─────────────────┐
│ Calculate UKC │
│ = depth - draft │
└────────┬────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ UKC ≤ 0 │ │ UKC ≤ margin│ │ UKC > margin│
│ │ │ │ │ │
│ GROUNDING │ │ RESTRICTED │ │ Check band │
│ blocking=999│ │ penalty=10 │ │ │
└────────────┘ └────────────┘ └──────┬─────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ UKC ≤ 0.5 │ │ UKC ≤ 1.0│ │ UKC > 1.0 │
│ × draft │ │ × draft │ │ × draft │
│ │ │ │ │ │
│ penalty=2 │ │ penalty=1.5│ │ bonus=0.9│
└──────────┘ └──────────┘ └──────────┘
Architecture Flow¶
Stage 1: Feature Absorption (enrich_edges_with_features)¶
Purpose: Extract S-57 attribute data via spatial intersection between graph edges and ENC features.
Input: - NetworkX graph with geometry column on edges - S-57 ENC data (PostGIS/GeoPackage) - ENC names for filtering (e.g., ['US5FL14M'])
Output: ft_* feature columns on each edge
Key Feature Columns Created:
| Column | Description | Source Attribute | Layers |
|---|---|---|---|
ft_depth | Water depth (meters) | DRVAL1 | DEPARE, DRGARE, SWPARE |
ft_sounding | Isolated depth (meters) | VALSOU | WRECKS, OBSTRN, UWTROC |
ft_sounding_point | Point depth (meters) | depth | SOUNDG |
ft_ver_clearance | Vertical clearance (meters) | VERCLR/VERCSA | BRIDGE, PYLONS, CBLOHD |
ft_hor_clearance | Horizontal clearance (meters) | HORCLR | DOCARE, CONVYR |
ft_orient | Feature orientation (degrees) | ORIENT | TSSLPT, FAIRWY, DWRTCL, RECTRC |
ft_trafic | Traffic flow code (1-4) | TRAFIC | TSSLPT, FAIRWY, DWRTPT |
ft_wreck_category | Wreck type | CATWRK | WRECKS |
ft_obstruction_category | Obstruction type | CATOBS | OBSTRN |
Note:
ft_hor_clearanceis extracted for future use but is not currently used in the weight calculation pipeline. Stage 4 (calculate_dynamic_weights) only checksft_ver_clearance(vertical clearance). Horizontal clearance checks (e.g., blocking if beam >ft_hor_clearance) are planned for a future release.
Usage Band Prioritization: Features from higher usage bands override lower bands: - Band 6 (Berthing) > Band 5 (Harbour) > Band 4 (Approach) > Band 3 (Coastal) > Band 2 (General) > Band 1 (Overview)
Stage 2: Static Weights (apply_static_weights)¶
Purpose: Apply distance-based weights from maritime features using the S-57 classification system.
Input: Graph with ft_* feature columns
Output: wt_static_* aggregated tiers + wt_layer_* individual weights (WeightsOpen)
Navigation Classification System (NavClass)¶
| NavClass | Value | Description | Base Weight Range | Behavior |
|---|---|---|---|---|
| INFORMATIONAL | 0 | Not relevant to navigation | 0 | Skipped in weighting |
| SAFE | 1 | Preferred routes | 0.5 - 0.9 | Bonus (weight < 1.0) |
| CAUTION | 2 | Requires caution | 2.0 - 8.0 | Penalty (weight > 1.0) |
| DANGEROUS | 3 | Direct hazard | 80 - 100 | Blocking (weight ≥ 999) |
Distance-Based Tier Degradation¶
Static weights change based on proximity to features:
┌─────────────────────────────────────────────────────────────────────┐
│ Distance from Feature │
│ > buffer 50-100% buffer ≤50% buffer │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────────┐ ┌──────────┐ │
│ │Base │ │ Degrade │ │ Amplify │ │
│ │Tier │ │ One Tier│ │ Further │ │
│ └─────┘ └─────────┘ └──────────┘ │
│ │
│ DANGEROUS → blocking → blocking (999) │
│ CAUTION → penalty → blocking (×10) │
│ SAFE → bonus → penalty (×2) → penalty (×4) │
└─────────────────────────────────────────────────────────────────────┘
Degradation Factors: - CAUTION within buffer: weight × 10.0 (becomes blocking) - SAFE within 50-100% buffer: weight × 2.0 (becomes penalty) - SAFE within <50% buffer: weight × 4.0 (penalty with amplification)
Three-Tier Aggregation System¶
| Tier | Aggregation | Description | Formula |
|---|---|---|---|
| Blocking | MAX | Absolute constraints | max(all_blocking_weights) |
| Penalty | MULTIPLY | Cumulative hazards | product(penalties) capped at 50.0 |
| Bonus | MULTIPLY | Route preferences | product(bonuses) floored at 0.5 |
Static Weight Columns¶
| Column | Type | Description | Example Values |
|---|---|---|---|
wt_static_blocking | Aggregated | MAX of all DANGEROUS features | 1.0 / 999.0 |
wt_static_penalty | Aggregated | PRODUCT of CAUTION features | 1.0 / 5.0 / 50.0 (capped) |
wt_static_bonus | Aggregated | PRODUCT of SAFE features | 0.5 / 0.65 / 1.0 |
wt_layer_lndare | Individual | Land area contribution | 999.0 |
wt_layer_fairway | Individual | Fairway contribution | 0.5 |
wt_layer_obstrn | Individual | Obstruction contribution | 100.0 |
Stage 3: Directional Weights (calculate_directional_weights)¶
Purpose: Optimize for traffic flow alignment based on feature orientation.
Input: - Graph with ft_orient and ft_trafic columns
Output: dir_* columns + wt_dir
Angle Band Configuration¶
| Band | Max Angle | Weight | Name | Description |
|---|---|---|---|---|
| 0 | 30° | 0.9 | aligned | Following intended direction |
| 1 | 60° | 1.3 | small deviation | Slight deviation |
| 2 | 85° | 5.0 | big deviation | Significant deviation |
| 3 | 95° | 20.0 | crossing | Perpendicular/crossing |
| 4 | 180° | 99.0 | opposite | Against traffic flow |
Two-Way Traffic Handling¶
For features with TRAFIC = 4 (two-way traffic): - Calculate angular difference with both forward and reverse orientations - Use the better (smaller) angle difference - Store ft_orient_rev when reverse orientation is used
Directional Columns¶
| Column | Description | Example | In-Memory | PostGIS | GeoPackage |
|---|---|---|---|---|---|
ft_orient | Feature orientation (degrees) | 45.0 | Read (Stage 1) | Read (Stage 1) | Read (Stage 1) |
ft_trafic | Traffic flow code | 1-4 | Read (Stage 1) | Read (Stage 1) | Read (Stage 1) |
ft_orient_rev | Reverse orientation (two-way traffic) | 225.0 | Written (conditional) | Written (conditional) | Not implemented |
dir_edge_fwd | Edge bearing from u→v | 60.0 | Written | Written | Written |
dir_diff | Angular difference | 15.0 | Written | Written | Written |
dir_band | Band index (0-4) | 0 | Written | Written | Written |
dir_band_name | Band name | "aligned" | Written | Written | Written |
wt_dir | Directional weight factor | 0.9 | Written | Written | Written |
ft_orientandft_traficare created by Stage 1 (enrich_edges_with_features) and only read here.ft_orient_revis only set whenTRAFIC=4and the reverse orientation provides better alignment; the GeoPackage backend does not yet implement two-way reversal.
Stage 4: Dynamic Weights (calculate_dynamic_weights)¶
Purpose: Apply vessel-specific constraints based on ship specifications and extracted features.
IMPORTANT: This stage must be called LAST because it: - Requires all previous stages to be complete (uses ft_depth, ft_sounding, ft_ver_clearance, etc.) - Is specific to each vessel's draft, height, and beam - Calculates the final adjusted_weight used for routing
Input: - Graph with ft_* and wt_static_* columns - Vessel parameters: draft, height, beam, safety_margin (UKC buffer), clearance_safety / clearance_safety_margin (vertical clearance buffer — see naming note in Configuration section)
Output: blocking_factor, penalty_factor, bonus_factor, ukc_meters + final adjusted_weight (In WeightsOpen: also wt_dynamic_* per-factor columns — see Dynamic Weight Columns table below)
UKC (Under Keel Clearance) Bands¶
| Band | UKC Range | Weight | wt_sources Key | Description |
|---|---|---|---|---|
| 4 - Grounding | UKC ≤ 0 | 999.0 | ukc_grounding | Impassable - vessel would run aground |
| 3 - Restricted | 0 < UKC ≤ safety_margin | 10.0 | ukc_restricted | High penalty - very shallow |
| 2 - Shallow | safety_margin < UKC ≤ 0.5×draft | 2.0 | ukc_shallow | Moderate penalty - limited UKC |
| 1 - Safe | 0.5×draft < UKC ≤ 1.0×draft | 1.5 | ukc_safe | Minor penalty - adequate UKC |
| 0 - Deep | UKC > draft | 1.0 (or 0.9 bonus) | — | Preferred - deep water |
Clearance Checks¶
| Check | Condition | Blocking | Penalty |
|---|---|---|---|
| Vertical | clearance < vessel_height | 999.0 | - |
| Vertical Restricted | clearance < height + clearance_safety_margin (YAML: clearance_safety) | - | 20.0 |
| Sounding Hazard | sounding_ukc ≤ safety_margin | - | 5.0 |
| Sounding Moderate | sounding_ukc ≤ draft | - | 3.0 |
Dynamic Weight Columns¶
Columns written to graph edges by Weights.calculate_dynamic_weights():
| Column | Description | Values |
|---|---|---|
base_weight | Geographic edge distance | float |
blocking_factor | Combined blocking (static + UKC grounding) | 1.0 / 999.0 |
penalty_factor | Combined penalties (static × UKC × clearance × hazard), capped at 50.0 | 1.0 – 50.0 |
bonus_factor | Combined bonuses (static × deep water × vessel type), floored at 0.5 | 0.5 – 1.0 |
directional_factor | Directional weight factor (wt_dir) | 0.9 – 99.0 |
adjusted_weight | Final routing weight | float |
ukc_meters | Calculated UKC (depth − draft) | float (e.g., 2.3) |
Internal intermediates (computed but NOT stored as edge columns in Weights):
| Internal value | Description | Values | Written in WeightsOpen? |
|---|---|---|---|
wt_dynamic_ukc_band | Per-band UKC penalty | 1.0 / 1.5 / 2.0 / 10.0 / 999.0 | Planned (refactoring target) |
wt_dynamic_clearance | Vertical clearance penalty | 1.0 / 20.0 / 999.0 | Planned (refactoring target) |
wt_dynamic_hazard | Sounding hazard penalty | 1.0 / 3.0 / 5.0 | Planned (refactoring target) |
wt_dynamic_deep_water | Deep water bonus | 0.9 / 1.0 | Planned (refactoring target) |
These four
wt_dynamic_*values are computed internally during_calculate_penalty_factor()and_calculate_bonus_factor()but are not stored on graph edges in the currentWeightsimplementation. In theWeightsOpenrefactoring, they will be written as separate edge columns to improve per-factor traceability for debugging and ML training.
Weight Aggregation Formula¶
The final adjusted_weight combines all factors:
adjusted_weight = base_weight × blocking_factor × penalty_factor × bonus_factor × directional_factor
Where: - base_weight = Geographic distance (from 'weight' column) - blocking_factor = max(wt_static_blocking, ukc_grounding_block) — UKC grounding (depth ≤ draft) is checked internally and produces a 999.0 blocking value; no separate wt_dynamic_blocking column is written - penalty_factor = wt_static_penalty × ukc_penalty × clearance_penalty × hazard_penalty (capped at 50.0) — each dynamic component is computed internally, not stored as a separate column in Weights - bonus_factor = wt_static_bonus × deep_water_bonus × vessel_type_bonus (floored at 0.5) - directional_factor = wt_dir (default 1.0)
Output Columns Written by Stage 4¶
The following edge columns are written by calculate_dynamic_weights() (and calculate_dynamic_weights_open()):
| Column | Source | Written by |
|---|---|---|
base_weight | data['weight'] (edge distance) | Both |
blocking_factor | max(wt_static_blocking, ukc_grounding) | Both |
penalty_factor | wt_static_penalty × ukc_penalty × clearance_penalty × hazard_penalty | Both |
bonus_factor | wt_static_bonus × deep_water_bonus × vessel_bonus | Both |
directional_factor | data['wt_dir'] (from Stage 3) | Both |
adjusted_weight | base × blocking × penalty × bonus × directional | Both |
ukc_meters | ft_depth − draft | Both |
wt_dynamic_ukc_band | per-factor UKC band penalty | WeightsOpen only (planned) |
wt_dynamic_clearance | per-factor clearance penalty | WeightsOpen only (planned) |
wt_dynamic_hazard | per-factor sounding hazard penalty | WeightsOpen only (planned) |
wt_dynamic_deep_water | per-factor deep water bonus | WeightsOpen only (planned) |
For pathfinding:
Column Naming Convention¶
Current Convention (Weights)¶
The Weights class uses the following prefixes for graph edge columns:
| Prefix | Category | Example |
|---|---|---|
ft_ | Feature | ft_depth, ft_orient |
wt_static_ | Aggregated static | wt_static_blocking, wt_static_penalty |
wt_layer_ | Per-layer individual weight | wt_layer_lndare, wt_layer_fairwy |
dir_ | Directional | dir_edge_fwd, dir_diff, wt_dir |
blocking_factor | Dynamic aggregate | written by Stage 4 |
penalty_factor | Dynamic aggregate | written by Stage 4 |
bonus_factor | Dynamic aggregate | written by Stage 4 |
Target Convention for WeightsOpen (refactoring target)¶
WeightsOpen will encode NavClass directly in the column prefix, producing ML-ready column names where the prefix encodes the risk class without a separate lookup:
| Prefix | NavClass | Example |
|---|---|---|
ft_ | Feature | ft_depth, ft_orient |
wt_ | Weight (aggregated) | wt_static_blocking |
wt_0_ | Informational (NavClass=0) | wt_0_layer_airare |
wt_1_ | Safe (NavClass=1) | wt_1_layer_fairwy |
wt_2_ | Caution (NavClass=2) | wt_2_layer_resare |
wt_3_ | Dangerous (NavClass=3) | wt_3_layer_lndare |
dir_ | Directional | dir_edge_fwd, dir_band_name |
Current state:
WeightsOpencurrently writeswt_layer_*columns without the NavClass prefix (same asWeights). Thewt_N_layer_*naming will be implemented during the refactoring. The non-prefixedwt_layer_*form remains forWeightsbackward compatibility.
Human-Readable Additions¶
| Column | Type | Purpose |
|---|---|---|
ukc_band_name | String | "grounding", "restricted", "safe", "deep" |
ukc_meters | Float | Actual UKC value (e.g., 2.3) |
clearance_band_name | String | "blocked", "restricted", "ample" |
clearance_meters | Float | Actual clearance (e.g., 35.0) |
depth_to_draft_ratio | Float | Normalized depth (depth / draft) |
dir_layer_name | String | "separation_zone", "fairway" |
dir_band_name | String | "aligned", "opposite" |
Weight Value Ranges¶
| Range | Category | Meaning |
|---|---|---|
| 0.5 - 0.99 | Bonus | Preferred routing |
| 1.0 | Neutral | No preference/penalty |
| 1.01 - 4.99 | Penalty | Avoid if alternatives exist |
| 5.0 - 99.0 | High Penalty | Strong avoidance |
| ≥ 999.0 | Blocking | Impassable |
PyTorch ML Support¶
WeightsOpen Class¶
The WeightsOpen class extends Weights with ML-focused features for per-layer weight tracking:
Key Features¶
-
Individual Layer Tracking
-
PyTorch-Ready Flat Columns (current:
wt_layer_*; post-refactoring target:wt_N_layer_*)
GraphWeightOptimizer Class¶
The GraphWeightOptimizer class provides stateless ML pipeline utilities. It has no dependency on factory/config state — all methods operate purely on graph data passed as arguments.
from nautical_graph_toolkit.core import GraphWeightOptimizer
optimizer = GraphWeightOptimizer() # no arguments needed
ML Pipeline Methods:
- Export/Import for Training
# Export to DataFrame for analysis df = optimizer.export_for_pytorch(graph, 'dataframe') # Returns: pd.DataFrame with wt_layer_* columns # Export to PyTorch tensors tensors = optimizer.export_for_pytorch(graph, 'tensors') # Returns: dict with edge_features, base_weights, layer_names, etc. # Import learned weights learned = {'lndare': 800.0, 'fairwy': 0.45} graph = optimizer.import_learned_weights(graph, learned, 'blend')
Deprecation: The ML methods (
export_for_pytorch,import_learned_weights,encode_vessel_params,load_historical_routes,validate_against_weights) are still available onWeightsOpenas deprecation stubs for backward compatibility, butGraphWeightOptimizershould be used directly in new code.
Export Format Details¶
DataFrame format ('dataframe'): - One row per edge with columns: edge_id, u, v, base_weight, wt_layer_*, wt_dynamic_*, blocking_factor, penalty_factor, bonus_factor, adjusted_weight - NaN values filled with 1.0 (neutral weight)
Tensors format ('tensors'): - edge_features: torch.Tensor [n_edges, n_layers] - static layer weights - base_weights: torch.Tensor [n_edges] - original edge weights - dynamic_features: torch.Tensor [n_edges, n_dynamic] - dynamic weights - edge_ids: List[(u, v)] - edge identifiers - layer_names: List[str] - layer names corresponding to feature columns - dynamic_feature_names: List[str] - dynamic feature names
Dict format ('dict'): - Maps edge_id → {wt_sources, base_weight, factors}
Migration Guide: Weights → WeightsOpen¶
Overview¶
WeightsOpen extends Weights with individual layer tracking for ML optimization while maintaining backward compatibility. This guide helps you migrate existing code.
Key Differences¶
| Feature | Weights | WeightsOpen |
|---|---|---|
| Storage | Aggregated 3-tier only | Individual layers + aggregated |
| Columns | wt_static_* only | wt_static_* + wt_layer_* + wt_sources |
| Memory | Lower | Higher (traceability overhead) |
| ML Ready | No | Yes (PyTorch export) |
| Explainability | Limited | Full layer attribution |
| Use Case | Production routing | ML training, optimization |
Migration Steps¶
Step 1: Change Import¶
# Before
from nautical_graph_toolkit.core.weights import Weights
# After
from nautical_graph_toolkit.core.weights import WeightsOpen
Step 2: Update Initialization¶
# Before - Weights
weights = Weights(factory)
# After - WeightsOpen (same signature)
weights_open = WeightsOpen(factory)
Step 3: Update Static Weight Calls¶
# Before
graph = weights.apply_static_weights(graph, enc_names=['US5FL14M'])
# After - just change method name
graph = weights_open.apply_static_weights_open(graph, enc_names=['US5FL14M'])
Step 4: Update Dynamic Weight Calls¶
# Before
graph = weights.calculate_dynamic_weights(graph, vessel_params)
# After - just change method name
graph = weights_open.calculate_dynamic_weights_open(graph, vessel_params)
Step 5: Access New Layer Information¶
# Now you can access individual layer contributions
for u, v, data in graph.edges(data=True):
# New in WeightsOpen: individual layer weights
print(f"Lndare: {data.get('wt_layer_lndare')}")
print(f"Fairway: {data.get('wt_layer_fairwy')}")
# New: nested source tracking
sources = data.get('wt_sources', {})
print(f"Blocking sources: {sources.get('blocking', {})}")
print(f"Penalty sources: {sources.get('penalty', {})}")
Step 6: Validate Compatibility¶
# WeightsOpen includes validation against Weights
# This ensures identical routing results
graph_weights = Weights(factory).apply_static_weights(graph.copy(), enc_names)
graph_open = WeightsOpen(factory).apply_static_weights_open(graph.copy(), enc_names)
# Validate (stateless — use GraphWeightOptimizer directly)
from nautical_graph_toolkit.core import GraphWeightOptimizer
results = GraphWeightOptimizer().validate_against_weights(graph_open, graph_weights)
print(f"Validation passed: {results['match']}")
Column Mapping¶
| Weights Output | WeightsOpen Output (current) | WeightsOpen Target (post-refactoring) |
|---|---|---|
wt_static_blocking | wt_static_blocking (same) | same |
wt_static_penalty | wt_static_penalty (same) | same |
wt_static_bonus | wt_static_bonus (same) | same |
| — | wt_layer_lndare (new) | wt_3_layer_lndare |
| — | wt_layer_fairwy (new) | wt_1_layer_fairwy |
| — | wt_layer_obstrn (new) | wt_3_layer_obstrn |
| — | wt_sources (new - nested dict) | same |
| — | — | wt_dynamic_ukc_band (planned) |
| — | — | wt_dynamic_clearance (planned) |
| — | — | wt_dynamic_hazard (planned) |
| — | — | wt_dynamic_deep_water (planned) |
ML Workflow Migration¶
# Old workflow with Weights - no ML support
weights = Weights(factory)
graph = weights.apply_static_weights(graph, enc_names)
# New workflow with WeightsOpen - ML ready
weights_open = WeightsOpen(factory)
graph = weights_open.apply_static_weights_open(graph, enc_names)
# Export for PyTorch (use GraphWeightOptimizer for ML pipeline)
from nautical_graph_toolkit.core import GraphWeightOptimizer
optimizer = GraphWeightOptimizer()
tensors = optimizer.export_for_pytorch(graph, 'tensors')
# After training, import learned weights
learned_weights = {'lndare': 850.0, 'fairwy': 0.45}
graph = optimizer.import_learned_weights(graph, learned_weights)
Performance Considerations¶
| Aspect | Weights | WeightsOpen | Impact |
|---|---|---|---|
| Graph Size | Same | Same + 5–15% (sparse) / ~30%+ (dense) | wt_sources + wt_layer_* per active layer; the S57 classification database contains 253 layers — actual overhead scales with the number of active layers in the ENC data |
| Processing Time | Baseline | +5-15% | Layer tracking overhead |
| Memory | Lower | Higher | Additional columns + wt_sources nested dict per edge |
| Routing Results | Identical | Identical | Validation available |
Memory overhead note: The
wt_sourcesnested dict adds per-edge overhead proportional to the number of matched layers. For graphs with many active CAUTION/DANGEROUS layers the overhead can reach 20–30% above the baseWeightsstorage, not the ~5–10% estimate from the initial design.
When to Use Each¶
Use Weights when: - Production routing (no ML needed) - Memory/storage is constrained - Processing speed is critical - Only final weights matter
Use WeightsOpen when: - Training ML models - Debugging weight contributions - Optimizing layer multipliers - Need full explainability - Running weight experiments
Backward Compatibility¶
WeightsOpen produces identical wt_static_* values to Weights, ensuring:
- Same routing results - Paths are identical
- Same adjusted_weight - Final weight formula unchanged
- Same performance - Routing algorithms work identically
- Validation method -
GraphWeightOptimizer().validate_against_weights()confirms match
Configuration Files¶
1. graph_config.yml¶
Located at: src/nautical_graph_toolkit/data/graph_config.yml
Static Layers Configuration:
weight_settings:
static_layers:
- lndare # Land area (DANGEROUS)
- obstrn # Obstruction (DANGEROUS)
- fairwy # Fairway (SAFE)
- tsslpt # TSS lane (SAFE)
- resare # Restricted area (CAUTION)
Directional Weights:
directional_weights:
enabled: true
angle_bands:
- { max_angle: 30, weight: 0.9, name: 'aligned' }
- { max_angle: 60, weight: 1.3, name: 'small deviation' }
- { max_angle: 85, weight: 5.0, name: 'big deviation' }
- { max_angle: 95, weight: 20.0, name: 'crossing' }
- { max_angle: 180, weight: 99.0, name: 'opposite' }
Default Vessel Parameters:
default_vessel:
draft: 7.5 # meters - vessel draft (depth below waterline)
height: 30.0 # meters - air draft (height above waterline)
beam: 25.0 # meters - vessel width
length: 150.0 # meters - vessel length
vessel_type: 'cargo' # cargo, tanker, passenger, fishing
ukc_safety_margin: 2.0 # meters - UKC safety buffer (Under Keel Clearance)
ver_clearance_margin: 3.0 # meters - vertical clearance buffer (bridges)
Standardized parameter names (GAP 5 — resolved):
Key Meaning Old keys (still supported via fallback) ukc_safety_marginUKC buffer (Under Keel Clearance) safety_marginver_clearance_marginVertical clearance buffer (bridges/cables) clearance_safety_margin,clearance_safetyhor_clearance_marginHorizontal clearance buffer (channels/docks) (reserved for future use) Old keys are supported via backward-compatible fallback chains in all Python code.
2. s57_classification.py¶
Located at: src/nautical_graph_toolkit/utils/s57_classification.py
Classification Database Structure:
_LAYER_NAME = (
NavClass, # 0=Informational, 1=Safe, 2=Caution, 3=Dangerous
Category, # Route, Depth, Obstruction, etc.
RiskMultiplier, # Base weight factor
BufferMeters, # Safety buffer distance
Description, # Human-readable name
)
Example Classifications:
'FAIRWY': (NavClass.SAFE, 'Route', 0.5, 0, 'Fairway - preferred route')
'RESARE': (NavClass.CAUTION, 'Restricted', 5.0, 200, 'Restricted area')
'LNDARE': (NavClass.DANGEROUS, 'Coastline', 100, 0, 'Land area')
'WRECKS': (NavClass.DANGEROUS, 'Obstruction', 100.0, 500, 'Wreck')
File Reference¶
| File | Purpose |
|---|---|
src/nautical_graph_toolkit/core/weights.py | Core Weights and WeightsOpen classes |
src/nautical_graph_toolkit/core/weight_calculator.py | Static weight calculation logic |
src/nautical_graph_toolkit/utils/s57_classification.py | S-57 layer classification database |
src/nautical_graph_toolkit/data/graph_config.yml | Weight configuration and thresholds |
Example Workflow¶
from nautical_graph_toolkit.core.weights import WeightsOpen
from nautical_graph_toolkit.core.s57_data import ENCDataFactory
# Initialize
factory = ENCDataFactory(source='enc_data.gpkg')
weights = WeightsOpen(factory)
# Define vessel
vessel = {
'draft': 7.5,
'height': 30.0,
'safety_margin': 2.0,
'vessel_type': 'cargo'
}
# Process pipeline (correct order: static → directional → dynamic)
# Stage 1: Extract S-57 feature attributes from ENCs into ft_* columns
graph = weights.enrich_edges_with_features(graph, enc_names=['US5FL14M'])
# Stage 2: Spatial join with ENC features to compute static weights
# (enc_names is used independently here for its own spatial join)
graph = weights.apply_static_weights_open(graph, enc_names=['US5FL14M'])
graph = weights.calculate_directional_weights(graph) # Before dynamic (uses ft_orient)
graph = weights.calculate_dynamic_weights_open(graph, vessel) # Last (vessel-specific)
# Inspect results
for u, v, data in graph.edges(data=True):
print(f"Edge {u}->{v}:")
print(f" Depth: {data.get('ft_depth')}m, UKC: {data.get('ukc_meters')}m")
print(f" Static: blocking={data.get('wt_static_blocking')}, "
f"penalty={data.get('wt_static_penalty')}, "
f"bonus={data.get('wt_static_bonus')}")
print(f" Adjusted weight: {data.get('adjusted_weight')}")
End of Documentation