Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Copyright 2011-2020 Kwant authors. 

2# 

3# This file is part of Kwant. It is subject to the license terms in the file 

4# LICENSE.rst found in the top-level directory of this distribution and at 

5# https://kwant-project.org/license. A list of Kwant authors can be found in 

6# the file AUTHORS.rst at the top-level directory of this distribution and at 

7# https://kwant-project.org/authors. 

8 

9"""Low-level interface of systems""" 

10 

11__all__ = [ 

12 'Site', 'SiteArray', 'SiteFamily', 'Symmetry', 'NoSymmetry', 

13 'System', 'VectorizedSystem', 'FiniteSystem', 'FiniteVectorizedSystem', 

14 'InfiniteSystem', 'InfiniteVectorizedSystem', 

15 'is_finite', 'is_infinite', 'is_vectorized', 

16] 

17 

18import abc 

19import warnings 

20import operator 

21from copy import copy 

22import collections 

23from functools import total_ordering, lru_cache 

24import numpy as np 

25import tinyarray as ta 

26from . import _system 

27from ._common import deprecate_args, KwantDeprecationWarning 

28 

29 

30 

31################ Sites and Site families 

32 

33class Site(tuple): 

34 """A site, member of a `SiteFamily`. 

35 

36 Sites are the vertices of the graph which describes the tight binding 

37 system in a `Builder`. 

38 

39 A site is uniquely identified by its family and its tag. 

40 

41 Parameters 

42 ---------- 

43 family : an instance of `SiteFamily` 

44 The 'type' of the site. 

45 tag : a hashable python object 

46 The unique identifier of the site within the site family, typically a 

47 vector of integers. 

48 

49 Raises 

50 ------ 

51 ValueError 

52 If `tag` is not a proper tag for `family`. 

53 

54 Notes 

55 ----- 

56 For convenience, ``family(*tag)`` can be used instead of ``Site(family, 

57 tag)`` to create a site. 

58 

59 The parameters of the constructor (see above) are stored as instance 

60 variables under the same names. Given a site ``site``, common things to 

61 query are thus ``site.family``, ``site.tag``, and ``site.pos``. 

62 """ 

63 __slots__ = () 

64 

65 family = property(operator.itemgetter(0), 

66 doc="The site family to which the site belongs.") 

67 tag = property(operator.itemgetter(1), doc="The tag of the site.") 

68 

69 

70 def __new__(cls, family, tag, _i_know_what_i_do=False): 

71 if _i_know_what_i_do: 

72 return tuple.__new__(cls, (family, tag)) 

73 try: 

74 tag = family.normalize_tag(tag) 

75 except (TypeError, ValueError) as e: 

76 msg = 'Tag {0} is not allowed for site family {1}: {2}' 

77 raise type(e)(msg.format(repr(tag), repr(family), e.args[0])) 

78 return tuple.__new__(cls, (family, tag)) 

79 

80 def __repr__(self): 

81 return 'Site({0}, {1})'.format(repr(self.family), repr(self.tag)) 

82 

83 def __str__(self): 

84 sf = self.family 

85 return '<Site {0} of {1}>'.format(self.tag, sf.name if sf.name else sf) 

86 

87 def __getnewargs__(self): 

88 return (self.family, self.tag, True) 

89 

90 @property 

91 def pos(self): 

92 """Real space position of the site. 

93 

94 This relies on ``family`` having a ``pos`` method (see `SiteFamily`). 

95 """ 

96 return self.family.pos(self.tag) 

97 

98 

99class SiteArray(collections.abc.Sequence): 

100 """An array of sites, members of a `SiteFamily`. 

101 

102 Parameters 

103 ---------- 

104 family : an instance of `SiteFamily` 

105 The 'type' of the sites. 

106 tags : a sequence of python objects 

107 Sequence of unique identifiers of the sites within the 

108 site array family, typically vectors of integers. 

109 

110 Raises 

111 ------ 

112 ValueError 

113 If ``tags`` are not proper tags for ``family``. 

114 

115 See Also 

116 -------- 

117 kwant.system.Site 

118 """ 

119 

120 def __init__(self, family, tags): 

121 self.family = family 

122 try: 

123 tags = family.normalize_tags(tags) 

124 except (TypeError, ValueError) as e: 

125 msg = 'Tags {0} are not allowed for site family {1}: {2}' 

126 raise type(e)(msg.format(repr(tags), repr(family), e.args[0])) 

127 self.tags = tags 

128 

129 def __repr__(self): 

130 return 'SiteArray({0}, {1})'.format(repr(self.family), repr(self.tags)) 

131 

132 def __str__(self): 

133 sf = self.family 

134 return ('<SiteArray {0} of {1}>' 

135 .format(self.tags, sf.name if sf.name else sf)) 

136 

137 def __len__(self): 

138 return len(self.tags) 

139 

140 def __getitem__(self, key): 

141 if isinstance(key, slice): 

142 return SiteArray(self.family, self.tags[key]) 

143 else: 

144 return Site(self.family, self.tags[key]) 

145 

146 def __eq__(self, other): 

147 if not isinstance(other, SiteArray): 147 ↛ 148line 147 didn't jump to line 148, because the condition on line 147 was never true

148 raise NotImplementedError() 

149 return self.family == other.family and np.all(self.tags == other.tags) 

150 

151 def positions(self): 

