A path and a trajectory

The same geometry with and without a clock attached, and why the clock changes what you can check.

U10.1Following a trajectoryUniversity25 min

Do this lesson in the simulator

U9 produced a list of waypoints. That is a path: pure geometry, a curve through space, with nothing in it about when the robot is anywhere.

A trajectory is a path with a clock attached. It is a function of time: give it t and it hands back where the robot is supposed to be, and usually how fast it is supposed to be going.

path trajectory
what it is a curve in space position as a function of time
written (x(s), y(s)) for arc length s (x(t), y(t))
you can check does it hit anything, is it short is it fast enough, is it too fast, does it exceed the motors
produced by a planner (U9) a time law applied to a path

The separation is not pedantry, it is how the work is divided. Planning the geometry is expensive and involves the map. Putting a clock on it is cheap and involves only the machine. Keep them apart and you can re-time a route for a low battery, or for a robot carrying something delicate, without planning it again.

Arc length is the natural parameter

Write the path as a function of distance along it, not as a function of a leg index. The arc length s runs from 0 at the start to the total length at the end, and every point on the path has exactly one value of it. That gives you two things for free: the length of the path (which is the last value of s) and a clean way to say "the point 20 cm further on", which is the whole of U10.4.

For a polyline, the arc length is a running sum of the leg lengths, and a point at arc length s is found by walking the legs until the remainder falls inside one.

The time law

The simplest time law is constant speed: s(t) = v * t. It is also, as U10.2 shows, a lie, because no machine gets to its cruise speed instantly. But it is the right starting point, and for a first trajectory it is enough.

from bugbot import *
import math
connect()

WAYPOINTS = [(40, 40), (40, 140), (140, 140), (140, 60)]
CRUISE = 12.0

legs, length = [], 0.0
for (ax, ay), (bx, by) in zip(WAYPOINTS, WAYPOINTS[1:]):
    d = math.hypot(bx - ax, by - ay)
    legs.append((ax, ay, bx, by, d))
    length += d

def at(t):
    s = max(0.0, min(length, CRUISE * t))
    for ax, ay, bx, by, d in legs:
        if s <= d:
            u = s / d
            return ax + (bx - ax) * u, ay + (by - ay) * u
        s -= d
    return WAYPOINTS[-1]

print("length", round(length, 1), "cm, duration", round(length / CRUISE, 1), "s")
for i in range(0, int(length / CRUISE / 0.1)):
    x, y = at(i * 0.1)
    plot("ref x", x)
    plot("ref y", y)

Run this in the simulator

Two lines on the chart, and between them they are the whole plan. Read across at any time and you have a target position. That target is what the controller in U10.3 chases.

Why a moving target beats a fixed one

There is an obvious alternative, and every student writes it first: drive at waypoint 1 until you are close, then drive at waypoint 2. It works, and it looks terrible. The robot arrives at each waypoint, slows to nothing because the error has gone to nothing, notices the next one, and sets off again. Stop, start, stop, start.

Chasing a reference that is itself moving fixes that, because the error never collapses to zero in the middle of the route. The target is always a little way ahead, so there is always a demand, so the robot keeps moving. Everything else in this module is a refinement of that one idea.

Feasibility

Once there is a clock, you can ask questions the path could not answer:

  • Is any part of it faster than the robot can go? Differentiate and look.
  • Does it demand more acceleration than the drive can produce?
  • How long will the whole thing take, and is that inside the battery, the time limit, or the patience of whoever is watching?

A trajectory that fails any of those is not a plan, it is a wish. Checking it before driving costs one loop over the samples.

Task: put a clock on a path

Print length:, the total length of the path through the four waypoints, and duration:, how long it takes at 12 cm/s. Then sample the trajectory every 0.1 s and plot ref x and ref y. The robot does not move in this task.

from bugbot import *
import math
connect()

WAYPOINTS = [(40, 40), (40, 140), (140, 140), (140, 60)]
CRUISE = 12.0

Challenges

  1. Re-time the same path at 6 cm/s. What changes, and what does not?
  2. Plot the reference speed in x and in y. What happens at a corner, and why is that a problem for a real drive?
  3. Add a fifth waypoint that doubles back on the path. Does your at(t) still behave?