def mieMonteCarlo(wavelength=450 * 10**-9, r=300 * 10**-9, Vs=0.1, nParticle=1.46, nMedium=1.36): """ Calculate the scattering parameters relevant for monte carlo simulation. Needs pymiecoated: https://code.google.com/p/pymiecoated/ These are calculate scattering coefficient [1/cm] and anisotropy factor for given: Args ____ wavelength: wavelength of the incident light [m] r: radius of the particle [m] Vs: volume fraction of scattering particles nParticle: refractive index of the particle that the light wave is scattered on (default value is the refractive index of collagen) nMedium: refractive index of the surronding medium (default is that of colonic mucosal tissue) Returns: ____ {'us', 'g'}: scattering coefficient us [1/m] and anisotropy factor g TODO: _____ Additional input parameter specifying a FWHM for the wavelength to simulate the scattering for a broad filter """ # create derived parameters sizeParamter = 2 * math.pi * r / wavelength nRelative = nParticle / nMedium #%% execute mie and create derived parameters mie = Mie(x=sizeParamter, m=complex(nRelative, 0.0)) # 0.0 complex for no attenuation A = math.pi * r**2 # geometrical cross sectional area cs = mie.qsca() * A # scattering cross section us = Vs / (4 / 3 * r**3 * math.pi) * cs # scattering coefficient [m⁻1] return {'us': us, 'g': mie.asy()}
def test_p11(): ''' Test to plot a phase function, and make sure it is normalized properly ''' alam = 500*u.nm r = 10*u.micron x = (2*math.pi * r / alam).to('1').value num_theta = 1000 theta = hbt.frange(0,math.pi, num_theta) p11 = np.zeros(num_theta) phase = math.pi - theta # Theta is scattering angle nm_refract = complex(1.5, 0.1) mie = Mie(x=x, m=nm_refract) # This is only for one x value, not an ensemble qext = mie.qext() qsca = mie.qsca() qbak = mie.qb() for i,theta_i in enumerate(theta): (S1, S2) = mie.S12(np.cos(theta_i)) # Looking at code, S12 returns tuple (S1, S2). S3, S4 are zero for sphere. k = 2*pi / alam sigma = pi * r**2 * qsca # For (S1,S2) -> P11: p. 2 of http://nit.colorado.edu/atoc5560/week8.pdf # qsca = scattering efficiency. sigma = scattering cross-section p11[i] = 4 * pi / (k**2 * sigma) * ( (np.abs(S1))**2 + (np.abs(S2))**2) / 2 # Check the normalization of the resulting phase function. dtheta = theta[1] - theta[0] norm = np.sum(p11 * np.sin(theta) * dtheta) # This should be 2, as per TPW04 eq. 4 print('Normalized integral = {:.2f}'.format(norm)) plt.plot(phase*hbt.r2d, p11) plt.yscale('log') plt.title('X = {:.1f}'.format(x)) plt.xlabel('Phase angle') plt.ylabel('$P_{11}$') plt.show() p = plt.plot([0,10], [0,10]) currentAxis = plt.gca() currentAxis.add_patch(Rectangle((0.5, 0.5), 0.7, 0.7,alpha=0.1, color='red')) plt.text(1, 1, 'Danger') plt.show()
def calc_Q_ext( x, m, type, y=[], m2=[], ): """ Calculate Q_ext. Can be dry, coated in water, or deliquescent with water :param x: dry particle size parameter :param m: complex index of refraction for particle :param y: wet particle size parameter :param m2: complex index of refraction for water :return: Q_ext :return: calc_type: how was particle treated? (e.g. dry, coated) """ from pymiecoated import Mie import numpy as np # Coated aerosol (insoluble aerosol that gets coated as it takes on water) if type == 'coated': if (y != []) & (m2 != []): all_particles_coat = [ Mie(x=x[i], m=m, y=y[i], m2=m2) for i in np.arange(len(x)) ] Q = np.array([particle.qext() for particle in all_particles_coat]) else: raise ValueError("type = coated, but y or m2 is empty []") # Calc extinction efficiency for dry aerosol (using r_md!!!! NOT r_m) # if singular, then type == complex, else type == array elif type == 'dry': all_particles_dry = [Mie(x=x, m=m) for x_i in x] Q = np.array([particle.qext() for particle in all_particles_dry]) # deliquescent aerosol (solute disolves as it takes on water) elif type == 'deliquescent': all_particles_del = [Mie(x=x[i], m=m[i]) for i in np.arange(len(x))] Q = np.array([particle.qext() for particle in all_particles_del]) return Q
def mie_analytical(radii, vacuum_wavelength, out_param='qsca', cross_section=False, **mie_params): """Returns the analytical values for the efficiencies using the `pymiecoated`-package. Pass additional parameters to the `Mie`-class using the `mie_params`, e.g. by writing `m=1.52` to pass the refractive index of the sphere. `out_param` can be each method of the `Mie`-class, e.g. 'qext', 'qsca' or 'qabs'. Use `cross_section=True` to return the related cross section instead of the efficiency.""" from pymiecoated import Mie import collections _is_iter = True if not isinstance(radii, collections.Iterable): _is_iter = False radii = np.array([radii]) out_vals = [] for radius in radii: x = 2 * np.pi * radius / vacuum_wavelength mie = Mie(x=x, **mie_params) out_func = getattr(mie, out_param) out_val = out_func() if cross_section: out_val = np.pi * np.square(radius) * out_val out_vals.append(out_val) if not _is_iter: return out_vals[0] return np.array(out_vals)
def mieMonteCarlo(wavelength = 450*10**-9, r = 300*10**-9, Vs = 0.1, nParticle = 1.46, nMedium = 1.36): """ Calculate the scattering parameters relevant for monte carlo simulation. Needs pymiecoated: https://code.google.com/p/pymiecoated/ These are calculate scattering coefficient [1/cm] and anisotropy factor for given: Args ____ wavelength: wavelength of the incident light [m] r: radius of the particle [m] Vs: volume fraction of scattering particles nParticle: refractive index of the particle that the light wave is scattered on (default value is the refractive index of collagen) nMedium: refractive index of the surronding medium (default is that of colonic mucosal tissue) Returns: ____ {'us', 'g'}: scattering coefficient us [1/m] and anisotropy factor g TODO: _____ Additional input parameter specifying a FWHM for the wavelength to simulate the scattering for a broad filter """ # create derived parameters sizeParamter = 2 * math.pi * r / wavelength nRelative = nParticle / nMedium #%% execute mie and create derived parameters mie = Mie(x=sizeParamter, m=complex(nRelative,0.0)) # 0.0 complex for no attenuation A = math.pi * r**2 # geometrical cross sectional area cs = mie.qsca() * A # scattering cross section us = Vs / (4/3 * r**3 * math.pi) * cs # scattering coefficient [m⁻1] return {'us': us, 'g': mie.asy()}
def mie(self, a=0.3): qsca = [] qabs = [] qext = [] mie = Mie() wl = np.arange(0.21, 1.2, .001) n_cu, k_cu, n_w, k_w = self.intp_data.intpdata(wl) for i in range(len(wl)): mie.x = a * 2 * np.pi / wl[i] temp_n = n_cu[i] / n_w[i] temp_k = k_cu[i] + k_w[i] 'print temp_n, temp_k' mie.m = complex(temp_n, temp_k) ''' mie.y = 3 * a * 2*np.pi/wl[i]; mie.m2 = complex(n_w[i], k_w[i]); ''' qsca.append(mie.qsca()) qabs.append(mie.qabs()) qext.append(mie.qb()) return wl, qsca, qabs, qext
def mie(self, a = 0.3): qsca = []; qabs = []; qext = []; mie = Mie(); wl = np.arange(0.21, 1.2, .001); n_cu, k_cu, n_w, k_w = self.intp_data.intpdata(wl); for i in range(len(wl)): mie.x = a * 2*np.pi/wl[i]; temp_n = n_cu[i]/n_w[i]; temp_k = k_cu[i]+k_w[i]; 'print temp_n, temp_k' mie.m = complex(temp_n, temp_k); ''' mie.y = 3 * a * 2*np.pi/wl[i]; mie.m2 = complex(n_w[i], k_w[i]); ''' qsca.append(mie.qsca()); qabs.append(mie.qabs()); qext.append(mie.qb()); return wl, qsca, qabs, qext;
if (1 == 1): extb = np.zeros((len(sbin) - 1, len(wavmin))) ssab = np.zeros((len(sbin) - 1, len(wavmin))) asyb = np.zeros((len(sbin) - 1, len(wavmin))) for nband in range(len(wavmin)): specbin = np.linspace(wavmin[nband], wavmax[nband], 50) for db in range(len(Deff)): qext = 0. ssa = 0. asy = 0. for wl in range(len(specbin)): sp = np.pi * Deff[db] / specbin[wl] k = kint(specbin[wl]) nd = ndint(specbin[wl]) mie = Mie(x=sp, m=complex(nd, k)) qext = qext + mie.qsca() + mie.qabs() qabs = mie.qabs() ssa = ssa + mie.qsca() / (mie.qsca() + mie.qabs()) asy = asy + mie.asy() extb[db, nband] = extb[db, nband] + (qext / len(specbin)) / ( 2. / 3. * rhop * Deff[db] * 1E-6) #cross section in m2/g ssab[db, nband] = ssab[db, nband] + (ssa / len(specbin)) asyb[db, nband] = asyb[db, nband] + (asy / len(specbin)) if (1 == 2): #method 2(slower) double integration /more precise prblem mie return nan for extrem coarse particles # calculate the mie parameter for every diameter of the range, averaged on spectral band extb = np.zeros((len(sbin) - 1, len(wavmin)))
# n_murk = complex(1.53, 0.007) - this is for 550 nm # n_store += [n_murk] # complex indices of refraction (n = n(bar) - ik) at ceilometer wavelength (910 nm) Hesse et al 1998 # n_water, _ = linear_interpolate_n('water', lam) # calculate size parameter for dry and wet x_dry = (2.0 * np.pi * r_md) / lam # x_store += [x_dry] # calculate swollen index of refraction using MURK # n_swoll = CIR_Hanel(n_water, n_murk, r_md, r_m) # Calc extinction efficiency for dry aerosol (using r_md!!!! NOT r_m) for aer_i, n_i in n_aerosol.iteritems(): all_particles_dry = [Mie(x=x_i, m=n_i) for x_i in x_dry] Q_dry[aer_i][lam_str] = np.array( [particle.qext() for particle in all_particles_dry]) # once 905 has been calcualted for aer_i, value in Q_dry.iteritems(): for lam_str_i in ceil_lambda_str: Q_diff[aer_i][ lam_str_i] = Q_dry[aer_i][lam_str_i] - Q_dry[aer_i]['905'] Q_ratio[aer_i][ lam_str_i] = Q_dry[aer_i][lam_str_i] / Q_dry[aer_i]['905'] # qsca, qabs are alternatives to qext # ----------------------------------------------- # Post processing, saving and plotting
def main(): import numpy as np from pymiecoated import Mie import matplotlib.pyplot as plt # ------------------------------------------------------------------- # Setup # setup ceil_lambda = [0.905e-06] # [m] # ceil_lambda = np.arange(0.69e-06, 1.19e-06, 0.05e-06) # [m] # ceil_lambda = np.arange(0.90e-06, 0.91e-06, 0.05e-08) # [m] # ceil_lambda = np.array(([0.90e-06, 0.91e-06, 0.92e-06])) # [m] B = 0.14 RH_crit = 0.38 # directories savedir = '/home/nerc/Documents/MieScatt/figures/' datadir = '/home/nerc/Documents/MieScatt/data/' # aerosol with relative volume - average from the 4 Haywood et al 2008 flights rel_vol = { 'Ammonium sulphate': 0.295, 'Ammonium nitrate': 0.325, 'Organic carbon': 0.38 } # all the aerosol types all_aer_order = [ 'Ammonium sulphate', 'Ammonium nitrate', 'Organic carbon', 'Biogenic', 'Generic NaCl', 'Soot', 'MURK' ] # all_aer = ['ammonium_sulphate', 'ammonium_nitrate', 'organic_carbon', 'biogenic', 'NaCl', 'soot'] all_aer = { 'Ammonium sulphate': 'red', 'Ammonium nitrate': 'orange', 'Organic carbon': [0.05, 0.9, 0.4], 'Biogenic': [0.05, 0.56, 0.85], 'Generic NaCl': 'magenta', 'Soot': 'brown' } # all_aer = ['soot'] # create dry size distribution [m] # r_md_microm = np.arange(0.03, 5.001, 0.001) # .shape() = 4971 # r_md_microm = np.arange(0.000 + step, 1.000 + step, step), when step = 0.005, .shape() = 200 step = 0.005 r_md_microm = np.arange(0.000 + step, 5.000 + step, step) r_md = r_md_microm * 1.0e-06 # RH array [fraction] # This gets fixed for each Q iteration (Q needs to be recalculated for each RH used) RH = 0.8 # densities of MURK constituents # [kg m-3] # range of densities of org carb is massive (0.625 - 2 g cm-3) # Haywood et al 2003 use 1.35 g cm-3 but Schkolnik et al., 2006 lit review this and claim 1.1 g cm-3 dens_amm_sulph = 1770 dens_amm_nit = 1720 dens_org_carb = 1100 # NOTE ABOVE # define array to store Q for each wavelength Q_dry = [] x_store = [] n_store = [] # save the Q(dry) curve for MURK? savedata = True # ----------------------------------------------- # Calculate Q for each lambda # ----------------------------------------------- # ------------------------------------------------------------------- # Process # calculate complex index of refraction for MURK species # output n is complex index of refraction (n + ik) n_aerosol = calc_n_aerosol(all_aer, ceil_lambda) # NOTE: Able to use volume in MURK equation instead of mass because, if mass is evenly distributed within a volume # then taking x of the mass = taking x of the volume. # after calculating volumes used in MURK, can find relative % and do volume mixing. # bulk complex index of refraction (CIR) for the MURK species using volume mixing method n_murk = calc_n_murk(rel_vol, n_aerosol) n_aerosol['MURK'] = n_murk all_aer['MURK'] = 'black' # add color for plotting # n_murk = complex(1.53, 0.007) - this is for 550 nm n_store += [n_murk] # complex indices of refraction (n = n(bar) - ik) at ceilometer wavelength (905 nm) Hesse et al 1998 # n_water, _ = linear_interpolate_n('water', lam) # swell particles using FO method # rm = np.ma.ones(RH.shape) - (B / np.ma.log(RH_ge_RHcrit)) r_m = 1 - (B / np.log(RH)) r_m2 = np.ma.power(r_m, 1. / 3.) r_m = np.ma.array(r_md) * r_m2 r_m_microm = r_m * 1.0e06 # calculate size parameter for dry and wet x_dry = (2.0 * np.pi * r_md) / ceil_lambda x_store += [x_dry] x_wet = (2.0 * np.pi * r_m) / ceil_lambda # calculate swollen index of refraction using MURK # n_swoll = CIR_Hanel(n_water, n_murk, r_md, r_m) # Calc extinction efficiency for dry aerosol (using r_md!!!! NOT r_m) # all_particles_dry = [Mie(x=x_i, m=n_murk) for x_i in x_dry] # Q_dry += [np.array([particle.qext() for particle in all_particles_dry])] Q_dry = {} for key, n_i in n_aerosol.iteritems(): all_particles_dry = [Mie(x=x_i, m=n_i) for x_i in x_dry] Q_dry[key] = np.array( [particle.qext() for particle in all_particles_dry]) # qsca, qabs # ----------------------------------------------- # Post processing, saving and plotting # ----------------------------------------------- # if running for single 905 nm wavelength, save the calculated Q if savedata == True: if type(ceil_lambda) == list: if ceil_lambda[0] == 0.905e-06: # save Q curve and radius [m] np.savetxt(datadir + 'calculated_Q_ext_905nm.csv', np.transpose(np.vstack((r_md, Q_dry['MURK']))), delimiter=',', header='radius,Q_ext') # plot fig = plt.figure(figsize=(6, 4)) for aer_i in all_aer_order: # plot it plt.semilogx(r_md_microm, Q_dry[aer_i], label=aer_i, color=all_aer[aer_i]) # plt.semilogx(r_md_microm, Q_dry, label='dry murk', color=[0,0,0]) # plt.semilogx(r_m_microm, Q_del, label='deliquescent murk (RH = ' + str(RH) + ')') # plt.semilogx(r_m_microm, Q_coat, label='coated murk (RH = ' + str(RH) + ')') # for aer_i, Q_dry_i in Q_dry.iteritems(): # # # plot it # plt.semilogx(r_md_microm, Q_dry_i, label=aer_i, color=all_aer[aer_i]) # # plt.semilogx(r_md_microm, Q_dry, label='dry murk', color=[0,0,0]) # # plt.semilogx(r_m_microm, Q_del, label='deliquescent murk (RH = ' + str(RH) + ')') # # plt.semilogx(r_m_microm, Q_coat, label='coated murk (RH = ' + str(RH) + ')') # plt.title('lambda = ' + str(ceil_lambda[0]) + 'nm') plt.xlabel(r'$r_{md} \/\mathrm{[\mu m]}$', labelpad=-10, fontsize=13) plt.xlim([0.05, 5.0]) plt.ylim([0.0, 5.0]) #plt.xlim([0.01, 0.2]) #plt.ylim([0.0, 0.1]) plt.ylabel(r'$Q_{ext,dry}$', fontsize=13) plt.legend(fontsize=8, loc='best') plt.tick_params(axis='both', labelsize=10) plt.grid(b=True, which='major', color='grey', linestyle='--') plt.grid(b=True, which='minor', color=[0.85, 0.85, 0.85], linestyle='--') plt.savefig(savedir + 'Q_ext_manyAer2_' + str(ceil_lambda[0]) + 'nm.png') print 'data dir is... ' + savedir + 'Q_ext_manyAer_' + str( ceil_lambda[0]) + 'nm.png' plt.tight_layout(h_pad=10.0) plt.close() # plot the radius # plot_radius(savedir, r_md, r_m) print 'END PROGRAM'
d_new = pow1d3(4 / 3 * np.pi / N) * a_eff #d_new = a_eff / 10 r *= (d_new / d_old) d_old = d_new # incident plane wave Ei = E_inc(E0, kvec, r) # direct incident field at dipoles alph = polarizability_LDR(d_new, m, kvec) # polarizability of dipoles A = interaction_A(k, r, alph) P = scipy.sparse.linalg.gmres(A, Ei)[0] Cext[ix] = C_ext(k, E0, Ei, P) * (lam / 100)**2 / 100 # Convert to um**2 mie = Mie(x=2 * np.pi * a_eff, m=n) Cext_mie[ix] = mie.qext() * np.pi * (a_eff / 2)**2 # Convert cross section del (A) evlukhin = spec.Spec.loadSpecFromASCII("./reference/sphere_r63nm.txt", ',') #Cext = numpy.divide(Cext, numpy.max(Cext)) #Cext_mie = numpy.divide(Cext_mie, numpy.max(Cext_mie)) #evlukhin.data = numpy.divide(evlukhin.data, numpy.max(evlukhin.data)) plt.figure(1) plt.plot(lambda_range, Cext) plt.plot(evlukhin.wavelengths, evlukhin.data) plt.plot(lambda_range, Cext_mie) plt.ylabel("$\sigma_{ext}$, $\mu$m$^2$") plt.xlabel("wavelength, nm")
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
S_r[aer_i] = np.empty(len(r_range_m)) S_r[aer_i][:] = np.nan # n_i = complex(1.47 + 0.099j) # fixed complex index of refraction i n_i = n_species[aer_i] for r_idx, r_i in enumerate(r_range_m): x_i = x_range[r_idx] # size parameter_i # print loop progress if r_idx in np.arange(0, 2100, 100): print r_idx # calculate Q_back and Q_ext efficiency for current size parameter and complex index of refraction 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 the lidar ratio S_r[aer_i][r_idx] = Q_ext / Q_back_alt # simple plot of S fig, ax = plt.subplots(1, 1, figsize=(7, 4)) for key, data in S_r.iteritems(): plt.loglog(r_range_um * 2.0, data, label=aer_labels[key]) # diameter [microns] # plt.xlim([0.01, 100.0]) plt.ylim([1.0, 10.0e5]) plt.xlim([0.01, 20])
# create excitation polarisation = np.array([1,0,0]) direction = np.array([0,0,1]) import mnpbempp.simulation.planewave.excitation exc = mnpbempp.simulation.planewave.excitation.exc( polarisation, direction) # loop over energies for j in range(0,n): j # solve with given excitation and wavelength sig = bem.solve( enei[j], exc( p, enei[j] ) ) # compute extinction ext[j] = exc.ext( sig ) ## plot solution and compare with mie solution from pymiecoated import Mie #radius of sphere R = 75 xs = 2*np.pi*R/enei ext_mie = np.array(ene, float) for imie in range(n): mie = Mie(x=xs[imie],eps=epstab[1](enei[imie])) ext_mie[imie]=mie.qext() #from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt #%matplotlib qt #plt.plot( ene, ext,'ro', ene, ext_mie , 'b' ) plt.plot(ene,ext/(1239.8*8*np.pi),'ro',ene,ext_mie,'b')
def main(): import numpy as np from pymiecoated import Mie import matplotlib.pyplot as plt # ------------------------------------------------------------------- # Setup # setup ceil_lambda = [0.91e-06] # [m] # ceil_lambda = np.arange(0.69e-06, 1.19e-06, 0.05e-06) # [m] # directories savedir = '/home/nerc/Documents/MieScatt/figures/' datadir = '/home/nerc/Documents/MieScatt/data/' # aerosol with relative volume - average from the 4 Haywood et al 2008 flights rel_vol = {'ammonium_sulphate': 0.295, 'ammonium_nitrate': 0.325, 'organic_carbon': 0.38} # create dry size distribution [m] # r_md_microm = np.arange(0.03, 5.001, 0.001) # .shape() = 4971 # r_md_microm = np.arange(0.000 + step, 1.000 + step, step), when step = 0.005, .shape() = 200 step = 0.0005 r_md_microm = np.arange(0.000 + step, 2.000 + step, step) r_md = r_md_microm * 1.0e-06 # RH array [fraction] # This gets fixed for each Q iteration (Q needs to be recalculated for each RH used) RH = 0.8 # densities of MURK constituents # [kg m-3] # range of densities of org carb is massive (0.625 - 2 g cm-3) # Haywood et al 2003 use 1.35 g cm-3 but Schkolnik et al., 2006 lit review this and claim 1.1 g cm-3 dens_amm_sulph = 1770 dens_amm_nit = 1720 dens_org_carb = 1100 # NOTE ABOVE # define array to store Q for each wavelength Q_dry = [] x_store =[] n_store=[] # ----------------------------------------------- # Calculate Q for each lambda # ----------------------------------------------- for lam in ceil_lambda: # ------------------------------------------------------------------- # Process # calculate complex index of refraction for MURK species # output n is complex index of refraction (n + ik) n_aerosol = calc_n_aerosol(rel_vol, lam) # NOTE: Able to use volume in MURK equation instead of mass because, if mass is evenly distributed within a volume # then taking x of the mass = taking x of the volume. # after calculating volumes used in MURK, can find relative % and do volume mixing. # bulk complex index of refraction (CIR) for the MURK species using volume mixing method n_murk = calc_n_murk(rel_vol, n_aerosol) # n_murk = complex(1.53, 0.007) - makes no sense as this is for 550 nm # n_store += [n_murk] # complex indices of refraction (n = n(bar) - ik) at ceilometer wavelength (910 nm) Hesse et al 1998 # n_water, _ = linear_interpolate_n('water', lam) # swell particles using FO method # rm = np.ma.ones(RH.shape) - (B / np.ma.log(RH_ge_RHcrit)) # r_m = 1 - (B / np.log(RH)) # r_m2 = np.ma.power(r_m, 1. / 3.) # r_m = np.ma.array(r_md) * r_m2 # r_m_microm = r_m * 1.0e06 # calculate size parameter for dry and wet x_dry = (2.0 * np.pi * r_md)/lam x_store += [x_dry] # x_wet = (2.0 * np.pi * r_m)/lam # calculate swollen index of refraction using MURK # n_swoll = CIR_Hanel(n_water, n_murk, r_md, r_m) # Calc extinction efficiency for dry aerosol (using r_md!!!! NOT r_m) all_particles_dry = [Mie(x=x_i, m=n_murk) for x_i in x_dry] Q_dry += [np.array([particle.qext() for particle in all_particles_dry])] # ----------------------------------------------- # Post processing, saving and plotting # ----------------------------------------------- # if running for single 910 nm wavelength, save the calculated Q if type(ceil_lambda) == list: if ceil_lambda[0] == 9.1e-07: # save Q curve and radius [m] np.savetxt(datadir + 'calculated_Q_ext_910nm.csv', np.transpose(np.vstack((r_md, Q_dry))), delimiter=',', header='radius,Q_ext') # plot fig = plt.figure(figsize=(7, 4.5)) for Q_i, lam in zip(Q_dry, ceil_lambda): # plot it plt.semilogx(r_md_microm, Q_i, label=str(lam) + 'm') # plt.semilogx(r_md_microm, Q_dry, label='dry murk', color=[0,0,0]) # plt.semilogx(r_m_microm, Q_del, label='deliquescent murk (RH = ' + str(RH) + ')') # plt.semilogx(r_m_microm, Q_coat, label='coated murk (RH = ' + str(RH) + ')') # # average Q_dry if multiple Q_drys were calculated # if Q_dry.__len__() != 1: # q = np.array(Q_dry) # Q_dry_avg = np.mean(q, axis=0) # ax = plt.semilogx(r_md_microm, Q_dry_avg, label='average', color='black', linewidth=2) plt.title('lambda = ' + str(ceil_lambda[0]) + '-' + str(ceil_lambda[-1]) + 'm, n = murk') plt.xlabel('radius [micrometer]', labelpad=-5) plt.xlim([0.05, 5.0]) plt.ylim([0.0, 5.0]) #plt.xlim([0.01, 0.2]) #plt.ylim([0.0, 0.1]) plt.ylabel('Q_ext') plt.legend(fontsize=8, loc='best') plt.grid(b=True, which='major', color='grey', linestyle='--') plt.grid(b=True, which='minor', color=[0.85, 0.85, 0.85], linestyle='--') plt.savefig(savedir + 'Q_ext_murk_' + str(ceil_lambda[0]) + '-' + str(ceil_lambda[-1]) + 'lam.png') plt.tight_layout() plt.close() # plot the radius # plot_radius(savedir, r_md, r_m) print 'END PROGRAM'
def scatter_mie_ensemble(nm_refract, n, r, ang_phase, alam, do_plot=False): """ Return the Mie scattering properties of an ensemble of particles. The size distribution may be any arbitrary n(r). The returned phase function is properly normalized s.t. \\int(0 .. pi) {P sin(alpha) d_alpha} = 2. Calculated using pymiecoated library. That library supports coated grains, but my functions do not. Parameters ------ nm_refract: Index of refraction. Complex. Imaginary component is typically positive, not negative. n: Particle number distribution. r: Particle size distribution. Astropy units. ang_phase: Scattering phase angle (array). alam: Wavelength. Astropy units. Return values ---- phase: Summed phase curve -- ie, P11 * n * pi * r^2 * qsca, summed. Array [num_angles] qsca: Scattering matrix. Array [num_radii]. Usually not needed. p11_out: Phase curve. Not summed. Array [num_angles, num_radii]. Usually not needed. """ num_r = len(n) num_ang = len(ang_phase) pi = math.pi k = 2*pi / alam qmie = np.zeros(num_r) qsca = np.zeros(num_r) qext = np.zeros(num_r) qbak = np.zeros(num_r) qabs = np.zeros(num_r) p11_mie = np.zeros((num_r, num_ang)) # Calc Q_ext *or* Q_sca, based on whether it's reflected or transmitted light x = (2*pi * r/alam).to('1').value print('Doing Mie code') # Mie code doesn't compute the phase function unless we ask it to, by passing dqv. for i,x_i in enumerate(x): mie = Mie(x=x_i, m=nm_refract) # This is only for one x value, not an ensemble qext[i] = mie.qext() qsca[i] = mie.qsca() qbak[i] = mie.qb() for j, ang_j in enumerate(ang_phase): (S1, S2) = mie.S12(np.cos(pi*u.rad - ang_j)) # Looking at code, S12 returns tuple (S1, S2). # For a sphere, S3 and S4 are zero. # Argument to S12() is scattering angle theta, not phase sigma = pi * r[i]**2 * qsca[i] # Now convert from S1 and S2, to P11: Use p. 2 of http://nit.colorado.edu/atoc5560/week8.pdf p11_mie[i, j] = 4 * pi / (k**2 * sigma) * ( (np.abs(S1))**2 + (np.abs(S2))**2) / 2 if (do_plot): for i, x_i in enumerate(x): plt.plot(ang_phase.to('deg'), p11_mie[i, :], label = 'X = {:.1f}'.format(x_i)) plt.title("P11, nm = {}".format(nm_refract)) plt.yscale('log') # plt.legend(loc='upper left') plt.xlabel('Phase Angle [deg]') plt.title('P11') plt.show() for i, x_i in enumerate(x): plt.plot(ang_phase.to('deg'), p11_mie[i, :] * n[i] * r[i]**2, label = 'X = {:.1f}'.format(x_i)) plt.title("P11 * n(r) * r^2, nm = {}".format(nm_refract)) plt.yscale('log') # plt.legend(loc='upper left') plt.xlabel('Phase Angle [deg]') plt.show() # Multiply by the size dist, and flatten the array and return just showing the angular dependence. # I(theta) = n(r) pi r^2 Q_sca P_11(theta) terms = np.transpose(np.tile(pi * n * r**2 * qsca, (num_ang,1))) phase_out = np.sum(p11_mie * terms, axis=0) # Remove any units from this phase_out = phase_out.value # Now normalize it. We want \int (0 .. pi) {P(alpha) sin(alpha) d_alpha} = 2 # The accuracy of the normalized result will depend on how many angular bins are used. # This normalization is d_ang = ang_phase - np.roll(ang_phase,1) d_ang[0] = 0 tot = np.sum(phase_out * np.sin(ang_phase) * d_ang.value) phase_out = phase_out * 2 / tot # Return everything return(phase_out, p11_mie, qsca)
def main(): # ------------------------------------------------------------------- # Setup # setup # ceil_lambda = [0.91e-06] # [m] # ceil_lambda = np.arange(0.69e-06, 1.19e-06, 0.05e-06) # [m] # ceil_lambda = np.arange(8.95e-07, 9.16e-07, 1.0e-09) # [m] ceil_lambda = np.array([905e-09, 1064e-09]) ceil_lambda_str = ['%d' % i for i in ceil_lambda * 1.0e9] # extra ='' extra = '_largelam' # directories savedir = '/home/nerc/Documents/MieScatt/figures/' datadir = '/home/nerc/Documents/MieScatt/data/' # aerosol with relative volume - average from the 4 Haywood et al 2008 flights rel_vol = { 'ammonium_sulphate': 0.295, 'ammonium_nitrate': 0.325, 'organic_carbon': 0.38 } # all the aerosol types # all_aer = ['ammonium_sulphate', 'ammonium_nitrate', 'organic_carbon', 'oceanic', 'biogenic', 'NaCl', 'soot'] aer_names = { 'ammonium_sulphate': 'Ammonium sulphate', 'ammonium_nitrate': 'Ammonium nitrate', 'organic_carbon': 'Organic carbon', 'NaCl': 'Generic NaCl', 'soot': 'Soot', 'MURK': 'MURK' } # all_aer = {'ammonium_sulphate': 'red', 'ammonium_nitrate':'orange', 'organic_carbon': 'green', # 'biogenic': 'cyan', 'NaCl': 'magenta', 'soot': 'brown'} all_aer = { 'ammonium_sulphate': 'red', 'ammonium_nitrate': 'orange', 'organic_carbon': 'green', 'NaCl': 'magenta', 'soot': 'brown', 'MURK': 'black' } all_aer_constits = [ 'ammonium_sulphate', 'ammonium_nitrate', 'organic_carbon', 'NaCl', 'soot' ] # create dry size distribution [m] # r_md_microm = np.arange(0.03, 5.001, 0.001) # .shape() = 4971 # r_md_microm = np.arange(0.000 + step, 1.000 + step, step), when step = 0.005, .shape() = 200 step = 0.005 r_md_microm = np.arange(0.000 + step, 10.000 + step, step) r_md = r_md_microm * 1.0e-06 # define array to store Q for each wavelength Q_dry = {} Q_diff = {} Q_ratio = {} # make each, first level key (i.e. Q_dry[firstkey]) the aerosol type, followed later by the wavelength [nm]. for aer_i in all_aer.iterkeys(): Q_dry[aer_i] = {} Q_diff[aer_i] = {} Q_ratio[aer_i] = {} for lam_i in ceil_lambda_str: Q_dry[aer_i][lam_i] = [] Q_diff[aer_i][lam_i] = [] Q_ratio[aer_i][lam_i] = [] # save the Q(dry) curve for MURK? savedata = False # ----------------------------------------------- # Calculate Q for each lambda # ----------------------------------------------- for lam, lam_str in zip(ceil_lambda, ceil_lambda_str): print '\n lam_i = ' + lam_str + ' nm' # ------------------------------------------------------------------- # Process # calculate complex index of refraction for MURK species # output n is complex index of refraction (n + ik) n_aerosol = calc_n_aerosol(all_aer_constits, lam) # NOTE: Able to use volume in MURK equation instead of mass because, if mass is evenly distributed within a volume # then taking x of the mass = taking x of the volume. # after calculating volumes used in MURK, can find relative % and do volume mixing. # bulk complex index of refraction (CIR) for the MURK species using volume mixing method n_murk = calc_n_murk(rel_vol, n_aerosol) n_aerosol['MURK'] = n_murk # n_murk = complex(1.53, 0.007) - this is for 550 nm # n_store += [n_murk] # complex indices of refraction (n = n(bar) - ik) at ceilometer wavelength (910 nm) Hesse et al 1998 # n_water, _ = linear_interpolate_n('water', lam) # calculate size parameter for dry and wet x_dry = (2.0 * np.pi * r_md) / lam # x_store += [x_dry] # calculate swollen index of refraction using MURK # n_swoll = CIR_Hanel(n_water, n_murk, r_md, r_m) # Calc extinction efficiency for dry aerosol (using r_md!!!! NOT r_m) for aer_i, n_i in n_aerosol.iteritems(): all_particles_dry = [Mie(x=x_i, m=n_i) for x_i in x_dry] Q_dry[aer_i][lam_str] = np.array( [particle.qext() for particle in all_particles_dry]) # once 910 has been calcualted for aer_i, value in Q_dry.iteritems(): for lam_str_i in ceil_lambda_str: Q_diff[aer_i][ lam_str_i] = Q_dry[aer_i][lam_str_i] - Q_dry[aer_i]['905'] Q_ratio[aer_i][ lam_str_i] = Q_dry[aer_i][lam_str_i] / Q_dry[aer_i]['905'] # qsca, qabs are alternatives to qext # ----------------------------------------------- # Post processing, saving and plotting # ----------------------------------------------- # plot # plot_one_aer_i plot_absolute(r_md_microm, Q_dry, ceil_lambda, ceil_lambda_str, all_aer_constits, aer_names, savedir, extra) plot_diff(r_md_microm, Q_diff, ceil_lambda, ceil_lambda_str, all_aer_constits, aer_names, savedir, extra) plot_ratio(r_md_microm, Q_ratio, ceil_lambda, ceil_lambda_str, all_aer_constits, aer_names, savedir, extra) # plot the radius # plot_radius(savedir, r_md, r_m) print 'END PROGRAM'
frac_lambert = 1 - frac_mie # Calc Q_ext *or* Q_sca, based on whether it's reflected or transmitted light n_refract = 1.33 m_refract = -0.001 nm_refract = complex(n_refract,m_refract) x = (2*pi * r/alam).to('1').value print('Doing Mie code') # Mie code doesn't compute the phase function unless we ask it to, by passing dqv. if DO_MIE: for i,x_i in enumerate(x): # Loop over particle size X, and get Q and P_11 for each size mie = Mie(x=x_i, m=nm_refract) # This is only for one x value, not an ensemble qext[i] = mie.qext() qsca[i] = mie.qsca() qbak[i] = mie.qb() (S1, S2) = mie.S12(np.cos(pi*u.rad - phase)) # Looking at code, S12 returns tuple (S1, S2). # For a sphere, S3 and S4 are zero. # Argument to S12() is scattering angle theta, not phase k = 2*pi / alam sigma = pi * r[i]**2 * qsca[i] # Just a guess here # Now convert from S1 and S2, to P11: Use p. 2 of http://nit.colorado.edu/atoc5560/week8.pdf p11_mie[i] = 4 * pi / (k**2 * sigma) * ( (np.abs(S1))**2 + (np.abs(S2))**2) / 2
def main(): """ Carry out a sensitivity analysis on the Mie scattering code :return: plots Mie scattering curve of murk, deliquecent and coated spheres at 80 % RH. """ import numpy as np from pymiecoated import Mie import matplotlib.pyplot as plt # ------------------------------------------------------------------- # Setup # setup ceil_lambda = 0.91e-06 # [m] B = 0.14 RH_crit = 0.38 # directories savedir = '/home/nerc/Documents/MieScatt/figures/sensitivity/' # aerosol with relative volume - varying manually - first one is average from haywood et al., 2008 rel_vol = { 'ammonium_sulphate': [0.295, 0.80, 0.10, 0.10], 'ammonium_nitrate': [0.325, 0.10, 0.80, 0.10], 'organic_carbon': [0.38, 0.10, 0.10, 0.80] } # create dry size distribution [m] r_md_microm = np.arange(0.03, 5.001, 0.001) r_md = r_md_microm * 1.0e-06 # RH array [fraction] # This gets fixed for each Q iteration (Q needs to be recalculated for each RH used) RH = 0.8 # densities of MURK constituents # [kg m-3] # range of densities of org carb is massive (0.625 - 2 g cm-3) # Haywood et al 2003 use 1.35 g cm-3 but Schkolnik et al., 2006 lit review this and claim 1.1 g cm-3 dens_amm_sulph = 1770 dens_amm_nit = 1720 dens_org_carb = 1100 # NOTE ABOVE # ------------------------------------------------------------------- # Process # calculate complex index of refraction for each particle # output n is complex index of refraction (n + ik) n_aerosol = calc_n_aerosol(rel_vol, ceil_lambda) # NOTE: Able to use volume in MURK equation instead of mass because, if mass is evenly distributed within a volume # then taking x of the mass = taking x of the volume. # after calculating volumes used in MURK, can find relative % and do volume mixing. n_mixed = [] for i in range(len(rel_vol['ammonium_sulphate'])): # extract out just this one set of relative amounts rel_vol_i = {} for key in rel_vol.keys(): rel_vol_i[key] = rel_vol[key][i] # uses relative amounts in the MURK equation! n_mixed += [calc_n_murk(rel_vol_i, n_aerosol)] # complex indices of refraction (n = n(bar) - ik) at ceilometer wavelength (910 nm) Hesse et al 1998 n_water, _ = linear_interpolate_n('water', ceil_lambda) # swell particles using FO method # rm = np.ma.ones(RH.shape) - (B / np.ma.log(RH_ge_RHcrit)) r_m = 1 - (B / np.log(RH)) r_m2 = np.ma.power(r_m, 1. / 3.) r_m = np.ma.array(r_md) * r_m2 r_m_microm = r_m * 1.0e06 # calculate size parameter for dry and wet x_dry = (2.0 * np.pi * r_md) / ceil_lambda x_wet = (2.0 * np.pi * r_m) / ceil_lambda # # calculate swollen index of refraction using MURK # n_swoll = CIR_Hanel(n_water, n_murk, r_md, r_m) for j in range(len(n_mixed)): # Calc extinction efficiency for dry aerosol (using r_md!!!! NOT r_m) all_particles_dry = [Mie(x=x_i, m=n_mixed[j]) for x_i in x_dry] Q_dry = np.array([particle.qext() for particle in all_particles_dry]) # use proportions of each to scale the colours on the plot colours = [ rel_vol['ammonium_sulphate'][j], rel_vol['ammonium_nitrate'][j], rel_vol['organic_carbon'][j] ] lab = 'AS=' + str(rel_vol['ammonium_sulphate'][j]) + '; ' +\ 'AN=' + str(rel_vol['ammonium_nitrate'][j]) + '; ' +\ 'OC=' + str(rel_vol['organic_carbon'][j]) # plot it plt.semilogx(r_md_microm, Q_dry, color=colours, label=lab) plt.title('lambda = interpolated to ' + str(ceil_lambda) + 'm') plt.xlabel('radius [micrometer]') plt.xlim([0.03, 5.0]) plt.ylim([0.0, 5.0]) plt.ylabel('Q') plt.legend(fontsize=9) plt.savefig(savedir + 'Q_ext_murk_sensitivity_' + str(ceil_lambda) + 'lam.png') # plt.close() print 'END PROGRAM'
def get_optical_properties(rhop, filename): # user defined parameter #define the number (or mass) distribution (e.g from Kok et al). #diameter micron D = np.arange(0.001, 20, 0.005) if (1 == 2): D = np.arange(0.01, 65, 0.05) if (1 == 1): cN = 0.9539 Ds = 3.4 sigs = 3.0 lamb = 12 A = np.log(D / Ds) / (np.sqrt(2) * np.log(sigs)) #care dND/dD or dND/d(lnD) !! dND = (1 / D) * (1 / (cN * D * D)) * (1 + erf(A)) * np.exp(-1. * (D / lamb)**3) #dND = (D/12.62) * (1 + erf(A)) * np.exp(-1.*(D/lamb)**3) # here use a 3 log distribution # e.g. alfaro Gomez if (1 == 2): sig = [1.75, 1.75, 1.75] Dm = [0.64, 3.45, 8.67] Nf = [0.74, 0.20, 0.06] dND = ddlogn(D, Dm, sig, Nf) if (1 == 1): #define 2 size bins for each SOA category sbin = np.array([0.001, 0.03, 0.70]) if (1 == 2): #define the 12 new size bin cut of diam from LISA optimised distribution #you can take also one big bin encompassing the whole distrib (that 's what #we do for BC/OC, in this case it is equivalent to integrate over the full #distrib mode). #here is LISA optimal distrib for dust sbin = np.array([0.09,0.18, 0.6, 1.55, 2.50, 3.75, 4.70, 5.70, 7.50,\ 14.50, 26.0, 41.0, 63.0]) #sbin = [0.01 ,1.,2.5,5.,20. ] #sbin =[0.98,1.2,2.5,5.,20.] ## Uncomment the following line for testing # rhop, ndbib, wbib, kbib = return_test_variables() wbib, ndbib, kbib = np.loadtxt(soa_ri_path + filename, skiprows=0, unpack=True) #kbib = [0.04, 0.027866667, 0.020111111, 0.014933333, 0.001, 0.001, 0.001, 0.001, 0.003177778, 0.003033333, 0.003011111, 0.002911111, 0.003111111, 0.003111111, 0.003066667 , 0.0028,0.0025,0.002, 0.00195, 0.0019] #with this function we can simply interpolate kbib for every value of wavelenght ! kint = interp1d(wbib, kbib, kind='linear') ndint = interp1d(wbib, ndbib, kind='linear') ######### # define the spectral band ( wavmin and wavemax) of the radiation model # here from radiation RegCM standard regcm code wavmin = [ 0.2000, 0.2450, 0.2650, 0.2750, 0.2850, 0.2950, 0.3050, 0.3500, 0.6400, 0.7000, 0.7010, 0.7010, 0.7010, 0.7010, 0.7020, 0.7020, 2.6300, 4.1600, 4.1600 ] wavmax = [ 0.2450, 0.2650, 0.2750, 0.2850, 0.2950, 0.3050, 0.3500, 0.6400, 0.7000, 5.0000, 5.0000, 5.0000, 5.0000, 5.0000, 5.0000, 5.0000, 2.8600, 4.5500, 4.5500 ] #num = 7 for visible band standard scheme num = 7 ## si RRTM set 1 == 1 to overwrite #if(1==2): # # RRTM Shortwave spectral band limits (wavenumbers) # #wavenum are in cm-1 / take the inverse for wavelenght # wavenum1 = np.array([2600., 3250., 4000., 4650., 5150., 6150., 7700., \ # 8050.,12850.,16000.,22650.,29000.,38000., 820.]) # wavenum2 = np.array([3250., 4000., 4650., 5150., 6150., 7700., 8050., \ # 12850.,16000.,22650.,29000.,38000.,50000., 2600.]) # ## Longwave spectral band limits (wavenumbers) # if(1==2): # wavenum1 = np.array([ 10., 350., 500., 630., 700., 820., \ # 980. ,1080. ,1180. ,1390. ,1480. ,1800. , \ # 2080. ,2250. ,2380. ,2600. ]) # wavenum2 = np.array([350. , 500. , 630. , 700. , 820. , 980. , \ # 1080. ,1180. ,1390. ,1480. ,1800. ,2080. , \ # 2250. ,2380. ,2600. ,3250. ]) ## determined from indica marc for the longwave # wbib= [3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 30, 1100] # kbib = [7.54E-2, 5.14E-2, 5.94E-2, 1.41E-1, 1.46E-1, 2.18E-1, \ # 5.35E-1, 3.70E-1, 1.18E-1, 2.28E-1, 2.79E-1, 6.12E-1,\ # 4.95E-1, 6.50E-1 ] # nbib = [1.49, 1.51, 1.58, 1.45, 1.46, 1.19, 1.86, 1.84, 1.78, \ # 1.65, 1.55, 2.25, 2.40, 2. ] # kint = interp1d(wbib,kbib,kind='linear') # ndint = interp1d(wbib,nbib,kind='linear') # #convert to wavelenght in micron # # visible is index # wavmin = np.power(wavenum2*1.E-4,-1.) # wavmax = np.power(wavenum1*1.E-4,-1.) # # if (1==1): # num = 7 # for visible band standard scheme # if (1==2): # num = 9 # for vis RRTM #print num #print wavmin #print wavmax # end of user deined parameter ################################################################## ####################################################################3 # 1) calculate the effective radius of each bin (used in regcm ) Sv = np.zeros(len(sbin) - 1) Ss = np.zeros(len(sbin) - 1) normb = np.zeros(len(sbin) - 1) for b in range(len(sbin) - 1): for dd in range(len(D)): if (D[dd] >= sbin[b] and D[dd] < sbin[b + 1]): Sv[b] = Sv[b] + D[dd]**3 * dND[dd] Ss[b] = Ss[b] + D[dd]**2 * dND[dd] normb[b] = normb[b] + dND[dd] Deff = Sv / Ss print Deff ## visu # #fig = plt.figure() #ax = fig.add_subplot(1,3,1) ##here plot dN/d(logD) !! #ax.plot(D,dND *D , color='blue') #ax.set_yscale('log') #ax.set_xscale('log') #ax.set_xlim([0.08, 65]) #ax.set_ylim([1.E-4, 1]) #ax.set_title("distribution and bins, dot= Deff") #ax.set_xlabel('D in micrometer') #plot also bin end effrad #for x in sbin: # ax.plot([x, x],[1.E-4, 1], color='black') #for x in Deff: # ax.scatter(x,2E-4, color='green') # # ##visu also the ref index spectral variation interp + values #ax2 = fig.add_subplot(1,3,2) ##xx = np.linspace(0.2,13,500) #xx = np.linspace(3,40,500) #ax2.plot(xx, ndint(xx), color='green') ##ax2.scatter(wbib,kbib) #ax2.set_title(" im ref index, dot = data, line = interp used for oppt calc. ") #ax2.set_xlabel('wavelenght in micrometer') #2 mie calculation ###################################### #calculate optical properties per bin #######################################3 # methode 1 considering only bin effective diam calculated before if (1 == 1): extb = np.zeros((len(sbin) - 1, len(wavmin))) ssab = np.zeros((len(sbin) - 1, len(wavmin))) asyb = np.zeros((len(sbin) - 1, len(wavmin))) for nband in range(len(wavmin)): specbin = np.linspace(wavmin[nband], wavmax[nband], 50) for db in range(len(Deff)): qext = 0. ssa = 0. asy = 0. for wl in range(len(specbin)): sp = np.pi * Deff[db] / specbin[wl] k = kint(specbin[wl]) nd = ndint(specbin[wl]) mie = Mie(x=sp, m=complex(nd, k)) qext = qext + mie.qsca() + mie.qabs() qabs = mie.qabs() ssa = ssa + mie.qsca() / (mie.qsca() + mie.qabs()) asy = asy + mie.asy() extb[db, nband] = extb[db, nband] + (qext / len(specbin)) / ( 2. / 3. * rhop * Deff[db] * 1E-6) #cross section in m2/g ssab[db, nband] = ssab[db, nband] + (ssa / len(specbin)) asyb[db, nband] = asyb[db, nband] + (asy / len(specbin)) if (1 == 2): #method 2(slower) double integration /more precise prblem mie return nan for extrem coarse particles # calculate the mie parameter for every diameter of the range, averaged on spectral band extb = np.zeros((len(sbin) - 1, len(wavmin))) ssab = np.zeros((len(sbin) - 1, len(wavmin))) asyb = np.zeros((len(sbin) - 1, len(wavmin))) for nband in range(len(wavmin)): specbin = np.linspace(wavmin[nband], wavmax[nband], 10) extd = np.zeros(len(D)) ssad = np.zeros(len(D)) asyd = np.zeros(len(D)) for db in range(len(D)): qext = 0. ssa = 0. asy = 0. for wl in range(len(specbin)): sp = np.pi * D[db] / specbin[wl] k = kint(specbin[wl]) nd = ndint(specbin[wl]) mie = Mie(x=sp, m=complex(nd, k)) qext = qext + mie.qsca() + mie.qabs() ssa = ssa + mie.qsca() / (mie.qsca() + mie.qabs()) asy = asy + mie.asy() extd[db] = qext / len(specbin) / (2. / 3. * rhop * D[db] * 1E-6) ssad[db] = ssa / len(specbin) asyd[db] = asy / len(specbin) # perform the bin wighting av according to distibution for b in range(len(sbin) - 1): if (D[db] >= sbin[b] and D[db] < sbin[b + 1]): extb[b, nband] = extb[b, nband] + extd[db] * dND[db] / normb[b] ssab[b, nband] = ssab[b, nband] + ssad[db] * dND[db] / normb[b] asyb[b, nband] = asyb[b, nband] + asyd[db] * dND[db] / normb[b] # 3 nan out for the big bin which can have nan values .... # the kext is set very low which means that the bin won't matter much in the rad extb[np.isnan(extb)] = 1.E-20 asyb[np.isnan(asyb)] = 0.99 ssab[np.isnan(ssab)] = 0.5 print "extb vis", extb[:, num] print "ssab vis", ssab[:, num] print "asyb vis", asyb[:, num] #print "absb vis", extb[:,num] * ssab[:,num] ##4 visu oppt / bin # #ax3 = fig.add_subplot(1,3,3) #xx = np.linspace(0.2,4,500) #ax3.set_xlim([0.08,65 ]) #ax3.set_ylim([0, 5]) #for n in range(len(sbin)-1): # ax3.plot([sbin[n], sbin[n+1]],[extb[n,num],extb[n,num]], color='black') # ax3.plot([sbin[n], sbin[n+1]],[ssab[n,num],ssab[n,num]], color='blue') # ax3.plot([sbin[n], sbin[n+1]],[asyb[n,num],asyb[n,num]], color='red') # #ax3.set_title(" bin oppt vis: blk:kext, blue:ssa, red:asy ") #ax3.set_xscale('log') #ax3.set_xlabel('D in micrometer') # finally save in afile in a format close to mod_rad_aerosol block data # will just need copy paste + a few edit for fortan # file = open("aeroppt_blocl.txt", "w") compound = os.path.splitext(os.path.basename(filename))[0] np.savetxt(compound + '_Deff.txt', Deff, fmt='%7.5E', delimiter=', ') np.savetxt(compound + '_extb.txt', extb, fmt='%7.5E', delimiter=', ') np.savetxt(compound + '_ssab.txt', ssab, fmt='%7.5E', delimiter=', ') np.savetxt(compound + '_asyb.txt', asyb, fmt='%7.5E', delimiter=', ')