Jacobians

moro provides tools for computing geometric Jacobians for the end-effector, arbitrary points on the manipulator, and link centers of mass.

The geometric Jacobian relates joint velocities to the linear and angular velocity of a point attached to the robot.

This section focuses on how to compute and inspect Jacobians in moro. For the mathematical derivation and interpretation of differential kinematics, see Theory → Differential Kinematics.

End-effector Jacobian

Once a Robot has been defined, the geometric Jacobian of the end-effector is available through the J property.

Consider a planar 2R manipulator:

from moro import Robot
from moro.abc import q1, q2, l1, l2

robot = Robot(
    (l1, 0, 0, q1, "r"),
    (l2, 0, 0, q2, "r"),
)

The end-effector Jacobian is:

J = robot.J
J

For a robot with \(n\) degrees of freedom, the geometric Jacobian has size:

\[ 6 \times n. \]

It can be written as:

\[\begin{split} J = \begin{bmatrix} J_v \\ J_\omega \end{bmatrix}, \end{split}\]

where:

  • \(J_v\) relates joint velocities to the linear velocity of the point;

  • \(J_\omega\) relates joint velocities to its angular velocity.

For robot.J, the point of interest is the origin of the final frame {n}, that is, the end-effector origin.

Linear and angular components

The linear and angular parts of a geometric Jacobian can be obtained directly by slicing the resulting SymPy matrix:

Jv = robot.J[:3, :]
Jw = robot.J[3:, :]

The upper part,

Jv

contains the linear-velocity contribution, while:

Jw

contains the angular-velocity contribution.

For a joint-velocity vector:

\[\begin{split} \dot q = \begin{bmatrix} \dot q_1 \\ \dot q_2 \\ \vdots \\ \dot q_n \end{bmatrix}, \end{split}\]

the corresponding end-effector velocities satisfy:

\[ v = J_v \dot q, \]

and:

\[ \omega = J_\omega \dot q. \]

Because the Jacobian is symbolic, these relationships can be inspected before assigning numerical values to the joint coordinates.

Jacobian of an arbitrary point

The Jacobian does not need to be restricted to the origin of the end-effector frame.

Use:

robot.J_point(point, i)

to compute the geometric Jacobian of a point attached to link i.

The coordinates in point must be expressed with respect to frame {i}.

For example:

Jp = robot.J_point([0.2, 0, 0], 2)

computes the Jacobian of a point located \(0.2\) units along the \(x_2\)-axis from the origin of frame {2}.

The point can be specified using a list, tuple, or a compatible SymPy vector.

For the origin of frame {i}, use:

Ji = robot.J_point([0, 0, 0], i)

The end-effector Jacobian itself is equivalent to:

robot.J_point([0, 0, 0], robot.dof)

This makes J_point() the most general Jacobian interface for points rigidly attached to the robot links.

Revolute and prismatic joint contributions

The columns of the geometric Jacobian depend on the corresponding joint type.

For a revolute joint \(j\), the contribution to a point located at \(p\) is:

\[ J_{v_j} = z_{j-1} \times \left( p-r_{O_{j-1}} \right), \]

and:

\[ J_{\omega_j} = z_{j-1}. \]

For a prismatic joint:

\[ J_{v_j} = z_{j-1}, \]

and:

\[ J_{\omega_j} = 0. \]

moro builds these contributions automatically from the joint types stored in the Robot model.

For a point attached to link i, joints located after that link do not affect its motion. Their corresponding Jacobian columns are therefore zero.

For example, in a three-joint robot, the Jacobian of a point attached to link 2 has no contribution from joint 3.

The mathematical derivation of these expressions is covered in Theory → Differential Kinematics.

Center-of-mass Jacobians

moro also provides Jacobians for the centers of mass of individual links.

These are especially useful in dynamic modeling.

Before computing them, the center-of-mass position of each link must be defined:

robot.cm_positions = [
    (...),
    (...),
]

Each center-of-mass position is expressed with respect to the corresponding link frame.

For example, for a planar 2R robot:

from moro.abc import lc1, lc2

robot.cm_positions = [
    (-lc1, 0, 0),
    (-lc2, 0, 0),
]

Full center-of-mass Jacobian

The geometric Jacobian of the center of mass of link i is:

Jcm = robot.J_cm_i(i)

For example:

Jcm1 = robot.J_cm_i(1)
Jcm2 = robot.J_cm_i(2)

Each result has size:

\[ 6 \times n. \]

Linear center-of-mass Jacobian

The linear part is available directly through:

Jv_cm = robot.Jv_cm_i(i)

For example:

Jv1 = robot.Jv_cm_i(1)

This is equivalent to the first three rows of the full center-of-mass Jacobian.

Angular center-of-mass Jacobian

The angular part is:

Jw_cm = robot.Jw_cm_i(i)

For example:

Jw1 = robot.Jw_cm_i(1)

