pylinkage.topology package

Subpackages

Submodules

pylinkage.topology.analysis module

Topological analysis of planar linkages.

Provides DOF (degree of freedom) computation using Grübler’s formula and related mobility analysis, operating on pure topology (no dimensions).

class pylinkage.topology.analysis.MobilityInfo(dof: int, num_links: int, num_full_joints: int, num_half_joints: int = 0)

Bases: object

Result of Grübler mobility analysis.

Variables:
  • dof (int) – Degree of freedom of the mechanism.

  • num_links (int) – Number of links (including ground).

  • num_full_joints (int) – Number of 1-DOF joints (revolute, prismatic).

  • num_half_joints (int) – Number of 2-DOF joints (higher pairs).

dof: int
num_full_joints: int
num_half_joints: int = 0
pylinkage.topology.analysis.compute_dof(graph: HypergraphLinkage) int

Compute the degree of freedom of a planar linkage using Grübler’s formula.

DOF = 3*(n - 1) - 2*j1 - j2

where: - n = number of links (including ground) - j1 = number of 1-DOF joints (revolute, prismatic) - j2 = number of 2-DOF joints (higher pairs)

Links are the rigid bodies of the graph: the ground (every GROUND node, or an implicit link when there is none), one body per edge and one per hyperedge, except that bodies sharing two or more nodes are pinned together and count as one. A hyperedge over a triangle whose sides are also edges is therefore one link, as is an edge between two ground nodes.

Joints are counted per node: a node shared by k bodies is k - 1 joints, so a coupler point that belongs to a single body is not a joint and a pin shared by three links is two. A PRISMATIC node is a slider block of its own with one prismatic joint to the hyperedge it slides along. All joints are 1-DOF.

Three edges closing a triangle are counted as three bars pinned together rather than one body; the DOF is the same either way.

Parameters:

graph – A HypergraphLinkage (topology only, no dimensions needed).

Returns:

  • 1: Single-input mechanism (four-bar, slider-crank)

  • 0: Rigid structure (truss)

  • <0: Over-constrained (statically indeterminate)

Return type:

The computed degree of freedom. Typical values

Example

>>> from pylinkage.hypergraph import HypergraphLinkage, Node, Edge, NodeRole
>>> # Four-bar linkage: 4 nodes, 3 driven edges + ground = 4 links, 4 joints
>>> hg = HypergraphLinkage()
>>> hg.add_node(Node("A", role=NodeRole.GROUND))
>>> hg.add_node(Node("B", role=NodeRole.DRIVER))
>>> hg.add_node(Node("C", role=NodeRole.DRIVEN))
>>> hg.add_node(Node("D", role=NodeRole.GROUND))
>>> hg.add_edge(Edge("AB", "A", "B"))
>>> hg.add_edge(Edge("BC", "B", "C"))
>>> hg.add_edge(Edge("CD", "C", "D"))
>>> compute_dof(hg)
1
pylinkage.topology.analysis.compute_mobility(graph: HypergraphLinkage) MobilityInfo

Compute full mobility analysis of a planar linkage.

See compute_dof() for the formula and link/joint counting rules.

Parameters:

graph – A HypergraphLinkage (topology only).

Returns:

MobilityInfo with DOF, link count, and joint counts.

pylinkage.topology.catalog module

Topology catalog: a collection of known planar linkage topologies.

Provides a built-in catalog of all 1-DOF planar linkage topologies up to 8 links (19 total: 1 four-bar, 2 six-bars, 16 eight-bars), validated against published atlases (Mruthyunjaya 1984).

The catalog is stored as a JSON file and loaded at runtime via load_catalog(). Each entry contains the topology as a serialized HypergraphLinkage plus metadata (link count, Assur group decomposition, human-readable name, etc.).

class pylinkage.topology.catalog.CatalogEntry(id: str, name: str, num_links: int = 0, num_joints: int = 0, dof: int = 1, link_assortment: tuple[int, ...]=(), assur_groups: tuple[str, ...]=(), family: str = '', _graph_data: dict[str, ~typing.Any]=<factory>)

Bases: object

A single topology in the catalog.

Variables:
  • id (str) – Unique identifier (e.g., “four-bar”, “watt”, “stephenson”).

  • name (str) – Human-readable name.

  • num_links (int) – Number of links including ground.

  • num_joints (int) – Number of joints.

  • dof (int) – Degree of freedom.

  • link_assortment (tuple[int, ...]) – Sorted degree sequence of links.

  • assur_groups (tuple[str, ...]) – List of Assur group signatures in solving order.

  • family (str) – Family name (“four-bar”, “six-bar”, “eight-bar”).

