Skip to content

feat(swopp3): integrate Phase 2 configurations and optimization updates#69

Closed
daniprec wants to merge 56 commits into
swoppfrom
feat/swopp3-clean
Closed

feat(swopp3): integrate Phase 2 configurations and optimization updates#69
daniprec wants to merge 56 commits into
swoppfrom
feat/swopp3-clean

Conversation

@daniprec

@daniprec daniprec commented Jun 9, 2026

Copy link
Copy Markdown
Member

This PR integrates all configurations and experiments developed for SWOPP3 Phase 2 into the branch.

Key additions and updates

Land penalty

  • Introduces smooth distance-to-land penalization.
  • Integrated into CMA‑ES and FMS optimization pipelines.

Weather data loader

  • Updated ERA5 loading logic to support Phase 2 experiments.
  • Ensures consistent temporal handling and compatibility with optimization routines.

RISE cost functions

  • Adds new cost models and integrates them into the routing framework.

Optimizer updates

  • Refactors CMA‑ES and FMS to use the new weather loader and cost functions.
  • Ensures compatibility with updated penalties and constraints.

Reproducibility infrastructure

  • Adds SLURM scripts to reproduce CMA‑ES experimental results.
  • Consolidates experiment configurations used in Phase 2.

Validation

  • Full test suite passing.
  • Experiments reproduced using provided SLURM scripts.
  • Configuration files aligned with Phase 2 benchmark runs.

fjsuarez and others added 30 commits March 20, 2026 03:29
Replace single average speed with per-segment speeds computed from
haversine segment distances divided by equal time intervals.  This
correctly models varying speed along the route when segments have
different spatial lengths.
Precompute Euclidean Distance Transform on Land init and expose
Land.distance_penalty() that returns weight * sum(1/(edt+eps))
per route.  Points near land receive large penalties; far points
contribute negligibly.  Fully JIT-compatible via map_coordinates.
…inutes)

Add dt_eval_minutes parameter to _cma_evolution_strategy, optimize(),
optimize_with_increasing_penalization(), swopp3_runner, and CLI.
This allows using a finer evaluation time grid (Δt₂) independently
of the corridor discretization (Δt₁), improving penalty sampling
without increasing the CMA-ES search dimension.
In wind_penalty_smooth and wave_penalty_smooth, 'weight * sharpness'
was mathematically a single scalar.  Collapse into just 'weight'
with default 50.0 (= old 10.0 * 5.0) for backward compatibility.
The combined weather_penalty_smooth retains sharpness unchanged.
Two job scripts for the rust cluster:
- swopp3_slurm_split_penalty.sh: wind=100, wave=100, distance=10
- swopp3_slurm_no_penalty.sh: no weather penalties, distance=10
Both use dt_eval_minutes=30 and per-corridor n_points (Atlantic=178,
Pacific=293) for Δt₁=2h.
Thread travel_time and time_offset from run_case → run_optimised_departure →
cmaes_optimize → penalty functions so weather lookups use the correct
per-departure temporal offset.
…URM script

Add --cmaes-k (default 10) and --sigma0 (default 0.1) CLI options to
swopp3_run.py so CMA-ES exploration parameters can be tuned from the
command line without code changes.

Add swopp3_slurm_optimal_params.sh using the sweep-optimal parameters:
K=15, sigma0=0.3, wind_pw=10, wave_pw=10, dist_pw=10.
The defaults dict in run_optimised_departure() was missing windfield
and wavefield, so cmaes_optimize() received None for both. The penalty
guards 'if wind_penalty_weight > 0 and windfield is not None' were
always False — penalty code never executed in any SWOPP3 run.

The _rise_cost closure correctly captured windfield/wavefield for
energy computation, masking the fact that the penalty path was dead.

Add windfield=windfield and wavefield=wavefield to defaults dict.
Add regression test that asserts cmaes.optimize receives the actual
field closures (not None).
Change jnp.sum → jnp.mean in wind_penalty_smooth, wave_penalty_smooth,
and weather_penalty_smooth so the penalty magnitude is independent of
route resolution (L).  Previously, doubling L doubled the penalty for
the same weather conditions, causing catastrophic energy explosions at
high route resolutions.

Add _safe_mean helper to handle the degenerate single-point curve edge
case (0 segments → return 0 instead of NaN).

Add resolution-independence tests verifying identical penalty values at
L=10, L=50, and L=100 for constant fields.
- K=10 penalty weight sweep (array job: w50, w100, w200, w500)
- K=10 hard weather penalty run (weather_pw=10)
- K=15 sweep SLURM script (w200, w500)
- --popsize: CMA-ES population size (default 200)
- --maxfevals: maximum function evaluations (default 25000)
- --cmaes-verbose: enable per-generation CMA-ES logging
- K15/p400/w1000 SLURM script using array tasks for Atlantic/Pacific parallelism
Using _safe_mean(excess**2) divided by all N segments, making the penalty
negligible when only a few segments violate (e.g. w=1000 * mean(0.64/178)
= 12 MWh, only 12% of energy). Switching to _safe_max(excess**2) directly
penalises the worst-violating segment and is resolution-independent.

