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