From 65e0e29bd513075be792bf0543fc689be30c0923 Mon Sep 17 00:00:00 2001 From: Joseph Weston <joseph@weston.cloud> Date: Mon, 11 Feb 2019 14:30:01 +0100 Subject: [PATCH] add 'parameters' property to all system/lead implementations Specifically: finalized Builder, PrecalculatedLead, ModesLead and SelfEnergyLead This can be used to inspect the parameters on which a system depends, and can be later used to give more informative error messages. Adding 'parameters' to ModesLead and SelfEnergyLead is strictly a backwards-incompatible change, however this is an advanced feature of Kwant and we don't want to let this block adding 'parameters' to the system API. --- kwant/builder.py | 30 +++++++++++++++++++++++++---- kwant/solvers/tests/_test_sparse.py | 1 + kwant/system.py | 3 +++ kwant/tests/test_builder.py | 23 +++++++++++++++++----- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/kwant/builder.py b/kwant/builder.py index 6441083d..c804c3a8 100644 --- a/kwant/builder.py +++ b/kwant/builder.py @@ -632,13 +632,16 @@ class SelfEnergyLead(Lead): Has the same signature as `selfenergy` (without the ``self`` parameter) and returns the self energy matrix for the interface sites. interface : sequence of `Site` instances + parameters : sequence of strings + The parameters on which the lead depends. """ - def __init__(self, selfenergy_func, interface): + def __init__(self, selfenergy_func, interface, parameters): self.interface = tuple(interface) # we changed the API of 'selfenergy_func' to have a keyword-only # parameter 'params', but we still need to support the old API # XXX: remove this when releasing Kwant 2.0 self.selfenergy_func = _ensure_signature(selfenergy_func) + self.parameters = frozenset(parameters) def finalized(self): """Trivial finalization: the object is returned itself.""" @@ -659,14 +662,16 @@ class ModesLead(Lead): `~kwant.physics.PropagatingModes` and `~kwant.physics.StabilizedModes`. interface : sequence of `Site` instances - + parameters : sequence of strings + The parameters on which the lead depends. """ - def __init__(self, modes_func, interface): + def __init__(self, modes_func, interface, parameters): self.interface = tuple(interface) # we changed the API of 'selfenergy_func' to have a keyword-only # parameter 'params', but we still need to support the old API # XXX: remove this when releasing Kwant 2.0 self.modes_func = _ensure_signature(modes_func) + self.parameters = frozenset(parameters) def finalized(self): """Trivial finalization: the object is returned itself.""" @@ -2018,19 +2023,27 @@ class FiniteSystem(_FinalizedBuilderMixin, system.FiniteSystem): hoppings = [cache(builder._get_edge(sites[tail], sites[head])) for tail, head in g] + # System parameters are the union of the parameters + # of onsites and hoppings. + # Here 'onsites' and 'hoppings' are pairs whos second element + # is a tuple of parameter names when matrix element is a function, + # and None otherwise. + parameters = frozenset(chain.from_iterable( + p for _, p in chain(onsites, hoppings) if p)) + self.graph = g self.sites = sites self.site_ranges = _site_ranges(sites) self.id_by_site = id_by_site self.hoppings = hoppings self.onsites = onsites + self.parameters = parameters self.symmetry = builder.symmetry self.leads = finalized_leads self.lead_interfaces = lead_interfaces self.lead_paddings = lead_paddings self._init_discrete_symmetries(builder) - def pos(self, i): return self.sites[i].pos @@ -2179,12 +2192,21 @@ class InfiniteSystem(_FinalizedBuilderMixin, system.InfiniteSystem): tail, head = sym.to_fd(tail, head) hoppings.append(cache(builder._get_edge(tail, head))) + # System parameters are the union of the parameters + # of onsites and hoppings. + # Here 'onsites' and 'hoppings' are pairs whos second element + # is a tuple of parameter names when matrix element is a function, + # and None otherwise. + parameters = frozenset(chain.from_iterable( + p for _, p in chain(onsites, hoppings) if p)) + self.graph = g self.sites = sites self.site_ranges = _site_ranges(sites) self.id_by_site = id_by_site self.hoppings = hoppings self.onsites = onsites + self.parameters = parameters self.symmetry = builder.symmetry self.cell_size = cell_size self._init_discrete_symmetries(builder) diff --git a/kwant/solvers/tests/_test_sparse.py b/kwant/solvers/tests/_test_sparse.py index 620b7c54..8b2efa92 100644 --- a/kwant/solvers/tests/_test_sparse.py +++ b/kwant/solvers/tests/_test_sparse.py @@ -22,6 +22,7 @@ sq = square = kwant.lattice.square() class LeadWithOnlySelfEnergy: def __init__(self, lead): self.lead = lead + self.parameters = frozenset() def selfenergy(self, energy, args=(), *, params=None): assert args == () diff --git a/kwant/system.py b/kwant/system.py index 6d98cd68..a670d773 100644 --- a/kwant/system.py +++ b/kwant/system.py @@ -326,6 +326,9 @@ class PrecalculatedLead: raise ValueError("No precalculated values provided.") self._modes = modes self._selfenergy = selfenergy + # Modes/Self-energy have already been evaluated, so there + # is no parametric dependence anymore + self.parameters = frozenset() def modes(self, energy=0, args=(), *, params=None): if self._modes is not None: diff --git a/kwant/tests/test_builder.py b/kwant/tests/test_builder.py index ccb40c7c..2d35df20 100644 --- a/kwant/tests/test_builder.py +++ b/kwant/tests/test_builder.py @@ -1088,7 +1088,7 @@ def test_ModesLead_and_SelfEnergyLead(): interface = [lat(L-1, lead.sites[i].tag[1]) for i in range(L)] # Re-attach right lead as ModesLead. - syst.leads[1] = builder.ModesLead(lead.modes, interface) + syst.leads[1] = builder.ModesLead(lead.modes, interface, lead.parameters) fsyst = syst.finalized() ts2 = [kwant.smatrix(fsyst, e).transmission(1, 0) for e in energies] assert_almost_equal(ts2, ts) @@ -1096,13 +1096,15 @@ def test_ModesLead_and_SelfEnergyLead(): # Re-attach right lead as ModesLead with old-style modes API # that does not take a 'params' keyword parameter. syst.leads[1] = builder.ModesLead( - lambda energy, args: lead.modes(energy, args), interface) + lambda energy, args: lead.modes(energy, args), + interface, lead.parameters) fsyst = syst.finalized() ts2 = [kwant.smatrix(fsyst, e).transmission(1, 0) for e in energies] assert_almost_equal(ts2, ts) # Re-attach right lead as SelfEnergyLead. - syst.leads[1] = builder.SelfEnergyLead(lead.selfenergy, interface) + syst.leads[1] = builder.SelfEnergyLead(lead.selfenergy, interface, + lead.parameters) fsyst = syst.finalized() ts2 = [kwant.greens_function(fsyst, e).transmission(1, 0) for e in energies] assert_almost_equal(ts2, ts) @@ -1110,7 +1112,8 @@ def test_ModesLead_and_SelfEnergyLead(): # Re-attach right lead as SelfEnergyLead with old-style selfenergy API # that does not take a 'params' keyword parameter. syst.leads[1] = builder.SelfEnergyLead( - lambda energy, args: lead.selfenergy(energy, args), interface) + lambda energy, args: lead.selfenergy(energy, args), + interface, lead.parameters) fsyst = syst.finalized() ts2 = [kwant.greens_function(fsyst, e).transmission(1, 0) for e in energies] assert_almost_equal(ts2, ts) @@ -1119,7 +1122,7 @@ def test_ModesLead_and_SelfEnergyLead(): # Also verifies that the selfenergy callback function can return exotic # arraylikes. syst.leads.append(builder.SelfEnergyLead( - lambda *args: list(ta.zeros((L, L))), interface)) + lambda *args: list(ta.zeros((L, L))), interface, lead.parameters)) fsyst = syst.finalized() ts2 = [kwant.greens_function(fsyst, e).transmission(1, 0) for e in energies] assert_almost_equal(ts2, ts) @@ -1346,18 +1349,28 @@ def test_subs(): # test basic substitutions syst = make_system() + assert syst.finalized().parameters == {'a', 'b', 'c'} expected = hamiltonian(syst, a=1, b=2, c=3) # 1 level of substitutions sub_syst = syst.substituted(a='d', b='e') + assert sub_syst.finalized().parameters == {'d', 'e', 'c'} assert np.allclose(hamiltonian(sub_syst, d=1, e=2, c=3), expected) # 2 levels of substitution sub_sub_syst = sub_syst.substituted(d='g', c='h') assert np.allclose(hamiltonian(sub_sub_syst, g=1, e=2, h=3), expected) + assert sub_sub_syst.finalized().parameters == {'g', 'e', 'h'} # very confusing but technically valid. 'a' does not appear in 'hopping', # so the signature of 'onsite' is valid. sub_syst = syst.substituted(a='sitea') + assert sub_syst.finalized().parameters == {'sitea', 'b', 'c'} assert np.allclose(hamiltonian(sub_syst, sitea=1, b=2, c=3), expected) + # Check that this also works for infinite systems, as their finalization + # follows a different code path. + lead = make_system(kwant.TranslationalSymmetry((-1,)), n=1) + lead = lead.substituted(a='lead_a', b='lead_b', c='lead_c') + lead = lead.finalized() + assert lead.parameters == {'lead_a', 'lead_b', 'lead_c'} def test_attach_stores_padding(): lat = kwant.lattice.chain() -- GitLab