star-hitran

Load line-by-line data from the HITRAN database
git clone git://git.meso-star.fr/star-hitran.git
Log | Files | Refs | README | LICENSE

shtr_isotope_metadata.c (29775B)


      1 /* Copyright (C) 2022, 2025, 2026 |Méso|Star> (contact@meso-star.com)
      2  * Copyright (C) 2025, 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 #define _POSIX_C_SOURCE 200112L /* strtok_r support */
     20 
     21 #include "shtr.h"
     22 #include "shtr_c.h"
     23 #include "shtr_param.h"
     24 
     25 #include <rsys/cstr.h>
     26 #include <rsys/dynamic_array_char.h>
     27 #include <rsys/ref_count.h>
     28 #include <rsys/str.h>
     29 #include <rsys/text_reader.h>
     30 
     31 #include <ctype.h>
     32 #include <string.h>
     33 
     34 /* Version of the isotope metadata. One should increment it and perform a
     35  * version management onto serialized data when the isotope metadata structure
     36  * is updated. */
     37 static const int SHTR_ISOTOPE_METADATA_VERSION = 0;
     38 
     39 /* Generate the dynamic array of isotopes */
     40 #define DARRAY_NAME isotope
     41 #define DARRAY_DATA struct shtr_isotope
     42 #include <rsys/dynamic_array.h>
     43 
     44 struct molecule {
     45   struct str name;
     46   size_t isotopes_range[2]; /* Range of the [1st and last[ isotopes */
     47   int id; /* Unique identifier of the molecule */
     48 };
     49 #define MOLECULE_IS_VALID(Molecule) ((Molecule)->id >= 0)
     50 
     51 static INLINE void
     52 molecule_clear(struct molecule* molecule)
     53 {
     54   ASSERT(molecule);
     55   molecule->isotopes_range[0] = SIZE_MAX;
     56   molecule->isotopes_range[1] = 0;
     57   molecule->id = -1;
     58 }
     59 
     60 static INLINE void
     61 molecule_init(struct mem_allocator* allocator, struct molecule* molecule)
     62 {
     63   str_init(allocator, &molecule->name);
     64   molecule_clear(molecule);
     65 }
     66 
     67 static INLINE void
     68 molecule_release(struct molecule* molecule)
     69 {
     70   str_release(&molecule->name);
     71 }
     72 
     73 static INLINE res_T
     74 molecule_copy(struct molecule* dst, const struct molecule* src)
     75 {
     76   dst->isotopes_range[0] = src->isotopes_range[0];
     77   dst->isotopes_range[1] = src->isotopes_range[1];
     78   dst->id = src->id;
     79   return str_copy(&dst->name, &src->name);
     80 }
     81 
     82 static INLINE res_T
     83 molecule_copy_and_release(struct molecule* dst, struct molecule* src)
     84 {
     85   dst->isotopes_range[0] = src->isotopes_range[0];
     86   dst->isotopes_range[1] = src->isotopes_range[1];
     87   dst->id = src->id;
     88   return str_copy_and_release(&dst->name, &src->name);
     89 }
     90 
     91 /* Generate the dynamic array of molecules */
     92 #define DARRAY_NAME molecule
     93 #define DARRAY_DATA struct molecule
     94 #define DARRAY_FUNCTOR_INIT molecule_init
     95 #define DARRAY_FUNCTOR_RELEASE molecule_release
     96 #define DARRAY_FUNCTOR_COPY molecule_copy
     97 #define DARRAY_FUNCTOR_COPY_AND_RELEASE molecule_copy_and_release
     98 #include <rsys/dynamic_array.h>
     99 
    100 struct shtr_isotope_metadata {
    101   /* List of molecules and isotopes */
    102   struct darray_molecule molecules;
    103   struct darray_isotope isotopes;
    104 
    105   /* Map the global identifier of a molecule to its correspond local index into
    106    * the dynamic array into which it is registered */
    107   int molid2idx[SHTR_MAX_MOLECULE_COUNT];
    108 
    109   struct shtr* shtr;
    110   ref_T ref;
    111 };
    112 
    113 /*******************************************************************************
    114  * Helper functions
    115  ******************************************************************************/
    116 static res_T
    117 create_isotope_metadata
    118   (struct shtr* shtr,
    119    struct shtr_isotope_metadata** out_isotopes)
    120 {
    121   struct shtr_isotope_metadata* metadata = NULL;
    122   res_T res = RES_OK;
    123   ASSERT(shtr && out_isotopes);
    124 
    125   metadata = MEM_CALLOC(shtr->allocator, 1, sizeof(*metadata));
    126   if(!metadata) {
    127     ERROR(shtr,
    128       "Could not allocate the isotope metadata data structure.\n");
    129     res = RES_MEM_ERR;
    130     goto error;
    131   }
    132   ref_init(&metadata->ref);
    133   SHTR(ref_get(shtr));
    134   metadata->shtr = shtr;
    135   darray_molecule_init(shtr->allocator, &metadata->molecules);
    136   darray_isotope_init(shtr->allocator, &metadata->isotopes);
    137   memset(metadata->molid2idx, 0xFF, sizeof(metadata->molid2idx));
    138 
    139 exit:
    140   *out_isotopes = metadata;
    141   return res;
    142 error:
    143   if(metadata) {
    144     SHTR(isotope_metadata_ref_put(metadata));
    145     metadata = NULL;
    146   }
    147   goto exit;
    148 }
    149 
    150 static res_T
    151 flush_molecule
    152   (struct shtr_isotope_metadata* metadata,
    153    struct molecule* molecule, /* Currently parsed molecule */
    154    struct txtrdr* txtrdr)
    155 {
    156   size_t nisotopes = 0;
    157   size_t ientry = SIZE_MAX;
    158   size_t* pimolecule = NULL;
    159   res_T res = RES_OK;
    160   ASSERT(metadata && molecule && MOLECULE_IS_VALID(molecule));
    161 
    162   /* Fetch _exclusive_ upper bound */
    163   molecule->isotopes_range[1] =
    164     darray_isotope_size_get(&metadata->isotopes);
    165   if(molecule->isotopes_range[0] >= molecule->isotopes_range[1]) {
    166     WARN(metadata->shtr,
    167       "%s:%lu: the %s molecule does not have any isotope.\n",
    168       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
    169       str_cget(&molecule->name));
    170   }
    171 
    172   nisotopes = molecule->isotopes_range[1] - molecule->isotopes_range[0];
    173   if(nisotopes >= SHTR_MAX_ISOTOPE_COUNT) {
    174     ERROR(metadata->shtr,
    175       "%s:%lu: %s has %lu isotopes, while the maximum number supported is %d.\n",
    176       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
    177       str_cget(&molecule->name), (unsigned long)nisotopes,
    178       SHTR_MAX_ISOTOPE_COUNT);
    179     res = RES_MEM_ERR;
    180     goto error;
    181   }
    182 
    183   /* Fetch the index where the molecule is going to be store */
    184   ientry = darray_molecule_size_get(&metadata->molecules);
    185   CHK(ientry < SHTR_MAX_MOLECULE_COUNT);
    186 
    187   /* Store the molecule */
    188   res = darray_molecule_push_back(&metadata->molecules, molecule);
    189   if(res != RES_OK) {
    190     ERROR(metadata->shtr,
    191       "%s: error storing the %s molecule -- %s.\n",
    192       txtrdr_get_name(txtrdr), str_cget(&molecule->name), res_to_cstr(res));
    193     goto error;
    194   }
    195 
    196   /* Register the molecule */
    197   if(metadata->molid2idx[molecule->id] >= 0) {
    198     const struct molecule* molecule2 = NULL;
    199     molecule2 = darray_molecule_cdata_get(&metadata->molecules) + *pimolecule;
    200     ERROR(metadata->shtr,
    201       "%s: cannot register the %s molecule. "
    202       "The %s molecule has the same identifier %i.\n",
    203       txtrdr_get_name(txtrdr),
    204       str_cget(&molecule->name),
    205       str_cget(&molecule2->name),
    206       molecule->id);
    207     res = RES_OK;
    208     goto error;
    209   }
    210   ASSERT((size_t)((int)ientry) == ientry);
    211   metadata->molid2idx[molecule->id] = (int)ientry;
    212   molecule_clear(molecule);
    213 
    214 exit:
    215   return res;
    216 error:
    217   if(ientry != SIZE_MAX) darray_molecule_resize(&metadata->molecules, ientry);
    218   metadata->molid2idx[molecule->id] = -1;
    219   goto exit;
    220 }
    221 
    222 static res_T
    223 parse_molecule
    224   (struct shtr_isotope_metadata* metadata,
    225    struct molecule* molecule,
    226    struct txtrdr* txtrdr)
    227 {
    228   char* name = NULL;
    229   char* id = NULL;
    230   char* tk = NULL;
    231   char* tk_ctx = NULL;
    232   size_t len;
    233   res_T res = RES_OK;
    234   ASSERT(molecule && txtrdr);
    235 
    236   name = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx);
    237   id = strtok_r(NULL, " \t", &tk_ctx);
    238 
    239   if(!name) {
    240     ERROR(metadata->shtr, "%s:%lu: molecule name is missing.\n",
    241       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr));
    242     res = RES_BAD_ARG;
    243     goto error;
    244   }
    245 
    246   res = str_set(&molecule->name, name);
    247   if(res != RES_OK) {
    248     ERROR(metadata->shtr,
    249       "%s:%lu: error seting the molecule name `%s' -- %s.\n",
    250       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
    251       name, res_to_cstr(res));
    252     goto error;
    253   }
    254 
    255   len = strlen(id);
    256   if(!id || !len || id[0] != '(' || id[len-1] != ')') {
    257     ERROR(metadata->shtr,
    258       "%s:%lu: invalid definition of the molecule identifier.\n",
    259       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr));
    260     res = RES_BAD_ARG;
    261     goto error;
    262   }
    263 
    264   id[len-1] = '\0'; /* Rm trailing parenthesis */
    265   res = cstr_to_int(id+1/*Rm leading parenthesis*/, &molecule->id);
    266   if(res != RES_OK || !MOLECULE_IS_VALID(molecule)) {
    267     id[len-1] = ')'; /* Re-add the trailing parenthesis */
    268     ERROR(metadata->shtr, "%s:%lu: invalid molecule identifier `%s'.\n",
    269       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), id);
    270     res = RES_BAD_ARG;
    271     goto error;
    272   }
    273 
    274   tk = strtok_r(NULL, " \t", &tk_ctx);
    275   if(tk) {
    276     WARN(metadata->shtr, "%s:%lu: unexpected text `%s'.\n",
    277       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk);
    278   }
    279 
    280   molecule->isotopes_range[0] = darray_isotope_size_get(&metadata->isotopes);
    281 
    282 exit:
    283   return res;
    284 error:
    285   goto exit;
    286 }
    287 
    288 static res_T
    289 parse_isotope
    290   (struct shtr_isotope_metadata* metadata,
    291    struct txtrdr* txtrdr)
    292 {
    293   struct shtr_isotope isotope = SHTR_ISOTOPE_NULL;
    294   struct param_desc param_desc = PARAM_DESC_NULL;
    295   struct shtr* shtr = NULL;
    296   char* tk = NULL;
    297   char* tk_ctx = NULL;
    298   size_t local_id = SIZE_MAX;
    299   res_T res = RES_OK;
    300   ASSERT(metadata && txtrdr);
    301 
    302   shtr = metadata->shtr;
    303   param_desc.path = txtrdr_get_name(txtrdr);
    304   param_desc.line = txtrdr_get_line_num(txtrdr);
    305 
    306   /* Fetch the index of the molecule to which the isotope belongs */
    307   isotope.molecule_id_local = darray_molecule_size_get(&metadata->molecules);
    308 
    309   tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx);
    310   param_desc.name = "isotope id";
    311   param_desc.low = 0;
    312   param_desc.upp = INT_MAX;
    313   param_desc.is_low_incl = 1;
    314   param_desc.is_upp_incl = 1;
    315   res = parse_param_int(shtr, tk, &param_desc, &isotope.id);
    316   if(res != RES_OK) goto error;
    317 
    318   tk = strtok_r(NULL, " \t", &tk_ctx);
    319   param_desc.name = "isotope abundance";
    320   param_desc.low = 0;
    321   param_desc.upp = 1;
    322   param_desc.is_low_incl = 0;
    323   param_desc.is_upp_incl = 1;
    324   res = parse_param_double(shtr, tk, &param_desc, &isotope.abundance);
    325   if(res != RES_OK) goto error;
    326 
    327   tk = strtok_r(NULL, " \t", &tk_ctx);
    328   param_desc.name = "isotope Q(296K)";
    329   param_desc.low = 0;
    330   param_desc.upp = INF;
    331   param_desc.is_low_incl = 0;
    332   param_desc.is_upp_incl = 1;
    333   res = parse_param_double(shtr, tk, &param_desc, &isotope.Q296K);
    334   if(res !=  RES_OK) goto error;
    335 
    336   tk = strtok_r(NULL, " \t", &tk_ctx);
    337   param_desc.name = "isotope state independant degeneracy factor";
    338   param_desc.low = -INT_MAX;
    339   param_desc.upp =  INT_MAX;
    340   param_desc.is_low_incl = 1;
    341   param_desc.is_upp_incl = 1;
    342   res = parse_param_int(shtr, tk, &param_desc, &isotope.gj);
    343   if(res != RES_OK) goto error;
    344 
    345   tk = strtok_r(NULL, " \t", &tk_ctx),
    346   param_desc.name = "isotope molar mass";
    347   param_desc.low = 0;
    348   param_desc.upp = INF;
    349   param_desc.is_low_incl = 0;
    350   param_desc.is_upp_incl = 1;
    351   res = parse_param_double(shtr, tk, &param_desc, &isotope.molar_mass);
    352   if(res != RES_OK) goto error;
    353 
    354   local_id = darray_isotope_size_get(&metadata->isotopes);
    355 
    356   /* Store the isotope */
    357   res = darray_isotope_push_back(&metadata->isotopes, &isotope);
    358   if(res != RES_OK) {
    359     ERROR(shtr, "%s:%lu: error storing the isotope %d -- %s.\n",
    360       param_desc.path, param_desc.line, isotope.id, res_to_cstr(res));
    361     res = RES_OK;
    362     goto error;
    363   }
    364 
    365 exit:
    366   return res;
    367 error:
    368   if(local_id != SIZE_MAX) darray_isotope_resize(&metadata->isotopes, local_id);
    369   goto exit;
    370 }
    371 
    372 static res_T
    373 parse_line
    374   (struct shtr_isotope_metadata* metadata,
    375    struct molecule* molecule, /* Currently parsed molecule */
    376    struct txtrdr* txtrdr)
    377 {
    378   const char* line = NULL;
    379   size_t i;
    380   res_T res = RES_OK;
    381   ASSERT(metadata && molecule && txtrdr);
    382 
    383   line = txtrdr_get_cline(txtrdr);
    384   ASSERT(line);
    385   i = strspn(line, " \t");
    386   ASSERT(i < strlen(line));
    387 
    388   if(isalpha(line[i])) {
    389     if(MOLECULE_IS_VALID(molecule)) {
    390       res = flush_molecule(metadata, molecule, txtrdr);
    391       if(res != RES_OK) goto error;
    392     }
    393     res = parse_molecule(metadata, molecule, txtrdr);
    394     if(res != RES_OK) goto error;
    395   } else {
    396     if(!MOLECULE_IS_VALID(molecule)) {
    397       ERROR(metadata->shtr, "%s:%lu: missing a molecule definition.\n",
    398         txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr));
    399       res = RES_BAD_ARG;
    400       goto error;
    401     }
    402     res = parse_isotope(metadata, txtrdr);
    403     if(res != RES_OK) goto error;
    404   }
    405 
    406 exit:
    407   return res;
    408 error:
    409   goto exit;
    410 }
    411 
    412 static res_T
    413 load_stream
    414   (struct shtr* shtr,
    415    FILE* stream,
    416    const char* name,
    417    struct shtr_isotope_metadata** out_isotopes)
    418 {
    419   struct molecule molecule; /* Current molecule */
    420   struct shtr_isotope_metadata* metadata = NULL;
    421   struct txtrdr* txtrdr = NULL;
    422   res_T res = RES_OK;
    423   ASSERT(shtr && stream && name && out_isotopes);
    424 
    425   molecule_init(shtr->allocator, &molecule);
    426 
    427   res = create_isotope_metadata(shtr, &metadata);
    428   if(res != RES_OK) goto error;
    429 
    430   res = txtrdr_stream(metadata->shtr->allocator, stream, name,
    431     0/*No comment char*/, &txtrdr);
    432   if(res != RES_OK) {
    433     ERROR(shtr, "%s: error creating the text reader -- %s.\n",
    434       name, res_to_cstr(res));
    435     goto error;
    436   }
    437 
    438   #define READ_LINE {                                                          \
    439     res = txtrdr_read_line(txtrdr);                                            \
    440     if(res != RES_OK) {                                                        \
    441       ERROR(shtr, "%s: error reading the line `%lu' -- %s.\n",               \
    442         name, (unsigned long)txtrdr_get_line_num(txtrdr), res_to_cstr(res));   \
    443       goto error;                                                              \
    444     }                                                                          \
    445   } (void)0
    446 
    447   /* Skip the 1st line that is a comment line */
    448   READ_LINE;
    449   if(!txtrdr_get_cline(txtrdr)) goto exit;
    450 
    451   for(;;) {
    452     READ_LINE;
    453 
    454     if(!txtrdr_get_cline(txtrdr)) break; /* No more parsed line */
    455     res = parse_line(metadata, &molecule, txtrdr);
    456     if(res != RES_OK) goto error;
    457   }
    458   #undef READ_LINE
    459 
    460   if(MOLECULE_IS_VALID(&molecule)) {
    461     res = flush_molecule(metadata, &molecule, txtrdr);
    462     if(res != RES_OK) goto error;
    463   }
    464 
    465 exit:
    466   if(txtrdr) txtrdr_ref_put(txtrdr);
    467   *out_isotopes = metadata;
    468   molecule_release(&molecule);
    469   return res;
    470 error:
    471   if(metadata) {
    472     SHTR(isotope_metadata_ref_put(metadata));
    473     metadata = NULL;
    474   }
    475   goto exit;
    476 }
    477 
    478 static res_T
    479 write_molecules
    480   (const struct shtr_isotope_metadata* metadata,
    481    const char* caller,
    482    FILE* stream)
    483 {
    484   const struct molecule* molecules = NULL;
    485   size_t i, nmolecules;
    486   res_T res = RES_OK;
    487   ASSERT(metadata && caller && stream);
    488 
    489   molecules = darray_molecule_cdata_get(&metadata->molecules);
    490   nmolecules = darray_molecule_size_get(&metadata->molecules);
    491 
    492   #define WRITE(Var, Nb) {                                                     \
    493     if(fwrite((Var), sizeof(*(Var)), (Nb), stream) != (Nb)) {                  \
    494       res = RES_IO_ERR;                                                        \
    495       goto error;                                                              \
    496     }                                                                          \
    497   } (void)0
    498   WRITE(&nmolecules, 1);
    499 
    500   FOR_EACH(i, 0, nmolecules) {
    501     const struct molecule* molecule = molecules + i;
    502     const size_t len = str_len(&molecule->name);
    503     WRITE(&len, 1);
    504     WRITE(str_cget(&molecule->name), len);
    505     WRITE(molecule->isotopes_range, 2);
    506     WRITE(&molecule->id, 1);
    507   }
    508   #undef WRITE
    509 
    510 exit:
    511   return res;
    512 error:
    513   ERROR(metadata->shtr, "%s: error writing molecules\n", caller);
    514   goto exit;
    515 }
    516 
    517 static res_T
    518 read_molecules
    519   (struct shtr_isotope_metadata* metadata,
    520    const char* caller,
    521    FILE* stream)
    522 {
    523   struct darray_char name;
    524   struct molecule* molecules = NULL;
    525   size_t i, nmolecules;
    526   res_T res = RES_OK;
    527   ASSERT(metadata && caller && stream);
    528 
    529   darray_char_init(metadata->shtr->allocator, &name);
    530 
    531   #define READ(Var, Nb) {                                                      \
    532     if(fread((Var), sizeof(*(Var)), (Nb), stream) != (Nb)) {                   \
    533       if(feof(stream)) {                                                       \
    534         res = RES_BAD_ARG;                                                     \
    535       } else if(ferror(stream)) {                                              \
    536         res = RES_IO_ERR;                                                      \
    537       } else {                                                                 \
    538         res = RES_UNKNOWN_ERR;                                                 \
    539       }                                                                        \
    540       goto error;                                                              \
    541     }                                                                          \
    542   } (void)0
    543 
    544   READ(&nmolecules, 1);
    545 
    546   res = darray_molecule_resize(&metadata->molecules, nmolecules);
    547   if(res != RES_OK) goto error;
    548 
    549   molecules = darray_molecule_data_get(&metadata->molecules);
    550   FOR_EACH(i, 0, nmolecules) {
    551     struct molecule* molecule = molecules + i;
    552     size_t len = 0;
    553 
    554     READ(&len, 1);
    555     res = darray_char_resize(&name, len+1);
    556     if(res != RES_OK) goto error;
    557 
    558     READ(darray_char_data_get(&name), len);
    559     darray_char_data_get(&name)[len] = '\0';
    560     res = str_set(&molecule->name, darray_char_data_get(&name));
    561     if(res != RES_OK) goto error;
    562 
    563     READ(molecule->isotopes_range, 2);
    564     READ(&molecule->id, 1);
    565   }
    566   #undef READ
    567 
    568 exit:
    569   darray_char_release(&name);
    570   return res;
    571 error:
    572   ERROR(metadata->shtr, "%s: error reading molecules -- %s.\n",
    573     caller, res_to_cstr(res));
    574   goto exit;
    575 }
    576 
    577 static res_T
    578 write_isotopes
    579   (const struct shtr_isotope_metadata* metadata,
    580    const char* caller,
    581    FILE* stream)
    582 {
    583   const struct shtr_isotope* isotopes = NULL;
    584   size_t nisotopes = 0;
    585   res_T res = RES_OK;
    586   ASSERT(metadata && caller && stream);
    587 
    588   isotopes = darray_isotope_cdata_get(&metadata->isotopes);
    589   nisotopes = darray_isotope_size_get(&metadata->isotopes);
    590 
    591   #define WRITE(Var, Nb) {                                                     \
    592     if(fwrite((Var), sizeof(*(Var)), (Nb), stream) != (Nb)) {                  \
    593       ERROR(metadata->shtr, "%s: error writing isotopes\n", caller);         \
    594       res = RES_IO_ERR;                                                        \
    595       goto error;                                                              \
    596     }                                                                          \
    597   } (void)0
    598   WRITE(&nisotopes, 1);
    599   WRITE(isotopes, nisotopes);
    600   #undef WRITE
    601 
    602 exit:
    603   return res;
    604 error:
    605   goto exit;
    606 }
    607 
    608 static res_T
    609 read_isotopes
    610   (struct shtr_isotope_metadata* metadata,
    611    const char* caller,
    612    FILE* stream)
    613 {
    614   size_t nisotopes = 0;
    615   res_T res = RES_OK;
    616   ASSERT(metadata && caller && stream);
    617 
    618   #define READ(Var, Nb) {                                                      \
    619     if(fread((Var), sizeof(*(Var)), (Nb), stream) != (Nb)) {                   \
    620       if(feof(stream)) {                                                       \
    621         res = RES_BAD_ARG;                                                     \
    622       } else if(ferror(stream)) {                                              \
    623         res = RES_IO_ERR;                                                      \
    624       } else {                                                                 \
    625         res = RES_UNKNOWN_ERR;                                                 \
    626       }                                                                        \
    627       goto error;                                                              \
    628     }                                                                          \
    629   } (void)0
    630   READ(&nisotopes, 1);
    631   res = darray_isotope_resize(&metadata->isotopes, nisotopes);
    632   if(res != RES_OK) goto error;
    633   READ(darray_isotope_data_get(&metadata->isotopes), nisotopes);
    634   #undef READ
    635 
    636 exit:
    637   return res;
    638 error:
    639   ERROR(metadata->shtr, "%s: error reading isotopes -- %s.\n",
    640     caller, res_to_cstr(res));
    641   goto exit;
    642 }
    643 
    644 static int
    645 cmp_isotopes(const void* a, const void* b)
    646 {
    647   const struct shtr_isotope* isotope0 = a;
    648   const struct shtr_isotope* isotope1 = b;
    649   ASSERT(a && b);
    650   return isotope0->id - isotope1->id;
    651 }
    652 
    653 static INLINE void
    654 hash_isotope(const struct shtr_isotope* isotope, struct sha256_ctx* ctx)
    655 {
    656   ASSERT(isotope && ctx);
    657 
    658   #define HASH(Var) \
    659     sha256_ctx_update(ctx, (const char*)(&isotope->Var), sizeof(isotope->Var))
    660   HASH(abundance);
    661   HASH(Q296K);
    662   HASH(molar_mass);
    663   HASH(gj);
    664   HASH(id);
    665   #undef HASH
    666 }
    667 
    668 static INLINE void
    669 hash_molecule
    670   (const struct shtr_isotope_metadata* metadata,
    671    const int molecule_id,
    672    struct sha256_ctx* ctx)
    673 {
    674   const struct molecule* molecule = NULL;
    675   const struct shtr_isotope* isotopes = NULL;
    676   struct shtr_isotope isotopes_sorted[SHTR_MAX_ISOTOPE_COUNT] = {0};
    677   size_t i = 0;
    678   size_t nisotopes = 0;
    679 
    680   ASSERT(metadata && molecule_id >= 0 && ctx);
    681   ASSERT((size_t)molecule_id < darray_molecule_size_get(&metadata->molecules));
    682 
    683   molecule = darray_molecule_cdata_get(&metadata->molecules) + molecule_id;
    684 
    685   #define HASH(Bytes, Size) \
    686     sha256_ctx_update(ctx, (const char*)(Bytes), (Size))
    687   HASH(str_cget(&molecule->name), str_len(&molecule->name));
    688   HASH(&molecule->id, sizeof(molecule->id));
    689   #undef HASH
    690 
    691   nisotopes = molecule->isotopes_range[1] - molecule->isotopes_range[0];
    692   ASSERT(nisotopes <= SHTR_MAX_ISOTOPE_COUNT);
    693 
    694   isotopes = darray_isotope_cdata_get(&metadata->isotopes)
    695            + molecule->isotopes_range[0];
    696 
    697   /* Sort molecular isotopes according to their identifier so that the
    698    * molecule's hash is independent of the order in which its isotopes are
    699    * loaded. */
    700   FOR_EACH(i, 0, nisotopes) isotopes_sorted[i] = isotopes[i];
    701   qsort(isotopes_sorted, nisotopes, sizeof(struct shtr_isotope), cmp_isotopes);
    702 
    703   FOR_EACH(i, 0, nisotopes) hash_isotope(isotopes_sorted+i, ctx);
    704 }
    705 
    706 static void
    707 hash_molecule_list
    708   (const struct shtr_isotope_metadata* metadata,
    709    struct sha256_ctx* ctx)
    710 {
    711   size_t i = 0;
    712   size_t nmolecules = 0;
    713   ASSERT(metadata && ctx);
    714 
    715   nmolecules = darray_molecule_size_get(&metadata->molecules);
    716 
    717   /* Iterate over the molecules in order of their ID to ensure that their
    718    * loading order does not affect the hash value of the list */
    719   FOR_EACH(i, 0, SHTR_MAX_MOLECULE_COUNT) {
    720     if(metadata->molid2idx[i] >= 0) {
    721       hash_molecule(metadata, metadata->molid2idx[i], ctx);
    722       --nmolecules;
    723       if(nmolecules == 0) break; /* All loaded molecules have been hashed */
    724     }
    725   }
    726 }
    727 
    728 static void
    729 release_isotope_metadata(ref_T* ref)
    730 {
    731   struct shtr* shtr = NULL;
    732   struct shtr_isotope_metadata* metadata = CONTAINER_OF
    733     (ref, struct shtr_isotope_metadata, ref);
    734   ASSERT(ref);
    735   shtr = metadata->shtr;
    736   darray_molecule_release(&metadata->molecules);
    737   darray_isotope_release(&metadata->isotopes);
    738   MEM_RM(shtr->allocator, metadata);
    739   SHTR(ref_put(shtr));
    740 }
    741 
    742 /*******************************************************************************
    743  * Exported functions
    744  ******************************************************************************/
    745 res_T
    746 shtr_isotope_metadata_load
    747   (struct shtr* shtr,
    748    const char* path,
    749    struct shtr_isotope_metadata** metadata)
    750 {
    751   FILE* file = NULL;
    752   res_T res = RES_OK;
    753 
    754   if(!shtr || !path || !metadata) {
    755     res = RES_BAD_ARG;
    756     goto error;
    757   }
    758 
    759   file = fopen(path, "r");
    760   if(!file) {
    761     ERROR(shtr, "%s: error opening file `%s'.\n", FUNC_NAME, path);
    762     res = RES_IO_ERR;
    763     goto error;
    764   }
    765 
    766   res = load_stream(shtr, file, path, metadata);
    767   if(res != RES_OK) goto error;
    768 
    769 exit:
    770   if(file) fclose(file);
    771   return res;
    772 error:
    773   goto exit;
    774 }
    775 
    776 res_T
    777 shtr_isotope_metadata_load_stream
    778   (struct shtr* shtr,
    779    FILE* stream,
    780    const char* stream_name,
    781    struct shtr_isotope_metadata** metadata)
    782 {
    783   if(!shtr || !stream || !metadata) return RES_BAD_ARG;
    784   return load_stream
    785     (shtr, stream, stream_name ? stream_name : "<stream>", metadata);
    786 }
    787 
    788 res_T
    789 shtr_isotope_metadata_create_from_stream
    790   (struct shtr* shtr,
    791    FILE* stream,
    792    struct shtr_isotope_metadata** out_metadata)
    793 {
    794   struct shtr_isotope_metadata* metadata = NULL;
    795   int version = 0;
    796   res_T res = RES_OK;
    797 
    798   if(!shtr || !out_metadata || !stream) {
    799     res = RES_BAD_ARG;
    800     goto error;
    801   }
    802 
    803   res = create_isotope_metadata(shtr, &metadata);
    804   if(res != RES_OK) goto error;
    805 
    806   #define READ(Var, Nb) {                                                      \
    807     if(fread((Var), sizeof(*(Var)), (Nb), stream) != (Nb)) {                   \
    808       if(feof(stream)) {                                                       \
    809         res = RES_BAD_ARG;                                                     \
    810       } else if(ferror(stream)) {                                              \
    811         res = RES_IO_ERR;                                                      \
    812       } else {                                                                 \
    813         res = RES_UNKNOWN_ERR;                                                 \
    814       }                                                                        \
    815       ERROR(shtr, "%s: error reading isotope metadata -- %s.\n",             \
    816         FUNC_NAME, res_to_cstr(res));                                          \
    817       goto error;                                                              \
    818     }                                                                          \
    819   } (void)0
    820   READ(&version, 1);
    821   if(version != SHTR_ISOTOPE_METADATA_VERSION) {
    822     ERROR(shtr,
    823       "%s: unexpected isotope metadata version %d. "
    824       "Expecting a isotope metadata in version %d.\n",
    825       FUNC_NAME, version, SHTR_ISOTOPE_METADATA_VERSION);
    826     res = RES_BAD_ARG;
    827     goto error;
    828   }
    829 
    830   res = read_molecules(metadata, FUNC_NAME, stream);
    831   if(res != RES_OK) goto error;
    832   res = read_isotopes(metadata, FUNC_NAME, stream);
    833   if(res != RES_OK) goto error;
    834 
    835   READ(metadata->molid2idx, SHTR_MAX_MOLECULE_COUNT);
    836   #undef READ
    837 
    838 exit:
    839   if(out_metadata) *out_metadata = metadata;
    840   return res;
    841 error:
    842   if(metadata) {
    843     SHTR(isotope_metadata_ref_put(metadata));
    844     metadata = NULL;
    845   }
    846   goto exit;
    847 }
    848 
    849 res_T
    850 shtr_isotope_metadata_ref_get
    851   (struct shtr_isotope_metadata* metadata)
    852 {
    853   if(!metadata) return RES_BAD_ARG;
    854   ref_get(&metadata->ref);
    855   return RES_OK;
    856 }
    857 
    858 res_T
    859 shtr_isotope_metadata_ref_put
    860   (struct shtr_isotope_metadata* metadata)
    861 {
    862   if(!metadata) return RES_BAD_ARG;
    863   ref_put(&metadata->ref, release_isotope_metadata);
    864   return RES_OK;
    865 }
    866 
    867 res_T
    868 shtr_isotope_metadata_get_molecules_count
    869   (const struct shtr_isotope_metadata* metadata,
    870    size_t* nmolecules)
    871 {
    872   if(!metadata || !nmolecules) return RES_BAD_ARG;
    873   *nmolecules = darray_molecule_size_get(&metadata->molecules);
    874   return RES_OK;
    875 }
    876 
    877 res_T
    878 shtr_isotope_metadata_get_isotopes_count
    879   (const struct shtr_isotope_metadata* metadata,
    880    size_t* nisotopes)
    881 {
    882   if(!metadata || !nisotopes) return RES_BAD_ARG;
    883   *nisotopes = darray_isotope_size_get(&metadata->isotopes);
    884   return RES_OK;
    885 }
    886 
    887 res_T
    888 shtr_isotope_metadata_get_molecule
    889   (const struct shtr_isotope_metadata* metadata,
    890    const size_t imolecule,
    891    struct shtr_molecule* out_molecule)
    892 {
    893   const struct molecule* molecule = NULL;
    894   res_T res = RES_OK;
    895 
    896   if(!metadata || !out_molecule) {
    897     res = RES_BAD_ARG;
    898     goto error;
    899   }
    900   if(imolecule >= darray_molecule_size_get(&metadata->molecules)) {
    901     ERROR(metadata->shtr, "%s: invalid molecule index %lu.\n",
    902       FUNC_NAME, (unsigned long)imolecule);
    903     res = RES_BAD_ARG;
    904     goto error;
    905   }
    906 
    907   molecule = darray_molecule_cdata_get(&metadata->molecules) + imolecule;
    908   out_molecule->name = str_cget(&molecule->name);
    909   out_molecule->id = molecule->id;
    910   out_molecule->nisotopes =
    911     molecule->isotopes_range[1] - molecule->isotopes_range[0];
    912   out_molecule->isotopes =
    913     darray_isotope_cdata_get(&metadata->isotopes) + molecule->isotopes_range[0];
    914 
    915 exit:
    916   return res;
    917 error:
    918   goto exit;
    919 }
    920 
    921 res_T
    922 shtr_isotope_metadata_find_molecule
    923   (struct shtr_isotope_metadata* metadata,
    924    const int molecule_id,
    925    struct shtr_molecule* out_molecule)
    926 {
    927   int imolecule = 0;
    928   res_T res = RES_OK;
    929 
    930   if(!metadata || !out_molecule) {
    931     res = RES_BAD_ARG;
    932     goto error;
    933   }
    934 
    935   imolecule = metadata->molid2idx[molecule_id];
    936   if(imolecule < 0) {
    937     *out_molecule = SHTR_MOLECULE_NULL;
    938   } else {
    939     res = shtr_isotope_metadata_get_molecule
    940       (metadata, (size_t)imolecule, out_molecule);
    941     if(res != RES_OK) goto error;
    942   }
    943 
    944 exit:
    945   return res;
    946 error:
    947   goto exit;
    948 }
    949 
    950 res_T
    951 shtr_isotope_metadata_write
    952   (const struct shtr_isotope_metadata* metadata,
    953    FILE* stream)
    954 {
    955   res_T res = RES_OK;
    956 
    957   if(!metadata || !stream) {
    958     res = RES_BAD_ARG;
    959     goto error;
    960   }
    961 
    962   #define WRITE(Var, Nb) {                                                     \
    963     if(fwrite((Var), sizeof(*(Var)), (Nb), stream) != (Nb)) {                  \
    964       ERROR(metadata->shtr, "%s: error writing metadata\n", FUNC_NAME);        \
    965       res = RES_IO_ERR;                                                        \
    966       goto error;                                                              \
    967     }                                                                          \
    968   } (void)0
    969   WRITE(&SHTR_ISOTOPE_METADATA_VERSION, 1);
    970 
    971   res = write_molecules(metadata, FUNC_NAME, stream);
    972   if(res != RES_OK) goto error;
    973   res = write_isotopes(metadata, FUNC_NAME, stream);
    974   if(res != RES_OK) goto error;
    975 
    976   WRITE(metadata->molid2idx, SHTR_MAX_MOLECULE_COUNT);
    977   #undef WRITE
    978 
    979 exit:
    980   return res;
    981 error:
    982   goto exit;
    983 }
    984 
    985 res_T
    986 shtr_isotope_metadata_hash
    987   (const struct shtr_isotope_metadata* metadata,
    988    hash256_T hash)
    989 {
    990   struct sha256_ctx ctx;
    991   res_T res = RES_OK;
    992 
    993   if(!metadata || !hash) {
    994     res = RES_BAD_ARG;
    995     goto error;
    996   }
    997 
    998   sha256_ctx_init(&ctx);
    999   hash_molecule_list(metadata, &ctx);
   1000   sha256_ctx_finalize(&ctx, hash);
   1001 
   1002 exit:
   1003   return res;
   1004 error:
   1005   goto exit;
   1006 }