diff --git a/ReleaseNotes2_2.md b/ReleaseNotes2_2.md index 9494f74..c560bbb 100644 --- a/ReleaseNotes2_2.md +++ b/ReleaseNotes2_2.md @@ -38,22 +38,35 @@ Prototypes of the thread-safe functions appear in the `epanet2_2.h` header file API users now have the ability to build a complete EPANET network model using just function calls, without the need to open an EPANET-formatted input file. All types of network objects can be created and have their properties set using these calls, including both simple and rule-based controls. Here is an example of building a simple 2-node, 1-pipe network just through code: ``` #include "epanet2_2.h" -int buildandrunEpanet(char *frpt) +int buildandrunEpanet(char *rptfile) { + // Create and initialize a project using gpm for flow + // units and the Hazen-Williams formula for head loss EN_Project ph = 0; - int err; + int err, index; err = EN_createproject(&ph); if (err) return err; - EN_init(ph, frpt, "", EN_GPM, EN_HW); - EN_addnode(ph, "J1, EN_JUNCTION); - EN_setjuncdata(ph, 1, 710, 500, ""); //elev, demand - EN_addnode(ph, "R1", EN_RESERVOIR); - EN_setnodevalue(ph, 2, EN_ELEVATION, 800); - EN_addlink(ph, "P1", EN_PIPE, "R1", "J1"); - EN_setpipedata(ph, 1, 5280, 14, 100, 0); // length, diam, C-factor + EN_init(ph, rptfile, "", EN_GPM, EN_HW); + + //Add a junction node with 710 ft elevation and 500 gpm demand + EN_addnode(ph, "J1", EN_JUNCTION, &index); + EN_setjuncdata(ph, index, 710, 500, ""); + + // Add a reservoir node at 800 ft elevation + EN_addnode(ph, "R1", EN_RESERVOIR, &index); + EN_setnodevalue(ph, index, EN_ELEVATION, 800); + + // Add a 5280 ft long, 14-inch pipe with C-factor of 100 + // from the reservoir to the demand node + EN_addlink(ph, "P1", EN_PIPE, "R1", "J1", &index); + EN_setpipedata(ph, index, 5280, 14, 100, 0); + + // Solve for hydraulics and report nodal results EN_setreport(ph, "NODES ALL"); err = EN_solveH(ph); if (!err) err = EN_report(ph); + + // Close and delete the project EN_close(ph); EN_deleteproject(&ph); return err; @@ -81,26 +94,26 @@ EPANET's original node re-ordering scheme has been replaced by the more powerful EPANET's hydraulic solver can generate an ill-conditioned solution matrix when pipe flows approach zero unless some adjustment is made (i.e., as a pipe's flow approaches 0 its head loss gradient also approaches 0 causing its reciprocal, which is used to form the solution matrix's coefficients, to approach infinity). EPANET 2.0 used an arbitrary cutoff on head loss gradient to prevent it from becoming 0. This approach doesn't allow a pipe to follow any head loss v. flow relation in the region below the cutoff and can produce incorrect solutions for some networks (see [Estrada et al., 2009](https://ascelibrary.org/doi/full/10.1061/%28ASCE%29IR.1943-4774.0000100)). -The hydraulic solver has been modified to use a linear head loss v. flow relation for flows approaching zero. For the Darcy-Weisbach equation, the linear Hagen-Poiseuille formula is used for laminar flow where the Reynolds Number is <= 2000. For the Hazen-Williams and Chezy-Manning equations, a flow limit `Qa` is established for each pipe, equal to the flow that produces the EPANET 2 gradient cutoff. For flows below this a linear head loss relation is used between 0 and the head loss at `Qa` and the gradient always equals the cutoff. EPANET 2.2 is now able to correctly solve the examples presented in Estrada et al. (2009) as well as those in [Gorev et al., (2013)](https://ascelibrary.org/doi/10.1061/%28ASCE%29HY.1943-7900.0000694) and [Elhay and Simpson (2011)](https://ascelibrary.org/doi/10.1061/%28ASCE%29HY.1943-7900.0000411). +The hydraulic solver has been modified to use a linear head loss v. flow relation for flows approaching zero. For the Darcy-Weisbach equation, the linear Hagen-Poiseuille formula is used for laminar flow where the Reynolds Number is <= 2000. For the Hazen-Williams and Chezy-Manning equations, a flow limit is established for each pipe, equal to the flow that produces the EPANET 2 gradient cutoff. For flows below this a linear head loss relation is used whose gradient always equals the cutoff. EPANET 2.2 is now able to correctly solve the examples presented in Estrada et al. (2009) as well as those in [Gorev et al., (2013)](https://ascelibrary.org/doi/10.1061/%28ASCE%29HY.1943-7900.0000694) and [Elhay and Simpson (2011)](https://ascelibrary.org/doi/10.1061/%28ASCE%29HY.1943-7900.0000411). ## Pressure Dependent Demands EPANET has always employed a Demand Driven Analysis (**DDA**) when modeling network hydraulics. Under this approach nodal demands at a given point in time are fixed values that must be delivered no matter what nodal heads and link flows are produced by a hydraulic solution. This can result in situations where required demands are satisfied at nodes that have negative pressures - a physical impossibility. -To address this issue EPANET has been extended to use a Pressure Driven Analysis (**PDA**) if so desired. Under **PDA**, the demand *D* delivered at a node depends on the node's available pressure *P* according to: +To address this issue EPANET has been extended to use a Pressure Driven Analysis (**PDA**) if so desired. Under **PDA**, the demand D delivered at a node depends on the node's available pressure P according to: -*D = Df [ (P - Pmin) / (Preq - Pmin) ]Pexp* +D = Dfull * [ (P - Pmin) / (Preq - Pmin) ]^Pexp -where *Df* is the full demand required, *Pmin* is the pressure below which demand is zero, *Preq* is the pressure required to deliver the full required demand and *Pexp* is an exponent. When *P < Pmin* demand is 0 and when *P > Preq* demand equals *Df*. +where Dfull is the full demand required, Pmin is the pressure below which demand is zero, Preq is the pressure required to deliver the full required demand and Pexp is an exponent. When P < Pmin demand is 0 and when P > Preq demand equals Dfull. To implement pressure driven analysis four new parameters have been added to the [OPTIONS] section of the EPANET input file: | Parameter | Description | Default | -|--|--|--| +|-----------|--------------|---------| | DEMAND MODEL | either DDA or PDA | DDA | -| MINIMUM PRESSURE | value for *Pmin* | 0 -| REQUIRED PRESSURE | value for *Preq* | 0 -| PRESSURE EXPONENT | value for *Pexp* | 0.5 | +| MINIMUM PRESSURE | value for Pmin | 0 +| REQUIRED PRESSURE | value for Preq | 0 +| PRESSURE EXPONENT | value for Pexp | 0.5 | These parameters can also be set and retrieved in code using the following API functions ``` @@ -116,30 +129,19 @@ for the thread-safe API. Some additional points regarding the new **PDA** option - If no DEMAND MODEL and its parameters are specified then the analysis defaults to being demand driven (**DDA**). - This implementation of **PDA** assumes that the same parameters apply to all nodes in the network. Extending the framework to allow different parameters for specific nodes is left as a future feature to implement. - - *Pmin* is allowed to equal to *Preq*. This condition can be used to find a solution that results in the smallest amount of demand reductions needed to insure that no node delivers positive demand at a pressure below *Pmin*. + - Pmin is allowed to equal to Preq. This condition can be used to find a solution that results in the smallest amount of demand reductions needed to insure that no node delivers positive demand at a pressure below Pmin. ## Improved Water Quality Mass Balance As described by [Davis et al. (2018)](https://www.drink-water-eng-sci.net/11/25/2018/dwes-11-25-2018.pdf) EPANET's water quality simulations can result in some significant mass balance errors when modeling short term mass injections (errors are much smaller for continuous source flows). The entire water quality engine has been re-written to eliminate these errors. It still uses the Lagrangian Time Driven transport method but now analyzes each network node in topologically sorted order rather than in arbitrary order. -A Mass Balance Report now appears the end of a simulation's Status Report that lists the various components (inflow, outflow, reaction) that comprise the network's overall mass balance. In addition `EN_MASSBALANCE` can be used as a parameter in the `ENgetstatistic` (or `EN_getstatistic`) function to retrieve the Mass Balance Ratio (Total Outflow Mass / Total Inflow Mass) at any point during a water quality simulation. +A Mass Balance Report now appears the end of a simulation's Status Report that lists the various components (inflow, outflow, reaction) that comprise the network's overall mass balance. In addition `EN_MASSBALANCE` can be used as a parameter in the `EN_getstatistic` (or `ENgetstatistic`) function to retrieve the Mass Balance Ratio (Total Outflow Mass / Total Inflow Mass) at any point during a water quality simulation. -Mass balance ratio (MBR) results for two of the networks analyzed by Davis et al. (2018) are shown in the following table. MBR-2.0 is for EPANET 2.0.012 as reported by Davis et al. while MBR-2.2 is for the re-written quality engine. - -| Network | Time Step (s) | MBR-2.0 | MBR-2.2 | -|--|--|--|--| -| N2 | 900 | 16.63 | 1.00 | -| | 300 | 23.45 | 1.00 | -| | 60 | 6.49 | 1.00 | -| N4 | 900 | 0.09 | 1.00 | -| | 300 | 0.70 | 1.00 | -| | 60 | 0.98 | 1.00 | - -Both network files are available [here](https://doi.org/10.23719/1375314). +With this change EPANET 2.2 now produces perfect mass balances when tested against the networks used in Davis et al. (2018). ## New API functions |Function|Description| -|--|--| +|--------|-----------| |`EN_createproject` | Creates a new EPANET project | |`EN_deleteproject` | Deletes an EPANET project | |`EN_init`|Initializes an EPANET project| @@ -163,6 +165,9 @@ Both network files are available [here](https://doi.org/10.23719/1375314). |`EN_setpipedata`|Sets values for a pipe's parameters| |`EN_getdemandmodel`|Retrieves the type of demand model in use | |`EN_setdemandmodel`|Sets the type of demand model to use| +|`EN_adddemand`|Adds a new demand category to a node| +|`EN_deletedemand`|Deletes a demand category from a node| +|`EN_getdemandindex`|Finds a demand category's index given its name| |`EN_getdemandname`|Gets the name of a node's demand category| |`EN_setdemandname`|Sets the name of a node's demand category| |`EN_setdemandpattern`|Assigns a time pattern to a node's demand category | @@ -170,7 +175,7 @@ Both network files are available [here](https://doi.org/10.23719/1375314). |`EN_setcurveid`|Changes the ID name of a data curve| |`EN_getcurvetype`|Gets a curve's type| |`EN_setheadcurveindex`|Sets the index of a head curve used by a pump | -|`EN_getrule`|Gets the number of elements in a rule-based control | +|`EN_getruleinfo`|Gets the number of elements in a rule-based control | |`EN_getruleid` | Gets the name assigned to a rule-based control | |`EN_getpremise`|Gets the contents of a premise in a rule-based control| |`EN_setpremise`|Sets the contents of a premise in a rule-based control| @@ -184,6 +189,8 @@ Both network files are available [here](https://doi.org/10.23719/1375314). |`EN_setrulepriority`|Sets the priority of a rule-based control| |`EN_gettitle` |Gets a project's title | |`EN_settitle` |Sets a project's title | +|`EN_getcomment` |Gets the descriptive comment assigned to an object| +|`EN_setcomment` |Assigns a descriptive comment to an object| |`EN_clearreport` |Clears the contents of a project's report file | |`EN_copyreport` | Copies the contents of a project's report file | In addition to these new functions, a tank's volume curve `EN_VOLCURVE` can be set using `EN_setnodevalue` and `EN_setlinkvalue` can now be used to set the following pump properties: @@ -222,13 +229,25 @@ Access to the following global energy options have been added to `EN_getoption` ### Hydraulic option types: - `EN_HEADERROR` - `EN_FLOWCHANGE` - - `EN_DEFDEMANDPAT` - `EN_HEADLOSSFORM` - `EN_GLOBALEFFIC` - `EN_GLOBALPRICE` - `EN_GLOBALPATTERN` - `EN_DEMANDCHARGE` + - `EN_SP_GRAVITY` + - `EN_SP_VISCOS` + - `EN_EXTRA_ITER` + - `EN_CHECKFREQ` + - `EN_MAXCHECK` + - `EN_DAMPLIMIT` +### Quality option types: +- `EN_SP_DIFFUS` + - `EN_BULKORDER` + - `EN_WALLORDER` + - `EN_TANKORDER` + - `EN_CONCENLIMIT` + ### Simulation statistic types: - `EN_MAXHEADERROR` - `EN_MAXFLOWCHANGE` @@ -248,6 +267,9 @@ Access to the following global energy options have been added to `EN_getoption` ### Demand model types: - `EN_DDA` - `EN_PDA` - + +## Documentation +Doxygen files have been created to generate a complete Users Guide for version 2.2's API. The guide's format is similar to the original EPANET Programmer's Toolkit help file and can be produced as a set of HTML pages, a Windows help file or a PDF document. + ## Authors contributing to this release: - List item diff --git a/doc/modules.dox b/doc/modules.dox index 6a990be..29b12e5 100644 --- a/doc/modules.dox +++ b/doc/modules.dox @@ -162,6 +162,9 @@ These are symbolic constants used as function arguments. @{ @fn int EN_getdemandmodel(EN_Project ph, int *type, double *pmin, double *preq, double *pexp) @fn int EN_setdemandmodel(EN_Project ph, int type, double pmin, double preq, double pexp) +@fn int EN_adddemand(EN_Project ph, int nodeIndex, double baseDemand, char *demandPattern, char *demandName) +@fn int EN_deletedemand(EN_Project ph, int nodeIndex, int demandIndex) +@fn int EN_getdemandindex(EN_Project p, int nodeIndex, char *demandName, int *demandIndex) @fn int EN_getnumdemands(EN_Project ph, int nodeIndex, int *numDemands) @fn int EN_getbasedemand(EN_Project ph, int nodeIndex, int demandIndex, double *baseDemand) @fn int EN_setbasedemand(EN_Project ph, int nodeIndex, int demandIndex, double baseDemand) @@ -376,6 +379,7 @@ When the Toolkit function @ref EN_solveH is used to make a hydraulic analysis, r | 254 | Function call refers to node with no coordinates | | 257 | Function call refers to nonexistent rule | | 258 | Function call refers to nonexistent rule clause | +| 259 | Function call attempts to delete a node that still has links connected to it | | 260 | Function call attempts to delete node assigned as a Trace Node | | 261 | Function call attempts to delete a node or link contained in a control | | 262 | Function call attempts to modify network structure while a solver is open | diff --git a/include/epanet2.bas b/include/epanet2.bas index 11d8312..db8c7af 100644 --- a/include/epanet2.bas +++ b/include/epanet2.bas @@ -307,6 +307,9 @@ Public Const EN_R_IS_ACTIVE = 3 'Nodal Demand Functions Declare Function ENgetdemandmodel Lib "epanet2.dll" (type_ As Long, pmin As Single, preq As Single, pexp As Single) As Long Declare Function ENsetdemandmodel Lib "epanet2.dll" (ByVal type_ As Long, ByVal pmin As Single, ByVal preq As Single, ByVal pexp As Single) As Long + Declare Function ENadddemand Lib "epanet2.dll" (ByVal nodeIndex As Long, ByVal baseDemand As Single, ByVal patternName As String, ByVal demandName As String) As Long + Declare Function ENdeletedemand Lib "epanet2.dll" (ByVal nodeIndex As Long, ByVal demandIndex As Long) As Long + Declare Function ENgetdemandindex Lib "epanet2.dll" (ByVal nodeIndex As Long, ByVal demandName As String, demandIndex As Long) As Long Declare Function ENgetnumdemands Lib "epanet2.dll" (ByVal nodeIndex As Long, numDemands As Long) As Long Declare Function ENgetbasedemand Lib "epanet2.dll" (ByVal nodeIndex As Long, ByVal demandIndex As Long, value As Single) As Long Declare Function ENsetbasedemand Lib "epanet2.dll" (ByVal nodeIndex As Long, ByVal demandIndex As Long, ByVal BaseDemand As Single) As Long diff --git a/include/epanet2.def b/include/epanet2.def index ebd08f9..b813d20 100644 --- a/include/epanet2.def +++ b/include/epanet2.def @@ -5,6 +5,7 @@ EXPORTS ENaddcurve = _ENaddcurve@4 ENaddlink = _ENaddlink@20 ENaddnode = _ENaddnode@12 + ENadddemand = _ENadddemand@16 ENaddpattern = _ENaddpattern@4 ENaddrule = _ENaddrule@4 ENclearreport = _ENclearreport@0 @@ -14,6 +15,7 @@ EXPORTS ENcopyreport = _ENcopyreport@4 ENdeletecontrol = _ENdeletecontrol@4 ENdeletecurve = _ENdeletecurve@4 + ENdeletedemand = _ENdeletedemand@8 ENdeletelink = _ENdeletelink@8 ENdeletenode = _ENdeletenode@8 ENdeletepattern = _ENdeletepattern@4 @@ -30,7 +32,8 @@ EXPORTS ENgetcurveindex = _ENgetcurveindex@8 ENgetcurvelen = _ENgetcurvelen@8 ENgetcurvetype = _ENgetcurvetype@8 - ENgetcurvevalue = _ENgetcurvevalue@16 + ENgetcurvevalue = _ENgetcurvevalue@16 + ENgetdemandindex = _ENgetdemandindex@12 ENgetdemandmodel = _ENgetdemandmodel@16 ENgetdemandname = _ENgetdemandname@12 ENgetdemandpattern = _ENgetdemandpattern@12 diff --git a/include/epanet2.h b/include/epanet2.h index 837d12c..d759c8d 100644 --- a/include/epanet2.h +++ b/include/epanet2.h @@ -230,8 +230,15 @@ extern "C" { int DLLEXPORT ENsetdemandmodel(int model, EN_API_FLOAT_TYPE pmin, EN_API_FLOAT_TYPE preq, EN_API_FLOAT_TYPE pexp); + int DLLEXPORT ENadddemand(int nodeIndex, EN_API_FLOAT_TYPE baseDemand, + char *demandPattern, char *demandName); + + int DLLEXPORT ENdeletedemand(int nodeIndex, int demandIndex); + int DLLEXPORT ENgetnumdemands(int nodeIndex, int *numDemands); + int DLLEXPORT ENgetdemandindex(int nodeIndex, char *demandName, int *demandIndex); + int DLLEXPORT ENgetbasedemand(int nodeIndex, int demandIndex, EN_API_FLOAT_TYPE *baseDemand); diff --git a/include/epanet2.vb b/include/epanet2.vb index 70132f1..9eca671 100644 --- a/include/epanet2.vb +++ b/include/epanet2.vb @@ -312,6 +312,9 @@ Public Const EN_R_IS_ACTIVE = 3 'Nodal Demand Functions Declare Function ENgetdemandmodel Lib "epanet2.dll" (type_ As Int32, pmin As Single, preq As Single, pexp As Single) As Int32 Declare Function ENsetdemandmodel Lib "epanet2.dll" (ByVal type_ As Int32, ByVal pmin As Single, ByVal preq As Single, ByVal pexp As Single) As Int32 + Declare Function ENadddemand Lib "epanet2.dll" (ByVal nodeIndex As Int32, ByVal baseDemand As Single, ByVal patternName As String, ByVal demandName As String) As Int32 + Declare Function ENdeletedemand Lib "epanet2.dll" (ByVal nodeIndex As Int32, ByVal demandIndex As Int32) As Int32 + Declare Function ENgetdemandindex Lib "epanet2.dll" (ByVal nodeIndex As Int32, ByVal demandName As String, demandIndex As Int32) As Int32 Declare Function ENgetnumdemands Lib "epanet2.dll" (ByVal nodeIndex As Int32, numDemands As Int32) As Int32 Declare Function ENgetbasedemand Lib "epanet2.dll" (ByVal nodeIndex As Int32, ByVal demandIndex As Int32, value As Single) As Int32 Declare Function ENsetbasedemand Lib "epanet2.dll" (ByVal nodeIndex As Int32, ByVal demandIndex As Int32, ByVal BaseDemand As Single) As Int32 diff --git a/include/epanet2_2.h b/include/epanet2_2.h index 67bf91a..1ddd535 100644 --- a/include/epanet2_2.h +++ b/include/epanet2_2.h @@ -853,13 +853,6 @@ typedef struct Project *EN_Project; int DLLEXPORT EN_setjuncdata(EN_Project ph, int index, double elev, double dmnd, char *dmndpat); - - int DLLEXPORT EN_adddemand(EN_Project p, int node_index, double demand, - char *demand_pattern, const char *category_name, int *demand_index); - - int DLLEXPORT EN_removedemand(EN_Project p, int node_index, int demand_index); - - /** @brief Sets a group of properties for a tank node. @param ph an EPANET project handle. @@ -940,6 +933,42 @@ typedef struct Project *EN_Project; int DLLEXPORT EN_setdemandmodel(EN_Project ph, int type, double pmin, double preq, double pexp); + + /** + @brief appends a new demand to a junction node demands list. + @param ph an EPANET project handle. + @param nodeIndex the index of a node (starting from 1). + @param baseDemand the demand's base value. + @param demandPattern the name of a time pattern used by the demand + @param demandName the name of the demand's category + @return an error code. + + A NULL or blank string can be used for `demandPattern` and for `demandName` to indicate + that no time pattern or category name is associated with the demand. + */ + int DLLEXPORT EN_adddemand(EN_Project ph, int nodeIndex, double baseDemand, + char *demandPattern, char *demandName); + + /** + @brief deletes a demand from a junction node. + @param ph an EPANET project handle. + @param nodeIndex the index of a node (starting from 1). + @param demandIndex the position of the demand in the node's demands list (starting from 1). + @return an error code. + */ + int DLLEXPORT EN_deletedemand(EN_Project ph, int nodeIndex, int demandIndex); + + /** + @brief Retrieves the index of a node's named demand category + @param ph an EPANET project handle. + @param nodeIndex the index of a node (starting from 1) + @param demandName the name of a demand category for the node + @param[out] demandIndex the index of the demand being sought + @return an error code + */ + int DLLEXPORT EN_getdemandindex(EN_Project ph, int nodeIndex, char *demandName, + int *demandIndex); + /** @brief Retrieves the number of demand categories for a junction node. @param ph an EPANET project handle. diff --git a/src/demand.c b/src/demand.c deleted file mode 100644 index 6f005c4..0000000 --- a/src/demand.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - ****************************************************************************** - Project: OWA EPANET - Version: 2.2 - Module: demand.c - Description: data for demand pattern list - Authors: see AUTHORS - Copyright: see AUTHORS - License: see LICENSE - Last Updated: 04/12/2019 - ****************************************************************************** -*/ - -#ifdef _DEBUG - #define _CRTDBG_MAP_ALLOC - #include - #include -#else - #include -#endif - -#include - -#include "demand.h" - - -typedef struct demand_data_s -{ - double base_demand; - int pattern_index; - char *category_name; -} demand_data_t; - - - -list_t *create_demand_list(double base_demand, int pattern_index, const char *category_name) -{ - list_t *demand_list; - demand_data_t *demand_data; - - demand_list = create_list(get_demand_data_size(), delete_demand_data); - if (!demand_list) return NULL; - - demand_data = create_demand_data(base_demand, pattern_index, category_name); - if (!demand_data) return NULL; - - append_list(demand_list, &demand_data); - - return demand_list; -} - - -demand_data_t *create_demand_data(double base_demand, int pattern_index, const char *category_name) -{ - demand_data_t *demand_data = (demand_data_t *)malloc(sizeof(demand_data_t)); - - demand_data->base_demand = base_demand; - demand_data->pattern_index = pattern_index; - - if (category_name) - demand_data->category_name = strdup(category_name); - else - demand_data->category_name = NULL; - - return demand_data; -} - -void delete_demand_data(void *data) -{ - demand_data_t *demand_data = *(demand_data_t **)data; - - if (demand_data->category_name) - free(demand_data->category_name); - - free(demand_data); -} - -size_t get_demand_data_size(void) -{ - return sizeof(demand_data_t *); -} - -demand_data_t *get_demand_data(list_node_t *lnode) -{ - return *(demand_data_t **)get_data(lnode); -} - - -bool convert_units(list_node_t *lnode, double unit_conversion) -{ - double base_demand = get_base_demand(lnode); - - set_base_demand(lnode, base_demand/unit_conversion); - return true; -} - - -double get_base_demand(list_node_t *lnode) -{ - return get_demand_data(lnode)->base_demand; -} - -void set_base_demand(list_node_t *lnode, double base_demand) -{ - get_demand_data(lnode)->base_demand = base_demand; -} - -int get_pattern_index(list_node_t *lnode) -{ - return get_demand_data(lnode)->pattern_index; -} - -void set_pattern_index(list_node_t *lnode, int pattern_index) -{ - get_demand_data(lnode)->pattern_index = pattern_index; -} - -char *get_category_name(list_node_t *lnode) -// Be advised: caller must free memory returned -{ - char *temp = get_demand_data(lnode)->category_name; - - if (temp) - return strdup(temp); - else - return NULL; -} - -void set_category_name(list_node_t *lnode, const char *category_name) -{ - free(get_demand_data(lnode)->category_name); - get_demand_data(lnode)->category_name = strdup(category_name); -} diff --git a/src/demand.h b/src/demand.h deleted file mode 100644 index 72dd3d8..0000000 --- a/src/demand.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - ****************************************************************************** - Project: OWA EPANET - Version: 2.2 - Module: demand.h - Description: data for demand pattern list - Authors: see AUTHORS - Copyright: see AUTHORS - License: see LICENSE - Last Updated: 04/12/2019 - ****************************************************************************** -*/ - -#ifndef DEMAND_H -#define DEMAND_H - - -#include "util/list.h" - - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declarations -typedef struct demand_data_s demand_data_t; - -// demand list gets declared in types.h struct Snode - - -list_t *create_demand_list(double base_demand, int pattern_index, const char *category_name); - - -demand_data_t *create_demand_data(double base_demand, int pat_index, const char *cat_name); - -void delete_demand_data(void *data); - -size_t get_demand_data_size(void); - - -bool convert_units(list_node_t *lnode, double unit_conversion); - - -double get_base_demand(list_node_t *lnode); -void set_base_demand(list_node_t *lnode, double base_demand); - -int get_pattern_index(list_node_t *lnode); -void set_pattern_index(list_node_t *lnode, int pattern_index); - -char *get_category_name(list_node_t *lnode); -void set_category_name(list_node_t *lnode, const char *category_name); - -// Make this private? -demand_data_t *get_demand_data(list_node_t *lnode); - - -#ifdef __cplusplus -} -#endif - - -#endif /* DEMAND_H */ diff --git a/src/epanet.c b/src/epanet.c index a2d86df..607fc74 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -20,7 +20,6 @@ #endif #include #include - #include #include @@ -30,8 +29,6 @@ #include "text.h" #include "enumstxt.h" -#include "demand.h" - #ifdef _WIN32 #define snprintf _snprintf #endif @@ -1721,8 +1718,7 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index) Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; - int i, nIdx; - int size; + int i, nIdx, size; Stank *tank; Snode *node; Scontrol *control; @@ -1752,8 +1748,8 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index) net->Njuncs++; nIdx = net->Njuncs; node = &net->Node[nIdx]; - - node->D = NULL; + node->D = NULL; + adddemand(node, 0.0, 0, NULL); // shift rest of Node array for (i = net->Nnodes; i >= net->Njuncs; i--) @@ -1766,21 +1762,18 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index) { net->Tank[i].Node += 1; } - // shift indices of Links, if necessary for (i = 1; i <= net->Nlinks; i++) { if (net->Link[i].N1 > net->Njuncs - 1) net->Link[i].N1 += 1; if (net->Link[i].N2 > net->Njuncs - 1) net->Link[i].N2 += 1; } - // shift indices of tanks/reservoir nodes in controls for (i = 1; i <= net->Ncontrols; ++i) { control = &net->Control[i]; if (control->Node > net->Njuncs - 1) control->Node += 1; } - // adjust indices of tanks/reservoirs in Rule premises (see RULES.C) adjusttankrules(p); } @@ -1854,7 +1847,6 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) int i, nodeType, tankindex; Snode *node; - list_t *demand; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; @@ -1886,10 +1878,7 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) hashtable_delete(net->NodeHashTable, node->ID); // Free memory allocated to node's demands, WQ source & comment - demand = node->D; - if (demand) - delete_list(demand); - + freedemands(node); free(node->S); free(node->Comment); @@ -2081,25 +2070,18 @@ int DLLEXPORT EN_getnodevalue(EN_Project p, int index, int property, double *val break; case EN_BASEDEMAND: - v = 0.0; // NOTE: primary demand category is first on demand list if (index <= nJuncs) { - list_t *demand = Node[index].D; - if (demand) - v = get_base_demand(head_list(demand, false)); + if (Node[index].D) v = Node[index].D->Base * Ucf[FLOW]; } - v *= Ucf[FLOW]; break; case EN_PATTERN: - v = 0.0; // NOTE: primary demand category is first on demand list if (index <= nJuncs) { - list_t *demand = Node[index].D; - if (demand) - v = get_pattern_index(head_list(demand, false)); + if (Node[index].D) v = (double)(Node[index].D->Pat); } else v = (double)(Tank[index - nJuncs].Pat); break; @@ -2280,9 +2262,7 @@ int DLLEXPORT EN_setnodevalue(EN_Project p, int index, int property, double valu // NOTE: primary demand category is first on demand list if (index <= nJuncs) { - list_t *demand = Node[index].D; - if (demand) - set_base_demand(head_list(demand, false), value / Ucf[FLOW]); + if (Node[index].D) Node[index].D->Base = value / Ucf[FLOW]; } break; @@ -2292,9 +2272,7 @@ int DLLEXPORT EN_setnodevalue(EN_Project p, int index, int property, double valu if (j < 0 || j > nPats) return 205; if (index <= nJuncs) { - list_t *demand = Node[index].D; - if (demand) - set_pattern_index(head_list(demand, false), j); + if (Node[index].D) Node[index].D->Pat = j; } else Tank[index - nJuncs].Pat = j; break; @@ -2542,81 +2520,33 @@ int DLLEXPORT EN_setjuncdata(EN_Project p, int index, double elev, **---------------------------------------------------------------- */ { - Network *net = &p->network; - - int i, patIndex = 0; - Snode *Node = net->Node; + int patIndex = 0; + Snode *node; // Check that junction exists if (!p->Openflag) return 102; - if (index <= 0 || index > net->Njuncs) return 203; + if (index <= 0 || index > p->network.Njuncs) return 203; // Check that demand pattern exists - if (strlen(dmndpat) > 0) + if (dmndpat && strlen(dmndpat) > 0) { - for (i = 1; i <= net->Npats; i++) - { - if (strcmp(dmndpat, net->Pattern[i].ID) == 0) - { - patIndex = i; - break; - } - } - if (patIndex == 0) return 205; + if (EN_getpatternindex(p, dmndpat, &patIndex) > 0) return 205; } - // Assign values to junction's parameters - Node[index].El = elev / p->Ucf[ELEV]; - - list_t *demand_list = Node[index].D; - if (!demand_list) { - demand_list = create_list(get_demand_data_size(), delete_demand_data); - if (!demand_list) return 101; + // Assign demand parameters to junction's primary demand category + node = &(p->network.Node[index]); + dmnd /= p->Ucf[FLOW]; + // Category exists - update its properties + if (node->D) + { + (node->D)->Base = dmnd; + (node->D)->Pat = patIndex; } - demand_data_t *demand_data = create_demand_data(dmnd/p->Ucf[FLOW], patIndex, NULL); - if (!demand_data) return 101; - - append_list(demand_list, &demand_data); - - return 0; -} - -int DLLEXPORT EN_adddemand(EN_Project p, int node_index, double demand, - char *demand_pattern, const char *category_name, int *demand_key) -{ - Network *net = &p->network; - - int pattern_index, error = 0; - *demand_key = -1; - - if (error = EN_getpatternindex(p, demand_pattern, &pattern_index) != 0) return error; - - Snode *Node = net->Node; - list_t *demand_list = Node[node_index].D; - if (!demand_list) { - demand_list = create_demand_list(demand/p->Ucf[FLOW], pattern_index, category_name); - if (!demand_list) return 101; - - Node[node_index].D = demand_list; - } - else { - demand_data_t *demand_data = create_demand_data(demand/p->Ucf[FLOW], pattern_index, category_name); - if (!demand_data) return 101; - - *demand_key = append_list(demand_list, &demand_data); - } - return 0; -} - -int DLLEXPORT EN_removedemand(EN_Project p, int node_index, int demand_key) -{ - Network *net = &p->network; - Snode *Node = net->Node; - - list_t *dlist = Node[node_index].D; - - remove_node(dlist, demand_key); + // No demand categories exist -- create a new one + else if (!adddemand(node, dmnd, patIndex, NULL)) return 101; + // Assign new elevation value to junction + node->El = elev / p->Ucf[ELEV]; return 0; } @@ -2793,6 +2723,138 @@ int DLLEXPORT EN_setdemandmodel(EN_Project p, int model, double pmin, return 0; } +int DLLEXPORT EN_adddemand(EN_Project p, int nodeIndex, double baseDemand, + char *demandPattern, char *demandName) +/*---------------------------------------------------------------- +** Input: nodeIndex = node index +** baseDemand = baseline demand value +** demandPattern = name of demand's time pattern (can be NULL or empty) +** demandName = name of demand's category (can be NULL or empty) +** Returns: error code +** Purpose: adds a new demand category to a junction node +**---------------------------------------------------------------- +*/ +{ + int patIndex = 0; + Snode *node; + + // Check for valid arguments + if (!p->Openflag) return 102; + if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; + if (demandPattern && strlen(demandPattern) > 0) + { + if (EN_getpatternindex(p, demandPattern, &patIndex) > 0) return 205; + } + + // Do nothing if node is not a junction + if (nodeIndex > p->network.Njuncs) return 0; + + // Add the new demand to the node's demands list + node = &(p->network.Node[nodeIndex]); + if (!adddemand(node, baseDemand / p->Ucf[FLOW], patIndex, demandName)) return 101; + return 0; +} + +int DLLEXPORT EN_deletedemand(EN_Project p, int nodeIndex, int demandIndex) +/*---------------------------------------------------------------- +** Input: nodeIndex = node index +** demandIndex = index of node's demand to be deleted +** Returns: error code +** Purpose: deletes an existing demand category from a junction node +**---------------------------------------------------------------- +*/ +{ + Pdemand d, dprev; + Snode *node; + int n = 1; + + // Check for valid arguments + if (!p->Openflag) return 102; + if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; + + // Only junctions have demands + if (nodeIndex <= p->network.Njuncs) + { + // Find head of node's list of demands + node = &p->network.Node[nodeIndex]; + d = node->D; + if (d == NULL) return 253; + dprev = d; + + // Check if target demand is head of demand list + if (demandIndex == 1) + { + node->D = d->next; + free(d->Name); + free(d); + return 0; + } + + // Otherwise locate target demand in demand list + while (d != NULL && n < demandIndex) + { + dprev = d; + d = d->next; + n++; + } + + // Return error if target demand not found + if (d == NULL) return 253; + + // Link the demands that precede and follow the target + dprev->next = d->next; + + // Delete the target demand + free(d->Name); + free(d); + } + return 0; +} + +int DLLEXPORT EN_getdemandindex(EN_Project p, int nodeIndex, char *demandName, + int *demandIndex) +/*---------------------------------------------------------------- +** Input: nodeIndex = node index +** demandName = name of demand being sought +** Output: demandIndex = index of demand being sought +** Returns: error code +** Purpose: retrieves the position of a named demand category +** in a node's list of demands +**---------------------------------------------------------------- +*/ +{ + Pdemand d; + int n = 0; + int nameEmpty = FALSE; + int found = FALSE; + + // Check for valid arguments + *demandIndex = 0; + if (!p->Openflag) return 102; + if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; + if (demandName == NULL) return 253; + + // Check if target name is empty + if (strlen(demandName) == 0) nameEmpty = TRUE; + + // Locate target demand in node's demands list + for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next) + { + n++; + if (d->Name == NULL) + { + if (nameEmpty) found = TRUE;; + } + else if (strcmp(d->Name, demandName) == 0) found = TRUE; + if (found) break; + } + + // Return target demand's index + if (!found) return 253; + *demandIndex = n; + return 0; +} + int DLLEXPORT EN_getnumdemands(EN_Project p, int nodeIndex, int *numDemands) /*---------------------------------------------------------------- ** Input: nodeIndex = node index @@ -2802,20 +2864,16 @@ int DLLEXPORT EN_getnumdemands(EN_Project p, int nodeIndex, int *numDemands) **---------------------------------------------------------------- */ { - //Pdemand d; - //int n = 0; + Pdemand d; + int n = 0; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; - // Count the number of demand categories - list_t *demand_list = p->network.Node[nodeIndex].D; - if (demand_list) - *numDemands = size_list(demand_list); - else - *numDemands = 0; - + // Count the number of demand categories assigned to node + for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next) n++; + *numDemands = n; return 0; } @@ -2830,22 +2888,19 @@ int DLLEXPORT EN_getbasedemand(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { + Pdemand d; + // Check for valid arguments + *baseDemand = 0.0; if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; - // Retrieve demand for specified category - if (nodeIndex <= p->network.Njuncs) - { - // Locate demand category record and assign demandName to it - list_t *dlist = p->network.Node[nodeIndex].D; - list_node_t *lnode = get_nth_list(dlist, demandIndex); - if (!lnode) - return 253; - else - *baseDemand = get_base_demand(lnode) * p->Ucf[FLOW]; - } - else *baseDemand = (double)(0.0); + // Locate target demand in node's demands list + d = finddemand(p->network.Node[nodeIndex].D, demandIndex); + if (d == NULL) return 253; + + // Retrieve target demand's base value + *baseDemand = d->Base * p->Ucf[FLOW]; return 0; } @@ -2861,30 +2916,18 @@ int DLLEXPORT EN_setbasedemand(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { + Pdemand d; + // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; - // Set baseline demand for specified category - if (nodeIndex <= p->network.Njuncs) - { - list_t *dlist = p->network.Node[nodeIndex].D; - // If demand list is null create one and set demand - if (!dlist) { - dlist = create_demand_list(baseDemand / p->Ucf[FLOW], 0, NULL); - if (!dlist) return 101; + // Locate target demand in node's demands list + d = finddemand(p->network.Node[nodeIndex].D, demandIndex); + if (d == NULL) return 253; - p->network.Node[nodeIndex].D = dlist; - } - // else find the demand entry and set demand - else { - list_node_t *lnode = get_nth_list(dlist, demandIndex); - if (!lnode) - return 253; - else - set_base_demand(lnode, baseDemand / p->Ucf[FLOW]); - } - } + // Assign new base value to target demand + d->Base = baseDemand / p->Ucf[FLOW]; return 0; } @@ -2899,7 +2942,7 @@ int DLLEXPORT EN_getdemandname(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { - char *temp = NULL; + Pdemand d; strcpy(demandName, ""); @@ -2907,22 +2950,12 @@ int DLLEXPORT EN_getdemandname(EN_Project p, int nodeIndex, int demandIndex, if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; - // Locate demand category record and retrieve its name - list_t *dlist = p->network.Node[nodeIndex].D; - if (dlist) { - list_node_t *lnode = get_nth_list(dlist, demandIndex); - if (!lnode) - return 253; - else - temp = get_category_name(lnode); - - if (temp) { - strcpy(demandName, temp); - free(temp); - } - } - else return 253; + // Locate target demand in node's demands list + d = finddemand(p->network.Node[nodeIndex].D, demandIndex); + if (d == NULL) return 253; + // Retrieve target demand's category name + if (d->Name) strcpy(demandName, d->Name); return 0; } @@ -2938,29 +2971,18 @@ int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { + Pdemand d; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; - // Check that demandName is not too long - if (strlen(demandName) > MAXID) return 252; + // Locate target demand in node's demands list + d = finddemand(p->network.Node[nodeIndex].D, demandIndex); + if (d == NULL) return 253; - // Locate demand category record and assign demandName to it - list_t *dlist = p->network.Node[nodeIndex].D; - if (!dlist) { - dlist = create_demand_list(0, 0, demandName); - if (!dlist) return 101; - - p->network.Node[nodeIndex].D = dlist; - } - else { - list_node_t *lnode = get_nth_list(dlist, demandIndex); - if (!lnode) - return 253; - else - set_category_name(lnode, demandName); - } + // Assign category name to target demand + d->Name = xstrcpy(&d->Name, demandName, MAXID); return 0; } @@ -2976,18 +2998,19 @@ int DLLEXPORT EN_getdemandpattern(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { + Pdemand d; + // Check for valid arguments + *patIndex = 0; if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; - // Locate demand category record and assign demandName to it - list_t *dlist = p->network.Node[nodeIndex].D; - list_node_t *lnode = get_nth_list(dlist, demandIndex); - if (!lnode) - return 253; - else - *patIndex = get_pattern_index(lnode); + // Locate target demand in node's demand list + d = finddemand(p->network.Node[nodeIndex].D, demandIndex); + if (d == NULL) return 253; + // Retrieve that demand's pattern index + *patIndex = d->Pat; return 0; } @@ -3005,29 +3028,19 @@ int DLLEXPORT EN_setdemandpattern(EN_Project p, int nodeIndex, int demandIndex, { Network *net = &p->network; + Pdemand d; + // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > net->Nnodes) return 203; - if (patIndex <= 0 || patIndex > net->Npats) return 205; + if (patIndex < 0 || patIndex > net->Npats) return 205; - // Locate demand category record and assign time pattern to it - if (nodeIndex <= net->Njuncs) { + // Locate target demand in node's demand list + d = finddemand(p->network.Node[nodeIndex].D, demandIndex); + if (d == NULL) return 253; - list_t *dlist = p->network.Node[nodeIndex].D; - if (!dlist) { - dlist = create_demand_list(0, patIndex, NULL); - if (!dlist) return 101; - - p->network.Node[nodeIndex].D = dlist; - } - else { - list_node_t *lnode = get_nth_list(dlist, demandIndex); - if (!lnode) - return 253; - else - set_pattern_index(lnode, patIndex); - } - } + // Assign new time pattern to target demand + d->Pat = patIndex; return 0; } diff --git a/src/epanet2.c b/src/epanet2.c index 928e30c..286018b 100644 --- a/src/epanet2.c +++ b/src/epanet2.c @@ -385,6 +385,22 @@ int DLLEXPORT ENsetdemandmodel(int model, EN_API_FLOAT_TYPE pmin, return EN_setdemandmodel(_defaultProject, model, pmin, preq, pexp); } +int DLLEXPORT ENadddemand(int nodeIndex, EN_API_FLOAT_TYPE baseDemand, + char *demandPattern, char *demandName) +{ + return EN_adddemand(_defaultProject, nodeIndex, baseDemand, demandPattern, demandName); +} + +int DLLEXPORT ENdeletedemand(int nodeIndex, int demandIndex) +{ + return EN_deletedemand(_defaultProject, nodeIndex, demandIndex); +} + +int DLLEXPORT ENgetdemandindex(int nodeIndex, char *demandName, int *demandIndex) +{ + return EN_getdemandindex(_defaultProject, nodeIndex, demandName, demandIndex); +} + int DLLEXPORT ENgetnumdemands(int nodeIndex, int *numDemands) { return EN_getnumdemands(_defaultProject, nodeIndex, numDemands); diff --git a/src/funcs.h b/src/funcs.h index 529cb12..44e4018 100755 --- a/src/funcs.h +++ b/src/funcs.h @@ -37,6 +37,10 @@ int findpump(Network *, int); int findpattern(Network *, char *); int findcurve(Network *, char *); +Pdemand finddemand(Pdemand, int); +int adddemand(Snode *, double, int, char *); +void freedemands(Snode *); + void adjustpatterns(Network *, int); void adjustcurves(Network *, int); int resizecurve(Scurve *, int); diff --git a/src/hydraul.c b/src/hydraul.c index 515be11..1a12971 100755 --- a/src/hydraul.c +++ b/src/hydraul.c @@ -27,9 +27,6 @@ #include "funcs.h" #include "text.h" -#include "demand.h" - - const double QZERO = 1.e-6; // Equivalent to zero flow in cfs // Imported functions @@ -556,7 +553,7 @@ void demands(Project *pr) int i ,j, n; long k, p; double djunc, sum; -// Pdemand demand; + Pdemand demand; // Determine total elapsed number of pattern periods p = (time->Htime + time->Pstart) / time->Pstep; @@ -566,19 +563,15 @@ void demands(Project *pr) for (i = 1; i <= net->Njuncs; i++) { sum = 0.0; - list_t *dlist = net->Node[i].D; - - if (dlist) { - for (list_node_t *lnode = first_list(dlist); done_list(lnode); lnode = next_list(lnode)) - { - // pattern period (k) = (elapsed periods) modulus (periods per pattern) - j = get_pattern_index(lnode); - k = p % (long)net->Pattern[j].Length; - djunc = (get_base_demand(lnode)) * net->Pattern[j].F[k] * hyd->Dmult; - if (djunc > 0.0) hyd->Dsystem += djunc; - sum += djunc; - } - } + for (demand = net->Node[i].D; demand != NULL; demand = demand->next) + { + // pattern period (k) = (elapsed periods) modulus (periods per pattern) + j = demand->Pat; + k = p % (long)net->Pattern[j].Length; + djunc = (demand->Base) * net->Pattern[j].F[k] * hyd->Dmult; + if (djunc > 0.0) hyd->Dsystem += djunc; + sum += djunc; + } hyd->NodeDemand[i] = sum; // Initialize pressure dependent demand diff --git a/src/inpfile.c b/src/inpfile.c index be24616..d0ed017 100644 --- a/src/inpfile.c +++ b/src/inpfile.c @@ -28,7 +28,6 @@ Last Updated: 04/03/2019 #include "hash.h" #include "text.h" -#include "demand.h" // Defined in enumstxt.h in EPANET.C extern char *LinkTxt[]; @@ -121,48 +120,6 @@ void saveauxdata(Project *pr, FILE *f) InFile = NULL; } - -void write_demands(Project *pr, FILE *f) { - int i, j; - - Snode *node = NULL; - list_node_t *lnode = NULL; - char *temp = NULL; - - char s[MAXLINE + 1], - s1[MAXLINE + 1]; - - double ucf = pr->Ucf[DEMAND]; - Network *net = &pr->network; - - fprintf(f, "\n\n"); - fprintf(f, s_DEMANDS); - - for (i = 1; i <= net->Njuncs; i++) { - node = &net->Node[i]; - if (node->D) { - for (lnode = first_list(node->D); done_list(lnode); lnode = next_list(lnode)) { - if (lnode) { - sprintf(s, " %-31s %14.6f", node->ID, ucf * get_base_demand(lnode)); - - if - ((j = get_pattern_index(lnode)) > 0) sprintf(s1, " %-31s", net->Pattern[j].ID); - else - strcpy(s1, " "); - - fprintf(f, "\n%s %-31s", s, s1); - - if (temp = get_category_name(lnode)) { - fprintf(f, " ;%s", temp); - free(temp); - } - } - } - } - } -} - - int saveinpfile(Project *pr, const char *fname) /* ------------------------------------------------- @@ -179,9 +136,9 @@ int saveinpfile(Project *pr, const char *fname) Times *time = &pr->times; int i, j, n; - double d, kc, ke, km; + double d, kc, ke, km, ucf; char s[MAXLINE + 1], s1[MAXLINE + 1], s2[MAXLINE + 1]; - //Pdemand demand; + Pdemand demand; Psource source; FILE *f; Slink *link; @@ -371,7 +328,21 @@ int saveinpfile(Project *pr, const char *fname) // Write [DEMANDS] section - write_demands(pr, f); + fprintf(f, "\n\n"); + fprintf(f, s_DEMANDS); + ucf = pr->Ucf[DEMAND]; + for (i = 1; i <= net->Njuncs; i++) + { + node = &net->Node[i]; + for (demand = node->D; demand != NULL; demand = demand->next) + { + sprintf(s, " %-31s %14.6f", node->ID, ucf * demand->Base); + if ((j = demand->Pat) > 0) sprintf(s1, " %-31s", net->Pattern[j].ID); + else strcpy(s1, " "); + fprintf(f, "\n%s %-31s", s, s1); + if (demand->Name) fprintf(f, " ;%s", demand->Name); + } + } // Write [EMITTERS] section diff --git a/src/input1.c b/src/input1.c index 391fc69..05b483b 100644 --- a/src/input1.c +++ b/src/input1.c @@ -20,11 +20,8 @@ Last Updated: 04/03/2019 #endif #include #include - #include -#include "demand.h" - #include "types.h" #include "funcs.h" #include "hash.h" @@ -222,9 +219,9 @@ void adjustdata(Project *pr) int i; double ucf; // Unit conversion factor - //Pdemand demand; // Pointer to demand record + Pdemand demand; // Pointer to demand record Slink *link; - //Snode *node; + Snode *node; Stank *tank; // Use 1 hr pattern & report time step if none specified @@ -334,14 +331,14 @@ void adjustdata(Project *pr) // Use default pattern if none assigned to a demand parser->DefPat = findpattern(net, parser->DefPatID); - if (parser->DefPat > 0) { - for (i = 1; i <= net->Nnodes; i++) { - for (list_node_t *lnode = first_list((&net->Node[i])->D); done_list(lnode); lnode = next_list(lnode)) { - if (get_pattern_index(lnode) == 0) - set_pattern_index(lnode, parser->DefPat); - } - } - } + if (parser->DefPat > 0) for (i = 1; i <= net->Nnodes; i++) + { + node = &net->Node[i]; + for (demand = node->D; demand != NULL; demand = demand->next) + { + if (demand->Pat == 0) demand->Pat = parser->DefPat; + } + } // Remove QUALITY as a reporting variable if no WQ analysis if (qual->Qualflag == NONE) rpt->Field[QUALITY].Enabled = FALSE; @@ -544,7 +541,7 @@ void convertunits(Project *pr) int i, j, k; double ucf; // Unit conversion factor - //Pdemand demand; // Pointer to demand record + Pdemand demand; // Pointer to demand record Snode *node; Stank *tank; Slink *link; @@ -564,15 +561,10 @@ void convertunits(Project *pr) for (i = 1; i <= net->Njuncs; i++) { node = &net->Node[i]; - list_t *dlist = node->D; - if (dlist) { - for (list_node_t *lnode = first_list(dlist); done_list(lnode); lnode = next_list(lnode)) - convert_units(lnode, pr->Ucf[DEMAND]); - } - // for (demand = node->D; demand != NULL; demand = demand->next) - // { - // demand->Base /= pr->Ucf[DEMAND]; - // } + for (demand = node->D; demand != NULL; demand = demand->next) + { + demand->Base /= pr->Ucf[DEMAND]; + } } hyd->Pmin /= pr->Ucf[PRESSURE]; diff --git a/src/input3.c b/src/input3.c index 7951cdf..e8bb71c 100644 --- a/src/input3.c +++ b/src/input3.c @@ -20,10 +20,8 @@ Last Updated: 04/03/2019 #endif #include #include - #include -#include "demand.h" #include "types.h" #include "funcs.h" #include "hash.h" @@ -86,10 +84,6 @@ int juncdata(Project *pr) int njuncs; // number of network junction nodes double el, // elevation y = 0.0; // base demand - - list_t *demand_list = NULL; // demand list - - Snode *node; // Add new junction to data base @@ -122,13 +116,9 @@ int juncdata(Project *pr) node->Type = JUNCTION; node->Comment = xstrcpy(&node->Comment, parser->Comment, MAXMSG); - // create demand data only if a demand has been specified - if (y != 0.0) { - demand_list = create_demand_list(y, p, NULL); - if (!demand_list) return 101; - } - node->D = demand_list; - + // Create a demand for the junction and use NodeDemand as an indicator + // to be used when processing demands from the [DEMANDS] section + if (!adddemand(node, y, p, NULL)) return 101; hyd->NodeDemand[njuncs] = y; return 0; } @@ -713,11 +703,7 @@ int demanddata(Project *pr) int j, n, p = 0; double y; - list_t *demand_list = NULL; - demand_data_t *demand_data = NULL; - - //Pdemand demand; - //Pdemand cur_demand; + Pdemand demand; // Extract data from tokens n = parser->Ntokens; @@ -741,27 +727,23 @@ int demanddata(Project *pr) if (p == 0) return setError(parser, 2, 205); } - - // if no demands were specified in [JUNCTIONS] create the list here - demand_list = net->Node[j].D; - if (demand_list == NULL) { - demand_list = create_list(get_demand_data_size(), delete_demand_data); - if (demand_list == NULL) return 101; - net->Node[j].D = demand_list; - } - - // else replace the demand data entered in [JUNCTIONS] section - else if (size_list(demand_list) == 1) { - list_node_t *lnode = head_list(demand_list, true); - delete_node(demand_list, lnode); - } - - // append the data to the list - demand_data = create_demand_data(y, p, parser->Comment); - if (demand_data == NULL) return 101; - - append_list(demand_list, &demand_data); + // Replace any demand entered in [JUNCTIONS] section + demand = net->Node[j].D; + if (demand && hyd->NodeDemand[j] != MISSING) + { + // First category encountered will overwrite demand category + // created when junction was read from [JUNCTIONS] section + demand->Base = y; + demand->Pat = p; + if (parser->Comment[0]) + { + demand->Name = xstrcpy(&demand->Name, parser->Comment, MAXID); + } + hyd->NodeDemand[j] = MISSING; // marker - next iteration will append a new category. + } + // Otherwise add new demand to junction + else if (!adddemand(&net->Node[j], y, p, parser->Comment) > 0) return 101; return 0; } diff --git a/src/project.c b/src/project.c index 49696a3..f8dc809 100644 --- a/src/project.c +++ b/src/project.c @@ -29,7 +29,6 @@ #include "types.h" #include "funcs.h" -#include "demand.h" int openfiles(Project *pr, const char *f1, const char *f2, const char *f3) /*---------------------------------------------------------------- @@ -387,7 +386,6 @@ void freedata(Project *pr) */ { int j; - //Pdemand demand, nextdemand; // Free memory for computed results free(pr->hydraul.NodeDemand); @@ -405,12 +403,8 @@ void freedata(Project *pr) { for (j = 1; j <= pr->parser.MaxNodes; j++) { - // Free memory used for demand category list - list_t *demand = pr->network.Node[j].D; - if(demand) - delete_list(demand); - - // Free memory used for WQ source data + // Free memory used for demands and WQ source data + freedemands(&(pr->network.Node[j])); free(pr->network.Node[j].S); free(pr->network.Node[j].Comment); } @@ -471,6 +465,83 @@ void freedata(Project *pr) } } +Pdemand finddemand(Pdemand d, int index) +/*---------------------------------------------------------------- +** Input: d = pointer to start of a list of demands +** index = the position of the demand to retrieve +** Output: none +** Returns: the demand at the requested position +** Purpose: finds the demand at a given position in a demand list +**---------------------------------------------------------------- +*/ +{ + int n = 1; + if (index <= 0)return NULL; + while (d) + { + if (n == index) break; + n++; + d = d->next; + } + return d; +} + +int adddemand(Snode *node, double dbase, int dpat, char *dname) +/*---------------------------------------------------------------- +** Input: node = a network junction node +** dbase = base demand value +** dpat = demand pattern index +** dname = name of demand category +** Output: returns TRUE if successful, FALSE if not +** Purpose: adds a new demand category to a node. +**---------------------------------------------------------------- +*/ +{ + Pdemand demand, lastdemand; + + // Create a new demand struct + demand = (struct Sdemand *)malloc(sizeof(struct Sdemand)); + if (demand == NULL) return FALSE; + + // Assign it the designated properties + demand->Base = dbase; + demand->Pat = dpat; + demand->Name = NULL; + if (dname && strlen(dname) > 0) xstrcpy(&demand->Name, dname, MAXID); + demand->next = NULL; + + // If node has no demands make this its first demand category + if (node->D == NULL) node->D = demand; + + // Otherwise append this demand to the end of the node's demands list + else + { + lastdemand = node->D; + while (lastdemand->next) lastdemand = lastdemand->next; + lastdemand->next = demand; + } + return TRUE; +} + +void freedemands(Snode *node) +/*---------------------------------------------------------------- +** Input: node = a network junction node +** Output: node +** Purpose: frees the memory used for a node's list of demands. +**---------------------------------------------------------------- +*/ +{ + Pdemand nextdemand; + Pdemand demand = node->D; + while (demand != NULL) + { + nextdemand = demand->next; + free(demand->Name); + free(demand); + demand = nextdemand; + } + node->D = NULL; +} int buildadjlists(Network *net) /* @@ -790,16 +861,6 @@ void adjustpattern(int *pat, int index) else if (*pat > index) (*pat)--; } - -void adjust_demand_pattern(list_node_t *list_node, int deletion_index) -{ - int pat_idx = get_pattern_index(list_node); - - if (pat_idx == deletion_index) set_pattern_index(list_node, 0); - else if (pat_idx > deletion_index) set_pattern_index(list_node, --pat_idx); -} - - void adjustpatterns(Network *network, int index) /*---------------------------------------------------------------- ** Input: index = index of time pattern being deleted @@ -809,17 +870,16 @@ void adjustpatterns(Network *network, int index) */ { int j; - //Pdemand demand; + Pdemand demand; Psource source; // Adjust patterns used by junctions for (j = 1; j <= network->Nnodes; j++) { // Adjust demand patterns - list_t *dlist = network->Node[j].D; - if (dlist) { - for (list_node_t *lnode = first_list(dlist); done_list(lnode); lnode = next_list(lnode)) - adjust_demand_pattern(lnode, index); + for (demand = network->Node[j].D; demand != NULL; demand = demand->next) + { + adjustpattern(&demand->Pat, index); } // Adjust WQ source patterns source = network->Node[j].S; diff --git a/src/rules.c b/src/rules.c index 7ee4d8a..46c4790 100644 --- a/src/rules.c +++ b/src/rules.c @@ -26,6 +26,10 @@ #include "hash.h" #include "text.h" +#ifdef _WIN32 +#define snprintf _snprintf +#endif + enum Rulewords { r_RULE, r_IF, diff --git a/src/types.h b/src/types.h index b848238..e0d6b7e 100755 --- a/src/types.h +++ b/src/types.h @@ -16,9 +16,7 @@ #include - #include "hash.h" -#include "util/list.h" /* ------------------------------------------- @@ -339,14 +337,14 @@ typedef struct // Curve Object double *Y; // y-values } Scurve; -//struct Sdemand // Demand List Item -//{ -// double Base; // baseline demand -// int Pat; // pattern index -// char *Name; // demand category name -// struct Sdemand *next; // next demand list item -//}; -//typedef struct Sdemand *Pdemand; // Pointer to demand list +struct Sdemand // Demand List Item +{ + double Base; // baseline demand + int Pat; // pattern index + char *Name; // demand category name + struct Sdemand *next; // next demand list item +}; +typedef struct Sdemand *Pdemand; // Pointer to demand list typedef struct // Energy Usage Object { @@ -373,8 +371,7 @@ typedef struct // Node Object double X; // x-coordinate double Y; // y-coordinate double El; // elevation -// Pdemand D; // demand pointer - list_t *D; // pointer to demand list + Pdemand D; // demand pointer Psource S; // source pointer double C0; // initial quality double Ke; // emitter coeff. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 62f6f00..d45e3f2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,16 +23,6 @@ add_test(NAME test_net_builder WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data) -add_executable(test_demand_data test_demand_data.cpp - ../src/demand.c - ../src/util/list.c) -target_include_directories(test_demand_data PUBLIC ../src/ ../src/util/) -target_link_libraries(test_demand_data ${Boost_LIBRARIES} epanet2) -add_test(NAME test_demand_data - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_demand_data - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data) - - set(toolkit_test_srcs test_toolkit.cpp test_project.cpp @@ -81,6 +71,3 @@ add_test(NAME test_filemanager add_test(NAME test_output COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_output WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/outfile/data) - -add_test(NAME test_list - COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_list) diff --git a/tests/test_demand.cpp b/tests/test_demand.cpp index 80bf042..d179d91 100644 --- a/tests/test_demand.cpp +++ b/tests/test_demand.cpp @@ -84,31 +84,42 @@ BOOST_AUTO_TEST_CASE(test_categories_reopen, * boost::unit_test::depends_on("tes BOOST_FIXTURE_TEST_CASE(test_adddemand, FixtureSingleNode) { - int key, demand_key; + int Dindex, nD1, nD2; - error = EN_adddemand(ph, node_qhut, 100.0, "PrimaryPattern", "PrimaryDemand", &demand_key); + error = EN_adddemand(ph, node_qhut, 100.0, "PrimaryPattern", "PrimaryDemand"); BOOST_CHECK(error != 0); error = EN_addpattern(ph, (char *)"PrimaryPattern"); BOOST_REQUIRE(error == 0); - error = EN_adddemand(ph, node_qhut, 100.0, "PrimaryPattern", "PrimaryDemand", &demand_key); + error = EN_adddemand(ph, node_qhut, 100.0, "PrimaryPattern", "PrimaryDemand"); BOOST_CHECK(error == 0); error = EN_addpattern(ph, (char *)"SecondaryPattern"); BOOST_REQUIRE(error == 0); - error = EN_adddemand(ph, node_qhut, 10.0, "SecondaryPattern", "SecondaryDemand", &key); + error = EN_adddemand(ph, node_qhut, 10.0, "SecondaryPattern", "SecondaryDemand"); BOOST_CHECK(error == 0); error = EN_addpattern(ph, (char *)"TertiaryPattern"); BOOST_REQUIRE(error == 0); - error = EN_adddemand(ph, node_qhut, 1.0, "TertiaryPattern", "TertiaryDemand", &demand_key); + error = EN_adddemand(ph, node_qhut, 1.0, "TertiaryPattern", "TertiaryDemand"); + BOOST_CHECK(error == 0); + + error = EN_getnumdemands(ph, node_qhut, &nD1); + BOOST_REQUIRE(error == 0); + + error = EN_getdemandindex(ph, node_qhut, "TertiaryDemand", &Dindex); + BOOST_CHECK(error == 0); + BOOST_CHECK(Dindex == nD1); + + error = EN_deletedemand(ph, node_qhut, Dindex); BOOST_CHECK(error == 0); - error = EN_removedemand(ph, node_qhut, key); - BOOST_CHECK(error == 0); + error = EN_getnumdemands(ph, node_qhut, &nD2); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(nD1 - nD2 == 1); } diff --git a/tests/test_demand_data.cpp b/tests/test_demand_data.cpp deleted file mode 100644 index bbf8526..0000000 --- a/tests/test_demand_data.cpp +++ /dev/null @@ -1,295 +0,0 @@ -/* -****************************************************************************** -Project: OWA EPANET -Version: 2.2 -Module: /test_demand_data.cpp -Description: tests demand data list node struct -Authors: see AUTHORS -Copyright: see AUTHORS -License: see LICENSE -Last Updated: 04/18/2019 -****************************************************************************** -*/ - -#define BOOST_TEST_MODULE demand_data -#include - -#include "demand.h" -#include "epanet2_2.h" - - -#define DATA_PATH_NET1 "./net1.inp" -#define DATA_PATH_TMP "./tmp.inp" -#define DATA_PATH_RPT "./test.rpt" -#define DATA_PATH_OUT "./test.out" - - -boost::test_tools::predicate_result check_string(std::string test, std::string ref) -{ - if (ref.compare(test) == 0) - return true; - else - return false; -} - - -BOOST_AUTO_TEST_SUITE(test_demand_data) - - -BOOST_AUTO_TEST_CASE(test_create_destroy_demand_list) -{ - list_t *dlist; - - dlist = create_demand_list(100.0, 1, "CUB_SCOUT_DAY_CAMP"); - BOOST_CHECK(dlist != NULL); - - delete_list(dlist); -} - - -BOOST_AUTO_TEST_CASE (test_create_destroy) -{ - void *data = NULL; - - data = create_demand_data(100.0, 1, NULL); - BOOST_CHECK(data != NULL); - - delete_demand_data(&data); - - data = NULL; - - data = create_demand_data(100.0, 1, "CUB_SCOUT_BASE_CAMP"); - BOOST_CHECK(data != NULL); - - delete_demand_data(&data); -} - -BOOST_AUTO_TEST_CASE(test_get_size) -{ - size_t size = get_demand_data_size(); - BOOST_CHECK(size == sizeof(demand_data_t *)); -} - - -struct Fixture { - Fixture() { - _data = NULL; - dlist = NULL; - - dlist = create_list(get_demand_data_size(), delete_demand_data); - _data = create_demand_data(100.0, 1, "CUB_SCOUT_BASE_CAMP"); - - append_list(dlist, &_data); - } - ~Fixture() { - delete_list(dlist); - } - demand_data_t *_data; - list_t *dlist; -}; - -BOOST_FIXTURE_TEST_CASE(test_demand_list, Fixture) -{ - list_node_t *lnode = head_list(dlist, false); - BOOST_CHECK(lnode != NULL); -} - -BOOST_FIXTURE_TEST_CASE(test_demand_getset, Fixture) -{ - list_node_t *lnode = head_list(dlist, false); - double demand; - - demand = get_base_demand(lnode); - BOOST_CHECK(demand == 100.0); - - set_base_demand(lnode, 200.0); - - demand = get_base_demand(lnode); - BOOST_CHECK(demand == 200.0); -} - -BOOST_FIXTURE_TEST_CASE(test_pattern_getset, Fixture) -{ - list_node_t *lnode = head_list(dlist, false); - int index; - - index = get_pattern_index(lnode); - BOOST_CHECK(index == 1); - - set_pattern_index(lnode, 2); - - index = get_pattern_index(lnode); - BOOST_CHECK(index == 2); -} - -BOOST_FIXTURE_TEST_CASE(test_category_getset, Fixture) -{ - list_node_t *lnode = head_list(dlist, false); - char *name = NULL; - - name = get_category_name(lnode); - BOOST_CHECK(check_string(name, (char *)"CUB_SCOUT_BASE_CAMP")); - - free(name); - name = NULL; - - set_category_name(lnode, (char *)"CUB_SCOUT_COMMAND"); - - name = get_category_name(lnode); - BOOST_CHECK(check_string(name, "CUB_SCOUT_COMMAND")); - - free(name); -} - -BOOST_FIXTURE_TEST_CASE(test_convert_demand, Fixture) -{ - list_node_t *lnode = head_list(dlist, false); - BOOST_CHECK(lnode != NULL); - - // 100.0 GPM == 6.31 LPS - convert_units(lnode, 15.850); - double demand = get_base_demand(lnode); - BOOST_TEST(demand == 6.31, boost::test_tools::tolerance(0.01)); -} - -BOOST_AUTO_TEST_CASE(test_initclose) -{ - int error; - - EN_Project ph = NULL; - - EN_createproject(&ph); - - error = EN_init(ph, DATA_PATH_RPT, DATA_PATH_OUT, EN_GPM, EN_HW); - BOOST_REQUIRE(error == 0); - - error = EN_close(ph); - BOOST_REQUIRE(error == 0); - - EN_deleteproject(&ph); -} - - -#define DATA_PATH_NET1 "./net1.inp" -#define DATA_PATH_TMP "./tmp.inp" -#define DATA_PATH_RPT "./test.rpt" -#define DATA_PATH_OUT "./test.out" - - -struct FixtureSingleNode { - FixtureSingleNode() { - error = 0; - ph = NULL; - - EN_createproject(&ph); - EN_init(ph, DATA_PATH_RPT, DATA_PATH_OUT, EN_GPM, EN_HW); - - EN_addnode(ph, (char *)"CUB_SCOUT_QUONSET_HUT", EN_JUNCTION, &node_qhut); - //EN_getnodeindex(ph, (char *)"CUB_SCOUT_QUONSET_HUT", &node_qhut); - } - - ~FixtureSingleNode() { - EN_close(ph); - EN_deleteproject(&ph); - } - int error, index, node_qhut; - EN_Project ph; -}; - - -BOOST_FIXTURE_TEST_CASE(test_single_node, FixtureSingleNode) -{ - int demand_idx, pattern_idx, n; - double demand; - - error = EN_getnumdemands(ph, node_qhut, &n); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(n == 0); - - demand_idx = 1; - error = EN_getbasedemand(ph, node_qhut, demand_idx, &demand); - BOOST_REQUIRE(error == 253); - - error = EN_getdemandpattern(ph, node_qhut, demand_idx, &pattern_idx); - BOOST_REQUIRE(error == 253); - - char demname[31]; - error = EN_getdemandname(ph, node_qhut, demand_idx, demname); - BOOST_REQUIRE(error == 253); - BOOST_CHECK(check_string(demname, "\0")); - - error = EN_setbasedemand(ph, node_qhut, demand_idx, 100.0); - BOOST_REQUIRE(error == 0); - - // only one demand category - pattern_idx = 1; - error = EN_setdemandpattern(ph, node_qhut, demand_idx, pattern_idx); - BOOST_REQUIRE(error == 205); - - // create pattern - error = EN_addpattern(ph, (char *)"Pat2"); - BOOST_REQUIRE(error == 0); - error = EN_getpatternindex(ph, (char *)"Pat2", &pattern_idx); - BOOST_REQUIRE(error == 0); - - error = EN_setdemandpattern(ph, node_qhut, demand_idx, pattern_idx); - BOOST_REQUIRE(error == 0); - - error = EN_setdemandname(ph, node_qhut, demand_idx, (char *)"CUB_SCOUT_MESS_HALL"); - BOOST_REQUIRE(error == 0); - -} - - -BOOST_FIXTURE_TEST_CASE(test_pattern_edits, FixtureSingleNode) -{ - int n, node_cpoint, pat2_idx, pat3_idx; - - EN_addnode(ph, (char *)"CUB_SCOUT_CHECKPOINT", EN_JUNCTION, &node_cpoint); - //EN_getnodeindex(ph, (char *)"CUB_SCOUT_CHECKPOINT", &node_cpoint); - - // Add 2 new patterns - error = EN_addpattern(ph, (char *)"DefPat"); - BOOST_REQUIRE(error == 0); - error = EN_addpattern(ph, (char *)"Pat2"); - BOOST_REQUIRE(error == 0); - error = EN_getpatternindex(ph, (char *)"Pat2", &pat2_idx); - BOOST_REQUIRE(error == 0); - - error = EN_addpattern(ph, (char *)"Pat3"); - BOOST_REQUIRE(error == 0); - error = EN_getpatternindex(ph, (char *)"Pat3", &pat3_idx); - BOOST_REQUIRE(error == 0); - - double f2[] = { 2.1, 2.2 }; - double f3[] = { 3.1, 3.2, 3.3, 3.4 }; - error = EN_setpattern(ph, pat2_idx, f2, 2); - BOOST_REQUIRE(error == 0); - error = EN_setpattern(ph, pat3_idx, f3, 4); - BOOST_REQUIRE(error == 0); - - // Assign Pat3 to 3rd junction - error = EN_setdemandpattern(ph, node_cpoint, 1, pat3_idx); - BOOST_REQUIRE(error == 0); - - // Delete Pat2 - error = EN_deletepattern(ph, pat2_idx); - BOOST_REQUIRE(error == 0); - - //Check that there are now 2 patterns - error = EN_getcount(ph, EN_PATCOUNT, &n); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(n == 2); - - // Check that Pat3 with 4 factors is still assigned to 3rd junction - error = EN_getdemandpattern(ph, node_cpoint, 1, &pat3_idx); - BOOST_REQUIRE(error == 0); - error = EN_getpatternlen(ph, pat3_idx, &n); - BOOST_REQUIRE(error == 0); - BOOST_CHECK(n == 4); -} - - - - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt index c707e0b..a612fc7 100644 --- a/tests/util/CMakeLists.txt +++ b/tests/util/CMakeLists.txt @@ -25,8 +25,3 @@ add_executable(test_filemanager ./test_filemanager.cpp target_include_directories(test_filemanager PUBLIC ../../src/) target_link_libraries(test_filemanager ${Boost_LIBRARIES}) - -add_executable(test_list ./test_list.cpp - ../../src/util/list.c) -target_include_directories(test_list PUBLIC ../../src/) -target_link_libraries(test_list ${Boost_LIBRARIES}) diff --git a/tests/util/test_list.cpp b/tests/util/test_list.cpp deleted file mode 100644 index 86fcd29..0000000 --- a/tests/util/test_list.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/* - ****************************************************************************** - Project: OWA EPANET - Version: 2.2 - Module: util/list.h - Description: Generic list - https://gist.github.com/pseudomuto/6334796#file-sample_app-c - Accessed: April 9, 2019 - Authors: David Muto, Modified by Michael E. Tryby - Copyright: see AUTHORS - License: see LICENSE - Last Updated: 04/09/2019 - ****************************************************************************** -*/ - -#include -#include -#include - -#define BOOST_TEST_MODULE list -#include - -#include "util/list.h" - - -boost::test_tools::predicate_result check_string(std::string test, std::string ref) -{ - if (ref.compare(test) == 0) - return true; - else - return false; -} - -int *get_int_data(list_node_t *lnode) { - return (int *)get_data(lnode); -} - -bool iterate_int(list_node_t *lnode) -{ - printf("At Key: %d Found value: %d\n", get_key(lnode), *get_int_data(lnode)); - return true; -} - - -BOOST_AUTO_TEST_SUITE(test_list) - - -BOOST_AUTO_TEST_CASE(test_create_delete) { - - list_t *list; - list = create_list(sizeof(int), NULL); - - delete_list(list); -} - - -BOOST_AUTO_TEST_CASE(test_int_list){ - - int i, numbers = 10; - list_t *list = NULL; - - int key[10 + 1]; - - srand((unsigned int)time(0)); - - list = create_list(sizeof(int), NULL); - - for(i = 1; i <= numbers; i++) { - key[i] = append_list(list, &i); - } - BOOST_CHECK(size_list(list) == 10); - - listIterator iterator = (listIterator)iterate_int; - for_each_list(list, iterator); - - list_node_t *lnode = search_list(list, key[5]); - BOOST_CHECK(get_key(lnode) == key[5]); - - delete_list(list); -} - - -inline char *get_string_data(list_node_t *lnode) -{ - return *(char **)get_data(lnode); -} - -bool iterate_string(list_node_t *lnode) -{ - printf("Found string value: %s\n", get_string_data(lnode)); - return true; -} - -void free_string(void *data) -{ - free(*(char **)data); -} - -struct FixtureStrings{ - FixtureStrings() { - list = NULL; - - int numNames = 5; - const char *names[] = { "David", "Kevin", "Michael", "Craig", "Jimi" }; - - list = create_list(sizeof(char *), free_string); - - char *name; - for (int i = 0; i < numNames; i++) { - name = strdup(names[i]); - append_list(list, &name); - } - } - ~FixtureStrings() { - delete_list(list); - } - list_t *list; -}; - -BOOST_FIXTURE_TEST_CASE(test_string_list, FixtureStrings) { - - BOOST_CHECK(size_list(list) == 5); - - listIterator iterator = (listIterator)iterate_string; - for_each_list(list, iterator); -} - - -BOOST_FIXTURE_TEST_CASE(test_head_list, FixtureStrings) { - - BOOST_CHECK(check_string(get_string_data(head_list(list, false)), "David")); - BOOST_CHECK(size_list(list) == 5); - - list_node_t *lnode = head_list(list, true); - BOOST_CHECK(check_string(get_string_data(lnode), "David")); - delete_node(list, lnode); - - BOOST_CHECK(check_string(get_string_data(head_list(list, false)), "Kevin")); - BOOST_CHECK(size_list(list) == 4); -} - - -BOOST_FIXTURE_TEST_CASE(test_tail_list, FixtureStrings) { - - BOOST_CHECK(check_string(get_string_data(tail_list(list)), "Jimi")); - BOOST_CHECK(size_list(list) == 5); -} - - -typedef struct test_data_s { - int num; - char *name; -} test_data_t; - -test_data_t *create_test_data(int number, const char *name){ - - test_data_t *data = (test_data_t *)malloc(sizeof(test_data_t)); - data->num = number; - if (name) - data->name = strdup(name); - else - data->name = NULL; - - return data; -} - -void delete_test_data(void *data) { - - test_data_t *test_data = *(test_data_t **)data; - - if (test_data->name) - free(test_data->name); - - free(test_data); -} - -inline test_data_t *get_test_data(list_node_t *lnode) -{ - return *(test_data_t **)get_data(lnode); -} - -bool iterate_test_data(list_node_t *lnode) -{ - test_data_t *test_data = get_test_data(lnode); - - printf("Found number: %i name: %s\n", test_data->num, test_data->name); - return true; -} - - -char *get_name(list_node_t *lnode) -{ - return get_test_data(lnode)->name; -} - -BOOST_AUTO_TEST_CASE(test_struct_list){ - - int key, head_key, tail_key; - - list_t *list = NULL; - list = create_list(sizeof(test_data_t *), delete_test_data); - - - test_data_t *data = create_test_data(1, "David"); - head_key = append_list(list, &data); - - data = create_test_data(2, "Kevin"); - key = append_list(list, &data); - - data = create_test_data(3, "Michael"); - append_list(list, &data); - - data = create_test_data(4, "Craig"); - append_list(list, &data); - - data = create_test_data(5, "Jimi"); - tail_key = append_list(list, &data); - - BOOST_CHECK(size_list(list) == 5); - - listIterator iterator = (listIterator)iterate_test_data; - for_each_list(list, iterator); - - - // locate a list node by a key - printf("Found %s!\n", get_name(search_list(list, key))); - - printf("Removing Kevin\n"); - remove_node(list, key); - for_each_list(list, iterator); - - printf("Removing David\n"); - remove_node(list, head_key); - for_each_list(list, iterator); - - printf("Removing Jimi\n"); - remove_node(list, tail_key); - for_each_list(list, iterator); - - list_node_t *lnode = head_list(list, true); - delete_node(list, lnode); - - delete_list(list); -} - -// TODO: search for an index and return data - -BOOST_AUTO_TEST_SUITE_END() diff --git a/win_build/WinSDK/epanet2.def b/win_build/WinSDK/epanet2.def index ebd08f9..b813d20 100644 --- a/win_build/WinSDK/epanet2.def +++ b/win_build/WinSDK/epanet2.def @@ -5,6 +5,7 @@ EXPORTS ENaddcurve = _ENaddcurve@4 ENaddlink = _ENaddlink@20 ENaddnode = _ENaddnode@12 + ENadddemand = _ENadddemand@16 ENaddpattern = _ENaddpattern@4 ENaddrule = _ENaddrule@4 ENclearreport = _ENclearreport@0 @@ -14,6 +15,7 @@ EXPORTS ENcopyreport = _ENcopyreport@4 ENdeletecontrol = _ENdeletecontrol@4 ENdeletecurve = _ENdeletecurve@4 + ENdeletedemand = _ENdeletedemand@8 ENdeletelink = _ENdeletelink@8 ENdeletenode = _ENdeletenode@8 ENdeletepattern = _ENdeletepattern@4 @@ -30,7 +32,8 @@ EXPORTS ENgetcurveindex = _ENgetcurveindex@8 ENgetcurvelen = _ENgetcurvelen@8 ENgetcurvetype = _ENgetcurvetype@8 - ENgetcurvevalue = _ENgetcurvevalue@16 + ENgetcurvevalue = _ENgetcurvevalue@16 + ENgetdemandindex = _ENgetdemandindex@12 ENgetdemandmodel = _ENgetdemandmodel@16 ENgetdemandname = _ENgetdemandname@12 ENgetdemandpattern = _ENgetdemandpattern@12