def _create_patch(self, shape, ax):
        type_ = type(shape)
        facecolor = shape.facecolor if hasattr(shape, 'facecolor') else (1, 1, 1, 1)
        edgecolor = shape.edgecolor if hasattr(shape, 'edgecolor') else (0, 0, 0, 1)

        if type_ == pymunk.shapes.Circle:
            R = rotation_matrix(shape.body.angle)
            p = np.array(shape.body.position) + np.array(shape.offset) @ R.T
            r = shape.radius
            patch = plt.Circle(p, r, facecolor=facecolor, edgecolor=edgecolor)
            ax.add_patch(patch)
        elif type_ == pymunk.shapes.Poly:
            p = np.array(shape.body.position)
            R = rotation_matrix(shape.body.angle)
            v = p + np.array(shape.get_vertices()) @ R.T
            patch = plt.Polygon(v, facecolor=facecolor, edgecolor=edgecolor, closed=True)
            ax.add_patch(patch)
        elif type_ == pymunk.shapes.Segment:
            p = np.array(shape.body.position)
            R = rotation_matrix(shape.body.angle)
            v = p + np.array([shape.a, shape.b]) @ R.T
            patch = plt.Line2D(v[:, 0], v[:, 1], color=edgecolor, linewidth=1)
            ax.add_line(patch)
        else:
            raise Exception(f'unsupported shape type: {type_}')
        self.patches[shape] = patch
 def _update_patch(self, shape):
     type_ = type(shape)
     patch = self.patches[shape]
     if type_ == pymunk.shapes.Circle:
         R = rotation_matrix(shape.body.angle)
         p = np.array(shape.body.position) + np.array(shape.offset) @ R.T
         patch.center = p
     elif type_ == pymunk.shapes.Poly:
         p = np.array(shape.body.position)
         R = rotation_matrix(shape.body.angle)
         v = p + np.array(shape.get_vertices()) @ R.T
         patch.set_xy(v)
     elif type_ == pymunk.shapes.Segment:
         p = np.array(shape.body.position)
         R = rotation_matrix(shape.body.angle)
         v = p + np.array([shape.a, shape.b]) @ R.T
         patch.set_data(v.T)
     else:
         raise Exception(f'unsupported shape type: {type_}')
    def render(self, ax):
        p_ew_w, θ_ew = self.P_ew_w[:2], self.P_ew_w[2]
        R_we = util.rotation_matrix(θ_ew)

        p_tw_w = p_ew_w + R_we @ self.p_te_e

        # sides
        p_lw_w = p_tw_w + R_we @ self.p_lt_t
        p_rw_w = p_tw_w + R_we @ self.p_rt_t

        # contact points
        p_c1w_w = p_ew_w + R_we @ self.p_c1e_e
        p_c2w_w = p_ew_w + R_we @ self.p_c2e_e

        self.tray, = ax.plot([p_lw_w[0], p_rw_w[0]], [p_lw_w[1], p_rw_w[1]],
                             color='k')
        self.com, = ax.plot(p_tw_w[0], p_tw_w[1], 'o', color='k')
        self.contacts, = ax.plot([p_c1w_w[0], p_c2w_w[0]],
                                 [p_c1w_w[1], p_c2w_w[1]],
                                 'o',
                                 color='r')
    def update_render(self):
        p_ew_w, θ_ew = self.P_ew_w[:2], self.P_ew_w[2]
        R_we = util.rotation_matrix(θ_ew)

        p_tw_w = p_ew_w + R_we @ self.p_te_e

        # sides
        p_lw_w = p_tw_w + R_we @ self.p_lt_t
        p_rw_w = p_tw_w + R_we @ self.p_rt_t

        # contact points
        p_c1w_w = p_ew_w + R_we @ self.p_c1e_e
        p_c2w_w = p_ew_w + R_we @ self.p_c2e_e

        self.tray.set_xdata([p_lw_w[0], p_rw_w[0]])
        self.tray.set_ydata([p_lw_w[1], p_rw_w[1]])

        self.com.set_xdata([p_tw_w[0]])
        self.com.set_ydata([p_tw_w[1]])

        self.contacts.set_xdata([p_c1w_w[0], p_c2w_w[0]])
        self.contacts.set_ydata([p_c1w_w[1], p_c2w_w[1]])
