Browse code

update cell/inter-cell Hamiltonian to use symmetry information

Previously we checked whether the subgraphs of a term used site
arrays corresponding to sites >= cell_size, now we can directly
check that the symmetry element of a term is not the identity (zero).

Joseph Weston authored on 04/12/2019 16:40:42
Showing 1 changed files
... ...
@@ -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