Exemple #1
0
    def _calc_R(self, name, lambdify=True):
        """ Uses Sympy to generate the rotation matrix for a joint or link

        Parameters
        ----------
        name : string
            name of the joint, link, or end-effector
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """
        R = None
        R_func = None
        filename = name + '_R'

        # check to see if we have the rotation matrix saved in file
        R, R_func = self._load_from_file(filename, lambdify=True)

        if R is None and R_func is None:
            # if no saved file was loaded, generate function
            print('Generating rotation matrix function.')
            R = self._calc_T(name=name)[:3, :3]

            # save to file
            os_utils.makedirs('%s/%s' % (self.config_folder, filename))
            cloudpickle.dump(
                sp.Matrix(R),
                open('%s/%s/%s' % (self.config_folder, filename, filename),
                     'wb'))

        if R_func is None:
            R_func = self._generate_and_save_function(filename=filename,
                                                      expression=R,
                                                      parameters=self.q)
        return R_func
Exemple #2
0
    def _calc_dJ(self, name, x, lambdify=True):
        """ Generate the derivative of the Jacobian

        Uses Sympy to generate the derivative of the Jacobian
        for a joint or link with respect to time

        Parameters
        ----------
        name : string
            name of the joint, link, or end-effector
        x : numpy.array
            the [x,y,z] offset inside the reference frame of 'name' [meters]
            if not specified, (0, 0, 0) is hard coded in, rather than using
            variable (x, y, z), which results in significant speedups.
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        dJ = None
        dJ_func = None
        filename = name + '[0,0,0]' if np.allclose(x, 0) else name
        filename += '_dJ'
        # check to see if should try to load functions from file
        dJ, dJ_func = self._load_from_file(filename, lambdify)

        if dJ is None and dJ_func is None:
            # if no saved file was loaded, generate function
            print('Generating derivative of Jacobian ',
                  'function for %s' % filename)

            J = self._calc_J(name, x=x, lambdify=False)
            dJ = sp.Matrix(np.zeros(J.shape, dtype='float32'))
            # calculate derivative of (x,y,z) wrt to time
            # which each joint is dependent on
            for ii in range(J.shape[0]):
                for jj in range(J.shape[1]):
                    for kk in range(self.N_JOINTS):
                        dJ[ii, jj] += J[ii, jj].diff(self.q[kk]) * self.dq[kk]
            dJ = sp.Matrix(dJ)

            # save expression to file
            os_utils.makedirs('%s/%s' % (self.config_folder, filename))
            cloudpickle.dump(
                dJ,
                open('%s/%s/%s' % (self.config_folder, filename, filename),
                     'wb'))

        if lambdify is False:
            # if should return expression not function
            return dJ

        if dJ_func is None:
            dJ_func = self._generate_and_save_function(filename=filename,
                                                       expression=dJ,
                                                       parameters=self.q +
                                                       self.dq + self.x)
        return dJ_func
Exemple #3
0
    def _calc_Tx(self, name, x=None, lambdify=True):
        """Return transform from x in reference frame of 'name' to the origin

        Uses Sympy to transform x from the reference frame of a joint
        or link to the origin (world) coordinates.

        Parameters
        ----------
        name : string
            name of the joint, link, or end-effector
        x : numpy.array
            the [x,y,z] offset inside the reference frame of 'name' [meters]
            if not specified, (0, 0, 0) is hard coded in, rather than using
            variable (x, y, z), which results in significant speedups.
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        Tx = None
        Tx_func = None
        filename = name + '[0,0,0]' if np.allclose(x, 0) else name
        filename += '_Tx'
        # check to see if we have our transformation saved in file
        Tx, Tx_func = self._load_from_file(filename, lambdify)

        if Tx is None and Tx_func is None:
            print('Generating transform function for %s' % filename)
            T = self._calc_T(name=name)
            # transform x into world coordinates
            if np.allclose(x, 0):
                # if we're only interested in the origin, not including
                # the x variables significantly speeds things up
                Tx = T * sp.Matrix([0, 0, 0, 1])
            else:
                # if we're interested in other points in the given frame
                # of reference, calculate transform with x variables
                Tx = T * sp.Matrix(self.x + [1])
            Tx = sp.Matrix(Tx)

            # save to file
            os_utils.makedirs('%s/%s' % (self.config_folder, filename))
            cloudpickle.dump(
                sp.Matrix(Tx),
                open('%s/%s/%s.Tx' % (self.config_folder, filename, filename),
                     'wb'))

        if lambdify is False:
            # if should return expression not function
            return Tx

        if Tx_func is None:
            Tx_func = self._generate_and_save_function(filename=filename,
                                                       expression=Tx,
                                                       parameters=self.q +
                                                       self.x)
        return Tx_func
