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.
9"""Low-level interface of systems"""
11__all__ = [
12 'Site', 'SiteArray', 'SiteFamily', 'Symmetry', 'NoSymmetry',
13 'System', 'VectorizedSystem', 'FiniteSystem', 'FiniteVectorizedSystem',
14 'InfiniteSystem', 'InfiniteVectorizedSystem',
15 'is_finite', 'is_infinite', 'is_vectorized',
16]
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
31################ Sites and Site families
33class Site(tuple):
34 """A site, member of a `SiteFamily`.
36 Sites are the vertices of the graph which describes the tight binding
37 system in a `Builder`.
39 A site is uniquely identified by its family and its tag.
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.
49 Raises
50 ------
51 ValueError
52 If `tag` is not a proper tag for `family`.
54 Notes
55 -----
56 For convenience, ``family(*tag)`` can be used instead of ``Site(family,
57 tag)`` to create a site.
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__ = ()
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.")
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))
80 def __repr__(self):
81 return 'Site({0}, {1})'.format(repr(self.family), repr(self.tag))
83 def __str__(self):
84 sf = self.family
85 return '<Site {0} of {1}>'.format(self.tag, sf.name if sf.name else sf)
87 def __getnewargs__(self):
88 return (self.family, self.tag, True)
90 @property
91 def pos(self):
92 """Real space position of the site.
94 This relies on ``family`` having a ``pos`` method (see `SiteFamily`).
95 """
96 return self.family.pos(self.tag)
99class SiteArray(collections.abc.Sequence):
100 """An array of sites, members of a `SiteFamily`.
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.
110 Raises
111 ------
112 ValueError
113 If ``tags`` are not proper tags for ``family``.
115 See Also
116 --------
117 kwant.system.Site
118 """
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
129 def __repr__(self):
130 return 'SiteArray({0}, {1})'.format(repr(self.family), repr(self.tags))
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))
137 def __len__(self):
138 return len(self.tags)
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])
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)
151 def positions(self):
152 """Real space position of the site.
154 This relies on ``family`` having a ``pos`` method (see `SiteFamily`).
155 """
156 return self.family.positions(self.tags)
159@total_ordering
160class SiteFamily:
161 """Abstract base class for site families.
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.
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.
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.
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.
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 """
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
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'")
221 def __repr__(self):
222 return self.canonical_repr
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)
232 def __hash__(self):
233 return self.hash
235 def __eq__(self, other):
236 try:
237 return self.canonical_repr == other.canonical_repr
238 except AttributeError:
239 return False
241 def __ne__(self, other):
242 try:
243 return self.canonical_repr != other.canonical_repr
244 except AttributeError:
245 return True
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
252 def normalize_tag(self, tag):
253 """Return a normalized version of the tag.
255 Raises TypeError or ValueError if the tag is not acceptable.
256 """
257 tag, = self.normalize_tags([tag])
258 return tag
260 def normalize_tags(self, tags):
261 """Return a normalized version of the tags.
263 Raises TypeError or ValueError if the tags are not acceptable.
264 """
265 return np.array([self.normalize_tag(tag) for tag in tags])
267 def __call__(self, *tag):
268 """
269 A convenience function.
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)
280################ Symmetries
282class Symmetry(metaclass=abc.ABCMeta):
283 """Abstract base class for spatial symmetries.
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.
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).
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.
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 """
313 @abc.abstractproperty
314 def num_directions(self):
315 """Number of elementary periods of the symmetry."""
316 pass
318 @abc.abstractmethod
319 def which(self, site):
320 """Calculate the domain of the site.
322 Parameters
323 ----------
324 site : `~kwant.system.Site` or `~kwant.system.SiteArray`
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
336 @abc.abstractmethod
337 def act(self, element, a, b=None):
338 """Act with symmetry group element(s) on site(s) or hopping(s).
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
354 def to_fd(self, a, b=None):
355 """Map a site or hopping to the fundamental domain.
357 Parameters
358 ----------
359 a, b : `~kwant.system.Site` or `~kwant.system.SiteArray`
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.
365 Equivalent to `self.act(-self.which(a), a, b)`.
366 """
367 return self.act(-self.which(a), a, b)
369 def in_fd(self, site):
370 """Tell whether ``site`` lies within the fundamental domain.
372 Parameters
373 ----------
374 site : `~kwant.system.Site` or `~kwant.system.SiteArray`
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")
395 @abc.abstractmethod
396 def subgroup(self, *generators):
397 """Return the subgroup generated by a sequence of group elements."""
398 pass
400 @abc.abstractmethod
401 def has_subgroup(self, other):
402 """Test whether `self` has the subgroup `other`...
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.
408 """
409 pass
412class NoSymmetry(Symmetry):
413 """A symmetry with a trivial symmetry group."""
415 def __eq__(self, other):
416 return isinstance(other, NoSymmetry)
418 def __ne__(self, other):
419 return not self.__eq__(other)
421 def __repr__(self):
422 return 'NoSymmetry()'
424 @property
425 def num_directions(self):
426 return 0
428 periods = ()
430 _empty_array = ta.array((), int)
432 def which(self, site):
433 return self._empty_array
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)
440 def to_fd(self, a, b=None):
441 return a if b is None else (a, b)
443 def in_fd(self, site):
444 return True
446 def subgroup(self, *generators):
447 if any(generators):
448 raise ValueError('Generators must be empty for NoSymmetry.')
449 return NoSymmetry(generators)
451 def has_subgroup(self, other):
452 return isinstance(other, NoSymmetry)
455################ Systems
458class System(metaclass=abc.ABCMeta):
459 """Abstract general low-level system.
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
477 Notes
478 -----
479 The sites of the system are indexed by integers ranging from 0 to
480 ``self.graph.num_nodes - 1``.
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.
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``.
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``.
497 If ``i == j``, return the on-site Hamiltonian of site ``i``.
499 if ``i != j``, return the hopping between site ``i`` and ``j``.
501 Hamiltonians may depend (optionally) on positional and
502 keyword arguments.
504 Providing positional arguments via 'args' is deprecated,
505 instead, provide named parameters as a dictionary via 'params'.
506 """
507 pass
509 @deprecate_args
510 def discrete_symmetry(self, args, *, params=None):
511 """Return the discrete symmetry of the system.
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()
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)
534 hamiltonian_submatrix = _system.hamiltonian_submatrix
537Term = collections.namedtuple(
538 "Term",
539 ["subgraph", "symmetry_element", "hermitian", "parameters"],
540)
543class VectorizedSystem(System, metaclass=abc.ABCMeta):
544 """Abstract general low-level system with support for vectorization.
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
582 Notes
583 -----
584 The sites of the system are indexed by integers ranging from 0 to
585 ``self.graph.num_nodes - 1``.
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 """
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.
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
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.
616 Providing positional arguments via 'args' is deprecated,
617 instead, provide named parameters as a dictionary via 'params'.
618 """
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()
630 hamiltonian_submatrix = _system.vectorized_hamiltonian_submatrix
633class FiniteSystemMixin(metaclass=abc.ABCMeta):
634 """Abstract finite low-level system, possibly with leads.
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
660 Notes
661 -----
662 The length of ``leads`` must be equal to the length of ``lead_interfaces``
663 and ``lead_paddings``.
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`.
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 """
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.
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.
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'.
704 Returns
705 -------
706 syst : FiniteSystem
707 A copy of the original system with some leads precalculated.
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 """
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))
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
740 @deprecate_args
741 def validate_symmetries(self, args=(), *, params=None):
742 """Check that the Hamiltonian satisfies discrete symmetries.
744 Applies `~kwant.physics.DiscreteSymmetry.validate` to the
745 Hamiltonian, see its documentation for details on the return
746 format.
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)
756class FiniteSystem(System, FiniteSystemMixin, metaclass=abc.ABCMeta):
757 pass
760class FiniteVectorizedSystem(VectorizedSystem, FiniteSystemMixin,
761 metaclass=abc.ABCMeta):
762 pass
765def is_finite(syst):
766 return isinstance(syst, (FiniteSystem, FiniteVectorizedSystem))
769class InfiniteSystemMixin(metaclass=abc.ABCMeta):
771 @deprecate_args
772 def modes(self, energy=0, args=(), *, params=None):
773 """Return mode decomposition of the lead
775 See documentation of `~kwant.physics.PropagatingModes` and
776 `~kwant.physics.StabilizedModes` for the return format details.
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).
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)
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
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)
815 @deprecate_args
816 def selfenergy(self, energy=0, args=(), *, params=None):
817 """Return self-energy of a lead.
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))``.
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))
836 @deprecate_args
837 def validate_symmetries(self, args=(), *, params=None):
838 """Check that the Hamiltonian satisfies discrete symmetries.
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.
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)
854class InfiniteSystem(System, InfiniteSystemMixin, metaclass=abc.ABCMeta):
855 """Abstract infinite low-level system.
857 An infinite system consists of an infinite series of identical cells.
858 Adjacent cells are connected by identical inter-cell hoppings.
860 Attributes
861 ----------
862 cell_size : integer
863 The number of sites in a single cell of the system.
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.
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::
883 * 2 *
884 ... | | | ...
885 * 0 3
886 |/|/|
887 *-1-4
889 <-- order of cells
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 """
896 @deprecate_args
897 def cell_hamiltonian(self, args=(), sparse=False, *, params=None):
898 """Hamiltonian of a single cell of the infinite system.
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)
907 @deprecate_args
908 def inter_cell_hopping(self, args=(), sparse=False, *, params=None):
909 """Hopping Hamiltonian between two cells of the infinite system.
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)
920class InfiniteVectorizedSystem(VectorizedSystem, InfiniteSystemMixin,
921 metaclass=abc.ABCMeta):
922 """Abstract vectorized infinite low-level system.
924 An infinite system consists of an infinite series of identical cells.
925 Adjacent cells are connected by identical inter-cell hoppings.
927 Attributes
928 ----------
929 cell_size : integer
930 The number of sites in a single cell of the system.
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`.
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
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 )
963def is_infinite(syst):
964 return isinstance(syst, (InfiniteSystem, InfiniteVectorizedSystem))
967def is_vectorized(syst):
968 return isinstance(syst, (FiniteVectorizedSystem, InfiniteVectorizedSystem))
971def is_selfenergy_lead(lead):
972 return hasattr(lead, "selfenergy") and not hasattr(lead, "modes")
975def _normalize_matrix_blocks(blocks, expected_shape, *, calling_function=None):
976 """Normalize a sequence of matrices into a single 3D numpy array
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
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))
1014 return blocks
1018class PrecalculatedLead:
1019 def __init__(self, modes=None, selfenergy=None):
1020 """A general lead defined by its self energy.
1022 Parameters
1023 ----------
1024 modes : (kwant.physics.PropagatingModes, kwant.physics.StabilizedModes)
1025 Modes of the lead.
1026 selfenergy : numpy array
1027 Lead self-energy.
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()
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'")
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'")