CAMP 1.0.0
Chemistry Across Multiple Phases
aero_rep_single_particle.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 * Single particle aerosol representation functions
6 *
7 */
8/** \file
9 * \brief Single particle aerosol representation functions
10 */
11#include <stdio.h>
12#include <stdlib.h>
13#include <camp/aero_phase_solver.h>
14#include <camp/aero_reps.h>
15#include <camp/camp_solver.h>
16
17// TODO Lookup environmental indicies during initialization
18#define TEMPERATURE_K_ env_data[0]
19#define PRESSURE_PA_ env_data[1]
20
21#define UPDATE_NUMBER 0
22
23#define NUM_LAYERS_ int_data[0]
24#define AERO_REP_ID_ int_data[1]
25#define MAX_PARTICLES_ int_data[2]
26#define PARTICLE_STATE_SIZE_ int_data[3]
27#define NUMBER_CONC_(x) aero_rep_env_data[x]
28#define NUM_INT_PROP_ 4
29#define NUM_FLOAT_PROP_ 0
30#define NUM_ENV_PARAM_ MAX_PARTICLES_
31#define LAYER_PHASE_START_(l) (int_data[NUM_INT_PROP_+l]-1)
32#define LAYER_PHASE_END_(l) (int_data[NUM_INT_PROP_+NUM_LAYERS_+l]-1)
33#define TOTAL_NUM_PHASES_ (LAYER_PHASE_END_(NUM_LAYERS_-1)-LAYER_PHASE_START_(0)+1)
34#define NUM_PHASES_(l) (LAYER_PHASE_END_(l)-LAYER_PHASE_START_(l)+1)
35#define PHASE_STATE_ID_(l,p) (int_data[NUM_INT_PROP_+2*NUM_LAYERS_+LAYER_PHASE_START_(l)+p]-1)
36#define PHASE_MODEL_DATA_ID_(l,p) (int_data[NUM_INT_PROP_+2*NUM_LAYERS_+TOTAL_NUM_PHASES_+LAYER_PHASE_START_(l)+p]-1)
37#define PHASE_NUM_JAC_ELEM_(l,p) int_data[NUM_INT_PROP_+2*NUM_LAYERS_+2*TOTAL_NUM_PHASES_+LAYER_PHASE_START_(l)+p]
38
39/** \brief Flag Jacobian elements used in calcualtions of mass and volume
40 *
41 * \param aero_rep_int_data Pointer to the aerosol representation integer data
42 * \param aero_rep_float_data Pointer to the aerosol representation
43 * floating-point data
44 * \param model_data Pointer to the model data
45 * \param aero_phase_idx Index of the aerosol phase to find elements for
46 * \param jac_struct 1D array of flags indicating potentially non-zero
47 * Jacobian elements. (The dependent variable should have
48 * been chosen by the calling function.)
49 * \return Number of Jacobian elements flagged
50 */
51
53 int aero_phase_idx,
54 int *aero_rep_int_data,
55 double *aero_rep_float_data,
56 bool *jac_struct) {
57 int *int_data = aero_rep_int_data;
58 double *float_data = aero_rep_float_data;
59 int n_jac_elem = 0;
60 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
61
62 // Each phase in a single particle has the same jac elements
63 // (one for each species in each phase in the particle)
64 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
65 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
67 model_data, PHASE_MODEL_DATA_ID_(i_layer,i_phase),
68 i_part * PARTICLE_STATE_SIZE_ + PHASE_STATE_ID_(i_layer,i_phase), jac_struct);
69 n_jac_elem += PHASE_NUM_JAC_ELEM_(i_layer,i_phase);
70 }
71 }
72 return n_jac_elem;
73}
74
75/** \brief Flag elements on the state array used by this aerosol representation
76 *
77 * The single particle aerosol representation functions do not use state array
78 * values
79 *
80 * \param aero_rep_int_data Pointer to the aerosol representation integer data
81 * \param aero_rep_float_data Pointer to the aerosol representation
82 * floating-point data
83 * \param state_flags Array of flags indicating state array elements used
84 */
85
87 double *aero_rep_float_data,
88 bool *state_flags) {
89 int *int_data = aero_rep_int_data;
90 double *float_data = aero_rep_float_data;
91
92 return;
93}
94
95/** \brief Update aerosol representation data for new environmental conditions
96 *
97 * The single particle aerosol representation does not use environmental
98 * conditions
99 *
100 * \param model_data Pointer to the model data
101 * \param aero_rep_int_data Pointer to the aerosol representation integer data
102 * \param aero_rep_float_data Pointer to the aerosol representation
103 * floating-point data
104 * \param aero_rep_env_data Pointer to the aerosol representation
105 * environment-dependent parameters
106 */
107
109 int *aero_rep_int_data,
110 double *aero_rep_float_data,
111 double *aero_rep_env_data) {
112 int *int_data = aero_rep_int_data;
113 double *float_data = aero_rep_float_data;
114 double *env_data = model_data->grid_cell_env;
115
116 return;
117}
118
119/** \brief Update aerosol representation data for a new state
120 *
121 * \param model_data Pointer to the model data, include the state array
122 * \param aero_rep_int_data Pointer to the aerosol representation integer data
123 * \param aero_rep_float_data Pointer to the aerosol representation
124 * floating-point data
125 * \param aero_rep_env_data Pointer to the aerosol representation
126 * environment-dependent parameters
127 */
128
129void aero_rep_single_particle_update_state(ModelData *model_data,
130 int *aero_rep_int_data,
131 double *aero_rep_float_data,
132 double *aero_rep_env_data) {
133 int *int_data = aero_rep_int_data;
134 double *float_data = aero_rep_float_data;
135
136 return;
137}
138
139/** \brief Get the effective radius of a specified layer \f$r_{layer}\f$ (m)
140 *
141 * \param model_data Pointer to the model data, including the state array
142 * \param aero_phase_idx Index of the aerosol phase within the representation
143 * \param layer_radius Effective layer radius (m)
144 * \param partial_deriv \f$\frac{\partial r_{eff}}{\partial y}\f$ where \f$y\f$
145 * are species on the state array
146 * \param aero_rep_int_data Pointer to the aerosol representation integer data
147 * \param aero_rep_float_data Pointer to the aerosol representation
148 * floating-point data
149 * \param aero_rep_env_data Pointer to the aerosol representation
150 * environment-dependent parameters
151 */
152
154 ModelData *model_data, int aero_phase_idx, double *layer_radius,
155 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
156 double *aero_rep_env_data) {
157
158 int *int_data = aero_rep_int_data;
159 double *float_data = aero_rep_float_data;
160 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
161 double *curr_partial = NULL;
162 int aero_phase_idx_temp = aero_phase_idx;
163 aero_phase_idx_temp -= i_part * TOTAL_NUM_PHASES_;
164
165 int i_layer_radius = -1;
166 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
167 if (LAYER_PHASE_START_(i_layer) <= aero_phase_idx_temp &&
168 aero_phase_idx_temp <= LAYER_PHASE_END_(i_layer)) {
169 i_layer_radius = i_layer;
170 break;
171 }
172 }
173
174 *layer_radius = 0.0;
175 if (partial_deriv) curr_partial = partial_deriv;
176 for (int i_layer = 0; i_layer <= i_layer_radius; ++i_layer) {
177 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
178 double *state = (double *)(model_data->grid_cell_state);
179 int phase_state_id = PHASE_STATE_ID_(i_layer,i_phase);
180 int phase_model_data_id = PHASE_MODEL_DATA_ID_(i_layer,i_phase);
181 state += i_part * PARTICLE_STATE_SIZE_ + phase_state_id;
182 double volume;
183 aero_phase_get_volume__m3_m3(model_data, phase_model_data_id,
184 state, &(volume), curr_partial);
185 if (partial_deriv) curr_partial += PHASE_NUM_JAC_ELEM_(i_layer,i_phase);
186 *layer_radius += volume;
187 }
188 }
189 *layer_radius = pow(((*layer_radius) * 3.0 / 4.0 / 3.14159265359), 1.0 / 3.0);
190 if (!partial_deriv) return;
191 for (int i_layer = 0; i_layer <= i_layer_radius; ++i_layer) {
192 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
193 for (int i_spec = 0; i_spec < PHASE_NUM_JAC_ELEM_(i_layer,i_phase); ++i_spec) {
194 *partial_deriv =
195 1.0 / 4.0 / 3.14159265359 * pow(*layer_radius, -2.0) * (*partial_deriv);
196 ++partial_deriv;
197 }
198 }
199 }
200 return;
201}
202
203/** \brief Get the effective particle radius \f$r_{eff}\f$ (m)
204 * Finds the radius of the largest layer in specified particle.
205 *
206 * \param model_data Pointer to the model data, including the state array
207 * \param aero_phase_idx Index of the aerosol phase within the representation
208 * \param radius Effective particle radius (m)
209 * \param partial_deriv \f$\frac{\partial r_{eff}}{\partial y}\f$ where \f$y\f$
210 * are species on the state array
211 * \param aero_rep_int_data Pointer to the aerosol representation integer data
212 * \param aero_rep_float_data Pointer to the aerosol representation
213 * floating-point data
214 * \param aero_rep_env_data Pointer to the aerosol representation
215 * environment-dependent parameters
216 */
217
219 ModelData *model_data, int aero_phase_idx, double *radius,
220 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
221 double *aero_rep_env_data) {
222
223 int *int_data = aero_rep_int_data;
224 double *float_data = aero_rep_float_data;
225 double *curr_partial = NULL;
226
227 // Adjust aero_phase_idx to the last phase in the particle.
228 // Add 1 to aero_phase_idx/TOTAL_NUM_PHASES_ to account for c indexing starting at 0.
229 int offset = (TOTAL_NUM_PHASES_*((aero_phase_idx / TOTAL_NUM_PHASES_)+1)) - aero_phase_idx;
230 aero_phase_idx += offset;
232 model_data,
233 aero_phase_idx-1, // Adjusted to last phase in particle.
234 radius,
235 partial_deriv,
236 int_data,
237 float_data,
238 aero_rep_env_data);
239
240 return;
241}
242
243/** \brief Get the volume of a specified phase in the corresponding layer
244 *
245 * \param model_data Pointer to the model data, including the state array
246 * \param aero_phase_idx Index of the aerosol phase within the representation
247 * \param phase_volume Volume of the phase (m^3)
248 * \param partial_deriv \f$\frac{\partial V}{\partial y}\f$ where \f$y\f$
249 * are species on the state array
250 * \param aero_rep_int_data Pointer to the aerosol representation integer data
251 * \param aero_rep_float_data Pointer to the aerosol representation
252 * floating-point data
253 * \param aero_rep_env_data Pointer to the aerosol representation
254 * environment-dependent parameters
255 */
256
258 ModelData *model_data, int aero_phase_idx, double *phase_volume,
259 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
260 double *aero_rep_env_data) {
261
262 int *int_data = aero_rep_int_data;
263 double *float_data = aero_rep_float_data;
264 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
265 double *curr_partial = NULL;
266 int aero_phase_idx_temp = aero_phase_idx;
267 aero_phase_idx_temp -= i_part * TOTAL_NUM_PHASES_;
268
269 int i_layer_phase = -1;
270 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
271 if (LAYER_PHASE_START_(i_layer) <= aero_phase_idx_temp &&
272 aero_phase_idx_temp <= LAYER_PHASE_END_(i_layer)) {
273 i_layer_phase = i_layer;
274 break;
275 }
276 }
277
278 if (i_layer_phase == -1) {
279 printf("\n\nERROR: aero_rep_single_particle_get_phase_volume__m3_m3: Could not determine i_layer_phase ");
280 printf("for aero_phase_idx=%d.\n\n", aero_phase_idx);
281 exit(1);
282 }
283
284 int total_phases_previous_layers = 0;
285 for (int i_layer = 0; i_layer < i_layer_phase; ++i_layer) {
286 total_phases_previous_layers += NUM_PHASES_(i_layer);
287 }
288 int i_phase = aero_phase_idx_temp - total_phases_previous_layers;
289
290 if (partial_deriv) curr_partial = partial_deriv;
291 double *state = (double *)(model_data->grid_cell_state);
292 state += i_part * PARTICLE_STATE_SIZE_ + PHASE_STATE_ID_(i_layer_phase,i_phase);
293 aero_phase_get_volume__m3_m3(model_data, PHASE_MODEL_DATA_ID_(i_layer_phase,i_phase),
294 state, phase_volume, curr_partial);
295 if (partial_deriv) curr_partial += PHASE_NUM_JAC_ELEM_(i_layer_phase,i_phase);
296
297 }
298
299/** \brief Get the surface area of specified particle layer \f$r_{eff}\f$ (m)
300 *
301 * Solve for the surface area of the interfacial layer that exists between the
302 * two phases considered in aerosol phase mass tranfer between layers. When more
303 * than one phase exists in a layer, a "fractional volume overlap" configuration
304 * is applied (see the single particle description in the Fortran script and
305 * CAMP Github Documentation for details).
306 *
307 * \param model_data Pointer to the model data, including the state array
308 * \param aero_phase_idx_first Index of the first aerosol phase within the representation
309 * \param aero_phase_idx_second Index of the second aerosol phase within the representation
310 * \param surface_area Pointer to surface area of inner layer (m^2)
311 * \param partial_deriv \f$\frac{\partial sa_{eff}}{\partial y}\f$ where \f$y\f$
312 * are species on the state array
313 * \param aero_rep_int_data Pointer to the aerosol representation integer data
314 * \param aero_rep_float_data Pointer to the aerosol representation
315 * floating-point data
316 * \param aero_rep_env_data Pointer to the aerosol representation
317 * environment-dependent parameters
318 */
319
321 ModelData *model_data, int aero_phase_idx_first, int aero_phase_idx_second,
322 double *surface_area, double *partial_deriv,
323 int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data) {
324
325 int *int_data = aero_rep_int_data;
326 double *float_data = aero_rep_float_data;
327 double *curr_partial = NULL;
328 int layer_first = -1;
329 int layer_second = -1;
330 int layer_interface = -1;
331 int phase_model_data_id_first = -1;
332 int phase_model_data_id_second = -1;
333 double radius;
334 int i_part = aero_phase_idx_first / TOTAL_NUM_PHASES_;
335 aero_phase_idx_first -= i_part * TOTAL_NUM_PHASES_;
336 aero_phase_idx_second -= i_part * TOTAL_NUM_PHASES_;
337
338 // Find the layer each phase (first and second) exist in
339 int i_phase_count = 0;
340 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
341 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
342 if (LAYER_PHASE_START_(i_layer) <= aero_phase_idx_first &&
343 aero_phase_idx_first <= LAYER_PHASE_END_(i_layer) &&
344 i_phase_count == aero_phase_idx_first) {
345 layer_first = i_layer;
346 phase_model_data_id_first = PHASE_MODEL_DATA_ID_(i_layer, i_phase);
347 } else if (LAYER_PHASE_START_(i_layer) <= aero_phase_idx_second &&
348 aero_phase_idx_second <= LAYER_PHASE_END_(i_layer) &&
349 i_phase_count == aero_phase_idx_second) {
350 layer_second = i_layer;
351 phase_model_data_id_second = PHASE_MODEL_DATA_ID_(i_layer, i_phase);
352 }
353 ++i_phase_count;
354 }
355 }
356 if (layer_first == -1 || layer_second == -1) {
357 printf("\n\nERROR: aero_rep_single_particle_get_interface_surface_area__m2: ");
358 printf("Could not determine layer_first and layer_second for ");
359 printf("aero_phase_idx_first=%d and aero_phase_idx_second=%d.\n\n", aero_phase_idx_first, aero_phase_idx_second);
360 exit(1);
361 }
362
363 if (layer_second < layer_first) {
364 printf("\n\nERROR: aero_rep_single_particle_get_interface_surface_area__m2: ");
365 printf("layer_second is less than layer_first for ");
366 printf("aero_phase_idx_first=%d and aero_phase_idx_second=%d.\n\n", aero_phase_idx_first, aero_phase_idx_second);
367 exit(1);
368 }
369
370 // Find the interface between the first and second layer
371 layer_interface = layer_first > layer_second ? layer_second : layer_first;
372
373 /* Solve for the total volume, total volume of the layer with the first phase,
374 * total volume of the layer with the second phase, volume of first phase (within
375 * first layer) and volume of second phase (within second layer).
376 */
377 double interface_volume = 0.0;
378 double total_volume_layer_first = 0.0;
379 double total_volume_layer_second = 0.0;
380 double volume_phase_first = 0.0;
381 double volume_phase_second = 0.0;
382 i_phase_count = 0;
383 if (partial_deriv) curr_partial = partial_deriv;
384 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
385 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
386 double *state = (double *)(model_data->grid_cell_state);
387 state += i_part *PARTICLE_STATE_SIZE_ + PHASE_STATE_ID_(i_layer,i_phase);
388 double volume;
389 aero_phase_get_volume__m3_m3(model_data, PHASE_MODEL_DATA_ID_(i_layer,i_phase),
390 state, &(volume), curr_partial);
391 if (i_layer == layer_first) total_volume_layer_first += volume;
392 if (i_phase_count == aero_phase_idx_first &&
393 PHASE_MODEL_DATA_ID_(i_layer, i_phase) ==
394 phase_model_data_id_first) volume_phase_first = volume;
395 if (i_layer == layer_second) total_volume_layer_second += volume;
396 if (i_phase_count == aero_phase_idx_second &&
397 PHASE_MODEL_DATA_ID_(i_layer, i_phase) ==
398 phase_model_data_id_second) volume_phase_second = volume;
399 if (i_layer <= layer_interface) interface_volume += volume;
400 if (partial_deriv) curr_partial += PHASE_NUM_JAC_ELEM_(i_layer,i_phase);
401 ++i_phase_count;
402 }
403 }
404
405 /* Calculate the fractional volume of first and second phase in their
406 * assocaited layers. Calculate the radius and surface area of the interface
407 * between the first and second layer.
408 */
409 double f_first = volume_phase_first / total_volume_layer_first;
410 double f_second = volume_phase_second / total_volume_layer_second;
411 radius = pow(((interface_volume) * 3.0 / 4.0 / M_PI), 1.0 / 3.0);
412 *surface_area = f_first * f_second * 4 * M_PI * pow(radius, 2.0);
413
414
415 // Calculate the partial derivatives for each layer/phase combination.
416 if (!partial_deriv) return;
417 i_phase_count = 0;
418 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
419 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
420 double *state = (double *)(model_data->grid_cell_state);
421 state += i_part * PARTICLE_STATE_SIZE_ + PHASE_STATE_ID_(i_layer,i_phase);
422 double volume_phase;
423 aero_phase_get_volume__m3_m3(model_data, PHASE_MODEL_DATA_ID_(i_layer,i_phase),
424 state, &(volume_phase), NULL);
425 for (int i_spec = 0; i_spec < PHASE_NUM_JAC_ELEM_(i_layer,i_phase); ++i_spec) {
426 // layer = layer_first, phase = aero_phase_idx_first
427 if (i_layer == layer_first && i_phase_count == aero_phase_idx_first) {
428 *partial_deriv =
429 (((total_volume_layer_first - volume_phase_first) *
430 pow(total_volume_layer_first, -2.0) * f_second * (*surface_area)) +
431 2.0 * f_first * f_second * pow(radius, -1.0)) * (*partial_deriv);
432 ++partial_deriv;
433 }
434 // layer = layer_first, phase != aero_phase_idx_first
435 else if (i_layer == layer_first && i_phase_count != aero_phase_idx_first) {
436 *partial_deriv =
437 (((-1 * volume_phase) * pow(total_volume_layer_first, -2.0) *
438 f_second * (*surface_area)) + 2.0 * f_first * f_second *
439 pow(radius, -1.0)) * (*partial_deriv);
440 ++partial_deriv;
441 }
442 // layer = layer_second, phase = aero_phase_idx_second
443 else if (i_layer == layer_second && i_phase_count == aero_phase_idx_second) {
444 *partial_deriv =
445 (((total_volume_layer_second - volume_phase_second) *
446 pow(total_volume_layer_second, -2.0) * f_first * (*surface_area)) +
447 2.0 * f_first * f_second * pow(radius, -1.0)) * (*partial_deriv);
448 ++partial_deriv;
449 }
450 // layer = layer_second, phase != aero_phase_idx_second
451 else if (i_layer == layer_second && i_phase_count != aero_phase_idx_second) {
452 *partial_deriv =
453 (((-1 * volume_phase) * pow(total_volume_layer_second, -2.0) *
454 f_first * (*surface_area)) + 2.0 * f_first * f_second *
455 pow(radius, -1.0)) * (*partial_deriv);
456 ++partial_deriv;
457 }
458 else if (i_layer < layer_first) {
459 *partial_deriv = 2.0 * f_first * f_second * pow(radius, -1.0) * (*partial_deriv);
460 ++partial_deriv;
461 }
462 // Set partial_derivative = 0 for all other layers.
463 else if (i_layer > layer_second) {
464 *(partial_deriv++) = ZERO;
465 }
466 else {
467 printf("\n\nERROR No conditions met for surface area partial derivative.\n\n");
468 exit(1);
469 }
470 }
471 ++i_phase_count;
472 }
473 }
474 return;
475}
476
477/** \brief Get the thickness of a particle layer (m)
478 *
479 * \param model_data Pointer to the model data, including the state array
480 * \param aero_phase_idx Index of the aerosol phase within the representation
481 * \param layer_thickness Effective layer thickness (m)
482 * \param partial_deriv \f$\frac{\partial r_{eff}}{\partial y}\f$ where \f$y\f$
483 * are species on the state array
484 * \param aero_rep_int_data Pointer to the aerosol representation integer data
485 * \param aero_rep_float_data Pointer to the aerosol representation
486 * floating-point data
487 * \param aero_rep_env_data Pointer to the aerosol representation
488 * environment-dependent parameters
489 */
490
492 ModelData *model_data, int aero_phase_idx, double *layer_thickness,
493 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
494 double *aero_rep_env_data) {
495
496 int *int_data = aero_rep_int_data;
497 double *float_data = aero_rep_float_data;
498 int jac_size = PARTICLE_STATE_SIZE_;
499 double radius_inner, radius_outer;
500 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
501 int aero_phase_idx_temp = aero_phase_idx;
502 aero_phase_idx_temp -= i_part * TOTAL_NUM_PHASES_;
503
504 // Temporary Jacobians
505 double *jac_inner = NULL;
506 double *jac_outer = NULL;
507
508 if (partial_deriv) {
509 jac_inner = (double *)calloc(jac_size, sizeof(double));
510 jac_outer = (double *)calloc(jac_size, sizeof(double));
511 if (jac_inner == NULL || jac_outer == NULL) {
512 printf("\n\nERROR: Memory allocation failed for jacobian arrays in ");
513 printf("aero_rep_single_particle_get_layer_thickness__m.\n\n");
514 exit(1);
515 }
516 }
517
518 int i_layer_inner = -1;
519 int i_layer_outer = -1;
520 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
521 if (LAYER_PHASE_START_(i_layer) <= aero_phase_idx_temp &&
522 aero_phase_idx_temp <= LAYER_PHASE_END_(i_layer)) {
523 i_layer_outer = i_layer;
524 i_layer_inner = (i_layer > 0) ? (i_layer - 1) : i_layer;
525 break;
526 }
527 }
528 if (i_layer_outer < 0) {
529 printf("ERROR: Could not determine layer for aero_phase_idx=%d (temp=%d)\n",
530 aero_phase_idx, aero_phase_idx_temp);
531 exit(1);
532 }
533 int offset = aero_phase_idx_temp - LAYER_PHASE_START_(i_layer_outer);
534 int aero_phase_idx_inner = -1;
535 if (i_layer_inner == i_layer_outer) {
536 aero_phase_idx_inner = aero_phase_idx;
537 } else {
538 aero_phase_idx_inner = aero_phase_idx - (offset+1);
539 }
540
542 model_data,
543 aero_phase_idx,
544 &radius_outer,
545 jac_outer,
546 int_data,
547 float_data,
548 aero_rep_env_data);
549
550 // For innermost layer, the inner radius is the same as the outer radius
552 model_data,
553 aero_phase_idx_inner,
554 &radius_inner,
555 jac_inner,
556 int_data,
557 float_data,
558 aero_rep_env_data);
559
560 if (i_layer_inner == i_layer_outer) {
561 *layer_thickness = radius_outer;
562 } else {
563 *layer_thickness = radius_outer - radius_inner;
564 }
565
566 if (partial_deriv) {
567 for (int i = 0; i < jac_size; ++i) {
568 if (i_layer_inner == i_layer_outer) {
569 partial_deriv[i] = jac_outer[i];
570 } else {
571 partial_deriv[i] = jac_outer[i] - jac_inner[i];
572 }
573 }
574 }
575
576 free(jac_inner);
577 free(jac_outer);
578 return;
579}
580
581/** \brief Get the particle number concentration \f$n\f$
582 * (\f$\mbox{\si{\#\per\cubic\metre}}\f$)
583 *
584 * This single particle number concentration is set by the aerosol model prior
585 * to solving the chemistry. Thus, all \f$\frac{\partial n}{\partial y}\f$ are
586 * zero. Also, there is only one set of particles in the single particle
587 * representation, so the phase index is not used.
588 *
589 * \param model_data Pointer to the model data, including the state array
590 * \param aero_phase_idx Index of the aerosol phase within the representation
591 * (not used)
592 * \param number_conc Particle number concentration, \f$n\f$
593 * (\f$\mbox{\si{\#\per\cubic\metre}}\f$)
594 * \param partial_deriv \f$\frac{\partial n}{\partial y}\f$ where \f$y\f$ are
595 * the species on the state array
596 * \param aero_rep_int_data Pointer to the aerosol representation integer data
597 * \param aero_rep_float_data Pointer to the aerosol representation
598 * floating-point data
599 * \param aero_rep_env_data Pointer to the aerosol representation
600 * environment-dependent parameters
601 */
602
604 ModelData *model_data, int aero_phase_idx, double *number_conc,
605 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
606 double *aero_rep_env_data) {
607
608
609 int *int_data = aero_rep_int_data;
610 double *float_data = aero_rep_float_data;
611 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
612
613 *number_conc = NUMBER_CONC_(i_part);
614
615 if (partial_deriv) {
616 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
617 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
618 for (int i_spec = 0; i_spec < PHASE_NUM_JAC_ELEM_(i_layer,i_phase); ++i_spec)
619 *(partial_deriv++) = ZERO;
620 }
621 }
622 }
623 return;
624}
625
626/** \brief Get the type of aerosol concentration used.
627 *
628 * Single particle concentrations are per-particle.
629 *
630 * \param aero_phase_idx Index of the aerosol phase within the representation
631 * \param aero_conc_type Pointer to int that will hold the concentration type
632 * code (0 = per particle mass concentrations;
633 * 1 = total particle mass concentrations)
634 * \param aero_rep_int_data Pointer to the aerosol representation integer data
635 * \param aero_rep_float_data Pointer to the aerosol representation
636 * floating-point data
637 * \param aero_rep_env_data Pointer to the aerosol representation
638 * environment-dependent parameters
639 */
640
642 int *aero_conc_type,
643 int *aero_rep_int_data,
644 double *aero_rep_float_data,
645 double *aero_rep_env_data) {
646 int *int_data = aero_rep_int_data;
647 double *float_data = aero_rep_float_data;
648
649 *aero_conc_type = 0;
650
651 return;
652}
653
654/** \brief Get the total mass in an aerosol phase \f$m\f$
655 * (\f$\mbox{\si{\kilogram\per\cubic\metre}}\f$)
656 *
657 * The single particle mass is set for each new state as the sum of the masses
658 * of the aerosol phases that compose the particle
659 *
660 * \param model_data Pointer to the model data, including the state array
661 * \param aero_phase_idx Index of the aerosol phase within the representation
662 * \param aero_phase_mass Total mass in the aerosol phase, \f$m\f$
663 * (\f$\mbox{\si{\kilogram\per\cubic\metre}}\f$)
664 * \param partial_deriv \f$\frac{\partial m}{\partial y}\f$ where \f$y\f$ are
665 * the species on the state array
666 * \param aero_rep_int_data Pointer to the aerosol representation integer data
667 * \param aero_rep_float_data Pointer to the aerosol representation
668 * floating-point data
669 * \param aero_rep_env_data Pointer to the aerosol representation
670 * environment-dependent parameters
671 */
672
674 ModelData *model_data, int aero_phase_idx, double *aero_phase_mass,
675 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
676 double *aero_rep_env_data) {
677
678 int *int_data = aero_rep_int_data;
679 double *float_data = aero_rep_float_data;
680 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
681 aero_phase_idx -= i_part * TOTAL_NUM_PHASES_;
682
683 int i_total_phase = 0;
684 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
685 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
686 if ( i_total_phase == aero_phase_idx ) {
687 double *state = (double *)(model_data->grid_cell_state);
688 state += i_part * PARTICLE_STATE_SIZE_ + PHASE_STATE_ID_(i_layer,i_phase);
689 double mw;
690 aero_phase_get_mass__kg_m3(model_data, PHASE_MODEL_DATA_ID_(i_layer,i_phase),
691 state, aero_phase_mass, &mw, partial_deriv, NULL);
692 if (partial_deriv) partial_deriv += PHASE_NUM_JAC_ELEM_(i_layer,i_phase);
693 } else if (partial_deriv) {
694 for (int i_spec = 0; i_spec < PHASE_NUM_JAC_ELEM_(i_layer,i_phase); ++i_spec)
695 *(partial_deriv++) = ZERO;
696 }
697 ++i_total_phase;
698 }
699 }
700 return;
701}
702
703/** \brief Get the average molecular weight in an aerosol phase
704 ** \f$m\f$ (\f$\mbox{\si{\kilo\gram\per\mol}}\f$)
705 *
706 * The single particle mass is set for each new state as the sum of the masses
707 * of the aerosol phases that compose the particle
708 *
709 * \param model_data Pointer to the model data, including the state array
710 * \param aero_phase_idx Index of the aerosol phase within the representation
711 * \param aero_phase_avg_MW Average molecular weight in the aerosol phase
712 * (\f$\mbox{\si{\kilogram\per\mole}}\f$)
713 * \param partial_deriv \f$\frac{\partial m}{\partial y}\f$ where \f$y\f$ are
714 * the species on the state array
715 * \param aero_rep_int_data Pointer to the aerosol representation integer data
716 * \param aero_rep_float_data Pointer to the aerosol representation
717 * floating-point data
718 * \param aero_rep_env_data Pointer to the aerosol representation
719 * environment-dependent parameters
720 */
721
723 ModelData *model_data, int aero_phase_idx, double *aero_phase_avg_MW,
724 double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data,
725 double *aero_rep_env_data) {
726
727 int *int_data = aero_rep_int_data;
728 double *float_data = aero_rep_float_data;
729 int i_part = aero_phase_idx / TOTAL_NUM_PHASES_;
730 aero_phase_idx -= i_part * TOTAL_NUM_PHASES_;
731
732 int i_total_phase = 0;
733 for (int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer) {
734 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
735 if ( i_total_phase == aero_phase_idx ) {
736 double *state = (double *)(model_data->grid_cell_state);
737 state += i_part * PARTICLE_STATE_SIZE_ + PHASE_STATE_ID_(i_layer,i_phase);
738 double mass;
739 aero_phase_get_mass__kg_m3(model_data, PHASE_MODEL_DATA_ID_(i_layer,i_phase),
740 state, &mass, aero_phase_avg_MW, NULL, partial_deriv);
741 if (partial_deriv) partial_deriv += PHASE_NUM_JAC_ELEM_(i_layer,i_phase);
742 } else if (partial_deriv) {
743 for (int i_spec = 0; i_spec < PHASE_NUM_JAC_ELEM_(i_layer,i_phase); ++i_spec)
744 *(partial_deriv++) = ZERO;
745 }
746 ++i_total_phase;
747 }
748 }
749 return;
750}
751
752/** \brief Update aerosol representation data
753 *
754 * Single particle aerosol representation update data is structured as follows:
755 *
756 * - \b int aero_rep_id (Id of one or more aerosol representations set by the
757 * host model using the
758 * camp_aero_rep_single_particle::aero_rep_single_particle_t::set_id
759 * function prior to initializing the solver.)
760 * - \b int update_type (Type of update to perform. Can be UPDATE_NUMBER
761 * only.)
762 * - \b double new_value (Either the new radius (m) or the new number
763 * concentration (\f$\mbox{\si{\#\per\cubic\centi\metre}}\f$).)
764 *
765 * \param update_data Pointer to the updated aerosol representation data
766 * \param aero_rep_int_data Pointer to the aerosol representation integer data
767 * \param aero_rep_float_data Pointer to the aerosol representation
768 * floating-point data
769 * \param aero_rep_env_data Pointer to the aerosol representation
770 * environment-dependent parameters
771 * \return Flag indicating whether this is the aerosol representation to update
772 */
773
775 int *aero_rep_int_data,
776 double *aero_rep_float_data,
777 double *aero_rep_env_data) {
778 int *int_data = aero_rep_int_data;
779 double *float_data = aero_rep_float_data;
780
781 int *aero_rep_id = (int *)update_data;
782 int *update_type = (int *)&(aero_rep_id[1]);
783 int *particle_id = (int *)&(update_type[1]);
784 double *new_value = (double *)&(update_type[2]);
785
786 // Set the new radius or number concentration for matching aerosol
787 // representations
788 if (*aero_rep_id == AERO_REP_ID_ && AERO_REP_ID_ != 0) {
789 if (*update_type == UPDATE_NUMBER) {
790 NUMBER_CONC_(*particle_id) = (double)*new_value;
791 return true;
792 }
793 }
794
795 return false;
796}
797
798/** \brief Print the Single Particle reaction parameters
799 *
800 * \param aero_rep_int_data Pointer to the aerosol representation integer data
801 * \param aero_rep_float_data Pointer to the aerosol representation
802 * floating-point data
803 */
804
805void aero_rep_single_particle_print(int *aero_rep_int_data,
806 double *aero_rep_float_data) {
807 int *int_data = aero_rep_int_data;
808 double *float_data = aero_rep_float_data;
809
810 printf("\n\nSingle particle aerosol representation\n");
811 printf("\nNumber of phases: %d", TOTAL_NUM_PHASES_);
812 printf("\nAerosol representation id: %d", AERO_REP_ID_);
813 printf("\nMax computational particles: %d", MAX_PARTICLES_);
814 printf("\nParticle state size: %d", PARTICLE_STATE_SIZE_);
815 for(int i_layer = 0; i_layer < NUM_LAYERS_; ++i_layer){
816 printf("\nLayer: %d", i_layer);
817 printf("\n Start phase: %d End phase: %d", LAYER_PHASE_START_(i_layer), LAYER_PHASE_END_(i_layer));
818 printf("\n Number of phases: %d", NUM_PHASES_(i_layer));
819 printf("\n\n - Phases -");
820 for (int i_phase = 0; i_phase < NUM_PHASES_(i_layer); ++i_phase) {
821 printf("\n state id: %d model data id: %d num Jac elements: %d",
822 PHASE_STATE_ID_(i_layer,i_phase), PHASE_MODEL_DATA_ID_(i_layer,i_phase),
823 PHASE_NUM_JAC_ELEM_(i_layer,i_phase));
824 }
825 }
826 printf("\n\nEnd single particle aerosol representation\n");
827 return;
828}
829
830
831/** \brief Create update data for new particle number
832 *
833 * \return Pointer to a new number update data object
834 */
836 int *update_data = (int *)malloc(3 * sizeof(int) + sizeof(double));
837 if (update_data == NULL) {
838 printf("\n\nERROR allocating space for number update data\n\n");
839 exit(1);
840 }
841 return (void *)update_data;
842}
843
844/** \brief Set number update data (#/m3)
845 *
846 * \param update_data Pointer to an allocated number update data object
847 * \param aero_rep_id Id of the aerosol representation(s) to update
848 * \param particle_id Id of the computational particle
849 * \param number_conc New particle number (#/m3)
850 */
852 int aero_rep_id,
853 int particle_id,
854 double number_conc) {
855 int *new_aero_rep_id = (int *)update_data;
856 int *update_type = (int *)&(new_aero_rep_id[1]);
857 int *new_particle_id = (int *)&(update_type[1]);
858 double *new_number_conc = (double *)&(update_type[2]);
859 *new_aero_rep_id = aero_rep_id;
860 *update_type = UPDATE_NUMBER;
861 *new_particle_id = particle_id;
862 *new_number_conc = number_conc;
863}
864
void aero_phase_get_volume__m3_m3(ModelData *model_data, int aero_phase_idx, double *state_var, double *volume, double *jac_elem)
Get the volume of an aerosol phase.
int aero_phase_get_used_jac_elem(ModelData *model_data, int aero_phase_idx, int state_var_id, bool *jac_struct)
Flag Jacobian elements used in calculations of mass and volume.
void aero_phase_get_mass__kg_m3(ModelData *model_data, int aero_phase_idx, double *state_var, double *mass, double *MW, double *jac_elem_mass, double *jac_elem_MW)
Get the mass and average MW in an aerosol phase.
int aero_rep_single_particle_get_used_jac_elem(ModelData *model_data, int aero_phase_idx, int *aero_rep_int_data, double *aero_rep_float_data, bool *jac_struct)
Flag Jacobian elements used in calcualtions of mass and volume.
void aero_rep_single_particle_get_aero_phase_avg_MW__kg_mol(ModelData *model_data, int aero_phase_idx, double *aero_phase_avg_MW, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the average molecular weight in an aerosol phase ( )
#define NUM_PHASES_(l)
void aero_rep_single_particle_get_effective_radius__m(ModelData *model_data, int aero_phase_idx, double *radius, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the effective particle radius (m) Finds the radius of the largest layer in specified particle.
#define PARTICLE_STATE_SIZE_
void aero_rep_single_particle_get_aero_conc_type(int aero_phase_idx, int *aero_conc_type, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the type of aerosol concentration used.
void * aero_rep_single_particle_create_number_update_data()
Create update data for new particle number.
#define PHASE_NUM_JAC_ELEM_(l, p)
void aero_rep_single_particle_get_interface_surface_area__m2(ModelData *model_data, int aero_phase_idx_first, int aero_phase_idx_second, double *surface_area, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the surface area of specified particle layer (m)
#define AERO_REP_ID_
void aero_rep_single_particle_print(int *aero_rep_int_data, double *aero_rep_float_data)
Print the Single Particle reaction parameters.
#define MAX_PARTICLES_
#define LAYER_PHASE_START_(l)
#define PHASE_MODEL_DATA_ID_(l, p)
#define TOTAL_NUM_PHASES_
#define PHASE_STATE_ID_(l, p)
void aero_rep_single_particle_get_phase_volume__m3_m3(ModelData *model_data, int aero_phase_idx, double *phase_volume, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the volume of a specified phase in the corresponding layer.
void aero_rep_single_particle_update_env_state(ModelData *model_data, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Update aerosol representation data for new environmental conditions.
void aero_rep_single_particle_get_layer_thickness__m(ModelData *model_data, int aero_phase_idx, double *layer_thickness, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the thickness of a particle layer (m)
bool aero_rep_single_particle_update_data(void *update_data, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Update aerosol representation data.
void aero_rep_single_particle_get_dependencies(int *aero_rep_int_data, double *aero_rep_float_data, bool *state_flags)
Flag elements on the state array used by this aerosol representation.
#define UPDATE_NUMBER
#define NUM_LAYERS_
void aero_rep_single_particle_get_aero_phase_mass__kg_m3(ModelData *model_data, int aero_phase_idx, double *aero_phase_mass, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the total mass in an aerosol phase ( )
#define LAYER_PHASE_END_(l)
void aero_rep_single_particle_get_effective_layer_radius__m(ModelData *model_data, int aero_phase_idx, double *layer_radius, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the effective radius of a specified layer (m)
#define NUMBER_CONC_(x)
void aero_rep_single_particle_get_number_conc__n_m3(ModelData *model_data, int aero_phase_idx, double *number_conc, double *partial_deriv, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Get the particle number concentration ( )
void aero_rep_single_particle_set_number_update_data__n_m3(void *update_data, int aero_rep_id, int particle_id, double number_conc)
Set number update data (#/m3)
void aero_rep_single_particle_update_state(ModelData *model_data, int *aero_rep_int_data, double *aero_rep_float_data, double *aero_rep_env_data)
Update aerosol representation data for a new state.