Program Listing for File snobal.cpp

Program Listing for File snobal.cpp#

Return to documentation for file (src/modules/snobal.cpp)

//
// Canadian Hydrological Model - The Canadian Hydrological Model (CHM) is a novel
// modular unstructured mesh based approach for hydrological modelling
// Copyright (C) 2018 Christopher Marsh
//
// This file is part of Canadian Hydrological Model.
//
// Canadian Hydrological Model 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.
//
// Canadian Hydrological Model 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 Canadian Hydrological Model.  If not, see
// <http://www.gnu.org/licenses/>.
//

#include "snobal.hpp"
REGISTER_MODULE_CPP(snobal);

snobal::snobal(config_file cfg)
        : module_base("snobal", parallel::data, cfg)
{
    depends("frac_precip_snow");
    depends("iswr");
    depends("rh");
    depends("t");
    depends("U_2m_above_srf");
    depends("p");
    depends("ilwr");

    // Optional subcanopy variables if a canopy module is included (used if exist)
    optional("frac_precip_snow_subcanopy");
    optional("iswr_subcanopy");
    optional("rh_subcanopy");
    optional("ta_subcanopy");
    optional("p_subcanopy");
    optional("ilwr_subcanopy");

    optional("drift_mass");
    depends("snow_albedo");
    optional("T_g");

    // Optional avalanche variables
    optional("delta_avalanche_snowdepth");
    optional("delta_avalanche_mass");

    provides("swe");
    provides("snowmelt_int");
    provides("R_n");
    provides("H");
    provides("E");
    provides("G");
    provides("M");
    provides("dQ");
    provides("cc");
    provides("T_s");
    provides("T_s_0");
    provides("T_s_l");
    provides("dead");
    provides("iswr_net");
    provides("isothermal");
    provides("ilwr_out");
    provides("sum_snowpack_runoff");
    provides("sum_snowpack_subl");
    provides("sum_snowpack_pcp");
    provides("sum_melt");
    provides("snowdepthavg");
    provides("snowdepthavg_vert");


}

