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
    }
Beispiel #2
0
    # 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
Beispiel #3
0
    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
Beispiel #11
0
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