assur_groups: tuple[str, ...] = ()
dof: int = 1
family: str = ''
id: str
name: str
num_joints: int = 0
to_graph() HypergraphLinkage

Deserialize the stored graph data into a HypergraphLinkage.

class pylinkage.topology.catalog.TopologyCatalog(entries: dict[str, ~pylinkage.topology.catalog.CatalogEntry]=<factory>)

Bases: object

Collection of known linkage topologies.

Loaded from the built-in JSON catalog or constructed programmatically.

all_graphs() list[HypergraphLinkage]

Return all topologies as HypergraphLinkage objects.

by_family(family: str) list[CatalogEntry]

Get all topologies in a family.

Get all topologies with n links.

compatible_topologies(*, max_links: int = 8) list[CatalogEntry]

Get all topologies up to max_links.

entries: dict[str, CatalogEntry]
classmethod from_json(path: str | Path) TopologyCatalog

Load a catalog from a JSON file.

get(topology_id: str) CatalogEntry | None

Look up a topology by ID.

classmethod load_builtin() TopologyCatalog

Load the built-in catalog shipped with pylinkage.

to_json(path: str | Path) None

Save catalog to a JSON file.

topology_by_index(index: int) CatalogEntry

Look up a topology by integer index.

Parameters:

index – Integer index (0-based).

Returns:

The CatalogEntry at that index.

Raises:

IndexError – If index is out of range.

topology_index(topology_id: str) int

Return the integer index of a topology (stable within a catalog instance).

Parameters:

topology_id – ID to look up.

Returns:

Integer index (0-based).

Raises:

KeyError – If topology_id is not in the catalog.

pylinkage.topology.catalog.generate_catalog(max_links: int = 8) TopologyCatalog

Generate the catalog by running the enumerator.

This is used to produce the static JSON file. Not needed at runtime.

Parameters:

max_links – Maximum number of links to enumerate (default 8).

Returns:

A TopologyCatalog with all enumerated topologies.

pylinkage.topology.catalog.load_catalog() TopologyCatalog

Load the built-in topology catalog.

Convenience function equivalent to TopologyCatalog.load_builtin().

Returns:

A TopologyCatalog with all built-in topologies.

pylinkage.topology.enumeration module

Systematic enumeration of planar linkage topologies.

Enumerates all non-isomorphic 1-DOF planar linkage topologies up to a given number of links, using link-adjacency graph generation and isomorphism filtering. Validated against published atlases (Mruthyunjaya 1984).

The enumeration works at the link-adjacency level (links = vertices, joints = edges), which is the standard representation in mechanism atlas literature. Results are converted to the joint-first HypergraphLinkage representation used by the rest of pylinkage.

Expected topology counts for DOF=1 (revolute only):
  • 4 links: 1 (four-bar)

  • 6 links: 2 (Watt, Stephenson)

  • 8 links: 16 (per Mruthyunjaya 1984)

pylinkage.topology.enumeration.enumerate_all(*, max_links: int = 8, dof: int = 1) dict[int, list[HypergraphLinkage]]

Enumerate all topologies up to max_links.

Parameters:
  • max_links – Maximum number of links (default 8).

  • dof – Target degree of freedom (default 1).

Returns:

Dict mapping num_links -> list of topologies.

pylinkage.topology.enumeration.enumerate_topologies(num_links: int, *, dof: int = 1) list[HypergraphLinkage]

Enumerate all non-isomorphic planar linkage topologies.

Parameters:
  • num_links – Number of links including ground (4, 6, 8, …).

  • dof – Target degree of freedom (default 1).

Returns:

List of HypergraphLinkage topologies, one per isomorphism class.

Raises:

ValueError – If num_links doesn’t yield integer joint count, or if num_links < 4.

pylinkage.topology.isomorphism module

Graph isomorphism detection for planar linkage topologies.

Provides canonical forms and isomorphism checking for HypergraphLinkage graphs using WL-1 (Weisfeiler-Leman) color refinement with backtracking verification for small graphs.

For the graph sizes in planar linkage enumeration (up to ~10 nodes for 8-link mechanisms), this approach is both correct and fast.

pylinkage.topology.isomorphism.are_isomorphic(g1: HypergraphLinkage, g2: HypergraphLinkage) bool

Check whether two topologies are isomorphic.

Two topologies are isomorphic if there exists a node permutation that preserves adjacency, joint_type, and role.

Uses canonical_hash as a fast reject, then definitive verification.

Parameters:
  • g1 – First HypergraphLinkage.

  • g2 – Second HypergraphLinkage.

Returns:

True if the topologies are isomorphic.

pylinkage.topology.isomorphism.canonical_form(graph: HypergraphLinkage) tuple[tuple[int, ...], ...]

Compute a canonical adjacency representation.