void snobal::init(mesh& domain)
{

    drift_density = cfg.get("drift_density",300.);
    const_T_g = cfg.get("const_T_g",-4.0);

    //use slope corrected SWE for compaction
    use_slope_SWE = cfg.get("use_slope_SWE",true);

    //store all of snobals global_param variables from this timestep to be used as ICs for the next timestep
    #pragma omp parallel for
    for (size_t i = 0; i < domain->size_local_faces(); i++)
    {
       auto face = domain->face(i);

       auto& g = face->make_module_data<snodata>(ID);
       auto* sbal = &(g.data);

       sbal->param_snow_compaction = cfg.get("param_snow_compaction", 1); // new param is the default
       sbal->max_h2o_vol = cfg.get("max_h2o_vol", .0001); // 0.0001
       sbal->KT_WETSAND = cfg.get("kt_wetsand", 0.08);
       sbal->max_z_s_0 = cfg.get("max_active_layer", .1);
       sbal->z_0 = cfg.get("z_0", 0.001);
       sbal->z_T = cfg.get("z_T", 2.6);
       sbal->z_u = cfg.get("z_u", 2.0);
       sbal->z_g = cfg.get("z_g", 0.1);
       // True (1) -- relative to the snow surface via scale_wind_speed which takes into account snowdepth.
       sbal->relative_hts = 1;

       // if we don't use the slope corrected SWE for compaction
       // set it to -1 here, and we can check for this within snobal
       sbal->slope = use_slope_SWE ? face->slope() : -1;


       sbal->time_since_out = 0;
       sbal->current_time = 0;
       sbal->run_no_snow = 1;
       sbal->stop_no_snow = 1;

       if(!global_param->from_checkpoint())
       {
           g.sum_runoff = 0;
           g.sum_melt = 0;
           g.sum_subl = 0;
           g.sum_pcp_sno = 0;

           g.dead = 0;
           g.delta_avalanche_snowdepth = 0;
           g.delta_avalanche_swe = 0;

           sbal->h2o_sat = .3;
           sbal->layer_count = 0;
           sbal->m_s = 0.;
           sbal->m_s_0 = 0.;
           sbal->m_s_l = 0.;

           sbal->rho = 0.;
           sbal->T_s = -75. + FREEZE;
           sbal->T_s_0 = -75. + FREEZE; // assuming no snow
           sbal->T_s_l = -75. + FREEZE;
           sbal->z_s = 0.;

           sbal->ro_data = 0;

           sbal->h2o_total = 0;
           sbal->isothermal = 0;

           sbal->R_n_bar = 0.0;
           sbal->H_bar = 0.0;
           sbal->L_v_E_bar = 0.0;
           sbal->G_bar = 0.0;
           sbal->M_bar = 0.0;
           sbal->delta_Q_bar = 0.0;
           sbal->E_s_sum = 0.0;
           sbal->melt_sum = 0.0;
           sbal->ro_pred_sum = 0.0;

           sbal->snowcover = 0;
           sbal->precip_now = 0;
       }

       //init the step_info struct
       sbal->tstep_info[DATA_TSTEP].level = DATA_TSTEP;
       sbal->tstep_info[DATA_TSTEP].time_step = global_param->dt();
       sbal->tstep_info[DATA_TSTEP].intervals = 0;
       sbal->tstep_info[DATA_TSTEP].threshold = 20;
       sbal->tstep_info[DATA_TSTEP].output = 0;


       sbal->tstep_info[NORMAL_TSTEP].level = NORMAL_TSTEP;
       sbal->tstep_info[NORMAL_TSTEP].time_step = global_param->dt();
       sbal->tstep_info[NORMAL_TSTEP].intervals = sbal->tstep_info[DATA_TSTEP].time_step /
           sbal->tstep_info[NORMAL_TSTEP].time_step;;
       sbal->tstep_info[NORMAL_TSTEP].threshold = 20;
       sbal->tstep_info[NORMAL_TSTEP].output = 0;


       sbal->tstep_info[MEDIUM_TSTEP].level = MEDIUM_TSTEP;
       sbal->tstep_info[MEDIUM_TSTEP].time_step = global_param->dt()/4;
       sbal->tstep_info[MEDIUM_TSTEP].intervals = sbal->tstep_info[NORMAL_TSTEP].time_step /
           sbal->tstep_info[MEDIUM_TSTEP].time_step;
       sbal->tstep_info[MEDIUM_TSTEP].threshold = 10;
       sbal->tstep_info[MEDIUM_TSTEP].output = 0;


       sbal->tstep_info[SMALL_TSTEP].level = SMALL_TSTEP;
       sbal->tstep_info[SMALL_TSTEP].time_step = global_param->dt()/ 100;// 60;
       sbal->tstep_info[SMALL_TSTEP].intervals = sbal->tstep_info[MEDIUM_TSTEP].time_step /
           sbal->tstep_info[SMALL_TSTEP].time_step;
       sbal->tstep_info[SMALL_TSTEP].threshold = 0.2;
       sbal->tstep_info[SMALL_TSTEP].output = 0;


       if (face->has_initial_condition("swe"))
       {
           if( !is_nan(face->get_initial_condition("swe")))
           {
               sbal->rho = cfg.get("IC_rho",300.);
               sbal->T_s =  -10 + FREEZE;
               sbal->T_s_0 = -15. + FREEZE; //assuming no snow
               sbal->T_s_l = -15. + FREEZE;
               sbal->z_s = face->get_initial_condition("swe") / sbal->rho;

           }
       }

       sbal->init_snow();

       //in point mode, the entire mesh still exists, but no timeseries has been allocated for the faces
       //thus this segfaults. This should be fixed

       if (face->has_initial_condition("swe") &&  !is_nan(face->get_initial_condition("swe")))
       {
           (*face)["swe"_s]= sbal->m_s;
           (*face)["R_n"_s]= sbal->R_n;
           (*face)["H"_s]= sbal->H;
           (*face)["E"_s]= sbal->L_v_E;
           (*face)["G"_s]= sbal->G;
           (*face)["M"_s]= sbal->M;
           (*face)["dQ"_s]= sbal->delta_Q;
           (*face)["cc"_s]= sbal->cc_s;
           (*face)["T_s"_s]= sbal->T_s;
           (*face)["T_s_0"_s]= sbal->T_s_0;
           (*face)["T_s_l"_s]= sbal->T_s_l;
           (*face)["iswr_net"_s]= sbal->S_n;
           (*face)["isothermal"_s]= sbal->isothermal;
           (*face)["ilwr_out"_s]= sbal->R_n - sbal->S_n - sbal->I_lw;
           (*face)["snowmelt_int"_s]= 0;
           (*face)["sum_melt"_s]= g.sum_melt;
           (*face)["sum_snowpack_runoff"_s]= g.sum_runoff;
           (*face)["sum_snowpack_subl"_s]= g.sum_subl;
           (*face)["sum_snowpack_pcp"_s]= g.sum_pcp_sno;

           (*face)["snowdepthavg"_s]= sbal->z_s;
       }
    }

}
snobal::~snobal()
{

}

