star-line

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

test_sln_mixture.c (13614B)


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