State Transition Matrix Across Matching Points

Sometimes we have a trajectory which was set up for a mission design or manoeuvre optimisation which uses multiple shooting and matching points.

For these trajectories, we also need to sometimes perform other analyses, e.g. Covariance or Guidance, for which we need the state transition matrix across matching points.

He we show how this is done.

First perform all necessary imports

In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from godot.core import tempo, util, constants
from godot.core import autodif as ad
from godot.core.autodif import bridge as br
from godot.model import interface, common, geometry
from godot import cosmos

# avoid verbose output
util.suppressLogger()

next load the universe configuration:

In [2]:
uni_config = cosmos.util.load_yaml('universe.yml')
uni = cosmos.Universe(uni_config)

Now load the trajectory configuration and create the trajectory object using the universe object:

This trajectory has a matching point which splits the trajectory into two parts.

Note that when we create the trajectory we set the provideState flag in the constructor to True. This is so that the gradients are resolved using trajectory, rather than the propagator.

In [3]:
tra_config = cosmos.util.load_yaml('trajectory.yml')
tra = cosmos.Trajectory(uni, tra_config, True) # <<< True here means, state leaves refer to trajectory, not propagator
tra.compute(True)

The following function is taken from MIDAS, and performs the linear mapping from Cartesian virtual states at the user provided epoch to a model_value. The state_model is the trajectory, the model_value is a `ad.Vector which is mapped to.

In [4]:
from typing import Iterable
def get_linear_mapping(
    state_model: interface.State,
    control_epoch: tempo.Epoch,
    target: interface.VectorTimeEvaluable,
    target_epoch: tempo.Epoch,
    parameters: Iterable[str],
) -> np.ndarray:
    """Computes the linear mapping matrix from a virtual cartesian state at control epoch to a target model value

    Parameters
    ----------
    state_model : interface.State
        Mapping state model
    control_epoch : tempo.Epoch
        Control epoch of the mapping
    target : VectorTimeEvaluable
        Target model VectorTimeEvaluable
    target_epoch : tempo.Epoch
        Target epoch of the mapping
    parameters : Iterable[str]
        All other parameters than state to include in the mapping generation        
    Returns
    -------
    np.ndarray
        Mapping matrix from control_epoch cartesian state to target_epoch model

    Raises
    ------
    RuntimeError
        If state mapping model does not contain virtual_state_leaf
    """
    # evaluate the target at the target epoch
    target_value = target.eval(tempo.XEpoch(target_epoch))
    state_dim = np.min(np.shape(state_model.transitionMatrix(control_epoch, control_epoch).state))
    # create a virtual gradient identity (cartesian)
    virtual_state_leaf = ad.BasicLeaf(state_dim)
    virtual_gradient = ad.VectorGradient(virtual_state_leaf, np.identity(state_dim))
    # create a resolve target
    target = common.ResolveTargetXVector(control_epoch, virtual_gradient)
    data = {state_model: target}
    # resolve to the target epoch and to the virtual cartesian state parameters
    grad = common.resolve(target_value, data).gradient()
    # get the matrix, raising an error if the resolve was not done (target not dependent on state at control epoch)
    if grad.contains(virtual_state_leaf):
        mat = grad[virtual_state_leaf]
    else:
        raise RuntimeError("State mapping model does not have access to required gradients")
    # add the parameter parts
    for p in parameters:
        if p not in grad:
            mat = np.column_stack((mat, np.zeros((len(target_value), 1))))
        else:
            mat = np.column_stack((mat, grad[p]))
    return mat

To use it, we first define and evaluate the target TimeEvaluable at the target epoch:

In [5]:
target_epoch = tempo.Epoch('10150 TDB')
target = geometry.Vector6(uni.frames, "Earth", "SC_center", "ICRF")

Calling the linear mapping function:

Note the states vector are not used, other than for their dimension. If present, they simply create virtual states with the same dimension and identity partials.

In [6]:
control_epoch = tempo.Epoch('9750 TDB')
map_control_to_target = get_linear_mapping(tra, control_epoch, target, target_epoch, [])
print(map_control_to_target)
[[ 2.72595561e+03  1.21503717e+04  6.79859099e+03  5.23424270e+07
  -2.72336397e+07 -1.09940520e+07  8.37944638e+04  0.00000000e+00]
 [ 3.10346707e+04  8.11364671e+03  7.27068049e+03  9.29779848e+07
   6.78879776e+07  3.60666836e+07  2.89641126e+04  0.00000000e+00]
 [ 1.34703332e+04  3.14612584e+03  3.34459467e+03  3.96069730e+07
   3.07633888e+07  1.48354886e+07  1.11082630e+04  0.00000000e+00]
 [ 1.10440222e+01  4.23278170e+00  3.35964336e+00  3.83692106e+04
   2.02789112e+04  1.10122672e+04  2.00111425e+01  0.00000000e+00]
 [ 1.30393619e-01  3.33528194e+00  1.85179801e+00  1.32184797e+04
  -9.27313288e+03 -4.07823843e+03  2.37072819e+01  0.00000000e+00]
 [-1.07075947e-02  1.49708618e+00  6.95472008e-01  5.56404233e+03
  -4.43486598e+03 -1.50906422e+03  1.03072207e+01  0.00000000e+00]]

Now, we repeat, this time constructing the trajectory with the False flag. This demonstrates the error which will be raised

In [7]:
uni_config = cosmos.util.load_yaml('universe.yml')
uni = cosmos.Universe(uni_config)
tra_config = cosmos.util.load_yaml('trajectory.yml')
tra2 = cosmos.Trajectory(uni, tra_config, False) # <<< False here means, state leaves refer to propagator, not trajectory
tra2.compute(True)
control_epoch2 = tempo.Epoch('9750 TDB')
target_epoch2 = tempo.Epoch('10150 TDB')
target2 = geometry.Vector6(uni.frames, "Earth", "SC_center", "ICRF")
# This will result in a failure because the propagator will not be able to 
try:
    map_control_to_target = get_linear_mapping(tra2, control_epoch2, target2, target_epoch2, [])
except Exception as e:
    print(f"Exception raised: {e}")
Exception raised: State mapping model does not have access to required gradients