Coverage for tbkit/orbital.py: 100%

243 statements  

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

1r""" 

2Real-space Tight-Binding models with several orbitals per site and an 

3optional spin: :class:`OrbitalSystem` extends :class:`tbkit.system.System` 

4with Slater-Koster hoppings between s, p and d orbitals (see 

5:mod:`tbkit.slater_koster`), non-orthogonal bases (an overlap matrix), 

6and spin-dependent terms -- Kane-Mele intrinsic spin-orbit coupling, 

7Rashba coupling, Zeeman fields and atomic :math:`\lambda\mathbf{L}\cdot\mathbf{S}`. 

8 

9Rows of the Hamiltonian are ordered site by site, then orbital by orbital 

10(in the order given for the site's tag), then spin (up, down): the row of 

11orbital *o* with spin *s* on site *i* is *sys.row(i, o, s)*. 

12""" 

13from __future__ import annotations 

14 

15from typing import Callable 

16 

17import numpy as np 

18from numpy.typing import NDArray 

19import scipy.linalg as LA 

20import scipy.sparse as sparse 

21 

22import tbkit.error_handling as error_handling 

23from tbkit.lattice import Lattice 

24from tbkit.slater_koster import sk_block, orbital_angular_momentum 

25from tbkit.system import System 

26 

27SIGMA = [np.array([[0., 1.], [1., 0.]], dtype='c16'), 

28 np.array([[0., -1j], [1j, 0.]], dtype='c16'), 

29 np.array([[1., 0.], [0., -1.]], dtype='c16')] 

30 

31 

32class OrbitalSystem(System): 

33 r''' 

34 Solve a real-space Tight-Binding model with several orbitals per site 

35 and, optionally, spin. 

36 

37 :param lat: **Lattice** class instance. 

38 :param orbitals: Dictionary. Default value None (one 's' orbital per 

39 site). key: sublattice tag, val: list of orbital names (see 

40 :data:`tbkit.slater_koster.ORBITALS`), e.g. ``{'a': ['s', 'px', 'py', 'pz']}``. 

41 :param spin: Boolean. Default value False. Give every orbital a 

42 spin-1/2 degree of freedom. 

43 :param periodic: Boolean, or tuple of booleans. Default value False. 

44 Periodic boundaries (see *System*); the bond vectors of the 

45 Slater-Koster, spin-orbit and Rashba blocks are then those of the 

46 shortest image. 

47 

48 Everything of **System** still applies: *set_hopping* (and the disorder, 

49 defect and Peierls methods) sets hoppings :math:`t` that connect equal 

50 orbitals and equal spins of the two sites (:math:`t\,\mathbb{1}`), and 

51 *set_onsite* with plain numbers shifts every orbital of a site. Onsite 

52 energies can also be given orbital by orbital, and full hopping blocks 

53 come from *set_slater_koster*, *set_spin_orbit*, *set_rashba* or 

54 *set_hopping_block*. Call *set_peierls_phase* / *set_magnetic_field* 

55 last: they multiply every hopping (and block) set so far. 

56 

57 Example usage:: 

58 

59 # graphene's sp3 bands: four orbitals per carbon 

60 sys = OrbitalSystem(lat, orbitals={'a': ['s', 'px', 'py', 'pz'], 

61 'b': ['s', 'px', 'py', 'pz']}) 

62 sys.set_onsite({'a': {'s': -8.87}, 'b': {'s': -8.87}}) 

63 sys.set_slater_koster(1, {'ss_sigma': -6.77, 'sp_sigma': 5.58, 

64 'pp_sigma': 5.04, 'pp_pi': -3.03}) 

65 sys.get_ham() 

66 ''' 

67 

68 def __init__(self, lat: Lattice, orbitals: dict[str, list[str]] | None = None, 

69 spin: bool = False, periodic: bool | tuple[bool, ...] = False) -> None: 

70 System.__init__(self, lat, periodic) 

71 error_handling.boolean(spin, 'spin') 

72 if orbitals is None: 

73 orbitals = {tag: ['s'] for tag in lat.tags} 

74 error_handling.orbital_dict(orbitals, lat.tags) 

75 self.orbitals = {tag: list(orbs) for tag, orbs in orbitals.items()} 

76 self.spin = spin 

77 self.ns = 2 if spin else 1 

78 self.onsite_orb: dict = {} # tag -> {orbital: energy} 

