DynamicalUtils#

class DynamicalUtils#

Bases: object

Shared geometry of the dynamics models: the spacecraft attitude of the surface-force models and the component frames of the empirical accelerations.

Spacecraft attitude. Force models whose surfaces or boresights are fixed in the spacecraft frame (nPlateSRP, ThermalRadiation, AntennaRadiationPressure) need the spacecraft-fixed to inertial rotation \(C\), so that a spacecraft-fixed vector \(\mathbf{e}_b\) reaches the inertial frame as \(\mathbf{e} = C\mathbf{e}_b\). The supported Spacecraft.attitudeMode values, ATTITUDE_MODES, are:

  • "CK": \(C\) from the loaded C-kernel (ck_dcm()).

  • "inertialFixedAttitude": \(C\) is Spacecraft.fixedInertialAttitude (fixed_dcm()).

  • "nadirPointingSun": the axes follow the heliocentric position \(\mathbf{s}\) and velocity \(\mathbf{w}\) (nadirPointingSun()),

    \[\hat{\mathbf{x}} = \frac{\mathbf{s}}{\lVert\mathbf{s}\rVert},\quad \hat{\mathbf{z}} = \frac{\hat{\mathbf{x}}\times\hat{\mathbf{w}}} {\lVert\hat{\mathbf{x}}\times\hat{\mathbf{w}}\rVert},\quad \hat{\mathbf{y}} = \hat{\mathbf{z}}\times\hat{\mathbf{x}},\quad C = \left[\hat{\mathbf{x}}\;\hat{\mathbf{y}}\;\hat{\mathbf{z}}\right]\]

    so the spacecraft \(-X\) axis points at the Sun. It replaces the deprecated inertial_to_body_DCM_nadirPointingSun(), which returns \(C^T\). Only this mode depends on the state, and on the velocity as well as the position.

The last nadir attitude evaluated with derivatives is kept here, since the position and velocity Jacobians of every surface model are taken at one state.

Acceleration frames. An empirical acceleration (FirstOrderGaussMarkov, PiecewiseFirstOrderGaussMarkov, PiecewisePolynomialAcceleration, FourierAcceleration) can express its components \(\mathbf{a}^{(f)}\) in a chosen frame. They reach the propagation frame as \(\mathbf{a} = T\,\mathbf{a}^{(f)}\) (from_frame()). The frame options are the keys of ACCELERATION_FRAME_AXES:

  • "inertial": components in the propagation frame, \(T = I\).

  • "body_fixed": components in a rotating body-fixed frame, rotated out through SPICE, \(T = R_{bf\to rf}(t)\) (body_fixed_rotation()).

  • "RTN": components in the radial / along-track / cross-track triad of the orbit (also called RSW or RIC), \(T = \left[\hat{\mathbf{R}}\;\hat{\mathbf{T}}\; \hat{\mathbf{N}}\right]\), from rtn(), which is built and differentiated like the nadir attitude. The triad is built from the state, so it adds the position and velocity partials frame_partials() returns. With a relative_body it describes the orbit about that body rather than the propagation origin (relative_state()).

A model resolves its settings once in its constructor with resolve_frame(), and exposes them as the public frame, body_fixed_frame and relative_body properties next to ref_frame and origin_name, which is all the methods here read. A model that also carries a _frame_memo dict gets the body-fixed rotation and the relative-body offset memoized for the epoch of the RHS step in progress: the acceleration and every partial are evaluated at the same epoch, so each SPICE lookup happens once per step rather than per call.

All methods are static or class methods and work on raw NumPy arrays; no instantiation is required:

mode = scb.DynamicalUtils.attitude_mode(spacecraft)
C, D_r, D_v = scb.DynamicalUtils.nadirPointingSun(s, w, derivatives=True)
frame, bff, rel = scb.DynamicalUtils.resolve_frame("rtn")
a = scb.DynamicalUtils.from_frame(model, a_rtn, r, state, epoch, sc)
da_dr, da_dv = scb.DynamicalUtils.frame_partials(
    model, a_rtn, r, state, epoch, sc
)

Methods

attitude_mode(body)

The spacecraft attitude mode, checked against ATTITUDE_MODES.

body_fixed_rotation(model, epoch)

