Skip to content

Commit f616786

Browse files
committed
resolved bugs in plotter_pp
minor bugs inside plotter_pp that were stopping plotting of particle surface areas have been resolved
1 parent 91a3d61 commit f616786

16 files changed

+165
-22
lines changed
Binary file not shown.
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
2.79 KB
Binary file not shown.
59 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
6 Bytes
Binary file not shown.

PyCHAM/dydt_rec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
# changes due to gas-phase photochemistry and partitioning are included;
2525
# generated in init_conc and treats loss from gas-phase as negative
2626

27-
# File Created at 2024-11-12 15:18:27.878545
27+
# File Created at 2024-11-12 16:11:40.155400
2828

2929
import numpy as np
3030

PyCHAM/err_log.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/Users/user/Documents/GitHub/PyCHAM/PyCHAM/output/autoAPRAM_TOY_20241104_scheme/TOY_course_03
1+
/Users/user/Library/CloudStorage/OneDrive-TheUniversityofManchester/Man_Teach/research_experience/SAPHIR-STAR/PyCHAM_output/mcmAP_scheme/Baker_2024_Exp2p1

PyCHAM/hyst_eq.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
##########################################################################################
2222
'''solution of deliquescence and efflorescence RH, generated by eqn_pars.py in fully functioning mode, or by ui_check.py in testing mode'''
2323
# module to estimate deliquescence and efflorescence relative humidity as a function of temperature
24-
# File Created at 2024-11-12 16:00:59.660521
24+
# File Created at 2024-11-12 16:26:35.051795
2525

2626
# function for deliquescence
2727
def drh(TEMP):

PyCHAM/ode_solv.py

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
##########################################################################################
2222
'''solution of ODEs, generated by eqn_pars.py'''
2323
# module to solve system of ordinary differential equations (ODEs) using solve_ivp of Scipy
24-
# File Created at 2024-11-12 15:18:27.869878
24+
# File Created at 2024-11-12 16:11:40.109821
2525

2626
import numpy as np
2727
import scipy.sparse as SP
@@ -156,6 +156,56 @@ def dydt(t, y): # define the ODE(s)
156156
df_indx = (df_indx == 1) # transform to Boolean array
157157
dd[df_indx, 0] -= y[df_indx, 0]*self.dil_fac_now
158158

159+
# gas-particle and gas-wall partitioning-----------------
160+
# transform component concentrations in particles and walls
161+
# into size bins in rows, components in columns
162+
ymat = (y[num_comp::, 0]).reshape(num_sb, num_comp)
163+
164+
# for particles, force all components in bins with no particle to zero
165+
ymat[0:num_asb, :][N_perbin[:, 0] == 0, :] = 0
166+
167+
# for particles, calculate total particle-phase concentration per size bin (# molecules/cm3 (air))
168+
csum = ((ymat[0:num_asb, :].sum(axis=1)-ymat[0:num_asb, self.seedi].sum(axis=1))+((ymat[0:num_asb, self.seedi]*core_diss).sum(axis=1)).reshape(-1)).reshape(-1, 1)
169+
# tile total particle-phase concentration over components (# molecules/cm3 (air))
170+
csum = np.tile(csum, [1, num_comp])
171+
# concatenate wall bin total concentrations to total particle-phase concentration (# molecules/cm3)
172+
csum = np.concatenate((csum, self.Cw), axis=0)
173+
174+
# size bins with contents
175+
isb = (csum[0:num_asb, 0] > 0.)
176+
177+
# wall bins with contents
178+
wsb = (self.Cw[:, 0] > 0.)
179+
180+
# container for gas-phase concentrations at particle surface and at wall surface
181+
Csit = np.zeros((num_sb, num_comp))
182+
183+
# mole fractions of components at particle surface
184+
Csit[0:num_asb, :][isb, :] = (ymat[0:num_asb, :][isb, :]/csum[0:num_asb, :][isb, :])
185+
# mole fraction of components on walls, note that Cw included in csum above
186+
Csit[num_asb::, :][wsb, :] = (ymat[num_asb::, :][wsb, :]/csum[num_asb::, :][wsb, :])
187+
188+
# gas-phase concentration of components at
189+
# particle surface (# molecules/cm3 (air))
190+
Csit[0:num_asb, :][isb, :] = Csit[0:num_asb, :][isb, :]*self.Psat[0:num_asb, :][isb, :]*kelv_fac[isb]*act_coeff[0:num_asb, :][isb, :]
191+
# partitioning rate (# molecules/cm3/s)
192+
dd_all = kimt[0:num_asb, :]*(y[0:num_comp, 0].reshape(1, -1)-Csit[0:num_asb, :])
193+
# gas-phase change
194+
dd[0:num_comp, 0] -= dd_all.sum(axis=0)
195+
# particle change
196+
dd[num_comp:num_comp*(num_asb+1), 0] += (dd_all.flatten())
197+
198+
if any(wsb):
199+
# gas-phase concentration of components at
200+
# wall surface (# molecules/cm3 (air))
201+
Csit[num_asb::, :][wsb, :] = Csit[num_asb::, :][wsb, :]*self.Psat[num_asb::, :][wsb, :]*act_coeff[num_asb::, :][wsb, :]
202+
# partitioning rate (# molecules/cm3/s)
203+
dd_all = kimt[num_asb::, :]*(y[0:num_comp, 0].reshape(1, -1)-Csit[num_asb::, :])
204+
# gas-phase change (summed over all wall bins)
205+
dd[0:num_comp, 0] -= dd_all.sum(axis=0)
206+
# wall change
207+
dd[num_comp*(num_asb+1)::, 0] += (dd_all.flatten())
208+
159209
dd = (dd[:, 0]).reshape(num_sb+1, num_comp)
160210
# force all components in size bins with no particle to zero
161211
if (num_asb > 0):
@@ -181,7 +231,7 @@ def jac(t, y): # define the Jacobian
181231
y = y.reshape(-1, 1)
182232