152 """Real space position of the site. 

153 

154 This relies on ``family`` having a ``pos`` method (see `SiteFamily`). 

155 """ 

156 return self.family.positions(self.tags) 

157 

158 

159@total_ordering 

160class SiteFamily: 

161 """Abstract base class for site families. 

162 

163 Site families are the 'type' of `Site` objects. Within a family, individual 

164 sites are uniquely identified by tags. Valid tags must be hashable Python 

165 objects, further details are up to the family. 

166 

167 Site families must be immutable and fully defined by their initial 

168 arguments. They must inherit from this abstract base class and call its 

169 __init__ function providing it with two arguments: a canonical 

170 representation and a name. The canonical representation will be returned as 

171 the objects representation and must uniquely identify the site family 

172 instance. The name is a string used to distinguish otherwise identical site 

173 families. It may be empty. ``norbs`` defines the number of orbitals 

174 on sites associated with this site family; it may be `None`, in which case 

175 the number of orbitals is not specified. 

176 

177 

178 All site families must define either 'normalize_tag' or 'normalize_tags', 

179 which brings a tag (or, in the latter case, a sequence of tags) to the 

180 standard format for this site family. 

181 

182 Site families may also implement methods ``pos(tag)`` and 

183 ``positions(tags)``, which return a vector of realspace coordinates or an 

184 array of vectors of realspace coordinates of the site(s) belonging to this 

185 family with the given tag(s). These methods are used in plotting routines. 

186 ``positions(tags)`` should return an array with shape ``(N, M)`` where 

187 ``N`` is the length of ``tags``, and ``M`` is the realspace dimension. 

188 

189 If the ``norbs`` of a site family are provided, and sites of this family 

190 are used to populate a `~kwant.builder.Builder`, then the associated 

191 Hamiltonian values must have the correct shape. That is, if a site family 

192 has ``norbs = 2``, then any on-site terms for sites belonging to this 

193 family should be 2x2 matrices. Similarly, any hoppings to/from sites 

194 belonging to this family must have a matrix structure where there are two 

195 rows/columns. This condition applies equally to Hamiltonian values that 

196 are given by functions. If this condition is not satisfied, an error will 

197 be raised. 

198 """ 

199 

200 def __init__(self, canonical_repr, name, norbs): 

201 self.canonical_repr = canonical_repr 

202 self.hash = hash(canonical_repr) 

203 self.name = name 

204 if norbs is None: 

205 warnings.warn("Not specfying norbs is deprecated. Always specify " 

206 "norbs when creating site families.", 

207 KwantDeprecationWarning, stacklevel=3) 

208 if norbs is not None: 

209 if int(norbs) != norbs or norbs <= 0: 

210 raise ValueError('The norbs parameter must be an integer > 0.') 

211 norbs = int(norbs) 

212 self.norbs = norbs 

213 

214 def __init_subclass__(cls, **kwargs): 

215 super().__init_subclass__(**kwargs) 

216 if (cls.normalize_tag is SiteFamily.normalize_tag 216 ↛ 218line 216 didn't jump to line 218, because the condition on line 216 was never true

217 and cls.normalize_tags is SiteFamily.normalize_tags): 

218 raise TypeError("Must redefine either 'normalize_tag' or " 

219 "'normalize_tags'") 

220 

221 def __repr__(self): 

222 return self.canonical_repr 

223 

224 def __str__(self): 

225 if self.name: 225 ↛ 226line 225 didn't jump to line 226, because the condition on line 225 was never true

226 msg = '<{0} site family {1}{2}>' 

227 else: 

228 msg = '<unnamed {0} site family{2}>' 

229 orbs = ' with {0} orbitals'.format(self.norbs) if self.norbs else '' 

230 return msg.format(self.__class__.__name__, self.name, orbs) 

231 

232 def __hash__(self): 

233 return self.hash 

234 

235 def __eq__(self, other): 

236 try: 

237 return self.canonical_repr == other.canonical_repr 

238 except AttributeError: 

239 return False 

240 

241 def __ne__(self, other): 

242 try: 

243 return self.canonical_repr != other.canonical_repr 

244 except AttributeError: 

245 return True 

246 

247 def __lt__(self, other): 

248 # If this raises an AttributeError, we were trying 

249 # to compare it to something non-comparable anyway. 

250 return self.canonical_repr < other.canonical_repr 

251 

252 def normalize_tag(self, tag): 

253 """Return a normalized version of the tag. 

254 

255 Raises TypeError or ValueError if the tag is not acceptable. 

256 """ 

257 tag, = self.normalize_tags([tag]) 

258 return tag 

259 

260 def normalize_tags(self, tags): 

261 """Return a normalized version of the tags. 

262 

263 Raises TypeError or ValueError if the tags are not acceptable. 

264 """ 

265 return np.array([self.normalize_tag(tag) for tag in tags]) 

266 

267 def __call__(self, *tag): 

268 """ 

269 A convenience function. 

270 

271 This function allows to write fam(1, 2) instead of Site(fam, (1, 2)). 

272 """ 

273 # Catch a likely and difficult to find mistake. 

274 if tag and isinstance(tag[0], tuple): 274 ↛ 275line 274 didn't jump to line 275, because the condition on line 274 was never true

275 raise ValueError('Use site_family(1, 2) instead of ' 

276 'site_family((1, 2))!') 

277 return Site(self, tag) 

278 

279 

280################ Symmetries 

281 

282class Symmetry(metaclass=abc.ABCMeta): 

