First steps in ND vectorized systems
Closes #326
See merge request kwant/kwant!341
... | ... |
@@ -548,13 +548,12 @@ def vectorized_cell_hamiltonian(self, args=(), sparse=False, *, params=None): |
548 | 548 |
# Site array where next cell starts |
549 | 549 |
next_cell = bisect.bisect(site_offsets, self.cell_size) - 1 |
550 | 550 |
|
551 |
- def inside_cell(term): |
|
552 |
- (to_which, from_which), _= self.subgraphs[term.subgraph] |
|
553 |
- return to_which < next_cell and from_which < next_cell |
|
551 |
+ def inside_fd(term): |
|
552 |
+ return all(s == 0 for s in term.symmetry_element) |
|
554 | 553 |
|
555 | 554 |
cell_terms = [ |
556 | 555 |
n for n, t in enumerate(self.terms) |
557 |
- if inside_cell(t) |
|
556 |
+ if inside_fd(t) |
|
558 | 557 |
] |
559 | 558 |
|
560 | 559 |
subgraphs = [ |
... | ... |
@@ -593,34 +592,38 @@ def vectorized_cell_hamiltonian(self, args=(), sparse=False, *, params=None): |
593 | 592 |
def vectorized_inter_cell_hopping(self, args=(), sparse=False, *, params=None): |
594 | 593 |
"""Hopping Hamiltonian between two cells of the infinite system. |
595 | 594 |
|
595 |
+ This method returns a complex matrix that represents the hopping from |
|
596 |
+ the *interface sites* of unit cell ``n - 1`` to *all* the sites of |
|
597 |
+ unit cell ``n``. It is therefore generally a *rectangular* matrix of |
|
598 |
+ shape ``(N_uc, N_iface)`` where ``N_uc`` is the number of orbitals |
|
599 |
+ in the unit cell, and ``N_iface`` is the number of orbitals on the |
|
600 |
+ *interface* sites (i.e. the sites with hoppings *to* the next unit cell). |
|
601 |
+ |
|
596 | 602 |
Providing positional arguments via 'args' is deprecated, |
597 | 603 |
instead, provide named parameters as a dictionary via 'params'. |
598 | 604 |
""" |
599 | 605 |
|
600 |
- # Take advantage of the fact that there are distinct entries in |
|
601 |
- # onsite_terms for sites inside the unit cell, and distinct entries |
|
602 |
- # in onsite_terms for hoppings between the unit cell sites and |
|
603 |
- # interface sites. This way we only need to check the first entry |
|
604 |
- # in onsite/hopping_terms |
|
605 |
- |
|
606 | 606 |
site_offsets = np.cumsum([0] + [len(arr) for arr in self.site_arrays]) |
607 |
- # Site array where next cell starts |
|
608 |
- next_cell = bisect.bisect(site_offsets, self.cell_size) - 1 |
|
609 | 607 |
|
608 |
+ # This method is only meaningful for systems with a 1D translational |
|
609 |
+ # symmetry, and we use this fact in several places |
|
610 |
+ assert all(len(t.symmetry_element) == 1 for t in self.terms) |
|
611 |
+ |
|
612 |
+ # Symmetry element -1 means hoppings *from* the *previous* |
|
613 |
+ # unit cell. These are directly the hoppings we wish to return. |
|
610 | 614 |
inter_cell_hopping_terms = [ |
611 | 615 |
n for n, t in enumerate(self.terms) |
612 |
- # *from* site is in interface |
|
613 |
- if (self.subgraphs[t.subgraph][0][1] >= next_cell |
|
614 |
- and self.subgraphs[t.subgraph][0][0] < next_cell) |
|
616 |
+ if t.symmetry_element[0] == -1 |
|
615 | 617 |
] |
618 |
+ # Symmetry element +1 means hoppings *from* the *next* unit cell. |
|
619 |
+ # These are related by translational symmetry to hoppings *to* |
|
620 |
+ # the *previous* unit cell. We therefore need the *reverse* |
|
621 |
+ # (and conjugate) of these hoppings. |
|
616 | 622 |
reversed_inter_cell_hopping_terms = [ |
617 | 623 |
n for n, t in enumerate(self.terms) |
618 |
- # *to* site is in interface |
|
619 |
- if (self.subgraphs[t.subgraph][0][0] >= next_cell |
|
620 |
- and self.subgraphs[t.subgraph][0][1] < next_cell) |
|
624 |
+ if t.symmetry_element[0] == +1 |
|
621 | 625 |
] |
622 | 626 |
|
623 |
- # Evaluate inter-cell hoppings only |
|
624 | 627 |
inter_cell_hams = [ |
625 | 628 |
self.hamiltonian_term(n, args=args, params=params) |
626 | 629 |
for n in inter_cell_hopping_terms |
... | ... |
@@ -642,18 +645,21 @@ def vectorized_inter_cell_hopping(self, args=(), sparse=False, *, params=None): |
642 | 645 |
for n in reversed_inter_cell_hopping_terms |
643 | 646 |
] |
644 | 647 |
|
645 |
- # All the 'from' sites are in the previous domain, but to build a |
|
646 |
- # matrix we need to get the equivalent sites in the fundamental domain. |
|
647 |
- # We set the 'from' site array to the one from the fundamental domain. |
|
648 |
- subgraphs = [ |
|
649 |
- ((to_sa, from_sa - next_cell), (to_off, from_off)) |
|
650 |
- for (to_sa, from_sa), (to_off, from_off) in subgraphs |
|
651 |
- ] |
|
652 |
- |
|
653 | 648 |
_, norbs, orb_offsets = self.site_ranges.transpose() |
654 | 649 |
|
655 |
- shape = (orb_offsets[next_cell], |
|
656 |
- orb_offsets[len(self.site_arrays) - next_cell]) |
|
650 |
+ # SiteArrays containing interface sites appear before SiteArrays |
|
651 |
+ # containing non-interface sites, so the max of the site array |
|
652 |
+ # indices that appear in the 'from' site arrays of the inter-cell |
|
653 |
+ # hoppings allows us to get the number of interface orbitals. |
|
654 |
+ last_iface_site_array = max( |
|
655 |
+ from_site_array for (_, from_site_array), _ in subgraphs |
|
656 |
+ ) |
|
657 |
+ iface_norbs = orb_offsets[last_iface_site_array + 1] |
|
658 |
+ fd_norbs = orb_offsets[-1] |
|
659 |
+ |
|
660 |
+ # TODO: return a square matrix when we no longer need to maintain |
|
661 |
+ # backwards compatibility with unvectorized systems. |
|
662 |
+ shape = (fd_norbs, iface_norbs) |
|
657 | 663 |
func = _vectorized_make_sparse if sparse else _vectorized_make_dense |
658 | 664 |
mat = func(subgraphs, hams, norbs, orb_offsets, site_offsets, shape=shape) |
659 | 665 |
return mat |
... | ... |
@@ -21,7 +21,7 @@ import tinyarray as ta |
21 | 21 |
import numpy as np |
22 | 22 |
from scipy import sparse |
23 | 23 |
from . import system, graph, KwantDeprecationWarning, UserCodeError |
24 |
-from .system import Site, SiteArray, SiteFamily |
|
24 |
+from .system import Site, SiteArray, SiteFamily, Symmetry, NoSymmetry |
|
25 | 25 |
from .linalg import lll |
26 | 26 |
from .operator import Density |
27 | 27 |
from .physics import DiscreteSymmetry, magnetic_gauge |
... | ... |
@@ -29,7 +29,7 @@ from ._common import (ensure_isinstance, get_parameters, reraise_warnings, |
29 | 29 |
interleave, deprecate_args, memoize) |
30 | 30 |
|
31 | 31 |
|
32 |
-__all__ = ['Builder', 'Symmetry', 'HoppingKind', 'Lead', |
|
32 |
+__all__ = ['Builder', 'HoppingKind', 'Lead', |
|
33 | 33 |
'BuilderLead', 'SelfEnergyLead', 'ModesLead', 'add_peierls_phase'] |
34 | 34 |
|
35 | 35 |
|
... | ... |
@@ -64,182 +64,6 @@ def validate_hopping(hopping): |
64 | 64 |
raise ValueError("A hopping connects the following site to itself:\n" |
65 | 65 |
"{0}".format(a)) |
66 | 66 |
|
67 |
- |
|
68 |
- |
|
69 |
-################ Symmetries |
|
70 |
- |
|
71 |
-class Symmetry(metaclass=abc.ABCMeta): |
|
72 |
- """Abstract base class for spatial symmetries. |
|
73 |
- |
|
74 |
- Many physical systems possess a discrete spatial symmetry, which results in |
|
75 |
- special properties of these systems. This class is the standard way to |
|
76 |
- describe discrete spatial symmetries in Kwant. An instance of this class |
|
77 |
- can be passed to a `Builder` instance at its creation. The most important |
|
78 |
- kind of symmetry is translational symmetry, used to define scattering |
|
79 |
- leads. |
|
80 |
- |
|
81 |
- Each symmetry has a fundamental domain -- a set of sites and hoppings, |
|
82 |
- generating all the possible sites and hoppings upon action of symmetry |
|
83 |
- group elements. A class derived from `Symmetry` has to implement mapping |
|
84 |
- of any site or hopping into the fundamental domain, applying a symmetry |
|
85 |
- group element to a site or a hopping, and a method `which` to determine the |
|
86 |
- group element bringing some site from the fundamental domain to the |
|
87 |
- requested one. Additionally, it has to have a property `num_directions` |
|
88 |
- returning the number of independent symmetry group generators (number of |
|
89 |
- elementary periods for translational symmetry). |
|
90 |
- |
|
91 |
- A ``ValueError`` must be raised by the symmetry class whenever a symmetry |
|
92 |
- is used together with sites whose site family is not compatible with it. A |
|
93 |
- typical example of this is when the vector defining a translational |
|
94 |
- symmetry is not a lattice vector. |
|
95 |
- |
|
96 |
- The type of the domain objects as handled by the methods of this class is |
|
97 |
- not specified. The only requirement is that it must support the unary |
|
98 |
- minus operation. The reference implementation of `to_fd()` is hence |
|
99 |
- `self.act(-self.which(a), a, b)`. |
|
100 |
- """ |
|
101 |
- |
|
102 |
- @abc.abstractproperty |
|
103 |
- def num_directions(self): |
|
104 |
- """Number of elementary periods of the symmetry.""" |
|
105 |
- pass |
|
106 |
- |
|
107 |
- @abc.abstractmethod |
|
108 |
- def which(self, site): |
|
109 |
- """Calculate the domain of the site. |
|
110 |
- |
|
111 |
- Parameters |
|
112 |
- ---------- |
|
113 |
- site : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
114 |
- |
|
115 |
- Returns |
|
116 |
- ------- |
|
117 |
- group_element : tuple or sequence of tuples |
|
118 |
- A single tuple if ``site`` is a Site, or a sequence of tuples if |
|
119 |
- ``site`` is a SiteArray. The group element(s) whose action |
|
120 |
- on a certain site(s) from the fundamental domain will result |
|
121 |
- in the given ``site``. |
|
122 |
- """ |
|
123 |
- pass |
|
124 |
- |
|
125 |
- @abc.abstractmethod |
|
126 |
- def act(self, element, a, b=None): |
|
127 |
- """Act with symmetry group element(s) on site(s) or hopping(s). |
|
128 |
- |
|
129 |
- Parameters |
|
130 |
- ---------- |
|
131 |
- element : tuple or sequence of tuples |
|
132 |
- Group element(s) with which to act on the provided site(s) |
|
133 |
- or hopping(s) |
|
134 |
- a, b : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
135 |
- If Site then ``element`` is a single tuple, if SiteArray then |
|
136 |
- ``element`` is a sequence of tuples. If only ``a`` is provided then |
|
137 |
- ``element`` acts on the site(s) of ``a``. If ``b`` is also provided |
|
138 |
- then ``element`` acts on the hopping(s) ``(a, b)``. |
|
139 |
- """ |
|
140 |
- pass |
|
141 |
- |
|
142 |
- def to_fd(self, a, b=None): |
|
143 |
- """Map a site or hopping to the fundamental domain. |
|
144 |
- |
|
145 |
- Parameters |
|
146 |
- ---------- |
|
147 |
- a, b : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
148 |
- |
|
149 |
- If ``b`` is None, return a site equivalent to ``a`` within the |
|
150 |
- fundamental domain. Otherwise, return a hopping equivalent to ``(a, |
|
151 |
- b)`` but where the first element belongs to the fundamental domain. |
|
152 |
- |
|
153 |
- Equivalent to `self.act(-self.which(a), a, b)`. |
|
154 |
- """ |
|
155 |
- return self.act(-self.which(a), a, b) |
|
156 |
- |
|
157 |
- def in_fd(self, site): |
|
158 |
- """Tell whether ``site`` lies within the fundamental domain. |
|
159 |
- |
|
160 |
- Parameters |
|
161 |
- ---------- |
|
162 |
- site : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
163 |
- |
|
164 |
- Returns |
|
165 |
- ------- |
|
166 |
- in_fd : bool or sequence of bool |
|
167 |
- single bool if ``site`` is a Site, or a sequence of |
|
168 |
- bool if ``site`` is a SiteArray. In the latter case |
|
169 |
- we return whether each site in the SiteArray is in |
|
170 |
- the fundamental domain. |
|
171 |
- """ |
|
172 |
- if isinstance(site, Site): |
|
173 |
- for d in self.which(site): |
|
174 |
- if d != 0: |
|
175 |
- return False |
|
176 |
- return True |
|
177 |
- elif isinstance(site, SiteArray): |
|
178 |
- which = self.which(site) |
|
179 |
- return np.logical_and.reduce(which != 0, axis=1) |
|
180 |
- else: |
|
181 |
- raise TypeError("'site' must be a Site or SiteArray") |
|
182 |
- |
|
183 |
- @abc.abstractmethod |
|
184 |
- def subgroup(self, *generators): |
|
185 |
- """Return the subgroup generated by a sequence of group elements.""" |
|
186 |
- pass |
|
187 |
- |
|
188 |
- @abc.abstractmethod |
|
189 |
- def has_subgroup(self, other): |
|
190 |
- """Test whether `self` has the subgroup `other`... |
|
191 |
- |
|
192 |
- or, in other words, whether `other` is a subgroup of `self`. The |
|
193 |
- reason why this is the abstract method (and not `is_subgroup`) is that |
|
194 |
- in general it's not possible for a subgroup to know its supergroups. |
|
195 |
- |
|
196 |
- """ |
|
197 |
- pass |
|
198 |
- |
|
199 |
- |
|
200 |
-class NoSymmetry(Symmetry): |
|
201 |
- """A symmetry with a trivial symmetry group.""" |
|
202 |
- |
|
203 |
- def __eq__(self, other): |
|
204 |
- return isinstance(other, NoSymmetry) |
|
205 |
- |
|
206 |
- def __ne__(self, other): |
|
207 |
- return not self.__eq__(other) |
|
208 |
- |
|
209 |
- def __repr__(self): |
|
210 |
- return 'NoSymmetry()' |
|
211 |
- |
|
212 |
- @property |
|
213 |
- def num_directions(self): |
|
214 |
- return 0 |
|
215 |
- |
|
216 |
- periods = () |
|
217 |
- |
|
218 |
- _empty_array = ta.array((), int) |
|
219 |
- |
|
220 |
- def which(self, site): |
|
221 |
- return self._empty_array |
|
222 |
- |
|
223 |
- def act(self, element, a, b=None): |
|
224 |
- if element: |
|
225 |
- raise ValueError('`element` must be empty for NoSymmetry.') |
|
226 |
- return a if b is None else (a, b) |
|
227 |
- |
|
228 |
- def to_fd(self, a, b=None): |
|
229 |
- return a if b is None else (a, b) |
|
230 |
- |
|
231 |
- def in_fd(self, site): |
|
232 |
- return True |
|
233 |
- |
|
234 |
- def subgroup(self, *generators): |
|
235 |
- if any(generators): |
|
236 |
- raise ValueError('Generators must be empty for NoSymmetry.') |
|
237 |
- return NoSymmetry(generators) |
|
238 |
- |
|
239 |
- def has_subgroup(self, other): |
|
240 |
- return isinstance(other, NoSymmetry) |
|
241 |
- |
|
242 |
- |
|
243 | 67 |
|
244 | 68 |
################ Hopping kinds |
245 | 69 |
|
... | ... |
@@ -658,7 +482,7 @@ class Builder: |
658 | 482 |
|
659 | 483 |
Parameters |
660 | 484 |
---------- |
661 |
- symmetry : `Symmetry` or `None` |
|
485 |
+ symmetry : `~kwant.system.Symmetry` or `None` |
|
662 | 486 |
The spatial symmetry of the system. |
663 | 487 |
conservation_law : 2D array, dictionary, function, or `None` |
664 | 488 |
An onsite operator with integer eigenvalues that commutes with the |
... | ... |
@@ -707,8 +531,8 @@ class Builder: |
707 | 531 |
that if ``builder[a, b]`` has been set, there is no need to set |
708 | 532 |
``builder[b, a]``. |
709 | 533 |
|
710 |
- Builder instances can be made to automatically respect a `Symmetry` that is |
|
711 |
- passed to them during creation. The behavior of builders with a symmetry |
|
534 |
+ Builder instances can be made to automatically respect a `~kwant.system.Symmetry` |
|
535 |
+ that is passed to them during creation. The behavior of builders with a symmetry |
|
712 | 536 |
is slightly more sophisticated: all keys are mapped to the fundamental |
713 | 537 |
domain of the symmetry before storing them. This may produce confusing |
714 | 538 |
results when neighbors of a site are queried. |
... | ... |
@@ -1657,7 +1481,7 @@ class Builder: |
1657 | 1481 |
system to be returned. |
1658 | 1482 |
|
1659 | 1483 |
Currently, only Builder instances without or with a 1D translational |
1660 |
- `Symmetry` can be finalized. |
|
1484 |
+ `~kwant.system.Symmetry` can be finalized. |
|
1661 | 1485 |
""" |
1662 | 1486 |
if self.symmetry.num_directions == 0: |
1663 | 1487 |
if self.vectorize: |
... | ... |
@@ -2025,7 +1849,7 @@ class _VectorizedFinalizedBuilderMixin(_FinalizedBuilderMixin): |
2025 | 1849 |
"elements at once using 'hamiltonian_term'.", |
2026 | 1850 |
KwantDeprecationWarning |
2027 | 1851 |
) |
2028 |
- site_offsets = np.cumsum([0] + [len(s) for s in self.site_arrays]) |
|
1852 |
+ site_offsets, _, _ = self.site_ranges.transpose() |
|
2029 | 1853 |
if i == j: |
2030 | 1854 |
which_term = self._onsite_term_by_site_id[i] |
2031 | 1855 |
(w, _), (off, _) = self.subgraphs[self.terms[which_term].subgraph] |
... | ... |
@@ -2036,6 +1860,15 @@ class _VectorizedFinalizedBuilderMixin(_FinalizedBuilderMixin): |
2036 | 1860 |
return onsite[0] |
2037 | 1861 |
else: |
2038 | 1862 |
edge_id = self.graph.first_edge_id(i, j) |
1863 |
+ # Map sites in previous cell to fundamental domain; vectorized |
|
1864 |
+ # infinite systems only store sites in the FD. We already know |
|
1865 |
+ # that this hopping is between unit cells because this is encoded |
|
1866 |
+ # in the edge_id and term id. |
|
1867 |
+ # Using 'is_infinite' is a bit of a hack, but 'hamiltonian' is |
|
1868 |
+ # deprecated anyway, and refactoring everything to avoid this check |
|
1869 |
+ # is not worth it. |
|
1870 |
+ if system.is_infinite(self): |
|
1871 |
+ i, j = i % self.cell_size, j % self.cell_size |
|
2039 | 1872 |
which_term = self._hopping_term_by_edge_id[edge_id] |
2040 | 1873 |
herm_conj = which_term < 0 |
2041 | 1874 |
if herm_conj: |
... | ... |
@@ -2087,10 +1920,15 @@ class _VectorizedFinalizedBuilderMixin(_FinalizedBuilderMixin): |
2087 | 1920 |
to_family = self.site_arrays[to_which].family |
2088 | 1921 |
to_tags = self.site_arrays[to_which].tags |
2089 | 1922 |
to_site_array = SiteArray(to_family, to_tags[to_off]) |
1923 |
+ site_arrays = (to_site_array,) |
|
2090 | 1924 |
if not is_onsite: |
2091 | 1925 |
from_family = self.site_arrays[from_which].family |
2092 | 1926 |
from_tags = self.site_arrays[from_which].tags |
2093 |
- from_site_array = SiteArray(from_family, from_tags[from_off]) |
|
1927 |
+ from_site_array = self.symmetry.act( |
|
1928 |
+ term.symmetry_element, |
|
1929 |
+ SiteArray(from_family, from_tags[from_off]) |
|
1930 |
+ ) |
|
1931 |
+ site_arrays = (to_site_array, from_site_array) |
|
2094 | 1932 |
|
2095 | 1933 |
# Construct args from params |
2096 | 1934 |
if params: |
... | ... |
@@ -2107,20 +1945,10 @@ class _VectorizedFinalizedBuilderMixin(_FinalizedBuilderMixin): |
2107 | 1945 |
', '.join(map('"{}"'.format, missing))) |
2108 | 1946 |
raise TypeError(''.join(msg)) |
2109 | 1947 |
|
2110 |
- if is_onsite: |
|
2111 |
- try: |
|
2112 |
- ham = val(to_site_array, *args) |
|
2113 |
- except Exception as exc: |
|
2114 |
- _raise_user_error(exc, val) |
|
2115 |
- else: |
|
2116 |
- try: |
|
2117 |
- ham = val( |
|
2118 |
- *self.symmetry.to_fd( |
|
2119 |
- to_site_array, |
|
2120 |
- from_site_array), |
|
2121 |
- *args) |
|
2122 |
- except Exception as exc: |
|
2123 |
- _raise_user_error(exc, val) |
|
1948 |
+ try: |
|
1949 |
+ ham = val(*site_arrays, *args) |
|
1950 |
+ except Exception as exc: |
|
1951 |
+ _raise_user_error(exc, val) |
|
2124 | 1952 |
|
2125 | 1953 |
expected_shape = ( |
2126 | 1954 |
len(to_site_array), |
... | ... |
@@ -2250,8 +2078,8 @@ class FiniteSystem(_FinalizedBuilderMixin, system.FiniteSystem): |
2250 | 2078 |
|
2251 | 2079 |
graph = _make_graph(builder.H, id_by_site) |
2252 | 2080 |
|
2253 |
- finalized_leads, lead_interfaces, lead_paddings =\ |
|
2254 |
- _finalize_leads(builder.leads, id_by_site) |
|
2081 |
+ finalized_leads, lead_interfaces, lead_paddings = _finalize_leads( |
|
2082 |
+ builder.leads, id_by_site) |
|
2255 | 2083 |
|
2256 | 2084 |
# Because many onsites/hoppings share the same (value, parameter) |
2257 | 2085 |
# pairs, we keep them in a cache so that we only store a given pair |
... | ... |
@@ -2485,9 +2313,15 @@ def _make_onsite_terms(builder, sites, site_arrays, term_offset): |
2485 | 2313 |
err if isinstance(err, Exception) else None |
2486 | 2314 |
for err in onsite_term_parameters |
2487 | 2315 |
] |
2316 |
+ |
|
2317 |
+ # We store sites in the fundamental domain, so the symmetry element |
|
2318 |
+ # is the same for all onsite terms; it's just the identity. |
|
2319 |
+ identity = ta.array([0] * builder.symmetry.num_directions) |
|
2320 |
+ |
|
2488 | 2321 |
onsite_terms = [ |
2489 | 2322 |
system.Term( |
2490 | 2323 |
subgraph=term_offset + i, |
2324 |
+ symmetry_element=identity, |
|
2491 | 2325 |
hermitian=False, |
2492 | 2326 |
parameters=( |
2493 | 2327 |
params if not isinstance(params, Exception) else None |
... | ... |
@@ -2511,6 +2345,11 @@ def _make_hopping_terms(builder, graph, sites, site_arrays, cell_size, term_offs |
2511 | 2345 |
# Maps hopping edge IDs to the number of the term that the hopping |
2512 | 2346 |
# is a part of. For Hermitian conjugate hoppings "-term_number -1" |
2513 | 2347 |
# is stored instead. |
2348 |
+ # |
|
2349 |
+ # NOTE: 'graph' contains site indices >= cell_size, however 'sites' |
|
2350 |
+ # and 'site_arrays' only contain information about the sites |
|
2351 |
+ # in the fundamental domain. |
|
2352 |
+ sym = builder.symmetry |
|
2514 | 2353 |
|
2515 | 2354 |
site_offsets = np.cumsum([0] + [len(sa) for sa in site_arrays]) |
2516 | 2355 |
|
... | ... |
@@ -2523,11 +2362,27 @@ def _make_hopping_terms(builder, graph, sites, site_arrays, cell_size, term_offs |
2523 | 2362 |
hopping_to_term_nr = collections.OrderedDict() |
2524 | 2363 |
_hopping_term_by_edge_id = [] |
2525 | 2364 |
for tail, head in graph: |
2526 |
- tail_site, head_site = sites[tail], sites[head] |
|
2527 |
- if tail >= cell_size: |
|
2528 |
- # The tail belongs to the previous domain. Find the |
|
2529 |
- # corresponding hopping with the tail in the fund. domain. |
|
2530 |
- tail_site, head_site = builder.symmetry.to_fd(tail_site, head_site) |
|
2365 |
+ # map 'tail' and 'head' so that they are both in the FD, and |
|
2366 |
+ # assign the symmetry element to apply to 'sites[head]' |
|
2367 |
+ # to make the actual hopping. Here we assume that the symmetry |
|
2368 |
+ # may only be NoSymmetry or 1D TranslationalSymmetry. |
|
2369 |
+ if isinstance(sym, NoSymmetry): |
|
2370 |
+ tail_site = sites[tail] |
|
2371 |
+ head_site = sites[head] |
|
2372 |
+ sym_el = ta.array([]) |
|
2373 |
+ else: |
|
2374 |
+ if tail < cell_size and head < cell_size: |
|
2375 |
+ sym_el = ta.array([0]) |
|
2376 |
+ elif head >= cell_size: |
|
2377 |
+ assert tail < cell_size |
|
2378 |
+ head = head - cell_size |
|
2379 |
+ sym_el = ta.array([-1]) |
|
2380 |
+ else: |
|
2381 |
+ tail = tail - cell_size |
|
2382 |
+ sym_el = ta.array([1]) |
|
2383 |
+ tail_site = sites[tail] |
|
2384 |
+ head_site = sym.act(sym_el, sites[head]) |
|
2385 |
+ |
|
2531 | 2386 |
val = builder._get_edge(tail_site, head_site) |
2532 | 2387 |
herm_conj = val is Other |
2533 | 2388 |
if herm_conj: |
... | ... |
@@ -2538,11 +2393,11 @@ def _make_hopping_terms(builder, graph, sites, site_arrays, cell_size, term_offs |
2538 | 2393 |
head_site_array = bisect.bisect(site_offsets, head) - 1 |
2539 | 2394 |
# "key" uniquely identifies a hopping term. |
2540 | 2395 |
if const_val: |
2541 |
- key = (None, tail_site_array, head_site_array) |
|
2396 |
+ key = (None, sym_el, tail_site_array, head_site_array) |
|
2542 | 2397 |
else: |
2543 |
- key = (id(val), tail_site_array, head_site_array) |
|
2398 |
+ key = (id(val), sym_el, tail_site_array, head_site_array) |
|
2544 | 2399 |
if herm_conj: |
2545 |
- key = (key[0], head_site_array, tail_site_array) |
|
2400 |
+ key = (key[0], -sym_el, head_site_array, tail_site_array) |
|
2546 | 2401 |
|
2547 | 2402 |
if key not in hopping_to_term_nr: |
2548 | 2403 |
# start a new term |
... | ... |
@@ -2584,7 +2439,7 @@ def _make_hopping_terms(builder, graph, sites, site_arrays, cell_size, term_offs |
2584 | 2439 |
site_arrays[from_sa].family.norbs, |
2585 | 2440 |
), |
2586 | 2441 |
) |
2587 |
- for (_, to_sa, from_sa), val in |
|
2442 |
+ for (_, _, to_sa, from_sa), val in |
|
2588 | 2443 |
zip(hopping_to_term_nr.keys(), hopping_term_values) |
2589 | 2444 |
] |
2590 | 2445 |
# Sort the hoppings in each term, and also sort the values |
... | ... |
@@ -2595,8 +2450,8 @@ def _make_hopping_terms(builder, graph, sites, site_arrays, cell_size, term_offs |
2595 | 2450 |
zip(hopping_subgraphs, hopping_term_values))) |
2596 | 2451 |
# Store site array index and site offsets rather than sites themselves |
2597 | 2452 |
tmp = [] |
2598 |
- for (_, tail_which, head_which), h in zip(hopping_to_term_nr, |
|
2599 |
- hopping_subgraphs): |
|
2453 |
+ for (_, _, tail_which, head_which), h in zip(hopping_to_term_nr, |
|
2454 |
+ hopping_subgraphs): |
|
2600 | 2455 |
start = (site_offsets[tail_which], site_offsets[head_which]) |
2601 | 2456 |
# Transpose to get a pair of arrays rather than array of pairs |
2602 | 2457 |
# We use the fact that the underlying array is stored in |
... | ... |
@@ -2613,15 +2468,20 @@ def _make_hopping_terms(builder, graph, sites, site_arrays, cell_size, term_offs |
2613 | 2468 |
err if isinstance(err, Exception) else None |
2614 | 2469 |
for err in hopping_term_parameters |
2615 | 2470 |
] |
2471 |
+ term_symmetry_elements = [ |
|
2472 |
+ sym_el for (_, sym_el, *_) in hopping_to_term_nr.keys() |
|
2473 |
+ ] |
|
2616 | 2474 |
hopping_terms = [ |
2617 | 2475 |
system.Term( |
2618 | 2476 |
subgraph=term_offset + i, |
2477 |
+ symmetry_element=sym_el, |
|
2619 | 2478 |
hermitian=True, # Builders are always Hermitian |
2620 | 2479 |
parameters=( |
2621 | 2480 |
params if not isinstance(params, Exception) else None |
2622 | 2481 |
), |
2623 | 2482 |
) |
2624 |
- for i, params in enumerate(hopping_term_parameters) |
|
2483 |
+ for i, (sym_el, params) in |
|
2484 |
+ enumerate(zip(term_symmetry_elements, hopping_term_parameters)) |
|
2625 | 2485 |
] |
2626 | 2486 |
_hopping_term_by_edge_id = np.array(_hopping_term_by_edge_id) |
2627 | 2487 |
|
... | ... |
@@ -2662,21 +2522,21 @@ class FiniteVectorizedSystem(_VectorizedFinalizedBuilderMixin, system.FiniteVect |
2662 | 2522 |
|
2663 | 2523 |
graph = _make_graph(builder.H, id_by_site) |
2664 | 2524 |
|
2665 |
- finalized_leads, lead_interfaces, lead_paddings =\ |
|
2666 |
- _finalize_leads(builder.leads, id_by_site) |
|
2525 |
+ finalized_leads, lead_interfaces, lead_paddings = _finalize_leads( |
|
2526 |
+ builder.leads, id_by_site) |
|
2667 | 2527 |
|
2668 | 2528 |
del id_by_site # cleanup due to large size |
2669 | 2529 |
|
2670 | 2530 |
site_arrays = _make_site_arrays(builder.H) |
2671 | 2531 |
|
2672 | 2532 |
(onsite_subgraphs, onsite_terms, onsite_term_values, |
2673 |
- onsite_term_errors, _onsite_term_by_site_id) =\ |
|
2674 |
- _make_onsite_terms(builder, sites, site_arrays, term_offset=0) |
|
2533 |
+ onsite_term_errors, _onsite_term_by_site_id) = _make_onsite_terms( |
|
2534 |
+ builder, sites, site_arrays, term_offset=0) |
|
2675 | 2535 |
|
2676 | 2536 |
(hopping_subgraphs, hopping_terms, hopping_term_values, |
2677 |
- hopping_term_errors, _hopping_term_by_edge_id) =\ |
|
2678 |
- _make_hopping_terms(builder, graph, sites, site_arrays, |
|
2679 |
- len(sites), term_offset=len(onsite_terms)) |
|
2537 |
+ hopping_term_errors, _hopping_term_by_edge_id) = _make_hopping_terms( |
|
2538 |
+ builder, graph, sites, site_arrays, len(sites), |
|
2539 |
+ term_offset=len(onsite_terms)) |
|
2680 | 2540 |
|
2681 | 2541 |
# Construct the combined onsite/hopping term datastructures |
2682 | 2542 |
subgraphs = tuple(onsite_subgraphs) + tuple(hopping_subgraphs) |
... | ... |
@@ -2834,8 +2694,8 @@ class InfiniteSystem(_FinalizedBuilderMixin, system.InfiniteSystem): |
2834 | 2694 |
sym = builder.symmetry |
2835 | 2695 |
assert sym.num_directions == 1 |
2836 | 2696 |
|
2837 |
- lsites_with, lsites_without, interface =\ |
|
2838 |
- _make_lead_sites(builder, interface_order) |
|
2697 |
+ lsites_with, lsites_without, interface = _make_lead_sites( |
|
2698 |
+ builder, interface_order) |
|
2839 | 2699 |
cell_size = len(lsites_with) + len(lsites_without) |
2840 | 2700 |
|
2841 | 2701 |
# we previously sorted the interface, so don't sort it again |
... | ... |
@@ -2943,12 +2803,13 @@ class InfiniteVectorizedSystem(_VectorizedFinalizedBuilderMixin, system.Infinite |
2943 | 2803 |
assert sym.num_directions == 1 |
2944 | 2804 |
assert builder.vectorize |
2945 | 2805 |
|
2946 |
- lsites_with, lsites_without, interface =\ |
|
2947 |
- _make_lead_sites(builder, interface_order) |
|
2806 |
+ lsites_with, lsites_without, interface = _make_lead_sites( |
|
2807 |
+ builder, interface_order) |
|
2948 | 2808 |
cell_size = len(lsites_with) + len(lsites_without) |
2949 | 2809 |
|
2950 | 2810 |
|
2951 |
- sites = lsites_with + lsites_without + interface |
|
2811 |
+ fd_sites = lsites_with + lsites_without |
|
2812 |
+ sites = fd_sites + interface |
|
2952 | 2813 |
id_by_site = {} |
2953 | 2814 |
for site_id, site in enumerate(sites): |
2954 | 2815 |
id_by_site[site] = site_id |
... | ... |
@@ -2967,25 +2828,24 @@ class InfiniteVectorizedSystem(_VectorizedFinalizedBuilderMixin, system.Infinite |
2967 | 2828 |
del id_by_site # cleanup due to large size |
2968 | 2829 |
|
2969 | 2830 |
# In order to conform to the kwant.system.InfiniteVectorizedSystem |
2970 |
- # interface we need to put the sites that connect to the previous |
|
2971 |
- # cell *first*, then the sites that do not couple to the previous |
|
2972 |
- # cell, then the sites in the previous cell. Because sites in |
|
2973 |
- # a SiteArray are sorted by tag this means that the sites in these |
|
2974 |
- # 3 different sets need to be in different SiteArrays. |
|
2831 |
+ # interface we need to put the interface sites (which have hoppings |
|
2832 |
+ # to the next unit cell) before non-interface sites. |
|
2833 |
+ # Note that we do not *store* the interface sites of the previous |
|
2834 |
+ # unit cell, but we still need to *handle* site indices >= cell_size |
|
2835 |
+ # because those are used e.g. in the graph. |
|
2975 | 2836 |
site_arrays = ( |
2976 | 2837 |
_make_site_arrays(lsites_with) |
2977 | 2838 |
+ _make_site_arrays(lsites_without) |
2978 |
- + _make_site_arrays(interface) |
|
2979 | 2839 |
) |
2980 | 2840 |
|
2981 | 2841 |
(onsite_subgraphs, onsite_terms, onsite_term_values, |
2982 |
- onsite_term_errors, _onsite_term_by_site_id) =\ |
|
2983 |
- _make_onsite_terms(builder, sites, site_arrays, term_offset=0) |
|
2842 |
+ onsite_term_errors, _onsite_term_by_site_id) = _make_onsite_terms( |
|
2843 |
+ builder, fd_sites, site_arrays, term_offset=0) |
|
2984 | 2844 |
|
2985 | 2845 |
(hopping_subgraphs, hopping_terms, hopping_term_values, |
2986 |
- hopping_term_errors, _hopping_term_by_edge_id) =\ |
|
2987 |
- _make_hopping_terms(builder, graph, sites, site_arrays, |
|
2988 |
- cell_size, term_offset=len(onsite_terms)) |
|
2846 |
+ hopping_term_errors, _hopping_term_by_edge_id) = _make_hopping_terms( |
|
2847 |
+ builder, graph, fd_sites, site_arrays, cell_size, |
|
2848 |
+ term_offset=len(onsite_terms)) |
|
2989 | 2849 |
|
2990 | 2850 |
# Construct the combined onsite/hopping term datastructures |
2991 | 2851 |
subgraphs = tuple(onsite_subgraphs) + tuple(hopping_subgraphs) |
... | ... |
@@ -3001,8 +2861,12 @@ class InfiniteVectorizedSystem(_VectorizedFinalizedBuilderMixin, system.Infinite |
3001 | 2861 |
parameters = frozenset(parameters) |
3002 | 2862 |
|
3003 | 2863 |
self.site_arrays = site_arrays |
3004 |
- self.sites = _Sites(self.site_arrays) |
|
3005 |
- self.id_by_site = _IdBySite(self.site_arrays) |
|
2864 |
+ # 'sites' and 'id_by_site' have to be backwards compatible with the |
|
2865 |
+ # unvectorized interface, so we have to be able to index the interface |
|
2866 |
+ # sites in the previous unit cell also |
|
2867 |
+ _extended_site_arrays = self.site_arrays + _make_site_arrays(interface) |
|
2868 |
+ self.sites = _Sites(_extended_site_arrays) |
|
2869 |
+ self.id_by_site = _IdBySite(_extended_site_arrays) |
|
3006 | 2870 |
self.graph = graph |
3007 | 2871 |
self.subgraphs = subgraphs |
3008 | 2872 |
self.terms = terms |
... | ... |
@@ -18,6 +18,7 @@ import sympy |
18 | 18 |
|
19 | 19 |
import kwant.lattice |
20 | 20 |
import kwant.builder |
21 |
+import kwant.system |
|
21 | 22 |
|
22 | 23 |
import kwant.continuum |
23 | 24 |
import kwant.continuum._common |
... | ... |
@@ -144,7 +145,7 @@ def discretize_landau(hamiltonian, N, momenta=None, grid_spacing=1): |
144 | 145 |
if _has_coordinate(normal_coordinate, hamiltonian): |
145 | 146 |
sym = kwant.lattice.TranslationalSymmetry([grid_spacing, 0]) |
146 | 147 |
else: |
147 |
- sym = kwant.builder.NoSymmetry() |
|
148 |
+ sym = kwant.system.NoSymmetry() |
|
148 | 149 |
lat = LandauLattice(grid_spacing, norbs=norbs) |
149 | 150 |
syst = kwant.Builder(sym) |
150 | 151 |
|
... | ... |
@@ -13,8 +13,8 @@ import sympy |
13 | 13 |
import pytest |
14 | 14 |
import itertools |
15 | 15 |
|
16 |
-import kwant.builder |
|
17 | 16 |
import kwant.lattice |
17 |
+import kwant.system |
|
18 | 18 |
|
19 | 19 |
from .._common import position_operators, momentum_operators, sympify |
20 | 20 |
from ..landau_levels import ( |
... | ... |
@@ -118,7 +118,7 @@ def test_discretize_landau(): |
118 | 118 |
# test a basic Hamiltonian with no normal coordinate dependence |
119 | 119 |
syst = discretize_landau("k_x**2 + k_y**2", N=n_levels) |
120 | 120 |
lat = LandauLattice(1, norbs=1) |
121 |
- assert isinstance(syst.symmetry, kwant.builder.NoSymmetry) |
|
121 |
+ assert isinstance(syst.symmetry, kwant.system.NoSymmetry) |
|
122 | 122 |
syst = syst.finalized() |
123 | 123 |
assert set(syst.sites) == {lat(0, j) for j in range(n_levels)} |
124 | 124 |
assert np.allclose( |
... | ... |
@@ -153,7 +153,7 @@ class Polyatomic: |
153 | 153 |
algorithm finds and yields all the lattice sites inside the specified |
154 | 154 |
shape starting from the specified position. |
155 | 155 |
|
156 |
- A `~kwant.builder.Symmetry` or `~kwant.builder.Builder` may be passed as |
|
156 |
+ A `~kwant.system.Symmetry` or `~kwant.builder.Builder` may be passed as |
|
157 | 157 |
sole argument when calling the function returned by this method. This |
158 | 158 |
will restrict the flood-fill to the fundamental domain of the symmetry |
159 | 159 |
(or the builder's symmetry). Note that unless the shape function has |
... | ... |
@@ -174,8 +174,8 @@ class Polyatomic: |
174 | 174 |
Site = system.Site |
175 | 175 |
|
176 | 176 |
if symmetry is None: |
177 |
- symmetry = builder.NoSymmetry() |
|
178 |
- elif not isinstance(symmetry, builder.Symmetry): |
|
177 |
+ symmetry = system.NoSymmetry() |
|
178 |
+ elif not isinstance(symmetry, system.Symmetry): |
|
179 | 179 |
symmetry = symmetry.symmetry |
180 | 180 |
|
181 | 181 |
def fd_site(lat, tag): |
... | ... |
@@ -259,7 +259,7 @@ class Polyatomic: |
259 | 259 |
center = ta.array(center, float) |
260 | 260 |
|
261 | 261 |
def wire_sites(sym): |
262 |
- if not isinstance(sym, builder.Symmetry): |
|
262 |
+ if not isinstance(sym, system.Symmetry): |
|
263 | 263 |
sym = sym.symmetry |
264 | 264 |
if not isinstance(sym, TranslationalSymmetry): |
265 | 265 |
raise ValueError('wire shape only works with ' |
... | ... |
@@ -514,7 +514,7 @@ class Monatomic(system.SiteFamily, Polyatomic): |
514 | 514 |
# The following class is designed such that it should avoid floating |
515 | 515 |
# point precision issues. |
516 | 516 |
|
517 |
-class TranslationalSymmetry(builder.Symmetry): |
|
517 |
+class TranslationalSymmetry(system.Symmetry): |
|
518 | 518 |
"""A translational symmetry defined in real space. |
519 | 519 |
|
520 | 520 |
An alias exists for this common name: ``kwant.TranslationalSymmetry``. |
... | ... |
@@ -568,7 +568,7 @@ class TranslationalSymmetry(builder.Symmetry): |
568 | 568 |
return TranslationalSymmetry(*ta.dot(generators, self.periods)) |
569 | 569 |
|
570 | 570 |
def has_subgroup(self, other): |
571 |
- if isinstance(other, builder.NoSymmetry): |
|
571 |
+ if isinstance(other, system.NoSymmetry): |
|
572 | 572 |
return True |
573 | 573 |
elif not isinstance(other, TranslationalSymmetry): |
574 | 574 |
raise ValueError("Unknown symmetry type.") |
... | ... |
@@ -718,8 +718,9 @@ class TranslationalSymmetry(builder.Symmetry): |
718 | 718 |
raise ValueError("must provide a single group element when " |
719 | 719 |
"acting on single sites.") |
720 | 720 |
if (len(element.shape) == 1 and not is_site): |
721 |
- raise ValueError("must provide a sequence of group elements " |
|
722 |
- "when acting on site arrays.") |
|
721 |
+ # We can act on a whole SiteArray with a single group element |
|
722 |
+ # using numpy broadcasting. |
|
723 |
+ element = element.reshape(1, -1) |
|
723 | 724 |
m_part = self._get_site_family_data(a.family)[0] |
724 | 725 |
try: |
725 | 726 |
delta = array_mod.dot(m_part, element) |
... | ... |
@@ -9,7 +9,7 @@ |
9 | 9 |
"""Low-level interface of systems""" |
10 | 10 |
|
11 | 11 |
__all__ = [ |
12 |
- 'Site', 'SiteArray', 'SiteFamily', |
|
12 |
+ 'Site', 'SiteArray', 'SiteFamily', 'Symmetry', 'NoSymmetry', |
|
13 | 13 |
'System', 'VectorizedSystem', 'FiniteSystem', 'FiniteVectorizedSystem', |
14 | 14 |
'InfiniteSystem', 'InfiniteVectorizedSystem', |
15 | 15 |
'is_finite', 'is_infinite', 'is_vectorized', |
... | ... |
@@ -22,6 +22,7 @@ from copy import copy |
22 | 22 |
from collections import namedtuple |
23 | 23 |
from functools import total_ordering, lru_cache |
24 | 24 |
import numpy as np |
25 |
+import tinyarray as ta |
|
25 | 26 |
from . import _system |
26 | 27 |
from ._common import deprecate_args, KwantDeprecationWarning |
27 | 28 |
|
... | ... |
@@ -269,6 +270,180 @@ class SiteFamily: |
269 | 270 |
'site_family((1, 2))!') |
270 | 271 |
return Site(self, tag) |
271 | 272 |
|
273 |
+ |
|
274 |
+################ Symmetries |
|
275 |
+ |
|
276 |
+class Symmetry(metaclass=abc.ABCMeta): |
|
277 |
+ """Abstract base class for spatial symmetries. |
|
278 |
+ |
|
279 |
+ Many physical systems possess a discrete spatial symmetry, which results in |
|
280 |
+ special properties of these systems. This class is the standard way to |
|
281 |
+ describe discrete spatial symmetries in Kwant. An instance of this class |
|
282 |
+ can be passed to a `Builder` instance at its creation. The most important |
|
283 |
+ kind of symmetry is translational symmetry, used to define scattering |
|
284 |
+ leads. |
|
285 |
+ |
|
286 |
+ Each symmetry has a fundamental domain -- a set of sites and hoppings, |
|
287 |
+ generating all the possible sites and hoppings upon action of symmetry |
|
288 |
+ group elements. A class derived from `Symmetry` has to implement mapping |
|
289 |
+ of any site or hopping into the fundamental domain, applying a symmetry |
|
290 |
+ group element to a site or a hopping, and a method `which` to determine the |
|
291 |
+ group element bringing some site from the fundamental domain to the |
|
292 |
+ requested one. Additionally, it has to have a property `num_directions` |
|
293 |
+ returning the number of independent symmetry group generators (number of |
|
294 |
+ elementary periods for translational symmetry). |
|
295 |
+ |
|
296 |
+ A ``ValueError`` must be raised by the symmetry class whenever a symmetry |
|
297 |
+ is used together with sites whose site family is not compatible with it. A |
|
298 |
+ typical example of this is when the vector defining a translational |
|
299 |
+ symmetry is not a lattice vector. |
|
300 |
+ |
|
301 |
+ The type of the domain objects as handled by the methods of this class is |
|
302 |
+ not specified. The only requirement is that it must support the unary |
|
303 |
+ minus operation. The reference implementation of `to_fd()` is hence |
|
304 |
+ `self.act(-self.which(a), a, b)`. |
|
305 |
+ """ |
|
306 |
+ |
|
307 |
+ @abc.abstractproperty |
|
308 |
+ def num_directions(self): |
|
309 |
+ """Number of elementary periods of the symmetry.""" |
|
310 |
+ pass |
|
311 |
+ |
|
312 |
+ @abc.abstractmethod |
|
313 |
+ def which(self, site): |
|
314 |
+ """Calculate the domain of the site. |
|
315 |
+ |
|
316 |
+ Parameters |
|
317 |
+ ---------- |
|
318 |
+ site : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
319 |
+ |
|
320 |
+ Returns |
|
321 |
+ ------- |
|
322 |
+ group_element : tuple or sequence of tuples |
|
323 |
+ A single tuple if ``site`` is a Site, or a sequence of tuples if |
|
324 |
+ ``site`` is a SiteArray. The group element(s) whose action |
|
325 |
+ on a certain site(s) from the fundamental domain will result |
|
326 |
+ in the given ``site``. |
|
327 |
+ """ |
|
328 |
+ pass |
|
329 |
+ |
|
330 |
+ @abc.abstractmethod |
|
331 |
+ def act(self, element, a, b=None): |
|
332 |
+ """Act with symmetry group element(s) on site(s) or hopping(s). |
|
333 |
+ |
|
334 |
+ Parameters |
|
335 |
+ ---------- |
|
336 |
+ element : tuple or sequence of tuples |
|
337 |
+ Group element(s) with which to act on the provided site(s) |
|
338 |
+ or hopping(s) |
|
339 |
+ a, b : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
340 |
+ If Site then ``element`` is a single tuple, if SiteArray then |
|
341 |
+ ``element`` is a single tuple or a sequence of tuples. |
|
342 |
+ If only ``a`` is provided then ``element`` acts on the site(s) |
|
343 |
+ of ``a``. If ``b`` is also provided then ``element`` acts |
|
344 |
+ on the hopping(s) ``(a, b)``. |
|
345 |
+ """ |
|
346 |
+ pass |
|
347 |
+ |
|
348 |
+ def to_fd(self, a, b=None): |
|
349 |
+ """Map a site or hopping to the fundamental domain. |
|
350 |
+ |
|
351 |
+ Parameters |
|
352 |
+ ---------- |
|
353 |
+ a, b : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
354 |
+ |
|
355 |
+ If ``b`` is None, return a site equivalent to ``a`` within the |
|
356 |
+ fundamental domain. Otherwise, return a hopping equivalent to ``(a, |
|
357 |
+ b)`` but where the first element belongs to the fundamental domain. |
|
358 |
+ |
|
359 |
+ Equivalent to `self.act(-self.which(a), a, b)`. |
|
360 |
+ """ |
|
361 |
+ return self.act(-self.which(a), a, b) |
|
362 |
+ |
|
363 |
+ def in_fd(self, site): |
|
364 |
+ """Tell whether ``site`` lies within the fundamental domain. |
|
365 |
+ |
|
366 |
+ Parameters |
|
367 |
+ ---------- |
|
368 |
+ site : `~kwant.system.Site` or `~kwant.system.SiteArray` |
|
369 |
+ |
|
370 |
+ Returns |
|
371 |
+ ------- |
|
372 |
+ in_fd : bool or sequence of bool |
|
373 |
+ single bool if ``site`` is a Site, or a sequence of |
|
374 |
+ bool if ``site`` is a SiteArray. In the latter case |
|
375 |
+ we return whether each site in the SiteArray is in |
|
376 |
+ the fundamental domain. |
|
377 |
+ """ |
|
378 |
+ if isinstance(site, Site): |
|
379 |
+ for d in self.which(site): |
|
380 |
+ if d != 0: |
|
381 |
+ return False |
|
382 |
+ return True |
|
383 |
+ elif isinstance(site, SiteArray): |
|
384 |
+ which = self.which(site) |
|
385 |
+ return np.logical_and.reduce(which != 0, axis=1) |
|
386 |
+ else: |
|
387 |
+ raise TypeError("'site' must be a Site or SiteArray") |
|
388 |
+ |
|
389 |
+ @abc.abstractmethod |
|
390 |
+ def subgroup(self, *generators): |
|
391 |
+ """Return the subgroup generated by a sequence of group elements.""" |
|
392 |
+ pass |
|
393 |
+ |
|
394 |
+ @abc.abstractmethod |
|
395 |
+ def has_subgroup(self, other): |
|
396 |
+ """Test whether `self` has the subgroup `other`... |
|
397 |
+ |
|
398 |
+ or, in other words, whether `other` is a subgroup of `self`. The |
|
399 |
+ reason why this is the abstract method (and not `is_subgroup`) is that |
|
400 |
+ in general it's not possible for a subgroup to know its supergroups. |
|
401 |
+ |
|
402 |
+ """ |
|
403 |
+ pass |
|
404 |
+ |
|
405 |
+ |
|
406 |
+class NoSymmetry(Symmetry): |
|
407 |
+ """A symmetry with a trivial symmetry group.""" |
|
408 |
+ |
|
409 |
+ def __eq__(self, other): |
|
410 |
+ return isinstance(other, NoSymmetry) |
|
411 |
+ |
|
412 |
+ def __ne__(self, other): |
|
413 |
+ return not self.__eq__(other) |
|
414 |
+ |
|
415 |
+ def __repr__(self): |
|
416 |
+ return 'NoSymmetry()' |
|
417 |
+ |
|
418 |
+ @property |
|
419 |
+ def num_directions(self): |
|
420 |
+ return 0 |
|
421 |
+ |
|
422 |
+ periods = () |
|
423 |
+ |
|
424 |
+ _empty_array = ta.array((), int) |
|
425 |
+ |
|
426 |
+ def which(self, site): |
|
427 |
+ return self._empty_array |
|
428 |
+ |
|
429 |
+ def act(self, element, a, b=None): |
|
430 |
+ if element: |
|
431 |
+ raise ValueError('`element` must be empty for NoSymmetry.') |
|
432 |
+ return a if b is None else (a, b) |
|
433 |
+ |
|
434 |
+ def to_fd(self, a, b=None): |
|
435 |
+ return a if b is None else (a, b) |
|
436 |
+ |
|
437 |
+ def in_fd(self, site): |
|
438 |
+ return True |
|
439 |
+ |
|
440 |
+ def subgroup(self, *generators): |
|
441 |
+ if any(generators): |
|
442 |
+ raise ValueError('Generators must be empty for NoSymmetry.') |
|
443 |
+ return NoSymmetry(generators) |
|
444 |
+ |
|
445 |
+ def has_subgroup(self, other): |
|
446 |
+ return isinstance(other, NoSymmetry) |
|
272 | 447 |
|
273 | 448 |
|
274 | 449 |
################ Systems |
... | ... |
@@ -354,7 +529,7 @@ class System(metaclass=abc.ABCMeta): |
354 | 529 |
|
355 | 530 |
Term = namedtuple( |
356 | 531 |
"Term", |
357 |
- ["subgraph", "hermitian", "parameters"], |
|
532 |
+ ["subgraph", "symmetry_element", "hermitian", "parameters"], |
|
358 | 533 |
) |
359 | 534 |
|
360 | 535 |
|
... | ... |
@@ -363,6 +538,8 @@ class VectorizedSystem(System, metaclass=abc.ABCMeta): |
363 | 538 |
|
364 | 539 |
Attributes |
365 | 540 |
---------- |
541 |
+ symmetry : kwant.system.Symmetry |
|
542 |
+ The symmetry of the system. |
|
366 | 543 |
graph : kwant.graph.CGraph |
367 | 544 |
The system graph. |
368 | 545 |
subgraphs : sequence of tuples |
... | ... |
@@ -371,9 +548,11 @@ class VectorizedSystem(System, metaclass=abc.ABCMeta): |
371 | 548 |
indexed by 'idx1' and 'idx2'. |
372 | 549 |
terms : sequence of tuples |
373 | 550 |
Each tuple has the following structure: |
374 |
- (subgraph: int, hermitian: bool, parameters: List(str)) |
|
551 |
+ (subgraph: int, symmetry_element: tuple, hermitian: bool, parameters: List(str)) |
|
375 | 552 |
'subgraph' indexes 'subgraphs' and supplies the to/from sites of this |
376 |
- term. 'hermitian' is 'True' if the term needs its Hermitian |
|
553 |
+ term. 'symmetry_element' is the symmetry group element that should be |
|
554 |
+ applied to the 'to-sites' of this term. |
|
555 |
+ 'hermitian' is 'True' if the term needs its Hermitian |
|
377 | 556 |
conjugate to be added when evaluating the Hamiltonian, and 'parameters' |
378 | 557 |
contains a list of parameter names used when evaluating this term. |
379 | 558 |
site_arrays : sequence of SiteArray |
... | ... |
@@ -576,50 +755,7 @@ def is_finite(syst): |
576 | 755 |
|
577 | 756 |
|
578 | 757 |
class InfiniteSystemMixin(metaclass=abc.ABCMeta): |
579 |
- """Abstract infinite low-level system. |
|
580 |
- |
|
581 |
- An infinite system consists of an infinite series of identical cells. |
|
582 |
- Adjacent cells are connected by identical inter-cell hoppings. |
|
583 |
- |
|
584 |
- Attributes |
|
585 |
- ---------- |
|
586 |
- cell_size : integer |
|
587 |
- The number of sites in a single cell of the system. |
|
588 |
- |
|
589 |
- Notes |
|
590 |
- ----- |
|
591 |
- The system graph of an infinite systems contains a single cell, as well as |
|
592 |
- the part of the previous cell which is connected to it. The first |
|
593 |
- `cell_size` sites form one complete single cell. The remaining ``N`` sites |
|
594 |
- of the graph (``N`` equals ``graph.num_nodes - cell_size``) belong to the |
|
595 |
- previous cell. They are included so that hoppings between cells can be |
|
596 |
- represented. The N sites of the previous cell correspond to the first |
|
597 |
- ``N`` sites of the fully included cell. When an ``InfiniteSystem`` is used |
|
598 |
- as a lead, ``N`` acts also as the number of interface sites to which it |
|
599 |
- must be connected. |
|
600 |
- |
|
601 |
- The drawing shows three cells of an infinite system. Each cell consists |
|
602 |
- of three sites. Numbers denote sites which are included into the system |
|
603 |
- graph. Stars denote sites which are not included. Hoppings are included |
|
604 |
- in the graph if and only if they occur between two sites which are part of |
|
605 |
- the graph:: |
|
606 |
- |
|
607 |
- * 2 * |
|
608 |
- ... | | | ... |
|
609 |
- * 0 3 |
|
610 |
- |/|/| |
|
611 |
- *-1-4 |
|
612 |
- |
|
613 |
- <-- order of cells |
|
614 | 758 |
|
615 |
- The numbering of sites in the drawing is one of the two valid ones for that |
|
616 |
- infinite system. The other scheme has the numbers of site 0 and 1 |
|
617 |
- exchanged, as well as of site 3 and 4. |
|
618 |
- |
|
619 |
- Sites in the fundamental domain cell must belong to a different site array |
|
620 |
- than the sites in the previous cell. In the above example this means that |
|
621 |
- sites '(0, 1, 2)' and '(3, 4)' must belong to different site arrays. |
|
622 |
- """ |
|
623 | 759 |
@deprecate_args |
624 | 760 |
def modes(self, energy=0, args=(), *, params=None): |
625 | 761 |
"""Return mode decomposition of the lead |
... | ... |
@@ -704,6 +840,46 @@ class InfiniteSystemMixin(metaclass=abc.ABCMeta): |
704 | 840 |
|
705 | 841 |
|
706 | 842 |
class InfiniteSystem(System, InfiniteSystemMixin, metaclass=abc.ABCMeta): |
843 |
+ """Abstract infinite low-level system. |
|
844 |
+ |
|
845 |
+ An infinite system consists of an infinite series of identical cells. |
|
846 |
+ Adjacent cells are connected by identical inter-cell hoppings. |
|
847 |
+ |
|
848 |
+ Attributes |
|
849 |
+ ---------- |
|
850 |
+ cell_size : integer |
|
851 |
+ The number of sites in a single cell of the system. |
|
852 |
+ |
|
853 |
+ Notes |
|
854 |
+ ----- |
|
855 |
+ The system graph of an infinite systems contains a single cell, as well as |
|
856 |
+ the part of the previous cell which is connected to it. The first |
|
857 |
+ `cell_size` sites form one complete single cell. The remaining ``N`` sites |
|
858 |
+ of the graph (``N`` equals ``graph.num_nodes - cell_size``) belong to the |
|
859 |
+ previous cell. They are included so that hoppings between cells can be |
|
860 |
+ represented. The N sites of the previous cell correspond to the first |
|
861 |
+ ``N`` sites of the fully included cell. When an ``InfiniteSystem`` is used |
|
862 |
+ as a lead, ``N`` acts also as the number of interface sites to which it |
|
863 |
+ must be connected. |
|
864 |
+ |
|
865 |
+ The drawing shows three cells of an infinite system. Each cell consists |
|
866 |
+ of three sites. Numbers denote sites which are included into the system |
|
867 |
+ graph. Stars denote sites which are not included. Hoppings are included |
|
868 |
+ in the graph if and only if they occur between two sites which are part of |
|
869 |
+ the graph:: |
|
870 |
+ |
|
871 |
+ * 2 * |
|
872 |
+ ... | | | ... |
|
873 |
+ * 0 3 |
|
874 |
+ |/|/| |
|
875 |
+ *-1-4 |
|
876 |
+ |
|
877 |
+ <-- order of cells |
|
878 |
+ |
|
879 |
+ The numbering of sites in the drawing is one of the two valid ones for that |
|
880 |
+ infinite system. The other scheme has the numbers of site 0 and 1 |
|
881 |
+ exchanged, as well as of site 3 and 4. |
|
882 |
+ """ |
|
707 | 883 |
|
708 | 884 |
@deprecate_args |
709 | 885 |
def cell_hamiltonian(self, args=(), sparse=False, *, params=None): |
... | ... |
@@ -730,9 +906,46 @@ class InfiniteSystem(System, InfiniteSystemMixin, metaclass=abc.ABCMeta): |
730 | 906 |
|
731 | 907 |
|
732 | 908 |
class InfiniteVectorizedSystem(VectorizedSystem, InfiniteSystemMixin, metaclass=abc.ABCMeta): |
909 |
+ """Abstract vectorized infinite low-level system. |
|
910 |
+ |
|
911 |
+ An infinite system consists of an infinite series of identical cells. |
|
912 |
+ Adjacent cells are connected by identical inter-cell hoppings. |
|
913 |
+ |
|
914 |
+ Attributes |
|
915 |
+ ---------- |
|
916 |
+ cell_size : integer |
|
917 |
+ The number of sites in a single cell of the system. |
|
918 |
+ |
|
919 |
+ Notes |
|
920 |
+ ----- |
|
921 |
+ Unlike `~kwant.system.InfiniteSystem`, vectorized infinite systems do |
|
922 |
+ not explicitly store the sites in the previous unit cell; only the |
|
923 |
+ sites in the fundamental domain are stored. Nevertheless, the |
|
924 |
+ SiteArrays of `~kwant.system.InfiniteVectorizedSystem` are ordered |
|
925 |
+ in an analogous way, in order to facilitate the representation of |
|
926 |
+ inter-cell hoppings. The ordering is as follows. The *interface sites* |
|
927 |
+ of a unit cell are the sites that have hoppings to the *next* unit cell |
|
928 |
+ (along the symmetry direction). Interface sites are always in different |
|
929 |
+ SiteArrays than non-interface sites, i.e. the sites in a given SiteArray |
|
930 |
+ are either all interface sites, or all non-interface sites. |
|
931 |
+ The SiteArrays consisting of interface sites always appear *before* the |
|
932 |
+ SiteArrays consisting of non-interface sites in ``self.site_arrays``. |
|
933 |
+ This is backwards compatible with `kwant.system.InfiniteSystem`. |
|
934 |
+ |
|
935 |
+ For backwards compatibility, `~kwant.system.InfiniteVectorizedSystem` |
|
936 |
+ maintains a ``graph``, that includes nodes for the sites |
|
937 |
+ in the previous unit cell. |
|
938 |
+ """ |
|
733 | 939 |
cell_hamiltonian = _system.vectorized_cell_hamiltonian |
734 | 940 |
inter_cell_hopping = _system.vectorized_inter_cell_hopping |
735 | 941 |
|
942 |
+ def hamiltonian_submatrix(self, args=(), sparse=False, |
|
943 |
+ return_norb=False, *, params=None): |
|
944 |
+ raise ValueError( |
|
945 |
+ "'hamiltonian_submatrix' is not meaningful for infinite" |
|
946 |
+ "systems. Use 'cell_hamiltonian' or 'inter_cell_hopping." |
|
947 |
+ ) |
|
948 |
+ |
|
736 | 949 |
|
737 | 950 |
def is_infinite(syst): |
738 | 951 |
return isinstance(syst, (InfiniteSystem, InfiniteVectorizedSystem)) |
... | ... |
@@ -153,7 +153,7 @@ def test_site_families(): |
153 | 153 |
assert fam1 < fam2 # string '1' is lexicographically less than '2' |
154 | 154 |
|
155 | 155 |
|
156 |
-class VerySimpleSymmetry(builder.Symmetry): |
|
156 |
+class VerySimpleSymmetry(system.Symmetry): |
|
157 | 157 |
def __init__(self, period): |
158 | 158 |
self.period = period |
159 | 159 |
|
... | ... |
@@ -162,7 +162,7 @@ class VerySimpleSymmetry(builder.Symmetry): |
162 | 162 |
return 1 |
163 | 163 |
|
164 | 164 |
def has_subgroup(self, other): |
165 |
- if isinstance(other, builder.NoSymmetry): |
|
165 |
+ if isinstance(other, system.NoSymmetry): |
|
166 | 166 |
return True |
167 | 167 |
elif isinstance(other, VerySimpleSymmetry): |
168 | 168 |
return not other.period % self.period |
... | ... |
@@ -321,10 +321,10 @@ def random_hopping_integral(rng): |
321 | 321 |
|
322 | 322 |
|
323 | 323 |
def check_onsite(fsyst, sites, subset=False, check_values=True): |
324 |
- vectorized = isinstance(fsyst, (system.FiniteVectorizedSystem, system.InfiniteVectorizedSystem)) |
|
324 |
+ vectorized = system.is_vectorized(fsyst) |
|
325 | 325 |
|
326 | 326 |
if vectorized: |
327 |
- site_offsets = np.cumsum([0] + [len(s) for s in fsyst.site_arrays]) |
|
327 |
+ site_offsets, _, _ = fsyst.site_ranges.transpose() |
|
328 | 328 |
|
329 | 329 |
freq = {} |
330 | 330 |
for node in range(fsyst.graph.num_nodes): |
... | ... |
@@ -353,10 +353,10 @@ def check_onsite(fsyst, sites, subset=False, check_values=True): |
353 | 353 |
|
354 | 354 |
|
355 | 355 |
def check_hoppings(fsyst, hops): |
356 |
- vectorized = isinstance(fsyst, (system.FiniteVectorizedSystem, system.InfiniteVectorizedSystem)) |
|
356 |
+ vectorized = system.is_vectorized(fsyst) |
|
357 | 357 |
|
358 | 358 |
if vectorized: |
359 |
- site_offsets = np.cumsum([0] + [len(s) for s in fsyst.site_arrays]) |
|
359 |
+ site_offsets, _, _ = fsyst.site_ranges.transpose() |
|
360 | 360 |
|
361 | 361 |
assert fsyst.graph.num_edges == 2 * len(hops) |
362 | 362 |
for edge_id, edge in enumerate(fsyst.graph): |
... | ... |
@@ -366,6 +366,12 @@ def check_hoppings(fsyst, hops): |
366 | 366 |
|
367 | 367 |
if vectorized: |
368 | 368 |
term = fsyst._hopping_term_by_edge_id[edge_id] |
369 |
+ # Map sites in previous cell to fundamental domain; vectorized |
|
370 |
+ # infinite systems only store sites in the FD. We already know |
|
371 |
+ # that this hopping is between unit cells because this is encoded |
|
372 |
+ # in the edge_id and term id. |
|
373 |
+ if system.is_infinite(fsyst): |
|
374 |
+ i, j = i % fsyst.cell_size, j % fsyst.cell_size |
|
369 | 375 |
if term < 0: # Hermitian conjugate |
370 | 376 |
assert (head, tail) in hops |
371 | 377 |
else: |
... | ... |
@@ -464,7 +470,8 @@ def test_finalization(vectorize): |
464 | 470 |
assert tuple(fsyst.sites) == tuple(sorted(fam(*site) for site in sr_sites)) |
465 | 471 |
|
466 | 472 |
# Build lead from blueprint and test it. |
467 |
- lead = builder.Builder(kwant.TranslationalSymmetry((size, 0))) |
|
473 |
+ lead = builder.Builder(kwant.TranslationalSymmetry((size, 0)), |
|
474 |
+ vectorize=vectorize) |
|
468 | 475 |
for site, value in lead_sites.items(): |
469 | 476 |
shift = rng.randrange(-5, 6) * size |
470 | 477 |
site = site[0] + shift, site[1] |
... | ... |
@@ -757,10 +764,6 @@ def test_vectorized_hamiltonian_evaluation(): |
757 | 764 |
syst_simple[(lat(2, 1), lat(1, 1))] = hopping |
758 | 765 |
fsyst_simple = syst_simple.finalized() |
759 | 766 |
|
760 |
- assert np.allclose( |
|
761 |
- fsyst_vectorized.hamiltonian_submatrix(), |
|
762 |
- fsyst_simple.hamiltonian_submatrix(), |
|
763 |
- ) |
|
764 | 767 |
assert np.allclose( |
765 | 768 |
fsyst_vectorized.cell_hamiltonian(), |
766 | 769 |
fsyst_simple.cell_hamiltonian(), |
... | ... |
@@ -812,7 +815,7 @@ def test_vectorized_value_normalization(): |
812 | 815 |
|
813 | 816 |
|
814 | 817 |
@pytest.mark.parametrize("sym", [ |
815 |
- builder.NoSymmetry(), |
|
818 |
+ system.NoSymmetry(), |
|
816 | 819 |
kwant.TranslationalSymmetry([-1]), |
817 | 820 |
]) |
818 | 821 |
def test_vectorized_requires_norbs(sym): |
... | ... |
@@ -921,7 +924,7 @@ def test_fill(): |
921 | 924 |
## Test that copying a builder by "fill" preserves everything. |
922 | 925 |
for sym, func in [(kwant.TranslationalSymmetry(*np.diag([3, 4, 5])), |
923 | 926 |
lambda pos: True), |
924 |
- (builder.NoSymmetry(), |
|
927 |
+ (system.NoSymmetry(), |
|
925 | 928 |
lambda pos: ta.dot(pos, pos) < 17)]: |
926 | 929 |
cubic = kwant.lattice.general(ta.identity(3), norbs=1) |
927 | 930 |
|
... | ... |
@@ -1642,7 +1645,7 @@ def test_subs(): |
1642 | 1645 |
|
1643 | 1646 |
lat = kwant.lattice.chain(norbs=1) |
1644 | 1647 |
|
1645 |
- def make_system(sym=kwant.builder.NoSymmetry(), n=3): |
|
1648 |
+ def make_system(sym=system.NoSymmetry(), n=3): |
|
1646 | 1649 |
syst = kwant.Builder(sym) |
1647 | 1650 |
syst[(lat(i) for i in range(n))] = onsite |
1648 | 1651 |
syst[lat.neighbors()] = hopping |
... | ... |
@@ -11,7 +11,7 @@ from math import sqrt |
11 | 11 |
import numpy as np |
12 | 12 |
import tinyarray as ta |
13 | 13 |
from pytest import raises |
14 |
-from kwant import lattice, builder |
|
14 |
+from kwant import lattice, builder, system |
|
15 | 15 |
from kwant._common import ensure_rng |
16 | 16 |
import pytest |
17 | 17 |
|
... | ... |
@@ -285,7 +285,7 @@ def test_symmetry_has_subgroup(): |
285 | 285 |
## test whether actual subgroups are detected as such |
286 | 286 |
vecs = rng.randn(3, 3) |
287 | 287 |
sym1 = lattice.TranslationalSymmetry(*vecs) |
288 |
- ns = builder.NoSymmetry() |
|
288 |
+ ns = system.NoSymmetry() |
|
289 | 289 |
assert ns.has_subgroup(ns) |
290 | 290 |
assert sym1.has_subgroup(sym1) |
291 | 291 |
assert sym1.has_subgroup(ns) |