def test_J2_propagation_Earth(): # from Curtis example 12.2: r0 = np.array([-2384.46, 5729.01, 3050.46]) # km v0 = np.array([-7.36138, -2.98997, 1.64354]) # km/s orbit = Orbit.from_vectors(Earth, r0 * u.km, v0 * u.km / u.s) tofs = [48.0] * u.h rr, vv = cowell( Earth.k, orbit.r, orbit.v, tofs, ad=J2_perturbation, J2=Earth.J2.value, R=Earth.R.to(u.km).value, ) k = Earth.k.to(u.km ** 3 / u.s ** 2).value _, _, _, raan0, argp0, _ = rv2coe(k, r0, v0) _, _, _, raan, argp, _ = rv2coe(k, rr[0].to(u.km).value, vv[0].to(u.km / u.s).value) raan_variation_rate = (raan - raan0) / tofs[0].to(u.s).value argp_variation_rate = (argp - argp0) / tofs[0].to(u.s).value raan_variation_rate = (raan_variation_rate * u.rad / u.s).to(u.deg / u.h) argp_variation_rate = (argp_variation_rate * u.rad / u.s).to(u.deg / u.h) assert_quantity_allclose(raan_variation_rate, -0.172 * u.deg / u.h, rtol=1e-2) assert_quantity_allclose(argp_variation_rate, 0.282 * u.deg / u.h, rtol=1e-2)
def test_J2_propagation_Earth(): # From Curtis example 12.2: r0 = np.array([-2384.46, 5729.01, 3050.46]) # km v0 = np.array([-7.36138, -2.98997, 1.64354]) # km/s orbit = Orbit.from_vectors(Earth, r0 * u.km, v0 * u.km / u.s) tofs = [48.0] * u.h def f(t0, u_, k): du_kep = func_twobody(t0, u_, k) ax, ay, az = J2_perturbation( t0, u_, k, J2=Earth.J2.value, R=Earth.R.to(u.km).value ) du_ad = np.array([0, 0, 0, ax, ay, az]) return du_kep + du_ad rr, vv = cowell(Earth.k, orbit.r, orbit.v, tofs, f=f) k = Earth.k.to(u.km ** 3 / u.s ** 2).value _, _, _, raan0, argp0, _ = rv2coe(k, r0, v0) _, _, _, raan, argp, _ = rv2coe(k, rr[0].to(u.km).value, vv[0].to(u.km / u.s).value) raan_variation_rate = (raan - raan0) / tofs[0].to(u.s).value # type: ignore argp_variation_rate = (argp - argp0) / tofs[0].to(u.s).value # type: ignore raan_variation_rate = (raan_variation_rate * u.rad / u.s).to(u.deg / u.h) argp_variation_rate = (argp_variation_rate * u.rad / u.s).to(u.deg / u.h) assert_quantity_allclose(raan_variation_rate, -0.172 * u.deg / u.h, rtol=1e-2) assert_quantity_allclose(argp_variation_rate, 0.282 * u.deg / u.h, rtol=1e-2)
def test_J2_propagation_Earth(): # from Curtis example 12.2: r0 = np.array([-2384.46, 5729.01, 3050.46]) # km v0 = np.array([-7.36138, -2.98997, 1.64354]) # km/s k = Earth.k.to(u.km**3 / u.s**2).value orbit = Orbit.from_vectors(Earth, r0 * u.km, v0 * u.km / u.s) tof = (48.0 * u.h).to(u.s).value r, v = cowell(orbit, tof, ad=J2_perturbation, J2=Earth.J2.value, R=Earth.R.to(u.km).value) _, _, _, raan0, argp0, _ = rv2coe(k, r0, v0) _, _, _, raan, argp, _ = rv2coe(k, r, v) raan_variation_rate = (raan - raan0) / tof argp_variation_rate = (argp - argp0) / tof raan_variation_rate = (raan_variation_rate * u.rad / u.s).to(u.deg / u.h) argp_variation_rate = (argp_variation_rate * u.rad / u.s).to(u.deg / u.h) assert_quantity_allclose(raan_variation_rate, -0.172 * u.deg / u.h, rtol=1e-2) assert_quantity_allclose(argp_variation_rate, 0.282 * u.deg / u.h, rtol=1e-2)
def test_long_propagation_preserves_orbit_elements(method): tof = 100 * u.year r_halleys = np.array([-9018878.63569932, -94116054.79839276, 22619058.69943215]) # km v_halleys = np.array([-49.95092305, -12.94843055, -4.29251577]) # km/s halleys = Orbit.from_vectors(Sun, r_halleys * u.km, v_halleys * u.km / u.s) params_ini = rv2coe(Sun.k.to(u.km**3 / u.s**2).value, r_halleys, v_halleys)[:-1] r_new, v_new = halleys.propagate(tof, method=method).rv() params_final = rv2coe(Sun.k.to(u.km**3 / u.s**2).value, r_new.to(u.km).value, v_new.to(u.km / u.s).value)[:-1] print(params_ini, params_final) assert_quantity_allclose(params_ini, params_final)
def test_3rd_body_Curtis(test_params): # based on example 12.11 from Howard Curtis body = test_params['body'] solar_system_ephemeris.set('de432s') j_date = 2454283.0 * u.day tof = (test_params['tof']).to(u.s).value body_r = build_ephem_interpolant(body, test_params['period'], (j_date, j_date + test_params['tof']), rtol=1e-2) epoch = Time(j_date, format='jd', scale='tdb') initial = Orbit.from_classical(Earth, *test_params['orbit'], epoch=epoch) r, v = cowell(initial, np.linspace(0, tof, 400), rtol=1e-10, ad=third_body, k_third=body.k.to(u.km**3 / u.s**2).value, third_body=body_r) incs, raans, argps = [], [], [] for ri, vi in zip(r, v): angles = Angle(rv2coe(Earth.k.to(u.km**3 / u.s**2).value, ri, vi)[2:5] * u.rad) # inc, raan, argp angles = angles.wrap_at(180 * u.deg) incs.append(angles[0].value) raans.append(angles[1].value) argps.append(angles[2].value) # averaging over 5 last values in the way Curtis does inc_f, raan_f, argp_f = np.mean(incs[-5:]), np.mean(raans[-5:]), np.mean(argps[-5:]) assert_quantity_allclose([(raan_f * u.rad).to(u.deg) - test_params['orbit'][3], (inc_f * u.rad).to(u.deg) - test_params['orbit'][2], (argp_f * u.rad).to(u.deg) - test_params['orbit'][4]], [test_params['raan'], test_params['inc'], test_params['argp']], rtol=1e-1)
def inertial_body_centered_to_pqw(r, v, source_body): """Converts position and velocity from inertial body-centered frame to perifocal frame. Parameters ---------- r : ~astropy.units.Quantity Position vector in a inertial body-centered reference frame. v : ~astropy.units.Quantity Velocity vector in a inertial body-centered reference frame. source_body : Body Source body. Returns ------- r_pqw, v_pqw : tuple (~astropy.units.Quantity) Position and velocity vectors in ICRS. """ r = r.to("km").value v = v.to("km/s").value k = source_body.k.to("km^3 / s^2").value p, ecc, inc, _, _, nu = rv2coe(k, r, v) r_pqw = (np.array([cos(nu), sin(nu), 0 * nu]) * p / (1 + ecc * cos(nu))).T * u.km v_pqw = (np.array([-sin(nu), (ecc + cos(nu)), 0]) * sqrt(k / p)).T * u.km / u.s return r_pqw, v_pqw
def inertial_body_centered_to_pqw(r, v, source_body): """Converts position and velocity from inertial body-centered frame to perifocal frame. Parameters ---------- r : ~astropy.units.Quantity Position vector in a inertial body-centered reference frame. v : ~astropy.units.Quantity Velocity vector in a inertial body-centered reference frame. source_body : Body Source body. Returns ------- r_pqw, v_pqw : tuple (~astropy.units.Quantity) Position and velocity vectors in ICRS. """ r = r.to('km').value v = v.to('km/s').value k = source_body.k.to('km^3 / s^2').value p, ecc, inc, _, _, nu = rv2coe(k, r, v) r_pqw = (np.array([cos(nu), sin(nu), 0 * nu]) * p / (1 + ecc * cos(nu))).T * u.km v_pqw = (np.array([-sin(nu), (ecc + cos(nu)), 0]) * sqrt(k / p)).T * u.km / u.s return r_pqw, v_pqw
def test_solar_pressure(t_days, deltas_expected, sun_r): # based on example 12.9 from Howard Curtis with solar_system_ephemeris.set("builtin"): j_date = 2_438_400.5 * u.day tof = 600 * u.day epoch = Time(j_date, format="jd", scale="tdb") initial = Orbit.from_classical( Earth, 10085.44 * u.km, 0.025422 * u.one, 88.3924 * u.deg, 45.38124 * u.deg, 227.493 * u.deg, 343.4268 * u.deg, epoch=epoch, ) # in Curtis, the mean distance to Sun is used. In order to validate against it, we have to do the same thing sun_normalized = functools.partial(normalize_to_Curtis, sun_r=sun_r) rr, vv = cowell( Earth.k, initial.r, initial.v, np.linspace(0, (tof).to(u.s).value, 4000) * u.s, rtol=1e-8, ad=radiation_pressure, R=Earth.R.to(u.km).value, C_R=2.0, A_over_m=2e-4 / 100, Wdivc_s=Wdivc_sun.value, star=sun_normalized, ) delta_eccs, delta_incs, delta_raans, delta_argps = [], [], [], [] for ri, vi in zip(rr.to(u.km).value, vv.to(u.km / u.s).value): orbit_params = rv2coe(Earth.k.to(u.km**3 / u.s**2).value, ri, vi) delta_eccs.append(orbit_params[1] - initial.ecc.value) delta_incs.append((orbit_params[2] * u.rad).to(u.deg).value - initial.inc.value) delta_raans.append((orbit_params[3] * u.rad).to(u.deg).value - initial.raan.value) delta_argps.append((orbit_params[4] * u.rad).to(u.deg).value - initial.argp.value) # averaging over 5 last values in the way Curtis does index = int(1.0 * t_days / tof.to(u.day).value * 4000 # type: ignore ) delta_ecc, delta_inc, delta_raan, delta_argp = ( np.mean(delta_eccs[index - 5:index]), np.mean(delta_incs[index - 5:index]), np.mean(delta_raans[index - 5:index]), np.mean(delta_argps[index - 5:index]), ) assert_quantity_allclose( [delta_ecc, delta_inc, delta_raan, delta_argp], deltas_expected, rtol=1e0, # TODO: Excessively low, rewrite test? atol=1e-4, )
def test_solar_pressure(): # based on example 12.9 from Howard Curtis with solar_system_ephemeris.set('builtin'): j_date = 2438400.5 * u.day tof = 600 * u.day sun_r = build_ephem_interpolant(Sun, 365 * u.day, (j_date, j_date + tof), rtol=1e-2) epoch = Time(j_date, format='jd', scale='tdb') drag_force_orbit = [10085.44 * u.km, 0.025422 * u.one, 88.3924 * u.deg, 45.38124 * u.deg, 227.493 * u.deg, 343.4268 * u.deg] initial = Orbit.from_classical(Earth, *drag_force_orbit, epoch=epoch) # in Curtis, the mean distance to Sun is used. In order to validate against it, we have to do the same thing sun_normalized = functools.partial(normalize_to_Curtis, sun_r=sun_r) r, v = cowell(initial, np.linspace(0, (tof).to(u.s).value, 4000), rtol=1e-8, ad=radiation_pressure, R=Earth.R.to(u.km).value, C_R=2.0, A=2e-4, m=100, Wdivc_s=Sun.Wdivc.value, star=sun_normalized) delta_eccs, delta_incs, delta_raans, delta_argps = [], [], [], [] for ri, vi in zip(r, v): orbit_params = rv2coe(Earth.k.to(u.km**3 / u.s**2).value, ri, vi) delta_eccs.append(orbit_params[1] - drag_force_orbit[1].value) delta_incs.append((orbit_params[2] * u.rad).to(u.deg).value - drag_force_orbit[2].value) delta_raans.append((orbit_params[3] * u.rad).to(u.deg).value - drag_force_orbit[3].value) delta_argps.append((orbit_params[4] * u.rad).to(u.deg).value - drag_force_orbit[4].value) # averaging over 5 last values in the way Curtis does for check in solar_pressure_checks: index = int(1.0 * check['t_days'] / tof.to(u.day).value * 4000) delta_ecc, delta_inc, delta_raan, delta_argp = np.mean(delta_eccs[index - 5:index]), \ np.mean(delta_incs[index - 5:index]), np.mean(delta_raans[index - 5:index]), \ np.mean(delta_argps[index - 5:index]) assert_quantity_allclose([delta_ecc, delta_inc, delta_raan, delta_argp], check['deltas_expected'], rtol=1e-1, atol=1e-4)
def test_3rd_body_Curtis(test_params): # based on example 12.11 from Howard Curtis body = test_params['body'] with solar_system_ephemeris.set('builtin'): j_date = 2454283.0 * u.day tof = (test_params['tof']).to(u.s).value body_r = build_ephem_interpolant(body, test_params['period'], (j_date, j_date + test_params['tof']), rtol=1e-2) epoch = Time(j_date, format='jd', scale='tdb') initial = Orbit.from_classical(Earth, *test_params['orbit'], epoch=epoch) r, v = cowell(initial, np.linspace(0, tof, 400), rtol=1e-10, ad=third_body, k_third=body.k.to(u.km**3 / u.s**2).value, third_body=body_r) incs, raans, argps = [], [], [] for ri, vi in zip(r, v): angles = Angle(rv2coe(Earth.k.to(u.km**3 / u.s**2).value, ri, vi)[2:5] * u.rad) # inc, raan, argp angles = angles.wrap_at(180 * u.deg) incs.append(angles[0].value) raans.append(angles[1].value) argps.append(angles[2].value) # averaging over 5 last values in the way Curtis does inc_f, raan_f, argp_f = np.mean(incs[-5:]), np.mean(raans[-5:]), np.mean(argps[-5:]) assert_quantity_allclose([(raan_f * u.rad).to(u.deg) - test_params['orbit'][3], (inc_f * u.rad).to(u.deg) - test_params['orbit'][2], (argp_f * u.rad).to(u.deg) - test_params['orbit'][4]], [test_params['raan'], test_params['inc'], test_params['argp']], rtol=1e-1)
def ORBel_frm_SV(svr, svv): from astropy import units as u from poliastro.bodies import Earth from poliastro.twobody import Orbit from poliastro.core.elements import rv2coe state_vector_r = svr state_vector_v = svv # unit vector conversion r = np.array(state_vector_r) * u.km v = np.array(state_vector_v) * u.km / u.s ss = Orbit.from_vectors(Earth, r, v) #standard_grav_param_earth k = 3.986004418 * 100000 coe = rv2coe(k, r, v) #a,e,i,raom,apom,truean=coe[0,1,2,3,4,5] # print('\n') # print('---------- ORBIT CALCULATIONS ---------------------- \n\n') # print(' SEMI MAJOR AXIS IS -- : '+str(coe[0])+' Meters \n') # print(' ECCENTRICITY IS -- : '+str(coe[1])+' \n') # print(' INCLINATION IS -- : '+str(coe[2])+' radians \n') #unit confirmation? # print(' ARGUMENT OF RA IS -- : '+str(coe[3])+' radians \n') # print(' ARGUMENT OF PERIGEE IS -- : '+str(coe[4])+' radians \n') # print(' TRUE ANAMOLY IS -- : '+str(coe[5])+' radians \n') # print('------------------------------------') # print('\n') return coe
def mikkola(k, r0, v0, tof, rtol=None): """Raw algorithm for Mikkola's Kepler solver. Parameters ---------- k : float Standard gravitational parameter of the attractor. r : ~np.array Position vector. v : ~np.array Velocity vector. tof : float Time of flight. rtol: float This method does not require tolerance since it is non-iterative. Returns ------- rr : ~np.array Final velocity vector. vv : ~np.array Final velocity vector. Note ---- Original paper: https://doi.org/10.1007/BF01235850 """ # Solving for the classical elements p, ecc, inc, raan, argp, nu = rv2coe(k, r0, v0) nu = mikkola_coe(k, p, ecc, inc, raan, argp, nu, tof) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def markley(k, r0, v0, tof): """Solves the kepler problem by a non-iterative method. Relative error is around 1e-18, only limited by machine double-precision errors. Parameters ---------- k : float Standar Gravitational parameter. r0 : ~np.array Initial position vector wrt attractor center. v0 : ~np.array Initial velocity vector. tof : float Time of flight. Returns ------- rr: ~np.array Final position vector. vv: ~np.array Final velocity vector. Note ---- The following algorithm was taken from http://dx.doi.org/10.1007/BF00691917. """ # Solve first for eccentricity and mean anomaly p, ecc, inc, raan, argp, nu = rv2coe(k, r0, v0) nu = markley_coe(k, p, ecc, inc, raan, argp, nu, tof) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def test_solar_pressure(): # based on example 12.9 from Howard Curtis solar_system_ephemeris.set('de432s') j_date = 2438400.5 * u.day tof = 600 * u.day sun_r = build_ephem_interpolant(Sun, 365 * u.day, (j_date, j_date + tof), rtol=1e-2) epoch = Time(j_date, format='jd', scale='tdb') drag_force_orbit = [10085.44 * u.km, 0.025422 * u.one, 88.3924 * u.deg, 45.38124 * u.deg, 227.493 * u.deg, 343.4268 * u.deg] initial = Orbit.from_classical(Earth, *drag_force_orbit, epoch=epoch) # in Curtis, the mean distance to Sun is used. In order to validate against it, we have to do the same thing sun_normalized = functools.partial(normalize_to_Curtis, sun_r=sun_r) r, v = cowell(initial, np.linspace(0, (tof).to(u.s).value, 4000), rtol=1e-8, ad=radiation_pressure, R=Earth.R.to(u.km).value, C_R=2.0, A=2e-4, m=100, Wdivc_s=Sun.Wdivc.value, star=sun_normalized) delta_as, delta_eccs, delta_incs, delta_raans, delta_argps, delta_hs = [], [], [], [], [], [] for ri, vi in zip(r, v): orbit_params = rv2coe(Earth.k.to(u.km**3 / u.s**2).value, ri, vi) delta_eccs.append(orbit_params[1] - drag_force_orbit[1].value) delta_incs.append((orbit_params[2] * u.rad).to(u.deg).value - drag_force_orbit[2].value) delta_raans.append((orbit_params[3] * u.rad).to(u.deg).value - drag_force_orbit[3].value) delta_argps.append((orbit_params[4] * u.rad).to(u.deg).value - drag_force_orbit[4].value) # averaging over 5 last values in the way Curtis does for check in solar_pressure_checks: index = int(1.0 * check['t_days'] / tof.to(u.day).value * 4000) delta_ecc, delta_inc, delta_raan, delta_argp = np.mean(delta_eccs[index - 5:index]), \ np.mean(delta_incs[index - 5:index]), np.mean(delta_raans[index - 5:index]), \ np.mean(delta_argps[index - 5:index]) assert_quantity_allclose([delta_ecc, delta_inc, delta_raan, delta_argp], check['deltas_expected'], rtol=1e-1, atol=1e-4)
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 hohmann(k, rv, r_f): r"""Calculate the Hohmann maneuver velocities and the duration of the maneuver. 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 ---------- k : float Standard Gravitational parameter rv : numpy.ndarray, numpy.ndarray Position and velocity vectors r_f : float Final orbital radius """ _, ecc, inc, raan, argp, nu = rv2coe(k, *rv) h_i = norm(cross(*rv)) p_i = h_i**2 / k r_i, v_i = rv_pqw(k, p_i, ecc, nu) r_i = norm(r_i) v_i = norm(v_i) a_trans = (r_i + r_f) / 2 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) dv_a = np.array([0, dv_a, 0]) dv_b = np.array([0, -dv_b, 0]) rot_matrix = coe_rotation_matrix(inc, raan, argp) dv_a = rot_matrix @ dv_a dv_b = rot_matrix @ dv_b t_trans = np.pi * np.sqrt(a_trans**3 / k) return dv_a, dv_b, t_trans
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 test_3rd_body_Curtis(test_params): # based on example 12.11 from Howard Curtis body = test_params["body"] with solar_system_ephemeris.set("builtin"): j_date = 2454283.0 * u.day tof = (test_params["tof"]).to(u.s).value body_r = build_ephem_interpolant( body, test_params["period"], (j_date, j_date + test_params["tof"]), rtol=1e-2, ) epoch = Time(j_date, format="jd", scale="tdb") initial = Orbit.from_classical(Earth, *test_params["orbit"], epoch=epoch) rr, vv = cowell( Earth.k, initial.r, initial.v, np.linspace(0, tof, 400) * u.s, rtol=1e-10, ad=third_body, k_third=body.k.to(u.km**3 / u.s**2).value, perturbation_body=body_r, ) incs, raans, argps = [], [], [] for ri, vi in zip(rr.to(u.km).value, vv.to(u.km / u.s).value): angles = Angle( rv2coe(Earth.k.to(u.km**3 / u.s**2).value, ri, vi)[2:5] * u.rad) # inc, raan, argp angles = angles.wrap_at(180 * u.deg) incs.append(angles[0].value) raans.append(angles[1].value) argps.append(angles[2].value) # averaging over 5 last values in the way Curtis does inc_f, raan_f, argp_f = ( np.mean(incs[-5:]), np.mean(raans[-5:]), np.mean(argps[-5:]), ) assert_quantity_allclose( [ (raan_f * u.rad).to(u.deg) - test_params["orbit"][3], (inc_f * u.rad).to(u.deg) - test_params["orbit"][2], (argp_f * u.rad).to(u.deg) - test_params["orbit"][4], ], [test_params["raan"], test_params["inc"], test_params["argp"]], rtol=1e-1, )
def to_classical(self): """Converts to classical orbital elements representation. """ (p, ecc, inc, raan, argp, nu) = rv2coe( self.attractor.k.to(u.km**3 / u.s**2).value, self.r.to(u.km).value, self.v.to(u.km / u.s).value) return classical.ClassicalState(self.attractor, p * u.km, ecc * u.one, inc * u.rad, raan * u.rad, argp * u.rad, nu * u.rad)
def propagate(self, value, method=mean_motion, rtol=1e-10, **kwargs): """Propagates an orbit. If value is true anomaly, propagate orbit to this anomaly and return the result. Otherwise, if time is provided, propagate this `Orbit` some `time` and return the result. Parameters ---------- value : Multiple options True anomaly values or time values. If given an angle, it will always propagate forward. rtol : float, optional Relative tolerance for the propagation algorithm, default to 1e-10. method : function, optional Method used for propagation **kwargs parameters used in perturbation models """ if hasattr(value, "unit") and value.unit in ("rad", "deg"): p, ecc, inc, raan, argp, _ = rv2coe( self.attractor.k.to(u.km**3 / u.s**2).value, self.r.to(u.km).value, self.v.to(u.km / u.s).value, ) # Compute time of flight for correct epoch M = nu_to_M(self.nu, self.ecc) new_M = nu_to_M(value, self.ecc) time_of_flight = Angle(new_M - M).wrap_at(360 * u.deg) / self.n return self.from_classical( self.attractor, p / (1.0 - ecc**2) * u.km, ecc * u.one, inc * u.rad, raan * u.rad, argp * u.rad, value, epoch=self.epoch + time_of_flight, plane=self._plane, ) else: if isinstance(value, time.Time) and not isinstance(value, time.TimeDelta): time_of_flight = value - self.epoch else: time_of_flight = time.TimeDelta(value) return propagate(self, time_of_flight, method=method, rtol=rtol, **kwargs)
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 mean_motion(k, r0, v0, tof): r"""Propagates orbit using mean motion. This algorithm depends on the geometric shape of the orbit. For the case of the strong elliptic or strong hyperbolic orbits: .. math:: M = M_{0} + \frac{\mu^{2}}{h^{3}}\left ( 1 -e^{2}\right )^{\frac{3}{2}}t .. versionadded:: 0.9.0 Parameters ---------- k : float Standar Gravitational parameter r0 : ~astropy.units.Quantity Initial position vector wrt attractor center. v0 : ~astropy.units.Quantity Initial velocity vector. tof : float Time of flight (s). Note ---- This method takes initial :math:`\vec{r}, \vec{v}`, calculates classical orbit parameters, increases mean anomaly and performs inverse transformation to get final :math:`\vec{r}, \vec{v}` The logic is based on formulae (4), (6) and (7) from http://dx.doi.org/10.1007/s10569-013-9476-9 """ # get the initial true anomaly and orbit parameters that are constant over time p, ecc, inc, raan, argp, nu0 = rv2coe(k, r0, v0) # get the initial mean anomaly M0 = nu_to_M(nu0, ecc) # strong elliptic or strong hyperbolic orbits if np.abs(ecc - 1.0) > 1e-2: a = p / (1.0 - ecc ** 2) # given the initial mean anomaly, calculate mean anomaly # at the end, mean motion (n) equals sqrt(mu / |a^3|) M = M0 + tof * np.sqrt(k / np.abs(a ** 3)) nu = M_to_nu(M, ecc) # near-parabolic orbit else: q = p * np.abs(1.0 - ecc) / np.abs(1.0 - ecc ** 2) # mean motion n = sqrt(mu / 2 q^3) for parabolic orbit M = M0 + tof * np.sqrt(k / 2.0 / (q ** 3)) nu = M_to_nu(M, ecc) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def mean_motion(k, r0, v0, tof): r"""Propagates orbit using mean motion. This algorithm depends on the geometric shape of the orbit. For the case of the strong elliptic or strong hyperbolic orbits: .. math:: M = M_{0} + \frac{\mu^{2}}{h^{3}}\left ( 1 -e^{2}\right )^{\frac{3}{2}}t .. versionadded:: 0.9.0 Parameters ---------- k : float Standar Gravitational parameter r0 : ~astropy.units.Quantity Initial position vector wrt attractor center. v0 : ~astropy.units.Quantity Initial velocity vector. tof : float Time of flight (s). Note ---- This method takes initial :math:`\vec{r}, \vec{v}`, calculates classical orbit parameters, increases mean anomaly and performs inverse transformation to get final :math:`\vec{r}, \vec{v}` The logic is based on formulae (4), (6) and (7) from http://dx.doi.org/10.1007/s10569-013-9476-9 """ # get the initial true anomaly and orbit parameters that are constant over time p, ecc, inc, raan, argp, nu0 = rv2coe(k, r0, v0) # get the initial mean anomaly M0 = nu_to_M(nu0, ecc) # strong elliptic or strong hyperbolic orbits if np.abs(ecc - 1.0) > 1e-2: a = p / (1.0 - ecc**2) # given the initial mean anomaly, calculate mean anomaly # at the end, mean motion (n) equals sqrt(mu / |a^3|) M = M0 + tof * np.sqrt(k / np.abs(a**3)) nu = M_to_nu(M, ecc) # near-parabolic orbit else: q = p * np.abs(1.0 - ecc) / np.abs(1.0 - ecc**2) # mean motion n = sqrt(mu / 2 q^3) for parabolic orbit M = M0 + tof * np.sqrt(k / 2.0 / (q**3)) nu = M_to_nu(M, ecc) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def plot_regimes(self, save_path=None, display=True): s = time.time() lla = np.array([ecef2lla(x[:3] @ self.trans_matrix[0]) for x in self.x_true[0]]) coes = np.array(rv2coe(k=Earth.k.to_value(u.km ** 3 / u.s ** 2), r=x[:3] / 1000, v=x[3:] / 1000) for x in self.x_true[0]) x = lla[:, 2] / 1000 y = coes[:, 1] plot_regimes(np.column_stack((x, y)), save_path=save_path, display=display) e = time.time() self.runtime['plot_visibility'] += e - s
def test_3rd_body_Curtis(test_params): # based on example 12.11 from Howard Curtis body = test_params["body"] with solar_system_ephemeris.set("builtin"): j_date = 2454283.0 * u.day tof = (test_params["tof"]).to(u.s).value body_r = build_ephem_interpolant( body, test_params["period"], (j_date, j_date + test_params["tof"]), rtol=1e-2, ) epoch = Time(j_date, format="jd", scale="tdb") initial = Orbit.from_classical(Earth, *test_params["orbit"], epoch=epoch) rr, vv = cowell( Earth.k, initial.r, initial.v, np.linspace(0, tof, 400) * u.s, rtol=1e-10, ad=third_body, k_third=body.k.to(u.km ** 3 / u.s ** 2).value, third_body=body_r, ) incs, raans, argps = [], [], [] for ri, vi in zip(rr.to(u.km).value, vv.to(u.km / u.s).value): angles = Angle( rv2coe(Earth.k.to(u.km ** 3 / u.s ** 2).value, ri, vi)[2:5] * u.rad ) # inc, raan, argp angles = angles.wrap_at(180 * u.deg) incs.append(angles[0].value) raans.append(angles[1].value) argps.append(angles[2].value) # averaging over 5 last values in the way Curtis does inc_f, raan_f, argp_f = ( np.mean(incs[-5:]), np.mean(raans[-5:]), np.mean(argps[-5:]), ) assert_quantity_allclose( [ (raan_f * u.rad).to(u.deg) - test_params["orbit"][3], (inc_f * u.rad).to(u.deg) - test_params["orbit"][2], (argp_f * u.rad).to(u.deg) - test_params["orbit"][4], ], [test_params["raan"], test_params["inc"], test_params["argp"]], rtol=1e-1, )
def test_convert_between_coe_and_rv_is_transitive(): k = Earth.k.to(u.km**3 / u.s**2).value # u.km**3 / u.s**2 p = 11067.790 # u.km ecc = 0.83285 # u.one inc = np.deg2rad(87.87) # u.rad raan = np.deg2rad(227.89) # u.rad argp = np.deg2rad(53.38) # u.rad nu = np.deg2rad(92.335) # u.rad expected_res = (p, ecc, inc, raan, argp, nu) res = rv2coe(k, *coe2rv(k, *expected_res)) assert_allclose(res, expected_res)
def test_propagation_mean_motion_parabolic(): # example from Howard Curtis (3rd edition), section 3.5, problem 3.15 p = 2.0 * 6600 * u.km _a = 0.0 * u.deg orbit = Orbit.parabolic(Earth, p, _a, _a, _a, _a) orbit = orbit.propagate(0.8897 / 2.0 * u.h, method=mean_motion) _, _, _, _, _, nu0 = rv2coe(Earth.k.to(u.km**3 / u.s**2).value, orbit.r.to(u.km).value, orbit.v.to(u.km / u.s).value) assert_quantity_allclose(nu0, np.deg2rad(90.0), rtol=1e-4) orbit = Orbit.parabolic(Earth, p, _a, _a, _a, _a) orbit = orbit.propagate(36.0 * u.h, method=mean_motion) assert_quantity_allclose(norm(orbit.r), 304700.0 * u.km, rtol=1e-4)
def eclipse_function(k, u_, r_sec, R_sec, R_primary, umbra=True): """Calculates a continuous shadow function. Parameters ---------- k: float Standard gravitational parameter (km^3 / s^2). u_: ~np.array Satellite position and velocity vector with respect to the primary body. r_sec: ~np.array Position vector of the secondary body with respect to the primary body. R_sec: float Equatorial radius of the secondary body. R_primary: float Equatorial radius of the primary body. umbra: bool Whether to calculate the shadow function for umbra or penumbra, defaults to True i.e. calculates for umbra. Note ---- The shadow function is taken from Escobal, P. (1985). Methods of orbit determination. The current implementation assumes circular bodies and doesn't account for flattening. """ # Plus or minus condition pm = 1 if umbra else -1 p, ecc, inc, raan, argp, nu = rv2coe(k, u_[:3], u_[3:]) PQW = coe_rotation_matrix(inc, raan, argp) # Make arrays contiguous for faster dot product with numba. P_, Q_ = np.ascontiguousarray(PQW[:, 0]), np.ascontiguousarray(PQW[:, 1]) r_sec_norm = norm(r_sec) beta = np.dot(P_, r_sec) / r_sec_norm zeta = np.dot(Q_, r_sec) / r_sec_norm sin_delta_shadow = np.sin((R_sec - pm * R_primary) / r_sec_norm) cos_psi = beta * np.cos(nu) + zeta * np.sin(nu) shadow_function = (((R_primary**2) * (1 + ecc * np.cos(nu))**2) + (p**2) * (cos_psi**2) - p**2 + pm * (2 * p * R_primary * cos_psi) * (1 + ecc * np.cos(nu)) * sin_delta_shadow) return shadow_function
def to_classical(self): """Converts to classical orbital elements representation. """ (p, ecc, inc, raan, argp, nu ) = rv2coe(self.attractor.k.to(u.km ** 3 / u.s ** 2).value, self.r.to(u.km).value, self.v.to(u.km / u.s).value) return classical.ClassicalState( self.attractor, p * u.km, ecc * u.one, inc * u.rad, raan * u.rad, argp * u.rad, nu * u.rad)
def recseries(k, r0, v0, tof, method="rtol", order=8, numiter=100, rtol=1e-8): """Kepler solver for elliptical orbits with recursive series approximation method. The order of the series is a user defined parameter. Parameters ---------- k : float Standard gravitational parameter of the attractor. r0 : numpy.ndarray Position vector. v0 : numpy.ndarray Velocity vector. tof : float Time of flight. method : str Type of termination method ('rtol','order') order : int, optional Order of recursion, defaults to 8. numiter : int, optional Number of iterations, defaults to 100. rtol : float, optional Relative error for accuracy of the method, defaults to 1e-8. Returns ------- rr : numpy.ndarray Final position vector. vv : numpy.ndarray Final velocity vector. Notes ----- This algorithm uses series discussed in the paper *Recursive solution to Kepler’s problem for elliptical orbits - application in robust Newton-Raphson and co-planar closest approach estimation* with DOI: http://dx.doi.org/10.13140/RG.2.2.18578.58563/1 """ # Solve first for eccentricity and mean anomaly p, ecc, inc, raan, argp, nu = rv2coe(k, r0, v0) nu = recseries_coe(k, p, ecc, inc, raan, argp, nu, tof, method, order, numiter, rtol) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def test_J3_propagation_Earth(test_params): # Nai-ming Qi, Qilong Sun, Yong Yang, (2018) "Effect of J3 perturbation on satellite position in LEO", # Aircraft Engineering and Aerospace Technology, Vol. 90 Issue: 1, # pp.74-86, https://doi.org/10.1108/AEAT-03-2015-0092 a_ini = 8970.667 * u.km ecc_ini = 0.25 * u.one raan_ini = 1.047 * u.rad nu_ini = 0.0 * u.rad argp_ini = 1.0 * u.rad inc_ini = test_params['inc'] k = Earth.k.to(u.km**3 / u.s**2).value orbit = Orbit.from_classical(Earth, a_ini, ecc_ini, inc_ini, raan_ini, argp_ini, nu_ini) tof = (10.0 * u.day).to(u.s).value r_J2, v_J2 = cowell(orbit, np.linspace(0, tof, int(1e+3)), ad=J2_perturbation, J2=Earth.J2.value, R=Earth.R.to(u.km).value, rtol=1e-8) a_J2J3 = lambda t0, u_, k_: J2_perturbation(t0, u_, k_, J2=Earth.J2.value, R=Earth.R.to(u.km).value) + \ J3_perturbation(t0, u_, k_, J3=Earth.J3.value, R=Earth.R.to(u.km).value) r_J3, v_J3 = cowell(orbit, np.linspace(0, tof, int(1e+3)), ad=a_J2J3, rtol=1e-8) a_values_J2 = np.array([rv2coe(k, ri, vi)[0] / (1.0 - rv2coe(k, ri, vi)[1] ** 2) for ri, vi in zip(r_J2, v_J2)]) a_values_J3 = np.array([rv2coe(k, ri, vi)[0] / (1.0 - rv2coe(k, ri, vi)[1] ** 2) for ri, vi in zip(r_J3, v_J3)]) da_max = np.max(np.abs(a_values_J2 - a_values_J3)) ecc_values_J2 = np.array([rv2coe(k, ri, vi)[1] for ri, vi in zip(r_J2, v_J2)]) ecc_values_J3 = np.array([rv2coe(k, ri, vi)[1] for ri, vi in zip(r_J3, v_J3)]) decc_max = np.max(np.abs(ecc_values_J2 - ecc_values_J3)) inc_values_J2 = np.array([rv2coe(k, ri, vi)[2] for ri, vi in zip(r_J2, v_J2)]) inc_values_J3 = np.array([rv2coe(k, ri, vi)[2] for ri, vi in zip(r_J3, v_J3)]) dinc_max = np.max(np.abs(inc_values_J2 - inc_values_J3)) assert_quantity_allclose(dinc_max, test_params['dinc_max'], rtol=1e-1, atol=1e-7) assert_quantity_allclose(decc_max, test_params['decc_max'], rtol=1e-1, atol=1e-7) try: assert_quantity_allclose(da_max * u.km, test_params['da_max']) except AssertionError as exc: pytest.xfail('this assertion disagrees with the paper')
def test_rv2coe_iss_exact(engine, earth_elliptic): r, v = earth_elliptic expected_result = engine.rv2coe(matlab.double(r), matlab.double(v), nargout=11) p, _, ecc, inc, omega, argp, nu, *_ = expected_result # Load constants to make all computations equivalent engine.constastro(nargout=0) k = engine.workspace["mu"] result = rv2coe(k, np.array(r), np.array(v)) assert result[0] == p assert result[1] == ecc assert result[2] == inc assert result[3] == omega assert result[4] == argp assert result[5] == nu
def mean_motion(k, r0, v0, tof): r"""Propagates orbit using mean motion .. versionadded:: 0.9.0 Parameters ---------- orbit : ~poliastro.twobody.orbit.Orbit the Orbit object to propagate. tof : float Time of flight (s). Notes ----- This method takes initial :math:`\vec{r}, \vec{v}`, calculates classical orbit parameters, increases mean anomaly and performs inverse transformation to get final :math:`\vec{r}, \vec{v}` The logic is based on formulae (4), (6) and (7) from http://dx.doi.org/10.1007/s10569-013-9476-9 """ # get the initial true anomaly and orbit parameters that are constant over time p, ecc, inc, raan, argp, nu0 = rv2coe(k, r0, v0) # get the initial mean anomaly M0 = nu_to_M(nu0, ecc) # strong elliptic or strong hyperbolic orbits if np.abs(ecc - 1.0) > 1e-2: a = p / (1.0 - ecc**2) # given the initial mean anomaly, calculate mean anomaly # at the end, mean motion (n) equals sqrt(mu / |a^3|) M = M0 + tof * np.sqrt(k / np.abs(a**3)) nu = M_to_nu(M, ecc) # near-parabolic orbit else: q = p * np.abs(1.0 - ecc) / np.abs(1.0 - ecc**2) # mean motion n = sqrt(mu / 2 q^3) for parabolic orbit M = M0 + tof * np.sqrt(k / 2.0 / (q**3)) nu = M_to_nu(M, ecc) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def propagate(self, value, method=mean_motion, rtol=1e-10, **kwargs): """Propagates an orbit. If value is true anomaly, propagate orbit to this anomaly and return the result. Otherwise, if time is provided, propagate this `Orbit` some `time` and return the result. Parameters ---------- value : Multiple options True anomaly values, Time values. rtol : float, optional Relative tolerance for the propagation algorithm, default to 1e-10. method : function, optional Method used for propagation **kwargs parameters used in perturbation models """ if hasattr(value, "unit") and value.unit in ('rad', 'deg'): p, ecc, inc, raan, argp, _ = rv2coe( self.attractor.k.to(u.km**3 / u.s**2).value, self.r.to(u.km).value, self.v.to(u.km / u.s).value) return self.from_classical(self.attractor, p / (1.0 - ecc**2) * u.km, ecc * u.one, inc * u.rad, raan * u.rad, argp * u.rad, value) else: if isinstance(value, time.Time) and not isinstance(value, time.TimeDelta): time_of_flight = value - self.epoch else: time_of_flight = time.TimeDelta(value) return propagate(self, time_of_flight, method=method, rtol=rtol, **kwargs)
def mean_motion(k, r0, v0, tof): r"""Propagates orbit using mean motion .. versionadded:: 0.9.0 Parameters ---------- orbit : ~poliastro.twobody.orbit.Orbit the Orbit object to propagate. tof : float Time of flight (s). Notes ----- This method takes initial :math:`\vec{r}, \vec{v}`, calculates classical orbit parameters, increases mean anomaly and performs inverse transformation to get final :math:`\vec{r}, \vec{v}` The logic is based on formulae (4), (6) and (7) from http://dx.doi.org/10.1007/s10569-013-9476-9 """ # get the initial true anomaly and orbit parameters that are constant over time p, ecc, inc, raan, argp, nu0 = rv2coe(k, r0, v0) # get the initial mean anomaly M0 = nu_to_M(nu0, ecc) # strong elliptic or strong hyperbolic orbits if np.abs(ecc - 1.0) > 1e-2: a = p / (1.0 - ecc ** 2) # given the initial mean anomaly, calculate mean anomaly # at the end, mean motion (n) equals sqrt(mu / |a^3|) M = M0 + tof * np.sqrt(k / np.abs(a ** 3)) nu = M_to_nu(M, ecc) # near-parabolic orbit else: q = p * np.abs(1.0 - ecc) / np.abs(1.0 - ecc ** 2) # mean motion n = sqrt(mu / 2 q^3) for parabolic orbit M = M0 + tof * np.sqrt(k / 2.0 / (q ** 3)) nu = M_to_nu(M, ecc) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def test_rv2coe_iss_approximate(engine, earth_elliptic): r, v = earth_elliptic rtol = atol = 1e-13 expected_result = engine.rv2coe(matlab.double(r), matlab.double(v), nargout=11) p, _, ecc, inc, omega, argp, nu, *_ = expected_result # Load constants to make all computations equivalent engine.constastro(nargout=0) k = engine.workspace["mu"] result = rv2coe(k, np.array(r), np.array(v)) assert result[0] == approx(p, rel=rtol, abs=atol) assert result[1] == approx(ecc, rel=rtol, abs=atol) assert result[2] == approx(inc, rel=rtol, abs=atol) assert result[3] == approx(omega, rel=rtol, abs=atol) assert result[4] == approx(argp, rel=rtol, abs=atol) assert result[5] == approx(nu, rel=rtol, abs=atol)
def test_propagation_parabolic(propagator): # example from Howard Curtis (3rd edition), section 3.5, problem 3.15 # TODO: add parabolic solver in some parabolic propagators, refer to #417 if propagator in [mikkola, gooding]: pytest.skip() p = 2.0 * 6600 * u.km _a = 0.0 * u.deg orbit = Orbit.parabolic(Earth, p, _a, _a, _a, _a) orbit = orbit.propagate(0.8897 / 2.0 * u.h, method=propagator) _, _, _, _, _, nu0 = rv2coe( Earth.k.to(u.km ** 3 / u.s ** 2).value, orbit.r.to(u.km).value, orbit.v.to(u.km / u.s).value, ) assert_quantity_allclose(nu0, np.deg2rad(90.0), rtol=1e-4) orbit = Orbit.parabolic(Earth, p, _a, _a, _a, _a) orbit = orbit.propagate(36.0 * u.h, method=propagator) assert_quantity_allclose(norm(orbit.r), 304700.0 * u.km, rtol=1e-4)
def propagate(self, value, method=mean_motion, rtol=1e-10, **kwargs): """Propagates an orbit. If value is true anomaly, propagate orbit to this anomaly and return the result. Otherwise, if time is provided, propagate this `Orbit` some `time` and return the result. Parameters ---------- value : Multiple options True anomaly values or time values. If given an angle, it will always propagate forward. rtol : float, optional Relative tolerance for the propagation algorithm, default to 1e-10. method : function, optional Method used for propagation **kwargs parameters used in perturbation models """ if hasattr(value, "unit") and value.unit in ('rad', 'deg'): p, ecc, inc, raan, argp, _ = rv2coe(self.attractor.k.to(u.km ** 3 / u.s ** 2).value, self.r.to(u.km).value, self.v.to(u.km / u.s).value) # Compute time of flight for correct epoch M = nu_to_M(self.nu, self.ecc) new_M = nu_to_M(value, self.ecc) time_of_flight = Angle(new_M - M).wrap_at(360 * u.deg) / self.n return self.from_classical(self.attractor, p / (1.0 - ecc ** 2) * u.km, ecc * u.one, inc * u.rad, raan * u.rad, argp * u.rad, value, epoch=self.epoch + time_of_flight, plane=self._plane) else: if isinstance(value, time.Time) and not isinstance(value, time.TimeDelta): time_of_flight = value - self.epoch else: time_of_flight = time.TimeDelta(value) return propagate(self, time_of_flight, method=method, rtol=rtol, **kwargs)
def danby(k, r0, v0, tof, numiter=20, rtol=1e-8): """Kepler solver for both elliptic and parabolic orbits based on Danby's algorithm. Parameters ---------- k : float Standard gravitational parameter of the attractor. r0 : ~np.array Position vector. v0 : ~np.array Velocity vector. tof : float Time of flight. numiter: int, optional Number of iterations, defaults to 20. rtol: float, optional Relative error for accuracy of the method, defaults to 1e-8. Returns ------- rr : ~np.array Final position vector. vv : ~np.array Final velocity vector. Note ---- This algorithm was developed by Danby in his paper *The solution of Kepler Equation* with DOI: https://doi.org/10.1007/BF01686811 """ # Solve first for eccentricity and mean anomaly p, ecc, inc, raan, argp, nu = rv2coe(k, r0, v0) nu = danby_coe(k, p, ecc, inc, raan, argp, nu, tof, numiter, rtol) return coe2rv(k, p, ecc, inc, raan, argp, nu)
def propagate_to_anomaly(self, value): """Propagates an orbit to a specific true anomaly. Parameters ---------- value : ~astropy.units.Quantity Returns ------- Orbit Resulting orbit after propagation. """ # TODO: Avoid repeating logic with mean_motion? p, ecc, inc, raan, argp, _ = rv2coe( self.attractor.k.to(u.km ** 3 / u.s ** 2).value, self.r.to(u.km).value, self.v.to(u.km / u.s).value, ) # Compute time of flight for correct epoch M = nu_to_M(self.nu, self.ecc) new_M = nu_to_M(value, self.ecc) time_of_flight = Angle(new_M - M).wrap_at(360 * u.deg) / self.n return self.from_classical( self.attractor, p / (1.0 - ecc ** 2) * u.km, ecc * u.one, inc * u.rad, raan * u.rad, argp * u.rad, value, epoch=self.epoch + time_of_flight, plane=self.plane, )
def test_convert_between_coe_and_rv_is_transitive(classical): k = Earth.k.to(u.km ** 3 / u.s ** 2).value # u.km**3 / u.s**2 res = rv2coe(k, *coe2rv(k, *classical)) assert_allclose(res, classical)
def test_convert_coe_and_rv_hyperbolic(hyperbolic): k, expected_res = hyperbolic res = rv2coe(k, *coe2rv(k, *expected_res)) assert_allclose(res, expected_res, atol=1e-8)
def test_convert_coe_and_rv_circular_equatorial(circular_equatorial): k, expected_res = circular_equatorial res = rv2coe(k, *coe2rv(k, *expected_res)) assert_allclose(res, expected_res, atol=1e-8)