Body-fixed to propagation-frame rotation \(R_{bf\to rf}(t)\).

body_vector_jacobian(vectors_b, D)

Jacobian of spacecraft-fixed vectors mapped to the inertial frame, \(\partial(C\mathbf{e}_b)/\partial\mathbf{q} = \sum_j e_{b,j} D[j]\).

ck_dcm(body, epoch[, frame])

Spacecraft-fixed to inertial rotation from the C-kernel.

cross(a, b)

Cross product of two (3,) vectors, written out by component.

depends_on_velocity(body)

Whether the attitude of body depends on its velocity.

fixed_dcm(body)

Spacecraft-fixed to inertial rotation of an inertialFixedAttitude spacecraft.

frame_matrix(model, position, current_state, ...)

Rotation from the component frame into the propagation frame.

frame_name(frame)

Reduce a Frame or string to the frame name SPICE expects.

frame_partials(model, a_frame, position, ...)

Position and velocity partials of \(T\,\mathbf{a}^{(f)}\) for frame-local components that do not depend on the state.

from_frame(model, values, position, ...)

Rotate frame-local components into the propagation frame.

nadirPointingSun(sun2body, sun2body_vel[, ...])

Spacecraft-fixed to inertial rotation of the nadirPointingSun attitude and, optionally, its derivatives.

relative_state(model, position, ...)

Spacecraft position and velocity about the body the orbit is described about.

resolve_frame([frame, body_fixed_frame, ...])

Validate the frame settings of a frame-expressed acceleration.

rtn(position, velocity[, derivatives])

Radial / along-track / cross-track (RTN) to inertial rotation of an orbit and, optionally, its derivatives.

rtn_triad(model, position, current_state, ...)

RTN triad of the orbit about the model's relative body.

skew(v)

Cross-product matrix \([\mathbf{v}]_\times\), so that \([\mathbf{v}]_\times \mathbf{u} = \mathbf{v} \times \mathbf{u}\).

sun_to_origin_velocity(origin_spice_name, ...)

Sun->origin velocity from SPICE.

velocity_from_state(current_state, body)

Spacecraft velocity relative to the origin, read from the state.

See also

scarabaeus.nPlateSRP

Solar radiation pressure on a plate model.

scarabaeus.ThermalRadiation

Thermal re-radiation recoil.

scarabaeus.AntennaRadiationPressure

Antenna and LIDAR recoil.

scarabaeus.Spacecraft

Carries attitudeMode and fixedInertialAttitude.

scarabaeus.FirstOrderGaussMarkov

Gauss-Markov empirical acceleration.

scarabaeus.FourierAcceleration

Fourier-series empirical acceleration.

classmethod attitude_mode(body) → str#

The spacecraft attitude mode, checked against ATTITUDE_MODES.

Parameters:
bodySpacecraft

The spacecraft.

Returns:
str

body.attitudeMode.

Raises:
InvalidAttitudeModeError

If the mode is not one of ATTITUDE_MODES.

classmethod body_fixed_rotation(model, epoch) → ndarray#

Body-fixed to propagation-frame rotation \(R_{bf\to rf}(t)\).

Parameters:
modelDynamicModel

Frame-expressed acceleration; reads body_fixed_frame and ref_frame.

epochfloat

Epoch of the rotation, in ephemeris seconds (TDB).

Returns:
numpy.ndarray

(3, 3) rotation matrix.

Raises:
MissingEpochError

If no epoch is supplied.

classmethod fixed_dcm(body) → ndarray#

Spacecraft-fixed to inertial rotation of an inertialFixedAttitude spacecraft.

Parameters:
bodySpacecraft

The spacecraft.

Returns:
numpy.ndarray

(3, 3) float copy of body.fixedInertialAttitude.

Raises:
InvalidFixedAttitudeError

If fixedInertialAttitude is not a 3x3 np.ndarray.

classmethod frame_matrix(model, position, current_state: dict, epoch, body)#

Rotation from the component frame into the propagation frame.

Parameters:
modelDynamicModel

Frame-expressed acceleration.

positionnumpy.ndarray

Spacecraft position 3-vector, km.

current_statedict

The propagated state.

epochfloat

Current epoch in ephemeris seconds (TDB).