283 """Abstract base class for spatial symmetries. 

284 

285 Many physical systems possess a discrete spatial symmetry, which results in 

286 special properties of these systems. This class is the standard way to 

287 describe discrete spatial symmetries in Kwant. An instance of this class 

288 can be passed to a `Builder` instance at its creation. The most important 

289 kind of symmetry is translational symmetry, used to define scattering 

290 leads. 

291 

292 Each symmetry has a fundamental domain -- a set of sites and hoppings, 

293 generating all the possible sites and hoppings upon action of symmetry 

294 group elements. A class derived from `Symmetry` has to implement mapping 

295 of any site or hopping into the fundamental domain, applying a symmetry 

296 group element to a site or a hopping, and a method `which` to determine the 

297 group element bringing some site from the fundamental domain to the 

298 requested one. Additionally, it has to have a property `num_directions` 

299 returning the number of independent symmetry group generators (number of 

300 elementary periods for translational symmetry). 

301 

302 A ``ValueError`` must be raised by the symmetry class whenever a symmetry 

303 is used together with sites whose site family is not compatible with it. A 

304 typical example of this is when the vector defining a translational 

305 symmetry is not a lattice vector. 

306 

307 The type of the domain objects as handled by the methods of this class is 

308 not specified. The only requirement is that it must support the unary 

309 minus operation. The reference implementation of `to_fd()` is hence 

310 `self.act(-self.which(a), a, b)`. 

311 """ 

312 

313 @abc.abstractproperty 

314 def num_directions(self): 

315 """Number of elementary periods of the symmetry.""" 

316 pass 

317 

318 @abc.abstractmethod 

319 def which(self, site): 

320 """Calculate the domain of the site. 

321 

322 Parameters 

323 ---------- 

324 site : `~kwant.system.Site` or `~kwant.system.SiteArray` 

325 

326 Returns 

327 ------- 

328 group_element : tuple or sequence of tuples 

329 A single tuple if ``site`` is a Site, or a sequence of tuples if 

330 ``site`` is a SiteArray. The group element(s) whose action 

331 on a certain site(s) from the fundamental domain will result 

332 in the given ``site``. 

333 """ 

334 pass 

335 

336 @abc.abstractmethod 

337 def act(self, element, a, b=None): 

338 """Act with symmetry group element(s) on site(s) or hopping(s). 

339 

340 Parameters 

341 ---------- 

342 element : tuple or sequence of tuples 

343 Group element(s) with which to act on the provided site(s) 

344 or hopping(s) 

345 a, b : `~kwant.system.Site` or `~kwant.system.SiteArray` 

346 If Site then ``element`` is a single tuple, if SiteArray then 

347 ``element`` is a single tuple or a sequence of tuples. 

348 If only ``a`` is provided then ``element`` acts on the site(s) 

349 of ``a``. If ``b`` is also provided then ``element`` acts 

350 on the hopping(s) ``(a, b)``. 

351 """ 

352 pass 

353 

354 def to_fd(self, a, b=None): 

355 """Map a site or hopping to the fundamental domain. 

356 

357 Parameters 

358 ---------- 

359 a, b : `~kwant.system.Site` or `~kwant.system.SiteArray` 

360 

361 If ``b`` is None, return a site equivalent to ``a`` within the 

362 fundamental domain. Otherwise, return a hopping equivalent to ``(a, 

363 b)`` but where the first element belongs to the fundamental domain. 

364 

365 Equivalent to `self.act(-self.which(a), a, b)`. 

366 """ 

367 return self.act(-self.which(a), a, b) 

368 

369 def in_fd(self, site): 

370 """Tell whether ``site`` lies within the fundamental domain. 

371 

372 Parameters 

373 ---------- 

374 site : `~kwant.system.Site` or `~kwant.system.SiteArray` 

375 

376 Returns 

377 ------- 

378 in_fd : bool or sequence of bool 

379 single bool if ``site`` is a Site, or a sequence of 

380 bool if ``site`` is a SiteArray. In the latter case 

381 we return whether each site in the SiteArray is in 

382 the fundamental domain. 

383 """ 

384 if isinstance(site, Site): 384 ↛ 389line 384 didn't jump to line 389, because the condition on line 384 was never false

385 for d in self.which(site): 

386 if d != 0: 

387 return False 

388 return True 

389 elif isinstance(site, SiteArray): 

390 which = self.which(site) 

391 return np.logical_and.reduce(which != 0, axis=1) 

392 else: 

393 raise TypeError("'site' must be a Site or SiteArray") 

394 

395 @abc.abstractmethod 

396 def subgroup(self, *generators): 

397 """Return the subgroup generated by a sequence of group elements.""" 

398 pass 

399 

400 @abc.abstractmethod 

401 def has_subgroup(self, other): 

402 """Test whether `self` has the subgroup `other`... 

403 

404 or, in other words, whether `other` is a subgroup of `self`. The 

405 reason why this is the abstract method (and not `is_subgroup`) is that 

406 in general it's not possible for a subgroup to know its supergroups. 

407 

408 """ 

409 pass 

410 

411 

412class NoSymmetry(Symmetry): 

413 """A symmetry with a trivial symmetry group.""" 

414 

415 def __eq__(self, other): 

416 return isinstance(other, NoSymmetry) 

417 

418 def __ne__(self, other): 

419 return not self.__eq__(other) 

420 

421 def __repr__(self): 

