feat: final SWOPP3 result improvements (hourly ERA5, weather constraints, EDT land penalty)#62
feat: final SWOPP3 result improvements (hourly ERA5, weather constraints, EDT land penalty)#62fjsuarez wants to merge 37 commits into
Conversation
- 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.
- 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).
8059119 to
b55d85a
Compare
There was a problem hiding this comment.
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_offsetplumbing and enable weather constraint penalties in CMA-ES so evaluation happens at correct absolute time. - Precompute an EDT in
Landand addLand.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.
- 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
There was a problem hiding this comment.
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_offsetso 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.
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
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.
…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.
Summary
Three improvements for the final SWOPP3 competition results:
Commit 1: Hourly ERA5 temporal resolution
_select_corridordefaulttime_stepfrom 6 → 1 (consistent withdownload_all_gcswhich already defaulted to hourly)data/era5directory and increase memory for hourly data volumesCommit 2: Operational weather constraints in CMA-ES
weather_penalty_weight=100inswopp3_runnerso CMA-ES avoids routes exceeding TWS > 20 m/s or Hs > 7 m (SWOPP3 operational limits)windfield,wavefield,travel_time, andtime_offsetto the optimizer so the penalty evaluates weather at the correct absolute timetime_offsetparameter toweather_penalty()andweather_penalty_smooth()— previously segment midpoint times were not shifted from elapsed to absolute field time_cma_evolution_strategyweather penalty calls to forwardtravel_timeandtime_offset(previously evaluated at t=0)Commit 3: Smooth distance-to-land penalty via EDT
scipy.ndimage.distance_transform_edton binary land array duringLand.__init__Land.distance_penalty()method: samples EDT viamap_coordinatesfor O(1) per-point lookups, returnsweight * sum(1/(edt + epsilon))per route — fully JIT-compatibleland_distance_weight/land_distance_epsiloninto_cma_evolution_strategy,optimize, andoptimize_with_increasing_penalizationland_distance_weight=10inswopp3_runnerfor SWOPP3 runsTests
UserWarningunrelated to changes)TestTimeOffset(4 tests),TestDistancePenalty(5 tests)