Coverage for local_installation/dynasor/qpoints/tools.py: 98%

63 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 07:34 +0000

1import itertools 

2from fractions import Fraction 

3from typing import Dict, List, Tuple 

4 

5import numpy as np 

6from numpy.typing import NDArray 

7 

8from dynasor.modes.tools import inv 

9 

10 

11def get_supercell_qpoints_along_path( 

12 path: List[Tuple[str, str]], 

13 coordinates: Dict[str, NDArray[float]], 

14 primitive_cell: NDArray[float], 

15 super_cell: NDArray[float]) -> List[NDArray[float]]: 

16 r""" 

17 Returns the q-points commensurate with the given supercell along the specific path. 

18 

19 Parameters 

20 ---------- 

21 path 

22 list of pairs of q-point labels 

23 coordinates 

24 dict with q-point labels and coordinates as keys and values, respectively; 

25 there must be one entry for each q-point label used in :attr:`path` 

26 primitive_cell 

27 cell metric of the primitive cell with lattice vectors as rows 

28 super_cell 

29 cell metric of the supercell with lattice vectors as rows 

30 

31 Returns 

32 ------- 

33 supercell_paths 

34 A list of the accessible q-point coordinates along the specified segment 

35 

36 Example 

37 -------- 

38 The following example illustrates how to retrieve the q-points that 

39 can be sampled using a supercell comprising :math:`6 \times 6 \times 6` 

40 conventional (4-atom) unit cells of FCC Al along the path X-:math:`\Gamma`-L. 

41 

42 >>> import numpy as np 

43 >>> from ase.build import bulk 

44 >>> from dynasor.qpoints import get_supercell_qpoints_along_path 

45 >>> prim = bulk('Al', 'fcc', a=4.0) 

46 >>> supercell = bulk('Al', 'fcc', a=4.0, cubic=True).repeat(6) 

47 >>> path = [('X', 'G'), ('G', 'L'), ('L', 'W')] 

48 >>> coordinates = dict(X=[0.5, 0.5, 0], G=[0, 0, 0], 

49 ... L=[0.5, 0.5, 0.5], W=[0.5, 0.25, 0.75]) 

50 >>> qpoints = get_supercell_qpoints_along_path(path, coordinates, prim.cell, supercell.cell) 

51 

52 """ 

53 from .lattice import Lattice 

54 lat = Lattice(primitive_cell, super_cell) 

55 

56 for lbl in np.array(path).flatten(): 

57 if lbl not in coordinates: 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

58 raise ValueError(f'Unknown point in path: {lbl}') 

59 

60 # build the segments 

61 supercell_paths = [] 

62 for k, (l1, l2) in enumerate(path): 

63 q1 = np.array(coordinates[l1], dtype=float) 

64 q2 = np.array(coordinates[l2], dtype=float) 

65 dynasor_path, _ = lat.make_path(q1, q2) 

66 supercell_paths.append(dynasor_path) 

67 return supercell_paths 

68 

69 

70def find_on_line(start: NDArray, stop: NDArray, P: NDArray): 

71 """Find fractional distances between start and stop combatible with P 

72 

73 A supercell is defined by P @ c = S for some repetition matrix P and we 

74 want to find fractions so that 

75 

76 [start + f * (stop - start)] @ P = n 

77 

78 Parameters 

79 ---------- 

80 start 

81 start of line in reduced supercell coordinates 

82 stop 

83 end of line in reduced supercell coordinates 

84 P 

85 repetion matrix defining the supercell 

86 """ 

87 

88 if np.allclose(start, stop): 

89 return [Fraction(0, 1)] 

90 

91 start = np.array([Fraction(s).limit_denominator() for s in start]) 

92 stop = np.array([Fraction(s).limit_denominator() for s in stop]) 

93 

94 A = start @ P 

95 B = (stop - start) @ P 

96 

97 fracs = None 

98 for a, b in zip(A, B): 

99 fs = solve_Diophantine(a, b) 

100 if fs is None: # "inf" solutions 

101 continue 

102 elif fs == []: # No solutions 

103 return [] 

104 fracs = set(fs) if fracs is None else fracs.intersection(fs) 

105 return sorted(fracs) 

106 

107 

108def solve_Diophantine(a: Fraction, b: Fraction) -> List[Fraction]: 

109 """Solve n = a + xb for all n in Z and a,b in Q such that 0 <= x <= 1""" 

110 

111 if b == 0: 

112 if a.denominator == 1: 

113 return None 

114 else: 

115 return [] 

116 

117 if b < 0: 

118 right = np.ceil(a) 

119 left = np.floor(a + b) 

120 else: 

121 left = np.floor(a) 

122 right = np.ceil(a + b) 

123 

124 ns = np.arange(left, right + 1) 

125 fracs = [Fraction(n - a, b) for n in ns] 

126 fracs = [f for f in fracs if 0 <= f <= 1] 

127 

128 return fracs 

129 

130 

131def get_commensurate_lattice_points(P: NDArray) -> NDArray: 

132 """Return commensurate points for a supercell defined by repetition matrix P 

133 

134 Finds all n such that n = f P where f is between 0 and 1 

135 

136 Parameters 

137 ---------- 

138 P 

139 the repetion matrix relating the primitive and supercell 

140 

141 Returns 

142 ------- 

143 lattice_points 

144 the commensurate lattice points 

145 """ 

146 inv_P_matrix = inv(P, as_fraction=True) 

147 

148 assert np.all(P @ inv_P_matrix == np.eye(3)) 

149 

150 n_max = np.where(P > 0, P, 0).sum(axis=0) + 1 

151 n_min = np.where(P < 0, P, 0).sum(axis=0) 

152 

153 ranges = [np.arange(*n) for n in zip(n_min, n_max)] 

154 

155 lattice_points = [] 

156 for lp in itertools.product(*ranges): 

157 tmp = lp @ inv_P_matrix 

158 if np.all(tmp >= 0) and np.all(tmp < 1): 

159 lattice_points.append(lp) 

160 

161 assert len(lattice_points) == len(set(lattice_points)) 

162 lattice_points = np.array(lattice_points) 

163 return lattice_points