NAPISD
PAHdb website C++ backend
Loading...
Searching...
No Matches
PAHEmissionModel.cpp
1#include "PAHEmissionModel.h"
2
3double PAHEmissionModel::_energy;
4
5double PAHEmissionModel::_frequency;
6
7double PAHEmissionModel::_nc;
8
9bool PAHEmissionModel::_approximate = false;
10
11PAHEmissionModel::PAHEmissionModel()
12 : _transitions(std::vector<std::vector<std::pair<double, double>>>(0)),
13 _grid(std::vector<double>(0)) {}
14
15PAHEmissionModel::PAHEmissionModel(
16 const std::vector<std::vector<std::pair<double, double>>> &transitions)
17 : _transitions(transitions), _grid(std::vector<double>(0)) {}
18
19void PAHEmissionModel::setGrid(const std::vector<double> &grid) {
20
21 _grid = grid;
22
23 _fmin = *min_element(_grid.begin(), _grid.end());
24
25 _fmax = *max_element(_grid.begin(), _grid.end());
26}
27
28void PAHEmissionModel::makeGrid(double fmin, double fmax, double step) {
29
30 if (fmin > fmax) {
31
32 double tmp = fmin;
33
34 fmin = fmax;
35
36 fmax = tmp;
37 }
38
39 _fmin = fmin;
40
41 _fmax = fmax;
42
43 _grid.clear();
44
45 while (fmin < fmax) {
46
47 _grid.push_back(fmin);
48
49 fmin += step;
50 }
51
52 if (_grid.back() != fmax) {
53
54 _grid.push_back(fmax);
55 }
56}
57
58void PAHEmissionModel::setTransitions(
59 const std::vector<std::vector<std::pair<double, double>>> &transitions) {
60
61 _transitions = transitions;
62}
63
64void PAHEmissionModel::getTransitions(
65 std::vector<std::vector<std::pair<double, double>>> &transitions) {
66
67 transitions = _transitions;
68}
69
70void PAHEmissionModel::printTransitions() {
71
72 std::cout.setf(std::ios::right);
73
74 std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(5)
75 << std::showpoint;
76
77 for (const auto &transition : _transitions) {
78
79 for (const auto &t : transition) {
80
81 std::cout << std::setw(10) << t.first << '\t' << std::setw(10) << t.second
82 << '\n';
83 }
84
85 std::cout << std::endl;
86 }
87}
88
89void PAHEmissionModel::getSpectraAndConvolveWithLorentianOfFHWM(
90 std::vector<std::vector<double>> &vector, double fwhm) {
91
92 vector.clear();
93
94 double hwhm = fwhm / 2.0;
95
96 for (auto &transition : _transitions) {
97
98 std::vector<double> intensities(_grid.size());
99
100 for (auto &t : transition) {
101
102 if (t.second == 0 || t.first < (_fmin - 22.0 * hwhm) ||
103 t.first > (_fmax + 22.0 * hwhm)) {
104 continue;
105 }
106
107 int i = 0;
108
109 for (const auto &g : _grid) {
110
111 intensities.at(i++) += t.second * Lorentzian(g, t.first, hwhm);
112 }
113 }
114
115 vector.push_back(intensities);
116 }
117}
118
119void PAHEmissionModel::getSpectraAndConvolveWithGaussianOfFHWM(
120 std::vector<std::vector<double>> &vector, double fwhm) {
121
122 vector.clear();
123
124 vector.reserve(_transitions.size());
125
126 double sigma = fwhm / 2.0 / sqrt(2.0 * log(2.0));
127
128 for (auto &transition : _transitions) {
129
130 std::vector<double> intensities(_grid.size());
131
132 for (auto &t : transition) {
133
134 if (t.second == 0 || t.first < (_fmin - 3.0 * sigma) ||
135 t.first > (_fmax + 3.0 * sigma)) {
136 continue;
137 }
138
139 int i = 0;
140
141 for (const auto &g : _grid) {
142
143 intensities.at(i++) += t.second * Gaussian(g, t.first, sigma);
144 }
145 }
146
147 vector.push_back(intensities);
148 }
149}
150
151void PAHEmissionModel::getSpectraAndConvolveWithDrudeOfFHWM(
152 std::vector<std::vector<double>> &vector, double fwhm) {
153
154 vector.clear();
155
156 vector.reserve(_transitions.size());
157
158 for (auto &transition : _transitions) {
159
160 std::vector<double> intensities(_grid.size());
161
162 for (auto &t : transition) {
163
164 if (t.second == 0 || t.first < (_fmin - 8.0 * fwhm) ||
165 t.first > (_fmax + 8.0 * fwhm)) {
166 continue;
167 }
168
169 int i = 0;
170
171 for (const auto &g : _grid) {
172
173 intensities.at(i++) += t.second * Drude(g, t.first, fwhm);
174 }
175 }
176
177 vector.push_back(intensities);
178 }
179}
180
181void PAHEmissionModel::applyBlackbodyWithTemperature(double temperature) {
182
183 for (auto &transition : _transitions) {
184
185 for (auto &t : transition) {
186
187 t.second *=
188 1e5 * 4.0 * M_PI * Blackbody(t.first, temperature) / AvogadrosNumber;
189 }
190 }
191}
192
193void PAHEmissionModel::applyBlackbodyWithTemperatureForEach(
194 const std::vector<double> &temperatures) {
195
196 size_t i = 0;
197
198 for (auto &transition : _transitions) {
199
200 for (auto &t : transition) {
201
202 t.second *= 1e5 * 4.0 * M_PI * Blackbody(t.first, temperatures[i]) /
203 AvogadrosNumber;
204 }
205 ++i;
206 }
207}
208
209void PAHEmissionModel::applyTemperatureWithEnergy(
210 double energy, std::vector<double> &temperatures) {
211
212 temperatures.clear();
213
214 temperatures.reserve(_transitions.size());
215
216 int j = 0;
217
218 for (auto &transition : _transitions) {
219
220 if (_approximate) {
221
222 _nc = static_cast<double>(_carbons.at(j++));
223 }
224
225 temperatures.push_back(solveInitialTemperature(energy, transition));
226 }
227
228 applyBlackbodyWithTemperatureForEach(temperatures);
229}
230
231void PAHEmissionModel::applyCascadeWithEnergy(
232 double energy, std::vector<double> &temperatures) {
233
234 temperatures.clear();
235
236 temperatures.reserve(_transitions.size());
237
238 double TemperatureMax;
239
240 double error;
241
242 double strength;
243
244 double factor;
245
246 gsl_integration_workspace *w = gsl_integration_workspace_alloc(MaxSteps);
247
248 gsl_function F;
249
250 if (!_approximate) {
251
252 F.function = PAHEmissionModel::featureStrength;
253 } else {
254
255 F.function = PAHEmissionModel::approximateFeatureStrength;
256 }
257
258 int j = 0;
259
260 for (auto &transition : _transitions) {
261
262 std::vector<std::pair<double, double>> transitions(transition.cbegin(),
263 transition.cend());
264
265 if (!_approximate) {
266
267 factor = 1.0;
268
269 F.params = &transitions;
270 } else {
271
272 _nc = static_cast<double>(_carbons.at(j));
273
274 factor = 2.48534271218563e-23 * _nc /
275 std::accumulate(
276 transitions.begin(), transitions.end(), 0.0,
277 [](const auto &a, const auto &b) { return a + b.second; });
278
279 F.params = &_charges.at(j++);
280 }
281
282 TemperatureMax = solveInitialTemperature(energy, transitions);
283
284 temperatures.push_back(TemperatureMax);
285
286 for (auto &t : transition) {
287
288 if (t.second == 0.0) {
289
290 continue;
291 }
292
293 PAHEmissionModel::_frequency = t.first;
294
295 gsl_integration_qag(&F, TemperatureMin, TemperatureMax, 0,
296 IntegrationAccuracy, MaxSteps, GSL_INTEG_GAUSS61, w,
297 &strength, &error);
298
299 t.second *= factor * pow(t.first, 3) * strength;
300 }
301 }
302
303 gsl_integration_workspace_free(w);
304}
305
306double PAHEmissionModel::featureStrength(double temperature,
307 void *transitions_p) {
308
309 std::vector<double> sum;
310
311 std::vector<std::pair<double, double>> *transitions =
312 static_cast<std::vector<std::pair<double, double>> *>(transitions_p);
313
314 sum.reserve(transitions->size());
315
316 double val;
317
318 for (auto &t : *transitions) {
319
320 val = PlanckConstant * SpeedOfLight * t.first /
321 (BoltzmannConstant * temperature);
322
323 sum.push_back(t.second * pow(t.first, 3) / (exp(val) - 1.0));
324 }
325
326 val = PlanckConstant * SpeedOfLight * _frequency /
327 (BoltzmannConstant * temperature);
328
329 return ((PAHEmissionModel::heatCapacity(temperature, transitions_p) /
330 (exp(val) - 1.0)) *
331 (1.0 / (std::accumulate(sum.begin(), sum.end(), 0.0))));
332}
333
334double PAHEmissionModel::approximateFeatureStrength(double temperature,
335 void *charge) {
336
337 double a = 0.0, b = 0.0;
338
339 if (*static_cast<int *>(charge) != 0) {
340 if (temperature > 1000.0) {
341 a = 4.8e-4;
342 b = 1.6119;
343 } else if (temperature > 300.0 && temperature <= 1000.0) {
344 a = 6.38e-7;
345 b = 2.5556;
346 } else if (temperature > 100.0 && temperature <= 300.0) {
347 a = 1.69e-12;
348 b = 4.7687;
349 } else if (temperature > 40.0 && temperature <= 100.0) {
350 a = 7.70e-9;
351 b = 2.9244;
352 } else if (temperature > 20.0 && temperature <= 40.0) {
353 a = 3.47e-12;
354 b = 5.0428;
355 } else if (temperature > 2.7 && temperature <= 20.0) {
356 a = 4.47e-19;
357 b = 10.3870;
358 }
359 } else {
360 if (temperature > 270.0) {
361 a = 5.52e-7;
362 b = 2.5270;
363 } else if (temperature > 200.0 && temperature <= 270.0) {
364 a = 1.70e-9;
365 b = 3.5607;
366 } else if (temperature > 60.0 && temperature <= 200.0) {
367 a = 1.35e-11;
368 b = 4.4800;
369 } else if (temperature > 30.0 && temperature <= 60.0) {
370 a = 4.18e-8;
371 b = 2.5217;
372 } else if (temperature > 2.7 && temperature <= 30.0) {
373 a = 1.88e-16;
374 b = 8.1860;
375 }
376 }
377
378 double val = 1.4387751297850830401 * _frequency / temperature;
379
380 if (a <= 0.0 || val > log(DBL_MAX)) {
381
382 return 0.0;
383 }
384
385 return 1.0 / ((exp(val) - 1.0) * a * pow(temperature, b));
386}
387
388double PAHEmissionModel::solveInitialTemperature(
389 double energy, std::vector<std::pair<double, double>> &transitions) {
390
391 PAHEmissionModel::_energy = energy;
392
393 size_t niterations = 0;
394
395 int status = 0;
396
397 const gsl_root_fsolver_type *T;
398
399 gsl_root_fsolver *s;
400
401 double temperature = 0;
402
403 gsl_function F;
404
405 if (!_approximate) {
406
407 F.function = PAHEmissionModel::solveInitialTemperatureFunc;
408
409 F.params = &transitions;
410 } else {
411
412 F.function = PAHEmissionModel::solveApproximateInitialTemperatureFunc;
413
414 F.params = &_nc;
415 }
416
417 T = gsl_root_fsolver_brent;
418
419 s = gsl_root_fsolver_alloc(T);
420
421 double min = TemperatureMin;
422
423 double max = TemperatureMax;
424
425 gsl_root_fsolver_set(s, &F, min, max);
426
427 do {
428
429 ++niterations;
430
431 status = gsl_root_fsolver_iterate(s);
432
433 temperature = gsl_root_fsolver_root(s);
434
435 min = gsl_root_fsolver_x_lower(s);
436
437 max = gsl_root_fsolver_x_upper(s);
438
439 status = gsl_root_test_interval(min, max, 0, RootAccuracy);
440 } while (status == GSL_CONTINUE && niterations < MaxIterations);
441
442 gsl_root_fsolver_free(s);
443
444 return (temperature);
445}
446
447inline double PAHEmissionModel::solveInitialTemperatureFunc(double temperature,
448 void *transitions) {
449 return integralOverHeatCapacity(temperature, transitions) - _energy;
450}
451
452inline double
453PAHEmissionModel::solveApproximateInitialTemperatureFunc(double temperature,
454 void *nc) {
455
456 return *static_cast<double *>(nc) *
457 (7.54267e-11 * erf(-4.989231 + 0.41778 * log(temperature)) +
458 7.542670e-11) -
459 _energy;
460}
461
462double PAHEmissionModel::integralOverHeatCapacity(double temperature,
463 void *transitions) {
464
465 gsl_integration_workspace *w = gsl_integration_workspace_alloc(MaxSteps);
466
467 double energy;
468
469 double error;
470
471 gsl_function F;
472
473 F.function = PAHEmissionModel::heatCapacity;
474
475 F.params = transitions;
476
477 gsl_integration_qag(&F, TemperatureMin, temperature, 0, IntegrationAccuracy,
478 MaxSteps, GSL_INTEG_GAUSS61, w, &energy, &error);
479
480 gsl_integration_workspace_free(w);
481
482 return (energy);
483}
484
485double PAHEmissionModel::heatCapacity(double temperature, void *transitions_p) {
486
487 double heatcapacity = 0.0;
488
489 double value;
490
491 std::vector<std::pair<double, double>> *transitions =
492 static_cast<std::vector<std::pair<double, double>> *>(transitions_p);
493
494 for (const auto &t : *transitions) {
495
496 value = PlanckConstant * SpeedOfLight * t.first /
497 (BoltzmannConstant * temperature);
498
499 heatcapacity +=
500 BoltzmannConstant * exp(-value) * pow((value / (1.0 - exp(-value))), 2);
501 }
502
503 return (heatcapacity);
504}
505
506void PAHEmissionModel::shiftTransitions(double shift) {
507
508 for (auto &transition : _transitions) {
509
510 for (auto &t : transition) {
511
512 t.first += shift;
513 }
514 }
515}
516
517void PAHEmissionModel::convertFromFrequencyToWavelength(
518 std::vector<double> &grid) {
519
520 for (auto &g : grid) {
521
522 g = 1e4 / g;
523 }
524}
525
526void PAHEmissionModel::convertFromFrequencyToWavelength(
527 std::array<double, 2> &grid) {
528
529 for (auto &g : grid) {
530
531 g = 1e4 / g;
532 }
533}
534
535inline void
536PAHEmissionModel::convertFromWavelengthToFrequency(std::vector<double> &grid) {
537
538 convertFromFrequencyToWavelength(grid);
539}
540
541void PAHEmissionModel::convertFromFrequencyToWavelength(
542 std::vector<std::vector<std::pair<double, double>>> &transitions_p) {
543
544 for (auto &transition : transitions_p) {
545
546 for (auto &t : transition) {
547
548 t.first = 1e4 / t.first;
549 }
550 }
551}
552
553inline void PAHEmissionModel::convertFromWavelengthToFrequency(
554 std::vector<std::vector<std::pair<double, double>>> &transitions) {
555 convertFromFrequencyToWavelength(transitions);
556}

Since FY2019 the NASA Ames PAH IR Spectroscopic Database is being supported through a directed Work Package at NASA Ames titled: "Laboratory Astrophysics - The NASA Ames PAH IR Spectroscopic Database".
Since FY2023 the NASA Ames PAH IR Spectroscopic Database is being supported through the Laboratory Astrophysics Rd 2 directed Work Package at NASA Ames.
© Copyright 2021-2025, Christiaan Boersma