|
| 1 | +## \file OptimalPropeller.py |
| 2 | +# \brief Python script for generating the ActuatorDisk.dat file. |
| 3 | +# \author E. Saetta, L. Russo, R. Tognaccini |
| 4 | +# \version 7.0.6 "Blackbird" |
| 5 | +# |
| 6 | +# SU2 Project Website: https://su2code.github.io |
| 7 | +# |
| 8 | +# The SU2 Project is maintained by the SU2 Foundation |
| 9 | +# (http://su2foundation.org) |
| 10 | +# |
| 11 | +# Copyright 2012-2020, SU2 Contributors (cf. AUTHORS.md) |
| 12 | +# |
| 13 | +# SU2 is free software; you can redistribute it and/or |
| 14 | +# modify it under the terms of the GNU Lesser General Public |
| 15 | +# License as published by the Free Software Foundation; either |
| 16 | +# version 2.1 of the License, or (at your option) any later version. |
| 17 | +# |
| 18 | +# SU2 is distributed in the hope that it will be useful, |
| 19 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 20 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 21 | +# Lesser General Public License for more details. |
| 22 | +# |
| 23 | +# You should have received a copy of the GNU Lesser General Public |
| 24 | +# License along with SU2. If not, see <http://www.gnu.org/licenses/>. |
| 25 | +# ============================================================================================== |
| 26 | +# Name : OptimalPropeller |
| 27 | +# Author : Ettore Saetta, Lorenzo Russo, Renato Tognaccini |
| 28 | +# Theoretical and Applied Aerodynamic Research Group (TAARG), |
| 29 | +# University of Naples Federico II. |
| 30 | +# Version : 1.0.0 - Python |
| 31 | +# Date : 01/09/2020 |
| 32 | +# Copyright : |
| 33 | +# Description : Compute the optimal load distribution along the propeller radius using |
| 34 | +# the inviscid theory of the optimal propeller. |
| 35 | +# Reference : Glauert H., Airplane Propellers, in Aerodynamic Theory, Ed. Durand W. F., |
| 36 | +# Vol. IV, pp. 169 - 360, Springer, 1935. |
| 37 | +# Input : Interactive. |
| 38 | +# Output : ActuatorDisk.cfg, containing part of SU2 .cfg file. |
| 39 | +# ActuatorDisk.dat, containing propeller load distribution to be read by SU2_CFD. |
| 40 | +# Note : Python 3 or higher needed. |
| 41 | +# ============================================================================================== |
| 42 | + |
| 43 | +import math |
| 44 | +import numpy as np |
| 45 | +import pylab as pl |
| 46 | + |
| 47 | +########################## |
| 48 | +### Functions ### |
| 49 | +########################## |
| 50 | +def a_distribution (w0, Chi): |
| 51 | + |
| 52 | + """Function used to compute the value of the axial interference factor using the inviscid theory of the optimal propeller.""" |
| 53 | + |
| 54 | + a = (w0*pow(Chi,2))/(pow(Chi,2)+pow((1+(w0)),2)) |
| 55 | + return a |
| 56 | + |
| 57 | +def Print_external_file(CTrs, CPrs): |
| 58 | + |
| 59 | + """Function used to write the actuator disk input data file""" |
| 60 | + file = open('ActuatorDisk.dat', 'w') |
| 61 | + file.write('# Automatic generated actuator disk input data file using the Optimal Propeller code.\n') |
| 62 | + file.write('# Data file needed for the actuator disk VARIABLE_LOAD type.\n') |
| 63 | + file.write('# The load distribution is obtained using the inviscid theory of the optimal propeller\n') |
| 64 | + file.write('# using global data.\n') |
| 65 | + file.write('#\n') |
| 66 | + file.write('# The first three lines must be filled.\n') |
| 67 | + file.write('# An example of this file can be found in the TestCases directory.\n') |
| 68 | + file.write('#\n') |
| 69 | + file.write('# Author: Ettore Saetta, Lorenzo Russo, Renato Tognaccini.\n') |
| 70 | + file.write('# Theoretical and Applied Aerodynamic Research Group (TAARG),\n') |
| 71 | + file.write('# University of Naples Federico II\n') |
| 72 | + file.write('# -------------------------------------------------------------------------------------\n') |
| 73 | + file.write('#\n') |
| 74 | + file.write('MARKER_ACTDISK= \n') |
| 75 | + file.write('CENTER= \n') |
| 76 | + file.write('AXIS= \n') |
| 77 | + file.write('RADIUS= '+str(R)+'\n') |
| 78 | + file.write('ADV_RATIO= '+str(J)+'\n') |
| 79 | + file.write('NROW= '+str(Stations)+'\n') |
| 80 | + file.write('# rs=r/R dCT/drs dCP/drs dCR/drs\n') |
| 81 | + |
| 82 | + for i in range(0, Stations): |
| 83 | + file.write(f' {r[i]:.7f} {CTrs[i]:.7f} {CPrs[i]:.7f} 0.0\n') |
| 84 | + |
| 85 | + file.close() |
| 86 | + |
| 87 | +########################## |
| 88 | +### Main ### |
| 89 | +########################## |
| 90 | +# Screen output |
| 91 | +print('------------------ Optimal Propeller vsn 7.0.6 ------------------') |
| 92 | +print('| Computation of the optimal dCT/dr and dCP/dr distributions. |') |
| 93 | +print('| Based on the inviscid theory of the optimal propeller. |') |
| 94 | +print('| |') |
| 95 | +print('| This code is used to generate the actuator disk input data |') |
| 96 | +print('| file needed for the VARIABLE_LOAD actuator disk type |') |
| 97 | +print('| implemented in SU2 7.0.6. |') |
| 98 | +print('| |') |
| 99 | +print('| Author: Ettore Saetta, Lorenzo Russo, Renato Tognaccini. |') |
| 100 | +print('| Theoretical and Applied Aerodynamic Research Group (TAARG), |') |
| 101 | +print('| University of Naples Federico II. |') |
| 102 | +print('-----------------------------------------------------------------') |
| 103 | + |
| 104 | +print('') |
| 105 | +print('Warning: present version requires input in SI units.') |
| 106 | +print('') |
| 107 | + |
| 108 | +# Number of radial stations in input. |
| 109 | +Stations = int(input('Number of radial stations: ')) |
| 110 | +print('') |
| 111 | + |
| 112 | +dStations = float(Stations) |
| 113 | + |
| 114 | +# Resize the vectors using the number of radial stations. |
| 115 | +r = np.empty(Stations) |
| 116 | +chi = np.empty(Stations) |
| 117 | +dCp = np.empty(Stations) |
| 118 | +w = np.empty(Stations) |
| 119 | +a_new = np.empty(Stations) |
| 120 | +a_old = np.empty(Stations) |
| 121 | +a_0 = np.empty(Stations) |
| 122 | +ap_old = np.empty(Stations) |
| 123 | +dCt_new = np.empty(Stations) |
| 124 | +dCt_old = np.empty(Stations) |
| 125 | +dCt_0 = np.empty(Stations) |
| 126 | +DeltaP = np.empty(Stations) |
| 127 | +F = np.empty(Stations) |
| 128 | +a_optimal = np.empty(Stations) |
| 129 | +ap_optimal = np.empty(Stations) |
| 130 | +dCt_optimal = np.empty(Stations) |
| 131 | + |
| 132 | +# Thrust coefficient in input. |
| 133 | +Ct = float(input('CT (Renard definition): ')) |
| 134 | +print('') |
| 135 | + |
| 136 | +# Propeller radius in input. |
| 137 | +R = float(input('R (propeller radius [m]): ')) |
| 138 | +print('') |
| 139 | + |
| 140 | +# Hub radius in input. |
| 141 | +rhub = float(input('r_hub (hub radius [m]): ')) |
| 142 | +print('') |
| 143 | + |
| 144 | +# Advance ratio in input. |
| 145 | +J = float(input('J (advance ratio): ')) |
| 146 | +print('') |
| 147 | + |
| 148 | +# Freestream velocity in input. |
| 149 | +Vinf = float(input('Vinf (m/s): ')) |
| 150 | +print('') |
| 151 | + |
| 152 | +# Asking if the tip loss Prandtl correction function needs to be used. |
| 153 | +Prandtl = input('Using tip loss Prandtl correction? (<y>/n): ') |
| 154 | +print('') |
| 155 | + |
| 156 | +if Prandtl == 'y' or Prandtl == 'Y' or Prandtl == '': |
| 157 | + # Number of propeller blades in input. |
| 158 | + N = int(input('N (number of propeller blades): ')) |
| 159 | + print('') |
| 160 | + |
| 161 | + corr = True |
| 162 | + |
| 163 | +else: |
| 164 | + corr = False |
| 165 | + |
| 166 | +# Computation of the non-dimensional hub radius. |
| 167 | +rs_hub = rhub/R |
| 168 | + |
| 169 | +# Computation of the non-dimensional radial stations. |
| 170 | +for i in range(1,Stations+1): |
| 171 | + r[i-1]=i/dStations |
| 172 | + if r[i-1] <= rs_hub: |
| 173 | + i_hub = i-1 |
| 174 | + |
| 175 | +# Computation of the propeller diameter. |
| 176 | +D = 2*R |
| 177 | +# Computation of the propeller angular velocity (Rounds/s). |
| 178 | +n = Vinf/(D*J) |
| 179 | +# Computation of the propeller angular velocity (Rad/s). |
| 180 | +Omega = n*2*math.pi |
| 181 | + |
| 182 | +# Computation of the tip loss Prandtl correction function F. |
| 183 | +if corr == True: |
| 184 | + for i in range(0, Stations): |
| 185 | + F[i] = (2/math.pi)*math.acos(math.exp(-0.5*N*(1-r[i])*math.sqrt(1+pow(Omega*R/Vinf,2)))) |
| 186 | + |
| 187 | +else: |
| 188 | + for i in range(0, Stations): |
| 189 | + F[i] = 1.0 |
| 190 | + |
| 191 | +# Computation of the non-dimensional radius chi=Omega*r/Vinf. |
| 192 | +for i in range(0, Stations): |
| 193 | + chi[i] = Omega*r[i]*R/Vinf |
| 194 | + |
| 195 | +eps = 5E-20 |
| 196 | +# Computation of the propeller radial stations spacing. |
| 197 | +h = (1.0/Stations) |
| 198 | + |
| 199 | +# Computation of the first try induced velocity distribution. |
| 200 | +for i in range(0, Stations): |
| 201 | + w[i] = (2/math.pow(Vinf,2))*((-1/Vinf)+math.sqrt(1+((math.pow(D,4)*(Ct)*math.pow(n,2))/(math.pow(Vinf,2)*math.pi*r[i])))) |
| 202 | + |
| 203 | +# Computation of the first try Lagrange moltiplicator. |
| 204 | +w_0 = 0.0 |
| 205 | +for i in range(0, Stations): |
| 206 | + w_0 += w[i] |
| 207 | + |
| 208 | +w_0 = w_0/(Vinf*Stations) |
| 209 | + |
| 210 | +# Computation of the first try axial interference factor distribution. |
| 211 | +for i in range(0, Stations): |
| 212 | + a_0[i] = a_distribution(w_0*F[i],chi[i]) |
| 213 | + |
| 214 | +# Computation of the thrust coefficient distribution |
| 215 | +for i in range(0, Stations): |
| 216 | + dCt_0[i]= math.pi*J*J*r[i]*(1+a_0[i])*a_0[i] |
| 217 | + |
| 218 | +# Computation of the total thrust coefficient. |
| 219 | +Ct_0 = 0.0 |
| 220 | +for i in range(i_hub, Stations): |
| 221 | + Ct_0 += h*dCt_0[i] |
| 222 | + |
| 223 | +# Compute the error with respect to the thrust coefficient given in input. |
| 224 | +err_0 = Ct_0 - Ct |
| 225 | +print('CONVERGENCE HISTORY:') |
| 226 | +print(err_0) |
| 227 | + |
| 228 | +# Computation of the second try Lagrange moltiplicator. |
| 229 | +w_old = w_0 + 0.1 |
| 230 | + |
| 231 | +# Computation of the second try axial interference factor distribution. |
| 232 | +for i in range(0, Stations): |
| 233 | + a_old[i] = a_distribution(w_old*F[i],chi[i]) |
| 234 | + |
| 235 | +# Computation of the thrust coefficient distribution |
| 236 | +for i in range(0, Stations): |
| 237 | + dCt_old[i]= math.pi*J*J*r[i]*(1+a_old[i])*a_old[i] |
| 238 | + |
| 239 | +# Computation of the total thrust coefficient. |
| 240 | +Ct_old = 0.0 |
| 241 | +for i in range(i_hub, Stations): |
| 242 | + Ct_old += h*dCt_old[i] |
| 243 | + |
| 244 | +# Compute the error with respect to the thrust coefficient given in input. |
| 245 | +err_old = Ct_old - Ct |
| 246 | +print(err_old) |
| 247 | + |
| 248 | +########################## |
| 249 | +### Iterations ### |
| 250 | +########################## |
| 251 | +# Iterate using the false position methods. |
| 252 | +# Based on the error from the thrust coefficient given in input. |
| 253 | +iteration = 2 |
| 254 | +err_new = err_old |
| 255 | +while math.fabs(err_new) >= eps and err_0 != err_old: |
| 256 | + |
| 257 | + iteration += 1 |
| 258 | + |
| 259 | + # Computation of the new Lagrange moltiplicator value based on the false position method. |
| 260 | + w_new = (w_old*err_0 - w_0*err_old)/(err_0 - err_old) |
| 261 | + |
| 262 | + # Computation of the new axial interference factor distribution. |
| 263 | + for i in range(0, Stations): |
| 264 | + a_new[i] = a_distribution(w_new*F[i],chi[i]) |
| 265 | + |
| 266 | + # Computation of the new thrust coefficient distribution. |
| 267 | + for i in range(0, Stations): |
| 268 | + dCt_new[i]= math.pi*J*J*r[i]*(1+a_new[i])*a_new[i] |
| 269 | + |
| 270 | + # Computation of the new total thrust coefficient. |
| 271 | + Ct_new = 0.0 |
| 272 | + for i in range(i_hub, Stations): |
| 273 | + Ct_new += h*dCt_new[i] |
| 274 | + |
| 275 | + # Computation of the total thrust coefficient error with respect to the input value. |
| 276 | + err_new = Ct_new - Ct |
| 277 | + |
| 278 | + print(err_new) |
| 279 | + |
| 280 | + # Updating the stored values for the next iteration. |
| 281 | + err_0 = err_old |
| 282 | + err_old = err_new |
| 283 | + |
| 284 | + w_0 = w_old |
| 285 | + w_old = w_new |
| 286 | + |
| 287 | +# Computation of the correct axial and rotational interference factors (a and ap). |
| 288 | +for i in range(0, Stations): |
| 289 | + a_optimal[i] = a_distribution(w_new*F[i],chi[i]) |
| 290 | + ap_optimal[i] = (w_new*F[i])*((1+w_new*F[i])/(chi[i]*chi[i]+math.pow(1+w_new*F[i],2))) |
| 291 | + |
| 292 | +# Computation of the correct thrust coefficient distribution. |
| 293 | +for i in range(0, Stations): |
| 294 | + dCt_optimal[i] = math.pi*J*J*r[i]*(1+a_optimal[i])*a_optimal[i] |
| 295 | + |
| 296 | +# Computation of the correct power coefficient distribution. |
| 297 | +for i in range(0, Stations): |
| 298 | + dCp[i] = (R*4*math.pi/(math.pow(n,3)*math.pow(D,5)))*(math.pow(Vinf,3)*math.pow(1+a_optimal[i],2)*a_optimal[i]*r[i]*R+math.pow(Omega,2)*Vinf*(1+a_optimal[i])*math.pow(ap_optimal[i],2)*math.pow(r[i]*R,3)) |
| 299 | + |
| 300 | +########################## |
| 301 | +### Check Results ### |
| 302 | +########################## |
| 303 | +# Computation of the total power coefficient. |
| 304 | +Cp = 0.0 |
| 305 | +for i in range(i_hub, Stations): |
| 306 | + Cp += h*dCp[i] |
| 307 | + |
| 308 | +# Computation of the total thrust coefficient. |
| 309 | +Ct_optimal = 0.0 |
| 310 | +for i in range(i_hub, Stations): |
| 311 | + Ct_optimal += h*dCt_optimal[i] |
| 312 | + |
| 313 | +# Computation of the static pressure jump distribution. |
| 314 | +for i in range(0, Stations): |
| 315 | + DeltaP[i] = (dCt_optimal[i])*(2*Vinf*Vinf)/(J*J*math.pi*r[i]) |
| 316 | + |
| 317 | +# Computation of the thrust over density (T) using the static pressure jump distribution. |
| 318 | +T = 0.0 |
| 319 | +for i in range(i_hub, Stations): |
| 320 | + T += 2*math.pi*r[i]*math.pow(R,2)*h*DeltaP[i] |
| 321 | + |
| 322 | +# Computation of the thrust coefficient using T. |
| 323 | +Ct_Renard = (T)/(math.pow(n,2)*math.pow(D,4)) |
| 324 | + |
| 325 | +# Computation of the efficiency. |
| 326 | +eta = J*(Ct_optimal/Cp) |
| 327 | + |
| 328 | +# Screen output used to check that everything worked correcty. |
| 329 | +print('%%%%%%%%%%%%%%%%%%%%%%%%% CHECK OUTPUT VALUES %%%%%%%%%%%%%%%%%%%%%%%%%') |
| 330 | +print(f' dCT distribution integral: {Ct_optimal:.4f}') |
| 331 | +print(f' dCT computed using the static pressure jump: {Ct_Renard:.4f}') |
| 332 | +print(f' dCP distribution integral: {Cp:.4f}') |
| 333 | +print(f' Thrust over Density (T/rho): {T:.4f} [N*m^3/kg]') |
| 334 | +print(f' Efficiency eta: {eta:.4f}') |
| 335 | +print(f' w0/Vinf: {w_new:.4f}') |
| 336 | +print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') |
| 337 | + |
| 338 | +########################## |
| 339 | +### File Writing ### |
| 340 | +########################## |
| 341 | +# Write the actuator disk configuration file |
| 342 | +file = open('ActuatorDisk.cfg', 'w') |
| 343 | + |
| 344 | +file.write('% Automatic generated actuator disk configuration file.\n') |
| 345 | +file.write('%\n') |
| 346 | +file.write('% The first two elements of MARKER_ACTDISK must be filled.\n') |
| 347 | +file.write('% An example of this file can be found in the TestCases directory.\n') |
| 348 | +file.write('%\n') |
| 349 | +file.write('% Author: Ettore Saetta, Lorenzo Russo, Renato Tognaccini.\n') |
| 350 | +file.write('% Theoretical and Applied Aerodynamic Research Group (TAARG),\n') |
| 351 | +file.write('% University of Naples Federico II\n') |
| 352 | +file.write('\n') |
| 353 | +file.write('ACTDISK_TYPE = VARIABLE_LOAD\n') |
| 354 | +file.write('ACTDISK_FILENAME = ActuatorDisk.dat\n') |
| 355 | +file.write('MARKER_ACTDISK = ( , , 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)\n') |
| 356 | +file.close() |
| 357 | + |
| 358 | +print('SU2 file generated!') |
| 359 | + |
| 360 | +# Write the actuator disk data file. |
| 361 | +# This is the actuator disk input data file. |
| 362 | +Print_external_file(dCt_optimal, dCp) |
| 363 | + |
| 364 | +########################## |
| 365 | +### Plots ### |
| 366 | +########################## |
| 367 | +# Automatically plot the computed propeller performance. |
| 368 | + |
| 369 | +f1 = pl.figure(1) |
| 370 | +pl.plot(r, dCt_optimal, 'r', markersize=4, label='$\\frac{dCT}{d\overline{r}}$') |
| 371 | +pl.plot(r, dCp, 'k', markersize=4, label='$\\frac{dCP}{d\overline{r}}$') |
| 372 | +pl.grid(True) |
| 373 | +pl.legend(numpoints=3) |
| 374 | +pl.xlabel('$\overline{r}$') |
| 375 | +pl.ylabel('') |
| 376 | +pl.title("Load Distribution") |
| 377 | + |
| 378 | +f1 = pl.figure(2) |
| 379 | +pl.plot(chi, a_optimal, 'r', markersize=4, label='$a$') |
| 380 | +pl.plot(chi, ap_optimal, 'k', markersize=4, label='$a^1$') |
| 381 | +pl.grid(True) |
| 382 | +pl.legend(numpoints=3) |
| 383 | +pl.xlabel('$\chi$') |
| 384 | +pl.ylabel('') |
| 385 | +pl.title("Interference Factors") |
| 386 | + |
| 387 | +if corr == True: |
| 388 | + f1 = pl.figure(3) |
| 389 | + pl.plot(r, F, 'k', markersize=4) |
| 390 | + pl.grid(True) |
| 391 | + pl.xlabel('$\overline{r}$') |
| 392 | + pl.ylabel('$F(\overline{r})$') |
| 393 | + pl.title("Tip Loss Prandtl Correction Function") |
| 394 | + |
| 395 | +pl.show() |
0 commit comments