def test_true_to_mean(): for row in ELLIPTIC_ANGLES_DATA: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = E_to_M(nu_to_E(nu, ecc), ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def test_expected_mean_anomaly(): # Example from Curtis expected_mean_anomaly = 77.93 * u.deg attractor = Earth _a = 1.0 * u.deg # Unused angle a = 15_300 * u.km ecc = 0.37255 * u.one nu = 120 * u.deg orbit = Orbit.from_classical(attractor, a, ecc, _a, _a, _a, nu) orbit_M = E_to_M(nu_to_E(orbit.nu, orbit.ecc), orbit.ecc) assert_quantity_allclose(orbit_M, expected_mean_anomaly, rtol=1e-2)
def propagate(self, duration=43200, step=60): """ Core method for relative trajectory generation. Inputs duration and time step (integers), and updates the six state arrays of the object (XYZ position and XYZ velocity) for N samples. Input: - duration - integer number of seconds (optional, default = 12 hours) - step - integer time step size (optional, default = 1 minute) Returns two numpy arrays - Nx3 matrix of all position vectors - Nx3 matrix of all velocity vector """ # Check if the orbit is an ellipse (closed) if self.satC.ecc < 1 and self.satD.ecc < 1: # Get the initial mean anomaly of the chief. mC = E_to_M(nu_to_E(self.satC.nu, self.satC.ecc), self.satC.ecc) # Get the mean anomaly of the deputy. mD = E_to_M(nu_to_E(self.satD.nu, self.satD.ecc), self.satD.ecc) # Get the mean motion of the chief. nC = self.satC.n # Get the mean motion of the deputy. nD = self.satD.n # Initialise relative position and velocity component arrays. relPosArrayX, relPosArrayY, relPosArrayZ = [], [], [] relVelArrayX, relVelArrayY, relVelArrayZ = [], [], [] # Get the gravitational constant in u.km**3 / u.s**2 mu = self.satC.attractor.k.to(u.km**3 / u.s**2) # Initialise pi in terms of astropy units pi = np.pi * 1 * u.rad # Get a time step in Astropy units (seconds) ts = step * u.s # For each sample... for t in range(0, duration, step): # Update the mean anomaly of the chief (loop over pi). mC = ((mC + pi + (nC * ts)) % (2 * pi)) - pi # Update the mean anomaly of the deputy (loop over pi). mD = ((mD + pi + (nD * ts)) % (2 * pi)) - pi # Compute the chief position, velocity and true anomaly. pC, vC, nuC = self._solve_posn(self.satC.a, self.satC.ecc, self.satC.inc, self.satC.argp, self.satC.raan, mC, mu) # Compute the deputy position, velocity and true anomaly. pD, vD, nuD = self._solve_posn(self.satD.a, self.satD.ecc, self.satD.inc, self.satD.argp, self.satD.raan, mD, mu) # Get the argument of latitude of the chief. uC = nuC + self.satC.argp uC = (uC + pi) % (2 * pi) - pi # Loop over pi # Get the argument of latitude of the deputy. uD = nuD + self.satD.argp uD = (uD + pi) % (2 * pi) - pi # Loop over pi # Get the relative argument of latitude. du = uD - uC du = (du + pi) % (2 * pi) - pi # Loop over pi # Save the chief initial argument of latitude. if t == 0: uC0 = uC # Compute the deputy elapsed argument of latitude. uD_elapsed = uD - uC0 uD_elapsed = (uD_elapsed + pi) % (2 * pi) - pi uD_elapsed = uD_elapsed.to_value(u.rad) # Compute the velocity magnitude vCMag = np.sqrt(vC[0]**2 + vC[1]**2 + vC[2]**2) # Initialize the time-dependent input vector uVect = np.array([1.0, uD_elapsed, np.cos(uC), np.sin(uC)]) # Update Row 1 Column 0 of the state transition matrix. self.M[1][0] = du.to_value(u.rad) + self.dR # We may now compute the normalized relative state vectors relPos = np.matmul(self.M[:3], uVect) relVel = np.matmul(self.M[3:], uVect) # Un-normalize the relative position vectors relPosArrayX.append((relPos[0] * self.satC.a).to_value(u.km)) relPosArrayY.append((relPos[1] * self.satC.a).to_value(u.km)) relPosArrayZ.append((relPos[2] * self.satC.a).to_value(u.km)) # Un-normalize the relative velocity vectors relVelArrayX.append((relVel[0] * vCMag).to_value(u.km / u.s)) relVelArrayY.append((relVel[1] * vCMag).to_value(u.km / u.s)) relVelArrayZ.append((relVel[2] * vCMag).to_value(u.km / u.s)) # Save the entire relativeEphem matrix. self.relPosArrayX = np.array(relPosArrayX) * (1 * u.km) self.relPosArrayY = np.array(relPosArrayY) * (1 * u.km) self.relPosArrayZ = np.array(relPosArrayZ) * (-1 * u.km) self.relVelArrayX = np.array(relVelArrayX) * (1 * u.km / u.s) self.relVelArrayY = np.array(relVelArrayY) * (1 * u.km / u.s) self.relVelArrayZ = np.array(relVelArrayZ) * (-1 * u.km / u.s) # To allow for chaining... return self # Or if the orbit is a para/hyperbola... else: print('Error! Formations for non-closed orbits not supported!') return self