This corresponds to the lower three rows of robot.J_cm_i(i).

These quantities are used internally when constructing dynamic terms such as the manipulator inertia matrix.

Evaluating a configuration

As with the forward-kinematics results, Jacobians returned by moro are symbolic SymPy matrices.

Consider the configuration:

\[ l_1 = 1,\qquad l_2 = 0.8,\qquad q_1 = \frac{\pi}{4},\qquad q_2 = -\frac{\pi}{6}. \]

Define:

from sympy import pi

values = {
    l1: 1,
    l2: 0.8,
    q1: pi / 4,
    q2: -pi / 6,
}

The end-effector Jacobian can then be evaluated with:

J_num = robot.J.subs(values)

and converted explicitly to floating-point values with:

J_num.evalf()

The same approach applies to arbitrary-point Jacobians:

Jp = robot.J_point([0.2, 0, 0], 2)

Jp_num = Jp.subs(values).evalf()

and to center-of-mass Jacobians:

Jcm2_num = robot.J_cm_i(2).subs(values).evalf()

provided all symbolic center-of-mass parameters are also included in the substitution dictionary.

Working with symbolic results

Because the Jacobians are SymPy matrices, they can be manipulated with standard symbolic operations.

For example:

from sympy import simplify

J = simplify(robot.J)

The linear and angular parts can be extracted:

Jv = J[:3, :]
Jw = J[3:, :]

Individual columns can also be inspected:

J1 = J[:, 0]
J2 = J[:, 1]

This is useful when studying how individual joints contribute to the velocity of the end-effector.

You can also inspect the rank of a numerically evaluated Jacobian:

J_num = robot.J.subs(values)

J_num.rank()

or compute symbolic expressions involving its entries.

For larger symbolic models, simplification may become computationally expensive, so it is often useful to simplify only the expressions that are relevant to the current analysis.

A worked example

Consider again a planar two-link robot:

from moro import Robot
from moro.abc import q1, q2, l1, l2

robot = Robot(
    (l1, 0, 0, q1, "r"),
    (l2, 0, 0, q2, "r"),
)

The end-effector Jacobian is:

J = robot.J

Its linear component is:

Jv = J[:3, :]

and its angular component is:

Jw = J[3:, :]

Now consider a point located halfway along the second link, assuming l2 represents its full length:

J_mid = robot.J_point(
    [-l2 / 2, 0, 0],
    2,
)

This point has a different linear Jacobian from the end-effector because its position relative to the joints is different.

Its angular Jacobian, however, is determined by the same revolute joints that affect link 2.

We can compare the linear parts:

Jv_end = robot.J[:3, :]
Jv_mid = J_mid[:3, :]

Now evaluate both at a specific configuration:

from sympy import pi

values = {
    l1: 1,
    l2: 0.8,
    q1: pi / 4,
    q2: -pi / 6,
}
Jv_end_num = Jv_end.subs(values).evalf()
Jv_mid_num = Jv_mid.subs(values).evalf()

This illustrates an important distinction:

same robot
    +
same joint configuration
    ↓
different point on the robot
    ↓
different linear Jacobian

The J_point() interface makes it possible to compute these quantities without defining a separate robot model.

Task-space Jacobians and Cartesian velocity

Moro 0.5.0 adds a functional differential-kinematics layer in moro.differential_kinematics. It reuses robot.J as the geometric source of truth and selects only the task components needed by the analysis.

from moro.differential_kinematics import (
    task_jacobian,
    cartesian_velocity,
    solve_velocity_ik,
)

The canonical geometric-twist component order is:

vx, vy, vz, wx, wy, wz

The convenience task presets are:

linear   -> vx, vy, vz
angular  -> wx, wy, wz
twist    -> vx, vy, vz, wx, wy, wz

Explicit ordered subsets are also supported:

J_xy = task_jacobian(robot, task=("vx", "vy"))
J_planar = task_jacobian(robot, task=("vx", "vy", "wz"))
J_custom = task_jacobian(robot, task=("wz", "vx"))

The order supplied by the user is preserved. Therefore the task ("wz", "vx") expects task-space velocities in the same order.

To evaluate the Jacobian at a configuration while keeping exact SymPy arithmetic:

from sympy import pi

Jq = task_jacobian(
    robot,
    q=[pi / 4, -pi / 6],
    task=("vx", "vy", "wz"),
    parameters={l1: 1, l2: 0.8},
)

Forward differential kinematics is evaluated with:

velocity = cartesian_velocity(
    robot,
    q=[pi / 4, -pi / 6],
    qd=[0.5, -0.2],
    task=("vx", "vy", "wz"),
    parameters={l1: 1, l2: 0.8},
)

which computes

\[ \dot x_{task}=J_{task}(q)\dot q. \]

cartesian_velocity() returns a SymPy column matrix and preserves exact symbolic numbers whenever all model parameters are resolved.