Exemple #4
0
    def __init__(self,
                 N_JOINTS,
                 N_LINKS,
                 ROBOT_NAME="robot",
                 use_cython=False,
                 MEANS=None,
                 SCALES=None):

        self.N_JOINTS = N_JOINTS
        self.N_LINKS = N_LINKS
        self.ROBOT_NAME = ROBOT_NAME
        self.use_cython = use_cython
        # dictionaries set by the sub-config, used for scaling input into
        # neural systems. Calculate by recording data from movement of interest
        self.MEANS = MEANS  # expected mean of joints angles / velocities
        self.SCALES = SCALES  # expected variance of joint angles / velocities

        # create function placeholders and dictionaries
        self._c = None
        self._dJ = {}
        self._g = None
        self._J = {}
        self._M = None
        self._orientation = {}
        self._R = {}
        self._S = None
        self._T_inv = {}
        self._Tx = {}

        self._KZ = sp.Matrix([0, 0, 1])

        # inertia matrix lists, to be filled out by subclasses
        self._M_LINKS = []
        self._M_JOINTS = []

        # specify / create the folder to save to and load from
        self.config_folder = (cache_dir + '/%s/saved_functions/' % ROBOT_NAME)
        # create a unique hash for the config file
        hasher = hashlib.md5()
        with open(sys.modules[self.__module__].__file__, 'rb') as afile:
            buf = afile.read()
            hasher.update(buf)
        self.config_hash = hasher.hexdigest()
        self.config_folder += self.config_hash
        # make config folder if it doesn't exist
        os_utils.makedirs(self.config_folder)

        # set up our joint angle symbols
        self.q = [sp.Symbol('q%i' % ii) for ii in range(self.N_JOINTS)]
        self.dq = [sp.Symbol('dq%i' % ii) for ii in range(self.N_JOINTS)]
        # set up an (x,y,z) offset
        self.x = [sp.Symbol('x'), sp.Symbol('y'), sp.Symbol('z')]

        self.gravity = sp.Matrix([[0, 0, -9.81, 0, 0, 0]]).T
