Skip to content

Commit 0d952f8

Browse files
Merge release-next into main
2 parents c391a4e + b78002e commit 0d952f8

File tree

3 files changed

+203
-5
lines changed
  • Framework/PythonInterface
  • docs/source/release/6.12.0/Diffraction/Single_Crystal/Bugfixes

3 files changed

+203
-5
lines changed

Framework/PythonInterface/plugins/algorithms/SaveINS.py

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,47 @@
55
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
66
# SPDX - License - Identifier: GPL - 3.0 +
77
from mantid.api import AlgorithmFactory, FileProperty, FileAction, WorkspaceProperty, PythonAlgorithm
8-
from mantid.kernel import Direction
8+
from mantid.kernel import Direction, V3D
99
from mantid.geometry import SymmetryOperationFactory, SpaceGroupFactory
1010
from os import path, makedirs
11+
import numpy as np
1112

1213

1314
class SaveINS(PythonAlgorithm):
1415
LATT_TYPE_MAP = {type: itype + 1 for itype, type in enumerate(["P", "I", "R", "F", "A", "B", "C"])}
16+
IDENTIY_OP = SymmetryOperationFactory.createSymOp("x,y,z")
1517
INVERSION_OP = SymmetryOperationFactory.createSymOp("-x,-y,-z")
18+
ROTATION_OPS = {1: [IDENTIY_OP, INVERSION_OP], -1: [IDENTIY_OP]}
19+
A_CENTERING_OP = SymmetryOperationFactory.createSymOp("x,y+1/2,z+1/2")
20+
B_CENTERING_OP = (SymmetryOperationFactory.createSymOp("x+1/2,y,z+1/2"),)
21+
C_CENTERING_OP = SymmetryOperationFactory.createSymOp("x+1/2,y+1/2,z")
22+
CENTERING_OPS = {
23+
1: [IDENTIY_OP],
24+
2: [
25+
IDENTIY_OP,
26+
SymmetryOperationFactory.createSymOp("x+1/2,y+1/2,z+1/2"),
27+
],
28+
3: [
29+
IDENTIY_OP,
30+
SymmetryOperationFactory.createSymOp("x+1/3,y+2/3,z+2/3"),
31+
SymmetryOperationFactory.createSymOp("x+2/3,y+1/3,z+1/3"),
32+
],
33+
4: [
34+
IDENTIY_OP,
35+
A_CENTERING_OP,
36+
B_CENTERING_OP,
37+
C_CENTERING_OP,
38+
],
39+
5: [
40+
IDENTIY_OP,
41+
A_CENTERING_OP,
42+
],
43+
6: [IDENTIY_OP, B_CENTERING_OP],
44+
7: [
45+
IDENTIY_OP,
46+
C_CENTERING_OP,
47+
],
48+
}
1649
DUMMY_WAVELENGTH = 1.0
1750

1851
def category(self):
@@ -113,7 +146,7 @@ def PyExec(self):
113146
f_handle.write(f"LATT {latt_type}\n")
114147

115148
# print sym operations
116-
for sym_str in spgr.getSymmetryOperationStrings():
149+
for sym_str in self._get_shelx_symmetry_operators(spgr, latt_type):
117150
f_handle.write(f"SYMM {sym_str}\n")
118151

119152
# print atom info
@@ -140,5 +173,84 @@ def PyExec(self):
140173
f_handle.write("HKLF 2\n") # tells SHELX the columns saved in the reflection file
141174
f_handle.write("END")
142175

