Program Listing for File WindNinja.cpp#
↰ Return to documentation for file (src/modules/interp_met/WindNinja.cpp)
#pragma clang diagnostic push
#pragma ide diagnostic ignored "openmp-use-default-none"
//
// 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 "WindNinja.hpp"
REGISTER_MODULE_CPP(WindNinja);
WindNinja::WindNinja(config_file cfg)
: module_base("WindNinja", parallel::domain, cfg)
{
depends_from_met("U_R");
depends_from_met("vw_dir");
provides("U_R");
provides("U_R_orig");
provides("Ninja_speed");
provides("vw_dir");
provides("vw_dir_orig");
provides("vw_dir_divergence");
provides("zonal_u");
provides("zonal_v");
provides("W_transf");
provides("interp_zonal_u");
provides("interp_zonal_v");
provides("lookup_d");
provides_vector("wind_direction_original");
provides_vector("wind_direction");
ninja_average = cfg.get("ninja_average",true);
compute_Sx = cfg.get("compute_Sx",true);
// We are going to use the winstral parameterization of Sx to modify out windfield. So we cannot do it later
// it needs to be coupled with the windspeed parameterization. Manually instantiate our own copy of this module
// and we can then access Sx as if it were any other object. The config for this will need to come from WN's config section
if(compute_Sx)
{
optional("snowdepthavg");
provides("Sx");
conflicts("Winstral_parameters"); // we cannot have Winstral_parameters alongside this if we generate the Sx.
}
SPDLOG_DEBUG("Successfully instantiated module {}",this->ID);
}
//Calculates the curvature required
void WindNinja::init(mesh& domain)
{
#pragma omp parallel for
for (size_t i = 0; i < domain->size_local_faces(); i++)
{
auto face = domain->face(i);
auto& d = face->make_module_data<data>(ID);
d.interp.init(global_param->interp_algorithm,face->stations().size() );
d.interp_smoothing.init(interp_alg::tpspline,3,{ {"reuse_LU","true"}});
}
N_windfield = 0;
for(auto& itr: domain->parameters() )
{
if( itr.find("Ninja") != std::string::npos &&
itr.find("_U") != std::string::npos) // ignore all _U, _V, and _LAvg params
{
++N_windfield;
}
}
SPDLOG_DEBUG("Found {} windfields", N_windfield);
try
{
// see if we have a manually specified number, error out as this is deprecated
// favour error as we dont want the user to think something is working when it's not
cfg.get<int>("N_windfield");
CHM_THROW_EXCEPTION(module_error, "N_windfield is deprecated and no longer used");
}
catch(pt::ptree_bad_path& e)
{
// not user specified, is ok
}
if (N_windfield == 0)
{
CHM_THROW_EXCEPTION(module_error,"Could not find any required wind ninja maps");
}
// ensure a user-specified L_avg value makes sense
L_avg = -1;
try
{
L_avg = cfg.get<int>("L_avg");
int n_lavg = 0; // number of params that have a specific l_avg
auto s_lavg = std::to_string(L_avg);
for(auto& itr: domain->parameters() )
{
if( itr.find("Ninja") != std::string::npos &&
itr.find("_"+s_lavg) != std::string::npos
)
{
++n_lavg;
}
}
if(n_lavg != N_windfield)
{
CHM_THROW_EXCEPTION(module_error,"The user specified value of L_avg=" +
s_lavg + " does not match the number of wind fields.");
}
}
catch(...)
{
// not user specified, is ok
}
// We need to handle the following case:
// if L_avg is not defined in the cfg file BUT the mesh contains only WN parameters with an explicit values for L_avg.
if(L_avg == -1)
{
std::set<int> param_found_L_avg={};
for(auto& itr: domain->parameters() )
{
if( itr.find("Ninja") != std::string::npos &&
itr.find("_") != std::string::npos &&
itr.find("_U") == std::string::npos && // AND these are the U and V fields as the Lavg isn't on U or V
itr.find("_V") == std::string::npos
)
{
// see if we can auto detect an Lavg value
auto pos = itr.find("_") + 1;
auto param_L_avg = -1;
try
{
param_L_avg = std::stoi(itr.substr(pos));
param_found_L_avg.insert(param_L_avg);
}
catch(std::invalid_argument& e)
{
CHM_THROW_EXCEPTION(module_error,"Found a WindNinja L_avg parameter that has a malformed name. Param = " + itr + ". L_avg = " + itr.substr(pos) );
}
}
}
if(param_found_L_avg.size() == 1) // we found only 1 possible Lavg set of values, so we can use it.
{
L_avg = *(param_found_L_avg.begin());
SPDLOG_DEBUG("Using L_avg from parameters. L_avg = {}",L_avg);
}
else if(param_found_L_avg.size() > 1 )
{
std::string found = "[ ";
for(auto& f : param_found_L_avg)
{
found += std::to_string(f);
found += " ";
}
found += " ]";
// Ok we have a problem. There are multiple NinjaX_LAVG params in the file
CHM_THROW_EXCEPTION(module_error,"WindNinja: Multiple parameters with possible L_avg values found, but L_avg was not specified. "
"Please set L_avg in the config file. Found the following L_avg values: " + found );
}
else
{
L_avg = -1;
SPDLOG_DEBUG("No L_avg parameter found, assuming tile averaging");
}
}
// Ensure that L_avg, either given or found, is valid
int valid_Lavg_params=0;
for(int theta = 0; theta<=2*M_PI;theta++)
{
auto delta_angle = 360. / N_windfield;
auto d = int(theta * 180.0 / M_PI / delta_angle);
if (d == 0) d = N_windfield;
auto face = domain->face(0);
std::string param="";
if(L_avg == -1)
{
param = "Ninja" + std::to_string(d);
}else
{
param = "Ninja" + std::to_string(d) +'_' + std::to_string(L_avg); // transfert function
}
if( !face->has_parameter(param))
{
CHM_THROW_EXCEPTION(module_error,"WindNinja: Missing parameter: " + param);
}
param = "Ninja" + std::to_string(d) + "_U";
if( !face->has_parameter(param))
{
CHM_THROW_EXCEPTION(module_error,"WindNinja: Missing parameter: " + param);
}
param = "Ninja" + std::to_string(d) + "_V";
if( !face->has_parameter(param))
{
CHM_THROW_EXCEPTION(module_error,"WindNinja: Missing parameter: " + param);
}
}
H_forc = cfg.get("H_forc",40.0);
Max_spdup = cfg.get("Max_spdup",3.);
Min_spdup = cfg.get("Min_spdup",0.1);
scale_factor = cfg.get("scale_factor",1.0);
ninja_recirc = cfg.get("ninja_recirc",false);
Sx_crit = cfg.get("Sx_crit", 30.);
if(compute_Sx)
{
config_file tmp;
tmp.put("angular_window",30.);
tmp.put("size_of_step",10.);
if(has_optional("snowdepthavg"))
{
tmp.put("incl_snw",false);
}
Sx = std::dynamic_pointer_cast<Winstral_parameters>(module_factory::create("Winstral_parameters",tmp));
}
}
void WindNinja::run(mesh& domain)
{
double transf_max = -9999.0;
double delta_angle = 360. / N_windfield;
#pragma omp parallel for
for (size_t i = 0; i < domain->size_local_faces(); i++)
{
auto face = domain->face(i);
std::vector<boost::tuple<double, double, double> > u;
std::vector<boost::tuple<double, double, double> > v;
for (auto &s : face->stations())
{
if (is_nan((*s)["U_R"_s]) || is_nan((*s)["vw_dir"_s]))
continue;
if ((*s)["vw_dir"_s] >= 360.0)
{
// enforce the criteria that direction must be on interval [0,360)
(*s)["vw_dir"_s] = (*s)["vw_dir"_s] - 360.0;
}
double theta = (*s)["vw_dir"_s] * M_PI / 180.;
double W = (*s)["U_R"_s];
W = std::max(W, 0.1);
W = Atmosphere::log_scale_wind(W,
Atmosphere::Z_U_R, // UR is at our reference height
H_forc, // Reference height for GEM forcing and WindNinja wind field library
0); // no canopy, no snow, but uses a snow roughness
double zonal_u = -W * sin(theta);
double zonal_v = -W * cos(theta);
u.push_back(boost::make_tuple(s->x(), s->y(), zonal_u));
v.push_back(boost::make_tuple(s->x(), s->y(), zonal_v));
}
// if(compute_Sx)
// (*face)["Sx"_s]= Sx->Sx(domain,face);
//http://mst.nerc.ac.uk/wind_vect_convs.html
// get an interpolated zonal U,V at our face
auto query = boost::make_tuple(face->get_x(), face->get_y(), face->get_z());
double zonal_u = face->get_module_data<data>(ID).interp(u, query);
double zonal_v = face->get_module_data<data>(ID).interp(v, query);
(*face)["interp_zonal_u"_s]= zonal_u;
(*face)["interp_zonal_v"_s]= zonal_v;
//Get back the interpolated wind direction
// -- not sure if there is a better way to do this, but at least a first order to getting the right direction
double theta = math::gis::zonal2dir(zonal_u, zonal_v);
double theta_orig = theta;
// Save original direction
Vector_2 v_orig = math::gis::bearing_to_cartesian(theta_orig* 180.0 / M_PI);
Vector_3 v3_orig(-v_orig.x(),-v_orig.y(), 0); //negate as direction it's blowing instead of where it is from!!
face->set_face_vector("wind_direction_original",v3_orig);
(*face)["vw_dir_orig"_s]= theta_orig * 180.0 / M_PI;
double U = 0.;
double V = 0.;
double W_transf = 0.;
if(!ninja_average) // No Linear interpolation between the closest 2 wind fields from the library
{
// Use this wind dir to figure out which wind field from the library we need
// Wind field are available each delta_angle deg.
int d = int(theta * 180.0 / M_PI / delta_angle);
if (d == 0) d = N_windfield;
(*face)["lookup_d"_s]= d;
// get the transfert function and associated wind component for the interpolated wind direction
if(L_avg == -1)
{
W_transf = face->parameter("Ninja" + std::to_string(d)); // transfert function
}else
{
W_transf = face->parameter("Ninja" + std::to_string(d) +'_' + std::to_string(L_avg)); // transfert function
}
U = face->parameter("Ninja" + std::to_string(d) + "_U"); // zonal component
V = face->parameter("Ninja" + std::to_string(d) + "_V"); // meridional component
}else // Linear interpolation between the closest 2 wind fields from the library
{
// Use this wind dir to figure out which wind fields from the library we need
// Wind fields are available each 15 deg.
int d1 = int(theta * 180.0 / M_PI / delta_angle);
double theta1 = d1 * delta_angle * M_PI / 180.0;
if (d1 == 0) d1 = N_windfield;
int d2 = int((theta * 180.0 / M_PI + delta_angle) / delta_angle);
double theta2 = d2 * delta_angle *M_PI / 180.0;
if (d2 == 0) d2 = N_windfield;
double d = d1*(theta2-theta)/(theta2-theta1)+d2*(theta-theta1)/(theta2-theta1);
(*face)["lookup_d"_s]= d;
double W_transf1 = 0.;
// get the transfert function and associated wind component for the interpolated wind direction
if(L_avg == -1)
{
W_transf1 = face->parameter("Ninja" + std::to_string(d1)); // transfert function
}else
{
W_transf1 = face->parameter("Ninja" + std::to_string(d1)+'_' + std::to_string(L_avg)); // transfert function
}
double U_lib1 = face->parameter("Ninja" + std::to_string(d1) + "_U"); // zonal component
double V_lib1 = face->parameter("Ninja" + std::to_string(d1) + "_V"); // meridional component
double W_transf2 = 0.;
if(L_avg == -1)
{
W_transf2 = face->parameter("Ninja" + std::to_string(d2)); // transfert function
}else
{
W_transf2 = face->parameter("Ninja" + std::to_string(d2)+'_' + std::to_string(L_avg)); // transfert function
}
double U_lib2 = face->parameter("Ninja" + std::to_string(d2) + "_U"); // zonal component
double V_lib2 = face->parameter("Ninja" + std::to_string(d2) + "_V"); // meridional component
// Determine wind component from the wind field library using a weighted mean
U = U_lib1*(theta2-theta)/(theta2-theta1)+U_lib2*(theta-theta1)/(theta2-theta1);
V = V_lib1*(theta2-theta)/(theta2-theta1)+V_lib2*(theta-theta1)/(theta2-theta1);
W_transf = W_transf1*(theta2-theta)/(theta2-theta1)+W_transf2*(theta-theta1)/(theta2-theta1);
}
if(fabs(W_transf)> transf_max )
transf_max = fabs(W_transf);
// NEW wind direction from the wind field library
theta = math::gis::zonal2dir(U, V);
double W = sqrt(zonal_u * zonal_u + zonal_v * zonal_v);
face->get_module_data<data>(ID).corrected_theta = theta;
face->get_module_data<data>(ID).W = W;
face->get_module_data<data>(ID).W_transf = W_transf;
}
#pragma omp parallel for
for (size_t i = 0; i < domain->size_local_faces(); i++)
{
auto face = domain->face(i);
double theta= face->get_module_data<data>(ID).corrected_theta;
double W= face->get_module_data<data>(ID).W;
double W_transf= face->get_module_data<data>(ID).W_transf;
(*face)["U_R_orig"_s]= W; // Wind speed without downscaling
(*face)["vw_dir"_s]= theta * 180.0 / M_PI;
// Limit speed up value to Max_spdup
// Can be used to avoid unrelistic values at crest top
if(W_transf>1. and transf_max>Max_spdup)
W_transf = 1.+(Max_spdup-1.)*(W_transf-1.)/(transf_max-1.);
if(compute_Sx)
{
if (ninja_recirc)
{ // Need further test
double sx_loc = Sx->Sx(domain,face);
(*face)["Sx"_s] =sx_loc;
if( sx_loc>Sx_crit ) //Reduce wind speed on the lee side of mountain crest identified by Sx>Sx_crit
W_transf = 0.25;
}
}
(*face)["W_transf"_s]= W_transf;
// NEW wind intensity from the wind field library
W = W * W_transf;
W = std::max(W, 0.1);
(*face)["Ninja_speed"_s]= W; // Wind speed with downscaling
// Update U and V wind components
double U = -W * sin(theta);
double V = -W * cos(theta);
//go back from H_forc to reference
W = Atmosphere::log_scale_wind(W,
H_forc, // Reference height for GEM forcing and WindNinja wind field library
Atmosphere::Z_U_R, // UR is at our reference height
0); // no canopy, no snow, but uses a snow roughness
// scale_factor can be used to correct any low bias. Heavy handed approach
(*face)["U_R"_s]= W * scale_factor;
(*face)["zonal_u"_s]= U; // these are still H_forc
(*face)["zonal_v"_s]= V;
Vector_2 v_corr = math::gis::bearing_to_cartesian(theta * 180.0 / M_PI);
Vector_3 v3(-v_corr.x(), -v_corr.y(), 0); //negate as direction it's blowing instead of where it is from!!
face->set_face_vector("wind_direction", v3);
double dtheta = (theta * 180.0 / M_PI) - (*face)["vw_dir_orig"_s];
(*face)["vw_dir_divergence"_s] = fabs(dtheta);
}
// Communicate U_R for neighbour access
// domain->ghost_neighbors_communicate_variable("U_R"_s);
//
// #pragma omp parallel for
// for (size_t i = 0; i < domain->size_local_faces(); i++)
// {
//
// auto face = domain->face(i);
// std::vector<boost::tuple<double, double, double> > u;
// for (size_t j = 0; j < 3; j++)
// {
// auto neigh = face->neighbor(j);
// if (neigh != nullptr)
// u.push_back(boost::make_tuple(neigh->get_x(), neigh->get_y(),(*neigh)["U_R"_s]));
// }
//
// auto query = boost::make_tuple(face->get_x(), face->get_y(), face->get_z());
// if(u.size() > 0)
// {
// double new_u = face->get_module_data<data>(ID).interp_smoothing(u, query);
// face->get_module_data<data>(ID).temp_u = new_u;
// }
// else
// {
// face->get_module_data<data>(ID).temp_u = (*face)["U_R"_s];
// }
//
// }
// #pragma omp parallel for
// for (size_t i = 0; i < domain->size_local_faces(); i++)
// {
// auto face = domain->face(i);
// (*face)["U_R"_s]= std::max(0.1, face->get_module_data<data>(ID).temp_u);
// }
}
WindNinja::~WindNinja()
{
}
#pragma clang diagnostic pop