sln_tree.c (30212B)
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 #include "sln.h" 22 #include "sln_device_c.h" 23 #include "sln_line.h" 24 #include "sln_tree_c.h" 25 26 #include <star/shtr.h> 27 #include <star/ssp.h> 28 29 #include <rsys/algorithm.h> 30 #include <rsys/cstr.h> 31 #include <rsys/math.h> 32 33 #include <omp.h> 34 35 struct stream { 36 const char* name; 37 FILE* fp; 38 int intern_fp; /* Define if the stream was internally opened */ 39 }; 40 static const struct stream STREAM_NULL = {NULL, NULL, 0}; 41 42 /******************************************************************************* 43 * Helper functions 44 ******************************************************************************/ 45 static INLINE res_T 46 check_molecule_concentration 47 (const struct sln_device* sln, 48 const char* caller, 49 const enum shtr_molecule_id molecule_id, 50 const double concentration) 51 { 52 ASSERT(sln && caller); 53 54 if(concentration == 0) { 55 /* A molecular concentration of zero is allowed, but may be a user error, 56 * as 0 is the default concentration in the tree creation arguments. 57 * Therefore, warn the user about this value so that they can determine 58 * whether or not it is an error on their part. */ 59 WARN(sln, "%s: the concentration of %s is zero.\n", 60 caller, shtr_molecule_cstr(molecule_id)); 61 62 } else if(concentration < 0) { 63 /* Concentration cannot be negative... */ 64 ERROR(sln, "%s: invalid %s concentration: %g.\n", 65 FUNC_NAME, shtr_molecule_cstr(molecule_id), 66 concentration); 67 return RES_BAD_ARG; 68 } 69 70 return RES_OK; 71 } 72 73 static res_T 74 check_concentrations_list 75 (const struct sln_device* sln, 76 const char* caller, 77 const double concentrations[SHTR_MAX_MOLECULE_COUNT]) 78 { 79 double sum = 0; 80 int i = 0; 81 res_T res = RES_OK; 82 ASSERT(sln && caller && concentrations); 83 84 FOR_EACH(i, 0, SHTR_MAX_MOLECULE_COUNT) { 85 if(i == SHTR_MOLECULE_ID_NULL) continue; 86 87 res = check_molecule_concentration(sln, caller, i, concentrations[i]); 88 if(res != RES_OK) goto error; 89 90 sum += concentrations[i]; 91 } 92 93 /* The sum of molecular concentrations must be less than or equal to 1. It may 94 * be less than 1 if the remaining part of the mixture is (implicitly) defined 95 * as a radiatively inactive gas */ 96 if(sum > 1 && sum-1 > 1e-6) { 97 ERROR(sln, 98 "%s: the sum of molecule concentrations is greater than 1: %g\n", 99 caller, sum); 100 res = RES_BAD_ARG; 101 goto error; 102 } 103 104 exit: 105 return res; 106 error: 107 goto exit; 108 } 109 110 /* Check the consistency of the molecular concentrations */ 111 static INLINE res_T 112 check_mixture_concentrations 113 (struct sln_device* sln, 114 const char* caller, 115 const struct sln_tree_create_args* args) 116 { 117 double concentrations[SHTR_MAX_MOLECULE_COUNT] = {0}; 118 int i = 0; 119 ASSERT(sln && caller && args); 120 121 FOR_EACH(i, 0, SHTR_MAX_MOLECULE_COUNT) { 122 concentrations[i] = args->molecules[i].concentration; 123 } 124 125 return check_concentrations_list(sln, caller, concentrations); 126 } 127 128 /* Verify that the isotope abundance are valids */ 129 static res_T 130 check_molecule_isotope_abundances 131 (struct sln_device* sln, 132 const char* caller, 133 const struct sln_molecule* molecule) 134 { 135 int i = 0; 136 double sum = 0; 137 ASSERT(sln && caller && molecule); 138 139 /* The isotopic abundances are the default ones. Nothing to do */ 140 if(!molecule->non_default_isotope_abundances) return RES_OK; 141 142 /* The isotopic abundances are not the default ones. 143 * Verify that they are valid ... */ 144 FOR_EACH(i, 0, SHTR_MAX_ISOTOPE_COUNT) { 145 if(molecule->isotopes[i].abundance < 0) { 146 const int isotope_id = i + 1; /* isotope id in [1, 10] */ 147 ERROR(sln, "%s: invalid abundance of isotopie %d of %s: %g.\n", 148 caller, isotope_id, shtr_molecule_cstr(i), 149 molecule->isotopes[i].abundance); 150 return RES_BAD_ARG; 151 } 152 153 sum += molecule->isotopes[i].abundance; 154 } 155 156 /* ... and that their sum equals 1 */ 157 if(!eq_eps(sum, 1, 1e-6)) { 158 ERROR(sln, "%s: the %s isotope abundances does not sum to 1: %g.\n", 159 caller, shtr_molecule_cstr(i), sum); 160 return RES_BAD_ARG; 161 } 162 163 return RES_OK; 164 } 165 166 static res_T 167 check_molecules 168 (struct sln_device* sln, 169 const char* caller, 170 const struct sln_tree_create_args* args) 171 { 172 char molecule_ok[SHTR_MAX_MOLECULE_COUNT] = {0}; 173 174 size_t iline = 0; 175 size_t nlines = 0; 176 res_T res = RES_OK; 177 ASSERT(args->lines); 178 179 res = check_mixture_concentrations(sln, caller, args); 180 if(res != RES_OK) goto error; 181 182 /* Iterate over the lines to define which molecules has to be checked, i.e., 183 * the ones used in the mixture */ 184 SHTR(line_list_get_size(args->lines, &nlines)); 185 FOR_EACH(iline, 0, nlines) { 186 struct shtr_line line = SHTR_LINE_NULL; 187 const struct sln_molecule* molecule = NULL; 188 189 SHTR(line_list_at(args->lines, iline, &line)); 190 191 /* This molecule was already checked */ 192 if(molecule_ok[line.molecule_id]) continue; 193 194 molecule = args->molecules + line.molecule_id; 195 196 if(molecule->cutoff <= 0) { 197 /* ... cutoff either */ 198 ERROR(sln, "%s: invalid %s cutoff: %g.\n", 199 caller, shtr_molecule_cstr(line.molecule_id), molecule->cutoff); 200 return RES_BAD_ARG; 201 } 202 203 res = check_molecule_isotope_abundances(sln, caller, molecule); 204 if(res != RES_OK) goto error; 205 206 molecule_ok[line.molecule_id] = 1; 207 } 208 209 exit: 210 return res; 211 error: 212 goto exit; 213 } 214 215 static INLINE res_T 216 check_pressure 217 (const struct sln_device* sln, 218 const char* caller, 219 const double pressure /*[atm]*/) 220 { 221 if(pressure < 0) { 222 ERROR(sln, "%s: invalid negative pressure %g atm\n", caller, pressure); 223 return RES_BAD_ARG; 224 } 225 return RES_OK; 226 } 227 228 static INLINE res_T 229 check_temperature 230 (const struct sln_device* sln, 231 const char* caller, 232 const double temperature /*[K]*/) 233 { 234 if(temperature < 0) { 235 ERROR(sln, "%s: invalid negative temperature %g K\n", caller, temperature); 236 return RES_BAD_ARG; 237 } 238 return RES_OK; 239 } 240 241 static res_T 242 check_sln_tree_create_args 243 (struct sln_device* sln, 244 const char* caller, 245 const struct sln_tree_create_args* args) 246 { 247 res_T res = RES_OK; 248 ASSERT(sln && caller); 249 250 if(!args) return RES_BAD_ARG; 251 252 if(!args->metadata) { 253 ERROR(sln, "%s: the isotope metadata are missing.\n", caller); 254 return RES_BAD_ARG; 255 } 256 257 if(!args->lines) { 258 ERROR(sln, "%s: the list of lines is missing.\n", caller); 259 return RES_BAD_ARG; 260 } 261 262 if((res = check_pressure(sln, caller, args->pressure)) != RES_OK) { 263 return res; 264 } 265 266 if((res = check_temperature(sln, caller, args->temperature)) != RES_OK) { 267 return res; 268 } 269 270 if(args->nvertices_hint == 0) { 271 ERROR(sln, 272 "%s: invalid hint on the number of vertices around the line center %lu.\n", 273 caller, (unsigned long)args->nvertices_hint); 274 return RES_BAD_ARG; 275 } 276 277 if(args->mesh_decimation_err < 0) { 278 ERROR(sln, "%s: invalid decimation error %g.\n", 279 caller, args->mesh_decimation_err); 280 return RES_BAD_ARG; 281 } 282 283 if((unsigned)args->mesh_type >= SLN_MESH_TYPES_COUNT__) { 284 ERROR(sln, "%s: invalid mesh type %d.\n", caller, args->mesh_type); 285 return RES_BAD_ARG; 286 } 287 288 if((unsigned)args->line_profile >= SLN_LINE_PROFILES_COUNT__) { 289 ERROR(sln, "%s: invalid line profile %d.\n", caller, args->line_profile); 290 return RES_BAD_ARG; 291 } 292 293 if(args->arity < 2 || args->arity > SLN_TREE_ARITY_MAX) { 294 ERROR(sln, "%s: invalid arity %u. It must be in [2, %d]\n", 295 caller, args->arity, SLN_TREE_ARITY_MAX); 296 return RES_BAD_ARG; 297 } 298 299 if(args->leaf_nlines < 1 || args->leaf_nlines > SLN_LEAF_NLINES_MAX) { 300 ERROR(sln, "%s: invalid number of lines per leaf %u. It must be in [1, %d]\n", 301 caller, args->leaf_nlines, SLN_LEAF_NLINES_MAX); 302 return RES_BAD_ARG; 303 } 304 305 if(args->nthreads_hint == 0) { 306 ERROR(sln, "%s: invalid number of threads %u\n", 307 caller, args->nthreads_hint); 308 return RES_BAD_ARG; 309 } 310 311 res = check_molecules(sln, caller, args); 312 if(res != RES_OK) return res; 313 314 return RES_OK; 315 } 316 317 static res_T 318 check_sln_tree_read_args 319 (struct sln_device* sln, 320 const char* caller, 321 const struct sln_tree_read_args* args) 322 { 323 if(!args) return RES_BAD_ARG; 324 325 if(!args->metadata) { 326 ERROR(sln, "%s: the isotope metadata are missing.\n", caller); 327 return RES_BAD_ARG; 328 } 329 330 if(!args->lines) { 331 ERROR(sln, "%s: the list of lines is missing.\n", caller); 332 return RES_BAD_ARG; 333 } 334 335 if(!args->file && !args->filename) { 336 ERROR(sln, 337 "%s: the source file is missing. No file name or stream is provided.\n", 338 caller); 339 return RES_BAD_ARG; 340 } 341 342 return RES_OK; 343 } 344 345 static res_T 346 check_sln_tree_write_args 347 (struct sln_device* sln, 348 const char* caller, 349 const struct sln_tree_write_args* args) 350 { 351 if(!args) return RES_BAD_ARG; 352 353 if(!args->file && !args->filename) { 354 ERROR(sln, 355 "%s: the destination file is missing. " 356 "No file name or stream is provided.\n", 357 caller); 358 return RES_BAD_ARG; 359 } 360 361 return RES_OK; 362 } 363 364 static res_T 365 check_line_thermo_props 366 (const struct sln_tree* tree, 367 const char* caller, 368 const size_t iline, 369 const struct sln_thermo_props* props) 370 { 371 struct shtr_line line = SHTR_LINE_NULL; 372 res_T res = RES_OK; 373 ASSERT(tree && caller); 374 375 if(!props) goto exit; /* Default thermo props */ 376 377 SHTR(line_list_at(tree->args.lines, iline, &line)); 378 379 res = check_molecule_concentration(tree->sln, caller, line.molecule_id, 380 props->concentrations[line.molecule_id]); 381 if(res != RES_OK) goto error; 382 383 res = check_pressure(tree->sln, caller, props->pressure); 384 if(res != RES_OK) goto error; 385 386 res = check_temperature(tree->sln, caller, props->temperature); 387 if(res != RES_OK) goto error; 388 389 exit: 390 return res; 391 error: 392 goto exit; 393 } 394 395 static INLINE res_T 396 check_sln_thermo_props 397 (const struct sln_device* sln, 398 const char* caller, 399 const struct sln_thermo_props* props) 400 { 401 res_T res = RES_OK; 402 ASSERT(sln && caller); 403 404 if(!props) goto exit; /* Default thermo props */ 405 406 res = check_concentrations_list(sln, caller, props->concentrations); 407 if(res != RES_OK) goto error; 408 409 res = check_pressure(sln, caller, props->pressure); 410 if(res != RES_OK) goto error; 411 412 res = check_temperature(sln, caller, props->temperature); 413 if(res != RES_OK) goto error; 414 415 exit: 416 return res; 417 error: 418 goto exit; 419 } 420 421 static INLINE void 422 stream_release(struct stream* stream) 423 { 424 ASSERT(stream); 425 if(stream->intern_fp && stream->fp) CHK(fclose(stream->fp) == 0); 426 } 427 428 static res_T 429 stream_init 430 (struct sln_device* sln, 431 const char* caller, 432 const char* name, /* NULL <=> default stream name */ 433 FILE* fp, /* NULL <=> open file "name" */ 434 const char* mode, /* mode in fopen */ 435 struct stream* stream) 436 { 437 res_T res = RES_OK; 438 439 ASSERT(sln && caller && stream); 440 ASSERT(fp || (name && mode)); 441 442 *stream = STREAM_NULL; 443 444 if(fp) { 445 stream->intern_fp = 0; 446 stream->name = name ? name : "stream"; 447 stream->fp = fp; 448 449 } else { 450 stream->intern_fp = 1; 451 stream->name = name; 452 if(!(stream->fp = fopen(name, mode))) { 453 ERROR(sln, "%s:%s: error opening file -- %s\n", 454 caller, name, strerror(errno)); 455 res = RES_IO_ERR; 456 goto error; 457 } 458 } 459 460 exit: 461 return res; 462 error: 463 if(stream->intern_fp && stream->fp) CHK(fclose(stream->fp) == 0); 464 goto exit; 465 } 466 467 static res_T 468 create_tree 469 (struct sln_device* sln, 470 const char* caller, 471 struct sln_tree** out_tree) 472 { 473 struct sln_tree* tree = NULL; 474 res_T res = RES_OK; 475 ASSERT(sln && caller && out_tree); 476 477 tree = MEM_CALLOC(sln->allocator, 1, sizeof(struct sln_tree)); 478 if(!tree) { 479 ERROR(sln, "%s: could not allocate the tree data structure.\n", 480 caller); 481 res = RES_MEM_ERR; 482 goto error; 483 } 484 ref_init(&tree->ref); 485 SLN(device_ref_get(sln)); 486 tree->sln = sln; 487 darray_node_init(sln->allocator, &tree->nodes); 488 darray_vertex_init(sln->allocator, &tree->vertices); 489 490 exit: 491 *out_tree = tree; 492 return res; 493 error: 494 if(tree) { SLN(tree_ref_put(tree)); tree = NULL; } 495 goto exit; 496 } 497 498 static INLINE int 499 cmp_nu_vtx(const void* key, const void* item) 500 { 501 const float nu = *((const float*)key); 502 const struct sln_vertex* vtx = item; 503 if(nu < vtx->wavenumber) return -1; 504 if(nu > vtx->wavenumber) return +1; 505 return 0; 506 } 507 508 static res_T 509 build_node_cumulative 510 (const struct sln_tree* tree, 511 const struct sln_node* node, 512 const double nu, /* [cm^-1] */ 513 double proba[SLN_TREE_ARITY_MAX], 514 double cumul[SLN_TREE_ARITY_MAX]) 515 { 516 struct sln_mesh mesh = SLN_MESH_NULL; 517 double ka = 0; 518 double sum = 0; 519 unsigned i=0, n=0; 520 res_T res = RES_OK; 521 ASSERT(tree && node && proba && cumul); 522 523 n = sln_node_get_child_count(tree, node); 524 ASSERT(n <= SLN_TREE_ARITY_MAX); 525 526 FOR_EACH(i, 0, n) { 527 const struct sln_node* child = sln_node_get_child(tree, node, i); 528 529 SLN(node_get_mesh(tree, child, &mesh)); 530 ka = sln_mesh_eval(&mesh, nu); 531 532 sum += ka; 533 cumul[i] = sum; 534 proba[i] = ka; 535 } 536 537 /* No lines could be sampled because none of them affect the absorption at the 538 * given wave number */ 539 if(sum == 0) { 540 res = RES_BAD_ARG; 541 goto error; 542 } 543 544 /* Check the criterion of transition importance sampling, i.e. the value of 545 * the parent node must be greater than or equal to the sum of the values of 546 * its children */ 547 SLN(node_get_mesh(tree, node, &mesh)); 548 ka = sln_mesh_eval(&mesh, nu); 549 if(ka < sum) { 550 ERROR(tree->sln, 551 "ka < ka_{0} + ka_{1} + ... + ka_{N-1}; %e < %e; nu=%-21.20g cm^-1\n", 552 ka, sum, nu); 553 res = RES_BAD_ARG; 554 goto error; 555 } 556 557 /* Complete the probability calculation and normalize the cumulative */ 558 ASSERT(sum != 0); 559 FOR_EACH(i, 0, n) proba[i] /= sum; 560 FOR_EACH(i, 0, n-1) cumul[i] /= sum; 561 cumul[n-1] = 1; /* Handle numerical uncertainty */ 562 563 exit: 564 return res; 565 error: 566 goto exit; 567 } 568 569 static void 570 release_tree(ref_T* ref) 571 { 572 struct sln_tree* tree = CONTAINER_OF(ref, struct sln_tree, ref); 573 struct sln_device* sln = NULL; 574 ASSERT(ref); 575 sln = tree->sln; 576 darray_node_release(&tree->nodes); 577 darray_vertex_release(&tree->vertices); 578 if(tree->args.lines) SHTR(line_list_ref_put(tree->args.lines)); 579 if(tree->args.metadata) SHTR(isotope_metadata_ref_put(tree->args.metadata)); 580 MEM_RM(sln->allocator, tree); 581 SLN(device_ref_put(sln)); 582 } 583 584 /******************************************************************************* 585 * Local function 586 ******************************************************************************/ 587 unsigned 588 node_child_count(const struct sln_node* node, const unsigned tree_arity) 589 { 590 size_t nlines = 0; /* #lines in the node */ 591 size_t nlines_per_child = 0; /* Max #lines in a child */ 592 size_t nchildren = 0; 593 594 /* Pre-conditions */ 595 ASSERT(node && tree_arity >= 2); 596 597 /* Retrieve the node data and compute the #lines it partitions */ 598 nlines = node->range[1] - node->range[0] + 1; 599 ASSERT(nlines); 600 601 /* Based on the arity of the tree, calculate how the lines of the node are 602 * distributed among its children. For low lines count, i.e. when the minimum 603 * number of lines par child is less than the tree arity, the policy below 604 * prioritizes an equal distribution of lines among the children over 605 * maintaining the tree's arity. Thus, if a smaller number of children 606 * results in a more equitable distribution, this option is preferred over 607 * ensuring a number of children equal to the tree's arity. In other words, 608 * the tree's balance is prioritized. */ 609 nlines_per_child = (nlines + tree_arity-1/*ceil*/)/tree_arity; 610 611 /* From the previous line repartition, compute the number of children */ 612 nchildren = (nlines + nlines_per_child-1/*ceil*/)/nlines_per_child; 613 ASSERT(nchildren >= 2); 614 615 ASSERT(nchildren <= UINT_MAX); 616 return (unsigned)nchildren; 617 } 618 619 /******************************************************************************* 620 * Exported symbols 621 ******************************************************************************/ 622 res_T 623 sln_tree_create 624 (struct sln_device* device, 625 const struct sln_tree_create_args* args, 626 struct sln_tree** out_tree) 627 { 628 struct sln_tree* tree = NULL; 629 unsigned nthreads_max = 0; 630 res_T res = RES_OK; 631 632 if(!device || !out_tree) { res = RES_BAD_ARG; goto error; } 633 res = check_sln_tree_create_args(device, FUNC_NAME, args); 634 if(res != RES_OK) goto error; 635 636 res = create_tree(device, FUNC_NAME, &tree); 637 if(res != RES_OK) goto error; 638 SHTR(line_list_ref_get(args->lines)); 639 SHTR(isotope_metadata_ref_get(args->metadata)); 640 tree->args = *args; 641 642 /* Set the #threads to match the maximum number of available threads */ 643 nthreads_max = (unsigned)MMAX(omp_get_max_threads(), omp_get_num_procs()); 644 tree->args.nthreads_hint = MMIN(tree->args.nthreads_hint, nthreads_max); 645 646 res = tree_build(tree); 647 if(res != RES_OK) goto error; 648 649 exit: 650 if(out_tree) *out_tree = tree; 651 return res; 652 error: 653 if(tree) { SLN(tree_ref_put(tree)); tree = NULL; } 654 goto exit; 655 } 656 657 res_T 658 sln_tree_read 659 (struct sln_device* sln, 660 const struct sln_tree_read_args* args, 661 struct sln_tree** out_tree) 662 { 663 hash256_T hash_mdata1; 664 hash256_T hash_mdata2; 665 hash256_T hash_lines1; 666 hash256_T hash_lines2; 667 668 struct stream stream = STREAM_NULL; 669 struct sln_tree* tree = NULL; 670 size_t n = 0; 671 int version = 0; 672 res_T res = RES_OK; 673 674 if(!sln || !out_tree) { res = RES_BAD_ARG; goto error; } 675 res = check_sln_tree_read_args(sln, FUNC_NAME, args); 676 if(res != RES_OK) goto error; 677 678 res = create_tree(sln, FUNC_NAME, &tree); 679 if(res != RES_OK) goto error; 680 681 res = stream_init(sln, FUNC_NAME, args->filename, args->file, "r", &stream); 682 if(res != RES_OK) goto error; 683 684 #define READ(Var, Nb) { \ 685 if(fread((Var), sizeof(*(Var)), (Nb), stream.fp) != (Nb)) { \ 686 if(feof(stream.fp)) { \ 687 res = RES_BAD_ARG; \ 688 } else if(ferror(stream.fp)) { \ 689 res = RES_IO_ERR; \ 690 } else { \ 691 res = RES_UNKNOWN_ERR; \ 692 } \ 693 ERROR(sln, "%s: error loading the tree structure -- %s.\n", \ 694 stream.name, res_to_cstr(res)); \ 695 goto error; \ 696 } \ 697 } (void)0 698 READ(&version, 1); 699 if(version != SLN_TREE_VERSION) { 700 ERROR(sln, 701 "%s: unexpected tree version %d. Expecting a tree in version %d.\n", 702 stream.name, version, SLN_TREE_VERSION); 703 res = RES_BAD_ARG; 704 goto error; 705 } 706 707 res = shtr_isotope_metadata_hash(args->metadata, hash_mdata1); 708 if(res != RES_OK) goto error; 709 710 READ(hash_mdata2, sizeof(hash256_T)); 711 if(!hash256_eq(hash_mdata1, hash_mdata2)) { 712 ERROR(sln, 713 "%s: the input isotopic metadata are not those used " 714 "during tree construction.\n", stream.name); 715 res = RES_BAD_ARG; 716 goto error; 717 } 718 719 SHTR(isotope_metadata_ref_get(args->metadata)); 720 tree->args.metadata = args->metadata; 721 722 READ(hash_lines1, sizeof(hash256_T)); 723 if(!args->disable_line_hash_check) { 724 res = shtr_line_list_hash(args->lines, hash_lines2); 725 if(res != RES_OK) goto error; 726 727 if(!hash256_eq(hash_lines1, hash_lines2)) { 728 ERROR(sln, 729 "%s: the input list of lines is not the one used to build the tree.\n", 730 stream.name); 731 res = RES_BAD_ARG; 732 goto error; 733 } 734 } 735 736 SHTR(line_list_ref_get(args->lines)); 737 tree->args.lines = args->lines; 738 739 READ(&n, 1); 740 if((res = darray_node_resize(&tree->nodes, n)) != RES_OK) goto error; 741 READ(darray_node_data_get(&tree->nodes), n); 742 743 READ(&n, 1); 744 if((res = darray_vertex_resize(&tree->vertices, n)) != RES_OK) goto error; 745 READ(darray_vertex_data_get(&tree->vertices), n); 746 747 READ(&tree->args.line_profile, 1); 748 READ(&tree->args.molecules, 1); 749 READ(&tree->args.pressure, 1); 750 READ(&tree->args.temperature, 1); 751 READ(&tree->args.nvertices_hint, 1); 752 READ(&tree->args.mesh_decimation_err, 1); 753 READ(&tree->args.mesh_type, 1); 754 READ(&tree->args.arity, 1); 755 READ(&tree->args.leaf_nlines, 1); 756 #undef READ 757 758 exit: 759 stream_release(&stream); 760 if(out_tree) *out_tree = tree; 761 return res; 762 error: 763 if(tree) { SLN(tree_ref_put(tree)); tree = NULL; } 764 goto exit; 765 } 766 767 res_T 768 sln_tree_ref_get(struct sln_tree* tree) 769 { 770 if(!tree) return RES_BAD_ARG; 771 ref_get(&tree->ref); 772 return RES_OK; 773 } 774 775 res_T 776 sln_tree_ref_put(struct sln_tree* tree) 777 { 778 if(!tree) return RES_BAD_ARG; 779 ref_put(&tree->ref, release_tree); 780 return RES_OK; 781 } 782 783 res_T 784 sln_tree_get_desc(const struct sln_tree* tree, struct sln_tree_desc* desc) 785 { 786 const struct sln_node* node = NULL; 787 unsigned depth = 0; 788 789 if(!tree || !desc) return RES_BAD_ARG; 790 791 desc->mesh_decimation_err = tree->args.mesh_decimation_err; 792 desc->mesh_type = tree->args.mesh_type; 793 desc->line_profile = tree->args.line_profile; 794 desc->nnodes = darray_node_size_get(&tree->nodes); 795 desc->nvertices = darray_vertex_size_get(&tree->vertices); 796 desc->pressure = tree->args.pressure; /* [atm] */ 797 desc->temperature = tree->args.temperature; /* [K] */ 798 desc->arity = tree->args.arity; 799 desc->leaf_nlines = tree->args.leaf_nlines; 800 801 SHTR(line_list_get_size(tree->args.lines, &desc->nlines)); 802 803 node = sln_tree_get_root(tree); 804 while(!sln_node_is_leaf(node)) { 805 node = sln_node_get_child(tree, node, 0); 806 ++depth; 807 } 808 desc->depth = depth; 809 810 return RES_OK; 811 } 812 813 const struct sln_node* 814 sln_tree_get_root(const struct sln_tree* tree) 815 { 816 ASSERT(tree); 817 if(darray_node_size_get(&tree->nodes)) { 818 return darray_node_cdata_get(&tree->nodes); 819 } else { 820 return NULL; 821 } 822 } 823 824 res_T 825 sln_tree_get_line 826 (const struct sln_tree* tree, 827 const size_t iline, 828 const struct sln_thermo_props* props, 829 struct sln_line* line) 830 { 831 size_t nlines = 0; 832 res_T res = RES_OK; 833 834 if(!tree || !line) { res = RES_BAD_ARG; goto error; } 835 836 SHTR(line_list_get_size(tree->args.lines, &nlines)); 837 if(iline >= nlines) { res = RES_BAD_ARG; goto error; } 838 839 res = check_line_thermo_props(tree, FUNC_NAME, iline, props); 840 if(res != RES_OK) goto error; 841 842 res = line_setup(tree, iline, props, line); 843 if(res != RES_OK) { 844 ERROR(tree->sln, "%s: could not setup the line %lu-- %s\n", 845 FUNC_NAME, iline, res_to_cstr(res)); 846 goto error; 847 } 848 849 exit: 850 return res; 851 error: 852 goto exit; 853 } 854 855 int 856 sln_node_is_leaf(const struct sln_node* node) 857 { 858 ASSERT(node); 859 return node->offset == 0; 860 } 861 862 unsigned 863 sln_node_get_child_count 864 (const struct sln_tree* tree, 865 const struct sln_node* node) 866 { 867 ASSERT(tree && node); 868 869 if(sln_node_is_leaf(node)) { 870 return 0; /* No child */ 871 } else { 872 return node_child_count(node, tree->args.arity); 873 } 874 } 875 876 const struct sln_node* 877 sln_node_get_child 878 (const struct sln_tree* tree, 879 const struct sln_node* node, 880 const unsigned ichild) 881 { 882 ASSERT(node && ichild < sln_node_get_child_count(tree, node)); 883 ASSERT(!sln_node_is_leaf(node)); 884 (void)tree; 885 return node + node->offset + ichild; 886 } 887 888 res_T 889 sln_node_get_mesh 890 (const struct sln_tree* tree, 891 const struct sln_node* node, 892 struct sln_mesh* mesh) 893 { 894 if(!tree || !node || !mesh) return RES_BAD_ARG; 895 mesh->vertices = darray_vertex_cdata_get(&tree->vertices) + node->ivertex; 896 mesh->nvertices = node->nvertices; 897 return RES_OK; 898 } 899 900 double 901 sln_node_eval 902 (const struct sln_tree* tree, 903 const struct sln_node* node, 904 const struct sln_thermo_props* props, 905 const double nu) 906 { 907 double ka = 0; 908 size_t iline; 909 ASSERT(tree && node); 910 ASSERT(check_sln_thermo_props(tree->sln, FUNC_NAME, props) == RES_OK); 911 912 FOR_EACH(iline, node->range[0], node->range[1]+1) { 913 struct sln_line line = SLN_LINE_NULL; 914 res_T res = RES_OK; 915 916 res = line_setup(tree, iline, props, &line); 917 if(res != RES_OK) { 918 WARN(tree->sln, "%s: could not setup the line %lu-- %s\n", 919 FUNC_NAME, iline, res_to_cstr(res)); 920 continue; 921 } 922 923 ka += sln_line_eval(tree, &line, nu); 924 } 925 return ka; 926 } 927 928 res_T 929 sln_node_get_desc 930 (const struct sln_tree* tree, 931 const struct sln_node* node, 932 struct sln_node_desc* desc) 933 { 934 if(!tree || !node || !desc) return RES_BAD_ARG; 935 desc->ilines[0] = node->range[0]; 936 desc->ilines[1] = node->range[1]; 937 desc->nvertices = node->nvertices; 938 desc->nchildren = sln_node_get_child_count(tree, node); 939 return RES_OK; 940 } 941 942 const struct sln_node* 943 sln_node_sample_leaf 944 (const struct sln_tree* tree, 945 const struct sln_node* root, 946 const double nu, /*[cm^-1]*/ 947 struct ssp_rng* rng, 948 double* out_leaf_proba) /* May be NULL */ 949 { 950 /* Temporary buffers used to store the cumulative of child nodes and their 951 * probability of being sampled based on their importance */ 952 double cumul[SLN_TREE_ARITY_MAX]; 953 double proba[SLN_TREE_ARITY_MAX]; 954 955 const struct sln_node* node = NULL; 956 double leaf_proba = 1; 957 int depth = 0; 958 res_T res = RES_OK; 959 960 if(!tree || !root || !rng) { res = RES_BAD_ARG; goto error; } 961 962 for(depth=0, node=root; !sln_node_is_leaf(node); ++depth) { 963 double r = 0; /* Random number */ 964 unsigned ichild = 0; 965 966 res = build_node_cumulative(tree, node, nu, proba, cumul); 967 if(res != RES_OK) goto error; 968 969 /* Sample a child node based on its importance. Use a simple linear search, 970 * since the tree's arity is small enough that a binary search is not 971 * necessary. FIXME if performance measurements show that this linear search 972 * incurs a significant cost */ 973 r = ssp_rng_canonical(rng); 974 FOR_EACH(ichild, 0, SLN_TREE_ARITY_MAX) { 975 if(r < cumul[ichild]) { 976 leaf_proba *= proba[ichild]; 977 node = sln_node_get_child(tree, node, ichild); 978 break; 979 } 980 } 981 ASSERT(ichild < SLN_TREE_ARITY_MAX); /* A node should have been sampled */ 982 } 983 984 exit: 985 if(out_leaf_proba) *out_leaf_proba = leaf_proba; 986 return node; 987 error: 988 node = NULL; 989 leaf_proba = NaN; 990 goto exit; 991 } 992 993 double 994 sln_mesh_eval(const struct sln_mesh* mesh, const double wavenumber) 995 { 996 const struct sln_vertex* vtx0 = NULL; 997 const struct sln_vertex* vtx1 = NULL; 998 const float nu = (float)wavenumber; 999 size_t n; /* #vertices */ 1000 double u; /* Linear interpolation parameter */ 1001 ASSERT(mesh && mesh->nvertices); 1002 1003 n = mesh->nvertices; 1004 1005 /* Handle special cases */ 1006 if(n == 1) return mesh->vertices[0].ka; 1007 if(nu < mesh->vertices[0].wavenumber 1008 || nu > mesh->vertices[n-1].wavenumber) { 1009 return 0; 1010 } 1011 if(nu == mesh->vertices[0].wavenumber) return mesh->vertices[0].ka; 1012 if(nu == mesh->vertices[n-1].wavenumber) return mesh->vertices[n-1].ka; 1013 1014 /* Dichotomic search of the mesh vertex whose wavenumber is greater than or 1015 * equal to the submitted wavenumber 'nu' */ 1016 vtx1 = search_lower_bound(&nu, mesh->vertices, n, sizeof(*vtx1), cmp_nu_vtx); 1017 vtx0 = vtx1 - 1; 1018 ASSERT(vtx1); /* A vertex is necessary found ...*/ 1019 ASSERT(vtx1 > mesh->vertices); /* ... and it cannot be the first one */ 1020 ASSERT(vtx0->wavenumber < nu && nu <= vtx1->wavenumber); 1021 1022 /* Compute the linear interpolation parameter */ 1023 u = (wavenumber - vtx0->wavenumber) / (vtx1->wavenumber - vtx0->wavenumber); 1024 u = CLAMP(u, 0, 1); /* Handle numerical imprecisions */ 1025 1026 if(u == 0) return vtx0->ka; 1027 if(u == 1) return vtx1->ka; 1028 return u*(vtx1->ka - vtx0->ka) + vtx0->ka; 1029 } 1030 1031 res_T 1032 sln_tree_write 1033 (const struct sln_tree* tree, 1034 const struct sln_tree_write_args* args) 1035 { 1036 struct stream stream = STREAM_NULL; 1037 size_t nnodes, nverts; 1038 hash256_T hash_mdata; 1039 hash256_T hash_lines; 1040 res_T res = RES_OK; 1041 1042 if(!tree) { res = RES_BAD_ARG; goto error; } 1043 res = check_sln_tree_write_args(tree->sln, FUNC_NAME, args); 1044 if(res != RES_OK) goto error; 1045 1046 res = shtr_isotope_metadata_hash(tree->args.metadata, hash_mdata); 1047 if(res != RES_OK) goto error; 1048 res = shtr_line_list_hash(tree->args.lines, hash_lines); 1049 if(res != RES_OK) goto error; 1050 1051 res = stream_init 1052 (tree->sln, FUNC_NAME, args->filename, args->file, "w", &stream); 1053 if(res != RES_OK) goto error; 1054 1055 #define WRITE(Var, Nb) { \ 1056 if(fwrite((Var), sizeof(*(Var)), (Nb), stream.fp) != (Nb)) { \ 1057 ERROR(tree->sln, "%s:%s: error writing the tree -- %s\n", \ 1058 FUNC_NAME, stream.name, strerror(errno)); \ 1059 res = RES_IO_ERR; \ 1060 goto error; \ 1061 } \ 1062 } (void)0 1063 WRITE(&SLN_TREE_VERSION, 1); 1064 WRITE(hash_mdata, sizeof(hash256_T)); 1065 WRITE(hash_lines, sizeof(hash256_T)); 1066 1067 nnodes = darray_node_size_get(&tree->nodes); 1068 WRITE(&nnodes, 1); 1069 WRITE(darray_node_cdata_get(&tree->nodes), nnodes); 1070 1071 nverts = darray_vertex_size_get(&tree->vertices); 1072 WRITE(&nverts, 1); 1073 WRITE(darray_vertex_cdata_get(&tree->vertices), nverts); 1074 1075 WRITE(&tree->args.line_profile, 1); 1076 WRITE(&tree->args.molecules, 1); 1077 WRITE(&tree->args.pressure, 1); 1078 WRITE(&tree->args.temperature, 1); 1079 WRITE(&tree->args.nvertices_hint, 1); 1080 WRITE(&tree->args.mesh_decimation_err, 1); 1081 WRITE(&tree->args.mesh_type, 1); 1082 WRITE(&tree->args.arity, 1); 1083 WRITE(&tree->args.leaf_nlines, 1); 1084 #undef WRITE 1085 1086 exit: 1087 stream_release(&stream); 1088 return res; 1089 error: 1090 goto exit; 1091 }