Coverage for /builds/hweiske/ase/ase/spacegroup/symmetrize.py: 98.78%

82 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-22 11:22 +0000

1""" 

2Provides utility functions for FixSymmetry class 

3""" 

4import numpy as np 

5 

6from ase.utils import atoms_to_spglib_cell 

7 

8__all__ = ['refine_symmetry', 'check_symmetry'] 

9 

10 

11def print_symmetry(symprec, dataset): 

12 print("ase.spacegroup.symmetrize: prec", symprec, 

13 "got symmetry group number", dataset["number"], 

14 ", international (Hermann-Mauguin)", dataset["international"], 

15 ", Hall ", dataset["hall"]) 

16 

17 

18def refine_symmetry(atoms, symprec=0.01, verbose=False): 

19 """ 

20 Refine symmetry of an Atoms object 

21 

22 Parameters 

23 ---------- 

24 atoms - input Atoms object 

25 symprec - symmetry precicion 

26 verbose - if True, print out symmetry information before and after 

27 

28 Returns 

29 ------- 

30 

31 spglib dataset 

32 

33 """ 

34 import spglib 

35 

36 # test orig config with desired tol 

37 dataset = check_symmetry(atoms, symprec, verbose=verbose) 

38 

39 # set actual cell to symmetrized cell vectors by copying 

40 # transformed and rotated standard cell 

41 std_cell = dataset['std_lattice'] 

42 trans_std_cell = dataset['transformation_matrix'].T @ std_cell 

43 rot_trans_std_cell = trans_std_cell @ dataset['std_rotation_matrix'] 

44 atoms.set_cell(rot_trans_std_cell, True) 

45 

46 # get new dataset and primitive cell 

47 dataset = check_symmetry(atoms, symprec=symprec, verbose=verbose) 

48 res = spglib.find_primitive(atoms_to_spglib_cell(atoms), symprec=symprec) 

49 prim_cell, prim_scaled_pos, prim_types = res 

50 

51 # calculate offset between standard cell and actual cell 

52 std_cell = dataset['std_lattice'] 

53 rot_std_cell = std_cell @ dataset['std_rotation_matrix'] 

54 rot_std_pos = dataset['std_positions'] @ rot_std_cell 

55 pos = atoms.get_positions() 

56 dp0 = (pos[list(dataset['mapping_to_primitive']).index(0)] - rot_std_pos[ 

57 list(dataset['std_mapping_to_primitive']).index(0)]) 

58 

59 # create aligned set of standard cell positions to figure out mapping 

60 rot_prim_cell = prim_cell @ dataset['std_rotation_matrix'] 

61 inv_rot_prim_cell = np.linalg.inv(rot_prim_cell) 

62 aligned_std_pos = rot_std_pos + dp0 

63 

64 # find ideal positions from position of corresponding std cell atom + 

65 # integer_vec . primitive cell vectors 

66 # here we are assuming that primitive vectors returned by find_primitive 

67 # are compatible with std_lattice returned by get_symmetry_dataset 

68 mapping_to_primitive = list(dataset['mapping_to_primitive']) 

69 std_mapping_to_primitive = list(dataset['std_mapping_to_primitive']) 

70 pos = atoms.get_positions() 

71 for i_at in range(len(atoms)): 

72 std_i_at = std_mapping_to_primitive.index(mapping_to_primitive[i_at]) 

73 dp = aligned_std_pos[std_i_at] - pos[i_at] 

74 dp_s = dp @ inv_rot_prim_cell 

75 pos[i_at] = (aligned_std_pos[std_i_at] - np.round(dp_s) @ rot_prim_cell) 

76 atoms.set_positions(pos) 

77 

78 # test final config with tight tol 

79 return check_symmetry(atoms, symprec=1e-4, verbose=verbose) 

80 

81 

82def check_symmetry(atoms, symprec=1.0e-6, verbose=False): 

