'red': [6, 7, 8], 'orange': [9, 10, 11] } month_ls = {'-': [1, 4, 7, 10], '--': [2, 5, 8, 11], '-.': [12, 3, 6, 9]} year = 2015 # --------------------------------------------------- # Read, Process and save f(RH) # --------------------------------------------------- # RH RH_paths = [ datadir + 'WXT_KSSW_' + y + '_15min.nc' for y in ['2014', '2015'] ] RH_obs = eu.netCDF_read(RH_paths) # N(D) #use the Dn_accum. N_path = N_dir + 'accum_Ntot_Dn_NK_SMPS_' + str(year) + '.npy' N = np.load(N_path).flat[0] r_accum_m = N['Dv_accum'] / 2 / 1e9 # conv. to [m] from [nm] # f(RH) read in f_RH_data = {} rel_vol_species = {} rel_vol_time = {} for site in ['NK', 'Ch', 'Ha']: # read in f(RH) LUTs for each site path = fRHdir + dataRes + '_f(RH)_' + site + '_905.0nm.nc'
def read_wxt_obs(years, time): """ Read in RH observations from KSSW, time match them to the model data, and extend them in height to match the dimensions of model RH :param years: :param time: :return wxt_obs: """ met_vars = ['RH', 'Tair', 'press'] vars = met_vars + ['time'] filepath = ['C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/MorningBL/data/L1/' + \ 'Davis_BGH_' + str(i) + '_15min.nc' for i in years] wxt_obs_raw = eu.netCDF_read(filepath, vars=vars) # set up array to be filled wxt_obs = {} for met_var in met_vars: wxt_obs[met_var] = np.empty(len(time)) wxt_obs[met_var][:] = np.nan wxt_obs['time'] = time # find data region and create an average if appropriate print_step = range(1000,20000, 1000) for t, time_t in enumerate(time): if t in print_step: print 't ='+str(t) # time t-1 (start of original time period, as all data is relevent for time ENDING at time_t) tm1 = t-1 time_tm1 = time_t - dt.timedelta(minutes=60) # # start of time period # idx_extent = 8000 # s_idx = int(eu.binary_search(wxt_obs_raw['time'], time_tm1, lo=max(0, tm1 - idx_extent), # hi=min(tm1 + idx_extent, len(wxt_obs_raw['time'])))) # # end of time period # e_idx = int(eu.binary_search(wxt_obs_raw['time'], time_t, lo=max(0, t - idx_extent), # hi=min(t + idx_extent, len(wxt_obs_raw['time'])))) s_idx = int(eu.binary_search(wxt_obs_raw['time'], time_tm1)) # end of time period e_idx = int(eu.binary_search(wxt_obs_raw['time'], time_t)) # if the time_range time and data['time'] found in this iteration are within an acceptable range (15 mins) tm1_diff = time_tm1 - wxt_obs_raw['time'][s_idx] t_diff = time_t - wxt_obs_raw['time'][e_idx] # _, s_idx, tm1_diff = eu.nearest(wxt_obs_raw['time'], time_tm1) # _, e_idx, t_diff = eu.nearest(wxt_obs_raw['time'], time_t) if (tm1_diff.total_seconds() <= 15 * 60) & (t_diff.total_seconds() <= 15 * 60): for met_var in met_vars: wxt_obs[met_var][t] = np.nanmean(wxt_obs_raw[met_var][s_idx:e_idx+1]) # create RH_frac using RH data wxt_obs['RH_frac'] = wxt_obs['RH'] / 100.0 # calculate extra variables e_s_hpa = 6.112 * (np.exp((17.67 * wxt_obs['Tair']) / (wxt_obs['Tair'] + 243.5))) # [hPa] # sat. v. pressure e_s = e_s_hpa * 100.0 # [Pa] # sat. v. pressure wxt_obs['e'] = wxt_obs['RH_frac'] * e_s # [Pa] # v. pressure wxt_obs['r_v'] = wxt_obs['e'] / (1.61 * ((wxt_obs['press']*100.0) - wxt_obs['e'])) # water_vapour mixing ratio [kg kg-1] wxt_obs['q'] = wxt_obs['e'] / ((1.61 * ((wxt_obs['press']*100.0) - wxt_obs['e'])) + wxt_obs['e']) # specific humidity [kg kg-1] wxt_obs['Tv'] = (1 + (0.61 * wxt_obs['q'])) * (wxt_obs['Tair'] + 273.15) # virtual temp [K] wxt_obs['air_density'] = (wxt_obs['press']*100.0) / (286.9 * wxt_obs['Tv'])# [kg m-3] return wxt_obs
def main(): # Read in the mass data for 2016 # Read in RH data for 2016 # convert gases and such into the aerosol particles # swell the particles based on the CLASSIC scheme stuff # use Mie code to calculate the backscatter and extinction # calculate lidar ratio # plot lidar ratio # ============================================================================== # Setup # ============================================================================== # which modelled data to read in model_type = 'UKV' # directories maindir = '/home/nerc/Documents/MieScatt/' datadir = '/home/nerc/Documents/MieScatt/data/' savedir = maindir + 'figures/LidarRatio/' # data wxtdatadir = datadir massdatadir = datadir ffoc_gfdir = datadir # RH data wxt_inst_site = 'WXT_KSSW' # data year year = '2016' # aerosol particles to calculate (OC = Organic carbon, CBLK = black carbon, both already measured) # match dictionary keys further down aer_particles = ['(NH4)2SO4', 'NH4NO3', 'NaCl', 'CORG', 'CBLK'] all_species = ['(NH4)2SO4', 'NH4NO3', 'NaCl', 'CORG', 'CBLK', 'H2O'] # aer names in the complex index of refraction files aer_names = { '(NH4)2SO4': 'Ammonium sulphate', 'NH4NO3': 'Ammonium nitrate', 'CORG': 'Organic carbon', 'NaCl': 'Generic NaCl', 'CBLK': 'Soot', 'MURK': 'MURK' } # density of molecules [kg m-3] # CBLK: # Zhang et al., (2016) Measuring the morphology and density of internally mixed black carbon # with SP2 and VTDMA: New insight into the absorption enhancement of black carbon in the atmosphere # ORG: Range of densities for organic carbon is mass (0.625 - 2 g cm-3) # Haywood et al 2003 used 1.35 g cm-3 but Schkolink et al., 2006 claim the average is 1.1 g cm-3 after a lit review aer_density = { '(NH4)2SO4': 1770.0, 'NH4NO3': 1720.0, 'NaCl': 2160.0, 'CORG': 1100.0, 'CBLK': 1200.0 } # Organic carbon growth curve (Assumed to be the same as aged fossil fuel organic carbon # pure water density water_density = 1000.0 # kg m-3 # wavelength to aim for ceil_lambda = [0.905e-06] # ============================================================================== # Read data # ============================================================================== # read in the complex index of refraction data for the aerosol species (can include water) n_species = read_n_data(aer_particles, aer_names, ceil_lambda, getH2O=True) # Read in physical growth factors (GF) for organic carbon (assumed to be the same as aged fossil fuel OC) gf_ffoc_raw = eu.csv_read(ffoc_gfdir + 'GF_fossilFuelOC_calcS.csv') gf_ffoc_raw = np.array(gf_ffoc_raw)[1:, :] # skip header gf_ffoc = { 'RH_frac': np.array(gf_ffoc_raw[:, 0], dtype=float), 'GF': np.array(gf_ffoc_raw[:, 1], dtype=float) } # Read in species by mass data # Units are grams m-3 mass_in = read_mass_data(massdatadir, year) # Read WXT data wxtfilepath = wxtdatadir + wxt_inst_site + '_' + year + '_15min.nc' WXT_in = eu.netCDF_read(wxtfilepath, vars=['RH', 'Tair', 'press', 'time']) WXT_in['RH_frac'] = WXT_in['RH'] * 0.01 WXT_in['time'] -= dt.timedelta( minutes=15 ) # change time from 'obs end' to 'start of obs', same as the other datasets # Trim times # as WXT and mass data are 15 mins and both line up exactly already # therefore trim WXT to match mass time mass_in, WXT_in = trim_mass_wxt_times(mass_in, WXT_in) # Time match so mass and WXT times line up INTERNALLY as well date_range = eu.date_range(WXT_in['time'][0], WXT_in['time'][-1], 15, 'minutes') # make sure there are no time stamp gaps in the data so mass and WXT will match up perfectly, timewise. print 'beginning time matching for WXT...' WXT = internal_time_completion(WXT_in, date_range) print 'end time matching for WXT...' # same but for mass data print 'beginning time matching for mass...' mass = internal_time_completion(mass_in, date_range) print 'end time matching for mass...' # Create idealised number distribution for now... (dry distribution) # idealised dist is equal for all particle types for now. step = 0.005 r_range_um = np.arange(0.000 + step, 5.000 + step, step) r_range_m = r_range_um * 1.0e-06 r_mean = 0.11e-06 sigma = 0 # ============================================================================== # Process data # ============================================================================== # molecular mass of each molecule mol_mass_amm_sulp = 132 mol_mass_amm_nit = 80 mol_mass_nh4 = 18 mol_mass_n03 = 62 mol_mass_s04 = 96 # Convert into moles # calculate number of moles (mass [g] / molar mass) # 1e-06 converts from micrograms to grams. moles = { 'SO4': mass['SO4'] / mol_mass_s04, 'NO3': mass['NO3'] / mol_mass_n03, 'NH4': mass['NH4'] / mol_mass_nh4 } # calculate ammonium sulphate and ammonium nitrate from gases # adds entries to the existing dictionary mass = calc_amm_sulph_and_amm_nit_from_gases(moles, mass) # convert chlorine into sea salt assuming all chlorine is sea salt, and enough sodium is present. # potentially weak assumption for the chlorine bit due to chlorine depletion! mass['NaCl'] = mass['CL'] * 1.65 # convert masses from g m-3 to kg kg-1_air for swelling. # Also creates the air density and is stored in WXT mass_kg_kg, WXT = convert_mass_to_kg_kg(mass, WXT, aer_particles) # start with just 0.11 microns as the radius - can make it more fancy later... r_d_microns = 0.11 # [microns] r_d_m = r_d_microns * 1.0e-6 # [m] # calculate the number of particles for each species using radius_m and the mass # Hopefull not needed! num_part = {} for aer_i in aer_particles: num_part[aer_i] = mass_kg_kg[aer_i] / ( (4.0 / 3.0) * np.pi * (aer_density[aer_i] / WXT['dryair_rho']) * (r_d_m**3.0)) # calculate dry volume V_dry_from_mass = {} for aer_i in aer_particles: # V_dry[aer_i] = (4.0/3.0) * np.pi * (r_d_m ** 3.0) V_dry_from_mass[aer_i] = mass_kg_kg[aer_i] / aer_density[aer_i] # [m3] # if np.nan (i.e. there was no mass therefore no volume) make it 0.0 bin = np.isnan(V_dry_from_mass[aer_i]) V_dry_from_mass[aer_i][bin] = 0.0 # --------------------------------------------------------- # Swell the particles (r_md,aer_i) [microns] # set up dictionary r_md = {} # calculate the swollen particle size for these three aerosol types # Follows CLASSIC guidence, based off of Fitzgerald (1975) for aer_i in ['(NH4)2SO4', 'NH4NO3', 'NaCl']: r_md[aer_i] = calc_r_md_species(r_d_microns, WXT, aer_i) # set r_md for black carbon as r_d, assuming black carbon is completely hydrophobic r_md['CBLK'] = np.empty(len(date_range)) r_md['CBLK'][:] = r_d_microns # calculate r_md for organic carbon using the MO empirically fitted g(RH) curves r_md['CORG'] = np.empty(len(date_range)) r_md['CORG'][:] = np.nan for t, time_t in enumerate(date_range): _, idx, _ = eu.nearest(gf_ffoc['RH_frac'], WXT['RH_frac'][t]) r_md['CORG'][t] = r_d_microns * gf_ffoc['GF'][idx] # ----------------------------------------------------------- # calculate abs volume of wetted particles (V_abs,wet,aer_i) # use the growth factors calculated based on r_d to calc V_wet from V_dry(mass, density) # calculate the physical growth factor, wetted particle density, wetted particle volume, ... # and water volume (V_wet - Vdry) GF = {} # aer_wet_density = {} V_wet_from_mass = {} V_water_i = {} for aer_i in aer_particles: # aer_particles: # physical growth factor GF[aer_i] = r_md[aer_i] / r_d_microns # # wet aerosol density # aer_wet_density[aer_i] = (aer_density[aer_i] / (GF[aer_i]**3.0)) + \ # (water_density * (1.0 - (1.0 / (GF[aer_i]**3.0)))) # wet volume, using the growth rate from r_d to r_md # if np.nan (i.e. there was no mass therefore no volume) make it 0.0 V_wet_from_mass[aer_i] = V_dry_from_mass[aer_i] * (GF[aer_i]**3.0) bin = np.isnan(V_wet_from_mass[aer_i]) V_wet_from_mass[aer_i][bin] = 0.0 # water volume contribution from just this aer_i V_water_i[aer_i] = V_wet_from_mass[aer_i] - V_dry_from_mass[aer_i] # --------------------------- # Calculate relative volume of all aerosol AND WATER (to help calculate n_mixed) # calculate total water volume V_water_2d = np.array(V_water_i.values( )) # turn into a 2D array (does not matter column order) V_water_tot = np.nansum(V_water_2d, axis=0) # combine volumes of the DRY aerosol and the water into a single 2d array shape=(time, substance) # V_abs = np.transpose(np.vstack([np.array(V_dry_from_mass.values()),V_water_tot])) # shape = (time, species) V_abs = np.transpose( np.vstack([ np.array([V_dry_from_mass[i] for i in aer_particles]), V_water_tot ])) # now calculate the relative volume of each of these (V_rel) # scale absolute volume to find relative volume of each (such that sum(all substances for time t = 1)) vol_sum = np.nansum(V_abs, axis=1) vol_sum[vol_sum == 0.0] = np.nan scaler = 1.0 / (vol_sum) # a value for each time step # if there is no mass data for time t, and therefore no volume data, then set scaler to np.nan bin = np.isinf(scaler) scaler[bin] = np.nan # Relative volumes V_rel = {'H2O': scaler * V_water_tot} for aer_i in aer_particles: V_rel[aer_i] = scaler * V_dry_from_mass[aer_i] # -------------------------------------------------------------- # Calculate relative volume of the swollen aerosol (to weight and calculate r_md) # V_wet_from_mass V_abs_aer_only = np.transpose( np.array([V_wet_from_mass[aer_i] for aer_i in aer_particles])) # now calculate the relative volume of each of these (V_rel_Aer_only) # scale absolute volume to find relative volume of each (such that sum(all substances for time t = 1)) vol_sum_aer_only = np.nansum(V_abs_aer_only, axis=1) vol_sum_aer_only[vol_sum_aer_only == 0.0] = np.nan scaler = 1.0 / (vol_sum_aer_only) # a value for each time step # if there is no mass data for time t, and therefore no volume data, then set scaler to np.nan bin = np.isinf(scaler) scaler[bin] = np.nan # Relative volumes V_rel_aer_only = {} for aer_i in aer_particles: V_rel_aer_only[aer_i] = scaler * V_wet_from_mass[aer_i] # for aer_i in aer_particles: # print aer_i # print V_rel[aer_i][-1] # -------------------------------------------------------------- # calculate n_mixed using volume mixing method # volume mixing for CIR (eq. 12, Liu and Daum 2008) n_mixed = np.array([V_rel[i] * n_species[i] for i in V_rel.iterkeys()]) n_mixed = np.sum(n_mixed, axis=0) # calculate volume mean radii from r_md,aer_i (weighted by V_rel,wet,aer_i) r_md_avg = np.array( [V_rel_aer_only[aer_i] * r_md[aer_i] for aer_i in aer_particles]) r_md_avg = np.nansum(r_md_avg, axis=0) r_md_avg[r_md_avg == 0.0] = np.nan # convert from microns to m r_md_avg_m = r_md_avg * 1e-6 # calculate the size parameter for the average aerosol size x_wet_mixed = (2.0 * np.pi * r_md_avg_m) / ceil_lambda[0] # -------------------------- # calculate Q_back and Q_ext from the avergae r_md and n_mixed S = np.empty(len(date_range)) S[:] = np.nan for t, time_t in enumerate(date_range): x_i = x_wet_mixed[t] # size parameter_i n_i = n_mixed[t] # complex index of refraction i if t in np.arange(0, 35000, 500): print t if np.logical_and(~np.isnan(x_i), ~np.isnan(n_i)): particle = Mie(x=x_i, m=n_i) Q_ext = particle.qext() Q_back = particle.qb() # calculate the lidar ratio S_t = Q_ext / Q_back S[t] = Q_ext / Q_back # --------------------- # simple plot of S fig, ax = plt.subplots(1, 1, figsize=(6, 6)) plt.plot_date(date_range, S) plt.savefig(savedir + 'quickplot.png') plt.close(fig) # -------------------------- # Testing lidar ratio computation # read in Franco's computation of the lidar ratio CIR=1.47 + 0.099i, lambda=905nm lr_f = eu.netCDF_read( '/home/nerc/Documents/MieScatt/testing/lr_1.47_0.099_0.905.nc', ['DIAMETER', 'LIDAR_RATIO']) step = 0.005 r_range_um = np.arange(0.000 + step, 10.000 + step, step) r_range_m = r_range_um * 1.0e-06 x_range = (2.0 * np.pi * r_range_m) / ceil_lambda[0] # calculate Q_back and Q_ext from the avergae r_md and n_mixed #S_r = lidar ratio S_r = np.empty(len(r_range_m)) S_r[:] = np.nan for r_idx, r_i in enumerate(r_range_m): x_i = x_range[r_idx] # size parameter_i n_i = complex(1.47 + 0.099j) # fixed complex index of refraction i # print loop progress if r_idx in np.arange(0, 2100, 100): print r_idx particle = Mie(x=x_i, m=n_i) Q_ext = particle.qext() Q_back = particle.qb() Q_back_alt = Q_back / (4.0 * np.pi) # #Q_back = particle.qb() # S12 = particle.S12(-1) # S11 = S12[0].imag # S22 = S12[1].imag # Q_back_fancy = ((np.abs(S11)**2) + (np.abs(S22)**2))/(2 * np.pi * (x_i**2)) # calculate the lidar ratio # S_t = Q_ext / Q_back S_r[r_idx] = Q_ext / Q_back_alt # simple plot of S fig, ax = plt.subplots(1, 1, figsize=(6, 5)) plt.loglog(r_range_um * 2, S_r, label='mine') # diameter [microns] plt.loglog(lr_f['DIAMETER'], lr_f['LIDAR_RATIO'], label='Franco' 's') plt.xlim([0.01, 100.0]) plt.ylim([1.0, 10.0e7]) plt.ylabel('Lidar Ratio') plt.xlabel('Diameter [microns]') plt.legend() plt.tight_layout() plt.savefig(savedir + 'quickplot_S_vs_r.png') plt.close(fig) # ----------------------------------------------- d_test = 0.001e-06 r_test = d_test / 2.0 r_test_microns = r_test * 1.0e6 x_i = (2.0 * np.pi * r_test) / ceil_lambda[0] # size parameter_i n_i = complex(1.47 + 0.099j) # fixed complex index of refraction i particle = Mie(x=x_i, m=n_i) Q_ext = particle.qext() Q_back = particle.qb() Q_back_alt = Q_back / (4.0 * np.pi) # calculate extinction and scattering cross section C_ext = Q_ext * np.pi * (r_test_microns**2.0) C_back = Q_back * np.pi * (r_test_microns**2.0) C_back_alt = Q_back_alt * np.pi * (r_test_microns**2.0) S12 = particle.S12(-1) S11 = S12[0].imag S22 = S12[1].imag Q_back_fancy = ((np.abs(S11)**2) + (np.abs(S22)**2)) / (2 * np.pi * (x_i**2)) # calculate the lidar ratio S_t = Q_ext / Q_back S_test = Q_ext / Q_back_alt S_c_test = C_ext / C_back S_c_alt = C_ext / C_back_alt return
def test_lidar_computation(ceil_lambda, r_md_m): """ Test my computation of the lidar ratio against Franco's. Done for a monodisperse, soot(like?) aerosol :param ceil_lambda: :param r_md_m: :return: """ import ellUtils as eu # Testing lidar ratio computation # read in Franco's computation of the lidar ratio CIR=1.47 + 0.099i, lambda=905nm lr_f = eu.netCDF_read( '/home/nerc/Documents/MieScatt/testing/lr_1.47_0.099_0.905.nc', ['DIAMETER', 'LIDAR_RATIO']) step = 0.005 r_range_um = np.arange(0.000 + step, 10.000 + step, step) r_range_m = r_range_um * 1.0e-06 x_range = (2.0 * np.pi * r_range_m) / ceil_lambda[0] # calculate Q_back and Q_ext from the avergae r_md and n_mixed #S_r = lidar ratio S_r = np.empty(len(r_range_m)) S_r[:] = np.nan for r_idx, r_i in enumerate(r_range_m): x_i = x_range[r_idx] # size parameter_i n_i = complex(1.47 + 0.0j) # fixed complex index of refraction i # n_i = complex(1.47 + 0.099j) # fixed complex index of refraction i for soot # print loop progress if r_idx in np.arange(0, 2100, 100): print r_idx particle = Mie(x=x_i, m=n_i) Q_ext = particle.qext() Q_back = particle.qb() Q_back_alt = Q_back / (4.0 * np.pi) # #Q_back = particle.qb() # S12 = particle.S12(-1) # S11 = S12[0].imag # S22 = S12[1].imag # Q_back_fancy = ((np.abs(S11)**2) + (np.abs(S22)**2))/(2 * np.pi * (x_i**2)) # calculate the lidar ratio # S_t = Q_ext / Q_back S_r[r_idx] = Q_ext / Q_back_alt # simple plot of S fig, ax = plt.subplots(1, 1, figsize=(8, 7)) plt.loglog(r_range_um * 2, S_r, label='mine') # diameter [microns] plt.loglog(lr_f['DIAMETER'], lr_f['LIDAR_RATIO'], label='Franco' 's') for aer_i, r_md_m_aer_i in r_md_m.iteritems(): for r_i in r_md_m_aer_i: plt.vlines(r_i, 1, 1e6, linestyle='--', alpha=0.5) plt.xlim([0.01, 100.0]) plt.ylim([1.0, 10.0e7]) plt.ylabel('Lidar Ratio') plt.xlabel('Diameter [microns]') plt.legend() plt.tight_layout() plt.savefig(maindir + 'figures/LidarRatio/' + 'quickplot_S_vs_r_with_rbin_lines.png') plt.close(fig) return
high_idx = np.where(y[idx] > 4.0e-06)[0] y[idx][high_idx] # # check transmission - seems ok # filename = 'C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/clearFO/data/L1/CL31-D_CCW30_NK_2015_15min.nc' # data = eu.netCDF_read(filename) # _, t_idx, _ = eu.nearest(data['time'], dt.datetime(2015, 6, 4)) # plt.figure() # plt.plot_date(data['time'][t_idx-(24*4):t_idx+(48*4)], data['transmission'][t_idx-(24*4):t_idx+(48*14)]) # plt.ylabel('transmission for CL31-NK') # plt.xlabel('date [YYYY:mm:DD]') # check RH trend at the time # filename = 'C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/MorningBL/data/L1/Davis_IMU_2015_15min.nc' filename = 'C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/MorningBL/data/L1/WXT_KSSW_2015_15min.nc' data = eu.netCDF_read(filename) _, t_idx, _ = eu.nearest(data['time'], dt.datetime(2015, 6, 4)) all_range = np.arange(t_idx, t_idx + (110 * 1)) _, ts_idx, _ = eu.nearest(data['time'], dt.datetime(2015, 6, 4, 20, 0, 0)) _, te_idx, _ = eu.nearest(data['time'], dt.datetime(2015, 6, 5, 0, 0, 0)) focus_range = np.arange(ts_idx, te_idx) plt.figure(figsize=(11, 4)) ax = plt.gca() # 96 = 1 day of 15 min data # plt.plot_date(data['time'][t_idx:t_idx+(110*1)], data['RH'][t_idx:t_idx+(110*1)], color='blue') # all data # plt.plot_date(data['time'][ts_idx:te_idx], data['RH'][ts_idx:te_idx], color='red') # just those 5 hours plt.plot_date(data['time'][all_range], data['RH'][all_range], color='blue') # all data plt.plot_date(data['time'][focus_range],
def main(): # Read in the mass data for 2016 # Read in RH data for 2016 # convert gases and such into the aerosol particles # swell the particles based on the CLASSIC scheme stuff # use Mie code to calculate the backscatter and extinction # calculate lidar ratio # plot lidar ratio # ============================================================================== # Setup # ============================================================================== # which modelled data to read in model_type = 'UKV' res = FOcon.model_resolution[model_type] # directories maindir = 'C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/MorningBL/' datadir = 'C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/MorningBL/data/' massdatadir = 'C:/Users/Elliott/Documents/PhD Reading/PhD Research/Aerosol Backscatter/clearFO/data/ERG/' savedir = maindir + 'figures/LidarRatio/' # data wxtdatadir = datadir + 'L1/' # RH data wxt_inst_site = 'WXT_KSSW' # data year year = '2016' # aerosol particles to calculate (OC = Organic carbon, CBLK = black carbon, both already measured) # match dictionary keys further down aer_particles = ['(NH4)2SO4', 'NH4NO3', 'NaCl', 'CORG', 'CBLK'] # density of molecules [kg m-3] # CBLK: # Zhang et al., (2016) Measuring the morphology and density of internally mixed black carbon # with SP2 and VTDMA: New insight into the absorption enhancement of black carbon in the atmosphere # ORG: Range of densities for organic carbon is mass (0.625 - 2 g cm-3) # Haywood et al 2003 used 1.35 g cm-3 but Schkolink et al., 2006 claim the average is 1.1 g cm-3 after a lit review aer_density = { '(NH4)2SO4': 1770.0, 'NH4NO3': 1720.0, 'NaCl': 2160.0, 'CORG': 1100.0, 'CBLK': 1200.0 } # pure water density water_density = 1000.0 # kg m-3 # ============================================================================== # Read data # ============================================================================== # Read in species by mass data # Units are grams m-3 mass_in = read_mass_data(massdatadir, year) # Read WXT data wxtfilepath = wxtdatadir + wxt_inst_site + '_' + year + '_15min.nc' WXT_in = eu.netCDF_read(wxtfilepath, vars=['RH', 'Tair', 'press', 'time']) WXT_in['RH_frac'] = WXT_in['RH'] * 0.01 WXT_in['time'] -= dt.timedelta( minutes=15 ) # change time from 'obs end' to 'start of obs', same as the other datasets # Trim times # as WXT and mass data are 15 mins and both line up exactly already # therefore trim WXT to match mass time mass_in, WXT_in = trim_mass_wxt_times(mass_in, WXT_in) # Time match so mass and WXT times line up INTERNALLY as well date_range = eu.date_range(WXT_in['time'][0], WXT_in['time'][-1], 15, 'minutes') # make sure there are no time stamp gaps in the data so mass and WXT will match up perfectly, timewise. print 'beginning time matching for WXT...' WXT = internal_time_completion(WXT_in, date_range) print 'end time matching for WXT...' # same but for mass data print 'beginning time matching for mass...' mass = internal_time_completion(mass_in, date_range) print 'end time matching for mass...' # ============================================================================== # Process data # ============================================================================== # molecular mass of each molecule mol_mass_amm_sulp = 132 mol_mass_amm_nit = 80 mol_mass_nh4 = 18 mol_mass_n03 = 62 mol_mass_s04 = 96 # Convert into moles # calculate number of moles (mass [g] / molar mass) # 1e-06 converts from micrograms to grams. moles = { 'SO4': mass['SO4'] / mol_mass_s04, 'NO3': mass['NO3'] / mol_mass_n03, 'NH4': mass['NH4'] / mol_mass_nh4 } # calculate ammonium sulphate and ammonium nitrate from gases # adds entries to the existing dictionary mass = calc_amm_sulph_and_amm_nit_from_gases(moles, mass) # convert chlorine into sea salt assuming all chlorine is sea salt, and enough sodium is present. # potentially weak assumption for the chlorine bit due to chlorine depletion! mass['NaCl'] = mass['CL'] * 1.65 # convert masses from g m-3 to kg kg-1_air for swelling. # Also creates the air density and is stored in WXT mass_kg_kg, WXT = convert_mass_to_kg_kg(mass, WXT, aer_particles) # start with just 0.11 microns as the radius - can make it more fancy later... r_d_microns = 0.11 # [microns] r_d_m = r_d_microns * 1.0e-6 # [m] V_dry_from_r_d = (4.0 / 3.0) * np.pi * (r_d_m**3.0) # [m3] # calculate the number of particles for each species using radius_m and the mass # Hopefull not needed! num_part = {} for aer_i in aer_particles: num_part[aer_i] = mass_kg_kg[aer_i] / ( (4.0 / 3.0) * np.pi * (aer_density[aer_i] / WXT['dryair_rho']) * (r_d_m**3.0)) # calculate dry volume V_dry_from_mass = {} for aer_i in aer_particles: # V_dry[aer_i] = (4.0/3.0) * np.pi * (r_d_m ** 3.0) V_dry_from_mass[aer_i] = mass_kg_kg[aer_i] / aer_density[aer_i] # [m3] # --------------------------------------------------------- # Swell the particles (r_md,aer_i) [microns] # set up dictionary r_md = {} # calculate the swollen particle size for these three aerosol types # Follows CLASSIC guidence, based off of Fitzgerald (1975) for aer_i in ['(NH4)2SO4', 'NH4NO3', 'NaCl']: r_md[aer_i] = calc_r_md_species(r_d_microns, WXT, aer_i) # set r_md for black carbon as r_d, assuming black carbon is completely hydrophobic r_md['CBLK'] = np.empty(len(date_range)) r_md['CBLK'][:] = r_d_microns # calculate r_md for organic carbon using the MO empirically fitted g(RH) curves # r_md['CORG'] = ... # ----------------------------------------------------------- # calculate abs volume of wetted particles (V_abs,wet,aer_i) # use the growth factors calculated based on r_d to calc V_wet from V_dry(mass, density) # calculate the physical growth factor, wetted particle density, wetted particle volume, ... # and water volume (V_wet - Vdry) GF = {} aer_wet_density = {} V_wet_from_mass = {} V_water_i = {} for aer_i in ['(NH4)2SO4', 'NH4NO3', 'NaCl', 'CBLK']: # aer_particles: # physical growth factor GF[aer_i] = r_md[aer_i] / r_d_microns # wet aerosol density aer_wet_density[aer_i] = (aer_density[aer_i] / (GF[aer_i]**3.0)) + \ (water_density * (1.0 - (1.0 / (GF[aer_i]**3.0)))) # wet volume, using the growth rate from r_d to r_md V_wet_from_mass[aer_i] = V_dry_from_mass[aer_i] * (GF[aer_i]**3.0) # if np.nan (i.e. there was no mass therefore no volume) make it 0.0 bin = np.isnan(V_wet_from_mass[aer_i]) V_wet_from_mass[aer_i][bin] = 0.0 # water volume contribution from just this aer_i V_water_i[aer_i] = V_wet_from_mass[aer_i] - V_dry_from_mass[aer_i] # calculate total water volume V_water_2d = np.array(V_water_i.values()) # turn into a 2D array V_water_tot = np.nansum(V_water_2d, axis=0) # combine volumes of the DRY aerosol and the water into a single 2d array shape=(time, substance) V_abs = np.transpose( np.vstack([np.array(V_dry_from_mass.values()), V_water_tot])) # now calculate the relative volume of each of these # scale absolute volume to find relative volume of each (such that sum(all substances for time t = 1)) scaler = 1.0 / (np.nansum(V_abs, axis=1)) # a value for each time step scaler_rep = np.transpose(np.array([scaler] * 6)) # 2d array shape=(time, substance) V_rel = scaler_rep * V_abs # -------------------------------------------------------------- # calculate n_mixed using volume mixing method # calculate relative volume of all wet aerosol (V_rel,wet,aer_i) # calculate n_mixed using volume mixing and (V_rel,wet,aer_i) # calculate volume mean radii from r_md,aer_i (weighted by V_rel,wet,aer_i) # calculate Q_back and Q_ext from the avergae r_md and n_mixed # calculate lidar ratio # assume radii are 0.11 microns for now... return