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