Trajectory Generation

Moro 0.5.0 provides numerical point-to-point trajectory generation in joint space and Cartesian position space.

The trajectory module is independent of Robot: it does not inspect robot geometry, joint types, joint limits, inverse kinematics, dynamics, collisions, or visualization state.

Path versus trajectory

A path describes geometry. A trajectory adds an explicit time parametrization:

\[ x=x(t). \]

Two trajectories may follow the same path with different timing.

Joint-space trajectories

Use:

import numpy as np
from moro.trajectory import joint_trajectory

t = np.linspace(0.0, 2.0, 101)

traj = joint_trajectory(
    [0.0, 0.2, -0.4],
    [0.8, -0.3, 0.5],
    t,
    method="quintic",
)

The result stores time-major arrays:

traj.t.shape   == (N,)
traj.q.shape   == (N, dof)
traj.qd.shape  == (N, dof)
traj.qdd.shape == (N, dof)

Useful derived properties are:

traj.samples
traj.duration
traj.dof

For one degree of freedom, scalar endpoints are accepted:

traj = joint_trajectory(0.0, 1.0, t)

but the result still has shape (N, 1).

Cartesian position trajectories

Use:

from moro.trajectory import position_trajectory

cart = position_trajectory(
    [0.0, 0.0, 0.0],
    [1.0, 0.5, 0.0],
    t,
)

Cartesian outputs always retain three components:

cart.p.shape == (N, 3)
cart.v.shape == (N, 3)
cart.a.shape == (N, 3)

Two-component planar inputs are intentionally not promoted to three dimensions.

Interpolation methods

The supported methods are:

linear
cubic
quintic

The default is "quintic".

Linear

Linear interpolation constrains endpoint positions only:

\[ x(\tau)=x_0+(x_f-x_0)\tau. \]

Velocity is constant and acceleration is zero inside the interval. Explicit velocity or acceleration boundary conditions are rejected.

Cubic

Cubic interpolation constrains position and velocity at both endpoints.

If qd0/qdf or v0/vf are omitted, they default independently to zero.

Acceleration boundary conditions are not supported by the cubic method.

Quintic

Quintic interpolation constrains position, velocity, and acceleration at both endpoints.

Omitted velocity and acceleration boundary conditions default independently to zero, making quintic interpolation a convenient smooth point-to-point default.

Explicit time vector

The caller supplies the complete numerical time vector:

t = np.linspace(5.0, 7.0, 101)

The initial time does not need to be zero.

Nonuniform samples are also valid:

t = [0.0, 0.1, 0.4, 1.0]

Time values must be finite and strictly increasing.

Public velocities and accelerations are derivatives with respect to physical time, not normalized time.

Boundary conditions

For multi-DOF joint trajectories, derivative boundary vectors must match the joint dimension exactly.

Scalar broadcasting is intentionally not supported:

# Invalid for a 2-DOF trajectory
qd0 = 0.5

For 1-DOF trajectories, scalar derivative conditions are accepted.

Integration with inverse kinematics

Cartesian trajectory generation and inverse kinematics remain separate operations:

cart = position_trajectory(
    p0,
    pf,
    t,
    method="quintic",
)

ik = solve_position_trajectory(
    robot,
    cart.p,
    q0=q_initial,
)

Current position IK consumes only cart.p; it does not use cart.v or cart.a.

Integration with visualization

RobotVisualizer accepts numerical joint vectors directly:

viz.plot([0.3, -0.2])

and numerical joint matrices:

traj = joint_trajectory(
    [0.0, 0.0],
    [0.6, -0.4],
    t,
)

viz.animate(traj.q)

The visualizer maps columns to robot.qs order.

Animation timing still uses the backend’s interval-style controls. Passing traj.q does not imply that a nonuniform traj.t is reproduced exactly in time.

Scope

Moro 0.5.0 trajectory generation intentionally does not include waypoints, splines, trapezoidal profiles, S-curves, automatic time allocation, pose interpolation, collision-aware planning, or dynamic-feasibility planning.