Skip to the lesson
FixTheRobot Home / VLA / Phase 9 / 9.4

VLA · Phase 9 · VLA Deployment and Robot Integration

9.4 — Joint Limits and Collision Checking

Rejecting or adjusting actions that would be unsafe or impossible.

  • Roadmap phase 9
  • VLA Deployment and Robot Integration
  • About 14 minutes to read

The policy does not know the robot’s limits or what is on the table. This lesson builds the checks that stand between its commands and the motors: limits, rate limits and collision tests along a path.

Claims about papers, models and textbooks carry numbered references, listed with what each one is cited for in the References. Code marked “Toy model” is our own illustration, not the cited system.

Learning objectives

After this lesson, you should be able to:

  • Compare rejecting, clamping and scaling a command.
  • Limit the rate of change of a command.
  • Check a robot shape against an obstacle by sampling.
  • Explain why a path needs dense checking.
  1. The policy proposes a command
  2. Check position and speed limits
  3. Limit the rate of change
  4. Check collisions along the path
  5. Send, adjust or reject (and log it)
Checks before the motors.

The one-sentence idea

The policy proposes; the checks decide.

Check the whole path, and log every rejection.

A learned policy does not know the robot’s limits

A policy imitates motions from data. It has no built-in guarantee that its next command respects a joint's range, a safe speed or the position of a table. Those guarantees must come from the surrounding software, which checks every command before it reaches the controller. This is a different layer from the clipping of lesson 4.3: that bounds sizes; this layer checks the robot's actual physical constraints: joint position limits, joint speed limits, and collisions with itself and the environment. In collaborative settings the standards go further, describing for example speed and separation monitoring, where a protective separation distance is computed from the positions and speeds of person and robot, the stopping distance, measurement uncertainty, latency and control frequency, and the robot is stopped if the actual separation falls below it.[1] (I read these standards only through secondary summaries; for any compliance work consult the current normative texts.)

Position and speed limits: reject, clamp or slow down

When a command would break a limit there are three responses: reject it (and hold), clamp it to the limit, or scale it down to a safe size. Each has consequences: clamping one joint changes the direction of the hand motion, whereas scaling the whole command keeps the direction. Here are all three on one command:

import numpy as np

q = np.array([2.3, 0.5])                                   # current joint angles (rad)
limits = np.array([[-2.5, 2.5], [-2.8, 2.8]])
max_step = 0.3                                              # largest allowed joint change per command (rad)
command = np.array([0.6, 0.4])                              # requested joint change

target = q + command
print("requested target:", target.tolist(), "-> breaks the limit of joint 1:", bool(target[0] > limits[0, 1]))

clamped = np.clip(target, limits[:, 0], limits[:, 1]) - q
print("clamp each joint:   change", np.round(clamped, 3).tolist(), "(direction changes)")

scale = min(1.0, max_step / np.abs(command).max())
scale = min(scale, np.min(np.where(command > 0, (limits[:, 1] - q) / command, np.where(command < 0, (limits[:, 0] - q) / command, np.inf))))
print("scale the command:  change", np.round(command * scale, 3).tolist(), "(direction kept, size reduced by", round(float(scale), 3), ")")
print("reject:             change [0.0, 0.0] (hold position)")

Output:

requested target: [2.9, 0.9] -> breaks the limit of joint 1: True
clamp each joint:   change [0.2, 0.4] (direction changes)
scale the command:  change [0.2, 0.133] (direction kept, size reduced by 0.333 )
reject:             change [0.0, 0.0] (hold position)

Which response is right depends on the task and the risk. Rejecting is simplest and safest; scaling is smooth. A policy command that is rejected often is a sign of a problem upstream (wrong adapter, wrong calibration, a model out of its depth), so log every rejection.

Rate limits: no sudden jumps

Even a safe target can be reached dangerously: a command that jumps the joints by 0.5 rad in one 10 ms step means a huge acceleration. A rate limiter bounds the change per step:

import numpy as np

def rate_limit(previous, requested, max_delta):
    return previous + np.clip(requested - previous, -max_delta, max_delta)

pos = 0.0
targets = [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]        # the policy suddenly asks for a jump of 1 rad
for t, target in enumerate(targets):
    pos = rate_limit(pos, target, max_delta=0.25)
    print(f"step {t}: requested {target:.1f} -> commanded {pos:.2f}")

Output:

step 0: requested 0.0 -> commanded 0.00
step 1: requested 0.0 -> commanded 0.00
step 2: requested 1.0 -> commanded 0.25
step 3: requested 1.0 -> commanded 0.50
step 4: requested 1.0 -> commanded 0.75
step 5: requested 1.0 -> commanded 1.00
step 6: requested 1.0 -> commanded 1.00
step 7: requested 1.0 -> commanded 1.00

The jump becomes a ramp over four steps. Limits on speed, acceleration and jerk are standard parts of motion controllers, and you should keep them even when the learned policy is smooth: a rare glitch is exactly what the limiter is for.

