Closed-loop economic MPC versus tracking MPC starting at x=(-8,8) with optimal steady state (8,4). Both controllers asymptotically stabilize the steady state. Dashed contours show cost functions for each controller.
# Compare economic vs. tracking mpc.# [makes] econvstracking.matimportnumpyasnpimportscipy.ioassioimportmatplotlib.pyplotaspltimportmpctoolsasmpcfromscipy.linalgimportsolve_discrete_lyapunovimportosNx=2Ny=NxNu=1hor=50N={}N['x']=NxN['y']=NyN['u']=NuN['t']=hor# Linear modelA=np.array([[0.5,1],[0,0.75]])B=np.array([[0],[1]])f=mpc.getCasadiFunc(lambdax,u:A@x+B@u,[Nx,Nu],['x','u'],'f')# Objective functionq=np.array([[-2],[2]])r=-10*np.eye(Nu)lecon=mpc.getCasadiFunc(lambdax,u:q.T@x+r@u,[Nx,Nu],['x','u'],'lecon')Q=np.array([[10,0],[0,10]])R=np.array([[1]])ltrack=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'],'ltrack')# Boundslb,ub={},{}lb['u']=-np.ones(Nu)ub['u']=np.ones(Nu)lb['x']=np.ones(Nx)*-10ub['x']=np.ones(Nx)*10# Target findertarget=mpc.sstarg(f=f,h=None,l=lecon,N=N,lb=lb,ub=ub,verbosity=0,inferargs=True)target.solve()xs=np.squeeze(target.var['x',0])us=np.squeeze(target.var['u',0])ls=target.obj# Set-up controllersinit={}init['xs']=xsinit['us']=us# Define equality constraint for terminal state xf# NOTE: keep some wiggle room, may encounter solver problems otherwisewiggle=1e-5lb['xf']=xs*(1-wiggle)ub['xf']=xs*(1+wiggle)econ=mpc.nmpc(f=f,l=lecon,N=N,lb=lb,ub=ub,verbosity=0,inferargs=True)track=mpc.nmpc(f=f,l=ltrack,N=N,par={'xsp':xs,'usp':us},lb=lb,ub=ub,verbosity=0,inferargs=True)# Simulate bothNsim=25x0=np.array([-8,8])controllers=[econ,track]names=['econ','track']data={}fori,controllerinenumerate(controllers):print(f'Simulating {names[i]} MPC\n')x=np.zeros((Nx,Nsim+1))x[:,0]=x0# Initial conditionu=np.zeros((Nu,Nsim))fortinrange(Nsim):controller.fixvar('x',0,x[:,t])controller.solve()u[:,t]=np.squeeze(controller.var['u',0])x[:,t+1]=np.squeeze(controller.var['x',1])controller.saveguess()data[names[i]]={}data[names[i]]['x']=xdata[names[i]]['u']=u# get data for objective function contoursx1=np.linspace(-10,15,251)x2=np.linspace(0,10,101)xx1,xx2=np.meshgrid(x1,x2)dx1=xx1-xs[0]dx2=xx2-xs[1]Vecon=q[0]*dx1+q[1]*dx2Vtrack=Q[0,0]*(dx1**2)+2*Q[1,0]*dx1*dx2+Q[1,1]*(dx2**2)data['contours']={}data['contours']['x1']=x1data['contours']['x2']=x2data['contours']['Vecon']=Vecondata['contours']['Vtrack']=Vtrackifnotos.getenv('OMIT_PLOTS')=='true':plt.figure()plt.contour(xx1,xx2,Vecon,levels=10)plt.contour(xx1,xx2,Vtrack,levels=10)colors=['blue','red']fori,nameinenumerate(names):plt.plot(data[name]['x'][0,:],data[name]['x'][1,:],'-o',color=colors[i],mfc='none')plt.xlabel(r'$x_1$')plt.ylabel(r'$x_2$',rotation=0)plt.show()# Find rotated cost functionmu=-np.linalg.solve((np.eye(Nx)-A).T,q)alpha=0.01Mu=solve_discrete_lyapunov(A.T,alpha*np.eye(Nx))lam=lambdax:mu.T@(x-xs)+(x-xs).T@Mu@(x-xs)lrot=lambdax,u:q.T@(x-xs)+r@(u-us)+lam(x)-lam(A@x+B@u)-alpha*(x-xs).T@(x-xs)x1_vals=np.linspace(lb['x'][0],ub['x'][0],21)x2_vals=np.linspace(lb['x'][1],ub['x'][1],21)u_vals=np.linspace(lb['u'],ub['u'],21)x1,x2,u=np.meshgrid(x1_vals,x2_vals,u_vals,indexing='ij')l=np.empty_like(x1)foriinrange(21):forjinrange(21):forkinrange(21):x=np.array([x1[i,j,k],x2[i,j,k]])l[i,j,k]=lrot(x,np.array([u[i,j,k]])).item()print(f"Minimum of dissipation inequality: {l.min():.6g}")# Simulate multiple economic MPC trajectoriesecon.saveguess(default=True)Nx0=16colors=plt.cm.jet(np.linspace(0,1,Nx0))theta=np.linspace(0,2*np.pi,Nx0+1)[1:]x0=np.vstack((8*np.cos(theta),8*np.sin(theta)))Nsim=21x=np.full((Nx,Nsim,Nx0),np.nan)u=np.full((Nu,Nsim,Nx0),np.nan)V=np.full((Nsim,Nx0),np.nan)ifnotos.getenv('OMIT_PLOTS')=='true':fig,axs=plt.subplots(2,2,figsize=(10,6))t=np.arange(Nsim)print('Simulating more economic MPC',end='',flush=True)forjinrange(Nx0):x[:,0,j]=x0[:,j]foriinrange(Nsim):econ.fixvar('x',0,x[:,i,j])econ.solve()ifecon.stats['status']!='Solve_Succeeded':print(f'Controller failed at time {i} with controller status {econ.stats["status"]}')breaku[:,i,j]=np.squeeze(econ.var['u',0])V[i,j]=econ.obj+lam(x[:,i,j]).item()-N['t']*lsifi<Nsim-1:x[:,i+1,j]=np.squeeze(econ.var['x',1])print('.',end='',flush=True)ifnotos.getenv('OMIT_PLOTS')=='true':axs[0,0].plot(t,x[0,:,j],color=colors[j])axs[0,0].set_ylabel(r'$x_1$',rotation=0)axs[0,1].plot(t,u[0,:,j],color=colors[j])axs[0,1].set_ylabel(r'$u$',rotation=0)axs[1,0].plot(t,x[1,:,j],color=colors[j])axs[1,0].set_ylabel(r'$x_2$',rotation=0)axs[1,0].set_xlabel('Time')axs[1,1].plot(t,V[:Nsim,j],color=colors[j])axs[1,1].set_ylabel(r'$V$',rotation=0)axs[1,1].set_xlabel('Time')ifnotos.getenv('OMIT_PLOTS')=='true':plt.tight_layout()plt.show()data['phase']={}data['phase']['x']=xdata['phase']['u']=udata['phase']['V']=V