Coverage for tbkit/optics.py: 100%

149 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-15 06:13 +0000

1r""" 

2Linear and nonlinear optical response of 2D Bloch models (:class:`~tbkit.kspace.KSpace`) 

3over a uniform Brillouin-zone mesh. 

4 

5* :func:`optical_conductivity` -- the Kubo-Greenwood conductivity 

6 :math:`\sigma_{ab}(\omega)`, interband and intraband (Drude), with a 

7 broadening :math:`\eta`. 

8* :func:`joint_dos` -- the joint density of states of vertical transitions. 

9* :func:`berry_curvature_dipole` -- the Berry curvature dipole of the 

10 nonlinear Hall effect (Sodemann and Fu 2015). 

11* :func:`shift_current` -- the shift-current conductivity 

12 :math:`\sigma^{abc}(0;\omega,-\omega)` (Sipe and Shkrebtii 2000). 

13 

14Every function works in units :math:`\hbar = e = 1`, energies (and 

15:math:`\hbar\omega`) in the units of the hoppings, lengths in those of 

16*prim_vec*, and counts each orbital once: a spinless model gives the 

17response *per spin*. 

18 

19The velocities are the exact :math:`\partial H/\partial k_a` of 

20*KSpace._bloch_derivatives* along the Cartesian axes :math:`x, y` (for a 2D 

21lattice embedded in 3D space, the axes of *KSpace.k_basis*). With 

22``positions=True`` (the default) the bond vectors include the orbital 

23positions, :math:`\mathbf{d} = \mathbf{R} + \boldsymbol\tau_j - 

24\boldsymbol\tau_i`, so that the velocity is the physical :math:`i[H, 

25\mathbf{r}]` (see *KSpace.hall_conductivity*). 

26 

27Example usage:: 

28 

29 from tbkit.optics import optical_conductivity 

30 omega = np.linspace(0.05, 3., 60) 

31 sigma = optical_conductivity(graphene, omega, e_fermi=0., nk=300) 

32 # Re sigma ~ pi/4 e^2/h per spin for hbar omega << t 

33""" 

34from __future__ import annotations 

35 

36from typing import Any, Iterator 

37 

38import numpy as np 

39from numpy.typing import ArrayLike, NDArray 

40 

41import tbkit.error_handling as error_handling 

42import tbkit.occupation as occupation 

43from tbkit.kspace import KSpace 

44 

45 

46_AXES = {'x': 0, 'y': 1} 

47_DEGENERATE = 1e-9 

48 

49 

50def _mesh(ks: KSpace, nk: int | tuple[int, int], positions: bool) -> NDArray[np.float64]: 

51 ''' 

52 Private function. Validate the model (a static, Hermitian, 2D KSpace) 

53 and return the k-points of a uniform Brillouin-zone mesh. 

54 ''' 

55 error_handling.kspace(ks, KSpace) 

56 ks._check_static() 

57 error_handling.dim_exact(ks.dim, 2) 

58 error_handling.hermitian_model(ks.is_hermitian()) 

59 error_handling.boolean(positions, 'positions') 

60 return ks.mesh_grid(nk)[1] 

61 

62 

63def cell_area(ks: KSpace) -> float: 

64 r''' 

65 Get the area :math:`A_c = |\mathbf{a}_1\times\mathbf{a}_2|` of the unit 

66 cell of a 2D model (in the units of *prim_vec*), which turns a 

67 Brillouin-zone average into an integral: 

68 :math:`\int\frac{d^2k}{(2\pi)^2}g = \frac{1}{N_kA_c}\sum_{\mathbf{k}}g`. 

69 

70 :param ks: **KSpace** instance, 2D. 

71 

72 :returns: 

73 * **area** -- Positive real. 

74 ''' 

75 error_handling.kspace(ks, KSpace) 

76 error_handling.dim_exact(ks.dim, 2) 

77 a1, a2 = (np.zeros(3) for _ in range(2)) 

78 a1[:ks.space_dim] = ks.lat.prim_vec[0] 

