stardis-solver

Solve coupled heat transfers
git clone git://git.meso-star.fr/stardis-solver.git
Log | Files | Refs | README | LICENSE

commit e6e567e2a6c398c6e5b65f9fb2c1356b78949d8d
parent 922814aeb8a1977f27ee6897a5160d2c957e71da
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Fri,  9 Feb 2024 10:43:30 +0100

Merge branch 'feature_solve_probe_list' into develop

Diffstat:
MMakefile | 6+++++-
Msrc/sdis.c | 4++--
Msrc/sdis.h | 54++++++++++++++++++++++++++++++++++++++++++------------
Msrc/sdis_estimator_buffer.c | 5+++++
Asrc/sdis_estimator_buffer_X_obs.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/sdis_estimator_buffer_c.h | 20++++++++++++++++++++
Msrc/sdis_solve.c | 16+++++++++++++++-
Msrc/sdis_solve_probe_Xd.h | 89++++++-------------------------------------------------------------------------
Msrc/sdis_solve_probe_boundary_Xd.h | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_sdis_draw_external_flux.c | 82+++++--------------------------------------------------------------------------
Asrc/test_sdis_mesh.h | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_sdis_solve_probe_boundary_list.c | 488+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_sdis_utils.c | 1-
13 files changed, 1121 insertions(+), 177 deletions(-)

