From 32c0972b63044352746dda9b086c3bc9db09475f Mon Sep 17 00:00:00 2001
From: Christoph Groth <christoph.groth@cea.fr>
Date: Fri, 12 May 2017 11:30:54 +0200
Subject: [PATCH] replace verbose by a printable builder

---
 .../images/continuum_discretizer.py.diff      |  33 +++--
 doc/source/tutorial/continuum_discretizer.py  |   6 +-
 doc/source/tutorial/discretizer.rst           |   4 +-
 kwant/continuum/discretizer.py                | 120 +++++++++++-------
 kwant/continuum/tests/test_discretizer.py     |  29 +----
 5 files changed, 92 insertions(+), 100 deletions(-)

diff --git a/doc/source/images/continuum_discretizer.py.diff b/doc/source/images/continuum_discretizer.py.diff
index 90cfb787..8902aaa0 100644
--- a/doc/source/images/continuum_discretizer.py.diff
+++ b/doc/source/images/continuum_discretizer.py.diff
@@ -1,15 +1,14 @@
 --- original
 +++ modified
-@@ -9,6 +9,8 @@
+@@ -9,6 +9,7 @@
  # --------------------------
  #  - discretizer module from kwant.continuum
  
 +import _defs
-+from contextlib import redirect_stdout
  
  import kwant
  import scipy.sparse.linalg
-@@ -20,9 +22,19 @@
+@@ -20,10 +21,19 @@
  from matplotlib import pyplot as plt
  
  
@@ -23,14 +22,14 @@
 +
  def stadium_system(L=20, W=20):
      hamiltonian = "k_x**2 + k_y**2 + V(x, y)"
--    template = kwant.continuum.discretize(hamiltonian, verbose=True)
+     template = kwant.continuum.discretize(hamiltonian)
+-    print(template)
 +    with open('discretizer_verbose.txt', 'w') as f:
-+        with redirect_stdout(f):
-+            template = kwant.continuum.discretize(hamiltonian, verbose=True)
++        print(template, file=f)
  
      def stadium(site):
          (x, y) = site.pos
-@@ -43,7 +55,7 @@
+@@ -44,7 +54,7 @@
      ham = syst.hamiltonian_submatrix(params=dict(V=potential), sparse=True)
      evecs = scipy.sparse.linalg.eigsh(ham, k=10, which='SM')[1]
      kwant.plotter.map(syst, abs(evecs[:, n])**2, show=False)
@@ -39,7 +38,7 @@
  
  
  def qsh_system(a=20, L=2000, W=1000):
-@@ -90,7 +102,8 @@
+@@ -91,7 +101,8 @@
      plt.ylim(-0.05, 0.05)
      plt.xlabel('momentum [1/A]')
      plt.ylabel('energy [eV]')
@@ -49,7 +48,7 @@
      # get scattering wave functions at E=0
      wf = kwant.wave_function(syst, energy=0, params=params)
  
-@@ -118,7 +131,7 @@
+@@ -119,7 +130,7 @@
  
      ax1.set_title('Probability density')
      ax2.set_title('Spin density')
@@ -58,7 +57,7 @@
  
  
  def lattice_spacing():
-@@ -159,7 +172,7 @@
+@@ -160,7 +171,7 @@
  
      plot(ax1, a=1)
      plot(ax2, a=.25)
@@ -67,27 +66,25 @@
  
  
  def substitutions():
-@@ -172,14 +185,20 @@
+@@ -173,15 +184,18 @@
          sympify('k_x**2 * sz + alpha * k_x * sx', subs=subs),
      )
  
 -    print(e[0] == e[1] == e[2])
 +    with open('discretizer_subs_1.txt', 'w') as f:
-+        with redirect_stdout(f):
-+            print(e[0] == e[1] == e[2])
++        print(e[0] == e[1] == e[2], file=f)
  
      subs = {'A': 'A(x) + B', 'V': 'V(x) + V_0', 'C': 5}
 -    print(sympify('k_x * A * k_x + V + C', subs=subs))
 +    with open('discretizer_subs_2.txt', 'w') as f:
-+        with redirect_stdout(f):
-+            print(sympify('k_x * A * k_x + V + C', subs=subs))
++        print(sympify('k_x * A * k_x + V + C', subs=subs), file=f)
  
  
  def main():
--    template = kwant.continuum.discretize('k_x * A(x) * k_x', verbose=True)
+     template = kwant.continuum.discretize('k_x * A(x) * k_x')
+-    print(template)
 +    with open('discretizer_intro_verbose.txt', 'w') as f:
