コード例 #1
0
def plotter(caller, dir_path, self, dryf, cdt, sdt, min_size, max_size, csbn, p_rho):
	
	# inputs: ------------------------------------------------------------------
	# caller - marker for whether PyCHAM (0) or tests (2) are the calling module
	# dir_path - path to folder containing results files to plot
	# self - reference to GUI
	# dryf - whether particles dried (0) or not (1)
	# cdt - particle number concentration detection limit (particles/cm3)
	# sdt - particle size at 50 % detection efficiency (nm), 
	# 	width factor for detection efficiency dependence on particle size
	# min_size - minimum size measure by counter (nm)
	# max_size - maximum size measure by counter (nm)
	# csbn - number of size bins for counter
	# p_rho - assumed density of particles (g/cm3)
	# --------------------------------------------------------------------------

	# chamber condition ---------------------------------------------------------
	# retrieve results
	(num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, 
		y_mw, Nwet, _, y_MV, _, wall_on, space_mode, indx_plot, 
		comp0, _, PsatPa, OC, H2Oi, _, siz_str, _, _, _, _) = retr_out.retr_out(dir_path)
	
	# number of actual particle size bins
	num_asb = (num_sb-wall_on)

	if (caller == 0):
		plt.ion() # show results to screen and turn on interactive mode

	
	# prepare figure -------------------------------------------
	fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))
	
	par1 = ax0.twinx() # first parasite axis
	par2 = ax0.twinx() # second parasite axis
		
	# Offset the right spine of par2.  The ticks and label have already been
	# placed on the right by twinx above.
	par2.spines["right"].set_position(("axes", 1.2))
	# Having been created by twinx, par2 has its frame off, so the line of its
	# detached spine is invisible.  First, activate the frame but make the patch
	# and spines invisible.
	make_patch_spines_invisible(par2)
	# Second, show the right spine.
	par2.spines["right"].set_visible(True)	
	
	# ---------------------------------------------------------------
		
	# affect whether particle number concentration is for dry or wet inlet to instrument (# particles/cm3)
	if (dryf == 0):
		Nuse = Ndry
	else:
		Nuse = Nwet
	
	# if just one size bin, ensure two dimensional
	if (num_sb-wall_on) == 1:
		Nuse = Nuse.reshape(-1, 1)
		x = x.reshape(-1, 1)
	
	# account for minimum particle size detectable by instrument (um)
	Nuse[x<(min_size*1.e-3)] = 0.
	
	# if moving centre used rather than full moving then 
	# change Nuse, x and rbou_rec to a two-point moving average
	if (siz_str[0] == 0):
		# two point moving average number concentration
		Nuse = (Nuse[:, 0:-1]+Nuse[:, 1::])/2.
	
		if (space_mode == 'log'):
			# two point moving average size (radius) bin centres (um)
			x = 10.**(np.log10(x[:, 0:-1])+(np.log10(x[:, 1::])-np.log10(x[:, 0:-1]))/2.)
			# two point moving average size (radius) bin bounds (um)
			rbou_rec = 10.**(np.log10(rbou_rec[:, 0:-1])+(np.log10(rbou_rec[:, 1::])-np.log10(rbou_rec[:, 0:-1]))/2.)
			# fixed point centre (radius) of size bins (um)
			xf = 10.**(np.log10(rbou_rec[:, 0:-1])+(np.log10(rbou_rec[:, 1::])-np.log10(rbou_rec[:, 0:-1]))/2.)
	
		if (space_mode == 'lin'):
			# two point moving average size (radius) bin centres (um)
			x = x[:, 0:-1]+(x[:, 1::]-x[:, 0:-1])/2.
			# two point moving average size (radius) bin bounds (um)
			rbou_rec = rbou_rec[:, 0:-1]+(rbou_rec[:, 1::]-rbou_rec[:, 0:-1])/2.
			# fixed point centre (radius) of size bins (um)
			xf = rbou_rec[:, 0:-1]+(rbou_rec[:, 1::]-rbou_rec[:, 0:-1])/2.
	
	else: # if using full-moving then set the size bin centre radius (um) as the known
		xf = x 
	
	# difference in the log10 of size (diameter) bin widths of model (um) 
	# (could vary with time depending on size structure)
	sbwm = (np.log10(rbou_rec[:, 1::]*2.))-(np.log10(rbou_rec[:, 0:-1]*2.))

	# normalise number concentration by size (diameter) bin width (# particles/cm3/um)
	Nuse = Nuse/sbwm
	
	# interpolate particle number concentration to the counter size bins --------------------
	
	Nint = np.zeros((len(timehr), csbn)) # empty array to hold interpolated results (#/m3)
	
	# size bin bounds of SMPS (diameter) (um)
	csbb = (np.logspace(np.log10(min_size), np.log10(max_size), csbn+1, base = 10.))*1.e-3

	# size bin centres of instrument (diameter) (um)
	csbc = (10.**(np.log10(csbb[0:-1])+(np.log10(csbb[1::])-np.log10(csbb[0:-1]))/2.))
	
	# difference in log10 of size bins (diameter) bin widths of instrument (um)
	sbwc = np.log10(csbb[1::])-np.log10(csbb[0:-1])

	# interpolation based on fixed size bin centres - note that this is preferred over changeable
	# size bin centres when calculating number size distribution, total particle number 
	# concentration and total mass concentration, since the area under the
	# number vs size line is comparable between times with changeable size bin centres, note when
	# this is not the case, even when a particle grows when using moving centre size structure
	# the area beneath the curve may decrease because it becomes more jagged, thereby 
	# unrealistically affecting particle number concentration during interpolation
	for ti in range(len(timehr)):
		Nint[ti, :] = np.interp(csbc, xf[ti, :]*2., Nuse[ti, :]) # (# particles/cm3/difference in log 10(size(um)))
		Nint[ti, :] = Nint[ti, :]*sbwc # correct for size bin width (# particles/cm3)
	
	
	# account for minimum detectable particle concentration (# particles/cm3)
	Nint[Nint<cdt] = 0.
	
	# get detection efficiency as a function of particle size (nm)
	[Dp, ce] = count_eff_plot(3, 0, self, sdt)
	
	# interpolate detection efficiency (fraction) to instrument size bin centres
	ce = np.interp(csbc, Dp, ce)
	Nint = Nint*ce # correct for detection efficiency

	# plotting number size distribution --------------------------------------
	
	# take log10 of instrument size (diameter) bin boundaries
	log10D = np.log10(csbb)
	# take difference of log 10 of diameter at size bin boundaries
	dlog10D = log10D[1::]-log10D[0:-1]
	dlog10D = np.tile(dlog10D, [len(timehr), 1]) # tile over times
			
	# number size distribution contours ((# particle/cm3))
	dNdlog10D = np.zeros((Nint.shape[0], Nint.shape[1]))
	dNdlog10D[:, :] = Nint/dlog10D
	# transpose ready for contour plot
	dNdlog10D = np.transpose(dNdlog10D)
	
	# customised colormap (https://www.rapidtables.com/web/color/RGB_Color.html)
	colors = [(0.6, 0., 0.7), (0, 0, 1), (0, 1., 1.), (0, 1., 0.), (1., 1., 0.), (1., 0., 0.)]  # R -> G -> B
	n_bin = 100  # discretizes the colormap interpolation into bins
	cmap_name = 'my_list'
	# create the colormap
	cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)
	
	# set contour levels
	levels = (MaxNLocator(nbins = 100).tick_values(np.min(dNdlog10D), np.max(dNdlog10D)))
	
	# associate colours and contour levels
	norm1 = BoundaryNorm(levels, ncolors=cm.N, clip=True)
		
	# contour plot with times (hours) along x axis and 
	# particle diameters (nm) along y axis
	for ti in range(len(timehr)-1): # loop through times
		p1 = ax0.pcolormesh(timehr[ti:ti+2], (csbb*1e3), dNdlog10D[:, ti].reshape(-1, 1), cmap=cm, norm=norm1)
	
	# plot vertical axis logarithmically
	ax0.set_yscale("log")
	
	# set tick format for vertical axis
	ax0.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.0e'))
	ax0.set_ylabel('Diameter (nm)', size = 14)
	ax0.xaxis.set_tick_params(labelsize = 14, direction = 'in', which= 'both')
	ax0.yaxis.set_tick_params(labelsize = 14, direction = 'in', which= 'both')

		
	ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
	
	cb = plt.colorbar(p1, format=ticker.FuncFormatter(fmt), pad=0.25)
	cb.ax.tick_params(labelsize=14)   
	# colour bar label
	cb.set_label('dN (#$\,$$\mathrm{cm^{-3}}$)/d$\,$log$_{10}$(D ($\mathrm{\mu m}$))', size=14, rotation=270, labelpad=20)

	# total particle number concentration (# particles/cm3) -------------------------
	
	# include total number concentration (# particles/cm3 (air)) on contour plot
	Nvs_time = Nint.sum(axis = 1)
	
	p3, = par1.plot(timehr, Nvs_time, '-+k', label = 'N')
	
	par1.set_ylabel('N (#$\,$ $\mathrm{cm^{-3})}$', size=14, rotation=270, labelpad=20) # vertical axis label
	par1.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e')) # set tick format for vertical axis
	par1.yaxis.set_tick_params(labelsize=14)

	# mass concentration of particles (ug/m3) ---------------------------------------------------------------
	
	MCvst = np.zeros((len(timehr))) # empty array for total mass concentration (ug/m3)
	
	# assumed volumes of particles per size bin (um3)
	Vn = ((4./3.)*np.pi)*((csbc/2.)**3.)
	Vn = Vn*1.e-12 # convert to cm3
	Vn = np.tile(Vn, [len(timehr), 1]) # tile over times
	# convert number concentration to volume concentration (cm3 (particle)/cm3 (air))
	Vc = Nint*Vn
	# convert volume concentration to total mass concentration (ug/m3)
	MCvst = ((Vc*p_rho)*1.e12).sum(axis=1)
		
	# log10 of maximum in mass concentration
	if (max(MCvst[:]) > 0):
		MCmax = int(np.log10(max(MCvst[:])))
	else:
		MCmax = 0.
	
	p5, = par2.plot(timehr, MCvst[:], '-xk', label = 'Total Particle Mass Concentration')
	par2.set_ylabel(str('Mass Concentration ($\mathrm{\mu g\, m^{-3}})$'), rotation=270, size=16, labelpad=25)
	# set colour of label, tick font and corresponding vertical axis to match scatter plot presentation
	par2.yaxis.label.set_color('black')
	par2.tick_params(axis='y', colors='black')
	par2.spines['right'].set_color('black')
	par2.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e')) # set tick format for vertical axis
	par2.yaxis.set_tick_params(labelsize=16)
	plt.legend(fontsize=14, handles=[p3, p5] ,loc=4)

	if (caller == 2): # display when in test mode
		plt.show()	
コード例 #2
0
def cpc_plotter(caller, dir_path, self, dryf, cdt, max_dt, sdt, max_size, uncert, 
		delays, wfuncs, Hz, loss_func_str, losst, av_int, Q, tau, coi_maxD):

	import rad_resp_hum
	import inlet_loss
	
	# inputs: ------------------------------------------------------------------
	# caller - marker for whether PyCHAM (0) or tests (2) are the calling module
	# dir_path - path to folder containing results files to plot
	# self - reference to GUI
	# dryf - relative humidity of aerosol at entrance to condensing unit of CPC (fraction 0-1)
	# cdt - false background counts (# particles/cm3)
	# max_dt - maximum detectable actual concentration (# particles/cm3)
	# sdt - particle size at 50 % detection efficiency (nm), 
	# 	width factor for detection efficiency dependence on particle size
	# max_size - maximum size measure by counter (nm)
	# uncert - uncertainty (%) around counts by counter
	# delays - the significant response times for counter
	# wfuncs - the weighting as a function of time for particles of different age
	# Hz - temporal frequency of output
	# loss_func_str - string stating loss rate (fraction/s) as a 
	#			function of particle size (um)
	# losst - time of passage through inlet (s)
	# av_int - the averaging interval (s)
	# Q - volumetric flow rate through counting unit (cm3/s)
	# tau - instrument dead time (s)
	# coi_maxDp - maximum actual concentration that 
	# coincidence convolution applies to (# particles/cm3)
	# --------------------------------------------------------------------------

	# required outputs ---------------------------------------------------------
	# retrieve results
	(num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, 
		y_mw, Nwet, _, y_MV, _, wall_on, space_mode, indx_plot, 
		comp0, _, PsatPa, OC, H2Oi, _, siz_str, _, _, _, _) = retr_out.retr_out(dir_path)
	# ------------------------------------------------------------------------------
	
	# condition wet particles assuming equilibrium with relative humidity at 
	# entrance to condensing unit of CPC.  Get new radius at size bin centre (um)
	[xn, yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)]]  = rad_resp_hum.rad_resp_hum(yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)], x, dryf, H2Oi, num_comp, (num_sb-wall_on), Nwet, y_MV)
	
	# remove particles lost during transit through inlet (# particles/cm3)
	[Nwet,  yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)]]= inlet_loss.inlet_loss(0, Nwet, xn, yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)], loss_func_str, losst, num_comp)
	
	# all CPC output times, assuming first report is at 0 s through experiment
	times = np.arange(0, timehr[-1]*3600., 1./Hz)
	
	# empty array for holding corrected concentrations (# particles/cm3)
	Nwetn = np.zeros((len(times), Nwet.shape[1]))
	xnn = np.zeros((len(times), Nwet.shape[1])) # empty array for holding corrected diameters (um)
	
	# interpolate simulation output to instrument output frequency (# particles/cm3)
	# loop through size bins
	for sbi in range(num_sb-wall_on):
		Nwetn[:, sbi] = np.interp(times, timehr*3600., Nwet[:, sbi])
		xnn[:, sbi] = np.interp(times, timehr*3600., xn[:, sbi])
	
	Nwet = Nwetn # rename Nwet (# particles/cm3)
	xn = xnn # rename xn (um)
	
	# number of simulation outputs within the instrument response time
	rt_num = delays[2]/(times[1]-times[0])

	# if more than one output within response time, then loop through times to correct
	# for response time and any mixing of ages of particle
	# an explanation of response time and mixing of particles of different ages
	# due to the parabolic speed distribution in the CPC tubing is
	# given by Enroth et al. (2018) in: https://doi.org/10.1080/02786826.2018.1460458
	if (rt_num >= 1):
	
		# account for response time and mixing of particles of different ages
		[weight, weightt] = resp_time_func(3, delays, wfuncs)
	
		# empty array for holding corrected concentrations (# particles/cm3)
		Nwetn = np.zeros((Nwet.shape[0], Nwet.shape[1]))
		xnn = np.zeros((Nwet.shape[0], Nwet.shape[1]))
	
		for it in range(1, len(times)): # loop through times
			# number of time points to consider
			trel = (times >= (times[it]-delays[2]))*(times <= times[it])
			tsim = times[trel] # extract relevant time points (s)
			tsim = np.abs(tsim-tsim[-1]) # time difference with present (s)
			Nsim = Nwet[trel, :] # extract relevant number concentrations (# particles/cm3)
			xsim = xn[trel, :]
			# interpolate weights, use flip to align times
			weightn = np.flip(np.interp(np.flip(tsim), weightt, weight))
			if (np.diff(weightt) == 0).all(): # if weight is all on one time
				weightn[:] = 0
				# identify time closest to response time
				t_diff = np.abs(tsim - weightt[0])
				tindx = t_diff == np.min(t_diff)
				weightn[tindx] = 1.

			# tile across size bins
			weightn = np.tile(weightn.reshape(-1, 1), [1, num_sb-wall_on])
			# corrected concentration
			Nwetn[it, :] = np.sum(Nsim*weightn, axis=0)
			xnn[it, :] = np.sum(xsim*weightn, axis=0)
		
		Nwet = Nwetn # rename Nwet
		xn = xnn # size bin radii (um)
	
	# correct for coincidence (only relevant at relatively moderate 
	# concentrations (# particles/cm3)), using eq. 11 of
	# https://doi.org/10.1080/02786826.2012.737049
	# where Q is the volumetric flow (cm3/s) rate and tau is the instrument
	# dead time (s)
	# bypass if coincidence flagged to not be considered
	if ((Q == -1)*(tau == -1)*(coi_maxD == -1) != 1):

		from scipy.special import lambertw
	
		# product of actual concentration with volumetric flow rate and instrument dead time
		Ca = Nwet.sum(axis=1)
		# cannot invert the Lambert function (eq. 9 of https://doi.org/10.1080/02786826.2012.737049)
		# directly as do not know the imaginary part, but can identify closest point to real part as we
		# we know that measure count must lie between blank counts and actual concentration
		for it in range(len(times)): # time loop
			
			# bypass if actual total particle concentration (# particles/cm3)
			# exceeds maximum that coincidence applicable to or is less than 
			# blank concentration
			if (Ca[it] > cdt and Ca[it] < coi_maxD):
				
				# the possible measured counts (# particles/cm3)
				x_poss = np.logspace(np.log10(cdt), np.log10(coi_maxD), int(1e3))
				# account for volumetric flow rate and dead time
				x_possn = -x_poss*(Q*tau)
				# take the Lambert function and obtain just the real part
				x_possn = (-lambertw(x_possn).real)/(Q*tau)
				
				# zero any negatives as these are useless
				x_possn[x_possn<0] = 0
				
				# find point closest to actual concentration (# particles/cm3)
				# if all possibilities fall below the actual concentration, then 
				# the instrument will have marked this as a maximum
				if all(x_possn < Ca[it]):
					Cm = coi_maxD
				else:
					# linear interpolation
					diff = (Ca[it]-x_possn)
					indx1 = (diff == np.max(diff[diff<=0.]))
					indx0 = (diff == np.min(diff[diff>0.]))
					# the measured concentration (# particles/cm3)
					diff[indx1] = -1*diff[indx1] # make absolute
					Cm = (x_poss[indx0]*(diff[indx1])+x_poss[indx1]*(diff[indx0]))/(diff[indx1]+diff[indx0])
					
				# get the fraction underestimation due to coincidence
				frac_un = Cm/Ca[it]
				
				# correct across all size bins (# particles/cm3)
				Nwet[it, :] = Nwet[it, :]*(frac_un)
	
	# moving-average over averaging interval
	# number of outputs within averaging interval
	# note that using int here means rounding down, which is sensible
	av_num = int(av_int/times[1]-times[0])
	if (av_num > 1):
		
		# empty array to hold moving averages (# particles/cm3)
		Nwetn = np.zeros((int(Nwet.shape[0]-(av_num-1)), Nwet.shape[1]))
		# empty array to hold moving average diameters (um)
		xnn = np.zeros((int(Nwet.shape[0]-(av_num-1)), Nwet.shape[1]))
		
		for avi in range(av_num):
			# (# particles/cm3)
			Nwetn[:, :] += Nwet[avi:Nwet.shape[0]-(av_num-avi-1), :]/av_num
			# (um)
			xnn[:, :] += xn[avi:Nwet.shape[0]-(av_num-avi-1), :]/av_num
		
		# correct time (s)
		times = times[av_num-1::]
		
		# return to working variable names
		Nwet = Nwetn
		xn = xnn
	
	# account for size dependent detection efficiency below one
	# get detection efficiency as a function of particle size (nm)
	[Dp, ce] = count_eff_plot(3, 0, self, sdt)
	
	# empty array to hold detection efficiencies across times and simulation size bins
	# Dp is in um
	ce_t = np.zeros((len(times), xn.shape[1]))
	
	
	# loop through times
	for it in range(len(times)):
		# interpolate detection efficiency (fraction) to simulation size bin centres
		# Dp is in um
		ce_t[it, :] = np.interp(xn[it, :]*2., Dp, ce)
		
		# correct for upper size range of instrument, note conversion of
		# upper size from nm to um
		if (max_size != -1):
			size_indx = (xn[it, :]*2. > max_size*1.e-3)
			Nwet[it, size_indx] = 0.
	
	Nwet = Nwet*ce_t # correct for detection efficiency
	
	
	Nwet = Nwet.sum(axis=1) # sum particle concentrations (# particles/cm3)
	
	
	
	# account for false background counts 
	# (minimum detectable particle concentration)  (# particles/cm3)
	Nwet[Nwet < cdt] = cdt
	
	# account for maximum particle concentration (# particles/cm3)
	if (max_dt != -1): # if maximum particle concentration to be considered
		Nwet[Nwet > max_dt] = max_dt
	
	if (caller == 0): # when called from gui
		plt.ion() # show results to screen and turn on interactive mode
	
	# plot temporal profile of total particle number concentration (# particles/cm3)
	# prepare figure -------------------------------------------
	fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))
	
	ax0.plot(times/3600.0, Nwet, label = 'uncertainty mid-point')
	
	# plot vertical axis logarithmically
	ax0.set_yscale("log")
	
	# include uncertainty region, note conversion of uncertainty from percentage to fraction
	ax0.fill_between(times/3600., Nwet-Nwet*uncert/100., Nwet+Nwet*uncert/100., alpha=0.3, label = 'uncertainty bounds')
	
	# set tick format for vertical axis
	ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
	ax0.set_ylabel('Total Number Concentration (#$\mathrm{particles\, cm^{-3}}$)', size = 14)
	ax0.xaxis.set_tick_params(labelsize = 14, direction = 'in', which= 'both')
	ax0.yaxis.set_tick_params(labelsize = 14, direction = 'in', which= 'both')
	ax0.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e'))
	ax0.set_title('Simulated total particle concentration convolved to represent \ncondensation particle counter (CPC) measurements')
	ax0.legend()
	if (caller == 2): # display when in test mode
		plt.show()
	
	return()
