1"""Online parameter identification for robust DDP controller.
3Uses Recursive Least Squares (RLS) with forgetting factor to identify:
4- alpha_F, alpha_O: Pressurization flow coefficients
5- tau_line_F, tau_line_O: Feed line time constants
6- copv_cF, copv_cO: COPV consumption coefficients
9from __future__
import annotations
11from typing
import Optional
14from .data_models
import ControllerConfig, ControllerState, Measurement
15from .dynamics
import (
30 """Online parameter identifier using RLS with forgetting factor."""
32 def __init__(self, cfg: ControllerConfig, forgetting_factor: float = 0.99):
34 Initialize parameter identifier.
38 cfg : ControllerConfig
39 Controller configuration (contains initial parameter values)
40 forgetting_factor : float
41 RLS forgetting factor (0 < lambda < 1, closer to 1 = slower forgetting)
75 self.
x_prev: Optional[np.ndarray] =
None
76 self.
u_prev: Optional[np.ndarray] =
None
86 state: ControllerState,
88 cfg: ControllerConfig,
89 x_current: np.ndarray,
90 u_current: np.ndarray,
92 mdot_F: Optional[float] =
None,
93 mdot_O: Optional[float] =
None,
96 Update identified parameters from measurements.
100 state : ControllerState
101 Controller state (not used, but kept for API consistency)
104 cfg : ControllerConfig
105 Controller configuration
106 x_current : np.ndarray
108 u_current : np.ndarray
109 Current control input
112 mdot_F : float, optional
113 Fuel mass flow [kg/s]
114 mdot_O : float, optional
115 Oxidizer mass flow [kg/s]
119 self.
x_prev = x_current.copy()
120 self.
u_prev = u_current.copy()
121 self.
t_prev = meas.timestamp
if hasattr(meas,
'timestamp')
else 0.0
125 dP_copv_dt = (x_current[IDX_P_COPV] - self.
x_prev[IDX_P_COPV]) / dt
126 dP_u_F_dt = (x_current[IDX_P_U_F] - self.
x_prev[IDX_P_U_F]) / dt
127 dP_u_O_dt = (x_current[IDX_P_U_O] - self.
x_prev[IDX_P_U_O]) / dt
128 dP_d_F_dt = (x_current[IDX_P_D_F] - self.
x_prev[IDX_P_D_F]) / dt
129 dP_d_O_dt = (x_current[IDX_P_D_O] - self.
x_prev[IDX_P_D_O]) / dt
133 dP_u_F_dt, x_current, u_current, dt, mdot_F
138 dP_u_O_dt, x_current, u_current, dt, mdot_O
151 self.
x_prev = x_current.copy()
152 self.
u_prev = u_current.copy()
153 self.
t_prev = meas.timestamp
if hasattr(meas,
'timestamp')
else self.
t_prev + dt
161 mdot_F: Optional[float],
163 """Update alpha_F using RLS."""
170 if u_F < 0.1
or P_reg <= P_u:
178 if V_u > 1e-10
and mdot_F
is not None:
179 blowdown_term = (P_u / V_u) * (mdot_F / self.
cfg.rho_F)
182 y = dP_u_dt + blowdown_term
185 phi = u_F * (P_reg - P_u)
206 mdot_O: Optional[float],
208 """Update alpha_O using RLS."""
215 if u_O < 0.1
or P_reg <= P_u:
220 if V_u > 1e-10
and mdot_O
is not None:
221 blowdown_term = (P_u / V_u) * (mdot_O / self.
cfg.rho_O)
224 y = dP_u_dt + blowdown_term
227 phi = u_O * (P_reg - P_u)
243 """Update tau_line_F using RLS."""
272 """Update tau_line_O using RLS."""
303 """Update COPV consumption coefficients using RLS."""
311 y = -dP_copv_dt - self.
cfg.copv_loss
314 phi = np.array([u_F, u_O])
316 if np.linalg.norm(phi) < 1e-6:
322 if abs(denominator) < 1e-10:
325 K = P_phi / denominator
336 """Get current parameter estimates."""
347 """Update config with identified parameters (slow update)."""
351 cfg.alpha_F = (1 - update_rate) * cfg.alpha_F + update_rate * self.
alpha_F
352 cfg.alpha_O = (1 - update_rate) * cfg.alpha_O + update_rate * self.
alpha_O
353 cfg.tau_line_F = (1 - update_rate) * cfg.tau_line_F + update_rate * self.
tau_line_F
354 cfg.tau_line_O = (1 - update_rate) * cfg.tau_line_O + update_rate * self.
tau_line_O
355 cfg.copv_cF = (1 - update_rate) * cfg.copv_cF + update_rate * self.
copv_cF
356 cfg.copv_cO = (1 - update_rate) * cfg.copv_cO + update_rate * self.
copv_cO
360 identifier: ParameterIdentifier,
361 state: ControllerState,
363 cfg: ControllerConfig,
364 x_current: np.ndarray,
365 u_current: np.ndarray,
367 mdot_F: Optional[float] =
None,
368 mdot_O: Optional[float] =
None,
371 Update identified parameters (convenience function).
375 identifier : ParameterIdentifier
376 Parameter identifier instance
377 state : ControllerState
381 cfg : ControllerConfig
382 Controller configuration
383 x_current : np.ndarray
385 u_current : np.ndarray
386 Current control input
389 mdot_F : float, optional
390 Fuel mass flow [kg/s]
391 mdot_O : float, optional
392 Oxidizer mass flow [kg/s]
394 identifier.update_params(
395 state, meas, cfg, x_current, u_current, dt, mdot_F, mdot_O
Optional[np.ndarray] u_prev
None _update_tau_line_F(self, float dP_d_dt, np.ndarray x, float dt)
None _update_tau_line_O(self, float dP_d_dt, np.ndarray x, float dt)
Optional[np.ndarray] x_prev
None update_params(self, ControllerState state, Measurement meas, ControllerConfig cfg, np.ndarray x_current, np.ndarray u_current, float dt, Optional[float] mdot_F=None, Optional[float] mdot_O=None)
__init__(self, ControllerConfig cfg, float forgetting_factor=0.99)
None _update_alpha_O(self, float dP_u_dt, np.ndarray x, np.ndarray u, float dt, Optional[float] mdot_O)
None _update_alpha_F(self, float dP_u_dt, np.ndarray x, np.ndarray u, float dt, Optional[float] mdot_F)
None _update_copv_coefficients(self, float dP_copv_dt, np.ndarray u, float dt)
None update_config(self, ControllerConfig cfg)
None update_params(ParameterIdentifier identifier, ControllerState state, Measurement meas, ControllerConfig cfg, np.ndarray x_current, np.ndarray u_current, float dt, Optional[float] mdot_F=None, Optional[float] mdot_O=None)