CAMP 1.0.0
Chemistry Across Multiple Phases
rxn_SIMPOL_phase_transfer.c
Go to the documentation of this file.
1/* Copyright (C) 2021 Barcelona Supercomputing Center and University of
2 * Illinois at Urbana-Champaign
3 * SPDX-License-Identifier: MIT
4 *
5 * Phase Transfer reaction solver functions
6 *
7 */
8/** \file
9 * \brief Phase Transfer reaction solver functions
10 */
11#include <math.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <camp/aero_rep_solver.h>
15#include <camp/rxns.h>
16#include <camp/sub_model_solver.h>
17#include <camp/util.h>
18
19// TODO Lookup environmental indices during initialization
20#define TEMPERATURE_K_ env_data[0]
21#define PRESSURE_PA_ env_data[1]
22
23// Jacobian set indices
24#define JAC_GAS 0
25#define JAC_AERO 1
26
27// Aerosol mass concentration types
28#define PER_PARTICLE_MASS 0
29#define TOTAL_PARTICLE_MASS 1
30
31#define DELTA_H_ float_data[0]
32#define DELTA_S_ float_data[1]
33#define DIFF_COEFF_ float_data[2]
34#define PRE_C_AVG_ float_data[3]
35#define B1_ float_data[4]
36#define B2_ float_data[5]
37#define B3_ float_data[6]
38#define B4_ float_data[7]
39#define CONV_ float_data[8]
40#define MW_ float_data[9]
41#define NUM_AERO_PHASE_ int_data[0]
42#define GAS_SPEC_ (int_data[1] - 1)
43#define MFP_M_ rxn_env_data[0]
44#define ALPHA_ rxn_env_data[1]
45#define EQUIL_CONST_ rxn_env_data[2]
46#define KGM3_TO_PPM_ rxn_env_data[3]
47#define NUM_INT_PROP_ 2
48#define NUM_FLOAT_PROP_ 10
49#define NUM_ENV_PARAM_ 4
50#define AERO_SPEC_(x) (int_data[NUM_INT_PROP_ + x] - 1)
51#define AERO_ACT_ID_(x) (int_data[NUM_INT_PROP_ + NUM_AERO_PHASE_ + x] - 1)
52#define AERO_PHASE_ID_(x) \
53 (int_data[NUM_INT_PROP_ + 2 * (NUM_AERO_PHASE_) + x] - 1)
54#define AERO_REP_ID_(x) \
55 (int_data[NUM_INT_PROP_ + 3 * (NUM_AERO_PHASE_) + x] - 1)
56#define DERIV_ID_(x) (int_data[NUM_INT_PROP_ + 4 * (NUM_AERO_PHASE_) + x])
57#define GAS_ACT_JAC_ID_(x) \
58 int_data[NUM_INT_PROP_ + 1 + 5 * (NUM_AERO_PHASE_) + x]
59#define AERO_ACT_JAC_ID_(x) \
60 int_data[NUM_INT_PROP_ + 1 + 6 * (NUM_AERO_PHASE_) + x]
61#define JAC_ID_(x) (int_data[NUM_INT_PROP_ + 1 + 7 * (NUM_AERO_PHASE_) + x])
62#define PHASE_INT_LOC_(x) \
63 (int_data[NUM_INT_PROP_ + 2 + 10 * (NUM_AERO_PHASE_) + x] - 1)
64#define PHASE_FLOAT_LOC_(x) \
65 (int_data[NUM_INT_PROP_ + 2 + 11 * (NUM_AERO_PHASE_) + x] - 1)
66#define NUM_AERO_PHASE_JAC_ELEM_(x) (int_data[PHASE_INT_LOC_(x)])
67#define PHASE_JAC_ID_(x, s, e) \
68 int_data[PHASE_INT_LOC_(x) + 1 + (s) * NUM_AERO_PHASE_JAC_ELEM_(x) + e]
69#define EFF_RAD_JAC_ELEM_(x, e) float_data[PHASE_FLOAT_LOC_(x) + e]
70#define NUM_CONC_JAC_ELEM_(x, e) \
71 float_data[PHASE_FLOAT_LOC_(x) + NUM_AERO_PHASE_JAC_ELEM_(x) + e]
72#define MASS_JAC_ELEM_(x, e) \
73 float_data[PHASE_FLOAT_LOC_(x) + 2 * NUM_AERO_PHASE_JAC_ELEM_(x) + e]
74#define MW_JAC_ELEM_(x, e) \
75 float_data[PHASE_FLOAT_LOC_(x) + 3 * NUM_AERO_PHASE_JAC_ELEM_(x) + e]
76
77/** \brief Flag Jacobian elements used by this reaction
78 *
79 * \param model_data Pointer to the model data
80 * \param rxn_int_data Pointer to the reaction integer data
81 * \param rxn_float_data Pointer to the reaction floating-point data
82 * \param jac Jacobian
83 */
85 int *rxn_int_data,
86 double *rxn_float_data,
87 Jacobian *jac) {
88 int *int_data = rxn_int_data;
89 double *float_data = rxn_float_data;
90
91 // Allocate a temporary array to flag Jacobian elements used by the aerosol
92 // representation functions. The array is the size of the number of state
93 // variables in the model.
94 bool *aero_jac_elem =
95 (bool *)malloc(sizeof(bool) * model_data->n_per_cell_state_var);
96 if (aero_jac_elem == NULL) {
97 printf(
98 "\n\nERROR allocating space for 1D Jacobian structure array for "
99 "SIMPOL phase transfer reaction\n\n");
100 exit(1);
101 }
102
104 for (int i_aero_phase = 0; i_aero_phase < NUM_AERO_PHASE_; i_aero_phase++) {
107 jacobian_register_element(jac, AERO_SPEC_(i_aero_phase),
108 AERO_SPEC_(i_aero_phase));
109
110 if (AERO_ACT_ID_(i_aero_phase) > 0) {
112 jacobian_register_element(jac, AERO_SPEC_(i_aero_phase),
113 AERO_ACT_ID_(i_aero_phase));
114 }
115
116 // Reset the aero_jac_elem array to false
117 for (int i_elem = 0; i_elem < model_data->n_per_cell_state_var; ++i_elem)
118 aero_jac_elem[i_elem] = false;
119
120 // This function will set aero_jac_elem to true for each state variable used
121 // in the various aerosol representation functions. Not every flagged variable
122 // will be used in every aerosol function algorithm, but this covers every
123 // variable that could be used in any of the functions. The number of elements
124 // flagged is returned.
125 int n_jac_elem =
126 aero_rep_get_used_jac_elem(model_data, AERO_REP_ID_(i_aero_phase),
127 AERO_PHASE_ID_(i_aero_phase), aero_jac_elem);
128
129 // The array used to store Jacobian indices was allocated during the
130 // initialization of the reaction (Fortran code). Make sure the allocated
131 // size is large enough (sanity check).
132 if (n_jac_elem > NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase)) {
133 printf(
134 "\n\nERROR Received more Jacobian elements than expected for SIMPOL "
135 "partitioning reaction. Got %d, expected <= %d",
136 n_jac_elem, NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase));
137 exit(1);
138 }
139
140 // Loop through the set of flags (one per state variable) and for each flagged
141 // variable:
142 // - register two non-zero Jacobian elements:
143 // - one for the gas phase species dependence on each flagged variable
144 // - one for the aerosol phase species dependence on each flagged variable
145 // - store the state variable index in the PHASE_JAC_ID_ array for later
146 // use in the Jacobian update function.
147 int i_used_elem = 0;
148 for (int i_elem = 0; i_elem < model_data->n_per_cell_state_var; ++i_elem) {
149 if (aero_jac_elem[i_elem] == true) {
151 jacobian_register_element(jac, AERO_SPEC_(i_aero_phase), i_elem);
152 PHASE_JAC_ID_(i_aero_phase, JAC_GAS, i_used_elem) = i_elem;
153 PHASE_JAC_ID_(i_aero_phase, JAC_AERO, i_used_elem) = i_elem;
154 ++i_used_elem;
155 }
156 }
157
158 // The array of Jacobian indices was allocated during the initialization to
159 // be large enough to hold the maximum number of elements that could be used by
160 // the aerosol representation functions. If fewer elements were used, set the
161 // remaining elements to -1 to indicate they are not used.
162 for (; i_used_elem < NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase);
163 ++i_used_elem) {
164 PHASE_JAC_ID_(i_aero_phase, JAC_GAS, i_used_elem) = -1;
165 PHASE_JAC_ID_(i_aero_phase, JAC_AERO, i_used_elem) = -1;
166 }
167 if (i_used_elem != n_jac_elem) {
168 printf(
169 "\n\nERROR setting used Jacobian elements in SIMPOL phase "
170 "transfer reaction %d %d\n\n",
171 i_used_elem, n_jac_elem);
172 rxn_SIMPOL_phase_transfer_print(rxn_int_data, rxn_float_data);
173 exit(1);
174 }
175 }
176
177 free(aero_jac_elem);
178 return;
179}
180
181/** \brief Update the time derivative and Jacbobian array indices
182 *
183 * \param model_data Pointer to the model data for finding sub model ids
184 * \param deriv_ids Id of each state variable in the derivative array
185 * \param jac Jacobian
186 * \param rxn_int_data Pointer to the reaction integer data
187 * \param rxn_float_data Pointer to the reaction floating-point data
188 */
189void rxn_SIMPOL_phase_transfer_update_ids(ModelData *model_data, int *deriv_ids,
190 Jacobian jac, int *rxn_int_data,
191 double *rxn_float_data) {
192 int *int_data = rxn_int_data;
193 double *float_data = rxn_float_data;
194
195 // Update the time derivative ids
196 DERIV_ID_(0) = deriv_ids[GAS_SPEC_];
197 for (int i = 0; i < NUM_AERO_PHASE_; i++)
198 DERIV_ID_(i + 1) = deriv_ids[AERO_SPEC_(i)];
199
200 // Save the index of each non-zero Jacobian element in the flattened sparse
201 // matrix.
202 // Note that the order the elements are added to the Jacobian is the same
203 // order as they will be accessed in the Jacobian update function.
204 int i_jac = 0;
206 for (int i_aero_phase = 0; i_aero_phase < NUM_AERO_PHASE_; i_aero_phase++) {
207 JAC_ID_(i_jac++) =
208 jacobian_get_element_id(jac, AERO_SPEC_(i_aero_phase), GAS_SPEC_);
209 JAC_ID_(i_jac++) =
210 jacobian_get_element_id(jac, GAS_SPEC_, AERO_SPEC_(i_aero_phase));
211 JAC_ID_(i_jac++) = jacobian_get_element_id(jac, AERO_SPEC_(i_aero_phase),
212 AERO_SPEC_(i_aero_phase));
213 if (AERO_ACT_ID_(i_aero_phase) > 0) {
214 GAS_ACT_JAC_ID_(i_aero_phase) =
217 jac, AERO_SPEC_(i_aero_phase), AERO_ACT_ID_(i_aero_phase));
218 } else {
219 GAS_ACT_JAC_ID_(i_aero_phase) = -1;
220 AERO_ACT_JAC_ID_(i_aero_phase) = -1;
221 }
222
223 // Save non-zero Jacobian element indices for aerosol representation
224 // function dependencies. We use the state-variable indices stored
225 // previously in PHASE_JAC_ID_ to look up the corresponding Jacobian
226 // element index in the flattened sparse matrix.
227 // We do this for both the dependence of the gas phase species and the
228 // aerosol phase species on each independent variable used by the aerosol
229 // representation functions.
230 for (int i_elem = 0; i_elem < NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase);
231 ++i_elem) {
232 if (PHASE_JAC_ID_(i_aero_phase, JAC_GAS, i_elem) > 0) {
233 PHASE_JAC_ID_(i_aero_phase, JAC_GAS, i_elem) = jacobian_get_element_id(
234 jac, GAS_SPEC_, PHASE_JAC_ID_(i_aero_phase, JAC_GAS, i_elem));
235 }
236 if (PHASE_JAC_ID_(i_aero_phase, JAC_AERO, i_elem) > 0) {
237 PHASE_JAC_ID_(i_aero_phase, JAC_AERO, i_elem) = jacobian_get_element_id(
238 jac, AERO_SPEC_(i_aero_phase),
239 PHASE_JAC_ID_(i_aero_phase, JAC_AERO, i_elem));
240 }
241 }
242 }
243
244 return;
245}
246
247/** \brief Update reaction data for new environmental conditions
248 *
249 * For Phase Transfer reaction this only involves recalculating the rate
250 * constant.
251 *
252 * \param model_data Pointer to the model data
253 * \param rxn_int_data Pointer to the reaction integer data
254 * \param rxn_float_data Pointer to the reaction floating-point data
255 * \param rxn_env_data Pointer to the environment-dependent parameters
256 */
258 int *rxn_int_data,
259 double *rxn_float_data,
260 double *rxn_env_data) {
261 int *int_data = rxn_int_data;
262 double *float_data = rxn_float_data;
263 double *env_data = model_data->grid_cell_env;
264
265 // Calculate the mass accomodation coefficient if the N* parameter
266 // was provided, otherwise set it to 0.1 (per Zaveri 2008)
267 ALPHA_ = 0.1;
268 if (DELTA_H_ != 0.0 || DELTA_S_ != 0.0) {
269 double del_G = DELTA_H_ - TEMPERATURE_K_ * DELTA_S_;
270 ALPHA_ = exp(-del_G / (UNIV_GAS_CONST_ * TEMPERATURE_K_));
271 ALPHA_ = ALPHA_ / (1.0 + ALPHA_);
272 }
273
274 // replaced by transition-regime rate equation
275#if 0
276 // Save c_rms * mass_acc for use in mass transfer rate calc
277 MFP_M_ = PRE_C_AVG_ * sqrt(TEMPERATURE_K_) * mass_acc; // [m/s]
278#endif
279
280 /// save the mean free path [m] for calculating condensation rates
281 MFP_M_ = mean_free_path__m(DIFF_COEFF_, TEMPERATURE_K_, MW_);
282
283 // SIMPOL.1 vapor pressure [Pa]
284 double vp = B1_ / TEMPERATURE_K_ + B2_ + B3_ * TEMPERATURE_K_ +
285 B4_ * log(TEMPERATURE_K_);
286 vp = 101325.0 * pow(10, vp);
287
288 // Calculate the conversion from kg_x/m^3 -> ppm_x
290
291 // Calculate the partitioning coefficient K_eq (ppm_x/kg_x*kg_tot)
292 // such that for partitioning species X at equilibrium:
293 // [X]_gas = [X]_aero * activity_coeff_X * K_eq * MW_tot_aero / [tot]_aero
294 // where 'tot' indicates all species within an aerosol phase combined
295 // with []_gas in (ppm) and []_aero in (kg/m^3)
296 EQUIL_CONST_ = vp // Pa_x / (mol_x_aero/mol_tot_aero)
297 / PRESSURE_PA_ // 1/Pa_air
298 / MW_ // mol_x_aero/kg_x_aero
299 * 1.0e6; // ppm_x / (Pa_x/P_air)
300 // = ppm_x * mol_tot_aero / kg_x_aero
301
302 return;
303}
304
305/** \brief Calculate contributions to the time derivative \f$f(t,y)\f$ from
306 * this reaction.
307 *
308 * \param model_data Pointer to the model data, including the state array
309 * \param time_deriv TimeDerivative object
310 * \param rxn_int_data Pointer to the reaction integer data
311 * \param rxn_float_data Pointer to the reaction floating-point data
312 * \param rxn_env_data Pointer to the environment-dependent parameters
313 * \param time_step Current time step being computed (s)
314 */
315#ifdef CAMP_USE_SUNDIALS
317 ModelData *model_data, TimeDerivative time_deriv, int *rxn_int_data,
318 double *rxn_float_data, double *rxn_env_data, realtype time_step) {
319 int *int_data = rxn_int_data;
320 double *float_data = rxn_float_data;
321 double *state = model_data->grid_cell_state;
322 double *env_data = model_data->grid_cell_env;
323
324 // Calculate derivative contributions for each aerosol phase
325 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
326 // Get the particle effective radius (m)
327 realtype radius;
329 model_data, // model data
330 AERO_REP_ID_(i_phase), // aerosol representation index
331 AERO_PHASE_ID_(i_phase), // aerosol phase index
332 &radius, // particle effective radius (m)
333 NULL); // partial derivative
334
335 // Check the aerosol concentration type (per-particle or total per-phase
336 // mass)
337 int aero_conc_type = aero_rep_get_aero_conc_type(
338 model_data, // model data
339 AERO_REP_ID_(i_phase), // aerosol representation index
340 AERO_PHASE_ID_(i_phase)); // aerosol phase index
341
342 // Get the particle number concentration (#/m3)
343 realtype number_conc;
345 model_data, // model data
346 AERO_REP_ID_(i_phase), // aerosol representation index
347 AERO_PHASE_ID_(i_phase), // aerosol phase index
348 &number_conc, // particle number conc (#/m3)
349 NULL); // partial derivative
350
351 // Get the total mass of the aerosol phase (kg/m3)
352 realtype aero_phase_mass;
354 model_data, // model data
355 AERO_REP_ID_(i_phase), // aerosol representation index
356 AERO_PHASE_ID_(i_phase), // aerosol phase index
357 &aero_phase_mass, // total aerosol-phase mass (kg/m3)
358 NULL); // partial derivatives
359
360 // Get the total mass of the aerosol phase (kg/mol)
361 realtype aero_phase_avg_MW;
363 model_data, // model data
364 AERO_REP_ID_(i_phase), // aerosol representation index
365 AERO_PHASE_ID_(i_phase), // aerosol phase index
366 &aero_phase_avg_MW, // avg MW in the aerosol phase (kg/mol)
367 NULL); // partial derivatives
368
369 // This was replaced with the transition-regime condensation rate
370 // equations
371#if 0
372 long double cond_rate =
373 ((long double)1.0) / (radius * radius / (3.0 * DIFF_COEFF_) +
374 4.0 * radius / (3.0 * MFP_M_));
375#endif
376
377 // Calculate the rate constant for diffusion limited mass transfer to the
378 // aerosol phase (m3/#/s)
379 long double cond_rate =
380 gas_aerosol_transition_rxn_rate_constant(DIFF_COEFF_, MFP_M_, radius, ALPHA_);
381
382 // Calculate the evaporation rate constant (ppm_x*m^3/kg_x/s)
383 long double evap_rate =
384 cond_rate * (EQUIL_CONST_ * aero_phase_avg_MW / aero_phase_mass);
385
386 // Get the activity coefficient (if one exists)
387 long double act_coeff = 1.0;
388 if (AERO_ACT_ID_(i_phase) > -1) {
389 act_coeff = state[AERO_ACT_ID_(i_phase)];
390 }
391
392 // Calculate aerosol-phase evaporation rate (ppm/s)
393 evap_rate *= act_coeff;
394
395 // Calculate the evaporation and condensation rates
396 cond_rate *= state[GAS_SPEC_];
397 evap_rate *= state[AERO_SPEC_(i_phase)];
398
399 // per-particle mass concentration rates
400 if (aero_conc_type == PER_PARTICLE_MASS) {
401 // Change in the gas-phase is evaporation - condensation (ppm/s)
402 if (DERIV_ID_(0) >= 0) {
404 number_conc * evap_rate);
406 -number_conc * cond_rate);
407 }
408
409 // Change in the aerosol-phase species is condensation - evaporation
410 // (kg/m^3/s)
411 if (DERIV_ID_(1 + i_phase) >= 0) {
412 time_derivative_add_value(time_deriv, DERIV_ID_(1 + i_phase),
413 -evap_rate / KGM3_TO_PPM_);
414 time_derivative_add_value(time_deriv, DERIV_ID_(1 + i_phase),
415 cond_rate / KGM3_TO_PPM_);
416 }
417
418 // total-aerosol mass concentration rates
419 } else {
420 // Change in the gas-phase is evaporation - condensation (ppm/s)
421 if (DERIV_ID_(0) >= 0) {
423 number_conc * evap_rate);
425 -number_conc * cond_rate);
426 }
427
428 // Change in the aerosol-phase species is condensation - evaporation
429 // (kg/m^3/s)
430 if (DERIV_ID_(1 + i_phase) >= 0) {
431 time_derivative_add_value(time_deriv, DERIV_ID_(1 + i_phase),
432 -number_conc * evap_rate / KGM3_TO_PPM_);
433 time_derivative_add_value(time_deriv, DERIV_ID_(1 + i_phase),
434 number_conc * cond_rate / KGM3_TO_PPM_);
435 }
436 }
437 }
438 return;
439}
440#endif
441
442/** \brief Calculate contributions to the Jacobian from this reaction
443 *
444 * \param model_data Pointer to the model data
445 * \param jac Reaction Jacobian
446 * \param rxn_int_data Pointer to the reaction integer data
447 * \param rxn_float_data Pointer to the reaction floating-point data
448 * \param rxn_env_data Pointer to the environment-dependent parameters
449 * \param time_step Current time step being calculated (s)
450 */
451#ifdef CAMP_USE_SUNDIALS
453 Jacobian jac, int *rxn_int_data,
454 double *rxn_float_data,
455 double *rxn_env_data,
456 realtype time_step) {
457 int *int_data = rxn_int_data;
458 double *float_data = rxn_float_data;
459 double *state = model_data->grid_cell_state;
460 double *env_data = model_data->grid_cell_env;
461
462 // Calculate derivative contributions for each aerosol phase
463 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
464 // Get the particle effective radius (m)
465 realtype radius;
467 model_data, // model data
468 AERO_REP_ID_(i_phase), // aerosol representation index
469 AERO_PHASE_ID_(i_phase), // aerosol phase index
470 &radius, // particle effective radius (m)
471 &(EFF_RAD_JAC_ELEM_(i_phase, 0))); // partial derivative
472
473 // Check the aerosol concentration type (per-particle or total per-phase
474 // mass)
475 int aero_conc_type = aero_rep_get_aero_conc_type(
476 model_data, // model data
477 AERO_REP_ID_(i_phase), // aerosol representation index
478 AERO_PHASE_ID_(i_phase)); // aerosol phase index
479
480 // Get the particle number concentration (#/m3)
481 realtype number_conc;
483 model_data, // model data
484 AERO_REP_ID_(i_phase), // aerosol representation index
485 AERO_PHASE_ID_(i_phase), // aerosol phase index
486 &number_conc, // particle number conc (#/m3)
487 &(NUM_CONC_JAC_ELEM_(i_phase, 0))); // partial derivative
488
489 // Get the total mass of the aerosol phase (kg/m3)
490 realtype aero_phase_mass;
492 model_data, // model data
493 AERO_REP_ID_(i_phase), // aerosol representation index
494 AERO_PHASE_ID_(i_phase), // aerosol phase index
495 &aero_phase_mass, // total aerosol-phase mass (kg/m3)
496 &(MASS_JAC_ELEM_(i_phase, 0))); // partial derivatives
497
498 // Get the total average MW of the aerosol phase (kg/mol)
499 realtype aero_phase_avg_MW;
501 model_data, // model data
502 AERO_REP_ID_(i_phase), // aerosol representation index
503 AERO_PHASE_ID_(i_phase), // aerosol phase index
504 &aero_phase_avg_MW, // avg MW in the aerosol phase (kg/mol)
505 &(MW_JAC_ELEM_(i_phase, 0))); // partial derivatives
506
507 // This was replaced with the transition-regime condensation rate
508 // equations
509#if 0
510 long double cond_rate =
511 ((long double)1.0) / (radius * radius / (3.0 * DIFF_COEFF_) +
512 4.0 * radius / (3.0 * MFP_M_));
513#endif
514
515 // Calculate the rate constant for diffusion limited mass transfer to the
516 // aerosol phase (m3/#/s)
517 long double cond_rate =
518 gas_aerosol_transition_rxn_rate_constant(DIFF_COEFF_, MFP_M_, radius, ALPHA_);
519
520 // Calculate the evaporation rate constant (ppm_x*m^3/kg_x/s)
521 long double evap_rate =
522 cond_rate * (EQUIL_CONST_ * aero_phase_avg_MW / aero_phase_mass);
523
524 // Get the activity coefficient (if one exists)
525 long double act_coeff = 1.0;
526 if (AERO_ACT_ID_(i_phase) > -1) {
527 act_coeff = state[AERO_ACT_ID_(i_phase)];
528 }
529
530 // per-particle mass concentrations
531 if (aero_conc_type == PER_PARTICLE_MASS) {
532 // Change in the gas-phase is evaporation - condensation (ppm/s)
533 if (JAC_ID_(1 + i_phase * 3 + 1) >= 0) {
534 jacobian_add_value(jac, (unsigned int)JAC_ID_(1 + i_phase * 3 + 1),
535 JACOBIAN_PRODUCTION,
536 number_conc * evap_rate * act_coeff);
537 }
538 if (JAC_ID_(0) >= 0) {
539 jacobian_add_value(jac, (unsigned int)JAC_ID_(0), JACOBIAN_LOSS,
540 number_conc * cond_rate);
541 }
542
543 // Change in the aerosol-phase species is condensation - evaporation
544 // (kg/m^3/s)
545 if (JAC_ID_(1 + i_phase * 3) >= 0) {
546 jacobian_add_value(jac, (unsigned int)JAC_ID_(1 + i_phase * 3),
547 JACOBIAN_PRODUCTION, cond_rate / KGM3_TO_PPM_);
548 }
549 if (JAC_ID_(1 + i_phase * 3 + 2) >= 0) {
550 jacobian_add_value(jac, (unsigned int)JAC_ID_(1 + i_phase * 3 + 2),
551 JACOBIAN_LOSS, evap_rate * act_coeff / KGM3_TO_PPM_);
552 }
553
554 // Activity coefficient contributions
555 if (GAS_ACT_JAC_ID_(i_phase) > 0) {
557 jac, (unsigned int)GAS_ACT_JAC_ID_(i_phase), JACOBIAN_PRODUCTION,
558 number_conc * evap_rate * state[AERO_SPEC_(i_phase)]);
559 }
560 if (AERO_ACT_JAC_ID_(i_phase) > 0) {
562 jac, (unsigned int)AERO_ACT_JAC_ID_(i_phase), JACOBIAN_LOSS,
563 evap_rate / KGM3_TO_PPM_ * state[AERO_SPEC_(i_phase)]);
564 }
565
566 // total-particle mass concentrations
567 } else {
568 // Change in the gas-phase is evaporation - condensation (ppm/s)
569 if (JAC_ID_(1 + i_phase * 3 + 1) >= 0) {
570 jacobian_add_value(jac, (unsigned int)JAC_ID_(1 + i_phase * 3 + 1),
571 JACOBIAN_PRODUCTION,
572 number_conc * evap_rate * act_coeff);
573 }
574 if (JAC_ID_(0) >= 0) {
575 jacobian_add_value(jac, (unsigned int)JAC_ID_(0), JACOBIAN_LOSS,
576 number_conc * cond_rate);
577 }
578
579 // Change in the aerosol-phase species is condensation - evaporation
580 // (kg/m^3/s)
581 if (JAC_ID_(1 + i_phase * 3) >= 0) {
582 jacobian_add_value(jac, (unsigned int)JAC_ID_(1 + i_phase * 3),
583 JACOBIAN_PRODUCTION,
584 number_conc * cond_rate / KGM3_TO_PPM_);
585 }
586 if (JAC_ID_(1 + i_phase * 3 + 2) >= 0) {
587 jacobian_add_value(jac, (unsigned int)JAC_ID_(1 + i_phase * 3 + 2),
588 JACOBIAN_LOSS,
589 number_conc * evap_rate * act_coeff / KGM3_TO_PPM_);
590 }
591
592 // Activity coefficient contributions
593 if (GAS_ACT_JAC_ID_(i_phase) > 0) {
595 jac, (unsigned int)GAS_ACT_JAC_ID_(i_phase), JACOBIAN_PRODUCTION,
596 number_conc * evap_rate * state[AERO_SPEC_(i_phase)]);
597 }
598 if (AERO_ACT_JAC_ID_(i_phase) > 0) {
599 jacobian_add_value(jac, (unsigned int)AERO_ACT_JAC_ID_(i_phase),
600 JACOBIAN_LOSS,
601 number_conc * evap_rate / KGM3_TO_PPM_ *
602 state[AERO_SPEC_(i_phase)]);
603 }
604 }
605
606 // Get the overall rates
607 evap_rate *= act_coeff;
608 cond_rate *= state[GAS_SPEC_];
609 evap_rate *= state[AERO_SPEC_(i_phase)];
610
611 // Calculate partial derivatives
612
613 // this was replaced with the transition regime rate equations
614#if 0
615 realtype d_cond_d_radius =
616 -(2.0 * radius / (3.0 * DIFF_COEFF_) + 4.0 / (3.0 * MFP_M_)) *
617 cond_rate * cond_rate / state[GAS_SPEC_];
618#endif
619 realtype d_cond_d_radius = d_gas_aerosol_transition_rxn_rate_constant_d_radius(
620 DIFF_COEFF_, MFP_M_, radius, ALPHA_) *
621 state[GAS_SPEC_];
622 realtype d_evap_d_radius = d_cond_d_radius / state[GAS_SPEC_] *
623 EQUIL_CONST_ * aero_phase_avg_MW /
624 aero_phase_mass * state[AERO_SPEC_(i_phase)];
625 realtype d_evap_d_mass = -evap_rate / aero_phase_mass;
626 realtype d_evap_d_MW = evap_rate / aero_phase_avg_MW;
627
628 // per-particle mass concentrations
629 if (aero_conc_type == PER_PARTICLE_MASS) {
630 // Loop through Jac elements and update
631 for (int i_elem = 0; i_elem < NUM_AERO_PHASE_JAC_ELEM_(i_phase);
632 ++i_elem) {
633 // Gas-phase species dependencies
634 if (PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem) > 0) {
635 // species involved in effective radius calculations
637 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
638 JACOBIAN_PRODUCTION,
639 number_conc * d_evap_d_radius *
640 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
642 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
643 JACOBIAN_LOSS,
644 number_conc * d_cond_d_radius *
645 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
646
647 // species involved in number concentration
649 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
650 JACOBIAN_PRODUCTION,
651 evap_rate * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
653 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
654 JACOBIAN_LOSS, cond_rate * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
655
656 // species involved in mass calculations
658 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
659 JACOBIAN_PRODUCTION,
660 number_conc * d_evap_d_mass * MASS_JAC_ELEM_(i_phase, i_elem));
661
662 // species involved in average MW calculations
664 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
665 JACOBIAN_PRODUCTION,
666 number_conc * d_evap_d_MW * MW_JAC_ELEM_(i_phase, i_elem));
667 }
668
669 // Aerosol-phase species dependencies
670 if (PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem) > 0) {
671 // species involved in effective radius calculations
673 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
674 JACOBIAN_LOSS,
675 d_evap_d_radius / KGM3_TO_PPM_ *
676 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
678 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
679 JACOBIAN_PRODUCTION,
680 d_cond_d_radius / KGM3_TO_PPM_ *
681 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
682
683 // species involved in mass calculations
685 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
686 JACOBIAN_LOSS,
687 d_evap_d_mass / KGM3_TO_PPM_ * MASS_JAC_ELEM_(i_phase, i_elem));
688
689 // species involved in average MW calculations
691 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
692 JACOBIAN_LOSS,
693 d_evap_d_MW / KGM3_TO_PPM_ * MW_JAC_ELEM_(i_phase, i_elem));
694 }
695 }
696
697 // total-particle mass concentrations
698 } else {
699 // Loop through Jac elements and update
700 for (int i_elem = 0; i_elem < NUM_AERO_PHASE_JAC_ELEM_(i_phase);
701 ++i_elem) {
702 // Gas-phase species dependencies
703 if (PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem) > 0) {
704 // species involved in effective radius calculations
706 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
707 JACOBIAN_PRODUCTION,
708 number_conc * d_evap_d_radius *
709 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
711 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
712 JACOBIAN_LOSS,
713 number_conc * d_cond_d_radius *
714 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
715
716 // species involved in number concentration
718 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
719 JACOBIAN_PRODUCTION,
720 evap_rate * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
722 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
723 JACOBIAN_LOSS, cond_rate * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
724
725 // species involved in mass calculations
727 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
728 JACOBIAN_PRODUCTION,
729 number_conc * d_evap_d_mass * MASS_JAC_ELEM_(i_phase, i_elem));
730
731 // species involved in average MW calculations
733 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_GAS, i_elem),
734 JACOBIAN_PRODUCTION,
735 number_conc * d_evap_d_MW * MW_JAC_ELEM_(i_phase, i_elem));
736 }
737
738 // Aerosol-phase species dependencies
739 if (PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem) > 0) {
740 // species involved in effective radius calculations
742 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
743 JACOBIAN_LOSS,
744 number_conc * d_evap_d_radius / KGM3_TO_PPM_ *
745 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
747 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
748 JACOBIAN_PRODUCTION,
749 number_conc * d_cond_d_radius / KGM3_TO_PPM_ *
750 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
751
752 // species involved in number concentration
754 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
755 JACOBIAN_LOSS,
756 evap_rate / KGM3_TO_PPM_ * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
758 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
759 JACOBIAN_PRODUCTION,
760 cond_rate / KGM3_TO_PPM_ * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
761
762 // species involved in mass calculations
764 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
765 JACOBIAN_LOSS,
766 number_conc * d_evap_d_mass / KGM3_TO_PPM_ *
767 MASS_JAC_ELEM_(i_phase, i_elem));
768
769 // species involved in average MW calculations
771 jac, (unsigned int)PHASE_JAC_ID_(i_phase, JAC_AERO, i_elem),
772 JACOBIAN_LOSS,
773 number_conc * d_evap_d_MW / KGM3_TO_PPM_ *
774 MW_JAC_ELEM_(i_phase, i_elem));
775 }
776 }
777 }
778 }
779 return;
780}
781#endif
782
783/** \brief Print the Phase Transfer reaction parameters
784 *
785 * \param rxn_int_data Pointer to the reaction integer data
786 * \param rxn_float_data Pointer to the reaction floating-point data
787 */
788void rxn_SIMPOL_phase_transfer_print(int *rxn_int_data,
789 double *rxn_float_data) {
790 int *int_data = rxn_int_data;
791 double *float_data = rxn_float_data;
792
793 printf("\n\nSIMPOL.1 Phase Transfer reaction\n");
794 printf("\ndelta H: %le delta S: %le diffusion coeff: %le Pre C_avg: %le",
796 printf("\nB1: %le B2: %le B3:%le B4: %le", B1_, B2_, B3_, B4_);
797 printf("\nconv: %le MW: %le", CONV_, MW_);
798 printf("\nNumber of aerosol phases: %d", NUM_AERO_PHASE_);
799 printf("\nGas-phase species id: %d", GAS_SPEC_);
800 printf("\nGas-phase derivative id: %d", DERIV_ID_(0));
801 printf("\ndGas/dGas Jac id: %d", JAC_ID_(0));
802 printf("\n*** Aerosol phase data ***");
803 for (int i = 0; i < NUM_AERO_PHASE_; ++i) {
804 printf("\n Aerosol species id: %d", AERO_SPEC_(i));
805 printf("\n Activity coefficient id: %d", AERO_ACT_ID_(i));
806 printf("\n Aerosol phase id: %d", AERO_PHASE_ID_(i));
807 printf("\n Aerosol representation id: %d", AERO_REP_ID_(i));
808 printf("\n Aerosol species derivative id: %d", DERIV_ID_(i + 1));
809 printf("\n dGas/dAct coeff Jac id: %d", GAS_ACT_JAC_ID_(i));
810 printf("\n dAero/dAct coeff Jac id: %d", AERO_ACT_JAC_ID_(i));
811 printf("\n dAero/dGas Jac id: %d", JAC_ID_(1 + i * 3));
812 printf("\n dGas/dAero Jac id: %d", JAC_ID_(2 + i * 3));
813 printf("\n dAero/dAero Jac id: %d", JAC_ID_(3 + i * 3));
814 printf("\n Number of aerosol-phase species Jac elements: %d",
816 printf("\n dGas/dx ids:");
817 for (int j = 0; j < NUM_AERO_PHASE_JAC_ELEM_(i); ++j)
818 printf(" %d", PHASE_JAC_ID_(i, JAC_GAS, j));
819 printf("\n dAero/dx ids:");
820 for (int j = 0; j < NUM_AERO_PHASE_JAC_ELEM_(i); ++j)
821 printf(" %d", PHASE_JAC_ID_(i, JAC_AERO, j));
822 printf("\n Effective radius Jac elem:");
823 for (int j = 0; j < NUM_AERO_PHASE_JAC_ELEM_(i); ++j)
824 printf(" %le", EFF_RAD_JAC_ELEM_(i, j));
825 printf("\n Number concentration Jac elem:");
826 for (int j = 0; j < NUM_AERO_PHASE_JAC_ELEM_(i); ++j)
827 printf(" %le", NUM_CONC_JAC_ELEM_(i, j));
828 printf("\n Aerosol mass Jac elem:");
829 for (int j = 0; j < NUM_AERO_PHASE_JAC_ELEM_(i); ++j)
830 printf(" %le", MASS_JAC_ELEM_(i, j));
831 printf("\n Average MW Jac elem:");
832 for (int j = 0; j < NUM_AERO_PHASE_JAC_ELEM_(i); ++j)
833 printf(" %le", MW_JAC_ELEM_(i, j));
834 }
835
836 return;
837}
unsigned int jacobian_get_element_id(Jacobian jac, unsigned int dep_id, unsigned int ind_id)
Definition: Jacobian.c:200
void jacobian_add_value(Jacobian jac, unsigned int elem_id, unsigned int prod_or_loss, long double jac_contribution)
Definition: Jacobian.c:234
void jacobian_register_element(Jacobian *jac, unsigned int dep_id, unsigned int ind_id)
Definition: Jacobian.c:105
int aero_rep_get_aero_conc_type(ModelData *model_data, int aero_rep_idx, int aero_phase_idx)
Check whether aerosol concentrations are per-particle or total for each phase.
int aero_rep_get_used_jac_elem(ModelData *model_data, int aero_rep_idx, int aero_phase_idx, bool *jac_struct)
Flag Jacobian elements used to calculated mass, volume, etc.
void aero_rep_get_aero_phase_mass__kg_m3(ModelData *model_data, int aero_rep_idx, int aero_phase_idx, double *aero_phase_mass, double *partial_deriv)
Get the total mass of an aerosol phase in this representation ( )
void aero_rep_get_aero_phase_avg_MW__kg_mol(ModelData *model_data, int aero_rep_idx, int aero_phase_idx, double *aero_phase_avg_MW, double *partial_deriv)
Get the average molecular weight of an aerosol phase in this representation ( )
void aero_rep_get_number_conc__n_m3(ModelData *model_data, int aero_rep_idx, int aero_phase_idx, double *number_conc, double *partial_deriv)
Get the particle number concentration ( )
void aero_rep_get_effective_radius__m(ModelData *model_data, int aero_rep_idx, int aero_phase_idx, double *radius, double *partial_deriv)
Get the effective particle radius, (m)
#define AERO_ACT_ID_(x)
#define JAC_AERO
#define MW_JAC_ELEM_(x, e)
#define DELTA_H_
#define MW_
#define JAC_GAS
void rxn_SIMPOL_phase_transfer_get_used_jac_elem(ModelData *model_data, int *rxn_int_data, double *rxn_float_data, Jacobian *jac)
Flag Jacobian elements used by this reaction.
void rxn_SIMPOL_phase_transfer_update_env_state(ModelData *model_data, int *rxn_int_data, double *rxn_float_data, double *rxn_env_data)
Update reaction data for new environmental conditions.
void rxn_SIMPOL_phase_transfer_calc_jac_contrib(ModelData *model_data, Jacobian jac, int *rxn_int_data, double *rxn_float_data, double *rxn_env_data, realtype time_step)
Calculate contributions to the Jacobian from this reaction.
#define PRESSURE_PA_
#define AERO_PHASE_ID_(x)
#define B2_
#define B1_
#define EQUIL_CONST_
void rxn_SIMPOL_phase_transfer_print(int *rxn_int_data, double *rxn_float_data)
Print the Phase Transfer reaction parameters.
#define PRE_C_AVG_
void rxn_SIMPOL_phase_transfer_calc_deriv_contrib(ModelData *model_data, TimeDerivative time_deriv, int *rxn_int_data, double *rxn_float_data, double *rxn_env_data, realtype time_step)
Calculate contributions to the time derivative from this reaction.
#define MFP_M_
#define KGM3_TO_PPM_
#define CONV_
#define ALPHA_
#define GAS_ACT_JAC_ID_(x)
#define GAS_SPEC_
#define TEMPERATURE_K_
#define PER_PARTICLE_MASS
#define B4_
#define JAC_ID_(x)
#define PHASE_JAC_ID_(x, s, e)
void rxn_SIMPOL_phase_transfer_update_ids(ModelData *model_data, int *deriv_ids, Jacobian jac, int *rxn_int_data, double *rxn_float_data)
Update the time derivative and Jacbobian array indices.
#define AERO_ACT_JAC_ID_(x)
#define MASS_JAC_ELEM_(x, e)
#define B3_
#define NUM_AERO_PHASE_JAC_ELEM_(x)
#define DIFF_COEFF_
#define NUM_AERO_PHASE_
#define AERO_REP_ID_(x)
#define EFF_RAD_JAC_ELEM_(x, e)
#define AERO_SPEC_(x)
#define DELTA_S_
#define NUM_CONC_JAC_ELEM_(x, e)
#define DERIV_ID_(x)
void time_derivative_add_value(TimeDerivative time_deriv, unsigned int spec_id, long double rate_contribution)