Skip to content

Commit 91dd8a0

Browse files
authored
Merge branch 'develop' into feature_aero_thermo_elasticity
2 parents f7fdb29 + b1dcaf2 commit 91dd8a0

1 file changed

Lines changed: 395 additions & 0 deletions

File tree

SU2_PY/OptimalPropeller.py

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
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

Comments
 (0)