# Converted from contour_pentane.m# Note: Uses fmincon which is not directly available in scipy.# We use scipy.optimize.minimize with constraints instead.importnumpyasnpfromscipy.optimizeimportfsolve,minimizefrommiscimportoctave_saveclassP:passp=P()p.deltag1=-3.72# kcal/molp.deltag2=-4.49# kcal/molp.T=400.0# Kp.P=2.5# atmp.R=1.987# cal/mol/Kp.K1=np.exp(-p.deltag1*1000/(p.R*p.T))p.K2=np.exp(-p.deltag2*1000/(p.R*p.T))defG(w,p):x,y=wz=0.5-x-ypp=0.5+zxlogx=0.0ifx==0elseabs(x)*np.log(abs(x/pp))ylogy=0.0ify==0elseabs(y)*np.log(abs(y/pp))zlogz=0.0ifz==0elseabs(z)*np.log(abs(z/pp))return-p.Gval-(x*np.log(p.K1)+y*np.log(p.K2))+pp*np.log(p.P)+ \
2*zlogz+xlogx+ylogydefGcontour(var,p,ext0,unk):w=ext0.copy()w[np.array(unk)-1]=varreturnG(w,p)definflate(z,zlast,dels,p,ext0,unk):returnnp.array([Gcontour(z,p,ext0,np.array(unk)),np.linalg.norm(z-zlast)-dels**2])p.Gval=0.0w0=np.array([0.2,0.2])# Minimize G subject to constraintsfromscipy.optimizeimportminimizecons=[{'type':'ineq','fun':lambdaw:w[0]},{'type':'ineq','fun':lambdaw:w[1]},{'type':'ineq','fun':lambdaw:0.5-w[0]-w[1]}]res=minimize(lambdaw:G(w,p),w0,method='SLSQP',constraints=cons,bounds=[(0,0.5),(0,0.5)])w=res.xprint(f'Optimal w = {w}')Glevels=[0,-1,-2,-2.5,-2.5,-2.53,-2.55,-2.559]nlev=len(Glevels)alpha_vec=[0.05,0.05,0.075,0.05,0.05,0.05,0.04,0.02]x0vec=np.array([[1e-3,1e-3,1e-3,0.133,0.133,0.133,0.133,0.133],[0.02,0.15,0.35,0.3,0.3,0.3,0.3,0.3]])delx=np.array([[0.01,0.01,0.01,0.01,-0.01,0.,0.,0.],[0.,0.,0.,0.,0.,0.,0.,0.]])sfinal=[9,9,9,9,9,9,9,5]ztables=[]foriinrange(nlev):p.Gval=Glevels[i]ext0=x0vec[:,i].copy()unk=[2]# 1-indexedx0=np.array([ext0[unk[0]-1]])try:x_sol=fsolve(lambdax:[Gcontour(x,p,ext0,unk)],x0,full_output=True)xsol=x_sol[0]ext=ext0.copy()ext[np.array(unk)-1]=xsolzlast=ext.copy()except:zlast=ext0.copy()ext=ext0.copy()dels=alpha_vec[i]svec=np.arange(dels,sfinal[i]+dels,dels)narc=len(svec)z0=ext+delx[:,i]unk2=[1,2]pts=[ext.copy()]forjinrange(narc):try:z_sol=fsolve(lambdaz:inflate(z,zlast,dels,p,ext0,unk2),z0)if(z_sol[0]>0andz_sol[1]>0and0.5-(z_sol[0]+z_sol[1])>0):pts.append(z_sol.copy())z0=2*z_sol-zlastzlast=z_sol.copy()else:breakexcept:breakiflen(pts)>0:ztables.append(np.array(pts))else:ztables.append(np.array([[np.nan,np.nan]]))feasbound=np.array([[0.,0.5],[0.5,0.]])# Save: each ztable as a named block, then feasboundpairs=[]fori,ztinenumerate(ztables):pairs.append((f'ztable_{i+1}',zt))pairs.append(('feasbound',feasbound))octave_save('contour_pentane.dat',*pairs)