176+
def _symmetry_operation_key(self, rot1_mat, trans1_vec, rot2_mat=np.eye(3), trans2_vec=np.zeros(3)):
177+
"""
178+
Generate a key for symmetry operation comparison.
179+
Combines rotation and translation into a unique tuple representation.
180+
Ex: "x,y,z+1/2" is equivalent to "x,y,z+0.5"
181+
"""
182+
rot_mat = rot1_mat @ rot2_mat
183+
trans_vec = rot1_mat @ trans2_vec + trans1_vec
184+
trans_vec = np.mod(trans_vec, 1) # Ensure trans_vec is within [0, 1)
185+
return tuple(np.round(rot_mat, 0).astype(int).flatten().tolist() + np.round(trans_vec, 3).tolist())
186+
187+
def _symmetry_matrix_vector(self, symop):
188+
"""
189+
Extract the rotation matrix (rot_mat) and translation vector (trans_vec) from a symmetry element.
190+
This symmetry operation transform any point via a matrix/translation pair.
191+
"""
192+
rot_mat = np.linalg.inv(np.vstack([symop.transformHKL(V3D(*vec)) for vec in np.eye(3)]))
193+
trans_vec = np.array(symop.transformCoordinates(V3D(0, 0, 0)))
194+
return rot_mat, trans_vec
195+
196+
def _generate_equivalent_operators(self, rotation_ops, centering_ops):
197+
"""
198+
Generate all equivalent symmetry operators for the given lattice rotation and centering operations.
199+
"""
200+
equivalent_ops = set()
201+
for rot in rotation_ops:
202+
rot2_mat, _ = self._symmetry_matrix_vector(rot)
203+
for cent in centering_ops:
204+
_, trans2_vec = self._symmetry_matrix_vector(cent)
205+
key = self._symmetry_operation_key(np.eye(3), np.zeros(3), rot2_mat, trans2_vec)
206+
equivalent_ops.add(key)
207+
return equivalent_ops
208+
209+
def _update_symmetry_dict(self, rot1_mat, trans1_vec, sym3, sym_key, sym_ops_dict, rot_mat_dict, trans_vec_dict):
210+
"""
211+
Update the symmetry operations dictionary with priority for closeness to identity/origin.
212+
This bias improves readability.
213+
# Ex: lattice type 3; "-x+y,-x,z" is simpler than "-x+y+1/3,-x+2/3,z+2/3"
214+
"""
215+
if sym3 not in sym_ops_dict or (
216+
np.linalg.det(rot1_mat) > np.linalg.det(rot_mat_dict[sym3]) # identity preferred
217+
or np.linalg.norm(trans1_vec) < np.linalg.norm(trans_vec_dict[sym3]) # origin preferred
218+
):
219+
sym_ops_dict[sym3] = sym_key
220+
rot_mat_dict[sym3] = rot1_mat
221+
trans_vec_dict[sym3] = trans1_vec
222+
223+
def _get_shelx_symmetry_operators(self, spgr, latt_type):
224+
"""
225+
Get SHELX symmetry operators for the given space group and lattice type.
226+
Returns symmetry set.
227+
"""
228+
latt_numb = abs(latt_type)
229+
latt_sign = 1 if latt_type > 0 else -1
230+
231+
# Generate equivalent lattice type operators common to lattice type.
232+
latt_type_ops_set = self._generate_equivalent_operators(self.ROTATION_OPS[latt_sign], self.CENTERING_OPS[latt_numb])
233+
234+
sym_ops = spgr.getSymmetryOperations()
235+
sym_ops_dict = {}
236+
rot_mat_dict = {}
237+
trans_vec_dict = {}
238+
239+
for sym_op in sym_ops:
240+
rot1_mat, trans1_vec = self._symmetry_matrix_vector(sym_op)
241+
sym_key = sym_op.getIdentifier()
242+
sym1 = self._symmetry_operation_key(rot1_mat, trans1_vec)
243+
244+
if sym1 not in latt_type_ops_set:
245+
# re-iterate over lattice operators to map equivalently generated
246+
for rot in self.ROTATION_OPS[latt_sign]:
247+
rot2_mat, _ = self._symmetry_matrix_vector(rot)
248+
for cent in self.CENTERING_OPS[latt_numb]:
249+
_, trans2_vec = self._symmetry_matrix_vector(cent)
250+
sym3 = self._symmetry_operation_key(rot1_mat, trans1_vec, rot2_mat, trans2_vec) # sym3 = sym1 X sym2
251+
self._update_symmetry_dict(rot1_mat, trans1_vec, sym3, sym_key, sym_ops_dict, rot_mat_dict, trans_vec_dict)
252+
253+
return set(sym_ops_dict.values())
254+
143255

144256
AlgorithmFactory.subscribe(SaveINS)

Framework/PythonInterface/test/python/plugins/algorithms/SaveINSTest.py

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ def setUpClass(cls):
2525
"ZERR 4 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000\n",
2626
"LATT 1\n",
2727
"SYMM -x+1/2,y+1/2,-z+1/2\n",
28-
"SYMM -x,-y,-z\n",
29-
"SYMM x+1/2,-y+1/2,z+1/2\n",
30-
"SYMM x,y,z\n",
3128
"NEUT\n",
3229
]
3330
cls.file_end = ["UNIT 48 36 12 8 4\n", "MERG 0\n", "HKLF 2\n", "END"]
@@ -143,13 +140,101 @@ def test_save_ins_constant_wavelength(self):
143140

144141
self._assert_file_contents(output_file, expected_lines)
145142

