Estimate Thermodynamic Parameters from Data
Main.VLEWilson History
Hide minor edits - Show changes to markup
(:html:)
<div id="disqus_thread"></div> <script type="text/javascript"> /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */ var disqus_shortname = 'apmonitor'; // required: replace example with your forum shortname /* * * DON'T EDIT BELOW THIS LINE * * */ (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = 'https://' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> <a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
(:htmlend:)
Using data from an ebulliometer, determine parameters for the Wilson activity coefficient model using the measured data for an ethanol-cyclohexane mixture at ambient pressure. Use the results to determine whether there is:
Using data from an ebulliometer, determine parameters for a Vapor Liquid Equilibrium chart using the Wilson activity coefficient model with measured data for an ethanol-cyclohexane mixture at ambient pressure. Use the results to determine whether there is:
from gekko import GEKKO import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.optimize import fsolve from scipy.optimize import curve_fit from scipy.interpolate import interp1d
- from numpy import *
R = 1.9858775 # cal/mol K
def vaporP(T: float, component: list):
''' :param T: temp [K] :param component: [vapor pressure parameters] :return: Vapor Pressure [Pa] ''' A = component[0] B = component[1] C = component[2] D = component[3] E = component[4] return np.exp(A + B/T + C*np.log(T) + D*T**E)
def liquidMolarVolume(T: float, component: list):
''' :param T: temp in [K} :param component: component liquid molar volume : constants in list or array :return: liquid molar volume [] ''' A = component[0] B = component[1] C = component[2] D = component[3] return 1 / A * B ** (1 + (1 - T / C) ** D)
def Lij(aij: float, T: float, Vlvali: float, Vlvalj: float):
# Vl1 = liquidMolarVolume(T, Vlvali) # Vl2 = liquidMolarVolume(T, Vlvalj) Vl1 = Vlvali Vl2 = Vlvalj return Vl2 / Vl1 * np.exp(-aij / (R * T))
def Gamma(a12: float, a21: float, x1: float, Temp: float, Component: int):
''' :param Component: :param a12: :param a21: :param x1: :param Temp: :return: ''' # Liquid molar volume Vethanol = 5.8492e-2 VCyclochexane = 0.10882 x2 = 1.0 - x1 L12 = Lij(a12, Temp, Vethanol, VCyclochexane) L21 = Lij(a21, Temp, VCyclochexane, Vethanol) L11 = 1.0 L22 = 1.0 if Component == 1: A = x1 + x2 * L12 B = x1 * L11 / (x1 * L11 + x2 * L12) +x2 * L21 / (x1 * L21 + x2 * L22) elif Component == 2: A = x2 + x1 * L21 B = x2 * L22 / (x2 * L22 + x1 * L21) +x1 * L12 / (x2 * L12 + x1 * L11) return np.exp(1.0 - np.log(A) - B)
- yi * P = xi * Îģi * Pisat
- Component 1: Ethanol
- Component 2: Cyclohexane
def massTOMoleF(x: list):
#Converts RI to mole fraction of Ethanol MW_Ethanol = 46.06844 MW_cyclohexane = 84.15948 xeth = 49.261*x**2 - 152.51*x + 117.32 xcyc = 1-xeth moleth = xeth / MW_Ethanol molcyc = xcyc/ MW_cyclohexane return moleth/ (moleth + molcyc)
- Read in actual data
- filename = 'RealData.csv'
filename = 'http://apmonitor.com/me575/uploads/Main/vle_data.txt' data = pd.read_csv(filename)
temp_data = data['Temp_D'].values + 273.15 # K IndexMeasuredY = data['Distills_RI'] IndexMeasuredX = data['Bottoms_RI'] x1_data = massTOMoleF(IndexMeasuredX) x1_data[0] = 0 x1_data[1] = 1 y1_data = massTOMoleF(IndexMeasuredY) y1_data[0] = 0 y1_data[1] = 1
- Ethanol & Cyclohexane Properties ---------
- Psat paramater values
PsatC2H5OH = [73.304, -7122.3, -7.1424, 2.8853e-6, 2.0] PsatC6H12 = [51.087, -5226.4, -4.2278, 9.7554e-18, 6.0]
- thermo Liquid Molar Volume paramaters
Vl_C2H5OH = [1.6288, 0.27469, 514.0, 0.23178] Vl_C6H12 = [0.88998, 0.27376, 553.8, 0.28571]
- Build Model ------------------------------
m = GEKKO()
- Define Variables & Parameters
a12_ = m.FV(value = 2026.3) a12_.STATUS = 1 a21_ = m.FV(value = 390.4) a21_.STATUS = 1 Temp = m.Var(333) x1 = m.Param(value = np.array(x1_data)) y1 = m.CV(value = np.array(y1_data)) y1.FSTATUS = 1 # min fstatus * (meas-pred)^2 y2 = m.Intermediate(1.0 - y1) P = 85900 # Pa in Provo, UT
- Calc Vapor pressures of ethanol & cyclohexane at
- different temps using thermo parameters
- Psat = exp(A + B / T + C * log(T) + D * T ** E)
PsatEthanol = m.Intermediate(
m.exp( PsatC2H5OH[0] + PsatC2H5OH[1] / Temp + PsatC2H5OH[2] * m.log(Temp) + PsatC2H5OH[3] * Temp ** PsatC2H5OH[4] )
)
PsatCyclohexane = m.Intermediate(
m.exp( PsatC6H12[0] + PsatC6H12[1] / Temp + PsatC6H12[2] * m.log(Temp) + PsatC6H12[3] * Temp ** PsatC6H12[4] )
)
- Calculate Liquid Molar volumes of components 1 and 2:
- ethanol & cyclohexane, respectively
LMV_C2H5OH = 5.8492e-2 LMV_C6H12 = 0.10882
- Construct parameters for Wilson Model
L12 = m.Intermediate(LMV_C6H12 / LMV_C2H5OH * m.exp(-a12_ / (R * Temp))) L21 = m.Intermediate(LMV_C2H5OH / LMV_C6H12 * m.exp(-a21_ / (R * Temp))) L11 = 1.0 L22 = 1.0
x2 = m.Intermediate(1.0 - x1)
A1 = m.Intermediate(x1 + x2 * L12) B1 = m.Intermediate(x1 * L11 / (x1 * L11 + x2 * L12) + x2 * L21 / (x1 * L21 + x2 * L22))
A2 = m.Intermediate(x2 + x1 * L21) B2 = m.Intermediate(x2 * L22 / (x2 * L22 + x1 * L21) + x1 * L12 / (x2 * L12 + x1 * L11))
- Find Gammas for each component
G_C2H5OH = m.Intermediate(m.exp(1.0 - m.log(A1) - B1)) G_C6H12 = m.Intermediate(m.exp(1.0 - m.log(A2) - B2))
m.Equation(y2 == (G_C6H12 * PsatCyclohexane / P) * x2) m.Equation(y1 == (G_C2H5OH * PsatEthanol / P) * x1)
- Options
m.options.IMODE = 2 m.options.EV_TYPE = 2 # Objective type, SSE m.options.NODES = 3 # Collocation nodes m.options.SOLVER = 3 # IPOPT
m.solve()
regressed_a12 = a12_.value[-1] regressed_a21 = a21_.value[-1]
a12_Hysys = 2026.3 a21_Hysys = 390.4
x1_linspace = np.linspace(0, 1.0, 100) y1_predicted = [] T_predicted = []
print("Activity Coefficient Parameters") print('Model a12 a21') print('Gekko: ' + str(round(regressed_a12,2)) + str(round(regressed_a21,2))) print('Hysys: ' + str(round(a12_Hysys,2)) + str(round(a21_Hysys,2)))
def getValues(y1_and_Temp: list, a12: float, a21: float, P: float, x1: float):
y1 = y1_and_Temp[0] T = y1_and_Temp[1] y2 = 1 - y1 x2 = 1 - x1 A = y1*P - x1*Gamma(a12,a21,x1,T,1) * vaporP(T,PsatC2H5OH) B = y2*P - x2*Gamma(a12,a21,x1,T,2) * vaporP(T,PsatC6H12) return [A,B]
TempInterp = interp1d(x1_data, temp_data) for i in range(len(x1_linspace)):
answers = fsolve(getValues, [x1_linspace[i], TempInterp(x1_linspace[i])], args = (regressed_a12, regressed_a21, P, x1_linspace[i])) y1_predicted.append(answers[0]) T_predicted.append(answers[1])
GammaPred = Gamma(regressed_a12, regressed_a21, x1_data, temp_data, 1) GammaPredHysys = Gamma(a12_Hysys, a21_Hysys, x1_data, temp_data, 2)
VP_Ethanol = vaporP(temp_data, PsatC2H5OH)
y1Predicted = (GammaPred * VP_Ethanol / P) * x1_data y1PredictedHysys = (GammaPredHysys * VP_Ethanol / P) * x1_data
plt.figure(1) plt.plot(x1_data,y1_data,"bx",label = "Measured Data") plt.plot(x1_linspace,x1_linspace,"-", color="#8bd8bd", label="Reference",markersize=7) plt.plot(x1_linspace,y1_predicted,"--", color="#243665",label = "Model",markersize=7) plt.plot(x1_data, y1Predicted, "o",
color="#ec8b5e",label="Predicted",markersize=5)
plt.legend(loc = "best") plt.xlabel("x") plt.ylabel("y") plt.grid()
plt.figure(2) plt.plot(x1_data, temp_data, "go") plt.plot(y1_data, temp_data, "bo") plt.plot(x1_linspace, T_predicted, "r--",) plt.plot(y1_predicted, T_predicted, "k--",) plt.legend(["$x_1$ Measured", "$y_1$ Measured", "$x_1$ Predicted", "$y_1$ Predicted"]) plt.xlabel("$x, y$") plt.ylabel("$Temp$") plt.grid()
plt.show()
APM Python
Python GEKKO