422 return 'NoSymmetry()' 

423 

424 @property 

425 def num_directions(self): 

426 return 0 

427 

428 periods = () 

429 

430 _empty_array = ta.array((), int) 

431 

432 def which(self, site): 

433 return self._empty_array 

434 

435 def act(self, element, a, b=None): 

436 if element: 436 ↛ 437line 436 didn't jump to line 437, because the condition on line 436 was never true

437 raise ValueError('`element` must be empty for NoSymmetry.') 

438 return a if b is None else (a, b) 

439 

440 def to_fd(self, a, b=None): 

441 return a if b is None else (a, b) 

442 

443 def in_fd(self, site): 

444 return True 

445 

446 def subgroup(self, *generators): 

447 if any(generators): 

448 raise ValueError('Generators must be empty for NoSymmetry.') 

449 return NoSymmetry(generators) 

450 

451 def has_subgroup(self, other): 

452 return isinstance(other, NoSymmetry) 

453 

454 

455################ Systems 

456 

457 

458class System(metaclass=abc.ABCMeta): 

459 """Abstract general low-level system. 

460 

461 Attributes 

462 ---------- 

463 graph : kwant.graph.CGraph 

464 The system graph. 

465 site_ranges : None or sorted sequence of triples of integers 

466 If provided, encodes ranges of sites that have the same number of 

467 orbitals. Each triple consists of ``(first_site, norbs, orb_offset)``: 

468 the first site in the range, the number of orbitals on each site in the 

469 range, and the offset of the first orbital of the first site in the 

470 range. In addition, the final triple should have the form 

471 ``(graph.num_nodes, 0, tot_norbs)`` where ``tot_norbs`` is the 

472 total number of orbitals in the system. 

473 parameters : frozenset of strings 

474 The names of the parameters on which the system depends. This attribute 

475 is provisional and may be changed in a future version of Kwant 

476 

477 Notes 

478 ----- 

479 The sites of the system are indexed by integers ranging from 0 to 

480 ``self.graph.num_nodes - 1``. 

481 

482 Optionally, a class derived from ``System`` can provide a method 

483 ``pos`` which is assumed to return the real-space position of a site 

484 given its index. 

485 

486 Due to the ordering semantics of sequences, and the fact that a given 

487 ``first_site`` can only appear *at most once* in ``site_ranges``, 

488 ``site_ranges`` is ordered according to ``first_site``. 

489 

490 Consecutive elements in ``site_ranges`` are not required to have different 

491 numbers of orbitals. 

492 """ 

493 @abc.abstractmethod 

494 def hamiltonian(self, i, j, *args, params=None): 

495 """Return the hamiltonian matrix element for sites ``i`` and ``j``. 

496 

497 If ``i == j``, return the on-site Hamiltonian of site ``i``. 

498 

499 if ``i != j``, return the hopping between site ``i`` and ``j``. 

500 

501 Hamiltonians may depend (optionally) on positional and 

502 keyword arguments. 

503 

504 Providing positional arguments via 'args' is deprecated, 

505 instead, provide named parameters as a dictionary via 'params'. 

506 """ 

507 pass 

508 

509 @deprecate_args 

510 def discrete_symmetry(self, args, *, params=None): 

511 """Return the discrete symmetry of the system. 

512 

513 Providing positional arguments via 'args' is deprecated, 

514 instead, provide named parameters as a dictionary via 'params'. 

515 """ 

516 # Avoid the circular import. 

517 from .physics import DiscreteSymmetry 

518 return DiscreteSymmetry() 

519 

520 

521 def __str__(self): 

522 items = [ 

523 # (format, extractor, skip if info not present) 

524 ('{} sites', self.graph.num_nodes, False), 

525 ('{} hoppings', self.graph.num_edges, False), 

526 ('parameters: {}', tuple(self.parameters), True), 

527 ] 

528 # Skip some information when it's not present (parameters) 

529 details = [fmt.format(info) for fmt, info, skip in items 

530 if (info or not skip)] 

531 details = ', and '.join((', '.join(details[:-1]), details[-1])) 

532 return '<{} with {}>'.format(self.__class__.__name__, details) 

533 

534 hamiltonian_submatrix = _system.hamiltonian_submatrix 

535 

536 

537Term = collections.namedtuple( 

538 "Term", 

539 ["subgraph", "symmetry_element", "hermitian", "parameters"], 

540) 

541 

542 

543class VectorizedSystem(System, metaclass=abc.ABCMeta): 

