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
« 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
5import numpy as np
6from numpy.typing import NDArray
8from dynasor.modes.tools import inv
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.
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
31 Returns
32 -------
33 supercell_paths
34 A list of the accessible q-point coordinates along the specified segment
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.
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)
52 """
53 from .lattice import Lattice
54 lat = Lattice(primitive_cell, super_cell)
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}')
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
70def find_on_line(start: NDArray, stop: NDArray, P: NDArray):
71 """Find fractional distances between start and stop combatible with P
73 A supercell is defined by P @ c = S for some repetition matrix P and we
74 want to find fractions so that
76 [start + f * (stop - start)] @ P = n
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 """
88 if np.allclose(start, stop):
89 return [Fraction(0, 1)]
91 start = np.array([Fraction(s).limit_denominator() for s in start])
92 stop = np.array([Fraction(s).limit_denominator() for s in stop])
94 A = start @ P
95 B = (stop - start) @ P
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)
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"""
111 if b == 0:
112 if a.denominator == 1:
113 return None
114 else:
115 return []
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)
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]
128 return fracs
131def get_commensurate_lattice_points(P: NDArray) -> NDArray:
132 """Return commensurate points for a supercell defined by repetition matrix P
134 Finds all n such that n = f P where f is between 0 and 1
136 Parameters
137 ----------
138 P
139 the repetion matrix relating the primitive and supercell
141 Returns
142 -------
143 lattice_points
144 the commensurate lattice points
145 """
146 inv_P_matrix = inv(P, as_fraction=True)
148 assert np.all(P @ inv_P_matrix == np.eye(3))
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)
153 ranges = [np.arange(*n) for n in zip(n_min, n_max)]
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)
161 assert len(lattice_points) == len(set(lattice_points))
162 lattice_points = np.array(lattice_points)
163 return lattice_points