bodyBody

Body the acceleration is evaluated for.

Returns:
numpy.ndarray or None

(3, 3) rotation, or None for the inertial frame, where the components already live in the propagation frame.

classmethod frame_partials(model, a_frame, position, current_state: dict, epoch, body) → tuple#

Position and velocity partials of \(T\,\mathbf{a}^{(f)}\) for frame-local components that do not depend on the state.

Only the RTN triad is built from the state, so

\[\frac{\partial \mathbf{a}}{\partial \mathbf{r}} = \sum_j a^{(f)}_j \, \frac{\partial \hat{\mathbf{e}}_j}{\partial \mathbf{r}}, \qquad \frac{\partial \mathbf{a}}{\partial \mathbf{v}} = \sum_j a^{(f)}_j \, \frac{\partial \hat{\mathbf{e}}_j}{\partial \mathbf{v}},\]

with \(\hat{\mathbf{e}}_j\) the RTN axes. The inertial and body-fixed rotations depend on the epoch alone, so both blocks are zero there.

Parameters:
modelDynamicModel

Frame-expressed acceleration.

a_framenumpy.ndarray

Frame-local acceleration 3-vector, km/s^2.

positionnumpy.ndarray

Spacecraft position 3-vector, km.

current_statedict

The propagated state.

epochfloat

Current epoch in ephemeris seconds (TDB).

bodyBody

Body the acceleration is evaluated for.

Returns:
tuple of numpy.ndarray

(da_dr, da_dv), each (3, 3).

classmethod from_frame(model, values, position, current_state: dict, epoch, body)#

Rotate frame-local components into the propagation frame.

Applied to the acceleration, \(\mathbf{a} = T\,\mathbf{a}^{(f)}\), and to its partial with respect to a parameter the frame does not depend on, \(\partial\mathbf{a}/\partial\mathbf{p} = T\,\partial\mathbf{a}^{(f)}/\partial\mathbf{p}\).

Parameters:
modelDynamicModel

Frame-expressed acceleration.

valuesnumpy.ndarray

3-vector or (3, n) matrix of frame-local components.

positionnumpy.ndarray

Spacecraft position 3-vector, km.

current_statedict

The propagated state.

epochfloat

Current epoch in ephemeris seconds (TDB).

bodyBody

Body the acceleration is evaluated for.

Returns:
numpy.ndarray

values expressed in the propagation frame.

classmethod nadirPointingSun(sun2body, sun2body_vel, derivatives: bool = False) → tuple#

Spacecraft-fixed to inertial rotation of the nadirPointingSun attitude and, optionally, its derivatives.

With \(C = [\hat{\mathbf{x}}\;\hat{\mathbf{y}}\;\hat{\mathbf{z}}]\) (see the class docstring) the derivatives are returned per axis, D[j] being the Jacobian of axis \(j\). With \(\hat{\mathbf{w}}\) the unit heliocentric velocity and \(\mathbf{z}_0 = \hat{\mathbf{x}}\times\hat{\mathbf{w}}\):

\[\frac{\partial\hat{\mathbf{x}}}{\partial\mathbf{r}} = \frac{I - \hat{\mathbf{x}}\hat{\mathbf{x}}^T} {\lVert\mathbf{s}\rVert},\quad \frac{\partial\hat{\mathbf{z}}}{\partial\mathbf{r}} = -\frac{I - \hat{\mathbf{z}}\hat{\mathbf{z}}^T} {\lVert\mathbf{z}_0\rVert} [\hat{\mathbf{w}}]_\times \frac{\partial\hat{\mathbf{x}}}{\partial\mathbf{r}},\quad \frac{\partial\hat{\mathbf{y}}}{\partial\mathbf{r}} = [\hat{\mathbf{z}}]_\times \frac{\partial\hat{\mathbf{x}}}{\partial\mathbf{r}} - [\hat{\mathbf{x}}]_\times \frac{\partial\hat{\mathbf{z}}}{\partial\mathbf{r}}\]

and, since \(\hat{\mathbf{x}}\) does not depend on the velocity,

