def test_atmospheric_drag(): # http://farside.ph.utexas.edu/teaching/celestial/Celestialhtml/node94.html#sair (10.148) # given the expression for \dot{r} / r, aproximate \Delta r \approx F_r * \Delta t R = Earth.R.to(u.km).value k = Earth.k.to(u.km**3 / u.s**2).value # parameters of a circular orbit with h = 250 km (any value would do, but not too small) orbit = Orbit.circular(Earth, 250 * u.km) r0, _ = orbit.rv() r0 = r0.to(u.km).value # parameters of a body C_D = 2.2 # dimentionless (any value would do) A = ((np.pi / 4.0) * (u.m**2)).to(u.km**2).value # km^2 m = 100 # kg B = C_D * A / m # parameters of the atmosphere rho0 = Earth.rho0.to(u.kg / u.km**3).value # kg/km^3 H0 = Earth.H0.to(u.km).value tof = 100000 # s dr_expected = -B * rho0 * np.exp(-(norm(r0) - R) / H0) * np.sqrt(k * norm(r0)) * tof # assuming the atmospheric decay during tof is small, # dr_expected = F_r * tof (Newton's integration formula), where # F_r = -B rho(r) |r|^2 sqrt(k / |r|^3) = -B rho(r) sqrt(k |r|) r, v = cowell(orbit, tof, ad=atmospheric_drag, R=R, C_D=C_D, A=A, m=m, H0=H0, rho0=rho0) assert_quantity_allclose(norm(r) - norm(r0), dr_expected, rtol=1e-2)
def change_ecc_quasioptimal(ss_0, ecc_f, f): """Guidance law from the model. Thrust is aligned with an inertially fixed direction perpendicular to the semimajor axis of the orbit. Parameters ---------- ss_0 : Orbit Initial orbit, containing all the information. ecc_f : float Final eccentricity. f : float Magnitude of constant acceleration """ # We fix the inertial direction at the beginning k = ss_0.attractor.k.to(u.km**3 / u.s**2).value a = ss_0.a.to(u.km).value ecc_0 = ss_0.ecc.value if ecc_0 > 0.001: # Arbitrary tolerance ref_vec = ss_0.e_vec / ecc_0 else: ref_vec = ss_0.r / norm(ss_0.r) h_unit = ss_0.h_vec / norm(ss_0.h_vec) thrust_unit = cross(h_unit, ref_vec) * np.sign(ecc_f - ecc_0) def a_d(t0, u_, k): accel_v = f * thrust_unit return accel_v delta_V, t_f = extra_quantities(k, a, ecc_0, ecc_f, f) return a_d, delta_V, t_f
def third_body(t0, state, k, k_third, third_body): r"""Calculates 3rd body acceleration (km/s2) .. math:: \vec{p} = \mu_{m}\left ( \frac{\vec{r_{m/s}}}{r_{m/s}^3} - \frac{\vec{r_{m}}}{r_{m}^3} \right ) Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) third_body: a callable object returning the position of 3rd body third body that causes the perturbation Note ---- This formula is taken from Howard Curtis, section 12.10. As an example, a third body could be the gravity from the Moon acting on a small satellite. """ body_r = third_body(t0) delta_r = body_r - state[:3] return k_third * delta_r / norm(delta_r)**3 - k_third * body_r / norm( body_r)**3
def third_body(t0, state, k, k_third, third_body): r"""Calculates 3rd body acceleration (km/s2) .. math:: \vec{p} = \mu_{m}\left ( \frac{\vec{r_{m/s}}}{r_{m/s}^3} - \frac{\vec{r_{m}}}{r_{m}^3} \right ) Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) third_body: a callable object returning the position of 3rd body third body that causes the perturbation Note ---- This formula is taken from Howard Curtis, section 12.10. As an example, a third body could be the gravity from the Moon acting on a small satellite. """ body_r = third_body(t0) delta_r = body_r - state[:3] return k_third * delta_r / norm(delta_r) ** 3 - k_third * body_r / norm(body_r) ** 3
def change_inc_ecc(ss_0, ecc_f, inc_f, f): """Guidance law from the model. Thrust is aligned with an inertially fixed direction perpendicular to the semimajor axis of the orbit. Parameters ---------- ss_0 : Orbit Initial orbit, containing all the information. ecc_f : float Final eccentricity. inc_f : float Final inclination. f : float Magnitude of constant acceleration. """ # We fix the inertial direction at the beginning ecc_0 = ss_0.ecc.value if ecc_0 > 0.001: # Arbitrary tolerance ref_vec = ss_0.e_vec / ecc_0 else: ref_vec = ss_0.r / norm(ss_0.r) h_unit = ss_0.h_vec / norm(ss_0.h_vec) thrust_unit = cross(h_unit, ref_vec) * np.sign(ecc_f - ecc_0) inc_0 = ss_0.inc.to(u.rad).value argp = ss_0.argp.to(u.rad).value beta_0_ = beta(ecc_0, ecc_f, inc_0, inc_f, argp) def a_d(t0, u_, k): r = u_[:3] v = u_[3:] nu = rv2coe(k, r, v)[-1] beta_ = beta_0_ * np.sign( np.cos(nu) ) # The sign of ß reverses at minor axis crossings w_ = cross(r, v) / norm(cross(r, v)) accel_v = f * (np.cos(beta_) * thrust_unit + np.sin(beta_) * w_) return accel_v delta_V, beta_, t_f = extra_quantities( ss_0.attractor.k.to(u.km ** 3 / u.s ** 2).value, ss_0.a.to(u.km).value, ecc_0, ecc_f, inc_0, inc_f, argp, f, ) return a_d, delta_V, beta_, t_f
def a_d(t0, u_, k): r = u_[:3] v = u_[3:] nu = rv2coe(k, r, v)[-1] alpha_ = nu - np.pi / 2 r_ = r / norm(r) w_ = cross(r, v) / norm(cross(r, v)) s_ = cross(w_, r_) accel_v = f * (np.cos(alpha_) * s_ + np.sin(alpha_) * r_) return accel_v
def a_d(t0, u_, k): r = u_[:3] v = u_[3:] # Change sign of beta with the out-of-plane velocity beta_ = beta(t0, V_0=V_0, f=f, beta_0=beta_0_) * np.sign(r[0] * (inc_f - inc_0)) t_ = v / norm(v) w_ = cross(r, v) / norm(cross(r, v)) # n_ = cross(t_, w_) accel_v = f * (np.cos(beta_) * t_ + np.sin(beta_) * w_) return accel_v
def a_d(t0, u_, k): r = u_[:3] v = u_[3:] # Change sign of beta with the out-of-plane velocity beta_ = beta(t0, V_0=V_0, f=f, beta_0=beta_0_) * np.sign( r[0] * (inc_f - inc_0)) t_ = v / norm(v) w_ = cross(r, v) / norm(cross(r, v)) # n_ = cross(t_, w_) accel_v = f * (np.cos(beta_) * t_ + np.sin(beta_) * w_) return accel_v
def J2_perturbation(t0, state, k, J2, R): """Calculates J2_perturbation acceleration (km/s2) .. versionadded:: 0.9.0 Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) J2: float oblateness factor R: float attractor radius Notes ----- The J2 accounts for the oblateness of the attractor. The formula is given in Howard Curtis, (12.30) """ r_vec = state[:3] r = norm(r_vec) factor = (3.0 / 2.0) * k * J2 * (R**2) / (r**5) a_x = 5.0 * r_vec[2]**2 / r**2 - 1 a_y = 5.0 * r_vec[2]**2 / r**2 - 1 a_z = 5.0 * r_vec[2]**2 / r**2 - 3 return np.array([a_x, a_y, a_z]) * r_vec * factor
def J2_perturbation(t0, state, k, J2, R): """Calculates J2_perturbation acceleration (km/s2) .. versionadded:: 0.9.0 Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) J2: float oblateness factor R: float attractor radius Notes ----- The J2 accounts for the oblateness of the attractor. The formula is given in Howard Curtis, (12.30) """ r_vec = state[:3] r = norm(r_vec) factor = (3.0 / 2.0) * k * J2 * (R ** 2) / (r ** 5) a_x = 5.0 * r_vec[2] ** 2 / r ** 2 - 1 a_y = 5.0 * r_vec[2] ** 2 / r ** 2 - 1 a_z = 5.0 * r_vec[2] ** 2 / r ** 2 - 3 return np.array([a_x, a_y, a_z]) * r_vec * factor
def J3_perturbation(t0, state, k, J3, R): """Calculates J3_perturbation acceleration (km/s2) Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) J3: float oblateness factor R: float attractor radius Notes ----- The J3 accounts for the oblateness of the attractor. The formula is given in Howard Curtis, problem 12.8 This perturbation has not been fully validated, see https://github.com/poliastro/poliastro/pull/398 """ r_vec = state[:3] r = norm(r_vec) factor = (1.0 / 2.0) * k * J3 * (R**3) / (r**5) cos_phi = r_vec[2] / r a_x = 5.0 * r_vec[0] / r * (7.0 * cos_phi**3 - 3.0 * cos_phi) a_y = 5.0 * r_vec[1] / r * (7.0 * cos_phi**3 - 3.0 * cos_phi) a_z = 3.0 * (35.0 / 3.0 * cos_phi**4 - 10.0 * cos_phi**2 + 1) return np.array([a_x, a_y, a_z]) * factor
def J3_perturbation(t0, state, k, J3, R): """Calculates J3_perturbation acceleration (km/s2) Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) J3: float oblateness factor R: float attractor radius Notes ----- The J3 accounts for the oblateness of the attractor. The formula is given in Howard Curtis, problem 12.8 This perturbation has not been fully validated, see https://github.com/poliastro/poliastro/pull/398 """ r_vec = state[:3] r = norm(r_vec) factor = (1.0 / 2.0) * k * J3 * (R ** 3) / (r ** 5) cos_phi = r_vec[2] / r a_x = 5.0 * r_vec[0] / r * (7.0 * cos_phi ** 3 - 3.0 * cos_phi) a_y = 5.0 * r_vec[1] / r * (7.0 * cos_phi ** 3 - 3.0 * cos_phi) a_z = 3.0 * (35.0 / 3.0 * cos_phi ** 4 - 10.0 * cos_phi ** 2 + 1) return np.array([a_x, a_y, a_z]) * factor
def atmospheric_drag(t0, state, k, R, C_D, A, m, H0, rho0): r"""Calculates atmospheric drag acceleration (km/s2) .. math:: \vec{p} = -\frac{1}{2}\rho v_{rel}\left ( \frac{C_{d}A}{m} \right )\vec{v_{rel}} .. versionadded:: 0.9.0 Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) R : float radius of the attractor (km) C_D: float dimensionless drag coefficient () A: float frontal area of the spacecraft (km^2) m: float mass of the spacecraft (kg) H0 : float atmospheric scale height, (km) rho0: float the exponent density pre-factor, (kg / m^3) Note ---- This function provides the acceleration due to atmospheric drag. We follow Howard Curtis, section 12.4 the atmospheric density model is rho(H) = rho0 x exp(-H / H0) """ H = norm(state[:3]) v_vec = state[3:] v = norm(v_vec) B = C_D * A / m rho = rho0 * np.exp(-(H - R) / H0) return -(1.0 / 2.0) * rho * B * v * v_vec
def third_body(t0, state, k, k_third, third_body): """Calculates 3rd body acceleration (km/s2) Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) third_body: a callable object returning the position of 3rd body third body that causes the perturbation """ body_r = third_body(t0) delta_r = body_r - state[:3] return k_third * delta_r / norm(delta_r) ** 3 - k_third * body_r / norm(body_r) ** 3
def atmospheric_drag(t0, state, k, R, C_D, A, m, H0, rho0): r"""Calculates atmospheric drag acceleration (km/s2) .. math:: \vec{p} = -\frac{1}{2}\rho v_{rel}\left ( \frac{C_{d}A}{m} \right )\vec{v_{rel}} .. versionadded:: 0.9.0 Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) C_D: float dimensionless drag coefficient () A: float frontal area of the spacecraft (km^2) m: float mass of the spacecraft (kg) H0 : float atmospheric scale height, (km) rho0: float the exponent density pre-factor, (kg / m^3) Note ---- This function provides the acceleration due to atmospheric drag. We follow Howard Curtis, section 12.4 the atmospheric density model is rho(H) = rho0 x exp(-H / H0) """ H = norm(state[:3]) v_vec = state[3:] v = norm(v_vec) B = C_D * A / m rho = rho0 * np.exp(-(H - R) / H0) return -(1.0 / 2.0) * rho * B * v * v_vec
def izzo(k, r1, r2, tof, M, numiter, rtol): # Check preconditions assert tof > 0 assert k > 0 # Chord c = r2 - r1 c_norm, r1_norm, r2_norm = norm(c), norm(r1), norm(r2) # Semiperimeter s = (r1_norm + r2_norm + c_norm) * .5 # Versors i_r1, i_r2 = r1 / r1_norm, r2 / r2_norm i_h = cross(i_r1, i_r2) i_h = i_h / norm(i_h) # Fixed from paper # Geometry of the problem ll = np.sqrt(1 - c_norm / s) if i_h[2] < 0: ll = -ll i_h = -i_h i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2) # Fixed from paper # Non dimensional time of flight T = np.sqrt(2 * k / s ** 3) * tof # Find solutions xy = _find_xy(ll, T, M, numiter, rtol) # Reconstruct gamma = np.sqrt(k * s / 2) rho = (r1_norm - r2_norm) / c_norm sigma = np.sqrt(1 - rho ** 2) for x, y in xy: V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll, gamma, rho, sigma) v1 = V_r1 * i_r1 + V_t1 * i_t1 v2 = V_r2 * i_r2 + V_t2 * i_t2 yield v1, v2
def third_body(t0, state, k, k_third, third_body): """Calculates 3rd body acceleration (km/s2) Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) third_body: a callable object returning the position of 3rd body third body that causes the perturbation """ body_r = third_body(t0) delta_r = body_r - state[:3] return k_third * delta_r / norm(delta_r)**3 - k_third * body_r / norm( body_r)**3
def izzo(k, r1, r2, tof, M, numiter, rtol): # Check preconditions assert tof > 0 assert k > 0 # Chord c = r2 - r1 c_norm, r1_norm, r2_norm = norm(c), norm(r1), norm(r2) # Semiperimeter s = (r1_norm + r2_norm + c_norm) * .5 # Versors i_r1, i_r2 = r1 / r1_norm, r2 / r2_norm i_h = cross(i_r1, i_r2) i_h = i_h / norm(i_h) # Fixed from paper # Geometry of the problem ll = np.sqrt(1 - c_norm / s) if i_h[2] < 0: ll = -ll i_h = -i_h i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2) # Fixed from paper # Non dimensional time of flight T = np.sqrt(2 * k / s**3) * tof # Find solutions xy = _find_xy(ll, T, M, numiter, rtol) # Reconstruct gamma = np.sqrt(k * s / 2) rho = (r1_norm - r2_norm) / c_norm sigma = np.sqrt(1 - rho**2) for x, y in xy: V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll, gamma, rho, sigma) v1 = V_r1 * i_r1 + V_t1 * i_t1 v2 = V_r2 * i_r2 + V_t2 * i_t2 yield v1, v2
def a_d(t0, u_, k): r = u_[:3] v = u_[3:] nu = rv2coe(k, r, v)[-1] beta_ = beta_0_ * np.sign( np.cos(nu)) # The sign of ß reverses at minor axis crossings w_ = cross(r, v) / norm(cross(r, v)) accel_v = f * (np.cos(beta_) * thrust_unit + np.sin(beta_) * w_) return accel_v
def a_d(t0, u_, k): r = u_[:3] v = u_[3:] nu = rv2coe(k, r, v)[-1] beta_ = beta_0_ * np.sign( np.cos(nu) ) # The sign of ß reverses at minor axis crossings w_ = cross(r, v) / norm(cross(r, v)) accel_v = f * (np.cos(beta_) * thrust_unit + np.sin(beta_) * w_) return accel_v
def radiation_pressure(t0, state, k, R, C_R, A, m, Wdivc_s, star): r"""Calculates radiation pressure acceleration (km/s2) .. math:: \vec{p} = -\nu \frac{S}{c} \left ( \frac{C_{r}A}{m} \right )\frac{\vec{r}}{r} Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) R : float radius of the attractor C_R: float dimensionless radiation pressure coefficient, 1 < C_R < 2 () A: float effective spacecraft area (km^2) m: float mass of the spacecraft (kg) Wdivc_s : float total star emitted power divided by the speed of light (W * s / km) star: a callable object returning the position of star in attractor frame star position Note ---- This function provides the acceleration due to star light pressure. We follow Howard Curtis, section 12.9 """ r_star = star(t0) r_sat = state[:3] P_s = Wdivc_s / (norm(r_star) ** 2) nu = float(shadow_function(r_sat, r_star, R)) return -nu * P_s * (C_R * A / m) * r_star / norm(r_star)
def radiation_pressure(t0, state, k, R, C_R, A, m, Wdivc_s, star): r"""Calculates radiation pressure acceleration (km/s2) .. math:: \vec{p} = -\nu \frac{S}{c} \left ( \frac{C_{r}A}{m} \right )\frac{\vec{r}}{r} Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) R : float radius of the attractor C_R: float dimensionless radiation pressure coefficient, 1 < C_R < 2 () A: float effective spacecraft area (km^2) m: float mass of the spacecraft (kg) Wdivc_s : float total star emitted power divided by the speed of light (W * s / km) star: a callable object returning the position of star in attractor frame star position Note ---- This function provides the acceleration due to star light pressure. We follow Howard Curtis, section 12.9 """ r_star = star(t0) r_sat = state[:3] P_s = Wdivc_s / (norm(r_star)**2) nu = float(shadow_function(r_sat, r_star, R)) return -nu * P_s * (C_R * A / m) * r_star / norm(r_star)
def rv2coe(k, r, v, tol=1e-8): """Converts from vectors to classical orbital elements. Parameters ---------- k : float Standard gravitational parameter (km^3 / s^2). r : array Position vector (km). v : array Velocity vector (km / s). tol : float, optional Tolerance for eccentricity and inclination checks, default to 1e-8. """ h = cross(r, v) n = cross([0, 0, 1], h) / norm(h) e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k ecc = norm(e) p = h.dot(h) / k inc = np.arccos(h[2] / norm(h)) circular = ecc < tol equatorial = abs(inc) < tol if equatorial and not circular: raan = 0 argp = np.arctan2(e[1], e[0]) % (2 * np.pi) # Longitude of periapsis nu = (np.arctan2(h.dot(cross(e, r)) / norm(h), r.dot(e)) % (2 * np.pi)) elif not equatorial and circular: raan = np.arctan2(n[1], n[0]) % (2 * np.pi) argp = 0 # Argument of latitude nu = (np.arctan2(r.dot(cross(h, n)) / norm(h), r.dot(n)) % (2 * np.pi)) elif equatorial and circular: raan = 0 argp = 0 nu = np.arctan2(r[1], r[0]) % (2 * np.pi) # True longitude else: raan = np.arctan2(n[1], n[0]) % (2 * np.pi) argp = (np.arctan2(e.dot(cross(h, n)) / norm(h), e.dot(n)) % (2 * np.pi)) nu = (np.arctan2(r.dot(cross(h, e)) / norm(h), r.dot(e)) % (2 * np.pi)) return p, ecc, inc, raan, argp, nu
def J2_perturbation(t0, state, k, J2, R): r"""Calculates J2_perturbation acceleration (km/s2) .. math:: \vec{p} = \frac{3}{2}\frac{J_{2}\mu R^{2}}{r^{4}}\left [\frac{x}{r}\left ( 5\frac{z^{2}}{r^{2}}-1 \right )\vec{i} + \frac{y}{r}\left ( 5\frac{z^{2}}{r^{2}}-1 \right )\vec{j} + \frac{z}{r}\left ( 5\frac{z^{2}}{r^{2}}-3 \right )\vec{k}\right] .. versionadded:: 0.9.0 Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) J2: float oblateness factor R: float attractor radius Note ---- The J2 accounts for the oblateness of the attractor. The formula is given in Howard Curtis, (12.30) """ r_vec = state[:3] r = norm(r_vec) factor = (3.0 / 2.0) * k * J2 * (R ** 2) / (r ** 5) a_x = 5.0 * r_vec[2] ** 2 / r ** 2 - 1 a_y = 5.0 * r_vec[2] ** 2 / r ** 2 - 1 a_z = 5.0 * r_vec[2] ** 2 / r ** 2 - 3 return np.array([a_x, a_y, a_z]) * r_vec * factor
def J2_perturbation(t0, state, k, J2, R): r"""Calculates J2_perturbation acceleration (km/s2) .. math:: \vec{p} = \frac{3}{2}\frac{J_{2}\mu R^{2}}{r^{4}}\left [\frac{x}{r}\left ( 5\frac{z^{2}}{r^{2}}-1 \right )\vec{i} + \frac{y}{r}\left ( 5\frac{z^{2}}{r^{2}}-1 \right )\vec{j} + \frac{z}{r}\left ( 5\frac{z^{2}}{r^{2}}-3 \right )\vec{k}\right] .. versionadded:: 0.9.0 Parameters ---------- t0 : float Current time (s) state : numpy.ndarray Six component state vector [x, y, z, vx, vy, vz] (km, km/s). k : float gravitational constant, (km^3/s^2) J2: float oblateness factor R: float attractor radius Note ---- The J2 accounts for the oblateness of the attractor. The formula is given in Howard Curtis, (12.30) """ r_vec = state[:3] r = norm(r_vec) factor = (3.0 / 2.0) * k * J2 * (R**2) / (r**5) a_x = 5.0 * r_vec[2]**2 / r**2 - 1 a_y = 5.0 * r_vec[2]**2 / r**2 - 1 a_z = 5.0 * r_vec[2]**2 / r**2 - 3 return np.array([a_x, a_y, a_z]) * r_vec * factor
def normalize_to_Curtis(t0, sun_r): r = sun_r(t0) return 149600000 * r / norm(r)
def rv2coe(k, r, v, tol=1e-8): r"""Converts from vectors to classical orbital elements. 1. First the angular momentum is computed: .. math:: \vec{h} = \vec{r} \times \vec{v} 2. With it the eccentricity can be solved: .. math:: \begin{align} \vec{e} &= \frac{1}{\mu}\left [ \left ( v^{2} - \frac{\mu}{r}\right ) \vec{r} - (\vec{r} \cdot \vec{v})\vec{v} \right ] \\ e &= \sqrt{\vec{e}\cdot\vec{e}} \\ \end{align} 3. The node vector line is solved: .. math:: \begin{align} \vec{N} &= \vec{k} \times \vec{h} \\ N &= \sqrt{\vec{N}\cdot\vec{N}} \end{align} 4. The rigth ascension node is computed: .. math:: \Omega = \left\{ \begin{array}{lcc} cos^{-1}{\left ( \frac{N_{x}}{N} \right )} & if & N_{y} \geq 0 \\ \\ 360^{o} -cos^{-1}{\left ( \frac{N_{x}}{N} \right )} & if & N_{y} < 0 \\ \end{array} \right. 5. The argument of perigee: .. math:: \omega = \left\{ \begin{array}{lcc} cos^{-1}{\left ( \frac{\vec{N}\vec{e}}{Ne} \right )} & if & e_{z} \geq 0 \\ \\ 360^{o} -cos^{-1}{\left ( \frac{\vec{N}\vec{e}}{Ne} \right )} & if & e_{z} < 0 \\ \end{array} \right. 6. And finally the true anomaly: .. math:: \nu = \left\{ \begin{array}{lcc} cos^{-1}{\left ( \frac{\vec{e}\vec{r}}{er} \right )} & if & v_{r} \geq 0 \\ \\ 360^{o} -cos^{-1}{\left ( \frac{\vec{e}\vec{r}}{er} \right )} & if & v_{r} < 0 \\ \end{array} \right. Parameters ---------- k : float Standard gravitational parameter (km^3 / s^2) r : array Position vector (km) v : array Velocity vector (km / s) tol : float, optional Tolerance for eccentricity and inclination checks, default to 1e-8 Returns ------- p : float Semi-latus rectum of parameter (km) ecc: float Eccentricity inc: float Inclination (rad) raan: float Right ascension of the ascending nod (rad) argp: float Argument of Perigee (rad) nu: float True Anomaly (rad) Examples -------- >>> from poliastro.constants import GM_earth >>> from astropy import units as u >>> k = GM_earth.to(u.km ** 3 / u.s ** 2).value # Earth gravitational parameter >>> r = np.array([-6045., -3490., 2500.]) >>> v = np.array([-3.457, 6.618, 2.533]) >>> p, ecc, inc, raan, argp, nu = rv2coe(k, r, v) >>> print("p:", p, "[km]") p: 8530.47436396927 [km] >>> print("ecc:", ecc) ecc: 0.17121118195416898 >>> print("inc:", np.rad2deg(inc), "[deg]") inc: 153.2492285182475 [deg] >>> print("raan:", np.rad2deg(raan), "[deg]") raan: 255.27928533439618 [deg] >>> print("argp:", np.rad2deg(argp), "[deg]") argp: 20.068139973005366 [deg] >>> print("nu:", np.rad2deg(nu), "[deg]") nu: 28.445804984192122 [deg] Note ---- This example is a real exercise from Orbital Mechanics for Engineering students by Howard D.Curtis. This exercise is 4.3 of 3rd. Edition, page 200. """ h = cross(r, v) n = cross([0, 0, 1], h) e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k ecc = norm(e) p = h.dot(h) / k inc = np.arccos(h[2] / norm(h)) circular = ecc < tol equatorial = abs(inc) < tol if equatorial and not circular: raan = 0 argp = np.arctan2(e[1], e[0]) % (2 * np.pi) # Longitude of periapsis nu = np.arctan2(h.dot(cross(e, r)) / norm(h), r.dot(e)) elif not equatorial and circular: raan = np.arctan2(n[1], n[0]) % (2 * np.pi) argp = 0 # Argument of latitude nu = np.arctan2(r.dot(cross(h, n)) / norm(h), r.dot(n)) elif equatorial and circular: raan = 0 argp = 0 nu = np.arctan2(r[1], r[0]) % (2 * np.pi) # True longitude else: a = p / (1 - (ecc ** 2)) ka = k * a if a > 0: e_se = r.dot(v) / sqrt(ka) e_ce = norm(r) * (norm(v) ** 2) / k - 1 nu = E_to_nu(np.arctan2(e_se, e_ce), ecc) else: e_sh = r.dot(v) / sqrt(-ka) e_ch = norm(r) * (norm(v) ** 2) / k - 1 nu = F_to_nu(np.log((e_ch + e_sh) / (e_ch - e_sh)) / 2, ecc) raan = np.arctan2(n[1], n[0]) % (2 * np.pi) px = r.dot(n) py = r.dot(cross(h, n)) / norm(h) argp = (np.arctan2(py, px) - nu) % (2 * np.pi) nu = (nu + np.pi) % (2 * np.pi) - np.pi return p, ecc, inc, raan, argp, nu
def rv2coe(k, r, v, tol=1e-8): r"""Converts from vectors to classical orbital elements. 1. First the angular momentum is computed: .. math:: \vec{h} = \vec{r} \times \vec{v} 2. With it the eccentricity can be solved: .. math:: \begin{align} \vec{e} &= \frac{1}{\mu}\left [ \left ( v^{2} - \frac{\mu}{r}\right ) \vec{r} - (\vec{r} \cdot \vec{v})\vec{v} \right ] \\ e &= \sqrt{\vec{e}\cdot\vec{e}} \\ \end{align} 3. The node vector line is solved: .. math:: \begin{align} \vec{N} &= \vec{k} \times \vec{h} \\ N &= \sqrt{\vec{N}\cdot\vec{N}} \end{align} 4. The rigth ascension node is computed: .. math:: \Omega = \left\{ \begin{array}{lcc} cos^{-1}{\left ( \frac{N_{x}}{N} \right )} & if & N_{y} \geq 0 \\ \\ 360^{o} -cos^{-1}{\left ( \frac{N_{x}}{N} \right )} & if & N_{y} < 0 \\ \end{array} \right. 5. The argument of perigee: .. math:: \omega = \left\{ \begin{array}{lcc} cos^{-1}{\left ( \frac{\vec{N}\vec{e}}{Ne} \right )} & if & e_{z} \geq 0 \\ \\ 360^{o} -cos^{-1}{\left ( \frac{\vec{N}\vec{e}}{Ne} \right )} & if & e_{z} < 0 \\ \end{array} \right. 6. And finally the true anomaly: .. math:: \nu = \left\{ \begin{array}{lcc} cos^{-1}{\left ( \frac{\vec{e}\vec{r}}{er} \right )} & if & v_{r} \geq 0 \\ \\ 360^{o} -cos^{-1}{\left ( \frac{\vec{e}\vec{r}}{er} \right )} & if & v_{r} < 0 \\ \end{array} \right. Parameters ---------- k : float Standard gravitational parameter (km^3 / s^2) r : array Position vector (km) v : array Velocity vector (km / s) tol : float, optional Tolerance for eccentricity and inclination checks, default to 1e-8 Returns ------- p : float Semi-latus rectum of parameter (km) ecc: float Eccentricity inc: float Inclination (rad) raan: float Right ascension of the ascending nod (rad) argp: float Argument of Perigee (rad) nu: float True Anomaly (rad) Examples -------- >>> from poliastro.core.elements import rv2coe >>> from poliastro.constants import GM_earth >>> from astropy import units as u >>> import numpy as np >>> k = GM_earth.to(u.km**3 / u.s**2) #Earth gravitational parameter >>> r = [-6045, -3490, 2500] * u.km >>> v = [-3.457, 6.618, 2.533] * u.km / u.s >>> p, ecc, inc, raan, argp, nu = rv2coe(k, r, v) >>> print("p:", p, "[km]") p: 8530.47436396927 [km] >>> print("ecc:", ecc) ecc: 0.17121118195416898 >>> print("inc:", np.rad2deg(inc), "[deg]") inc: 153.2492285182475 [deg] >>> print("raan:", np.rad2deg(raan), "[deg]") raan: 255.27928533439618 [deg] >>> print("argp:", np.rad2deg(argp), "[deg]") argp: 20.068139973005362 [deg] >>> print("nu:", np.rad2deg(nu), "[deg]") nu: 28.445804984192108 [deg] Note ---- This example is a real exercise from Orbital Mechanics for Engineering students by Howard D.Curtis. This exercise is 4.3 of 3rd. Edition, page 200. """ h = cross(r, v) n = cross([0, 0, 1], h) / norm(h) e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k ecc = norm(e) p = h.dot(h) / k inc = np.arccos(h[2] / norm(h)) circular = ecc < tol equatorial = abs(inc) < tol if equatorial and not circular: raan = 0 argp = np.arctan2(e[1], e[0]) % (2 * np.pi) # Longitude of periapsis nu = np.arctan2(h.dot(cross(e, r)) / norm(h), r.dot(e)) % (2 * np.pi) elif not equatorial and circular: raan = np.arctan2(n[1], n[0]) % (2 * np.pi) argp = 0 # Argument of latitude nu = np.arctan2(r.dot(cross(h, n)) / norm(h), r.dot(n)) % (2 * np.pi) elif equatorial and circular: raan = 0 argp = 0 nu = np.arctan2(r[1], r[0]) % (2 * np.pi) # True longitude else: raan = np.arctan2(n[1], n[0]) % (2 * np.pi) argp = np.arctan2(e.dot(cross(h, n)) / norm(h), e.dot(n)) % (2 * np.pi) nu = np.arctan2(r.dot(cross(h, e)) / norm(h), r.dot(e)) % (2 * np.pi) return p, ecc, inc, raan, argp, nu
def izzo(k, r1, r2, tof, M, numiter, rtol): """ Aplies izzo algorithm to solve Lambert's problem. Parameters ---------- k: float Gravitational Constant r1: ~numpy.array Initial position vector r2: ~numpy.array Final position vector tof: float Time of flight between both positions M: int Number of revolutions numiter: int Numbert of iterations rotl: float Error tolerance Returns ------- v1: ~numpy.array Initial velocity vector v2: ~numpy.array FInal velocity vector """ # Check preconditions assert tof > 0 assert k > 0 # Check collinearity of r1 and r2 if np.all(cross(r1, r2) == 0): raise ValueError( "Lambert solution cannot be computed for collinear vectors") # Chord c = r2 - r1 c_norm, r1_norm, r2_norm = norm(c), norm(r1), norm(r2) # Semiperimeter s = (r1_norm + r2_norm + c_norm) * 0.5 # Versors i_r1, i_r2 = r1 / r1_norm, r2 / r2_norm i_h = cross(i_r1, i_r2) i_h = i_h / norm(i_h) # Fixed from paper # Geometry of the problem ll = np.sqrt(1 - min(1.0, c_norm / s)) if i_h[2] < 0: ll = -ll i_h = -i_h i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2) # Fixed from paper # Non dimensional time of flight T = np.sqrt(2 * k / s**3) * tof # Find solutions xy = _find_xy(ll, T, M, numiter, rtol) # Reconstruct gamma = np.sqrt(k * s / 2) rho = (r1_norm - r2_norm) / c_norm sigma = np.sqrt(1 - rho**2) for x, y in xy: V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll, gamma, rho, sigma) v1 = V_r1 * i_r1 + V_t1 * i_t1 v2 = V_r2 * i_r2 + V_t2 * i_t2 yield v1, v2
def izzo(k, r1, r2, tof, M, numiter, rtol): """ Aplies izzo algorithm to solve Lambert's problem. Parameters ---------- k: float Gravitational Constant r1: ~numpy.array Initial position vector r2: ~numpy.array Final position vector tof: float Time of flight between both positions M: int Number of revolutions numiter: int Numbert of iterations rtol: float Error tolerance Returns ------- v1: ~numpy.array Initial velocity vector v2: ~numpy.array Final velocity vector """ # Check preconditions assert tof > 0 assert k > 0 # Check collinearity of r1 and r2 if np.all(cross(r1, r2) == 0): raise ValueError("Lambert solution cannot be computed for collinear vectors") # Chord c = r2 - r1 c_norm, r1_norm, r2_norm = norm(c), norm(r1), norm(r2) # Semiperimeter s = (r1_norm + r2_norm + c_norm) * 0.5 # Versors i_r1, i_r2 = r1 / r1_norm, r2 / r2_norm i_h = cross(i_r1, i_r2) i_h = i_h / norm(i_h) # Fixed from paper # Geometry of the problem ll = np.sqrt(1 - min(1.0, c_norm / s)) if i_h[2] < 0: ll = -ll i_h = -i_h i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2) # Fixed from paper # Non dimensional time of flight T = np.sqrt(2 * k / s ** 3) * tof # Find solutions xy = _find_xy(ll, T, M, numiter, rtol) # Reconstruct gamma = np.sqrt(k * s / 2) rho = (r1_norm - r2_norm) / c_norm sigma = np.sqrt(1 - rho ** 2) for x, y in xy: V_r1, V_r2, V_t1, V_t2 = _reconstruct( x, y, r1_norm, r2_norm, ll, gamma, rho, sigma ) v1 = V_r1 * i_r1 + V_t1 * i_t1 v2 = V_r2 * i_r2 + V_t2 * i_t2 yield v1, v2