1 /** 2 * Module for analysis of circular shaped open channels. 3 * 4 * Note: Careful for the flowing full channel as this module 5 * calculates even if the channel is full, even though that case 6 * falls on closed conduits. 7 * Authors: 8 * Alexius Academia 9 * License: 10 * MIT 11 * Copyright: 12 * Alexius Academia, 2019 13 */ 14 module libs.circular_open_channel; 15 16 // Standard modules 17 import std.math : sqrt, abs, pow, isNaN, PI, sin, acos; 18 import std.algorithm : canFind; 19 20 // Custom modules 21 import libs.openchannel; 22 23 /** 24 * Subclass of OpenChannel for the calculation and 25 * analysis of circular sections. 26 */ 27 class CircularOpenChannel : OpenChannel 28 { 29 //++++++++++++++++++++++++++++++++++++++++++++++ 30 // Properties + 31 //+++++++++++++++++++++++++++++++++++++++++++++/ 32 /// Pipe diameter 33 protected double diameter; 34 /// More than half full 35 private bool almostFull; 36 /// Percentage full 37 private double percentFull; 38 /// Area of central triangle 39 private double triangleArea; 40 /// Trial discharge 41 private double trialDischarge; 42 /// Initial increment for trial and error 43 private double increment; 44 /// Available unknowns for this section. 45 private Unknown[] availableUnknowns = [ 46 Unknown.DISCHARGE, Unknown.WATER_DEPTH, Unknown.BED_SLOPE 47 ]; 48 49 //++++++++++++++++++++++++++++++++++++++++++++++ 50 // Setters + 51 //+++++++++++++++++++++++++++++++++++++++++++++/ 52 /** 53 * Sets the pipe diameter. 54 * Params: 55 * d = Diameter given either in meter or in foot.. 56 */ 57 void setDiameter(double d) 58 { 59 if (unit == Units.ENGLISH) 60 { 61 diameter = d / 3.28; 62 } else { 63 diameter = d; 64 } 65 } 66 67 //++++++++++++++++++++++++++++++++++++++++++++++ 68 // Getters + 69 //+++++++++++++++++++++++++++++++++++++++++++++/ 70 71 /** 72 * Gets the diameter of the pipe. 73 * Returns: 74 * The pipe diameter either in meter on in foot. 75 */ 76 double getDiameter() 77 { 78 if (unit == Units.ENGLISH) 79 { 80 return diameter * 3.28; 81 } 82 return diameter; 83 } 84 85 /** 86 * Shows if the pipe is more than half full. 87 * Returns: 88 * True if the water depth if greater than the radius of the pipe. 89 */ 90 bool isAlmostFull() 91 { 92 return almostFull; 93 } 94 95 //++++++++++++++++++++++++++++++++++++++++++++++ 96 // Methods + 97 //+++++++++++++++++++++++++++++++++++++++++++++/ 98 /** 99 * Solution summary. 100 * To be called in the application API. 101 */ 102 103 bool solve() 104 { 105 // Reset variables 106 trialDischarge = 0; 107 increment = 0.000001; 108 109 if (!canFind(availableUnknowns, unknown)) 110 { 111 errorMessage = "The specified unknown is not included in the available unknowns."; 112 return false; 113 } 114 115 switch (this.unknown) 116 { 117 case Unknown.DISCHARGE: 118 if (solveForDischarge) 119 { 120 solveForCriticalFlow(); 121 solveForPercentFull(); 122 return true; 123 } 124 break; 125 case Unknown.WATER_DEPTH: 126 if (solveForWaterDepth) 127 { 128 solveForCriticalFlow(); 129 solveForPercentFull(); 130 return true; 131 } 132 break; 133 case Unknown.BED_SLOPE: 134 if (solveForBedSlope) 135 { 136 solveForCriticalFlow(); 137 solveForPercentFull(); 138 return true; 139 } 140 break; 141 default: 142 break; 143 } 144 145 return false; 146 } 147 148 /** 149 * Solve for the unknown discharge. 150 * Returns: 151 * True if the calculation for discharge is successful. 152 */ 153 private bool solveForDischarge() 154 { 155 if (isValidInputs(isValidDiameter(Unknown.DISCHARGE), isValidBedSlope(Unknown.DISCHARGE), 156 isValidWaterDepth(Unknown.DISCHARGE), isValidManning)) 157 { 158 calculateWettedProperties(); 159 160 // Check if wetted perimeter is zero. 161 // Cancel the calculation is so, which will yield infinity in calculation 162 // of hydraulic radius, R. 163 if (wettedPerimeter == 0.0) 164 { 165 errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions"; 166 return false; 167 } 168 169 hydraulicRadius = wettedArea / wettedPerimeter; 170 averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius, 171 (2.0 / 3)); 172 discharge = averageVelocity * wettedArea; 173 174 return true; 175 } 176 else 177 { 178 return false; 179 } 180 } 181 182 /** 183 * Solve for the unknown water depth. 184 * Returns: 185 * True if the calculation for water depth is successful. 186 */ 187 private bool solveForWaterDepth() 188 { 189 if (isValidInputs(isValidDiameter(Unknown.WATER_DEPTH), isValidBedSlope(Unknown.WATER_DEPTH), 190 isValidDischarge(Unknown.WATER_DEPTH), isValidManning)) 191 { 192 waterDepth = 0; 193 194 const allowedDiff = discharge * ERROR; 195 196 // Start of trial and error 197 while (abs(discharge - trialDischarge) > allowedDiff) 198 { 199 waterDepth += increment; 200 201 // Check to make sure water depth is not greater than diameter 202 if (waterDepth > diameter) 203 { 204 errorMessage = "Diameter of the pipe is insufficient to hold the discharge."; 205 return false; 206 } 207 208 calculateWettedProperties(); 209 210 // Check if wetted perimeter is zero. 211 // Cancel the calculation is so, which will yield infinity in calculation 212 // of hydraulic radius, R. 213 if (wettedPerimeter == 0.0) 214 { 215 errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions"; 216 return false; 217 } 218 219 calculateTrialDischarge(); 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 waterDepth -= 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 /** 247 * Solve for the unknown bed slope. 248 * Returns: 249 * True if the calculation is successful. 250 */ 251 private bool solveForBedSlope() 252 { 253 if (isValidInputs(isValidDiameter(Unknown.BED_SLOPE), isValidWaterDepth(Unknown.BED_SLOPE), 254 isValidDischarge(Unknown.BED_SLOPE), isValidManning)) 255 { 256 bedSlope = increment; 257 258 const allowedDiff = discharge * ERROR; 259 260 // Start of trial and error 261 while (trialDischarge < discharge) 262 { 263 bedSlope += increment; 264 265 calculateWettedProperties(); 266 267 // Check if wetted perimeter is zero. 268 // Cancel the calculation is so, which will yield infinity in calculation 269 // of hydraulic radius, R. 270 if (wettedPerimeter == 0.0) 271 { 272 errorMessage = "Perimeter shall be non-zero positive result. Please check your dimensions"; 273 return false; 274 } 275 276 calculateTrialDischarge(); 277 } 278 return true; 279 } 280 else 281 { 282 return false; 283 } 284 } 285 286 //++++++++++++++++++++++++++++++++++++++++++++++ 287 // Error handling + 288 //+++++++++++++++++++++++++++++++++++++++++++++/ 289 290 /** 291 * Base width error checking. 292 * Params: 293 * u = The unknown for the channel. 294 * Returns: 295 * True if the diameter given is valid. 296 */ 297 private bool isValidDiameter(Unknown u) 298 { 299 if (isNaN(diameter) && (u != Unknown.PIPE_DIAMETER)) 300 { 301 errorMessage = "Diameter must be numeric."; 302 return false; 303 } 304 305 if (diameter < 0.0 && u != Unknown.PIPE_DIAMETER) 306 { 307 errorMessage = "Diameter must be greater than zero."; 308 return false; 309 } 310 311 errorMessage = "Calculation successful."; 312 return true; 313 } 314 315 //++++++++++++++++++++++++++++++++++++++++++++++ 316 // Helper Functions + 317 //+++++++++++++++++++++++++++++++++++++++++++++/ 318 319 /** 320 * Calculates properties such as wetted area and wetted perimeter. 321 */ 322 private void calculateWettedProperties() 323 { 324 float theta; 325 float aSec; // Area of sector 326 327 almostFull = (waterDepth >= (diameter / 2.0)); 328 329 // Calculate theta 330 if (almostFull) 331 { 332 theta = 2 * acos((2 * waterDepth - diameter) / diameter) * 180 / PI; 333 } 334 else 335 { 336 theta = 2 * acos((diameter - 2 * waterDepth) / diameter) * 180 / PI; 337 } 338 339 // Calculate the area of central triangle 340 triangleArea = pow(diameter, 2) * sin(theta * PI / 180) / 8; 341 342 // Calculate area of sector 343 if (almostFull) 344 { 345 aSec = PI * pow(diameter, 2) * (360 - theta) / 1440; 346 wettedArea = aSec + triangleArea; 347 wettedPerimeter = PI * diameter * (360 - theta) / 360; 348 } 349 else 350 { 351 aSec = theta * PI * pow(diameter, 2) / 1440; 352 wettedArea = aSec - triangleArea; 353 wettedPerimeter = PI * diameter * theta / 360; 354 } 355 } 356 357 /** 358 * Calculates discharge for the trial and error loop. 359 */ 360 private void calculateTrialDischarge() 361 { 362 hydraulicRadius = wettedArea / wettedPerimeter; 363 averageVelocity = (1.0 / manningRoughness) * sqrt(bedSlope) * pow(hydraulicRadius, (2.0 / 3)); 364 trialDischarge = averageVelocity * wettedArea; 365 } 366 367 /** 368 * Solves the top width of the water in a circular section for a 369 * given water depth. 370 * Params: 371 * y = Height of the water.\ 372 * triangleArea = Area of triangle consisting of water intersection with the pipe and center point.\ 373 * almostFull = A boolean indicating if y is more than half of the pipe diameter. 374 * Returns: 375 * The top width of the water. 376 */ 377 private double solveForTopWidth(double y, double triangleArea, bool almostFull) 378 { 379 double topWidth; 380 double triangleHeight; 381 382 if (almostFull) 383 { 384 triangleHeight = y - this.diameter / 2; 385 } 386 else 387 { 388 triangleHeight = this.diameter / 2 - y; 389 } 390 topWidth = 2 * triangleArea / triangleHeight; 391 392 return topWidth; 393 } 394 395 /** 396 * Solve for the percentage of height of water over pipe diameter. 397 * 100% means the pipe is flowing full, in this case, the problem no longer 398 * behaves as open channel. 399 */ 400 private void solveForPercentFull() 401 { 402 percentFull = this.waterDepth / this.diameter * 100; 403 } 404 405 /** 406 * Analyzes the criticality of the flow. 407 */ 408 private void solveForCriticalFlow() 409 { 410 // Q^2 / g 411 double Q2g = pow(discharge, 2) / GRAVITY_METRIC; 412 413 // Other side of equation 414 double tester = 0; 415 416 // Critical depth 417 double yc = 0.0; 418 419 // Critical area, perimeter, hydraulic radius, critical slope 420 double Ac = 0, Pc = 0, Rc, Sc; 421 422 // Top width 423 double T = 0; 424 425 // Angle of water edges from the center 426 double thetaC = 0; 427 428 // Triangle at critical flow 429 double aTriC = 0; 430 431 // Sector at critical flow 432 double aSecC; 433 434 while (tester < Q2g) 435 { 436 yc += 0.0001; 437 438 // Calculate theta 439 if (yc > (diameter / 2)) 440 { 441 // Almost full 442 thetaC = 2 * acos((2 * yc - diameter) / diameter) * 180 / PI; 443 } 444 else 445 { 446 // Less than half full 447 thetaC = 2 * acos((diameter - 2 * yc) / diameter) * 180 / PI; 448 } 449 450 // Calculate area of triangle 451 aTriC = pow(diameter, 2) * sin(thetaC * PI / 180) / 8; 452 T = solveForTopWidth(yc, aTriC, (yc > (diameter / 2))); 453 // Calculate area of sector 454 if (yc > (diameter / 2)) 455 { 456 aSecC = PI * pow(diameter, 2) * (360 - thetaC) / 1440; 457 Ac = aSecC + aTriC; 458 Pc = PI * diameter * (360 - thetaC) / 360; 459 } 460 else 461 { 462 aSecC = thetaC * PI * pow(diameter, 2) / 1440; 463 Ac = aSecC - aTriC; 464 Pc = PI * diameter * thetaC / 360; 465 } 466 467 // Compare the equation for equality 468 tester = pow(Ac, 3) / T; 469 } 470 471 // Pass to global variable 472 criticalDepth = yc; 473 474 // Hydraulic radius at critical flow 475 Rc = Ac / Pc; 476 477 Sc = pow((discharge / (Ac * pow(Rc, (2.0 / 3.0))) * manningRoughness), 2); 478 criticalSlope = Sc; 479 480 // Solve for froude number 481 hydraulicDepth = wettedArea / solveForTopWidth(waterDepth, triangleArea, almostFull); 482 froudeNumber = averageVelocity / sqrt(GRAVITY_METRIC * hydraulicDepth); 483 484 // Select the flow type 485 calculateFlowType(); 486 } 487 }