star-line

Structure for accelerating line importance sampling
git clone git://git.meso-star.com/star-line.git
Log | Files | Refs | README | LICENSE

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 }