packages = ['numpy']

Exercise 5.a
Forward dynamics

Forward dynamics is the problem of computing the joint (angular) accelerations of a robot given the applied joint torques. This allows us to construct physical models that can predict the next steps of the motion when applying certain torques. You can read about forward dynamics in Section 6 of the RCFS documentation.

The goal of this exercise is to familiarize with the dynamics of a 2D planar manipulator. You can move the robot by dragging its joints using the left mouse button.

  • Use your mouse to perturb the robot and observe the effect of the dynamics.
  • Change param.l, param.l_m and param.damping to see how these parameters affect the dynamics. Note that some parameters will produce instable behaviors (if this happens, you can reload the page to restart from a stable initial state).

param.l = [2, 2] # Robot links lengths param.l_m = [1, 1] # Links masses param.damping = 5 # Damping




def print(x): display(x, target='output') from pyodide.ffi import create_proxy from js import Path2D, document, console import numpy as np import asyncio # Forward kinematics for all joints (in robot coordinate system) def fkin0(x, param): L = np.tril(np.ones([param.nbVarX, param.nbVarX])) f = np.vstack([ L @ np.diag(param.l) @ np.cos(L @ x), L @ np.diag(param.l) @ np.sin(L @ x) ]) f = np.hstack([np.zeros([2,1]), f]) return f # Forward kinematics for end-effector (in robot coordinate system) def fdyn(x, u, param): l = np.reshape(param.l, [1,param.nbVarX]) m = np.reshape(param.l_m, [1,param.nbVarX]) T = np.tril(np.ones([param.nbVarX, param.nbVarX])) Tm = np.multiply(np.triu(np.ones([m.shape[1], m.shape[1]])), np.repeat(m, m.shape[1],0)) ## Computation in matrix form of G,M, and C #G =-np.reshape(np.sum(Tm, 1), [param.nbVarX, 1]) * l.T * np.cos(T @ np.reshape(x[0:param.nbVarX], [param.nbVarX, 1])) * param.gravity #G = T.T @ G #M = (l.T * l) * np.cos(np.reshape(T @ x[:param.nbVarX], [param.nbVarX, 1]) - T @ x[:param.nbVarX]) * (Tm**.5 @ ((Tm**.5).T)) #M = T.T @ M @ T #C = -(l.T * l) * np.sin(np.reshape(T @ x[:param.nbVarX], [param.nbVarX, 1]) - T @ x[:param.nbVarX]) * (Tm**.5 @ ((Tm**.5).T)) # Elementwise computation of G, M, and C G = np.zeros([param.nbVarX, 1]) M = np.zeros([param.nbVarX, param.nbVarX]) C = np.zeros([param.nbVarX, param.nbVarX]) for k in range(param.nbVarX): G[k,0] = -sum(m[0,k:]) * param.gravity * l[0,k] * np.cos(T[k,:] @ x[:param.nbVarX]) for i in range(param.nbVarX): S = sum(m[0,k:param.nbVarX] * np.heaviside(np.array(range(k, param.nbVarX)) - i, 1)) M[k,i] = l[0,k] * l[0,i] * np.cos(T[k,:] @ x[:param.nbVarX] - T[i,:] @ x[:param.nbVarX]) * S C[k,i] = -l[0,k] * l[0,i] * np.sin(T[k,:] @ x[:param.nbVarX] - T[i,:] @ x[:param.nbVarX]) * S G = T.T @ G M = T.T @ M @ T # Compute acceleration tau = np.reshape(u, [param.nbVarX, 1]) inv_M = np.linalg.inv(M) ddq = inv_M @ (tau + G + T.T @ C @ (T @ np.reshape(x[param.nbVarX:],[param.nbVarX, 1]))**2 - \ T @ np.reshape(x[param.nbVarX:],[param.nbVarX, 1]) * param.damping) # compute the next step dq = x[param.nbVarX:] + ddq[:,0] * param.dt q = x[:param.nbVarX] + x[param.nbVarX:] * param.dt + 0.5 * ddq[:,0] * (param.dt**2) xt = np.append(q, dq) return xt ## Parameters # =============================== param = lambda: None # Lazy way to define an empty class in python param.nbVarX = 2 # State space dimension (x1,x2,x3) param.l = [2, 2] # Robot links lengths param.l_m = [1, 1] # Link masses param.damping = 8. # Damping param.gravity = 9.81 # Gravity param.dt = 5E-2 # Time step length ######################################################################################### # Mouse events mouse0 = np.zeros(2) mousedown = 0 hover_joint = -1 move_joint= -1 hover0 = np.zeros(2) def onMouseMove(event): global mouse, mouse0, hover0, x offset = canvas.getBoundingClientRect() mouse0[0] = (event.clientX - offset.x) * canvas.width / canvas.clientWidth mouse0[1] = (event.clientY - offset.y) * canvas.height / canvas.clientHeight if move_joint >= 0: x[move_joint] -= 1E-2 * np.sum(hover0 - mouse0) hover0 = np.copy(mouse0) def onTouchMove(event): global mouse, mouse0, hover0, x offset = event.target.getBoundingClientRect() mouse0[0] = (event.touches.item(0).clientX - offset.x) * canvas.width / canvas.clientWidth mouse0[1] = (event.touches.item(0).clientY - offset.y) * canvas.height / canvas.clientHeight if move_joint >= 0: x[move_joint] -= 1E-2 * np.sum(hover0 - mouse0) hover0 = np.copy(mouse0) def onMouseDown(event): global mousedown, move_joint, hover0 mousedown = 1 if hover_joint >= 0: move_joint = hover_joint hover0 = np.copy(mouse0) def onMouseUp(event): global mousedown, move_joint mousedown = 0 move_joint = -1 def onWheel(event): global x if hover_joint >= 0: x[hover_joint] -= 0.2 * (event.deltaY/106) document.addEventListener('mousemove', create_proxy(onMouseMove)) #for standard mouse document.addEventListener('touchmove', create_proxy(onTouchMove)) #for mobile interfaces document.addEventListener('mousedown', create_proxy(onMouseDown)) #for standard mouse #document.addEventListener('pointerdown', create_proxy(onMouseDown)) #for mobile interfaces document.addEventListener('touchstart', create_proxy(onMouseDown)) #for mobile interfaces document.addEventListener('mouseup', create_proxy(onMouseUp)) #for standard mouse #document.addEventListener('pointerup', create_proxy(onMouseUp)) #for mobile interfaces document.addEventListener('touchend', create_proxy(onMouseUp)) #for mobile interfaces document.addEventListener('wheel', create_proxy(onWheel)) #for standard mouse ######################################################################################### canvas = document.getElementById('canvas') ctx = canvas.getContext('2d') def clear_screen(): ctx.setTransform(1, 0, 0, 1, 0, 0) ctx.fillStyle = 'white' ctx.fillRect(0, 0, canvas.width, canvas.height) def draw_ground(): ctx.setTransform(1, 0, 0, -1, canvas.width*0.5, canvas.height*0.9) ctx.translate(0, -40) ctx.lineCap = 'round' ctx.lineJoin = 'round' ctx.lineWidth = '5' ctx.strokeStyle = '#CCCCCC' ctx.beginPath() ctx.moveTo(-400, 0) ctx.lineTo(400, 0) ctx.stroke() def draw_robot(x, color): global hover_joint ctx.setTransform(1, 0, 0, -1, canvas.width*0.5, canvas.height*0.5) f = fkin0(x, param)*100 # Draw base ctx.translate(f[0,0], f[1,0]) ctx.lineWidth = '4' ctx.strokeStyle = 'white' ctx.fillStyle = color ctx.beginPath() ctx.arc(0, 0, 40, 0, np.pi) ctx.rect(-40, 0, 80, -40) ctx.fill() ctx.strokeStyle = color for i in range(5): ctx.beginPath() ctx.moveTo(-30+i*15, -40) ctx.lineTo(-40+i*15, -60) ctx.stroke() # Draw links ctx.lineCap = 'round' ctx.lineJoin = 'round' for i in range(param.nbVarX): # Draw links outlines ctx.lineWidth = '46' ctx.strokeStyle = 'white' ctx.beginPath() ctx.lineTo(f[0,i], f[1,i]) ctx.lineTo(f[0,i+1], f[1,i+1]) ctx.stroke() # Draw links ctx.lineWidth = '38' ctx.strokeStyle = color ctx.beginPath() ctx.lineTo(f[0,i], f[1,i]) ctx.lineTo(f[0,i+1], f[1,i+1]) ctx.stroke() # Draw articulations obj = Path2D.new() obj.arc(0, 0, 12, 0, 2*np.pi) ctx.lineWidth = '4' ctx.strokeStyle = 'white' for i in range(param.nbVarX+1): ctx.translate(f[0,i], f[1,i]) ctx.stroke(obj) if ctx.isPointInPath(obj, mouse0[0], mouse0[1]): hover_joint = i ctx.translate(-f[0,i], -f[1,i]) def draw_tip(f, color): ctx.setTransform(1, 0, 0, -1, canvas.width*0.5, canvas.height*0.9) # Draw object obj = Path2D.new() obj.arc(0, 0, 16, 0, 2*np.pi) ctx.translate(f[0], f[1]) ctx.fillStyle = color ctx.fill(obj) ######################################################################################### def errorHandler(e): msg = 'Error: ' + str(e) console.error(msg) el = document.getElementById('repl-err') el.innerText = msg #el.textContent = msg ######################################################################################### x = np.zeros(param.nbVarX) # Initial robot state state = np.append(x, np.zeros(param.nbVarX)) u = np.zeros(param.nbVarX) async def main(): global hover_joint, x, state while True: try: state = fdyn(state, u, param) except Exception as e: errorHandler(e) state = np.zeros(param.nbVarX) x = state[:param.nbVarX] # Reinit hovering variables hover_joint = -1 # Rendering clear_screen() # draw_ground() draw_robot(x, '#AAAAAA') #draw_tip(f, '#FF3399') await asyncio.sleep(0.01) pyscript.run_until_complete(main())