diff --git a/matlab/iLQR_distMaintenance.m b/matlab/iLQR_distMaintenance.m index f8cdb1df044a18998cfbc98595d29e120d94bf33..1db55be76006b282ea7554eb057e59276d666a89 100644 --- a/matlab/iLQR_distMaintenance.m +++ b/matlab/iLQR_distMaintenance.m @@ -20,11 +20,11 @@ param.Mu = [1.0; 0.3]; %Object location %param.dist = .4; %Distance to maintain %param.Sigma = eye(param.nbVarX) * param.dist^2; %Covariance matrix -vtmp = [1; 1]; %Main axis of covariance matrix -param.Sigma = vtmp * vtmp' + eye(2) * 1E-1; %Covariance matrix +vtmp = [.8; .8]; %Main axis of covariance matrix +param.Sigma = vtmp * vtmp' + eye(2) * 2E-2; %Covariance matrix param.q = 1E0; %Distance maintenance weight term -param.r = 1E-3; %Control weight term +param.r = 1E-6; %Control weight term R = speye((param.nbData-1) * param.nbVarU) * param.r; %Control weight matrix (at trajectory level) @@ -73,8 +73,8 @@ al = linspace(-pi, pi, 50); %msh = param.dist * [cos(al); sin(al)] + repmat(param.Mu(1:2), 1, 50); [V,D] = eig(param.Sigma); msh = V * D.^.5 * [cos(al); sin(al)] + repmat(param.Mu(1:2), 1, 50); - patch(msh(1,:), msh(2,:), [1 .8 .8],'linewidth',2,'edgecolor',[.8 .4 .4]); + plot(param.Mu(1), param.Mu(2), '.','markersize',25,'color',[.8 0 0]); plot(x(1,:), x(2,:), '-','linewidth',2,'color',[0 0 0]); plot(x(1,1), x(2,1), '.','markersize',25,'color',[0 0 0]); diff --git a/python/iLQR_manipulator_boundary.py b/python/iLQR_manipulator_boundary.py new file mode 100644 index 0000000000000000000000000000000000000000..d8f58e545efe0bbb117b4167c6055515174fae0a --- /dev/null +++ b/python/iLQR_manipulator_boundary.py @@ -0,0 +1,244 @@ +""" +iLQR applied to a planar manipulator for a viapoints task with bounding on x + +Copyright (c) 2021 Idiap Research Institute, http://www.idiap.ch/ +Written by Ekansh Sharma <ekanshh.sharma@gmail.com> and +Sylvain Calinon <https://calinon.ch> + +This file is part of RCFS <https://robotics-codes-from-scratch.github.io/> +License: GPL-3.0-only +""" + +import matplotlib.pyplot as plt +import numpy as np + + +def fkin(x, param): + """Forward kinematics for end-effector (in robot coordinate system)""" + T = np.tril(np.ones(len(x))) + f = np.vstack([param.l @ np.cos(T @ x), param.l @ np.sin(T @ x)]) + return f + + +def jacob0(x, param): + """Jacobian with analytical computation (for single time step)""" + T = np.tril(np.ones(len(x))) + J = np.array( + [ + -np.sin(T @ x).T @ np.diag((param.l)) @ T, + np.cos(T @ x).T @ np.diag(param.l) @ T, + ] + ) + return J + + +def f_reach(x, param): + """Cost and gradient for a viapoints reaching task (in object coordinate system)""" + f = fkin(x, param) - param.Mu + J = np.array([]) + for t in range(x.shape[1]): + Jtmp = jacob0(x[:, t], param) + if np.any(J): + J = np.block( + [ + [J, np.zeros((J.shape[0], Jtmp.shape[1]))], + [np.zeros((Jtmp.shape[0], J.shape[1])), Jtmp], + ] + ) + else: + J = Jtmp + return f, J + + +def x_bound(x, param): + """Cost and gradient for a viapoints reaching task (in object coordinate system)""" + xlim = np.tile(param.xlim.T, (param.nbData)) + idv = np.abs(x) > xlim + Jv = np.eye(np.sum(idv)) + v = x[idv] - np.sign(x[idv]) * xlim[idv].flatten() + return v, Jv, idv + + +def fkin0(x, params): + """Compute forward kinematics for all joints (in robot coordinate system)""" + L = np.tril(np.ones(len(x))) + f = np.vstack( + [ + L @ np.diag(params.l) @ np.cos(L @ x), + L @ np.diag(params.l) @ np.sin(L @ x), + ] + ) + f = np.hstack((np.zeros((2, 1)), f)) + return f + + +def plot_fkin0_for_via_points(x0, x, tl, param): + """Plot robot forward kinematics for initial configuration, via-points, and path.""" + _, ax = plt.subplots(figsize=(12, 8)) + + fkin00 = fkin0(x0, param) + ax.plot( + fkin00[0, :], + fkin00[1, :], + color=(0.9, 0.9, 0.9), + linewidth=5, + label="Initial Configuration", + ) + ax.scatter(fkin00[0, 1:], fkin00[1, 1:], color="skyblue", marker="o", s=100, zorder=2) + ax.scatter(fkin00[0, 0], fkin00[1, 0], color="black", marker="s", s=100, zorder=2) + + for i, idx in enumerate(tl): + fkin0_i = fkin0(x[:, idx], param) + color_factor = len(param.Mu) / (len(param.Mu) - i) + ax.plot( + fkin0_i[0, :], + fkin0_i[1, :], + linewidth=5, + color=(0.8 / color_factor, 0.8 / color_factor, 0.8 / color_factor), + label=f"Via-point {i + 1} Configuration", + ) + ax.scatter(fkin0_i[0, 1:], fkin0_i[1, 1:], color="skyblue", marker="o", s=100, zorder=2) + ax.scatter(fkin0_i[0, 0], fkin0_i[1, 0], color="black", marker="s", s=100, zorder=2) + + ftmp0 = fkin(x, param) + ax.plot( + ftmp0[0, :], + ftmp0[1, :], + "--", + linewidth=1, + color="black", + label="End-effector trajectory", + ) + ax.plot( + param.Mu[0, 0], + param.Mu[1, 0], + ".", + markersize=10, + color="darkred", + label="Via-point 1 Marker", + ) + ax.plot( + param.Mu[0, 1], + param.Mu[1, 1], + ".", + markersize=10, + color="purple", + label="Via-point 2 Marker", + ) + + ax.axis("off") + ax.set_aspect("equal", adjustable="box") + ax.legend(loc="upper left", bbox_to_anchor=(1.05, 1), borderaxespad=0.0) + plt.show() + + +def plot_x(x0, x, param): + """Plot the change of x ( i.e. [x1, x2, x3]) over time""" + _, axs = plt.subplots(3, 1, figsize=(10, 8)) + + for i in range(3): + axs[i].plot(x[i, :], color="black", label=f"x{i}") + axs[i].axhline(y=x0[i], color="blue", linestyle="--", label=f"x{i}_0") + axs[i].axhline(y=param.xlim[i], color="red", linestyle="--", label=f"x{i}_lim") + axs[i].set_title(f"x{i} vs t") + axs[i].set_xlabel("t") + axs[i].set_ylabel(f"x{i}") + axs[i].legend() + + plt.tight_layout() + plt.show() + + +## Parameters +# =============================== + +param = lambda: None +param.dt = 1e-2 # Time step size +param.nbData = 100 # Number of datapoints +param.nbIter = 100 # Maximum number of iterations for iLQR +param.nbPoints = 2 # Number of viapoints +param.nbVarX = 3 # State space dimension (x1,x2,x3) +param.nbVarU = 3 # Control space dimension (dx1,dx2,dx3) +param.nbVarF = 2 # Task space dimension (f1,f2) +param.l = np.array([3, 2, 1]) # Robot links lengths +param.xlim = np.array([np.pi * 2, np.pi * 2, np.pi * 0.05]) # joint angles range +param.q = 1e0 # Tracking weighting term +param.rv = 1e3 # Bounding weighting term +param.r = 1e-6 # Control weighting term +param.Mu = np.array([[2, 3], [1, 2]]) # Viapoints + +# Main program +# =============================== + +# Precision matrix +Q = np.eye(param.nbVarF * param.nbPoints) + +# Control weight matrix +R = np.eye((param.nbData - 1) * param.nbVarU) * param.r + +# Time occurrence of viapoints +tl = np.rint(np.linspace(0, param.nbData - 1, param.nbPoints + 1)[1:]).astype(np.int64) +idx = np.array([i + np.arange(0, param.nbVarX, 1) for i in (tl * param.nbVarX)]) +idx = idx.flatten() + +# Transfer matrices (for linear system as single integrator) +Su0 = np.vstack( + [ + np.zeros([param.nbVarX, param.nbVarX * (param.nbData - 1)]), + np.kron(np.tril(np.ones(param.nbData - 1)), np.eye(param.nbVarX) * param.dt), + ] +) +Sx0 = np.kron(np.ones(param.nbData), np.eye(param.nbVarX)).T +Su = Su0[idx, :] # We remove the lines that are out of interest + +# iLQR +# =============================== + +u = np.zeros(param.nbVarU * (param.nbData - 1)) # Initial control command +x0 = np.array([3 * np.pi / 4, -np.pi / 2, np.pi / 4]) # Initial state + +for i in range(param.nbIter): + x = Sx0 @ x0 + Su0 @ u # System evolution + x = x.reshape([param.nbVarX, param.nbData], order="F") + + f, J = f_reach(x[:, tl], param) # Residuals and Jacobians for reaching task + x = x.flatten(order="F") + v, Jv, idv = x_bound(x, param) # Residuals and Jacobians for boundary on x + + Sv = Su0[idv, :] + + du = np.linalg.inv(Su.T @ J.T @ Q @ J @ Su + Sv.T @ Jv.T @ Jv @ Sv * param.rv + R) @ ( + -Su.T @ J.T @ Q @ f.flatten("F") - Sv.T @ Jv.T @ v * param.rv - u * param.r + ) # Gauss-Newton update + + # Estimate step size with backtracking line search method + alpha = 1 + cost0 = f.flatten("F").T @ Q @ f.flatten("F") + np.linalg.norm(v) ** 2 * param.rv + np.linalg.norm(u) ** 2 * param.r + while True: + utmp = u + du * alpha + xtmp = Su0 @ utmp + Sx0 @ x0 # System evolution + xtmp = xtmp.reshape([param.nbVarX, param.nbData], order="F") + ftmp, _ = f_reach(xtmp[:, tl], param) # Residuals + xtmp = xtmp.flatten(order="F") + vtmp, _, _ = x_bound(xtmp, param) + cost = ( + ftmp.flatten("F").T @ Q @ ftmp.flatten("F") + + np.linalg.norm(vtmp) ** 2 * param.rv + + np.linalg.norm(utmp) ** 2 * param.r + ) + if cost < cost0 or alpha < 1e-4: + print("Iteration {}, cost: {}, alpha: {}".format(i, cost, alpha)) + break + alpha /= 2 + + u = u + du * alpha + + if np.linalg.norm(du * alpha) < 1e-2: + break # Stop iLQR iterations when solution is reached + +print(f"iLQR converged in {i} iterations") + +# Visualize +x = x.reshape([param.nbVarX, param.nbData], order="F") +plot_fkin0_for_via_points(x0, x, tl, param) +plot_x(x0, x, param)