star-line

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

commit e88aeea9206c33e54cd184a8876046af19fd8ced
parent facc7d874dcb11552c33cc175ac7469b8ff2190b
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Wed, 23 Sep 2026 12:32:56 +0200

Merge branch 'release_0.1'

Diffstat:
M.gitignore | 1+
MMakefile | 24++++++++++++++++++------
MREADME.md | 13+++++++++++++
Mconfig.mk | 2+-
Mdoc/sln-build.1 | 6++++--
Mdoc/sln-get.1 | 6++++--
Mdoc/sln-mixture.5 | 2++
Mdoc/sln-slab.1 | 2++
Adoc/sln-stat.1 | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/sln.h | 17+++++++++++++++++
Msrc/sln_build.c | 2++
Msrc/sln_device.c | 2++
Msrc/sln_device_c.h | 2++
Msrc/sln_faddeeva.c | 2++
Msrc/sln_get.c | 4+++-
Msrc/sln_line.c | 47++++++++++++++++++++++++++++++++---------------
Msrc/sln_line.h | 5+++++
Msrc/sln_mixture.c | 2++
Msrc/sln_polyline.c | 2++
Msrc/sln_polyline.h | 2++
Msrc/sln_slab.c | 4+++-
Asrc/sln_stat.c | 488+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/sln_tree.c | 208++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/sln_tree_build.c | 7+++++--
Msrc/sln_tree_c.h | 2++
Msrc/test_sln_device.c | 2++
Msrc/test_sln_lines.h | 2++
Msrc/test_sln_mesh.c | 2++
Msrc/test_sln_mixture.c | 2++
Asrc/test_sln_thermo_props.c | 379+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_sln_tree.c | 68+++++++++++++-------------------------------------------------------
Asrc/test_sln_tree_sample.c | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
32 files changed, 1513 insertions(+), 125 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -10,6 +10,7 @@ mixture.txt sln-build sln-get sln-slab +sln-stat tags tags test_* diff --git a/Makefile b/Makefile @@ -3,6 +3,8 @@ # Copyright (C) 2022 Centre National de la Recherche Scientifique # Copyright (C) 2022 Université Paul Sabatier # +# This file is part of Star-Line. +# # 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 @@ -84,7 +86,7 @@ libsln.o: $(OBJ) ################################################################################ # Utils ################################################################################ -UTIL_SRC = src/sln_build.c src/sln_get.c src/sln_slab.c +UTIL_SRC = src/sln_build.c src/sln_get.c src/sln_slab.c src/sln_stat.c UTIL_OBJ = $(UTIL_SRC:.c=.o) UTIL_DEP = $(UTIL_SRC:.c=.d) @@ -103,14 +105,14 @@ LDFLAGS_SLAB = $(LDFLAGS_EXE) $(LIBS_SLAB) utils: library $(UTIL_DEP) .config @$(MAKE) -fMakefile \ $$(for i in $(UTIL_DEP); do printf -- '-f%s\n' "$${i}"; done) \ - sln-build sln-get sln-slab + sln-build sln-get sln-slab sln-stat $(UTIL_DEP) $(UTIL_OBJ): config.mk sln-local.pc - src/sln_build.d src/sln_build.o: src/sln_build.c src/sln_get.d src/sln_get.o: src/sln_get.c src/sln_slab.d src/sln_slab.o: src/sln_slab.c +src/sln_stat.d src/sln_stat.o: src/sln_stat.c sln-build: config.mk sln-local.pc src/sln_build.o $(LIBNAME) $(CC) $(CFLAGS_UTIL) -o $@ src/sln_build.o $(LDFLAGS_UTIL) @@ -121,20 +123,23 @@ sln-get: config.mk sln-local.pc src/sln_get.o $(LIBNAME) sln-slab: config.mk sln-local.pc src/sln_slab.o $(LIBNAME) $(CC) $(CFLAGS_SLAB) -o $@ src/sln_slab.o $(LDFLAGS_SLAB) +sln-stat: config.mk sln-local.pc src/sln_stat.o $(LIBNAME) + $(CC) $(CFLAGS_SLAB) -o $@ src/sln_stat.o $(LDFLAGS_SLAB) + src/sln_build.d src/sln_get.d: @$(CC) $(CFLAGS_UTIL) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ -src/sln_slab.d: +src/sln_slab.d src/sln_stat.d: @$(CC) $(CFLAGS_SLAB) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ src/sln_build.o src/sln_get.o: $(CC) $(CFLAGS_UTIL) -c $(@:.o=.c) -o $@ -src/sln_slab.o: +src/sln_slab.o src/sln_stat.o: $(CC) $(CFLAGS_SLAB) -c $(@:.o=.c) -o $@ clean_utils: - rm -f $(UTIL_OBJ) $(UTIL_DEP) sln-build sln-get sln-slab + rm -f $(UTIL_OBJ) $(UTIL_DEP) sln-build sln-get sln-slab sln-stat ################################################################################ # Installation @@ -172,11 +177,13 @@ install: library pkg utils install 755 "$(DESTDIR)$(BINPREFIX)" sln-build; \ install 755 "$(DESTDIR)$(BINPREFIX)" sln-get; \ install 755 "$(DESTDIR)$(BINPREFIX)" sln-slab; \ + install 755 "$(DESTDIR)$(BINPREFIX)" sln-stat; \ install 644 "$(DESTDIR)$(LIBPREFIX)/pkgconfig" sln.pc; \ install 644 "$(DESTDIR)$(INCPREFIX)/star" src/sln.h; \ install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/sln-build.1; \ install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/sln-get.1; \ install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/sln-slab.1; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/sln-stat.1; \ install 644 "$(DESTDIR)$(MANPREFIX)/man5" doc/sln-mixture.5; \ install 644 "$(DESTDIR)$(PREFIX)/share/doc/star-line" COPYING README.md @@ -185,12 +192,14 @@ uninstall: rm -f "$(DESTDIR)$(BINPREFIX)/sln-build" rm -f "$(DESTDIR)$(BINPREFIX)/sln-get" rm -f "$(DESTDIR)$(BINPREFIX)/sln-slab" + rm -f "$(DESTDIR)$(BINPREFIX)/sln-stat" rm -f "$(DESTDIR)$(LIBPREFIX)/pkgconfig/sln.pc" rm -f "$(DESTDIR)$(BINPREFIX)/sln" rm -f "$(DESTDIR)$(INCPREFIX)/star/sln.h" rm -f "$(DESTDIR)$(MANPREFIX)/man1/sln-build.1" rm -f "$(DESTDIR)$(MANPREFIX)/man1/sln-get.1" rm -f "$(DESTDIR)$(MANPREFIX)/man1/sln-slab.1" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/sln-stat.1" rm -f "$(DESTDIR)$(MANPREFIX)/man5/sln-mixture.5" rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-line/COPYING" rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-line/README.md" @@ -203,6 +212,7 @@ lint: mandoc -Tlint -Wwarning doc/sln-build.1 mandoc -Tlint -Wwarning doc/sln-get.1 mandoc -Tlint -Wwarning doc/sln-slab.1 + mandoc -Tlint -Wwarning doc/sln-stat.1 mandoc -Tlint doc/sln-mixture.5 ################################################################################ @@ -212,6 +222,7 @@ TEST_SRC =\ src/test_sln_device.c\ src/test_sln_mesh.c\ src/test_sln_mixture.c\ + src/test_sln_thermo_props.c\ src/test_sln_tree.c\ src/test_sln_tree_sample.c TEST_OBJ = $(TEST_SRC:.c=.o) @@ -246,6 +257,7 @@ $(TEST_OBJ): config.mk sln-local.pc test_sln_device \ test_sln_mesh \ test_sln_mixture \ +test_sln_thermo_props\ test_sln_tree_sample \ : config.mk sln-local.pc $(LIBNAME) $(CC) $(CFLAGS_TEST) -o $@ src/$@.o $(LDFLAGS_TEST) diff --git a/README.md b/README.md @@ -21,6 +21,19 @@ Edit config.mk as needed, then run: ## Release notes +### Version 0.1 + +- Allow the caller to modify the thermodynamic properties at which it + evaluates the node of a tree or the lines it partitions, i.e., to use + others than those used to build the tree. + The same acceleration structure can therefore be used to sample lines + with different thermodynamic properties *as long as the sampling + criteria are met*; + such as the polylines of nodes that must be an upper limit of the + lines they encompass. +- Provide the `sln-stat` utility, which uses line importance sampling to + estimate the absorption coefficient of a spectrum, and its square. + ### Version 0.0 - Initial version of the library that structures a set of lines into an diff --git a/config.mk b/config.mk @@ -1,4 +1,4 @@ -VERSION = 0.0 +VERSION = 0.1 PREFIX = /usr/local BINPREFIX = $(PREFIX)/bin diff --git a/doc/sln-build.1 b/doc/sln-build.1 @@ -3,6 +3,8 @@ .\" Copyright (C) 2022 Centre National de la Recherche Scientifique .\" Copyright (C) 2022 Université Paul Sabatier .\" +.\" This file is part of Star-Line. +.\" .\" 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 @@ -15,7 +17,7 @@ .\" .\" 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 April 30, 2026 +.Dd August 17, 2026 .Dt SLN-BUILD 1 .Os .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -204,7 +206,7 @@ global radiative cooling .Rs .%A L.S. Rothman et al. .%T HITEMP, the high-temperature molecular spectroscopic database -.%J Journal of Quantitative Spectroscopu & Radiative Transfer +.%J Journal of Quantitative Spectroscopy & Radiative Transfer .%V 111 .%P pp. 2139\(en2150 .%D 2010 diff --git a/doc/sln-get.1 b/doc/sln-get.1 @@ -3,6 +3,8 @@ .\" Copyright (C) 2022 Centre National de la Recherche Scientifique .\" Copyright (C) 2022 Université Paul Sabatier .\" +.\" This file is part of Star-Line. +.\" .\" 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 @@ -15,7 +17,7 @@ .\" .\" 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 April 10, 2026 +.Dd August 17, 2026 .Dt SLN-GET 1 .Os .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -229,7 +231,7 @@ sln-get -i lines.par -p molparams.txt -c0:3 -w 50 tree.sln .Rs .%A L.S. Rothman et al. .%T HITEMP, the high-temperature molecular spectroscopic database -.%J Journal of Quantitative Spectroscopu & Radiative Transfer +.%J Journal of Quantitative Spectroscopy & Radiative Transfer .%V 111 .%P pp. 2139\(en2150 .%D 2010 diff --git a/doc/sln-mixture.5 b/doc/sln-mixture.5 @@ -3,6 +3,8 @@ .\" Copyright (C) 2022 Centre National de la Recherche Scientifique .\" Copyright (C) 2022 Université Paul Sabatier .\" +.\" This file is part of Star-Line. +.\" .\" 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 diff --git a/doc/sln-slab.1 b/doc/sln-slab.1 @@ -3,6 +3,8 @@ .\" Copyright (C) 2022 Centre National de la Recherche Scientifique .\" Copyright (C) 2022 Université Paul Sabatier .\" +.\" This file is part of Star-Line. +.\" .\" 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 diff --git a/doc/sln-stat.1 b/doc/sln-stat.1 @@ -0,0 +1,170 @@ +.\" Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com) +.\" Copyright (C) 2026 Université de Lorraine +.\" Copyright (C) 2022 Centre National de la Recherche Scientifique +.\" Copyright (C) 2022 Université Paul Sabatier +.\" +.\" This file is part of Star-Line. +.\" +.\" 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 May 5, 2026 +.Dt SLN-STAT 1 +.Os +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh NAME +.Nm sln-stat +.Nd computations of basic statistics over k spectrum +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SYNOPSIS +.Nm +.Op Fl dhsv +.Op Fl n Ar nrealisations +.Op Fl t Ar threads +.Fl S Ar nu_min , Ns Ar nu_max +.Fl a Ar accel_struct +.Fl m Ar molparams +.Fl l Ar lines +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh DESCRIPTION +.Nm +calculates the mean and mean of squares of k absorption values using a Monte +Carlo algorithm that samples the spectral lines that make up the gas mixture. +These computations are accelerated by sampling the lines based on the magnitude +of their contribution to the mixture’s spectrum, so that few Monte Carlo runs +are required to estimate the spectrum statistics with a high degree of +confidence. +The core of the proposal rests on this sampling strategy, made possible +by constructing an acceleration structure from the set of lines in the +mixture. +A structure built using the +.Xr sln-build 1 +utility and provided as input to the program. +.Pp +The output of +.Nm +displays the estimated mean and mean of squares, their standard +deviation, and the number of Monte Carlo realisations rejected due to +issues encountered during the computation, such as numerical +uncertainty. +Each estimate is displayed on a line formatted as follows: +.Bd -literal -offset Ds +"%-16s: %e +/- %e; %lu\en", name, estimate, std_err, rejects_count +.Ed +.Pp +The options are as follows: +.Bl -tag -width Ds +.\"""""""""""""""""""""""""""""""""" +.It Fl a Ar accel_struct +An acceleration structure corresponding to the input +.Ar lines , +used to accelerate their sampling based on their importance. +This structure is generated by the +.Xr sln-build 1 +tool. +.\"""""""""""""""""""""""""""""""""" +.It Fl d +Disables verification of the correspondence between the lines provided +by the +.Fl l +option and those used to construct +the acceleration structure defined by the +.Fl a +option. +.Pp +Warning! +It is always recommended to verify that the data is correct, even though +this verification can take a significant amount of time when there are a +large number of lines. +Anyway, a user who is +.Em certain +of the data’s consistency may nevertheless use this option +.Pq at their own risk +to disable this verification and thus speed up the execution. +.\"""""""""""""""""""""""""""""""""" +.It Fl h +Display short help and exit. +.It Fl l Ar lines +List of lines from which the tree was built. +This list is in binary format as generated by the +.Xr shtr 1 +binary, or in plain text HITRAN format, depending on whether the +.Fl s +option is set or not, respectively. +.\"""""""""""""""""""""""""""""""""" +.It Fl m Ar molparams +Isotopologue metadata in HITRAN format. +.\"""""""""""""""""""""""""""""""""" +.It Fl n Ar nrealisations +Number of Monte Carlo realisations. +By default the number of realisations is 10000. +.\"""""""""""""""""""""""""""""""""" +.It Fl S Ar nu_min , Ns Ar nu_max +The spectral range, in cm^-1, over which the computations are performed. +The default spectral range is from 0 to infinity. +.\"""""""""""""""""""""""""""""""""" +.It Fl s +Specifies that input lines are formatted according to the binary format +as written by the +.Xr shtr 1 +utility, and not according to the HITRAN format. +This format is more compact, allowing for faster loading of line data. +.\"""""""""""""""""""""""""""""""""" +.It Fl t Ar threads +Advice on the number of threads to use. +By default, +.Nm +uses as many threads as processor cores. +.\"""""""""""""""""""""""""""""""""" +.It Fl v +Make +.Nm +verbose. +Multiple +.Fl v +options increase the verbosity. +The maximum is 3. +.El +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXIT STATUS +.Ex -std +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXAMPLES +Estimate the mean k and mean square k between 100 and 2500 cm^-1 +for a gaz mixture made of H2O, CO2 and CO molecules. +The thermodynamic properties of the mixture, such as its pressure, +temperature and molecular concentrations, correspond to those used to +construct the acceleration structures with sln-build, provided as input +arguments +.Pq option Fl a . +The isotopic metadata +.Pq option Fl m +and the list of lines +.Pq option Fl l +partitioned by the acceleration structure, complete the list of input +data. +The latter is encoded in the format generated by the +.Xr shtr 1 +tool +.Pq option Fl s . +The isotopes are in HITRAN format. +Finally, make the program as verbose as possible +.Pq options Fl vvv . +.Bd -literal -offset Ds +sln-stat -S 100,2500 -a tree_H2O_CO2_CO_1atm_600K.sln \e + -m molparam.txt -sl H2O_CO2_CO_100-2500cm-1.shtr -vvv +.Ed +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SEE ALSO +.Xr shtr 1 , +.Xr sln-build 1 , +.Xr sln-slab 1 diff --git a/src/sln.h b/src/sln.h @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -210,6 +212,15 @@ struct sln_tree_desc { } static const struct sln_tree_desc SLN_TREE_DESC_NULL = SLN_TREE_DESC_NULL__; +struct sln_thermo_props { + double concentrations[SHTR_MAX_MOLECULE_COUNT]; + double pressure; /* [atm] */ + double temperature; /* [K] */ +}; +#define SLN_THERMO_PROPS_NULL__ {{0},0,0} +static const struct sln_thermo_props SLN_THERMO_PROPS_NULL = + SLN_THERMO_PROPS_NULL__; + struct sln_node_desc { /* Range of lines belonging to the node. The endpoints are included */ size_t ilines[2]; @@ -351,6 +362,9 @@ SLN_API res_T sln_tree_get_line (const struct sln_tree* tree, const size_t iline, + /* Thermodynamic properties to which the line is recovered. + * Can be NULL, so these properties are those used to build the tree */ + const struct sln_thermo_props* props, struct sln_line* line); SLN_API res_T @@ -381,6 +395,9 @@ SLN_API double sln_node_eval (const struct sln_tree* tree, const struct sln_node* node, + /* Thermodynamic properties to which the node lines are evaluated. + * Can be NULL, so these properties are those used to build the tree */ + const struct sln_thermo_props* props, const double wavenumber); /* In cm^-1 */ SLN_API res_T diff --git a/src/sln_build.c b/src/sln_build.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_device.c b/src/sln_device.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_device_c.h b/src/sln_device_c.h @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_faddeeva.c b/src/sln_faddeeva.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_get.c b/src/sln_get.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -427,7 +429,7 @@ print_node_value(const struct cmd* cmd) if(res != RES_OK) goto error; val_mesh = sln_mesh_eval(&mesh, cmd->args.wavenumber); - val_node = sln_node_eval(cmd->tree, node, cmd->args.wavenumber); + val_node = sln_node_eval(cmd->tree, node, NULL, cmd->args.wavenumber); printf("ka(%e) = %e ~ %e\n", cmd->args.wavenumber, val_node, val_mesh); diff --git a/src/sln_line.c b/src/sln_line.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -70,6 +72,9 @@ static res_T line_profile_factor (const struct sln_tree* tree, const struct shtr_line* shtr_line, + const double concentration, + const double pressure, + const double temperature, double* out_profile_factor) { /* Star-HITRAN data */ @@ -103,19 +108,19 @@ line_profile_factor ASSERT(molecule.nisotopes > (size_t)shtr_line->isotope_id_local); isotope = molecule.isotopes + shtr_line->isotope_id_local; - nu_c = line_center(shtr_line, tree->args.pressure); + nu_c = line_center(shtr_line, pressure); /* Compute the intensity */ - Ps = tree->args.pressure * mol_params->concentration; + Ps = pressure * concentration; density = (AVOGADRO_NUMBER * Ps); - density = density / (PERFECT_GAZ_CONSTANT * tree->args.temperature); + density = density / (PERFECT_GAZ_CONSTANT * temperature); density = density * 1e-6; /* Convert in molec.cm^-3 */ - /* Compute the partition function. TODO precompute it for molid/isoid */ + /* Compute the partition function */ Q_Tref = isotope->Q296K; molid = shtr_line->molecule_id; isoid = shtr_line->isotope_id_local+1/*Local indices start at 1 in BD_TIPS*/; - T = tree->args.temperature; + T = temperature; BD_TIPS_2017(&molid, &T, &isoid, &gj, &Q_T); if(Q_T <= 0) { ERROR(tree->sln, @@ -135,7 +140,7 @@ line_profile_factor intensity_ref = shtr_line->intensity/isotope->abundance*iso_abundance; } intensity = line_intensity(intensity_ref, shtr_line->lower_state_energy, Q, - tree->args.temperature, T_REF, nu_c); + temperature, T_REF, nu_c); profile_factor = 1.e2 * density * intensity; /* In m^-1.cm^-1 */ @@ -427,12 +432,15 @@ res_T line_setup (const struct sln_tree* tree, const size_t iline, + const struct sln_thermo_props* props, struct sln_line* line) { struct shtr_molecule molecule = SHTR_MOLECULE_NULL; struct shtr_line shtr_line = SHTR_LINE_NULL; - double molar_mass = 0; /* In kg.mol^-1 */ - const struct sln_molecule* mol_params = NULL; + double concentration = 0; + double molar_mass = 0; /*[kg.mol^-1]*/ + double pressure = 0; /*[atm]*/ + double temperature = 0; /*[K]*/ res_T res = RES_OK; ASSERT(tree && line); @@ -443,21 +451,30 @@ line_setup ASSERT(!SHTR_MOLECULE_IS_NULL(&molecule)); ASSERT(molecule.nisotopes > (size_t)shtr_line.isotope_id_local); - mol_params = tree->args.molecules + shtr_line.molecule_id; + if(!props) { /* Use thermo properties used to build the tree */ + concentration = tree->args.molecules[shtr_line.molecule_id].concentration; + pressure = tree->args.pressure; /*[atm]*/ + temperature = tree->args.temperature; /*[K]*/ + } else { + concentration = props->concentrations[shtr_line.molecule_id]; + pressure = props->pressure; /*[atm]*/ + temperature = props->temperature; /*[K]*/ + } /* Convert the molar mass of the line from g.mol^-1 to kg.mol^-1 */ molar_mass = molecule.isotopes[shtr_line.isotope_id_local].molar_mass*1e-3; /* Setup the line */ - res = line_profile_factor(tree, &shtr_line, &line->profile_factor); + res = line_profile_factor(tree, &shtr_line, concentration, pressure, + temperature, &line->profile_factor); if(res != RES_OK) goto error; - line->wavenumber = line_center(&shtr_line, tree->args.pressure); + line->wavenumber = line_center(&shtr_line, pressure); line->gamma_d = sln_compute_line_half_width_doppler - (line->wavenumber, molar_mass, tree->args.temperature); + (line->wavenumber, molar_mass, temperature); line->gamma_l = sln_compute_line_half_width_lorentz - (shtr_line.gamma_air, shtr_line.gamma_self, tree->args.temperature, - tree->args.pressure, shtr_line.n_air, mol_params->concentration); + (shtr_line.gamma_air, shtr_line.gamma_self, temperature, + pressure, shtr_line.n_air, concentration); line->molecule_id = shtr_line.molecule_id; exit: @@ -493,7 +510,7 @@ line_mesh /* Setup the line wrt molecule concentration, isotope abundance, temperature * and pressure */ - res = line_setup(tree, iline, &line); + res = line_setup(tree, iline, NULL/*default thermo props*/, &line); if(res != RES_OK) goto error; /* Adjust the hint on the number of vertices. This is not actually the real diff --git a/src/sln_line.h b/src/sln_line.h @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -43,6 +45,9 @@ extern LOCAL_SYM res_T line_setup (const struct sln_tree* tree, const size_t iline, + /* Thermodynamic properties to which the line is recovered. + * Can be NULL, so these properties are those used to build the tree */ + const struct sln_thermo_props* props, struct sln_line* line); extern LOCAL_SYM res_T diff --git a/src/sln_mixture.c b/src/sln_mixture.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_polyline.c b/src/sln_polyline.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_polyline.h b/src/sln_polyline.h @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/sln_slab.c b/src/sln_slab.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -411,7 +413,7 @@ realisation /* Evaluate the value of the line and compute the probability of being * absorbed by it */ - leaf_ka = sln_node_eval(cmd->tree, leaf, nu); + leaf_ka = sln_node_eval(cmd->tree, leaf, NULL, nu); proba_abs = leaf_ka / (leaf_proba*ka_max); if((res = check_proba(cmd, proba_abs)) != RES_OK) goto error; diff --git a/src/sln_stat.c b/src/sln_stat.c @@ -0,0 +1,488 @@ +/* Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2026 Université de Lorraine + * Copyright (C) 2022 Centre National de la Recherche Scientifique + * Copyright (C) 2022 Université Paul Sabatier + * + * This file is part of Star-Line. + * + * 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 200112L /* getopt */ + +#include "sln.h" + +#include <star/shtr.h> +#include <star/sbb.h> +#include <star/ssp.h> + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> +#include <rsys/str.h> + +#include <omp.h> + +#include <unistd.h> /* getopt */ + +enum estimate { + MEAN, + SQMEAN, + ESTIMATE_COUNT__ +}; + +#define WAVENUMBER_TO_WAVELENGTH(Nu/* [cm^-1] */) (1.e-2/(Nu))/*[m]*/ + +struct args { + const char* tree; /* Acceleration structure */ + const char* molparams; + const char* lines; + + double spectral_range[2]; /* [cm^-1]^2 */ + + unsigned long nrealisations; /* Number of Monte Carlo realisations */ + + /* Miscellaneous */ + unsigned nthreads_hint; /* Hint on the number of threads to use */ + int disable_line_hash_check; + int lines_in_shtr_format; + int verbose; + int quit; +}; +#define ARGS_DEFAULT__ {NULL,NULL,NULL,{0,DBL_MAX},10000,UINT_MAX,0,0,0,0} +static const struct args ARGS_DEFAULT = ARGS_DEFAULT__; + +struct cmd { + struct args args; + + struct sln_tree* tree; + unsigned nthreads; +}; +#define CMD_NULL__ {0} +static const struct cmd CMD_NULL = CMD_NULL__; + +struct accum { + double sum; + double sum2; + size_t count; +}; +#define ACCUM_NULL__ {0} + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +usage(FILE* stream) +{ + fprintf(stream, +"usage: sln-stat [-dhsv] [-n nrealisations] [-t threads]\n" +" -S nu_min,nu_max -a accel_struct -m molparams -l lines\n"); +} + +static res_T +parse_spectral_range(const char* str, double spectral_range[2]) +{ + size_t len = 0; + res_T res = RES_OK; + ASSERT(str && spectral_range); + + res = cstr_to_list_double(str, ',', spectral_range, &len, 2); + if(res == RES_OK && len < 2) res = RES_BAD_ARG; + + return res; +} + +static res_T +args_init(struct args* args, int argc, char** argv) +{ + int opt = 0; + res_T res = RES_OK; + + ASSERT(args); + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "a:dhl:m:n:S:st:v")) != -1) { + switch(opt) { + case 'a': args->tree = optarg; break; + case 'd': args->disable_line_hash_check = 1; break; + case 'h': + usage(stdout); + args->quit = 1; + goto exit; + case 'l': args->lines = optarg; break; + case 'm': args->molparams = optarg; break; + case 'n': res = cstr_to_ulong(optarg, &args->nrealisations); break; + case 'S': res = parse_spectral_range(optarg, args->spectral_range); break; + case 's': args->lines_in_shtr_format = 1; break; + case 't': + res = cstr_to_uint(optarg, &args->nthreads_hint); + if(res == RES_OK && args->nthreads_hint == 0) res = RES_BAD_ARG; + break; + case 'v': args->verbose += (args->verbose < 3); break; + default: res = RES_BAD_ARG; break; + } + if(res != RES_OK) { + if(optarg) { + fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n", + argv[0], optarg, opt); + } + goto error; + } + } + + #define MANDATORY(Cond, Name, Opt) { \ + if(!(Cond)) { \ + fprintf(stderr, "%s: %s missing -- option '-%c'\n", argv[0], (Name), (Opt)); \ + res = RES_BAD_ARG; \ + goto error; \ + } \ + } (void)0 + MANDATORY(args->molparams, "molparams", 'm'); + MANDATORY(args->lines, "line list", 'l'); + MANDATORY(args->tree, "acceleration structure", 'a'); + #undef MANDATORY + +exit: + return res; +error: + usage(stderr); + goto exit; +} + +static res_T +load_lines + (struct shtr* shtr, + const struct args* args, + struct shtr_line_list** out_lines) +{ + struct shtr_line_list* lines = NULL; + res_T res = RES_OK; + ASSERT(shtr && args && out_lines); + + if(args->lines_in_shtr_format) { + struct shtr_line_list_read_args read_args = SHTR_LINE_LIST_READ_ARGS_NULL; + + /* Loads lines from data serialized by the Star-HITRAN library */ + read_args.filename = args->lines; + res = shtr_line_list_read(shtr, &read_args, &lines); + if(res != RES_OK) goto error; + + } else { + struct shtr_line_list_load_args load_args = SHTR_LINE_LIST_LOAD_ARGS_NULL; + + /* Loads lines from a file in HITRAN format */ + load_args.filename = args->lines; + res = shtr_line_list_load(shtr, &load_args, &lines); + if(res != RES_OK) goto error; + } + +exit: + *out_lines = lines; + return res; +error: + if(lines) { SHTR(line_list_ref_put(lines)); lines = NULL; } + goto exit; +} + +static void +delete_per_thread_rngs(const struct cmd* cmd, struct ssp_rng* rngs[]) +{ + unsigned i = 0; + ASSERT(cmd && rngs); + + FOR_EACH(i, 0, cmd->nthreads) { + if(rngs[i]) SSP(rng_ref_put(rngs[i])); + } + mem_rm(rngs); +} + +static res_T +create_per_thread_rngs(const struct cmd* cmd, struct ssp_rng** out_rngs[]) +{ + struct ssp_rng_proxy* proxy = NULL; + struct ssp_rng** rngs = NULL; + size_t i = 0; + res_T res = RES_OK; + ASSERT(cmd); + + rngs = mem_calloc(cmd->nthreads, sizeof(*rngs)); + if(!rngs) { res = RES_MEM_ERR; goto error; } + + res = ssp_rng_proxy_create(NULL, SSP_RNG_THREEFRY, cmd->nthreads, &proxy); + if(res != RES_OK) goto error; + + FOR_EACH(i, 0, cmd->nthreads) { + res = ssp_rng_proxy_create_rng(proxy, i, &rngs[i]); + if(res != RES_OK) goto error; + } + +exit: + *out_rngs = rngs; + if(proxy) SSP(rng_proxy_ref_put(proxy)); + return res; +error: + if(cmd->args.verbose >= 1) { + fprintf(stderr, + "Error creating the list of per thread RNG -- %s\n", + res_to_cstr(res)); + } + if(rngs) delete_per_thread_rngs(cmd, rngs); + rngs = NULL; + goto exit; +} + +static void +cmd_release(struct cmd* cmd) +{ + ASSERT(cmd); + if(cmd->tree) SLN(tree_ref_put(cmd->tree)); +} + +static res_T +cmd_init(struct cmd* cmd, const struct args* args) +{ + /* Star Line */ + struct sln_device_create_args sln_args = SLN_DEVICE_CREATE_ARGS_DEFAULT; + struct sln_tree_read_args tree_args = SLN_TREE_READ_ARGS_NULL; + struct sln_device* sln = NULL; + + /* Star HITRAN */ + struct shtr_create_args shtr_args = SHTR_CREATE_ARGS_DEFAULT; + struct shtr* shtr = NULL; + struct shtr_isotope_metadata* molparams = NULL; + struct shtr_line_list* lines = NULL; + + /* Miscellaneous */ + unsigned nthreads_max = 0; + res_T res = RES_OK; + + ASSERT(cmd && args); + + *cmd = CMD_NULL; + + shtr_args.verbose = args->verbose; + res = shtr_create(&shtr_args, &shtr); + if(res != RES_OK) goto error; + + res = shtr_isotope_metadata_load(shtr, args->molparams, &molparams); + if(res != RES_OK) goto error; + + res = load_lines(shtr, args, &lines); + if(res != RES_OK) goto error; + + sln_args.verbose = args->verbose; + res = sln_device_create(&sln_args, &sln); + if(res != RES_OK) goto error; + + tree_args.metadata = molparams; + tree_args.lines = lines; + tree_args.filename = args->tree; + tree_args.disable_line_hash_check = args->disable_line_hash_check; + res = sln_tree_read(sln, &tree_args, &cmd->tree); + if(res != RES_OK) goto error; + + nthreads_max = (unsigned)MMAX(omp_get_max_threads(), omp_get_num_procs()); + cmd->args = *args; + cmd->nthreads = MMIN(cmd->args.nthreads_hint, nthreads_max); + +exit: + if(sln) SLN(device_ref_put(sln)); + if(shtr) SHTR(ref_put(shtr)); + if(molparams) SHTR(isotope_metadata_ref_put(molparams)); + if(lines) SHTR(line_list_ref_put(lines)); + return res; +error: + cmd_release(cmd); + *cmd = CMD_NULL; + goto exit; +} + +static INLINE const char* +estimate_cstr(const enum estimate estimate) +{ + const char* cstr = NULL; + switch(estimate) { + case MEAN: cstr="mean"; break; + case SQMEAN: cstr="mean_of_squares"; break; + default: FATAL("Unreachable code\n"); break; + } + return cstr; +} + +static res_T +realisation + (const struct cmd* cmd, + struct ssp_rng* rng, + double out_weights[ESTIMATE_COUNT__]) +{ + /* Acceleration structure */ + struct sln_tree_desc tree_desc = SLN_TREE_DESC_NULL; + const struct sln_node* root = NULL; + + /* Variables to sample k */ + const struct sln_node* leaf1 = NULL; + const struct sln_node* leaf2 = NULL; + double leaf_proba1 = 0; /* Probability of sampling a line */ + double leaf_proba2 = 0; /* Probability of sampling a line */ + double leaf_ka1 = 0; /* Value of a line */ + double leaf_ka2 = 0; /* Value of a line */ + + /* Miscellaneous */ + double w[ESTIMATE_COUNT__] = {0, 0}; /* Monte Carlo weight */ + double nu = 0; /* Sampled wavenumber [cm^-1] */ + int i = 0; + res_T res = RES_OK; + + ASSERT(cmd && rng && out_weights); /* Check pre-conditions */ + + /* Uniformly sample the spectral dimension */ + nu = ssp_rng_uniform_double + (rng, cmd->args.spectral_range[0], cmd->args.spectral_range[1]); + + SLN(tree_get_desc(cmd->tree, &tree_desc)); + + /* Store the root node of the tree */ + root = sln_tree_get_root(cmd->tree); + + /* Importance sampling of a line and evaluation of the line contribution */ + leaf1 = sln_node_sample_leaf(cmd->tree, root, nu, rng, &leaf_proba1); + if(!leaf1) { res = RES_BAD_ARG; goto error; } + leaf_ka1 = sln_node_eval(cmd->tree, leaf1, NULL, nu); + + /* Importance sampling of a line and evaluation of the line contribution */ + leaf2 = sln_node_sample_leaf(cmd->tree, root, nu, rng, &leaf_proba2); + if(!leaf2) { res = RES_BAD_ARG; goto error; } + leaf_ka2 = sln_node_eval(cmd->tree, leaf2, NULL, nu); + + w[MEAN] = leaf_ka1 / leaf_proba1; + w[SQMEAN] = (leaf_ka1/leaf_proba1)*(leaf_ka2/leaf_proba2); + +exit: + FOR_EACH(i, 0, ESTIMATE_COUNT__) out_weights[i] = w[i]; + return res; +error: + FOR_EACH(i, 0, ESTIMATE_COUNT__) w[i] = NaN; + goto exit; +} + +static res_T +cmd_run(const struct cmd* cmd) +{ + /* Random Number Generator */ + struct ssp_rng** rngs = NULL; + + /* Monte Carlo */ + struct accum accum[ESTIMATE_COUNT__] = {0}; + int64_t i = 0; /* Index of the realisation */ + size_t nrejects = 0; /* Number of rejected realisations */ + + /* Progress */ + size_t nrealisations = 0; + size_t realisation_done = 0; + int progress = 0; + int progress_pcent = 10; + + res_T res = RES_OK; + ASSERT(cmd); + + res = create_per_thread_rngs(cmd, &rngs); + if(res != RES_OK) goto error; + + #define PROGRESS_MSG "Solving: %3d%%\n" + if(cmd->args.verbose >= 3) fprintf(stderr, PROGRESS_MSG, progress); + + nrealisations = cmd->args.nrealisations; + + omp_set_num_threads((int)cmd->nthreads); + + #pragma omp parallel for schedule(static) + for(i = 0; i < (int64_t)nrealisations; ++i) { + double w[ESTIMATE_COUNT__] = {0}; /* Monte Carlo weights */ + const int ithread = omp_get_thread_num(); + int pcent = 0; + res_T res_realisation = RES_OK; + + res_realisation = realisation(cmd, rngs[ithread], w); + + #pragma omp critical + { + /* Update the Monte Carlo accumulator */ + if(res_realisation == RES_OK) { + int iestim = 0; + FOR_EACH(iestim, 0, ESTIMATE_COUNT__) { + accum[iestim].sum += w[iestim]; + accum[iestim].sum2 += w[iestim]*w[iestim]; + accum[iestim].count += 1; + } + } + + if(cmd->args.verbose >= 3) { + /* Update progress */ + realisation_done += 1; + pcent = (int)((double)realisation_done*100.0/(double)nrealisations+0.5); + if(pcent/progress_pcent > progress/progress_pcent) { + progress = pcent; + fprintf(stderr, PROGRESS_MSG, progress); + } + } + } + } + + #undef PROGRESS_MSG + + nrejects = nrealisations - accum[0].count; + + FOR_EACH(i, 0, ESTIMATE_COUNT__) { + const double E = accum[i].sum / (double)accum[i].count; + const double V = accum[i].sum2 / (double)accum[i].count - E*E; + const double SE = sqrt(V/(double)accum[i].count); + + /* Assume that the number of realisations is the same for all estimates */ + ASSERT(accum[i].count == accum[0].count); + + printf("%-16s: %e +/- %e; %lu\n", + estimate_cstr(i), E, SE, (unsigned long)nrejects); + } + +exit: + delete_per_thread_rngs(cmd, rngs); + return res; +error: + goto exit; +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct args args = ARGS_DEFAULT; + struct cmd cmd = CMD_NULL; + int err = 0; + res_T res = RES_OK; + + if((res = args_init(&args, argc, argv)) != RES_OK) goto error; + if(args.quit) goto exit; + + if((res = cmd_init(&cmd, &args)) != RES_OK) goto error; + if((res = cmd_run(&cmd)) != RES_OK) goto error; + +exit: + cmd_release(&cmd); + CHK(mem_allocated_size() == 0); + return err; +error: + err = 1; + goto exit; +} diff --git a/src/sln_tree.c b/src/sln_tree.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -40,20 +42,52 @@ static const struct stream STREAM_NULL = {NULL, NULL, 0}; /******************************************************************************* * Helper functions ******************************************************************************/ -/* Check that the sum of the molecular concentrations is equal to 1 */ -static res_T +static INLINE res_T check_molecule_concentration - (struct sln_device* sln, + (const struct sln_device* sln, const char* caller, - const struct sln_tree_create_args* args) + const enum shtr_molecule_id molecule_id, + const double concentration) +{ + ASSERT(sln && caller); + + if(concentration == 0) { + /* A molecular concentration of zero is allowed, but may be a user error, + * as 0 is the default concentration in the tree creation arguments. + * Therefore, warn the user about this value so that they can determine + * whether or not it is an error on their part. */ + WARN(sln, "%s: the concentration of %s is zero.\n", + caller, shtr_molecule_cstr(molecule_id)); + + } else if(concentration < 0) { + /* Concentration cannot be negative... */ + ERROR(sln, "%s: invalid %s concentration: %g.\n", + FUNC_NAME, shtr_molecule_cstr(molecule_id), + concentration); + return RES_BAD_ARG; + } + + return RES_OK; +} + +static res_T +check_concentrations_list + (const struct sln_device* sln, + const char* caller, + const double concentrations[SHTR_MAX_MOLECULE_COUNT]) { - int i = 0; double sum = 0; - ASSERT(sln && caller && args); + int i = 0; + res_T res = RES_OK; + ASSERT(sln && caller && concentrations); FOR_EACH(i, 0, SHTR_MAX_MOLECULE_COUNT) { if(i == SHTR_MOLECULE_ID_NULL) continue; - sum += args->molecules[i].concentration; + + res = check_molecule_concentration(sln, caller, i, concentrations[i]); + if(res != RES_OK) goto error; + + sum += concentrations[i]; } /* The sum of molecular concentrations must be less than or equal to 1. It may @@ -63,10 +97,32 @@ check_molecule_concentration ERROR(sln, "%s: the sum of molecule concentrations is greater than 1: %g\n", caller, sum); - return RES_BAD_ARG; + res = RES_BAD_ARG; + goto error; } - return RES_OK; +exit: + return res; +error: + goto exit; +} + +/* Check the consistency of the molecular concentrations */ +static INLINE res_T +check_mixture_concentrations + (struct sln_device* sln, + const char* caller, + const struct sln_tree_create_args* args) +{ + double concentrations[SHTR_MAX_MOLECULE_COUNT] = {0}; + int i = 0; + ASSERT(sln && caller && args); + + FOR_EACH(i, 0, SHTR_MAX_MOLECULE_COUNT) { + concentrations[i] = args->molecules[i].concentration; + } + + return check_concentrations_list(sln, caller, concentrations); } /* Verify that the isotope abundance are valids */ @@ -115,14 +171,13 @@ check_molecules { char molecule_ok[SHTR_MAX_MOLECULE_COUNT] = {0}; - double concentrations_sum = 0; size_t iline = 0; size_t nlines = 0; res_T res = RES_OK; ASSERT(args->lines); - res = check_molecule_concentration(sln, caller, args); - if(res != RES_OK) return res; + res = check_mixture_concentrations(sln, caller, args); + if(res != RES_OK) goto error; /* Iterate over the lines to define which molecules has to be checked, i.e., * the ones used in the mixture */ @@ -138,24 +193,6 @@ check_molecules molecule = args->molecules + line.molecule_id; - if(molecule->concentration == 0) { - /* A molecular concentration of zero is allowed, but may be a user error, - * as 0 is the default concentration in the tree creation arguments. - * Therefore, warn the user about this value so that they can determine - * whether or not it is an error on their part. */ - WARN(sln, "%s: the concentration of %s is zero.\n", - caller, shtr_molecule_cstr(line.molecule_id)); - - } else if(molecule->concentration < 0) { - /* Concentration cannot be negative... */ - ERROR(sln, "%s: invalid %s concentration: %g.\n", - FUNC_NAME, shtr_molecule_cstr(line.molecule_id), - molecule->concentration); - return RES_BAD_ARG; - } - - concentrations_sum += molecule->concentration; - if(molecule->cutoff <= 0) { /* ... cutoff either */ ERROR(sln, "%s: invalid %s cutoff: %g.\n", @@ -164,21 +201,40 @@ check_molecules } res = check_molecule_isotope_abundances(sln, caller, molecule); - if(res != RES_OK) return res; + if(res != RES_OK) goto error; molecule_ok[line.molecule_id] = 1; } - /* The sum of molecular concentrations must be less than or equal to 1. It may - * be less than 1 if the remaining part of the mixture is (implicitly) defined - * as a radiatively inactive gas */ - if(concentrations_sum > 1 && (concentrations_sum - 1) > 1e-6) { - ERROR(sln, - "%s: the sum of molecule concentrations is greater than 1: %g\n", - caller, concentrations_sum); +exit: + return res; +error: + goto exit; +} + +static INLINE res_T +check_pressure + (const struct sln_device* sln, + const char* caller, + const double pressure /*[atm]*/) +{ + if(pressure < 0) { + ERROR(sln, "%s: invalid negative pressure %g atm\n", caller, pressure); return RES_BAD_ARG; } + return RES_OK; +} +static INLINE res_T +check_temperature + (const struct sln_device* sln, + const char* caller, + const double temperature /*[K]*/) +{ + if(temperature < 0) { + ERROR(sln, "%s: invalid negative temperature %g K\n", caller, temperature); + return RES_BAD_ARG; + } return RES_OK; } @@ -203,6 +259,14 @@ check_sln_tree_create_args return RES_BAD_ARG; } + if((res = check_pressure(sln, caller, args->pressure)) != RES_OK) { + return res; + } + + if((res = check_temperature(sln, caller, args->temperature)) != RES_OK) { + return res; + } + if(args->nvertices_hint == 0) { ERROR(sln, "%s: invalid hint on the number of vertices around the line center %lu.\n", @@ -296,6 +360,64 @@ check_sln_tree_write_args return RES_OK; } + +static res_T +check_line_thermo_props + (const struct sln_tree* tree, + const char* caller, + const size_t iline, + const struct sln_thermo_props* props) +{ + struct shtr_line line = SHTR_LINE_NULL; + res_T res = RES_OK; + ASSERT(tree && caller); + + if(!props) goto exit; /* Default thermo props */ + + SHTR(line_list_at(tree->args.lines, iline, &line)); + + res = check_molecule_concentration(tree->sln, caller, line.molecule_id, + props->concentrations[line.molecule_id]); + if(res != RES_OK) goto error; + + res = check_pressure(tree->sln, caller, props->pressure); + if(res != RES_OK) goto error; + + res = check_temperature(tree->sln, caller, props->temperature); + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} + +static INLINE res_T +check_sln_thermo_props + (const struct sln_device* sln, + const char* caller, + const struct sln_thermo_props* props) +{ + res_T res = RES_OK; + ASSERT(sln && caller); + + if(!props) goto exit; /* Default thermo props */ + + res = check_concentrations_list(sln, caller, props->concentrations); + if(res != RES_OK) goto error; + + res = check_pressure(sln, caller, props->pressure); + if(res != RES_OK) goto error; + + res = check_temperature(sln, caller, props->temperature); + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} + static INLINE void stream_release(struct stream* stream) { @@ -703,6 +825,7 @@ res_T sln_tree_get_line (const struct sln_tree* tree, const size_t iline, + const struct sln_thermo_props* props, struct sln_line* line) { size_t nlines = 0; @@ -713,7 +836,10 @@ sln_tree_get_line SHTR(line_list_get_size(tree->args.lines, &nlines)); if(iline >= nlines) { res = RES_BAD_ARG; goto error; } - res = line_setup(tree, iline, line); + res = check_line_thermo_props(tree, FUNC_NAME, iline, props); + if(res != RES_OK) goto error; + + res = line_setup(tree, iline, props, line); if(res != RES_OK) { ERROR(tree->sln, "%s: could not setup the line %lu-- %s\n", FUNC_NAME, iline, res_to_cstr(res)); @@ -775,17 +901,19 @@ double sln_node_eval (const struct sln_tree* tree, const struct sln_node* node, + const struct sln_thermo_props* props, const double nu) { double ka = 0; size_t iline; ASSERT(tree && node); + ASSERT(check_sln_thermo_props(tree->sln, FUNC_NAME, props) == RES_OK); FOR_EACH(iline, node->range[0], node->range[1]+1) { struct sln_line line = SLN_LINE_NULL; res_T res = RES_OK; - res = line_setup(tree, iline, &line); + res = line_setup(tree, iline, props, &line); if(res != RES_OK) { WARN(tree->sln, "%s: could not setup the line %lu-- %s\n", FUNC_NAME, iline, res_to_cstr(res)); diff --git a/src/sln_tree_build.c b/src/sln_tree_build.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -270,8 +272,9 @@ build_leaf_polyline_from_Nlines const size_t iline = leaf->range[0] + i; /* Mesh the line in the temporary vertex buffer */ - res = line_mesh(tree, iline, tree->args.nvertices_hint, &scratch->vertices, - vertices_range); + res = line_mesh + (/* in */ tree, iline, tree->args.nvertices_hint, + /* out */ &scratch->vertices, vertices_range); if(res != RES_OK) goto error; /* Decimate the line mesh */ diff --git a/src/sln_tree_c.h b/src/sln_tree_c.h @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/test_sln_device.c b/src/test_sln_device.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/test_sln_lines.h b/src/test_sln_lines.h @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/test_sln_mesh.c b/src/test_sln_mesh.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/test_sln_mixture.c b/src/test_sln_mixture.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 diff --git a/src/test_sln_thermo_props.c b/src/test_sln_thermo_props.c @@ -0,0 +1,379 @@ +/* Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2026 Université de Lorraine + * Copyright (C) 2022 Centre National de la Recherche Scientifique + * Copyright (C) 2022 Université Paul Sabatier + * + * This file is part of Star-Line. + * + * 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/>. */ + +#include <test_sln_lines.h> + +#include "sln.h" + +#include <rsys/mem_allocator.h> +#include <rsys/rsys.h> + +struct thermo_props { + double xH2O; + double xCO2; + double xO3; + double pressure; /*[atm]*/ + double temperature; /*[K]*/ +}; + +static const struct thermo_props thermo_props1 = {0.15, 0.10, 0.05, 10, 600}; +static const struct thermo_props thermo_props2 = {0.10, 0.15, 0.25, 20, 450}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static struct shtr_isotope_metadata* +setup_isotopes(struct shtr* shtr) +{ + struct shtr_isotope_metadata* metadata = NULL; + FILE* fp = NULL; + + CHK(fp = tmpfile()); + fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n"); + write_shtr_molecule(fp, &g_H2O); + write_shtr_molecule(fp, &g_CO2); + write_shtr_molecule(fp, &g_O3); + rewind(fp); + + CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &metadata) == RES_OK); + + CHK(fclose(fp) == 0); + + return metadata; +} + +static struct shtr_line_list* +setup_lines(struct shtr* shtr) +{ + struct shtr_line_list_load_args args = SHTR_LINE_LIST_LOAD_ARGS_NULL; + struct shtr_line_list* lines = NULL; + FILE* fp = NULL; + + CHK(fp = tmpfile()); + write_shtr_lines(fp, g_lines, g_nlines); + rewind(fp); + + args.filename = "stream"; + args.file = fp; + CHK(shtr_line_list_load(shtr, &args, &lines) == RES_OK); + + CHK(fclose(fp) == 0); + + return lines; +} + +static struct sln_tree* +create_tree + (struct sln_device* sln, + struct shtr_isotope_metadata* mdata, + struct shtr_line_list* lines, + const struct thermo_props* props) +{ + struct sln_tree_create_args args = SLN_TREE_CREATE_ARGS_DEFAULT; + struct sln_tree* tree = NULL; + + args.metadata = mdata; + args.lines = lines; + + args.molecules[SHTR_H2O].concentration = props->xH2O; + args.molecules[SHTR_H2O].cutoff = 25; /* [cm^-1] */ + args.molecules[SHTR_CO2].concentration = props->xCO2; + args.molecules[SHTR_CO2].cutoff = 50; /* [cm^-1] */ + args.molecules[SHTR_O3].concentration = props->xO3; + args.molecules[SHTR_O3].cutoff = 25; /* [cm^-1] */ + + args.pressure = props->pressure; /*[atm]*/ + args.temperature = props->temperature; /*[K]*/ + + CHK(sln_tree_create(sln, &args, &tree) == RES_OK); + return tree; +} + +static INLINE double /* in [0,1[ */ +rand_canonic(void) +{ + return (double)rand() / (double)((long)RAND_MAX+1); +} + +static double /* [cm^-1] */ +line_sample_nu(const struct sln_tree* tree, const size_t iline) +{ + struct sln_line line = SLN_LINE_NULL; + double nu_range[2] = {0,0}; /* [cm^-1] */ + double nu = 0; /* [cm^-1] */ + + CHK(sln_tree_get_line(tree, iline, NULL, &line) == RES_OK); + nu_range[0] = line.wavenumber - 20; + nu_range[1] = line.wavenumber + 20; + nu = nu_range[0] + rand_canonic() * (nu_range[1] - nu_range[0]); + return nu; +} + +static double /* [cm^-1] */ +node_sample_nu(const struct sln_tree* tree, const struct sln_node* node) +{ + struct sln_line line = SLN_LINE_NULL; + struct sln_node_desc desc = SLN_NODE_DESC_NULL; + double nu_range[2] = {0,0}; /* [cm^-1] */ + double nu = 0; /* [cm^-1] */ + + CHK(sln_node_get_desc(tree, node, &desc) == RES_OK); + + CHK(sln_tree_get_line(tree, desc.ilines[0], NULL, &line) == RES_OK); + nu_range[0] = line.wavenumber - 10; + CHK(sln_tree_get_line(tree, desc.ilines[1], NULL, &line) == RES_OK); + nu_range[1] = line.wavenumber + 10; + + nu = nu_range[0] + rand_canonic() * (nu_range[1] - nu_range[0]); + return nu; +} + +static size_t +node_sample_line(const struct sln_tree* tree, const struct sln_node* node) +{ + struct sln_node_desc desc = SLN_NODE_DESC_NULL; + const double r = rand_canonic(); + size_t iline = 0; + + CHK(sln_node_get_desc(tree, node, &desc) == RES_OK); + iline = desc.ilines[0]; + iline += (size_t)(r * (double)(desc.ilines[1] - desc.ilines[0] + 1)); + return iline; +} + +/* Check that, even belonging to 2 trees built from different thermodynamic + * properties, a line has the _exact_ same value when queried with the same + * thermodynamic properties. */ +static void +cmp_lines_values + (const struct sln_tree* tree1, + const struct sln_tree* tree2, + const size_t iline, + const double nu/*[cm^-1]*/) +{ + struct sln_line line1 = SLN_LINE_NULL; + struct sln_line line2 = SLN_LINE_NULL; + struct sln_thermo_props props = SLN_THERMO_PROPS_NULL; + double ka1 = 0; + double ka2 = 0; + + CHK(sln_tree_get_line(tree1, iline, NULL, &line1) == RES_OK); + CHK(sln_tree_get_line(tree2, iline, NULL, &line2) == RES_OK); + ka1 = sln_line_eval(tree1, &line1, nu); + ka2 = sln_line_eval(tree2, &line2, nu); + CHK(ka1 != ka2); + + props.concentrations[SHTR_H2O] = thermo_props1.xH2O; + props.concentrations[SHTR_CO2] = thermo_props1.xCO2; + props.concentrations[SHTR_O3] = thermo_props1.xO3; + props.pressure = thermo_props1.pressure; + props.temperature = thermo_props1.temperature; + CHK(sln_tree_get_line(tree1, iline, NULL, &line1) == RES_OK); + CHK(sln_tree_get_line(tree2, iline, &props, &line2) == RES_OK); + ka1 = sln_line_eval(tree1, &line1, nu); + ka2 = sln_line_eval(tree2, &line2, nu); + CHK(ka1 == ka2); + + props.concentrations[SHTR_H2O] = thermo_props2.xH2O; + props.concentrations[SHTR_CO2] = thermo_props2.xCO2; + props.concentrations[SHTR_O3] = thermo_props2.xO3; + props.pressure = thermo_props2.pressure; + props.temperature = thermo_props2.temperature; + CHK(sln_tree_get_line(tree1, iline, &props, &line1) == RES_OK); + CHK(sln_tree_get_line(tree2, iline, NULL, &line2) == RES_OK); + ka1 = sln_line_eval(tree1, &line1, nu); + ka2 = sln_line_eval(tree2, &line2, nu); + CHK(ka1 == ka2); + + props.concentrations[SHTR_H2O] = thermo_props1.xH2O; + props.concentrations[SHTR_CO2] = thermo_props2.xCO2; + props.concentrations[SHTR_O3] = thermo_props1.xO3; + props.pressure = thermo_props2.pressure; + props.temperature = thermo_props1.temperature; + CHK(sln_tree_get_line(tree1, iline, &props, &line1) == RES_OK); + CHK(sln_tree_get_line(tree2, iline, &props, &line2) == RES_OK); + ka1 = sln_line_eval(tree1, &line1, nu); + ka2 = sln_line_eval(tree2, &line2, nu); + CHK(ka1 == ka2); +} + +static void +cmp_lines + (const struct sln_tree* tree1, + const struct sln_tree* tree2, + const size_t iline) +{ + const size_t N = 50; + size_t i = 0; + + FOR_EACH(i, 0, N) { + const double nu = line_sample_nu(tree1, iline); + cmp_lines_values(tree1, tree2, iline, nu); + } +} + +static void +cmp_nodes_lines + (const struct sln_tree* tree1, + const struct sln_node* node1, + const struct sln_tree* tree2, + const struct sln_node* node2) +{ + const size_t N = 50; + size_t i = 0; + (void)node2; + + FOR_EACH(i, 0, N) { + const size_t iline = node_sample_line(tree1, node1); + cmp_lines(tree1, tree2, iline); + } +} + +/* Check that, even belonging to 2 trees built from different thermodynamic + * properties, a node has the _exact_ same value when evaluated with the same + * thermodynamic properties. */ +static void +cmp_nodes_values + (const struct sln_tree* tree1, + const struct sln_node* node1, + const struct sln_tree* tree2, + const struct sln_node* node2, + const double nu/*[cm^-1]*/) +{ + struct sln_thermo_props props = SLN_THERMO_PROPS_NULL; + double ka1 = 0; + double ka2 = 0; + + ka1 = sln_node_eval(tree1, node1, NULL, nu); + ka2 = sln_node_eval(tree2, node2, NULL, nu); + CHK(ka1 != ka2); + + props.concentrations[SHTR_H2O] = thermo_props1.xH2O; + props.concentrations[SHTR_CO2] = thermo_props1.xCO2; + props.concentrations[SHTR_O3] = thermo_props1.xO3; + props.pressure = thermo_props1.pressure; + props.temperature = thermo_props1.temperature; + ka1 = sln_node_eval(tree1, node1, NULL, nu); + ka2 = sln_node_eval(tree2, node2, &props, nu); + CHK(ka1 == ka2); + + props.concentrations[SHTR_H2O] = thermo_props2.xH2O; + props.concentrations[SHTR_CO2] = thermo_props2.xCO2; + props.concentrations[SHTR_O3] = thermo_props2.xO3; + props.pressure = thermo_props2.pressure; + props.temperature = thermo_props2.temperature; + ka1 = sln_node_eval(tree1, node1, &props, nu); + ka2 = sln_node_eval(tree2, node2, NULL, nu); + CHK(ka1 == ka2); + + props.concentrations[SHTR_H2O] = thermo_props1.xH2O; + props.concentrations[SHTR_CO2] = thermo_props2.xCO2; + props.concentrations[SHTR_O3] = thermo_props1.xO3; + props.pressure = thermo_props2.pressure; + props.temperature = thermo_props1.temperature; + ka1 = sln_node_eval(tree1, node1, &props, nu); + ka2 = sln_node_eval(tree2, node2, &props, nu); + CHK(ka1 == ka2); +} + +static void +cmp_nodes + (const struct sln_tree* tree1, + const struct sln_node* node1, + const struct sln_tree* tree2, + const struct sln_node* node2) +{ + const size_t N = 50; + size_t i = 0; + + FOR_EACH(i, 0, N) { + const double nu = node_sample_nu(tree1, node1); + cmp_nodes_values(tree1, node1, tree2, node2, nu); + } + + cmp_nodes_lines(tree1, node1, tree2, node2); +} + +static void +cmp_trees(const struct sln_tree* tree1, const struct sln_tree* tree2) +{ + const struct sln_node* node1 = NULL; + const struct sln_node* node2 = NULL; + struct sln_tree_desc desc = SLN_TREE_DESC_NULL; + + node1 = sln_tree_get_root(tree1); + node2 = sln_tree_get_root(tree2); + + CHK(sln_tree_get_desc(tree1, &desc) == RES_OK); + CHK(desc.arity == 2); /* Assume that the arity of the tree is 2 */ + + for(;;) { + unsigned ichild = 0; + + cmp_nodes(tree1, node1, tree2, node2); + if(sln_node_is_leaf(node1)) break; + + /* Randomly choose one node child */ + ichild = rand_canonic() < 0.5 ? 0 : 1; + node1 = sln_node_get_child(tree1, node1, ichild); + node2 = sln_node_get_child(tree2, node2, ichild); + } +} + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(void) +{ + struct shtr_create_args shtr_args = SHTR_CREATE_ARGS_DEFAULT; + struct shtr* shtr = NULL; + struct shtr_isotope_metadata* mdata = NULL; + struct shtr_line_list* lines = NULL; + + struct sln_device_create_args sln_args = SLN_DEVICE_CREATE_ARGS_DEFAULT; + struct sln_device* sln = NULL; + struct sln_tree* tree1 = NULL; + struct sln_tree* tree2 = NULL; + + shtr_args.verbose = 1; + CHK(shtr_create(&shtr_args, &shtr) == RES_OK); + sln_args.verbose = 1; + CHK(sln_device_create(&sln_args, &sln) == RES_OK); + + mdata = setup_isotopes(shtr); + lines = setup_lines(shtr); + + tree1 = create_tree(sln, mdata, lines, &thermo_props1); + tree2 = create_tree(sln, mdata, lines, &thermo_props2); + + cmp_trees(tree1, tree2); + + CHK(shtr_ref_put(shtr) == RES_OK); + CHK(shtr_line_list_ref_put(lines) == RES_OK); + CHK(shtr_isotope_metadata_ref_put(mdata) == RES_OK); + + CHK(sln_device_ref_put(sln) == RES_OK); + CHK(sln_tree_ref_put(tree1) == RES_OK); + CHK(sln_tree_ref_put(tree2) == RES_OK); + + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_sln_tree.c b/src/test_sln_tree.c @@ -3,6 +3,8 @@ * Copyright (C) 2022 Centre National de la Recherche Scientifique * Copyright (C) 2022 Université Paul Sabatier * + * This file is part of Star-Line. + * * 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 @@ -28,50 +30,6 @@ /******************************************************************************* * Helper function ******************************************************************************/ -/* Return the index of the line in the line_list or SIZE_MAX if the line does - * not exist */ -static INLINE size_t -find_line - (struct shtr_line_list* line_list, - const struct shtr_line* line) -{ - struct shtr_line ln = SHTR_LINE_NULL; - size_t lo, hi, mid; - size_t iline; - size_t nlines; - - CHK(shtr_line_list_get_size(line_list, &nlines) == RES_OK); - - /* Dichotomic search */ - lo = 0; hi = nlines -1; - while(lo < hi) { - mid = (lo+hi)/2; - - CHK(shtr_line_list_at(line_list, mid, &ln) == RES_OK); - if(line->wavenumber > ln.wavenumber) { - lo = mid + 1; - } else { - hi = mid; - } - } - iline = lo; - - CHK(shtr_line_list_at(line_list, iline, &ln) == RES_OK); - if(ln.wavenumber != line->wavenumber) return SIZE_MAX; - - - /* Find a line with the same wavenumber as the one searched for and whose - * other member variables are also equal to those of the line searched for */ - while(ln.wavenumber == line->wavenumber - && !shtr_line_eq(&ln, line) - && iline < nlines) { - iline += 1; - CHK(shtr_line_list_at(line_list, iline, &ln) == RES_OK); - } - - return shtr_line_eq(&ln, line) ? iline : SIZE_MAX; -} - /* This test assumes that all the lines contained into the list are * partitionned in the tree */ static void @@ -179,8 +137,8 @@ check_node_equality FOR_EACH(iline, desc1.ilines[0], desc1.ilines[1]+1) { struct sln_line line1 = SLN_LINE_NULL; struct sln_line line2 = SLN_LINE_NULL; - CHK(sln_tree_get_line(tree1, iline, &line1) == RES_OK); - CHK(sln_tree_get_line(tree2, iline, &line2) == RES_OK); + CHK(sln_tree_get_line(tree1, iline, NULL, &line1) == RES_OK); + CHK(sln_tree_get_line(tree2, iline, NULL, &line2) == RES_OK); CHK(line1.wavenumber == line2.wavenumber); CHK(line1.profile_factor == line2.profile_factor); @@ -267,15 +225,15 @@ check_node_value(const struct sln_tree* tree, const struct sln_node* node) CHK(sln_node_get_desc(tree, node, &desc) == RES_OK); - CHK(sln_tree_get_line(tree, desc.ilines[0], &line) == RES_OK); + CHK(sln_tree_get_line(tree, desc.ilines[0], NULL, &line) == RES_OK); nu = line.wavenumber + 10; - CHK(sln_tree_get_line(tree, desc.ilines[1], &line) == RES_OK); + CHK(sln_tree_get_line(tree, desc.ilines[1], NULL, &line) == RES_OK); nu += line.wavenumber - 10; nu *= 0.5; - ka_node = sln_node_eval(tree, node, nu); + ka_node = sln_node_eval(tree, node, NULL, nu); FOR_EACH(iline, desc.ilines[0], desc.ilines[1]+1/*inclusive*/) { - CHK(sln_tree_get_line(tree, iline, &line) == RES_OK); + CHK(sln_tree_get_line(tree, iline, NULL, &line) == RES_OK); ka_ref += sln_line_eval(tree, &line, nu); } @@ -329,11 +287,11 @@ test_tree CHK(desc.temperature == tree_args.temperature); CHK(desc.arity == tree_args.arity); - CHK(sln_tree_get_line(NULL, 0, &line) == RES_BAD_ARG); - CHK(sln_tree_get_line(tree, nlines, &line) == RES_BAD_ARG); - CHK(sln_tree_get_line(tree, 0, NULL) == RES_BAD_ARG); - CHK(sln_tree_get_line(tree, 0, &line) == RES_OK); - CHK(sln_tree_get_line(tree, nlines-1, &line) == RES_OK); + CHK(sln_tree_get_line(NULL, 0, NULL, &line) == RES_BAD_ARG); + CHK(sln_tree_get_line(tree, nlines, NULL, &line) == RES_BAD_ARG); + CHK(sln_tree_get_line(tree, 0, NULL, NULL) == RES_BAD_ARG); + CHK(sln_tree_get_line(tree, 0, NULL, &line) == RES_OK); + CHK(sln_tree_get_line(tree, nlines-1, NULL, &line) == RES_OK); CHK(node = sln_tree_get_root(tree)); CHK(node != NULL); diff --git a/src/test_sln_tree_sample.c b/src/test_sln_tree_sample.c @@ -0,0 +1,161 @@ +/* Copyright (C) 2022, 2026 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2026 Université de Lorraine + * Copyright (C) 2022 Centre National de la Recherche Scientifique + * Copyright (C) 2022 Université Paul Sabatier + * + * This file is part of Star-Line. + * + * 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/>. */ + +#include "test_sln_lines.h" + +#include "sln.h" + +#include <rsys/mem_allocator.h> +#include <rsys/rsys.h> + +#include <star/ssp.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static struct shtr_isotope_metadata* +setup_isotopes + (struct shtr* shtr, + struct sln_molecule molecules[SHTR_MAX_MOLECULE_COUNT]) +{ + struct shtr_isotope_metadata* metadata = NULL; + FILE* fp = NULL; + + CHK(fp = tmpfile()); + fprintf(fp, "Molecule # Iso Abundance Q(296K) gj Molar Mass(g)\n"); + write_shtr_molecule(fp, &g_H2O); + write_shtr_molecule(fp, &g_CO2); + write_shtr_molecule(fp, &g_O3); + rewind(fp); + + CHK(molecules); + molecules[SHTR_H2O].concentration = 0.15; + molecules[SHTR_H2O].cutoff = 25; /* [cm^-1] */ + molecules[SHTR_CO2].concentration = 0.10; + molecules[SHTR_CO2].cutoff = 50; /* [cm^-1] */ + molecules[SHTR_O3].concentration = 0.05; + molecules[SHTR_O3].cutoff = 25; /* [cm^-1] */ + + CHK(shtr_isotope_metadata_load_stream(shtr, fp, NULL, &metadata) == RES_OK); + + CHK(fclose(fp) == 0); + + return metadata; +} + +static struct shtr_line_list* +setup_lines(struct shtr* shtr) +{ + struct shtr_line_list_load_args args = SHTR_LINE_LIST_LOAD_ARGS_NULL; + struct shtr_line_list* lines = NULL; + FILE* fp = NULL; + + CHK(fp = tmpfile()); + write_shtr_lines(fp, g_lines, g_nlines); + rewind(fp); + + args.filename = "stream"; + args.file = fp; + CHK(shtr_line_list_load(shtr, &args, &lines) == RES_OK); + + CHK(fclose(fp) == 0); + + return lines; +} + +static void +test_sample(struct sln_tree* tree) +{ + struct ssp_rng* rng = NULL; + const struct sln_node* node = NULL; + const struct sln_node* leaf = NULL; + double nu = 0; /* [cm^-2] */ + double proba = 0; + + CHK(ssp_rng_create(NULL, SSP_RNG_MT19937_64, &rng) == RES_OK); + + CHK(node = sln_tree_get_root(tree)); + + /* Set an arbitrary wave number within the range of the lines */ + nu = (g_lines[g_nlines-1].wavenumber + g_lines[0].wavenumber) / 3.0; + + CHK(sln_node_sample_leaf(NULL, node, nu, rng, NULL) == NULL); + CHK(sln_node_sample_leaf(tree, NULL, nu, rng, NULL) == NULL); + CHK(sln_node_sample_leaf(tree, node, nu, NULL, NULL) == NULL); + CHK(sln_node_sample_leaf(tree, node, nu, rng, NULL) != NULL); + + CHK(leaf = sln_node_sample_leaf(tree, node, nu, rng, &proba)); + CHK(proba > 0 && proba < 1); + + CHK(sln_node_sample_leaf(tree, leaf, nu, rng, &proba)); + CHK(proba == 1); + + /* Attempt to sample a line outside the spectral range. There are no lines + * with a non-zero value at the wavelength in question. In this case, the + * library assumes that no line can be sampled. + * + * To ensure that the nu value corresponds to a wave number whose value at the + * node is 0, take the last line and add 51 cm^-1 to it, which is 1 cm^-1 more + * than the maximum cutoff defined for the molecules in the mixture */ + nu = g_lines[g_nlines-1].wavenumber + 51 /* [cm^-1] */; + CHK(sln_node_sample_leaf(tree, node, nu, rng, &proba) == NULL); + CHK(sln_node_sample_leaf(tree, node, INF, rng, &proba) == NULL); + + CHK(ssp_rng_ref_put(rng) == RES_OK); +} + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(void) +{ + struct sln_device_create_args dev_args = SLN_DEVICE_CREATE_ARGS_DEFAULT; + struct sln_tree_create_args tree_args = SLN_TREE_CREATE_ARGS_DEFAULT; + struct sln_device* sln = NULL; + struct sln_tree* tree = NULL; + + struct shtr_create_args shtr_args = SHTR_CREATE_ARGS_DEFAULT; + struct shtr* shtr = NULL; + + shtr_args.verbose = 1; + CHK(shtr_create(&shtr_args, &shtr) == RES_OK); + + dev_args.verbose = 1; + CHK(sln_device_create(&dev_args, &sln) == RES_OK); + + tree_args.metadata = setup_isotopes(shtr, tree_args.molecules); + tree_args.lines = setup_lines(shtr); + tree_args.pressure = 10; /* [atm] */ + tree_args.temperature = 600; /* [K] */ + CHK(sln_tree_create(sln, &tree_args, &tree) == RES_OK); + + test_sample(tree); + + CHK(shtr_ref_put(shtr) == RES_OK); + CHK(shtr_line_list_ref_put(tree_args.lines) == RES_OK); + CHK(shtr_isotope_metadata_ref_put(tree_args.metadata) == RES_OK); + + CHK(sln_tree_ref_put(tree) == RES_OK); + CHK(sln_device_ref_put(sln) == RES_OK); + + CHK(mem_allocated_size() == 0); + return 0; +}