STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
validation.py
Go to the documentation of this file.
1"""Centralized validation functions for physics quantities.
2
3This module provides explicit validation functions that raise clear errors
4instead of hiding problems with clipping, clamping, or default fallbacks.
5
6Design principles:
71. NEVER hide errors with defaults or clipping
82. Raise ValueError with descriptive messages
93. Include relevant context in error messages
104. Validate physical constraints explicitly
11"""
12
13import numpy as np
14from typing import Any, Optional, Tuple
15
16
18 value: float,
19 name: str,
20 context: Optional[str] = None
21) -> None:
22 """Validate that a value is positive and finite.
23
24 Parameters
25 ----------
26 value : float
27 Value to validate
28 name : str
29 Name of the variable for error message
30 context : str, optional
31 Additional context for error message
32
33 Raises
34 ------
35 ValueError
36 If value is not positive and finite
37 """
38 if not np.isfinite(value):
39 msg = f"Non-finite {name}: {value}"
40 if context:
41 msg += f". Context: {context}"
42 raise ValueError(msg)
43
44 if value <= 0:
45 msg = f"Non-positive {name}: {value}. Must be > 0"
46 if context:
47 msg += f". Context: {context}"
48 raise ValueError(msg)
49
50
52 value: float,
53 name: str,
54 min_val: float,
55 max_val: float,
56 context: Optional[str] = None
57) -> None:
58 """Validate that a value is in a specified range.
59
60 Parameters
61 ----------
62 value : float
63 Value to validate
64 name : str
65 Name of the variable for error message
66 min_val : float
67 Minimum allowed value (inclusive)
68 max_val : float
69 Maximum allowed value (inclusive)
70 context : str, optional
71 Additional context for error message
72
73 Raises
74 ------
75 ValueError
76 If value is outside the specified range
77 """
78 if not np.isfinite(value):
79 msg = f"Non-finite {name}: {value}"
80 if context:
81 msg += f". Context: {context}"
82 raise ValueError(msg)
83
84 if not (min_val <= value <= max_val):
85 msg = f"{name} out of range: {value}. Must be in [{min_val}, {max_val}]"
86 if context:
87 msg += f". Context: {context}"
88 raise ValueError(msg)
89
90
92 efficiency: float,
93 name: str,
94 context: Optional[str] = None
95) -> None:
96 """Validate that an efficiency is in [0, 1].
97
98 Parameters
99 ----------
100 efficiency : float
101 Efficiency value to validate
102 name : str
103 Name of the efficiency for error message
104 context : str, optional
105 Additional context (e.g., "L*=0.15 m, mixing=0.8")
106
107 Raises
108 ------
109 ValueError
110 If efficiency is not in [0, 1]
111 """
112 validate_range(efficiency, name, 0.0, 1.0, context)
113
114
116 temperature: float,
117 name: str,
118 min_temp: float = 200.0,
119 max_temp: float = 5000.0,
120 context: Optional[str] = None
121) -> None:
122 """Validate that a temperature is physically reasonable.
123
124 Parameters
125 ----------
126 temperature : float
127 Temperature in Kelvin
128 name : str
129 Name of the temperature for error message
130 min_temp : float, optional
131 Minimum reasonable temperature (default: 200 K)
132 max_temp : float, optional
133 Maximum reasonable temperature (default: 5000 K)
134 context : str, optional
135 Additional context for error message
136
137 Raises
138 ------
139 ValueError
140 If temperature is outside reasonable range
141 """
142 if not np.isfinite(temperature):
143 msg = f"Non-finite {name}: {temperature} K"
144 if context:
145 msg += f". Context: {context}"
146 raise ValueError(msg)
147
148 if temperature < min_temp or temperature > max_temp:
149 msg = (
150 f"{name} outside reasonable range: {temperature:.1f} K. "
151 f"Expected [{min_temp:.1f}, {max_temp:.1f}] K for rocket engines"
152 )
153 if context:
154 msg += f". Context: {context}"
155 raise ValueError(msg)
156
157
159 pressure: float,
160 name: str,
161 min_pressure: float = 1e4, # 0.1 bar
162 max_pressure: float = 50e6, # 50 MPa
163 context: Optional[str] = None
164) -> None:
165 """Validate that a pressure is physically reasonable.
166
167 Parameters
168 ----------
169 pressure : float
170 Pressure in Pascal
171 name : str
172 Name of the pressure for error message
173 min_pressure : float, optional
174 Minimum reasonable pressure (default: 10 kPa)
175 max_pressure : float, optional
176 Maximum reasonable pressure (default: 50 MPa)
177 context : str, optional
178 Additional context for error message
179
180 Raises
181 ------
182 ValueError
183 If pressure is outside reasonable range
184 """
185 if not np.isfinite(pressure):
186 msg = f"Non-finite {name}: {pressure} Pa"
187 if context:
188 msg += f". Context: {context}"
189 raise ValueError(msg)
190
191 if pressure < 0:
192 msg = f"Negative {name}: {pressure:.3e} Pa. Pressure cannot be negative"
193 if context:
194 msg += f". Context: {context}"
195 raise ValueError(msg)
196
197 if pressure < min_pressure or pressure > max_pressure:
198 msg = (
199 f"{name} outside reasonable range: {pressure/1e6:.4f} MPa. "
200 f"Expected [{min_pressure/1e6:.4f}, {max_pressure/1e6:.4f}] MPa for rocket engines"
201 )
202 if context:
203 msg += f". Context: {context}"
204 raise ValueError(msg)
205
206
208 mach: float,
209 name: str,
210 supersonic: bool = False,
211 context: Optional[str] = None
212) -> None:
213 """Validate that a Mach number is physically reasonable.
214
215 Parameters
216 ----------
217 mach : float
218 Mach number
219 name : str
220 Name of the Mach number for error message
221 supersonic : bool, optional
222 If True, require M > 1 (default: False)
223 context : str, optional
224 Additional context for error message
225
226 Raises
227 ------
228 ValueError
229 If Mach number is invalid
230 """
231 if not np.isfinite(mach):
232 msg = f"Non-finite {name}: {mach}"
233 if context:
234 msg += f". Context: {context}"
235 raise ValueError(msg)
236
237 if mach < 0:
238 msg = f"Negative {name}: {mach}. Mach number cannot be negative"
239 if context:
240 msg += f". Context: {context}"
241 raise ValueError(msg)
242
243 if supersonic and mach <= 1.0:
244 msg = f"Subsonic {name}: {mach:.4f}. Expected supersonic (M > 1)"
245 if context:
246 msg += f". Context: {context}"
247 raise ValueError(msg)
248
249 if mach > 10.0:
250 msg = f"Unreasonably high {name}: {mach:.4f}. Expected < 10 for rocket nozzles"
251 if context:
252 msg += f". Context: {context}"
253 raise ValueError(msg)
254
255
257 gamma: float,
258 name: str = "gamma",
259 context: Optional[str] = None
260) -> None:
261 """Validate that specific heat ratio is physically reasonable.
262
263 Parameters
264 ----------
265 gamma : float
266 Specific heat ratio
267 name : str, optional
268 Name for error message (default: "gamma")
269 context : str, optional
270 Additional context for error message
271
272 Raises
273 ------
274 ValueError
275 If gamma is not in reasonable range
276 """
277 if not np.isfinite(gamma):
278 msg = f"Non-finite {name}: {gamma}"
279 if context:
280 msg += f". Context: {context}"
281 raise ValueError(msg)
282
283 if gamma <= 1.0:
284 msg = f"Invalid {name}: {gamma:.4f}. Must be > 1.0 for physical gas"
285 if context:
286 msg += f". Context: {context}"
287 raise ValueError(msg)
288
289 if gamma < 1.05 or gamma > 1.67:
290 msg = (
291 f"{name} outside typical range: {gamma:.4f}. "
292 f"Expected [1.05, 1.67] for rocket combustion products"
293 )
294 if context:
295 msg += f". Context: {context}"
296 raise ValueError(msg)
297
298
300 data_dict: dict,
301 required_keys: list,
302 dict_name: str = "dictionary"
303) -> None:
304 """Validate that all required keys are present in a dictionary.
305
306 Parameters
307 ----------
308 data_dict : dict
309 Dictionary to validate
310 required_keys : list
311 List of required key names
312 dict_name : str, optional
313 Name of the dictionary for error message
314
315 Raises
316 ------
317 KeyError
318 If any required keys are missing
319 """
320 missing_keys = [key for key in required_keys if key not in data_dict]
321
322 if missing_keys:
323 raise KeyError(
324 f"Missing required keys in {dict_name}: {missing_keys}. "
325 f"Available keys: {list(data_dict.keys())}"
326 )
327
328
330 array: np.ndarray,
331 name: str,
332 context: Optional[str] = None
333) -> None:
334 """Validate that an array is monotonically decreasing.
335
336 Parameters
337 ----------
338 array : np.ndarray
339 Array to validate
340 name : str
341 Name of the array for error message
342 context : str, optional
343 Additional context for error message
344
345 Raises
346 ------
347 ValueError
348 If array is not monotonically decreasing
349 """
350 if not np.all(np.isfinite(array)):
351 msg = f"Non-finite values in {name}"
352 if context:
353 msg += f". Context: {context}"
354 raise ValueError(msg)
355
356 # Check monotonicity
357 diff = np.diff(array)
358 if not np.all(diff <= 0):
359 # Find first violation
360 violation_idx = np.where(diff > 0)[0][0]
361 msg = (
362 f"{name} is not monotonically decreasing. "
363 f"Violation at index {violation_idx}: "
364 f"value[{violation_idx}]={array[violation_idx]:.3e}, "
365 f"value[{violation_idx+1}]={array[violation_idx+1]:.3e}"
366 )
367 if context:
368 msg += f". Context: {context}"
369 raise ValueError(msg)
370
371
373 mdot: float,
374 name: str,
375 min_mdot: float = 0.001, # 1 g/s
376 max_mdot: float = 100.0, # 100 kg/s
377 context: Optional[str] = None
378) -> None:
379 """Validate that a mass flow rate is physically reasonable.
380
381 Parameters
382 ----------
383 mdot : float
384 Mass flow rate in kg/s
385 name : str
386 Name of the mass flow for error message
387 min_mdot : float, optional
388 Minimum reasonable mass flow (default: 1 g/s)
389 max_mdot : float, optional
390 Maximum reasonable mass flow (default: 100 kg/s)
391 context : str, optional
392 Additional context for error message
393
394 Raises
395 ------
396 ValueError
397 If mass flow rate is invalid
398 """
399 if not np.isfinite(mdot):
400 msg = f"Non-finite {name}: {mdot} kg/s"
401 if context:
402 msg += f". Context: {context}"
403 raise ValueError(msg)
404
405 if mdot <= 0:
406 msg = f"Non-positive {name}: {mdot:.6f} kg/s. Must be > 0"
407 if context:
408 msg += f". Context: {context}"
409 raise ValueError(msg)
410
411 if mdot < min_mdot or mdot > max_mdot:
412 msg = (
413 f"{name} outside reasonable range: {mdot:.4f} kg/s. "
414 f"Expected [{min_mdot:.4f}, {max_mdot:.4f}] kg/s"
415 )
416 if context:
417 msg += f". Context: {context}"
418 raise ValueError(msg)
419
None validate_mach_number(float mach, str name, bool supersonic=False, Optional[str] context=None)
None validate_mass_flow_rate(float mdot, str name, float min_mdot=0.001, float max_mdot=100.0, Optional[str] context=None)
None validate_efficiency(float efficiency, str name, Optional[str] context=None)
Definition validation.py:95
None validate_required_keys(dict data_dict, list required_keys, str dict_name="dictionary")
None validate_positive(float value, str name, Optional[str] context=None)
Definition validation.py:21
None validate_monotonic_decreasing(np.ndarray array, str name, Optional[str] context=None)
None validate_temperature(float temperature, str name, float min_temp=200.0, float max_temp=5000.0, Optional[str] context=None)
None validate_pressure(float pressure, str name, float min_pressure=1e4, float max_pressure=50e6, Optional[str] context=None)
None validate_range(float value, str name, float min_val, float max_val, Optional[str] context=None)
Definition validation.py:57
None validate_gamma(float gamma, str name="gamma", Optional[str] context=None)