Skip to content

feat: final SWOPP3 result improvements (hourly ERA5, weather constraints, EDT land penalty)#62

Open
fjsuarez wants to merge 37 commits into
swoppfrom
feat/swopp3-final-results
Open

feat: final SWOPP3 result improvements (hourly ERA5, weather constraints, EDT land penalty)#62
fjsuarez wants to merge 37 commits into
swoppfrom
feat/swopp3-final-results

Conversation

@fjsuarez
Copy link
Copy Markdown

@fjsuarez fjsuarez commented Mar 16, 2026

Summary

Three improvements for the final SWOPP3 competition results:

Commit 1: Hourly ERA5 temporal resolution

  • Fix _select_corridor default time_step from 6 → 1 (consistent with download_all_gcs which already defaulted to hourly)
  • Update SLURM scripts (GPU and CPU) to use data/era5 directory and increase memory for hourly data volumes

Commit 2: Operational weather constraints in CMA-ES

  • Enable weather_penalty_weight=100 in swopp3_runner so CMA-ES avoids routes exceeding TWS > 20 m/s or Hs > 7 m (SWOPP3 operational limits)
  • Pass windfield, wavefield, travel_time, and time_offset to the optimizer so the penalty evaluates weather at the correct absolute time
  • Add time_offset parameter to weather_penalty() and weather_penalty_smooth() — previously segment midpoint times were not shifted from elapsed to absolute field time
  • Fix _cma_evolution_strategy weather penalty calls to forward travel_time and time_offset (previously evaluated at t=0)

Commit 3: Smooth distance-to-land penalty via EDT

  • Precompute scipy.ndimage.distance_transform_edt on binary land array during Land.__init__
  • Add Land.distance_penalty() method: samples EDT via map_coordinates for O(1) per-point lookups, returns weight * sum(1/(edt + epsilon)) per route — fully JIT-compatible
  • Wire land_distance_weight / land_distance_epsilon into _cma_evolution_strategy, optimize, and optimize_with_increasing_penalization
  • Enable land_distance_weight=10 in swopp3_runner for SWOPP3 runs

Tests

  • 963 tests pass (1 flaky CMA timer UserWarning unrelated to changes)
  • New tests: TestTimeOffset (4 tests), TestDistancePenalty (5 tests)

- Fix _select_corridor default time_step from 6 to 1 (consistent with
  download_all_gcs which already defaulted to hourly).
- Update SLURM scripts (GPU and CPU) to use data/era5_1h directory and
  increase memory for hourly data volumes.
- Enable weather_penalty_weight=100 in swopp3_runner so CMA-ES avoids
  routes exceeding TWS > 20 m/s or Hs > 7 m (SWOPP3 operational limits).
- Pass windfield, wavefield, travel_time, and time_offset to the
  optimizer so the penalty evaluates weather at the correct absolute time.
- Add time_offset parameter to weather_penalty() and
  weather_penalty_smooth() so segment midpoint times are shifted from
  elapsed to absolute field time.
- Fix _cma_evolution_strategy weather penalty calls to forward
  travel_time and time_offset (previously evaluated at t=0).
- Add TestTimeOffset tests for the new parameter.
Copilot AI review requested due to automatic review settings March 16, 2026 21:34
- Precompute scipy.ndimage.distance_transform_edt on binary land array
  during Land.__init__, stored as self._edt.
- Add Land.distance_penalty() method: samples EDT via map_coordinates
  for O(1) per-point lookups, returns weight * sum(1/(edt + epsilon))
  per route. Fully JIT-compatible.
- Wire land_distance_weight / land_distance_epsilon into
  _cma_evolution_strategy, optimize, and
  optimize_with_increasing_penalization as additive cost after the
  existing death penalty.
- Enable land_distance_weight=10 in swopp3_runner for SWOPP3 runs.
- Add TestDistancePenalty tests (on-land, far, closer-vs-far, weight
  scaling, batch dimension).
@fjsuarez fjsuarez force-pushed the feat/swopp3-final-results branch from 8059119 to b55d85a Compare March 16, 2026 21:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves SWOPP3 final results by switching ERA5 handling to hourly resolution, enforcing operational weather constraints during CMA-ES optimization, and adding a smooth EDT-based land-distance penalty to repel routes from coastlines.