Exemple #5
0
    def _calc_M(self, lambdify=True):
        """ Uses Sympy to generate the inertia matrix in joint space

        Parameters
        ----------
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        M = None
        M_func = None

        # check to see if we have our inertia matrix saved in file
        M, M_func = self._load_from_file('M', lambdify)

        if M is None and M_func is None:
            # if no saved file was loaded, generate function
            print('Generating inertia matrix function')

            # get the Jacobians for each link's COM
            J_links = [
                self._calc_J('link%s' % ii, x=[0, 0, 0], lambdify=False)
                for ii in range(self.N_LINKS)
            ]
            J_joints = [
                self._calc_J('joint%s' % ii, x=[0, 0, 0], lambdify=False)
                for ii in range(self.N_JOINTS)
            ]

            # sum together the effects of each arm segment's inertia
            M = sp.zeros(self.N_JOINTS)
            for ii in range(self.N_LINKS):
                # transform each inertia matrix into joint space
                M += (J_links[ii].T * self._M_LINKS[ii] * J_links[ii])
            # sum together the effects of each joint's inertia on each motor
            for ii in range(self.N_JOINTS):
                # transform each inertia matrix into joint space
                M += (J_joints[ii].T * self._M_JOINTS[ii] * J_joints[ii])
            M = sp.Matrix(M)

            # save to file
            os_utils.makedirs('%s/M' % (self.config_folder))
            cloudpickle.dump(M, open('%s/M/M' % self.config_folder, 'wb'))

        if lambdify is False:
            # if should return expression not function
            return M

        if M_func is None:
            M_func = self._generate_and_save_function(filename='M',
                                                      expression=M,
                                                      parameters=self.q)
        return M_func
Exemple #6
0
    def _calc_g(self, lambdify=True):
        """ Generate the force of gravity in joint space

        Uses Sympy to generate the force of gravity in joint space

        Parameters
        ----------
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """
        g = None
        g_func = None
        # check to see if we have our gravity term saved in file
        g, g_func = self._load_from_file('g', lambdify)

        if g is None and g_func is None:
            # if no saved file was loaded, generate function
            print('Generating gravity compensation function')

            # get the Jacobians for each link's COM
            J_links = [
                self._calc_J('link%s' % ii, x=[0, 0, 0], lambdify=False)
                for ii in range(self.N_LINKS)
            ]
            J_joints = [
                self._calc_J('joint%s' % ii, x=[0, 0, 0], lambdify=False)
                for ii in range(self.N_JOINTS)
            ]

            # sum together the effects of each arm segment's inertia
            g = sp.zeros(self.N_JOINTS, 1)
            for ii in range(self.N_LINKS):
                # transform each inertia matrix into joint space
                g += (J_links[ii].T * self._M_LINKS[ii] * self.gravity)
            # sum together the effects of each joint's inertia on each motor
            for ii in range(self.N_JOINTS):
                # transform each inertia matrix into joint space
                g += (J_joints[ii].T * self._M_JOINTS[ii] * self.gravity)
            g = sp.Matrix(g)

            # save to file
            os_utils.makedirs('%s/g' % self.config_folder)
            cloudpickle.dump(g, open('%s/g/g' % self.config_folder, 'wb'))

        if lambdify is False:
            # if should return expression not function
            return g

        if g_func is None:
            g_func = self._generate_and_save_function(filename='g',
                                                      expression=g,
                                                      parameters=self.q)
        return g_func
Exemple #7
0
    def _calc_S(self, lambdify=True):
        """ Uses Sympy to generate the centrifugal and Coriolis forces
        Derivation from vector format 2 on slide 22 at:
        www.diag.uniroma1.it/~deluca/rob2_en/03_LagrangianDynamics_1.pdf
        NOTE: the full effects are calculated in the _calc_c method

        Parameters
        ----------
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        S = None
        S_func = None
        # check to see if we have our term saved in file
        S, S_func = self._load_from_file('S', lambdify)

        if S is None and S_func is None:
            # if no saved file was loaded, generate function
            print('Generating centripetal and Coriolis compensation function')

            # first get the inertia matrix
            M = self._calc_M(lambdify=False)
            # C_k = .5 * (\frac{\partial m_k}{\partial q} +
            #           \frac{\partial m_k}{\partial q}^T +
            #           \frac{\partial M}{\partia q_k})
            # S_kj = sum_i (C_{kij}(q) * dq[i])
            # where c_k and m_k are the kth element of c and column of M
            S = sp.zeros(self.N_JOINTS, self.N_JOINTS)
            for kk in range(self.N_JOINTS):
                dMkdq = M[:, kk].jacobian(sp.Matrix(self.q))
                Ck = 0.5 * (dMkdq + dMkdq.T - M.diff(self.q[kk]))
                for jj in range(self.N_JOINTS):
                    S[kk] = np.sum([
                        Ck[ii, jj] * self.dq[ii] for ii in range(self.N_JOINTS)
                    ])
            S = sp.Matrix(S)

            # save to file
            os_utils.makedirs('%s/S' % self.config_folder)
            cloudpickle.dump(S, open('%s/S/S' % self.config_folder, 'wb'))

        if lambdify is False:
            # if should return expression not function
            return S

        if S_func is None:
            S_func = self._generate_and_save_function(filename='S',
                                                      expression=S,
                                                      parameters=self.q +
                                                      self.dq)
        return S_func