(:toggle hide gekko button show="Show GEKKO (Python) Code":) (:div id=gekko:)

(:source lang=python:)
(:sourceend:) (:divend:)
Nonlinear Confidence Interval
(:html:) <iframe width="560" height="315" src="https://www.youtube.com/embed/rL7Mvl2-XIM" frameborder="0" allowfullscreen></iframe> (:htmlend:)
Nonlinear confidence intervals can be visualized as a function of 2 parameters. In this case, both parameters are simultaneously varied to find the confidence region. The confidence interval is determined with an F-test that specifies an upper limit to the deviation from the optimal solution

with p=2 (number of parameters), n=number of measurements, theta=[parameter 1, parameter 2] (parameters), theta* as the optimal parameters, SSE as the sum of squared errors, and the F statistic that has 3 arguments (alpha=confidence level, degrees of freedom 1, and degrees of freedom 2). For many problems, this creates a multi-dimensional nonlinear confidence region. In the case of 2 parameters, the nonlinear confidence region is a 2-dimensional space. Below is an example that shows the confidence region for the dye fading experiment confidence region for forward and reverse activation energies.

The optimal parameter values are in the 95% confidence region. This plot demonstrates that the 2D confidence region is not necessarily symmetric.
Case Study on Thermodynamic Parameter Estimation
Thermodynamic Parameter Estimation
(:html:) <iframe width="560" height="315" src="https://www.youtube.com/embed/ZJXgcrJ7Elw" frameborder="0" allowfullscreen></iframe> (:htmlend:)
(:htmlend:)
(:html:)
<div id="disqus_thread"></div> <script type="text/javascript"> /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */ var disqus_shortname = 'apmonitor'; // required: replace example with your forum shortname /* * * DON'T EDIT BELOW THIS LINE * * */ (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = 'https://' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> <a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
Using data from an ebulliometer, determine parameters for the Wilson activity coefficient model using the measured data for an ethanol-cyclohexane mixture at ambient pressure. Use the results to determine whether there is:
Using data from an ebulliometer, determine parameters for the Wilson activity coefficient model using the measured data for an ethanol-cyclohexane mixture at ambient pressure. Use the results to determine whether there is:
where y1is the vapor mole fraction, P is the pressure, x1 is the liquid mole fraction, gamma1 is the activity coefficient that is different than 1.0 for non-ideal mixtures, and Psat1 is the pure component vapor pressure. The same equation also applies to component 2 in the mixture with the corresponding equation with subscript 2.
where y1is the vapor mole fraction, P is the pressure, x1 is the liquid mole fraction, gamma1 is the activity coefficient that is different than 1.0 for non-ideal mixtures, and P1sat is the pure component vapor pressure. The same equation also applies to component 2 in the mixture with the corresponding equation with subscript 2.
There are correlations for Psat1 for many common pure components from BYU's DIPPR database. In this case Psat1 is a function of temperature according to
There are correlations for Psat1 and density (rho) for many common pure components from BYU's DIPPR database. In this case P1sat is a function of temperature according to
and density rho or molar volume v is also a function of temperature according to

