Skip to main content

Airfoil analysis in Python — XFOIL-faithful solver with a local web UI

Project description

flexfoil

Airfoil analysis in Python -- XFOIL-faithful viscous solver with a local web UI.

import flexfoil

foil = flexfoil.naca("2412")
result = foil.solve(alpha=5.0, Re=1e6)
print(result)
# SolveResult(α=5.00°, Re=1e+06, CL=0.8094, CD=0.00775, CM=-0.0540, converged)

Install

pip install flexfoil

Optional extras:

pip install flexfoil[all]       # server + matplotlib + pandas
pip install flexfoil[plotting]  # matplotlib for polar.plot()
pip install flexfoil[dataframe] # pandas for polar.to_dataframe()
pip install flexfoil[server]    # starlette + uvicorn for flexfoil.serve()

What is this?

flexfoil wraps the RustFoil solver (an XFOIL-faithful Rust reimplementation) in native Python bindings via PyO3. Every solve is cached in a local SQLite database (~/.flexfoil/runs.db). The same web UI from foil.flexcompute.com can be launched locally with flexfoil.serve(), reading from the same database. No data leaves your machine unless you explicitly export it.

Quick start

Single-point solve

import flexfoil

foil = flexfoil.naca("2412")
result = foil.solve(alpha=5.0, Re=1e6)

print(result.cl)          # 0.8094
print(result.cd)          # 0.00775
print(result.cm)          # -0.0540
print(result.converged)   # True
print(result.ld)          # 104.5
print(result.x_tr_upper)  # 0.315  (transition location, upper surface)

Polar sweep

polar = foil.polar(alpha=(-5, 15, 0.5), Re=1e6)
print(polar)
# PolarResult('NACA 2412', Re=1e+06, 40/41 converged)

# 4-panel matplotlib figure: CL-α, CD-α, CL-CD, CM-α
polar.plot()

# Export to pandas
df = polar.to_dataframe()
df.to_csv("polar.csv", index=False)

Compare multiple airfoils

import matplotlib.pyplot as plt
import flexfoil

for naca in ["0012", "2412", "4412"]:
    foil = flexfoil.naca(naca)
    polar = foil.polar(alpha=(-4, 14, 1.0), Re=1e6)
    plt.plot(polar.alpha, polar.cl, ".-", label=foil.name)

plt.xlabel("α (°)")
plt.ylabel("CL")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Load a .dat file

foil = flexfoil.load("e387.dat")   # Selig or Lednicer format
result = foil.solve(alpha=4.0, Re=2e5)

Custom coordinates

foil = flexfoil.from_coordinates(x_list, y_list, name="my shape")
result = foil.solve(alpha=3.0, Re=1e6)

Inviscid analysis

result = foil.solve(alpha=5.0, viscous=False)
# result.cd == 0.0 (no drag in potential flow)

Launch the web UI

flexfoil.serve()
# Opens http://localhost:8420 in your browser.
# The full flexfoil web app reads from the same local SQLite database.

Or from the command line:

flexfoil serve
flexfoil serve --port 9000 --no-browser

The browser-based WASM solver still works for interactive exploration. Any runs you solve from the web UI are also written to the shared SQLite, so flexfoil.runs() in Python will see them.

Query the run database

Every solve (from Python or the web UI) is cached in ~/.flexfoil/runs.db.

# All runs as a pandas DataFrame (or list[dict] if pandas not installed)
df = flexfoil.runs()
print(f"{len(df)} runs cached")

# Filter by airfoil
naca_runs = df[df.airfoil_name == "NACA 2412"]

CLI

flexfoil solve 2412 -a 5 -r 1e6    # quick solve from the terminal
flexfoil serve                       # launch the web UI
flexfoil info                        # show config and DB location

API reference

Top-level functions

Function Description
flexfoil.naca(designation, n_panels=160) Create airfoil from NACA 4-digit string
flexfoil.load(path, n_panels=160) Load from .dat file
flexfoil.from_coordinates(x, y, name, n_panels) Create from raw x/y arrays
flexfoil.runs() All cached runs (DataFrame or list)
flexfoil.serve(port, host, open_browser) Launch local web UI + API server

Airfoil

Property / Method Description
.name Airfoil name
.n_panels Number of panel nodes
.raw_coords Original coordinates list[(x, y)]
.panel_coords Repaneled coordinates list[(x, y)]
.hash SHA-256 hash of panel coords (cache key)
.solve(alpha, Re, mach, ncrit, max_iter, viscous, store) Single-point analysis
.polar(alpha, Re, mach, ncrit, max_iter, viscous, store) Sweep over alpha range

SolveResult

Field Type Description
.cl float Lift coefficient
.cd float Drag coefficient
.cm float Moment coefficient (quarter-chord)
.converged bool Whether the Newton solve converged
.iterations int Newton iterations used
.residual float Final Newton residual
.x_tr_upper float Transition x/c, upper surface
.x_tr_lower float Transition x/c, lower surface
.alpha float Angle of attack (degrees)
.reynolds float Reynolds number
.mach float Mach number
.ncrit float e^N transition criterion
.ld float | None Lift-to-drag ratio
.success bool Overall success flag
.error str | None Error message if failed

PolarResult

Property / Method Description
.alpha list[float] — angles (converged only)
.cl list[float] — lift coefficients
.cd list[float] — drag coefficients
.cm list[float] — moment coefficients
.ld list[float] — lift-to-drag ratios
.converged list[SolveResult] — converged results only
.results list[SolveResult] — all results
.to_dict() Export as dict
.to_dataframe() Export as pandas.DataFrame
.plot(show=True) 4-panel matplotlib figure