コード例 #3
0
def RO2_av_molec(caller, dir_path, comp_names_to_plot, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0 for ug/m3 or 1 for ppb, 3 for # molecules/cm3) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, rel_SMILES, y_MW,
     _, comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, H2Oi, _,
     _, _, group_indx, _, _) = retr_out.retr_out(dir_path)

    y_MW = np.array(y_MW)  # convert to numpy array from list
    Cfac = (np.array(Cfac)).reshape(-1, 1)  # convert to numpy array from list

    if (caller == 0):
        plt.ion()  # show results to screen and turn on interactive mode

    # prepare plot
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    # get RO2 indices
    indx_plot = (np.array((group_indx['RO2i'])))

    # gas-phase concentration (# molecules/cm3)
    conc = yrec[:, indx_plot].reshape(yrec.shape[0],
                                      (indx_plot).shape[0]) * Cfac

    # sum of concentrations (# molecules/cm3)
    conc_sum = np.sum(conc, axis=1)

    # get SMILES of RO2 componenets
    rel_SMILES = rel_SMILES[indx_plot]

    # number of carbons and oxygens in each component
    Ccnt = []
    Ocnt = []
    Hcnt = []
    for i in rel_SMILES:
        Ccnt.append(i.count('C') + i.count('c'))
        Ocnt.append(i.count('O'))
        # generate pybel object
        Pybel_object = pybel.readstring('smi', i)
        try:
            Hi = (Pybel_objects[indx].formula).index('H')
            Hcnt = -1
        except:
            Hcnt = 0.0
        if (Hcnt == -1):
            try:
                Hcnt.append(float(Pybel_objects[indx].formula[Hi + 1:Hi + 3]))
            except:
                Hcnt.append(float(Pybel_objects[indx].formula[Hi + 1:Hi + 2]))
    Ccnt = np.tile(((np.array((Ccnt))).reshape(1, -1)), (conc.shape[0], 1))
    Ocnt = np.tile(((np.array((Ocnt))).reshape(1, -1)), (conc.shape[0], 1))
    Hcnt = np.tile(((np.array((Hcnt))).reshape(1, -1)), (conc.shape[0], 1))

    # average carbon number of organic peroxy radicals (RO2) in gas-phase at each time step
    Cav_cnt = ((np.sum(Ccnt * conc, axis=1)) / conc_sum)
    # average oxygen number of organic peroxy radicals (RO2) in gas-phase at each time step
    Oav_cnt = ((np.sum(Ocnt * conc, axis=1)) / conc_sum)
    # average hydrogen number of organic peroxy radicals (RO2) in gas-phase at each time step
    Hav_cnt = ((np.sum(Hcnt * conc, axis=1)) / conc_sum)

    ax0.plot(timehr, Cav_cnt, '-+', linewidth=4., label='Carbon number')
    ax0.plot(timehr, Oav_cnt, '-+', linewidth=4., label='Oxygen number')
    ax0.plot(timehr, Hav_cnt, '-+', linewidth=4., label='Hydrogen number')

    ax0.set_ylabel(r'Average number of atoms per RO2 molecule', fontsize=14)
    ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
    ax0.yaxis.set_tick_params(labelsize=14, direction='in')
    ax0.xaxis.set_tick_params(labelsize=14, direction='in')
    ax0.legend(fontsize=14)
コード例 #4
0
ファイル: plotter_atom_frac.py プロジェクト: simonom/PyCHAM
def plotter(caller, dir_path, atom_name, atom_num, self):  # define function

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # atom_name - SMILE string names of atom/functional group to target
    # atom_num - number of top contributors to plot
    # self - reference to GUI
    # --------------------------------------------------------------------------

    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, SMILES, y_mw, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, _, _, _, _,
     RO2i, _, _) = retr_out.retr_out(dir_path)

    # empty lists to contain results
    cnt_list = []
    ind_list = []

    if (atom_name != 'RO2'):  # if not organic peroxy radicals

        cn = 0  # count on components
        for ci in SMILES:  # loop through component names
            at_cnt = ci.count(atom_name)
            if (at_cnt > 0):  # if a contributor
                cnt_list.append(at_cnt)
                ind_list.append(int(cn))

            cn += 1
    else:  # if organic peroxy radicals

        cnt_list = [1] * len(RO2i)
        ind_list = RO2i

    # empty results array for contributing component index and number of occurrences
    res = np.zeros((len(cnt_list), 2))
    res[:, 0] = np.array((ind_list))  # index in first column
    res[:, 1] = np.array((cnt_list))  # count in second column
    res = res.astype(int)

    # reshape so that time in rows and components per size bin in columns
    yrec = yrec.reshape(len(timehr), num_comp * (num_sb + 1))
    # convert gas-phase concentrations to molecules/cm3 from ppb
    yrec[:, 0:num_comp] = yrec[:, 0:num_comp] * (np.array(
        (Cfac)).reshape(-1, 1))

    # extract just the relevant concentrations
    yrel = np.zeros((len(timehr), res.shape[0]))  # molecules/cm3
    nam_rel = []
    cin = 0  # count on the relevant components
    for ci in res[:, 0]:  # loop through relevant components

        yrel[:, cin] = np.sum(yrec[:, ci::num_comp], axis=1) * res[cin, 1]
        nam_rel.append(comp_names[ci])
        cin += 1

    # identify the components to be plotted
    ytot = np.sum(yrel, axis=0)
    ytot_asc = np.sort(ytot)
    cutoff = ytot_asc[
        -atom_num]  # the cutoff for the requested number of contributors
    yreln = yrel[:, ytot >= cutoff]
    nam_rel_indx = (np.where(ytot >= cutoff))[0]

    nam_up = []  # empty list
    for nri in nam_rel_indx:
        nam_up.append(nam_rel[nri])

    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))  # prepare figure

    for ci in range(len(yreln[0, :])):  # loop through relevant components
        if (any(yreln[:, ci] > 0.)):
            ax0.semilogy(timehr,
                         yreln[:, ci] / np.sum(yrel, axis=1),
                         label=nam_up[ci])

    # details of plot
    ax0.set_ylabel(r'Fraction contribution', fontsize=14)
    ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
    ax0.set_title(str('Fraction contribution to the atom/functional group ' +
                      str(atom_name)),
                  fontsize=14)
    ax0.yaxis.set_tick_params(labelsize=14, direction='in')
    ax0.xaxis.set_tick_params(labelsize=14, direction='in')
    ax0.legend(fontsize=14, loc='lower right')
    plt.show()

    return ()
コード例 #5
0
def plotter(caller, dir_path, comp_names_to_plot, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_MW, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, yrec_p2w, PsatPa, OC,
     H2Oi, _, _, _, _, _, _) = retr_out.retr_out(dir_path)

    # number of actual particle size bins
    num_asb = (num_sb - wall_on)

    if (caller == 0):
        plt.ion()  # show results to screen and turn on interactive mode

    # prepare plot
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    if (comp_names_to_plot):  # if component names specified

        # plotting section ---------------------------------------------
        for i in range(len(comp_names_to_plot)):

            if comp_names_to_plot[i].strip() == 'H2O':
                indx_plot = H2Oi
            else:
                try:  # will work if provided components were in simulation chemical scheme
                    # get index of this specified component, removing any white space
                    indx_plot = comp_names.index(comp_names_to_plot[i].strip())
                except:
                    self.l203a.setText(
                        str('Component ' + comp_names_to_plot[i] +
                            ' not found in chemical scheme used for this simulation'
                            ))
                    # set border around error message
                    if (self.bd_pl == 1):
                        self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                        self.bd_pl = 2
                    else:
                        self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                        self.bd_pl = 1

                    plt.ioff()  # turn off interactive mode
                    plt.close()  # close figure window
                    return ()

            if (wall_on == 1):
                # total concentration on wall (from particle deposition to wall) (molecules/cc)
                conc = (yrec_p2w[:, indx_plot::num_comp]).sum(axis=1)

            else:
                self.l203a.setText(
                    str('Wall not considered in this simulation'))

                # set border around error message
                if (self.bd_pl == 1):
                    self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                    self.bd_pl = 2
                if (self.bd_pl >= 2):
                    self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                    self.bd_pl = 1

                plt.ioff()  # turn off interactive mode
                plt.close()  # close figure window
                return ()

            # concentration in ug/m3
            conc = ((conc / si.N_A) * y_MW[indx_plot]) * 1.e12
            # plot this component
            ax0.plot(timehr,
                     conc,
                     '+',
                     linewidth=4.,
                     label=str(
                         str(comp_names[indx_plot] +
                             ' (wall (from particle deposition to wall))')))

        ax0.set_ylabel(r'Concentration ($\rm{\mu}$g$\,$m$\rm{^{-3}}$)',
                       fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

        # end of gas-phase concentration sub-plot ---------------------------------------

    # display
    if (caller == 2):
        plt.show()

    return ()
コード例 #6
0
ファイル: vol_contr_analys.py プロジェクト: simonom/PyCHAM
def plotter_2DVBS(caller, dir_path, self, t_thro):

    # inputs: -------------------------------
    # caller - the module calling (0 for gui)
    # dir_path - path to results
    # self - reference to GUI
    # t_thro - time (s) through experiment at which to pot the 2D-VBS
    # -----------------------------------------

    # ----------------------------------------------------------------------------------------
    if (caller == 0):  # if calling function is gui
        plt.ion()  # show figure

    # prepare plot
    fig, (ax1) = plt.subplots(1, 1, figsize=(10, 7))
    fig.subplots_adjust(hspace=0.7)

    # prepare plot data --------------------------------------
    # required outputs from full-moving
    (num_sb, num_comp, Cfac, y, Ndry, rbou_rec, xfm, t_array, rel_SMILES, y_mw,
     N, comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, H2Oi,
     seedi, _, _, _, _, _) = retr_out.retr_out(dir_path)

    # subtract recorded times from requested time and absolute
    t_diff = np.abs(t_thro - (t_array * 3600.))

    # find closest recorded time to requested time to plot
    t_indx = (np.where(t_diff == np.min(t_diff)))[0][0]

    # temperature for vapour pressures
    TEMP = 298.15

    # convert lists to numpy array
    y_mw = np.array((y_mw))
    PsatPa = np.array((PsatPa))

    # convert standard (at 298.15 K) vapour pressures in Pa to
    # saturation concentrations in ug/m3
    # using eq. 1 of O'Meara et al. 2014
    Psat_Cst = (1.e6 * y_mw) * (PsatPa / 101325.) / (8.2057e-5 * TEMP)

    # get particle concentrations at this time (molecules/cc)
    pc = y[t_indx, num_comp:num_comp * (num_sb - wall_on)]

    # zero water and seed components
    pc[H2Oi[0]::num_comp] = 0.
    for seed_indxs in seedi:
        pc[seed_indxs::num_comp] = 0.

    # tile molecular weights over size bins
    y_mw = np.tile(y_mw, (num_sb - wall_on - 1))

    # convert concentrations from molecules/cc to ug/m3
    pc = ((pc / si.N_A) * y_mw) * 1.e12

    # sum particle concentrations (ug/m3) at this time, without water and seed
    tot_pc = pc.sum()

    OC_range = np.arange(0., 2., 0.2)  # the O:C range
    VP_range = np.arange(
        -2.5, 7.5,
        1.)  # the log10 of the vapour pressure (ug/m3) at 298.15 K range

    # empty mass fractions matrix
    mf = np.zeros((len(OC_range), len(VP_range)))

    # convert list to array
    PsatPa = np.array((PsatPa))
    OC = np.array((OC))

    # loop over size bins to get total particle-phase
    # concentration of each component (ug/m3)
    for sbi in range(1, (num_sb - wall_on) - 1):
        pc[0:num_comp] += pc[num_comp * sbi:num_comp * (sbi + 1)]

    # forget all excess size bins (ug/m3)
    pc = pc[0:num_comp]

    # loop through O:C ratios and vapour pressures to estimate mass fractions
    for OCi in range(len(OC_range) - 1):

        VPo = 0.  # reset lower vapour pressure limit (ug/m3)

        for VPi in range(len(VP_range)):

            # upper vapour pressure limit now (ug/m3)
            VPn = 10**(VP_range[VPi] + 0.5)
            if (VPi == len(VP_range) -
                    1):  # final upper vapour pressure limit (ug/m3)
                VPn = np.inf
            if (OCi == len(OC_range) - 1):  # final upper O:C
                OC_up = np.inf
            else:
                OC_up = OC_range[OCi + 1]

            # index of all components with this vapour pressure and O:C ratio
            compi = ((Psat_Cst >= VPo) *
                     (Psat_Cst < VPn)) * ((OC >= OC_range[OCi]) * (OC < OC_up))

            # mass fractions of components with this combination of properties
            if (tot_pc > 0):
                mf[OCi, VPi] = sum(pc[compi]) / tot_pc

            VPo = VPn  # reset lower vapour pressure limit (ug/m3)

    # do the plotting ------------------------

    # customised colormap (https://www.rapidtables.com/web/color/RGB_Color.html)
    colors = [(0.6, 0., 0.7), (0, 0, 1), (0, 1., 1.), (0, 1., 0.),
              (1., 1., 0.), (1., 0., 0.)]  # R -> G -> B
    n_bin = 100  # discretizes the colormap interpolation into bins
    cmap_name = 'my_list'
    # create the colormap
    cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)

    # set contour levels
    levels = (MaxNLocator(nbins=100).tick_values(0., 1.))

    # associate colours and contour levels
    norm1 = BoundaryNorm(levels, ncolors=cm.N, clip=True)

    p0 = ax1.pcolormesh(VP_range,
                        OC_range,
                        mf,
                        cmap=cm,
                        norm=norm1,
                        shading='auto')

    cax = plt.axes([0.875, 0.40, 0.02, 0.18])  # specify colour bar position
    cb = plt.colorbar(p0,
                      cax=cax,
                      ticks=[0.00, 0.25, 0.50, 0.75, 1.00],
                      orientation='vertical')
    cb.ax.tick_params(labelsize=12)
    cb.set_label('mass fraction', size=12, rotation=270, labelpad=10.)

    ax1.set_xlabel(
        r'$\rm{log_{10}(}$$C*_{\mathrm{298.15 K}}$$\rm{\, (\mu g\, m^{-3}))}$',
        fontsize=14)
    ax1.set_ylabel(r'O:C ratio', fontsize=14, labelpad=10.)
    ax1.set_title(
        str('Mass fraction of non-water and non-seed components at ' +
            str(t_thro) + str(' s through experiment')),
        fontsize=14)

    ax1.yaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax1.xaxis.set_tick_params(labelsize=14, direction='in', which='both')

    # array containing the location of tick labels
    xtloc = VP_range
    ax1.set_xticks(xtloc)
    ytloc = OC_range
    ax1.set_yticks(ytloc)
    # prepare list of strings for the tick labels
    xtl = []
    for i in xtloc:
        if (i == np.min(xtloc)):  # if the minimum include less than sign
            xtl.append(str('$\less$' + str(i + 0.5)))
            continue
        if (i == np.max(xtloc)
            ):  # if the maximum include the greater than or equal to sign
            xtl.append(str('$\geq$' + str(i + 0.5)))
            continue
        xtl.append(str(i + 0.5))  # otherwise just state number

    ax1.set_xticklabels(xtl)

    if (caller != 0):
        plt.show()  # show figure

    return ()  # end function
