stardis-solver

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

sdis_heat_path_radiative_Xd.h (16363B)


      1 /* Copyright (C) 2016-2025 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #include "sdis_brdf.h"
     17 #include "sdis_device_c.h"
     18 #include "sdis_green.h"
     19 #include "sdis_heat_path.h"
     20 #include "sdis_heat_path_boundary_c.h"
     21 #include "sdis_interface_c.h"
     22 #include "sdis_log.h"
     23 #include "sdis_medium_c.h"
     24 #include "sdis_misc.h"
     25 #include "sdis_radiative_env_c.h"
     26 #include "sdis_scene_c.h"
     27 
     28 #include <star/ssp.h>
     29 
     30 #include "sdis_Xd_begin.h"
     31 
     32 /*******************************************************************************
     33  * Non generic helper functions
     34  ******************************************************************************/
     35 #ifndef SDIS_HEAT_PATH_RADIATIVE_XD_H
     36 #define SDIS_HEAT_PATH_RADIATIVE_XD_H
     37 
     38 static res_T
     39 set_limit_radiative_temperature
     40   (struct sdis_scene* scn,
     41    struct rwalk_context* ctx,
     42    struct rwalk* rwalk,
     43    /* Direction along which the random walk reached the radiative environment */
     44    const double dir[3],
     45    const int branch_id,
     46    struct temperature* T)
     47 {
     48   struct sdis_radiative_ray ray = SDIS_RADIATIVE_RAY_NULL;
     49   double trad = 0; /* [K] */
     50   res_T res = RES_OK;
     51 
     52   /* Check pre-conditions */
     53   ASSERT(scn && ctx && rwalk && dir && T);
     54   ASSERT(SXD_HIT_NONE(&rwalk->XD(hit)));
     55 
     56   rwalk->hit_side = SDIS_SIDE_NULL__;
     57   d3_set(rwalk->dir, dir);
     58   d3_normalize(rwalk->dir, rwalk->dir);
     59   d3_set(ray.dir, rwalk->dir);
     60   ray.time = rwalk->vtx.time;
     61 
     62   trad = radiative_env_get_temperature(scn->radenv, &ray);
     63   if(SDIS_TEMPERATURE_IS_UNKNOWN(trad)) {
     64     log_err(scn->dev,
     65       "%s:%s: the random walk has reached an invalid radiative environment from "
     66       "position (%g, %g, %g) along direction (%g, %g, %g): the temperature is "
     67       "unknown. This may be due to numerical inaccuracies or inconsistencies "
     68       "in the simulated system (e.g. non-closed geometry). For systems where "
     69       "sampled paths can reach such a temperature, we need to define a valid "
     70       "radiative temperature, i.e. one with a known temperature.\n",
     71       __FILE__, FUNC_NAME, SPLIT3(rwalk->vtx.P), SPLIT3(rwalk->dir));
     72     res = RES_BAD_OP;
     73     goto error;
     74   }
     75 
     76   /* The limit condition is reached */
     77   T->value += trad;
     78   T->done = 1;
     79 
     80   /* Update green path */
     81   if(ctx->green_path) {
     82     res = green_path_set_limit_radiative_ray
     83       (ctx->green_path, &ray, rwalk->elapsed_time);
     84     if(res != RES_OK) goto error;
     85   }
     86 
     87   /* Record the limit vertex of the sampled path. Set it arbitrarily at a
     88    * distance of 0.1 meters from the surface along the direction reaching the
     89    * radiative environment */
     90   if(ctx->heat_path) {
     91     const double empirical_dst = 0.1 * (float)scn->fp_to_meter;
     92     struct sdis_rwalk_vertex vtx = SDIS_RWALK_VERTEX_NULL;
     93 
     94     vtx = rwalk->vtx;
     95     vtx.P[0] += dir[0] * empirical_dst;
     96     vtx.P[1] += dir[1] * empirical_dst;
     97     vtx.P[2] += dir[2] * empirical_dst;
     98     res = register_heat_vertex(ctx->heat_path, &vtx, T->value,
     99       SDIS_HEAT_VERTEX_RADIATIVE, branch_id);
    100     if(res != RES_OK) goto error;
    101   }
    102 
    103 exit:
    104   return res;
    105 error:
    106   goto exit;
    107 }
    108 
    109 /* Check that the trajectory reaches a valid interface, i.e. that it is on a
    110  * fluid/solid interface and has reached it from the fluid */
    111 static res_T
    112 check_interface
    113   (const struct sdis_interface* interf,
    114    const struct sdis_interface_fragment* frag,
    115    const int verbose) /* Control the verbosity of the function */
    116 {
    117   enum sdis_medium_type mdm_frt_type = SDIS_MEDIUM_TYPES_COUNT__;
    118   enum sdis_medium_type mdm_bck_type = SDIS_MEDIUM_TYPES_COUNT__;
    119   enum sdis_side fluid_side = SDIS_SIDE_NULL__;
    120   res_T res = RES_OK;
    121 
    122   mdm_frt_type = sdis_medium_get_type(interf->medium_front);
    123   mdm_bck_type = sdis_medium_get_type(interf->medium_back);
    124 
    125   /* Semi-transparent materials are not supported. This means that a solid/solid
    126    * interface must not be intersected when tracing radiative paths */
    127   if(mdm_frt_type == SDIS_SOLID && mdm_bck_type == SDIS_SOLID) {
    128     if(verbose) {
    129       log_err(interf->dev,
    130         "Error when sampling the radiatve path. The trajectory reaches a "
    131         "solid/solid interface, whereas this is supposed to be impossible "
    132         "(path position: %g, %g, %g).\n",
    133       SPLIT3(frag->P));
    134     }
    135     res = RES_BAD_OP;
    136     goto error;
    137   }
    138 
    139   /* Find out which side of the interface the fluid is on */
    140   if(mdm_frt_type == SDIS_FLUID) {
    141     fluid_side = SDIS_FRONT;
    142   } else if(mdm_bck_type == SDIS_FLUID) {
    143     fluid_side = SDIS_BACK;
    144   } else {
    145     FATAL("Unreachable code\n");
    146   }
    147 
    148   /* Check that the current position is on the correct side of the interface */
    149   if(frag->side != fluid_side) {
    150     if(verbose) {
    151       log_err(interf->dev,
    152         "Inconsistent intersection when sampling the radiative path. "
    153         "The path reaches an interface on its solid side, whereas this is "
    154         "supposed to be impossible (path position: %g, %g, %g).\n",
    155         SPLIT3(frag->P));
    156     }
    157     res = RES_BAD_OP;
    158     goto error;
    159   }
    160 
    161 exit:
    162   return res;
    163 error:
    164   goto exit;
    165 }
    166 
    167 #endif /* SDIS_HEAT_PATH_RADIATIVE_XD_H */
    168 
    169 /*******************************************************************************
    170  * Generic helper functions
    171  ******************************************************************************/
    172 static INLINE void
    173 XD(setup_fragment)
    174   (struct sdis_interface_fragment* frag,
    175    const double pos[DIM],
    176    const double dir[DIM], /* Direction _toward_ the hit position */
    177    const double time, /* Current time */
    178    const double N[DIM],/* Surface normal */
    179    const struct sXd(hit)* hit)
    180 {
    181   struct sdis_rwalk_vertex vtx = SDIS_RWALK_VERTEX_NULL;
    182   enum sdis_side side = SDIS_SIDE_NULL__;
    183   ASSERT(frag && pos && dir && N);
    184   ASSERT(dX(is_normalized)(N));
    185 
    186   /* Setup the interface fragment at the intersection position */
    187   dX(set)(vtx.P, pos);
    188   vtx.time = time;
    189   side = dX(dot)(dir, N) < 0 ? SDIS_FRONT : SDIS_BACK;
    190   XD(setup_interface_fragment)(frag, &vtx, hit, side);
    191 }
    192 
    193 /*******************************************************************************
    194  * Local functions
    195  ******************************************************************************/
    196 res_T
    197 XD(trace_radiative_path)
    198   (struct sdis_scene* scn,
    199    const float ray_dir[3],
    200    struct rwalk_context* ctx,
    201    struct rwalk* rwalk,
    202    struct ssp_rng* rng,
    203    struct temperature* T)
    204 {
    205   /* The radiative random walk is always performed in 3D. In 2D, the geometry
    206    * are assumed to be extruded to the infinity along the Z dimension. */
    207   double N[3] = {0,0,0};
    208   double dir[3] = {0,0,0};
    209   double pos[3] = {0,0,0};
    210   int branch_id;
    211   size_t nbounces = 0; /* For debug */
    212   res_T res = RES_OK;
    213 
    214   ASSERT(scn && ray_dir && ctx && rwalk && rng && T);
    215 
    216   d3_set_f3(dir, ray_dir);
    217   d3_normalize(dir, dir);
    218 
    219   /* (int)ctx->nbranchings < 0 <=> Beginning of the realisation */
    220   branch_id = MMAX((int)ctx->nbranchings, 0);
    221 
    222   /* Launch the radiative random walk */
    223   for(;;) {
    224     /* BRDF */
    225     struct brdf brdf = BRDF_NULL;
    226     struct brdf_sample bounce = BRDF_SAMPLE_NULL;
    227     struct brdf_setup_args brdf_setup_args = BRDF_SETUP_ARGS_NULL;
    228 
    229     /* Miscellaneous */
    230     struct sdis_interface_fragment frag = SDIS_INTERFACE_FRAGMENT_NULL;
    231     struct sdis_interface* interf = NULL;
    232     struct sdis_medium* chk_mdm = NULL;
    233     double wi[3] = {0,0,0};
    234 
    235     d3_set(pos, rwalk->vtx.P);
    236     d3_minus(wi, dir);
    237 
    238     res = XD(find_next_fragment)(scn, pos, dir, &rwalk->XD(hit),
    239       rwalk->vtx.time, rwalk->enc_id, &rwalk->XD(hit), &interf, &frag);
    240     if(res != RES_OK) goto error;
    241 
    242     /* The path reaches the radiative environment */
    243     if(SXD_HIT_NONE(&rwalk->XD(hit))) {
    244       res = set_limit_radiative_temperature(scn, ctx, rwalk, dir, branch_id, T);
    245       if(res != RES_OK) goto error;
    246 
    247       ASSERT(T->done);
    248       break; /* Stop the radiative path */
    249     }
    250 
    251    /* Move the random walk to the hit position, i.e., the next position on the
    252     * interface returned as a fragment by the find_next_fragment function. Do
    253     * not use the sampled direction and distance to the hit point to
    254     * calculate the new position, as the current position may have been slightly
    255     * shifted on the starting triangle by the find_next_fragment function in
    256     * order to avoid numerical inaccuracy issues, making it impossible to
    257     * reconstruct the position actually returned by the function. The starting
    258     * point and distance returned are not, in any case, those used by the
    259     * function to calculate the new wall position. So simply use the position
    260     * returned by this function. */
    261     d3_set(rwalk->vtx.P, frag.P);
    262     rwalk->hit_side = frag.side;
    263 
    264     /* Verify that the intersection, although in the same enclosure, touches the
    265      * interface of a fluid. We verify this by interface, since a radiative path
    266      * can be traced in an enclosure containing several media used to describe a
    267      * set of boundary conditions.
    268      *
    269      * If the enclosure is good but the media type is not, this means that the
    270      * radiative path is sampled in the wrong media. This is not a numerical
    271      * problem, but a user problem: trying to sample a radiative path in a solid
    272      * when semi-transparent solids are not yet supported by Stardis. This error
    273      * is therefore fatal for the calculation */
    274     chk_mdm = rwalk->hit_side == SDIS_FRONT
    275       ? interf->medium_front
    276       : interf->medium_back;
    277     if(sdis_medium_get_type(chk_mdm) == SDIS_SOLID) {
    278       log_err(scn->dev,
    279         "%s: a radiative path cannot evolve in a solid -- pos=(%g, %g, %g)\n",
    280         FUNC_NAME, SPLIT3(rwalk->vtx.P));
    281       res = RES_BAD_OP_IRRECOVERABLE;
    282       goto error;
    283     }
    284 
    285     /* Register the random walk vertex against the heat path */
    286     res = register_heat_vertex(ctx->heat_path, &rwalk->vtx, T->value,
    287       SDIS_HEAT_VERTEX_RADIATIVE, branch_id);
    288     if(res != RES_OK) goto error;
    289 
    290     /* Retrieve BRDF at current interface position */
    291     brdf_setup_args.interf = interf;
    292     brdf_setup_args.frag = &frag;
    293     brdf_setup_args.source_id = SDIS_INTERN_SOURCE_ID;
    294     res = brdf_setup(scn->dev, &brdf_setup_args, &brdf);
    295     if(res != RES_OK) goto error;
    296 
    297     /* Switch in boundary temperature? */
    298     if(ssp_rng_canonical(rng) < brdf.emissivity) {
    299       T->func = XD(boundary_path);
    300       rwalk->enc_id = ENCLOSURE_ID_NULL; /* Interface between 2 enclosures */
    301       break;
    302     }
    303 
    304     /* Normalize the normal of the interface and ensure that it points toward the
    305      * current medium */
    306     switch(frag.side) {
    307       case SDIS_FRONT: d3_set(N, frag.Ng); break;
    308       case SDIS_BACK:  d3_minus(N, frag.Ng); break;
    309       default: FATAL("Unreachable code\n"); break;
    310     }
    311     brdf_sample(&brdf, rng, wi, N, &bounce);
    312     d3_set(dir, bounce.dir); /* Always in 3D */
    313 
    314     ++nbounces;
    315   }
    316 
    317 exit:
    318   return res;
    319 error:
    320   goto exit;
    321 }
    322 
    323 res_T
    324 XD(radiative_path)
    325   (struct sdis_scene* scn,
    326    struct rwalk_context* ctx,
    327    struct rwalk* rwalk,
    328    struct ssp_rng* rng,
    329    struct temperature* T)
    330 {
    331   /* The radiative random walk is always performed in 3D. In 2D, the geometry
    332    * are assumed to be extruded to the infinity along the Z dimension. */
    333   float N[3] = {0, 0, 0};
    334   float dir[3] = {0, 0, 0};
    335   res_T res = RES_OK;
    336 
    337   ASSERT(scn && ctx && rwalk && rng && T);
    338   ASSERT(!SXD_HIT_NONE(&rwalk->XD(hit)));
    339 
    340   /* Normalize the normal of the interface and ensure that it points toward the
    341    * current medium */
    342   fX(normalize(N, rwalk->XD(hit).normal));
    343   if(rwalk->hit_side == SDIS_BACK) {
    344     fX(minus(N, N));
    345   }
    346 
    347   /* Cosine weighted sampling of a direction around the surface normal */
    348   ssp_ran_hemisphere_cos_float(rng, N, dir, NULL);
    349 
    350   /* Launch the radiative random walk */
    351   res = XD(trace_radiative_path)(scn, dir, ctx, rwalk, rng, T);
    352   if(res != RES_OK) goto error;
    353 
    354 exit:
    355   return res;
    356 error:
    357   goto exit;
    358 }
    359 
    360 void
    361 XD(trace_ray)
    362   (struct sdis_scene* scn,
    363    const double pos[DIM],
    364    const double dir[3],
    365    const double distance,
    366    const unsigned enc_id,
    367    const struct sXd(hit)* hit_from,
    368    struct sXd(hit)* hit)
    369 {
    370   struct hit_filter_data filter_data = HIT_FILTER_DATA_NULL;
    371   float ray_org[DIM] = {0};
    372   float ray_dir[3] = {0};
    373   float ray_range[2] = {0};
    374   ASSERT(scn && pos && dir && distance >= 0 && hit_from && hit);
    375 
    376   fX_set_dX(ray_org, pos);
    377   f3_set_d3(ray_dir, dir);
    378   ray_range[0] = 0;
    379   ray_range[1] = (float)distance;
    380   filter_data.XD(hit) = *hit_from;
    381   filter_data.epsilon = 1.e-6;
    382   filter_data.scn = scn; /* Enable the filtering wrt the enclosure id */
    383   filter_data.enc_id = enc_id;
    384 #if DIM == 2
    385   SXD(scene_view_trace_ray_3d
    386     (scn->sXd(view), ray_org, ray_dir, ray_range, &filter_data, hit));
    387 #else
    388   SXD(scene_view_trace_ray
    389     (scn->sXd(view), ray_org, ray_dir, ray_range, &filter_data, hit));
    390 #endif
    391 }
    392 
    393 res_T
    394 XD(find_next_fragment)
    395   (struct sdis_scene* scn,
    396    const double in_pos[DIM],
    397    const double in_dir[3], /* Always in 3D */
    398    const struct sXd(hit)* in_hit,
    399    const double time,
    400    const unsigned enc_id,
    401    struct sXd(hit)* out_hit,
    402    struct sdis_interface** out_interf,
    403    struct sdis_interface_fragment* out_frag)
    404 {
    405   int NATTEMPTS_MAX = 10;
    406   int nattempts = 1;
    407 
    408   /* Stardis */
    409   struct sdis_interface_fragment frag = SDIS_INTERFACE_FRAGMENT_NULL;
    410   struct sdis_interface* interf = NULL;
    411 
    412   struct sXd(hit) hit = SXD_HIT_NULL;
    413   double rt_pos[DIM] = {0};
    414   res_T res = RES_OK;
    415 
    416   ASSERT(scn && in_pos && in_dir && in_hit);
    417   ASSERT(out_hit && out_interf && out_frag);
    418 
    419   /* Only one attempt is allowed when the ray does not start from a primitive */
    420   NATTEMPTS_MAX = S3D_HIT_NONE(in_hit) ? 1 : 10;
    421 
    422   dX(set)(rt_pos, in_pos);
    423 
    424   do {
    425     struct sdis_rwalk_vertex vtx = SDIS_RWALK_VERTEX_NULL;
    426     struct sdis_medium* solid = NULL;
    427     double pos[3] = {0};
    428     double vec[3] = {0};
    429     double N[3] = {0};
    430     double delta = 0;
    431 
    432     /* Reset result code. It may have been modified during a previous attempt */
    433     res = RES_OK;
    434 
    435     /* Find the following surface along the direction of propagation */
    436     XD(trace_ray)(scn, rt_pos, in_dir, INF, enc_id, in_hit, &hit);
    437     if(SXD_HIT_NONE(&hit)) break;
    438 
    439     /* Retrieve the current position and normal */
    440     dX(add)(pos, rt_pos, dX(muld)(vec, in_dir, hit.distance));
    441     dX_set_fX(N, hit.normal);
    442     dX(normalize(N, N));
    443 
    444     /* Retrieve the current interface properties */
    445     interf = scene_get_interface(scn, hit.prim.prim_id);
    446     XD(setup_fragment)(&frag, pos, in_dir, time, N, &hit);
    447 
    448     /* Check that the path reaches a valid interface.
    449      * An invalid fragment may mean that the ray position is in a corner and the
    450      * traced ray has missed the surface of that corner. To correct this, the
    451      * ray position is moved slightly away from the corner before a ray is drawn
    452      * in the same direction. This fallback solution is executed a number of
    453      * times, after which, if the fragment is still invalid, it is considered
    454      * that the numerical error cannot be mitigated. */
    455     res = check_interface(interf, &frag, nattempts == NATTEMPTS_MAX);
    456     if(res != RES_OK && nattempts == NATTEMPTS_MAX) goto error;
    457     ++nattempts;
    458 
    459     if(res != RES_OK) { /* Mitigate numerical error (see above) */
    460       if(sdis_medium_get_type(interf->medium_front) == SDIS_SOLID) {
    461         solid = interf->medium_front;
    462       } else {
    463         ASSERT(sdis_medium_get_type(interf->medium_back) == SDIS_SOLID);
    464         solid = interf->medium_back;
    465       }
    466 
    467       /* Retrieves the delta of the solid that surrounds the boundary, as it is
    468        * actually the only numerical parameter that says something about the
    469        * system. */
    470       vtx.P[0] = pos[0];
    471       vtx.P[1] = pos[1];
    472       vtx.P[2] = pos[2];
    473       vtx.time = time;
    474       delta = solid_get_delta(solid, &vtx);
    475 
    476       XD(move_away_primitive_boundaries)(in_hit, delta, rt_pos);
    477     }
    478   } while(res != RES_OK);
    479 
    480 exit:
    481   *out_hit = hit;
    482   *out_interf = interf;
    483   *out_frag = frag;
    484   return res;
    485 error:
    486   goto exit;
    487 }
    488 
    489 #include "sdis_Xd_end.h"