Commit 65e0e29b authored by Joseph Weston's avatar Joseph Weston

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.
parent 784a7332
......@@ -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)
......
......@@ -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 == ()
......
......@@ -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:
......
......@@ -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()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment