Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
156 changes: 156 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,162 @@ 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] - 03/11/2026

**Default results change.** The lifting-line coupling now follows the consistent
formulation of Gaunaa, Li & Pirrung (TORQUE 2026): circulation from the inner
velocity, 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_inner| c Cl` with the inner
(span-perpendicular, own-2D-induction-removed) velocity, instead of
`0.5 Umag^2 / Umagw c Cl`. 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 chord-regularised correction (the actual minus a locally
unrolled vortex system). 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.
- `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).

### 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).
- `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