sln.h (17012B)
1 /* Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com) 2 * Copyright (C) 2026 Université de Lorraine 3 * Copyright (C) 2022 Centre National de la Recherche Scientifique 4 * Copyright (C) 2022 Université Paul Sabatier 5 * 6 * This file is part of Star-Line. 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 20 21 #ifndef SLN_H 22 #define SLN_H 23 24 #include <star/shtr.h> 25 #include <rsys/rsys.h> 26 27 #include <float.h> 28 #include <math.h> 29 30 /* Library symbol management */ 31 #if defined(SLN_SHARED_BUILD) /* Build shared library */ 32 #define SLN_API extern EXPORT_SYM 33 #elif defined(SLN_STATIC) /* Use/build static library */ 34 #define SLN_API extern LOCAL_SYM 35 #else 36 #define SLN_API extern IMPORT_SYM 37 #endif 38 39 /* Helper macro that asserts if the invocation of the sln function `Func' 40 * returns an error. One should use this macro on sln calls for which no 41 * explicit error checking is performed */ 42 #ifndef NDEBUG 43 #define SLN(Func) ASSERT(sln_ ## Func == RES_OK) 44 #else 45 #define SLN(Func) sln_ ## Func 46 #endif 47 48 #define SLN_TREE_DEPTH_MAX 64 /* Maximum depth of a tree */ 49 #define SLN_TREE_ARITY_MAX 256 /* Maximum arity of a tree */ 50 #define SLN_LEAF_NLINES_MAX 16384 /* Maximum number of lines per leaf */ 51 52 /* Forward declaration of external data structures */ 53 struct logger; 54 struct mem_allocator; 55 struct shtr; 56 struct shtr_line; 57 struct shtr_isotope_metadata; 58 struct shtr_line_list; 59 60 enum sln_mesh_type { 61 SLN_MESH_FIT, /* Fit the spectrum */ 62 SLN_MESH_UPPER, /* Upper limit of the spectrum */ 63 SLN_MESH_TYPES_COUNT__ 64 }; 65 66 enum sln_line_profile { 67 SLN_LINE_PROFILE_VOIGT, 68 SLN_LINE_PROFILES_COUNT__ 69 }; 70 71 struct sln_device_create_args { 72 struct logger* logger; /* May be NULL <=> default logger */ 73 struct mem_allocator* allocator; /* NULL <=> use default allocator */ 74 int verbose; /* Verbosity level */ 75 }; 76 #define SLN_DEVICE_CREATE_ARGS_DEFAULT__ {NULL,NULL,0} 77 static const struct sln_device_create_args SLN_DEVICE_CREATE_ARGS_DEFAULT = 78 SLN_DEVICE_CREATE_ARGS_DEFAULT__; 79 80 struct sln_isotope { 81 double abundance; /* in [0, 1] */ 82 int id; /* Identifier of the isotope */ 83 }; 84 85 struct sln_molecule { 86 struct sln_isotope isotopes[SHTR_MAX_ISOTOPE_COUNT]; 87 double concentration; 88 double cutoff; /* [cm^-1] */ 89 int non_default_isotope_abundances; 90 }; 91 #define SLN_MOLECULE_NULL__ {{{0}},0,0,0} 92 static const struct sln_molecule SLN_MOLECULE_NULL = SLN_MOLECULE_NULL__; 93 94 struct sln_tree_create_args { 95 /* Isotope metadata and list of spectral lines */ 96 struct shtr_isotope_metadata* metadata; 97 struct shtr_line_list* lines; 98 99 enum sln_line_profile line_profile; 100 /* Mixture description */ 101 struct sln_molecule molecules[SHTR_MAX_MOLECULE_COUNT]; 102 103 /* Thermo dynamic properties */ 104 double pressure; /* [atm] */ 105 double temperature; /* [K] */ 106 107 /* Hint on the number of vertices around the line center */ 108 size_t nvertices_hint; 109 110 /* Relative error used to simplify the spectrum mesh. The larger it is, the 111 * coarser the mesh */ 112 double mesh_decimation_err; /* > 0 */ 113 enum sln_mesh_type mesh_type; /* Type of mesh to generate */ 114 115 /* Maximum number of children per node */ 116 unsigned arity; 117 118 /* Maximum number of lines per leaf */ 119 unsigned leaf_nlines; 120 121 /* When this option is enabled, the polylines of internal nodes are 122 * constructed by merging their children's polylines in pairs (and then 123 * simplifying the result), and repeating the process until only a single 124 * polyline remains, which becomes the internal node's polyline. 125 * 126 * If this option is disabled, all child polylines are merged in a single step 127 * before being simplified. 128 * 129 * Enabling this option only makes sense for trees with an arity greater than 130 * two. For a binary tree, both methods should produce exactly the same tree, 131 * down to the bit */ 132 int collapse_polylines; 133 134 /* Advice on the number of threads to use */ 135 unsigned nthreads_hint; 136 }; 137 #define SLN_TREE_CREATE_ARGS_DEFAULT__ { \ 138 NULL, /* metadata */ \ 139 NULL, /* line list */ \ 140 SLN_LINE_PROFILE_VOIGT, /* Profile */ \ 141 {SLN_MOLECULE_NULL__}, /* Molecules */ \ 142 0, /* Pressure [atm] */ \ 143 0, /* Temperature [K] */ \ 144 16, /* #vertices hint */ \ 145 0.01f, /* Mesh decimation error */ \ 146 SLN_MESH_UPPER, /* Mesh type */ \ 147 2, /* Arity */ \ 148 1, /* Number of lines per leaf */ \ 149 0, /* Collapse polylines */ \ 150 (unsigned)(-1), /* #threads hint */ \ 151 } 152 static const struct sln_tree_create_args SLN_TREE_CREATE_ARGS_DEFAULT = 153 SLN_TREE_CREATE_ARGS_DEFAULT__; 154 155 struct sln_tree_read_args { 156 /* Metadata and list of spectral lines from which the tree was constructed */ 157 struct shtr_isotope_metadata* metadata; 158 struct shtr_line_list* lines; 159 160 /* Name of the file to read or of the provided stream. 161 * NULL <=> uses a default name for the stream to be read, which must 162 * therefore be defined. */ 163 const char* filename; /* Name of the file to read */ 164 FILE* file; /* Stream from where data are read. NULL <=> read from file */ 165 166 /* Verify that the digital signature of the input lines matches the one stored 167 * in the tree. In other words, ensure that this list of lines is indeed the 168 * one used to construct the tree. An error is returned if the signatures do 169 * not match. 170 * 171 * Although it is always advisable to verify that the data matches what is 172 * expected, calculating the signatures of the lines can be time-consuming. 173 * Therefore, a user who is _certain_ that the data matches can disable this 174 * verification */ 175 int disable_line_hash_check; 176 }; 177 #define SLN_TREE_READ_ARGS_NULL__ {NULL,NULL,NULL,NULL,0} 178 static const struct sln_tree_read_args SLN_TREE_READ_ARGS_NULL = 179 SLN_TREE_READ_ARGS_NULL__; 180 181 struct sln_tree_write_args { 182 /* Name of the file in which the tree is serialized. 183 * NULL <=> uses a default name for the stream to be written, which must 184 * therefore be defined. */ 185 const char* filename; /* Name of the file to read */ 186 187 /* Stream where data is written. 188 * NULL <=> write to the file defined by "filename" */ 189 FILE* file; 190 }; 191 #define SLN_TREE_WRITE_ARGS_NULL__ {NULL,NULL} 192 static const struct sln_tree_write_args SLN_TREE_WRITE_ARGS_NULL = 193 SLN_TREE_WRITE_ARGS_NULL__; 194 195 struct sln_tree_desc { 196 double mesh_decimation_err; 197 enum sln_mesh_type mesh_type; 198 enum sln_line_profile line_profile; 199 200 double pressure; /* [atm] */ 201 double temperature; /* [K] */ 202 203 unsigned depth; /* #edges from the root to the deepest leaf */ 204 size_t nlines; 205 size_t nvertices; 206 size_t nnodes; 207 unsigned arity; 208 unsigned leaf_nlines; 209 }; 210 #define SLN_TREE_DESC_NULL__ { \ 211 0,SLN_MESH_TYPES_COUNT__,SLN_LINE_PROFILES_COUNT__,0,0,0,0,0,0,0,0 \ 212 } 213 static const struct sln_tree_desc SLN_TREE_DESC_NULL = SLN_TREE_DESC_NULL__; 214 215 struct sln_thermo_props { 216 double concentrations[SHTR_MAX_MOLECULE_COUNT]; 217 double pressure; /* [atm] */ 218 double temperature; /* [K] */ 219 }; 220 #define SLN_THERMO_PROPS_NULL__ {{0},0,0} 221 static const struct sln_thermo_props SLN_THERMO_PROPS_NULL = 222 SLN_THERMO_PROPS_NULL__; 223 224 struct sln_node_desc { 225 /* Range of lines belonging to the node. The endpoints are included */ 226 size_t ilines[2]; 227 size_t nvertices; 228 unsigned nchildren; 229 }; 230 #define SLN_NODE_DESC_NULL__ {{0,0},0,0} 231 static const struct sln_node_desc SLN_NODE_DESC_NULL = SLN_NODE_DESC_NULL__; 232 233 struct sln_vertex { /* 8 Bytes */ 234 float wavenumber; /* in cm^-1 */ 235 float ka; 236 }; 237 #define SLN_VERTEX_NULL__ {0,0} 238 static const struct sln_vertex SLN_VERTEX_NULL = SLN_VERTEX_NULL__; 239 240 struct sln_mesh { 241 const struct sln_vertex* vertices; 242 size_t nvertices; 243 }; 244 #define SLN_MESH_NULL__ {NULL,0} 245 static const struct sln_mesh SLN_MESH_NULL = SLN_MESH_NULL__; 246 247 struct sln_mixture_load_args { 248 const char* filename; /* Name of the file to load or of the provided stream */ 249 FILE* file; /* Stream from where data are loaded. NULL <=> load from file */ 250 251 /* Metadata from which the mix is defined */ 252 struct shtr_isotope_metadata* molparam; 253 }; 254 #define SLN_MIXTURE_LOAD_ARGS_NULL__ {NULL,NULL,NULL} 255 static const struct sln_mixture_load_args SLN_MIXTURE_LOAD_ARGS_NULL = 256 SLN_MIXTURE_LOAD_ARGS_NULL__; 257 258 struct sln_line { 259 double wavenumber; /* Line center wrt pressure in cm^-1 */ 260 double profile_factor; /* m^-1.cm^-1 (1e2*density*intensity) */ 261 double gamma_d; /* Doppler half width */ 262 double gamma_l; /* Lorentz half width */ 263 enum shtr_molecule_id molecule_id; 264 }; 265 #define SLN_LINE_NULL__ {0,0,0,0,SHTR_MOLECULE_ID_NULL} 266 static const struct sln_line SLN_LINE_NULL = SLN_LINE_NULL__; 267 268 /* External data structure */ 269 struct ssp_rng; 270 271 /* Forward declarations of opaque data structures */ 272 struct sln_device; 273 struct sln_mixture; 274 struct sln_node; 275 struct sln_tree; 276 277 BEGIN_DECLS 278 279 /******************************************************************************* 280 * Device API 281 ******************************************************************************/ 282 SLN_API res_T 283 sln_device_create 284 (const struct sln_device_create_args* args, 285 struct sln_device** sln); 286 287 SLN_API res_T 288 sln_device_ref_get 289 (struct sln_device* sln); 290 291 SLN_API res_T 292 sln_device_ref_put 293 (struct sln_device* sln); 294 295 296 /******************************************************************************* 297 * Mixture API 298 ******************************************************************************/ 299 SLN_API res_T 300 sln_mixture_load 301 (struct sln_device* dev, 302 const struct sln_mixture_load_args* args, 303 struct sln_mixture** mixture); 304 305 SLN_API res_T 306 sln_mixture_ref_get 307 (struct sln_mixture* mixture); 308 309 SLN_API res_T 310 sln_mixture_ref_put 311 (struct sln_mixture* mixture); 312 313 SLN_API int 314 sln_mixture_get_molecule_count 315 (const struct sln_mixture* mixture); 316 317 SLN_API enum shtr_molecule_id 318 sln_mixture_get_molecule_id 319 (const struct sln_mixture* mixture, 320 const int index); 321 322 SLN_API res_T 323 sln_mixture_get_molecule 324 (const struct sln_mixture* mixture, 325 const int index, 326 struct sln_molecule* molecule); 327 328 /******************************************************************************* 329 * Tree API 330 ******************************************************************************/ 331 SLN_API res_T 332 sln_tree_create 333 (struct sln_device* dev, 334 const struct sln_tree_create_args* args, 335 struct sln_tree** tree); 336 337 /* Read a tree serialized with the "sln_tree_write" function */ 338 SLN_API res_T 339 sln_tree_read 340 (struct sln_device* sln, 341 const struct sln_tree_read_args* args, 342 struct sln_tree** tree); 343 344 SLN_API res_T 345 sln_tree_ref_get 346 (struct sln_tree* tree); 347 348 SLN_API res_T 349 sln_tree_ref_put 350 (struct sln_tree* tree); 351 352 SLN_API res_T 353 sln_tree_get_desc 354 (const struct sln_tree* tree, 355 struct sln_tree_desc* desc); 356 357 SLN_API const struct sln_node* /* NULL <=> No node */ 358 sln_tree_get_root 359 (const struct sln_tree* tree); 360 361 SLN_API res_T 362 sln_tree_get_line 363 (const struct sln_tree* tree, 364 const size_t iline, 365 /* Thermodynamic properties to which the line is recovered. 366 * Can be NULL, so these properties are those used to build the tree */ 367 const struct sln_thermo_props* props, 368 struct sln_line* line); 369 370 SLN_API res_T 371 sln_tree_write 372 (const struct sln_tree* tree, 373 const struct sln_tree_write_args* args); 374 375 /******************************************************************************* 376 * Node API 377 ******************************************************************************/ 378 SLN_API int 379 sln_node_is_leaf 380 (const struct sln_node* node); 381 382 SLN_API unsigned 383 sln_node_get_child_count 384 (const struct sln_tree* tree, 385 const struct sln_node* node); 386 387 /* The node must not be a leaf */ 388 SLN_API const struct sln_node* 389 sln_node_get_child 390 (const struct sln_tree* tree, 391 const struct sln_node* node, 392 const unsigned ichild); /* 0 or #children */ 393 394 SLN_API double 395 sln_node_eval 396 (const struct sln_tree* tree, 397 const struct sln_node* node, 398 /* Thermodynamic properties to which the node lines are evaluated. 399 * Can be NULL, so these properties are those used to build the tree */ 400 const struct sln_thermo_props* props, 401 const double wavenumber); /* In cm^-1 */ 402 403 SLN_API res_T 404 sln_node_get_desc 405 (const struct sln_tree* tree, 406 const struct sln_node* node, 407 struct sln_node_desc* desc); 408 409 SLN_API res_T 410 sln_node_get_mesh 411 (const struct sln_tree* tree, 412 const struct sln_node* node, 413 struct sln_mesh* mesh); 414 415 /* Sample a leaf based on its importance, that is, its contribution to the 416 * node's absorption spectrum at the given wave number */ 417 SLN_API const struct sln_node* /* NULL <=> an error occurs */ 418 sln_node_sample_leaf 419 (const struct sln_tree* tree, 420 const struct sln_node* node, 421 const double nu, /* [cm^-1] */ 422 struct ssp_rng* rng, 423 double* proba); /* May be NULL */ 424 425 /******************************************************************************* 426 * Miscellaneous 427 ******************************************************************************/ 428 SLN_API double 429 sln_line_eval 430 (const struct sln_tree* tree, 431 const struct sln_line* line, 432 const double wavenumber); /* In cm^-1 */ 433 434 SLN_API double 435 sln_mesh_eval 436 (const struct sln_mesh* mesh, 437 const double wavenumber); /* In cm^-1 */ 438 439 /******************************************************************************* 440 * Helper functions 441 ******************************************************************************/ 442 /* Purpose: to calculate the Faddeeva function with relative error less than 443 * 10^(-4). 444 * 445 * Inputs: x and y, parameters for the Voigt function : 446 * - x is defined as x=(nu-nu_c)/gamma_D*sqrt(ln(2)) with nu the current 447 * wavenumber, nu_c the wavenumber at line center, gamma_D the Doppler 448 * linewidth. 449 * - y is defined as y=gamma_L/gamma_D*sqrt(ln(2)) with gamma_L the Lorentz 450 * linewith and gamma_D the Doppler linewidth 451 * 452 * Output: k, the Voigt function; it has to be multiplied by 453 * sqrt(ln(2)/pi)*1/gamma_D so that the result may be interpretable in terms of 454 * line profile. 455 * 456 * TODO check the copyright */ 457 SLN_API double 458 sln_faddeeva 459 (const double x, 460 const double y); 461 462 static INLINE double 463 sln_compute_line_half_width_doppler 464 (const double nu, /* Line center wrt pressure in cm^-1 */ /* TODO check this */ 465 const double molar_mass, /* In kg.mol^-1 */ 466 const double temperature) /* In K */ 467 { 468 /* kb = 1.3806e-23 469 * Na = 6.02214076e23 470 * c = 299792458 471 * sqrt(2*log(2)*kb*Na)/c */ 472 const double sqrt_two_ln2_kb_Na_over_c = 1.1324431552553545042e-08; 473 const double gamma_d = nu * sqrt_two_ln2_kb_Na_over_c * sqrt(temperature/molar_mass); 474 ASSERT(temperature >= 0 && molar_mass > 0); 475 return gamma_d; 476 } 477 478 static INLINE double 479 sln_compute_line_half_width_lorentz 480 (const double gamma_air, /* Air broadening half width [cm^-1.atm^-1] */ 481 const double gamma_self, /* Air broadening half width [cm^-1.atm^-1] */ 482 const double temperature, /* [K] */ 483 const double pressure, /* [atm^-1] */ 484 const double n_air, 485 const double concentration) 486 { 487 const double TREF=296; /* Ref temperature [K] for HITRAN/HITEMP database */ 488 const double Ps = pressure * concentration; 489 const double n_self = n_air; /* In HITRAN n_air == n_self */ 490 const double gamma_l = 491 pow(TREF/temperature, n_air) * (pressure - Ps) * gamma_air 492 + pow(TREF/temperature, n_self) * Ps * gamma_self; 493 ASSERT(gamma_air > 0 && gamma_self > 0); 494 ASSERT(pressure > 0 && concentration >= 0 && concentration <= 1); 495 496 return gamma_l; 497 } 498 499 static INLINE double 500 sln_compute_voigt_profile 501 (const double wavenumber, /* In cm^-1 */ 502 const double nu, /* Line center in cm^-1 */ 503 const double gamma_d, /* Doppler line half width in cm^-1 */ 504 const double gamma_l) /* Lorentz line half width in cm^-1 */ 505 { 506 /* Constants */ 507 const double sqrt_ln2 = 0.83255461115769768821; /* sqrt(log(2)) */ 508 const double sqrt_ln2_over_pi = 0.46971863934982566180; /* sqrt(log(2)/M_PI) */ 509 const double sqrt_ln2_over_gamma_d = sqrt_ln2 / gamma_d; 510 511 const double x = (wavenumber - nu) * sqrt_ln2_over_gamma_d; 512 const double y = gamma_l * sqrt_ln2_over_gamma_d; 513 const double k = sln_faddeeva(x, y); 514 return k*sqrt_ln2_over_pi/gamma_d; 515 } 516 517 static INLINE const char* 518 sln_mesh_type_cstr(const enum sln_mesh_type type) 519 { 520 const char* cstr = NULL; 521 522 switch(type) { 523 case SLN_MESH_FIT: cstr = "fit"; break; 524 case SLN_MESH_UPPER: cstr = "upper"; break; 525 default: FATAL("Unreachable code\n"); break; 526 } 527 return cstr; 528 } 529 530 static INLINE const char* 531 sln_line_profile_cstr(const enum sln_line_profile profile) 532 { 533 const char* cstr = NULL; 534 535 switch(profile) { 536 case SLN_LINE_PROFILE_VOIGT: cstr = "voigt"; break; 537 default: FATAL("Unreachable code\n"); break; 538 } 539 return cstr; 540 } 541 542 END_DECLS 543 544 #endif /* SLN_H */