star-line

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

sln_tree.c (24409B)


      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 program is free software: you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation, either version 3 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     18 
     19 #include "sln.h"
     20 #include "sln_device_c.h"
     21 #include "sln_line.h"
     22 #include "sln_tree_c.h"
     23 
     24 #include <star/shtr.h>
     25 
     26 #include <rsys/algorithm.h>
     27 #include <rsys/cstr.h>
     28 #include <rsys/math.h>
     29 
     30 #include <omp.h>
     31 
     32 struct stream {
     33   const char* name;
     34   FILE* fp;
     35   int intern_fp; /* Define if the stream was internally opened */
     36 };
     37 static const struct stream STREAM_NULL = {NULL, NULL, 0};
     38 
     39 /*******************************************************************************
     40  * Helper functions
     41  ******************************************************************************/
     42 /* Check that the sum of the molecular concentrations is equal to 1 */
     43 static res_T
     44 check_molecule_concentration
     45   (struct sln_device* sln,
     46    const char* caller,
     47    const struct sln_tree_create_args* args)
     48 {
     49   int i = 0;
     50   double sum = 0;
     51   ASSERT(sln && caller && args);
     52 
     53   FOR_EACH(i, 0, SHTR_MAX_MOLECULE_COUNT) {
     54     if(i == SHTR_MOLECULE_ID_NULL) continue;
     55     sum += args->molecules[i].concentration;
     56   }
     57 
     58   /* The sum of molecular concentrations must be less than or equal to 1. It may
     59    * be less than 1 if the remaining part of the mixture is (implicitly) defined
     60    * as a radiatively inactive gas */
     61   if(sum > 1 && sum-1 > 1e-6) {
     62     ERROR(sln,
     63       "%s: the sum of molecule concentrations is greater than 1: %g\n",
     64       caller, sum);
     65     return RES_BAD_ARG;
     66   }
     67 
     68   return RES_OK;
     69 }
     70 
     71 /* Verify that the isotope abundance are valids */
     72 static res_T
     73 check_molecule_isotope_abundances
     74   (struct sln_device* sln,
     75    const char* caller,
     76    const struct sln_molecule* molecule)
     77 {
     78   int i = 0;
     79   double sum = 0;
     80   ASSERT(sln && caller && molecule);
     81 
     82   /* The isotopic abundances are the default ones. Nothing to do */
     83   if(!molecule->non_default_isotope_abundances) return RES_OK;
     84 
     85   /* The isotopic abundances are not the default ones.
     86    * Verify that they are valid ... */
     87   FOR_EACH(i, 0, SHTR_MAX_ISOTOPE_COUNT) {
     88     if(molecule->isotopes[i].abundance < 0) {
     89       const int isotope_id = i + 1; /* isotope id in [1, 10] */
     90       ERROR(sln, "%s: invalid abundance of isotopie %d of %s: %g.\n",
     91         caller, isotope_id, shtr_molecule_cstr(i),
     92         molecule->isotopes[i].abundance);
     93       return RES_BAD_ARG;
     94     }
     95 
     96     sum += molecule->isotopes[i].abundance;
     97   }
     98 
     99   /* ... and that their sum equals 1 */
    100   if(!eq_eps(sum, 1, 1e-6)) {
    101     ERROR(sln, "%s: the %s isotope abundances does not sum to 1: %g.\n",
    102       caller, shtr_molecule_cstr(i), sum);
    103     return RES_BAD_ARG;
    104   }
    105 
    106   return RES_OK;
    107 }
    108 
    109 static res_T
    110 check_molecules
    111   (struct sln_device* sln,
    112    const char* caller,
    113    const struct sln_tree_create_args* args)
    114 {
    115   char molecule_ok[SHTR_MAX_MOLECULE_COUNT] = {0};
    116 
    117   double concentrations_sum = 0;
    118   size_t iline = 0;
    119   size_t nlines = 0;
    120   res_T res = RES_OK;
    121   ASSERT(args->lines);
    122 
    123   res = check_molecule_concentration(sln, caller, args);
    124   if(res != RES_OK) return res;
    125 
    126   /* Iterate over the lines to define which molecules has to be checked, i.e.,
    127    * the ones used in the mixture */
    128   SHTR(line_list_get_size(args->lines, &nlines));
    129   FOR_EACH(iline, 0, nlines) {
    130     struct shtr_line line = SHTR_LINE_NULL;
    131     const struct sln_molecule* molecule = NULL;
    132 
    133     SHTR(line_list_at(args->lines, iline, &line));
    134 
    135     /* This molecule was already checked */
    136     if(molecule_ok[line.molecule_id]) continue;
    137 
    138     molecule = args->molecules + line.molecule_id;
    139 
    140     if(molecule->concentration == 0) {
    141       /* A molecular concentration of zero is allowed, but may be a user error,
    142        * as 0 is the default concentration in the tree creation arguments.
    143        * Therefore, warn the user about this value so that they can determine
    144        * whether or not it is an error on their part. */
    145       WARN(sln, "%s: the concentration of %s is zero.\n",
    146         caller, shtr_molecule_cstr(line.molecule_id));
    147 
    148     } else if(molecule->concentration < 0) {
    149       /* Concentration cannot be negative... */
    150       ERROR(sln, "%s: invalid %s concentration: %g.\n",
    151         FUNC_NAME, shtr_molecule_cstr(line.molecule_id),
    152         molecule->concentration);
    153       return RES_BAD_ARG;
    154     }
    155 
    156     concentrations_sum += molecule->concentration;
    157 
    158     if(molecule->cutoff <= 0) {
    159       /* ... cutoff either */
    160       ERROR(sln, "%s: invalid %s cutoff: %g.\n",
    161         caller, shtr_molecule_cstr(line.molecule_id), molecule->cutoff);
    162       return RES_BAD_ARG;
    163     }
    164 
    165     res = check_molecule_isotope_abundances(sln, caller, molecule);
    166     if(res != RES_OK) return res;
    167 
    168     molecule_ok[line.molecule_id] = 1;
    169   }
    170 
    171   /* The sum of molecular concentrations must be less than or equal to 1. It may
    172    * be less than 1 if the remaining part of the mixture is (implicitly) defined
    173    * as a radiatively inactive gas */
    174   if(concentrations_sum > 1 && (concentrations_sum - 1) > 1e-6) {
    175     ERROR(sln,
    176       "%s: the sum of molecule concentrations is greater than 1: %g\n",
    177       caller, concentrations_sum);
    178     return RES_BAD_ARG;
    179   }
    180 
    181   return RES_OK;
    182 }
    183 
    184 static res_T
    185 check_sln_tree_create_args
    186   (struct sln_device* sln,
    187    const char* caller,
    188    const struct sln_tree_create_args* args)
    189 {
    190   res_T res = RES_OK;
    191   ASSERT(sln && caller);
    192 
    193   if(!args) return RES_BAD_ARG;
    194 
    195   if(!args->metadata) {
    196     ERROR(sln, "%s: the isotope metadata are missing.\n", caller);
    197     return RES_BAD_ARG;
    198   }
    199 
    200   if(!args->lines) {
    201     ERROR(sln, "%s: the list of lines is missing.\n", caller);
    202     return RES_BAD_ARG;
    203   }
    204 
    205   if(args->nvertices_hint == 0) {
    206     ERROR(sln,
    207       "%s: invalid hint on the number of vertices around the line center %lu.\n",
    208       caller, (unsigned long)args->nvertices_hint);
    209     return RES_BAD_ARG;
    210   }
    211 
    212   if(args->mesh_decimation_err < 0) {
    213     ERROR(sln, "%s: invalid decimation error %g.\n",
    214       caller, args->mesh_decimation_err);
    215     return RES_BAD_ARG;
    216   }
    217 
    218   if((unsigned)args->mesh_type >= SLN_MESH_TYPES_COUNT__) {
    219     ERROR(sln, "%s: invalid mesh type %d.\n", caller, args->mesh_type);
    220     return RES_BAD_ARG;
    221   }
    222 
    223   if((unsigned)args->line_profile >= SLN_LINE_PROFILES_COUNT__) {
    224     ERROR(sln, "%s: invalid line profile %d.\n", caller, args->line_profile);
    225     return RES_BAD_ARG;
    226   }
    227 
    228   if(args->arity < 2 || args->arity > SLN_TREE_ARITY_MAX) {
    229     ERROR(sln, "%s: invalid arity %u. It must be in [2, %d]\n",
    230       caller, args->arity, SLN_TREE_ARITY_MAX);
    231     return RES_BAD_ARG;
    232   }
    233 
    234   if(args->leaf_nlines < 1 || args->leaf_nlines > SLN_LEAF_NLINES_MAX) {
    235     ERROR(sln, "%s: invalid number of lines per leaf %u. It must be in [1, %d]\n",
    236       caller, args->leaf_nlines, SLN_LEAF_NLINES_MAX);
    237     return RES_BAD_ARG;
    238   }
    239 
    240   if(args->nthreads_hint == 0) {
    241     ERROR(sln, "%s: invalid number of threads %u\n",
    242       caller, args->nthreads_hint);
    243     return RES_BAD_ARG;
    244   }
    245 
    246   res = check_molecules(sln, caller, args);
    247   if(res != RES_OK) return res;
    248 
    249   return RES_OK;
    250 }
    251 
    252 static res_T
    253 check_sln_tree_read_args
    254   (struct sln_device* sln,
    255    const char* caller,
    256    const struct sln_tree_read_args* args)
    257 {
    258   if(!args) return RES_BAD_ARG;
    259 
    260   if(!args->metadata) {
    261     ERROR(sln, "%s: the isotope metadata are missing.\n", caller);
    262     return RES_BAD_ARG;
    263   }
    264 
    265   if(!args->lines) {
    266     ERROR(sln, "%s: the list of lines is missing.\n", caller);
    267     return RES_BAD_ARG;
    268   }
    269 
    270   if(!args->file && !args->filename) {
    271     ERROR(sln,
    272       "%s: the source file is missing. No file name or stream is provided.\n",
    273       caller);
    274     return RES_BAD_ARG;
    275   }
    276 
    277   return RES_OK;
    278 }
    279 
    280 static res_T
    281 check_sln_tree_write_args
    282   (struct sln_device* sln,
    283    const char* caller,
    284    const struct sln_tree_write_args* args)
    285 {
    286   if(!args) return RES_BAD_ARG;
    287 
    288   if(!args->file && !args->filename) {
    289     ERROR(sln,
    290       "%s: the destination file is missing. "
    291       "No file name or stream is provided.\n",
    292       caller);
    293     return RES_BAD_ARG;
    294   }
    295 
    296   return RES_OK;
    297 }
    298 static INLINE void
    299 stream_release(struct stream* stream)
    300 {
    301   ASSERT(stream);
    302   if(stream->intern_fp && stream->fp) CHK(fclose(stream->fp) == 0);
    303 }
    304 
    305 static res_T
    306 stream_init
    307   (struct sln_device* sln,
    308    const char* caller,
    309    const char* name, /* NULL <=> default stream name */
    310    FILE* fp, /* NULL <=> open file "name" */
    311    const char* mode, /* mode in fopen */
    312    struct stream* stream)
    313 {
    314   res_T res = RES_OK;
    315 
    316   ASSERT(sln && caller && stream);
    317   ASSERT(fp || (name && mode));
    318 
    319   *stream = STREAM_NULL;
    320 
    321   if(fp) {
    322     stream->intern_fp = 0;
    323     stream->name = name ? name : "stream";
    324     stream->fp = fp;
    325 
    326   } else {
    327     stream->intern_fp = 1;
    328     stream->name = name;
    329     if(!(stream->fp = fopen(name, mode))) {
    330       ERROR(sln, "%s:%s: error opening file -- %s\n",
    331         caller, name, strerror(errno));
    332       res = RES_IO_ERR;
    333       goto error;
    334     }
    335   }
    336 
    337 exit:
    338   return res;
    339 error:
    340   if(stream->intern_fp && stream->fp) CHK(fclose(stream->fp) == 0);
    341   goto exit;
    342 }
    343 
    344 static res_T
    345 create_tree
    346   (struct sln_device* sln,
    347    const char* caller,
    348    struct sln_tree** out_tree)
    349 {
    350   struct sln_tree* tree = NULL;
    351   res_T res = RES_OK;
    352   ASSERT(sln && caller && out_tree);
    353 
    354   tree = MEM_CALLOC(sln->allocator, 1, sizeof(struct sln_tree));
    355   if(!tree) {
    356     ERROR(sln, "%s: could not allocate the tree data structure.\n",
    357       caller);
    358     res = RES_MEM_ERR;
    359     goto error;
    360   }
    361   ref_init(&tree->ref);
    362   SLN(device_ref_get(sln));
    363   tree->sln = sln;
    364   darray_node_init(sln->allocator, &tree->nodes);
    365   darray_vertex_init(sln->allocator, &tree->vertices);
    366 
    367 exit:
    368   *out_tree = tree;
    369   return res;
    370 error:
    371   if(tree) { SLN(tree_ref_put(tree)); tree = NULL; }
    372   goto exit;
    373 }
    374 
    375 static INLINE int
    376 cmp_nu_vtx(const void* key, const void* item)
    377 {
    378   const float nu = *((const float*)key);
    379   const struct sln_vertex* vtx = item;
    380   if(nu < vtx->wavenumber) return -1;
    381   if(nu > vtx->wavenumber) return +1;
    382   return 0;
    383 }
    384 
    385 static void
    386 release_tree(ref_T* ref)
    387 {
    388   struct sln_tree* tree = CONTAINER_OF(ref, struct sln_tree, ref);
    389   struct sln_device* sln = NULL;
    390   ASSERT(ref);
    391   sln = tree->sln;
    392   darray_node_release(&tree->nodes);
    393   darray_vertex_release(&tree->vertices);
    394   if(tree->args.lines) SHTR(line_list_ref_put(tree->args.lines));
    395   if(tree->args.metadata) SHTR(isotope_metadata_ref_put(tree->args.metadata));
    396   MEM_RM(sln->allocator, tree);
    397   SLN(device_ref_put(sln));
    398 }
    399 
    400 /*******************************************************************************
    401  * Local function
    402  ******************************************************************************/
    403 unsigned
    404 node_child_count(const struct sln_node* node, const unsigned tree_arity)
    405 {
    406   size_t nlines = 0; /* #lines in the node */
    407   size_t nlines_per_child =  0; /* Max #lines in a child */
    408   size_t nchildren = 0;
    409 
    410   /* Pre-conditions */
    411   ASSERT(node && tree_arity >= 2);
    412 
    413   /* Retrieve the node data and compute the #lines it partitions */
    414   nlines = node->range[1] - node->range[0] + 1;
    415   ASSERT(nlines);
    416 
    417   /* Based on the arity of the tree, calculate how the lines of the node are
    418    * distributed among its children. For low lines count, i.e. when the minimum
    419    * number of lines par child is less than the tree arity, the policy below
    420    * prioritizes an equal distribution of lines among the children over
    421    * maintaining the tree's arity.  Thus, if a smaller number of children
    422    * results in a more equitable distribution, this option is preferred over
    423    * ensuring a number of children equal to the tree's arity. In other words,
    424    * the tree's balance is prioritized. */
    425   nlines_per_child = (nlines + tree_arity-1/*ceil*/)/tree_arity;
    426 
    427   /* From the previous line repartition, compute the number of children */
    428   nchildren = (nlines + nlines_per_child-1/*ceil*/)/nlines_per_child;
    429   ASSERT(nchildren >= 2);
    430 
    431   ASSERT(nchildren <= UINT_MAX);
    432   return (unsigned)nchildren;
    433 }
    434 
    435 /*******************************************************************************
    436  * Exported symbols
    437  ******************************************************************************/
    438 res_T
    439 sln_tree_create
    440   (struct sln_device* device,
    441    const struct sln_tree_create_args* args,
    442    struct sln_tree** out_tree)
    443 {
    444   struct sln_tree* tree = NULL;
    445   unsigned nthreads_max = 0;
    446   res_T res = RES_OK;
    447 
    448   if(!device || !out_tree) { res = RES_BAD_ARG; goto error; }
    449   res = check_sln_tree_create_args(device, FUNC_NAME, args);
    450   if(res != RES_OK) goto error;
    451 
    452   res = create_tree(device, FUNC_NAME, &tree);
    453   if(res != RES_OK) goto error;
    454   SHTR(line_list_ref_get(args->lines));
    455   SHTR(isotope_metadata_ref_get(args->metadata));
    456   tree->args = *args;
    457 
    458   /* Set the #threads to match the maximum number of available threads */
    459   nthreads_max = (unsigned)MMAX(omp_get_max_threads(), omp_get_num_procs());
    460   tree->args.nthreads_hint = MMIN(tree->args.nthreads_hint, nthreads_max);
    461 
    462   res = tree_build(tree);
    463   if(res != RES_OK) goto error;
    464 
    465 exit:
    466   if(out_tree) *out_tree = tree;
    467   return res;
    468 error:
    469   if(tree) { SLN(tree_ref_put(tree)); tree = NULL; }
    470   goto exit;
    471 }
    472 
    473 res_T
    474 sln_tree_read
    475   (struct sln_device* sln,
    476    const struct sln_tree_read_args* args,
    477    struct sln_tree** out_tree)
    478 {
    479   hash256_T hash_mdata1;
    480   hash256_T hash_mdata2;
    481   hash256_T hash_lines1;
    482   hash256_T hash_lines2;
    483 
    484   struct stream stream = STREAM_NULL;
    485   struct sln_tree* tree = NULL;
    486   size_t n = 0;
    487   int version = 0;
    488   res_T res = RES_OK;
    489 
    490   if(!sln || !out_tree) { res = RES_BAD_ARG; goto error; }
    491   res = check_sln_tree_read_args(sln, FUNC_NAME, args);
    492   if(res != RES_OK) goto error;
    493 
    494   res = create_tree(sln, FUNC_NAME, &tree);
    495   if(res != RES_OK) goto error;
    496 
    497   res = stream_init(sln, FUNC_NAME, args->filename, args->file, "r", &stream);
    498   if(res != RES_OK) goto error;
    499 
    500   #define READ(Var, Nb) {                                                      \
    501     if(fread((Var), sizeof(*(Var)), (Nb), stream.fp) != (Nb)) {                \
    502       if(feof(stream.fp)) {                                                    \
    503         res = RES_BAD_ARG;                                                     \
    504       } else if(ferror(stream.fp)) {                                           \
    505         res = RES_IO_ERR;                                                      \
    506       } else {                                                                 \
    507         res = RES_UNKNOWN_ERR;                                                 \
    508       }                                                                        \
    509       ERROR(sln, "%s: error loading the tree structure -- %s.\n",              \
    510         stream.name, res_to_cstr(res));                                        \
    511       goto error;                                                              \
    512     }                                                                          \
    513   } (void)0
    514   READ(&version, 1);
    515   if(version != SLN_TREE_VERSION) {
    516     ERROR(sln,
    517       "%s: unexpected tree version %d. Expecting a tree in version %d.\n",
    518       stream.name, version, SLN_TREE_VERSION);
    519     res = RES_BAD_ARG;
    520     goto error;
    521   }
    522 
    523   res = shtr_isotope_metadata_hash(args->metadata, hash_mdata1);
    524   if(res != RES_OK) goto error;
    525 
    526   READ(hash_mdata2, sizeof(hash256_T));
    527   if(!hash256_eq(hash_mdata1, hash_mdata2)) {
    528     ERROR(sln,
    529       "%s: the input isotopic metadata are not those used "
    530       "during tree construction.\n", stream.name);
    531     res = RES_BAD_ARG;
    532     goto error;
    533   }
    534 
    535   SHTR(isotope_metadata_ref_get(args->metadata));
    536   tree->args.metadata = args->metadata;
    537 
    538   res = shtr_line_list_hash(args->lines, hash_lines1);
    539   if(res != RES_OK) goto error;
    540 
    541   READ(hash_lines2, sizeof(hash256_T));
    542   if(!hash256_eq(hash_lines1, hash_lines2)) {
    543     ERROR(sln,
    544       "%s: the input list of lines is not the one used to build the tree.\n",
    545       stream.name);
    546     res = RES_BAD_ARG;
    547     goto error;
    548   }
    549 
    550   SHTR(line_list_ref_get(args->lines));
    551   tree->args.lines = args->lines;
    552 
    553   READ(&n, 1);
    554   if((res = darray_node_resize(&tree->nodes, n)) != RES_OK) goto error;
    555   READ(darray_node_data_get(&tree->nodes), n);
    556 
    557   READ(&n, 1);
    558   if((res = darray_vertex_resize(&tree->vertices, n)) != RES_OK) goto error;
    559   READ(darray_vertex_data_get(&tree->vertices), n);
    560 
    561   READ(&tree->args.line_profile, 1);
    562   READ(&tree->args.molecules, 1);
    563   READ(&tree->args.pressure, 1);
    564   READ(&tree->args.temperature, 1);
    565   READ(&tree->args.nvertices_hint, 1);
    566   READ(&tree->args.mesh_decimation_err, 1);
    567   READ(&tree->args.mesh_type, 1);
    568   READ(&tree->args.arity, 1);
    569   READ(&tree->args.leaf_nlines, 1);
    570   #undef READ
    571 
    572 exit:
    573   stream_release(&stream);
    574   if(out_tree) *out_tree = tree;
    575   return res;
    576 error:
    577   if(tree) { SLN(tree_ref_put(tree)); tree = NULL; }
    578   goto exit;
    579 }
    580 
    581 res_T
    582 sln_tree_ref_get(struct sln_tree* tree)
    583 {
    584   if(!tree) return RES_BAD_ARG;
    585   ref_get(&tree->ref);
    586   return RES_OK;
    587 }
    588 
    589 res_T
    590 sln_tree_ref_put(struct sln_tree* tree)
    591 {
    592   if(!tree) return RES_BAD_ARG;
    593   ref_put(&tree->ref, release_tree);
    594   return RES_OK;
    595 }
    596 
    597 res_T
    598 sln_tree_get_desc(const struct sln_tree* tree, struct sln_tree_desc* desc)
    599 {
    600   const struct sln_node* node = NULL;
    601   unsigned depth = 0;
    602 
    603   if(!tree || !desc) return RES_BAD_ARG;
    604 
    605   desc->mesh_decimation_err = tree->args.mesh_decimation_err;
    606   desc->mesh_type = tree->args.mesh_type;
    607   desc->line_profile = tree->args.line_profile;
    608   desc->nnodes = darray_node_size_get(&tree->nodes);
    609   desc->nvertices = darray_vertex_size_get(&tree->vertices);
    610   desc->pressure = tree->args.pressure; /* [atm] */
    611   desc->temperature = tree->args.temperature; /* [K] */
    612   desc->arity = tree->args.arity;
    613   desc->leaf_nlines = tree->args.leaf_nlines;
    614 
    615   SHTR(line_list_get_size(tree->args.lines, &desc->nlines));
    616 
    617   node = sln_tree_get_root(tree);
    618   while(!sln_node_is_leaf(node)) {
    619     node = sln_node_get_child(tree, node, 0);
    620     ++depth;
    621   }
    622   desc->depth = depth;
    623 
    624   return RES_OK;
    625 }
    626 
    627 const struct sln_node*
    628 sln_tree_get_root(const struct sln_tree* tree)
    629 {
    630   ASSERT(tree);
    631   if(darray_node_size_get(&tree->nodes)) {
    632     return darray_node_cdata_get(&tree->nodes);
    633   } else {
    634     return NULL;
    635   }
    636 }
    637 
    638 res_T
    639 sln_tree_get_line
    640   (const struct sln_tree* tree,
    641    const size_t iline,
    642    struct sln_line* line)
    643 {
    644   size_t nlines = 0;
    645   res_T res = RES_OK;
    646 
    647   if(!tree || !line) { res = RES_BAD_ARG; goto error; }
    648 
    649   SHTR(line_list_get_size(tree->args.lines, &nlines));
    650   if(iline >= nlines) { res = RES_BAD_ARG; goto error; }
    651 
    652   res = line_setup(tree, iline, line);
    653   if(res != RES_OK) {
    654     ERROR(tree->sln, "%s: could not setup the line %lu-- %s\n",
    655       FUNC_NAME, iline, res_to_cstr(res));
    656     goto error;
    657   }
    658 
    659 exit:
    660   return res;
    661 error:
    662   goto exit;
    663 }
    664 
    665 int
    666 sln_node_is_leaf(const struct sln_node* node)
    667 {
    668   ASSERT(node);
    669   return node->offset == 0;
    670 }
    671 
    672 unsigned
    673 sln_node_get_child_count
    674   (const struct sln_tree* tree,
    675    const struct sln_node* node)
    676 {
    677   ASSERT(tree && node);
    678 
    679   if(sln_node_is_leaf(node)) {
    680     return 0; /* No child */
    681   } else {
    682     return node_child_count(node, tree->args.arity);
    683   }
    684 }
    685 
    686 const struct sln_node*
    687 sln_node_get_child
    688   (const struct sln_tree* tree,
    689    const struct sln_node* node,
    690    const unsigned ichild)
    691 {
    692   ASSERT(node && ichild < sln_node_get_child_count(tree, node));
    693   ASSERT(!sln_node_is_leaf(node));
    694   (void)tree;
    695   return node + node->offset + ichild;
    696 }
    697 
    698 res_T
    699 sln_node_get_mesh
    700   (const struct sln_tree* tree,
    701    const struct sln_node* node,
    702    struct sln_mesh* mesh)
    703 {
    704   if(!tree || !node || !mesh) return RES_BAD_ARG;
    705   mesh->vertices = darray_vertex_cdata_get(&tree->vertices) + node->ivertex;
    706   mesh->nvertices = node->nvertices;
    707   return RES_OK;
    708 }
    709 
    710 double
    711 sln_node_eval
    712   (const struct sln_tree* tree,
    713    const struct sln_node* node,
    714    const double nu)
    715 {
    716   double ka = 0;
    717   size_t iline;
    718   ASSERT(tree && node);
    719 
    720   FOR_EACH(iline, node->range[0], node->range[1]+1) {
    721     struct sln_line line = SLN_LINE_NULL;
    722     res_T res = RES_OK;
    723 
    724     res = line_setup(tree, iline, &line);
    725     if(res != RES_OK) {
    726       WARN(tree->sln, "%s: could not setup the line %lu-- %s\n",
    727         FUNC_NAME, iline, res_to_cstr(res));
    728       continue;
    729     }
    730 
    731     ka += sln_line_eval(tree, &line, nu);
    732   }
    733   return ka;
    734 }
    735 
    736 res_T
    737 sln_node_get_desc
    738   (const struct sln_tree* tree,
    739    const struct sln_node* node,
    740    struct sln_node_desc* desc)
    741 {
    742   if(!tree || !node || !desc) return RES_BAD_ARG;
    743   desc->ilines[0] = node->range[0];
    744   desc->ilines[1] = node->range[1];
    745   desc->nvertices = node->nvertices;
    746   desc->nchildren = sln_node_get_child_count(tree, node);
    747   return RES_OK;
    748 }
    749 
    750 double
    751 sln_mesh_eval(const struct sln_mesh* mesh, const double wavenumber)
    752 {
    753   const struct sln_vertex* vtx0 = NULL;
    754   const struct sln_vertex* vtx1 = NULL;
    755   const float nu = (float)wavenumber;
    756   size_t n; /* #vertices */
    757   double u; /* Linear interpolation parameter */
    758   ASSERT(mesh && mesh->nvertices);
    759 
    760   n = mesh->nvertices;
    761 
    762   /* Handle special cases */
    763   if(n == 1) return mesh->vertices[0].ka;
    764   if(nu < mesh->vertices[0].wavenumber
    765   || nu > mesh->vertices[n-1].wavenumber) {
    766     return 0;
    767   }
    768   if(nu == mesh->vertices[0].wavenumber) return mesh->vertices[0].ka;
    769   if(nu == mesh->vertices[n-1].wavenumber) return mesh->vertices[n-1].ka;
    770 
    771   /* Dichotomic search of the mesh vertex whose wavenumber is greater than or
    772    * equal to the submitted wavenumber 'nu' */
    773   vtx1 = search_lower_bound(&nu, mesh->vertices, n, sizeof(*vtx1), cmp_nu_vtx);
    774   vtx0 = vtx1 - 1;
    775   ASSERT(vtx1); /* A vertex is necessary found ...*/
    776   ASSERT(vtx1 > mesh->vertices); /* ... and it cannot be the first one */
    777   ASSERT(vtx0->wavenumber < nu && nu <= vtx1->wavenumber);
    778 
    779   /* Compute the linear interpolation parameter */
    780   u = (wavenumber - vtx0->wavenumber) / (vtx1->wavenumber - vtx0->wavenumber);
    781   u = CLAMP(u, 0, 1); /* Handle numerical imprecisions */
    782 
    783   if(u == 0) return vtx0->ka;
    784   if(u == 1) return vtx1->ka;
    785   return u*(vtx1->ka - vtx0->ka) + vtx0->ka;
    786 }
    787 
    788 res_T
    789 sln_tree_write
    790   (const struct sln_tree* tree,
    791    const struct sln_tree_write_args* args)
    792 {
    793   struct stream stream = STREAM_NULL;
    794   size_t nnodes, nverts;
    795   hash256_T hash_mdata;
    796   hash256_T hash_lines;
    797   res_T res = RES_OK;
    798 
    799   if(!tree) { res = RES_BAD_ARG; goto error; }
    800   res = check_sln_tree_write_args(tree->sln, FUNC_NAME, args);
    801   if(res != RES_OK) goto error;
    802 
    803   res = shtr_isotope_metadata_hash(tree->args.metadata, hash_mdata);
    804   if(res != RES_OK) goto error;
    805   res = shtr_line_list_hash(tree->args.lines, hash_lines);
    806   if(res != RES_OK) goto error;
    807 
    808   res = stream_init
    809     (tree->sln, FUNC_NAME, args->filename, args->file, "w", &stream);
    810   if(res != RES_OK) goto error;
    811 
    812   #define WRITE(Var, Nb) {                                                     \
    813     if(fwrite((Var), sizeof(*(Var)), (Nb), stream.fp) != (Nb)) {               \
    814       ERROR(tree->sln, "%s:%s: error writing the tree -- %s\n",                \
    815         FUNC_NAME, stream.name, strerror(errno));                              \
    816       res = RES_IO_ERR;                                                        \
    817       goto error;                                                              \
    818     }                                                                          \
    819   } (void)0
    820   WRITE(&SLN_TREE_VERSION, 1);
    821   WRITE(hash_mdata, sizeof(hash256_T));
    822   WRITE(hash_lines, sizeof(hash256_T));
    823 
    824   nnodes = darray_node_size_get(&tree->nodes);
    825   WRITE(&nnodes, 1);
    826   WRITE(darray_node_cdata_get(&tree->nodes), nnodes);
    827 
    828   nverts = darray_vertex_size_get(&tree->vertices);
    829   WRITE(&nverts, 1);
    830   WRITE(darray_vertex_cdata_get(&tree->vertices), nverts);
    831 
    832   WRITE(&tree->args.line_profile, 1);
    833   WRITE(&tree->args.molecules, 1);
    834   WRITE(&tree->args.pressure, 1);
    835   WRITE(&tree->args.temperature, 1);
    836   WRITE(&tree->args.nvertices_hint, 1);
    837   WRITE(&tree->args.mesh_decimation_err, 1);
    838   WRITE(&tree->args.mesh_type, 1);
    839   WRITE(&tree->args.arity, 1);
    840   WRITE(&tree->args.leaf_nlines, 1);
    841   #undef WRITE
    842 
    843 exit:
    844   stream_release(&stream);
    845   return res;
    846 error:
    847   goto exit;
    848 }