183233
# elements of sparse Jacobian matrix
184-
data = np.zeros((151))
234+
data = np.zeros((4492+jac_mod_len))
185235

186236
for i in range(self.rindx_g.shape[0]): # gas-phase reaction loop
187237
# reaction rate (# molecules/cm3/s)
@@ -195,6 +245,84 @@ def jac(t, y): # define the Jacobian
195245
data[self.jac_indx_g[i, 0:self.njac_g[i, 0]]] += jac_coeff
196246

197247

248+
# gas-particle partitioning
249+
part_eff = np.zeros((1328))
250+
if (sum(N_perbin[:, 0]) > 0.): # if any particles present
251+
part_eff[0:664:2] = -kimt[0:num_asb, :].sum(axis=0) # effect of gas on gas
252+
253+
# empty array for any particle-on-gas and particle-on-particle effects on water in the particle-phase for rows of Jacobian
254+
part_eff_rw = np.zeros((len(jac_part_hmf_indx)))
255+
# empty array for any particle-on-gas and particle-on-particle effects of water in the particle-phase on non-water components in the particle-phase for columns of Jacobian
256+
part_eff_cl = np.zeros((len(jac_part_H2O_indx)))
257+
# starting index for jacobian row inputs for effect on water
258+
sti_rw = 0
259+
260+
# transform particle phase concentrations into
261+
# size bins in rows, components in columns
262+
ymat = (y[num_comp:num_comp*(num_asb+1), 0]).reshape(num_asb, num_comp)
263+
ymat[N_perbin[:, 0] == 0, :] = 0 # ensure zero components where zero particles
264+
# total particle-phase concentration per size bin (molecules/cm3 (air))
265+
csum = ymat.sum(axis=1)-ymat[:, self.seedi].sum(axis=1)+(ymat[:, self.seedi]*core_diss).sum(axis=1)
266+
267+
# effect of particle on gas
268+
for isb in range(int(num_asb)): # size bin loop
269+
if (csum[isb] > 0): # if components present in this size bin
270+
# effect of gas on particle
271+
part_eff[1+isb:num_comp*(num_asb+1):num_asb+1] = +kimt[isb, :]
272+
# start index
273+
sti = int((num_asb+1)*num_comp+isb*(num_comp*2))
274+
# diagonal index
275+
diag_indxg = sti+np.arange(0, num_comp*2, 2).astype('int')
276+
diag_indxp = sti+np.arange(1, num_comp*2, 2).astype('int')
277+
# prepare for diagonal (component effect on itself)
278+
diag = kimt[isb, :]*self.Psat[0, :]*act_coeff[0, :]*kelv_fac[isb, 0]*(-(csum[isb]-ymat[isb, :])/(csum[isb]**2.))
279+
# implement to part_eff
280+
part_eff[diag_indxg] -= diag
281+
part_eff[diag_indxp] += diag
282+
283+
if (rw_indx[isb] > -1): # if water in this size bin
284+
# prepare for row(s) (particle-phase non-water component effects on water in particle phase)
285+
rw = kimt[isb, rw_indx[isb]]*self.Psat[0, rw_indx[isb]]*act_coeff[0, rw_indx[isb]]*kelv_fac[isb, 0]*(-(-ymat[isb, rw_indx[isb]])/(csum[isb]**2.))
286+
# indices
287+
indxg = sti_rw+np.arange(0, ((num_comp-1)*2), 2).astype('int')
288+
indxp = sti_rw+np.arange(1, ((num_comp-1)*2), 2).astype('int')
289+
# implement to part_eff_rw
290+
part_eff_rw[indxg] -= rw
291+
part_eff_rw[indxp] += rw
292+
293+
# prepare for column(s) (particle-phase water effect on non-water in particle phase)
294+
#cl = kimt[isb, :]*self.Psat[0, :]*act_coeff[0, :]*kelv_fac[isb, 0]*(-(-ymat[isb, :])/(csum[isb]**2.))
295+
#cl = np.zeros((num_comp))
296+
# remove water
297+
#cl = np.concatenate((cl[0:H2Oi], cl[H2Oi+1::]))
298+
#indxg = sti_rw+np.arange(0, (num_comp-1)).astype('int')
299+
#indxp = sti_rw+np.arange((num_comp-1), (num_comp-1)*2).astype('int')
300+
# implement to part_eff_cl
301+
#part_eff_cl[indxg] -= cl
302+
#part_eff_cl[indxp] += cl
303+
304+
# starting index update
305+
sti_rw += (num_comp-1)*2
306+
307+
data[self.jac_part_indxn] += part_eff # diagonal
308+
data[jac_part_hmf_indx] += part_eff_rw # rows
309+
#data[jac_part_H2O_indx] += part_eff_cl # columns
310+
311+
wsb = 0 # count on wall bins
312+
# holder for wall effect
313+
wall_eff = np.zeros((1328))
314+
# effect of gas on gas
315+
wall_eff[0:664:2] = -np.sum(kimt[num_asb::, :], axis=0)
316+
for wsb in range(int(self.wall_on)): # wall bin loop
317+
if (self.Cw[wsb, 0] > 0.):
318+
# effect of gas on wall
319+
wall_eff[wsb+1:664:2] = +kimt[num_asb+wsb, :]
320+
# effect of wall on gas
321+
wall_eff[wsb*2*num_comp+num_comp*(self.wall_on+1):num_comp*(self.wall_on+1)+(wsb+1)*2*num_comp:2] = +kimt[num_asb::, :][wsb, :]*(self.Psat[num_asb::, :][wsb, :]*act_coeff[num_asb::, :][wsb, :]/self.Cw[wsb, :])
322+
# effect of wall on wall
323+
wall_eff[wsb*2*num_comp+num_comp*(self.wall_on+1)+1:num_comp*(self.wall_on+1)+(wsb+1)*2*num_comp:2] = -kimt[num_asb::, :][wsb, :]*(self.Psat[num_asb::, :][wsb, :]*act_coeff[num_asb::, :][wsb, :]/self.Cw[wsb, :])
324+
data[self.jac_wall_indxn] += wall_eff
325+
198326
data[self.jac_extr_indx] -= 1.*self.dil_fac_now
199327

