1 /**
2 * trapezoidal_open_channel module.
3 * Contains class for the analysis of trapezoidal and triangular channels.
4 */
5 module libs.trapezoidal_open_channel;
6 
7 // Standard modules
8 import std.math : pow, sqrt, pow, isNaN, abs;
9 import std.algorithm : canFind;
10 
11 // Custom modules
12 import libs.openchannel;
13 
14 /**
15 * Class for the analysis of trapezoidal and triangular channels.
16 */
17 class TrapezoidalOpenChannel : OpenChannel
18 {
19     //++++++++++++++++++++++++++++++++++++++++++++++
20     //                Properties                   +
21     //+++++++++++++++++++++++++++++++++++++++++++++/
22     /// Base width or the channel width for rectangular sections.
23     protected double baseWidth;
24 
25     /// Sideslope
26     protected float sideSlope;
27 
28     /// Available unknowns for this class.
29     protected Unknown[] availableUnknowns = [
30         Unknown.DISCHARGE, Unknown.WATER_DEPTH, Unknown.BED_SLOPE, Unknown.BASE_WIDTH
31     ];
32 
33     //++++++++++++++++++++++++++++++++++++++++++++++ 
34     //                 Setters                     +
35     //+++++++++++++++++++++++++++++++++++++++++++++/
36     /**
37     * Sets the width at the smaller base of the channel.
38     * Params:
39     *   b = Base width
40     */
41     void setBaseWidth(double b)
42     {
43         if (unit == Units.ENGLISH)
44         {
45             baseWidth = b / 3.28;
46         }
47         else
48         {
49             baseWidth = b;
50         }
51     }
52 
53     /**
54     * Sets the side slope of the channel. Assumming symmetrical section.
55     * Params:
56     *   ss = Side slope
57     */
58     void setSideSlope(float ss)
59     {
60         sideSlope = ss;
61     }
62 
63     //++++++++++++++++++++++++++++++++++++++++++++++ 
64     //                 Getters                     +
65     //+++++++++++++++++++++++++++++++++++++++++++++/
66     /// Returns the base width of the channel.
67     double getBaseWidth()
68     {
69         if (unit == Units.ENGLISH)
70         {
71             return baseWidth * 3.28;
72         }
73         return baseWidth;
74     }
75 
76     //++++++++++++++++++++++++++++++++++++++++++++++
77     //                  Methods                    +
78     //+++++++++++++++++++++++++++++++++++++++++++++/
79     // Solution summary.
80     // To be called in the application API
81     /// Method to be called for the analysis regardless of the unknown.
82     bool solve()
83     {
84         if (!canFind(availableUnknowns, unknown))
85         {
86             errorMessage = "The specified unknown is not included in the available unknowns.";
87             return false;
88         }
89 
90         switch (this.unknown)
91         {
92         case Unknown.DISCHARGE:
93             if (solveForDischarge)
94             {
95                 solveForCriticalFlow();
96                 return true;
97             }
98             break;
99         case Unknown.WATER_DEPTH:
100             if (solveForWaterDepth)
101             {
102                 solveForCriticalFlow();
103                 return true;
104             }
105             break;
106         case Unknown.BASE_WIDTH:
107             if (solveForBaseWidth)
108             {
109                 solveForCriticalFlow();
110                 return true;
111             }
112             break;
113         case Unknown.BED_SLOPE:
114             if (solveForBedSlope)
115             {
116                 solveForCriticalFlow();
117                 return true;
118             }
119             break;
120         default:
121             break;
122         }
123 
124         return false;
125     }
126 
127     /// Solve for the unknown discharge.
128     protected bool solveForDischarge()
129     {
130         if (isValidInputs(isValidBaseWidth(Unknown.DISCHARGE), isValidBedSlope(Unknown.DISCHARGE),
131                 isValidWaterDepth(Unknown.DISCHARGE),
132                 isValidSideslope(Unknown.DISCHARGE), isValidManning))
133         {
134             wettedArea = (baseWidth + waterDepth * sideSlope) * waterDepth;
135             wettedPerimeter = 2 * waterDepth * sqrt(pow(sideSlope, 2) + 1) + baseWidth;
136 
137             // Check if wetted perimeter is zero.
138             // Cancel the calculation is so, which will yield infinity in calculation
139             // of hydraulic radius, R.
140             if (wettedPerimeter == 0.0)
141             {
142                 errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
143                 return false;
144             }
145 
146             hydraulicRadius = wettedArea / wettedPerimeter;
147             averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius,
148                     (2.0 / 3));
149             discharge = averageVelocity * wettedArea;
150 
151             return true;
152         }
153         else
154         {
155             return false;
156         }
157     }
158 
159     /// Solve for the unknown water depth
160     protected bool solveForWaterDepth()
161     {
162         if (isValidInputs(isValidBaseWidth(Unknown.WATER_DEPTH), isValidBedSlope(Unknown.WATER_DEPTH),
163                 isValidDischarge(Unknown.WATER_DEPTH),
164                 isValidSideslope(Unknown.WATER_DEPTH), isValidManning))
165         {
166 
167             double trialDischarge = 0, increment = 0.0001;
168             waterDepth = 0;
169 
170             const allowedDiff = discharge * ERROR;
171 
172             // Start of trial and error
173             while (abs(discharge - trialDischarge) > allowedDiff)
174             {
175                 waterDepth += increment;
176                 wettedArea = (baseWidth + waterDepth * sideSlope) * waterDepth;
177                 wettedPerimeter = 2 * waterDepth * sqrt(pow(sideSlope, 2) + 1) + baseWidth;
178 
179                 // Check if wetted perimeter is zero.
180                 // Cancel the calculation is so, which will yield infinity in calculation
181                 // of hydraulic radius, R.
182                 if (wettedPerimeter == 0.0)
183                 {
184                     errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
185                     return false;
186                 }
187 
188                 hydraulicRadius = wettedArea / wettedPerimeter;
189                 averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius,
190                         (2.0 / 3));
191                 trialDischarge = averageVelocity * wettedArea;
192 
193                 /+
194                 + My root finding algorithm
195                 +/
196                 if (trialDischarge < discharge)
197                 {
198                     increment *= 2.1;
199                 }
200 
201                 if (trialDischarge > discharge)
202                 {
203                     waterDepth -= increment;
204                     increment *= .75;
205                 }
206                 /+
207                 + End of root finding algorithm
208                 +/
209             }
210             return true;
211         }
212         else
213         {
214             return false;
215         }
216     }
217 
218     /// Solve for the unknown base width
219     protected bool solveForBaseWidth()
220     {
221         if (isValidInputs(isValidWaterDepth(Unknown.BASE_WIDTH), isValidBedSlope(Unknown.BASE_WIDTH),
222                 isValidDischarge(Unknown.BASE_WIDTH),
223                 isValidSideslope(Unknown.BASE_WIDTH), isValidManning))
224         {
225 
226             double trialDischarge = 0, increment = 0.0001;
227             baseWidth = 0;
228 
229             const allowedDiff = discharge * ERROR;
230 
231             // Start of trial and error
232             while (abs(discharge - trialDischarge) > allowedDiff)
233             {
234                 baseWidth += increment;
235                 wettedArea = (baseWidth + waterDepth * sideSlope) * waterDepth;
236                 wettedPerimeter = 2 * waterDepth * sqrt(pow(sideSlope, 2) + 1) + baseWidth;
237 
238                 // Check if wetted perimeter is zero.
239                 // Cancel the calculation is so, which will yield infinity in calculation
240                 // of hydraulic radius, R.
241                 if (wettedPerimeter == 0.0)
242                 {
243                     errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
244                     return false;
245                 }
246 
247                 hydraulicRadius = wettedArea / wettedPerimeter;
248                 averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius,
249                         (2.0 / 3));
250                 trialDischarge = averageVelocity * wettedArea;
251 
252                 /+
253                 + My root finding algorithm
254                 +/
255                 if (trialDischarge < discharge)
256                 {
257                     increment *= 2.1;
258                 }
259 
260                 if (trialDischarge > discharge)
261                 {
262                     baseWidth -= increment;
263                     increment *= .75;
264                 }
265                 /+
266                 + End of root finding algorithm
267                 +/
268             }
269             return true;
270         }
271         else
272         {
273             return false;
274         }
275     }
276 
277     /// Solve for the unknown bed slope
278     protected bool solveForBedSlope()
279     {
280         if (isValidInputs(isValidWaterDepth(Unknown.BED_SLOPE), isValidBaseWidth(Unknown.BED_SLOPE),
281                 isValidDischarge(Unknown.BED_SLOPE),
282                 isValidSideslope(Unknown.BED_SLOPE), isValidManning))
283         {
284 
285             double trialDischarge = 0, increment = 0.0000001;
286             bedSlope = 0;
287 
288             const allowedDiff = discharge * ERROR;
289 
290             // Start of trial and error
291             while (abs(discharge - trialDischarge) > allowedDiff)
292             {
293                 bedSlope += increment;
294                 wettedArea = (baseWidth + waterDepth * sideSlope) * waterDepth;
295                 wettedPerimeter = 2 * waterDepth * sqrt(pow(sideSlope, 2) + 1) + baseWidth;
296 
297                 // Check if wetted perimeter is zero.
298                 // Cancel the calculation is so, which will yield infinity in calculation
299                 // of hydraulic radius, R.
300                 if (wettedPerimeter == 0.0)
301                 {
302                     errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions";
303                     return false;
304                 }
305 
306                 hydraulicRadius = wettedArea / wettedPerimeter;
307                 averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius,
308                         (2.0 / 3));
309                 trialDischarge = averageVelocity * wettedArea;
310 
311                 // Start of my root finding algorithm
312                 if (trialDischarge < discharge)
313                 {
314                     increment *= 2.1;
315                 }
316 
317                 if (trialDischarge > discharge)
318                 {
319                     bedSlope -= increment;
320                     increment *= .75;
321                 }
322                 // End of root finding algorithm
323             }
324             return true;
325         }
326         else
327         {
328             return false;
329         }
330     }
331 
332     //++++++++++++++++++++++++++++++++++++++++++++++
333     //              Error handling                 +
334     //+++++++++++++++++++++++++++++++++++++++++++++/
335     /**
336     * Base width error checking.
337     * Params:
338     *   u = Unknown 
339     * Returns:
340     *   Returns true if the base width given is valid.
341     */
342     protected bool isValidBaseWidth(Unknown u)
343     {
344         if (isNaN(baseWidth) && (u != Unknown.BASE_WIDTH))
345         {
346             errorMessage = "Base width must be numeric.";
347             return false;
348         }
349 
350         if (baseWidth < 0.0 && u != Unknown.BASE_WIDTH)
351         {
352             errorMessage = "Base width must be greater than zero.";
353             return false;
354         }
355 
356         errorMessage = "Calculation successful.";
357         return true;
358     }
359 
360     /**
361     * Sideslope error checking
362     * Params:
363     *   u = Unknown
364     * Returns:
365     *   Returns true if the side slope given is valid.
366     */
367     protected bool isValidSideslope(Unknown u)
368     {
369         if (isNaN(sideSlope))
370         {
371             errorMessage = "Sideslope must be numeric.";
372             return false;
373         }
374 
375         if (sideSlope < 0)
376         {
377             errorMessage = "Sideslope must be greater than zero.";
378             return false;
379         }
380 
381         errorMessage = "Calculation successful.";
382         return true;
383     }
384 
385     /**
386     * Critical flow analysis.
387     */
388     protected void solveForCriticalFlow()
389     {
390         const Q2g = pow(discharge, 2) / GRAVITY_METRIC;
391 
392         double tester = 0;
393 
394         // Critical depth
395         double yc = 0;
396 
397         // Critical area, perimeter, hydraulic radius, critical slope
398         double Ac = 0, Pc, Rc, Sc;
399 
400         // Top width
401         double T = 0;
402 
403         while (tester < Q2g)
404         {
405             yc += 0.00001;
406             T = baseWidth + 2 * sideSlope * yc;
407             Ac = (T + baseWidth) / 2 * yc;
408             tester = pow(Ac, 3) / T;
409         }
410 
411         criticalDepth = yc;
412 
413         Pc = 2 * yc * sqrt(pow(this.sideSlope, 2) + 1) + this.baseWidth;
414         Rc = Ac / Pc;
415         Sc = pow(this.discharge / (Ac * pow(Rc, (2.0 / 3.0))) * this.manningRoughness, 2);
416         this.criticalSlope = Sc;
417 
418         // Solve for froude number
419         const topWidth = this.baseWidth + 2 * this.sideSlope * this.waterDepth;
420         this.hydraulicDepth = this.wettedArea / topWidth;
421         this.froudeNumber = this.averageVelocity / sqrt(
422                 this.GRAVITY_METRIC * this.hydraulicDepth);
423 
424         // Select the flow type
425         calculateFlowType();
426     }
427 }