diff --git a/Makefile b/Makefile @@ -212,7 +212,8 @@ TEST_SRC_MPI =\ src/test_sdis_solve_boundary.c\ src/test_sdis_solve_boundary_flux.c\ src/test_sdis_solve_probe2.c\ - src/test_sdis_solve_probe_list.c + src/test_sdis_solve_probe_list.c\ + src/test_sdis_solve_probe_boundary_list.c TEST_OBJ =\ $(TEST_SRC:.c=.o)\ $(TEST_SRC_MPI:.c=.o)\ @@ -430,18 +431,21 @@ test_sdis_solve_medium_2d \ src/test_sdis_compute_power.d \ src/test_sdis_solve_camera.d \ src/test_sdis_solve_medium.d \ +src/test_sdis_solve_probe_boundary_list.d \ : config.mk sdis-local.pc @$(CC) $(TEST_CFLAGS_MPI) $(S3DUT_CFLAGS) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ src/test_sdis_compute_power.o \ src/test_sdis_solve_camera.o \ src/test_sdis_solve_medium.o \ +src/test_sdis_solve_probe_boundary_list.o \ : config.mk sdis-local.pc $(CC) $(TEST_CFLAGS_MPI) $(S3DUT_CFLAGS) -c $(@:.o=.c) -o $@ test_sdis_compute_power \ test_sdis_solve_camera \ test_sdis_solve_medium \ +test_sdis_solve_probe_boundary_list \ : config.mk sdis-local.pc $(LIBNAME) src/test_sdis_utils.o $(CC) $(TEST_CFLAGS_MPI) $(S3DUT_CFLAGS) -o $@ src/$@.o $(TEST_LIBS_MPI) $(S3DUT_LIBS) diff --git a/src/sdis.c b/src/sdis.c @@ -476,10 +476,10 @@ compute_process_index_range per_process_indices = nindices / (size_t)dev->mpi_nprocs; range[0] = per_process_indices * (size_t)dev->mpi_rank; - range[1] = range[0] + per_process_indices; /* Upper bound is _exclusive */ + range[1] = range[0] + per_process_indices; /* Upper bound is _exclusive_ */ ASSERT(range[0] <= range[1]); - /* Set the remaining number of indexes that are not managed by one process */ + /* Set the remaining number of indices that are not managed by one process */ remaining_indices = nindices - per_process_indices * (size_t)dev->mpi_nprocs; diff --git a/src/sdis.h b/src/sdis.h @@ -553,6 +553,27 @@ static const struct sdis_solve_probe_boundary_args SDIS_SOLVE_PROBE_BOUNDARY_ARGS_DEFAULT = SDIS_SOLVE_PROBE_BOUNDARY_ARGS_DEFAULT__; +/* Input arguments of the solve function that distributes the calculations of + * several boundary probes rather than the realizations of a probe */ +struct sdis_solve_probe_boundary_list_args { + struct sdis_solve_probe_boundary_args* probes; /* List of probes to compute */ + size_t nprobes; /* Total number of probes */ + + /* State/type of the RNG to use for the list of probes to calculate. + * The state/type defines per probe is ignored */ + struct ssp_rng* rng_state; /* Initial RNG state. May be NULL */ + enum ssp_rng_type rng_type; /* RNG type to use if `rng_state' is NULL */ +}; +#define SDIS_SOLVE_PROBE_BOUNDARY_LIST_ARGS_DEFAULT__ { \ + NULL, /* List of probes */ \ + 0, /* #probes */ \ + NULL, /* RNG state */ \ + SSP_RNG_THREEFRY /* RNG type */ \ +} +static const struct sdis_solve_probe_boundary_list_args +SDIS_SOLVE_PROBE_BOUNDARY_LIST_ARGS_DEFAULT = + SDIS_SOLVE_PROBE_BOUNDARY_LIST_ARGS_DEFAULT__; + struct sdis_solve_boundary_args { size_t nrealisations; /* #realisations */ const size_t* primitives; /* List of boundary primitives to handle */ @@ -1424,18 +1445,6 @@ sdis_solve_probe const struct sdis_solve_probe_args* args, struct sdis_estimator** estimator); -/* Calculate temperature for a list of probe points. Unlike its - * single-probe counterpart, this function parallelizes the list of - * probes, rather than calculating a single probe. Calling this function - * is therefore more advantageous in terms of load distribution when the - * number of probe points to be evaluated is large compared to the cost - * of calculating a single probe point. */ -SDIS_API res_T -sdis_solve_probe_list - (struct sdis_scene* scn, - const struct sdis_solve_probe_list_args* args, - struct sdis_estimator_buffer** buf); - SDIS_API res_T sdis_solve_probe_boundary (struct sdis_scene* scn, @@ -1486,6 +1495,27 @@ sdis_compute_power struct sdis_estimator** estimator); /******************************************************************************* + * Solvers of a list of probes + * + * Unlike their single-probe counterpart, this function parallelizes the list of + * probes, rather than calculating a single probe. Calling these functions is + * therefore more advantageous in terms of load distribution when the number of + * probes to be evaluated is large compared to the cost of calculating a single + * probe. + ******************************************************************************/ +SDIS_API res_T +sdis_solve_probe_list + (struct sdis_scene* scn, + const struct sdis_solve_probe_list_args* args, + struct sdis_estimator_buffer** buf); + +SDIS_API res_T +sdis_solve_probe_boundary_list + (struct sdis_scene* scn, + const struct sdis_solve_probe_boundary_list_args* args, + struct sdis_estimator_buffer** buf); + +/******************************************************************************* * Green solvers. * * Note that only the interfaces/media with flux/volumic power defined during diff --git a/src/sdis_estimator_buffer.c b/src/sdis_estimator_buffer.c @@ -273,3 +273,8 @@ estimator_buffer_save_rng_state return create_rng_from_rng_proxy(buf->dev, proxy, &buf->rng); } +/* Define the functions generic to the observable type */ +#define SDIS_X_OBS probe +#include "sdis_estimator_buffer_X_obs.h" +#define SDIS_X_OBS probe_boundary +#include "sdis_estimator_buffer_X_obs.h" diff --git a/src/sdis_estimator_buffer_X_obs.h b/src/sdis_estimator_buffer_X_obs.h @@ -0,0 +1,118 @@ +/* Copyright (C) 2016-2023 |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/>. */ + +/* Define functions on estimator buffer generic to the observable type. */ + +#ifndef SDIS_ESTIMATOR_BUFFER_X_OBS_H +#define SDIS_ESTIMATOR_BUFFER_X_OBS_H + +#include "sdis.h" +#include "sdis_estimator_c.h" +#include "sdis_estimator_buffer_c.h" +#include "sdis_misc.h" + +#endif /* SDIS_ESTIMATOR_BUFFER_X_OBS_H */ + +/* Check the generic observable parameter */ +#ifndef SDIS_X_OBS + #error "The SDIS_X_OBS macro must be defined." +#endif + +#define X_OBS(Name) CONCAT(CONCAT(Name, _), SDIS_X_OBS) +#define SDIS_SOLVE_X_OBS_ARGS CONCAT(CONCAT(sdis_solve_, SDIS_X_OBS),_args) + +res_T +X_OBS(estimator_buffer_create_from_observable_list) + (struct sdis_device* dev, + struct ssp_rng_proxy* rng_proxy, + const struct SDIS_SOLVE_X_OBS_ARGS obs_list_args[], + const struct accum* per_obs_acc_temp, + const struct accum* per_obs_acc_time, + const size_t nobs, /* #observables */ + struct sdis_estimator_buffer** out_estim_buffer) +{ + /* Accumulators throughout the buffer */ + struct accum acc_temp = ACCUM_NULL; + struct accum acc_time = ACCUM_NULL; + size_t nrealisations = 0; + + struct sdis_estimator_buffer* estim_buf = NULL; + size_t iobs = 0; + res_T res = RES_OK; + + ASSERT(dev && rng_proxy); + ASSERT(obs_list_args || !nobs); + ASSERT(per_obs_acc_time && per_obs_acc_time && out_estim_buffer); + + res = estimator_buffer_create(dev, nobs, 1, &estim_buf); + if(res != RES_OK) { + log_err(dev, "Unable to allocate the estimator buffer.\n"); + goto error; + } + + FOR_EACH(iobs, 0, nobs) { + const struct SDIS_SOLVE_X_OBS_ARGS* obs_args = NULL; + const struct accum* obs_acc_temp = NULL; + const struct accum* obs_acc_time = NULL; + struct sdis_estimator* estim = NULL; + + /* Get observable data */ + obs_args = obs_list_args + iobs; + obs_acc_temp = per_obs_acc_temp + iobs; + obs_acc_time = per_obs_acc_time + iobs; + ASSERT(obs_acc_temp->count == obs_acc_time->count); + + /* Setup the estimator of the current observable */ + estim = estimator_buffer_grab(estim_buf, iobs, 0); + estimator_setup_realisations_count + (estim, obs_args->nrealisations, obs_acc_temp->count); + estimator_setup_temperature + (estim, obs_acc_temp->sum, obs_acc_temp->sum2); + estimator_setup_realisation_time + (estim, obs_acc_time->sum, obs_acc_time->sum2); + + /* Update global accumulators */ + acc_temp.sum += obs_acc_temp->sum; + acc_temp.sum2 += obs_acc_temp->sum2; + acc_temp.count += obs_acc_temp->count; + acc_time.sum += obs_acc_time->sum; + acc_time.sum2 += obs_acc_time->sum2; + acc_time.count += obs_acc_time->count; + nrealisations += obs_args->nrealisations; + } + + ASSERT(acc_temp.count == acc_time.count); + + /* Setup global estimator */ + estimator_buffer_setup_realisations_count + (estim_buf, nrealisations, acc_temp.count); + estimator_buffer_setup_temperature + (estim_buf, acc_temp.sum, acc_temp.sum2); + estimator_buffer_setup_realisation_time + (estim_buf, acc_time.sum, acc_time.sum2); + + res = estimator_buffer_save_rng_state(estim_buf, rng_proxy); + if(res != RES_OK) goto error; + +exit: + *out_estim_buffer = estim_buf; + return res; +error: + goto exit; +} + +#undef X_OBS +#undef SDIS_SOLVE_X_OBS_ARGS +#undef SDIS_X_OBS diff --git a/src/sdis_estimator_buffer_c.h b/src/sdis_estimator_buffer_c.h @@ -59,5 +59,25 @@ estimator_buffer_save_rng_state (struct sdis_estimator_buffer* buf, const struct ssp_rng_proxy* proxy); +extern LOCAL_SYM res_T +estimator_buffer_create_from_observable_list_probe + (struct sdis_device* dev, + struct ssp_rng_proxy* rng_proxy, + const struct sdis_solve_probe_args obs_list_args[], + const struct accum* per_obs_acc_temp, + const struct accum* per_obs_acc_time, + const size_t nobs, /* #observables */ + struct sdis_estimator_buffer** out_estim_buffer); + +extern LOCAL_SYM res_T +estimator_buffer_create_from_observable_list_probe_boundary + (struct sdis_device* dev, + struct ssp_rng_proxy* rng_proxy, + const struct sdis_solve_probe_boundary_args obs_list_args[], + const struct accum* per_obs_acc_temp, + const struct accum* per_obs_acc_time, + const size_t nobs, /* #observables */ + struct sdis_estimator_buffer** out_estim_buffer); + #endif /* SDIS_ESTIMATOR_BUFFER_C_H */ diff --git a/src/sdis_solve.c b/src/sdis_solve.c @@ -59,7 +59,7 @@ sdis_solve_probe res_T sdis_solve_probe_list (struct sdis_scene* scn, - const struct sdis_solve_probe_list_args args[], + const struct sdis_solve_probe_list_args* args, struct sdis_estimator_buffer** out_buf) { if(!scn) return RES_BAD_ARG; @@ -99,6 +99,20 @@ sdis_solve_probe_boundary } res_T +sdis_solve_probe_boundary_list + (struct sdis_scene* scn, + const struct sdis_solve_probe_boundary_list_args* args, + struct sdis_estimator_buffer** out_buf) +{ + if(!scn) return RES_BAD_ARG; + if(scene_is_2d(scn)) { + return solve_probe_boundary_list_2d(scn, args, out_buf); + } else { + return solve_probe_boundary_list_3d(scn, args, out_buf); + } +} + +res_T sdis_solve_probe_boundary_green_function (struct sdis_scene* scn, const struct sdis_solve_probe_boundary_args* args, diff --git a/src/sdis_solve_probe_Xd.h b/src/sdis_solve_probe_Xd.h @@ -101,84 +101,6 @@ check_solve_probe_list_args return RES_OK; } -static res_T -setup_estimator_buffer - (struct sdis_device* dev, - struct ssp_rng_proxy* rng_proxy, - const struct sdis_solve_probe_list_args* solve_args, - const struct accum* per_probe_acc_temp, - const struct accum* per_probe_acc_time, - struct sdis_estimator_buffer** out_estim_buffer) -{ - /* Accumulators throughout the buffer */ - struct accum acc_temp = ACCUM_NULL; - struct accum acc_time = ACCUM_NULL; - size_t nrealisations = 0; - - struct sdis_estimator_buffer* estim_buf = NULL; - size_t iprobe = 0; - res_T res = RES_OK; - - ASSERT(dev && rng_proxy && solve_args); - ASSERT(per_probe_acc_time && per_probe_acc_time && out_estim_buffer); - - res = estimator_buffer_create(dev, solve_args->nprobes, 1, &estim_buf); - if(res != RES_OK) { - log_err(dev, "Unable to allocate the estimator buffer.\n"); - goto error; - } - - FOR_EACH(iprobe, 0, solve_args->nprobes) { - const struct sdis_solve_probe_args* probe = NULL; - const struct accum* probe_acc_temp = NULL; - const struct accum* probe_acc_time = NULL; - struct sdis_estimator* estim = NULL; - - /* Get probe data */ - probe = solve_args->probes + iprobe; - probe_acc_temp = per_probe_acc_temp + iprobe; - probe_acc_time = per_probe_acc_time + iprobe; - ASSERT(probe_acc_temp->count == probe_acc_time->count); - - /* Setup probe estimator */ - estim = estimator_buffer_grab(estim_buf, iprobe, 0); - estimator_setup_realisations_count - (estim, probe->nrealisations, probe_acc_temp->count); - estimator_setup_temperature - (estim, probe_acc_temp->sum, probe_acc_temp->sum2); - estimator_setup_realisation_time - (estim, probe_acc_time->sum, probe_acc_time->sum2); - - /* Update global accumulators */ - acc_temp.sum += probe_acc_temp->sum; - acc_temp.sum2 += probe_acc_temp->sum2; - acc_temp.count += probe_acc_temp->count; - acc_time.sum += probe_acc_time->sum; - acc_time.sum2 += probe_acc_time->sum2; - acc_time.count += probe_acc_time->count; - nrealisations += probe->nrealisations; - } - - ASSERT(acc_temp.count == acc_time.count); - - /* Setup global estimator */ - estimator_buffer_setup_realisations_count - (estim_buf, nrealisations, acc_temp.count); - estimator_buffer_setup_temperature - (estim_buf, acc_temp.sum, acc_temp.sum2); - estimator_buffer_setup_realisation_time - (estim_buf, acc_time.sum, acc_time.sum2); - - res = estimator_buffer_save_rng_state(estim_buf, rng_proxy); - if(res != RES_OK) goto error; - -exit: - *out_estim_buffer = estim_buf; - return res; -error: - goto exit; -} - #endif /* SDIS_SOLVE_PROBE_XD_H */ static res_T @@ -626,9 +548,6 @@ XD(solve_probe_list) goto post_sync; } - /* Begin time registration of the computation */ - time_current(&time0); - /* Allocate the list of accumulators per probe. On the master process, * allocate a complete list in which the accumulators of all processes will be * stored. */ @@ -641,6 +560,9 @@ XD(solve_probe_list) goto error; } + /* Begin time registration of the computation */ + time_current(&time0); + /* Here we go! Calculation of probe list */ omp_set_num_threads((int)scn->dev->nthreads); #pragma omp parallel for schedule(static) @@ -726,8 +648,9 @@ post_sync: log_info(scn->dev, "Probes accumulator gathered in %s.\n", buf); if(is_master_process) { - res = setup_estimator_buffer(scn->dev, rng_proxy, args, per_probe_acc_temp, - per_probe_acc_time, &estim_buf); + res = estimator_buffer_create_from_observable_list_probe + (scn->dev, rng_proxy, args->probes, per_probe_acc_temp, + per_probe_acc_time, args->nprobes, &estim_buf); if(res != RES_OK) goto error; } diff --git a/src/sdis_solve_probe_boundary_Xd.h b/src/sdis_solve_probe_boundary_Xd.h @@ -75,6 +75,40 @@ check_solve_probe_boundary_args } static INLINE res_T +check_solve_probe_boundary_list_args + (struct sdis_device* dev, + const struct sdis_solve_probe_boundary_list_args* args) +{ + size_t iprobe = 0; + + if(!args) return RES_BAD_ARG; + + /* Check the list of probes */ + if(!args->probes || !args->nprobes) { + return RES_BAD_ARG; + } + + /* Check the RNG type */ + if(!args->rng_state && args->rng_type >= SSP_RNG_TYPES_COUNT__) { + return RES_BAD_ARG; + } + + FOR_EACH(iprobe, 0, args->nprobes) { + const res_T res = check_solve_probe_boundary_args(args->probes+iprobe); + if(res != RES_OK) return res; + + if(args->probes[iprobe].register_paths != SDIS_HEAT_PATH_NONE) { + log_warn(dev, + "Unable to save paths for probe boundary %lu. " + "Saving path is not supported when solving multiple probes\n", + (unsigned long)iprobe); + } + } + + return RES_OK; +} + +static INLINE res_T check_solve_probe_boundary_flux_args (const struct sdis_solve_probe_boundary_flux_args* args) { @@ -109,6 +143,66 @@ check_solve_probe_boundary_flux_args #endif /* SDIS_SOLVE_PROBE_BOUNDARY_XD_H */ +static res_T +XD(solve_one_probe_boundary) + (struct sdis_scene* scn, + struct ssp_rng* rng, + const struct sdis_solve_probe_boundary_args* args, + struct accum* acc_temp, + struct accum* acc_time) +{ + size_t irealisation = 0; + res_T res = RES_OK; + ASSERT(scn && rng && check_solve_probe_boundary_args(args) == RES_OK); + + *acc_temp = ACCUM_NULL; + *acc_time = ACCUM_NULL; + + FOR_EACH(irealisation, 0, args->nrealisations) { + struct boundary_realisation_args realis_args = BOUNDARY_REALISATION_ARGS_NULL; + double w = NaN; /* MC weight */ + double usec = 0; /* Time of a realisation */ + double time = 0; /* Sampled observation time */ + struct time t0, t1; /* Register the time spent solving a realisation */ + + /* Begin time registration of the realisation */ + time_current(&t0); + + /* Sample observation time */ + time = sample_time(rng, args->time_range); + + /* Run a realisation */ + realis_args.rng = rng; + realis_args.iprim = args->iprim; + realis_args.time = time; + realis_args.picard_order = args->picard_order; + realis_args.side = args->side; + realis_args.irealisation = irealisation; + realis_args.uv[0] = args->uv[0]; +#if SDIS_XD_DIMENSION == 3 + realis_args.uv[1] = args->uv[1]; +#endif + res = XD(boundary_realisation)(scn, &realis_args, &w); + if(res != RES_OK) goto error; + + /* Stop time registration */ + time_sub(&t0, time_current(&t1), &t0); + usec = (double)time_val(&t0, TIME_NSEC) * 0.001; + + /* Update MC weights */ + acc_temp->sum += w; + acc_temp->sum2 += w*w; + acc_temp->count += 1; + acc_time->sum += usec; + acc_time->sum2 += usec*usec; + acc_time->count += 1; + } +exit: + return res; +error: + goto exit; +} + /******************************************************************************* * Local functions ******************************************************************************/ @@ -411,6 +505,207 @@ error: } static res_T +XD(solve_probe_boundary_list) + (struct sdis_scene* scn, + const struct sdis_solve_probe_boundary_list_args* args, + struct sdis_estimator_buffer** out_estim_buf) +{ + /* Time registration */ + struct time time0, time1; + char buf[128]; /* Temporary buffer used to store formated time */ + + /* Device variable */ + struct mem_allocator* allocator = NULL; + + /* Stardis variables */ + struct sdis_estimator_buffer* estim_buf = NULL; + + /* Random Number generator */ + struct ssp_rng_proxy* rng_proxy = NULL; + struct ssp_rng** per_thread_rng = NULL; + + /* Probe variables */ + size_t process_probes[2] = {0, 0}; /* Probes range managed by the process */ + size_t process_nprobes = 0; /* Number of probes managed by the process */ + size_t nprobes = 0; + struct accum* per_probe_acc_temp = NULL; + struct accum* per_probe_acc_time = NULL; + + /* Miscellaneous */ + int32_t* progress = NULL; /* Per process progress bar */ + int is_master_process = 0; + int pcent_progress = 1; /* Percentage requiring progress update */ + int64_t i = 0; + ATOMIC nsolved_probes = 0; + ATOMIC res = RES_OK; + + if(!scn || !out_estim_buf) { res = RES_BAD_ARG; goto error; } + res = check_solve_probe_boundary_list_args(scn->dev, args); + if(res != RES_OK) goto error; + res = XD(scene_check_dimensionality)(scn); + if(res != RES_OK) goto error; + +#ifdef SDIS_ENABLE_MPI + is_master_process = !scn->dev->use_mpi || scn->dev->mpi_rank == 0; +#endif + + allocator = scn->dev->allocator; + + /* Update the progress bar every percent if escape sequences are allowed in + * log messages or only every 10 percent when only plain text is allowed. + * This reduces the number of lines of plain text printed */ + pcent_progress = scn->dev->no_escape_sequence ? 10 : 1; + + /* Create the per threads RNGs */ + res = create_per_thread_rng + (scn->dev, args->rng_state, args->rng_type, &rng_proxy, &per_thread_rng); + if(res != RES_OK) goto error; + + /* Allocate the per process progress status */ + res = alloc_process_progress(scn->dev, &progress); + if(res != RES_OK) goto error; + + /* Synchronise the processes */ + process_barrier(scn->dev); + + /* Define the range of probes to manage in this process */ + process_nprobes = compute_process_index_range + (scn->dev, args->nprobes, process_probes); + + #define PROGRESS_MSG "Solving surface probes: " + print_progress(scn->dev, progress, PROGRESS_MSG); + + /* If there is no work to be done on this process (i.e. no probe to + * calculate), simply print its completion and go straight to the + * synchronization barrier.*/ + if(process_nprobes == 0) { + progress[0] = 100; + print_progress_update(scn->dev, progress, PROGRESS_MSG); + goto post_sync; + } + + /* Allocate the list of accumulators per probe. On the master process, + * allocate a complete list in which the accumulators of all processes will be + * stored. */ + nprobes = is_master_process ? args->nprobes : process_nprobes; + per_probe_acc_temp = MEM_CALLOC(allocator, nprobes, sizeof(*per_probe_acc_temp)); + per_probe_acc_time = MEM_CALLOC(allocator, nprobes, sizeof(*per_probe_acc_time)); + if(!per_probe_acc_temp || !per_probe_acc_time) { + log_err(scn->dev, "Unable to allocate the list of accumulators per probe.\n"); + res = RES_MEM_ERR; + goto error; + } + + /* Begin time registration of the computation */ + time_current(&time0); + + /* Calculation of probe list */ + #pragma omp parallel for schedule(static) + for(i = 0; i < (int64_t)process_nprobes; ++i) { + /* Thread */ + const int ithread = omp_get_thread_num(); /* Thread ID */ + struct ssp_rng* rng = per_thread_rng[ithread]; /* RNG of the thread */ + + /* Probe */ + struct accum* probe_acc_temp = NULL; + struct accum* probe_acc_time = NULL; + const struct sdis_solve_probe_boundary_args* probe_args = NULL; + const size_t iprobe = process_probes[0] + (size_t)i; /* Probe ID */ + + /* Miscellaneous */ + size_t n = 0; /* Number of solved probes */ + int pcent = 0; /* Current progress */ + res_T res_local = RES_OK; + + if(ATOMIC_GET(&res) != RES_OK) continue; /* An error occurred */ + + /* Retrieve the probe arguments */ + probe_args = &args->probes[iprobe]; + + /* Retrieve the probe accumulators */ + probe_acc_temp = &per_probe_acc_temp[i]; + probe_acc_time = &per_probe_acc_time[i]; + + res_local = XD(solve_one_probe_boundary) + (scn, rng, probe_args, probe_acc_temp, probe_acc_time); + if(res_local != RES_OK) { + ATOMIC_SET(&res, res_local); + continue; + } + + /* Update progress */ + n = (size_t)ATOMIC_INCR(&nsolved_probes); + pcent = (int)((double)n * 100.0 / (double)process_nprobes + 0.5/*round*/); + + #pragma omp critical + if(pcent/pcent_progress > progress[0]/pcent_progress) { + progress[0] = pcent; + print_progress_update(scn->dev, progress, PROGRESS_MSG); + } + } + +post_sync: + /* Synchronise processes */ + process_barrier(scn->dev); + + res = gather_res_T(scn->dev, (res_T)res); + if(res != RES_OK) goto error; + + print_progress_completion(scn->dev, progress, PROGRESS_MSG); + #undef PROGRESS_MSG + + /* Report computatio time */ + time_sub(&time0, time_current(&time1), &time0); + time_dump(&time0, TIME_ALL, NULL, buf, sizeof(buf)); + log_info(scn->dev, "%lu surface probes solved in %s.\n", + (unsigned long)args->nprobes, buf); + + /* Gather the RNG proxy sequence IDs and ensure that the RNG proxy state of + * the master process is greater than the RNG proxy state of all other + * processes */ + res = gather_rng_proxy_sequence_id(scn->dev, rng_proxy); + if(res != RES_OK) goto error; + + time_current(&time0); + + /* Gather the list of accumulators */ + #define GATHER_ACCUMS_LIST(Msg, Acc) { \ + res = gather_accumulators_list \ + (scn->dev, Msg, args->nprobes, process_probes, per_probe_acc_##Acc); \ + if(res != RES_OK) goto error; \ + } (void)0 + GATHER_ACCUMS_LIST(MPI_SDIS_MSG_ACCUM_TEMP, temp); + GATHER_ACCUMS_LIST(MPI_SDIS_MSG_ACCUM_TIME, time); + #undef GATHER_ACCUMS_LIST + + time_sub(&time0, time_current(&time1), &time0); + time_dump(&time0, TIME_ALL, NULL, buf, sizeof(buf)); + log_info(scn->dev, "Probes accumulator gathered in %s.\n", buf); + + if(is_master_process) { + res = estimator_buffer_create_from_observable_list_probe_boundary + (scn->dev, rng_proxy, args->probes, per_probe_acc_temp, + per_probe_acc_time, args->nprobes, &estim_buf); + if(res != RES_OK) goto error; + } + +exit: + if(per_thread_rng) release_per_thread_rng(scn->dev, per_thread_rng); + if(rng_proxy) SSP(rng_proxy_ref_put(rng_proxy)); + if(per_probe_acc_temp) MEM_RM(allocator, per_probe_acc_temp); + if(per_probe_acc_time) MEM_RM(allocator, per_probe_acc_time); + if(progress) free_process_progress(scn->dev, progress); + if(out_estim_buf) *out_estim_buf = estim_buf; + return (res_T)res; +error: + if(estim_buf) { + SDIS(estimator_buffer_ref_put(estim_buf)); + estim_buf = NULL; + } + goto exit; +} + +static res_T XD(solve_probe_boundary_flux) (struct sdis_scene* scn, const struct sdis_solve_probe_boundary_flux_args* args, diff --git a/src/test_sdis_draw_external_flux.c b/src/test_sdis_draw_external_flux.c @@ -14,10 +14,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "sdis.h" +#include "test_sdis_mesh.h" #include "test_sdis_utils.h" #include <star/s3dut.h> -#include <rsys/stretchy_array.h> #define IMG_WIDTH 320 #define IMG_HEIGHT 240 @@ -37,80 +37,6 @@ /******************************************************************************* * Geometries ******************************************************************************/ -struct mesh { - double* positions; /* List of double 3 */ - size_t* indices; /* List of size_t 3 */ -}; -#define MESH_NULL__ {NULL, NULL} -static const struct mesh MESH_NULL = MESH_NULL__; - -static INLINE void -mesh_init(struct mesh* mesh) -{ - CHK(mesh); - *mesh = MESH_NULL; -} - -static INLINE void -mesh_release(struct mesh* mesh) -{ - CHK(mesh); - sa_release(mesh->positions); - sa_release(mesh->indices); -} - -/* Number of vertices */ -static INLINE size_t -mesh_nvertices(const struct mesh* mesh) -{ - CHK(mesh); - return sa_size(mesh->positions) / 3/* #coords per vertex */; -} - -/* Number of triangles */ -static INLINE size_t -mesh_ntriangles(const struct mesh* mesh) -{ - CHK(mesh); - return sa_size(mesh->indices) / 3/* #indices per triangle */; -} - -static void -mesh_append - (struct mesh* mesh, - const struct s3dut_mesh_data* mesh_data, - const double in_translate[3]) /* May be NULL */ -{ - double translate[3] = {0, 0, 0}; - double* positions = NULL; - size_t* indices = NULL; - size_t ivert = 0; - size_t i = 0; - CHK(mesh != NULL); - - ivert = mesh_nvertices(mesh); - positions = sa_add(mesh->positions, mesh_data->nvertices*3); - indices = sa_add(mesh->indices, mesh_data->nprimitives*3); - - if(in_translate) { - translate[0] = in_translate[0]; - translate[1] = in_translate[1]; - translate[2] = in_translate[2]; - } - - FOR_EACH(i, 0, mesh_data->nvertices) { - positions[i*3 + 0] = mesh_data->positions[i*3 + 0] + translate[0]; - positions[i*3 + 1] = mesh_data->positions[i*3 + 1] + translate[1]; - positions[i*3 + 2] = mesh_data->positions[i*3 + 2] + translate[2]; - } - - FOR_EACH(i, 0, mesh_data->nprimitives) { - indices[i*3 + 0] = mesh_data->indices[i*3 + 0] + ivert; - indices[i*3 + 1] = mesh_data->indices[i*3 + 1] + ivert; - indices[i*3 + 2] = mesh_data->indices[i*3 + 2] + ivert; - } -} - static void mesh_add_super_shape(struct mesh* mesh) { @@ -125,7 +51,8 @@ mesh_add_super_shape(struct mesh* mesh) f1.A = 1.0; f1.B = 2; f1.M = 3.6; f1.N0 = 1; f1.N1 = 2; f1.N2 = 0.7; OK(s3dut_create_super_shape(NULL, &f0, &f1, radius, nslices, nslices/2, &sshape)); OK(s3dut_mesh_get_data(sshape, &sshape_data)); - mesh_append(mesh, &sshape_data, NULL); + mesh_append(mesh, sshape_data.positions, sshape_data.nvertices, + sshape_data.indices, sshape_data.nprimitives, NULL); OK(s3dut_mesh_ref_put(sshape)); } @@ -140,7 +67,8 @@ mesh_add_ground(struct mesh* mesh) OK(s3dut_create_cuboid(NULL, 20, 20, DEPTH, &ground)); OK(s3dut_mesh_get_data(ground, &ground_data)); - mesh_append(mesh, &ground_data, translate); + mesh_append(mesh, ground_data.positions, ground_data.nvertices, + ground_data.indices, ground_data.nprimitives, translate); OK(s3dut_mesh_ref_put(ground)); #undef DEPTH } diff --git a/src/test_sdis_mesh.h b/src/test_sdis_mesh.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2016-2023 |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/>. */ + +#ifndef TEST_SDIS_MESH_H +#define TEST_SDIS_MESH_H + +#include <rsys/rsys.h> +#include <rsys/stretchy_array.h> + +struct mesh { + double* positions; /* List of double 3 */ + size_t* indices; /* List of size_t 3 */ +}; +#define MESH_NULL__ {NULL, NULL} +static const struct mesh MESH_NULL = MESH_NULL__; + +static INLINE void +mesh_init(struct mesh* mesh) +{ + CHK(mesh); + *mesh = MESH_NULL; +} + +static INLINE void +mesh_release(struct mesh* mesh) +{ + CHK(mesh); + sa_release(mesh->positions); + sa_release(mesh->indices); +} + +/* Number of vertices */ +static INLINE size_t +mesh_nvertices(const struct mesh* mesh) +{ + CHK(mesh); + return sa_size(mesh->positions) / 3/* #coords per vertex */; +} + +/* Number of triangles */ +static INLINE size_t +mesh_ntriangles(const struct mesh* mesh) +{ + CHK(mesh); + return sa_size(mesh->indices) / 3/* #indices per triangle */; +} + +static INLINE void +mesh_append + (struct mesh* mesh, + const double* in_positions, + const size_t in_nvertices, + const size_t* in_indices, + const size_t in_ntriangles, + const double in_translate[3]) /* May be NULL */ +{ + double translate[3] = {0, 0, 0}; + double* positions = NULL; + size_t* indices = NULL; + size_t ivert = 0; + size_t i = 0; + CHK(mesh != NULL); + + ivert = mesh_nvertices(mesh); + positions = sa_add(mesh->positions, in_nvertices*3); + indices = sa_add(mesh->indices, in_ntriangles*3); + + if(in_translate) { + translate[0] = in_translate[0]; + translate[1] = in_translate[1]; + translate[2] = in_translate[2]; + } + + FOR_EACH(i, 0, in_nvertices) { + positions[i*3 + 0] = in_positions[i*3 + 0] + translate[0]; + positions[i*3 + 1] = in_positions[i*3 + 1] + translate[1]; + positions[i*3 + 2] = in_positions[i*3 + 2] + translate[2]; + } + + FOR_EACH(i, 0, in_ntriangles) { + indices[i*3 + 0] = in_indices[i*3 + 0] + ivert; + indices[i*3 + 1] = in_indices[i*3 + 1] + ivert; + indices[i*3 + 2] = in_indices[i*3 + 2] + ivert; + } +} + +static INLINE void +mesh_dump(const struct mesh* mesh, FILE* stream) +{ + size_t i, n; + CHK(mesh != NULL); + + n = mesh_nvertices(mesh); + FOR_EACH(i, 0, n) { + fprintf(stream, "v %g %g %g\n", SPLIT3(mesh->positions+i*3)); + } + + n = mesh_ntriangles(mesh); + FOR_EACH(i, 0, n) { + fprintf(stream, "f %lu %lu %lu\n", + (unsigned long)(mesh->indices[i*3+0] + 1), + (unsigned long)(mesh->indices[i*3+1] + 1), + (unsigned long)(mesh->indices[i*3+2] + 1)); + } + fflush(stream); +} + +#endif /* TEST_SDIS_MESH_H */ diff --git a/src/test_sdis_solve_probe_boundary_list.c b/src/test_sdis_solve_probe_boundary_list.c @@ -0,0 +1,488 @@ +/* Copyright (C) 2016-2023 |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 "sdis.h" + +#include "test_sdis_utils.h" +#include "test_sdis_mesh.h" + +#include <star/s3dut.h> +#include <rsys/math.h> + +#include <math.h> + +/* + * The system is a trilinear profile of the temperature at steady state, i.e. at + * each point of the system we can calculate the temperature analytically. Two + * forms are immersed in this temperature field: a super shape and a sphere + * included in the super shape. On the Monte Carlo side, the temperature is + * unknown everywhere except on the surface of the super shape whose + * temperature is defined from the aformentionned trilinear profile. We will + * estimate the temperature at the sphere boundary at several probe points. We + * should find by Monte Carlo the temperature of the trilinear profile at the + * position of the probe. It's the test. + * /\ <-- T(x,y,z) + * ___/ \___ + * T(z) \ __ / + * | T(y) \ / \ / + * |/ T=? *__/ \ + * o--- T(x) /_ __ _\ + * \/ \/ + */ + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static double +trilinear_profile(const double pos[3]) +{ + /* Range in X, Y and Z in which the trilinear profile is defined */ + const double lower = -4; + const double upper = +4; + + /* Upper temperature limit in X, Y and Z [K]. Lower temperature limit is + * implicitly 0 */ + const double a = 333; /* Upper temperature limit in X [K] */ + const double b = 432; /* Upper temperature limit in Y [K] */ + const double c = 579; /* Upper temperature limit in Z [K] */ + + double x, y, z; + + /* Check pre-conditions */ + CHK(pos); + CHK(lower <= pos[0] && pos[0] <= upper); + CHK(lower <= pos[1] && pos[1] <= upper); + CHK(lower <= pos[2] && pos[2] <= upper); + + x = (pos[0] - lower) / (upper - lower); + y = (pos[1] - lower) / (upper - lower); + z = (pos[2] - lower) / (upper - lower); + return a*x + b*y + c*z; +} + +static INLINE float +rand_canonic(void) +{ + return (float)rand() / (float)((unsigned)RAND_MAX+1); +} + +static void +sample_sphere(double pos[3]) +{ + const double phi = rand_canonic() * 2 * PI; + const double v = rand_canonic(); + const double cos_theta = 1 - 2 * v; + const double sin_theta = 2 * sqrt(v * (1 - v)); + pos[0] = cos(phi) * sin_theta; + pos[1] = sin(phi) * sin_theta; + pos[2] = cos_theta; +} + +static INLINE void +check_intersection + (const double val0, + const double eps0, + const double val1, + const double eps1) +{ + double interval0[2], interval1[2]; + double intersection[2]; + interval0[0] = val0 - eps0; + interval0[1] = val0 + eps0; + interval1[0] = val1 - eps1; + interval1[1] = val1 + eps1; + intersection[0] = MMAX(interval0[0], interval1[0]); + intersection[1] = MMIN(interval0[1], interval1[1]); + CHK(intersection[0] <= intersection[1]); +} + +static void +mesh_add_super_shape(struct mesh* mesh) +{ + struct s3dut_mesh* sshape = NULL; + struct s3dut_mesh_data sshape_data; + struct s3dut_super_formula f0 = S3DUT_SUPER_FORMULA_NULL; + struct s3dut_super_formula f1 = S3DUT_SUPER_FORMULA_NULL; + const double radius = 2; + const unsigned nslices = 256; + + f0.A = 1.5; f0.B = 1; f0.M = 11.0; f0.N0 = 1; f0.N1 = 1; f0.N2 = 2.0; + f1.A = 1.0; f1.B = 2; f1.M = 3.6; f1.N0 = 1; f1.N1 = 2; f1.N2 = 0.7; + OK(s3dut_create_super_shape(NULL, &f0, &f1, radius, nslices, nslices/2, &sshape)); + OK(s3dut_mesh_get_data(sshape, &sshape_data)); + mesh_append(mesh, sshape_data.positions, sshape_data.nvertices, + sshape_data.indices, sshape_data.nprimitives, NULL); + OK(s3dut_mesh_ref_put(sshape)); +} + +static void +mesh_add_sphere(struct mesh* mesh) +{ + struct s3dut_mesh* sphere = NULL; + struct s3dut_mesh_data sphere_data; + const double radius = 1; + const unsigned nslices = 128; + + OK(s3dut_create_sphere(NULL, radius, nslices, nslices/2, &sphere)); + OK(s3dut_mesh_get_data(sphere, &sphere_data)); + mesh_append(mesh, sphere_data.positions, sphere_data.nvertices, + sphere_data.indices, sphere_data.nprimitives, NULL); + OK(s3dut_mesh_ref_put(sphere)); +} + +/******************************************************************************* + * Solid, i.e. medium of super shape and sphere + ******************************************************************************/ +#define SOLID_PROP(Prop, Val) \ + static double \ + solid_get_##Prop \ + (const struct sdis_rwalk_vertex* vtx, \ + struct sdis_data* data) \ + { \ + (void)vtx, (void)data; /* Avoid the "unused variable" warning */ \ + return Val; \ + } +SOLID_PROP(calorific_capacity, 500.0) /* [J/K/Kg] */ +SOLID_PROP(thermal_conductivity, 25.0) /* [W/m/K] */ +SOLID_PROP(volumic_mass, 7500.0) /* [kg/m^3] */ +SOLID_PROP(temperature, -1/*<=> unknown*/) /* [K] */ +SOLID_PROP(delta, 1.0/20.0) /* [m] */ + +static struct sdis_medium* +create_solid(struct sdis_device* sdis) +{ + struct sdis_solid_shader shader = SDIS_SOLID_SHADER_NULL; + struct sdis_medium* solid = NULL; + + shader.calorific_capacity = solid_get_calorific_capacity; + shader.thermal_conductivity = solid_get_thermal_conductivity; + shader.volumic_mass = solid_get_volumic_mass; + shader.delta = solid_get_delta; + shader.temperature = solid_get_temperature; + OK(sdis_solid_create(sdis, &shader, NULL, &solid)); + return solid; +} + +/******************************************************************************* + * Dummy environment, i.e. environment surrounding the super shape. It is + * defined only for Stardis compliance: in Stardis, an interface must divide 2 + * media. + ******************************************************************************/ +static struct sdis_medium* +create_dummy(struct sdis_device* sdis) +{ + struct sdis_fluid_shader shader = SDIS_FLUID_SHADER_NULL; + struct sdis_medium* dummy = NULL; + + shader.calorific_capacity = dummy_medium_getter; + shader.volumic_mass = dummy_medium_getter; + shader.temperature = dummy_medium_getter; + OK(sdis_fluid_create(sdis, &shader, NULL, &dummy)); + return dummy; +} + +/******************************************************************************* + * Interfaces + ******************************************************************************/ +static double +interface_get_temperature + (const struct sdis_interface_fragment* frag, + struct sdis_data* data) +{ + (void)data; /* Avoid the "unused variable" warning */ + return trilinear_profile(frag->P); +} + +static struct sdis_interface* +create_interface + (struct sdis_device* sdis, + struct sdis_medium* front, + struct sdis_medium* back) +{ + struct sdis_interface* interf = NULL; + struct sdis_interface_shader shader = SDIS_INTERFACE_SHADER_NULL; + + /* Fluid/solid interface: fix the temperature to the temperature profile */ + if(sdis_medium_get_type(front) != sdis_medium_get_type(back)) { + shader.front.temperature = interface_get_temperature; + shader.back.temperature = interface_get_temperature; + } + + OK(sdis_interface_create(sdis, front, back, &shader, NULL, &interf)); + return interf; +} + +/******************************************************************************* + * Scene, i.e. the system to simulate + ******************************************************************************/ +struct scene_context { + const struct mesh* mesh; + size_t sshape_end_id; /* Last triangle index of the super shape */ + struct sdis_interface* sshape; + struct sdis_interface* sphere; +}; +#define SCENE_CONTEXT_NULL__ {NULL, 0, 0, 0} +static const struct scene_context SCENE_CONTEXT_NULL = SCENE_CONTEXT_NULL__; + +static void +scene_get_indices(const size_t itri, size_t ids[3], void* ctx) +{ + struct scene_context* context = ctx; + CHK(ids && context && itri < mesh_ntriangles(context->mesh)); + /* Flip the indices to ensure that the normal points into the super shape */ + ids[0] = (unsigned)context->mesh->indices[itri*3+0]; + ids[1] = (unsigned)context->mesh->indices[itri*3+2]; + ids[2] = (unsigned)context->mesh->indices[itri*3+1]; +} + +static void +scene_get_interface(const size_t itri, struct sdis_interface** interf, void* ctx) +{ + struct scene_context* context = ctx; + CHK(interf && context && itri < mesh_ntriangles(context->mesh)); + if(itri < context->sshape_end_id) { + *interf = context->sshape; + } else { + *interf = context->sphere; + } +} + +static void +scene_get_position(const size_t ivert, double pos[3], void* ctx) +{ + struct scene_context* context = ctx; + CHK(pos && context && ivert < mesh_nvertices(context->mesh)); + pos[0] = context->mesh->positions[ivert*3+0]; + pos[1] = context->mesh->positions[ivert*3+1]; + pos[2] = context->mesh->positions[ivert*3+2]; +} + +static struct sdis_scene* +create_scene(struct sdis_device* sdis, struct scene_context* ctx) + +{ + struct sdis_scene* scn = NULL; + struct sdis_scene_create_args scn_args = SDIS_SCENE_CREATE_ARGS_DEFAULT; + + scn_args.get_indices = scene_get_indices; + scn_args.get_interface = scene_get_interface; + scn_args.get_position = scene_get_position; + scn_args.nprimitives = mesh_ntriangles(ctx->mesh); + scn_args.nvertices = mesh_nvertices(ctx->mesh); + scn_args.context = ctx; + OK(sdis_scene_create(sdis, &scn_args, &scn)); + return scn; +} + +/******************************************************************************* + * Validations + ******************************************************************************/ +static void +check_probe_boundary_list_api + (struct sdis_scene* scn, + const struct sdis_solve_probe_boundary_list_args* in_args) +{ + struct sdis_solve_probe_boundary_list_args args = *in_args; + struct sdis_estimator_buffer* estim_buf = NULL; + + /* Check API */ + BA(sdis_solve_probe_boundary_list(NULL, &args, &estim_buf)); + BA(sdis_solve_probe_boundary_list(scn, NULL, &estim_buf)); + BA(sdis_solve_probe_boundary_list(scn, &args, NULL)); + args.nprobes = 0; + BA(sdis_solve_probe_boundary_list(scn, &args, &estim_buf)); + args.nprobes = in_args->nprobes; + args.probes = NULL; + BA(sdis_solve_probe_boundary_list(scn, &args, &estim_buf)); +} + +/* Check the estimators against the analytical solution */ +static void +check_estimator_buffer + (const struct sdis_estimator_buffer* estim_buf, + struct sdis_scene* scn, + const struct sdis_solve_probe_boundary_list_args* args) +{ + struct sdis_mc T = SDIS_MC_NULL; + size_t iprobe = 0; + + /* Variables used to check the global estimation results */ + size_t total_nrealisations = 0; + size_t total_nrealisations_ref = 0; + size_t total_nfailures = 0; + size_t total_nfailures_ref = 0; + double sum = 0; /* Global sum of weights */ + double sum2 = 0; /* Global sum of squared weights */ + double N = 0; /* Number of (successful) realisations */ + double E = 0; /* Expected value */ + double V = 0; /* Variance */ + double SE = 0; /* Standard Error */ + + /* Check the results */ + FOR_EACH(iprobe, 0, args->nprobes) { + const struct sdis_estimator* estimator = NULL; + size_t probe_nrealisations = 0; + size_t probe_nfailures = 0; + double pos[3] = {0,0,0}; + double probe_sum = 0; + double probe_sum2 = 0; + double ref = 0; + + OK(sdis_scene_get_boundary_position(scn, args->probes[iprobe].iprim, + args->probes[iprobe].uv, pos)); + + /* Fetch result */ + OK(sdis_estimator_buffer_at(estim_buf, iprobe, 0, &estimator)); + OK(sdis_estimator_get_temperature(estimator, &T)); + OK(sdis_estimator_get_realisation_count(estimator, &probe_nrealisations)); + OK(sdis_estimator_get_failure_count(estimator, &probe_nfailures)); + + /* Check probe estimation */ + ref = trilinear_profile(pos); + printf("T(%g, %g, %g) = %g ~ %g +/- %g\n", SPLIT3(pos), ref, T.E, T.SE); + CHK(eq_eps(ref, T.E, 3*T.SE)); + + /* Check miscellaneous results */ + CHK(probe_nrealisations == args->probes[iprobe].nrealisations); + + /* Compute the sum of weights and sum of squared weights of the probe */ + probe_sum = T.E * (double)probe_nrealisations; + probe_sum2 = (T.V + T.E*T.E) * (double)probe_nrealisations; + + /* Update the global estimation */ + total_nrealisations_ref += probe_nrealisations; + total_nfailures_ref += probe_nfailures; + sum += probe_sum; + sum2 += probe_sum2; + } + + /* Check the overall estimate of the estimate buffer. This estimate is not + * really a result expected by the caller since the probes are all + * independent. But to be consistent with the estimate buffer API, we need to + * provide it. And so we check its validity */ + OK(sdis_estimator_buffer_get_temperature(estim_buf, &T)); + OK(sdis_estimator_buffer_get_realisation_count(estim_buf, &total_nrealisations)); + OK(sdis_estimator_buffer_get_failure_count(estim_buf, &total_nfailures)); + + CHK(total_nrealisations == total_nrealisations_ref); + CHK(total_nfailures == total_nfailures_ref); + + N = (double)total_nrealisations_ref; + E = sum / N; + V = sum2 / N - E*E; + SE = sqrt(V/N); + check_intersection(E, SE, T.E, T.SE); +} + +static void +check_probe_boundary_list(struct sdis_scene* scn, const int is_master_process) +{ + #define NPROBES 10 + + /* Estimations */ + struct sdis_estimator_buffer* estim_buf = NULL; + + /* Probe variables */ + struct sdis_solve_probe_boundary_args probes[NPROBES]; + struct sdis_solve_probe_boundary_list_args args = + SDIS_SOLVE_PROBE_BOUNDARY_LIST_ARGS_DEFAULT; + size_t iprobe; + + (void)is_master_process; + + /* Setup the list of probes to calculate */ + args.probes = probes; + args.nprobes = NPROBES; + FOR_EACH(iprobe, 0, NPROBES) { + double pos[3] = {0, 0, 0}; + sample_sphere(pos); + + probes[iprobe] = SDIS_SOLVE_PROBE_BOUNDARY_ARGS_DEFAULT; + probes[iprobe].nrealisations = 10000; + probes[iprobe].side = SDIS_FRONT; + OK(sdis_scene_find_closest_point + (scn, pos, INF, &probes[iprobe].iprim, probes[iprobe].uv)); + } + + check_probe_boundary_list_api(scn, &args); + + /* Solve the probes */ + OK(sdis_solve_probe_boundary_list(scn, &args, &estim_buf)); + if(!is_master_process) { + CHK(estim_buf == NULL); + } else { + check_estimator_buffer(estim_buf, scn, &args); + OK(sdis_estimator_buffer_ref_put(estim_buf)); + } + + #undef NPROBES +} + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(int argc, char** argv) +{ + /* Stardis */ + struct sdis_device* dev = NULL; + struct sdis_interface* solid_fluid = NULL; + struct sdis_interface* solid_solid = NULL; + struct sdis_medium* fluid = NULL; + struct sdis_medium* solid = NULL; + struct sdis_scene* scn = NULL; + + /* Miscellaneous */ + struct scene_context ctx = SCENE_CONTEXT_NULL; + struct mesh mesh = MESH_NULL; + size_t sshape_end_id = 0; /* Last index of the super shape */ + int is_master_process = 1; + (void)argc, (void)argv; + + create_default_device(&argc, &argv, &is_master_process, &dev); + + /* Setup the mesh */ + mesh_init(&mesh); + mesh_add_super_shape(&mesh); + sshape_end_id = mesh_ntriangles(&mesh); + mesh_add_sphere(&mesh); + + /* Setup physical properties */ + fluid = create_dummy(dev); + solid = create_solid(dev); + solid_fluid = create_interface(dev, solid, fluid); + solid_solid = create_interface(dev, solid, solid); + + /* Create the scene */ + ctx.mesh = &mesh; + ctx.sshape_end_id = sshape_end_id; + ctx.sshape = solid_fluid; + ctx.sphere = solid_solid; + scn = create_scene(dev, &ctx); + + check_probe_boundary_list(scn, is_master_process); + + mesh_release(&mesh); + OK(sdis_interface_ref_put(solid_fluid)); + OK(sdis_interface_ref_put(solid_solid)); + OK(sdis_medium_ref_put(fluid)); + OK(sdis_medium_ref_put(solid)); + OK(sdis_scene_ref_put(scn)); + + free_default_device(dev); + + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_sdis_utils.c b/src/test_sdis_utils.c @@ -498,4 +498,3 @@ check_green_serialization OK(sdis_estimator_ref_put(e2)); OK(sdis_green_function_ref_put(green2)); } -