diff --git a/include/epanet2_enums.h b/include/epanet2_enums.h index 58870ec..f7b5cb2 100644 --- a/include/epanet2_enums.h +++ b/include/epanet2_enums.h @@ -61,7 +61,8 @@ typedef enum { EN_MIXFRACTION = 22, //!< Tank mixing fraction EN_TANK_KBULK = 23, //!< Tank bulk decay coefficient EN_TANKVOLUME = 24, //!< Current computed tank volume (read only) - EN_MAXVOLUME = 25 //!< Tank maximum volume (read only) + EN_MAXVOLUME = 25, //!< Tank maximum volume (read only) + EN_CANOVERFLOW = 26 //!< Tank can overflow (= 1) or not (= 0) } EN_NodeProperty; /// Link properties diff --git a/src/epanet.c b/src/epanet.c index 409069f..44ed46b 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -1802,6 +1802,7 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index) tank->Vcurve = 0; tank->MixModel = 0; tank->V1max = 10000; + tank->CanOverflow = FALSE; } net->Nnodes++; p->parser.MaxNodes = net->Nnodes; @@ -2197,6 +2198,11 @@ int DLLEXPORT EN_getnodevalue(EN_Project p, int index, int property, double *val v = tankvolume(p, index - nJuncs, NodeHead[index]) * Ucf[VOLUME]; break; + case EN_CANOVERFLOW: + if (Node[index].Type != TANK) return 0; + v = Tank[index - nJuncs].CanOverflow; + break; + default: return 251; } @@ -2495,6 +2501,11 @@ int DLLEXPORT EN_setnodevalue(EN_Project p, int index, int property, double valu } break; + case EN_CANOVERFLOW: + if (Node[index].Type != TANK) return 0; + Tank[index - nJuncs].CanOverflow = (value != 0.0); + break; + default: return 251; } diff --git a/src/hydstatus.c b/src/hydstatus.c index 0dd2da1..87d45c6 100644 --- a/src/hydstatus.c +++ b/src/hydstatus.c @@ -441,7 +441,7 @@ void tankstatus(Project *pr, int k, int n1, int n2) h = hyd->NodeHead[n1] - hyd->NodeHead[n2]; // If tank is full, then prevent flow into it - if (hyd->NodeHead[n1] >= tank->Hmax - hyd->Htol) + if (hyd->NodeHead[n1] >= tank->Hmax - hyd->Htol && !tank->CanOverflow) { // Case 1: Link is a pump discharging into tank if (link->Type == PUMP) diff --git a/src/inpfile.c b/src/inpfile.c index f357ddd..7060119 100644 --- a/src/inpfile.c +++ b/src/inpfile.c @@ -195,8 +195,10 @@ int saveinpfile(Project *pr, const char *fname) sqrt(4.0 * tank->A / PI) * pr->Ucf[ELEV], tank->Vmin * SQR(pr->Ucf[ELEV]) * pr->Ucf[ELEV]); if ((j = tank->Vcurve) > 0) sprintf(s1, "%s", net->Curve[j].ID); + else if (tank->CanOverflow) strcpy(s1, "*"); else strcpy(s1, " "); fprintf(f, "\n%s %-31s", s, s1); + if (tank->CanOverflow) fprintf(f, " YES "); if (node->Comment) fprintf(f, " ;%s", node->Comment); } } diff --git a/src/input3.c b/src/input3.c index 116b419..0f20388 100644 --- a/src/input3.c +++ b/src/input3.c @@ -138,7 +138,8 @@ int tankdata(Project *pr) int i, // Node index n, // # data items pattern = 0, // Time pattern index - curve = 0; // Curve index + curve = 0, // Curve index + overflow = FALSE;// Overflow indicator double el = 0.0, // Elevation initlevel = 0.0, // Initial level minlevel = 0.0, // Minimum level @@ -185,12 +186,20 @@ int tankdata(Project *pr) if (n >= 7 && !getfloat(parser->Tok[6], &minvol)) return setError(parser, 6, 202); // If volume curve supplied check it exists - if (n == 8) + if (n >= 8) { - curve = findcurve(net, parser->Tok[7]); - if (curve == 0) return setError(parser, 7, 206); - net->Curve[curve].Type = VOLUME_CURVE; + if (strlen(parser->Tok[7]) > 0 && *(parser->Tok[7]) != '*') + { + curve = findcurve(net, parser->Tok[7]); + if (curve == 0) return setError(parser, 7, 206); + net->Curve[curve].Type = VOLUME_CURVE; + } } + + // Parse overflow indicator if present + if (n >= 9 && match(parser->Tok[8], w_YES)) + overflow = TRUE; + if (initlevel < 0.0) return setError(parser, 2, 209); if (minlevel < 0.0) return setError(parser, 3, 209); if (maxlevel < 0.0) return setError(parser, 4, 209); @@ -216,6 +225,7 @@ int tankdata(Project *pr) tank->A = diam; tank->Pat = pattern; tank->Kb = MISSING; + tank->CanOverflow = overflow; //******************************************************************* // NOTE: The min, max, & initial volumes set here are based on a diff --git a/src/types.h b/src/types.h index 6072d54..fe7142e 100755 --- a/src/types.h +++ b/src/types.h @@ -417,6 +417,7 @@ typedef struct // Tank Object int Vcurve; // volume v. elev. curve index MixType MixModel; // type of mixing model double V1max; // mixing compartment size + int CanOverflow; // tank can overflow or not } Stank; typedef struct // Pump Object diff --git a/tests/test_overflow.cpp b/tests/test_overflow.cpp new file mode 100644 index 0000000..290303b --- /dev/null +++ b/tests/test_overflow.cpp @@ -0,0 +1,119 @@ +/* + ****************************************************************************** + Project: OWA EPANET + Version: 2.2 + Module: test_overflow.cpp + Description: Tests EPANET toolkit api functions + Authors: see AUTHORS + Copyright: see AUTHORS + License: see LICENSE + Last Updated: 06/16/2019 + ****************************************************************************** +*/ + +/* + Tests the EN_CANOVERFLOW option for Tank nodes +*/ + +#include + +#include "test_toolkit.hpp" + +BOOST_AUTO_TEST_SUITE (test_overflow) + +BOOST_AUTO_TEST_CASE(test_tank_overflow) + +{ + int error = 0; + int Nindex, Lindex; + double level, spillage, spillage2, inflow; + char testFile[] = "test_overflow.inp"; + + EN_Project ph = NULL; + + error = EN_createproject(&ph); + error = EN_open(ph, DATA_PATH_NET1, DATA_PATH_RPT, ""); + + // Get index of the tank and its inlet/outlet pipe + error = EN_getnodeindex(ph, (char *)"2", &Nindex); + BOOST_REQUIRE(error == 0); + error = EN_getlinkindex(ph, (char *)"110", &Lindex); + BOOST_REQUIRE(error == 0); + + // Set initial & maximum level to 130 + error = EN_setnodevalue(ph, Nindex, EN_TANKLEVEL, 130); + BOOST_REQUIRE(error == 0); + error = EN_setnodevalue(ph, Nindex, EN_MAXLEVEL, 130); + BOOST_REQUIRE(error == 0); + + // Set duration to 1 hr + error = EN_settimeparam(ph, EN_DURATION, 3600); + BOOST_REQUIRE(error == 0); + + // Solve hydraulics with default of no tank spillage allowed + error = EN_solveH(ph); + BOOST_REQUIRE(error == 0); + + // Check that tank remains full + error = EN_getnodevalue(ph, Nindex, EN_TANKLEVEL, &level); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(abs(level - 130.0) < 0.0001); + + // Check that there is no spillage + error = EN_getnodevalue(ph, Nindex, EN_DEMAND, &spillage); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(abs(spillage) < 0.0001); + + // Check that inflow link is closed + error = EN_getlinkvalue(ph, Lindex, EN_FLOW, &inflow); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(abs(inflow) < 0.0001); + + // Turn tank overflow option on + error = EN_setnodevalue(ph, Nindex, EN_CANOVERFLOW, 1); + BOOST_REQUIRE(error == 0); + + // Solve hydraulics again + error = EN_solveH(ph); + BOOST_REQUIRE(error == 0); + + // Check that tank remains full + error = EN_getnodevalue(ph, Nindex, EN_TANKLEVEL, &level); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(abs(level - 130.0) < 0.0001); + + // Check that there is spillage equal to tank inflow + // (inflow has neg. sign since tank is start node of inflow pipe) + error = EN_getnodevalue(ph, Nindex, EN_DEMAND, &spillage); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(spillage > 0.0001); + error = EN_getlinkvalue(ph, Lindex, EN_FLOW, &inflow); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(abs(-inflow - spillage) < 0.0001); + + // Save project to file and then close it + error = EN_saveinpfile(ph, testFile); + BOOST_REQUIRE(error == 0); + error = EN_close(ph); + BOOST_REQUIRE(error == 0); + + // Re-open saved file & run it + error = EN_open(ph, testFile, DATA_PATH_RPT, ""); + BOOST_REQUIRE(error == 0); + error = EN_solveH(ph); + BOOST_REQUIRE(error == 0); + + // Check that tank spillage has same value as before + error = EN_getnodevalue(ph, Nindex, EN_DEMAND, &spillage2); + BOOST_REQUIRE(error == 0); + BOOST_REQUIRE(abs(spillage - spillage2) < 0.0001); + + // Clean up + error = EN_close(ph); + BOOST_REQUIRE(error == 0); + error = EN_deleteproject(&ph); + BOOST_REQUIRE(error == 0); + +} + +BOOST_AUTO_TEST_SUITE_END()