def InitialiseParticles(TriData): LastTimeStep = Table(TriData[TriData['time'] == max(TriData['time'])]) # Determine the entries you want darkflighted # entries = np.arange(len(LastTimeStep['weight'])).tolist() # entries = LastTimeStep['weight'].argsort()[-1000:].tolist() # top 1000 most weighted Pos_ECEF_all = np.vstack( (LastTimeStep['X_geo'], LastTimeStep['Y_geo'], LastTimeStep['Z_geo'])) entries = (np.unique(Pos_ECEF_all, return_index=True, axis=1)[1]).tolist() entries = np.array(entries)[LastTimeStep['mass'][entries] > 0.01].tolist( ) # Remove negative mass terms M = LastTimeStep['mass'][entries] # rho = LastTimeStep['rho'][entries] rho = (1.5 / LastTimeStep['kappa'][entries])**(3 / 2.0) # rho = [(1.5/x)**(3/2.0) for x in LastTimeStep['kappa'][entries]] A = LastTimeStep['A'][entries] W = LastTimeStep['weight'][entries] # cd_hypersonic = LastTimeStep['cd'][entries] c_ml = LastTimeStep['sigma'][entries] * cd_hypersonic(A)[0] Pos_ECEF0 = np.vstack( (LastTimeStep['X_geo'][entries], LastTimeStep['Y_geo'][entries], LastTimeStep['Z_geo'][entries])) Vel_ECEF0 = np.vstack( (LastTimeStep['X_geo_DT'][entries], LastTimeStep['Y_geo_DT'][entries], LastTimeStep['Z_geo_DT'][entries])) t0 = Time(LastTimeStep['datetime'][entries], format='isot', scale='utc').jd [Pos_ECI0, Vel_ECI0] = ECEF2ECI(Pos_ECEF0, Vel_ECEF0, t0) return { 'time_jd': t0, 'pos_eci': Pos_ECI0, 'vel_eci': Vel_ECI0, 'mass': M, 'rho': rho, 'shape': A, 'c_ml': c_ml, 'weight': W }
# Gather Earth parameters Pos_earth = np.vstack((sim.particles['earth'].xyz)) Vel_earth = np.vstack((sim.particles['earth'].vxyz)) ''' Generate particles ''' [t0, pos_xyz, vel_geo, ra_geo, dec_geo, vel_err, ra_err, dec_err] = parent Vel_geo = np.random.normal(vel_geo, vel_err, size=N_particles[rank]) Ra_geo = np.deg2rad(np.random.normal(ra_geo, ra_err, size=N_particles[rank])) Dec_geo = np.deg2rad(np.random.normal(dec_geo, dec_err, size=N_particles[rank])) Vel_xyz = -np.vstack((Vel_geo * np.cos(Ra_geo) * np.cos(Dec_geo), Vel_geo * np.sin(Ra_geo) * np.cos(Dec_geo), Vel_geo * np.sin(Dec_geo))) [pos_ECI, Vel_ECI] = ECEF2ECI(pos_xyz, Vel_xyz, t0) pos_BCI = Pos_earth + HCRS2HCI(pos_ECI) / AU Vel_BCI = Vel_earth + HCRS2HCI(Vel_ECI) / AU*(365.2422*24*60*60) for j in range(N_particles[rank]): sim.add(x=pos_BCI[0,0], y=pos_BCI[1,0], z=pos_BCI[2,0], vx=Vel_BCI[0,j], vy=Vel_BCI[1,j], vz=Vel_BCI[2,j]) # Integrator options sim.integrator = "ias15" # sim.integrator = "mercurius" # sim.integrator = "hermes" # sim.testparticle_type = 1 # sim.integrator = "whfast"#; sim.dt = 0.000005
if errors: mc = 1000 # Number of particles ParticleFolder = os.path.join(PointDir, str(mc) + '_particles_per_timestep') os.mkdir(ParticleFolder) [Pos_ECEF, t_rel, N_cams, Cov_ECEF] = PointTriangulation(AllData, ParticleFolder, mc) else: [Pos_ECEF, t_rel, N_cams] = PointTriangulation(AllData) # Coordinate transforms T_jd = t0.jd + t_rel / (24 * 60 * 60) Pos_LLH = ECEF2LLH(Pos_ECEF) Pos_ECI = ECEF2ECI(Pos_ECEF, Pos_ECEF, T_jd)[0] # Raw velocity calculations vel_eci = norm(Pos_ECI[:, 1:] - Pos_ECI[:, :-1], axis=0) / (t_rel[1:] - t_rel[:-1]) vel_geo = norm(Pos_ECEF[:, 1:] - Pos_ECEF[:, :-1], axis=0) / (t_rel[1:] - t_rel[:-1]) gamma = np.arcsin((Pos_LLH[2][1:] - Pos_LLH[2][:-1]) / (vel_geo * (t_rel[1:] - t_rel[:-1]))) vel_eci = np.hstack((vel_eci, np.nan)) vel_geo = np.hstack((vel_geo, np.nan)) gamma = np.hstack((gamma, np.nan)) t_isot_col = Column(name='datetime', data=Time(T_jd, format='jd', scale='utc').isot) t_rel_col = Column(name='time', data=t_rel * u.second)
def propagateOrbit(stateVec, Perturbations=True, Plot=False, Sol='NoSol', verbose=True, ephem=False): ''' The propagateOrbit function calculates the origin of the meteor by reverse propergating the position and velocity out of the atmosphere using the equinoctial element model. ''' import time starttime = time.time() SharedGlobals('Reset') Pos_geo = stateVec.position Vel_geo = stateVec.vel_xyz t0 = stateVec.epoch M = stateVec.mass CA = stateVec.cs_area ''' Convert from geo to ECI coordinates ''' [Pos_ECI, Vel_ECI] = ECEF2ECI(Pos_geo, Vel_geo, t0) ''' Calculate the initial meteor's state in ECI coords ''' X0 = np.vstack((Pos_ECI, Vel_ECI, M, CA)) OrbitType = 'Heliocentric' # Initial assumption # Integrate with RK4 until outside Earth's sphere of influence if verbose: print('Rewinding from Bright Flight...') [t, X] = Propagate_ECI(t0, X0, Perturbations) [Pos_ECI, Vel_ECI] = [X[:3], X[3:6]] t_soi = [t[-1]] if norm(Pos_ECI[:, -1:]) < R_SOI: # Geocentric OrbitType = 'Geocentric' # Convert to geocentric orbital elements COE = PosVel2OrbitalElements(Pos_ECI, Vel_ECI, 'Earth', 'Classical') ra_corr = np.nan dec_corr = np.nan v_g = np.nan else: # Heliocentric # Convert the ECI position and velocity to HCI coordinates [Pos_HCI, Vel_HCI] = ECI2HCI(Pos_ECI, Vel_ECI, t) # Convert to heliocentric orbital elements X = np.vstack((Pos_HCI, Vel_HCI)) if verbose: print('\rNow entering a Sun centred orbit...') [t, X] = Propagate_HCI(t, X, t0) [Pos_HCI, Vel_HCI] = [X[:3], X[3:6]] COE = PosVel2OrbitalElements(Pos_HCI, Vel_HCI, 'Sun', 'Classical') [Pos_eci, Vel_eci] = HCI2ECI(Pos_HCI[:, -1:], Vel_HCI[:, -1:], t[-1]) ra_corr = float(np.arctan2(-Vel_eci[1], -Vel_eci[0])) dec_corr = float(np.arcsin(-Vel_eci[2] / norm(Vel_eci))) v_g = norm(Vel_eci) if COE[0, -1] < 0 and COE[1, -1] > 1: OrbitType = 'Hyperbolic' print('\r' + OrbitType + ' orbit determined') if verbose: print("--- %s seconds ---" % (time.time() - starttime)) # Create the orbit object DeterminedOrbit = OrbitObject(OrbitType, COE[0, -1] * u.m, COE[1, -1], COE[2, -1] * u.rad, COE[3, -1] * u.rad, COE[4, -1] * u.rad, COE[5, -1] * u.rad, ra_corr * u.rad, dec_corr * u.rad, v_g * u.m / u.second) # Plots if required if Plot: if Pert.shape[1] > 1: # Plot the perturbations over time # global Pert PlotPerts(Perts) # Plot dt PlotIntStep(t) # Plot the orbit in 3D PlotOrbit3D([DeterminedOrbit], t0, Sol) # Plot the individual orbital elements PlotOrbitalElements(COE, t, t_soi, Sol) ephem_dict = None if ephem: # Also return the ephemeris dict t_max = np.argmin(t) t_jd = t[:t_max] pos_hci = Pos_HCI[:, :t_max] ephem_dict = generate_ephemeris(pos_hci, t_jd) # from scipy.integrate import simps # global Pert#, time_tot, pert_tot # time_tot = (t.max() - t.min()) * u.d # if (Perturbations - 10) == True: # pert_tot = (-24*60*60 * simps(Pert[1,1:] + Pert[0,1:], t[1:Pert.shape[1]])) * (u.m / u.second) # else: # pert_tot = 0 return DeterminedOrbit, ephem_dict #, time_tot, pert_tot
def read_config(ifile): Config = configparser.RawConfigParser() Config.read(ifile) traj_dict = {} obs_dict = {} extract = lambda header, name: ast.literal_eval(Config.get(header, name)) # Event identity event_name = Config.get('identity', 'name') # trajectory conditions t0_isot = Config.get('dynamic_properties', 'initial_datetime') traj_dict['t0'] = Time(t0_isot, format='isot', scale='utc').jd # time lat0 = np.deg2rad(Config.getfloat('dynamic_properties', 'initial_latitude')) lon0 = np.deg2rad( Config.getfloat('dynamic_properties', 'initial_longitude')) hei0 = Config.getfloat('dynamic_properties', 'initial_height') pos_ecef = LLH2ECEF(np.vstack((lat0, lon0, hei0))) speed0 = Config.getfloat('dynamic_properties', 'initial_velocity') slope0 = np.deg2rad(Config.getfloat('dynamic_properties', 'initial_slope')) bearing0 = np.deg2rad( Config.getfloat('dynamic_properties', 'initial_bearing')) vel_enu = -speed0 * np.vstack( (np.sin(bearing0) * np.cos(slope0), np.cos(bearing0) * np.cos(slope0), np.sin(slope0))) vel_ecef = ENU2ECEF(lon0, lat0).dot(vel_enu) [pos_eci, vel_eci] = ECEF2ECI(pos_ecef, vel_ecef, traj_dict['t0']) traj_dict['pos_eci'] = pos_eci.flatten() # position traj_dict['vel_eci'] = vel_eci.flatten() # velocity # Physical conditions traj_dict['m0'] = Config.getfloat('physical_properties', 'initial_mass') # mass traj_dict['rho'] = Config.getfloat('physical_properties', 'density') # density traj_dict['A'] = Config.getfloat('physical_properties', 'shape') # shape traj_dict['c_ml'] = Config.getfloat( 'physical_properties', 'ablation_coeff') # ablation coefficient traj_dict['mu'] = Config.getfloat('physical_properties', 'spin_state') # spin state traj_dict['tau'] = Config.getfloat( 'physical_properties', 'luminous_efficiency') # luminous efficiency traj_dict['dm_height'] = extract( 'physical_properties', 'mass_loss_height') # mass loss height [n] traj_dict['dm_percent'] = extract( 'physical_properties', 'mass_loss_percent') # mass loss percent [n] # Observatory conditions obs_dict['obs_name'] = extract('observatory_properties', 'obs_name') # observatory names [m] obs_dict['obs_location'] = extract( 'observatory_properties', 'obs_location') # observatory locations [m] obs_lat = np.deg2rad(extract('observatory_properties', 'obs_latitude')) obs_lon = np.deg2rad(extract('observatory_properties', 'obs_longitude')) obs_hei = np.array(extract('observatory_properties', 'obs_height')) obs_dict['obs_llh'] = np.vstack( (obs_lat, obs_lon, obs_hei)).T # observatory locations [m,3] obs_dict['obs_dt'] = np.array( extract('observatory_properties', 'obs_timing_offset')) # timing offsets [m] obs_dict['obs_dang'] = np.array( extract('observatory_properties', 'obs_calibration_offset')) # calibration offsets [m] obs_dict['measurement_err'] = Config.getfloat( 'observatory_properties', 'measurement_uncertainty') # Measurement uncertainty for simulation obs_dict['picking_err'] = Config.getfloat( 'observatory_properties', 'picking_uncertainty') # Picking uncertainty from point-picker return event_name, traj_dict, obs_dict
def EarthDynamics(t, X, WindData, t0, return_abs_mag=False): ''' The state rate dynamics are used in Runge-Kutta integration method to calculate the next set of equinoctial element values. ''' ''' State Rates ''' # State parameter vector decomposed Pos_ECI = np.vstack((X[:3])) Vel_ECI = np.vstack((X[3:6])) M = X[6] rho = X[7] A = X[8] c_ml = X[9] # c_massloss = sigma*cd_hyp t_jd = t0 + t / (24 * 60 * 60) # Absolute time [jd] ''' Primary Gravitational Acceleration ''' a_grav = gravity_vector(Pos_ECI) ''' Atmospheric Drag Perturbation - Better Model Needed ''' Pos_ECEF = ECI2ECEF_pos(Pos_ECI, t_jd) Pos_LLH = ECEF2LLH(Pos_ECEF) # Atmospheric velocity if type(WindData) == Table: #1D vertical profile maxwindheight = max(WindData['# Height']) if float(Pos_LLH[2]) > maxwindheight: [v_atm, rho_a, temp] = AtmosphericModel([], Pos_ECI, t_jd) else: [v_atm, rho_a, temp] = AtmosphericModel(WindData, Pos_ECI, t_jd) else: #3D wind profile maxwindheight = max(WindData[2, :, :, :]) if float(Pos_LLH[2]) > maxwindheight: [v_atm, rho_a, temp] = AtmosphericModel([], Pos_ECI, t_jd) else: [Wind_ENU, rho_a, temp] = WRF3D(WindData, Pos_LLH) Wind_ECEF = ENU2ECEF(Pos_LLH[1], Pos_LLH[0]).dot(Wind_ENU) v_atm = ECEF2ECI(Pos_ECEF, Wind_ECEF, t_jd)[1] # Velocity relative to the atmosphere v_rel = Vel_ECI - v_atm v = norm(v_rel) # New drag equations - function that fits the literature cd = dragcoeff(v, temp, rho_a, A)[0] # Total drag perturbation a_drag = -cd * A * rho_a * v * v_rel / (2 * M**(1. / 3) * rho**(2. / 3)) ''' Total Perturbing Acceleration ''' a_tot = a_grav + a_drag # Mass-loss equation dm_dt = -c_ml * A * rho_a * v**3 * M**(2. / 3) / (2 * rho**(2. / 3)) # See (Sansom, 2019) as reference if return_abs_mag: # sigma = c_ml / cd lum = -X[10] * (v**2 / 2 + cd / c_ml) * dm_dt * 1e7 return -2.5 * np.log10(lum / 1.5e10) ''' State Rate Equation ''' X_dot = np.zeros(X.shape) X_dot[:3] = Vel_ECI.flatten() X_dot[3:6] = a_tot.flatten() X_dot[6] = dm_dt return X_dot
def InitialiseCFG(TriData, mass, rho, shape, mc): # Position lat = np.deg2rad(Config.getfloat('met', 'lat0')) lon = np.deg2rad(Config.getfloat('met', 'lon0')) hei = Config.getfloat('met', 'z0') if mc: lat_err = np.deg2rad(Config.getfloat('met', 'dlat')) lon_err = np.deg2rad(Config.getfloat('met', 'dlon')) hei_err = Config.getfloat('met', 'dz') lat = np.random.normal(lat, lat_err, mc) lon = np.random.normal(lon, lon_err, mc) hei = np.random.normal(hei, hei_err, mc) Pos_LLH0 = np.vstack((lat, lon, hei)) Pos_ECEF0 = LLH2ECEF(Pos_LLH0) # Velocity vel = Config.getfloat('met', 'vtot0') zen = np.deg2rad(Config.getfloat('met', 'zenangle')) azi = np.deg2rad(Config.getfloat('met', 'azimuth0')) if mc: vel_err = Config.getfloat('met', 'dvtot') zen_err = np.deg2rad(Config.getfloat('met', 'dzenith')) azi_err = np.deg2rad(Config.getfloat('met', 'dazimuth0')) vel = np.random.normal(vel, vel_err, mc) zen = np.random.normal(zen, zen_err, mc) azi = np.random.normal(azi, azi_err, mc) Vel_ENU0 = vel * np.vstack( (np.sin(zen) * np.sin(azi), np.sin(zen) * np.cos(azi), -np.cos(zen))) if mc: Vel_ECEF0 = np.hstack([ ENU2ECEF(lon[i], lat[i]).dot(Vel_ENU0[:, i:i + 1]) for i in range(mc) ]) else: Vel_ECEF0 = ENU2ECEF(lon, lat).dot(Vel_ENU0) # Time - no time in config file.. t0 = Time(Config.get('met', 'exposure_time'), format='isot', scale='utc').jd # t0 = np.array([2451545.0]) # 2000-01-01T12:00:00 if mc: t0 = t0 * np.ones(mc) # Physical if type(mass) == float: M = np.array([mass]) elif type(mass) == np.ndarray: M = mass else: # M = [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0] M = np.array([0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]) # M = np.concatenate((M, 1.5*M, 1.25*M, 1.75*M)) if not rho: rho = Config.getfloat('met', 'rdens0') if mc: if type(mass) == float: M = np.array([mass]) else: M = np.array([Config.getfloat('met', 'mass0')]) m_err = Config.get('montecarlo', 'dmass') if m_err[-1] == '%': m_err = M * float(m_err[:-1]) / 100 else: m_err = float(m_err) rho_err = Config.getfloat('montecarlo', 'drdens') M = truncnorm.rvs(loc=M, scale=m_err, a=0, b=np.inf, size=mc) rho = truncnorm.rvs(loc=rho, scale=rho_err, a=0, b=np.inf, size=mc) ParameterDict = InitialiseParams(rho, shape) A = ParameterDict['shape'] c_ml = ParameterDict['c_ml'] [Pos_ECI0, Vel_ECI0] = ECEF2ECI(Pos_ECEF0, Vel_ECEF0, t0) return { 'time_jd': t0, 'pos_eci': Pos_ECI0, 'vel_eci': Vel_ECI0, 'mass': M, 'rho': rho, 'shape': A, 'c_ml': c_ml, 'weight': np.ones(len(M)) }
def Initialise(TriData, velType, mass, rho, shape): LastTimeStep = TriData[TriData['datetime'] == max(TriData['datetime'])] # Set the velcity model if velType == 'eks': v_col = 'D_DT_EKS' elif velType == 'grits': v_col = 'D_DT_fitted' elif velType == 'raw': v_col = 'D_DT_geo' else: print('Unknown velocity model: ', velType, "\nPlease choose between 'eks', 'grits', or 'raw'") exit(1) # Set the masses if type(mass) == float: M = np.array([mass]) elif type(mass) == np.ndarray: M = mass else: M = np.logspace(np.log10(0.005), np.log10(5), num=16) # Dynamical parameters ------------------------------------------------------ t0 = Time(LastTimeStep['datetime'][0], format='isot', scale='utc').jd Pos_ECEF0 = np.vstack( (LastTimeStep['X_geo'], LastTimeStep['Y_geo'], LastTimeStep['Z_geo'])) if velType == 'raw': try: Vel_ECEF0 = np.vstack( (LastTimeStep['DX_DT_geo'], LastTimeStep['DY_DT_geo'], LastTimeStep['DZ_DT_geo'])) except KeyError: TriData.sort('datetime') SecondLastTimeStep = TriData[-2] try: Vel_ECEF0 = np.vstack((SecondLastTimeStep['DX_DT_geo'], SecondLastTimeStep['DY_DT_geo'], SecondLastTimeStep['DZ_DT_geo'])) except KeyError: Pos_ECEF1 = np.vstack( (SecondLastTimeStep['X_geo'], SecondLastTimeStep['Y_geo'], SecondLastTimeStep['Z_geo'])) t1 = Time(SecondLastTimeStep['datetime'], format='isot', scale='utc').jd Vel_ECEF0 = (Pos_ECEF0 - Pos_ECEF1) / ((t0 - t1) * (24 * 60 * 60)) else: try: # Assuming straight line fit: ra_ecef = np.deg2rad( LastTimeStep.meta['triangulation_ra_ecef_inf']) dec_ecef = np.deg2rad( LastTimeStep.meta['triangulation_dec_ecef_inf']) vel = LastTimeStep[v_col] Vel_ECEF0 = -vel * np.vstack( (np.cos(ra_ecef) * np.cos(dec_ecef), np.sin(ra_ecef) * np.cos(dec_ecef), np.sin(dec_ecef))) except KeyError: TriData.sort('datetime') SecondLastTimeStep = TriData[-2] Pos_ECEF1 = np.vstack( (SecondLastTimeStep['X_geo'], SecondLastTimeStep['Y_geo'], SecondLastTimeStep['Z_geo'])) radiant = (Pos_ECEF0[:, :1] - Pos_ECEF1) / norm(Pos_ECEF0[:, :1] - Pos_ECEF1) vel = LastTimeStep[v_col] Vel_ECEF0 = vel * radiant except KeyError: print('Velocity error: ' + v_col + " column doesn't exist.") exit(2) # Physical parameters ------------------------------------------------------ # try: # Check if a least_squares file was given # beta = float(LastTimeStep['beta'].data) # sigma = float(LastTimeStep['sigma'].data) # [v_wind, rho_a, temp] = AtmosphericModel(False, Pos_ECI0[:,:1], t0) # # Velocity relative to the still atmosphere # v = norm(Vel_ECEF0[:,0]); A, cd = [], []#; M_temp = M # drag_diff = lambda cdd,m: dragcoeff(v, temp, rho_a, rho, m, rho**(2./3) * m**(1./3) / (cdd * beta))[0] - cdd # for m in M: # try: # Tests if the mass is possible for the given beta/sigma values. # cd.append( brentq(drag_diff, 0.001, 10, args=(m), xtol=0.0001) ) # A.append( rho**(2./3) * m**(1./3) / (cd[-1] * beta) ) # print('M: {0:.2f}kg, A: {1:.2f}, cd: {2:.2f}'.format(m, A[-1], cd[-1])) # except ValueError: # print(m,'kg is non-physical with these beta/sigma values.') # M = M[M!=m] # remove the unrealistic mass value # cd = np.array(cd) # A = np.array(A) # c_ml = sigma * cd # rho = rho * np.ones(len(M)) ParameterDict = InitialiseParams(rho, shape) A = ParameterDict['shape'] * np.ones(len(M)) c_ml = ParameterDict['c_ml'] * np.ones(len(M)) rho = rho * np.ones(len(M)) Pos_ECEF0 = Pos_ECEF0 * np.ones(len(M)) Vel_ECEF0 = Vel_ECEF0 * np.ones(len(M)) [Pos_ECI0, Vel_ECI0] = ECEF2ECI(Pos_ECEF0, Vel_ECEF0, t0) return { 'time_jd': t0, 'pos_eci': Pos_ECI0, 'vel_eci': Vel_ECI0, 'mass': M, 'rho': rho, 'shape': A, 'c_ml': c_ml, 'weight': np.ones(len(M)) }
def EarthDynamics(t, X, WindData, t0, return_abs_mag=False): ''' The state rate dynamics are used in Runge-Kutta integration method to calculate the next set of equinoctial element values. ''' ''' State Rates ''' # State parameter vector decomposed Pos_ECI = np.vstack((X[:3])) Vel_ECI = np.vstack((X[3:6])) M = X[6] rho = X[7] A = X[8] c_ml = X[9] # c_massloss = sigma*cd_hyp t_jd = t0 + t / (24 * 60 * 60) # Absolute time [jd] ''' Primary Gravitational Acceleration ''' a_grav = gravity_vector(Pos_ECI) ''' Atmospheric Drag Perturbation - Better Model Needed ''' # Atmospheric velocity if type(WindData) == Table: #1D vertical profile [v_atm, rho_a, temp] = AtmosphericModel(WindData, Pos_ECI, t_jd) else: #3D wind profile Pos_ECEF = ECI2ECEF_pos(Pos_ECI, t_jd) Pos_LLH = ECEF2LLH(Pos_ECEF) [Wind_ENU, rho_a, temp] = WRF3D(WindData, Pos_LLH) Wind_ECEF = ENU2ECEF(Pos_LLH[1], Pos_LLH[0]).dot(Wind_ENU) v_atm = ECEF2ECI(Pos_ECEF, Wind_ECEF, t_jd)[1] # Velocity relative to the atmosphere v_rel = Vel_ECI - v_atm v = norm(v_rel) # # Constants for drag coeff # d = 2 * np.sqrt(A / rho**(2./3) * M**(2./3)) # mu_a = viscosity(temp) # Air Viscosity (Pa.s) # mach = v / SoS(temp) # Mach Number # re = reynolds(v, rho_a, mu_a, d) # Reynolds Number # kn = knudsen(mach, re) # Knudsen Number # cd = dragcoef(re, mach, kn)#, A) # Drag Coefficient # # cd = 2.0 # Approximation # New drag equations - function that fits the literature cd = dragcoeff(v, temp, rho_a, A)[0] # cd = 1 # # New drag equations 2 # mach = v / SoS(temp) # Mach Number # cd = dragcoefff(mach, A) # Total drag perturbation a_drag = -cd * A * rho_a * v * v_rel / (2 * M**(1. / 3) * rho**(2. / 3)) ''' Total Perturbing Acceleration ''' a_tot = a_grav + a_drag # Mass-loss equation dm_dt = -c_ml * A * rho_a * v**3 * M**(2. / 3) / (2 * rho**(2. / 3)) # See (Sansom, 2019) as reference if return_abs_mag: # sigma = c_ml / cd lum = -X[10] * (v**2 / 2 + cd / c_ml) * dm_dt * 1e7 return -2.5 * np.log10(lum / 1.5e10) ''' State Rate Equation ''' X_dot = np.zeros(X.shape) X_dot[:3] = Vel_ECI.flatten() X_dot[3:6] = a_tot.flatten() X_dot[6] = dm_dt return X_dot
def propagateOrbit(stateVec, Perturbations=True, Plot=False, Sol='NoSol', verbose=True, ephem=False): ''' The propagateOrbit function calculates the origin of the meteor by reverse propergating the position and velocity out of the atmosphere using the equinoctial element model. ''' import time starttime = time.time() SharedGlobals('Reset') Pos_geo = stateVec.position Vel_geo = stateVec.vel_xyz t0 = stateVec.epoch M = stateVec.mass CA = stateVec.cs_area ''' Convert from geo to ECI coordinates ''' [Pos_ECI, Vel_ECI] = ECEF2ECI(Pos_geo, Vel_geo, t0) ''' Calculate the initial meteors equinoctial elements in ECI coords ''' EOE = PosVel2OrbitalElements(Pos_ECI, Vel_ECI, 'Earth', 'Equinoctial') ''' Numerically integrate until its beyond the Earth's SOI ''' # Earth's levels of satellites R_ATM = 500.0e3 + R_e Above_ATM = False R_LEO = 2000.0e3 + R_e Above_LEO = False R_GEO = 35786.0e3 + R_e Above_GEO = False R_MOON = 384400.0e3 Above_MOON = False # Earth's sphere of influence (SOI) R_SOI = a_earth * (mu_e / mu_s)**0.4 # ~3*R_MOON # Setup initial values Y = np.vstack((EOE, M, CA)) w = 1 + Y[1] * np.cos(Y[5]) + Y[2] * np.sin(Y[5]) r = Y[0] / w # Initial radius rmax = r # Maximum radius reached OrbitType = 'Heliocentric' # Initial assumption # Check the time step is negative dt = -0.1 / (24 * 60 * 60) # 1sec (JD) t = np.array([t0]) # Start the time at epoch (JD) # Integrate with RK4 until outside Earth's sphere of influence if verbose: print('Rewinding from Bright Flight...') while r < R_SOI: # Runge Kutta Integration (Dormand-Prince) [Y_new, t_new, dt_new] = ODE45(EarthDynamics, Y[:, -1:], t[-1], dt, Perturbations) # Save EOE's and time steps Y = np.hstack((Y, Y_new)) t = np.hstack((t, t_new)) dt = dt_new # Calculate the current distance from Earth (m) w = 1 + Y_new[1] * np.cos(Y_new[5]) + Y_new[2] * np.sin(Y_new[5]) r = Y_new[0] / w # Record the maximum radius reached if r > rmax: rmax = r # Print progress sys.stdout.write('\r%.3f%%' % (r / (10 * R_SOI) * 100)) sys.stdout.flush() if r > R_ATM and Above_ATM == False: if verbose: print('\rPassing ATM.') Above_ATM = True if r > R_LEO and Above_LEO == False: if verbose: print('\rPassing LEO.') Above_LEO = True if r > R_GEO and Above_GEO == False: if verbose: print('\rNow passing GEO!') Above_GEO = True if r > R_MOON and Above_MOON == False: if verbose: print('\rWe\'re over the Moon!!') Above_MOON = True if r < rmax / 2.: # Make sure it isn't coming back OrbitType = 'Geocentric' break t_soi = [t[-1]] # t_soi = [t[-1], # Save time for plotting reference ## Time('2010-06-13T13:35:00.0',format='isot',scale='utc').jd, # Time('2010-06-09T06:04:00.0',format='isot',scale='utc').jd] if OrbitType == 'Geocentric': # Convert to geocentric orbital elements [Pos_ECI, Vel_ECI] = OrbitalElements2PosVel(Y, 'Earth', 'Equinoctial') COE = PosVel2OrbitalElements(Pos_ECI, Vel_ECI, 'Earth', 'Classical') ra_corr = np.nan dec_corr = np.nan v_g = np.nan elif OrbitType == 'Heliocentric': if verbose: print('\rNow entering a Sun centred orbit...') # Convert the equinoctial elements to position and velocity [Pos_ECI, Vel_ECI] = OrbitalElements2PosVel(Y, 'Earth', 'Equinoctial') # Convert the ECI position and velocity to HCI coordinates [Pos_HCI, Vel_HCI] = ECI2HCI(Pos_ECI, Vel_ECI, t) ''' Numerically integrate until we get 10*R_SOI and back again ''' # Convert to heliocentric orbital elements EOE = PosVel2OrbitalElements(Pos_HCI, Vel_HCI, 'Sun', 'Equinoctial') Y = np.vstack((EOE, np.vstack((M, CA)) * np.ones( (1, np.shape(EOE)[1])))) # Integrate with RK4 until outside 10 times the sphere of influence ReachTenSOI = False # t0 = Time('2010-06-09T06:04:00.0', format='isot', scale='utc').jd # Time of TCM4 while t0 != t[-1]: # Runge Kutta Integration (Dormand-Prince) [Y_new, t_new, dt_new] = ODE45(SunDynamics, Y[:, -1:], t[-1], dt, Perturbations) # Save EOE's and time steps Y = np.hstack((Y, Y_new)) t = np.hstack((t, t_new)) dt = dt_new # Record the current HCI position and velocity [Pos_hci, Vel_hci] = OrbitalElements2PosVel(Y_new, 'Sun', 'Equinoctial') Pos_HCI = np.hstack((Pos_HCI, Pos_hci)) Vel_HCI = np.hstack((Vel_HCI, Vel_hci)) # Calculate the current distance from Earth r = norm(EarthPosition(t[-1]) - Pos_hci) # Print progress sys.stdout.write('\r%.3f%%' % (r / (10 * R_SOI) * 100)) sys.stdout.flush() if r > 10 * R_SOI and ReachTenSOI == False: if verbose: print( '\rWe reached 10*R_SOI, now we go... \nBack to the future!!!' ) ReachTenSOI = True dt = abs(dt) # Ensure the time step is now positive Perturbations = Perturbations + 10 # Encode the time step info if abs(t0 - t[-1]) < abs( dt): # This ensures we end intergration on epoch dt = (t0 - t[-1]) if abs(t[-1] - t0) > 3272.7: # This is 3272.7 days of flight time (max) print('\rExceeded maximum flight time:') print( 'Did not escape 10 times Earth\'s sphere of influence.\n') break COE = PosVel2OrbitalElements(Pos_HCI, Vel_HCI, 'Sun', 'Classical') [Pos_eci, Vel_eci] = HCI2ECI(Pos_hci, Vel_hci, t[-1]) ra_corr = float(np.arctan2(-Vel_eci[1], -Vel_eci[0])) dec_corr = float(np.arcsin(-Vel_eci[2] / norm(Vel_eci))) v_g = norm(Vel_eci) if COE[0, -1] < 0 and COE[1, -1] > 1: OrbitType = 'Hyperbolic' print('\r' + OrbitType + ' orbit determined!') if verbose: print("--- %s seconds ---" % (time.time() - starttime)) # Create the orbit object DeterminedOrbit = OrbitObject(OrbitType, COE[0, -1] * u.m, COE[1, -1], COE[2, -1] * u.rad, COE[3, -1] * u.rad, COE[4, -1] * u.rad, COE[5, -1] * u.rad, ra_corr * u.rad, dec_corr * u.rad, v_g * u.m / u.second) # Plots if required if Plot: if len(Pert) > 0: # Plot the perturbations over time # global Pert PlotPerts(Pert) # Plot dt PlotIntStep(t) # Plot the orbit in 3D PlotOrbit3D([DeterminedOrbit], t0, Sol) # Plot the individual orbital elements PlotOrbitalElements(COE, t, t_soi, Sol) ephem_dict = None if ephem: # Also return the ephemeris dict t_max = np.argmin(t) t_jd = t[:t_max] pos_hci = Pos_HCI[:, :t_max] ephem_dict = generate_ephemeris(pos_hci, t_jd) # from scipy.integrate import simps # global Pert#, time_tot, pert_tot # time_tot = (t.max() - t.min()) * u.d # if (Perturbations - 10) == True: # pert_tot = (-24*60*60 * simps(Pert[1,1:] + Pert[0,1:], t[1:Pert.shape[1]])) * (u.m / u.second) # else: # pert_tot = 0 return DeterminedOrbit, ephem_dict #, time_tot, pert_tot
def propagateOrbit(stateVec, Plot=False, Sol='NoSol', verbose=True, ephem=False): ''' The CelpechaOrbit function calculates the origin of the meteor by correcting the magnitude and direction of the velocity vector to exclude Earth's influencing effects. ''' Pos_geo = stateVec.position Vel_geo = stateVec.vel_xyz t0 = stateVec.epoch ''' Convert from geo to ECI coordinates ''' [Pos_ECI, Vel_ECI] = ECEF2ECI(Pos_geo, Vel_geo, t0) ''' Calculate V_g, the geocentric velocity outside Earth's influence ''' V_inf = norm(Vel_ECI) print('V inf : {0:.1f}'.format(V_inf)) V_esc = np.sqrt(2 * mu_e / norm(Pos_ECI)) V_g = np.sqrt(V_inf**2 - V_esc**2) ''' Calculate the zenith attraction due to Earth's influence ''' # Calculate the velocity vector in the ENU frame ra = float(np.arctan2(Pos_ECI[1], Pos_ECI[0])) dec = float(np.arcsin(Pos_ECI[2] / norm(Pos_ECI))) C_ENU2ECI = np.array( [[-np.sin(ra), -np.sin(dec) * np.cos(ra), np.cos(dec) * np.cos(ra)], [np.cos(ra), -np.sin(dec) * np.sin(ra), np.cos(dec) * np.sin(ra)], [0, np.cos(dec), np.sin(dec)]]) Vel_ENU = np.dot(C_ENU2ECI.T, Vel_ECI) # Calculate the azimuth and zenith angles a_c = np.arctan2(-Vel_ENU[0], -Vel_ENU[1]) z_c = np.arccos(-Vel_ENU[2] / V_inf) # Make zenith correction due to Earth's influence dz_c = 2 * np.arctan((V_inf - V_g) * np.tan(z_c / 2) / (V_inf + V_g)) z_g = z_c + dz_c a_g = a_c ''' Combine the zenith and velocity corrections ''' Vel_ENU = V_g * np.vstack( (-np.sin(z_g) * np.sin(a_g), -np.sin(z_g) * np.cos(a_g), -np.cos(z_g))) Vel_ECI = np.dot(C_ENU2ECI, Vel_ENU) ''' Convert from ECI to HCI coordinates ''' [Pos_HCI, Vel_HCI] = ECI2HCI(Pos_ECI, Vel_ECI, t0) ra_corr = float(np.arctan2(-Vel_ECI[1], -Vel_ECI[0])) dec_corr = float(np.arcsin(-Vel_ECI[2] / norm(Vel_ECI))) COE = PosVel2OrbitalElements(Pos_HCI, Vel_HCI, 'Sun', 'Classical') DeterminedOrbit = OrbitObject('Heliocentric', COE[0, -1] * u.m, COE[1, -1], COE[2, -1] * u.rad, COE[3, -1] * u.rad, COE[4, -1] * u.rad, COE[5, -1] * u.rad, ra_corr * u.rad, dec_corr * u.rad, V_g * u.m / u.second) # Plots if required if Plot: # Plot the orbit in 3D PlotOrbit3D([DeterminedOrbit], t0, Sol) ephem_dict = None if ephem: # Also return the ephemeris dict t_max = np.argmin(t) t_jd = t[:t_max] pos_hci = Pos_HCI[:, :t_max] ephem_dict = generate_ephemeris(pos_hci, t_jd) return DeterminedOrbit, ephem_dict