🔬
Solver Purpose & Physical Scope
Solves supersonic flow expansion around a convex corner. Using the Prandtl-Meyer function, it relates the deflection angle to the upstream and downstream Mach numbers. A high-precision Newton-Raphson iterative solver is used to find the exit Mach number. It also evaluates isentropic static pressure, temperature, and density ratios, as well as the orientations of the leading and trailing Mach waves.
📂 Discipline: Fluid
⚡ Precision: IEEE-754 64-bit Real(`real(8)`)
📥 Total Downloads: 701 times
📄 Source File:
prandtl_meyer.f90
📁 calcul/FluidMechanics /
prandtl_meyer.f90
! ============================================================================
! ThermoFluidCalc — Prandtl-Meyer Expansion Fan Solver
! Reference: Anderson Modern Compressible Flow Ch. 4
! ============================================================================
program prandtl_meyer
implicit none
! Input variables
integer :: solve_for ! 1 = Given M1 & theta, solve M2; 2 = Given M1 & M2, solve theta; 3 = Given M2 & theta, solve M1
double precision :: M1 ! Upstream Mach number
double precision :: theta ! Deflection angle [deg]
double precision :: M2 ! Downstream Mach number
double precision :: gamma ! Specific heat ratio (default 1.4)
double precision :: T1 ! Upstream Temperature [K] (0 = skip)
double precision :: P1 ! Upstream Pressure [kPa] (0 = skip)
! Constants
double precision, parameter :: pi = 3.141592653589793d0
double precision, parameter :: rad = pi / 180.0d0
double precision, parameter :: R_air = 287.0d0 ! Gas constant for air [J/kg-K]
! Intermediate and output variables
double precision :: nu1_rad, nu2_rad, theta_rad
double precision :: mu1, mu2, nu_max_rad, nu_max_deg
double precision :: P2_P1, T2_T1, rho2_rho1
double precision :: T2, P2, a1, a2, V1, V2
! Read input from stdin
read(*,*) solve_for
read(*,*) M1
read(*,*) theta
read(*,*) M2
read(*,*) gamma
read(*,*) T1
read(*,*) P1
! Check inputs
if (gamma <= 1.0d0) gamma = 1.4d0
! Compute nu_max analytically for the given gamma
nu_max_rad = (sqrt((gamma + 1.0d0) / (gamma - 1.0d0)) - 1.0d0) * (pi / 2.0d0)
nu_max_deg = nu_max_rad / rad
! ── PERFORM SOLVING ACCORDING TO MODE ────────────────────
select case (solve_for)
case (1) ! Given M1 and theta, solve M2
if (M1 <= 1.0d0) then
write(*,*) "ERROR: Upstream Mach number M1 must be supersonic (> 1.0) for Prandtl-Meyer expansion."
stop
end if
nu1_rad = nu_mach(M1, gamma)
theta_rad = theta * rad
nu2_rad = nu1_rad + theta_rad
if (nu2_rad >= nu_max_rad) then
write(*,*) "ERROR: Deflection angle theta exceeds maximum possible turn angle for M1 = ", M1
stop
end if
M2 = solve_mach_from_nu(nu2_rad, gamma, M1)
case (2) ! Given M1 and M2, solve theta
if (M1 <= 1.0d0 .or. M2 <= 1.0d0) then
write(*,*) "ERROR: Mach numbers must be supersonic (> 1.0) for expansion waves."
stop
end if
if (M2 < M1) then
write(*,*) "ERROR: Downstream Mach number M2 must be greater than upstream Mach number M1."
stop
end if
nu1_rad = nu_mach(M1, gamma)
nu2_rad = nu_mach(M2, gamma)
theta_rad = nu2_rad - nu1_rad
theta = theta_rad / rad
case (3) ! Given M2 and theta, solve M1
if (M2 <= 1.0d0) then
write(*,*) "ERROR: Downstream Mach number M2 must be supersonic (> 1.0)."
stop
end if
nu2_rad = nu_mach(M2, gamma)
theta_rad = theta * rad
nu1_rad = nu2_rad - theta_rad
if (nu1_rad < 0.0d0) then
write(*,*) "ERROR: Deflection angle theta exceeds total downstream PM angle."
stop
end if
M1 = solve_mach_from_nu(nu1_rad, gamma, 1.5d0)
case default
write(*,*) "ERROR: Invalid solve target mode."
stop
end select
! ── COMPUTE DOWNSTREAM PROPERTIES ────────────────────────
nu1_rad = nu_mach(M1, gamma)
nu2_rad = nu_mach(M2, gamma)
mu1 = asin(1.0d0 / M1) / rad
mu2 = asin(1.0d0 / M2) / rad
! Isentropic ratios
T2_T1 = (1.0d0 + (gamma - 1.0d0)/2.0d0 * M1**2) / (1.0d0 + (gamma - 1.0d0)/2.0d0 * M2**2)
P2_P1 = (T2_T1)**(gamma / (gamma - 1.0d0))
rho2_rho1 = (T2_T1)**(1.0d0 / (gamma - 1.0d0))
! Optional static temperatures/pressures calculation
T2 = 0.0d0
P2 = 0.0d0
V1 = 0.0d0
V2 = 0.0d0
if (T1 > 0.0d0) then
T2 = T1 * T2_T1
a1 = sqrt(gamma * R_air * T1)
a2 = sqrt(gamma * R_air * T2)
V1 = M1 * a1
V2 = M2 * a2
end if
if (P1 > 0.0d0) then
P2 = P1 * P2_P1
end if
! ── OUTPUT RESULTS IN KEY-VALUE FORMAT ───────────────────
write(*, '(A, F14.6)') "Upstream Mach (M1) = ", M1
write(*, '(A, F14.6)') "Downstream Mach (M2) = ", M2
write(*, '(A, F14.6)') "Deflection Angle (theta) = ", theta
write(*, '(A, F14.6)') "PM Angle Upstream (nu1) = ", nu1_rad / rad
write(*, '(A, F14.6)') "PM Angle Downstream (nu2) = ", nu2_rad / rad
write(*, '(A, F14.6)') "Mach Angle Upstream (mu1) = ", mu1
write(*, '(A, F14.6)') "Mach Angle Downstream (mu2) = ", mu2
write(*, '(A, F14.6)') "Max Turning Angle (nu_max) = ", nu_max_deg
write(*, '(A, F14.6)') "Pressure Ratio (P2/P1) = ", P2_P1
write(*, '(A, F14.6)') "Temperature Ratio (T2/T1) = ", T2_T1
write(*, '(A, F14.6)') "Density Ratio (rho2/rho1) = ", rho2_rho1
write(*, '(A, F14.6)') "Specific Heat Ratio (gamma) = ", gamma
if (T1 > 0.0d0) then
write(*, '(A, F14.4)') "Upstream Temp (T1) = ", T1
write(*, '(A, F14.4)') "Downstream Temp (T2) = ", T2
write(*, '(A, F14.2)') "Upstream Velocity (V1) = ", V1
write(*, '(A, F14.2)') "Downstream Velocity (V2) = ", V2
end if
if (P1 > 0.0d0) then
write(*, '(A, F14.4)') "Upstream Pressure (P1) = ", P1
write(*, '(A, F14.4)') "Downstream Pressure (P2) = ", P2
end if
contains
! Prandtl-Meyer function calculation: nu(M) in radians
double precision function nu_mach(M, g)
double precision, intent(in) :: M, g
double precision :: factor
if (M <= 1.0d0) then
nu_mach = 0.0d0
else
factor = sqrt((g + 1.0d0) / (g - 1.0d0))
nu_mach = factor * atan(sqrt((g - 1.0d0) / (g + 1.0d0) * (M**2 - 1.0d0))) - &
atan(sqrt(M**2 - 1.0d0))
end if
end function nu_mach
! Derivative of the Prandtl-Meyer function: dnu/dM
double precision function dnu_dM(M, g)
double precision, intent(in) :: M, g
if (M <= 1.0d0) then
dnu_dM = 1.0d-10 ! Avoid division by zero
else
dnu_dM = sqrt(M**2 - 1.0d0) / (M * (1.0d0 + (g - 1.0d0) / 2.0d0 * M**2))
end if
end function dnu_dM
! Newton-Raphson solver to find M from a target nu in radians
double precision function solve_mach_from_nu(nu_target, g, start_M)
double precision, intent(in) :: nu_target, g, start_M
double precision :: M, diff, f_val, df_val
integer :: i
if (nu_target <= 1.0d-12) then
solve_mach_from_nu = 1.0d0
return
end if
! Initialize solver with starting Mach or standard guess
M = start_M
if (M <= 1.0d0) M = 2.0d0
do i = 1, 100
f_val = nu_mach(M, g) - nu_target
df_val = dnu_dM(M, g)
diff = f_val / df_val
M = M - diff
if (M < 1.0d0) M = 1.0001d0 ! Floor Mach to supersonic limit
if (abs(diff) < 1.0d-14) exit
end do
solve_mach_from_nu = M
end function solve_mach_from_nu
end program prandtl_meyer
💻 How to Compile & Run Locally
1. Compilation (GNU Fortran / Intel oneAPI):
gfortran -O3 prandtl_meyer.f90 -o prandtl_meyer_calc
2. Execution with input.txt redirection:
prandtl_meyer_calc < input.txt
📄 Sample input.txt File Structure
Sample Data:
1 2.0 15.0 0.0 1.4 288.15 101.325
Parameter Description:
Solve mode (1=Solve M2, 2=Solve theta, 3=Solve M1) Upstream Mach M1 Deflection angle theta [deg] Downstream Mach M2 Specific heat ratio gamma Upstream Temp T1 [K] Upstream Pressure P1 [kPa]