-+        with redirect_stdout(f):
-+            kwant.continuum.discretize('k_x * A(x) * k_x', verbose=True)
++        print(template, file=f)
  
      syst = stadium_system()
      plot_eigenstate(syst)
diff --git a/doc/source/tutorial/continuum_discretizer.py b/doc/source/tutorial/continuum_discretizer.py
index 405a3adb..6e8866ff 100644
--- a/doc/source/tutorial/continuum_discretizer.py
+++ b/doc/source/tutorial/continuum_discretizer.py
@@ -23,7 +23,8 @@ from matplotlib import pyplot as plt
 def stadium_system(L=20, W=20):
 #HIDDEN_BEGIN_template
     hamiltonian = "k_x**2 + k_y**2 + V(x, y)"
-    template = kwant.continuum.discretize(hamiltonian, verbose=True)
+    template = kwant.continuum.discretize(hamiltonian)
+    print(template)
 #HIDDEN_END_template
 
 #HIDDEN_BEGIN_fill
@@ -204,7 +205,8 @@ def substitutions():
 
 def main():
 #HIDDEN_BEGIN_symbolic_discretization
-    template = kwant.continuum.discretize('k_x * A(x) * k_x', verbose=True)
+    template = kwant.continuum.discretize('k_x * A(x) * k_x')
+    print(template)
 #HIDDEN_END_symbolic_discretization
 
     syst = stadium_system()
diff --git a/doc/source/tutorial/discretizer.rst b/doc/source/tutorial/discretizer.rst
index 216f24b1..90d0408f 100644
--- a/doc/source/tutorial/discretizer.rst
+++ b/doc/source/tutorial/discretizer.rst
@@ -68,9 +68,7 @@ It is worth noting that ``discretize`` treats ``k_x`` and ``x`` as
 non-commuting operators, and so their order is preserved during the
 discretization process.
 
-Setting the ``verbose`` parameter to ``True`` prints extra information about the
-onsite and hopping functions assigned to the ``Builder`` produced
-by ``discretize``:
+The builder produced by ``discretize`` may be printed to show the source code of its onsite and hopping functions (this is a special feature of builders returned by ``discretize``):
 
 .. literalinclude:: ../images/discretizer_intro_verbose.txt
 
diff --git a/kwant/continuum/discretizer.py b/kwant/continuum/discretizer.py
index 68e55f5c..eaa190b1 100644
--- a/kwant/continuum/discretizer.py
+++ b/kwant/continuum/discretizer.py
@@ -7,6 +7,7 @@
 # http://kwant-project.org/authors.
 
 from collections import defaultdict
+import itertools
 
 import numpy as np
 import tinyarray as ta
@@ -25,18 +26,62 @@ from ._common import (sympify, gcd, position_operators, momentum_operators,
 __all__ = ['discretize']
 
 
-################ Globals variables and definitions
-
 _wf = sympy.Function('_internal_unique_name', commutative=False)
 _momentum_operators = {s.name: s for s in momentum_operators}
 _position_operators = {s.name: s for s in position_operators}
 _displacements = {s: sympy.Symbol('_internal_a_{}'.format(s)) for s in 'xyz'}
 
 
+class _DiscretizedBuilder(builder.Builder):
+    """A builder that is made from a discretized model and knows how to
+    pretty-print itself."""
+
+    def __init__(self, symmetry=None, discrete_coords=[], **kwargs):
+        super().__init__(symmetry, **kwargs)
+        self._discrete_coords = discrete_coords
+
+    def __str__(self):
+        result = []
+
+        sv = list(s for s in self.site_value_pairs())
+        if len(sv) != 1:
+            raise ValueError("Cannot pretty-print _DiscretizedBuilder: "
+                             "must contain a single site.")
+        site, site_value = sv[0]
+        if any(e != 0 for e in site.tag):
+            raise ValueError("Cannot pretty-print _DiscretizedBuilder: "
+                             "site must be located at origin.")
+
+        result.extend(["# Discrete coordinates: ",
+                       " ".join(self._discrete_coords),
+                       "\n\n"])
+
+        for key, val in itertools.chain(self.site_value_pairs(),
+                                        self.hopping_value_pairs()):
+            if isinstance(key, builder.Site):
+                result.append("# Onsite element:\n")
+            else:
+                a, b = key
+                assert a is site
+                result.extend(["# Hopping in direction ",
+                               str(tuple(b.tag)),
+                               ":\n"])
+            result.append(val._source if callable(val) else repr(val))
+            result.append('\n\n')
+
+        result.pop()
+
+        return "".join(result)
+
+    # For the Jupyter notebook:
+    def __repr_html__(self):
+        return self.__str__()
+
+
 ################ Interface functions
 
 def discretize(hamiltonian, discrete_coords=None, *, grid_spacing=1,
-               subs=None, verbose=False):
+               subs=None):
     """Construct a tight-binding model from a continuum Hamiltonian.
 
     This is a convenience function that is equivalent to first calling
@@ -66,8 +111,6 @@ def discretize(hamiltonian, discrete_coords=None, *, grid_spacing=1,
         proceeding further. For example:
         ``subs={'k': 'k_x + I * k_y'}`` or
         ``subs={'s_z': [[1, 0], [0, -1]]}``.
-    verbose : bool, default: False
-        If ``True`` additional information will be printed.
 
     Returns
     -------
@@ -75,14 +118,12 @@ def discretize(hamiltonian, discrete_coords=None, *, grid_spacing=1,
     with translational symmetry, which can be used as a template.
     """
     tb, coords = discretize_symbolic(hamiltonian, discrete_coords,
-                                     subs=subs, verbose=verbose)
+                                     subs=subs)
 
