pylinkage is a Python library for planar linkage synthesis: you design a mechanism, define a target motion, and the optimizer — using particle swarm optimization — finds the right dimensions. If you’re new to the project, I wrote about its origins in A Solid Snake. Version 0.7.0 is a foundational rewrite. Not new features stacked on old code, but everything rebuilt on proper abstractions.

A four-bar linkage animated in pylinkage

Here’s why I made these changes and how the library is now much better.

The joints were wrong

The original pylinkage had a concept I called the “pivot joint.” I found it really interesting and really smart at the time — two bars connected at a point, free to rotate. Simple.

Except it wasn’t a joint at all. What I was calling a single joint was actually a group of two solids connected by three revolute joints. It worked, but the internal representation didn’t match the reality of the mechanism. Every time I tried to build on top of it, I was wrapping my head around something that was ill-defined.

So I dug into the literature, and I found something humbling: these groups already have a name. They’re not “Hugo’s groups” — they’re Assur groups. An Assur group is the smallest kinematic unit you can add to a mechanism without changing its degree of freedom. My pivot joint was an RRR dyad (three revolute joints). Even the linear actuator maps to a prismatic joint, which is also part of the Assur group formalism.

Once I aligned the two systems, things became instantly much easier. The code made more sense, it matched the literature, and extending it no longer felt like fighting the abstractions. This was the foundational step that made everything else possible.

Assur group decomposition of a six-bar linkage: the complete linkage on the left, the driver crank in the center, and the two RRR dyads on the right

Hypergraphs as the backbone

A linkage is a set of rigid links connected by joints. A joint connects exactly two links, but a single link can participate in many joints. This is exactly what a hypergraph is: nodes are joints, and hyperedges are links that may connect one to several nodes.

The previous version had no explicit graph model — the topology was implicit in the order you constructed Python objects. It worked, but it wasn’t something you could reuse or reason about. Now that we have a proper hypergraph, operations like finding the solving order and checking constraints can be built on mathematical literature rather than ad-hoc traversals.

Hypergraph-based linkage definition: component library on the left, hierarchical composition in the center, and the flattened linkage ready for simulation on the right

100× faster with Numba

But users don’t care about elegant abstractions if the library is slow. And pylinkage was slow. The core loop — solving joint positions at each step of the optimization — was pure Python. NumPy helped (roughly 10× faster), but it wasn’t enough.

Numba JIT-compiles numerical Python functions to machine code. The implementation is surprisingly simple: add an @njit decorator to your function, make sure it only uses NumPy arrays and scalar math, and you’re done. Most of my solving operations — circle intersections, geometric projections — needed just one line of change each. The result is roughly a 100× speedup on the position-solving inner loop.

The tradeoff: Numba adds about 250 MB to the install. For a library that was otherwise extremely lightweight, that’s significant. But for anyone running real optimizations, this wasn’t really a tradeoff — it was a necessity. It takes what used to run overnight and brings it down to five minutes.

The linkage can live outside the code

Before 0.7.0, a linkage existed only as Python objects constructed in a script. You couldn’t save one, send it to someone, or load it in a different tool. Now every linkage serializes to JSON: topology on one side, dimensions on the other.

This is a small feature with large consequences. A linkage is no longer trapped inside a Python session. It can be stored in a database, versioned in git, loaded by a web frontend, or consumed by a completely different solver. The library becomes an ecosystem component instead of a standalone script.

{
  "name": "FourBar",
  "joints": [
    {
      "type": "Crank",
      "name": "Crank",
      "x": 1,
      "y": 0,
      "joint0": {
        "inline": true,
        "type": "Static",
        "x": 0,
        "y": 0,
        "name": "Frame_Crank"
      },
      "distance": 1.0,
      "angle": 0.1
    },
    {
      "type": "Revolute",
      "name": "Coupler",
      "x": 3,
      "y": 1,
      "joint0": {
        "ref": "Crank"
      },
      "joint1": {
        "inline": true,
        "type": "Static",
        "x": 4,
        "y": 0,
        "name": "Frame_Coupler"
      },
      "distance0": 3.0,
      "distance1": 2.0
    }
  ]
}

What this enables

This release is deliberately foundational. I wanted to see what I could achieve without changing the user-facing API. Assur groups, hypergraphs, Numba, and serialization are four foundational steps done for the next version.

My long-term goal is to make mechanical design accessible to everyone — something I’ve been working toward since The Great Walker and leggedsnake. Linkage synthesis has always been a narrow engineering specialty. My mission with pylinkage is to open it up — to let anyone get their hands on it. That means the backend has to abstract things cleanly, so that a simple interface can always map to sound concepts underneath. Getting the foundations right was the prerequisite.

The next version will build on this clean base to extend the library into something genuinely new. Stay tuned.

The full changelog is on GitHub. pylinkage is available on PyPI: pip install pylinkage.