using IStation.Epanet.Enums; using IStation.Epanet.Log; using IStation.Epanet.Network.Structures; using IStation.Epanet.Util; using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace IStation.Epanet.Network.IO.Input { ///Abstract input file parser. public abstract class InputParser { protected Network net; ///Reference to the error logger. protected readonly TraceSource log = new TraceSource("epanet", SourceLevels.All); protected readonly List errors = new List(); protected void LogException(EnException ex) { errors.Add(ex); log.Error(ex); if (errors.Count > Constants.MAXERRS) throw new EnException(ErrorCode.Err200); } public static InputParser Create(FileType type) { switch (type) { case FileType.INP_FILE: return new InpParser(); case FileType.NET_FILE: return new NetParser(); case FileType.NULL_FILE: return new NullParser(); } return null; } public abstract Network Parse(Network net, string fileName); /// /// Parses string S into N tokens stored in T. /// Words between " " are stored as a single token. /// Characters to right of ';' are ignored. /// protected static int GetTokens(string stringToSplit, List results) { // const string SEPARATORS = " \t,\r\n"; // char[] SEPARATORS = { ' ', '\t', ',', '\r', '\n' }; if (results == null) throw new ArgumentNullException(nameof(results)); bool inQuote = false; var tok = new StringBuilder(Constants.MAXLINE); results.Clear(); foreach (char c in stringToSplit) { if (c == ';') break; if (c == '"') { // When we see a ", we need to decide whether we are // at the start or send of a quoted section... inQuote = !inQuote; } else if (!inQuote && char.IsWhiteSpace(c)) { // We've come to the end of a token, so we find the token, // trim it and add it to the collection of results... if (tok.Length > 0) { string result = tok.ToString().Trim(); if (!string.IsNullOrEmpty(result)) results.Add(result); } // We start a new token... tok.Length = 0; } else { // We've got a 'normal' character, so we add it to the curent token... tok.Append(c); } } // We've come to the end of the string, so we add the last token... if (tok.Length > 0) { string lastResult = tok.ToString().Trim(); if (!string.IsNullOrEmpty(lastResult)) results.Add(lastResult); } return results.Count; } ///Prepare the hydraulic network for simulation. protected void Convert() { InitTanks(net); InitPumps(net); InitPatterns(net); CheckUnlinked(); ConvertUnits(net); } ///Adjust simulation configurations. protected static void AdjustData(Network net) { if (net.PStep.Ticks <= 0) net.PStep = TimeSpan.FromHours(1); if (net.RStep.IsZero()) net.RStep = net.PStep; if (net.HStep.Ticks <= 0) net.HStep = TimeSpan.FromHours(1); if (net.HStep > net.PStep) net.HStep = net.PStep; if (net.HStep > net.RStep) net.HStep = net.RStep; if (net.RStart > net.Duration) net.RStart = TimeSpan.Zero; if (net.Duration.IsZero()) net.QualFlag = QualType.None; if (net.QStep.IsZero()) net.QStep = new TimeSpan(net.HStep.Ticks / 10); if (net.RuleStep.IsZero()) net.RuleStep = new TimeSpan(net.HStep.Ticks / 10); net.RuleStep = new TimeSpan(Math.Min(net.RuleStep.Ticks, net.HStep.Ticks)); net.QStep = new TimeSpan(Math.Min(net.QStep.Ticks, net.HStep.Ticks)); if (double.IsNaN(net.Ctol)) { net.Ctol = net.QualFlag == QualType.Age ? Constants.AGETOL : Constants.CHEMTOL; } switch (net.FlowFlag) { case FlowUnitsType.Lps: case FlowUnitsType.Lpm: case FlowUnitsType.Mld: case FlowUnitsType.Cmh: case FlowUnitsType.Cmd: net.UnitsFlag = UnitsType.SI; break; default: net.UnitsFlag = UnitsType.US; break; } if (net.UnitsFlag != UnitsType.SI) net.PressFlag = PressUnitsType.PSI; else if (net.PressFlag == PressUnitsType.PSI) net.PressFlag = PressUnitsType.METERS; var ucf = 1.0; if (net.UnitsFlag == UnitsType.SI) ucf = Math.Pow(Constants.MperFT, 2); if (double.IsNaN(net.Viscos)) net.Viscos = Constants.VISCOS; else if (net.Viscos > 1e-3) net.Viscos *= Constants.VISCOS; else net.Viscos /= ucf; if (double.IsNaN(net.Diffus)) net.Diffus = Constants.DIFFUS; else if (net.Diffus > 1e-4) net.Diffus *= Constants.DIFFUS; else net.Diffus /= ucf; net.HExp = net.FormFlag == HeadLossFormulaType.HW ? 1.852 : 2.0; double rfactor = net.RFactor; HeadLossFormulaType formFlag = net.FormFlag; double kbulk = net.KBulk; foreach (Link link in net.Links) { if (link.LinkType > LinkType.Pipe) continue; if (double.IsNaN(link.Kb)) link.Kb = kbulk; if (!double.IsNaN(link.Kw)) continue; if (rfactor.IsZero()) link.Kw = net.KWall; else if (link.Kc > 0.0 && link.Diameter > 0.0) { switch (formFlag) { case HeadLossFormulaType.HW: link.Kw = rfactor / link.Kc; break; case HeadLossFormulaType.DW: link.Kw = rfactor / Math.Abs(Math.Log(link.Kc / link.Diameter)); break; case HeadLossFormulaType.CM: link.Kw = rfactor * link.Kc; break; } } else link.Kw = 0.0; } foreach (Tank tank in net.Tanks) if (double.IsNaN(tank.Kb)) tank.Kb = kbulk; Pattern defpat = net.GetPattern(net.DefPatId) ?? net.GetPattern(""); foreach (Node node in net.Nodes) { if (node.Demands.Count == 0) node.Demands.Add(node.PrimaryDemand); foreach (Demand d in node.Demands) { if (d.Pattern == null) d.Pattern = defpat; } } if (net.QualFlag == QualType.None) net.FieldsMap.GetField(FieldType.QUALITY).Enabled = false; } ///Initialize tank properties. /// Hydraulic network reference. private static void InitTanks(Network net) { int n = 0; foreach (Tank tank in net.Tanks) { int levelerr = 0; if (tank.H0 > tank.Hmax || tank.Hmin > tank.Hmax || tank.H0 < tank.Hmin) levelerr = 1; Curve curv = tank.Vcurve; if (curv != null) { n = curv.Count - 1; if (tank.Hmin < curv[0].X || tank.Hmax > curv[n].X) levelerr = 1; } if (levelerr != 0) { throw new EnException(ErrorCode.Err225, tank.Name); } if (curv != null) { tank.Vmin = curv.Interpolate(tank.Hmin); tank.Vmax = curv.Interpolate(tank.Hmax); tank.V0 = curv.Interpolate(tank.H0); double a = (curv[n].Y - curv[0].Y) / (curv[n].X - curv[0].X); tank.Area = Math.Sqrt(4.0 * a / Math.PI); } } } ///Convert hydraulic structures values from user units to simulation system units. /// Hydraulic network reference. private static void ConvertUnits(Network nw) { FieldsMap fMap = nw.FieldsMap; foreach (Node node in nw.Nodes) { node.ConvertUnits(nw); } nw.CLimit /= fMap.GetUnits(FieldType.QUALITY); nw.Ctol /= fMap.GetUnits(FieldType.QUALITY); nw.KBulk /= Constants.SECperDAY; nw.KWall /= Constants.SECperDAY; foreach (Link link in nw.Links) { link.ConvertUnits(nw); link.InitResistance(nw.FormFlag, nw.HExp); } foreach (Control ctl in nw.Controls) { ctl.ConvertUnits(nw); } } ///Initialize pump properties. /// Hydraulic network reference. private static void InitPumps(Network net) { double h0 = 0.0, h1 = 0.0, h2 = 0.0, q1 = 0.0, q2 = 0.0; foreach (Pump pump in net.Pumps) { switch (pump.Ptype) { case PumpType.CONST_HP: // Constant Hp pump pump.H0 = 0.0; pump.FlowCoefficient = -8.814 * pump.Km; pump.N = -1.0; pump.Hmax = Constants.BIG; pump.Qmax = Constants.BIG; pump.Q0 = 1.0; continue; case PumpType.NOCURVE: // Set parameters for pump curves Curve curve = pump.HCurve; if (curve == null) throw new EnException(ErrorCode.Err226, pump.Name); int n = curve.Count; if (n == 1) { pump.Ptype = PumpType.POWER_FUNC; var pt = curve[0]; q1 = pt.X; h1 = pt.Y; h0 = 1.33334 * h1; q2 = 2.0 * q1; h2 = 0.0; } else if (n == 3 && curve[0].X.IsZero()) { pump.Ptype = PumpType.POWER_FUNC; h0 = curve[0].Y; q1 = curve[1].X; h1 = curve[1].Y; q2 = curve[2].X; h2 = curve[2].Y; } else pump.Ptype = PumpType.CUSTOM; // Compute shape factors & limits of power function pump curves if (pump.Ptype == PumpType.POWER_FUNC) { if (!GetPowerCurve(h0, h1, h2, q1, q2, out double a, out double b, out double c)) throw new EnException(ErrorCode.Err227, pump.Name); pump.H0 = -a; pump.FlowCoefficient = -b; pump.N = c; pump.Q0 = q1; pump.Qmax = Math.Pow(-a / b, 1.0 / c); pump.Hmax = h0; } break; } // Assign limits to custom pump curves if (pump.Ptype == PumpType.CUSTOM) { Curve curve = pump.HCurve; for (int i = 1; i < curve.Count; i++) { // Check for invalid curve if (curve[i].Y >= curve[i - 1].Y) { throw new EnException(ErrorCode.Err227, pump.Name); } } pump.Qmax = curve[curve.Count - 1].X; pump.Q0 = (curve[0].X + pump.Qmax) / 2.0; pump.Hmax = curve[0].Y; } } } ///Initialize patterns. /// Hydraulic network reference. private static void InitPatterns(Network net) { foreach (Pattern pat in net.Patterns) if (pat.Count == 0) pat.Add(1.0); } ///Check for unlinked nodes. private void CheckUnlinked() { //int[] marked = new int[net.Nodes.Count + 1]; var nodes = net.Nodes; var marked = new Dictionary(nodes.Count + 1); foreach (Link link in net.Links) { marked[link.FirstNode] = true; marked[link.SecondNode] = true; } foreach (Node node in nodes) { if (!marked[node]) { LogException(new EnException(ErrorCode.Err233, node.Name)); } } } /// Computes coeffs. for pump curve. /// shutoff head /// design head /// head at max. flow /// design flow /// max. flow /// pump curve coeffs. (H = a-bQ^c) /// pump curve coeffs. (H = a-bQ^c) /// pump curve coeffs. (H = a-bQ^c) /// Returns true if sucessful, false otherwise. private static bool GetPowerCurve(double h0, double h1, double h2, double q1, double q2, out double a, out double b, out double c) { a = b = c = 0; if ( h0 < Constants.TINY || h0 - h1 < Constants.TINY || h1 - h2 < Constants.TINY || q1 < Constants.TINY || q2 - q1 < Constants.TINY ) return false; a = h0; double h4 = h0 - h1; double h5 = h0 - h2; c = Math.Log(h5 / h4) / Math.Log(q2 / q1); if (c <= 0.0 || c > 20.0) return false; b = -h4 / Math.Pow(q1, c); return !(b >= 0.0); } /// checks for legal connections between PRVs & PSVs /// /// valve type /// upstream node /// downstream node /// returns true for legal connection, false otherwise protected static bool Valvecheck(Network net, ValveType type, Node j1, Node j2) { // Examine each existing valve foreach (Valve valve in net.Valves) { Node fNode = valve.FirstNode; Node tNode = valve.SecondNode; ValveType type2 = valve.ValveType; /* Cannot have two PRVs sharing downstream nodes or in series */ if (type2 == ValveType.PRV && type == ValveType.PRV) { if (Equals(tNode, j2) || Equals(tNode, j1) || Equals(fNode, j2)) return false; } /* Cannot have two PSVs sharing upstream nodes or in series */ if (type2 == ValveType.PSV && type == ValveType.PSV) { if (Equals(fNode, j1) || Equals(fNode, j2) || Equals(tNode, j1)) return false; } /* Cannot have PSV connected to downstream node of PRV */ if (type2 == ValveType.PSV && type == ValveType.PRV && fNode == j2) return false; if (type2 == ValveType.PRV && type == ValveType.PSV && tNode == j1) return false; /* Cannot have PSV connected to downstream node of FCV */ /* nor have PRV connected to upstream node of FCV */ if (type2 == ValveType.FCV && type == ValveType.PSV && tNode == j1) return false; if (type2 == ValveType.FCV && type == ValveType.PRV && fNode == j2) return false; if (type2 == ValveType.PSV && type == ValveType.FCV && fNode == j2) return false; if (type2 == ValveType.PRV && type == ValveType.FCV && tNode == j1) return false; } return true; } protected static void GetPumpCurve(Pump pump, int n, double[] x) { if (n == 1) { if (x[0] <= 0.0) throw new EnException(ErrorCode.Err202); pump.Ptype = PumpType.CONST_HP; pump.Km = x[0]; } else { double h0, h1, h2, q1, q2; if (n == 2) { q1 = x[1]; h1 = x[0]; h0 = 1.33334 * h1; q2 = 2.0 * q1; h2 = 0.0; } else if (n >= 5) { h0 = x[0]; h1 = x[1]; q1 = x[2]; h2 = x[3]; q2 = x[4]; } else throw new EnException(ErrorCode.Err202); pump.Ptype = PumpType.POWER_FUNC; if (!GetPowerCurve(h0, h1, h2, q1, q2, out double a, out double b, out double c)) throw new EnException(ErrorCode.Err206); pump.H0 = -a; pump.FlowCoefficient = -b; pump.N = c; pump.Q0 = q1; pump.Qmax = Math.Pow(-a / b, 1.0 / c); pump.Hmax = h0; } } // TODO: move to link class? protected static void ChangeStatus(Link link, StatusType status, double y) { switch (link.LinkType) { case LinkType.Pipe: if (!((Pipe)link).HasCheckValve && status != StatusType.ACTIVE) link.Status = status; break; case LinkType.Pump: switch (status) { case StatusType.ACTIVE: link.Kc = y; link.Status = y.IsZero() ? StatusType.CLOSED : StatusType.OPEN; break; case StatusType.OPEN: link.Kc = 1.0; break; } link.Status = status; break; case LinkType.VALVE: switch (((Valve)link).ValveType) { case ValveType.GPV: if (status != StatusType.ACTIVE) link.Status = status; break; case ValveType.PRV: case ValveType.PSV: case ValveType.PBV: case ValveType.FCV: case ValveType.TCV: link.Status = status; link.Kc = status == StatusType.ACTIVE ? y : double.NaN; break; } break; } } } }