Source code for irfpy.pep.pep_attitude

''' A PEP attitude module.

.. note::

    Can be generalized for the future use.

The JUICE attitude is not very clear, however, I have implemented
more generally as a :class:`NadirLookingSc` class.
This represents the nadir looking spacecraft and its fixed coordinate
system to any Cartesian reference frame.
'''
import logging

logging.basicConfig()
_logger = logging.getLogger('pep_attitude')
_logger.setLevel(logging.DEBUG)

import numpy as np

unitvector = lambda s: s / np.sqrt((s ** 2).sum())
''' Return unit vector'''


[docs]class NadirLookingSc: ''' A conversion between the frames of nadir-looking spacecraft and a user-selected reference frame. .. note:: This is used for the preparation during PEP proposal (2011). It is no longer usable, because of clearer definition of the spacecraft attitude. Use :class:`JuiceNominal` or other related classes. The use case is as follows. >>> sc = NadirLookingSc() The planetary body should be first placed at the origin, i.e. (0, 0, 0). Set the position and velocity of the spacecraft at a time of interest. The coordinate system of the position and velocity is arbitrary, but should be consistent (for example, you may use JSE, J2000, IAU_GANYMEDE or whatever). The used coordinate system is considered as a *reference* corrdinate system from now on. Sample: JUICE is at the position of (2500, 0, 0) with velocity (0, 0, -1.5) in the IAU_GANYMEDE frame, you can set them as >>> sc.set_posvel([2500., 0, 0], [0, 0, -1.5]) Then, the object ``sc`` can convert the frames between the *nadir-looking-spacecraft* (NSC) frame and the *reference* frame. Here, the *nadir-looking-spacecraft* (NSC) coordinate system is defined as follows. - Center: The spacecraft - *z*-axis: Nadir direction, i.e. Spacecraft to the origin of the body. - *y*-axis: *y*-*z* plane should be the same as position-velocity vector plane. Angle between plus *y* and velocity vector should be less than 90 degrees. This means that the *y* component of velocity in the NSC coordinate should be positive. - *x*-axis: Completes the right hand system. Now, you can convert in between by the methods :meth:`convert_to_nsc` and :meth:`convert_to_ref`. The class may be used for - Ganymede orbiting phase - Jupiter pointing - Sun pointing - Earth pointing ''' def __init__(self): ''' ''' pass
[docs] def set_posvel(self, pos, vel): ''' Set the spacecraft position and velocity. The following specifies the spacecraft position and velocity in arbitrary coordinate system. >>> sc = NadirLookingSc() >>> sc.set_posvel([2500., 0, 0], [0, 0, -1.5]) ''' pos3 = np.array(pos) if pos3.shape != (3,): raise ValueError('Position should have (3,) shape') vel3 = np.array(vel) if vel3.shape != (3,): raise ValueError('Velocity should have (3,) shape') self.pos = pos3 self.vel = vel3
[docs] def convert_to_ref(self, nscvecs): ''' Convert nsc vectors to ref. :param nscvecs: (3, X) shaped array. >>> sc = NadirLookingSc() >>> sc.set_posvel([2500., 1800, -3000], [1., 3, 0]) The z-axis in NSC frame should be the antiparallel direction of the spacecraft position in the reference frame. >>> zref = sc.convert_to_ref([0, 0, 1]) >>> print(zref) [-0.58139535 -0.41860465 0.69767442] If you want to convert 5 vectors as (0, 0, 1), (1, 2, 3), (2, 3, 4), (3, 4, 5), and (4, 5, 6), you can instance np.array object as follows. >>> nscvecs = np.array([[0, 1, 2, 3, 4], ... [0, 2, 3, 4, 5], ... [1, 3, 4, 5, 6]]) >>> print(nscvecs.shape) (3, 5) Then convert. >>> refvecs = sc.convert_to_ref(nscvecs) >>> refvecs.shape (3, 5) The converted values can be referred like >>> print(refvecs[:, 0]) # Corresponding to (0, 0, 1) [-0.58139535 -0.41860465 0.69767442] ''' matrix = self.get_matrix_nsc2ref() return matrix.dot(nscvecs)
[docs] def convert_to_nsc(self, refvecs): matrix = self.get_matrix_ref2nsc() return matrix.dot(refvecs)
[docs] def get_matrix_nsc2ref(self): ''' Return the 3x3 np.array converting NSC frame to the reference frame >>> sc = NadirLookingSc() >>> sc.set_posvel([2500., 1800, -3000], [1., 3, 0]) The z-axis of the NSC frame should be parallel to (-2500, -1800, +3000), meaning (-0.5814, -0.4186, 6977) >>> z = np.array([0, 0, 1]) >>> z_ref = sc.get_matrix_nsc2ref().dot(z) >>> print('%.4f %.4f %.4f' % (z_ref[0], z_ref[1], z_ref[2])) -0.5814 -0.4186 0.6977 ''' return self.get_matrix_ref2nsc().T
[docs] def get_matrix_ref2nsc(self): ''' Return the 3x3 np.array converting the reference frame to NSC frame ''' nadir = -np.array(self.pos).copy() nadirlen = np.sqrt((nadir * nadir).sum()) z = nadir / nadirlen # outer(y, z) = outer(v, z) is x. vel = np.array(self.vel).copy() x = np.cross(vel, z) xlen = np.sqrt((x * x).sum()) x = x / xlen y = np.cross(z, x) return np.array([x, y, z])
[docs]class NadirLookingYawSteeringSc: ''' Nadir looking spacecraft with yaw steering support. .. note:: This is used for the preparation during PEP proposal (2011). It is no longer usable, because of clearer definition of the spacecraft attitude. Use :class:`JuiceMoonYaw` class. You can set yaw steering angle in :meth:`set_posvel` method. The yaw steering is considered by the JUICE project (:ref:`issue_yaw_steering`). The reference frame is referred by the name "ysc". Sample follows. Consider the spacecraft at the position of (100, 100, 0) in a certain coordinate system (reference frame) with the velocity of (0, 50, 50). Then, the nadir point spacecraft NSC and its yaw steering enabled YSC can be instance as follows >>> nsc = NadirLookingSc() >>> nsc.set_posvel([100, 100, 0], [0, 50, 50]) >>> ysc = NadirLookingYawSteeringSc() >>> ysc.set_posvel([100, 100, 0], [0, 50, 50], 90) For yaw-steering YSC, the yaw steering angle is here defined as 90 degrees. The z-axis in the NSC and YSC should be the same in the reference frame. >>> zn = nsc.convert_to_ref([0, 0, 1]) >>> zy = ysc.convert_to_ref([0, 0, 1]) >>> print((zn == zy).all()) True Indeed, the component-wise, >>> print(zy) [-0.70710678 -0.70710678 0. ] However, the x and y axis should be different. For NSC, the x axis is (0.707, 0, 0.707) and the y axis is (-0.577, 0.577, 0.577). >>> xn = nsc.convert_to_ref([1, 0, 0]) >>> print(xn) [ 0.57735027 -0.57735027 0.57735027] >>> yn = nsc.convert_to_ref([0, 1, 0]) >>> print(yn) [-0.40824829 0.40824829 0.81649658] On the other hand, for the YSC frame, the 90 degrees yaw steering is considered, so that the +x(YSC) axis is the same as +y(NSC) and +y(YSC) is the same as -x(NSC). >>> xy = ysc.convert_to_ref([1, 0, 0]) >>> print(np.linalg.norm(xy - yn) < 1e-10) True >>> print(xy) [-0.40824829 0.40824829 0.81649658] >>> yy = ysc.convert_to_ref([0, 1, 0]) >>> print(np.linalg.norm(yy + xn) < 1e-10) True >>> print(yy) [-0.57735027 0.57735027 -0.57735027] ''' def __init__(self): pass
[docs] def set_posvel(self, pos, vel, yawangle_deg): ''' Set the position and velocity with yawangle_deg. The yaw angle is defined referring to *z* axis, nadir direction. This means that the positive yaw is as the spacecraft rotates toward the right seeing from space. It is equivalent to the *x* axis is toward *y* axis. ''' pos3 = np.array(pos) if pos3.shape != (3,): raise ValueError('Position should have (3,) shape') vel3 = np.array(vel) if vel3.shape != (3,): raise ValueError('Velocity should have (3,) shape') self.pos = pos3 self.vel = vel3 self.yawrad = np.deg2rad(yawangle_deg)
[docs] def convert_to_ref(self, yscvecs): ''' Convert ysc vectors to ref. :param yscvecs: (3, X) shaped array. >>> sc = NadirLookingYawSteeringSc() >>> sc.set_posvel([2500., 1800, -3000], [1., 3, 0], 30.) ''' matrix = self.get_matrix_ysc2ref() return matrix.dot(yscvecs)
[docs] def convert_to_ysc(self, refvecs): matrix = self.get_matrix_ref2ysc() return matrix.dot(refvecs)
[docs] def get_matrix_ysc2nsc(self): return self.get_matrix_nsc2ysc().T
[docs] def get_matrix_nsc2ysc(self): cosyaw = np.cos(self.yawrad) sinyaw = np.sin(self.yawrad) nsc2ysc = np.array([[cosyaw, sinyaw, 0], [-sinyaw, cosyaw, 0], [0, 0, 1.]]) return nsc2ysc
[docs] def get_matrix_nsc2ref(self): return self.get_matrix_ref2nsc().T
[docs] def get_matrix_ref2nsc(self): nadir = -np.array(self.pos).copy() nadirlen = np.sqrt((nadir * nadir).sum()) z = nadir / nadirlen # outer(y, z) = outer(v, z) is x. vel = np.array(self.vel).copy() x = np.cross(vel, z) xlen = np.sqrt((x * x).sum()) x = x / xlen y = np.cross(z, x) ref2nsc = np.array([x, y, z]) return ref2nsc
[docs] def get_matrix_ysc2ref(self): ''' Return the 3x3 np.array converting NSC frame to the reference frame >>> sc = NadirLookingSc() >>> sc.set_posvel([2500., 1800, -3000], [1., 3, 0]) The z-axis of the NSC frame should be parallel to (-2500, -1800, +3000), meaning (-0.5814, -0.4186, 6977) >>> z = np.array([0, 0, 1]) >>> z_ref = sc.get_matrix_nsc2ref().dot(z) >>> print('%.4f %.4f %.4f' % (z_ref[0], z_ref[1], z_ref[2])) -0.5814 -0.4186 0.6977 ''' return self.get_matrix_ref2ysc().T
[docs] def get_matrix_ref2ysc(self): ''' Return the 3x3 np.array converting the reference frame to YSC frame ''' ref2nsc = self.get_matrix_ref2nsc() nsc2ysc = self.get_matrix_nsc2ysc() ref2ysc = nsc2ysc.dot(ref2nsc) return ref2ysc