Group algorithms and space-group symmetries for `Lattice`
Created by: attila-i-szabo
This PR implements most of the features discussed in #703 (closed) pertaining to symmorphic space groups.
New stuff and changes
Groups
- New module
netket.utils.group
to replacesemigroup.py
: it takes care of everything related to symmetry groups- contains
SemiGroup
,identity
,Element
,PermutationGroup
,Permutation
with unchanged API - some new features, e.g.
Permutation
objects can carry an arbitrary name
- contains
- New class
Group
: base class for all group-like objects that are guaranteed to satisfy group axioms- methods to calculate inverse mapping, times table, conjugacy classes, character tables (using Burnside's algorithm) for a generic group
- equality checking via the function
_canonical()
provided by subclasses: this must return an integer array for all group elements such that equal arrays imply equal group elements (this is a property of the specific group classes to allow them to handleIdentity
as they see fit)
- New class
PGSymmetry
: represents a point group symmetry around the origin, specified by a transformation matrix- autogenerated name describes transformation in human-readable form
- New class
PointGroup
: storesPGSymmetry
objects - All crystallographically relevant point groups provided in submodules
planar
(2D),axial
,cubic
(3D) - New class
SpaceGroupBuilder
(innetket.graph
)- translates
PointGroup
s intoPermutationGroup
s acting on a particularLattice
- generates the translation group of a
Lattice
asPermutationGroup
s (with sensible names attached) - hence generates space groups as
PermutationGroup
s - helps calculate the character table of the space group intuitively (i.e., calculates irreps consistent with a given wave vector)
- translates
Graphs
-
The custom rotation etc. groups of
Lattice
are removed in favour of using the above machinery -
NetworkX.automorphisms()
changed to only return an array of permutation indices; thePermutationGroup
is made by a free-floating method insymmetry
. (SpaceGroupBuilder
needs to useLattice
, which means that having a reference to anything insymmetry
withinLattice
produces a circular import viasymmetry/__init__py
.) It is also deprecated and should eventually be replaced by a hidden method that feeds intosymmetry.automorphism_group()
: it makes little sense to have this single piece of symmetry functionality outsidesymmetry
. Alternatively,SpaceGroupBuilder
could live in thegraph
module and be blended into the functionality ofLattice
, similar to howautomorphisms()
behaves now. -
Lattice
is given several new methods:-
space_group_builder()
returns aSpaceGroupBuilder
object (see above) corresponding to the translations of the lattice and the supplied point group. TheLattice
constructor also takes aPointGroup
argument that is cached as a default point group. -
point_group()
returns the representation of itsPointGroup
argument or the default point group as aPermutationGroup
-
rotation_group()
picks out the rotations (determinant of rotation matrix is +1) -
translation_group()
returns the group of lattice translations as aPermutationGroup
. It takes an optional argument to specify the axes along which to translate -
space_group()
is the semidirect producttranslation_group() @ point_group()
.
All of these are convenience wrappers around methods of
space_group_builder()
. -
-
The "hashing logic" in
Lattice
is tidied up and extended to wave vectors (needed inSpaceGroupBuilder
). It now honours periodic and open BCs. -
The
Grid
class is removed and replaced by functions of the same calling sequence that returnLattice
s. (The space-group functionality is built aroundLattice
s, so it is better to focus on improving that one API rather than developing several independent ones.)- This breaks
Grid
's ability to colour its edges by direction. A more flexible constructor forLattice
will solve this problem. -
planar_rotation()
andaxis_reflection()
are dropped as they were inLattice
- The name
space_group()
was used incorrectly: instead of that andlattice_group()
, we haveLattice.point_group()
andLattice.space_group()
. Deprecation would be hard, since one of the names is reused in a different meaning. -
point_group()
only returns symmetries that leave the origin in place. This is different from the original behaviour for open BC axes (could be fixed by allowing nonsymmorphic point groups, but I need more reason than this to implement those).
- This breaks
-
Specialised constructors for triangle, honeycomb and kagome lattices are added.
Odds and ends
- The "hashing logic" (used both in
PointGroup
andLattice
) is moved intonetket.utils.float
and fine-tuned. It also supports a mix of periodic and open boundary conditions. I've also added_utils
- a function that prunes nearly-zero real and imaginary parts from an array;
- a function that checks whether elements of an array are nearly integers.
-
netket.jax.logsumexp
is added, which extends the functionality of JAXlogsumexp
to handle complex numbers well (i.e., it forces the output to be complex and doesn't error out on complex inputs/outputs) - Functions to project the outputs of
DenseSymm
andDenseEquivariant
onto irreps using their characters. The default flavour useslogsumexp
, but there is one with plain sums too.
Typical workflows
A simple workflow, without character tables
We just want to generate the space group of a Lattice
given a point group we know it's invariant under:
from netket.utils import group
from netket.graph import Lattice
graph = Lattice(basis_vectors = [[1,0],[0.5,0.75**0.5]], extent = (6,6)) # triangle lattice
space_group = graph.space_group(group.planar.D(6))
The resulting space_group
is a PermutationGroup
that can be used directly in a GCNN, for instance. Alternatively, we can use the premade triangular lattice that is loaded with the D_6 group:
from netket.graph import TriangularLattice
graph = TriangularLattice([6,6])
space_group = graph.space_group()
Using character tables
For this, one needs a basic appreciation of how crystallographic character tables are constructed. They can be described in terms of a wave vector (or rather a star of symmetry-related wave vectors) and the irreps of the corresponding little group (the subgroup of the point group that leaves the wave vector unchanged). The latter can be read off from a human-readable character table one can generate in an interactive session:
from netket.utils import group
from netket.graph import TriangularLattice
from math import pi
graph = TriangularLattice([6,6])
sgb = graph.space_group_builder()
k = [4*pi/3,0] # corner of the hexagonal BZ
sgb.little_group(k)
> PointGroup(elems=[Id(), Rot(120°), Rot(-120°), Refl(0°), Refl(-60°), Refl(60°)], ndim=2)
sgb.little_group(k).character_table_readable()
> (['1xId()', '2xRot(120°)', '3xRefl(0°)'],
array([[ 1., 1., 1.],
[ 1., 1., -1.],
[ 2., -1., 0.]]))
The first output confirms that the little group of D_6 at the corner of the Brillouin zone is D_3, whose known character table is generated by the second command; given the labels in the first part of the output, they are easy to match to the characters in these tables, so we can look up their physical/geometrical meaning. Any of these can be turned into an irrep of the full space group using SpaceGroupBuilder
: in fact, it generates all of them as a 2D array (in the same order as the irreps printed above), so we'd write something like
chi = sgb.space_group_irreps(k)[2] # [2] selects the "E" irrep
# ...
# in the definition of the GCNN
return irrep_project_logsumexp(output, chi)
To do
Writing tests. I tested most of the stuff manually and it seems to work in all cases, but of course it has to be more systematic.- Writing docs. Probably the best place for the kind of workflow docs you see above would be in @chrisrothUT's tutorial on GCNNs, and #700 will be updated with how the abstract stuff gets implemented here.
Checking if the stuff that got caught up in an earlier git-rebase (see first 2 commits) affects the behaviour ofstruct.dataclass
. Everything seems to work fine, so I'm not too worried, but @PhilipVinc could you perhaps check and suggest what I should do?- Non-symmorphic groups? I've given some thought to it,
PointGroup
wouldn't be too hard to extend, the main question is whether the automatic construction of character tables generalises nicely. I have a hunch that it does, but I would need some downtime with a group theory textbook to make sure. Probably left for another PR - Extending
Lattice
so it can have further-neighbour and coloured edges (this is functionality that is lost from the newGrid
for instance). I just flag this up, but it can wait.
An Easter egg
The NetworkX algorithm really looks for all automorphisms:
lattice = nk.graph.Square(4)
len(symmetry.automorphism_group(lattice))
> 384
len(symmetry.space_group(lattice, symmetry.planar.D(4)))
> 128
It turns out that a 4x4 square lattice with PBC is isomorphic to a 2^4 hypercube, which has many more symmetries. E.g., you can check that this maps nearest neighbours to nearest neighbours without making any geometrical sense:
0, 1, 5, 4
3, 2, 6, 7
15, 14, 10, 11
12, 13, 9, 8
This is an interesting caveat for using NetworkX graph matching. PS. The 3×3 triangle lattice turns out to have 1296 isomorphisms, of which only 108 are space-group symmetries!