544 """Abstract general low-level system with support for vectorization. 

545 

546 Attributes 

547 ---------- 

548 symmetry : kwant.system.Symmetry 

549 The symmetry of the system. 

550 site_arrays : sequence of SiteArray 

551 The sites of the system. The family of each site array must have 

552 ``norbs`` specified. 

553 site_ranges : Nx3 integer array 

554 Has 1 row per site array, plus one extra row. Each row consists 

555 of ``(first_site, norbs, orb_offset)``: the index of the first 

556 site in the site array, the number of orbitals on each site in 

557 the site array, and the offset of the first orbital of the first 

558 site in the site array. In addition, the final row has the form 

559 ``(len(graph.num_nodes), 0, tot_norbs)`` where ``tot_norbs`` is the 

560 total number of orbitals in the system. Note ``site_ranges`` 

561 is directly computable from ``site_arrays``. 

562 graph : kwant.graph.CGraph 

563 The system graph. 

564 subgraphs : sequence of tuples 

565 Each subgraph has the form ``((idx1, idx2), (offsets1, offsets2))`` 

566 where ``offsets1`` and ``offsets2`` index sites within the site arrays 

567 indexed by ``idx1`` and ``idx2``. 

568 terms : sequence of tuples 

569 Each tuple has the following structure: 

570 (subgraph: int, symmetry_element: tuple, hermitian: bool, 

571 parameters: List(str)) 

572 ``subgraph`` indexes ``subgraphs`` and supplies the to/from sites of this 

573 term. ``symmetry_element`` is the symmetry group element that should be 

574 applied to the to-sites of this term. 

575 ``hermitian`` is ``True`` if the term needs its Hermitian 

576 conjugate to be added when evaluating the Hamiltonian, and ``parameters`` 

577 contains a list of parameter names used when evaluating this term. 

578 parameters : frozenset of strings 

579 The names of the parameters on which the system depends. This attribute 

580 is provisional and may be changed in a future version of Kwant 

581 

582 Notes 

583 ----- 

584 The sites of the system are indexed by integers ranging from 0 to 

585 ``self.graph.num_nodes - 1``. 

586 

587 Optionally, a class derived from ``System`` can provide a method 

588 ``pos`` which is assumed to return the real-space position of a site 

589 given its index. 

590 """ 

591 

592 @abc.abstractmethod 

593 def hamiltonian_term(self, index, selector=slice(None), 

594 args=(), params=None): 

595 """Return the Hamiltonians for hamiltonian term number k. 

596 

597 Parameters 

598 ---------- 

599 index : int 

600 The index of the term to evaluate. 

601 selector : slice or sequence of int, default: slice(None) 

602 The elements of the term to evaluate. 

603 args : tuple 

604 Positional arguments to the term. (Deprecated) 

605 params : dict 

606 Keyword parameters to the term 

607 

608 Returns 

609 ------- 

610 hamiltonian : 3d complex array 

611 Has shape ``(N, P, Q)`` where ``N`` is the number of matrix 

612 elements in this term (or the number selected by 'selector' 

613 if provided), ``P`` and ``Q`` are the number of orbitals in the 

614 'to' and 'from' site arrays associated with this term. 

615 

616 Providing positional arguments via 'args' is deprecated, 

617 instead, provide named parameters as a dictionary via 'params'. 

618 """ 

619 

620 @property 

621 @lru_cache(1) 

622 def site_ranges(self): 

623 site_offsets = np.cumsum([0] + [len(arr) for arr in self.site_arrays]) 

624 norbs = [arr.family.norbs for arr in self.site_arrays] + [0] 

625 orb_offsets = np.cumsum( 

626 [0] + [len(arr) * arr.family.norbs for arr in self.site_arrays] 

627 ) 

628 return np.array([site_offsets, norbs, orb_offsets]).transpose() 

629 

630 hamiltonian_submatrix = _system.vectorized_hamiltonian_submatrix 

631 

632 

633class FiniteSystemMixin(metaclass=abc.ABCMeta): 

634 """Abstract finite low-level system, possibly with leads. 

635 

636 Attributes 

637 ---------- 

638 leads : sequence of leads 

639 Each lead has to provide a method ``selfenergy`` that has 

640 the same signature as `InfiniteSystem.selfenergy` (without the 

641 ``self`` parameter), and must have property ``parameters``: 

642 a collection of strings that name the system parameters ( 

643 though this requirement is provisional and may be removed in 

644 a future version of Kwant). 

645 It may also provide ``modes`` that has the 

646 same signature as `InfiniteSystem.modes` (without the ``self`` 

647 parameter). 

648 lead_interfaces : sequence of sequences of integers 

649 Each sub-sequence contains the indices of the system sites 

650 to which the lead is connected. 

651 lead_paddings : sequence of sequences of integers 

652 Each sub-sequence contains the indices of the system sites 

653 that belong to the lead, and therefore have the same onsite as the lead 

654 sites, and are connected by the same hoppings as the lead sites. 

655 parameters : frozenset of strings 

656 The names of the parameters on which the system depends. This does 

657 not include the parameters for any leads. This attribute 

658 is provisional and may be changed in a future version of Kwant 

659 

660 Notes 

661 ----- 

662 The length of ``leads`` must be equal to the length of ``lead_interfaces`` 

663 and ``lead_paddings``. 

664 

665 For lead ``n``, the method leads[n].selfenergy must return a square 

666 matrix whose size is ``sum(len(self.hamiltonian(site, site)) for 

667 site in self.lead_interfaces[n])``. The output of ``leads[n].modes`` 

668 has to be a tuple of `~kwant.physics.PropagatingModes`, 

669 `~kwant.physics.StabilizedModes`. 

670 

671 Often, the elements of `leads` will be instances of `InfiniteSystem`. If 

672 this is the case for lead ``n``, the sites ``lead_interfaces[n]`` match 

673 the first ``len(lead_interfaces[n])`` sites of the InfiniteSystem. 

674 """ 

675 

676 @deprecate_args 

677 def precalculate(self, energy=0, args=(), leads=None, 

678 what='modes', *, params=None): 

