Skip to content

Commit efc31f8

Browse files
committed
test(bmi_mass_balance): Test the protocol via the C and multi formulation tests
1 parent 049476b commit efc31f8

File tree

2 files changed

+229
-9
lines changed

2 files changed

+229
-9
lines changed

test/realizations/catchments/Bmi_C_Formulation_Test.cpp

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#include "FileChecker.h"
2525
#include "Formulation_Manager.hpp"
2626
#include <boost/date_time.hpp>
27-
27+
#include "Bmi_Protocols.hpp"
2828
using ::testing::MatchesRegex;
2929
using namespace realization;
3030

@@ -116,6 +116,7 @@ class Bmi_C_Formulation_Test : public ::testing::Test {
116116
std::vector<std::string> main_output_variable;
117117
std::vector<std::string> registration_functions;
118118
std::vector<bool> uses_forcing_file;
119+
// std::vector<bool> tries_mass_balance;
119120
std::vector<std::shared_ptr<forcing_params>> forcing_params_examples;
120121
std::vector<geojson::GeoJSON> config_properties;
121122
std::vector<boost::property_tree::ptree> config_prop_ptree;
@@ -148,6 +149,7 @@ void Bmi_C_Formulation_Test::SetUp() {
148149
main_output_variable = std::vector<std::string>(EX_COUNT);
149150
registration_functions = std::vector<std::string>(EX_COUNT);
150151
uses_forcing_file = std::vector<bool>(EX_COUNT);
152+
// tries_mass_balance = std::vector<bool>(EX_COUNT);
151153
forcing_params_examples = std::vector<std::shared_ptr<forcing_params>>(EX_COUNT);
152154
config_properties = std::vector<geojson::GeoJSON>(EX_COUNT);
153155
config_prop_ptree = std::vector<boost::property_tree::ptree>(EX_COUNT);
@@ -161,6 +163,7 @@ void Bmi_C_Formulation_Test::SetUp() {
161163
main_output_variable[0] = "OUTPUT_VAR_1";
162164
registration_functions[0] = "register_bmi";
163165
uses_forcing_file[0] = false;
166+
// tries_mass_balance[0] = true;
164167

165168
catchment_ids[1] = "cat-27";
166169
model_type_name[1] = "test_bmi_c";
@@ -170,6 +173,7 @@ void Bmi_C_Formulation_Test::SetUp() {
170173
main_output_variable[1] = "OUTPUT_VAR_1";
171174
registration_functions[1] = "register_bmi";
172175
uses_forcing_file[1] = false;
176+
// tries_mass_balance[1] = false;
173177

174178
std::string variables_with_rain_rate = " \"output_variables\": [\"OUTPUT_VAR_2\",\n"
175179
" \"OUTPUT_VAR_1\"],\n";
@@ -198,6 +202,9 @@ void Bmi_C_Formulation_Test::SetUp() {
198202
+ variables_line +
199203
" \"uses_forcing_file\": " + (uses_forcing_file[i] ? "true" : "false") + ","
200204
" \"model_params\": { \"PARAM_VAR_1\": 42, \"PARAM_VAR_2\": 4.2, \"PARAM_VAR_3\": [4, 2]}" +
205+
// (tries_mass_balance[i] ? \
206+
// ", \"mass_balance\": {\"tolerance\": 1e-12, \"fatal\":true}" \
207+
// :"" )+
201208
" },"
202209
" \"forcing\": { \"path\": \"" + forcing_file[i] + "\", \"provider\": \"CsvPerFeature\"}"
203210
" }"
@@ -387,6 +394,200 @@ TEST_F(Bmi_C_Formulation_Test, determine_model_time_offset_0_c) {
387394
ASSERT_EQ(get_friend_bmi_model_start_time_forcing_offset_s(formulation), expected_offset);
388395
}
389396

397+
using models::bmi::protocols::INPUT_MASS_NAME;
398+
using models::bmi::protocols::OUTPUT_MASS_NAME;
399+
using models::bmi::protocols::STORED_MASS_NAME;
400+
using models::bmi::protocols::LEAKED_MASS_NAME;
401+
using models::bmi::protocols::MassBalanceError;
402+
403+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance) {
404+
int ex_index = 0;
405+
406+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
407+
auto ptree = config_prop_ptree[ex_index];
408+
// mass balance failure will throw an exception
409+
ptree.put_child("mass_balance", MassBalanceMock(true).get());
410+
formulation.create_formulation(ptree);
411+
formulation.check_mass_balance(0, 2, "t0");
412+
formulation.get_response(1, 3600);
413+
formulation.check_mass_balance(1, 2, "t1");
414+
}
415+
416+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_warns) {
417+
int ex_index = 0;
418+
419+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
420+
auto ptree = config_prop_ptree[ex_index];
421+
ptree.put_child("mass_balance", MassBalanceMock(false).get());
422+
formulation.create_formulation(ptree);
423+
//formulation.check_mass_balance(0, 1, "t0");
424+
formulation.get_response(1, 3600);
425+
double mass_error = 100;
426+
get_friend_bmi_model(formulation)->SetValue(STORED_MASS_NAME, &mass_error); // Force a mass balance error
427+
//ASSERT_THROW(formulation.check_mass_balance(0, 1, "t0"), MassBalanceWarning);
428+
testing::internal::CaptureStderr();
429+
formulation.check_mass_balance(0, 1, "t0");
430+
std::string output = testing::internal::GetCapturedStderr();
431+
std::cerr << output;
432+
EXPECT_THAT(output, testing::HasSubstr("mass_balance::warning"));
433+
}
434+
435+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_stored_fails) {
436+
int ex_index = 0;
437+
438+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
439+
auto ptree = config_prop_ptree[ex_index];
440+
ptree.put_child("mass_balance", MassBalanceMock(true).get());
441+
formulation.create_formulation(ptree);
442+
//formulation.check_mass_balance(0, 1, "t0");
443+
formulation.get_response(1, 3600);
444+
double mass_error = 100;
445+
get_friend_bmi_model(formulation)->SetValue(STORED_MASS_NAME, &mass_error); // Force a mass balance error
446+
//formulation.check_mass_balance(0, 1, "t0");
447+
ASSERT_THROW(formulation.check_mass_balance(0, 1, "t0"), MassBalanceError);
448+
}
449+
450+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_in_fails_a) {
451+
int ex_index = 0;
452+
453+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
454+
auto ptree = config_prop_ptree[ex_index];
455+
ptree.put_child("mass_balance", MassBalanceMock(true, 1e-5).get());
456+
formulation.create_formulation(ptree);
457+
formulation.get_response(1, 3600);
458+
double mass_error = 2;
459+
get_friend_bmi_model(formulation)->SetValue(INPUT_MASS_NAME, &mass_error); // Force a mass balance error
460+
ASSERT_THROW(formulation.check_mass_balance(0, 1, "t0"), MassBalanceError);
461+
}
462+
463+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_out_fails) {
464+
int ex_index = 0;
465+
466+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
467+
auto ptree = config_prop_ptree[ex_index];
468+
ptree.put_child("mass_balance", MassBalanceMock(true).get());
469+
formulation.create_formulation(ptree);
470+
formulation.get_response(1, 3600);
471+
double mass_error = 2;
472+
get_friend_bmi_model(formulation)->SetValue(OUTPUT_MASS_NAME, &mass_error); // Force a mass balance error
473+
ASSERT_THROW(formulation.check_mass_balance(0, 1, "t0"), MassBalanceError);
474+
}
475+
476+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_leaked_fails) {
477+
int ex_index = 0;
478+
479+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
480+
auto ptree = config_prop_ptree[ex_index];
481+
ptree.put_child("mass_balance", MassBalanceMock(true).get());
482+
formulation.create_formulation(ptree);
483+
formulation.get_response(1, 3600);
484+
double mass_error = 2;
485+
get_friend_bmi_model(formulation)->SetValue(LEAKED_MASS_NAME, &mass_error); // Force a mass balance error
486+
ASSERT_THROW(formulation.check_mass_balance(0, 1, "t0"), MassBalanceError);
487+
}
488+
489+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_tolerance) {
490+
int ex_index = 0;
491+
492+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
493+
auto ptree = config_prop_ptree[ex_index];
494+
ptree.put_child("mass_balance", MassBalanceMock(true, 1e-5).get());
495+
formulation.create_formulation(ptree);
496+
//formulation.check_mass_balance(0, "t0");
497+
formulation.get_response(1, 3600);
498+
double mass_error;
499+
get_friend_bmi_model(formulation)->GetValue(INPUT_MASS_NAME, &mass_error);
500+
mass_error += 1e-4; // Force a mass balance error not within tolerance
501+
get_friend_bmi_model(formulation)->SetValue(INPUT_MASS_NAME, &mass_error); // Force a mass balance error
502+
ASSERT_THROW(formulation.check_mass_balance(0, 1, "t0"), MassBalanceError);
503+
}
504+
505+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_tolerance_a) {
506+
int ex_index = 0;
507+
508+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
509+
auto ptree = config_prop_ptree[ex_index];
510+
ptree.put_child("mass_balance", MassBalanceMock(true, 1e-5).get());
511+
formulation.create_formulation(ptree);
512+
//formulation.check_mass_balance(0, 1, "t0");
513+
formulation.get_response(1, 3600);
514+
double mass_error;
515+
get_friend_bmi_model(formulation)->GetValue(INPUT_MASS_NAME, &mass_error);
516+
mass_error += 1e-6; // Force a mass balance error within tolerance
517+
get_friend_bmi_model(formulation)->SetValue(INPUT_MASS_NAME, &mass_error); // Force a mass balance error
518+
formulation.check_mass_balance(0, 1, "t0"); // Should not throw an error
519+
}
520+
521+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_off) {
522+
int ex_index = 1;
523+
524+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
525+
formulation.create_formulation(config_prop_ptree[ex_index]);
526+
formulation.check_mass_balance(0, 2, "t0");
527+
formulation.get_response(1, 3600);
528+
formulation.check_mass_balance(1, 2, "t1");
529+
}
530+
531+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_missing) {
532+
int ex_index = 0;
533+
534+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
535+
auto ptree = config_prop_ptree[ex_index];
536+
537+
std::string catchment_path = "catchments." + catchment_ids[ex_index] + ".bmi_c";
538+
ptree.erase("mass_balance");
539+
formulation.create_formulation(ptree);
540+
formulation.check_mass_balance(0, 2, "t0");
541+
formulation.get_response(1, 3600);
542+
double mass_error = 100;
543+
double more_mass_error = 99;
544+
get_friend_bmi_model(formulation)->SetValue(OUTPUT_MASS_NAME, &mass_error); // Force a mass balance error
545+
get_friend_bmi_model(formulation)->SetValue(INPUT_MASS_NAME, &more_mass_error); // Force a mass balance error
546+
formulation.check_mass_balance(1, 2, "t1");
547+
548+
}
549+
550+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_frequency) {
551+
int ex_index = 0;
552+
553+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
554+
auto ptree = config_prop_ptree[ex_index];
555+
ptree.put_child("mass_balance", MassBalanceMock(true, 1e-5, 2).get());
556+
formulation.create_formulation(ptree);
557+
double mass_error;
558+
mass_error += 10; // Force a mass balance error above tolerance
559+
get_friend_bmi_model(formulation)->SetValue(OUTPUT_MASS_NAME, &mass_error); //
560+
//Check initial mass balance -- should error which indicates it was propoerly checked
561+
//per frequency setting
562+
ASSERT_THROW(formulation.check_mass_balance(0, 2, "t0"), MassBalanceError);
563+
// Call mass balance check again, this should NOT error, since the actual check
564+
// should be skipped due to the frequency setting
565+
formulation.check_mass_balance(1, 2, "t1");
566+
// Check mass balance again, this SHOULD error since the previous mass balance
567+
// will propagate, and it should now be checked based on the frequency
568+
ASSERT_THROW(formulation.check_mass_balance(2, 2, "t2"), MassBalanceError);
569+
}
570+
571+
TEST_F(Bmi_C_Formulation_Test, check_mass_balance_frequency_1) {
572+
int ex_index = 0;
573+
574+
Bmi_C_Formulation formulation(catchment_ids[ex_index], std::make_shared<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
575+
auto ptree = config_prop_ptree[ex_index];
576+
ptree.put_child("mass_balance", MassBalanceMock(true, 1e-5, -1).get());
577+
formulation.create_formulation(ptree);
578+
double mass_error;
579+
mass_error += 10; // Force a mass balance error above tolerance
580+
get_friend_bmi_model(formulation)->SetValue(OUTPUT_MASS_NAME, &mass_error); //
581+
//Check initial mass balance -- should NOT error
582+
formulation.check_mass_balance(0, 2, "t0");
583+
// Call mass balance check again, this should NOT error, since the actual check
584+
// should be skipped due to the frequency setting
585+
formulation.check_mass_balance(1, 2, "t1");
586+
// Check mass balance again, this SHOULD error since the this is step 2/2
587+
// and it will now be checked based on the frequency (-1, check at end)
588+
ASSERT_THROW(formulation.check_mass_balance(2, 2, "t2"), MassBalanceError);
589+
}
590+
390591
#endif // NGEN_BMI_C_LIB_TESTS_ACTIVE
391592

392593
#endif // NGEN_BMI_C_FORMULATION_TEST_CPP

test/realizations/catchments/Bmi_Multi_Formulation_Test.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ class Bmi_Multi_Formulation_Test : public ::testing::Test {
349349
return s;
350350
}
351351

352-
inline void buildExampleConfig(const int ex_index) {
352+
inline void buildExampleConfig(const int ex_index, const int nested_count) {
353353
std::string config =
354354
"{\n"
355355
" \"global\": {},\n"
@@ -364,10 +364,12 @@ class Bmi_Multi_Formulation_Test : public ::testing::Test {
364364
" \"init_config\": \"\",\n"
365365
" \"allow_exceed_end_time\": true,\n"
366366
" \"main_output_variable\": \"" + main_output_variables[ex_index] + "\",\n"
367-
" \"modules\": [\n"
368-
+ buildExampleNestedModuleSubConfig(ex_index, 0) + ",\n"
369-
+ buildExampleNestedModuleSubConfig(ex_index, 1) + "\n"
370-
" ],\n"
367+
" \"modules\": [\n";
368+
for (int i = 0; i < nested_count - 1; ++i) {
369+
config += buildExampleNestedModuleSubConfig(ex_index, i) + ",\n";
370+
}
371+
config += buildExampleNestedModuleSubConfig(ex_index, nested_count - 1) + "\n";
372+
config += " ],\n"
371373
" \"uses_forcing_file\": false\n"
372374
+ buildExampleOutputVariablesSubConfig(ex_index) + "\n"
373375
" }\n"
@@ -423,7 +425,7 @@ class Bmi_Multi_Formulation_Test : public ::testing::Test {
423425
main_output_variables[ex_index] = nested_module_main_output_variables[ex_index][example_module_depth[ex_index] - 1];
424426
specified_output_variables[ex_index] = output_variables;
425427

426-
buildExampleConfig(ex_index);
428+
buildExampleConfig(ex_index, nested_module_lists[ex_index].size());
427429
}
428430

429431

@@ -451,7 +453,7 @@ void Bmi_Multi_Formulation_Test::SetUp() {
451453

452454
// Define this manually to set how many nested modules per example, and implicitly how many examples.
453455
// This means example_module_depth.size() example scenarios with example_module_depth[i] nested modules in each scenario.
454-
example_module_depth = {2, 2, 2, 2, 2, 2};
456+
example_module_depth = {2, 2, 2, 2, 2, 2, 3};
455457

456458
// Initialize the members for holding required input and result test data for individual example scenarios
457459
setupExampleDataCollections();
@@ -491,7 +493,14 @@ void Bmi_Multi_Formulation_Test::SetUp() {
491493
// Cases 4 and 5 Specifically to test output_variables failure cases...
492494
initializeTestExample(4, "cat-27", {std::string(BMI_FORTRAN_TYPE), std::string(BMI_PYTHON_TYPE)}, { "bogus_variable" });
493495
initializeTestExample(5, "cat-27", {std::string(BMI_FORTRAN_TYPE), std::string(BMI_PYTHON_TYPE)}, { "OUTPUT_VAR_1" });
494-
496+
497+
#if NGEN_WITH_BMI_C
498+
initializeTestExample(6, "cat-27", {std::string(BMI_C_TYPE), std::string(BMI_FORTRAN_TYPE), std::string(BMI_PYTHON_TYPE)}, {"OUTPUT_VAR_1__0"}); // Output var from C module...
499+
#else
500+
initializeTestExample(6, "cat-27", {std::string(BMI_FORTRAN_TYPE), std::string(BMI_PYTHON_TYPE)}, {"OUTPUT_VAR_1__0"}); // Output var from Fortran module...
501+
502+
#endif // NGEN_WITH_PYTHON
503+
495504
}
496505

497506
/** Simple test to make sure the model config from example 0 initializes. */
@@ -884,6 +893,16 @@ TEST_F(Bmi_Multi_Formulation_Test, GetAvailableVariableNames) {
884893
);
885894
}
886895
}
896+
897+
TEST_F(Bmi_Multi_Formulation_Test, MassBalanceCheck) {
898+
int ex_index = 6;
899+
900+
Bmi_Multi_Formulation formulation(catchment_ids[ex_index], std::make_unique<CsvPerFeatureForcingProvider>(*forcing_params_examples[ex_index]), utils::StreamHandler());
901+
formulation.create_formulation(config_prop_ptree[ex_index]);
902+
903+
formulation.check_mass_balance(0, 1, "t0");
904+
}
905+
887906
#endif // NGEN_WITH_BMI_C || NGEN_WITH_BMI_FORTRAN || NGEN_WITH_PYTHON
888907

889908
#endif // NGEN_BMI_MULTI_FORMULATION_TEST_CPP

0 commit comments

Comments
 (0)