79 self.blocks: dict = {} # name -> list of (i, j, matrix): bond blocks, upper part 

80 self.overlap_blocks: dict = {} # name -> list of (i, j, matrix) 

81 self.onsite_blocks: dict = {} # name -> {site: matrix} 

82 self.overlap = None 

83 self._index() 

84 

85 # ------------------------------------------------------------------ 

86 # indexing 

87 # ------------------------------------------------------------------ 

88 

89 def _index(self) -> None: 

90 ''' 

91 Private method. Rows of every site, orbital and spin. 

92 ''' 

93 tags = self.lat.coor['tag'] 

94 self.norb_site = np.array([len(self.orbitals[t]) * self.ns for t in tags], dtype=int) 

95 self.offset = np.concatenate([[0], np.cumsum(self.norb_site)[:-1]]).astype(int) 

96 self.n_rows = int(self.norb_site.sum()) 

97 self.orb_site = np.repeat(np.arange(self.lat.sites), self.norb_site) 

98 

99 def row(self, site: int, orbital: str, spin: int = 0) -> int: 

100 ''' 

101 Get the row of an orbital. 

102 

103 :param site: Integer. Site index. 

104 :param orbital: String. Orbital name (one of the site's). 

105 :param spin: 0 (up) or 1 (down). Default value 0. 

106 

107 :returns: 

108 * **row** -- Integer. 

109 ''' 

110 self._index() 

111 error_handling.site_index(site, self.lat.sites) 

112 orbs = self.orbitals[self.lat.coor['tag'][site]] 

113 error_handling.orbital_of_site(orbital, orbs) 

114 error_handling.spin_index(spin, self.ns) 

115 return int(self.offset[site] + orbs.index(orbital) * self.ns + spin) 

116 

117 def _site_orbs(self, site: int) -> list[str]: 

118 return self.orbitals[self.lat.coor['tag'][site]] 

119 

120 def _kron_spin(self, mat: NDArray) -> NDArray: 

121 ''' 

122 Private method. Orbital matrix times the spin identity. 

123 ''' 

124 if not self.spin: 

125 return np.asarray(mat, dtype='c16') 

126 return np.kron(mat, np.eye(2)) 

127 

128 def _same_orbitals(self, i: int, j: int) -> NDArray: 

129 ''' 

130 Private method. Matrix connecting equal orbitals of sites i and j. 

131 ''' 

132 oi, oj = self._site_orbs(i), self._site_orbs(j) 

133 return np.array([[1. if a == b else 0. for b in oj] for a in oi]) 

134 

135 # ------------------------------------------------------------------ 

136 # onsite terms 

137 # ------------------------------------------------------------------ 

138 

139 def set_onsite(self, dict_onsite: dict) -> None: 

140 ''' 

141 Set onsite energies. 

142 

143 :param dict_onsite: Dictionary. key: sublattice tag, val: onsite 

144 energy (a number, applied to every orbital of the sites), or a 

145 dictionary {orbital: energy} (orbitals not given get 0). 

146 

147 Example usage:: 

148 

149 sys.set_onsite({'a': 0.5, 'b': {'s': -8.87, 'pz': 0.}}) 

150 ''' 

151 error_handling.sites(self.lat.sites) 

152 error_handling.set_onsite_orb(dict_onsite, self.lat.tags, self.orbitals) 

153 numbers = {tag: val for tag, val in dict_onsite.items() if not isinstance(val, dict)} 

154 System.set_onsite(self, numbers) 

155 self.onsite_orb = {tag: dict(val) for tag, val in dict_onsite.items() if isinstance(val, dict)} 

156 

157 def set_zeeman(self, b: tuple[float, float, float]) -> None: 

158 r''' 

159 Set a Zeeman field :math:`\mathbf{b}\cdot\boldsymbol\sigma` on every 

160 orbital (``spin=True`` only). 

161 

162 :param b: Tuple of three real numbers :math:`(b_x, b_y, b_z)`. 

163 ''' 

164 error_handling.spinful(self.spin) 

165 error_handling.vector3(b, 'b') 

166 field = sum(c * s for c, s in zip(b, SIGMA)) 

167 self.onsite_blocks['zeeman'] = { 

168 i: np.kron(np.eye(len(self._site_orbs(i))), field) for i in range(self.lat.sites)} 

169 

170 def set_atomic_soc(self, lam: float, tags: list[str] | None = None) -> None: 