Velocity inverse kinematics

solve_velocity_ik() computes joint velocities that best realize a desired task-space velocity.

solution = solve_velocity_ik(
    robot,
    q=[pi / 4, -pi / 6],
    velocity=[0.1, 0.0],
    task=("vx", "vy"),
    parameters={l1: 1, l2: 0.8},
)

The default method is the Moore–Penrose pseudoinverse. Damped least squares is selected explicitly:

solution = solve_velocity_ik(
    robot,
    q=[pi / 4, -pi / 6],
    velocity=[0.1, 0.0],
    task=("vx", "vy"),
    method="dls",
    damping=0.05,
    parameters={l1: 1, l2: 0.8},
)

The result contains:

  • qd: the returned joint velocity;

  • unconstrained_qd: the pre-limit solution;

  • desired_velocity and achieved_velocity;

  • residual = desired_velocity - achieved_velocity;

  • residual_norm;

  • numerical Jacobian rank and condition_number;

  • success, limited, method, and an explanatory message.

success depends only on whether the final task-space residual norm is within tol. Rank deficiency or a large condition number does not automatically mean failure.

Joint-velocity limits

Optional limits are applied as post-solution clipping. Symmetric limits use one positive magnitude per joint:

joint_velocity_limits=[1.0, 0.5]

and asymmetric limits use (lower, upper) pairs:

joint_velocity_limits=[(-1.0, 0.8), (-0.4, 0.6)]

After clipping, Moro recomputes achieved velocity, residual, and success. It does not redistribute the saturated motion through other joints.

Singularity and manipulability analysis

The same task conventions can be used to inspect local kinematic capability:

from moro.differential_kinematics import (
    singular_values,
    jacobian_rank,
    condition_number,
    is_singular,
    manipulability,
)

For a planar position task:

q = [pi / 4, -pi / 6]
parameters = {l1: 1.0, l2: 0.8}
task = ("vx", "vy")

s = singular_values(robot, q, task=task, parameters=parameters)
rank = jacobian_rank(robot, q, task=task, parameters=parameters)
kappa = condition_number(robot, q, task=task, parameters=parameters)
singular = is_singular(robot, q, task=task, parameters=parameters)
w = manipulability(robot, q, task=task, parameters=parameters)

singular_values() returns a SymPy column matrix; rank is an integer; condition number and manipulability are floats; and is_singular() is a Python Boolean.

Controlling effective rank

jacobian_rank() and is_singular() accept an optional positive absolute singular-value tolerance:

is_singular(
    robot,
    q,
    task=("vx", "vy"),
    parameters=parameters,
    tol=1e-4,
)

condition_number() intentionally has no public tolerance argument and always uses Moro’s automatic SVD threshold. A user-selected rank tolerance can therefore classify a near-singular matrix differently from the automatic condition-number policy.

Manipulability requires a task

manipulability() intentionally requires task to be supplied:

w = manipulability(
    robot,
    q,
    task=("vx", "vy"),
    parameters=parameters,
)

This avoids attaching an apparently universal meaning to a quantity that depends strongly on which velocity components and units are included. Full-twist manipulability is valid, but mixes translational and angular scales.

For an overdetermined task with more task components than joints (\(m>n\)), Yoshikawa’s \(m\)-dimensional volume is zero even when the Jacobian has full column rank. In that case manipulability() can return 0.0 while is_singular() returns False.

Notes and conventions

When working with Jacobians in moro, keep the following points in mind:

  • robot.J is the geometric Jacobian of the end-effector origin;

  • geometric Jacobians have size \(6\times n\);

  • the first three rows correspond to linear velocity;

  • the last three rows correspond to angular velocity;

  • robot.J_point(point, i) expects the point coordinates to be expressed in frame {i};

  • the resulting Jacobian describes the point motion with respect to the base frame;

  • revolute and prismatic joints are handled automatically according to the robot model;

  • joints located after the link containing the point contribute zero columns;

  • center-of-mass Jacobians require robot.cm_positions to be defined;

  • Jacobian results are symbolic SymPy matrices unless numerical values are substituted.

The Jacobians returned by moro are geometric Jacobians. Analytical Jacobians associated with specific minimal orientation parameterizations are outside the current scope of this interface.

See also

  • Forward Kinematics — compute the frame positions and axes used in geometric Jacobian construction.

  • Robot Modeling — define revolute and prismatic joints and their parameters.

  • Inverse Kinematics — use Jacobian-based numerical methods to solve position inverse kinematics problems.

  • Dynamics — use center-of-mass Jacobians when deriving the manipulator dynamic model.

  • Theory → Differential Kinematics — mathematical derivation and interpretation of the geometric Jacobian.

  • API Reference → Robot — complete signatures and descriptions for J, J_point(), J_cm_i(), Jv_cm_i(), and Jw_cm_i().