CAMP 1.0.0
Chemistry Across Multiple Phases
rxn_surface.c
Go to the documentation of this file.
1/* Copyright (C) 2023 Barcelona Supercomputing Center, University of
2 * Illinois at Urbana-Champaign, and National Center for Atmospheric Research
3 * SPDX-License-Identifier: MIT
4 *
5 * Surface reaction solver functions
6 *
7 */
8/** \file
9 * \brief Surface reaction solver functions
10 */
11#include <math.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include "../aero_rep_solver.h"
15#include "../rxns.h"
16#include "../util.h"
17
18// TODO Lookup environmental indices during initialization
19#define TEMPERATURE_K_ env_data[0]
20#define PRESSURE_PA_ env_data[1]
21
22#define DIFF_COEFF_ float_data[0]
23#define GAMMA_ float_data[1]
24#define MW_ float_data[2]
25#define NUM_AERO_PHASE_ int_data[0]
26#define REACT_ID_ (int_data[1] - 1)
27#define NUM_PROD_ int_data[2]
28#define MEAN_SPEED_MS_ rxn_env_data[0]
29#define NUM_INT_PROP_ 3
30#define NUM_FLOAT_PROP_ 3
31#define NUM_ENV_PARAM_ 1
32#define PROD_ID_(x) (int_data[NUM_INT_PROP_ + x] - 1)
33#define DERIV_ID_(x) int_data[NUM_INT_PROP_ + NUM_PROD_ + x]
34#define JAC_ID_(x) int_data[NUM_INT_PROP_ + 1 + 2 * NUM_PROD_ + x]
35#define PHASE_INT_LOC_(x) \
36 (int_data[NUM_INT_PROP_ + 2 + 3 * NUM_PROD_ + x] - 1)
37#define PHASE_FLOAT_LOC_(x) \
38 (int_data[NUM_INT_PROP_ + 2 + 3 * NUM_PROD_ + NUM_AERO_PHASE_ + x] - 1)
39#define AERO_PHASE_ID_(x) (int_data[PHASE_INT_LOC_(x)] - 1)
40#define AERO_REP_ID_(x) (int_data[PHASE_INT_LOC_(x) + 1] - 1)
41#define NUM_AERO_PHASE_JAC_ELEM_(x) (int_data[PHASE_INT_LOC_(x) + 2])
42#define PHASE_JAC_ID_(x, s, e) \
43 int_data[PHASE_INT_LOC_(x) + 3 + (s) * NUM_AERO_PHASE_JAC_ELEM_(x) + e]
44#define YIELD_(x) float_data[NUM_FLOAT_PROP_ + x]
45#define EFF_RAD_JAC_ELEM_(x, e) float_data[PHASE_FLOAT_LOC_(x) + e]
46#define NUM_CONC_JAC_ELEM_(x, e) \
47 float_data[PHASE_FLOAT_LOC_(x) + NUM_AERO_PHASE_JAC_ELEM_(x) + e]
48
49/** \brief Flag Jacobian elements used by this reaction
50 *
51 * \param model_data Pointer to the model data
52 * \param rxn_int_data Pointer to the reaction integer data
53 * \param rxn_float_data Pointer to the reaction floating-point data
54 * \param jac Jacobian
55 */
57 int *rxn_int_data,
58 double *rxn_float_data,
59 Jacobian *jac) {
60 int *int_data = rxn_int_data;
61 double *float_data = rxn_float_data;
62
63 bool *aero_jac_elem =
64 (bool *)malloc(sizeof(bool) * model_data->n_per_cell_state_var);
65 if (aero_jac_elem == NULL) {
66 printf(
67 "\n\nERROR allocating space for 1D jacobian structure array for "
68 "surface reaction\n\n");
69 exit(1);
70 }
71
73 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
75 }
76
77 for (int i_aero_phase = 0; i_aero_phase < NUM_AERO_PHASE_; ++i_aero_phase) {
78 for (int i_elem = 0; i_elem < model_data->n_per_cell_state_var; ++i_elem) {
79 aero_jac_elem[i_elem] = false;
80 }
81 int n_jac_elem =
82 aero_rep_get_used_jac_elem(model_data, AERO_REP_ID_(i_aero_phase),
83 AERO_PHASE_ID_(i_aero_phase), aero_jac_elem);
84 if (n_jac_elem > NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase)) {
85 printf(
86 "\n\nERROR Received more Jacobian elements than expected for surface "
87 "reaction. Got %d, expected <= %d",
88 n_jac_elem, NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase));
89 exit(1);
90 }
91 int i_used_elem = 0;
92 for (int i_elem = 0; i_elem < model_data->n_per_cell_state_var; ++i_elem) {
93 if (aero_jac_elem[i_elem] == true) {
95 PHASE_JAC_ID_(i_aero_phase, 0, i_used_elem) = i_elem;
96 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
97 jacobian_register_element(jac, PROD_ID_(i_prod), i_elem);
98 PHASE_JAC_ID_(i_aero_phase, i_prod + 1, i_used_elem) = i_elem;
99 }
100 ++i_used_elem;
101 }
102 }
103 for (; i_used_elem < NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase);
104 ++i_used_elem) {
105 for (int i_spec = 0; i_spec < NUM_PROD_ + 1; ++i_spec) {
106 PHASE_JAC_ID_(i_aero_phase, i_spec, i_used_elem) = -1;
107 }
108 }
109 if (i_used_elem != n_jac_elem) {
110 printf(
111 "\n\nERROR Error setting used Jacobian elements in surface "
112 "reaction %d %d\n\n",
113 i_used_elem, n_jac_elem);
114 rxn_surface_print(rxn_int_data, rxn_float_data);
115 exit(1);
116 }
117 }
118
119 free(aero_jac_elem);
120
121 return;
122}
123
124/** \brief Update the time derivative and Jacbobian array indices
125 *
126 * \param model_data Pointer to the model data
127 * \param deriv_ids Id of each state variable in the derivative array
128 * \param jac Jacobian
129 * \param rxn_int_data Pointer to the reaction integer data
130 * \param rxn_float_data Pointer to the reaction floating-point data
131 */
132void rxn_surface_update_ids(ModelData *model_data, int *deriv_ids,
133 Jacobian jac, int *rxn_int_data,
134 double *rxn_float_data) {
135 int *int_data = rxn_int_data;
136 double *float_data = rxn_float_data;
137
138 // Update the time derivative ids
139 DERIV_ID_(0) = deriv_ids[REACT_ID_];
140 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
141 DERIV_ID_(i_prod + 1) = deriv_ids[PROD_ID_(i_prod)];
142 }
143
144 // Update the Jacobian element ids
145 int i_jac = 0;
147 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
148 JAC_ID_(i_jac++) = jacobian_get_element_id(jac, PROD_ID_(i_prod), REACT_ID_);
149 }
150 for (int i_aero_phase = 0; i_aero_phase < NUM_AERO_PHASE_; ++i_aero_phase) {
151 for (int i_elem = 0; i_elem < NUM_AERO_PHASE_JAC_ELEM_(i_aero_phase); ++i_elem) {
152 if (PHASE_JAC_ID_(i_aero_phase, 0, i_elem) > 0) {
153 PHASE_JAC_ID_(i_aero_phase, 0, i_elem) = jacobian_get_element_id(
154 jac, REACT_ID_, PHASE_JAC_ID_(i_aero_phase, 0, i_elem));
155 }
156 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
157 if (PHASE_JAC_ID_(i_aero_phase, i_prod + 1, i_elem) > 0) {
158 PHASE_JAC_ID_(i_aero_phase, i_prod + 1, i_elem) =
160 PHASE_JAC_ID_(i_aero_phase, i_prod + 1, i_elem));
161 }
162 }
163 }
164 }
165 return;
166}
167
168/** \brief Update reaction data for new environmental conditions
169 *
170 * For surface reactions this only involves calculating the mean
171 * speed of the reacting species
172 *
173 * \param model_data Pointer to the model data
174 * \param rxn_int_data Pointer to the reaction integer data
175 * \param rxn_float_data Pointer to the reaction floating-point data
176 * \param rxn_env_data Pointer to the environment-dependent parameters
177 */
179 int *rxn_int_data,
180 double *rxn_float_data,
181 double *rxn_env_data) {
182 int *int_data = rxn_int_data;
183 double *float_data = rxn_float_data;
184 double *env_data = model_data->grid_cell_env;
185
186 // save the mean speed [m s-1] for calculating condensation rates
188
189 return;
190}
191
192/** \brief Calculate contributions to the time derivative \f$f(t,y)\f$ from
193 * this reaction.
194 *
195 * \param model_data Pointer to the model data, including the state array
196 * \param time_deriv TimeDerivative object
197 * \param rxn_int_data Pointer to the reaction integer data
198 * \param rxn_float_data Pointer to the reaction floating-point data
199 * \param rxn_env_data Pointer to the environment-dependent parameters
200 * \param time_step Current time step being computed (s)
201 */
202#ifdef CAMP_USE_SUNDIALS
204 ModelData *model_data, TimeDerivative time_deriv, int *rxn_int_data,
205 double *rxn_float_data, double *rxn_env_data, realtype time_step) {
206 int *int_data = rxn_int_data;
207 double *float_data = rxn_float_data;
208 double *state = model_data->grid_cell_state;
209 double *env_data = model_data->grid_cell_env;
210
211 // Calculate derivative contributions for each aerosol phase
212 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
213 // Get the particle effective radius (m)
214 realtype radius;
216 model_data, // model data
217 AERO_REP_ID_(i_phase), // aerosol representation index
218 AERO_PHASE_ID_(i_phase), // aerosol phase index
219 &radius, // particle effective radius (m)
220 NULL); // partial derivative
221
222 // Get the particle number concentration (#/m3)
223 realtype number_conc;
225 model_data, // model data
226 AERO_REP_ID_(i_phase), // aerosol representation index
227 AERO_PHASE_ID_(i_phase), // aerosol phase index
228 &number_conc, // particle number concentration
229 // (#/m3)
230 NULL); // partial derivative
231
232 // Calculate the rate constant for diffusion limited mass transfer to the
233 // aerosol phase (1/s)
234 double cond_rate = state[REACT_ID_] * number_conc *
236 radius, GAMMA_);
237
238 // Loss of the reactant
239 if (DERIV_ID_(0) >= 0) {
240 time_derivative_add_value(time_deriv, DERIV_ID_(0), -cond_rate);
241 }
242 // Gain of each product
243 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
244 if (DERIV_ID_(i_prod + 1) >= 0) {
245 time_derivative_add_value(time_deriv, DERIV_ID_(i_prod + 1),
246 YIELD_(i_prod) * cond_rate);
247 }
248 }
249 }
250 return;
251}
252#endif
253
254/** \brief Calculate contributions to the Jacobian from this reaction
255 *
256 * \param model_data Pointer to the model data
257 * \param jac Reaction Jacobian
258 * \param rxn_int_data Pointer to the reaction integer data
259 * \param rxn_float_data Pointer to the reaction floating-point data
260 * \param rxn_env_data Pointer to the environment-dependent parameters
261 * \param time_step Current time step being calculated (s)
262 */
263#ifdef CAMP_USE_SUNDIALS
265 int *rxn_int_data,
266 double *rxn_float_data,
267 double *rxn_env_data,
268 realtype time_step) {
269 int *int_data = rxn_int_data;
270 double *float_data = rxn_float_data;
271 double *state = model_data->grid_cell_state;
272 double *env_data = model_data->grid_cell_env;
273
274 // Calculate derivative contributions for each aerosol phase
275 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; i_phase++) {
276 // Get the particle effective radius (m)
277 realtype radius;
279 model_data, // model data
280 AERO_REP_ID_(i_phase), // aerosol representation index
281 AERO_PHASE_ID_(i_phase), // aerosol phase index
282 &radius, // particle effective radius (m)
283 &(EFF_RAD_JAC_ELEM_(i_phase, 0))); // partial derivative
284
285 // Get the particle number concentration (#/m3)
286 realtype number_conc;
288 model_data, // model data
289 AERO_REP_ID_(i_phase), // aerosol representation index
290 AERO_PHASE_ID_(i_phase), // aerosol phase index
291 &number_conc, // particle number concentration
292 // (#/m3)
293 &(NUM_CONC_JAC_ELEM_(i_phase, 0))); // partial derivative
294
295 // Calculate the rate constant for diffusion limited mass transfer to the
296 // aerosol phase (1/s)
297 double rate_const =
299 radius, GAMMA_);
300 double cond_rate = state[REACT_ID_] * number_conc * rate_const;
301
302 // Dependence on the reactant
303 if (JAC_ID_(0) >=0) {
304 jacobian_add_value(jac, (unsigned int)JAC_ID_(0), JACOBIAN_LOSS,
305 number_conc * rate_const);
306 }
307 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
308 if (JAC_ID_(i_prod + 1) >= 0) {
309 jacobian_add_value(jac, (unsigned int)JAC_ID_(i_prod + 1), JACOBIAN_PRODUCTION,
310 YIELD_(i_prod) * number_conc * rate_const);
311 }
312 }
313
314 // Calculate d_rate/d_effective_radius and d_rate/d_number_concentration
315 double d_rate_d_radius = state[REACT_ID_] * number_conc *
317 radius, GAMMA_);
318 double d_rate_d_number = state[REACT_ID_] * rate_const;
319
320 // Loop through aerosol dependencies
321 for (int i_elem = 0; i_elem < NUM_AERO_PHASE_JAC_ELEM_(i_phase); ++i_elem) {
322 // Reactant dependencies
323 if (PHASE_JAC_ID_(i_phase, 0, i_elem) > 0) {
324 // Dependence on effective radius
326 jac, (unsigned int)PHASE_JAC_ID_(i_phase, 0, i_elem),
328 d_rate_d_radius * EFF_RAD_JAC_ELEM_(i_phase, i_elem));
329 // Dependence on number concentration
331 jac, (unsigned int)PHASE_JAC_ID_(i_phase, 0, i_elem),
333 d_rate_d_number * NUM_CONC_JAC_ELEM_(i_phase, i_elem));
334 }
335 // Product dependencies
336 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
337 if (PHASE_JAC_ID_(i_phase, i_prod + 1, i_elem) > 0) {
338 // Dependence on effective radius
340 jac, (unsigned int)PHASE_JAC_ID_(i_phase, i_prod + 1, i_elem),
342 YIELD_(i_prod) * d_rate_d_radius *
343 EFF_RAD_JAC_ELEM_(i_phase, i_elem));
344 // Dependence on number concentration
346 jac, (unsigned int)PHASE_JAC_ID_(i_phase, i_prod + 1, i_elem),
348 YIELD_(i_prod) * d_rate_d_number *
349 NUM_CONC_JAC_ELEM_(i_phase, i_elem));
350 }
351 }
352 }
353 }
354 return;
355}
356#endif
357
358/** \brief Print the surface reaction parameters
359 *
360 * \param rxn_int_data Pointer to the reaction integer data
361 * \param rxn_float_data Pointer to the reaction floating-point data
362 */
363void rxn_surface_print(int *rxn_int_data, double *rxn_float_data) {
364 int *int_data = rxn_int_data;
365 double *float_data = rxn_float_data;
366
367 printf("\n\nSurface reaction\n");
368 printf("\ndiffusion coefficient: %lg gamma: %lg, MW: %lg", DIFF_COEFF_,
369 GAMMA_, MW_);
370 printf("\nnumber of aerosol phases: %d", NUM_AERO_PHASE_);
371 printf("\nreactant state id: %d", REACT_ID_);
372 printf("\nnumber of products: %d", NUM_PROD_);
373 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
374 printf("\n product %d id: %d", i_prod, PROD_ID_(i_prod));
375 }
376 printf("\nreactant derivative id: %d", DERIV_ID_(0));
377 printf("\nd_reactant/d_reactant Jacobian id %d", JAC_ID_(0));
378 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
379 printf("\n product %d derivative id: %d", i_prod, DERIV_ID_(i_prod+1));
380 printf("\n d_product_%d/d_reactant Jacobian id: %d", i_prod,
381 JAC_ID_(i_prod+1));
382 }
383 for (int i_phase = 0; i_phase < NUM_AERO_PHASE_; ++i_phase) {
384 printf("\nPhase %d start indices int: %d float: %d", i_phase,
385 PHASE_INT_LOC_(i_phase), PHASE_FLOAT_LOC_(i_phase));
386 printf("\n phase id %d; aerosol representation id %d",
387 AERO_PHASE_ID_(i_phase), AERO_REP_ID_(i_phase));
388 printf("\n number of Jacobian elements: %d",
389 NUM_AERO_PHASE_JAC_ELEM_(i_phase));
390 for (int i_elem = 0; i_elem < NUM_AERO_PHASE_JAC_ELEM_(i_phase); ++i_elem) {
391 printf("\n - d_reactant/d_phase_species_%d Jacobian id %d",
392 i_elem, PHASE_JAC_ID_(i_phase,0,i_elem));
393 for (int i_prod = 0; i_prod < NUM_PROD_; ++i_prod) {
394 printf("\n - d_product_%d/d_phase_species_%d Jacobian id %d",
395 i_prod, i_elem, PHASE_JAC_ID_(i_phase,i_prod+1,i_elem));
396 }
397 printf("\n - d_radius/d_phase_species_%d = %le",
398 i_elem, EFF_RAD_JAC_ELEM_(i_phase,i_elem));
399 printf("\n - d_number/d_phase_species_%d = %le",
400 i_elem, EFF_RAD_JAC_ELEM_(i_phase,i_elem));
401 }
402 }
403 printf("\n *** end surface reaction ***\n\n");
404 return;
405}
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
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_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 PHASE_INT_LOC_(x)
Definition rxn_surface.c:35
#define MW_
Definition rxn_surface.c:24
void rxn_surface_get_used_jac_elem(ModelData *model_data, int *rxn_int_data, double *rxn_float_data, Jacobian *jac)
Flag Jacobian elements used by this reaction.
Definition rxn_surface.c:56
void rxn_surface_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 MEAN_SPEED_MS_
Definition rxn_surface.c:28
#define AERO_PHASE_ID_(x)
Definition rxn_surface.c:39
void rxn_surface_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.
void rxn_surface_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 GAMMA_
Definition rxn_surface.c:23
void rxn_surface_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 YIELD_(x)
Definition rxn_surface.c:44
#define TEMPERATURE_K_
Definition rxn_surface.c:19
#define JAC_ID_(x)
Definition rxn_surface.c:34
#define PHASE_JAC_ID_(x, s, e)
Definition rxn_surface.c:42
#define NUM_AERO_PHASE_JAC_ELEM_(x)
Definition rxn_surface.c:41
#define NUM_PROD_
Definition rxn_surface.c:27
#define DIFF_COEFF_
Definition rxn_surface.c:22
#define REACT_ID_
Definition rxn_surface.c:26
#define PROD_ID_(x)
Definition rxn_surface.c:32
#define NUM_AERO_PHASE_
Definition rxn_surface.c:25
#define AERO_REP_ID_(x)
Definition rxn_surface.c:40
#define EFF_RAD_JAC_ELEM_(x, e)
Definition rxn_surface.c:45
#define NUM_CONC_JAC_ELEM_(x, e)
Definition rxn_surface.c:46
void rxn_surface_print(int *rxn_int_data, double *rxn_float_data)
Print the surface reaction parameters.
#define DERIV_ID_(x)
Definition rxn_surface.c:33
#define PHASE_FLOAT_LOC_(x)
Definition rxn_surface.c:37
int n_per_cell_state_var
Definition camp_common.h:63
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.
static double mean_speed__m_s(double temperature__K, double mw__kg_mol)
Definition util.h:30
static double d_gas_aerosol_continuum_rxn_rate_constant_d_radius(double diffusion_coeff__m2_s, double mean_speed__m_s, double radius__m, double alpha)
Definition util.h:209
static double gas_aerosol_continuum_rxn_rate_constant(double diffusion_coeff__m2_s, double mean_speed__m_s, double radius__m, double alpha)
Definition util.h:183