STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
waterhammer.py
Go to the documentation of this file.
1"""Layer X water hammer: the pressure a main valve's closing (and the start's arrival) puts on each
2liquid line, against the ratings on the drawing.
3
4``waterhammer_from_run(prep, result, config=None, settings=HammerSettings(), start=None)``
5-> ``diagnostics.water_hammer`` (a list, one row per liquid feed line; DATA-CONTRACT 3).
6
7Three numbers per line, each with its source:
8
9* **Joukowsky** (Joukowsky 1898): an instantaneous stop of a column moving at ``v0`` raises the
10 pressure at the valve by ``dp = rho a v0``. The upper bound for any closure faster than the
11 line's round trip ``2L/a``.
12* **Slow closure** (Michaud's formula; J. Michaud, "Coups de bélier dans les conduites", Bull. Soc.
13 Vaud. Ing. Arch., 1878): a closure that brings the velocity down linearly over
14 ``t_c > 2L/a`` raises it by ``2 rho L v0 / t_c`` at most: each part of the velocity change sends a
15 ``rho a dv`` wave up the line, and the tank's reflection cancels it after ``2L/a``, so the peak is
16 ``rho a (v0/t_c)(2L/a)``. Checked here against the MOC with a prescribed linear-velocity closure
17 (tests/test_layerx_transients.py), not against a book's printed equation.
18* **The closure as drawn**, by the method of characteristics on the reservoir-pipe-valve system
19 (Wylie & Streeter 1993, the MOC for a single pipeline with a reservoir upstream and a valve
20 downstream): the tank holds its pressure behind the outlet loss; the pipe has the drawing's bore,
21 length and friction; the main valve closes linearly in travel over ``closure_s`` along its own Cv
22 characteristic, in series with the downstream line and the injector, quasi-steady, discharging
23 into the chamber held at its pre-closure pressure. A linear-*travel* closure does most of its
24 velocity change at the end of the stroke, so it can exceed Michaud's linear-*velocity* figure;
25 in a line this short (no line packing) its first wave does not exceed Joukowsky. The MOC's step
26 is ``L/(N a)`` with ``N`` at least ``n_reaches`` and enough reaches that the step is under
27 ``max_step_s``: the effective closure of a large valve is a few ms, and 16 reaches on the 0.91 m
28 fuel line (50 us) read the LE4 peak 1.3 % low against the converged value.
29
30If the valve-end pressure falls below the liquid's vapour pressure the column separates; the MOC
31is not valid past that instant and the cavity's collapse is not computed. The collapse can put a
32short pulse on the line above the Joukowsky value (A. Bergant, A. R. Simpson and A. S. Tijsseling,
33"Water hammer with column separation: a historical review", J. Fluids Struct. 22(2), 2006), so the
34row then carries the Joukowsky value as the figure it is graded on and says it is not a bound.
35
36The wave speed carries the Korteweg correction for the tube's elasticity,
37``a = a_f / sqrt(1 + c1 K D / (E e))``, ``K = rho a_f^2`` (Korteweg 1878, in the form of Wylie &
38Streeter 1993), with the liquid's own sound speed from CoolProp (feedtwin ``Fluid``), the bore and
39wall from the drawing, ``E`` of 316 stainless (an input: 193 GPa, AK Steel 316/316L product data
40bulletin, 28.0e6 psi, room temperature) and ``c1`` (1: a pipe free to move axially; an input).
41
42The **opening surge** is the start's: the liquid filling the gas-filled manifold arrives at the
43orifices at the flow ``diag.start`` computes (rigid column) and must slow to what the orifices pass.
44Treated as the MOC valve boundary appearing instantly at the front: ``p = p_front + B (m_arr - m)``
45with ``m = phi sqrt(p - Pc)``, ``B = a/A``. The gas cushion the last of the manifold gas gives is
46ignored, so this is an upper bound.
47
48Ratings: the drawing rates only the tanks (MAWP or burst pressure, estimated) and the bottle; no
49line, valve or manifold carries a rating. Each line is graded against the absolute pressure its tank
50trips the stand at (``prepare.vessel_trip``, feedtwin's own trip), and the row says so.
51"""
52
53from __future__ import annotations
54
55import math
56from dataclasses import dataclass
57from typing import Any, Callable, Dict, List, Mapping, Optional
58
59from engine.layerx.diag.start import (
60 PSI, SIDES, feed_line, inp, linear_characteristic, model_block, unavailable,
61)
62
63MODEL_NAME = "water_hammer_moc"
64E_316_PA = 193.0e9
65E_316_SOURCE = ("assumed 193 GPa (28.0 x 10^6 psi): the room-temperature modulus producers' 316/316L data sheets "
66 "list (the AK Steel 316/316L product data bulletin was cited from memory and not re-checked; a "
67 "distributor's 316/316L technical data sheet gives 193 GPa); not corrected to LOX temperature "
68 "(by Korteweg, a 10 % higher E moves the LE4 LOX wave speed by +0.3 %)")
69
70
71# ------------------------------------------------------------------ closed forms
72
73
74def wave_speed(a_fluid: float, rho: float, bore: float, wall: float, E: float = E_316_PA, c1: float = 1.0) -> float:
75 """Korteweg: pressure-wave speed in a liquid-filled elastic tube [m/s]."""
76 K = rho * a_fluid ** 2
77 if wall <= 0.0 or not math.isfinite(E):
78 return a_fluid
79 return a_fluid / math.sqrt(1.0 + c1 * K * bore / (E * wall))
80
81
82def joukowsky(rho: float, a: float, dv: float) -> float:
83 """Joukowsky 1898: dp = rho a dv [Pa]."""
84 return rho * a * dv
85
86
87def michaud(rho: float, length: float, v0: float, closure_s: float, a: float) -> float:
88 """Slow-closure bound for a linear velocity decrease over ``closure_s`` [Pa]; Joukowsky when
89 the closure is inside the round trip 2L/a."""
90 if closure_s <= 2.0 * length / a:
91 return joukowsky(rho, a, v0)
92 return 2.0 * rho * length * v0 / closure_s
93
94
95def _solve_boundary(R: float, B: float, rhs: float) -> float:
96 """``R m|m| + B m = rhs`` for m (monotone in m; written without cancellation)."""
97 if rhs == 0.0:
98 return 0.0
99 s = 1.0 if rhs > 0.0 else -1.0
100 a = abs(rhs)
101 if R <= 0.0:
102 return rhs / B
103 return s * 2.0 * a / (B + math.sqrt(B * B + 4.0 * R * a))
104
105
106# ------------------------------------------------------------------ method of characteristics
107
108
109@dataclass(frozen=True)
111 """Reservoir -> one pipe -> valve -> (quasi-steady downstream) -> chamber. SI."""
112
113 rho: float
114 a: float
115 bore: float
116 length: float
117 p_tank: float
118 mdot0: float
119 R_entry: float = 0.0
120 """Tank-outlet loss, Pa/(kg/s)^2 (K/(2 rho A^2))."""
121 R_friction: float = 0.0
122 """The pipe's whole friction loss, Pa/(kg/s)^2, spread over its reaches."""
123
124 @property
125 def area(self) -> float:
126 return math.pi * self.bore ** 2 / 4.0
127
128
129def moc(pipe: Pipeline, *, t_end: float, n_reaches: int = 16,
130 valve_R: Optional[Callable[[float], float]] = None,
131 valve_flow: Optional[Callable[[float], float]] = None,
132 R_down0: Optional[float] = None) -> Dict[str, Any]:
133 """Method of characteristics on the reservoir-pipe-valve line, in pressure and mass flow:
134
135 C+: p_P = p_A - B (m_P - m_A) - Rf m_A|m_A| C-: p_P = p_B + B (m_P - m_B) + Rf m_B|m_B|
136
137 with ``B = a/A`` and ``Rf`` the reach's friction (Pa/(kg/s)^2). The valve end is either a
138 resistance ``valve_R(t)`` [Pa/(kg/s)^2, inf = shut] to a downstream pressure set so the initial
139 state is steady (``R_down0`` is its value at t = 0), or a prescribed flow ``valve_flow(t)``.
140 Returns the valve-end pressure and flow histories and their extremes."""
141 A = pipe.area
142 B = pipe.a / A
143 N = max(2, int(n_reaches))
144 dx = pipe.length / N
145 dt = dx / pipe.a
146 Rf = pipe.R_friction / N
147 m0 = pipe.mdot0
148 p = [0.0] * (N + 1)
149 m = [m0] * (N + 1)
150 p[0] = pipe.p_tank - pipe.R_entry * m0 * abs(m0)
151 for i in range(1, N + 1):
152 p[i] = p[i - 1] - Rf * m0 * abs(m0)
153 if valve_flow is None:
154 if valve_R is None:
155 raise ValueError("a valve resistance or a prescribed flow is needed")
156 r0 = valve_R(0.0) if R_down0 is None else R_down0
157 p_down = p[N] - r0 * m0 * abs(m0)
158 else:
159 p_down = 0.0
160 ts, pv, mv = [0.0], [p[N]], [m0]
161 steps = int(math.ceil(t_end / dt))
162 for n in range(1, steps + 1):
163 t = n * dt
164 pn = [0.0] * (N + 1)
165 mn = [0.0] * (N + 1)
166 for i in range(1, N):
167 cp = p[i - 1] + B * m[i - 1] - Rf * m[i - 1] * abs(m[i - 1])
168 cm = p[i + 1] - B * m[i + 1] + Rf * m[i + 1] * abs(m[i + 1])
169 mn[i] = (cp - cm) / (2.0 * B)
170 pn[i] = 0.5 * (cp + cm)
171 # Reservoir: p_0 = p_tank - R_entry m|m| on the C- characteristic.
172 cm = p[1] - B * m[1] + Rf * m[1] * abs(m[1])
173 mn[0] = _solve_boundary(pipe.R_entry, B, pipe.p_tank - cm)
174 pn[0] = cm + B * mn[0]
175 # Valve on the C+ characteristic.
176 cp = p[N - 1] + B * m[N - 1] - Rf * m[N - 1] * abs(m[N - 1])
177 if valve_flow is not None:
178 mn[N] = valve_flow(t)
179 else:
180 R = valve_R(t) # type: ignore[misc]
181 mn[N] = 0.0 if not math.isfinite(R) else _solve_boundary(R, B, cp - p_down)
182 pn[N] = cp - B * mn[N]
183 p, m = pn, mn
184 ts.append(t)
185 pv.append(p[N])
186 mv.append(m[N])
187 return {"t": ts, "p_valve": pv, "m_valve": mv, "p0_valve": pv[0], "p_max": max(pv), "p_min": min(pv),
188 "dt": dt, "B": B, "p_down": p_down, "n_reaches": N}
189
190
191def opening_surge(*, p_front: float, pc: float, mdot_arrival: float, phi: float, B: float) -> Dict[str, float]:
192 """The liquid front meets the orifices at ``mdot_arrival``: the orifice boundary appears at the
193 front, ``p = p_front + B (m_arr - m)``, ``m = phi sqrt(p - Pc)``. Returns the peak and the flow."""
194 R = 0.0 if not math.isfinite(phi) else 1.0 / phi ** 2
195 cp = p_front + B * mdot_arrival
196 m = _solve_boundary(R, B, cp - pc)
197 return {"p_peak": cp - B * m, "mdot_after": m}
198
199
200# ------------------------------------------------------------------ from a run
201
202
203@dataclass(frozen=True)
205 closure_s: Optional[float] = None
206 """Main-valve closing time [s]. None = the drawing's ``travel_time``."""
207 E_wall_pa: float = E_316_PA
208 c1: float = 1.0
209 """Korteweg anchoring factor: 1 for a pipe free to move axially (expansion joints); 1 - nu^2
210 for one anchored throughout (Wylie & Streeter 1993)."""
211 n_reaches: int = 16
212 """The fewest MOC reaches on the line."""
213 max_step_s: float = 2.5e-5
214 """The MOC's largest time step ``L/(N a)``: more reaches are used on a long line, so the few-ms
215 effective closure of a large valve is resolved (LE4 fuel line: 32 reaches, converged to 0.01 %
216 against 64)."""
217 after_closure_s: float = 0.02
218 """Integrated this long past the closure."""
219
220
221def waterhammer_from_run(prep: Any, result: Mapping[str, Any], config: Any = None,
222 settings: HammerSettings = HammerSettings(),
223 start: Optional[Mapping[str, Any]] = None) -> List[Dict[str, Any]]:
224 """``diagnostics.water_hammer`` rows. ``start`` is ``diagnostics.start`` (for the opening surge).
225 Never raises: a line that cannot be computed is a row ``{line, side, available: false, error}``."""
226 rows: List[Dict[str, Any]] = []
227 for side, key in SIDES:
228 try:
229 rows.append(_row(prep, result, side, key, settings, start))
230 except Exception as exc: # noqa: BLE001
231 rows.append({"line": None, "side": key, **unavailable(f"water hammer: {exc}")})
232 return rows
233
234
235def _row(prep: Any, result: Mapping[str, Any], side: str, key: str, settings: HammerSettings,
236 start: Optional[Mapping[str, Any]]) -> Dict[str, Any]:
237 series = result["series"]
238 firing = [i for i, f in enumerate(series.get("firing") or []) if f]
239 if not firing:
240 raise ValueError("the burn never fired")
241 sub = series[key]
242 # Worst instant for a closure: the largest line flow while firing (the end of a rising burn).
243 i = max(firing, key=lambda j: float(sub["mdot"][j]))
244 m0 = float(sub["mdot"][i])
245 p_tank = float(sub["tank_psia"][i]) * PSI
246 T = float(sub["liquid_K"][i])
247 pc = float(series["chamber"]["pc_psia"][i]) * PSI
248 feed = feed_line(prep, side, mdot_ref=m0, p_ref=p_tank, T_ref=T)
249 if feed.valve is None:
250 raise ValueError("no main valve with a travel time on the drawn path")
251 pipes = [e for e in feed.upstream if e.kind == "pipe"]
252 if not pipes:
253 raise ValueError("no drawn tube between the tank and the main valve")
254 warnings: List[str] = list(feed.warnings)
255 bores = {round(e.bore, 6) for e in pipes}
256 if len(bores) > 1:
257 warnings.append("the upstream tube changes bore; taken as one pipe at the bore nearest the valve")
258 pipe0 = pipes[-1]
259 L = sum(e.length for e in pipes)
260 A = pipe0.area
261 wall = pipe0.wall or 0.0
262 a = wave_speed(feed.a_fluid, feed.rho, pipe0.bore, wall, settings.E_wall_pa, settings.c1)
263 v0 = m0 / (feed.rho * A)
264 closure = settings.closure_s if settings.closure_s is not None else float(feed.valve.travel_s or 0.0)
265 # Split each upstream element's loss into the tank-outlet K (at the reservoir) and friction.
266 comp = prep.model.built.network.branches[pipes[0].id].component
267 inst_params = getattr(comp, "p", {}) or {}
268 K_entry = float(inst_params.get("K_minor", 0.0) or 0.0)
269 R_entry = K_entry / (2.0 * feed.rho * pipes[0].area ** 2)
270 R_up_total = feed.sum("up", "R")
271 R_fric = max(R_up_total - R_entry, 0.0)
272 # Downstream of the seat, quasi-steady: the drawn tube after the valve and the injector (card
273 # capacity at this step).
274 p_in = float(sub["inlet_psia"][i]) * PSI
275 phi = m0 / math.sqrt(max(p_in - pc, 1.0))
276 R_rest = feed.sum("dn", "R") + 1.0 / phi ** 2
277 R_v = feed.valve.R
278 frac = feed.valve.cv_fraction or linear_characteristic
279
280 def valve_R(t: float) -> float:
281 if closure <= 0.0:
282 return math.inf if t > 0.0 else R_v + R_rest
283 x = 1.0 - t / closure
284 f = frac(max(x, 0.0)) if x < 1.0 else 1.0
285 return math.inf if f <= 0.0 else R_v / (f * f) + R_rest
286
287 pipeline = Pipeline(rho=feed.rho, a=a, bore=pipe0.bore, length=L, p_tank=p_tank, mdot0=m0,
288 R_entry=R_entry, R_friction=R_fric)
289 n_step = int(math.ceil(L / (a * settings.max_step_s))) if settings.max_step_s > 0 else 0
290 n_reaches = max(int(settings.n_reaches), n_step)
291 sim = moc(pipeline, t_end=max(closure, 0.0) + settings.after_closure_s, n_reaches=n_reaches,
292 valve_R=valve_R, R_down0=R_v + R_rest)
293 surge_moc = sim["p_max"] - sim["p0_valve"]
294 jouk = joukowsky(feed.rho, a, v0)
295 slow = michaud(feed.rho, L, v0, closure, a)
296 separation = sim["p_min"] < feed.p_sat
297 # Opening surge from the start model.
298 opening: Dict[str, Any] = {"available": False}
299 if start and start.get("available"):
300 arr = (start.get("arrival_mdot") or {}).get(key)
301 if arr:
302 dn_pipes = [e for e in feed.downstream if e.kind == "pipe"]
303 A_front = dn_pipes[-1].area if dn_pipes else A
304 a_front = wave_speed(feed.a_fluid, feed.rho, dn_pipes[-1].bore if dn_pipes else pipe0.bore,
305 (dn_pipes[-1].wall if dn_pipes else pipe0.wall) or 0.0, settings.E_wall_pa, settings.c1)
306 phi_s = (((start.get("model") or {}).get("inputs") or {}).get(f"{key}.phi") or {}).get("value") or phi
307 pa = float(prep.ambient_pa)
308 o = opening_surge(p_front=pa, pc=pa, mdot_arrival=float(arr), phi=float(phi_s), B=a_front / A_front)
309 opening = {"available": True, "peak_psia": o["p_peak"] / PSI, "arrival_mdot": float(arr),
310 "mdot_after": o["mdot_after"],
311 "basis": "start model's rigid-column arrival flow meeting the orifices (gas cushion ignored: "
312 "upper bound); at the manifold, which carries no rating"}
313 tank = feed.tank
314 # ``tank_mawp_psi`` is already the absolute pressure the twin trips the tank at (prepare.vessel_trip:
315 # the drawn MAWP, a bare psi read as gauge, or burst over the safety factor). It used to have the
316 # standard atmosphere added again here: a rating one atmosphere over the trip.
317 mawp = ((prep.derived or {}).get("tank_mawp_psi") or {}).get(tank)
318 rating = float(mawp) if mawp is not None else None
319 said = (((prep.derived or {}).get("vessel_ratings") or {}).get(tank) or {}).get("words")
320 rating_prov = (f"{tank} trips the stand at {said or f'{float(mawp):.1f} psia'} (feedtwin's trip); no line, valve or "
321 "manifold rating is drawn") if mawp is not None else "no rating drawn"
322 peak_close = sim["p_max"]
323 peak_open = opening["peak_psia"] * PSI if opening.get("available") else 0.0
324 peak, peak_source = (peak_close, "closure") if peak_close >= peak_open else (peak_open, "opening")
325 if separation:
326 # Below vapour pressure the column parts and the MOC no longer holds; the cavity's collapse
327 # can put a spike on the line that this run does not compute. Grade on the larger of the
328 # computed peak and the Joukowsky peak instead.
329 warnings.append("column separation: the valve-end pressure falls below the liquid's vapour pressure after "
330 "the closure; the MOC is not valid past it and the cavity collapse is not computed. Graded on "
331 "the larger of the computed peak and the Joukowsky value, which is not an upper bound here: "
332 "a cavity's collapse can exceed it (Bergant, Simpson & Tijsseling, J. Fluids Struct. 22, 2006)")
333 bound = sim["p0_valve"] + jouk
334 if bound > peak:
335 peak, peak_source = bound, "closure (Joukowsky value, column separation; the collapse can exceed it)"
336 ok = None if rating is None else bool(peak / PSI <= rating)
337 characteristic = str((getattr(prep.model.built.network.branches[feed.valve.id].component, "opt", {}) or {})
338 .get("characteristic", "linear"))
339 inputs = {
340 "length": inp(L, "m", "drawn tube, tank outlet to the main valve: " + " + ".join(e.id for e in pipes)),
341 "bore": inp(pipe0.bore, "m", (pipe0.inputs.get(f"{pipe0.id}.bore") or {}).get("provenance", "drawing")),
342 "wall": inp(wall, "m", ((pipe0.inputs.get(f"{pipe0.id}.wall_thickness") or {}).get("provenance", "drawing"))
343 + " -- flagged (AUDIT 6): one wall value is copy-pasted on every edge of the LE4 drawings"),
344 "E_wall": inp(settings.E_wall_pa, "Pa", E_316_SOURCE),
345 "c1": inp(settings.c1, "-", "assumed 1: tube free to move axially (Wylie & Streeter 1993)"),
346 "a_fluid": inp(feed.a_fluid, "m/s", f"CoolProp via feedtwin Fluid('{feed.fluid}') at {p_tank / PSI:.1f} psia, {T:.2f} K"),
347 "rho": inp(feed.rho, "kg/m^3", f"CoolProp via feedtwin Fluid('{feed.fluid}')"),
348 "mdot0": inp(m0, "kg/s", f"largest line flow while firing (series, t = {float(series['t'][i]):.3f} s)"),
349 "p_tank": inp(p_tank / PSI, "psia", "tank at that step (series)"),
350 "closure_s": inp(closure, "s", ((feed.valve.inputs.get(f"{feed.valve.id}.travel_time") or {}).get(
351 "provenance", "drawing main-valve travel_time") + " (AUDIT 6: no test cited)")
352 if settings.closure_s is None else "setting"),
353 "n_reaches": inp(sim["n_reaches"], "-", f"MOC reaches: at least {settings.n_reaches}, and a step under "
354 f"{settings.max_step_s:g} s"),
355 "K_entry": inp(K_entry, "-", "drawing K_minor of the first tube (tank exit)"),
356 "characteristic": inp(characteristic, "-", "the main valve's Cv characteristic as the twin builds it (the drawn "
357 "mains declare none, so feedtwin's default; AUDIT 9.5 §5). With a valve this large against "
358 "the line and injector, the flow falls only in the last few % of travel, so the effective "
359 "closure is a few ms, not the full travel"),
360 "p_sat": inp(feed.p_sat / PSI, "psia", "vapour pressure at the liquid temperature (feedtwin Fluid)"),
361 }
362 model = model_block(
363 MODEL_NAME,
364 "Joukowsky (1898) dp = rho a dv; Michaud's (1878) slow-closure figure 2 rho L v0 / t_c; the method of "
365 "characteristics for a reservoir-pipe-valve line and the Korteweg wave speed (Wylie & Streeter, Fluid "
366 "Transients in Systems, 1993); opening surge as the MOC orifice boundary at the arriving front.",
367 [
368 "the tank holds its pressure behind the outlet loss (ullage compliance >> line compliance)",
369 "the upstream tube is one pipe at the bore nearest the valve; friction steady (frozen factor)",
370 "the valve closes linearly in travel along its Cv characteristic; downstream of the seat the tube and "
371 "injector are quasi-steady and discharge to the chamber held at its pre-closure pressure (a Pc that "
372 "decays during the closure keeps more flow through the last of the travel and steepens the stop, so "
373 "the computed peak can be low; Joukowsky is the first-wave limit)",
374 "the liquid column downstream of the seat is not followed: its own suction and column separation at "
375 "the seat's downstream face are not computed",
376 "worst instant: the largest line flow of the burn, at that step's tank pressure",
377 "column separation (pressure below vapour) ends the MOC's validity; it is flagged, not modelled, and "
378 "graded on the Joukowsky value, which a cavity's collapse can exceed",
379 "the opening surge ignores the gas cushion in the manifold (upper bound)",
380 "ratings: only the tanks are rated on the drawing (MAWP or burst, estimated); lines are graded against the "
381 "pressure their tank trips the stand at",
382 ],
383 inputs)
384 return {
385 "line": "+".join(e.id for e in pipes), "side": key, "valve": feed.valve.id, "available": True,
386 "closure_s": closure,
387 "joukowsky_psi": jouk / PSI,
388 "slow_close_psi": slow / PSI,
389 "moc_surge_psi": surge_moc / PSI,
390 "peak_psia": peak / PSI,
391 "peak_source": peak_source,
392 "close_peak_psia": peak_close / PSI,
393 "min_psia": sim["p_min"] / PSI,
394 "column_separation": bool(separation),
395 "opening": opening,
396 "rating_psia": rating,
397 "rating_basis": rating_prov,
398 "ok": ok,
399 "wave_speed_m_s": a, "v0_m_s": v0, "length_m": L, "round_trip_s": 2.0 * L / a,
400 "at_s": float(series["t"][i]),
401 "warnings": warnings,
402 "unmeasured": [f"closure time (travel {closure:g} s, no test cited)", "main valve Cv characteristic (not drawn)",
403 "tube wall (copy-pasted on the drawing)",
404 "E at LOX temperature", "c1 (tube anchoring)", "line and manifold pressure ratings",
405 "manifold gas cushion at priming"],
406 "model": model,
407 }
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
float _solve_boundary(float R, float B, float rhs)
float joukowsky(float rho, float a, float dv)
float michaud(float rho, float length, float v0, float closure_s, float a)
Dict[str, float] opening_surge(*, float p_front, float pc, float mdot_arrival, float phi, float B)
Dict[str, Any] _row(Any prep, Mapping[str, Any] result, str side, str key, HammerSettings settings, Optional[Mapping[str, Any]] start)
Dict[str, Any] moc(Pipeline pipe, *, float t_end, int n_reaches=16, Optional[Callable[[float], float]] valve_R=None, Optional[Callable[[float], float]] valve_flow=None, Optional[float] R_down0=None)
float wave_speed(float a_fluid, float rho, float bore, float wall, float E=E_316_PA, float c1=1.0)
List[Dict[str, Any]] waterhammer_from_run(Any prep, Mapping[str, Any] result, Any config=None, HammerSettings settings=HammerSettings(), Optional[Mapping[str, Any]] start=None)