def _generate_time_values(self, nu_vals): # Subtract current anomaly to start from the desired point M_vals = np.array([ nu_to_M(nu_val, self.ecc).value - nu_to_M(self.nu, self.ecc).value for nu_val in nu_vals ]) * u.rad time_values = self.epoch + (M_vals / self.n).decompose() return time_values
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 test_orbit_from_spk_id_has_proper_values(mock_get, mock_response): mock_orbital_data = { 'orbital_data': { 'eccentricity': '.2225889698301071', 'semi_major_axis': '1.457940027185708', 'inclination': '10.82759100494802', 'ascending_node_longitude': '304.3221633898424', 'perihelion_argument': '178.8165910886752', 'mean_anomaly': '71.28027812836476', 'epoch_osculation': '2458000.5', } } mock_response.json.return_value = mock_orbital_data mock_get.return_value = mock_response ss = neows.orbit_from_spk_id('') assert ss.ecc == mock_orbital_data['orbital_data']['eccentricity'] * u.one assert ss.a == mock_orbital_data['orbital_data']['semi_major_axis'] * u.AU assert ss.inc == mock_orbital_data['orbital_data']['inclination'] * u.deg assert ss.raan == mock_orbital_data['orbital_data'][ 'ascending_node_longitude'] * u.deg assert ss.argp == mock_orbital_data['orbital_data'][ 'perihelion_argument'] * u.deg assert nu_to_M( ss.nu, ss.ecc) == mock_orbital_data['orbital_data']['mean_anomaly'] * u.deg
def test_true_to_mean(): # Data from Schlesinger & Udick, 1912 data = [ # ecc, M (deg), nu (deg) (0.0, 0.0, 0.0), (0.05, 10.0, 11.06), (0.06, 30.0, 33.67), (0.04, 120.0, 123.87), (0.14, 65.0, 80.50), (0.19, 21.0, 30.94), (0.35, 65.0, 105.71), (0.48, 180.0, 180.0), (0.75, 125.0, 167.57) ] for row in data: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = angles.nu_to_M(nu, ecc) assert_almost_equal(M.to(u.rad).value, expected_M.to(u.rad).value, decimal=3)
def test_orbit_from_spk_id_has_proper_values(mock_get, mock_response): mock_orbital_data = { "orbital_data": { "eccentricity": ".2225889698301071", "semi_major_axis": "1.457940027185708", "inclination": "10.82759100494802", "ascending_node_longitude": "304.3221633898424", "perihelion_argument": "178.8165910886752", "mean_anomaly": "71.28027812836476", "epoch_osculation": "2458000.5", } } mock_response.json.return_value = mock_orbital_data mock_get.return_value = mock_response ss = neows.orbit_from_spk_id("") assert ss.frame.is_equivalent_frame( HeliocentricEclipticJ2000(obstime=ss.epoch)) assert ss.ecc == mock_orbital_data["orbital_data"]["eccentricity"] * u.one assert ss.a == mock_orbital_data["orbital_data"]["semi_major_axis"] * u.AU assert ss.inc == mock_orbital_data["orbital_data"]["inclination"] * u.deg assert (ss.raan == mock_orbital_data["orbital_data"]["ascending_node_longitude"] * u.deg) assert ss.argp == mock_orbital_data["orbital_data"][ "perihelion_argument"] * u.deg assert (nu_to_M( ss.nu, ss.ecc) == mock_orbital_data["orbital_data"]["mean_anomaly"] * u.deg)
def test_true_to_mean(): for row in ANGLES_DATA: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = angles.nu_to_M(nu, ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def test_true_to_mean_hyperbolic(): # Data from Curtis, H. (2013). *Orbital mechanics for engineering students*. # Example 3.5 nu = 100 * u.deg ecc = 2.7696 * u.one expected_M = 11.279 * u.rad M = angles.nu_to_M(nu, ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def test_true_to_mean(): for row in ANGLES_DATA: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = angles.nu_to_M(nu, ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def test_true_to_mean_hyperbolic(): # Data from Curtis, H. (2013). *Orbital mechanics for engineering students*. # Example 3.5 nu = 100 * u.deg ecc = 2.7696 * u.one expected_M = 11.279 * u.rad M = angles.nu_to_M(nu, ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def time_to_anomaly(self, value): """ Returns time required to be in a specific true anomaly. Parameters ---------- value : ~astropy.units.Quantity Returns ------- tof: ~astropy.units.Quantity Time of flight required. """ # Compute time of flight for correct epoch M = nu_to_M(self.nu, self.ecc) new_M = nu_to_M(value, self.ecc) tof = Angle(new_M - M).wrap_at(360 * u.deg) / self.n return tof
def mean_motion(k, r0, v0, tof, **kwargs): r"""Propagates orbit using mean motion Parameters ---------- k : float Gravitational constant of main attractor (km^3 / s^2). r0 : array Initial position (km). v0 : array Initial velocity (km). ad : function(t0, u, k), optional Non Keplerian acceleration (km/s2), default to None. 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) # elliptic or hyperbolic orbits if not np.isclose(ecc, 1.0, rtol=1e-06): 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|) with u.set_enabled_equivalencies(u.dimensionless_angles()): M = M0 + tof * np.sqrt(k / np.abs(a**3)) * u.rad nu = M_to_nu(M, ecc) # parabolic orbit else: q = p / 2.0 # mean motion n = sqrt(mu / 2 q^3) for parabolic orbit with u.set_enabled_equivalencies(u.dimensionless_angles()): M = M0 + tof * np.sqrt(k / (2.0 * q**3)) # using Barker's equation, which is solved analytically # for parabolic orbit, get true anomaly B = 3.0 * M / 2.0 A = (B + np.sqrt(1.0 + B**2))**(2.0 / 3.0) D = 2.0 * A * B / (1.0 + A + A**2) nu = 2.0 * np.arctan(D) with u.set_enabled_equivalencies(u.dimensionless_angles()): return coe2rv(k, p, ecc, inc, raan, argp, nu)
def Earth_to_Jupiter(delta_v): """ Calculates the manuever and the launch windows from Earth to Jupiter, assuming tangential impulsive maneuver from Earth orbit around the Sun Earth_to_Jupiter(dv): 'dv' is given with velocity units (u.m/u.s) Returns: ss_transfer = trasfer orbit object t = time to the first next launch window T = period between possible launch windows t_transfer = time of transfer from launch to Jupiter arrival """ dv = delta_v * v_Earth_dir # Initial transfer orbit calculation (to obtain transfer time) man_tmp = Maneuver.impulse(dv) transfer = ss_Earth.apply_maneuver(man_tmp) # Angles calculation # nu_Jupiter: nu of transfer orbit where it crosses Jupiter nu_Jupiter = (np.math.acos((transfer.p/R_J - 1)/transfer.ecc)*u.rad) ang_vel_Earth = (norm(v_Earth).value / R_E.value)*u.rad/u.s ang_vel_Jupiter = (norm(v_Jupiter).value / R_J.value)*u.rad/u.s # Transfer time M = nu_to_M(nu_Jupiter, transfer.ecc) t_transfer = M/transfer.n # Delta_nu at launch (Final nu - nu that Jupiter walks during transfer) delta_nu_at_launch = nu_Jupiter - ang_vel_Jupiter * t_transfer # Angles in radians nu_init = angle(r_Earth, r_Jupiter)*u.rad # Next launch window in t: t = (nu_init - delta_nu_at_launch) / (ang_vel_Earth - ang_vel_Jupiter) # Time between windows T = (2*np.pi*u.rad) / (ang_vel_Earth - ang_vel_Jupiter) # If time to the windows is negative (in the past), we should wait until next window while t < 0: t = t + T # Earth at launch ss_Earth_launch = ss_Earth.propagate(t) launch_dir = ss_Earth_launch.v / norm(ss_Earth_launch.v) dv = delta_v * launch_dir # Maneuver calculation man = Maneuver((t, dv)) ss_transfer = ss_Earth.apply_maneuver(man) return ss_transfer, t, T, t_transfer
def mean_motion(orbit, tof, **kwargs): 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 """ k = orbit.attractor.k.to(u.km**3 / u.s**2).value r0 = orbit.r.to(u.km).value v0 = orbit.v.to(u.km / u.s).value # 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) # elliptic or hyperbolic orbits if not np.isclose(ecc, 1.0, rtol=1e-06): 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|) with u.set_enabled_equivalencies(u.dimensionless_angles()): M = M0 + tof * np.sqrt(k / np.abs(a**3)) * u.rad nu = M_to_nu(M, ecc) # parabolic orbit else: q = p / 2.0 # mean motion n = sqrt(mu / 2 q^3) for parabolic orbit with u.set_enabled_equivalencies(u.dimensionless_angles()): M = M0 + tof * np.sqrt(k / (2.0 * q**3)) # using Barker's equation, which is solved analytically # for parabolic orbit, get true anomaly B = 3.0 * M / 2.0 A = (B + np.sqrt(1.0 + B**2))**(2.0 / 3.0) D = 2.0 * A * B / (1.0 + A + A**2) nu = 2.0 * np.arctan(D) with u.set_enabled_equivalencies(u.dimensionless_angles()): 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 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 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 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_true_to_mean(): # Data from Schlesinger & Udick, 1912 data = [ # ecc, M (deg), nu (deg) (0.0, 0.0, 0.0), (0.05, 10.0, 11.06), (0.06, 30.0, 33.67), (0.04, 120.0, 123.87), (0.14, 65.0, 80.50), (0.19, 21.0, 30.94), (0.35, 65.0, 105.71), (0.48, 180.0, 180.0), (0.75, 125.0, 167.57) ] for row in data: ecc, expected_M, nu = row M = angles.nu_to_M(np.deg2rad(nu), ecc) assert_almost_equal(M, np.deg2rad(expected_M), decimal=3)
def test_true_to_mean(): # Data from Schlesinger & Udick, 1912 data = [ # ecc, M (deg), nu (deg) (0.0, 0.0, 0.0), (0.05, 10.0, 11.06), (0.06, 30.0, 33.67), (0.04, 120.0, 123.87), (0.14, 65.0, 80.50), (0.19, 21.0, 30.94), (0.35, 65.0, 105.71), (0.48, 180.0, 180.0), (0.75, 125.0, 167.57) ] for row in data: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = angles.nu_to_M(nu, ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def test_true_to_mean(): # Data from Schlesinger & Udick, 1912 data = [ # ecc, M (deg), nu (deg) (0.0, 0.0, 0.0), (0.05, 10.0, 11.06), (0.06, 30.0, 33.67), (0.04, 120.0, 123.87), (0.14, 65.0, 80.50), (0.19, 21.0, 30.94), (0.35, 65.0, 105.71), (0.48, 180.0, 180.0), (0.75, 125.0, 167.57) ] for row in data: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = angles.nu_to_M(nu, ecc) assert_quantity_allclose(M, expected_M, rtol=1e-4)
def test_true_to_mean(): # Data from Schlesinger & Udick, 1912 data = [ # ecc, M (deg), nu (deg) (0.0, 0.0, 0.0), (0.05, 10.0, 11.06), (0.06, 30.0, 33.67), (0.04, 120.0, 123.87), (0.14, 65.0, 80.50), (0.19, 21.0, 30.94), (0.35, 65.0, 105.71), (0.48, 180.0, 180.0), (0.75, 125.0, 167.57) ] for row in data: ecc, expected_M, nu = row ecc = ecc * u.one expected_M = expected_M * u.deg nu = nu * u.deg M = angles.nu_to_M(nu, ecc) assert_almost_equal(M.to(u.rad).value, expected_M.to(u.rad).value, decimal=3)
def test_mean_to_true_hyperbolic_highecc(expected_nu, ecc): M = angles.nu_to_M(expected_nu, ecc) print(M, ecc, M.value, ecc.value) nu = angles.M_to_nu(M, ecc) assert_quantity_allclose(nu, expected_nu, rtol=1e-4)
def test_mean_to_true_hyperbolic_highecc(expected_nu, ecc): M = angles.nu_to_M(expected_nu, ecc) print(M, ecc, M.value, ecc.value) nu = angles.M_to_nu(M, ecc) assert_quantity_allclose(nu, expected_nu, rtol=1e-4)
def _generate_time_values(self, nu_vals): # Subtract current anomaly to start from the desired point M_vals = nu_to_M(nu_vals, self.ecc) - nu_to_M(self.nu, self.ecc) time_values = self.epoch + (M_vals / self.n).decompose() return time_values
def _generate_time_values(self, nu_vals): M_vals = nu_to_M(nu_vals, self.ecc) time_values = self.epoch + (M_vals / self.n).decompose() return time_values
def M(self): """Mean anomaly. """ return nu_to_M(self.nu, self.ecc)