star-hitran

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

shtr_isotope_metadata.c (30136B)


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