void snobal::run(mesh_elem &face)
{
    if(is_water(face))
    {
        set_all_nan_on_skip(face);
        return;
    }

    //debugging
    auto id = face->cell_local_id;

    bool run=false;


    auto hour = global_param->hour();
    auto day = global_param->day();
    auto month = global_param->month();


    //get the previous timesteps data out of the global_param store.
    auto& g = face->get_module_data<snodata>(ID);
    auto* sbal = &(g.data);

    sbal->_debug_id = id;

    sbal->P_a = mio::Atmosphere::stdAirPressure( face->get_z());

    double albedo = (*face)["snow_albedo"_s];

    // Optional inputs if there is a canopy or not
    double ilwr;
    if(has_optional("ilwr_subcanopy")) {
        ilwr = (*face)["ilwr_subcanopy"_s];
    } else {
        ilwr = (*face)["ilwr"_s];
    }

    // Optional inputs if there is a canopy or not
    double rh;
    if(has_optional("rh_subcanopy")) {
        rh = (*face)["rh_subcanopy"_s];
    } else {
        rh = (*face)["rh"_s];
    }

    // Optional inputs if there is a canopy or not
    double t;
    if(has_optional("ta_subcanopy")) {
        t = (*face)["ta_subcanopy"_s];
    } else {
        t = (*face)["t"_s];
    }

    double ea = mio::Atmosphere::vaporSaturationPressure(t+273.15)  * rh/100.;

    // Optional inputs if there is a canopy or not
    if(has_optional("iswr_subcanopy")) {
        sbal->input_rec2.S_n = (1.0-albedo) * (*face)["iswr_subcanopy"_s];
    } else {
        sbal->input_rec2.S_n = (1.0-albedo) * (*face)["iswr"_s];
    }
    sbal->input_rec2.I_lw = ilwr;
    sbal->input_rec2.T_a = t+FREEZE;
    sbal->input_rec2.e_a = ea;

    // Optional inputs if there is a canopy or not
    sbal->input_rec2.u = (*face)["U_2m_above_srf"_s];
    sbal->input_rec2.u = std::max(sbal->input_rec2.u,1.0);

    if(has_optional("T_g"))
        sbal->input_rec2.T_g = (*face)["T_g"_s] + 273.15;
    else
        sbal->input_rec2.T_g = const_T_g+FREEZE;


    sbal->input_rec2.ro = 0.;

    if(global_param->first_time_step)
    {
        sbal->input_rec1.S_n = sbal->input_rec2.S_n;
        sbal->input_rec1.I_lw =  sbal->input_rec2.I_lw;
        sbal->input_rec1.T_a =  sbal->input_rec2.T_a;
        sbal->input_rec1.e_a =  sbal->input_rec2.e_a;
        sbal->input_rec1.u = sbal->input_rec2.u;
        sbal->input_rec1.T_g =  sbal->input_rec2.T_g; //TODO: FIx this with a gflux estimate
        sbal->input_rec1.ro =  sbal->input_rec2.ro;
    }

    // Optional inputs if there is a canopy or not
    double p;
    if(has_optional("p_subcanopy")) {
        p = (*face)["p_subcanopy"_s];
    } else {
        p = (*face)["p"_s];
    }

    if(p >= 0.00025) //0.25mm swe
    {
        sbal->precip_now = 1;
        sbal->m_pp = p;

        // Optional inputs if there is a canopy or not
        if(has_optional("frac_precip_snow_subcanopy")) {
            sbal->percent_snow = (*face)["frac_precip_snow_subcanopy"_s];
        } else {
            sbal->percent_snow = (*face)["frac_precip_snow"_s];
        }
        sbal->rho_snow = 100.; //http://ccc.atmos.colostate.edu/pdfs/SnowDensity_BAMS.pdf
        sbal->T_pp = t+FREEZE; // The comments are wrong this is definietly not in C and is K
        sbal->stop_no_snow=0;
    }
    else
    {
        sbal->precip_now = 0;
        sbal->stop_no_snow=0;
        sbal->m_pp = 0.;
    }

    if(has_optional("drift_mass"))
    {

        double mass = (*face)["drift_mass"_s];
        mass = is_nan(mass) ? 0 : mass;

        double transport_density;
        if(mass < 0.)
        {
            // Erosion: use the density of snow on the ground to compute the depth of eroded snow
            transport_density = sbal->rho;
        }
        else
        {
            // Deposition: assume snow is deposited with a given density: drift_density
            transport_density = drift_density;
        }

        //m_s is kg/m^2 and mass is kg/m^2
        //negative = mass removal
//        if(mass < 0 && (sbal->m_s+mass ) < 0 ) // are we about to remove more mass than what exists???
//            mass = -sbal->m_s; //cap it to remove no more than available mass

        sbal->_adj_snow(mass / transport_density, mass);
    }

    // If snow avalanche variables are available
    bool snow_slide = false;
    if(has_optional("delta_avalanche_snowdepth")) {
        g.delta_avalanche_snowdepth = (*face)["delta_avalanche_snowdepth"_s];
    }
    if(has_optional("delta_avalanche_mass")) {
        g.delta_avalanche_swe = (*face)["delta_avalanche_mass"_s];
        snow_slide = true;
    }

    // Redistribute snow (if snow_slide is used)
    if(snow_slide) {
        // _adj_snow(depth change (m), swe change (kg/m^2))
        // Convert change in volume and mass back to depth and mass per area, respectivly.
        // Assumes snow depth is uniform across triangle
        double area = face->get_area(); // area of current triangle (m^2)
        double d_depth = g.delta_avalanche_snowdepth / area; // m^3 / m^2 = m
        double d_mass  = g.delta_avalanche_swe / area * 1000; // m^3 / m^2 * 1000 kg/m^3 = kg/m^2
        sbal->_adj_snow(d_depth,d_mass);
    }


    if(g.dead == 1)
    {
        sbal->init_snow();
        g.dead = 0;
    }

    double prev_ts_swe = sbal->m_s;
    try
    {
        sbal->do_data_tstep();
    }catch(module_error& e)
    {
        g.dead=1;
        SPDLOG_DEBUG(boost::diagnostic_information(e));
        auto details = "("+std::to_string(face->center().x()) + "," + std::to_string(face->center().y())+","+std::to_string(face->center().z())+") ID = " + std::to_string(face->cell_local_id);
//        BOOST_THROW_EXCEPTION(module_error() << errstr_info ("Snobal died. Triangle center = "+details));
    }

    // something has gone very wrong with, likely, a thin snowcover
    if( std::isnan(sbal->m_s))
    {
        // "melt" what we had
        sbal->layer_count = 0;
        sbal->m_s = 0.;
        sbal->m_s_0 = 0.;
        sbal->m_s_l = 0.;
        sbal->rho = 0.;
        sbal->T_s = -75. + FREEZE;
        sbal->T_s_0 = -75. + FREEZE; //assuming no snow
        sbal->T_s_l = -75. + FREEZE;
        sbal->z_s = 0.;

        g.dead = 1;
        sbal->init_snow(); // try to get back a sane internel state

    // if something went wrong and was trapped by the above if, m_s (current mass) is 0 mm.
    // thus swe_diff will have the previous timestep's mass, which will go directly to runoff and melt
       double swe_diff = prev_ts_swe - sbal->m_s;
       swe_diff = swe_diff > 0. ? swe_diff : 0;
       g.sum_runoff += swe_diff;
       g.sum_melt += swe_diff;

    }
    else
    {
   // Normal time step. Simply increment cumulated snow melt, runoff, sublimation and precipitation.
       g.sum_runoff += sbal->ro_predict;
       g.sum_melt += sbal->melt;
       g.sum_subl = sbal->E_s_sum;
       g.sum_pcp_sno +=  sbal->m_pp;
    }


    double sd_ver = sbal->z_s/std::max(0.001,cos(face->slope()));

    (*face)["dead"_s]=g.dead;

    (*face)["swe"_s]=sbal->m_s;

    (*face)["R_n"_s]=sbal->R_n;
    (*face)["H"_s]=sbal->H;
    (*face)["E"_s]=sbal->L_v_E;
    (*face)["G"_s]=sbal->G;
    (*face)["M"_s]=sbal->M;
    (*face)["dQ"_s]=sbal->delta_Q;
    (*face)["cc"_s]=sbal->cc_s;
    (*face)["T_s"_s]=sbal->T_s;
    (*face)["T_s_0"_s]=sbal->T_s_0;
    (*face)["T_s_l"_s]=sbal->T_s_l;
    (*face)["iswr_net"_s]=sbal->S_n;
    (*face)["isothermal"_s]=sbal->isothermal;
    (*face)["ilwr_out"_s]= sbal->R_n - sbal->S_n - sbal->I_lw;
    (*face)["snowmelt_int"_s]=sbal->ro_predict;

//    (*face)["snowmelt_int"_s]=swe_diff;
    (*face)["sum_melt"_s]=g.sum_melt;
    (*face)["sum_snowpack_runoff"_s]=g.sum_runoff;
    (*face)["sum_snowpack_subl"_s]=g.sum_subl;
    (*face)["sum_snowpack_pcp"_s]=g.sum_pcp_sno;

    (*face)["snowdepthavg"_s]=sbal->z_s;
    (*face)["snowdepthavg_vert"_s]=sd_ver;

    sbal->input_rec1.S_n =sbal->input_rec2.S_n;
    sbal->input_rec1.I_lw =sbal->input_rec2.I_lw;
    sbal->input_rec1.T_a =sbal->input_rec2.T_a;
    sbal->input_rec1.e_a =sbal->input_rec2.e_a;
    sbal->input_rec1.u =sbal->input_rec2.u;
    sbal->input_rec1.T_g =sbal->input_rec2.T_g;
    sbal->input_rec1.ro =sbal->input_rec2.ro;

    // reset flag
//    g.dead = 0;
}