コード例 #7
0
ファイル: plotter_ct.py プロジェクト: simonom/PyCHAM
def plotter_ind(caller, dir_path, comp_names_to_plot, top_num, uc, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # top_num - top number of chemical reactions to plot
    # uc - units to use for change tendency
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_mw, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, _, _, _, _,
     _, _, ro_obj) = retr_out.retr_out(dir_path)

    # loop through components to plot to check they are available
    for comp_name in (comp_names_to_plot):

        fname = str(dir_path + '/' + comp_name + '_rate_of_change')
        try:  # try to open
            dydt = np.loadtxt(fname, delimiter=',',
                              skiprows=0)  # skiprows = 0 skips first header
        except:
            mess = str(
                'Please note, a change tendency record for the component ' +
                str(comp_name) +
                ' was not found, was it specified in the tracked_comp input of the model variables file?  Please see README for more information.'
            )
            self.l203a.setText(mess)

            # set border around error message
            if (self.bd_pl == 1):
                self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                self.bd_pl = 2
            else:
                self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                self.bd_pl = 1

            plt.ioff()  # turn off interactive mode
            plt.close()  # close figure window

            return ()

    # if all files are available, then proceed without error message
    mess = str('')
    self.l203a.setText(mess)

    if (self.bd_pl < 3):
        self.l203a.setStyleSheet(0., '0px solid red', 0., 0.)
        self.bd_pl == 3

    # prepare figure
    plt.ion()  # display figure in interactive mode
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    for comp_name in (comp_names_to_plot):  # loop through components to plot

        if (comp_name != 'HOMRO2' and comp_name != 'RO2'):
            ci = comp_names.index(comp_name)  # get index of this component

        if (comp_name == 'HOMRO2'):
            # get mean molecular weight of HOMRO2 (g/mol)
            counter = 0  # count on HOMRO2 components
            mw_extra = 0.  # count on HOMRO2 molecular weights (g/mol)
            for cni in range(len(comp_names)):  # loop through all components
                if 'API_' in comp_names[cni] or 'api_' in comp_names[cni]:
                    if 'RO2' in comp_names[cni]:
                        mw_extra += y_mw[cni]
                        counter += 1
            y_mw = np.concatenate(
                (y_mw, (np.array(mw_extra / counter)).reshape(1)))
            ci = len(y_mw) - 1

        if (comp_name == 'RO2'):
            # get indices of RO2
            RO2indx = (np.array((ro_obj.gi['RO2i'])))
            # get mean molecular weight of RO2 (g/mol)
            mw_extra = np.sum((np.array((y_mw)))[RO2indx]) / len(RO2indx)

            y_mw = np.concatenate((y_mw, (np.array(mw_extra)).reshape(1)))
            ci = len(y_mw) - 1

        # note that penultimate column in dydt is gas-particle
        # partitioning and final column is gas-wall partitioning, whilst
        # the first row contains chemical reaction numbers

        # prepare to store results of change tendency due to chemical reactions
        res = np.zeros((dydt.shape[0], dydt.shape[1] - 2))
        res[:, :] = dydt[:, 0:
                         -2]  # get chemical reaction numbers and change tendencies

        if (uc == 0):
            Cfaca = (np.array(Cfac)).reshape(
                -1, 1)  # convert to numpy array from list
            # convert change tendencies from # molecules/cm3/s to ppb
            res[1::, :] = (res[1::, :] / Cfaca[1::])
            ct_units = str('(ppb/s)')
        if (uc == 1):
            # convert change tendencies from # molecules/cm3/s to ug/m3/s
            res[1::, :] = ((res[1::, :] / si.N_A) * y_mw[ci]) * 1.e12
            ct_units = str('(' + u'\u03BC' + 'g/m' + u'\u00B3' + '/s)')
        if (uc == 2):
            # keep change tendencies as # molecules/cm3/s
            res[1::, :] = res[1::, :]
            ct_units = str('\n(' + u'\u0023' + ' molecules/cm' + u'\u00B3' +
                           '/s)')

        # identify most active chemical reactions
        # first sum total change tendency over time (ug/m3/s)
        res_sum = np.abs(np.sum(res[1::, :], axis=0))

        # sort in ascending order
        res_sort = np.sort(res_sum)

        if (len(res_sort) < top_num[0]):

            # if less reactions are present than the number requested inform user
            mess = str(
                'Please note that ' + str(len(res_sort)) +
                ' relevant reactions were found, although a maximum of ' +
                str(top_num[0]) + ' were requested by the user.')
            self.l203a.setText(mess)

        # get all reactions out of the used chemical scheme --------------------------------------------------------------------
        import sch_interr  # for interpeting chemical scheme
        import re  # for parsing chemical scheme
        import scipy.constants as si

        sch_name = ro_obj.sp
        inname = ro_obj.vp

        f_open_eqn = open(sch_name, mode='r')  # open model variables file
        # read the file and store everything into a list
        total_list_eqn = f_open_eqn.readlines()
        f_open_eqn.close()  # close file

        inputs = open(inname, mode='r')  # open model variables file
        in_list = inputs.readlines(
        )  # read file and store everything into a list
        inputs.close()  # close file
        for i in range(len(in_list)
                       ):  # loop through supplied model variables to interpret

            # ----------------------------------------------------
            # if commented out continue to next line
            if (in_list[i][0] == '#'):
                continue
            key, value = in_list[i].split('=')  # split values from keys
            # model variable name - a string with bounding white space removed
            key = key.strip()
            # ----------------------------------------------------

            if key == 'chem_scheme_markers' and (
                    value.strip()):  # formatting for chemical scheme
                chem_sch_mrk = [str(i).strip() for i in (value.split(','))]

        # interrogate scheme to list equations
        [eqn_list, aqeqn_list, eqn_num, rrc, rrc_name,
         RO2_names] = sch_interr.sch_interr(total_list_eqn, chem_sch_mrk)

        for cnum in range(np.min([top_num[0], len(res_sort)
                                  ])):  # loop through chemical reactions

            # identify this chemical reaction
            cindx = np.where((res_sort[-(cnum + 1)] == res_sum) == 1)[0]

            for indx_two in (cindx):

                reac_txt = str(eqn_list[int(
                    res[0, indx_two])])  # get equation text
                # plot, note the +1 in the label to bring label into MCM index
                ax0.plot(timehr[0:-1],
                         res[1::, indx_two],
                         label=str(' Eq. # ' + str(int(res[0, indx_two]) + 1) +
                                   ':  ' + reac_txt))

        ax0.yaxis.set_tick_params(direction='in')

        ax0.set_title(
            'Change tendencies, where a tendency to decrease \ngas-phase concentrations is negative'
        )
        ax0.set_xlabel('Time through experiment (hours)')
        ax0.set_ylabel(str('Change tendency ' + ct_units))

        ax0.yaxis.set_tick_params(direction='in')
        ax0.xaxis.set_tick_params(direction='in')

        ax0.legend()

    return ()
コード例 #8
0
fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(12, 6.5), sharex='all')
fig.subplots_adjust(hspace=0.05)

# ------------------------------------------------------------------------------
# results - note that all results for table 1 saved in fig11_data
cwd = os.getcwd()  # get current working directory
try:  # if calling from the PyCHAM home directory
    output_by_sim = str(
        cwd +
        '/PyCHAM/output/GMD_paper_plotting_scripts/fig11_data/nuc_vsobs_output_fm_n1_2e4_n2_-4e2_n3_1e2'
    )
    # required outputs
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, xfm, time_array, comp_names,
     _, N, _, y_MV, _, wall_on, space_mode,
     _) = retr_out.retr_out(output_by_sim)

except:  # if calling from the GMD paper Results folder
    output_by_sim = str(
        cwd + '/fig11_data/nuc_vsobs_output_fm_n1_2e4_n2_-4e2_n3_1e2')
    # required outputs
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, xfm, time_array, comp_names,
     _, N, _, y_MV, _, wall_on, space_mode,
     _) = retr_out.retr_out(output_by_sim)

dlog10D = np.log10(rbou_rec[:, 1::] * 2.0) - np.log10(rbou_rec[:, 0:-1] * 2.0)
dNdD = Ndry / dlog10D  # normalised number size distribution (#/cc (air))

# observation part ---------------------------------------------------------------
import xlrd  # for opening xlsx file
コード例 #9
0
def plotter_CIMS(dir_path, res_in, tn, iont, sens_func):

    # inputs: -----------------
    # dir_path - path to folder containing results files to plot
    # res_in - inputs for resolution of molar mass to charge ratio (g/mol/charge)
    # tn - time through experiment to plot at (s)
    # iont - type of ionisation
    # sens_func - sensitivity to molar mass function
    # ---------------------------

    # retrieve results, note that num_sb (number of size bins)
    # includes wall if wall turned on
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_MW, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, H2Oi, _, _,
     _, RO2i, _, _) = retr_out.retr_out(dir_path)

    # convert to 2D numpy array
    y_MW = np.array((y_MW)).reshape(-1, 1)

    # get index of time wanted
    ti = (np.where(
        np.abs(timehr - tn / 3600.) == np.min(np.abs(timehr -
                                                     tn / 3600.))))[0][0]

    # convert yrec from 1D to 2D with times in rows, then select time wanted
    yrec = (yrec.reshape(len(timehr), num_comp * (num_sb + 1)))[ti, :]

    # get gas-phase concentrations (ppt, note starting concentration is ppb)
    gp = yrec[
        0:
        num_comp] * 1.e3  # conversion to ug/m3 (if wanted): /Cfac[ti]/si.N_A*y_MW[:, 0]*1.e12

    # get particle-phase concentrations (molecules/cm3)
    pp = yrec[num_comp:num_comp * (num_sb + 1 - wall_on)]

    # sum each component over size bins (molecules/cm3)
    pp = np.sum(pp.reshape(num_sb - wall_on, num_comp), axis=0)

    # convert to ppt
    pp = (pp /
          si.N_A) * Cfac[ti] * 1.e6  # or convert to ug/m3: *y_MW[:, 0]*1.e12

    # correct for sensitivity to molar mass
    fac_per_comp = write_sens2mm(0, sens_func, y_MW)

    gp = gp * fac_per_comp[:]
    pp = pp * fac_per_comp[:]

    # if ionisation source molar mass to be added
    # (e.g. because not corrected for in measurment software), then add
    if (int(iont[1]) == 1):
        if (iont[0] == 'I'
            ):  # (https://pubchem.ncbi.nlm.nih.gov/compound/Iodide-ion)
            y_MW += 126.9045
        if (iont[0] == 'N'
            ):  # (https://pubchem.ncbi.nlm.nih.gov/compound/nitrate)
            y_MW += 62.005

    # remove water
    gp = np.append(gp[0:H2Oi], gp[H2Oi + 1::])
    pp = np.append(pp[0:H2Oi], pp[H2Oi + 1::])
    y_MW = np.append(y_MW[0:H2Oi, 0], y_MW[H2Oi + 1::, 0])

    # account for mass to charge resolution
    [pdf, comp_indx, comp_prob, mm_all] = write_mzres(1, res_in, y_MW)
    gpres = np.zeros((len(comp_indx)))
    ppres = np.zeros((len(comp_indx)))

    for pdfi in range(len(comp_indx)):  # loop through resolution intervals
        gpres[pdfi] = np.sum(gp[comp_indx[pdfi]] * comp_prob[pdfi])
        ppres[pdfi] = np.sum(pp[comp_indx[pdfi]] * comp_prob[pdfi])

    plt.ion()  # disply plot in interactive mode

    # prepare plot
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    ax0.semilogy(mm_all,
                 gpres,
                 '+m',
                 markersize=14,
                 markeredgewidth=5,
                 label=str('gas-phase'))
    ax0.semilogy(mm_all,
                 ppres,
                 'xb',
                 markersize=14,
                 markeredgewidth=5,
                 label=str('particle-phase'))

    ax0.set_title(str('Mass spectrum at ' + str(timehr[ti]) + ' hours'),
                  fontsize=14)
    ax0.set_xlabel(r'Mass/charge (Th)', fontsize=14)
    ax0.set_ylabel(r'Concentration (ppt)', fontsize=14)
    ax0.xaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax0.yaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax0.legend(fontsize=14)

    return ()
コード例 #10
0
ファイル: plotter_ct.py プロジェクト: simonom/PyCHAM
def plotter(caller, dir_path, comp_names_to_plot, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_mw, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, _, _, _, _,
     _, _, _) = retr_out.retr_out(dir_path)

    # no record of change tendency for final experiment time point
    timehr = timehr[0:-1]

    # loop through components to plot to check they are available
    for comp_name in (comp_names_to_plot):

        fname = str(dir_path + '/' + comp_name + '_rate_of_change')
        try:  # try to open
            dydt = np.loadtxt(fname, delimiter=',',
                              skiprows=1)  # skiprows = 1 omits header
        except:
            mess = str(
                'Please note, a change tendency record for the component ' +
                str(comp_name) +
                ' was not found, was it specified in the tracked_comp input of the model variables file?  Please see README for more information.'
            )
            self.l203a.setText(mess)

            # set border around error message
            if (self.bd_pl == 1):
                self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                self.bd_pl = 2
            else:
                self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                self.bd_pl = 1

            plt.ioff()  # turn off interactive mode
            plt.close()  # close figure window

            return ()

    # if all files are available, then proceed without error message
    mess = str('')
    self.l203a.setText(mess)

    if (self.bd_pl < 3):
        self.l203a.setStyleSheet(0., '0px solid red', 0., 0.)
        self.bd_pl == 3

    # prepare figure
    plt.ion()  # display figure in interactive mode
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    for comp_name in (comp_names_to_plot):  # loop through components to plot

        if (comp_name != 'HOMRO2' and comp_name != 'RO2'):
            ci = comp_names.index(comp_name)  # get index of this component

        if (comp_name == 'HOMRO2'):
            # get mean molecular weight of HOMRO2 (g/mol)
            counter = 0  # count on HOMRO2 components
            mw_extra = 0.  # count on HOMRO2 molecular weights (g/mol)
            for cni in range(len(comp_names)):  # loop through all components
                if 'API_' in comp_names[cni] or 'api_' in comp_names[cni]:
                    if 'RO2' in comp_names[cni]:
                        mw_extra += y_mw[cni]
                        counter += 1
            y_mw = np.concatenate(
                (y_mw, (np.array(mw_extra / counter)).reshape(1)))
            ci = len(y_mw) - 1

        if (comp_name == 'RO2'):
            # get indices of RO2
            RO2indx = (np.array((ro_obj.gi['RO2i'])))
            # get mean molecular weight of RO2 (g/mol)
            mw_extra = np.sum((np.array((y_mw)))[RO2indx]) / len(RO2indx)

            y_mw = np.concatenate((y_mw, (np.array(mw_extra)).reshape(1)))
            ci = len(y_mw) - 1

        # note that penultimate column in dydt is gas-particle
        # partitioning and final column is gas-wall partitioning, whilst
        # the first row contains chemical reaction numbers
        # extract the change tendency due to gas-particle partitioning
        gpp = dydt[1::, -2]
        # extract the change tendency due to gas-wall partitioning
        gwp = dydt[1::, -1]
        # sum chemical reaction gains
        crg = np.zeros((dydt.shape[0] - 1, 1))
        # sum chemical reaction losses
        crl = np.zeros((dydt.shape[0] - 1, 1))
        for ti in range(dydt.shape[0] - 1):  # loop through times
            indx = dydt[
                ti + 1,
                0:-2] > 0  # indices of reactions that produce component
            crg[ti] = dydt[ti + 1, 0:-2][indx].sum()
            indx = dydt[ti + 1,
                        0:-2] < 0  # indices of reactions that lose component
            crl[ti] = dydt[ti + 1, 0:-2][indx].sum()

        # convert change tendencies from molecules/cc/s to ug/m3/s
        gpp = ((gpp / si.N_A) * y_mw[ci]) * 1.e12
        gwp = ((gwp / si.N_A) * y_mw[ci]) * 1.e12
        crg = ((crg / si.N_A) * y_mw[ci]) * 1.e12
        crl = ((crl / si.N_A) * y_mw[ci]) * 1.e12

        # plot temporal profiles of change tendencies due to chemical
        # reaction production and loss, gas-particle partitioning and gas-wall partitioning
        ax0.plot(timehr,
                 gpp,
                 label=str('gas-particle partitioning ' + comp_name))
        ax0.plot(timehr, gwp, label=str('gas-wall partitioning ' + comp_name))
        ax0.plot(timehr, crg, label=str('chemical reaction gain ' + comp_name))
        ax0.plot(timehr, crl, label=str('chemical reaction loss ' + comp_name))
        ax0.yaxis.set_tick_params(direction='in')

        ax0.set_title(
            'Change tendencies, where a tendency to decrease \ngas-phase concentrations is treated as negative'
        )
        ax0.set_xlabel('Time through experiment (hours)')
        ax0.set_ylabel('Change tendency ($\mathrm{\mu g\, m^{-3}\, s^{-1}}$)')

        ax0.yaxis.set_tick_params(direction='in')
        ax0.xaxis.set_tick_params(direction='in')

        ax0.legend()

    return ()
コード例 #11
0
ファイル: table1_res_plot.py プロジェクト: simonom/PyCHAM
# best particle and gas loss to wall
#mass_trans_coeff = 1.e-6
#eff_abs_wall_massC = 1.e0
#inflectDp = 1.e-6
#Grad_pre_inflect = 1.
#Grad_post_inflect = 1.
#Rate_at_inflect = 6.e-6

output_by_sim = str(
    cwd + '/PyCHAM/output/fig11_full_scheme/nuc_vsobs_output_mc_24sb_gpm')
# required outputs
(num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, xfm, time_array, comp_names, _,
 N, _, y_MV, _, wall_on, space_mode, indx_plot, comp0, yrec_p2w, PsatPa, OC,
 H2Oi, seedi, siz_str, cham_env, group_indx,
 tot_in_res) = retr_out.retr_out(output_by_sim)

dlog10D = np.log10(rbou_rec[:, 1::] * 2.) - np.log10(rbou_rec[:, 0:-1] * 2.)
dNdD = Ndry / dlog10D  # normalised number size distribution (# particles/cm3 (air))

# observation part ---------------------------------------------------------------
import openpyxl  # for opening xlsx file

# if calling from the PyCHAM home folder
wb = openpyxl.load_workbook(
    str(cwd + '/PyCHAM/output/GMD_paper_plotting_scripts/obs_fig11.xlsx'))
wb = wb['20190628_Dark_limonene_LowNOx']  # take just the required sheet

sr = 14  # starting row for desired time since O3 injection

