Skip to content

StuartGJohnson/trajectory_utils

Repository files navigation

Trajectory Utils (work in progress!)

Tools for computing or manipulating trajectories for robotics or control systems.

Installation

This code has been targeted to run on top of ROS2 Humble. An example would be running a trajectory solver from within a ROS2 node. Current procedure is to use a virtual env, which can safely extend your ROS2 Humble installation. This repo was developed and used on linux platforms running python 3.10.12 (the version supported by ROS2 Humble).

A makefile is included with useful targets, to be run from the repo directory, e.g.:

make setup-cu124
make check

And finally,

source venv/bin/activate

You should see that your shell is in the ros2-ml venv after these steps. Even if you are not using ros2! :)

On top of this, this repo provides the python package torch_traj_utils via pyproject.toml and src/torch_traj_utils. A convenient way to use this package is, using the venv above (from this repo - the location of pyproject.toml):

pip install -e .

This needs to only be done once for this venv.

Contents

An SCP (Sequential Convex Programming) control trajectory solver for a differential drive robot:

src/torch_traj_utils/diff_drive_solver.py
scripts/run_diff_drive.py

A CasADi/IPOPT control trajectory solver for a differential drive robot:

src/torch_traj_utils/diff_drive_solver_cas.py
scripts/run_diff_drive_cas.py

An SCP trajectory solver for a cartpole (with a particular physical twin - more to come) driven by force on the pole hub (a belt driven "cart"):

src/torch_traj_utils/cartpole_solver_force.py
scripts/run_cartpole_force.py

An SCP trajectory solver for a cartpole (with a particular physical twin - more to come) driven by a velocity servo on the pole hub (a belt driven "cart"):

src/torch_traj_utils/cartpole_solver_velocity.py
scripts/run_cartpole_velocity.py

A CasADi/IPOPT trajectory solver for a cartpole (more to come) driven by a velocity servo on the pole hub (a belt driven "cart"):

src/torch_traj_utils/cartpole_solver_velocity_cas.py
scripts/run_cartpole_velocity_cas.py

Sequential Convex Programming (SCP)

SCP solvers are generalized via polymorphism circa:

src/torch_traj_utils/scp_solver.py

SCP solvers define a subclass of SCPSolver. Current examples are CartpoleSolverVelocity, CartpoleSolverForce and DiffDriveSolver (see the Contents section). These solvers use cvxpy and pytorch. These solvers are based on principles and examples covered in Optimal Control (e.g., Stanford AA203, Optimal and Learning-based Control).

CasADi/IPOPT trajectory solvers

CasADi/IPOPT solvers are generalized via polymorphism circa:

src/torch_traj_utils/cas_solver.py
src/torch_traj_utils/casadi_solver.py

CasADi/IPOPT nonlinear optimization tools can be used to define solvers as well. In this implementation, these solvers define a subclass of CasadiSolver. The current example is CartpoleSolverVelocityCas, with more to come. These solvers use casadi, and pytorch is still used for solution rollout. The Casadi solver is generally faster (generally >10x) than cvxpy, and the arduous task of managing cvxpy-based solutions (trust regions and the vagaries of convex solvers) is avoided.

Robot control trajectory solutions - SCP

When collecting data to observe sensor data (e.g., IMU and odometry) under robot actions, it would be useful to have a method to compute control trajectories. These trajectories should satisfy some nominal control strategy and avoid obstacles. Optimal control to the rescue! Code in:

scripts/run_diff_drive.py
src/torch_traj_utils/scalar_field_interpolator.py
src/torch_traj_utils/diff_drive_solver.py

generates an occupancy map of a 2x3m open area with a point obstacle. The class ScalarFieldInterpolator uses pytorch to generate a differentiable signed distance function (SDF) of the map which serves as an obstacle constraint for our trajectory solver. This class can be easily used to generate an SDF of any obstacle map (e.g, a ROS2 occupancy map). The script run_diff_drive.py produces a trajectory which starts at the current robot pose.

Minimized cost, states and actions computed by this script are:

diff_drive_cost.png scripts/diff_drive_state.png

An overlay of the computed trajectory and the SDF is:

scripts/diff_drive_traj.png

Robot control trajectory solutions - CasaADi/IPOPT

As noted in the previous section, When collecting data to observe sensor data (e.g., IMU and odometry) under robot actions, it would be useful to have a method to compute control trajectories. These trajectories should satisfy some nominal control strategy and avoid obstacles. Optimal control to the rescue! Code in:

scripts/run_diff_drive_cas.py
src/torch_traj_utils/scalar_field_interpolator_cas.py
src/torch_traj_utils/diff_drive_solver_cas.py

