ADCS.estimators.attitude_estimators.attitude_EKF module¶
1. Construct and validate the physical estimate
The constructor delegates to the shared estimator. It checks that the input
is an EstimatorState, that its covariance has full
quaternion dimension, and that all augmented blocks are empty. The initial
quaternion is normalized and the covariance is transformed with the
corresponding normalization Jacobian.
class EKF(AttitudeEstimator):
def __init__(self, satellite, state, *, dt, ...):
super().__init__(
satellite, state, dt=dt,
covariance_coordinates="full",
correction_mode="full_quaternion",
...,
)
2. Propagate the nominal state
Given control \(\mathbf{u}_k\), orbital states at the beginning and end of the step, and \(\Delta t\), the deterministic model produces
In code, propagate_state() delegates
the physical integration to satellite.noiseless_rk4. For an
EstimatorState, it propagates w, q, and h
while copying the empty parameter blocks and uncertainty containers.
predicted = propagate_state(
prior, self.satellite, control, step,
orbital_state_start, orbital_state_end,
midpoint_orbital_state=midpoint_orbital_state,
)
3. Linearize the process model and discretize process noise
The continuous local error model is
The shared process-noise code obtains \(\mathbf{F}_k\) from the
spacecraft dynamics Jacobian and assembles \(\mathbf{Q}_{c,k}\) from
configured hardware noise plus unmodeled_dynamics_psd. Van Loan
discretization returns the transition matrix \(\mathbf{\Phi}_k\) and
discrete noise \(\mathbf{Q}_{d,k}^{\mathrm{VL}}\). Held actuator-command
noise adds
A caller may also provide an independent discrete covariance through
EstimatorState.int_cov. Prediction adds it to
\(\mathbf{Q}_{d,k}^{\mathrm{model}}\).
transition, process_noise = discretize_process_noise(
prior, self.satellite, control, orbital_state_start, step,
final_state=predicted,
quaternion_mode="full_quaternion",
quaternion_order="right",
unmodeled_dynamics_psd=self.unmodeled_dynamics_psd,
)
4. Predict the covariance
The Covariance operation
predicted_linear() applies
The result is assigned to the predicted
EstimatorState, and the discrete process noise is kept
alongside it for diagnostics and downstream consumers.
predicted_covariance = prior.covariance.predicted_linear(
transition, process_noise
)
predicted.covariance = predicted_covariance
predicted.process_noise = Covariance(
process_noise,
form=prior.process_noise.form,
coordinates=prior.process_noise.coordinates,
)
5. Select measurements and construct the innovation
MeasurementStack owns the
canonical order, availability checks, sensor models, and wheel measurements.
Raw measurements are filtered for finite values, enabled sources, and
sampling schedules. The predicted measurement is
Additive measurements use \(\mathbf{r}=\mathbf{z}-\hat{\mathbf{z}}\). A quaternion measurement uses a right relative quaternion and a minimal three-coordinate map:
Thus the residual can be three-dimensional even though the EKF covariance uses four stored quaternion coordinates.
candidate = stack.active_mask(measurements, enabled=enabled, ...)
predicted_measurements = stack.predict(
self._state, orbital_state, active_mask=candidate
)
active = stack.active_mask(
measurements, enabled=candidate,
predicted=predicted_measurements, ...
)
residual = stack.residual(
measurements, predicted_measurements, active,
quaternion_mode=self.measurement_quaternion_mode,
)
6. Build \(\mathbf{H}\) and \(\mathbf{R}\)
The active measurement Jacobian is expressed with respect to the full additive state coordinates:
For quaternion measurements, the stack converts the four-coefficient
measurement covariance into the same three-coordinate residual chart before
assembling \(\mathbf{R}_k\). Covariance keeps
this measurement covariance in a validated block-diagonal object.
measurement_jacobian = stack.jacobian(
self._state, orbital_state, active,
quaternion_mode=self.measurement_quaternion_mode,
coordinates="full",
)
measurement_noise = stack.covariance(
self._state, active,
quaternion_mode=self.measurement_quaternion_mode,
)
7. Compute the innovation covariance, gain, and correction
For \(m\) active residual elements,
The correction has full state dimension \(7+n_h\); its quaternion part has four additive components. The covariance uses the Joseph form:
gain, joseph_covariance = prior.covariance.updated_linear(
measurement_jacobian, measurement_noise, joseph=True
)
correction = gain @ residual
8. Retract the state, normalize the quaternion, and reset the covariance
The additive correction is applied by
plus() in the full_quaternion chart:
Because normalization changes the local linearization point, the covariance is transported with the reset Jacobian \(\mathbf{J}_{reset}\):
The reset is identity for angular velocity and wheel momentum; only the
quaternion block needs the chart-aware calculation owned by
State.
reset_jacobian = prior.retraction_jacobian(
correction, quaternion_mode="full_quaternion", quaternion_order="right"
)
corrected_covariance = prior.transport_covariance(
joseph_covariance, correction,
quaternion_mode="full_quaternion", quaternion_order="right"
)
corrected = prior.plus(
correction, quaternion_mode="full_quaternion", quaternion_order="right"
)
corrected.covariance = corrected_covariance
- class ADCS.estimators.attitude_estimators.attitude_EKF.EKF(satellite, state, *, dt, unmodeled_dynamics_psd=0.0, measurement_quaternion_mode='quaternion_vector')[source]¶
Bases:
AttitudeEstimatorNaive additive EKF with a normalized four-element quaternion block.
- Parameters:
satellite (Any)
state (EstimatorState)
dt (float)
unmodeled_dynamics_psd (Any)
measurement_quaternion_mode (str)