Returns a sorted tuple-of-tuples encoding the adjacency matrix under the canonical node ordering. This is hashable and can be used as a dict key or set element.

Two graphs have the same canonical_form if and only if they are isomorphic. This is guaranteed by exhaustive permutation search within WL-1 color classes for ambiguous cases.

Parameters:

graph – A HypergraphLinkage (topology only).

Returns:

A hashable canonical adjacency representation.

pylinkage.topology.isomorphism.canonical_hash(graph: HypergraphLinkage) int

Compute a canonical hash for a topology using WL-1 color refinement.

Two graphs with the same hash are probably isomorphic. For a definitive check, use are_isomorphic().

The hash accounts for joint_type, role, and adjacency structure. Hyperedges are expanded to cliques before hashing.

Parameters:

graph – A HypergraphLinkage (topology only).

Returns:

An integer hash. Equal hashes suggest isomorphism; different hashes guarantee non-isomorphism.

Module contents

Topology analysis and enumeration tools for planar linkages.

This module provides: - DOF computation using Grübler’s formula - Graph isomorphism detection for topology deduplication - Systematic enumeration of all valid 1-DOF topologies - A built-in catalog of known topologies (up to 8 links)

class pylinkage.topology.CatalogEntry(id: str, name: str, num_links: int = 0, num_joints: int = 0, dof: int = 1, link_assortment: tuple[int, ...]=(), assur_groups: tuple[str, ...]=(), family: str = '', _graph_data: dict[str, ~typing.Any]=<factory>)

Bases: object

A single topology in the catalog.

Variables:
  • id (str) – Unique identifier (e.g., “four-bar”, “watt”, “stephenson”).

  • name (str) – Human-readable name.

  • num_links (int) – Number of links including ground.

  • num_joints (int) – Number of joints.

  • dof (int) – Degree of freedom.

  • link_assortment (tuple[int, ...]) – Sorted degree sequence of links.

  • assur_groups (tuple[str, ...]) – List of Assur group signatures in solving order.

  • family (str) – Family name (“four-bar”, “six-bar”, “eight-bar”).

assur_groups: tuple[str, ...] = ()
dof: int = 1
family: str = ''
id: str
link_assortment: tuple[int, ...] = ()
name: str
num_joints: int = 0
num_links: int = 0
to_graph() HypergraphLinkage

Deserialize the stored graph data into a HypergraphLinkage.

class pylinkage.topology.MobilityInfo(dof: int, num_links: int, num_full_joints: int, num_half_joints: int = 0)

Bases: object

Result of Grübler mobility analysis.

Variables:
  • dof (int) – Degree of freedom of the mechanism.

  • num_links (int) – Number of links (including ground).

  • num_full_joints (int) – Number of 1-DOF joints (revolute, prismatic).

  • num_half_joints (int) – Number of 2-DOF joints (higher pairs).

dof: int
num_full_joints: int
num_half_joints: int = 0
num_links: int
class pylinkage.topology.TopologyCatalog(entries: dict[str, ~pylinkage.topology.catalog.CatalogEntry]=<factory>)

Bases: object

Collection of known linkage topologies.

Loaded from the built-in JSON catalog or constructed programmatically.

all_graphs() list[HypergraphLinkage]

Return all topologies as HypergraphLinkage objects.

by_family(family: str) list[CatalogEntry]

Get all topologies in a family.

by_num_links(n: int) list[CatalogEntry]

Get all topologies with n links.

compatible_topologies(*, max_links: int = 8) list[CatalogEntry]

Get all topologies up to max_links.

entries: dict[str, CatalogEntry]
classmethod from_json(path: str | Path) TopologyCatalog

Load a catalog from a JSON file.

get(topology_id: str) CatalogEntry | None

Look up a topology by ID.

classmethod load_builtin() TopologyCatalog

Load the built-in catalog shipped with pylinkage.

to_json(path: str | Path) None

Save catalog to a JSON file.

topology_by_index(index: int) CatalogEntry

Look up a topology by integer index.

Parameters:

index – Integer index (0-based).

Returns:

The CatalogEntry at that index.

Raises:

IndexError – If index is out of range.

topology_index(topology_id: str) int

Return the integer index of a topology (stable within a catalog instance).

Parameters:

topology_id – ID to look up.

Returns:

Integer index (0-based).

Raises:

KeyError – If topology_id is not in the catalog.

pylinkage.topology.are_isomorphic(g1: HypergraphLinkage, g2: HypergraphLinkage) bool

Check whether two topologies are isomorphic.

Two topologies are isomorphic if there exists a node permutation that preserves adjacency, joint_type, and role.

Uses canonical_hash as a fast reject, then definitive verification.