(:title Estimate Thermodynamic Parameters from Data:) (:keywords VLE, wilson equation, nonlinear, optimization, engineering optimization, dynamic estimation, interior point, active set, differential, algebraic, modeling language, university course:) (:description Case study on data reconciliation for thermodynamic properties using optimization techniques in engineering:)
Case Study on Thermodynamic Parameter Estimation
Using data from an ebulliometer, determine parameters for the Wilson activity coefficient model using the measured data for an ethanol-cyclohexane mixture at ambient pressure. Use the results to determine whether there is:
- An azeotrope in the system and, if so, at what composition
- The values of the activity coefficients at the infinitely dilute compositions
- gamma1 at x1=0
- gamma2 at x1=1
The liquid and vapor compositions of this binary mixture are related by the following thermodynamic relationships

where y1is the vapor mole fraction, P is the pressure, x1 is the liquid mole fraction, gamma1 is the activity coefficient that is different than 1.0 for non-ideal mixtures, and Psat1 is the pure component vapor pressure. The same equation also applies to component 2 in the mixture with the corresponding equation with subscript 2.
The Wilson equation is used to predict the activity coefficients gamma2 and gamma2 over the range of liquid compositions.


There are correlations for Psat1 for many common pure components from BYU's DIPPR database. In this case Psat1 is a function of temperature according to

The number of degrees of freedom in a multi-component and multi-phase system is given by DOF = 2 + #Components - #Phases. In this case, there are two phases (liquid and vapor) and two components (ethanol and cyclohexane). This leads to two degrees of freedom that must be specified. In this case, we can chose to fix two of the four measured values for this system with either x1, y1, P, or T. It is recommended to fix the values of x1 and P as shown in the tutorial below.
Background on Parameter Estimation
A common application of optimization is to estimate parameters from experimental data. One of the most common forms of parameter estimation is the least squares objective with (model-measurement)^2 summed over all of the data points. The optimization problem is subject to the model equations that relate the model parameters or exogenous inputs to the predicted measurements. The model predictions are connected by common parameters that are adjusted to minimize the sum of squared errors.
Tutorial on Parameter Estimation
(:html:) <iframe width="560" height="315" src="https://www.youtube.com/embed/ss4jDiLTQ1A" frameborder="0" allowfullscreen></iframe> (:htmlend:)