Collision checking by sampling

To check whether a planned motion hits an obstacle, sample the robot's shape along the path and test each sample against the obstacle. For a two-link arm and a rectangular obstacle, sample points along each link:

import numpy as np

L1, L2 = 1.0, 1.0
box = (0.9, 1.4, 0.2, 0.6)                                   # obstacle: x from 0.9 to 1.4, y from 0.2 to 0.6

def link_points(q, n=40):
    elbow = L1 * np.array([np.cos(q[0]), np.sin(q[0])])
    hand = elbow + L2 * np.array([np.cos(q[0] + q[1]), np.sin(q[0] + q[1])])
    s = np.linspace(0, 1, n)[:, None]
    return np.vstack([s * elbow, elbow + s * (hand - elbow)])

def hits(q):
    p = link_points(q)
    return bool(np.any((p[:, 0] > box[0]) & (p[:, 0] < box[1]) & (p[:, 1] > box[2]) & (p[:, 1] < box[3])))

for name, q in (("arm raised", np.array([1.2, 0.3])), ("arm reaching into the box", np.array([0.4, 0.5])), ("arm lowered", np.array([-0.5, 0.3]))):
    print(f"{name:<26} -> collision: {hits(q)}")

Output:

arm raised                 -> collision: False
arm reaching into the box  -> collision: True
arm lowered                -> collision: False

Toy model. A planar arm, a box obstacle and point sampling along the links. Real systems use the robot’s full 3D geometry and libraries designed for collision checking.

Notice the sampling: 40 points per link. If the obstacle is thinner than the spacing between samples, the check can miss it. The same is true along a path: checking only the start and end of a motion can miss a collision in the middle.

Check the path, not just the end

A motion from joint configuration A to B passes through many configurations. A coarse check looks at A and B only. A finer one interpolates between them:

import numpy as np

L1, L2 = 1.0, 1.0
box = (0.9, 1.4, 0.2, 0.6)

def link_points(q, n=60):
    elbow = L1 * np.array([np.cos(q[0]), np.sin(q[0])])
    hand = elbow + L2 * np.array([np.cos(q[0] + q[1]), np.sin(q[0] + q[1])])
    s = np.linspace(0, 1, n)[:, None]
    return np.vstack([s * elbow, elbow + s * (hand - elbow)])

def hits(q):
    p = link_points(q)
    return bool(np.any((p[:, 0] > box[0]) & (p[:, 0] < box[1]) & (p[:, 1] > box[2]) & (p[:, 1] < box[3])))

A, B = np.array([1.2, 0.3]), np.array([-0.2, 0.3])           # the arm swings from one side to the other
for samples in (2, 5, 20, 100):
    path = [A + (B - A) * t for t in np.linspace(0, 1, samples)]
    print(f"{samples:>3} configurations checked -> collision found: {any(hits(q) for q in path)}")

Output:

  2 configurations checked -> collision found: False
  5 configurations checked -> collision found: True
 20 configurations checked -> collision found: True
100 configurations checked -> collision found: True

The check with only the two endpoints says the motion is clear; finer checks find the collision. The right spacing depends on the obstacle sizes and the robot speed. When in doubt, check more densely and inflate obstacles by a safety margin.

Common misconceptions

Misconception 1 “A well-trained policy stays within limits.”

Correction Nothing in imitation guarantees it.

Misconception 2 “Clamping is the same as scaling.”

Correction Clamping one joint changes the direction of the motion.

Misconception 3 “Checking the endpoints is enough.”

Correction The motion between them can collide.

Engineering takeaways

  1. Reject, clamp or scale.
  2. Rate limits remove jumps.
  3. Sampling can miss thin obstacles.
  4. Check paths densely.

Everything the policy asks is checked.

Knowledge check

Try each question first, then reveal the explanation.

Question 1

Why does clamping one joint change the hand’s direction?

Explanation

The hand motion depends on all joints together.

Question 2

A rate limit of 0.1 per step and a jump of 0.5. How many steps to complete?

Explanation

5.

Question 3

Why log rejections?

Explanation

Frequent rejections signal an upstream problem.

References

Numbered notes in the text, like [1], point here. Each entry says what it is cited for and how that was checked. Papers and books can be revised or misread, so follow the links before relying on a detail. Code marked “Toy model” is our own illustration of an idea, not the code of the cited system.

  1. International Organization for Standardization (2016). ISO 10218-1/-2 (industrial robots) and ISO/TS 15066 (collaborative robots). www.iso.org

    Cited for:

    • Speed and separation monitoring keeps a protective separation distance between person and robot, computed from the positions and speeds of both, the robot’s stopping distance, measurement uncertainty, system latency and control frequency; if the actual separation falls below it, the robot is stopped. Checked against the publication record.

What’s next

Next: connecting the policy to a robot with ROS 2.

Next lesson

9.5 — ROS 2 integration

Connecting the policy to a robot through ROS 2 nodes and messages.

Back to the VLA roadmap