From 5d2e4910d04cddac309103d801f83b7fdef4fc66 Mon Sep 17 00:00:00 2001
From: Bas Nijholt <basnijholt@gmail.com>
Date: Mon, 23 Sep 2019 13:26:49 +0200
Subject: [PATCH] add phase diagram code

---
 phase_diagram.ipynb | 616 ++++++++++++++++++++++++++++++++++++++++++++
 phase_diagram.py    | 472 +++++++++++++++++++++++++++++++++
 2 files changed, 1088 insertions(+)
 create mode 100644 phase_diagram.ipynb
 create mode 100644 phase_diagram.py

diff --git a/phase_diagram.ipynb b/phase_diagram.ipynb
new file mode 100644
index 0000000..4b73362
--- /dev/null
+++ b/phase_diagram.ipynb
@@ -0,0 +1,616 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%writefile phase_diagram.py\n",
+    "\n",
+    "\n",
+    "import inspect\n",
+    "import operator\n",
+    "import sys\n",
+    "from collections import OrderedDict\n",
+    "from copy import copy, deepcopy\n",
+    "from functools import wraps\n",
+    "from itertools import product\n",
+    "from types import SimpleNamespace\n",
+    "\n",
+    "import kwant\n",
+    "import numpy as np\n",
+    "import scipy.constants\n",
+    "from kwant.continuum.discretizer import discretize\n",
+    "from skimage import measure\n",
+    "\n",
+    "import adaptive\n",
+    "import pfaffian as pf\n",
+    "\n",
+    "assert sys.version_info >= (3, 6), \"Use Python ≥3.6\"\n",
+    "\n",
+    "# Parameters taken from arXiv:1204.2792\n",
+    "# All constant parameters, mostly fundamental constants, in a SimpleNamespace.\n",
+    "constants = SimpleNamespace(\n",
+    "    m_eff=0.015 * scipy.constants.m_e,  # effective mass in kg\n",
+    "    hbar=scipy.constants.hbar,\n",
+    "    m_e=scipy.constants.m_e,\n",
+    "    eV=scipy.constants.eV,\n",
+    "    e=scipy.constants.e,\n",
+    "    c=1e18 / (scipy.constants.eV * 1e-3),  # to get to meV * nm^2\n",
+    "    mu_B=scipy.constants.physical_constants[\"Bohr magneton in eV/T\"][0] * 1e3,\n",
+    ")\n",
+    "\n",
+    "constants.t = (constants.hbar ** 2 / (2 * constants.m_eff)) * constants.c\n",
+    "\n",
+    "\n",
+    "def get_names(sig):\n",
+    "    names = [\n",
+    "        (name, value)\n",
+    "        for name, value in sig.parameters.items()\n",
+    "        if value.kind\n",
+    "        in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)\n",
+    "    ]\n",
+    "    return OrderedDict(names)\n",
+    "\n",
+    "\n",
+    "def filter_kwargs(sig, names, kwargs):\n",
+    "    names_in_kwargs = [(name, value) for name, value in kwargs.items() if name in names]\n",
+    "    return OrderedDict(names_in_kwargs)\n",
+    "\n",
+    "\n",
+    "def skip_pars(names1, names2, num_skipped):\n",
+    "    skipped_pars1 = list(names1.keys())[:num_skipped]\n",
+    "    skipped_pars2 = list(names2.keys())[:num_skipped]\n",
+    "    if skipped_pars1 == skipped_pars2:\n",
+    "        pars1 = list(names1.values())[num_skipped:]\n",
+    "        pars2 = list(names2.values())[num_skipped:]\n",
+    "    else:\n",
+    "        raise Exception(\"First {} arguments \" \"have to be the same\".format(num_skipped))\n",
+    "    return pars1, pars2\n",
+    "\n",
+    "\n",
+    "def combine(f, g, operator, num_skipped=0):\n",
+    "    if not callable(f) or not callable(g):\n",
+    "        raise Exception(\"One of the functions is not a function\")\n",
+    "\n",
+    "    sig1 = inspect.signature(f)\n",
+    "    sig2 = inspect.signature(g)\n",
+    "\n",
+    "    names1 = get_names(sig1)\n",
+    "    names2 = get_names(sig2)\n",
+    "\n",
+    "    pars1, pars2 = skip_pars(names1, names2, num_skipped)\n",
+    "    skipped_pars = list(names1.values())[:num_skipped]\n",
+    "\n",
+    "    pars1_names = {p.name for p in pars1}\n",
+    "    pars2 = [p for p in pars2 if p.name not in pars1_names]\n",
+    "\n",
+    "    parameters = pars1 + pars2\n",
+    "    kind = inspect.Parameter.POSITIONAL_OR_KEYWORD\n",
+    "    parameters = [p.replace(kind=kind) for p in parameters]\n",
+    "    parameters = skipped_pars + parameters\n",
+    "\n",
+    "    def wrapped(*args):\n",
+    "        d = {p.name: arg for arg, p in zip(args, parameters)}\n",
+    "        fval = f(*[d[name] for name in names1.keys()])\n",
+    "        gval = g(*[d[name] for name in names2.keys()])\n",
+    "        return operator(fval, gval)\n",
+    "\n",
+    "    wrapped.__signature__ = inspect.Signature(parameters=parameters)\n",
+    "    return wrapped\n",
+    "\n",
+    "\n",
+    "def memoize(obj):\n",
+    "    cache = obj.cache = {}\n",
+    "\n",
+    "    @wraps(obj)\n",
+    "    def memoizer(*args, **kwargs):\n",
+    "        key = str(args) + str(kwargs)\n",
+    "        if key not in cache:\n",
+    "            cache[key] = obj(*args, **kwargs)\n",
+    "        return cache[key]\n",
+    "\n",
+    "    return memoizer\n",
+    "\n",
+    "\n",
+    "@memoize\n",
+    "def discretized_hamiltonian(a, which_lead=None):\n",
+    "    ham = (\n",
+    "        \"(0.5 * hbar**2 * (k_x**2 + k_y**2 + k_z**2) / m_eff * c - mu + V) * kron(sigma_0, sigma_z) + \"\n",
+    "        \"alpha * (k_y * kron(sigma_x, sigma_z) - k_x * kron(sigma_y, sigma_z)) + \"\n",
+    "        \"0.5 * g * mu_B * (B_x * kron(sigma_x, sigma_0) + B_y * kron(sigma_y, sigma_0) + B_z * kron(sigma_z, sigma_0)) + \"\n",
+    "        \"Delta * kron(sigma_0, sigma_x)\"\n",
+    "    )\n",
+    "\n",
+    "    subst_sm = {\"Delta\": 0}\n",
+    "\n",
+    "    if which_lead is not None:\n",
+    "        subst_sm[\"V\"] = f\"V_{which_lead}(z, V_0, V_r, V_l, x0, sigma, r1)\"\n",
+    "        subst_sm[\"mu\"] = f\"mu_{which_lead}(x0, sigma, mu_lead, mu_wire)\"\n",
+    "    else:\n",
+    "        subst_sm[\"V\"] = \"V(x, z, V_0, V_r, V_l, x0, sigma, r1)\"\n",
+    "        subst_sm[\"mu\"] = \"mu(x, x0, sigma, mu_lead, mu_wire)\"\n",
+    "\n",
+    "    subst_sc = {\"g\": 0, \"alpha\": 0, \"mu\": \"mu_sc\", \"V\": 0}\n",
+    "    subst_interface = {\"c\": \"c * c_tunnel\", \"alpha\": 0, \"V\": 0}\n",
+    "\n",
+    "    templ_sm = discretize(ham, locals=subst_sm, grid_spacing=a)\n",
+    "    templ_sc = discretize(ham, locals=subst_sc, grid_spacing=a)\n",
+    "    templ_interface = discretize(ham, locals=subst_interface, grid_spacing=a)\n",
+    "\n",
+    "    return templ_sm, templ_sc, templ_interface\n",
+    "\n",
+    "\n",
+    "def cylinder_sector(r_out, r_in=0, L=1, L0=0, coverage_angle=360, angle=0, a=10):\n",
+    "    \"\"\"Returns the shape function and start coords for a wire with\n",
+    "    as cylindrical cross section.\n",
+    "\n",
+    "    Parameters\n",
+    "    ----------\n",
+    "    r_out : int\n",
+    "        Outer radius in nm.\n",
+    "    r_in : int, optional\n",
+    "        Inner radius in nm.\n",
+    "    L : int, optional\n",
+    "        Length of wire from L0 in nm, -1 if infinite in x-direction.\n",
+    "    L0 : int, optional\n",
+    "        Start position in x.\n",
+    "    coverage_angle : int, optional\n",
+    "        Coverage angle in degrees.\n",
+    "    angle : int, optional\n",
+    "        Angle of tilting from top in degrees.\n",
+    "    a : int, optional\n",
+    "        Discretization constant in nm.\n",
+    "\n",
+    "    Returns\n",
+    "    -------\n",
+    "    (shape_func, *(start_coords))\n",
+    "    \"\"\"\n",
+    "    coverage_angle *= np.pi / 360\n",
+    "    angle *= np.pi / 180\n",
+    "    r_out_sq, r_in_sq = r_out ** 2, r_in ** 2\n",
+    "\n",
+    "    def shape(site):\n",
+    "        try:\n",
+    "            x, y, z = site.pos\n",
+    "        except AttributeError:\n",
+    "            x, y, z = site\n",
+    "        n = (y + 1j * z) * np.exp(1j * angle)\n",
+    "        y, z = n.real, n.imag\n",
+    "        rsq = y ** 2 + z ** 2\n",
+    "        shape_yz = r_in_sq <= rsq < r_out_sq and z >= np.cos(coverage_angle) * np.sqrt(\n",
+    "            rsq\n",
+    "        )\n",
+    "        return (shape_yz and L0 <= x < L) if L > 0 else shape_yz\n",
+    "\n",
+    "    r_mid = (r_out + r_in) / 2\n",
+    "    start_coords = np.array([L - a, r_mid * np.sin(angle), r_mid * np.cos(angle)])\n",
+    "\n",
+    "    return shape, start_coords\n",
+    "\n",
+    "\n",
+    "def is_antisymmetric(H):\n",
+    "    return np.allclose(-H, H.T)\n",
+    "\n",
+    "\n",
+    "def cell_mats(lead, params, bias=0):\n",
+    "    h = lead.cell_hamiltonian(params=params)\n",
+    "    h -= bias * np.identity(len(h))\n",
+    "    t = lead.inter_cell_hopping(params=params)\n",
+    "    return h, t\n",
+    "\n",
+    "\n",
+    "def get_h_k(lead, params):\n",
+    "    h, t = cell_mats(lead, params)\n",
+    "\n",
+    "    def h_k(k):\n",
+    "        return h + t * np.exp(1j * k) + t.T.conj() * np.exp(-1j * k)\n",
+    "\n",
+    "    return h_k\n",
+    "\n",
+    "\n",
+    "def make_skew_symmetric(ham):\n",
+    "    \"\"\"\n",
+    "    Makes a skew symmetric matrix by a matrix multiplication of a unitary\n",
+    "    matrix U. This unitary matrix is taken from the Topology MOOC 0D, but\n",
+    "    that is in a different basis. To get to the right basis one multiplies\n",
+    "    by [[np.eye(2), 0], [0, sigma_y]].\n",
+    "\n",
+    "    Parameters:\n",
+    "    -----------\n",
+    "    ham : numpy.ndarray\n",
+    "        Hamiltonian matrix gotten from sys.cell_hamiltonian()\n",
+    "\n",
+    "    Returns:\n",
+    "    --------\n",
+    "    skew_ham : numpy.ndarray\n",
+    "        Skew symmetrized Hamiltonian\n",
+    "    \"\"\"\n",
+    "    W = ham.shape[0] // 4\n",
+    "    I = np.eye(2, dtype=complex)\n",
+    "    sigma_y = np.array([[0, 1j], [-1j, 0]], dtype=complex)\n",
+    "    U_1 = np.bmat([[I, I], [1j * I, -1j * I]])\n",
+    "    U_2 = np.bmat([[I, 0 * I], [0 * I, sigma_y]])\n",
+    "    U = U_1 @ U_2\n",
+    "    U = np.kron(np.eye(W, dtype=complex), U)\n",
+    "    skew_ham = U @ ham @ U.H\n",
+    "\n",
+    "    assert is_antisymmetric(skew_ham)\n",
+    "\n",
+    "    return skew_ham\n",
+    "\n",
+    "\n",
+    "def calculate_pfaffian(lead, params):\n",
+    "    \"\"\"\n",
+    "    Calculates the Pfaffian for the infinite system by computing it at k = 0\n",
+    "    and k = pi.\n",
+    "\n",
+    "    Parameters:\n",
+    "    -----------\n",
+    "    lead : kwant.builder.InfiniteSystem object\n",
+    "          The finalized system.\n",
+    "\n",
+    "    \"\"\"\n",
+    "    h_k = get_h_k(lead, params)\n",
+    "\n",
+    "    skew_h0 = make_skew_symmetric(h_k(0))\n",
+    "    skew_h_pi = make_skew_symmetric(h_k(np.pi))\n",
+    "\n",
+    "    pf_0 = np.sign(pf.pfaffian(1j * skew_h0, sign_only=True).real)\n",
+    "    pf_pi = np.sign(pf.pfaffian(1j * skew_h_pi, sign_only=True).real)\n",
+    "    pfaf = pf_0 * pf_pi\n",
+    "\n",
+    "    return pfaf\n",
+    "\n",
+    "\n",
+    "def at_interface(site1, site2, shape1, shape2):\n",
+    "    return (shape1[0](site1) and shape2[0](site2)) or (\n",
+    "        shape2[0](site1) and shape1[0](site2)\n",
+    "    )\n",
+    "\n",
+    "\n",
+    "def change_hopping_at_interface(syst, template, shape1, shape2):\n",
+    "    for (site1, site2), hop in syst.hopping_value_pairs():\n",
+    "        if at_interface(site1, site2, shape1, shape2):\n",
+    "            syst[site1, site2] = template[site1, site2]\n",
+    "    return syst\n",
+    "\n",
+    "\n",
+    "@memoize\n",
+    "def make_lead(a, r1, r2, coverage_angle, angle, with_shell, which_lead):\n",
+    "    \"\"\"Create an infinite cylindrical 3D wire partially covered with a\n",
+    "    superconducting (SC) shell.\n",
+    "\n",
+    "    Parameters\n",
+    "    ----------\n",
+    "    a : int\n",
+    "        Discretization constant in nm.\n",
+    "    r1 : int\n",
+    "        Radius of normal part of wire in nm.\n",
+    "    r2 : int\n",
+    "        Radius of superconductor in nm.\n",
+    "    coverage_angle : int\n",
+    "        Coverage angle of superconductor in degrees.\n",
+    "    angle : int\n",
+    "        Angle of tilting of superconductor from top in degrees.\n",
+    "    with_shell : bool\n",
+    "        Adds shell to the scattering area. If False no SC shell is added and\n",
+    "        only a cylindrical wire will be created.\n",
+    "    which_lead : str\n",
+    "        Name of the potential function of the lead, e.g. `which_lead = 'left'` will\n",
+    "        require a function `V_left(z, V_0)` and\n",
+    "        `mu_left(mu_func(x, x0, sigma, mu_lead, mu_wire)`.\n",
+    "\n",
+    "    Returns\n",
+    "    -------\n",
+    "    syst : kwant.builder.InfiniteSystem\n",
+    "        The finilized kwant system.\n",
+    "\n",
+    "    Examples\n",
+    "    --------\n",
+    "    This doesn't use default parameters because the variables need to be saved,\n",
+    "    to a file. So I create a dictionary that is passed to the function.\n",
+    "\n",
+    "    >>> syst_params = dict(a=10, angle=0, coverage_angle=185, r1=50,\n",
+    "    ...                    r2=70, with_shell=True)\n",
+    "    >>> syst, hopping = make_lead(**syst_params)\n",
+    "    \"\"\"\n",
+    "\n",
+    "    shape_normal_lead = cylinder_sector(r_out=r1, angle=angle, L=-1, a=a)\n",
+    "    shape_sc_lead = cylinder_sector(\n",
+    "        r_out=r2, r_in=r1, coverage_angle=coverage_angle, angle=angle, L=-1, a=a\n",
+    "    )\n",
+    "\n",
+    "    sz = np.array([[1, 0], [0, -1]])\n",
+    "    cons_law = np.kron(np.eye(2), -sz)\n",
+    "    symmetry = kwant.TranslationalSymmetry((a, 0, 0))\n",
+    "    lead = kwant.Builder(\n",
+    "        symmetry, conservation_law=cons_law if not with_shell else None\n",
+    "    )\n",
+    "\n",
+    "    templ_sm, templ_sc, templ_interface = discretized_hamiltonian(\n",
+    "        a, which_lead=which_lead\n",
+    "    )\n",
+    "    templ_sm = apply_peierls_to_template(templ_sm)\n",
+    "    lead.fill(templ_sm, *shape_normal_lead)\n",
+    "\n",
+    "    if with_shell:\n",
+    "        lat = templ_sc.lattice\n",
+    "        shape_sc = cylinder_sector(\n",
+    "            r_out=r2, r_in=r1, coverage_angle=coverage_angle, angle=angle, L=a, a=a\n",
+    "        )\n",
+    "\n",
+    "        xyz_offset = get_offset(*shape_sc, lat)\n",
+    "\n",
+    "        templ_sc = apply_peierls_to_template(templ_sc, xyz_offset=xyz_offset)\n",
+    "        templ_interface = apply_peierls_to_template(templ_interface)\n",
+    "        lead.fill(templ_sc, *shape_sc_lead)\n",
+    "\n",
+    "        # Adding a tunnel barrier between SM and SC\n",
+    "        lead = change_hopping_at_interface(\n",
+    "            lead, templ_interface, shape_normal_lead, shape_sc_lead\n",
+    "        )\n",
+    "\n",
+    "    return lead\n",
+    "\n",
+    "\n",
+    "def apply_peierls_to_template(template, xyz_offset=(0, 0, 0)):\n",
+    "    \"\"\"Adds p.orbital argument to the hopping functions.\"\"\"\n",
+    "    template = deepcopy(template)  # Needed because kwant.Builder is mutable\n",
+    "    x0, y0, z0 = xyz_offset\n",
+    "    lat = template.lattice\n",
+    "    a = np.max(lat.prim_vecs)  # lattice contant\n",
+    "\n",
+    "    def phase(site1, site2, B_x, B_y, B_z, orbital, e, hbar):\n",
+    "        if orbital:\n",
+    "            x, y, z = site1.tag\n",
+    "            direction = site1.tag - site2.tag\n",
+    "            A = [B_y * (z - z0) - B_z * (y - y0), 0, B_x * (y - y0)]\n",
+    "            A = np.dot(A, direction) * a ** 2 * 1e-18 * e / hbar\n",
+    "            phase = np.exp(-1j * A)\n",
+    "            if lat.norbs == 2:  # No PH degrees of freedom\n",
+    "                return phase\n",
+    "            elif lat.norbs == 4:\n",
+    "                return np.array(\n",
+    "                    [phase, phase.conj(), phase, phase.conj()], dtype=\"complex128\"\n",
+    "                )\n",
+    "        else:  # No orbital phase\n",
+    "            return 1\n",
+    "\n",
+    "    for (site1, site2), hop in template.hopping_value_pairs():\n",
+    "        template[site1, site2] = combine(hop, phase, operator.mul, 2)\n",
+    "    return template\n",
+    "\n",
+    "\n",
+    "def get_offset(shape, start, lat):\n",
+    "    a = np.max(lat.prim_vecs)\n",
+    "    coords = [site.pos for site in lat.shape(shape, start)()]\n",
+    "    xyz_offset = np.mean(coords, axis=0)\n",
+    "    return xyz_offset\n",
+    "\n",
+    "\n",
+    "def translation_ev(h, t, tol=1e6):\n",
+    "    \"\"\"Compute the eigenvalues of the translation operator of a lead.\n",
+    "\n",
+    "    Adapted from kwant.physics.leads.modes.\n",
+    "\n",
+    "    Parameters\n",
+    "    ----------\n",
+    "    h : numpy array, real or complex, shape (N, N) The unit cell\n",
+    "        Hamiltonian of the lead unit cell.\n",
+    "    t : numpy array, real or complex, shape (N, M)\n",
+    "        The hopping matrix from a lead cell to the one on which self-energy\n",
+    "        has to be calculated (and any other hopping in the same direction).\n",
+    "    tol : float\n",
+    "        Numbers and differences are considered zero when they are smaller\n",
+    "        than `tol` times the machine precision.\n",
+    "\n",
+    "    Returns\n",
+    "    -------\n",
+    "    ev : numpy array\n",
+    "        Eigenvalues of the translation operator in the form lambda=r*exp(i*k),\n",
+    "        for |r|=1 they are propagating modes.\n",
+    "    \"\"\"\n",
+    "    a, b = kwant.physics.leads.setup_linsys(h, t, tol, None).eigenproblem\n",
+    "    ev = kwant.physics.leads.unified_eigenproblem(a, b, tol=tol)[0]\n",
+    "    return ev\n",
+    "\n",
+    "\n",
+    "def gap_minimizer(lead, params, energy):\n",
+    "    \"\"\"Function that minimizes a function to find the band gap.\n",
+    "    This objective function checks if there are progagating modes at a\n",
+    "    certain energy. Returns zero if there is a propagating mode.\n",
+    "\n",
+    "    Parameters\n",
+    "    ----------\n",
+    "    lead : kwant.builder.InfiniteSystem object\n",
+    "        The finalized infinite system.\n",
+    "    params : dict\n",
+    "        A dict that is used to store Hamiltonian parameters.\n",
+    "    energy : float\n",
+    "        Energy at which this function checks for propagating modes.\n",
+    "\n",
+    "    Returns\n",
+    "    -------\n",
+    "    minimized_scalar : float\n",
+    "        Value that is zero when there is a propagating mode.\n",
+    "    \"\"\"\n",
+    "    h, t = cell_mats(lead, params, bias=energy)\n",
+    "    ev = translation_ev(h, t)\n",
+    "    norm = (ev * ev.conj()).real\n",
+    "    return np.min(np.abs(norm - 1))\n",
+    "\n",
+    "\n",
+    "def gap_from_modes(lead, params, tol=1e-6):\n",
+    "    \"\"\"Finds the gapsize by peforming a binary search of the modes with a\n",
+    "    tolarance of tol.\n",
+    "\n",
+    "    Parameters\n",
+    "    ----------\n",
+    "    lead : kwant.builder.InfiniteSystem object\n",
+    "        The finalized infinite system.\n",
+    "    params : dict\n",
+    "        A dict that is used to store Hamiltonian parameters.\n",
+    "    tol : float\n",
+    "        The precision of the binary search.\n",
+    "\n",
+    "    Returns\n",
+    "    -------\n",
+    "    gap : float\n",
+    "        Size of the gap.\n",
+    "\n",
+    "    Notes\n",
+    "    -----\n",
+    "    For use with `lead = funcs.make_lead()`.\n",
+    "    \"\"\"\n",
+    "    Es = kwant.physics.Bands(lead, params=params)(k=0)\n",
+    "    lim = [0, np.abs(Es).min()]\n",
+    "    if gap_minimizer(lead, params, energy=0) < 1e-15:\n",
+    "        # No band gap\n",
+    "        gap = 0\n",
+    "    else:\n",
+    "        while lim[1] - lim[0] > tol:\n",
+    "            energy = sum(lim) / 2\n",
+    "            par = gap_minimizer(lead, params, energy)\n",
+    "            if par < 1e-10:\n",
+    "                lim[1] = energy\n",
+    "            else:\n",
+    "                lim[0] = energy\n",
+    "        gap = sum(lim) / 2\n",
+    "    return gap"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import adaptive\n",
+    "adaptive.notebook_extension()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\n",
+    "import phase_diagram as funcs\n",
+    "\n",
+    "lead_pars = dict(\n",
+    "    a=10, r1=50, r2=70, coverage_angle=135, angle=45, with_shell=True, which_lead=''\n",
+    ")\n",
+    "\n",
+    "params = dict(\n",
+    "    alpha=20,\n",
+    "    B_x=0,\n",
+    "    B_y=0,\n",
+    "    B_z=0,\n",
+    "    Delta=110,\n",
+    "    g=50,\n",
+    "    orbital=True,\n",
+    "    mu_sc=100,\n",
+    "    c_tunnel=3 / 4,\n",
+    "    V_r=-50,\n",
+    "    intrinsic_sc=False,\n",
+    "    mu_=lambda x0, sigma, mu_lead, mu_wire: mu_lead,\n",
+    "    V_=lambda z, V_0, V_r, V_l, x0, sigma, r1: 0,\n",
+    "    V_0=None,\n",
+    "    V_l=None,\n",
+    "    mu_lead=10,\n",
+    "    mu_wire=None,\n",
+    "    r1=None,\n",
+    "    sigma=None,\n",
+    "    x0=None,\n",
+    "    **funcs.constants.__dict__\n",
+    ")\n",
+    "\n",
+    "def pf(xy, params=params, lead_pars=lead_pars):\n",
+    "    import phase_diagram as funcs\n",
+    "    params[\"B_x\"], params[\"mu_lead\"] = xy\n",
+    "    lead = funcs.make_lead(**lead_pars).finalized()\n",
+    "    return funcs.calculate_pfaffian(lead, params)\n",
+    "\n",
+    "def smallest_gap(xy, params=params, lead_pars=lead_pars):\n",
+    "    import phase_diagram as funcs\n",
+    "    params[\"B_x\"], params[\"mu_lead\"] = xy\n",
+    "    lead = funcs.make_lead(**lead_pars).finalized()\n",
+    "    pf = funcs.calculate_pfaffian(lead, params)\n",
+    "    gap = funcs.gap_from_modes(lead, params)\n",
+    "    return pf * gap "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "learner = adaptive.Learner2D(smallest_gap, bounds=[(0, 2), (0, 35)])\n",
+    "# learner.load('phase_diagram.pickle')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "runner = adaptive.Runner(learner, goal=lambda l: l.npoints > 60000)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "runner.live_info()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%output size=100\n",
+    "learner.plot(tri_alpha=0.4, n=None)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "learner.save('phase_diagram.pickle')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "language_info": {
+   "name": "python",
+   "pygments_lexer": "ipython3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/phase_diagram.py b/phase_diagram.py
new file mode 100644
index 0000000..f154fca
--- /dev/null
+++ b/phase_diagram.py
@@ -0,0 +1,472 @@
+
+
+import inspect
+import operator
+import sys
+from collections import OrderedDict
+from copy import copy, deepcopy
+from functools import wraps
+from itertools import product
+from types import SimpleNamespace
+
+import kwant
+import numpy as np
+import scipy.constants
+from kwant.continuum.discretizer import discretize
+from skimage import measure
+
+import adaptive
+import pfaffian as pf
+
+assert sys.version_info >= (3, 6), "Use Python ≥3.6"
+
+# Parameters taken from arXiv:1204.2792
+# All constant parameters, mostly fundamental constants, in a SimpleNamespace.
+constants = SimpleNamespace(
+    m_eff=0.015 * scipy.constants.m_e,  # effective mass in kg
+    hbar=scipy.constants.hbar,
+    m_e=scipy.constants.m_e,
+    eV=scipy.constants.eV,
+    e=scipy.constants.e,
+    c=1e18 / (scipy.constants.eV * 1e-3),  # to get to meV * nm^2
+    mu_B=scipy.constants.physical_constants["Bohr magneton in eV/T"][0] * 1e3,
+)
+
+constants.t = (constants.hbar ** 2 / (2 * constants.m_eff)) * constants.c
+
+
+def get_names(sig):
+    names = [
+        (name, value)
+        for name, value in sig.parameters.items()
+        if value.kind
+        in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
+    ]
+    return OrderedDict(names)
+
+
+def filter_kwargs(sig, names, kwargs):
+    names_in_kwargs = [(name, value) for name, value in kwargs.items() if name in names]
+    return OrderedDict(names_in_kwargs)
+
+
+def skip_pars(names1, names2, num_skipped):
+    skipped_pars1 = list(names1.keys())[:num_skipped]
+    skipped_pars2 = list(names2.keys())[:num_skipped]
+    if skipped_pars1 == skipped_pars2:
+        pars1 = list(names1.values())[num_skipped:]
+        pars2 = list(names2.values())[num_skipped:]
+    else:
+        raise Exception("First {} arguments " "have to be the same".format(num_skipped))
+    return pars1, pars2
+
+
+def combine(f, g, operator, num_skipped=0):
+    if not callable(f) or not callable(g):
+        raise Exception("One of the functions is not a function")
+
+    sig1 = inspect.signature(f)
+    sig2 = inspect.signature(g)
+
+    names1 = get_names(sig1)
+    names2 = get_names(sig2)
+
+    pars1, pars2 = skip_pars(names1, names2, num_skipped)
+    skipped_pars = list(names1.values())[:num_skipped]
+
+    pars1_names = {p.name for p in pars1}
+    pars2 = [p for p in pars2 if p.name not in pars1_names]
+
+    parameters = pars1 + pars2
+    kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
+    parameters = [p.replace(kind=kind) for p in parameters]
+    parameters = skipped_pars + parameters
+
+    def wrapped(*args):
+        d = {p.name: arg for arg, p in zip(args, parameters)}
+        fval = f(*[d[name] for name in names1.keys()])
+        gval = g(*[d[name] for name in names2.keys()])
+        return operator(fval, gval)
+
+    wrapped.__signature__ = inspect.Signature(parameters=parameters)
+    return wrapped
+
+
+def memoize(obj):
+    cache = obj.cache = {}
+
+    @wraps(obj)
+    def memoizer(*args, **kwargs):
+        key = str(args) + str(kwargs)
+        if key not in cache:
+            cache[key] = obj(*args, **kwargs)
+        return cache[key]
+
+    return memoizer
+
+
+@memoize
+def discretized_hamiltonian(a, which_lead=None):
+    ham = (
+        "(0.5 * hbar**2 * (k_x**2 + k_y**2 + k_z**2) / m_eff * c - mu + V) * kron(sigma_0, sigma_z) + "
+        "alpha * (k_y * kron(sigma_x, sigma_z) - k_x * kron(sigma_y, sigma_z)) + "
+        "0.5 * g * mu_B * (B_x * kron(sigma_x, sigma_0) + B_y * kron(sigma_y, sigma_0) + B_z * kron(sigma_z, sigma_0)) + "
+        "Delta * kron(sigma_0, sigma_x)"
+    )
+
+    subst_sm = {"Delta": 0}
+
+    if which_lead is not None:
+        subst_sm["V"] = f"V_{which_lead}(z, V_0, V_r, V_l, x0, sigma, r1)"
+        subst_sm["mu"] = f"mu_{which_lead}(x0, sigma, mu_lead, mu_wire)"
+    else:
+        subst_sm["V"] = "V(x, z, V_0, V_r, V_l, x0, sigma, r1)"
+        subst_sm["mu"] = "mu(x, x0, sigma, mu_lead, mu_wire)"
+
+    subst_sc = {"g": 0, "alpha": 0, "mu": "mu_sc", "V": 0}
+    subst_interface = {"c": "c * c_tunnel", "alpha": 0, "V": 0}
+
+    templ_sm = discretize(ham, locals=subst_sm, grid_spacing=a)
+    templ_sc = discretize(ham, locals=subst_sc, grid_spacing=a)
+    templ_interface = discretize(ham, locals=subst_interface, grid_spacing=a)
+
+    return templ_sm, templ_sc, templ_interface
+
+
+def cylinder_sector(r_out, r_in=0, L=1, L0=0, coverage_angle=360, angle=0, a=10):
+    """Returns the shape function and start coords for a wire with
+    as cylindrical cross section.
+
+    Parameters
+    ----------
+    r_out : int
+        Outer radius in nm.
+    r_in : int, optional
+        Inner radius in nm.
+    L : int, optional
+        Length of wire from L0 in nm, -1 if infinite in x-direction.
+    L0 : int, optional
+        Start position in x.
+    coverage_angle : int, optional
+        Coverage angle in degrees.
+    angle : int, optional
+        Angle of tilting from top in degrees.
+    a : int, optional
+        Discretization constant in nm.
+
+    Returns
+    -------
+    (shape_func, *(start_coords))
+    """
+    coverage_angle *= np.pi / 360
+    angle *= np.pi / 180
+    r_out_sq, r_in_sq = r_out ** 2, r_in ** 2
+
+    def shape(site):
+        try:
+            x, y, z = site.pos
+        except AttributeError:
+            x, y, z = site
+        n = (y + 1j * z) * np.exp(1j * angle)
+        y, z = n.real, n.imag
+        rsq = y ** 2 + z ** 2
+        shape_yz = r_in_sq <= rsq < r_out_sq and z >= np.cos(coverage_angle) * np.sqrt(
+            rsq
+        )
+        return (shape_yz and L0 <= x < L) if L > 0 else shape_yz
+
+    r_mid = (r_out + r_in) / 2
+    start_coords = np.array([L - a, r_mid * np.sin(angle), r_mid * np.cos(angle)])
+
+    return shape, start_coords
+
+
+def is_antisymmetric(H):
+    return np.allclose(-H, H.T)
+
+
+def cell_mats(lead, params, bias=0):
+    h = lead.cell_hamiltonian(params=params)
+    h -= bias * np.identity(len(h))
+    t = lead.inter_cell_hopping(params=params)
+    return h, t
+
+
+def get_h_k(lead, params):
+    h, t = cell_mats(lead, params)
+
+    def h_k(k):
+        return h + t * np.exp(1j * k) + t.T.conj() * np.exp(-1j * k)
+
+    return h_k
+
+
+def make_skew_symmetric(ham):
+    """
+    Makes a skew symmetric matrix by a matrix multiplication of a unitary
+    matrix U. This unitary matrix is taken from the Topology MOOC 0D, but
+    that is in a different basis. To get to the right basis one multiplies
+    by [[np.eye(2), 0], [0, sigma_y]].
+
+    Parameters:
+    -----------
+    ham : numpy.ndarray
+        Hamiltonian matrix gotten from sys.cell_hamiltonian()
+
+    Returns:
+    --------
+    skew_ham : numpy.ndarray
+        Skew symmetrized Hamiltonian
+    """
+    W = ham.shape[0] // 4
+    I = np.eye(2, dtype=complex)
+    sigma_y = np.array([[0, 1j], [-1j, 0]], dtype=complex)
+    U_1 = np.bmat([[I, I], [1j * I, -1j * I]])
+    U_2 = np.bmat([[I, 0 * I], [0 * I, sigma_y]])
+    U = U_1 @ U_2
+    U = np.kron(np.eye(W, dtype=complex), U)
+    skew_ham = U @ ham @ U.H
+
+    assert is_antisymmetric(skew_ham)
+
+    return skew_ham
+
+
+def calculate_pfaffian(lead, params):
+    """
+    Calculates the Pfaffian for the infinite system by computing it at k = 0
+    and k = pi.
+
+    Parameters:
+    -----------
+    lead : kwant.builder.InfiniteSystem object
+          The finalized system.
+
+    """
+    h_k = get_h_k(lead, params)
+
+    skew_h0 = make_skew_symmetric(h_k(0))
+    skew_h_pi = make_skew_symmetric(h_k(np.pi))
+
+    pf_0 = np.sign(pf.pfaffian(1j * skew_h0, sign_only=True).real)
+    pf_pi = np.sign(pf.pfaffian(1j * skew_h_pi, sign_only=True).real)
+    pfaf = pf_0 * pf_pi
+
+    return pfaf
+
+
+def at_interface(site1, site2, shape1, shape2):
+    return (shape1[0](site1) and shape2[0](site2)) or (
+        shape2[0](site1) and shape1[0](site2)
+    )
+
+
+def change_hopping_at_interface(syst, template, shape1, shape2):
+    for (site1, site2), hop in syst.hopping_value_pairs():
+        if at_interface(site1, site2, shape1, shape2):
+            syst[site1, site2] = template[site1, site2]
+    return syst
+
+
+@memoize
+def make_lead(a, r1, r2, coverage_angle, angle, with_shell, which_lead):
+    """Create an infinite cylindrical 3D wire partially covered with a
+    superconducting (SC) shell.
+
+    Parameters
+    ----------
+    a : int
+        Discretization constant in nm.
+    r1 : int
+        Radius of normal part of wire in nm.
+    r2 : int
+        Radius of superconductor in nm.
+    coverage_angle : int
+        Coverage angle of superconductor in degrees.
+    angle : int
+        Angle of tilting of superconductor from top in degrees.
+    with_shell : bool
+        Adds shell to the scattering area. If False no SC shell is added and
+        only a cylindrical wire will be created.
+    which_lead : str
+        Name of the potential function of the lead, e.g. `which_lead = 'left'` will
+        require a function `V_left(z, V_0)` and
+        `mu_left(mu_func(x, x0, sigma, mu_lead, mu_wire)`.
+
+    Returns
+    -------
+    syst : kwant.builder.InfiniteSystem
+        The finilized kwant system.
+
+    Examples
+    --------
+    This doesn't use default parameters because the variables need to be saved,
+    to a file. So I create a dictionary that is passed to the function.
+
+    >>> syst_params = dict(a=10, angle=0, coverage_angle=185, r1=50,
+    ...                    r2=70, with_shell=True)
+    >>> syst, hopping = make_lead(**syst_params)
+    """
+
+    shape_normal_lead = cylinder_sector(r_out=r1, angle=angle, L=-1, a=a)
+    shape_sc_lead = cylinder_sector(
+        r_out=r2, r_in=r1, coverage_angle=coverage_angle, angle=angle, L=-1, a=a
+    )
+
+    sz = np.array([[1, 0], [0, -1]])
+    cons_law = np.kron(np.eye(2), -sz)
+    symmetry = kwant.TranslationalSymmetry((a, 0, 0))
+    lead = kwant.Builder(
+        symmetry, conservation_law=cons_law if not with_shell else None
+    )
+
+    templ_sm, templ_sc, templ_interface = discretized_hamiltonian(
+        a, which_lead=which_lead
+    )
+    templ_sm = apply_peierls_to_template(templ_sm)
+    lead.fill(templ_sm, *shape_normal_lead)
+
+    if with_shell:
+        lat = templ_sc.lattice
+        shape_sc = cylinder_sector(
+            r_out=r2, r_in=r1, coverage_angle=coverage_angle, angle=angle, L=a, a=a
+        )
+
+        xyz_offset = get_offset(*shape_sc, lat)
+
+        templ_sc = apply_peierls_to_template(templ_sc, xyz_offset=xyz_offset)
+        templ_interface = apply_peierls_to_template(templ_interface)
+        lead.fill(templ_sc, *shape_sc_lead)
+
+        # Adding a tunnel barrier between SM and SC
+        lead = change_hopping_at_interface(
+            lead, templ_interface, shape_normal_lead, shape_sc_lead
+        )
+
+    return lead
+
+
+def apply_peierls_to_template(template, xyz_offset=(0, 0, 0)):
+    """Adds p.orbital argument to the hopping functions."""
+    template = deepcopy(template)  # Needed because kwant.Builder is mutable
+    x0, y0, z0 = xyz_offset
+    lat = template.lattice
+    a = np.max(lat.prim_vecs)  # lattice contant
+
+    def phase(site1, site2, B_x, B_y, B_z, orbital, e, hbar):
+        if orbital:
+            x, y, z = site1.tag
+            direction = site1.tag - site2.tag
+            A = [B_y * (z - z0) - B_z * (y - y0), 0, B_x * (y - y0)]
+            A = np.dot(A, direction) * a ** 2 * 1e-18 * e / hbar
+            phase = np.exp(-1j * A)
+            if lat.norbs == 2:  # No PH degrees of freedom
+                return phase
+            elif lat.norbs == 4:
+                return np.array(
+                    [phase, phase.conj(), phase, phase.conj()], dtype="complex128"
+                )
+        else:  # No orbital phase
+            return 1
+
+    for (site1, site2), hop in template.hopping_value_pairs():
+        template[site1, site2] = combine(hop, phase, operator.mul, 2)
+    return template
+
+
+def get_offset(shape, start, lat):
+    a = np.max(lat.prim_vecs)
+    coords = [site.pos for site in lat.shape(shape, start)()]
+    xyz_offset = np.mean(coords, axis=0)
+    return xyz_offset
+
+
+def translation_ev(h, t, tol=1e6):
+    """Compute the eigenvalues of the translation operator of a lead.
+
+    Adapted from kwant.physics.leads.modes.
+
+    Parameters
+    ----------
+    h : numpy array, real or complex, shape (N, N) The unit cell
+        Hamiltonian of the lead unit cell.
+    t : numpy array, real or complex, shape (N, M)
+        The hopping matrix from a lead cell to the one on which self-energy
+        has to be calculated (and any other hopping in the same direction).
+    tol : float
+        Numbers and differences are considered zero when they are smaller
+        than `tol` times the machine precision.
+
+    Returns
+    -------
+    ev : numpy array
+        Eigenvalues of the translation operator in the form lambda=r*exp(i*k),
+        for |r|=1 they are propagating modes.
+    """
+    a, b = kwant.physics.leads.setup_linsys(h, t, tol, None).eigenproblem
+    ev = kwant.physics.leads.unified_eigenproblem(a, b, tol=tol)[0]
+    return ev
+
+
+def gap_minimizer(lead, params, energy):
+    """Function that minimizes a function to find the band gap.
+    This objective function checks if there are progagating modes at a
+    certain energy. Returns zero if there is a propagating mode.
+
+    Parameters
+    ----------
+    lead : kwant.builder.InfiniteSystem object
+        The finalized infinite system.
+    params : dict
+        A dict that is used to store Hamiltonian parameters.
+    energy : float
+        Energy at which this function checks for propagating modes.
+
+    Returns
+    -------
+    minimized_scalar : float
+        Value that is zero when there is a propagating mode.
+    """
+    h, t = cell_mats(lead, params, bias=energy)
+    ev = translation_ev(h, t)
+    norm = (ev * ev.conj()).real
+    return np.min(np.abs(norm - 1))
+
+
+def gap_from_modes(lead, params, tol=1e-6):
+    """Finds the gapsize by peforming a binary search of the modes with a
+    tolarance of tol.
+
+    Parameters
+    ----------
+    lead : kwant.builder.InfiniteSystem object
+        The finalized infinite system.
+    params : dict
+        A dict that is used to store Hamiltonian parameters.
+    tol : float
+        The precision of the binary search.
+
+    Returns
+    -------
+    gap : float
+        Size of the gap.
+
+    Notes
+    -----
+    For use with `lead = funcs.make_lead()`.
+    """
+    Es = kwant.physics.Bands(lead, params=params)(k=0)
+    lim = [0, np.abs(Es).min()]
+    if gap_minimizer(lead, params, energy=0) < 1e-15:
+        # No band gap
+        gap = 0
+    else:
+        while lim[1] - lim[0] > tol:
+            energy = sum(lim) / 2
+            par = gap_minimizer(lead, params, energy)
+            if par < 1e-10:
+                lim[1] = energy
+            else:
+                lim[0] = energy
+        gap = sum(lim) / 2
+    return gap
-- 
GitLab