Robot Workspace Analysis
The workspace of a robot is one of the most important concepts in robotics. It defines all the positions and orientations that the robot’s end-effector can reach. Understanding workspace analysis is crucial for robot design, task planning, and operation. Today, we’ll dive deep into workspace analysis techniques and learn how to visualize and optimize robot workspaces.
What is Robot Workspace?
The workspace of a robot is the region of space that the robot’s end-effector can reach. It’s typically divided into several types:
1. Reachable Workspace
The set of all points that can be reached by the end-effector, regardless of orientation.
2. Dexterous Workspace
The subset of the reachable workspace where the end-effector can achieve any desired orientation.
3. Constant Orientation Workspace
The set of points reachable with a fixed end-effector orientation.
Analyzing 2D Robot Workspace
Let’s start with a comprehensive 2D workspace analysis:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import matplotlib.patches as patches
class Robot2DWorkspace:
def __init__(self, link_lengths):
"""
Initialize 2D robot for workspace analysis
Args:
link_lengths: List of link lengths
"""
self.link_lengths = np.array(link_lengths)
self.total_length = np.sum(link_lengths)
self.num_joints = len(link_lengths)
def forward_kinematics(self, joint_angles):
"""
Calculate end-effector position from joint angles
Args:
joint_angles: List of joint angles in radians
Returns:
End-effector position (x, y)
"""
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)
return x, y
def generate_workspace_boundary(self, num_points=1000):
"""
Generate the boundary of the reachable workspace
Args:
num_points: Number of points to generate
Returns:
Boundary points (x, y)
"""
# The workspace boundary is a circle with radius = total link length
theta = np.linspace(0, 2*np.pi, num_points)
x_boundary = self.total_length * np.cos(theta)
y_boundary = self.total_length * np.sin(theta)
return x_boundary, y_boundary
def generate_reachable_points(self, num_samples=5000):
"""
Generate random reachable points within the workspace
Args:
num_samples: Number of random points to generate
Returns:
Array of reachable points (x, y)
"""
reachable_points = []
for _ in range(num_samples):
# Generate random joint angles
joint_angles = np.random.uniform(-np.pi, np.pi, self.num_joints)
# Calculate end-effector position
x, y = self.forward_kinematics(joint_angles)
# Check if position is reachable (within total length)
if np.sqrt(x**2 + y**2) <= self.total_length:
reachable_points.append([x, y])
return np.array(reachable_points)
def plot_workspace(self, show_boundary=True, show_samples=True, show_obstacles=False):
"""
Visualize the robot workspace
Args:
show_boundary: Show workspace boundary
show_samples: Show sample reachable points
show_obstacles: Show obstacle positions
"""
fig, ax = plt.subplots(1, 1, figsize=(12, 10))
# Plot workspace boundary
if show_boundary:
x_boundary, y_boundary = self.generate_workspace_boundary()
ax.plot(x_boundary, y_boundary, 'b-', linewidth=2, label='Workspace Boundary')
# Fill workspace
workspace_circle = Circle((0, 0), self.total_length, fill=True,
alpha=0.1, color='blue', label='Reachable Workspace')
ax.add_patch(workspace_circle)
# Plot sample reachable points
if show_samples:
reachable_points = self.generate_reachable_points()
ax.scatter(reachable_points[:, 0], reachable_points[:, 1],
c='red', s=1, alpha=0.6, label='Reachable Points')
# Plot obstacles if specified
if show_obstacles:
obstacles = self.generate_obstacles()
for obs in obstacles:
obstacle_circle = Circle(obs['position'], obs['radius'],
fill=True, color='gray', alpha=0.7)
ax.add_patch(obstacle_circle)
# Plot robot arm in multiple configurations
self.plot_robot_configurations(ax, num_configs=5)
ax.set_xlim(-self.total_length*1.1, self.total_length*1.1)
ax.set_ylim(-self.total_length*1.1, self.total_length*1.1)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.legend()
ax.set_xlabel('X Position')
ax.set_ylabel('Y Position')
ax.set_title('2D Robot Workspace Analysis')
plt.tight_layout()
plt.show()
def plot_robot_configurations(self, ax, num_configs=5):
"""
Plot robot arm in multiple configurations
Args:
ax: Matplotlib axis
num_configs: Number of configurations to show
"""
colors = plt.cm.Set3(np.linspace(0, 1, num_configs))
for i, color in enumerate(colors):
# Random joint angles
joint_angles = np.random.uniform(-np.pi, np.pi, self.num_joints)
# Calculate joint positions
positions = [(0, 0)]
x, y = 0, 0
angle_sum = 0
for j, (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))
# Plot robot arm
x_coords = [pos[0] for pos in positions]
y_coords = [pos[1] for pos in positions]
ax.plot(x_coords, y_coords, color=color, linewidth=2, alpha=0.7)
ax.plot(x_coords[-1], y_coords[-1], 'o', color=color, markersize=6)
def calculate_workspace_metrics(self):
"""
Calculate various workspace metrics
Returns:
Dictionary of workspace metrics
"""
metrics = {
'total_area': np.pi * self.total_length**2,
'link_lengths': self.link_lengths.tolist(),
'num_joints': self.num_joints,
'max_reach': self.total_length,
'min_reach': abs(self.link_lengths[0] - np.sum(self.link_lengths[1:]))
}
return metrics
def generate_obstacles(self, num_obstacles=3):
"""
Generate random obstacles in the workspace
Args:
num_obstacles: Number of obstacles to generate
Returns:
List of obstacle dictionaries
"""
obstacles = []
for _ in range(num_obstacles):
# Random position within workspace
angle = np.random.uniform(0, 2*np.pi)
radius = np.random.uniform(0, self.total_length * 0.8)
position = [radius * np.cos(angle), radius * np.sin(angle)]
# Random radius
obstacle_radius = np.random.uniform(0.1, 0.3)
obstacles.append({
'position': position,
'radius': obstacle_radius
})
return obstacles
# Example usage
robot_2d = Robot2DWorkspace([2.0, 1.5])
metrics = robot_2d.calculate_workspace_metrics()
print("Workspace Metrics:")
for key, value in metrics.items():
print(f"{key}: {value}")
# Visualize workspace
robot_2d.plot_workspace(show_boundary=True, show_samples=True, show_obstacles=True)
3D Workspace Analysis
For 3D robots, workspace analysis becomes more complex but equally important:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
class Robot3DWorkspace:
def __init__(self, link_lengths):
"""
Initialize 3D robot for workspace analysis
Args:
link_lengths: List of link lengths [L1, L2, L3]
"""
self.link_lengths = np.array(link_lengths)
self.total_length = np.sum(link_lengths)
def forward_kinematics(self, joint_angles):
"""
Calculate 3D end-effector position
Args:
joint_angles: [θ1, θ2, θ3] joint angles in radians
Returns:
End-effector position (x, y, z)
"""
theta1, theta2, theta3 = joint_angles
x = (self.link_lengths[0] + self.link_lengths[1]*np.cos(theta2) +
self.link_lengths[2]*np.cos(theta2+theta3)) * np.cos(theta1)
y = (self.link_lengths[0] + self.link_lengths[1]*np.cos(theta2) +
self.link_lengths[2]*np.cos(theta2+theta3)) * np.sin(theta1)
z = self.link_lengths[1]*np.sin(theta2) + self.link_lengths[2]*np.sin(theta2+theta3)
return x, y, z
def generate_workspace_surface(self, resolution=50):
"""
Generate workspace surface points
Args:
resolution: Resolution of the surface grid
Returns:
X, Y, Z coordinates of workspace surface
"""
# Generate angles for the outer two joints
theta2_range = np.linspace(-np.pi/2, np.pi/2, resolution)
theta3_range = np.linspace(-np.pi, np.pi, resolution)
theta2_grid, theta3_grid = np.meshgrid(theta2_range, theta3_range)
# Calculate reach for each combination
reach_grid = np.zeros_like(theta2_grid)
for i in range(resolution):
for j in range(resolution):
theta2 = theta2_grid[i, j]
theta3 = theta3_grid[i, j]
# Maximum reach for this configuration
max_reach = (self.link_lengths[0] +
self.link_lengths[1] * abs(np.cos(theta2)) +
self.link_lengths[2] * abs(np.cos(theta2 + theta3)))
reach_grid[i, j] = max_reach
# Convert to Cartesian coordinates
X = reach_grid * np.cos(0) # Fixed θ1 = 0 for simplicity
Y = reach_grid * np.sin(0) # Fixed θ1 = 0 for simplicity
Z = reach_grid
return X, Y, Z
def plot_3d_workspace(self):
"""
Visualize 3D workspace
"""
fig = plt.figure(figsize=(15, 5))
# 3D workspace visualization
ax1 = fig.add_subplot(131, projection='3d')
# Generate and plot workspace surface
X, Y, Z = self.generate_workspace_surface()
ax1.plot_surface(X, Y, Z, alpha=0.6, cmap='viridis')
# Plot sample robot configurations
for _ in range(10):
joint_angles = np.random.uniform(-np.pi, np.pi, 3)
x, y, z = self.forward_kinematics(joint_angles)
ax1.scatter([x], [y], [z], color='red', s=50)
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
ax1.set_zlabel('Z')
ax1.set_title('3D Robot Workspace')
# XY projection
ax2 = fig.add_subplot(132)
theta_range = np.linspace(0, 2*np.pi, 100)
x_circle = self.total_length * np.cos(theta_range)
y_circle = self.total_length * np.sin(theta_range)
ax2.plot(x_circle, y_circle, 'b-', linewidth=2)
ax2.set_aspect('equal')
ax2.set_xlabel('X')
ax2.set_ylabel('Y')
ax2.set_title('XY Projection')
ax2.grid(True, alpha=0.3)
# XZ projection
ax3 = fig.add_subplot(133)
z_range = np.linspace(-self.total_length, self.total_length, 100)
x_circle = self.total_length * np.ones_like(z_range)
ax3.plot(x_circle, z_range, 'b-', linewidth=2)
ax3.plot(-x_circle, z_range, 'b-', linewidth=2)
ax3.set_xlabel('X')
ax3.set_ylabel('Z')
ax3.set_title('XZ Projection')
ax3.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Example 3D workspace analysis
robot_3d = Robot3DWorkspace([1.0, 0.8, 0.5])
robot_3d.plot_3d_workspace()
Workspace Optimization
Optimizing robot workspace is crucial for better performance:
def optimize_workspace(link_lengths, target_task):
"""
Optimize robot link lengths for a specific task
Args:
link_lengths: Initial link lengths
target_task: Dictionary with task requirements
Returns:
Optimized link lengths
"""
# Task requirements
required_reach = target_task.get('required_reach', 2.0)
dexterity_requirement = target_task.get('dexterity_requirement', 0.8)
def objective_function(lengths):
"""
Objective function to minimize
"""
# Create robot with current lengths
robot = Robot2DWorkspace(lengths)
# Calculate workspace area
workspace_area = np.pi * np.sum(lengths)**2
# Calculate manipulability (simplified)
manipulability = np.prod(lengths) / np.sum(lengths)**2
# Penalty for not meeting reach requirement
reach_penalty = max(0, required_reach - np.sum(lengths))**2
# Penalty for poor dexterity
dexterity_penalty = max(0, dexterity_requirement - manipulability)**2
# Objective: maximize workspace and manipulability, minimize penalties
objective = -(workspace_area + 10*manipulability) - 100*(reach_penalty + dexterity_penalty)
return objective
# Optimize using scipy
from scipy.optimize import minimize
# Constraints: positive lengths
bounds = [(0.1, 3.0) for _ in link_lengths]
result = minimize(objective_function, link_lengths, bounds=bounds, method='L-BFGS-B')
return result.x
# Example workspace optimization
target_task = {
'required_reach': 2.5,
'dexterity_requirement': 0.7
}
initial_lengths = [1.0, 1.0, 1.0]
optimized_lengths = optimize_workspace(initial_lengths, target_task)
print(f"Initial lengths: {initial_lengths}")
print(f"Optimized lengths: {optimized_lengths}")
print(f"Total reach: {sum(optimized_lengths):.2f}")
Workspace with Obstacles
Real-world robots must work around obstacles:
def plan_path_with_obstacles(start_pos, end_pos, obstacles, robot_workspace):
"""
Plan a path avoiding obstacles
Args:
start_pos: Starting position (x, y)
end_pos: Ending position (x, y)
obstacles: List of obstacle dictionaries
robot_workspace: Robot workspace object
Returns:
Path waypoints and collision information
"""
# Simple straight-line path planning
path = np.linspace(start_pos, end_pos, 20)
# Check for collisions
collision_points = []
for i, point in enumerate(path):
for obstacle in obstacles:
dist = np.linalg.norm(np.array(point) - np.array(obstacle['position']))
if dist < obstacle['radius']:
collision_points.append(i)
# If collisions found, plan alternative path
if collision_points:
# Simple obstacle avoidance: go around
avoid_path = []
for i, point in enumerate(path):
if i in collision_points:
# Move perpendicular to path direction
direction = path[i+1] - path[i-1] if i > 0 and i < len(path)-1 else np.array([0, 1])
perpendicular = np.array([-direction[1], direction[0]])
perpendicular = perpendicular / np.linalg.norm(perpendicular)
avoid_point = point + perpendicular * obstacle['radius'] * 1.5
avoid_path.append(avoid_point)
else:
avoid_path.append(point)
path = np.array(avoid_path)
return path, len(collision_points) > 0
# Example path planning with obstacles
robot_ws = Robot2DWorkspace([2.0, 1.5])
obstacles = robot_ws.generate_obstacles(num_obstacles=3)
start_pos = [-1.5, -1.0]
end_pos = [1.5, 1.0]
path, has_collisions = plan_path_with_obstacles(start_pos, end_pos, obstacles, robot_ws)
print(f"Path planning completed. Collisions detected: {has_collisions}")
# Visualize path planning
fig, ax = plt.subplots(1, 1, figsize=(10, 8))
# Plot workspace
x_boundary, y_boundary = robot_ws.generate_workspace_boundary()
ax.plot(x_boundary, y_boundary, 'b-', linewidth=2, label='Workspace Boundary')
# Plot obstacles
for obs in obstacles:
obstacle_circle = Circle(obs['position'], obs['radius'],
fill=True, color='gray', alpha=0.7, label='Obstacle' if obs == obstacles[0] else "")
ax.add_patch(obstacle_circle)
# Plot path
ax.plot(path[:, 0], path[:, 1], 'r-', linewidth=2, label='Planned Path')
ax.plot(start_pos[0], start_pos[1], 'go', markersize=10, label='Start')
ax.plot(end_pos[0], end_pos[1], 'ro', markersize=10, label='End')
ax.set_xlim(-3, 3)
ax.set_ylim(-3, 3)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.legend()
ax.set_xlabel('X Position')
ax.set_ylabel('Y Position')
ax.set_title('Path Planning with Obstacles')
plt.tight_layout()
plt.show()
Key Takeaways
- Workspace Types: Reachable, dexterous, and constant orientation workspaces
- 2D Analysis: Circular workspace boundary with radius = total link length
- 3D Analysis: More complex surfaces with multiple projections
- Manipulability: Measure of robot dexterity and ability to move in different directions
- Obstacle Avoidance: Essential for real-world robot operation
- Optimization: Link length optimization for specific tasks
- Path Planning: Finding collision-free paths between points
Next Steps
Tomorrow, we’ll explore robot path planning algorithms, including A*, RRT, and other popular methods. We’ll implement these algorithms and test them on complex workspace scenarios with multiple obstacles.
Practice Exercise: Implement a comprehensive workspace analysis tool for your own robot configuration. Include workspace boundary calculation, manipulability analysis, and obstacle avoidance planning. Create visualizations for both 2D and 3D workspaces.