Skip to content

Commit e07d4b3

Browse files
committed
Merge branch 'lookup-sg-by-symops'
* resolve CIF spacegroup from a list of symmetry operations * add function FindSpaceGroup * fix incorrect symop list for sg1008 B11m * add utility function `_hashSymOpList` Resolve #20
2 parents 32da806 + 75c9568 commit e07d4b3

File tree

6 files changed

+203
-18
lines changed

6 files changed

+203
-18
lines changed

src/diffpy/structure/mmlibspacegroups.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7231,9 +7231,9 @@
72317231
pdb_name = "B 1 1 m",
72327232
symop_list = [
72337233
SymOp(Rot_X_Y_Z, Tr_0_0_0),
7234-
SymOp(Rot_X_mY_Z, Tr_0_0_0),
7235-
SymOp(Rot_X_Y_Z, Tr_12_12_0),
7236-
SymOp(Rot_X_mY_Z, Tr_12_12_0)])
7234+
SymOp(Rot_X_Y_mZ, Tr_0_0_0),
7235+
SymOp(Rot_X_Y_Z, Tr_12_0_12),
7236+
SymOp(Rot_X_Y_mZ, Tr_12_0_12)])
72377237

72387238
sg1009 = SpaceGroup(
72397239
number = 1009,

src/diffpy/structure/parsers/p_cif.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import sys
2222
import re
23-
import copy
2423
from contextlib import contextmanager
2524
import numpy
2625
import six
@@ -447,6 +446,7 @@ def _parse_space_group_symop_operation_xyz(self, block):
447446
"""
448447
from diffpy.structure.spacegroups import IsSpaceGroupIdentifier
449448
from diffpy.structure.spacegroups import SpaceGroup, GetSpaceGroup
449+
from diffpy.structure.spacegroups import FindSpaceGroup
450450
self.asymmetric_unit = list(self.stru)
451451
sym_synonyms = ('_space_group_symop_operation_xyz',
452452
'_symmetry_equiv_pos_as_xyz')
@@ -469,21 +469,16 @@ def _parse_space_group_symop_operation_xyz(self, block):
469469
sgid = (int(block.get('_space_group_IT_number', '0')) or
470470
int(block.get('_symmetry_Int_Tables_number', '0')) or
471471
sg_nameHM)
472-
# try to reuse existing space group
473472
self.spacegroup = None
474-
if sgid and IsSpaceGroupIdentifier(sgid):
475-
sgstd = GetSpaceGroup(sgid)
476-
oprep_std = [str(op) for op in sgstd.iter_symops()]
477-
oprep_std.sort()
478-
oprep_cif = [str(op) for op in symop_list]
479-
oprep_cif.sort()
480-
# make sure symmetry operations have the same order
481-
if oprep_std == oprep_cif:
482-
self.spacegroup = copy.copy(sgstd)
483-
self.spacegroup.symop_list = symop_list
484-
# use standard definition when symmetry operations were not listed
485-
elif not symop_list:
486-
self.spacegroup = sgstd
473+
# try to reuse existing space group from symmetry operations
474+
if symop_list:
475+
try:
476+
self.spacegroup = FindSpaceGroup(symop_list)
477+
except ValueError:
478+
pass
479+
# otherwise lookup the space group from its identifier
480+
if self.spacegroup is None and sgid and IsSpaceGroupIdentifier(sgid):
481+
self.spacegroup = GetSpaceGroup(sgid)
487482
# define new spacegroup when symmetry operations were listed, but
488483
# there is no match to an existing definition
489484
if symop_list and self.spacegroup is None:

src/diffpy/structure/spacegroups.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,66 @@ def IsSpaceGroupIdentifier(sgid):
7373
return rv
7474

7575

76+
def FindSpaceGroup(symops, shuffle=False):
77+
"""Lookup SpaceGroup from a given list of symmetry operations.
78+
79+
Parameters
80+
----------
81+
symops : list
82+
The list of `SymOp` objects for which to find SpaceGroup.
83+
shuffle : bool, optional
84+
Flag for allowing different order of symops in the returned
85+
SpaceGroup. The default is ``False``.
86+
87+
Returns
88+
-------
89+
SpaceGroup
90+
The SpaceGroup object with equivalent list of symmetry
91+
operations. Return predefined SpaceGroup instance when
92+
symmetry operations have the same order or when the
93+
`shuffle` flag is set.
94+
95+
Raises
96+
------
97+
ValueError
98+
When `symops` do not match any known SpaceGroup.
99+
"""
100+
import copy
101+
from six.moves import zip_longest
102+
tb = _getSGHashLookupTable()
103+
hh = _hashSymOpList(symops)
104+
if not hh in tb:
105+
raise ValueError('Cannot find SpaceGroup for the specified symops.')
106+
rv = tb[hh]
107+
if not shuffle:
108+
zz = zip_longest(rv.iter_symops(), symops, fillvalue='')
109+
sameorder = all(str(o0) == str(o1) for o0, o1 in zz)
110+
if not sameorder:
111+
rv = copy.copy(rv)
112+
rv.symop_list = symops
113+
return rv
114+
115+
116+
def _hashSymOpList(symops):
117+
"""Return hash value for a sequence of `SymOp` objects.
118+
119+
The symops are sorted so the results is independent of symops order.
120+
121+
Parameters
122+
----------
123+
symops : sequence
124+
The sequence of `SymOp` objects to be hashed
125+
126+
Returns
127+
-------
128+
int
129+
The hash value.
130+
"""
131+
ssop = sorted(str(o) for o in symops)
132+
rv = hash(tuple(ssop))
133+
return rv
134+
135+
76136
def _buildSGLookupTable():
77137
"""Rebuild space group lookup table from the SpaceGroupList data.
78138
@@ -116,6 +176,19 @@ def _buildSGLookupTable():
116176
return
117177
_sg_lookup_table = {}
118178

179+
180+
def _getSGHashLookupTable():
181+
"""Return lookup table of symop hashes to standard SpaceGroup objects.
182+
"""
183+
if _sg_hash_lookup_table:
184+
return _sg_hash_lookup_table
185+
for sg in SpaceGroupList:
186+
h = _hashSymOpList(sg.symop_list)
187+
_sg_hash_lookup_table[h] = sg
188+
assert len(_sg_hash_lookup_table) == len(SpaceGroupList)
189+
return _getSGHashLookupTable()
190+
_sg_hash_lookup_table = {}
191+
119192
# Import SpaceGroup objects --------------------------------------------------
120193

121194
from diffpy.structure.spacegroupmod import (
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
data_9008569
2+
_chemical_name 'Graphite'
3+
loop_
4+
_publ_author_name
5+
'Wyckoff R W G'
6+
_journal_name_full "Crystal Structures"
7+
_journal_volume 1
8+
_journal_year 1963
9+
_journal_page_first 7
10+
_journal_page_last 83
11+
_publ_section_title
12+
;
13+
Second edition. Interscience Publishers, New York, New York
14+
;
15+
_chemical_formula_sum 'C'
16+
_cell_length_a 2.456
17+
_cell_length_b 2.456
18+
_cell_length_c 6.696
19+
_cell_angle_alpha 90
20+
_cell_angle_beta 90
21+
_cell_angle_gamma 120
22+
_cell_volume 34.979
23+
# _symmetry_space_group_name_H-M 'P 63 m c'
24+
loop_
25+
_symmetry_equiv_pos_as_xyz
26+
'x,y,z'
27+
'-x,-x+y,1/2+z'
28+
'x-y,x,1/2+z'
29+
'-y,-x,z'
30+
'-y,x-y,z'
31+
'x-y,-y,1/2+z'
32+
# '-x,-y,1/2+z'
33+
# 'x,x-y,z'
34+
# '-x+y,-x,z'
35+
# 'y,x,1/2+z'
36+
# 'y,-x+y,1/2+z'
37+
# '-x+y,y,z'
38+
loop_
39+
_atom_site_label
40+
_atom_site_fract_x
41+
_atom_site_fract_y
42+
_atom_site_fract_z
43+
C1 0.00000 0.00000 0.00000
44+
C2 0.33333 0.66667 0.00000

src/diffpy/structure/tests/testp_cif.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,18 @@ def test_badspacegroup_cif(self):
277277
return
278278

279279

280+
def test_custom_spacegroup_cif(self):
281+
"""Test parsing of nonstandard symops-defined space group.
282+
"""
283+
pfile = self.pfile
284+
filename = datafile('customsg.cif')
285+
pfile.parseFile(filename)
286+
sg = pfile.spacegroup
287+
self.assertEqual('CIF data', sg.short_name)
288+
self.assertEqual(6, len(sg.symop_list))
289+
return
290+
291+
280292
def test_getParser(self):
281293
"""Test passing of eps keyword argument by getParser function.
282294
"""
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env python
2+
##############################################################################
3+
#
4+
# diffpy.structure Complex Modeling Initiative
5+
# (c) 2019 Brookhaven Science Associates,
6+
# Brookhaven National Laboratory.
7+
# All rights reserved.
8+
#
9+
# File coded by: Pavol Juhas
10+
#
11+
# See AUTHORS.txt for a list of people who contributed.
12+
# See LICENSE.txt for license information.
13+
#
14+
##############################################################################
15+
16+
"""Unit tests for diffpy.structure.spacegroups
17+
"""
18+
19+
20+
import unittest
21+
22+
from diffpy.structure.spacegroups import SpaceGroupList, _hashSymOpList
23+
from diffpy.structure.spacegroups import GetSpaceGroup, FindSpaceGroup
24+
25+
# ----------------------------------------------------------------------------
26+
27+
class TestRoutines(unittest.TestCase):
28+
29+
def setUp(self):
30+
return
31+
32+
33+
def test_FindSpaceGroup(self):
34+
"check FindSpaceGroup function"
35+
sg123 = GetSpaceGroup(123)
36+
ops123 = list(sg123.iter_symops())
37+
self.assertRaises(ValueError, FindSpaceGroup, [])
38+
self.assertRaises(ValueError, FindSpaceGroup, 2 * ops123)
39+
self.assertIs(sg123, FindSpaceGroup(ops123))
40+
sg123r = FindSpaceGroup(ops123[::-1])
41+
self.assertIsNot(sg123, sg123r)
42+
self.assertIsNot(sg123.symop_list, sg123r.symop_list)
43+
self.assertEqual(ops123[::-1], sg123r.symop_list)
44+
self.assertEqual(_hashSymOpList(sg123.symop_list),
45+
_hashSymOpList(sg123r.symop_list))
46+
self.assertIs(sg123, FindSpaceGroup(ops123[::-1], shuffle=True))
47+
return
48+
49+
50+
def test__hashSymOpList(self):
51+
"verify _hashSymOpList is unique for each spacegroup"
52+
hset = set(_hashSymOpList(sg.symop_list) for sg in SpaceGroupList)
53+
self.assertEqual(len(SpaceGroupList), len(hset))
54+
return
55+
56+
# End of class TestRoutines
57+
58+
# ----------------------------------------------------------------------------
59+
60+
if __name__ == '__main__':
61+
unittest.main()

0 commit comments

Comments
 (0)