void snobal::checkpoint(mesh& domain,  netcdf& chkpt)
{

    chkpt.create_variable1D("snobal:m_s",domain->size_local_faces());
    chkpt.create_variable1D("snobal:rho",domain->size_local_faces());
    chkpt.create_variable1D("snobal:T_s",domain->size_local_faces());
    chkpt.create_variable1D("snobal:T_s_0",domain->size_local_faces());
    chkpt.create_variable1D("snobal:T_s_l",domain->size_local_faces());
    chkpt.create_variable1D("snobal:z_s",domain->size_local_faces());
    chkpt.create_variable1D("snobal:h2o_sat",domain->size_local_faces());
    chkpt.create_variable1D("snobal:max_h2o_vol",domain->size_local_faces());
    chkpt.create_variable1D("snobal:sum_runoff",domain->size_local_faces());
    chkpt.create_variable1D("snobal:sum_melt",domain->size_local_faces());
    chkpt.create_variable1D("snobal:sum_pcp_sno",domain->size_local_faces());
    chkpt.create_variable1D("snobal:sum_subl",domain->size_local_faces());
    chkpt.create_variable1D("snobal:E_s_sum",domain->size_local_faces());
    chkpt.create_variable1D("snobal:melt_sum",domain->size_local_faces());
    chkpt.create_variable1D("snobal:ro_pred_sum",domain->size_local_faces());
    chkpt.create_variable1D("snobal:h2o_total",domain->size_local_faces());

//netcdf puts are not threadsafe.
    for (size_t i = 0; i < domain->size_local_faces(); i++)
    {
        auto face = domain->face(i);
        auto& g = face->get_module_data<snodata>(ID);
        auto *sbal = &(g.data);

        chkpt.put_var1D("snobal:m_s",i,sbal->m_s);
        chkpt.put_var1D("snobal:rho",i,sbal->rho);
        chkpt.put_var1D("snobal:T_s",i,sbal->T_s);
        chkpt.put_var1D("snobal:T_s_0",i,sbal->T_s_0);
        chkpt.put_var1D("snobal:T_s_l",i,sbal->T_s_l);
        chkpt.put_var1D("snobal:z_s",i, sbal->z_s);
        chkpt.put_var1D("snobal:h2o_sat",i,sbal->h2o_sat);
        chkpt.put_var1D("snobal:max_h2o_vol",i,sbal->max_h2o_vol);

        chkpt.put_var1D("snobal:sum_runoff",i,g.sum_runoff);
        chkpt.put_var1D("snobal:sum_melt",i,g.sum_melt);
        chkpt.put_var1D("snobal:sum_subl",i,g.sum_subl);
        chkpt.put_var1D("snobal:sum_pcp_sno",i,g.sum_pcp_sno);
        chkpt.put_var1D("snobal:E_s_sum",i,sbal->E_s_sum);
        chkpt.put_var1D("snobal:melt_sum",i,sbal->melt_sum);
        chkpt.put_var1D("snobal:ro_pred_sum",i,sbal->ro_pred_sum);
        chkpt.put_var1D("snobal:h2o_total",i,sbal->h2o_total);
    }

}