obst = np.zeros((46 - sr, 1))  # empty array for observation times (s)
コード例 #12
0
ファイル: plotter.py プロジェクト: vigorss/PyCHAM
def plotter(caller):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # --------------------------------------------------------------------------

    # retrieve useful information from pickle file
    [sav_name, sch_name, indx_plot, Comp0] = ui.share(1)

    if (sav_name == 'default_res_name'):
        print(
            'Default results name was used, therefore results not saved and nothing to plot'
        )
        return ()

    dir_path = os.getcwd()  # current working directory
    # obtain just part of the path up to PyCHAM home directory
    for i in range(len(dir_path)):
        if dir_path[i:i + 7] == 'PyCHAM':
            dir_path = dir_path[0:i + 7]
            break
    # isolate the scheme name from path to scheme
    for i in range(len(sch_name) - 1, 0, -1):
        if sch_name[i] == '/':
            sch_name = sch_name[i + 1::]
            break
    # remove any file formats
    for i in range(len(sch_name) - 1, 0, -1):
        if sch_name[i] == '.':
            sch_name = sch_name[0:i]
            break

    dir_path = str(dir_path + '/PyCHAM/output/' + sch_name + '/' + sav_name)

    # chamber condition ---------------------------------------------------------

    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, comp_names, _, _,
     _, y_MV, _, wall_on, space_mode) = retr_out.retr_out(dir_path)

    # number of actual particle size bins
    num_asb = num_sb - wall_on

    if caller == 0:
        plt.ion()  # show results to screen and turn on interactive mode

    # prepare sub-plots depending on whether particles present
    if (num_asb) == 0:  # no particle size bins
        if not (indx_plot
                ):  # check whether there are any gaseous components to plot
            print(
                'Please note no initial gas-phase concentrations were received and no particle size bins were present, therefore there is nothing for the standard plot to show'
            )
            return ()
        fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    if (num_asb > 0):
        if not (indx_plot):
            print(
                'Please note, no initial gas-phase concentrations were registered, therefore the gas-phase standard plot will not be shown'
            )
            fig, (ax1) = plt.subplots(1, 1, figsize=(14, 7))
        else:
            fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(14, 7))

        par1 = ax1.twinx()  # first parasite axis
        par2 = ax1.twinx()  # second parasite axis

        # Offset the right spine of par2.  The ticks and label have already been
        # placed on the right by twinx above.
        par2.spines["right"].set_position(("axes", 1.2))
        # Having been created by twinx, par2 has its frame off, so the line of its
        # detached spine is invisible.  First, activate the frame but make the patch
        # and spines invisible.
        make_patch_spines_invisible(par2)
        # Second, show the right spine.
        par2.spines["right"].set_visible(True)

    if (indx_plot):
        # gas-phase concentration sub-plot ---------------------------------------------
        for i in range(len(indx_plot)):

            ax0.semilogy(timehr,
                         yrec[:, indx_plot[i]],
                         '+',
                         linewidth=4.0,
                         label=str(str(Comp0[i])))

        ax0.set_ylabel(r'Gas-phase concentration (ppb)', fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

        # find maximum and minimum of plotted concentrations for sub-plot label
        maxy = max(yrec[:, indx_plot].flatten())
        miny = min(yrec[:, indx_plot].flatten())

        ax0.text(x=timehr[0] - (timehr[-1] - timehr[0]) / 10.,
                 y=maxy + ((maxy - miny) / 10.),
                 s='a)',
                 size=14)

        # end of gas-phase concentration sub-plot ---------------------------------------

    # particle properties sub-plot --------------------------------------------------
    if (num_asb > 0):  # if particles present

        if timehr.ndim == 0:  # occurs if only one time step saved
            Ndry = np.array(Ndry.reshape(1, num_asb))
        if num_asb == 1:  # just one particle size bin (wall included in num_sb)
            Ndry = np.array(Ndry.reshape(len(timehr), num_asb))

        if timehr.ndim == 0:  # occurs if only one time step saved
            x = np.array(x.reshape(1, num_asb))
        if num_asb == 1:  # just one particle size bin (wall included in num_sb)
            x = np.array(x.reshape(len(timehr), num_asb))

        if timehr.ndim == 0:  # occurs if only one time step saved
            rbou_rec = np.array(rbou_rec.reshape(1, num_sb))

        # plotting number size distribution --------------------------------------

        # don't use the first boundary as it's zero, so will error when log10 taken
        log10D = np.log10(rbou_rec[:, 1::] * 2.0)
        if (num_asb > 1):
            # note, can't append zero to start of log10D to cover first size bin as the log10 of the
            # non-zero boundaries give negative results due to the value being below 1, so instead
            # assume same log10 distance as the next pair
            log10D = np.append(
                (log10D[:, 0] - (log10D[:, 1] - log10D[:, 0])).reshape(-1, 1),
                log10D,
                axis=1)
            # radius distance covered by each size bin (log10(um))
            dlog10D = (log10D[:, 1::] - log10D[:, 0:-1]).reshape(
                log10D.shape[0], log10D.shape[1] - 1)
        if (num_asb == 1):  # single particle size bin
            # assume lower radius bound is ten times smaller than upper
            dlog10D = (log10D[:, 0] - np.log10(
                (rbou_rec[:, 1] / 10.) * 2.)).reshape(log10D.shape[0], 1)

        # number size distribution contours (/cc (air))
        dNdlog10D = np.zeros((Ndry.shape[0], Ndry.shape[1]))
        dNdlog10D[:, :] = Ndry[:, :] / dlog10D[:, :]
        # transpose ready for contour plot
        dNdlog10D = np.transpose(dNdlog10D)

        # mask the nan values so they're not plotted
        z = np.ma.masked_where(np.isnan(dNdlog10D), dNdlog10D)

        # customised colormap (https://www.rapidtables.com/web/color/RGB_Color.html)
        colors = [(0.60, 0.0, 0.70), (0, 0, 1), (0, 1.0, 1.0), (0, 1.0, 0.0),
                  (1.0, 1.0, 0.0), (1.0, 0.0, 0.0)]  # R -> G -> B
        n_bin = 100  # Discretizes the colormap interpolation into bins
        cmap_name = 'my_list'
        # Create the colormap
        cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)

        # set contour levels
        levels = (MaxNLocator(nbins=100).tick_values(np.min(z[~np.isnan(z)]),
                                                     np.max(z[~np.isnan(z)])))

        # associate colours and contour levels
        norm1 = BoundaryNorm(levels, ncolors=cm.N, clip=True)

        # contour plot with times (hours) along x axis and
        # particle diameters (nm) along y axis
        for ti in range(len(timehr) - 1):  # loop through times
            p1 = ax1.pcolormesh(timehr[ti:ti + 2], (rbou_rec[ti, :] * 2 * 1e3),
                                z[:, ti].reshape(-1, 1),
                                cmap=cm,
                                norm=norm1)

        # if logarithmic spacing of size bins specified, plot vertical axis
        # logarithmically
        if space_mode == 'log':
            ax1.set_yscale("log")
        ax1.set_ylabel('Diameter (nm)', size=14)
        ax1.xaxis.set_tick_params(labelsize=14, direction='in')
        ax1.yaxis.set_tick_params(labelsize=14, direction='in')

        # label according to whether gas-phase plot also displayed
        if (indx_plot):
            ax1.text(x=timehr[0] - (timehr[-1] - timehr[0]) / 11.,
                     y=np.amax(rbou_rec * 2 * 1e3) * 1.05,
                     s='b)',
                     size=14)
        else:
            ax1.text(x=timehr[0] - (timehr[-1] - timehr[0]) / 11.,
                     y=np.amax(rbou_rec * 2 * 1e3) * 1.3,
                     s='a)',
                     size=14)
        ax1.set_xlabel(r'Time through simulation (hours)', fontsize=14)

        cb = plt.colorbar(p1, format=ticker.FuncFormatter(fmt), pad=0.25)
        cb.ax.tick_params(labelsize=14)
        # colour bar label
        cb.set_label('dN/dlog10(D) $\mathrm{(cm^{-3})}$',
                     size=14,
                     rotation=270,
                     labelpad=20)

        # ----------------------------------------------------------------------------------------
        # total particle number concentration #/cm3

        # include total number concentration (# particles/cc (air)) on contour plot
        # first identify size bins with radius exceeding 3nm
        # empty array for holding total number of particles
        Nvs_time = np.zeros((Ndry.shape[0]))

        for i in range(num_asb):  # size bin loop
            # get the times when bin exceeds 3nm - might be wanted to deal with particle counter detection limits
            # 			ish = x[:, i]>3.0e-3
            # 			Nvs_time[ish] += Ndry[ish, i] # sum number
            Nvs_time[:] += Ndry[:, i]  # sum number

        p3, = par1.plot(timehr, Nvs_time, '+k', label='N')

        par1.set_ylabel('N (# $\mathrm{cm^{-3})}$',
                        size=14,
                        rotation=270,
                        labelpad=20)  # vertical axis label
        par1.yaxis.set_major_formatter(ticker.FormatStrFormatter(
            '%.0e'))  # set tick format for vertical axis
        par1.yaxis.set_tick_params(labelsize=14)

        # SOA mass concentration ---------------------------------------------------------------
        # array for SOA sum with time
        SOAvst = np.zeros((1, len(timehr)))

        final_i = 0
        # check whether water and/or core is present
        if comp_names[-2] == 'H2O':  # if both present
            final_i = 2
        if comp_names[-1] == 'H2O':  # if just water
            final_i = 1
        # note that the seed component is only registered in init_conc_func if initial
        # particle concentration (pconc) exceeds zero, therefore particle-phase material
        # must be present at start of experiment (row 0 in y)
        if final_i == 0 and y[0, num_speci:(num_speci *
                                            (num_asb + 1))].sum() > 1.0e-10:
            final_i = 1

        for i in range(num_asb):  # particle size bin loop

            # sum of organics in condensed-phase at end of simulation (ug/m3 (air))

            # to replicate the SMPS results, find the volume of particles then
            # assume a density of 1.0 g/cm3
            SOAvst[0, :] += np.sum(
                (yrec[:, ((i + 1) * num_comp):((i + 2) * num_comp - final_i)] /
                 si.N_A * (y_MV[0:-final_i]) * 1.0e12),
                axis=1)

        # log10 of maximum in SOA
        if (max(SOAvst[0, :]) > 0):
            SOAmax = int(np.log10(max(SOAvst[0, :])))
        else:
            SOAmax = 0.
        # transform SOA so no standard notation required
        SOAvst[0, :] = SOAvst[0, :]

        p5, = par2.plot(timehr, SOAvst[0, :], 'xk', label='[secondary]')
        par2.set_ylabel(str('[secondary] ($\mathrm{\mu g\, m^{-3}})$'),
                        rotation=270,
                        size=16,
                        labelpad=25)
        # set label, tick font and [SOA] vertical axis to red to match scatter plot presentation
        par2.yaxis.label.set_color('black')
        par2.tick_params(axis='y', colors='black')
        par2.spines['right'].set_color('black')
        par2.yaxis.set_major_formatter(ticker.FormatStrFormatter(
            '%.0e'))  # set tick format for vertical axis
        par2.yaxis.set_tick_params(labelsize=16)
        par2.text((timehr)[0],
                  max(SOAvst[0, :]) / 2.0,
                  'assumed particle density = 1.0 $\mathrm{g\, cm^{-3}}$')
        plt.legend(fontsize=14, handles=[p3, p5], loc=4)

    # end of particle properties sub-plot -----------------------------------

    # save and display
    plt.savefig(str(dir_path + '/' + sav_name + '_output_plot.png'))
    if caller == 2:
        plt.show()

    return ()
コード例 #13
0
ファイル: plotter_counters.py プロジェクト: baccandr/PyCHAM
def cpc_plotter(caller, dir_path, self, dryf, cdt, max_dt, sdt, max_size,
                uncert, delays, wfuncs, Hz, loss_func_str, losst):

    import rad_resp_hum
    import inlet_loss

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # self - reference to GUI
    # dryf - relative humidity of aerosol at entrance to condensing unit of CPC (fraction 0-1)
    # cdt - false background counts (# particles/cm3)
    # max_dt - maximum detectable concentration (# particles/cm3)
    # sdt - particle size at 50 % detection efficiency (nm),
    # 	width factor for detection efficiency dependence on particle size
    # max_size - maximum size measure by counter (nm)
    # uncert - uncertainty (%) around counts by counter
    # delays - the significant response times for counter
    # wfuncs - the weighting as a function of time for particles of different age
    # Hz - temporal frequency of output
    # loss_func_str - string stating loss rate (fraction/s) as a
    #			function of particle size (um)
    # losst - time of passage through inlet (s)
    # --------------------------------------------------------------------------

    # required outputs ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_mw, Nwet, _,
     y_MV, _, wall_on, space_mode, indx_plot, comp0, _, PsatPa, OC, H2Oi, _,
     siz_str, _) = retr_out.retr_out(dir_path)
    # ------------------------------------------------------------------------------

    # condition wet particles assuming equilibrium with relative humidity at
    # entrance to condensing unit of CPC.  Get new radius at size bin centre (um)
    [xn, yrec[:, num_comp:(num_sb - wall_on + 1) * (num_comp)]
     ] = rad_resp_hum.rad_resp_hum(
         yrec[:, num_comp:(num_sb - wall_on + 1) * (num_comp)], x, dryf, H2Oi,
         num_comp, (num_sb - wall_on), Nwet, y_MV)

    # remove particles lost during transit through inlet (# particles/cm3)
    [Nwet, yrec[:, num_comp:(num_sb - wall_on + 1) * (num_comp)]
     ] = inlet_loss.inlet_loss(
         Nwet, xn, yrec[:, num_comp:(num_sb - wall_on + 1) * (num_comp)],
         loss_func_str, losst, num_comp)

    # all CPC output times, assuming first report is at 0 s through experiment
    times = np.arange(0, timehr[-1] * 3600., 1. / Hz)

    # empty array for holding corrected concentrations (# particles/cm3)
    Nwetn = np.zeros((len(times), Nwet.shape[1]))
    xnn = np.zeros((len(times), Nwet.shape[1]))

    # interpolate simulation output to instrument output frequency (# particles/cm3)
    # loop through size bins
    for sbi in range(num_sb - wall_on):
        Nwetn[:, sbi] = np.interp(times, timehr * 3600., Nwet[:, sbi])
        xnn[:, sbi] = np.interp(times, timehr * 3600., xn[:, sbi])

    Nwet = Nwetn  # rename Nwet (# particles/cm3)
    xn = xnn  # rename xn (um)

    # number of simulation outputs within the instrument response time
    rt_num = (times[1] - times[0]) / delays[2]

    # if more than one output within response time, then loop through times to correct
    # for response time and any mixing of ages of particle
    if (rt_num > 1):

        # account for response time and mixing of particles of different ages
        [weight, weightt] = resp_time_func(3, delays, wfuncs)

        # empty array for holding corrected concentrations (# particles/cm3)
        Nwetn = np.zeros((Nwet.shape[0], Nwet.shape[1]))
        xnn = np.zeros((Nwet.shape[0], Nwet.shape[1]))

        for it in range(1, len(times)):  # loop through times
            # number of time points to consider
            trel = (times >= (times[it] - delays[2])) * (times <= times[it])
            tsim = times[trel]  # extract relevant time points (s)
            tsim = np.abs(tsim - tsim[-1])  # time difference with present (s)
            Nsim = Nwet[
                trel, :]  # extract relevant number concentrations (# particles/cm3)
            xsim = xn[trel, :]
            # interpolate weights, use flip to align times
            weightn = np.flip(np.interp(np.flip(tsim), weightt, weight))
            # tile across size bins
            weightn = np.tile(weightn.reshape(-1, 1), [1, num_sb - wall_on])
            # corrected concentration
            Nwetn[it, :] = np.sum(Nsim * weightn, axis=0)
            xnn[it, :] = np.sum(xsim * weightn, axis=0)

        Nwet = Nwetn  # rename Nwet
        xn = xnn  # size bin radii (um)

    # account for size dependent detection efficiency below one
    # get detection efficiency as a function of particle size (nm)
    [Dp, ce] = count_eff_plot(3, 0, self, sdt)

    # empty array to hold detection efficiencies across times and simulation size bins
    # Dp is in um
    ce_t = np.zeros((len(times), xn.shape[1]))

    # loop through times
    for it in range(len(times)):
        # interpolate detection efficiency (fraction) to simulation size bin centres
        # Dp is in um
        ce_t[it, :] = np.interp(xn[it, :] * 2., Dp, ce)

        # correct for upper size range of instrument, note conversion of
        # upper size from nm to um
        size_indx = (xn[it, :] * 2. > max_size * 1.e-3)
        Nwet[it, size_indx] = 0.

    Nwet = Nwet * ce_t  # correct for detection efficiency

    # ------------
    # sum particle number concentration across size bins
    Nwet = Nwet.sum(axis=1)

    plt.ion()  # show results to screen and turn on interactive mode

    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))
    ax0.plot(times / 3600., Nwet, label='uncertainty mid-point')

    # plot vertical axis logarithmically
    ax0.set_yscale("log")

    # include uncertainty region, note conversion of uncertainty from percentage to fraction
    ax0.fill_between(times / 3600.,
                     Nwet - Nwet * uncert / 100.,
                     Nwet + Nwet * uncert / 100.,
                     alpha=0.3,
                     label='uncertainty bounds')

    # set tick format for vertical axis
    ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
    ax0.set_ylabel(
        'Total Number Concentration (#$\mathrm{particles\, cm^{-3}}$)',
        size=14)
    ax0.xaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax0.yaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax0.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e'))
    ax0.set_title(
        'Simulated total particle concentration convolved to represent \ncondensation particle counter (CPC) measurements'
    )
    ax0.legend()

    return ()
    # ------------

    Nwet = Nwet.sum(axis=1)  # sum particle concentrations (# particles/cm3)

    # account for false background counts
    # (minimum detectable particle concentration)  (# particles/cm3)
    Nwet[Nwet < cdt] = cdt

    # account for maximum particle concentration (# particles/cm3)
    Nwet[Nwet > max_dt] = max_dt

    if (caller == 0):  # when called from gui
        plt.ion()  # show results to screen and turn on interactive mode

    # plot temporal profile of total particle number concentration (# particles/cm3)
    # prepare figure -------------------------------------------
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    ax0.plot(times / 3600.0, Nwet, label='uncertainty mid-point')

    # plot vertical axis logarithmically
    ax0.set_yscale("log")

    # include uncertainty region, note conversion of uncertainty from percentage to fraction
    ax0.fill_between(times / 3600.,
                     Nwet - Nwet * uncert / 100.,
                     Nwet + Nwet * uncert / 100.,
                     alpha=0.3,
                     label='uncertainty bounds')

    # set tick format for vertical axis
    ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
    ax0.set_ylabel(
        'Total Number Concentration (#$\mathrm{particles\, cm^{-3}}$)',
        size=14)
    ax0.xaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax0.yaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax0.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e'))
    ax0.set_title(
        'Simulated total particle concentration convolved to represent \ncondensation particle counter (CPC) measurements'
    )
    ax0.legend()
    if (caller == 2):  # display when in test mode
        plt.show()

    return ()