200328
# create Jacobian

PyCHAM/plotter_pp.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
6767
rbou_rec = np.zeros((self.ro_obj.rad.shape[0], self.ro_obj.rad.shape[1]))
6868
rbou_rec[:, :] = self.ro_obj.rad[:, :]
6969
group_indx = self.ro_obj.gi
70+
y_MV = np.array((self.ro_obj.comp_MV)) # cm3/mol
7071

7172
# number of actual particle size bins
7273
num_asb = (self.ro_obj.nsb-self.ro_obj.wf)
@@ -296,7 +297,7 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
296297
ax0.xaxis.set_tick_params(labelsize = 14, direction = 'in')
297298
ax0.legend(fontsize = 14)
298299

299-
# end of particle-phase concentration sub-plot ---------------------------------------
300+
# end of particle-phase concentration sub-plot ----------------------------
300301

301302
# if called by button to plot temporal profile of total particle-phase concentration
302303
# excluding water and seed
@@ -314,7 +315,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
314315
ppc[:, i::num_comp] = 0.
315316

316317
# tile molar weights over size bins and times
317-
y_mwt = np.tile(np.array((self.ro_obj.comp_MW)).reshape(1, -1), (1, self.ro_obj.nsb-self.ro_obj.wf))
318+
y_mwt = np.tile(np.array((self.ro_obj.comp_MW)).reshape(1, -1),
319+
(1, self.ro_obj.nsb-self.ro_obj.wf))
318320
y_mwt = np.tile(y_mwt, (ppc.shape[0], 1))
319321
# convert from # molecules/cm3 to ug/m3
320322
ppc = (ppc/si.N_A)*y_mwt*1.e12
@@ -323,7 +325,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
323325
ppc = np.sum(ppc, axis=1)
324326

