htrdr

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

commit 77da6433dffb3b402fbe9cae8607d3e908540e98
parent d29b31e13c9fdaeb0142ec4597f44fe08d73eaf4
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Wed,  9 Jun 2021 15:05:06 +0200

Support isotropic phase functions in htrdr-combustion

Diffstat:
Mcmake/atmosphere/CMakeLists.txt | 2+-
Mcmake/combustion/CMakeLists.txt | 5+++--
Mdoc/htrdr-combustion.1.txt.in | 3+++
Msrc/combustion/htrdr_combustion.c | 95++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/combustion/htrdr_combustion_args.c | 8+++++++-
Msrc/combustion/htrdr_combustion_args.h.in | 8++++++++
Msrc/combustion/htrdr_combustion_c.h | 13+++++++++++--
Msrc/combustion/htrdr_combustion_compute_radiance_sw.c | 56++++++++------------------------------------------------
Asrc/combustion/htrdr_combustion_phase_func.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 200 insertions(+), 99 deletions(-)

diff --git a/cmake/atmosphere/CMakeLists.txt b/cmake/atmosphere/CMakeLists.txt @@ -26,7 +26,7 @@ find_package(RCMake 0.3 REQUIRED) find_package(RSys 0.11 REQUIRED) find_package(Star3D 0.7.1 REQUIRED) find_package(StarSF 0.6 REQUIRED) -find_package(StarSP 0.8 REQUIRED) +find_package(StarSP 0.9 REQUIRED) find_package(StarVX 0.1 REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) diff --git a/cmake/combustion/CMakeLists.txt b/cmake/combustion/CMakeLists.txt @@ -26,7 +26,7 @@ find_package(RCMake 0.3 REQUIRED) find_package(RSys 0.11 REQUIRED) find_package(Star3D 0.7.1 REQUIRED) find_package(StarSF 0.6 REQUIRED) -find_package(StarSP 0.8 REQUIRED) +find_package(StarSP 0.9 REQUIRED) find_package(StarVX 0.1 REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) @@ -66,7 +66,8 @@ set(HTRDR_COMBUSTION_FILES_SRC htrdr_combustion_compute_radiance_sw.c htrdr_combustion_geometry_ray_filter.c htrdr_combustion_laser.c - htrdr_combustion_main.c) + htrdr_combustion_main.c + htrdr_combustion_phase_func.c) set(HTRDR_COMBUSTION_FILES_INC htrdr_combustion.h diff --git a/doc/htrdr-combustion.1.txt.in b/doc/htrdr-combustion.1.txt.in @@ -138,6 +138,9 @@ OPTIONS *-h*:: List short help and exit. +*-I*:: + Use an isotropic phase function rather than the RDG-FA model. + *-i* <__image-parameter__:...>:: Define the sensor array. Available image parameters are: diff --git a/src/combustion/htrdr_combustion.c b/src/combustion/htrdr_combustion.c @@ -38,21 +38,20 @@ * Helper functions ******************************************************************************/ static void -release_phase_functions - (struct htrdr_combustion* cmd, - struct ssf_phase* phases[]) +release_phase_functions(struct htrdr_combustion* cmd) { size_t i; ASSERT(cmd); - if(!phases) return; /* Nothing to release */ + if(!cmd->phase_functions) return; /* Nothing to release */ FOR_EACH(i, 0, htrdr_get_threads_count(cmd->htrdr)) { - if(phases[i]) { - SSF(phase_ref_put(phases[i])); + if(cmd->phase_functions[i]) { + SSF(phase_ref_put(cmd->phase_functions[i])); } } - MEM_RM(htrdr_get_allocator(cmd->htrdr), phases); + MEM_RM(htrdr_get_allocator(cmd->htrdr), cmd->phase_functions); + cmd->phase_functions = NULL; } static res_T @@ -107,29 +106,29 @@ setup_simd (struct htrdr_combustion* cmd, const struct htrdr_combustion_args* args) { + struct ssf_info ssf_info = SSF_INFO_NULL; ASSERT(cmd && args); - if(!args->use_simd) { - cmd->rdgfa_simd = SSF_SIMD_NONE; + cmd->rdgfa_simd = SSF_SIMD_NONE; + + if(args->phase_func_type != HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA) + return RES_OK; /* Nothing to do */ + + /* Check SIMD support for the RDG-FA phase function */ + ssf_get_info(&ssf_info); + if(ssf_info.simd_256) { + htrdr_log(cmd->htrdr, + "Use the SIMD-256 instruction set for the RDG-FA phase function.\n"); + cmd->rdgfa_simd = SSF_SIMD_256; + } else if(ssf_info.simd_128) { + htrdr_log(cmd->htrdr, + "Use the SIMD-128 instruction set for the RDG-FA phase function.\n"); + cmd->rdgfa_simd = SSF_SIMD_128; } else { - struct ssf_info ssf_info = SSF_INFO_NULL; - - /* Check SIMD support for the RDG-FA phase function */ - ssf_get_info(&ssf_info); - if(ssf_info.simd_256) { - htrdr_log(cmd->htrdr, - "Use the SIMD-256 instruction set for the RDG-FA phase function.\n"); - cmd->rdgfa_simd = SSF_SIMD_256; - } else if(ssf_info.simd_128) { - htrdr_log(cmd->htrdr, - "Use the SIMD-128 instruction set for the RDG-FA phase function.\n"); - cmd->rdgfa_simd = SSF_SIMD_128; - } else { - htrdr_log_warn(cmd->htrdr, - "Cannot use SIMD for the RDG-FA phase function: the " - "Star-ScatteringFunction library was compiled without SIMD support.\n"); - cmd->rdgfa_simd = SSF_SIMD_NONE; - } + htrdr_log_warn(cmd->htrdr, + "Cannot use SIMD for the RDG-FA phase function: the " + "Star-ScatteringFunction library was compiled without SIMD support.\n"); + cmd->rdgfa_simd = SSF_SIMD_NONE; } return RES_OK; } @@ -232,24 +231,34 @@ setup_laser } static res_T -setup_phase_functions - (struct htrdr_combustion* cmd, - const struct ssf_phase_type* phase_type, - struct ssf_phase** out_phases[]) +setup_phase_functions(struct htrdr_combustion* cmd) { struct mem_allocator* allocator = NULL; - struct ssf_phase** phases = NULL; + const struct ssf_phase_type* phase_type = NULL; size_t nthreads; size_t i; res_T res = RES_OK; - ASSERT(cmd && phase_type && out_phases); + ASSERT(cmd); nthreads = htrdr_get_threads_count(cmd->htrdr); allocator = htrdr_get_allocator(cmd->htrdr); + switch(cmd->phase_func_type) { + case HTRDR_COMBUSTION_ARGS_PHASE_FUNC_ISOTROPIC: + htrdr_log(cmd->htrdr, "Use an isotropic phase function.\n"); + phase_type = &ssf_phase_hg; + break; + case HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA: + htrdr_log(cmd->htrdr, "Use the RDG-FA phase function.\n"); + phase_type = &ssf_phase_rdgfa; + break; + default: FATAL("Unreachable code.\n"); break; + } + /* Allocate the list of per thread phase function */ - phases = MEM_CALLOC(allocator, nthreads, sizeof(*phases)); - if(!phases) { + cmd->phase_functions = MEM_CALLOC + (allocator, nthreads, sizeof(*cmd->phase_functions)); + if(!cmd->phase_functions) { htrdr_log_err(cmd->htrdr, "Could not allocate the per thread RDG-FA phase function.\n"); res = RES_MEM_ERR; @@ -258,7 +267,7 @@ setup_phase_functions /* Create the per thread phase function */ FOR_EACH(i, 0, nthreads) { - res = ssf_phase_create(allocator, phase_type, phases+i); + res = ssf_phase_create(allocator, phase_type, cmd->phase_functions+i); if(res != RES_OK) { htrdr_log_err(cmd->htrdr, "Could not create the phase function for the thread %lu -- %s.\n", @@ -268,11 +277,9 @@ setup_phase_functions } exit: - *out_phases = phases; return res; error: - release_phase_functions(cmd, phases); - phases = NULL; + release_phase_functions(cmd); goto exit; } @@ -506,8 +513,7 @@ combustion_release(ref_T* ref) if(cmd->laser) htrdr_combustion_laser_ref_put(cmd->laser); if(cmd->buf) htrdr_buffer_ref_put(cmd->buf); if(cmd->output && cmd->output != stdout) CHK(fclose(cmd->output) == 0); - release_phase_functions(cmd, cmd->rdgfa_phase_functions); - release_phase_functions(cmd, cmd->hg_phase_functions); + release_phase_functions(cmd); str_release(&cmd->output_name); htrdr = cmd->htrdr; @@ -543,9 +549,12 @@ htrdr_combustion_create cmd->spp = args->image.spp; cmd->output_type = args->output_type; + cmd->phase_func_type = args->phase_func_type; res = setup_output(cmd, args); if(res != RES_OK) goto error; + res = setup_phase_functions(cmd); + if(res != RES_OK) goto error; res = setup_simd(cmd, args); if(res != RES_OK) goto error; res = setup_geometry(cmd, args); @@ -554,10 +563,6 @@ htrdr_combustion_create if(res != RES_OK) goto error; res = setup_laser(cmd, args); if(res != RES_OK) goto error; - res = setup_phase_functions(cmd, &ssf_phase_rdgfa, &cmd->rdgfa_phase_functions); - if(res != RES_OK) goto error; - res = setup_phase_functions(cmd, &ssf_phase_hg, &cmd->hg_phase_functions); - if(res != RES_OK) goto error; res = setup_buffer(cmd, args); if(res != RES_OK) goto error; res = setup_medium(cmd, args); diff --git a/src/combustion/htrdr_combustion_args.c b/src/combustion/htrdr_combustion_args.c @@ -67,6 +67,8 @@ print_help(const char* cmd) printf( " -h display this help and exit.\n"); printf( +" -I use an isotropic phase function rather than the RDG-FA.\n"); + printf( " -i <image> define the image to compute. Refer to the man\n" " page for the list of image options.\n"); printf( @@ -244,7 +246,7 @@ htrdr_combustion_args_init *args = HTRDR_COMBUSTION_ARGS_DEFAULT; - while((opt = getopt(argc, argv, "C:D:d:F:fg:hi:l:m:NO:o:p:R:r:sT:t:V:vw:")) != -1) { + while((opt = getopt(argc, argv, "C:D:d:F:fg:hIi:l:m:NO:o:p:R:r:sT:t:V:vw:")) != -1) { switch(opt) { case 'C': args->output_type = HTRDR_COMBUSTION_ARGS_OUTPUT_IMAGE; @@ -259,6 +261,7 @@ htrdr_combustion_args_init break; case 'F': res = cstr_parse_list(optarg, ':', parse_fractal_parameters, args); + args->phase_func_type = HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA; break; case 'f': args->force_overwriting = 1; break; case 'g': @@ -269,6 +272,9 @@ htrdr_combustion_args_init htrdr_combustion_args_release(args); args->quit = 1; goto exit; + case 'I': + args->phase_func_type = HTRDR_COMBUSTION_ARGS_PHASE_FUNC_ISOTROPIC; + break; case 'i': res = htrdr_args_image_parse(&args->image, optarg); break; diff --git a/src/combustion/htrdr_combustion_args.h.in b/src/combustion/htrdr_combustion_args.h.in @@ -37,6 +37,12 @@ enum htrdr_combustion_args_grid_definition_type { HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_TYPES_COUNT__ }; +enum htrdr_combustion_args_phase_func_type { + HTRDR_COMBUSTION_ARGS_PHASE_FUNC_ISOTROPIC, + HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA, + HTRDR_COMBUSTION_ARGS_PHASE_FUNC_TYPES_COUNT__ +}; + struct htrdr_combustion_args_grid_definition { union { unsigned hint; /* Hint on the grid definition to eval */ @@ -82,6 +88,7 @@ struct htrdr_combustion_args { /* Miscellaneous parameters */ unsigned nthreads; /* Hint on the number of threads to use */ enum htrdr_combustion_args_output_type output_type; + enum htrdr_combustion_args_phase_func_type phase_func_type; int precompute_normals; /* Pre-compute the tetrahedra normals */ int force_overwriting; int verbose; /* Verbosity level */ @@ -118,6 +125,7 @@ struct htrdr_combustion_args { \ UINT_MAX, /* #threads */ \ HTRDR_COMBUSTION_ARGS_OUTPUT_IMAGE, /* Output type */ \ + HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA, /* Phase func type */ \ 0, /* Precompute normals */ \ 0, /* Force overwriting */ \ 0, /* Verbose flag */ \ diff --git a/src/combustion/htrdr_combustion_c.h b/src/combustion/htrdr_combustion_c.h @@ -39,6 +39,7 @@ struct htrdr_materials; struct htrdr_rectangle; struct ssf_phase; struct ssp_rng; +struct suvm_primitive; struct combustion_pixel_flux { struct htrdr_accum flux; /* In W/m^2 */ @@ -72,8 +73,7 @@ struct htrdr_combustion { struct htrdr_combustion_laser* laser; /* Laser sheet */ double wavelength; /* Wavelength of the laser in nanometer */ - struct ssf_phase** rdgfa_phase_functions; /* Per thread RDG-FA phase func */ - struct ssf_phase** hg_phase_functions; /* Per thread Henyey-Greenstein func */ + struct ssf_phase** phase_functions; /* Per thread phase func */ enum ssf_simd rdgfa_simd; /* SIMD support for the RDG-FA phase func */ struct htrdr_buffer_layout buf_layout; @@ -83,6 +83,7 @@ struct htrdr_combustion { FILE* output; /* Output stream */ struct str output_name; /* Name of the output stream */ enum htrdr_combustion_args_output_type output_type; /* Type of output data */ + enum htrdr_combustion_args_phase_func_type phase_func_type; /* Phase func */ ref_T ref; struct htrdr* htrdr; @@ -106,4 +107,12 @@ combustion_compute_radiance_sw const double dir_in[3], double* out_weigh); /* Shortwave radiance in W/m^2/sr */ +extern LOCAL_SYM struct ssf_phase* +combustion_fetch_phase_function + (struct htrdr_combustion* cmd, + const double wavelength, /* In nanometer */ + const struct suvm_primitive* prim, + const double bcoords[4], + const size_t ithread); + #endif /* HTRDR_COMBUSTION_C_H */ diff --git a/src/combustion/htrdr_combustion_compute_radiance_sw.c b/src/combustion/htrdr_combustion_compute_radiance_sw.c @@ -536,12 +536,7 @@ laser_once_scattered const double dir[3], const double range_in[2]) { - /* RDG-FA phase function */ - struct atrstm_rdgfa rdgfa_param = ATRSTM_RDGFA_NULL; - struct atrstm_fetch_rdgfa_args fetch_rdgfa_args = - ATRSTM_FETCH_RDGFA_ARGS_DEFAULT; - struct ssf_phase_rdgfa_setup_args setup_rdgfa_args = - SSF_PHASE_RDGFA_SETUP_ARGS_DEFAULT; + /* Phase function */ struct ssf_phase* phase = NULL; /* Surface ray tracing */ @@ -650,23 +645,9 @@ laser_once_scattered Tr_ext_xsc_lse = transmissivity(cmd, rng, ATRSTM_RADCOEF_Kext, xsc, wi, range); if(Tr_ext_xsc_lse == 0) return 0; /* No laser contribution */ - /* Retrieve the RDG-FA phase function parameters from the semi transparent - * medium */ - fetch_rdgfa_args.wavelength = wlen; - fetch_rdgfa_args.prim = sc_sample.position.prim; - fetch_rdgfa_args.bcoords[0] = sc_sample.position.bcoords[0]; - fetch_rdgfa_args.bcoords[1] = sc_sample.position.bcoords[1]; - fetch_rdgfa_args.bcoords[2] = sc_sample.position.bcoords[2]; - fetch_rdgfa_args.bcoords[3] = sc_sample.position.bcoords[3]; - ATRSTM(fetch_rdgfa(cmd->medium, &fetch_rdgfa_args, &rdgfa_param)); - - /* Setup the RDG-FA phase function */ - phase = cmd->rdgfa_phase_functions[ithread]; - setup_rdgfa_args.wavelength = rdgfa_param.wavelength; - setup_rdgfa_args.fractal_dimension = rdgfa_param.fractal_dimension; - setup_rdgfa_args.gyration_radius = rdgfa_param.gyration_radius; - setup_rdgfa_args.simd = cmd->rdgfa_simd; - SSF(phase_rdgfa_setup(phase, &setup_rdgfa_args)); + /* Retrieve phase function */ + phase = combustion_fetch_phase_function + (cmd, wlen, &sc_sample.position.prim, sc_sample.position.bcoords, ithread); /* Evaluate the phase function at the scattering position */ d3_minus(wo, dir); /* Ensure SSF convention */ @@ -682,7 +663,7 @@ laser_once_scattered return L; } -static void +static INLINE void sample_scattering_direction (struct htrdr_combustion* cmd, const size_t ithread, @@ -692,36 +673,15 @@ sample_scattering_direction const double incoming_dir[3], double scattering_dir[3]) { - /* RDG-FA phase function */ - struct atrstm_rdgfa rdgfa_param = ATRSTM_RDGFA_NULL; - struct atrstm_fetch_rdgfa_args fetch_rdgfa_args = - ATRSTM_FETCH_RDGFA_ARGS_DEFAULT; - struct ssf_phase_rdgfa_setup_args setup_rdgfa_args = - SSF_PHASE_RDGFA_SETUP_ARGS_DEFAULT; struct ssf_phase* phase = NULL; - - /* Miscellaneous variable */ double wo[3]; ASSERT(cmd && rng && scattering && incoming_dir && scattering_dir); ASSERT(!POSITION_NONE(scattering)); - /* Retrieve the RDG-FA phase function parameters */ - fetch_rdgfa_args.wavelength = wlen; - fetch_rdgfa_args.prim = scattering->prim; - fetch_rdgfa_args.bcoords[0] = scattering->bcoords[0]; - fetch_rdgfa_args.bcoords[1] = scattering->bcoords[1]; - fetch_rdgfa_args.bcoords[2] = scattering->bcoords[2]; - fetch_rdgfa_args.bcoords[3] = scattering->bcoords[3]; - ATRSTM(fetch_rdgfa(cmd->medium, &fetch_rdgfa_args, &rdgfa_param)); - - /* Setup the RDG-FA phase function corresponding to the scattering event */ - phase = cmd->rdgfa_phase_functions[ithread]; - setup_rdgfa_args.wavelength = rdgfa_param.wavelength; - setup_rdgfa_args.fractal_dimension = rdgfa_param.fractal_dimension; - setup_rdgfa_args.gyration_radius = rdgfa_param.gyration_radius; - setup_rdgfa_args.simd = cmd->rdgfa_simd; - SSF(phase_rdgfa_setup(phase, &setup_rdgfa_args)); + /* Fetch the phase function */ + phase = combustion_fetch_phase_function + (cmd, wlen, &scattering->prim, scattering->bcoords, ithread); /* Sample a new optical path direction from the phase function */ d3_minus(wo, incoming_dir); /* Ensure SSF convention */ diff --git a/src/combustion/htrdr_combustion_phase_func.c b/src/combustion/htrdr_combustion_phase_func.c @@ -0,0 +1,109 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "htrdr_combustion_c.h" + +#include <astoria/atrstm.h> + +#include <star/ssf.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static struct ssf_phase* +combustion_fetch_phase_isotropic + (struct htrdr_combustion* cmd, + const double wavelength, /* In nanometer */ + const struct suvm_primitive* prim, + const double bcoords[4], + const size_t ithread) +{ + struct ssf_phase* phase = NULL; + ASSERT(cmd && wavelength > 0 && prim && bcoords); + ASSERT(ithread < htrdr_get_threads_count(cmd->htrdr)); + ASSERT(cmd->phase_func_type == HTRDR_COMBUSTION_ARGS_PHASE_FUNC_ISOTROPIC); + (void)wavelength, (void)prim, (void)bcoords; + + /* Setup the isotropic phase function */ + phase = cmd->phase_functions[ithread]; + SSF(phase_hg_setup(phase, 0)); + return phase; +} + +static struct ssf_phase* +combustion_fetch_phase_rdgfa + (struct htrdr_combustion* cmd, + const double wavelength, /* In nanometer */ + const struct suvm_primitive* prim, + const double bcoords[4], + const size_t ithread) +{ + struct atrstm_rdgfa rdgfa_param = ATRSTM_RDGFA_NULL; + struct atrstm_fetch_rdgfa_args fetch_rdgfa_args = + ATRSTM_FETCH_RDGFA_ARGS_DEFAULT; + struct ssf_phase_rdgfa_setup_args setup_rdgfa_args = + SSF_PHASE_RDGFA_SETUP_ARGS_DEFAULT; + struct ssf_phase* phase = NULL; + ASSERT(cmd && wavelength > 0 && prim && bcoords); + ASSERT(ithread < htrdr_get_threads_count(cmd->htrdr)); + ASSERT(cmd->phase_func_type == HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA); + + /* Retrieve the RDG-FA phase function parameters from the semi transparent + * medium */ + fetch_rdgfa_args.wavelength = wavelength; + fetch_rdgfa_args.prim = *prim; + fetch_rdgfa_args.bcoords[0] = bcoords[0]; + fetch_rdgfa_args.bcoords[1] = bcoords[1]; + fetch_rdgfa_args.bcoords[2] = bcoords[2]; + fetch_rdgfa_args.bcoords[3] = bcoords[3]; + ATRSTM(fetch_rdgfa(cmd->medium, &fetch_rdgfa_args, &rdgfa_param)); + + /* Setup the RDG-FA phase function */ + phase = cmd->phase_functions[ithread]; + setup_rdgfa_args.wavelength = rdgfa_param.wavelength; + setup_rdgfa_args.fractal_dimension = rdgfa_param.fractal_dimension; + setup_rdgfa_args.gyration_radius = rdgfa_param.gyration_radius; + setup_rdgfa_args.simd = cmd->rdgfa_simd; + SSF(phase_rdgfa_setup(phase, &setup_rdgfa_args)); + + return phase; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +struct ssf_phase* +combustion_fetch_phase_function + (struct htrdr_combustion* cmd, + const double wlen, /* In nanometer */ + const struct suvm_primitive* prim, + const double bcoords[4], + const size_t ithread) +{ + struct ssf_phase* phase = NULL; + switch(cmd->phase_func_type) { + case HTRDR_COMBUSTION_ARGS_PHASE_FUNC_ISOTROPIC: + phase = combustion_fetch_phase_isotropic(cmd, wlen, prim, bcoords, ithread); + break; + case HTRDR_COMBUSTION_ARGS_PHASE_FUNC_RDGFA: + phase = combustion_fetch_phase_rdgfa(cmd, wlen, prim, bcoords, ithread); + break; + default: FATAL("Unreachable code.\n"); break; + } + return phase; +} +