1 /**
2 * Module for analysis of circular shaped open channels.
3 * 
4 * Note: Careful for the flowing full channel as this module
5 *   calculates even if the channel is full, even though that case
6 *   falls on closed conduits.
7 * Authors:
8 *   Alexius Academia
9 * License:
10 *   MIT
11 * Copyright:
12 *   Alexius Academia, 2019
13 */
14 module libs.circular_open_channel;
15 
16 // Standard modules
17 import std.math : sqrt, abs, pow, isNaN, PI, sin, acos;
18 import std.algorithm : canFind;
19 
20 // Custom modules
21 import libs.openchannel;
22 
23 /**
24 * Subclass of OpenChannel for the calculation and 
25 * analysis of circular sections.
26 */
27 class CircularOpenChannel : OpenChannel
28 {
29     //++++++++++++++++++++++++++++++++++++++++++++++
30     //                Properties                   +
31     //+++++++++++++++++++++++++++++++++++++++++++++/
32     /// Pipe diameter
33     protected double diameter;
34     /// More than half full
35     private bool almostFull;
36     /// Percentage full
37     private double percentFull;
38     /// Area of central triangle
39     private double triangleArea;
40     /// Trial discharge
41     private double trialDischarge;
42     /// Initial increment for trial and error
43     private double increment;
44     /// Available unknowns for this section.
45     private Unknown[] availableUnknowns = [
46         Unknown.DISCHARGE, Unknown.WATER_DEPTH, Unknown.BED_SLOPE
47     ];
48 
49     //++++++++++++++++++++++++++++++++++++++++++++++ 
50     //                 Setters                     +
51     //+++++++++++++++++++++++++++++++++++++++++++++/
52     /**
53     * Sets the pipe diameter.
54     * Params:
55     *   d = Diameter given either in meter or in foot..
56     */
57     void setDiameter(double d)
58     {
59         if (unit == Units.ENGLISH)
60         {
61             diameter = d / 3.28;
62         } else {
63             diameter = d;
64         }
65     }
66 
67     //++++++++++++++++++++++++++++++++++++++++++++++ 
68     //                 Getters                     +
69     //+++++++++++++++++++++++++++++++++++++++++++++/
70 
71     /**
72     * Gets the diameter of the pipe. 
73     * Returns:
74     *   The pipe diameter either in meter on in foot.
75     */
76     double getDiameter()
77     {
78         if (unit == Units.ENGLISH)
79         {
80             return diameter * 3.28;
81         }
82         return diameter;
83     }
84 
85     /** 
86     * Shows if the pipe is more than half full. 
87     * Returns:
88     *   True if the water depth if greater than the radius of the pipe.
89     */
90     bool isAlmostFull()
91     {
92         return almostFull;
93     }
94 
95     //++++++++++++++++++++++++++++++++++++++++++++++
96     //                  Methods                    +
97     //+++++++++++++++++++++++++++++++++++++++++++++/
98     /**
99     * Solution summary.
100     * To be called in the application API.
101     */
102 
103     bool solve()
104     {
105         // Reset variables
106         trialDischarge = 0;
107         increment = 0.000001;
108 
109         if (!canFind(availableUnknowns, unknown))
110         {
111             errorMessage = "The specified unknown is not included in the available unknowns.";
112             return false;
113         }
114 
115         switch (this.unknown)
116         {
117         case Unknown.DISCHARGE:
118             if (solveForDischarge)
119             {
120                 solveForCriticalFlow();
121                 solveForPercentFull();
122                 return true;
123             }
124             break;
125         case Unknown.WATER_DEPTH:
126             if (solveForWaterDepth)
127             {
128                 solveForCriticalFlow();
129                 solveForPercentFull();
130                 return true;
131             }
132             break;
133         case Unknown.BED_SLOPE:
134             if (solveForBedSlope)
135             {
136                 solveForCriticalFlow();
137                 solveForPercentFull();
138                 return true;
139             }
140             break;
141         default:
142             break;
143         }
144 
145         return false;
146     }
147 
148     /**
149     * Solve for the unknown discharge.
150     * Returns:
151     *   True if the calculation for discharge is successful.
152     */
153     private bool solveForDischarge()
154     {
155         if (isValidInputs(isValidDiameter(Unknown.DISCHARGE), isValidBedSlope(Unknown.DISCHARGE),
156                 isValidWaterDepth(Unknown.DISCHARGE), isValidManning))
157         {
158             calculateWettedProperties();
159 
160             // Check if wetted perimeter is zero.
161             // Cancel the calculation is so, which will yield infinity in calculation
162             // of hydraulic radius, R.
163             if (wettedPerimeter == 0.0)
164             {
165                 errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
166                 return false;
167             }
168 
169             hydraulicRadius = wettedArea / wettedPerimeter;
170             averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius,
171                     (2.0 / 3));
172             discharge = averageVelocity * wettedArea;
173 
174             return true;
175         }
176         else
177         {
178             return false;
179         }
180     }
181 
182     /**
183     * Solve for the unknown water depth.
184     * Returns:
185     *   True if the calculation for water depth is successful.
186     */
187     private bool solveForWaterDepth()
188     {
189         if (isValidInputs(isValidDiameter(Unknown.WATER_DEPTH), isValidBedSlope(Unknown.WATER_DEPTH),
190                 isValidDischarge(Unknown.WATER_DEPTH), isValidManning))
191         {
192             waterDepth = 0;
193 
194             const allowedDiff = discharge * ERROR;
195 
196             // Start of trial and error
197             while (abs(discharge - trialDischarge) > allowedDiff)
198             {
199                 waterDepth += increment;
200 
201                 // Check to make sure water depth is not greater than diameter
202                 if (waterDepth > diameter)
203                 {
204                     errorMessage = "Diameter of the pipe is insufficient to hold the discharge.";
205                     return false;
206                 }
207 
208                 calculateWettedProperties();
209 
210                 // Check if wetted perimeter is zero.
211                 // Cancel the calculation is so, which will yield infinity in calculation
212                 // of hydraulic radius, R.
213                 if (wettedPerimeter == 0.0)
214                 {
215                     errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
216                     return false;
217                 }
218 
219                 calculateTrialDischarge();
220 
221                 /+
222                 + My root finding algorithm
223                 +/
224                 if (trialDischarge < discharge)
225                 {
226                     increment *= 2.1;
227                 }
228 
229                 if (trialDischarge > discharge)
230                 {
231                     waterDepth -= increment;
232                     increment *= .75;
233                 }
234                 /+
235                 + End of root finding algorithm
236                 +/
237             }
238             return true;
239         }
240         else
241         {
242             return false;
243         }
244     }
245 
246     /**
247     * Solve for the unknown bed slope.
248     * Returns:
249     *   True if the calculation is successful.
250     */
251     private bool solveForBedSlope()
252     {
253         if (isValidInputs(isValidDiameter(Unknown.BED_SLOPE), isValidWaterDepth(Unknown.BED_SLOPE),
254                 isValidDischarge(Unknown.BED_SLOPE), isValidManning))
255         {
256             bedSlope = increment;
257 
258             const allowedDiff = discharge * ERROR;
259 
260             // Start of trial and error
261             while (trialDischarge < discharge)
262             {
263                 bedSlope += increment;
264 
265                 calculateWettedProperties();
266 
267                 // Check if wetted perimeter is zero.
268                 // Cancel the calculation is so, which will yield infinity in calculation
269                 // of hydraulic radius, R.
270                 if (wettedPerimeter == 0.0)
271                 {
272                     errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
273                     return false;
274                 }
275 
276                 calculateTrialDischarge();
277             }
278             return true;
279         }
280         else
281         {
282             return false;
283         }
284     }
285 
286     //++++++++++++++++++++++++++++++++++++++++++++++
287     //              Error handling                 +
288     //+++++++++++++++++++++++++++++++++++++++++++++/
289     
290     /**
291     * Base width error checking.
292     * Params:
293     *   u = The unknown for the channel.
294     * Returns:
295     *   True if the diameter given is valid.
296     */
297     private bool isValidDiameter(Unknown u)
298     {
299         if (isNaN(diameter) && (u != Unknown.PIPE_DIAMETER))
300         {
301             errorMessage = "Diameter must be numeric.";
302             return false;
303         }
304 
305         if (diameter < 0.0 && u != Unknown.PIPE_DIAMETER)
306         {
307             errorMessage = "Diameter must be greater than zero.";
308             return false;
309         }
310 
311         errorMessage = "Calculation successful.";
312         return true;
313     }
314 
315     //++++++++++++++++++++++++++++++++++++++++++++++
316     //              Helper Functions                +
317     //+++++++++++++++++++++++++++++++++++++++++++++/
318 
319     /**
320     * Calculates properties such as wetted area and wetted perimeter.
321     */
322     private void calculateWettedProperties()
323     {
324         float theta;
325         float aSec; // Area of sector
326 
327         almostFull = (waterDepth >= (diameter / 2.0));
328 
329         // Calculate theta
330         if (almostFull)
331         {
332             theta = 2 * acos((2 * waterDepth - diameter) / diameter) * 180 / PI;
333         }
334         else
335         {
336             theta = 2 * acos((diameter - 2 * waterDepth) / diameter) * 180 / PI;
337         }
338 
339         // Calculate the area of central triangle
340         triangleArea = pow(diameter, 2) * sin(theta * PI / 180) / 8;
341 
342         // Calculate area of sector
343         if (almostFull)
344         {
345             aSec = PI * pow(diameter, 2) * (360 - theta) / 1440;
346             wettedArea = aSec + triangleArea;
347             wettedPerimeter = PI * diameter * (360 - theta) / 360;
348         }
349         else
350         {
351             aSec = theta * PI * pow(diameter, 2) / 1440;
352             wettedArea = aSec - triangleArea;
353             wettedPerimeter = PI * diameter * theta / 360;
354         }
355     }
356 
357     /**
358     * Calculates discharge for the trial and error loop.
359     */
360     private void calculateTrialDischarge()
361     {
362         hydraulicRadius = wettedArea / wettedPerimeter;
363         averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius, (2.0 / 3));
364         trialDischarge = averageVelocity * wettedArea;
365     }
366 
367     /**
368     * Solves the top width of the water in a circular section for a 
369     * given water depth.
370     * Params:
371     *   y = Height of the water.\
372     *   triangleArea = Area of triangle consisting of water intersection with the pipe and center point.\
373     *   almostFull = A boolean indicating if y is more than half of the pipe diameter.
374     * Returns:
375     *   The top width of the water.
376     */
377     private double solveForTopWidth(double y, double triangleArea, bool almostFull)
378     {
379         double topWidth;
380         double triangleHeight;
381 
382         if (almostFull)
383         {
384             triangleHeight = y - this.diameter / 2;
385         }
386         else
387         {
388             triangleHeight = this.diameter / 2 - y;
389         }
390         topWidth = 2 * triangleArea / triangleHeight;
391 
392         return topWidth;
393     }
394 
395     /**
396     * Solve for the percentage of height of water over pipe diameter.
397     * 100% means the pipe is flowing full, in this case, the problem no longer
398     * behaves as open channel.
399     */
400     private void solveForPercentFull()
401     {
402         percentFull = this.waterDepth / this.diameter * 100;
403     }
404 
405     /**
406     * Analyzes the criticality of the flow.
407     */
408     private void solveForCriticalFlow()
409     {
410         // Q^2 / g
411         double Q2g = pow(discharge, 2) / GRAVITY_METRIC;
412 
413         // Other side of equation
414         double tester = 0;
415 
416         // Critical depth
417         double yc = 0.0;
418 
419         // Critical area, perimeter, hydraulic radius, critical slope
420         double Ac = 0, Pc = 0, Rc, Sc;
421 
422         // Top width
423         double T = 0;
424 
425         // Angle of water edges from the center
426         double thetaC = 0;
427 
428         // Triangle at critical flow
429         double aTriC = 0;
430 
431         // Sector at critical flow
432         double aSecC;
433 
434         while (tester < Q2g)
435         {
436             yc += 0.0001;
437 
438             // Calculate theta
439             if (yc > (diameter / 2))
440             {
441                 // Almost full
442                 thetaC = 2 * acos((2 * yc - diameter) / diameter) * 180 / PI;
443             }
444             else
445             {
446                 // Less than half full
447                 thetaC = 2 * acos((diameter - 2 * yc) / diameter) * 180 / PI;
448             }
449 
450             // Calculate area of triangle
451             aTriC = pow(diameter, 2) * sin(thetaC * PI / 180) / 8;
452             T = solveForTopWidth(yc, aTriC, (yc > (diameter / 2)));
453             // Calculate area of sector
454             if (yc > (diameter / 2))
455             {
456                 aSecC = PI * pow(diameter, 2) * (360 - thetaC) / 1440;
457                 Ac = aSecC + aTriC;
458                 Pc = PI * diameter * (360 - thetaC) / 360;
459             }
460             else
461             {
462                 aSecC = thetaC * PI * pow(diameter, 2) / 1440;
463                 Ac = aSecC - aTriC;
464                 Pc = PI * diameter * thetaC / 360;
465             }
466 
467             // Compare the equation for equality  
468             tester = pow(Ac, 3) / T;
469         }
470 
471         // Pass to global variable
472         criticalDepth = yc;
473 
474         // Hydraulic radius at critical flow
475         Rc = Ac / Pc;
476 
477         Sc = pow((discharge / (Ac * pow(Rc, (2.0 / 3.0))) * manningRoughness), 2);
478         criticalSlope = Sc;
479 
480         // Solve for froude number
481         hydraulicDepth = wettedArea / solveForTopWidth(waterDepth, triangleArea, almostFull);
482         froudeNumber = averageVelocity / sqrt(GRAVITY_METRIC * hydraulicDepth);
483 
484         // Select the flow type
485         calculateFlowType();
486     }
487 }