Julia implementation of a hybrid blade element momentum (BEM) propeller model based on Davoudi [1].
The model supports:
- Linear inflow coupled with BEM, using Pitt-Peters by default
- Multi-threaded sweeps when Julia is started with multiple threads
- Prandtl hub/tip-loss correction [2]
- Glauert Mach correction for polar data
- Offline and online rotational 3D polar correction
- Export helpers for DUST and FLOWUnsteady workflows
src/: package implementationconfig/rotor/: rotor YAML files and source geometry datadata/airfoils/: airfoil coordinate datadata/polars/: polar CSV dataexample.jl: small RPM sweep examplerotor_calculations.jl: pre-calculation helper, for example Reynolds number estimatesvalidation/: validation scripts and reference datamisc/: export and data-generation utilitiestest/: package tests
Install Julia, Git, and Conda or Miniconda first. Run all project commands from the repository root unless noted otherwise.
On Windows PowerShell:
git clone https://github.com/christianhauschel/Propeller.jl C:\path\to\Propeller.jl
Set-Location C:\path\to\Propeller.jl
julia --project=.If the repository is already on a mapped drive, use the normal Windows path:
Set-Location P:\code\09_aero\Propeller.jl
julia --project=.On Linux/macOS:
git clone https://github.com/christianhauschel/Propeller.jl <mypath>
cd <mypath>
julia --project=.In the Julia REPL, instantiate the project:
import Pkg
Pkg.instantiate()This repository contains a Manifest.toml, so Pkg.instantiate() is the preferred path. If Julia cannot resolve the private/unregistered dependencies, add them by URL:
import Pkg
url = "https://github.com/christianhauschel/"
packages = [
"AirfoilPolars.jl",
"AirfoilFast.jl",
"PrettySections.jl",
"Gradient.jl",
"FlightConditions.jl",
"GeometricTools.jl",
"GridSpacing.jl",
]
Pkg.add([Pkg.PackageSpec(url=url * p) for p in packages])
Pkg.instantiate()AirfoilPolars.jl must be available before AirfoilFast.jl, because AirfoilFast.jl depends on it.
Some examples and validation scripts use Python plotting through PyCall and ultraplot.
Create a Python environment.
On Windows PowerShell:
conda create -n julia python=3.10
conda activate julia
python -m pip install ultraplot
where pythonUse the Python path inside the julia environment. It usually looks like:
C:\Users\<user>\AppData\Local\miniconda3\envs\julia\python.exe
If conda activate julia is not recognized in PowerShell, run once:
conda init powershellThen close and reopen PowerShell.
On Linux/macOS:
conda create -n julia python=3.10
conda activate julia
python -m pip install ultraplot
which pythonConfigure PyCall from Julia:
import Pkg
Pkg.add("PyCall")
ENV["PYTHON"] = raw"C:\Users\<user>\AppData\Local\miniconda3\envs\julia\python.exe"
Pkg.build("PyCall")On Linux/macOS, use the path returned by which python:
import Pkg
Pkg.add("PyCall")
ENV["PYTHON"] = "/home/<user>/miniconda3/envs/julia/bin/python"
Pkg.build("PyCall")Restart Julia, then test:
using PyCall
pyversion
uplt = pyimport("ultraplot")From the repository root:
julia --project=. example.jlOr from the Julia REPL:
include("example.jl")For threaded runs:
julia --threads=4 --project=. example.jlThe default example uses:
name_rotor = "apc_11x47SF"which loads:
config/rotor/apc_11x47SF.yaml
Plots and generated files are written below out/.
Available rotor YAML configs include:
apc_11x47SF.yamlapc_845MR.yamlapc_845MR_model.yamldavoudi.yamldji9443.yamldjimatrice300rtk.yamlrotor_anopp.yamlrotor_simple.yamltest.yaml
When changing scripts, set name_rotor to the YAML filename without .yaml. For example:
name_rotor = "dji9443"not dji_9443, because config/rotor/dji_9443/ is a data directory, not a YAML config file.
example.jl: runs an RPM sweep and optionally plots thrust, torque,C_T, andC_Qrotor_calculations.jl: estimates Reynolds number, Mach number, and velocity ranges for a rotorvalidation/apc11x47.jl: APC 11x4.7 static validation sweepvalidation/davoudi.jl: comparison against Davoudi reference datavalidation/loading.jl: normal and tangential loading comparisonvalidation/polarmodel.jl: compares model polars against polar datamisc/export_dust.jl: exports a rotor blade geometry for DUSTmisc/export_flowunsteady.jl: exports rotor data for FLOWUnsteadymisc/generate_training.jl: generates training samples and writestrainingdata.csvmisc/airfoil_csv2dat.jl: converts airfoil CSV data to DAT-style files
Most scripts have settings near the top. Edit name_rotor, RPM ranges, plotting flags, and output folders there.
Run the package tests with:
julia --project=. -e "import Pkg; Pkg.test()"If Julia cannot find a config file, check that you are in the repo root:
pwd()
readdir("config/rotor")If build("PyCall") fails with build not defined, use:
import Pkg
Pkg.build("PyCall")If pyversion is not defined, load PyCall first:
using PyCall
pyversionIf using PyCall fails because Python cannot import ctypes, verify the Python environment outside Julia:
conda activate julia
python -c "import ctypes; print('ctypes ok')"
conda install -y libffiThen rebuild PyCall and restart Julia.
Simple comparison with uniform inflow:
Data taken from Davoudi [1]:
Comparison to FLOWUnsteady BEM code:
- B. Davoudi, "A Hybrid Blade Element Momentum Model for Flight Simulation of Rotary Wing Unmanned Aerial Vehicles," AIAA Paper, 2019.
- https://flow.byu.edu/FLOWUnsteady/examples/rotorhover-aero/