171 r''' 

172 Set the atomic spin-orbit coupling 

173 :math:`\lambda\,\mathbf{L}\cdot\mathbf{S} = \frac{\lambda}{2}\sum_k L_k\sigma_k` 

174 on the p orbitals (``spin=True`` only; the sites must carry all three 

175 of px, py, pz). It splits them into :math:`j = 3/2` (at 

176 :math:`+\lambda/2`) and :math:`j = 1/2` (at :math:`-\lambda`) levels. 

177 

178 :param lam: Real number. :math:`\lambda`. 

179 :param tags: List of tags. Default value None (every tag with p orbitals). 

180 ''' 

181 error_handling.spinful(self.spin) 

182 error_handling.real_number(lam, 'lam') 

183 if tags is None: 

184 tags = [t for t, orbs in self.orbitals.items() if 'px' in orbs] 

185 error_handling.p_shell(tags, self.orbitals) 

186 L = orbital_angular_momentum() 

187 blocks = {} 

188 for i in range(self.lat.sites): 

189 orbs = self._site_orbs(i) 

190 if self.lat.coor['tag'][i] not in tags: 

191 continue 

192 mat = np.zeros((2 * len(orbs), 2 * len(orbs)), 'c16') 

193 idx = [orbs.index(o) for o in ('px', 'py', 'pz')] 

194 for k in range(3): 

195 emb = np.zeros((len(orbs), len(orbs)), 'c16') 

196 emb[np.ix_(idx, idx)] = L[k] 

197 mat += 0.5 * lam * np.kron(emb, SIGMA[k]) 

198 blocks[i] = mat 

199 self.onsite_blocks['atomic_soc'] = blocks 

200 

201 # ------------------------------------------------------------------ 

202 # bond blocks 

203 # ------------------------------------------------------------------ 

204 

205 def _bonds(self, n: int) -> tuple[NDArray, NDArray, NDArray]: 

206 ''' 

207 Private method. Bonds of order n (oriented, see *get_bonds*) and their vectors. 

208 ''' 

209 self.get_distances(n) 

210 self.nmax = len(self.dist_uni) - 1 

211 error_handling.positive_int_lim(n, 'n', self.nmax) 

212 i, j, _ = self.get_bonds(n) 

213 return i, j, self._bond_vectors(i, j) 

214 

215 def set_slater_koster(self, n: int, params: dict, overlap: dict | None = None) -> None: 

216 r''' 

217 Set the hoppings between the orbitals of every pair of *n*-th 

218 neighbours from Slater-Koster bond integrals (see 

219 :func:`tbkit.slater_koster.sk_block`), replacing those set before for 

220 the same *n*. The blocks are spin independent. 

221 

222 :param n: Positive integer. Neighbour order (1 for nearest neighbours). 

223 :param params: Dictionary of bond integrals (e.g. ``{'pp_sigma': 5.04, 

224 'pp_pi': -3.03}``), or dictionary {tag pair: bond integrals} (e.g. 

225 ``{'ab': {...}, 'ba': {...}}``, for bonds from an 'a' to a 'b' site). 

226 :param overlap: Dictionary. Default value None. Bond integrals of the 

227 overlap matrix :math:`S_{ij} = \langle i|j\rangle`, in the same 

228 format: the basis is then non-orthogonal, and *get_eig* solves 

229 :math:`H\psi = ES\psi`. 

230 ''' 

231 error_handling.positive_int(n, 'n') 

232 i, j, d = self._bonds(n) 

233 tags = self.lat.coor['tag'] 

234 for store, par in ((self.blocks, params), (self.overlap_blocks, overlap)): 

235 if par is None: 

236 store.pop(('sk', n), None) 

237 continue 

238 by_pair = error_handling.sk_pair_params(par, tags[i], tags[j]) 

239 blocks = [] 

240 for a, b, vec in zip(i, j, d): 

241 p = par[tags[a] + tags[b]] if by_pair else par 

242 blocks.append((a, b, self._kron_spin(sk_block(self._site_orbs(a), self._site_orbs(b), vec, p)))) 

243 store[('sk', n)] = blocks 

244 

245 def set_spin_orbit(self, lam: float, n: int = 2) -> None: 

