diff --git a/doc/source/whatsnew/0.3.rst b/doc/source/whatsnew/0.3.rst index eaaf78ce93fbb80452ceff0c7dcc00bd85a1c85e..594270133bfd9f5b95f1ac7df2ad579871ceaafe 100644 --- a/doc/source/whatsnew/0.3.rst +++ b/doc/source/whatsnew/0.3.rst @@ -133,3 +133,8 @@ responsibility. The new class `~kwant.builder.ModesLead` allows to attach leads that have a custom way of calculating their modes (e.g. ideal leads) directly to a `~kwant.builder.Builder`. + +Modes or self-energies can now be precomputed before passing the system to a +solver, using the method `~kwant.system.FiniteSystem.precalculate`. This may +save time, when the linear system has to be solved many times with the same +lead parameters. diff --git a/kwant/system.py b/kwant/system.py index d434b3cca953b8eee7168098edff767ebd1bb550..41b84f78456e6ab346a0583baf48e4404af29ffc 100644 --- a/kwant/system.py +++ b/kwant/system.py @@ -13,6 +13,7 @@ __all__ = ['System', 'FiniteSystem', 'InfiniteSystem'] import abc import types +from copy import copy import numpy as np from . import physics, _system @@ -90,6 +91,57 @@ class FiniteSystem(System): """ __metaclass__ = abc.ABCMeta + def precalculate(self, energy, args=(), leads=None, check=True, + calculate_selfenergy=True): + """ + Precalculate modes or self-energies in the leads. + + Construct a copy of the system, with the lead modes precalculated, + which may significantly speed up calculations where only the system + is changing. + + Parameters + ---------- + energy : float + Energy at which the modes or self-energies have to be + evaluated. + args : sequence + Additional parameters required for calculating the Hamiltionians + leads : list of integers or None + Numbers of the leads to be precalculated. If `None`, all are + precalculated. + check : bool + Whether the resulting system should raise an error if solving + is attempted at parameter or energy values other than used + during precalculation. + calculate_selfenergy : bool + Whether to calculate self-energy if modes are available. + + Returns + ------- + sys : FiniteSystem + A copy of the original system with some leads precalculated. + """ + result = copy(self) + if leads is None: + leads = range(len(self.leads)) + new_leads = [] + res_energy, res_args = (energy, args) if check else (None, None) + for nr, lead in enumerate(self.leads): + if nr not in leads: + new_leads.append(lead) + continue + modes, selfenergy = None, None + try: + modes = lead.modes(energy, args) + if calculate_selfenergy: + selfenergy = physics.selfenergy_from_modes(modes) + except AttributeError: + selfenergy = lead.selfenergy(energy, args) + new_leads.append(PrecalculatedLead(modes, selfenergy, res_energy, + res_args)) + result.leads = new_leads + return result class InfiniteSystem(System): """ @@ -175,3 +227,60 @@ class InfiniteSystem(System): # Subtract energy from the diagonal. ham.flat[::ham.shape[0] + 1] -= energy return physics.selfenergy(ham, self.inter_slice_hopping(args=args)) + + +class PrecalculatedLead(object): + def __init__(self, modes=None, selfenergy=None, energy=None, args=None): + """A general lead defined by its self energy. + + Parameters + ---------- + modes : kwant.physics.ModesTuple + Modes of the lead. + selfenergy : numpy array + Lead self-energy. + energy : float + Energy at which modes or self-energy were calculated. + args : sequence + Extra parameters used in the calculation. + + Raises + ------ + ValueError + If modes or self-energy are requested, at the energy or argument + values different from the ones specified during initialization. + If the initial values were `None`, no check is performed. + + Notes + ----- + At least one of `modes` and `selfenergy` must be provided. + """ + if modes is None and selfenergy is None: + raise ValueError("No precalculated values provided.") + self._modes = modes + self._selfenergy = selfenergy + self._energy = energy + self._args = args + + def _check_params(self, energy, args=()): + cond1 = self._energy is not None and self._energy != energy + cond2 = self._args is not None and tuple(self._args) != tuple(args) + if cond1 or cond2: + raise ValueError("The lead modes or self-energy are requested " + "at parameter values different from the ones " + "at which they were evaluated.") + + def modes(self, energy, args=()): + self._check_params(energy, args) + if self._modes is not None: + return self._modes + else: + raise ValueError("No precalculated modes were provided.") + + def selfenergy(self, energy, args=()): + self._check_params(energy, args) + return self._selfenergy + if self._selfenergy is not None: + return self._selfenergy + else: + raise ValueError("No precalculated self-energy was provided.") diff --git a/kwant/tests/test_system.py b/kwant/tests/test_system.py index 72d1caf7921307c3b9c657bb655166a4ad5d19c5..736c2e5576af367a6f80b702a9977a1492c65fc6 100644 --- a/kwant/tests/test_system.py +++ b/kwant/tests/test_system.py @@ -56,6 +56,19 @@ def test_hamiltonian_submatrix(): mat_sp = sys2.hamiltonian_submatrix(sparse=True).todense() np.testing.assert_array_equal(mat_sp, mat_dense) + # Test precalculation of modes. + np.random.seed(5) + lead = kwant.Builder(kwant.TranslationalSymmetry((-1,))) + lead[gr(0)] = np.zeros((2, 2)) + lead[gr(0), gr(1)] = np.random.randn(2, 2) + sys.attach_lead(lead) + sys2 = sys.finalized() + smatrix = kwant.solve(sys2, .1).data + sys3 = sys2.precalculate(.1) + smatrix2 = kwant.solve(sys3, .1).data + np.testing.assert_almost_equal(smatrix, smatrix2) + assert_raises(ValueError, kwant.solve, sys3, 0.2) + # Test for shape errors. sys[gr(0), gr(2)] = np.array([[1, 2]]) sys2 = sys.finalized() @@ -86,3 +99,4 @@ def test_hamiltonian_submatrix(): mat = mat[perm, :] mat = mat[:, perm] np.testing.assert_array_equal(mat, mat_should_be) +