def main():
    if TRAY_W < TRAY_MU * TRAY_H:
        print('must have w >= μh')
        return

    N = int(DURATION / SIM_DT) + 1

    p_te_e = np.array([0, 0.05 + TRAY_H])
    p_oe_e = p_te_e + np.array([0, TRAY_H + OBJ_H])

    p_c1e_e = np.array([-TRAY_W, 0.05])
    p_c2e_e = np.array([TRAY_W, 0.05])

    model = FourInputModel(l1=L1, l2=L2, vel_lim=VEL_LIM, acc_lim=ACC_LIM)

    ts = SIM_DT * np.arange(N)
    us = np.zeros((N, ni))
    P_ew_ws = np.zeros((N, 3))
    P_ew_wds = np.zeros((N, 3))
    V_ew_ws = np.zeros((N, 3))
    P_tw_ws = np.zeros((N, 3))
    p_te_es = np.zeros((N, 2))

    ineq_cons = np.zeros((N, 3))

    p_te_es[0, :] = p_te_e

    # state of joints
    q0 = np.array([0, 0, 0.25*np.pi, -0.25*np.pi])
    dq0 = np.zeros(ni)
    X_q = np.concatenate((q0, dq0))

    P_ew_w = model.ee_position(X_q)
    V_ew_w = model.ee_velocity(X_q)
    P_ew_ws[0, :] = P_ew_w
    V_ew_ws[0, :] = V_ew_w

    # physics simulation
    sim = PymunkSimulationTrayBalance(SIM_DT, gravity=-GRAVITY, iterations=30)
    sim.add_robot(model, q0, TRAY_W, TRAY_MU)

    # tray
    tray_body = pymunk.Body(mass=MASS, moment=INERTIA)
    tray_body.position = tuple(P_ew_w[:2] + p_te_e)
    tray_corners = [(-RADIUS, TRAY_H), (-RADIUS, -TRAY_H), (RADIUS, -TRAY_H),
                    (RADIUS, TRAY_H)]
    tray = pymunk.Poly(tray_body, tray_corners, radius=0)
    tray.facecolor = (0.25, 0.5, 1, 1)
    tray.friction = TRAY_MU
    tray.collision_type = 1
    sim.space.add(tray.body, tray)

    obj_body = pymunk.Body(mass=OBJ_MASS, moment=OBJ_INERTIA)
    obj_body.position = tuple(P_ew_w[:2] + p_oe_e)
    obj_corners = [(-OBJ_W, OBJ_H), (-OBJ_W, -OBJ_H), (OBJ_W, -OBJ_H),
                   (OBJ_W, OBJ_H)]
    obj = pymunk.Poly(obj_body, obj_corners, radius=0)
    obj.facecolor = (0.5, 0.5, 0.5, 1)
    obj.friction = OBJ_MU / TRAY_MU  # so that mu with tray = OBJ_MU
    obj.collision_type = 1
    # sim.space.add(obj.body, obj)

    # sim.space.add_collision_handler(1, 1).post_solve = tray_cb

    # reference trajectory
    # timescaling = trajectories.QuinticTimeScaling(DURATION)
    # trajectory = trajectories.Circle(P_ew_w[:2], 0.25, timescaling, DURATION)
    trajectory = trajectories.Point(P_ew_w[:2] + np.array([1, -1]))
    P_ew_wds[:, :2], _, _ = trajectory.sample(ts)
    # P_ew_wds[:, 2] = -np.pi / 2

    # rendering
    goal_renderer = plotting.PointRenderer(P_ew_wds[-1, :2], color='r')
    sim_renderer = PymunkRenderer(sim.space, sim.markers)
    # trajectory_renderer = plotting.TrajectoryRenderer(trajectory, ts)
    renderers = [goal_renderer, sim_renderer]
    video = plotting.Video(name='tray_balance.mp4', fps=1./(SIM_DT*PLOT_PERIOD))
    plotter = plotting.RealtimePlotter(renderers, video=video)
    plotter.start()  # TODO for some reason setting grid=True messes up the base rendering

    # pymunk rendering
    # plt.ion()
    # fig = plt.figure()
    # ax = plt.gca()
    # plt.grid()
    #
    # ax.set_xlabel('x (m)')
    # ax.set_ylabel('y (m)')
    # ax.set_xlim([-1, 6])
    # ax.set_ylim([-1, 2])
    #
    # ax.set_aspect('equal')
    #
    # options = pymunk.matplotlib_util.DrawOptions(ax)
    # options.flags = pymunk.SpaceDebugDrawOptions.DRAW_SHAPES

    def objective_unrolled(X_q_0, X_ee_d, var):
        ''' Unroll the objective over n timesteps. '''
        obj = 0
        X_q = X_q_0

        for i in range(MPC_STEPS):
            u = var[i*nv:(i+1)*nv]
            X_q = model.step_unconstrained(X_q, u, MPC_DT)
            X_ee = model.ee_state(X_q)
            e = X_ee_d[i*ns_ee:(i+1)*ns_ee] - X_ee
            obj = obj + 0.5 * (e @ W @ e + X_q @ Q @ X_q + u @ R @ u)

        return obj

    def ineq_constraints(X_ee, a_ee, jnp=jnp):
        θ_ew, dθ_ew = X_ee[2], X_ee[5]
        a_ew_w, ddθ_ew = a_ee[:2], a_ee[2]
        R_ew = jnp.array([[ jnp.cos(θ_ew), jnp.sin(θ_ew)],
                          [-jnp.sin(θ_ew), jnp.cos(θ_ew)]])
        S1 = skew1(1)
        g = jnp.array([0, GRAVITY])

        ddR_we = (ddθ_ew*S1 - dθ_ew**2*jnp.eye(2)) @ R_ew.T

        α1, α2 = MASS * R_ew @ (a_ew_w+g) + MASS * (ddθ_ew*S1 - dθ_ew**2*jnp.eye(2)) @ p_te_e
        # α1, α2 = OBJ_MASS * R_ew @ (a_ew_w + g) + OBJ_MASS * (ddθ_ew*S1 - dθ_ew**2*jnp.eye(2)) @ p_oe_e
        α3 = INERTIA * ddθ_ew

        # constraints considering y acceleration at each contact point
        # β1 = jnp.array([0, 1]) @ R_ew @ (a_ew_w + ddR_we @ p_c1e_e + g)
        # β2 = jnp.array([0, 1]) @ R_ew @ (a_ew_w + ddR_we @ p_c2e_e + g)

        # ineq_con = jnp.array([
        #     TRAY_MU*jnp.abs(α2) - jnp.abs(α1),
        #     β1,
        #     β2])

        h1 = TRAY_MU*jnp.abs(α2) - jnp.abs(α1)
        # h1 = OBJ_MU*jnp.abs(α2) - jnp.abs(α1)
        h2 = α2
        # h3 = TRAY_H**2*α1**2 + TRAY_W*(TRAY_W-2*TRAY_MU*TRAY_H)*α2**2 - α3**2
        # h3 = OBJ_H**2 * α1**2 + OBJ_W * (OBJ_W - 2*OBJ_MU*OBJ_H)*α2**2 - α3**2
        h3 = 1

        return jnp.array([h1, h2, h3])

    def ineq_constraints_unrolled(X_q_0, X_ee_d, var):
        # var is now just the lifted joint acceleration inputs
        X_q = X_q_0
        ineq_con = jnp.zeros(MPC_STEPS * nc_ineq)

        for i in range(MPC_STEPS):
            u = var[i*nv:(i+1)*nv]

            X_ee = model.ee_state(X_q)
            a_ee = model.ee_acceleration(X_q, u)

            ineq_coni = ineq_constraints(X_ee, a_ee)
            ineq_con = jax.ops.index_update(ineq_con, jax.ops.index[i*nc_ineq:(i+1)*nc_ineq], ineq_coni)
            X_q = model.step_unconstrained(X_q, u, MPC_DT)
        return ineq_con

    # X_ee = model.ee_state(X_q)
    # a_ee = model.ee_acceleration(X_q, np.zeros(4))
    # ineq_constraints(X_ee, a_ee)
    # return

    # Construct the SQP controller
    obj_fun = jax.jit(objective_unrolled)
    obj_jac = jax.jit(jax.jacfwd(objective_unrolled, argnums=2))
    obj_hess = jax.jit(hessian(objective_unrolled, argnums=2))
    objective = sqp.Objective(obj_fun, obj_jac, obj_hess)

    lbA = np.zeros(MPC_STEPS * nc)
    ubA = np.infty * np.ones(MPC_STEPS * nc)
    con_fun = jax.jit(ineq_constraints_unrolled)
    con_jac = jax.jit(jax.jacfwd(ineq_constraints_unrolled, argnums=2))
    constraints = sqp.Constraints(con_fun, con_jac, lbA, ubA)

    lb = -ACC_LIM * np.ones(MPC_STEPS * nv)
    ub =  ACC_LIM * np.ones(MPC_STEPS * nv)
    bounds = sqp.Bounds(lb, ub)

    controller = sqp.SQP(nv*MPC_STEPS, nc*MPC_STEPS, objective, constraints, bounds, num_iter=3, verbose=True)

    for i in range(N - 1):
        t = ts[i+1]

        if i % CTRL_PERIOD == 0:
            t_sample = np.minimum(t + MPC_DT*np.arange(MPC_STEPS), DURATION)
            pd, vd, _ = trajectory.sample(t_sample, flatten=False)
            z = np.zeros((MPC_STEPS, 1))
            X_ee_d = np.hstack((pd, z, vd, z)).flatten()
            # -0.5*np.pi*np.ones((MPC_STEPS, 1))

            # start = time.time()
            var = controller.solve(X_q, X_ee_d)
            # print(time.time() - start)
            u = var[:ni]  # joint acceleration input
            sim.command_acceleration(u)

        # integrate the system
        X_q = np.concatenate(sim.step())
        P_ew_w = model.ee_position(X_q)
        V_ew_w = model.ee_velocity(X_q)

        X_ee = model.ee_state(X_q)
        a_ee = model.ee_acceleration(X_q, u)
        ineq_cons[i+1, :] = ineq_constraints(X_ee, a_ee, jnp=np)
        # if (ineq_con < 0).any():
        #     IPython.embed()

        # tray position is (ideally) a constant offset from EE frame
        θ_ew = P_ew_w[2]
        R_we = util.rotation_matrix(θ_ew)
        P_tw_w = np.array([tray.body.position.x, tray.body.position.y, tray.body.angle])

        # record
        us[i, :] = u
        P_ew_ws[i+1, :] = P_ew_w
        V_ew_ws[i+1, :] = V_ew_w
        P_tw_ws[i+1, :] = P_tw_w
        p_te_es[i+1, :] = R_we.T @ (P_tw_w[:2] - P_ew_w[:2])

        if i % PLOT_PERIOD == 0:
            # ax.cla()
            # ax.set_xlim([-1, 6])
            # ax.set_ylim([-1, 2])
            #
            # sim.space.debug_draw(options)
            # fig.canvas.draw()
            # fig.canvas.flush_events()

            plotter.update()
    plotter.done()

    v_te_es = (p_te_es[1:] - p_te_es[:-1]) / SIM_DT
    v_te_es_smooth = np.zeros_like(v_te_es)
    v_te_es_smooth[:, 0] = np.convolve(v_te_es[:, 0], np.ones(100) / 100, 'same')
    v_te_es_smooth[:, 1] = np.convolve(v_te_es[:, 1], np.ones(100) / 100, 'same')

    plt.figure()
    plt.plot(ts[:N], P_ew_wds[:, 0], label='$x_d$', color='b', linestyle='--')
    plt.plot(ts[:N], P_ew_wds[:, 1], label='$y_d$', color='r', linestyle='--')
    plt.plot(ts[:N], P_ew_ws[:, 0],  label='$x$', color='b')
    plt.plot(ts[:N], P_ew_ws[:, 1],  label='$y$', color='r')
    plt.plot(ts[:N], P_tw_ws[:, 0],  label='$t_x$')
    plt.plot(ts[:N], P_tw_ws[:, 1],  label='$t_y$')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('Position')
    plt.title('End effector position')

    plt.figure()
    plt.plot(ts[:N], p_te_es[:, 0], label='$x$', color='b')
    plt.plot(ts[:N], p_te_es[:, 1], label='$y$', color='r')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('$p^{te}_e$')
    plt.title('$p^{te}_e$')

    plt.figure()
    plt.plot(ts[:N], p_te_e[0] - p_te_es[:, 0], label='$x$', color='b')
    plt.plot(ts[:N], p_te_e[1] - p_te_es[:, 1], label='$y$', color='r')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('$p^{te}_e$ error')
    plt.title('$p^{te}_e$ error')

    # plt.figure()
    # plt.plot(ts[:N-1], v_te_es[:, 0], label='$v_x$', color='b')
    # plt.plot(ts[:N-1], v_te_es[:, 1], label='$v_y$', color='r')
    # plt.plot(ts[:N-1], v_te_es_smooth[:, 0], label='$v_x$ (smooth)')
    # plt.plot(ts[:N-1], v_te_es_smooth[:, 1], label='$v_y$ (smooth)')
    # plt.grid()
    # plt.legend()
    # plt.xlabel('Time (s)')
    # plt.ylabel('$v^{te}_e$')
    # plt.title('$v^{te}_e$')

    plt.figure()
    plt.plot(ts[:N], ineq_cons[:, 0], label='$h_1$')
    plt.plot(ts[:N], ineq_cons[:, 1], label='$h_2$')
    plt.plot(ts[:N], ineq_cons[:, 2], label='$h_3$')
    plt.plot([0, ts[-1]], [0, 0], color='k')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('Value')
    plt.title('Inequality constraints')

    plt.show()
