CAMP 1.0.0
Chemistry Across Multiple Phases
rxn_condensed_phase_arrhenius.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 * Condensed Phase Arrhenius reaction solver functions
6 *
7 */
8/** \file
9 * \brief Condensed Phase Arrhenius reaction solver functions
10 */
11#include <math.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include "../rxns.h"
15
16// TODO Lookup environmental indices during initialization
17#define TEMPERATURE_K_ env_data[0]
18#define PRESSURE_PA_ env_data[1]
19
20#define NUM_REACT_ (int_data[0])
21#define NUM_PROD_ (int_data[1])
22#define NUM_AERO_PHASE_ (int_data[2])
23#define A_ (float_data[0])
24#define B_ (float_data[1])
25#define C_ (float_data[2])
26#define D_ (float_data[3])
27#define E_ (float_data[4])
28#define RATE_CONSTANT_ (rxn_env_data[0])
29#define NUM_INT_PROP_ 3
30#define NUM_FLOAT_PROP_ 5
31#define NUM_ENV_PARAM_ 1
32#define REACT_(x) (int_data[NUM_INT_PROP_ + x] - 1)
33#define PROD_(x) \
34 (int_data[NUM_INT_PROP_ + NUM_REACT_ * NUM_AERO_PHASE_ + x] - 1)
35#define WATER_(x) \
36 (int_data[NUM_INT_PROP_ + (NUM_REACT_ + NUM_PROD_) * NUM_AERO_PHASE_ + x] - 1)
37#define DERIV_ID_(x) \
38 (int_data[NUM_INT_PROP_ + (NUM_REACT_ + NUM_PROD_ + 1) * NUM_AERO_PHASE_ + x])
39#define JAC_ID_(x) \
40 (int_data[NUM_INT_PROP_ + \
41 (2 * (NUM_REACT_ + NUM_PROD_) + 1) * NUM_AERO_PHASE_ + x])
42#define YIELD_(x) (float_data[NUM_FLOAT_PROP_ + x])
43#define KGM3_TO_MOLM3_(x) (float_data[NUM_FLOAT_PROP_ + NUM_PROD_ + x])
44
45/** \brief Flag Jacobian elements used by this reaction
46 *
47 * \param rxn_int_data Pointer to the reaction integer data
48 * \param rxn_float_data Pointer to the reaction floating-point data
49 * \param jac Jacobian
50 */
52 double *rxn_float_data,
53 Jacobian *jac) {
54 int *int_data = rxn_int_data;
55 double *float_data = rxn_float_data;
56
57 // Loop over all the instances of the specified phase
58 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
59 // Add dependence on reactants for reactants and products
60 for (int i_react_ind = i_phase * NUM_REACT_;
61 i_react_ind < (i_phase + 1) * NUM_REACT_; i_react_ind++) {
62 for (int i_react_dep = i_phase * NUM_REACT_;
63 i_react_dep < (i_phase + 1) * NUM_REACT_; i_react_dep++)
64 jacobian_register_element(jac, REACT_(i_react_dep),
65 REACT_(i_react_ind));
66 for (int i_prod_dep = i_phase * NUM_PROD_;
67 i_prod_dep < (i_phase + 1) * NUM_PROD_; i_prod_dep++)
68 jacobian_register_element(jac, PROD_(i_prod_dep), REACT_(i_react_ind));
69 }
70
71 // Add dependence on aerosol-phase water for reactants and products in
72 // aqueous reactions
73 if (WATER_(i_phase) >= 0) {
74 for (int i_react_dep = i_phase * NUM_REACT_;
75 i_react_dep < (i_phase + 1) * NUM_REACT_; i_react_dep++)
76 jacobian_register_element(jac, REACT_(i_react_dep), WATER_(i_phase));
77 for (int i_prod_dep = i_phase * NUM_PROD_;
78 i_prod_dep < (i_phase + 1) * NUM_PROD_; i_prod_dep++)
79 jacobian_register_element(jac, PROD_(i_prod_dep), WATER_(i_phase));
80 }
81 }
82
83 return;
84}
85
86/** \brief Update the time derivative and Jacbobian array indices
87 *
88 * \param model_data Pointer to the model data
89 * \param deriv_ids Id of each state variable in the derivative array
90 * \param jac Jacobian
91 * \param rxn_int_data Pointer to the reaction integer data
92 * \param rxn_float_data Pointer to the reaction floating-point data
93 */
95 int *deriv_ids, Jacobian jac,
96 int *rxn_int_data,
97 double *rxn_float_data) {
98 int *int_data = rxn_int_data;
99 double *float_data = rxn_float_data;
100
101 // Update the time derivative ids
102 for (int i_phase = 0, i_deriv = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
103 for (int i_react = 0; i_react < NUM_REACT_; i_react++)
104 DERIV_ID_(i_deriv++) = deriv_ids[REACT_(i_phase * NUM_REACT_ + i_react)];
105 for (int i_prod = 0; i_prod < NUM_PROD_; i_prod++)
106 DERIV_ID_(i_deriv++) = deriv_ids[PROD_(i_phase * NUM_PROD_ + i_prod)];
107 }
108
109 // Update the Jacobian ids
110 for (int i_phase = 0, i_jac = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
111 // Add dependence on reactants for reactants and products
112 for (int i_react_ind = i_phase * NUM_REACT_;
113 i_react_ind < (i_phase + 1) * NUM_REACT_; i_react_ind++) {
114 for (int i_react_dep = i_phase * NUM_REACT_;
115 i_react_dep < (i_phase + 1) * NUM_REACT_; i_react_dep++)
116 JAC_ID_(i_jac++) = jacobian_get_element_id(jac, REACT_(i_react_dep),
117 REACT_(i_react_ind));
118 for (int i_prod_dep = i_phase * NUM_PROD_;
119 i_prod_dep < (i_phase + 1) * NUM_PROD_; i_prod_dep++)
120 JAC_ID_(i_jac++) = jacobian_get_element_id(jac, PROD_(i_prod_dep),
121 REACT_(i_react_ind));
122 }
123
124 // Add dependence on aerosol-phase water for reactants and products
125 for (int i_react_dep = i_phase * NUM_REACT_;
126 i_react_dep < (i_phase + 1) * NUM_REACT_; i_react_dep++)
127 if (WATER_(i_phase) >= 0) {
128 JAC_ID_(i_jac++) =
129 jacobian_get_element_id(jac, REACT_(i_react_dep), WATER_(i_phase));
130 } else {
131 JAC_ID_(i_jac++) = -1;
132 }
133 for (int i_prod_dep = i_phase * NUM_PROD_;
134 i_prod_dep < (i_phase + 1) * NUM_PROD_; i_prod_dep++)
135 if (WATER_(i_phase) >= 0) {
136 JAC_ID_(i_jac++) =
137 jacobian_get_element_id(jac, PROD_(i_prod_dep), WATER_(i_phase));
138 } else {
139 JAC_ID_(i_jac++) = -1;
140 }
141 }
142
143 return;
144}
145
146/** \brief Update reaction data for new environmental conditions
147 *
148 * For Condensed Phase Arrhenius reaction this only involves recalculating the
149 * forward rate constant.
150 *
151 * \param model_data Pointer to the model data
152 * \param rxn_int_data Pointer to the reaction integer data
153 * \param rxn_float_data Pointer to the reaction floating-point data
154 * \param rxn_env_data Pointer to the environment-dependent parameters
155 */
157 int *rxn_int_data,
158 double *rxn_float_data,
159 double *rxn_env_data) {
160 int *int_data = rxn_int_data;
161 double *float_data = rxn_float_data;
162 double *env_data = model_data->grid_cell_env;
163
164 // Calculate the rate constant in (M or mol/m3)
165 // k = A*exp(C/T) * (T/D)^B * (1+E*P)
167 (B_ == 0.0 ? 1.0 : pow(TEMPERATURE_K_ / D_, B_)) *
168 (E_ == 0.0 ? 1.0 : (1.0 + E_ * PRESSURE_PA_));
169
170 return;
171}
172
173/** \brief Calculate contributions to the time derivative f(t,y) from this
174 * reaction.
175 *
176 * \param model_data Pointer to the model data, including the state array
177 * \param time_deriv TimeDerivative object
178 * \param rxn_int_data Pointer to the reaction integer data
179 * \param rxn_float_data Pointer to the reaction floating-point data
180 * \param rxn_env_data Pointer to the environment-dependent parameters
181 * \param time_step Current time step of the itegrator (s)
182 */
183#ifdef CAMP_USE_SUNDIALS
185 ModelData *model_data, TimeDerivative time_deriv, int *rxn_int_data,
186 double *rxn_float_data, double *rxn_env_data, double time_step) {
187 int *int_data = rxn_int_data;
188 double *float_data = rxn_float_data;
189 double *state = model_data->grid_cell_state;
190 double *env_data = model_data->grid_cell_env;
191
192 // Calculate derivative contributions for each aerosol phase
193 for (int i_phase = 0, i_deriv = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
194 // If this is an aqueous reaction, get the unit conversion from mol/m3 -> M
195 long double unit_conv = 1.0;
196 if (WATER_(i_phase) >= 0) {
197 unit_conv = state[WATER_(i_phase)]; // convert from kg/m3->L/m3
198
199 // For aqueous reactions, if no aerosol water is present, no reaction
200 // occurs
201 if (unit_conv <= ZERO) {
202 i_deriv += NUM_REACT_ + NUM_PROD_;
203 continue;
204 }
205 unit_conv = 1.0 / unit_conv;
206 }
207
208 // Calculate the reaction rate rate (M/s or mol/m3/s)
209 long double rate = RATE_CONSTANT_;
210 for (int i_react = 0; i_react < NUM_REACT_; i_react++) {
211 rate *= state[REACT_(i_phase * NUM_REACT_ + i_react)] *
212 KGM3_TO_MOLM3_(i_react) * unit_conv;
213 }
214
215 // Reactant change
216 for (int i_react = 0; i_react < NUM_REACT_; i_react++) {
217 if (DERIV_ID_(i_deriv) < 0) {
218 i_deriv++;
219 continue;
220 }
221 time_derivative_add_value(time_deriv, DERIV_ID_(i_deriv++),
222 -rate / (KGM3_TO_MOLM3_(i_react) * unit_conv));
223 }
224
225 // Products change
226 for (int i_prod = 0; i_prod < NUM_PROD_; i_prod++) {
227 if (DERIV_ID_(i_deriv) < 0) {
228 i_deriv++;
229 continue;
230 }
232 time_deriv, DERIV_ID_(i_deriv++),
233 rate * YIELD_(i_prod) /
234 (KGM3_TO_MOLM3_(NUM_REACT_ + i_prod) * unit_conv));
235 }
236 }
237
238 return;
239}
240#endif
241
242/** \brief Calculate contributions to the Jacobian from this reaction
243 *
244 * \param model_data Pointer to the model data
245 * \param jac Reaction Jacobian
246 * \param rxn_int_data Pointer to the reaction integer data
247 * \param rxn_float_data Pointer to the reaction floating-point data
248 * \param rxn_env_data Pointer to the environment-dependent parameters
249 * \param time_step Current time step of the itegrator (s)
250 */
251#ifdef CAMP_USE_SUNDIALS
253 ModelData *model_data, Jacobian jac, int *rxn_int_data,
254 double *rxn_float_data, double *rxn_env_data, double time_step) {
255 int *int_data = rxn_int_data;
256 double *float_data = rxn_float_data;
257 double *state = model_data->grid_cell_state;
258 double *env_data = model_data->grid_cell_env;
259
260 // Calculate Jacobian contributions for each aerosol phase
261 for (int i_phase = 0, i_jac = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
262 // If this is an aqueous reaction, get the unit conversion from mol/m3 -> M
263 realtype unit_conv = 1.0;
264 if (WATER_(i_phase) >= 0) {
265 unit_conv = state[WATER_(i_phase)]; // convert from kg/m3->L/m3
266
267 // For aqueous reactions, if no aerosol water is present, no reaction
268 // occurs
269 if (unit_conv <= ZERO) {
270 i_jac += (NUM_REACT_ + NUM_PROD_) * (NUM_REACT_ + 1);
271 continue;
272 }
273 unit_conv = 1.0 / unit_conv;
274 }
275
276 // Add dependence on reactants for reactants and products
277 for (int i_react_ind = 0; i_react_ind < NUM_REACT_; i_react_ind++) {
278 // Calculate d_rate / d_react_i
279 realtype rate = RATE_CONSTANT_;
280 for (int i_react = 0; i_react < NUM_REACT_; i_react++) {
281 if (i_react == i_react_ind) {
282 rate *= KGM3_TO_MOLM3_(i_react) * unit_conv;
283 } else {
284 rate *= state[REACT_(i_phase * NUM_REACT_ + i_react)] *
285 KGM3_TO_MOLM3_(i_react) * unit_conv;
286 }
287 }
288
289 // Add the Jacobian elements
290 //
291 // For reactant dependence on reactants
292 for (int i_react_dep = 0; i_react_dep < NUM_REACT_; i_react_dep++) {
293 if (JAC_ID_(i_jac) < 0) {
294 i_jac++;
295 continue;
296 }
297 jacobian_add_value(jac, (unsigned int)JAC_ID_(i_jac++), JACOBIAN_LOSS,
298 rate / (KGM3_TO_MOLM3_(i_react_dep) * unit_conv));
299 }
300 // For product dependence on reactants
301 for (int i_prod_dep = 0; i_prod_dep < NUM_PROD_; i_prod_dep++) {
302 if (JAC_ID_(i_jac) < 0) {
303 i_jac++;
304 continue;
305 }
307 jac, (unsigned int)JAC_ID_(i_jac++), JACOBIAN_PRODUCTION,
308 rate * YIELD_(i_prod_dep) /
309 (KGM3_TO_MOLM3_(NUM_REACT_ + i_prod_dep) * unit_conv));
310 }
311 }
312
313 // Add dependence on aerosol-phase water for reactants and products in
314 // aqueous reactions
315 if (WATER_(i_phase) < 0) {
316 i_jac += NUM_REACT_ + NUM_PROD_;
317 continue;
318 }
319
320 // Calculate the overall reaction rate (M/s or mol/m3/s)
321 realtype rate = RATE_CONSTANT_;
322 for (int i_react = 0; i_react < NUM_REACT_; i_react++) {
323 rate *= state[REACT_(i_phase * NUM_REACT_ + i_react)] *
324 KGM3_TO_MOLM3_(i_react) * unit_conv;
325 }
326
327 // Dependence of reactants on aerosol-phase water
328 for (int i_react_dep = 0; i_react_dep < NUM_REACT_; i_react_dep++) {
329 if (JAC_ID_(i_jac) < 0) {
330 i_jac++;
331 continue;
332 }
334 jac, (unsigned int)JAC_ID_(i_jac++), JACOBIAN_LOSS,
335 -(NUM_REACT_ - 1) * rate / KGM3_TO_MOLM3_(i_react_dep));
336 }
337 // Dependence of products on aerosol-phase water
338 for (int i_prod_dep = 0; i_prod_dep < NUM_PROD_; i_prod_dep++) {
339 if (JAC_ID_(i_jac) < 0) {
340 i_jac++;
341 continue;
342 }
343 jacobian_add_value(jac, (unsigned int)JAC_ID_(i_jac++),
345 -(NUM_REACT_ - 1) * rate * YIELD_(i_prod_dep) /
346 KGM3_TO_MOLM3_(NUM_REACT_ + i_prod_dep));
347 }
348 }
349
350 return;
351}
352#endif
353
354/** \brief Print the Condensed Phase Arrhenius reaction parameters
355 *
356 * \param rxn_int_data Pointer to the reaction integer data
357 * \param rxn_float_data Pointer to the reaction floating-point data
358 */
360 double *rxn_float_data) {
361 int *int_data = rxn_int_data;
362 double *float_data = rxn_float_data;
363
364 int phase_jac_size = (NUM_REACT_ + 1) * (NUM_REACT_ + NUM_PROD_);
365
366 printf("\n\nCondensed Phase Arrhenius reaction\n");
367
368 printf("\n number of reactants: %d", NUM_REACT_);
369 printf("\n number of products: %d", NUM_PROD_);
370 printf("\n number of aerosol phases: %d", NUM_AERO_PHASE_);
371 printf("\n A: %le, B: %le, C: %le, D: %le, E: %le", A_, B_, C_, D_, E_);
372 printf("\n water state ids (by phase):");
373 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase)
374 printf(" %d", WATER_(i_phase));
375 printf("\n *** Reactants ***");
376 for (int i_react = 0; i_react < NUM_REACT_; ++i_react) {
377 printf("\n reactant %d", i_react);
378 printf("\n kg/m3 -> mol/m3: %le", KGM3_TO_MOLM3_(i_react));
379 printf("\n state id (by phase):");
380 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase)
381 printf(" %d", REACT_(i_phase * NUM_REACT_ + i_react));
382 printf("\n deriv id (by phase):");
383 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase)
384 printf(" %d", DERIV_ID_(i_phase * (NUM_REACT_ + NUM_PROD_) + i_react));
385 }
386 printf("\n *** Products ***");
387 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
388 printf("\n product %d", i_prod);
389 printf("\n kg/m3 -> mol/m3: %le", KGM3_TO_MOLM3_(NUM_REACT_ + i_prod));
390 printf("\n yield: %le", YIELD_(i_prod));
391 printf("\n state id (by phase):");
392 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase)
393 printf(" %d", PROD_(i_phase * NUM_PROD_ + i_prod));
394 printf("\n deriv id (by phase):");
395 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase)
396 printf(" %d", DERIV_ID_(i_phase * (NUM_REACT_ + NUM_PROD_) + NUM_REACT_ +
397 i_prod));
398 }
399 printf("\n *** Jac Ids (by phase) ***");
400 for (int i_ind = 0; i_ind < NUM_REACT_; ++i_ind) {
401 for (int i_react = 0; i_react < NUM_REACT_; ++i_react) {
402 printf("\n R->R");
403 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase) {
404 printf(" Jac[%d][%d] = %d;", REACT_(i_phase * NUM_REACT_ + i_react),
405 REACT_(i_phase * NUM_REACT_ + i_ind),
406 JAC_ID_(i_phase * phase_jac_size +
407 i_ind * (NUM_REACT_ + NUM_PROD_) + i_react));
408 }
409 }
410 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
411 printf("\n P->R");
412 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase) {
413 printf(" Jac[%d][%d] = %d;", PROD_(i_phase * NUM_PROD_ + i_prod),
414 REACT_(i_phase * NUM_REACT_ + i_ind),
415 JAC_ID_(i_phase * phase_jac_size +
416 i_ind * (NUM_REACT_ + NUM_PROD_) + NUM_REACT_ + i_prod));
417 }
418 }
419 }
420 for (int i_react = 0; i_react < NUM_REACT_; ++i_react) {
421 printf("\n R->W");
422 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase) {
423 printf(" Jac[%d][%d] = %d;", REACT_(i_phase * NUM_REACT_ + i_react),
424 WATER_(i_phase),
425 JAC_ID_(i_phase * phase_jac_size +
426 NUM_REACT_ * (NUM_REACT_ + NUM_PROD_) + i_react));
427 }
428 }
429 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
430 printf("\n P->W");
431 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase) {
432 printf(
433 " Jac[%d][%d] = %d;", PROD_(i_phase * NUM_PROD_ + i_prod),
434 WATER_(i_phase),
435 JAC_ID_(i_phase * phase_jac_size +
436 NUM_REACT_ * (NUM_REACT_ + NUM_PROD_) + NUM_REACT_ + i_prod));
437 }
438 }
439 return;
440}
unsigned int jacobian_get_element_id(Jacobian jac, unsigned int dep_id, unsigned int ind_id)
Get an element id in the Jacobian data arrays.
Definition Jacobian.c:200
void jacobian_add_value(Jacobian jac, unsigned int elem_id, unsigned int prod_or_loss, long double jac_contribution)
Add a contribution to the Jacobian.
Definition Jacobian.c:234
void jacobian_register_element(Jacobian *jac, unsigned int dep_id, unsigned int ind_id)
Adds an element to the sparse matrix.
Definition Jacobian.c:105
#define JACOBIAN_LOSS
Definition Jacobian.h:19
#define JACOBIAN_PRODUCTION
Definition Jacobian.h:18
#define ZERO
Definition camp_common.h:42
#define WATER_(x)
#define KGM3_TO_MOLM3_(x)
#define PRESSURE_PA_
void rxn_condensed_phase_arrhenius_calc_deriv_contrib(ModelData *model_data, TimeDerivative time_deriv, int *rxn_int_data, double *rxn_float_data, double *rxn_env_data, double time_step)
Calculate contributions to the time derivative f(t,y) from this reaction.
void rxn_condensed_phase_arrhenius_get_used_jac_elem(int *rxn_int_data, double *rxn_float_data, Jacobian *jac)
Flag Jacobian elements used by this reaction.
void rxn_condensed_phase_arrhenius_calc_jac_contrib(ModelData *model_data, Jacobian jac, int *rxn_int_data, double *rxn_float_data, double *rxn_env_data, double time_step)
Calculate contributions to the Jacobian from this reaction.
#define RATE_CONSTANT_
void rxn_condensed_phase_arrhenius_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.
#define YIELD_(x)
#define PROD_(x)
#define TEMPERATURE_K_
void rxn_condensed_phase_arrhenius_print(int *rxn_int_data, double *rxn_float_data)
Print the Condensed Phase Arrhenius reaction parameters.
#define JAC_ID_(x)
void rxn_condensed_phase_arrhenius_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 NUM_AERO_PHASE_
#define REACT_(x)
#define DERIV_ID_(x)
double * grid_cell_env
double * grid_cell_state
void time_derivative_add_value(TimeDerivative time_deriv, unsigned int spec_id, long double rate_contribution)
Add a contribution to the time derivative.