[docs]classFaultType(Enum):"""Actuator and sensor fault modes for FDI classification."""STUCK_ACTUATOR=auto()OPEN_CIRCUIT_ACTUATOR=auto()SENSOR_DROPOUT=auto()SENSOR_DRIFT=auto()SENSOR_NOISE_INCREASE=auto()
[docs]@dataclassclassFaultReport:"""Detected fault: which component, fault mode, confidence, and detection time."""component_index:intis_sensor:boolfault_type:FaultTypeconfidence:floattime_detected:float
[docs]classFDIMonitor:"""Fault Detection and Isolation based on innovation monitoring."""def__init__(self,n_sensors:int,n_actuators:int,threshold_sigma:float=3.0,n_alert:int=5):self.n_sensors=n_sensorsself.n_actuators=n_actuatorsself.threshold_sigma=threshold_sigmaself.n_alert=n_alertself.innovation_history=np.zeros((n_alert,n_sensors))self.innovation_idx=0self.S_diag=np.ones(n_sensors)self.detected_faults:list[FaultReport]=[]self.faulted_sensors:set[int]=set()
[docs]defupdate(self,y_measured:np.ndarray,y_predicted:np.ndarray,t:float)->list[FaultReport]:"""Compare measurements to predictions; flag sensors exceeding threshold_sigma for n_alert consecutive steps."""nu=y_measured-y_predictedself.innovation_history[self.innovation_idx]=nuself.innovation_idx=(self.innovation_idx+1)%self.n_alertnew_faults=[]foriinrange(self.n_sensors):ifiinself.faulted_sensors:continuehist=self.innovation_history[:,i]sigma=np.sqrt(self.S_diag[i])ifnp.all(np.abs(hist)>self.threshold_sigma*sigma):ifnp.isnan(y_measured[i])orabs(y_measured[i])<1e-6:ftype=FaultType.SENSOR_DROPOUTelse:ftype=FaultType.SENSOR_DRIFTreport=FaultReport(component_index=i,is_sensor=True,fault_type=ftype,confidence=1.0,time_detected=t,)new_faults.append(report)self.detected_faults.append(report)self.faulted_sensors.add(i)returnnew_faults
[docs]classReconfigurableController:"""Adjusts control allocation based on detected faults."""def__init__(self,base_controller:Any,jacobian:np.ndarray,n_coils:int,n_sensors:int):self.base_controller=base_controllerself.nominal_jacobian=jacobian.copy()self.current_jacobian=jacobian.copy()self.n_coils=n_coilsself.n_sensors=n_sensorsself.faulted_coils:set[int]=set()self.stuck_values:dict[int,float]={}self.W=np.eye(jacobian.shape[0])self.lambda_reg=1e-6self.K=self._compute_gain()def_compute_gain(self)->np.ndarray:"""Tikhonov pseudoinverse with faulted-coil rows zeroed."""J=self.current_jacobianJ_T_W=J.T@self.WH=J_T_W@J+self.lambda_reg*np.eye(self.n_coils)try:H_inv=np.linalg.inv(H)K=H_inv@J_T_Wexceptnp.linalg.LinAlgError:K=np.zeros_like(J.T)foriinself.faulted_coils:K[i,:]=0.0returnnp.asarray(K)
[docs]defhandle_actuator_fault(self,coil_index:int,fault_type:FaultType,stuck_val:float=0.0)->None:"""Zero out the faulted coil column in J and recompute gain."""ifcoil_indexinself.faulted_coils:returnself.faulted_coils.add(coil_index)iffault_type==FaultType.STUCK_ACTUATOR:self.stuck_values[coil_index]=stuck_valself.current_jacobian[:,coil_index]=0.0self.K=self._compute_gain()
[docs]defhandle_sensor_fault(self,sensor_index:int,fault_type:FaultType)->None:"""Placeholder for sensor fault accommodation (e.g. observer reconfiguration)."""pass
[docs]defstep(self,error:np.ndarray,dt:float)->np.ndarray:"""Compute coil current corrections, compensating stuck-actuator offsets."""adjusted_error=error.copy()forc_idx,valinself.stuck_values.items():adjusted_error-=self.nominal_jacobian[:,c_idx]*valdelta_u=self.K@adjusted_errorforc_idxinself.faulted_coils:delta_u[c_idx]=0.0returnnp.asarray(delta_u)
[docs]defcontrollability_check(self)->bool:"""True if enough healthy coils remain for minimum-rank controllability."""iflen(self.faulted_coils)>self.n_coils//2:returnFalseJ=self.current_jacobianrank=np.linalg.matrix_rank(J)min_required_rank=2returnbool(rank>=min_required_rank)
[docs]defgraceful_shutdown(self)->np.ndarray:"""Return zero-current command vector for safe ramp-down."""returnnp.zeros(self.n_coils)
[docs]classFaultInjector:"""Injects sensor faults (dropout or drift) at a specified time for testing."""def__init__(self,fault_time:float,component_index:int,fault_type:FaultType,severity:float=1.0):self.fault_time=fault_timeself.component_index=component_indexself.fault_type=fault_typeself.severity=severity