Concepts and API guide¶
This page explains how to think about pbcgraph objects and what the main operations mean in practice. For exact function/class signatures, see the API Reference section.
Quotient graph vs infinite lift¶
A periodic graph in pbcgraph is stored as a finite quotient graph (internally a NetworkX MultiDiGraph):
- nodes are "templates" (e.g., sites, molecules, net vertices),
- directed edges represent connections between templates,
- each directed edge carries a translation vector \(t\in\mathbb{Z}^d\) telling you how the unit-cell index changes.
A lifted instance node is written as:
and an edge \(u\to v\) with translation \(t\) maps:
This gives you a precise infinite graph semantics without materializing the infinite graph.
Directed vs undirected¶
PeriodicDiGraphstores directed quotient edges exactly as you add them.PeriodicGraphis undirected but is stored internally as two directed realizations per undirected edge:u -> vwith translationtvecv -> uwith translation-tvec
Both realizations share the same underlying user-attributes dict.
Note that get_edge_data() returns a read-only live view of that mapping
(so updating attributes must be done via set_edge_attrs()).
Important invariant (PeriodicGraph)¶
Removing an edge by (u, v, key) removes both directed realizations.
A single directed realization does not represent a valid undirected periodic edge by itself.
Multi-edges and edge identity¶
pbcgraph distinguishes two container families:
PeriodicDiGraph/PeriodicGraph: at most one edge per(u, v, tvec). Here, the translation vector is treated as part of the edge identity.PeriodicMultiDiGraph/PeriodicMultiGraph: allow multiple edges per(u, v, tvec).
In all cases, edges are addressed by (u, v, key).
Edge keys must be Python integers (bool is rejected).
- If you do not provide a key, pbcgraph generates a deterministic fresh integer key.
- A base key is shared between the two realizations of an undirected edge in
PeriodicGraph/PeriodicMultiGraph.
Internal representation for self-loops¶
A crystallographically common pattern is a quotient self-loop with non-zero translation (`u == v`, `tvec != 0`), representing a bond to a periodic image.
NetworkX identifies edges by `(u, v, key)`, so the two directed realizations of such an undirected edge would collide if they shared the same key.
To support this, pbcgraph stores undirected realizations using private internal keys derived from the base key (a pair `_UKey(base, +1)` / `_UKey(base, -1)`). The public API always exposes only the base key (an int).
Attributes and version counters¶
pbcgraph tracks two counters:
structural_version: increments on node/edge add/remove.data_version: increments on attribute updates performed via pbcgraph APIs.
get_node_data() / get_edge_data() return a read-only live view of the underlying mapping.
Direct mutation is not allowed; use set_node_attrs() / set_edge_attrs().
Components, rank, and torsion¶
A connected component in the quotient graph corresponds to a periodic component in the infinite lift. For directed containers in v0.1, pbcgraph uses weak connectivity (directions are ignored) when building components. Inside such a component, translations generated by cycles form a subgroup:
pbcgraph computes:
rank: the rank of \(L\), i.e. the periodic dimension of the component (0D/1D/2D/3D inside a 3D lattice).torsion_invariants: torsion invariants from Smith Normal Form (SNF). Non-empty invariants indicate multiple interpenetrating fragments in the infinite lift.
Instance keys (inst_key)¶
PeriodicComponent.inst_key((u, shift)) returns a tuple of integers encoding the coset of the lifted instance in \(\mathbb{Z}^d/L\).
The numerical representation depends on a unimodular change of coordinates (see Theory), but the induced equivalence relation is exact:
same_fragment(a, b)is True iff the lifted nodes lie in the same infinite-lift connected fragment.
Quotient shortest paths¶
shortest_path_quotient(G, source, target, connectivity=...) runs BFS on the quotient graph:
connectivity='directed': successors only (default forPeriodicDiGraph)connectivity='weak': successors ∪ predecessors (default behavior for undirected use-cases)
This is a quotient path; it does not compute an instance-aware shortest path in the infinite lift.
Finite patches of the lift¶
Sometimes you want a finite non-periodic graph that represents a local fragment of the infinite lift (for visualization, local feature computation, or feeding to non-periodic algorithms).
lift_patch(...) builds such a patch around a seed instance (u, shift) using
either a BFS radius and/or a cell-index bounding box.
Important details:
-
Traversal is weakly connected in the lift: from an instance it considers both outgoing and incoming periodic edges (successors ∪ predecessors). This makes patch extraction useful even for directed quotient graphs.
-
Patch direction follows the container:
- from undirected containers (
PeriodicGraph,PeriodicMultiGraph), the patch is undirected; - from directed containers (
PeriodicDiGraph,PeriodicMultiDiGraph), the patch is directed.
- from undirected containers (
-
Undirected views of directed patches are available via
LiftPatch.to_networkx(as_undirected=True, undirected_mode=...):undirected_mode='multigraph': one undirected multiedge per directed edge; direction metadata is stored under the__pbcgraph__edge attribute.undirected_mode='orig_edges': collapsed simple graph; each undirected adjacency storesorig_edges=[...]snapshots under the__pbcgraph__edge attribute.
NetworkX export metadata key. Direction/origin information is stored under a single
reserved edge attribute named __pbcgraph__. In code, prefer using the constant
PBC_META_KEY (exported from pbcgraph) instead of hardcoding the string.
The library reserves this key for its own metadata; attempting to set it as a user
edge attribute will raise an error.
These export options avoid silent loss of information when you want an undirected representation for an inherently directed relation.