Add _safe_max() helper that handles zero-segment edge case (single-point
curve) by using jnp.max with initial=0.0 and a where mask.
Change array from 0-1 (Atlantic/Pacific) to 0-3, one per optimised case:
  0=AO_WPS  1=AO_noWPS  (Atlantic, n=178)
  2=PO_WPS  3=PO_noWPS  (Pacific,  n=293)

GC cases omitted (--strategy optimised): the great-circle route is
deterministic and unaffected by penalty weights or CMA-ES parameters.
Results can be copied from any prior run.

Each task loads only its own corridor's ERA5 files (2 files vs 4 before),
and all 4 cases run in parallel instead of sequentially per corridor.
20-task array job (5 weights x 4 optimised cases). GC cases skipped.
Weights chosen to cover the equivalent range of the old mean-diluted sweep
after accounting for the ~0.05 dilution factor (old w500 ≈ new w25).
_segment_midpoints used distance-proportional time (longer segments get
more time, i.e. constant speed), but evaluate_route_energy uses uniform
time per segment (dt = passage_hours / n_seg, i.e. variable speed).

This caused the CMA-ES penalty to query weather at different timestamps
than the post-hoc evaluation, so the optimizer was avoiding violations
at the wrong times while the reported max_tws/max_hs saw violations at
times the penalty never checked.
When track files referenced in File A are missing from the tracks/
directory, the scorer now reports:
- How many tracks are missing out of the expected total per case
- A clear PENALTY message explaining why energy was set to 1e12
- Guidance on how to fix the submission (include all File B CSVs)

This helps submitters diagnose incomplete submissions, e.g. when
optimisation for some cases hasn't finished yet.
Freol's reference tracks confirm the SWOPP3 Atlantic destination is
USNYS (lat=40.6, lon=-69.0), not USNYC (lat=40.53, lon=-73.80).
The USNYS port was already defined in _ports.py but not wired into
the SWOPP3 case definitions.

Updated:
- routetools/swopp3.py: ROUTE_ATLANTIC, SWOPP3_CASES, _SWOPP3_PORT_CODES
- codabench/scoring_program/scoring.py: all 4 Atlantic CASE_DEFS
- tests/test_swopp3.py: all endpoint assertions
- tests/test_swopp3_runner.py: helper and spy mock coordinates
MWA values near 360° (e.g. 356°) were converted directly to radians
(~6.2 rad), making exp(-K_W·|mwa_rad|³) vanish and zeroing the wave
added resistance term. Fix: center MWA to [-180°, 180°] via
(mwa + 180) % 360 - 180 before radians conversion.

Applied to all four power functions: predict_power_no_wps,
predict_power_with_wps, predict_power_batch, predict_power_jax.

Added regression tests for MWA symmetry (x vs 360-x), non-zero wave
contribution at MWA=356°, batch symmetry, and wheel model parity.
Add TestCircularInterpolation class verifying that MWD interpolation
across the 360°/0° boundary uses sin/cos decomposition instead of
naive linear interpolation. Also add _interp_era5_angle_trilinear
helper and precompute mwd_sin/mwd_cos in _load_era5_numpy.
6×6 grid of wind_penalty_weight × wave_penalty_weight (0-1000)
as a SLURM array job (--array=0-35). Fixed K=15, σ₀=0.3.
ERA5 data from /data/fjsuarez/era5/ (persistent NFS).
Jobs run as root where HOME=/root, but repo is at /home/fjsuarez/routetools.
Replace $HOME references with absolute paths.
Resamples tracks to Δt₂ = 30 min, queries ERA5 fields at each
evaluation point, and reports wind/wave violations as % of total
evaluation points (not per-departure max).
daniprec and others added 26 commits April 1, 2026 16:03
- evaluate_route returns dict with energy, max_tws, max_hs, and
  wind/wave violation segment counts
- validate_file_b returns (errors, land_count) tuple
- score_submission collects per-case violation data and writes
  per-case wind/wave/land violation departure counts to scores
- _write_detailed_results renders a violations summary HTML table
  showing which cases have weather or land constraint violations
The scorer uses USNYS (40.6, -69.0) as the Atlantic destination but
the starting kit still had the old USNYC coordinates (40.53, -73.80).
Routes generated by the starting kit would fail endpoint validation
(4.8° off, well outside the 0.5° tolerance).

Fixes #87
Add SWOPP3 analysis script and violations module
@daniprec

daniprec commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

The branchs diverged on 16 March and now there are too many conflicts. I am opening a new branch to reconcile these two.

@daniprec daniprec closed this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants