/* ********************************************************************* REPORT.C -- Reporting Routines for EPANET Program VERSION: 2.00 DATE: 5/30/00 6/24/02 8/15/07 (2.00.11) 2/14/08 (2.00.12) AUTHOR: L. Rossman US EPA - NRMRL This module contains various procedures (all beginning with 'write') that are called from other modules to write formatted output to a report file. It also contains function disconnected(), called from writehydwarn() and writehyderr(), that checks if a hydraulic solution causes a network to become disconnected. The function writeline(S) is used throughout to write a formatted string S to the report file. ******************************************************************** */ #include #include #include #include #include #include "hash.h" #include "text.h" #include "types.h" #include "funcs.h" #define EXTERN extern #include "vars.h" #define MAXCOUNT 10 /* Max. # of disconnected nodes listed */ long LineNum; /* Current line number */ long PageNum; /* Current page number */ char DateStamp[26]; /* Current date & time */ char Fprinterr; /* File write error flag */ /* Defined in enumstxt.h in EPANET.C */ extern char *NodeTxt[]; extern char *LinkTxt[]; extern char *StatTxt[]; extern char *TstatTxt[]; extern char *LogoTxt[]; extern char *RptFormTxt[]; typedef REAL4 *Pfloat; void writenodetable(Pfloat *); void writelinktable(Pfloat *); int writereport() /* **------------------------------------------------------ ** Input: none ** Output: returns error code ** Purpose: writes formatted output report to file ** ** Calls strcomp() from the EPANET.C module. **------------------------------------------------------ */ { char tflag; FILE *tfile; int errcode = 0; /* If no secondary report file specified then */ /* write formatted output to primary report file. */ Fprinterr = FALSE; if (Rptflag && strlen(Rpt2Fname) == 0 && RptFile != NULL) { writecon(FMT17); writecon(Rpt1Fname); if (Energyflag) writeenergy(); errcode = writeresults(); } /* A secondary report file was specified */ else if (strlen(Rpt2Fname) > 0) { /* If secondary report file has same name as either input */ /* or primary report file then use primary report file. */ if (strcomp(Rpt2Fname,InpFname) || strcomp(Rpt2Fname,Rpt1Fname)) { writecon(FMT17); writecon(Rpt1Fname); if (Energyflag) writeenergy(); errcode = writeresults(); } /* Otherwise write report to secondary report file. */ else { /* Try to open file */ tfile = RptFile; tflag = Rptflag; if ((RptFile = fopen(Rpt2Fname,"wt")) == NULL) { RptFile = tfile; Rptflag = tflag; errcode = 303; } /* Write full formatted report to file */ else { Rptflag = 1; writecon(FMT17); writecon(Rpt2Fname); writelogo(); if (Summaryflag) writesummary(); if (Energyflag) writeenergy(); errcode = writeresults(); fclose(RptFile); RptFile = tfile; Rptflag = tflag; } } } /* Special error handler for write-to-file error */ if (Fprinterr) errmsg(309); return(errcode); } /* End of writereport */ void writelogo() /* **-------------------------------------------------------------- ** Input: none ** Output: none ** Purpose: writes program logo to report file. **-------------------------------------------------------------- */ { int i; time_t timer; /* time_t structure & functions time() & */ /* ctime() are defined in time.h */ time(&timer); strcpy(DateStamp,ctime(&timer)); PageNum = 1; LineNum = 2; fprintf(RptFile,FMT18); fprintf(RptFile,"%s",DateStamp); for (i=0; LogoTxt[i] != NULL; i++) writeline(LogoTxt[i]); writeline(""); } /* End of writelogo */ void writesummary() /* **-------------------------------------------------------------- ** Input: none ** Output: none ** Purpose: writes summary system information to report file **-------------------------------------------------------------- */ { char s[MAXFNAME+1]; int i; int nres = 0; for (i=0; i<3; i++) { if (strlen(Title[i]) > 0) { sprintf(s,"%-.70s",Title[i]); writeline(s); } } writeline(" "); sprintf(s,FMT19,InpFname); writeline(s); sprintf(s,FMT20,Njuncs); writeline(s); for (i=1; i<=Ntanks; i++) if (Tank[i].A == 0.0) nres++; sprintf(s,FMT21a,nres); writeline(s); sprintf(s,FMT21b,Ntanks-nres); writeline(s); sprintf(s,FMT22,Npipes); writeline(s); sprintf(s,FMT23,Npumps); writeline(s); sprintf(s,FMT24,Nvalves); writeline(s); sprintf(s,FMT25,RptFormTxt[Formflag]); writeline(s); sprintf(s,FMT26,Hstep*Ucf[TIME],Field[TIME].Units); writeline(s); sprintf(s,FMT27,Hacc); writeline(s); sprintf(s,FMT27a,CheckFreq); //(2.00.12 - LR) writeline(s); //(2.00.12 - LR) sprintf(s,FMT27b,MaxCheck); //(2.00.12 - LR) writeline(s); //(2.00.12 - LR) sprintf(s,FMT27c,DampLimit); //(2.00.12 - LR) writeline(s); //(2.00.12 - LR) sprintf(s,FMT28,MaxIter); writeline(s); if (Qualflag == NONE || Dur == 0.0) sprintf(s,FMT29); else if (Qualflag == CHEM) sprintf(s,FMT30,ChemName); else if (Qualflag == TRACE) sprintf(s,FMT31,Node[TraceNode].ID); else if (Qualflag == AGE) sprintf(s,FMT32); writeline(s); if (Qualflag != NONE && Dur > 0) { sprintf(s,FMT33,(float)Qstep/60.0); writeline(s); sprintf(s,FMT34,Ctol*Ucf[QUALITY],Field[QUALITY].Units); writeline(s); } sprintf(s,FMT36,SpGrav); writeline(s); sprintf(s,FMT37a,Viscos/VISCOS); writeline(s); sprintf(s,FMT37b,Diffus/DIFFUS); writeline(s); sprintf(s,FMT38,Dmult); writeline(s); sprintf(s,FMT39,Dur*Ucf[TIME],Field[TIME].Units); writeline(s); if (Rptflag) { sprintf(s,FMT40); writeline(s); if (Nodeflag == 0) writeline(FMT41); if (Nodeflag == 1) writeline(FMT42); if (Nodeflag == 2) writeline(FMT43); writelimits(DEMAND,QUALITY); if (Linkflag == 0) writeline(FMT44); if (Linkflag == 1) writeline(FMT45); if (Linkflag == 2) writeline(FMT46); writelimits(DIAM,HEADLOSS); } writeline(" "); } /* End of writesummary */ void writehydstat(int iter, double relerr) /* **-------------------------------------------------------------- ** Input: iter = # iterations to find hydraulic solution ** relerr = convergence error in hydraulic solution ** Output: none ** Purpose: writes hydraulic status report for solution found ** at current time period to report file **-------------------------------------------------------------- */ { int i,n; char newstat; char s1[MAXLINE+1]; /*** Updated 6/24/02 ***/ char atime[13]; /* Display system status */ strcpy(atime,clocktime(Atime,Htime)); if (iter > 0) { if (relerr <= Hacc) sprintf(s1,FMT58,atime,iter); else sprintf(s1,FMT59,atime,iter,relerr); writeline(s1); } /* Display status changes for tanks. D[n] is net inflow to tank at node n. Old tank status is stored in OldStat[] at indexes Nlinks+1 to Nlinks+Ntanks. */ for (i=1; i<=Ntanks; i++) { n = Tank[i].Node; if (ABS(D[n]) < 0.001) newstat = CLOSED; else if (D[n] > 0.0) newstat = FILLING; else if (D[n] < 0.0) newstat = EMPTYING; else newstat = OldStat[Nlinks+i]; if (newstat != OldStat[Nlinks+i]) { if (Tank[i].A > 0.0) sprintf(s1,FMT50,atime,Node[n].ID,StatTxt[newstat], (H[n]-Node[n].El)*Ucf[HEAD],Field[HEAD].Units); else sprintf(s1,FMT51,atime,Node[n].ID,StatTxt[newstat]); writeline(s1); OldStat[Nlinks+i] = newstat; } } /* Display status changes for links */ for (i=1; i<=Nlinks; i++) { if (S[i] != OldStat[i]) { if (Htime == 0) sprintf(s1,FMT52,atime,LinkTxt[Link[i].Type],Link[i].ID, StatTxt[S[i]]); else sprintf(s1,FMT53,atime,LinkTxt[Link[i].Type],Link[i].ID, StatTxt[OldStat[i]],StatTxt[S[i]]); writeline(s1); OldStat[i] = S[i]; } } writeline(" "); } /* End of writehydstat */ void writeenergy() /* **------------------------------------------------------------- ** Input: none ** Output: none ** Purpose: writes energy usage report to report file **------------------------------------------------------------- */ { int j; double csum; char s[MAXLINE+1]; if (Npumps == 0) return; writeline(" "); writeheader(ENERHDR,0); csum = 0.0; for (j=1; j<=Npumps; j++) { csum += Pump[j].Energy[5]; if (LineNum == (long)PageSize) writeheader(ENERHDR,1); sprintf(s,"%-8s %6.2f %6.2f %9.2f %9.2f %9.2f %9.2f", Link[Pump[j].Link].ID,Pump[j].Energy[0],Pump[j].Energy[1], Pump[j].Energy[2],Pump[j].Energy[3],Pump[j].Energy[4], Pump[j].Energy[5]); writeline(s); } fillstr(s,'-',63); writeline(s); /*** Updated 6/24/02 ***/ sprintf(s,FMT74,"",Emax*Dcost); writeline(s); sprintf(s,FMT75,"",csum+Emax*Dcost); /*** End of update ***/ writeline(s); writeline(" "); } /* End of writeenergy */ int writeresults() /* **-------------------------------------------------------------- ** Input: none ** Output: returns error code ** Purpose: writes simulation results to report file **-------------------------------------------------------------- */ { Pfloat *x; /* Array of pointers to floats */ int j,m,n,np,nnv,nlv; int errcode = 0; /* **----------------------------------------------------------- ** NOTE: The OutFile contains results for 4 node variables ** (demand, head, pressure, & quality) and 8 link ** variables (flow, velocity, headloss, quality, ** status, setting, reaction rate & friction factor) ** at each reporting time. **----------------------------------------------------------- */ /* Return if no output file */ if (OutFile == NULL) return(106); /* Return if no nodes or links selected for reporting */ /* or if no node or link report variables enabled. */ if (!Nodeflag && !Linkflag) return(errcode); nnv = 0; for (j=ELEV; j<=QUALITY; j++) nnv += Field[j].Enabled; nlv = 0; for (j=LENGTH; j<=FRICTION; j++) nlv += Field[j].Enabled; if (nnv == 0 && nlv == 0) return(errcode); /* Allocate memory for output variables. */ /* m = larger of # node variables & # link variables */ /* n = larger of # nodes & # links */ m = MAX( (QUALITY-DEMAND+1), (FRICTION-FLOW+1) ); n = MAX( (Nnodes+1), (Nlinks+1)); x = (Pfloat *) calloc(m, sizeof(Pfloat)); ERRCODE( MEMCHECK(x) ); if (errcode) return(errcode); for (j=0; j 0 && Nodeflag > 0) writenodetable(x); /* Read in link results & write link table. */ for (j=FLOW; j<=FRICTION; j++) fread((x[j-FLOW])+1,sizeof(REAL4),Nlinks,OutFile); if (nlv > 0 && Linkflag > 0) writelinktable(x); Htime += Rstep; } /* Free allocated memory */ for (j=0; j 1.e6) sprintf(s1, "%10.2e", y[j]); else sprintf(s1, "%10.*f", Field[j].Precision, y[j]); /*** End of update ***/ strcat(s, s1); } } /* Note if node is a reservoir/tank */ if (i > Njuncs) { strcat(s, " "); strcat(s, NodeTxt[getnodetype(i)]); } /* Write results for node */ writeline(s); } } writeline(" "); } void writelinktable(Pfloat *x) /* **--------------------------------------------------------------- ** Input: x = pointer to link results for current time ** Output: none ** Purpose: writes link results for current time to report file **--------------------------------------------------------------- */ { int i,j,k; char s[MAXLINE+1],s1[16]; double y[MAXVAR]; /* Write table header */ writeheader(LINKHDR,0); /* For each link: */ for (i=1; i<=Nlinks; i++) { /* Place results for each link variable in y */ y[LENGTH] = Link[i].Len*Ucf[LENGTH]; y[DIAM] = Link[i].Diam*Ucf[DIAM]; for (j=FLOW; j<=FRICTION; j++) y[j] = *((x[j-FLOW])+i); /* Check if link gets reported on */ if ((Linkflag == 1 || Link[i].Rpt) && checklimits(y,DIAM,FRICTION)) { /* Check if new page needed */ if (LineNum == (long)PageSize) writeheader(LINKHDR,1); /* Add link ID and each reported field to string s */ sprintf(s,"%-15s",Link[i].ID); for (j=LENGTH; j<=FRICTION; j++) { if (Field[j].Enabled == TRUE) { if (j == STATUS) { if (y[j] <= CLOSED) k = CLOSED; else if (y[j] == ACTIVE) k = ACTIVE; else k = OPEN; sprintf(s1, "%10s", StatTxt[k]); } /*** Updated 6/24/02 ***/ else { if (fabs(y[j]) > 1.e6) sprintf(s1, "%10.2e", y[j]); else sprintf(s1, "%10.*f", Field[j].Precision, y[j]); } /*** End of update ***/ strcat(s, s1); } } /* Note if link is a pump or valve */ if ( (j = Link[i].Type) > PIPE) { strcat(s, " "); strcat(s, LinkTxt[j]); } /* Write results for link */ writeline(s); } } writeline(" "); } void writeheader(int type, int contin) /* **-------------------------------------------------------------- ** Input: type = table type ** contin = table continuation flag ** Output: none ** Purpose: writes column headings for output report tables **-------------------------------------------------------------- */ { char s[MAXLINE+1],s1[MAXLINE+1],s2[MAXLINE+1],s3[MAXLINE+1]; int i,n; /* Move to next page if < 11 lines remain on current page. */ if (Rptflag && LineNum+11 > (long)PageSize) { while (LineNum < (long)PageSize) writeline(" "); } writeline(" "); /* Hydraulic Status Table */ if (type == STATHDR) { sprintf(s,FMT49); if (contin) strcat(s,t_CONTINUED); writeline(s); fillstr(s,'-',70); writeline(s); } /* Energy Usage Table */ if (type == ENERHDR) { if (Unitsflag == SI) strcpy(s1,t_perM3); else strcpy(s1,t_perMGAL); sprintf(s,FMT71); if (contin) strcat(s,t_CONTINUED); writeline(s); fillstr(s,'-',63); writeline(s); sprintf(s,FMT72); writeline(s); sprintf(s,FMT73,s1); writeline(s); fillstr(s,'-',63); writeline(s); } /* Node Results Table */ if (type == NODEHDR) { if (Tstatflag == RANGE) sprintf(s,FMT76,t_DIFFER); else if (Tstatflag != SERIES) sprintf(s,FMT76,TstatTxt[Tstatflag]); else if (Dur == 0) sprintf(s,FMT77); else sprintf(s,FMT78,clocktime(Atime,Htime)); if (contin) strcat(s,t_CONTINUED); writeline(s); n = 15; sprintf(s2,"%15s",""); strcpy(s,t_NODEID); sprintf(s3,"%-15s",s); for (i=ELEV; i MaxIter && relerr <= Hacc) { sprintf(Msg,WARN02,clocktime(Atime,Htime)); if (Messageflag) writeline(Msg); flag = 2; } /* Check for negative pressures */ for (i=1; i<=Njuncs; i++) { if (H[i] < Node[i].El && D[i] > 0.0) { sprintf(Msg,WARN06,clocktime(Atime,Htime)); if (Messageflag) writeline(Msg); flag = 6; break; } } /* Check for abnormal valve condition */ for (i=1; i<=Nvalves; i++) { j = Valve[i].Link; if (S[j] >= XFCV) { sprintf(Msg,WARN05,LinkTxt[Link[j].Type],Link[j].ID, StatTxt[S[j]],clocktime(Atime,Htime)); if (Messageflag) writeline(Msg); flag = 5; } } /* Check for abnormal pump condition */ for (i=1; i<=Npumps; i++) { j = Pump[i].Link; s = S[j]; //(2.00.11 - LR) if (S[j] >= OPEN) //(2.00.11 - LR) { //(2.00.11 - LR) if (Q[j] > K[j]*Pump[i].Qmax) s = XFLOW; //(2.00.11 - LR) if (Q[j] < 0.0) s = XHEAD; //(2.00.11 - LR) } //(2.00.11 - LR) if (s == XHEAD || s == XFLOW) //(2.00.11 - LR) { sprintf(Msg,WARN04,Link[j].ID,StatTxt[s], //(2.00.11 - LR) clocktime(Atime,Htime)); if (Messageflag) writeline(Msg); flag = 4; } } /* Check if system is unbalanced */ if (iter > MaxIter && relerr > Hacc) { sprintf(Msg,WARN01,clocktime(Atime,Htime)); if (ExtraIter == -1) strcat(Msg,t_HALTED); if (Messageflag) writeline(Msg); flag = 1; } /* Check for disconnected network */ /* & update global warning flag */ if (flag > 0) { disconnected(); Warnflag = flag; } return(flag); } /* End of writehydwarn */ void writehyderr(int errnode) /* **----------------------------------------------------------- ** Input: none ** Output: none ** Purpose: outputs status & checks connectivity when ** network hydraulic equations cannot be solved. **----------------------------------------------------------- */ { sprintf(Msg,FMT62,clocktime(Atime,Htime),Node[errnode].ID); if (Messageflag) writeline(Msg); writehydstat(0,0); disconnected(); } /* End of writehyderr */ int disconnected() /* **------------------------------------------------------------------- ** Input: None ** Output: Returns number of disconnected nodes ** Purpose: Tests current hydraulic solution to see if any closed ** links have caused the network to become disconnected. **------------------------------------------------------------------- */ { int i, j; int count, mcount; int errcode = 0; int *nodelist; char *marked; /* Allocate memory for node list & marked list */ nodelist = (int *) calloc(Nnodes+1,sizeof(int)); marked = (char *) calloc(Nnodes+1,sizeof(char)); ERRCODE(MEMCHECK(nodelist)); ERRCODE(MEMCHECK(marked)); if (errcode) return(0); /* Place tanks on node list and marked list */ for (i=1; i<=Ntanks; i++) { j = Njuncs + i; nodelist[i] = j; marked[j] = 1; } /* Place junctions with negative demands on the lists */ mcount = Ntanks; for (i=1; i<=Njuncs; i++) { if (D[i] < 0.0) { mcount++; nodelist[mcount] = i; marked[i] = 1; } } /* Mark all nodes that can be connected to tanks */ /* and count number of nodes remaining unmarked. */ marknodes(mcount,nodelist,marked); j = 0; count = 0; for (i=1; i<=Njuncs; i++) { if (!marked[i] && D[i] != 0.0) /* Skip if no demand */ { count++; if (count <= MAXCOUNT && Messageflag) { sprintf(Msg,WARN03a,Node[i].ID,clocktime(Atime,Htime)); writeline(Msg); } j = i; /* Last unmarked node */ } } /* Report number of unmarked nodes and find closed link */ /* on path from node j back to a tank. */ if (count > 0 && Messageflag) { if (count > MAXCOUNT) { sprintf(Msg, WARN03b, count-MAXCOUNT, clocktime(Atime,Htime)); writeline(Msg); } getclosedlink(j,marked); } /* Free allocated memory */ free(nodelist); free(marked); return(count); } /* End of disconnected() */ void marknodes(int m, int *nodelist, char *marked) /* **---------------------------------------------------------------- ** Input: m = number of source nodes ** nodelist[] = list of nodes to be traced from ** marked[] = TRUE if node connected to source ** Output: None. ** Purpose: Marks all junction nodes connected to tanks. **---------------------------------------------------------------- */ { int i, j, k, n; Padjlist alink; /* Scan each successive entry of node list */ n = 1; while (n <= m ) { /* Scan all nodes connected to current node */ i = nodelist[n]; for (alink = Adjlist[i]; alink != NULL; alink = alink->next) { /* Get indexes of connecting link and node */ k = alink->link; j = alink->node; if (marked[j]) continue; /* Check if valve connection is in correct direction */ switch (Link[k].Type) { case CV: case PRV: case PSV: if (j == Link[k].N1) continue; } /* Mark connection node if link not closed */ if (S[k] > CLOSED) { marked[j] = 1; m++; nodelist[m] = j; } } n++; } } /* End of marknodes() */ void getclosedlink(int i, char *marked) /* **---------------------------------------------------------------- ** Input: i = junction index ** marked[] = marks nodes already examined ** Output: None. ** Purpose: Determines if a closed link connects to junction i. **---------------------------------------------------------------- */ { int j,k; Padjlist alink; marked[i] = 2; for (alink = Adjlist[i]; alink != NULL; alink = alink->next) { k = alink->link; j = alink->node; if (marked[j] == 2) continue; if (marked[j] == 1) { sprintf(Msg, WARN03c, Link[k].ID); writeline(Msg); return; } else getclosedlink(j,marked); } } void writelimits(int j1, int j2) /* **-------------------------------------------------------------- ** Input: j1 = index of first output variable ** j2 = index of last output variable ** Output: none ** Purpose: writes reporting criteria to output report **-------------------------------------------------------------- */ { int j; for (j=j1; j<=j2; j++) { if (Field[j].RptLim[LOW] < BIG) { sprintf(Msg,FMT47, Field[j].Name,Field[j].RptLim[LOW],Field[j].Units); writeline(Msg); } if (Field[j].RptLim[HI] > -BIG) { sprintf(Msg,FMT48, Field[j].Name,Field[j].RptLim[HI],Field[j].Units); writeline(Msg); } } } /* End of writelimits */ int checklimits(double *y, int j1, int j2) /* **-------------------------------------------------------------- ** Input: *y = array of output results ** j1 = index of first output variable ** j2 = index of last output variable ** Output: returns 1 if criteria met, 0 otherwise ** Purpose: checks if output reporting criteria is met **-------------------------------------------------------------- */ { int j; for (j=j1; j<=j2; j++) { if (y[j] > Field[j].RptLim[LOW] || y[j] < Field[j].RptLim[HI]) return(0); } return(1); } /* End of checklim */ void writetime(char *fmt) /* **---------------------------------------------------------------- ** Input: fmt = format string ** Output: none ** Purpose: writes starting/ending time of a run to report file **---------------------------------------------------------------- */ { time_t timer; time(&timer); sprintf(Msg, fmt, ctime(&timer)); writeline(Msg); } char *clocktime(char *atime, long seconds) /* **-------------------------------------------------------------- ** Input: seconds = time in seconds ** Output: atime = time in hrs:min ** (returns pointer to atime) ** Purpose: converts time in seconds to hours:minutes format **-------------------------------------------------------------- */ { /*** Updated 6/24/02 ***/ int h,m,s; h = seconds/3600; m = (seconds % 3600) / 60; s = seconds - 3600*h - 60*m; sprintf(atime, "%01d:%02d:%02d", h,m,s); return(atime); } /* End of clocktime */ char *fillstr(char *s, char ch, int n) /* **--------------------------------------------------------- ** Fills n bytes of s to character ch. ** NOTE: does not check for overwriting s. **--------------------------------------------------------- */ { int i; for (i=0; i<=n; i++) s[i] = ch; s[n+1] = '\0'; return(s); } int getnodetype(int i) /* **--------------------------------------------------------- ** Determines type of node with index i ** (junction = 0, reservoir = 1, tank = 2). **--------------------------------------------------------- */ { if (i <= Njuncs) return(0); if (Tank[i-Njuncs].A == 0.0) return(1); return(2); } /********************* END OF REPORT.C ********************/