htrdr

Solving radiative transfer in heterogeneous media
git clone git://git.meso-star.fr/htrdr.git
Log | Files | Refs | README | LICENSE

commit c2cb907802e65c023d683e735398dd7f998b8870
parent 9d002482a618421a40eaf90931850c85b7b6903b
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Fri, 28 Oct 2022 16:38:30 +0200

Rename htrdr_cie_xyz and htrdr_ran_wlen

Both are wavelength distributions. Therefore, htrdr_cie_xyz is renamed
to htrdr_ran_wlen_cie_xyz and htrdr_ran_wlen is renamed to
htrdr_ran_wlen_planck

Diffstat:
Mcmake/core/CMakeLists.txt | 8++++----
Msrc/atmosphere/htrdr_atmosphere.c | 15++++++++-------
Msrc/atmosphere/htrdr_atmosphere_c.h | 8++++----
Msrc/atmosphere/htrdr_atmosphere_draw_map.c | 14+++++++-------
Msrc/core/htrdr_args.c | 4++--
Msrc/core/htrdr_args.h.in | 4++--
Dsrc/core/htrdr_cie_xyz.c | 389-------------------------------------------------------------------------------
Dsrc/core/htrdr_cie_xyz.h | 72------------------------------------------------------------------------
Dsrc/core/htrdr_ran_wlen.c | 364-------------------------------------------------------------------------------
Dsrc/core/htrdr_ran_wlen.h | 61-------------------------------------------------------------
Asrc/core/htrdr_ran_wlen_cie_xyz.c | 391+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen_cie_xyz.h | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen_planck.c | 365+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen_planck.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/planeto/htrdr_planeto.c | 18+++++++++---------
Msrc/planeto/htrdr_planeto_c.h | 8++++----
16 files changed, 931 insertions(+), 925 deletions(-)

