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
« 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
5from dynasor.tools.structures import get_P_matrix
6from dynasor.qpoints.tools import get_commensurate_lattice_points, find_on_line
9class Lattice:
11 def __init__(self, primitive: NDArray, supercell: NDArray):
12 """Representation of a crystal supercell.
14 The supercell `S` is given by the primitive cell p and a repetition
15 matrix `P` such that:
17 dot(P, p) = S
19 In this convention the cell vectors are row vectors of `p` and `S` as in
20 ASE. An inverse cell is defined as:
22 c_inv = inv(c).T
24 and the reciprocal cell is defined as:
26 c_rec = 2*pi*inv(c).T
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:
32 dot(P.T, S_inv) = p_inv
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.
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.
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 """
53 self._primitive = np.array(primitive)
54 self._supercell = np.array(supercell)
56 self._P = get_P_matrix(self.primitive, self.supercell)
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
62 @property
63 def primitive(self) -> NDArray[float]:
64 """Returns the primitive cell with lattice vectors as rows"""
65 return self._primitive
67 @property
68 def supercell(self) -> NDArray[float]:
69 """Returns the supercell with lattice vectors as rows"""
70 return self._supercell
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
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
82 @property
83 def P(self) -> NDArray[int]:
84 """The P-matrix for this system, P @ primitive = supercell"""
85 return self._P
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
93 @property
94 def qpoints(self) -> NDArray[float]:
95 """Cartesian commensurate q-points"""
96 return self._qpoints
98 def __len__(self) -> int:
99 return len(self._qpoints)
101 def reduced_to_cartesian(self, qpoints: NDArray[float]) -> NDArray[float]:
102 """Convert from reduced to cartesian coordinates"""
103 return qpoints @ self.reciprocal_primitive
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
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
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.
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