star-ck

Describe the radiative properties of gas mixtures
git clone git://git.meso-star.fr/star-ck.git
Log | Files | Refs | README | LICENSE

commit aa8047462282f848c0f09be920dfe12378540966
parent 4721e560111b01f1088c3874007eab36f7c3e8bf
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 30 Oct 2023 12:12:11 +0100

Merge branch 'release_0.1'

Diffstat:
M.gitignore | 14+++++++-------
AMakefile | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MREADME.md | 51+++++++++++++++++++++++++++++----------------------
Dcmake/CMakeLists.txt | 119-------------------------------------------------------------------------------
Aconfig.mk | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ddoc/sck.5.scd | 110-------------------------------------------------------------------------------
Amake.sh | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asck.5 | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asck.pc.in | 11+++++++++++
Msrc/sck.c | 571++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/sck.h | 41+++++++++++++++++++++++++++++++++++++----
Msrc/sck_c.h | 49++++++++++++++++++++++++++++++++-----------------
Msrc/test_sck_load.c | 110+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
13 files changed, 1077 insertions(+), 411 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,12 +1,12 @@ .gitignore -CMakeCache.txt -CMakeFiles -Makefile -tmp [Bb]uild* *.sw[po] -*.[ao] -*.orig +*.[aod] +*.so *~ +test* +!test*.[ch] +.config +.test tags - +*.pc diff --git a/Makefile b/Makefile @@ -0,0 +1,151 @@ +# Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) +# Copyright (C) 2020, 2021 CNRS +# +# 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/>. + +.POSIX: +.SUFFIXES: # Clean up default inference rules + +include config.mk + +LIBNAME_STATIC = libsck.a +LIBNAME_SHARED = libsck.so +LIBNAME = $(LIBNAME_$(LIB_TYPE)) + +################################################################################ +# Library building +################################################################################ +SRC = src/sck.c src/sck_log.c +OBJ = $(SRC:.c=.o) +DEP = $(SRC:.c=.d) + +build_library: .config $(DEP) + @$(MAKE) -fMakefile $$(for i in $(DEP); do echo -f $${i}; done) \ + $$(if [ -n "$(LIBNAME)" ]; then \ + echo "$(LIBNAME)"; \ + else \ + echo "$(LIBNAME_SHARED)"; \ + fi) + +$(DEP) $(OBJ): config.mk + +$(LIBNAME_SHARED): $(OBJ) + $(CC) $(CFLAGS_SO) $(RSYS_CFLAGS) -o $@ $(OBJ) $(LDFLAGS_SO) $(RSYS_LIBS) -lm + +$(LIBNAME_STATIC): libsck.o + $(AR) -rc $@ $? + $(RANLIB) $@ + +libsck.o: $(OBJ) + $(LD) -r $(OBJ) -o $@ + $(OBJCOPY) $(OCPFLAGS) $@ + +.config: config.mk + @if ! $(PKG_CONFIG) --atleast-version $(RSYS_VERSION) rsys; then \ + echo "rsys $(RSYS_VERSION) not found" >&2; exit 1; fi + @echo "config done" > $@ + +.SUFFIXES: .c .d .o +.c.d: + @$(CC) $(CFLAGS_SO) $(RSYS_CFLAGS) -MM -MT "$(@:.d=.o) $@" $< -MF $@ + +.c.o: + $(CC) $(CFLAGS_SO) $(RSYS_CFLAGS) -DSCK_SHARED_BUILD -c $< -o $@ + +################################################################################ +# Installation +################################################################################ +pkg: + sed -e 's#@PREFIX@#$(PREFIX)#g'\ + -e 's#@VERSION@#$(VERSION)#g'\ + -e 's#@RSYS_VERSION@#$(RSYS_VERSION)#g'\ + sck.pc.in > sck.pc + +sck-local.pc: sck.pc.in + sed -e '1d'\ + -e 's#^includedir=.*#includedir=./src/#'\ + -e 's#^libdir=.*#libdir=./#'\ + -e 's#@VERSION@#$(VERSION)#g'\ + -e 's#@RSYS_VERSION@#$(RSYS_VERSION)#g'\ + sck.pc.in > $@ + +install: build_library pkg + @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/lib" $(LIBNAME) + @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/lib/pkgconfig" sck.pc + @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/include/star" src/sck.h + @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/share/doc/star-ck" COPYING README.md + @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/share/man/man5" sck.5 + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)/lib/$(LIBNAME)" + rm -f "$(DESTDIR)$(PREFIX)/lib/pkgconfig/sck.pc" + rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-ck/COPYING" + rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-ck/README.md" + rm -f "$(DESTDIR)$(PREFIX)/include/star/sck.h" + rm -f "$(DESTDIR)$(PREFIX)/share/man/man5/sck.5" + +################################################################################ +# Miscellaneous targets +################################################################################ +all: build_library build_tests + +clean: clean_test + rm -f $(OBJ) $(TEST_OBJ) $(LIBNAME) + rm -f .config .test libsck.o sck.pc sck-local.pc + +distclean: clean + rm -f $(DEP) $(TEST_DEP) + +lint: + shellcheck -o all make.sh + mandoc -Tlint -Wall sck.5 || [ $$? -le 1 ] + +################################################################################ +# Tests +################################################################################ +TEST_SRC =\ + src/test_sck.c\ + src/test_sck_load.c +TEST_OBJ = $(TEST_SRC:.c=.o) +TEST_DEP = $(TEST_SRC:.c=.d) + +PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) +SCK_CFLAGS = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags sck-local.pc) +SCK_LIBS = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs sck-local.pc) + +build_tests: build_library $(TEST_DEP) .test + @$(MAKE) -fMakefile -f.test $$(for i in $(TEST_DEP); do echo -f"$${i}"; done) test_bin + +test: build_tests + @$(SHELL) make.sh run_test $(TEST_SRC) + +.test: Makefile + @$(SHELL) make.sh config_test $(TEST_SRC) > $@ + +clean_test: + $(SHELL) make.sh clean_test $(TEST_SRC) + rm -f test_file.sck + +$(TEST_DEP): config.mk sck-local.pc + @$(CC) $(CFLAGS_EXE) $(SCK_CFLAGS) $(RSYS_CFLAGS) \ + -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ + +$(TEST_OBJ): config.mk sck-local.pc + $(CC) $(CFLAGS_EXE) $(SCK_CFLAGS) $(RSYS_CFLAGS) -c $(@:.o=.c) -o $@ + +test_sck: config.mk sck-local.pc $(LIBNAME) + $(CC) $(CFLAGS_EXE) -o $@ src/$@.o $(LDFLAGS_EXE) $(SCK_LIBS) $(RSYS_LIBS) + +test_sck_load: config.mk sck-local.pc $(LIBNAME) + $(CC) $(CFLAGS_EXE) -o $@ src/$@.o $(LDFLAGS_EXE) $(SCK_LIBS) $(RSYS_LIBS) -lm diff --git a/README.md b/README.md @@ -1,32 +1,39 @@ # Star-CorrelatedK -This C library loads the radiative properties of a gas mixture saved wrt the -Star-CK file format. +This C library loads the radiative properties of a gas mixture saved in +sck file format. See `sck.5` for the format specification. -## How to build +## Requirements -This library is compatible with 64-bits POSIX systems. It relies the -[CMake](http://www.cmake.org) and the -[RCMake](https://gitlab.com/vaplv/rcmake/) packages to build. It also depends -on the [RSys](https://gitlab.com/vaplv/rsys/) library. It optionally depends on -[scdoc](https://sr.ht/~sircmpwn/scdoc/) which, if available, is used to -generate the man page of the Star-CK file format. +- C compiler +- POSIX make +- pkg-config +- [RSys](https://gitlab.com/vaplv/rsys) +- [mandoc](https://mandoc.bsd.lv) -First ensure that CMake is installed on your system. Then install the RCMake -package as well as the aforementioned prerequisites. Finally generate the -project from the `cmake/CMakeLists.txt` file by appending to the -`CMAKE_PREFIX_PATH` variable the install directories of its dependencies. The -resulting project can be edited, built, tested and installed as any CMake -project. Refer to the [CMake documentation](https://cmake.org/documentation) -for further informations on CMake. +## Installation -## Copyright notice +Edit config.mk as needed, then run: -Copyright (C) 2022, 2023 [|Méso|Star>](https://www.meso-star.com) (<contact@meso-star.com>). -Copyright (C) 2020, 2021 Centre National de la Recherche Scientifique (CNRS). + make clean install + +## Release notes + +### Version 0.1 + +- Write the man page directly in mdoc's roff macros, instead of using + the intermediate scdoc source. +- Replace CMake by Makefile as build system. +- Update compiler and linker flags to increase the security and + robustness of generated binaries. +- Provide a pkg-config file to link the library as an external + dependency. ## License -Star-CK is free software released under the GPL v3+ license: GNU GPL version 3 -or later. You are welcome to redistribute it under certain conditions; refer to -the COPYING file for details. +Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) +Copyright (C) 2020, 2021 Centre National de la Recherche Scientifique (CNRS) + +Star-CK is free software released under the GPL v3+ license: GNU GPL +version 3 or later. You are welcome to redistribute it under certain +conditions; refer to the COPYING file for details. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -1,119 +0,0 @@ -# Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) -# Copyright (C) 2020, 2021 CNRS -# -# 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/>. - -cmake_minimum_required(VERSION 3.1) -project(sck C) -enable_testing() - -set(SCK_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) -option(NO_TEST "Do not build tests" OFF) - -################################################################################ -# Check dependencies -################################################################################ -find_package(RCMake 0.4 REQUIRED) -find_package(RSys 0.13 REQUIRED) - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) -include(rcmake) -include(rcmake_runtime) - -include_directories(${RSys_INCLUDE_DIR}) - -################################################################################ -# Configure and define targets -################################################################################ -set(VERSION_MAJOR 0) -set(VERSION_MINOR 0) -set(VERSION_PATCH 0) -set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) - -set(SCK_FILES_SRC - sck.c - sck_log.c) -set(SCK_FILES_INC - sck_c.h - sck_log.h) -set(SCK_FILES_INC_API - sck.h) - -set(SCK_FILES_DOC COPYING README.md) - -# Prepend each file in the `SCK_FILES_<SRC|INC>' list by `SCK_SOURCE_DIR' -rcmake_prepend_path(SCK_FILES_SRC ${SCK_SOURCE_DIR}) -rcmake_prepend_path(SCK_FILES_INC ${SCK_SOURCE_DIR}) -rcmake_prepend_path(SCK_FILES_INC_API ${SCK_SOURCE_DIR}) -rcmake_prepend_path(SCK_FILES_DOC ${PROJECT_SOURCE_DIR}/../) - -add_library(sck SHARED ${SCK_FILES_SRC} ${SCK_FILES_INC} ${SCK_FILES_INC_API}) -target_link_libraries(sck RSys) - -if(CMAKE_COMPILER_IS_GNUCC) - target_link_libraries(sck m) -endif() - -set_target_properties(sck PROPERTIES - DEFINE_SYMBOL SCK_SHARED_BUILD - VERSION ${VERSION} - SOVERSION ${VERSION_MAJOR}) - -rcmake_setup_devel(sck StarCK ${VERSION} star/sck_version.h) - -################################################################################ -# Add tests -################################################################################ -if(NOT NO_TEST) - function(new_test _name) - add_executable(${_name} ${SCK_SOURCE_DIR}/${_name}.c) - target_link_libraries(${_name} sck RSys ${ARGN}) - add_test(${_name} ${_name}) - endfunction() - - new_test(test_sck) - new_test(test_sck_load) -endif() - -################################################################################ -# Man page -############################################################################### -find_program(SCDOC NAMES scdoc) -if(NOT SCDOC) - message(WARNING - "The `scdoc' program is missing. " - "The Star-CK man page cannot be generated.") -else() - set(_src ${PROJECT_SOURCE_DIR}/../doc/sck.5.scd) - add_custom_command( - OUTPUT sck.5 - COMMAND ${SCDOC} < ${_src} > sck.5 - DEPENDS ${_src} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Buid ROFF man page sck.5" - VERBATIM) - add_custom_target(man-roff ALL DEPENDS sck.5) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sck.5 DESTINATION share/man/man5) -endif() - -################################################################################ -# Define output & install directories -################################################################################ -install(TARGETS sck - ARCHIVE DESTINATION bin - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) -install(FILES ${SCK_FILES_INC_API} DESTINATION include/star) -install(FILES ${SCK_FILES_DOC} DESTINATION share/doc/star-ck) - diff --git a/config.mk b/config.mk @@ -0,0 +1,77 @@ +VERSION = 0.1.0 +PREFIX = /usr/local + +LIB_TYPE = SHARED +#LIB_TYPE = STATIC + +BUILD_TYPE = RELEASE +#BUILD_TYPE = DEBUG + +################################################################################ +# Tools +################################################################################ +AR = ar +CC = cc +LD = ld +OBJCOPY = objcopy +PKG_CONFIG = pkg-config +RANLIB = ranlib + +################################################################################ +# Dependencies +################################################################################ +PCFLAGS_SHARED = +PCFLAGS_STATIC = --static +PCFLAGS = $(PCFLAGS_$(LIB_TYPE)) + +RSYS_VERSION = 0.14 +RSYS_CFLAGS = $$($(PKG_CONFIG) $(PCFLAGS) --cflags rsys) +RSYS_LIBS = $$($(PKG_CONFIG) $(PCFLAGS) --libs rsys) + +################################################################################ +# Compilation options +################################################################################ +WFLAGS =\ + -Wall\ + -Wcast-align\ + -Wconversion\ + -Wextra\ + -Wmissing-declarations\ + -Wmissing-prototypes\ + -Wshadow + +CFLAGS_HARDENED =\ + -D_FORTIFY_SOURCES=2\ + -fcf-protection=full\ + -fstack-clash-protection\ + -fstack-protector-strong + +CFLAGS_COMMON =\ + -std=c89\ + -pedantic\ + -fvisibility=hidden\ + -fstrict-aliasing\ + $(CFLAGS_HARDENED)\ + $(WFLAGS) + +CFLAGS_DEBUG = -g $(CFLAGS_COMMON) +CFLAGS_RELEASE = -O2 -DNDEBUG $(CFLAGS_COMMON) +CFLAGS = $(CFLAGS_$(BUILD_TYPE)) + +CFLAGS_SO = $(CFLAGS) -fPIC +CFLAGS_EXE = $(CFLAGS) -fPIE + +################################################################################ +# Linker options +################################################################################ +LDFLAGS_HARDENED = -Wl,-z,relro,-z,now +LDFLAGS_DEBUG = $(LDFLAGS_HARDENED) +LDFLAGS_RELEASE = -s $(LDFLAGS_HARDENED) +LDFLAGS = $(LDFLAGS_$(BUILD_TYPE)) + +LDFLAGS_SO = $(LDFLAGS) -shared -Wl,--no-undefined +LDFLAGS_EXE = $(LDFLAGS) -pie + +OCPFLAGS_DEBUG = --localize-hidden +OCPFLAGS_RELEASE = --localize-hidden --strip-unneeded +OCPFLAGS = $(OCPFLAGS_$(BUILD_TYPE)) diff --git a/doc/sck.5.scd b/doc/sck.5.scd @@ -1,110 +0,0 @@ -sck(5) - -; Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) -; Copyright (C) 2020, 2021 CNRS -; -; 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/>. - -# NAME - -sck - Star Correlated K file format - -# DESCRIPTION - -*sck* is a binary file format for storing the correlated K of a gas mixture. -The volumetric mesh to which the CKs are attached is _not_ described there but -must be defined in a separated file via, for example, the *smsh*(5) file -format. - -An *sck* file begins by a header that describes the layout of the data. Next the -spectral bands on which the CKs were evaluated are listed. Finally, the entire -list of CKs are stored. - -A *sck* file begins with a header that describes the layout of the data. Then, -the spectral bands on which the CKs have been evaluated are listed. Finally, -the entire list of radiative coefficients is stored. - -The header consists of 3 64-bit integers. The first integer is a power of two -(usually 4096) that defines the _pagesize_ in bytes on which the radiative -coefficients are aligned. The remaining two integers store the number of bands -and the number of nodes, that is, the number of radiative coefficients per band -or per quadrature point, respectively, which is actually the number of nodes in -the volumetric mesh to which these coefficients are attached. - -After the header comes the list of spectral bands sorted in ascending order -relative to their interval. Each spectral band begins with 2 double-precision -floating-point numbers that represent the lower and upper limits of the band in -nanometers. Another 64-bit integer then gives the number of quadrature points in -the band. Finally, the weights of the quadrature points are stored in a list of -floating numbers with double precision. - -Fill bytes follow the list of spectral bands to ensure alignment of the first -radiative coefficients on _pagesize_. Then, for each band, the diffusion -coefficients are listed before the list of absorption coefficients for each -quadrature point. Each list of radiative coefficients is followed by a -_padding_, which is a list of bytes that provides memory alignment of the -following data to _pagesize_. Bands and quadrature points are sorted according -to the order in which they were previously declared. - -# BINARY FILE FORMAT - -Data are encoded with respect to the little endian bytes ordering, i.e. least -significant bytes are stored first. - -``` -<sck> ::= <pagesize> <#bands> <#nodes> - <bands> - <padding> - <rad-coefs> - -<pagesize> ::= UINT64 -<#bands> ::= UINT64 -<#nodes> ::= UINT64 -<padding> ::= [ BYTE ... ] #ensure alignment on <pagesize> - ---- - -<band-list> ::= <band> [ <band> ... ] -<band> ::= <band-low> <band-upp> <#quad-pts> <quad-pt-list> - -<band-id> ::= UINT64 -<band-low> ::= DOUBLE # In nm (inclusive) -<band-upp> ::= DOUBLE # In nm (exclusive) -<#quad-pts> ::= UINT64 - ---- - -<quad-pt-list> ::= <quad-weight> [ <quad-weight> ... ] -<quad-weight> ::= DOUBLE - ---- - -<rad-coefs> ::= <per-band-k> - [ <per-band-k> ... ] - -<per-band-k> ::= <ks-list> - <per-quad-pt-ka> - -<per-quad-pt-ka> ::= <ka-list> - [ <ka-list>... ] - -<ka-list> ::= <ka> [ <ka> ... ] <padding> -<ks-list> ::= <ks> [ <ks> ... ] <padding> -<ka> ::= FLOAT # in m^-1 -<ks> ::= FLOAT # in m^-1 -``` - -# SEE ALSO - -*mmap*(2), *smsh*(5) diff --git a/make.sh b/make.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +# Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) +# Copyright (C) 2020, 2021 CNRS +# +# 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/>. + +set -e + +config_test() +{ + for i in "$@"; do + test=$(basename "${i}" ".c") + test_list="${test_list} ${test}" + printf "%s: %s\n" "${test}" "src/${test}.o" + done + printf "test_bin: %s\n" "${test_list}" +} + +run_test() +{ + for i in "$@"; do + test=$(basename "${i}" ".c") + + printf "%s " "${test}" + if "./${test}" > /dev/null 2>&1; then + printf "\033[1;32mOK\033[m\n" + else + printf "\033[1;31mError\033[m\n" + fi + done 2> /dev/null +} + + +clean_test() +{ + for i in "$@"; do + rm -f "$(basename "${i}" ".c")" + done +} + +install() +{ + prefix=$1 + shift 1 + + mkdir -p "${prefix}" + + for i in "$@"; do + dst="${prefix}/${i##*/}" + + if cmp -s "${i}" "${dst}"; then + printf "Up to date %s\n" "${dst}" + else + printf "Installing %s\n" "${dst}" + cp "${i}" "${prefix}" + fi + done +} + +"$@" diff --git a/sck.5 b/sck.5 @@ -0,0 +1,112 @@ +.\" Copyright (C) 2022, 2023 |Méso|Star> (contact@meso-star.com) +.\" Copyright (C) 2020, 2021 CNRS +.\" +.\" 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 July 28, 2023 +.Dt SCK 5 +.Os +.Sh NAME +.Nm sck +.Nd Star Correlated K file format +.Sh DESCRIPTION +.Nm +is a binary file format for storing the correlated K of a gas mixture. +The volumetric mesh to which the CKs are attached is +.Em not +described there but must be defined in a separated file via, for example, the +.Xr smsh 5 +file format. +.Pp +A +.Nm +file begins with a header that describes the layout of the data. +Then, the spectral bands on which the CKs have been evaluated are listed. +Finally, the entire list of radiative coefficients is stored. +.Pp +The header consists of 3 64-bit integers. +The first integer is a power of two +.Pq usually 4096 +that defines the +.Va pagesize +in bytes on which the radiative coefficients are aligned. +The remaining two integers store the number of bands and the number of nodes, +that is, the number of radiative coefficients per band or per quadrature point, +respectively, which is actually the number of nodes in the volumetric mesh to +which these coefficients are attached. +.Pp +After the header comes the list of spectral bands sorted in ascending order +relative to their interval. +Each spectral band begins with 2 double-precision floating-point numbers that +represent the lower and upper limits of the band in nanometers. +Another 64-bit integer then gives the number of quadrature points in the band. +Finally, the weights of the quadrature points are stored in a list of floating +numbers with double precision. +.Pp +Fill bytes follow the list of spectral bands to ensure alignment of the first +radiative coefficients on +.Va pagesize . +Then, for each band, the diffusion coefficients are listed before the list of +absorption coefficients for each quadrature point. +Each list of radiative coefficients is followed by a +.Va padding , +which is a list of bytes that provides memory alignment of the +following data to +.Va pagesize . +Bands and quadrature points are sorted according to the order in which they +were previously declared. +.Pp +Data are encoded with respect to the little endian bytes ordering, i.e. least +significant bytes are stored first. +.Pp +The file format is as follows: +.Bl -column (per-quad-pt-ka) (::=) () +.It Ao Va sck Ac Ta ::= Ta Ao Va pagesize Ac Ao Va #bands Ac Ao Va #nodes Ac +.It Ta Ta Aq Va bands +.It Ta Ta Aq Va padding +.It Ta Ta Aq Va rad-coefs +.It \ Ta Ta +.It Ao Va pagesize Ac Ta ::= Ta Vt uint64_t +.It Ao Va #bands Ac Ta ::= Ta Vt uint64_t +.It Ao Va #nodes Ac Ta ::= Ta Vt uint64_t +.It Ao Va padding Ac Ta ::= Ta Op Va int8_t ... +# Ensure alignment on +.Va pagesize +.It \ Ta Ta +.It Ao Va bands Ac Ta ::= Ta Ao Va band Ac Va ... +.It Ao Va band Ac Ta ::= Ta +.Aq Va band-low +.Aq Va band-upp +.Aq Va #quad-pts +.Aq Va quad-pts +.It Ao Va band-low Ac Ta ::= Ta Vt double +# Inclusive bound in nm +.It Ao Va band-upp Ac Ta ::= Ta Vt double +# Exclusive bound in nm +.It \ Ta Ta +.It Ao Va #quad-pts Ac Ta ::= Ta Vt uint64_t +.It Ao Va quad-pts Ac Ta ::= Ta Ao Va quad-weight Ac Va ... +.It Ao Va quad-weight Ac Ta ::= Ta Vt double +.It \ Ta Ta +.It Ao Va rad-coefs Ac Ta ::= Ta Ao Va per-band-k Ac Va ... +.It Ao Va per-band-k Ac Ta ::= Ta Ao Va ks-list Ac Ao Va per-quad-pt-ka Ac +.It Ao Va per-quad-pt-ka Ac Ta ::= Ta Ao Va ka-list Ac Va ... +.It Ao Va ka-list Ac Ta ::= Ta Ao Va ka Ac Va ... Ao Va padding Ac +.It Ao Va ks-list Ac Ta ::= Ta Ao Va ks Ac Va ... Ao Va padding Ac +.It Ao Va ka Ac Ta ::= Ta Vt float +# In m^-1 +.It Ao Va ks Ac Ta ::= Ta Vt float +# In m^-1 +.El +.Sh SEE ALSO +.Xr smsh 5 diff --git a/sck.pc.in b/sck.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +includedir=${prefix}/include +libdir=${prefix}/lib + +Requires: rsys >= @RSYS_VERSION@ +Name: Star-CK +Description: Star Correlated K library +Version: @VERSION@ +Libs: -L${libdir} -lsck +Libs.private: -lm +CFlags: -I${includedir} diff --git a/src/sck.c b/src/sck.c @@ -23,16 +23,29 @@ #include "sck_log.h" #include <rsys/algorithm.h> +#include <rsys/cstr.h> #include <unistd.h> /* sysconf support */ #include <errno.h> #include <string.h> #include <sys/mman.h> /* mmap */ +#include <sys/stat.h> /* fstat */ /******************************************************************************* * Helper functions ******************************************************************************/ +static INLINE int +is_stdin(FILE* stream) +{ + struct stat stream_buf; + struct stat stdin_buf; + ASSERT(stream); + CHK(fstat(fileno(stream), &stream_buf) == 0); + CHK(fstat(STDIN_FILENO, &stdin_buf) == 0); + return stream_buf.st_dev == stdin_buf.st_dev; +} + static INLINE res_T check_sck_create_args(const struct sck_create_args* args) { @@ -40,6 +53,27 @@ check_sck_create_args(const struct sck_create_args* args) return args ? RES_OK : RES_BAD_ARG; } +static INLINE res_T +check_sck_load_args(const struct sck_load_args* args) +{ + if(!args || !args->path) return RES_BAD_ARG; + return RES_OK; +} + +static INLINE res_T +check_sck_load_stream_args + (struct sck* sck, + const struct sck_load_stream_args* args) +{ + if(!args || !args->stream || !args->name) return RES_BAD_ARG; + if(args->memory_mapping && is_stdin(args->stream)) { + log_err(sck, "%s: unable to use memory mapping on data loaded from stdin\n", + args->name); + return RES_BAD_ARG; + } + return RES_OK; +} + static void reset_sck(struct sck* sck) { @@ -47,6 +81,7 @@ reset_sck(struct sck* sck) sck->pagesize = 0; sck->nnodes = 0; darray_band_purge(&sck->bands); + str_clear(&sck->name); } static INLINE int @@ -62,17 +97,18 @@ read_quad_pt (struct sck* sck, struct quad_pt* quad_pt, FILE* stream, - const char* stream_name, size_t iband, size_t iquad_pt) { res_T res = RES_OK; - ASSERT(sck && quad_pt && stream_name); + ASSERT(sck && quad_pt); + + quad_pt->band = darray_band_data_get(&sck->bands) + iband; if(fread(&quad_pt->weight, sizeof(quad_pt->weight), 1, stream) != 1) { log_err(sck, "%s: band %lu: quadrature point %lu: could not read the weight.\n", - stream_name, iband, iquad_pt); + sck_get_name(sck), iband, iquad_pt); res = RES_IO_ERR; goto error; } @@ -80,7 +116,7 @@ read_quad_pt if(quad_pt->weight < 0) { log_err(sck, "%s: band %lu: quadrature point %lu: invalid weight %g.\n", - stream_name, iband, iquad_pt, quad_pt->weight); + sck_get_name(sck), iband, iquad_pt, quad_pt->weight); res = RES_BAD_ARG; goto error; @@ -98,23 +134,23 @@ static res_T read_band (struct sck* sck, struct band* band, - FILE* stream, - const char* stream_name) + FILE* stream) { double cumul; size_t iquad_pt; size_t iband; uint64_t nquad_pts; res_T res = RES_OK; - ASSERT(sck && band && stream_name); + ASSERT(sck && band); + band->sck = sck; iband = (size_t)(band - darray_band_cdata_get(&sck->bands)); /* Read band definition */ #define READ(Var, Name) { \ if(fread((Var), sizeof(*(Var)), 1, stream) != 1) { \ log_err(sck, "%s: band %lu: could not read the %s.\n", \ - stream_name, (unsigned long)iband, (Name)); \ + sck_get_name(sck), (unsigned long)iband, (Name)); \ res = RES_IO_ERR; \ goto error; \ } \ @@ -128,14 +164,14 @@ read_band if(band->low < 0 || band->low > band->upp) { log_err(sck, "%s: band %lu: invalid band range [%g, %g].\n", - stream_name, (unsigned long)iband, band->low, band->upp); + sck_get_name(sck), (unsigned long)iband, band->low, band->upp); res = RES_BAD_ARG; goto error; } if(nquad_pts == 0) { log_err(sck, "%s: band %lu: invalid number fo quadrature points (#points=%lu).\n", - stream_name, (unsigned long)iband, (unsigned long)nquad_pts); + sck_get_name(sck), (unsigned long)iband, (unsigned long)nquad_pts); res = RES_BAD_ARG; goto error; } @@ -146,7 +182,7 @@ read_band log_err(sck, "%s: band %lu: could not allocate the list of quadrature points " "(#points=%lu).\n", - stream_name, (unsigned long)iband, (unsigned long)nquad_pts); + sck_get_name(sck), (unsigned long)iband, (unsigned long)nquad_pts); goto error; } @@ -156,7 +192,7 @@ read_band log_err(sck, "%s: band %lu: could not allocate the cumulative of quadrature points " "(#points=%lu).\n", - stream_name, (unsigned long)iband, (unsigned long)nquad_pts); + sck_get_name(sck), (unsigned long)iband, (unsigned long)nquad_pts); goto error; } @@ -166,7 +202,7 @@ read_band struct quad_pt* quad_pt = darray_quad_pt_data_get(&band->quad_pts)+iquad_pt; /* Read current quadrature point */ - res = read_quad_pt(sck, quad_pt, stream, stream_name, iband, iquad_pt); + res = read_quad_pt(sck, quad_pt, stream, iband, iquad_pt); if(res != RES_OK) goto error; /* Compute the quadrature point cumulative */ @@ -178,7 +214,7 @@ read_band if(!eq_eps(cumul, 1.0, 1.e-6)) { log_warn(sck, "%s: band %lu: the weights of the quadrature points are not normalised.\n", - stream_name, (unsigned long)iband); + sck_get_name(sck), (unsigned long)iband); /* Renormalize the cumulative */ FOR_EACH(iquad_pt, 0, nquad_pts) { @@ -196,26 +232,281 @@ error: } static res_T -load_stream(struct sck* sck, FILE* stream, const char* stream_name) +map_data + (struct sck* sck, + const int fd, /* File descriptor */ + const size_t filesz, /* Overall filesize */ + const char* data_name, + const off_t offset, /* Offset of the data into file */ + const size_t map_len, + void** out_map) /* Lenght of the data to map */ { + void* map = NULL; + res_T res = RES_OK; + ASSERT(sck && filesz && data_name && map_len && out_map); + ASSERT(IS_ALIGNED((size_t)offset, (size_t)sck->pagesize)); + + if((size_t)offset + map_len > filesz) { + log_err(sck, "%s: the %s to map exceed the file size\n", + sck_get_name(sck), data_name); + res = RES_IO_ERR; + goto error; + } + + map = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE|MAP_POPULATE, fd, offset); + if(map == MAP_FAILED) { + log_err(sck, "%s: could not map the %s -- %s\n", + sck_get_name(sck), data_name, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + +exit: + *out_map = map; + return res; +error: + if(map == MAP_FAILED) map = NULL; + goto exit; +} + +static res_T +map_file(struct sck* sck, FILE* stream) +{ + size_t filesz; size_t map_len; size_t iband; + off_t offset; + res_T res = RES_OK; + ASSERT(sck && stream); + + /* Compute the length in bytes of the k to map for each band/quadrature point */ + map_len = ALIGN_SIZE(sck->nnodes * sizeof(float), sck->pagesize); + + /* Compute the offset toward the 1st list of radiative coefficients */ + offset = ftell(stream); + offset = (off_t)ALIGN_SIZE((uint64_t)offset, sck->pagesize); + + /* Retrieve the overall filesize */ + fseek(stream, 0, SEEK_END); + filesz = (size_t)ftell(stream); + + FOR_EACH(iband, 0, darray_band_size_get(&sck->bands)) { + struct band* band = NULL; + size_t iquad_pt; + + band = darray_band_data_get(&sck->bands) + iband; + band->map_len = map_len; + + /* Mapping per band scattering coefficients */ + res = map_data(sck, fileno(stream), filesz, "scattering coefficients", + offset, band->map_len, (void**)&band->ks_list); + if(res != RES_OK) { + log_err(sck, + "%s: data mapping error for band %lu\n", + sck_get_name(sck), (unsigned long)iband); + goto error; + } + + offset = (off_t)((size_t)offset + map_len); + + FOR_EACH(iquad_pt, 0, darray_quad_pt_size_get(&band->quad_pts)) { + struct quad_pt* quad_pt = NULL; + + /* Mapping absorption coefficients per band and quadrature point */ + quad_pt = darray_quad_pt_data_get(&band->quad_pts)+iquad_pt; + quad_pt->map_len = map_len; + + res = map_data(sck, fileno(stream), filesz, "correlated Ka", + offset, quad_pt->map_len, (void**)&quad_pt->ka_list); + if(res != RES_OK) { + log_err(sck, + "%s: data mapping error for band %lu quadrature point %lu\n", + sck_get_name(sck), (unsigned long)iband, (unsigned long)iquad_pt); + goto error; + } + + offset = (off_t)((size_t)offset + map_len); + } + } + +exit: + return res; +error: + goto exit; +} + +static res_T +read_padding(FILE* stream, const size_t padding) +{ + char chunk[1024]; + size_t remaining_nbytes = padding; + + while(remaining_nbytes) { + const size_t nbytes = MMIN(sizeof(chunk), remaining_nbytes); + if(fread(chunk, 1, nbytes, stream) != nbytes) return RES_IO_ERR; + remaining_nbytes -= nbytes; + } + return RES_OK; +} + +/* Return the size in bytes of the data layout and band descriptors */ +static size_t +compute_sizeof_header(struct sck* sck) +{ + size_t sizeof_header = 0; + size_t iband = 0; + size_t nbands = 0; + ASSERT(sck); + + sizeof_header = + sizeof(uint64_t) /* pagesize */ + + sizeof(uint64_t) /* #bands */ + + sizeof(uint64_t);/* #nodes */ + + nbands = darray_band_size_get(&sck->bands); + FOR_EACH(iband, 0, nbands) { + const struct band* band = darray_band_cdata_get(&sck->bands)+iband; + const size_t nquad_pts = darray_quad_pt_size_get(&band->quad_pts); + const size_t sizeof_band_desc = + sizeof(double) /* Lower bound */ + + sizeof(double) /* Upper bound */ + + sizeof(uint64_t) /* #quad_pts */ + + sizeof(double) * nquad_pts; /* Weights */ + + sizeof_header += sizeof_band_desc; + } + return sizeof_header; +} + +static res_T +load_data + (struct sck* sck, + FILE* stream, + const char* data_name, + float** out_data) +{ + float* data = NULL; + res_T res = RES_OK; + ASSERT(sck && stream && data_name && out_data); + + data = MEM_ALLOC(sck->allocator, sizeof(float)*sck->nnodes); + if(!data) { + res = RES_MEM_ERR; + log_err(sck, "%s: could not allocate the %s -- %s\n", + sck_get_name(sck), data_name, res_to_cstr(res)); + goto error; + } + + if(fread(data, sizeof(float), sck->nnodes, stream) != sck->nnodes) { + res = RES_IO_ERR; + log_err(sck, "%s: could not read the %s -- %s\n", + sck_get_name(sck), data_name, res_to_cstr(res)); + goto error; + } + +exit: + *out_data = data; + return res; +error: + if(data) { MEM_RM(sck->allocator, data); data = NULL; } + goto exit; +} + +static res_T +load_file(struct sck* sck, FILE* stream) +{ + size_t padding_bytes; + size_t sizeof_header; + size_t sizeof_k_list; + size_t iband; + size_t nbands; + res_T res = RES_OK; + + sizeof_header = compute_sizeof_header(sck); + sizeof_k_list = sizeof(float)*sck->nnodes; + + padding_bytes = ALIGN_SIZE(sizeof_header, sck->pagesize) - sizeof_header; + if((res = read_padding(stream, padding_bytes)) != RES_OK) goto error; + + /* Calculate the padding between the lists of radiative coefficients. Note + * that this padding is the same between each list */ + padding_bytes = ALIGN_SIZE(sizeof_k_list, sck->pagesize) - sizeof_k_list; + + nbands = darray_band_size_get(&sck->bands); + FOR_EACH(iband, 0, nbands) { + struct band* band = NULL; + size_t iquad_pt = 0; + + band = darray_band_data_get(&sck->bands) + iband; + ASSERT(!band->ks_list && band->sck == sck); + + /* Loading per band scattering coefficients */ + res = load_data(sck, stream, "scattering coefficients", &band->ks_list); + if(res != RES_OK) { + log_err(sck, + "%s: data loading error for band %lu\n", + sck_get_name(sck), (unsigned long)iband); + goto error; + } + + if((res = read_padding(stream, padding_bytes)) != RES_OK) goto error; + + FOR_EACH(iquad_pt, 0, darray_quad_pt_size_get(&band->quad_pts)) { + struct quad_pt* quad_pt = NULL; + + quad_pt = darray_quad_pt_data_get(&band->quad_pts) + iquad_pt; + + /* Loading absorption coefficients per band and quadrature point */ + res = load_data(sck, stream, "correlated Ka", &quad_pt->ka_list); + if(res != RES_OK) { + log_err(sck, + "%s: data loading error for band %lu quadrature point %lu\n", + sck_get_name(sck), (unsigned long)iband, (unsigned long)iquad_pt); + goto error; + } + + if((res = read_padding(stream, padding_bytes)) != RES_OK) goto error; + } + } + +exit: + return res; +error: + goto exit; +} + +static res_T +load_stream(struct sck* sck, const struct sck_load_stream_args* args) +{ + size_t iband; uint64_t nbands; - off_t offset = 0; res_T res = RES_OK; - ASSERT(sck && stream && stream_name); + ASSERT(sck && check_sck_load_stream_args(sck, args) == RES_OK); reset_sck(sck); + res = str_set(&sck->name, args->name); + if(res != RES_OK) { + log_err(sck, "%s: unable to duplicate path to loaded data or stream name\n", + args->name); + goto error; + } + /* Read file header */ + if(fread(&sck->pagesize, sizeof(sck->pagesize), 1, args->stream) != 1) { + if(ferror(args->stream)) { + log_err(sck, "%s: could not read the pagesize.\n", sck_get_name(sck)); + } + res = RES_IO_ERR; + goto error; + } #define READ(Var, Name) { \ - if(fread((Var), sizeof(*(Var)), 1, stream) != 1) { \ - log_err(sck, "%s: could not read the %s.\n", stream_name, (Name)); \ + if(fread((Var), sizeof(*(Var)), 1, args->stream) != 1) { \ + log_err(sck, "%s: could not read the %s.\n", sck_get_name(sck), (Name)); \ res = RES_IO_ERR; \ goto error; \ } \ } (void)0 - READ(&sck->pagesize, "page size"); READ(&nbands, "number of bands"); READ(&sck->nnodes, "number of nodes"); #undef READ @@ -225,21 +516,20 @@ load_stream(struct sck* sck, FILE* stream, const char* stream_name) log_err(sck, "%s: invalid page size %lu. The page size attribute must be aligned on " "the page size of the operating system (%lu).\n", - stream_name, - (unsigned long)sck->pagesize, - (unsigned long)sck->pagesize_os); + sck_get_name(sck), sck->pagesize, (unsigned long)sck->pagesize_os); res = RES_BAD_ARG; goto error; } + if(!nbands) { log_err(sck, "%s: invalid number of bands %lu.\n", - stream_name, (unsigned long)nbands); + sck_get_name(sck), (unsigned long)nbands); res = RES_BAD_ARG; goto error; } if(!sck->nnodes) { log_err(sck, "%s: invalid number of nodes %lu.\n", - stream_name, (unsigned long)sck->nnodes); + sck_get_name(sck), (unsigned long)sck->nnodes); res = RES_BAD_ARG; goto error; } @@ -248,20 +538,20 @@ load_stream(struct sck* sck, FILE* stream, const char* stream_name) res = darray_band_resize(&sck->bands, nbands); if(res != RES_OK) { log_err(sck, "%s: could not allocate the list of bands (#bands=%lu).\n", - stream_name, (unsigned long)nbands); + sck_get_name(sck), (unsigned long)nbands); goto error; } /* Read the band description */ FOR_EACH(iband, 0, nbands) { struct band* band = darray_band_data_get(&sck->bands) + iband; - res = read_band(sck, band, stream, stream_name); + res = read_band(sck, band, args->stream); if(res != RES_OK) goto error; if(iband > 0 && band[0].low < band[-1].upp) { log_err(sck, "%s: bands must be sorted in ascending order and must not " "overlap (band %lu in [%g, %g[ nm; band %lu in [%g, %g[ nm).\n", - stream_name, + sck_get_name(sck), (unsigned long)(iband-1), band[-1].low, band[-1].upp, (unsigned long)(iband), band[ 0].low, band[ 0].upp); res = RES_BAD_ARG; @@ -269,52 +559,12 @@ load_stream(struct sck* sck, FILE* stream, const char* stream_name) } } - /* Compute the length in bytes of the k to map for each band/quadrature point */ - map_len = ALIGN_SIZE(sck->nnodes * sizeof(float), sck->pagesize); - - /* Compute the offset toward the 1st list of radiative coefficients */ - offset = ftell(stream); - offset = (off_t)ALIGN_SIZE((uint64_t)offset, sck->pagesize); - - FOR_EACH(iband, 0, nbands) { - struct band* band = NULL; - size_t iquad_pt; - - /* Map the per band scattering coefficient */ - band = darray_band_data_get(&sck->bands) + iband; - band->map_len = map_len; - band->ks_list = mmap(NULL, band->map_len, PROT_READ, - MAP_PRIVATE|MAP_POPULATE, fileno(stream), offset); - if(band->ks_list == MAP_FAILED) { - log_err(sck, - "%s: band %lu: could not map the scattering coefficients -- %s.\n", - stream_name, (unsigned long)iband, strerror(errno)); - res = RES_IO_ERR; - goto error; - } - offset = (off_t)((size_t)offset + map_len); - - FOR_EACH(iquad_pt, 0, darray_quad_pt_size_get(&band->quad_pts)) { - struct quad_pt* quad_pt = NULL; - - /* Map the per band absorption coefficient */ - quad_pt = darray_quad_pt_data_get(&band->quad_pts)+iquad_pt; - quad_pt->map_len = map_len; - quad_pt->ka_list = mmap(NULL, quad_pt->map_len, PROT_READ, - MAP_PRIVATE|MAP_POPULATE, fileno(stream), offset); - if(quad_pt->ka_list == MAP_FAILED) { - log_err(sck, - "%s: band %lu: quadrature point %lu: coult not map the correlated K " - "-- %s.\n", - stream_name, - (unsigned long)iband, - (unsigned long)iquad_pt, - strerror(errno)); - res = RES_IO_ERR; - goto error; - } - offset = (off_t)((size_t)offset + map_len); - } + if(args->memory_mapping) { + res = map_file(sck, args->stream); + if(res != RES_OK) goto error; + } else { + res = load_file(sck, args->stream); + if(res != RES_OK) goto error; } exit: @@ -383,6 +633,7 @@ release_sck(ref_T* ref) sck = CONTAINER_OF(ref, struct sck, ref); if(sck->logger == &sck->logger__) logger_release(&sck->logger__); darray_band_release(&sck->bands); + str_release(&sck->name); MEM_RM(sck->allocator, sck); } @@ -421,6 +672,7 @@ sck_create sck->allocator = allocator; sck->verbose = args->verbose; sck->pagesize_os = (size_t)sysconf(_SC_PAGESIZE); + str_init(allocator, &sck->name); darray_band_init(allocator, &sck->bands); if(args->logger) { sck->logger = args->logger; @@ -456,24 +708,27 @@ sck_ref_put(struct sck* sck) } res_T -sck_load(struct sck* sck, const char* path) +sck_load(struct sck* sck, const struct sck_load_args* args) { + struct sck_load_stream_args stream_args = SCK_LOAD_STREAM_ARGS_NULL; FILE* file = NULL; res_T res = RES_OK; - if(!sck || !path) { - res = RES_BAD_ARG; - goto error; - } + if(!sck) { res = RES_BAD_ARG; goto error; } + res = check_sck_load_args(args); + if(res != RES_OK) goto error; - file = fopen(path, "r"); + file = fopen(args->path, "r"); if(!file) { - log_err(sck, "%s: error opening file `%s'.\n", FUNC_NAME, path); + log_err(sck, "%s: error opening file `%s'.\n", FUNC_NAME, args->path); res = RES_IO_ERR; goto error; } - res = load_stream(sck, file, path); + stream_args.stream = file; + stream_args.name = args->path; + stream_args.memory_mapping = args->memory_mapping; + res = load_stream(sck, &stream_args); if(res != RES_OK) goto error; exit: @@ -484,13 +739,78 @@ error: } res_T -sck_load_stream - (struct sck* sck, - FILE* stream, - const char* stream_name) +sck_load_stream(struct sck* sck, const struct sck_load_stream_args* args) +{ + res_T res = RES_OK; + if(!sck) return RES_BAD_ARG; + res = check_sck_load_stream_args(sck, args); + if(res != RES_OK) return res; + return load_stream(sck, args); +} + +res_T +sck_validate(const struct sck* sck) { - if(!sck || !stream) return RES_BAD_ARG; - return load_stream(sck, stream, stream_name ? stream_name : "<stream>"); + size_t iband; + if(!sck) return RES_BAD_ARG; + + FOR_EACH(iband, 0, sck_get_bands_count(sck)) { + struct sck_band band = SCK_BAND_NULL; + size_t inode; + size_t iquad_pt; + + SCK(get_band(sck, iband, &band)); + + /* Check band limits */ + if(band.lower != band.lower /* NaN? */ + || band.upper != band.upper) { /* NaN? */ + log_err(sck, "%s: invalid limits for band %lu: [%g, %g[\n", + sck_get_name(sck), (unsigned long)iband, band.lower, band.upper); + return RES_BAD_ARG; + } + + /* Check scattering coefficients */ + FOR_EACH(inode, 0, sck_get_nodes_count(sck)) { + if(band.ks_list[inode] != band.ks_list[inode] /* NaN? */ + || band.ks_list[inode] < 0) { + log_err(sck, + "%s: invalid scattering coefficient for band %lu at node %lu: %g\n", + sck_get_name(sck), (unsigned long)iband, (unsigned long) inode, + band.ks_list[inode]); + return RES_BAD_ARG; + } + } + + FOR_EACH(iquad_pt, 0, band.quad_pts_count) { + struct sck_quad_pt quad_pt = SCK_QUAD_PT_NULL; + + SCK(band_get_quad_pt(&band, iquad_pt, &quad_pt)); + + /* Check quadrature point weight */ + if(quad_pt.weight != quad_pt.weight /* NaN? */ + || quad_pt.weight <= 0) { + log_err(sck, + "%s: invalid weight for quadrature point %lu of band %lu: %g\n", + sck_get_name(sck), (unsigned long)iquad_pt, (unsigned long)iband, + quad_pt.weight); + return RES_BAD_ARG; + } + + /* Check absorption coefficient */ + FOR_EACH(inode, 0, sck_get_nodes_count(sck)) { + if(quad_pt.ka_list[inode] != quad_pt.ka_list[inode] /* NaN? */ + || quad_pt.ka_list[inode] < 0) { + log_err(sck, + "%s: invalid absorption coefficient for quadrature point %lu " + "of band %lu at node %lu: %g\n", + sck_get_name(sck), (unsigned long)iquad_pt, (unsigned long)iband, + (unsigned long)inode, quad_pt.ka_list[inode]); + return RES_BAD_ARG; + } + } + } + } + return RES_OK; } size_t @@ -770,6 +1090,13 @@ error: goto exit; } +const char* +sck_get_name(const struct sck* sck) +{ + ASSERT(sck); + return str_cget(&sck->name); +} + /******************************************************************************* * Local functions ******************************************************************************/ @@ -779,16 +1106,80 @@ band_release(struct band* band) ASSERT(band); darray_quad_pt_release(&band->quad_pts); darray_double_release(&band->quad_pts_cumul); - if(band->ks_list && band->ks_list != MAP_FAILED) { + + if(!band->ks_list) return; + + if(!band->map_len) { + MEM_RM(band->sck->allocator, band->ks_list); + } else if(band->ks_list != MAP_FAILED) { munmap(band->ks_list, band->map_len); } } +res_T +band_copy(struct band* dst, const struct band* src) +{ + res_T res = RES_OK; + ASSERT(dst && src); + + dst->sck = src->sck; + dst->low = src->low; + dst->upp = src->upp; + + dst->map_len = src->map_len; + dst->ks_list = NULL; + + if(src->map_len) { + /* The ks are mapped: copy the pointer */ + dst->ks_list = src->ks_list; + + } else if(src->ks_list != NULL) { + /* The ks are loaded: duplicate table contents */ + const size_t memsz = sizeof(*dst->ks_list)*src->sck->nnodes; + dst->ks_list = MEM_ALLOC(dst->sck->allocator, memsz); + if(!dst->ks_list) return RES_MEM_ERR; + memcpy(dst->ks_list, src->ks_list, memsz); + } + + res = darray_quad_pt_copy(&dst->quad_pts, &src->quad_pts); + if(res != RES_OK) return res; + res = darray_double_copy(&dst->quad_pts_cumul, &src->quad_pts_cumul); + if(res != RES_OK) return res; + return RES_OK; +} + void quad_pt_release(struct quad_pt* quad_pt) { ASSERT(quad_pt); - if(quad_pt->ka_list && quad_pt->ka_list != MAP_FAILED) { + if(!quad_pt->ka_list) return; + + if(!quad_pt->map_len) { + MEM_RM(quad_pt->band->sck->allocator, quad_pt->ka_list); + } else if(quad_pt->ka_list != MAP_FAILED) { munmap(quad_pt->ka_list, quad_pt->map_len); } } + +res_T +quad_pt_copy(struct quad_pt* dst, const struct quad_pt* src) +{ + ASSERT(dst && src); + + dst->band = src->band; + dst->map_len = src->map_len; + dst->weight = src->weight; + dst->ka_list = NULL; + if(src->map_len) { + /* The ka are mapped: copy the pointer */ + dst->ka_list = src->ka_list; + + } else if(src->ka_list != NULL) { + /* The ka are loaded: duplicate table contents */ + const size_t memsz = sizeof(*dst->ka_list)*src->band->sck->nnodes; + dst->ka_list = MEM_ALLOC(dst->band->sck->allocator, memsz); + if(!dst->ka_list) return RES_MEM_ERR; + memcpy(dst->ka_list, src->ka_list, memsz); + } + return RES_OK; +} diff --git a/src/sck.h b/src/sck.h @@ -51,6 +51,24 @@ struct sck_create_args { static const struct sck_create_args SCK_CREATE_ARGS_DEFAULT = SCK_CREATE_ARGS_DEFAULT__; +struct sck_load_args { + const char* path; + int memory_mapping; /* Use memory mapping instead of normal loading */ +}; +#define SCK_LOAD_ARGS_NULL__ {NULL, 0} +static const struct sck_load_args SCK_LOAD_ARGS_NULL = SCK_LOAD_ARGS_NULL__; + +struct sck_load_stream_args { + FILE* stream; + const char* name; /* Stream name */ + /* Use memory mapping instead of normal loading. Note that memory mapping + * cannot be used on some stream like stdin */ + int memory_mapping; +}; +#define SCK_LOAD_STREAM_ARGS_NULL__ {NULL, "stream", 0} +static const struct sck_load_stream_args SCK_LOAD_STREAM_ARGS_NULL = + SCK_LOAD_STREAM_ARGS_NULL__; + struct sck_band { double lower; /* Lower band wavelength in nm (inclusive) */ double upper; /* Upper band wavelength in nm (exclusive) */ @@ -98,13 +116,23 @@ sck_ref_put SCK_API res_T sck_load (struct sck* sck, - const char* path); + const struct sck_load_args* args); SCK_API res_T sck_load_stream (struct sck* sck, - FILE* stream, - const char* stream_name); /* Can be NULL */ + const struct sck_load_stream_args* args); + +/* Validates radiative coefficients. Data checks have already been carried out + * during loading, notably on spectral bands and quadrature points, but this + * function performs longer and more thorough tests. It reviews all scattering + * and absorption coefficients to check their validity, i.e. whether they are + * positive or zero. Note that checking radiative coefficients is not mandatory, + * in order to speed up the loading step and avoid loading/unloading them when + * using memory mapping. */ +SCK_API res_T +sck_validate + (const struct sck* sck); SCK_API size_t sck_get_bands_count @@ -158,7 +186,12 @@ sck_compute_hash (const struct sck* sck, hash256_T hash); +/* Returns the path of the file or the name of the stream from which the data + * was loaded */ +SCK_API const char* +sck_get_name + (const struct sck* sck); + END_DECLS #endif /* SCK_H */ - diff --git a/src/sck_c.h b/src/sck_c.h @@ -20,10 +20,12 @@ #include <rsys/dynamic_array_double.h> #include <rsys/logger.h> #include <rsys/ref_count.h> +#include <rsys/str.h> struct mem_allocator; struct quad_pt { + struct band* band; /* Band to which the quadrature point belongs */ float* ka_list; /* Per node ka */ size_t map_len; double weight; @@ -34,6 +36,7 @@ quad_pt_init(struct mem_allocator* allocator, struct quad_pt* quad) { ASSERT(quad); (void)allocator; + quad->band = NULL; quad->ka_list = NULL; quad->map_len = 0; quad->weight = 0; @@ -43,15 +46,36 @@ extern LOCAL_SYM void quad_pt_release (struct quad_pt* quad); +extern LOCAL_SYM res_T +quad_pt_copy + (struct quad_pt* dst, + const struct quad_pt* src); + +static INLINE res_T +quad_pt_copy_and_release(struct quad_pt* dst, struct quad_pt* src) +{ + ASSERT(dst && src); + dst->band = src->band; + dst->ka_list = src->ka_list; + dst->map_len = src->map_len; + dst->weight = src->weight; + return RES_OK; +} + /* Define the dynamic array of quadrature points */ #define DARRAY_NAME quad_pt #define DARRAY_DATA struct quad_pt +#define DARRAY_FUNCTOR_INIT quad_pt_init +#define DARRAY_FUNCTOR_RELEASE quad_pt_release +#define DARRAY_FUNCTOR_COPY quad_pt_copy +#define DARRAY_FUNCTOR_COPY_AND_RELEASE quad_pt_copy_and_release #include <rsys/dynamic_array.h> struct band { + struct sck* sck; double low; /* Lower bound in nm (inclusive) */ double upp; /* Upper bound in nm (exclusive) */ - size_t map_len; + size_t map_len; /* 0 <=> band's data are not mapped, i.e. they are loaded */ float* ks_list; /* Per node ks */ struct darray_quad_pt quad_pts; struct darray_double quad_pts_cumul; @@ -61,6 +85,7 @@ static INLINE void band_init(struct mem_allocator* allocator, struct band* band) { ASSERT(band); + band->sck = NULL; band->low = DBL_MAX; band->upp =-DBL_MAX; band->map_len = 0; @@ -73,22 +98,10 @@ extern LOCAL_SYM void band_release (struct band* band); -static INLINE res_T -band_copy(struct band* dst, const struct band* src) -{ - res_T res = RES_OK; - ASSERT(dst && src); - - dst->low = src->low; - dst->upp = src->upp; - dst->map_len = src->map_len; - dst->ks_list = src->ks_list; - res = darray_quad_pt_copy(&dst->quad_pts, &src->quad_pts); - if(res != RES_OK) return res; - res = darray_double_copy(&dst->quad_pts_cumul, &src->quad_pts_cumul); - if(res != RES_OK) return res; - return RES_OK; -} +extern LOCAL_SYM res_T +band_copy + (struct band* dst, + const struct band* src); static INLINE res_T band_copy_and_release(struct band* dst, struct band* src) @@ -96,6 +109,7 @@ band_copy_and_release(struct band* dst, struct band* src) res_T res = RES_OK; ASSERT(dst && src); + dst->sck = src->sck; dst->low = src->low; dst->upp = src->upp; dst->map_len = src->map_len; @@ -126,6 +140,7 @@ struct sck { struct darray_band bands; size_t pagesize_os; + struct str name; /* path/stream name */ struct mem_allocator* allocator; struct logger* logger; diff --git a/src/test_sck_load.c b/src/test_sck_load.c @@ -21,6 +21,7 @@ #include <rsys/math.h> #include <rsys/mem_allocator.h> #include <math.h> +#include <string.h> static INLINE double rand_canonic(void) @@ -51,6 +52,9 @@ check_sck_load CHK(nbands); CHK(nnodes); + CHK(sck_validate(NULL) == RES_BAD_ARG); + CHK(sck_validate(sck) == RES_OK); + CHK(sck_get_bands_count(sck) == nbands); CHK(sck_get_nodes_count(sck) == nnodes); @@ -217,6 +221,8 @@ test_load(struct sck* sck) hash256_T band_hash1; hash256_T pt_hash0; hash256_T pt_hash1; + struct sck_load_args args = SCK_LOAD_ARGS_NULL; + struct sck_load_stream_args stream_args = SCK_LOAD_STREAM_ARGS_NULL; struct sck_band band; FILE* fp = NULL; @@ -229,9 +235,16 @@ test_load(struct sck* sck) write_sck(fp, pagesize, nbands, nnodes); rewind(fp); - CHK(sck_load_stream(NULL, fp, filename) == RES_BAD_ARG); - CHK(sck_load_stream(sck, NULL, filename) == RES_BAD_ARG); - CHK(sck_load_stream(sck, fp, NULL) == RES_OK); + stream_args.stream = fp; + stream_args.name = filename; + CHK(sck_load_stream(NULL, &stream_args) == RES_BAD_ARG); + CHK(sck_load_stream(sck, NULL) == RES_BAD_ARG); + stream_args.stream = NULL; + CHK(sck_load_stream(sck, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_OK); + + CHK(!strcmp(sck_get_name(sck), filename)); CHK(sck_compute_hash(sck, NULL) == RES_BAD_ARG); CHK(sck_compute_hash(NULL, hash0) == RES_BAD_ARG); @@ -254,12 +267,20 @@ test_load(struct sck* sck) check_sck_load(sck, nbands, nnodes); rewind(fp); - CHK(sck_load_stream(sck, fp, filename) == RES_OK); + stream_args.name = NULL; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); + stream_args.name = SCK_LOAD_STREAM_ARGS_NULL.name; + stream_args.memory_mapping = 1; + CHK(sck_load_stream(sck, &stream_args) == RES_OK); + CHK(!strcmp(sck_get_name(sck), SCK_LOAD_STREAM_ARGS_NULL.name)); - CHK(sck_load(NULL, filename) == RES_BAD_ARG); + args.path = "nop"; + CHK(sck_load(NULL, &args) == RES_BAD_ARG); CHK(sck_load(sck, NULL) == RES_BAD_ARG); - CHK(sck_load(sck, "nop") == RES_IO_ERR); - CHK(sck_load(sck, filename) == RES_OK); + CHK(sck_load(sck, &args) == RES_IO_ERR); + args.path = filename; + CHK(sck_load(sck, &args) == RES_OK); + CHK(!strcmp(sck_get_name(sck), args.path)); check_sck_load(sck, nbands, nnodes); CHK(sck_compute_hash(sck, hash1) == RES_OK); @@ -269,7 +290,9 @@ test_load(struct sck* sck) write_sck(fp, pagesize, nbands+1, nnodes); rewind(fp); - CHK(sck_load_stream(sck, fp, filename) == RES_OK); + stream_args.stream = fp; + stream_args.name = filename; + CHK(sck_load_stream(sck, &stream_args) == RES_OK); CHK(sck_compute_hash(sck, hash1) == RES_OK); CHK(!hash256_eq(hash0, hash1)); @@ -286,36 +309,42 @@ test_load(struct sck* sck) static void test_load_fail(struct sck* sck) { + struct sck_load_stream_args stream_args = SCK_LOAD_STREAM_ARGS_NULL; FILE* fp = NULL; double low; double upp; + /* The pagesize is less than the operating system page size*/ CHK(fp = tmpfile()); write_sck(fp, 2048, 1, 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); /* The pagesize is not a power of two */ CHK(fp = tmpfile()); write_sck(fp, 4100, 1, 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); /* Wrong #bands */ CHK(fp = tmpfile()); write_sck(fp, 4096, 0, 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); /* Wrong #nodes */ CHK(fp = tmpfile()); write_sck(fp, 4096, 1, 0); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); /* Wrong band boundaries */ @@ -327,7 +356,8 @@ test_load_fail(struct sck* sck) CHK(fwrite(&low, sizeof(low), 1, fp) == 1); CHK(fwrite(&upp, sizeof(upp), 1, fp) == 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); /* Unsorted bands */ @@ -342,7 +372,8 @@ test_load_fail(struct sck* sck) CHK(fwrite(&low, sizeof(low), 1, fp) == 1); CHK(fwrite(&upp, sizeof(upp), 1, fp) == 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); /* Bands overlap */ @@ -357,7 +388,8 @@ test_load_fail(struct sck* sck) CHK(fwrite(&low, sizeof(low), 1, fp) == 1); CHK(fwrite(&upp, sizeof(upp), 1, fp) == 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_BAD_ARG); + stream_args.stream = fp; + CHK(sck_load_stream(sck, &stream_args) == RES_BAD_ARG); CHK(fclose(fp) == 0); } @@ -372,9 +404,22 @@ test_load_files(struct sck* sck, int argc, char** argv) size_t nbands; size_t iband; - printf("Load %s\n", argv[1]); - - CHK(sck_load(sck, argv[i]) == RES_OK); + if(!strcmp(argv[i], "-")) { + struct sck_load_stream_args args = SCK_LOAD_STREAM_ARGS_NULL; + printf("Load from stdin\n"); + args.stream = stdin; + args.name = "stdin"; + args.memory_mapping = 1; + CHK(sck_load_stream(sck, &args) == RES_BAD_ARG); + args.memory_mapping = 0; + CHK(sck_load_stream(sck, &args) == RES_OK); + } else { + struct sck_load_args args = SCK_LOAD_ARGS_NULL; + printf("Load %s\n", argv[1]); + args.path = argv[i]; + args.memory_mapping = 1; + CHK(sck_load(sck, &args) == RES_OK); + } nbands = sck_get_bands_count(sck); nnodes = sck_get_nodes_count(sck); CHK(nbands); @@ -382,37 +427,15 @@ test_load_files(struct sck* sck, int argc, char** argv) FOR_EACH(iband, 0, nbands) { struct sck_band band = SCK_BAND_NULL; - size_t inode; - size_t iqpt; CHK(sck_get_band(sck, iband, &band) == RES_OK); printf("band %lu in [%g, %g[ nm with %lu quadrature points\n", (unsigned long)band.id, band.lower, band.upper, (unsigned long)band.quad_pts_count); - - CHK(band.lower == band.lower); /* !NaN */ - CHK(band.upper == band.upper); /* !NaN */ - CHK(band.lower < band.upper); - CHK(band.quad_pts_count); - - FOR_EACH(inode, 0, nnodes) { - CHK(band.ks_list[inode] == band.ks_list[inode]); /* !NaN */ - } - - FOR_EACH(iqpt, 0, band.quad_pts_count) { - struct sck_quad_pt qpt; - - CHK(sck_band_get_quad_pt(&band, iqpt, &qpt) == RES_OK); - CHK(qpt.weight == qpt.weight); /* !NaN */ - CHK(qpt.weight > 0); - - FOR_EACH(inode, 0, nnodes) { - CHK(qpt.ka_list[inode] == qpt.ka_list[inode]); /* !NaN */ - } - } } + CHK(sck_validate(sck) == RES_OK); CHK(sck_compute_hash(sck, hash) == RES_OK); } } @@ -420,6 +443,7 @@ test_load_files(struct sck* sck, int argc, char** argv) static void test_find(struct sck* sck) { + struct sck_load_stream_args stream_args = SCK_LOAD_STREAM_ARGS_NULL; size_t ibands[2]; double range[2]; FILE* fp; @@ -427,7 +451,9 @@ test_find(struct sck* sck) CHK(fp = tmpfile()); write_sck(fp, 4096, 10, 1); rewind(fp); - CHK(sck_load_stream(sck, fp, NULL) == RES_OK); + stream_args.stream = fp; + stream_args.memory_mapping = 1; + CHK(sck_load_stream(sck, &stream_args) == RES_OK); range[0] = 0; range[1] = 10;