sln_build.c (14616B)
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 /* strtok_r */ 22 23 #include "sln.h" 24 25 #include <rsys/cstr.h> 26 #include <rsys/mem_allocator.h> 27 #include <rsys/rsys.h> 28 29 #include <stdio.h> 30 #include <string.h> 31 #include <unistd.h> /* getopt */ 32 33 enum line_list_format { 34 LINE_LIST_HITRAN, 35 LINE_LIST_SHTR, 36 LINE_LIST_FORMAT_COUNT__ 37 }; 38 39 struct args { 40 /* Spectroscopic parameters */ 41 const char* lines; 42 const char* molparams; 43 enum sln_line_profile line_profile; 44 45 const char* output; 46 47 /* Thermodynamic properties */ 48 const char* mixture; 49 double pressure; /* [atm] */ 50 double temperature; /* [K] */ 51 52 /* Polyline */ 53 double mesh_decimation_err; 54 unsigned nvertices_hint; 55 enum sln_mesh_type mesh_type; 56 int collapse_polylines; 57 58 /* Miscellaneous */ 59 int quit; 60 int verbose; 61 unsigned arity; /* tree arity */ 62 unsigned leaf_nlines; /* Maximum number of lines per leaf */ 63 unsigned nthreads_hint; /* Advice on the number of threads to use */ 64 enum line_list_format line_format; 65 }; 66 #define ARGS_DEFAULT__ { \ 67 /* Spectroscopic parameters */ \ 68 NULL, /* line list */ \ 69 NULL, /* Isotopologue metadata */ \ 70 SLN_LINE_PROFILE_VOIGT, /* Line profile */ \ 71 \ 72 NULL, /* Output */ \ 73 \ 74 /* Thermodynamic properties */ \ 75 NULL, \ 76 -1, /* Pressure [atm] */ \ 77 -1, /* Temperature [K] */ \ 78 \ 79 /* Polyline */ \ 80 0.01, \ 81 16, \ 82 SLN_MESH_UPPER, \ 83 0, \ 84 \ 85 /* Miscellaneous */ \ 86 0, /* Quit */ \ 87 0, /* Verbose */ \ 88 2, /* Tree arity */ \ 89 1, /* Number of lines per leaf */ \ 90 (unsigned)(-1), /* #threads hint */ \ 91 LINE_LIST_HITRAN /* lines_format */ \ 92 } 93 static const struct args ARGS_DEFAULT = ARGS_DEFAULT__; 94 95 struct cmd { 96 struct args args; 97 98 struct sln_device* sln; 99 struct sln_mixture* mixture; 100 101 struct shtr* shtr; 102 struct shtr_line_list* lines; 103 struct shtr_isotope_metadata* molparam; 104 105 FILE* output; 106 }; 107 static const struct cmd CMD_NULL = {0}; 108 109 /******************************************************************************* 110 * Helper functions 111 ******************************************************************************/ 112 static void 113 usage(FILE* stream) 114 { 115 fprintf(stream, 116 "usage: sln-build [-chsv] [-a arity] [-e polyline_opt[:polyline_opt ...]]\n" 117 " [-L leaf_nlines] [-l line_profile] [-o accel_struct]\n" 118 " [-t thread_count] -P pressure -T temperature -m molparms\n" 119 " -x mixture [lines]\n"); 120 } 121 122 static res_T 123 parse_line_profile(const char* str, enum sln_line_profile* profile) 124 { 125 res_T res = RES_OK; 126 ASSERT(str && profile); 127 128 if(!strcmp(str, "voigt")) { 129 *profile = SLN_LINE_PROFILE_VOIGT; 130 131 } else { 132 fprintf(stderr, "invalid line profile '%s'\n", str); 133 res = RES_BAD_ARG; 134 goto error; 135 } 136 137 exit: 138 return res; 139 error: 140 goto exit; 141 } 142 143 static res_T 144 parse_mesh_type(const char* str, enum sln_mesh_type* type) 145 { 146 res_T res = RES_OK; 147 ASSERT(str && type); 148 149 if(!strcmp(str, "fit")) { 150 *type = SLN_MESH_FIT; 151 } else if(!strcmp(str, "upper")) { 152 *type = SLN_MESH_UPPER; 153 } else { 154 fprintf(stderr, "invalid mesh type `%s'\n", str); 155 res = RES_BAD_ARG; 156 goto error; 157 } 158 159 exit: 160 return res; 161 error: 162 goto exit; 163 } 164 165 static res_T 166 parse_polyline_opt(const char* str, void* ptr) 167 { 168 enum { ERR, MESH, VCOUNT } opt; 169 char buf[BUFSIZ]; 170 171 struct args* args = ptr; 172 173 char* key = NULL; 174 char* val = NULL; 175 char* tk_ctx = NULL; 176 res_T res = RES_OK; 177 178 ASSERT(str && ptr); 179 180 if(strlen(str)+1/*NULL char*/ > sizeof(buf)) { 181 fprintf(stderr, "could not duplicate polyline option `%s'\n", str); 182 res = RES_MEM_ERR; 183 goto error; 184 } 185 186 strncpy(buf, str, sizeof(buf)); 187 188 key = strtok_r(buf, "=", &tk_ctx); 189 val = strtok_r(NULL, "", &tk_ctx); 190 191 if(!strcmp(key, "err")) opt = ERR; 192 else if(!strcmp(key, "mesh")) opt = MESH; 193 else if(!strcmp(key, "vcount")) opt = VCOUNT; 194 else { 195 fprintf(stderr, "invalid polyline option `%s'\n", key); 196 res = RES_BAD_ARG; 197 goto error; 198 } 199 200 switch(opt) { 201 case ERR: 202 res = cstr_to_double(val, &args->mesh_decimation_err); 203 if(res == RES_OK && args->mesh_decimation_err < 0) res = RES_BAD_ARG; 204 break; 205 case MESH: 206 res = parse_mesh_type(val, &args->mesh_type); 207 break; 208 case VCOUNT: 209 res = cstr_to_uint(val, &args->nvertices_hint); 210 break; 211 default: FATAL("Unreachable code\n"); break; 212 } 213 214 if(res != RES_OK) { 215 fprintf(stderr, 216 "error while parsing the polyline option `%s' -- %s\n", 217 str, res_to_cstr(res)); 218 goto error; 219 } 220 221 exit: 222 return res; 223 error: 224 goto exit; 225 } 226 227 static res_T 228 args_init(struct args* args, int argc, char** argv) 229 { 230 int opt = 0; 231 res_T res = RES_OK; 232 233 ASSERT(args); 234 235 *args = ARGS_DEFAULT; 236 237 while((opt = getopt(argc, argv, "a:ce:hL:l:m:o:P:sT:t:vx:")) != -1) { 238 switch(opt) { 239 case 'a': 240 res = cstr_to_uint(optarg, &args->arity); 241 if(res == RES_OK && args->arity < 2) res = RES_BAD_ARG; 242 break; 243 case 'c': args->collapse_polylines = 1; break; 244 case 'e': 245 res = cstr_parse_list(optarg, ':', parse_polyline_opt, args); 246 break; 247 case 'h': 248 usage(stdout); 249 args->quit = 1; 250 goto exit; 251 case 'L': 252 res = cstr_to_uint(optarg, &args->leaf_nlines); 253 if(res == RES_OK && args->leaf_nlines < 1) res = RES_BAD_ARG; 254 break; 255 case 'l': 256 res = parse_line_profile(optarg, &args->line_profile); 257 break; 258 case 'o': args->output = optarg; break; 259 case 'P': 260 res = cstr_to_double(optarg, &args->pressure); 261 if(res == RES_OK && args->pressure < 0) res = RES_BAD_ARG; 262 break; 263 case 's': args->line_format = LINE_LIST_SHTR; break; 264 case 'T': 265 res = cstr_to_double(optarg, &args->temperature); 266 if(res == RES_OK && args->temperature < 0) res = RES_BAD_ARG; 267 break; 268 case 't': 269 res = cstr_to_uint(optarg, &args->nthreads_hint); 270 if(res == RES_OK && args->nthreads_hint < 1) res = RES_BAD_ARG; 271 break; 272 case 'm': args->molparams = optarg; break; 273 case 'v': args->verbose += (args->verbose < 3); break; 274 case 'x': args->mixture = optarg; break; 275 default: res = RES_BAD_ARG; break; 276 } 277 278 if(res != RES_OK) { 279 if(optarg) { 280 fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n", 281 argv[0], optarg, opt); 282 } 283 goto error; 284 } 285 } 286 287 if(optind < argc) args->lines = argv[optind]; 288 289 #define MANDATORY(Cond, Name, Opt) { \ 290 if(!(Cond)) { \ 291 fprintf(stderr, "%s: %s missing -- option '-%c'\n", argv[0], (Name), (Opt)); \ 292 res = RES_BAD_ARG; \ 293 goto error; \ 294 } \ 295 } (void)0 296 MANDATORY(args->pressure >= 0, "pressure", 'P'); 297 MANDATORY(args->temperature >= 0, "temperature", 'T'); 298 MANDATORY(args->molparams, "molparams", 'm'); 299 MANDATORY(args->mixture, "mixture", 'x'); 300 #undef MANDATORY 301 302 exit: 303 return res; 304 error: 305 usage(stderr); 306 goto exit; 307 } 308 309 static res_T 310 load_lines_hitran(struct cmd* cmd, const struct args* args) 311 { 312 struct shtr_line_list_load_args load_args = SHTR_LINE_LIST_LOAD_ARGS_NULL; 313 ASSERT(cmd && args); 314 315 if(args->lines != NULL) { 316 load_args.filename = args->lines; 317 } else { 318 load_args.filename = "stdin"; 319 load_args.file = stdin; 320 } 321 322 return shtr_line_list_load(cmd->shtr, &load_args, &cmd->lines); 323 } 324 325 static res_T 326 load_lines_shtr(struct cmd* cmd, const struct args* args) 327 { 328 struct shtr_line_list_read_args rlines_args = SHTR_LINE_LIST_READ_ARGS_NULL; 329 330 ASSERT(cmd && args); 331 332 rlines_args.filename = args->lines; 333 return shtr_line_list_read(cmd->shtr, &rlines_args, &cmd->lines); 334 } 335 336 static res_T 337 load_lines(struct cmd* cmd, const struct args* args) 338 { 339 res_T res = RES_OK; 340 switch(args->line_format) { 341 case LINE_LIST_HITRAN: res = load_lines_hitran(cmd, args); break; 342 case LINE_LIST_SHTR: res = load_lines_shtr(cmd, args); break; 343 default: FATAL("Unreachable code\n"); break; 344 } 345 return res; 346 } 347 348 static res_T 349 setup_output(struct cmd* cmd, const struct args* args) 350 { 351 res_T res = RES_OK; 352 ASSERT(cmd && args); 353 354 if(!args->output) { 355 cmd->output = stdout; 356 357 } else { 358 cmd->output = fopen(args->output, "w"); 359 if(!cmd->output) { 360 fprintf(stderr, "error opening file `%s' -- %s\n", 361 args->output, strerror(errno)); 362 res = RES_IO_ERR; 363 goto error; 364 } 365 } 366 367 exit: 368 return res; 369 error: 370 if(cmd->output && cmd->output != stdout) CHK(fclose(cmd->output) == 0); 371 goto exit; 372 } 373 374 static res_T 375 setup_tree_mixture 376 (const struct cmd* cmd, 377 struct sln_molecule molecules[SHTR_MAX_MOLECULE_COUNT]) 378 { 379 int i = 0; 380 int n = 0; 381 res_T res = RES_OK; 382 ASSERT(cmd && molecules); 383 384 n = sln_mixture_get_molecule_count(cmd->mixture); 385 FOR_EACH(i, 0, n) { 386 enum shtr_molecule_id id = sln_mixture_get_molecule_id(cmd->mixture, i); 387 res = sln_mixture_get_molecule(cmd->mixture, i, molecules+id); 388 if(res != RES_OK) goto error; 389 } 390 391 exit: 392 return res; 393 error: 394 goto exit; 395 } 396 397 static void 398 cmd_release(struct cmd* cmd) 399 { 400 ASSERT(cmd); 401 if(cmd->sln) SLN(device_ref_put(cmd->sln)); 402 if(cmd->mixture) SLN(mixture_ref_put(cmd->mixture)); 403 if(cmd->shtr) SHTR(ref_put(cmd->shtr)); 404 if(cmd->lines) SHTR(line_list_ref_put(cmd->lines)); 405 if(cmd->molparam) SHTR(isotope_metadata_ref_put(cmd->molparam)); 406 } 407 408 static res_T 409 cmd_init(struct cmd* cmd, const struct args* args) 410 { 411 struct sln_device_create_args sln_args = SLN_DEVICE_CREATE_ARGS_DEFAULT; 412 struct sln_mixture_load_args mixture_args = SLN_MIXTURE_LOAD_ARGS_NULL; 413 struct shtr_create_args shtr_args = SHTR_CREATE_ARGS_DEFAULT; 414 res_T res = RES_OK; 415 416 ASSERT(cmd && args); 417 418 *cmd = CMD_NULL; 419 420 shtr_args.verbose = args->verbose; 421 res = shtr_create(&shtr_args, &cmd->shtr); 422 if(res != RES_OK) goto error; 423 424 sln_args.verbose = args->verbose; 425 res = sln_device_create(&sln_args, &cmd->sln); 426 if(res != RES_OK) goto error; 427 428 res = shtr_isotope_metadata_load(cmd->shtr, args->molparams, &cmd->molparam); 429 if(res != RES_OK) goto error; 430 431 mixture_args.filename = args->mixture; 432 mixture_args.molparam = cmd->molparam; 433 res = sln_mixture_load(cmd->sln, &mixture_args, &cmd->mixture); 434 if(res != RES_OK) goto error; 435 436 res = setup_output(cmd, args); 437 if(res != RES_OK) goto error; 438 439 res = load_lines(cmd, args); 440 if(res != RES_OK) goto error; 441 442 cmd->args = *args; 443 444 exit: 445 return res; 446 error: 447 cmd_release(cmd); 448 *cmd = CMD_NULL; 449 goto exit; 450 } 451 452 static res_T 453 cmd_run(struct cmd* cmd) 454 { 455 struct sln_tree_create_args tree_args = SLN_TREE_CREATE_ARGS_DEFAULT; 456 struct sln_tree_write_args write_args = SLN_TREE_WRITE_ARGS_NULL; 457 struct sln_tree* tree = NULL; 458 res_T res = RES_OK; 459 460 ASSERT(cmd); 461 462 tree_args.metadata = cmd->molparam; 463 tree_args.lines = cmd->lines; 464 tree_args.pressure = cmd->args.pressure; 465 tree_args.temperature = cmd->args.temperature; 466 tree_args.nvertices_hint = cmd->args.nvertices_hint; 467 tree_args.mesh_decimation_err = cmd->args.mesh_decimation_err; 468 tree_args.mesh_type = cmd->args.mesh_type; 469 tree_args.arity = cmd->args.arity; 470 tree_args.leaf_nlines = cmd->args.leaf_nlines; 471 tree_args.collapse_polylines = cmd->args.collapse_polylines; 472 tree_args.nthreads_hint = cmd->args.nthreads_hint; 473 474 if(cmd->args.output) { 475 write_args.filename = cmd->args.output; 476 } else { 477 write_args.filename = "stdout"; 478 write_args.file = stdout; 479 } 480 481 if((res = setup_tree_mixture(cmd, tree_args.molecules)) != RES_OK) goto error; 482 if((res = sln_tree_create(cmd->sln, &tree_args, &tree)) != RES_OK) goto error; 483 if((res = sln_tree_write(tree, &write_args)) != RES_OK) goto error; 484 485 exit: 486 if(tree) SLN(tree_ref_put(tree)); 487 return res; 488 error: 489 goto exit; 490 } 491 492 /******************************************************************************* 493 * The program 494 ******************************************************************************/ 495 int 496 main(int argc, char** argv) 497 { 498 struct args args = ARGS_DEFAULT; 499 struct cmd cmd = CMD_NULL; 500 int err = 0; 501 res_T res = RES_OK; 502 503 if((res = args_init(&args, argc, argv)) != RES_OK) goto error; 504 if(args.quit) goto exit; 505 506 if((res = cmd_init(&cmd, &args)) != RES_OK) goto error; 507 if((res = cmd_run(&cmd)) != RES_OK) goto error; 508 509 exit: 510 cmd_release(&cmd); 511 CHK(mem_allocated_size() == 0); 512 return err; 513 error: 514 err = 1; 515 goto exit; 516 }