diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml
index 4f163f3..dc121b5 100644
--- a/.github/workflows/ccpp.yml
+++ b/.github/workflows/ccpp.yml
@@ -1,6 +1,10 @@
-name: C/C++ CI
+name: linux
-on: [push]
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 0 1 * *'
jobs:
build:
@@ -17,3 +21,7 @@ jobs:
- name: make
working-directory: ./buildproducts
run: make
+ - uses: actions/upload-artifact@v2
+ with:
+ name: libepanet-output
+ path: /home/runner/work/EPANET/EPANET/buildproducts/
diff --git a/.github/workflows/win32.yml b/.github/workflows/win32.yml
new file mode 100644
index 0000000..dd673c8
--- /dev/null
+++ b/.github/workflows/win32.yml
@@ -0,0 +1,26 @@
+name: epanet2-win32
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 0 1 * *'
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: setup_build_dir
+ run: mkdir buildproducts
+ - name: cmake
+ working-directory: ./buildproducts
+ run: cmake .. -A Win32 && cmake --build . --config Release
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: epanet2-win32
+ path: D:\a\EPANET\EPANET\buildproducts\bin\Release\
diff --git a/.github/workflows/win64.yml b/.github/workflows/win64.yml
new file mode 100644
index 0000000..560a4a3
--- /dev/null
+++ b/.github/workflows/win64.yml
@@ -0,0 +1,26 @@
+name: epanet2-win64
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 0 1 * *'
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: setup_build_dir
+ run: mkdir buildproducts
+ - name: cmake
+ working-directory: ./buildproducts
+ run: cmake .. -A x64 && cmake --build . --config Release
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: epanet2-win64
+ path: D:\a\EPANET\EPANET\buildproducts\bin\Release\
diff --git a/README.md b/README.md
index 65fa407..af87356 100755
--- a/README.md
+++ b/README.md
@@ -3,10 +3,13 @@ OWA-EPANET
## Build Status
[](https://ci.appveyor.com/project/OpenWaterAnalytics/epanet)
-[](https://travis-ci.org/OpenWaterAnalytics/EPANET)
[](https://codecov.io/gh/OpenWaterAnalytics/EPANET)
+[](https://github.com/OpenWaterAnalytics/EPANET/actions/workflows/ccpp.yml)
+[](https://github.com/OpenWaterAnalytics/EPANET/actions/workflows/win32.yml)
+[](https://github.com/OpenWaterAnalytics/EPANET/actions/workflows/win64.yml)
+
## DESCRIPTION
**EPANET** is an industry-standard program for modeling the hydraulic and water quality behavior of water distribution system pipe networks. The EPANET Programmer's Toolkit is a library of functions (or API) written in C that allow programmers to customize the use of EPANET's solution engine for their own applications. Both EPANET and its toolkit were originally developed by the U.S. Environmental Protection Agency (USEPA). If you are interested in using/extending the EPANET engine and its API for academic, personal, or commercial use, then you've come to the right place. [Read more about EPANET on Wikipedia](https://en.wikipedia.org/wiki/EPANET). (Please note that this project covers only the EPANET hydraulic and water quality solver engine, not the graphical user interface.)
diff --git a/ReleaseNotes2_3.md b/ReleaseNotes2_3.md
index 3e3f924..5a3b2d3 100644
--- a/ReleaseNotes2_3.md
+++ b/ReleaseNotes2_3.md
@@ -22,6 +22,9 @@ This document describes the changes and updates that have been made in version 2
- changing the absolute tolerance used to compare the closeness of test results to benchmark values from 0 to 0.0001
- dropping the "correct decimal digits" test
- dropping the check for identical status report content since it prevents accepting code changes that produce more accurate solutions in fewer iterations.
+ - A possible loss of network connectivity when evaluating a Pressure Sustaining Valve was prevented.
+ - Having the implied loss coefficient for an active Flow Control Valve be less than its fully opened value was prevented.
+ - A new type of valve, a Positional Control Valve (PCV), was added that uses a valve characteristic curve to relate its loss coefficient to its fraction open setting.
diff --git a/conanfile.py b/conanfile.py
new file mode 100644
index 0000000..fb9db09
--- /dev/null
+++ b/conanfile.py
@@ -0,0 +1,45 @@
+from conans import ConanFile
+from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
+
+class EpanetConan(ConanFile):
+ name = "epanet"
+ version = "2.2"
+ description = "EPANET is an industry-standard program for modeling the hydraulic and water quality behavior of water distribution system pipe networks."
+ homepage = "https://github.com/OpenWaterAnalytics/EPANET"
+ url = "https://github.com/OpenWaterAnalytics/EPANET"
+ license = "MIT"
+
+ # Binary configuration
+ settings = "os", "compiler", "build_type", "arch"
+ options = {"shared": [True, False], "fPIC": [True, False]}
+ default_options = {"shared": False, "fPIC": True}
+
+ exports_sources = "CMakeLists.txt", "src/*", "include/*", "run/*"
+
+ def config_options(self):
+ if self.settings.os == "Windows":
+ del self.options.fPIC
+
+ def layout(self):
+ cmake_layout(self)
+
+ def generate(self):
+ tc = CMakeToolchain(self)
+ tc.generate()
+
+ def build(self):
+ cmake = CMake(self)
+ cmake.configure()
+ cmake.build()
+
+ def package(self):
+ self.copy("lib/libepanet2.dylib", "lib", keep_path=False)
+ self.copy("lib/libepanet-output.dylib", "lib", keep_path=False)
+ self.copy("*.h", "include", "include", keep_path=False)
+ self.copy("types.h", "include", "src", keep_path=False)
+ self.copy("hash.h", "include", "src", keep_path=False)
+
+ def package_info(self):
+ self.cpp_info.libdirs = ["lib"]
+ self.cpp_info.libs = ["epanet2", "epanet-output"]
+ self.cpp_info.includedirs = ["include"]
diff --git a/doc/input-file.dox b/doc/input-file.dox
index 8bbe5c1..f4d36ce 100644
--- a/doc/input-file.dox
+++ b/doc/input-file.dox
@@ -92,9 +92,10 @@ __Remarks:__
- Head v. Flow for pumps
- Efficiency v. Flow for pumps
- Volume v. Depth for tanks
+ - Fraction of fully open flow v. fraction open for Positional Control Valves
- Head Loss v. Flow for General Purpose Valves
2. The points of a curve must be entered in order of increasing X-values (lower to higher).
-3. If the input file will be used with the Windows version of EPANET, then adding a comment which contains the curve type and description, separated by a colon, directly above the first entry for a curve will ensure that these items appear correctly in EPANET's Curve Editor. Curve types include PUMP, EFFICIENCY, VOLUME, and HEADLOSS. See the examples below.
+3. If the input file will be used with the Windows version of EPANET, then adding a comment which contains the curve type and description, separated by a colon, directly above the first entry for a curve will ensure that these items appear correctly in EPANET's Curve Editor. Curve types include PUMP, EFFICIENCY, VOLUME, VALVE, and HEADLOSS. See the examples below.
__Example:__
```
@@ -1028,7 +1029,8 @@ One line for each valve containing:
- Diameter, inches (mm)
- Valve type
- Valve setting
-- Minor loss coefficient
+- Minor loss coefficient when fully open
+- ID of valve characteristic curve (PCVs only)
__Remarks:__
1. Valve types and settings include:
@@ -1038,9 +1040,14 @@ __Remarks:__
|PSV (pressure sustaining valve) | Pressure, psi (m) |
|PBV (pressure breaker valve) | Pressure, psi (m) |
|FCV (flow control valve) | Flow (flow units) |
-|TCV (throttle control valve) | Loss Coefficient |
+|TCV (throttle control valve) | Partially open loss coefficient |
+|PCV (positional control valve) | Fraction open |
|GPV (general purpose valve) | ID of head loss curve |
2. Shutoff valves and check valves are considered to be part of a pipe, not a separate control valve component (see @ref PipesPage).
+3. The loss coefficient setting for a TCV should not be less than its fully open loss coefficient.
+4. The characteristic curve for a PCV relates the valve's fraction of fully open flow to the fraction open. If not supplied then a linear curve is assumed.
+5. The partially opened loss coefficient for a PCV is the inverse of the squared value from its characteristic curve times the fully open loss coefficient.
+6. The head loss curve for a GPV relates head loss across the valve to the flow rate through it.
*/
/**
diff --git a/include/epanet.cs b/include/epanet.cs
index 2cec376..68489e6 100644
--- a/include/epanet.cs
+++ b/include/epanet.cs
@@ -73,6 +73,9 @@ namespace EpanetCSharpLibrary
public const int EN_PUMP_ECURVE = 20;
public const int EN_PUMP_ECOST = 21;
public const int EN_PUMP_EPAT = 22;
+ public const int EN_LINK_INCONTROL = 23;
+ public const int EN_GPV_CURVE = 24;
+ public const int EN_PCV_CURVE = 25;
public const int EN_DURATION = 0; //Time parameters
public const int EN_HYDSTEP = 1;
@@ -126,6 +129,7 @@ namespace EpanetCSharpLibrary
public const int EN_FCV = 6;
public const int EN_TCV = 7;
public const int EN_GPV = 8;
+ public const int EN_PCV = 9;
public const int EN_NONE = 0; //Quality analysis types
public const int EN_CHEM = 1;
@@ -209,6 +213,7 @@ namespace EpanetCSharpLibrary
public const int EN_EFFIC_CURVE = 2; //Efficiency curve
public const int EN_HLOSS_CURVE = 3; //Head loss curve
public const int EN_GENERIC_CURVE = 4; //Generic curve
+ public const int EN_VALVE_CURVE = 5; //Valve position curve
public const int EN_UNCONDITIONAL = 0; //Unconditional object deletion
public const int EN_CONDITIONAL = 1; //Conditional object deletion
diff --git a/include/epanet2.bas b/include/epanet2.bas
index c38003e..bc2ce1e 100644
--- a/include/epanet2.bas
+++ b/include/epanet2.bas
@@ -5,7 +5,7 @@ Attribute VB_Name = "Module1"
'Declarations of functions in the EPANET PROGRAMMERs TOOLKIT
'(EPANET2.DLL)
-'Last updated on 02/01/2020
+'Last updated on 07/28/2022
' These are codes used by the DLL functions
Public Const EN_ELEVATION = 0 ' Node parameters
@@ -63,6 +63,7 @@ Public Const EN_PUMP_ECOST = 21
Public Const EN_PUMP_EPAT = 22
Public Const EN_LINK_INCONTROL = 23
Public Const EN_GPV_CURVE = 24
+Public Const EN_PCV_CURVE= 25
Public Const EN_DURATION = 0 ' Time parameters
Public Const EN_HYDSTEP = 1
@@ -117,6 +118,7 @@ Public Const EN_PBV = 5
Public Const EN_FCV = 6
Public Const EN_TCV = 7
Public Const EN_GPV = 8
+Public Const EN_PCV = 9
Public Const EN_CLOSED = 0 ' Link status types
Public Const EN_OPEN = 1
@@ -209,6 +211,7 @@ Public Const EN_PUMP_CURVE = 1 ' Pump curve
Public Const EN_EFFIC_CURVE = 2 ' Efficiency curve
Public Const EN_HLOSS_CURVE = 3 ' Head loss curve
Public Const EN_GENERIC_CURVE = 4 ' Generic curve
+Public Const EN_VALVE_CURVE = 5 ' Valve position curve
Public Const EN_UNCONDITIONAL = 0 ' Unconditional object deletion
Public Const EN_CONDITIONAL = 1 ' Conditional object deletion
diff --git a/include/epanet2.h b/include/epanet2.h
index c8d023f..ec32e67 100644
--- a/include/epanet2.h
+++ b/include/epanet2.h
@@ -161,6 +161,10 @@ extern "C" {
int DLLEXPORT ENtimetonextevent(int *eventType, long *duration, int *elementIndex);
+ int DLLEXPORT ENsetreportcallback(void (*callback)(void *userData, void *EN_projectHandle, char*));
+ int DLLEXPORT ENsetreportcallbackuserdata(void *userData);
+
+
/********************************************************************
Analysis Options Functions
diff --git a/include/epanet2.pas b/include/epanet2.pas
index cbba41e..fba8147 100644
--- a/include/epanet2.pas
+++ b/include/epanet2.pas
@@ -3,7 +3,7 @@ unit epanet2;
{ Declarations of imported procedures from the EPANET PROGRAMMERs TOOLKIT }
{ (EPANET2.DLL) }
-{Last updated on 02/01/2020}
+{Last updated on 07/28/2022}
interface
@@ -69,6 +69,7 @@ const
EN_PUMP_EPAT = 22;
EN_LINK_INCONTROL = 23;
EN_GPV_CURVE = 24;
+ EN_PCV_CURVE = 25;
EN_DURATION = 0; { Time parameters }
EN_HYDSTEP = 1;
@@ -123,6 +124,7 @@ const
EN_FCV = 6;
EN_TCV = 7;
EN_GPV = 8;
+ EN_PCV = 9;
EN_CLOSED = 0; { Link status types }
EN_OPEN = 1;
@@ -214,7 +216,8 @@ const
EN_PUMP_CURVE = 1;
EN_EFFIC_CURVE = 2;
EN_HLOSS_CURVE = 3;
- EN_GENERIC_CURVE = 4;
+ EN_GENERIC_CURVE = 4;
+ EN_VALVE_CURVE = 5;
EN_UNCONDITIONAL = 0; { Deletion action codes }
EN_CONDITIONAL = 1;
diff --git a/include/epanet2.vb b/include/epanet2.vb
index bbd365e..7c283f0 100644
--- a/include/epanet2.vb
+++ b/include/epanet2.vb
@@ -4,7 +4,7 @@
'Declarations of functions in the EPANET PROGRAMMERs TOOLKIT
'(EPANET2.DLL) for use with VB.Net.
-'Last updated on 02/01/2020
+'Last updated on 07/28/2022
Imports System.Runtime.InteropServices
Imports System.Text
@@ -67,6 +67,7 @@ Public Const EN_PUMP_ECOST = 21
Public Const EN_PUMP_EPAT = 22
Public Const EN_LINK_INCONTROL = 23
Public Const EN_GPV_CURVE = 24
+Public Const EN_PCV_CURVE = 25
Public Const EN_DURATION = 0 ' Time parameters
Public Const EN_HYDSTEP = 1
@@ -120,6 +121,7 @@ Public Const EN_PBV = 5
Public Const EN_FCV = 6
Public Const EN_TCV = 7
Public Const EN_GPV = 8
+Public Const EN_PCV = 9
Public Const EN_NONE = 0 ' Quality analysis types
Public Const EN_CHEM = 1
@@ -203,6 +205,7 @@ Public Const EN_PUMP_CURVE = 1 ' Pump curve
Public Const EN_EFFIC_CURVE = 2 ' Efficiency curve
Public Const EN_HLOSS_CURVE = 3 ' Head loss curve
Public Const EN_GENERIC_CURVE = 4 ' Generic curve
+Public Const EN_VALVE_CURVE = 5 ' Valve position curve
Public Const EN_UNCONDITIONAL = 0 ' Unconditional object deletion
Public Const EN_CONDITIONAL = 1 ' Conditional object deletion
diff --git a/include/epanet2_2.h b/include/epanet2_2.h
index 4f49d1a..76dea18 100644
--- a/include/epanet2_2.h
+++ b/include/epanet2_2.h
@@ -517,6 +517,18 @@ typedef struct Project *EN_Project;
********************************************************************/
+ /**
+ @brief Set a user-supplied callback function for reporting
+ @param ph an EPANET project handle.
+ @param callback a function pointer with declared signature, which gets called by EPANET for reporting.
+ @return an error code.
+ @details The report callback function must have the signature specified - void(void* userData, EN_Project, char*) -
+ use the userData parameter to pass any client context necessary (a context pointer or wrapper object perhaps).
+ Leave un-set or set the report callback to NULL to revert to EPANET's default behavior.
+ **/
+ int DLLEXPORT EN_setreportcallback(EN_Project ph, void (*callback)(void *userData, void *EN_projectHandle, char*));
+ int DLLEXPORT EN_setreportcallbackuserdata(EN_Project ph, void *userData);
+
/**
@brief Writes a line of text to a project's report file.
@param ph an EPANET project handle.
diff --git a/include/epanet2_enums.h b/include/epanet2_enums.h
index e0aa9ae..8c4be41 100644
--- a/include/epanet2_enums.h
+++ b/include/epanet2_enums.h
@@ -9,7 +9,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 02/01/2020
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -97,7 +97,8 @@ typedef enum {
EN_PUMP_ECOST = 21, //!< Pump average energy price
EN_PUMP_EPAT = 22, //!< Pump energy price time pattern index
EN_LINK_INCONTROL = 23, //!< Is present in any simple or rule-based control (= 1) or not (= 0)
- EN_GPV_CURVE = 24 //!< GPV head loss v. flow curve index
+ EN_GPV_CURVE = 24, //!< GPV head loss v. flow curve index
+ EN_PCV_CURVE = 25 //!< PCV loss coeff. curve index
} EN_LinkProperty;
/// Time parameters
@@ -203,7 +204,8 @@ typedef enum {
EN_PBV = 5, //!< Pressure breaker valve
EN_FCV = 6, //!< Flow control valve
EN_TCV = 7, //!< Throttle control valve
- EN_GPV = 8 //!< General purpose valve
+ EN_GPV = 8, //!< General purpose valve
+ EN_PCV = 9 //!< Positional control valve
} EN_LinkType;
/// Link status
@@ -409,7 +411,8 @@ typedef enum {
EN_PUMP_CURVE = 1, //!< Pump head v. flow curve
EN_EFFIC_CURVE = 2, //!< Pump efficiency v. flow curve
EN_HLOSS_CURVE = 3, //!< Valve head loss v. flow curve
- EN_GENERIC_CURVE = 4 //!< Generic curve
+ EN_GENERIC_CURVE = 4, //!< Generic curve
+ EN_VALVE_CURVE = 5 //!< Valve loss coeff. v. frac. open
} EN_CurveType;
/// Deletion action codes
diff --git a/src/enumstxt.h b/src/enumstxt.h
index fdb9311..ccc12e8 100755
--- a/src/enumstxt.h
+++ b/src/enumstxt.h
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 06/20/2019
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -27,7 +27,8 @@ char *LinkTxt[] = {w_CV,
w_PBV,
w_FCV,
w_TCV,
- w_GPV};
+ w_GPV,
+ w_PCV};
char *StatTxt[] = {t_XHEAD,
t_TEMPCLOSED,
diff --git a/src/epanet.c b/src/epanet.c
index fb3861e..a5c83ac 100644
--- a/src/epanet.c
+++ b/src/epanet.c
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 11/08/2020
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -841,6 +841,19 @@ int DLLEXPORT EN_closeQ(EN_Project p)
********************************************************************/
+
+ int DLLEXPORT EN_setreportcallback(EN_Project p, void (*callback)(void*,void*,char*))
+ {
+ p->report.reportCallback = callback;
+ return 0;
+ }
+
+ int DLLEXPORT EN_setreportcallbackuserdata(EN_Project p, void *userData)
+ {
+ p->report.reportCallbackUserData = userData;
+ return 0;
+ }
+
int DLLEXPORT EN_writeline(EN_Project p, char *line)
/*----------------------------------------------------------------
** Input: line = line of text
@@ -3184,7 +3197,7 @@ int DLLEXPORT EN_addlink(EN_Project p, char *id, int linkType,
if (EN_getlinkindex(p, id, &i) == 0) return 215;
// Check for valid link type
- if (linkType < CVPIPE || linkType > GPV) return 251;
+ if (linkType < CVPIPE || linkType > PCV) return 251;
// Lookup the link's from and to nodes
n1 = hashtable_find(net->NodeHashTable, fromNode);
@@ -3244,6 +3257,7 @@ int DLLEXPORT EN_addlink(EN_Project p, char *id, int linkType,
size = (net->Nvalves + 1) * sizeof(Svalve);
net->Valve = (Svalve *)realloc(net->Valve, size);
net->Valve[net->Nvalves].Link = n;
+ net->Valve[net->Nvalves].Curve = 0;
}
link->Type = linkType;
@@ -3503,7 +3517,7 @@ int DLLEXPORT EN_setlinktype(EN_Project p, int *index, int linkType, int actionC
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
// Check for valid input parameters
- if (linkType < 0 || linkType > GPV || actionCode < EN_UNCONDITIONAL ||
+ if (linkType < 0 || linkType > PCV || actionCode < EN_UNCONDITIONAL ||
actionCode > EN_CONDITIONAL)
{
return 251;
@@ -3828,7 +3842,14 @@ int DLLEXPORT EN_getlinkvalue(EN_Project p, int index, int property, double *val
v = (double)Pump[findpump(&p->network, index)].Epat;
}
break;
-
+
+ case EN_PCV_CURVE:
+ if (Link[index].Type == PCV)
+ {
+ v = net->Valve[findvalve(&p->network, index)].Curve;
+ }
+ break;
+
case EN_GPV_CURVE:
if (Link[index].Type == GPV)
{
@@ -3951,6 +3972,7 @@ int DLLEXPORT EN_setlinkvalue(EN_Project p, int index, int property, double valu
value /= Ucf[FLOW];
break;
case TCV:
+ case PCV:
break;
case GPV:
return 207; // Cannot modify setting for GPV
@@ -4046,6 +4068,15 @@ int DLLEXPORT EN_setlinkvalue(EN_Project p, int index, int property, double valu
net->Pump[pumpIndex].Epat = patIndex;
}
break;
+
+ case EN_PCV_CURVE:
+ if (Link[index].Type == PCV)
+ {
+ curveIndex = ROUND(value);
+ if (curveIndex < 0 || curveIndex > net->Ncurves) return 206;
+ net->Valve[findvalve(&p->network, index)].Curve = curveIndex;
+ }
+ break;
case EN_GPV_CURVE:
if (Link[index].Type == GPV)
diff --git a/src/epanet2.c b/src/epanet2.c
index 3534d70..be16f6a 100644
--- a/src/epanet2.c
+++ b/src/epanet2.c
@@ -212,6 +212,16 @@ int DLLEXPORT ENsetstatusreport(int level)
return EN_setstatusreport(_defaultProject, level);
}
+int DLLEXPORT ENsetreportcallback(void (*callback)(void *userData, void *EN_projectHandle, char*))
+{
+ return EN_setreportcallback(_defaultProject, callback);
+}
+
+int DLLEXPORT ENsetreportcallbackuserdata(void *userData)
+{
+ return EN_setreportcallbackuserdata(_defaultProject, userData);
+}
+
int DLLEXPORT ENgetversion(int *version) { return EN_getversion(version); }
int DLLEXPORT ENgeterror(int errcode, char *errmsg, int maxLen)
diff --git a/src/funcs.h b/src/funcs.h
index 1e6be71..69d3f81 100755
--- a/src/funcs.h
+++ b/src/funcs.h
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 02/03/2020
+ Last Updated: 08/13/2022
******************************************************************************
*/
#ifndef FUNCS_H
@@ -167,6 +167,7 @@ void headlosscoeffs(Project *);
void matrixcoeffs(Project *);
void emitterheadloss(Project *, int, double *, double *);
void demandheadloss(Project *, int, double, double, double *, double *);
+double pcvlosscoeff(Project *, int, double);
// ------- QUALITY.C --------------------
diff --git a/src/hydcoeffs.c b/src/hydcoeffs.c
index 73c38a1..b45f032 100644
--- a/src/hydcoeffs.c
+++ b/src/hydcoeffs.c
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 03/30/2022
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -36,6 +36,7 @@ const double CBIG = 1.e8;
// Exported functions
//void resistcoeff(Project *, int );
+//double pcvlosscoeff(Project *, int, double);
//void headlosscoeffs(Project *);
//void matrixcoeffs(Project *);
//void emitterheadloss(Project *, int, double *, double *);
@@ -59,6 +60,7 @@ static void valvecoeff(Project *pr, int k);
static void gpvcoeff(Project *pr, int k);
static void pbvcoeff(Project *pr, int k);
static void tcvcoeff(Project *pr, int k);
+static void pcvcoeff(Project *pr, int k);
static void prvcoeff(Project *pr, int k, int n1, int n2);
static void psvcoeff(Project *pr, int k, int n1, int n2);
static void fcvcoeff(Project *pr, int k, int n1, int n2);
@@ -107,6 +109,10 @@ void resistcoeff(Project *pr, int k)
case PUMP:
link->R = CBIG;
break;
+
+ case PCV:
+ link->R = pcvlosscoeff(pr, k, link->Kc);
+ break;
// ... For all other links (e.g. valves) use a small resistance
default:
@@ -116,6 +122,86 @@ void resistcoeff(Project *pr, int k)
}
+double pcvlosscoeff(Project* pr, int k, double s)
+/*
+**--------------------------------------------------------------
+** Input: k = link index
+** s = valve fraction open setting
+** Output: returns a valve loss coefficient
+** Purpose: finds a Positional Control Valve's loss
+** coefficient from its fraction open setting.
+**--------------------------------------------------------------
+*/
+{
+ Network* net = &pr->network;
+
+ int v = findvalve(net, k); // valve index
+ int c = net->Valve[v].Curve; // Kv curve index
+ double d; // valve diameter
+ double kmo; // fully open loss coeff.
+ double km; // partly open loss coeff.
+ double kvr; // Kv / Kvo (Kvo = Kv at fully open)
+ double *x, *y; // points on kvr v. frac. open curve
+ int k1, k2, npts;
+ Scurve *curve;
+
+ // Valve has no setting so return 0
+ if (s == MISSING) return 0.0;
+
+ // Valve is completely open so return its Km value
+ d = net->Link[k].Diam;
+ kmo = net->Link[k].Km;
+ if (s >= 1.0) return kmo;
+
+ // Valve is completely closed so return a large coeff.
+ if (s <= 0.0) return CBIG;
+
+ // Valve has no assigned curve so assume a linear one
+ if (c == 0) kvr = s;
+
+ else
+ {
+ // Valve curve data
+ curve = &net->Curve[c];
+ npts = curve->Npts;
+ x = curve->X; // x = frac. open
+ y = curve->Y; // y = Kv / Kvo
+
+ // s lies below first point of curve
+ if (s < x[0])
+ kvr = s / x[0] * y[0];
+
+ // s lies above last point of curve
+ else if (s > x[npts-1])
+ {
+ k2 = npts - 1;
+ kvr = (s - x[k2]) / (1. - x[k2]) * (1. - y[k2]) + y[k2];
+ }
+
+ // Otherwise interpolate over curve segment that brackets s
+ else
+ {
+ k2 = 0;
+ while (k2 < npts && x[k2] < s) k2++;
+ if (k2 == 0) k2++;
+ else if (k2 == npts) k2--;
+ k1 = k2 - 1;
+ kvr = (y[k2] - y[k1]) / (x[k2] - x[k1]);
+ kvr = y[k1] + kvr * (s - x[k1]);
+ }
+ }
+
+ // kvr can't be > 1 or <= 0
+ kvr = MIN(kvr, 1.0);
+ kvr = MAX(kvr, CSMALL);
+
+ // Convert from Kv ratio to minor loss coeff.
+ km = kmo / (kvr * kvr);
+ km = MIN(km, CBIG);
+ return km;
+}
+
+
void headlosscoeffs(Project *pr)
/*
**--------------------------------------------------------------
@@ -148,6 +234,9 @@ void headlosscoeffs(Project *pr)
case TCV:
tcvcoeff(pr, k);
break;
+ case PCV:
+ pcvcoeff(pr, k);
+ break;
case GPV:
gpvcoeff(pr, k);
break;
@@ -945,6 +1034,36 @@ void tcvcoeff(Project *pr, int k)
}
+void pcvcoeff(Project *pr, int k)
+/*
+**--------------------------------------------------------------
+** Input: k = link index
+** Output: none
+** Purpose: computes P & Y coeffs. for positional control valve
+**--------------------------------------------------------------
+*/
+{
+ double km;
+ Hydraul *hyd = &pr->hydraul;
+ Slink *link = &pr->network.Link[k];
+
+ // Save original loss coeff. for open valve
+ km = link->Km;
+
+ // If valve not fixed OPEN or CLOSED, compute its loss coeff.
+ if (hyd->LinkSetting[k] != MISSING)
+ {
+ link->Km = link->R;
+ }
+
+ // Then apply usual valve formula
+ valvecoeff(pr, k);
+
+ // Restore original loss coeff.
+ link->Km = km;
+}
+
+
void prvcoeff(Project *pr, int k, int n1, int n2)
/*
**--------------------------------------------------------------
@@ -1035,6 +1154,8 @@ void psvcoeff(Project *pr, int k, int n1, int n2)
{
sm->F[j] += hyd->Xflow[n1];
}
+ sm->Aij[sm->Ndx[k]] -= 1.0 / CBIG; // Preserve connectivity
+ sm->Aii[j] += 1.0 / CBIG;
return;
}
diff --git a/src/hydraul.c b/src/hydraul.c
index fecc9f8..d6e4424 100755
--- a/src/hydraul.c
+++ b/src/hydraul.c
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 03/19/2022
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -449,6 +449,7 @@ void setlinksetting(Project *pr, int index, double value, StatusType *s,
else
{
if (*k == MISSING && *s <= CLOSED) *s = OPEN;
+ if (t == PCV) link->R = pcvlosscoeff(pr, index, link->Kc);
*k = value;
}
}
@@ -604,6 +605,7 @@ int controls(Project *pr)
{
hyd->LinkStatus[k] = s2;
hyd->LinkSetting[k] = k2;
+ if (link->Type == PCV) link->R = pcvlosscoeff(pr, k, k2);
if (pr->report.Statflag) writecontrolaction(pr,k,i);
setsum++;
}
diff --git a/src/hydstatus.c b/src/hydstatus.c
index 4013ec9..f3cae46 100644
--- a/src/hydstatus.c
+++ b/src/hydstatus.c
@@ -7,7 +7,7 @@ Description: updates hydraulic status of network elements
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
-Last Updated: 05/15/2019
+Last Updated: 08/08/2022
******************************************************************************
*/
@@ -394,6 +394,15 @@ StatusType fcvstatus(Project *pr, int k, StatusType s, double h1, double h2)
{
status = ACTIVE;
}
+
+ // Active valve's loss coeff. can't be < fully open loss coeff.
+ else if (status == ACTIVE)
+ {
+ if ((h1 - h2) / SQR(hyd->LinkFlow[k]) < pr->network.Link[k].Km)
+ {
+ status = XFCV;
+ }
+ }
return status;
}
diff --git a/src/inpfile.c b/src/inpfile.c
index bc5d4e6..c1fcc48 100644
--- a/src/inpfile.c
+++ b/src/inpfile.c
@@ -7,7 +7,7 @@ Description: saves network data to an EPANET formatted text file
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
-Last Updated: 10/29/2019
+Last Updated: 08/13/2022
******************************************************************************
*/
@@ -312,6 +312,11 @@ int saveinpfile(Project *pr, const char *fname)
{
sprintf(s1, "%-31s %12.4f", net->Curve[j].ID, km);
}
+ // For PCV add loss curve if present
+ else if (link->Type == PCV && (j = net->Valve[i].Curve) > 0)
+ {
+ sprintf(s1, "%12.4f %12.4f %-31s", kc, km, net->Curve[j].ID);
+ }
else sprintf(s1, "%12.4f %12.4f", kc, km);
fprintf(f, "\n%s %s", s, s1);
if (link->Comment) fprintf(f, " ;%s", link->Comment);
diff --git a/src/input3.c b/src/input3.c
index c32eaf7..9534b35 100644
--- a/src/input3.c
+++ b/src/input3.c
@@ -7,7 +7,7 @@ Description: parses network data from a line of an EPANET input file
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
-Last Updated: 03/20/2022
+Last Updated: 08/13/2022
******************************************************************************
*/
@@ -471,7 +471,7 @@ int valvedata(Project *pr)
** Purpose: processes valve data
** Format:
** [VALVE]
-** id node1 node2 diam type setting (lcoeff)
+** id node1 node2 diam type setting (lcoeff lcurve)
**--------------------------------------------------------------
*/
{
@@ -488,7 +488,8 @@ int valvedata(Project *pr)
setting, // Valve setting
lcoeff = 0.0; // Minor loss coeff.
Slink *link;
- int err = 0;
+ int err = 0,
+ losscurve = 0; // Loss coeff. curve
// Add new valve to data base
n = parser->Ntokens;
@@ -521,6 +522,8 @@ int valvedata(Project *pr)
type = TCV;
else if (match(parser->Tok[4], w_GPV))
type = GPV;
+ else if (match(parser->Tok[4], w_PCV))
+ type = PCV;
else
return setError(parser, 4, 213);
@@ -538,6 +541,16 @@ int valvedata(Project *pr)
}
else if (!getfloat(parser->Tok[5], &setting)) return setError(parser, 5, 202);
if (n >= 7 && !getfloat(parser->Tok[6], &lcoeff)) return setError(parser, 6, 202);
+
+ // Find loss coeff. curve for PCV
+ if (type == PCV && n >= 8)
+ {
+ c = findcurve(net, parser->Tok[7]);
+ if (c == 0) return setError(parser, 7, 206);
+ losscurve = c;
+ net->Curve[c].Type = VALVE_CURVE;
+ if (setting > 1.0) setting = 1.0;
+ }
// Check for illegal connections
if (valvecheck(pr, net->Nlinks, type, j1, j2))
@@ -563,6 +576,7 @@ int valvedata(Project *pr)
link->ResultIndex = 0;
link->Comment = xstrcpy(&link->Comment, parser->Comment, MAXMSG);
net->Valve[net->Nvalves].Link = net->Nlinks;
+ net->Valve[net->Nvalves].Curve = losscurve;
return 0;
}
diff --git a/src/output.c b/src/output.c
index 1e9a6ab..51685e2 100644
--- a/src/output.c
+++ b/src/output.c
@@ -7,7 +7,7 @@ Description: binary file read/write routines
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
-Last Updated: 05/13/2019
+Last Updated: 08/13/2022
******************************************************************************
*/
@@ -567,6 +567,7 @@ int linkoutput(Project *pr, int j, REAL4 *x, double ucf)
case FCV:
x[i] = (REAL4)(setting * pr->Ucf[FLOW]); break;
case TCV:
+ case PCV:
x[i] = (REAL4)setting; break;
default: x[i] = 0.0f;
}
diff --git a/src/project.c b/src/project.c
index b470ec3..2aefa21 100644
--- a/src/project.c
+++ b/src/project.c
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 02/03/2020
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -281,6 +281,8 @@ void initpointers(Project *pr)
pr->hydraul.smatrix.NZSUB = NULL;
pr->hydraul.smatrix.LNZ = NULL;
+ pr->report.reportCallback = NULL;
+
initrules(pr);
}
@@ -860,7 +862,7 @@ int findtank(Network *network, int index)
/*----------------------------------------------------------------
** Input: index = node index
** Output: none
-** Returns: index of tank with given node id, or NOTFOUND if tank not found
+** Returns: index of tank with given node index, or NOTFOUND if tank not found
** Purpose: for use in the deletenode function
**----------------------------------------------------------------
*/
@@ -877,7 +879,7 @@ int findpump(Network *network, int index)
/*----------------------------------------------------------------
** Input: index = link ID
** Output: none
-** Returns: index of pump with given link id, or NOTFOUND if pump not found
+** Returns: index of pump with given link index, or NOTFOUND if pump not found
** Purpose: for use in the deletelink function
**----------------------------------------------------------------
*/
@@ -894,7 +896,7 @@ int findvalve(Network *network, int index)
/*----------------------------------------------------------------
** Input: index = link ID
** Output: none
-** Returns: index of valve with given link id, or NOTFOUND if valve not found
+** Returns: index of valve with given link index, or NOTFOUND if valve not found
** Purpose: for use in the deletelink function
**----------------------------------------------------------------
*/
@@ -1010,7 +1012,7 @@ void adjustcurves(Network *network, int index)
**----------------------------------------------------------------
*/
{
- int j, k, setting;
+ int j, k, curve;
// Adjust tank volume curves
for (j = 1; j <= network->Ntanks; j++)
@@ -1025,15 +1027,25 @@ void adjustcurves(Network *network, int index)
adjustcurve(&network->Pump[j].Ecurve, index);
}
- // Adjust GPV curves
+ // Adjust PCV & GPV curves
for (j = 1; j <= network->Nvalves; j++)
{
k = network->Valve[j].Link;
+ if (network->Link[k].Type == PCV)
+ {
+ if ((curve = network->Valve[j].Curve) > 0)
+ {
+ adjustcurve(&curve, index);
+ network->Valve[j].Curve = curve;
+ if (curve == 0)
+ network->Link[k].Kc = 0.0;
+ }
+ }
if (network->Link[k].Type == GPV)
{
- setting = INT(network->Link[k].Kc);
- adjustcurve(&setting, index);
- network->Link[k].Kc = setting;
+ curve = INT(network->Link[k].Kc);
+ adjustcurve(&curve, index);
+ network->Link[k].Kc = curve;
}
}
}
diff --git a/src/report.c b/src/report.c
index 8fc6d16..2922332 100644
--- a/src/report.c
+++ b/src/report.c
@@ -885,6 +885,12 @@ void writeline(Project *pr, char *s)
**--------------------------------------------------------------
*/
{
+ if (pr->report.reportCallback != NULL)
+ {
+ pr->report.reportCallback(pr->report.reportCallbackUserData, pr, s);
+ return;
+ }
+
Report *rpt = &pr->report;
if (rpt->RptFile == NULL) return;
diff --git a/src/text.h b/src/text.h
index a4ac84b..72fef68 100755
--- a/src/text.h
+++ b/src/text.h
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 07/15/2019
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -40,6 +40,7 @@
#define w_FCV "FCV"
#define w_TCV "TCV"
#define w_GPV "GPV"
+#define w_PCV "PCV"
#define w_OPEN "OPEN"
#define w_CLOSED "CLOSED"
diff --git a/src/types.h b/src/types.h
index b0e91b5..e719c6b 100755
--- a/src/types.h
+++ b/src/types.h
@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
- Last Updated: 07/11/2020
+ Last Updated: 08/13/2022
******************************************************************************
*/
@@ -145,7 +145,8 @@ typedef enum {
PBV, // pressure breaker valve
FCV, // flow control valve
TCV, // throttle control valve
- GPV // general purpose valve
+ GPV, // general purpose valve
+ PCV // positional control valve
} LinkType;
typedef enum {
@@ -166,7 +167,8 @@ typedef enum {
PUMP_CURVE, // pump curve
EFFIC_CURVE, // efficiency curve
HLOSS_CURVE, // head loss curve
- GENERIC_CURVE // generic curve
+ GENERIC_CURVE, // generic curve
+ VALVE_CURVE // positional valve loss curve
} CurveType;
typedef enum {
@@ -455,6 +457,7 @@ typedef struct // Pump Object
typedef struct // Valve Object
{
int Link; // link index of valve
+ int Curve; // positional loss coeff. curve
} Svalve;
typedef struct // Control Statement
@@ -629,7 +632,10 @@ typedef struct {
Rpt2Fname[MAXFNAME+1], // Secondary report file name
DateStamp[26]; // Current date & time
- SField Field[MAXVAR]; // Output reporting fields
+ SField Field[MAXVAR]; // Output reporting fields
+
+ void (*reportCallback)(void*,void*,char*); // user-supplied reporting callback
+ void *reportCallbackUserData; // user-supplied reporting context
} Report;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 995d01f..946e9f6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -39,6 +39,7 @@ set(toolkit_test_srcs
test_control.cpp
test_overflow.cpp
test_pda.cpp
+ test_valve.cpp
)
add_executable(test_toolkit ${toolkit_test_srcs})
diff --git a/tests/test_valve.cpp b/tests/test_valve.cpp
new file mode 100644
index 0000000..a4c3c89
--- /dev/null
+++ b/tests/test_valve.cpp
@@ -0,0 +1,73 @@
+/*
+ ******************************************************************************
+ Project: OWA EPANET
+ Version: 2.2
+ Module: test_valve.cpp
+ Description: Tests EPANET toolkit api functions
+ Authors: see AUTHORS
+ Copyright: see AUTHORS
+ License: see LICENSE
+ Last Updated: 07/28/2022
+ ******************************************************************************
+*/
+
+/*
+ Tests PCV valve with position curve
+*/
+
+#include
+
+#include "test_toolkit.hpp"
+
+BOOST_AUTO_TEST_SUITE (test_valve)
+
+BOOST_FIXTURE_TEST_CASE(test_PCV_valve, FixtureOpenClose)
+
+{
+ int npts = 5;
+ double x[] = { 0.0, 0.25, 0.5, 0.75, 1. };
+ double y[] = {0.0, 0.089, 0.184, 0.406, 1.0};
+ double v;
+ int linkIndex, curveIndex;
+
+ // Make steady state run
+ error = EN_settimeparam(ph, EN_DURATION, 0);
+ BOOST_REQUIRE(error == 0);
+
+ // Convert pipe 22 to a PCV
+ error = EN_getlinkindex(ph, (char*)"22", &linkIndex);
+ BOOST_REQUIRE(error == 0);
+ error = EN_setlinktype(ph, &linkIndex, EN_PCV, EN_UNCONDITIONAL);
+ BOOST_REQUIRE(error == 0);
+ error = EN_setlinkvalue(ph, linkIndex, EN_DIAMETER, 12);
+ BOOST_REQUIRE(error == 0);
+ error = EN_setlinkvalue(ph, linkIndex, EN_MINORLOSS, 0.19);
+
+ // Create the PCV's position-loss curve
+ error = EN_addcurve(ph, (char*)"ValveCurve");
+ BOOST_REQUIRE(error == 0);
+ error = EN_getcurveindex(ph, (char*)"ValveCurve", &curveIndex);
+ BOOST_REQUIRE(error == 0);
+ error = EN_setcurve(ph, curveIndex, x, y, npts);
+ BOOST_REQUIRE(error == 0);
+
+ // Assign curve & initial setting to PCV
+ error = EN_setlinkvalue(ph, linkIndex, EN_PCV_CURVE, curveIndex);
+ BOOST_REQUIRE(error == 0);
+ error = EN_setlinkvalue(ph, linkIndex, EN_INITSETTING, 0.35);
+ BOOST_REQUIRE(error == 0);
+
+ // Solve for hydraulics
+ error = EN_solveH(ph);
+ BOOST_REQUIRE(error == 0);
+
+ // The PCV interpolated relative flow coeff. at 0.35 open is 0.127.
+ // This translates to a minor loss coeff. of 0.19 / 0.127^2 = 11.78.
+ // If the PCV were replaced with a TCV at that setting the resulting
+ // head loss would be 0.0255 ft which should equal the PCV result.
+ error = EN_getlinkvalue(ph, linkIndex, EN_HEADLOSS, &v);
+ BOOST_REQUIRE(error == 0);
+ BOOST_REQUIRE(abs(v - 0.0255) < 0.001);
+}
+
+BOOST_AUTO_TEST_SUITE_END()