2424#include " FileChecker.h"
2525#include " Formulation_Manager.hpp"
2626#include < boost/date_time.hpp>
27-
27+ # include " Bmi_Protocols.hpp "
2828using ::testing::MatchesRegex;
2929using 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
0 commit comments