Parameters:
  • g1 – First HypergraphLinkage.

  • g2 – Second HypergraphLinkage.

Returns:

True if the topologies are isomorphic.

pylinkage.topology.canonical_form(graph: HypergraphLinkage) tuple[tuple[int, ...], ...]

Compute a canonical adjacency representation.

Returns a sorted tuple-of-tuples encoding the adjacency matrix under the canonical node ordering. This is hashable and can be used as a dict key or set element.

Two graphs have the same canonical_form if and only if they are isomorphic. This is guaranteed by exhaustive permutation search within WL-1 color classes for ambiguous cases.

Parameters:

graph – A HypergraphLinkage (topology only).

Returns:

A hashable canonical adjacency representation.

pylinkage.topology.canonical_hash(graph: HypergraphLinkage) int

Compute a canonical hash for a topology using WL-1 color refinement.

Two graphs with the same hash are probably isomorphic. For a definitive check, use are_isomorphic().

The hash accounts for joint_type, role, and adjacency structure. Hyperedges are expanded to cliques before hashing.

Parameters:

graph – A HypergraphLinkage (topology only).

Returns:

An integer hash. Equal hashes suggest isomorphism; different hashes guarantee non-isomorphism.

pylinkage.topology.compute_dof(graph: HypergraphLinkage) int

Compute the degree of freedom of a planar linkage using Grübler’s formula.

DOF = 3*(n - 1) - 2*j1 - j2

where: - n = number of links (including ground) - j1 = number of 1-DOF joints (revolute, prismatic) - j2 = number of 2-DOF joints (higher pairs)

Links are the rigid bodies of the graph: the ground (every GROUND node, or an implicit link when there is none), one body per edge and one per hyperedge, except that bodies sharing two or more nodes are pinned together and count as one. A hyperedge over a triangle whose sides are also edges is therefore one link, as is an edge between two ground nodes.

Joints are counted per node: a node shared by k bodies is k - 1 joints, so a coupler point that belongs to a single body is not a joint and a pin shared by three links is two. A PRISMATIC node is a slider block of its own with one prismatic joint to the hyperedge it slides along. All joints are 1-DOF.

Three edges closing a triangle are counted as three bars pinned together rather than one body; the DOF is the same either way.

Parameters:

graph – A HypergraphLinkage (topology only, no dimensions needed).

Returns:

  • 1: Single-input mechanism (four-bar, slider-crank)

  • 0: Rigid structure (truss)

  • <0: Over-constrained (statically indeterminate)

Return type:

The computed degree of freedom. Typical values

Example

>>> from pylinkage.hypergraph import HypergraphLinkage, Node, Edge, NodeRole
>>> # Four-bar linkage: 4 nodes, 3 driven edges + ground = 4 links, 4 joints
>>> hg = HypergraphLinkage()
>>> hg.add_node(Node("A", role=NodeRole.GROUND))
>>> hg.add_node(Node("B", role=NodeRole.DRIVER))
>>> hg.add_node(Node("C", role=NodeRole.DRIVEN))
>>> hg.add_node(Node("D", role=NodeRole.GROUND))
>>> hg.add_edge(Edge("AB", "A", "B"))
>>> hg.add_edge(Edge("BC", "B", "C"))
>>> hg.add_edge(Edge("CD", "C", "D"))
>>> compute_dof(hg)
1
pylinkage.topology.compute_mobility(graph: HypergraphLinkage) MobilityInfo

Compute full mobility analysis of a planar linkage.

See compute_dof() for the formula and link/joint counting rules.

Parameters:

graph – A HypergraphLinkage (topology only).

Returns:

MobilityInfo with DOF, link count, and joint counts.

pylinkage.topology.enumerate_all(*, max_links: int = 8, dof: int = 1) dict[int, list[HypergraphLinkage]]

Enumerate all topologies up to max_links.

Parameters:
  • max_links – Maximum number of links (default 8).

  • dof – Target degree of freedom (default 1).

Returns:

Dict mapping num_links -> list of topologies.

pylinkage.topology.enumerate_topologies(num_links: int, *, dof: int = 1) list[HypergraphLinkage]

Enumerate all non-isomorphic planar linkage topologies.

Parameters:
  • num_links – Number of links including ground (4, 6, 8, …).

  • dof – Target degree of freedom (default 1).

Returns:

List of HypergraphLinkage topologies, one per isomorphism class.

Raises:

ValueError – If num_links doesn’t yield integer joint count, or if num_links < 4.

pylinkage.topology.load_catalog() TopologyCatalog

Load the built-in topology catalog.

Convenience function equivalent to TopologyCatalog.load_builtin().

Returns:

A TopologyCatalog with all built-in topologies.