def test_closest(): rng = ensure_rng(10) for sym_dim in range(1, 4): for space_dim in range(sym_dim, 4): lat = kwant.lattice.general(ta.identity(space_dim)) # Choose random periods. while True: periods = rng.randint(-10, 11, (sym_dim, space_dim)) if np.linalg.det(np.dot(periods, periods.T)) > 0.1: # Periods are reasonably linearly independent. break syst = builder.Builder(kwant.TranslationalSymmetry(*periods)) for tag in rng.randint(-30, 31, (4, space_dim)): # Add site and connect it to the others. old_sites = list(syst.sites()) new_site = lat(*tag) syst[new_site] = None syst[((new_site, os) for os in old_sites)] = None # Test consistency with fill(). for point in 200 * rng.random_sample((10, space_dim)) - 100: closest = syst.closest(point) dist = closest.pos - point dist = ta.dot(dist, dist) syst2 = builder.Builder() syst2.fill(syst, inside_disc(point, 2 * dist), closest) assert syst2.closest(point) == closest for site in syst2.sites(): dd = site.pos - point dd = ta.dot(dd, dd) assert dd >= 0.999999 * dist
def test_symm_algorithm_equivalence(): """Test different stabilization methods in the computation of modes, in the presence and/or absence of the discrete symmetries.""" rng = ensure_rng(400) n = 8 for sym in kwant.rmt.sym_list: # Random onsite and hopping matrices in symmetry class h_cell = kwant.rmt.gaussian(n, sym, rng=rng) # Hopping is an offdiagonal block of a Hamiltonian. We rescale it # to ensure that there are modes at the Fermi level. h_hop = 10 * kwant.rmt.gaussian(2*n, sym, rng=rng)[:n, n:] if kwant.rmt.p(sym): p_mat = np.array(kwant.rmt.h_p_matrix[sym]) p_mat = np.kron(np.identity(n // len(p_mat)), p_mat) else: p_mat = None if kwant.rmt.t(sym): t_mat = np.array(kwant.rmt.h_t_matrix[sym]) t_mat = np.kron(np.identity(n // len(t_mat)), t_mat) else: t_mat = None if kwant.rmt.c(sym): c_mat = np.kron(np.identity(n // 2), np.diag([1, -1])) else: c_mat = None check_equivalence(h_cell, h_hop, n, sym=sym, particle_hole=p_mat, chiral=c_mat, time_reversal=t_mat)
def test_singular_graph_system(smatrix): rng = ensure_rng(11) system = kwant.Builder() lead = kwant.Builder(kwant.TranslationalSymmetry((-1, 0))) h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() h *= 0.8 t = 4 * rng.random_sample((n, n)) + 4j * rng.random_sample((n, n)) t1 = 4 * rng.random_sample((n, n)) + 4j * rng.random_sample((n, n)) lead[sq(0, 0)] = system[[sq(0, 0), sq(1, 0)]] = h lead[sq(0, 1)] = system[[sq(0, 1), sq(1, 1)]] = 4 * h for builder in [system, lead]: builder[sq(0, 0), sq(1, 0)] = t builder[sq(0, 1), sq(1, 0)] = t1 system.attach_lead(lead) system.attach_lead(lead.reversed()) fsyst = system.finalized() result = smatrix(fsyst) s, leads = result.data, result.lead_info assert_almost_equal(np.dot(s.conjugate().transpose(), s), np.identity(s.shape[0])) n_modes = len(leads[0].momenta) // 2 assert len(leads[1].momenta) // 2 == n_modes assert_almost_equal(s[:n_modes, :n_modes], 0) t_elements = np.sort(abs(np.asarray(s[n_modes:, :n_modes])), axis=None) t_el_should_be = n_modes * (n_modes - 1) * [0] + n_modes * [1] assert_almost_equal(t_elements, t_el_should_be)
def test_selfenergy_reflection(greens_function, smatrix): rng = ensure_rng(4) system = kwant.Builder() left_lead = kwant.Builder(kwant.TranslationalSymmetry((-1, ))) for b, site in [(system, chain(0)), (system, chain(1)), (left_lead, chain(0))]: h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() b[site] = h for b, hopp in [(system, (chain(0), chain(1))), (left_lead, (chain(0), chain(1)))]: b[hopp] = (10 * rng.random_sample((n, n)) + 1j * rng.random_sample( (n, n))) system.attach_lead(left_lead) fsyst = system.finalized() t = smatrix(fsyst, 0, (), [0], [0]) fsyst.leads[0] = LeadWithOnlySelfEnergy(fsyst.leads[0]) sol = greens_function(fsyst, 0, (), [0], [0]) assert_almost_equal(sol.transmission(0, 0), t.transmission(0, 0)) fsyst = system.finalized() for syst in (fsyst.precalculate(what='selfenergy'), fsyst.precalculate(what='all')): sol = greens_function(syst, 0, (), [0], [0]) assert_almost_equal(sol.transmission(0, 0), t.transmission(0, 0)) raises(ValueError, greens_function, fsyst.precalculate(what='modes'), 0, (), [0], [0])
def test_density_interpolation(): ## Passing a Builder will raise an error pytest.raises(TypeError, plotter.interpolate_density, syst_2d(), None) # Test that the density is always identically zero at the box boundaries # as the bump function has finite support and we add a padding _test_border_0(kwant.plotter.interpolate_density) def R(theta): return ta.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]]) # Make lattice with lattice vectors perturbed from x and y directions def make_lattice(a, salt='0'): theta_x = kwant.digest.uniform('x', salt=salt) * np.pi / 6 theta_y = kwant.digest.uniform('y', salt=salt) * np.pi / 6 x = ta.dot(R(theta_x), (a, 0)) y = ta.dot(R(theta_y), (0, a)) return kwant.lattice.general([x, y], norbs=1) # Check that integrating the interpolated density gives the same result # as summing the densities on all sites. We check this for several lattice # widths, lattice orientations and bump widths. for a, width in itertools.product((1, 2), (1, 0.5)): lat = make_lattice(a) syst = syst_rect(lat, salt='0').finalized() psi = kwant.wave_function(syst, energy=3)(0)[0] density = kwant.operator.Density(syst)(psi) exact_charge = sum(density) # We verify that the result is good by interpolating for # various numbers of points-per-bump and verifying that # the error falls of as 1/n. data = [] for n in [4, 6, 8, 11, 16]: rho, box = plotter.interpolate_density(syst, density, n=n, abswidth=width) (xmin, xmax), (ymin, ymax) = box area = xmax - xmin * (ymax - ymin) N = rho.shape[0] * rho.shape[1] charge = np.sum(rho) * area / N data.append((n, abs(charge - exact_charge))) _, _, rvalue, *_ = scipy.stats.linregress(np.log(data)) # Gradient of -1 on log-log plot means error falls off as 1/n # TODO: review this value once #280 has been dealt with. assert rvalue < -0.7 # Test that the interpolation is linear in the input. rng = ensure_rng(1) lat = make_lattice(1, '1') syst = syst_rect(lat, salt='1').finalized() rho_0 = rng.rand(len(syst.sites)) rho_1 = rng.rand(len(syst.sites)) irho_0, _ = plotter.interpolate_density(syst, rho_0) irho_1, _ = plotter.interpolate_density(syst, rho_1) rho_tot, _ = plotter.interpolate_density(syst, rho_0 + 2 * rho_1) assert np.allclose(rho_tot, irho_0 + 2 * irho_1)
def test_gaussian_symmetries(): rng = ensure_rng(10) for n in (5, 8, 100, 200): for sym in rmt.sym_list: if sym not in ('A', 'D', 'AI') and n % 2: raises(ValueError, rmt.gaussian, 5, sym) continue h = rmt.gaussian(n, sym, rng=rng) if rmt.t(sym): t_mat = np.array(rmt.h_t_matrix[sym]) t_mat = np.kron(np.identity(n // len(t_mat)), t_mat) assert_allclose(h, np.dot(t_mat, np.dot(h.conj(), t_mat)), err_msg='TRS broken in ' + sym) if rmt.p(sym): p_mat = np.array(rmt.h_p_matrix[sym]) p_mat = np.kron(np.identity(n // len(p_mat)), p_mat) assert_allclose(h, -np.dot(p_mat, np.dot(h.conj(), p_mat)), err_msg='PHS broken in ' + sym) if rmt.c(sym): sz = np.kron(np.identity(n // 2), np.diag([1, -1])) assert_allclose(h, -np.dot(sz, np.dot(h, sz)), err_msg='SLS broken in ' + sym)
def test_gaussian_distributions(): rng = ensure_rng(1) n = 8 for sym in rmt.sym_list: matrices = np.array( [rmt.gaussian(n, sym, rng=rng)[-1, 0] for i in range(3000)]) matrices = matrices.imag if sym in ('D', 'BDI') else matrices.real ks = stats.kstest(matrices, 'norm') assert (ks[1] > 0.1), (sym, ks)
def test_lll(): rng = ensure_rng(1) for i in range(50): x = rng.randint(4) + 1 mat = rng.randn(x, x + rng.randint(2)) c = 1.34 + .5 * rng.random_sample() reduced_mat, coefs = lll.lll(mat) assert lll.is_c_reduced(reduced_mat, c) assert np.allclose(np.dot(mat.T, coefs), reduced_mat.T)
def test_cvp(): rng = ensure_rng(0) for i in range(1, 5): for j in range(i, 5): mat = rng.randn(i, j) mat = lll.lll(mat)[0] for k in range(4): point = 50 * rng.randn(j) assert np.array_equal( lll.cvp(point, mat, 10)[:3], lll.cvp(point, mat, 3))
def test_closest(): rng = ensure_rng(4) lat = lattice.general(((1, 0), (0.5, sqrt(3) / 2)), norbs=1) for i in range(50): point = 20 * rng.random_sample(2) closest = lat(*lat.closest(point)).pos assert np.linalg.norm(point - closest) <= 1 / sqrt(3) lat = lattice.general(rng.randn(3, 3), norbs=1) for i in range(50): tag = rng.randint(10, size=(3, )) assert lat.closest(lat(*tag).pos) == tag
def twoterminal_system(): rng = ensure_rng(11) system = kwant.Builder() lead = kwant.Builder(kwant.TranslationalSymmetry((1, ))) h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() h *= 0.8 t = 4 * rng.random_sample((n, n)) + 4j * rng.random_sample((n, n)) lead[chain(0)] = h system[chain(0)] = h * 1.2 lead[chain(0), chain(1)] = t system.attach_lead(lead) system.attach_lead(lead.reversed()) return system
def test_circular(): rng = ensure_rng(10) n = 6 sy = np.kron(np.identity(n // 2), [[0, 1j], [-1j, 0]]) for sym in rmt.sym_list: if rmt.t(sym) == -1 or rmt.p(sym) == -1: raises(ValueError, rmt.circular, 5, sym) s = rmt.circular(n, sym, rng=rng) assert_allclose(np.dot(s, s.T.conj()), np.identity(n), atol=1e-9, err_msg='Unitarity broken in ' + sym) if rmt.t(sym): s1 = np.copy( s.T if rmt.p(sym) != -1 else np.dot(sy, np.dot(s.T, sy))) s1 *= rmt.t(sym) * (-1 if rmt.p(sym) == -1 else 1) assert_allclose(s, s1, atol=1e-9, err_msg='TRS broken in ' + sym) if rmt.p(sym): s1 = np.copy(s.conj() if rmt.p(sym) != -1 else np. dot(sy, np.dot(s.conj(), sy))) if sym in ('DIII', 'CI'): s1 *= -1 assert_allclose(s, s1, atol=1e-9, err_msg='PHS broken in ' + sym) if rmt.c(sym): assert_allclose(s, s.T.conj(), atol=1e-9, err_msg='SLS broken in ' + sym) # Check for distribution properties if the ensemble is a symmetric group. f = lambda x: x[0] / np.linalg.norm(x) for sym in ('A', 'C'): sample_distr = np.apply_along_axis(f, 0, rng.randn(2 * n, 1000)) s_sample = np.array( [rmt.circular(n, sym, rng=rng) for i in range(1000)]) assert stats.ks_2samp(sample_distr, s_sample[:, 0, 0].real)[1] > 0.1, \ 'Noncircular distribution in ' + sym assert stats.ks_2samp(sample_distr, s_sample[:, 3, 2].real)[1] > 0.1, \ 'Noncircular distribution in ' + sym assert stats.ks_2samp(sample_distr, s_sample[:, 1, 1].imag)[1] > 0.1, \ 'Noncircular distribution in ' + sym assert stats.ks_2samp(sample_distr, s_sample[:, 2, 3].imag)[1] > 0.1, \ 'Noncircular distribution in ' + sym sample_distr = np.apply_along_axis(f, 0, rng.randn(n, 500)) s_sample = np.array([rmt.circular(n, 'D', rng=rng) for i in range(500)]) ks = stats.ks_2samp(sample_distr, s_sample[:, 0, 0]) assert ks[1] > 0.1, 'Noncircular distribution in D ' + str(ks) ks = stats.ks_2samp(sample_distr, s_sample[:, 3, 2]) assert ks[1] > 0.1, 'Noncircular distribution in D ' + str(ks)
def test_wire(): rng = ensure_rng(5) vecs = rng.randn(3, 3) vecs[0] = [1, 0, 0] center = rng.randn(3) lat = lattice.general(vecs, rng.randn(4, 3), norbs=1) syst = builder.Builder(lattice.TranslationalSymmetry((2, 0, 0))) def wire_shape(pos): pos = np.array(pos) return np.linalg.norm(pos[1:] - center[1:])**2 <= 8.6**2 syst[lat.shape(wire_shape, center)] = 0 sites2 = set(syst.sites()) syst = builder.Builder(lattice.TranslationalSymmetry((2, 0, 0))) syst[lat.wire(center, 8.6)] = 1 sites1 = set(syst.sites()) assert sites1 == sites2
def test_translational_symmetry_reversed(): rng = ensure_rng(30) lat = lattice.general(np.identity(3), norbs=1) sites = [lat(i, j, k) for i in range(-2, 6) for j in range(-2, 6) for k in range(-2, 6)] for i in range(4): periods = rng.randint(-5, 5, (3, 3)) try: sym = lattice.TranslationalSymmetry(*periods) rsym = sym.reversed() for site in sites: assert sym.to_fd(site) == rsym.to_fd(site) assert sym.which(site) == -rsym.which(site) vec = np.array([1, 1, 1]) assert sym.act(vec, site), rsym.act(-vec == site) except ValueError: pass
def twolead_builder(): rng = ensure_rng(4) system = kwant.Builder() left_lead = kwant.Builder(kwant.TranslationalSymmetry((-1,))) right_lead = kwant.Builder(kwant.TranslationalSymmetry((1,))) for b, site in [(system, chain(0)), (system, chain(1)), (left_lead, chain(0)), (right_lead, chain(0))]: h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() b[site] = h for b, hopp in [(system, (chain(0), chain(1))), (left_lead, (chain(0), chain(1))), (right_lead, (chain(0), chain(1)))]: b[hopp] = (10 * rng.random_sample((n, n)) + 1j * rng.random_sample((n, n))) system.attach_lead(left_lead) system.attach_lead(right_lead) return system
def test_selfenergy(greens_function, smatrix): rng = ensure_rng(4) system = kwant.Builder() left_lead = kwant.Builder(kwant.TranslationalSymmetry((-1, ))) right_lead = kwant.Builder(kwant.TranslationalSymmetry((1, ))) for b, site in [(system, chain(0)), (system, chain(1)), (left_lead, chain(0)), (right_lead, chain(0))]: h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() b[site] = h for b, hopp in [(system, (chain(0), chain(1))), (left_lead, (chain(0), chain(1))), (right_lead, (chain(0), chain(1)))]: b[hopp] = (10 * rng.random_sample((n, n)) + 1j * rng.random_sample( (n, n))) system.attach_lead(left_lead) system.attach_lead(right_lead) fsyst = system.finalized() t = smatrix(fsyst, 0, (), [1], [0]).data eig_should_be = np.linalg.eigvals(t * t.conjugate().transpose()) n_eig = len(eig_should_be) def check_fsyst(fsyst): sol = greens_function(fsyst, 0, (), [1], [0]) ttdagnew = sol._a_ttdagger_a_inv(1, 0) eig_are = np.linalg.eigvals(ttdagnew) t_should_be = np.sum(eig_are) assert_almost_equal(eig_are.imag, 0) assert_almost_equal( np.sort(eig_are.real)[-n_eig:], np.sort(eig_should_be.real)) assert_almost_equal(t_should_be, sol.transmission(1, 0)) fsyst.leads[1] = LeadWithOnlySelfEnergy(fsyst.leads[1]) check_fsyst(fsyst) fsyst.leads[0] = LeadWithOnlySelfEnergy(fsyst.leads[0]) check_fsyst(fsyst) fsyst = system.finalized() for syst in (fsyst, fsyst.precalculate(what='selfenergy'), fsyst.precalculate(what='all')): check_fsyst(syst) raises(ValueError, check_fsyst, fsyst.precalculate(what='modes'))
def test_wavefunc_ldos_consistency(wave_function, ldos): L = 2 W = 3 rng = ensure_rng(31) syst = kwant.Builder() left_lead = kwant.Builder(kwant.TranslationalSymmetry((-1, 0))) top_lead = kwant.Builder(kwant.TranslationalSymmetry((1, 0))) for b, sites in [(syst, [square(x, y) for x in range(L) for y in range(W)]), (left_lead, [square(0, y) for y in range(W)]), (top_lead, [square(x, 0) for x in range(L)])]: for site in sites: h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() b[site] = h for hopping_kind in square.neighbors(): for hop in hopping_kind(b): b[hop] = (10 * rng.random_sample( (n, n)) + 1j * rng.random_sample((n, n))) syst.attach_lead(left_lead) syst.attach_lead(top_lead) syst = syst.finalized() def check(syst): for energy in [0, 1000]: wf = wave_function(syst, energy) ldos2 = np.zeros(wf.num_orb, float) for lead in range(len(syst.leads)): temp = abs(wf(lead)) temp **= 2 ldos2 += temp.sum(axis=0) ldos2 *= (0.5 / np.pi) assert_almost_equal(ldos2, ldos(syst, energy)) for fsyst in (syst, syst.precalculate(what='modes'), syst.precalculate(what='all')): check(fsyst) raises(ValueError, check, syst.precalculate(what='selfenergy')) syst.leads[0] = LeadWithOnlySelfEnergy(syst.leads[0]) raises(NotImplementedError, check, syst)
def test_cvp(): rng = ensure_rng(0) for i in range(1, 5): for j in range(i, 5): mat = rng.randn(i, j) mat = lll.lll(mat)[0] for k in range(4): point = 50 * rng.randn(j) assert np.array_equal( lll.cvp(point, mat, 10)[:3], lll.cvp(point, mat, 3)) # Test equidistant vectors # Cubic lattice basis = np.eye(3) vec = np.zeros((3)) assert len(lll.cvp(vec, basis, n=2, group_by_length=True)) == 7 assert len(lll.cvp(vec, basis, n=3, group_by_length=True)) == 19 vec = 0.5 * np.array([1, 1, 1]) assert len(lll.cvp(vec, basis, group_by_length=True)) == 8 vec = 0.5 * np.array([1, 1, 0]) assert len(lll.cvp(vec, basis, group_by_length=True)) == 4 vec = 0.5 * np.array([1, 0, 0]) assert len(lll.cvp(vec, basis, group_by_length=True)) == 2 # Square lattice with offset offset = np.array([0, 0, 1]) basis = np.eye(3)[:2] vec = np.zeros((3)) + rng.rand() * offset assert len(lll.cvp(vec, basis, n=2, group_by_length=True)) == 5 assert len(lll.cvp(vec, basis, n=3, group_by_length=True)) == 9 vec = 0.5 * np.array([1, 1, 0]) + rng.rand() * offset assert len(lll.cvp(vec, basis, group_by_length=True)) == 4 vec = 0.5 * np.array([1, 0, 0]) + rng.rand() * offset assert len(lll.cvp(vec, basis, group_by_length=True)) == 2 # Hexagonal lattice basis = np.array([[1, 0], [-0.5, 0.5 * np.sqrt(3)]]) vec = np.zeros((2)) assert len(lll.cvp(vec, basis, n=2, group_by_length=True)) == 7 assert len(lll.cvp(vec, basis, n=3, group_by_length=True)) == 13 vec = np.array([0.5, 0.5 / np.sqrt(3)]) assert len(lll.cvp(vec, basis, group_by_length=True)) == 3 assert len(lll.cvp(vec, basis, n=2, group_by_length=True)) == 6 assert len(lll.cvp(vec, basis, n=3, group_by_length=True)) == 12
def test_two_equal_leads(smatrix): def check_fsyst(fsyst): sol = smatrix(fsyst) s, leads = sol.data, sol.lead_info assert_almost_equal(np.dot(s.conjugate().transpose(), s), np.identity(s.shape[0])) n_modes = len(leads[0].momenta) // 2 assert len(leads[1].momenta) // 2 == n_modes assert_almost_equal(s[:n_modes, :n_modes], 0) t_elements = np.sort(abs(np.asarray(s[n_modes:, :n_modes])), axis=None) t_el_should_be = n_modes * (n_modes - 1) * [0] + n_modes * [1] assert_almost_equal(t_elements, t_el_should_be) assert_almost_equal(sol.transmission(1, 0), n_modes) rng = ensure_rng(11) system = kwant.Builder() lead = kwant.Builder(kwant.TranslationalSymmetry((1, ))) h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() h *= 0.8 t = 4 * rng.random_sample((n, n)) + 4j * rng.random_sample((n, n)) lead[chain(0)] = system[chain(0)] = h lead[chain(0), chain(1)] = t system.attach_lead(lead) system.attach_lead(lead.reversed()) fsyst = system.finalized() for syst in (fsyst, fsyst.precalculate(), fsyst.precalculate(what='all')): check_fsyst(syst) raises(ValueError, check_fsyst, fsyst.precalculate(what='selfenergy')) # Test the same, but with a larger scattering region. system = kwant.Builder() system[[chain(0), chain(1)]] = h system[chain(0), chain(1)] = t system.attach_lead(lead) system.attach_lead(lead.reversed()) fsyst = system.finalized() for syst in (fsyst, fsyst.precalculate(), fsyst.precalculate(what='all')): check_fsyst(syst) raises(ValueError, check_fsyst, fsyst.precalculate(what='selfenergy'))
def test_output(smatrix): rng = ensure_rng(3) system = kwant.Builder() left_lead = kwant.Builder(kwant.TranslationalSymmetry((-1, ))) right_lead = kwant.Builder(kwant.TranslationalSymmetry((1, ))) for b, site in [(system, chain(0)), (system, chain(1)), (left_lead, chain(0)), (right_lead, chain(0))]: h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() b[site] = h for b, hopp in [(system, (chain(0), chain(1))), (left_lead, (chain(0), chain(1))), (right_lead, (chain(0), chain(1)))]: b[hopp] = (10 * rng.random_sample((n, n)) + 1j * rng.random_sample( (n, n))) system.attach_lead(left_lead) system.attach_lead(right_lead) fsyst = system.finalized() result1 = smatrix(fsyst) s, modes1 = result1.data, result1.lead_info assert s.shape == 2 * (sum(len(i.momenta) for i in modes1) // 2, ) s1 = result1.submatrix(1, 0) result2 = smatrix(fsyst, 0, (), [1], [0]) s2, modes2 = result2.data, result2.lead_info assert s2.shape == (len(modes2[1].momenta) // 2, len(modes2[0].momenta) // 2) assert_almost_equal(abs(s1), abs(s2)) assert_almost_equal(np.dot(s.T.conj(), s), np.identity(s.shape[0])) raises(ValueError, smatrix, fsyst, out_leads=[]) modes = smatrix(fsyst).lead_info h = fsyst.leads[0].cell_hamiltonian() t = fsyst.leads[0].inter_cell_hopping() modes1 = kwant.physics.modes(h, t)[0] h = fsyst.leads[1].cell_hamiltonian() t = fsyst.leads[1].inter_cell_hopping() modes2 = kwant.physics.modes(h, t)[0] assert_modes_equal(modes1, modes[0]) assert_modes_equal(modes2, modes[1])
def test_one_lead(smatrix): rng = ensure_rng(3) system = kwant.Builder() lead = kwant.Builder(kwant.TranslationalSymmetry((-1, ))) for b, site in [(system, chain(0)), (system, chain(1)), (system, chain(2)), (lead, chain(0))]: h = rng.random_sample((n, n)) + 1j * rng.random_sample((n, n)) h += h.conjugate().transpose() b[site] = h for b, hopp in [(system, (chain(0), chain(1))), (system, (chain(1), chain(2))), (lead, (chain(0), chain(1)))]: b[hopp] = (10 * rng.random_sample((n, n)) + 1j * rng.random_sample( (n, n))) system.attach_lead(lead) fsyst = system.finalized() for syst in (fsyst, fsyst.precalculate(), fsyst.precalculate(what='all')): s = smatrix(syst).data assert_almost_equal(np.dot(s.conjugate().transpose(), s), np.identity(s.shape[0])) raises(ValueError, smatrix, fsyst.precalculate(what='selfenergy'))
def test_current_interpolation(): ## Passing a Builder will raise an error pytest.raises(TypeError, plotter.interpolate_current, syst_2d(), None) def R(theta): return ta.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]]) def make_lattice(a, theta): x = ta.dot(R(theta), (a, 0)) y = ta.dot(R(theta), (0, a)) return kwant.lattice.general([x, y], norbs=1) _test_border_0(plotter.interpolate_current) ## Check current through cross section is same for different lattice ## parameters and orientations of the system wrt. the discretization grid for a, theta, width in [(1, 0, 1), (1, 0, 0.5), (2, 0, 1), (1, 0.2, 1), (2, 0.4, 1)]: lat = make_lattice(a, theta) syst = syst_rect(lat, salt='0').finalized() psi = kwant.wave_function(syst, energy=3)(0) def cut(a, b): return b.tag[0] < 0 and a.tag[0] >= 0 J = kwant.operator.Current(syst).bind() J_cut = kwant.operator.Current(syst, where=cut, sum=True).bind() J_exact = J_cut(psi[0]) data = [] for n in [4, 6, 8, 11, 16]: j0, box = plotter.interpolate_current(syst, J(psi[0]), n=n, abswidth=width) x, y = (np.linspace(mn, mx, shape) for (mn, mx), shape in zip(box, j0.shape)) # slice field perpendicular to a cut along the y axis y_axis = (np.argmin(np.abs(x)), slice(None), 0) J_interp = scipy.integrate.simps(j0[y_axis], y) data.append((n, abs(J_interp - J_exact))) # 3rd value returned from 'linregress' is 'rvalue' # TODO: review this value once #280 has been dealt with. assert scipy.stats.linregress(np.log(data))[2] < -0.7 ### Tests on a divergence-free current (closed system) lat = kwant.lattice.general([(1, 0), (0.5, np.sqrt(3) / 2)], norbs=1) syst = kwant.Builder() sites = [lat(0, 0), lat(1, 0), lat(0, 1), lat(2, 2)] syst[sites] = None syst[((s, t) for s, t in itertools.product(sites, sites) if s != t)] = None del syst[lat(0, 0), lat(2, 2)] syst = syst.finalized() # generate random divergence-free currents Js = rotational_currents(syst.graph) rng = ensure_rng(3) J0 = sum(rng.rand(len(Js))[:, None] * Js) J1 = sum(rng.rand(len(Js))[:, None] * Js) # Sanity check that diverence on the graph is 0 divergence = np.zeros(len(syst.sites)) for (a, _), current in zip(syst.graph, J0): divergence[a] += current assert np.allclose(divergence, 0) j0, _ = plotter.interpolate_current(syst, J0) j1, _ = plotter.interpolate_current(syst, J1) ## Test linearity of interpolation. j_tot, _ = plotter.interpolate_current(syst, J0 + 2 * J1) assert np.allclose(j_tot, j0 + 2 * j1) ## Test that divergence of interpolated current approaches zero as we make ## the interpolation finer. data = [] for n in [4, 6, 8, 11, 16]: j, box = plotter.interpolate_current(syst, J0, n=n) dx = [(mx - mn) / (shape - 1) for (mn, mx), shape in zip(box, j.shape)] div_j = np.max(np.abs(div(j, dx))) data.append((n, div_j)) # 3rd value returned from 'linregress' is 'rvalue' # TODO: review this value once #280 has been dealt with. assert scipy.stats.linregress(np.log(data))[2] < -0.7
def test_blocks_symm_complex_projectors(): # Two blocks of equal size, related by any one of the discrete # symmetries. Each block by itself has no symmetry. The system is # transformed with a random unitary, such that the projectors onto the # blocks are complex. n = 8 rng = ensure_rng(27) # Symmetry class, sign of H under symmetry transformation. sym_info = [('AI', 1), ('AII', 1), ('D', -1), ('C', -1), ('AIII', -1)] for (sym, trans_sign) in sym_info: # Conservation law values cs = n*[-1] + n*[1] # Random onsite and hopping blocks h_cell, h_hop = random_onsite_hop(n, rng) # Symmetry operator if sym in ['AI', 'AII']: sym_op = np.array(kwant.rmt.h_t_matrix[sym]) sym_op = np.kron(np.identity(n // len(sym_op)), sym_op) elif sym in ['D', 'C']: sym_op = np.array(kwant.rmt.h_p_matrix[sym]) sym_op = np.kron(np.identity(n // len(sym_op)), sym_op) elif sym in ['AIII']: sym_op = np.kron(np.identity(n // 2), np.diag([1, -1])) else: raise ValueError('Symmetry class not covered.') # Full onsite and hoppings if sym in ['AI', 'AII', 'C', 'D']: # Antiunitary symmetries H_cell = la.block_diag(h_cell, trans_sign*sym_op.dot( h_cell.conj()).dot(sym_op.T.conj())) H_hop = la.block_diag(h_hop, trans_sign*sym_op.dot( h_hop.conj()).dot(sym_op.T.conj())) elif sym in ['AIII']: # Unitary symmetries H_cell = la.block_diag(h_cell, trans_sign*sym_op.dot( h_cell).dot(sym_op.T.conj())) H_hop = la.block_diag(h_hop, trans_sign*sym_op.dot( h_hop).dot(sym_op.T.conj())) sx = np.array([[0,1],[1,0]]) # Full symmetry operator relating the blocks S = np.kron(sx, sym_op) check_symm_ham(H_cell, H_hop, S, trans_sign, sym=sym) # Mix with a random unitary U = kwant.rmt.circular(2*n, 'A', rng=3) H_cell_t = U.T.conj().dot(H_cell).dot(U) H_hop_t = U.T.conj().dot(H_hop).dot(U) if sym in ['AI', 'AII', 'C', 'D']: S_t = U.T.conj().dot(S).dot(U.conj()) elif sym in ['AIII']: S_t = U.T.conj().dot(S).dot(U) # Conservation law matrix in the new basis cs_t = U.T.conj().dot(np.diag(cs)).dot(U) check_symm_ham(H_cell_t, H_hop_t, S_t, trans_sign, sym=sym) # Get the projectors. evals, evecs = np.linalg.eigh(cs_t) # Make sure the ordering is correct. assert_almost_equal(evals-np.array(cs), 0) projectors = [np.reshape(evecs[:, :n], (2*n, n)), np.reshape(evecs[:, n:2*n], (2*n, n))] # Ensure that the projectors sum to a unitary. assert_almost_equal(sum(projector.dot(projector.conj().T) for projector in projectors), np.eye(2*n)) projectors = [sparse.csr_matrix(p) for p in projectors] if sym in ['AI', 'AII']: prop, stab = kwant.physics.leads.modes(H_cell_t, H_hop_t, time_reversal=S_t, projectors=projectors) elif sym in ['C', 'D']: prop, stab = kwant.physics.leads.modes(H_cell_t, H_hop_t, particle_hole=S_t, projectors=projectors) elif sym in ['AIII']: prop, stab = kwant.physics.leads.modes(H_cell_t, H_hop_t, chiral=S_t, projectors=projectors) current_conserving(stab) nmodes = stab.nmodes block_nmodes = prop.block_nmodes vecs, vecslmbdainv = stab.vecs, stab.vecslmbdainv # Row indices for the blocks. Both are of size n. block_rows = [slice(0, n), slice(n, 2*n)] ######## Check that the two blocks are related by symmetry. # Compare the stabilized propagating modes of incident and outgoing # modes for the two blocks that are related by symmetry. Column # indices for the blocks. offsets = np.cumsum([0]+block_nmodes) block_cols = [slice(*i) for i in np.vstack([offsets[:-1], offsets[1:]]).T] # Mode rearrangement for each symmetry bnmodes = block_nmodes[0] # Number of modes in the first block if sym in ['AI', 'AII']: perm = np.arange(2*bnmodes)[::-1] elif sym in ['C', 'D']: perm = ((-1-np.arange(2*bnmodes)) % bnmodes + bnmodes * (np.arange(2*bnmodes) // bnmodes)) elif sym in ['AIII']: perm = (np.arange(2*bnmodes) % bnmodes + bnmodes * (np.arange(2*bnmodes) < bnmodes)) # Need both incident and outgoing stabilized modes to make the # comparison between blocks prop_modes = [(vecs[:, :nmodes], vecs[:, nmodes:2*nmodes], 1), (vecslmbdainv[:, :nmodes], vecslmbdainv[:, nmodes:2*nmodes], trans_sign)] # Symmetries that flip the sign of energy change the sign of # vecslmbdainv when used to construct modes between blocks. for (in_modes, out_modes, vecs_sign) in prop_modes: rows0, cols0 = block_rows[0], block_cols[0] rows1, cols1 = block_rows[1], block_cols[1] # Make sure the blocks are not empty if in_modes[rows0, cols0].size: # Check the real space representations of the stabilized modes sqrt_hop = stab.sqrt_hop modes0 = sqrt_hop.dot(np.hstack([in_modes[:, cols0], out_modes[:, cols0]])) modes1 = sqrt_hop.dot(np.hstack([in_modes[:, cols1], out_modes[:, cols1]])) # In the algorithm, the blocks are compared in the same order # they are specified. Block 0 is computed before block 1, so # block 1 is obtained by symmetry transforming the modes of # block 0. Check that it is so. if sym in ['AI', 'AII', 'C', 'D']: assert_almost_equal(S_t.dot(modes0.conj())[:, perm], vecs_sign*modes1) elif sym in ['AIII']: assert_almost_equal(S_t.dot(modes0)[:, perm], vecs_sign*modes1) # If first block is empty, so is the second one. else: assert not in_modes[rows1, cols1].size
def test_PHS_TRIM(): """Test the function that makes particle-hole symmetric modes at a TRIM. """ rng = ensure_rng(10) for n in (4, 8, 16, 60): for sym in kwant.rmt.sym_list: if kwant.rmt.p(sym): p_mat = np.array(kwant.rmt.h_p_matrix[sym]) p_mat = np.kron(np.identity(n // len(p_mat)), p_mat) P_squared = 1 if np.allclose(p_mat.conj().dot(p_mat), np.eye(*p_mat.shape)) else -1 if P_squared == 1: for nmodes in (1, 3, n//4, n//2, n): # Random matrix of 'modes.' Take part of a unitary # matrix to ensure that the modes form a basis. modes = kwant.rmt.circular(n, 'A', rng=rng)[:, :nmodes] # Ensure modes are particle-hole symmetric and # orthonormal modes = modes + p_mat.dot(modes.conj()) modes = la.qr(modes, mode='economic')[0] # Mix the modes with a random unitary transformation U = kwant.rmt.circular(nmodes, 'A', rng=rng) modes = modes.dot(U) # Make the modes PHS symmetric using the method for a # TRIM. phs_modes = leads.phs_symmetrization(modes, p_mat)[0] assert_almost_equal(phs_modes, p_mat.dot(phs_modes.conj()), err_msg='PHS broken at a TRIM in ' + sym) assert_almost_equal(phs_modes.T.conj().dot(phs_modes), np.eye(phs_modes.shape[1]), err_msg='Modes are not orthonormal,' 'TRIM PHS in ' + sym) elif P_squared == -1: # Need even number of modes =< n for nmodes in (2, 4, n//2, n): # Random matrix of 'modes.' Take part of a unitary # matrix to ensure that the modes form a basis. modes = kwant.rmt.circular(n, 'A', rng=rng)[:, :nmodes] # Ensure modes are particle-hole symmetric and # orthonormal. modes[:, nmodes//2:] = \ p_mat.dot(modes[:, :nmodes//2].conj()) modes = la.qr(modes, mode='economic')[0] # Mix the modes with a random unitary transformation U = kwant.rmt.circular(nmodes, 'A', rng=rng) modes = modes.dot(U) # Make the modes PHS symmetric using the method for a # TRIM. phs_modes = leads.phs_symmetrization(modes, p_mat)[0] assert_almost_equal(phs_modes[:, 1::2], p_mat.dot(phs_modes[:, ::2].conj()), err_msg='PHS broken at a TRIM in ' + sym) assert_almost_equal(phs_modes.T.conj().dot(phs_modes), np.eye(phs_modes.shape[1]), err_msg='Modes are not orthonormal,' ' TRIM PHS in ' + sym) # Test the off-diagonal case when p_mat = sigma_x p_mat = np.array([[0, 1], [1, 0]]) p_mat = np.kron(p_mat, np.identity(n // len(p_mat))) for nmodes in (1, 3, n//4, n//2): if nmodes > n//2: continue # Random matrix of 'modes.' Take part of a unitary # matrix to ensure that the modes form a basis, all modes # are only in half of the space modes = kwant.rmt.circular(n//2, 'A', rng=rng)[:, :nmodes] modes = np.vstack((modes, np.zeros((n//2, nmodes)))) # Add an orthogonal set of vectors that are ph images modes = np.hstack((modes, p_mat.dot(modes.conj()))) # Make the modes PHS symmetric using the method for a # TRIM. phs_modes = leads.phs_symmetrization(modes, p_mat)[0] assert_almost_equal(phs_modes, p_mat.dot(phs_modes.conj()), err_msg='PHS broken at a TRIM in ' 'off-diagonal test') assert_almost_equal(phs_modes.T.conj().dot(phs_modes), np.eye(phs_modes.shape[1]), err_msg='Modes are not orthonormal,' 'off-diagonal test')
def test_translational_symmetry(): ts = lattice.TranslationalSymmetry f2 = lattice.general(np.identity(2), norbs=1) f3 = lattice.general(np.identity(3), norbs=1) shifted = lambda site, delta: site.family(*ta.add(site.tag, delta)) raises(ValueError, ts, (0, 0, 4), (0, 5, 0), (0, 0, 2)) sym = ts((3.3, 0)) raises(ValueError, sym.add_site_family, f2) # Test lattices with dimension smaller than dimension of space. f2in3 = lattice.general([[4, 4, 0], [4, -4, 0]], norbs=1) sym = ts((8, 0, 0)) sym.add_site_family(f2in3) sym = ts((8, 0, 1)) raises(ValueError, sym.add_site_family, f2in3) # Test automatic fill-in of transverse vectors. sym = ts((1, 2)) sym.add_site_family(f2) assert sym.site_family_data[f2][2] != 0 sym = ts((1, 0, 2), (3, 0, 2)) sym.add_site_family(f3) assert sym.site_family_data[f3][2] != 0 transl_vecs = np.array([[10, 0], [7, 7]], dtype=int) sym = ts(*transl_vecs) assert sym.num_directions == 2 sym2 = ts(*transl_vecs[:1, :]) sym2.add_site_family(f2, transl_vecs[1:, :]) for site in [f2(0, 0), f2(4, 0), f2(2, 1), f2(5, 5), f2(15, 6)]: assert sym.in_fd(site) assert sym2.in_fd(site) assert sym.which(site) == (0, 0) assert sym2.which(site) == (0, ) for v in [(1, 0), (0, 1), (-1, 0), (0, -1), (5, 10), (-111, 573)]: site2 = shifted(site, np.dot(v, transl_vecs)) assert not sym.in_fd(site2) assert (v[0] != 0) != sym2.in_fd(site2) assert sym.to_fd(site2) == site assert (v[1] == 0) == (sym2.to_fd(site2) == site) assert sym.which(site2) == v assert sym2.which(site2) == v[:1] for hop in [(0, 0), (100, 0), (0, 5), (-2134, 3213)]: assert (sym.to_fd(site2, shifted(site2, hop)) == (site, shifted(site, hop))) # Test act for hoppings belonging to different lattices. f2p = lattice.general(2 * np.identity(2), norbs=1) sym = ts(*(2 * np.identity(2))) assert sym.act((1, 1), f2(0, 0), f2p(0, 0)) == (f2(2, 2), f2p(1, 1)) assert sym.act((1, 1), f2p(0, 0), f2(0, 0)) == (f2p(1, 1), f2(2, 2)) # Test add_site_family on random lattices and symmetries by ensuring that # it's possible to add site groups that are compatible with a randomly # generated symmetry with proper vectors. rng = ensure_rng(30) vec = rng.randn(3, 5) lat = lattice.general(vec, norbs=1) total = 0 for k in range(1, 4): for i in range(10): sym_vec = rng.randint(-10, 10, size=(k, 3)) if np.linalg.matrix_rank(sym_vec) < k: continue total += 1 sym_vec = np.dot(sym_vec, vec) sym = ts(*sym_vec) sym.add_site_family(lat) assert total > 20
def test_PHS_TRIM_degenerate_ordering(): """ Test PHS at a TRIM, both when it squares to 1 and -1. Take a Hamiltonian with 3 degenerate bands, the degeneracy of each is given in the tuple dims. The bands have different velocities. All bands intersect zero energy only at k = 0 and at the edge of the BZ, so all momenta are 0 or -pi. We thus have multiple TRIM modes, both with the same and different velocities. If P^2 = 1, all TRIM modes are eigenmodes of P. If P^2 = -1, TRIM modes come in pairs of particle-hole partners, ordered by a predefined convention.""" sy = np.array([[0,-1j],[1j,0]]) sz = np.array([[1,0],[0,-1]]) # P squares to 1. rng = ensure_rng(42) dims = (4, 8, 12) ts = (1.0, 1.7, 13.8) rand_hop = 1j*(0.1+rng.random_sample()) hop = la.block_diag(*[t*rand_hop*np.eye(dim) for t, dim in zip(ts, dims)]) pmat = np.eye(sum(dims)) onsite = np.zeros(hop.shape, dtype=complex) prop, stab = leads.modes(onsite, hop, particle_hole=pmat) current_conserving(stab) assert np.all([np.any(momentum - np.array([0, -np.pi])) for momentum in prop.momenta]) assert np.all([np.allclose(wf, pmat.dot(wf.conj())) for wf in prop.wave_functions.T]) # P squares to -1 dims = (1, 4, 10) ts = (1.0, 17.2, 13.4) hop_mat = np.kron(sz, 1j * (0.1 + rng.random_sample()) * np.eye(2)) blocks = [] for t, dim in zip(ts, dims): blocks += dim*[t*hop_mat] hop = la.block_diag(*blocks) pmat = np.kron(np.eye(sum(dims)), 1j*np.kron(sz, sy)) assert_almost_equal(pmat.dot(pmat.conj()), -np.eye(pmat.shape[0])) # The Hamiltonian anticommutes with P assert_almost_equal(pmat.dot(hop.conj()).dot(np.linalg.inv(pmat)), -hop) onsite = np.zeros(hop.shape, dtype=complex) prop, stab = leads.modes(onsite, hop, particle_hole=pmat) current_conserving(stab) # By design, all momenta are either 0 or -pi. assert np.all([np.any(momentum - np.array([0, -np.pi])) for momentum in prop.momenta]) wfs = prop.wave_functions momenta = prop.momenta velocities = prop.velocities nmodes = stab.nmodes # By design, all modes are at a TRIM here. Each must thus have a # particle-hole partner at the same TRIM and with the same velocity. # Incident modes check_PHS(0, momenta[:nmodes], velocities[:nmodes], wfs[:, :nmodes], pmat) check_PHS(-np.pi, momenta[:nmodes], velocities[:nmodes], wfs[:, :nmodes], pmat) # Outgoing modes check_PHS(0, momenta[nmodes:], velocities[nmodes:], wfs[:, nmodes:], pmat) check_PHS(-np.pi, momenta[nmodes:], velocities[nmodes:], wfs[:, nmodes:], pmat)
def test_hamiltonian_submatrix(): syst = kwant.Builder() chain = kwant.lattice.chain() for i in range(3): syst[chain(i)] = 0.5 * i for i in range(2): syst[chain(i), chain(i + 1)] = 1j * (i + 1) syst2 = syst.finalized() mat = syst2.hamiltonian_submatrix() assert mat.shape == (3, 3) # Sorting is required due to unknown compression order of builder. perm = np.argsort(syst2.onsite_hamiltonians) mat_should_be = np.array([[0, 1j, 0], [-1j, 0.5, 2j], [0, -2j, 1]]) mat = mat[perm, :] mat = mat[:, perm] np.testing.assert_array_equal(mat, mat_should_be) mat = syst2.hamiltonian_submatrix(sparse=True) assert sparse.isspmatrix_coo(mat) mat = mat.todense() mat = mat[perm, :] mat = mat[:, perm] np.testing.assert_array_equal(mat, mat_should_be) mat = syst2.hamiltonian_submatrix((), perm[[0, 1]], perm[[2]]) np.testing.assert_array_equal(mat, mat_should_be[:2, 2:3]) mat = syst2.hamiltonian_submatrix((), perm[[0, 1]], perm[[2]], sparse=True) mat = mat.todense() np.testing.assert_array_equal(mat, mat_should_be[:2, 2:3]) # Test for correct treatment of matrix input. syst = kwant.Builder() syst[chain(0)] = np.array([[0, 1j], [-1j, 0]]) syst[chain(1)] = np.array([[1]]) syst[chain(2)] = np.array([[2]]) syst[chain(1), chain(0)] = np.array([[1, 2j]]) syst[chain(2), chain(1)] = np.array([[3j]]) syst2 = syst.finalized() mat_dense = syst2.hamiltonian_submatrix() mat_sp = syst2.hamiltonian_submatrix(sparse=True).todense() np.testing.assert_array_equal(mat_sp, mat_dense) # Test precalculation of modes. rng = ensure_rng(5) lead = kwant.Builder(kwant.TranslationalSymmetry((-1,))) lead[chain(0)] = np.zeros((2, 2)) lead[chain(0), chain(1)] = rng.randn(2, 2) syst.attach_lead(lead) syst2 = syst.finalized() smatrix = kwant.smatrix(syst2, .1).data syst3 = syst2.precalculate(.1, what='modes') smatrix2 = kwant.smatrix(syst3, .1).data np.testing.assert_almost_equal(smatrix, smatrix2) raises(ValueError, kwant.solvers.default.greens_function, syst3, 0.2) # Test for shape errors. syst[chain(0), chain(2)] = np.array([[1, 2]]) syst2 = syst.finalized() raises(ValueError, syst2.hamiltonian_submatrix) raises(ValueError, syst2.hamiltonian_submatrix, sparse=True) syst[chain(0), chain(2)] = 1 syst2 = syst.finalized() raises(ValueError, syst2.hamiltonian_submatrix) raises(ValueError, syst2.hamiltonian_submatrix, sparse=True)
def test_modes_symmetries(): rng = ensure_rng(10) for n in (4, 8, 40, 60): for sym in kwant.rmt.sym_list: # Random onsite and hopping matrices in symmetry class h_cell = kwant.rmt.gaussian(n, sym, rng=rng) # Hopping is an offdiagonal block of a Hamiltonian. We rescale it # to ensure that there are modes at the Fermi level. h_hop = 10 * kwant.rmt.gaussian(2*n, sym, rng=rng)[:n, n:] if kwant.rmt.p(sym): p_mat = np.array(kwant.rmt.h_p_matrix[sym]) p_mat = np.kron(np.identity(n // len(p_mat)), p_mat) else: p_mat = None if kwant.rmt.t(sym): t_mat = np.array(kwant.rmt.h_t_matrix[sym]) t_mat = np.kron(np.identity(n // len(t_mat)), t_mat) else: t_mat = None if kwant.rmt.c(sym): c_mat = np.kron(np.identity(n // 2), np.diag([1, -1])) else: c_mat = None prop_modes, stab_modes = leads.modes(h_cell, h_hop, particle_hole=p_mat, time_reversal=t_mat, chiral=c_mat) current_conserving(stab_modes) wave_functions = prop_modes.wave_functions momenta = prop_modes.momenta nmodes = stab_modes.nmodes if t_mat is not None: assert_almost_equal(wave_functions[:, nmodes:], t_mat.dot(wave_functions[:, :nmodes].conj()), err_msg='TRS broken in ' + sym) if c_mat is not None: assert_almost_equal(wave_functions[:, nmodes:], c_mat.dot(wave_functions[:, :nmodes][:, ::-1]), err_msg='SLS broken in ' + sym) if p_mat is not None: # If P^2 = -1, then P psi(-k) = -psi(k) for k>0, so one must # look at positive and negative momenta separately. Test # positive momenta. first, last = momenta[:nmodes], momenta[nmodes:] in_positive_k = (np.pi > first) * (first > 0) out_positive_k = (np.pi > last) * (last > 0) wf_first = wave_functions[:, :nmodes] wf_last = wave_functions[:, nmodes:] assert_almost_equal(wf_first[:, in_positive_k[::-1]], p_mat.dot((wf_first[:, in_positive_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym) assert_almost_equal(wf_last[:, out_positive_k[::-1]], p_mat.dot((wf_last[:, out_positive_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym) # Test negative momenta. Need the sign of P^2 here. p_squared_sign = np.sign(p_mat.dot(p_mat.conj())[0, 0].real) in_neg_k = (-np.pi < first) * (first < 0) out_neg_k = (-np.pi < last) * (last < 0) assert_almost_equal(p_squared_sign*wf_first[:, in_neg_k[::-1]], p_mat.dot((wf_first[:, in_neg_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym) assert_almost_equal(p_squared_sign*wf_last[:, out_neg_k[::-1]], p_mat.dot((wf_last[:, out_neg_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym)
def random_onsite_hop(n, rng=0): rng = ensure_rng(rng) onsite = rng.randn(n, n) + 1j * rng.randn(n, n) onsite = onsite + onsite.T.conj() hop = rng.rand(n, n) + 1j * rng.rand(n, n) return onsite, hop
def test_block_relations_cons_PHS(): # Four blocks. Two identical blocks, each with particle-hole symmetry. Then # two blocks, related by particle-hole symmetry, but neither possessing it # on its own. These two blocks are not identical. There is a conservation # law relating the first two, and a discrete symmetry relating the latter # two. Also check the case when the latter two blocks have singular # hopping. n = 8 rng = ensure_rng(99) sym = 'C' # Particle-hole squares to -1 # Onsite and hopping blocks with particle-hole symm hP_cell = kwant.rmt.gaussian(n, sym, rng=rng) hP_hop = 10 * kwant.rmt.gaussian(2*n, sym, rng=rng)[:n, n:] p_mat = np.array(kwant.rmt.h_p_matrix[sym]) p_mat = np.kron(np.identity(n // len(p_mat)), p_mat) # Random onsite and hopping blocks h_cell, h_hop = random_onsite_hop(n) # Full onsite and hoppings H_cell = la.block_diag(hP_cell, hP_cell, h_cell, -p_mat.dot(h_cell.conj()).dot(p_mat.T.conj())) H_hop = la.block_diag(hP_hop, hP_hop, h_hop, -p_mat.dot(h_hop.conj()).dot(p_mat.T.conj())) # Also check the case when the hopping is singular (but square) in the # second two blocks. h_hop[:, ::2] = 0 H_hop_s = la.block_diag(hP_hop, hP_hop, h_hop, -p_mat.dot(h_hop.conj()).dot(p_mat.T.conj())) sx = np.array([[0,1],[1,0]]) # Particle-hole symmetry operator P_mat = la.block_diag(p_mat, p_mat, np.kron(sx, p_mat)) assert_almost_equal(P_mat.dot(H_cell.conj()) + H_cell.dot(P_mat), 0) assert_almost_equal(P_mat.dot(H_hop.conj()) + H_hop.dot(P_mat), 0) assert_almost_equal(P_mat.dot(H_hop_s.conj()) + H_hop_s.dot(P_mat), 0) assert_almost_equal(P_mat.dot(P_mat.conj()), -np.eye(P_mat.shape[0])) # Projectors projectors = np.split(np.eye(4*n), 4, 1) # Make the projectors sparse. projectors = [sparse.csr_matrix(p) for p in projectors] # Cover both singular and nonsingular hopping ham = [(H_cell, H_hop), (H_cell, H_hop_s)] for (H_cell, H_hop) in ham: prop, stab = kwant.physics.leads.modes(H_cell, H_hop, particle_hole=P_mat, projectors=projectors) current_conserving(stab) nmodes = stab.nmodes block_nmodes = prop.block_nmodes vecs, vecslmbdainv = stab.vecs, stab.vecslmbdainv # Row indices for the blocks. # All 4 blocks are of size n. block_rows = [slice(0, n), slice(n, 2*n), slice(2*n, 3*n), slice(3*n, 4*n)] ######## Check that the first two blocks are identical. ### Propagating modes ### # Column indices for the blocks. offsets = np.cumsum([0]+block_nmodes) block_cols = [slice(*i) for i in np.vstack([offsets[:-1], offsets[1:]]).T] prop_modes = (vecs[:, :nmodes], vecs[:, nmodes:2*nmodes], vecslmbdainv[:, :nmodes], vecslmbdainv[:, nmodes:2*nmodes]) check_identical_modes(prop_modes, block_rows, block_cols) ### Evanescent modes ### # First figure out the number of evanescent modes per block. The # number of relevant evanescent modes (outward decaying) in a block is # N - nmodes, where N is the dimension of the block and nmodes the # number of incident or outgoing propagating modes. block_cols = [N - nmodes for nmodes, N in zip(block_nmodes, 4*[n])] offsets = np.cumsum([0]+block_cols) block_cols = [slice(*i) for i in np.vstack([offsets[:-1], offsets[1:]]).T] ev_modes = (vecs[:, 2*nmodes:], vecslmbdainv[:, 2*nmodes:]) check_identical_modes(ev_modes, block_rows, block_cols) # Check that the second two blocks are related by PHS. # Here we only look at propagating modes. Compare the stabilized modes # of incident and outgoing modes for the two blocks that are related by # particle-hole symmetry, i.e. blocks 2 and 3. Column indices for the # blocks. offsets = np.cumsum([0]+block_nmodes) block_cols = [slice(*i) for i in np.vstack([offsets[:-1], offsets[1:]]).T] # Need both incident and outgoing stabilized modes to make the # comparison between blocks prop_modes = [(vecs[:, :nmodes], vecs[:, nmodes:2*nmodes], 1), (vecslmbdainv[:, :nmodes], vecslmbdainv[:, nmodes:2*nmodes], -1)] # P is antiunitary, such that vecslmbdainv changes sign when # used between blocks to construct modes. for (in_modes, out_modes, vecs_sign) in prop_modes: # Coordinates of blocks 2 and 3 rows2, cols2 = block_rows[2], block_cols[2] cols3 = block_cols[3] bnmodes = block_nmodes[2] # Number of modes in block 2 # Mode rearrangement by particle-hole symmetry perm = ((-1-np.arange(2*bnmodes)) % bnmodes + bnmodes * (np.arange(2*bnmodes) // bnmodes)) # Make sure the blocks are not empty if in_modes[rows2, cols2].size: # Check that the real space representations of the stabilized # modes of blocks 2 and 3 are related by particle-hole # symmetry. sqrt_hop = stab.sqrt_hop modes2 = sqrt_hop.dot(np.hstack([in_modes[:, cols2], out_modes[:, cols2]])) modes3 = sqrt_hop.dot(np.hstack([in_modes[:, cols3], out_modes[:, cols3]])) # In the algorithm, the blocks are compared in the same order # they are specified. Block 2 is computed before block 3, so # block 3 is obtained by particle-hole transforming the modes # of block 2. Check that it is so. assert_almost_equal(P_mat.dot(modes2.conj())[:, perm], vecs_sign*modes3)