\[\frac{\partial\hat{\mathbf{z}}}{\partial\mathbf{v}} = \frac{I - \hat{\mathbf{z}}\hat{\mathbf{z}}^T} {\lVert\mathbf{z}_0\rVert} [\hat{\mathbf{x}}]_\times \frac{I - \hat{\mathbf{w}}\hat{\mathbf{w}}^T} {\lVert\mathbf{w}\rVert},\quad \frac{\partial\hat{\mathbf{y}}}{\partial\mathbf{v}} = -[\hat{\mathbf{x}}]_\times \frac{\partial\hat{\mathbf{z}}}{\partial\mathbf{v}}\]

The Sun->origin terms do not depend on the spacecraft state, so these are the derivatives with respect to the spacecraft position \(\mathbf{r}\) and velocity \(\mathbf{v}\) relative to the origin. A spacecraft-fixed vector then has \(\partial\mathbf{e}/\partial\mathbf{r} = \sum_j e_{b,j} D_r[j]\) (body_vector_jacobian()).

Parameters:
sun2bodynumpy.ndarray or ArrayBox

(3,) Sun->spacecraft position, km.

sun2body_velnumpy.ndarray or ArrayBox

(3,) Sun->spacecraft velocity, km/s.

derivativesbool, optional

Also return the derivatives. Defaults to False.

Returns:
tuple

(C, D_r, D_v): C is the (3, 3) spacecraft-fixed to inertial DCM, traced when the inputs are; D_r and D_v are plain (3, 3, 3) arrays, or None when derivatives is false.

classmethod relative_state(model, position, current_state: dict, epoch, body) → tuple#

Spacecraft position and velocity about the body the orbit is described about.

Identical to the propagated state when model.relative_body is unset. When it is set, the body’s own state is subtracted, so the RTN triad describes the orbit about that body.

The offset comes from the ephemeris and so does not depend on the spacecraft state: the partials of anything built from the relative state are therefore unchanged in form, since \(\partial \mathbf{r}_{rel}/\partial \mathbf{r} = I\).

Parameters:
modelDynamicModel

Frame-expressed acceleration; reads relative_body, ref_frame and origin_name.

positionnumpy.ndarray

Spacecraft position 3-vector from the propagation origin, km.

current_statedict

The propagated state.

epochfloat

Current epoch in ephemeris seconds (TDB).

bodyBody

Body the acceleration is evaluated for.

Returns:
tuple of numpy.ndarray

(r, v) relative to relative_body, in km and km/s.

Raises:
MissingEpochError

If relative_body is set and no epoch is supplied.

MissingVelocityError

If the state carries no velocity for body.

classmethod resolve_frame(frame='inertial', body_fixed_frame=None, relative_body=None) → tuple#

Validate the frame settings of a frame-expressed acceleration.

Parameters:
framestr, optional

"inertial", "body_fixed" or "RTN", in any case. Defaults to "inertial".

body_fixed_framestr or Frame, optional

Frame the components are expressed in, for frame="body_fixed" (for example 'IAU_BENNU').

relative_bodystr or Body, optional

Body the RTN triad is built about, when that is not the propagation origin: a lunar RTN frame while propagating an Earth-centred arc. Defaults to the propagation origin.

Returns:
tuple

(frame, body_fixed_frame, relative_body): the frame key as spelled in ACCELERATION_FRAME_AXES, and the two names, None where unset.

Raises:
InvalidFrameError

If frame is not a key of ACCELERATION_FRAME_AXES.

MissingBodyFixedFrameError

If frame="body_fixed" is requested without body_fixed_frame.

classmethod rtn(position, velocity, derivatives: bool = False) → tuple#

Radial / along-track / cross-track (RTN) to inertial rotation of an orbit and, optionally, its derivatives.

With the angular momentum \(\mathbf{h} = \mathbf{r}\times\mathbf{v}\),

\[\hat{\mathbf{R}} = \frac{\mathbf{r}}{\lVert\mathbf{r}\rVert},\quad \hat{\mathbf{N}} = \frac{\mathbf{h}}{\lVert\mathbf{h}\rVert},\quad \hat{\mathbf{T}} = \hat{\mathbf{N}}\times\hat{\mathbf{R}},\quad C = \left[\hat{\mathbf{R}}\;\hat{\mathbf{T}}\;\hat{\mathbf{N}}\right]\]

