Browse code

Merge branch 'stable'

Joseph Weston authored on 13/11/2019 11:41:47
Showing 10 changed files
... ...
@@ -89,24 +89,18 @@ build-env:default:
89 89
   before_script:
90 90
     - source deactivate
91 91
     - source activate kwant-latest
92
-    - pip install qsymm
93 92
 
94 93
 .bleeding-edge-env: &bleeding_edge_env
95 94
   before_script:
96 95
     - source deactivate
97 96
     - conda env update -f /kwant-latest.yml
98 97
     - source activate kwant-latest
99
-    - pip install qsymm
100 98
 
101 99
 .ubuntu-env: &ubuntu_env
102 100
   image: gitlab.kwant-project.org:5005/kwant/kwant/ubuntu
103
-  before_script:
104
-    - pip3 install qsymm
105 101
 
106 102
 .debian-env: &debian_env
107 103
   image: gitlab.kwant-project.org:5005/kwant/kwant/debian
108
-  before_script:
109
-    - pip3 install qsymm
110 104
 
111 105
 ## Build Jobs
112 106
 
... ...
@@ -45,7 +45,7 @@ a NumPy-like Python package optimized for very small arrays,
45 45
 The following software is highly recommended though not strictly required:
46 46
  * `matplotlib <http://matplotlib.org/>`_ 1.5.1 or newer, for the module `kwant.plotter` and the tutorial,
47 47
  * `SymPy <http://sympy.org/>`_ 0.7.6 or newer, for the subpackage `kwant.continuum`.
48
- * `Qsymm <https://pypi.org/project/qsymm/>`_ 1.1.0 or newer, for the subpackage `kwant.qsymm`.
48
+ * `Qsymm <https://pypi.org/project/qsymm/>`_ 1.2.6 or newer, for the subpackage `kwant.qsymm`.
49 49
  * `MUMPS <http://graal.ens-lyon.fr/MUMPS/>`_, a sparse linear algebra library
50 50
    that will in many cases speed up Kwant several times and reduce the memory