Exemple #8
0
    def _calc_T_inv(self, name, x, lambdify=True):
        """ Return the inverse transform matrix

        Return the inverse transform matrix, which converts from
        world coordinates into the robot's end-effector reference frame

        Parameters
        ----------
        name : string
            name of the joint, link, or end-effector
        x : numpy.array
            the [x,y,z] offset inside the reference frame of 'name' [meters]
            if not specified, (0, 0, 0) is hard coded in, rather than using
            variable (x, y, z), which results in significant speedups.
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        T_inv = None
        T_inv_func = None
        filename = name + '[0,0,0]' if np.allclose(x, 0) else name
        filename += '_Tinv'
        # check to see if we have our transformation saved in file
        T_inv, T_inv_func = self._load_from_file(filename, lambdify)

        if T_inv is None and T_inv_func is None:
            print('Generating inverse transform function for %s' % filename)
            T = self._calc_T(name=name)
            rotation_inv = T[:3, :3].T
            translation_inv = -rotation_inv * T[:3, 3]
            T_inv = rotation_inv.row_join(translation_inv).col_join(
                sp.Matrix([[0, 0, 0, 1]]))
            T_inv = sp.Matrix(T_inv)

            # save to file
            os_utils.makedirs('%s/%s' % (self.config_folder, filename))
            cloudpickle.dump(
                T_inv,
                open('%s/%s.T_inv' % (self.config_folder, filename), 'wb'))

        if lambdify is False:
            # if should return expression not function
            return T_inv

        if T_inv_func is None:
            T_inv_func = self._generate_and_save_function(filename=filename,
                                                          expression=T_inv,
                                                          parameters=self.q +
                                                          self.x)
        return T_inv_func
Exemple #9
0
    def _calc_c(self, lambdify=True):
        """ Uses Sympy to generate the centrifugal and Coriolis forces
        Derivation from vector form 1 on slide 22 at:
        www.diag.uniroma1.it/~deluca/rob2_en/03_LagrangianDynamics_1.pdf
        NOTE: the partial effects are calculated in the _calc_S method

        Parameters
        ----------
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        c = None
        c_func = None
        # check to see if we have our term saved in file
        c, c_func = self._load_from_file('c', lambdify)

        if c is None and c_func is None:
            # if no saved file was loaded, generate function
            print('Generating centripetal and Coriolis compensation function')

            # first get the inertia matrix
            M = self._calc_M(lambdify=False)
            # c_k = dq.T * C_k * dq
            # C_k = .5 * (\frac{\partial m_k}{\partial q} +
            #           \frac{\partial m_k}{\partial q}^T +
            #           \frac{\partial M}{\partia q_k})
            # where c_k and m_k are the kth element of c and column of M
            c = sp.zeros(self.N_JOINTS, 1)
            for kk in range(self.N_JOINTS):
                dMkdq = M[:, kk].jacobian(sp.Matrix(self.q))
                Ck = 0.5 * (dMkdq + dMkdq.T - M.diff(self.q[kk]))
                c[kk] = sp.Matrix(self.dq).T * Ck * sp.Matrix(self.dq)
            c = sp.Matrix(c)

            # save to file
            os_utils.makedirs('%s/c' % self.config_folder)
            cloudpickle.dump(c, open('%s/c/c' % self.config_folder, 'wb'))

        if lambdify is False:
            # if should return expression not function
            return c

        if c_func is None:
            c_func = self._generate_and_save_function(filename='c',
                                                      expression=c,
                                                      parameters=self.q +
                                                      self.dq)
        return c_func
