Point groups¶
Finds the point group of a finite cage from its atoms. Used by
CageBuilder.point_group() and by isomer counting when
symmetry_method = "detect": cage_isomer_builder.utils.pointgroup.
Point-group detection for finite structures (cages, molecules) from the atoms, with no assumption about topology, orientation or the number of nodes.
Why not spglib for finite cages?
spglib finds the symmetry of a crystal. A lattice only allows rotations of
order 1, 2, 3, 4 and 6 (the crystallographic restriction), so spglib can
never report the C5 axes of an icosahedral (Ih) or D5h cage, or the S8 axis
of a D4d square antiprism. Periodic structures keep using spglib (see
symmetry.spacegroup_transformation_library); finite ones use this module.
Algorithm
- Centre the structure on its centroid. Every symmetry operation of a finite point set fixes its centroid, so all operations are 3x3 orthogonal matrices about that point.
- Pick two reference atoms
aandb(not collinear with the centre) from the rarest (element, radius) classes. Any symmetry operation R must sendato an atoma'of the same element at the same radius, andbto a same-element atomb'at the same radius and the same distance froma'. Two such pairs fix R up to handedness, so each candidate pair gives one proper and one improper candidate matrix. This search is exhaustive: no symmetry operation can be missed. - Validate each candidate on every atom: each image must land within
tolof an atom of the same element, one-to-one. Then refine R by an orthogonal Procrustes fit over all matched atoms and record the largest remaining deviation. - Check that the accepted operations are closed under composition (a group). A tolerance that is too loose or too tight for a slightly distorted structure then raises an error instead of giving a wrong isomer count.
The result is the symmetry group as permutations of all atoms, which restricts directly to the FG anchor slots.
PointGroup
dataclass
¶
PointGroup(name: str, rotations: ndarray, atom_perms: ndarray, centre: ndarray, max_deviation: float)
Symmetry of a finite structure.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Schoenflies symbol, e.g. |
rotations |
np.ndarray, shape (|G|, 3, 3)
|
Orthogonal matrices (row-vector convention: |
atom_perms |
np.ndarray, shape (|G|, n_atoms)
|
|
centre |
(ndarray, shape(3))
|
|
max_deviation |
float
|
Largest distance (Angstrom) between a transformed atom and the atom it was matched to, over all operations: how exact the symmetry is. |
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.pointgroup import detect_point_group
>>> pg = detect_point_group(molecule("CH4"))
>>> pg.name, pg.order, pg.rotations.shape
('Td', 24, (24, 3, 3))
order
property
¶
SlotSymmetry
dataclass
¶
Symmetry of the FG anchor slots.
Attributes:
| Name | Type | Description |
|---|---|---|
library |
list of tuple
|
Slot-major transformation library (see symmetry.py). |
framework |
PointGroup
|
Point group of the reference atoms. |
n_kept |
int
|
Framework operations that also map the anchors onto themselves
(including the identity). Fewer than |
max_anchor_deviation |
float
|
Largest anchor-to-partner distance over the kept operations. |
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.pointgroup import slot_symmetry
>>> benzene = molecule("C6H6")
>>> result = slot_symmetry(benzene, list(range(6, 12)), reference="heavy", tol=0.1)
>>> result.framework.name, result.n_kept
('D6h', 24)
detect_point_group ¶
Find every point symmetry operation of a finite structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
Finite structure. Element types matter: an operation must send every atom to an atom of the same element. |
required |
tol
|
float
|
Largest allowed distance (Angstrom) between a transformed atom and its partner. Must be well below the shortest distance between two same-element atoms (so matches are unambiguous) and above the structure's own distortion from perfect symmetry. |
0.3
|
min_radius
|
float
|
Reference atoms closer than this to the centre are not used to fix orientations (their direction is too sensitive to noise). For a structure smaller than that (e.g. water), half the largest distance from the centre is used instead. |
1.0
|
Returns:
| Type | Description |
|---|---|
PointGroup
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the structure is (nearly) linear or the operations found do not form a closed group (see the module docstring). |
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.pointgroup import detect_point_group
>>> [detect_point_group(molecule(m)).name for m in ("H2O", "NH3", "C6H6", "CH4")]
['C2v', 'C3v', 'D6h', 'Td']
>>> from cage_isomer_builder.utils.symmetry import ideal_orbit # works for any order
>>> detect_point_group(ideal_orbit("Ih"), tol=0.1).name
'Ih'
Source code in cage_isomer_builder/utils/pointgroup.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
reference_indices ¶
Atoms the symmetry operations are detected from.
"all": every atom (strict: the whole structure must be symmetric).
"heavy": everything except H and the X site markers.
"metals": transition-metal/lanthanide/actinide atoms.
"auto" (default): metals unless they are fewer than three or all on
one line, else heavy atoms.
"all" is often too strict: after optimisation, linkers settle at
slightly different ring twists (a 10-degree twist moves an aromatic H by
about 0.4 A) while the metal framework keeps the cage's symmetry. The
anchor slots are then matched under each framework operation with a
looser tolerance (see :func:pointgroup_transformation_library).
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.pointgroup import reference_indices
>>> reference_indices(molecule("C6H6"), "heavy").tolist() # metals absent: heavy atoms
[0, 1, 2, 3, 4, 5]
Source code in cage_isomer_builder/utils/pointgroup.py
slot_symmetry ¶
slot_symmetry(atoms, fg_anchor_indices, reference='auto', tol=0.5, anchor_tol=1.0, point_group=None)
Symmetry operations of a finite structure restricted to its FG anchor slots.
- Detect the point group of the reference atoms (:func:
reference_indices) attol, exhaustively and checked to be a group. - For every operation, map the anchors with a one-to-one (Hungarian)
assignment; keep the operation only if every anchor lands within
anchor_tolof its partner.
The kept set is checked for closure (and linker blocks) by the caller
via rgroup.prepare_group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
|
required |
fg_anchor_indices
|
sequence of int
|
Anchor atoms in slot order. |
required |
reference
|
str
|
|
"auto"
|
tol
|
float
|
Matching tolerance (Angstrom) for the reference atoms. |
0.5
|
anchor_tol
|
float
|
Matching tolerance (Angstrom) for the anchors. Must stay well below the anchor-anchor distance on one ring (~2.5 A for aromatic H). |
1.0
|
point_group
|
PointGroup
|
Reuse an already detected framework group. |
None
|
Returns:
| Type | Description |
|---|---|
SlotSymmetry
|
|
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.pointgroup import slot_symmetry
>>> benzene = molecule("C6H6") # the six H as slots
>>> result = slot_symmetry(benzene, list(range(6, 12)), reference="heavy", tol=0.1)
>>> len(result.library), round(result.max_anchor_deviation, 6)
(6, 0.0)
Source code in cage_isomer_builder/utils/pointgroup.py
pointgroup_transformation_library ¶
pointgroup_transformation_library(atoms, fg_anchor_indices, reference='auto', tol=0.5, anchor_tol=1.0, point_group=None)
Transformation library of a finite structure from its detected point
group: the finite counterpart of
symmetry.spacegroup_transformation_library, in the same slot-major
format. See :func:slot_symmetry for the parameters.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.pointgroup import pointgroup_transformation_library
>>> library = pointgroup_transformation_library(molecule("C6H6"), list(range(6, 12)),
... reference="heavy", tol=0.1)
>>> sorted(set(op[0] for op in zip(*library))) # slot 0 reaches every H (mirrors fix it)
[0, 1, 2, 3, 4, 5]