stardis-solver

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

commit 67b1bd0dbe0ae288b355ae37c127fc3ee890f572
parent c18be649d7ac8fda4d2ca2cc16989901179c7a5a
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 21 Dec 2017 10:45:28 +0100

Implement the sdis_solve_probe_temperature function

Diffstat:
Mcmake/CMakeLists.txt | 8+++++---
Msrc/sdis.h | 1+
Msrc/sdis_medium_c.h | 18+++++++++---------
Asrc/sdis_solve_probe.c | 532+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 547 insertions(+), 12 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -25,6 +25,7 @@ option(NO_TEST "Do not build tests" OFF) ################################################################################ find_package(RCMake 0.3 REQUIRED) find_package(Star3D 0.4 REQUIRED) +find_package(StarSP 0.5 REQUIRED) find_package(RSys 0.6 REQUIRED) find_package(OpenMP 1.2 REQUIRED) @@ -32,7 +33,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) include(rcmake) include(rcmake_runtime) -rcmake_append_runtime_dirs(_runtime_dirs RSys) +rcmake_append_runtime_dirs(_runtime_dirs RSys Star3D StarSP) ################################################################################ # Configure and define targets @@ -48,7 +49,8 @@ set(SDIS_FILES_SRC sdis_estimator.c sdis_interface.c sdis_medium.c - sdis_scene.c) + sdis_scene.c + sdis_solve_probe.c) set(SDIS_FILES_INC_API sdis.h) @@ -72,7 +74,7 @@ add_library(sdis SHARED ${SDIS_FILES_SRC} ${SDIS_FILES_INC} ${SDIS_FILES_INC_API}) -target_link_libraries(sdis RSys Star3D) +target_link_libraries(sdis RSys Star3D StarSP) set_target_properties(sdis PROPERTIES DEFINE_SYMBOL SDIS_SHARED_BUILD diff --git a/src/sdis.h b/src/sdis.h @@ -309,6 +309,7 @@ sdis_estimator_get_temperature SDIS_API res_T sdis_solve_probe_temperature (struct sdis_scene* scn, + const size_t nrealisations, const double position[3], const double time, const double fp_to_meter,/* Scale factor from floating point unit to meter */ diff --git a/src/sdis_medium_c.h b/src/sdis_medium_c.h @@ -36,7 +36,7 @@ struct sdis_medium { ******************************************************************************/ static INLINE double fluid_get_calorific_capacity - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double cp = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_FLUID); @@ -46,7 +46,7 @@ fluid_get_calorific_capacity static INLINE double fluid_get_volumic_mass - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double rho = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_FLUID); @@ -56,7 +56,7 @@ fluid_get_volumic_mass static INLINE double fluid_get_temperature - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double T = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_FLUID); @@ -69,7 +69,7 @@ fluid_get_temperature ******************************************************************************/ static INLINE double solid_get_calorific_capacity - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double cp = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_SOLID); @@ -79,7 +79,7 @@ solid_get_calorific_capacity static INLINE double solid_get_thermal_conductivity - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double lambda = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_SOLID); @@ -89,7 +89,7 @@ solid_get_thermal_conductivity static INLINE double solid_get_volumic_mass - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double rho = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_SOLID); @@ -99,7 +99,7 @@ solid_get_volumic_mass static INLINE double solid_get_delta - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double delta = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_SOLID); @@ -109,7 +109,7 @@ solid_get_delta static INLINE double solid_get_delta_boundary - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double delta_bound = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_SOLID); @@ -119,7 +119,7 @@ solid_get_delta_boundary static INLINE double solid_get_temperature - (struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) + (const struct sdis_medium* mdm, const struct sdis_rwalk_vertex* vtx) { double T = -1; ASSERT(mdm && mdm->type == SDIS_MEDIUM_SOLID); diff --git a/src/sdis_solve_probe.c b/src/sdis_solve_probe.c @@ -0,0 +1,532 @@ +/* Copyright (C) |Meso|Star> 2016-2017 (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 "sdis_device_c.h" +#include "sdis_estimator_c.h" +#include "sdis_interface_c.h" +#include "sdis_medium_c.h" +#include "sdis_scene_c.h" + +#include <rsys/double2.h> +#include <rsys/double3.h> +#include <rsys/float3.h> +#include <rsys/stretchy_array.h> + +#include <star/s3d.h> +#include <star/ssp.h> + +/* Emperical scale factor to apply to the upper bound of the ray range in order + * to handle numerical imprecisions */ +#define RAY_RANGE_MAX_SCALE 1.0001f + +/* Current state of the random walk */ +struct rwalk { + struct sdis_rwalk_vertex vtx; /* Position and time of the Random walk */ + const struct sdis_medium* mdm; /* Medium in which the random walk lies */ + struct s3d_hit hit; /* Hit of the random walk */ +}; +static const struct rwalk RWALK_NULL = { + SDIS_RWALK_VERTEX_NULL__, NULL, S3D_HIT_NULL__ +}; + +struct temperature { + res_T (*func)/* Next function to invoke in order to compute the temperature */ + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* temp); + double value; /* Current value of the temperature */ +}; +static const struct temperature TEMPERATURE_NULL = { NULL, -1 }; + +static res_T +boundary_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T); + +static res_T +solid_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T); + +static res_T +fluid_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T); + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static FINLINE void +move_pos(double pos[3], const float dir[3], const float delta) +{ + ASSERT(pos && dir); + pos[0] += dir[0] * delta; + pos[1] += dir[1] * delta; + pos[2] += dir[2] * delta; +} + +/* Check that the interface fragment is consistent with the current state of + * the random walk */ +static INLINE int +check_rwalk_fragment_consistency + (const struct rwalk* rwalk, + const struct sdis_interface_fragment* frag) +{ + double N[3]; + double uv[2]; + ASSERT(rwalk && frag); + d3_normalize(N, d3_set_f3(N, rwalk->hit.normal)); + d2_set_f2(uv, rwalk->hit.uv); + return !S3D_HIT_NONE(&rwalk->hit) + && d3_eq_eps(rwalk->vtx.P, frag->P, 1.e-6) + && eq_eps(rwalk->vtx.time, frag->time, 1.e-6) + && d3_eq_eps(N, frag->Ng, 1.e-6) + && d2_eq_eps(uv, frag->uv, 1.e-6); +} + +res_T +fluid_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T) +{ + double tmp; + (void)rng, (void)fp_to_meter; + ASSERT(scn && fp_to_meter > 0 && rwalk && rng && T); + ASSERT(rwalk->mdm->type == SDIS_MEDIUM_FLUID); + + tmp = fluid_get_temperature(rwalk->mdm, &rwalk->vtx); + if(tmp < 0) { + log_err(scn->dev, "%s: unknown fluid temperature is not supported.\n", + FUNC_NAME); + return RES_BAD_OP; + } + T->value += tmp; + return RES_OK; +} + +static void +solid_solid_boundary_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + const struct sdis_interface_fragment* frag, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T) +{ + const struct sdis_interface* interface = NULL; + const struct sdis_medium* solid_front = NULL; + const struct sdis_medium* solid_back = NULL; + double lambda_front, lambda_back; + double delta_front_boundary, delta_back_boundary; + double delta_front_boundary_meter, delta_back_boundary_meter; + double delta_boundary; + double proba; + double tmp; + double r; + float pos[3], dir[3], range[2]; + ASSERT(scn && fp_to_meter > 0 && frag && rwalk && rng && T); + ASSERT(check_rwalk_fragment_consistency(rwalk, frag)); + (void)frag; + + /* Retrieve the current boundary media */ + interface = scene_get_interface(scn, rwalk->hit.prim.prim_id); + solid_front = interface_get_medium(interface, SDIS_FRONT); + solid_back = interface_get_medium(interface, SDIS_BACK); + ASSERT(solid_front->type == SDIS_MEDIUM_SOLID); + ASSERT(solid_back->type == SDIS_MEDIUM_SOLID); + + /* Fetch the properties of the media */ + lambda_front = solid_get_thermal_conductivity(solid_front, &rwalk->vtx); + lambda_back = solid_get_thermal_conductivity(solid_back, &rwalk->vtx); + delta_front_boundary = solid_get_delta_boundary(solid_front, &rwalk->vtx); + delta_back_boundary = solid_get_delta_boundary(solid_back, &rwalk->vtx); + + /* Convert the delta boundary from floating point units to meters */ + delta_front_boundary_meter = fp_to_meter * delta_front_boundary; + delta_back_boundary_meter = fp_to_meter * delta_back_boundary; + + /* Compute the proba to switch in solid0 or in solid1 */ + tmp = lambda_front / delta_front_boundary_meter; + proba = tmp / (tmp + lambda_back / delta_back_boundary_meter); + + r = ssp_rng_canonical(rng); + f3_normalize(dir, rwalk->hit.normal); + if(r < proba) { /* Reinject in front */ + delta_boundary = delta_front_boundary; + rwalk->mdm = solid_front; + } else { /* Reinject in back */ + delta_boundary = delta_back_boundary; + f3_minus(dir, dir); + rwalk->mdm = solid_back; + } + + /* "Reinject" the path into the solid along the surface normal. */ + f3_set_d3(pos, rwalk->vtx.P); + range[0] = 0, range[1] = (float)delta_boundary*RAY_RANGE_MAX_SCALE; + S3D(scene_view_trace_ray + (scn->s3d_view, pos, dir, range, &rwalk->hit, &rwalk->hit)); + if(!S3D_HIT_NONE(&rwalk->hit)) delta_boundary = rwalk->hit.distance * 0.5; + move_pos(rwalk->vtx.P, dir, (float)delta_boundary); + + /* Switch in solid random walk */ + T->func = solid_temperature; +} + +static void +solid_fluid_boundary_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + const struct sdis_interface_fragment* frag, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T) +{ + const struct sdis_interface* interface = NULL; + const struct sdis_medium* mdm_front = NULL; + const struct sdis_medium* mdm_back = NULL; + const struct sdis_medium* solid = NULL; + const struct sdis_medium* fluid = NULL; + double hc; + double lambda; + double fluid_proba; + double delta_boundary; + double r; + double tmp; + float dir[3], pos[3], range[2]; + + ASSERT(scn && fp_to_meter > 0 && rwalk && rng && T); + ASSERT(check_rwalk_fragment_consistency(rwalk, frag)); + + /* Retrieve the solid and the fluid split by the boundary */ + interface = scene_get_interface(scn, rwalk->hit.prim.prim_id); + mdm_front = interface_get_medium(interface, SDIS_FRONT); + mdm_back = interface_get_medium(interface, SDIS_BACK); + ASSERT(mdm_front->type != mdm_back->type); + if(mdm_front->type == SDIS_MEDIUM_SOLID) { + solid = mdm_front; + fluid = mdm_back; + } else { + solid = mdm_back; + fluid = mdm_front; + } + + /* Fetch the solid properties */ + lambda = solid_get_thermal_conductivity(solid, &rwalk->vtx); + delta_boundary = solid_get_delta_boundary(solid, &rwalk->vtx); + hc = interface_get_convection_coef(interface, frag); + + /* Compute the probas to switch in solid or fluid random walk */ + tmp = lambda / (delta_boundary*fp_to_meter); + fluid_proba = hc / (tmp + hc); + + r = ssp_rng_canonical(rng); + if(r < fluid_proba) { /* Switch to fluid random walk */ + rwalk->mdm = fluid; + T->func = fluid_temperature; + } else { /* Solid random walk */ + rwalk->mdm = solid; + f3_normalize(dir, rwalk->hit.normal); + if(solid == mdm_back) f3_minus(dir, dir); + + /* "Reinject" the random walk into the solid */ + range[0] = 0, range[1] = (float)delta_boundary*RAY_RANGE_MAX_SCALE; + S3D(scene_view_trace_ray + (scn->s3d_view, pos, dir, range, &rwalk->hit, &rwalk->hit)); + if(!S3D_HIT_NONE(&rwalk->hit)) delta_boundary = rwalk->hit.distance * 0.5; + move_pos(rwalk->vtx.P, dir, (float)delta_boundary); + + /* Switch in solid random walk */ + T->func = solid_temperature; + } +} + +res_T +boundary_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T) +{ + struct sdis_interface_fragment frag = SDIS_INTERFACE_FRAGMENT_NULL; + const struct sdis_interface* interface = NULL; + const struct sdis_medium* mdm_front = NULL; + const struct sdis_medium* mdm_back = NULL; + double tmp; + ASSERT(scn && fp_to_meter > 0 && rwalk && rng && T); + ASSERT(!S3D_HIT_NONE(&rwalk->hit)); + + setup_interface_fragment(&frag, &rwalk->vtx, &rwalk->hit); + + /* Retrieve the current interface */ + interface = scene_get_interface(scn, rwalk->hit.prim.prim_id); + + /* Check if the boundary condition is known */ + tmp = interface_get_temperature(interface, &frag); + if(tmp >= 0) { + T->value += tmp; + return RES_OK; + } + + mdm_front = interface_get_medium(interface, SDIS_FRONT); + mdm_back = interface_get_medium(interface, SDIS_BACK); + + if(mdm_front->type == mdm_back->type) { + solid_solid_boundary_temperature(scn, fp_to_meter, &frag, rwalk, rng, T); + } else { + solid_fluid_boundary_temperature(scn, fp_to_meter, &frag, rwalk, rng, T); + } + return RES_OK; +} + +res_T +solid_temperature + (const struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T) +{ + double position_start[3]; + const struct sdis_medium* mdm; + ASSERT(scn && fp_to_meter > 0 && rwalk && rng && T); + ASSERT(rwalk->mdm->type == SDIS_MEDIUM_SOLID); + + /* Check the random walk consistency */ + CHK(scene_get_medium(scn, rwalk->vtx.P, &mdm) == RES_OK); + if(mdm != rwalk->mdm) { + log_err(scn->dev, "%s: invalid solid random walk. " + "Unexpected medium at {%g, %g, %g}.\n", + FUNC_NAME, SPLIT3(rwalk->vtx.P)); + return RES_BAD_ARG; + } + /* Save the submitted position */ + d3_set(position_start, rwalk->vtx.P); + + do { /* Solid random walk */ + struct s3d_hit hit0, hit1; + double lambda; /* Thermal conductivity */ + double rho; /* Volumic mass */ + double cp; /* Calorific capacity */ + double tau, mu; + double tmp; + float delta, delta_solid; /* Random walk numerical parameter */ + float range[2]; + float dir0[3], dir1[3]; + float org[3]; + + /* Check the limit condition */ + tmp = solid_get_temperature(mdm, &rwalk->vtx); + if(tmp >= 0) { + T->value += tmp; + return RES_OK; + } + + /* Fetch solid properties */ + delta_solid = (float)solid_get_delta(mdm, &rwalk->vtx); + lambda = solid_get_thermal_conductivity(mdm, &rwalk->vtx); + rho = solid_get_volumic_mass(mdm, &rwalk->vtx); + cp = solid_get_calorific_capacity(mdm, &rwalk->vtx); + + /* Sample a direction around 4PI */ + ssp_ran_sphere_uniform_float(rng, dir0, NULL); + + /* Trace a ray along the sampled direction and its opposite to check if a + * surface is hit in [0, delta_solid]. */ + f3_set_d3(org, rwalk->vtx.P); + f3_minus(dir1, dir0); + hit0 = hit1 = S3D_HIT_NULL; + range[0] = 0.f, range[1] = delta_solid*RAY_RANGE_MAX_SCALE; + S3D(scene_view_trace_ray(scn->s3d_view, org, dir0, range, NULL, &hit0)); + S3D(scene_view_trace_ray(scn->s3d_view, org, dir1, range, NULL, &hit1)); + + if(S3D_HIT_NONE(&hit0) && S3D_HIT_NONE(&hit1)) { + /* Hit nothing: move along dir0 of the original delta */ + delta = delta_solid; + } else { + /* Hit something: move along dir0 of the minimum hit distance */ + delta = MMIN(hit0.distance, hit1.distance); + } + + /* Sample the time */ + mu = (6 * lambda) / (rho * cp * delta * fp_to_meter * delta * fp_to_meter ); + tau = ssp_ran_exp(rng, mu); + rwalk->vtx.time -= tau; + + /* Check the initial condition */ + tmp = solid_get_temperature(mdm, &rwalk->vtx); + if(tmp >= 0) { + T->value += tmp; + return RES_OK; + } + + /* Define if the random walk hits something along dir0 */ + rwalk->hit = hit0.distance > delta ? S3D_HIT_NULL : hit0; + + /* Update the random walk position */ + move_pos(rwalk->vtx.P, dir0, delta); + + /* Fetch the current medium */ + if(S3D_HIT_NONE(&rwalk->hit)) { + CHK(scene_get_medium(scn, rwalk->vtx.P, &mdm) == RES_OK); + } else { + const struct sdis_interface* interface; + interface = scene_get_interface(scn, rwalk->hit.prim.prim_id); + mdm = interface_get_medium + (interface, + f3_dot(rwalk->hit.normal, dir0) < 0 ? SDIS_FRONT : SDIS_BACK); + } + + /* Check random walk consistency */ + if(mdm != rwalk->mdm) { + log_err(scn->dev, + "%s: inconsistent medium during the solid random walk.\n" + " start position: %g %g %g; current position: %g %g %g\n", + FUNC_NAME, SPLIT3(position_start), SPLIT3(rwalk->vtx.P)); + return RES_BAD_OP; + } + + /* Keep going while the solid random walk does not hit an interface */ + } while(S3D_HIT_NONE(&rwalk->hit)); + + T->func = boundary_temperature; + return RES_OK; +} + +static res_T +compute_temperature + (struct sdis_scene* scn, + const double fp_to_meter, + struct rwalk* rwalk, + struct ssp_rng* rng, + struct temperature* T) +{ + struct temperature* stack = NULL; + size_t istack; + res_T res = RES_OK; + ASSERT(scn && fp_to_meter && rwalk && rng && T); + + while(T->value) { /* Unknown temperature */ + res = T->func(scn, fp_to_meter, rwalk, rng, T); + if(res != RES_OK) goto error; + + sa_push(stack, *T); + ++istack; + } + +exit: + sa_release(stack); + return res; +error: + goto exit; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +sdis_solve_probe_temperature + (struct sdis_scene* scn, + const size_t nrealisations, + const double position[3], + const double time, + const double fp_to_meter,/* Scale factor from floating point unit to meter */ + struct sdis_estimator** out_estimator) +{ + const struct sdis_medium* medium = NULL; + struct sdis_estimator* estimator = NULL; + struct ssp_rng* rng = NULL; + double weight = 0; + double sqr_weight = 0; + size_t irealisation = 0; + res_T res = RES_OK; + + if(!scn || !position || time < 0 || fp_to_meter < 0 || !out_estimator) { + res = RES_BAD_ARG; + goto error; + } + + res = scene_get_medium(scn, position, &medium); + if(res != RES_OK) goto error; + + res = ssp_rng_create(scn->dev->allocator, &ssp_rng_mt19937_64, &rng); + if(res != RES_OK) goto error; + res = estimator_create(scn->dev, &estimator); + if(res != RES_OK) goto error; + + FOR_EACH(irealisation, 0, nrealisations) { + struct rwalk rwalk = RWALK_NULL; + struct temperature T = TEMPERATURE_NULL; + + switch(medium->type) { + case SDIS_MEDIUM_FLUID: T.func = solid_temperature; break; + case SDIS_MEDIUM_SOLID: T.func = fluid_temperature; break; + default: FATAL("Unreachable code\n"); break; + } + + rwalk.vtx.P[0] = position[0]; + rwalk.vtx.P[1] = position[1]; + rwalk.vtx.P[2] = position[2]; + rwalk.vtx.time = time; + rwalk.hit = S3D_HIT_NULL; + rwalk.mdm = medium; + + res = compute_temperature(scn, fp_to_meter, &rwalk, rng, &T); + if(res != RES_OK) { + if(res == RES_BAD_OP) { + ++estimator->nfailures; + } else { + goto error; + } + } else { + weight += T.value; + sqr_weight += T.value*T.value; + ++estimator->nrealisations; + } + } + + estimator->temperature.E = weight / (double)estimator->nrealisations; + estimator->temperature.V = + sqr_weight / (double)estimator->nrealisations + - estimator->temperature.E * estimator->temperature.E; + estimator->temperature.SE = + sqrt(estimator->temperature.V / (double)estimator->nrealisations); + +exit: + if(rng) SSP(rng_ref_put(rng)); + if(out_estimator) *out_estimator = estimator; + return res; +error: + if(estimator) { + SDIS(estimator_ref_put(estimator)); + estimator = NULL; + } + goto exit; +}