Source code for giant.stellar_opnav.estimators.attitude_estimator

from dataclasses import dataclass
from abc import ABCMeta, abstractmethod

import numpy as np

from giant.utilities.options import UserOptions
from giant.utilities.mixin_classes import AttributeEqualityComparison, AttributePrinting, UserOptionConfigured
from giant.rotations import Rotation
from giant._typing import DOUBLE_ARRAY

[docs] @dataclass class AttitudeEstimatorOptions(UserOptions): """ Dataclass for configuring attitude estimator subclasses """ weighted_estimation: bool = False """ A flag specifying whether to use weights in the estimation of the rotation. """
[docs] class AttitudeEstimator(UserOptionConfigured[AttitudeEstimatorOptions], AttitudeEstimatorOptions, AttributePrinting, AttributeEqualityComparison, metaclass=ABCMeta): """ This abstract base class (ABC) serves as a template for creating an attitude estimator that GIANT can use. While it is not required to subclass this ABC in user created estimators, it is encouraged as it will ensure that the appropriate methods and attributes are created to ensure a seamless integration. In particular, the following attributes should be implemented: .. rubric:: Attributes * :attr:`post_fit_covariance` These attributes will be accessed directly for reading or writing by the GIANT :class:`.StarID` and :class:`.StellarClass` classes (that is, they won't be specified during initialization). In addition, the following method should be implemented with no arguments: .. rubric:: Methods * :meth:`estimate` See the :class:`DavenportQMethod` class for an example of how to make a working attitude estimator """ def __init__(self, options_type: type[AttitudeEstimatorOptions], *args, options: AttitudeEstimatorOptions | None = None, **kwargs) -> None: super().__init__(options_type, *args, options=options, **kwargs)
[docs] @abstractmethod def estimate(self, target_frame_directions: DOUBLE_ARRAY, base_frame_directions: DOUBLE_ARRAY, weights: DOUBLE_ARRAY | None) -> Rotation: """ This method solves for the rotation matrix that best aligns the unit vectors in `base_frame_directions` with the unit vectors in `target_frame_directions` and returns the results The solved for rotation should represent the best fit rotation from the base frame to the target frame. This method should also prepare the :attr:`post_fit_covariance` attribute if applicable :param target_frame_directions: unit vectors in the target frame as a 3xn double array :param base_frame_directions: unit vectors in the base frame as a 3xn double array :param weights: the weights for each observation or None """ pass
@property @abstractmethod def post_fit_covariance(self) -> DOUBLE_ARRAY: """ The post-fit covariance from the attitude estimation as a 4x4 array """ return np.zeros((4, 4), dtype=np.float64)
[docs] @staticmethod def compute_residuals(target_frame_directions: DOUBLE_ARRAY, base_frame_directions: DOUBLE_ARRAY, rotation: Rotation) -> float: r""" This method computes the residuals between the aligned unit vectors according to Wahba's problem definitions. The residuals are computed according to .. math:: r_i=\frac{1}{2}\left\|\mathbf{a}_i-\mathbf{T}\mathbf{b}_i\right\|^2 where :math:`r_i` is the residual, :math:`\mathbf{a}_i` is the camera direction unit vector, :math:`\mathbf{b}_i` is the database direction unit vector, and :math:`\mathbf{T}` is the solved for rotation matrix from the base frame to the target frame. The output will be a length n array with each element representing the residual for the correspond unit vector pair. :return: The residuals between the aligned unit vectors """ return ((target_frame_directions - np.matmul(rotation.matrix, base_frame_directions)) ** 2).sum(axis=0) / 2.0
[docs] @staticmethod def attitude_profile_matrix(base_frame_directions: DOUBLE_ARRAY, target_frame_directions: DOUBLE_ARRAY) -> DOUBLE_ARRAY: """ Computes the attitude profile matrix for the provided vector sets """ # compute the attitude profile matrix (sum of the outer products of the vector sets) return np.einsum('ij,jk->jik', base_frame_directions, target_frame_directions.T).sum(axis=0)