Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Representing symmetries #703

Closed
attila-i-szabo opened this issue May 11, 2021 · 32 comments
Closed

[RFC] Representing symmetries #703

attila-i-szabo opened this issue May 11, 2021 · 32 comments
Assignees

Comments

@attila-i-szabo
Copy link
Collaborator

attila-i-szabo commented May 11, 2021

Space groups

A limitation of the "interpretable" symmetry group implemented in #702 is the kinds of point group that can be easily represented in it. Namely, we are restricted to dihedral groups that act within a Cartesian coordinate plane. This is enough in 2D, but 3D space groups do get more complicated (e.g. on the cubic lattice).

I would recommend to introduce a PointGroup class for point-group symmetries (I think finite translation groups are better handled by the Lattice class that knows how large the lattice is). Its elements would be objects that transform Cartesian coordinates (and maybe calculate preimages too):

class PGSymmetry:
    M: np.ndarray

    def __call__(x: np.ndarray):
        return M @ x

    def preimage(x: np.ndarray):
        return np.linalg.inv(M) @ x # store inv(M) too?

The symmetries of the actual lattice are still better written in terms of permutations of sites (e.g. it makes constructing times tables for DenseEquivariant easier). We could have a method in either PointGroup or Lattice (I think the latter makes more sense because we can have different graphs with different implementations) that turn a PGSymmetry into a permutation. The result could be semidirect-producted [1] with the translation group, which would be generated the same way as it is now, without direct reference to actual geometry.

Once this is done, we could offer a catalogue of ready-made common point groups:

[1] SemiGroup::@ is not a direct product, for we don't check whether the elements commute. I think it would usually be a semidirect product in the cases we care for.

Nice things to potentially add to PGSymmetry

  • Automatic generation of human-readable description from M.
    • In 2D, this could take the form rotation by ...°, reflection across axis at ...°.
    • In 3D, it gets a bit lengthier: rotation by ...° around (x,y,z), reflection across plane (x,y,z), inversion, rotoreflection (not even sure how to describe it)
    • I wouldn't worry about higher dimensions
    • Of course, passing a label should still be an option, but having autogeneration would be nice for bigger, more complicated groups
  • A routine that shows which group elements preserve a wave vector k. This (together with naming them as above) will be very helpful for users who want to pass little-group irreps to build broken-symmetry wave functions. This will probably have to cooperate with Lattice too since it has to check for equality modulo reciprocal lattice vectors...

Non-space-group symmetries

It would also be nice to support symmetries beyond the space group. A good example is parity symmetry in spin systems, which is very useful for calculating spin gaps, etc.

This requires us to generalise SymmGroup beyond permutation groups, as well as DenseSymm to embed input vectors into groups that aren't just permutations. A most general SymmGroup has to provide two things:

  • A times table (or rather a table of g–1h) for DenseEquivariant
  • The logic of DenseSymm. For a general symmetry we need a function that takes care of the all aspects of the convolution in DenseSymm::__call__; in some cases (permutations, permutations+parity), we could get away with tweaking full_kernel.

I'd recommend that we keep SymmGroup as the base class (i.e. we take it to mean "symmetry group of the Hamiltonian" and call the permutation groups that encode graph symmetries GraphGroup, and make it a subclass of SymmGroup

For the specific case of parity, I'd define a ParityGroup that wraps another SymmGroup and doubles its size. I don't think @ing would cut it in this case, because we need to provide a times table and the logic, which is not done by a basic SemiGroup.

Odds and ends

  • We could take this refactoring to collect everything symmetry-related (the space group of Grid, SemiGroup, etc.) into one place, say netket.symm

@chrisrothUT @femtobit @PhilipVinc @fabienalet @gcarleo what do you think?

@femtobit
Copy link
Collaborator

femtobit commented May 11, 2021

Let me check I understand (the space group and PGSymmetry part of) your proposal correctly:

Symmetry operations that are elements of graph.SymmGroup should already act as permutations on any sequence whose last dimension is of size N (being the lattice spacing).
However, utils.semigroup.Semigroup support more general elements, so the PGSymmetry here acting on cartesian coordinates would indeed work as Semigroup element and could be translated to graph.SymmGroup by a Lattice method. Is this more or less what you have in mind?

