/* ********************************************************************** INPUT2.C -- Input data file interpreter for EPANET VERSION: 2.00 DATE: 5/30/00 9/7/00 10/25/00 AUTHOR: L. Rossman US EPA - NRMRL This module reads and interprets the input data from file InFile. The entry points for this module are: netsize() -- called from ENopen() in EPANET.C readdata() -- called from getdata() in INPUT1.C The following utility functions are all called from INPUT3.C addnodeID() addlinkID() findID() getfloat() ********************************************************************** */ #include #include #include #ifndef __APPLE__ #include #endif #include "hash.h" #include "text.h" #include "types.h" #include "epanet2.h" #include "funcs.h" #include #define MAXERRS 10 /* Max. input errors reported */ /* Defined in enumstxt.h in EPANET.C */ extern char *SectTxt[]; /* Input section keywords */ int netsize(EN_Project *pr) /* **-------------------------------------------------------------- ** Input: none ** Output: returns error code ** Purpose: determines number of system components **-------------------------------------------------------------- */ { parser_data_t *par = &pr->parser; char line[MAXLINE + 1]; /* Line from input data file */ char *tok; /* First token of line */ int sect, newsect; /* Input data sections */ int errcode = 0; /* Error code */ /* Initialize network component counts */ par->MaxJuncs = 0; par->MaxTanks = 0; par->MaxPipes = 0; par->MaxPumps = 0; par->MaxValves = 0; par->MaxControls = 0; par->MaxRules = 0; par->MaxCurves = 0; sect = -1; /* Add a default pattern 0 */ par->MaxPats = -1; addpattern(par,""); if (par->InFile == NULL) { return (0); } /* Make pass through data file counting number of each component */ while (fgets(line, MAXLINE, par->InFile) != NULL) { /* Skip blank lines & those beginning with a comment */ tok = strtok(line, SEPSTR); if (tok == NULL) continue; if (*tok == ';') continue; /* Check if line begins with a new section heading */ if (tok[0] == '[') { newsect = findmatch(tok, SectTxt); if (newsect >= 0) { sect = newsect; if (sect == _END) break; continue; } else continue; } /* Add to count of current component */ switch (sect) { case _JUNCTIONS: par->MaxJuncs++; break; case _RESERVOIRS: case _TANKS: par->MaxTanks++; break; case _PIPES: par->MaxPipes++; break; case _PUMPS: par->MaxPumps++; break; case _VALVES: par->MaxValves++; break; case _CONTROLS: par->MaxControls++; break; case _RULES: addrule(par,tok); break; /* See RULES.C */ case _PATTERNS: errcode = addpattern(par, tok); break; case _CURVES: errcode = addcurve(par, tok); break; } if (errcode) break; } par->MaxNodes = par->MaxJuncs + par->MaxTanks; par->MaxLinks = par->MaxPipes + par->MaxPumps + par->MaxValves; if (par->MaxPats < 1) par->MaxPats = 1; if (!errcode) { if (par->MaxJuncs < 1) errcode = 223; /* Not enough nodes */ else if (par->MaxTanks == 0) errcode = 224; /* No tanks */ } return (errcode); } /* End of netsize */ int readdata(EN_Project *pr) /* **-------------------------------------------------------------- ** Input: none ** Output: returns error code ** Purpose: reads contents of input data file **-------------------------------------------------------------- */ { EN_Network *net = &pr->network; parser_data_t *par = &pr->parser; char line[MAXLINE + 1], /* Line from input data file */ wline[MAXLINE + 1]; /* Working copy of input line */ int sect, newsect, /* Data sections */ errcode = 0, /* Error code */ inperr, errsum; /* Error code & total error count */ /* Allocate input buffer */ par->X = (double *)calloc(MAXTOKS, sizeof(double)); ERRCODE(MEMCHECK(par->X)); if (!errcode) { /* Initialize number of network components */ par->Ntitle = 0; net->Nnodes = 0; net->Njuncs = 0; net->Ntanks = 0; net->Nlinks = 0; net->Npipes = 0; net->Npumps = 0; net->Nvalves = 0; net->Ncontrols = 0; net->Nrules = 0; net->Ncurves = par->MaxCurves; net->Npats = par->MaxPats; par->PrevPat = NULL; par->PrevCurve = NULL; sect = -1; errsum = 0; /* Read each line from input file. */ while (fgets(line, MAXLINE, par->InFile) != NULL) { /* Make copy of line and scan for tokens */ strcpy(wline, line); par->Ntokens = gettokens(wline, par->Tok, MAXTOKS, par->Comment); /* Skip blank lines and comments */ if (par->Ntokens == 0) continue; if (*par->Tok[0] == ';') continue; /* Check if max. length exceeded */ if (strlen(line) >= MAXLINE) { char errMsg[MAXMSG+1]; EN_geterror(214, errMsg, MAXMSG); sprintf(pr->Msg, "%s section: %s", errMsg, SectTxt[sect]); writeline(pr, pr->Msg); writeline(pr, line); errsum++; } /* Check if at start of a new input section */ if (par->Tok[0][0] == '[') { newsect = findmatch(par->Tok[0], SectTxt); if (newsect >= 0) { sect = newsect; if (sect == _END) break; continue; } else { inperrmsg(pr, 201, sect, line); errsum++; break; } } /* Otherwise process next line of input in current section */ else { if (sect >= 0) // for cases were no section is present on the top of the // input file { inperr = newline(pr, sect, line); if (inperr > 0) { inperrmsg(pr,inperr, sect, line); errsum++; } } else { errcode = 200; break; } } /* Stop if reach end of file or max. error count */ if (errsum == MAXERRS) break; } /* End of while */ /* Check for errors */ if (errsum > 0) errcode = 200; } /* Check for unlinked nodes */ if (!errcode) errcode = unlinked(pr); /* Get pattern & curve data from temp. lists */ if (!errcode) errcode = getpatterns(pr); if (!errcode) errcode = getcurves(pr); if (!errcode) errcode = getpumpparams(pr); /* Free input buffer */ free(par->X); return (errcode); } /* End of readdata */ int newline(EN_Project *pr, int sect, char *line) /* **-------------------------------------------------------------- ** Input: sect = current section of input file ** *line = line read from input file ** Output: returns error code or 0 if no error found ** Purpose: processes a new line of data from input file **-------------------------------------------------------------- */ { parser_data_t *par = &pr->parser; int n; switch (sect) { case _TITLE: if (par->Ntitle < 3) { n = (int)strlen(line); if (line[n - 1] == 10) line[n - 1] = ' '; strncpy(pr->Title[par->Ntitle], line, MAXMSG); par->Ntitle++; } return (0); case _JUNCTIONS: return (juncdata(pr)); case _RESERVOIRS: case _TANKS: return (tankdata(pr)); case _PIPES: return (pipedata(pr)); case _PUMPS: return (pumpdata(pr)); case _VALVES: return (valvedata(pr)); case _PATTERNS: return (patterndata(pr)); case _CURVES: return (curvedata(pr)); case _DEMANDS: return (demanddata(pr)); case _CONTROLS: return (controldata(pr)); case _RULES: return (ruledata(pr)); /* See RULES.C */ case _SOURCES: return (sourcedata(pr)); case _EMITTERS: return (emitterdata(pr)); case _QUALITY: return (qualdata(pr)); case _STATUS: return (statusdata(pr)); case _ROUGHNESS: return (0); case _ENERGY: return (energydata(pr)); case _REACTIONS: return (reactdata(pr)); case _MIXING: return (mixingdata(pr)); case _REPORT: return (reportdata(pr)); case _TIMES: return (timedata(pr)); case _OPTIONS: return (optiondata(pr)); /* Data in these sections are not used for any computations */ case _COORDS: if (par->Coordflag == TRUE) { return (coordata(pr)); } else return (0); case _LABELS: return (0); case _TAGS: return (0); case _VERTICES: return (0); case _BACKDROP: return (0); } return (201); } /* end of newline */ int getpumpparams(EN_Project *pr) /* **------------------------------------------------------------- ** Input: none ** Output: returns error code ** Purpose: computes & checks pump curve parameters **-------------------------------------------------------------- */ { int i, j = 0, k, m, n = 0; double a, b, c, h0 = 0.0, h1 = 0.0, h2 = 0.0, q1 = 0.0, q2 = 0.0; char errMsg[MAXMSG+1]; Spump *pump; Slink *link; Scurve *curve; EN_Network *net = &pr->network; for (i = 1; i <= net->Npumps; i++) { pump = &net->Pump[i]; k = pump->Link; link = &net->Link[k]; if (pump->Ptype == CONST_HP) { /* Constant Hp pump */ pump->H0 = 0.0; pump->R = -8.814 * link->Km; pump->N = -1.0; pump->Hmax = BIG; /* No head limit */ pump->Qmax = BIG; /* No flow limit */ pump->Q0 = 1.0; /* Init. flow = 1 cfs */ continue; } else if (pump->Ptype == NOCURVE) { /* Pump curve specified */ j = pump->Hcurve; /* Get index of head curve */ if (j == 0) { /* Error: No head curve */ EN_geterror(226, errMsg, MAXMSG); sprintf(pr->Msg, "%s link: %s", errMsg, link->ID); writeline(pr, pr->Msg); return (200); } curve = &net->Curve[j]; curve->Type = P_CURVE; n = curve->Npts; if (n == 1) { /* Only a single h-q point supplied so use generic */ pump->Ptype = POWER_FUNC; /* power function curve. */ q1 = curve->X[0]; h1 = curve->Y[0]; h0 = 1.33334 * h1; q2 = 2.0 * q1; h2 = 0.0; } else if (n == 3 && curve->X[0] == 0.0) /* 3 h-q points supplied with */ { /* shutoff head so use fitted */ pump->Ptype = POWER_FUNC; /* power function curve. */ h0 = curve->Y[0]; q1 = curve->X[1]; h1 = curve->Y[1]; q2 = curve->X[2]; h2 = curve->Y[2]; } else { // use a custom curve, referenced by ID pump->Ptype = CUSTOM; /* Else use custom pump curve.*/ // at this point, j is set to that curve's index. } /* Compute shape factors & limits of power function pump curves */ if (pump->Ptype == POWER_FUNC) { if (!powercurve(h0, h1, h2, q1, q2, &a, &b, &c)) { /* Error: Invalid curve */ EN_geterror(227, errMsg, MAXMSG); sprintf(pr->Msg, "%s link: %s", errMsg, link->ID); writeline(pr, pr->Msg); return (200); } else { pump->H0 = -a; pump->R = -b; pump->N = c; pump->Q0 = q1; pump->Qmax = pow((-a / b), (1.0 / c)); pump->Hmax = h0; } } } /* Assign limits to custom pump curves */ if (pump->Ptype == CUSTOM) { curve = &net->Curve[j]; for (m = 1; m < n; m++) { if (curve->Y[m] >= curve->Y[m - 1]) { /* Error: Invalid curve */ EN_geterror(227, errMsg, MAXMSG); sprintf(pr->Msg, "%s link: %s", errMsg, link->ID); writeline(pr, pr->Msg); return (200); } } pump->Qmax = curve->X[n - 1]; pump->Q0 = (curve->X[0] + pump->Qmax) / 2.0; pump->Hmax = curve->Y[0]; } } /* Next pump */ return (0); } int addnodeID(EN_Network *net, int n, char *id) /* **------------------------------------------------------------- ** Input: n = node index ** id = ID label ** Output: returns 0 if ID already in use, 1 if not ** Purpose: adds a node ID to the Node Hash Table **-------------------------------------------------------------- */ { if (findnode(net,id)) { return (0); /* see EPANET.C */ } strncpy(net->Node[n].ID, id, MAXID); ENHashTableInsert(net->NodeHashTable, net->Node[n].ID, n); /* see HASH.C */ return (1); } int addlinkID(EN_Network *net, int n, char *id) /* **------------------------------------------------------------- ** Input: n = link index ** id = ID label ** Output: returns 0 if ID already in use, 1 if not ** Purpose: adds a link ID to the Link Hash Table **-------------------------------------------------------------- */ { if (findlink(net,id)) { return (0); /* see EPANET.C */ } strncpy(net->Link[n].ID, id, MAXID); ENHashTableInsert(net->LinkHashTable, net->Link[n].ID, n); /* see HASH.C */ return (1); } int addpattern(parser_data_t *par, char *id) /* **------------------------------------------------------------- ** Input: id = pattern ID label ** Output: returns error code ** Purpose: adds a new pattern to the database **-------------------------------------------------------------- */ { STmplist *p; /* Check if ID is same as last one processed */ if (par->Patlist != NULL && strcmp(id, par->Patlist->ID) == 0) { return (0); } /* Check that pattern was not already created */ if (findID(id, par->Patlist) == NULL) { /* Update pattern count & create new list element */ (par->MaxPats)++; p = (STmplist *)malloc(sizeof(STmplist)); if (p == NULL) return (101); /* Initialize list element properties */ else { p->i = par->MaxPats; strncpy(p->ID, id, MAXID); p->x = NULL; p->y = NULL; p->next = par->Patlist; par->Patlist = p; } } return (0); } int addcurve(parser_data_t *par, char *id) /* **------------------------------------------------------------- ** Input: id = curve ID label ** Output: returns error code ** Purpose: adds a new curve to the database **-------------------------------------------------------------- */ { STmplist *c; /* Check if ID is same as last one processed */ if (par->Curvelist != NULL && strcmp(id, par->Curvelist->ID) == 0) return (0); /* Check that curve was not already created */ if (findID(id, par->Curvelist) == NULL) { /* Update curve count & create new list element */ (par->MaxCurves)++; c = (STmplist *)malloc(sizeof(STmplist)); if (c == NULL) return (101); /* Initialize list element properties */ else { c->i = par->MaxCurves; strncpy(c->ID, id, MAXID); c->x = NULL; c->y = NULL; c->next = par->Curvelist; par->Curvelist = c; } } return (0); } STmplist *findID(char *id, STmplist *list) /* **------------------------------------------------------------- ** Input: id = ID label ** list = pointer to head of a temporary list ** Output: returns list item with requested ID label ** Purpose: searches for item in temporary list **------------------------------------------------------------- */ { STmplist *item; for (item = list; item != NULL; item = item->next) { if (strcmp(item->ID, id) == 0) { return (item); } } return (NULL); } int unlinked(EN_Project *pr) /* **-------------------------------------------------------------- ** Input: none ** Output: returns error code if any unlinked junctions found ** Purpose: checks for unlinked junctions in network ** ** NOTE: unlinked tanks have no effect on computations. **-------------------------------------------------------------- */ { EN_Network *net = &pr->network; int *marked; int i, err, errcode; char errMsg[MAXMSG+1]; errcode = 0; err = 0; marked = (int *)calloc(net->Nnodes + 1, sizeof(int)); ERRCODE(MEMCHECK(marked)); if (!errcode) { memset(marked, 0, (net->Nnodes + 1) * sizeof(int)); for (i = 1; i <= net->Nlinks; i++) /* Mark end nodes of each link */ { marked[net->Link[i].N1]++; marked[net->Link[i].N2]++; } for (i = 1; i <= net->Njuncs; i++) /* Check each junction */ { if (marked[i] == 0) /* If not marked then error */ { err++; EN_geterror(233, errMsg, MAXMSG); sprintf(pr->Msg, "%s node: %s", errMsg, net->Node[i].ID); writeline(pr, pr->Msg); } if (err >= MAXERRS) break; } if (err > 0) errcode = 200; } free(marked); return (errcode); } /* End of unlinked */ int getpatterns(EN_Project *pr) /* **----------------------------------------------------------- ** Input: none ** Output: returns error code ** Purpose: retrieves pattern data from temporary linked list **------------------------------------------------------------- */ { int i, j; SFloatlist *f; STmplist *pat; EN_Network *net = &pr->network; hydraulics_t *hyd = &pr->hydraulics; parser_data_t *par = &pr->parser; /* Start at head of list */ pat = par->Patlist; /* Traverse list of patterns */ while (pat != NULL) { /* Get index of current pattern in Pattern array */ i = pat->i; /* Check if this is the default pattern */ if (strcmp(pat->ID, par->DefPatID) == 0) { hyd->DefPat = i; } if (i >= 0 && i <= par->MaxPats) { /* Save pattern ID */ Spattern *pattern = &net->Pattern[i]; strcpy(pattern->ID, pat->ID); /* Give pattern a length of at least 1 */ if (pattern->Length == 0) pattern->Length = 1; pattern->F = (double *)calloc(pattern->Length, sizeof(double)); if (pattern->F == NULL) return (101); /* Start at head of pattern multiplier list */ /* (which holds multipliers in reverse order)*/ f = pat->x; j = pattern->Length - 1; /* Use at least one multiplier equal to 1.0 */ if (f == NULL) pattern->F[0] = 1.0; /* Traverse list, storing multipliers in Pattern array */ else while (f != NULL && j >= 0) { pattern->F[j] = f->value; f = f->next; j--; } } pat = pat->next; } return (0); } int getcurves(EN_Project *pr) /* **----------------------------------------------------------- ** Input: none ** Output: returns error code ** Purpose: retrieves curve data from temporary linked list **----------------------------------------------------------- */ { EN_Network *net = &pr->network; parser_data_t *par = &pr->parser; int i, j; double x; SFloatlist *fx, *fy; STmplist *c; /* Start at head of curve list */ c = par->Curvelist; /* Traverse list of curves */ while (c != NULL) { i = c->i; if (i >= 1 && i <= par->MaxCurves) { Scurve *curve = &net->Curve[i]; /* Save curve ID */ strcpy(curve->ID, c->ID); /* Check that curve has data points */ if (curve->Npts <= 0) { char errMsg[MAXMSG+1]; EN_geterror(230, errMsg, MAXMSG); sprintf(pr->Msg, "%s curve: %s", errMsg, curve->ID); writeline(pr, pr->Msg); return (200); } /* Allocate memory for curve data */ curve->X = (double *)calloc(curve->Npts, sizeof(double)); curve->Y = (double *)calloc(curve->Npts, sizeof(double)); if (curve->X == NULL || curve->Y == NULL) return (101); /* Traverse list of x,y data */ x = BIG; fx = c->x; fy = c->y; j = curve->Npts - 1; while (fx != NULL && fy != NULL && j >= 0) { /* Check that x data is in ascending order */ if (fx->value >= x) { char errMsg[MAXMSG+1]; EN_geterror(230, errMsg, MAXMSG); sprintf(pr->Msg, "%s node: %s", errMsg, curve->ID); writeline(pr, pr->Msg); return (200); } x = fx->value; /* Save x,y data in Curve structure */ curve->X[j] = fx->value; fx = fx->next; curve->Y[j] = fy->value; fy = fy->next; j--; } } c = c->next; } return (0); } int findmatch(char *line, char *keyword[]) /* **-------------------------------------------------------------- ** Input: *line = line from input file ** *keyword[] = list of NULL terminated keywords ** Output: returns index of matching keyword or ** -1 if no match found ** Purpose: determines which keyword appears on input line **-------------------------------------------------------------- */ { int i = 0; while (keyword[i] != NULL) { if (match(line, keyword[i])) return (i); i++; } return (-1); } /* end of findmatch */ int match(const char *str, const char *substr) /* **-------------------------------------------------------------- ** Input: *str = string being searched ** *substr = substring being searched for ** Output: returns 1 if substr found in str, 0 if not ** Purpose: sees if substr matches any part of str ** ** (Not case sensitive) **-------------------------------------------------------------- */ { int i, j; /*** Updated 9/7/00 ***/ /* Fail if substring is empty */ if (!substr[0]) return (0); /* Skip leading blanks of str. */ for (i = 0; str[i]; i++) if (str[i] != ' ') break; /* Check if substr matches remainder of str. */ for (j = 0; substr[j]; i++, j++) if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return (0); return (1); } /* end of match */ int gettokens(char *s, char** Tok, int maxToks, char *comment) /* **-------------------------------------------------------------- ** Input: *s = string to be tokenized ** Output: returns number of tokens in s ** Purpose: scans string for tokens, saving pointers to them ** in module global variable Tok[] ** ** Tokens can be separated by the characters listed in SEPSTR ** (spaces, tabs, newline, carriage return) which is defined ** in TYPES.H. Text between quotes is treated as a single token. **-------------------------------------------------------------- */ { int m, n; size_t len; char *c, *c2; // clear comment comment[0] = '\0'; /* Begin with no tokens */ for (n=0; n 0) { len = strcspn(c2, "\n\r"); len = MIN(len, MAXMSG); strncpy(comment, c2, len); comment[MIN(len,MAXMSG)] = '\0'; } } *c = '\0'; } len = (int)strlen(s); /* Scan s for tokens until nothing left */ while (len > 0 && n < MAXTOKS) { m = (int)strcspn(s,SEPSTR); /* Find token length */ len -= m+1; /* Update length of s */ if (m == 0) s++; /* No token found */ else { if (*s == '"') /* Token begins with quote */ { s++; /* Start token after quote */ m = (int)strcspn(s,"\"\n\r"); /* Find end quote (or EOL) */ } s[m] = '\0'; /* Null-terminate the token */ Tok[n] = s; /* Save pointer to token */ n++; /* Update token count */ s += m+1; /* Begin next token */ } } return(n); } double hour(char *time, char *units) /* **--------------------------------------------------------- ** Input: *time = string containing a time value ** *units = string containing time units ** Output: returns numerical value of time in hours, ** or -1 if an error occurs ** Purpose: converts time from units to hours **--------------------------------------------------------- */ { int n; double y[3]; char *s; /* Separate clock time into hrs, min, sec. */ for (n = 0; n < 3; n++) y[n] = 0.0; n = 0; s = strtok(time, ":"); while (s != NULL && n <= 3) { if (!getfloat(s, &y[n])) return (-1.0); s = strtok(NULL, ":"); n++; } /* If decimal time with units attached then convert to hours. */ if (n == 1) { /*if (units[0] == '\0') return(y[0]);*/ if (strlen(units) == 0) return (y[0]); if (match(units, w_SECONDS)) return (y[0] / 3600.0); if (match(units, w_MINUTES)) return (y[0] / 60.0); if (match(units, w_HOURS)) return (y[0]); if (match(units, w_DAYS)) return (y[0] * 24.0); } /* Convert hh:mm:ss format to decimal hours */ if (n > 1) y[0] = y[0] + y[1] / 60.0 + y[2] / 3600.0; /* If am/pm attached then adjust hour accordingly */ /* (12 am is midnight, 12 pm is noon) */ if (units[0] == '\0') return (y[0]); if (match(units, w_AM)) { if (y[0] >= 13.0) return (-1.0); if (y[0] >= 12.0) return (y[0] - 12.0); else return (y[0]); } if (match(units, w_PM)) { if (y[0] >= 13.0) return (-1.0); if (y[0] >= 12.0) return (y[0]); else return (y[0] + 12.0); } return (-1.0); } /* end of hour */ int getfloat(char *s, double *y) /* **----------------------------------------------------------- ** Input: *s = character string ** Output: *y = floating point number ** returns 1 if conversion successful, 0 if not ** Purpose: converts string to floating point number **----------------------------------------------------------- */ { char *endptr; *y = (double)strtod(s, &endptr); if (*endptr > 0) return (0); return (1); } int setreport(EN_Project *pr, char *s) /* **----------------------------------------------------------- ** Input: *s = report format command ** Output: none ** Returns: error code ** Purpose: processes a report formatting command ** issued by the ENsetreport function **----------------------------------------------------------- */ { parser_data_t *par = &pr->parser; par->Ntokens = gettokens(s, par->Tok, MAXTOKS, par->Comment); return (reportdata(pr)); } void inperrmsg(EN_Project *pr, int err, int sect, char *line) /* **------------------------------------------------------------- ** Input: err = error code ** sect = input data section ** *line = line from input file ** Output: none ** Purpose: displays input error message **------------------------------------------------------------- */ { parser_data_t *par = &pr->parser; char errStr[MAXMSG + 1]; char id[MAXMSG + 1]; EN_geterror(err, errStr, MAXMSG); /* get text for error message */ sprintf(pr->Msg, "%s - section: %s", errStr, SectTxt[sect]); // append ID? /* Retrieve ID label of object with input error */ /* (No ID used for CONTROLS or REPORT sections).*/ switch (sect) { case _CONTROLS: case _REPORT: // don't append break; case _ENERGY: sprintf(id, " id: %s", par->Tok[1]); break; default: sprintf(id, " id: %s", par->Tok[0]); break; } strcat(pr->Msg, id); writeline(pr, pr->Msg); /* Echo input line for syntax errors, and */ /* errors in CONTROLS and OPTIONS sections. */ if (sect == _CONTROLS || err == 201 || err == 213) writeline(pr, line); else writeline(pr, ""); } /********************** END OF INPUT2.C ************************/