325327
# plot
326-
ax0.plot(self.ro_obj.thr, ppc, '+', linewidth = 4., label = 'total particle-phase excluding seed and water')
328+
ax0.plot(self.ro_obj.thr, ppc, '+', linewidth = 4.,
329+
label = 'total particle-phase excluding seed and water')
327330
ax0.set_ylabel(r'Concentration ($\rm{\mu}$g$\,$m$\rm{^{-3}}$)', fontsize = 14)
328331
ax0.set_xlabel(r'Time through simulation (hours)', fontsize = 14)
329332
ax0.yaxis.set_tick_params(labelsize = 14, direction = 'in')
@@ -340,7 +343,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
340343
ppc = yrec[:, self.ro_obj.nc::]
341344

342345
# tile molar weights over size bins and times
343-
y_mwt = np.tile(np.array((self.ro_obj.comp_MW)).reshape(1, -1), (1, (self.ro_obj.nsb-self.ro_obj.wf)))
346+
y_mwt = np.tile(np.array((self.ro_obj.comp_MW)).reshape(1, -1),
347+
(1, (self.ro_obj.nsb-self.ro_obj.wf)))
344348
y_mwt = np.tile(y_mwt, (ppc.shape[0], 1))
345349

346350
# convert to ug/m3 from # molecules/cm3
@@ -351,7 +355,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
351355

352356
# sum particle-phase concentrations over size bins (ug/m3),
353357
# but keeping components separate
354-
ppc_t = np.sum(ppc_t.reshape(self.ro_obj.nsb-self.ro_obj.wf, self.ro_obj.nc), axis=0)
358+
ppc_t = np.sum(ppc_t.reshape(self.ro_obj.nsb-self.ro_obj.wf,
359+
self.ro_obj.nc), axis=0)
355360

356361
# convert to mass contributions
357362
ppc_t = ((ppc_t/np.sum(ppc_t))*100.).reshape(-1, 1)
@@ -377,15 +382,18 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
377382
# get contribution (%)
378383
ppci = (ppci[ppc_sbc>0.]/ppc_sbc[ppc_sbc>0.])*100.
379384

380-
ax0.plot(self.ro_obj.thr[ppc_sbc>0.], ppci, '-+', linewidth = 4., label = namei)
385+
ax0.plot(self.ro_obj.thr[ppc_sbc>0.], ppci, '-+', linewidth = 4.,
386+
label = namei)
381387

382-
ax0.set_ylabel(r'Contribution to particle-phase mass concentration (%)', fontsize = 14)
388+
ax0.set_ylabel(r'Contribution to particle-phase mass concentration (%)',
389+
fontsize = 14)
383390
ax0.set_xlabel(r'Time through simulation (hours)', fontsize = 14)
384391
ax0.yaxis.set_tick_params(labelsize = 14, direction = 'in')
385392
ax0.xaxis.set_tick_params(labelsize = 14, direction = 'in')
386393
ax0.legend(fontsize = 14)
387394

388-
# if called by button to plot top contributors to particle-phase excluding seed and water
395+
# if called by button to plot top contributors to particle-phase excluding
396+
# seed and water
389397
if (caller == 7):
390398

391399
import scipy.constants as si
@@ -402,7 +410,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
402410
ppc[:, self.ro_obj.H2O_ind::self.ro_obj.nc] = 0. # zero water
403411

