Source code for ADCS.satellite_hardware.disturbances.disturbance

__all__ = ["Disturbance"]

import numpy as np
from typing import List

from ADCS.covariance import Covariance

[docs] class Disturbance: def __init__( self, estimate_dist: bool = False, estimated_vector_length: int = 0, parameter_std_rate: np.ndarray | float = 0.0, ): r""" Base Class for Disturbance Models. This class defines the **common interface and configuration** for all disturbance models used in the spacecraft dynamics and attitude determination and control system (ADCS) framework. A disturbance represents any **non-commanded force or torque** acting on the spacecraft, such as magnetic dipole torques, aerodynamic drag, gravity gradient effects, or solar radiation pressure. Disturbance Estimation Concept ------------------------------ Disturbances may optionally be treated as **estimable parameters** within a state estimation framework (e.g., EKF, UKF). Let the spacecraft dynamics be written as .. math:: \dot{\mathbf{x}} = \mathbf{f}(\mathbf{x}, \mathbf{u}) + \mathbf{d}, where :math:`\mathbf{d}` represents a disturbance contribution. When disturbance estimation is enabled, the disturbance vector is augmented into the estimator state: .. math:: \mathbf{x}_\text{aug} = \begin{bmatrix} \mathbf{x} \\ \mathbf{d} \end{bmatrix}. The length of the disturbance subvector is specified by ``estimated_vector_length``. Design Intent ------------- This class is intended to be **subclassed**, not instantiated directly. Derived classes implement the physical disturbance model and optionally provide Jacobians and Hessians for use in estimation or optimization. :param estimate_dist: Enables augmentation of the disturbance into the estimator state vector. :type estimate_dist: bool :param estimated_vector_length: Length of the disturbance parameter vector to be estimated. :type estimated_vector_length: int :param parameter_std_rate: Continuous random-walk standard-deviation rate for each estimated parameter. :type parameter_std_rate: float or numpy.ndarray :return: None :rtype: None """ self.estimate_dist = estimate_dist self.estimated_vector_length = estimated_vector_length # Disturbance-parameter-estimation interface consumed by # EstimatedSatellite.match_estimate. # # `active`: a Drag-only deactivation flag; default True so every # disturbance exposes it (a disturbance with estimate_dist=True is # active for estimation by definition). # `std`: posterior estimated-parameter 1-sigma values. # `parameter_std_rate`: configured random-walk diffusion rates. These # must not be overwritten when an estimate is matched. # Estimable disturbances (`estimated_vector_length > 0`) MUST also # implement the `main_param` property (the estimated parameter # vector, read by the estimator and written back by match_estimate); # the base raises a clear error rather than silently mis-estimating. self.active = True self.std = np.zeros(int(estimated_vector_length)) rate = np.asarray(parameter_std_rate, dtype=float) try: self.parameter_std_rate = np.broadcast_to( rate, (int(estimated_vector_length),) ).copy() except ValueError as error: raise ValueError( "parameter_std_rate must be scalar or match estimated_vector_length" ) from error if np.any(~np.isfinite(self.parameter_std_rate)) or np.any( self.parameter_std_rate < 0.0 ): raise ValueError("parameter_std_rate must be finite and non-negative")
[docs] def parameter_process_psd(self, *, form: str = "full") -> Covariance: r"""Return the continuous PSD of estimated disturbance parameters. ``parameter_std_rate`` is configuration, while ``std`` reports the current estimator uncertainty. A disturbance with no estimated parameters naturally returns a zero-dimensional PSD. """ return Covariance( np.diagflat(self.parameter_std_rate * self.parameter_std_rate), form=form, coordinates="disturbance_parameter_rate", )
@property def main_param(self) -> "np.ndarray": raise NotImplementedError( f"{type(self).__name__} declares estimated_vector_length=" f"{getattr(self, 'estimated_vector_length', 0)} but does not " f"implement the `main_param` parameter vector required for " f"disturbance-parameter estimation." ) @main_param.setter def main_param(self, value) -> None: raise NotImplementedError( f"{type(self).__name__} does not implement a settable " f"`main_param`; cannot write back an estimated disturbance " f"parameter." )