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