| """
|
| AlgoRythm Prandtl Aero — Deterministic Physics Core
|
| All calculations in SI units internally, output in mm for PicoGK.
|
| Validated against: RL-10, Merlin 1D, Raptor 3, Rutherford
|
| """
|
| import math
|
|
|
| G0 = 9.80665
|
|
|
|
|
| PROPELLANTS = {
|
| "LOX/LH2": {"gamma": 1.20, "Tc": 3500, "cstar": 2360, "Isp_vac": 450, "Isp_sl": 363, "Rspec": 692.0, "MW": 12.0, "rho_f": 70.8, "rho_o": 1141, "OF": 5.5},
|
| "LOX/RP1": {"gamma": 1.24, "Tc": 3670, "cstar": 1800, "Isp_vac": 338, "Isp_sl": 311, "Rspec": 362.0, "MW": 23.0, "rho_f": 810, "rho_o": 1141, "OF": 2.34},
|
| "LOX/CH4": {"gamma": 1.20, "Tc": 3540, "cstar": 1860, "Isp_vac": 363, "Isp_sl": 330, "Rspec": 519.0, "MW": 16.0, "rho_f": 422.6, "rho_o": 1141, "OF": 3.5},
|
| "N2O4/UDMH":{"gamma": 1.25, "Tc": 3150, "cstar": 1720, "Isp_vac": 320, "Isp_sl": 285, "Rspec": 340.0, "MW": 24.5, "rho_f": 793, "rho_o": 1440, "OF": 2.0},
|
| }
|
|
|
|
|
| MATERIALS = {
|
| "Inconel_718": {"sigma_y_MPa": 1035, "sigma_u_MPa": 1240, "E_GPa": 205, "rho": 8190, "k_W_mK": 11.4, "Tmax_K": 990, "CTE": 13e-6},
|
| "Inconel_625": {"sigma_y_MPa": 490, "sigma_u_MPa": 827, "E_GPa": 205, "rho": 8440, "k_W_mK": 9.8, "Tmax_K": 980, "CTE": 12.8e-6},
|
| "CuCrZr": {"sigma_y_MPa": 320, "sigma_u_MPa": 440, "E_GPa": 130, "rho": 8900, "k_W_mK": 320, "Tmax_K": 573, "CTE": 17e-6},
|
| "GRCop84": {"sigma_y_MPa": 207, "sigma_u_MPa": 345, "E_GPa": 115, "rho": 8810, "k_W_mK": 285, "Tmax_K": 700, "CTE": 17.7e-6},
|
| "Haynes_230": {"sigma_y_MPa": 390, "sigma_u_MPa": 860, "E_GPa": 211, "rho": 8970, "k_W_mK": 8.9, "Tmax_K": 1420, "CTE": 12.7e-6},
|
| "Ti6Al4V": {"sigma_y_MPa": 880, "sigma_u_MPa": 950, "E_GPa": 114, "rho": 4430, "k_W_mK": 6.7, "Tmax_K": 600, "CTE": 8.6e-6},
|
| "C103_Niobium": {"sigma_y_MPa": 270, "sigma_u_MPa": 400, "E_GPa": 95, "rho": 8850, "k_W_mK": 43, "Tmax_K": 1650, "CTE": 6.5e-6},
|
| }
|
|
|
|
|
| REFERENCE_ENGINES = {
|
| "RL-10A": {"thrust_N": 110000, "Pc_bar": 44, "prop": "LOX/LH2", "Isp": 465, "Dt_mm": 102, "eps": 84, "cycle": "Expander"},
|
| "Merlin_1D": {"thrust_N": 845000, "Pc_bar": 97, "prop": "LOX/RP1", "Isp": 311, "Dt_mm": 262, "eps": 16, "cycle": "Gas Generator"},
|
| "Raptor_3": {"thrust_N": 2690000, "Pc_bar": 350, "prop": "LOX/CH4", "Isp": 350, "Dt_mm": 200, "eps": 40, "cycle": "Full-Flow Staged"},
|
| "Vulcain_2": {"thrust_N": 1359000, "Pc_bar": 115, "prop": "LOX/LH2", "Isp": 434, "Dt_mm": 270, "eps": 58, "cycle": "Gas Generator"},
|
| "Rutherford": {"thrust_N": 25000, "Pc_bar": 12, "prop": "LOX/RP1", "Isp": 343, "Dt_mm": 60, "eps": 12.6, "cycle": "Electric Pump"},
|
| "RD-180": {"thrust_N": 4152000, "Pc_bar": 267, "prop": "LOX/RP1", "Isp": 338, "Dt_mm": 330, "eps": 36.9, "cycle": "Staged Combustion"},
|
| "Vinci": {"thrust_N": 180000, "Pc_bar": 61, "prop": "LOX/LH2", "Isp": 465, "Dt_mm": 135, "eps": 240, "cycle": "Expander"},
|
| "BE-4": {"thrust_N": 2400000, "Pc_bar": 134, "prop": "LOX/CH4", "Isp": 340, "Dt_mm": 310, "eps": 35, "cycle": "Oxidizer-Rich Staged"},
|
| }
|
|
|
|
|
| def calc_thrust_coefficient(gamma, eps, Pc_Pa, Pe_Pa, Pa_Pa=0):
|
| """Thrust coefficient Cf from isentropic relations (NASA SP-125)"""
|
| g = gamma
|
| term1 = (2*g*g/(g-1)) * (2/(g+1))**((g+1)/(g-1))
|
| pr = Pe_Pa / Pc_Pa
|
| term2 = 1 - pr**((g-1)/g)
|
| Cf = math.sqrt(term1 * term2) + eps * (Pe_Pa - Pa_Pa) / Pc_Pa
|
| return max(Cf, 0.8)
|
|
|
| def calc_exit_pressure(gamma, eps):
|
| """Iterative solve for Pe/Pc from area ratio using Newton's method"""
|
| g = gamma
|
| pr = 0.01
|
| for _ in range(50):
|
| M2_term = (2/(g-1)) * (pr**(-(g-1)/g) - 1)
|
| if M2_term <= 0:
|
| pr *= 0.5
|
| continue
|
| Me = math.sqrt(M2_term)
|
| eps_calc = (1/Me) * ((2/(g+1)) * (1 + (g-1)/2 * Me*Me))**((g+1)/(2*(g-1)))
|
| deps = eps_calc - eps
|
| if abs(deps) < 0.001:
|
| break
|
| pr *= (1 - 0.1 * deps / max(eps, 1))
|
| pr = max(pr, 1e-6)
|
| return pr
|
|
|
| def calc_throat_area(thrust_N, Pc_bar, Cf):
|
| """A* = F / (Pc × Cf) — returns m²"""
|
| Pc_Pa = Pc_bar * 1e5
|
| return thrust_N / (Pc_Pa * Cf)
|
|
|
| def throat_area_to_diameter_mm(At_m2):
|
| """Convert throat area (m²) to diameter (mm)"""
|
| Dt_m = math.sqrt(4 * At_m2 / math.pi)
|
| return Dt_m * 1000
|
|
|
| def calc_exit_diameter_mm(Dt_mm, eps):
|
| """De = Dt × sqrt(ε)"""
|
| return Dt_mm * math.sqrt(eps)
|
|
|
| def calc_mass_flow(thrust_N, Isp_s):
|
| """mdot = F / (Isp × g0) — returns kg/s"""
|
| return thrust_N / (Isp_s * G0)
|
|
|
| def calc_cstar(Tc, gamma, Rspec):
|
| """c* = sqrt(gamma*Rspec*Tc) / (gamma * sqrt((2/(gamma+1))^((gamma+1)/(gamma-1))))"""
|
| g = gamma
|
| num = math.sqrt(g * Rspec * Tc)
|
| denom = g * math.sqrt((2/(g+1))**((g+1)/(g-1)))
|
| return num / denom
|
|
|
| def calc_nozzle_length_mm(Dt_mm, De_mm, half_angle_deg=15.0, percent_bell=80.0):
|
| """Rao 80% bell nozzle length"""
|
| Rt = Dt_mm / 2
|
| Re = De_mm / 2
|
| Ln_conical = (Re - Rt) / math.tan(math.radians(half_angle_deg))
|
| return Ln_conical * (percent_bell / 100.0)
|
|
|
| def calc_chamber_length_mm(cstar, Pc_bar, At_m2, mdot, Lstar_m=1.0):
|
| """Chamber length from L* (characteristic length)"""
|
| Vc_m3 = Lstar_m * At_m2
|
| Ac_m2 = At_m2 * 3.0
|
| Lc_m = Vc_m3 / Ac_m2
|
| return Lc_m * 1000
|
|
|
| def calc_chamber_diameter_mm(Dt_mm, contraction_ratio=3.0):
|
| """Dc = Dt × sqrt(CR)"""
|
| return Dt_mm * math.sqrt(contraction_ratio)
|
|
|
| def calc_hoop_stress_MPa(Pc_bar, inner_D_mm, wall_t_mm):
|
| """Thin-wall hoop stress: σ = P×r / t (all consistent units)"""
|
| P_MPa = Pc_bar * 0.1
|
| r_mm = inner_D_mm / 2.0
|
| return (P_MPa * r_mm) / wall_t_mm
|
|
|
| def calc_margin_of_safety(sigma_yield_MPa, sigma_applied_MPa, safety_factor=1.25):
|
| """MoS = (σ_yield / (SF × σ_applied)) - 1"""
|
| return (sigma_yield_MPa / (safety_factor * sigma_applied_MPa)) - 1.0
|
|
|
| def calc_bartz_heat_flux(Pc_bar, Dt_mm, cstar, mdot, Tc, gamma, Pr=0.5, mu=5e-5):
|
| """Bartz correlation for convective heat transfer at throat (W/m²)"""
|
| Dt_m = Dt_mm / 1000
|
| At_m2 = math.pi * (Dt_m/2)**2
|
| g = gamma
|
| sigma = 1.0
|
| Cp = gamma * 287 / (gamma - 1)
|
| h_g = (0.026 / (Dt_m**0.2)) * ((mu**0.2 * Cp) / (Pr**0.6)) * \
|
| ((Pc_bar * 1e5 / cstar)**0.8) * (Dt_m / 1.0)**0.1 * sigma
|
| q = h_g * (Tc * 0.9)
|
| return q / 1e6
|
|
|
| def calc_coolant_velocity(q_MW_m2, Tc_wall_K, Tc_cool_K, rho_cool, Cp_cool):
|
| """Required coolant velocity for given heat flux"""
|
| q_W = q_MW_m2 * 1e6
|
| dT = Tc_wall_K - Tc_cool_K
|
| return q_W / (rho_cool * Cp_cool * dT)
|
|
|
| def select_wall_thickness_mm(Pc_bar, Dt_mm, material_key, target_MoS=0.5):
|
| """Auto-select wall thickness for target MoS"""
|
| mat = MATERIALS[material_key]
|
| sigma_y = mat["sigma_y_MPa"]
|
| P_MPa = Pc_bar * 0.1
|
| r_mm = Dt_mm / 2
|
|
|
| t_mm = (P_MPa * r_mm * 1.25 * (1 + target_MoS)) / sigma_y
|
| return max(round(t_mm, 2), 0.5)
|
|
|
| def select_cycle(thrust_N, propellant):
|
| """Deterministic power cycle selection based on physics"""
|
| p = PROPELLANTS[propellant]
|
| if thrust_N > 1500000:
|
| if "LH2" in propellant:
|
| return "Gas Generator", "High thrust LH2: GG avoids LH2 pre-burner complexity (Vulcain heritage)."
|
| return "Staged Combustion", "High thrust >1.5MN requires high Pc for compact design. Staged combustion maximizes Isp."
|
| elif thrust_N > 500000:
|
| if "CH4" in propellant:
|
| return "Full-Flow Staged Combustion", "Medium-high thrust CH4: FFSC maximizes Isp and eliminates turbine coking (Raptor heritage)."
|
| return "Gas Generator", "Medium thrust: GG provides simplicity with adequate performance."
|
| elif "LH2" in propellant and thrust_N < 200000:
|
| return "Expander Cycle", "Low thrust LH2: High heat capacity enables turbine drive from jacket heat alone (RL-10 heritage)."
|
| elif thrust_N < 5000:
|
| return "Pressure-Fed", "Very low thrust: Pressure-fed eliminates turbopump mass and complexity."
|
| else:
|
| return "Gas Generator", "Default: GG provides best reliability-to-performance ratio."
|
|
|
| def validate_against_reference(thrust_N, Pc_bar, prop, Dt_calc_mm):
|
| """Check calculated throat against nearest reference engine"""
|
| best_match = None
|
| best_dist = float('inf')
|
| for name, ref in REFERENCE_ENGINES.items():
|
| if ref["prop"] == prop:
|
| dist = abs(ref["thrust_N"] - thrust_N) / ref["thrust_N"]
|
| if dist < best_dist:
|
| best_dist = dist
|
| best_match = (name, ref)
|
| if best_match and best_dist < 0.3:
|
| name, ref = best_match
|
| ratio = Dt_calc_mm / ref["Dt_mm"]
|
| scaled_ratio = ratio * math.sqrt(ref["Pc_bar"] / Pc_bar) * math.sqrt(thrust_N / ref["thrust_N"])
|
| return name, ref["Dt_mm"], abs(1 - scaled_ratio) < 0.5
|
| return None, None, True
|
|
|
|
|
| LPBF_PARAMS = {
|
| "Inconel_718": {"power_W": 285, "speed_mm_s": 960, "hatch_um": 110, "layer_um": 40},
|
| "Inconel_625": {"power_W": 275, "speed_mm_s": 800, "hatch_um": 100, "layer_um": 40},
|
| "CuCrZr": {"power_W": 370, "speed_mm_s": 600, "hatch_um": 90, "layer_um": 30},
|
| "GRCop84": {"power_W": 350, "speed_mm_s": 500, "hatch_um": 100, "layer_um": 30},
|
| "Ti6Al4V": {"power_W": 280, "speed_mm_s": 1200, "hatch_um": 120, "layer_um": 30},
|
| "C103_Niobium": {"power_W": 300, "speed_mm_s": 700, "hatch_um": 100, "layer_um": 40},
|
| }
|
|
|
|
|
|
|
| def calc_exit_mach(gamma, eps):
|
| """Solve for exit Mach number from area ratio (Newton iteration)"""
|
| g = gamma
|
| Me = 3.0
|
| for _ in range(100):
|
| f = (1/Me) * ((2/(g+1)) * (1 + (g-1)/2 * Me**2))**((g+1)/(2*(g-1))) - eps
|
| df = -f/Me + (1/Me) * ((g+1)/(2*(g-1))) * ((2/(g+1)) * (1 + (g-1)/2 * Me**2))**((g+1)/(2*(g-1)) - 1) * (2/(g+1)) * (g-1) * Me
|
| if abs(df) < 1e-12: break
|
| Me_new = Me - f / df
|
| if abs(Me_new - Me) < 1e-8: break
|
| Me = max(Me_new, 1.001)
|
| return Me
|
|
|
| def calc_exhaust_velocity(gamma, Rspec, Tc, Pe_Pc):
|
| """Ve = sqrt(2γRTc/(γ-1) × (1-(Pe/Pc)^((γ-1)/γ)))"""
|
| g = gamma
|
| term = (2 * g * Rspec * Tc / (g - 1)) * (1 - Pe_Pc**((g-1)/g))
|
| return math.sqrt(max(term, 0))
|
|
|
| def calc_isp_from_first_principles(gamma, Rspec, Tc, Pc_bar, eps, Pa_bar=0):
|
| """Isp = Ve/g0 + (Pe-Pa)×Ae/(mdot×g0) — full first-principles calculation"""
|
| pe_ratio = calc_exit_pressure(gamma, eps)
|
| Pe_bar = pe_ratio * Pc_bar
|
| Ve = calc_exhaust_velocity(gamma, Rspec, Tc, pe_ratio)
|
|
|
| Isp_mom = Ve / G0
|
|
|
|
|
| Cf = calc_thrust_coefficient(gamma, eps, 1.0, pe_ratio, Pa_bar/Pc_bar)
|
| return Ve / G0
|
|
|
| def calc_reynolds(rho, v, D_m, mu):
|
| """Re = ρvD/μ"""
|
| return rho * v * D_m / mu
|
|
|
| def calc_hydraulic_diameter(width_mm, depth_mm):
|
| """Dh = 4A/P for rectangular channel"""
|
| A = width_mm * depth_mm
|
| P = 2 * (width_mm + depth_mm)
|
| return 4 * A / P
|
|
|
| def calc_nusselt_dittus_boelter(Re, Pr, heating=True):
|
| """Dittus-Boelter: Nu = 0.023 × Re^0.8 × Pr^n (n=0.4 heating, 0.3 cooling)"""
|
| n = 0.4 if heating else 0.3
|
| return 0.023 * Re**0.8 * Pr**n
|
|
|
| def calc_nusselt_sieder_tate(Re, Pr, L_D, mu_bulk, mu_wall):
|
| """Sieder-Tate: Nu = 0.027 × Re^0.8 × Pr^(1/3) × (μb/μw)^0.14"""
|
| return 0.027 * Re**0.8 * Pr**(1/3) * (mu_bulk / mu_wall)**0.14
|
|
|
| def calc_thermal_resistance_wall(t_mm, k_W_mK, A_mm2):
|
| """R_wall = t / (k × A) — conductive thermal resistance"""
|
| t_m = t_mm / 1000
|
| A_m2 = A_mm2 / 1e6
|
| return t_m / (k_W_mK * A_m2)
|
|
|
| def calc_wall_temperature(Tc_K, q_MW_m2, wall_t_mm, k_wall):
|
| """T_wall_hot = Tc - q × t / k (hot-side wall temperature)"""
|
| q_W = q_MW_m2 * 1e6
|
| t_m = wall_t_mm / 1000
|
| return Tc_K - q_W * t_m / k_wall
|
|
|
| def calc_turbopump_power(mdot, dp_bar, rho, eta=0.65):
|
| """P_pump = mdot × ΔP / (ρ × η)"""
|
| dp_Pa = dp_bar * 1e5
|
| return mdot * dp_Pa / (rho * eta)
|
|
|
| def calc_specific_speed(N_rpm, Q_m3s, H_m):
|
| """Ns = N×sqrt(Q) / H^0.75 — turbopump specific speed"""
|
| return N_rpm * math.sqrt(Q_m3s) / H_m**0.75
|
|
|
| def calc_npsh_required(v_ms, rho, P_vapor_Pa, P_inlet_Pa):
|
| """NPSHr = (P_inlet - P_vapor)/(ρg) — cavitation check"""
|
| return (P_inlet_Pa - P_vapor_Pa) / (rho * G0)
|
|
|
| def calc_volumetric_energy_density(power_W, speed_mm_s, hatch_mm, layer_mm):
|
| """VED = P / (v × h × t) in J/mm³ — key L-PBF quality metric"""
|
| return power_W / (speed_mm_s * hatch_mm * layer_mm)
|
|
|
| def calc_contraction_angle(Dc_mm, Dt_mm, Lcon_mm):
|
| """Half-angle of converging section"""
|
| return math.degrees(math.atan((Dc_mm - Dt_mm) / (2 * Lcon_mm)))
|
|
|
| def calc_divergence_loss(half_angle_deg):
|
| """λ = (1 + cos(α))/2 — divergence efficiency factor"""
|
| return (1 + math.cos(math.radians(half_angle_deg))) / 2
|
|
|
| def calc_combustion_efficiency(Lstar_m, cstar_theoretical):
|
| """η_c* approximation based on L* (higher L* = more complete combustion)"""
|
| eta = min(0.99, 0.85 + 0.12 * math.log(max(Lstar_m, 0.1)))
|
| return eta
|
|
|
| def calc_mixture_density(rho_f, rho_o, OF):
|
| """Bulk propellant density: 1/ρ_mix = (1/(1+OF))/ρ_f + (OF/(1+OF))/ρ_o"""
|
| w_f = 1 / (1 + OF)
|
| w_o = OF / (1 + OF)
|
| return 1 / (w_f / rho_f + w_o / rho_o)
|
|
|
|
|
| UNIVERSAL_GAS_CONST = 8314.46
|
| STEFAN_BOLTZMANN = 5.67e-8
|
| BOLTZMANN_K = 1.381e-23
|
|
|
| def calc_radiation_heat_loss(emissivity, T_surface_K, T_ambient_K=300):
|
| """q_rad = ε × σ × (T⁴_s - T⁴_a) — Stefan-Boltzmann radiation"""
|
| return emissivity * STEFAN_BOLTZMANN * (T_surface_K**4 - T_ambient_K**4)
|
|
|
| def calc_speed_of_sound(gamma, Rspec, T_K):
|
| """a = sqrt(γ × R × T)"""
|
| return math.sqrt(gamma * Rspec * T_K)
|
|
|
|
|
|
|
|
|
| def calc_beam_deflection(load_N, length_mm, width_mm, height_mm, E_GPa):
|
| """Max deflection of cantilever beam: δ = (F*L^3) / (3*E*I)"""
|
| L = length_mm / 1000
|
| w = width_mm / 1000
|
| h = height_mm / 1000
|
| E = E_GPa * 1e9
|
|
|
|
|
| I = (w * h**3) / 12
|
|
|
| deflection_m = (load_N * L**3) / (3 * E * I)
|
| return deflection_m * 1000
|
|
|
| def calc_plate_buckling_stress(E_GPa, t_mm, width_mm, poisson=0.3):
|
| """Critical buckling stress for simply supported plate: σ_cr = (k*π^2*E)/(12*(1-v^2)*(b/t)^2)"""
|
| k = 4.0
|
| E = E_GPa * 1e9
|
| ratio = width_mm / t_mm
|
|
|
| sigma_cr = (k * math.pi**2 * E) / (12 * (1 - poisson**2) * ratio**2)
|
| return sigma_cr / 1e6
|
|
|
| def calc_conduction_resistance(length_mm, area_mm2, k_W_mK):
|
| """Thermal resistance R = L / (k * A)"""
|
| L = length_mm / 1000
|
| A = area_mm2 / 1e6
|
| return L / (k_W_mK * A)
|
|
|
| def calc_heatsink_performance(power_W, T_max_C, T_amb_C, R_ideal_CW):
|
| """Check if heatsink meets thermal resistance target"""
|
| delta_T = T_max_C - T_amb_C
|
| R_actual = delta_T / power_W
|
| margin = (R_actual - R_ideal_CW) / R_ideal_CW
|
| return R_actual, margin
|
|
|
| def calc_bolt_shear_strength(diameter_mm, material_key):
|
| """Shear strength of a bolt ≈ 0.6 * Ultimate Tensile Strength"""
|
| mat = MATERIALS[material_key]
|
| area = math.pi * (diameter_mm/2)**2
|
| shear_yield_MPa = 0.577 * mat["sigma_y_MPa"]
|
|
|
| max_load_N = shear_yield_MPa * area
|
| return max_load_N
|
|
|
| def calc_natural_frequency_cantilever(length_mm, width_mm, height_mm, E_GPa, rho_kg_m3):
|
| """Fundamental frequency f = (0.56/L^2) * sqrt(E*I / (rho*A))"""
|
| L = length_mm / 1000
|
| w = width_mm / 1000
|
| h = height_mm / 1000
|
| E = E_GPa * 1e9
|
|
|
| I = (w * h**3) / 12
|
| A = w * h
|
|
|
| freq_Hz = (3.52 / (2 * math.pi * L**2)) * math.sqrt((E * I) / (rho_kg_m3 * A))
|
| return freq_Hz
|
|
|