star-wf

First-passage time of brownian motion
git clone git://git.meso-star.fr/star-wf.git
Log | Files | Refs | README | LICENSE

commit 596ae52a870c494f3a2d9971abea4a48c0502564
parent b20d98f6b06ea0e385280a38cbe515f7ce7461fd
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Wed, 13 Mar 2024 15:43:17 +0100

Add and test the 2D H function

This is the 2D version of the H-function, i.e. the cumulative
probability density function used to sample a passage time for a circle.
Its API is the same as that of its 3D version: the function can be
evaluated inverted and tabulated to improve inversion efficiency.

The tests are the same as for the 3D H function. We check its API and
ensure its numerical accuracy.

Diffstat:
MMakefile | 4++--
Mconfig.mk | 2+-
Msrc/swf.h | 36+++++++++++++++++++++++++++++-------
Msrc/swf_H.c | 232++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Asrc/swf_HXd.h | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_swf_H2d.c | 35+++++++++++++++++++++++++++++++++++
Msrc/test_swf_H3d.c | 97+++++--------------------------------------------------------------------------
Asrc/test_swf_HXd.h | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 489 insertions(+), 180 deletions(-)

diff --git a/Makefile b/Makefile @@ -110,7 +110,7 @@ lint: ################################################################################ # Tests ################################################################################ -TEST_SRC = src/test_swf_H3d.c +TEST_SRC = src/test_swf_H2d.c src/test_swf_H3d.c TEST_OBJ = $(TEST_SRC:.c=.o) TEST_DEP = $(TEST_SRC:.c=.d) @@ -137,5 +137,5 @@ $(TEST_DEP): config.mk swf-local.pc $(TEST_OBJ): config.mk swf-local.pc $(CC) $(CFLAGS_EXE) $(SWF_CFLAGS) $(RSYS_CFLAGS) -c $(@:.o=.c) -o $@ -test_swf_H3d: config.mk swf-local.pc $(LIBNAME) +test_swf_H2d test_swf_H3d: config.mk swf-local.pc $(LIBNAME) $(CC) $(CFLAGS_EXE) -o $@ src/$@.o $(LDFLAGS_EXE) $(SWF_LIBS) $(RSYS_LIBS) -lm diff --git a/config.mk b/config.mk @@ -47,7 +47,7 @@ CFLAGS_HARDENED =\ -fstack-protector-strong CFLAGS_COMMON =\ - -std=c89\ + -std=c99\ -pedantic\ -fvisibility=hidden\ -fstrict-aliasing\ diff --git a/src/swf.h b/src/swf.h @@ -62,6 +62,9 @@ struct swf_H_tabulate_args { /* The H function's default tab values guarantee both numerical accuracy and a * small memory footprint */ +#define SWF_H2D_TABULATE_ARGS_DEFAULT__ {8.0e-3, 6.05, 1e-3, 0, NULL} +static const struct swf_H_tabulate_args SWF_H2D_TABULATE_ARGS_DEFAULT = + SWF_H2D_TABULATE_ARGS_DEFAULT__; #define SWF_H3D_TABULATE_ARGS_DEFAULT__ {7.5e-3, 3.337, 1e-3, 0, NULL} static const struct swf_H_tabulate_args SWF_H3D_TABULATE_ARGS_DEFAULT = SWF_H3D_TABULATE_ARGS_DEFAULT__; @@ -71,19 +74,25 @@ struct swf_tabulation; BEGIN_DECLS -/* This function evaluates the 3D H function, as specified in: +/* These functions evaluate the H function, as specified in: * * "The floating random walk and its application to Monte Carlo solutions of * heat equations" - Haji-Sheikh A. and Sparrow E.M., SIAM Journal on Applied * Mathematics, 14(2): 370-389, 1966 * - * This function is expressed as an infinite series, which is numerically - * evaluated by summing all terms, until convergence is reached : + * These function are expressed as an infinite series, which is numerically + * evaluated by summing all terms, until convergence is reached. + * Two functions are defined here, H2d and H3d, which are used in 2D and 3D + * respectively: + * +INF + * __ + * H2d(x) = 1 - 2 >_ exp(-x*ak^2) / (ak*J1(ak)); ak is the k^th root of J0 + * k=1 * - * +INF - * __ - * H(x) = 1 + 2 >_ (-1)^k * exp(-(PI*k)^2 * x) - * k=1 + * +INF + * __ + * H3d(x) = 1 + 2 >_ (-1)^k * exp(-(PI*k)^2*x) + * k=1 * * The H function is the cumulated function of the probability density used to * sample passage times for a given sphere radius. Its parameter x is positive @@ -92,6 +101,10 @@ BEGIN_DECLS * - tau > 0 the time interval [s] * - r > 0 is the radius of the sphere [m] */ SWF_API double +swf_H2d_eval + (const double x); + +SWF_API double swf_H3d_eval (const double x); @@ -99,6 +112,15 @@ SWF_API double swf_H3d_inverse (const double y); /* in [0, 1[ */ +SWF_API double +swf_H2d_inverse + (const double y); /* in [0, 1[ */ + +SWF_API res_T +swf_H2d_tabulate + (const struct swf_H_tabulate_args* args, + struct swf_tabulation** tab); + SWF_API res_T swf_H3d_tabulate (const struct swf_H_tabulate_args* args, diff --git a/src/swf_H.c b/src/swf_H.c @@ -13,6 +13,8 @@ * 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 _XOPEN_SOURCE /* j1 */ + #include "swf.h" #include "swf_tabulation.h" @@ -21,6 +23,60 @@ #include <limits.h> #include <math.h> +/* Location of the first positive zero of the Bessel function J0. + * + * They are precalculated using the gsl_sf_bessel_zero_J0 function and defined + * in their hexadecimal floating-point representation, to ensure that they match + * the value calculated by the GSL to the nearest bit. The C program used to + * calculate these values can be summarized as follows: + * + * #include <stdio.h> + * #include <gsl/gsl_sf_bessel.h> + * int main(void) + * { + * for(unsigned i=1; i<=100; printf("%a\n", gsl_sf_bessel_zero_J0(i++))); + * return 0; + * } + */ +static const double j0_roots[] = { + NaN, + 0x1.33d152e971b3bp+1, 0x1.6148f5b2c2e3ep+2, 0x1.14eb56cccded1p+3, + 0x1.79544008272abp+3, 0x1.ddca13ef271e1p+3, 0x1.212313f8a19edp+4, + 0x1.5362dd173f795p+4, 0x1.85a3b930156e8p+4, 0x1.b7e54a5fd5f1cp+4, + 0x1.ea27591cbbed8p+4, 0x1.0e34e13a66fe6p+5, 0x1.275637a9619eap+5, + 0x1.4077a7ed62935p+5, 0x1.59992c65d0d86p+5, 0x1.72bac0f810807p+5, + 0x1.8bdc6293f064ep+5, 0x1.a4fe0ee444c7p+5, 0x1.be1fc41a4c5fcp+5, + 0x1.d74180c9e41eap+5, 0x1.f06343d0971c9p+5, 0x1.04c28621f11ep+6, + 0x1.11536cb22d724p+6, 0x1.1de4554a1c2d6p+6, 0x1.2a753fa82047ap+6, + 0x1.37062b9535d1p+6, 0x1.439718e2e3795p+6, 0x1.50280769a218fp+6, + 0x1.5cb8f7079c7aep+6, 0x1.6949e79fb1f06p+6, 0x1.75dad918abf93p+6, + 0x1.826bcb5c9b61dp+6, 0x1.8efcbe585425p+6, 0x1.9b8db1fb017fbp+6, + 0x1.a81ea635cd31dp+6, 0x1.b4af9afb9610bp+6, 0x1.c1409040b2ea7p+6, + 0x1.cdd185fabf635p+6, 0x1.da627c2070f25p+6, 0x1.e6f372a97287p+6, + 0x1.f384698e45aa8p+6, 0x1.000ab06414167p+7, 0x1.06532c287ecd1p+7, + 0x1.0c9ba8119dec5p+7, 0x1.12e4241ced385p+7, 0x1.192ca04822089p+7, + 0x1.1f751c9124fd1p+7, 0x1.25bd98f60c82fp+7, 0x1.2c06157518087p+7, + 0x1.324e920cabc8fp+7, 0x1.38970ebb4d19ep+7, 0x1.3edf8b7f9f282p+7, + 0x1.4528085860164p+7, 0x1.4b708544666ep+7, 0x1.51b902429edb7p+7, + 0x1.58017f520a27dp+7, 0x1.5e49fc71bb6b2p+7, 0x1.649279a0d6701p+7, + 0x1.6adaf6de8e417p+7, 0x1.7123742a23dep+7, 0x1.776bf182e50dcp+7, + 0x1.7db46ee82b546p+7, 0x1.83fcec595afe4p+7, 0x1.8a4569d5e2445p+7, + 0x1.908de75d3884dp+7, 0x1.96d664eedd8edp+7, 0x1.9d1ee28a58fdap+7, + 0x1.a367602f39a33p+7, 0x1.a9afdddd15001p+7, 0x1.aff85b9386c73p+7, + 0x1.b640d952306b7p+7, 0x1.bc895718b8b88p+7, 0x1.c2d1d4e6cb72dp+7, + 0x1.c91a52bc19007p+7, 0x1.cf62d0985618dp+7, 0x1.d5ab4e7b3b7a4p+7, + 0x1.dbf3cc6485a69p+7, 0x1.e23c4a53f4a4p+7, 0x1.e884c8494bc31p+7, + 0x1.eecd46445169ap+7, 0x1.f515c444cee1p+7, 0x1.fb5e424a90284p+7, + 0x1.00d3602ab1e5p+8, 0x1.03f79f328d5a5p+8, 0x1.071bde3cc40b1p+8, + 0x1.0a401d49409dp+8, 0x1.0d645c57eeb4ep+8, 0x1.10889b68bae7ap+8, + 0x1.13acda7b92acep+8, 0x1.16d119906452p+8, 0x1.19f558a71eee5p+8, + 0x1.1d1997bfb2581p+8, 0x1.203dd6da0f199p+8, 0x1.236215f626682p+8, + 0x1.26865513ea1a6p+8, 0x1.29aa94334ca02p+8, 0x1.2cced35440fa3p+8, + 0x1.2ff31276bab31p+8, 0x1.3317519aadd77p+8, 0x1.363b90c00ef04p+8, + 0x1.395fcfe6d2fbfp+8 +}; +static const size_t j0_nroots = sizeof(j0_roots)/sizeof(*j0_roots); + /******************************************************************************* * Helper functions ******************************************************************************/ @@ -44,6 +100,70 @@ check_swf_H_tabulate_args(const struct swf_H_tabulate_args* args) return RES_OK; } +/* H(x) = 1 - 2 Sum(k=1..INF)[exp(-x*ak^2) / (ak*J1(ak))] + * H'(x) = 2 Sum(k=1..INF)[exp(-x*ak^2) * ak/J1(ak)] + * H"(x) = - 2 Sum(k=1..INF)[exp(-x*ak^2) * ak^3/J1(ak)] */ +static INLINE double +H2d + (const double x, + double* dHx, /* First derivative. NULL <=> do not compute it */ + double* d2Hx) /* Second derivative. NULL <=> do not compute it */ +{ + double Hx = 0; /* Value of the function */ + + /* Sum of the terms in the series */ + double sum = 0; /* Sum of the terms */ + double sumd = 0; /* Sum of the derivative terms */ + double sumd2 = 0; /* Sum of the second derivative terms */ + + unsigned k = 1; /* Index of the serie term */ + + /* Have the calculations converged or are there errors? */ + int error = 1; + int errord = dHx != NULL; + int errord2 = d2Hx != NULL; + + ASSERT(x >= 0); + + if(x == 0) return 0; + + do { + double ak, j, t, term, sum_next; + CHK(k < j0_nroots); + + ak = j0_roots[k]; + j = j1(ak); + t = exp(-x*ak*ak); + term = t / (ak*j); + sum_next = sum + term; + + error = sum_next != sum; + sum = sum_next; + + if(dHx) { /* Derivative */ + const double termd = t/j * ak; + const double sumd_next = sumd + termd; + errord = sumd_next != sumd; + sumd = sumd_next; + } + + if(d2Hx) { /* Second derivative */ + const double termd2 = t/j * ak*ak*ak; + const double sumd2_next = sumd2 + termd2; + errord2 = sumd2_next != sumd2; + sumd2 = sumd2_next; + } + + } while((error || errord || errord2) && ++k < UINT_MAX); + + Hx = 1.0 - 2.0 * sum; + + if(dHx) *dHx = 2.0 * sumd; /* Derivative */ + if(d2Hx) *d2Hx = -2.0 * sumd2; /* Second Derivative */ + + return Hx; +} + /* H(x) = 1 + 2 Sum(k=1..INF)[(-1)^k * exp(-(PI*k)^2 * x)] * H'(x) = - 2 Sum(k=1..INF)[(-1)^k * exp(-(PI*k)^2 * x) * (PI*k)^2] * H"(x) = + 2 Sum(k=1..INF)[(-1)^k * exp(-(PI*k)^2 * x) * (PI*k)^4] */ @@ -107,36 +227,51 @@ H3d return Hx; } +/* Define generic dimension functions */ +#define SWF_DIMENSION 2 +#include "swf_HXd.h" +#define SWF_DIMENSION 3 +#include "swf_HXd.h" + /******************************************************************************* * Exported symbols ******************************************************************************/ double +swf_H2d_eval(const double x) +{ + return H2d(x, NULL, NULL); +} + +res_T +swf_H2d_tabulate + (const struct swf_H_tabulate_args* args, + struct swf_tabulation** out_tab) +{ + return H_tabulate2d(args, out_tab); +} + +double swf_H3d_eval(const double x) { return H3d(x, NULL, NULL); } double -swf_H3d_inverse(const double y) +swf_H2d_inverse(const double y) { - const double epsilon = 1.0e-12; - double x_min = SWF_H_TABULATE_ARGS_DEFAULT.x_min; - double x_max = SWF_H_TABULATE_ARGS_DEFAULT.x_max; - ASSERT(y >= 0 && y < 1); - - if(y == 0) return 0; - - while(x_max - x_min > epsilon) { - const double x = (x_min + x_max) * 0.5; - const double f_x = swf_H3d_eval(x); + return H_inverse2d(y, + SWF_H2D_TABULATE_ARGS_DEFAULT.x_min, + SWF_H2D_TABULATE_ARGS_DEFAULT.x_max, + 1.0e-12); /* Epsilon */ +} - if(f_x < y) { - x_min = x; - } else { - x_max = x; - } - } - return (x_min + x_max) * 0.5; +double +swf_H3d_inverse(const double y) +{ + return H_inverse3d(y, + SWF_H3D_TABULATE_ARGS_DEFAULT.x_min, + SWF_H3D_TABULATE_ARGS_DEFAULT.x_max, + 1.0e-12); /* Epsilon */ } res_T @@ -144,64 +279,5 @@ swf_H3d_tabulate (const struct swf_H_tabulate_args* args, struct swf_tabulation** out_tab) { - struct swf_tabulation* tab = NULL; - size_t i = 0; - double x = 0; - res_T res = RES_OK; - - if(!out_tab) { res = RES_BAD_ARG; goto error; } - - if((res = check_swf_H_tabulate_args(args)) != RES_OK) goto error; - if((res = tabulation_create(args->allocator, &tab)) != RES_OK) goto error; - - /* Setup the tabulation */ - i = 0; - for(x = args->x_min; x <= args->x_max; x += x*args->step) { - const struct item* pitem = darray_item_cdata_get(&tab->items) + i - 1; - struct item item = ITEM_NULL; - - /* Evaluate H(x) and its derivatives up to order 2 */ - item.x = MMIN(x, args->x_max); - item.fx = H3d(item.x, &item.dfx, &item.d2fx); - - if(i > 0) { - /* Detect oscillations due to numerical problems. - * These oscillations occur close to 0 */ - if(item.fx < pitem->fx) { - res = RES_BAD_OP; - goto error; - } - /* Do not tabulate argument whose value is (numerically) equal to the - * previous one: it would artificially add staircase steps */ - if(item.fx == pitem->fx) { - continue; - } - } - - ++i; - - res = darray_item_push_back(&tab->items, &item); - if(res != RES_OK) goto error; - } - ASSERT(darray_item_size_get(&tab->items) == i); - - - if(args->normalize) { - struct item* items = darray_item_data_get(&tab->items); - const size_t nitems = darray_item_size_get(&tab->items); - const double norm = items[nitems - 1].fx; - - /* Normalize the tabulation */ - FOR_EACH(i, 0, nitems) items[i].fx /= norm; - } - -exit: - if(out_tab) *out_tab = tab; - return res; -error: - if(tab) { - SWF(tabulation_ref_put(tab)); - tab = NULL; - } - goto exit; + return H_tabulate3d(args, out_tab); } diff --git a/src/swf_HXd.h b/src/swf_HXd.h @@ -0,0 +1,127 @@ +/* Copyright (C) 2024 |Méso|Star> (contact@meso-star.com) + * + * 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 "swf.h" +#include "swf_tabulation.h" + +#include <rsys/rsys.h> + +#ifndef SWF_DIMENSION + #error "The SWF_DIMENSION macro must be defined" +#endif + +#if SWF_DIMENSION != 2 && SWF_DIMENSION != 3 + #error "Invalid SWF_DIMEMSION" +#endif + +#define Xd(Name) CONCAT(CONCAT(Name, SWF_DIMENSION), d) + +/******************************************************************************* + * Helper generic dimension functions + ******************************************************************************/ +static INLINE double +Xd(H_inverse) + (const double y, + const double x_min, + const double x_max, + const double epsilon) +{ + double x0 = x_min; + double x1 = x_max; + ASSERT(y >= 0 && y < 1 && x_min < x_max && epsilon > 0); + + if(y == 0) return 0; + + while(x1 - x0 > epsilon) { + const double x = (x0 + x1) * 0.5; + const double f_x = Xd(H)(x, NULL, NULL); + + if(f_x < y) { + x0 = x; + } else { + x1 = x; + } + } + return (x0 + x1) * 0.5; +} + +static INLINE res_T +Xd(H_tabulate) + (const struct swf_H_tabulate_args* args, + struct swf_tabulation** out_tab) +{ + struct swf_tabulation* tab = NULL; + size_t i = 0; + double x = 0; + res_T res = RES_OK; + + if(!out_tab) { res = RES_BAD_ARG; goto error; } + + if((res = check_swf_H_tabulate_args(args)) != RES_OK) goto error; + if((res = tabulation_create(args->allocator, &tab)) != RES_OK) goto error; + + /* Setup the tabulation */ + i = 0; + for(x = args->x_min; x <= args->x_max; x += x*args->step) { + const struct item* pitem = darray_item_cdata_get(&tab->items) + i - 1; + struct item item = ITEM_NULL; + + /* Evaluate H(x) and its derivatives up to order 2 */ + item.x = x; + item.fx = Xd(H)(item.x, &item.dfx, &item.d2fx); + + if(i > 0) { + /* Detect oscillations due to numerical problems. + * These oscillations occur close to 0 */ + if(item.fx < pitem->fx) { + res = RES_BAD_OP; + goto error; + } + /* Do not tabulate argument whose value is (numerically) equal to the + * previous one: it would artificially add staircase steps */ + if(item.fx == pitem->fx) { + continue; + } + } + + ++i; + + res = darray_item_push_back(&tab->items, &item); + if(res != RES_OK) goto error; + } + ASSERT(darray_item_size_get(&tab->items) == i); + + if(args->normalize) { + struct item* items = darray_item_data_get(&tab->items); + const size_t nitems = darray_item_size_get(&tab->items); + const double norm = items[nitems - 1].fx; + + /* Normalize the tabulation */ + FOR_EACH(i, 0, nitems) items[i].fx /= norm; + } + +exit: + if(out_tab) *out_tab = tab; + return res; +error: + if(tab) { + SWF(tabulation_ref_put(tab)); + tab = NULL; + } + goto exit; +} + +#undef SWF_DIMENSION +#undef Xd diff --git a/src/test_swf_H2d.c b/src/test_swf_H2d.c @@ -0,0 +1,35 @@ +/* Copyright (C) 2024 |Méso|Star> (contact@meso-star.com) + * + * 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 "swf.h" +#include <rsys/mem_allocator.h> + +/* Define generic dimension helper functions */ +#define TEST_SWF_DIMENSION 2 +#include "test_swf_HXd.h" + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(int argc, char** argv) +{ + (void)argc, (void)argv; + + check_tabulation_creation_2d(); + check_inversion_2d(); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_swf_H3d.c b/src/test_swf_H3d.c @@ -14,98 +14,11 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "swf.h" - -#include <rsys/rsys.h> -#include <rsys/math.h> #include <rsys/mem_allocator.h> -#include <stdlib.h> - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static INLINE void -check_tabulation_creation(void) -{ - struct swf_H_tabulate_args args = SWF_H3D_TABULATE_ARGS_DEFAULT; - struct swf_tabulation* tab = NULL; - - CHK(swf_H3d_tabulate(NULL, &tab) == RES_BAD_ARG); - CHK(swf_H3d_tabulate(&args, NULL) == RES_BAD_ARG); - CHK(swf_H3d_tabulate(&args, &tab) == RES_OK); - - CHK(swf_tabulation_ref_get(NULL) == RES_BAD_ARG); - CHK(swf_tabulation_ref_get(tab) == RES_OK); - CHK(swf_tabulation_ref_put(NULL) == RES_BAD_ARG); - CHK(swf_tabulation_ref_put(tab) == RES_OK); - CHK(swf_tabulation_ref_put(tab) == RES_OK); - - args.x_min = SWF_H3D_TABULATE_ARGS_DEFAULT.x_max; - args.x_max = SWF_H3D_TABULATE_ARGS_DEFAULT.x_min; - CHK(swf_H3d_tabulate(&args, &tab) == RES_BAD_ARG); - - args.x_max = args.x_min; - CHK(swf_H3d_tabulate(&args, &tab) == RES_BAD_ARG); - - args.x_min = SWF_H3D_TABULATE_ARGS_DEFAULT.x_min; - args.x_max = SWF_H3D_TABULATE_ARGS_DEFAULT.x_max; - args.step = 0; - CHK(swf_H3d_tabulate(&args, &tab) == RES_BAD_ARG); - - args.step = 1.0e-2; - CHK(swf_H3d_tabulate(&args, &tab) == RES_OK); - CHK(swf_tabulation_ref_put(tab) == RES_OK); -} - -static void -check_inversion(void) -{ - const double nsteps = 10; - - struct swf_H_tabulate_args args = SWF_H3D_TABULATE_ARGS_DEFAULT; - struct swf_tabulation* tab = NULL; - double pHx = 0; - double x0 = 0; - size_t i = 0; - - CHK(swf_H3d_tabulate(&args, &tab) == RES_OK); - - for(x0 = args.x_min; x0 <= args.x_max; x0 += x0*args.step) { - const double x1 = MMIN(x0 + x0*args.step, args.x_max); - const double delta_x = (x1 - x0) / (double)nsteps; - - for(i = 0; i < nsteps; ++i) { - const double x = x0 + (double)i*delta_x; - const double Hx = swf_H3d_eval(x); - const double xl = swf_tabulation_inverse(tab, SWF_LINEAR, Hx); - const double xq = swf_tabulation_inverse(tab, SWF_QUADRATIC, Hx); - const double xi = swf_H3d_inverse(Hx); - const double errl = fabs((x - xl) / x); - const double errq = fabs((x - xq) / x); - const double erri = fabs((x - xi) / x); - - /* Do not check the value of x if the corresponding H is indistinguishable - * from an H calculated from a previous x. In other words, the H's are - * numerically equal and any of the corresponding x-values is a valid - * inversion result, numerically speaking. */ - if(Hx == pHx) continue; - - printf("%e %e %e %e %e\n", x, Hx, errq, errl, erri); - if(1e-9 < Hx && Hx < (1.0 - 1e-9)) { - CHK(errl < 1.e-5); - CHK(errq < 1.e-7); - CHK(erri < 1.e-7); - } else { - CHK(errl < 1.e-3); - CHK(errq < 1.e-3); - CHK(erri < 1.e-3); - } - pHx = Hx; - } - } - - CHK(swf_tabulation_ref_put(tab) == RES_OK); -} +/* Define generic dimension helper functions */ +#define TEST_SWF_DIMENSION 3 +#include "test_swf_HXd.h" /******************************************************************************* * The test @@ -115,8 +28,8 @@ main(int argc, char** argv) { (void)argc, (void)argv; - check_tabulation_creation(); - check_inversion(); + check_tabulation_creation_3d(); + check_inversion_3d(); CHK(mem_allocated_size() == 0); return 0; } diff --git a/src/test_swf_HXd.h b/src/test_swf_HXd.h @@ -0,0 +1,136 @@ +/* Copyright (C) 2024 |Méso|Star> (contact@meso-star.com) + * + * 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 "swf.h" + +#include <rsys/rsys.h> +#include <rsys/math.h> + +#include <stdlib.h> + +#ifndef TEST_SWF_DIMENSION + #error "The TEST_SWF_DIMENSION macro must be defined" +#endif + +/* 2D H function */ +#if TEST_SWF_DIMENSION == 2 + #define SWF_HXD_TABULATE_ARGS_DEFAULT SWF_H2D_TABULATE_ARGS_DEFAULT + #define swf_HXd_eval swf_H2d_eval + #define swf_HXd_inverse swf_H2d_inverse + #define swf_HXd_tabulate swf_H2d_tabulate + +/* 3D H function */ +#elif TEST_SWF_DIMENSION == 3 + #define SWF_HXD_TABULATE_ARGS_DEFAULT SWF_H3D_TABULATE_ARGS_DEFAULT + #define swf_HXd_eval swf_H3d_eval + #define swf_HXd_inverse swf_H3d_inverse + #define swf_HXd_tabulate swf_H3d_tabulate +#else + #error "Invalid TEST_SWF_DIMENSION" +#endif + +/* Macro defining a generic function name for the dimension*/ +#define Xd(Name) CONCAT(CONCAT(CONCAT(Name, _), TEST_SWF_DIMENSION), d) + +static INLINE void +Xd(check_tabulation_creation)(void) +{ + struct swf_H_tabulate_args args = SWF_HXD_TABULATE_ARGS_DEFAULT; + struct swf_tabulation* tab = NULL; + + CHK(swf_HXd_tabulate(NULL, &tab) == RES_BAD_ARG); + CHK(swf_HXd_tabulate(&args, NULL) == RES_BAD_ARG); + CHK(swf_HXd_tabulate(&args, &tab) == RES_OK); + + CHK(swf_tabulation_ref_get(NULL) == RES_BAD_ARG); + CHK(swf_tabulation_ref_get(tab) == RES_OK); + CHK(swf_tabulation_ref_put(NULL) == RES_BAD_ARG); + CHK(swf_tabulation_ref_put(tab) == RES_OK); + CHK(swf_tabulation_ref_put(tab) == RES_OK); + + args.x_min = SWF_HXD_TABULATE_ARGS_DEFAULT.x_max; + args.x_max = SWF_HXD_TABULATE_ARGS_DEFAULT.x_min; + CHK(swf_HXd_tabulate(&args, &tab) == RES_BAD_ARG); + + args.x_max = args.x_min; + CHK(swf_HXd_tabulate(&args, &tab) == RES_BAD_ARG); + + args.x_min = SWF_HXD_TABULATE_ARGS_DEFAULT.x_min; + args.x_max = SWF_HXD_TABULATE_ARGS_DEFAULT.x_max; + args.step = 0; + CHK(swf_HXd_tabulate(&args, &tab) == RES_BAD_ARG); + + args.step = 1.0e-2; + CHK(swf_HXd_tabulate(&args, &tab) == RES_OK); + CHK(swf_tabulation_ref_put(tab) == RES_OK); +} + +static void +Xd(check_inversion)(void) +{ + const double nsteps = 10; + + struct swf_H_tabulate_args args = SWF_HXD_TABULATE_ARGS_DEFAULT; + struct swf_tabulation* tab = NULL; + double pHx = 0; + double x0 = 0; + size_t i = 0; + + CHK(swf_HXd_tabulate(&args, &tab) == RES_OK); + + for(x0 = args.x_min; x0 <= args.x_max; x0 += x0*args.step) { + const double x1 = MMIN(x0 + x0*args.step, args.x_max); + const double delta_x = (x1 - x0) / (double)nsteps; + + for(i = 0; i < nsteps; ++i) { + const double x = x0 + (double)i*delta_x; + const double Hx = swf_HXd_eval(x); + const double xl = swf_tabulation_inverse(tab, SWF_LINEAR, Hx); + const double xq = swf_tabulation_inverse(tab, SWF_QUADRATIC, Hx); + const double xi = swf_HXd_inverse(Hx); + const double errl = fabs((x - xl) / x); + const double errq = fabs((x - xq) / x); + const double erri = fabs((x - xi) / x); + + /* Do not check the value of x if the corresponding H is indistinguishable + * from an H calculated from a previous x. In other words, the H's are + * numerically equal and any of the corresponding x-values is a valid + * inversion result, numerically speaking. */ + if(Hx == pHx) continue; + + printf("%e %e %e %e %e\n", x, Hx, errq, errl, erri); + if(1e-9 < Hx && Hx < (1.0 - 1e-9)) { + CHK(errl < 1.e-5); + CHK(errq < 1.e-7); + CHK(erri < 1.e-7); + } else { + CHK(errl < 1.e-3); + CHK(errq < 1.e-3); + CHK(erri < 1.e-3); + } + pHx = Hx; + } + } + + CHK(swf_tabulation_ref_put(tab) == RES_OK); +} + +/* Undefine macros used for genericity */ +#undef TEST_SWF_DIMENSION +#undef SWF_HXD_TABULATE_ARGS_DEFAULT +#undef swf_HXd_eval +#undef swf_HXd_inverse +#undef swf_HXd_tabulate +#undef Xd