diff --git a/cmake/core/CMakeLists.txt b/cmake/core/CMakeLists.txt @@ -84,12 +84,12 @@ set(HTRDR_CORE_FILES_SRC htrdr.c htrdr_args.c htrdr_buffer.c - htrdr_cie_xyz.c htrdr_draw_map.c htrdr_geometry.c htrdr_log.c htrdr_materials.c - htrdr_ran_wlen.c + htrdr_ran_wlen_cie_xyz.c + htrdr_ran_wlen_planck.c htrdr_rectangle.c htrdr_slab.c htrdr_spectral.c) @@ -98,13 +98,13 @@ set(HTRDR_CORE_FILES_INC htrdr_accum.h htrdr_buffer.h htrdr_c.h - htrdr_cie_xyz.h htrdr_draw_map.h htrdr_geometry.c htrdr_interface.h htrdr_log.h htrdr_materials.h - htrdr_ran_wlen.h + htrdr_ran_wlen_cie_xyz.h + htrdr_ran_wlen_planck.h htrdr_rectangle.h htrdr_sensor.h htrdr_slab.h diff --git a/src/atmosphere/htrdr_atmosphere.c b/src/atmosphere/htrdr_atmosphere.c @@ -24,10 +24,10 @@ #include "atmosphere/htrdr_atmosphere_sun.h" #include "core/htrdr_buffer.h" -#include "core/htrdr_cie_xyz.h" #include "core/htrdr_log.h" #include "core/htrdr_materials.h" -#include "core/htrdr_ran_wlen.h" +#include "core/htrdr_ran_wlen_cie_xyz.h" +#include "core/htrdr_ran_wlen_planck.h" #include "core/htrdr_rectangle.h" #include <high_tune/htsky.h> @@ -306,8 +306,8 @@ atmosphere_release(ref_T* ref) if(cmd->ground) htrdr_atmosphere_ground_ref_put(cmd->ground); if(cmd->mats) htrdr_materials_ref_put(cmd->mats); if(cmd->sun) htrdr_atmosphere_sun_ref_put(cmd->sun); - if(cmd->cie) htrdr_cie_xyz_ref_put(cmd->cie); - if(cmd->ran_wlen) htrdr_ran_wlen_ref_put(cmd->ran_wlen); + if(cmd->cie) htrdr_ran_wlen_cie_xyz_ref_put(cmd->cie); + if(cmd->planck) htrdr_ran_wlen_planck_ref_put(cmd->planck); if(cmd->camera) SCAM(ref_put(cmd->camera)); if(cmd->flux_map) htrdr_rectangle_ref_put(cmd->flux_map); if(cmd->buf) htrdr_buffer_ref_put(cmd->buf); @@ -439,7 +439,8 @@ htrdr_atmosphere_create nintervals = compute_spectral_bands_count(cmd); if(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) { - res = htrdr_cie_xyz_create(htrdr, spectral_range, nintervals, &cmd->cie); + res = htrdr_ran_wlen_cie_xyz_create + (htrdr, spectral_range, nintervals, &cmd->cie); if(res != RES_OK) goto error; } else { if(cmd->ref_temperature <= 0) { @@ -448,8 +449,8 @@ htrdr_atmosphere_create res = RES_BAD_ARG; goto error; } - res = htrdr_ran_wlen_create - (htrdr, spectral_range, nintervals, cmd->ref_temperature, &cmd->ran_wlen); + res = htrdr_ran_wlen_planck_create + (htrdr, spectral_range, nintervals, cmd->ref_temperature, &cmd->planck); if(res != RES_OK) goto error; } diff --git a/src/atmosphere/htrdr_atmosphere_c.h b/src/atmosphere/htrdr_atmosphere_c.h @@ -81,17 +81,17 @@ struct htsky; struct htrdr; struct htrdr_atmosphere_args; struct htrdr_buffer; -struct htrdr_cie_xyz; struct htrdr_materials; -struct htrdr_ran_wlen; +struct htrdr_ran_wlen_cie_xyz; +struct htrdr_ran_wlen_planck; struct ssp_rng; struct htrdr_atmosphere { struct htrdr_atmosphere_ground* ground; struct htrdr_atmosphere_sun* sun; struct htrdr_materials* mats; - struct htrdr_cie_xyz* cie; - struct htrdr_ran_wlen* ran_wlen; + struct htrdr_ran_wlen_cie_xyz* cie; + struct htrdr_ran_wlen_planck* planck; struct scam* camera; /* Camera */ struct htrdr_rectangle* flux_map; /* Flux map */ diff --git a/src/atmosphere/htrdr_atmosphere_draw_map.c b/src/atmosphere/htrdr_atmosphere_draw_map.c @@ -21,11 +21,11 @@ #include "core/htrdr.h" #include "core/htrdr_buffer.h" -#include "core/htrdr_cie_xyz.h" #include "core/htrdr_draw_map.h" #include "core/htrdr_interface.h" #include "core/htrdr_log.h" -#include "core/htrdr_ran_wlen.h" +#include "core/htrdr_ran_wlen_cie_xyz.h" +#include "core/htrdr_ran_wlen_planck.h" #include "core/htrdr_rectangle.h" #include <high_tune/htsky.h> @@ -160,9 +160,9 @@ draw_pixel_image /* Sample a spectral band and a quadrature point */ switch(ichannel) { - case 0: wlen = htrdr_cie_xyz_sample_X(cmd->cie, r0, r1, &pdf); break; - case 1: wlen = htrdr_cie_xyz_sample_Y(cmd->cie, r0, r1, &pdf); break; - case 2: wlen = htrdr_cie_xyz_sample_Z(cmd->cie, r0, r1, &pdf); break; + case 0: wlen = htrdr_ran_wlen_cie_xyz_sample_X(cmd->cie, r0, r1, &pdf); break; + case 1: wlen = htrdr_ran_wlen_cie_xyz_sample_Y(cmd->cie, r0, r1, &pdf); break; + case 2: wlen = htrdr_ran_wlen_cie_xyz_sample_Z(cmd->cie, r0, r1, &pdf); break; default: FATAL("Unreachable code.\n"); break; } pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ @@ -249,7 +249,7 @@ draw_pixel_flux r2 = ssp_rng_canonical(args->rng); /* Sample a wavelength */ - wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf); + wlen = htrdr_ran_wlen_planck_sample(cmd->planck, r0, r1, &band_pdf); band_pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ /* Select the associated band and sample a quadrature point */ @@ -370,7 +370,7 @@ draw_pixel_xwave r2 = ssp_rng_canonical(args->rng); /* Sample a wavelength */ - wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf); + wlen = htrdr_ran_wlen_planck_sample(cmd->planck, r0, r1, &band_pdf); band_pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ /* Select the associated band and sample a quadrature point */ diff --git a/src/core/htrdr_args.c b/src/core/htrdr_args.c @@ -444,8 +444,8 @@ parse_spectral_parameter(const char* str, void* ptr) if(!strcmp(key, "cie_xyz")) { args->spectral_type = HTRDR_SPECTRAL_SW_CIE_XYZ; - args->wlen_range[0] = HTRDR_CIE_XYZ_RANGE_DEFAULT[0]; - args->wlen_range[1] = HTRDR_CIE_XYZ_RANGE_DEFAULT[1]; + args->wlen_range[0] = HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT[0]; + args->wlen_range[1] = HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT[1]; } else { if(!val) { fprintf(stderr, "Missing value to the spectral option `%s'.\n", key); diff --git a/src/core/htrdr_args.h.in b/src/core/htrdr_args.h.in @@ -18,7 +18,7 @@ #ifndef HTRDR_ARGS_H #define HTRDR_ARGS_H -#include "core/htrdr_cie_xyz.h" +#include "core/htrdr_ran_wlen_cie_xyz.h" #include "core/htrdr_spectral.h" #include <rsys/rsys.h> @@ -115,7 +115,7 @@ struct htrdr_args_spectral { enum htrdr_spectral_type spectral_type; }; #define HTRDR_ARGS_SPECTRAL_DEFAULT__ { \ - HTRDR_CIE_XYZ_RANGE_DEFAULT__, /* Spectral range */ \ + HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT__, /* Spectral range */ \ -1, /* Reference temperature */ \ HTRDR_SPECTRAL_SW_CIE_XYZ, /* Spectral type */ \ } diff --git a/src/core/htrdr_cie_xyz.c b/src/core/htrdr_cie_xyz.c @@ -1,389 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|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 _POSIX_C_SOURCE 200112L /* nextafter */ - -#include "core/htrdr.h" -#include "core/htrdr_c.h" -#include "core/htrdr_cie_xyz.h" -#include "core/htrdr_log.h" - -#include <rsys/algorithm.h> -#include <rsys/cstr.h> -#include <rsys/dynamic_array_double.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -#include <math.h> /* nextafter */ - -struct htrdr_cie_xyz { - struct darray_double cdf_X; - struct darray_double cdf_Y; - struct darray_double cdf_Z; - double rcp_integral_X; - double rcp_integral_Y; - double rcp_integral_Z; - double range[2]; /* Boundaries of the handled CIE XYZ color space */ - double band_len; /* Length in nanometers of a band */ - - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static INLINE double -trapezoidal_integration - (const double lambda_lo, /* Integral lower bound. In nanometer */ - const double lambda_hi, /* Integral upper bound. In nanometer */ - double (*f_bar)(const double lambda)) /* Function to integrate */ -{ - double dlambda; - size_t i, n; - double integral = 0; - ASSERT(lambda_lo <= lambda_hi); - ASSERT(lambda_lo > 0); - - n = (size_t)(lambda_hi - lambda_lo) + 1; - dlambda = (lambda_hi - lambda_lo) / (double)n; - - FOR_EACH(i, 0, n) { - const double lambda1 = lambda_lo + dlambda*(double)(i+0); - const double lambda2 = lambda_lo + dlambda*(double)(i+1); - const double f1 = f_bar(lambda1); - const double f2 = f_bar(lambda2); - integral += (f1 + f2)*dlambda*0.5; - } - return integral; -} - -/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved - * has defined by the 1931 standard. These analytical fits are propsed by C. - * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the - * CIE XYZ Color Matching Functions" - JCGT 2013. */ -static INLINE double -fit_x_bar_1931(const double lambda) -{ - const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374); - const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323); - const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382); - return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c); -} - -static FINLINE double -fit_y_bar_1931(const double lambda) -{ - const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247); - const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322); - return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b); -} - -static FINLINE double -fit_z_bar_1931(const double lambda) -{ - const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278); - const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725); - return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b); -} - -static INLINE double -sample_cie_xyz - (const struct htrdr_cie_xyz* cie, - const double* cdf, - const size_t cdf_length, - double (*f_bar)(const double lambda), /* Function to integrate */ - const double r0, /* Canonical number in [0, 1[ */ - const double r1) /* Canonical number in [0, 1[ */ -{ - double r0_next = nextafter(r0, DBL_MAX); - double* find; - double f_min, f_max; /* CIE 1931 value for the band boundaries */ - double lambda; /* Sampled wavelength */ - double lambda_min, lambda_max; /* Boundaries of the sampled band */ - double lambda_1, lambda_2; /* Solutions if the equation to solve */ - double a, b, c, d; /* Equation parameters */ - double delta, sqrt_delta; - size_t iband; /* Index of the sampled band */ - ASSERT(cie && cdf && cdf_length); - ASSERT(0 <= r0 && r0 < 1); - ASSERT(0 <= r1 && r1 < 1); - - /* Use r_next rather than r in order to find the first entry that is not less - * than *or equal* to r */ - find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); - ASSERT(find); - - /* Define and check the sampled band */ - iband = (size_t)(find - cdf); - ASSERT(iband < cdf_length); - ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0)); - - /* Define the boundaries of the sampled band */ - lambda_min = cie->range[0] + cie->band_len * (double)iband; - lambda_max = lambda_min + cie->band_len; - - /* Define the value of the CIE 1931 function for the boudaries of the sampled - * band */ - f_min = f_bar(lambda_min); - f_max = f_bar(lambda_max); - - /* Compute the equation constants */ - a = 0.5 * (f_max - f_min) / cie->band_len; - b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len; - c = -lambda_min * f_min + lambda_min*lambda_min * a; - d = 0.5 * (f_max + f_min) * cie->band_len; - - delta = b*b - 4*a*(c-d*r1); - if(delta < 0 && eq_eps(delta, 0, 1.e-6)) { - delta = 0; - } - ASSERT(delta > 0); - sqrt_delta = sqrt(delta); - - /* Compute the roots that solve the equation */ - lambda_1 = (-b - sqrt_delta) / (2*a); - lambda_2 = (-b + sqrt_delta) / (2*a); - - /* Select the solution */ - if(lambda_min <= lambda_1 && lambda_1 < lambda_max) { - lambda = lambda_1; - } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) { - lambda = lambda_2; - } else { - htrdr_log_warn(cie->htrdr, - "%s: cannot sample a wavelength in [%g, %g[. The possible wavelengths" - "were %g and %g.\n", - FUNC_NAME, lambda_min, lambda_max, lambda_1, lambda_2); - /* Arbitrarly choose the wavelength at the center of the sampled band */ - lambda = (lambda_min + lambda_max)*0.5; - } - - return lambda; -} - -static res_T -setup_cie_xyz - (struct htrdr_cie_xyz* cie, - const char* func_name, - const size_t nbands) -{ - enum { X, Y, Z }; /* Helper constant */ - double* pdf[3] = {NULL, NULL, NULL}; - double* cdf[3] = {NULL, NULL, NULL}; - double sum[3] = {0, 0, 0}; - size_t i; - res_T res = RES_OK; - - ASSERT(cie && func_name && nbands); - ASSERT(cie->range[0] >= HTRDR_CIE_XYZ_RANGE_DEFAULT[0]); - ASSERT(cie->range[1] <= HTRDR_CIE_XYZ_RANGE_DEFAULT[1]); - ASSERT(cie->range[0] < cie->range[1]); - - /* Allocate and reset the memory space for the tristimulus CDF */ - #define SETUP_STIMULUS(Stimulus) { \ - res = darray_double_resize(&cie->cdf_ ## Stimulus, nbands); \ - if(res != RES_OK) { \ - htrdr_log_err(cie->htrdr, \ - "%s: Could not reserve the memory space for the CDF " \ - "of the "STR(X)" stimulus -- %s.\n", func_name, res_to_cstr(res)); \ - goto error; \ - } \ - cdf[Stimulus] = darray_double_data_get(&cie->cdf_ ## Stimulus); \ - pdf[Stimulus] = cdf[Stimulus]; \ - memset(cdf[Stimulus], 0, nbands*sizeof(double)); \ - } (void)0 - SETUP_STIMULUS(X); - SETUP_STIMULUS(Y); - SETUP_STIMULUS(Z); - #undef SETUP_STIMULUS - - /* Compute the *unormalized* pdf of the tristimulus */ - FOR_EACH(i, 0, nbands) { - const double lambda_lo = cie->range[0] + (double)i * cie->band_len; - const double lambda_hi = MMIN(lambda_lo + cie->band_len, cie->range[1]); - ASSERT(lambda_lo <= lambda_hi); - ASSERT(lambda_lo >= cie->range[0]); - ASSERT(lambda_hi <= cie->range[1]); - pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931); - pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931); - pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931); - sum[X] += pdf[X][i]; - sum[Y] += pdf[Y][i]; - sum[Z] += pdf[Z][i]; - } - #define CHK_SUM(Sum, Range, Fit) \ - ASSERT(eq_eps(Sum, trapezoidal_integration(Range[0], Range[1], Fit), 1.e-3)) - CHK_SUM(sum[X], cie->range, fit_x_bar_1931); - CHK_SUM(sum[Y], cie->range, fit_y_bar_1931); - CHK_SUM(sum[Z], cie->range, fit_z_bar_1931); - #undef CHK_SUM - cie->rcp_integral_X = 1.0 / sum[X]; - cie->rcp_integral_Y = 1.0 / sum[Y]; - cie->rcp_integral_Z = 1.0 / sum[Z]; - - FOR_EACH(i, 0, nbands) { - /* Normalize the pdf */ - pdf[X][i] /= sum[X]; - pdf[Y][i] /= sum[Y]; - pdf[Z][i] /= sum[Z]; - /* Setup the cumulative */ - if(i == 0) { - cdf[X][i] = pdf[X][i]; - cdf[Y][i] = pdf[Y][i]; - cdf[Z][i] = pdf[Z][i]; - } else { - cdf[X][i] = pdf[X][i] + cdf[X][i-1]; - cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1]; - cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1]; - ASSERT(cdf[X][i] >= cdf[X][i-1]); - ASSERT(cdf[Y][i] >= cdf[Y][i-1]); - ASSERT(cdf[Z][i] >= cdf[Z][i-1]); - } - } - ASSERT(eq_eps(cdf[X][nbands-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Y][nbands-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Z][nbands-1], 1, 1.e-6)); - - /* Handle numerical issue */ - cdf[X][nbands-1] = 1.0; - cdf[Y][nbands-1] = 1.0; - cdf[Z][nbands-1] = 1.0; - -exit: - return res; -error: - darray_double_clear(&cie->cdf_X); - darray_double_clear(&cie->cdf_Y); - darray_double_clear(&cie->cdf_Z); - goto exit; -} - -static void -release_cie_xyz(ref_T* ref) -{ - struct htrdr_cie_xyz* cie = NULL; - struct htrdr* htrdr = NULL; - ASSERT(ref); - cie = CONTAINER_OF(ref, struct htrdr_cie_xyz, ref); - darray_double_release(&cie->cdf_X); - darray_double_release(&cie->cdf_Y); - darray_double_release(&cie->cdf_Z); - htrdr = cie->htrdr; - MEM_RM(htrdr_get_allocator(cie->htrdr), cie); - htrdr_ref_put(htrdr); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_cie_xyz_create - (struct htrdr* htrdr, - const double range[2], /* Must be included in [380, 780] nanometers */ - const size_t bands_count, /* # bands used to discretize the CIE tristimulus */ - struct htrdr_cie_xyz** out_cie) -{ - struct htrdr_cie_xyz* cie = NULL; - size_t nbands = bands_count; - res_T res = RES_OK; - ASSERT(htrdr && range && nbands && out_cie); - - cie = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cie)); - if(!cie) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate the CIE XYZ data structure -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - ref_init(&cie->ref); - darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_X); - darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_Y); - darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_Z); - cie->range[0] = range[0]; - cie->range[1] = range[1]; - htrdr_ref_get(htrdr); - cie->htrdr = htrdr; - - cie->band_len = (range[1] - range[0]) / (double)nbands; - - res = setup_cie_xyz(cie, FUNC_NAME, nbands); - if(res != RES_OK) goto error; - - htrdr_log(htrdr, "CIE XYZ spectral interval defined on [%g, %g] nanometers.\n", - range[0], range[1]); - -exit: - *out_cie = cie; - return res; -error: - if(cie) htrdr_cie_xyz_ref_put(cie); - goto exit; -} - -void -htrdr_cie_xyz_ref_get(struct htrdr_cie_xyz* cie) -{ - ASSERT(cie); - ref_get(&cie->ref); -} - -void -htrdr_cie_xyz_ref_put(struct htrdr_cie_xyz* cie) -{ - ASSERT(cie); - ref_put(&cie->ref, release_cie_xyz); -} - -double -htrdr_cie_xyz_sample_X - (struct htrdr_cie_xyz* cie, - const double r0, - const double r1, - double* pdf) -{ - const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X), - darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1); - if(pdf) *pdf = cie->rcp_integral_X; - return wlen; -} - -double -htrdr_cie_xyz_sample_Y - (struct htrdr_cie_xyz* cie, - const double r0, - const double r1, - double* pdf) -{ - const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y), - darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1); - if(pdf) *pdf = cie->rcp_integral_Y; - return wlen; -} - -double -htrdr_cie_xyz_sample_Z - (struct htrdr_cie_xyz* cie, - const double r0, - const double r1, - double* pdf) -{ - const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z), - darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1); - if(pdf) *pdf = cie->rcp_integral_Z; - return wlen; -} - diff --git a/src/core/htrdr_cie_xyz.h b/src/core/htrdr_cie_xyz.h @@ -1,72 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|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 HTRDR_CIE_XYZ_H -#define HTRDR_CIE_XYZ_H - -#include "core/htrdr.h" -#include <rsys/rsys.h> - -/* Wavelength boundaries of the CIE XYZ color space in nanometers */ -#define HTRDR_CIE_XYZ_RANGE_DEFAULT__ {380, 780} -static const double HTRDR_CIE_XYZ_RANGE_DEFAULT[2] = - HTRDR_CIE_XYZ_RANGE_DEFAULT__; - -/* Forward declarations */ -struct htrdr; -struct htrdr_cie_xyz; - -BEGIN_DECLS - -HTRDR_CORE_API res_T -htrdr_cie_xyz_create - (struct htrdr* htrdr, - const double range[2], /* Must be included in [380, 780] nanometers */ - const size_t nbands, /* # bands used to discretize the CIE tristimulus */ - struct htrdr_cie_xyz** cie); - -HTRDR_CORE_API void -htrdr_cie_xyz_ref_get - (struct htrdr_cie_xyz* cie); - -HTRDR_CORE_API void -htrdr_cie_xyz_ref_put - (struct htrdr_cie_xyz* cie); - -/* Return a wavelength in nanometer */ -HTRDR_CORE_API double -htrdr_cie_xyz_sample_X - (struct htrdr_cie_xyz* cie, - const double r0, const double r1, /* Canonical numbers in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -/* Return a wavelength in nanometer */ -HTRDR_CORE_API double -htrdr_cie_xyz_sample_Y - (struct htrdr_cie_xyz* cie, - const double r0, const double r1, /* Canonical number in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -/* Return a wavelength in nanometer */ -HTRDR_CORE_API double -htrdr_cie_xyz_sample_Z - (struct htrdr_cie_xyz* cie, - const double r0, const double r1, /* Canonical number in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -END_DECLS - -#endif /* HTRDR_cie_xyz_H */ - diff --git a/src/core/htrdr_ran_wlen.c b/src/core/htrdr_ran_wlen.c @@ -1,364 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019, 2021 CNRS - * Copyright (C) 2018, 2019 Université Paul Sabatier - * - * 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 _POSIX_C_SOURCE 200112L /* nextafter */ - -#include "core/htrdr.h" -#include "core/htrdr_c.h" -#include "core/htrdr_log.h" -#include "core/htrdr_ran_wlen.h" -#include "core/htrdr_spectral.h" - -#include <rsys/algorithm.h> -#include <rsys/cstr.h> -#include <rsys/dynamic_array_double.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -#include <math.h> /* nextafter */ - -struct htrdr_ran_wlen { - struct darray_double pdf; - struct darray_double cdf; - double range[2]; /* Boundaries of the spectral integration interval */ - double band_len; /* Length in nanometers of a band */ - double ref_temperature; /* In Kelvin */ - size_t nbands; /* # bands */ - - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static res_T -setup_wlen_ran_cdf - (struct htrdr_ran_wlen* wlen_ran, - const char* func_name) -{ - double* pdf = NULL; - double* cdf = NULL; - double sum = 0; - size_t i; - res_T res = RES_OK; - ASSERT(wlen_ran && func_name && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE); - - res = darray_double_resize(&wlen_ran->cdf, wlen_ran->nbands); - if(res != RES_OK) { - htrdr_log_err(wlen_ran->htrdr, - "%s: Error allocating the CDF of the spectral bands -- %s.\n", - func_name, res_to_cstr(res)); - goto error; - } - res = darray_double_resize(&wlen_ran->pdf, wlen_ran->nbands); - if(res != RES_OK) { - htrdr_log_err(wlen_ran->htrdr, - "%s: Error allocating the pdf of the spectral bands -- %s.\n", - func_name, res_to_cstr(res)); - goto error; - } - - cdf = darray_double_data_get(&wlen_ran->cdf); - pdf = darray_double_data_get(&wlen_ran->pdf); /* Now save pdf to correct weight */ - - htrdr_log(wlen_ran->htrdr, - "Number of bands of the spectrum cumulative: %lu\n", - (unsigned long)wlen_ran->nbands); - - /* Compute the *unnormalized* probability to sample a small band */ - FOR_EACH(i, 0, wlen_ran->nbands) { - double lambda_lo = wlen_ran->range[0] + (double)i * wlen_ran->band_len; - double lambda_hi = MMIN(lambda_lo + wlen_ran->band_len, wlen_ran->range[1]); - ASSERT(lambda_lo<= lambda_hi); - ASSERT(lambda_lo > wlen_ran->range[0] - || eq_eps(lambda_lo, wlen_ran->range[0], 1.e-6)); - ASSERT(lambda_lo < wlen_ran->range[1] - || eq_eps(lambda_lo, wlen_ran->range[1], 1.e-6)); - - /* Convert from nanometer to meter */ - lambda_lo *= 1.e-9; - lambda_hi *= 1.e-9; - - /* Compute the probability of the current band */ - pdf[i] = htrdr_blackbody_fraction - (lambda_lo, lambda_hi, wlen_ran->ref_temperature); - - /* Update the norm */ - sum += pdf[i]; - } - - /* Compute the cumulative of the previously computed probabilities */ - FOR_EACH(i, 0, wlen_ran->nbands) { - /* Normalize the probability */ - pdf[i] /= sum; - - /* Setup the cumulative */ - if(i == 0) { - cdf[i] = pdf[i]; - } else { - cdf[i] = pdf[i] + cdf[i-1]; - ASSERT(cdf[i] >= cdf[i-1]); - } - } - ASSERT(eq_eps(cdf[wlen_ran->nbands-1], 1, 1.e-6)); - cdf[wlen_ran->nbands - 1] = 1.0; /* Handle numerical issue */ - -exit: - return res; -error: - darray_double_clear(&wlen_ran->cdf); - darray_double_clear(&wlen_ran->pdf); - goto exit; -} - -static double -wlen_ran_sample_continue - (const struct htrdr_ran_wlen* wlen_ran, - const double r, - const double range[2], /* In nanometer */ - const char* func_name, - double* pdf) -{ - /* Numerical parameters of the dichotomy search */ - const size_t MAX_ITER = 100; - const double EPSILON_LAMBDA_M = 1e-15; - const double EPSILON_BF = 1e-6; - - /* Local variables */ - double bf = 0; - double bf_prev = 0; - double bf_min_max = 0; - double lambda_m = 0; - double lambda_m_prev = 0; - double lambda_m_min = 0; - double lambda_m_max = 0; - double range_m[2] = {0, 0}; - size_t i; - - /* Check precondition */ - ASSERT(wlen_ran && func_name); - ASSERT(range && range[0] < range[1]); - ASSERT(0 <= r && r < 1); - - /* Convert the wavelength range in meters */ - range_m[0] = range[0] * 1.0e-9; - range_m[1] = range[1] * 1.0e-9; - - /* Setup the dichotomy search */ - lambda_m_min = range_m[0]; - lambda_m_max = range_m[1]; - bf_min_max = htrdr_blackbody_fraction - (range_m[0], range_m[1], wlen_ran->ref_temperature); - - /* Numerically search the lambda corresponding to the submitted canonical - * number */ - FOR_EACH(i, 0, MAX_ITER) { - double r_test; - lambda_m = (lambda_m_min + lambda_m_max) * 0.5; - bf = htrdr_blackbody_fraction - (range_m[0], lambda_m, wlen_ran->ref_temperature); - - r_test = bf / bf_min_max; - if(r_test < r) { - lambda_m_min = lambda_m; - } else { - lambda_m_max = lambda_m; - } - - if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA_M - || fabs(bf_prev - bf) < EPSILON_BF) - break; - - lambda_m_prev = lambda_m; - bf_prev = bf; - } - if(i >= MAX_ITER) { - htrdr_log_warn(wlen_ran->htrdr, - "%s: could not sample a wavelength in the range [%g, %g] nanometers " - "for the reference temperature %g Kelvin.\n", - func_name, SPLIT2(range), wlen_ran->ref_temperature); - } - - if(pdf) { - const double Tref = wlen_ran->ref_temperature; - const double B_lambda = htrdr_planck(lambda_m, lambda_m, Tref); - const double B_mean = htrdr_planck(range_m[0], range_m[1], Tref); - *pdf = B_lambda / (B_mean * (range_m[1]-range_m[0])); - *pdf *= 1.e-9; /* Transform from m^-1 to nm^-1 */ - } - - return lambda_m * 1.e9; /* Convert in nanometers */ -} - -static double -wlen_ran_sample_discrete - (const struct htrdr_ran_wlen* wlen_ran, - const double r0, - const double r1, - const char* func_name, - double* pdf) -{ - const double* cdf = NULL; - const double* find = NULL; - double r0_next = nextafter(r0, DBL_MAX); - double band_range[2]; - double lambda = 0; - double pdf_continue = 0; - double pdf_band = 0; - size_t cdf_length = 0; - size_t i; - ASSERT(wlen_ran && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE); - ASSERT(0 <= r0 && r0 < 1); - ASSERT(0 <= r1 && r1 < 1); - (void)func_name; - (void)pdf_band; - - cdf = darray_double_cdata_get(&wlen_ran->cdf); - cdf_length = darray_double_size_get(&wlen_ran->cdf); - ASSERT(cdf_length > 0); - - /* Use r_next rather than r0 in order to find the first entry that is not less - * than *or equal* to r0 */ - find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); - ASSERT(find); - - i = (size_t)(find - cdf); - ASSERT(i < cdf_length && cdf[i] > r0 && (!i || cdf[i-1] <= r0)); - - band_range[0] = wlen_ran->range[0] + (double)i*wlen_ran->band_len; - band_range[1] = band_range[0] + wlen_ran->band_len; - - /* Fetch the pdf of the sampled band */ - pdf_band = darray_double_cdata_get(&wlen_ran->pdf)[i]; - - /* Uniformly sample a wavelength in the sampled band */ - lambda = band_range[0] + (band_range[1] - band_range[0]) * r1; - pdf_continue = 1.0 / (band_range[1] - band_range[0]); - - if(pdf) { - *pdf = pdf_band * pdf_continue; - } - - return lambda; -} - -static void -release_wlen_ran(ref_T* ref) -{ - struct htrdr_ran_wlen* wlen_ran = NULL; - struct htrdr* htrdr = NULL; - ASSERT(ref); - wlen_ran = CONTAINER_OF(ref, struct htrdr_ran_wlen, ref); - darray_double_release(&wlen_ran->cdf); - darray_double_release(&wlen_ran->pdf); - htrdr = wlen_ran->htrdr; - MEM_RM(htrdr_get_allocator(htrdr), wlen_ran); - htrdr_ref_put(wlen_ran->htrdr); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_ran_wlen_create - (struct htrdr* htrdr, - /* range must be included in [200,1000] nm for shortwave or in [1000,100000] - * nanometers for longwave (thermal) */ - const double range[2], - const size_t nbands, /* # bands used to discretized CDF */ - const double ref_temperature, - struct htrdr_ran_wlen** out_wlen_ran) -{ - struct htrdr_ran_wlen* wlen_ran = NULL; - res_T res = RES_OK; - ASSERT(htrdr && range && out_wlen_ran && ref_temperature > 0); - ASSERT(ref_temperature > 0); - ASSERT(range[0] <= range[1]); - - wlen_ran = MEM_CALLOC(htrdr->allocator, 1, sizeof(*wlen_ran)); - if(!wlen_ran) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate longwave random variate data structure -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - ref_init(&wlen_ran->ref); - darray_double_init(htrdr->allocator, &wlen_ran->cdf); - darray_double_init(htrdr->allocator, &wlen_ran->pdf); - htrdr_ref_get(htrdr); - wlen_ran->htrdr = htrdr; - - wlen_ran->range[0] = range[0]; - wlen_ran->range[1] = range[1]; - wlen_ran->ref_temperature = ref_temperature; - wlen_ran->nbands = nbands; - - if(nbands == HTRDR_WLEN_RAN_CONTINUE) { - wlen_ran->band_len = 0; - } else { - wlen_ran->band_len = (range[1] - range[0]) / (double)wlen_ran->nbands; - - res = setup_wlen_ran_cdf(wlen_ran, FUNC_NAME); - if(res != RES_OK) goto error; - } - - htrdr_log(htrdr, "Spectral interval defined on [%g, %g] nanometers.\n", - range[0], range[1]); - -exit: - *out_wlen_ran = wlen_ran; - return res; -error: - if(wlen_ran) htrdr_ran_wlen_ref_put(wlen_ran); - goto exit; -} - -void -htrdr_ran_wlen_ref_get(struct htrdr_ran_wlen* wlen_ran) -{ - ASSERT(wlen_ran); - ref_get(&wlen_ran->ref); -} - -void -htrdr_ran_wlen_ref_put(struct htrdr_ran_wlen* wlen_ran) -{ - ASSERT(wlen_ran); - ref_put(&wlen_ran->ref, release_wlen_ran); -} - -double -htrdr_ran_wlen_sample - (const struct htrdr_ran_wlen* wlen_ran, - const double r0, - const double r1, - double* pdf) -{ - ASSERT(wlen_ran); - if(wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE) { /* Discrete */ - return wlen_ran_sample_discrete(wlen_ran, r0, r1, FUNC_NAME, pdf); - } else if(eq_eps(wlen_ran->range[0], wlen_ran->range[1], 1.e-6)) { - if(pdf) *pdf = 1; - return wlen_ran->range[0]; - } else { /* Continue */ - return wlen_ran_sample_continue - (wlen_ran, r0, wlen_ran->range, FUNC_NAME, pdf); - } -} - diff --git a/src/core/htrdr_ran_wlen.h b/src/core/htrdr_ran_wlen.h @@ -1,61 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019, 2021 CNRS - * Copyright (C) 2018, 2019 Université Paul Sabatier - * - * 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 HTRDR_RAN_WLEN_H -#define HTRDR_RAN_WLEN_H - -#include "core/htrdr.h" -#include <rsys/rsys.h> - -#define HTRDR_WLEN_RAN_CONTINUE 0 - -/* Forward declarations */ -struct htrdr; -struct htrdr_ran_wlen; - -BEGIN_DECLS - -HTRDR_CORE_API res_T -htrdr_ran_wlen_create - (struct htrdr* htrdr, - const double range[2], - /* # bands used to discretisze the spectral domain. HTRDR_WLEN_RAN_CONTINUE - * <=> no discretisation */ - const size_t nbands, /* Hint on #bands used to discretised th CDF */ - const double ref_temperature, /* Reference temperature */ - struct htrdr_ran_wlen** wlen_ran); - -HTRDR_CORE_API void -htrdr_ran_wlen_ref_get - (struct htrdr_ran_wlen* wlen_ran); - -HTRDR_CORE_API void -htrdr_ran_wlen_ref_put - (struct htrdr_ran_wlen* wlen_ran); - -/* Return a wavelength in nanometer */ -HTRDR_CORE_API double -htrdr_ran_wlen_sample - (const struct htrdr_ran_wlen* wlen_ran, - const double r0, /* Canonical number in [0, 1[ */ - const double r1, /* Canonical number in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -END_DECLS - -#endif /* HTRDR_RAN_WLEN_H */ - diff --git a/src/core/htrdr_ran_wlen_cie_xyz.c b/src/core/htrdr_ran_wlen_cie_xyz.c @@ -0,0 +1,391 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * 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 _POSIX_C_SOURCE 200112L /* nextafter */ + +#include "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_ran_wlen_cie_xyz.h" +#include "core/htrdr_log.h" + +#include <rsys/algorithm.h> +#include <rsys/cstr.h> +#include <rsys/dynamic_array_double.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +#include <math.h> /* nextafter */ + +struct htrdr_ran_wlen_cie_xyz { + struct darray_double cdf_X; + struct darray_double cdf_Y; + struct darray_double cdf_Z; + double rcp_integral_X; + double rcp_integral_Y; + double rcp_integral_Z; + double range[2]; /* Boundaries of the handled CIE XYZ color space */ + double band_len; /* Length in nanometers of a band */ + + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE double +trapezoidal_integration + (const double lambda_lo, /* Integral lower bound. In nanometer */ + const double lambda_hi, /* Integral upper bound. In nanometer */ + double (*f_bar)(const double lambda)) /* Function to integrate */ +{ + double dlambda; + size_t i, n; + double integral = 0; + ASSERT(lambda_lo <= lambda_hi); + ASSERT(lambda_lo > 0); + + n = (size_t)(lambda_hi - lambda_lo) + 1; + dlambda = (lambda_hi - lambda_lo) / (double)n; + + FOR_EACH(i, 0, n) { + const double lambda1 = lambda_lo + dlambda*(double)(i+0); + const double lambda2 = lambda_lo + dlambda*(double)(i+1); + const double f1 = f_bar(lambda1); + const double f2 = f_bar(lambda2); + integral += (f1 + f2)*dlambda*0.5; + } + return integral; +} + +/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved + * has defined by the 1931 standard. These analytical fits are propsed by C. + * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the + * CIE XYZ Color Matching Functions" - JCGT 2013. */ +static INLINE double +fit_x_bar_1931(const double lambda) +{ + const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374); + const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323); + const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382); + return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c); +} + +static FINLINE double +fit_y_bar_1931(const double lambda) +{ + const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247); + const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322); + return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b); +} + +static FINLINE double +fit_z_bar_1931(const double lambda) +{ + const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278); + const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725); + return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b); +} + +static INLINE double +sample_cie_xyz + (const struct htrdr_ran_wlen_cie_xyz* cie, + const double* cdf, + const size_t cdf_length, + double (*f_bar)(const double lambda), /* Function to integrate */ + const double r0, /* Canonical number in [0, 1[ */ + const double r1) /* Canonical number in [0, 1[ */ +{ + double r0_next = nextafter(r0, DBL_MAX); + double* find; + double f_min, f_max; /* CIE 1931 value for the band boundaries */ + double lambda; /* Sampled wavelength */ + double lambda_min, lambda_max; /* Boundaries of the sampled band */ + double lambda_1, lambda_2; /* Solutions if the equation to solve */ + double a, b, c, d; /* Equation parameters */ + double delta, sqrt_delta; + size_t iband; /* Index of the sampled band */ + ASSERT(cie && cdf && cdf_length); + ASSERT(0 <= r0 && r0 < 1); + ASSERT(0 <= r1 && r1 < 1); + + /* Use r_next rather than r in order to find the first entry that is not less + * than *or equal* to r */ + find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); + ASSERT(find); + + /* Define and check the sampled band */ + iband = (size_t)(find - cdf); + ASSERT(iband < cdf_length); + ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0)); + + /* Define the boundaries of the sampled band */ + lambda_min = cie->range[0] + cie->band_len * (double)iband; + lambda_max = lambda_min + cie->band_len; + + /* Define the value of the CIE 1931 function for the boudaries of the sampled + * band */ + f_min = f_bar(lambda_min); + f_max = f_bar(lambda_max); + + /* Compute the equation constants */ + a = 0.5 * (f_max - f_min) / cie->band_len; + b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len; + c = -lambda_min * f_min + lambda_min*lambda_min * a; + d = 0.5 * (f_max + f_min) * cie->band_len; + + delta = b*b - 4*a*(c-d*r1); + if(delta < 0 && eq_eps(delta, 0, 1.e-6)) { + delta = 0; + } + ASSERT(delta > 0); + sqrt_delta = sqrt(delta); + + /* Compute the roots that solve the equation */ + lambda_1 = (-b - sqrt_delta) / (2*a); + lambda_2 = (-b + sqrt_delta) / (2*a); + + /* Select the solution */ + if(lambda_min <= lambda_1 && lambda_1 < lambda_max) { + lambda = lambda_1; + } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) { + lambda = lambda_2; + } else { + htrdr_log_warn(cie->htrdr, + "%s: cannot sample a wavelength in [%g, %g[. The possible wavelengths" + "were %g and %g.\n", + FUNC_NAME, lambda_min, lambda_max, lambda_1, lambda_2); + /* Arbitrarly choose the wavelength at the center of the sampled band */ + lambda = (lambda_min + lambda_max)*0.5; + } + + return lambda; +} + +static res_T +setup_cie_xyz + (struct htrdr_ran_wlen_cie_xyz* cie, + const char* func_name, + const size_t nbands) +{ + enum { X, Y, Z }; /* Helper constant */ + double* pdf[3] = {NULL, NULL, NULL}; + double* cdf[3] = {NULL, NULL, NULL}; + double sum[3] = {0, 0, 0}; + size_t i; + res_T res = RES_OK; + + ASSERT(cie && func_name && nbands); + ASSERT(cie->range[0] >= HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT[0]); + ASSERT(cie->range[1] <= HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT[1]); + ASSERT(cie->range[0] < cie->range[1]); + + /* Allocate and reset the memory space for the tristimulus CDF */ + #define SETUP_STIMULUS(Stimulus) { \ + res = darray_double_resize(&cie->cdf_ ## Stimulus, nbands); \ + if(res != RES_OK) { \ + htrdr_log_err(cie->htrdr, \ + "%s: Could not reserve the memory space for the CDF " \ + "of the "STR(X)" stimulus -- %s.\n", func_name, res_to_cstr(res)); \ + goto error; \ + } \ + cdf[Stimulus] = darray_double_data_get(&cie->cdf_ ## Stimulus); \ + pdf[Stimulus] = cdf[Stimulus]; \ + memset(cdf[Stimulus], 0, nbands*sizeof(double)); \ + } (void)0 + SETUP_STIMULUS(X); + SETUP_STIMULUS(Y); + SETUP_STIMULUS(Z); + #undef SETUP_STIMULUS + + /* Compute the *unormalized* pdf of the tristimulus */ + FOR_EACH(i, 0, nbands) { + const double lambda_lo = cie->range[0] + (double)i * cie->band_len; + const double lambda_hi = MMIN(lambda_lo + cie->band_len, cie->range[1]); + ASSERT(lambda_lo <= lambda_hi); + ASSERT(lambda_lo >= cie->range[0]); + ASSERT(lambda_hi <= cie->range[1]); + pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931); + pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931); + pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931); + sum[X] += pdf[X][i]; + sum[Y] += pdf[Y][i]; + sum[Z] += pdf[Z][i]; + } + #define CHK_SUM(Sum, Range, Fit) \ + ASSERT(eq_eps(Sum, trapezoidal_integration(Range[0], Range[1], Fit), 1.e-3)) + CHK_SUM(sum[X], cie->range, fit_x_bar_1931); + CHK_SUM(sum[Y], cie->range, fit_y_bar_1931); + CHK_SUM(sum[Z], cie->range, fit_z_bar_1931); + #undef CHK_SUM + cie->rcp_integral_X = 1.0 / sum[X]; + cie->rcp_integral_Y = 1.0 / sum[Y]; + cie->rcp_integral_Z = 1.0 / sum[Z]; + + FOR_EACH(i, 0, nbands) { + /* Normalize the pdf */ + pdf[X][i] /= sum[X]; + pdf[Y][i] /= sum[Y]; + pdf[Z][i] /= sum[Z]; + /* Setup the cumulative */ + if(i == 0) { + cdf[X][i] = pdf[X][i]; + cdf[Y][i] = pdf[Y][i]; + cdf[Z][i] = pdf[Z][i]; + } else { + cdf[X][i] = pdf[X][i] + cdf[X][i-1]; + cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1]; + cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1]; + ASSERT(cdf[X][i] >= cdf[X][i-1]); + ASSERT(cdf[Y][i] >= cdf[Y][i-1]); + ASSERT(cdf[Z][i] >= cdf[Z][i-1]); + } + } + ASSERT(eq_eps(cdf[X][nbands-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Y][nbands-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Z][nbands-1], 1, 1.e-6)); + + /* Handle numerical issue */ + cdf[X][nbands-1] = 1.0; + cdf[Y][nbands-1] = 1.0; + cdf[Z][nbands-1] = 1.0; + +exit: + return res; +error: + darray_double_clear(&cie->cdf_X); + darray_double_clear(&cie->cdf_Y); + darray_double_clear(&cie->cdf_Z); + goto exit; +} + +static void +release_cie_xyz(ref_T* ref) +{ + struct htrdr_ran_wlen_cie_xyz* cie = NULL; + struct htrdr* htrdr = NULL; + ASSERT(ref); + cie = CONTAINER_OF(ref, struct htrdr_ran_wlen_cie_xyz, ref); + darray_double_release(&cie->cdf_X); + darray_double_release(&cie->cdf_Y); + darray_double_release(&cie->cdf_Z); + htrdr = cie->htrdr; + MEM_RM(htrdr_get_allocator(cie->htrdr), cie); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_ran_wlen_cie_xyz_create + (struct htrdr* htrdr, + const double range[2], /* Must be included in [380, 780] nanometers */ + const size_t bands_count, /* # bands used to discretize the CIE tristimulus */ + struct htrdr_ran_wlen_cie_xyz** out_cie) +{ + struct htrdr_ran_wlen_cie_xyz* cie = NULL; + size_t nbands = bands_count; + res_T res = RES_OK; + ASSERT(htrdr && range && nbands && out_cie); + + cie = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cie)); + if(!cie) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate the CIE XYZ data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&cie->ref); + darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_X); + darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_Y); + darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_Z); + cie->range[0] = range[0]; + cie->range[1] = range[1]; + htrdr_ref_get(htrdr); + cie->htrdr = htrdr; + + cie->band_len = (range[1] - range[0]) / (double)nbands; + + res = setup_cie_xyz(cie, FUNC_NAME, nbands); + if(res != RES_OK) goto error; + + htrdr_log(htrdr, "CIE XYZ spectral interval defined on [%g, %g] nanometers.\n", + range[0], range[1]); + +exit: + *out_cie = cie; + return res; +error: + if(cie) htrdr_ran_wlen_cie_xyz_ref_put(cie); + goto exit; +} + +void +htrdr_ran_wlen_cie_xyz_ref_get(struct htrdr_ran_wlen_cie_xyz* cie) +{ + ASSERT(cie); + ref_get(&cie->ref); +} + +void +htrdr_ran_wlen_cie_xyz_ref_put(struct htrdr_ran_wlen_cie_xyz* cie) +{ + ASSERT(cie); + ref_put(&cie->ref, release_cie_xyz); +} + +double +htrdr_ran_wlen_cie_xyz_sample_X + (struct htrdr_ran_wlen_cie_xyz* cie, + const double r0, + const double r1, + double* pdf) +{ + const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X), + darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1); + if(pdf) *pdf = cie->rcp_integral_X; + return wlen; +} + +double +htrdr_ran_wlen_cie_xyz_sample_Y + (struct htrdr_ran_wlen_cie_xyz* cie, + const double r0, + const double r1, + double* pdf) +{ + const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y), + darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1); + if(pdf) *pdf = cie->rcp_integral_Y; + return wlen; +} + +double +htrdr_ran_wlen_cie_xyz_sample_Z + (struct htrdr_ran_wlen_cie_xyz* cie, + const double r0, + const double r1, + double* pdf) +{ + const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z), + darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1); + if(pdf) *pdf = cie->rcp_integral_Z; + return wlen; +} + diff --git a/src/core/htrdr_ran_wlen_cie_xyz.h b/src/core/htrdr_ran_wlen_cie_xyz.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * 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 HTRDR_RAN_WLEN_CIE_XYZ_H +#define HTRDR_RAN_WLEN_CIE_XYZ_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Wavelength boundaries of the CIE XYZ color space in nanometers */ +#define HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT__ {380, 780} +static const double HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT[2] = + HTRDR_RAN_WLEN_CIE_XYZ_RANGE_DEFAULT__; + +/* Forward declarations */ +struct htrdr; +struct htrdr_ran_wlen_cie_xyz; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_ran_wlen_cie_xyz_create + (struct htrdr* htrdr, + const double range[2], /* Must be included in [380, 780] nanometers */ + const size_t nbands, /* # bands used to discretize the CIE tristimulus */ + struct htrdr_ran_wlen_cie_xyz** cie); + +HTRDR_CORE_API void +htrdr_ran_wlen_cie_xyz_ref_get + (struct htrdr_ran_wlen_cie_xyz* cie); + +HTRDR_CORE_API void +htrdr_ran_wlen_cie_xyz_ref_put + (struct htrdr_ran_wlen_cie_xyz* cie); + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_ran_wlen_cie_xyz_sample_X + (struct htrdr_ran_wlen_cie_xyz* cie, + const double r0, const double r1, /* Canonical numbers in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_ran_wlen_cie_xyz_sample_Y + (struct htrdr_ran_wlen_cie_xyz* cie, + const double r0, const double r1, /* Canonical number in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_ran_wlen_cie_xyz_sample_Z + (struct htrdr_ran_wlen_cie_xyz* cie, + const double r0, const double r1, /* Canonical number in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +END_DECLS + +#endif /* HTRDR_RAN_WLEN_CIE_XYZ_H */ + diff --git a/src/core/htrdr_ran_wlen_planck.c b/src/core/htrdr_ran_wlen_planck.c @@ -0,0 +1,365 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019 Université Paul Sabatier + * + * 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 _POSIX_C_SOURCE 200112L /* nextafter */ + +#include "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_log.h" +#include "core/htrdr_ran_wlen_planck.h" +#include "core/htrdr_spectral.h" + +#include <rsys/algorithm.h> +#include <rsys/cstr.h> +#include <rsys/dynamic_array_double.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +#include <math.h> /* nextafter */ + +struct htrdr_ran_wlen_planck { + struct darray_double pdf; + struct darray_double cdf; + double range[2]; /* Boundaries of the spectral integration interval */ + double band_len; /* Length in nanometers of a band */ + double ref_temperature; /* In Kelvin */ + size_t nbands; /* # bands */ + + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static res_T +setup_wlen_planck_ran_cdf + (struct htrdr_ran_wlen_planck* planck, + const char* func_name) +{ + double* pdf = NULL; + double* cdf = NULL; + double sum = 0; + size_t i; + res_T res = RES_OK; + ASSERT(planck && func_name); + ASSERT(planck->nbands != HTRDR_WLEN_RAN_PLANCK_CONTINUE); + + res = darray_double_resize(&planck->cdf, planck->nbands); + if(res != RES_OK) { + htrdr_log_err(planck->htrdr, + "%s: Error allocating the CDF of the spectral bands -- %s.\n", + func_name, res_to_cstr(res)); + goto error; + } + res = darray_double_resize(&planck->pdf, planck->nbands); + if(res != RES_OK) { + htrdr_log_err(planck->htrdr, + "%s: Error allocating the pdf of the spectral bands -- %s.\n", + func_name, res_to_cstr(res)); + goto error; + } + + cdf = darray_double_data_get(&planck->cdf); + pdf = darray_double_data_get(&planck->pdf); /* Now save pdf to correct weight */ + + htrdr_log(planck->htrdr, + "Number of bands of the spectrum cumulative: %lu\n", + (unsigned long)planck->nbands); + + /* Compute the *unnormalized* probability to sample a small band */ + FOR_EACH(i, 0, planck->nbands) { + double lambda_lo = planck->range[0] + (double)i * planck->band_len; + double lambda_hi = MMIN(lambda_lo + planck->band_len, planck->range[1]); + ASSERT(lambda_lo<= lambda_hi); + ASSERT(lambda_lo > planck->range[0] + || eq_eps(lambda_lo, planck->range[0], 1.e-6)); + ASSERT(lambda_lo < planck->range[1] + || eq_eps(lambda_lo, planck->range[1], 1.e-6)); + + /* Convert from nanometer to meter */ + lambda_lo *= 1.e-9; + lambda_hi *= 1.e-9; + + /* Compute the probability of the current band */ + pdf[i] = htrdr_blackbody_fraction + (lambda_lo, lambda_hi, planck->ref_temperature); + + /* Update the norm */ + sum += pdf[i]; + } + + /* Compute the cumulative of the previously computed probabilities */ + FOR_EACH(i, 0, planck->nbands) { + /* Normalize the probability */ + pdf[i] /= sum; + + /* Setup the cumulative */ + if(i == 0) { + cdf[i] = pdf[i]; + } else { + cdf[i] = pdf[i] + cdf[i-1]; + ASSERT(cdf[i] >= cdf[i-1]); + } + } + ASSERT(eq_eps(cdf[planck->nbands-1], 1, 1.e-6)); + cdf[planck->nbands - 1] = 1.0; /* Handle numerical issue */ + +exit: + return res; +error: + darray_double_clear(&planck->cdf); + darray_double_clear(&planck->pdf); + goto exit; +} + +static double +wlen_ran_sample_continue + (const struct htrdr_ran_wlen_planck* planck, + const double r, + const double range[2], /* In nanometer */ + const char* func_name, + double* pdf) +{ + /* Numerical parameters of the dichotomy search */ + const size_t MAX_ITER = 100; + const double EPSILON_LAMBDA_M = 1e-15; + const double EPSILON_BF = 1e-6; + + /* Local variables */ + double bf = 0; + double bf_prev = 0; + double bf_min_max = 0; + double lambda_m = 0; + double lambda_m_prev = 0; + double lambda_m_min = 0; + double lambda_m_max = 0; + double range_m[2] = {0, 0}; + size_t i; + + /* Check precondition */ + ASSERT(planck && func_name); + ASSERT(range && range[0] < range[1]); + ASSERT(0 <= r && r < 1); + + /* Convert the wavelength range in meters */ + range_m[0] = range[0] * 1.0e-9; + range_m[1] = range[1] * 1.0e-9; + + /* Setup the dichotomy search */ + lambda_m_min = range_m[0]; + lambda_m_max = range_m[1]; + bf_min_max = htrdr_blackbody_fraction + (range_m[0], range_m[1], planck->ref_temperature); + + /* Numerically search the lambda corresponding to the submitted canonical + * number */ + FOR_EACH(i, 0, MAX_ITER) { + double r_test; + lambda_m = (lambda_m_min + lambda_m_max) * 0.5; + bf = htrdr_blackbody_fraction + (range_m[0], lambda_m, planck->ref_temperature); + + r_test = bf / bf_min_max; + if(r_test < r) { + lambda_m_min = lambda_m; + } else { + lambda_m_max = lambda_m; + } + + if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA_M + || fabs(bf_prev - bf) < EPSILON_BF) + break; + + lambda_m_prev = lambda_m; + bf_prev = bf; + } + if(i >= MAX_ITER) { + htrdr_log_warn(planck->htrdr, + "%s: could not sample a wavelength in the range [%g, %g] nanometers " + "for the reference temperature %g Kelvin.\n", + func_name, SPLIT2(range), planck->ref_temperature); + } + + if(pdf) { + const double Tref = planck->ref_temperature; + const double B_lambda = htrdr_planck(lambda_m, lambda_m, Tref); + const double B_mean = htrdr_planck(range_m[0], range_m[1], Tref); + *pdf = B_lambda / (B_mean * (range_m[1]-range_m[0])); + *pdf *= 1.e-9; /* Transform from m^-1 to nm^-1 */ + } + + return lambda_m * 1.e9; /* Convert in nanometers */ +} + +static double +wlen_ran_sample_discrete + (const struct htrdr_ran_wlen_planck* planck, + const double r0, + const double r1, + const char* func_name, + double* pdf) +{ + const double* cdf = NULL; + const double* find = NULL; + double r0_next = nextafter(r0, DBL_MAX); + double band_range[2]; + double lambda = 0; + double pdf_continue = 0; + double pdf_band = 0; + size_t cdf_length = 0; + size_t i; + ASSERT(planck && planck->nbands != HTRDR_WLEN_RAN_PLANCK_CONTINUE); + ASSERT(0 <= r0 && r0 < 1); + ASSERT(0 <= r1 && r1 < 1); + (void)func_name; + (void)pdf_band; + + cdf = darray_double_cdata_get(&planck->cdf); + cdf_length = darray_double_size_get(&planck->cdf); + ASSERT(cdf_length > 0); + + /* Use r_next rather than r0 in order to find the first entry that is not less + * than *or equal* to r0 */ + find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); + ASSERT(find); + + i = (size_t)(find - cdf); + ASSERT(i < cdf_length && cdf[i] > r0 && (!i || cdf[i-1] <= r0)); + + band_range[0] = planck->range[0] + (double)i*planck->band_len; + band_range[1] = band_range[0] + planck->band_len; + + /* Fetch the pdf of the sampled band */ + pdf_band = darray_double_cdata_get(&planck->pdf)[i]; + + /* Uniformly sample a wavelength in the sampled band */ + lambda = band_range[0] + (band_range[1] - band_range[0]) * r1; + pdf_continue = 1.0 / (band_range[1] - band_range[0]); + + if(pdf) { + *pdf = pdf_band * pdf_continue; + } + + return lambda; +} + +static void +release_wlen_planck_ran(ref_T* ref) +{ + struct htrdr_ran_wlen_planck* planck = NULL; + struct htrdr* htrdr = NULL; + ASSERT(ref); + planck = CONTAINER_OF(ref, struct htrdr_ran_wlen_planck, ref); + darray_double_release(&planck->cdf); + darray_double_release(&planck->pdf); + htrdr = planck->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), planck); + htrdr_ref_put(planck->htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_ran_wlen_planck_create + (struct htrdr* htrdr, + /* range must be included in [200,1000] nm for shortwave or in [1000,100000] + * nanometers for longwave (thermal) */ + const double range[2], + const size_t nbands, /* # bands used to discretized CDF */ + const double ref_temperature, + struct htrdr_ran_wlen_planck** out_wlen_planck_ran) +{ + struct htrdr_ran_wlen_planck* planck = NULL; + res_T res = RES_OK; + ASSERT(htrdr && range && out_wlen_planck_ran && ref_temperature > 0); + ASSERT(ref_temperature > 0); + ASSERT(range[0] <= range[1]); + + planck = MEM_CALLOC(htrdr->allocator, 1, sizeof(*planck)); + if(!planck) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate longwave random variate data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&planck->ref); + darray_double_init(htrdr->allocator, &planck->cdf); + darray_double_init(htrdr->allocator, &planck->pdf); + htrdr_ref_get(htrdr); + planck->htrdr = htrdr; + + planck->range[0] = range[0]; + planck->range[1] = range[1]; + planck->ref_temperature = ref_temperature; + planck->nbands = nbands; + + if(nbands == HTRDR_WLEN_RAN_PLANCK_CONTINUE) { + planck->band_len = 0; + } else { + planck->band_len = (range[1] - range[0]) / (double)planck->nbands; + + res = setup_wlen_planck_ran_cdf(planck, FUNC_NAME); + if(res != RES_OK) goto error; + } + + htrdr_log(htrdr, "Spectral interval defined on [%g, %g] nanometers.\n", + range[0], range[1]); + +exit: + *out_wlen_planck_ran = planck; + return res; +error: + if(planck) htrdr_ran_wlen_planck_ref_put(planck); + goto exit; +} + +void +htrdr_ran_wlen_planck_ref_get(struct htrdr_ran_wlen_planck* planck) +{ + ASSERT(planck); + ref_get(&planck->ref); +} + +void +htrdr_ran_wlen_planck_ref_put(struct htrdr_ran_wlen_planck* planck) +{ + ASSERT(planck); + ref_put(&planck->ref, release_wlen_planck_ran); +} + +double +htrdr_ran_wlen_planck_sample + (const struct htrdr_ran_wlen_planck* planck, + const double r0, + const double r1, + double* pdf) +{ + ASSERT(planck); + if(planck->nbands != HTRDR_WLEN_RAN_PLANCK_CONTINUE) { /* Discrete */ + return wlen_ran_sample_discrete(planck, r0, r1, FUNC_NAME, pdf); + } else if(eq_eps(planck->range[0], planck->range[1], 1.e-6)) { + if(pdf) *pdf = 1; + return planck->range[0]; + } else { /* Continue */ + return wlen_ran_sample_continue + (planck, r0, planck->range, FUNC_NAME, pdf); + } +} + diff --git a/src/core/htrdr_ran_wlen_planck.h b/src/core/htrdr_ran_wlen_planck.h @@ -0,0 +1,61 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019 Université Paul Sabatier + * + * 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 HTRDR_RAN_WLEN_PLANCK_H +#define HTRDR_RAN_WLEN_PLANCK_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +#define HTRDR_WLEN_RAN_PLANCK_CONTINUE 0 + +/* Forward declarations */ +struct htrdr; +struct htrdr_ran_wlen_planck; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_ran_wlen_planck_create + (struct htrdr* htrdr, + const double range[2], + /* # bands used to discretisze the spectral domain. HTRDR_WLEN_RAN_CONTINUE + * <=> no discretisation */ + const size_t nbands, /* Hint on #bands used to discretised th CDF */ + const double ref_temperature, /* Reference temperature */ + struct htrdr_ran_wlen_planck** planck); + +HTRDR_CORE_API void +htrdr_ran_wlen_planck_ref_get + (struct htrdr_ran_wlen_planck* planck); + +HTRDR_CORE_API void +htrdr_ran_wlen_planck_ref_put + (struct htrdr_ran_wlen_planck* planck); + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_ran_wlen_planck_sample + (const struct htrdr_ran_wlen_planck* planck, + const double r0, /* Canonical number in [0, 1[ */ + const double r1, /* Canonical number in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +END_DECLS + +#endif /* HTRDR_RAN_WLEN_PLANCK_H */ + diff --git a/src/planeto/htrdr_planeto.c b/src/planeto/htrdr_planeto.c @@ -18,8 +18,8 @@ #define _POSIX_C_SOURCE 200112L /* fdopen, nextafter, rint */ #include "core/htrdr.h" -#include "core/htrdr_cie_xyz.h" -#include "core/htrdr_ran_wlen.h" +#include "core/htrdr_ran_wlen_cie_xyz.h" +#include "core/htrdr_ran_wlen_planck.h" #include "core/htrdr_log.h" #include "planeto/htrdr_planeto.h" @@ -247,14 +247,14 @@ setup_spectral_domain /* Planck distribution */ case HTRDR_SPECTRAL_LW: case HTRDR_SPECTRAL_SW: - res = htrdr_ran_wlen_create(cmd->htrdr, cmd->spectral_domain.wlen_range, - nintervals, cmd->spectral_domain.ref_temperature, - &cmd->ran_wlen_planck); + res = htrdr_ran_wlen_planck_create(cmd->htrdr, + cmd->spectral_domain.wlen_range, nintervals, + cmd->spectral_domain.ref_temperature, &cmd->planck); break; /* CIE XYZ distribution */ case HTRDR_SPECTRAL_SW_CIE_XYZ: - res = htrdr_cie_xyz_create(cmd->htrdr, cmd->spectral_domain.wlen_range, - nintervals, &cmd->ran_wlen_cie); + res = htrdr_ran_wlen_cie_xyz_create(cmd->htrdr, + cmd->spectral_domain.wlen_range, nintervals, &cmd->cie); break; default: FATAL("Unreachable code\n"); break; @@ -443,8 +443,8 @@ planeto_release(ref_T* ref) if(cmd->atmosphere) RNATM(ref_put(cmd->atmosphere)); if(cmd->ground) RNGRD(ref_put(cmd->ground)); if(cmd->source) htrdr_planeto_source_ref_put(cmd->source); - if(cmd->ran_wlen_cie) htrdr_cie_xyz_ref_put(cmd->ran_wlen_cie); - if(cmd->ran_wlen_planck) htrdr_ran_wlen_ref_put(cmd->ran_wlen_planck); + if(cmd->cie) htrdr_ran_wlen_cie_xyz_ref_put(cmd->cie); + if(cmd->planck) htrdr_ran_wlen_planck_ref_put(cmd->planck); if(cmd->octrees_storage) CHK(fclose(cmd->octrees_storage) == 0); if(cmd->output && cmd->output != stdout) CHK(fclose(cmd->output) == 0); if(cmd->buf) htrdr_buffer_ref_put(cmd->buf); diff --git a/src/planeto/htrdr_planeto_c.h b/src/planeto/htrdr_planeto_c.h @@ -29,9 +29,9 @@ /* Forward declarations */ struct htrdr; -struct htrdr_cie; struct htrdr_pixel_format; -struct htrdr_ran_wlen; +struct htrdr_ran_wlen_cie_xyz; +struct htrdr_ran_wlen_planck; struct rnatm; struct rngrd; struct scam; @@ -70,8 +70,8 @@ struct htrdr_planeto { struct htrdr_planeto_source* source; struct htrdr_args_spectral spectral_domain; - struct htrdr_cie_xyz* ran_wlen_cie; /* HTRDR_SPECTRAL_SW_CIE_XYZ */ - struct htrdr_ran_wlen* ran_wlen_planck; /* HTRDR_SPECTRAL_<LW|SW> */ + struct htrdr_ran_wlen_cie_xyz* cie; /* HTRDR_SPECTRAL_SW_CIE_XYZ */ + struct htrdr_ran_wlen_planck* planck; /* HTRDR_SPECTRAL_<LW|SW> */ FILE* octrees_storage;