Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1ce07ee
Improve plotting geometry and update tutorial
jellepoland Jul 24, 2026
3b92415
Add CLAUDE.md with repo guidance for Claude Code
jellepoland Jul 24, 2026
2eb7fe9
Feat: Fancy Plotly plot with inflatable tubes and curved canopy
jellepoland Jul 24, 2026
f3005aa
Refine inflatable tube geometry: strut-to-LE join, tip wrap, framing
jellepoland Jul 24, 2026
7f4d749
Fancy plot: panel toggle, surface-normal force grid, draw order
jellepoland Jul 24, 2026
282cbbc
Fancy plot: Cp-distributed chord-normal force vectors from AoA database
jellepoland Jul 24, 2026
1cfe544
Fancy plot: Cp-scaled force field, tube end-caps, high-res render, ex…
jellepoland Jul 24, 2026
3c680eb
readme
jellepoland Jul 24, 2026
3cc3896
radme
jellepoland Jul 24, 2026
c58337d
Release v2.2.0: fix body_axis in angle-derivative evaluation, drop de…
jellepoland Sep 2, 2026
f43bcdb
feat(solver): chord-regularised curved bound vortex at the quarter chord
ocayon Sep 17, 2026
a4ca2ca
docs(validation): report and reproducible validation of the curved bo…
ocayon Oct 12, 2026
118e91d
docs(validation): rib versus perpendicular attached-trailed legs with…
ocayon Oct 12, 2026
f9a24d6
docs(validation): correct the sign of the authors' swept-wing Trefftz…
ocayon Oct 12, 2026
bf07a8b
feat(solver): leg_direction option for the attached-trailed legs, jud…
ocayon Oct 12, 2026
def20bb
feat(solver): streamwise attached-trailed legs by default
ocayon Oct 12, 2026
deef069
fix(solver): rib legs stay the default; streamwise remains an option
ocayon Oct 12, 2026
a37ebab
docs: new aerodynamic model document with formulation, conventions an…
ocayon Oct 12, 2026
90926a7
Merge remote-tracking branch 'origin/develop' into develop
ocayon Oct 12, 2026
313d052
fix(solver): restore the reject condition broken by 1105122 (SyntaxEr…
ocayon Oct 8, 2026
2644b5c
perf(solver): compute the curved bound-vortex correction once per geo…
ocayon Oct 12, 2026
0e5a67f
perf(solver): reuse the curved bound-vortex correction under rigid mo…
ocayon Oct 12, 2026
7f44bfa
docs: the curved bound-vortex correction is computed once per shape, …
ocayon Oct 13, 2026
ba30878
docs: figure of the per-panel unrolled reference wing in the report a…
ocayon Oct 13, 2026
66f5013
docs: figure of the chordwise spreading of the bound vorticity
ocayon Oct 13, 2026
5667520
docs(validation): review pass on the curved bound-vortex report befor…
ocayon Oct 13, 2026
3130ba2
docs(validation): why sheets converge where lines do not, and the hyp…
ocayon Oct 13, 2026
0c35679
docs(validation): sensitivity of the correction to the chordwise stri…
ocayon Oct 13, 2026
b5fb374
docs(validation): rebuild the report PDF with the strip-loading table
ocayon Oct 13, 2026
ee92398
docs(validation): rewrite the curved bound-vortex report around the p…
ocayon Oct 13, 2026
1201f6f
Merge pull request #165 from awegroup/feature/curved-bound-vortex-reg…
ocayon Oct 13, 2026
160d139
perf(utils): allocation-free numba filament kernels (~70x faster AIC)
ocayon Nov 3, 2026
424d15d
feat(core): quarter-chord force law as xp kernels with a public API
ocayon Nov 3, 2026
a801b5b
perf: vectorised Trefftz drag and projected area, cheaper Newton resi…
ocayon Nov 3, 2026
3cb7bd0
feat(solver): polar tables as inputs of the CasADi panel functions
ocayon Nov 3, 2026
2d7a6df
Merge pull request #166 from awegroup/feature/quarter-chord-xp-kernels
ocayon Nov 3, 2026
bdce11a
Merge pull request #167 from awegroup/feature/casadi-polar-table-inputs
ocayon Nov 3, 2026
a44adf7
Merge tag v2.2.0 into the v2.3.0 release
ocayon Nov 3, 2026
3f3ea4a
Merge #167 (polar tables as CasADi inputs) into the v2.3.0 release
ocayon Nov 3, 2026
0a051a7
docs(changelog): 2.3.0 entry
ocayon Nov 3, 2026
ae5515c
Merge pull request #168 from awegroup/feature/quarter-chord-xp-kernels
ocayon Nov 3, 2026
b7debde
test(masure): skip the regression tests when the model files are absent
ocayon Nov 4, 2026
7654085
Merge pull request #169 from awegroup/release/v2.3.0
ocayon Nov 4, 2026
f90a2da
docs(changelog): complete the 2.3.0 entry
ocayon Nov 4, 2026
3025b76
Merge pull request #171 from awegroup/docs/changelog-2.3.0-completeness
ocayon Nov 4, 2026
a132267
docs(changelog): describe the circulation velocity as the effective v…
ocayon Nov 4, 2026
a0fe3aa
Merge pull request #172 from awegroup/docs/changelog-effective-velocity
ocayon Nov 4, 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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,7 @@ cython_debug/
#.idea/

#Ignore old thesis
Aerostructural_model_LEI
Aerostructural_model_LEI

# Generated cache from the fancy-plot SurfplanAdapter conversion
vsm_processed/
46 changes: 46 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository purpose

Python implementation of the Vortex Step Method (VSM): an enhanced lifting-line aerodynamic solver coupled with 2D viscous airfoil polars, aimed at low-aspect-ratio wings and leading-edge inflatable (LEI) kites. Scientific software — physical correctness, numerical stability, and reproducibility outrank abstraction and stylistic churn. **Read `AGENTS.md` before changing solver/aerodynamic logic** (sign conventions, reference frames, and circulation definitions must never change silently).

## Commands

```bash
source venv/bin/activate # project venv at ./venv — required; system python lacks deps
pip install -e .[dev] # editable install (dev = pytest, pytest-cov, black)

pytest # full suite (~35 s), config in pytest.ini (testpaths = tests)
pytest tests/Solver/test_solver.py # one file
pytest tests/Solver/test_solver.py::test_name # one test
black src tests # formatting (used by the project)
```

Examples run as plain scripts: `python examples/TUDELFT_V3_KITE/tutorial.py`. CI (`.github/workflows/testing.yml`) runs pytest with coverage on Python 3.10.

## Architecture (big picture)

Data flow: **geometry YAML → `BodyAerodynamics.instantiate()` → `Wing`/`Panel` mesh → `Solver.solve()` → results dict → plotting**.

- `src/VSM/core/BodyAerodynamics.py` — central orchestrator. `BodyAerodynamics.instantiate(n_panels, file_path=<aero_geometry.yaml>, spanwise_panel_distribution=..., bridle_path=...)` reads the YAML's `wing_sections` (headers `[airfoil_id, LE_x..z, TE_x..z]`) and `wing_airfoils` tables, builds polars per airfoil, adds sections to a `Wing`, and refines to `n_panels`. YAML readers index columns **by header name**, so extra columns (e.g. `VUP_x..z` from newer SurfplanAdapter exports) are ignored harmlessly.
- `src/VSM/core/AirfoilAerodynamics.py` — polar generation per airfoil type: `breukels_regression`, `masure_regression` (needs `ml_models_dir`; ~2.3 GB models downloaded from Zenodo into `data/ml_models/`, not in git), `neuralfoil`, `polars` (CSV), `inviscid`. Batch entry point: `from_yaml_entry_batch()`.
- `src/VSM/core/WingGeometry.py` (`Wing`), `Panel.py`, `Filament.py`, `Wake.py` — mesh & vortex elements. **Panels retain only LE/TE points + interpolated polar data**; airfoil `info_dict` parameters (`t`, `chord`, …) are discarded during instantiation — anything needing them later must re-read the YAML.
- `src/VSM/core/Solver.py` — iterative circulation (gamma) loop; optional Anderson acceleration and Li/Gaunaa spanwise artificial viscosity for post-stall stabilization. Returns a results dict (forces, moments, distributions).
- Top-level `src/VSM/` modules build on core: `stability_derivatives.py`, `trim_angle.py`, plus plotting: `plotting.py` (polars/distributions), `plot_geometry_matplotlib.py` (`plot_geometry`), `plot_geometry_plotly.py` (`interactive_plot` — the primary interactive 3D wing view, incl. bridle rendering), `plot_styling.py`.
- Performance-critical vector math is `numba`-jitted (`core/utils.py`).

## Data layout

`data/<KITE_NAME>/` holds per-kite inputs, e.g. `data/TUDELFT_V3_KITE/`:
- `CAD_derived_geometry/` and `Surfplan_derived_geometry/` — `aero_geometry_*.yaml` variants (one per airfoil-model type) + `struc_geometry_*.yaml` (bridle/particle data, used via `bridle_path`).
- `Surfplan_export/` — raw Surfplan export (`<name>.txt` + `profiles/*.dat`). Converted to VSM YAMLs by the sibling [SurfplanAdapter](https://github.com/awegroup/SurfplanAdapter) project, which also handles the Surfplan→VSM coordinate transform.
- Different YAML sources use different global frames (CAD-derived vs Surfplan-derived origins differ by ~10 m in z) — never mix coordinates across YAML families.

## Conventions that matter

- Body-fixed reference frame: **x rearward (LE→TE), y right wing, z up**; α positive nose-up, β positive wind from port. Aircraft-frame conversion via `stability_derivatives.map_derivatives_to_aircraft_frame()`.
- Wing sections are ordered/symmetric tip-to-tip; `airfoil_id` in `wing_sections` must match `wing_airfoils`.
- Style (`docs/style_guide.md`): typed Google docstrings, `snake_case`, path variables end `_path`, directories end `_dir`.
- Tests include verification cases against analytical solutions (`tests/verification_cases/`: elliptical, swept, curved wing, horseshoe vortices) — solver changes must keep these passing; add regression tests when numerical behaviour intentionally changes and explain what changed and why in the commit.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ The Vortex Step Method (VSM) is an enhanced lifting line method that improves up
A Julia version of this project is available at [VortexStepMethod.jl](https://github.com/Albatross-Kite-Transport/VortexStepMethod.jl)


## Key Features
- **Accurate low-aspect-ratio wing modeling** with enhanced lifting line theory
- **Viscous-inviscid coupling** using 2D airfoil polars (inviscid, CFD, ML-based)
- **Complex geometry support**: sweep, dihedral, anhedral, leading-edge inflatable (LEI) kites
- **Rigid-body stability derivatives**: automatic computation of dCx/dα, dCMy/dq, etc.
- **Trim angle solver**: automatic determination of trimmed angle of attack
- **Non-dimensional rate derivatives**: controls-friendly output (hat_p, hat_q, hat_r)
- **Reference point flexibility**: correctly handles moment reference point for rotational velocities
- **Interactive visualization**: Plotly and Matplotlib geometry and results plotting
<!-- ![](docs/TUDELFT_V3_KITE_plotly.gif) -->
![](docs/plotly_render.png)

## Reference Frame

The VSM uses a body-fixed reference frame with the following conventions:
Expand All @@ -30,17 +42,6 @@ The reference frame is illustrated below for the open-source example kite, the [
**Aircraft Frame Transformation:** For stability derivatives in standard aircraft coordinates (x-forward, y-right, z-down), use `map_derivatives_to_aircraft_frame()` from the `VSM.stability_derivatives` module. See `examples/TUDELFT_V3_KITE/evaluate_stability_derivatives.py` for usage.


## Key Features

- **Accurate low-aspect-ratio wing modeling** with enhanced lifting line theory
- **Viscous-inviscid coupling** using 2D airfoil polars (inviscid, CFD, ML-based)
- **Complex geometry support**: sweep, dihedral, anhedral, leading-edge inflatable (LEI) kites
- **Rigid-body stability derivatives**: automatic computation of dCx/dα, dCMy/dq, etc.
- **Trim angle solver**: automatic determination of trimmed angle of attack
- **Non-dimensional rate derivatives**: controls-friendly output (hat_p, hat_q, hat_r)
- **Reference point flexibility**: correctly handles moment reference point for rotational velocities
- **Interactive visualization**: Plotly and Matplotlib geometry and results plotting

## Documentation
For detailed documentation, please refer to the following resources.

Expand Down
165 changes: 165 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,171 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [2.3.0] - 04/11/2026

**Default results change.** The lifting-line coupling now follows the consistent
formulation of Gaunaa, Li & Pirrung (TORQUE 2026): circulation from the
effective velocity including induction, span-perpendicular sections,
quarter-chord force directions,
attached-trailed-vortex forces and a chord-regularised curved bound vortex.
Forces, moments and coefficients computed with the defaults differ from 2.2.0,
most on swept and curved wings (kites). Each new default can be switched back
individually where an option exists (listed below). See
`docs/aerodynamic_model` for the formulation and its validation.

This release also contains everything shipped in 2.2.0. `v2.2.0` was tagged on
the `improve_plotting` branch, which had not been merged into `develop`/`main`.

### Changed (results)
- Circulation target is `Gamma = 0.5 |V_eff| c Cl`, instead of
`0.5 |V_eff|^2 / |V_a| c Cl`. `V_eff` is the span-perpendicular effective
velocity: inflow plus induction, without the section's own 2D induction (the
inner velocity of Gaunaa et al.). `V_a` is the span-perpendicular inflow
without induction. On the V3 kite the old map put the circulation 2-6 % low.
- Panel frame and chord follow the Crossflow Principle: the chord direction is
perpendicular to the bound vortex, the normal is chord x span, and the panel
chord is the perpendicular one. Unswept panels are unchanged; on swept panels
the chord shrinks (V3 tip panels by 56 % and 19 %).
- `is_aoa_corrected=True` by default: lift and drag directions come from the
flow at the quarter chord (TAT3), with magnitudes from the 3/4-chord angle.
`is_aoa_corrected=False` restores the 3/4-chord directions.
- The quarter-chord flow includes the influence of a curved or swept bound
vortex through a correction: the actual minus a locally unrolled vortex
system, with the bound vorticity and the leg starting points spread over the
chord using the flat-plate thin-airfoil loading `gamma(x) ~ sqrt((c - x)/x)`
(24 strips, effective cut-off 0.15 c, not a tuning parameter). This is a new
modelling assumption. The correction is zero on a straight wing with
perpendicular legs. On-blade induced drag matches the Trefftz plane within
about 1 % on arc, swept and kite geometries, independently of the panel
count. It is computed once per shape and rotated under rigid motion.
- Drag acts along the local inner flow, not the global span axis.
- `is_with_attached_trailed_vortex_force=True` by default: Kutta-Joukowski force
on the chordwise attached trailed vortex segments (`F_attached_trailed_distribution`
in the results).
- `relaxation_factor=None` by default: 0.8 x the fixed-point stability limit of
Li et al. (2026), evaluated per solve (`Solver.compute_relaxation_factor_limit`).
2.2.0 used a fixed 0.01; pass a number to keep a fixed value.
- Each wake ring follows its own panel's apparent velocity. Results in uniform
translating inflow are unchanged to round-off; they change under body rates or
distributed inflow.
- Bridle line segments are charged at their own inflow, not the wing's mean.
- With `is_with_viscous_drag_correction=True`, the Gaunaa 2024 correction receives
the full 3D relative velocity (the spanwise flow it models).
- With `is_with_artificial_viscosity=True`, the regularization is also gated on
negative stall.
- `anderson_max_iterations` default raised from 100 to 1000.
- `Wing.preserve_section_order` (default False): skip the section re-sorting;
`update_wing_from_points` sets it, since its arrays already define the arc order.

### Removed / breaking
- `Solver` options `is_smooth_circulation`, `smoothness_factor`,
`is_artificial_damping`, `artificial_damping`,
`is_with_simonet_artificial_viscosity`, `simonet_artificial_viscosity_fva`
(stored, never read) and the `gamma_loop_type` values `simonet_stall`,
`non_linear_simonet_stall`, `non_linear_simonet_stall_newton_raphson` (they
raised `ImportError`). An unknown `gamma_loop_type` now raises `ValueError`.
- `Solver.compute_aerodynamic_quantities` no longer returns `Umagw`; the relative
velocity is available from `Solver.compute_relative_velocity(gamma)`.
- The Anderson loop no longer falls back to the base loop by default
(`anderson_fallback_to_base=True` restores it).
- `SurfplanAdapter` is now a required dependency.

### Added
- `gamma_loop_type="casadi_newton"`: Newton solve of the circulation residual
with an exact CasADi Jacobian, globalised by pseudo-transient continuation,
with fallback to the base loop (`newton_max_iterations`,
`newton_pseudo_time_step`, `newton_fallback_to_base`, `polar_interpolation`).
Requires the optional `casadi` package.
- `stagnation_patience` / `stagnation_rtol`: optional early stop of stagnating
circulation loops.
- `BodyAerodynamics(..., leg_direction="rib" | "streamwise" | "perpendicular")`
for the attached-trailed legs (default `"rib"`, unchanged).
- `Panel.set_leg_end_points` and `Panel.reset_legs_to_ribs`.
- `results["drag_induced_trefftz"]`: Trefftz-plane induced drag as a far-wake
reference (uniform inflow only).
- `VSM.core.panel_kernels`: the section force law as functions over an `xp`
namespace (`numpy` or `casadi`): `relative_flow`, `quarter_chord_velocity`,
`direction_angle`, `panel_forces`, `attached_trailed_forces` and row helpers.
`compute_results` uses them itself.
- `BodyAerodynamics.compute_quarter_chord_AIC_matrices`: the induction of the
force-direction flow at the quarter chords, for callers that freeze it.
- `Solver.casadi_panel_force_function()`: per-panel forces as a CasADi function.
With `table_inputs=True` (also on the residual function) the polar tables are
inputs, so one graph serves every deformed shape;
`Solver.casadi_polar_table_inputs()` and `casadi_stall_angle_inputs()` return
them in the input layout.
- Documentation: aerodynamic model document (formulation, conventions,
validation) and the curved bound-vortex validation report.

### Fixed
- Circulation loops stop on non-finite circulation instead of running on.
- `Solver`: SyntaxError in the pseudo-transient reject condition (hotfix on `main`
after 2.2.0, now also here).
- `Wing.compute_projected_area` no longer normalises the caller's
`z_plane_vector` in place.

### Performance
- AIC assembly JIT-compiled with numba, then made allocation-free (vectors as
tuples): about 1 us -> 20 ns per filament evaluation, 7.3 -> 0.1 ms for a
36-panel AIC.
- Curved bound-vortex correction computed once per shape and rotated under
rigid motion.
- Faster Li/Gaunaa artificial viscosity, vectorised force law, Trefftz drag and
projected area, cheaper `casadi_newton` residual calls. A 36-panel V3
`casadi_newton` solve takes about 17 ms (about 50 ms before these changes).

## [2.2.0] - 02/09/2026

Release cut from the `improve_plotting` branch. This is the version used to
generate the rigid-body stability derivatives (Table 8) of Poland et al. (2026),
Wind Energ. Sci., "Computational aerodynamics for soft-wing kite design".

### Fixed
- `BodyAerodynamics.va` setter: the rotational inflow due to body rates is now
`va -= omega x (r - r0)` (apparent wind = freestream minus body-point velocity).
v2.1.2 used `+=`, i.e. the body rotating at `-omega`. **This flips the sign of
every rate derivative** (`d/dp_hat`, `d/dq_hat`, `d/dr_hat`) computed with
`stability_derivatives.py`; angle derivatives and trim angles are unaffected.
Roll, pitch and yaw damping derivatives are now negative, as physics requires.
- `stability_derivatives.py::_evaluate_with_angles` now passes `body_axis`, so a
non-zero baseline roll/pitch rate is no longer silently applied about the z-axis.
- `trim_angle.py`: removed leftover debug `matplotlib` plot/`plt.show()` from the
coarse sweep (blocked headless pipelines).
- `Filament`: vortex-core regularization uses the radial projection of the
evaluation point onto the filament.
- `WingGeometry`: section meshing is orientation-agnostic.
- Elliptical-wing y-offset bug in tests fixed; bridle line index handling fixed.

### Changed
- **Breaking:** `BodyAerodynamics.va_initialize` / `va` setter no longer accept
`roll_rate`, `pitch_rate`, `yaw_rate`. Use `body_rates` (scalar or list of
magnitudes, rad/s), `body_axis` (matching axes, default +z) and
`rates_in_body_frame`. `compute_trim_angle` and
`compute_rigid_body_stability_derivatives` keep their `roll_rate/pitch_rate/yaw_rate`
keyword arguments.
- **Breaking:** bridle-line dictionaries use header key `d` (line diameter)
instead of `diameter`, matching the SurfPlanAdapter export.
- `BodyAerodynamics.instantiate` accepts `aerodynamic_center_location` and
`control_point_location` (chord fractions, defaults 0.25 / 0.75); AC and CP
chord weights are decoupled.
- `Solver`: opt-in Anderson-accelerated inner circulation loop; opt-in
Li/Gaunaa spanwise artificial viscosity for post-stall stabilization;
`relaxation_factor` default restored to 0.01.
- `Solver.solve` results include `q_ref` (reference dynamic pressure); coefficients
are normalised with an area-weighted reference velocity for distributed inflow.
- Quasi-steady-state solver API and examples added and subsequently removed.

### Added
- `BodyAerodynamics.rotate(angle_deg|angle_rad, axis, point)` to rotate the full
geometry; body rates follow the geometry unless `rates_in_body_frame=True`.
- `BodyAerodynamics.compute_flat_area`.
- `src/VSM/plotly/`: interactive Plotly geometry plots with inflatable tubes,
curved canopy, Cp-scaled force fields (`fancy_interactive_plot.py` example).
- Examples: `compute_trim_angle.py`, `calculate_max_roll.py`,
`attitude_and_yaw_rate_optimize.py`, `cmz_sensitivity_default.py`,
`varying_spanwise_va.py`.
- Tests for gamma initialization, tube geometry, updated filament/solver tests.

## [2.1.0] - 11/02/2026

### Added
Expand Down
Loading
Loading