void snobal::load_checkpoint(mesh& domain, netcdf& chkpt)
{
    for (size_t i = 0; i < domain->size_local_faces(); i++)
    {
        auto face = domain->face(i);
        auto& g = face->get_module_data<snodata>(ID);
        auto *sbal = &(g.data);

        sbal->m_s = chkpt.get_var1D("snobal:m_s",i);
        sbal->rho = chkpt.get_var1D("snobal:rho",i);
        sbal->T_s = chkpt.get_var1D("snobal:T_s",i);
        sbal->T_s_0 = chkpt.get_var1D("snobal:T_s_0",i);
        sbal->T_s_l = chkpt.get_var1D("snobal:T_s_l",i);
        sbal->z_s =  chkpt.get_var1D("snobal:z_s",i);
        sbal->h2o_sat =  chkpt.get_var1D("snobal:h2o_sat",i);
        sbal->max_h2o_vol = chkpt.get_var1D("snobal:max_h2o_vol",i);

        g.sum_runoff = chkpt.get_var1D("snobal:sum_runoff",i);
        g.sum_melt = chkpt.get_var1D("snobal:sum_melt",i);
        g.sum_subl = chkpt.get_var1D("snobal:sum_subl",i);
        g.sum_pcp_sno = chkpt.get_var1D("snobal:sum_pcp_sno",i);
        sbal->E_s_sum = chkpt.get_var1D("snobal:E_s_sum",i);
        sbal->melt_sum = chkpt.get_var1D("snobal:melt_sum",i);
        sbal->ro_pred_sum = chkpt.get_var1D("snobal:ro_pred_sum",i);
        sbal->h2o_total = chkpt.get_var1D("snobal:h2o_total",i);

        sbal->init_snow();

        (*face)["dead"_s]=g.dead;

        (*face)["swe"_s]=sbal->m_s;

        (*face)["R_n"_s]=sbal->R_n;
        (*face)["H"_s]=sbal->H;
        (*face)["E"_s]=sbal->L_v_E;
        (*face)["G"_s]=sbal->G;
        (*face)["M"_s]=sbal->M;
        (*face)["dQ"_s]=sbal->delta_Q;
        (*face)["cc"_s]=sbal->cc_s;
        (*face)["T_s"_s]=sbal->T_s;
        (*face)["T_s_0"_s]=sbal->T_s_0;
        (*face)["T_s_l"_s]=sbal->T_s_l;
        (*face)["iswr_net"_s]=sbal->S_n;
        (*face)["isothermal"_s]=sbal->isothermal;
        (*face)["ilwr_out"_s]= sbal->R_n - sbal->S_n - sbal->I_lw;
        (*face)["snowmelt_int"_s]=sbal->ro_predict;

        (*face)["sum_melt"_s]=g.sum_melt;
        (*face)["sum_snowpack_runoff"_s]=g.sum_runoff;
        (*face)["sum_snowpack_subl"_s]=g.sum_subl;
        (*face)["sum_snowpack_pcp"_s]=g.sum_pcp_sno;

        (*face)["snowdepthavg"_s]=sbal->z_s;

        double sd_ver = sbal->z_s/std::max(0.001,cos(face->slope()));
        (*face)["snowdepthavg_vert"_s]=sd_ver;

    }
}