コード例 #14
0
def smps_plotter(caller, dir_path, self, dryf, cdt, max_dt, sdt, max_size, uncert, 
		delays, wfuncs, Hz, loss_func_str, losst, av_int, Q, tau, coi_maxD, csbn):

	import rad_resp_hum
	import inlet_loss
	
	# inputs: ------------------------------------------------------------------
	# caller - marker for whether PyCHAM (0) or tests (2) are the calling module
	# dir_path - path to folder containing results files to plot
	# self - reference to GUI
	# dryf - relative humidity of aerosol at entrance to condensing unit of CPC (fraction 0-1)
	# cdt - false background counts (# particles/cm3)
	# max_dt - maximum detectable actual concentration (# particles/cm3)
	# sdt - particle size at 50 % detection efficiency (nm), 
	# 	width factor for detection efficiency dependence on particle size
	# max_size - minimum and maximum size measured (nm)
	# uncert - uncertainty (%) around counts by counter
	# delays - the significant response times for counter
	# wfuncs - the weighting as a function of time for particles of different age
	# Hz - temporal frequency of output
	# loss_func_str - string stating loss rate (fraction/s) as a 
	#			function of particle size (um)
	# losst - time of passage through inlet (s)
	# av_int - the averaging interval (s)
	# Q - volumetric flow rate through counting unit (cm3/s)
	# tau - instrument dead time (s)
	# coi_maxD - maximum actual concentration that 
	# coincidence convolution applies to (# particles/cm3)
	# csbn - the number of channels per decade of particle size
	# --------------------------------------------------------------------------

	# required outputs ---------------------------------------------------------
	# retrieve results
	(num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, 
		y_mw, Nwet, _, y_MV, _, wall_on, space_mode, indx_plot, 
		comp0, _, PsatPa, OC, H2Oi, _, siz_str, _, _, _, _) = retr_out.retr_out(dir_path)
	# ------------------------------------------------------------------------------
	
	# condition wet particles assuming equilibrium with relative humidity inside instrument.  
	# Get new radius at size bin centre (um)
	[xn, yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)]]  = rad_resp_hum.rad_resp_hum(yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)], x, dryf, H2Oi, num_comp, (num_sb-wall_on), Nwet, y_MV)
	
	# remove particles lost during transit through instrument (# particles/cm3)
	[Nwet,  yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)]]= inlet_loss.inlet_loss(0, Nwet, xn, yrec[:, num_comp:(num_sb-wall_on+1)*(num_comp)], loss_func_str, losst, num_comp)
	
	# all instrument output times, assuming first report is at 0 s through experiment
	times = np.arange(0, timehr[-1]*3600., 1./Hz)
	
	# empty array for holding corrected concentrations (# particles/cm3)
	Nwetn = np.zeros((len(times), Nwet.shape[1]))
	xnn = np.zeros((len(times), Nwet.shape[1])) # empty array for holding corrected radii (um)
	rbou_recn = np.zeros((len(times), Nwet.shape[1]+1))# empty array for holding corrected size bin boundaries
	
	# interpolate simulation output to instrument output frequency (# particles/cm3)
	# loop through size bins
	for sbi in range(num_sb-wall_on+1):
		if (sbi == (num_sb-wall_on+1)-1): # only consider size bin boundary for final step
			rbou_recn[:, sbi] = np.interp(times, timehr*3600., rbou_rec[:, sbi])
		else: # otherwise consider all arrays
			Nwetn[:, sbi] = np.interp(times, timehr*3600., Nwet[:, sbi])
			xnn[:, sbi] = np.interp(times, timehr*3600., xn[:, sbi])
			rbou_recn[:, sbi] = np.interp(times, timehr*3600., rbou_rec[:, sbi])
	
	Nwet = Nwetn # rename Nwet (# particles/cm3)
	xn = xnn # rename xn (um)
	rbou_rec = rbou_recn # rename size bin boundary (um) array
	
	# number of simulation outputs within the instrument response time
	rt_num = delays[2]/(times[1]-times[0])

	# if more than one output within response time, then loop through times to correct
	# for response time and any mixing of ages of particle
	# an explanation of response time and mixing of particles of different ages
	# due to the parabolic speed distribution in instrument tubing is
	# given by Enroth et al. (2018) in: https://doi.org/10.1080/02786826.2018.1460458
	if (rt_num >= 1):
	
		# account for response time and mixing of particles of different ages
		[weight, weightt] = resp_time_func(3, delays, wfuncs)
	
		# empty array for holding corrected concentrations (# particles/cm3)
		Nwetn = np.zeros((Nwet.shape[0], Nwet.shape[1]))
		xnn = np.zeros((Nwet.shape[0], Nwet.shape[1]))
	
		for it in range(1, len(times)): # loop through times
			# number of time points to consider
			trel = (times >= (times[it]-delays[2]))*(times <= times[it])
			tsim = times[trel] # extract relevant time points (s)
			tsim = np.abs(tsim-tsim[-1]) # time difference with present (s)
			Nsim = Nwet[trel, :] # extract relevant number concentrations (# particles/cm3)
			xsim = xn[trel, :]
			# interpolate weights, use flip to align times
			weightn = np.flip(np.interp(np.flip(tsim), weightt, weight))
			if (np.diff(weightt) == 0).all(): # if weight is all on one time
				weightn[:] = 0
				# identify time closest to response time
				t_diff = np.abs(tsim - weightt[0])
				tindx = t_diff == np.min(t_diff)
				weightn[tindx] = 1.

			# tile across size bins
			weightn = np.tile(weightn.reshape(-1, 1), [1, num_sb-wall_on])
			# corrected concentration
			Nwetn[it, :] = np.sum(Nsim*weightn, axis=0)
			xnn[it, :] = np.sum(xsim*weightn, axis=0)
		
		Nwet = Nwetn # rename Nwet
		xn = xnn # size bin radii (um)
	
	# correct for coincidence (only relevant at relatively moderate 
	# concentrations (# particles/cm3)), using eq. 11 of
	# https://doi.org/10.1080/02786826.2012.737049
	# where Q is the volumetric flow (cm3/s) rate and tau is the instrument
	# dead time (s)
	# bypass if coincidence flagged to not be considered
	if ((Q == -1)*(tau == -1)*(coi_maxD == -1) != 1):

		from scipy.special import lambertw
	
		# product of actual concentration with volumetric flow rate and instrument dead time
		Ca = Nwet.sum(axis=1)
		# cannot invert the Lambert function (eq. 9 of https://doi.org/10.1080/02786826.2012.737049)
		# directly as do not know the imaginary part, but can identify closest point to real part as we
		# we know that measure count must lie between blank counts and actual concentration
		for it in range(len(times)): # time loop
			
			# bypass if actual total particle concentration (# particles/cm3)
			# exceeds maximum that coincidence applicable to or is less than 
			# blank concentration
			if (Ca[it] > cdt and Ca[it] < coi_maxD):
				
				# the possible measured counts (# particles/cm3)
				x_poss = np.logspace(np.log10(cdt), np.log10(coi_maxD), int(1e3))
				# account for volumetric flow rate and dead time
				x_possn = -x_poss*(Q*tau)
				# take the Lambert function and obtain just the real part
				x_possn = (-lambertw(x_possn).real)/(Q*tau)
				
				# zero any negatives as these are useless
				x_possn[x_possn<0] = 0
				
				# find point closest to actual concentration (# particles/cm3)
				# if all possibilities fall below the actual concentration, then 
				# the instrument will have marked this as a maximum
				if all(x_possn < Ca[it]):
					Cm = coi_maxD
				else:
					# linear interpolation
					diff = (Ca[it]-x_possn)
					indx1 = (diff == np.max(diff[diff<=0.]))
					indx0 = (diff == np.min(diff[diff>0.]))
					# the measured concentration (# particles/cm3)
					diff[indx1] = -1*diff[indx1] # make absolute
					Cm = (x_poss[indx0]*(diff[indx1])+x_poss[indx1]*(diff[indx0]))/(diff[indx1]+diff[indx0])
					
				# get the fraction underestimation due to coincidence
				frac_un = Cm/Ca[it]
				
				# correct across all size bins (# particles/cm3)
				Nwet[it, :] = Nwet[it, :]*(frac_un)
	
	# moving-average over averaging interval
	# number of outputs within averaging interval
	# note that using int here means rounding down, which is sensible
	av_num = int(av_int/times[1]-times[0])
	if (av_num > 1):
		
		# empty array to hold moving averages (# particles/cm3)
		Nwetn = np.zeros((int(Nwet.shape[0]-(av_num-1)), Nwet.shape[1]))
		# empty array to hold moving average radii (um)
		xnn = np.zeros((int(Nwet.shape[0]-(av_num-1)), Nwet.shape[1]))
		
		for avi in range(av_num):
			# (# particles/cm3)
			Nwetn[:, :] += Nwet[avi:Nwet.shape[0]-(av_num-avi-1), :]/av_num
			# radii (um)
			xnn[:, :] += xn[avi:Nwet.shape[0]-(av_num-avi-1), :]/av_num
		
		# correct time (s)
		times = times[av_num-1::]
		
		# return to working variable names
		Nwet = Nwetn
		xn = xnn
	
	# prepare for interpolating concentrations of simulated sizes to instrument size bins ---------------------------
	
	# if no maximum or minimum size given, then assume same limits as simulation
	if (max_size[0] == -1): # no minimum diameter given (nm)
		max_size[0] = np.min(np.min(xn[xn>0.]*2.e3))
	if (max_size[1] == -1): # no maximum diameter given (nm)
		max_size[1] = np.max(np.max(xn*2.e3))	
	
	# total number of decades of particle size for instrument
	dec = (np.log10(max_size[1])-np.log10(max_size[0]))
	# total number of size bins
	nsb_ins = int(dec*csbn) 
	
	# instrument size bin bounds (for diameters) (nm)
	ins_sizbb = np.logspace(np.log10(max_size[0]), np.log10(max_size[1]), num = (nsb_ins+1), base = 10.)
	
	# instrument size bin centres (diameter) (nm)
	ins_sizc = ins_sizbb[0:-1]+np.diff(ins_sizbb)
	
	# difference (nm) between simulated size bins (diameter)
	sim_diff = np.diff(rbou_rec*2.e3, axis = 1)
	# difference (nm) between measurement size bins (diameter)
	ins_diff = np.diff(ins_sizbb)
	
	# normalise simulated particle number concentrations by size bin width (diameters) (# particles/cm3/nm)
	Nwet = Nwet/sim_diff
	
	# empty array for holding particle concentrations in instrument size bins (# particles/cm3)
	Nwetn = np.zeros((Nwet.shape[0], nsb_ins))
	
	# account for size dependent detection efficiency below one
	# get detection efficiency as a function of particle size (diameter) (um)
	[Dp, ce] = count_eff_plot(3, 0, self, sdt)

	# empty array to hold detection efficiencies across times and simulation size bins
	# Dp is in um
	ce_t = np.zeros((len(times), nsb_ins))
	
	# loop through times
	for it in range(len(times)):
	
		# distribute normalised simulated particle concentrations across instrument size array (# particles/cm3/nm)
		insNwet = np.interp(ins_sizc, xn[it, :]*2.e3, Nwet[it, :])
		
		# correct to width of instrument size bins (# particles/cm3)
		Nwetn[it, :] = insNwet*ins_diff
		
		# interpolate detection efficiency (fraction) to instrument size bin centres
		# Dp is in um, so convert to nm
		ce_t[it, :] = np.interp(ins_sizc, Dp*1.e3, ce)
		
		# correct for upper size range of instrument, note conversion of
		# upper size from nm to um
		if (max_size[-1] != -1):
			size_indx = (ins_sizc > max_size[-1])
			Nwetn[it, size_indx] = 0.
	
	
	Nwetn = Nwetn*ce_t # correct for detection efficiency
	
	# rename Nwetn variable
	Nwet = np.zeros((Nwetn.shape[0], Nwetn.shape[1]))
	Nwet[:, :] = Nwetn[:, :]
	
	# account for false background counts 
	# (minimum detectable particle concentration)  (# particles/cm3)
	Nwet[Nwet < cdt] = cdt
	
	# account for maximum particle concentration (# particles/cm3)
	if (max_dt != -1): # if maximum particle concentration to be considered
		Nwet[Nwet > max_dt] = max_dt
	
	if (caller == 0): # when called from gui
		plt.ion() # show results to screen and turn on interactive mode
	
	# plot temporal profile of particle number size distribution (# particles/cm3/log10(Dp))
	# prepare figure -------------------------------------------
	fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))
	
	# the difference in log10 of size bin boundaries (diameters (nm))
	dlog10D = (np.diff(np.log10(ins_sizbb))).reshape(1, -1)
	
	# number size distribution contours (/cc (air))
	dNdlog10D = np.zeros((Nwet.shape[0], Nwet.shape[1]))
	
	dNdlog10D[:, :] = Nwet[:, :]/dlog10D[:, :]
	
	# transpose ready for contour plot
	dNdlog10D = np.transpose(dNdlog10D)
	
		
	# mask any nan values so they are not plotted
	z = np.ma.masked_where(np.isnan(dNdlog10D), dNdlog10D)
	
	# customised colormap (https://www.rapidtables.com/web/color/RGB_Color.html)
	colors = [(0.6, 0., 0.7), (0, 0, 1), (0, 1., 1.), (0, 1., 0.), (1., 1., 0.), (1., 0., 0.)]  # R -> G -> B
	n_bin = 100  # discretizes the colormap interpolation into bins
	cmap_name = 'my_list'
	# create the colormap
	cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)
	
	# set contour levels
	levels = (MaxNLocator(nbins = 100).tick_values(np.min(z[~np.isnan(z)]), 
			np.max(z[~np.isnan(z)])))
	
	# fix upper value of contours, e.g. when trying to compare plots
	#levels = (MaxNLocator(nbins = 100).tick_values(np.min(z[~np.isnan(z)]), 
	#		1.89e5))
	
	# associate colours and contour levels
	norm1 = BoundaryNorm(levels, ncolors=cm.N, clip=True)
		
	# contour plot with times (hours) along x axis and 
	# particle diameters (nm) along y axis
	p1 = ax0.pcolormesh(times/3600.0, ins_sizbb, z, cmap = cm, norm = norm1, shading = 'auto')
	
	ax0.set_yscale("log") # set vertical axis to logarithmic spacing
			
	# set tick format for vertical axis
	ax0.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e'))	
	
	cb = plt.colorbar(p1, format=ticker.FuncFormatter(fmt))
	cb.ax.tick_params(labelsize=14)   
	# colour bar label
	cb.set_label('dN (#$\,$$\mathrm{cm^{-3}}$)/d$\,$log$_{10}$(D$\mathrm{_p}$ ($\mathrm{nm}$))', size=14, rotation=270, labelpad=20)
	
	# set tick format for vertical axis
	ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
	ax0.set_ylabel('Diameter (nm)', size = 14)
	ax0.xaxis.set_tick_params(labelsize = 14, direction = 'in', which= 'both')
	ax0.yaxis.set_tick_params(labelsize = 14, direction = 'in', which= 'both')
	ax0.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e'))
	ax0.set_title('Simulated particle number concentration convolved to represent \nscanning mobility particle spectrometer (SMPS) measurements')
	
	if (caller == 2): # display when in test mode
		plt.show()
	
	return()
コード例 #15
0
ファイル: plotter_ct.py プロジェクト: simonom/PyCHAM
def plotter_prod(caller, dir_path, comp_names_to_plot, tp, uc, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # tp - times to calculate between (hours)
    # uc - units to use for change tendency
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_mw, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, _, _, _, _,
     _, _, ro_obj) = retr_out.retr_out(dir_path)

    # loop through components due to be plotted, to check they are available
    for comp_name in (comp_names_to_plot):

        fname = str(dir_path + '/' + comp_name + '_rate_of_change')
        try:  # try to open
            dydt = np.loadtxt(fname, delimiter=',',
                              skiprows=0)  # skiprows = 0 skips first header
        except:
            mess = str(
                'Please note, a change tendency record for the component ' +
                str(comp_name) +
                ' was not found, was it specified in the tracked_comp input of the model variables file?  Please see README for more information.'
            )
            self.l203a.setText(mess)

            # set border around error message
            if (self.bd_pl == 1):
                self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                self.bd_pl = 2
            else:
                self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                self.bd_pl = 1

            plt.ioff()  # turn off interactive mode
            plt.close()  # close figure window

            return ()

    # if all files are available, then proceed without error message
    mess = str('')
    self.l203a.setText(mess)

    if (self.bd_pl < 3):
        self.l203a.setStyleSheet(0., '0px solid red', 0., 0.)
        self.bd_pl == 3

    for comp_name in (comp_names_to_plot):  # loop through components to plot

        if (comp_name != 'HOMRO2' and comp_name != 'RO2'):
            ci = comp_names.index(comp_name)  # get index of this component

        if (comp_name == 'HOMRO2'):
            # get mean molecular weight of HOMRO2 (g/mol)
            counter = 0  # count on HOMRO2 components
            mw_extra = 0.  # count on HOMRO2 molecular weights (g/mol)
            for cni in range(len(comp_names)):  # loop through all components
                if 'API_' in comp_names[cni] or 'api_' in comp_names[cni]:
                    if 'RO2' in comp_names[cni]:
                        mw_extra += y_mw[cni]
                        counter += 1
            y_mw = np.concatenate(
                (y_mw, (np.array(mw_extra / counter)).reshape(1)))
            ci = len(y_mw) - 1

        if (comp_name == 'RO2'):
            # get indices of RO2
            RO2indx = (np.array((ro_obj.gi['RO2i'])))
            # get mean molecular weight of RO2 (g/mol)
            mw_extra = np.sum((np.array((y_mw)))[RO2indx]) / len(RO2indx)

            y_mw = np.concatenate((y_mw, (np.array(mw_extra)).reshape(1)))
            ci = len(y_mw) - 1

        # note that penultimate column in dydt is gas-particle
        # partitioning and final column is gas-wall partitioning, whilst
        # the first row contains chemical reaction numbers

        # prepare to store results of change tendency due to chemical reactions
        res = np.zeros((dydt.shape[0], dydt.shape[1] - 2))
        res[:, :] = dydt[:, 0:
                         -2]  # get chemical reaction numbers and change tendencies

        if (uc == 0):
            Cfaca = (np.array(Cfac)).reshape(
                -1, 1)  # convert to numpy array from list
            # convert change tendencies from # molecules/cm3/s to ppb/s
            res[1::, :] = (res[1::, :] / Cfaca[1::])
            ct_units = str('ppb')
        if (uc == 1):
            # convert change tendencies from # molecules/cm3/s to ug/m3/s
            res[1::, :] = ((res[1::, :] / si.N_A) * y_mw[ci]) * 1.e12
            ct_units = str(u'\u03BC' + 'g/m' + u'\u00B3')
        if (uc == 2):
            # keep change tendencies as # molecules/cm3/s
            res[1::, :] = res[1::, :]
            ct_units = str(u'\u0023' + ' molecules/cm' + u'\u00B3')

        # remove reactions that destroy component
        res[1::, :][res[1::, :] < 0] = 0.

        # sum production rates over production reactions
        res_sum = np.sum(res[1::, :], axis=1)

        # retain only the required time period
        tindx = (timehr >= tp[0]) * (timehr < tp[1])
        res_sum = res_sum[tindx[0:-1]]

        # time intervals over this period (s)
        tindx = (timehr >= tp[0]) * (timehr <= tp[1])
        tint = (timehr[tindx][1::] - timehr[tindx][0:-1]) * 3600.
        # if one short then concatenate assuming same interval
        if len(tint) < (len(res_sum)):
            tint = np.concatenate((tint, np.reshape(np.array(tint[-1]), 1)))

        # integrate production rate to get total production
        res_sum = np.sum(res_sum * tint)

        # display to message board
        mess = str(mess + 'Total production of ' + str(comp_name) + ': ' +
                   str(res_sum) + ' ' + ct_units)
        self.l203a.setText(mess)

        # set border around message
        if (self.bd_pl == 1):
            self.l203a.setStyleSheet(0., '2px dashed magenta', 0., 0.)
            self.bd_pl = 2
        else:
            self.l203a.setStyleSheet(0., '2px solid magenta', 0., 0.)
            self.bd_pl = 1
        return ()  # return now

    return ()