Example #6
0
 def update_render(self):
     v = self.vertices @ rotation_matrix(self.angle).T
     self.patch.set_xy(self.p + v)
Example #7
0
 def render(self, ax):
     v = self.vertices @ rotation_matrix(self.angle).T
     self.patch = plt.Polygon(self.p + v, color='k', closed=True)
     ax.add_patch(self.patch)
 def get_position(self):
     R = rotation_matrix(self.body.angle)
     return np.array(self.body.position) + self.offset @ R.T
Example #9
0
def main():
    if TRAY_W < TRAY_MU * TRAY_H:
        print('must have w >= μh')
        return

    N = int(DURATION / SIM_DT) + 1

    p_te_e = np.array([0, 0.05 + TRAY_H])
    p_oe_e = p_te_e + np.array([0, TRAY_H + OBJ_H])

    r_c1e_e = np.array([-TRAY_W, 0.05])
    r_c2e_e = np.array([TRAY_W, 0.05])

    r_tc1_t = np.array([TRAY_W, TRAY_H])
    r_tc2_t = np.array([-TRAY_W, TRAY_H])
    r_t2t1_t = -r_tc2_t + r_tc1_t

    model = FourInputModel(l1=L1, l2=L2, vel_lim=VEL_LIM, acc_lim=ACC_LIM)

    ts = SIM_DT * np.arange(N)
    us = np.zeros((N, ni))
    P_ew_ws = np.zeros((N, 3))
    P_ew_wds = np.zeros((N, 3))
    V_ew_ws = np.zeros((N, 3))
    P_tw_ws = np.zeros((N, 3))
    p_te_es = np.zeros((N, 2))

    ineq_cons = np.zeros((N, 3))

    p_te_es[0, :] = p_te_e

    # state of joints
    q0 = np.array([0, 0, 0.25 * np.pi, -0.25 * np.pi])
    dq0 = np.zeros(ni)
    X_q = np.concatenate((q0, dq0))

    P_ew_w = model.ee_position(X_q)
    V_ew_w = model.ee_velocity(X_q)
    P_ew_ws[0, :] = P_ew_w
    V_ew_ws[0, :] = V_ew_w

    # physics simulation
    sim = PymunkSimulationTrayBalance(SIM_DT, gravity=-GRAVITY, iterations=30)
    sim.add_robot(model, q0, TRAY_W, TRAY_MU)

    # tray
    tray_body = pymunk.Body(mass=MASS, moment=INERTIA)
    tray_body.position = tuple(P_ew_w[:2] + p_te_e)
    tray_corners = [(-RADIUS, TRAY_H), (-RADIUS, -TRAY_H), (RADIUS, -TRAY_H),
                    (RADIUS, TRAY_H)]
    tray = pymunk.Poly(tray_body, tray_corners, radius=0)
    tray.facecolor = (0.25, 0.5, 1, 1)
    tray.friction = TRAY_MU
    tray.collision_type = 1
    sim.space.add(tray.body, tray)

    obj_body = pymunk.Body(mass=OBJ_MASS, moment=OBJ_INERTIA)
    obj_body.position = tuple(P_ew_w[:2] + p_oe_e)
    obj_corners = [(-OBJ_W, OBJ_H), (-OBJ_W, -OBJ_H), (OBJ_W, -OBJ_H),
                   (OBJ_W, OBJ_H)]
    obj = pymunk.Poly(obj_body, obj_corners, radius=0)
    obj.facecolor = (0.5, 0.5, 0.5, 1)
    obj.friction = OBJ_MU / TRAY_MU  # so that mu with tray = OBJ_MU
    obj.collision_type = 1
    # sim.space.add(obj.body, obj)

    # sim.space.add_collision_handler(1, 1).post_solve = tray_cb

    # reference trajectory
    # timescaling = trajectories.QuinticTimeScaling(DURATION)
    # trajectory = trajectories.Circle(P_ew_w[:2], 0.25, timescaling, DURATION)
    trajectory = trajectories.Point(P_ew_w[:2] + np.array([1, -1]))
    P_ew_wds[:, :2], _, _ = trajectory.sample(ts)
    # P_ew_wds[:, 2] = -np.pi / 2

    # rendering
    goal_renderer = plotting.PointRenderer(P_ew_wds[-1, :2], color='r')
    sim_renderer = PymunkRenderer(sim.space, sim.markers)
    # trajectory_renderer = plotting.TrajectoryRenderer(trajectory, ts)
    renderers = [goal_renderer, sim_renderer]
    video = plotting.Video(name='tray_balance.mp4',
                           fps=1. / (SIM_DT * PLOT_PERIOD))
    plotter = plotting.RealtimePlotter(renderers, video=video)
    plotter.start(
    )  # TODO for some reason setting grid=True messes up the base rendering

    # pymunk rendering
    # plt.ion()
    # fig = plt.figure()
    # ax = plt.gca()
    # plt.grid()
    #
    # ax.set_xlabel('x (m)')
    # ax.set_ylabel('y (m)')
    # ax.set_xlim([-1, 6])
    # ax.set_ylim([-1, 2])
    #
    # ax.set_aspect('equal')
    #
    # options = pymunk.matplotlib_util.DrawOptions(ax)
    # options.flags = pymunk.SpaceDebugDrawOptions.DRAW_SHAPES

    def jax_rotation_matrix(θ):
        return jnp.array([[jnp.cos(θ), -jnp.sin(θ)], [jnp.sin(θ), jnp.cos(θ)]])

    def calc_tray_pose(q):
        """Calculate tray pose in terms of x1, y1, y2."""
        x1, y1, y2 = q
        Δy = y2 - y1
        Δx = jnp.sqrt((2 * TRAY_W)**2 - Δy**2)
        θ_tw = jnp.arctan2(Δy, Δx)
        R_wt = jax_rotation_matrix(θ_tw)
        r_c1w_w = jnp.array([x1, y1])
        r_tw_w = r_c1w_w + R_wt @ r_tc1_t
        return jnp.array([r_tw_w[0], r_tw_w[1], θ_tw])

    # def calc_tray_state(P_tw_w, V_tw_w):
    #     """Calculate tray parameters q given the pose."""
    #     # TODO ideally we'd just keep everything in terms of q, but the old
    #     # approach parameterizes the tray by its pose.
    #     # TODO inconsistent notation
    #     r_tw_w, θ_tw = P_tw_w[:2], P_tw_w[2]
    #     R_wt = jnp.array([[jnp.cos(θ_tw), -jnp.sin(θ_tw)],
    #                       [jnp.sin(θ_tw),  jnp.cos(θ_tw)]])
    #     r_t1w_w = r_tw_w - R_wt @ r_tc1_t
    #     r_t2w_w = r_tw_w - R_wt @ r_tc2_t
    #     q = jnp.array([r_t1w_w[0], r_t1w_w[1], r_t2w_w[1]])
    #
    #     return q, dq

    calc_tray_jacobian = jax.jit(jax.jacfwd(calc_tray_pose))

    def calc_tray_potential(q):
        """Calculate tray potential energy."""
        x, y, θ = calc_tray_pose(q)
        return MASS * GRAVITY * y

    calc_g = jax.jit(jax.jacfwd(calc_tray_potential))

    def calc_tray_mass_matrix(q):
        """Calculate tray mass matrix."""
        J = calc_tray_jacobian(q)
        M = J.T @ TRAY_G_MAT @ J
        return M

    def calc_ddR_wt(q, dq, ddq):
        """Calculate the second time derivative of tray's rotation matrix."""
        Δy = q[2] - q[1]
        dΔy = dq[2] - dq[1]
        ddΔy = ddq[2] - ddq[1]
        Δx = jnp.sqrt((2 * TRAY_W)**2 - Δy**2)

        θ_tw = jnp.arctan2(Δy, Δx)
        dθ_tw = dΔy / Δx  # TODO possible division by zero
        ddθ_tw = (ddΔy * Δx**2 + dΔy**2 * Δy) / Δx**3

        S1 = skew1(1)
        ddR_wt = (ddθ_tw * S1 - dθ_tw**2) @ jax_rotation_matrix(θ_tw)
        return ddR_wt

    calc_dMdq = jax.jit(jax.jacfwd(calc_tray_mass_matrix))

    def calc_tray_h(q, dq):
        """Calculate h term in dynamics of the form M(q)*ddq + h(q,dq) = f."""
        dMdq = calc_dMdq(q)
        Γ = dMdq - 0.5 * dMdq.T
        g = calc_g(q)
        h = dq.dot(Γ).dot(dq) + g
        return h

    def objective_unrolled(X_q_0, X_ee_d, var):
        """Unroll the objective over n timesteps."""
        obj = 0
        X_q = X_q_0

        for i in range(MPC_STEPS):
            u = var[i * nv:(i + 1) * nv]
            X_q = model.step_unconstrained(X_q, u, MPC_DT)
            X_ee = model.ee_state(X_q)
            e = X_ee_d[i * ns_ee:(i + 1) * ns_ee] - X_ee
            obj = obj + 0.5 * (e @ W @ e + X_q @ Q @ X_q + u @ R @ u)

        return obj

    def ineq_constraints(X_ee, a_ee, jnp=jnp):
        r_ew_w = X_ee[:2]
        v_ew_w = X_ee[3:5]
        θ_ew, dθ_ew = X_ee[2], X_ee[5]
        a_ew_w, ddθ_ew = a_ee[:2], a_ee[2]
        R_ew = jnp.array([[jnp.cos(θ_ew), jnp.sin(θ_ew)],
                          [-jnp.sin(θ_ew), jnp.cos(θ_ew)]])
        S1 = skew1(1)
        g = jnp.array([0, GRAVITY])

        # calculate tray state from EE state
        r_c1w_w = r_ew_w + R_ew.T @ r_c1e_e
        r_c2w_w = r_ew_w + R_ew.T @ r_c2e_e
        v_c1w_w = v_ew_w + dθ_ew * S1 @ R_ew.T @ r_c1e_e
        v_c2w_w = v_ew_w + dθ_ew * S1 @ R_ew.T @ r_c2e_e
        qt = jnp.array([r_c1w_w[0], r_c1w_w[1], r_c2w_w[1]])
        dqt = jnp.array([v_c1w_w[0], v_c1w_w[1], v_c2w_w[1]])

        # calculate tray dynamics
        Mt = calc_tray_mass_matrix(qt)
        ht = calc_tray_h(qt, dqt)
        Qt = jnp.linalg.solve(
            Mt, ht)  # TODO bad name: this isn't the generalized forces

        ddR_we = (ddθ_ew * S1 - dθ_ew**2 * jnp.eye(2)) @ R_ew.T
        ddR_wt = calc_ddR_wt(qt, dqt, Qt)

        a_t1w_w = Qt[:2]
        a_t2w_w = a_t1w_w + ddR_wt @ r_t2t1_t

        ddy1 = np.array([0, 1]) @ R_ew @ (a_ew_w + ddR_we @ r_c1e_e)
        ddy2 = np.array([0, 1]) @ R_ew @ (a_ew_w + ddR_we @ r_c2e_e)

        # TODO: how to deal with friction under this formulation?
        h1 = 1
        h2 = ddy1 + a_t1w_w[1]
        h3 = ddy2 + a_t2w_w[1]

        # α1, α2 = MASS * R_ew @ (a_ew_w+g) + MASS * (ddθ_ew*S1 - dθ_ew**2*jnp.eye(2)) @ p_te_e

        # α3 = INERTIA * ddθ_ew
        #
        # h1 = TRAY_MU*jnp.abs(α2) - jnp.abs(α1)
        # h2 = α2
        # h3 = 1

        return jnp.array([h1, h2, h3])

    def ineq_constraints_unrolled(X_q_0, X_ee_d, var):
        # var is now just the lifted joint acceleration inputs
        X_q = X_q_0
        ineq_con = jnp.zeros(MPC_STEPS * nc_ineq)

        for i in range(MPC_STEPS):
            u = var[i * nv:(i + 1) * nv]

            X_ee = model.ee_state(X_q)
            a_ee = model.ee_acceleration(X_q, u)

            ineq_coni = ineq_constraints(X_ee, a_ee)
            ineq_con = jax.ops.index_update(
                ineq_con, jax.ops.index[i * nc_ineq:(i + 1) * nc_ineq],
                ineq_coni)
            X_q = model.step_unconstrained(X_q, u, MPC_DT)
        return ineq_con

    # Construct the SQP controller
    obj_fun = jax.jit(objective_unrolled)
    obj_jac = jax.jit(jax.jacfwd(objective_unrolled, argnums=2))
    obj_hess = jax.jit(hessian(objective_unrolled, argnums=2))
    objective = sqp.Objective(obj_fun, obj_jac, obj_hess)

    lbA = np.zeros(MPC_STEPS * nc)
    ubA = np.infty * np.ones(MPC_STEPS * nc)
    con_fun = jax.jit(ineq_constraints_unrolled)
    con_jac = jax.jit(jax.jacfwd(ineq_constraints_unrolled, argnums=2))
    constraints = sqp.Constraints(con_fun, con_jac, lbA, ubA)

    lb = -ACC_LIM * np.ones(MPC_STEPS * nv)
    ub = ACC_LIM * np.ones(MPC_STEPS * nv)
    bounds = sqp.Bounds(lb, ub)

    controller = sqp.SQP(nv * MPC_STEPS,
                         nc * MPC_STEPS,
                         objective,
                         constraints,
                         bounds,
                         num_iter=3,
                         verbose=False)

    for i in range(N - 1):
        t = ts[i + 1]

        if i % CTRL_PERIOD == 0:
            t_sample = np.minimum(t + MPC_DT * np.arange(MPC_STEPS), DURATION)
            pd, vd, _ = trajectory.sample(t_sample, flatten=False)
            z = np.zeros((MPC_STEPS, 1))
            X_ee_d = np.hstack((pd, z, vd, z)).flatten()
            # -0.5*np.pi*np.ones((MPC_STEPS, 1))

            # start = time.time()
            var = controller.solve(X_q, X_ee_d)
            # print(time.time() - start)
            u = var[:ni]  # joint acceleration input
            sim.command_acceleration(u)

        # integrate the system
        X_q = np.concatenate(sim.step())
        P_ew_w = model.ee_position(X_q)
        V_ew_w = model.ee_velocity(X_q)

        X_ee = model.ee_state(X_q)
        a_ee = model.ee_acceleration(X_q, u)
        ineq_cons[i + 1, :] = ineq_constraints(X_ee, a_ee, jnp=np)
        # if (ineq_con < 0).any():
        #     IPython.embed()

        # tray position is (ideally) a constant offset from EE frame
        θ_ew = P_ew_w[2]
        R_we = util.rotation_matrix(θ_ew)
        P_tw_w = np.array(
            [tray.body.position.x, tray.body.position.y, tray.body.angle])

        # record
        us[i, :] = u
        P_ew_ws[i + 1, :] = P_ew_w
        V_ew_ws[i + 1, :] = V_ew_w
        P_tw_ws[i + 1, :] = P_tw_w
        p_te_es[i + 1, :] = R_we.T @ (P_tw_w[:2] - P_ew_w[:2])

        if i % PLOT_PERIOD == 0:
            # ax.cla()
            # ax.set_xlim([-1, 6])
            # ax.set_ylim([-1, 2])
            #
            # sim.space.debug_draw(options)
            # fig.canvas.draw()
            # fig.canvas.flush_events()

            plotter.update()
    plotter.done()

    v_te_es = (p_te_es[1:] - p_te_es[:-1]) / SIM_DT
    v_te_es_smooth = np.zeros_like(v_te_es)
    v_te_es_smooth[:, 0] = np.convolve(v_te_es[:, 0],
                                       np.ones(100) / 100, 'same')
    v_te_es_smooth[:, 1] = np.convolve(v_te_es[:, 1],
                                       np.ones(100) / 100, 'same')

    plt.figure()
    plt.plot(ts[:N], P_ew_wds[:, 0], label='$x_d$', color='b', linestyle='--')
    plt.plot(ts[:N], P_ew_wds[:, 1], label='$y_d$', color='r', linestyle='--')
    plt.plot(ts[:N], P_ew_ws[:, 0], label='$x$', color='b')
    plt.plot(ts[:N], P_ew_ws[:, 1], label='$y$', color='r')
    plt.plot(ts[:N], P_tw_ws[:, 0], label='$t_x$')
    plt.plot(ts[:N], P_tw_ws[:, 1], label='$t_y$')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('Position')
    plt.title('End effector position')

    plt.figure()
    plt.plot(ts[:N], p_te_es[:, 0], label='$x$', color='b')
    plt.plot(ts[:N], p_te_es[:, 1], label='$y$', color='r')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('$p^{te}_e$')
    plt.title('$p^{te}_e$')

    plt.figure()
    plt.plot(ts[:N], p_te_e[0] - p_te_es[:, 0], label='$x$', color='b')
    plt.plot(ts[:N], p_te_e[1] - p_te_es[:, 1], label='$y$', color='r')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('$p^{te}_e$ error')
    plt.title('$p^{te}_e$ error')

    # plt.figure()
    # plt.plot(ts[:N-1], v_te_es[:, 0], label='$v_x$', color='b')
    # plt.plot(ts[:N-1], v_te_es[:, 1], label='$v_y$', color='r')
    # plt.plot(ts[:N-1], v_te_es_smooth[:, 0], label='$v_x$ (smooth)')
    # plt.plot(ts[:N-1], v_te_es_smooth[:, 1], label='$v_y$ (smooth)')
    # plt.grid()
    # plt.legend()
    # plt.xlabel('Time (s)')
    # plt.ylabel('$v^{te}_e$')
    # plt.title('$v^{te}_e$')

    plt.figure()
    plt.plot(ts[:N], ineq_cons[:, 0], label='$h_1$')
    plt.plot(ts[:N], ineq_cons[:, 1], label='$h_2$')
    plt.plot(ts[:N], ineq_cons[:, 2], label='$h_3$')
    plt.plot([0, ts[-1]], [0, 0], color='k')
    plt.grid()
    plt.legend()
    plt.xlabel('Time (s)')
    plt.ylabel('Value')
    plt.title('Inequality constraints')

    plt.show()
