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:
objectResult 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
- 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
GROUNDnode, 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
kbodies isk - 1joints, so a coupler point that belongs to a single body is not a joint and a pin shared by three links is two. APRISMATICnode 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:
objectA 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.catalog.TopologyCatalog(entries: dict[str, ~pylinkage.topology.catalog.CatalogEntry]=<factory>)
Bases:
objectCollection 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.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:
objectA 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:
objectResult 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:
objectCollection 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
GROUNDnode, 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
kbodies isk - 1joints, so a coupler point that belongs to a single body is not a joint and a pin shared by three links is two. APRISMATICnode 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.