RunDatabase

Method Description
.insert_run(...) Insert a solver run
.lookup_cache(...) Cache lookup by (hash, alpha, Re, ...)
.query_all_runs() All runs as list[dict]
.query_runs(airfoil_name, limit, offset) Filtered query
.row_count() Number of cached runs
.delete_all_runs() Clear all runs
.save_airfoil(name, coords_json) Save a named airfoil
.list_airfoils() List saved airfoils
.export_bytes() Export SQLite as bytes
.import_bytes(data) Import SQLite from bytes

Configuration

Environment variable Default Description
FLEXFOIL_DATA_DIR ~/.flexfoil Directory for runs.db

How it works

┌──────────────────────────────────┐     ┌──────────────────────────┐
│  Python                          │     │  Browser                 │
│                                  │     │                          │
│  import flexfoil                 │     │  localhost:8420           │
│  foil.solve() ──► Rust solver    │     │  React SPA               │
│        │         (PyO3)          │     │  WASM solver ──┐         │
│        ▼                         │     │                │         │
│  ~/.flexfoil/runs.db ◄──────────────── REST API ◄──────┘         │
│  (SQLite, WAL mode)              │     │                          │
│                                  │     │  SSE live updates ◄──┐  │
│  flexfoil.serve() ───► Starlette ──────►                     │  │
│                        uvicorn   │     │  (runs appear as     │  │
│                                  │     │   Python solves them) │  │
│  foil.polar() ──► insert rows ───────────────────────────────┘  │
└──────────────────────────────────┘     └──────────────────────────┘

The Rust solver called from Python is the exact same code as the WASM solver in the browser — both are compiled from the rustfoil-xfoil crate. Results are byte-identical.

Examples

See the examples/ directory:

Script What it does
01_quickstart.py Single-point solve
02_polar_sweep.py Full polar with table + plot
03_compare_airfoils.py Overlay multiple NACA foils
04_reynolds_sweep.py Re effect on drag polar
05_dat_file.py Load from .dat file
06_pandas_export.py Export to CSV via pandas
07_inviscid_vs_viscous.py Inviscid vs viscous comparison
08_batch_matrix.py Batch sweep: airfoils x Re x alpha
09_custom_coordinates.py Build airfoil from x, y arrays

Development

cd packages/flexfoil-python

# Install Rust toolchain and maturin
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
pip install maturin

# Build and install in development mode
maturin develop --extras dev

# Run tests
pytest tests/ -v

# Bundle the web UI (optional, for `flexfoil serve`)
./build_frontend.sh

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flexfoil-1.1.2.tar.gz (536.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

flexfoil-1.1.2-cp311-cp311-win_amd64.whl (4.6 MB view details)

Uploaded CPython 3.11Windows x86-64

flexfoil-1.1.2-cp311-cp311-macosx_11_0_arm64.whl (4.7 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

flexfoil-1.1.2-cp311-cp311-macosx_10_12_x86_64.whl (4.7 MB view details)

Uploaded CPython 3.11macOS 10.12+ x86-64

flexfoil-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.7 MB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

File details

Details for the file flexfoil-1.1.2.tar.gz.

File metadata

  • Download URL: flexfoil-1.1.2.tar.gz
  • Upload date:
  • Size: 536.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for flexfoil-1.1.2.tar.gz
Algorithm Hash digest
SHA256 db4938b84a215ae46baa009d9d2a38eb7fce366cd44da89f7ec1789830e49ba5
MD5 41babe3fe5c8e39e51133d29c0dff781
BLAKE2b-256 6af9743388f0e2d02331d8354107831de0a1282ec1a1c7828e3152218f7881d4

See more details on using hashes here.

File details

Details for the file flexfoil-1.1.2-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: flexfoil-1.1.2-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 4.6 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for flexfoil-1.1.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 901b0a0aaba82e64ba1297e300ae29cfc66c29223897b9ecc54fb3bfbae3d4bd
MD5 45889d5b6ad4b0e135e38455186d7956
BLAKE2b-256 130c321b39bb5a737e8a5188e8dcf9a345b21f11db6d00865bc0131e5524e695

See more details on using hashes here.

File details

Details for the file flexfoil-1.1.2-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for flexfoil-1.1.2-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0d1ce8603ce7e4032812002d9c061b6705f191576d419be7f5cfb1bf99a91599
MD5 b831688dd9e2da5f44c8940fb5c69547
BLAKE2b-256 ed17a332f90d34928293a578741c49cc321d6957def124f4b72937b845a24914

See more details on using hashes here.

File details

Details for the file flexfoil-1.1.2-cp311-cp311-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for flexfoil-1.1.2-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 16c350a1ab917fcf708b7b770f88f2caf8b84fb7c3ad12a8fe2fd3d62aaaec47
MD5 11d455a6e80b84eae926a721aec5e761
BLAKE2b-256 b895da023e5c888a961a1736e1e72b3853a7154d2d8e7e137fa751cb6328cd3c

See more details on using hashes here.

File details

Details for the file flexfoil-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for flexfoil-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 998ef59d7162a7e53e3e304cd71c45e5b9ccb72f5f11aaed9fa84b216a3fd64b
MD5 47c5f16c4c48eab3de11a920d2614b66
BLAKE2b-256 51ac2e8ad29585516269082b99a4e6726536107506b4f370465e8e63557b2542

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page