diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100755 new mode 100644 index 93fbda1..cc15f07 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,8 +83,8 @@ ENDIF (MSVC) # configure file groups -file(GLOB EPANET_SOURCES RELATIVE ${PROJECT_SOURCE_DIR} src/*.c) -file(GLOB EPANET_LIB_ALL RELATIVE ${PROJECT_SOURCE_DIR} src/*) +file(GLOB EPANET_SOURCES RELATIVE ${PROJECT_SOURCE_DIR} src/*.c src/util/*.c) +file(GLOB EPANET_LIB_ALL RELATIVE ${PROJECT_SOURCE_DIR} src/* src/util/*) # exclude epanet python API from the default build list(REMOVE_ITEM EPANET_LIB_ALL "src/epanet_py.c") source_group("Library" FILES ${EPANET_LIB_ALL}) diff --git a/include/epanet2_2.h b/include/epanet2_2.h index 051e623..67bf91a 100644 --- a/include/epanet2_2.h +++ b/include/epanet2_2.h @@ -165,7 +165,7 @@ typedef struct Project *EN_Project; @return an error code */ int DLLEXPORT EN_setcomment(EN_Project ph, int object, int index, char *comment); - + /** @brief Retrieves the number of objects of a given type in a project. @param ph an EPANET project handle. @@ -851,7 +851,14 @@ typedef struct Project *EN_Project; These properties have units that depend on the units used for flow rate (see @ref Units). */ int DLLEXPORT EN_setjuncdata(EN_Project ph, int index, double elev, double dmnd, - char *dmndpat); + 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. diff --git a/src/demand.c b/src/demand.c new file mode 100644 index 0000000..6f005c4 --- /dev/null +++ b/src/demand.c @@ -0,0 +1,133 @@ +/* + ****************************************************************************** + 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 new file mode 100644 index 0000000..72dd3d8 --- /dev/null +++ b/src/demand.h @@ -0,0 +1,63 @@ +/* + ****************************************************************************** + 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 f6c7065..3419097 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -30,6 +30,8 @@ #include "text.h" #include "enumstxt.h" +#include "demand.h" + #ifdef _WIN32 #define snprintf _snprintf #endif @@ -1719,8 +1721,8 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index) Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; - int i, nIdx, size; - struct Sdemand *demand; + int i, nIdx; + int size; Stank *tank; Snode *node; Scontrol *control; @@ -1751,12 +1753,7 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index) nIdx = net->Njuncs; node = &net->Node[nIdx]; - demand = (struct Sdemand *)malloc(sizeof(struct Sdemand)); - demand->Base = 0.0; - demand->Pat = 0; - demand->Name = NULL; - demand->next = NULL; - node->D = demand; + node->D = NULL; // shift rest of Node array for (i = net->Nnodes; i >= net->Njuncs; i--) @@ -1857,7 +1854,7 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) int i, nodeType, tankindex; Snode *node; - Pdemand demand, nextdemand; + list_t *demand; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; @@ -1891,13 +1888,9 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) // Free memory allocated to node's demands, WQ source & comment demand = node->D; - while (demand != NULL) - { - nextdemand = demand->next; - free(demand->Name); - free(demand); - demand = nextdemand; - } + if (demand) + delete_list(demand); + free(node->S); free(node->Comment); @@ -2064,7 +2057,6 @@ int DLLEXPORT EN_getnodevalue(EN_Project p, int index, int property, double *val Quality *qual = &p->quality; double v = 0.0; - Pdemand demand; Psource source; Snode *Node = net->Node; @@ -2091,26 +2083,24 @@ int DLLEXPORT EN_getnodevalue(EN_Project p, int index, int property, double *val case EN_BASEDEMAND: v = 0.0; - // NOTE: primary demand category is last on demand list + // NOTE: primary demand category is first on demand list if (index <= nJuncs) { - for (demand = Node[index].D; demand != NULL; demand = demand->next) - { - v = (demand->Base); - } + list_t *demand = Node[index].D; + if (demand) + v = get_base_demand(head_list(demand, false)); } v *= Ucf[FLOW]; break; case EN_PATTERN: v = 0.0; - // NOTE: primary demand category is last on demand list + // NOTE: primary demand category is first on demand list if (index <= nJuncs) { - for (demand = Node[index].D; demand != NULL; demand = demand->next) - { - v = (double)(demand->Pat); - } + list_t *demand = Node[index].D; + if (demand) + v = get_pattern_index(head_list(demand, false)); } else v = (double)(Tank[index - nJuncs].Pat); break; @@ -2265,7 +2255,6 @@ int DLLEXPORT EN_setnodevalue(EN_Project p, int index, int property, double valu double *Ucf = p->Ucf; int i, j, n; - Pdemand demand; Psource source; double hTmp; @@ -2288,26 +2277,24 @@ int DLLEXPORT EN_setnodevalue(EN_Project p, int index, int property, double valu break; case EN_BASEDEMAND: - // NOTE: primary demand category is last on demand list + // NOTE: primary demand category is first on demand list if (index <= nJuncs) { - for (demand = Node[index].D; demand != NULL; demand = demand->next) - { - if (demand->next == NULL) demand->Base = value / Ucf[FLOW]; - } + list_t *demand = Node[index].D; + if (demand) + set_base_demand(head_list(demand, false), value / Ucf[FLOW]); } break; case EN_PATTERN: - // NOTE: primary demand category is last on demand list + // NOTE: primary demand category is first on demand list j = ROUND(value); if (j < 0 || j > nPats) return 205; if (index <= nJuncs) { - for (demand = Node[index].D; demand != NULL; demand = demand->next) - { - if (demand->next == NULL) demand->Pat = j; - } + list_t *demand = Node[index].D; + if (demand) + set_pattern_index(head_list(demand, false), j); } else Tank[index - nJuncs].Pat = j; break; @@ -2504,7 +2491,6 @@ int DLLEXPORT EN_setjuncdata(EN_Project p, int index, double elev, int i, patIndex = 0; Snode *Node = net->Node; - Pdemand demand; // Check that junction exists if (!p->Openflag) return 102; @@ -2526,14 +2512,56 @@ int DLLEXPORT EN_setjuncdata(EN_Project p, int index, double elev, // Assign values to junction's parameters Node[index].El = elev / p->Ucf[ELEV]; - for (demand = Node[index].D; demand != NULL; demand = demand->next) - { - if (demand->next == NULL) - { - demand->Base = dmnd / p->Ucf[FLOW]; - demand->Pat = patIndex; - } + + 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; } + 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); + return 0; } @@ -2714,16 +2742,20 @@ 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 - for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next) n++; - *numDemands = n; + list_t *demand_list = p->network.Node[nodeIndex].D; + if (demand_list) + *numDemands = size_list(demand_list); + else + *numDemands = 0; + return 0; } @@ -2738,9 +2770,6 @@ int DLLEXPORT EN_getbasedemand(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { - Pdemand d; - int n = 1; - // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; @@ -2748,10 +2777,13 @@ int DLLEXPORT EN_getbasedemand(EN_Project p, int nodeIndex, int demandIndex, // Retrieve demand for specified category if (nodeIndex <= p->network.Njuncs) { - for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; - d = d->next) n++; - if (n != demandIndex) return 253; - *baseDemand = (double)(d->Base * p->Ucf[FLOW]); + // 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); return 0; @@ -2769,9 +2801,6 @@ int DLLEXPORT EN_setbasedemand(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { - Pdemand d; - int n = 1; - // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; @@ -2779,10 +2808,22 @@ int DLLEXPORT EN_setbasedemand(EN_Project p, int nodeIndex, int demandIndex, // Set baseline demand for specified category if (nodeIndex <= p->network.Njuncs) { - for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; - d = d->next) n++; - if (n != demandIndex) return 253; - d->Base = baseDemand / p->Ucf[FLOW]; + 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; + + 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]); + } } return 0; } @@ -2798,8 +2839,7 @@ int DLLEXPORT EN_getdemandname(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { - Pdemand d; - int n = 1; + char *temp = NULL; strcpy(demandName, ""); @@ -2808,12 +2848,21 @@ int DLLEXPORT EN_getdemandname(EN_Project p, int nodeIndex, int demandIndex, if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; // Locate demand category record and retrieve its name - for (d = p->network.Node[nodeIndex].D; - n < demandIndex && d->next != NULL; d = d->next) n++; - if (n != demandIndex) return 253; + 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; - if (d->Name) strcpy(demandName, d->Name); - else demandName[0] = '\0'; return 0; } @@ -2829,8 +2878,6 @@ int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { - Pdemand d; - int n = 1; // Check for valid arguments if (!p->Openflag) return 102; @@ -2840,10 +2887,20 @@ int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex, if (strlen(demandName) > MAXID) return 252; // Locate demand category record and assign demandName to it - for (d = p->network.Node[nodeIndex].D; - n < demandIndex && d->next != NULL; d = d->next) n++; - if (n != demandIndex) return 253; - d->Name = xstrcpy(&d->Name, demandName, MAXID); + 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); + } return 0; } @@ -2859,16 +2916,18 @@ int DLLEXPORT EN_getdemandpattern(EN_Project p, int nodeIndex, int demandIndex, **---------------------------------------------------------------- */ { - Pdemand d; - int n = 1; - // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; - for (d = p->network.Node[nodeIndex].D; - n < demandIndex && d->next != NULL; d = d->next) n++; - if (n != demandIndex) return 253; - *patIndex = d->Pat; + + // 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); + return 0; } @@ -2886,21 +2945,28 @@ int DLLEXPORT EN_setdemandpattern(EN_Project p, int nodeIndex, int demandIndex, { Network *net = &p->network; - Pdemand d; - int n = 1; - // 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; // Locate demand category record and assign time pattern to it - if (nodeIndex <= net->Njuncs) - { - for (d = net->Node[nodeIndex].D; - n < demandIndex && d->next != NULL; d = d->next) n++; - if (n != demandIndex) return 253; - d->Pat = patIndex; + if (nodeIndex <= net->Njuncs) { + + 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); + } } return 0; } @@ -4338,7 +4404,7 @@ int DLLEXPORT EN_setcurveid(EN_Project p, int index, char *id) // Check if id name contains invalid characters if (!namevalid(id)) return 252; - + for (i = 1; i <= p->network.Ncurves; i++) { if (i != index && strcmp(id, p->network.Curve[i].ID) == 0) return 215; diff --git a/src/hydraul.c b/src/hydraul.c index 1e3532e..515be11 100755 --- a/src/hydraul.c +++ b/src/hydraul.c @@ -27,6 +27,9 @@ #include "funcs.h" #include "text.h" +#include "demand.h" + + const double QZERO = 1.e-6; // Equivalent to zero flow in cfs // Imported functions @@ -553,7 +556,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; @@ -563,15 +566,19 @@ void demands(Project *pr) for (i = 1; i <= net->Njuncs; i++) { sum = 0.0; - 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; - } + 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; + } + } hyd->NodeDemand[i] = sum; // Initialize pressure dependent demand diff --git a/src/inpfile.c b/src/inpfile.c index 946f364..be24616 100644 --- a/src/inpfile.c +++ b/src/inpfile.c @@ -28,6 +28,8 @@ Last Updated: 04/03/2019 #include "hash.h" #include "text.h" +#include "demand.h" + // Defined in enumstxt.h in EPANET.C extern char *LinkTxt[]; extern char *FormTxt[]; @@ -119,6 +121,48 @@ 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) /* ------------------------------------------------- @@ -135,9 +179,9 @@ int saveinpfile(Project *pr, const char *fname) Times *time = &pr->times; int i, j, n; - double d, kc, ke, km, ucf; + double d, kc, ke, km; char s[MAXLINE + 1], s1[MAXLINE + 1], s2[MAXLINE + 1]; - Pdemand demand; + //Pdemand demand; Psource source; FILE *f; Slink *link; @@ -325,22 +369,10 @@ int saveinpfile(Project *pr, const char *fname) if (link->Comment) fprintf(f, " ;%s", link->Comment); } + // Write [DEMANDS] section - 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_demands(pr, f); + // Write [EMITTERS] section fprintf(f, "\n\n"); diff --git a/src/input1.c b/src/input1.c index 219caaf..391fc69 100644 --- a/src/input1.c +++ b/src/input1.c @@ -23,6 +23,8 @@ Last Updated: 04/03/2019 #include +#include "demand.h" + #include "types.h" #include "funcs.h" #include "hash.h" @@ -220,9 +222,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 @@ -329,17 +331,17 @@ void adjustdata(Project *pr) tank = &net->Tank[i]; if (tank->Kb == MISSING) tank->Kb = qual->Kbulk; } - - // 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++) - { - node = &net->Node[i]; - for (demand = node->D; demand != NULL; demand = demand->next) - { - if (demand->Pat == 0) demand->Pat = parser->DefPat; - } - } + + // 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); + } + } + } // Remove QUALITY as a reporting variable if no WQ analysis if (qual->Qualflag == NONE) rpt->Field[QUALITY].Enabled = FALSE; @@ -542,7 +544,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; @@ -562,11 +564,17 @@ void convertunits(Project *pr) for (i = 1; i <= net->Njuncs; i++) { node = &net->Node[i]; - for (demand = node->D; demand != NULL; demand = demand->next) - { - demand->Base /= pr->Ucf[DEMAND]; - } + 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]; + // } } + hyd->Pmin /= pr->Ucf[PRESSURE]; hyd->Preq /= pr->Ucf[PRESSURE]; diff --git a/src/input3.c b/src/input3.c index 61324a3..7951cdf 100644 --- a/src/input3.c +++ b/src/input3.c @@ -23,6 +23,7 @@ Last Updated: 04/03/2019 #include +#include "demand.h" #include "types.h" #include "funcs.h" #include "hash.h" @@ -80,12 +81,15 @@ int juncdata(Project *pr) Parser *parser = &pr->parser; Hydraul *hyd = &pr->hydraul; - int p = 0; // time pattern index - int n; // number of tokens - int njuncs; // number of network junction nodes - double el, // elevation - y = 0.0; // base demand - Pdemand demand; // demand record + int p = 0; // time pattern index + int n; // number of tokens + 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 @@ -118,14 +122,13 @@ int juncdata(Project *pr) node->Type = JUNCTION; node->Comment = xstrcpy(&node->Comment, parser->Comment, MAXMSG); - // create a demand record, even if no demand is specified here. - demand = (struct Sdemand *) malloc(sizeof(struct Sdemand)); - if (demand == NULL) return 101; - demand->Base = y; - demand->Pat = p; - demand->Name = NULL; - demand->next = NULL; - node->D = demand; + // 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; + hyd->NodeDemand[njuncs] = y; return 0; } @@ -709,8 +712,12 @@ int demanddata(Project *pr) int j, n, p = 0; double y; - Pdemand demand; - Pdemand cur_demand; + + list_t *demand_list = NULL; + demand_data_t *demand_data = NULL; + + //Pdemand demand; + //Pdemand cur_demand; // Extract data from tokens n = parser->Ntokens; @@ -734,38 +741,27 @@ int demanddata(Project *pr) if (p == 0) return setError(parser, 2, 205); } - // Replace any demand entered in [JUNCTIONS] section - demand = net->Node[j].D; - if (hyd->NodeDemand[j] != MISSING) - { - // First category encountered will overwrite "dummy" demand category - // with what is specified in this 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. - } + + // 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); - // Otherwise add new demand to junction - else - { - cur_demand = net->Node[j].D; - while (cur_demand->next != NULL) cur_demand = cur_demand->next; - demand = (struct Sdemand *)malloc(sizeof(struct Sdemand)); - if (demand == NULL) return 101; - demand->Base = y; - demand->Pat = p; - demand->Name = NULL; - if (parser->Comment[0]) - { - demand->Name = xstrcpy(&demand->Name, parser->Comment, MAXID); - } - demand->next = NULL; - cur_demand->next = demand; - } return 0; } diff --git a/src/project.c b/src/project.c index f98be65..49696a3 100644 --- a/src/project.c +++ b/src/project.c @@ -29,6 +29,8 @@ #include "types.h" #include "funcs.h" +#include "demand.h" + int openfiles(Project *pr, const char *f1, const char *f2, const char *f3) /*---------------------------------------------------------------- ** Input: f1 = pointer to name of input file @@ -385,7 +387,7 @@ void freedata(Project *pr) */ { int j; - Pdemand demand, nextdemand; + //Pdemand demand, nextdemand; // Free memory for computed results free(pr->hydraul.NodeDemand); @@ -404,14 +406,10 @@ void freedata(Project *pr) for (j = 1; j <= pr->parser.MaxNodes; j++) { // Free memory used for demand category list - demand = pr->network.Node[j].D; - while (demand != NULL) - { - nextdemand = demand->next; - free(demand->Name); - free(demand); - demand = nextdemand; - } + list_t *demand = pr->network.Node[j].D; + if(demand) + delete_list(demand); + // Free memory used for WQ source data free(pr->network.Node[j].S); free(pr->network.Node[j].Comment); @@ -788,10 +786,20 @@ void adjustpattern(int *pat, int index) **---------------------------------------------------------------- */ { - if (*pat == index) *pat = 0; - else if (*pat > index) (*pat)--; + if (*pat == index) *pat = 0; + 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 @@ -801,16 +809,17 @@ 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 - for (demand = network->Node[j].D; demand != NULL; demand = demand->next) - { - adjustpattern(&demand->Pat, index); + 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); } // Adjust WQ source patterns source = network->Node[j].S; diff --git a/src/types.h b/src/types.h index 476afac..b848238 100755 --- a/src/types.h +++ b/src/types.h @@ -14,15 +14,18 @@ #ifndef TYPES_H #define TYPES_H -#include "hash.h" #include + +#include "hash.h" +#include "util/list.h" + /* ------------------------------------------- Definition of 4-byte integers & reals ------------------------------------------- */ -typedef float REAL4; +typedef float REAL4; typedef int INT4; /* @@ -64,7 +67,7 @@ typedef int INT4; Flow units conversion factors ---------------------------------------------- */ -#define GPMperCFS 448.831 +#define GPMperCFS 448.831 #define AFDperCFS 1.9837 #define MGDperCFS 0.64632 #define IMGDperCFS 0.5382 @@ -91,9 +94,9 @@ typedef int INT4; /* --------------------------------------------------------------------- - Conversion macros to be used in place of functions + Conversion macros to be used in place of functions --------------------------------------------------------------------- -*/ +*/ #define INT(x) ((int)(x)) // integer portion of x #define FRAC(x) ((x)-(int)(x)) // fractional part of x #define ABS(x) (((x)<0) ? -(x) : (x)) // absolute value of x @@ -109,10 +112,10 @@ typedef int INT4; /* ------------------------------------------------------ Macro to evaluate function x with error checking - (Fatal errors are numbered higher than 100) + (Fatal errors are numbered higher than 100) ------------------------------------------------------ */ -#define ERRCODE(x) (errcode = ((errcode>100) ? (errcode) : (x))) +#define ERRCODE(x) (errcode = ((errcode>100) ? (errcode) : (x))) /* ---------------------------------------------- @@ -187,7 +190,7 @@ typedef enum { HILEVEL, // act when grade above set level TIMER, // act when set time reached TIMEOFDAY // act when time of day occurs -} ControlType; +} ControlType; typedef enum { XHEAD, // pump cannot deliver head (closed) @@ -206,12 +209,12 @@ typedef enum { HW, // Hazen-Williams DW, // Darcy-Weisbach CM // Chezy-Manning -} HeadLossType; +} HeadLossType; typedef enum { US, // US SI // SI (metric) -} UnitsType; +} UnitsType; typedef enum { CFS, // cubic feet per second @@ -224,7 +227,7 @@ typedef enum { MLD, // megaliters per day CMH, // cubic meters per hour CMD // cubic meters per day -} FlowUnitsType; +} FlowUnitsType; typedef enum { PSI, // pounds per square inch @@ -236,14 +239,14 @@ typedef enum { LOW, // lower limit HI, // upper limit PREC // precision -} RangeType; +} RangeType; typedef enum { MIX1, // complete mix model MIX2, // 2-compartment model FIFO, // first in, first out model LIFO // last in, first out model -} MixType; +} MixType; typedef enum { SERIES, // point time series @@ -259,7 +262,7 @@ typedef enum { HEAD, // nodal hydraulic head PRESSURE, // nodal pressure QUALITY, // nodal water quality - + LENGTH, // link length DIAM, // link diameter FLOW, // link flow rate @@ -270,7 +273,7 @@ typedef enum { SETTING, // pump/valve setting REACTRATE, // avg. reaction rate in link FRICTION, // link friction factor - + POWER, // pump power output TIME, // simulation time VOLUME, // tank volume @@ -293,7 +296,7 @@ typedef enum { ENERHDR, // energy usage header NODEHDR, // node results header LINKHDR // link results header -} HdrType; +} HdrType; typedef enum { NEGATIVE = -1, // flow in reverse of pre-assigned direction @@ -302,13 +305,13 @@ typedef enum { } FlowDirection; typedef enum { - DDA, // demand driven analysis + DDA, // demand driven analysis PDA // pressure driven analysis } DemandModelType; /* ------------------------------------------------------ - Fundamental Data Structures + Fundamental Data Structures ------------------------------------------------------ */ @@ -336,14 +339,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 { @@ -370,7 +373,8 @@ typedef struct // Node Object double X; // x-coordinate double Y; // y-coordinate double El; // elevation - Pdemand D; // demand pointer +// Pdemand D; // demand pointer + list_t *D; // pointer to demand list Psource S; // source pointer double C0; // initial quality double Ke; // emitter coeff. @@ -383,7 +387,7 @@ typedef struct // Link Object { char ID[MAXID+1]; // link ID int N1; // start node index - int N2; // end node index + int N2; // end node index double Diam; // diameter double Len; // length double Kc; // roughness @@ -510,7 +514,7 @@ typedef struct s_ActionItem // Action List Item { int ruleIndex; // index of rule action belongs to Saction *action; // an action clause - struct s_ActionItem *next; // next action on the list + struct s_ActionItem *next; // next action on the list } SactionList; typedef struct // Mass Balance Components @@ -532,15 +536,15 @@ typedef struct // Mass Balance Components // Input File Parser Wrapper typedef struct { FILE *InFile; // Input file handle - + char DefPatID[MAXID + 1], // Default demand pattern ID InpFname[MAXFNAME + 1], // Input file name *Tok[MAXTOKS], // Array of token strings Comment[MAXMSG + 1], // Comment text LineComment[MAXMSG + 1]; // Full line comment - - int + + int MaxNodes, // Node count from input file */ MaxLinks, // Link count " " " MaxJuncs, // Junction count " " " @@ -559,7 +563,7 @@ typedef struct { Flowflag, // Flow units flag Pressflag, // Pressure units flag DefPat; // Default demand pattern - + Spattern *PrevPat; // Previous pattern processed Scurve *PrevCurve; // Previous curve processed double *X; // Temporary array for curve data @@ -590,7 +594,7 @@ typedef struct { typedef struct { FILE *RptFile; // Report file handle - + int Nperiods, // Number of reporting periods PageSize, // Lines/page in output report/ @@ -607,13 +611,13 @@ typedef struct { long LineNum, // Current line number PageNum; // Current page number - + char Atime[13], // Clock time (hrs:min:sec) Rpt1Fname[MAXFNAME+1], // Primary report file name Rpt2Fname[MAXFNAME+1], // Secondary report file name DateStamp[26]; // Current date & time - + SField Field[MAXVAR]; // Output reporting fields } Report; @@ -624,19 +628,19 @@ typedef struct { char HydFname[MAXFNAME+1], // Hydraulics file name OutFname[MAXFNAME+1]; // Binary output file name - + int Outflag, // Output file flag Hydflag, // Hydraulics flag SaveHflag, // Hydraulic results saved flag SaveQflag, // Quality results saved flag Saveflag; // General purpose save flag - - long + + long HydOffset, // Hydraulics file byte offset OutOffset1, // 1st output file byte offset OutOffset2; // 2nd output file byte offset - + FILE *OutFile, // Output file handle *HydFile, // Hydraulics file handle @@ -665,7 +669,7 @@ typedef struct { *Aij, // Non-zero, off-diagonal matrix coeffs. *F, // Right hand side vector *temp; // Array used by linear eqn. solver - + int Ncoeffs, // Number of non-zero matrix coeffs *Order, // Node-to-row of re-ordered matrix @@ -683,7 +687,7 @@ typedef struct { // Hydraulics Solver Wrapper typedef struct { - double + double *NodeHead, // Node hydraulic heads *NodeDemand, // Node demand + emitter flows *DemandFlow, // Demand outflows @@ -711,13 +715,13 @@ typedef struct { Dcost, // Energy demand charge/kw/day Emax, // Peak energy usage RelativeError, // Total flow change / total flow - MaxHeadError, // Max. error for link head loss + MaxHeadError, // Max. error for link head loss MaxFlowChange, // Max. change in link flow RelaxFactor, // Relaxation factor for flow updating *P, // Inverse of head loss derivatives *Y, // Flow correction factors *Xflow; // Inflow - outflow at each node - + int Epat, // Energy cost time pattern DemandModel, // Fixed or pressure dependent @@ -730,10 +734,10 @@ typedef struct { OpenHflag, // Hydraulic system opened flag Haltflag; // Flag to halt simulation - StatusType + StatusType *LinkStatus, // Link status *OldStatus; // Previous link/tank status - + Smatrix smatrix; // Sparse matrix storage } Hydraul; @@ -747,7 +751,7 @@ typedef struct { int Qualflag, // Water quality analysis flag OpenQflag, // Quality system opened flag - Reactflag, // Reaction indicator + Reactflag, // Reaction indicator OutOfMemory, // Out of memory indicator TraceNode, // Source node for flow tracing *SortedNodes; // Topologically sorted node indexes @@ -768,17 +772,17 @@ typedef struct { Bucf, // Bulk reaction units conversion factor Tucf, // Tank reaction units conversion factor BulkOrder, // Bulk flow reaction order - WallOrder, // Pipe wall reaction order - TankOrder, // Tank reaction order - Kbulk, // Global bulk reaction coeff. - Kwall, // Global wall reaction coeff. + WallOrder, // Pipe wall reaction order + TankOrder, // Tank reaction order + Kbulk, // Global bulk reaction coeff. + Kwall, // Global wall reaction coeff. Climit, // Limiting potential quality SourceQual, // External source quality *NodeQual, // Reported node quality state *PipeRateCoeff; // Pipe reaction rate coeffs. struct Mempool - *SegPool; // Memory pool for water quality segments + *SegPool; // Memory pool for water quality segments Pseg FreeSeg, // Pointer to unused segment @@ -808,7 +812,7 @@ typedef struct { Nrules, // Number of control rules Npats, // Number of time patterns Ncurves; // Number of data curves - + Snode *Node; // Node array Slink *Link; // Link array Stank *Tank; // Tank array @@ -836,13 +840,13 @@ typedef struct Project { Rules rules; // Rule-based controls wrapper Hydraul hydraul; // Hydraulics solver wrapper Quality quality; // Water quality solver wrapper - + double Ucf[MAXVAR]; // Unit conversion factors - + int Openflag, // Project open flag Warnflag; // Warning flag - + char Msg[MAXMSG+1], // General-purpose string: errors, messages Title[MAXTITLE][TITLELEN+1], // Project title @@ -850,9 +854,9 @@ typedef struct Project { TmpHydFname[MAXFNAME+1], // Temporary hydraulics file name TmpOutFname[MAXFNAME+1], // Temporary output file name TmpStatFname[MAXFNAME+1]; // Temporary statistic file name - - void (* viewprog) (char *); // Pointer to progress viewing function - + + void (* viewprog) (char *); // Pointer to progress viewing function + } Project, *EN_Project; #endif diff --git a/src/util/list.c b/src/util/list.c index d3e41d8..443fcf2 100644 --- a/src/util/list.c +++ b/src/util/list.c @@ -22,6 +22,7 @@ #endif #include +#include #include #include "list.h" @@ -29,6 +30,7 @@ typedef struct list_node_s { void *data; + int key; struct list_node_s *next; } list_node_t; @@ -42,6 +44,9 @@ typedef struct list_s { } list_t; +// local declarations +int gen_key(); + list_t *create_list(size_t elementSize, freeFunction freeFn) { list_t *list; @@ -67,12 +72,14 @@ void delete_list(list_t *list) free(list); } -void prepend_list(list_t *list, void *element) +int prepend_list(list_t *list, void *element) { list_node_t *node = malloc(sizeof(list_node_t)); node->data = malloc(list->elementSize); memcpy(node->data, element, list->elementSize); + node->key = gen_key(); + node->next = list->head; list->head = node; @@ -82,14 +89,18 @@ void prepend_list(list_t *list, void *element) } list->logicalLength++; + + return node->key; } -void append_list(list_t *list, void *element) +int append_list(list_t *list, void *element) { list_node_t *node = malloc(sizeof(list_node_t)); node->data = malloc(list->elementSize); node->next = NULL; + node->key = gen_key(); + memcpy(node->data, element, list->elementSize); if(list->logicalLength == 0) { @@ -100,34 +111,37 @@ void append_list(list_t *list, void *element) } list->logicalLength++; + + return node->key; } + void for_each_list(list_t *list, listIterator iterator) { assert(iterator != NULL); list_node_t *node = list->head; bool result = true; - while(node != NULL && result) { - result = iterator(node); + + while(node != NULL && result) { + result = iterator(node); node = node->next; } } list_node_t *head_list(list_t *list, bool removeFromList) -// // Warning: When node is removed caller is responsible for freeing it. -// { - assert(list->head != NULL); - - list_node_t *node = list->head; - if(removeFromList) { - // Disconnecting head node - list->head = node->next; - list->logicalLength--; + if (list) { + list_node_t *node = list->head; + if (removeFromList) { + // Disconnecting head node + list->head = node->next; + list->logicalLength--; + } + return node; } - return node; + return NULL; } list_node_t *tail_list(list_t *list) @@ -136,16 +150,83 @@ list_node_t *tail_list(list_t *list) return list->tail; } +list_node_t *get_nth_list(list_t *list, int index) +{ + int n; + list_node_t *lnode; + + for (n = 1, lnode = first_list(list); n < index && done_list(lnode); n++, lnode = next_list(lnode)); + if (n != index) + return NULL; + else + return lnode; +} + +list_node_t *search_list(list_t *list, int key) +// Naive list search. Will not perform for large lists. +{ + list_node_t *lnode = first_list(list); + + while (done_list(lnode)) { + if (get_key(lnode) == key) + return lnode; + lnode = next_list(lnode); + } + return NULL; +} + +void remove_node(list_t *list, int key) +{ + list_node_t *temp; + list_node_t *target = search_list(list, key); + + if (target == list->head) + delete_node(list, head_list(list, true)); + + else if (target == list->tail) { + // find next to last node + temp = list->head; + while (temp != NULL) { + if (temp->next == target) + break; + temp = temp->next; + } + // detatch tail + temp->next = NULL; + delete_node(list, list->tail); + } + else { + temp = target->next; + list->freeFn(target->data); + free(target->data); + + target->data = temp->data; + target->next = temp->next; + + free(temp); + } +} + int size_list(list_t *list) { return list->logicalLength; } +int get_key(list_node_t *lnode) +{ + return lnode->key; +} + void *get_data(list_node_t *lnode) { return lnode->data; } +list_node_t *get_next(list_node_t *lnode) +{ + return lnode->next; +} + void delete_node(list_t *list, list_node_t *lnode) { if (list->freeFn) @@ -155,22 +236,11 @@ void delete_node(list_t *list, list_node_t *lnode) free(lnode); } -// -// Iterator first/done/next operations provide containment for list abstraction -// http://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)Iterators.html -// Accessed on April 11, 2019 -// -list_node_t *first_list(list_t *list) -{ - return list->head; -} -bool done_list(list_node_t *lnode) -{ - return lnode != NULL; -} +// local functions -list_node_t *next_list(list_node_t *lnode) +int gen_key() +// Naive key generator. No guarentee of uniqueness { - return lnode->next; + return rand(); } diff --git a/src/util/list.h b/src/util/list.h index 090fa4a..599acde 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -19,6 +19,7 @@ #include + #if defined(__cplusplus) extern "C" { #endif @@ -28,8 +29,8 @@ extern "C" { typedef struct list_node_s list_node_t; typedef struct list_s list_t; -typedef void(*freeFunction)(void *); -typedef bool(*listIterator)(list_node_t *); +typedef void (*freeFunction) (void *); +typedef bool (*listIterator) (list_node_t *); /** @@ -45,14 +46,14 @@ with each node’s data pointer. void delete_list(list_t *list); /** -@brief Adds a node to the head of the list. +@brief Adds a node to the head of the list and returns its key. */ -void prepend_list(list_t *list, void *element); +int prepend_list(list_t *list, void *element); /** -@brief Adds a node to the tail of the list. +@brief Adds a node to the tail of the list and returns its key. */ -void append_list(list_t *list, void *element); +int append_list(list_t *list, void *element); /** @brief Returns the number of items in the list. @@ -65,6 +66,16 @@ int size_list(list_t *list); */ void *get_data(list_node_t *lnode); +/** +@brief Returns list node's key value. +*/ +int get_key(list_node_t *lnode); + +/** +@brief Returns next list node. +*/ +list_node_t *get_next(list_node_t *lnode); + /** @brief Frees memory associated with a list node. */ @@ -87,21 +98,41 @@ list_node_t *head_list(list_t *list, bool removeFromList); */ list_node_t *tail_list(list_t *list); +/** +@brief Returns nth node of the list or NULL. +*/ +list_node_t *get_nth_list(list_t *list, int index); + +/** +@brief Returns the list node with the given key or NULL. +*/ +list_node_t *search_list(list_t *list, int key); + +/** +@brief Removes the list node with the given key from the list. +*/ +void remove_node(list_t *list, int key); + +// +// Iterator first/done/next operations +// http://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)Iterators.html +// Accessed on April 11, 2019 +// /** @brief Returns list head node. */ -list_node_t *first_list(list_t *list); +static inline list_node_t *first_list(list_t *list) { return head_list(list, false); } /** @brief Returns true if end of list false otherwise. */ -bool done_list(list_node_t *lnode); +static inline bool done_list(list_node_t *lnode) { return lnode != NULL; } /** @brief Returns next node in the list. */ -list_node_t *next_list(list_node_t *lnode); +static inline list_node_t *next_list(list_node_t *lnode) { return get_next(lnode); } #if defined(__cplusplus) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0a5f91e..62f6f00 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,15 @@ 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 diff --git a/tests/test_demand.cpp b/tests/test_demand.cpp index 9a02ff7..80bf042 100644 --- a/tests/test_demand.cpp +++ b/tests/test_demand.cpp @@ -18,6 +18,7 @@ BOOST_AUTO_TEST_SUITE (test_demand) + BOOST_AUTO_TEST_CASE(test_categories_save) { int error = 0; @@ -38,7 +39,7 @@ BOOST_AUTO_TEST_CASE(test_categories_save) error = EN_getdemandname(ph, Nindex, ndem, demname); BOOST_REQUIRE(error == 0); - error = EN_setdemandname(ph, Nindex, ndem, (char *)"Demand category name"); + error = EN_setdemandname(ph, Nindex, ndem, (char *)"CUB_SCOUT_MOTOR_POOL"); BOOST_REQUIRE(error == 0); error = EN_saveinpfile(ph, "net1_dem_cat.inp"); BOOST_REQUIRE(error == 0); @@ -49,6 +50,7 @@ BOOST_AUTO_TEST_CASE(test_categories_save) BOOST_REQUIRE(error == 0); } + BOOST_AUTO_TEST_CASE(test_categories_reopen, * boost::unit_test::depends_on("test_demand/test_categories_save")) { int error = 0; @@ -70,9 +72,9 @@ BOOST_AUTO_TEST_CASE(test_categories_reopen, * boost::unit_test::depends_on("tes char demname[31]; error = EN_getdemandname(ph, Nindex, ndem, demname); - BOOST_REQUIRE(error == 0); + BOOST_CHECK(error == 0); - BOOST_CHECK(check_string(demname, "Demand category name")); + BOOST_CHECK(check_string(demname, "CUB_SCOUT_MOTOR_POOL")); error = EN_close(ph); BOOST_REQUIRE(error == 0); @@ -80,4 +82,34 @@ BOOST_AUTO_TEST_CASE(test_categories_reopen, * boost::unit_test::depends_on("tes BOOST_REQUIRE(error == 0); } +BOOST_FIXTURE_TEST_CASE(test_adddemand, FixtureSingleNode) +{ + int key, demand_key; + + error = EN_adddemand(ph, node_qhut, 100.0, "PrimaryPattern", "PrimaryDemand", &demand_key); + 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); + 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); + 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); + BOOST_CHECK(error == 0); + + error = EN_removedemand(ph, node_qhut, key); + BOOST_CHECK(error == 0); +} + + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/test_demand_data.cpp b/tests/test_demand_data.cpp new file mode 100644 index 0000000..bbf8526 --- /dev/null +++ b/tests/test_demand_data.cpp @@ -0,0 +1,295 @@ +/* +****************************************************************************** +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/test_pattern.cpp b/tests/test_pattern.cpp index 13d0510..596c8c0 100644 --- a/tests/test_pattern.cpp +++ b/tests/test_pattern.cpp @@ -18,7 +18,51 @@ BOOST_AUTO_TEST_SUITE (pattern) -BOOST_AUTO_TEST_CASE(add_set_pattern) +BOOST_FIXTURE_TEST_CASE(test_set_get_default_pattern, FixtureOpenClose) +{ + // Assign the default pattern index + int defPatIdx = 1; + int patIdx; + + // Rename the default pattern + error = EN_setpatternid(ph, defPatIdx, (char *)"Pat1"); + BOOST_REQUIRE(error == 0); + + error = EN_getpatternindex(ph, (char *)"Pat1", &patIdx); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK(defPatIdx == patIdx); +} + +BOOST_FIXTURE_TEST_CASE(test_add_delete, FixtureOpenClose) +{ + double f2[] = { 2.1, 2.2 }; + double f3[] = { 3.1, 3.2, 3.3, 3.4 }; + + // Add 2 new patterns + error = EN_addpattern(ph, (char *)"Pat2"); + BOOST_REQUIRE(error == 0); + error = EN_addpattern(ph, (char *)"Pat3"); + BOOST_REQUIRE(error == 0); + + error = EN_setpattern(ph, 2, f2, 2); + BOOST_REQUIRE(error == 0); + error = EN_setpattern(ph, 3, f3, 4); + BOOST_REQUIRE(error == 0); + + // Delete Pat2 + error = EN_deletepattern(ph, 2); + BOOST_REQUIRE(error == 0); + + // Check that there are now 2 patterns + int n; + error = EN_getcount(ph, EN_PATCOUNT, &n); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(n == 2); +} + + +BOOST_AUTO_TEST_CASE(test_add_set) { std::string path_inp(DATA_PATH_NET1); std::string path_rpt(DATA_PATH_RPT); @@ -34,7 +78,7 @@ BOOST_AUTO_TEST_CASE(add_set_pattern) BOOST_REQUIRE(error == 0); // Assign the default pattern index - int defPatIdx = 1; + int n, defPatIdx = 1; int patIdx; // Rename the default pattern @@ -56,8 +100,7 @@ BOOST_AUTO_TEST_CASE(add_set_pattern) // Delete Pat2 EN_deletepattern(ph, 2); - // Check that there are now 2 patterns - int n; + //Check that there are now 2 patterns EN_getcount(ph, EN_PATCOUNT, &n); BOOST_REQUIRE(n == 2); diff --git a/tests/test_project.cpp b/tests/test_project.cpp index 098f8f4..dd0c3b7 100644 --- a/tests/test_project.cpp +++ b/tests/test_project.cpp @@ -81,10 +81,10 @@ BOOST_AUTO_TEST_CASE(test_save) error = EN_saveinpfile(ph_save, "test_reopen.inp"); BOOST_REQUIRE(error == 0); - BOOST_CHECK(boost::filesystem::exists("test_reopen.inp") == true); + BOOST_CHECK(boost::filesystem::exists("test_reopen.inp") == true); - error = EN_close(ph_save); - BOOST_REQUIRE(error == 0); + error = EN_close(ph_save); + BOOST_REQUIRE(error == 0); EN_deleteproject(&ph_save); } diff --git a/tests/test_toolkit.hpp b/tests/test_toolkit.hpp index 08f6538..b682d5d 100644 --- a/tests/test_toolkit.hpp +++ b/tests/test_toolkit.hpp @@ -102,6 +102,26 @@ struct FixtureAfterStep{ EN_Project ph; }; +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); + } + + ~FixtureSingleNode() { + EN_close(ph); + EN_deleteproject(&ph); + } + int error, index, node_qhut; + EN_Project ph; +}; + + boost::test_tools::predicate_result check_cdd_double(std::vector& test, std::vector& ref, long cdd_tol); boost::test_tools::predicate_result check_string(std::string test, std::string ref); diff --git a/tests/util/test_list.cpp b/tests/util/test_list.cpp index 7844e6e..86fcd29 100644 --- a/tests/util/test_list.cpp +++ b/tests/util/test_list.cpp @@ -13,7 +13,9 @@ ****************************************************************************** */ +#include #include +#include #define BOOST_TEST_MODULE list #include @@ -35,7 +37,7 @@ int *get_int_data(list_node_t *lnode) { bool iterate_int(list_node_t *lnode) { - printf("Found value: %d\n", *get_int_data(lnode)); + printf("At Key: %d Found value: %d\n", get_key(lnode), *get_int_data(lnode)); return true; } @@ -57,14 +59,22 @@ 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++) { - append_list(list, &i); + key[i] = append_list(list, &i); } BOOST_CHECK(size_list(list) == 10); - for_each_list(list, iterate_int); + 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); } @@ -111,7 +121,8 @@ BOOST_FIXTURE_TEST_CASE(test_string_list, FixtureStrings) { BOOST_CHECK(size_list(list) == 5); - for_each_list(list, iterate_string); + listIterator iterator = (listIterator)iterate_string; + for_each_list(list, iterator); } @@ -177,38 +188,56 @@ bool iterate_test_data(list_node_t *lnode) } +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"); - append_list(list, &data); + head_key = append_list(list, &data); data = create_test_data(2, "Kevin"); - append_list(list, &data); + 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); - BOOST_CHECK(size_list(list) == 3); + data = create_test_data(5, "Jimi"); + tail_key = append_list(list, &data); - for_each_list(list, iterate_test_data); + BOOST_CHECK(size_list(list) == 5); + + listIterator iterator = (listIterator)iterate_test_data; + for_each_list(list, iterator); - list_node_t *lnode; - // Iterate over list while maintaining containment of list abstraction - for (lnode = first_list(list); done_list(lnode); lnode = next_list(lnode)) { - test_data_t *test_data = get_test_data(lnode); + // locate a list node by a key + printf("Found %s!\n", get_name(search_list(list, key))); - if (test_data->num == 2) - printf("Found %s!\n", test_data->name); - } + printf("Removing Kevin\n"); + remove_node(list, key); + for_each_list(list, iterator); - lnode = head_list(list, true); + 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);