679 """ 

680 Precalculate modes or self-energies in the leads. 

681 

682 Construct a copy of the system, with the lead modes precalculated, 

683 which may significantly speed up calculations where only the system 

684 is changing. 

685 

686 Parameters 

687 ---------- 

688 energy : float 

689 Energy at which the modes or self-energies have to be 

690 evaluated. 

691 args : sequence 

692 Additional parameters required for calculating the Hamiltionians. 

693 Deprecated in favor of 'params' (and mutually exclusive with it). 

694 leads : sequence of integers or None 

695 Indices of the leads to be precalculated. If ``None``, all are 

696 precalculated. 

697 what : 'modes', 'selfenergy', 'all' 

698 The quantitity to precompute. 'all' will compute both 

699 modes and self-energies. Defaults to 'modes'. 

700 params : dict, optional 

701 Dictionary of parameter names and their values. Mutually exclusive 

702 with 'args'. 

703 

704 Returns 

705 ------- 

706 syst : FiniteSystem 

707 A copy of the original system with some leads precalculated. 

708 

709 Notes 

710 ----- 

711 If the leads are precalculated at certain `energy` or `args` values, 

712 they might give wrong results if used to solve the system with 

713 different parameter values. Use this function with caution. 

714 """ 

715 

716 if what not in ('modes', 'selfenergy', 'all'): 716 ↛ 717line 716 didn't jump to line 717, because the condition on line 716 was never true

717 raise ValueError("Invalid value of argument 'what': " 

718 "{0}".format(what)) 

719 

720 result = copy(self) 

721 if leads is None: 721 ↛ 723line 721 didn't jump to line 723, because the condition on line 721 was never false

722 leads = list(range(len(self.leads))) 

723 new_leads = [] 

724 for nr, lead in enumerate(self.leads): 

725 if nr not in leads: 725 ↛ 726line 725 didn't jump to line 726, because the condition on line 725 was never true

726 new_leads.append(lead) 

727 continue 

728 modes, selfenergy = None, None 

729 if what in ('modes', 'all'): 

730 modes = lead.modes(energy, args, params=params) 

731 if what in ('selfenergy', 'all'): 

732 if modes: 

733 selfenergy = modes[1].selfenergy() 

734 else: 

735 selfenergy = lead.selfenergy(energy, args, params=params) 

736 new_leads.append(PrecalculatedLead(modes, selfenergy)) 

737 result.leads = new_leads 

738 return result 

739 

740 @deprecate_args 

741 def validate_symmetries(self, args=(), *, params=None): 

742 """Check that the Hamiltonian satisfies discrete symmetries. 

743 

744 Applies `~kwant.physics.DiscreteSymmetry.validate` to the 

745 Hamiltonian, see its documentation for details on the return 

746 format. 

747 

748 Providing positional arguments via 'args' is deprecated, 

749 instead, provide named parameters as a dictionary via 'params'. 

750 """ 

751 symmetries = self.discrete_symmetry(args=args, params=params) 

752 ham = self.hamiltonian_submatrix(args, sparse=True, params=params) 

753 return symmetries.validate(ham) 

754 

755 

756class FiniteSystem(System, FiniteSystemMixin, metaclass=abc.ABCMeta): 

757 pass 

758 

759 

760class FiniteVectorizedSystem(VectorizedSystem, FiniteSystemMixin, 

761 metaclass=abc.ABCMeta): 

762 pass 

763 

764 

765def is_finite(syst): 

766 return isinstance(syst, (FiniteSystem, FiniteVectorizedSystem)) 

767 

768 

769class InfiniteSystemMixin(metaclass=abc.ABCMeta): 

770 

771 @deprecate_args 

772 def modes(self, energy=0, args=(), *, params=None): 

773 """Return mode decomposition of the lead 

774 

775 See documentation of `~kwant.physics.PropagatingModes` and 

776 `~kwant.physics.StabilizedModes` for the return format details. 

777 

778 The wave functions of the returned modes are defined over the 

779 *unit cell* of the system, which corresponds to the degrees of 

780 freedom on the first ``cell_sites`` sites of the system 

781 (recall that infinite systems store first the sites in the unit 

782 cell, then connected sites in the neighboring unit cell). 

783 

784 Providing positional arguments via 'args' is deprecated, 

785 instead, provide named parameters as a dictionary via 'params'. 

786 """ 

787 from . import physics # Putting this here avoids a circular import. 

788 ham = self.cell_hamiltonian(args, params=params) 

789 hop = self.inter_cell_hopping(args, params=params) 

790 symmetries = self.discrete_symmetry(args, params=params) 

791 # Check whether each symmetry is broken. 

792 # If a symmetry is broken, it is ignored in the computation. 

793 broken = set(symmetries.validate(ham) + symmetries.validate(hop)) 

794 attribute_names = {'Conservation law': 'projectors', 

795 'Time reversal': 'time_reversal', 

796 'Particle-hole': 'particle-hole', 

797 'Chiral': 'chiral'} 

798 for name in broken: 798 ↛ 799line 798 didn't jump to line 799, because the loop on line 798 never started

799 warnings.warn('Hamiltonian breaks ' + name + 

800 ', ignoring the symmetry in the computation.') 

801 assert name in attribute_names, 'Inconsistent naming of symmetries' 

802 setattr(symmetries, attribute_names[name], None) 

803 

804 shape = ham.shape 

805 assert len(shape) == 2 

806 assert shape[0] == shape[1] 

807 # Subtract energy from the diagonal. 

808 ham.flat[::ham.shape[0] + 1] -= energy 

809 

810 # Particle-hole and chiral symmetries only apply at zero energy. 