Changes:

  • Default ERA5 corridor subsetting to hourly (time_step=1) and update SLURM scripts for hourly data + higher memory.
  • Add time_offset plumbing and enable weather constraint penalties in CMA-ES so evaluation happens at correct absolute time.
  • Precompute an EDT in Land and add Land.distance_penalty(); wire new land-distance penalty knobs through CMA-ES and runner defaults; add tests.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_weather.py Adds coverage for the new time_offset behavior in weather penalties.
tests/test_land.py Adds coverage for the new EDT-based Land.distance_penalty().
scripts/swopp3_slurm_gpu.sh Adjusts GPU SLURM job for hourly ERA5 inputs and higher memory.
scripts/swopp3_slurm.sh Adjusts CPU SLURM job for hourly ERA5 inputs and higher memory.
routetools/weather.py Adds time_offset parameter to both hard/smooth weather penalties.
routetools/swopp3_runner.py Enables operational weather penalties and EDT land-distance penalty by default for SWOPP3 runs.
routetools/land.py Precomputes EDT and adds distance_penalty() for smooth land repulsion.
routetools/era5/download_gcs.py Changes _select_corridor default time_step to hourly.
routetools/cmaes.py Wires new land-distance penalty params + forwards time parameters to weather penalty.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread routetools/weather.py
Comment thread tests/test_weather.py Outdated
Comment thread routetools/land.py Outdated
Comment thread routetools/land.py Outdated
Comment thread scripts/swopp3_slurm_gpu.sh
- weather.py: clarify time_offset/t_mid unit docs (units match travel_time)
- test_weather.py: fix ambiguous JAX array assert (float(pen[0]))
- land.py: cast EDT to float32, reuse instance map_order/map_mode in distance_penalty
@fjsuarez fjsuarez requested a review from Copilot March 16, 2026 22:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves SWOPP3 final-result quality by switching to hourly ERA5, enforcing operational weather constraints during CMA-ES optimization, and adding a smooth distance-to-land penalty using an EDT precompute.

Changes:

  • Default ERA5 corridor subsetting to hourly time steps and update SLURM run scripts for hourly data volume (paths + memory).
  • Extend weather penalties with time_offset so constraints are evaluated at correct absolute times.
  • Add EDT-based Land.distance_penalty() and wire new land-distance penalty options through CMA-ES and SWOPP3 runner defaults.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_weather.py Adds regression tests for time_offset behavior in weather penalties.
tests/test_land.py Adds tests for new EDT-based Land.distance_penalty().
scripts/swopp3_slurm_gpu.sh Updates hourly ERA5 data paths and increases memory for GPU runs.
scripts/swopp3_slurm.sh Updates hourly ERA5 data paths and increases memory for CPU runs.
routetools/weather.py Adds time_offset parameter and clarifies _segment_midpoints time units.
routetools/swopp3_runner.py Enables weather constraints and land-distance penalty defaults for SWOPP3 runs.
routetools/land.py Precomputes EDT in Land.__init__ and adds distance_penalty() method.
routetools/era5/download_gcs.py Changes _select_corridor default time_step to hourly.
routetools/cmaes.py Wires EDT land-distance and time-offset weather penalties into CMA-ES optimization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread routetools/weather.py Outdated
Comment thread routetools/weather.py Outdated
Comment thread routetools/land.py Outdated
Comment thread routetools/land.py
Comment thread tests/test_land.py Outdated
load_era5_land_mask and load_natural_earth_land_mask use
Land.__new__ to bypass __init__, so _edt was never set.
This caused AttributeError in distance_penalty() at runtime.
They load identical ERA5 10-m wind data. Eliminates a redundant
~4 GB GPU memory allocation and NetCDF read per corridor.
When running all 8 cases sequentially, Atlantic and Pacific ERA5
arrays would both reside on GPU memory simultaneously. On the
RTX 6000 Ada (48 GB) this caused a JaxRuntimeError when trying to
allocate constants for the Pacific corridor after the Atlantic
corridor had already used ~7.76 GB.

Clear cached wind/wave/vectorfield/land dicts, run gc.collect(),
and call jax.clear_caches() when switching corridors so that the
previous corridor's GPU memory is freed.
evaluate_route_energy was computing a single average speed across the
entire route and passing it uniformly to every segment. This mismatched
cost_function_rise (the CMA-ES objective), which correctly uses
per-segment speed derived from segment distance / dt.

Now compute per-segment haversine distances and divide by the uniform
dt to get the actual speed at each segment, consistent with the
optimisation model.

Add regression test verifying per-segment speed is passed to the
performance model.
Atlantic: --n-points 355 (dt ≈ 1.0h, 354 segments for 354h passage)
Pacific:  --n-points 584 (dt ≈ 1.0h, 583 segments for 583h passage)

