You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Python BEM is a Blade Element Momentum Theory tool for wind turbines and propellers.
This README is an implementation reference for the current codebase. The equations below are written from the actual solver and helper modules, primarily calculation.py, popravki.py, utils.py, montgomerie.py, and optimization.py.
What The Program Does
The software:
manages airfoil polar data,
extrapolates airfoil polars to 360 degrees,
generates preliminary blade geometry,
solves blade sections with BEM,
combines section results into rotor-level performance,
performs simple structural post-processing,
and runs numerical optimization of blade geometry.
Installation And Launch
Requirements
Python 3
packages from requirements.txt
Install
pip install -r requirements.txt
Run
python bem.py
Build Executable
pyinstaller main.spec
macOS Note For XFOIL
brew install xquartz
Code Map
calculation.py: main BEM solver and post-processing
popravki.py: induction-factor and correction models
utils.py: interpolation and geometry-generation helpers
montgomerie.py: 360 degree polar extrapolation
optimization.py: section-wise and whole-blade optimization
Notation
The same symbols are used below as in the code where practical.
Symbol
Meaning
Units
$R$
tip radius
m
$R_{hub}$
hub radius
m
$r$
local section radius
m
$dr$
section spanwise width
m
$B$
number of blades
-
$c$
local chord
m
$\theta$
local twist angle
deg or rad depending on context
$p$
blade pitch
deg
$v$
freestream speed
m/s
$\Omega$
angular speed
rad/s
$rpm$
rotational speed
rev/min
$\lambda$
tip-speed ratio
-
$\lambda_r$
local tip-speed ratio
-
$a$
axial induction factor
-
$a'$
tangential induction factor
-
$\phi$
relative-flow angle
rad or deg depending on context
$\alpha$
angle of attack
rad or deg depending on context
$W$
relative speed magnitude
m/s
$Re$
Reynolds number
-
$\rho$
fluid density
kg/m$^3$
$\nu$
kinematic viscosity
m$^2$/s
$C_l$
lift coefficient
-
$C_d$
drag coefficient
-
$C_n$
normal-force coefficient
-
$C_t$
tangential-force coefficient
-
$F$
combined loss factor
-
$\Gamma_B$
bound circulation proxy used in code
m$^2$/s
Solver Workflow
The main rotor calculation is Calculator.run_array(...) in calculation.py.
The code solves each blade section independently, then combines section results into rotor totals.
1. Rotor-Level Precomputation
The angular speed is
$$
\Omega = rpm \cdot \frac{2\pi}{60}
$$
The tip-speed ratio is
$$
\lambda = \frac{\Omega R}{v}
$$
For propeller reporting, the advance ratio is
$$
J = \frac{v}{(rpm/60) \cdot 2R}
$$
2. Section Geometry Quantities
For each section, the local tip-speed ratio is
$$
\lambda_r = \frac{\Omega r}{v}
$$
The local solidity is
$$
\sigma = \frac{cB}{2\pi r}
$$
Parameters:
$\sigma$: local blade solidity
$c$: local chord
$B$: number of blades
$r$: local radius
3. Velocity Components Used By The Code
The wind is resolved in the blade coordinate system (Ning, CCBlade) with yaw, tilt, coning and the blade azimuth $\psi_b$ ($0$ = blade pointing up):
In the current implementation, the section-loop sets $\psi = 0$ and $\tau = 0$.
Yawed Inflow: Azimuthal Averaging
In axial inflow every blade position sees the same flow and each section is solved once. In yawed inflow the in-plane wind component $v\sin\gamma$ adds to or subtracts from the rotational speed depending on where the blade is, so each section is solved at num_azimuth equally spaced blade positions (default 16) and every result is averaged over the revolution; the rotor loads are the time averages. The momentum balance uses the axial component $V_x$, consistent with $U_n = V_x(1\mp a)$.
4. Induced Velocity Components
For a wind turbine:
$$
U_n = V_x(1-a)
$$
$$
U_t = V_y(1+a')
$$
For a propeller:
$$
U_n = V_x(1+a)
$$
$$
U_t = V_y(1-a')
$$
The relative-flow angle is
$$
\phi = atan2(U_n, U_t)
$$
The relative speed magnitude is
$$
W = \sqrt{U_n^2 + U_t^2}
$$
5. Reynolds Number
If Reynolds number is not forced by the user, the code computes
$$
Re = \frac{Wc}{\nu}
$$
If fix_reynolds=True, the user-supplied constant Reynolds number is used instead.
The vortex sheets of the root vortex have the same helix pitch as at the tip ($r\tan\phi = R_{hub}\tan\phi_{hub}$), so with the same small-angle step as for the tip ($R_{hub}\sin\phi_{hub} \approx r\sin\phi$) the hub loss is
multiplies the blade-element force coefficients, $C_n \leftarrow F_1C_n$ and $C_t \leftarrow F_1C_t$, so that the loads go to zero at the tip. $\lambda = \Omega R / v$ is the tip speed ratio of the rotor.
Modified Hub Loss
Analogous to the tip: the Prandtl hub factor stays in the momentum balance and
where $\theta$ and $p$ are converted to radians inside the solver before use.
Sections Where The Loss Factor Is Zero
With tip loss, $F = 0$ at $r = R$; with hub loss, $F = 0$ at $r = R_{hub}$. The bound circulation and the loads vanish there, and the BEM equations are singular, so such a section is not iterated: its loads, $F$, $a$ and $a'$ are zero.
8. Cascade Correction
This correction is formulated for wind turbines only; the solver refuses it for a propeller.
The drag term is Eggers et al. (2003), as in AirfoilPrep / WISDEM: it turns the added force almost normal to the chord, because delayed separation raises the normal force, not the leading-edge suction (pressure measurements on rotating blades, NREL Phase VI and MEXICO). The weight $w$ blends the correction out in deep stall, where there is no attached flow left to delay: $w = 1$ for $|\alpha| \le 25^\circ$, falling linearly to $0$ at $45^\circ$ (AirfoilPrep "linear_25_45").
Rotational augmentation is delayed separation, so it recovers at most the lift lost to separation: the correction acts only where $C_{l,lin} - C_l$ has the sign of $C_{l,lin}$, with $0 \le f_l \le 1$, and not on sections without a lift slope ($C_{l,\alpha} < 1$/rad, e.g. cylinder roots). Gur & Rosen keeps its own sign convention. The function $f_l$ depends on the selected model:
If enabled, the code applies the Prandtl-Glauert correction to lift
$$
C_l \leftarrow \frac{C_l}{\sqrt{1-M^2}}
$$
with the local Mach number of the relative flow at the section
$$
M = \frac{W}{a_s}
$$
where $a_s$ is the speed of sound (speed_of_sound, default 343 m/s for air at 20 °C).
The drag coefficient is not scaled, and the correction is skipped where $M \ge 1$.
13. Circulation Proxy
The code computes
$$
\Gamma_B = \frac{1}{2}WcC_l
$$
This is later used in the optional wake-rotation quantity
$$
k = \frac{\Omega \Gamma_B}{\pi v^2}
$$
$$
a'_{vct} = \frac{k}{4\lambda_r^2}
$$
The value $a'_{vct}$ is calculated but not used as the final tangential induction value.
14. Normal And Tangential Aerodynamic Coefficients
For a propeller:
$$
C_n = C_l\cos\phi - C_d\sin\phi
$$
$$
C_t = C_l\sin\phi + C_d\cos\phi
$$
For a wind turbine:
$$
C_n = C_l\cos\phi + C_d\sin\phi
$$
$$
C_t = C_l\sin\phi - C_d\cos\phi
$$
15. Section Lift, Drag, Normal Force, And Tangential Force
Lift and drag on a blade element are
$$
dF_L = \frac{1}{2}\rho W^2 c,dr,C_l
$$
$$
dF_D = \frac{1}{2}\rho W^2 c,dr,C_d
$$
The code resolves them with the normal and tangential coefficients of the selected turbine type:
$$
dF_n = \frac{1}{2}\rho W^2 c,dr,C_n
$$
$$
dF_t = \frac{1}{2}\rho W^2 c,dr,C_t
$$
For a wind turbine this is $dF_n = dF_L\cos\phi + dF_D\sin\phi$, $dF_t = dF_L\sin\phi - dF_D\cos\phi$; for a propeller $dF_n = dF_L\cos\phi - dF_D\sin\phi$, $dF_t = dF_L\sin\phi + dF_D\cos\phi$.
The per-unit-length outputs stored as dFt/n and dFn/n are
$$
\frac{dF_t}{dr}
$$
$$
\frac{dF_n}{dr}
$$
The local thrust coefficient used by the induction models is
The tangential induction follows from the tangential momentum balance:
$$
a' = \frac{\sigma C_t}{4F\sin\phi\cos\phi - \sigma C_t}
$$
QBlade uses the drag-free relation $a'(1+a')\lambda_r^2 = a(1-a)$ instead. It does not hold with drag, while the high-thrust correction replaces only the axial momentum balance, so with the tangential balance this method gives the same result as method 2.
Method 4: Wilson And Walker
With $a_c = 0.2$:
If
$$
C_{T,r} \le 0.64F
$$
then
$$
a = \frac{1 - \sqrt{1 - C_{T,r}/F}}{2}
$$
otherwise
$$
a = \frac{C_{T,r} - 4Fa_c^2}{4F(1-2a_c)}
$$
and
$$
a' = \left(\frac{4F\sin\phi\cos\phi}{\sigma C_t} - 1\right)^{-1}
$$
Method 5: Modified ABS Model
If
$$
C_{T,r} < 0.96F
$$
then
$$
a = \frac{1 - \sqrt{1 - C_{T,r}/F}}{2}
$$
otherwise
$$
a = 0.1432 + \sqrt{-0.55106 + 0.6427\frac{C_{T,r}}{F}}
$$
The tangential induction follows from the tangential momentum balance, as in method 3:
$$
a' = \frac{\sigma C_t}{4F\sin\phi\cos\phi - \sigma C_t}
$$
Propellers are calculated with method 0 only: the flow tube of a propeller only accelerates, so plain momentum theory holds for every $a > 0$, while the high-thrust corrections of methods 1–5 describe the turbulent-wake state of a wind turbine.
17. Relaxation And Convergence
The iterative solver converges when both induction changes satisfy
$$
|a-a_{last}| < \varepsilon
$$
$$
|a'-a'_{last}| < \varepsilon
$$
where $\varepsilon$ is convergence_limit.
If not yet converged, the relaxed update is
$$
a \leftarrow a_{last}(1-f_r) + a f_r
$$
$$
a' \leftarrow a'_{last}(1-f_r) + a' f_r
$$
where $f_r$ is relaxation_factor.
18. Optional Skewed-Wake Correction
The Pitt and Peters correction (as in AeroDyn) is applied once to the converged induction of each blade position, and the loads are then evaluated with the corrected inflow:
where $\gamma$ is the yaw angle and $\psi_{dw}$ the blade azimuth measured from the most downwind blade position ($\cos\psi_{dw} = \sin\psi_b$ in the solver's convention). Over one revolution the correction averages to zero; it moves induction to the downwind half of the disc. Formulated for wind turbines only.
19. Thrust And Torque Equations In The Code
The solver computes both momentum-theory and blade-element expressions.
$$
dQ_{BET,p} = \frac{1}{2}\rho v cB\Omega r^2\frac{(1+a)(1-a')}{\sin\phi\cos\phi}C_t,dr
$$
Which Equations Are Returned
for both turbine types, the implementation returns the blade-element forces dT = dT_BET and dQ = dQ_BET, with $C_n$ and $C_t$ of the selected turbine type (for a propeller these equal dT_BET_p and dQ_BET_p, and at convergence the momentum forms)
20. Wake Speeds Stored In Results
The speed is continuous through the actuator disc, so $U_2 = U_3$.
For propellers:
$$
U_1 = v
$$
$$
U_2 = U_3 = U_1(1+a)
$$
$$
U_4 = U_1(1+2a)
$$
For wind turbines:
$$
U_1 = v
$$
$$
U_2 = U_3 = U_1(1-a)
$$
$$
U_4 = U_1(1-2a)
$$
Rotor-Level Integration And Performance
After all sections are solved, the code aggregates results.
where stall_i is 1 for a stalled section and 0 otherwise.
Structural Post-Processing
Calculator.statical_analysis(...) computes the normal stresses of one blade with Euler-Bernoulli beam theory (no shear, no torsion).
Section Mass
Using the cross-sectional area $A$ from the airfoil geometry routine:
$$
m_{sec} = A,dr,\rho_m
$$
where $\rho_m$ is the blade material density.
Section In The Rotor Frame
Each section (for a transition section, the interpolated airfoil shape) is scaled by the chord and turned by its blade angle $\theta + p$ (twist plus pitch) into the rotor frame: $X$ in the direction of rotation, $Y$ downwind for a wind turbine and in the thrust direction for a propeller. Area, centroid and the second moments of area about the centroid,
$$
I_{xx} = \int Y^2 dA, \quad I_{yy} = \int X^2 dA, \quad I_{xy} = \int XY,dA
$$
are computed with the polygon formulas (a hollow section subtracts the inner outline). The outputs Ix, Iy, Ixy are $I_{xx}$, $I_{yy}$, $I_{xy}$ in mm⁴.
Section Bending Moments
Each section carries the loads of its strip $dr$. For section $i$ the moments of the loads outboard of $r_i$ (the outer half of its own strip included, with its centre at $dr_i/4$) are
where $dF_X = dF_t$ for a wind turbine and $-dF_t$ for a propeller (its tangential force opposes the rotation). Ms_n is $m_Y$ and Ms_t the moment of $dF_t$.
Bending Stress (Unsymmetric Bending)
An airfoil section has no symmetry axis in the rotor frame ($I_{xy} \ne 0$), so the normal stress at a point $(X, Y)$ of the section is
Bending and centrifugal stresses are normal stresses along the span, so the stress state is uniaxial (shear and torsion are not modelled) and the equivalent stress is the largest combined stress over the section outline:
The full list is defined in OUTPUT_VARIABLES_LIST in calculation.py.
Important Implementation Notes
This README documents the code as implemented, not an idealized textbook BEM derivation.
Some formulas differ between wind-turbine and propeller branches by sign convention and by which thrust/torque expression is finally stored.
The Adkins and Montgomerie parts are iterative and partially empirical; their equations above are intended as a code reference.
License
Python BEM - Blade Element Momentum Theory Software.
Copyright (C) 2022 M. Smrekar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/.
About
Python Pyqt5 GUI app for calculating wind turbine and propeller characteristics using BEM theory (Blade Element Momentum Theory).