htgop

Optical properties of a gas mixture
git clone git://git.meso-star.fr/htgop.git
Log | Files | Refs | README | LICENSE

commit fb44c28d0176fa571d5c96bd754e4c27903c45a0
parent 69d3ddf421892235c04c476ac25f4dc53fe5e6f4
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Tue, 31 Jul 2018 11:19:07 +0200

Refactoring and implement htgop_fetch_<sw|lw>_<ka|ks> functions

Diffstat:
Mcmake/CMakeLists.txt | 5++++-
Msrc/htgop.c | 325+------------------------------------------------------------------------------
Msrc/htgop.h | 113++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/htgop_c.h | 14+++++++++++++-
Asrc/htgop_fetch_radiative_properties.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/htgop_fetch_radiative_properties.h | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/htgop_parse_layers_spectral_intervals_data.h | 6+++---
Asrc/htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c | 285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_htgop_sample.c | 10++++++----
9 files changed, 526 insertions(+), 371 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -41,9 +41,12 @@ set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(HTGOP_FILES_SRC - htgop.c) + htgop.c + htgop_fetch_radiative_properties.c + htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c) set(SSOL_FILES_INC htgop_c.h + htgop_fetch_radiative_properties.h htgop_layer.h htgop_parse_layers_spectral_intervals_data.h htgop_reader.h diff --git a/src/htgop.c b/src/htgop.c @@ -26,14 +26,6 @@ #include <math.h> -#define WNUM_TO_WLEN(Num) (1.0e7 / ((double)Num)) -#define WLEN_TO_WNUM(Len) WNUM_TO_WLEN(Len) - -#define CIE_XYZ_WAVELENGTH_MIN 380.0 /* In nanometer */ -#define CIE_XYZ_WAVELENGTH_MAX 780.0 /* In nanometer */ -#define CIE_XYZ_WAVENUMBER_MIN WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX) /*In cm^-1*/ -#define CIE_XYZ_WAVENUMBER_MAX WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN) /*In cm^-1*/ - /******************************************************************************* * Helper functions ******************************************************************************/ @@ -53,14 +45,6 @@ log_msg } static INLINE int -cmp_dbl(const void* a, const void* b) -{ - const double d0 = *((const double*)a); - const double d1 = *((const double*)b); - return d0 < d1 ? -1 : (d0 > d1 ? 1 : 0); -} - -static INLINE int cmp_lvl(const void* key, const void* data) { const double height = *((const double*)key); @@ -68,215 +52,6 @@ cmp_lvl(const void* key, const void* data) return height < lvl->height ? -1 : (height > lvl->height ? 1 : 0); } -static FINLINE 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_hi + 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 FINLINE 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 res_T -compute_CIE_XYZ_cdf(struct htgop* htgop) -{ - enum { X, Y, Z }; /* Helper constant */ - size_t iband_min, iband_max; - const double* wnums = NULL; - double sum[3] = {0,0,0}; - double* pdf[3] = {NULL, NULL, NULL}; - double* cdf[3] = {NULL, NULL, NULL}; - size_t i, n; - res_T res = RES_OK; - ASSERT(htgop); - - HTGOP(get_sw_spectral_intervals_wave_numbers(htgop, &wnums)); - HTGOP(get_sw_spectral_intervals_count(htgop, &n)); - ASSERT(n); - - /* Allocate and reset the memory space for the tristimulus CDF */ - #define SETUP(Stimulus) { \ - res = darray_double_resize(&htgop->sw_ ## Stimulus ## _cdf, n); \ - if(res != RES_OK) { \ - log_err(htgop, \ - "Could not reserve the memory space for the CDF " \ - "of the "STR(X)" stimulus.\n"); \ - goto error; \ - } \ - memset(darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf), 0, \ - n*sizeof(double)); \ - cdf[Stimulus] = darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf); \ - pdf[Stimulus] = cdf[Stimulus]; \ - } (void)0 - SETUP(X); - SETUP(Y); - SETUP(Z); - #undef RESERVE - - ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MAX, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN), 1.e-6)); - ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MIN, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX), 1.e-6)); - ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MAX, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MIN), 1.e-6)); - ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MIN, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MAX), 1.e-6)); - - /* The sw spectral intervals does not intersect the CIE XYZ interval */ - if(wnums[0] > CIE_XYZ_WAVENUMBER_MAX || wnums[n] < CIE_XYZ_WAVENUMBER_MIN) { - log_warn(htgop, - "The loaded SW spectral intervals does not intersect " - "the CIE XYZ interval.\n"); - goto exit; - } - - /* Search the lower and upper bound of the bands that overlap the XYZ - * spectral interval. Use a simple linear search since the overall number of - * bands should be small */ - iband_min = 0, iband_max = n-1; - FOR_EACH(i, 0, n) { - if(wnums[i]<=CIE_XYZ_WAVENUMBER_MIN && CIE_XYZ_WAVENUMBER_MIN<=wnums[i+1]) - iband_min = i; - if(wnums[i]<=CIE_XYZ_WAVENUMBER_MAX && CIE_XYZ_WAVENUMBER_MAX<=wnums[i+1]) - iband_max = i; - } - ASSERT(iband_min <= iband_max); - - /* Compute the *unormalized* pdf of the tristimulus */ - FOR_EACH(i, iband_min, iband_max+1) { - const double lambda_lo = WNUM_TO_WLEN(wnums[i+1]); - const double lambda_hi = WNUM_TO_WLEN(wnums[i+0]); - ASSERT(lambda_lo <= lambda_hi); - 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]; - } - - /* Compute the cdf of the tristimulus. Note that the upper bound is 'n' and not - * 'iband_max+1' in order to ensure that the CDF is strictly increasing. */ - FOR_EACH(i, iband_min, n) { - /* 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(eq_eps(cdf[X][n-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Y][n-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Z][n-1], 1, 1.e-6)); - -#ifndef NDEBUG - FOR_EACH(i, 1, n) { - ASSERT(cdf[X][i] >= cdf[X][i-1]); - ASSERT(cdf[Y][i] >= cdf[Y][i-1]); - ASSERT(cdf[Z][i] >= cdf[Z][i-1]); - } -#endif - - /* Handle numerical issue */ - cdf[X][n-1] = 1.0; - cdf[Y][n-1] = 1.0; - cdf[Z][n-1] = 1.0; - -exit: - return res; -error: - darray_double_clear(&htgop->sw_X_cdf); - darray_double_clear(&htgop->sw_Y_cdf); - darray_double_clear(&htgop->sw_Z_cdf); - goto exit; -} - -static INLINE res_T -sample_sw_spectral_interval_CIE - (const struct htgop* htgop, - const char* caller, - const double* cdf, - const size_t cdf_length, - const double r, /* Canonical number in [0, 1[ */ - size_t* id) -{ - double r_next = nextafter(r, DBL_MAX); - double* find; - size_t i; - ASSERT(htgop && cdf && cdf_length); - - if(!id) return RES_BAD_ARG; - if(r < 0 || r >= 1) { - log_err(htgop, "%s: invalid canonical random number `%g'.\n", caller, r); - return RES_BAD_ARG; - } - - /* Use r_next rather than r in order to find the first entry that is not less - * that *or equal* to r */ - find = search_lower_bound(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); - if(!find) { /* <=> CIE XYZ band does not intersect the loaded intervals */ - log_err(htgop, - "%s: could not sample a spectral interval in the CIE XYZ spectral band.\n", - caller); - return RES_BAD_OP; - } - - i = (size_t)(find - cdf); - ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); - *id = i; - return RES_OK; -} - - static res_T parse_spectral_intervals (struct htgop* htgop, @@ -529,7 +304,7 @@ load_stream(struct htgop* htgop, FILE* stream, const char* stream_name) #undef CALL /* Compute the cdf for the SW CIE XYZ spectral interval */ - res = compute_CIE_XYZ_cdf(htgop); + res = compute_CIE_1931_XYZ_cdf(htgop); if(res != RES_OK) goto error; exit: @@ -912,9 +687,8 @@ htgop_spectral_interval_sample_quadrature return RES_BAD_ARG; } - /* Use r_next rather than r in order to find the first entry that is not less - * that *or equal* to r */ + * than *or equal* to r */ find = search_lower_bound(&r_next, specint->quadrature_cdf, specint->quadrature_length, sizeof(double), cmp_dbl); ASSERT(find); @@ -927,45 +701,6 @@ htgop_spectral_interval_sample_quadrature } res_T -htgop_sample_sw_spectral_interval_CIE_X - (const struct htgop* htgop, - const double r, /* Canonical number in [0, 1[ */ - size_t* ispecint) -{ - if(!htgop) return RES_BAD_ARG; - return sample_sw_spectral_interval_CIE(htgop, FUNC_NAME, - darray_double_cdata_get(&htgop->sw_X_cdf), - darray_double_size_get(&htgop->sw_X_cdf), - r, ispecint); -} - -res_T -htgop_sample_sw_spectral_interval_CIE_Y - (const struct htgop* htgop, - const double r, /* Canonical number in [0, 1[ */ - size_t* ispecint) -{ - if(!htgop) return RES_BAD_ARG; - return sample_sw_spectral_interval_CIE(htgop, FUNC_NAME, - darray_double_cdata_get(&htgop->sw_Y_cdf), - darray_double_size_get(&htgop->sw_Y_cdf), - r, ispecint); -} - -res_T -htgop_sample_sw_spectral_interval_CIE_Z - (const struct htgop* htgop, - const double r, /* Canonical number in [0, 1[ */ - size_t* ispecint) -{ - if(!htgop) return RES_BAD_ARG; - return sample_sw_spectral_interval_CIE(htgop, FUNC_NAME, - darray_double_cdata_get(&htgop->sw_Z_cdf), - darray_double_size_get(&htgop->sw_Z_cdf), - r, ispecint); -} - -res_T htgop_position_to_layer_id (const struct htgop* htgop, const double pos, size_t* ilayer) { @@ -1001,62 +736,6 @@ htgop_position_to_layer_id return RES_OK; } -res_T -htgop_layer_fetch_sw_ka - (const struct htgop_layer* layer, - const size_t ispecint, - const size_t iquad_point, - const double x_h2o, /* H2O mol / mixture mol */ - double* out_ka) -{ - struct htgop_layer_sw_spectral_interval specint; - struct htgop_layer_sw_spectral_interval_tab tab; - double* find; - double x1, x2, k1, k2; - double ka; - res_T res = RES_OK; - - if(!layer || !out_ka) return RES_BAD_ARG; - - find = search_lower_bound(&x_h2o, layer->x_h2o_tab, layer->tab_length, - sizeof(double), cmp_dbl); - if(!find) return RES_BAD_ARG; - - res = htgop_layer_get_sw_spectral_interval(layer, ispecint, &specint); - if(res != RES_OK) return res; - res = htgop_layer_sw_spectral_interval_get_tab(&specint, iquad_point, &tab); - if(res != RES_OK) return res; - - ASSERT(layer->tab_length); - ASSERT(tab.tab_length == layer->tab_length); - - if(find == layer->x_h2o_tab) { - ASSERT(layer->x_h2o_tab[0] >= x_h2o); - x2 = layer->x_h2o_tab[0]; - k2 = tab.ka_tab[0]; - ka = k2 / x2*x_h2o; - } else { - const size_t i = (size_t)(find - layer->x_h2o_tab); - ASSERT(i < layer->tab_length); - ASSERT(layer->x_h2o_tab[i-0] >= x_h2o); - ASSERT(layer->x_h2o_tab[i-1] < x_h2o); - x1 = layer->x_h2o_tab[i-1]; - x2 = layer->x_h2o_tab[i-0]; - k1 = tab.ka_tab[i-1]; - k2 = tab.ka_tab[i-0]; - - if(!k1 || !k2) { - ka = k1 + (k2-k1)/(x2-x1) * (x_h2o-x1); - } else { - const double alpha = (log(k2) - log(k1))/(log(x1) - log(x2)); - const double beta = log(k1) - alpha * log(x1); - ka = exp(alpha * log(x_h2o) + beta); - } - } - *out_ka = ka; - return RES_OK; -} - /******************************************************************************* * Local functions ******************************************************************************/ diff --git a/src/htgop.h b/src/htgop.h @@ -51,7 +51,7 @@ struct htgop_ground { struct htgop_spectral_interval { double wave_numbers[2]; /* Lower/Upper wave number of the interval in cm^-1 */ - const double* quadrature_pdf; /* PDF of the quadrature */ + const double* quadrature_pdf; /* Normalized PDF of the quadrature */ const double* quadrature_cdf; /* CDF of the quadrature */ size_t quadrature_length; /* #quadrature points of the interval */ const void* data__; /* Internal data */ @@ -80,7 +80,7 @@ struct htgop_layer_lw_spectral_interval { struct htgop_layer_lw_spectral_interval_tab { const double* ka_tab; /* Tabulated absorption coef */ - size_t tab_length; + size_t tab_length; /* == lengh of the tabulated xH2O */ }; struct htgop_layer_sw_spectral_interval { @@ -93,13 +93,13 @@ struct htgop_layer_sw_spectral_interval { struct htgop_layer_sw_spectral_interval_tab { const double* ka_tab; /* Tabulated absorption coef */ const double* ks_tab; /* Tabulated scattering coef */ - size_t tab_length; + size_t tab_length; /* == lengh of the tabulated xH2O */ }; -BEGIN_DECLS +BEGIN_DECLS /* HTGOP API */ /******************************************************************************* - * HTGOP API + * HTGOP device management ******************************************************************************/ HTGOP_API res_T htgop_create @@ -116,6 +116,9 @@ HTGOP_API res_T htgop_ref_put (struct htgop* htgop); +/******************************************************************************* + * Loading functions + ******************************************************************************/ HTGOP_API res_T htgop_load (struct htgop* htgop, @@ -126,6 +129,9 @@ htgop_load_stream (struct htgop* htgop, FILE* stream); +/******************************************************************************* + * Getters of the the loaded data + ******************************************************************************/ HTGOP_API res_T htgop_get_ground (const struct htgop* htgop, @@ -164,24 +170,6 @@ htgop_get_sw_spectral_intervals_count size_t* nspecints); HTGOP_API res_T -htgop_sample_sw_spectral_interval_CIE_X - (const struct htgop* htgop, - const double r, /* Canonical random number in [0, 1[ */ - size_t* ispecint); /* Id of the sampled interval */ - -HTGOP_API res_T -htgop_sample_sw_spectral_interval_CIE_Y - (const struct htgop* htgop, - const double r, /* Canonical random number in [0, 1[ */ - size_t* ispecint); /* Id of the sampled interval */ - -HTGOP_API res_T -htgop_sample_sw_spectral_interval_CIE_Z - (const struct htgop* htgop, - const double r, /* Canonical random number in [0, 1[ */ - size_t* ispecint); /* Id of the sampled interval */ - -HTGOP_API res_T htgop_get_lw_spectral_intervals_wave_numbers (const struct htgop* htgop, const double* wave_numbers[]); @@ -204,12 +192,6 @@ htgop_get_sw_spectral_interval struct htgop_spectral_interval* interval); HTGOP_API res_T -htgop_spectral_interval_sample_quadrature - (const struct htgop_spectral_interval* interval, - const double r, /* Canonical random number in [0, 1[ */ - size_t* iquad_point); /* Id of the sample quadrature point */ - -HTGOP_API res_T htgop_layer_get_lw_spectral_interval (const struct htgop_layer* layer, const size_t ispectral_interval, @@ -233,13 +215,30 @@ htgop_layer_sw_spectral_interval_get_tab const size_t iquadrature_point, struct htgop_layer_sw_spectral_interval_tab* tab); -HTGOP_API res_T -htgop_position_to_layer_id - (const struct htgop* htgop, - const double pos, - size_t* ilayer); - -/* TODO comment the interpolation procedure */ +/******************************************************************************* + * The following functions Retrieve the absorption/scattering coefficient in a + * layer, in short/long wave, at a given spectral interval for a specific + * quadrature point with respect to a submitted water vapor molar fraction + * x_h2o. x_h2o is compared to the values of tabulated water vapor molar + * fractions of the layer: x_h2o in [x1, x2], with the corresponding tabulated + * values of the required radiative property k(x1) = k1 and k(x2) = k2. Now + * there is 3 cases: + * + * - If x_h2o is smaller than the first tabulated value of the water vapor molar + * fraction, x1 and k1 is assumed to be null and k(x_h2o) is computed with a + * linear interpolation: + * k(x_h2o) = x_h2o * k2/x2 + * + * - If either k1 or k2 is null, k(x_h2o) is also computed with a simple linear + * interpolation: + * k(x_h2o) = k1 + (k2-k1)/(x2-x1) * (x_h2o-x1) + * + * - Otherwise, k(x_h2o) is computed as bellow: + * k(x_h2o) = exp(alpha * ln(x_h2o) + beta) + * with: + * alpha = (ln(k2) - ln(k1)) / (ln(x2) - ln(x1)) + * beta = ln(k1) - alpha * ln(x1) + ******************************************************************************/ HTGOP_API res_T htgop_layer_fetch_lw_ka (const struct htgop_layer* layer, @@ -248,16 +247,14 @@ htgop_layer_fetch_lw_ka const double x_h2o, /* H2O mol / mixture mol */ double* ka); -/* TODO comment the interpolation procedure */ HTGOP_API res_T htgop_layer_fetch_sw_ka (const struct htgop_layer* layer, const size_t ispecint, const size_t iquad_point, const double x_h2o, /* H2O mol / mixture mol */ - double* ka); + double* ka); -/* TODO comment the interpolation procedure */ HTGOP_API res_T htgop_layer_fetch_sw_ks (const struct htgop_layer* layer, @@ -266,6 +263,44 @@ htgop_layer_fetch_sw_ks const double x_h2o, /* H2O mol / mixture mol */ double* ks); +/******************************************************************************* + * Sample of a short wave spectral interval according to the CIE 1931 XYZ + * tristimulus values. + ******************************************************************************/ +HTGOP_API res_T +htgop_sample_sw_spectral_interval_CIE_1931_X + (const struct htgop* htgop, + const double r, /* Canonical random number in [0, 1[ */ + size_t* ispecint); /* Id of the sampled interval */ + +HTGOP_API res_T +htgop_sample_sw_spectral_interval_CIE_1931_Y + (const struct htgop* htgop, + const double r, /* Canonical random number in [0, 1[ */ + size_t* ispecint); /* Id of the sampled interval */ + +HTGOP_API res_T +htgop_sample_sw_spectral_interval_CIE_1931_Z + (const struct htgop* htgop, + const double r, /* Canonical random number in [0, 1[ */ + size_t* ispecint); /* Id of the sampled interval */ + +/******************************************************************************* + * Miscellaneous functions + ******************************************************************************/ +/* Return the layer index into which the subimitted a 1D position lies */ +HTGOP_API res_T +htgop_position_to_layer_id + (const struct htgop* htgop, + const double pos, + size_t* ilayer); + +HTGOP_API res_T +htgop_spectral_interval_sample_quadrature + (const struct htgop_spectral_interval* interval, + const double r, /* Canonical random number in [0, 1[ */ + size_t* iquad_point); /* Id of the sample quadrature point */ + END_DECLS #endif /* HTGOP */ diff --git a/src/htgop_c.h b/src/htgop_c.h @@ -34,7 +34,7 @@ struct htgop { struct spectral_intervals lw_specints; struct spectral_intervals sw_specints; - /* Cumulative distribution function for the Short Wave CIE XYZ tristimulus */ + /* Cumulative distribution function for the Short Wave CIE XYZ values */ struct darray_double sw_X_cdf; struct darray_double sw_Y_cdf; struct darray_double sw_Z_cdf; @@ -48,6 +48,14 @@ struct htgop { ref_T ref; }; +static INLINE int +cmp_dbl(const void* a, const void* b) +{ + const double d0 = *((const double*)a); + const double d1 = *((const double*)b); + return d0 < d1 ? -1 : (d0 > d1 ? 1 : 0); +} + extern LOCAL_SYM void log_err (const struct htgop* htgop, @@ -68,5 +76,9 @@ log_warn #endif ; +extern LOCAL_SYM res_T +compute_CIE_1931_XYZ_cdf + (struct htgop* htgop); + #endif /* HTGOP_C_H */ diff --git a/src/htgop_fetch_radiative_properties.c b/src/htgop_fetch_radiative_properties.c @@ -0,0 +1,38 @@ +/* Copyright (C) |Meso|Star> 2018 (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 "htgop.h" +#include "htgop_c.h" + +#include <rsys/algorithm.h> + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +/* Generate the htgop_layer_fetch_lw_ka function */ +#define LAYER_SPECINT_DOMAIN lw +#define LAYER_SPECINT_DATA ka +#include "htgop_fetch_radiative_properties.h" + +/* Generate the htgop_layer_fetch_sw_ka function */ +#define LAYER_SPECINT_DOMAIN sw +#define LAYER_SPECINT_DATA ka +#include "htgop_fetch_radiative_properties.h" + +/* Generate the htgop_layer_fetch_sw_ks function */ +#define LAYER_SPECINT_DOMAIN sw +#define LAYER_SPECINT_DATA ks +#include "htgop_fetch_radiative_properties.h" + diff --git a/src/htgop_fetch_radiative_properties.h b/src/htgop_fetch_radiative_properties.h @@ -0,0 +1,101 @@ +/* Copyright (C) |Meso|Star> 2018 (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/>. */ + +#if !defined(LAYER_SPECINT_DATA) || !defined(LAYER_SPECINT_DOMAIN) + #error "Missing the LAYER_SPECINT_<DATA|DOMAIN> macro." +#endif + +/* + * Generate the functions that fetch and interpolate a radiative property in a + * given spectral domain + */ + +/* Helper macros */ +#define SPECINT \ + CONCAT(CONCAT(htgop_layer_, LAYER_SPECINT_DOMAIN),_spectral_interval) +#define SPECINT_TAB \ + CONCAT(CONCAT(htgop_layer_, LAYER_SPECINT_DOMAIN),_spectral_interval_tab) +#define GET_SPECINT \ + CONCAT(CONCAT(htgop_layer_get_, LAYER_SPECINT_DOMAIN), _spectral_interval) +#define GET_SPECINT_TAB \ + CONCAT(CONCAT(htgop_layer_, LAYER_SPECINT_DOMAIN), _spectral_interval_get_tab) +#define K_tab CONCAT(LAYER_SPECINT_DATA, _tab) + +res_T +CONCAT(CONCAT(CONCAT(htgop_layer_fetch_,LAYER_SPECINT_DOMAIN),_),LAYER_SPECINT_DATA) + (const struct htgop_layer* layer, + const size_t ispecint, + const size_t iquad_point, + const double x_h2o, /* H2O mol / mixture mol */ + double* out_k) +{ + struct SPECINT specint; + struct SPECINT_TAB tab; + double* find; + double x1, x2, k1, k2; + double k; + res_T res = RES_OK; + + if(!layer || !out_k) return RES_BAD_ARG; + + find = search_lower_bound(&x_h2o, layer->x_h2o_tab, layer->tab_length, + sizeof(double), cmp_dbl); + if(!find) return RES_BAD_ARG; + + res = GET_SPECINT(layer, ispecint, &specint); + if(res != RES_OK) return res; + res = GET_SPECINT_TAB(&specint, iquad_point, &tab); + if(res != RES_OK) return res; + + ASSERT(layer->tab_length); + ASSERT(tab.tab_length == layer->tab_length); + + if(find == layer->x_h2o_tab) { + /* The submitted x_h2o is smaller that the first tabulated value of the + * water vapor molar fraction. Use a simple linear interpolation to define + * K by assuming that k1 and x1 is null */ + ASSERT(layer->x_h2o_tab[0] >= x_h2o); + x2 = layer->x_h2o_tab[0]; + k2 = tab.K_tab[0]; + k = k2 / x2*x_h2o; + } else { + const size_t i = (size_t)(find - layer->x_h2o_tab); + ASSERT(i < layer->tab_length); + ASSERT(layer->x_h2o_tab[i-0] >= x_h2o); + ASSERT(layer->x_h2o_tab[i-1] < x_h2o); + x1 = layer->x_h2o_tab[i-1]; + x2 = layer->x_h2o_tab[i-0]; + k1 = tab.K_tab[i-1]; + k2 = tab.K_tab[i-0]; + + if(!k1 || !k2) { /* Linear interpolation */ + k = k1 + (k2-k1)/(x2-x1) * (x_h2o-x1); + } else { + const double alpha = (log(k2) - log(k1))/(log(x1) - log(x2)); + const double beta = log(k1) - alpha * log(x1); + k = exp(alpha * log(x_h2o) + beta); + } + } + *out_k = k; + return RES_OK; +} + +#undef SPECINT +#undef SPECINT_TAB +#undef GET_SPECINT +#undef GET_SPECINT_TAB +#undef K_tab +#undef LAYER_SPECINT_DATA +#undef LAYER_SPECINT_DOMAIN diff --git a/src/htgop_parse_layers_spectral_intervals_data.h b/src/htgop_parse_layers_spectral_intervals_data.h @@ -14,12 +14,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* - * Generate the function parsing a per layer and per spectral interval data for - * a given domain long/short wave. + * Generate the function that parses a per layer and per spectral interval data + * for a given domain. */ #if !defined(LAYER_SPECINT_DATA) || !defined(LAYER_SPECINT_DOMAIN) - #error "Missing the LAYER_SPECINT_<DATA|XDOMAIN> macro." + #error "Missing the LAYER_SPECINT_<DATA|DOMAIN> macro." #endif #define DATA CONCAT(CONCAT(LAYER_SPECINT_DATA, _), LAYER_SPECINT_DOMAIN) diff --git a/src/htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c b/src/htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c @@ -0,0 +1,285 @@ +/* Copyright (C) |Meso|Star> 2018 (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 support */ + +#include "htgop.h" +#include "htgop_c.h" + +#include <rsys/algorithm.h> + +/* Helper macros to convert from wave number to wavelength and vice versa */ +#define WNUM_TO_WLEN(Num) (1.0e7 / ((double)Num)) +#define WLEN_TO_WNUM(Len) WNUM_TO_WLEN(Len) + +/* Boundaries of the CIE XYZ color space in wavelength and wave number */ +#define CIE_XYZ_WAVELENGTH_MIN 380.0 /* In nanometer */ +#define CIE_XYZ_WAVELENGTH_MAX 780.0 /* In nanometer */ +#define CIE_XYZ_WAVENUMBER_MIN WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX) /*In cm^-1*/ +#define CIE_XYZ_WAVENUMBER_MAX WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN) /*In cm^-1*/ + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static FINLINE 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_hi + 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 FINLINE 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 res_T +sample_sw_spectral_interval_CIE + (const struct htgop* htgop, + const double* cdf, + const size_t cdf_length, + const double r, /* Canonical number in [0, 1[ */ + size_t* id) +{ + double r_next = nextafter(r, DBL_MAX); + double* find; + size_t i; + ASSERT(htgop && cdf && cdf_length); + + if(!id) return RES_BAD_ARG; + if(r < 0 || r >= 1) { + log_err(htgop, "Could nont sample a spectral interval in the CIE XYZ color " + "space: invalid canonical random number `%g'.\n", r); + return RES_BAD_ARG; + } + + /* 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(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); + if(!find) { /* <=> CIE XYZ band does not intersect the loaded intervals */ + log_err(htgop, "Could not sample a spectral interval in the CIE XYZ color " + "space : the loaded data does not overlap the CIE XYZ color space.\n"); + return RES_BAD_OP; + } + + i = (size_t)(find - cdf); + ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); + *id = i; + return RES_OK; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +htgop_sample_sw_spectral_interval_CIE_1931_X + (const struct htgop* htgop, + const double r, /* Canonical number in [0, 1[ */ + size_t* ispecint) +{ + if(!htgop) return RES_BAD_ARG; + return sample_sw_spectral_interval_CIE + (htgop, darray_double_cdata_get(&htgop->sw_X_cdf), + darray_double_size_get(&htgop->sw_X_cdf), r, ispecint); +} + +res_T +htgop_sample_sw_spectral_interval_CIE_1931_Y + (const struct htgop* htgop, + const double r, /* Canonical number in [0, 1[ */ + size_t* ispecint) +{ + if(!htgop) return RES_BAD_ARG; + return sample_sw_spectral_interval_CIE + (htgop, darray_double_cdata_get(&htgop->sw_Y_cdf), + darray_double_size_get(&htgop->sw_Y_cdf), r, ispecint); +} + +res_T +htgop_sample_sw_spectral_interval_CIE_1931_Z + (const struct htgop* htgop, + const double r, /* Canonical number in [0, 1[ */ + size_t* ispecint) +{ + if(!htgop) return RES_BAD_ARG; + return sample_sw_spectral_interval_CIE + (htgop, darray_double_cdata_get(&htgop->sw_Z_cdf), + darray_double_size_get(&htgop->sw_Z_cdf), r, ispecint); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +compute_CIE_1931_XYZ_cdf(struct htgop* htgop) +{ + enum { X, Y, Z }; /* Helper constant */ + size_t iband_min, iband_max; + const double* wnums = NULL; + double sum[3] = {0,0,0}; + double* pdf[3] = {NULL, NULL, NULL}; + double* cdf[3] = {NULL, NULL, NULL}; + size_t i, n; + res_T res = RES_OK; + ASSERT(htgop); + + HTGOP(get_sw_spectral_intervals_wave_numbers(htgop, &wnums)); + HTGOP(get_sw_spectral_intervals_count(htgop, &n)); + ASSERT(n); + + /* Allocate and reset the memory space for the tristimulus CDF */ + #define SETUP_STIMULUS(Stimulus) { \ + res = darray_double_resize(&htgop->sw_ ## Stimulus ## _cdf, n); \ + if(res != RES_OK) { \ + log_err(htgop, \ + "Could not reserve the memory space for the CDF " \ + "of the "STR(X)" stimulus.\n"); \ + goto error; \ + } \ + memset(darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf), 0, \ + n*sizeof(double)); \ + cdf[Stimulus] = darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf); \ + pdf[Stimulus] = cdf[Stimulus]; \ + } (void)0 + SETUP_STIMULUS(X); + SETUP_STIMULUS(Y); + SETUP_STIMULUS(Z); + #undef RESERVE + + ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MAX, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN), 1.e-6)); + ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MIN, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX), 1.e-6)); + ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MAX, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MIN), 1.e-6)); + ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MIN, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MAX), 1.e-6)); + + /* The sw spectral intervals does not intersect the CIE XYZ interval */ + if(wnums[0] > CIE_XYZ_WAVENUMBER_MAX || wnums[n] < CIE_XYZ_WAVENUMBER_MIN) { + log_warn(htgop, + "The loaded SW spectral intervals does not intersect " + "the CIE XYZ interval.\n"); + goto exit; + } + + /* Search the lower and upper bound of the bands that overlap the XYZ + * spectral interval. Use a simple linear search since the overall number of + * bands should be small */ + iband_min = 0, iband_max = n-1; + FOR_EACH(i, 0, n) { + if(wnums[i]<=CIE_XYZ_WAVENUMBER_MIN && CIE_XYZ_WAVENUMBER_MIN<=wnums[i+1]) + iband_min = i; + if(wnums[i]<=CIE_XYZ_WAVENUMBER_MAX && CIE_XYZ_WAVENUMBER_MAX<=wnums[i+1]) + iband_max = i; + } + ASSERT(iband_min <= iband_max); + + /* Compute the *unormalized* pdf of the tristimulus */ + FOR_EACH(i, iband_min, iband_max+1) { + const double lambda_lo = WNUM_TO_WLEN(wnums[i+1]); + const double lambda_hi = WNUM_TO_WLEN(wnums[i+0]); + ASSERT(lambda_lo <= lambda_hi); + 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]; + } + + /* Compute the cdf of the tristimulus. Note that the upper bound is 'n' and not + * 'iband_max+1' in order to ensure that the CDF is strictly increasing. */ + FOR_EACH(i, iband_min, n) { + /* 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(eq_eps(cdf[X][n-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Y][n-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Z][n-1], 1, 1.e-6)); + +#ifndef NDEBUG + FOR_EACH(i, 1, n) { + ASSERT(cdf[X][i] >= cdf[X][i-1]); + ASSERT(cdf[Y][i] >= cdf[Y][i-1]); + ASSERT(cdf[Z][i] >= cdf[Z][i-1]); + } +#endif + + /* Handle numerical issue */ + cdf[X][n-1] = 1.0; + cdf[Y][n-1] = 1.0; + cdf[Z][n-1] = 1.0; + +exit: + return res; +error: + darray_double_clear(&htgop->sw_X_cdf); + darray_double_clear(&htgop->sw_Y_cdf); + darray_double_clear(&htgop->sw_Z_cdf); + goto exit; +} + + diff --git a/src/test_htgop_sample.c b/src/test_htgop_sample.c @@ -104,7 +104,6 @@ check_sample_quadrature } } - int main(int argc, char** argv) { @@ -135,9 +134,12 @@ main(int argc, char** argv) CHK(hist_Y = MEM_CALLOC(&allocator, nspecints, sizeof(*hist_Y))); CHK(hist_Z = MEM_CALLOC(&allocator, nspecints, sizeof(*hist_Z))); - check_sw_sample_specint(htgop, htgop_sample_sw_spectral_interval_CIE_X, hist_X); - check_sw_sample_specint(htgop, htgop_sample_sw_spectral_interval_CIE_Y, hist_Y); - check_sw_sample_specint(htgop, htgop_sample_sw_spectral_interval_CIE_Z, hist_Z); + check_sw_sample_specint + (htgop, htgop_sample_sw_spectral_interval_CIE_1931_X, hist_X); + check_sw_sample_specint + (htgop, htgop_sample_sw_spectral_interval_CIE_1931_Y, hist_Y); + check_sw_sample_specint + (htgop, htgop_sample_sw_spectral_interval_CIE_1931_Z, hist_Z); FOR_EACH(ispecint, 0, nspecints) { if(wnums[ispecint+0] > CIE_XYZ_WAVENUMBER_MAX