star-line

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

test_sln_mixture.c (13576B)


      1 /* Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com)
      2  * Copyright (C) 2026 Université de Lorraine
      3  * Copyright (C) 2022 Centre National de la Recherche Scientifique
      4  * Copyright (C) 2022 Université Paul Sabatier
      5  *
      6  * This program is free software: you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation, either version 3 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     18 
     19 #define _POSIX_C_SOURCE 200809L /* fmemopen */
     20 
     21 #include "test_sln_lines.h"
     22 #include "sln.h"
     23 
     24 #include <star/shtr.h>
     25 #include <rsys/math.h>
     26 #include <rsys/mem_allocator.h>
     27 
     28 #include <stdio.h>
     29 #include <string.h>
     30 
     31 /*******************************************************************************
     32  * Helper function
     33  ******************************************************************************/
     34 static void
     35 test_api
     36   (struct sln_device* sln,
     37    struct shtr_isotope_metadata* molparam)
     38 {
     39   struct sln_mixture_load_args args = SLN_MIXTURE_LOAD_ARGS_NULL;
     40   struct sln_mixture* mixture = NULL;
     41   struct sln_molecule molecule = SLN_MOLECULE_NULL;
     42 
     43   const struct sln_isotope H2O_isotopes[] = {
     44     {2.41974E-08, 161},
     45     {1.15853E-07, 181},
     46     {6.23003E-07, 171},
     47     {3.10693E-04, 162},
     48     {3.71884E-04, 182},
     49     {1.99983E-03, 172},
     50     {9.97317E-01, 262}
     51   };
     52   const size_t H2O_nisotopes = sizeof(H2O_isotopes)/sizeof(H2O_isotopes[0]);
     53 
     54   const char* filename = "mixture.txt";
     55   FILE* fp = NULL;
     56   size_t i = 0;
     57 
     58   CHK(fp = fopen(filename, "w+"));
     59 
     60   fprintf(fp, "# Molecule concentration cutoff [cm-1]\n");
     61   fprintf(fp, "  H2O      0.3           25\n");
     62   fprintf(fp, "# Isotopes abundance\n");
     63   FOR_EACH(i, 0, H2O_nisotopes) {
     64     fprintf(fp, "%d %E\n", H2O_isotopes[i].id, H2O_isotopes[i].abundance);
     65   }
     66 
     67   fprintf(fp, "# Molecule concentration cutoff [cm-1]\n");
     68   fprintf(fp, "  CO2      0.7           50\n");
     69 
     70   rewind(fp);
     71 
     72   args.filename = filename;
     73   args.molparam = molparam;
     74   CHK(sln_mixture_load(NULL, &args, &mixture) == RES_BAD_ARG);
     75   CHK(sln_mixture_load(sln, NULL, &mixture) == RES_BAD_ARG);
     76   CHK(sln_mixture_load(sln, &args, NULL) == RES_BAD_ARG);
     77   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
     78 
     79   CHK(sln_mixture_get_molecule_count(mixture) == 2);
     80   CHK(sln_mixture_get_molecule_id(mixture, 0) == SHTR_H2O);
     81   CHK(sln_mixture_get_molecule_id(mixture, 1) == SHTR_CO2);
     82 
     83   CHK(sln_mixture_get_molecule(NULL, 0, &molecule) == RES_BAD_ARG);
     84   CHK(sln_mixture_get_molecule(mixture, -1, &molecule) == RES_BAD_ARG);
     85   CHK(sln_mixture_get_molecule(mixture, 2, &molecule) == RES_BAD_ARG);
     86   CHK(sln_mixture_get_molecule(mixture, 0, NULL) == RES_BAD_ARG);
     87 
     88   /* Check the H2O molecule */
     89   CHK(sln_mixture_get_molecule(mixture, 0, &molecule) == RES_OK);
     90   CHK(molecule.concentration == 0.3);
     91   CHK(molecule.cutoff == 25.0);
     92   CHK(molecule.non_default_isotope_abundances != 0);
     93   FOR_EACH(i, 0, H2O_nisotopes) {
     94     CHK(molecule.isotopes[i].id == H2O_isotopes[i].id);
     95     CHK(molecule.isotopes[i].abundance == H2O_isotopes[i].abundance);
     96   }
     97 
     98   /* Check the CO2 molecule */
     99   CHK(sln_mixture_get_molecule(mixture, 1, &molecule) == RES_OK);
    100   CHK(molecule.concentration == 0.7);
    101   CHK(molecule.cutoff == 50);
    102   CHK(molecule.non_default_isotope_abundances == 0);
    103 
    104   CHK(sln_mixture_ref_get(NULL) == RES_BAD_ARG);
    105   CHK(sln_mixture_ref_get(mixture) == RES_OK);
    106   CHK(sln_mixture_ref_put(NULL) == RES_BAD_ARG);
    107   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    108   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    109 
    110   /* Check load from stream */
    111   args.file = fp;
    112   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
    113 
    114   CHK(sln_mixture_get_molecule_id(mixture, 0) == SHTR_H2O);
    115   CHK(sln_mixture_get_molecule(mixture, 0, &molecule) == RES_OK);
    116   CHK(molecule.concentration == 0.3);
    117   CHK(molecule.cutoff == 25.0);
    118   CHK(molecule.non_default_isotope_abundances != 0);
    119   CHK(sln_mixture_get_molecule_id(mixture, 1) == SHTR_CO2);
    120   CHK(sln_mixture_get_molecule(mixture, 1, &molecule) == RES_OK);
    121   CHK(molecule.concentration == 0.7);
    122   CHK(molecule.cutoff == 50);
    123   CHK(molecule.non_default_isotope_abundances == 0);
    124 
    125   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    126 
    127   CHK(fclose(fp) == 0);
    128 }
    129 
    130 static void
    131 test_empty_file
    132   (struct sln_device* sln,
    133    struct shtr_isotope_metadata* molparam)
    134 {
    135   struct sln_mixture_load_args args = SLN_MIXTURE_LOAD_ARGS_NULL;
    136   struct sln_mixture* mixture = NULL;
    137   struct sln_molecule molecule = SLN_MOLECULE_NULL;
    138 
    139   FILE* fp = NULL;
    140 
    141   CHK(fp = tmpfile());
    142 
    143   args.filename = "tmpfile";
    144   args.molparam = molparam;
    145   args.file = fp;
    146 
    147   /* An empty file is valid */
    148   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
    149   CHK(sln_mixture_get_molecule_count(mixture) == 0);
    150   CHK(sln_mixture_get_molecule(mixture, 0, &molecule) == RES_BAD_ARG);
    151   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    152 
    153   CHK(fclose(fp) == 0);
    154 }
    155 
    156 static void
    157 test_invalid_molecule
    158   (struct sln_device* sln,
    159    struct shtr_isotope_metadata* molparam)
    160 {
    161   struct sln_mixture_load_args args = SLN_MIXTURE_LOAD_ARGS_NULL;
    162   struct sln_mixture* mixture = NULL;
    163 
    164   char buf[1024] = {0};
    165   FILE* fp;
    166 
    167   /* Note that the comment char will be added as the last character written to
    168    * the file in order to fill the rest of the file with a comment */
    169   CHK(fp = fmemopen(buf, sizeof(buf), "w+"));
    170 
    171   args.filename = "memstream";
    172   args.molparam = molparam;
    173   args.file = fp;
    174 
    175   #define RESET { memset(buf, 0, sizeof(buf)); rewind(fp); } (void)0
    176 
    177   /* Name is missing */
    178   RESET;
    179   CHK(fprintf(fp, "0.3 25\n#") > 0);
    180   rewind(fp);
    181   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    182 
    183   /* Name is invalid */
    184   RESET;
    185   CHK(fprintf(fp, "Water 0.3 25\n#") > 0);
    186   rewind(fp);
    187   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    188 
    189   /* Name is valid but is not in the isotope metadata */
    190   RESET;
    191   CHK(fprintf(fp, "O2 0.1 25\n#") > 0);
    192   rewind(fp);
    193   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    194 
    195   /* Definition of duplicate molecule */
    196   RESET;
    197   CHK(fprintf(fp, "H2O 0.3 25\n") > 0);
    198   CHK(fprintf(fp, "H2O 0.1 50\n#") > 0);
    199   rewind(fp);
    200   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    201 
    202   /* Concentration is missing */
    203   RESET;
    204   CHK(fprintf(fp, "H2O 25\n#") > 0);
    205   rewind(fp);
    206   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    207 
    208   /* Invalid concentration */
    209   RESET;
    210   CHK(fprintf(fp, "H2O -0.1 25\n#") > 0);
    211   rewind(fp);
    212   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    213   RESET;
    214   CHK(fprintf(fp, "H2O  1.1 25\n#") > 0);
    215   rewind(fp);
    216   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    217 
    218   /* Missing cutoff */
    219   RESET;
    220   CHK(fprintf(fp, "H2O 0.3\n#") > 0);
    221   rewind(fp);
    222   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    223 
    224   /* Invalid cutoff */
    225   RESET;
    226   CHK(fprintf(fp, "H2O 0.3 0\n#") > 0);
    227   rewind(fp);
    228   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    229 
    230   /* Invalid overall cocentration */
    231   RESET;
    232   CHK(fprintf(fp, "H2O 0.3 25\n") > 0);
    233   CHK(fprintf(fp, "CO2 0.7 50\n") > 0);
    234   CHK(fprintf(fp, "O3  0.1 25\n#") > 0);
    235   rewind(fp);
    236   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    237 
    238   /* An overall concentration < 1 is valid */
    239   RESET;
    240   CHK(fprintf(fp, "H2O 0.2 25 # Comment\n") > 0);
    241   CHK(fprintf(fp, "CO2 0.3 50\n") > 0);
    242   CHK(fprintf(fp, "O3  0.4 25\n#") > 0);
    243   rewind(fp);
    244   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
    245   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    246 
    247   /* Additional text is questionable, but still acceptable
    248    * (a warning message should be displayed) */
    249   RESET;
    250   CHK(fprintf(fp, "H2O 0.3 25 dummy text 3.14\n#") > 0);
    251   rewind(fp);
    252   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
    253   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    254 
    255   #undef RESET
    256 
    257   CHK(fclose(fp) == 0);
    258 }
    259 
    260 static void
    261 test_invalid_isotope
    262   (struct sln_device* sln,
    263    struct shtr_isotope_metadata* molparam)
    264 {
    265   struct sln_mixture_load_args args = SLN_MIXTURE_LOAD_ARGS_NULL;
    266   struct sln_mixture* mixture = NULL;
    267 
    268   char buf[1024] = {0};
    269   FILE* fp = NULL;
    270   size_t i = 0;
    271 
    272   const struct sln_isotope H2O_iso[] = {
    273     {1.0/7.0, 161},
    274     {1.0/7.0, 181},
    275     {1.0/7.0, 171},
    276     {1.0/7.0, 162},
    277     {1.0/7.0, 182},
    278     {1.0/7.0, 172},
    279     {1.0/7.0, 262}
    280   };
    281   const size_t H2O_niso = sizeof(H2O_iso)/sizeof(H2O_iso[0]);
    282 
    283   /* Note that the comment char will be added as the last character written to
    284    * the file in order to fill the rest of the file with a comment */
    285   CHK(fp = fmemopen(buf, sizeof(buf), "w+"));
    286 
    287   args.filename = "memstream";
    288   args.molparam = molparam;
    289   args.file = fp;
    290 
    291   #define RESET { memset(buf, 0, sizeof(buf)); rewind(fp); } (void)0
    292 
    293   /* Isotope ID is missing */
    294   RESET;
    295   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    296   FOR_EACH(i, 0, H2O_niso) {
    297     /* Arbitrarily select an isotope whose identifier is not defined */
    298     if(i == H2O_niso/2) {
    299       CHK(fprintf(fp, "   %E\n", H2O_iso[i].abundance) > 0);
    300     } else {
    301       CHK(fprintf(fp, "%d %E\n", H2O_iso[i].id, H2O_iso[i].abundance) > 0);
    302     }
    303   }
    304   CHK(fprintf(fp, "#") > 0); /* The rest of the file is a comment */
    305   rewind(fp);
    306   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    307 
    308   /* Isotope abundance is missing */
    309   RESET;
    310   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    311   FOR_EACH(i, 0, H2O_niso) {
    312     /* Arbitrarily select an isotope whose identifier is not defined */
    313     if(i == H2O_niso/2) {
    314       CHK(fprintf(fp, "%d   \n", H2O_iso[i].id) > 0);
    315     } else {
    316       CHK(fprintf(fp, "%d %E\n", H2O_iso[i].id, H2O_iso[i].abundance) > 0);
    317     }
    318   }
    319   CHK(fprintf(fp, "#") > 0); /* The rest of the file is a comment */
    320   rewind(fp);
    321   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    322 
    323   /* Missing an isotope */
    324   RESET;
    325   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    326   FOR_EACH(i, 0, H2O_niso-1) {
    327     CHK(fprintf(fp, "%d %E\n", H2O_iso[i].id, H2O_iso[i].abundance) > 0);
    328   }
    329   CHK(fprintf(fp, "#") > 0);
    330   rewind(fp);
    331   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    332 
    333   /* Inconsistency in the order of isotopes compared to that of metadata */
    334   RESET;
    335   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    336   FOR_EACH_REVERSE(i, H2O_niso, 0) {
    337     CHK(fprintf(fp, "%d %E\n", H2O_iso[i-1].id, H2O_iso[i-1].abundance) > 0);
    338   }
    339   CHK(fprintf(fp, "#") > 0);
    340   rewind(fp);
    341   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    342 
    343   /* The sum of the abundances is not 1 */
    344   RESET;
    345   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    346   FOR_EACH(i, 0, H2O_niso) {
    347     double abundance = H2O_iso[i].abundance;
    348     if(i == H2O_niso/2)  abundance /= 2;
    349     CHK(fprintf(fp, "%d %E\n", H2O_iso[i].id, abundance) > 0);
    350   }
    351   CHK(fprintf(fp, "#") > 0);
    352   rewind(fp);
    353   CHK(sln_mixture_load(sln, &args, &mixture) == RES_BAD_ARG);
    354 
    355   /* Additional text is questionable, but still acceptable
    356    * (a warning message should be displayed) */
    357   RESET;
    358   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    359   FOR_EACH(i, 0, H2O_niso) {
    360     CHK(fprintf(fp, "%d %E", H2O_iso[i].id, H2O_iso[i].abundance) > 0);
    361     if(i == H2O_niso*2 / 3) {
    362       CHK(fprintf(fp, " dummy text 42\n") > 0);
    363     } else {
    364       CHK(fprintf(fp, "\n") > 0);
    365     }
    366   }
    367   CHK(fprintf(fp, "#") > 0);
    368   rewind(fp);
    369   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
    370   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    371 
    372   /* Only a subset of isotopes can be defined as long as the sum of their
    373    * abundances equals 1. Also check that tabs and multiple spaces are handled
    374    * correctly */
    375   RESET;
    376   CHK(fprintf(fp, "H2O 0.2 25\n") > 0);
    377   CHK(fprintf(fp, "\t%d\t0.5\n", H2O_iso[0].id) > 0);
    378   CHK(fprintf(fp, "\t%d  0.5\n", H2O_iso[1].id) > 0);
    379   CHK(fprintf(fp, "#") > 0);
    380   rewind(fp);
    381   CHK(sln_mixture_load(sln, &args, &mixture) == RES_OK);
    382   CHK(sln_mixture_ref_put(mixture) == RES_OK);
    383 
    384   #undef RESET
    385 
    386   CHK(fclose(fp) == 0);
    387 }
    388 
    389 static struct shtr_isotope_metadata*
    390 load_isotope_metadata(struct shtr* shtr)
    391 {
    392   struct shtr_isotope_metadata* molparam = NULL;
    393   FILE* fp = NULL;
    394 
    395   CHK(fp = tmpfile());
    396 
    397   fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n");
    398 
    399   write_shtr_molecule(fp, &g_H2O);
    400   write_shtr_molecule(fp, &g_CO2);
    401   write_shtr_molecule(fp, &g_O3);
    402 
    403   rewind(fp);
    404   CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &molparam) == RES_OK);
    405   CHK(fclose(fp) == 0);
    406   return molparam;
    407 }
    408 
    409 /*******************************************************************************
    410  * Test function
    411  ******************************************************************************/
    412 int
    413 main(void)
    414 {
    415   struct sln_device_create_args sln_args = SLN_DEVICE_CREATE_ARGS_DEFAULT;
    416   struct sln_device* sln = NULL;
    417 
    418   struct shtr_create_args shtr_args = SHTR_CREATE_ARGS_DEFAULT;
    419   struct shtr* shtr = NULL;
    420   struct shtr_isotope_metadata* molparam = NULL;
    421 
    422   shtr_args.verbose = 3;
    423   CHK(shtr_create(&shtr_args, &shtr) == RES_OK);
    424 
    425   sln_args.verbose = 3;
    426   CHK(sln_device_create(&sln_args, &sln) == RES_OK);
    427 
    428   molparam = load_isotope_metadata(shtr);
    429 
    430   test_api(sln, molparam);
    431   test_empty_file(sln, molparam);
    432   test_invalid_molecule(sln, molparam);
    433   test_invalid_isotope(sln, molparam);
    434 
    435   CHK(sln_device_ref_put(sln) == RES_OK);
    436   CHK(shtr_ref_put(shtr) == RES_OK);
    437   CHK(shtr_isotope_metadata_ref_put(molparam) == RES_OK);
    438   CHK(mem_allocated_size() == 0);
    439   return 0;
    440 }