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

test_shtr_isotope_metadata.c (20202B)


      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 #include "shtr.h"
     20 
     21 #include <rsys/mem_allocator.h>
     22 #include <rsys/math.h>
     23 
     24 #include <string.h>
     25 
     26 static const struct shtr_isotope H2O_isotopes[] = {
     27   {9.97317E-01, 1.7458E+02, 18.010565, 0, 1, 161},
     28   {1.99983E-03, 1.7605E+02, 20.014811, 0, 1, 181},
     29   {3.71884E-04, 1.0521E+03, 19.014780, 0, 6, 171},
     30   {3.10693E-04, 8.6474E+02, 19.016740, 0, 6, 162},
     31   {6.23003E-07, 8.7557E+02, 21.020985, 0, 6, 182},
     32   {1.15853E-07, 5.2268E+03, 20.020956, 0, 36, 172},
     33   {2.41974E-08, 1.0278E+03, 20.022915, 0, 1, 262}
     34 };
     35 
     36 
     37 static const struct shtr_molecule H2O = {
     38   "H2O", sizeof(H2O_isotopes)/sizeof(struct shtr_isotope), H2O_isotopes, 1,
     39 };
     40 
     41 static const struct shtr_isotope CO2_isotopes[] = {
     42   {9.84204E-01, 2.8609E+02, 43.989830, 1, 1, 626},
     43   {1.10574E-02, 5.7664E+02, 44.993185, 1, 2, 636},
     44   {3.94707E-03, 6.0781E+02, 45.994076, 1, 1, 628},
     45   {7.33989E-04, 3.5426E+03, 44.994045, 1, 6, 627},
     46   {4.43446E-05, 1.2255E+03, 46.997431, 1, 2, 638},
     47   {8.24623E-06, 7.1413E+03, 45.997400, 1, 12, 637},
     48   {3.95734E-06, 3.2342E+02, 47.998320, 1, 1, 828},
     49   {1.47180E-06, 3.7666E+03, 46.998291, 1, 6, 827},
     50   {1.36847E-07, 1.0972E+04, 45.998262, 1, 1, 727},
     51   {4.44600E-08, 6.5224E+02, 49.001675, 1, 2, 838},
     52   {1.65354E-08, 7.5950E+03, 48.001646, 1, 12, 837},
     53   {1.53745E-09, 2.2120E+04, 47.001618, 1, 2, 737}
     54 };
     55 
     56 static const struct shtr_molecule CO2 = {
     57   "CO2", sizeof(CO2_isotopes)/sizeof(struct shtr_isotope), CO2_isotopes, 2,
     58 };
     59 
     60 static void
     61 isotope_print(FILE* fp, const struct shtr_isotope* isotope)
     62 {
     63   CHK(fp && isotope);
     64   fprintf(fp, "    %d %.5E %.4E %d %.6f\n",
     65     isotope->id,
     66     isotope->abundance,
     67     isotope->Q296K,
     68     isotope->gj,
     69     isotope->molar_mass);
     70 }
     71 
     72 static void
     73 molecule_print(FILE* fp, const struct shtr_molecule* molecule)
     74 {
     75   size_t i;
     76   CHK(fp && molecule);
     77 
     78   fprintf(fp, "  %s (%d)\n", molecule->name, molecule->id);
     79   FOR_EACH(i, 0, molecule->nisotopes) {
     80     isotope_print(fp, molecule->isotopes+i);
     81   }
     82 }
     83 
     84 static int
     85 isotope_eq(const struct shtr_isotope* i0, const struct shtr_isotope* i1)
     86 {
     87   CHK(i0 && i1);
     88   return i0->abundance == i1->abundance
     89       && i0->Q296K == i1->Q296K
     90       && i0->molar_mass == i1->molar_mass
     91       && i0->molecule_id_local == i1->molecule_id_local
     92       && i0->gj == i1->gj
     93       && i0->id == i1->id;
     94 }
     95 
     96 static int
     97 molecule_eq(const struct shtr_molecule* m0, const struct shtr_molecule* m1)
     98 {
     99   size_t i;
    100 
    101   CHK(m0 && m1);
    102   if(strcmp(m0->name, m1->name)
    103   || m0->id != m1->id
    104   || m0->nisotopes != m1->nisotopes)
    105     return 0;
    106 
    107   FOR_EACH(i, 0, m0->nisotopes) {
    108     if(!isotope_eq(m0->isotopes+i, m1->isotopes+i))
    109       return 0;
    110   }
    111   return 1;
    112 }
    113 
    114 static void
    115 check_isotope
    116   (struct shtr_isotope_metadata* mdata,
    117    const struct shtr_molecule* molecule,
    118    const struct shtr_isotope* isotope)
    119 {
    120   struct shtr_molecule molecule2 = SHTR_MOLECULE_NULL;
    121 
    122   /* Check NaN */
    123   CHK(isotope->abundance == isotope->abundance);
    124   CHK(isotope->Q296K == isotope->Q296K);
    125   CHK(isotope->molar_mass == isotope->molar_mass);
    126 
    127   CHK(isotope->abundance > 0 && isotope->abundance <= 1);
    128   CHK(isotope->Q296K > 0);
    129   CHK(isotope->molar_mass > 0);
    130   CHK(isotope->id >= 0);
    131 
    132   CHK(shtr_isotope_metadata_get_molecule
    133     (mdata, isotope->molecule_id_local, &molecule2) == RES_OK);
    134   CHK(molecule_eq(molecule, &molecule2));
    135 }
    136 
    137 static void
    138 check_molecule
    139   (struct shtr_isotope_metadata* mdata,
    140    struct shtr_molecule* molecule)
    141 {
    142   size_t i = 0;
    143 
    144   CHK(mdata && molecule);
    145   CHK(molecule->name);
    146   CHK(molecule->id >= 0);
    147 
    148   FOR_EACH(i, 0, molecule->nisotopes) {
    149     check_isotope(mdata, molecule, molecule->isotopes+i);
    150   }
    151 }
    152 
    153 static void
    154 test_load(struct shtr* shtr)
    155 {
    156   struct shtr_molecule molecule = SHTR_MOLECULE_NULL;
    157   const char* filename = "test_isotope_metadata.txt";
    158   struct shtr_isotope_metadata* mdata = NULL;
    159   FILE* fp = NULL;
    160   size_t nmolecules = 0;
    161   size_t nisotopes = 0;
    162 
    163   CHK(fp = fopen(filename, "w+"));
    164 
    165   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    166   molecule_print(fp, &H2O);
    167   molecule_print(fp, &CO2);
    168   rewind(fp);
    169 
    170   CHK(shtr_isotope_metadata_load_stream(NULL, fp, NULL, &mdata) == RES_BAD_ARG);
    171   CHK(shtr_isotope_metadata_load_stream(shtr, NULL, NULL, &mdata) == RES_BAD_ARG);
    172   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, NULL) == RES_BAD_ARG);
    173   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_OK);
    174 
    175   CHK(shtr_isotope_metadata_get_molecules_count(NULL, &nmolecules) == RES_BAD_ARG);
    176   CHK(shtr_isotope_metadata_get_molecules_count(mdata, NULL) == RES_BAD_ARG);
    177   CHK(shtr_isotope_metadata_get_molecules_count(mdata, &nmolecules) == RES_OK);
    178   CHK(nmolecules == 2);
    179 
    180   CHK(shtr_isotope_metadata_get_isotopes_count(NULL, &nisotopes) == RES_BAD_ARG);
    181   CHK(shtr_isotope_metadata_get_isotopes_count(mdata, NULL) == RES_BAD_ARG);
    182   CHK(shtr_isotope_metadata_get_isotopes_count(mdata, &nisotopes) == RES_OK);
    183   CHK(nisotopes == H2O.nisotopes + CO2.nisotopes);
    184 
    185   CHK(shtr_isotope_metadata_get_molecule(NULL, 0, &molecule) == RES_BAD_ARG);
    186   CHK(shtr_isotope_metadata_get_molecule(mdata, 2, &molecule) == RES_BAD_ARG);
    187 
    188   CHK(shtr_isotope_metadata_get_molecule(mdata, 0, &molecule) == RES_OK);
    189   CHK(molecule_eq(&molecule, &H2O));
    190   check_molecule(mdata, &molecule);
    191 
    192   CHK(shtr_isotope_metadata_get_molecule(mdata, 1, &molecule) == RES_OK);
    193   CHK(molecule_eq(&molecule, &CO2));
    194   check_molecule(mdata, &molecule);
    195 
    196   CHK(shtr_isotope_metadata_find_molecule(NULL, 1, &molecule) == RES_BAD_ARG);
    197   CHK(shtr_isotope_metadata_find_molecule(mdata, 1, NULL) == RES_BAD_ARG);
    198   CHK(shtr_isotope_metadata_find_molecule(mdata, 1, &molecule) == RES_OK);
    199   CHK(!SHTR_MOLECULE_IS_NULL(&molecule));
    200   CHK(molecule_eq(&molecule, &H2O));
    201 
    202   CHK(shtr_isotope_metadata_find_molecule(mdata, 2, &molecule) == RES_OK);
    203   CHK(!SHTR_MOLECULE_IS_NULL(&molecule));
    204   CHK(molecule_eq(&molecule, &CO2));
    205 
    206   CHK(shtr_isotope_metadata_find_molecule(mdata, 0, &molecule) == RES_OK);
    207   CHK(SHTR_MOLECULE_IS_NULL(&molecule));
    208 
    209   CHK(shtr_isotope_metadata_ref_get(NULL) == RES_BAD_ARG);
    210   CHK(shtr_isotope_metadata_ref_get(mdata) == RES_OK);
    211   CHK(shtr_isotope_metadata_ref_put(NULL) == RES_BAD_ARG);
    212   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    213   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    214 
    215   CHK(fclose(fp) == 0);
    216 
    217   CHK(shtr_isotope_metadata_load(NULL, filename, &mdata) == RES_BAD_ARG);
    218   CHK(shtr_isotope_metadata_load(shtr, NULL, &mdata) == RES_BAD_ARG);
    219   CHK(shtr_isotope_metadata_load(shtr, filename, NULL) == RES_BAD_ARG);
    220   CHK(shtr_isotope_metadata_load(shtr, "no_file", &mdata) == RES_IO_ERR);
    221 
    222   CHK(shtr_isotope_metadata_load(shtr, filename, &mdata) == RES_OK);
    223   CHK(shtr_isotope_metadata_get_molecules_count(mdata, &nmolecules) == RES_OK);
    224   CHK(nmolecules == 2);
    225   CHK(shtr_isotope_metadata_get_molecule(mdata, 0, &molecule) == RES_OK);
    226   CHK(molecule_eq(&molecule, &H2O));
    227   CHK(shtr_isotope_metadata_get_molecule(mdata, 1, &molecule) == RES_OK);
    228   CHK(molecule_eq(&molecule, &CO2));
    229   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    230 
    231   /* Empty file */
    232   CHK(fp = tmpfile());
    233   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_OK);
    234   CHK(shtr_isotope_metadata_get_molecules_count(mdata, &nmolecules) == RES_OK);
    235   CHK(nmolecules == 0);
    236   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    237   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    238   rewind(fp);
    239   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_OK);
    240   CHK(shtr_isotope_metadata_get_molecules_count(mdata, &nmolecules) == RES_OK);
    241   CHK(nmolecules == 0);
    242   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    243   CHK(fclose(fp) == 0);
    244 
    245   /* Molecule without isotope */
    246   CHK(fp = tmpfile());
    247   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    248   fprintf(fp, "%s (%d)\n", H2O.name, H2O.id);
    249   fprintf(fp, "%s (%d)\n", CO2.name, CO2.id);
    250   rewind(fp);
    251   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_OK);
    252   CHK(shtr_isotope_metadata_get_molecules_count(mdata, &nmolecules) == RES_OK);
    253   CHK(nmolecules == 2);
    254 
    255   CHK(shtr_isotope_metadata_get_molecule(mdata, 0, &molecule) == RES_OK);
    256   CHK(!strcmp(molecule.name, H2O.name));
    257   CHK(molecule.id == H2O.id);
    258   CHK(molecule.nisotopes == 0);
    259   CHK(molecule.isotopes == NULL);
    260 
    261   CHK(shtr_isotope_metadata_get_molecule(mdata, 1, &molecule) == RES_OK);
    262   CHK(!strcmp(molecule.name, CO2.name));
    263   CHK(molecule.id == CO2.id);
    264   CHK(molecule.nisotopes == 0);
    265   CHK(molecule.isotopes == NULL);
    266 
    267   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    268   CHK(fclose(fp) == 0);
    269 }
    270 
    271 static void
    272 check_equality
    273   (const struct shtr_isotope_metadata* mdata1,
    274    const struct shtr_isotope_metadata* mdata2)
    275 {
    276   hash256_T hash1;
    277   hash256_T hash2;
    278   size_t n1, n2;
    279   size_t imol, nmol;
    280   CHK(mdata1 && mdata2);
    281 
    282   CHK(shtr_isotope_metadata_get_molecules_count(mdata1, &n1) == RES_OK);
    283   CHK(shtr_isotope_metadata_get_molecules_count(mdata2, &n2) == RES_OK);
    284   CHK(n1 == n2);
    285   nmol = n1;
    286 
    287   CHK(shtr_isotope_metadata_get_isotopes_count(mdata1, &n1) == RES_OK);
    288   CHK(shtr_isotope_metadata_get_isotopes_count(mdata2, &n2) == RES_OK);
    289   CHK(n1 == n2);
    290 
    291   FOR_EACH(imol, 0, nmol) {
    292     struct shtr_molecule mol1 = SHTR_MOLECULE_NULL;
    293     struct shtr_molecule mol2 = SHTR_MOLECULE_NULL;
    294     size_t iiso, niso;
    295 
    296     CHK(shtr_isotope_metadata_get_molecule(mdata1, imol, &mol1) == RES_OK);
    297     CHK(shtr_isotope_metadata_get_molecule(mdata2, imol, &mol2) == RES_OK);
    298     CHK(!strcmp(mol1.name, mol2.name));
    299     CHK(mol1.id == mol2.id);
    300     CHK(mol1.nisotopes == mol2.nisotopes);
    301     niso = mol1.nisotopes;
    302 
    303     FOR_EACH(iiso, 0, niso) {
    304       CHK(mol1.isotopes[iiso].abundance == mol2.isotopes[iiso].abundance);
    305       CHK(mol1.isotopes[iiso].Q296K == mol2.isotopes[iiso].Q296K);
    306       CHK(mol1.isotopes[iiso].molar_mass == mol2.isotopes[iiso].molar_mass);
    307       CHK(mol1.isotopes[iiso].molecule_id_local == mol2.isotopes[iiso].molecule_id_local);
    308       CHK(mol1.isotopes[iiso].gj == mol2.isotopes[iiso].gj);
    309       CHK(mol1.isotopes[iiso].id == mol2.isotopes[iiso].id);
    310     }
    311   }
    312 
    313   CHK(shtr_isotope_metadata_hash(mdata1, hash1) == RES_OK);
    314   CHK(shtr_isotope_metadata_hash(mdata2, hash2) == RES_OK);
    315   CHK(hash256_eq(hash1, hash2) != 0);
    316 }
    317 
    318 static void
    319 test_hash(struct shtr* shtr)
    320 {
    321   struct shtr_molecule CO2_custom = SHTR_MOLECULE_NULL;
    322   struct shtr_isotope isotopes_custom[SHTR_MAX_ISOTOPE_COUNT];
    323 
    324   struct shtr_isotope_metadata* mdata1 = NULL;
    325   struct shtr_isotope_metadata* mdata2 = NULL;
    326   struct shtr_isotope_metadata* mdata3 = NULL;
    327   struct shtr_isotope_metadata* mdata4 = NULL;
    328   struct shtr_isotope_metadata* mdata5 = NULL;
    329   struct shtr_isotope_metadata* mdata6 = NULL;
    330   hash256_T hash1;
    331   hash256_T hash2;
    332   hash256_T hash3;
    333   hash256_T hash4;
    334   hash256_T hash5;
    335   hash256_T hash6;
    336   FILE* fp = NULL;
    337   size_t i = 0;
    338 
    339   /* Setup the metadata of the H2O and the CO2 molecules */
    340   CHK(fp = tmpfile());
    341   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    342   molecule_print(fp, &H2O);
    343   molecule_print(fp, &CO2);
    344   rewind(fp);
    345   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata1) == RES_OK);
    346   CHK(fclose(fp) == 0);
    347 
    348   /* Check the hash API */
    349   CHK(shtr_isotope_metadata_hash(NULL, hash1) == RES_BAD_ARG);
    350   CHK(shtr_isotope_metadata_hash(mdata1, NULL) == RES_BAD_ARG);
    351   CHK(shtr_isotope_metadata_hash(mdata1, hash1) == RES_OK);
    352 
    353   /* Setup the same metadata of the first one, in the same order */
    354   CHK(fp = tmpfile());
    355   fprintf(fp, "Molecule # Dummy comment\n");
    356   molecule_print(fp, &H2O);
    357   molecule_print(fp, &CO2);
    358   rewind(fp);
    359   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata2) == RES_OK);
    360   CHK(fclose(fp) == 0);
    361 
    362   CHK(shtr_isotope_metadata_hash(mdata2, hash2) == RES_OK);
    363   CHK(hash256_eq(hash2, hash1) != 0);
    364 
    365   /* Configure metadata for H2O and CO2 molecules but in a different order.
    366    * This may not change the hash */
    367   CHK(fp = tmpfile());
    368   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    369   molecule_print(fp, &CO2);
    370   molecule_print(fp, &H2O);
    371   rewind(fp);
    372   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata3) == RES_OK);
    373   CHK(fclose(fp) == 0);
    374 
    375   CHK(shtr_isotope_metadata_hash(mdata3, hash3) == RES_OK);
    376   CHK(hash256_eq(hash3, hash1) != 0);
    377 
    378   /* Configure metadata for CO2 */
    379   CHK(fp = tmpfile());
    380   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    381   molecule_print(fp, &CO2);
    382   rewind(fp);
    383   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata4) == RES_OK);
    384   CHK(fclose(fp) == 0);
    385 
    386   CHK(shtr_isotope_metadata_hash(mdata4, hash4) == RES_OK);
    387   CHK(hash256_eq(hash4, hash1) == 0);
    388 
    389   /* Configure metadata for CO2 but print its isotope in different order.
    390    * This may not change the hash */
    391   CO2_custom = CO2;
    392   CO2_custom.isotopes = isotopes_custom;
    393   FOR_EACH(i, 0, CO2.nisotopes) {
    394     isotopes_custom[i] = CO2_isotopes[CO2.nisotopes-i-1];
    395   }
    396   CHK(fp = tmpfile());
    397   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    398   molecule_print(fp, &CO2_custom);
    399   rewind(fp);
    400   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata5) == RES_OK);
    401   CHK(fclose(fp) == 0);
    402 
    403   CHK(shtr_isotope_metadata_hash(mdata5, hash5) == RES_OK);
    404   CHK(hash256_eq(hash5, hash4) != 0);
    405 
    406   /* Configure metadata for CO2 with only few isotopes */
    407   CO2_custom.nisotopes = CO2_custom.nisotopes/4;
    408   CHK(fp = tmpfile());
    409   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    410   molecule_print(fp, &CO2_custom);
    411   rewind(fp);
    412   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata6) == RES_OK);
    413   CHK(fclose(fp) == 0);
    414 
    415   CHK(shtr_isotope_metadata_hash(mdata6, hash6) == RES_OK);
    416   CHK(hash256_eq(hash6, hash5) == 0);
    417 
    418   CHK(shtr_isotope_metadata_ref_put(mdata1) == RES_OK);
    419   CHK(shtr_isotope_metadata_ref_put(mdata2) == RES_OK);
    420   CHK(shtr_isotope_metadata_ref_put(mdata3) == RES_OK);
    421   CHK(shtr_isotope_metadata_ref_put(mdata4) == RES_OK);
    422   CHK(shtr_isotope_metadata_ref_put(mdata5) == RES_OK);
    423   CHK(shtr_isotope_metadata_ref_put(mdata6) == RES_OK);
    424 }
    425 
    426 static void
    427 test_serialization(struct shtr* shtr)
    428 {
    429   struct shtr_isotope_metadata* mdata1 = NULL;
    430   struct shtr_isotope_metadata* mdata2 = NULL;
    431   FILE* fp = NULL;
    432 
    433   CHK(fp = tmpfile());
    434 
    435   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    436   molecule_print(fp, &H2O);
    437   molecule_print(fp, &CO2);
    438   rewind(fp);
    439 
    440   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata1) == RES_OK);
    441   fclose(fp);
    442 
    443   CHK(fp = tmpfile());
    444   CHK(shtr_isotope_metadata_write(NULL, fp) == RES_BAD_ARG);
    445   CHK(shtr_isotope_metadata_write(mdata1, NULL) == RES_BAD_ARG);
    446   CHK(shtr_isotope_metadata_write(mdata1, fp) == RES_OK);
    447   rewind(fp);
    448 
    449   CHK(shtr_isotope_metadata_create_from_stream(NULL, fp, &mdata2) == RES_BAD_ARG);
    450   CHK(shtr_isotope_metadata_create_from_stream(shtr, NULL, &mdata2) == RES_BAD_ARG);
    451   CHK(shtr_isotope_metadata_create_from_stream(shtr, fp, NULL) == RES_BAD_ARG);
    452   CHK(shtr_isotope_metadata_create_from_stream(shtr, fp, &mdata2) == RES_OK);
    453   fclose(fp);
    454 
    455   check_equality(mdata1, mdata2);
    456 
    457   CHK(shtr_isotope_metadata_ref_put(mdata1) == RES_OK);
    458   CHK(shtr_isotope_metadata_ref_put(mdata2) == RES_OK);
    459 }
    460 
    461 static void
    462 test_load_failures(struct shtr* shtr)
    463 {
    464   struct shtr_isotope CO2_too_many_isotopes[SHTR_MAX_ISOTOPE_COUNT+1] = {0};
    465   struct shtr_molecule CO2_bad = SHTR_MOLECULE_NULL;
    466 
    467   struct shtr_isotope isotope = SHTR_ISOTOPE_NULL;
    468   struct shtr_isotope_metadata* mdata = NULL;
    469   FILE* fp = NULL;
    470   size_t i = 0;
    471 
    472   CHK(shtr);
    473 
    474   /* 1st line is missing */
    475   CHK(fp = tmpfile());
    476   molecule_print(fp, &H2O);
    477   rewind(fp);
    478   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    479   CHK(fclose(fp) == 0);
    480 
    481   /* Invalid molecule id */
    482   CHK(fp = tmpfile());
    483   fprintf(fp, "Comment line\n");
    484   fprintf(fp, "H2O 1\n");
    485   isotope_print(fp, &H2O_isotopes[0]);
    486   rewind(fp);
    487   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    488   rewind(fp);
    489   fprintf(fp, "Comment line\n");
    490   fprintf(fp, "H2O (-1)\n");
    491   isotope_print(fp, &H2O_isotopes[0]);
    492   rewind(fp);
    493   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    494   CHK(fclose(fp) == 0);
    495 
    496   /* Invalid isotope id */
    497   CHK(fp = tmpfile());
    498   fprintf(fp, "Comment line\n");
    499   fprintf(fp, "H2O (1)\n");
    500   isotope = H2O_isotopes[0];
    501   isotope.id = -1;
    502   isotope_print(fp, &isotope);
    503   rewind(fp);
    504   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    505   CHK(fclose(fp) == 0);
    506 
    507   /* Invalid abundance */
    508   CHK(fp = tmpfile());
    509   fprintf(fp, "Comment line\n");
    510   fprintf(fp, "H2O (1)\n");
    511   isotope = H2O_isotopes[0];
    512   isotope.abundance = 0;
    513   isotope_print(fp, &isotope);
    514   rewind(fp);
    515   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    516   CHK(fclose(fp) == 0);
    517   CHK(fp = tmpfile());
    518   fprintf(fp, "Comment line\n");
    519   fprintf(fp, "H2O (1)\n");
    520   isotope.abundance = 1.00001;
    521   isotope_print(fp, &isotope);
    522   rewind(fp);
    523   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    524   CHK(fclose(fp) == 0);
    525 
    526   /* Invalid Q(296K) */
    527   CHK(fp = tmpfile());
    528   fprintf(fp, "Comment line\n");
    529   fprintf(fp, "H2O (1)\n");
    530   isotope = H2O_isotopes[0];
    531   isotope.Q296K = 0;
    532   isotope_print(fp, &isotope);
    533   rewind(fp);
    534   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    535   CHK(fclose(fp) == 0);
    536 
    537   /* Invalid molar mass */
    538   CHK(fp = tmpfile());
    539   fprintf(fp, "Comment line\n");
    540   fprintf(fp, "H2O (1)\n");
    541   isotope = H2O_isotopes[0];
    542   isotope.molar_mass = 0;
    543   isotope_print(fp, &isotope);
    544   rewind(fp);
    545   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_BAD_ARG);
    546   CHK(fclose(fp) == 0);
    547 
    548   /* Too many isotope for a molecule */
    549   FOR_EACH(i, 0, SHTR_MAX_ISOTOPE_COUNT+1) {
    550     CO2_too_many_isotopes[i] = CO2_isotopes[i % CO2.nisotopes];
    551     CO2_too_many_isotopes[i].abundance = 1.0/(double)(SHTR_MAX_ISOTOPE_COUNT+1);
    552   }
    553   CHK(fp = tmpfile());
    554   CO2_bad = CO2;
    555   CO2_bad.nisotopes = SHTR_MAX_ISOTOPE_COUNT+1;
    556   CO2_bad.isotopes = CO2_too_many_isotopes;
    557   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    558   molecule_print(fp, &CO2_bad);
    559   rewind(fp);
    560   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &mdata) == RES_MEM_ERR);
    561   CHK(fclose(fp) == 0);
    562 }
    563 
    564 static void
    565 test_load_file(struct shtr* shtr, const char* path)
    566 {
    567   struct shtr_isotope_metadata* mdata = NULL;
    568   size_t i, n;
    569   CHK(path);
    570   printf("Loading `%s'.\n", path);
    571   CHK(shtr_isotope_metadata_load(shtr, path, &mdata) == RES_OK);
    572   CHK(shtr_isotope_metadata_get_molecules_count(mdata, &n) == RES_OK);
    573   printf("  #molecules: %lu\n", n);
    574   FOR_EACH(i, 0, n) {
    575     struct shtr_molecule molecule = SHTR_MOLECULE_NULL;
    576     CHK(shtr_isotope_metadata_get_molecule(mdata, i, &molecule) == RES_OK);
    577     printf("  Checking %s\n", molecule.name);
    578     check_molecule(mdata, &molecule);
    579   }
    580   CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK);
    581 }
    582 
    583 int
    584 main(int argc, char** argv)
    585 {
    586   struct shtr_create_args args = SHTR_CREATE_ARGS_DEFAULT;
    587   struct shtr* shtr = NULL;
    588   int i;
    589   (void)argc, (void)argv;
    590 
    591   args.verbose = 1;
    592   CHK(shtr_create(&args, &shtr) == RES_OK);
    593 
    594   test_load(shtr);
    595   test_load_failures(shtr);
    596   test_hash(shtr);
    597   test_serialization(shtr);
    598   FOR_EACH(i, 1, argc) {
    599     test_load_file(shtr, argv[i]);
    600   }
    601 
    602   CHK(shtr_ref_put(shtr) == RES_OK);
    603   CHK(mem_allocated_size() == 0);
    604   return 0;
    605 }