(the triad is also called RSW or RIC). The derivatives are returned per axis, as for nadirPointingSun():

\[\frac{\partial\hat{\mathbf{R}}}{\partial\mathbf{r}} = \frac{I - \hat{\mathbf{R}}\hat{\mathbf{R}}^T} {\lVert\mathbf{r}\rVert},\quad \frac{\partial\hat{\mathbf{N}}}{\partial\mathbf{r}} = -P_h[\mathbf{v}]_\times,\quad \frac{\partial\hat{\mathbf{N}}}{\partial\mathbf{v}} = P_h[\mathbf{r}]_\times,\quad P_h = \frac{I - \hat{\mathbf{N}}\hat{\mathbf{N}}^T}{\lVert\mathbf{h}\rVert}\]

and \(d\hat{\mathbf{T}} = -[\hat{\mathbf{R}}]_\times d\hat{\mathbf{N}} + [\hat{\mathbf{N}}]_\times d\hat{\mathbf{R}}\). The radial axis does not depend on the velocity, so D_v[0] is zero.

Parameters:
positionnumpy.ndarray or ArrayBox

(3,) position about the body the orbit is described about, km.

velocitynumpy.ndarray or ArrayBox

(3,) velocity about the same body, km/s.

derivativesbool, optional

Also return the derivatives. Defaults to False.

Returns:
tuple

(C, D_r, D_v): C is the (3, 3) RTN to inertial DCM, traced when the inputs are; D_r and D_v are plain (3, 3, 3) arrays, or None when derivatives is false.

Raises:
DegenerateGeometryError

If the radius or the angular momentum vanishes.

classmethod rtn_triad(model, position, current_state: dict, epoch, body, derivatives: bool = False) → tuple#

RTN triad of the orbit about the model’s relative body.

relative_state() followed by rtn().

Parameters:
modelDynamicModel

Frame-expressed acceleration.

positionnumpy.ndarray

Spacecraft position 3-vector, km.

current_statedict

The propagated state.

epochfloat

Current epoch in ephemeris seconds (TDB).

bodyBody

Body the acceleration is evaluated for.

derivativesbool, optional

Also return the position and velocity derivatives of the axes.

Returns:
tuple

(T, D_r, D_v): the (3, 3) matrix \([\hat{\mathbf{R}}\;\hat{\mathbf{T}}\;\hat{\mathbf{N}}]\) and, with derivatives, the (3, 3, 3) stacks whose [j] is the Jacobian of axis j (None otherwise).

Raises:
DegenerateGeometryError

If the radius or the angular momentum vanishes.

classmethod velocity_from_state(current_state: dict, body)#

Spacecraft velocity relative to the origin, read from the state.

Parameters:
current_statedict

The propagated state.

bodySpacecraft

The spacecraft.

Returns:
numpy.ndarray or ArrayBox

(3,) velocity in km/s, still traced when the state is.

Raises:
MissingVelocityError

If the state holds neither ("velocity", spice_id) nor "velocity".

static body_vector_jacobian(vectors_b: ndarray, D: ndarray) → ndarray#

Jacobian of spacecraft-fixed vectors mapped to the inertial frame, \(\partial(C\mathbf{e}_b)/\partial\mathbf{q} = \sum_j e_{b,j} D[j]\).

Parameters:
vectors_bnumpy.ndarray

(3,) vector, or (..., 3) stack of vectors, in the spacecraft frame.

Dnumpy.ndarray

(3, 3, 3) axis derivatives from nadirPointingSun() or rtn().

Returns:
numpy.ndarray

(3, 3) Jacobian, or (..., 3, 3) for a stack.

static ck_dcm(body, epoch: float, frame: Frame = None) → ndarray#

Spacecraft-fixed to inertial rotation from the C-kernel.

Parameters:
bodySpacecraft

Spacecraft whose C-kernel attitude is queried.

epochfloat

TDB epoch (seconds past J2000).

frameFrame, optional

Inertial frame. Anything that is not a Frame, including None, means J2000.

Returns:
numpy.ndarray

(3, 3) spacecraft-fixed to inertial DCM.

static cross(a, b)#

Cross product of two (3,) vectors, written out by component.

np.cross spends most of its time on axis handling for inputs this small; this is several times faster and stays traceable by autograd.

