diff --git a/python/LQR_infHor.py b/python/LQR_infHor.py new file mode 100644 index 0000000000000000000000000000000000000000..52781eeffaf34c2cabf2f410150f7a3a17fc8b92 --- /dev/null +++ b/python/LQR_infHor.py @@ -0,0 +1,114 @@ +''' + Point-mass LQR with infinite horizon + + Copyright (c) 2021 Idiap Research Institute, http://www.idiap.ch/ + Written by Jeremy Maceiras <jeremy.maceiras@idiap.ch>, + Sylvain Calinon <https://calinon.ch> + + This file is part of RCFS. + + RCFS is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3 as + published by the Free Software Foundation. + + RCFS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with RCFS. If not, see <http://www.gnu.org/licenses/>. +''' + +import numpy as np +import copy +from scipy.linalg import solve_discrete_are as solve_algebraic_riccati_discrete +from scipy.stats import multivariate_normal +from math import factorial +import matplotlib.pyplot as plt + +# Plot a 2D Gaussians +def plot_gaussian(mu, sigma): + w, h = 100, 100 + + std = [np.sqrt(sigma[0, 0]), np.sqrt(sigma[1, 1])] + x = np.linspace(mu[0] - 3 * std[0], mu[0] + 3 * std[0], w) + y = np.linspace(mu[1] - 3 * std[1], mu[1] + 3 * std[1], h) + + x, y = np.meshgrid(x, y) + + x_ = x.flatten() + y_ = y.flatten() + xy = np.vstack((x_, y_)).T + + normal_rv = multivariate_normal(mu, sigma) + z = normal_rv.pdf(xy) + z = z.reshape(w, h, order='F') + + plt.contourf(x, y, z.T,levels=1,colors=["white","red"],alpha=.5) + +# Parameters +# =============================== + +param = lambda: None # Lazy way to define an empty class in python +param.nbData = 200 # Number of datapoints +param.nbRepros = 4 #Number of reproductions +param.nbVarPos = 2 #Dimension of position data (here: x1,x2) +param.nbDeriv = 2 #Number of static & dynamic features (D=2 for [x,dx]) +param.nbVar = param.nbVarPos * param.nbDeriv #Dimension of state vector in the tangent space +param.dt = 1e-2 #Time step duration +param.rfactor = 4e-2 #Control cost in LQR + +# Control cost matrix +R = np.identity(param.nbVarPos) * param.rfactor + +# Target and desired covariance +param.Mu = np.hstack(( np.random.uniform(size=param.nbVarPos) , np.zeros(param.nbVarPos) )) +Ar,_ = np.linalg.qr(np.random.uniform(size=(param.nbVarPos,param.nbVarPos))) +xCov = Ar @ np.diag(np.random.uniform(size=param.nbVarPos)) @ Ar.T * 1e-1 + +# Discrete dynamical System settings +# =================================== + +A1d = np.zeros((param.nbDeriv,param.nbDeriv)) +B1d = np.zeros((param.nbDeriv,1)) + +for i in range(param.nbDeriv): + A1d += np.diag( np.ones(param.nbDeriv-i) ,i ) * param.dt**i * 1/factorial(i) + B1d[param.nbDeriv-i-1] = param.dt**(i+1) * 1/factorial(i+1) + +A = np.kron(A1d,np.identity(param.nbVarPos)) +B = np.kron(B1d,np.identity(param.nbVarPos)) + +# Discrete LQR with infinite horizon +# =================================== + +Q = np.zeros((param.nbVar,param.nbVar)) +Q[:param.nbVarPos,:param.nbVarPos] = np.linalg.inv(xCov) # Precision matrix +P = solve_algebraic_riccati_discrete(A,B,Q,R) +L = np.linalg.inv(B.T @ P @ B + R) @ B.T @ P @ A # Feedback gain (discrete version) + +reproducitons = [] + +for i in range(param.nbRepros): + xt = np.zeros(param.nbVar) + xt[:param.nbVarPos] = 1+np.random.uniform(param.nbVarPos)/2 + xs = [copy.deepcopy(xt)] + for t in range(param.nbData): + u = L @ (param.Mu - xt) + xt = A @ xt + B @ u + xs += [copy.deepcopy(xt)] + reproducitons += [ np.asarray(xs) ] + +# Plots +# ====== + +plt.figure() +plt.title("Position") + +for r in reproducitons: + plt.plot(r[:,0],r[:,1],c="black") +plot_gaussian(param.Mu[:param.nbVarPos],xCov) +plt.axis("off") +plt.gca().set_aspect('equal', adjustable='box') +plt.show()