generates an occupancy map of a 2x3m open area with a point obstacle. The class ScalarFieldInterpolatorCas uses CasADi to generate a differentiable signed distance function (SDF) of the map which serves as an obstacle constraint for our trajectory solver. This class can be easily used to generate an SDF of any obstacle map (e.g, a ROS2 occupancy map). The script run_diff_drive_cas.py produces a trajectory which starts at the current robot pose.

States and actions computed by this script are:

scripts/diff_drive_cas_state.png

An overlay of the computed trajectory and the SDF is:

scripts/diff_drive_cas_traj.png

Cartpole solutions

See writeup/math_background.pdf for more detail.

Solutions for the "swing-up" of a cartpole from a stationary unmoving pendulum are shown below. These solutions model a cartpole system where the pendulum is a rod attached to a pivot in the center of a belt-driven "cart". (More details to come). This system differs somewhat from typical textbook examples where the pendulum mass is concentrated at the end of the pendulum.

Cart force control (SCP)

The system could in principle be driven by a force on the cart. A solution to swing-up is produced by executing scripts/run_cartpole_force.py.

Minimized cost, optimal states and actions computed by this script are:

scripts/cartpole__force_cost.png scripts/cartpole_force_state.png

An animation of the cartpole is:

scripts/cartpole_force.gif

Cart velocity control (SCP)

The system could also be driven by a velocity servo on the cart. A solution to swing-up is produced by executing scripts/run_cartpole_velocity.py.

Minimized cost, and optimal states and actions computed by this script are:

scripts/cartpole_velocity_cost.png scripts/cartpole_velocity_state.png

An animation of the cartpole is:

scripts/cartpole_velocity.gif

Cart velocity control (CasADi/IPOPT)

The solution of the velocity servo control is found via CasADi/IPOPT. A solution to swing-up is produced by executing scripts/run_cartpole_velocity_cas.py.

Optimal states and actions computed by this script are:

scripts/cartpole_velocity_cas_state.png

An animation of the cartpole is:

scripts/cartpole_velocity_cas.gif

Cart Neural Network control

One of the intents of this work is to train a neural network to reproduce the wisdom of the trajectory solvers. Then, we make these networks run really fast (more to come). A typical pytorch/cvxpy solve requires at least 20-30 seconds - so could only be used to produce open-loop control trajectories. A first cut at imitation (supervised) learning of the controller is implemented in:

scripts/generate_trajectories.py
scripts/assess_trajectories.py
scripts/trajectory_loader.py
scripts/train.py
scripts/predict.py

Training checkpoints are included for 32=bit and 64-bit pytorch models. See:

scripts/train/trajectories_big_1_32
scripts/train/trajectories_big_1_64

These are generated via obvious manipulations of the arguments to train() in train.py.

An example of learning curves (for the 32-bit model) from an 80/20 data split follows. First, loss:

scripts/train/trajectories_big_1_32/learning_curves_0.png

Then metrics:

scripts/train/trajectories_big_1_32/metric_curves_0.png

The metrics are obtained by rolling out the initial conditions in the training set using the learned NN controller. The behavior of the metrics indicates the challenge of training a NN controller by fitting flattened state-control pairs (more to come).

The input data for this model training was generated from "expert" solves from random (but stationary) starts of the pendulum in the range of +/- 45 degrees from pointing down. The expert was the SCP solver for the velocity servo model of the cartpole (see above). The resulting training data consists of 3598 3.5 second successful swingups.

A median quality goal rollout from the "best" model (epoch 205) is:

scripts/nn_test_3_state.png

The animation of this rollout is:

scripts/nn_test_3.gif

For this test particular test, the NN seems to have nailed the controller. But, in general the model is insufficient. According to the metrics, only around 25% of the sampled trajectories satisfied limits on goals and constraints.

The current training process DOES NOT YET include expert trajectories based on rollouts of the model being trained (a la DAgger, for example). Also, I have not tuned the model, the datasets, etc. First cut! Also, the trajectory data is heavily biased to the slow parts of the trajectory:

scripts/trajectories_big_1_theta_hist.png

GPT comments

ChatGPT4 through ChatGPT5.2 (OpenAI 2025 and 2026) were used to accelerate the development of this repo. In particular, debugging and development via CVXPY and CasADi can be quite opaque at times. GPT5.2 was especially adept at debugging problems with CasADi optimization problem specification. In my experience, it is still best to target questions to the GPT via appropriate code sections. ChatGPT 5.2 can be a skilled debugger.

About

Tools for trajectory generation for robotics or controls.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors