Program Listing for File snowpack.cpp

Program Listing for File snowpack.cpp#

Return to documentation for file (src/modules/snowpack.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 "snowpack.hpp"
REGISTER_MODULE_CPP(Lehning_snowpack);

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

    depends("snow_albedo");

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

    optional("T_g");

    provides("dQ");
    provides("swe");
    provides("T_s");
    provides("T_s_0");
    provides("n_nodes");
    provides("n_elem");
    provides("snowdepthavg");

//    if(!has_optional("snow_albedo"))
//        provides("snow_albedo");

    provides("H");
    provides("E");
    provides("G");
    provides("ilwr_out");
    provides("iswr_out");
    provides("R_n");
    provides("runoff");
    provides("mass_snowpack_removed");
    provides("sum_runoff");
    provides("sum_subl");
    provides("sublimation");
    provides("evap");

    provides("MS_SWE");
    provides("MS_WATER");
    provides("MS_TOTALMASS");
    provides("MS_SOIL_RUNOFF");

}

Lehning_snowpack::~Lehning_snowpack()
{

}

void Lehning_snowpack::run(mesh_elem &face)
{
    if(is_water(face))
    {
        set_all_nan_on_skip(face);
        return;
    }
    auto& data = face->get_module_data<Lehning_snowpack::data>(ID);

    CurrentMeteo Mdata(data.config);
    Mdata.date   =  mio::Date( global_param->year(), global_param->month(), global_param->day(),global_param->hour(),global_param->min(),-6 );
    // Optional inputs if there is a canopy or not
    if(has_optional("ta_subcanopy")) {
        Mdata.ta     =  (*face)["ta_subcanopy"_s]+mio::Cst::t_water_freezing_pt;
    } else {
        Mdata.ta     =  (*face)["t"_s]+mio::Cst::t_water_freezing_pt;
    }

    if(has_optional("rh_subcanopy")) {
        Mdata.rh     =  (*face)["rh_subcanopy"_s]/100.;
    } else {
        Mdata.rh     =  (*face)["rh"_s]/100.;
    }


    Mdata.vw     =  (*face)["U_2m_above_srf"_s];

    Mdata.vw_max = mio::IOUtils::nodata;// TODO: fix md(MeteoData::VW_MAX);

    Mdata.vw_drift = Mdata.vw ;//mio::IOUtils::nodata;
    Mdata.dw_drift = Mdata.dw;//0;


    if(has_optional("iswr_subcanopy")) {
        Mdata.iswr     =  std::max(0.0,(*face)["iswr_subcanopy"_s]);
    } else {
        Mdata.iswr     =  std::max(0.0,(*face)["iswr"_s]);
    }

    // If  Snowpack, "SW_MODE" : "BOTH"  then rswr and iswr needs to be definined.
    // If an external albedo is not used, a parametrized one is used. but rswr and iswr both must be defined.
    // If "SW_MODE" : "INCOMING", is used, then mAlbedo needs to be undefined to trigger the appropriate rswr and albedo calculations.
    // rswr must still be set, but we use the garbage that is in Xdata instead.
//    if(has_optional("snow_albedo"))
//    {
        //measured albedo in snowpack will be fed from an albedo model
        //in the config 'both' will enable this
        Mdata.mAlbedo   =  (*face)["snow_albedo"_s];
        Mdata.rswr      =  std::max(0.0,(*face)["snow_albedo"_s] * Mdata.iswr);

//    }
//    else
//    {
//        Mdata.rswr = std::max(0.0,data.Xdata->Albedo * Mdata.iswr);
//        Mdata.mAlbedo = Constants::undefined; //this will trigger calculating a paramaterized albedo
//    }


    if(has_optional("ilwr_subcanopy")) {
        Mdata.ilwr     =  (*face)["ilwr_subcanopy"_s];
    } else {
        Mdata.ilwr     =  (*face)["ilwr"_s];
    }

    Mdata.ea =  mio::Atmosphere::blkBody_Emissivity(Mdata.ilwr, Mdata.ta); //follows apline3d
//    Mdata.ea  = SnLaws::AirEmissivity(Mdata.ilwr, Mdata.ta, "default"); //atmospheric emissivity!
    // Note this is used later throughout the code line 673 in Snowpack.cc, dealing with the emissivity of the snowpack! (might be a bug).
    // Left the ea calculation here for now - NIC

    //double thresh_rain = 2 + mio::Cst::t_water_freezing_pt;

    // Define fraction rain and total precipitaiton (soild and liquid)
    if(has_optional("p_subcanopy")) {
        Mdata.psum_ph = (*face)["frac_precip_rain_subcanopy"_s]; //  0 = snow, 1 = rain
        Mdata.psum = (*face)["p_subcanopy"_s];
    } else {
        Mdata.psum_ph = (*face)["frac_precip_rain"_s]; //  0 = snow, 1 = rain
        Mdata.psum = (*face)["p"_s];
    }

    Mdata.rho_hn = 100;

    //setup a single ground temp measurement
    mio::MeteoData soil_meas;
//    soil_meas.addParameter("HTS1");
//    soil_meas.addParameter("TS1");
//    soil_meas("HTS1") = -0.1;
//    soil_meas("TS1") = 269;
//    Mdata.setMeasTempParameters(soil_meas);
//    Mdata.ts.push_back(soil_meas("TS1"));
//    Mdata.zv_ts.push_back(soil_meas("HTS1"));

    Mdata.tss = data.Xdata->Ndata[data.Xdata->getNumberOfElements()].T;  //we use previous timestep value//mio::IOUtils::nodata; //Constants::undefined;;//
    //setting this to tss is inline with Alpine3d if there is no soil node. However, it might make more sense to use a const ground temp?
    if(has_optional("T_g"))
        Mdata.ts0 = (*face)["T_g"_s] + 273.15;
    else
        Mdata.ts0 =  const_T_g + 273.15; //Mdata.tss <- is in line with Alpine3D, but this makes no sense. So use a const temp.

    Mdata.hs = mio::IOUtils::nodata; //follows alpine3d
    Mdata.elev      = (*face)["solar_el"_s]*mio::Cst::to_rad;

    data.cum_precip  += Mdata.psum; //running sum of the precip. snowpack removes the rain component for us.
    data.meteo->compMeteo(Mdata,*(data.Xdata),false); // no canopy model

    double mass_erode = 0;

    if(has_optional("drift_mass"))
    {

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

        if(mass > 0)
        {

            Mdata.psum += mass;
            data.cum_precip  += Mdata.psum;
            Mdata.psum_ph = 0;
        }
        else
        {
            mass_erode = mass; // snowpack expects the mass erode to be negative
        }
    }

    // To collect surface exchange data for output
    SurfaceFluxes surface_fluxes;
//    surface_fluxes.reset(false);
//    surface_fluxes.drift = 0.;
//    surface_fluxes.mass[SurfaceFluxes::MS_WIND] = 0.;


    // Boundary condition (fluxes)
    BoundCond Bdata;

    try
    {
        data.sp->runSnowpackModel(Mdata, *(data.Xdata), data.cum_precip, Bdata,surface_fluxes,mass_erode);
        surface_fluxes.collectSurfaceFluxes(Bdata, *(data.Xdata), Mdata);
    }catch(...)
    {
        if (data.Xdata->swe > 3)
        {

            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);
            CHM_THROW_EXCEPTION(module_error, "Snowpack died. Triangle center = " + details);
        }
    }


    (*face)["sublimation"_s]=surface_fluxes.mass[SurfaceFluxes::MS_SUBLIMATION];

    if(data.Xdata->swe > 0)
    {

        double bulk_T_s=0;
        for(size_t i = 0; i < data.Xdata->getNumberOfElements(); ++i)
        {
            bulk_T_s += data.Xdata->Ndata[i].T;
        }

        bulk_T_s /= data.Xdata->getNumberOfElements();

        (*face)["T_s"_s]=bulk_T_s;
        (*face)["T_s_0"_s]=Mdata.tss;
        (*face)["n_nodes"_s]=data.Xdata->getNumberOfNodes();
        (*face)["n_elem"_s]=data.Xdata->getNumberOfElements();
        (*face)["H"_s]=surface_fluxes.qs;
        (*face)["E"_s]=surface_fluxes.ql;


        (*face)["G"_s]=surface_fluxes.qg0 == -999 ? 0 : surface_fluxes.qg0; //qg0 is the correct ground heatflux to match snowpack output. qg is just uninit

        (*face)["ilwr_out"_s]=-surface_fluxes.lw_out; //these are actually positive!
        (*face)["iswr_out"_s]=-surface_fluxes.sw_out;
        (*face)["R_n"_s]= surface_fluxes.lw_net + (surface_fluxes.sw_in-surface_fluxes.sw_out );
        (*face)["dQ"_s]=surface_fluxes.dIntEnergy;
//        if(!has_optional("snow_albedo"))
//        {
            (*face)["snow_albedo"_s]=data.Xdata->Albedo;  //even if we have a measured albedo, Xdata will reflect this. //surface_fluxes.pAlbedo);
//        }

    } else{
       set_all_nan_on_skip(face);

    }

    //always write out 0 swe regardless of amount of swee
    (*face)["swe"_s]=data.Xdata->swe;
    (*face)["mass_snowpack_removed"_s]=data.Xdata->ErosionMass;
    (*face)["snowdepthavg"_s]=data.Xdata->cH - data.Xdata->Ground; // cH includes soil depth if SNP_SOIL == 1, hence subtracting Ground height
    (*face)["runoff"_s]=surface_fluxes.mass[SurfaceFluxes::MS_SNOWPACK_RUNOFF];
    (*face)["evap"_s]=surface_fluxes.mass[SurfaceFluxes::MS_EVAPORATION];