246 r''' 

247 Set the Kane-Mele intrinsic spin-orbit coupling on the *n*-th 

248 neighbours (``spin=True`` only), 

249 

250 .. math:: 

251 

252 H_{SO} = i\lambda\sum_{\langle\langle ij\rangle\rangle}\nu_{ij}\, 

253 c_i^\dagger\sigma_z c_j\, ,\qquad 

254 \nu_{ij} = \sum_k \mathrm{sign}\left[(\mathbf{d}_{ik}\times\mathbf{d}_{kj})_z\right]\, , 

255 

256 the sum running over the nearest neighbours :math:`k` shared by 

257 :math:`i` and :math:`j` (one on the honeycomb lattice, where 

258 :math:`\nu_{ij} = \pm1` says whether the path turns left or right). 

259 It connects equal orbitals of the two sites. 

260 

261 :param lam: Real number. :math:`\lambda`. 

262 :param n: Positive integer. Default value 2. Neighbour order. 

263 ''' 

264 error_handling.spinful(self.spin) 

265 error_handling.real_number(lam, 'lam') 

266 error_handling.positive_int(n, 'n') 

267 ni, nj, _ = self._bonds(1) 

268 neighbours: list[set[int]] = [set() for _ in range(self.lat.sites)] 

269 for a, b in zip(ni, nj): 

270 neighbours[a].add(b) 

271 neighbours[b].add(a) 

272 i, j, _ = self._bonds(n) 

273 coords = self._coords() 

274 blocks = [] 

275 for a, b in zip(i, j): 

276 nu = 0. 

277 for k in neighbours[a] & neighbours[b]: 

278 d1, d2 = self._min_image(coords[k] - coords[a]), self._min_image(coords[b] - coords[k]) 

279 nu += np.sign(d1[0] * d2[1] - d1[1] * d2[0]) 

280 if nu: 

281 blocks.append((a, b, 1j * lam * nu * np.kron(self._same_orbitals(a, b), SIGMA[2]))) 

282 self.blocks[('soc', n)] = blocks 

283 

284 def set_rashba(self, lam: float, n: int = 1) -> None: 

285 r''' 

286 Set a Rashba spin-orbit coupling on the *n*-th neighbours 

287 (``spin=True`` only), 

288 :math:`H_R = i\lambda\sum_{\langle ij\rangle} c_i^\dagger 

289 (\boldsymbol\sigma\times\hat{\mathbf{d}}_{ij})_z\, c_j`, 

290 :math:`\hat{\mathbf{d}}_{ij}` the bond direction. It connects equal 

291 orbitals of the two sites. 

292 

293 :param lam: Real number. :math:`\lambda`. 

294 :param n: Positive integer. Default value 1. 

295 ''' 

296 error_handling.spinful(self.spin) 

297 error_handling.real_number(lam, 'lam') 

298 error_handling.positive_int(n, 'n') 

299 i, j, d = self._bonds(n) 

300 blocks = [] 

301 for a, b, vec in zip(i, j, d): 

302 u = vec / np.linalg.norm(vec) 

303 spin = 1j * lam * (SIGMA[0] * u[1] - SIGMA[1] * u[0]) 

304 blocks.append((a, b, np.kron(self._same_orbitals(a, b), spin))) 

305 self.blocks[('rashba', n)] = blocks 

306 

307 def set_hopping_block(self, dict_block: dict[tuple[int, int], NDArray]) -> None: 

308 r''' 

309 Set hopping blocks by hand, added to the others: key: site pair 

310 :math:`(i, j)`, val: matrix of shape (rows of i, rows of j) -- 

311 orbitals, then spin, as *row* orders them. Their Hermitian conjugates 

312 are added. 

313 

314 :param dict_block: Dictionary. 

315 ''' 

316 error_handling.set_hopping_def(None, {k: 0. for k in dict_block}, self.lat.sites, 'dict_block') 

317 self._index() 

318 blocks = self.blocks.setdefault(('manual', 0), []) 

319 for (a, b), mat in dict_block.items(): 

320 mat = np.asarray(mat, dtype='c16') 

321 error_handling.block_shape(mat, (self.norb_site[a], self.norb_site[b])) 

322 blocks.append((a, b, mat)) 

323 

324 def clear_hopping(self) -> None: 

325 ''' 

326 Clear the hoppings: *hop*, and every hopping block. 

327 ''' 

328 System.clear_hopping(self) 

329 self.blocks = {} 

330 self.overlap_blocks = {} 

331 

332 def set_peierls_phase(self, phase: Callable[..., NDArray]) -> None: 

