Polytropic Methods
Contents
Polytropic Methods#
The work of [Schultz, 1962] was the first to present a polytropic analysis considering real-gas relations. According to this work the polytropic head can be calculated by:
The accuracy of the above result depends upon the constancy of \(n\) along \(p\). To improve this result and try to make it independent of the constancy of \(n\) along \(p\), [Schultz, 1962] proposed the use of the polytropic head factor \(f\):
Although the correction by this factor can improve the results, publications such as [Mallen and Saville, 1977], [Huntington, 1985] and [Sandberg and Colby, 2013] have questioned their accuracy and proposed different methods to carry out the polytropic head and efficiency calculation.
Available polytropic methods in ccp
#
With ccp
we can make the polytropic calculations using the following methods:
Schultz method as per [Schultz, 1962] implemented in
ccp.point.head_pol_schultz()
;Mallen-Saville method as per [Mallen and Saville, 1977] implemented in
ccp.point.head_pol_mallen_saville()
;Huntington 3 point method as per [Huntington, 1985] implemented in
ccp.point.head_pol_huntington()
;Sandberg-Colby method as per [Sandberg and Colby, 2013] implemented in
ccp.point.head_pol_sandberg_colby()
;Reference method as per [Huntington, 1985] implemented in
ccp.point.head_reference()
;Reference method as per [Huntington, 2017] implemented in
ccp.point.head_reference_2017()
.
But which method should we use? And how can we select the method to use with ccp
?
To make a comparison between these methods we need to have a base value. In our case we are going to use the reference method described by [Huntington, 2017]. This method does an integration on the polytropic path and it can be considered as a base value for comparison.
The cases that will be used for comparison are from [Evans et al., 2017].
import pandas as pd
import ccp
import plotly.graph_objects as go
import time
import ipywidgets as widgets
from tqdm.notebook import tqdm
Q_ = ccp.Q_
pd.options.plotting.backend = "plotly"
# load cases
df = pd.read_csv('cases_all.csv', index_col='Parameters')
The following functions are used to help us calculate the suction and discharge thermodynamic states and the polytropic efficiency for each method:
def states(case):
"""Calculate the suction and discharge states for each case."""
ps = Q_(df.loc["ps bara", case], "bar")
pd = Q_(df.loc["pd bara", case], "bar")
Ts = Q_(df.loc["Ts degC", case], "degC")
Td = Q_(df.loc["Td degC", case], "degC")
fluid = {}
for component in [
"METHANE",
"ETHANE",
"PROPANE",
"ISOBUTANE",
"BUTANE",
"PENTANE",
"ISOPENTANE",
"HEXANE",
"NITROGEN",
"CO2",
"ETHYLENE",
"HYDROGENSULFIDE",
"R12",
"R134A",
]:
frac_molar = df.loc[component, case]
if frac_molar != 0.:
fluid[component] = frac_molar
suc = ccp.State(p=ps, T=Ts, fluid=fluid)
disch = ccp.State(p=pd, T=Td, fluid=fluid)
return suc, disch
def calculate_case(case, func):
"""Calculate the polytropic efficiency for a case given a specific function."""
tic = time.perf_counter()
suc, disch = states(case)
eff = func(suc, disch)
toc = time.perf_counter()
total_time = toc - tic
return eff, total_time
Now we build a loop to go over each case with our five different polytropic methods:
methods = {
"Efficiency (No correction)": ccp.point.eff_pol,
"Efficiency Schultz f": ccp.point.eff_pol_schultz,
"Efficiency Mallen-Savile": ccp.point.eff_pol_mallen_saville,
"Efficiency Sandberg-Colby": ccp.point.eff_pol_sandberg_colby,
"Efficiency Huntington 3": ccp.point.eff_pol_huntington,
}
for case in tqdm(df.columns):
(head_ref, eff_ref), total_time = calculate_case(case, ccp.point.head_reference_2017)
df.loc["Efficiency Reference (100 steps)", case] = eff_ref
for method, func in methods.items():
eff, total_time = calculate_case(case, func)
df.loc[method, case] = eff
df.loc[f"{method}_time", case] = total_time
df.loc[f"{method}_error", case] = abs(df.loc[method, case] - eff_ref)
Error comparison for polytropic methods#
Finally, we build a plot to visualize the error for each method when compared to the reference method:
fig = go.Figure()
x = [case for case in df.columns]
y = [method for method in methods.keys()]
z = []
z_time = []
hovertext = []
for method in methods.keys():
results = []
time_results = []
text_list = []
for case in df.columns:
results.append(df.loc[f"{method}_error", case])
time_results.append(df.loc[f"{method}_time", case])
suc, disch = states(case)
text = (f"Case: {case}"
f"<br>Error: {df.loc[f'{method}_error', case]:.5f}"
f"<br>Time: {df.loc[f'{method}_time', case]:.5f}"
f"<br>ps: {df.loc['ps bara', case]}"
f"<br>pd: {df.loc['pd bara', case]}"
f"<br>Ts: {df.loc['Ts degC', case]}"
f"<br>Td: {df.loc['Td degC', case]}"
f"<br>zs: {suc.z():.2f}"
f"<br>zd: {disch.z():.2f}"
f"<br>ρ_s: {suc.rho():.2f}"
f"<br>ρ_d: {disch.rho():.2f}"
f"<br>MW: {suc.molar_mass('g/mole'):.2f}"
f"<br>Num. Comp.: {len(suc.fluid)}"
)
text_list.append(text)
hovertext.append(text_list)
z.append(results)
z_time.append(time_results)
fig.add_trace(
go.Heatmap(
z=z,
x=x,
y=y,
hoverinfo='text',
text=hovertext,
colorscale='viridis',
)
)
In the plot below we can visualize the same results for some cases in a bar plot:
def tab_cases(variable):
num_cases = 15
children = []
for case in df.columns[:num_cases]:
fig = df.loc[(df.index.str.contains(variable))].loc[:, case].plot(kind='bar')
g = go.FigureWidget(data=next(fig.select_traces(0)))
g.update_layout(showlegend=False)
suc, disch = states(case)
text = widgets.HTML(f"<br><br><br>Case: {case}"
f"<br>Error: {df.loc[f'{method}_error', case]:.5f}"
f"<br>Time: {df.loc[f'{method}_time', case]:.5f}"
f"<br>ps: {df.loc['ps bara', case]} bar"
f"<br>pd: {df.loc['pd bara', case]} bar"
f"<br>Ts: {df.loc['Ts degC', case]} K"
f"<br>Td: {df.loc['Td degC', case]} K"
f"<br>zs: {suc.z():~.2H}"
f"<br>zd: {disch.z():~.2H}"
f"<br>ρ_s: {suc.rho():~.2H}"
f"<br>ρ_d: {disch.rho():~.2H}"
f"<br>MW: {suc.molar_mass('g/mole'):~.2H}"
f"<br>Num. Comp.: {len(suc.fluid)}"
)
children.append(widgets.HBox([g, text]))
tab = widgets.Tab(children=children)
for i in range(len(children)):
tab.set_title(i, df.columns[i])
return tab
tab_cases('error')
And here we have the mean error for each method:
fig = df.loc[(df.index.str.contains('error'))].mean(axis=1).plot(kind='bar')
fig.update_layout(showlegend=False)
fig
Time comparison for polytropic methods#
As we can see in plot, the Huntington 3 point method produces the best results. But it is also important to evaluate the speed of each method. In the plot bellow we see the time needed to calculate each case.
fig = go.Figure()
fig.add_trace(
go.Heatmap(
z=z_time,
x=x,
y=y,
hoverinfo='text',
text=hovertext,
colorscale='viridis',
)
)
tab_cases('time')
fig = df.loc[(df.index.str.contains('time'))].mean(axis=1).plot(kind='bar')
fig.update_layout(showlegend=False)
fig
As we can see the Huntington 3 point method is slower than the other methods. This occurs because the method requires more calls to calculate state properties.
Changing the ccp polytropic method#
When we create a point with ccp the head and efficiency are automatically calculated and are available in the
.head
and .eff
attributes:
ps = Q_(3, "bar")
Ts = 300
fluid = {
"CarbonDioxide": 0.79585,
"Nitrogen": 0.16751,
"Oxygen": 0.02903,
}
suc0 = ccp.State(fluid=fluid, p=ps, T=Ts)
disch0 = ccp.State(fluid=fluid, p=Q_(7.255, "bar"), T=391.1)
point0 = ccp.Point(
suc=suc0,
disch=disch0,
speed=Q_(7941, "RPM"),
flow_m=Q_(34203.6, "kg/hr"),
b=0.0285,
D=0.365,
)
print(point0.head)
print(point0.eff)
60979.58278842361 joule / kilogram
0.744749320240875 dimensionless
This calculation is carried out with the polytropic method defined in the ccp.config.POLYTROPIC_METHOD
variable:
ccp.config.POLYTROPIC_METHOD
'schultz'
As we can see, the default calculation method for ccp
is 'schultz'
, which is the polytropic method adopted by
the ASME PTC 10 standard. This could change in the future. As pointed out by [Huntington, 2020] the ASME PTC
10 could adopt in the future the Huntington 3 point method and the Sandberg-Colby method.
If you want to change the calculation method in ccp you can do it like this:
ccp.config.POLYTROPIC_METHOD = 'huntington'
point0 = ccp.Point(
suc=suc0,
disch=disch0,
speed=Q_(7941, "RPM"),
flow_m=Q_(34203.6, "kg/hr"),
b=0.0285,
D=0.365,
)
print(point0.head)
print(point0.eff)
61024.20643742365 joule / kilogram
0.7452943130194375 dimensionless
References#
- EH+17
Fred Evans, Spencer Huble, and others. Centrifugal compressor performance making enlightened analysis decisions. 2017. doi:https://hdl.handle.net/1969.1/166782.
- Hun85
R. A. Huntington. Evaluation of Polytropic Calculation Methods for Turbomachinery Performance. Journal of Engineering for Gas Turbines and Power, 107(4):872–876, 10 1985. doi:10.1115/1.3239827.
- Hun17
Richard Huntington. Another new look at polytropic calculations methods for turbomachinery performance. pages, 09 2017. doi:10.13140/RG.2.2.10331.87841.
- Hun20
Richard Huntington. Polytropic calculation wars are over. pages, 11 2020. doi:10.13140/RG.2.2.26600.90887/1.
- MS77
M Mallen and G Saville. Polytropic processes in the performance prediction of centrifugal compressors. Institution of mechanical engineers, London, United Kingdom, pages 89–96, 1977.
- SC13
Mark E Sandberg and Gary M Colby. Limitations of asme ptc 10 in accurately evaluation centrifugal compressor thermodynamic performance. In Proceedings of the 42nd Turbomachinery Symposium. Texas A&M University. Turbomachinery Laboratories, 2013. doi:10.21423/R1505Q.
- Sch62
John M. Schultz. The Polytropic Analysis of Centrifugal Compressors. Journal of Engineering for Power, 84(1):69–82, 01 1962. doi:10.1115/1.3673381.