Feasible sets \mathcal {X}_N for two values of \dot {Q}_min. Note that for \dot {Q}_min=9 (right-hand side), \mathcal {X}_N for N \leq 4 are disconnected sets.
# 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
# [depends] functions/getcooler.py# [makes] coolerXn.mat# Calculation of X_n sets for a linear system with discrete actuators.# Note that for computational purposes, we use a polyhedral approximation of# the true elliptical X_n.importsysimportosimportnumpyasnpsys.path.append('functions/')fromscipyimportlinalgfromscipyimportioimportmatplotlib.pyplotaspltfromgetcoolerimportgetcoolerfromscipy.spatialimportConvexHullimportpicklesys=getcooler()Qmax=sys['Qmax']Qmin=sys['Qmin']ns=sys['ns']A=sys['A']B=sys['B'][:,0].reshape(-1,1)# Second input doesn't affect dynamicsF=sys['F']P=sys['Xf']['P']xss=sys['xss']rho=sys['Xf']['rho']defellipseterm(A,P,xss,rho,n):""" Returns a polyhedral tangent approximation of an ellipsoidal terminal set. Also makes a plot of the evolution of Xf under A. Xf is a 2 by n matrix of extreme points. A and b are the polyhedral representation of Xf. P is the terminal penalty weight. """ifnot(isinstance(n,int)andn>0):raiseValueError("n must be positive integer")eigvals,eigvecs=linalg.eigh(P/rho)a=1/np.sqrt(eigvals[0])b=1/np.sqrt(eigvals[1])defcalc(t):returnnp.vstack((eigvecs[0,0]*a*np.cos(t)+eigvecs[0,1]*b*np.sin(t),eigvecs[1,0]*a*np.cos(t)+eigvecs[1,1]*b*np.sin(t)))T=np.linspace(0,2*np.pi,n+1)t=np.linspace(0,2*np.pi,10*n+1)E=calc(T)AE=A@Ee=calc(t)Ae=A@eifnotos.getenv('OMIT_PLOTS')=='true':plt.figure()plt.plot(E[0,:]+xss[0],E[1,:]+xss[1],'-ob',label='X_f')plt.plot(e[0,:]+xss[0],e[1,:]+xss[1],'--b')plt.plot(AE[0,:]+xss[0],AE[1,:]+xss[1],'-or',label='f(X_f, \\kappa_f(X_f))')plt.plot(Ae[0,:]+xss[0],Ae[1,:]+xss[1],'--r')plt.xlabel('T_1')plt.ylabel('T_2')plt.title('Terminal Region X_f = {x | x\'Px <= '+str(rho)+'}')plt.legend(loc='upper right')returnxss.reshape(-1,1)+E[:,:-1]Xf=ellipseterm(A,P,xss,rho,32)defaddpoints(p1,p2):"""Adds two arrays of points (2D with rows x and y)."""p=p1.reshape(p1.shape[0],p1.shape[1],1)+p2.reshape(p2.shape[0],1,p2.shape[1])psize=p.shapereturnp.reshape(psize[0],psize[1]*psize[2])defminkowski(p1,p2=None):""" Returns minkowski sum of extreme point polytopes p and q. Only works for two dimensions. """ifp2isNone:p2=np.zeros((2,1))p=addpoints(p1,p2)ch=ConvexHull(p.T)returnp[:,ch.vertices]defcalcXn(A,B,f,N,Xf,U,x0=None):""" Calculate X_n sets for the system x^+ = Ax + Bu + f Xf should be an extreme point representation of Xf, and U should be a list of extreme-point polytopes for U. """Ainv=linalg.inv(A)BU=[B@u+fforuinU]Xn=[None]*(N+1)Xn[0]=[Xf]print(f'*** Calculating Xk (max k = {N}) ***')forninrange(1,N+1):Xn[n]=[]forxninXn[n-1]:forbuinBU:Xn[n].append(Ainv@minkowski(xn,-bu))print(f' k = {n}: {len(Xn[n])} regions')returnXn# Calculate feasible setsUcontinuous=[np.array([[0,max(ns)*Qmax]])]Udiscrete=[n*np.array([[Qmin,Qmax]])forninns]N=7Xc=calcXn(A,B,F,N,Xf,Ucontinuous)Xd=calcXn(A,B,F,N,Xf,Udiscrete)defplotXn(Xn,lims=None):"""Plots a set of regions on the current axes."""ifos.getenv('OMIT_PLOTS')=='true':returniflimsisnotNone:plt.axis(lims)colors=plt.cm.viridis(np.linspace(0,1,len(Xn)))[::-1]forninrange(len(Xn)-1,-1,-1):xn=Xn[n]foriinrange(len(xn)):plt.fill(xn[i][0,:],xn[i][1,:],color=colors[n],edgecolor='none',zorder=-n)plt.xlabel('T_1')plt.ylabel('T_2')lims=np.hstack(((sys['T1ss']+25*np.array([-1,1])),(sys['T2ss']+15*np.array([-1,1]))))ifnotos.getenv('OMIT_PLOTS')=='true':print('Plotting. May take some time.')plt.figure()plt.subplot(121)plotXn(Xc,lims)plt.title('Q_{min} = 0, Q_{max} = 10')plt.subplot(122)plotXn(Xd,lims)plt.title('Q_{min} = 9, Q_{max} = 10')# Convert each 'listed' array to just an arraydefto_cellarray(pylist):"""Convert a Python list of arrays/lists into a MATLAB cell-array-like object array."""cell=np.empty((len(pylist),),dtype=object)fori,eleminenumerate(pylist):ifisinstance(elem,(list,tuple)):cell[i]=to_cellarray(elem)# recursive wrapelse:cell[i]=np.asarray(elem)obj=np.empty(len(cell),dtype=object)fori,arrinenumerate(cell):obj[i]=arrreturncellXdf=to_cellarray(Xd)Xcf=to_cellarray(Xc)data={'continuous':Xcf,'discrete':Xdf,'lims':lims}io.savemat('coolerXn.mat',data,5)