コード例 #16
0
# create figure to plot results

fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(12, 6.5), sharex='all')
fig.subplots_adjust(hspace=0.05)

# ------------------------------------------------------------------------------
# results - note that all results for table 1 saved in fig11_data
cwd = os.getcwd()  # get current working directory
try:  # if calling from the PyCHAM home directory
    output_by_sim = str(
        cwd +
        '/PyCHAM/output/GMD_paper_plotting_scripts/fig11_data/nuc_vsobs_output_fm_n1_2e4_n2_-4e2_n3_1e2'
    )
    # required outputs
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, xfm, time_array, comp_names,
     _, N, _, y_MV, _, wall_on, space_mode) = retr_out.retr_out(output_by_sim)

except:  # if calling from the GMD paper Results folder
    output_by_sim = str(
        cwd + '/fig11_data/nuc_vsobs_output_fm_n1_2e4_n2_-4e2_n3_1e2')
    # required outputs
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, xfm, time_array, comp_names,
     _, N, _, y_MV, _, wall_on, space_mode) = retr_out.retr_out(output_by_sim)

dlog10D = np.log10(rbou_rec[:, 1::] * 2.0) - np.log10(rbou_rec[:, 0:-1] * 2.0)
dNdD = Ndry / dlog10D  # normalised number size distribution (#/cc (air))

# observation part ---------------------------------------------------------------
import xlrd  # for opening xlsx file

