Skip to content

Migrate to uv, fix bugs and deprecated scipy APIs#55

Open
TAJD wants to merge 58 commits into
marinlauber:masterfrom
TAJD:uv-migration
Open

Migrate to uv, fix bugs and deprecated scipy APIs#55
TAJD wants to merge 58 commits into
marinlauber:masterfrom
TAJD:uv-migration

Conversation

@TAJD

@TAJD TAJD commented Feb 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Migrate build system from setup.py + requirements.txt to pyproject.toml (hatchling) + uv.lock
  • Modernize CI: test workflow uses astral-sh/setup-uv with Python 3.11/3.12 matrix; publish workflow uses uv build + PyPI trusted publishers (OIDC)
  • Fix string raise bugraise "string" is invalid Python 3, replaced with RuntimeError (fixes if not self.upToDate in run does not work since the excpetion is not derived from a BaseException #46)
  • Fix README install instructions — incorrect pip install requirements.txt replaced with pip install -e ".[dev]" and uv sync (fixes fix incorrect dependency install instructions in readme #43)
  • Replace deprecated scipy APIsinterp1dmake_interp_spline, interp2dRegularGridInterpolator (addresses Update dependencies to latest versions #36)
  • Add comprehensive docstrings — NumPy-style docstrings for all classes: Sail, Jib, Kite, Yacht, Keel, Rudder, Bulb, AeroMod, HydroMod (addresses Add documentation on parameters to the documentation #53)
  • Expand Sphinx documentation — autodoc now covers all 6 source modules, not just VPPMod
  • Lint cleanup — ruff auto-fix for import sorting and whitespace across src/ and tests/
  • Add Daring 5.5m yacht — yacht definition, Streamlit preset, and baseline performance results
  • Iterative depowering — sail flattening and reefing with configurable heel limit (phi_max), making flat and RED tuneable parameters
  • Expose depowering in Streamlit UI — new Flat & RED polar plots and expandable data table after running the VPP
  • Input validation and error handling — guard against empty TWS/TWA ranges and API errors in Streamlit UI
  • Move flask and streamlit to base dependencies — required for Streamlit Cloud deployment

Issues addressed

Issue Action
#53 - Document parameters Fixed — full docstrings on all classes, especially Jib (I, J, LPG, HBI)
#46 - String raise bug Fixed
#43 - Incorrect pip install Fixed
#36 - Update dependencies Fixed (unpinned lower bounds + uv.lock)
#25 - Python package release Unblocked (proper pyproject.toml + publish workflow)
#24 - Roadmap (docs + CI) Addressed (docstrings, Sphinx expansion, CI, ruff)
#21 - Refactor into package Partially addressed (proper packaging)

Supersedes PR #51 and PR #41.

Test plan

  • All tests pass (including validation and error handling tests)
  • runVPP.py runs end-to-end producing correct output
  • No scipy deprecation warnings
  • Streamlit UI displays depowering (Flat & RED) polar plots and data table
  • Verify CI passes on GitHub Actions
  • Verify publish workflow (owner needs to configure PyPI trusted publishers)
  • Build Sphinx docs and verify all modules render correctly

Notes

  • requires-python set to >=3.11 (CI tests 3.11-3.12)
  • flask and streamlit moved to base dependencies (needed for Streamlit Cloud)
  • ruff replaces black + isort (faster, single tool)

🤖 Generated with Claude Code

TAJD and others added 14 commits February 26, 2026 13:00
Replace legacy setuptools build with hatchling + PEP 621 metadata.
Add uv.lock for reproducible installs. Replace black+isort with ruff.
Move streamlit/flask to optional dependency groups.
Update Python requirement to >=3.12 for improved dependency compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace setup.py/twine with uv build and PyPI OIDC trusted publishers.
Run on ubuntu-latest instead of self-hosted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test on Python 3.10-3.12. Use astral-sh/setup-uv action.
Bump actions/checkout to v4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rinlauber#43)

Replace incorrect 'pip install requirements.txt' with correct commands.
Add uv as the recommended install method.
Fix hardcoded shebang in runVPP.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lauber#46)

String raises are invalid in Python 3 and caused TypeError.
Also initialize self.upToDate = False in __init__ so the guard
works when set_analysis() hasn't been called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
interp1d was deprecated in scipy 1.10. Use make_interp_spline
which returns a BSpline with the same callable interface.

Filter NaN values before passing to make_interp_spline since it
has stricter input validation than the deprecated interp1d.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
interp2d was deprecated in scipy 1.10 and removed in 1.14.
Use RegularGridInterpolator for sail chart interpolation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Run ruff check --fix to clean up import ordering, trailing whitespace,
and blank line whitespace across src/ and tests/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document Sail, Jib, Kite __init__ parameters and measure methods.
Jib parameters (I, J, LPG, HBI) were the most requested per marinlauber#53.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document Appendage, Keel, Rudder, Bulb, and complete Yacht
parameter documentation (was missing 7 of 16 params).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document __init__ and update() parameters for both force models.
Fix typos in existing docstrings (_vce, _cf).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add modules.rst with autodoc for VPPMod, YachtMod, SailMod,
AeroMod, HydroMod, and UtilsMod. Restructure index.rst with
toctree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump requires-python to >=3.11 (3.10 EOL Oct 2026)
- Test matrix now 3.11, 3.12, 3.13
- Install --extra api in CI so test_api.py can import flask

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nlopt 2.7.1 has no cp313 wheels. nlopt 2.9+ requires numpy>=2.
Updated lower bounds to ensure compatibility with Python 3.11-3.13.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@TAJD

TAJD commented Feb 26, 2026

Copy link
Copy Markdown
Collaborator Author

@marinlauber - was able to get this one off, please lmk what you think, I think it covers main feedback points plus upgrades CI

I'm going to probably do some hobby modelling of the Daring yacht class (one design in cowes) to model the impact of potential sail changes but hard to find time ofc

TAJD and others added 12 commits February 27, 2026 22:13
Allow per-yacht configuration of GZ righting arm curves via an optional
GZ parameter in the Yacht constructor. When provided, uses the supplied
curve instead of loading from the global righting_moment.json file,
maintaining backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add conftest.py with matplotlib Agg backend so plt.show() is a no-op
during tests. Update test_vpp.py to save plots to tmp_path and assert
the files exist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create Daring yacht definition with published specifications from classicsailboats.org
and estimated hydrodynamic parameters. Includes:
- righting_moment.json with GZ curve for ~50% ballast ratio, GM ~0.70m
- test_daring.py with comprehensive VPP tests (solve, speed sanity, polars/sail chart)
- runDaring.py script to generate polars and sail charts across 4-22 knots

All 3 tests pass and script runs successfully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9 wind speeds (4-20 kts), 31 TWA angles, main+jib and main+kite.
Sanity checked: max speed ~6.7 kts at 10 kts TWS, VMG up ~4.7 kts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of hardcoding flat=1.0 and RED=2.0, resid() now accepts them
as parameters with defaults that preserve existing behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow users to specify a heel angle limit when setting up the analysis.
Defaults to 35.0 degrees to preserve existing behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add _depower_solve() that flattens sails (flat: 1.0 -> 0.62) then
reefs/furls (RED: 2.0 -> 0.5) when heel exceeds phi_max. Uses bounded
least_squares solver to enforce the heel constraint during depowering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensure least_squares initial guess stays within bounds when the
unconstrained solve produces out-of-range values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marinlauber

Copy link
Copy Markdown
Owner

@TAJD Lots of great stuff going on here, thanks!
From what I get, it also sorts things from #51? Should I merge 51 first and then we check what's going on here?

@TAJD

TAJD commented Feb 28, 2026

Copy link
Copy Markdown
Collaborator Author

It does - I think merge in #51 and then I'll rebase can continue

TAJD and others added 8 commits February 28, 2026 13:07
- Remove max(0, leeway) clamp in HydroMod.update that silently zeroed
  negative leeway values, breaking solver convergence when the optimizer
  explored the valid [-2, 6] degree range
- Replace confusing `leeway / 180.0 * np.pi` with `np.radians(leeway)`
  for clarity (same result, clearer intent)
- Remove unused Appendage._cl() method — side force is computed directly
  in HydroMod._get_Ri() via cla * np.radians(leeway)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bounded solver pins heel at phi_max, so `phi <= phi_max` was always
true after the first flat step. Added 1-degree margin check so
depowering continues until heel genuinely drops below the limit.

Also finer step sizes: flat 0.02 (was 0.05), RED 0.1 (was 0.2).

Impact on Daring at 27kts/40TWA: Vb 3.3→6.0 kts, flat 0.95→0.62,
RED 2.0→0.9, leeway 6.0→2.6 degrees.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Streamlit widget state can cause the type field to be missing or
inconsistent with the actual keel parameters. Fall back to checking
for 'Length' key to detect short keel payloads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover three keel type dispatch paths: explicit type='short', key-based
detection (no type field), and explicit type='fin'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…icients

- Remove nlopt dependency, add scipy SLSQP 5-DOF constrained optimizer
  that simultaneously optimizes [vb, phi, leeway, flat, red]
- Add method parameter to VPP.run() ("iterative" or "5dof")
- Upgrade sail coefficient interpolation from linear interp1d to cubic
  B-splines (make_interp_spline k=3), with NaN filtering and k=1 fallback
- Add pluggable data_source parameter (dat/{source}/) with backward compat
- Add user-loadable sail polars via cl_data/cd_data dicts
- API accepts method and data_source parameters
- Streamlit UI: solver method and data source selectboxes on both pages
- Copy existing sail data to dat/orc/ subdirectory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l polar plots

- _get_cross() now accepts a pad parameter; speed plot (j=0) keeps pad=2
  for visual overlap, heel/leeway (j>0) use pad=0 to avoid discontinuities
- Legend is now rendered on all axes, not just the speed plot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The three sail combinations (e.g. MN1+J1, MN1+A2, MN1+A5) each have a
distinct colour but were unlabelled. Add a "Sail set" legend on the
centre (heel) axis; TWS legends remain on the speed and leeway axes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each plot keeps its own TWS legend; a shared "Sail set" legend with
colour-coded sail combinations sits along the bottom centre.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marinlauber

Copy link
Copy Markdown
Owner

@TAJD I'm going through those slowly, as there is a lot of stuff. I'm testing as I go, and everything looks nice.

@TAJD

TAJD commented Feb 28, 2026

Copy link
Copy Markdown
Collaborator Author

Good to hear! Any concerns just shout, as you can see I'm using Claude and whilst I'm adding tests and thinking through the logic I'm not checking all the code line by line yet.

TAJD and others added 9 commits March 1, 2026 21:24
New data files from ORC VPP documentation:
- sym_kite.dat: symmetric spinnaker (peak CL 1.456)
- asym_cl_kite.dat: asymmetric spinnaker, centerline tack (peak CL 1.513)
- asym_pole_kite.dat: asymmetric spinnaker, pole tack (peak CL 1.548)
- main_low.dat: mainsail low-performance rig variant
- jib_low.dat: jib low-performance rig variant

Add sail_type parameter to Main, Jib, Kite constructors to select
coefficient variant (e.g. sail_type="sym_kite"). API passes through
sail_type from JSON payload per sail section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add sail_type selectboxes for main (main/main_low), jib (jib/jib_low),
  and kite (kite/sym_kite/asym_cl_kite/asym_pole_kite) to both VPP and
  Compare Streamlit pages
- Wire sail_types through run_vpp() to API payload per sail section
- Add API tests for sym_kite and low-performance sail_type parameters

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…it tests

- ITTC 1978 surface roughness allowance in HydroMod
- Bretschneider wave spectrum added resistance model
- Match racing simulation engine (RaceMod) with tactical rules,
  wind shadow model, and Monte Carlo runner
- Pluggable wind model API (WindMod): constant, Brownian, mean-reverting
- Pre-computed polar caches for YD41 and Daring
- Match Race UI page with full parameter controls
- Popover explainers across all Streamlit pages
- Streamlit AppTest suite (25 tests) for UI verification
- Finer TWA grid (39 points) and environment parameter sliders

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two changes to break perfect symmetry between identical boats:
- Random start-line offset (0.5-2 boat lengths lateral, 0-0.5 along-course)
  models winning the pin at a real start
- Fractional mark-crossing time interpolation gives sub-second precision
  instead of rounding to integer timesteps

Draw rate drops from 100% to 0% even with identical boats, constant wind,
and zero noise.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Distinct colours (blue/orange) and markers (circle/triangle) per boat
- Dashed line for boat B vs solid for boat A (colourblind accessible)
- Mark/buoy positions shown as black diamonds on course trace
- Course corridor boundaries drawn as dotted lines
- Start positions marked with larger symbols
- Grid lines, cleaner spines, higher DPI (120) across all plots
- Win probability bar uses cleaner styling with hidden spines
- Analytical wind triangle (law of cosines) replacing scipy fsolve
- LRU caching for wind_triangle() and sail coefficient deduplication
- Parallel TWS/TWA grid computation via ProcessPoolExecutor
- TWS sensitivity sweep and parameter sensitivity analysis for match racing
- Per-leg race breakdown with timing and leg type tracking
- Tactical statistics: shadow tracking and encounter counting
- Field help tooltips on all VPP, Compare, and Match Race UI inputs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace plain-text parameter labels (Lwl, Vol, Bwl, etc.) with proper
LaTeX notation ($L_{wl}$, $\nabla$, $B_{wl}$, etc.) across all
Streamlit pages. Adds FIELD_LABELS mapping and field_label() helper in
utils.py. Also formats environment sliders and match race parameters
with Greek symbols for sigma, kappa, tau, and theta.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ht, UI improvements

- Replace matplotlib polar plot on Compare page with interactive Plotly
  Scatterpolar (hover tooltips, zoom, legend toggle)
- Replace manual st.progress callback with built-in st.status on VPP
  and Compare pages
- Move hull roughness from environment to per-yacht (number_input with
  guidance tooltip), applied per-config on Compare and per-boat on
  Match Race
- Add run_vpp_direct() to bypass Flask and call solver directly
- Add progress_callback parameter to VPP.run() for future use
- Improve Compare page change summary: use mathematical field labels,
  section titles, and collapse to expander when >4 changes
- VMG tables now stacked vertically with percentage deltas
- Add plotly dependency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add per-TWS progress updates via st.status on VPP and Compare pages
  (shows each wind speed as it completes per config)
- Make TWS and TWA slider ranges inclusive of the end value
- Add seconds-per-nautical-mile delta column to VMG comparison tables
- Simplify VPP.run() progress_callback to fire at TWS level

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marinlauber

Copy link
Copy Markdown
Owner

@TAJD Let's strategise how to merge parts of this large PR. There are a lot of nice things in there, but all at once is a bit too much to review.

@TAJD

TAJD commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

@marinlauber — agreed, it grew way too big. I've split it into four stacked PRs you can review and merge in order:

  1. chore: migrate to uv, update CI for Python 3.13 #57 pr/uv-ci — uv migration, CI, Python 3.13 support (pure tooling, no logic changes)
  2. feat: scipy 5-DOF optimizer, depowering, and input validation #58 pr/core-physics — scipy 5-DOF optimizer (replaces NLopt), iterative depowering, leeway fix, input validation
  3. feat: ORC sail variants, ShortKeel appendage, and Daring yacht #59 pr/sail-daring — ORC spinnaker/low-performance sail data, ShortKeel appendage, Daring 5.5m yacht definition
  4. feat: Streamlit UI — comparison page, Plotly polar, LaTeX labels #60 pr/streamlit-ui — comparison page, interactive Plotly polar, LaTeX labels, sail type selectors

Each PR targets the previous one, so you'll see only the delta in each review. Merge in order 57 → 58 → 59 → 60.

The match racing simulation and spatial wind/routing work is staying on this branch (uv-migration) as prototype/exploratory code — not ready for review and not blocking anything above. Happy to discuss direction on those separately once the core stuff is in.

@TAJD

TAJD commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

@marinlauber — agreed, it's gotten unwieldy. You're right to pump the brakes.

A few things crept into this branch that were never meant for review yet — the match racing simulation and the spatial wind/routing work are pure prototyping at this stage, not polished enough to propose for merge. I'll leave those sitting on uv-migration and open clean PRs for the parts that are actually ready.

Splitting into four, each targeting the previous so you can review the delta cleanly:

  1. chore: migrate to uv, update CI for Python 3.13 #57 pr/uv-cimaster — uv migration, pyproject.toml, CI update for Python 3.13
  2. feat: scipy 5-DOF optimizer, depowering, and input validation #58 pr/core-physicspr/uv-ci — scipy 5-DOF optimizer replacing NLopt, iterative depowering with heel limit, leeway fix, input validation
  3. feat: ORC sail variants, ShortKeel appendage, and Daring yacht #59 pr/sail-daringpr/core-physics — ORC sail variants (spinnaker, low-sails), ShortKeel appendage, Daring 5.5m definition
  4. feat: Streamlit UI — comparison page, Plotly polar, LaTeX labels #60 pr/streamlit-uipr/sail-daring — comparison page, Plotly polar, LaTeX labels, shared UI components

Happy to merge them in order or adjust the split if you'd rather group things differently.

@marinlauber

Copy link
Copy Markdown
Owner

Thanks a lot @TAJD, I think it's already much more manageable.

I'll go through them in the next few days.

@TAJD

TAJD commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

No problem - let me know if I can be any more help! 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants