#!/usr/bin/env python3
"""
@author: Jesse Haviland
@author: Peter Corke
"""
from collections import UserList
import numpy as np
from numpy.random import uniform
from numpy.linalg import inv, det, cond, svd
from spatialmath import SE3, SE2
from spatialmath.base import (
    getvector,
    issymbol,
    tr2jac,
    verifymatrix,
    tr2jac2,
    t2r,
    rotvelxform,
    simplify,
    getmatrix,
)
from roboticstoolbox import rtb_get_param
from roboticstoolbox.robot.IK import IK_GN, IK_LM, IK_NR, IK_QP
from roboticstoolbox.fknm import (
    ETS_init,
    ETS_fkine,
    ETS_jacob0,
    ETS_jacobe,
    ETS_hessian0,
    ETS_hessiane,
    IK_NR_c,
    IK_GN_c,
    IK_LM_c,
)
from copy import deepcopy
from roboticstoolbox.robot.ET import ET, ET2
from typing import Union, overload, List, Set, Tuple
from typing_extensions import Literal as L
from sys import version_info
from roboticstoolbox.tools.types import ArrayLike, NDArray
py_ver = version_info
if version_info >= (3, 9):
    from functools import cached_property
    c_property = cached_property
else:  # pragma: nocover
    c_property = property
class BaseETS(UserList):
    def __init__(self, *args):
        super().__init__(*args)
    def _update_internals(self):
        self._m = len(self.data)
        self._n = len([True for et in self.data if et.isjoint])
        self._fknm = ETS_init(
            [et.fknm for et in self.data],
            self._n,
            self._m,
        )
        # self._fknm = [et.fknm for et in self.data]
    def __str__(self, q: Union[str, None] = None):
        """
        Pretty prints the ETS
        ``q`` controls how the joint variables are displayed:
        - None, format depends on number of joint variables
            - one, display joint variable as q
            - more, display joint variables as q0, q1, ...
            - if a joint index was provided, use this value
        - "", display all joint variables as empty parentheses ``()``
        - "θ", display all joint variables as ``(θ)``
        - format string with passed joint variables ``(j, j+1)``, so "θ{0}"
          would display joint variables as θ0, θ1, ... while "θ{1}" would
          display joint variables as θ1, θ2, ...  ``j`` is either the joint
          index, if provided, otherwise a sequential value.
        Parameters
        ----------
        q
            control how joint variables are displayed
        Returns
        -------
        str
            Pretty printed ETS
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz() * ET.tx(1) * ET.Rz()
        >>> print(e[:2])
        >>> print(e)
        >>> print(e.__str__(""))
        >>> print(e.__str__("θ{0}"))  # numbering from 0
        >>> print(e.__str__("θ{1}"))  # numbering from 1
        >>> # explicit joint indices
        >>> e = ET.Rz(jindex=3) * ET.tx(1) * ET.Rz(jindex=4)
        >>> print(e)
        >>> print(e.__str__("θ{0}"))
        Angular parameters are converted to degrees, except if they
        are symbolic.
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> from spatialmath.base import symbol
        >>> theta, d = symbol('theta, d')
        >>> e = ET.Rx(theta) * ET.tx(2) * ET.Rx(45, 'deg') * ET.Ry(0.2) * ET.ty(d)
        >>> str(e)
        """
        es = []
        j = 0
        c = 0
        s = None
        unicode = rtb_get_param("unicode")
        # An empty SE3
        if len(self.data) == 0:
            return "SE3()"
        if q is None:
            if len(self.joints()) > 1:
                q = "q{0}"
            else:
                q = "q"
        # For et in the object, display it, data comes from properties
        # which come from the named tuple
        for et in self.data:
            if et.isjoint:
                if q is not None:
                    if et.jindex is None:  # pragma: nocover  this is no longer possible
                        _j = j
                    else:
                        _j = et.jindex
                    qvar = q.format(  # lgtm [py/str-format/surplus-argument]  # noqa
                        _j, _j + 1
                    )
                # else:
                #     qvar = ""
                if et.isflip:
                    s = f"{et.axis}(-{qvar})"
                else:
                    s = f"{et.axis}({qvar})"
                j += 1
            elif et.isrotation:
                if issymbol(et.eta):
                    s = f"{et.axis}({et.eta})"
                else:
                    s = f"{et.axis}({et.eta * 180 / np.pi:.4g}°)"
            elif et.istranslation:
                try:
                    s = f"{et.axis}({et.eta:.4g})"
                except TypeError:  # pragma: nocover
                    s = f"{et.axis}({et.eta})"
            elif not et.iselementary:
                s = str(et)
                c += 1
            es.append(s)
        if unicode:
            return " \u2295 ".join(es)
        else:  # pragma: nocover
            return " * ".join(es)
    def _repr_pretty_(self, p, cycle):
        """
        Pretty string for IPython
        Print stringified version when variable is displayed in IPython, ie. on
        a line by itself.
        Parameters
        ----------
        p
            pretty printer handle (ignored)
        cycle
            pretty printer flag (ignored)
        Examples
        --------
        In [1]: e
        Out [1]: R(q0) ⊕ tx(1) ⊕ R(q1) ⊕ tx(1)
        """
        print(self.__str__())  # pragma: nocover
    def joint_idx(self) -> List[int]:
        """
        Get index of joint transforms
        Returns
        -------
        joint_idx
            indices of transforms that are joints
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1)
        >>> e.joint_idx()
        """
        return np.where([e.isjoint for e in self])[0]  # type: ignore
    def joints(self) -> List[ET]:
        """
        Get a list of the variable ETs with this ETS
        Returns
        -------
        joints
            list of ETs that are joints
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1)
        >>> e.joints()
        """
        return [e for e in self if e.isjoint]
    def jindex_set(self) -> Set[int]:  #
        """
        Get set of joint indices
        Returns
        -------
        jindex_set
            set of unique joint indices
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz(jindex=1) * ET.tx(jindex=2) * ET.Rz(jindex=1) * ET.tx(1)
        >>> e.jointset()
        """
        return set([self[j].jindex for j in self.joint_idx()])  # type: ignore
    @c_property
    def jindices(self) -> NDArray:
        """
        Get an array of joint indices
        Returns
        -------
        jindices
            array of unique joint indices
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz(jindex=1) * ET.tx(jindex=2) * ET.Rz(jindex=1) * ET.tx(1)
        >>> e.jointset()
        """
        return np.array([j.jindex for j in self.joints()])  # type: ignore
    @property
    def qlim(self):
        r"""
        Get/Set Joint limits
        Limits are extracted from the link objects.  If joints limits are
        not set for:
        - a revolute joint [-𝜋. 𝜋] is returned
        - a prismatic joint an exception is raised
        Parameters
        ----------
        new_qlim
            An ndarray(2, n) of the new joint limits to set
        Returns
        -------
        :return: Array of joint limit values
        :rtype: ndarray(2,n)
        Raises
        ------
        ValueError
            unset limits for a prismatic joint
        Examples
        --------
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> robot = rtb.models.DH.Puma560()
        >>> robot.qlim
        """
        limits = np.zeros((2, self.n))
        for i, et in enumerate(self.joints()):
            if et.isrotation:
                if et.qlim is None:
                    v = [-np.pi, np.pi]
                else:
                    v = et.qlim
            elif et.istranslation:
                if et.qlim is None:
                    raise ValueError("undefined prismatic joint limit")
                else:
                    v = et.qlim
            else:
                raise ValueError("Undefined Joint Type")  # pragma: nocover
            limits[:, i] = v
        return limits
    @qlim.setter
    def qlim(self, new_qlim: ArrayLike):
        new_qlim = np.array(new_qlim)
        if new_qlim.shape == (2,) and self.n == 1:
            new_qlim = new_qlim.reshape(2, 1)
        if new_qlim.shape != (2, self.n):
            raise ValueError("new_qlim must be of shape (2, n)")
        for j, i in enumerate(self.joint_idx()):
            et = self[i]
            et.qlim = new_qlim[:, j]
            self[i] = et
        self._update_internals()
    @property
    def structure(self) -> str:
        """
        Joint structure string
        A string comprising the characters 'R' or 'P' which indicate the types
        of joints in order from left to right.
        Returns
        -------
        structure
            A string indicating the joint types
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.tz() * ET.tx(1) * ET.Rz() * ET.tx(1)
        >>> e.structure
        """
        return "".join(
            ["R" if self.data[i].isrotation else "P" for i in self.joint_idx()]
        )
    @property
    def n(self) -> int:
        """
        Number of joints
        Counts the number of joints in the ETS.
        Returns
        -------
        n
            the number of joints in the ETS
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rx() * ET.tx(1) * ET.tz()
        >>> e.n
        See Also
        --------
        :func:`joints`
        """
        return self._n
    @property
    def m(self) -> int:
        """
        Number of transforms
        Counts the number of transforms in the ETS.
        Returns
        -------
        m
            the number of transforms in the ETS
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rx() * ET.tx(1) * ET.tz()
        >>> e.m
        """
        return self._m
    @overload
    def data(self: "ETS") -> List[ET]:
        ...  # pragma: nocover
    @overload
    def data(self: "ETS2") -> List[ET2]:
        ...  # pragma: nocover
    @property
    def data(self):
        return self._data
    @data.setter
    @overload
    def data(self: "ETS", new_data: List[ET]):
        ...  # pragma: nocover
    @data.setter
    @overload
    def data(self: "ETS", new_data: List[ET2]):
        ...  # pragma: nocover
    @data.setter
    def data(self, new_data):
        self._data = new_data
    @overload
    def pop(self: "ETS", i: int = -1) -> ET:
        ...  # pragma: nocover
    @overload
    def pop(self: "ETS2", i: int = -1) -> ET2:
        ...  # pragma: nocover
    def pop(self, i=-1):
        """
        Pop value
        Removes a value from the value list and returns it.  The original
        instance is modified.
        Parameters
        ----------
        i
            item in the list to pop, default is last
        Returns
        -------
        pop
            the popped value
        Raises
        ------
        IndexError
            if there are no values to pop
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1)
        >>> tail = e.pop()
        >>> tail
        >>> e
        """
        item = super().pop(i)
        self._update_internals()
        return item
    @overload
    def split(self: "ETS") -> List["ETS"]:
        ...  # pragma: nocover
    @overload
    def split(self: "ETS2") -> List["ETS2"]:
        ...  # pragma: nocover
    def split(self):
        """
        Split ETS into link segments
        Returns
        -------
        split
            a list of ETS, each one, apart from the last,
            ends with a variable ET.
        """
        segments = []
        start = 0
        for j, k in enumerate(self.joint_idx()):
            ets_j = self.data[start : k + 1]
            start = k + 1
            segments.append(ets_j)
        tail = self.data[start:]
        if isinstance(tail, list):
            tail_len = len(tail)
        elif tail is not None:  # pragma: nocover
            tail_len = 1
        else:  # pragma: nocover
            tail_len = 0
        if tail_len > 0:
            segments.append(tail)
        return segments
    @overload
    def inv(self: "ETS") -> "ETS":
        ...  # pragma: nocover
    @overload
    def inv(self: "ETS2") -> "ETS2":
        ...  # pragma: nocover
    def inv(self):
        r"""
        Inverse of ETS
        The inverse of a given ETS.  It is computed as the inverse of the
        individual ETs in the reverse order.
        .. math::
            (\mathbf{E}_0, \mathbf{E}_1 \cdots \mathbf{E}_{n-1} )^{-1} = (\mathbf{E}_{n-1}^{-1}, \mathbf{E}_{n-2}^{-1} \cdots \mathbf{E}_0^{-1}{n-1} )
        Returns
        -------
        inv
            Inverse of the ETS
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz(jindex=2) * ET.tx(1) * ET.Rx(jindex=3,flip=True) * ET.tx(1)
        >>> print(e)
        >>> print(e.inv())
        Notes
        -----
        - It is essential to use explicit joint indices to account for
            the reversed order of the transforms.
        """  # noqa
        return self.__class__([et.inv() for et in reversed(self.data)])
    @overload
    def __getitem__(self: "ETS", i: int) -> ET:
        ...  # pragma: nocover
    @overload
    def __getitem__(self: "ETS", i: slice) -> List[ET]:
        ...  # pragma: nocover
    @overload
    def __getitem__(self: "ETS2", i: int) -> ET2:
        ...  # pragma: nocover
    @overload
    def __getitem__(self: "ETS2", i: slice) -> List[ET2]:
        ...  # pragma: nocover
    def __getitem__(self, i):
        """
        Index or slice an ETS
        Parameters
        ----------
        i
            the index or slince
        Returns
        -------
        et
            Elementary transform
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1)
        >>> e[0]
        >>> e[1]
        >>> e[1:3]
        """
        return self.data[i]  # can be [2] or slice, eg. [3:5]
    def __deepcopy__(self, memo):
        new_data = []
        for data in self:
            new_data.append(deepcopy(data))
        cls = self.__class__
        result = cls(new_data)
        memo[id(self)] = result
        return result
    def plot(self, *args, **kwargs):
        from roboticstoolbox.robot.Robot import Robot, Robot2
        if isinstance(self, ETS):
            robot = Robot(self)
        else:
            robot = Robot2(self)
        robot.plot(*args, **kwargs)
    def teach(self, *args, **kwargs):
        from roboticstoolbox.robot.Robot import Robot, Robot2
        if isinstance(self, ETS):
            robot = Robot(self)
        else:
            robot = Robot2(self)
        robot.teach(*args, **kwargs)
    def random_q(self, i: int = 1) -> NDArray:
        """
        Generate a random valid joint configuration
        Generates a random q vector within the joint limits defined by
        `self.qlim`.
        Parameters
        ----------
        i
            number of configurations to generate
        Examples
        --------
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> robot = rtb.models.Panda()
        >>> ets = robot.ets()
        >>> q = ets.random_q()
        >>> q
        """
        if i == 1:
            q = np.zeros(self.n)
            for i in range(self.n):
                q[i] = uniform(self.qlim[0, i], self.qlim[1, i])
        else:
            q = np.zeros((i, self.n))
            for j in range(i):
                for i in range(self.n):
                    q[j, i] = uniform(self.qlim[0, i], self.qlim[1, i])
        return q
[docs]class ETS(BaseETS):
    """
    This class implements an elementary transform sequence (ETS) for 3D
    An instance can contain an elementary transform (ET) or an elementary
    transform sequence (ETS). It has list-like properties by subclassing
    UserList, which means we can perform indexing, slicing pop, insert, as well
    as using it as an iterator over its values.
    - ``ETS()`` an empty ETS list
    - ``ETS(et)`` an ETS containing a single ET
    - ``ETS([et0, et1, et2])`` an ETS consisting of three ET's
    Parameters
    ----------
    arg
        Function to compute ET value
    Examples
    --------
    .. runblock:: pycon
    >>> from roboticstoolbox import ETS, ET
    >>> e = ET.Rz(0.3) # a single ET, rotation about z
    >>> ets1 = ETS(e)
    >>> len(ets1)
    >>> ets2 = ET.Rz(0.3) * ET.tx(2) # an ETS
    >>> len(ets2)                    # of length 2
    >>> ets2[1]                      # an ET sliced from the ETS
    References
    ----------
    - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
        Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
    - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
        Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
    See Also
    --------
    :func:`rx`
    :func:`ry`
    :func:`rz`
    :func:`tx`
    :func:`ty`
    :func:`tz`
    """
    def __init__(
        self,
        arg: Union[
            List[Union["ETS", ET]], List[ET], List["ETS"], ET, "ETS", None
        ] = None,
    ):
        super().__init__()
        if isinstance(arg, list):
            for item in arg:
                if isinstance(item, ET):
                    self.data.append(deepcopy(item))
                elif isinstance(item, ETS):
                    for ets_item in item:
                        self.data.append(deepcopy(ets_item))
                else:
                    raise TypeError("Invalid arg")
        elif isinstance(arg, ET):
            self.data.append(deepcopy(arg))
        elif isinstance(arg, ETS):
            for ets_item in arg:
                self.data.append(deepcopy(ets_item))
        elif arg is None:
            self.data = []
        else:
            raise TypeError("Invalid arg")
        super()._update_internals()
        self._auto_jindex = False
        # Check if jindices are set
        joints = self.joints()
        # Number of joints with a jindex
        jindices = 0
        # Number of joints with a sequential jindex (j[2] -> jindex = 2)
        seq_jindex = 0
        # Count them up
        for j, joint in enumerate(joints):
            if joint.jindex is not None:
                jindices += 1
                if joint.jindex == j:
                    seq_jindex += 1
        if (
            jindices == self.n - 1
            and seq_jindex == self.n - 1
            and joints[-1].jindex is None
        ):
            # ets has sequential jindicies, except for the last.
            joints[-1].jindex = self.n - 1
            self._auto_jindex = True
        elif jindices > 0 and not jindices == self.n:
            raise ValueError(
                "You can not have some jindices set for the ET's in arg. It must be all"
                " or none"
            )  # pragma: nocover
        elif jindices == 0 and self.n > 0:
            # Set them ourself
            for j, joint in enumerate(joints):
                joint.jindex = j
            self._auto_jindex = True
[docs]    def __mul__(self, other: Union["ET", "ETS"]) -> "ETS":
        if isinstance(other, ET):
            return ETS([*self.data, other])
        else:
            return ETS([*self.data, *other.data])  # pragma: nocover 
    def __rmul__(self, other: Union["ET", "ETS"]) -> "ETS":
        return ETS([other, *self.data])  # pragma: nocover
    def __imul__(self, rest: "ETS"):
        return self + rest  # pragma: nocover
    def __add__(self, rest) -> "ETS":
        return self.__mul__(rest)  # pragma: nocover
[docs]    def compile(self) -> "ETS":
        """
        Compile an ETS
        Perform constant folding for faster evaluation.  Consecutive constant
        ETs are compounded, leading to a constant ET which is denoted by
        ``SE3`` when displayed.
        Returns
        -------
        compile
            optimised ETS
        Examples
        --------
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> robot = rtb.models.ETS.Panda()
        >>> ets = robot.ets()
        >>> ets
        >>> ets.compile()
        See Also
        --------
        :func:`isconstant`
        """
        const = None
        ets = ETS()
        for et in self:
            if et.isjoint:
                # a joint
                if const is not None:
                    # flush the constant
                    if not np.array_equal(const, np.eye(4)):
                        ets *= ET.SE3(const)
                    const = None
                ets *= et  # emit the joint ET
            else:
                # not a joint
                if const is None:
                    const = et.A()
                else:
                    const = const @ et.A()
        if const is not None:
            # flush the constant, tool transform
            if not np.array_equal(const, np.eye(4)):
                ets *= ET.SE3(const)
        return ets 
[docs]    def insert(
        self,
        arg: Union[ET, "ETS"],
        i: int = -1,
    ) -> None:
        """
        Insert value
        Inserts an ET or ETS into the ET sequence.  The inserted value is at position
        ``i``.
        Parameters
        ----------
        i
            insert an ET or ETS into the ETS, default is at the end
        arg
            the elementary transform or sequence to insert
        Examples
        --------
        .. runblock:: pycon
        >>> from roboticstoolbox import ET
        >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1)
        >>> f = ET.Ry()
        >>> e.insert(f, 2)
        >>> e
        """
        if isinstance(arg, ET):
            if i == -1:
                self.data.append(arg)
            else:
                self.data.insert(i, arg)
        elif isinstance(arg, ETS):
            if i == -1:
                for et in arg:
                    self.data.append(et)
            else:
                for j, et in enumerate(arg):
                    self.data.insert(i + j, et)
        self._update_internals() 
[docs]    def fkine(
        self,
        q: ArrayLike,
        base: Union[NDArray, SE3, None] = None,
        tool: Union[NDArray, SE3, None] = None,
        include_base: bool = True,
    ) -> SE3:
        """
        Forward kinematics
        ``T = ets.fkine(q)`` evaluates forward kinematics for the ets at
        joint configuration ``q``.
        **Trajectory operation**:
        If ``q`` has multiple rows (mxn), it is considered a trajectory and the
        result is an ``SE3`` instance with ``m`` values.
        Attributes
        ----------
        q
            Joint coordinates
        base
            A base transform applied before the ETS
        tool
            tool transform, optional
        include_base
            set to True if the base transform should be considered
        Returns
        -------
            The transformation matrix representing the pose of the
            end-effector
        Examples
        --------
        The following example makes a ``panda`` robot object, gets the ets, and
        solves for the forward kinematics at the listed configuration.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        Notes
        -----
        - A tool transform, if provided, is incorporated into the result.
        - Works from the end-effector link to the base
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        """  # noqa
        ret = SE3.Empty()
        fk = self.eval(q, base, tool, include_base)
        if fk.dtype == "O":
            # symbolic
            fk = np.array(simplify(fk))
        if fk.ndim == 3:
            for T in fk:
                ret.append(SE3(T, check=False))  # type: ignore
        else:
            ret = SE3(fk, check=False)
        return ret 
    def eval(
        self,
        q: ArrayLike,
        base: Union[NDArray, SE3, None] = None,
        tool: Union[NDArray, SE3, None] = None,
        include_base: bool = True,
    ) -> NDArray:
        """
        Forward kinematics
        ``T = ets.fkine(q)`` evaluates forward kinematics for the ets at
        joint configuration ``q``.
        **Trajectory operation**:
        If ``q`` has multiple rows (mxn), it is considered a trajectory and the
        result is an ``SE3`` instance with ``m`` values.
        Attributes
        ----------
        q
            Joint coordinates
        base
            A base transform applied before the ETS
        tool
            tool transform, optional
        include_base
            set to True if the base transform should be considered
        Returns
        -------
            The transformation matrix representing the pose of the
            end-effector
        Examples
        --------
        The following example makes a ``panda`` robot object, gets the ets, and
        solves for the forward kinematics at the listed configuration.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        Notes
        -----
        - A tool transform, if provided, is incorporated into the result.
        - Works from the end-effector link to the base
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        """  # noqa
        try:
            return ETS_fkine(self._fknm, q, base, tool, include_base)
        except BaseException:
            pass
        q = getmatrix(q, (None, None))
        l, _ = q.shape  # type: ignore
        end = self.data[-1]
        if isinstance(tool, SE3):
            tool = np.array(tool.A)
        if isinstance(base, SE3):
            base = np.array(base.A)
        if base is None:
            bases = None
        elif np.array_equal(base, np.eye(3)):  # pragma: nocover
            bases = None
        else:  # pragma: nocover
            bases = base
        if tool is None:
            tools = None
        elif np.array_equal(tool, np.eye(3)):  # pragma: nocover
            tools = None
        else:  # pragma: nocover
            tools = tool
        if l > 1:
            T = np.zeros((l, 4, 4), dtype=object)
        else:
            T = np.zeros((4, 4), dtype=object)
        # Tk = None
        for k, qk in enumerate(q):  # type: ignore
            link = end  # start with last link
            jindex = 0 if link.jindex is None and link.isjoint else link.jindex
            Tk = link.A(qk[jindex])
            if tools is not None:
                Tk = Tk @ tools
            # add remaining links, back toward the base
            for i in range(self.m - 2, -1, -1):
                link = self.data[i]
                jindex = 0 if link.jindex is None and link.isjoint else link.jindex
                A = link.A(qk[jindex])
                if A is not None:
                    Tk = A @ Tk
            # add base transform if it is set
            if include_base is True and bases is not None:
                Tk = bases @ Tk
            # append
            if l > 1:
                T[k, :, :] = Tk
            else:
                T = Tk
        return T
[docs]    def jacob0(
        self,
        q: ArrayLike,
        tool: Union[NDArray, SE3, None] = None,
    ) -> NDArray:
        r"""
        Manipulator geometric Jacobian in the base frame
        ``robot.jacobo(q)`` is the manipulator Jacobian matrix which maps
        joint  velocity to end-effector spatial velocity expressed in the
        base frame.
        End-effector spatial velocity :math:`\nu = (v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)^T`
        is related to joint velocity by :math:`{}^{E}\!\nu = \mathbf{J}_m(q) \dot{q}`.
        Parameters
        ----------
        q
            Joint coordinate vector
        tool
            a static tool transformation matrix to apply to the
            end of end, defaults to None
        Returns
        -------
        J0
            Manipulator Jacobian in the base frame
        Examples
        --------
        The following example makes a ``Puma560`` robot object, and solves for the
        base-frame Jacobian at the zero joint angle configuration
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> puma = rtb.models.Puma560().ets()
        >>> puma.jacob0([0, 0, 0, 0, 0, 0])
        Notes
        -----
        - This is the geometric Jacobian as described in texts by
            Corke, Spong etal., Siciliano etal.  The end-effector velocity is
            described in terms of translational and angular velocity, not a
            velocity twist as per the text by Lynch & Park.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        """  # noqa
        # Use c extension
        try:
            return ETS_jacob0(self._fknm, q, tool)
        except TypeError:
            pass
        # Otherwise use Python
        if tool is None:
            tools = np.eye(4)
        elif isinstance(tool, SE3):
            tools = np.array(tool.A)
        else:  # pragma: nocover
            tools = np.eye(4)
        q = getvector(q, None)
        T = self.eval(q, include_base=False) @ tools
        U = np.eye(4)
        j = 0
        J = np.zeros((6, self.n), dtype="object")
        zero = np.array([0, 0, 0])
        end = self.data[-1]
        for link in self.data:
            jindex = 0 if link.jindex is None and link.isjoint else link.jindex
            if link.isjoint:
                U = U @ link.A(q[jindex])  # type: ignore
                if link == end:
                    U = U @ tools
                Tu = SE3(U, check=False).inv().A @ T
                n = U[:3, 0]
                o = U[:3, 1]
                a = U[:3, 2]
                x = Tu[0, 3]
                y = Tu[1, 3]
                z = Tu[2, 3]
                if link.axis == "Rz":
                    J[:3, j] = (o * x) - (n * y)
                    J[3:, j] = a
                elif link.axis == "Ry":
                    J[:3, j] = (n * z) - (a * x)
                    J[3:, j] = o
                elif link.axis == "Rx":
                    J[:3, j] = (a * y) - (o * z)
                    J[3:, j] = n
                elif link.axis == "tx":
                    J[:3, j] = n
                    J[3:, j] = zero
                elif link.axis == "ty":
                    J[:3, j] = o
                    J[3:, j] = zero
                elif link.axis == "tz":
                    J[:3, j] = a
                    J[3:, j] = zero
                j += 1
            else:
                A = link.A()
                if A is not None:
                    U = U @ A
        return J 
[docs]    def jacobe(
        self,
        q: ArrayLike,
        tool: Union[NDArray, SE3, None] = None,
    ) -> NDArray:
        r"""
        Manipulator geometric Jacobian in the end-effector frame
        ``robot.jacobe(q)`` is the manipulator Jacobian matrix which maps
        joint  velocity to end-effector spatial velocity expressed in the
        ``end`` frame.
        End-effector spatial velocity :math:`\nu = (v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)^T`
        is related to joint velocity by :math:`{}^{E}\!\nu = \mathbf{J}_m(q) \dot{q}`.
        Parameters
        ----------
        q
            Joint coordinate vector
        end
            the particular link or gripper whose velocity the Jacobian
            describes, defaults to the end-effector if only one is present
        start
            the link considered as the base frame, defaults to the robots's base frame
        tool
            a static tool transformation matrix to apply to the
            end of end, defaults to None
        Returns
        -------
        Je
            Manipulator Jacobian in the ``end`` frame
        Examples
        --------
        The following example makes a ``Puma560`` robot object, and solves for the
        end-effector frame Jacobian at the zero joint angle configuration
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> puma = rtb.models.Puma560().ets()
        >>> puma.jacobe([0, 0, 0, 0, 0, 0])
        Notes
        -----
        - This is the geometric Jacobian as described in texts by
            Corke, Spong etal., Siciliano etal.  The end-effector velocity is
            described in terms of translational and angular velocity, not a
            velocity twist as per the text by Lynch & Park.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        """  # noqa
        # Use c extension
        try:
            return ETS_jacobe(self._fknm, q, tool)
        except TypeError:
            pass
        T = self.eval(q, tool=tool, include_base=False)
        return tr2jac(T.T) @ self.jacob0(q, tool=tool) 
[docs]    def hessian0(
        self,
        q: Union[ArrayLike, None] = None,
        J0: Union[NDArray, None] = None,
        tool: Union[NDArray, SE3, None] = None,
    ) -> NDArray:
        r"""
        Manipulator Hessian
        The manipulator Hessian tensor maps joint acceleration to end-effector
        spatial acceleration, expressed in the base frame. This
        function calulcates this based on the ETS of the robot. One of J0 or q
        is required. Supply J0 if already calculated to save computation time
        Parameters
        ----------
        q
            The joint angles/configuration of the robot (Optional,
            if not supplied will use the stored q values).
        J0
            The manipulator Jacobian in the base frame
        tool
            a static tool transformation matrix to apply to the
            end of end, defaults to None
        Returns
        -------
        h0
            The manipulator Hessian in the base frame
        Synopsis
        --------
        This method computes the manipulator Hessian in the base frame.  If
        we take the time derivative of the differential kinematic relationship
        .. math::
            \nu    &= \mat{J}(\vec{q}) \dvec{q} \\
            \alpha &= \dmat{J} \dvec{q} + \mat{J} \ddvec{q}
        where
        .. math::
            \dmat{J} = \mat{H} \dvec{q}
        and :math:`\mat{H} \in \mathbb{R}^{6\times n \times n}` is the
        Hessian tensor.
        The elements of the Hessian are
        .. math::
            \mat{H}_{i,j,k} =  \frac{d^2 u_i}{d q_j d q_k}
        where :math:`u = \{t_x, t_y, t_z, r_x, r_y, r_z\}` are the elements
        of the spatial velocity vector.
        Similarly, we can write
        .. math::
            \mat{J}_{i,j} = \frac{d u_i}{d q_j}
        Examples
        --------
        The following example makes a ``Panda`` robot object, and solves for the
        base frame Hessian at the given joint angle configuration
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> panda.hessian0([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        """  # noqa
        # Use c extension
        try:
            return ETS_hessian0(self._fknm, q, J0, tool)
        except TypeError:
            pass
        def cross(a, b):
            x = a[1] * b[2] - a[2] * b[1]
            y = a[2] * b[0] - a[0] * b[2]
            z = a[0] * b[1] - a[1] * b[0]
            return np.array([x, y, z])
        n = self.n
        if J0 is None:
            q = getvector(q, None)
            J0 = self.jacob0(q, tool=tool)
        else:
            verifymatrix(J0, (6, self.n))
        H = np.zeros((n, 6, n))
        for j in range(n):
            for i in range(j, n):
                H[j, :3, i] = cross(J0[3:, j], J0[:3, i])
                H[j, 3:, i] = cross(J0[3:, j], J0[3:, i])
                if i != j:
                    H[i, :3, j] = H[j, :3, i]
        return H 
[docs]    def hessiane(
        self,
        q: Union[ArrayLike, None] = None,
        Je: Union[NDArray, None] = None,
        tool: Union[NDArray, SE3, None] = None,
    ) -> NDArray:
        r"""
        Manipulator Hessian
        The manipulator Hessian tensor maps joint acceleration to end-effector
        spatial acceleration, expressed in the end-effector coordinate frame. This
        function calulcates this based on the ETS of the robot. One of J0 or q
        is required. Supply J0 if already calculated to save computation time
        Parameters
        ----------
        q
            The joint angles/configuration of the robot (Optional,
            if not supplied will use the stored q values).
        J0
            The manipulator Jacobian in the end-effector frame
        tool
            a static tool transformation matrix to apply to the
            end of end, defaults to None
        Returns
        -------
        he
            The manipulator Hessian in end-effector frame
        Synopsis
        --------
        This method computes the manipulator Hessian in the end-effector frame.  If
        we take the time derivative of the differential kinematic relationship
        .. math::
            \nu    &= \mat{J}(\vec{q}) \dvec{q} \\
            \alpha &= \dmat{J} \dvec{q} + \mat{J} \ddvec{q}
        where
        .. math::
            \dmat{J} = \mat{H} \dvec{q}
        and :math:`\mat{H} \in \mathbb{R}^{6\times n \times n}` is the
        Hessian tensor.
        The elements of the Hessian are
        .. math::
            \mat{H}_{i,j,k} =  \frac{d^2 u_i}{d q_j d q_k}
        where :math:`u = \{t_x, t_y, t_z, r_x, r_y, r_z\}` are the elements
        of the spatial velocity vector.
        Similarly, we can write
        .. math::
            \mat{J}_{i,j} = \frac{d u_i}{d q_j}
        Examples
        --------
        The following example makes a ``Panda`` robot object, and solves for the
        end-effector frame Hessian at the given joint angle configuration
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> panda.hessiane([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        """  # noqa
        # Use c extension
        try:
            return ETS_hessiane(self._fknm, q, Je, tool)
        except TypeError:
            pass
        def cross(a, b):
            x = a[1] * b[2] - a[2] * b[1]
            y = a[2] * b[0] - a[0] * b[2]
            z = a[0] * b[1] - a[1] * b[0]
            return np.array([x, y, z])
        n = self.n
        if Je is None:
            q = getvector(q, None)
            Je = self.jacobe(q, tool=tool)
        else:
            verifymatrix(Je, (6, self.n))
        H = np.zeros((n, 6, n))
        for j in range(n):
            for i in range(j, n):
                H[j, :3, i] = cross(Je[3:, j], Je[:3, i])
                H[j, 3:, i] = cross(Je[3:, j], Je[3:, i])
                if i != j:
                    H[i, :3, j] = H[j, :3, i]
        return H 
    def jacob0_analytical(
        self,
        q: ArrayLike,
        representation: str = "rpy/xyz",
        tool: Union[NDArray, SE3, None] = None,
    ):
        r"""
        Manipulator analytical Jacobian in the base frame
        ``robot.jacob0_analytical(q)`` is the manipulator Jacobian matrix which maps
        joint  velocity to end-effector spatial velocity expressed in the
        base frame.
        Parameters
        ----------
        q
            Joint coordinate vector
        representation
            angular representation
        tool
            a static tool transformation matrix to apply to the
            end of end, defaults to None
        Returns
        -------
        jacob0
            Manipulator Jacobian in the base frame
        Synopsis
        --------
        End-effector spatial velocity :math:`\nu = (v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)^T`
        is related to joint velocity by :math:`{}^{E}\!\nu = \mathbf{J}_m(q) \dot{q}`.
        |``representation``   |       Rotational representation     |
        |---------------------|-------------------------------------|
        |``'rpy/xyz'``        |   RPY angular rates in XYZ order    |
        |``'rpy/zyx'``        |   RPY angular rates in XYZ order    |
        |``'eul'``            |   Euler angular rates in ZYZ order  |
        |``'exp'``            |   exponential coordinate rates      |
        Examples
        --------
        Makes a robot object and computes the analytic Jacobian for the given
        joint configuration
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> puma = rtb.models.ETS.Puma560().ets()
        >>> puma.jacob0_analytical([0, 0, 0, 0, 0, 0])
        """  # noqa
        T = self.eval(q, tool=tool)
        J = self.jacob0(q, tool=tool)
        A = rotvelxform(t2r(T), full=True, inverse=True, representation=representation)
        return A @ J
    def jacobm(self, q: ArrayLike) -> NDArray:
        r"""
        The manipulability Jacobian
        This measure relates the rate of change of the manipulability to the
        joint velocities of the robot. One of J or q is required. Supply J
        and H if already calculated to save computation time
        Parameters
        ----------
        q
            The joint angles/configuration of the robot (Optional,
            if not supplied will use the stored q values).
        Returns
        -------
        jacobm
            The manipulability Jacobian
        Synopsis
        --------
        Yoshikawa's manipulability measure
        .. math::
            m(\vec{q}) = \sqrt{\mat{J}(\vec{q}) \mat{J}(\vec{q})^T}
        This method returns its Jacobian with respect to configuration
        .. math::
            \frac{\partial m(\vec{q})}{\partial \vec{q}}
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        """  # noqa
        J = self.jacob0(q)
        H = self.hessian0(q)
        manipulability = self.manipulability(q)
        # J = J[axes, :]
        # H = H[:, axes, :]
        b = inv(J @ J.T)
        Jm = np.zeros((self.n, 1))
        for i in range(self.n):
            c = J @ H[i, :, :].T
            Jm[i, 0] = manipulability * (c.flatten("F")).T @ b.flatten("F")
        return Jm
    def manipulability(
        self,
        q,
        method: L["yoshikawa", "minsingular", "invcondition"] = "yoshikawa",
        axes: Union[L["all", "trans", "rot"], List[bool]] = "all",
    ):
        """
        Manipulability measure
        ``manipulability(q)`` is the scalar manipulability index
        for the ets at the joint configuration ``q``.  It indicates
        dexterity, that is, how well conditioned the ets is for motion
        with respect to the 6 degrees of Cartesian motion.  The values is
        zero if the ets is at a singularity.
        Parameters
        ----------
        q
            Joint coordinates, one of J or q required
        method
            method to use, "yoshikawa" (default), "invcondition",
            "minsingular"
        axes
            Task space axes to consider: "all" [default],
            "trans", or "rot"
        Returns
        -------
        manipulability
            the manipulability metric
        Synopsis
        --------
        Various measures are supported:
        | Measure           |       Description                               |
        |-------------------|-------------------------------------------------|
        | ``"yoshikawa"``   | Volume of the velocity ellipsoid, *distance*    |
        |                   | from singularity [Yoshikawa85]_                 |
        | ``"invcondition"``| Inverse condition number of Jacobian, isotropy  |
        |                   | of the velocity ellipsoid [Klein87]_            |
        | ``"minsingular"`` | Minimum singular value of the Jacobian,         |
        |                   | *distance*  from singularity [Klein87]_         |
        **Trajectory operation**:
        If ``q`` is a matrix (m,n) then the result (m,) is a vector of
        manipulability indices for each joint configuration specified by a row
        of ``q``.
        Notes
        -----
        - Invokes the ``jacob0`` method of the robot if ``J`` is not passed
        - The "all" option includes rotational and translational
            dexterity, but this involves adding different units. It can be
            more useful to look at the translational and rotational
            manipulability separately.
        - Examples in the RVC book (1st edition) can be replicated by
            using the "all" option
        - Asada's measure requires inertial a robot model with inertial
            parameters.
        References
        ----------
        .. [Yoshikawa85] Manipulability of Robotic Mechanisms. Yoshikawa T.,
                The International Journal of Robotics Research.
                1985;4(2):3-9. doi:10.1177/027836498500400201
        .. [Klein87] Dexterity Measures for the Design and Control of
                Kinematically Redundant Manipulators. Klein CA, Blaho BE.
                The International Journal of Robotics Research.
                1987;6(2):72-83. doi:10.1177/027836498700600206
        - Robotics, Vision & Control in Python, 3e, P. Corke, Springer 2023, Chap 7.
        .. versionchanged:: 1.0.4
            Removed 'both' option for axes, added a custom list option.
        """
        axes_list: List[bool] = []
        if isinstance(axes, list):
            axes_list = axes
        elif axes == "all":
            axes_list = [True, True, True, True, True, True]
        elif axes.startswith("trans"):
            axes_list = [True, True, True, False, False, False]
        elif axes.startswith("rot"):
            axes_list = [False, False, False, True, True, True]
        else:
            raise ValueError("axes must be all, trans, rot or both")
        def yoshikawa(robot, J, q, axes, **kwargs):
            J = J[axes, :]
            if J.shape[0] == J.shape[1]:
                # simplified case for square matrix
                return abs(det(J))
            else:
                m2 = det(J @ J.T)
                return np.sqrt(abs(m2))
        def condition(robot, J, q, axes, **kwargs):
            J = J[axes, :]
            return 1 / cond(J)
        def minsingular(robot, J, q, axes, **kwargs):
            J = J[axes, :]
            s = svd(J, compute_uv=False)
            return s[-1]  # return last/smallest singular value of J
        # choose the handler function
        if method == "yoshikawa":
            mfunc = yoshikawa
        elif method == "invcondition":
            mfunc = condition
        elif method == "minsingular":
            mfunc = minsingular
        else:
            raise ValueError("Invalid method chosen")
        # Otherwise use the q vector/matrix
        q = np.array(getmatrix(q, (None, self.n)))
        w = np.zeros(q.shape[0])
        for k, qk in enumerate(q):
            Jk = self.jacob0(qk)
            w[k] = mfunc(self, Jk, qk, axes_list)
        if len(w) == 1:
            return w[0]
        else:
            return w
    def partial_fkine0(self, q: ArrayLike, n: int) -> NDArray:
        r"""
        Manipulator Forward Kinematics nth Partial Derivative
        This method computes the nth derivative of the forward kinematics where ``n`` is
        greater than or equal to 3. This is an extension of the differential kinematics
        where the Jacobian is the first partial derivative and the Hessian is the
        second.
        Parameters
        ----------
        q
            The joint angles/configuration of the robot (Optional,
            if not supplied will use the stored q values).
        end
            the final link/Gripper which the Hessian represents
        start
            the first link which the Hessian represents
        tool
            a static tool transformation matrix to apply to the
            end of end, defaults to None
        Returns
        -------
        A
            The nth Partial Derivative of the forward kinematics
        Examples
        --------
        The following example makes a ``Panda`` robot object, and solves for the
        base-effector frame 4th defivative of the forward kinematics at the given
        joint angle configuration
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> panda.partial_fkine0([0, -0.3, 0, -2.2, 0, 2, 0.7854], n=4)
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        """  # noqa
        # Calculate the Jacobian and Hessian
        J = self.jacob0(q)
        H = self.hessian0(q)
        # A list of derivatives, starting with the jacobian and hessian
        dT = [J, H]
        # The tensor dimensions of the latest derivative
        # Set to the current size of the Hessian
        size = [self.n, 6, self.n]
        # An array which keeps track of the index of the partial derivative
        # we are calculating
        # It stores the indices in the order: "j, k, l. m, n, o, ..."
        # where count is extended to match oder of the partial derivative
        count = np.array([0, 0])
        # The order of derivative for which we are calculating
        # The Hessian is the 2nd-order so we start with c = 2
        c = 2
        def add_indices(indices, c):
            total = len(indices * 2)
            new_indices = []
            for i in range(total):
                j = i // 2
                new_indices.append([])
                new_indices[i].append(indices[j][0].copy())
                new_indices[i].append(indices[j][1].copy())
                if i % 2 == 0:
                    # if even number
                    new_indices[i][0].append(c)
                else:
                    # if odd number
                    new_indices[i][1].append(c)
            return new_indices
        def add_pdi(pdi):
            total = len(pdi * 2)
            new_pdi = []
            for i in range(total):
                j = i // 2
                new_pdi.append([])
                new_pdi[i].append(pdi[j][0])
                new_pdi[i].append(pdi[j][1])
                # if even number
                if i % 2 == 0:
                    new_pdi[i][0] += 1
                # if odd number
                else:
                    new_pdi[i][1] += 1
            return new_pdi
        # these are the indices used for the hessian
        indices = [[[1], [0]]]
        # The partial derivative indices (pdi)
        # the are the pd indices used in the cross products
        pdi = [[0, 0]]
        # The length of dT correspods to the number of derivatives we have calculated
        while len(dT) != n:
            # Add to the start of the tensor size list
            size.insert(0, self.n)
            # Add an axis to the count array
            count = np.concatenate(([0], count))
            # This variables corresponds to indices within the previous
            # partial derivatives
            # to be cross prodded
            # The order is: "[j, k, l, m, n, o, ...]"
            # Although, our partial derivatives have the order:
            # pd[..., o, n, m, l, k, cartesian DoF, j]
            # For example, consider the Hessian Tensor H[n, 6, n],
            # the index H[k, :, j]. This corrsponds
            # to the second partial derivative of the kinematics of joint j with
            # respect to joint k.
            indices = add_indices(indices, c)
            # This variable corresponds to the indices in Td which corresponds to the
            # partial derivatives we need to use
            pdi = add_pdi(pdi)
            c += 1
            # Allocate our new partial derivative tensor
            pd = np.zeros(size)
            # We need to loop n^c times
            # There are n^c columns to calculate
            for _ in range(self.n**c):
                # Allocate the rotation and translation components
                rot = np.zeros(3)
                trn = np.zeros(3)
                # This loop calculates a single column ([trn, rot])
                # of the tensor for dT(x)
                for j in range(len(indices)):
                    pdr0 = dT[pdi[j][0]]
                    pdr1 = dT[pdi[j][1]]
                    idx0 = count[indices[j][0]]
                    idx1 = count[indices[j][1]]
                    # This is a list of indices selecting the slices of the
                    # previous tensor
                    idx0_slices = np.flip(idx0[1:])
                    idx1_slices = np.flip(idx1[1:])
                    # This index selecting the column within the 2d slice of the
                    # previous tensor
                    idx0_n = idx0[0]
                    idx1_n = idx1[0]
                    # Use our indices to select the rotational column from pdr0 and pdr1
                    col0_rot = pdr0[(*idx0_slices, slice(3, 6), idx0_n)]
                    col1_rot = pdr1[(*idx1_slices, slice(3, 6), idx1_n)]
                    # Use our indices to select the translational column from pdr1
                    col1_trn = pdr1[(*idx1_slices, slice(0, 3), idx1_n)]
                    # Perform the cross product as described in the maths above
                    rot += np.cross(col0_rot, col1_rot)
                    trn += np.cross(col0_rot, col1_trn)
                pd[(*np.flip(count[1:]), slice(0, 3), count[0])] = trn
                pd[(*np.flip(count[1:]), slice(3, 6), count[0])] = rot
                count[0] += 1
                for j in range(len(count)):
                    if count[j] == self.n:
                        count[j] = 0
                        if j != len(count) - 1:
                            count[j + 1] += 1
            dT.append(pd)
        return dT[-1]
[docs]    def ik_LM(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[NDArray, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[NDArray, None] = None,
        joint_limits: bool = True,
        k: float = 1.0,
        method: L["chan", "wampler", "sugihara"] = "chan",
    ) -> Tuple[NDArray, int, int, int, float]:
        r"""
        Fast levenberg-Marquadt Numerical Inverse Kinematics Solver
        A method which provides functionality to perform numerical inverse kinematics (IK)
        using the Levemberg-Marquadt method. This
        is a fast solver implemented in C++.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Parameters
        ----------
        Tep
            The desired end-effector pose
        q0
            The initial joint coordinate vector
        ilimit
            How many iterations are allowed within a search before a new search
            is started
        slimit
            How many searches are allowed before being deemed unsuccessful
        tol
            Maximum allowed residual error E
        mask
            A 6 vector which assigns weights to Cartesian degrees-of-freedom
            error priority
        joint_limits
            Reject solutions with joint limit violations
        seed
            A seed for the private RNG used to generate random joint coordinate
            vectors
        k
            Sets the gain value for the damping matrix Wn in the next iteration. See
            synopsis
        method
            One of "chan", "sugihara" or "wampler". Defines which method is used
            to calculate the damping matrix Wn in the ``step`` method
        Synopsis
        --------
        The operation is defined by the choice of the ``method`` kwarg.
        The step is deined as
        .. math::
            \vec{q}_{k+1}
            &=
            \vec{q}_k +
            \left(
                \mat{A}_k
            \right)^{-1}
            \bf{g}_k \\
            %
            \mat{A}_k
            &=
            {\mat{J}(\vec{q}_k)}^\top
            \mat{W}_e \
            {\mat{J}(\vec{q}_k)}
            +
            \mat{W}_n
        where :math:`\mat{W}_n = \text{diag}(\vec{w_n})(\vec{w_n} \in \mathbb{R}^n_{>0})` is a
        diagonal damping matrix. The damping matrix ensures that :math:`\mat{A}_k` is
        non-singular and positive definite. The performance of the LM method largely depends
        on the choice of :math:`\mat{W}_n`.
        *Chan's Method*
        Chan proposed
        .. math::
            \mat{W}_n
            =
            λ E_k \mat{1}_n
        where λ is a constant which reportedly does not have much influence on performance.
        Use the kwarg `k` to adjust the weighting term λ.
        *Sugihara's Method*
        Sugihara proposed
        .. math::
            \mat{W}_n
            =
            E_k \mat{1}_n + \text{diag}(\hat{\vec{w}}_n)
        where :math:`\hat{\vec{w}}_n \in \mathbb{R}^n`, :math:`\hat{w}_{n_i} = l^2 \sim 0.01 l^2`,
        and :math:`l` is the length of a typical link within the manipulator. We provide the
        variable `k` as a kwarg to adjust the value of :math:`w_n`.
        *Wampler's Method*
        Wampler proposed :math:`\vec{w_n}` to be a constant. This is set through the `k` kwarg.
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_LM` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ikine_LM(Tep)
        Notes
        -----
        The value for the ``k`` kwarg will depend on the ``method`` chosen and the arm you are
        using. Use the following as a rough guide ``chan, k = 1.0 - 0.01``,
        ``wampler, k = 0.01 - 0.0001``, and ``sugihara, k = 0.1 - 0.0001``
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        This class supports null-space motion to assist with maximising manipulability and
        avoiding joint limits. These are enabled by setting kq and km to non-zero values.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        ik_NR
            A fast numerical inverse kinematics solver using Newton-Raphson optimisation
        ik_GN
            A fast numerical inverse kinematics solver using Gauss-Newton optimisation
        .. versionchanged:: 1.0.4
            Merged the Levemberg-Marquadt IK solvers into the ik_LM method
        """  # noqa
        return IK_LM_c(
            self._fknm, Tep, q0, ilimit, slimit, tol, joint_limits, mask, k, method
        ) 
[docs]    def ik_NR(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[NDArray, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[NDArray, None] = None,
        joint_limits: bool = True,
        pinv: int = True,
        pinv_damping: float = 0.0,
    ) -> Tuple[NDArray, int, int, int, float]:
        r"""
        Fast numerical inverse kinematics using Newton-Raphson optimization
        ``sol = ets.ik_NR(Tep)`` are the joint coordinates (n) corresponding
        to the robot end-effector pose ``Tep`` which is an ``SE3`` or ``ndarray`` object.
        This method can be used for robots with any number of degrees of freedom. This
        is a fast solver implemented in C++.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Note
        ----
        When using this method with redundant robots (>6 DoF), ``pinv`` must be set to ``True``
        Parameters
        ----------
        Tep
            The desired end-effector pose or pose trajectory
        q0
            initial joint configuration (default to random valid joint
            configuration contrained by the joint limits of the robot)
        ilimit
            maximum number of iterations per search
        slimit
            maximum number of search attempts
        tol
            final error tolerance
        mask
            a mask vector which weights the end-effector error priority.
            Corresponds to translation in X, Y and Z and rotation about X, Y and Z
            respectively
        joint_limits
            constrain the solution to being within the joint limits of
            the robot (reject solution with invalid joint configurations and perfrom
            another search up to the slimit)
        pinv
            Use the psuedo-inverse instad of the normal matrix inverse
        pinv_damping
            Damping factor for the psuedo-inverse
        Returns
        -------
        sol
            tuple (q, success, iterations, searches, residual)
        The return value ``sol`` is a tuple with elements:
        ============    ==========  ===============================================
        Element         Type        Description
        ============    ==========  ===============================================
        ``q``           ndarray(n)  joint coordinates in units of radians or metres
        ``success``     int         whether a solution was found
        ``iterations``  int         total number of iterations
        ``searches``    int         total number of searches
        ``residual``    float       final value of cost function
        ============    ==========  ===============================================
        If ``success == 0`` the ``q`` values will be valid numbers, but the
        solution will be in error.  The amount of error is indicated by
        the ``residual``.
        Synopsis
        --------
        Each iteration uses the Newton-Raphson optimisation method
        .. math::
            \vec{q}_{k+1} = \vec{q}_k + {^0\mat{J}(\vec{q}_k)}^{-1} \vec{e}_k
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_GN` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ik_NR(Tep)
        Notes
        -----
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        ik_LM
            A fast numerical inverse kinematics solver using Levenberg-Marquadt optimisation
        ik_GN
            A fast numerical inverse kinematics solver using Gauss-Newton optimisation
        """  # noqa
        return IK_NR_c(
            self._fknm,
            Tep,
            q0,
            ilimit,
            slimit,
            tol,
            joint_limits,
            mask,
            pinv,
            pinv_damping,
        ) 
[docs]    def ik_GN(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[NDArray, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[NDArray, None] = None,
        joint_limits: bool = True,
        pinv: int = True,
        pinv_damping: float = 0.0,
    ) -> Tuple[NDArray, int, int, int, float]:
        r"""
        Fast numerical inverse kinematics by Gauss-Newton optimization
        ``sol = ets.ik_GN(Tep)`` are the joint coordinates (n) corresponding
        to the robot end-effector pose ``Tep`` which is an ``SE3`` or ``ndarray`` object.
        This method can be used for robots with any number of degrees of freedom. This
        is a fast solver implemented in C++.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Note
        ----
        When using this method with redundant robots (>6 DoF), ``pinv`` must be set to ``True``
        Parameters
        ----------
        Tep
            The desired end-effector pose or pose trajectory
        q0
            initial joint configuration (default to random valid joint
            configuration contrained by the joint limits of the robot)
        ilimit
            maximum number of iterations per search
        slimit
            maximum number of search attempts
        tol
            final error tolerance
        mask
            a mask vector which weights the end-effector error priority.
            Corresponds to translation in X, Y and Z and rotation about X, Y and Z
            respectively
        joint_limits
            constrain the solution to being within the joint limits of
            the robot (reject solution with invalid joint configurations and perfrom
            another search up to the slimit)
        pinv
            Use the psuedo-inverse instad of the normal matrix inverse
        pinv_damping
            Damping factor for the psuedo-inverse
        Returns
        -------
        sol
            tuple (q, success, iterations, searches, residual)
        The return value ``sol`` is a tuple with elements:
        ============    ==========  ===============================================
        Element         Type        Description
        ============    ==========  ===============================================
        ``q``           ndarray(n)  joint coordinates in units of radians or metres
        ``success``     int         whether a solution was found
        ``iterations``  int         total number of iterations
        ``searches``    int         total number of searches
        ``residual``    float       final value of cost function
        ============    ==========  ===============================================
        If ``success == 0`` the ``q`` values will be valid numbers, but the
        solution will be in error.  The amount of error is indicated by
        the ``residual``.
        Synopsis
        --------
        Each iteration uses the Gauss-Newton optimisation method
        .. math::
            \vec{q}_{k+1} &= \vec{q}_k +
            \left(
            {\mat{J}(\vec{q}_k)}^\top
            \mat{W}_e \
            {\mat{J}(\vec{q}_k)}
            \right)^{-1}
            \bf{g}_k \\
            \bf{g}_k &=
            {\mat{J}(\vec{q}_k)}^\top
            \mat{W}_e
            \vec{e}_k
        where :math:`\mat{J} = {^0\mat{J}}` is the base-frame manipulator Jacobian. If
        :math:`\mat{J}(\vec{q}_k)` is non-singular, and :math:`\mat{W}_e = \mat{1}_n`, then
        the above provides the pseudoinverse solution. However, if :math:`\mat{J}(\vec{q}_k)`
        is singular, the above can not be computed and the GN solution is infeasible.
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_GN` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ik_GN(Tep)
        Notes
        -----
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        ik_NR
            A fast numerical inverse kinematics solver using Newton-Raphson optimisation
        ik_GN
            A fast numerical inverse kinematics solver using Gauss-Newton optimisation
        """  # noqa
        return IK_GN_c(
            self._fknm,
            Tep,
            q0,
            ilimit,
            slimit,
            tol,
            joint_limits,
            mask,
            pinv,
            pinv_damping,
        ) 
[docs]    def ikine_LM(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[ArrayLike, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[ArrayLike, None] = None,
        joint_limits: bool = True,
        seed: Union[int, None] = None,
        k: float = 1.0,
        method: L["chan", "wampler", "sugihara"] = "chan",
        kq: float = 0.0,
        km: float = 0.0,
        ps: float = 0.0,
        pi: Union[NDArray, float] = 0.3,
        **kwargs,
    ):
        r"""
        Levemberg-Marquadt Numerical Inverse Kinematics Solver
        A method which provides functionality to perform numerical inverse kinematics (IK)
        using the Levemberg-Marquadt method.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a 
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Parameters
        ----------
        Tep
            The desired end-effector pose
        q0
            The initial joint coordinate vector
        ilimit
            How many iterations are allowed within a search before a new search
            is started
        slimit
            How many searches are allowed before being deemed unsuccessful
        tol
            Maximum allowed residual error E
        mask
            A 6 vector which assigns weights to Cartesian degrees-of-freedom
            error priority
        joint_limits
            Reject solutions with joint limit violations
        seed
            A seed for the private RNG used to generate random joint coordinate
            vectors
        k
            Sets the gain value for the damping matrix Wn in the next iteration. See
            synopsis
        method
            One of "chan", "sugihara" or "wampler". Defines which method is used
            to calculate the damping matrix Wn in the ``step`` method
        kq
            The gain for joint limit avoidance. Setting to 0.0 will remove this
            completely from the solution
        km
            The gain for maximisation. Setting to 0.0 will remove this completely
            from the solution
        ps
            The minimum angle/distance (in radians or metres) in which the joint is
            allowed to approach to its limit
        pi
            The influence angle/distance (in radians or metres) in null space motion
            becomes active
        Synopsis
        --------
        The operation is defined by the choice of the ``method`` kwarg. 
        The step is deined as
        .. math::
            \vec{q}_{k+1} 
            &= 
            \vec{q}_k +
            \left(
                \mat{A}_k
            \right)^{-1}
            \bf{g}_k \\
            %
            \mat{A}_k
            &=
            {\mat{J}(\vec{q}_k)}^\top
            \mat{W}_e \
            {\mat{J}(\vec{q}_k)}
            +
            \mat{W}_n
        where :math:`\mat{W}_n = \text{diag}(\vec{w_n})(\vec{w_n} \in \mathbb{R}^n_{>0})` is a
        diagonal damping matrix. The damping matrix ensures that :math:`\mat{A}_k` is
        non-singular and positive definite. The performance of the LM method largely depends
        on the choice of :math:`\mat{W}_n`.
        *Chan's Method*
        Chan proposed
        .. math::
            \mat{W}_n
            =
            λ E_k \mat{1}_n
        where λ is a constant which reportedly does not have much influence on performance.
        Use the kwarg `k` to adjust the weighting term λ.
        *Sugihara's Method*
        Sugihara proposed
        .. math::
            \mat{W}_n
            =
            E_k \mat{1}_n + \text{diag}(\hat{\vec{w}}_n)
        where :math:`\hat{\vec{w}}_n \in \mathbb{R}^n`, :math:`\hat{w}_{n_i} = l^2 \sim 0.01 l^2`,
        and :math:`l` is the length of a typical link within the manipulator. We provide the
        variable `k` as a kwarg to adjust the value of :math:`w_n`.
        *Wampler's Method*
        Wampler proposed :math:`\vec{w_n}` to be a constant. This is set through the `k` kwarg.
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_LM` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ikine_LM(Tep)
        Notes
        -----
        The value for the ``k`` kwarg will depend on the ``method`` chosen and the arm you are
        using. Use the following as a rough guide ``chan, k = 1.0 - 0.01``,
        ``wampler, k = 0.01 - 0.0001``, and ``sugihara, k = 0.1 - 0.0001``
        
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        This class supports null-space motion to assist with maximising manipulability and
        avoiding joint limits. These are enabled by setting kq and km to non-zero values.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        :py:class:`~roboticstoolbox.robot.IK.IK_LM`
            An IK Solver class which implements the Levemberg Marquadt optimisation technique
        ikine_NR
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_NR` class as a method within the :py:class:`ETS` class
        ikine_GN
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_GN` class as a method within the :py:class:`ETS` class
        ikine_QP
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_QP` class as a method within the :py:class:`ETS` class
        .. versionchanged:: 1.0.4
            Added the Levemberg-Marquadt IK solver method on the `ETS` class
        """  # noqa
        solver = IK_LM(
            ilimit=ilimit,
            slimit=slimit,
            tol=tol,
            joint_limits=joint_limits,
            mask=mask,
            seed=seed,
            k=k,
            method=method,
            kq=kq,
            km=km,
            ps=ps,
            pi=pi,
            **kwargs,
        )
        # if isinstance(Tep, SE3):
        #     Tep = Tep.A
        return solver.solve(ets=self, Tep=Tep, q0=q0) 
[docs]    def ikine_NR(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[ArrayLike, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[ArrayLike, None] = None,
        joint_limits: bool = True,
        seed: Union[int, None] = None,
        pinv: bool = False,
        kq: float = 0.0,
        km: float = 0.0,
        ps: float = 0.0,
        pi: Union[NDArray, float] = 0.3,
        **kwargs,
    ):
        r"""
        Newton-Raphson Numerical Inverse Kinematics Solver
        A method which provides functionality to perform numerical inverse kinematics (IK)
        using the Newton-Raphson method.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Note
        ----
        When using this method with redundant robots (>6 DoF), ``pinv`` must be set to ``True``
        Parameters
        ----------
        Tep
            The desired end-effector pose
        q0
            The initial joint coordinate vector
        ilimit
            How many iterations are allowed within a search before a new search
            is started
        slimit
            How many searches are allowed before being deemed unsuccessful
        tol
            Maximum allowed residual error E
        mask
            A 6 vector which assigns weights to Cartesian degrees-of-freedom
            error priority
        joint_limits
            Reject solutions with joint limit violations
        seed
            A seed for the private RNG used to generate random joint coordinate
            vectors
        pinv
            If True, will use the psuedoinverse in the `step` method instead of
            the normal inverse
        kq
            The gain for joint limit avoidance. Setting to 0.0 will remove this
            completely from the solution
        km
            The gain for maximisation. Setting to 0.0 will remove this completely
            from the solution
        ps
            The minimum angle/distance (in radians or metres) in which the joint is
            allowed to approach to its limit
        pi
            The influence angle/distance (in radians or metres) in null space motion
            becomes active
        Synopsis
        --------
        Each iteration uses the Newton-Raphson optimisation method
        .. math::
            \vec{q}_{k+1} = \vec{q}_k + {^0\mat{J}(\vec{q}_k)}^{-1} \vec{e}_k
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_NR` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ikine_NR(Tep)
        Notes
        -----
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        This class supports null-space motion to assist with maximising manipulability and
        avoiding joint limits. These are enabled by setting kq and km to non-zero values.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        :py:class:`~roboticstoolbox.robot.IK.IK_NR`
            An IK Solver class which implements the Newton-Raphson optimisation technique
        ikine_LM
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_LM` class as a method within the :py:class:`ETS` class
        ikine_GN
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_GN` class as a method within the :py:class:`ETS` class
        ikine_QP
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_QP` class as a method within the :py:class:`ETS` class
        .. versionchanged:: 1.0.4
            Added the Newton-Raphson IK solver method on the `ETS` class
        """  # noqa
        solver = IK_NR(
            ilimit=ilimit,
            slimit=slimit,
            tol=tol,
            joint_limits=joint_limits,
            mask=mask,
            seed=seed,
            pinv=pinv,
            kq=kq,
            km=km,
            ps=ps,
            pi=pi,
            **kwargs,
        )
        # if isinstance(Tep, SE3):
        #     Tep = Tep.A
        return solver.solve(ets=self, Tep=Tep, q0=q0) 
[docs]    def ikine_GN(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[ArrayLike, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[ArrayLike, None] = None,
        joint_limits: bool = True,
        seed: Union[int, None] = None,
        pinv: bool = False,
        kq: float = 0.0,
        km: float = 0.0,
        ps: float = 0.0,
        pi: Union[NDArray, float] = 0.3,
        **kwargs,
    ):
        r"""
        Gauss-Newton Numerical Inverse Kinematics Solver
        A method which provides functionality to perform numerical inverse kinematics (IK)
        using the Gauss-Newton method.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Note
        ----
        When using this method with redundant robots (>6 DoF), ``pinv`` must be set to ``True``
        Parameters
        ----------
        Tep
            The desired end-effector pose
        q0
            The initial joint coordinate vector
        ilimit
            How many iterations are allowed within a search before a new search
            is started
        slimit
            How many searches are allowed before being deemed unsuccessful
        tol
            Maximum allowed residual error E
        mask
            A 6 vector which assigns weights to Cartesian degrees-of-freedom
            error priority
        joint_limits
            Reject solutions with joint limit violations
        seed
            A seed for the private RNG used to generate random joint coordinate
            vectors
        pinv
            If True, will use the psuedoinverse in the `step` method instead of
            the normal inverse
        kq
            The gain for joint limit avoidance. Setting to 0.0 will remove this
            completely from the solution
        km
            The gain for maximisation. Setting to 0.0 will remove this completely
            from the solution
        ps
            The minimum angle/distance (in radians or metres) in which the joint is
            allowed to approach to its limit
        pi
            The influence angle/distance (in radians or metres) in null space motion
            becomes active
        Synopsis
        --------
        Each iteration uses the Gauss-Newton optimisation method
        .. math::
            \vec{q}_{k+1} &= \vec{q}_k +
            \left(
            {\mat{J}(\vec{q}_k)}^\top
            \mat{W}_e \
            {\mat{J}(\vec{q}_k)}
            \right)^{-1}
            \bf{g}_k \\
            \bf{g}_k &=
            {\mat{J}(\vec{q}_k)}^\top
            \mat{W}_e
            \vec{e}_k
        where :math:`\mat{J} = {^0\mat{J}}` is the base-frame manipulator Jacobian. If
        :math:`\mat{J}(\vec{q}_k)` is non-singular, and :math:`\mat{W}_e = \mat{1}_n`, then
        the above provides the pseudoinverse solution. However, if :math:`\mat{J}(\vec{q}_k)`
        is singular, the above can not be computed and the GN solution is infeasible.
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_GN` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ikine_GN(Tep)
        Notes
        -----
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        This class supports null-space motion to assist with maximising manipulability and
        avoiding joint limits. These are enabled by setting kq and km to non-zero values.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        :py:class:`~roboticstoolbox.robot.IK.IK_NR`
            An IK Solver class which implements the Newton-Raphson optimisation technique
        ikine_LM
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_LM` class as a method within the :py:class:`ETS` class
        ikine_NR
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_NR` class as a method within the :py:class:`ETS` class
        ikine_QP
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_QP` class as a method within the :py:class:`ETS` class
        .. versionchanged:: 1.0.4
            Added the Gauss-Newton IK solver method on the `ETS` class
        """  # noqa
        solver = IK_GN(
            ilimit=ilimit,
            slimit=slimit,
            tol=tol,
            joint_limits=joint_limits,
            mask=mask,
            seed=seed,
            pinv=pinv,
            kq=kq,
            km=km,
            ps=ps,
            pi=pi,
            **kwargs,
        )
        # if isinstance(Tep, SE3):
        #     Tep = Tep.A
        return solver.solve(ets=self, Tep=Tep, q0=q0) 
[docs]    def ikine_QP(
        self,
        Tep: Union[NDArray, SE3],
        q0: Union[ArrayLike, None] = None,
        ilimit: int = 30,
        slimit: int = 100,
        tol: float = 1e-6,
        mask: Union[ArrayLike, None] = None,
        joint_limits: bool = True,
        seed: Union[int, None] = None,
        kj=1.0,
        ks=1.0,
        kq: float = 0.0,
        km: float = 0.0,
        ps: float = 0.0,
        pi: Union[NDArray, float] = 0.3,
        **kwargs,
    ):
        r"""
        Quadratic Programming Numerical Inverse Kinematics Solver
        A method that provides functionality to perform numerical inverse kinematics
        (IK) using a quadratic progamming approach.
        See the :ref:`Inverse Kinematics Docs Page <IK>` for more details and for a
        **tutorial** on numerical IK, see `here <https://bit.ly/3ak5GDi>`_.
        Parameters
        ----------
        Tep
            The desired end-effector pose
        q0
            The initial joint coordinate vector
        ilimit
            How many iterations are allowed within a search before a new search
            is started
        slimit
            How many searches are allowed before being deemed unsuccessful
        tol
            Maximum allowed residual error E
        mask
            A 6 vector which assigns weights to Cartesian degrees-of-freedom
            error priority
        joint_limits
            Reject solutions with joint limit violations
        seed
            A seed for the private RNG used to generate random joint coordinate
            vectors
        kj
            A gain for joint velocity norm minimisation
        ks
            A gain which adjusts the cost of slack (intentional error)
        kq
            The gain for joint limit avoidance. Setting to 0.0 will remove this
            completely from the solution
        km
            The gain for maximisation. Setting to 0.0 will remove this completely
            from the solution
        ps
            The minimum angle/distance (in radians or metres) in which the joint is
            allowed to approach to its limit
        pi
            The influence angle/distance (in radians or metres) in null space motion
            becomes active
        Raises
        ------
        ImportError
            If the package ``qpsolvers`` is not installed
        Synopsis
        --------
        Each iteration uses the following approach
        .. math::
            \vec{q}_{k+1} = \vec{q}_{k} + \dot{\vec{q}}.
        where the QP is defined as
        .. math::
            \min_x \quad f_o(\vec{x}) &= \frac{1}{2} \vec{x}^\top \mathcal{Q} \vec{x}+ \mathcal{C}^\top \vec{x}, \\
            \text{subject to} \quad \mathcal{J} \vec{x} &= \vec{\nu},  \\
            \mathcal{A} \vec{x} &\leq \mathcal{B},  \\
            \vec{x}^- &\leq \vec{x} \leq \vec{x}^+
        with
        .. math::
            \vec{x} &=
            \begin{pmatrix}
                \dvec{q} \\ \vec{\delta}
            \end{pmatrix} \in \mathbb{R}^{(n+6)}  \\
            \mathcal{Q} &=
            \begin{pmatrix}
                \lambda_q \mat{1}_{n} & \mathbf{0}_{6 \times 6} \\ \mathbf{0}_{n \times n} & \lambda_\delta \mat{1}_{6}
            \end{pmatrix} \in \mathbb{R}^{(n+6) \times (n+6)} \\
            \mathcal{J} &=
            \begin{pmatrix}
                \mat{J}(\vec{q}) & \mat{1}_{6}
            \end{pmatrix} \in \mathbb{R}^{6 \times (n+6)} \\
            \mathcal{C} &=
            \begin{pmatrix}
                \mat{J}_m \\ \bf{0}_{6 \times 1}
            \end{pmatrix} \in \mathbb{R}^{(n + 6)} \\
            \mathcal{A} &=
            \begin{pmatrix}
                \mat{1}_{n \times n + 6} \\
            \end{pmatrix} \in \mathbb{R}^{(l + n) \times (n + 6)} \\
            \mathcal{B} &=
            \eta
            \begin{pmatrix}
                \frac{\rho_0 - \rho_s}
                        {\rho_i - \rho_s} \\
                \vdots \\
                \frac{\rho_n - \rho_s}
                        {\rho_i - \rho_s}
            \end{pmatrix} \in \mathbb{R}^{n} \\
            \vec{x}^{-, +} &=
            \begin{pmatrix}
                \dvec{q}^{-, +} \\
                \vec{\delta}^{-, +}
            \end{pmatrix} \in \mathbb{R}^{(n+6)},
        where :math:`\vec{\delta} \in \mathbb{R}^6` is the slack vector,
        :math:`\lambda_\delta \in \mathbb{R}^+` is a gain term which adjusts the
        cost of the norm of the slack vector in the optimiser,
        :math:`\dvec{q}^{-,+}` are the minimum and maximum joint velocities, and
        :math:`\dvec{\delta}^{-,+}` are the minimum and maximum slack velocities.
        Examples
        --------
        The following example gets the ``ets`` of a ``panda`` robot object, makes a goal
        pose ``Tep``, and then solves for the joint coordinates which result in the pose
        ``Tep`` using the `ikine_QP` method.
        .. runblock:: pycon
        >>> import roboticstoolbox as rtb
        >>> panda = rtb.models.Panda().ets()
        >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
        >>> panda.ikine_QP(Tep)
        Notes
        -----
        When using the this method, the initial joint coordinates :math:`q_0`, should correspond
        to a non-singular manipulator pose, since it uses the manipulator Jacobian.
        This class supports null-space motion to assist with maximising manipulability and
        avoiding joint limits. These are enabled by setting kq and km to non-zero values.
        References
        ----------
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I:
          Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022).
        - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II:
          Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022).
        See Also
        --------
        :py:class:`~roboticstoolbox.robot.IK.IK_NR`
            An IK Solver class which implements the Newton-Raphson optimisation technique
        ikine_LM
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_LM` class as a method within the :py:class:`ETS` class
        ikine_GN
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_GN` class as a method within the :py:class:`ETS` class
        ikine_NR
            Implements the :py:class:`~roboticstoolbox.robot.IK.IK_NR` class as a method within the :py:class:`ETS` class
        .. versionchanged:: 1.0.4
            Added the Quadratic Programming IK solver method on the `ETS` class
        """  # noqa: E501
        solver = IK_QP(
            ilimit=ilimit,
            slimit=slimit,
            tol=tol,
            joint_limits=joint_limits,
            mask=mask,
            seed=seed,
            kj=kj,
            ks=ks,
            kq=kq,
            km=km,
            ps=ps,
            pi=pi,
            **kwargs,
        )
        # if isinstance(Tep, SE3):
        #     Tep = Tep.A
        return solver.solve(ets=self, Tep=Tep, q0=q0)  
[docs]class ETS2(BaseETS):
    """
    This class implements an elementary transform sequence (ETS) for 2D
    :param arg: Function to compute ET value
    An instance can contain an elementary transform (ET) or an elementary
    transform sequence (ETS). It has list-like properties by subclassing
    UserList, which means we can perform indexing, slicing pop, insert, as well
    as using it as an iterator over its values.
    - ``ETS()`` an empty ETS list
    - ``ET2.XY(η)`` is a constant elementary transform
    - ``ET2.XY(η, 'deg')`` as above but the angle is expressed in degrees
    - ``ET2.XY()`` is a joint variable, the value is left free until evaluation
      time
    - ``ET2.XY(j=J)`` as above but the joint index is explicitly given, this
      might correspond to the joint number of a multi-joint robot.
    - ``ET2.XY(flip=True)`` as above but the joint moves in the opposite sense
    where ``XY`` is one of ``R``, ``tx``, ``ty``.
    Example:
        .. runblock:: pycon
            >>> from roboticstoolbox import ETS2 as ET2
            >>> e = ET2.R(0.3)  # a single ET, rotation about z
            >>> len(e)
            >>> e = ET2.R(0.3) * ET2.tx(2)  # an ETS
            >>> len(e)                      # of length 2
            >>> e[1]                        # an ET sliced from the ETS
    :references:
        - Kinematic Derivatives using the Elementary Transform Sequence,
          J. Haviland and P. Corke
    :seealso: :func:`r`, :func:`tx`, :func:`ty`
    """
    def __init__(
        self,
        arg: Union[
            List[Union["ETS2", ET2]], List[ET2], List["ETS2"], ET2, "ETS2", None
        ] = None,
    ):
        super().__init__()
        if isinstance(arg, list):
            for item in arg:
                if isinstance(item, ET2):
                    self.data.append(deepcopy(item))
                elif isinstance(item, ETS2):
                    for ets_item in item:
                        self.data.append(deepcopy(ets_item))
                else:
                    raise TypeError("bad arg")
        elif isinstance(arg, ET2):
            self.data.append(deepcopy(arg))
        elif isinstance(arg, ETS2):
            for ets_item in arg:
                self.data.append(deepcopy(ets_item))
        elif arg is None:
            self.data = []
        else:
            raise TypeError("Invalid arg")
        self._update_internals()
        self._ndims = 2
        self._auto_jindex = False
        # Check if jindices are set
        joints = self.joints()
        # Number of joints with a jindex
        jindices = 0
        # Number of joints with a sequential jindex (j[2] -> jindex = 2)
        seq_jindex = 0
        # Count them up
        for j, joint in enumerate(joints):
            if joint.jindex is not None:
                jindices += 1
                if joint.jindex == j:
                    seq_jindex += 1
        if (
            jindices == self.n - 1
            and seq_jindex == self.n - 1
            and joints[-1].jindex is None
        ):
            # ets has sequential jindicies, except for the last.
            joints[-1].jindex = self.n - 1
            self._auto_jindex = True
        elif jindices > 0 and not jindices == self.n:
            raise ValueError(
                "You can not have some jindices set for the ET's in arg. It must be all"
                " or none"
            )  # pragma: nocover
        elif jindices == 0 and self.n > 0:
            # Set them ourself
            for j, joint in enumerate(joints):
                joint.jindex = j
            self._auto_jindex = True
[docs]    def __mul__(self, other: Union[ET2, "ETS2"]) -> "ETS2":
        if isinstance(other, ET2):
            return ETS2([*self.data, other])
        else:
            return ETS2([*self.data, *other.data])  # pragma: nocover 
    def __rmul__(self, other: Union[ET2, "ETS2"]) -> "ETS2":
        return ETS2([other, self.data])  # pragma: nocover
    def __imul__(self, rest: "ETS2"):
        return self + rest  # pragma: nocover
    def __add__(self, rest) -> "ETS2":
        return self.__mul__(rest)  # pragma: nocover
[docs]    def compile(self) -> "ETS2":
        """
        Compile an ETS2
        :return: optimised ETS2
        Perform constant folding for faster evaluation.  Consecutive constant
        ETs are compounded, leading to a constant ET which is denoted by
        ``SE3`` when displayed.
        :seealso: :func:`isconstant`
        """
        const = None
        ets = ETS2()
        for et in self:
            if et.isjoint:
                # a joint
                if const is not None:
                    # flush the constant
                    if not np.array_equal(const, np.eye(3)):
                        ets *= ET2.SE2(const)
                    const = None
                ets *= et  # emit the joint ET
            else:
                # not a joint
                if const is None:
                    const = et.A()
                else:
                    const = const @ et.A()
        if const is not None:
            # flush the constant, tool transform
            if not np.array_equal(const, np.eye(3)):
                ets *= ET2.SE2(const)
        return ets 
[docs]    def insert(
        self,
        arg: Union[ET2, "ETS2"],
        i: int = -1,
    ) -> None:
        """
        Insert value
        :param i: insert an ET or ETS into the ETS, default is at the end
        :param arg: the elementary transform or sequence to insert
        Inserts an ET or ETS into the ET sequence.  The inserted value is at position
        ``i``.
        Example:
        .. runblock:: pycon
            >>> from roboticstoolbox import ET2
            >>> e = ET2.R() * ET2.tx(1) * ET2.R() * ET2.tx(1)
            >>> f = ET2.R()
            >>> e.insert(f, 2)
            >>> e
        """
        if isinstance(arg, ET2):
            if i == -1:
                self.data.append(arg)
            else:
                self.data.insert(i, arg)
        elif isinstance(arg, ETS2):
            if i == -1:
                for et in arg:
                    self.data.append(et)
            else:
                for j, et in enumerate(arg):
                    self.data.insert(i + j, et)
        self._update_internals() 
[docs]    def fkine(
        self,
        q: ArrayLike,
        base: Union[NDArray, SE2, None] = None,
        tool: Union[NDArray, SE2, None] = None,
        include_base: bool = True,
    ) -> SE2:
        """
        Forward kinematics
        :param q: Joint coordinates
        :type q: ArrayLike
        :param base: base transform, optional
        :param tool: tool transform, optional
        :return: The transformation matrix representing the pose of the
            end-effector
        - ``T = ets.fkine(q)`` evaluates forward kinematics for the robot at
          joint configuration ``q``.
        **Trajectory operation**:
        If ``q`` has multiple rows (mxn), it is considered a trajectory and the
        result is an ``SE2`` instance with ``m`` values.
        .. note::
            - The robot's base tool transform, if set, is incorporated
              into the result.
            - A tool transform, if provided, is incorporated into the result.
            - Works from the end-effector link to the base
        :references:
            - Kinematic Derivatives using the Elementary Transform
              Sequence, J. Haviland and P. Corke
        """
        ret = SE2.Empty()
        fk = self.eval(q, base, tool, include_base)
        if fk.dtype == "O":
            # symbolic
            fk = np.array(simplify(fk))
        if fk.ndim == 3:
            for T in fk:
                ret.append(SE2(T, check=False))  # type: ignore
        else:
            ret = SE2(fk, check=False)
        return ret 
    def eval(
        self,
        q: ArrayLike,
        base: Union[NDArray, SE2, None] = None,
        tool: Union[NDArray, SE2, None] = None,
        include_base: bool = True,
    ) -> NDArray:
        """
        Forward kinematics
        :param q: Joint coordinates
        :type q: ArrayLike
        :param base: base transform, optional
        :param tool: tool transform, optional
        :return: The transformation matrix representing the pose of the
            end-effector
        - ``T = ets.fkine(q)`` evaluates forward kinematics for the robot at
          joint configuration ``q``.
        **Trajectory operation**:
        If ``q`` has multiple rows (mxn), it is considered a trajectory and the
        result is an ``SE2`` instance with ``m`` values.
        .. note::
            - The robot's base tool transform, if set, is incorporated
              into the result.
            - A tool transform, if provided, is incorporated into the result.
            - Works from the end-effector link to the base
        :references:
            - Kinematic Derivatives using the Elementary Transform
              Sequence, J. Haviland and P. Corke
        """
        q = getmatrix(q, (None, None))
        l, _ = q.shape  # type: ignore
        end = self[-1]
        if base is None:
            bases = None
        elif isinstance(base, SE2):
            bases = np.array(base.A)
        elif np.array_equal(base, np.eye(3)):  # pragma: nocover
            bases = None
        else:  # pragma: nocover
            bases = base
        if tool is None:
            tools = None
        elif isinstance(tool, SE2):
            tools = np.array(tool.A)
        elif np.array_equal(tool, np.eye(3)):  # pragma: nocover
            tools = None
        else:  # pragma: nocover
            tools = tool
        if l > 1:
            T = np.zeros((l, 3, 3), dtype=object)
        else:
            T = np.zeros((3, 3), dtype=object)
        for k, qk in enumerate(q):  # type: ignore
            link = end  # start with last link
            jindex = 0 if link.jindex is None and link.isjoint else link.jindex
            Tk = link.A(qk[jindex])
            if tools is not None:
                Tk = Tk @ tools
            # add remaining links, back toward the base
            for i in range(self.m - 2, -1, -1):
                link = self.data[i]
                jindex = 0 if link.jindex is None and link.isjoint else link.jindex
                A = link.A(qk[jindex])
                if A is not None:
                    Tk = A @ Tk
            # add base transform if it is set
            if include_base is True and bases is not None:
                Tk = bases @ Tk
            # append
            if l > 1:
                T[k, :, :] = Tk
                # ret.append(SE2(Tk, check=False))  # type: ignore
            else:
                T = Tk
                # ret = SE2(Tk, check=False)
        return T
[docs]    def jacob0(
        self,
        q: ArrayLike,
    ) -> NDArray:
        # very inefficient implementation, just put a 1 in last row
        # if its a rotation joint
        q = getvector(q)
        j = 0
        J = np.zeros((3, self.n))
        etjoints = self.joint_idx()
        if not np.all(np.array([self[i].jindex for i in etjoints])):
            # not all joints have a jindex it is required, set them
            for j in range(self.n):
                i = etjoints[j]
                self[i].jindex = j
        for j in range(self.n):
            i = etjoints[j]
            if self[i].jindex is not None:
                jindex = self[i].jindex
            else:
                jindex = 0  # pragma: nocover
            # jindex = 0 if self[i].jindex is None else self[i].jindex
            axis = self[i].axis
            if axis == "R":
                dTdq = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 0]]) @ self[i].A(
                    q[jindex]  # type: ignore
                )
            elif axis == "tx":
                dTdq = np.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]])
            elif axis == "ty":
                dTdq = np.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]])
            else:  # pragma: nocover
                raise TypeError("Invalid axes")
            E0 = ETS2(self[:i])
            if len(E0) > 0:
                dTdq = E0.fkine(q).A @ dTdq
            Ef = ETS2(self[i + 1 :])
            if len(Ef) > 0:
                dTdq = dTdq @ Ef.fkine(q).A
            T = self.fkine(q).A
            dRdt = dTdq[:2, :2] @ T[:2, :2].T
            J[:2, j] = dTdq[:2, 2]
            J[2, j] = dRdt[1, 0]
        return J 
[docs]    def jacobe(
        self,
        q: ArrayLike,
    ):
        r"""
        Jacobian in base frame
        :param q: joint coordinates
        :type q: ArrayLike
        :return: Jacobian matrix
        ``jacobe(q)`` is the manipulator Jacobian matrix which maps joint
        velocity to end-effector spatial velocity.
        End-effector spatial velocity :math:`\nu = (v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)^T`
        is related to joint velocity by :math:`{}^{e}\nu = {}^{e}\mathbf{J}_0(q) \dot{q}`.
        :seealso: :func:`jacob`, :func:`hessian0`
        """  # noqa
        T = self.fkine(q, include_base=False).A
        return tr2jac2(T.T) @ self.jacob0(q)