Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
59aeeae
feat: scipy 5-DOF optimizer, depowering, and input validation
TAJD Jun 17, 2026
1c3ab04
fix: revert api.py to master — ShortKeel belongs in sail-daring PR
TAJD Jun 17, 2026
b2ac11c
feat: ORC sail variants, ShortKeel appendage, and Daring yacht
TAJD Jun 17, 2026
aab8d20
feat: ShortKeel API detection and extended API tests
TAJD Jun 17, 2026
c34c774
Merge master into pr/core-physics
marinlauber Oct 13, 2026
b428e9d
docs: update install and run instructions for uv
marinlauber Oct 13, 2026
96dad5b
Merge pr/core-physics into pr/sail-daring
marinlauber Oct 13, 2026
bb7c70a
Remove Daring-specific example, tests and polar data
marinlauber Oct 13, 2026
c42a327
Remove unused polars_YD41.json results file
marinlauber Oct 13, 2026
5da143e
Merge pull request #59 from marinlauber/pr/sail-daring
marinlauber Oct 13, 2026
148bce2
fix: default hull roughness to smooth, as in the ORC VPP
marinlauber Oct 14, 2026
65ecd4a
fix: don't pad polar curves over unsolved sail points
marinlauber Oct 14, 2026
fd460a3
fix: realistic A5 dimensions in the YD-41 example
marinlauber Oct 14, 2026
3a56892
fix: keep rudder side-force factor at 2 above 30 deg heel
marinlauber Oct 14, 2026
b765693
feat: maximise boat speed over depowering with SLSQP
marinlauber Oct 14, 2026
44ec568
fix: plot sail chart TWS in knots, not m/s
marinlauber Oct 15, 2026
8c8bf3b
fix: draw TWS legend in black, colours mean sail sets
marinlauber Oct 15, 2026
f50a15a
feat: align aero and stability models with the ORC VPP 2026
marinlauber Oct 15, 2026
1100109
feat: save figures as PDF for sharper curves
marinlauber Oct 15, 2026
cbe0ef4
feat: cap heel under spinnaker at the ORC 21.5 deg
marinlauber Oct 15, 2026
e2237ec
feat: heeled wind triangle with the ORC log wind gradient
marinlauber Oct 16, 2026
490b996
feat: ORC appendage drag and induced drag corrections
marinlauber Oct 16, 2026
249c684
fix: appendage aspect ratio span^2/area and lifting-line lift slope
marinlauber Oct 16, 2026
44a96d5
chore: release 1.0.0, drop module headers and unused verbose arg
marinlauber Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Figure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Polars.pdf
Binary file not shown.
Binary file removed Polars.png
Binary file not shown.
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,38 @@ Please see the demo hosted at [https://yacht-vpp.streamlit.app/](https://yacht-v

## Using the code

To use the code, first clone or download this repository then install the required dependencies (see below).
The project is managed with [uv](https://docs.astral.sh/uv/). Install it once (see the [uv docs](https://docs.astral.sh/uv/getting-started/installation/) for other methods):

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

Then clone the repository and install the dependencies into a local `.venv`:

```bash
git clone https://github.com/marinlauber/Python-VPP.git
cd Python-VPP
uv sync
```

The main files that are used are `runVPP.py` and `righting_moment.json`. These have to be filled with the data of your boat. By default they are using the YD-41 (from Principle of Yacht Design).

The VPP is run with.
The VPP is run from the root of the repository with

```bash
$ python runVPP.py
uv run python runVPP.py
```

Once the code has run, it should generate the following figure (or a similar one)
At each TWS/TWA the boat speed is maximised over boat speed, heel, leeway, sail flattening and reefing, subject to force and moment equilibrium (SLSQP). `VPP.set_analysis` takes an optional maximum heel angle (`phi_max`, default 35 degrees); the sails are only flattened (down to flat = 0.53) and reefed as much as is fastest within it, following the ORC VPP 2026 depowering scheme. Kites are further limited to their own `max_heel` (default 21.5 degrees, the ORC limit under spinnaker). Points where a sail set cannot be carried within its heel limit are left at 0 and not plotted. The apparent wind follows the ORC heeled wind triangle: TWS is taken at 10 m and scaled to the sail plan's centre of effort with a log wind profile (z0 = 0.005 m), and its cross-track component is reduced by cos(heel). Appendage drag follows the ORC strip friction, form factor and lift-dependent drag, and the induced drag the ORC rudder ramp (zero lift upright), heel multiplier and free-surface/funsteady span corrections. Appendage lift uses the geometric aspect ratio span²/area, doubled for the reflection in the hull, in a lifting-line lift-curve slope (Jones slender-wing slope for short keels). `VPP.run` accepts `method="sequential"` (default) or `"parallel"` (same results, spread over all CPU cores).

To run the Streamlit demo locally:

```bash
uv sync --extra demo --extra api
uv run streamlit run demos/Home.py
```

The run writes the results to `results.json` and saves the polars (`Polars.pdf`) and the sail chart (`SailChart.pdf`) as PDF, so the curves stay sharp when zoomed. For the YD-41 the polars look like this (`Figure.png` is a PNG copy of `Polars.pdf` for this page)

<p align="center">
<img src="Figure.png" alt="YD-41 VPP results" width="1024">
Expand All @@ -32,6 +53,7 @@ Here is a list of the key variables used in the VPP.
* Cu : Root Chord / Upper Chord (m)
* Cl : Tip Chord / Lower Chord (m)
* Span : Span (m)
* Tc_ratio : thickness/chord ratio for the ORC form factor (keel 0.20, rudder 0.10 by default)
1. Yacht :
* Lwl : Length waterline (m)
* Vol : Displ. volume of canoebody (m^3)
Expand All @@ -49,7 +71,8 @@ Here is a list of the key variables used in the VPP.
* Sails : List of Sails
1. Sails:
Standard measurements, except Roach is defined as 1-A/(0.5PE)
Kite only takes area and vce estimate (this is very rough)
HBI (jib) is the height of the base of I above the waterline; sail centres of effort (kite vce) are measured from it
Kite only takes area and vce estimate (this is very rough), min_reef (default 0.85) for depowering and max_heel (default 21.5 degrees)
1. VPP.set_analysis()
* TWA range : range of TWA to use
* TWS range : range of TWS, must be between [2, 35]
Expand All @@ -64,16 +87,18 @@ Follow the steps below to contribute to this project.

### Install dependencies

Install the project using [uv](https://docs.astral.sh/uv/):
Install the project with the development tools (pytest, ruff, mypy) and the API dependencies:

```bash
uv sync --extra dev
uv sync --extra dev --extra api
```

If using `pip`:
Note that `uv sync` removes extras that are not listed, so pass every extra you need (or use `uv sync --all-extras`).

If using `pip` instead:

```bash
pip install -e ".[dev]"
pip install -e ".[dev,api]"
```

### Run tests
Expand All @@ -83,13 +108,13 @@ Tests are implemented using [pytest](https://docs.pytest.org/en/8.0.x/).
You can run tests with

```bash
pytest -vv
uv run pytest -vv
```

You can run a benchmark against the YD-41 results from WinVPP by running the `benchmark.py` script.
You can run a benchmark against the YD-41 results from WinVPP by running the `benchmark.py` script (`-g` saves the polars as one `Figure_<label>_<TWS>.pdf` per wind speed plus a `Figure_<label>_stats.pdf` error summary, `-s` saves the results).

```bash
$ python benchmark/benchmark.py -g -o
uv run python benchmark/benchmark.py -g -s
```

## Acknowledgements
Expand Down
Binary file added SailChart.pdf
Binary file not shown.
Binary file removed SailChart.png
Binary file not shown.
4 changes: 2 additions & 2 deletions benchmark/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def plot_polar(bm, calc, stats):
transform=ax.transAxes, fontsize=12)

plt.tight_layout()
plt.savefig("Figure_%s_%02d.png" % (label, s), dpi=500)
plt.savefig("Figure_%s_%02d.pdf" % (label, s), dpi=500)

def plot_stats(stats):
print("Generating statistics plots...")
Expand Down Expand Up @@ -210,7 +210,7 @@ def plot_stats(stats):
ax.legend(title=r"Stats",loc=1, bbox_to_anchor=(1.05, 1.05))

plt.tight_layout()
plt.savefig("Figure_%s_stats.png" % (label), dpi=500)
plt.savefig("Figure_%s_stats.pdf" % (label), dpi=500)

def benchmark(args):
if args.purge:
Expand Down
4 changes: 4 additions & 0 deletions dat/Daring/righting_moment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0],
"GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260]
}
2 changes: 1 addition & 1 deletion dat/YD-41/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@
ax.set_ylabel(r"$V_B$ (knots)", labelpad=-40)
ax.legend(title=r"TWS (knots)", loc=1, bbox_to_anchor=(1.05, 1.05))
plt.tight_layout()
plt.savefig("Figure.png", dpi=500)
plt.savefig("Figure.pdf")
plt.show()
5 changes: 5 additions & 0 deletions dat/orc/asym_cl_kite.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# asymmetric spinnaker tacked on centreline (table 5.9) lift drag and lift coefficients (ORC VPP 2026). first row is awa
0.02648,,,,,,,,,,,,
0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,170.000,180.000
0.0000,0.15405,0.23500,0.28500,0.33500,0.36500,0.40000,0.51500,0.56500,0.54000,0.42000,0.22800,0.19500
0.0000,0.01738,0.69825,0.92000,1.08000,1.12500,1.13000,1.03500,0.88500,0.59200,0.24000,0.03900,0.00000
5 changes: 5 additions & 0 deletions dat/orc/asym_pole_kite.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# asymmetric spinnaker tacked on a pole (table 5.10) lift drag and lift coefficients (ORC VPP 2026). first row is awa
0.02648,,,,,,,,,,,,
0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,170.000,180.000
0.0000,0.15405,0.23500,0.28500,0.33500,0.36500,0.40000,0.51500,0.59000,0.68000,0.70200,0.65500,0.63500
0.0000,0.01738,0.69825,0.92000,1.08000,1.12500,1.13000,1.03500,0.95000,0.71000,0.39100,0.11500,0.00000
5 changes: 5 additions & 0 deletions dat/orc/jib.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# jib lift drag and lift coefficient (high, ORC VPP 2026 table 5.5). first row is awa
0.016,,,,,,,,,
0.00000,7.0000,15.0000,20.0000,27.0000,50.0000,60.0000,100.0000,150.0000,180.0000
0.003,0.05000,0.03200,0.03100,0.03700,0.25000,0.35000,0.73000,0.95000,0.90000
0.00000,0.00000,1.10000,1.47500,1.50000,1.45000,1.25000,0.40000,0.00000,-0.10000
5 changes: 5 additions & 0 deletions dat/orc/jib_low.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# jib lift drag and lift coefficient (low, ORC VPP 2026 table 5.5). first row is awa
0.016,,,,,,,,,
0.00000,7.0000,15.0000,20.0000,27.0000,50.0000,60.0000,100.0000,150.0000,180.0000
0.003,0.05000,0.03200,0.03100,0.03700,0.25000,0.35000,0.73000,0.95000,0.90000
0.00000,0.00000,1.00000,1.37500,1.45000,1.45000,1.25000,0.40000,0.00000,-0.10000
5 changes: 5 additions & 0 deletions dat/orc/kite.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# symmetric spinnaker (table 5.8, default kite) lift drag and lift coefficients (ORC VPP 2026). first row is awa
0.02639,,,,,,,,,,,,
0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,170.000,180.000
0.0000,0.18194,0.26744,0.33721,0.41724,0.47100,0.51100,0.58824,0.63200,0.66200,0.70200,0.65500,0.63500
0.0000,-0.02360,0.65965,0.86143,0.99180,1.01300,1.00000,0.92500,0.82700,0.67300,0.39100,0.11500,0.00000
5 changes: 5 additions & 0 deletions dat/orc/main.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# mainsail lift drag and lift coefficient (high, ORC VPP 2026 table 5.2). first row is awa
0.01379,,,,,,,,,
0.00000,7.0000,9.0000,12.0000,28.0000,60.0000,90.0000,120.0000,150.0000,180.000
0.03448,0.01724,0.01466,0.01466,0.02586,0.11302,0.38250,0.96888,1.31578,1.34483
0.00000,0.94828,1.13793,1.25000,1.42681,1.38319,1.26724,0.93103,0.38793,-0.11207
5 changes: 5 additions & 0 deletions dat/orc/main_low.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# mainsail lift drag and lift coefficient (low, ORC VPP 2026 table 5.2). first row is awa
0.01379,,,,,,,,,
0.00000,7.0000,9.0000,12.0000,28.0000,60.0000,90.0000,120.0000,150.0000,180.000
0.04310,0.02586,0.02328,0.02328,0.03259,0.11302,0.38250,0.96888,1.31578,1.34483
0.00000,0.86207,1.05172,1.16379,1.34698,1.35345,1.26724,0.93103,0.38793,-0.11207
5 changes: 5 additions & 0 deletions dat/orc/sym_kite.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# symmetric spinnaker (table 5.8) lift drag and lift coefficients (ORC VPP 2026). first row is awa
0.02639,,,,,,,,,,,,
0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,170.000,180.000
0.0000,0.18194,0.26744,0.33721,0.41724,0.47100,0.51100,0.58824,0.63200,0.66200,0.70200,0.65500,0.63500
0.0000,-0.02360,0.65965,0.86143,0.99180,1.01300,1.00000,0.92500,0.82700,0.67300,0.39100,0.11500,0.00000
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "python-vpp"
version = "0.0.2"
version = "1.0.0"
description = "OOP Velocity Prediction Program"
readme = "README.md"
license = "MIT"
requires-python = ">=3.11"
authors = [
{ name = "Marin Lauber", email = "M.Lauber@soton.ac.uk" },
{ name = "Marin Lauber", email = "marinlauber@gmail.com" },
{ name = "Otto Villani" },
{ name = "Thomas Dickson" },
]
Expand All @@ -19,7 +19,6 @@ dependencies = [
"numpy>=2.0",
"matplotlib>=3.8",
"scipy>=1.12",
"tqdm>=4.66",
]

[project.optional-dependencies]
Expand Down
Loading
Loading