811 if energy: 

812 symmetries.particle_hole = symmetries.chiral = None 

813 return physics.modes(ham, hop, discrete_symmetry=symmetries) 

814 

815 @deprecate_args 

816 def selfenergy(self, energy=0, args=(), *, params=None): 

817 """Return self-energy of a lead. 

818 

819 The returned matrix has the shape (s, s), where s is 

820 ``sum(len(self.hamiltonian(i, i)) for i in range(self.graph.num_nodes - 

821 self.cell_size))``. 

822 

823 Providing positional arguments via 'args' is deprecated, 

824 instead, provide named parameters as a dictionary via 'params'. 

825 """ 

826 from . import physics # Putting this here avoids a circular import. 

827 ham = self.cell_hamiltonian(args, params=params) 

828 shape = ham.shape 

829 assert len(shape) == 2 

830 assert shape[0] == shape[1] 

831 # Subtract energy from the diagonal. 

832 ham.flat[::ham.shape[0] + 1] -= energy 

833 return physics.selfenergy(ham, 

834 self.inter_cell_hopping(args, params=params)) 

835 

836 @deprecate_args 

837 def validate_symmetries(self, args=(), *, params=None): 

838 """Check that the Hamiltonian satisfies discrete symmetries. 

839 

840 Returns `~kwant.physics.DiscreteSymmetry.validate` applied 

841 to the onsite matrix and the hopping. See its documentation for 

842 details on the return format. 

843 

844 Providing positional arguments via 'args' is deprecated, 

845 instead, provide named parameters as a dictionary via 'params'. 

846 """ 

847 symmetries = self.discrete_symmetry(args=args, params=params) 

848 ham = self.cell_hamiltonian(args=args, sparse=True, params=params) 

849 hop = self.inter_cell_hopping(args=args, sparse=True, params=params) 

850 broken = set(symmetries.validate(ham) + symmetries.validate(hop)) 

851 return list(broken) 

852 

853 

854class InfiniteSystem(System, InfiniteSystemMixin, metaclass=abc.ABCMeta): 

855 """Abstract infinite low-level system. 

856 

857 An infinite system consists of an infinite series of identical cells. 

858 Adjacent cells are connected by identical inter-cell hoppings. 

859 

860 Attributes 

861 ---------- 

862 cell_size : integer 

863 The number of sites in a single cell of the system. 

864 

865 Notes 

866 ----- 

867 The system graph of an infinite systems contains a single cell, as well as 

868 the part of the previous cell which is connected to it. The first 

869 `cell_size` sites form one complete single cell. The remaining ``N`` sites 

870 of the graph (``N`` equals ``graph.num_nodes - cell_size``) belong to the 

871 previous cell. They are included so that hoppings between cells can be 

872 represented. The N sites of the previous cell correspond to the first 

873 ``N`` sites of the fully included cell. When an ``InfiniteSystem`` is used 

874 as a lead, ``N`` acts also as the number of interface sites to which it 

875 must be connected. 

876 

877 The drawing shows three cells of an infinite system. Each cell consists 

878 of three sites. Numbers denote sites which are included into the system 

879 graph. Stars denote sites which are not included. Hoppings are included 

880 in the graph if and only if they occur between two sites which are part of 

881 the graph:: 

882 

883 * 2 * 

884 ... | | | ... 

885 * 0 3 

886 |/|/| 

887 *-1-4 

888 

889 <-- order of cells 

890 

891 The numbering of sites in the drawing is one of the two valid ones for that 

892 infinite system. The other scheme has the numbers of site 0 and 1 

893 exchanged, as well as of site 3 and 4. 

894 """ 

895 

896 @deprecate_args 

897 def cell_hamiltonian(self, args=(), sparse=False, *, params=None): 

898 """Hamiltonian of a single cell of the infinite system. 

899 

900 Providing positional arguments via 'args' is deprecated, 

901 instead, provide named parameters as a dictionary via 'params'. 

902 """ 

903 cell_sites = range(self.cell_size) 

904 return self.hamiltonian_submatrix(args, cell_sites, cell_sites, 

905 sparse=sparse, params=params) 

906 

907 @deprecate_args 

908 def inter_cell_hopping(self, args=(), sparse=False, *, params=None): 

909 """Hopping Hamiltonian between two cells of the infinite system. 

910 

911 Providing positional arguments via 'args' is deprecated, 

912 instead, provide named parameters as a dictionary via 'params'. 

913 """ 

914 cell_sites = range(self.cell_size) 

915 interface_sites = range(self.cell_size, self.graph.num_nodes) 

916 return self.hamiltonian_submatrix(args, cell_sites, interface_sites, 

917 sparse=sparse, params=params) 

918 

919 

920class InfiniteVectorizedSystem(VectorizedSystem, InfiniteSystemMixin, 

921 metaclass=abc.ABCMeta): 