try:  # if calling from the PyCHAM home folder
コード例 #17
0
def plotter(caller, dir_path, comp_names_to_plot, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0 for individual components, 3 for
    #	total excluding seed and water, 4 for top contributors to
    #	particle-phase, 5 for particle surface area concentration,
    #	6 for seed surface area concentration, 7 for top contributors
    #	to particle-phase excluding seed and water)
    #	or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, rel_SMILES, y_MW,
     Nwet, comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, H2Oi,
     seedi, _, _, group_indx, _, ro_obj) = retr_out.retr_out(dir_path)

    # number of actual particle size bins
    num_asb = (num_sb - wall_on)

    if (caller == 0 or caller >= 3):
        plt.ion()  # show results to screen and turn on interactive mode

    # prepare plot
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    if (comp_names_to_plot):  # if component names specified

        # concentration plot ---------------------------------------------
        for i in range(len(comp_names_to_plot)):

            if (comp_names_to_plot[i].strip() == 'H2O'):
                indx_plot = [H2Oi]
                indx_plot = np.array((indx_plot))
            if (comp_names_to_plot[i].strip() == 'RO2'):
                indx_plot = (np.array((group_indx['RO2i'])))
            if (comp_names_to_plot[i].strip() == 'RO'):
                indx_plot = (np.array((group_indx['ROi'])))

            if (comp_names_to_plot[i].strip() != 'H2O'
                    and comp_names_to_plot[i].strip() != 'RO2'
                    and comp_names_to_plot[i].strip() != 'RO'):
                try:  # will work if provided components were in simulation chemical scheme
                    # get index of this specified component, removing any white space
                    indx_plot = [
                        comp_names.index(comp_names_to_plot[i].strip())
                    ]
                    indx_plot = np.array((indx_plot))
                except:
                    self.l203a.setText(
                        str('Component ' + comp_names_to_plot[i] +
                            ' not found in chemical scheme used for this simulation'
                            ))
                    # set border around error message
                    if (self.bd_pl == 1):
                        self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                        self.bd_pl = 2
                    else:
                        self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                        self.bd_pl = 1

                    plt.ioff()  # turn off interactive mode
                    plt.close()  # close figure window
                    return ()

            # particle-phase concentrations of all components (# molecules/cm3)
            if (wall_on == 1):  # wall on
                ppc = yrec[:, num_comp:-num_comp]
            if (wall_on == 0):  # wall off
                ppc = yrec[:, num_comp::]

            # particle-phase concentration of this component over all size bins (# molecules/cm3)
            conc = np.zeros((ppc.shape[0], num_sb - wall_on))
            for indxn in indx_plot:  # loop through the indices
                concf = ppc[:, indxn::num_comp]
                # particle-phase concentration (ug/m3)
                conc[:, :] += ((concf / si.N_A) * y_MW[indxn]) * 1.e12

            if (comp_names_to_plot[i].strip() != 'RO2'
                    and comp_names_to_plot[i].strip() != 'RO'):  # if not a sum
                # plot this component
                ax0.plot(timehr,
                         conc.sum(axis=1),
                         '+',
                         linewidth=4.,
                         label=str(
                             str(comp_names[indx_plot[0]]) +
                             ' (particle-phase)'))

            if (comp_names_to_plot[i].strip() == 'RO2'
                ):  # if is the sum of organic peroxy radicals
                ax0.plot(timehr,
                         conc.sum(axis=1),
                         '-+',
                         linewidth=4.,
                         label=str(r'$\Sigma$RO2 (particle-phase)'))

            if (comp_names_to_plot[i].strip() == 'RO'
                ):  # if is the sum of organic alkoxy radicals
                ax0.plot(timehr,
                         conc.sum(axis=1),
                         '-+',
                         linewidth=4.,
                         label=str(r'$\Sigma$RO (particle-phase)'))

        ax0.set_ylabel(r'Concentration ($\rm{\mu}$g$\,$m$\rm{^{-3}}$)',
                       fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

        # end of gas-phase concentration sub-plot ---------------------------------------

    # if called by button to plot temporal profile of total particle-phase concentration
    # excluding water and seed
    if (caller == 3):
        import scipy.constants as si
        # particle-phase concentrations of all components (# molecules/cm3)
        if (wall_on == 1):  # wall on
            ppc = yrec[:, num_comp:-num_comp]
        if (wall_on == 0):  # wall off
            ppc = yrec[:, num_comp::]

        # zero water and seed
        ppc[:, H2Oi::num_comp] = 0.
        for i in seedi:  # loop through seed components
            ppc[:, i::num_comp] = 0.
        # tile molar weights over size bins and times
        y_mwt = np.tile(np.array((y_MW)).reshape(1, -1), (1, num_sb - wall_on))
        y_mwt = np.tile(y_mwt, (ppc.shape[0], 1))
        # convert from # molecules/cm3 to ug/m3
        ppc = (ppc / si.N_A) * y_mwt * 1.e12
        # sum over components and size bins
        ppc = np.sum(ppc, axis=1)

        # plot
        ax0.plot(timehr,
                 ppc,
                 '+',
                 linewidth=4.,
                 label='total particle-phase excluding seed and water')
        ax0.set_ylabel(r'Concentration ($\rm{\mu}$g$\,$m$\rm{^{-3}}$)',
                       fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

    # if called by button to plot top contributors to particle-phase
    if (caller == 4):
        import scipy.constants as si
        # particle-phase concentrations of all components (# molecules/cm3)
        if (wall_on == 1):  # wall on
            ppc = yrec[:, num_comp:-num_comp]
        if (wall_on == 0):  # wall off
            ppc = yrec[:, num_comp::]

        # tile molar weights over size bins and times
        y_mwt = np.tile(
            np.array((y_MW)).reshape(1, -1), (1, (num_sb - wall_on)))
        y_mwt = np.tile(y_mwt, (ppc.shape[0], 1))

        # convert to ug/m3 from # molecules/cm3
        ppc = ((ppc / si.N_A) * y_mwt) * 1.e12

        # sum particle-phase concentration per component over time (ug/m3)
        ppc_t = (np.sum(ppc, axis=0)).reshape(1, -1)

        # sum particle-phase concentrations over size bins (ug/m3),
        # but keeping components separate
        ppc_t = np.sum(ppc_t.reshape(num_sb - wall_on, num_comp), axis=0)

        # convert to mass contributions
        ppc_t = ((ppc_t / np.sum(ppc_t)) * 100.).reshape(-1, 1)

        # rank in ascending order
        ppc_ts = np.flip(np.sort(np.squeeze(ppc_t)))

        # take just top number as supplied by user
        ppc_ts = ppc_ts[0:self.e300r]

        # sum concentrations over size bins and components
        ppc_sbc = np.sum(ppc, axis=1)

        # loop through to plot
        for i in range(self.e300r):

            # get index
            indx = np.where(ppc_t == ppc_ts[i])[0][0]
            # get name
            namei = comp_names[indx]
            # sum for this component over size bins (ug/m3)
            ppci = np.sum(ppc[:, indx::num_comp], axis=1)
            # get contribution (%)
            ppci = (ppci[ppc_sbc > 0.] / ppc_sbc[ppc_sbc > 0.]) * 100.

            ax0.plot(timehr[ppc_sbc > 0.],
                     ppci,
                     '-+',
                     linewidth=4.,
                     label=namei)

        ax0.set_ylabel(
            r'Contribution to particle-phase mass concentration (%)',
            fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

    # if called by button to plot top contributors to particle-phase excluding seed and water
    if (caller == 7):

        import scipy.constants as si

        # particle-phase concentrations of all components (# molecules/cm3)
        if (wall_on == 1):  # wall on
            ppc = yrec[:, num_comp:-num_comp]
        if (wall_on == 0):  # wall off
            ppc = yrec[:, num_comp::]

        for i in seedi:  # loop through seed components
            ppc[:, i::num_comp] = 0.  # zero seed

        ppc[:, H2Oi::num_comp] = 0.  # zero water

        # tile molar weights over size bins and times
        y_mwt = np.tile(
            np.array((y_MW)).reshape(1, -1), (1, (num_sb - wall_on)))
        y_mwt = np.tile(y_mwt, (ppc.shape[0], 1))

        # convert to ug/m3 from # molecules/cm3
        ppc = ((ppc / si.N_A) * y_mwt) * 1.e12

        # sum particle-phase concentration per component over time (ug/m3)
        ppc_t = (np.sum(ppc, axis=0)).reshape(1, -1)

        # sum particle-phase concentrations over size bins (ug/m3),
        # but keeping components separate
        ppc_t = np.sum(ppc_t.reshape(num_sb - wall_on, num_comp), axis=0)

        # convert to mass contributions
        ppc_t = ((ppc_t / np.sum(ppc_t)) * 100.).reshape(-1, 1)

        # rank in ascending order
        ppc_ts = np.flip(np.sort(np.squeeze(ppc_t)))

        # take just top number as supplied by user
        ppc_ts = ppc_ts[0:self.e300r]

        # sum concentrations over size bins and components
        ppc_sbc = np.sum(ppc, axis=1)

        # loop through to plot
        for i in range(self.e300r):

            # get index
            indx = np.where(ppc_t == ppc_ts[i])[0][0]
            # get name
            namei = comp_names[indx]
            # sum for this component over size bins (ug/m3)
            ppci = np.sum(ppc[:, indx::num_comp], axis=1)
            # get contribution (%)
            ppci = (ppci[ppc_sbc > 0.] / ppc_sbc[ppc_sbc > 0.]) * 100.

            ax0.plot(timehr[ppc_sbc > 0.],
                     ppci,
                     '-+',
                     linewidth=4.,
                     label=namei)

        ax0.set_ylabel(
            r'Contribution to particle-phase mass concentration (%)',
            fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

    # if called by button to plot total particle surface area concentration (m2/m3)
    if (caller == 5):
        # get surface area of single particles per size bin
        # at all times (m2), note radius converted from um
        # to m
        asp = 4. * np.pi * (x * 1.e-6)**2.
        # integrate over all particles in a size bin,
        # note conversion from /cm3 to /m3 (m2/m3)
        asp = asp * (Nwet * 1.e6)
        # sum over size bins (m2/m3)
        asp = np.sum(asp, axis=1)
        # plot
        ax0.plot(timehr, asp, '-+', linewidth=4.)
        ax0.set_ylabel(
            r'Total particle-phase surface area concentration ($\rm{m^{2}\,m^{-3}}$)',
            fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')

    # if called by button to plot seed particle surface area concentration (m2/m3)
    if (caller == 6):

        import scipy.constants as si

        # particle-phase concentrations of all components (# molecules/cm3)
        if (wall_on == 1):  # wall on
            ppc = yrec[:, num_comp:-num_comp]
        if (wall_on == 0):  # wall off
            ppc = yrec[:, num_comp::]

        # empty results array for seed particle volume concentrations per size bin
        ppcs = np.zeros((ppc.shape[0], (num_sb - wall_on)))

        for i in seedi:  # loop through seed indices
            # get just seed component particle-phase volume concentration
            # (cm3/cm3)
            ppcs[:, :] += (ppc[:, i::num_comp] / si.N_A) * (np.array(
                (y_MV))[i])

        # convert total volume to volume per particle (cm3)
        ppcs[Nwet > 0] = ppcs[Nwet > 0] / Nwet[Nwet > 0]

        # convert volume to radius (cm)
        ppcs = ((3. * ppcs) / (4. * np.pi))**(1. / 3.)

        # convert cm to m
        ppcs = ppcs * 1.e-2
        # get surface area over all particles in a size bin (m2/m3),
        # note conversion from m2/cm3 to m2/m3
        ppcs = (4. * np.pi * ppcs**2.) * Nwet * 1.e6
        # sum over all size bins (m2/m3)
        ppcs = np.sum(ppcs, axis=1)

        # plot
        ax0.plot(timehr, ppcs, '-+', linewidth=4.)
        ax0.set_ylabel(
            r'Seed particle-phase surface area concentration ($\rm{m^{2}\,m^{-3}}$)',
            fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')

    if (
            caller == 8
    ):  # if called by button to plot generational contribution to particle-phase mass

        import sch_interr  # for interpeting chemical scheme
        import re  # for parsing chemical scheme
        import scipy.constants as si

        gen_num = []  # empty results list
        ci = 0  # component count
        sch_name = ro_obj.sp
        inname = ro_obj.vp

        sch_name = str(dir_path + '/inputs/api_iso_ch4_mechHOM_scheme.txt')
        inname = str(dir_path + '/inputs/api_iso_ch4_mechHOM_model_var.txt')

        f_open_eqn = open(sch_name, mode='r')  # open the chemical scheme file
        # read the file and store everything into a list
        total_list_eqn = f_open_eqn.readlines()
        f_open_eqn.close()  # close file

        inputs = open(inname, mode='r')  # open model variables file
        in_list = inputs.readlines(
        )  # read file and store everything into a list
        inputs.close()  # close file
        for i in range(len(in_list)
                       ):  # loop through supplied model variables to interpret

            # ----------------------------------------------------
            # if commented out continue to next line
            if (in_list[i][0] == '#'):
                continue
            key, value = in_list[i].split('=')  # split values from keys
            # model variable name - a string with bounding white space removed
            key = key.strip()
            # ----------------------------------------------------

            if key == 'chem_scheme_markers' and (
                    value.strip()):  # formatting for chemical scheme
                chem_sch_mrk = [str(i).strip() for i in (value.split(','))]

        # interrogate scheme to list equations
        [eqn_list, aqeqn_list, eqn_num, rrc, rrc_name,
         RO2_names] = sch_interr.sch_interr(total_list_eqn, chem_sch_mrk)

        # loop through components to identify their generation
        for compi in comp_names[0:-2]:  # don't include core and water

            comp_fin = 0  # flag for when component generation number found
            gen_num.append(0)  # assume zero generation by default
            #print(ci, compi, rel_SMILES[ci], len(comp_names), len(rel_SMILES))
            # if an organic molecule
            if ('C' in rel_SMILES[ci] or 'c' in rel_SMILES[ci]):

                # loop through reactions to identify where this first occurs,
                # assuming that if first occurrence is as a reactant
                # it is zero generation and if as a product is >zero generation
                for eqn_step in range(len(eqn_list)):

                    line = eqn_list[eqn_step]  # extract this line
                    # work out whether equation or reaction rate coefficient part comes first
                    eqn_start = str('.*\\' + chem_sch_mrk[10])
                    rrc_start = str('.*\\' + chem_sch_mrk[9])
                    # get index of these markers, note span is the property of the match object that
                    # gives the location of the marker
                    eqn_start_indx = (re.match(eqn_start, line)).span()[1]
                    rrc_start_indx = (re.match(rrc_start, line)).span()[1]

                    if (eqn_start_indx > rrc_start_indx):
                        eqn_sec = 1  # equation is second part
                    else:
                        eqn_sec = 0  # equation is first part

                    # split the line into 2 parts: equation and rate coefficient
                    # . means match with anything except a new line character., when followed by a *
                    # means match zero or more times (so now we match with all characters in the line
                    # except for new line characters, so final part is stating the character(s) we
                    # are specifically looking for, \\ ensures the marker is recognised
                    if eqn_sec == 1:
                        eqn_markers = str('\\' + chem_sch_mrk[10] + '.*\\' +
                                          chem_sch_mrk[11])
                    else:  # end of equation part is start of reaction rate coefficient part
                        eqn_markers = str('\\' + chem_sch_mrk[10] + '.*\\' +
                                          chem_sch_mrk[9])

                    # extract the equation as a string ([0] extracts the equation section and
                    # [1:-1] removes the bounding markers)
                    eqn = re.findall(eqn_markers, line)[0][1:-1].strip()

                    eqn_split = eqn.split()
                    eqmark_pos = eqn_split.index('=')
                    # reactants with stoichiometry number and omit any photon
                    reactants = [
                        i for i in eqn_split[:eqmark_pos]
                        if i != '+' and i != 'hv'
                    ]
                    # products with stoichiometry number
                    products = [
                        t for t in eqn_split[eqmark_pos + 1:] if t != '+'
                    ]

                    for ri in reactants:
                        # note that no spaces or other punctuation included around
                        # the component name as extracted from the equation
                        if compi in ri and len(compi) == len(ri):
                            # assuming that components appearing first as a reactant
                            # must be zero generation
                            gen_num[ci] = 0
                            # finished with this component, break out of reactant loop
                            comp_fin = 1
                            break

                    for pi in products:
                        # note that no spaces or other punctuation included around
                        # the component name as extracted from the equation
                        if compi in pi and len(compi) == len(pi):
                            # check which reactant has earliest generation
                            rcheck = []
                            for ri in reactants:
                                # get its index
                                rindx = comp_names.index(ri)
                                # if an organic, note if inorgnic then ignore
                                if ('C' in rel_SMILES[rindx]
                                        or 'c' in rel_SMILES[rindx]):
                                    if (
                                            rindx < ci
                                    ):  # if generation number of recatant already known
                                        rcheck.append(gen_num[rindx])
                            # identify earliest generation reactant
                            egen = np.min(rcheck)
                            # check whether this species has a charge, i.e. is open shell, note that according
                            # to DAYLIGHT, the square brackets in a SMILES string deontes when an atom has a
                            # valence other than normal, i.e. is in an excited (radical) state:
                            # https://daylight.com/dayhtml/doc/theory/theory.smiles.html
                            if ('[' in rel_SMILES[ci]
                                    or ']' in rel_SMILES[ci]):  # if open-shell
                                gen_num[ci] = egen
                            else:  # if closed-shell
                                gen_num[ci] = egen + 1
                            # finished with this component, break out of reactant loop
                            comp_fin = 1
                            break

                    # finished with this component, break out of equation loop,
                    # move onto next component
                    if (comp_fin == 1):
                        break

            ci += 1  # component count
        # convert list to numpy array
        gen_num = np.array(gen_num)
        # append two zeros for core and water
        gen_num = np.concatenate((gen_num, np.zeros(2)), axis=0)
        # in case we want to see generation number per component -------------
        #ax0.plot(np.arange(len(comp_names)), gen_num, '+')
        #ax0.set_xticks(np.arange(len(comp_names)))
        #ax0.set_xticklabels(comp_names, rotation = 90)
        # --------------------------------------------------------------------

        # empty results matrix for contribution (%) to particle-phase mass per
        # generation per time step
        res = np.zeros((len(timehr), int(np.max(gen_num))))
        # particle-phase concentrations of all components (# molecules/cm3)
        if (wall_on == 1):  # wall on
            ppc = yrec[:, num_comp:-num_comp]
        if (wall_on == 0):  # wall off
            ppc = yrec[:, num_comp::]

        y_MW = np.tile(y_MW, (len(timehr), num_sb - wall_on))

        # convert particle-phase concentrations into mass
        # concentration (ug/m3) from # molecules/cm3
        ppc[:, :] += ((ppc / si.N_A) * y_MW) * 1.e12
        # zero water and seed contribution
        ppc[:, int(H2Oi)::int(num_comp)] = 0.
        for seei in seedi:
            ppc[:, int(seei)::int(num_comp)] = 0.
        # sum concentrations over all size bins
        # and components per time step (ug/m3)
        ppc_sum = np.sum(ppc, axis=1)

        # tile generation number over size bins
        gen_num = np.tile(gen_num, (num_sb - wall_on))
        # loop through generations
        for gi in range(int(np.max(gen_num))):
            # get percentage contribution from this generation
            res[:,
                gi] = (np.sum(ppc[:, gen_num == gi], axis=1) / ppc_sum) * 100.
            ax0.plot(timehr, res[:, gi], label=str('Generation ' + str(gi)))
        ax0.set_ylabel(
            r'% Contribution to organic particle-phase mass concentration ($\%$)',
            fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

        return ()

    # display
    if (caller == 2):
        plt.show()

    return ()
コード例 #18
0
ファイル: plotter_cham_env.py プロジェクト: simonom/PyCHAM
def plotter(caller, dir_path, self):

	# inputs: ------------------------------------------------------------------
	# caller - marker for whether PyCHAM (0) or tests (2) are the calling module
	# dir_path - path to folder containing results files to plot
	# self - reference to GUI
	# --------------------------------------------------------------------------	

	# retrieve results
	(_, _, _, _, _, _, _, timehr, _, 
		_, _, _, _, _, _, _, 
		_, _, _, _, _, _, _, _, cham_env, _, _, _) = retr_out.retr_out(dir_path)

	# check whether chamber environment variables were saved and therefore
	# retrieved
	if (cham_env == []):
		mess = str('Please note, no chamber environmental variables were found, perhaps the simulation was completed in a PyCHAM version predating this functionality')
		self.l203a.setText(mess)
		# set border around error message
		if (self.bd_pl == 1):
			self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
			self.bd_pl = 2
		else:
			self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
			self.bd_pl = 1
			
		plt.ioff() # turn off interactive mode
		plt.close() # close figure window
		
		return()

	# prepare figure
	plt.ion() # display figure in interactive mode
	fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))
	# ensure that all axes can be seen
	plt.subplots_adjust(left=0.1, right=0.8)
	
	# parasite axis part ---------------------------------------------------
	par1 = ax0.twinx() # first parasite axis
	par2 = ax0.twinx() # second parasite axis
	# Offset the right spine of par2.  The ticks and label have already been
	# placed on the right by twinx above.
	par2.spines["right"].set_position(("axes", 1.11))
	# Having been created by twinx, par2 has its frame off, so the line of its
	# detached spine is invisible.  First, activate the frame but make the patch
	# and spines invisible.
	make_patch_spines_invisible(par2)
	# second, show the right spine
	par2.spines['right'].set_visible(True)
	# -------------------------------------------------------------------------
	
	# plot temporal profiles
	# temperature on original vertical axis
	p0, = ax0.plot(timehr, cham_env[:, 0], 'k', label = 'temperature (K)')
	ax0.set_ylabel('Temperature (K)', size=16)
	ax0.yaxis.label.set_color('black')
	ax0.tick_params(axis='y', colors='black')
	ax0.spines['left'].set_color('black')
	ax0.yaxis.set_tick_params(direction = 'in', which = 'both')
	
	# pressure on first parasite axis
	p1, = par1.plot(timehr, cham_env[:, 1], '--m', label = 'pressure (Pa)')
	par1.set_ylabel('Pressure (Pa)', rotation=270, size=16, labelpad = 15)
	par1.yaxis.label.set_color('magenta')
	par1.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.2e')) # set tick format for vertical axis
	par1.tick_params(axis='y', colors='magenta')
	par1.spines['right'].set_color('magenta')
	par1.yaxis.set_tick_params(direction = 'in', which = 'both')
	
	# relative humidity on second parasite axis
	p2, = par2.plot(timehr, cham_env[:, 2], '-.b', label = 'relative humidity (fraction)')
	par2.set_ylabel('Relative Humidity (0-1)', rotation=270, size=16, labelpad = 15)
	par2.yaxis.label.set_color('blue')
	par2.tick_params(axis='y', colors='blue')
	par2.spines['right'].set_color('blue')
	par2.yaxis.set_tick_params(direction = 'in', which = 'both')
	
	
	
	ax0.xaxis.set_tick_params(direction = 'in', which = 'both')
		
	ax0.set_xlabel('Time through experiment (hours)', size=16)
	plt.legend(fontsize=14, handles=[p0, p1, p2], loc=4)
	

	return()
コード例 #19
0
def plotter(caller, dir_path, uc, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # uc - number representing the units to be used for gas-phase concentrations
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_mw, Nwet, _,
     y_MV, _, wall_on, space_mode, indx_plot, comp0, _, PsatPa, OC, _, _, _, _,
     _, _, _) = retr_out.retr_out(dir_path)

    # number of actual particle size bins
    num_asb = (num_sb - wall_on)

    if (caller == 0):
        plt.ion()  # show results to screen and turn on interactive mode

    # prepare sub-plots depending on whether particles present
    if (num_asb == 0):  # no particle size bins
        if not (indx_plot
                ):  # check whether there are any gaseous components to plot
            mess = str(
                'Please note, no initial gas-phase concentrations were received and no particle size bins were present, therefore there is nothing for the standard plot to show'
            )
            self.l203a.setText(mess)
            return ()

        # if there are gaseous components to plot, then prepare figure
        fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))
        mess = str(
            'Please note, no particle size bins were present, therefore the particle-phase standard plot will not be shown'
        )
        self.l203a.setText(mess)

    if (num_asb > 0):
        if not (indx_plot):
            mess = str(
                'Please note, no initial gas-phase concentrations were registered, therefore the gas-phase standard plot will not be shown'
            )
            self.l203a.setText(mess)
            # if there are no gaseous components then prepare figure
            fig, (ax1) = plt.subplots(1, 1, figsize=(14, 7))
        else:
            # if there are both gaseous components and particle size bins then prepare figure
            fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(14, 7))

        # parasite axis setup --------------------------------------------------------------
        par1 = ax1.twinx()  # first parasite axis
        par2 = ax1.twinx()  # second parasite axis

        # Offset the right spine of par2.  The ticks and label have already been
        # placed on the right by twinx above.
        par2.spines["right"].set_position(("axes", 1.2))
        # Having been created by twinx, par2 has its frame off, so the line of its
        # detached spine is invisible.  First, activate the frame but make the patch
        # and spines invisible.
        make_patch_spines_invisible(par2)
        # second, show the right spine
        par2.spines["right"].set_visible(True)
        # ----------------------------------------------------------------------------------------

    if (indx_plot):

        ymax = 0.  # start tracking maximum value for plot label

        # action units for gas-phase concentrations
        if (uc == 0):  # ppb
            gp_conc = yrec[:, 0:num_comp]  # ppb is original units
            gpunit = '(ppb)'
        if (uc == 1 or uc == 2):  # ug/m3 or # molecules/cm3

            y_MW = np.array(y_mw)  # convert to numpy array from list
            Cfaca = (np.array(Cfac)).reshape(
                -1, 1)  # convert to numpy array from list

            gp_conc = yrec[:, 0:num_comp]

            # # molecules/cm3
            gp_conc = gp_conc.reshape(yrec.shape[0], num_comp) * Cfaca
            gpunit = str('\n(' + u'\u0023' + ' molecules/cm' + u'\u00B3' + ')')

            if (uc == 1):  # ug/m3
                gp_conc = ((gp_conc / si.N_A) * y_MW) * 1.e12
                gpunit = str('(' + u'\u03BC' + 'g/m' + u'\u00B3' + ')')

        # gas-phase concentration sub-plot ---------------------------------------------
        for i in range(len(indx_plot)):

            ax0.semilogy(timehr,
                         gp_conc[:, indx_plot[i]],
                         '+',
                         linewidth=4.0,
                         label=str(str(comp0[i]).strip()))
            ymax = max(ymax, max(yrec[:, indx_plot[i]]))
        if (uc == 1 or uc == 2):  # ug/m3 or # molecules/cm3
            ax0.set_ylabel(r'Gas-phase concentration ' + gpunit, fontsize=14)
        if (uc == 0):  # ppb
            ax0.set_ylabel(r'Gas-phase mixing ratio ' + gpunit, fontsize=14)
        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in', which='both')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in', which='both')
        ax0.legend(fontsize=14)

        # find maximum and minimum of plotted concentrations for sub-plot label
        maxy = max(yrec[:, indx_plot].flatten())
        miny = min(yrec[:, indx_plot].flatten())

        if (num_asb > 0):  # if more than one plot
            # get the location of ticks
            locs = ax0.get_yticks()
            maxloc = max(locs)
            ax0.text(x=timehr[0] - (timehr[-1] - timehr[0]) / 9.5,
                     y=ymax * 1.05,
                     s='a)',
                     size=14)

        # end of gas-phase concentration sub-plot ---------------------------------------

    # particle properties sub-plot --------------------------------------------------
    if (num_asb > 0):  # if particles present

        if (timehr.ndim == 0):  # occurs if only one time step saved
            Ndry = np.array(Ndry.reshape(1, num_asb))
            x = np.array(x.reshape(1, num_asb))
            rbou_rec = np.array(rbou_rec.reshape(1, num_sb))
        if (num_asb == 1
            ):  # just one particle size bin (wall included in num_sb)
            Ndry = np.array(Ndry.reshape(len(timehr), num_asb))
            x = np.array(x.reshape(len(timehr), num_asb))

        # plotting number size distribution --------------------------------------

        # don't use the first boundary as it could be zero, which will error when log10 taken
        log10D = np.log10(rbou_rec[:, 1::] * 2.)
        if (num_asb > 1):
            # note, can't append zero to start of log10D to cover first size bin as the log10 of the
            # non-zero boundaries give negative results due to the value being below 1, so instead
            # assume same log10 distance as the next pair
            log10D = np.append(
                (log10D[:, 0] - (log10D[:, 1] - log10D[:, 0])).reshape(-1, 1),
                log10D,
                axis=1)
            # radius distance covered by each size bin (log10(um))
            dlog10D = (log10D[:, 1::] - log10D[:, 0:-1]).reshape(
                log10D.shape[0], log10D.shape[1] - 1)
        if (num_asb == 1):  # single particle size bin
            # assume lower radius bound is ten times smaller than upper
            dlog10D = (log10D[:, 0] - np.log10(
                (rbou_rec[:, 1] / 10.) * 2.)).reshape(log10D.shape[0], 1)

        # number size distribution contours (/cc (air))
        dNdlog10D = np.zeros((Nwet.shape[0], Nwet.shape[1]))
        dNdlog10D[:, :] = Nwet[:, :] / dlog10D[:, :]
        # transpose ready for contour plot
        dNdlog10D = np.transpose(dNdlog10D)

        # mask any nan values so they are not plotted
        z = np.ma.masked_where(np.isnan(dNdlog10D), dNdlog10D)

        # customised colormap (https://www.rapidtables.com/web/color/RGB_Color.html)
        colors = [(0.6, 0., 0.7), (0, 0, 1), (0, 1., 1.), (0, 1., 0.),
                  (1., 1., 0.), (1., 0., 0.)]  # R -> G -> B
        n_bin = 100  # discretizes the colormap interpolation into bins
        cmap_name = 'my_list'
        # create the colormap
        cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)

        # set contour levels
        levels = (MaxNLocator(nbins=100).tick_values(np.min(z[~np.isnan(z)]),
                                                     np.max(z[~np.isnan(z)])))

        # associate colours and contour levels
        norm1 = BoundaryNorm(levels, ncolors=cm.N, clip=True)

        # contour plot with times (hours) along x axis and
        # particle diameters (nm) along y axis
        for ti in range(len(timehr) - 1):  # loop through times
            p1 = ax1.pcolormesh(timehr[ti:ti + 2], (rbou_rec[ti, :] * 2 * 1e3),
                                z[:, ti].reshape(-1, 1),
                                cmap=cm,
                                norm=norm1)

        # if logarithmic spacing of size bins specified, plot vertical axis
        # logarithmically
        if space_mode == 'log':
            ax1.set_yscale("log")
        # set tick format for vertical axis
        ax1.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1e'))
        ax1.set_ylabel('Diameter (nm)', size=14)
        ax1.xaxis.set_tick_params(labelsize=14, direction='in', which='both')
        ax1.yaxis.set_tick_params(labelsize=14, direction='in', which='both')

        # label according to whether gas-phase plot also displayed
        if (indx_plot):
            ax1.text(x=timehr[0] - (timehr[-1] - timehr[0]) / 11.,
                     y=np.amax(rbou_rec * 2 * 1e3) * 1.05,
                     s='b)',
                     size=14)
        ax1.set_xlabel(r'Time through simulation (hours)', fontsize=14)

        cb = plt.colorbar(p1,
                          format=ticker.FuncFormatter(fmt),
                          pad=0.25,
                          ax=ax1)
        cb.ax.tick_params(labelsize=14)
        # colour bar label
        cb.set_label(
            'dN (#$\,$$\mathrm{cm^{-3}}$)/d$\,$log$_{10}$(D$\mathrm{_p}$ ($\mathrm{\mu m}$))',
            size=14,
            rotation=270,
            labelpad=20)

        # ----------------------------------------------------------------------------------------
        # total particle number concentration # particles/cm3

        # include total number concentration (# particles/cm3 (air)) on contour plot
        # first identify size bins with radius exceeding 3nm
        # empty array for holding total number of particles
        Nvs_time = np.zeros((Nwet.shape[0]))

        for i in range(num_asb):  # size bin loop
            Nvs_time[:] += Nwet[:, i]  # sum number

        p3, = par1.plot(timehr, Nvs_time, '+k', label='N')

        par1.set_ylabel('N (#$\,$ $\mathrm{cm^{-3})}$',
                        size=14,
                        rotation=270,
                        labelpad=20)  # vertical axis label
        par1.yaxis.set_major_formatter(ticker.FormatStrFormatter(
            '%.1e'))  # set tick format for vertical axis
        par1.yaxis.set_tick_params(labelsize=14)

        # mass concentration of particles ---------------------------------------------------------------
        # array for mass concentration with time
        MCvst = np.zeros((1, len(timehr)))

        # first obtain just the particle-phase concentrations (molecules/cm3)
        yrp = yrec[:, num_comp:num_comp * (num_asb + 1)]
        # loop through size bins to convert to ug/m3
        for sbi in range(num_asb):
            yrp[:, sbi * num_comp:(sbi + 1) * num_comp] = (
                (yrp[:, sbi * num_comp:(sbi + 1) * num_comp] / si.N_A) *
                y_mw) * 1.e12

        MCvst[0, :] = yrp.sum(axis=1)

        # log10 of maximum in mass concentration
        if (max(MCvst[0, :]) > 0):
            MCmax = int(np.log10(max(MCvst[0, :])))
        else:
            MCmax = 0.

        p5, = par2.plot(timehr,
                        MCvst[0, :],
                        'xk',
                        label='Total Particle Mass Concentration')
        par2.set_ylabel(str('Mass Concentration ($\mathrm{\mu g\, m^{-3}})$'),
                        rotation=270,
                        size=16,
                        labelpad=25)
        # set colour of label, tick font and corresponding vertical axis to match scatter plot presentation
        par2.yaxis.label.set_color('black')
        par2.tick_params(axis='y', colors='black')
        par2.spines['right'].set_color('black')
        par2.yaxis.set_major_formatter(ticker.FormatStrFormatter(
            '%.1e'))  # set tick format for vertical axis
        par2.yaxis.set_tick_params(labelsize=16)
        plt.legend(fontsize=14,
                   handles=[p3, p5],
                   loc=4,
                   fancybox=True,
                   framealpha=0.5)

    # end of particle properties sub-plot -----------------------------------

    if (caller == 2):  # display when in test mode
        plt.show()

    return ()
