Coverage for gpaw/analyse/wignerseitz.py: 61%
84 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-20 00:19 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-20 00:19 +0000
1from math import sqrt, pi
3import numpy as np
4from ase.units import Bohr
6from gpaw.utilities import pack_density
7from gpaw.analyse.hirshfeld import HirshfeldDensity
8from gpaw.utilities.tools import coordinates
11def wignerseitz(gd, atoms, scale=None):
12 """Determine which atom is closest to each grid point.
14 The atom distances might be scaled by the scale list."""
15 if scale is None:
16 scale = [1.] * len(atoms)
17 else:
18 assert len(scale) == len(atoms)
19 r_vG, R2min_G = coordinates(gd, atoms[0].position / Bohr)
20 R2min_G *= scale[0] ** 2
21 index_G = gd.zeros(dtype=int)
22 for a, atom in enumerate(atoms[1:]):
23 r_vG, r2_G = coordinates(gd, atom.position / Bohr)
24 r2_G *= scale[a + 1] ** 2
25 index_G = np.where(R2min_G > r2_G, a + 1, index_G)
26 R2min_G = np.where(R2min_G > r2_G, r2_G, R2min_G)
27 return index_G
30class WignerSeitz:
32 def __init__(self, gd, atoms,
33 calculator=None, scale=None):
34 """Find the grid points nearest to the atoms"""
36 self.atoms = atoms
37 self.gd = gd
38 self.calculator = calculator
40 self.atom_index = wignerseitz(gd, atoms, scale)
42 def expand(self, density):
43 """Expand a smooth density in Wigner-Seitz cells around the atoms"""
44 n = len(self.atoms)
45 weights = np.empty((n,))
46 for a in range(n):
47 mask = np.where(self.atom_index == a, density, 0.0)
48 # XXX Optimize! No need to integrate in zero-region
49 weights[a] = self.gd.integrate(mask)
51 return weights
53 def expand_density(self, nt_G, s, nspins):
54 """Get the weights of spin-density in Wigner-Seitz cells
55 around the atoms. The spin index and number of spins are
56 needed for the augmentation sphere corrections."""
57 weights_a = self.expand(nt_G)
58 for a, nucleus in enumerate(self.atoms):
59 weights_a[a] += nucleus.get_density_correction(s, nspins)
60 return weights_a
62 def expand_wave_function(self, psit_G, u, n):
63 """Get the weights of wave function in Wigner-Seitz cells
64 around the atoms. The spin-k-point index u and band number n
65 are needed for the augmentation sphere corrections."""
67 assert psit_G.dtype == float
68 # smooth part
69 weigths = self.expand(psit_G ** 2)
71 # add augmentation sphere corrections
72 for a, nucleus in enumerate(self.atoms):
73 P_i = nucleus.P_uni[u, n]
74 P_p = pack_density(np.outer(P_i, P_i))
75 Delta_p = sqrt(4 * pi) * nucleus.setup.Delta_pL[:, 0]
76 weigths[a] += np.dot(Delta_p, P_p)
78 return weigths
80 def get_charges(self, den_g):
81 """Charge on the atom according to the Wigner-Seitz partitioning
83 Can be applied to any density den_g.
84 """
85 assert den_g.shape == tuple(self.gd.n_c)
86 charges = []
87 for atom, q in zip(self.atoms, self.expand(den_g)):
88 charges.append(atom.number - q)
89 return charges
91 def get_effective_volume_ratio(self, atom_index):
92 """Effective volume to free volume ratio.
94 After: Tkatchenko and Scheffler PRL 102 (2009) 073005
95 """
96 atoms = self.atoms
97 finegd = self.gd
99 den_g, gd = self.calculator.density.get_all_electron_density(atoms)
100 assert gd == finegd
101 denfree_g, gd = self.hdensity.get_density([atom_index])
102 assert gd == finegd
104 # the atoms r^3 grid
105 position = self.atoms[atom_index].position / Bohr
106 r_vg, r2_g = coordinates(finegd, origin=position)
107 r3_g = r2_g * np.sqrt(r2_g)
109 weight_g = np.where(self.atom_index == atom_index, 1.0, 0.0)
111 nom = finegd.integrate(r3_g * den_g[0] * weight_g)
112 denom = finegd.integrate(r3_g * denfree_g)
114 return nom / denom
116 def get_effective_volume_ratios(self):
117 """Return the list of effective volume to free volume ratios."""
118 ratios = []
119 self.hdensity = HirshfeldDensity(self.calculator)
120 for a, atom in enumerate(self.atoms):
121 ratios.append(self.get_effective_volume_ratio(a))
122 return np.array(ratios)
125class LDOSbyBand:
127 """Base class for a band by band LDOS"""
129 def by_element(self):
130 # get element indicees
131 elemi = {}
132 for i, nucleus in enumerate(self.paw.atoms):
133 symbol = nucleus.setup.symbol
134 if symbol in elemi:
135 elemi[symbol].append(i)
136 else:
137 elemi[symbol] = [i]
138 for key in elemi.keys():
139 elemi[key] = self.get(elemi[key])
140 return elemi
143'''
144class WignerSeitzLDOS(LDOSbyBand):
146 """Class to get the unfolded LDOS defined by Wigner-Seitz cells"""
148 def __init__(self, paw):
149 self.paw = paw
150 self.ws = WignerSeitz(paw.gd, paw.atoms)
152 nu = paw.nkpts * paw.nspins
153 ldos = np.empty((nu, paw.nbands, len(paw.atoms)))
154 for u, kpt in enumerate(paw.kpt_u):
155 for n, psit_G in enumerate(kpt.psit_nG):
156 # XXX variable ws undefined. Probably self.ws
157 ldos[u, n, :] = ws.expand_wave_function(psit_G, u, n)
159 def write(self, filename, slavewrite=False):
160 if self.world.rank == MASTER or slavewrite:
161 paw = self.paw
162 f = open(filename, 'w')
164 nn = len(paw.atoms)
166 for k in range(paw.nkpts):
167 for s in range(paw.nspins):
168 u = s * paw.nkpts + k
169 for n in range(paw.nbands):
170 # avery: Added dummy loop body to make compiling work.
171 1
172'''