solstice

Compute collected power and efficiencies of a solar plant
git clone git://git.meso-star.com/solstice.git
Log | Files | Refs | README | LICENSE

solstice.c (24109B)


      1 /* Copyright (C) 2018-2026 |Méso|Star> (contact@meso-star.com)
      2  * Copyright (C) 2016-2018 CNRS
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 3 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     16 
     17 #define _POSIX_C_SOURCE 200112L /* close support */
     18 
     19 #include "solstice.h"
     20 #include "solstice_c.h"
     21 #include "solstice_args.h"
     22 #include "parser/solparser.h"
     23 
     24 #include <rsys/double2.h>
     25 
     26 #include <sys/stat.h>
     27 #include <sys/types.h>
     28 
     29 #include <errno.h>
     30 #include <stdio.h>
     31 #include <fcntl.h>
     32 
     33 /* open/close functions */
     34 #include <unistd.h>
     35 
     36 #include <solstice/ssol.h>
     37 
     38 /*******************************************************************************
     39  * Helper functions
     40  ******************************************************************************/
     41 static void
     42 log_err(const char* msg, void* ctx)
     43 {
     44   ASSERT(msg);
     45   (void)ctx;
     46   fprintf(stderr, "\x1b[31merror:\x1b[0m %s", msg);
     47 }
     48 
     49 static void
     50 log_warn(const char* msg, void* ctx)
     51 {
     52   ASSERT(msg);
     53   (void)ctx;
     54   fprintf(stderr, "\x1b[33mwarning:\x1b[0m %s", msg);
     55 }
     56 
     57 static void
     58 clear_materials(struct htable_material* materials)
     59 {
     60   struct htable_material_iterator it, end;
     61   ASSERT(materials);
     62 
     63   htable_material_begin(materials, &it);
     64   htable_material_end(materials, &end);
     65   while(!htable_material_iterator_eq(&it, &end)) {
     66     struct ssol_material* mtl = *htable_material_iterator_data_get(&it);
     67     SSOL(material_ref_put(mtl));
     68     htable_material_iterator_next(&it);
     69   }
     70   htable_material_clear(materials);
     71 }
     72 
     73 static void
     74 clear_objects(struct htable_object* objects)
     75 {
     76   struct htable_object_iterator it, end;
     77   ASSERT(objects);
     78 
     79   htable_object_begin(objects, &it);
     80   htable_object_end(objects, &end);
     81   while(!htable_object_iterator_eq(&it, &end)) {
     82     struct ssol_object* obj = *htable_object_iterator_data_get(&it);
     83     SSOL(object_ref_put(obj));
     84     htable_object_iterator_next(&it);
     85   }
     86   htable_object_clear(objects);
     87 }
     88 
     89 static void
     90 clear_nodes(struct darray_nodes* nodes)
     91 {
     92   size_t i, n;
     93   ASSERT(nodes);
     94   n = darray_nodes_size_get(nodes);
     95   FOR_EACH(i, 0, n) {
     96     solstice_node_ref_put(darray_nodes_data_get(nodes)[i]);
     97   }
     98   darray_nodes_clear(nodes);
     99 }
    100 
    101 static void
    102 clear_anchors(struct htable_anchor* anchors)
    103 {
    104   struct htable_anchor_iterator it, end;
    105   ASSERT(anchors);
    106 
    107   htable_anchor_begin(anchors, &it);
    108   htable_anchor_end(anchors, &end);
    109   while(!htable_anchor_iterator_eq(&it, &end)) {
    110     struct solstice_node* node = *htable_anchor_iterator_data_get(&it);
    111     solstice_node_ref_put(node);
    112     htable_anchor_iterator_next(&it);
    113   }
    114   htable_anchor_clear(anchors);
    115 }
    116 
    117 static res_T
    118 auto_look_at
    119   (struct ssol_scene* scn,
    120    const double fov_x, /* Horizontal field of view in radian */
    121    const double proj_ratio, /* Width / height */
    122    const double up[3], /* Up vector */
    123    double position[3],
    124    double target[3])
    125 {
    126   float flower[3], fupper[3];
    127   double lower[3], upper[3];
    128   double up_abs[3];
    129   double axis_min[3];
    130   double axis_x[3];
    131   double axis_z[3];
    132   double tmp[3];
    133   double radius;
    134   double depth;
    135   res_T res;
    136   ASSERT(scn && fov_x > 0 && proj_ratio > 0 && up);
    137 
    138   res = ssol_scene_compute_aabb(scn, flower, fupper);
    139   if(res != RES_OK) {
    140     fprintf(stderr, "Couldn't compute the scene bounding box.\n");
    141     goto error;
    142   }
    143 
    144   if(flower[0] > fupper[0]
    145   || flower[1] > fupper[1]
    146   || flower[2] > fupper[2]) { /* Empty scene */
    147     d3_set(position, SOLSTICE_ARGS_DEFAULT.camera.pos);
    148     d3_set(target, SOLSTICE_ARGS_DEFAULT.camera.tgt);
    149     goto exit;
    150   }
    151 
    152   d3_set_f3(upper, fupper);
    153   d3_set_f3(lower, flower);
    154 
    155   /* The target is the scene centroid */
    156   d3_muld(target, d3_add(target, lower, upper), 0.5);
    157 
    158   /* Define which up dimension is minimal and use its unit vector to compute a
    159    * vector orthogonal to `up'. This ensures that the unit vector and `up' are
    160    * not collinear and thus that their cross product is not a zero vector. */
    161   up_abs[0] = fabs(up[0]);
    162   up_abs[1] = fabs(up[1]);
    163   up_abs[2] = fabs(up[2]);
    164   if(up_abs[0] < up_abs[1]) {
    165     if(up_abs[0] < up_abs[2]) d3(axis_min, 1, 0, 0);
    166     else d3(axis_min, 0, 0, 1);
    167   } else {
    168     if(up_abs[1] < up_abs[2]) d3(axis_min, 0, 1, 0);
    169     else d3(axis_min, 0, 0, 1);
    170   }
    171   d3_normalize(axis_x, d3_cross(axis_x, up, axis_min));
    172   d3_normalize(axis_z, d3_cross(axis_z, up, axis_x));
    173 
    174   /* Approximate whether on the XYZ or the ZYX basis the visible part of the
    175    * model is maximise */
    176   if(fabs(d3_dot(axis_x, upper)) < fabs(d3_dot(axis_z, upper))) {
    177     SWAP(double, axis_x[0], axis_z[0]);
    178     SWAP(double, axis_x[1], axis_z[1]);
    179     SWAP(double, axis_x[2], axis_z[2]);
    180   }
    181 
    182   /* Ensure that the whole model is visible */
    183   radius = d3_len(d3_sub(tmp, upper, lower)) * 0.5;
    184   if(proj_ratio < 1) {
    185     depth = radius / sin(fov_x/2.0);
    186   } else {
    187     depth = radius / sin(fov_x/(2.0*proj_ratio));
    188   }
    189 
    190   /* Define the camera position */
    191   d3_sub(position, target, d3_muld(tmp, axis_z, depth));
    192 
    193   /* Empirically move the position to find a better point of view */
    194   d3_add(position, position, d3_muld(tmp, up, radius)); /*Empirical offset*/
    195   d3_add(position, position, d3_muld(tmp, axis_x, radius)); /*Empirical offset*/
    196   d3_normalize(tmp, d3_sub(tmp, target, position));
    197   d3_sub(position, target, d3_muld(tmp, tmp, depth));
    198 
    199 exit:
    200   return res;
    201 error:
    202   goto exit;
    203 }
    204 
    205 static res_T
    206 setup_camera(struct solstice* solstice, const struct solstice_args* args)
    207 {
    208   struct ssol_camera* cam = NULL;
    209   double proj_ratio = 0;
    210   double pos[3], tgt[3];
    211   res_T res = RES_OK;
    212   ASSERT(solstice && args);
    213 
    214   res = ssol_camera_create(solstice->ssol, &cam);
    215   if(res != RES_OK) {
    216     fprintf(stderr, "Could not create the rendering camera.\n");
    217     goto error;
    218   }
    219 
    220   proj_ratio = (double)args->img.width / (double)args->img.height;
    221   res = ssol_camera_set_proj_ratio(cam, proj_ratio);
    222   if(res != RES_OK) {
    223     fprintf(stderr, "Invalid image ratio '%g'.\n", proj_ratio);
    224     goto error;
    225   }
    226 
    227   res = ssol_camera_set_fov(cam, MDEG2RAD(args->camera.fov_x));
    228   if(res != RES_OK) {
    229     fprintf(stderr, "Invalid horizontal field of view '%g' degrees.\n",
    230       args->camera.fov_x);
    231     goto error;
    232   }
    233 
    234   if(!args->camera.auto_look_at) {
    235     d3_set(pos, args->camera.pos);
    236     d3_set(tgt, args->camera.tgt);
    237   } else {
    238     res = auto_look_at(solstice->scene, MDEG2RAD(args->camera.fov_x),
    239       proj_ratio, args->camera.up, pos, tgt);
    240     if(res != RES_OK) goto error;
    241   }
    242 
    243   res = ssol_camera_look_at(cam, pos, tgt, args->camera.up);
    244   if(res != RES_OK) {
    245     fprintf(stderr,
    246 "Invalid camera point of view:\n"
    247 "  position = %g %g %g\n"
    248 "  target = %g %g %g\n"
    249 "  up = %g %g %g\n",
    250       SPLIT3(args->camera.pos),
    251       SPLIT3(args->camera.tgt),
    252       SPLIT3(args->camera.up));
    253     goto error;
    254   }
    255 
    256 exit:
    257   solstice->camera = cam;
    258   return res;
    259 error:
    260   if(cam) {
    261     SSOL(camera_ref_put(cam));
    262     cam = NULL;
    263   }
    264   goto exit;
    265 }
    266 
    267 static res_T
    268 setup_framebuffer(struct solstice* solstice, const struct solstice_args* args)
    269 {
    270   struct ssol_image* fbuf = NULL;
    271   res_T res = RES_OK;
    272   ASSERT(solstice && args);
    273 
    274   res = ssol_image_create(solstice->ssol, &fbuf);
    275   if(res != RES_OK) {
    276     fprintf(stderr, "Could not create the rendering framebuffer.\n");
    277     goto error;
    278   }
    279 
    280   res = ssol_image_setup
    281     (fbuf, args->img.width, args->img.height, SSOL_PIXEL_DOUBLE3);
    282   if(res != RES_OK) {
    283     fprintf(stderr,
    284       "Could not set the framebuffer definition to %lux%lu.\n",
    285       args->img.width, args->img.height);
    286     goto error;
    287   }
    288 
    289 exit:
    290   solstice->framebuffer = fbuf;
    291   return res;
    292 
    293 error:
    294   if(fbuf) {
    295     SSOL(image_ref_put(fbuf));
    296     fbuf = NULL;
    297   }
    298   goto exit;
    299 }
    300 
    301 /* This function differs from scem_sun_position_to_sun_vector because 1) angles
    302  * are in degrees VS radians, 2) the conventions on azimuth are different. */
    303 static INLINE void
    304 spherical_to_cartesian_sun_dir
    305   (const struct solstice_spherical* spherical, double sun_dir[3])
    306 {
    307   double cos_azimuth;
    308   double sin_azimuth;
    309   double cos_elevation;
    310   double sin_elevation;
    311   double azimuth;
    312   double elevation;
    313   ASSERT(spherical && sun_dir);
    314 
    315   azimuth = MDEG2RAD(spherical->azimuth);
    316   elevation = MDEG2RAD(spherical->elevation);
    317 
    318   cos_azimuth = cos(azimuth);
    319   sin_azimuth = sin(azimuth);
    320   cos_elevation = cos(elevation);
    321   sin_elevation = sin(elevation);
    322 
    323   sun_dir[0] = -(cos_elevation * cos_azimuth);
    324   sun_dir[1] = -(cos_elevation * sin_azimuth);
    325   sun_dir[2] = -(sin_elevation);
    326 }
    327 
    328 static res_T
    329 setup_sun_dirs(struct solstice* solstice, const struct solstice_args* args)
    330 {
    331   struct solstice_sun_dir* sun_dirs = NULL;
    332   size_t i;
    333   res_T res = RES_OK;
    334   ASSERT(solstice && args);
    335 
    336   res = darray_sun_dir_resize(&solstice->sun_dirs, args->nsun_dirs);
    337   if(res != RES_OK) {
    338     fprintf(stderr,
    339       "Could not reserve the list of %lu sun directions.\n",
    340       (unsigned long)args->nsun_dirs);
    341     goto error;
    342   }
    343   sun_dirs = darray_sun_dir_data_get(&solstice->sun_dirs);
    344   FOR_EACH(i, 0, args->nsun_dirs) {
    345     struct solstice_args_sun_dir* r = args->sun_dirs + i;
    346     switch(r->type) {
    347       case SOLSTICE_ARGS_SPHERICAL:
    348         sun_dirs[i].type = SOLSTICE_SUN_DIR_SPHERICAL;
    349         sun_dirs[i].u.spherical.azimuth = r->spherical.azimuth;
    350         sun_dirs[i].u.spherical.elevation = r->spherical.elevation;
    351         break;
    352       case SOLSTICE_SUN_DIR_LOCATION_TIME:
    353         sun_dirs[i].type = SOLSTICE_SUN_DIR_LOCATION_TIME;
    354         sun_dirs[i].u.location_time.time = r->location_time.time;
    355         sun_dirs[i].u.location_time.latitude = r->location_time.latitude;
    356         sun_dirs[i].u.location_time.longitude = r->location_time.longitude;
    357         break;
    358       default: FATAL("Invalid enum value.\n");
    359     }
    360   }
    361 
    362 exit:
    363   return res;
    364 error:
    365   darray_sun_dir_clear(&solstice->sun_dirs);
    366   goto exit;
    367 }
    368 
    369 static res_T
    370 load_data(struct solstice* solstice, const struct solstice_args* args)
    371 {
    372   struct solparser_entity_iterator it, end;
    373   FILE* file = stdin;
    374   const char* name = "stdin";
    375   res_T res = RES_OK;
    376   ASSERT(solstice && args);
    377 
    378   if(args->input_filename) {
    379     file = fopen(args->input_filename, "r");
    380     if(!file) {
    381       fprintf(stderr, "Could not open the file `%s'.\n", args->input_filename);
    382       res = RES_IO_ERR;
    383       goto error;
    384     }
    385     name = args->input_filename;
    386   } else if(!args->quiet) {
    387     fprintf(stderr,
    388       "Enter the solar facility data. Type ^Z (i.e. CTRL+z) to stop:\n");
    389   }
    390 
    391   res = solparser_setup(solstice->parser, name, file);
    392   if(res != RES_OK) goto error;
    393 
    394   res = solparser_load(solstice->parser);
    395   if(res != RES_OK) goto error;
    396 
    397   solparser_entity_iterator_begin(solstice->parser, &it);
    398   solparser_entity_iterator_end(solstice->parser, &end);
    399   if(solparser_entity_iterator_eq(&it, &end)) {
    400     fprintf(stderr, "No entity is defined.\n");
    401     res = RES_BAD_ARG;
    402     goto error;
    403   }
    404 
    405 exit:
    406   if(file && file != stdin) fclose(file);
    407   return res;
    408 error:
    409   goto exit;
    410 }
    411 
    412 static res_T
    413 setup_receivers(struct solstice* solstice, struct srcvl* srcvl)
    414 {
    415   size_t i, n;
    416   res_T res = RES_OK;
    417   ASSERT(solstice && srcvl);
    418 
    419   htable_receiver_clear(&solstice->receivers);
    420   darray_receiver_clear(&solstice->rcvs_list);
    421 
    422   n = srcvl_count(srcvl);
    423 
    424   res = darray_receiver_resize(&solstice->rcvs_list, n);
    425   if(res != RES_OK) {
    426     fprintf(stderr, "Could not reserve memory space for the receivers.\n");
    427     goto error;
    428   }
    429 
    430   FOR_EACH(i, 0, n) {
    431     struct solstice_receiver* receiver;
    432     struct srcvl_receiver rcv;
    433     const struct solparser_entity* entity;
    434     const char* name;
    435 
    436     receiver = darray_receiver_data_get(&solstice->rcvs_list) + i;
    437 
    438     srcvl_get(srcvl, i, &rcv);
    439     entity = solparser_find_entity(solstice->parser, rcv.name);
    440     if(!entity) {
    441       fprintf(stderr, "Invalid entity `%s'.\n", rcv.name);
    442       res = RES_BAD_ARG;
    443       goto error;
    444     }
    445 
    446     if(entity->type != SOLPARSER_ENTITY_GEOMETRY) {
    447       fprintf(stderr,
    448         "The entity `%s' is not a geometry. It cannot be a receiver.\n",
    449         rcv.name);
    450       res = RES_BAD_ARG;
    451       goto error;
    452     }
    453 
    454     res = str_set(&receiver->name, rcv.name);
    455     if(res != RES_OK) {
    456       fprintf(stderr, "Could not copy the receiver name.\n");
    457       goto error;
    458     }
    459 
    460     receiver->node = NULL;
    461     receiver->side = rcv.side;
    462     receiver->per_primitive_output = rcv.per_primitive_output;
    463 
    464     name = str_cget(&receiver->name);
    465     res = htable_receiver_set(&solstice->receivers, &name, &i);
    466     if(res != RES_OK) {
    467       fprintf(stderr,
    468         "Could not register the receiver `%s' against Solstice.\n",
    469         rcv.name);
    470       goto error;
    471     }
    472   }
    473 
    474 exit:
    475   return res;
    476 error:
    477   htable_receiver_clear(&solstice->receivers);
    478   darray_receiver_clear(&solstice->rcvs_list);
    479   goto exit;
    480 }
    481 
    482 static res_T
    483 load_receivers(struct solstice* solstice, const struct solstice_args* args)
    484 {
    485   FILE* file = NULL;
    486   struct srcvl* srcvl = NULL;
    487   res_T res = RES_OK;
    488   ASSERT(solstice && args);
    489 
    490   file = fopen(args->receivers_filename, "r");
    491   if(!file) {
    492     fprintf(stderr, "Could not open the list of receivers `%s'.\n",
    493       args->receivers_filename);
    494     res = RES_IO_ERR;
    495     goto error;
    496   }
    497 
    498   res = srcvl_create(solstice->allocator, &srcvl);
    499   if(res != RES_OK) goto error;
    500   res = srcvl_setup_stream(srcvl, args->receivers_filename, file);
    501   if(res != RES_OK) goto error;
    502   res = srcvl_load(srcvl);
    503   if(res != RES_OK) goto error;
    504   res = setup_receivers(solstice, srcvl);
    505   if(res != RES_OK) goto error;
    506 
    507 exit:
    508   if(file) fclose(file);
    509   if(srcvl) {
    510     srcvl_ref_put(srcvl);
    511     srcvl = NULL;
    512   }
    513   return res;
    514 error:
    515   goto exit;
    516 }
    517 
    518 static res_T
    519 open_output_stream(const char* name, const int force_overwriting, FILE** stream)
    520 {
    521   res_T res = RES_OK;
    522   int fd = -1;
    523   FILE* fp = NULL;
    524   ASSERT(name);
    525 
    526   if(force_overwriting) {
    527     fp = fopen(name, "w");
    528     if(!fp) {
    529       fprintf(stderr, "Could not open the output file `%s'.\n", name);
    530       goto error;
    531     }
    532   } else {
    533     fd = open(name, O_CREAT|O_WRONLY|O_EXCL|O_TRUNC, S_IRUSR|S_IWUSR);
    534     if(fd >= 0) {
    535       fp = fdopen(fd, "w");
    536       if(fp == NULL) {
    537         fprintf(stderr, "Could not open the output file `%s'.\n", name);
    538         goto error;
    539       }
    540     } else if(errno == EEXIST) {
    541       fprintf(stderr,
    542         "The output file `%s' already exists. Use -f to overwrite it.\n", name);
    543       goto error;
    544     } else {
    545       fprintf(stderr,
    546         "Unexpected error while opening the output file `%s'.\n", name);
    547       goto error;
    548     }
    549   }
    550 
    551 exit:
    552   *stream = fp;
    553   return res;
    554 error:
    555   res = RES_IO_ERR;
    556   if(fp) {
    557     CHK(fclose(fp) == 0);
    558     fp = NULL;
    559   } else if(fd >= 0) {
    560     CHK(close(fd) == 0);
    561   }
    562   goto exit;
    563 }
    564 
    565 /*******************************************************************************
    566  * Solstice local functions
    567  ******************************************************************************/
    568 res_T
    569 solstice_init
    570   (struct mem_allocator* allocator,
    571    const struct solstice_args* args,
    572    struct solstice* solstice)
    573 {
    574   res_T res = RES_OK;
    575   ASSERT(solstice && args);
    576 
    577   memset(solstice, 0, sizeof(struct solstice));
    578   htable_material_init(allocator, &solstice->materials);
    579   htable_object_init(allocator, &solstice->objects);
    580   htable_anchor_init(allocator, &solstice->anchors);
    581   htable_receiver_init(allocator, &solstice->receivers);
    582   htable_primary_init(allocator, &solstice->primaries);
    583   darray_nodes_init(allocator, &solstice->roots);
    584   darray_nodes_init(allocator, &solstice->pivots);
    585   darray_receiver_init(allocator, &solstice->rcvs_list);
    586   darray_sun_dir_init(allocator, &solstice->sun_dirs);
    587 
    588   solstice->allocator = allocator ? allocator : &mem_default_allocator;
    589 
    590   logger_init(solstice->allocator, &solstice->logger);
    591   logger_set_stream(&solstice->logger, LOG_ERROR, log_err, NULL);
    592   logger_set_stream(&solstice->logger, LOG_WARNING, log_warn, NULL);
    593 
    594   res = ssol_device_create(&solstice->logger, allocator, args->nthreads,
    595     args->verbose, &solstice->ssol);
    596   if(res != RES_OK) {
    597     fprintf(stderr, "Could not create the Solstice Solver device.\n");
    598     goto error;
    599   }
    600 
    601   res = ssol_scene_create(solstice->ssol, &solstice->scene);
    602   if(res != RES_OK) {
    603     fprintf(stderr, "Could not create the Solstice Solver scene.\n");
    604     goto error;
    605   }
    606 
    607   res = ssol_material_create_virtual(solstice->ssol, &solstice->mtl_virtual);
    608   if(res != RES_OK) {
    609     fprintf(stderr, "Could not create the global virtual material.\n");
    610     goto error;
    611   }
    612 
    613   res = solparser_create(allocator, &solstice->parser);
    614   if(res != RES_OK) {
    615     fprintf(stderr, "Could not create the Solstice Parser.\n");
    616     goto error;
    617   }
    618 
    619   res = setup_sun_dirs(solstice, args);
    620   if(res != RES_OK) goto error;
    621 
    622   switch(args->sun_algorithm) {
    623     case SOLSTICE_SUN_DIRECTION_ALGORITHM_MEEUS:
    624      solstice->sun_algorithm = SSOL_SUN_DIRECTION_ALGORITHM_MEEUS;
    625      break;
    626     case SOLSTICE_SUN_DIRECTION_ALGORITHM_PSA:
    627      solstice->sun_algorithm = SSOL_SUN_DIRECTION_ALGORITHM_PSA;
    628      break;
    629     default: FATAL("Invalid sun algorithm.\n"); break;
    630   }
    631 
    632   if(args->rng_state_input_filename) {
    633     solstice->rng_state_input = fopen(args->rng_state_input_filename, "r");
    634     if(!solstice->rng_state_input) {
    635       fprintf(stderr, "Could not open the input RNG state file.\n");
    636       res = RES_IO_ERR;
    637       goto error;
    638     }
    639   }
    640 
    641   if(args->rng_state_output_filename) {
    642     res = open_output_stream(args->rng_state_output_filename,
    643       args->force_overwriting, &solstice->rng_state_output);
    644     if(res != RES_OK) goto error;
    645   }
    646 
    647   if(!args->output_filename) {
    648     solstice->output = stdout;
    649   } else {
    650     res = open_output_stream(args->output_filename, args->force_overwriting,
    651       &solstice->output);
    652     if(res != RES_OK) goto error;
    653   }
    654 
    655   res = load_data(solstice, args);
    656   if(res != RES_OK) goto error;
    657 
    658   if(args->receivers_filename) {
    659     res = load_receivers(solstice, args);
    660     if(res != RES_OK) goto error;
    661   }
    662 
    663   res = solstice_setup_entities(solstice);
    664   if(res != RES_OK) {
    665     fprintf(stderr, "Could not setup the Solstice entities.\n");
    666     goto error;
    667   }
    668 
    669   res = solstice_create_sun(solstice);
    670   if(res != RES_OK) {
    671     fprintf(stderr, "Could not setup the Solstice sun.\n");
    672     goto error;
    673   }
    674 
    675   res = solstice_create_atmosphere(solstice);
    676   if(res != RES_OK) {
    677     fprintf(stderr, "Could not setup the Solstice atmosphere.\n");
    678     goto error;
    679   }
    680 
    681   solstice->nexperiments = args->nexperiments;
    682   solstice->dump_format = args->dump_format;
    683   solstice->dump_split_mode = args->dump_split_mode;
    684   solstice->dump_paths = args->dump_paths;
    685 
    686   /* If a dni value is provided on the command line, the dni defined in the yaml
    687    * description of the sun is no longer used */
    688   if(args->dni > 0) {
    689     res = ssol_sun_set_dni(solstice->sun, args->dni);
    690     if(res != RES_OK) {
    691       fprintf(stderr, "Could not setup the DNI of the sun.\n");
    692       goto error;
    693     }
    694   }
    695 
    696   solstice->path_tracker = SSOL_PATH_TRACKER_DEFAULT;
    697   solstice->path_tracker.infinite_ray_length = args->infinite_ray_length;
    698   solstice->path_tracker.sun_ray_length = args->sun_ray_length;
    699 
    700   if(args->rendering) {
    701     res = setup_camera(solstice, args);
    702     if(res != RES_OK) goto error;
    703     res = setup_framebuffer(solstice, args);
    704     if(res != RES_OK) goto error;
    705     solstice->render_mode = args->render_mode;
    706     solstice->spp = args->img.spp;
    707     d3_set(solstice->up, args->camera.up);
    708   }
    709 
    710 exit:
    711   return res;
    712 error:
    713   solstice_release(solstice);
    714   goto exit;
    715 }
    716 
    717 void
    718 solstice_release(struct solstice* solstice)
    719 {
    720   ASSERT(solstice);
    721   clear_materials(&solstice->materials);
    722   clear_objects(&solstice->objects);
    723   clear_nodes(&solstice->roots);
    724   clear_anchors(&solstice->anchors);
    725   /* Don't clear pivots, as they are cleared from some root */
    726   if(solstice->ssol) SSOL(device_ref_put(solstice->ssol));
    727   if(solstice->scene) SSOL(scene_ref_put(solstice->scene));
    728   if(solstice->sun) SSOL(sun_ref_put(solstice->sun));
    729   if(solstice->atmosphere) SSOL(atmosphere_ref_put(solstice->atmosphere));
    730   if(solstice->parser) solparser_ref_put(solstice->parser);
    731   if(solstice->camera) SSOL(camera_ref_put(solstice->camera));
    732   if(solstice->framebuffer) SSOL(image_ref_put(solstice->framebuffer));
    733   if(solstice->output && solstice->output != stdout) fclose(solstice->output);
    734   if(solstice->mtl_virtual) SSOL(material_ref_put(solstice->mtl_virtual));
    735   if(solstice->rng_state_input) fclose(solstice->rng_state_input);
    736   if(solstice->rng_state_output) fclose(solstice->rng_state_output);
    737   htable_material_release(&solstice->materials);
    738   htable_object_release(&solstice->objects);
    739   htable_anchor_release(&solstice->anchors);
    740   htable_receiver_release(&solstice->receivers);
    741   htable_primary_release(&solstice->primaries);
    742   darray_nodes_release(&solstice->roots);
    743   darray_nodes_release(&solstice->pivots);
    744   darray_receiver_release(&solstice->rcvs_list);
    745   darray_sun_dir_release(&solstice->sun_dirs);
    746   logger_release(&solstice->logger);
    747 }
    748 
    749 res_T
    750 solstice_run(struct solstice* solstice)
    751 {
    752   const struct solstice_sun_dir* sun_dirs = NULL;
    753   size_t nsun_dirs = 0;
    754   size_t i;
    755   int dump;
    756   int draw;
    757   res_T res = RES_OK;
    758   ASSERT(solstice);
    759 
    760   sun_dirs = darray_sun_dir_cdata_get(&solstice->sun_dirs);
    761   nsun_dirs = darray_sun_dir_size_get(&solstice->sun_dirs);
    762 
    763   dump = solstice->dump_format != SOLSTICE_ARGS_DUMP_NONE;
    764   draw = solstice->framebuffer != NULL;
    765 
    766   if(!nsun_dirs) {
    767     const double sun_dir[3] = {0, 0, -1};
    768 
    769     res = ssol_sun_set_direction(solstice->sun, sun_dir);
    770     if(res != RES_OK) {
    771       fprintf(stderr, "Could not update the sun direction.\n");
    772       goto error;
    773     }
    774 
    775     fprintf(solstice->output, "#--- No Sun direction\n");
    776 
    777     if(dump) {
    778       res = solstice_dump(solstice);
    779       if(res != RES_OK) goto error;
    780     } else {
    781       ASSERT(draw);
    782       res = solstice_draw(solstice);
    783       if(res != RES_OK) goto error;
    784     }
    785   } else {
    786     FOR_EACH(i, 0, nsun_dirs) {
    787       double sun_dir[3];
    788       if(sun_dirs[i].type == SOLSTICE_SUN_DIR_SPHERICAL) {
    789         spherical_to_cartesian_sun_dir(&sun_dirs[i].u.spherical, sun_dir);
    790         fprintf(solstice->output, "#--- Sun direction: %g %g (%g %g %g)\n",
    791             sun_dirs[i].u.spherical.azimuth, sun_dirs[i].u.spherical.elevation,
    792             SPLIT3(sun_dir));
    793         res = ssol_sun_set_direction(solstice->sun, sun_dir);
    794         if(res != RES_OK) {
    795           fprintf(stderr, "Could not update the sun direction.\n");
    796           goto error;
    797         }
    798       } else {
    799         char buf[128];
    800         struct ssol_location loc;
    801         const struct solstice_location_time* lt = &sun_dirs[i].u.location_time;
    802         ASSERT(sun_dirs[i].type == SOLSTICE_SUN_DIR_LOCATION_TIME);
    803         loc.latitude = lt->latitude;
    804         loc.longitude = lt->longitude;
    805         res = ssol_sun_set_location_and_date(solstice->sun, &loc, &lt->time);
    806         if(res != RES_OK) {
    807           fprintf(stderr, "Could not update the sun location and time.\n");
    808           goto error;
    809         }
    810         res = ssol_sun_get_direction(solstice->sun, sun_dir);
    811         if(res != RES_OK) goto error;
    812         if(0 == strftime(buf, sizeof(buf),
    813               "%Y-%m-%dT%H:%M:%S", &sun_dirs[i].u.location_time.time)) {
    814           res = RES_BAD_ARG;
    815           goto error;
    816         }
    817         fprintf(solstice->output,
    818             "#--- Sun location and time: %g %g %s (%g %g %g)\n",
    819             lt->latitude, lt->longitude, buf,
    820             SPLIT3(sun_dir));
    821       }
    822 
    823       res = solstice_update_entities(solstice, sun_dir);
    824       if(res != RES_OK) goto error;
    825 
    826       if(draw) {
    827         res = solstice_draw(solstice);
    828         if(res != RES_OK) goto error;
    829       } else if(dump) {
    830         res = solstice_dump(solstice);
    831         if(res != RES_OK) goto error;
    832       } else {
    833         res = solstice_solve(solstice);
    834         if(res != RES_OK) goto error;
    835       }
    836     }
    837   }
    838 
    839 exit:
    840   return res;
    841 error:
    842   goto exit;
    843 }
    844