83 """ 

84 Check symmetry of `atoms` with precision `symprec` using `spglib` 

85 

86 Prints a summary and returns result of `spglib.get_symmetry_dataset()` 

87 """ 

88 import spglib 

89 dataset = spglib.get_symmetry_dataset(atoms_to_spglib_cell(atoms), 

90 symprec=symprec) 

91 if verbose: 

92 print_symmetry(symprec, dataset) 

93 return dataset 

94 

95 

96def is_subgroup(sup_data, sub_data, tol=1e-10): 

97 """ 

98 Test if spglib dataset `sub_data` is a subgroup of dataset `sup_data` 

99 """ 

100 for rot1, trns1 in zip(sub_data['rotations'], sub_data['translations']): 

101 for rot2, trns2 in zip(sup_data['rotations'], sup_data['translations']): 

102 if np.all(rot1 == rot2) and np.linalg.norm(trns1 - trns2) < tol: 

103 break 

104 else: 

105 return False 

106 return True 

107 

108 

109def prep_symmetry(atoms, symprec=1.0e-6, verbose=False): 

110 """ 

111 Prepare `at` for symmetry-preserving minimisation at precision `symprec` 

112 

113 Returns a tuple `(rotations, translations, symm_map)` 

114 """ 

115 import spglib 

116 

117 dataset = spglib.get_symmetry_dataset(atoms_to_spglib_cell(atoms), 

118 symprec=symprec) 

119 if verbose: 

120 print_symmetry(symprec, dataset) 

121 rotations = dataset['rotations'].copy() 

122 translations = dataset['translations'].copy() 

123 symm_map = [] 

124 scaled_pos = atoms.get_scaled_positions() 

125 for (rot, trans) in zip(rotations, translations): 

126 this_op_map = [-1] * len(atoms) 

127 for i_at in range(len(atoms)): 

128 new_p = rot @ scaled_pos[i_at, :] + trans 

129 dp = scaled_pos - new_p 

130 dp -= np.round(dp) 

131 i_at_map = np.argmin(np.linalg.norm(dp, axis=1)) 

132 this_op_map[i_at] = i_at_map 

133 symm_map.append(this_op_map) 

134 return (rotations, translations, symm_map) 

135 

136 

137def symmetrize_rank1(lattice, inv_lattice, forces, rot, trans, symm_map): 

138 """ 

139 Return symmetrized forces 

140 

141 lattice vectors expected as row vectors (same as ASE get_cell() convention), 

142 inv_lattice is its matrix inverse (reciprocal().T) 

143 """ 

144 scaled_symmetrized_forces_T = np.zeros(forces.T.shape) 

145 

146 scaled_forces_T = np.dot(inv_lattice.T, forces.T) 

147 for (r, t, this_op_map) in zip(rot, trans, symm_map): 

148 transformed_forces_T = np.dot(r, scaled_forces_T) 

149 scaled_symmetrized_forces_T[:, this_op_map] += transformed_forces_T 

150 scaled_symmetrized_forces_T /= len(rot) 

151 symmetrized_forces = (lattice.T @ scaled_symmetrized_forces_T).T 

152 

153 return symmetrized_forces 

154 

155 

156def symmetrize_rank2(lattice, lattice_inv, stress_3_3, rot): 

157 """ 

158 Return symmetrized stress 

159 

160 lattice vectors expected as row vectors (same as ASE get_cell() convention), 

161 inv_lattice is its matrix inverse (reciprocal().T) 

162 """ 

163 scaled_stress = np.dot(np.dot(lattice, stress_3_3), lattice.T) 

164 

165 symmetrized_scaled_stress = np.zeros((3, 3)) 

166 for r in rot: 

167 symmetrized_scaled_stress += np.dot(np.dot(r.T, scaled_stress), r) 

168 symmetrized_scaled_stress /= len(rot) 

169 

170 sym = np.dot(np.dot(lattice_inv, symmetrized_scaled_stress), lattice_inv.T) 

171 return sym