Splitting avoids GPU OOM from both corridors' data coexisting and
matches the hourly ERA5 temporal resolution.
- Add explicit 'bounds' parameter to optimize() and _cma_evolution_strategy()
  that passes per-dimension bounds to CMA-ES, preventing control points from
  drifting far from the corridor (fixes wild turns)
- Compute corridor bounds from the great-circle route ±15° lon / ±10° lat
  in swopp3_runner, constraining waypoints to the geographic corridor
- Increase death penalty from 1000 to 1e6 (prohibitive for land crossings)
- Increase EDT land_distance_weight from 10 to 50 (stronger repulsion)
- Add test_optimize_with_bounds regression test
daniprec and others added 13 commits March 17, 2026 09:27
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fewer Bézier control points (4 free instead of 8) produce smoother
curves that cannot self-intersect, eliminating the looping artifacts.
Geographic bounds removed — the reduced K plus land/weather penalties
are sufficient constraints.
Resamples existing optimized tracks to fewer waypoints (50, 100, 200)
and re-evaluates energy to measure reporting resolution sensitivity.
Picks best, median, and worst departures automatically.
fjsuarez and others added 13 commits March 17, 2026 22:11
…order

- Resample tracks to uniform spacing before scoring
- Use trapezoidal rule for time integration
- Distinguish strong/weak constraint violations
- Fix interpolation order for consistency
- Add numpy-only integration tests for resampling pipeline
- Add weather_penalty_type parameter to optimize() and
  optimize_with_increasing_penalization() ("hard" or "smooth")
- Add --control-points (-K) and --weather-penalty-type CLI flags
- Change SWOPP3 defaults: K=10, weather_penalty_type="smooth"
- Add parametrized test for both penalty types in optimize()
- Update swopp3_slurm.sh to work from /scratch (staged by stage script)
- Add copy-back of results to ~/routetools/output/swopp3_cpu
- Switch staging script to cpu partition
- Fix ruff B007 lint in test_resample_track.py
The count-only threshold (10%) missed Pacific departures where the
optimizer used 2-3x more energy than GC.  Add a per-departure magnitude
check (default 20% excess) so catastrophic outliers are always flagged.

- Add excess_threshold_pct parameter (default 20%)
- Report worst departure with its actual vs GC energy
- Add 4 new tests: large excess, small excess, both checks, count-only
- Wire weather_penalty_sharpness through CMA-ES optimize() chain
- Change ERA5 interpolation order to 3 (tricubic) in swopp3_run.py
- Add sweep_stage_a.py: weather penalty weight/sharpness/sigma0 grid
  on 10 catastrophic-detour Pacific departures (300 runs)
- Add sweep_stage_b.py: sigma0/popsize/maxfevals/K grid on 20
  GC-stuck Pacific departures (480 runs)
- Add SLURM submission scripts for cpu partition on rust
JAX's map_coordinates only supports order<=1. The scorer uses SciPy
(order=3) but the optimizer must stay at order=1.
- sweep_atlantic.py: grid over wpw×sharpness×sigma0×K on 20 Atlantic
  departures (6 detour + 14 representative) = 1200 runs
- slurm_sweep_atlantic.sh: SLURM submission for cpu partition
- swopp3_sweep_results.md: analysis of Pacific Stage A & B results
- Add routetools/interpolate.py: JIT-compatible cubic B-spline that matches
  scipy.ndimage.map_coordinates order=3 exactly (pad+shift prefilter)
- Integrate into ERA5 loader for order>=3
- Add --weather-penalty-weight CLI flag to swopp3_run.py
- Add vangelis_pacific_noconstraint.sh for no-constraint Pacific runs
- Add comprehensive test suite (12 tests)
Subsample every Nth ERA5 timestep at load time. With hourly
Pacific data (8784 timesteps → 11.8 GB JIT constants), stride=3
yields 2928 timesteps (~3.6 GB) fitting comfortably in GPU memory
for XLA compilation.

- _load_datasets() accepts temporal_stride parameter
- Propagated to load_era5_windfield/wavefield/vectorfield
- --temporal-stride CLI option in swopp3_run.py
- Vangelis script updated to use stride=3
temporal_stride=3 caused different energy evaluations on the
same GC route due to coarser weather interpolation.  Switch to
CPU mode where 125 GB system RAM easily fits the 11.8 GB of
JIT constants without needing stride.
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.

3 participants