333 r''' 

334 Apply the Peierls substitution (see *System.set_peierls_phase*) to 

335 the hoppings *hop* and to every hopping block set so far. 

336 

337 :param phase: Callable ``phase(xi, yi, xj, yj)``. 

338 ''' 

339 error_handling.is_callable(phase, 'phase') 

340 if self.hop.size: 

341 System.set_peierls_phase(self, phase) 

342 x, y = self.lat.coor['x'], self.lat.coor['y'] 

343 for key, blocks in self.blocks.items(): 

344 # the end of the short bond (outside the sample if it wraps around) 

345 d = self._bond_vectors(np.array([a for a, _, _ in blocks], int), 

346 np.array([b for _, b, _ in blocks], int)) 

347 self.blocks[key] = [(a, b, mat * np.exp(1j * phase(x[a], y[a], x[a] + dx, y[a] + dy))) 

348 for (a, b, mat), (dx, dy) in zip(blocks, d[:, :2])] 

349 

350 # ------------------------------------------------------------------ 

351 # Hamiltonian and eigenstates 

352 # ------------------------------------------------------------------ 

353 

354 def _assemble(self, blocks: list, n: int) -> sparse.csr_matrix: 

355 ''' 

356 Private method. Sparse matrix of bond blocks, upper part only. 

357 ''' 

358 rows, cols, vals = [], [], [] 

359 for a, b, mat in blocks: 

360 r = self.offset[a] + np.arange(mat.shape[0]) 

361 c = self.offset[b] + np.arange(mat.shape[1]) 

362 rr, cc = np.meshgrid(r, c, indexing='ij') 

363 rows.append(rr.ravel()) 

364 cols.append(cc.ravel()) 

365 vals.append(mat.ravel()) 

366 if not rows: 

367 return sparse.csr_matrix((n, n), dtype='c16') 

368 return sparse.csr_matrix((np.concatenate(vals), (np.concatenate(rows), np.concatenate(cols))), 

369 shape=(n, n)) 

370 

371 def get_ham(self, **params) -> None: 

372 ''' 

373 Get the Hamiltonian (and, for a non-orthogonal basis, the overlap 

374 matrix *overlap*). 

375 

376 :param params: Values of the parameters of the value functions of 

377 *hop* (see *System.get_ham*). 

378 ''' 

379 error_handling.orbital_terms(self.hop, self.blocks) 

380 self._index() 

381 n = self.n_rows 

382 ham = sparse.csr_matrix((n, n), dtype='c16') 

383 if self.hop.size: 

384 error_handling.hop_sites(self.hop, self.lat.sites) 

385 rows, cols, vals = [], [], [] 

386 names = sorted({o for orbs in self.orbitals.values() for o in orbs}) 

387 index = {o: np.full(self.lat.sites, -1) for o in names} 

388 for site in range(self.lat.sites): 

389 for k, o in enumerate(self._site_orbs(site)): 

390 index[o][site] = self.offset[site] + k * self.ns 

391 hi, hj = self.hop['i'].astype(int), self.hop['j'].astype(int) 

392 t = self._hop_t(params) 

393 for o in names: 

394 keep = (index[o][hi] >= 0) & (index[o][hj] >= 0) 

395 for s in range(self.ns): 

396 rows.append(index[o][hi[keep]] + s) 

397 cols.append(index[o][hj[keep]] + s) 

398 vals.append(t[keep]) 

399 scalar = sparse.csr_matrix((np.concatenate(vals), (np.concatenate(rows), np.concatenate(cols))), 

400 shape=(n, n)) 

401 if np.all(self.hop['ang'] >= 0) or np.all(self.hop['ang'] < 0): 

402 scalar = scalar + scalar.conj().T 

403 ham = ham + scalar 

404 bonds = self._assemble([blk for blocks in self.blocks.values() for blk in blocks], n) 

405 ham = ham + bonds + bonds.conj().T 

406 diag = np.zeros(n, 'c16') 

407 if self.onsite.size == self.lat.sites: 

408 diag += self.onsite[self.orb_site] 

409 for tag, energies in self.onsite_orb.items(): 

410 for site in np.flatnonzero(self.lat.coor['tag'] == tag).tolist(): 

411 for o, e in energies.items(): 

412 k = self._site_orbs(site).index(o) 

413 diag[self.offset[site] + k * self.ns: self.offset[site] + (k + 1) * self.ns] += e 

414 ham = ham + sparse.diags(diag) 

