Skip to content

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

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

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

Conversation

@TAJD
Copy link
Collaborator

@TAJD TAJD commented Feb 26, 2026

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
Copy link
Collaborator Author

TAJD commented Feb 26, 2026

@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>
TAJD and others added 2 commits February 28, 2026 08:46
…t model comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New page lets users set up two configurations side by side and compare:
- Overlaid polar plots (blue vs orange)
- VMG comparison table (upwind/downwind angles and speeds)
- Full speed delta table with absolute and percentage differences

Also extracts PRESETS into shared demos/presets.py module.

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

TAJD commented Feb 28, 2026

Short Keel vs Fin Keel — Physics Differences for Daring VPP

The Daring has a short/integrated keel, not a modern separated fin. The current Keel class assumes a trapezoidal fin, which mis-models the Daring in four key ways:

1. Geometry

A short keel is defined by its fore-aft length along the hull and depth below the canoe body, not root/tip chord of a separated trapezoid. Aspect ratio is much lower (0.2–0.5 vs 1.0–2.0 for a fin).

2. Lift Efficiency

At very low aspect ratio, the Prandtl correction (2π / (1 + 0.5·AR)) over-predicts lift. Low-AR foils follow the Jones formula: dCL/dα = π·AR/2 — lift-curve slope is proportional to AR, not corrected from 2π. This means a short keel generates much less side force per degree of leeway, so the boat needs more leeway to balance sail forces.

3. Form Drag

Higher than a fin (cof ~1.4–1.6 vs 1.31) because the keel–hull junction runs along a long chord, and the sections are thicker relative to their depth.

4. Residuary Resistance

A short keel's wave-making is largely captured by the hull's ORC resistance surface (since it's integrated with the hull). The separate appendage residuary term from rrk.dat (calibrated for discrete fins) should be reduced or zero for a short keel.

Impact

These differences compound — the Daring model currently over-predicts side force (too little leeway), under-predicts form drag, and may double-count residuary resistance. A ShortKeel appendage subclass is needed to capture these effects properly.

TAJD and others added 6 commits February 28, 2026 09:12
- Replace fixed two-column layout with numbered tabs (Config 1, 2, ...)
- Add/remove configs dynamically (up to 6)
- Highlight fields that differ from Config 1 below each tab
- Use distinct colours and markers per config on overlaid polars
- VMG and delta tables scale to N configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jones low-AR lift formula (pi*AR/2) replaces Prandtl correction for
short/integrated keels like the Daring 5.5m class. Higher form drag
factor, zero appendage residuary resistance (captured by hull), and
reduced wall-effect multiplier.

- Add ShortKeel class to YachtMod.py
- Support keel_type in API (fin/short)
- Switch Daring preset and runDaring to ShortKeel
- Add keel type selector to Streamlit UI
- Add unit tests for Jones formula, zero Rr, and lift comparison

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show Cu/Cl/Span for fin keels and Length/Depth/Tc_ratio for short
keels, with sensible defaults when switching between types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract keel type selector + field swapping into utils.render_keel_inputs
so both the single VPP page and the comparison page show the correct
inputs (Cu/Cl/Span for fin, Length/Depth/Tc_ratio for short).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move run_vpp, render_environment_inputs, and validate_ranges into
demos/utils.py so both the VPP and Compare pages use the same code.
Removes ~40 lines of duplication across the two pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marinlauber
Copy link
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
Copy link
Collaborator Author

TAJD commented Feb 28, 2026

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
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
Copy link
Collaborator Author

TAJD commented Feb 28, 2026

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>
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