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