415 for blocks in self.onsite_blocks.values(): 

416 ham = ham + self._assemble([(i, i, mat) for i, mat in blocks.items()], n) 

417 self.ham = sparse.csr_matrix(ham) 

418 self.overlap = None 

419 overlap = [blk for blocks in self.overlap_blocks.values() for blk in blocks] 

420 if overlap: 

421 o = self._assemble(overlap, n) 

422 self.overlap = sparse.csr_matrix(sparse.identity(n, dtype='c16') + o + o.conj().T) 

423 

424 def _set_states(self) -> None: 

425 ''' 

426 Private method. Intensities summed over each site's orbitals and spin 

427 (Mulliken populations for a non-orthogonal basis), and sublattice 

428 polarizations. 

429 ''' 

430 if self.overlap is None: 

431 weight = np.abs(self.rn) ** 2 

432 else: 

433 weight = np.real(self.rn.conj() * (self.overlap @ self.rn)) 

434 self.intensity = np.zeros((self.lat.sites, self.rn.shape[1])) 

435 np.add.at(self.intensity, self.orb_site, weight) 

436 self.pola = np.zeros((self.rn.shape[1], len(self.lat.tags))) 

437 for i, tag in enumerate(self.lat.tags): 

438 self.pola[:, i] = np.sum(self.intensity[self.lat.coor['tag'] == tag, :], axis=0) 

439 

440 def get_eig(self, eigenvec: bool = False, left: bool = False) -> None: 

441 r''' 

442 Get the eigenenergies (and eigenvectors), as *System.get_eig*. With a 

443 non-orthogonal basis (see *set_slater_koster*), solve the generalized 

444 problem :math:`H\psi = ES\psi` (eigenvectors normalized as 

445 :math:`\psi^\dagger S\psi = 1` for a Hermitian :math:`H`). 

446 

447 :param eigenvec: Boolean. Default value False. 

448 :param left: Boolean. Default value False (orthogonal basis only). 

449 ''' 

450 if self.overlap is None: 

451 return System.get_eig(self, eigenvec, left) 

452 error_handling.empty_ham(self.ham) 

453 error_handling.boolean(eigenvec, 'eigenvec') 

454 error_handling.no_left(left) 

455 self.ln = np.array([], 'c16') 

456 ham, s = self.ham.toarray(), self.overlap.toarray() 

457 if (self.ham.conj().T != self.ham).nnz: 

458 en, vec = LA.eig(ham, s) 

459 ind = np.argsort(en.real, kind='stable') 

460 self.en, self.rn = en[ind], vec[:, ind] 

461 else: 

462 self.en, self.rn = LA.eigh(ham, s) 

463 if eigenvec: 

464 self._set_states() 

465 

466 def _row_sites(self) -> NDArray: 

467 ''' 

468 Private method. Site of every row of the Hamiltonian. 

469 ''' 

470 self._index() 

471 return self.orb_site 

472 

473 def get_eig_sparse(self, n_eig: int = 10, sigma: complex = 0., eigenvec: bool = False) -> None: 

474 ''' 

475 Get the eigenpairs closest to *sigma* (see *System.get_eig_sparse*; 

476 orthogonal basis only). 

477 ''' 

478 error_handling.orthogonal(self.overlap) 

479 System.get_eig_sparse(self, n_eig, sigma, eigenvec) 

480 

481 def get_local_chern_marker(self, e_fermi: float = 0., area: float | None = None) -> NDArray[np.float64]: 

482 ''' 

483 Get the local Chern marker (see *System.get_local_chern_marker*), 

484 summed over the orbitals and spin of each site (orthogonal basis only). 

485 

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

487 :param area: Positive real number. Default value None. 

488 

489 :returns: 

490 * **marker** -- Real ndarray, shape (sites,). 

491 ''' 

492 error_handling.orthogonal(self.overlap) 

493 return System.get_local_chern_marker(self, e_fermi, area) 

494 

495 def get_bott_index(self, e_fermi: float = 0.) -> float: 

496 ''' 

497 Get the Bott index of a torus (see *System.get_bott_index*), the 

498 orbitals and spin of a site sharing its position (orthogonal basis only). 

499 

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

501 

502 :returns: 

503 * **bott** -- Real number, close to an integer. 

504 ''' 

505 error_handling.orthogonal(self.overlap) 

506 return System.get_bott_index(self, e_fermi)