Skip to content

Commit 4eeb550

Browse files
authored
Add EWTS message to Bmi_Module_Formulation's get_response on BMI error thrown (NGWPC-7474) (#13)
Adds an EWTS log when a formulation model throws an error during the Update[Until] call. The message includes information on which formulation and catchment the error occurred on and information injected through set_model_inputs_prior_to_update. Logging this information will help debugging errors in data in the future.
1 parent e64398c commit 4eeb550

2 files changed

Lines changed: 192 additions & 4 deletions

File tree

include/realizations/catchment/Bmi_Module_Formulation.hpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,48 @@ namespace realization {
387387
*/
388388
void set_model_inputs_prior_to_update(const double &model_init_time, time_step_t t_delta);
389389

390+
/**
391+
* Append `set_model_inputs_prior_to_update` values to an error message. This is intended to be used if the BMI fails to run `update()`.
392+
*
393+
* @param model_initial_time The model's time prior to the update, in its internal units and representation.
394+
* @param t_delta The size of the time step over which the formulation is going to update the model, which might
395+
* be different than the model's internal time step.
396+
* @param inputs Stream the inputs message will be appended to.
397+
*/
398+
void append_model_inputs_to_stream(const double &model_init_time, time_step_t t_delta, std::stringstream &inputs);
399+
400+
/**
401+
* Convert a pointer to an array of data to its correct type, then append these items to a stream.
402+
* The format will appear like a python list, e.g., [0.5, 1.2, 5.8]
403+
*
404+
* @param values Raw pointer to data that will be interpreted based on the `type`
405+
* @param num_items The number of items expected in the array.
406+
* @param inputs Stream the values will be appended to.
407+
*/
408+
template<typename T>
409+
void append_inputs(std::shared_ptr<void> values, int num_items, std::stringstream &inputs);
410+
411+
/**
412+
* Convert a pointer to an array of data to its correct type, then append these items to a stream.
413+
* The format will appear like a python list, e.g., [0.5, 1.2, 5.8]
414+
*
415+
* @param type String representing the type of data the pointer is for.
416+
* @param values Raw pointer to data that will be interpreted based on the `type`
417+
* @param num_items The number of items expected in the array.
418+
* @param inputs Stream the values will be appended to.
419+
*/
420+
void append_inputs(std::string type, std::shared_ptr<void> values, int num_items, std::stringstream &inputs);
421+
422+
/**
423+
* Convert data to a target data type and append it to an stream.
424+
*
425+
* @param type String representing the type of data the pointer is for.
426+
* @param value Raw data that will be cast to a value based on the `type`.
427+
* @param inputs Stream the interpreted ata will be appeneded to.
428+
*/
429+
template<typename T>
430+
void append_input(std::string type, T value, std::stringstream &inputs);
431+
390432
/** The delta of the last model update execution (typically, this is time step size). */
391433
time_step_t last_model_response_delta = 0;
392434
/** The epoch time of the model at the beginning of its last update. */

src/realizations/catchment/Bmi_Module_Formulation.cpp

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,29 @@ namespace realization {
6161
}
6262
}
6363

64+
int update_method;
6465
while (next_time_step_index <= t_index) {
6566
double model_initial_time = get_bmi_model()->GetCurrentTime();
6667
set_model_inputs_prior_to_update(model_initial_time, t_delta);
67-
if (t_delta_model_units == get_bmi_model()->GetTimeStep())
68-
get_bmi_model()->Update();
69-
else
70-
get_bmi_model()->UpdateUntil(model_initial_time + t_delta_model_units);
68+
try {
69+
if (t_delta_model_units == get_bmi_model()->GetTimeStep()) {
70+
update_method = 0;
71+
get_bmi_model()->Update();
72+
}
73+
else {
74+
update_method = 1;
75+
get_bmi_model()->UpdateUntil(model_initial_time + t_delta_model_units);
76+
}
77+
} catch (const std::exception &e) {
78+
std::stringstream error_message;
79+
error_message << "Model " << (update_method == 0 ? "Update" : "UpdateUntil")
80+
<< " failed on catchment " << this->get_catchment_id()
81+
<< ". t_index=" << t_index
82+
<< ", next_step_index=" << next_time_step_index << "\n";
83+
append_model_inputs_to_stream(model_initial_time, t_delta, error_message);
84+
Logger::Log(LogLevel::FATAL, error_message.str());
85+
throw;
86+
}
7187
// TODO: again, consider whether we should store any historic response, ts_delta, or other var values
7288
next_time_step_index++;
7389
}
@@ -717,6 +733,136 @@ namespace realization {
717733
}
718734
}
719735

