Smith Normal Form (SNF)¶
PeriodicComponent computes the translation subgroup L ⊂ Z^d induced by quotient cycles, then uses a Smith
Normal Form (SNF) decomposition to expose:
rank: the periodic dimension of the component,torsion_invariants: finite factors ofZ^d / L.
Non-empty torsion_invariants means the quotient component splits into multiple disconnected but congruent
fragments in the infinite lift (a common "interpenetration" signature).
from pbcgraph import PeriodicDiGraph
Example 1: torsion-free (generator = 1)¶
A 1D quotient where cycle translations generate L = Z has no torsion.
G = PeriodicDiGraph(dim=1)
G.add_edge('A', 'B', tvec=(0,))
G.add_edge('B', 'A', tvec=(1,))
c = G.components()[0]
print('rank:', c.rank)
print('torsion invariants:', c.torsion_invariants)
print(c.same_fragment(('A', (0,)), ('A', (5,)))) # True
rank: 1 torsion invariants: () True
Example 2: torsion = 2 (generator = 2)¶
Now the only cycle translation is 2, so L = 2Z.
This produces torsion Z/2Z: two congruent strands (even and odd) that never connect in the lift.
H = PeriodicDiGraph(dim=1)
H.add_edge('A', 'B', tvec=(0,))
H.add_edge('B', 'A', tvec=(2,))
c2 = H.components()[0]
print('rank:', c2.rank)
print('torsion invariants:', c2.torsion_invariants)
print('A@0 vs A@2:', c2.same_fragment(('A', (0,)), ('A', (2,)))) # True (same parity)
print('A@0 vs A@1:', c2.same_fragment(('A', (0,)), ('A', (1,)))) # False (different parity)
print('inst_key(A@0):', c2.inst_key(('A', (0,))))
print('inst_key(A@1):', c2.inst_key(('A', (1,))))
print('inst_key(A@2):', c2.inst_key(('A', (2,))))
rank: 1 torsion invariants: (2,) A@0 vs A@2: True A@0 vs A@1: False inst_key(A@0): (0,) inst_key(A@1): (1,) inst_key(A@2): (0,)
Torsion directions in the original basis¶
transversal_basis() provides a deterministic description of:
- free directions (spanning
Z^{d-r}), - torsion directions and their moduli.
In this example (d=1, rank=1) there is no free part, and the torsion modulus is 2.
c2.transversal_basis()
{'free': [], 'torsion_dirs': [(1,)], 'torsion_moduli': [2]}
Example 3: torsion in higher dimension¶
Torsion can also happen in d>1. Here we generate:
- a rank-2 subgroup
L = <(2, 0), (0, 1)> ⊂ Z^2, - so
Z^2 / Lhas a torsion factorZ/2Z.
Interpretation: the component is periodic in both directions, but it only connects even x-shifts.
K = PeriodicDiGraph(dim=2)
K.add_edge('A', 'B', tvec=(0, 0))
K.add_edge('B', 'A', tvec=(2, 0))
K.add_edge('A', 'C', tvec=(0, 0))
K.add_edge('C', 'A', tvec=(0, 1))
c3 = K.components()[0]
print('rank:', c3.rank)
print('torsion invariants:', c3.torsion_invariants)
print('A@(0,0) vs A@(2,0):', c3.same_fragment(('A', (0, 0)), ('A', (2, 0))))
print('A@(0,0) vs A@(1,0):', c3.same_fragment(('A', (0, 0)), ('A', (1, 0))))
c3.transversal_basis()
rank: 2 torsion invariants: (2,) A@(0,0) vs A@(2,0): True A@(0,0) vs A@(1,0): False
{'free': [], 'torsion_dirs': [(1, 0)], 'torsion_moduli': [2]}
Practical takeaway¶
- If you only need to check "same infinite fragment?", use
same_fragment(...). torsion_invariantsis a compact signature for interpenetration-like splitting.inst_key(...)is a deterministic key for fragment instances within one quotient component.