diff --git a/doc/source/images/ab_ring.py.diff b/doc/source/images/ab_ring.py.diff index da410f8559cc46ad4400f0da7cf2ea930771fac6..7f09a4b46989b118ecc31b84ad89e348445fedc9 100644 --- a/doc/source/images/ab_ring.py.diff +++ b/doc/source/images/ab_ring.py.diff @@ -8,9 +8,9 @@ def make_system(a=1, t=1.0, W=10, r1=10, r2=20): -@@ -38,12 +39,13 @@ - for hopping in lat.nearest: - sys[sys.possible_hoppings(*hopping)] = -t +@@ -37,12 +38,13 @@ + sys[lat.shape(ring, (0, r1 + 1))] = 4 * t + sys[lat.nearest] = -t - # In order to introduce a flux through the ring, we introduce a phase on - # the hoppings on the line cut through one of the arms. Since we want to @@ -28,7 +28,7 @@ return exp(1j * phi) def crosses_branchcut(hop): -@@ -81,6 +83,54 @@ +@@ -82,6 +84,50 @@ return sys @@ -40,16 +40,14 @@ + rsq = x**2 + y**2 + return ( r1**2 < rsq < r2**2) + sys[lat.shape(ring, (0, 11))] = 4 * t -+ for hopping in lat.nearest: -+ sys[sys.possible_hoppings(*hopping)] = -t ++ sys[lat.nearest] = -t + sym_lead0 = kwant.TranslationalSymmetry((-a, 0)) + lead0 = kwant.Builder(sym_lead0) + def lead_shape(pos): + (x, y) = pos + return (-1 < x < 1) and ( 0.5 * W < y < 1.5 * W ) + lead0[lat.shape(lead_shape, (0, W))] = 4 * t -+ for hopping in lat.nearest: -+ lead0[lead0.possible_hoppings(*hopping)] = -t ++ lead0[lat.nearest] = -t + lead1 = lead0.reversed() + sys.attach_lead(lead0) + sys.attach_lead(lead1) @@ -64,16 +62,14 @@ + rsq = x**2 + y**2 + return ( r1**2 < rsq < r2**2) + sys[lat.shape(ring, (0, 11))] = 4 * t -+ for hopping in lat.nearest: -+ sys[sys.possible_hoppings(*hopping)] = -t ++ sys[lat.nearest] = -t + sym_lead0 = kwant.TranslationalSymmetry((-a, 0)) + lead0 = kwant.Builder(sym_lead0) + def lead_shape(pos): + (x, y) = pos + return (-1 < x < 1) and ( -W/2 < y < W/2 ) + lead0[lat.shape(lead_shape, (0, 0))] = 4 * t -+ for hopping in lat.nearest: -+ lead0[lead0.possible_hoppings(*hopping)] = -t ++ lead0[lat.nearest] = -t + lead1 = lead0.reversed() + sys.attach_lead(lead0) + sys.attach_lead(lead1, lat(0, 0)) @@ -83,7 +79,7 @@ def plot_conductance(sys, energy, fluxes): # compute conductance -@@ -90,18 +140,29 @@ +@@ -91,18 +137,29 @@ smatrix = kwant.solve(sys, energy, kwargs={'phi': flux}) data.append(smatrix.transmission(1, 0)) @@ -118,7 +114,7 @@ # Finalize the system. sys = sys.finalized() -@@ -111,6 +172,15 @@ +@@ -112,6 +169,15 @@ for i in xrange(100)]) diff --git a/doc/source/images/graphene.py.diff b/doc/source/images/graphene.py.diff index facca27ff253f624db2b625366d3771b5a08997f..8443e7d186930bc2f4d3e52399652e4297d29cfa 100644 --- a/doc/source/images/graphene.py.diff +++ b/doc/source/images/graphene.py.diff @@ -8,16 +8,7 @@ # Define the graphene lattice -@@ -63,7 +64,7 @@ - return (-1 < x < 1) and (-0.4 * r < y < 0.4 * r) - - lead0 = kwant.Builder(sym0) -- lead0[graphene.shape(lead0_shape, (0, 0))] = -pot -+ lead0[graphene.shape(lead0_shape, (0, 0))] = - pot - for hopping in hoppings: - lead0[lead0.possible_hoppings(*hopping)] = -1 - -@@ -105,22 +106,40 @@ +@@ -102,22 +103,40 @@ smatrix = kwant.solve(sys, energy) data.append(smatrix.transmission(0, 1)) @@ -66,7 +57,7 @@ def main(): -@@ -133,17 +152,22 @@ +@@ -130,17 +149,22 @@ return 0 if site.group == a else 1 # Plot the closed system without leads. diff --git a/doc/source/images/quantum_well.py.diff b/doc/source/images/quantum_well.py.diff index d1f21475e83d17964b7f0f0a1b0c128d2852cca4..e5b45b0d3d2ab5dbfd22104dde3831b4f3a9731f 100644 --- a/doc/source/images/quantum_well.py.diff +++ b/doc/source/images/quantum_well.py.diff @@ -8,7 +8,7 @@ def make_system(a=1, t=1.0, W=10, L=30, L_well=10): -@@ -63,19 +64,25 @@ +@@ -61,19 +62,25 @@ smatrix = kwant.solve(sys, energy, kwargs={'pot': -welldepth}) data.append(smatrix.transmission(1, 0)) diff --git a/doc/source/reference/kwant.builder.rst b/doc/source/reference/kwant.builder.rst index 0ed06ff9892cc63bb879dde42e79a589955ea289..e31567ec644366988bb2f8a034895db37eccfc48 100644 --- a/doc/source/reference/kwant.builder.rst +++ b/doc/source/reference/kwant.builder.rst @@ -10,6 +10,7 @@ Types Builder Site + HoppingKind SimpleSiteGroup BuilderLead SelfEnergy diff --git a/doc/source/tutorial/ab_ring.py b/doc/source/tutorial/ab_ring.py index 5c39ce688210b82992a76b384152ed346e890d45..e2ec01d61925ae076cfeea79d8f2a32205bb9b82 100644 --- a/doc/source/tutorial/ab_ring.py +++ b/doc/source/tutorial/ab_ring.py @@ -38,8 +38,7 @@ def make_system(a=1, t=1.0, W=10, r1=10, r2=20): # and add the corresponding lattice points using the `shape`-function #HIDDEN_BEGIN_lcak sys[lat.shape(ring, (0, r1 + 1))] = 4 * t - for hopping in lat.nearest: - sys[sys.possible_hoppings(*hopping)] = -t + sys[lat.nearest] = -t #HIDDEN_END_lcak # In order to introduce a flux through the ring, we introduce a phase on @@ -54,13 +53,16 @@ def make_system(a=1, t=1.0, W=10, r1=10, r2=20): def crosses_branchcut(hop): ix0, iy0 = hop[0].tag - # possible_hoppings with the argument (1, 0) below + # builder.HoppingKind with the argument (1, 0) below # returns hoppings ordered as ((i+1, j), (i, j)) return iy0 < 0 and ix0 == 1 # ix1 == 0 then implied # Modify only those hopings in x-direction that cross the branch cut - sys[(hop for hop in sys.possible_hoppings((1, 0), lat, lat) - if crosses_branchcut(hop))] = fluxphase + def hops_across_cut(sys): + for hop in kwant.builder.HoppingKind((1, 0), lat, lat)(sys): + if crosses_branchcut(hop): + yield hop + sys[hops_across_cut] = fluxphase #HIDDEN_END_lvkt #### Define the leads. #### @@ -74,8 +76,7 @@ def make_system(a=1, t=1.0, W=10, r1=10, r2=20): return (-1 < x < 1) and (-W / 2 < y < W / 2) lead0[lat.shape(lead_shape, (0, 0))] = 4 * t - for hopping in lat.nearest: - lead0[lead0.possible_hoppings(*hopping)] = -t + lead0[lat.nearest] = -t #HIDDEN_END_qwgr # Then the lead to the right diff --git a/doc/source/tutorial/closed_system.py b/doc/source/tutorial/closed_system.py index d67c28992a2df393cf116a3ed7f32573399e83a4..a7bf7d2d9f48a224220884ffd19a3c10c7de29bc 100644 --- a/doc/source/tutorial/closed_system.py +++ b/doc/source/tutorial/closed_system.py @@ -43,9 +43,9 @@ def make_system(a=1, t=1.0, r=10): sys[lat.shape(circle, (0, 0))] = 4 * t # hoppings in x-direction - sys[sys.possible_hoppings((1, 0), lat, lat)] = hopx + sys[kwant.builder.HoppingKind((1, 0), lat, lat)] = hopx # hoppings in y-directions - sys[sys.possible_hoppings((0, 1), lat, lat)] = -t + sys[kwant.builder.HoppingKind((0, 1), lat, lat)] = -t # It's a closed system for a change, so no leads return sys diff --git a/doc/source/tutorial/graphene.py b/doc/source/tutorial/graphene.py index 201a380c4bd7362cbcb27ca10a1b9c0d52fbe16f..4d12e1d76fba80ff73a27adbb965bec40ab55c49 100644 --- a/doc/source/tutorial/graphene.py +++ b/doc/source/tutorial/graphene.py @@ -49,13 +49,12 @@ def make_system(r=10, w=2.0, pot=0.1): #HIDDEN_END_shzy # specify the hoppings of the graphene lattice in the - # format expected by possibe_hoppings() + # format expected by builder.HoppingKind #HIDDEN_BEGIN_hsmc hoppings = (((0, 0), a, b), ((0, 1), a, b), ((-1, 1), a, b)) #HIDDEN_END_hsmc #HIDDEN_BEGIN_bfwb - for hopping in hoppings: - sys[sys.possible_hoppings(*hopping)] = -1 + sys[[kwant.builder.HoppingKind(*hopping) for hopping in hoppings]] = -1 #HIDDEN_END_bfwb # Modify the scattering region @@ -75,8 +74,7 @@ def make_system(r=10, w=2.0, pot=0.1): lead0 = kwant.Builder(sym0) lead0[graphene.shape(lead0_shape, (0, 0))] = -pot - for hopping in hoppings: - lead0[lead0.possible_hoppings(*hopping)] = -1 + lead0[[kwant.builder.HoppingKind(*hopping) for hopping in hoppings]] = -1 # The second lead, going to the top right sym1 = kwant.TranslationalSymmetry(graphene.vec((0, 1))) @@ -89,8 +87,7 @@ def make_system(r=10, w=2.0, pot=0.1): lead1 = kwant.Builder(sym1) lead1[graphene.shape(lead1_shape, (0, 0))] = pot - for hopping in hoppings: - lead1[lead1.possible_hoppings(*hopping)] = -1 + lead1[[kwant.builder.HoppingKind(*hopping) for hopping in hoppings]] = -1 #HIDDEN_END_aakh #HIDDEN_BEGIN_kmmw diff --git a/doc/source/tutorial/quantum_well.py b/doc/source/tutorial/quantum_well.py index f9563c8f38147a704ac974cc7faa5166fcbc8d60..c4d80c26a038b25088e0891730653a816f900b5e 100644 --- a/doc/source/tutorial/quantum_well.py +++ b/doc/source/tutorial/quantum_well.py @@ -35,8 +35,7 @@ def make_system(a=1, t=1.0, W=10, L=30, L_well=10): return 4 * t + potential(site, pot) sys[(lat(x, y) for x in range(L) for y in range(W))] = onsite - for hopping in lat.nearest: - sys[sys.possible_hoppings(*hopping)] = -t + sys[lat.nearest] = -t #HIDDEN_END_coid #### Define the leads. #### @@ -45,8 +44,7 @@ def make_system(a=1, t=1.0, W=10, L=30, L_well=10): lead0 = kwant.Builder(sym_lead0) lead0[(lat(0, j) for j in xrange(W))] = 4 * t - for hopping in lat.nearest: - lead0[lead0.possible_hoppings(*hopping)] = -t + lead0[lat.nearest] = -t # ... then the lead to the right. We use a method that returns a copy of # `lead0` with its direction reversed. diff --git a/doc/source/tutorial/quantum_wire_revisited.py b/doc/source/tutorial/quantum_wire_revisited.py index 920ded17d3d3963903224fcd9cc3d2fb450b8da5..9ce7abcd256165896d1a14529cac55aaf68bf860 100644 --- a/doc/source/tutorial/quantum_wire_revisited.py +++ b/doc/source/tutorial/quantum_wire_revisited.py @@ -4,7 +4,7 @@ # # Kwant features highlighted # -------------------------- -# - Using iterables and possible_hoppings() for making systems +# - Using iterables and builder.HoppingKind for making systems # - introducing `reversed()` for the leads # # Note: Does the same as tutorial1a.py, but using other features of kwant @@ -29,8 +29,7 @@ def make_system(a=1, t=1.0, W=10, L=30): sys[(lat(x, y) for x in range(L) for y in range(W))] = 4 * t #HIDDEN_END_vvjt #HIDDEN_BEGIN_nooi - for hopping in lat.nearest: - sys[sys.possible_hoppings(*hopping)] = -t + sys[lat.nearest] = -t #HIDDEN_END_nooi #### Define the leads. #### @@ -41,8 +40,7 @@ def make_system(a=1, t=1.0, W=10, L=30): lead0 = kwant.Builder(sym_lead0) lead0[(lat(0, j) for j in xrange(W))] = 4 * t - for hopping in lat.nearest: - lead0[lead0.possible_hoppings(*hopping)] = -t + lead0[lat.nearest] = -t #HIDDEN_END_iepx # ... then the lead to the right. We use a method that returns a copy of diff --git a/doc/source/tutorial/spin_orbit.py b/doc/source/tutorial/spin_orbit.py index 55cce77809326bd24a78bfdef1eb744735635b2f..1d14331af4ab491a580b2b270e3e1b4d28cff0d2 100644 --- a/doc/source/tutorial/spin_orbit.py +++ b/doc/source/tutorial/spin_orbit.py @@ -41,10 +41,10 @@ def make_system(a=1, t=1.0, alpha=0.5, e_z=0.08, W=10, L=30): sys[(lat(x, y) for x in range(L) for y in range(W))] = 4 * t * sigma_0 + \ e_z * sigma_z # hoppings in x-direction - sys[sys.possible_hoppings((1, 0), lat, lat)] = -t * sigma_0 - \ + sys[kwant.builder.HoppingKind((1, 0), lat, lat)] = -t * sigma_0 - \ 1j * alpha * sigma_y # hoppings in y-directions - sys[sys.possible_hoppings((0, 1), lat, lat)] = -t * sigma_0 + \ + sys[kwant.builder.HoppingKind((0, 1), lat, lat)] = -t * sigma_0 + \ 1j * alpha * sigma_x #HIDDEN_END_uxrm @@ -56,10 +56,10 @@ def make_system(a=1, t=1.0, alpha=0.5, e_z=0.08, W=10, L=30): #HIDDEN_BEGIN_yliu lead0[(lat(0, j) for j in xrange(W))] = 4 * t * sigma_0 + e_z * sigma_z # hoppings in x-direction - lead0[lead0.possible_hoppings((1, 0), lat, lat)] = -t * sigma_0 - \ + lead0[kwant.builder.HoppingKind((1, 0), lat, lat)] = -t * sigma_0 - \ 1j * alpha * sigma_y # hoppings in y-directions - lead0[lead0.possible_hoppings((0, 1), lat, lat)] = -t * sigma_0 + \ + lead0[kwant.builder.HoppingKind((0, 1), lat, lat)] = -t * sigma_0 + \ 1j * alpha * sigma_x #HIDDEN_END_yliu diff --git a/doc/source/tutorial/superconductor_transport.py b/doc/source/tutorial/superconductor_transport.py index ba7743f945751731b10d47069a0c7677d38af24e..55d2e6d0c3ae34a151b2d670175318074838b82f 100644 --- a/doc/source/tutorial/superconductor_transport.py +++ b/doc/source/tutorial/superconductor_transport.py @@ -36,10 +36,10 @@ def make_system(a=1, W=10, L=10, barrier=1.5, barrierpos=(3, 4), for y in range(W))] = mu - 4 * t - barrier # hoppings in x and y-directions, for both electrons and holes - sys[sys.possible_hoppings((1, 0), lat_e, lat_e)] = -t - sys[sys.possible_hoppings((0, 1), lat_e, lat_e)] = -t - sys[sys.possible_hoppings((1, 0), lat_h, lat_h)] = t - sys[sys.possible_hoppings((0, 1), lat_h, lat_h)] = t + sys[kwant.builder.HoppingKind((1, 0), lat_e, lat_e)] = -t + sys[kwant.builder.HoppingKind((0, 1), lat_e, lat_e)] = -t + sys[kwant.builder.HoppingKind((1, 0), lat_h, lat_h)] = t + sys[kwant.builder.HoppingKind((0, 1), lat_h, lat_h)] = t # Superconducting order parameter enters as hopping between # electrons and holes @@ -56,15 +56,15 @@ def make_system(a=1, W=10, L=10, barrier=1.5, barrierpos=(3, 4), lead0 = kwant.Builder(sym_left) lead0[(lat_e(0, j) for j in xrange(W))] = 4 * t - mu # hoppings in x and y-direction - lead0[lead0.possible_hoppings((1, 0), lat_e, lat_e)] = -t - lead0[lead0.possible_hoppings((0, 1), lat_e, lat_e)] = -t + lead0[kwant.builder.HoppingKind((1, 0), lat_e, lat_e)] = -t + lead0[kwant.builder.HoppingKind((0, 1), lat_e, lat_e)] = -t # left hole lead lead1 = kwant.Builder(sym_left) lead1[(lat_h(0, j) for j in xrange(W))] = mu - 4 * t # hoppings in x and y-direction - lead1[lead1.possible_hoppings((1, 0), lat_h, lat_h)] = t - lead1[lead1.possible_hoppings((0, 1), lat_h, lat_h)] = t + lead1[kwant.builder.HoppingKind((1, 0), lat_h, lat_h)] = t + lead1[kwant.builder.HoppingKind((0, 1), lat_h, lat_h)] = t #HIDDEN_END_ttth # Then the lead to the right @@ -77,10 +77,10 @@ def make_system(a=1, W=10, L=10, barrier=1.5, barrierpos=(3, 4), lead2[(lat_e(0, j) for j in xrange(W))] = 4 * t - mu lead2[(lat_h(0, j) for j in xrange(W))] = mu - 4 * t # hoppings in x and y-direction - lead2[lead2.possible_hoppings((1, 0), lat_e, lat_e)] = -t - lead2[lead2.possible_hoppings((0, 1), lat_e, lat_e)] = -t - lead2[lead2.possible_hoppings((1, 0), lat_h, lat_h)] = t - lead2[lead2.possible_hoppings((0, 1), lat_h, lat_h)] = t + lead2[kwant.builder.HoppingKind((1, 0), lat_e, lat_e)] = -t + lead2[kwant.builder.HoppingKind((0, 1), lat_e, lat_e)] = -t + lead2[kwant.builder.HoppingKind((1, 0), lat_h, lat_h)] = t + lead2[kwant.builder.HoppingKind((0, 1), lat_h, lat_h)] = t lead2[((lat_e(0, j), lat_h(0, j)) for j in xrange(W))] = Delta #HIDDEN_END_mhiw diff --git a/doc/source/tutorial/tutorial1.rst b/doc/source/tutorial/tutorial1.rst index 8d52163c241e2353b849a9dbcbfdaf9b568a4131..910733b7a89ca58eb78e9b098ada3b75467e1268 100644 --- a/doc/source/tutorial/tutorial1.rst +++ b/doc/source/tutorial/tutorial1.rst @@ -307,18 +307,21 @@ feature of kwant: :start-after: #HIDDEN_BEGIN_nooi :end-before: #HIDDEN_END_nooi -In regular lattices, one has only very few types of different hoppings -(by one lattice point in x or y-direction in the case of a square -lattice considered here). For the square lattice, these types of -hoppings are stored as a list in ``lat.nearest``, and the ``for``-loop -runs over all of them. -`~kwant.builder.Builder.possible_hoppings` takes as an argument -one type of hopping (more about that in the notes below; -details on the hopping definition will be discussed in -:ref:`tutorial_spinorbit`), and generates all -hoppings of this type that are possible with all the lattice points -that were added before. ``sys[sys.possible_hoppings(*hopping)] = -t`` -then sets all of those hopping matrix elements at once. +In regular lattices, hoppings form large groups such that hoppings within a +group can be transformed into one another by lattice translations. In order to +allow to easily manipulate such hoppings, an object +`~kwant.builder.HoppingKind` is provided. When given a `~kwant.builder.Builder` as +an argument, `~kwant.builder.HoppingKind` yields all the hoppings of a +certain kind that can be added to this builder without adding new sites. When +`~kwant.builder.HoppingKind` is given to `~kwant.builder.Builder` as a key, it +means that something is done to all the possible hoppings of this kind. A list +of `~kwant.builder.HoppingKind` objects corresponding to nearest neighbors in +pre-defined lattices in kwant (that is `~kwant.lattice.chain`, +`~kwant.lattice.square`, and `~kwant.lattice.honeycomb`) is stored in +``lat.nearest``. ``sys[lat.nearest] = -t`` then sets all of those hopping +matrix elements at once. More detailed example of using +`~kwant.builder.HoppingKind` directly will be provided in +:ref:`tutorial_spinorbit`. The leads can be constructed in an analogous way: @@ -390,16 +393,16 @@ The result of the example should be identical to the previous one. :end-before: #HIDDEN_END_nooi we write ``*hopping`` instead of ``hopping``. The reason is as follows: - `~kwant.builder.Builder.possible_hoppings` expects the hopping to + `~kwant.builder.HoppingKind` expects the hopping to be defined using three parameters (in particular, a tuple containing a relative lattice vector, and two (sub)lattice objects that indicate the start and end lattice, more about that in a :ref:`later tutorial <tutorial_spinorbit>`). ``lat.nearest`` is a list of tuples, with every tuple containing the three - parameters expected by `~kwant.builder.Builder.possible_hoppings`. + parameters expected by `~kwant.builder.HoppingKind`. Hence, ``hopping`` is a tuple. But passing it to - `~kwant.builder.Builder.possible_hoppings` would fail, + `~kwant.builder.HoppingKind` would fail, as three parameters are expected (not a single tuple). ``*hopping`` unpacks the tuple into these three separate parameters (see <http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists>) @@ -441,7 +444,7 @@ The result of the example should be identical to the previous one. However, both can be used in ``for``-loops, for example. - In the example, we have added all the hoppings using - `~kwant.builder.Builder.possible_hoppings`. In fact, + `~kwant.builder.HoppingKind`. In fact, hoppings can be added in the same fashion as sites, namely specifying * a single hopping diff --git a/doc/source/tutorial/tutorial2.rst b/doc/source/tutorial/tutorial2.rst index d2a45c45097f4aed25a371f755d444bb461c8340..094d6635a0562744dea43a9fcd2dc56706d2d08a 100644 --- a/doc/source/tutorial/tutorial2.rst +++ b/doc/source/tutorial/tutorial2.rst @@ -59,11 +59,10 @@ we can simply write: Note that the Zeeman energy adds to the onsite term, whereas the Rashba spin-orbit term adds to the hoppings (due to the derivative operator). Furthermore, the hoppings in x and y-direction have a different matrix -structure. We still use `~kwant.builder.Builder.possible_hoppings` -to add all the hoppings at once, but we now have to distinguish -x and y-direction. Because of that, we have to explicitly specify -the hoppings in the form expected by -`~kwant.builder.Builder.possible_hoppings`: +structure. We now cannot use ``lat.nearest`` to add all the hoppings at once, +since we now have to distinguish x and y-direction. Because of that, we have to +explicitly specify the hoppings in the form expected by +`~kwant.builder.HoppingKind`: - A tuple with relative lattice indices. For example, `(1, 0)` means hopping from `(i, j)` to `(i+1, j)`, whereas `(1, 1)` would @@ -113,7 +112,7 @@ the following, clearly non-monotonic conductance steps: for kwant: it allows them to be used directly as dictionary keys. - It should be emphasized that the relative hopping used for - `~kwant.builder.Builder.possible_hoppings` is given in terms of + `~kwant.builder.HoppingKind` is given in terms of lattice indices, i.e. relative to the Bravais lattice vectors. For a square lattice, the Bravais lattice vectors are simply `(a,0)` and `(0,a)`, and hence the mapping from @@ -250,7 +249,7 @@ provided by the lattice: Here, ``lat.shape`` takes as a second parameter a (real-space) point that is inside the desired shape. The hoppings can still be added using -`~kwant.builder.Builder.possible_hoppings` as before. +``lat.nearest`` as before. Up to now, the system contains constant hoppings and onsite energies, and we still need to include the phase shift due to the magnetic flux. diff --git a/doc/source/tutorial/tutorial4.rst b/doc/source/tutorial/tutorial4.rst index 7c70174f7844ae82c722c6040d5439e89ed2912b..02a6db709b5ca96fd42eb0b735ffb838d968a4ef 100644 --- a/doc/source/tutorial/tutorial4.rst +++ b/doc/source/tutorial/tutorial4.rst @@ -38,7 +38,7 @@ from the scope of `make_system`, since we keep the potential fixed in this example. As a next step we add the hoppings, making use of -`~kwant.builder.Builder.possible_hoppings`. Since we use our home-made +`~kwant.builder.HoppingKind`. Since we use our home-made lattice (instead of `kwant.lattice.honeycomb`), we have to define the hoppings ourselves: diff --git a/doc/source/whatsnew/0.3.rst b/doc/source/whatsnew/0.3.rst index 01823b4ea53b56153fb404b5153ea70b955b453e..6b3ffcd371f9470c3f0cbc6b40046b589e6f6375 100644 --- a/doc/source/whatsnew/0.3.rst +++ b/doc/source/whatsnew/0.3.rst @@ -3,6 +3,27 @@ What's New in kwant 0.3 This article explains the user-visible changes in kwant 0.3. + +``possible_hoppings`` replaced by `~kwant.builder.HoppingKind` +-------------------------------------------------------------- +The `~kwant.builder.Builder` method ``possible_hoppings`` has been rendered +obsolete. Where previously one would have had :: + + for kind in lat.nearest: + sys[sys.possible_hoppings(*kind)] = t + +now it suffices to write :: + + sys[lat.nearest] = t + +This is possible because `~kwant.builder.Builder` now accepts *functions* as +keys in addition to `~kwant.builder.Site` objects and tuples of them +(hoppings). These functions are expected to yield either sites or hoppings, +when given a builder instance as the sole argument. The use of such keys is to +implement sets of sites or hoppings that depend on what is already present in +the builder, such as `~kwant.builder.HoppingKind`. In the above example, +``lat.nearest`` is a list of ``HoppingKind`` objects. + Some renames ------------ * ``wave_func`` has been renamed to `~kwant.solvers.default.wave_function`, diff --git a/kwant/builder.py b/kwant/builder.py index 5b312d5503616b26332653bfd948a8725d910817..0fec2cfd9521d02a32d3a49c8006995849738942 100644 --- a/kwant/builder.py +++ b/kwant/builder.py @@ -9,7 +9,7 @@ from __future__ import division __all__ = ['Builder', 'Site', 'SiteGroup', 'SimpleSiteGroup', 'Symmetry', - 'Lead', 'BuilderLead', 'SelfEnergy'] + 'HoppingKind', 'Lead', 'BuilderLead', 'SelfEnergy'] import abc import sys @@ -20,7 +20,7 @@ import tinyarray as ta import numpy as np from . import system, graph - + ################ Sites and site groups class Site(tuple): @@ -176,7 +176,7 @@ class SimpleSiteGroup(SiteGroup): 'its representation.') return tag - + ################ Symmetries class Symmetry(object): @@ -275,7 +275,72 @@ class NoSymmetry(Symmetry): def in_fd(self, site): return True + +################ Hopping kinds + +class HoppingKind(object): + """A pattern for matching hoppings. + + A hopping ``(a, b)`` matches precisely when the site group of ``a`` equals + `group_a` and that of ``b`` equals `group_b` and ``(a.tag - b.tag)`` is + equal to `delta`. In other words, the matching hoppings have the form: + ``(group_a(x + delta), group_b(x))`` + + Parameters + ---------- + delta : Sequence of integers + The sequence is interpreted as a vector with integer elements. + group_a : `~kwant.builder.SiteGroup` + grpup_b : `~kwant.builder.SiteGroup` or ``None`` (default) + The default value means: use the same group as `group_a`. + + Notes + ----- + A ``HoppingKind`` is a callable object: When called with a + `~kwant.builder.Builder` as sole argument, an instance of this class will + return an iterator over all possible matching hoppings whose sites are + already present in the system. The hoppings do *not* have to be already + present in the system. For example:: + + kind = kwant.builder.HoppingKind((1, 0), lat) + sys[kind(sys)] = 1 + + Because a `~kwant.builder.Builder` can be indexed with functions or + iterables of functions, ``HoppingKind`` instances (or any non-tuple + iterables of them, e.g. a list) can be used directly as "wildcards" when + setting or deleting hoppings:: + + kinds = [kwant.builder.HoppingKind(v, lat) for v in [(1, 0), (0, 1)]] + sys[kinds] = 1 + """ + __slots__ = ('delta', 'group_a', 'group_b') + + def __init__(self, delta, group_a, group_b=None): + self.delta = ta.array(delta, int) + self.group_a = group_a + self.group_b = group_b if group_b is not None else group_a + def __call__(self, builder): + delta = self.delta + group_a = self.group_a + group_b = self.group_b + H = builder.H + symtofd = builder.symmetry.to_fd + + for a in H: + if a.group != group_a: + continue + b = Site(group_b, a.tag - delta, True) + if symtofd(b) in H: + yield a, b + + def __repr__(self): + return '{0}({1}, {2}{3})'.format( + self.__class__.__name__, repr(tuple(self.delta)), + repr(self.group_a), + ', ' + repr(self.group_b) if self.group_a != self.group_b else '') + + ################ Support for Hermitian conjugation def herm_conj(value): @@ -303,7 +368,7 @@ class HermConjOfFunc(object): def __call__(self, i, j): return herm_conj(self.function(j, i)) - + ################ Leads class Lead(object): @@ -398,43 +463,9 @@ class SelfEnergy(Lead): def self_energy(self, energy): return self.self_energy_func(energy) - + ################ Builder class - -def for_each_in_key(key, f_site, f_hopp): - """Perform an operation on each site or hopping in key. - - Key may be - * a single site or hopping object, - * a non-tuple iterable of sites, - * a non-tuple iterable of hoppings. - """ - if isinstance(key, Site): - f_site(key) - elif isinstance(key, tuple): - f_hopp(key) - else: - try: - ikey = iter(key) - except: - raise KeyError(key) - try: - first = next(ikey) - except StopIteration: - return - if isinstance(first, Site): - f_site(first) - for site in ikey: - f_site(site) - elif isinstance(first, tuple): - f_hopp(first) - for hopping in ikey: - f_hopp(hopping) - else: - raise KeyError(first) - - # A marker, meaning for hopping (i, j): this value is given by the Hermitian # conjugate the value of the hopping (j, i). Used by Builder and System. Other = type('Other', (object,), {'__repr__': lambda s: 'Other'})() @@ -457,20 +488,13 @@ class Builder(object): The nodes of the graph are `Site` instances. The edges, i.e. the hoppings, are pairs (2-tuples) of sites. Each node and each edge has a value - associated with it. That value can be in fact any python object, but - currently the only *useful* values are matrices and numbers or functions - returning them. The values associated with nodes are interpreted as + associated with it. The values associated with nodes are interpreted as on-site Hamiltonians, the ones associated with edges as hopping integrals. - To make the graph accessible in a way that is natural within the python + To make the graph accessible in a way that is natural within the Python language it is exposed as a *mapping* (much like a built-in Python - dictionary). Keys are sites or pairs of them. Possible values are 2d - NumPy arrays, numbers (interpreted as 1 by 1 matrices), or functions. - Functions receive the site or the hopping (passed to the function as two - sites) and are expected to return a valid value. - - Builder instances can be made to automatically respect a `Symmetry` that is - passed to them during creation. + dictionary). Keys are sites or hoppings. Values are 2d arrays + (e.g. NumPy or tinyarray) or numbers (interpreted as 1 by 1 matrices). Parameters ---------- @@ -479,24 +503,33 @@ class Builder(object): Notes ----- + Values can be also functions that receive the site or the hopping (passed + to the function as two sites) and possibly additional arguments and are + expected to return a valid value. This allows to define systems quickly, + to modify them without reconstructing, and to save memory for many-orbital + models. + + Any (non-tuple) iterable (e.g. a list) of keys is also a key: Lists or + generator expressions of hoppings/sites can be used as keys. Additionally, + a function that returns a key when given a builder as sole argument is a + key as well. This makes it possible to use (lists of) `HoppingKind` + instances as keys. + Builder instances automatically ensure that every hopping is Hermitian, so that if ``builder[a, b]`` has been set, there is no need to set ``builder[b, a]``. - Values which are functions allow to define systems quickly, to modify them - without reconstructing, and to save memory for many-orbital models. - - The behavior of builders with a symmetry is slightly more sophisticated. - First of all, it is implicitly assumed throughout kwant that **every** - function assigned as a value to a builder with a symmetry possesses the - same symmetry. Secondly, all keys are mapped to the fundamental domain - before storing them. This may produce confusing results when neighbors of - a site are queried. + Builder instances can be made to automatically respect a `Symmetry` that is + passed to them during creation. The behavior of builders with a symmetry + is slightly more sophisticated. First of all, it is implicitly assumed + throughout kwant that **every** function assigned as a value to a builder + with a symmetry possesses the same symmetry. Secondly, all keys are mapped + to the fundamental domain of the symmetry before storing them. This may + produce confusing results when neighbors of a site are queried. - The methods `possible_hoppings` and `attach_lead` *work* only if the sites - affected by them have tags which are sequences of integers. They *make - sense* only when these sites live on a regular lattice, like one provided - by `kwant.lattice`. + The method `attach_lead` *works* only if the sites affected by them have + tags which are sequences of integers. It *makes sense* only when these + sites live on a regular lattice, like the ones provided by `kwant.lattice`. .. warning:: @@ -603,6 +636,39 @@ class Builder(object): def __nonzero__(self): return bool(self.H) + # TODO: rewrite using "yield from" once we can take Python 3.3 for granted. + def _for_each_in_key(self, key, f_site, f_hopp): + if isinstance(key, Site): + f_site(key) + return 0 + elif isinstance(key, tuple): + f_hopp(key) + return 1 + elif callable(key): + return self._for_each_in_key(key(self), f_site, f_hopp) + else: + try: + ret = None + for item in key: + last = self._for_each_in_key(item, f_site, f_hopp) + if last != ret: + if ret is None: + ret = last + elif last is not None: + raise KeyError(item) + return ret + except TypeError: + raise KeyError(key) + # The following clauses make sure that a useful error message is + # generated for infinitely iterable keys (like strings). + except KeyError as e: + if not e.args and key != item: + raise KeyError(key) + else: + raise + except RuntimeError: + raise KeyError() + def _get_site(self, site): site = self.symmetry.to_fd(site) try: @@ -701,9 +767,9 @@ class Builder(object): def __setitem__(self, key, value): """Set a single site/hopping or an iterable of them.""" - for_each_in_key(key, - lambda s: self._set_site(s, value), - lambda h: self._set_hopping(h, value)) + self._for_each_in_key(key, + lambda s: self._set_site(s, value), + lambda h: self._set_hopping(h, value)) def _del_site(self, site): """Delete a single site and all associated hoppings.""" @@ -744,9 +810,9 @@ class Builder(object): def __delitem__(self, key): """Delete a single site/hopping or an iterable of them.""" - for_each_in_key(key, - lambda s: self._del_site(s), - lambda h: self._del_hopping(h)) + self._for_each_in_key(key, + lambda s: self._del_site(s), + lambda h: self._del_hopping(h)) def eradicate_dangling(self): """Keep deleting dangling sites until none are left.""" @@ -842,38 +908,6 @@ class Builder(object): self.leads.extend(other_sys.leads) return self - def possible_hoppings(self, delta, group_a, group_b): - """Return all matching possible hoppings between existing sites. - - A hopping ``(a, b)`` matches precisely when the site group of ``a`` - equals `group_a` and that of ``b`` equals `group_b` and - ``(a.tag - b.tag)`` is equal to `delta`. - - In other words, the matching hoppings have the form: - ``(group_a(x + delta), group_b(x))`` - - Parameters - ---------- - delta : Sequence of integers - The sequence is interpreted as a vector with integer elements. - group_a : `~kwant.builder.SiteGroup` - grpup_b : `~kwant.builder.SiteGroup` - - Returns - ------- - hoppings : Iterator over hoppings - All matching possible hoppings - """ - H = self.H - symtofd = self.symmetry.to_fd - delta = ta.array(delta, int) - for a in self.H: - if a.group != group_a: - continue - b = Site(group_b, a.tag - delta, True) - if symtofd(b) in H: - yield a, b - def attach_lead(self, lead_builder, origin=None): """Attach a lead to the builder, possibly adding missing sites. @@ -1186,10 +1220,9 @@ class Builder(object): result.symmetry = self.symmetry return result - + ################ Finalized systems - class FiniteSystem(system.FiniteSystem): """ Finalized `Builder` with leads. diff --git a/kwant/graph/tests/test_slicer.py b/kwant/graph/tests/test_slicer.py index ff343166e519dbda5155db15918ae0eda60807db..cce57f6069b63faa720727aec5ce7ca2ab5ddfe5 100644 --- a/kwant/graph/tests/test_slicer.py +++ b/kwant/graph/tests/test_slicer.py @@ -42,8 +42,9 @@ def test_rectangle(): lead[(lat(0, i) for i in xrange(w))] = 0 sys[(lat(j, i) for j in xrange(l) for i in xrange(w))] = 0 for s in [lead, sys]: - for delta in [(1, 0), (0, 1)]: - s[s.possible_hoppings(delta, lat, lat)] = -1 + for kind in [kwant.builder.HoppingKind((1, 0), lat), + kwant.builder.HoppingKind((0, 1), lat)]: + s[kind] = -1 sys.attach_lead(lead) sys.attach_lead(lead.reversed()) fsys = sys.finalized() diff --git a/kwant/lattice.py b/kwant/lattice.py index f36199ed0cb05ef245713e102eacc530f7b6266d..8e6cee6e00f44ef6c28c008ef3ad5900b20f5987 100644 --- a/kwant/lattice.py +++ b/kwant/lattice.py @@ -428,15 +428,15 @@ class TranslationalSymmetry(builder.Symmetry): def chain(a=1, name=''): """Create a one-dimensional lattice.""" lat = Monatomic(((a,),), name=name) - lat.nearest = [((1,), lat, lat)] + lat.nearest = [builder.HoppingKind((1,), lat, lat)] return lat def square(a=1, name=''): """Create a square lattice.""" lat = Monatomic(((a, 0), (0, a)), name=name) - lat.nearest = [((1, 0), lat, lat), - ((0, 1), lat, lat)] + lat.nearest = [builder.HoppingKind((1, 0), lat, lat), + builder.HoppingKind((0, 1), lat, lat)] return lat @@ -445,7 +445,7 @@ def honeycomb(a=1, name=''): lat = Polyatomic(((a, 0), (0.5 * a, 0.5 * a * sqrt(3))), ((0, 0), (0, a / sqrt(3))), name=name) lat.a, lat.b = lat.sublattices - lat.nearest = [((0, 0), lat.a, lat.b), - ((0, 1), lat.a, lat.b), - ((-1, 1), lat.a, lat.b)] + lat.nearest = [builder.HoppingKind((0, 0), lat.a, lat.b), + builder.HoppingKind((0, 1), lat.a, lat.b), + builder.HoppingKind((-1, 1), lat.a, lat.b)] return lat diff --git a/kwant/linalg/tests/test_mumps.py b/kwant/linalg/tests/test_mumps.py index f630a0199fa6f8ba234b6a748412eae541bcafbf..857c5cc66fe9f5b29e46bec1cb0d8b1250527f72 100644 --- a/kwant/linalg/tests/test_mumps.py +++ b/kwant/linalg/tests/test_mumps.py @@ -13,7 +13,7 @@ except ImportError: _no_mumps = True from kwant.lattice import honeycomb -from kwant import Builder +from kwant.builder import Builder, HoppingKind from nose.tools import assert_equal, assert_true from numpy.testing.decorators import skipif import numpy as np @@ -74,9 +74,8 @@ def test_error_minus_9(r=10): sys = Builder() sys[graphene.shape(circle, (0,0))] = -0.0001 - hoppings = (((0, 0), b, a), ((0, 1), b, a), ((-1, 1), b, a)) - for hopping in hoppings: - sys[sys.possible_hoppings(*hopping)] = - 1 + for kind in [((0, 0), b, a), ((0, 1), b, a), ((-1, 1), b, a)]: + sys[HoppingKind(*kind)] = - 1 ham = sys.finalized().hamiltonian_submatrix(sparse=True) diff --git a/kwant/solvers/tests/_test_sparse.py b/kwant/solvers/tests/_test_sparse.py index 6eaebff5b222d6aea813b7264ad17fed78a74455..95c8bbfb541c8e561ba3cde0aee9f604c10011eb 100644 --- a/kwant/solvers/tests/_test_sparse.py +++ b/kwant/solvers/tests/_test_sparse.py @@ -387,8 +387,8 @@ def test_wavefunc_ldos_consistency(wave_function, ldos): h = np.random.rand(n, n) + 1j * np.random.rand(n, n) h += h.conjugate().transpose() b[site] = h - for kind in square.nearest: - for hop in b.possible_hoppings(*kind): + for hopping_kind in square.nearest: + for hop in hopping_kind(b): b[hop] = 10 * np.random.rand(n, n) + 1j * np.random.rand(n, n) sys.attach_lead(left_lead) sys.attach_lead(top_lead) diff --git a/kwant/tests/test_builder.py b/kwant/tests/test_builder.py index 12a6e7820bd1f23f02274b7e1a0e1730109c4804..f825638042a9e0066404e203d785b9716037d457 100644 --- a/kwant/tests/test_builder.py +++ b/kwant/tests/test_builder.py @@ -479,8 +479,8 @@ def test_neighbors_not_in_single_domain(): lead = builder.Builder(VerySimpleSymmetry(-1)) gr = builder.SimpleSiteGroup() sr[(gr(x, y) for x in range(3) for y in range(3) if x >= y)] = 0 - sr[sr.possible_hoppings((1, 0), gr, gr)] = 1 - sr[sr.possible_hoppings((0, 1), gr, gr)] = 1 + sr[builder.HoppingKind((1, 0), gr)] = 1 + sr[builder.HoppingKind((0, 1), gr)] = 1 lead[(gr(0, y) for y in range(3))] = 0 lead[((gr(0, y), gr(1, y)) for y in range(3))] = 1 lead[((gr(0, y), gr(0, y + 1)) for y in range(2))] = 1 @@ -529,33 +529,32 @@ def test_iadd(): # hhgh hhgh # ghgh hhgh # -def test_possible_hoppings(): +def test_HoppingKind(): g = kwant.lattice.general(ta.identity(3), name='some_lattice') h = kwant.lattice.general(ta.identity(3), name='another_lattice') sym = kwant.TranslationalSymmetry((0, 2, 0)) sys = builder.Builder(sym) sys[((h if max(x, y, z) % 2 else g)(x, y, z) for x in range(4) for y in range(2) for z in range(4))] = None - for delta, group_a, group_b, n in [((1, 0, 0), g, h, 4), - ((1, 0, 0), h, g, 7), - ((0, 1, 0), g, h, 1), - ((0, 4, 0), h, h, 21), - ((0, 0, 1), g, h, 4) - ]: - ph = list(sys.possible_hoppings(delta, group_a, group_b)) + for delta, ga, gb, n in [((1, 0, 0), g, h, 4), + ((1, 0, 0), h, g, 7), + ((0, 1, 0), g, h, 1), + ((0, 4, 0), h, h, 21), + ((0, 0, 1), g, h, 4)]: + ph = list(builder.HoppingKind(delta, ga, gb)(sys)) assert_equal(len(ph), n) ph = set(ph) assert_equal(len(ph), n) ph2 = list(( sym.to_fd(b, a) for a, b in - sys.possible_hoppings(ta.negative(delta), group_b, group_a))) + builder.HoppingKind(ta.negative(delta), gb, ga)(sys))) assert_equal(len(ph2), n) ph2 = set(ph2) assert_equal(ph2, ph) for a, b in ph: - assert a.group == group_a - assert b.group == group_b + assert a.group == ga + assert b.group == gb assert sym.to_fd(a) == a assert_equal(a.tag - b.tag, delta) diff --git a/kwant/tests/test_plotter.py b/kwant/tests/test_plotter.py index 82b198fe3b2158e229e535dba08a9c6ae470d74f..7290117cd546e624e04c4d337bbc33495c5ad5b7 100644 --- a/kwant/tests/test_plotter.py +++ b/kwant/tests/test_plotter.py @@ -28,8 +28,7 @@ def sys_2d(W=3, r1=3, r2=8): return r1 ** 2 < rsq < r2 ** 2 sys[lat.shape(ring, (0, r1 + 1))] = 4 * t - for hopping in lat.nearest: - sys[sys.possible_hoppings(*hopping)] = - t + sys[lat.nearest] = -t sym_lead0 = kwant.TranslationalSymmetry(lat.vec((-1, 0))) lead0 = kwant.Builder(sym_lead0) lead2 = kwant.Builder(sym_lead0) @@ -41,8 +40,7 @@ def sys_2d(W=3, r1=3, r2=8): lead0[lat.shape(lead_shape, (0, 0))] = 4 * t lead2[lat.shape(lead_shape, (0, 0))] = 4 * t sys.attach_lead(lead2) - for hopping in lat.nearest: - lead0[lead0.possible_hoppings(*hopping)] = - t + lead0[lat.nearest] = - t lead1 = lead0.reversed() sys.attach_lead(lead0) sys.attach_lead(lead1) @@ -51,8 +49,8 @@ def sys_2d(W=3, r1=3, r2=8): def sys_3d(W=3, r1=2, r2=4, a=1, t=1.0): lat = kwant.lattice.general(((a, 0, 0), (0, a, 0), (0, 0, a))) - lat.nearest = (((1, 0, 0), lat, lat), ((0, 1, 0), lat, lat), - ((0, 0, 1), lat, lat)) + lat.nearest = [kwant.builder.HoppingKind(*kind) for kind in + [((1, 0, 0), lat), ((0, 1, 0), lat), ((0, 0, 1), lat)]] sys = kwant.Builder() def ring(pos): @@ -60,8 +58,7 @@ def sys_3d(W=3, r1=2, r2=4, a=1, t=1.0): rsq = x ** 2 + y ** 2 return (r1 ** 2 < rsq < r2 ** 2) and abs(z) < 2 sys[lat.shape(ring, (0, -r2 + 1, 0))] = 4 * t - for hopping in lat.nearest: - sys[sys.possible_hoppings(*hopping)] = - t + sys[lat.nearest] = - t sym_lead0 = kwant.TranslationalSymmetry(lat.vec((-1, 0, 0))) lead0 = kwant.Builder(sym_lead0) @@ -70,8 +67,7 @@ def sys_3d(W=3, r1=2, r2=4, a=1, t=1.0): return (-1 < x < 1) and (-W / 2 < y < W / 2) and abs(z) < 2 lead0[lat.shape(lead_shape, (0, 0, 0))] = 4 * t - for hopping in lat.nearest: - lead0[lead0.possible_hoppings(*hopping)] = - t + lead0[lat.nearest] = - t lead1 = lead0.reversed() sys.attach_lead(lead0) sys.attach_lead(lead1)