Source code for opengnc.classical_control.momentum_dumping

"""
Reaction wheel momentum desaturation using magnetic torque (Cross-Product Law).
"""


import numpy as np


[docs] class CrossProductLaw: r""" Reaction wheel momentum desaturation using the Cross-Product Law. This controller calculates a magnetic dipole moment 'm' such that the resulting magnetic torque $T = m \times B$ opposes the component of the angular momentum error perpendicular to the magnetic field. Control law: $m = k \frac{H_{err} \times B}{|B|^2}$ Parameters ---------- gain : float Feedback gain $k$ ($k > 0$). Units: $[s^{-1}]$. max_dipole : float, optional Maximum magnetic dipole moment allowed (saturation) [Am^2]. """ def __init__(self, gain: float, max_dipole: float | None = None) -> None: """Initialize the momentum dumping controller.""" self.gain = gain self.max_dipole = max_dipole
[docs] def calculate_control( self, h_error: np.ndarray | list[float], b_field: np.ndarray | list[float] ) -> np.ndarray: """ Calculate the required magnetic dipole moment. Parameters ---------- h_error : np.ndarray or list The angular momentum vector to be dumped (3,). Units: [Nms]. b_field : np.ndarray or list The local magnetic field vector in Body frame (3,). Units: [T]. Returns ------- np.ndarray Magnetic dipole moment vector $m$ [Am^2] (3,). """ h_vec = np.asarray(h_error, dtype=float) b_vec = np.asarray(b_field, dtype=float) b_sq = np.dot(b_vec, b_vec) # Singular handling for weak/zero magnetic fields if b_sq < 1e-18: return np.zeros(3, dtype=float) dipole_moment = (self.gain / b_sq) * np.cross(h_vec, b_vec) # Apply dipole saturation if self.max_dipole is not None: norm_m = np.linalg.norm(dipole_moment) if norm_m > self.max_dipole: dipole_moment *= self.max_dipole / norm_m return np.asarray(dipole_moment)