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:

(1)#\[\begin{equation} H_p \cong (\frac{n}{n - 1}) (p_d v_d - p_s v_s) \end{equation}\]

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\):

(2)#\[\begin{equation} f = \frac{H_{ds} - H_s}{(\frac{n_s}{n_s - 1})(p_d v_{ds} - p_s v_s)} \end{equation}\]

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:

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.