Joint Space vs Cartesian Space
In robotics, we work with two fundamental coordinate spaces: joint space and Cartesian space. Understanding the relationship between these spaces is crucial for robot control, path planning, and manipulation. Today, we’ll explore these concepts and learn how to transform between them using the powerful Jacobian matrix.
Understanding Coordinate Spaces
Joint Space
Joint space represents the robot’s configuration in terms of its joint angles or joint displacements. For a robot with n joints, the joint space is an n-dimensional space where each dimension corresponds to one joint.
Characteristics of Joint Space:
- Each point represents a unique robot configuration
- Natural representation for robot control
- Simple bounds (joint limits)
- Intuitive for robot programmers
- Dimension equals number of joints (DOF)
For a 6-DOF robot, joint space is a 6-dimensional space: [θ₁, θ₂, θ₃, θ₄, θ₅, θ₆]
Cartesian Space
Cartesian space (or task space) represents the robot’s end-effector position and orientation in 3D space. It’s the space where we typically specify tasks like “pick up the object” or “move to position (x, y, z).”
Characteristics of Cartesian Space:
- Represents the robot’s position and orientation in the world
- Intuitive for human operators
- Complex bounds (workspace boundaries)
- May have different dimensions than joint space
- Includes position (3D) and orientation (3D for rotation)
For most robots, Cartesian space is 6-dimensional: [x, y, z, roll, pitch, yaw]
The Jacobian Matrix
The Jacobian matrix is the mathematical bridge between joint space and Cartesian space. It represents the relationship between joint velocities and end-effector velocities.
Mathematical Definition
For a robot with n joints and m end-effector variables, the Jacobian J is an m×n matrix:
J = [∂x/∂θ₁ ∂x/∂θ₂ ... ∂x/∂θₙ]
[∂y/∂θ₁ ∂y/∂θ₂ ... ∂y/∂θₙ]
[∂z/∂θ₁ ∂z/∂θ₂ ... ∂z/∂θₙ]
[∂φ/∂θ₁ ∂φ/∂θ₂ ... ∂φ/∂θₙ]
Where:
- x, y, z are end-effector position coordinates
- φ represents orientation angles
- θᵢ are joint angles
Computing the Jacobian
Let’s implement a Jacobian calculator for a 2D robot:
import numpy as np
class Robot2DJacobian:
def __init__(self, link_lengths):
"""
Initialize a 2D robot arm with Jacobian capabilities
Args:
link_lengths: List of link lengths
"""
self.link_lengths = link_lengths
self.num_joints = len(link_lengths)
def forward_kinematics(self, joint_angles):
"""
Calculate joint positions from joint angles
Args:
joint_angles: List of joint angles in radians
Returns:
List of joint positions and end-effector position
"""
positions = [(0, 0)]
x, y = 0, 0
angle_sum = 0
for i, (length, angle) in enumerate(zip(self.link_lengths, joint_angles)):
angle_sum += angle
x += length * np.cos(angle_sum)
y += length * np.sin(angle_sum)
positions.append((x, y))
return positions, (x, y)
def jacobian(self, joint_angles):
"""
Calculate the Jacobian matrix for the 2D robot
Args:
joint_angles: List of joint angles in radians
Returns:
2xN Jacobian matrix (2D position, N joints)
"""
x_end, y_end = self.forward_kinematics(joint_angles)[1]
J = np.zeros((2, self.num_joints))
angle_sum = 0
for i in range(self.num_joints):
angle_sum += joint_angles[i]
# Partial derivatives for x and y
for j in range(i, self.num_joints):
angle_sum += joint_angles[j]
J[0, i] -= self.link_lengths[j] * np.sin(angle_sum)
J[1, i] += self.link_lengths[j] * np.cos(angle_sum)
angle_sum -= joint_angles[j]
return J
def velocity_transform(self, joint_angles, joint_velocities):
"""
Transform joint velocities to Cartesian velocities
Args:
joint_angles: Current joint angles
joint_velocities: Joint velocities [θ̇₁, θ̇₂, ...]
Returns:
Cartesian velocities [ẋ, ẏ]
"""
J = self.jacobian(joint_angles)
return J @ joint_velocities
# Example usage
robot = Robot2DJacobian([2.0, 1.5])
joint_angles = [np.pi/4, np.pi/6]
# Calculate Jacobian
J = robot.jacobian(joint_angles)
print("Jacobian Matrix:")
print(J)
# Test velocity transformation
joint_velocities = [0.1, -0.05] # rad/s
cartesian_velocities = robot.velocity_transform(joint_angles, joint_velocities)
print(f"\nJoint velocities: {joint_velocities}")
print(f"Cartesian velocities: {cartesian_velocities}")
3D Jacobian Implementation
For 3D robots, the Jacobian is more complex as it includes both linear and angular velocities:
def compute_3d_jacobian(joint_angles, link_lengths):
"""
Compute 3D Jacobian for a simple robot arm
Args:
joint_angles: [θ1, θ2, θ3] joint angles
link_lengths: [L1, L2, L3] link lengths
Returns:
6x3 Jacobian matrix [linear; angular]
"""
# Extract joint angles
theta1, theta2, theta3 = joint_angles
L1, L2, L3 = link_lengths
# Position of end-effector
x = L1*np.cos(theta1) + L2*np.cos(theta1)*np.cos(theta2) + L3*np.cos(theta1)*np.cos(theta2+theta3)
y = L1*np.sin(theta1) + L2*np.sin(theta1)*np.cos(theta2) + L3*np.sin(theta1)*np.cos(theta2+theta3)
z = L2*np.sin(theta2) + L3*np.sin(theta2+theta3)
# Linear velocity Jacobian (3x3)
J_linear = np.array([
[-L1*np.sin(theta1) - L2*np.sin(theta1)*np.cos(theta2) - L3*np.sin(theta1)*np.cos(theta2+theta3),
-L2*np.cos(theta1)*np.sin(theta2) - L3*np.cos(theta1)*np.sin(theta2+theta3),
-L3*np.cos(theta1)*np.sin(theta2+theta3)],
[L1*np.cos(theta1) + L2*np.cos(theta1)*np.cos(theta2) + L3*np.cos(theta1)*np.cos(theta2+theta3),
-L2*np.sin(theta1)*np.sin(theta2) - L3*np.sin(theta1)*np.sin(theta2+theta3),
-L3*np.sin(theta1)*np.sin(theta2+theta3)],
[0,
L2*np.cos(theta2) + L3*np.cos(theta2+theta3),
L3*np.cos(theta2+theta3)]
])
# Angular velocity Jacobian (3x3)
J_angular = np.array([
[0, 0, 0],
[0, 0, 0],
[1, 1, 1]
])
# Combine linear and angular parts
J = np.vstack([J_linear, J_angular])
return J, np.array([x, y, z])
# Test 3D Jacobian
joint_angles_3d = [np.pi/4, np.pi/3, np.pi/6]
link_lengths_3d = [1.0, 0.8, 0.5]
J_3d, position = compute_3d_jacobian(joint_angles_3d, link_lengths_3d)
print("3D Jacobian Matrix:")
print(J_3d)
print(f"\nEnd-effector position: {position}")
Practical Applications
1. Singularities and Jacobian Analysis
Singularities occur when the Jacobian loses rank, meaning the robot loses degrees of freedom:
def check_singularity(J, threshold=1e-6):
"""
Check if the Jacobian is near a singularity
Args:
J: Jacobian matrix
threshold: Singular value threshold
Returns:
is_singular: Boolean indicating if singular
condition_number: Condition number of the Jacobian
"""
try:
singular_values = np.linalg.svd(J, compute_uv=False)
condition_number = np.max(singular_values) / np.min(singular_values)
is_singular = np.min(singular_values) < threshold
return is_singular, condition_number
except:
return True, float('inf')
# Test for singularities
for theta2 in [0, np.pi/2, np.pi]:
test_angles = [np.pi/4, theta2, np.pi/6]
J_test, _ = compute_3d_jacobian(test_angles, link_lengths_3d)
is_singular, cond_num = check_singularity(J_test)
print(f"θ2 = {theta2:.2f}: Singular = {is_singular}, Condition number = {cond_num:.2f}")
2. Inverse Kinematics using Jacobian
The Jacobian can be used for iterative inverse kinematics:
def jacobian_ik(target_position, initial_angles, link_lengths, max_iterations=100, tolerance=1e-3):
"""
Solve inverse kinematics using Jacobian pseudo-inverse
Args:
target_position: Desired end-effector position [x, y, z]
initial_angles: Initial joint angles guess
link_lengths: Link lengths
max_iterations: Maximum number of iterations
tolerance: Position tolerance
Returns:
joint_angles: Solution joint angles
success: Whether solution was found
"""
current_angles = np.array(initial_angles)
for iteration in range(max_iterations):
# Calculate current position and Jacobian
J, current_pos = compute_3d_jacobian(current_angles, link_lengths)
# Check if we're close enough to target
error = np.linalg.norm(target_position - current_pos)
if error < tolerance:
return current_angles, True
# Calculate velocity using pseudo-inverse
delta_pos = target_position - current_pos
try:
J_pinv = np.linalg.pinv(J)
delta_angles = J_pinv @ delta_pos
current_angles += 0.1 * delta_angles # Step size
except:
break
return current_angles, False
# Test Jacobian IK
target_pos = [1.5, 1.0, 0.5]
initial_guess = [0.5, 0.5, 0.5]
solution, success = jacobian_ik(target_pos, initial_guess, link_lengths_3d)
print(f"Target position: {target_pos}")
print(f"Initial guess: {initial_guess}")
print(f"Solution: {solution}")
print(f"Success: {success}")
Workspace Analysis
The Jacobian helps us understand the robot’s workspace and manipulability:
def manipulability_index(J):
"""
Calculate the manipulability index of the robot
Args:
J: Jacobian matrix
Returns:
manipulability: Scalar measure of dexterity
"""
return np.sqrt(np.linalg.det(J @ J.T))
def analyze_workspace(robot, joint_ranges, resolution=20):
"""
Analyze robot workspace manipulability
Args:
robot: Robot instance with Jacobian method
joint_ranges: List of (min, max) angle ranges for each joint
resolution: Number of points per dimension
"""
# Generate random joint configurations
manipulabilities = []
positions = []
for _ in range(1000):
# Random joint angles within limits
joint_angles = [np.random.uniform(low, high) for low, high in joint_ranges]
# Calculate position and manipulability
J = robot.jacobian(joint_angles)
manip = manipulability_index(J)
pos, _ = robot.forward_kinematics(joint_angles)
manipulabilities.append(manip)
positions.append(pos[-1]) # End-effector position
return positions, manipulabilities
# Analyze workspace
joint_ranges = [(-np.pi, np.pi), (-np.pi/2, np.pi/2)] # Joint angle limits
positions, manipulabilities = analyze_workspace(robot, joint_ranges)
print(f"Average manipulability: {np.mean(manipulabilities):.4f}")
print(f"Min manipulability: {np.min(manipulabilities):.4f}")
print(f"Max manipulability: {np.max(manipulabilities):.4f}")
Key Takeaways
- Joint Space: Robot configuration in terms of joint angles
- Cartesian Space: End-effector position and orientation in world coordinates
- Jacobian Matrix: Mathematical relationship between joint and Cartesian velocities
- Singularities: Configurations where the robot loses degrees of freedom
- Manipulability: Measure of how well the robot can move in different directions
- Applications: Inverse kinematics, singularity analysis, workspace optimization
Next Steps
Tomorrow, we’ll explore robot workspace analysis and understand how to visualize and analyze the reachable workspace of different robot configurations. We’ll also implement workspace boundary calculation and optimization techniques.
Practice Exercise: Implement a complete Jacobian-based inverse kinematics solver for a 3D robot arm. Test it with different target positions and analyze the convergence behavior. Experiment with different initial guesses to understand how they affect the solution.