CAMP 1.0.0
Chemistry Across Multiple Phases
rxn_wennberg_no_ro2.c
Go to the documentation of this file.
1/* Copyright (C) 2021 Barcelona Supercomputing Center,
2 * University of Illinois at Urbana-Champaign, and
3 * National Center for Atmospheric Research
4 * SPDX-License-Identifier: MIT
5 *
6 * Wennberg NO + RO2 reaction solver functions
7 *
8 */
9/** \file
10 * \brief Wennberg NO + RO2 reaction solver functions
11 */
12#include <math.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include "../rxns.h"
16
17// TODO Lookup environmental indices during initialization
18#define TEMPERATURE_K_ env_data[0]
19#define PRESSURE_PA_ env_data[1]
20
21#define NUM_REACT_ int_data[0]
22#define NUM_ALKOXY_PROD_ int_data[1]
23#define NUM_NITRATE_PROD_ int_data[2]
24#define X_ float_data[0]
25#define Y_ float_data[1]
26#define a0_ float_data[2]
27#define n_ float_data[3]
28#define CONV_ float_data[4]
29#define ALKOXY_RATE_CONSTANT_ rxn_env_data[0]
30#define NITRATE_RATE_CONSTANT_ rxn_env_data[1]
31#define NUM_INT_PROP_ 3
32#define NUM_FLOAT_PROP_ 5
33#define NUM_ENV_PARAM_ 2
34#define REACT_(x) (int_data[NUM_INT_PROP_ + x] - 1)
35#define PROD_(x) (int_data[NUM_INT_PROP_ + NUM_REACT_ + x] - 1)
36#define DERIV_ID_(x) \
37 int_data[NUM_INT_PROP_ + NUM_REACT_ + NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_ + \
38 x]
39#define JAC_ID_(x) \
40 int_data[NUM_INT_PROP_ + \
41 2 * (NUM_REACT_ + NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_) + x]
42#define YIELD_(x) float_data[NUM_FLOAT_PROP_ + x]
43
44/** \brief Flag Jacobian elements used by this reaction
45 *
46 * \param rxn_int_data Pointer to the reaction integer data
47 * \param rxn_float_data Pointer to the reaction floating-point data
48 * \param jac Jacobian
49 */
51 double *rxn_float_data,
52 Jacobian *jac) {
53 int *int_data = rxn_int_data;
54 double *float_data = rxn_float_data;
55
56 for (int i_ind = 0; i_ind < NUM_REACT_; i_ind++) {
57 for (int i_dep = 0; i_dep < NUM_REACT_; i_dep++) {
58 jacobian_register_element(jac, REACT_(i_dep), REACT_(i_ind));
59 }
60 for (int i_dep = 0; i_dep < NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_; i_dep++) {
61 jacobian_register_element(jac, PROD_(i_dep), REACT_(i_ind));
62 }
63 }
64
65 return;
66}
67
68/** \brief Update the time derivative and Jacbobian array indices
69 *
70 * \param model_data Pointer to the model data
71 * \param deriv_ids Id of each state variable in the derivative array
72 * \param jac Jacobian
73 * \param rxn_int_data Pointer to the reaction integer data
74 * \param rxn_float_data Pointer to the reaction floating-point data
75 */
76void rxn_wennberg_no_ro2_update_ids(ModelData *model_data, int *deriv_ids,
77 Jacobian jac, int *rxn_int_data,
78 double *rxn_float_data) {
79 int *int_data = rxn_int_data;
80 double *float_data = rxn_float_data;
81
82 // Update the time derivative ids
83 for (int i = 0; i < NUM_REACT_; i++) DERIV_ID_(i) = deriv_ids[REACT_(i)];
84 for (int i = 0; i < NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_; i++)
85 DERIV_ID_(i + NUM_REACT_) = deriv_ids[PROD_(i)];
86
87 // Update the Jacobian ids
88 int i_jac = 0;
89 for (int i_ind = 0; i_ind < NUM_REACT_; i_ind++) {
90 for (int i_dep = 0; i_dep < NUM_REACT_; i_dep++)
91 JAC_ID_(i_jac++) =
92 jacobian_get_element_id(jac, REACT_(i_dep), REACT_(i_ind));
93 for (int i_dep = 0; i_dep < NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_; i_dep++)
94 JAC_ID_(i_jac++) =
95 jacobian_get_element_id(jac, PROD_(i_dep), REACT_(i_ind));
96 }
97 return;
98}
99
100/** \brief Calculates the Troe-like parameter A(T, [M], n)
101 *
102 * k0 = 2e-22 e^n
103 * kinf = 0.43 * (T/298)^-8
104 * A = k0 [M] / ( 1 + k0 [M] / kinf ) *
105 * 0.41^( 1 / ( 1 + [ log10( k0 [M] / kinf ) ]^2 ) )
106 *
107 * \param T temperature [K]
108 * \param M air density [#/cc]
109 * \param n number of heavy atoms in RO2 species
110 */
111double calculate_A(double T, double M, double n) {
112 double k0, kinf;
113 k0 = 2.0e-22 * exp(n);
114 kinf = 0.43 * pow(T / 298.0, -8);
115 return k0 * M / (1.0 + k0 * M / kinf) *
116 pow(0.41, 1.0 / (1.0 + pow(log10(k0 * M / kinf), 2)));
117}
118
119/** \brief Update reaction data for new environmental conditions
120 *
121 * For Wennberg NO + RO2 reaction this only involves recalculating the rate
122 * constant.
123 *
124 * \param model_data Pointer to the model data
125 * \param rxn_int_data Pointer to the reaction integer data
126 * \param rxn_float_data Pointer to the reaction floating-point data
127 * \param rxn_env_data Pointer to the environment-dependent parameters
128 */
130 int *rxn_int_data,
131 double *rxn_float_data,
132 double *rxn_env_data) {
133 int *int_data = rxn_int_data;
134 double *float_data = rxn_float_data;
135 double *env_data = model_data->grid_cell_env;
136
137 double base_rate, A, Z, M;
138
139 // Calculate the rate constant in (#/cc)
140 base_rate = X_ * exp(-Y_ / TEMPERATURE_K_) *
142 M = CONV_ * PRESSURE_PA_ / TEMPERATURE_K_ * 1e6; // [#/cm3]
143 Z = calculate_A(293.0, 2.45e19, n_) * (1.0 - a0_) / a0_;
145 ALKOXY_RATE_CONSTANT_ = base_rate * Z / (Z + A);
146 NITRATE_RATE_CONSTANT_ = base_rate * A / (A + Z);
147 return;
148}
149
150/** \brief Calculate contributions to the time derivative \f$f(t,y)\f$ from
151 * this reaction.
152 *
153 * \param model_data Model data
154 * \param time_deriv TimeDerivative object
155 * \param rxn_int_data Pointer to the reaction integer data
156 * \param rxn_float_data Pointer to the reaction floating-point data
157 * \param rxn_env_data Pointer to the environment-dependent parameters
158 * \param time_step Current time step being computed (s)
159 */
160#ifdef CAMP_USE_SUNDIALS
162 ModelData *model_data, TimeDerivative time_deriv, int *rxn_int_data,
163 double *rxn_float_data, double *rxn_env_data, double time_step) {
164 int *int_data = rxn_int_data;
165 double *float_data = rxn_float_data;
166 double *state = model_data->grid_cell_state;
167 double *env_data = model_data->grid_cell_env;
168
169 // Calculate the reaction rate
170 long double rate = 1.0;
171 long double k_a = ALKOXY_RATE_CONSTANT_;
172 long double k_n = NITRATE_RATE_CONSTANT_;
173 for (int i_spec = 0; i_spec < NUM_REACT_; i_spec++)
174 rate *= state[REACT_(i_spec)];
175
176 // Add contributions to the time derivative
177 if (rate != ZERO) {
178 int i_dep_var = 0;
179 for (int i_spec = 0; i_spec < NUM_REACT_; i_spec++, i_dep_var++) {
180 if (DERIV_ID_(i_dep_var) < 0) continue;
181 time_derivative_add_value(time_deriv, DERIV_ID_(i_dep_var),
182 -(k_a + k_n) * rate);
183 }
184 for (int i_spec = 0; i_spec < NUM_ALKOXY_PROD_; i_spec++, i_dep_var++) {
185 if (DERIV_ID_(i_dep_var) < 0) continue;
186
187 // Negative yields are allowed, but prevented from causing negative
188 // concentrations that lead to solver failures
189 if (-k_a * rate * YIELD_(i_spec) * time_step <= state[PROD_(i_spec)]) {
190 time_derivative_add_value(time_deriv, DERIV_ID_(i_dep_var),
191 k_a * rate * YIELD_(i_spec));
192 }
193 }
194 for (int i_spec = NUM_ALKOXY_PROD_;
195 i_spec < NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_; i_spec++, i_dep_var++) {
196 if (DERIV_ID_(i_dep_var) < 0) continue;
197
198 // Negative yields are allowed, but prevented from causing negative
199 // concentrations that lead to solver failures
200 if (-k_n * rate * YIELD_(i_spec) * time_step <= state[PROD_(i_spec)]) {
201 time_derivative_add_value(time_deriv, DERIV_ID_(i_dep_var),
202 k_n * rate * YIELD_(i_spec));
203 }
204 }
205 }
206
207 return;
208}
209#endif
210
211/** \brief Calculate contributions to the Jacobian from this reaction
212 *
213 * \param model_data Model data
214 * \param jac Reaction Jacobian
215 * \param rxn_int_data Pointer to the reaction integer data
216 * \param rxn_float_data Pointer to the reaction floating-point data
217 * \param rxn_env_data Pointer to the environment-dependent parameters
218 * \param time_step Current time step being calculated (s)
219 */
220#ifdef CAMP_USE_SUNDIALS
222 int *rxn_int_data,
223 double *rxn_float_data,
224 double *rxn_env_data,
225 double time_step) {
226 int *int_data = rxn_int_data;
227 double *float_data = rxn_float_data;
228 double *state = model_data->grid_cell_state;
229 double *env_data = model_data->grid_cell_env;
230
231 // Add contributions to the Jacobian
232 int i_elem = 0;
233 for (int i_ind = 0; i_ind < NUM_REACT_; i_ind++) {
234 // Calculate d_rate / d_i_ind
235 realtype rate = 1.0;
236 realtype k_a = ALKOXY_RATE_CONSTANT_;
237 realtype k_n = NITRATE_RATE_CONSTANT_;
238 for (int i_spec = 0; i_spec < NUM_REACT_; i_spec++)
239 if (i_spec != i_ind) rate *= state[REACT_(i_spec)];
240
241 for (int i_dep = 0; i_dep < NUM_REACT_; i_dep++, i_elem++) {
242 if (JAC_ID_(i_elem) < 0) continue;
243 jacobian_add_value(jac, (unsigned int)JAC_ID_(i_elem), JACOBIAN_LOSS,
244 (k_a + k_n) * rate);
245 }
246 for (int i_dep = 0; i_dep < NUM_ALKOXY_PROD_; i_dep++, i_elem++) {
247 if (JAC_ID_(i_elem) < 0) continue;
248 // Negative yields are allowed, but prevented from causing negative
249 // concentrations that lead to solver failures
250 if (-k_a * rate * state[REACT_(i_ind)] * YIELD_(i_dep) * time_step <=
251 state[PROD_(i_dep)]) {
252 jacobian_add_value(jac, (unsigned int)JAC_ID_(i_elem),
253 JACOBIAN_PRODUCTION, k_a * YIELD_(i_dep) * rate);
254 }
255 }
256 for (int i_dep = NUM_ALKOXY_PROD_;
257 i_dep < NUM_ALKOXY_PROD_ + NUM_NITRATE_PROD_; i_dep++, i_elem++) {
258 if (JAC_ID_(i_elem) < 0) continue;
259 // Negative yields are allowed, but prevented from causing negative
260 // concentrations that lead to solver failures
261 if (-k_n * rate * state[REACT_(i_ind)] * YIELD_(i_dep) * time_step <=
262 state[PROD_(i_dep)]) {
263 jacobian_add_value(jac, (unsigned int)JAC_ID_(i_elem),
264 JACOBIAN_PRODUCTION, k_n * YIELD_(i_dep) * rate);
265 }
266 }
267 }
268
269 return;
270}
271#endif
272
273/** \brief Print the Wennberg NO + RO2 reaction parameters
274 *
275 * \param rxn_int_data Pointer to the reaction integer data
276 * \param rxn_float_data Pointer to the reaction floating-point data
277 */
278void rxn_wennberg_no_ro2_print(int *rxn_int_data, double *rxn_float_data) {
279 int *int_data = rxn_int_data;
280 double *float_data = rxn_float_data;
281
282 printf("\n\nWennberg NO + RO2 reaction\n");
283
284 return;
285}
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
double calculate_A(double T, double M, double n)
Calculates the Troe-like parameter A(T, [M], n)
#define NUM_REACT_
#define a0_
#define NUM_ALKOXY_PROD_
#define PRESSURE_PA_
void rxn_wennberg_no_ro2_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 ALKOXY_RATE_CONSTANT_
#define X_
void rxn_wennberg_no_ro2_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 CONV_
#define YIELD_(x)
#define PROD_(x)
void rxn_wennberg_no_ro2_get_used_jac_elem(int *rxn_int_data, double *rxn_float_data, Jacobian *jac)
Flag Jacobian elements used by this reaction.
#define NITRATE_RATE_CONSTANT_
#define TEMPERATURE_K_
#define JAC_ID_(x)
#define NUM_NITRATE_PROD_
void rxn_wennberg_no_ro2_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 Y_
#define REACT_(x)
#define n_
void rxn_wennberg_no_ro2_print(int *rxn_int_data, double *rxn_float_data)
Print the Wennberg NO + RO2 reaction parameters.
void rxn_wennberg_no_ro2_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 from this reaction.
#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.