-    return build_discretized(tb, coords, grid_spacing=grid_spacing,
-                             verbose=verbose)
+    return build_discretized(tb, coords, grid_spacing=grid_spacing)
 
 
-def discretize_symbolic(hamiltonian, discrete_coords=None, *,
-                        subs=None, verbose=False):
+def discretize_symbolic(hamiltonian, discrete_coords=None, *, subs=None):
     """Discretize a continuous Hamiltonian into a tight-binding representation.
 
     The two objects returned by this function may be used directly as the first
@@ -109,8 +150,6 @@ def discretize_symbolic(hamiltonian, discrete_coords=None, *,
         proceeding further. For example:
         ``subs={'k': 'k_x + I * k_y'}`` or
         ``subs={'s_z': [[1, 0], [0, -1]]}``.
-    verbose : bool, default: False
-        If ``True`` additional information will be printed.
 
     Returns
     -------
@@ -146,10 +185,6 @@ def discretize_symbolic(hamiltonian, discrete_coords=None, *,
                          "your input. You can use the 'discrete_coords'"
                          "parameter to provide them.")
 
-    if verbose:
-        print('Discrete coordinates set to: ',
-              discrete_coords, end='\n\n')
-
     onsite_zeros = (0,) * len(discrete_coords)
 
     if not isinstance(hamiltonian, sympy.matrices.MatrixBase):
@@ -179,7 +214,7 @@ def discretize_symbolic(hamiltonian, discrete_coords=None, *,
 
 
 def build_discretized(tb_hamiltonian, discrete_coords, *,
-                      grid_spacing=1, subs=None, verbose=False):
+                      grid_spacing=1, subs=None):
     """Create a template Builder from a symbolic tight-binding Hamiltonian.
 
     This return values of `~kwant.continuum.discretize_symbolic` may be used
