Coverage for gpaw/elph/filter.py: 17%
41 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-12 00:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-12 00:18 +0000
1import numpy as np
2import numpy.fft as fft
3import numpy.linalg as la
5from ase import Atoms
6import ase.units as units
8from gpaw.typing import ArrayND
11def fourier_filter(atoms: Atoms, supercell: tuple, V1t_xG: ArrayND,
12 components: str, criteria=0) -> None:
13 """Fourier filter atomic gradients of the effective potential.
15 ... or any other property defined the same way.
17 This method is not tested.
19 Parameters
20 ----------
21 atoms: Atoms
22 Atoms object, primitive cell
23 supercell: tuple
24 Size of supercell given by the number of repetitions (l, m, n) of
25 the small unit cell in each direction.
26 V1t_xG: ndarray
27 Array representation of atomic gradients of the effective potential
28 in the supercell grid.
29 components: str
30 Fourier components to filter out (``normal`` or ``umklapp``).
31 """
32 assert components in ['normal', 'umklapp']
33 # Grid shape
34 shape = V1t_xG.shape[-3:]
36 # Primitive unit cells in Bohr/Bohr^-1
37 cell_cv = atoms.get_cell() / units.Bohr
38 reci_vc = 2 * np.pi * la.inv(cell_cv)
39 norm_c = np.sqrt(np.sum(reci_vc**2, axis=0))
40 # Periodic BC array
41 pbc_c = np.array(atoms.get_pbc(), dtype=bool)
43 # Supercell atoms and cell
44 atoms_N = atoms * supercell
45 supercell_cv = atoms_N.get_cell() / units.Bohr
47 # q-grid in units of the grid spacing (FFT ordering)
48 q_cG = np.indices(shape).reshape(3, -1)
49 q_c = np.array(shape)[:, np.newaxis]
50 q_cG += q_c // 2
51 q_cG %= q_c
52 q_cG -= q_c // 2
54 # Locate q-points inside the Brillouin zone
55 if criteria == 0:
56 # Works for all cases
57 # Grid spacing in direction of reciprocal lattice vectors
58 h_c = np.sqrt(np.sum((2 * np.pi * la.inv(supercell_cv))**2,
59 axis=0))
60 # XXX Why does a "*=" operation on q_cG not work here ??
61 q1_cG = q_cG * h_c[:, np.newaxis] / (norm_c[:, np.newaxis] / 2)
62 mask_G = np.ones(np.prod(shape), dtype=bool)
63 for i, pbc in enumerate(pbc_c):
64 if not pbc:
65 continue
66 mask_G &= (-1. < q1_cG[i]) & (q1_cG[i] <= 1.)
67 else:
68 # 2D hexagonal lattice
69 # Projection of q points onto the periodic directions. Only in
70 # these directions do normal and umklapp processees make sense.
71 q_vG = np.dot(q_cG[pbc_c].T,
72 2 * np.pi * la.inv(supercell_cv).T[pbc_c]).T.copy()
73 # Parametrize the BZ boundary in terms of the angle theta
74 theta_G = np.arctan2(q_vG[1], q_vG[0]) % (np.pi / 3)
75 phi_G = np.pi / 6 - np.abs(theta_G)
76 qmax_G = norm_c[0] / 2 / np.cos(phi_G)
77 norm_G = np.sqrt(np.sum(q_vG**2, axis=0))
78 # Includes point on BZ boundary with +1e-2
79 mask_G = (norm_G <= qmax_G + 1e-2)
81 if components == 'umklapp':
82 mask_G = ~mask_G
84 # Reshape to grid shape
85 mask_G.shape = shape
87 for V1t_G in V1t_xG:
88 # Fourier transform atomic gradient
89 V1tq_G = fft.fftn(V1t_G)
90 # Zero normal/umklapp components
91 V1tq_G[mask_G] = 0.0
92 # Fourier transform back
93 V1t_G[:] = fft.ifftn(V1tq_G).real