import pandas as pd
import numpy as np
import sys
import re
import matplotlib.pyplot as plt
import seaborn as sns
if sys.version_info[0] < 3:
from StringIO import S
tringIO
else:
from io import StringIO
read_txt(filename, model)
def read_txt(filename, model):
"""Reads the .txt files from APC and returns a pandas Dataframe.
Parameters
----------
filename : str, path object or file-like object
Name of the file to be read.
model : str
Name of the model to be read.
Returns
-------
pandas.DataFrame
A txt file is returned as a two-dimensional data structure with labeled axes.
"""
buffer=""
model=str(model)
num_lines = sum(1 for line in open(filename))
line_on_focus = []
rpm_lines = np.arange(14, num_lines, 37)
count = 1
headers=['Velocity (mph)', 'J (Adv Ratio)', 'Pe', 'Ct', 'Cp', 'PWR (Hp)', 'Torque (In-Lbf)', 'Thrust(Lbf)', 'RPM']
np.arange(14, num_lines, 37)
for i in range(18, num_lines+1):
if (count==38):
count=1
if (count<=30):
line_on_focus.append(i)
count=count+1
count=1
with open(filename) as f:
for line in f:
if count in rpm_lines:
line = line.strip()
line = re.sub(" +", " ",line)
line = re.sub("PROP RPM = ", "", line)
rpm=line
if count in line_on_focus:
line = line.strip()
line = re.sub(" +", " ",line)
buffer = buffer+line+' '+rpm+"\n"
count+=1
f.close()
buffer=buffer.replace(" ", ",")
buffer=StringIO(buffer)
df=pd.read_csv(buffer, header=None, names=headers)
print('Erros de NAN:\n',df.isna().sum(), '\n')
df = df.dropna()
df = df.apply(pd.to_numeric)
df['Model'] = model
return df
def SI_dataframe (df):
"""Reads Dataframes with the data in the imperial system and returns a dataframe in the international measurement system.
Parameters
----------
df : Pandas DataFrame
DataFrame to be converted to the SI system.
Returns
-------
pandas.DataFrame
Pandas DataFrame converted to the international measurement system.
"""
df['Velocity (mph)'] = df['Velocity (mph)']*0.44704
df['PWR (Hp)'] = df['PWR (Hp)']*746
df['Torque (In-Lbf)'] = df['Torque (In-Lbf)']*0.112984833333333
df['Thrust(Lbf)'] = df['Thrust(Lbf)']*4.448222
df['RPM'] = df['RPM']/60
df.columns=['Velocity (m/s)', 'J (Adv Ratio)', 'Pe', 'Ct', 'Cp', 'PWR (W)', 'Torque (N/m)', 'Thrust (N)', 'Frequency (Hz)', 'Model']
return df
APCplot(df, group, x, y, xlabel=None, ylabel=None, percent_x=False, percent_y=False, save_at=None)
def APCplot(df, group, x, y, xlabel=None, ylabel=None, percent_x=False, percent_y=False, save_at=None):
"""Receives a DataFrame with the data, groups it by model and plots a 2D figure with the columns specified in * x * and * y *. You can also save the picture to a file.
Parameters
----------
df : Pandas DataFrame
DataFrame which is the source of the data.
group : str with name of one of the df columns
Used to group the DataFrame, also used for the legend of the plots
x : str with name of one of the df columns
Values to be line plotted on the x axis.
y : str with name of one of the df columns
Values to be line plotted on the y axis.
xlabel : str, optional
Title of the x axis (default is x).
ylabel : str, optional
Title of the y axis (default is y).
percent_x : bool, optional
Are values in x percentages stored as fractions of 1 (default is False).
percent_y : bool, optional
Are values in y percentages stored as fractions of 1 (default is False).
save_at : str, optional
Path wherein to save the figure on disk (default is None).
"""
import matplotlib.ticker as mtick
grouped = df.groupby(group)
plt.style.use('seaborn')
fig = plt.figure()
ax = fig.add_subplot(111)
colors = list(sns.color_palette('husl', len(grouped.size().index)))
if percent_x:
ax.xaxis.set_major_formatter(mtick.PercentFormatter())
count=0
for i in grouped.size().index:
ax.plot(100*grouped.get_group(i)[x], grouped.get_group(i)[y], label=i, color=colors[count])
count+=1
if percent_y:
ax.yaxis.set_major_formatter(mtick.PercentFormatter())
count=0
for i in grouped.size().index:
ax.plot(grouped.get_group(i)[x], 100*grouped.get_group(i)[y], label=i, color=colors[count])
count+=1
else:
count=0
for i in grouped.size().index:
ax.plot(grouped.get_group(i)[x], grouped.get_group(i)[y], label=i, color=colors[count])
count+=1
if xlabel is not None:
ax.set_xlabel(xlabel)
if ylabel is not None:
ax.set_ylabel(ylabel)
else:
ax.set_xlabel(x)
ax.set_ylabel(y)
ax.legend()
if save_at is not None:
plt.savefig(save_at)
return
models = {'13x4' : '13x4.dat.txt',
'12x6' : '12x6.dat.txt',
'12x4' : '12x4.dat.txt',
'12.25x3.75' : '1225x375.dat.txt',
'11x7' : '11x7.dat.txt',
'11.5x6' : '115x6.dat.txt',
'11.5x4' : '115x4.dat.txt'}
data = []
count=1
for i in models.keys():
print('Model ', i, 'imported succefully.')
cache = read_txt(models[i], i)
cache = SI_dataframe(cache)
data.append(cache)
if count==len(models):
print('All files read succefully, Data Imported.')
count+=1
df = pd.concat(data, ignore_index=True)
Model 13x4 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 0 Ct 0 Cp 0 PWR (Hp) 0 Torque (In-Lbf) 0 Thrust(Lbf) 0 RPM 0 dtype: int64 Model 12x6 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 1 Ct 1 Cp 1 PWR (Hp) 1 Torque (In-Lbf) 1 Thrust(Lbf) 0 RPM 1 dtype: int64 Model 12x4 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 1 Ct 1 Cp 1 PWR (Hp) 1 Torque (In-Lbf) 1 Thrust(Lbf) 0 RPM 1 dtype: int64 Model 12.25x3.75 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 0 Ct 0 Cp 0 PWR (Hp) 0 Torque (In-Lbf) 0 Thrust(Lbf) 0 RPM 0 dtype: int64 Model 11x7 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 5 Ct 5 Cp 5 PWR (Hp) 5 Torque (In-Lbf) 5 Thrust(Lbf) 0 RPM 5 dtype: int64 Model 11.5x6 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 0 Ct 0 Cp 0 PWR (Hp) 0 Torque (In-Lbf) 0 Thrust(Lbf) 0 RPM 0 dtype: int64 Model 11.5x4 imported succefully. Erros de NAN: Velocity (mph) 0 J (Adv Ratio) 0 Pe 0 Ct 0 Cp 0 PWR (Hp) 0 Torque (In-Lbf) 0 Thrust(Lbf) 0 RPM 0 dtype: int64 All files read succefully, Data Imported.
df
Velocity (m/s) | J (Adv Ratio) | Pe | Ct | Cp | PWR (W) | Torque (N/m) | Thrust (N) | Frequency (Hz) | Model | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0.000000 | 0.00 | 0.0000 | 0.0738 | 0.0239 | 0.746 | 0.004858 | 0.289134 | 16.666667 | 13x4 |
1 | 0.089408 | 0.02 | 0.0545 | 0.0723 | 0.0240 | 0.746 | 0.004858 | 0.284686 | 16.666667 | 13x4 |
2 | 0.178816 | 0.04 | 0.1064 | 0.0708 | 0.0240 | 0.746 | 0.004971 | 0.275790 | 16.666667 | 13x4 |
3 | 0.312928 | 0.05 | 0.1557 | 0.0692 | 0.0241 | 0.746 | 0.004971 | 0.271342 | 16.666667 | 13x4 |
4 | 0.402336 | 0.07 | 0.2023 | 0.0675 | 0.0241 | 0.746 | 0.004971 | 0.266893 | 16.666667 | 13x4 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
4008 | 46.939200 | 0.46 | 0.3796 | 0.0216 | 0.0261 | 2916.860 | 1.325651 | 23.575577 | 350.000000 | 11.5x4 |
4009 | 48.816768 | 0.48 | 0.3302 | 0.0165 | 0.0239 | 2674.410 | 1.215491 | 18.077574 | 350.000000 | 11.5x4 |
4010 | 50.694336 | 0.50 | 0.2519 | 0.0112 | 0.0221 | 2466.276 | 1.121149 | 12.250403 | 350.000000 | 11.5x4 |
4011 | 52.571904 | 0.51 | 0.1442 | 0.0057 | 0.0202 | 2260.380 | 1.027484 | 6.196373 | 350.000000 | 11.5x4 |
4012 | 54.449472 | 0.53 | -0.0018 | -0.0001 | 0.0184 | 2057.468 | 0.935062 | -0.066723 | 350.000000 | 11.5x4 |
4013 rows × 10 columns
A Senior project was found that shows the maximum frequency values for two of these propellers on that engine, models 12.25 x 3.75 and 13x4, to be 206.25 Hz and 192.3834 Hz respectively.
With that, the maximum power, at these frequencies in these models, was found to be 736,302 W and 741,524 W and the average of the two, 738,913 W, was taken as the maximum power delivered by the engine.
df1=0
df1 = df.groupby('Model')
df1 = df1.get_group('12.25x3.75')
df1 = df1[(df1['Frequency (Hz)'] <= 206.25)]
max_power_1225x375 = df1['PWR (W)'].max()
df1=0
df1 = df.groupby('Model')
df1 = df1.get_group('13x4')
df1 = df1[(df1['Frequency (Hz)'] <= 192.3834)]
max_power_13x4 = df1['PWR (W)'].max()
max_power = np.mean([max_power_1225x375, max_power_13x4])
Afterwards, all entries with power greater than max power were removed from the dataframe.
dfs=[]
cache = 0
for i in models.keys():
df1=0
df1=df.groupby('Model')
df1 = df1.get_group(i)
df1=df1[(df1['PWR (W)'] <= max_power)]
df1 = df1.groupby('Frequency (Hz)')
cache = df1.size()
cache = cache.sort_index(ascending=False)
best_frequency = cache.idxmax()
df1 = df1.get_group(best_frequency)
dfs.append(df1)
df1 = pd.concat(dfs)
df1
Velocity (m/s) | J (Adv Ratio) | Pe | Ct | Cp | PWR (W) | Torque (N/m) | Thrust (N) | Frequency (Hz) | Model | |
---|---|---|---|---|---|---|---|---|---|---|
270 | 0.000000 | 0.00 | 0.0000 | 0.0782 | 0.0252 | 539.358 | 0.514646 | 30.697180 | 166.666667 | 13x4 |
271 | 0.983488 | 0.02 | 0.0557 | 0.0766 | 0.0248 | 531.898 | 0.507415 | 30.069981 | 166.666667 | 13x4 |
272 | 1.966976 | 0.04 | 0.1105 | 0.0749 | 0.0244 | 524.438 | 0.500297 | 29.407196 | 166.666667 | 13x4 |
273 | 2.950464 | 0.05 | 0.1642 | 0.0732 | 0.0241 | 516.978 | 0.493179 | 28.713273 | 166.666667 | 13x4 |
274 | 3.933952 | 0.07 | 0.2165 | 0.0713 | 0.0238 | 509.518 | 0.486174 | 27.988213 | 166.666667 | 13x4 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
3738 | 27.716480 | 0.47 | 0.7061 | 0.0173 | 0.0116 | 243.196 | 0.193204 | 6.187477 | 200.000000 | 11.5x4 |
3739 | 28.834080 | 0.49 | 0.6530 | 0.0131 | 0.0099 | 206.642 | 0.164393 | 4.679530 | 200.000000 | 11.5x4 |
3740 | 29.906976 | 0.51 | 0.5579 | 0.0088 | 0.0081 | 168.596 | 0.134339 | 3.144893 | 200.000000 | 11.5x4 |
3741 | 31.024576 | 0.53 | 0.3741 | 0.0044 | 0.0063 | 131.296 | 0.104511 | 1.583567 | 200.000000 | 11.5x4 |
3742 | 32.142176 | 0.55 | -0.0021 | 0.0000 | 0.0046 | 95.488 | 0.076152 | -0.004448 | 200.000000 | 11.5x4 |
210 rows × 10 columns
One of the fundamental ways to measure performance of propellers is to assess their performance in low velocities, (below takeoff velocity) since a higher thrust in these velocities can mean a faster acceleration a reduced runway length.
The figure below show that it is quite clear that the 12.25 x 3.75 model is the best performing one in this set.
APCplot(df=df1, group='Model', x='Velocity (m/s)', y='Thrust (N)')
We can find the coefficients of the thrust curve (T) as a function of velocity (v) assuming a quadratic relationship and applying a least squares method, the following is the equation with the relationship and the sum of squares of the errors.
$$T(v) = -0.014436v^{2} + -0.845198v + 38.942274 \quad error = 0.010472$$grouped = 0
grouped = df1.groupby('Model')
x=np.arange(0, 40, step=0.01)
coef = np.polyfit(grouped.get_group('12.25x3.75')['Velocity (m/s)'], grouped.get_group('12.25x3.75')['Thrust (N)'], 2, full=True)
#print('v^%f + v*%f + %f, erro = %f' % (coef[0][0], coef[0][1], coef[0][2], coef[1]))
fit = np.polyval(coef[0] , x)
fit = fit[fit>=0]
x = x[0:len(fit)]
plt.scatter(grouped.get_group('12.25x3.75')['Velocity (m/s)'], grouped.get_group('12.25x3.75')['Thrust (N)'], label='Data from propeller 12.25 x 3.75', color=sns.color_palette('husl', 7)[3])
plt.plot(x, fit, label='Fit', color='k')
plt.legend()
plt.xlabel('Velocity (m/s)')
plt.ylabel('Thrust (N)')
plt.savefig('fig_4.png', dpi=100)
plt.show()
The formal definition of the efficiency (𝜂) of a propeller is fraction of the power it delivers in relation to the power recieved by the engine: $$\eta=\frac{{P}_{effective}}{{P}_{engine}}$$
In this case calculated with the available data, as defined by the bibliographic reference of the suplier UIUC:
D being the diameter of the propeller, 𝜌 the density of the air and n the frequency in revolutions per second, one can define the coefficients of thrust ($C_{t}$) and power ($C_{p}$) as: ${C}_{t} = \frac{T}{\rho {n}^{2} {D}^{4}}$ and ${C}_{p} = \frac{P}{\rho {n}^{3} {D}^{5}}$
With that, 𝜂 can be written as: $$\eta=\frac{{C}_{t}J}{{C}_{p}}$$
APCplot(df=df1, group='Model', x='J (Adv Ratio)', y='Pe',
xlabel='J (Advance Ratio)', ylabel = 'η (Efficiency)',
percent_y = True, save_at = 'fig_2.png')
As you can see in the figure above, the propellers all have relatively high efficiencies (above 60%).
The 12.25 x 3.75 model was chosen, even though it had the lowest efficiency of all, 71.56%, for having an efficiency above the values set as acceptable in the literature (above 60%) and for having the largest area in the thrust-efficiency curve. This is due to an inverse relationship between these two variables as shown in the Figure below.
APCplot(df=df1, group='Model', x='Thrust (N)', y='Pe',
xlabel='Empuxo (N)', ylabel = 'η (Eficiência)',
percent_y = True, save_at = 'fig_3.png')