@@ -204,8 +239,6 @@ def build_discretized(tb_hamiltonian, discrete_coords, *,
         proceeding further. For example:
         ``subs={'k': 'k_x + I * k_y'}`` or
         ``subs={'s_z': [[1, 0], [0, -1]]}``.
-    verbose : bool, default: False
-        If ``True`` additional information will be printed.
 
     Returns
     -------
@@ -222,15 +255,7 @@ def build_discretized(tb_hamiltonian, discrete_coords, *,
     discrete_coords = sorted(discrete_coords)
 
     tb = {}
-    first = True
     for n, (offset, hopping) in enumerate(tb_hamiltonian.items()):
-        if verbose:
-            if first:
-                first = False
-            else:
-                print('\n')
-            print("Function generated for {}:".format(offset))
-
         onsite = all(i == 0 for i in offset)
 
         if onsite:
@@ -238,9 +263,8 @@ def build_discretized(tb_hamiltonian, discrete_coords, *,
         else:
             name = 'hopping_{}'.format(n)
 
-        tb[offset] = _value_function(hopping, discrete_coords,
-                                     grid_spacing, onsite, name,
-                                     verbose=verbose)
+        tb[offset] = _builder_value(hopping, discrete_coords,
+                                    grid_spacing, onsite, name)
 
     dim = len(discrete_coords)
     onsite_zeros = (0,) * dim
@@ -256,7 +280,8 @@ def build_discretized(tb_hamiltonian, discrete_coords, *,
     hoppings = {builder.HoppingKind(tuple(-i for i in d), lat): val
                 for d, val in tb.items()}
 
-    syst = builder.Builder(lattice.TranslationalSymmetry(*prim_vecs))
+    syst = _DiscretizedBuilder(lattice.TranslationalSymmetry(*prim_vecs),
+                               discrete_coords)
     syst[lat(*onsite_zeros)] = onsite
     for hop, val in hoppings.items():
         syst[hop] = val
@@ -507,9 +532,9 @@ def _assign_symbols(map_func_calls, grid_spacing,
     return lines
 
 
-def _value_function(expr, discrete_coords, grid_spacing, onsite,
-                    name='_anonymous_func', verbose=False):
-    """Generate a numeric function from a sympy expression.
+def _builder_value(expr, discrete_coords, grid_spacing, onsite,
+                   name='_anonymous_func'):
+    """Generate a builder value from a sympy expression.
 
     Parameters
     ----------
@@ -519,14 +544,15 @@ def _value_function(expr, discrete_coords, grid_spacing, onsite,
         List of coodinates present in the system.
     grid_spacing : int or float
         Lattice spacing of the system
-    verbose : bool, default: False
-        If True, the function body is printed.
 
     Returns
     -------
-    numerical function that can be used with Kwant.
+    `expr` transformed into an object that can be used as a
+    `kwant.builder.Builder` value.  Either a numerical value
+    (``tinyarray.array`` instance or complex number) or a value function.  In
+    the case of a function, the source code is available in its `_source`
+    attribute.
     """
-
     expr = expr.subs({sympy.Symbol('a'): grid_spacing})
     return_string, map_func_calls, const_symbols, _cache = _return_string(
         expr, discrete_coords=discrete_coords)
@@ -545,14 +571,9 @@ def _value_function(expr, discrete_coords, grid_spacing, onsite,
     if (not required_kwargs) and (discrete_coords is None):
         # we can just use a constant value instead of a value function
         if isinstance(expr, sympy.MatrixBase):
-            output = ta.array(expr.tolist(), complex)
+            return ta.array(expr.tolist(), complex)
         else:
-            output = complex(expr)
-
-        if verbose:
-            print("\n{}".format(output))
-
-        return output
+            return complex(expr)
 
     lines = _assign_symbols(map_func_calls, onsite=onsite,
                             grid_spacing=grid_spacing,
@@ -573,12 +594,13 @@ def _value_function(expr, discrete_coords, grid_spacing, onsite,
     namespace = {'pi': np.pi}
     namespace.update(_cache)
 
-    if verbose:
-        for k, v in _cache.items():
-            print("\n{} = (\n{})".format(k, repr(np.array(v))))
-        print('\n' + func_code)
+    source = []
+    for k, v in _cache.items():
+        source.append("{} = (\n{})\n".format(k, repr(np.array(v))))
+    source.append(func_code)
 
     exec(func_code, namespace)
     f = namespace[name]
+    f._source = "".join(source)
 
     return f
diff --git a/kwant/continuum/tests/test_discretizer.py b/kwant/continuum/tests/test_discretizer.py
index a724239a..7219c7f5 100644
--- a/kwant/continuum/tests/test_discretizer.py
+++ b/kwant/continuum/tests/test_discretizer.py
@@ -335,7 +335,7 @@ def test_numeric_functions_basic_symbolic():
 @pytest.mark.parametrize('commutative', [ True, False])
 def test_numeric_function_coords_from_site(commutative):
     tb = {(0,): sympy.symbols('x', commutative=commutative)}
-    builder = build_discretized(tb, 'x', verbose=True)
+    builder = build_discretized(tb, 'x')
 
     lat = next(iter(builder.sites()))[0]
     onsite = builder[lat(0)]
@@ -515,30 +515,3 @@ def test_numeric_functions_with_parameter():
                         rhs = f_num
 
                     assert np.allclose(lhs, rhs)
-
-
-def test_basic_verbose(capsys): # or use "capfd" for fd-level
-    discretize('k_x * A(x) * k_x', verbose=True)
-    out, err = capsys.readouterr()
-    assert "Discrete coordinates set to" in out
-    assert "Function generated for (0,)" in out
-
-
-def test_that_verbose_covers_all_hoppings(capsys):
-    discretize('k_x**2 + k_y**2 + k_x*k_y', verbose=True)
-    out, err = capsys.readouterr()
-
-    for tag in [(0, 1), (0, 0), (1, -1), (1, 1)]:
-        assert "Function generated for {}".format(tag) in out
-
-
-def test_verbose_cache(capsys):
-    discretize('[[k_x * A(x) * k_x]]', verbose=True)
-    out, err = capsys.readouterr()
-    assert '_cache_0' in out
-
-
-def test_no_output_when_verbose_false(capsys):
-    discretize('[[k_x * A(x) * k_x]]', verbose=False)
-    out, err = capsys.readouterr()
-    assert out == ''
-- 
GitLab