922 """Abstract vectorized infinite low-level system. 

923 

924 An infinite system consists of an infinite series of identical cells. 

925 Adjacent cells are connected by identical inter-cell hoppings. 

926 

927 Attributes 

928 ---------- 

929 cell_size : integer 

930 The number of sites in a single cell of the system. 

931 

932 Notes 

933 ----- 

934 Unlike `~kwant.system.InfiniteSystem`, vectorized infinite systems do 

935 not explicitly store the sites in the previous unit cell; only the 

936 sites in the fundamental domain are stored. Nevertheless, the 

937 SiteArrays of `~kwant.system.InfiniteVectorizedSystem` are ordered 

938 in an analogous way, in order to facilitate the representation of 

939 inter-cell hoppings. The ordering is as follows. The *interface sites* 

940 of a unit cell are the sites that have hoppings to the *next* unit cell 

941 (along the symmetry direction). Interface sites are always in different 

942 SiteArrays than non-interface sites, i.e. the sites in a given SiteArray 

943 are either all interface sites, or all non-interface sites. 

944 The SiteArrays consisting of interface sites always appear *before* the 

945 SiteArrays consisting of non-interface sites in ``self.site_arrays``. 

946 This is backwards compatible with `kwant.system.InfiniteSystem`. 

947 

948 For backwards compatibility, `~kwant.system.InfiniteVectorizedSystem` 

949 maintains a ``graph``, that includes nodes for the sites 

950 in the previous unit cell. 

951 """ 

952 cell_hamiltonian = _system.vectorized_cell_hamiltonian 

953 inter_cell_hopping = _system.vectorized_inter_cell_hopping 

954 

955 def hamiltonian_submatrix(self, args=(), sparse=False, 

956 return_norb=False, *, params=None): 

957 raise ValueError( 

958 "'hamiltonian_submatrix' is not meaningful for infinite" 

959 "systems. Use 'cell_hamiltonian' or 'inter_cell_hopping." 

960 ) 

961 

962 

963def is_infinite(syst): 

964 return isinstance(syst, (InfiniteSystem, InfiniteVectorizedSystem)) 

965 

966 

967def is_vectorized(syst): 

968 return isinstance(syst, (FiniteVectorizedSystem, InfiniteVectorizedSystem)) 

969 

970 

971def is_selfenergy_lead(lead): 

972 return hasattr(lead, "selfenergy") and not hasattr(lead, "modes") 

973 

974 

975def _normalize_matrix_blocks(blocks, expected_shape, *, calling_function=None): 

976 """Normalize a sequence of matrices into a single 3D numpy array 

977 

978 Parameters 

979 ---------- 

980 blocks : sequence of complex array-like 

981 expected_shape : (int, int, int) 

982 calling_function : callable (optional) 

983 The function that produced 'blocks'. If provided, used to give 

984 a more helpful error message if 'blocks' is not of the correct shape. 

985 """ 

986 try: 

987 blocks = np.asarray(blocks, dtype=complex) 

988 except TypeError: 

989 raise ValueError( 

990 "Matrix elements declared with incompatible shapes." 

991 ) from None 

992 original_shape = blocks.shape 

993 was_broadcast = True # Did the shape get broadcasted to a more general one? 

994 if len(blocks.shape) == 0: # scalar → broadcast to vector of 1x1 matrices 

995 blocks = np.tile(blocks, (expected_shape[0], 1, 1)) 

996 elif len(blocks.shape) == 1: # vector → interpret as vector of 1x1 matrices 

997 blocks = blocks.reshape(-1, 1, 1) 

998 elif len(blocks.shape) == 2: # matrix → broadcast to vector of matrices 

999 blocks = np.tile(blocks, (expected_shape[0], 1, 1)) 

1000 else: 

1001 was_broadcast = False 

1002 

1003 if blocks.shape != expected_shape: 

1004 msg = ( 

1005 "Expected values of shape {}, but received values of shape {}" 

1006 .format(expected_shape, blocks.shape), 

1007 "(broadcasted from shape {})".format(original_shape) 

1008 if was_broadcast else "", 

1009 "when evaluating {}".format(calling_function.__name__) 

1010 if callable(calling_function) else "", 

1011 ) 

1012 raise ValueError(" ".join(msg)) 

1013 

1014 return blocks 

1015 

1016 

1017 

1018class PrecalculatedLead: 

1019 def __init__(self, modes=None, selfenergy=None): 

1020 """A general lead defined by its self energy. 

1021 

1022 Parameters 

1023 ---------- 

1024 modes : (kwant.physics.PropagatingModes, kwant.physics.StabilizedModes) 

1025 Modes of the lead. 

1026 selfenergy : numpy array 

1027 Lead self-energy. 

1028 

1029 Notes 

1030 ----- 

1031 At least one of ``modes`` and ``selfenergy`` must be provided. 

1032 """ 

1033 if modes is None and selfenergy is None: 1033 ↛ 1034line 1033 didn't jump to line 1034, because the condition on line 1033 was never true

1034 raise ValueError("No precalculated values provided.") 

1035 self._modes = modes 

1036 self._selfenergy = selfenergy 

1037 # Modes/Self-energy have already been evaluated, so there 

1038 # is no parametric dependence anymore 

1039 self.parameters = frozenset() 

1040 

1041 @deprecate_args 

1042 def modes(self, energy=0, args=(), *, params=None): 

1043 if self._modes is not None: 

1044 return self._modes 

1045 else: 

1046 raise ValueError("No precalculated modes were provided. " 

1047 "Consider using precalculate() with " 

1048 "what='modes' or what='all'") 

1049 

1050 @deprecate_args 

1051 def selfenergy(self, energy=0, args=(), *, params=None): 

1052 if self._selfenergy is not None: 

1053 return self._selfenergy 

1054 else: 

1055 raise ValueError("No precalculated selfenergy was provided. " 

1056 "Consider using precalculate() with " 

1057 "what='selfenergy' or what='all'")