diff --git a/kwant/physics/gauge.py b/kwant/physics/gauge.py index b6d1d54dfb4e2866ce18db81758c3cb9227a6286..1ba5cf857c76ffdd89647aac6d24dc182cae848c 100644 --- a/kwant/physics/gauge.py +++ b/kwant/physics/gauge.py @@ -366,8 +366,9 @@ def loops_in_composite(syst): Returns ------- loops : sequence of sequences of integers - The sites in each loop belong to the extended system (see notes). - The first and last site in each loop are guaranteed to be in 'syst'. + The sites in each loop belong to the extended scattering region + (see notes). The first and last site in each loop are guaranteed + to be in 'syst'. which_patch : callable : int -> int Given a site index in the extended scattering region (see notes), returns the lead patch (see notes) to which the site belongs. Returns @@ -378,7 +379,7 @@ def loops_in_composite(syst): Notes ----- - extended system + extended scattering region The scattering region with a single lead unit cell attached at each interface. This unit cell is added so that we can "see" any loops formed with sites in the lead (see 'check_infinite_syst' @@ -386,19 +387,21 @@ def loops_in_composite(syst): order as the leads, and within a given added unit cell the sites are ordered in the same way as the associated lead. lead patch - Sites in the extended system that belong to the added unit cell - for a given lead, or the lead padding for a given lead are said - to be in the "lead patch" for that lead. + Sites in the extended scattering region that belong to the added + unit cell for a given lead, or the lead padding for a given lead + are said to be in the "lead patch" for that lead. reduced scattering region - The sites of the extended system that are not in a lead patch. + The sites of the extended scattering region that are not + in a lead patch. """ # Check that we can consistently fix the gauge in the scattering region, # given that we have independently fixed gauges in the leads. check_composite_syst(syst) - # Get distance matrix for the extended system, a function that maps sites - # to their lead patches (-1 for sites in the reduced scattering region), - # and a function that maps sites to high-level 'kwant.builder.Site' objects. + # Get distance matrix for the extended scattering region, + # a function that maps sites to their lead patches (-1 for sites + # in the reduced scattering region), and a function that maps sites + # to high-level 'kwant.builder.Site' objects. distance_matrix, which_patch, extended_sites = extended_scattering_region(syst) spanning_tree = spanning_tree_composite(distance_matrix, which_patch).tocsr() @@ -864,75 +867,64 @@ def _infinite_wrapper(syst, phases, a, b): return phases.get((i, j), -phases.get((j, i), 0)) -def _gauge_finite(syst): - loops = loops_in_finite(syst) - - def _gauge(syst_field, tol=1E-8, average=False): - integrate = partial(surface_integral, syst_field, - tol=tol, average=average) - phases = calculate_phases( - loops, - syst.pos, - _previous_phase_finite, - integrate, - ) - return partial(_finite_wrapper, syst, phases) - - return _gauge - - -def _gauge_infinite(syst): - loops, extended_sites = loops_in_infinite(syst) - - def _gauge(syst_field, tol=1E-8, average=False): - integrate = partial(surface_integral, syst_field, - tol=tol, average=average) - phases = calculate_phases( - loops, - lambda i: extended_sites(i).pos, - partial(_previous_phase_infinite, syst.cell_size), - integrate, - ) - return partial(_infinite_wrapper, syst, phases) - - return _gauge - - -def _gauge_composite(syst): - loops, which_patch, extended_sites = loops_in_composite(syst) - lead_gauges = [_gauge_infinite(lead) for lead in syst.leads] - - def _gauge(syst_field, *lead_fields, tol=1E-8, average=False): - if len(lead_fields) != len(syst.leads): - raise ValueError('Magnetic fields must be provided for all leads.') +def _peierls_finite(syst, loops, syst_field, tol, average): + integrate = partial(surface_integral, syst_field, + tol=tol, average=average) + phases = calculate_phases( + loops, + syst.pos, + _previous_phase_finite, + integrate, + ) + return partial(_finite_wrapper, syst, phases) - lead_phases = [gauge(B, tol=tol, average=average) - for gauge, B in zip(lead_gauges, lead_fields)] - flux = partial(surface_integral, syst_field, tol=tol, average=average) +def _peierls_infinite(syst, loops, extended_sites, syst_field, tol, average): + integrate = partial(surface_integral, syst_field, + tol=tol, average=average) + phases = calculate_phases( + loops, + lambda i: extended_sites(i).pos, + partial(_previous_phase_infinite, syst.cell_size), + integrate, + ) + return partial(_infinite_wrapper, syst, phases) + + +def _peierls_composite(syst, loops, which_patch, extended_sites, lead_gauges, + syst_field, *lead_fields, tol, average): + if len(lead_fields) != len(syst.leads): + raise ValueError('Magnetic fields must be provided for all leads.') + + lead_phases = [gauge(B, tol=tol, average=average) + for gauge, B in zip(lead_gauges, lead_fields)] + + flux = partial(surface_integral, syst_field, tol=tol, average=average) + + # NOTE: uses the scattering region magnetic field to set the phase + # of the inteface hoppings this choice is somewhat arbitrary, + # but it is consistent with the position defined in the scattering + # region coordinate system. the integrate functions for the leads + # may be defined far from the interface. + phases = calculate_phases( + loops, + lambda i: extended_sites(i).pos, + partial(_previous_phase_composite, + which_patch, extended_sites, lead_phases), + flux, + ) - # NOTE: uses the scattering region magnetic field to set the phase - # of the inteface hoppings this choice is somewhat arbitrary, - # but it is consistent with the position defined in the scattering - # region coordinate system. the integrate functions for the leads - # may be defined far from the interface. - phases = calculate_phases( - loops, - lambda i: extended_sites(i).pos, - partial(_previous_phase_composite, - which_patch, extended_sites, lead_phases), - flux, - ) + return (partial(_finite_wrapper, syst, phases), *lead_phases) - return (partial(_finite_wrapper, syst, phases), *lead_phases) - return _gauge +# This class is essentially a closure, but documenting closures is a pain. +# To emphasise the lack of manipulatable or inspectable state, we name the +# class as we would for a function. -def magnetic_gauge(syst): +class magnetic_gauge: """Fix the magnetic gauge for a finalized system. - Fix the magnetic gauge for a Kwant system. This can - be used to later calculate the Peierls phases that + This can be used to later calculate the Peierls phases that should be applied to each hopping, given a magnetic field. This API is currently provisional. Refer to the documentation @@ -942,19 +934,6 @@ def magnetic_gauge(syst): ---------- syst : kwant.builder.FiniteSystem or kwant.builder.InfiniteSystem - Returns - ------- - gauge : a callable or sequence thereof - If 'syst' is an infinite system, or a finite system without - leads, returns a single callable. If 'syst' has leads, then - returns a sequence of callables, where the first callable - is the gauge of the scattering region, and the rest are - the gauges of the leads. - When the returned callable(s) is called with a magnetic field - as argument, returns another callable 'phase' that takes pairs - of sites and returns the Peierls phase to apply to the - corresponding hopping. - Examples -------- The following illustrates basic usage for a finite system: @@ -965,20 +944,72 @@ def magnetic_gauge(syst): >>> def hopping(a, b, t, phi): >>> return -t * np.exp(-1j * phi(a, b)) >>> - >>> syst = make_system(hopping).finalized() + >>> syst = make_system(hopping) + >>> lead = make_lead(hopping) + >>> lead.substitute(phi='phi_lead') + >>> syst.attach_lead(lead) + >>> syst = syst.finalized() + >>> >>> gauge = kwant.physics.magnetic_gauge(syst) >>> - >>> def B(pos): + >>> def B_syst(pos): >>> return np.exp(-np.sum(pos * pos)) >>> - >>> kwant.hamiltonian_submatrix(syst, params=dict(t=1, phi=gauge(B)) + >>> phi_syst, phi_lead = gauge(B_syst, 0) + >>> + >>> params = dict(t=1, phi=phi_syst, phi_lead=phi_lead) + >>> kwant.hamiltonian_submatrix(syst, params=params) """ - if isinstance(syst, builder.FiniteSystem): - if syst.leads: - return _gauge_composite(syst) + + def __init__(self, syst): + if isinstance(syst, builder.FiniteSystem): + if syst.leads: + loops, which_patch, extended_sites = loops_in_composite(syst) + lead_gauges = [magnetic_gauge(lead) for lead in syst.leads] + self._peierls = partial(_peierls_composite, syst, + loops, which_patch, + extended_sites, lead_gauges) + else: + loops = loops_in_finite(syst) + self._peierls = partial(_peierls_finite, syst, loops) + elif isinstance(syst, builder.InfiniteSystem): + loops, extended_sites = loops_in_infinite(syst) + self._peierls = partial(_peierls_infinite, syst, + loops, extended_sites) else: - return _gauge_finite(syst) - elif isinstance(syst, builder.InfiniteSystem): - return _gauge_infinite(syst) - else: - raise TypeError('Expected a finalized Builder') + raise TypeError('Expected a finalized Builder') + + def __call__(self, syst_field, *lead_fields, tol=1E-8, average=False): + """Return the Peierls phase for a magnetic field configuration. + + Parameters + ---------- + syst_field : scalar, vector or callable + The magnetic field to apply to the scattering region. + If callable, takes a position and returns the + magnetic field at that position. Can be a scalar if + the system is 1D or 2D, otherwise must be a vector. + *lead_fields : scalar, vector or callable + The magnetic fields to apply to each of the leads, in + the same format as 'syst_field'. In addition, if a callable + is provided, then the magnetic field must have the symmetry + of the associated lead. + tol : float, default: 1E-8 + The tolerance to which to calculate the flux through each + hopping loop in the system. + average : bool, default: False + If True, estimate the magnetic flux through each hopping loop + in the system by evaluating the magnetic field at a single + position inside the loop and multiplying it by the area of the + loop. If False, then `~scipy.integrate.quad` is used to integrate + the magnetic field. This parameter is only used when 'syst_field' + or 'lead_fields' are callable. + + Returns + ------- + phases : callable, or sequence of callables + The first callable computes the Peierls phase in the scattering + region and the remaining callables compute the Peierls phases + in each of the leads. + """ + return self._peierls(syst_field, *lead_fields, tol=tol, average=False)