Coverage for dynasor / qpoints / lattice.py: 96%

47 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-16 12:31 +0000

1import warnings 

2import numpy as np 

3from numpy.typing import NDArray 

4 

5from dynasor.tools.structures import get_P_matrix 

6from dynasor.qpoints.tools import get_commensurate_lattice_points, find_on_line 

7 

8 

9class Lattice: 

10 

11 def __init__(self, primitive: NDArray, supercell: NDArray): 

12 """Representation of a crystal supercell. 

13 

14 The supercell `S` is given by the primitive cell p and a repetition 

15 matrix `P` such that: 

16 

17 dot(P, p) = S 

18 

19 In this convention the cell vectors are row vectors of `p` and `S` as in 

20 ASE. An inverse cell is defined as: 

21 

22 c_inv = inv(c).T 

23 

24 and the reciprocal cell is defined as: 

25 

26 c_rec = 2*pi*inv(c).T 

27 

28 Notice that the inverse cell here is defined with the tranpose so that 

29 the lattic vectors of the inverse/reciprocal lattice are also row 

30 vectors. The above also implies: 

31 

32 dot(P.T, S_inv) = p_inv 

33 

34 The inverse supercell S_inv defines a new lattice in reciprocal space. 

35 Those inverse lattice points which resides inside the inverse primitive 

36 cell p_inv are called commensurate lattice points. These are typically 

37 the only points of interest in MD simulations from a crystallographic 

38 and lattice dynamics point of view. 

39 

40 The convention taken here is that the reciprocal cell carries the 2pi 

41 factor onto the cartesian q-points. This is consistent with e.g. 

42 Kittel. The reduced coordinates are always with respect to the 

43 reciprocal primitive cell. 

44 

45 Parameters 

46 ---------- 

47 primitive 

48 Cell metric of the primitive cell with lattice vectors as rows. 

49 supercell 

50 Cell metric of the supercell with lattice vectors as rows. 

51 """ 

52 

53 self._primitive = np.array(primitive) 

54 self._supercell = np.array(supercell) 

55 

56 self._P = get_P_matrix(self.primitive, self.supercell) 

57 

58 # As stated in the doc the P matrix relating the inverse cells are just P.T 

59 com = get_commensurate_lattice_points(self.P.T) 

60 self._qpoints = com @ self.reciprocal_supercell 

61 

62 @property 

63 def primitive(self) -> NDArray[float]: 

64 """Returns the primitive cell with lattice vectors as rows""" 

65 return self._primitive 

66 

67 @property 

68 def supercell(self) -> NDArray[float]: 

69 """Returns the supercell with lattice vectors as rows""" 

70 return self._supercell 

71 

72 @property 

73 def reciprocal_primitive(self) -> NDArray[float]: 

74 """Returns inv(primitive).T so that the rows are the inverse lattice vectors""" 

75 return 2*np.pi * np.linalg.inv(self.primitive.T) # inverse lattice as rows 

76 

77 @property 

78 def reciprocal_supercell(self) -> NDArray[float]: 

79 """Returns inv(super).T so that the rows are the inverse lattice vectors""" 

80 return 2*np.pi * np.linalg.inv(self.supercell.T) # reciprocal lattice as rows 

81 

82 @property 

83 def P(self) -> NDArray[int]: 

84 """The P-matrix for this system, P @ primitive = supercell""" 

85 return self._P 

86 

87 def __repr__(self) -> str: 

88 rep = (f'{self.__class__.__name__}(' 

89 f'primitive={self.primitive.tolist()}, ' 

90 f'supercell={self.supercell.tolist()})') 

91 return rep 

92 

93 @property 

94 def qpoints(self) -> NDArray[float]: 

95 """Cartesian commensurate q-points""" 

96 return self._qpoints 

97 

98 def __len__(self) -> int: 

99 return len(self._qpoints) 

100 

101 def reduced_to_cartesian(self, qpoints: NDArray[float]) -> NDArray[float]: 

102 """Convert from reduced to cartesian coordinates""" 

103 return qpoints @ self.reciprocal_primitive 

104 

105 def cartesian_to_reduced(self, qpoints: NDArray[float]) -> NDArray[float]: 

106 """Convert from Cartesian to reduced coordinates.""" 

107 return np.linalg.solve(self.reciprocal_primitive.T, qpoints.T).T 

108 

109 def make_path( 

110 self, 

111 start: NDArray[float], 

112 stop: NDArray[float], 

113 ) -> tuple[NDArray[float], NDArray[float]]: 

114 """Takes qpoints in reduced coordinates and returns all points in between 

115 

116 Parameters 

117 ---------- 

118 start 

119 Coordinate of starting point in reduced inverse coordinates. 

120 For example, a zone mode is given as (0.5, 0, 0), (0,0,0) == (1,0,0) etc. 

121 stop 

122 Stop position. 

123 

124 Returns 

125 ------- 

126 Tuple of the coordinates of commensurate points along path in Cartesian 

127 reciprocals and the fractional distances along th path. 

128 """ 

129 start = np.array(start) 

130 stop = np.array(stop) 

131 fracs = find_on_line(start, stop, self.P.T) 

132 if not len(fracs): 

133 warnings.warn('No q-points along path!') 

134 return np.zeros(shape=(0, 3)), np.zeros(shape=(0,)) 

135 points = np.array([start + float(f) * (stop - start) for f in fracs]) 

136 qpoints = self.reduced_to_cartesian(points) 

137 dists = np.array([float(f) for f in fracs]) 

138 return qpoints, dists