From 29b34603ede5aa3dddaedaf1e68410686ec031e3 Mon Sep 17 00:00:00 2001 From: Ian Bell <ian.bell@nist.gov> Date: Thu, 15 Jul 2021 11:01:33 -0400 Subject: [PATCH] Updated isochoric to to make it more like a reference --- notebooks/isochoric.py | 417 +++++++++++------------------------------ 1 file changed, 110 insertions(+), 307 deletions(-) diff --git a/notebooks/isochoric.py b/notebooks/isochoric.py index 3edea4c..30052a0 100644 --- a/notebooks/isochoric.py +++ b/notebooks/isochoric.py @@ -1,82 +1,20 @@ +# Standard libraries import timeit, sys +# Scipy stack packages import numpy as np import pandas from scipy.integrate import solve_ivp import matplotlib.pyplot as plt +from matplotlib.backends.backend_pdf import PdfPages -import VLEIsoTracer as vle -# print('VLEIsoTracer is located at:', vle.__file__) -IMPOSED_T = vle.VLEIsolineTracer.imposed_variable.IMPOSED_T +# Some integration tools from PDSim +from PDSIMintegrators import AbstractRK45ODEIntegrator, AbstractSimpleEulerODEIntegrator -sys.path.append('../bld/Release') +# Special packages import teqp import CoolProp.CoolProp as CP -def get_drhovecdp_sat(model, T, rhovecL, rhovecV): - tic = timeit.default_timer() - Hliq = teqp.build_Psi_Hessian_autodiff(model, T, rhovecL) - Hvap = teqp.build_Psi_Hessian_autodiff(model, T, rhovecV) - Hvap[~np.isfinite(Hvap)] = 1e20 - Hliq[~np.isfinite(Hliq)] = 1e20 - - N = len(rhovecL) - A = np.zeros((N, N)) - b = np.ones((N, 1)) - assert(len(rhovecL)==len(rhovecV)) - if np.all(rhovecL != 0) and np.all(rhovecV != 0): - A[0,0] = np.dot(Hliq[0,:], rhovecV) - A[0,1] = np.dot(Hliq[1,:], rhovecV) - A[1,0] = np.dot(Hliq[0,:], rhovecL) - A[1,1] = np.dot(Hliq[1,:], rhovecL) - - drhodp_liq = np.linalg.solve(A, b).squeeze() - drhodp_vap = np.linalg.solve(Hvap, np.dot(Hliq,drhodp_liq)) - - return drhodp_liq, drhodp_vap - else: - # Special treatment for infinite dilution - - murL = teqp.build_Psir_gradient_autodiff( model, T, rhovecL) - murV = teqp.build_Psir_gradient_autodiff( model, T, rhovecV) - RL = model.get_R(rhovecL/rhovecL.sum()) - RV = model.get_R(rhovecV/rhovecV.sum()) - - # First, for the liquid part - for i in range(N): - for j in range(N): - if rhovecL[j] == 0: - # Analysis is special if j is the index that is a zero concentration. If you are multiplying by the vector - # of liquid concentrations, a different treatment than the case where you multiply by the vector - # of vapor concentrations is required - # ... - # Initial values - Aij = Hliq[j,:]*(rhovecV if i == 0 else rhovecL) # coefficient-wise product - # A throwaway boolean for clarity - is_liq = (i==1) - # Apply correction to the j term (RT if liquid, RT*phi for vapor) - Aij[j] = RL*T if is_liq else RL*T*np.exp(-(murV[j] - murL[j])/(RL*T)) - # Fill in entry - A[i, j] = Aij.sum() - else: - # Normal - A[i, j] = np.dot(Hliq[j,:], rhovecV if (i == 0) else rhovecL) - drhodp_liq = np.linalg.solve(A, b) - - # Then, for the vapor part, also requiring special treatment - # Left-multiplication of both sides of equation by diagonal matrix with liquid concentrations along diagonal, all others zero - diagrhovecL = np.diagflat(rhovecL) - PSIVstar = diagrhovecL@Hvap - PSILstar = diagrhovecL@Hliq - for j in range(N): - if rhovecL[j] == 0: - PSILstar[j, j] = RL*T - PSIVstar[j, j] = RV*T/np.exp(-(murV[j] - murL[j])/(RV*T)) - drhodp_vap = np.linalg.solve(PSIVstar, PSILstar@drhodp_liq) - toc = timeit.default_timer() - # print(toc-tic) - return drhodp_liq.squeeze(), drhodp_vap.squeeze() - def get_dxdp(*, rhovec, drhovecdp): rhomolar = np.sum(rhovec) molefrac = rhovec/rhomolar @@ -90,145 +28,139 @@ def get_drhovecdx(*, i, model, T, rhovec): dpdxL = 1.0/get_dxdp(rhovec=rhovecL, drhovecdp=drhovecdpL) return np.array((drhovecdpL*dpdxL[i]).tolist()+(drhovecdpV*dpdxL[i]).tolist()) -def traceT(model, T, rhovec0, *, kwargs={}): +def traceT_x(model, T, rhovec0, *, kwargs={}): R = model.get_R(np.array([0.0, 1.0])) def rhovecprime(x, rhovec): + print(x) return get_drhovecdx(i=0, model=model, T=T, rhovec=rhovec) tic = timeit.default_timer() - sol = solve_ivp(rhovecprime, [0.0, 0.5], y0=rhovec0, method='RK45',dense_output=True, t_eval = np.linspace(0,0.49,1000), rtol=1e-8) + sol = solve_ivp(rhovecprime, [1.0, 0.2], y0=rhovec0, method='RK45',dense_output=True, t_eval = np.linspace(1,0.21,1000), rtol=1e-8) p = [teqp.get_pr(model, T, sol.y[0:2,j]) + R*T*sol.y[0:2,j].sum() for j in range(len(sol.t))] toc = timeit.default_timer() - print(toc-tic) + x = sol.y[0,:]/(sol.y[0,:] + sol.y[1,:]) y = sol.y[2,:]/(sol.y[2,:] + sol.y[3,:]) - plt.plot(sol.t, p, 'r',**kwargs) + plt.plot(x, p, 'r',**kwargs) plt.plot(y, p, 'g',**kwargs) def traceT_arclength(model, T, rhovec0, *, kwargs={}): R = model.get_R(np.array([0.0, 1.0])) print(T) + c = 1.0 + def norm(x): return (x*x).sum()**0.5 + def rhovecprime(t, rhovec): tic = timeit.default_timer() drhovecdpL, drhovecdpV = teqp.get_drhovecdp_Tsat(model, T, rhovec[0:2], rhovec[2::]) + # drhovecdpL, drhovecdpV = tracer.get_drhovecdp_sat(rhovec[0:2], rhovec[2::], T)[0:2] + # print(, drhovecdpL, drhovecdpV) + toc = timeit.default_timer() # print(toc-tic) - - def norm(x): - return (x*x).sum()**0.5 dpdt = (norm(drhovecdpL) + norm(drhovecdpV))**-0.5 der = np.zeros_like(rhovec) der[0:2] = (drhovecdpL*dpdt).squeeze() der[2:4] = (drhovecdpV*dpdt).squeeze() - # print(rhovec, rhovec[0]+rhovec[1], rhovec[2]+rhovec[3]) + # print(t, rhovec, rhovec[0]+rhovec[1], rhovec[2]+rhovec[3], rhovec[0]/(rhovec[0]+rhovec[1])) + # if any(~np.isfinite(rhovec)): + # raise ValueError() return c*der + class TestIntegrator(object): + """ + Implements the functions needed to satisfy the ABC requirements + """ + + def __init__(self, Nmax): + self.x, self.y = [], [] + self.Itheta = 0 + self.Nmax = Nmax + self.termination_reason = None + + def post_deriv_callback(self): pass + + def premature_termination(self): + if self.Itheta > self.Nmax: + self.termination_reason = 'too many steps' + return True + if np.any(self.xold<0): + self.termination_reason = 'negative x' + return True + if np.any(~np.isfinite(self.xold)): + self.termination_reason = 'nan value of x' + return True + return False + + def get_initial_array(self): + return rhovec0.copy() + + def pre_step_callback(self): + if self.Itheta == 0: + self.x.append(0) + self.y.append(self.get_initial_array().tolist()) + + def post_step_callback(self): + self.x.append(self.t0_cache) + self.y.append(self.xold_cache.tolist()) + # Polish the solution... + + def derivs(self, t0, xold): + self.t0_cache = t0 + self.xold_cache = xold.copy() + return rhovecprime(t0, xold) + + class EulerIntegrator(TestIntegrator, AbstractSimpleEulerODEIntegrator): + """ Mixin class using the functions defined in TestIntegrator """ + pass + + class RK45Integrator(TestIntegrator, AbstractRK45ODEIntegrator): + """ Mixin class using the functions defined in TestIntegrator """ + pass + der_init = rhovecprime(0, rhovec0) if np.any(rhovec0 + der_init*1e-6 < 0): c *= -1 - # print('init', der_init, T) + print('flip c', der_init, rhovec0) events = [lambda t,x: x[0], lambda t,x: x[1], lambda t,x: x[2], lambda t,x: x[3]] events.append(lambda t, z: ((z[0]+z[1])/(z[2]+z[3])-1)-0.2) - # class StepCounter(): - # def __init__(self, Nmax): - # self.counter = 0 - # self.Nmax = Nmax - # def __call__(self, t, z): - # self.counter += 1 - # v = self.Nmax-self.counter+0.5 - # return v - # events.append(StepCounter(200)) for e in events: e.direction = -1 e.terminal = True + # tic = timeit.default_timer() + # sol = solve_ivp(rhovecprime, [0.0, 200000], y0=rhovec0.copy(), method='RK45',dense_output=True, events=events, rtol=1e-8, atol=1e-10) + # # print(sol.t_events, sol.y_events) + # # print(sol.message) + # print(sol.t, sol.nfev) + # toc = timeit.default_timer() + # print(toc-tic,'s for parametric w/ solve_ivp') + # t = np.linspace(np.min(sol.t), np.max(sol.t), 10000) + # soly = sol.sol(t) + tic = timeit.default_timer() - sol = solve_ivp(rhovecprime, [0.0, 3500000], y0=rhovec0.copy(), method='RK45',dense_output=True, events=events)#, rtol=1e-8) - # print(sol.t_events, sol.y_events) - # print(sol.message) + i = RK45Integrator(Nmax=1000) + i.do_integration(0, 5000000, atol=1e-6, rtol=1e-6, hmin=4.46772051e-03) + t = i.x + soly = np.array(i.y).T toc = timeit.default_timer() - print(toc-tic,'s for parametric') - t = np.linspace(np.min(sol.t), np.max(sol.t), 1000) - soly = sol.sol(t) + print(toc-tic,'s for parametric w/ my RK45') + reason = i.termination_reason + if reason: + print('reason:', reason) + print(i.Itheta) + x = soly[0,:]/(soly[0,:] + soly[1,:]) y = soly[2,:]/(soly[2,:] + soly[3,:]) - p = [teqp.get_pr(model, T, soly[0:2,j]) + R*T*soly[0:2,j].sum() for j in range(len(t))] - plt.plot(x, p, 'r', **kwargs) - plt.plot(y, p, 'g', **kwargs) - # print(x,p) - -def drhovecdT_isopleth(model, T, rhovec): - assert(len(rhovec)==4) - rhovecL = rhovec[0:2] - rhovecV = rhovec[2::] - Hliq = teqp.build_Psi_Hessian_autodiff(model, T, rhovecL) - Hvap = teqp.build_Psi_Hessian_autodiff(model, T, rhovecV) - rhoL = sum(rhovecL) - rhoV = sum(rhovecV) - xL = rhovecL/rhoL - xV = rhovecV/rhoV - R = model.get_R(xL) - deltas = teqp.get_dchempotdT_autodiff(model, T, rhovecV) - teqp.get_dchempotdT_autodiff(model, T, rhovecL) - deltarho = rhovecV-rhovecL - dpdTV = R*rhoV*(1+model.get_Ar01(T, rhoV, xV)-model.get_Ar11(T, rhoV, xV)) - dpdTL = R*rhoL*(1+model.get_Ar01(T, rhoL, xL)-model.get_Ar11(T, rhoL, xL)) - deltabeta = dpdTV-dpdTL - drhovecdTL = (np.dot(deltas,rhovecV)-deltabeta)/np.dot(Hliq@deltarho, xL)*xL - drhovecdTV = np.linalg.solve(Hvap, Hliq@drhovecdTL-deltas) - return np.array(drhovecdTL.tolist()+drhovecdTV.tolist()) - -def robust_VLE(model, T, Tc, rhoc): - DeltaT = 1e-2*Tc - Tg = Tc-DeltaT - if Tg < 0: - raise ValueError("T > Tc") - rholiq, rhovap = teqp.extrapolate_from_critical(model, Tc, rhoc, Tg) - if rholiq < 0 or rhovap < 0: - raise ValueError("Negative density obtained from critical extrapolation") - rholiq, rhovap = teqp.pure_VLE_T(model, Tg, rholiq, rhovap, 100) - - z = np.array([1.0]) - R = model.get_R(z) - - def dpdTsat(model, T, rhoL, rhoV): - """ dp/dT = ((hV-hL)/T)/(vV-vL) - The ideal parts of enthalpy cancel, leaving just the residual parts - """ - numV = R*(model.get_Ar01(T, rhoV, z) + model.get_Ar10(T,rhoV,z)) - numL = R*(model.get_Ar01(T, rhoL, z) + model.get_Ar10(T,rhoL,z)) - num = numV-numL - den = 1/rhoV-1/rhoL - return num/den - - def get_drhodT_sat(model, T, rhoL, rhoV, Q): - dpdT = dpdTsat(model, T, rhoL, rhoV) - - rho = rhoL if Q == 0 else rhoV - Ar01 = model.get_Ar01(T, rho, z) - Ar02 = model.get_Ar02n(T, rho, z)[2] - Ar11 = model.get_Ar11(T, rho, z) + pL = [teqp.get_pr(model, T, soly[0:2,j]) + R*T*soly[0:2,j].sum() for j in range(len(t))] + pV = [teqp.get_pr(model, T, soly[2:4,j]) + R*T*soly[2:4,j].sum() for j in range(len(t))] + plt.plot(x, pL, 'r', ms=0.1, **kwargs) + plt.plot(y, pV, 'g', ms=0.1, **kwargs) + # plt.plot(y, p, 'g', **kwargs) - dpdrho__T = R*T*(1 + 2*Ar01 + Ar02) - dpdT__rho = R*rho*(1 + Ar01 - Ar11) - - drhodT__p = -dpdT__rho/dpdrho__T - drhodp__T = 1/dpdrho__T - return drhodT__p + drhodp__T*dpdT - - def rhoprime(T, rhoLrhoV): - rhoL, rhoV = rhoLrhoV - drhoLdT = get_drhodT_sat(model, T, rhoL, rhoV, 0) - drhoVdT = get_drhodT_sat(model, T, rhoL, rhoV, 1) - return np.array([drhoLdT, drhoVdT]) - - sol = solve_ivp(rhoprime, [Tg, T], y0=[rholiq, rhovap], method='RK45', dense_output=False, rtol=1e-8) - rholiq, rhovap = sol.y[:,-1] - rholiq, rhovap = teqp.pure_VLE_T(model, T, rholiq, rhovap, 100) - return rholiq, rhovap - -def prettyPX(model, puremodels, ipure, Tvec): +def prettyPX(model, puremodels, names, ipure, Tvec, *, plot_critical=True, show=True, ofname=None): """ ipure: int index (0-based) of the pure fluid model to use @@ -237,7 +169,7 @@ def prettyPX(model, puremodels, ipure, Tvec): tic = timeit.default_timer() Tcvec = model.get_Tcvec() rhocvec = 1/model.get_vcvec() - k = 1 # Have to start at pure second component for now... In theory either are possible. + k = 1 T0 = Tcvec[k] rho0 = rhocvec rho0[1-k] = 0 @@ -247,6 +179,7 @@ def prettyPX(model, puremodels, ipure, Tvec): df = pandas.DataFrame(curveJSON) df['z0 / mole frac.'] = df['rho0 / mol/m^3']/(df['rho0 / mol/m^3']+df['rho1 / mol/m^3']) plt.plot(df['z0 / mole frac.'], df['p / Pa'], lw=2) + #print(toc-tic, 'complete critical locus') def plot_isotherms(): for T in Tvec: @@ -265,149 +198,19 @@ def prettyPX(model, puremodels, ipure, Tvec): except BaseException as BE: print(BE) # raise - plot_critical_curve() - plot_isotherms() - - plt.gca().set(xlabel='$x_1$ / mole frac.', ylabel='$p$ / Pa') - plt.tight_layout(pad=0.2) - plt.savefig('pxdiagram.pdf') - plt.show() - -def main(names): - T = 300 - - backend = 'HEOS' - tracer = vle.VLEIsolineTracer(IMPOSED_T, T, backend, names) - # Set flags - tracer.set_allowable_error(1e-6) - tracer.polishing(True) - tracer.set_debug_polishing(False) - - tracer.set_forwards_integration(True) - tracer.set_unstable_termination(False) - tracer.set_stepping_variable(vle.VLEIsolineTracer.stepping_variable.STEP_IN_RHO1) - - tracer.trace() - data = tracer.get_tracer_data() - print(tracer.get_termination_reason()) - print(tracer.get_tracing_time()) - - fig, ax4 = plt.subplots(1,1,figsize=(6, 4)) - x = np.array(data.x).T[0] - x1 = np.array(data.x).T[1] - y = np.array(data.y).T[0] - pL = np.array(data.pL) - rhoLmat = np.array(data.rhoL) - rhoVmat = np.array(data.rhoV) - # ax4.plot(x, rhoLmat[:,0],color='b') - # ax4.plot(x, rhoLmat[:,1],color='r') - # ax4.plot(x, rhoVmat[:,0],dashes = [2,2], color='b') - # ax4.plot(x, rhoVmat[:,1],dashes = [2,2], color='r') - dx0dpL = np.diff(x)/np.diff(pL) - dx1dpL = np.diff(x1)/np.diff(pL) - ax4.plot(x, pL, dashes=[2,2]) - ax4.plot(y, pL, dashes=[2,2]) - - model = teqp.build_multifluid_model(names, '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json') - - # For now, using the EOS directly to get started... (ultimately either superancillary or extrapolation from critical) - AS = CP.AbstractState('HEOS', names[1]) - AS.update(CP.QT_INPUTS, 0, T) - rhoL = AS.saturated_liquid_keyed_output(CP.iDmolar) - rhoV = AS.saturated_vapor_keyed_output(CP.iDmolar) - rhovec = np.array([0, rhoL, 0, rhoV]) - - # Initial slopes of dpdx1 and dpdy1 - for m in [model]: - drhodp_liq, drhodp_vap = get_drhovecdp_sat(m, T, rhovec[0:2], rhovec[2::]) - dpdxL = 1.0/get_dxdp(rhovec=rhovec[0:2], drhovecdp=drhodp_liq) - dpdxV = 1.0/get_dxdp(rhovec=rhovec[2::], drhovecdp=drhodp_vap) - xx = np.linspace(0, 0.4) - plt.plot(xx, xx*dpdxL[0]+AS.p(), dashes=[2, 2]) - plt.plot(xx, xx*dpdxV[0]+AS.p(), dashes=[2, 2]) - traceT(model, T, rhovec.copy()) - - # for f in np.linspace(0.9, 1.1, 100): - # modeltweak = teqp.build_BIPmodified(model, {'betaT': 1.0, 'gammaT': 1.001633952*f, 'betaV': 1.0, 'gammaV': 1.006268954}) - # try: - # traceT(modeltweak, T, rhovec.copy(), kwargs=dict(lw=0.5, dashes=[3,1,1,1])) - # except: - # pass - - for increment in np.linspace(-120,60,30): - T2 = T+increment - try: - AS = CP.AbstractState('HEOS', names[1]) - AS.update(CP.QT_INPUTS, 0, T2) - rhoL = AS.saturated_liquid_keyed_output(CP.iDmolar) - rhoV = AS.saturated_vapor_keyed_output(CP.iDmolar) - rhovec = np.array([0, rhoL, 0, rhoV]) - traceT_arclength(model, T2, rhovec.copy(), kwargs={'lw':0.5}) - except BaseException as BE: - print(T2, BE) - - tic = timeit.default_timer() - Tcvec = model.get_Tcvec() - rhocvec = 1/model.get_vcvec() - T0 = Tcvec[1] - rho0 = rhocvec*np.array([1,0]) - curveJSON = teqp.trace_critical_arclength_binary(model, T0, rho0, "") - toc = timeit.default_timer() - print(toc-tic, 'to trace critical locus') - - df = pandas.DataFrame(curveJSON) - df['z0 / mole frac.'] = df['rho0 / mol/m^3']/(df['rho0 / mol/m^3']+df['rho1 / mol/m^3']) - plt.plot(df['z0 / mole frac.'], df['p / Pa'], lw=2) - - # plt.ylim(0.00019e6, 0.026e6) + if plot_critical: + plot_critical_curve() + plot_isotherms() plt.gca().set(xlabel='$x_1$ / mole frac.', ylabel='$p$ / Pa') plt.tight_layout(pad=0.2) - plt.show() - -def trace_isopleth(names): - AS = CP.AbstractState('HEOS', '&'.join(names)) - AS.set_mole_fractions([0.25, 0.75]) - AS.build_phase_envelope("") - PE = AS.get_phase_envelope_data() - - model = teqp.build_multifluid_model(names, '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json') - i = 0 - rhovec0 = np.array([ - PE.rhomolar_liq[i]*PE.x[0][i], PE.rhomolar_liq[i]*PE.x[1][i], - PE.rhomolar_vap[i]*PE.y[0][i], PE.rhomolar_vap[i]*PE.y[1][i]]) - T = PE.T[i] - dT = 1e-1 - R = model.get_R(np.array([1.0])) - rhovec = rhovec0.copy() - - def rhovecprime(T, rhovec): - return drhovecdT_isopleth(model=model, T=T, rhovec=rhovec) - tic = timeit.default_timer() - Tmax = 430 - sol = solve_ivp(rhovecprime, [T, Tmax], y0=rhovec0.copy(), method='RK45',dense_output=True, t_eval = np.linspace(T,Tmax,1000)) - p = [teqp.get_pr(model, T, sol.y[0:2,j]) + R*T*sol.y[0:2,j].sum() for T,j in zip(sol.t,range(len(sol.t)))] - x = sol.y[0,:]/(sol.y[0,:]+sol.y[1,:]) - y = sol.y[2,:]/(sol.y[2,:]+sol.y[3,:]) - toc = timeit.default_timer() - plt.plot(sol.t, p) - print(toc-tic) - - # plt.plot(PE.T, PE.rhomolar_vap) - # plt.plot(PE.T, PE.rhomolar_liq) - plt.plot(PE.T, PE.p) - plt.show() + if ofname: + plt.savefig(ofname) + if show: + plt.show() if __name__ == '__main__': - # main(names) - # trace_isopleth(names) - - # names = ['Methane', 'n-Propane'] - # model = teqp.build_multifluid_model(names, '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json') - # puremodel = teqp.build_multifluid_model(['n-Propane'], '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json') - # prettyPX(model, puremodel, np.linspace(180, 365, 100)) - - names = ['n-Butane', 'HydrogenSulfide'] - model = teqp.build_multifluid_model(names, '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json',{'estimate':'linear'}) + names = ['Methane', 'n-Butane'] + model = teqp.build_multifluid_model(names, '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json')#,{'estimate':'Lorentz-Berthelot'}) puremodels = [teqp.build_multifluid_model([name], '../mycp', '../mycp/dev/mixtures/mixture_binary_pairs.json') for name in names] - prettyPX(model, puremodels, 0, np.linspace(230, 420, 100)) \ No newline at end of file + prettyPX(model, puremodels, names, 1, np.linspace(190, 423, 20), show=False, plot_critical=True, ofname='pxdiagram.pdf') \ No newline at end of file -- GitLab