コード例 #20
0
ファイル: consumption.py プロジェクト: simonom/PyCHAM
def cons(comp_chem_schem_name, dir_path, self, caller):

    # inputs: -------------------------------
    # comp_chem_schem_name - chemical scheme name of component
    # dir_path - path to results
    # self - reference to GUI
    # caller - flag for calling function
    # ---------------------------------------

    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_mw, Nwet,
     comp_names, y_MV, _, wall_on, space_mode, indx_plot, comp0, _, PsatPa, OC,
     H2Oi, seedi, _, _, _, tot_in_res, _) = retr_out.retr_out(dir_path)

    try:
        # get index of component of interest
        compi = comp_names.index(comp_chem_schem_name)
    except:
        self.l203a.setText(
            str('Error - could not find component ' + comp_chem_schem_name +
                ' in the simulated system.  Please check whether the name matches that in the chemical scheme.'
                ))
        # set border around error message
        if (self.bd_pl == 1):
            self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
            self.bd_pl = 2
        else:
            self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
            self.bd_pl = 1
        return ()  # return now

    # get consumption
    try:
        indx_int = (np.where(tot_in_res[0, :].astype('int') == compi))[0][0]

    except:
        self.l203a.setText(
            str('Error - could not find a consumption value for component ' +
                comp_chem_schem_name +
                '.  Please check whether the name matches that in the chemical scheme and that it had gas-phase influx through the model variables file.'
                ))
        # set border around message
        if (self.bd_pl == 1):
            self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
            self.bd_pl = 2
        else:
            self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
            self.bd_pl = 1
        return ()  # return now

    # indices for supplied time
    indxt = (timehr >= self.tmin) * (timehr <= self.tmax)

    # cumulative influxed over all times (ug/m3)
    tot_in = tot_in_res[1::, indx_int]

    # influxes over interested time period
    tot_in = tot_in[indxt]

    # gas-phase concentration (ppb) over all times
    yrecn = yrec[:, compi]

    # gas-phase concentration (ppb) over interested time period (ppb)
    yrecn = yrecn[indxt]

    # change in gas-phase concentration (ug/m3)
    yrecn = ((
        (yrecn[-1] - yrecn[0]) * Cfac[-1]) / si.N_A) * y_mw[compi] * 1.e12

    # total influx over this time (ug/m3)
    tot_in = tot_in[-2] - tot_in[0]

    # total consumed (ug/m3)
    cons = tot_in - yrecn

    if (caller == 0):  # call from the consumption button

        self.l203a.setText(
            str('Consumption of ' + comp_chem_schem_name + ': ' + str(cons) +
                ' ' + u'\u03BC' + 'g/m' + u'\u00B3'))
        # set border around message
        if (self.bd_pl == 1):
            self.l203a.setStyleSheet(0., '2px dashed magenta', 0., 0.)
            self.bd_pl = 2
        else:
            self.l203a.setStyleSheet(0., '2px solid magenta', 0., 0.)
            self.bd_pl = 1
        return ()  # return now

    if (caller == 1):  # call from the yield button

        # concentrations of components in particle phase at end of simulation (# molecules/cm3)
        SOA = yrec[-2, num_comp:num_comp * (num_sb - wall_on + 1)]
        # remove seed and water in all size bins
        SOA[seedi[0]::num_comp] = 0.
        SOA[H2Oi::num_comp] = 0.

        # convert from # molecules/cm3 to ug/m3
        SOA = ((SOA / si.N_A) * np.tile(y_mw, (num_sb - wall_on))) * 1.e12

        # sum for total (ug/m3)
        SOA = np.sum(SOA)

        yld = SOA / cons

        self.l203a.setText(
            str('Yield of ' + comp_chem_schem_name + ': ' + str(yld)))
        # set border around message
        if (self.bd_pl == 1):
            self.l203a.setStyleSheet(0., '2px dashed magenta', 0., 0.)
            self.bd_pl = 2
        else:
            self.l203a.setStyleSheet(0., '2px solid magenta', 0., 0.)
            self.bd_pl = 1
        return ()  # return now

    return ()
コード例 #21
0
ファイル: fig05_res_plot.py プロジェクト: simonom/PyCHAM
import os
import matplotlib.pyplot as plt

# ----------------------------------------------------------------------------------------

# get current working directory
cwd = os.getcwd()
import retr_out

try:  # if calling from the GMD paper results folder

    # 60 s intervals
    # file name
    Pyfname = str(cwd + '/fig03_data/PyCHAM_time_res/PyCHAM_time_res60s')
    (num_sb, num_comp, Cfac, y0, Ndry, rbou_rec, xfm, thr0, PyCHAM_names, _, N,
     _, y_MV, _, wall_on, space_mode) = retr_out.retr_out(Pyfname)

    # 600 s intervals
    Pyfname = str(cwd + '/fig03_data/PyCHAM_time_res/PyCHAM_time_res600s')
    (num_sb, num_comp, Cfac, y1, Ndry, rbou_rec, xfm, thr1, PyCHAM_names, _, N,
     _, y_MV, _, wall_on, space_mode) = retr_out.retr_out(Pyfname)

    # 6000 s intervals
    Pyfname = str(cwd + '/fig03_data/PyCHAM_time_res/PyCHAM_time_res6000s')
    (num_sb, num_comp, Cfac, y2, Ndry, rbou_rec, xfm, thr2, PyCHAM_names, _, N,
     _, y_MV, _, wall_on, space_mode) = retr_out.retr_out(Pyfname)

except:  # if calling from the PyCHAM home folder

    # 60 s intervals
    Pyfname = str(
コード例 #22
0
ファイル: vol_contr_analys.py プロジェクト: simonom/PyCHAM
def plotter_wiw(caller, dir_path, self, now):  # define function

    # inputs: -------------------------------
    # caller - the module calling (0 for gui)
    # dir_path - path to results
    # self - reference to GUI
    # now - whether to include (0) or exclude water (1)
    # -----------------------------------------

    # ----------------------------------------------------------------------------------------
    if (caller == 0):  # if calling function is gui
        plt.ion()  # show figure

    # prepare plot
    fig, (ax1) = plt.subplots(1, 1, figsize=(10, 7))
    fig.subplots_adjust(hspace=0.7)

    # ----------------------------------------------------------------------------------------
    # prepare the volatility basis set interpretation
    # of particle-phase concentrations

    # required outputs from full-moving
    (num_sb, num_comp, Cfac, y, Ndry, rbou_rec, xfm, t_array, rel_SMILES, y_mw,
     N, comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, H2Oi,
     seedi, _, _, _, _, _) = retr_out.retr_out(dir_path)

    # number of particle size bins without wall
    num_asb = (num_sb - wall_on)

    # convert from list to array
    y_mw = (np.array((y_mw))).reshape(1, -1)
    PsatPa = (np.array((PsatPa))).reshape(1, -1)

    # repeat molecular weights over size bins and times
    y_mw_rep = np.tile(y_mw, (1, num_asb))
    y_mw_rep = np.tile(y_mw_rep, (len(t_array), 1))

    # particulate concentrations of individual components (*1.e-12 to convert from g/cc (air) to ug/m3 (air))
    # including any water and core
    pc = (y[:, num_comp:num_comp * (num_asb + 1)] / si.N_A) * y_mw_rep * 1.e12

    if (now == 1):  # if water to be excluded, zero its contribution
        pc[:, H2Oi::num_comp] = 0.

    # total particulate concentrations (ug/m3)
    tpc = pc.sum(axis=1)

    # standard temperature for pure component saturation vapour pressures (K)
    TEMP = 298.15

    # convert standard (at 298.15 K) vapour pressures in Pa to
    # saturation concentrations in ug/m3
    # using eq. 1 of O'Meara et al. 2014
    Psat_Cst = (1.e6 * y_mw) * (PsatPa / 101325.) / (8.2057e-5 * TEMP)

    # tile over size bins
    Psat_Cst = np.tile(Psat_Cst, num_asb)
    # remove excess dimension
    Psat_Cst = Psat_Cst.squeeze()

    # the saturation concentrations to consider (log10(C* (ug/m3)))
    # note these will be values at the centre of the volatility size bins
    # setting the final input argument to 1 means decadal bins of vapour pressure
    sc = np.arange(-2.5, 7.5, 1.)

    # empty array for normalised mass contributions
    nmc = np.zeros((len(sc), len(t_array)))

    for it in range(len(t_array)):  # loop through times

        # loop through saturation concentrations and find normalised mass contributions
        # to particulate loading
        for i in range(len(sc)):

            if (i == 0):
                indx = Psat_Cst < 10**(sc[i] + 0.5)
            if (i > 0 and i < len(sc) - 1):
                indx = (Psat_Cst >= 10**(sc[i - 1] + 0.5)) * (Psat_Cst < 10**
                                                              (sc[i] + 0.5))
            if (i == len(sc) - 1):
                indx = (Psat_Cst >= 10**(sc[i - 1] + 0.5))

            if (tpc[it] > 0.):
                nmc[i, it] = (pc[it, indx].sum()) / tpc[it]

    # customised colormap (https://www.rapidtables.com/web/color/RGB_Color.html)
    colors = [(0.6, 0., 0.7), (0, 0, 1), (0, 1., 1.), (0, 1., 0.),
              (1., 1., 0.), (1., 0., 0.)]  # R -> G -> B
    n_bin = 100  # discretizes the colormap interpolation into bins
    cmap_name = 'my_list'
    # create the colormap
    cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)

    # set contour levels
    levels = (MaxNLocator(nbins=100).tick_values(np.min(nmc), np.max(nmc)))

    # associate colours and contour levels
    norm1 = BoundaryNorm(levels, ncolors=cm.N, clip=True)

    ptindx = (tpc > 0)  # indices of times where secondary material present

    p0 = ax1.pcolormesh(t_array[ptindx],
                        sc,
                        nmc[:, ptindx],
                        cmap=cm,
                        norm=norm1,
                        shading='auto')

    cax = plt.axes([0.875, 0.40, 0.02, 0.18])  # specify colour bar position
    cb = plt.colorbar(p0,
                      cax=cax,
                      ticks=[0.00, 0.25, 0.50, 0.75, 1.00],
                      orientation='vertical')
    cb.ax.tick_params(labelsize=12)
    cb.set_label('mass fraction', size=12, rotation=270, labelpad=10.)

    ax1.set_xlabel(r'Time through experiment (hours)', fontsize=14)
    ax1.set_ylabel(
        r'$\rm{log_{10}(}$$C*_{\mathrm{298.15 K}}$$\rm{\, (\mu g\, m^{-3}))}$',
        fontsize=14,
        labelpad=10.)
    ax1.yaxis.set_tick_params(labelsize=14, direction='in', which='both')
    ax1.xaxis.set_tick_params(labelsize=14, direction='in', which='both')
    # array containing the location of tick labels
    ytloc = sc
    ax1.set_yticks(ytloc)
    # prepare list of strings for the tick labels
    ytl = []
    for i in ytloc:
        if (i == np.min(ytloc)):  # if the minimum include less than sign
            ytl.append(str('$\less$' + str(i + 0.5)))
            continue
        if (i == np.max(ytloc)
            ):  # if the maximum include the greater than or equal to sign
            ytl.append(str('$\geq$' + str(i + 0.5)))
            continue
        ytl.append(str(i + 0.5))  # otherwise just state number

    ax1.set_yticklabels(ytl)

    if (caller != 0):
        plt.show()  # show figure

    return ()  # end function
コード例 #23
0
def plotter(caller, dir_path, comp_names_to_plot, self):

    # inputs: ------------------------------------------------------------------
    # caller - marker for whether PyCHAM (0 for ug/m3 or 1 for ppb, 3 for # molecules/cm3) or tests (2) are the calling module
    # dir_path - path to folder containing results files to plot
    # comp_names_to_plot - chemical scheme names of components to plot
    # self - reference to GUI
    # --------------------------------------------------------------------------

    # chamber condition ---------------------------------------------------------
    # retrieve results
    (num_sb, num_comp, Cfac, yrec, Ndry, rbou_rec, x, timehr, _, y_MW, _,
     comp_names, y_MV, _, wall_on, space_mode, _, _, _, PsatPa, OC, H2Oi, _, _,
     _, group_indx, _, _) = retr_out.retr_out(dir_path)

    y_MW = np.array(y_MW)  # convert to numpy array from list
    Cfac = (np.array(Cfac)).reshape(-1, 1)  # convert to numpy array from list

    # number of actual particle size bins
    num_asb = (num_sb - wall_on)

    if (caller == 0 or caller == 1 or caller == 3):
        plt.ion()  # show results to screen and turn on interactive mode

    # prepare plot
    fig, (ax0) = plt.subplots(1, 1, figsize=(14, 7))

    if (comp_names_to_plot):  # if component names specified

        # gas-phase concentration sub-plot ---------------------------------------------
        for i in range(len(comp_names_to_plot)):

            if (comp_names_to_plot[i].strip() == 'H2O'):
                indx_plot = [H2Oi]
                indx_plot = np.array((indx_plot))
            if (comp_names_to_plot[i].strip() == 'RO2'):
                indx_plot = (np.array((group_indx['RO2i'])))
            if (comp_names_to_plot[i].strip() == 'RO'):
                indx_plot = (np.array((group_indx['ROi'])))
            if (comp_names_to_plot[i].strip() == 'HOMRO2'):
                indx_plot = []
                cindn = 0  # number of components
                for cind in comp_names:
                    if 'API_' in cind or 'api_' in cind:
                        if 'RO2' in cind:
                            indx_plot.append(cindn)
                    cindn += 1
                indx_plot = np.array(indx_plot)

            if (comp_names_to_plot[i].strip() != 'H2O'
                    and comp_names_to_plot[i].strip() != 'RO2'
                    and comp_names_to_plot[i].strip() != 'RO'
                    and comp_names_to_plot[i].strip() != 'HOMRO2'):
                try:  # will work if provided components were in simulation chemical scheme
                    # get index of this specified component, removing any white space
                    indx_plot = [
                        comp_names.index(comp_names_to_plot[i].strip())
                    ]
                    indx_plot = np.array((indx_plot))
                except:
                    self.l203a.setText(
                        str('Component ' + comp_names_to_plot[i] +
                            ' not found in chemical scheme used for this simulation'
                            ))
                    # set border around error message
                    if (self.bd_pl == 1):
                        self.l203a.setStyleSheet(0., '2px dashed red', 0., 0.)
                        self.bd_pl = 2
                    else:
                        self.l203a.setStyleSheet(0., '2px solid red', 0., 0.)
                        self.bd_pl = 1

                    plt.ioff()  # turn off interactive mode
                    plt.close()  # close figure window
                    return ()

            if (caller == 0):  # ug/m3 plot

                # gas-phase concentration (# molecules/cm3)
                conc = yrec[:, indx_plot].reshape(yrec.shape[0],
                                                  (indx_plot).shape[0]) * Cfac

                # gas-phase concentration (ug/m3)
                conc = ((conc / si.N_A) * y_MW[indx_plot]) * 1.e12

            if (caller == 1):  # ppb plot

                # gas-phase concentration (ppb)
                conc = yrec[:, indx_plot].reshape(yrec.shape[0],
                                                  (indx_plot).shape[0])

            if (caller == 3):  # # molecules/cm3 plot

                # gas-phase concentration (# molecules/cm3)
                conc = yrec[:, indx_plot].reshape(yrec.shape[0],
                                                  (indx_plot).shape[0]) * Cfac

            if (len(indx_plot) > 1):
                conc = np.sum(conc, axis=1)  # sum multiple components

            # plot this component
            if (comp_names_to_plot[i].strip() != 'RO2'
                    and comp_names_to_plot[i].strip() != 'RO'
                    and comp_names_to_plot[i].strip() !=
                    'HOMRO2'):  # if not the sum of organic peroxy radicals
                # log10 y axis
                ax0.semilogy(timehr,
                             conc,
                             '-+',
                             linewidth=4.,
                             label=str(
                                 str(comp_names[int(indx_plot)] +
                                     ' (gas-phase)')))
                # linear y axis
                #ax0.plot(timehr, conc, '-+', linewidth = 4., label = str(str(comp_names[int(indx_plot)]+' (gas-phase)')))

            if (comp_names_to_plot[i].strip() == 'RO2'
                ):  # if is the sum of organic peroxy radicals
                ax0.semilogy(timehr,
                             conc,
                             '-+',
                             linewidth=4.,
                             label=str(r'$\Sigma$RO2 (gas-phase)'))

            if (comp_names_to_plot[i].strip() == 'RO'
                ):  # if is the sum of organic alkoxy radicals
                ax0.semilogy(timehr,
                             conc,
                             '-+',
                             linewidth=4.,
                             label=str(r'$\Sigma$RO (gas-phase)'))

            if (comp_names_to_plot[i].strip() == 'HOMRO2'
                ):  # if is the sum of HOM organic peroxy radicals
                ax0.semilogy(timehr,
                             conc,
                             '-+',
                             linewidth=4.,
                             label=str(r'$\Sigma$HOMRO2 (gas-phase)'))

        if (caller == 0):  # ug/m3 plot
            ax0.set_ylabel(r'Concentration ($\rm{\mu}$g$\,$m$\rm{^{-3}}$)',
                           fontsize=14)
        if (caller == 1):  # ppb plot
            ax0.set_ylabel(r'Mixing ratio (ppb)', fontsize=14)
        if (caller == 3):  # # molecules/cm3 plot
            gpunit = str('\n(' + u'\u0023' + ' molecules/cm' + u'\u00B3' + ')')
            ax0.set_ylabel(r'Concentration ' + gpunit, fontsize=14)

        ax0.set_xlabel(r'Time through simulation (hours)', fontsize=14)
        ax0.yaxis.set_tick_params(labelsize=14, direction='in')
        ax0.xaxis.set_tick_params(labelsize=14, direction='in')
        ax0.legend(fontsize=14)

        # end of gas-phase concentration sub-plot ---------------------------------------

    if (caller == 2):  # display
        plt.show()

    return ()