STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
vehicle_drag.py
Go to the documentation of this file.
1"""Axial drag of the vehicle the flight sim builds: Cd(M), motor off and motor on.
2
3Component build-up of the OpenRocket technical documentation (S. Niskanen, "OpenRocket
4technical documentation", v13.05, 2013, sec. 3.4 and app. B), which is Barrowman's method
5(TIR-33, 1967) with Hoerner's (Fluid-Dynamic Drag, 1965) and Stoney's (NASA TR R-100, 1961)
6data:
7
8 friction (3.78)-(3.85) fully turbulent Cf, roughness-limited Cf, compressibility,
9 body-fineness and fin-thickness corrections on the wetted area
10 nose (3.86)-(3.87), (B.1)-(B.9) 0.8 sin^2(phi) at M = 0, wave drag from Stoney's
11 fineness-3 curves scaled to this nose's fineness
12 fins (3.89)-(3.93) leading edge by profile with the cos^2 sweep correction, trailing
13 edge as base drag, on the fin frontal area N*t*s
14 base (3.94) 0.12 + 0.13 M^2 below M 1, 0.25/M above; while thrusting the
15 nozzle exit is taken out of the base area (sec. 3.4.5)
16
17Where the document and OpenRocket's source (BarrowmanCalculator, SymmetricComponentCalc)
18differ, the source is followed and the line says so. Not modelled: launch lugs and rail
19buttons (3.95), fin-body interference, boattails (the built body has none), laminar runs
20(the doc assumes turbulent from the tip, sec. 3.4.1).
21
22RocketPy takes Cd as a function of Mach alone, so each row's Reynolds number is taken in the
23ISA at one reference altitude, the launch site. At 1.5 km MSL the smooth-wall Cf is ~2 % higher;
24a roughness-limited Cf does not depend on Re at all.
25"""
26
27from __future__ import annotations
28
29import math
30from dataclasses import dataclass, field, replace
31from functools import lru_cache
32from typing import Any, Dict, List, Optional, Sequence, Tuple
33
34import numpy as np
35
36GAMMA_AIR = 1.4
37R_AIR = 287.05287 # J/(kg K), US Standard Atmosphere 1976
38FIN_PROFILES = ("square", "rounded", "airfoil")
39MODEL_NAME = "openrocket_barrowman"
40
41#: Mach rows of a built-up table. RocketPy extrapolates constant below the first row; M = 0
42#: itself is left out because Re = 0 there hits the (3.81) low-Re floor.
43MACH_GRID = tuple(round(0.01 * i, 2) for i in range(1, 201))
44
45#: Stoney (NASA TR R-100) wave drag of fineness-3 noses, as tabulated in OpenRocket's
46#: SymmetricComponentCalc (vonKarmanInterpolator, lvHaackInterpolator). Constant beyond the ends.
47_STONEY_FN3: Dict[str, Tuple[Tuple[float, ...], Tuple[float, ...]]] = {
48 "vonkarman": (
49 (0.9, 0.95, 1.0, 1.05, 1.1, 1.2, 1.4, 1.6, 2.0, 3.0),
50 (0.0, 0.010, 0.027, 0.055, 0.070, 0.081, 0.095, 0.097, 0.091, 0.083),
51 ),
52 "lvhaack": (
53 (0.9, 0.95, 1.0, 1.05, 1.1, 1.2, 1.4, 1.6, 2.0),
54 (0.0, 0.010, 0.024, 0.066, 0.084, 0.100, 0.114, 0.117, 0.113),
55 ),
56}
57
58
59def isa_troposphere(h_m: float) -> Tuple[float, float, float, float, float]:
60 """(T [K], p [Pa], rho [kg/m3], a [m/s], nu [m2/s]) of the 1976 US Standard Atmosphere at a
61 geometric altitude, layers 0-2 (to 32 km geopotential). Sutherland viscosity, the standard's
62 1.458e-6 / 110.4 K."""
63 h = 6356766.0 * float(h_m) / (6356766.0 + float(h_m)) # geopotential
64 if not -610.0 <= h <= 32000.0:
65 raise ValueError(f"ISA 1976 layers 0-2 only: {h:.0f} m is outside -610..32000 m")
66 g_R = 9.80665 / R_AIR
67 if h <= 11000.0:
68 T = 288.15 - 0.0065 * h
69 p = 101325.0 * (T / 288.15) ** (g_R / 0.0065)
70 else:
71 p11 = 101325.0 * (216.65 / 288.15) ** (g_R / 0.0065)
72 if h <= 20000.0:
73 T = 216.65
74 p = p11 * math.exp(-g_R * (h - 11000.0) / T)
75 else:
76 T = 216.65 + 0.001 * (h - 20000.0)
77 p = p11 * math.exp(-g_R * 9000.0 / 216.65) * (T / 216.65) ** (-g_R / 0.001)
78 rho = p / (R_AIR * T)
79 mu = 1.458e-6 * T**1.5 / (T + 110.4)
80 return T, p, rho, math.sqrt(GAMMA_AIR * R_AIR * T), mu / rho
81
82
83@dataclass(frozen=True)
85 """What the drag sees. Lengths in m, tail at z = 0, fins as RocketPy builds them."""
86
87 length_m: float # tail to nose tip
88 radius_m: float # body; also the reference radius
89 nose_kind: str
90 nose_length_m: float
91 fin_count: int
92 fin_root_chord_m: float
93 fin_tip_chord_m: float
94 fin_span_m: float
95 fin_sweep_length_m: float # root LE to tip LE along the axis
96 fin_thickness_m: float
97 fin_profile: str
98 surface_roughness_m: float
99 nozzle_exit_area_m2: float
100
101 @property
102 def reference_area_m2(self) -> float:
103 return math.pi * self.radius_m**2
104
105
106@dataclass(frozen=True)
108 """Cd(M) with the motor off and on, plus where they came from."""
109
110 mach: Tuple[float, ...]
111 cd_power_off: Tuple[float, ...]
112 cd_power_on: Tuple[float, ...]
113 model: str # MODEL_NAME, or "table"
114 source: str
115 inputs: Dict[str, Any] = field(default_factory=dict)
116 components_power_off: Dict[str, Tuple[float, ...]] = field(default_factory=dict)
117
118 def rocketpy(self, power_on: bool) -> List[List[float]]:
119 cd = self.cd_power_on if power_on else self.cd_power_off
120 return [[m, c] for m, c in zip(self.mach, cd)]
121
122 def at(self, mach: float, power_on: bool = False) -> float:
123 cd = self.cd_power_on if power_on else self.cd_power_off
124 return float(np.interp(mach, self.mach, cd))
125
126 def to_dict(self) -> Dict[str, Any]:
127 return {
128 "model": self.model,
129 "source": self.source,
130 "mach": list(self.mach),
131 "cd_power_off": list(self.cd_power_off),
132 "cd_power_on": list(self.cd_power_on),
133 "components_power_off": {k: list(v) for k, v in self.components_power_off.items()},
134 "inputs": dict(self.inputs),
135 }
136
137
138# ---------------------------------------------------------------- component coefficients
139
140
141def skin_friction_cf(Re: float, mach: float, roughness_m: float, length_m: float) -> float:
142 """Compressibility-corrected Cf on the wetted area, (3.78)-(3.84)."""
143 M = float(mach)
144 # (3.81): below R = 1e4 the value at 1e4
145 cf_turb = 1.48e-2 if Re < 1.0e4 else 1.0 / (1.50 * math.log(Re) - 5.6) ** 2
146 # (3.82) subsonic, (3.83) supersonic; OpenRocket switches at M 1.1
147 c_turb = 1.0 - 0.1 * M * M if M < 1.1 else 1.0 / (1.0 + 0.15 * M * M) ** 0.58
148 turb = cf_turb * c_turb
149 if roughness_m <= 0.0:
150 return turb
151 # (3.80) with (3.82)/(3.84); OpenRocket blends the two linearly across M 0.9-1.1
152 if M < 0.9:
153 c_rough = 1.0 - 0.1 * M * M
154 elif M > 1.1:
155 c_rough = 1.0 / (1.0 + 0.18 * M * M)
156 else:
157 c_lo, c_hi = 1.0 - 0.1 * 0.9**2, 1.0 / (1.0 + 0.18 * 1.1**2)
158 c_rough = c_hi * (M - 0.9) / 0.2 + c_lo * (1.1 - M) / 0.2
159 rough = 0.032 * (roughness_m / length_m) ** 0.2 * c_rough
160 # sec. 3.4.2: the roughness-limited value is never used below the turbulent one
161 return max(turb, rough)
162
163
164def stagnation_cd(mach: float) -> float:
165 """Blunt-cylinder front drag on its frontal area, (B.1)-(B.2)."""
166 M = float(mach)
167 if M <= 1.0:
168 q = 1.0 + M**2 / 4.0 + M**4 / 40.0
169 else:
170 q = 1.84 - 0.76 / M**2 + 0.166 / M**4 + 0.035 / M**6
171 return 0.85 * q
172
173
174def base_cd(mach: float) -> float:
175 """(3.94), on the base area."""
176 M = float(mach)
177 return 0.12 + 0.13 * M * M if M <= 1.0 else 0.25 / M
178
179
180def _subsonic_fit(M: float, m_lo: float, value: float, slope: float, cd0: float) -> float:
181 """(3.87): a M^b + cd0, fitted to the value and slope at the transonic lower bound."""
182 if value <= cd0:
183 return cd0 # non-decreasing and zero-slope at M = 0: flat
184 b = m_lo * slope / (value - cd0)
185 a = (value - cd0) / m_lo**b
186 return a * M**b + cd0
187
188
189def _nose_key(kind: str) -> str:
190 return str(kind).replace(" ", "").replace("_", "").replace("-", "").lower()
191
192
193def _haack_wave_cd(mach: float, fineness: float, key: str) -> float:
194 xs, ys = _STONEY_FN3[key]
195 log4 = math.log(fineness + 1.0) / math.log(4.0)
196
197 def scaled(m: float) -> float: # (B.9)
198 c3 = float(np.interp(m, xs, ys))
199 stag = stagnation_cd(m)
200 return 0.0 if c3 <= 0.0 else stag * (c3 / stag) ** log4
201
202 m_lo = xs[0]
203 if mach >= m_lo:
204 return scaled(mach)
205 v = scaled(m_lo)
206 return _subsonic_fit(mach, m_lo, v, (scaled(m_lo + 0.01) - v) / 0.01, 0.0)
207
208
209def _cone_wave_cd(mach: float, fineness: float, cd0: float, shape_factor: float) -> float:
210 """Conical wave drag (B.4)-(B.6), times the ogive shape factor (B.8) at and above M 1."""
211 sin_e = 1.0 / math.sqrt(1.0 + 4.0 * fineness**2) # (B.3)
212 cd1 = 1.0 * sin_e # (B.6)
213 slope1 = 4.0 / (GAMMA_AIR + 1.0) * (1.0 - 0.5 * cd1) # (B.5)
214
215 def sup(m: float) -> float: # (B.4)
216 return 2.1 * sin_e**2 + 0.5 * sin_e / math.sqrt(m * m - 1.0)
217
218 M = float(mach)
219 if M >= 1.3:
220 return shape_factor * sup(M)
221 if M >= 1.0: # cubic Hermite through (B.6)/(B.5) at M 1 and (B.4) at M 1.3
222 h = 0.3
223 y1 = sup(1.3)
224 d1 = -0.5 * sin_e * 1.3 / (1.3**2 - 1.0) ** 1.5
225 s = (M - 1.0) / h
226 h00, h10 = 2 * s**3 - 3 * s**2 + 1, s**3 - 2 * s**2 + s
227 h01, h11 = -2 * s**3 + 3 * s**2, s**3 - s**2
228 return shape_factor * (h00 * cd1 + h10 * h * slope1 + h01 * y1 + h11 * h * d1)
229 return _subsonic_fit(M, 1.0, shape_factor * cd1, shape_factor * slope1, cd0)
230
231
232def nose_pressure_cd(mach: float, kind: str, fineness: float) -> float:
233 """Nose pressure (wave) drag on the nose base area, for a nose flush with the body."""
234 key = _nose_key(kind)
235 if key in _STONEY_FN3:
236 return _haack_wave_cd(mach, fineness, key) # tangent joint: (3.86) gives 0 at M 0
237 if key == "conical":
238 sin_e = 1.0 / math.sqrt(1.0 + 4.0 * fineness**2)
239 return _cone_wave_cd(mach, fineness, 0.8 * sin_e**2, 1.0) # joint angle = half-apex
240 if key in ("ogive", "tangent", "tangentogive"):
241 # RocketPy's "ogive" is the tangent ogive, kappa = 1: (B.8) factor 1.0, joint angle 0
242 return _cone_wave_cd(mach, fineness, 0.0, 0.72 * (1.0 - 0.5) ** 2 + 0.82)
243 raise ValueError(
244 f"No transonic nose drag data for nose kind '{kind}' (have: vonKarman, lvhaack, conical, "
245 "ogive). Give rocket.drag_curve_power_off / drag_curve_power_on from OpenRocket or RASAero."
246 )
247
248
249def fin_pressure_cd(mach: float, profile: str, le_sweep_rad: float) -> float:
250 """Leading plus trailing edge drag on the fin frontal area N*t*s, (3.89)-(3.93)."""
251 M = float(mach)
252 if profile == "square":
253 le_perp = stagnation_cd(M) # (3.90)
254 elif profile in ("rounded", "airfoil"): # (3.89)
255 if M < 0.9:
256 le_perp = (1.0 - M * M) ** -0.417 - 1.0
257 elif M < 1.0:
258 le_perp = 1.0 - 1.785 * (M - 0.9)
259 else:
260 le_perp = 1.214 - 0.502 / M**2 + 0.1095 / M**4
261 else:
262 raise ValueError(f"fin_profile must be one of {FIN_PROFILES}, not {profile!r}")
263 le = le_perp * math.cos(le_sweep_rad) ** 2 # (3.91)
264 te = {"square": 1.0, "rounded": 0.5, "airfoil": 0.0}[profile] * base_cd(M) # (3.92)
265 return le + te
266
267
268# ---------------------------------------------------------------- geometry
269
270
271def nose_profile(kind: str, length_m: float, radius_m: float, x: np.ndarray) -> np.ndarray:
272 """Nose radius at distance x from the tip (the shapes RocketPy draws)."""
273 key = _nose_key(kind)
274 xi = np.clip(np.asarray(x, dtype=float) / length_m, 0.0, 1.0)
275 if key == "conical":
276 return radius_m * xi
277 if key in ("vonkarman", "lvhaack"):
278 th = np.arccos(1.0 - 2.0 * xi)
279 c = 1.0 / 3.0 if key == "lvhaack" else 0.0
280 return radius_m * np.sqrt(np.maximum(th - np.sin(2 * th) / 2 + c * np.sin(th) ** 3, 0.0)) / math.sqrt(math.pi)
281 if key in ("ogive", "tangent", "tangentogive"):
282 rho = (radius_m**2 + length_m**2) / (2.0 * radius_m)
283 return np.sqrt(rho**2 - (length_m * (1.0 - xi)) ** 2) + radius_m - rho
284 raise ValueError(f"No profile for nose kind '{kind}'")
285
286
287@lru_cache(maxsize=64)
288def nose_wetted_area(kind: str, length_m: float, radius_m: float, n: int = 20001) -> float:
289 """Surface of revolution, summed as frusta (exact for the polyline)."""
290 x = np.linspace(0.0, length_m, n)
291 y = nose_profile(kind, length_m, radius_m, x)
292 return float(np.sum(math.pi * (y[1:] + y[:-1]) * np.hypot(np.diff(x), np.diff(y))))
293
294
295def _tank_height(section: Any, h_attr: str, r_attr: str) -> float:
296 """Tank cylinder height from the volume the caps and the ullage use (tank_capacity)."""
297 from engine.pipeline.tank_capacity import resolve_cylindrical_tank_volume_m3
298
299 V = resolve_cylindrical_tank_volume_m3(section, height_attr=h_attr, radius_attr=r_attr)
300 return V / (math.pi * float(getattr(section, r_attr)) ** 2)
301
302
303def built_stack(config: Any) -> Dict[str, float]:
304 """The stack ui/flight_sim.py assembles, tail at z = 0 (RocketPy tail_to_nose).
305
306 Tank tops sit at motor_position + tank centre + h/2; the nose TIP is avionics_payload_length_m
307 above the highest of them (RocketPy's add_nose position is the tip). So the avionics length
308 includes the nose, and the body is a plain cylinder from the tail to the nose base.
309 """
310 rk = config.rocket
311 motor_position = float(getattr(rk, "motor_position", 0.0) or 0.0)
312 lox_h = _tank_height(config.lox_tank, "lox_h", "lox_radius")
313 fuel_h = _tank_height(config.fuel_tank, "rp1_h", "rp1_radius")
314 lox_top = motor_position + config.lox_tank.ox_tank_pos + lox_h / 2
315 fuel_pos = config.fuel_tank.fuel_tank_pos
316 fuel_top = motor_position + fuel_pos + fuel_h / 2 if fuel_pos > 0 else 0.0
317 press_top = 0.0
318 if getattr(config, "press_tank", None):
319 press_top = motor_position + config.press_tank.pres_tank_pos + config.press_tank.press_h / 2
320 avionics = float(getattr(rk, "avionics_payload_length_m", 4.0) or 0.0)
321 nose_tip = max(lox_top, fuel_top, press_top, motor_position) + avionics
322 diameter = 2.0 * float(rk.radius)
323 nose_len_override = getattr(rk, "nose_length", None)
324 if nose_len_override and float(nose_len_override) > 0:
325 nose_length = float(nose_len_override)
326 else:
327 nose_length = float(getattr(rk, "nose_fineness_ratio", 4.5) or 4.5) * diameter
328 return {
329 "motor_position": motor_position,
330 "lox_h": lox_h,
331 "fuel_h": fuel_h,
332 "lox_top": lox_top,
333 "fuel_top": fuel_top,
334 "press_top": press_top,
335 "nose_tip": nose_tip,
336 "nose_length": nose_length,
337 "length": nose_tip,
338 }
339
340
341def vehicle_geometry(config: Any, nozzle_exit_area_m2: float, stack: Optional[Dict[str, float]] = None) -> VehicleGeometry:
342 rk = config.rocket
343 stack = stack or built_stack(config)
344 fins = getattr(rk, "fins", None)
345 if fins is not None:
346 n, cr, ct, s = int(fins.no_fins), float(fins.root_chord), float(fins.tip_chord), float(fins.fin_span)
347 sweep = cr - ct # RocketPy's default: right trapezoid, trailing edge square to the body
348 else:
349 n, cr, ct, s, sweep = 0, 1.0, 1.0, 0.0, 0.0
350 return VehicleGeometry(
351 length_m=float(stack["length"]),
352 radius_m=float(rk.radius),
353 nose_kind=str(getattr(rk, "nose_kind", None) or "vonKarman"),
354 nose_length_m=float(stack["nose_length"]),
355 fin_count=n,
356 fin_root_chord_m=cr,
357 fin_tip_chord_m=ct,
358 fin_span_m=s,
359 fin_sweep_length_m=sweep,
360 fin_thickness_m=float(rk.fin_thickness_m),
361 fin_profile=str(rk.fin_profile),
362 surface_roughness_m=float(rk.surface_roughness_m),
363 nozzle_exit_area_m2=float(nozzle_exit_area_m2),
364 )
365
366
367# ---------------------------------------------------------------- build-up
368
369
370def drag_components(geom: VehicleGeometry, mach: float, *, a_m_s: float, nu_m2_s: float, thrusting: bool) -> Dict[str, float]:
371 """Cd on the body cross-section, by component, (3.97)."""
372 M = float(mach)
373 D, L, A_ref = 2.0 * geom.radius_m, geom.length_m, geom.reference_area_m2
374 Re = M * a_m_s * L / nu_m2_s
375 cf = skin_friction_cf(Re, M, geom.surface_roughness_m, L)
376 body_wet = nose_wetted_area(geom.nose_kind, geom.nose_length_m, geom.radius_m) + math.pi * D * (L - geom.nose_length_m)
377 fin_planform = 0.5 * (geom.fin_root_chord_m + geom.fin_tip_chord_m) * geom.fin_span_m
378 fins_wet = 2.0 * geom.fin_count * fin_planform
379 lam = geom.fin_tip_chord_m / geom.fin_root_chord_m
380 mac = (2.0 / 3.0) * geom.fin_root_chord_m * (1.0 + lam + lam * lam) / (1.0 + lam)
381 fB = L / D # rocket fineness, length over diameter as the doc defines f
382 friction = cf * ((1.0 + 1.0 / (2.0 * fB)) * body_wet + (1.0 + 2.0 * geom.fin_thickness_m / mac) * fins_wet) / A_ref # (3.85)
383 nose = nose_pressure_cd(M, geom.nose_kind, geom.nose_length_m / D)
384 fins = 0.0
385 if geom.fin_count > 0 and geom.fin_span_m > 0:
386 sweep = math.atan2(geom.fin_sweep_length_m, geom.fin_span_m)
387 fins = fin_pressure_cd(M, geom.fin_profile, sweep) * geom.fin_count * geom.fin_thickness_m * geom.fin_span_m / A_ref
388 base_area = A_ref # flat base: the body ends in the nozzle plane, no boattail
389 if thrusting:
390 base_area = max(base_area - geom.nozzle_exit_area_m2, 0.0) # sec. 3.4.5
391 base = base_cd(M) * base_area / A_ref
392 return {"Re": Re, "cf": cf, "friction": friction, "nose": nose, "fins": fins, "base": base, "total": friction + nose + fins + base}
393
394
395def buildup_curves(geom: VehicleGeometry, reference_altitude_m: float, mach_grid: Sequence[float] = MACH_GRID) -> DragCurves:
396 _, _, _, a, nu = isa_troposphere(reference_altitude_m)
397 off = [drag_components(geom, m, a_m_s=a, nu_m2_s=nu, thrusting=False) for m in mach_grid]
398 on = [drag_components(geom, m, a_m_s=a, nu_m2_s=nu, thrusting=True) for m in mach_grid]
399 return DragCurves(
400 mach=tuple(float(m) for m in mach_grid),
401 cd_power_off=tuple(c["total"] for c in off),
402 cd_power_on=tuple(c["total"] for c in on),
403 model=MODEL_NAME,
404 source="Component build-up, OpenRocket technical documentation (Niskanen 2013) sec. 3.4 / app. B",
405 inputs={
406 "length_m": geom.length_m,
407 "diameter_m": 2.0 * geom.radius_m,
408 "nose_kind": geom.nose_kind,
409 "nose_length_m": geom.nose_length_m,
410 "fin_count": geom.fin_count,
411 "fin_le_sweep_deg": math.degrees(math.atan2(geom.fin_sweep_length_m, geom.fin_span_m)) if geom.fin_span_m > 0 else None,
412 "fin_thickness_m": geom.fin_thickness_m,
413 "fin_profile": geom.fin_profile,
414 "surface_roughness_m": geom.surface_roughness_m,
415 "nozzle_exit_area_m2": geom.nozzle_exit_area_m2,
416 "reynolds_reference_altitude_m": float(reference_altitude_m),
417 },
418 components_power_off={k: tuple(c[k] for c in off) for k in ("friction", "nose", "fins", "base")},
419 )
420
421
422def table_curves(power_off: Sequence[Sequence[float]], power_on: Sequence[Sequence[float]], source: str) -> DragCurves:
423 """A user's Cd(M) tables, put on one Mach grid (the union of both, linear in between)."""
424 off = np.asarray(power_off, dtype=float)
425 on = np.asarray(power_on, dtype=float)
426 mach = np.unique(np.concatenate([off[:, 0], on[:, 0]]))
427 return DragCurves(
428 mach=tuple(float(m) for m in mach),
429 cd_power_off=tuple(float(v) for v in np.interp(mach, off[:, 0], off[:, 1])),
430 cd_power_on=tuple(float(v) for v in np.interp(mach, on[:, 0], on[:, 1])),
431 model="table",
432 source=str(source),
433 inputs={"rows_power_off": int(len(off)), "rows_power_on": int(len(on))},
434 )
435
436
437def resolve_drag_curves(config: Any, nozzle_exit_area_m2: float, stack: Optional[Dict[str, float]] = None, **overrides: Any) -> DragCurves:
438 """The user's tables when given, else the build-up of the vehicle the sim assembles.
439
440 ``overrides`` replaces build-up inputs by VehicleGeometry field name, e.g. the ceiling check's
441 ``surface_roughness_m=0.0`` (hydraulically smooth: the low-drag end of the finish).
442 """
443 rk = config.rocket
444 if getattr(rk, "drag_curve_power_off", None) is not None:
445 return table_curves(rk.drag_curve_power_off, rk.drag_curve_power_on, rk.drag_curve_source)
446 geom = vehicle_geometry(config, nozzle_exit_area_m2, stack)
447 if overrides:
448 geom = replace(geom, **overrides)
449 return buildup_curves(geom, float(config.environment.elevation))
#define max(a, b)
Definition Arduino.h:43
List[List[float]] rocketpy(self, bool power_on)
float at(self, float mach, bool power_on=False)
DragCurves table_curves(Sequence[Sequence[float]] power_off, Sequence[Sequence[float]] power_on, str source)
float fin_pressure_cd(float mach, str profile, float le_sweep_rad)
float _haack_wave_cd(float mach, float fineness, str key)
DragCurves buildup_curves(VehicleGeometry geom, float reference_altitude_m, Sequence[float] mach_grid=MACH_GRID)
VehicleGeometry vehicle_geometry(Any config, float nozzle_exit_area_m2, Optional[Dict[str, float]] stack=None)
float _subsonic_fit(float M, float m_lo, float value, float slope, float cd0)
float nose_wetted_area(str kind, float length_m, float radius_m, int n=20001)
DragCurves resolve_drag_curves(Any config, float nozzle_exit_area_m2, Optional[Dict[str, float]] stack=None, **Any overrides)
Dict[str, float] drag_components(VehicleGeometry geom, float mach, *, float a_m_s, float nu_m2_s, bool thrusting)
float _tank_height(Any section, str h_attr, str r_attr)
float skin_friction_cf(float Re, float mach, float roughness_m, float length_m)
Tuple[float, float, float, float, float] isa_troposphere(float h_m)
float nose_pressure_cd(float mach, str kind, float fineness)
np.ndarray nose_profile(str kind, float length_m, float radius_m, np.ndarray x)
float stagnation_cd(float mach)
Dict[str, float] built_stack(Any config)
float _cone_wave_cd(float mach, float fineness, float cd0, float shape_factor)