Case Study: Analysis of a Double Pipe Heat Exchanger
Contents
1.4. Case Study: Analysis of a Double Pipe Heat Exchanger¶
A stalwart of undergraduate chemical engineering laboratories is study of a double-pipe heat exchanger in counter-current flow. In this case, a student group collected multiple measurements of flow and temperature data from a heat exchanger with sensors configured as shown in the following diagram. (Note: The diagram shows co-current flow. The data was collected with the valves configured for counter-current flow of the hot stream.)
Source: Gunt WL315C Product Description
1.4.1. Reading Data¶
The raw data was copied to a new sheet in the same Google Sheets file, edited to conform with Tidy Data, and a link created using the procedures outlined above for reading data from Google Sheets. The data is read in the following cell.
hx = pd.read_csv("https://docs.google.com/spreadsheets/d/e/2PACX-1vSNUCEFMaGZ-y18p-AnDoImEeenMLbRxXBABwFNeP8I3xiUejolPJx-kr4aUywD0szRel81Kftr8J0R/pub?gid=865146464&single=true&output=csv")
hx.head()
Flow Rate H | Flow Rate C | Trial # | Hot Flow (L/hr) | Cold Flow (L/hr) | Time | H Outlet | H Inlet | C Inlet | C Outlet | |
---|---|---|---|---|---|---|---|---|---|---|
0 | H | H | 1 | 651 | 798 | 32:08.1 | 37.3 | 56.4 | 15.5 | 30.8 |
1 | H | H | 1 | 651 | 798 | 32:07.8 | 37.2 | 56.3 | 15.4 | 30.8 |
2 | H | H | 1 | 651 | 798 | 32:07.6 | 37.2 | 56.3 | 15.4 | 30.8 |
3 | H | M | 2 | 650 | 512 | 29:13.0 | 41.4 | 56.4 | 15.6 | 34.7 |
4 | H | M | 2 | 650 | 512 | 29:12.3 | 41.4 | 56.4 | 15.6 | 34.7 |
1.4.2. Energy Balances¶
The first step in this analysis is to verify the energy balance.
The next cell creates two new calculated variables in the dataframe for \(Q_h\) and \(Q_c\), and uses the pandas plotting facility to visualize the results. This calculation takes advantage of the “one variable per column” rule of Tidy Data which enables calculations for all observations to be done in a single line of code.
# heat capacity of water
rho = 1.00 # kg / liter
Cp = 4.18 # kJ/ kg / deg C
# heat balances
hx["Qh"] = rho * Cp * hx["Hot Flow (L/hr)"] * (hx["H Inlet"] - hx["H Outlet"]) / 3600
hx["Qc"] = rho * Cp * hx["Cold Flow (L/hr)"] * (hx["C Outlet"] - hx["C Inlet"]) / 3600
hx["Loss (%)"] = 100 * (1 - hx["Qc"]/hx["Qh"])
# plot
display(hx[["Qh", "Qc", "Loss (%)"]].style.format(precision=2))
hx.plot(y = ["Qh", "Qc"], ylim = (0, 15), grid=True, xlabel="Observation", ylabel="kW")
Qh | Qc | Loss (%) | |
---|---|---|---|
0 | 14.44 | 14.18 | 1.81 |
1 | 14.44 | 14.27 | 1.17 |
2 | 14.44 | 14.27 | 1.17 |
3 | 11.32 | 11.35 | -0.30 |
4 | 11.32 | 11.35 | -0.30 |
5 | 11.32 | 11.35 | -0.30 |
6 | 6.46 | 6.11 | 5.41 |
7 | 6.46 | 6.09 | 5.77 |
8 | 6.46 | 6.09 | 5.77 |
9 | 12.79 | 12.55 | 1.85 |
10 | 12.79 | 12.55 | 1.85 |
11 | 12.79 | 12.55 | 1.85 |
12 | 10.33 | 9.89 | 4.32 |
13 | 10.33 | 9.95 | 3.76 |
14 | 10.28 | 9.95 | 3.21 |
15 | 6.06 | 5.80 | 4.33 |
16 | 6.06 | 5.80 | 4.33 |
17 | 6.06 | 5.80 | 4.33 |
18 | 7.09 | 7.25 | -2.27 |
19 | 7.12 | 7.25 | -1.93 |
20 | 7.12 | 7.25 | -1.93 |
21 | 6.28 | 6.33 | -0.81 |
22 | 6.28 | 6.33 | -0.81 |
23 | 6.28 | 6.33 | -0.81 |
<AxesSubplot:xlabel='Observation', ylabel='kW'>
1.4.3. Overall Heat Transfer Coefficient \(UA\)¶
The performance of a counter-current heat exchanger is given the relationship
where \(\Delta T_{lm}\) is the log-mean temperature given by
dT0 = hx["H Outlet"] - hx["C Inlet"]
dT1 = hx["H Inlet"] - hx["C Outlet"]
hx["LMTD"] = (dT1 - dT0) / np.log(dT1/dT0)
Q = (hx.Qh + hx.Qc)/2
hx["UA"] = Q/hx.LMTD
hx.plot(y="UA", xlabel="Observation", ylabel="kW/deg C", grid=True)
<AxesSubplot:xlabel='Observation', ylabel='kW/deg C'>
1.4.4. How does \(UA\) depend on flowrates?¶
The data clearly demonstrate that the heat transfer coefficient in the double pipe heat exchanger depends on flowrates of both the cold and hot liquid streams. We can see this by inspecting the data.
hx[["Flow Rate H", "Flow Rate C", "Hot Flow (L/hr)", "Cold Flow (L/hr)", "UA"]]
Flow Rate H | Flow Rate C | Hot Flow (L/hr) | Cold Flow (L/hr) | UA | |
---|---|---|---|---|---|
0 | H | H | 651 | 798 | 0.604966 |
1 | H | H | 651 | 798 | 0.608145 |
2 | H | H | 651 | 798 | 0.608145 |
3 | H | M | 650 | 512 | 0.478571 |
4 | H | M | 650 | 512 | 0.478571 |
5 | H | M | 650 | 512 | 0.478571 |
6 | H | L | 655 | 201 | 0.288997 |
7 | H | L | 655 | 201 | 0.288976 |
8 | H | L | 655 | 201 | 0.288976 |
9 | M | H | 503 | 795 | 0.553374 |
10 | M | H | 503 | 795 | 0.553374 |
11 | M | H | 503 | 795 | 0.553374 |
12 | M | M | 500 | 498 | 0.436787 |
13 | M | M | 500 | 498 | 0.438975 |
14 | M | M | 500 | 498 | 0.436765 |
15 | M | L | 502 | 199 | 0.276936 |
16 | M | L | 502 | 199 | 0.276936 |
17 | M | L | 502 | 199 | 0.276936 |
18 | L | H | 205 | 801 | 0.383826 |
19 | L | H | 205 | 801 | 0.383744 |
20 | L | H | 205 | 801 | 0.383744 |
21 | L | M | 204 | 500 | 0.320247 |
22 | L | M | 204 | 500 | 0.320247 |
23 | L | M | 204 | 500 | 0.320247 |
The replicated measurements provide an opportunity to compute averages. Here we use the pandas .groupby()
function to group observations and compute means. The data will be used to plot results, so we’ll save the results of these calculations as a new dataframe for reuse.
sx = hx.groupby(["Flow Rate H", "Flow Rate C"]).mean()[["Hot Flow (L/hr)", "Cold Flow (L/hr)", "UA"]]
sx
Hot Flow (L/hr) | Cold Flow (L/hr) | UA | ||
---|---|---|---|---|
Flow Rate H | Flow Rate C | |||
H | H | 651.0 | 798.0 | 0.607085 |
L | 655.0 | 201.0 | 0.288983 | |
M | 650.0 | 512.0 | 0.478571 | |
L | H | 205.0 | 801.0 | 0.383771 |
M | 204.0 | 500.0 | 0.320247 | |
M | H | 503.0 | 795.0 | 0.553374 |
L | 502.0 | 199.0 | 0.276936 | |
M | 500.0 | 498.0 | 0.437509 |
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1)
sx.sort_values("Cold Flow (L/hr)").groupby("Flow Rate H").plot(x = "Cold Flow (L/hr)", y = "UA",
style={"UA": 'ro-'}, ax=ax)
Flow Rate H
H AxesSubplot(0.125,0.125;0.775x0.755)
L AxesSubplot(0.125,0.125;0.775x0.755)
M AxesSubplot(0.125,0.125;0.775x0.755)
dtype: object
fig, ax = plt.subplots(1, 1)
sx.sort_values("Hot Flow (L/hr)").groupby("Flow Rate C").plot(x = "Hot Flow (L/hr)", y = "UA",
style={"UA": 'ro-'}, ax=ax)
Flow Rate C
H AxesSubplot(0.125,0.125;0.775x0.755)
L AxesSubplot(0.125,0.125;0.775x0.755)
M AxesSubplot(0.125,0.125;0.775x0.755)
dtype: object
1.4.5. Fitting a Model for \(UA\)¶
For a series of transport mechanisms, the overall heat transfer coefficient
\(U_{tube}A\) is a constant for this experiment. \(U_h\)A and \(U_c\)A varying with flowrate and proporitonal to dimensionless Nusselt number. The hot and cold liquid flows in the double pipe heat exchanger are well within the range for fully developed turbulent flow. Under these conditions for flows inside closed tubes, the Dittus-Boelter equation provides an explicit expression for Nusselt number
where \(C\) is a constant, \(Re\) is the Reynold’s number that is proportional to flowrate, and \(Pr\) is the Prandtl number determined by fluid properties.
Experimentally, consider a set of values for \(UA\) determined by varying \(\dot{m}_h\) and \(\dot{m}_c\) over range of values. Because Reynold’s number is proportional to flowrate, we can propose a model
This suggests a linear regression for \(R = \frac{1}{UA}\) in terms of \(X_h = \dot{q}_h^{-0.8}\) and \(X_c = \dot{q}_c^{-0.8}\).
Enhancement: Convert to Skikit-learn
The following model fit should be converted to Scikit-learn to enable use of ML tools to the model building process.
hx["R"] = 1.0/hx["UA"]
hx["Xh"] = hx["Hot Flow (L/hr)"]**(-0.8)
hx["Xc"] = hx["Cold Flow (L/hr)"]**(-0.8)
import statsmodels.formula.api as sm
result = sm.ols(formula="R ~ Xh + Xc", data = hx).fit()
print(result.params)
print(result.summary())
Intercept 0.141716
Xh 115.292199
Xc 186.346764
dtype: float64
OLS Regression Results
==============================================================================
Dep. Variable: R R-squared: 0.997
Model: OLS Adj. R-squared: 0.997
Method: Least Squares F-statistic: 3711.
Date: Tue, 01 Feb 2022 Prob (F-statistic): 1.70e-27
Time: 08:53:59 Log-Likelihood: 44.929
No. Observations: 24 AIC: -83.86
Df Residuals: 21 BIC: -80.32
Df Model: 2
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 0.1417 0.032 4.425 0.000 0.075 0.208
Xh 115.2922 2.465 46.762 0.000 110.165 120.419
Xc 186.3468 2.233 83.445 0.000 181.703 190.991
==============================================================================
Omnibus: 9.087 Durbin-Watson: 0.894
Prob(Omnibus): 0.011 Jarque-Bera (JB): 2.268
Skew: 0.227 Prob(JB): 0.322
Kurtosis: 1.564 Cond. No. 334.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
hx["Rh"] = 115.3 * hx["Xh"]
hx["Rc"] = 186.3 * hx["Xc"]
hx["Rt"] = 0.142
hx["R_pred"] = hx["Rt"] + hx["Rh"] + hx["Rc"]
hx[["R", "R_pred", "Rt", "Rh", "Rc"]]
R | R_pred | Rt | Rh | Rc | |
---|---|---|---|---|---|
0 | 1.652986 | 1.677494 | 0.142 | 0.647090 | 0.888404 |
1 | 1.644344 | 1.677494 | 0.142 | 0.647090 | 0.888404 |
2 | 1.644344 | 1.677494 | 0.142 | 0.647090 | 0.888404 |
3 | 2.089553 | 2.056945 | 0.142 | 0.647886 | 1.267059 |
4 | 2.089553 | 2.056945 | 0.142 | 0.647886 | 1.267059 |
5 | 2.089553 | 2.056945 | 0.142 | 0.647886 | 1.267059 |
6 | 3.460242 | 3.462974 | 0.142 | 0.643926 | 2.677047 |
7 | 3.460489 | 3.462974 | 0.142 | 0.643926 | 2.677047 |
8 | 3.460489 | 3.462974 | 0.142 | 0.643926 | 2.677047 |
9 | 1.807095 | 1.828466 | 0.142 | 0.795380 | 0.891085 |
10 | 1.807095 | 1.828466 | 0.142 | 0.795380 | 0.891085 |
11 | 1.807095 | 1.828466 | 0.142 | 0.795380 | 0.891085 |
12 | 2.289447 | 2.236672 | 0.142 | 0.799196 | 1.295476 |
13 | 2.278034 | 2.236672 | 0.142 | 0.799196 | 1.295476 |
14 | 2.289559 | 2.236672 | 0.142 | 0.799196 | 1.295476 |
15 | 3.610936 | 3.637197 | 0.142 | 0.796648 | 2.698550 |
16 | 3.610936 | 3.637197 | 0.142 | 0.796648 | 2.698550 |
17 | 3.610936 | 3.637197 | 0.142 | 0.796648 | 2.698550 |
18 | 2.605346 | 2.658637 | 0.142 | 1.630896 | 0.885741 |
19 | 2.605903 | 2.658637 | 0.142 | 1.630896 | 0.885741 |
20 | 2.605903 | 2.658637 | 0.142 | 1.630896 | 0.885741 |
21 | 3.122589 | 3.070617 | 0.142 | 1.637288 | 1.291329 |
22 | 3.122589 | 3.070617 | 0.142 | 1.637288 | 1.291329 |
23 | 3.122589 | 3.070617 | 0.142 | 1.637288 | 1.291329 |
1.4.6. Comparison of Model to Experimental Data¶
hx["UA_pred"] = 1/hx["R_pred"]
hx.plot(y = ["UA", "UA_pred"], grid=True, title="Heat Transfer Coefficient")
<AxesSubplot:title={'center':'Heat Transfer Coefficient'}>