404412
# tile molar weights over size bins and times
405-
y_mwt = np.tile(np.array((self.ro_obj.comp_MW)).reshape(1, -1), (1, (self.ro_obj.nsb-self.ro_obj.wf)))
413+
y_mwt = np.tile(np.array((self.ro_obj.comp_MW)).reshape(1, -1),
414+
(1, (self.ro_obj.nsb-self.ro_obj.wf)))
406415
y_mwt = np.tile(y_mwt, (ppc.shape[0], 1))
407416

408417
# convert to ug/m3 from # molecules/cm3
@@ -439,7 +448,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
439448
# get contribution (%)
440449
ppci = (ppci[ppc_sbc>0.]/ppc_sbc[ppc_sbc>0.])*100.
441450

442-
ax0.plot(self.ro_obj.thr[ppc_sbc>0.], ppci, '-+', linewidth = 4., label = namei)
451+
ax0.plot(self.ro_obj.thr[ppc_sbc>0.], ppci, '-+',
452+
linewidth = 4., label = namei)
443453

444454
ax0.set_ylabel(r'Contribution to particle-phase mass concentration (%)', fontsize = 14)
445455
ax0.set_xlabel(r'Time through simulation (hours)', fontsize = 14)
@@ -460,7 +470,8 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
460470
asp = np.sum(asp, axis=1)
461471
# plot
462472
ax0.plot(self.ro_obj.thr, asp, '-+', linewidth = 4.)
463-
ax0.set_ylabel(r'Total particle-phase surface area concentration ($\rm{m^{2}\,m^{-3}}$)', fontsize = 14)
473+
ax0.set_ylabel(str(r'Total particle-phase surface area concentration ' +
474+
r'($\rm{m^{2}\,m^{-3}}$)'), fontsize = 14)
464475
ax0.set_xlabel(r'Time through simulation (hours)', fontsize = 14)
465476
ax0.yaxis.set_tick_params(labelsize = 14, direction = 'in')
466477
ax0.xaxis.set_tick_params(labelsize = 14, direction = 'in')
@@ -480,9 +491,10 @@ def plotter(caller, dir_path, comp_names_to_plot, self):
480491
ppcs = np.zeros((ppc.shape[0], (num_sb-wall_on)))
481492

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

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

PyCHAM/rate_coeffs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
##########################################################################################
2222
'''module for calculating reaction rate coefficients (automatically generated)'''
2323
# module to hold expressions for calculating rate coefficients #
24-
# created at 2024-11-12 16:00:59.641214
24+
# created at 2024-11-12 16:26:35.038128
2525

2626
import numpy
2727
import photolysisRates

PyCHAM/retr_out.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def retr_out(self):
4545
try: # try opening file
4646
const_in = open(fname)
4747
except:
48-
err_mess = str('Error - no such file ' + fname + ', please check it still exists')
48+
err_mess = str('Error - no such file ' + fname +
49+
', please check it still exists')
4950
self.l203a.setText(err_mess)
5051
# set border around error message
5152
if (self.bd_pl == 1):

PyCHAM/save.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,14 +442,16 @@ def saving(y_mat, Nresult_dry, Nresult_wet, t_out, num_comp,
442442
Nresult_dry = (np.array((Nresult_dry)))
443443
rbou_rec = (np.array((rbou_rec)))
444444

445-
# to ensure quick use of output, store results in an object in an identical way to retr_out
445+
# to ensure quick use of output, store results in an object in an identical
446+
# way to retr_out
446447
# create a class to hold outputs
447448
class ro_outputs:
448449
sp = output_by_sim_sch_ext # chemical scheme path
449450
vp = output_by_sim_mv_ext # model variables path
450451
gi = group_indx # indices of groups of components
451452
gen_numbers = self.gen_num # for each component, the generation number
452-
HyC = self.HC.tolist() # hydrogen:carbon ratios for each component, this output added on 31/05/2022
453+
# hydrogen:carbon ratios for each component, this output added on 31/05/2022
454+
HyC = self.HC.tolist()
453455
nominal_mass = self.nom_mass.tolist()
454456
nsb = numsb
455457
nc = num_comp

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name="PyCHAM",
11-
version="5.2.5",
11+
version="5.2.6",
1212
author="Simon P. O'Meara, Shuxuan Xu and Ademipo Onanuga",
1313
author_email="simon.omeara@manchester.ac.uk",
1414
description="PyCHAM: CHemistry with Aerosol Microphysics in Python",

0 commit comments

Comments
 (0)