1"""Data models for robust DDP controller."""
3from __future__
import annotations
5from dataclasses
import dataclass, field, asdict
7from typing
import Optional, Union, List, Dict, Any
10from datetime
import datetime
20 """Type of control command."""
21 THRUST_DESIRED =
"thrust_desired"
22 ALTITUDE_GOAL =
"altitude_goal"
27 """Sensor measurement data.
29 Contains pressure transducer readings and timestamp.
30 All pressures in Pascals [Pa].
47 timestamp: Union[float, datetime] = field(default_factory=datetime.now)
50 """Validate measurement data."""
61 for i, p
in enumerate(pressures):
63 raise ValueError(f
"Pressure {i} is negative: {p} Pa")
64 if not np.isfinite(p):
65 raise ValueError(f
"Pressure {i} is not finite: {p}")
68 """Convert to dictionary for serialization."""
80 def from_dict(cls, data: Dict[str, Any]) -> Measurement:
81 """Create from dictionary."""
83 P_copv=float(data[
"P_copv"]),
84 P_reg=float(data[
"P_reg"]),
85 P_u_fuel=float(data[
"P_u_fuel"]),
86 P_u_ox=float(data[
"P_u_ox"]),
87 P_d_fuel=float(data[
"P_d_fuel"]),
88 P_d_ox=float(data[
"P_d_ox"]),
89 timestamp=float(data.get(
"timestamp", datetime.now().
timestamp())),
95 """Navigation state (vehicle state).
97 Contains altitude, velocity, attitude, and mass estimate.
110 quaternion: Optional[List[float]] =
None
113 mass_estimate: Optional[float] =
None
116 """Validate navigation state."""
118 raise ValueError(f
"Altitude h is negative: {self.h} m")
119 if not np.isfinite(self.
h):
120 raise ValueError(f
"Altitude h is not finite: {self.h}")
121 if not np.isfinite(self.
vz):
122 raise ValueError(f
"Vertical velocity vz is not finite: {self.vz}")
123 if not np.isfinite(self.
theta):
124 raise ValueError(f
"Tilt angle theta is not finite: {self.theta}")
129 raise ValueError(f
"Quaternion must have 4 elements, got {len(self.quaternion)}")
131 if not np.isclose(q_norm, 1.0, atol=1e-3):
132 raise ValueError(f
"Quaternion must be normalized, got norm={q_norm}")
137 raise ValueError(f
"Mass estimate must be positive, got {self.mass_estimate} kg")
139 raise ValueError(f
"Mass estimate is not finite: {self.mass_estimate}")
142 """Convert to dictionary for serialization."""
145 "vz": float(self.
vz),
146 "theta": float(self.
theta),
149 result[
"quaternion"] = [float(q)
for q
in self.
quaternion]
156 """Create from dictionary."""
159 vz=float(data.get(
"vz", 0.0)),
160 theta=float(data.get(
"theta", 0.0)),
161 quaternion=data.get(
"quaternion"),
162 mass_estimate=data.get(
"mass_estimate"),
170 Supports either thrust desired (time-varying) or altitude goal.
173 command_type: CommandType
177 thrust_desired: Optional[Union[float, List[tuple]]] =
None
180 altitude_goal: Optional[float] =
None
183 """Validate command."""
186 raise ValueError(
"thrust_desired must be provided for THRUST_DESIRED command")
189 raise ValueError(f
"Thrust desired must be non-negative, got {self.thrust_desired} N")
193 raise ValueError(f
"Thrust at t={t} is negative: {F} N")
196 raise ValueError(
"altitude_goal must be provided for ALTITUDE_GOAL command")
198 raise ValueError(f
"Altitude goal must be non-negative, got {self.altitude_goal} m")
201 """Convert to dictionary for serialization."""
213 """Create from dictionary."""
216 command_type=command_type,
217 thrust_desired=data.get(
"thrust_desired"),
218 altitude_goal=data.get(
"altitude_goal"),
224 """Controller configuration parameters.
226 Contains all tunable parameters for the robust DDP controller.
233 dwell_time: float = 0.05
236 duty_quantization: float = 0.01
242 qSwitch: float = 0.0001
247 injector_dp_frac: float = 0.1
249 P_u_max: float = 10e6
250 P_copv_min: float = 1e6
251 headroom_dp_min: float = 0.05e6
258 max_iterations: int = 20
259 convergence_tol: float = 1e-3
265 copv_loss: float = 1e3
268 reg_setpoint: Optional[float] =
None
269 reg_ratio: float = 0.8
273 gamma_gas: float = 1.4
274 Cd_regulator: float = 0.7
275 Cd_valve: float = 0.65
276 A_regulator: float = 2e-5
277 A_valve_F: float = 5e-5
278 A_valve_O: float = 5e-5
281 alpha_F: float = 10.0
282 alpha_O: float = 10.0
286 rho_O: float = 1140.0
289 tau_line_F: float = 0.01
290 tau_line_O: float = 0.01
293 V_u_F_init: Optional[float] =
None
294 V_u_O_init: Optional[float] =
None
297 use_policy_lut: bool =
False
298 policy_lut_path: Optional[str] =
None
301 engine_lut_path: Optional[str] =
None
304 """Validate configuration."""
306 raise ValueError(f
"N must be positive, got {self.N}")
308 raise ValueError(f
"dt must be positive, got {self.dt} s")
310 raise ValueError(f
"dwell_time must be non-negative, got {self.dwell_time} s")
312 raise ValueError(f
"duty_quantization must be in (0, 1], got {self.duty_quantization}")
314 raise ValueError(f
"MR_min ({self.MR_min}) must be < MR_max ({self.MR_max})")
316 raise ValueError(f
"P_u_max must be positive, got {self.P_u_max} Pa")
318 raise ValueError(f
"P_copv_min must be positive, got {self.P_copv_min} Pa")
320 raise ValueError(f
"rho must be non-negative, got {self.rho}")
322 raise ValueError(f
"eta must be non-negative, got {self.eta}")
326 raise ValueError(f
"copv_cF must be non-negative, got {self.copv_cF}")
328 raise ValueError(f
"copv_cO must be non-negative, got {self.copv_cO}")
330 raise ValueError(f
"copv_loss must be non-negative, got {self.copv_loss}")
332 raise ValueError(f
"reg_ratio must be in (0, 1], got {self.reg_ratio}")
334 raise ValueError(f
"alpha_F must be positive, got {self.alpha_F}")
336 raise ValueError(f
"alpha_O must be positive, got {self.alpha_O}")
338 raise ValueError(f
"rho_F must be positive, got {self.rho_F}")
340 raise ValueError(f
"rho_O must be positive, got {self.rho_O}")
342 raise ValueError(f
"tau_line_F must be positive, got {self.tau_line_F}")
344 raise ValueError(f
"tau_line_O must be positive, got {self.tau_line_O}")
347 """Convert to dictionary for serialization."""
351 def from_dict(cls, data: Dict[str, Any]) -> ControllerConfig:
352 """Create from dictionary."""
356 """Save to JSON file."""
357 with open(filepath,
'w')
as f:
358 json.dump(self.
to_dict(), f, indent=2)
362 """Load from JSON file."""
363 with open(filepath,
'r')
as f:
368 """Save to YAML file."""
369 with open(filepath,
'w')
as f:
370 yaml.dump(self.
to_dict(), f, default_flow_style=
False, indent=2)
374 """Load from YAML file."""
375 with open(filepath,
'r')
as f:
376 data = yaml.safe_load(f)
382 """Persistent controller state.
384 Maintains state across control ticks for robust DDP controller.
387 u_prev: Dict[str, float] = field(default_factory=
lambda: {
"P_u_fuel": 0.0,
"P_u_ox": 0.0})
390 dwell_timers: Dict[str, float] = field(default_factory=
lambda: {
"P_u_fuel": 0.0,
"P_u_ox": 0.0})
393 last_trajectory: Optional[List[Dict[str, Any]]] =
None
397 w_bar: Dict[str, float] = field(default_factory=
lambda: {
"thrust": 0.0,
"MR": 0.0})
398 w_bar_array: Optional[np.ndarray] =
None
404 iteration_count: int = 0
405 last_cost: float = float(
'inf')
408 """Reset controller state to initial values."""
409 self.
u_prev = {
"P_u_fuel": 0.0,
"P_u_ox": 0.0}
412 self.
w_bar = {
"thrust": 0.0,
"MR": 0.0}
419 """Convert to dictionary for serialization."""
421 "u_prev": self.
u_prev.copy(),
424 "w_bar": self.
w_bar.copy(),
425 "beta": float(self.
beta),
434 def from_dict(cls, data: Dict[str, Any]) -> ControllerState:
435 """Create from dictionary."""
437 u_prev=data.get(
"u_prev", {
"P_u_fuel": 0.0,
"P_u_ox": 0.0}),
438 dwell_timers=data.get(
"dwell_timers", {
"P_u_fuel": 0.0,
"P_u_ox": 0.0}),
439 last_trajectory=data.get(
"last_trajectory"),
440 w_bar=data.get(
"w_bar", {
"thrust": 0.0,
"MR": 0.0}),
441 beta=float(data.get(
"beta", 0.0)),
442 iteration_count=int(data.get(
"iteration_count", 0)),
443 last_cost=float(data.get(
"last_cost", float(
'inf')))
if data.get(
"last_cost")
is not None else float(
'inf'),
445 if "w_bar_array" in data
and data[
"w_bar_array"]
is not None:
446 state.w_bar_array = np.array(data[
"w_bar_array"], dtype=np.float64)
Command from_dict(cls, Dict[str, Any] data)
Dict[str, Any] to_dict(self)
ControllerConfig from_json(cls, str filepath)
ControllerConfig from_dict(cls, Dict[str, Any] data)
Dict[str, Any] to_dict(self)
None to_json(self, str filepath)
None to_yaml(self, str filepath)
ControllerConfig from_yaml(cls, str filepath)
ControllerState from_dict(cls, Dict[str, Any] data)
Dict[str, Any] to_dict(self)
Dict[str, Any] to_dict(self)
Measurement from_dict(cls, Dict[str, Any] data)
Dict[str, Any] to_dict(self)
NavState from_dict(cls, Dict[str, Any] data)