Example #10
0
    def _lookahead(self, q0, pr, u, N, pc):
        ''' Generate lifted matrices proprogating the state N timesteps into the
            future. '''
        ni = self.model.ni  # number of joints
        no = self.model.no  # number of Cartesian outputs

        fbar = np.zeros(no * N)  # Lifted forward kinematics
        Jbar = np.zeros((no * N, ni * N))  # Lifted Jacobian
        Qbar = np.kron(np.eye(N), self.Q)
        Rbar = np.kron(np.eye(N), self.R)

        # lower triangular matrix of ni*ni identity matrices
        Ebar = np.kron(np.tril(np.ones((N, N))), np.eye(ni))

        # Integrate joint positions from the last iteration
        qbar = np.tile(q0, N + 1)
        qbar[ni:] = qbar[ni:] + self.dt * Ebar.dot(u)

        # TODO: need to integrate pc as well: this takes the place of fbar

        num_body_pts = 2 + 1
        Abar = np.zeros((N * num_body_pts, ni * N))
        lbA = np.zeros(N * num_body_pts)

        for k in range(N):
            q = qbar[(k + 1) * ni:(k + 2) * ni]

            # calculate points defining front of base
            pb = q[:2]
            θb = q[2]
            R = util.rotation_matrix(θb)
            rx = 0.5
            ry = 0.25
            p1 = R.dot(np.array([rx, ry]))
            p2 = R.dot(np.array([rx, -ry]))

            # pf is the closest point to the line segment
            pf, _ = util.dist_to_line_segment(pc, p1, p2)

            # transform into body frame
            b_pf = R.T.dot(pf - pb)

            JR = util.rotation_jacobian(θb)
            Jf = np.hstack((R, JR.dot(pb + b_pf)[:, None], np.zeros((2, 2))))

            pe = self.model.forward(q)
            Je = self.model.jacobian(q)

            re = (pc - pe) / np.linalg.norm(pc - pe)
            rf = (pc - pf) / np.linalg.norm(pc - pf)

            # propagate center of object forward
            pc = pc + self.dt * (Jf + Je).dot(u)

            # fbar[k*no:(k+1)*no] = pm
            # Jbar[k*no:(k+1)*no, k*ni:(k+1)*ni] = Jm

            # # TODO hardcoded radius
            # # EE and obstacle
            # d_ee_obs = np.linalg.norm(p - pc) - 0.45
            # Abar[k*num_body_pts, k*ni:(k+1)*ni] = (p - pc).T.dot(J) / np.linalg.norm(p - pc)
            # lbA[k*num_body_pts] = -d_ee_obs
            #
            # # base and obstacle
            # pb = q[:2]
            # Jb = np.array([[1, 0, 0, 0, 0],
            #                [0, 1, 0, 0, 0]])
            # d_base_obs = np.linalg.norm(pb - pc) - 0.45 - 0.56
            # Abar[k*num_body_pts+1, k*ni:(k+1)*ni] = (pb - pc).T.dot(Jb) / np.linalg.norm(pb - pc)
            # lbA[k*num_body_pts+1] = -d_base_obs
            #
            # # pf and ee: these need to stay close together
            # pf = self.model.forward_f(q)
            # Jf = self.model.jacobian_f(q)
            # d_pf_ee = np.linalg.norm(p - pf)
            # A_pf_ee = -(pf - p).T.dot(Jf - J) / d_pf_ee
            # lbA_pf_ee = d_pf_ee - 0.75
            # Abar[k*num_body_pts+2, k*ni:(k+1)*ni] = A_pf_ee
            # lbA[k*num_body_pts+2] = lbA_pf_ee

        dbar = fbar - pr

        H = Rbar + self.dt**2 * Ebar.T.dot(
            Jbar.T).dot(Qbar).dot(Jbar).dot(Ebar)
        g = u.T.dot(Rbar) + self.dt * dbar.T.dot(Qbar).dot(Jbar).dot(Ebar)
        A = self.dt * Abar.dot(Ebar)

        return H, g, A, lbA