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 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:] 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 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 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 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 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 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 wxyz(v1, v2): """ Calculates w, x, y, z coefficients for quaternion from v1 to v2 Parameters ---------- v1 : v2 : """ n1 = np.linalg.norm(v1) n2 = np.linalg.norm(v2) u1 = v1 / n1 u2 = v2 / n2 xyz = cross(u1, u2) w = np.dot(u1, u2) + 1 return w, xyz[0], xyz[1], xyz[2]
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 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 hohmann(cls, orbit_i, r_f): r"""Compute a Hohmann transfer between two circular orbits. By defining the relationship between orbit radius: .. math:: a_{trans} = \frac{r_{i} + r_{f}}{2} The Hohmann maneuver velocities can be expressed as: .. math:: \begin{align} \Delta v_{a} &= \sqrt{\frac{2\mu}{r_{i}} - \frac{\mu}{a_{trans}}} - v_{i}\\ \Delta v_{b} &= \sqrt{\frac{\mu}{r_{f}}} - \sqrt{\frac{2\mu}{r_{f}} - \frac{\mu}{a_{trans}}} \end{align} The time that takes to complete the maneuver can be computed as: .. math:: \tau_{trans} = \pi \sqrt{\frac{(a_{trans})^{3}}{\mu}} Parameters ---------- orbit_i: poliastro.twobody.orbit.Orbit Initial orbit r_f: astropy.unit.Quantity Final altitude of the orbit """ if orbit_i.nu is not 0 * u.deg: orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg) # Initial orbit data k = orbit_i.attractor.k r_i = orbit_i.r v_i = orbit_i.v h_i = norm(cross(r_i.to(u.m).value, v_i.to(u.m / u.s).value) * u.m ** 2 / u.s) p_i = h_i ** 2 / k.to(u.m ** 3 / u.s ** 2) # Hohmann is defined always from the PQW frame, since it is the # natural plane of the orbit r_i, v_i = rv_pqw( k.to(u.m ** 3 / u.s ** 2).value, p_i.to(u.m).value, orbit_i.ecc.value, orbit_i.nu.to(u.rad).value, ) # Now, we apply Hohmman maneuver r_i = norm(r_i * u.m) v_i = norm(v_i * u.m / u.s) a_trans = (r_i + r_f) / 2 # This is the modulus of the velocities dv_a = np.sqrt(2 * k / r_i - k / a_trans) - v_i dv_b = np.sqrt(k / r_f) - np.sqrt(2 * k / r_f - k / a_trans) # Write them in PQW frame dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0]) dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0]) # Transform to IJK frame rot_matrix = coe_rotation_matrix( orbit_i.inc.to(u.rad).value, orbit_i.raan.to(u.rad).value, orbit_i.argp.to(u.rad).value, ) dv_a = (rot_matrix @ dv_a) * u.m / u.s dv_b = (rot_matrix @ dv_b) * u.m / u.s t_trans = np.pi * np.sqrt(a_trans ** 3 / k) return cls((0 * u.s, dv_a), (t_trans, dv_b))
def bielliptic(cls, orbit_i, r_b, r_f): r"""Compute a bielliptic transfer between two circular orbits. The bielliptic maneuver employs two Hohmann transfers, therefore two intermediate orbits are established. We define the different radius relationships as follows: .. math:: \begin{align} a_{trans1} &= \frac{r_{i} + r_{b}}{2}\\ a_{trans2} &= \frac{r_{b} + r_{f}}{2}\\ \end{align} The increments in the velocity are: .. math:: \begin{align} \Delta v_{a} &= \sqrt{\frac{2\mu}{r_{i}} - \frac{\mu}{a_{trans1}}} - v_{i}\\ \Delta v_{b} &= \sqrt{\frac{2\mu}{r_{b}} - \frac{\mu}{a_{trans2}}} - \sqrt{\frac{2\mu}{r_{b}} - \frac{\mu}{a_trans{1}}}\\ \Delta v_{c} &= \sqrt{\frac{\mu}{r_{f}}} - \sqrt{\frac{2\mu}{r_{f}} - \frac{\mu}{a_{trans2}}}\\ \end{align} The time of flight for this maneuver is the addition of the time needed for both transition orbits, following the same formula as Hohmann: .. math:: \begin{align} \tau_{trans1} &= \pi \sqrt{\frac{a_{trans1}^{3}}{\mu}}\\ \tau_{trans2} &= \pi \sqrt{\frac{a_{trans2}^{3}}{\mu}}\\ \end{align} Parameters ---------- orbit_i: poliastro.twobody.orbit.Orbit Initial orbit r_b: astropy.unit.Quantity Altitude of the intermediate orbit r_f: astropy.unit.Quantity Final altitude of the orbit """ if orbit_i.nu is not 0 * u.deg: orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg) # Initial orbit data k = orbit_i.attractor.k r_i = orbit_i.r v_i = orbit_i.v h_i = norm(cross(r_i.to(u.m).value, v_i.to(u.m / u.s).value) * u.m ** 2 / u.s) p_i = h_i ** 2 / k.to(u.m ** 3 / u.s ** 2) # Bielliptic is defined always from the PQW frame, since it is the # natural plane of the orbit r_i, v_i = rv_pqw( k.to(u.m ** 3 / u.s ** 2).value, p_i.to(u.m).value, orbit_i.ecc.value, orbit_i.nu.to(u.rad).value, ) # Define the transfer radius r_i = norm(r_i * u.m) v_i = norm(v_i * u.m / u.s) a_trans1 = (r_i + r_b) / 2 a_trans2 = (r_b + r_f) / 2 # Compute impulses dv_a = np.sqrt(2 * k / r_i - k / a_trans1) - v_i dv_b = np.sqrt(2 * k / r_b - k / a_trans2) - np.sqrt(2 * k / r_b - k / a_trans1) dv_c = np.sqrt(k / r_f) - np.sqrt(2 * k / r_f - k / a_trans2) # Write impulses in PQW frame dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0]) dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0]) dv_c = np.array([0, dv_c.to(u.m / u.s).value, 0]) rot_matrix = coe_rotation_matrix( orbit_i.inc.to(u.rad).value, orbit_i.raan.to(u.rad).value, orbit_i.argp.to(u.rad).value, ) # Transform to IJK frame dv_a = (rot_matrix @ dv_a) * u.m / u.s dv_b = (rot_matrix @ dv_b) * u.m / u.s dv_c = (rot_matrix @ dv_c) * u.m / u.s # Compute time for maneuver t_trans1 = np.pi * np.sqrt(a_trans1 ** 3 / k) t_trans2 = np.pi * np.sqrt(a_trans2 ** 3 / k) return cls((0 * u.s, dv_a), (t_trans1, dv_b), (t_trans2, dv_c))
def hohmann(cls, orbit_i, r_f): r"""Compute a Hohmann transfer between two circular orbits. By defining the relationship between orbit radius: .. math:: a_{trans} = \frac{r_{i} + r_{f}}{2} The Hohmann maneuver velocities can be expressed as: .. math:: \begin{align} \Delta v_{a} &= \sqrt{\frac{2\mu}{r_{i}} - \frac{\mu}{a_{trans}}} - v_{i}\\ \Delta v_{b} &= \sqrt{\frac{\mu}{r_{f}}} - \sqrt{\frac{2\mu}{r_{f}} - \frac{\mu}{a_{trans}}} \end{align} The time that takes to complete the maneuver can be computed as: .. math:: \tau_{trans} = \pi \sqrt{\frac{(a_{trans})^{3}}{\mu}} Parameters ---------- orbit_i: poliastro.twobody.orbit.Orbit Initial orbit r_f: astropy.unit.Quantity Final altitude of the orbit """ if orbit_i.nu is not 0 * u.deg: orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg) # Initial orbit data k = orbit_i.attractor.k r_i = orbit_i.r v_i = orbit_i.v h_i = norm( cross(r_i.to(u.m).value, v_i.to(u.m / u.s).value) * u.m**2 / u.s) p_i = h_i**2 / k.to(u.m**3 / u.s**2) # Hohmann is defined always from the PQW frame, since it is the # natural plane of the orbit r_i, v_i = rv_pqw( k.to(u.m**3 / u.s**2).value, p_i.to(u.m).value, orbit_i.ecc.value, orbit_i.nu.to(u.rad).value, ) # Now, we apply Hohmman maneuver r_i = norm(r_i * u.m) v_i = norm(v_i * u.m / u.s) a_trans = (r_i + r_f) / 2 # This is the modulus of the velocities dv_a = np.sqrt(2 * k / r_i - k / a_trans) - v_i dv_b = np.sqrt(k / r_f) - np.sqrt(2 * k / r_f - k / a_trans) # Write them in PQW frame dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0]) dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0]) # Transform to IJK frame rot_matrix = coe_rotation_matrix( orbit_i.inc.to(u.rad).value, orbit_i.raan.to(u.rad).value, orbit_i.argp.to(u.rad).value, ) dv_a = (rot_matrix @ dv_a) * u.m / u.s dv_b = (rot_matrix @ dv_b) * u.m / u.s t_trans = np.pi * np.sqrt(a_trans**3 / k) return cls((0 * u.s, dv_a), (t_trans, dv_b))
def bielliptic(cls, orbit_i, r_b, r_f): r"""Compute a bielliptic transfer between two circular orbits. The bielliptic maneuver employs two Hohmann transfers, therefore two intermediate orbits are established. We define the different radius relationships as follows: .. math:: \begin{align} a_{trans1} &= \frac{r_{i} + r_{b}}{2}\\ a_{trans2} &= \frac{r_{b} + r_{f}}{2}\\ \end{align} The increments in the velocity are: .. math:: \begin{align} \Delta v_{a} &= \sqrt{\frac{2\mu}{r_{i}} - \frac{\mu}{a_{trans1}}} - v_{i}\\ \Delta v_{b} &= \sqrt{\frac{2\mu}{r_{b}} - \frac{\mu}{a_{trans2}}} - \sqrt{\frac{2\mu}{r_{b}} - \frac{\mu}{a_trans{1}}}\\ \Delta v_{c} &= \sqrt{\frac{\mu}{r_{f}}} - \sqrt{\frac{2\mu}{r_{f}} - \frac{\mu}{a_{trans2}}}\\ \end{align} The time of flight for this maneuver is the addition of the time needed for both transition orbits, following the same formula as Hohmann: .. math:: \begin{align} \tau_{trans1} &= \pi \sqrt{\frac{a_{trans1}^{3}}{\mu}}\\ \tau_{trans2} &= \pi \sqrt{\frac{a_{trans2}^{3}}{\mu}}\\ \end{align} Parameters ---------- orbit_i: poliastro.twobody.orbit.Orbit Initial orbit r_b: astropy.unit.Quantity Altitude of the intermediate orbit r_f: astropy.unit.Quantity Final altitude of the orbit """ if orbit_i.nu is not 0 * u.deg: orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg) # Initial orbit data k = orbit_i.attractor.k r_i = orbit_i.r v_i = orbit_i.v h_i = norm( cross(r_i.to(u.m).value, v_i.to(u.m / u.s).value) * u.m**2 / u.s) p_i = h_i**2 / k.to(u.m**3 / u.s**2) # Bielliptic is defined always from the PQW frame, since it is the # natural plane of the orbit r_i, v_i = rv_pqw( k.to(u.m**3 / u.s**2).value, p_i.to(u.m).value, orbit_i.ecc.value, orbit_i.nu.to(u.rad).value, ) # Define the transfer radius r_i = norm(r_i * u.m) v_i = norm(v_i * u.m / u.s) a_trans1 = (r_i + r_b) / 2 a_trans2 = (r_b + r_f) / 2 # Compute impulses dv_a = np.sqrt(2 * k / r_i - k / a_trans1) - v_i dv_b = np.sqrt(2 * k / r_b - k / a_trans2) - np.sqrt(2 * k / r_b - k / a_trans1) dv_c = np.sqrt(k / r_f) - np.sqrt(2 * k / r_f - k / a_trans2) # Write impulses in PQW frame dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0]) dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0]) dv_c = np.array([0, dv_c.to(u.m / u.s).value, 0]) rot_matrix = coe_rotation_matrix( orbit_i.inc.to(u.rad).value, orbit_i.raan.to(u.rad).value, orbit_i.argp.to(u.rad).value, ) # Transform to IJK frame dv_a = (rot_matrix @ dv_a) * u.m / u.s dv_b = (rot_matrix @ dv_b) * u.m / u.s dv_c = (rot_matrix @ dv_c) * u.m / u.s # Compute time for maneuver t_trans1 = np.pi * np.sqrt(a_trans1**3 / k) t_trans2 = np.pi * np.sqrt(a_trans2**3 / k) return cls((0 * u.s, dv_a), (t_trans1, dv_b), (t_trans2, dv_c))
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
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 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