A study focused on implementing a clear and instructive program to compute VLM for simple yet generic aerodynamic configurations.
The current implementation is based on the book Flight Vehicle Aerodynamics, by Mark Drela, on section 6.5.
The code consists of a potential method for solving 3D flows, optionally coupled to 2D viscous section data from XFOIL. It can be very useful for configuration analysis, load estimation, trim conditions, stability and control derivatives.
The geometry of the configuration is highly simplified, any volumes of the geometry and its effects are ignored. The lattice itself is a flat plate: the only geometric effect it carries is control-surface deflection, achieved by the rotation of the surface normals. Section properties — camber, thickness, profile drag — enter only through the viscous coupling described below, which is why the lattice needs no camber model of its own.
The current implementation uses the JAX library as its numerical engine. This choice was made to make use of Automatic Differentiation, which could be useful in trade and optimization studies and is used for calculation of control derivatives. JAX also allows the user to specify which hardware the program runs on, either the CPU, the GPU, or even the TPU if available, and offers a series of function transformations that are useful for parallel computation and vectorization.
The inviscid solver needs only three packages:
pip install jax numpy matplotlibThe viscous coupling adds XFOIL, which ships as a Fortran extension and is compiled at install time, so it needs a toolchain:
sudo apt install gfortran cmake make # Debian/Ubuntu
pip install -r requirements.txtrequirements.txt takes XFOIL from the upstream GitHub tag, not from PyPI: the PyPI sdist
of 1.1.1 no longer builds against modern setuptools.
XFoil.aseq reports a non-converged angle by writing NaN into the angle array, not
only into the coefficients. generatePolars therefore rebuilds the angle sequence itself
(alphaSequence) rather than trusting what comes back, and trimPolar bridges short gaps
before interpolating.
The VLM approach simplifies each lifting surface into a vortex sheet, and each vortex sheet is split into panels. Each panel has a horse-shoe vortex attached to the quarter chord of the panel.
The influence of each vortex at each panel is calculated at a collocation point, generally placed at the three-quarter chord of the panel. The strength of all the vortices of the surfaces is such that the induced velocity at such collocation points is parallel to the surface. This condition allows for the creation of a linear system to compute the vorticity distribution that satisfies the condition at all panels.
With the panel vorticity computed it is possible to compute the force at each panel and in the whole surface.
For more details see the reference material in Flight Vehicle Aerodynamics, by Mark Drela.
The program is composed of two classes: Surfaces and Aircraft.
The Surface class takes geometric parameters for the definition of lifting surfaces and discretization. From this it derives a mesh of panels and computes all relevant components for the construction of the linear system of equations. It also has means to draw its own geometry on a figure, which can be passed to other surfaces to create a 3D representation of a full aircraft.
The Aircraft class is responsible for aggregating all the surfaces into a single system of equations. It computes the matrix of influences and the boundary conditions, taking into account the effects of any control surfaces.
From these the circulation at each panel is computed, along with the aerodynamic forces and moments. From this the coefficients are easily computable with a nondimensionalization and a rotation to the wind axes. Using the facilities of automatic differentiation from JAX it is possible to compute the stability derivatives and the control derivatives for each coefficient.
An easy result from this is the possibility of computing the derivative of pitch (
The lattice alone is linear and inviscid: no stall, no profile drag, and coefficientsAt(..., viscous=True) couples each spanwise
strip to 2D XFOIL data using the alpha method of Parenteau, Laurendeau and Carrier
(Combined High-speed and High-lift Wing Aerodynamic Optimization Using a Coupled VLM-2.5D
RANS Approach, §2.3).
Each strip carries one unknown, an incidence shift
At the fixed point each strip produces exactly the 2D viscous lift at its downwash-corrected
angle, with the lattice supplying the induced angle
XFOIL data is 2D and unswept. Simple sweep theory (Küchemann) converts it to the swept section, reading the polar in the plane normal to the quarter-chord line:
which reproduces the classic swept lift-curve slope sweep argument — that one sweeps the three-quarter-chord reference line, so with
taper the two differ substantially. For the demo wing in main.py, sweep=10° corresponds
to a quarter-chord sweep of
Profile drag is read at
Polars are tabulated once per distinct (airfoil, jax.jacfwd still
traces it. Re scales per section with the local chord relative to the MAC, and sections
between two breakpoints blend linearly between their polars, exactly as the chord does.
A surface is coupled if it carries a real airfoil. The default '0000' is a zero-thickness
plate, which XFOIL rejects, so those surfaces stay inviscid. Polars are built on the first
viscous call and rebuilt only if M or Re change.
At convergence the lattice already carries the section lift exactly, so only two things are
added: the profile drag force along the local flow, and the section pitching couple. Both are
summed into the force and moment before the rotation to stability axes, so
Sweep enters kinematically only — the viscous crossflow that reduces
Chord 1, span 8,
Chosen because it has a known response for comparison:
| quantity | reference |
|---|---|
| ≈ 4.70 / rad (VLM / Weissinger) | |
| −0.5000 (1/4 chord; the leading edge is at −0.75) | |
| < 1 — equal to 1 only for elliptical loading |
| chord×span | |||||||
|---|---|---|---|---|---|---|---|
| 1×24 | 48 | 0.4020 | 4.606 | 0.00646 | 0.995 | -0.4988 | 14.8 |
| 2×24 | 96 | 0.4037 | 4.626 | 0.00653 | 0.994 | -0.5045 | 13.7 |
| 4×24 | 192 | 0.4041 | 4.631 | 0.00654 | 0.993 | -0.5061 | 13.9 |
| 8×24 | 384 | 0.4042 | 4.632 | 0.00655 | 0.993 | -0.5065 | 14.8 |
| 16×24 | 768 | 0.4042 | 4.632 | 0.00655 | 0.993 | -0.5066 | 16.0 |
| 32×24 | 1536 | 0.4042 | 4.632 | 0.00655 | 0.993 | -0.5066 | 17.4 |
| chord×span | |||||||
|---|---|---|---|---|---|---|---|
| 8×4 | 64 | 0.4251 | 4.872 | 0.00654 | 1.099 | -0.5045 | 2.9 |
| 8×8 | 128 | 0.4135 | 4.738 | 0.00658 | 1.034 | -0.5058 | 5.1 |
| 8×16 | 256 | 0.4066 | 4.660 | 0.00656 | 1.003 | -0.5063 | 9.9 |
| 8×24 | 384 | 0.4042 | 4.632 | 0.00655 | 0.993 | -0.5065 | 14.8 |
| 8×40 | 640 | 0.4022 | 4.609 | 0.00653 | 0.985 | -0.5066 | 24.5 |
| 8×60 | 960 | 0.4012 | 4.597 | 0.00653 | 0.981 | -0.5066 | 36.7 |
TODO: add figure of aircraft
| wing panels | total |
|
|||
|---|---|---|---|---|---|
| 90 | 468 | 0.4180 | 0.2006 | 0.00709 | 102.1 |
| 220 | 598 | 0.4163 | 0.2007 | 0.00722 | 67.2 |
| 504 | 882 | 0.4149 | 0.2001 | 0.00729 | 50.6 |
| 900 | 1278 | 0.4142 | 0.1998 | 0.00733 | 41.0 |
| 1562 | 1940 | 0.4137 | 0.1995 | 0.00735 | 52.4 |
| tail panels | total |
|
|||
|---|---|---|---|---|---|
| 12 | 666 | 0.4163 | 0.2137 | 0.00732 | 39.0 |
| 48 | 720 | 0.4155 | 0.2059 | 0.00732 | 26.4 |
| 140 | 858 | 0.4150 | 0.2021 | 0.00732 | 26.3 |
| 252 | 1026 | 0.4148 | 0.2003 | 0.00732 | 41.1 |
| 572 | 1506 | 0.4147 | 0.1986 | 0.00732 | 91.8 |
The neutral point remains governed by the tail, not the wing
- Quadrupling the wing panels moves
$x_{np}$ by 0.0012 m; - Quadrupling the tail panels moves
$x_{np}$ by 0.0152 m (12.9× more sensitive).
| kernel ( |
AIC ( |
np.linalg.solve |
|
|---|---|---|---|
| 960 | 11 MB | 4 MB | 0.0047 s |
| 1920 | 44 MB | 15 MB | 0.0104 s |
| 3840 | 177 MB | 59 MB | 0.0284 s |
Rotation of the normals about the hinge line (Drela), assembly of the global gain per
panel, and control derivatives (
The control surface rotates the normal of each panel about the hinge, perpendicular to the normal. This gives:
| even, ≈0 | value | odd, free | value | |
|---|---|---|---|---|
| -0.000009 | -0.001334 | |||
| 0.000704 | -0.018774 | |||
| 0.000044 | -0.000314 |
| coefficient | value |
|---|---|
| 0.001083 | |
| -0.030673 | |
| -0.000144 | |
| -0.004780 | |
| 0.001661 | |
| 0.007711 |
Straight, high-$AR$ wing, full-span flap at
which for
| panels in chord | VLM ratio | deviation from theory | |
|---|---|---|---|
| 4 | 240 | 0.4752 | 22.0% |
| 6 | 400 | 0.5554 | 8.8% |
| 9 | 640 | 0.5848 | 4.0% |
| 14 | 1040 | 0.5953 | 2.2% |
| 20 | 1520 | 0.6026 | 1.1% |
The ~1% residual in the finest case is expected: the test wing has high but finite
The flap in this test is symmetric and
|
|
maximum relative error ( |
in float32 |
|---|---|---|
| 1.0000 | 1.02e-04 | 1.02e-04 |
| 0.1000 | 1.02e-06 | 1.22e-06 |
| 0.0100 | 1.02e-08 | 8.12e-08 |
| 0.0010 | 1.02e-10 | 2.37e-07 |
| 0.0001 | 1.02e-12 | 2.11e-07 |
With the solver in float64 (jax_enable_x64) the decay is monotone and exactly second
order: every 10× reduction in
Validation against asb.VortexLatticeMethod from
AeroSandbox — an independent, actively
maintained implementation of the same horseshoe-vortex formalism (Drela, Flight Vehicle
Aerodynamics, the same chapters used here).
Same case with analytical reference from the convergence study, matched mesh:
| quantity | main.py | AeroSandbox | relative difference |
|---|---|---|---|
|
|
0.401175 | 0.401171 | 0.001% |
|
|
0.006528 | 0.006528 | 0.002% |
|
|
0.203234 | 0.203227 | 0.004% |
With the same mesh and the same reference point, the two codes practically coincide, differing only in the 5th/6th decimal place.
Refining both codes together, same uniform spacing,
| panels/half-wing |
|
|
|
|---|---|---|---|
| 16 | 0.4066 | 0.4066 | 1.8e-05 |
| 32 | 0.4030 | 0.4029 | 1.7e-05 |
| 64 | 0.4010 | 0.4010 | 1.5e-05 |
| 128 | 0.4001 | 0.4001 | 1.3e-05 |
| 256 | 0.3996 | 0.3996 | 1.2e-05 |
Wing (2 segments) + winglets + horizontal and vertical tail, converted surface by surface.
| quantity | main.py | AeroSandbox | relative difference |
|---|---|---|---|
|
|
0.426036 | 0.426954 | 0.22% |
|
|
0.006926 | 0.007288 | 5.23% |
|
|
0.321280 | 0.320494 | 0.24% |
|
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|
| -5° | +0.0270 | +0.0261 | +0.0123 | +0.0122 | -0.0052 | -0.0041 |
| -2° | +0.0109 | +0.0105 | +0.0049 | +0.0049 | -0.0021 | -0.0017 |
| +0° | +0.0000 | +0.0000 | +0.0000 | +0.0000 | +0.0000 | -0.0000 |
| +2° | -0.0109 | -0.0105 | -0.0049 | -0.0049 | +0.0021 | +0.0017 |
| +5° | -0.0270 | -0.0261 | -0.0123 | -0.0122 | +0.0052 | +0.0041 |
The current state of the code does not take into account any body elements, and its tail refinement seems to have a severe impact on the prediction of the position of the neutral point of the aircraft.
As the present code was designed with clarity in mind as a study for the Vortex Lattice Method, its computational performance is sub-optimal both in the sense of time and memory allocation. As such, this limits its application to MDO and optimization, which need to explore a large number of possibilities. A viscous run compounds this: the coupling re-assembles the AIC and re-solves the linear system at every iteration, so one flight condition costs roughly twenty inviscid solves.
The test results reported below were all produced on the inviscid path; the viscous coupling is verified separately, against synthetic polars with a known closed-form answer and against the 2D limit at high aspect ratio.
The present code is a clear and objective tool for the learning and exploration of different aircraft configurations. It delivers a flexible format for study of different architectures and allows for quick and easy iteration on the geometry.
Though not guaranteed, reasonable precision and agreement with a classical and verified tool was obtained.







