# sys = getcooler()## Returns a struct of parameters for the cooler system.importnumpyasnpfromscipy.linalgimportexpm,solve_discrete_lyapunovdefgetcooler():""" sys = getcooler() Returns a struct of parameters for the cooler system. """# Input validationiflen(locals())>0:raiseValueError('Function takes no input arguments')# Define systemNx=2Nu=2alpha=2beta=1rho1=1rho2=1ns=np.array([0,1,2])Qmin=9Qmax=10nss=1Qss=nss*Qmaxuss=np.array([[Qss],[nss]])T0=40T1ss=35T2ss=25xss=np.array([[T1ss],[T2ss]])# Continuous-time systema=np.array([[-alpha-rho1,rho1],[rho2,-rho2]])b=np.array([[0,0],[-beta,0]])f=np.array([[alpha*T0],[0]])# DiscretizeDelta=0.25A=expm(Delta*a)B=np.linalg.solve(a,(A-np.eye(2))@b)F=np.linalg.solve(a,(A-np.eye(2))@f)# Choose penaltyQ=np.eye(Nx)R=np.eye(Nu)# Pick terminal regionXf={'rho':1,'P':solve_discrete_lyapunov(A.T,Q)}# Level set for Xf# Save everything to a dictsys={}local_vars=locals()forvarinlist(local_vars.keys()):ifvar!='sys':sys[var]=local_vars[var]returnsys
# Closed-loop optimization of cooler system.# [makes] coolercl.mat# [depends] functions/getcooler.py## [rule] copymatimportsysimportossys.path.append('functions/')importnumpyasnpimportscipy.ioassioimportmatplotlib.pyplotaspltimportmpctoolsasmpcfromgetcoolerimportgetcooler# get systemsys=getcooler()Nx=sys['Nx']Nu=sys['Nu']xss=sys['xss']uss=sys['uss']F=sys['F'].flatten()# avoid (2,) + (2,1) broadcast to (2,2) when mpctools# wraps x, u as 1-D numpy arrays of CasADi scalarsf=mpc.getCasadiFunc(lambdax,u:sys['A']@x+sys['B']@u+F,[Nx,Nu],['x','u'],'f')# Choose cost functionQ=sys['Q']R=0.001*sys['R']P=sys['Xf']['P']l=mpc.getCasadiFunc(lambdax,u,x_sp,u_sp:(x-x_sp).T@Q@(x-x_sp)+(u-u_sp).T@R@(u-u_sp),[Nx,Nu,Nx,Nu],['x','u','x_sp','u_sp'],'l')Vf=mpc.getCasadiFunc(lambdax,x_sp:(x-x_sp).T@P@(x-x_sp),[Nx,Nx],['x','x_sp'],'Vf')# Define input constraints and terminal constraint.Qmin=sys['Qmin']Qmax=sys['Qmax']e=mpc.getCasadiFunc(lambdau:[u[0]-u[1]*Qmax,u[1]*Qmin-u[0]],[Nu],['u'],'e')udiscrete=np.array([False,True])rho=sys['Xf']['rho']# Level set for terminal regionef=mpc.getCasadiFunc(lambdax,x_sp:(x-x_sp).T@P@(x-x_sp)-rho,[Nx,Nx],['x','x_sp'],'ef')# Build controllerNt=8N={}N['x']=NxN['u']=NuN['t']=NtN['e']=2nmax=max(sys['ns'])lb,ub={},{}lb['u']=np.zeros(Nu)ub['u']=np.array([nmax*Qmax,nmax])init={}init['x_sp']=xssinit['u_sp']=uss# mpc regulatorcontroller=mpc.nmpc(f=f,l=l,Vf=Vf,N=N,e=e,ef=ef,lb=lb,ub=ub,par={'xsp':init['x_sp'],'usp':init['u_sp']},verbosity=0,solver='bonmin',udiscrete=udiscrete,inferargs=True)# Bonmin's DiveMIP heuristic asserts in some CasADi/Bonmin binary builds on# small problems like this one; disable it so solve() doesn't abort.controller.initialize(solveroptions={'heuristic_dive_MIP_fractional':'no','heuristic_dive_MIP_vectorLength':'no','heuristic_dive_fractional':'no','heuristic_dive_vectorLength':'no'})# Run closed-loop simulation for various initial conditions.Nx0=16theta=np.linspace(0,2*np.pi,Nx0+1)[1:]x0=xss+np.vstack([25*np.cos(theta),15*np.sin(theta)])Nsim=16x=np.full((Nx,Nsim+1,Nx0),np.nan)u=np.full((Nu,Nsim,Nx0),np.nan)forjinrange(Nx0):x[:,0,j]=x0[:,j]foriinrange(Nsim):controller.fixvar('x',0,x[:,i,j])controller.solve()ifcontroller.stats['status']!='SUCCESS':print(f'Solver failed at time {i} with status {controller.stats["status"]}')breaku[:,i,j]=np.squeeze(controller.var['u',0])x[:,i+1,j]=np.squeeze(controller.var['x',1])lims=[sys['T1ss']+25*np.array([-1,1]),sys['T2ss']+15*np.array([-1,1])]colors=['r','c','b']labels=['n = 0','n = 1','n = 2']ifnotos.getenv('OMIT_PLOTS')=='true':plt.figure()plt.axis([lims[0][0],lims[0][1],lims[1][0],lims[1][1]])forjinrange(Nx0):foriinrange(Nsim):plt.plot(x[0,i:i+2,j],x[1,i:i+2,j],color=colors[int(u[1,i,j])])forkinrange(3):plt.plot(np.nan,np.nan,'-'+colors[k],label=labels[k])plt.legend()plt.tight_layout()plt.show()