143+
def test_save_ins_symmetry_Rbar3(self):
144+
output_file = path.join(self._tmp_directory, "test5.ins")
145+
146+
SaveINS(InputWorkspace=self.ws, Filename=output_file, Spacegroup="R -3")
147+
148+
self.file_start = [
149+
"TITL ws\n",
150+
"REM This file was produced by mantid using SaveINS\n",
151+
"CELL 1.0 7.6508 13.2431 11.6243 90.0000 104.1183 90.0000\n",
152+
"ZERR 4 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000\n",
153+
"LATT 3\n",
154+
"SYMM -x+y,-x,z\n",
155+
"SYMM -y,x-y,z\n",
156+
"NEUT\n",
157+
]
158+
159+
expected_lines = [*self.file_start, "SFAC C H N O S\n", *self.file_end]
160+
161+
self._assert_line_in_file_contents(output_file, expected_lines)
162+
163+
def test_save_ins_symmetry_R3(self):
164+
output_file = path.join(self._tmp_directory, "test6.ins")
165+
166+
SaveINS(InputWorkspace=self.ws, Filename=output_file, Spacegroup="R 3")
167+
168+
self.file_start = [
169+
"TITL ws\n",
170+
"REM This file was produced by mantid using SaveINS\n",
171+
"CELL 1.0 7.6508 13.2431 11.6243 90.0000 104.1183 90.0000\n",
172+
"ZERR 4 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000\n",
173+
"LATT -3\n",
174+
"SYMM -x+y,-x,z\n",
175+
"SYMM -y,x-y,z\n",
176+
"NEUT\n",
177+
]
178+
179+
expected_lines = [*self.file_start, "SFAC C H N O S\n", *self.file_end]
180+
181+
self._assert_line_in_file_contents(output_file, expected_lines)
182+
183+
def test_save_ins_symmetry_Iabar3d(self):
184+
output_file = path.join(self._tmp_directory, "test7.ins")
185+
186+
SaveINS(InputWorkspace=self.ws, Filename=output_file, Spacegroup="I a -3 d")
187+
188+
self.file_start = [
189+
"TITL ws\n",
190+
"REM This file was produced by mantid using SaveINS\n",
191+
"CELL 1.0 7.6508 13.2431 11.6243 90.0000 104.1183 90.0000\n",
192+
"ZERR 4 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000\n",
193+
"LATT 2\n",
194+
"SYMM -x+1/4,-z+1/4,-y+1/4\n",
195+
"SYMM -x,-y+1/2,z\n",
196+
"SYMM x+3/4,z+1/4,-y+1/4\n",
197+
"SYMM -z+1/4,y+3/4,x+1/4\n",
198+
"SYMM z,x,y\n",
199+
"SYMM -x+1/2,y,-z\n",
200+
"SYMM x,-y,-z+1/2\n",
201+
"SYMM -y+1/4,-x+1/4,-z+1/4\n",
202+
"SYMM -z+1/4,-y+1/4,-x+1/4\n",
203+
"SYMM y,-z,-x+1/2\n",
204+
"SYMM y+3/4,x+1/4,-z+1/4\n",
205+
"SYMM -z,-x+1/2,y\n",
206+
"SYMM y+1/4,-x+1/4,z+3/4\n",
207+
"SYMM -x+1/4,z+3/4,y+1/4\n",
208+
"SYMM -y+1/2,z,-x\n",
209+
"SYMM -y,-z+1/2,x\n",
210+
"SYMM y,z,x\n",
211+
"SYMM z+3/4,y+1/4,-x+1/4\n",
212+
"SYMM x+1/4,-z+1/4,y+3/4\n",
213+
"SYMM z,-x,-y+1/2\n",
214+
"SYMM -y+1/4,x+3/4,z+1/4\n",
215+
"SYMM z+1/4,-y+1/4,x+3/4\n",
216+
"SYMM -z+1/2,x,-y\n",
217+
"NEUT\n",
218+
]
219+
220+
expected_lines = [*self.file_start, "SFAC C H N O S\n", *self.file_end]
221+
222+
self._assert_line_in_file_contents(output_file, expected_lines)
223+
146224
def _assert_file_contents(self, filepath, expected_lines):
147225
with open(filepath, "r") as f:
148226
lines = f.readlines()
149227
self.assertEqual(len(lines), len(expected_lines))
150228
for iline, line in enumerate(lines):
151229
self.assertEqual(line, expected_lines[iline])
152230

231+
def _assert_line_in_file_contents(self, filepath, expected_lines):
232+
with open(filepath, "r") as f:
233+
lines = f.readlines()
234+
self.assertEqual(len(lines), len(expected_lines))
235+
for line in lines:
236+
self.assertTrue(line in lines)
237+
153238

154239
if __name__ == "__main__":
155240
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix :ref:`SaveINS<algm-SaveINS-v1>` that saved all symmetry records to file. Only the minimum are needed that can be generated by translation/rotation corresponding to the lattice type.

0 commit comments

Comments
 (0)