''' 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