star-wf

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

commit 60b0db63ea3bc9d669eb28e3312c20fdc9d7cba7
parent 83bcb658fe11bb59afa6f4572067fceb185362b7
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 11 Mar 2024 15:09:42 +0100

Tabulation update for function H

From now on, the smaller the x, the smaller the tabulation step. This
ensures that the function is finely tabulated where estimation of H is
difficult (i.e. close to 0).

The test is updated to check the relative error on x over the whole
range. We check that the relative error is less than 1e-5 or 1e-6,
except for Hx values outside [1e-9, 1-1e-9].

Diffstat:
Msrc/swf.h | 12++++++++++--
Msrc/swf_H.c | 58++++++++++++++++++++++++++++++----------------------------
Msrc/test_swf_H3d.c | 66+++++++++++++++++++++++++++++++++---------------------------------
3 files changed, 73 insertions(+), 63 deletions(-)

diff --git a/src/swf.h b/src/swf.h @@ -42,10 +42,18 @@ struct mem_allocator; struct swf_H_tabulate_args { double x_min; double x_max; - double delta_x; + + /* The step member variable is used to calculate the x arguments to be + * tabulated. Each x to be tabulated is calculated by adding its product with + * the step value to the previous one. The delta x is therefore relative to + * each x value: the smaller the x, the smaller the tabulation step. And this + * is what we're looking for, since the function is more difficult to estimate + * near 0 */ + double step; /* x_next = x + x*step: */ + struct mem_allocator* allocator; /* NULL <=> use default allocator */ }; -#define SWF_H_TABULATE_ARGS_DEFAULT__ {7.5e-3, 3.8, 1e-5, NULL} +#define SWF_H_TABULATE_ARGS_DEFAULT__ {7.5e-3, 3.8, 1e-3, NULL} static const struct swf_H_tabulate_args SWF_H_TABULATE_ARGS_DEFAULT = SWF_H_TABULATE_ARGS_DEFAULT__; diff --git a/src/swf_H.c b/src/swf_H.c @@ -38,11 +38,11 @@ check_swf_H_tabulate_args(const struct swf_H_tabulate_args* args) return RES_BAD_ARG; /* Delta X cannot be null */ - if(args->delta_x <= 0) + if(args->step <= 0) return RES_BAD_ARG; /* Delta X cannot be greater than the X range */ - if(args->delta_x > args->x_max - args->x_min) + if(args->step > args->x_max - args->x_min) return RES_BAD_ARG; return RES_OK; @@ -163,8 +163,7 @@ swf_H3d_tabulate struct item* items = NULL; size_t nitems = 0; size_t i = 0; - size_t j = 0; - double x_range = 0; + double x = 0; double norm = 0; res_T res = RES_OK; @@ -173,36 +172,39 @@ swf_H3d_tabulate if((res = check_swf_H_tabulate_args(args)) != RES_OK) goto error; if((res = tabulation_create(args->allocator, &tab)) != RES_OK) goto error; - /* Calculate the maximum number of arguments x to be tabulated. Note that - * there is one more argument than the number of intervals, hence the +1 */ - x_range = args->x_max - args->x_min; - nitems = (size_t)ceil(x_range / args->delta_x) + 1; - - /* Tabulation memory space allocation */ - if((res = darray_item_resize(&tab->items, nitems)) != RES_OK) goto error; - items = darray_item_data_get(&tab->items); - /* Setup the tabulation */ - FOR_EACH(i, 0, nitems) { + 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 3 */ - items[j].x = MMIN(args->x_min + (double)i*args->delta_x, args->x_max); - items[j].fx = H3d(items[j].x, &items[j].dfx, &items[j].d2fx, &items[j].d3fx); + item.x = MMIN(x, args->x_max); + item.fx = H3d(item.x, &item.dfx, &item.d2fx, &item.d3fx); + + 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; + } + } - if(j == 0) { - ++j; - } else { - /* Detect oscillations due to numerical problems. - * These oscillations occur close to 0 */ - if(items[j].fx < items[j-1].fx) { res = RES_BAD_OP; goto error; } + ++i; - /* Reject tabulation whose value is (numerically) equal to the previous - * one: it would artificially add staircase steps */ - j += items[j].fx > items[j-1].fx; - } + res = darray_item_push_back(&tab->items, &item); + if(res != RES_OK) goto error; } - CHK(j && darray_item_resize(&tab->items, j) == RES_OK); - nitems = j; + ASSERT(darray_item_size_get(&tab->items) == i); + items = darray_item_data_get(&tab->items); + nitems = darray_item_size_get(&tab->items); /* Normalize the tabulation */ norm = items[nitems - 1].fx; diff --git a/src/test_swf_H3d.c b/src/test_swf_H3d.c @@ -24,12 +24,6 @@ /******************************************************************************* * Helper functions ******************************************************************************/ -static double -rand_canonic(void) -{ - return (double)rand() / ((double)RAND_MAX+1); -} - static INLINE void check_tabulation_creation(void) { @@ -55,13 +49,13 @@ check_tabulation_creation(void) args.x_min = SWF_H_TABULATE_ARGS_DEFAULT.x_min; args.x_max = SWF_H_TABULATE_ARGS_DEFAULT.x_max; - args.delta_x = args.x_max - args.x_min + 1.e-3; + args.step = args.x_max - args.x_min + 1.e-3; CHK(swf_H3d_tabulate(&args, &tab) == RES_BAD_ARG); - args.delta_x = 0; + args.step = 0; CHK(swf_H3d_tabulate(&args, &tab) == RES_BAD_ARG); - args.delta_x = 1.0e-3; + args.step = 1.0e-2; CHK(swf_H3d_tabulate(&args, &tab) == RES_OK); CHK(swf_tabulation_ref_put(tab) == RES_OK); } @@ -69,37 +63,43 @@ check_tabulation_creation(void) static void check_tabulation_inversion(void) { - const double delta_Hx = 1.0e-6; + const double nsteps = 10; struct swf_H_tabulate_args args = SWF_H_TABULATE_ARGS_DEFAULT; struct swf_tabulation* tab = NULL; - double errl = 0; - double errq = 0; - size_t n = 0; + double pHx = 0; + double x0 = 0; size_t i = 0; CHK(swf_H3d_tabulate(&args, &tab) == RES_OK); - /*errl = swf_tabulation_max_x_error(tab, SWF_LINEAR); - errq = swf_tabulation_max_x_error(tab, SWF_QUADRATIC); - CHK(errq < errl);*/ - - n = (size_t)ceil(1.0/delta_Hx); - - FOR_EACH(i, 0, n) { - const double Hx = (double)i*delta_Hx + rand_canonic()*delta_Hx; - const double x_ref = swf_H3d_inverse(Hx); - const double xl = swf_tabulation_inverse(tab, SWF_LINEAR, Hx); - const double xq = swf_tabulation_inverse(tab, SWF_QUADRATIC, Hx); - - if(!x_ref) { - CHK(x_ref == xl); - } else { - errl = fabs((x_ref - xl) / x_ref); - errq = fabs((x_ref - xq) / x_ref); - /*printf("%e %e %e %e %e\n", x_ref, xq, xl, errq, errl);*/ - CHK(errl < 1.e-4); - CHK(errq < 1.e-6); + for(x0 = args.x_min; x0 < args.x_max; x0 += x0*args.step) { + const double x1 = x0 + x0*args.step; + const double delta_x = (x1 - x0) / (double)nsteps; + + for(i = 1; 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 errl = fabs((x - xl) / x); + const double errq = fabs((x - xq) / 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 %.20e %e %e\n", x, Hx, errq, errl); + if(1e-9 < Hx && Hx < (1.0 - 1e-9)) { + CHK(errl < 1.e-5); + CHK(errq < 1.e-6); + } else { + CHK(errl < 1.e-1); + CHK(errq < 1.e-1); + } + pHx = Hx; } }