Skip to main content
$$ % Define your custom commands here \newcommand{\bmat}[1]{\begin{bmatrix}#1\end{bmatrix}} \newcommand{\E}{\mathbb{E}} \newcommand{\P}{\mathbb{P}} \newcommand{\S}{\mathbb{S}} \newcommand{\R}{\mathbb{R}} \newcommand{\S}{\mathbb{S}} \newcommand{\norm}[2]{\|{#1}\|_{{}_{#2}}} \newcommand{\pd}[2]{\frac{\partial #1}{\partial #2}} \newcommand{\pdd}[2]{\frac{\partial^2 #1}{\partial #2^2}} \newcommand{\vectornorm}[1]{\left|\left|#1\right|\right|} \newcommand{\abs}[1]{\left|{#1}\right|} \newcommand{\mbf}[1]{\mathbf{#1}} \newcommand{\mc}[1]{\mathcal{#1}} \newcommand{\bm}[1]{\boldsymbol{#1}} \newcommand{\nicefrac}[2]{{}^{#1}\!/_{\!#2}} \newcommand{\argmin}{\operatorname*{arg\,min}} \newcommand{\argmax}{\operatorname*{arg\,max}} \newcommand{\dd}{\operatorname{d}\!} $$

Code Walkthrough

The cmg planner one layer at a time: commands to run, the output they printed, why, and exercises with solutions.

cmg is the reference implementation of Planning Through Contact: a contact-formation search for the box-over-a-step task, built on JAX, Equinox, MuJoCo-MJX and MPAX. This walkthrough runs it layer by layer, from the scene file up to the search loop. Every page gives a command, the script (folded), the output it printed, what to notice in that output, where to read the code, and a few exercises with folded solutions.

It studies the code as it is, including its known problems; several pages end on a defect that the next stage of the project fixes. The last page, Where step 1 starts, collects them.

Code version. Angelo’s branch phase1/dev at commit 7d2d343 (2026-11-04). phase1/aykut matches it except for the search save/load added in the last five commits, which only step 8 uses.

Setup

Run everything in bash (type bash first if your shell is fish).

cd ~/repos/research/c-gnn              # your clone of Symplectomorphism/c-gnn
git fetch origin
git switch --detach origin/phase1/dev  # 7d2d343, the commit used here
cd cmg
uv venv --python 3.12 .venv
source .venv/bin/activate
uv pip install -e ".[dev]" matplotlib

The install takes a minute or two. Then fetch the walkthrough scripts into cmg/walkthrough/ and keep that folder out of git:

mkdir -p walkthrough && cd walkthrough
base=https://symplectomorphism.github.io/research/nonprehensile/code-walkthrough/scripts
for f in _common.py 01_scene.py 02_templates.py 03_formations.py 04_graph.py \
         05_hold.py 06_candidates.py 07_mppi.py 08_search.py replay.py; do
  curl -fsSLO "$base/$f"
done
cd ..
echo "walkthrough/" >> ../.git/info/exclude

Every command on the following pages runs from cmg/ with the virtual environment active. The scripts are also linked on each page.

Baseline: the test suite.

PYTHONPATH=tests python -m pytest -q tests
......                                                                   [100%]
6 passed, 2 warnings in 97.70s (0:01:37)

PYTHONPATH=tests is needed because two tests import a helper module from tests/. Only four of the nine test files contain tests that pytest collects. test_relocate, test_markgraph_structure, test_gap_greedy_misses and test_horiz_support are placeholders. test_local_solver is a hand-run script (everything is under if __name__ == "__main__"), and it reads a gradient_warm_start attribute that MPPISolver no longer has. The suite writes plots and a video into tests/result/ (ignored by git); steps 3 and 5 show them.

Lines you can ignore. Every script first prints An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu. and two Failed to import warp lines. JAX runs on the CPU here, which is what the timings below assume. MuJoCo may also create an untracked MUJOCO_LOG.TXT in cmg/. The outputs on these pages omit those lines.

The steps

Step What it shows Script Time
1. The scene step.yaml and step.xml: bodies, features, the 24 templates, tolerances 01_scene.py 2 s
2. Templates and gaps Every template’s signed gap at one pose, and the band test 02_templates.py 3 s
3. Formations The band formation at seven named poses and along a 1,721-frame demo 03_formations.py 7 s
4. The contact graph Transfer-adjacent and shared-feature edges, and what is lost 04_graph.py 1 s
5. The hold LP Supported and EnvStable: the friction-cone LP behind the hold test 05_hold.py 23 s
6. Candidate edits Which makes and breaks are legal at a node, and why 06_candidates.py 26 s
7. One solver call MPPI realizing one make, and how its result is judged 07_mppi.py 2 min
8. The search loop Two batches of breadth-first search, saving and loading the graph 08_search.py 2 min
9. Where step 1 starts What the walkthrough exposed, mapped to the planar rewrite none none

Times are wall-clock times on a 24-core CPU, including JAX compilation. _common.py holds shared helpers: the scene path, the named poses, and readable template names such as M:boundary-B:xn (body:feature on each side). replay.py loops a trajectory in MuJoCo’s viewer if you have a display.

What exists in the code

About 2,900 lines are implemented; the other 45 modules are stubs that hold only a docstring describing their intended role.

Layer Implemented Stub only
Scene scene/spec.py (schema and YAML loader) scene/loader.py
Geometry geometry/primitives.py (3D gap kernels), geometry/features.py (faces, vertices, adjacency) none
Catalog catalog/templates.py (templates, Catalog.gap, band), catalog/graph.py (partial) graph versioning
Marking marking/candidate.py (Cand: legal edits) bitset.py, near.py, operators.py, statics.py
Physics physics/world.py (MJX world), physics/hold.py (support LP) mjx_backend.py
Solver solver/mppi.py, solver/gradient.py cem.py, success.py, cost/compiler.py
Search search/__init__.py (Search, SearchState, Discretizer, BFS ranker) engine.py, keys.py, node.py, all four policies
Learning none features/, model/, diffusion/, training/, calibration/
Orchestration none orchestration/

The roles of the stubbed marking modules are covered in practice by Cand (step 6) and physics/hold.py (step 5). The learning and orchestration layers are frozen by Decision 5, so this walkthrough does not visit them.