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 }