def state2ap( state, mu = pd.earth[ 'mu' ] ): h = nt.norm( np.cross( state[ :3 ], state[ 3: ] ) ) epsilon = nt.norm( state[ 3: ] ) ** 2 / 2.0 - mu / nt.norm( state[ :3 ] ) e = math.sqrt( 2 * epsilon * h ** 2 / mu ** 2 + 1 ) a = h ** 2 / mu / ( 1 - e ** 2 ) ra = a * ( 1 + e ) rp = a * ( 1 - e ) return ra, rp
def state2period( state, mu = pd.earth['mu'] ): # specific mechanical energy epsilon = nt.norm( state[ 3:6 ] ) ** 2 / 2.0 - mu / nt.norm( state[ :3 ] ) # semi major axis a = -mu / ( 2.0 * epsilon ) # period return 2 * math.pi * math.sqrt( a ** 3 / mu )
def diffy_q(self, et, state): rx, ry, rz, vx, vy, vz = state r13_vec = [rx + self.mu, ry, rz] r23_vec = [rx - 1 + self.mu, ry, rz] r13_3 = nt.norm(r13_vec)**3 r23_3 = nt.norm(r23_vec)**3 omega_x = rx - self.one_mu * ( rx + self.mu ) / r13_3 -\ self.mu * ( rx - 1 + self.mu ) / r23_3 omega_y = ry - self.one_mu * ry / r13_3 - self.mu * ry / r23_3 omega_z = -self.one_mu * rz / r13_3 - self.mu * rz / r23_3 state_dot = np.zeros(6) state_dot[:3] = [vx, vy, vz] state_dot[3] = 2 * vy + omega_x state_dot[4] = -2 * vx + omega_y state_dot[5] = omega_z return state_dot
def calc_J2(self, et, state): z2 = state[2]**2 norm_r = nt.norm(state[:3]) r2 = norm_r**2 tx = state[0] / norm_r * (5 * z2 / r2 - 1) ty = state[1] / norm_r * (5 * z2 / r2 - 1) tz = state[2] / norm_r * (5 * z2 / r2 - 3) return 1.5 * self.cb[ 'J2' ] * self.cb[ 'mu' ] *\ self.cb[ 'radius' ] ** 2 \ / r2 ** 2 * np.array( [ tx, ty, tz ] )
def check_eclipse( et, r, body, frame = 'J2000', r_body = 0 ): r_sun2body = spice.spkpos( str( body[ 'SPICE_ID' ] ), et, frame, 'LT', 'SUN' )[ 0 ] delta_ps = nt.norm( r_sun2body ) s_hat = r_sun2body / delta_ps proj_scalar = np.dot( r, s_hat ) if proj_scalar <= 0.0: return -1 proj = proj_scalar * s_hat rej_norm = nt.norm( r - proj ) if r_body == 0: if check_umbra( delta_ps, body[ 'diameter' ], proj_scalar, rej_norm, r_body ): return 2 elif check_penumbra( delta_ps, body[ 'diameter' ], proj_scalar, rej_norm, r_body ): return 1 else: return -1
def calc_vinfinity( tof, args ): r1_planet1 = spice.spkgps( args[ 'planet1_ID' ], args[ 'et0' ] + tof, args[ 'frame' ], args[ 'center_ID' ] )[ 0 ] v0_sc_depart, v1_sc_arrive = lt.lamberts_universal_variables( args[ 'state0_planet0' ][ :3 ], r1_planet1, tof, { 'mu': args[ 'mu' ], 'tm': args[ 'tm' ] } ) vinf = nt.norm( v0_sc_depart - args[ 'state0_planet0' ][ 3: ] ) return args[ 'vinf' ] - vinf
def check_solar_eclipse_latlons( et, body0, body1, frame = 'J2000' ): r_sun2body = spice.spkpos( str( body0[ 'SPICE_ID' ] ), et, frame, 'LT', 'SUN' )[ 0 ] r = spice.spkpos( str( body1[ 'SPICE_ID' ] ), et, frame, 'LT', str( body0[ 'SPICE_ID' ] ) )[ 0 ] delta_ps = nt.norm( r_sun2body ) s_hat = r_sun2body / delta_ps proj_scalar = np.dot( r, s_hat ) if proj_scalar <= 0.0: return -1, None proj = proj_scalar * s_hat rej_norm = nt.norm( r - proj ) umbra = check_umbra( delta_ps, body0[ 'diameter' ], proj_scalar, rej_norm, body1[ 'radius' ] ) if umbra: args = { 'r': r, 's_hat': s_hat, 'radius': body1[ 'radius' ] } try: sigma = nt.newton_root_single_fd( eclipse_root_func, proj_scalar - body1[ 'radius' ], args )[ 0 ] except RuntimeError: return -1, None r_eclipse = sigma * s_hat - r r_bf = np.dot( spice.pxform( frame, body1[ 'body_fixed_frame' ], et ), r_eclipse ) latlon = np.array( spice.reclat( r_bf ) ) latlon[ 1: ] *= nt.r2d return 2, latlon else: return -1, None
def propagate_orbit(self): print('Propagating orbit..') while self.solver.successful() and self.step < self.steps: self.solver.integrate(self.solver.t + self.config['dt']) self.states[self.step] = self.solver.y self.alts [ self.step ] = nt.norm( self.solver.y[ :3 ] ) -\ self.cb[ 'radius' ] if self.check_stop_conditions(): self.step += 1 else: break self.ets = self.ets[:self.step] self.states = self.states[:self.step] self.alts = self.alts[:self.step]
def diffy_q(self, et, state): rx, ry, rz, vx, vy, vz, mass = state r = np.array([rx, ry, rz]) v = np.array([vx, vy, vz]) norm_r = nt.norm(r) mass_dot = 0.0 state_dot = np.zeros(7) et += self.et0 a = -r * self.cb['mu'] / norm_r**3 for pert in self.orbit_perts_funcs: a += pert(et, state) state_dot[:3] = v state_dot[3:6] = a state_dot[6] = mass_dot return state_dot
def __init__(self, config): self.config = null_config() for key in config.keys(): self.config[key] = config[key] self.orbit_perts = self.config['orbit_perts'] self.cb = self.config['cb'] if self.config['coes']: self.config['orbit_state'] = oc.coes2state( self.config['coes'], mu=self.config['cb']['mu']) if type(self.config['tspan']) == str: self.config[ 'tspan' ] = float( self.config[ 'tspan'] ) *\ oc.state2period( self.config[ 'orbit_state' ], self.cb[ 'mu' ] ) self.steps = int(np.ceil(self.config['tspan'] / self.config['dt']) + 1) self.step = 1 self.ets = np.zeros((self.steps, 1)) self.states = np.zeros((self.steps, 7)) self.alts = np.zeros((self.steps, 1)) self.states[0, :6] = self.config['orbit_state'] self.states[0, 6] = self.config['mass0'] self.alts [ 0 ] = nt.norm( self.states[ 0, :3 ] ) -\ self.cb[ 'radius' ] self.assign_stop_condition_functions() self.assign_orbit_perturbations_functions() self.load_spice_kernels() if not os.path.exists(self.config['output_dir']): os.mkdir(self.config['output_dir']) self.solver = ode(self.diffy_q) self.solver.set_integrator(self.config['propagator']) self.solver.set_initial_value(self.states[0, :], 0) self.coes_calculated = False self.latlons_calculated = False if self.config['propagate']: self.propagate_orbit()
def test_penumbra_edge_cases(): spice.furnsh( sd.leapseconds_kernel ) spice.furnsh( sd.de432 ) Dp = 2 * 6378.0 alt = 600.0 proj_mag = 6378.0 + alt et = spice.str2et( '2021-11-16' ) r_earth = spice.spkpos( '399', et, 'J2000', 'LT', 'SUN' )[ 0 ] s_hat = nt.normed( r_earth ) v_perp = nt.normed( np.cross( s_hat, [ 1, 0, 0 ] ) ) Xp = ( Dp * nt.norm( r_earth ) ) / ( pd.sun[ 'diameter' ] + Dp ) alphap = np.arcsin( Dp / ( 2 * Xp ) ) kappa = ( Xp + proj_mag ) * np.tan( alphap ) r_sc0 = proj_mag * s_hat + v_perp * kappa * 1.01 r_sc1 = proj_mag * s_hat + v_perp * kappa * 0.99 assert oc.check_eclipse( et, r_sc0, pd.earth ) == -1 assert oc.check_eclipse( et, r_sc1, pd.earth ) == 1
def vinfinity_match( planet0, planet1, v0_sc, et0, tof0, args = {} ): ''' Given an incoming v-infinity vector to planet0, calculate the outgoing v-infinity vector that will arrive at planet1 after time of flight (tof) where the incoming and outgoing v-infinity vectors at planet0 have equal magnitude ''' _args = { 'et0' : et0, 'planet1_ID': planet1, 'frame' : 'ECLIPJ2000', 'center_ID' : 0, 'mu' : pd.sun[ 'mu' ], 'tm' : 1, 'diff_step' : 1e-3, 'tol' : 1e-4 } for key in args.keys(): _args[ key ] = args[ key ] _args[ 'state0_planet0' ] = spice.spkgeo( planet0, et0, _args[ 'frame' ], _args[ 'center_ID' ] )[ 0 ] _args[ 'vinf' ] = nt.norm( v0_sc - _args[ 'state0_planet0' ][ 3: ] ) tof, steps = nt.newton_root_single_fd( calc_vinfinity, tof0, _args ) r1_planet1 = spice.spkgps( planet1, et0 + tof, _args[ 'frame' ], _args[ 'center_ID' ] )[ 0 ] v0_sc_depart, v1_sc_arrive = lt.lamberts_universal_variables( _args[ 'state0_planet0' ][ :3 ], r1_planet1, tof, { 'mu': _args[ 'mu' ], 'tm': _args[ 'tm' ] } ) return tof, v0_sc_depart, v1_sc_arrive
from numerical_tools import norm if __name__ == '__main__': spice.furnsh(sd.leapseconds_kernel) spice.furnsh('voyager2_jupiter_flyby.bsp') et = spice.str2et('1979-07-09 TDB') dt = 20 * 24 * 3600.0 ets = np.arange(et - dt, et + dt, 5000.0) states = st.calc_ephemeris(-32, ets, 'ECLIPJ2000', 5) pt.plot_orbits( [states[:, :3]], { 'labels': ['Voyager 2'], 'colors': ['m'], 'cb_radius': pd.jupiter['radius'], 'cb_cmap': 'Oranges', 'dist_unit': 'JR', 'azimuth': -57, 'elevation': 15, 'axes_mag': 0.5, 'show': True }) hline = {'val': norm(states[0, 3:]), 'color': 'm'} pt.plot_velocities(ets, states[:, 3:], { 'time_unit': 'hours', 'hlines': [hline], 'show': True })
def test_norm_basic_usage(): assert nt.norm( [ 1.0, 0, 0 ] ) == 1.0
def test_norm_pythagorean(): assert nt.norm( [ 3.0, 4.0 ] ) == 5.0
import matplotlib.pyplot as plt plt.style.use('dark_background') plt.rcParams.update({'font.size': 13}) # AWP library import orbit_calculations as oc import numerical_tools as nt import planetary_data as pd from EVME_1963 import calc_EVME_1963 if __name__ == '__main__': itvim = calc_EVME_1963() seq1 = itvim.seq[1] v_arrive = seq1['state_sc_arrive'][3:] state_venus = spice.spkgeo(2, seq1['et'], 'ECLIPJ2000', 0)[0] vinf = nt.norm(v_arrive - state_venus[3:]) span = 60 * 24 * 3600.0 dt = 1 * 24 * 3600.0 tofs = np.arange(seq1['tof'] - span, seq1['tof'] + span, dt) n_tofs = len(tofs) vinfs = np.zeros(n_tofs) args = { 'planet1_ID': 4, 'center_ID': 0, 'et0': seq1['et'], 'frame': 'ECLIPJ2000', 'state0_planet0': state_venus, 'mu': pd.sun['mu'], 'tm': 1, 'vinf': vinf }
def eclipse_root_func( sigma, args ): return nt.norm( sigma * args[ 's_hat' ] - args[ 'r' ] ) - args[ 'radius' ]
def lamberts_universal_variables(r0, r1, deltat, args): ''' Solve Lambert's problem using universal variable method ''' _args = { 'tm': 1, 'mu': pd.sun['mu'], 'tol': 1e-6, 'max_steps': 200, 'psi': 0.0, 'psi_u': 4.0 * math.pi**2, 'psi_l': -4.0 * math.pi**2, } for key in args.keys(): _args[key] = args[key] psi = _args['psi'] psi_l = _args['psi_l'] psi_u = _args['psi_u'] sqrt_mu = math.sqrt(_args['mu']) r0_norm = nt.norm(r0) r1_norm = nt.norm(r1) gamma = np.dot(r0, r1) / r0_norm / r1_norm c2 = 0.5 c3 = 1 / 6.0 solved = False A = _args['tm'] * math.sqrt(r0_norm * r1_norm * (1 + gamma)) if A == 0.0: raise RuntimeWarning( 'Universal variables solution was passed in Hohmann transfer') return np.array([0, 0, 0]), np.array([0, 0, 0]) for n in range(_args['max_steps']): B = r0_norm + r1_norm + A * (psi * c3 - 1) / math.sqrt(c2) if A > 0.0 and B < 0.0: psi_l += math.pi B *= -1.0 chi3 = math.sqrt(B / c2)**3 deltat_ = (chi3 * c3 + A * math.sqrt(B)) / sqrt_mu if abs(deltat - deltat_) < _args['tol']: solved = True break if deltat_ <= deltat: psi_l = psi else: psi_u = psi psi = (psi_u + psi_l) / 2.0 c2 = C2(psi) c3 = C3(psi) if not solved: raise RuntimeWarning('Universal variables solver did not converge.') return np.array([0, 0, 0]), np.array([0, 0, 0]) f = 1 - B / r0_norm g = A * math.sqrt(B / _args['mu']) gdot = 1 - B / r1_norm v0 = (r1 - f * r0) / g v1 = (gdot * r1 - r0) / g return v0, v1
Voyager 2 SPICE kernel is correct. Or you can change the path to fit your needs ''' # 3rd party libraries import spiceypy as spice import numpy as np # AWP library import spice_tools as st import plotting_tools as pt import spice_data as sd from numerical_tools import norm if __name__ == '__main__': spice.furnsh(sd.leapseconds_kernel) spice.furnsh('voyager2_jupiter_flyby.bsp') et = spice.str2et('1979-07-09 TDB') dt = 20 * 24 * 3600.0 ets = np.arange(et - dt, et + dt, 5000.0) states = st.calc_ephemeris(-32, ets, 'ECLIPJ2000', 10) state_jup = spice.spkgeo(5, et, 'ECLIPJ2000', 10)[0] hline = {'val': norm(state_jup[3:]), 'color': 'C3'} pt.plot_velocities(ets, states[:, 3:], { 'time_unit': 'days', 'hlines': [hline], 'show': True, })
import spice_data as sd import numpy as np import spiceypy as spice import matplotlib.pyplot as plt plt.style.use('dark_background') if __name__ == '__main__': spice.furnsh(sd.leapseconds_kernel) spice.furnsh(sd.de432) spice.furnsh(sd.pck00010) et = spice.str2et('2017-08-21 18:30') r_sun2moon = spice.spkpos('301', et, 'J2000', 'LT', 'SUN')[0] r_moon2earth = spice.spkpos('399', et, 'J2000', 'LT', '301')[0] delta_ps = nt.norm(r_sun2moon) s_hat = r_sun2moon / delta_ps proj_scalar = np.dot(r_moon2earth, s_hat) proj = proj_scalar * s_hat rej_norm = nt.norm(r_moon2earth - proj) args = {'r': r_moon2earth, 's_hat': s_hat, 'radius': 6378.0} sigma = nt.newton_root_single_fd(oc.eclipse_root_func, proj_scalar / 2.0, args)[0] sigmas = np.arange(sigma - 10000, sigma + 15000, 10) n_sigmas = len(sigmas) vals = np.zeros(n_sigmas) for n in range(n_sigmas): vals[n] = oc.eclipse_root_func(sigmas[n], args) plt.figure(figsize=(16, 8))
def test_norm_zero_vector(): assert nt.norm( [ 0, 0, 0 ] ) == 0.0