Exemple #10
0
    def _generate_and_save_function(self, filename, expression, parameters):
        """ Creates a folder, saves generated cython functions

        Create a folder in the users cache directory, named based on a hash
        of the current robot_config subclass.

        If use_cython is True, uses the created folder to save the autowrap
        binaries, so that they can be loaded in quickly later.
        """

        # check for / create the save folder for this expression
        folder = self.config_folder + '/' + filename
        os_utils.makedirs(folder)

        if self.use_cython is True:
            # binaries saved by specifying tempdir parameter
            function = autowrap(expression,
                                backend="cython",
                                args=parameters,
                                tempdir=folder)
        function = sp.lambdify(parameters, expression, "numpy")

        return function
Exemple #11
0
    def _calc_J(self, name, x, lambdify=True):
        """ Uses Sympy to generate the Jacobian for a joint or link

        Parameters
        ----------
        name : string
            name of the joint, link, or end-effector
        x : numpy.array
            the [x,y,z] offset inside the reference frame of 'name' [meters]
            if not specified, (0, 0, 0) is hard coded in, rather than using
            variable (x, y, z), which results in significant speedups.
        lambdify : boolean, optional (Default: True)
            if True returns a function to calculate the matrix.
            If False returns the Sympy matrix
        """

        J = None
        J_func = None
        filename = name + '[0,0,0]' if np.allclose(x, 0) else name
        filename += '_J'

        # check to see if should try to load functions from file
        J, J_func = self._load_from_file(filename, lambdify)

        if J is None and J_func is None:
            # if no saved file was loaded, generate function
            print('Generating Jacobian function for %s' % filename)

            Tx = self._calc_Tx(name, x=x, lambdify=False)
            # NOTE: calculating the Jacobian this way doesn't incur any
            # real computational cost (maybe 30ms) and it simplifies adding
            # the orientation information below (as opposed to using
            # sympy's Tx.jacobian method)
            J = []
            # calculate derivative of (x,y,z) wrt to each joint
            for ii in range(self.N_JOINTS):
                J.append([])
                J[ii].append(Tx[0].diff(self.q[ii]))  # dx/dq[ii]
                J[ii].append(Tx[1].diff(self.q[ii]))  # dy/dq[ii]
                J[ii].append(Tx[2].diff(self.q[ii]))  # dz/dq[ii]

            end_point = name.strip('link').strip('joint')
            end_point = self.N_JOINTS if 'EE' in end_point else end_point

            end_point = min(int(end_point) + 1, self.N_JOINTS)
            # add on the orientation information up to the last joint
            for ii in range(end_point):
                J[ii] = J[ii] + list(self.J_orientation[ii])
            # fill in the rest of the joints orientation info with 0
            for ii in range(end_point, self.N_JOINTS):
                J[ii] = J[ii] + [0, 0, 0]
            J = sp.Matrix(J).T  # correct the orientation of J

            # save to file
            os_utils.makedirs('%s/%s' % (self.config_folder, filename))
            cloudpickle.dump(
                J,
                open('%s/%s/%s' % (self.config_folder, filename, filename),
                     'wb'))

        if lambdify is False:
            # if should return expression not function
            return J

        if J_func is None:
            J_func = self._generate_and_save_function(filename=filename,
                                                      expression=J,
                                                      parameters=self.q +
                                                      self.x)
        return J_func
