From 9cde32080a1730cb8830ec4c87ff785e75dcce7f Mon Sep 17 00:00:00 2001 From: Luke Butler Date: Thu, 30 Mar 2023 16:22:58 -0400 Subject: [PATCH] added function updateruleunits --- src/epanet.c | 27 ++++- src/funcs.h | 1 + src/rules.c | 120 ++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_project.cpp | 79 ------------ tests/test_units.cpp | 267 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 415 insertions(+), 80 deletions(-) create mode 100644 tests/test_units.cpp diff --git a/src/epanet.c b/src/epanet.c index 7e7b994b..db47e2fb 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -1237,6 +1237,9 @@ int DLLEXPORT EN_setoption(EN_Project p, int option, double value) int i, j, pat, unit; double Ke, n, ucf; + double qfactor, hfactor, pfactor, dfactor; + double dcf, pcf, hcf, qcf; + if (!p->Openflag) return 102; // The EN_UNBALANCED option can be < 0 indicating that the simulation @@ -1384,7 +1387,19 @@ int DLLEXPORT EN_setoption(EN_Project p, int option, double value) if (p->parser.Unitsflag == US && unit > PSI) return 0; if (p->parser.Unitsflag == SI && unit == PSI) return 0; p->parser.Pressflag = unit; + + dfactor = Ucf[DEMAND]; + pfactor = Ucf[PRESSURE]; + hfactor = Ucf[HEAD]; + qfactor = Ucf[FLOW]; initunits(p); + + // Update units in rules + dcf = Ucf[DEMAND] / dfactor; + pcf = Ucf[PRESSURE] / pfactor; + hcf = Ucf[HEAD] / hfactor; + qcf = Ucf[FLOW] / qfactor; + updateruleunits(p, dcf, pcf, hcf, qcf); break; default: @@ -1420,7 +1435,8 @@ int DLLEXPORT EN_setflowunits(EN_Project p, int units) Network *net = &p->network; int i, j; - double qfactor, vfactor, hfactor, efactor, xfactor, yfactor; + double qfactor, vfactor, hfactor, efactor, pfactor, dfactor, xfactor, yfactor; + double dcf, pcf, hcf, qcf; double *Ucf = p->Ucf; if (!p->Openflag) return 102; @@ -1430,6 +1446,8 @@ int DLLEXPORT EN_setflowunits(EN_Project p, int units) vfactor = Ucf[VOLUME]; hfactor = Ucf[HEAD]; efactor = Ucf[ELEV]; + pfactor = Ucf[PRESSURE]; + dfactor = Ucf[DEMAND]; p->parser.Flowflag = units; switch (units) @@ -1452,6 +1470,13 @@ int DLLEXPORT EN_setflowunits(EN_Project p, int units) else if (p->parser.Pressflag == PSI) p->parser.Pressflag = METERS; initunits(p); + // Update pressure units in rules + dcf = Ucf[DEMAND] / dfactor; + pcf = Ucf[PRESSURE] / pfactor; + hcf = Ucf[HEAD] / hfactor; + qcf = Ucf[FLOW] / qfactor; + updateruleunits(p, dcf, pcf, hcf, qcf); + //update curves for (i = 1; i <= net->Ncurves; i++) { diff --git a/src/funcs.h b/src/funcs.h index 23914771..3fe38fe6 100755 --- a/src/funcs.h +++ b/src/funcs.h @@ -124,6 +124,7 @@ Spremise *getpremise(Spremise *, int); Saction *getaction(Saction *, int); int writerule(Project *, FILE *, int); int checkrules(Project *, long); +void updateruleunits(Project *pr, double dcf, double pcf, double hcf, double qcf); // ------- REPORT.C ----------------- diff --git a/src/rules.c b/src/rules.c index 68b8de6c..4eaf3f51 100644 --- a/src/rules.c +++ b/src/rules.c @@ -549,6 +549,126 @@ int checkrules(Project *pr, long dt) return actionCount; } +void updateruleunits(Project *pr, double dcf, double pcf, double hcf, double qcf) +//----------------------------------------------------------- +// Updates the units of a rule's premises and actions. +//----------------------------------------------------------- +{ + Network *net = &pr->network; + Slink *Link = net->Link; + + int i, k; + double x; + Spremise *p; + Saction *a; + + for (i = 1; i <= net->Nrules; i++) + { + p = net->Rule[i].Premises; + while (p != NULL) + { + + switch (p->variable) + { + case r_DEMAND: + p->value *= dcf; + break; + + case r_HEAD: + case r_GRADE: + p->value *= hcf; + break; + + case r_PRESSURE: + p->value *= pcf; + break; + + case r_LEVEL: + p->value *= hcf; + break; + + case r_FLOW: + p->value *= qcf; + break; + + case r_SETTING: + + switch (Link[p->index].Type) + { + case PRV: + case PSV: + case PBV: + p->value *= pcf; + break; + case FCV: + p->value *= qcf; + break; + default: + break; + } + break; + + default: + break; + + } + p = p->next; + } + + a = net->Rule[i].ThenActions; + while (a != NULL) + { + k = a->link; + x = a->setting; + + // Change link's setting + if (x != MISSING) + { + switch (net->Link[k].Type) + { + case PRV: + case PSV: + case PBV: + a->setting *= pcf; + break; + case FCV: + a->setting *= qcf; + break; + default: + break; + } + } + a = a->next; + } + a = net->Rule[i].ElseActions; + while (a != NULL) + { + k = a->link; + x = a->setting; + + // Change link's setting + if (x != MISSING) + { + switch (net->Link[k].Type) + { + case PRV: + case PSV: + case PBV: + a->setting *= pcf; + break; + case FCV: + a->setting *= qcf; + break; + default: + break; + } + } + a = a->next; + } + } +} + + void newrule(Project *pr) //---------------------------------------------------------- // Adds a new rule to the project diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 946e9f60..d90167f7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,7 @@ set(toolkit_test_srcs test_overflow.cpp test_pda.cpp test_valve.cpp + test_units.cpp ) add_executable(test_toolkit ${toolkit_test_srcs}) diff --git a/tests/test_project.cpp b/tests/test_project.cpp index 6b102d3b..903cd0a8 100644 --- a/tests/test_project.cpp +++ b/tests/test_project.cpp @@ -116,85 +116,6 @@ BOOST_AUTO_TEST_CASE(test_run) EN_deleteproject(ph); } -BOOST_FIXTURE_TEST_CASE(test_pressure_units, FixtureInitClose) -{ - int index; - long t; - double p, units; - - // Create basic network - error = EN_addnode(ph, "R1", EN_RESERVOIR, &index); - BOOST_REQUIRE(error == 0); - error = EN_setnodevalue(ph, index, EN_ELEVATION, 100); - BOOST_REQUIRE(error == 0); - error = EN_addnode(ph, "J1", EN_JUNCTION, &index); - BOOST_REQUIRE(error == 0); - error = EN_addlink(ph, "P1", EN_PIPE, "R1", "J1", &index); - BOOST_REQUIRE(error == 0); - - // Run simulation and get junction pressure - error = EN_openH(ph); - BOOST_REQUIRE(error == 0); - error = EN_initH(ph, EN_NOSAVE); - BOOST_REQUIRE(error == 0); - error = EN_runH(ph, &t); - BOOST_REQUIRE(error == 0); - error = EN_getnodevalue(ph, 1, EN_PRESSURE, &p); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(abs(p - 43.33) < 1.e-5); - - // Get pressure unit and check that it is PSI - error = EN_getoption(ph, EN_PRESS_UNITS, &units); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(units == EN_PSI); - - // Check that pressure unit is PSI - error = EN_setoption(ph, EN_PRESS_UNITS, EN_METERS); - BOOST_REQUIRE(error == 0); - - // Change to meters and confirm that units are still PSI - error = EN_getoption(ph, EN_PRESS_UNITS, &units); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(units == EN_PSI); - - // Change flow units to LPS to change to metric units and rerun simulation - error = EN_setflowunits(ph, EN_LPS); - BOOST_REQUIRE(error == 0); - error = EN_initH(ph, EN_NOSAVE); - BOOST_REQUIRE(error == 0); - error = EN_runH(ph, &t); - BOOST_REQUIRE(error == 0); - - // Confirm that pressure is now in meters - error = EN_getnodevalue(ph, 1, EN_PRESSURE, &p); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(abs(p - 30.48) < 1.e-5); - error = EN_getoption(ph, EN_PRESS_UNITS, &units); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(units == EN_METERS); - - // Set and check that pressure units are in kPa - error = EN_setoption(ph, EN_PRESS_UNITS, EN_KPA); - BOOST_REQUIRE(error == 0); - error = EN_getoption(ph, EN_PRESS_UNITS, &units); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(units == EN_KPA); - error = EN_getnodevalue(ph, 1, EN_PRESSURE, &p); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(abs(p - 298.76035) < 1.e-5); - - // Set pressure to PSI and check that it remains in kPa - error = EN_setoption(ph, EN_PRESS_UNITS, EN_PSI); - BOOST_REQUIRE(error == 0); - error = EN_getoption(ph, EN_PRESS_UNITS, &units); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(units == EN_KPA); - - error = EN_closeH(ph); - BOOST_REQUIRE(error == 0); - -} - BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(test_proj_fixture) diff --git a/tests/test_units.cpp b/tests/test_units.cpp new file mode 100644 index 00000000..ffdfc26f --- /dev/null +++ b/tests/test_units.cpp @@ -0,0 +1,267 @@ +/* + ****************************************************************************** + Project: OWA EPANET + Version: 2.2 + Module: test_units.cpp + Description: Tests EPANET toolkit api functions + Authors: see AUTHORS + Copyright: see AUTHORS + License: see LICENSE + Last Updated: 03/30/2023 + ****************************************************************************** +*/ + +/* +This is a test for the API functions that change the units of a project. +*/ + +#include + +#include "test_toolkit.hpp" + +/* +---------------------------------------------- + Flow units conversion factors +---------------------------------------------- +*/ +double GPMperCFS = 448.831; +double LPSperCFS = 28.317; +double MperFT = 0.3048; +double PSIperFT = 0.4333; +double KPAperPSI = 6.895; + +char unitrules[] = "RULE 1\n IF NODE 10 DEMAND > 10 \n" + "AND NODE 10 HEAD > 20 \n" + "AND NODE 10 PRESSURE > 30 \n" + "AND NODE 10 LEVEL > 40 \n" + "AND LINK 10 FLOW > 50 \n" + "AND LINK PRV1 SETTING > 60 \n" + "AND LINK FCV1 SETTING > 70 \n" + "THEN LINK PRV1 SETTING = 80\n ELSE LINK FCV1 SETTING = 90"; + + +BOOST_AUTO_TEST_SUITE (test_units) + +BOOST_FIXTURE_TEST_CASE(test_pressure_units, FixtureInitClose) +{ + int index; + long t; + double p, units; + + // Create basic network + error = EN_addnode(ph, "R1", EN_RESERVOIR, &index); + BOOST_REQUIRE(error == 0); + error = EN_setnodevalue(ph, index, EN_ELEVATION, 100); + BOOST_REQUIRE(error == 0); + error = EN_addnode(ph, "J1", EN_JUNCTION, &index); + BOOST_REQUIRE(error == 0); + error = EN_addlink(ph, "P1", EN_PIPE, "R1", "J1", &index); + BOOST_REQUIRE(error == 0); + + // Run simulation and get junction pressure + error = EN_openH(ph); + BOOST_REQUIRE(error == 0); + error = EN_initH(ph, EN_NOSAVE); + BOOST_REQUIRE(error == 0); + error = EN_runH(ph, &t); + BOOST_REQUIRE(error == 0); + error = EN_getnodevalue(ph, 1, EN_PRESSURE, &p); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(p - 43.33) < 1.e-5); + + // Get pressure unit and check that it is PSI + error = EN_getoption(ph, EN_PRESS_UNITS, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(units == EN_PSI); + + // Change to pressure from PSI to meters and check it's still PSI + error = EN_setoption(ph, EN_PRESS_UNITS, EN_METERS); + BOOST_REQUIRE(error == 0); + + error = EN_getoption(ph, EN_PRESS_UNITS, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(units == EN_PSI); + + // Change flow units to LPS to change to metric units and rerun simulation + error = EN_setflowunits(ph, EN_LPS); + BOOST_REQUIRE(error == 0); + error = EN_initH(ph, EN_NOSAVE); + BOOST_REQUIRE(error == 0); + error = EN_runH(ph, &t); + BOOST_REQUIRE(error == 0); + + // Confirm that pressure is now in meters + error = EN_getnodevalue(ph, 1, EN_PRESSURE, &p); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(p - 30.48) < 1.e-5); + error = EN_getoption(ph, EN_PRESS_UNITS, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(units == EN_METERS); + + // Set and check that pressure units are in kPa + error = EN_setoption(ph, EN_PRESS_UNITS, EN_KPA); + BOOST_REQUIRE(error == 0); + error = EN_getoption(ph, EN_PRESS_UNITS, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(units == EN_KPA); + error = EN_getnodevalue(ph, 1, EN_PRESSURE, &p); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(p - 298.76035) < 1.e-5); + + // Set pressure to PSI and check that it remains in kPa + error = EN_setoption(ph, EN_PRESS_UNITS, EN_PSI); + BOOST_REQUIRE(error == 0); + error = EN_getoption(ph, EN_PRESS_UNITS, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(units == EN_KPA); + + error = EN_closeH(ph); + BOOST_REQUIRE(error == 0); + +} + +BOOST_FIXTURE_TEST_CASE(test_pda_unit_change, FixtureOpenClose) +{ + int type; + double pmin, preq, pexp; + + // Switch to PDA with pressure limits of 20 - 100 psi + error = EN_setdemandmodel(ph, EN_PDA, 20, 100, 0.5); + BOOST_REQUIRE(error == 0); + + error = EN_setflowunits(ph, EN_LPS); + BOOST_REQUIRE(error == 0); + + error = EN_getdemandmodel(ph, &type, &pmin, &preq, &pexp); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(pmin - (20/PSIperFT*MperFT)) < 1.e-5); + BOOST_CHECK(abs(preq - (100/PSIperFT*MperFT)) < 1.e-5); + +} + +BOOST_FIXTURE_TEST_CASE(test_rule_unit_change, FixtureOpenClose) +{ + int index, node22, link12; + double units; + + // Rule variables + int r_logop, r_object, r_objIndex, r_variable, r_relop, r_status; + double r_value; + + // Control variables + int c_index, c_type, c_linkIndex, c_nodeIndex; + double c_setting, c_level; + + // Add new PRV and FCV to test rules + error = EN_addlink(ph, (char *)"PRV1", EN_PRV, (char *)"10", (char *)"11", &index); + BOOST_REQUIRE(error == 0); + error = EN_addlink(ph, (char *)"FCV1", EN_FCV, (char *)"12", (char *)"13", &index); + BOOST_REQUIRE(error == 0); + + // Add the rule to the project + error = EN_addrule(ph, unitrules); + BOOST_REQUIRE(error == 0); + + // Add control that checks junction pressure + EN_getnodeindex(ph, (char *)"22", &node22); + EN_getlinkindex(ph, (char *)"12", &link12); + error = EN_addcontrol(ph, EN_HILEVEL, link12, 0, node22, 250, &c_index); + BOOST_REQUIRE(error == 0); + + // Check that rules and controls are in US units + error = EN_getpremise(ph, 1, 3, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(r_value == 30); + + error = EN_getcontrol(ph, c_index, &c_type, &c_linkIndex, &c_setting, &c_nodeIndex, &c_level); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(c_level == 250); + + // Change flow units to lps and pressure to meters + error = EN_setflowunits(ph, EN_LPS); + BOOST_REQUIRE(error == 0); + + error = EN_getoption(ph, EN_PRESS_UNITS, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(units == EN_METERS); + + // Check that rules and controls are in meters + + // Simple Control - 250 psi to meters + error = EN_getcontrol(ph, c_index, &c_type, &c_linkIndex, &c_setting, &c_nodeIndex, &c_level); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(c_level - (250/PSIperFT*MperFT)) < 1.e-5); // 250 PSI to M + + // Premise 1 - Demand GPM to LPS + error = EN_getpremise(ph, 1, 1, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (10/GPMperCFS*LPSperCFS)) < 1.e-5); //10 GPM to LPS + + // Premise 2 - Head FT to Meters + error = EN_getpremise(ph, 1, 2, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (20*MperFT)) < 1.e-5); //20 FT to M + + // Premise 3 - Pressure PSI to Meters + error = EN_getpremise(ph, 1, 3, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (30/PSIperFT*MperFT)) < 1.e-5); //30 PSI to M + + // Premise 4 - Level FT to Meters + error = EN_getpremise(ph, 1, 4, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (40*MperFT)) < 1.e-5); //40 FT to M + + // Premise 5 - Flow GPM to LPS + error = EN_getpremise(ph, 1, 5, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (50/GPMperCFS*LPSperCFS)) < 1.e-5); //50 GPM to LPS + + // Premise 6 - Setting PSI to Meters + error = EN_getpremise(ph, 1, 6, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (60/PSIperFT*MperFT)) < 1.e-5); //60 PSI to M + + // Premise 7 - Setting GPM to LPS + error = EN_getpremise(ph, 1, 7, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (70/GPMperCFS*LPSperCFS)) < 1.e-5); //70 GPM to LPS + + // ThenAction - Setting PSI to Meters + error = EN_getthenaction(ph, 1, 1, &r_objIndex, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (80/PSIperFT*MperFT)) < 1.e-5); //80 PSI to M + + // ElseAction - Setting GPM to LPS + error = EN_getelseaction(ph, 1, 1, &r_objIndex, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (90/GPMperCFS*LPSperCFS)) < 1.e-5); //90 GPM to LPS + + // Change pressure units to kPa + error = EN_setoption(ph, EN_PRESS_UNITS, EN_KPA); + BOOST_REQUIRE(error == 0); + + // Simple Control - 250 psi to kPa + error = EN_getcontrol(ph, c_index, &c_type, &c_linkIndex, &c_setting, &c_nodeIndex, &c_level); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(c_level - (250*KPAperPSI)) < 1.e-5); //250 PSI to kPa + + // Premise 3 - Pressure PSI to kPa + error = EN_getpremise(ph, 1, 3, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (30*KPAperPSI)) < 1.e-5); //30 PSI to kPa + + // Premise 6 - Setting PSI to kPa + error = EN_getpremise(ph, 1, 6, &r_logop, &r_object, &r_objIndex, &r_variable, &r_relop, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (60*KPAperPSI)) < 1.e-5); //60 PSI to kPa + + // ThenAction - Setting PSI to kPa + error = EN_getthenaction(ph, 1, 1, &r_objIndex, &r_status, &r_value); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(abs(r_value - (80*KPAperPSI)) < 1.e-5); //80 PSI to kPa + + +} + +BOOST_AUTO_TEST_SUITE_END()