51 51
    footprint.  (Kwant uses only the sequential, single core version
... ...
@@ -864,6 +864,10 @@ class Builder:
864 864
     amounts to creating a `Lead` object and appending it to the list of leads
865 865
     accessbile as the `~Builder.leads` attribute.
866 866
 
867
+    `conservation_law`, `time_reversal`, `particle_hole`, and `chiral`
868
+    affect the basis in which scattering modes derived from the builder
869
+    are expressed - see `~kwant.physics.DiscreteSymmetry` for details.
870
+
867 871
     .. warning::
868 872
 
869 873
         If functions are used to set values in a builder with a symmetry, then
... ...
@@ -780,6 +780,8 @@ def conductivity(hamiltonian, alpha='x', beta='x', positions=None, **kwargs):
780 780
 
781 781
     Parameters
782 782
     ----------
783
+    hamiltonian : `~kwant.system.FiniteSystem` or matrix Hamiltonian
784
+        If a system is passed, it should contain no leads.
783 785
     alpha, beta : str, or operators
784 786
         If ``hamiltonian`` is a kwant system, or if the ``positions``
785 787
         are provided, ``alpha`` and ``beta`` can be the directions of the
... ...
@@ -1051,10 +1053,11 @@ def _velocity(hamiltonian, params, op_type, positions):
1051 1053
     elif isinstance(op_type, str):
1052 1054
         direction = directions[op_type]
1053 1055
         if isinstance(hamiltonian, system.System):
1054
-            operator = hamiltonian.hamiltonian_submatrix(params=params,
1055
-                                                         sparse=True)
1056
-            positions = np.array([site.pos for site in hamiltonian.sites
1057
-                                  for iorb in range(site.family.norbs)])
1056
+            operator, norbs, norbs = hamiltonian.hamiltonian_submatrix(
1057
+                params=params, sparse=True, return_norb=True
1058
+            )
1059
+            positions = np.vstack([[hamiltonian.pos(i)] * norb
1060
+                                   for i, norb in enumerate(norbs)])
1058 1061
         elif positions is not None:
1059 1062
             operator = coo_matrix(hamiltonian, copy=True)
1060 1063
         displacements = positions[operator.col] - positions[operator.row]
... ...
@@ -130,11 +130,10 @@ class PropagatingModes:
130 130
     momentum and velocity, an arbitrary orthonormal basis in the subspace of
131 131
     these modes is chosen.
132 132
 
133
-    If a conservation law is specified to block diagonalize the Hamiltonian
134
-    to N blocks, then `block_nmodes[i]` is the number of left or right moving
135
-    propagating modes in conservation law block `i`. The ordering of blocks
136
-    is the same as the ordering of the projectors used to block diagonalize
137
-    the Hamiltonian.
133
+    If a conservation law is specified to block diagonalize the Hamiltonian,
134
+    then `block_nmodes[i]` is the number of left or right moving propagating
135
+    modes in conservation law block `i`. The ordering of blocks is the same as
136
+    the ordering of the projectors used to block diagonalize the Hamiltonian.
138 137
     """
139 138
     def __init__(self, wave_functions, velocities, momenta):
140 139
         kwargs = locals()
... ...
@@ -1046,6 +1045,10 @@ def modes(h_cell, h_hop, tol=1e6, stabilization=None, *,
1046 1045
     Propagating modes with the same momentum are orthogonalized. All the
1047 1046
     propagating modes are normalized by current.
1048 1047
 
1048
+    `projectors`, `time_reversal`, `particle_hole`, and `chiral` affect the
1049
+    basis in which the scattering modes are expressed - see
1050
+    `~kwant.physics.DiscreteSymmetry` for details.
1051
+
1049 1052
     This function uses the most stable and efficient algorithm for calculating
1050 1053
     the mode decomposition that the Kwant authors are aware about. Its details
1051 1054
     are to be published.
... ...
@@ -37,7 +37,7 @@ _names = ['Time reversal', 'Particle-hole', 'Chiral']
37 37
 _signs = [1, -1, -1]
38 38
 
39 39
 class DiscreteSymmetry:
40
-    """A collection of discrete symmetries and conservation laws.
40
+    r"""A collection of discrete symmetries and conservation laws.
41 41
 
42 42
     Parameters
43 43
     ----------
... ...
@@ -52,22 +52,46 @@ class DiscreteSymmetry:
52 52
 
53 53
     Notes
54 54
     -----
55
-    Whenever one or more discrete symmetry is declared in conjunction with a
56
-    conservation law, the symmetry operators and projectors must be declared
57
-    in canonical form. This means that each block of the Hamiltonian is
58
-    transformed either to itself by a discrete symmetry or to a single
59
-    other block.
60
-
61
-    More formally, consider a discrete symmetry S. The symmetry projection
62
-    that maps from block i to block j of the Hamiltonian with projectors
63
-    :math:`P_i` and :math:`P_j` is :math:`S_{ji} = P_j^+ S P_i`.
64
-    If :math:`S_{ji}` is nonzero, a symmetry relation exists between
65
-    blocks i and j. Canonical form means that for each j, the block
66
-    :math:`S_{ji}` is nonzero at most for one i, while all other blocks vanish.
67
-
68
-    If the operators are not in canonical form, they can be made so by
69
-    further splitting the Hamiltonian into smaller blocks, i.e. by adding
70
-    more projectors.
55
+
56
+    When computing scattering modes, the representation of the
57
+    modes is chosen to reflect declared discrete symmetries and
58
+    conservation laws.
59
+
60
+    `projectors` block diagonalize the Hamiltonian, and modes are computed
61
+    separately in each block. The ordering of blocks is the same as of
62
+    `projectors`. If `conservation_law` is declared in
63
+    `~kwant.builder.Builder`, `projectors` is computed as the projectors
64
+    onto its orthogonal eigensubspaces. The projectors are stored in the
65
+    order of ascending eigenvalues of `conservation_law`.
66
+
67
+    Symmetrization using discrete symmetries varies depending on whether
68
+    a conservation law/projectors are declared. Consider the case with no
69
+    conservation law declared. With `time_reversal` declared, the outgoing
70
+    modes are chosen as the time-reversed partners of the incoming modes,
71
+    i.e. :math:`\psi_{out}(-k) = T \psi_{in}(k)` with k the momentum.
72
+    `chiral` also relates incoming and outgoing modes, such that
73
+    :math:`\psi_{out}(k) = C \psi_{in}(k)`. `particle_hole` gives symmetric
74
+    incoming and outgoing modes separately, such that
75
+    :math:`\psi_{in/out}(-k) = P \psi_{in/out}(k)`, except when k=-k, at
76
+    k = 0 or :math:`\pi`. In this case, each mode is chosen as an eigenstate
77
+    :math:`P \psi = \psi` if :math:`P^2=1`. If :math:`P^2=-1`, we
78
+    symmetrize the modes by generating pairs of orthogonal modes
79
+    :math:`\psi` and :math:`P\psi`. Because `chiral` and `particle_hole`
80
+    flip the sign of energy, they only apply at zero energy.
81
+
82
+    Discrete symmetries can be combined with a conservation law if they
83
+    leave each block invariant or transform it to another block. With S
84
+    a discrete symmetry and :math:`P_i` and :math:`P_j` projectors onto
85
+    blocks i and j of the Hamiltonian, :math:`S_{ji} = P_j^+ S P_i` is the
86
+    symmetry projection that maps from block i to block j.
87
+    :math:`S_{ji} = P_j^+ S P_i` must for each j be nonzero for exactly
88
+    one i. If S leaves block i invariant, the modes within block i are
89
+    symmetrized using the nonzero projection :math:`S_{ii}`, like in the
90
+    case without a conservation law. If S transforms between blocks i and
91
+    j, the modes of the block with the larger index are obtained by
92
+    transforming the modes of the block with the lower index. Thus, with
93
+    :math:`\psi_i` and :math:`\psi_j` the modes of blocks i and j, we have
94
+    :math:`\psi_j = S_{ji} \psi_i`.
71 95
     """
72 96
     def __init__(self, projectors=None, time_reversal=None, particle_hole=None,
73 97
                  chiral=None):
... ...
@@ -68,8 +68,8 @@ def builder_to_model(syst, momenta=None, real_space=True,
68 68
     is used in finalized kwant systems.
69 69
     """
70 70
     def term_to_model(d, par, matrix):
71
-        if np.allclose(matrix, 0):
72
-            result = BlochModel({})
71
+        if allclose(matrix, 0):
72
+            result = BlochModel({}, shape=matrix.shape, format=np.ndarray)
73 73
         else:
74 74
             result = BlochModel({BlochCoeff(d, qsymm.sympify(par)): matrix},
75 75
                                 momenta=momenta)
... ...
@@ -323,8 +323,8 @@ def model_to_builder(model, norbs, lat_vecs, atom_coords, *, coeffs=None):
323 323
 
324 324
     # Keep track of the hoppings and onsites by storing those
325 325
     # which have already been set.
326
-    hopping_dict = defaultdict(dict)
327
-    onsites_dict = defaultdict(dict)
326
+    hopping_dict = defaultdict(lambda: 0)
327
+    onsites_dict = defaultdict(lambda: 0)
328 328
 
329 329
     # Iterate over all terms in the model.
330 330
     for key, hop_mat in model.items():
... ...
@@ -80,6 +80,13 @@ def test_conductivity():
80 80
         cond = kwant.kpm.conductivity(syst, alpha=alpha, beta=beta,
81 81
                                         positions=None, **kpm_params)
82 82
 
83
+    # test when 'norbs' not provided
84
+    n = lat.norbs
85
+    lat.norbs = None
86
+    cond = kwant.kpm.conductivity(syst, alpha=alpha, beta=beta,
87
+                                  positions=None, **kpm_params)
88
+    lat.norbs = n
89
+
83 90
     # test system or hamiltonian, no positions, but velocity operators
84 91
     cond_xx = kwant.kpm.conductivity(syst, alpha='x', beta='x',
85 92
                                      positions=None, **kpm_params)
... ...
@@ -19,7 +19,7 @@ from qsymm.symmetry_finder import symmetries
19 19
 from qsymm.hamiltonian_generator import bloch_family, hamiltonian_from_family
20 20
 from qsymm.groups import (hexagonal, PointGroupElement, spin_matrices,
21 21
                           spin_rotation, ContinuousGroupGenerator)
22
-from qsymm.model import Model, e, I, _commutative_momenta
22
+from qsymm.model import Model, BlochModel, BlochCoeff
23 23
 from qsymm.linalg import allclose
24 24
 
25 25
 import kwant
... ...
@@ -41,7 +41,7 @@ def test_honeycomb():
41 41
     syst[lat.neighbors(1)] = -1
42 42
 
43 43
     H = builder_to_model(syst)
44
-    sg, cs = symmetries(H, hexagonal(sympy_R=False), prettify=True)
44
+    sg, cs = symmetries(H, hexagonal(sympy_R=False))
45 45
     assert len(sg) == 24
46 46
     assert len(cs) == 0
47 47
 
... ...
@@ -52,7 +52,7 @@ def test_honeycomb():
52 52
     syst[lat.neighbors(1)] = lambda site1, site2, t: t
53 53
 
54 54
     H = builder_to_model(syst)
55
-    sg, cs = symmetries(H, hexagonal(sympy_R=False), prettify=True)
55
+    sg, cs = symmetries(H, hexagonal(sympy_R=False))
56 56
     assert len(sg) == 12
57 57
     assert len(cs) == 0
58 58
 
... ...
@@ -93,7 +93,7 @@ def test_higher_dim():
93 93
     syst[lat(0, 0, 0), lat(1, 0, -1)] = -1
94 94
 
95 95
     H = builder_to_model(syst)
96
-    sg, cs = symmetries(H, prettify=True)
96
+    sg, cs = symmetries(H)
97 97
     assert len(sg) == 2
98 98
     assert len(cs) == 5
99 99
 
... ...
@@ -107,7 +107,7 @@ def test_higher_dim():
107 107
     syst[lat(0, 0, 0), lat(1, 0, -1)] = -1
108 108
 
109 109
     H = builder_to_model(syst)
110
-    sg, cs = symmetries(H, hexagonal(sympy_R=False), prettify=True)
110
+    sg, cs = symmetries(H, hexagonal(sympy_R=False))
111 111
     assert len(sg) == 24
112 112
     assert len(cs) == 0
113 113
 
... ...
@@ -137,9 +137,9 @@ def test_graphene_to_kwant():
137 137
     family = bloch_family(hopping_vectors, symmetries, norbs)
138 138
     syst_from_family = model_to_builder(family, norbs, lat_vecs, atom_coords, coeffs=None)
139 139
     # Generate using a single Model object
140
-    g = sympy.Symbol('g', real=True)
141
-    ham = hamiltonian_from_family(family, coeffs=[g])
142
-    ham = Model(hamiltonian=ham, momenta=family[0].momenta)
140
+    g = sympy.Symbol('g')
141
+    # tosympy=False to return a BlochModel
142
+    ham = hamiltonian_from_family(family, coeffs=[g], tosympy=False)
143 143
     syst_from_model = model_to_builder(ham, norbs, lat_vecs, atom_coords)
144 144
 
145 145
     # Make the graphene Hamiltonian using kwant only
... ...
@@ -185,9 +185,9 @@ def test_graphene_to_kwant():
185 185
                Model({one: np.array([[0, 0], [0, 1]])}, momenta=family[0].momenta)]
186 186
     family = family + onsites
187 187
     syst_from_family = model_to_builder(family, norbs, lat_vecs, atom_coords, coeffs=None)
188
-    gs = list(sympy.symbols('g0:%d'%3, real=True))
189
-    ham = hamiltonian_from_family(family, coeffs=gs)
190
-    ham = Model(hamiltonian=ham, momenta=family[0].momenta)
188
+    gs = list(sympy.symbols('g0:3'))
189
+    # tosympy=False to return a BlochModel
190
+    ham = hamiltonian_from_family(family, coeffs=gs, tosympy=False)
191 191
     syst_from_model = model_to_builder(ham, norbs, lat_vecs, atom_coords)
192 192
 
193 193
     def onsite_A(site, c1):
... ...
@@ -277,16 +277,16 @@ def test_inverse_transform():
277 277
     # Hopping to a neighbouring atom one primitive lattice vector away
278 278
     hopping_vectors = [('A', 'A', [1, 0])]
279 279
     # Make family
280
-    family = bloch_family(hopping_vectors, symmetries, norbs)
280
+    family = bloch_family(hopping_vectors, symmetries, norbs, bloch_model=True)
281 281
     fam = hamiltonian_from_family(family, tosympy=False)
282 282
     # Atomic coordinates within the unit cell
283 283
     atom_coords = [(0, 0)]
284 284
     lat_vecs = [(1, 0), (0, 1)]
285 285
     syst = model_to_builder(fam, norbs, lat_vecs, atom_coords)
286 286
     # Convert it back
287
-    ham2 = builder_to_model(syst).tomodel(nsimplify=True)
287
+    ham2 = builder_to_model(syst)
288 288
     # Check that it's the same as the original
289
-    assert fam == ham2
289
+    assert fam.allclose(ham2)
290 290
 
291 291
     # Check that the Hamiltonians are identical at random points in the Brillouin zone
292 292
     sysw = kwant.wraparound.wraparound(syst).finalized()
... ...
@@ -314,12 +314,11 @@ def test_consistency_kwant():
314 314
     H += H.T.conj()
315 315
 
316 316
     # Make the 1D Model manually using only qsymm features.
317
-    c0, c1 = sympy.symbols('c0 c1', real=True)
318
-    kx = _commutative_momenta[0]
317
+    c0, c1 = sympy.symbols('c0 c1')
319 318
 
320
-    Ham = Model({c0 * e**(-I*kx): T}, momenta=['k_x'])
319
+    Ham = BlochModel({BlochCoeff(np.array([-1]), c0): T}, momenta=['k_x'])
321 320
     Ham += Ham.T().conj()
322
-    Ham += Model({c1: H}, momenta=['k_x'])
321
+    Ham += BlochModel({BlochCoeff(np.array([0]), c1): H}, momenta=['k_x'])
323 322
 
324 323
     # Two superimposed atoms, same number of orbitals on each
325 324
     norbs = OrderedDict([('A', orbs), ('B', orbs)])
... ...
@@ -367,8 +366,8 @@ def test_consistency_kwant():
367 366
                      np.exp(1j*k)*model_kwant_hop.T.conj()) # As in kwant.Bands
368 367
     h_model = Ham.lambdify()
369 368
     wsyst = kwant.wraparound.wraparound(model_syst).finalized()
370
-    for _ in range(20):
371
-        k = (np.random.rand() - 0.5)*2*np.pi
369
+    ks = np.linspace(-np.pi, np.pi, 21)
370
+    for k in ks:
372 371
         assert allclose(h_model_kwant(k), h_model(coeffs[0], coeffs[1], k))
373 372
         params['k_x'] = k
374 373
         h_wrap = wsyst.hamiltonian_submatrix(params=params)
... ...
@@ -376,11 +375,11 @@ def test_consistency_kwant():
376 375
 
377 376
     # Get the model back from the builder
378 377
     # From the Kwant builder based on original Model
379
-    Ham1 = builder_to_model(model_syst, momenta=Ham.momenta).tomodel(nsimplify=True)
378
+    Ham1 = builder_to_model(model_syst, momenta=Ham.momenta)
380 379
     # From the pure Kwant builder
381
-    Ham2 = builder_to_model(kwant_syst, momenta=Ham.momenta).tomodel(nsimplify=True)
382
-    assert Ham == Ham1
383
-    assert Ham == Ham2
380
+    Ham2 = builder_to_model(kwant_syst, momenta=Ham.momenta)
381
+    assert Ham.allclose(Ham1)
382
+    assert Ham.allclose(Ham2)
384 383
 
385 384
 
386 385
 def test_find_builder_discrete_symmetries():
... ...
@@ -405,12 +404,15 @@ def test_find_builder_discrete_symmetries():
405 404
         bulk[kwant.builder.HoppingKind((1, 0), lat)] = h_hop
406 405
         bulk[kwant.builder.HoppingKind((0, 1), lat)] = h_hop
407 406
 
407
+        # We need to specify 'prettify=True' here to ensure that we do not end up with
408
+        # an overcomplete set of symmetries. In some badly conditioned cases sparse=True
409
+        # or sparse=False may affect how many symmetries are found.
408 410
         builder_symmetries_default = find_builder_symmetries(bulk, spatial_symmetries=True,
409 411
                                                              prettify=True)
410 412
         builder_symmetries_sparse = find_builder_symmetries(bulk, spatial_symmetries=True,
411 413
                                                             prettify=True, sparse=True)
412 414
         builder_symmetries_dense = find_builder_symmetries(bulk, spatial_symmetries=True,
413
-                                                            prettify=True, sparse=False)
415
+                                                           prettify=True, sparse=False)
414 416
 
415 417
         assert len(builder_symmetries_default) == len(builder_symmetries_sparse)
416 418
         assert len(builder_symmetries_default) == len(builder_symmetries_dense)
... ...
@@ -476,7 +478,7 @@ def test_find_cons_law():
476 478
     syst[lat(1)] = np.kron(sy, ons)
477 479
     syst[lat(1), lat(0)] = np.kron(sy, hop)
478 480
 
479
-    builder_symmetries = find_builder_symmetries(syst, spatial_symmetries=False, prettify=True)
481
+    builder_symmetries = find_builder_symmetries(syst, spatial_symmetries=False)
480 482
     onsites = [symm for symm in builder_symmetries if
481 483
                isinstance(symm, qsymm.ContinuousGroupGenerator) and symm.R is None]
482 484
     mham = builder_to_model(syst)
... ...
@@ -508,8 +510,7 @@ def test_basis_ordering():
508 510
 
509 511
         # Find the symmetries of the square
510 512
         builder_symmetries = find_builder_symmetries(square,
511
-                                                     spatial_symmetries=False,
512
-                                                     prettify=True)
513
+                                                     spatial_symmetries=False)
513 514
         # Finalize the square, extract Hamiltonian
514 515
         fsquare = square.finalized()
515 516
         ham = fsquare.hamiltonian_submatrix()
... ...
@@ -588,7 +588,7 @@ def main():
588 588
               'plotting': 'matplotlib >= 1.5.1',
589 589
               'continuum': 'sympy >= 0.7.6',
590 590
               # qsymm is only packaged on PyPI
591
-              'qsymm': 'qsymm >= 1.1.2',
591
+              'qsymm': 'qsymm >= 1.2.6',
592 592
           },
593 593
           classifiers=[c.strip() for c in classifiers.split('\n')])
594 594