Regarding the SymmGroup name: It's really meant to be specific to the graph, which is why it is graph.SymmGroup. A better name would have been GraphPermutationGroup or so, but that seemed to be too long to me at the time. (But we can still change names before 3.0.)

Since we can implement

@dispatch
def product(a: PGSymmetry, b: PGSymmetry):
    return PGSymmetry(M=a.M @ b.M)

this would work well with the existing symmetry group code (and if we make M a HashableArray). We could even impement a proper (semi?)direct product, as equivalent elements should have equal M (is this right?).

@attila-i-szabo
Copy link
Collaborator Author

Symmetry operations that are elements of graph.SymmGroup should already act as permutations on any sequence whose last dimension is of size N (being the lattice spacing).
However, utils.semigroup.Semigroup support more general elements, so the PGSymmetry here acting on cartesian coordinates would indeed work as Semigroup element and could be translated to graph.SymmGroup by a Lattice method. Is this more or less what you have in mind?

Exactly. The advantage of PointGroup is that we can construct complicated ones (like cubic symmetry) once and for all, and supply it to any Lattice that can then figure out how to use it.

Regarding the SymmGroup name: It's really meant to be specific to the graph, which is why it is graph.SymmGroup. A better name would have been GraphPermutationGroup or so, but that seemed to be too long to me at the time. (But we can still change names before 3.0.)

I see. I was thinking about it as a symmetry of the Hamiltonian, though up till now they mean the same.
I'm in favour of short names too, how about GraphGroup(SymmGroup)?

Since we can implement

@dispatch
def product(a: PGSymmetry, b: PGSymmetry):
    return PGSymmetry(M=a.M @ b.M)

this would work well with the existing symmetry group code (and if we make M a HashableArray).

Yep, this should work and would speed up writing stuff like dihedral groups.

We could even impement a proper (semi?)direct product, as equivalent elements should have equal M (is this right?).

I think Semigroup::__matmul__ is enough for our purposes without worrying about closure etc. checks (if you mean that). Also, we need a times table for the space group at then end of the day, and I would rather leave that to GraphGroup because it's just easier to match permutations

@attila-i-szabo
Copy link
Collaborator Author

Re @PhilipVinc's suggestion to have the separate the logic from all classes: #702 (comment)

I think that's feasible and might help with neat design. The only thing we really need Lattice or Grid to do is converting between indices and Cartesian coordinates. We could require two methods Graph::positions() and Graph::locate(xyz) that do this, at which point we can have

def point_group_to_graph_group(p: PointGroup, g: Graph): GraphGroup:
     perms = []
     sites = g.positions()
     for pgs in p:
         preimages = pgs.preimage(sites)
         perms.append(g.locate(preimages))
     return GraphGroup(perms)

We can then wrap this function in either class. I'd still favour Lattice because we should supply the translations from there directly

@chrisrothUT
Copy link
Collaborator

chrisrothUT commented May 11, 2021

I like this idea. I.e. the group is stored abstractly and then finds the concrete implementation by interacting with Lattice.py (or whatever it needs to interact with). That being said I will defer to someone else to implement this :)

As a note, we do have a quick and dirty way to implement higher dimensional groups. For example the cubic point group (in Lattice.py) could be constructed by:

L = nk.graph.Lattice([[1,0,0],[0,1,0],[0,0,1]])
cubic_group = L.rotations(4,(0,1))@L.rotations(4,(0,2))@L.rotations(4,(1,2))@L.reflections(0)
cubic_group.remove_duplicates()

Yeah, I know it’s not an irreducible representation but at least the elements are labeled. This is a pretty general procedure too.

@attila-i-szabo
Copy link
Collaborator Author

For example the cubic point group (in Lattice.py) could be constructed by:

L = nk.graph.Lattice([[1,0,0],[0,1,0],[0,0,1]])
cubic_group = L.rotations(4,(0,1))@L.rotations(4,(0,2))@L.rotations(4,(1,2))@L.reflections(0)
cubic_group.remove_duplicates()