736+
737+
void Bmi_Module_Formulation::append_model_inputs_to_stream(const double &model_init_time, time_step_t t_delta, std::stringstream &inputs) {
738+
std::vector<std::string> in_var_names = get_bmi_model()->GetInputVarNames();
739+
time_t model_epoch_time = convert_model_time(model_init_time) + get_bmi_model_start_time_forcing_offset_s();
740+
inputs << "Input variables were as follows:";
741+
742+
for (std::string & var_name : in_var_names) {
743+
data_access::GenericDataProvider *provider;
744+
std::string var_map_alias = get_config_mapped_variable_name(var_name);
745+
if (input_forcing_providers.find(var_map_alias) != input_forcing_providers.end()) {
746+
provider = input_forcing_providers[var_map_alias].get();
747+
}
748+
else if (var_map_alias != var_name && input_forcing_providers.find(var_name) != input_forcing_providers.end()) {
749+
provider = input_forcing_providers[var_name].get();
750+
}
751+
else {
752+
provider = forcing.get();
753+
}
754+
755+
// TODO: probably need to actually allow this by default and warn, but have config option to activate
756+
// this type of behavior
757+
// TODO: account for arrays later
758+
int nbytes = get_bmi_model()->GetVarNbytes(var_name);
759+
int varItemSize = get_bmi_model()->GetVarItemsize(var_name);
760+
int numItems = nbytes / varItemSize;
761+
762+
std::shared_ptr<void> value_ptr;
763+
// Finally, use the value obtained to set the model input
764+
std::string type = get_bmi_model()->get_analogous_cxx_type(get_bmi_model()->GetVarType(var_name),
765+
varItemSize);
766+
767+
inputs << "\n" << var_map_alias << " = ";
768+
if (numItems != 1) {
769+
//more than a single value needed for var_name
770+
auto values = provider->get_values(CatchmentAggrDataSelector(this->get_catchment_id(),var_map_alias, model_epoch_time, t_delta,
771+
get_bmi_model()->GetVarUnits(var_name)));
772+
value_ptr = get_values_as_type( type, values.begin(), values.end() );
773+
// array like input: precipitation_mm_per_h = [0.2, 0.8, 1.8]
774+
this->append_inputs(type, value_ptr, numItems, inputs);
775+
776+
} else {
777+
//scalar value
778+
double value = provider->get_value(CatchmentAggrDataSelector(this->get_catchment_id(),var_map_alias, model_epoch_time, t_delta,
779+
get_bmi_model()->GetVarUnits(var_name)));
780+
this->append_input(type, value, inputs);
781+
}
782+
}
783+
}
784+
785+
template<typename T>
786+
void Bmi_Module_Formulation::append_inputs(std::shared_ptr<void> values, int num_items, std::stringstream &inputs) {
787+
T *array = (T*)values.get();
788+
inputs << "[";
789+
for (int i = 0; i < num_items; ++i) {
790+
if (i != 0)
791+
inputs << ", ";
792+
inputs << array[i];
793+
}
794+
inputs << "]";
795+
}
796+
797+
void Bmi_Module_Formulation::append_inputs(std::string type, std::shared_ptr<void> values, int num_items, std::stringstream &inputs) {
798+
799+
if (type == "double" || type == "double precision")
800+
this->append_inputs<double>(values, num_items, inputs);
801+
802+
else if (type == "float" || type == "real")
803+
this->append_inputs<float>(values, num_items, inputs);
804+
805+
else if (type == "short" || type == "short int" || type == "signed short" || type == "signed short int")
806+
this->append_inputs<short>(values, num_items, inputs);
807+
808+
else if (type == "unsigned short" || type == "unsigned short int")
809+
this->append_inputs<unsigned short>(values, num_items, inputs);
810+
811+
else if (type == "int" || type == "signed" || type == "signed int" || type == "integer")
812+
this->append_inputs<int>(values, num_items, inputs);
813+
814+
else if (type == "unsigned" || type == "unsigned int")
815+
this->append_inputs<unsigned int>(values, num_items, inputs);
816+
817+
else if (type == "long" || type == "long int" || type == "signed long" || type == "signed long int")
818+
this->append_inputs<long>(values, num_items, inputs);
819+
820+
else if (type == "unsigned long" || type == "unsigned long int")
821+
this->append_inputs<unsigned long>(values, num_items, inputs);
822+
823+
else if (type == "long long" || type == "long long int" || type == "signed long long" || type == "signed long long int")
824+
this->append_inputs<long long>(values, num_items, inputs);
825+
826+
else if (type == "unsigned long long" || type == "unsigned long long int")
827+
this->append_inputs<unsigned long long>(values, num_items, inputs);
828+
829+
}
830+
831+
template<typename T>
832+
void Bmi_Module_Formulation::append_input(std::string type, T value, std::stringstream &inputs) {
833+
834+
if (type == "double" || type == "double precision")
835+
inputs << static_cast<double>(value);
836+
837+
else if (type == "float" || type == "real")
838+
inputs << static_cast<float>(value);
839+
840+
else if (type == "short" || type == "short int" || type == "signed short" || type == "signed short int")
841+
inputs << static_cast<short>(value);
842+
843+
else if (type == "unsigned short" || type == "unsigned short int")
844+
inputs << static_cast<unsigned short>(value);
845+
846+
else if (type == "int" || type == "signed" || type == "signed int" || type == "integer")
847+
inputs << static_cast<int>(value);
848+
849+
else if (type == "unsigned" || type == "unsigned int")
850+
inputs << static_cast<unsigned int>(value);
851+
852+
else if (type == "long" || type == "long int" || type == "signed long" || type == "signed long int")
853+
inputs << static_cast<long>(value);
854+
855+
else if (type == "unsigned long" || type == "unsigned long int")
856+
inputs << static_cast<unsigned long>(value);
857+
858+
else if (type == "long long" || type == "long long int" || type == "signed long long" || type == "signed long long int")
859+
inputs << static_cast<long long>(value);
860+
861+
else if (type == "unsigned long long" || type == "unsigned long long int")
862+
inputs << static_cast<unsigned long long>(value);
863+
864+
}
865+
720866
const boost::span<char> Bmi_Module_Formulation::get_serialization_state() const {
721867
auto bmi = this->bmi_model;
722868
// create a new serialized state, getting the amount of data that was saved

0 commit comments

Comments
 (0)