//    (*face)["sum_runoff"_s]=  (*face["sum_runoff"_s] + surface_fluxes.mass[SurfaceFluxes::MS_SNOWPACK_RUNOFF]);
    data.sum_subl +=surface_fluxes.mass[SurfaceFluxes::MS_SUBLIMATION];
    (*face)["sum_subl"_s]=   data.sum_subl ;


    (*face)["MS_SWE"_s]=surface_fluxes.mass[SurfaceFluxes::MS_SWE];
    (*face)["MS_WATER"_s]=surface_fluxes.mass[SurfaceFluxes::MS_WATER];
    (*face)["MS_TOTALMASS"_s]=surface_fluxes.mass[SurfaceFluxes::MS_TOTALMASS];
    (*face)["MS_SOIL_RUNOFF"_s]=surface_fluxes.mass[SurfaceFluxes::MS_SOIL_RUNOFF];
}

void Lehning_snowpack::init(mesh& domain)
{
    const_T_g = cfg.get("const_T_g",-4.0);

    for(size_t i=0;i<domain->size_local_faces();i++)
    {
        auto face = domain->face(i);

        auto& d = face->make_module_data<Lehning_snowpack::data>(ID);


        //setup critical keys.
        //overwrite the user if a dangerous key is set
        d.config.addKey("METEO_STEP_LENGTH", "Snowpack", std::to_string( 3600.0 / global_param->dt())); // Hz. Number of met per hour
        d.config.addKey("MEAS_TSS", "Snowpack", "false");

        //specified as minutes, snowpack will convert to s for us. CHM dt is in s
        d.config.addKey("CALCULATION_STEP_LENGTH","Snowpack", std::to_string(global_param->dt()  / 60 ) );
        //default values for
        //  "Snowpack": { }

        d.config.addKey("MEAS_TSS","Snowpack","false");
        d.config.addKey("ENFORCE_MEASURED_SNOW_HEIGHTS","Snowpack","false");
        d.config.addKey("SW_MODE","Snowpack","BOTH");
        d.config.addKey("HEIGHT_OF_WIND_VALUE","Snowpack","2");
        d.config.addKey("HEIGHT_OF_METEO_VALUES","Snowpack","2");
        d.config.addKey("ATMOSPHERIC_STABILITY","Snowpack","MO_MICHLMAYR");
        d.config.addKey("ROUGHNESS_LENGTH","Snowpack","0.001");
        d.config.addKey("CHANGE_BC","Snowpack","false");
        d.config.addKey("THRESH_CHANGE_BC","Snowpack","-1.0");
        d.config.addKey("SNP_SOIL","Snowpack","false");
        d.config.addKey("SOIL_FLUX","Snowpack","false");
        d.config.addKey("GEO_HEAT","Snowpack","0.06");
        d.config.addKey("CANOPY","Snowpack","false");

        //default values for
        //  "SnowpackAdvanced": { }
        d.config.addKey("MAX_NUMBER_MEAS_TEMPERATURES","SnowpackAdvanced","1");
        d.config.addKey("ALPINE3D","SnowpackAdvanced","true"); //must be true for any blowing snow module
        d.config.addKey("SNOW_EROSION","SnowpackAdvanced","false");
        d.config.addKey("MEAS_INCOMING_LONGWAVE","SnowpackAdvanced","true");
        d.config.addKey("THRESH_RAIN","SnowpackAdvanced","2");
        d.config.addKey("THRESH_RAIN_RANGE","SnowpackAdvanced","2");
        d.config.addKey("WATERTRANSPORTMODEL_SNOW","SnowpackAdvanced","BUCKET");
        d.config.addKey("VARIANT","SnowpackAdvanced","DEFAULT");
        d.config.addKey("ADJUST_HEIGHT_OF_WIND_VALUE","SnowpackAdvanced","false"); // we always provide a 2m wind, even if there is snowcover
        d.config.addKey("HN_DENSITY","SnowpackAdvanced","MEASURED"); //We can then set it in at run time. Do it this way so we can have temporally variable if we want.

        d.config.addKey("ADVECTIVE_HEAT","SnowpackAdvanced","TRUE"); // This enables heat transport caused by moving water. Error if true/false not specified.

        d.config.addKey("COMBINE_ELEMENTS","SnowpackAdvanced","true"); //Defines whether joining elements will be considered at all
        //Activates algorithm to reduce the number of elements deeper in the snowpack AND to split elements again when they come back to the surface
        //Only works when COMBINE_ELEMENTS == TRUE.
        d.config.addKey("REDUCE_N_ELEMENTS","SnowpackAdvanced","true");


        // because we use our own config, we need to do the conversion
        //format is same key-val pairs that snowpack expects, case sensitive
        for(auto itr : cfg)
        {
            for(auto jtr : itr.second)
            {
                d.config.addKey(jtr.first.data(),itr.first.data(),jtr.second.data());
            }
        }


        d.Spackconfig = std::make_shared<SnowpackConfig>(d.config);

        d.cum_precip=0.;

        //addSpecial keys goes here to deal with Antarctica, canopy, and detect grass

        SN_SNOWSOIL_DATA SSdata;
        SSdata.SoilAlb = cfg.get<double>("sno.SoilAlbedo",0.09);
        SSdata.Albedo = SSdata.SoilAlb; // following snowpacks' no snow default.
        SSdata.BareSoil_z0 = cfg.get<double>("sno.BareSoil_z0",0.2);
        if (SSdata.BareSoil_z0 == 0.)
        {
            SPDLOG_WARN("[snowpack] BareSoil_z0 == 0, set to 0.2");
            SSdata.BareSoil_z0 = 0.2;
        }

        SSdata.WindScalingFactor= cfg.get<double>("sno.WindScalingFactor",1);
        SSdata.TimeCountDeltaHS = cfg.get<double>("sno.TimeCountDeltaHS",0.0);


        SSdata.meta.stationName = cfg.get<std::string>("sno.station_name","chm");
        SSdata.meta.position.setAltitude(face->get_z());

        SSdata.meta.position.setXY(face->get_x(),face->get_y(),face->get_z());
        SSdata.meta.setSlope(mio::IOUtils::nodata,mio::IOUtils::nodata);
//        SSdata.meta.setSlope(face->slope() * ,face->aspect());
//        SSdata.meta.setSlope(0,0);

        SSdata.HS_last = 0.; //cfg.get<double>("sno.HS_Last");

        //meta data in *sno files that we don't use
//        cfg.get<std::string>("sno.station_id");

//        cfg.get<double>("sno.latitude");
//        cfg.get<double>("sno.longitude");
//        cfg.get<double>("sno.altitude");
//        cfg.get<double>("sno.nodata");
//        cfg.get<double>("sno.tz");
//        cfg.get<std::string>("sno.source");
//        cfg.get<std::string>("sno.ProfileDate");



        //assumes no starting layers
        SSdata.nN = 1;
        SSdata.Height = 0.;

        SSdata.nLayers = 0;// cfg.get("sno.nSoilLayerData",0);
//        SSdata.nLayers += cfg.get("sno.nSnowLayerData",0);
//        SSdata.Ldata



        SSdata.Canopy_Height = cfg.get<double>("sno.CanopyHeight",0);
        SSdata.Canopy_LAI = cfg.get<double>("sno.CanopyLeafAreaIndex",0);
        SSdata.Canopy_Direct_Throughfall = cfg.get<double>("sno.CanopyDirectThroughfall",1);

        SSdata.ErosionLevel = cfg.get<double>("sno.ErosionLevel",0);

        d.Xdata = std::make_shared<SnowStation>(false,false);
        d.Xdata->initialize(SSdata,0);
//        d.Xdata->cos_sl = 1;
//        d.Xdata->windward = false;
//        d.Xdata->rho_hn = 0;
//        d.Xdata->hn = 0;
//        d.Xdata->mH = 0;

        d.sp = std::make_shared<Snowpack>(*(d.Spackconfig));
        d.meteo = std::make_shared<Meteo>( (d.config));
        d.stability = std::make_shared<Stability> ( (d.config), false);

        d.sum_subl = 0;


    }
}