htrdr

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

commit b9aba36680492ebc8f20769da4f6f90d3c63619f
parent 60d5057cdbe6b7f9feb292aca7e3e3b1b010a1b0
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 12 Mar 2020 16:40:18 +0100

Add the compute radiance in long waves

Diffstat:
Mcmake/CMakeLists.txt | 1+
Msrc/htrdr.c | 6+++---
Asrc/htrdr_compute_radiance_lw.c | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/htrdr_compute_radiance_sw.c | 5+++--
Msrc/htrdr_solve.h | 10++++++++++
5 files changed, 357 insertions(+), 5 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -92,6 +92,7 @@ set(HTRDR_FILES_SRC htrdr_buffer.c htrdr_camera.c htrdr_compute_radiance_sw.c + htrdr_compute_radiance_lw.c htrdr_draw_radiance_sw.c htrdr_grid.c htrdr_ground.c diff --git a/src/htrdr.c b/src/htrdr.c @@ -538,15 +538,15 @@ htrdr_run(struct htrdr* htrdr) { res_T res = RES_OK; if(htrdr->dump_vtk) { - const size_t nbands = htsky_get_sw_spectral_bands_count(htrdr->sky); + const size_t nbands = htsky_get_spectral_bands_count(htrdr->sky); size_t i; /* Nothing to do */ if(htrdr->mpi_rank != 0) goto exit; FOR_EACH(i, 0, nbands) { - const size_t iband = htsky_get_sw_spectral_band_id(htrdr->sky, i); - const size_t nquads = htsky_get_sw_spectral_band_quadrature_length + const size_t iband = htsky_get_spectral_band_id(htrdr->sky, i); + const size_t nquads = htsky_get_spectral_band_quadrature_length (htrdr->sky, iband); size_t iquad; FOR_EACH(iquad, 0, nquads) { diff --git a/src/htrdr_compute_radiance_lw.c b/src/htrdr_compute_radiance_lw.c @@ -0,0 +1,340 @@ +/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019 CNRS, 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/>. */ + +#include "htrdr.h" +#include "htrdr_c.h" +#include "htrdr_interface.h" +#include "htrdr_ground.h" +#include "htrdr_solve.h" + +#include <high_tune/htsky.h> + +#include <star/s3d.h> +#include <star/ssf.h> +#include <star/ssp.h> +#include <star/svx.h> + +#include <rsys/double2.h> +#include <rsys/double3.h> + +#define BOLTZMANN_CONSTANT 5.6696e-8 /* W/m^2/K^4 */ + +enum event { + EVENT_ABSORPTION, + EVENT_SCATTERING, + EVENT_NONE +}; + +struct filter_context { + struct ssp_rng* rng; + const struct htsky* htsky; + size_t iband; /* Index of the spectral band */ + size_t iquad; /* Index of the quadrature point into the band */ + + double Ts; /* Sampled optical thickness */ + double traversal_dst; /* Distance traversed along the ray */ + + enum event event_type; +}; +static const struct filter_context FILTER_CONTEXT_NULL = { + NULL, NULL, 0, 0, 0.0, 0.0, EVENT_NONE +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static FINLINE double +wiebelt(const double v) +{ + int m; + double w, v2, v4; + /*.153989717364e+00;*/ + const double fifteen_over_pi_power_4 = 15.0/(PI*PI*PI*PI); + const double z0 = 1.0/3.0; + const double z1 = 1.0/8.0; + const double z2 = 1.0/60.0; + const double z4 = 1.0/5040.0; + const double z6 = 1.0/272160.0; + const double z8 = 1.0/13305600.0; + + if(v >= 2.) { + w = 0; + for(m=1; m<6 ;m++) + w+=exp(-m*v)/(m*m*m*m) * (((m*v+3)*m*v+6)*m*v+6); + w = w * fifteen_over_pi_power_4; + } else { + v2 = v*v; + v4 = v2*v2; + w = z0 - z1*v + z2*v2 - z4*v2*v2 + z6*v4*v2 - z8*v4*v4; + w = 1. - fifteen_over_pi_power_4*v2*v*w; + } + ASSERT(w >= 0.0 && w <= 1.0); + return w; +} + +static FINLINE double +blackbody_fraction + (const double lambda0, /* In meter */ + const double lambda1, /* In meter */ + const double temperature) /* In Kelvin */ +{ + const double C2 = 1.43877735e-2; /* m.K */ + double x0 = C2 / lambda0; + double x1 = C2 / lambda1; + double v0 = x0 / temperature; + double v1 = x1 / temperature; + double w0 = wiebelt(v0); + double w1 = wiebelt(v1); + return w1 - w0; +} + +static FINLINE double +planck + (const double lambda_min, /* In meter */ + const double lambda_max, /* In meter */ + const double temperature) /* In Kelvin */ +{ + const double T2 = temperature*temperature; + const double T4 = T2*T2; + ASSERT(lambda_min < lambda_max && temperature >= 0); + return blackbody_fraction(lambda_min, lambda_max, temperature) + * BOLTZMANN_CONSTANT * T4; +} + +static int +hit_filter + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + struct filter_context* ctx = context; + double kext_max; + int pursue_traversal = 1; + ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); + (void)range; + + kext_max = htsky_fetch_svx_voxel_property(ctx->htsky, HTSKY_Kext, + HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel); + + ctx->traversal_dst = hit->distance[0]; + + for(;;) { + double vox_dst = hit->distance[1] - ctx->traversal_dst; + const double T = vox_dst * kext_max; + + /* A collision occurs behind `vox_dst' */ + if(ctx->Ts > T) { + ctx->Ts -= T; + ctx->traversal_dst = hit->distance[1]; + pursue_traversal = 1; + break; + + /* A real/null collision occurs before `vox_dst' */ + } else { + const double collision_dst = ctx->Ts / kext_max; + double pos[3]; + double ks, ka; + double r; + double proba_abs; + double proba_sca; + + /* Compute the traversed distance up to the challenged collision */ + ctx->traversal_dst += collision_dst; + ASSERT(ctx->traversal_dst >= hit->distance[0]); + ASSERT(ctx->traversal_dst <= hit->distance[1]); + + /* Compute the world space position where a collision may occur */ + pos[0] = org[0] + ctx->traversal_dst * dir[0]; + pos[1] = org[1] + ctx->traversal_dst * dir[1]; + pos[2] = org[2] + ctx->traversal_dst * dir[2]; + + ka = htsky_fetch_raw_property(ctx->htsky, HTSKY_Ka, HTSKY_CPNT_MASK_ALL, + ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); + ks = htsky_fetch_raw_property(ctx->htsky, HTSKY_Ks, HTSKY_CPNT_MASK_ALL, + ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); + + r = ssp_rng_canonical(ctx->rng); + proba_abs = ka / kext_max; + proba_sca = ks / kext_max; + if(r < proba_abs) { /* Absorption event */ + pursue_traversal = 0; + ctx->event_type = EVENT_ABSORPTION; + } else if(r < proba_abs + proba_sca) { /* Scattering event */ + pursue_traversal = 0; + ctx->event_type = EVENT_SCATTERING; + } else { /* Null collision */ + ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ + } + } + } + return pursue_traversal; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +double +htrdr_compute_radiance_lw + (struct htrdr* htrdr, + const size_t ithread, + struct ssp_rng* rng, + const double pos_in[3], + const double dir_in[3], + const size_t iband, + const size_t iquad) +{ + struct s3d_hit s3d_hit = S3D_HIT_NULL; + struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; + struct svx_hit svx_hit = SVX_HIT_NULL; + struct ssf_phase* phase_hg = NULL; + + double wo[3]; + double pos[3]; + double dir[3]; + double range[2]; + double pos_next[3]; + double dir_next[3]; + double band_bounds[2]; /* In nanometers */ + double band_bounds_m[2]; /* In meters */ + double wlen; + double temperature; + double g; + double w = 0; /* Weight */ + + ASSERT(htrdr && rng && pos_in && dir_in && ithread < htrdr->nthreads); + + /* Retrieve the band boundaries */ + htsky_get_spectral_band_bounds(htrdr->sky, iband, band_bounds); + + /* Arbitrarly use the wavelength at the center of the band as the wavelength + * to use for data that depend on wavelength rather than spectral band as the + * BRDF */ + wlen = (band_bounds[0] + band_bounds[1]) * 0.5; + band_bounds_m[0] = band_bounds[0] * 1e9; + band_bounds_m[1] = band_bounds[1] * 1e9; + + /* Setup the phase function for this spectral band & quadrature point */ + CHK(RES_OK == ssf_phase_create + (&htrdr->lifo_allocators[ithread], &ssf_phase_hg, &phase_hg)); + g = htsky_fetch_particle_phase_function_asymmetry_parameter + (htrdr->sky, iband, iquad); + SSF(phase_hg_setup(phase_hg, g)); + + /* Initialise the random walk */ + d3_set(pos, pos_in); + d3_set(dir, dir_in); + d2(range, 0, INF); + + for(;;) { + struct filter_context ctx = FILTER_CONTEXT_NULL; + + /* Sample an optical thickness */ + ctx.Ts = ssp_ran_exp(rng, 1); + + /* Setup the remaining fields of the hit filter context */ + ctx.rng = rng; + ctx.htsky = htrdr->sky; + ctx.iband = iband; + ctx.iquad = iquad; + + /* Found the first intersection with the surface geometry */ + HTRDR(ground_trace_ray + (htrdr->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit)); + + /* Fit the ray range to the surface distance along the ray */ + range[0] = 0; + range[1] = s3d_hit.distance; + + /* Trace a ray into the participating media */ + HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL, + hit_filter, &ctx, iband, iquad, &svx_hit)); + + /* No scattering and no surface reflection. + * Congratulation !! You are in space. */ + if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { + w = 0; + break; + } + + /* Compute the next position */ + pos_next[0] = pos[0] + dir[0]*ctx.traversal_dst; + pos_next[1] = pos[1] + dir[1]*ctx.traversal_dst; + pos_next[2] = pos[2] + dir[2]*ctx.traversal_dst; + + /* Absorption event. Stop the realisation */ + if(ctx.event_type == EVENT_ABSORPTION) { + ASSERT(!SVX_HIT_NONE(&svx_hit)); + temperature = htsky_fetch_temperature(htrdr->sky, pos_next); + w = planck(band_bounds_m[0], band_bounds_m[1], temperature); + break; + } + + /* Negate the incoming dir to match the convention of the SSF library */ + d3_minus(wo, dir); + + /* Scattering in the volume */ + if(ctx.event_type == EVENT_SCATTERING) { + ASSERT(!SVX_HIT_NONE(&svx_hit)); + ssf_phase_sample(phase_hg, rng, wo, dir_next, NULL); + s3d_hit_prev = S3D_HIT_NULL; + + /* Scattering at a surface */ + } else { + struct htrdr_interface interf = HTRDR_INTERFACE_NULL; + struct ssf_bsdf* bsdf = NULL; + double bounce_reflectivity = 0; + double N[3]; + int type; + ASSERT(ctx.event_type == EVENT_NONE); + ASSERT(!S3D_HIT_NONE(&s3d_hit)); + + /* Fetch the hit interface and build its BSDF */ + htrdr_ground_get_interface(htrdr->ground, &s3d_hit, &interf); + HTRDR(interface_create_bsdf + (htrdr, &interf, ithread, wlen, pos_next, dir, &s3d_hit, &bsdf)); + + d3_normalize(N, d3_set_f3(N, s3d_hit.normal)); + if(d3_dot(N, wo) < 0) d3_minus(N, N); + + bounce_reflectivity = ssf_bsdf_sample + (bsdf, rng, wo, N, dir_next, &type, NULL); + if(!(type & SSF_REFLECTION)) { /* Handle only reflections */ + bounce_reflectivity = 0; + } + + /* Release the BSDF */ + SSF(bsdf_ref_put(bsdf)); + + if(ssp_rng_canonical(rng) >= bounce_reflectivity) { /* Absorbed at boundary */ + /* Retrieve the temperature of the interface. Anyway, since we do not + * have this data yet, we use the temperature of the sky at the current + * position as the temperature of the surface. */ + temperature = htsky_fetch_temperature(htrdr->sky, pos_next); + w = planck(band_bounds_m[0], band_bounds_m[1], temperature); + break; + } + + s3d_hit_prev = s3d_hit; + } + d3_set(pos, pos_next); + d3_set(dir, dir_next); + } + SSF(phase_ref_put(phase_hg)); + return w; +} + diff --git a/src/htrdr_compute_radiance_sw.c b/src/htrdr_compute_radiance_sw.c @@ -35,7 +35,7 @@ struct scattering_context { struct ssp_rng* rng; const struct htsky* sky; - size_t iband; /* Index of the spectrald */ + size_t iband; /* Index of the spectral band */ size_t iquad; /* Index of the quadrature point into the band */ double Ts; /* Sampled optical thickness */ @@ -279,6 +279,7 @@ htrdr_compute_radiance_sw double g = 0; /* Asymmetry parameter of the HG phase function */ ASSERT(htrdr && rng && pos_in && dir_in && ithread < htrdr->nthreads); + ASSERT(!htsky_is_long_wave(htrdr->sky)); CHK(RES_OK == ssf_phase_create (&htrdr->lifo_allocators[ithread], &ssf_phase_hg, &phase_hg)); @@ -295,7 +296,7 @@ htrdr_compute_radiance_sw * spectral data are defined by bands that, actually are the same of the SW * spectral bands defined in the default "ecrad_opt_prot.txt" file provided * by the HTGOP project. */ - htsky_get_sw_spectral_band_bounds(htrdr->sky, iband, band_bounds); + htsky_get_spectral_band_bounds(htrdr->sky, iband, band_bounds); wlen = (band_bounds[0] + band_bounds[1]) * 0.5; sun_solid_angle = htrdr_sun_get_solid_angle(htrdr->sun); L_sun = htrdr_sun_get_radiance(htrdr->sun, wlen); diff --git a/src/htrdr_solve.h b/src/htrdr_solve.h @@ -45,6 +45,16 @@ htrdr_compute_radiance_sw const size_t iband, /* Index of the spectral band */ const size_t iquad); /* Index of the quadrature point into the band */ +extern LOCAL_SYM double +htrdr_compute_radiance_lw + (struct htrdr* htrdr, + const size_t ithread, + struct ssp_rng* rng, + const double pos[3], + const double dir[3], + const size_t iband, /* Index of the spectral band */ + const size_t iquad); /* Index of the quadrature point into the band */ + extern LOCAL_SYM res_T htrdr_draw_radiance_sw (struct htrdr* htrdr,