diff --git a/matlab/iLQR_manipulator_boundary.m b/matlab/iLQR_manipulator_boundary.m
new file mode 100644
index 0000000000000000000000000000000000000000..1b449e66933bf0cb281c6e2ec352a01d93945ea0
--- /dev/null
+++ b/matlab/iLQR_manipulator_boundary.m
@@ -0,0 +1,214 @@
+%% iLQR applied to a planar manipulator for a viapoints task with bounding box on x (or u)
+%% 
+%% Copyright (c) 2023 Idiap Research Institute <https://www.idiap.ch/>
+%% Written by Teguh Lembono <teguh.lembono@idiap.ch> and 
+%% 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_manipulator_boundary
+
+%% Parameters
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+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.nbDOFs = 3; % Number of articulated links
+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 = [3, 2, 1]; %Robot links lengths
+param.sz = [.2, .3]; %Size of objects
+param.ulim = [15, 5, 15]; %Control commands range
+param.xlim = [pi*2, pi*2, pi*.05]; %joint angles range
+param.q = 1E0; %Tracking weighting term
+param.rv = 1E3; %Bounding weighting term
+param.r = 1E-4; %Control weighting term
+param.Mu = [[2; 1], [3; 2]]; %Viapoints
+
+Q = speye(param.nbVarF * param.nbPoints) * param.q; %Precision matrix
+R = speye(param.nbVarU * (param.nbData-1)) * param.r; %Control weight matrix (at trajectory level)
+
+%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]';
+
+
+%% Iterative LQR (iLQR)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+u = ones(param.nbVarU*(param.nbData-1), 1) * 0; %Initial control commands
+x0 = [3*pi/4; -pi/2; pi/4]; %Initial robot pose 
+
+%Transfer matrices (for linear system as single integrator)
+Su0 = [zeros(param.nbVarX, param.nbVarX*(param.nbData-1)); kron(tril(ones(param.nbData-1)), eye(param.nbVarX)*param.dt)];
+Sx0 = kron(ones(param.nbData,1), eye(param.nbVarX));
+Su = Su0(idx,:);
+
+X0 = reshape(Su0 * u + Sx0 * x0, param.nbVarX, param.nbData);
+for n=1:param.nbIter
+	x = reshape(Su0 * u + Sx0 * x0, param.nbVarX, param.nbData); %System evolution
+	[f, J] = f_reach(x(:,tl), param);
+%	[v, Jv] = u_bound(u, model);
+	[v, Jv, idv] = x_bound(x(:), param);
+	Sv = Su0(idv,:);
+	
+	du = (Su' * J' * Q * J * Su + Sv' * Jv' * Jv * Sv * param.rv + R) \ (-Su' * J' * Q * f(:) - Sv' * Jv' * v * param.rv - u * param.r); %Gradient
+%	du = (Su' * J' * Q * J * Su + Sv' * Sv * param.rv + R) \ (-Su' * J' * Q * f(:) - Sv' * v * param.rv - u * param.r); %Gradient
+
+%	du = (Su' * J' * Q * J * Su + Jv' * Jv * param.rv + R) \ (-Su' * J' * Q * f(:) - Jv' * v * param.rv - u * param.r); %Gradient
+%	du = (Jv' * Su' * J' * Q * J * Su * Jv + Jv' * Jv * param.rv + R) \ (-Jv' * Su' * J' * Q * f(:) - Jv' * v * param.rv - u * param.r); %Gradient
+	
+	%Estimate step size with backtracking line search method
+	alpha = 1;
+	cost0 = f(:)' * Q * f(:) + norm(v)^2 * param.rv + norm(u)^2 * param.r; 
+	while 1
+		utmp = u + du * alpha;
+		xtmp = reshape(Su0 * utmp + Sx0 * x0, param.nbVarX, param.nbData);
+		ftmp = f_reach(xtmp(:,tl), param);
+		%vtmp = u_bound(utmp, param);
+		vtmp = x_bound(xtmp(:), param);
+		cost = ftmp(:)' * Q * ftmp(:) + norm(vtmp)^2 * param.rv + norm(utmp)^2 * param.r; 
+		if cost < cost0 || alpha < 1E-3
+			break;
+		end
+		alpha = alpha * 0.5;
+	end
+	u = u + du * alpha;
+	
+	if norm(du * alpha) < 1E-2
+		break; %Stop iLQR when solution is reached
+	end
+end
+disp(['iLQR converged in ' num2str(n) ' iterations.']);
+
+
+%% Plot state space
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+figure('position',[10,10,800,800],'color',[1,1,1]); hold on; axis off;
+
+ftmp = fkin0(x(:, 1), param);
+plot(ftmp(1,:), ftmp(2,:), '-','linewidth',4,'color',[.8 .8 .8]);
+
+ftmp = fkin0(x(:,tl(1)), param);
+plot(ftmp(1,:), ftmp(2,:), '-','linewidth',4,'color',[.6 .6 .6]);
+
+ftmp = fkin0(x(:,tl(2)), param);
+plot(ftmp(1,:), ftmp(2,:), '-','linewidth',4,'color',[.4 .4 .4]);
+
+colMat = lines(param.nbPoints);
+for t=1:param.nbPoints
+	plot(param.Mu(1,t), param.Mu(2,t), '.','markersize',40,'color',colMat(t,:));
+end
+
+ftmp = fkin(x, param); 
+plot(ftmp(1,:), ftmp(2,:), '-','linewidth',2,'color',[0 0 0]);
+plot(ftmp(1,1), ftmp(2,1), '.','markersize',40,'color',[0 0 0]);
+plot(ftmp(1,tl), ftmp(2,tl), '.','markersize',30,'color',[0 0 0]);
+axis equal;
+
+%% Timeline plot
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+h = figure('position',[950 10 800 800],'color',[1 1 1]);
+%Plot x
+for j=1:param.nbVarX
+	subplot(param.nbVarX, 1, j); grid on; hold on; box on; 
+	plot([1, param.nbData], [param.xlim(j), param.xlim(j)], 'r-');
+	plot([1, param.nbData], -[param.xlim(j), param.xlim(j)], 'r-');
+	plot(X0(j,:), '-','linewidth',3,'color',[.7 .7 .7]);
+    plot(x(j,:), '-','linewidth',3,'color',[0 0 0]);
+	ylabel(['$x_' num2str(j) '$'], 'interpreter','latex','fontsize',26);
+end
+xlabel('$t$', 'interpreter','latex','fontsize',26); 
+
+waitfor(h);
+end 
+
+
+%%%%%%%%%%%%%%%%%%%%%%
+%Forward kinematics (in robot coordinate system)
+function f = fkin(x, param)
+	T = tril(ones(size(x,1)));
+	f = [param.l * cos(T * x); ...
+		 param.l * sin(T * x)]; 
+end
+
+%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%
+%Forward kinematics for all robot articulations (in robot coordinate system)
+function f = fkin0(x, param)
+	L = tril(ones(size(x,1)));
+	f = [L * diag(param.l) * cos(L * x), ...
+	     L * diag(param.l) * sin(L * x)]'; 
+	f = [zeros(2,1), f];
+end
+
+%%%%%%%%%%%%%%%%%%%%%%
+%Jacobian with analytical computation (for single time step)
+function J = Jkin(x, param)
+	T = tril(ones(size(x,1)));
+	J = [-sin(T * x)' * diag(param.l) * T; ...
+		  cos(T * x)' * diag(param.l) * T]; 
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%
+%%Jacobian with numerical computation (for single time step)
+%function J = jacob0_num(x, param)
+%	e = 1E-6;
+%	J = zeros(param.nbVarF, param.nbVarX);
+%	for n=1:size(x,1)
+%		xtmp = x;
+%		xtmp(n) = xtmp(n) + e;
+%		J(:,n) = (fkine0(xtmp, param) - fkine0(x, param)) / e;
+%	end
+%end
+
+%%%%%%%%%%%%%%%%%%%%%%
+%Cost and gradient for a viapoints reaching task (in object coordinate system)
+function [f, J] = f_reach(x, param)
+ 	f = fkin(x, param) - param.Mu; %Error by ignoring manifold
+	
+	J = []; 
+	for t=1:size(x,2)
+%		f(1:2,t) = param.A(:,:,t)' * f(1:2,t); %Object-centered FK
+		
+		Jtmp = Jkin(x(:,t), param);
+		%Jtmp = jacob0_num(x(:,t), param);
+		
+%		Jtmp(1:2,:) = param.A(:,:,t)' * Jtmp(1:2,:); %Object-centered Jacobian
+		
+%		%Bounding boxes (optional)
+%		for i=1:2
+%			if abs(f(i,t)) < param.sz(i)
+%				f(i,t) = 0;
+%				Jtmp(i,:) = 0;
+%			else
+%				f(i,t) = f(i,t) - sign(f(i,t)) * param.sz(i);
+%			end
+%		end
+		
+		J = blkdiag(J, Jtmp);
+	end
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%
+%%Cost and gradient for boundary on u
+%function [v, Jv, idv] = u_bound(u, param)
+%	v = zeros(param.nbVarU*(param.nbData-1), 1);
+%	ulim = repmat(param.ulim', param.nbData-1, 1);
+%	idv = abs(u) > ulim; %Bounding boxes
+%	Jv = diag(idv);
+%	v(idv) = u(idv) - sign(u(idv)) .* ulim(idv);
+%end	
+
+%%%%%%%%%%%%%%%%%%%%%%
+%Cost and gradient for boundary on x
+function [v, Jv, idv] = x_bound(x, param)
+	%v = zeros(param.nbVarU*param.nbData, 1);
+	xlim = repmat(param.xlim', param.nbData, 1);
+	idv = abs(x) > xlim; %Bounding boxes
+	Jv = eye(sum(idv));
+	v = x(idv) - sign(x(idv)) .* xlim(idv);
+end	
diff --git a/python/iLQR_manipulator_boundary.py b/python/iLQR_manipulator_boundary.py
index d8f58e545efe0bbb117b4167c6055515174fae0a..d35cdb8d81662fb12b55c2ba99efe58f4e10b8e1 100644
--- a/python/iLQR_manipulator_boundary.py
+++ b/python/iLQR_manipulator_boundary.py
@@ -49,7 +49,6 @@ def f_reach(x, param):
             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))
@@ -71,84 +70,6 @@ def fkin0(x, params):
     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
 # ===============================
 
@@ -164,7 +85,7 @@ 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.r = 1e-4  # Control weighting term
 param.Mu = np.array([[2, 3], [1, 2]])  # Viapoints
 
 # Main program
@@ -240,5 +161,56 @@ 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)
+"""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()
+
+"""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()