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 }