Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formulation configuration refactoring #745

Merged
merged 10 commits into from
Mar 1, 2024
Merged
8 changes: 8 additions & 0 deletions include/realizations/catchment/Formulation_Constructors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ namespace realization {
#endif // ACTIVATE_PYTHON
};

static std::string valid_formulation_keys(){
std::string keys = "";
for(const auto& kv : formulations){
keys.append(kv.first+" ");
}
return keys;
}

static bool formulation_exists(std::string formulation_type) {
return formulations.count(formulation_type) > 0;
}
Expand Down
174 changes: 41 additions & 133 deletions include/realizations/catchment/Formulation_Manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "LayerData.hpp"
#include "realizations/config/time.hpp"
#include "realizations/config/routing.hpp"
#include "realizations/config/config.hpp"

namespace realization {

Expand Down Expand Up @@ -58,31 +59,9 @@ namespace realization {
auto possible_global_config = tree.get_child_optional("global");

if (possible_global_config) {
this->global_formulation_tree = *possible_global_config;

//get forcing info
for (auto &forcing_parameter : (*possible_global_config).get_child("forcing")) {
this->global_forcing.emplace(
forcing_parameter.first,
geojson::JSONProperty(forcing_parameter.first, forcing_parameter.second)
);
}

//get first empty key under formulations (corresponds to first json array element)
auto formulation = (*possible_global_config).get_child("formulations..");

for (std::pair<std::string, boost::property_tree::ptree> global_setting : formulation.get_child("params")) {
this->global_formulation_parameters.emplace(
global_setting.first,
geojson::JSONProperty(global_setting.first, global_setting.second)
);
}
global_config = realization::config::Config(*possible_global_config);
}

/**
* Read simulation time from configuration file
* /// \todo TODO: Separate input_interval from output_interval
*/
auto possible_simulation_time = tree.get_child_optional("time");

if (!possible_simulation_time) {
Expand Down Expand Up @@ -186,46 +165,24 @@ namespace realization {
#endif
continue;
}
realization::config::Config catchment_formulation(catchment_config.second);

decltype(auto) formulations = catchment_config.second.get_child_optional("formulations");
if( !formulations ) {
if(!catchment_formulation.has_formulation()){
throw std::runtime_error("ERROR: No formulations defined for "+catchment_config.first+".");
}

// Parse catchment-specific model_params
auto catchment_feature = fabric->get_feature(catchment_index);

for (auto &formulation: *formulations) {
// Handle single-bmi
decltype(auto) model_params = formulation.second.get_child_optional("params.model_params");
if (model_params) {
parse_external_model_params(*model_params, catchment_feature);
}

// Handle multi-bmi
// FIXME: this will not handle doubly nested multi-BMI configs,
// might need a recursive helper here?
decltype(auto) nested_modules = formulation.second.get_child_optional("params.modules");
if (nested_modules) {
for (decltype(auto) nested_formulation : *nested_modules) {
decltype(auto) nested_model_params = nested_formulation.second.get_child_optional("params.model_params");
if (nested_model_params) {
parse_external_model_params(*nested_model_params, catchment_feature);
}
}
}

this->add_formulation(
this->construct_formulation_from_tree(
simulation_time_config,
catchment_config.first,
catchment_config.second,
formulation.second,
output_stream
)
);
break; //only construct one for now FIXME
} //end for formulaitons
catchment_formulation.formulation.link_external(catchment_feature);
this->add_formulation(
this->construct_formulation_from_config(
simulation_time_config,
catchment_config.first,
catchment_formulation,
output_stream
)
);
// break; //only construct one for now FIXME
// } //end for formulaitons
}//end for catchments


Expand Down Expand Up @@ -328,35 +285,24 @@ namespace realization {


protected:
std::shared_ptr<Catchment_Formulation> construct_formulation_from_tree(
std::shared_ptr<Catchment_Formulation> construct_formulation_from_config(
simulation_time_params &simulation_time_config,
std::string identifier,
boost::property_tree::ptree &tree,
const boost::property_tree::ptree &formulation,
const realization::config::Config& catchment_formulation,
utils::StreamHandler output_stream
) {
auto params = formulation.get_child("params");
std::string formulation_type_key;
try {
formulation_type_key = get_formulation_key(formulation);
}
catch(std::exception& e) {
throw std::runtime_error("Catchment " + identifier + " failed initialization: " + e.what());
if(!formulation_exists(catchment_formulation.formulation.type)){
throw std::runtime_error("Catchment " + identifier + " failed initialization: " +
catchment_formulation.formulation.type + "is not a valid formulation. Options are: "+valid_formulation_keys());
}

boost::property_tree::ptree formulation_config = formulation.get_child("params");

auto possible_forcing = tree.get_child_optional("forcing");

if (!possible_forcing) {
if(catchment_formulation.forcing.parameters.empty()){
throw std::runtime_error("No forcing definition was found for " + identifier);
}

geojson::JSONProperty forcing_parameters("forcing", *possible_forcing);

std::vector<std::string> missing_parameters;

if (!forcing_parameters.has_key("path")) {
if (!catchment_formulation.forcing.has_key("path")) {
missing_parameters.push_back("path");
}

Expand All @@ -374,71 +320,37 @@ namespace realization {
throw std::runtime_error(message);
}

geojson::PropertyMap local_forcing;
for (auto &forcing_parameter : *possible_forcing) {
local_forcing.emplace(
forcing_parameter.first,
geojson::JSONProperty(forcing_parameter.first, forcing_parameter.second)
);
}

forcing_params forcing_config = this->get_forcing_params(local_forcing, identifier, simulation_time_config);

std::shared_ptr<Catchment_Formulation> constructed_formulation = construct_formulation(formulation_type_key, identifier, forcing_config, output_stream);
forcing_params forcing_config = this->get_forcing_params(catchment_formulation.forcing.parameters, identifier, simulation_time_config);
std::shared_ptr<Catchment_Formulation> constructed_formulation = construct_formulation(catchment_formulation.formulation.type, identifier, forcing_config, output_stream);
//, geometry);
constructed_formulation->create_formulation(formulation_config, &global_formulation_parameters);

constructed_formulation->create_formulation(catchment_formulation.formulation.parameters);
return constructed_formulation;
}

std::shared_ptr<Catchment_Formulation> construct_missing_formulation(geojson::Feature& feature, utils::StreamHandler output_stream, simulation_time_params &simulation_time_config){
const std::string identifier = feature->get_id();

std::string formulation_type_key = get_formulation_key(global_formulation_tree.get_child("formulations.."));

forcing_params forcing_config = this->get_forcing_params(this->global_forcing, identifier, simulation_time_config);

std::shared_ptr<Catchment_Formulation> missing_formulation = construct_formulation(formulation_type_key, identifier, forcing_config, output_stream);

forcing_params forcing_config = this->get_forcing_params(global_config.forcing.parameters, identifier, simulation_time_config);
std::shared_ptr<Catchment_Formulation> missing_formulation = construct_formulation(global_config.formulation.type, identifier, forcing_config, output_stream);
// Need to work with a copy, since it is altered in-place
geojson::PropertyMap global_properties_copy = global_formulation_parameters;
Catchment_Formulation::config_pattern_substitution(global_properties_copy,
realization::config::Config global_copy = global_config;
Catchment_Formulation::config_pattern_substitution(global_copy.formulation.parameters,
BMI_REALIZATION_CFG_PARAM_REQ__INIT_CONFIG, "{{id}}",
identifier);

// parse any external model parameters in this config

// handle single-bmi
if (global_properties_copy.count("model_params") > 0) {
decltype(auto) model_params = global_properties_copy.at("model_params");
geojson::PropertyMap model_params_copy = model_params.get_values();
parse_external_model_params(model_params_copy, feature);
global_properties_copy.at("model_params") = geojson::JSONProperty("model_params", model_params_copy);
}
//Some helpful debugging prints, commented out, but left for later
//because they will eventually be used by someone, someday, looking at configurations
//being turned into concrecte formulations...
// geojson::JSONProperty::print_property(global_config.formulation.parameters.at("modules"));
global_config.formulation.link_external(feature);
// geojson::JSONProperty::print_property(global_config.formulation.parameters.at("modules"));
missing_formulation->create_formulation(global_config.formulation.parameters);

// handle multi-bmi
// FIXME: this seems inefficient -- is there a better way?
if (global_properties_copy.count("modules") > 0) {
decltype(auto) nested_modules = global_properties_copy.at("modules").as_list();
for (auto& bmi_module : nested_modules) {
geojson::PropertyMap module_def = bmi_module.get_values();
geojson::PropertyMap module_params = module_def.at("params").get_values();
if (module_params.count("model_params") > 0) {
decltype(auto) model_params = module_params.at("model_params");
geojson::PropertyMap model_params_copy = model_params.get_values();
parse_external_model_params(model_params_copy, feature);
module_params.at("model_params") = geojson::JSONProperty("model_params", model_params_copy);
}
module_def.at("params") = geojson::JSONProperty("params", module_params);
bmi_module = geojson::JSONProperty("", module_def);
}
global_properties_copy.at("modules") = geojson::JSONProperty("", nested_modules);
}

missing_formulation->create_formulation(global_properties_copy);
return missing_formulation;
}

forcing_params get_forcing_params(geojson::PropertyMap &forcing_prop_map, std::string identifier, simulation_time_params &simulation_time_config) {
std::string path;
forcing_params get_forcing_params(const geojson::PropertyMap &forcing_prop_map, std::string identifier, simulation_time_params &simulation_time_config) {
std::string path = "";
if(forcing_prop_map.count("path") != 0){
path = forcing_prop_map.at("path").as_string();
}
Expand Down Expand Up @@ -703,11 +615,7 @@ namespace realization {

boost::property_tree::ptree tree;

boost::property_tree::ptree global_formulation_tree;

geojson::PropertyMap global_formulation_parameters;

geojson::PropertyMap global_forcing;
realization::config::Config global_config;

std::map<std::string, std::shared_ptr<Catchment_Formulation>> formulations;

Expand Down
60 changes: 60 additions & 0 deletions include/realizations/config/config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef NGEN_REALIZATION_CONFIG_H
#define NGEN_REALIZATION_CONFIG_H

#include <boost/property_tree/ptree.hpp>

#include "formulation.hpp"
#include "forcing.hpp"

namespace realization{
namespace config{

/**
* @brief Structure representing the configuration for a general Formulation.
*
*/
struct Config{

/**
* @brief Construct a new Config object
*
*/
Config() = default;

/**
* @brief Construct a new Config object from a property tree
*
* @param tree
*/
Config(const boost::property_tree::ptree& tree){

auto possible_forcing = tree.get_child_optional("forcing");

if (possible_forcing) {
forcing = Forcing(*possible_forcing);
}
//get first empty key under formulations (corresponds to first json array element)
auto possible_formulation_tree = tree.get_child_optional("formulations..");
if(possible_formulation_tree){
formulation = Formulation(*possible_formulation_tree);
}
}

/**
* @brief Determine if the config has a formulation
*
* @return true if the formulation name/type is set or if model parameters are present
* @return false if either the type or parameters are empty
*/
bool has_formulation(){
return !(formulation.type.empty() || formulation.parameters.empty());
}

Formulation formulation;
Forcing forcing;
};


};//end namespace config
}//end namespace realization
#endif //NGEN_REALIZATION_CONFIG_H
58 changes: 58 additions & 0 deletions include/realizations/config/forcing.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef NGEN_REALIZATION_CONFIG_FORCING_H
#define NGEN_REALIZATION_CONFIG_FORCING_H

#include <boost/property_tree/ptree.hpp>

#include "JSONProperty.hpp"

namespace realization{
namespace config{

/**
* @brief Structure for holding forcing configuration information
*
*/
struct Forcing{
/**
* @brief key -> Property mapping for forcing parameters
*
*/
geojson::PropertyMap parameters;

/**
* @brief Construct a new, empty Forcing object
*
*/
Forcing():parameters(geojson::PropertyMap()){};

/**
* @brief Construct a new Forcing object from a property_tree
*
* @param tree
*/
Forcing(const boost::property_tree::ptree& tree){
//get forcing info
for (auto &forcing_parameter : tree) {
this->parameters.emplace(
forcing_parameter.first,
geojson::JSONProperty(forcing_parameter.first, forcing_parameter.second)
);
}
}

/**
* @brief Test if a particualr forcing parameter exists
*
* @param key parameter name to test
* @return true if the forcing properties contain key
* @return false if the key is not in the forcing properties
*/
bool has_key(const std::string& key) const{
return parameters.count(key) > 0;
}
};


};//end namespace config
}//end namespace realization
#endif //NGEN_REALIZATION_CONFIG_FORCING_H
Loading
Loading