I was always under the impression that this doesn't generate the triads around the [111] axes (which are usually used to define cubic symmetry), but I might be wrong, and it has to do with what is the least you need for cubic symmetry...
In any case, there are a number of different "cubic" symmetry groups so I think providing explicit implementations for all or most of them won't be a bad thing (much like dihedral groups are a good thing to have out of the box).

I like this idea. I.e. the group is stored abstractly and then finds the concrete implementation by interacting with Lattice.py (or whatever it needs to interact with). That being said I will defer to someone else to implement this :)

I can take a stab at implementing it, though not right now, I have work to do! 😛

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 13, 2021

@PhilipVinc @femtobit is it OK with the v3.0 timeline if I submit a PR for this mid-next-week or so with significant API changes? I have thought about this a lot and have a pile of notes for implementation, so it would make sense that I work through this in the first place, but I have a lot to do these days, so it would take a while to get around everything.

@PhilipVinc
Copy link
Member

PhilipVinc commented May 13, 2021 via email

@PhilipVinc
Copy link
Member

PhilipVinc commented May 14, 2021

I agree with the idea of having a general PGSymmetry.

If we then implement its elements, I think this would allow us to get rid of the two rotation implementations and the reflection, right?
because those are all elements of point groups.
Then they can be easily constructed with a class method, but i'd like them in general to be constructible with a standalone method.

--

I think we should start moving things to a netket.graph.symmetries submodule and put those groups there with the dispatch logic.
We need to come up with a constructor that needs quite general things and so can take everything it needs from a common api that graphs will implement.
This way (which is what i am striving for) if someone implements a custom lattice/graph, he gets support for simmetries out of the box if he implements just one or two api methods.

Can you write some pseudocode for the general API we should have before writign the actual code?

--
Re: GraphGroup: why do you want to rename symmgroup? Admittedly SymmGroup is a group of permutations, so PermutationGroup would be a better fit, wouldn't it?
Also, i don't get your example

def point_group_to_graph_group(p: PointGroup, g: Graph): GraphGroup:
    ....
     return GraphGroup(perms)

Unless i'm missing something, the PointGroup is a particular SymmGroup or am I wrong? The correct thing to do here is subclassing.

Maybe what's confusing is that you want to write PointGroup to replace the way we generate the elements of groups (Translations/rotations/reflections) so we need to write the implementation of the group and of it's elements.

--

As for symmetries in general: all the simmetries we are working with are simmetries of the lattice.
We are starting to think about more general simmetries of the hamiltonian (parity, SU2... etc) but those are still far away and not in scope for those discussions.
It's not yet clear how to implement them and there are at least two competing ways to do so.

Let's keep the discussion in this issue focused about lattice simmetries.
If you want to discuss hamiltonian simmetries lets open another issue, ok?

@femtobit
Copy link
Collaborator

@PhilipVinc Thinking about it, nothing in graph.SymmGroup strictly requires the graph. Actually, I think the only thing that is needed in practice is n_nodes. So yes, I think just turning that into a generic PermutationGroup should be pretty straightforward and I think this is a good idea.

