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