79 a2[:ks.space_dim] = ks.lat.prim_vec[1] 

80 return float(np.linalg.norm(np.cross(a1, a2))) 

81 

82 

83def _bands( 

84 ks: KSpace, kpts: NDArray[np.float64], positions: bool, second: bool = False, 

85) -> Iterator[tuple]: 

86 r''' 

87 Private function. Over chunks of the k-points *kpts*: the band energies 

88 (nk, n), the velocity matrices :math:`v^a_{nm} = \langle n|\partial_aH|m\rangle` 

89 in the eigenbasis (2, nk, n, n) and, with *second*, the matrices 

90 :math:`w^{ab}_{nm} = \langle n|\partial_a\partial_bH|m\rangle` (2, 2, nk, n, n). 

91 ''' 

92 directions = [ks.k_basis[:, 0], ks.k_basis[:, 1]] 

93 chunk = max(1, 200000 // ks.norb ** 2) 

94 if second: 

95 # d^2 H / dk_a dk_b is the first derivative of the Bloch sum of the 

96 # hoppings t * i(d.u_a): the same _bloch_sum, with rescaled amplitudes. 

97 hops = ks._hop 

98 d = np.array([h[2] for h in hops], dtype='f8').reshape(len(hops), ks.space_dim) 

99 if positions: 

100 tau = ks.orbital_positions() 

101 d = d + tau[[h[1] for h in hops]] - tau[[h[0] for h in hops]] 

102 scaled = [[(h[0], h[1], h[2], h[3] * 1j * (dd @ u)) for h, dd in zip(hops, d)] 

103 for u in directions] 

104 for c0 in range(0, len(kpts), chunk): 

105 kc = kpts[c0:c0 + chunk] 

106 ham, dham = ks._bloch_derivatives(kc, directions, positions) 

107 en, vec = np.linalg.eigh(ham) 

108 vh = vec.conj().transpose(0, 2, 1) 

109 vel = np.array([vh @ dh @ vec for dh in dham]) 

110 if not second: 

111 yield en, vel 

112 continue 

113 k_cart = kc @ ks.k_basis.T 

114 w = np.array([ks._bloch_sum(h, k_cart, directions, positions)[1] for h in scaled]) 

115 yield en, vel, vh[None, None] @ w @ vec[None, None] 

116 

117 

118def _as_output(values: NDArray, like: ArrayLike) -> Any: 

119 ''' 

120 Private function. A scalar for a scalar input, else shaped like it. 

121 ''' 

122 if np.ndim(like) == 0: 

123 return values[0] 

124 return values.reshape(np.shape(like) + values.shape[1:]) 

125 

126 

127def optical_conductivity( 

128 ks: KSpace, omega: float | ArrayLike, e_fermi: float = 0., temperature: float = 0., 

129 eta: float = 0.05, nk: int | tuple[int, int] = 100, component: str = 'xx', 

130 positions: bool = True, 

131) -> complex | NDArray[np.complex128]: 

132 r''' 

133 Get the optical conductivity :math:`\sigma_{ab}(\omega)` of a 2D model 

134 from the Kubo-Greenwood formula over a uniform Brillouin-zone mesh, 

135 

136 .. math:: 

137 

138 \sigma_{ab}(\omega) = \frac{i e^2}{\hbar}\int\frac{d^2k}{(2\pi)^2} 

139 \sum_{n,m}\frac{f_n - f_m}{E_m - E_n}\, 

140 \frac{v^a_{nm}\,v^b_{mn}}{\hbar\omega + E_n - E_m + i\eta}\, , 

141 

142 with :math:`v^a_{nm} = \langle n|\partial H/\partial k_a|m\rangle`, 

143 :math:`f_n` the Fermi-Dirac occupations at *e_fermi* and *temperature*, 

144 and :math:`\eta` a phenomenological broadening (the inverse lifetime). 

145 Pairs of degenerate states, :math:`n = m` included, enter through the 

146 limit :math:`(f_n-f_m)/(E_m-E_n)\to-\partial f/\partial E`: the 

147 intraband (Drude) term :math:`\frac{i}{\hbar\omega+i\eta}\sum_n 

148 (-f'_n)v^a_{nn}v^b_{nn}`. It samples the Fermi surface only through the 

149 thermal window, so it needs a *temperature* larger than the level 

150 spacing of the mesh; at ``temperature=0`` only the interband part is 

151 computed. 

152 

153 It is the Ohm's-law tensor, :math:`j_a = \sigma_{ab}E_b`. In a gap its 

154 static Hall part is the TKNN conductance: 

155 :math:`\sigma_{yx}(0) = -\sigma_{xy}(0)` equals 

156 *KSpace.hall_conductivity* (which returns :math:`\sigma^{\mathrm{Ohm}}_{yx}`, 

157 see its docstring). The absorptive part obeys the f-sum rule 

158 :math:`\int_0^\infty\mathrm{Re}\,\sigma_{aa}\,d\omega = 

159 \frac{\pi e^2}{2\hbar^2}\int\frac{d^2k}{(2\pi)^2}\sum_n f_n\langle n|\partial_a^2H|n\rangle`. 

160 For graphene at neutrality, :math:`\mathrm{Re}\,\sigma_{xx} \to 

161 \frac{e^2}{16\hbar}` per spin and valley for :math:`\eta \ll \hbar\omega 

162 \ll t`: the universal :math:`\sigma_0 = e^2/4\hbar` once spin and 

163 valleys are counted, :math:`\pi/4` in units of :math:`e^2/h` for a 

164 spinless model. 

165 

166 :param ks: **KSpace** instance, 2D, Hermitian. 

167 :param omega: Real number or array of real numbers. Photon energies 

168 :math:`\hbar\omega`. 

169 :param e_fermi: Real number. Default value 0. Fermi energy. 

170 :param temperature: Positive real or zero. Default value 0 (:math:`k_B = 1`). 

171 :param eta: Positive real. Default value 0.05. Broadening, larger than 

172 the spacing of the transition energies on the mesh. 

173 :param nk: Positive integer, or tuple of 2. Default value 100. k-mesh. 

174 :param component: 'xx', 'xy', 'yx' or 'yy'. Default value 'xx'. 

175 :param positions: Boolean. Default value True. Orbital positions in the 

176 bond vectors (see *KSpace.hall_conductivity*). 

177 

178 :returns: 

179 * **sigma** -- Complex number (or ndarray shaped like *omega*), in 

180 units of :math:`e^2/h`. 

181 ''' 

182 kpts = _mesh(ks, nk, positions) 

183 error_handling.frequencies(omega, 'omega') 

184 error_handling.real_number(e_fermi, 'e_fermi') 

185 error_handling.positive_real_zero(temperature, 'temperature') 

186 error_handling.positive_real(eta, 'eta') 

187 a, b = error_handling.tensor_component(component, 2) 

188 w_arr = np.atleast_1d(np.asarray(omega, dtype='f8')).ravel() 

189 total = np.zeros(len(w_arr), 'c16') 

190 for en, vel in _bands(ks, kpts, positions): 

191 f = occupation.fermi_dirac(en, e_fermi, temperature) 

192 minus_df = f * (1. - f) / temperature if temperature > 0 else np.zeros_like(f) 

193 de = en[:, None, :] - en[:, :, None] # E_m - E_n 

194 degenerate = np.abs(de) < _DEGENERATE 

195 ratio = np.where(degenerate, minus_df[:, :, None], 

196 (f[:, :, None] - f[:, None, :]) / np.where(degenerate, 1., de)) 

197 weight = ratio * vel[a] * vel[b].transpose(0, 2, 1) 

198 keep = weight != 0 

199 wk, dk = weight[keep], np.where(degenerate, 0., de)[keep] 

200 step = max(1, 2000000 // len(w_arr)) 

201 for c0 in range(0, len(wk), step): 

202 total += (wk[None, c0:c0 + step] 

203 / (w_arr[:, None] - dk[None, c0:c0 + step] + 1j * eta)).sum(axis=1) 

204 sigma = 1j * 2 * np.pi * total / (len(kpts) * cell_area(ks)) 

205 return _as_output(sigma, omega) 

206 

207 

208def joint_dos( 

209 ks: KSpace, omega: float | ArrayLike, e_fermi: float = 0., temperature: float = 0., 

210 eta: float = 0.05, nk: int | tuple[int, int] = 100, 

211) -> float | NDArray[np.float64]: 

212 r''' 

213 Get the joint density of states of the vertical (interband) transitions, 

214 per unit cell, 

215 

216 .. math:: 

217 

218 J(\omega) = \frac{1}{N_k}\sum_{\mathbf{k}}\sum_{n\neq m} 

219 f_n\,(1 - f_m)\,\delta(\hbar\omega - E_m + E_n)\, , 

220 

221 with the delta function a Gaussian of standard deviation :math:`\eta`. 

222 Its integral over :math:`\omega` is the number of (occupied, empty) 

223 pairs per k-point. For graphene at neutrality, 

224 :math:`J = A_c\,\hbar\omega/(4\pi\hbar^2 v_F^2)` near the Dirac points 

225 (both valleys, per spin). 

226 

227 :param ks: **KSpace** instance, 2D, Hermitian. 

228 :param omega: Real number or array of real numbers. Photon energies. 

229 :param e_fermi: Real number. Default value 0. 

230 :param temperature: Positive real or zero. Default value 0. 

231 :param eta: Positive real. Default value 0.05. Gaussian width. 

232 :param nk: Positive integer, or tuple of 2. Default value 100. 

233 

234 :returns: 

235 * **jdos** -- Real number (or ndarray shaped like *omega*), in states 

236 per unit cell and per unit energy. 

237 ''' 

238 kpts = _mesh(ks, nk, True) 

239 error_handling.frequencies(omega, 'omega') 

240 error_handling.real_number(e_fermi, 'e_fermi') 

241 error_handling.positive_real_zero(temperature, 'temperature') 

242 error_handling.positive_real(eta, 'eta') 

243 w_arr = np.atleast_1d(np.asarray(omega, dtype='f8')).ravel() 

244 total = np.zeros(len(w_arr)) 

245 for en, _ in _bands(ks, kpts, False): 

246 f = occupation.fermi_dirac(en, e_fermi, temperature) 

247 weight = f[:, :, None] * (1. - f[:, None, :]) 

248 weight[:, np.arange(ks.norb), np.arange(ks.norb)] = 0. 

249 keep = weight > 0 

250 wk = weight[keep] 

251 dk = (en[:, None, :] - en[:, :, None])[keep] 

252 total += _gaussian_sum(w_arr, dk, wk, eta) 

253 return _as_output(total / len(kpts), omega) 

254 

255 

256def _gaussian_sum( 

257 x: NDArray[np.float64], centers: NDArray[np.float64], weights: NDArray[np.float64], eta: float, 

258) -> NDArray[np.float64]: 

259 r''' 

260 Private function. :math:`\sum_i w_i\,g(x - c_i)` with the normalized 

261 Gaussian :math:`g` of standard deviation *eta*, in chunks of the centers. 

262 ''' 

263 out = np.zeros(len(x)) 

264 step = max(1, 2000000 // len(x)) 

265 for c0 in range(0, len(centers), step): 

266 u = (x[:, None] - centers[None, c0:c0 + step]) / eta 

267 out += (weights[None, c0:c0 + step] * np.exp(-0.5 * u ** 2)).sum(axis=1) 

268 return out / (np.sqrt(2 * np.pi) * eta) 

269 

270 

271def berry_curvature_dipole( 

272 ks: KSpace, e_fermi: float | ArrayLike, temperature: float, 

273 nk: int | tuple[int, int] = 100, positions: bool = True, 

274) -> NDArray[np.float64]: 

275 r''' 

276 Get the Berry curvature dipole of a 2D model (Sodemann and Fu, Phys. 

277 Rev. Lett. 115, 216806 (2015)), 

278 

279 .. math:: 

280 

281 D_a = \int\frac{d^2k}{(2\pi)^2}\sum_n f_n\,\partial_a\Omega_n 

282 = \int\frac{d^2k}{(2\pi)^2}\sum_n 

283 \left(-\frac{\partial f}{\partial E}\right)_{E_n} 

284 v^a_{nn}\,\Omega_n\, , 

285 

286 the first moment of the Berry curvature :math:`\Omega_n` (the 

287 convention of *KSpace.berry_curvature* and *hall_conductivity*, 

288 :math:`\Omega_n = -2\,\mathrm{Im}\sum_{m\neq n}v^x_{nm}v^y_{mn}/(E_n-E_m)^2`) 

289 over the occupied states. It is a Fermi-surface property: the second 

290 form, used here, samples it through the thermal window 

291 :math:`-\partial f/\partial E`, so *temperature* must be positive and 

292 larger than the level spacing of the mesh near :math:`E_F`. A finite 

293 dipole needs broken inversion symmetry and a single mirror line at 

294 most. In a DC field it drives the nonlinear Hall current 

295 :math:`j_a = \chi_{abc}E_bE_c`, 

296 :math:`\chi_{abc} = -\varepsilon_{adc}\frac{e^3\tau}{2\hbar^2(1+i\omega\tau)}D_{bd}`, 

297 with :math:`D_{bd} = D_b` along :math:`d = z` in 2D. 

298 

299 For the tilted massive Dirac cone 

300 :math:`H = t k_x + v(k_x\sigma_x + k_y\sigma_y) + m\sigma_z` doped into 

301 the conduction band (:math:`\mu > |m|`), the first order in the tilt is 

302 :math:`D_x = -\frac{3\,t\,m\,(\mu^2 - m^2)}{8\pi\mu^4}`, 

303 :math:`D_y = 0`, from :math:`\Omega_c = -mv^2/2\varepsilon^3` 

304 (Sodemann and Fu's model, in these conventions). 

305 

306 :param ks: **KSpace** instance, 2D, Hermitian. 

307 :param e_fermi: Real number, or array of real numbers. Fermi energies. 

308 :param temperature: Positive real. Temperature (:math:`k_B = 1`). 

309 :param nk: Positive integer, or tuple of 2. Default value 100. 

310 :param positions: Boolean. Default value True. See *KSpace.hall_conductivity*. 

311 

312 :returns: 

313 * **dipole** -- Real ndarray of shape (2,): :math:`(D_x, D_y)` (or 

314 ``e_fermi.shape + (2,)``), in units of length (of *prim_vec*). 

315 ''' 

316 kpts = _mesh(ks, nk, positions) 

317 error_handling.fermi_energies(e_fermi) 

318 error_handling.positive_real(temperature, 'temperature') 

319 mus = np.atleast_1d(np.asarray(e_fermi, dtype='f8')).ravel() 

320 total = np.zeros((len(mus), 2)) 

321 for en, vel in _bands(ks, kpts, positions): 

322 de = en[:, :, None] - en[:, None, :] 

323 degenerate = np.abs(de) < _DEGENERATE 

324 inv2 = np.where(degenerate, 0., 1. / np.where(degenerate, 1., de) ** 2) 

325 omega_n = -2 * np.sum(np.imag(vel[0] * vel[1].transpose(0, 2, 1)) * inv2, axis=2) 

326 vdiag = np.real(np.diagonal(vel, axis1=2, axis2=3)) # (2, nk, n) 

327 for e, mu in enumerate(mus): 

328 f = occupation.fermi_dirac(en, float(mu), temperature) 

329 window = f * (1. - f) / temperature 

330 total[e] += np.einsum('kn,akn->a', window * omega_n, vdiag) 

331 return _as_output(total / (len(kpts) * cell_area(ks)), e_fermi) 

332 

333 

334def generalized_derivative( 

335 en: NDArray[np.float64], vel: NDArray[np.complex128], w: NDArray[np.complex128], 

336) -> tuple[NDArray[np.complex128], NDArray[np.complex128]]: 

337 r''' 

338 Get the interband position matrix elements and their generalized 

339 derivatives from the band energies and the first and second derivatives 

340 of :math:`H` in the eigenbasis (a sum over states, Sipe and Shkrebtii 

341 2000; Cook et al. 2017; :math:`\hbar = 1`, :math:`\omega_{nm} = E_n - E_m`, 

342 :math:`\Delta^a_{nm} = v^a_{nn} - v^a_{mm}`): 

343 

344 .. math:: 

345 

346 r^b_{nm} = \frac{v^b_{nm}}{i\omega_{nm}}\, ,\qquad 

347 r^b_{nm;a} = \frac{i}{\omega_{nm}}\left[ 

348 \frac{v^b_{nm}\Delta^a_{nm} + v^a_{nm}\Delta^b_{nm}}{\omega_{nm}} 

349 - w^{ba}_{nm} + \sum_{p\neq n,m}\left( 

350 \frac{v^b_{np}v^a_{pm}}{\omega_{pm}} - \frac{v^a_{np}v^b_{pm}}{\omega_{np}} 

351 \right)\right]\, , 

352 

353 :math:`r^b_{nm;a} = \partial_ar^b_{nm} - i(A^a_{nn} - A^a_{mm})r^b_{nm}`, 

354 gauge covariant. Degenerate pairs (:math:`|\omega_{nm}| < 10^{-9}`) are 

355 set to zero, and dropped from the sum over :math:`p`. 

356 

357 :param en: Real ndarray, shape (nk, n). Band energies. 

358 :param vel: Complex ndarray, shape (2, nk, n, n). :math:`v^a_{nm}`. 

359 :param w: Complex ndarray, shape (2, 2, nk, n, n). :math:`w^{ab}_{nm} = 

360 \langle n|\partial_a\partial_bH|m\rangle`. 

361 

362 :returns: 

363 * **r** -- Complex ndarray, shape (2, nk, n, n): r[b] is :math:`r^b_{nm}`. 

364 * **r_der** -- Complex ndarray, shape (2, 2, nk, n, n): r_der[b, a] 

365 is :math:`r^b_{nm;a}`. 

366 ''' 

367 en, vel, w = np.asarray(en), np.asarray(vel), np.asarray(w) 

368 error_handling.band_derivatives(en, vel, w) 

369 om = en[:, :, None] - en[:, None, :] 

370 degenerate = np.abs(om) < _DEGENERATE 

371 inv = np.where(degenerate, 0., 1. / np.where(degenerate, 1., om)) 

372 diag = np.diagonal(vel, axis1=2, axis2=3) # (2, nk, n) 

373 delta = diag[:, :, :, None] - diag[:, :, None, :] 

374 r = -1j * vel * inv[None] 

375 r_der = np.zeros((2, 2) + om.shape, 'c16') 

376 for b in range(2): 

377 for a in range(2): 

378 # the full sums over p include p = n, m: those terms add 

379 # Delta^b v^a / omega, removed again below 

380 full = vel[b] @ (vel[a] * inv) - (vel[a] * inv) @ vel[b] 

381 bracket = ((vel[b] * delta[a] + vel[a] * delta[b]) * inv - w[b, a] + full 

382 - vel[a] * delta[b] * inv) 

383 r_der[b, a] = 1j * inv * bracket 

384 return r, r_der 

385 

386 

387def shift_current( 

388 ks: KSpace, omega: float | ArrayLike, e_fermi: float = 0., temperature: float = 0., 

389 eta: float = 0.05, nk: int | tuple[int, int] = 100, component: str = 'xxx', 

390 positions: bool = True, 

391) -> float | NDArray[np.float64]: 

392 r''' 

393 Get the shift-current conductivity of a 2D model under linearly 

394 polarized light (Sipe and Shkrebtii, Phys. Rev. B 61, 5337 (2000)), 

395 :math:`j^a = 2\sigma^{abc}(0;\omega,-\omega)E^b(\omega)E^c(-\omega)`, 

396 

397 .. math:: 

398 

399 \sigma^{abc}(\omega) = -\frac{\pi e^3}{2\hbar^2}\int\frac{d^2k}{(2\pi)^2} 

400 \sum_{n,m}f_{nm}\,\mathrm{Im}\left[r^b_{mn}r^c_{nm;a} 

401 + r^c_{mn}r^b_{nm;a}\right]\delta(\omega_{mn} - \omega)\, , 

402 

403 :math:`f_{nm} = f_n - f_m`, with the position matrix elements 

404 :math:`r` and their generalized derivatives from 

405 :func:`generalized_derivative` (a sum over states, with the exact second 

406 derivatives of :math:`H`). The delta function is a Gaussian of standard 

407 deviation :math:`\eta`. For :math:`b = c`, 

408 :math:`\mathrm{Im}[r^b_{mn}r^b_{nm;a}] = |r^b_{nm}|^2R^{a,b}_{nm}` with 

409 :math:`R^{a,b}_{nm}` the shift vector, the real-space displacement of 

410 the electron in the transition. It vanishes with inversion symmetry. 

411 Unlike the Hall conductance of full bands, it depends on where the 

412 orbitals sit in the cell (Ibanez-Azpiroz, Tsirkin and Souza, Phys. Rev. 

413 B 97, 245143 (2018)): ``positions=False``, which puts them all at the 

414 cell origin, describes a different crystal. 

415 

416 :param ks: **KSpace** instance, 2D, Hermitian, without overlap. 

417 :param omega: Real number or array of real numbers. Photon energies :math:`\hbar\omega`. 

418 :param e_fermi: Real number. Default value 0. 

419 :param temperature: Positive real or zero. Default value 0. 

420 :param eta: Positive real. Default value 0.05. Gaussian width. 

421 :param nk: Positive integer, or tuple of 2. Default value 100. 

422 :param component: Three letters among 'x', 'y' (current direction, then 

423 the two field directions). Default value 'xxx'. 

424 :param positions: Boolean. Default value True. See *KSpace.hall_conductivity*. 

425 

426 :returns: 

427 * **sigma** -- Real number (or ndarray shaped like *omega*), in units 

428 of :math:`e^3/\hbar\times` length / energy (:math:`\hbar = e = 1`), 

429 counting each orbital once. 

430 ''' 

431 kpts = _mesh(ks, nk, positions) 

432 error_handling.no_overlap(bool(ks._overlap_hop)) 

433 error_handling.frequencies(omega, 'omega') 

434 error_handling.real_number(e_fermi, 'e_fermi') 

435 error_handling.positive_real_zero(temperature, 'temperature') 

436 error_handling.positive_real(eta, 'eta') 

437 a, b, c = error_handling.tensor_component(component, 3) 

438 w_arr = np.atleast_1d(np.asarray(omega, dtype='f8')).ravel() 

439 total = np.zeros(len(w_arr)) 

440 for en, vel, w in _bands(ks, kpts, positions, second=True): 

441 r, r_der = generalized_derivative(en, vel, w) 

442 f = occupation.fermi_dirac(en, e_fermi, temperature) 

443 fnm = f[:, :, None] - f[:, None, :] 

444 integrand = np.imag(r[b].transpose(0, 2, 1) * r_der[c, a] 

445 + r[c].transpose(0, 2, 1) * r_der[b, a]) * fnm 

446 keep = integrand != 0 

447 wmn = (en[:, None, :] - en[:, :, None])[keep] 

448 total += _gaussian_sum(w_arr, wmn, integrand[keep], eta) 

449 sigma = -np.pi / 2 * total / (len(kpts) * cell_area(ks)) 

450 return _as_output(sigma, omega)