Exemple #12
0
    def __init__(self, hand_attached=False, **kwargs):

        self.hand_attached = hand_attached
        N_LINKS = 7 if hand_attached is True else 6
        super(Config, self).__init__(N_JOINTS=6,
                                     N_LINKS=N_LINKS,
                                     ROBOT_NAME='jaco2',
                                     **kwargs)

        if self.MEANS is None:
            self.MEANS = {  # expected mean of joint angles / velocities
                'q':
                np.ones(self.N_JOINTS) * np.pi,
                'dq':
                np.array(
                    [-0.01337, 0.00192, 0.00324, 0.02502, -0.02226, -0.01342])
            }

        if self.SCALES is None:
            self.SCALES = {  # expected variance of joint angles / velocities
                'q':
                np.ones(self.N_JOINTS) * np.pi * np.sqrt(self.N_JOINTS),
                'dq':
                (np.array([1.22826, 2.0, 1.42348, 2.58221, 2.50768, 1.27004]) *
                 np.sqrt(self.N_JOINTS))
            }

        self._T = {}  # dictionary for storing calculated transforms

        # set up saved functions folder to be in the abr_jaco repo
        self.config_folder += ('with_hand'
                               if self.hand_attached is True else 'no_hand')
        # make config folder if it doesn't exist
        os_utils.makedirs(self.config_folder)

        self.JOINT_NAMES = ['joint%i' % ii for ii in range(self.N_JOINTS)]

        self.REST_ANGLES = np.array([None, 2.42, 2.42, 0.0, 0.0, 0.0],
                                    dtype='float32')

        # inertia values in VREP are divided by mass, account for that here
        self._M_LINKS = [
            sp.diag(0.5, 0.5, 0.5, 0.02, 0.02, 0.02),  # link0
            sp.diag(0.5, 0.5, 0.5, 0.02, 0.02, 0.02),  # link1
            sp.diag(0.5, 0.5, 0.5, 0.02, 0.02, 0.02),  # link2
            sp.diag(0.5, 0.5, 0.5, 0.02, 0.02, 0.02),  # link3
            sp.diag(0.5, 0.5, 0.5, 0.02, 0.02, 0.02),  # link3
            sp.diag(0.5, 0.5, 0.5, 0.02, 0.02, 0.02),  # link4
            sp.diag(0.25, 0.25, 0.25, 0.01, 0.01, 0.01)
        ]  # link5
        if self.hand_attached is True:
            self._M_LINKS.append(sp.diag(0.37, 0.37, 0.37, 0.04, 0.04,
                                         0.04))  # link6

        # the joints don't weigh anything in VREP
        self._M_JOINTS = [sp.zeros(6, 6) for ii in range(self.N_JOINTS)]

        # ignoring lengths < 1e-6
        self.L = [
            [0.0, 0.0, 7.8369e-02],  # link 0 offset
            [-3.2712e-05, -1.7324e-05, 7.8381e-02],  # joint 0 offset
            [2.1217e-05, 4.8455e-05, -7.9515e-02],  # link 1 offset
            [-2.2042e-05, 1.3245e-04, -3.8863e-02],  # joint 1 offset
            [-1.9519e-03, 2.0902e-01, -2.8839e-02],  # link 2 offset
            [-2.3094e-02, -1.0980e-06, 2.0503e-01],  # joint 2 offset
            [-4.8786e-04, -8.1945e-02, -1.2931e-02],  # link 3 offset
            [2.5923e-04, -3.8935e-03, -1.2393e-01],  # joint 3 offset
            [-4.0053e-04, 1.2581e-02, -3.5270e-02],  # link 4 offset
            [-2.3603e-03, -4.8662e-03, 3.7097e-02],  # joint 4 offset
            [-5.2974e-04, 1.2272e-02, -3.5485e-02],  # link 5 offset
            [-1.9534e-03, 5.0298e-03, -3.7176e-02]
        ]  # joint 5 offset
        if self.hand_attached is True:  # add in hand offset
            self.L.append([0.0, 0.0, 0.0])  # offset for the end of fingers
        self.L = np.array(self.L)

        if self.hand_attached is True:  # add in hand offset
            self.L_HANDCOM = np.array([0.0, 0.0, -0.08])  # com of the hand

        # ---- Transform Matrices ----

        # Transform matrix : origin -> link 0
        # no change of axes, account for offsets
        self.Torgl0 = sp.Matrix([[1, 0, 0, self.L[0, 0]],
                                 [0, 1, 0, self.L[0, 1]],
                                 [0, 0, 1, self.L[0, 2]], [0, 0, 0, 1]])

        # Transform matrix : link0 -> joint 0
        # account for change of axes and offsets
        self.Tl0j0 = sp.Matrix([[1, 0, 0, self.L[1, 0]],
                                [0, -1, 0, self.L[1, 1]],
                                [0, 0, -1, self.L[1, 2]], [0, 0, 0, 1]])

        # Transform matrix : joint 0 -> link 1
        # account for rotations due to q
        self.Tj0l1a = sp.Matrix([[sp.cos(self.q[0]), -sp.sin(self.q[0]), 0, 0],
                                 [sp.sin(self.q[0]),
                                  sp.cos(self.q[0]), 0, 0], [0, 0, 1, 0],
                                 [0, 0, 0, 1]])
        # account for change of axes and offsets
        self.Tj0l1b = sp.Matrix([[-1, 0, 0, self.L[2, 0]],
                                 [0, -1, 0, self.L[2, 1]],
                                 [0, 0, 1, self.L[2, 2]], [0, 0, 0, 1]])
        self.Tj0l1 = self.Tj0l1a * self.Tj0l1b

        # Transform matrix : link 1 -> joint 1
        # account for axes rotation and offset
        self.Tl1j1 = sp.Matrix([[1, 0, 0, self.L[3, 0]],
                                [0, 0, -1, self.L[3, 1]],
                                [0, 1, 0, self.L[3, 2]], [0, 0, 0, 1]])

        # Transform matrix : joint 1 -> link 2
        # account for rotations due to q
        self.Tj1l2a = sp.Matrix([[sp.cos(self.q[1]), -sp.sin(self.q[1]), 0, 0],
                                 [sp.sin(self.q[1]),
                                  sp.cos(self.q[1]), 0, 0], [0, 0, 1, 0],
                                 [0, 0, 0, 1]])
        # account for axes rotation and offsets
        self.Tj1l2b = sp.Matrix([[0, -1, 0, self.L[4, 0]],
                                 [0, 0, 1, self.L[4, 1]],
                                 [-1, 0, 0, self.L[4, 2]], [0, 0, 0, 1]])
        self.Tj1l2 = self.Tj1l2a * self.Tj1l2b

        # Transform matrix : link 2 -> joint 2
        # account for axes rotation and offsets
        self.Tl2j2 = sp.Matrix([[0, 0, 1, self.L[5,
                                                 0]], [1, 0, 0, self.L[5, 1]],
                                [0, 1, 0, self.L[5, 2]], [0, 0, 0, 1]])

        # Transform matrix : joint 2 -> link 3
        # account for rotations due to q
        self.Tj2l3a = sp.Matrix([[sp.cos(self.q[2]), -sp.sin(self.q[2]), 0, 0],
                                 [sp.sin(self.q[2]),
                                  sp.cos(self.q[2]), 0, 0], [0, 0, 1, 0],
                                 [0, 0, 0, 1]])
        # account for axes rotation and offsets
        self.Tj2l3b = sp.Matrix([[0.14262926, -0.98977618, 0, self.L[6, 0]],
                                 [0, 0, 1, self.L[6, 1]],
                                 [-0.98977618, -0.14262926, 0, self.L[6, 2]],
                                 [0, 0, 0, 1]])
        self.Tj2l3 = self.Tj2l3a * self.Tj2l3b

        # Transform matrix : link 3 -> joint 3
        # account for axes change and offsets
        self.Tl3j3 = sp.Matrix([[-0.14262861, -0.98977628, 0, self.L[7, 0]],
                                [0.98977628, -0.14262861, 0, self.L[7, 1]],
                                [0, 0, 1, self.L[7, 2]], [0, 0, 0, 1]])

        # Transform matrix: joint 3 -> link 4
        # account for rotations due to q
        self.Tj3l4a = sp.Matrix([[sp.cos(self.q[3]), -sp.sin(self.q[3]), 0, 0],
                                 [sp.sin(self.q[3]),
                                  sp.cos(self.q[3]), 0, 0], [0, 0, 1, 0],
                                 [0, 0, 0, 1]])
        # account for axes and rotation and offsets
        self.Tj3l4b = sp.Matrix(
            [[0.85536427, -0.51802699, 0, self.L[8, 0]],
             [-0.45991232, -0.75940555, 0.46019982, self.L[8, 1]],
             [-0.23839593, -0.39363848, -0.88781537, self.L[8, 2]],
             [0, 0, 0, 1]])
        self.Tj3l4 = self.Tj3l4a * self.Tj3l4b

        # Transform matrix: link 4 -> joint 4
        # no axes change, account for offsets
        self.Tl4j4 = sp.Matrix(
            [[-0.855753802, 0.458851168, 0.239041914, self.L[9, 0]],
             [0.517383113, 0.758601438, 0.396028500, self.L[9, 1]],
             [0, 0.462579144, -0.886577910, self.L[9, 2]], [0, 0, 0, 1]])

        # Transform matrix: joint 4 -> link 5
        # account for rotations due to q
        self.Tj4l5a = sp.Matrix([[sp.cos(self.q[4]), -sp.sin(self.q[4]), 0, 0],
                                 [sp.sin(self.q[4]),
                                  sp.cos(self.q[4]), 0, 0], [0, 0, 1, 0],
                                 [0, 0, 0, 1]])
        # account for axes and rotation and offsets
        # no axes change, account for offsets
        self.Tj4l5b = sp.Matrix(
            [[0.89059413, 0.45479896, 0, self.L[10, 0]],
             [-0.40329059, 0.78972966, -0.46225942, self.L[10, 1]],
             [-0.2102351, 0.41168552, 0.88674474, self.L[10, 2]], [0, 0, 0,
                                                                   1]])
        self.Tj4l5 = self.Tj4l5a * self.Tj4l5b

        # Transform matrix : link 5 -> joint 5
        # account for axes change and offsets
        self.Tl5j5 = sp.Matrix(
            [[-0.890598824, 0.403618758, 0.209584432, self.L[11, 0]],
             [-0.454789710, -0.790154512, -0.410879747, self.L[11, 1]],
             [0, -0.461245863, 0.887272337, self.L[11, 2]], [0, 0, 0, 1]])

        if self.hand_attached is True:  # add in hand offset
            # Transform matrix: joint 5 -> link 6 / hand COM
            # account for rotations due to q
            self.Tj5handcoma = sp.Matrix(
                [[sp.cos(self.q[5]), -sp.sin(self.q[5]), 0, 0],
                 [sp.sin(self.q[5]),
                  sp.cos(self.q[5]), 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
            # account for axes changes and offsets
            self.Tj5handcomb = sp.Matrix([[-1, 0, 0, self.L_HANDCOM[0]],
                                          [0, 1, 0, self.L_HANDCOM[1]],
                                          [0, 0, -1, self.L_HANDCOM[2]],
                                          [0, 0, 0, 1]])
            self.Tj5handcom = self.Tj5handcoma * self.Tj5handcomb

            # no axes change, account for offsets
            self.Thandcomfingers = sp.Matrix([[1, 0, 0, self.L[12, 0]],
                                              [0, 1, 0, self.L[12, 1]],
                                              [0, 0, 1, self.L[12, 2]],
                                              [0, 0, 0, 1]])

        # orientation part of the Jacobian (compensating for angular velocity)
        self.J_orientation = [
            self._calc_T('joint0')[:3, :3] * self._KZ,  # joint 0 orientation
            self._calc_T('joint1')[:3, :3] * self._KZ,  # joint 1 orientation
            self._calc_T('joint2')[:3, :3] * self._KZ,  # joint 2 orientation
            self._calc_T('joint3')[:3, :3] * self._KZ,  # joint 3 orientation
            self._calc_T('joint4')[:3, :3] * self._KZ,  # joint 4 orientation
            self._calc_T('joint5')[:3, :3] * self._KZ
        ]  # joint 5 orientation