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 }