STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
feed_loss.py
Go to the documentation of this file.
1"""Generalized feed system pressure loss model with K_eff(P)
2
3For twin balanced parallel runs each with nominal diameter ``d_line``, set YAML
4``A_hydraulic = 2 × π (d_line/2)²`` with ``d_inlet`` omitted (the schema derives the equal-area
5bore) so bulk velocity halves vs a single tube at fixed ṁ.
6
7See constants: ``FEED_LINE_DUAL_3_8_EQUIVALENT_D_INLET_M`` and
8``FEED_LINE_DUAL_3_8_EQUIVALENT_A_HYDRAULIC_M2`` for two 3/8″ lines.
9"""
10
11import logging
12import math
13from typing import Optional
14
15import numpy as np
16from .config_schemas import FeedSystemConfig
17
18try: # the reference implementation; EngineDesign does not require it
19 from fluids.friction import friction_factor as _fluids_friction_factor
20except Exception: # pragma: no cover - exercised only where fluids is absent
21 _fluids_friction_factor = None
22
23_log = logging.getLogger(__name__)
24_warned_no_mu = False
25
26#: Laminar/turbulent switch for a pipe, as fluids.friction.LAMINAR_TRANSITION_PIPE (2040,
27#: Avila et al. 2011). Below it f = 64/Re; the fallback mirrors fluids so both agree.
28LAMINAR_TRANSITION_PIPE = 2040.0
29
30
31def colebrook_darcy(Re: float, eD: float) -> float:
32 """Darcy f from Colebrook-White, 1/sqrt(f) = -2 log10(eD/3.7 + 2.51/(Re sqrt(f))).
33
34 Solved to machine precision by fixed-point iteration on x = 1/sqrt(f) from the Haaland
35 (1983) explicit start. This is the fallback for when ``fluids`` is not importable; the
36 tests hold it to fluids.friction.Colebrook. Laminar (Re < 2040) returns 64/Re, as
37 fluids.friction.friction_factor does.
38 """
39 if not (Re > 0.0):
40 return 0.0
41 if Re < LAMINAR_TRANSITION_PIPE:
42 return 64.0 / Re
43 # Haaland: 1/sqrt(f) = -1.8 log10((eD/3.7)^1.11 + 6.9/Re)
44 x = -1.8 * math.log10((eD / 3.7) ** 1.11 + 6.9 / Re)
45 for _ in range(100):
46 x_new = -2.0 * math.log10(eD / 3.7 + 2.51 * x / Re)
47 if abs(x_new - x) <= 1e-15 * abs(x_new):
48 x = x_new
49 break
50 x = x_new
51 return 1.0 / (x * x)
52
53
54def darcy_friction_factor(Re: float, eD: float) -> float:
55 """Darcy friction factor: fluids.friction.friction_factor (Clamond's exact Colebrook
56 solution) when fluids is installed, colebrook_darcy() otherwise."""
57 if not (Re > 0.0):
58 return 0.0
59 if _fluids_friction_factor is not None:
60 return float(_fluids_friction_factor(Re=Re, eD=eD))
61 return colebrook_darcy(Re, eD)
62
63
64def fittings_K(config: FeedSystemConfig) -> float:
65 """Sum of the itemised fitting K's (line velocity heads); 0.0 when none are declared.
66
67 engine/accel/params.py folds this into K0 for the kernel, so the order of summation here
68 is the order there.
69 """
70 total = 0.0
71 for f in getattr(config, "fittings", None) or ():
72 total += float(f.K)
73 return total
74
75
77 mdot: float,
78 rho: float,
79 config: FeedSystemConfig,
80 P_tank: float,
81 mu: Optional[float] = None,
82) -> float:
83 """
84 Calculate feed system pressure loss using generalized K_eff(P) model.
85
86 Δp_feed = K_eff(P) × (ρ/2) × (ṁ/(ρ×A_hyd))² + K_exit × (ρ/2) × (ṁ/(ρ×A_exit))²
87
88 where K_eff(P) = K_base + ΣK_fittings + K1 × φ(P), K_base = K0, or (roughness_m set and mu
89 given) K_entrance + f L/d_inlet with Darcy f from Colebrook-White at Re = ρ v d_inlet/μ;
90 and the second term is the exit bore's velocity head
91 dumped into the manifold (Borda-Carnot; K_exit = 1 for a plenum). P_tank minus this is the
92 still-manifold pressure that drives the orifices.
93
94 Parameters:
95 -----------
96 mdot : float
97 Mass flow rate [kg/s]
98 rho : float
99 Fluid density [kg/m³]
100 config : FeedSystemConfig
101 Feed system configuration
102 P_tank : float
103 Tank pressure [Pa] (used for pressure-dependent K_eff)
104 mu : float, optional
105 Liquid dynamic viscosity [Pa s]. Needed only by the friction path (``roughness_m``
106 set): K0 is replaced by K_entrance + f L/d_inlet with Colebrook f at
107 Re = rho v d_inlet / mu. Without mu that path cannot run and the K0 path is used
108 (logged once).
109
110 Fittings (``config.fittings``) add to K_eff on either path.
111
112 Returns:
113 --------
114 delta_p : float
115 Pressure loss [Pa]
116 """
117 global _warned_no_mu
118 roughness = getattr(config, "roughness_m", None)
119 use_friction = roughness is not None
120 if use_friction and mu is None:
121 use_friction = False
122 if not _warned_no_mu:
123 _warned_no_mu = True
124 _log.warning(
125 "feed_loss: roughness_m is set but the caller passed no viscosity; using K0 "
126 "(the measured-override path) instead of Colebrook friction.")
127 if use_friction and not (mu > 0):
128 raise ValueError(f"Invalid viscosity for feed friction: mu={mu!r} Pa s. Must be > 0.")
129
130 # Base coefficient: K0 (measured/lumped) or entrance + Colebrook f L/D at this call's Re.
131 # Fittings add to either. With no fittings and roughness_m None this is exactly K0.
132 if use_friction:
133 d = float(config.d_inlet)
134 v_line = mdot / (rho * float(config.A_hydraulic)) if rho > 0 else 0.0
135 Re = rho * v_line * d / float(mu)
136 f = darcy_friction_factor(Re, float(roughness) / d)
137 K_base = float(config.K_entrance) + f * float(config.length) / d
138 else:
139 K_base = config.K0
140 if getattr(config, "fittings", None):
141 K_base = K_base + fittings_K(config)
142
143 # Calculate effective loss coefficient
144 if config.phi_type == "none":
145 K_eff = K_base
146 elif config.phi_type == "sqrtP":
147 # FIXED: Ensure sqrt input is positive
148 K_eff = K_base + config.K1 * np.sqrt(max(0, P_tank))
149 elif config.phi_type == "logP":
150 K_eff = K_base + config.K1 * np.log(P_tank)
151 else:
152 raise ValueError(f"Unknown phi_type: {config.phi_type}")
153
154 # The passage is A_hydraulic (the schema derives it from d_inlet when omitted); the same
155 # area sets the chug model's line inertance.
156 A_area = float(config.A_hydraulic)
157 d_exit = getattr(config, "d_exit", None)
158 A_exit = np.pi * (float(d_exit) / 2.0) ** 2 if d_exit else A_area
159 K_exit = float(getattr(config, "K_exit", 0.0) or 0.0)
160
161 # Validate inputs
162 if A_area <= 0:
163 raise ValueError(f"Invalid feed system area: A_area={A_area:.6e} m². Must be > 0. Check d_inlet or A_hydraulic in config.")
164 if rho <= 0:
165 raise ValueError(f"Invalid fluid density: rho={rho:.2f} kg/m³. Must be > 0.")
166 if mdot < 0:
167 raise ValueError(f"Invalid mass flow: mdot={mdot:.4f} kg/s. Must be >= 0.")
168
169 # Calculate velocity
170 velocity = mdot / (rho * A_area)
171 v_exit = mdot / (rho * A_exit)
172
173 # Δp_feed = K_eff × (ρ/2) × v² along the line, plus the exit dump at the exit bore
174 delta_p = K_eff * (rho / 2) * velocity**2 + K_exit * (rho / 2) * v_exit**2
175
176 # Ensure non-negative (pressure loss can't be negative)
177 delta_p = max(0.0, delta_p)
178
179 # Debug output for zero pressure drop
180 if delta_p == 0.0 and mdot > 0.01: # Only warn if there's significant flow
181 import warnings
182 warnings.warn(
183 f"Feed system pressure drop is zero with mdot={mdot:.4f} kg/s, rho={rho:.2f} kg/m³, "
184 f"A_area={A_area:.6e} m², K_eff={K_eff:.2f}, velocity={velocity:.2f} m/s. "
185 f"Check feed system configuration."
186 )
187
188 return float(delta_p)
189
190
191
#define max(a, b)
Definition Arduino.h:43
float darcy_friction_factor(float Re, float eD)
Definition feed_loss.py:54
float delta_p_feed(float mdot, float rho, FeedSystemConfig config, float P_tank, Optional[float] mu=None)
Definition feed_loss.py:82
float colebrook_darcy(float Re, float eD)
Definition feed_loss.py:31
float fittings_K(FeedSystemConfig config)
Definition feed_loss.py:64