rnatm

Load and structure data describing an atmosphere
git clone git://git.meso-star.fr/rnatm.git
Log | Files | Refs | README | LICENSE

commit 3a4f5a242ec26c8db11447ef3551ec0bae661921
parent 018caeab084f4d5b8622cd6bfa106a28d3eb3940
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Wed,  1 Oct 2025 12:00:02 +0200

Make test_rnatm (renamed rnatm) a standalone utility.

It installs with the library, and a manual page replaces its detailed
built-in help to conform to the usage of any UNIX tool.

This tool can thus be used to help verify the data used to describe an
atmosphere for htrdr planets. As a side effect, it could also be used to
pre-construct its acceleration structures.

Diffstat:
M.gitignore | 3+--
MMakefile | 80++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mconfig.mk | 1+
Adoc/rnatm.1 | 304+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rrngt.5 -> doc/rngt.5 | 0
Rrnpfi.5 -> doc/rnpfi.5 | 0
Asrc/rnatm_main.c | 424+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/test_rnatm.c | 485-------------------------------------------------------------------------------
8 files changed, 770 insertions(+), 527 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,8 +4,7 @@ *.[aod] *.so *~ -test* -!test*.[ch] +rnatm .config .test tags diff --git a/Makefile b/Makefile @@ -30,7 +30,7 @@ LIBNAME_SHARED = librnatm.so LIBNAME = $(LIBNAME_$(LIB_TYPE)) default: library -all: default tests +all: default util ################################################################################ # Library building @@ -93,7 +93,33 @@ librnatm.o: $(OBJ) $(CC) $(CFLAGS_LIB) -c $< -o $@ ################################################################################ -# Installation +# Tests +################################################################################ +UTIL_SRC = src/rnatm_main.c +UTIL_OBJ = $(UTIL_SRC:.c=.o) +UTIL_DEP = $(UTIL_SRC:.c=.d) + +PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) +INCS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rnatm-local rsys) +LIBS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rnatm-local rsys) + +CFLAGS_UTIL = $(CFLAGS_EXE) $(INCS_UTIL) +LDFLAGS_UTIL = $(LDFLAGS_EXE) $(LIBS_UTIL) + +util: library $(UTIL_DEP) + @$(MAKE) -fMakefile -f src/rnatm_main.d rnatm + +$(UTIL_DEP): config.mk rnatm-local.pc + @$(CC) $(CFLAGS_UTIL) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ + +$(UTIL_OBJ): config.mk rnatm-local.pc + $(CC) $(CFLAGS_UTIL) -c $(@:.o=.c) -o $@ + +rnatm: src/rnatm_main.o config.mk rnatm-local.pc $(LIBNAME) + $(CC) $(CFLAGS_UTIL) -o $@ src/rnatm_main.o $(LDFLAGS_UTIL) + +################################################################################ +# Miscellaneous ################################################################################ pkg: sed -e 's#@PREFIX@#$(PREFIX)#g'\ @@ -127,65 +153,39 @@ rnatm-local.pc: rnatm.pc.in -e 's#@SVX_VERSION@#$(SVX_VERSION)#g'\ rnatm.pc.in > $@ -install: library pkg +install: library util pkg install() { mode="$$1"; prefix="$$2"; shift 2; \ mkdir -p "$${prefix}"; \ cp "$$@" "$${prefix}"; \ chmod "$${mode}" "$${prefix}/$${@##*/}"; \ }; \ + install 755 "$(DESTDIR)$(BINPREFIX)" rnatm; \ install 755 "$(DESTDIR)$(LIBPREFIX)" $(LIBNAME); \ install 644 "$(DESTDIR)$(LIBPREFIX)/pkgconfig" rnatm.pc; \ install 644 "$(DESTDIR)$(INCPREFIX)/rad-net" src/rnatm.h; \ - install 644 "$(DESTDIR)$(MANPREFIX)/man5" rngt.5; \ - install 644 "$(DESTDIR)$(MANPREFIX)/man5" rnpfi.5; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/rnatm.1; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man5" doc/rngt.5; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man5" doc/rnpfi.5; \ install 644 "$(DESTDIR)$(PREFIX)/share/doc/rnatm" COPYING; \ install 644 "$(DESTDIR)$(PREFIX)/share/doc/rnatm" README.md uninstall: + rm -f "$(DESTDIR)$(BINPREFIX)/rnatm" rm -f "$(DESTDIR)$(LIBPREFIX)/$(LIBNAME)" rm -f "$(DESTDIR)$(LIBPREFIX)/pkgconfig/rnatm.pc" rm -f "$(DESTDIR)$(INCPREFIX)/rad-net/rnatm.h" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/rnatm.1" rm -f "$(DESTDIR)$(MANPREFIX)/man5/rngt.5" rm -f "$(DESTDIR)$(MANPREFIX)/man5/rnpfi.5" rm -f "$(DESTDIR)$(PREFIX)/share/doc/rnatm/COPYING" rm -f "$(DESTDIR)$(PREFIX)/share/doc/rnatm/README.md" -################################################################################ -# Miscellaneous targets -################################################################################ -clean: clean_test +clean: rm -f $(DEP) $(OBJ) $(LIBNAME) + rm -f $(UTIL_DEP) $(UTIL_OBJ) rnatm rm -f .config librnatm.o rnatm.pc rnatm-local.pc lint: - mandoc -Tlint -Wall rngt.5 || [ $$? -le 1 ] - mandoc -Tlint -Wall rnpfi.5 || [ $$? -le 1 ] - -################################################################################ -# Tests -################################################################################ -TEST_SRC = src/test_rnatm.c -TEST_OBJ = $(TEST_SRC:.c=.o) -TEST_DEP = $(TEST_SRC:.c=.d) - -PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) -INCS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rnatm-local rsys) -LIBS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rnatm-local rsys) - -CFLAGS_TEST = $(CFLAGS_EXE) $(INCS_TEST) -LDFLAGS_TEST = $(LDFLAGS_EXE) $(LIBS_TEST) - -tests: library $(TEST_DEP) src/test_rnatm.d - @$(MAKE) -fMakefile -f src/test_rnatm.d test_rnatm - -clean_test: - rm -f test_rnatm src/test_rnatm.d src/test_rnatm.o - -$(TEST_DEP): config.mk rnatm-local.pc - @$(CC) $(CFLAGS_TEST) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ - -$(TEST_OBJ): config.mk rnatm-local.pc - $(CC) $(CFLAGS_TEST) -c $(@:.o=.c) -o $@ - -test_rnatm: src/test_rnatm.o config.mk rnatm-local.pc $(LIBNAME) - $(CC) $(CFLAGS_TEST) -o $@ src/$@.o $(LDFLAGS_TEST) + mandoc -Tlint -Wall doc/rnatm.1 || [ $$? -le 1 ] + mandoc -Tlint -Wall doc/rngt.5 || [ $$? -le 1 ] + mandoc -Tlint -Wall doc/rnpfi.5 || [ $$? -le 1 ] diff --git a/config.mk b/config.mk @@ -1,6 +1,7 @@ VERSION = 0.1.0 PREFIX = /usr/local +BINPREFIX = $(PREFIX)/bin LIBPREFIX = $(PREFIX)/lib INCPREFIX = $(PREFIX)/include MANPREFIX = $(PREFIX)/share/man diff --git a/doc/rnatm.1 b/doc/rnatm.1 @@ -0,0 +1,304 @@ +.\" Copyright (C) 2022, 2023 Centre National de la Recherche Scientifique +.\" Copyright (C) 2022, 2023 Institut Pierre-Simon Laplace +.\" Copyright (C) 2022, 2023 Institut de Physique du Globe de Paris +.\" Copyright (C) 2022, 2023 |Méso|Star>(contact@meso-star.com) +.\" Copyright (C) 2022, 2023 Observatoire de Paris +.\" Copyright (C) 2022, 2023 Université de Reims Champagne-Ardenne +.\" Copyright (C) 2022, 2023 Université de Versaille Saint-Quentin +.\" Copyright (C) 2022, 2023 Université Paul Sabatier +.\" +.\" This program is free software: you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation, either version 3 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program. If not, see <http://www.gnu.org/licenses/>. +.Dd October 1, 2025 +.Dt RNATM 1 +.Os +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh NAME +.Nm rnatm +.Nd check atmospheric data and structures built to manage them +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SYNOPSIS +.Nm +.Op Fl hNv +.Op Fl a Ar aerosol_opt Ns Op : Ns Ar aerosol_opt No ... +.Op Fl c +.Op Fl d Ar octrees +.Op Fl i Ar storage +.Op Fl n Ar name +.Op Fl o Ar storage +.Op Fl s Ar nu0 , Ns Ar nu1 +.Op Fl T Ar optical_thickness +.Op Fl t Ar thread_count +.Op Fl V Ar definition +.Fl g Ar gas_opt Ns Op : Ns Ar gas_opt No ... +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh DESCRIPTION +.Nm +helps to check and diagnose errors in input data as submitted to the RaD-net +atmospheric library. +It also makes it easy to check the internal workings of the library, such as the +construction of structures used to accelerate ray tracing in the heterogeneous +semi-transparent environment described by the input data. +.Pp +In addition to regular verification of the input data format, additional tests +can be performed +.Pq option Fl c +to further validate the data, at the cost of longer execution time. +For example, enabling this option allows to verify the validity of aerosol +phase function indices with respect to the mesh to which they are associated and +the list of loaded phase functions. +.Pp +The options are as follows: +.Bl -tag -width Ds +.\"""""""""""""""""""""""""""""""""""""" +.It Fl a Ar aerosol +Define an aerosol. +Use this option once per aerosol, and duplicate it as many times as +necessary. +.Pp +The aerosol options are as follows: +.Bl -tag -width Ds +.It Cm mesh= Ns Ar volume_mesh +Aerosol tetrahedral mesh saved in +.Xr smsh 5 +format. +.It Cm name= Ns Ar string +Name assigned to the aerosol. +.It Cm radprop= Ns Ar radiative_properties +Radiatve properties of the aerosol saved in +.Xr sars 5 +format. +Radiative properties are defined per volumetric mesh node. +This file and the tetrahedral mesh +.Pq option Cm mesh +must therefore be consistent with each other, i.e. the nodes must be +listed in the same order. +.It Cm phasefn= Ns Ar phase_functions_list +List in +.Xr rnsl 5 +format of phase functions to be loaded. +Each phase function is saved in +.Xr rnsf 5 +format. +The correspondence between these phase functions and the nodes of the +volumetric mesh is defined in another file +.Pq option Cm phaseids . +.It Cm phaseids= Ns Ar per_node_phase_function +Path to the +.Xr rnpfi 5 +file that stores the index of the phase function to be used per +volumetric mesh node. +The list of phase function is defined in another file +.Pq option Cm phasefn . +Note that this file and the tetrahedral mesh +.Pq option Cm mesh +must be consistent with each other, i.e. the nodes must be +listed in the same order. +.El +.\"""""""""""""""""""""""""""""""""""""" +.It Fl c +Thorough data validation. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl d Ar octrees +Write the builded acceleration structures (i.e., the octrees) to file according +to the VTK file format. +They are written to standard ouput if the file is a dash +.Pq - . +To split the resulting file +into +.Ar n +VTK files, each storing an octree, one can use the csplit command. +For example with +.Ar n += 4: +.Bd -literal -offset Ds +csplit -f octree -k file %^#\ vtk% /^#\ vtk/ {2}; +.Ed +.\"""""""""""""""""""""""""""""""""""""" +.It Fl g Ar gas_opt Ns Op : Ns Ar gas_opt No ... +Gas mixture. +.Pp +The gas options are as follows: +.Bl -tag -width Ds +.It Cm mesh= Ns Ar volumetric_mesh +Gas tetrahedral mesh saved in +.Xr smsh 5 +format. +.It Cm ck= Ns Ar correlated_k +Correlated K fof the gas saved in +.Xr sck 5 +format. +The correlated K are defined per volumetric mesh node. +This file and the tetrahedral mesh +.Pq option Cm mesh +must therefore be consistent with each other, i.e. the nodes must be +listed in the same order. +.It Cm temp= Ns Ar temperature +Gas temperatures saved in +.Xr rngt 5 +format. +The temperature is defined per volumetric mesh node. +This file and the tetrahedral mesh +.Pq option Cm mesh +must therefore be consistent with each other, i.e. the nodes must be +listed in the same order. +.El +.\"""""""""""""""""""""""""""""""""""""" +.It Fl h +Display short help and exit. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl i Ar storage +Acceleration structures are not built from scratch, but loaded from the +.Ar storage +file. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl N +Precalculate tetrahedron normals. +This speeds up execution performance for applications that search for the +tetrahedron to which a position belongs. +In return, the memory space used to store normals increases the memory +footprint. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl n Ar name +Atmosphere name. +Default is +.Dq atmosphere . +.\"""""""""""""""""""""""""""""""""""""" +.It Fl o Ar storage +Offload acceleration structures to the +.Ar storage +file. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl s Ar nu0 , Ns Ar nu1 +Spectral range to consider +.Pq in nanometers . +Default is visible spectrum, i.e., [380, 780] nm. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl T Ar optical_thickness +Optical thickness criteria for the construction of acceleration structures. +Default is 1. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl t Ar thread_count +Advice on the number of threads to use to build acceleration structures. +Default assumes as many threads as processor cores. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl V Ar definition +Advice on the definition of the acceleration structures along the 3 axis. +Default is 512. +.\"""""""""""""""""""""""""""""""""""""" +.It Fl v +Make +.Nm +Verbose. +.El +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXIT STATUS +.Ex -std +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXAMPLES +A +.Nm +command line can be lengthy due to the options required to describe the +atmopshere. +For editing purposes, the following command line spans multiple lines using the +backslash character +.Pq \e . +While there's nothing original about this practice, we'd like to +emphasize the importance of spaces or their absence before the backslash +character, particularly for options defining aerosols +.Pq option Fl a : +their argument must be a single character string with no spaces other +than those that may be required for file names. +.Bd -literal -offset Ds +rnatm -v -c \\ + -g mesh=gas.smsh:ck=gas.sck:temp=gas.rngt \\ + -a name=clouds\\ +:mesh=clouds_tetrahedra.smsh\\ +:radprop=clouds_properties.sars\\ +:phasefn=clouds_phase_functions.rnsf\\ +:phaseids=clouds_phase_function_ids.rnpfi \\ + -a name=haze\\ +:mesh=haze_tetrahedra.smsh\\ +:radprop=haze_properties.sars\\ +:phasefn=haze_phase_functions.rnsf\\ +:phaseids=haze_phase_function_ids.rnpfi \\ + -N \\ + -T 10 \\ + -V 1024 + -o storage.bin +.Ed +.Pp +The above command line runs +.Nm +in a verbose way +.Pq option Fl v +for a thorough verification of the input data +.Pq option Fl c . +The gas of the planetary atmosphere +.Pq option Fl g +is described by the tetrahedral mesh recorded in the +.Pa gas.smsh +file, while its spectral data and temperature are given by the files +.Pa gas.sck +and +.Pa gas.rngt , +respectively. +.Pp +Two aerosols complete the planetary atmosphere +.Pq option Fl a : +one for +.Ar clouds +and one for +.Ar haze . +Their respective meshes are stored in the +.Pa clouds_tetrahedra.smsh No and Pa haze_tetrahedra.smsh +files while their radiative properties are given by the +.Pa clouds_properties.sars No and Pa haze_properties.sars +files. +Finally, their phase functions are described by a set of 2 files: the +.Pa clouds_phase_functions.rnsf No and Pa haze_phase_functions.rnsf +files which list the aerosol phase functions, +and the +.Pa clouds_phase_function_ids.rnpfi No and Pa haze_phase_function_ids.rnpfi +files which reference them by volumetric mesh node. +.Pp +The normals of the tetrahedral meshes of the gas and aerosols are +precalculated +once and for all +.Pq option Fl N . +The definition of acceleration structures +cannot exceed +.Ar 1024^3 +.Pq option Fl V +and its voxels can be merged until their optical thickness +is greater than +.Ar 10 +.Pq option Fl T . +These structures are finally stored in +.Pa storage.bin +.Pq option Fl o . +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SEE ALSO +.Xr htrdr-planets 1 , +.Xr rnpfi 5 , +.Xr rnsf 5 , +.Xr rnsl 5 , +.Xr sars 5 , +.Xr sck 5 , +.Xr smsh 5 +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh HISTORY +.Nm +has been developed as part of +.Ql ANR-21-CE49-0020 +RaD-net project. diff --git a/rngt.5 b/doc/rngt.5 diff --git a/rnpfi.5 b/doc/rnpfi.5 diff --git a/src/rnatm_main.c b/src/rnatm_main.c @@ -0,0 +1,424 @@ +/* Copyright (C) 2022, 2023 Centre National de la Recherche Scientifique + * Copyright (C) 2022, 2023 Institut Pierre-Simon Laplace + * Copyright (C) 2022, 2023 Institut de Physique du Globe de Paris + * Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2022, 2023 Observatoire de Paris + * Copyright (C) 2022, 2023 Université de Reims Champagne-Ardenne + * Copyright (C) 2022, 2023 Université de Versaille Saint-Quentin + * Copyright (C) 2022, 2023 Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200809L /* strdup, strtok_r */ + +#include "rnatm.h" + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> +#include <rsys/rsys.h> +#include <rsys/stretchy_array.h> + +#include <getopt.h> +#include <string.h> + +struct args { + struct rnatm_create_args rnatm; + const char* vtk_filename; + int check; + int quit; +}; +#define ARGS_DEFAULT__ { RNATM_CREATE_ARGS_DEFAULT__, NULL, 0, 0 } +static const struct args ARGS_DEFAULT = ARGS_DEFAULT__; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +usage(FILE* stream) +{ + ASSERT(stream); + fprintf(stream, +"usage: rnatm [-hNv] [-a aerosol_opt[:aerosol_opt ...]] [-c] [-d octrees]\n" +" [-i storage] [-n name] [-o storage] [-s nu0,nu1]\n" +" [-T optical_thickness] [-t thread_count] [-V definition]\n" +" -g gas_opt[:gas_opt ...]\n"); +} + +static res_T +parse_gas_parameters(const char* str, void* ptr) +{ + enum { MESH, CK, TEMP } iparam; + char buf[BUFSIZ]; + struct args* args = ptr; + char* key; + char* val; + char* tk_ctx; + res_T res = RES_OK; + ASSERT(args && str); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, "Could not duplicate the gas parameter `%s'\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &tk_ctx); + val = strtok_r(NULL, "", &tk_ctx); + + if(!strcmp(key, "mesh")) iparam = MESH; + else if(!strcmp(key, "ck")) iparam = CK; + else if(!strcmp(key, "temp")) iparam = TEMP; + else { + fprintf(stderr, "Invalid gas parameter `%s'\n", key); + res = RES_BAD_ARG; + goto error; + } + + if(!val) { + fprintf(stderr, "Invalid null value for gas parameter `%s'\n", key); + res = RES_BAD_ARG; + goto error; + } + + switch(iparam) { + case MESH: + args->rnatm.gas.smsh_filename = strdup(val); + if(!args->rnatm.gas.smsh_filename) res = RES_MEM_ERR; + break; + case CK: + args->rnatm.gas.sck_filename = strdup(val); + if(!args->rnatm.gas.sck_filename) res = RES_MEM_ERR; + break; + case TEMP: + args->rnatm.gas.temperatures_filename = strdup(val); + if(!args->rnatm.gas.temperatures_filename) res = RES_MEM_ERR; + break; + default: FATAL("Unreachable code\n"); break; + } + if(res != RES_OK) { + fprintf(stderr, "Unable to parse the gas parameter `%s' -- %s\n", + str, res_to_cstr(res)); + goto error; + } + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_aerosol_parameters(const char* str, void* ptr) +{ + enum { MESH, NAME, RADPROP, PHASEFN, PHASEIDS } iparam; + struct rnatm_aerosol_args* aerosol = NULL; + char buf[BUFSIZ]; + struct args* args = ptr; + char* key; + char* val; + char* tk_ctx; + res_T res = RES_OK; + ASSERT(args && str); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, "Could not duplicate the aerosol parameter `%s'\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &tk_ctx); + val = strtok_r(NULL, "", &tk_ctx); + + if(!strcmp(key, "mesh")) iparam = MESH; + else if(!strcmp(key, "name")) iparam = NAME; + else if(!strcmp(key, "radprop")) iparam = RADPROP; + else if(!strcmp(key, "phasefn")) iparam = PHASEFN; + else if(!strcmp(key, "phaseids")) iparam = PHASEIDS; + else { + fprintf(stderr, "Invalid aerosol parameter `%s'\n", key); + res = RES_BAD_ARG; + goto error; + } + + if(!val) { + fprintf(stderr, "Invalid null value for aerosol parameter `%s'\n", key); + res = RES_BAD_ARG; + goto error; + } + + ASSERT(args->rnatm.naerosols); + aerosol = args->rnatm.aerosols + (args->rnatm.naerosols - 1); + + switch(iparam) { + case MESH: + aerosol->smsh_filename = strdup(val); + if(!aerosol->smsh_filename) res = RES_MEM_ERR; + break; + case NAME: + aerosol->name = strdup(val); + if(!aerosol->name) res = RES_MEM_ERR; + break; + case RADPROP: + aerosol->sars_filename = strdup(val); + if(!aerosol->sars_filename) res = RES_MEM_ERR; + break; + case PHASEFN: + aerosol->phase_fn_lst_filename = strdup(val); + if(!aerosol->phase_fn_lst_filename) res = RES_MEM_ERR; + break; + case PHASEIDS: + aerosol->phase_fn_ids_filename = strdup(val); + if(!aerosol->phase_fn_ids_filename) res = RES_MEM_ERR; + break; + default: FATAL("Unreachable code\n"); break; + } + if(res != RES_OK) { + fprintf(stderr, "Unable to parse the aerosol parameter `%s' -- %s\n", + str, res_to_cstr(res)); + goto error; + } + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_spectral_range(struct args* args, char* str) +{ + size_t len = 0; + res_T res = RES_OK; + ASSERT(args && str); + + res = cstr_to_list_double(str, ',', args->rnatm.spectral_range, &len, 2); + if(res == RES_OK && len != 2) res = RES_BAD_ARG; + if(res != RES_OK) goto error; + + if(args->rnatm.spectral_range[0] < 0 + || args->rnatm.spectral_range[1] < 0 + || args->rnatm.spectral_range[0] > args->rnatm.spectral_range[1]) + goto error; + +exit: + return res; +error: + goto exit; +} + +static void +args_release(struct args* args) +{ + size_t i; + ASSERT(args); + if(args->rnatm.gas.smsh_filename) free(args->rnatm.gas.smsh_filename); + if(args->rnatm.gas.sck_filename) free(args->rnatm.gas.sck_filename); + if(args->rnatm.gas.temperatures_filename) + free(args->rnatm.gas.temperatures_filename); + if(args->rnatm.octrees_storage) CHK(fclose(args->rnatm.octrees_storage) == 0); + + FOR_EACH(i, 0, args->rnatm.naerosols) { + struct rnatm_aerosol_args* aerosol = args->rnatm.aerosols + i; + if(aerosol->name) free(aerosol->name); + if(aerosol->smsh_filename) free(aerosol->smsh_filename); + if(aerosol->sars_filename) free(aerosol->sars_filename); + if(aerosol->phase_fn_ids_filename) free(aerosol->phase_fn_ids_filename); + if(aerosol->phase_fn_lst_filename) free(aerosol->phase_fn_lst_filename); + } + sa_release(args->rnatm.aerosols); + *args = ARGS_DEFAULT; +} + +static res_T +args_init(struct args* args, int argc, char** argv) +{ + const char* storage_filename = NULL; + size_t i = 0; + res_T res = RES_OK; + int opt; + ASSERT(args && argc && argv); + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "a:cd:g:hi:Nn:o:s:T:t:V:v")) != -1) { + switch(opt) { + case 'a': + (void)sa_add(args->rnatm.aerosols, 1); + args->rnatm.aerosols[args->rnatm.naerosols] = RNATM_AEROSOL_ARGS_NULL; + args->rnatm.naerosols += 1; + res = cstr_parse_list(optarg, ':', parse_aerosol_parameters, args); + break; + case 'c': args->check = 1; break; + case 'd': args->vtk_filename = optarg; break; + case 'g': + res = cstr_parse_list(optarg, ':', parse_gas_parameters, args); + break; + case 'h': + usage(stdout); + args_release(args); + args->quit = 1; + goto exit; + case 'N': args->rnatm.precompute_normals = 1; break; + case 'n': args->rnatm.name = optarg; break; + case 'o': + args->rnatm.load_octrees_from_storage = 0; + storage_filename = optarg; + break; + case 'i': + args->rnatm.load_octrees_from_storage = 1; + storage_filename = optarg; + break; + case 's': + res = parse_spectral_range(args, optarg); + break; + case 'T': + res = cstr_to_double(optarg, &args->rnatm.optical_thickness); + if(res != RES_OK && args->rnatm.optical_thickness<=0) res = RES_BAD_ARG; + break; + case 't': + res = cstr_to_uint(optarg, &args->rnatm.nthreads); + if(res == RES_OK && !args->rnatm.nthreads) res = RES_BAD_ARG; + break; + case 'V': + res = cstr_to_uint(optarg, &args->rnatm.grid_definition_hint); + if(res == RES_OK && !args->rnatm.grid_definition_hint) res = RES_BAD_ARG; + break; + case 'v': args->rnatm.verbose = 1; break; + default: res = RES_BAD_ARG; break; + } + if(res != RES_OK) { + if(optarg) { + fprintf(stderr, "%s: invalid option args `%s' -- `%c'\n", + argv[0], optarg, opt); + } + goto error; + } + } + + if(storage_filename) { + const char* mode = args->rnatm.load_octrees_from_storage ? "r" : "w+"; + args->rnatm.octrees_storage = fopen(storage_filename, mode); + if(!args->rnatm.octrees_storage) { + fprintf(stderr, "Unable to open octree storage file %s\n", + storage_filename); + res = RES_IO_ERR; + goto error; + } + } + + /* Check the required options */ + if(!args->rnatm.gas.smsh_filename + || !args->rnatm.gas.sck_filename + || !args->rnatm.gas.temperatures_filename) { + fprintf(stderr, "Incomplete gas definition -- option `-g'\n"); + res = RES_BAD_ARG; + goto error; + } + + FOR_EACH(i, 0, args->rnatm.naerosols) { + struct rnatm_aerosol_args* aerosol = args->rnatm.aerosols + i; + + if(!aerosol->smsh_filename + || !aerosol->sars_filename + || !aerosol->phase_fn_ids_filename + || !aerosol->phase_fn_lst_filename) { + fprintf(stderr, "Incomplete %lu^th aerosol definition -- option `-a'\n", + (unsigned long)i); + res = RES_BAD_ARG; + goto error; + } + } + +exit: + return res; +error: + usage(stderr); + args_release(args); + goto exit; +} + +static res_T +write_vtk_octrees(struct rnatm* atm, const char* filename) +{ + size_t octrees_range[2]; + FILE* fp = NULL; + res_T res = RES_OK; + ASSERT(atm && filename); + + if(!strcmp(filename, "-")) { + fp = stdout; + } else { + fp = fopen(filename, "w"); + if(!fp) { + fprintf(stderr, "Could not open `%s' -- %s\n", filename, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + } + + octrees_range[0] = 0; + octrees_range[1] = rnatm_get_spectral_items_count(atm) - 1; + + res = rnatm_write_vtk_octrees(atm, octrees_range, fp); + if(res != RES_OK) goto error; + +exit: + if(fp != stdout) CHK(fclose(fp) == 0); + return res; +error: + goto exit; +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct args args = ARGS_DEFAULT; + struct rnatm* atm = NULL; + res_T res = RES_OK; + int err = 0; + + res = args_init(&args, argc, argv); + if(res != RES_OK) goto error; + + res = rnatm_create(&args.rnatm, &atm); + if(res != RES_OK) goto error; + + if(args.check) { + res = rnatm_validate(atm); + if(res != RES_OK) goto error; + } + + if(args.vtk_filename) { + res = write_vtk_octrees(atm, args.vtk_filename); + if(res != RES_OK) goto error; + } + +exit: + args_release(&args); + if(atm) RNATM(ref_put(atm)); + if(mem_allocated_size() != 0) { + fprintf(stderr, "Memory leaks: %lu bytes\n", + (unsigned long)mem_allocated_size()); + err = -1; + } + return err; +error: + err = -1; + goto exit; +} diff --git a/src/test_rnatm.c b/src/test_rnatm.c @@ -1,485 +0,0 @@ -/* Copyright (C) 2022, 2023 Centre National de la Recherche Scientifique - * Copyright (C) 2022, 2023 Institut Pierre-Simon Laplace - * Copyright (C) 2022, 2023 Institut de Physique du Globe de Paris - * Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) - * Copyright (C) 2022, 2023 Observatoire de Paris - * Copyright (C) 2022, 2023 Université de Reims Champagne-Ardenne - * Copyright (C) 2022, 2023 Université de Versaille Saint-Quentin - * Copyright (C) 2022, 2023 Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200809L /* strdup, strtok_r */ - -#include "rnatm.h" - -#include <rsys/cstr.h> -#include <rsys/mem_allocator.h> -#include <rsys/rsys.h> -#include <rsys/stretchy_array.h> - -#include <getopt.h> -#include <string.h> - -struct args { - struct rnatm_create_args rnatm; - const char* vtk_filename; - int check; - int quit; -}; -#define ARGS_DEFAULT__ { RNATM_CREATE_ARGS_DEFAULT__, NULL, 0, 0 } -static const struct args ARGS_DEFAULT = ARGS_DEFAULT__; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -print_help(const char* cmd) -{ - ASSERT(cmd); - printf( -"Usage: %s -g gas\n" -"Test the Rad-Net ATMosphere library\n\n", - cmd); - printf( -" -a aerosol atmospheric aerosol. Use this option as many times as\n" -" there are aerosols to declare. An aerosol is defined\n" -" by the following parameters, each parameter is\n" -" separated from the previous one by a colon (:)\n\n"); - printf( -" mesh=path aerosol mesh (smsh(5))\n" -" name=string aerosol name\n" -" radprop=path radiative properties (sars(5))\n" -" phasefn=path list of phase functions (rnsl(5))\n" -" phaseids=path phase function id by node (rnpfi(5))\n\n"); - printf( -" -c check data\n"); - printf( -" -d file write the builded octrees to file according to the VTK\n" -" file format. The octrees are written to standard ouput\n" -" if the file is a dash (-). To split the resulting file\n" -" into n VTK files, each storing an octree, one can use\n" -" the csplit command. For example with n = 4:\n\n"); - printf( -" csplit -f octree -k file %%^#\\ vtk%% /^#\\ vtk/ {2}\n\n"); - printf( -" -g gas atmospheric gas mixture. This mixture is defined by\n" -" the following parameters, each parameter is separated\n" -" from the previous one by a colon (:)\n\n"); - printf( -" mesh=path gas mesh (smsh(5))\n" -" ck=path correlated-K of the mixture (sck(5))\n" -" temp=path temperatures (rngt(5))\n\n"); - printf( -" -h display this help and exit\n"); - printf( -" -i file octrees are loaded from file\n"); - printf( -" -N precompute_normals the tetrahedra normals\n"); - printf( -" -n atmosphere name. Default is `%s'\n", - ARGS_DEFAULT.rnatm.name); - printf( -" -o file offload octrees to file\n"); - printf( -" -s nu0,nu1 spectral range to consider (in nm).\n" -" Default is visible spectrum, i.e [%g, %g] nm\n", - ARGS_DEFAULT.rnatm.spectral_range[0], - ARGS_DEFAULT.rnatm.spectral_range[1]); - printf( -" -T threshold optical thickness criteria for octree building.\n" -" Default is %g\n", - ARGS_DEFAULT.rnatm.optical_thickness); - printf( -" -t nthreads hint on the number of threads. Default assumes\n" -" as many threads as CPU cores\n"); - printf( -" -V definition advice on the definiton of the acceleration\n" -" structure along the 3 axes. Default is %u\n", - ARGS_DEFAULT.rnatm.grid_definition_hint); - printf( -" -v make the program verbose\n"); - printf("\n"); - printf( -"This is free software released under the GNU GPL license, version 3 or\n" -"later. You are free to change or redistribute it under certain\n" -"conditions <http://gnu.org.licenses/gpl.html>\n"); -} - -static res_T -parse_gas_parameters(const char* str, void* ptr) -{ - enum { MESH, CK, TEMP } iparam; - char buf[BUFSIZ]; - struct args* args = ptr; - char* key; - char* val; - char* tk_ctx; - res_T res = RES_OK; - ASSERT(args && str); - - if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { - fprintf(stderr, "Could not duplicate the gas parameter `%s'\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - key = strtok_r(buf, "=", &tk_ctx); - val = strtok_r(NULL, "", &tk_ctx); - - if(!strcmp(key, "mesh")) iparam = MESH; - else if(!strcmp(key, "ck")) iparam = CK; - else if(!strcmp(key, "temp")) iparam = TEMP; - else { - fprintf(stderr, "Invalid gas parameter `%s'\n", key); - res = RES_BAD_ARG; - goto error; - } - - if(!val) { - fprintf(stderr, "Invalid null value for gas parameter `%s'\n", key); - res = RES_BAD_ARG; - goto error; - } - - switch(iparam) { - case MESH: - args->rnatm.gas.smsh_filename = strdup(val); - if(!args->rnatm.gas.smsh_filename) res = RES_MEM_ERR; - break; - case CK: - args->rnatm.gas.sck_filename = strdup(val); - if(!args->rnatm.gas.sck_filename) res = RES_MEM_ERR; - break; - case TEMP: - args->rnatm.gas.temperatures_filename = strdup(val); - if(!args->rnatm.gas.temperatures_filename) res = RES_MEM_ERR; - break; - default: FATAL("Unreachable code\n"); break; - } - if(res != RES_OK) { - fprintf(stderr, "Unable to parse the gas parameter `%s' -- %s\n", - str, res_to_cstr(res)); - goto error; - } - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_aerosol_parameters(const char* str, void* ptr) -{ - enum { MESH, NAME, RADPROP, PHASEFN, PHASEIDS } iparam; - struct rnatm_aerosol_args* aerosol = NULL; - char buf[BUFSIZ]; - struct args* args = ptr; - char* key; - char* val; - char* tk_ctx; - res_T res = RES_OK; - ASSERT(args && str); - - if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { - fprintf(stderr, "Could not duplicate the aerosol parameter `%s'\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - key = strtok_r(buf, "=", &tk_ctx); - val = strtok_r(NULL, "", &tk_ctx); - - if(!strcmp(key, "mesh")) iparam = MESH; - else if(!strcmp(key, "name")) iparam = NAME; - else if(!strcmp(key, "radprop")) iparam = RADPROP; - else if(!strcmp(key, "phasefn")) iparam = PHASEFN; - else if(!strcmp(key, "phaseids")) iparam = PHASEIDS; - else { - fprintf(stderr, "Invalid aerosol parameter `%s'\n", key); - res = RES_BAD_ARG; - goto error; - } - - if(!val) { - fprintf(stderr, "Invalid null value for aerosol parameter `%s'\n", key); - res = RES_BAD_ARG; - goto error; - } - - ASSERT(args->rnatm.naerosols); - aerosol = args->rnatm.aerosols + (args->rnatm.naerosols - 1); - - switch(iparam) { - case MESH: - aerosol->smsh_filename = strdup(val); - if(!aerosol->smsh_filename) res = RES_MEM_ERR; - break; - case NAME: - aerosol->name = strdup(val); - if(!aerosol->name) res = RES_MEM_ERR; - break; - case RADPROP: - aerosol->sars_filename = strdup(val); - if(!aerosol->sars_filename) res = RES_MEM_ERR; - break; - case PHASEFN: - aerosol->phase_fn_lst_filename = strdup(val); - if(!aerosol->phase_fn_lst_filename) res = RES_MEM_ERR; - break; - case PHASEIDS: - aerosol->phase_fn_ids_filename = strdup(val); - if(!aerosol->phase_fn_ids_filename) res = RES_MEM_ERR; - break; - default: FATAL("Unreachable code\n"); break; - } - if(res != RES_OK) { - fprintf(stderr, "Unable to parse the aerosol parameter `%s' -- %s\n", - str, res_to_cstr(res)); - goto error; - } - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_spectral_range(struct args* args, char* str) -{ - size_t len = 0; - res_T res = RES_OK; - ASSERT(args && str); - - res = cstr_to_list_double(str, ',', args->rnatm.spectral_range, &len, 2); - if(res == RES_OK && len != 2) res = RES_BAD_ARG; - if(res != RES_OK) goto error; - - if(args->rnatm.spectral_range[0] < 0 - || args->rnatm.spectral_range[1] < 0 - || args->rnatm.spectral_range[0] > args->rnatm.spectral_range[1]) - goto error; - -exit: - return res; -error: - goto exit; -} - -static void -args_release(struct args* args) -{ - size_t i; - ASSERT(args); - if(args->rnatm.gas.smsh_filename) free(args->rnatm.gas.smsh_filename); - if(args->rnatm.gas.sck_filename) free(args->rnatm.gas.sck_filename); - if(args->rnatm.gas.temperatures_filename) - free(args->rnatm.gas.temperatures_filename); - if(args->rnatm.octrees_storage) CHK(fclose(args->rnatm.octrees_storage) == 0); - - FOR_EACH(i, 0, args->rnatm.naerosols) { - struct rnatm_aerosol_args* aerosol = args->rnatm.aerosols + i; - if(aerosol->name) free(aerosol->name); - if(aerosol->smsh_filename) free(aerosol->smsh_filename); - if(aerosol->sars_filename) free(aerosol->sars_filename); - if(aerosol->phase_fn_ids_filename) free(aerosol->phase_fn_ids_filename); - if(aerosol->phase_fn_lst_filename) free(aerosol->phase_fn_lst_filename); - } - sa_release(args->rnatm.aerosols); - *args = ARGS_DEFAULT; -} - -static res_T -args_init(struct args* args, int argc, char** argv) -{ - const char* storage_filename = NULL; - size_t i = 0; - res_T res = RES_OK; - int opt; - ASSERT(args && argc && argv); - - *args = ARGS_DEFAULT; - - while((opt = getopt(argc, argv, "a:cd:g:hi:Nn:o:s:T:t:V:v")) != -1) { - switch(opt) { - case 'a': - (void)sa_add(args->rnatm.aerosols, 1); - args->rnatm.aerosols[args->rnatm.naerosols] = RNATM_AEROSOL_ARGS_NULL; - args->rnatm.naerosols += 1; - res = cstr_parse_list(optarg, ':', parse_aerosol_parameters, args); - break; - case 'c': args->check = 1; break; - case 'd': args->vtk_filename = optarg; break; - case 'g': - res = cstr_parse_list(optarg, ':', parse_gas_parameters, args); - break; - case 'h': - print_help(argv[0]); - args_release(args); - args->quit = 1; - goto exit; - case 'N': args->rnatm.precompute_normals = 1; break; - case 'n': args->rnatm.name = optarg; break; - case 'o': - args->rnatm.load_octrees_from_storage = 0; - storage_filename = optarg; - break; - case 'i': - args->rnatm.load_octrees_from_storage = 1; - storage_filename = optarg; - break; - case 's': - res = parse_spectral_range(args, optarg); - break; - case 'T': - res = cstr_to_double(optarg, &args->rnatm.optical_thickness); - if(res != RES_OK && args->rnatm.optical_thickness<=0) res = RES_BAD_ARG; - break; - case 't': - res = cstr_to_uint(optarg, &args->rnatm.nthreads); - if(res == RES_OK && !args->rnatm.nthreads) res = RES_BAD_ARG; - break; - case 'V': - res = cstr_to_uint(optarg, &args->rnatm.grid_definition_hint); - if(res == RES_OK && !args->rnatm.grid_definition_hint) res = RES_BAD_ARG; - break; - case 'v': args->rnatm.verbose = 1; break; - default: res = RES_BAD_ARG; break; - } - if(res != RES_OK) { - if(optarg) { - fprintf(stderr, "%s: invalid option args `%s' -- `%c'\n", - argv[0], optarg, opt); - } - goto error; - } - } - - if(storage_filename) { - const char* mode = args->rnatm.load_octrees_from_storage ? "r" : "w+"; - args->rnatm.octrees_storage = fopen(storage_filename, mode); - if(!args->rnatm.octrees_storage) { - fprintf(stderr, "Unable to open octree storage file %s\n", - storage_filename); - res = RES_IO_ERR; - goto error; - } - } - - /* Check the required options */ - if(!args->rnatm.gas.smsh_filename - || !args->rnatm.gas.sck_filename - || !args->rnatm.gas.temperatures_filename) { - fprintf(stderr, "Incomplete gas definition -- option `-g'\n"); - res = RES_BAD_ARG; - goto error; - } - - FOR_EACH(i, 0, args->rnatm.naerosols) { - struct rnatm_aerosol_args* aerosol = args->rnatm.aerosols + i; - - if(!aerosol->smsh_filename - || !aerosol->sars_filename - || !aerosol->phase_fn_ids_filename - || !aerosol->phase_fn_lst_filename) { - fprintf(stderr, "Incomplete %lu^th aerosol definition -- option `-a'\n", - (unsigned long)i); - res = RES_BAD_ARG; - goto error; - } - } - -exit: - return res; -error: - args_release(args); - goto exit; -} - -static res_T -write_vtk_octrees(struct rnatm* atm, const char* filename) -{ - size_t octrees_range[2]; - FILE* fp = NULL; - res_T res = RES_OK; - ASSERT(atm && filename); - - if(!strcmp(filename, "-")) { - fp = stdout; - } else { - fp = fopen(filename, "w"); - if(!fp) { - fprintf(stderr, "Could not open `%s' -- %s\n", filename, strerror(errno)); - res = RES_IO_ERR; - goto error; - } - } - - octrees_range[0] = 0; - octrees_range[1] = rnatm_get_spectral_items_count(atm) - 1; - - res = rnatm_write_vtk_octrees(atm, octrees_range, fp); - if(res != RES_OK) goto error; - -exit: - if(fp != stdout) CHK(fclose(fp) == 0); - return res; -error: - goto exit; -} - -/******************************************************************************* - * Main function - ******************************************************************************/ -int -main(int argc, char** argv) -{ - struct args args = ARGS_DEFAULT; - struct rnatm* atm = NULL; - res_T res = RES_OK; - int err = 0; - - res = args_init(&args, argc, argv); - if(res != RES_OK) goto error; - - res = rnatm_create(&args.rnatm, &atm); - if(res != RES_OK) goto error; - - if(args.check) { - res = rnatm_validate(atm); - if(res != RES_OK) goto error; - } - - if(args.vtk_filename) { - res = write_vtk_octrees(atm, args.vtk_filename); - if(res != RES_OK) goto error; - } - -exit: - args_release(&args); - if(atm) RNATM(ref_put(atm)); - if(mem_allocated_size() != 0) { - fprintf(stderr, "Memory leaks: %lu bytes\n", - (unsigned long)mem_allocated_size()); - err = -1; - } - return err; -error: - err = -1; - goto exit; -}