From a455c9e5acbc6fce9edde3745b8e923d1bf81724 Mon Sep 17 00:00:00 2001 From: Sylvain CALINON <sylvain.calinon@idiap.ch> Date: Mon, 14 Oct 2024 10:38:22 +0200 Subject: [PATCH] curvature example added --- matlab/iLQR_curvature.m | 234 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 matlab/iLQR_curvature.m diff --git a/matlab/iLQR_curvature.m b/matlab/iLQR_curvature.m new file mode 100644 index 0000000..a30ecd3 --- /dev/null +++ b/matlab/iLQR_curvature.m @@ -0,0 +1,234 @@ +%% Viapoint task for a point-mass system with a cost on curvature +%% +%% Copyright (c) 2024 Idiap Research Institute <https://www.idiap.ch/> +%% Written by Sylvain Calinon <https://calinon.ch> +%% +%% This file is part of RCFS <https://robotics-codes-from-scratch.github.io/> +%% License: GPL-3.0-only + +function iLQR_curvature + +%% Parameters +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +param.dt = 1E-2; %Time step size +param.nbData = 100; %Number of datapoints +param.nbPoints = 3; %Number of viapoints +param.nbMinIter = 5; %Minimum number of iterations for iLQR +param.nbMaxIter = 20; %Maximum number of iterations for iLQR +param.nbVarPos = 2; %Dimension of position data +param.nbDeriv = 3; %Number of static and dynamic features (nbDeriv=2 for [x,dx] and u=ddx) +param.nbVarX = param.nbVarPos * param.nbDeriv; %State space dimension +param.q = 1E1; %Tracking weight +param.qc = 1E-6; %Curvature weight +param.r = 1E-9; %Control weight + +idp = [0:param.nbPoints-1]*param.nbVarX + [1:param.nbVarPos]'; +idv = [0:param.nbPoints-1]*param.nbVarX + [param.nbVarPos+1:2*param.nbVarPos]'; + +%Viapoints +param.Mu = zeros(param.nbVarX*param.nbPoints,1); +%param.Mu(idp(:)) = rand(param.nbVarPos*param.nbPoints,1); +param.Mu(idp(:)) = [[1; 1]; [0.5; 0.2]; [0.8; 2]]; + +%Control weight matrix (at trajectory level) +R = speye((param.nbData-1) * param.nbVarPos) * param.r; + +%Time occurrence of viapoints +tl = linspace(1, param.nbData, param.nbPoints+1); +tl = round(tl(2:end)); +idx = (tl - 1) * param.nbVarX + [1:param.nbVarX]'; +%idPos = (tl - 1) * param.nbVarX + [1:param.nbVarPos]'; +%idVel = (tl - 1) * param.nbVarX + [param.nbVarPos+1:2*param.nbVarPos]'; + +%Dynamical System settings (discrete version) +A1d = zeros(param.nbDeriv); +for i=0:param.nbDeriv-1 + A1d = A1d + diag(ones(param.nbDeriv-i,1),i) * param.dt^i * 1/factorial(i); %Discrete 1D +end +B1d = zeros(param.nbDeriv,1); +for i=1:param.nbDeriv + B1d(param.nbDeriv-i+1) = param.dt^i * 1/factorial(i); %Discrete 1D +end +A = repmat(kron(A1d, eye(param.nbVarPos)), [1 1 param.nbData-1]); %Discrete nD +B = repmat(kron(B1d, eye(param.nbVarPos)), [1 1 param.nbData-1]); %Discrete nD + +[Su0, Sx0] = transferMatrices(A, B); %Constant Su and Sx for the proposed system +Su = Su0(idx,:); + + +%% Constraining the position of two consecutive keypoints to be same and crossing at given angle -> forming a loop +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%param.Q = eye(param.nbPoints*param.nbVarX); + +%Only cares about position part of the state variable +param.Q = diag(kron(ones(param.nbPoints,1), [ones(param.nbVarPos,1); zeros(param.nbVarX-param.nbVarPos,1)])); + +param.Mu(idp(:,2)) = param.Mu(idp(:,1)); %Viapoints 1 and 2 form the loop + +a = pi/2; %desired crossing angle +V = [cos(a) -sin(a); sin(a) cos(a)]; %rotation matrix + +%Impose cost on crossing angle +param.Q(idv(:,1), idv(:,1)) = eye(param.nbVarPos)*1E0; +param.Q(idv(:,2), idv(:,2)) = eye(param.nbVarPos)*1E0; +param.Q(idv(:,1), idv(:,2)) = V; %-eye(nbVarPos)*1E0; +param.Q(idv(:,2), idv(:,1)) = V'; %-eye(nbVarPos)*1E0; + + +%% iLQR without curvature cost (for initialization) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +x0 = zeros(param.nbVarX,1); %Initial state +u = zeros(param.nbVarPos*(param.nbData-1), 1); %Initial commands + +for n=1:param.nbMaxIter + x = reshape(Su0 * u + Sx0 * x0, param.nbVarX, param.nbData); %System evolution + [f, J] = f_reach(x(:,tl), param); %Residuals and Jacobians (viapoints tracking objective) + du = (Su' * J' * param.Q * J * Su + R) \ (-Su' * J' * param.Q * f - u * param.r); %Update + + %Estimate step size with backtracking line search method + alpha = 1; + cost0 = costFct(f, 0, u, param); %Cost + while 1 + utmp = u + du * alpha; + xtmp = reshape(Su0 * utmp + Sx0 * x0, param.nbVarX, param.nbData); + ftmp = f_reach(xtmp(:,tl), param); %Residuals (viapoints tracking objective) + cost = costFct(ftmp, 0, utmp, param); %Cost + if cost < cost0 || alpha < 1E-3 + break; + end + alpha = alpha * 0.5; + end + u = u + du * alpha; + + r(n).x = x; %Log data + + if norm(du * alpha) < 5E-2 && n>param.nbMinIter + break; %Stop iLQR when solution is reached + end +end +disp(['iLQR converged in ' num2str(n) ' iterations.']); + +x1 = x; %Log data +fc = f_curvature(x, param); %Residuals and Jacobians (motion objective) +norm(fc(:))^2 + + +%% iLQR with curvature cost +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%x0 = zeros(param.nbVarX,1); %Initial state +%u = zeros(param.nbVarPos*(param.nbData-1), 1); %Initial commands + +for n=1:param.nbMaxIter + x = reshape(Su0 * u + Sx0 * x0, param.nbVarX, param.nbData); %System evolution + [f, J] = f_reach(x(:,tl), param); %Residuals and Jacobians (viapoints tracking objective) + [fc, Jc] = f_curvature(x, param); %Residuals and Jacobians (curvature objective) + du = (Su' * J' * param.Q * J * Su + Su0' * Jc' * Jc * Su0 * param.qc + R) \ ... + (-Su' * J' * param.Q * f - Su0' * Jc' * fc(:) * param.qc - u * param.r); %Update + + %Estimate step size with backtracking line search method + alpha = 1; + cost0 = costFct(f, fc, u, param); %Cost + while 1 + utmp = u + du * alpha; + xtmp = reshape(Su0 * utmp + Sx0 * x0, param.nbVarX, param.nbData); + ftmp = f_reach(xtmp(:,tl), param); %Residuals (viapoints tracking objective) + fctmp = f_curvature(xtmp, param); %Residuals (curvature objective) + cost = costFct(ftmp, fctmp, utmp, param); %Cost + if cost < cost0 || alpha < 1E-3 + break; + end + alpha = alpha * 0.5; + end + u = u + du * alpha; + + if norm(du * alpha) < 5E-2 && n>param.nbMinIter + break; %Stop iLQR when solution is reached + end +end +disp(['iLQR converged in ' num2str(n) ' iterations.']); +norm(fc(:))^2 + + +%% Plot state space +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +h = figure('position',[10,10,600,600],'color',[1,1,1]); hold on; axis off; +hl(1) = plot(x1(1,:), x1(2,:), '-','linewidth',4,'color',[.7 .7 .7]); +hl(2) = plot(x(1,:), x(2,:), '-','linewidth',4,'color',[0 0 0]); +%hl(3) = +plot(x(1,1), x(2,1), '.','markersize',30,'color',[0 0 0]); +%Display curvature information +%for t=1:param.nbData +% v = [x(4,t); -x(3,t)]; %Normal to curve +% r = 1 / (fc(t)+1E-12); +% +% if abs(r)>1E-2 && abs(r)<0.5 +% v = -r * v / norm(v); +% plot([x(1,t), x(1,t)+v(1)], [x(2,t), x(2,t)+v(2)], '-','color',[0 .6 0]); +% end +%end +plot(param.Mu(idp(1,:)), param.Mu(idp(2,:)), '.','markersize',30,'color',[.8 0 0]); +axis equal; +legend(hl,{'Without cost on curvature','With cost on curvature'}, 'fontsize',20,'location','southeast'); %,'Viapoints' +%print('-dpng','graphs/iLQR_curvature01.png'); + +waitfor(h); +close all; +end + +%%%%%%%%%%%%%%%%%%%%%% +function [Su, Sx] = transferMatrices(A, B) + [nbVarX, nbVarU, nbData] = size(B); + nbData = nbData+1; + Sx = kron(ones(nbData,1), speye(nbVarX)); + Su = sparse(zeros(nbVarX*(nbData-1), nbVarU*(nbData-1))); + for t=1:nbData-1 + id1 = (t-1)*nbVarX+1:t*nbVarX; + id2 = t*nbVarX+1:(t+1)*nbVarX; + id3 = (t-1)*nbVarU+1:t*nbVarU; + Sx(id2,:) = squeeze(A(:,:,t)) * Sx(id1,:); + Su(id2,:) = squeeze(A(:,:,t)) * Su(id1,:); + Su(id2,id3) = B(:,:,t); + end +end + +%%%%%%%%%%%%%%%%%%%%%% +%Cost function +function c = costFct(f, fc, u, param) +% c = norm(f(:))^2 * param.q + norm(fc(:))^2 * param.qc + norm(u)^2 * param.r; + c = f(:)' * param.Q * f(:) + norm(fc(:))^2 * param.qc + norm(u)^2 * param.r; +end + +%%%%%%%%%%%%%%%%%%%%%% +%Residuals f and Jacobians J for a viapoints reaching task with point mass system +function [f, J] = f_reach(x, param) + f = x(:) - param.Mu; %Residuals + J = eye(param.nbVarX * size(x,2)); %Jacobian +end + +%%%%%%%%%%%%%%%%%%%%%% +%Residuals f and Jacobians J for curvature minimization over path +function [f, J] = f_curvature(x, param) +% dx = x(:,2:end) - x(:,1:end-1); +% l = sum(dx.^2,1).^.5; %Segment lengths +% dx0 = dx ./ l; %Unit direction vectors +% theta = acos(dx0(:,2:end)' * dx0(:,1:end-1)); %Angles + + ddx = x(2*param.nbVarPos+1:3*param.nbVarPos,:); %Second derivative + dx = x(param.nbVarPos+1:2*param.nbVarPos,:); %First derivative + dxn = sum(dx.^2,1).^(3/2); + f = (dx(1,:) .* ddx(2,:) - dx(2,:) .* ddx(1,:)) ./ (dxn + 1E-8); %Curvature + + s11 = zeros(param.nbVarX,1); s11(3) = 1; %Selection vector + s12 = zeros(param.nbVarX,1); s12(4) = 1; %Selection vector + s21 = zeros(param.nbVarX,1); s21(5) = 1; %Selection vector + s22 = zeros(param.nbVarX,1); s22(6) = 1; %Selection vector + Sa = s11 * s22' - s12 * s21'; %Selection matrix for numerator + Sb = s11 * s11' + s12 * s12'; %Selection matrix for denominator + J = []; + for t=1:param.nbData + a = x(:,t)' * Sa * x(:,t); + b = x(:,t)' * Sb * x(:,t) + 1E-8; + Jtmp = 2 * b^(-3/2) * Sa * x(:,t) - 3 * a * b^(-5/2) * Sb * x(:,t); + J = blkdiag(J, Jtmp'); + end +end -- GitLab