def _run_propagation(self, init_coords, time_list): """Solves the ODE or runs the actual propagation. Parameters ---------- init_coords : SkyCoord Initial coordinates (the first value is used) time_list : Time Time list for outputs Returns ------- SkyCoord The output coordinates corresponding to `time_list` """ # generate the time list since epoch (in seconds) t_list_epoch = (time_list - time_list[0]).to_value(u.s) # concat the init vector rv_init = [ *init_coords.cartesian.without_differentials().xyz.to_value(u.km), *init_coords.velocity.d_xyz.to_value(u.km / u.s), ] # noinspection PyTypeChecker solver = solve_ivp( self._ode_diff_eqns, (t_list_epoch[0], t_list_epoch[-1]), rv_init, method=self._solver_type.value, dense_output=True, rtol=self.rtol, atol=self.atol, ) r_list = np.empty((3, len(t_list_epoch))) v_list = np.empty((3, len(t_list_epoch))) # Fill the raw output data i = 0 for t in t_list_epoch: rv = solver.sol(t) r_list[:, i] = rv[:3] v_list[:, i] = rv[3:] i = i + 1 # Load the time, pos, vel info into astropy objects (shallow copied) rv_list = CartesianRepresentation( r_list, unit=u.km).with_differentials( CartesianDifferential(v_list, unit=u.km / u.s)) coords_list = init_pvt(self.central_body.inert_coord, time_list, rv_list, copy=False) return coords_list
def __call__(self, t): """ Computes the interpolated coordinates at the given time(s). Parameters ---------- t : Time Time or list of times where an interpolated coordinate is required Returns ------- coords : SkyCoord A `SkyCoord` object with the requested coordinate(s), in the original frame Raises ------ ValueError If the requested `t` value is out of bounds for the interpolator """ if not self._interpolators_initialised: # Lazy init interpolators only when required self._init_interpolators() # **** get coords at requested time **** # compute raw r and v vectors and # fill Astropy cartesian vectors (with velocities if available) if t.isscalar: # there is a single time instance coords = CartesianRepresentation(self._compute_pos(t), copy=False) if self._has_velocity: v = CartesianDifferential(self._compute_vel(t), copy=False) coords = coords.with_differentials(v) else: # there is more than one time instance r = [self._compute_pos(time) for time in t] coords = CartesianRepresentation( np.asarray(r), unit=r[0].unit, copy=False, xyz_axis=1 ) if self._has_velocity: v = [self._compute_vel(time) for time in t] v = CartesianDifferential( np.asarray(v), unit=v[0].unit, copy=False, xyz_axis=1 ) coords = coords.with_differentials(v) return init_pvt(self._frame_name, t, coords)
def test_init_cart(): """Tests the cartesian initialisation convenience method.""" rv_gcrs_true_sky = SkyCoord(rv_gcrs_true) # easy init rv_gcrs_0 = init_pvt(GCRS, time, r_gcrs_true, v_gcrs_true) # init with str frame name (should be lowercase letters) rv_gcrs_1 = init_pvt("GCRS", time, r_gcrs_true, v_gcrs_true) # init without velocity rv_gcrs_2 = init_pvt(GCRS, time, r_gcrs_true, copy=False) assert rv_gcrs_0 == rv_gcrs_true_sky assert rv_gcrs_1 == rv_gcrs_true_sky assert rv_gcrs_2 == SkyCoord( GCRS( r_gcrs_true, obstime=time, representation_type="cartesian", differential_type="cartesian", ))
def test_cb_crs_to_icrs_sun(): """Check the basic quasi-round-trip accuracy for the generic Central Body Celestial Reference System, using Sun (equal to HCRS). Converts Sun CRS to ICRS, then ICRS to HCRS.""" allowable_pos_diff = 5e-4 * u.mm allowable_vel_diff = 0.0001 * u.mm / u.s rv_suncrs_true = init_pvt(_SunCRS, time, r_gcrs_true.with_differentials(v_gcrs_true)) rv_icrs = rv_suncrs_true.transform_to(ICRS) rv_hcrs = rv_icrs.transform_to(HCRS) r_diff = pos_err(rv_hcrs, rv_suncrs_true) v_diff = vel_err(rv_hcrs, rv_suncrs_true) # print(f"r {rv_hcrs.name} diff : {r_diff}") # print(f"v {rv_hcrs.name} diff : {v_diff}") assert r_diff < allowable_pos_diff assert v_diff < allowable_vel_diff
def parse_rv_line(rv_line, coord_sys): """Converts a line of t, r, v string into a SkyCoord object.""" rv_items = [elem for elem in rv_line.strip().split(" ") if elem] time = Time(rv_items[0], scale="utc") v = CartesianDifferential( [float(rv_items[4]), float(rv_items[5]), float(rv_items[6])], unit=u.km / u.s, ) r = CartesianRepresentation( [float(rv_items[1]), float(rv_items[2]), float(rv_items[3])], unit=u.km) rv_init = init_pvt(coord_sys, time, r.with_differentials(v)) return rv_init
def propagation_engine(line1, line2, time_list): """Tests the interpolation accuracy for a given TLE.""" # Init satellite object from the TLE sat = Satrec.twoline2rv(line1, line2) # ****** Generate the pos, vel vectors for each time instant ****** # Run the propagation and init pos and vel vectors in TEME e, r_list, v_list = sat.sgp4_array(time_list.jd1, time_list.jd2) # Load the time, pos, vel info into astropy objects (shallow copied) vel_list = CartesianDifferential(v_list, unit=u.km / u.s, xyz_axis=1) pos_list = CartesianRepresentation(r_list, unit=u.km, xyz_axis=1).with_differentials(vel_list) # trajectory in astropy traj_astropy = init_pvt(TEME, time_list, pos_list) # Init trajectory in Trajectory object trajectory = Trajectory(traj_astropy) return {"traj": trajectory, "sat": sat}
def test_equatorial_mars(): """NASA HORIZONS/GMAT testing between CRS and TOD & J2000 Equatorial as well as Body Fixed (GMAT). GMAT and HORIZONS use a different model in the IAU polar rotation parameters (IAU 2000). Therefore test setup uses this version but not IAU 2015 versions. Satellite orbit is taken from Mars Reconnaissance Orbiter, using the NASA Horizons Web Interface (https://ssd.jpl.nasa.gov/horizons.cgi).""" obstime = Time("2020-01-10T11:30:00.0003", scale="tdb") v_eq_horz = CartesianDifferential( [-2.061955207079347, -2.671302888480574, 0.269551765393186], unit=u.km / u.s, ) r_eq_horz = CartesianRepresentation( [322.259677663235, 120.3781499221643, 3676.074343158492], unit=u.km) rv_eq_horz = init_pvt(_MarsIAU2000TODEquatorial, obstime, r_eq_horz, v_eq_horz) v_inert_horz = CartesianDifferential( [-2.062805178080304, -2.670750348642094, 0.2685217398016297], unit=u.km / u.s, ) r_inert_horz = CartesianRepresentation( [321.472501283011, 119.500545946684, 3676.171898278387], unit=u.km) rv_inert_horz = init_pvt(_MarsIAU2000J2000Equatorial, obstime, r_inert_horz, v_inert_horz) v_body_fixed_gmat = CartesianDifferential( [0.622659762445902, 3.329753468204519, 0.2695517652769852], unit=u.km / u.s, ) r_body_fixed_gmat = CartesianRepresentation( [-233.7124768823157, -252.4295484193542, 3676.074343157078], unit=u.km) rv_body_fixed_gmat = init_pvt(_MarsIAU2000BodyFixed, obstime, r_body_fixed_gmat, v_body_fixed_gmat) allowable_pos_diff = 800 * u.mm allowable_vel_diff = 0.35 * u.mm / u.s # init CRS - modify frame name to point at test coords pvt_crs = _init_orbit_mars() pvt_crs.frame.body_name = "Mars_IAU_2000" # Convert to TOD Equatorial pvt_tod_eq = pvt_crs.transform_to(_MarsIAU2000TODEquatorial) # Convert to J2000 Equatorial pvt_j2000_eq = pvt_crs.transform_to(_MarsIAU2000J2000Equatorial) # Convert to Body Fixed pvt_body_fixed = pvt_crs.transform_to(_MarsIAU2000BodyFixed) print(pvt_crs) print(pvt_tod_eq) print(pvt_j2000_eq) print(pvt_body_fixed) print(pos_err(pvt_body_fixed, rv_body_fixed_gmat)) print(vel_err(pvt_body_fixed, rv_body_fixed_gmat)) # Diff between TOD Eq values assert pos_err(pvt_tod_eq, rv_eq_horz) < allowable_pos_diff assert vel_err(pvt_tod_eq, rv_eq_horz) < allowable_vel_diff # Diff between J2000 Eq (Inertial) values assert pos_err(pvt_j2000_eq, rv_inert_horz) < allowable_pos_diff assert vel_err(pvt_j2000_eq, rv_inert_horz) < allowable_vel_diff # Diff between Body Fixed values assert pos_err(pvt_body_fixed, rv_body_fixed_gmat) < allowable_pos_diff assert vel_err(pvt_body_fixed, rv_body_fixed_gmat) < allowable_vel_diff
def test_init_cart_wrong_time(): """Tests cartesian init with incorrect frame name.""" with pytest.raises(TypeError): # noinspection PyTypeChecker init_pvt("GCRS", "2004-04-06T07:51:28.386009", r_gcrs_true, v_gcrs_true)
def test_init_cart_wrong_frame(): """Tests cartesian init with incorrect frame name.""" with pytest.raises(ValueError): init_pvt("GCRSx", time, r_gcrs_true, v_gcrs_true)
def to_cartesian(self): """ Converts the orbital elements to the cartesian coordinates in the local inertial frame of the central body. Returns ------- SkyCoord cartesian coordinates in the local inertial frame of the central body Raises ------ ValueError Parabolic orbits or singularity """ # shorthands a = self.sm_axis i = self.inclination e = self.eccentricity argp = self.arg_periapsis raan = self.raan nu = self.true_anomaly node = argp + nu mu = self.central_body.mu p = a * (1 - e**2) r = p / (1 + e * np.cos(nu)) # compute position components x = r * (np.cos(node) * np.cos(raan) - np.cos(i) * np.sin(node) * np.sin(raan)) y = r * (np.cos(node) * np.sin(raan) + np.cos(i) * np.sin(node) * np.cos(raan)) z = r * (np.sin(node) * np.sin(i)) # If orbit is too close to parabolic, raise error. if np.abs(p) < 1e-30 * u.km: raise ValueError( "Orbit eccentricity is close to 1. Parabolic orbits are not allowed." ) sqrt_mup = np.sqrt(mu / p) # compute velocity components v_x = sqrt_mup * (np.cos(nu) + e) * ( -np.sin(argp) * np.cos(raan) - np.cos(i) * np.sin(raan) * np.cos(argp) ) - sqrt_mup * np.sin(nu) * (np.cos(argp) * np.cos(raan) - np.cos(i) * np.sin(raan) * np.sin(argp)) v_y = sqrt_mup * (np.cos(nu) + e) * ( -np.sin(argp) * np.sin(raan) + np.cos(i) * np.cos(raan) * np.cos(argp) ) - sqrt_mup * np.sin(nu) * (np.cos(argp) * np.sin(raan) + np.cos(i) * np.cos(raan) * np.sin(argp)) v_z = sqrt_mup * ((np.cos(nu) + e) * np.sin(i) * np.cos(argp) - np.sin(i) * np.sin(nu) * np.sin(argp)) # collate the SkyCoord object time = self.epoch.replicate() r = CartesianRepresentation([x, y, z], unit=u.km) v = CartesianDifferential( [v_x, v_y, v_z], unit=u.km / u.s, ) rv_init = init_pvt(self.central_body.inert_coord, time, r.with_differentials(v)) return rv_init