Source code for tkwant.system

# -*- coding: utf-8 -*-
# Copyright 2016-2020 tkwant authors.
#
# This file is part of tkwant.  It is subject to the license terms in the file
# LICENSE.rst found in the top-level directory of this distribution and at
# http://kwant-project.org/license.  A list of tkwant authors can be found in
# the file AUTHORS.rst at the top-level directory of this distribution and at
# http://kwant-project.org/authors.
"""Tools for extracting time-dependent parts of Kwant systems."""

import numpy as np
import inspect
import warnings

with warnings.catch_warnings():
    warnings.simplefilter('ignore')
    import scipy.sparse as sps

from .import _common


__all__ = ['hamiltonian_with_boundaries', 'add_time_to_params',
           'is_time_dependent_function', 'orb_range', 'ExtendedSystem']


[docs]def add_time_to_params(params, time_name, time, check_numeric_type=False): """Add a ``time_name: time`` key-value pair to a ``params`` dict. Parameters ---------- params : dict or None Input dict. Can be empty or None. ``params`` must not contain the ``time_name`` key - this will raise a `KeyError`. time_name : obj A dict key to refer to the "time". time : obj The actual value of "time". check_numeric_type : bool, optional If true, check if the `time` argument is a finite real number. By default, no check is performed. Returns ------- tparams : dict A copy of the input dict ``params`` that contains an additional ``time_name: time`` key-value pair. """ if check_numeric_type: if not _common.is_type(time, 'real_number', require_finite=True): raise TypeError('time must be a finite real number') if params is None: tparams = {} else: if time_name in params: raise KeyError("'params' must not contain {}".format(time_name)) tparams = params.copy() tparams[time_name] = time return tparams
[docs]def is_time_dependent_function(obj, time_name): """Return `True` if `obj` is callable and has a time argument""" return callable(obj) and time_name in str(inspect.signature(obj))
class ExtendedSystem: """Hamiltonian matrix of central system + boundary conditions. Parameters ---------- hamiltonian : `~scipy.sparse.coo_matrix` The Hamiltonian matrix of the central system + boundary conditions. boundary_slices : sequence of `slice` Slices into ``hamiltonian`` that project onto the boundary conditions. solution_validators : sequence of callable One callable per boundary condition. Each callable takes a single argument, an array representing a solution *inside the boundary conditions*, and returns True if the solution is valid, and False otherwise. time_validators : sequence of callable One callable per boundary condition. Each callable has signature ``(next_time)`` and return True if the given boundary condition can be used up to the time ``next_time``, otherwise returns False. syst : `~kwant.system.FiniteSystem` The original kwant system. """ def __init__(self, hamiltonian, boundary_slices, solution_validators, time_validators, syst): self.hamiltonian = hamiltonian self.boundary_slices = boundary_slices self.syst = syst self._solution_validators = solution_validators self._time_validators = time_validators def solution_is_valid(self, solution): return all(is_valid(solution[boundary]) for is_valid, boundary in zip(self._solution_validators, self.boundary_slices)) def time_is_valid(self, time): return all(is_valid(time) for is_valid in self._time_validators)
[docs]def hamiltonian_with_boundaries(syst, boundaries, params): r"""Generate the Hamiltonian with boundary conditions attached. Only generate the time-independent part of the Hamiltonian. The boundary conditions are represented by matrices :math:`h_n` (one per lead) that will form a direct sum with the Hamiltonian of the central system, :math:`H_S`: .. math:: H_{tot} = H_S \oplus h_0 \oplus h_1 \oplus \cdots In addition, coupling terms given by the lead inter-cell hoppings will be added between the added boundary conditions and the corresponding lead interface in the central system Hamiltonian. Parameters ---------- syst : `~kwant.system.FiniteSystem` System with leads attached. boundaries : sequence of `~tkwant.leads.BoundaryBase` The boundary conditions to attach; one per lead. params : dict, optional Extra arguments to pass to the Hamiltonian of ``syst`` at the initial time. Must include the time argument explicitly if Hamiltonian is time dependent. Returns ------- `ExtendedSystem` The Hamiltonian of the central system, evaluated at t=0, with boundary conditions attached, and accompanying metadata. """ if not len(syst.leads) == len(boundaries): raise ValueError('Number of leads= {} does not match ' 'the number of boundaries provided= {}' .format(len(syst.leads), len(boundaries))) # generate time-independent Hamiltonian and boundary condition # matrices and glue them together boundaries = [bc(lead, params) for lead, bc in zip(syst.leads, boundaries)] h_0 = syst.hamiltonian_submatrix(params=params, sparse=True) h_tot = sps.block_diag([h_0] + [bdy.hamiltonian for bdy in boundaries], format='lil') boundary_slices = [] # couple central system to boundary conditions and vice versa orb_offset = h_0.shape[0] for lead, lead_iface, boundary in zip(syst.leads, syst.lead_interfaces, boundaries): V = lead.inter_cell_hopping(params=params) # slices over lead interface orbitals within the lead num_iface_sites = lead.graph.num_nodes - lead.cell_size V_slices = [slice(*orb_range(lead, s)) for s in range(num_iface_sites)] # slices going *to* and *from* the boundary conditions/central system to_slices = [slice(*(s + orb_offset)) for s in boundary.to_slices] from_slices = [slice(*(s + orb_offset)) for s in boundary.from_slices] # iterate through all sites in the interface -- they have to # be treated separately, as interface sites in `syst` are not # necessarily grouped consecutively. for syst_site, V_slice in zip(lead_iface, V_slices): syst_slice = slice(*orb_range(syst, syst_site)) # coupled *to* system, *from* boundary conditions for to_slice in to_slices: h_tot[syst_slice, to_slice] =\ V[:, V_slice].conjugate().transpose() # couple *from* system, *to* boundary conditions for from_slice in from_slices: h_tot[from_slice, syst_slice] = V[:, V_slice] pass # remember which orbitals correspond to this boundary condition boundary_slices.append(slice(orb_offset, orb_offset + boundary.hamiltonian.shape[0])) # now point to the start of the next boundary condition block orb_offset += boundary.hamiltonian.shape[0] solution_is_valid = [bdy.solution_is_valid for bdy in boundaries] time_is_valid = [bdy.time_is_valid for bdy in boundaries] return ExtendedSystem(h_tot, boundary_slices, solution_is_valid, time_is_valid, syst)
[docs]def orb_range(syst, site): """Return the first orbital of this and the next site. Parameters ---------- syst : `~kwant.system.System` site : int Returns ------- pair of integers """ assert 0 <= site < syst.graph.num_nodes if syst.site_ranges is None: raise RuntimeError('Number of orbitals not defined.\n' 'Declare the number of orbitals using the `norbs` ' 'keyword argument when constructing site families') # Calculate the index of the run that contains the site. column = np.asarray(syst.site_ranges)[:, 0] run_idx = np.searchsorted(column, site, 'right') - 1 # calculate the slice first_site, norbs, orb_offset = syst.site_ranges[run_idx] orb = orb_offset + (site - first_site) * norbs return orb, orb + norbs