Parameters:
a, bnumpy.ndarray or ArrayBox

(3,) vectors.

Returns:
numpy.ndarray or ArrayBox

(3,) vector \(\mathbf{a}\times\mathbf{b}\).

static depends_on_velocity(body) → bool#

Whether the attitude of body depends on its velocity.

True only for "nadirPointingSun", whose Y axis follows the heliocentric velocity. The mode is not validated here.

Parameters:
bodySpacecraft

The spacecraft.

Returns:
bool
static frame_name(frame)#

Reduce a Frame or string to the frame name SPICE expects.

Parameters:
frameFrame or str or None

Frame to resolve.

Returns:
str or None

The frame name, or None when nothing was supplied.

static skew(v: ndarray) → ndarray#

Cross-product matrix \([\mathbf{v}]_\times\), so that \([\mathbf{v}]_\times \mathbf{u} = \mathbf{v} \times \mathbf{u}\).

Parameters:
vnumpy.ndarray

(3,) vector.

Returns:
numpy.ndarray

(3, 3) skew-symmetric matrix.

static sun_to_origin_velocity(origin_spice_name: str, frame, epoch: float) → ndarray#

Sun->origin velocity from SPICE.

Parameters:
origin_spice_namestr

SPICE name of the propagation origin.

frameFrame or str

Inertial frame of the result.

epochfloat

TDB epoch (seconds past J2000).

Returns:
numpy.ndarray

(3,) Sun->origin velocity in km/s.

ACCELERATION_FRAME_AXES = {'RTN': ('R', 'T', 'N'), 'body_fixed': ('x', 'y', 'z'), 'inertial': ('x', 'y', 'z')}#

Frames an empirical acceleration can be expressed in, with the axis labels of each. The keys are the frame options resolve_frame() accepts; the labels name the components in reports and plot legends. The inertial and body-fixed frames are Cartesian triads, the RTN one is radial / along-track / cross-track.

ATTITUDE_MODES = ('CK', 'nadirPointingSun', 'inertialFixedAttitude')#

Spacecraft.attitudeMode values the surface-force models support. Each is turned into the spacecraft-fixed to inertial rotation: from the loaded C-kernel, from the heliocentric position and velocity, or from Spacecraft.fixedInertialAttitude.

NADIR_ATTITUDE_MODE = 'nadirPointingSun'#

The entry of ATTITUDE_MODES whose rotation depends on the spacecraft state, on its velocity as well as its position.

exception DegenerateGeometryError#

Bases: ValueError

The RTN triad is undefined for the given state.

The along-track and cross-track directions follow the orbital angular momentum \(\mathbf{r}\times\mathbf{v}\), which vanishes for a zero radius or a purely radial trajectory.

exception InvalidAttitudeModeError#

Bases: ValueError

The spacecraft attitude mode is not supported.

Surface normals and boresights are fixed in the spacecraft frame, so a spacecraft-to-inertial rotation is needed. It is available for the "CK", "nadirPointingSun" and "inertialFixedAttitude" modes only.

exception InvalidFixedAttitudeError#

Bases: ValueError

fixedInertialAttitude is not a 3x3 array.

An inertialFixedAttitude spacecraft must carry the spacecraft-fixed to inertial rotation as a 3x3 np.ndarray.

exception InvalidFrameError#

Bases: ValueError

frame is not one of the supported frames.

The options are the keys of DynamicalUtils.ACCELERATION_FRAME_AXES, matched without regard to case.

exception MissingBodyFixedFrameError#

Bases: ValueError

frame='body_fixed' was requested without a frame name.

The body-fixed frame the components are expressed in (for example 'IAU_BENNU') is what SPICE rotates them out of.

exception MissingEpochError#

Bases: ValueError

An ephemeris lookup was needed but no epoch was supplied.

The body-fixed rotation and the offset of a relative_body are both evaluated at the epoch of the acceleration.

exception MissingVelocityError#

Bases: KeyError

The spacecraft velocity is not in the propagated state.

The nadirPointingSun attitude uses the heliocentric velocity to fix the spacecraft Y axis, and the RTN triad uses the orbital angular momentum, so the state must hold ("velocity", spice_id) or a plain "velocity" entry.