21
21
##########################################################################################
22
22
'''solution of ODEs, generated by eqn_pars.py'''
23
23
# 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
25
25
26
26
import numpy as np
27
27
import scipy .sparse as SP
@@ -156,6 +156,56 @@ def dydt(t, y): # define the ODE(s)
156
156
df_indx = (df_indx == 1 ) # transform to Boolean array
157
157
dd [df_indx , 0 ] -= y [df_indx , 0 ]* self .dil_fac_now
158
158
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
+
159
209
dd = (dd [:, 0 ]).reshape (num_sb + 1 , num_comp )
160
210
# force all components in size bins with no particle to zero
161
211
if (num_asb > 0 ):
@@ -181,7 +231,7 @@ def jac(t, y): # define the Jacobian
181
231
y = y .reshape (- 1 , 1 )
182
232
183
233
# elements of sparse Jacobian matrix
184
- data = np .zeros ((151 ))
234
+ data = np .zeros ((4492 + jac_mod_len ))
185
235
186
236
for i in range (self .rindx_g .shape [0 ]): # gas-phase reaction loop
187
237
# reaction rate (# molecules/cm3/s)
@@ -195,6 +245,84 @@ def jac(t, y): # define the Jacobian
195
245
data [self .jac_indx_g [i , 0 :self .njac_g [i , 0 ]]] += jac_coeff
196
246
197
247
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
+
198
326
data [self .jac_extr_indx ] -= 1. * self .dil_fac_now
199
327
200
328
# create Jacobian
0 commit comments