Not sure if this is off-topic, but a slightly related point: Several parts of our code I think still have the implicit assumption that all nodes of a graph are labeled precisely by np.arange(n_nodes) (the old NetKet code didn't even have a method to return .nodes() like the current graph classes). I'd argue we should just document and standardize that, then we can, e.g., assume that the permutation indices given by a permutation group directly correspond to nodes in a graph without any surprises, in particular without any need to reference the specific graph anywhere (and if people want different node labels than [0, ... N) they can be supplied as additional metadata easily anyways).

(By the way: I agree that Hamiltonian symmetries etc. are out of scope here. Let's just focus on [lattice] permutations for now.)

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

Re: GraphGroup: why do you want to rename symmgroup? Admittedly SymmGroup is a group of permutations, so PermutationGroup would be a better fit, wouldn't it?
Also, i don't get your example

def point_group_to_graph_group(p: PointGroup, g: Graph): GraphGroup:
    ....
     return GraphGroup(perms)

Unless i'm missing something, the PointGroup is a particular SymmGroup or am I wrong? The correct thing to do here is subclassing.

Maybe what's confusing is that you want to write PointGroup to replace the way we generate the elements of groups (Translations/rotations/reflections) so we need to write the implementation of the group and of it's elements.

PointGroup is quite a different object, because its elements are geometrical symmetries (i.e. mapping from an arbitrary position vector to another vector), rather than their site-permutation action. We would need a base class (say Group) than can provide times tables and everything else that follows from that (conjugacy classes, irrep computation, etc.), which would be subclassed by SymmGroup and PointGroup with very different kinds of element

@attila-i-szabo
Copy link
Collaborator Author

As for symmetries in general: all the simmetries we are working with are simmetries of the lattice.
We are starting to think about more general simmetries of the hamiltonian (parity, SU2... etc) but those are still far away and not in scope for those discussions.
It's not yet clear how to implement them and there are at least two competing ways to do so.

Let's keep the discussion in this issue focused about lattice simmetries.
If you want to discuss hamiltonian simmetries lets open another issue, ok?

Okay. The only API decision would be is whether we want to leave room for these other symmetries, so we would have a reserved name for every kind of Hamiltonian symmetry, which would (later if not now) be subclassed by graph permutation symmetries

@attila-i-szabo
Copy link
Collaborator Author

Not sure if this is off-topic, but a slightly related point: Several parts of our code I think still have the implicit assumption that all nodes of a graph are labeled precisely by np.arange(n_nodes) (the old NetKet code didn't even have a method to return .nodes() like the current graph classes). I'd argue we should just document and standardize that, then we can, e.g., assume that the permutation indices given by a permutation group directly correspond to nodes in a graph without any surprises, in particular without any need to reference the specific graph anywhere (and if people want different node labels than [0, ... N) they can be supplied as additional metadata easily anyways).

Agreed.

@PhilipVinc
Copy link
Member

PhilipVinc commented May 14, 2021

PointGroup is quite a different object, because its elements are geometrical symmetries

Technically you are right.
But practically we need to cast everything back to Permutations in the end, to use them, don't we?
So might as well behave like whatever it wants to behave like, but if it cannot be casted back to permutations it cannot be used.

My question is simply: can it inherit from SymmGroup and respect it's interface while implementing the extra bells and whistles it needs?

Okay. The only API decision would be is whether we want to leave room for these other symmetries, so we would have a reserved name for every kind of Hamiltonian symmetry, which would (later if not now) be subclassed by graph permutation symmetries

We don't even know what form this will take!
Regardless, changing the inheritance tree is nor a breaking change nor complicated to do so this is not an issue IMO.

@attila-i-szabo
Copy link
Collaborator Author

I agree with the idea of having a general PGSymmetry.

If we then implement its elements, I think this would allow us to get rid of the two rotation implementations and the reflection, right?
because those are all elements of point groups.
Then they can be easily constructed with a class method, but i'd like them in general to be constructible with a standalone method.

Again, PGSymmetry does not know about lattices, but can they can be translated into a Permutation on any lattice that is symmetric under it. I'm happy for that to be a function between the two classes, it makes the interfaces less porous. Then, Lattice can provide a wrapper for providing the whole set of point-group permutations based on an abstract PointGroup

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

PointGroup is quite a different object, because its elements are geometrical symmetries

Technically you are right.
But practically we need to cast everything back to Permutations in the end, to use them, don't we?
So might as well behave like whatever it wants to behave like, but if it cannot be casted back to permutations it cannot be used.

My question is simply: can it inherit from SymmGroup and respect it's interface while implementing the extra bells and whistles it needs?

Why do you want a non-permutation group to be derived from a permutation group? I'd rather say to put everything that works for any group (like constructing times tables) into a class that is SymmGroup and PointGroup subclasses.

But practically we need to cast everything back to Permutations in the end, to use them, don't we?

Yes, but how you do that depends on the particular lattice, and there are a lot of things a PointGroup can do (you can catalogue all relevant point groups, and they can provide character tables automatically among other things) without passing it to a lattice

@attila-i-szabo
Copy link
Collaborator Author

I think we should start moving things to a netket.graph.symmetries submodule and put those groups there with the dispatch logic.
We need to come up with a constructor that needs quite general things and so can take everything it needs from a common api that graphs will implement.
This way (which is what i am striving for) if someone implements a custom lattice/graph, he gets support for simmetries out of the box if he implements just one or two api methods.

Can you write some pseudocode for the general API we should have before writign the actual code?

I'm not sure how pseudocode is easier than python :-) But I'll type out what the structure is that I have in mind in words, bear with me

@PhilipVinc
Copy link
Member

What I was missing is that SymmGroup stores the graph inside.
It's really a permutations group of a graph, not a general permutation group.
Sorry. So that's somewhere else in the hierarchy.

My point is, all those objects are groups. What are the (relevant) operations we can do on group? for example, does it make sense to combine a PointGroup with a SymmGroup? This kind of thing. Do we have other groups than PointGroup that we might want to implement in the future? can you also sketch them in (even if they won't be implemented now) to be sure that we have a good idea of things that might come?

Can you sketch a list of what operations we need to do on all those different objects and their elements?
Then it can be easier to understand the relationships between those objects.

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

Groups

  • Semigroup does the things it can do now
  • Group subclasses it and adds a product_table method that would be cut and pasted from SymmGroup. It will require its subclasses to implement a canonical_form which returns a HashableArray of integers such that they can be used for equality testing. This we need because product_table runs on hashing/dict lookups. A lot of purely group calculations (up to calculating a full character table) can be derived from this product table, so you can save yourself from code duplication with having this step.
    • SemiGroup::@ needs to be dispatched to turn a pair of Groups into another Group. This is more of a sensible contract than anything mathematically justified, but product_table will very visibly die if the contract is broken. We already do this with SymmGroups
  • PointGroup subclasses Group with PGSymmetry objects. The canonical form is the rounded transformation matrix
    • I would offer a range of these symmetry groups as a matter of convenience (this is a major reason this is useful, you have access to complicated point groups in a "nice" form without thinking about how to implement them as permutations)
  • SymmGroup/PermGroup/whatever subclasses group with Permutation objects. The canonical form is the permutation list. I don't think it needs to store the graph for any reason once it's constructed.
  • EDIT: @chrisrothUT suggested a SpaceGroup that would take a PointGroup and a Lattice and would take care of constructing the PermGroup of the whole space group, as well as more elaborate space-group-symmetry calculations (e.g. intelligible irrep generation)
    • SemiGroup::@ needs to be dispatched for matching pairs only (cross-multiplying different kinds of group doesn't make sense). Whether that's a good (semi)direct product of groups remains a matter of trust

I'm not sure atm what other groups we'd need later, probably not more than these for this RFC. An example would be the ParityGroup I suggested earlier, but we agreed that's left for later

Another question is where to put all this. I think both PointGroup and PermutationGroup are independent enough of graphs that we don't need them to be in netket.graph. But I think it is also confusing that all of this now lives in a module called semigroup. I would open a directory symmetry in netket.utils and make the various group classes a module there. Then we could also have modules that store the preimplemented point groups in some vaguely civilised arrangement. However, to stop us from writing things like nk.utils.symmetry.cubic.Oh, we might want to make an alias nk.symmetry = nk.utils.symmetry?
EDIT shall we put SpaceGroup here too, or is that better with nk.graph?

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

Graphs

  • Lattice would have a method for calculating its own translation_group as a group of Permutations (explaining a PBC translation group in purely geometrical terms is a pain, so I go for pragmatism than beauty)
  • We'd have a method external to both Group and Graph that can translate a PointGroup to a SymmGroup. This requires that the Graph can tell the positions of its atoms and tell what (if anything) is at a given position, even if it's shifted by the PBC. (Kinda what @chrisrothUT's "hashing" routine now does in Lattice.)
    • This means that extending any Graph with these lookup methods will allow PointGroup to act on them
  • Lattice would have a point_group method that takes a PointGroup and calls this function for all of its elements, and a space_group method that @s the resulting SymmGroups
  • Grid at this point would be a Lattice with a particular structure and default PointGroup, so I think we can scrap it in favour of a smart constructor or a very lean subclass of Lattice

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

Odds and ends

  • Fine-tune the "hash floats by rounding them and turning them into integers" logic and move it to nk.utils
  • Change the name of the default graph class to something more sensible than NetworkX
  • Allow Permutations to carry an arbitrary name and come up with sensible rules for combining them upon @ing

@attila-i-szabo
Copy link
Collaborator Author

does it make sense to combine a PointGroup with a SymmGroup

not really, because they act on different things. But if you specify a Graph, a PointGroup can be translated into a SymmGroup by calculating its permutation action on that graph

@attila-i-szabo
Copy link
Collaborator Author

@femtobit @PhilipVinc @chrisrothUT How do you like this layout?

@chrisrothUT
Copy link
Collaborator

I definitely like the layout. A few comments

Lattice would have a method for calculating its own translation_group as a group of Permutations

I think that Lattice.py should just contain the generators of rotation/translation/reflection (which are the perm functions I’ve written)

Lattice would have a point_group method that takes a PointGroup and calls this function for all of its elements, and a space_group method that @s the resulting SymmGroups

Why do this all in Lattice? What I’m envisioning is something like

Class space_group(Group):
    Lattice: Lattice
    “Lattice on which the space group is defined”
    Point_group
    “Desired point group to extract from the lattice (i.e. D_6)”
  
    Def __postinit__(self):
          Self.perm_group = self.get_perm_group()

    def get_perm_group(self):
            “Gets the permutation group corresponding to the specified point group + lattice translations 
    @property
    Def irreps(self):
            “Returns irreducible representations”
    Def character table(self,irrep):
            “Produces character table corresponding to chosen irrep”

Ignore the weird capitalization, the IPad be like that sometimes.

TLDR: just let lattice specify the generators, and let this new group object do everything else that way if we want to add new functionality we don’t have to look all over the place

As for the whole grid thing, we should probably just make it a subclass of Lattice, and add some other common subclasses like triangular lattice etc.

.

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

As for the whole grid thing, we should probably just make it a subclass of Lattice, and add some other common subclasses like triangular lattice etc.

Agreed. I'll write Grid as an example, not very keen on writing a catalogue of them.

What I’m envisioning is something like

Class space_group(Group):
    Lattice: Lattice
    “Lattice on which the space group is defined”
    Point_group
    “Desired point group to extract from the lattice (i.e. D_6)”
  
    Def __postinit__(self):
          Self.perm_group = self.get_perm_group()

    def get_perm_group(self):
            “Gets the permutation group corresponding to the specified point group + lattice translations 
    @property
    Def irreps(self):
            “Returns irreducible representations”
    Def character table(self,irrep):
            “Produces character table corresponding to chosen irrep”

This is nice. However, I would then decouple the PointG → PermG translation from Lattice altogether. (PG generators aren't super helpful with groups that are more complicated than dihedral groups, and that's a low bar.) Maybe keep the free-floating translation function and incorporate it here?
The only caveat is that this would be built heavily around Lattice, because a generic Graph won't have the information needed to construct the translation group (nor should we require a large interface that is only ever implemented in Lattice). But that might not be a huge problem?

PS. It turns out there is a very generic (but pretty slow) algorithm for building the character table out of the times table. I'll implement it in Group (with a view of using it on PointGroups), so it will be available for SpaceGroups, but the output would generally be an incomprehensible mess for a group that size, so there'll be cleverer methods.

@chrisrothUT
Copy link
Collaborator

However, I would then decouple the PointG → PermG translation from Lattice altogether

I have no idea how this could be possible. How could you distinguish between a D_6 on a Kagome and a D_6 on a triangular?

The only caveat is that this would be built heavily around Lattice

Yeah, this seems pretty necessary. It’s probably possible to build a symmetry group from a generic graph object, but that does not seem worth it at all to me. It may be worth it to allow lattice to take a list of space coordinates as input.

It turns out there is a very generic (but pretty slow) algorithm for building the character table out of the times table.

How slow? It’s a startup cost, and nothing could possibly take as long as the NetworkX automorphisms function. Definitely seems worth implementing over a specialized method, as this could be done for any possible group.

but the output would generally be an incomprehensible mess for a group that size

As long as the group elements are ordered in a sensible way, shouldn’t the character table be ordered the same way?

@attila-i-szabo
Copy link
Collaborator Author

However, I would then decouple the PointG → PermG translation from Lattice altogether

I have no idea how this could be possible. How could you distinguish between a D_6 on a Kagome and a D_6 on a triangular?

I mean you wouldn't make the Lattice class do the translation.

The only caveat is that this would be built heavily around Lattice

Yeah, this seems pretty necessary. It’s probably possible to build a symmetry group from a generic graph object, but that does not seem worth it at all to me. It may be worth it to allow lattice to take a list of space coordinates as input.

Yeah, I guess the counterargument is that someone might want to implement things very differently from Lattice, but it seems like a fringe case, or they will just subclass Lattice...

It turns out there is a very generic (but pretty slow) algorithm for building the character table out of the times table.

How slow? It’s a startup cost, and nothing could possibly take as long as the NetworkX automorphisms function. Definitely seems worth implementing over a specialized method, as this could be done for any possible group.

It requires diagonalising a matrix with as many rows/columns as there are conjugacy classes/irreps. Fine for a point group with <10 classes, not great for a space group on a large lattice.
It will be available for any Group. However, for a space group you usually want to specify a wave vector and a (little) point group irrep, rather than browsing in a massive table.

but the output would generally be an incomprehensible mess for a group that size

As long as the group elements are ordered in a sensible way, shouldn’t the character table be ordered the same way?

It's more that you get hundreds of irreps with not very intuitive characters (because they contain sums of phase factors for a bunch of different wave vectors). It's better if you have a function that allows you to get the one you're looking for directly. (See above)

@chrisrothUT
Copy link
Collaborator

chrisrothUT commented May 14, 2021

I mean you wouldn't make the Lattice class do the translation.

Ahh misread what you said. Although I’m still a bit confused about how you’d know what permutation corresponds to a translation without using Lattice. I do think it may be worth it to have permutation functions work for a general translations/rotations (instead of generators) so we don’t have to permute arrays a bunch of times

EDIT: Actually applying the matching algorithm is probably slower than calling perm[perm] so never mind.

However, for a space group you usually want to specify a wave vector and a (little) point group irrep, rather than browsing in a massive table.

This is definitely true. Is the plan right now to just import a database with the space groups in <=3D and allow the user to specify that information?

@attila-i-szabo
Copy link
Collaborator Author

attila-i-szabo commented May 14, 2021

I mean you wouldn't make the Lattice class do the translation.

Ahh misread what you said. Although I’m still a bit confused about how you’d know what permutation corresponds to a translation without using Lattice. I do think it may be worth it to have permutation functions work for a general translations/rotations (instead of generators) so we don’t have to permute arrays a bunch of times

Of course you need the information content in Lattice, it's just that you'd have a function external to it that takes a PGSymmetry and a Lattice and returns a Permutation. Then in SpaceGroup you iterate over all elements of the PointGroupmember with this function. The translation group would be generated internally by SpaceGroup already as a PermGroup because giving an abstract geometric form of that for finite lattices would be a pain.

EDIT: Actually applying the matching algorithm is probably slower than calling perm[perm] so never mind.

Maybe, but this is a minor startup cost, and I think the hashing trick gets us close in performance anyway. It's more that you don't have to explain the rotation group of a tetrahedron (for instance) in terms of generators, but you can just pass a premade 12-element PointGroup. Even the biggest 3D crystallographical point group has only 48 elements, and composing translations on it would be done in terms of Permutations anyway
Another advantage is that a 2D/3D orthogonal matrix is easy to interpret geometrically, so you could autogenerate a good name for them, which has been an issue with Permutation-based solutions (I don't think Rotation(pi/2)**3 @ Reflection(axis=0) was a great name for a diagonal mirror). I actually want to allow Permutations to carry an arbitrary name that could be supplied to them upon translating the PointGroup

However, for a space group you usually want to specify a wave vector and a (little) point group irrep, rather than browsing in a massive table.

This is definitely true. Is the plan right now to just import a database with the space groups in <=3D and allow the user to specify that information?

I would stop at point groups.

  1. I don't feel like handcoding 230 space groups (be my guest though!), there are way fewer relevant point groups
    EDIT: most of these have free parameters, so it's even worse
  2. Again, you have to worry about how these abstract space groups would learn about the periodic BCs.
  3. Most of the 230 aren't symmorphic. I start to think implementing those wouldn't be tremendously hard either, but I would be happy with having a nice API for the symmorphic ones that can be extended without tearing everything up again. (Actually, having SpaceGroup seems very good for that, because it allows you to check closure of non-symmorphic "point groups" modulo a lattice vector. This is not for now though!)

With decorating PointGroup and/or SpaceGroup with some methods, the user could generate the little group at their wave vector of choice, print out its character table (generated by Burnside's algorithm on the fly) and pick the one they want to use (with a bit of care, we can make sure they would always be listed in the same order, so they could just use its serial number to specify it).

@chrisrothUT
Copy link
Collaborator

it's just that you'd have a function external to it that takes a PGSymmetry and a Lattice and returns a Permutation

Yeah I’m definitely on the same page about everything, except I don’t really see the point of why you’d use an external function instead of just having the function exist within in the Group object itself (as long as we give it access to lattice). I’m new to working on open source projects though, so I don’t have a strong opinion on this.

It's more that you don't have to explain the rotation group of a tetrahedron (for instance) in terms of generators, but you can just pass a premade 12-element PointGroup

Ahh I see the point of your layout now, it avoids the extra step of having to express point group operations in terms of generators when you can just express them as coordinate transforms then use the hashing trick to find the perm.

I don't feel like handcoding 230 space groups (be my guest though!)

Hmm yeah though the 73 symmorphic group sounds like a lot too. I’d be happy to help with the grunt work

@attila-i-szabo
Copy link
Collaborator Author

it's just that you'd have a function external to it that takes a PGSymmetry and a Lattice and returns a Permutation

Yeah I’m definitely on the same page about everything, except I don’t really see the point of why you’d use an external function instead of just having the function exist within in the Group object itself (as long as we give it access to lattice). I’m new to working on open source projects though, so I don’t have a strong opinion on this.

This started off as a request from @PhilipVinc who doesn't like code that marries two classes to be tied to either of them. I don't think it has much to do with open-source, rather than NetKet being a big project, so you want to write stuff that is easy to maintain/refactor. In this specific case, Lattice has an equally good claim on this logic, and it might only turn out later which choice is better. And behold, your SpaceGroup class is probably an even better place for it than either PGSymmetry or Lattice. Having a free-floating method means that severing a method out from one class and putting it in another can be done with changing trivial wrappers, rather than code that actually does things.

It's more that you don't have to explain the rotation group of a tetrahedron (for instance) in terms of generators, but you can just pass a premade 12-element PointGroup

Ahh I see the point of your layout now, it avoids the extra step of having to express point group operations in terms of generators when you can just express them as coordinate transforms then use the hashing trick to find the perm.

Exactly – it's not a trivial thing for many point groups, especially if you don't want a lot of repetitions.

I don't feel like handcoding 230 space groups (be my guest though!)

Hmm yeah though the 73 symmorphic group sounds like a lot too. I’d be happy to help with the grunt work

Well, it's certainly not a high priority :-) Most of them are so obscure anyway that cataloguing them wouldn't benefit anyone, point groups are a bit more versatile

@femtobit
Copy link
Collaborator

femtobit commented Jun 8, 2021

I believe this issue is resolved by #724, do you agree? @PhilipVinc @attila-i-szabo @chrisrothUT

@chrisrothUT
Copy link
Collaborator

Yeah this issue is certainly resolved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants