def test_regular_fully_degenerate(): """Selfenergy with an invertible hopping matrix, and degenerate bands.""" w = 6 # width t = 0.5 # hopping element e = 1.3 # Fermi energy h_hop_s = -t * np.identity(w) h_cell_s = h_cell_s_func(t, w, e) h_hop = np.zeros((2*w, 2*w)) h_hop[:w, :w] = h_hop_s h_hop[w:, w:] = h_hop_s h_cell = np.zeros((2*w, 2*w)) h_cell[:w, :w] = h_cell_s h_cell[w:, w:] = h_cell_s g = np.zeros((2*w, 2*w), dtype=complex) g[:w, :w] = leads.square_selfenergy(w, t, e) g[w:, w:] = leads.square_selfenergy(w, t, e) assert_almost_equal(g, modes_se(h_cell, h_hop)) # Now with conservation laws and symmetries. conserved = np.identity(2*w) projectors = [sparse.csr_matrix(i) for i in [conserved[:, :w], conserved[:, w:]]] modes2 = leads.modes(h_cell, h_hop, projectors=projectors) current_conserving(modes2[1]) assert_almost_equal(g, modes2[1].selfenergy()) trs = sparse.identity(2*w) modes3 = leads.modes(h_cell, h_hop, projectors=projectors, time_reversal=trs) current_conserving(modes3[1]) assert_almost_equal(g, modes3[1].selfenergy()) phs = np.eye(2*w, 2*w, w) + np.eye(2*w, 2*w, -w) modes4 = leads.modes(h_cell, h_hop, projectors=projectors, time_reversal=trs, particle_hole=phs) current_conserving(modes4[1]) assert_almost_equal(g, modes4[1].selfenergy())
def test_for_all_evs_equal(): """Test an 'ideal lead' which has all eigenvalues e^ik equal.""" onsite = np.array([[0., 1.], [1., 0.]], dtype=complex) hopping = np.array([[0.0], [-1.0]], dtype=complex) modes = leads.modes(onsite, hopping)[1] assert modes.vecs.shape == (1, 2) assert modes.vecslmbdainv.shape == (1, 2) assert modes.nmodes == 1
def test_modes_bearded_ribbon(): # Check if bearded graphene ribbons work. lat = kwant.lattice.honeycomb() syst = kwant.Builder(kwant.TranslationalSymmetry((1, 0))) syst[lat.shape((lambda pos: -20 < pos[1] < 20), (0, 0))] = 0.3 syst[lat.neighbors()] = -1 syst = syst.finalized() h, t = syst.cell_hamiltonian(), syst.inter_cell_hopping() # The number of expected modes is calculated by plotting the dispersion. assert leads.modes(h, t)[1].nmodes == 8
def test_modes_bearded_ribbon(): # Check if bearded graphene ribbons work. lat = kwant.lattice.honeycomb(norbs=1) syst = kwant.Builder(kwant.TranslationalSymmetry((1, 0))) syst[lat.shape((lambda pos: -20 < pos[1] < 20), (0, 0))] = 0.3 syst[lat.neighbors()] = -1 syst = syst.finalized() h, t = syst.cell_hamiltonian(), syst.inter_cell_hopping() # The number of expected modes is calculated by plotting the dispersion. assert leads.modes(h, t)[1].nmodes == 8
def test_for_all_evs_equal(): """Test an 'ideal lead' which has all eigenvalues e^ik equal.""" onsite = np.array([[0., 1.], [1., 0.]], dtype=complex) hopping = np.array([[0.0], [-1.0]], dtype=complex) modes = leads.modes(onsite, hopping)[1] current_conserving(modes) assert modes.vecs.shape == (1, 2) assert modes.vecslmbdainv.shape == (1, 2) assert modes.nmodes == 1
def test_modes(): h, t = .3, .7 k = np.arccos(-h / (2 * t)) v = 2 * t * np.sin(k) prop, stab = leads.modes(np.array([[h]]), np.array([[t]])) assert stab.nmodes == 1 assert stab.sqrt_hop[0] == np.sqrt(np.linalg.norm(t)) np.testing.assert_almost_equal(prop.velocities, [-v, v]) np.testing.assert_almost_equal(prop.momenta, [k, -k]) # Test for normalization by current. np.testing.assert_almost_equal( 2 * (stab.vecs[0] * stab.vecslmbdainv[0].conj()).imag, [1, -1])
def test_modes(): h, t = .3, .7 k = np.arccos(-h / (2 * t)) v = 2 * t * np.sin(k) prop, stab = leads.modes(np.array([[h]]), np.array([[t]])) current_conserving(stab) assert stab.nmodes == 1 assert stab.sqrt_hop[0] == np.sqrt(np.linalg.norm(t)) np.testing.assert_almost_equal(prop.velocities, [-v, v]) np.testing.assert_almost_equal(prop.momenta, [k, -k]) # Test for normalization by current. np.testing.assert_almost_equal( 2 * (stab.vecs[0] * stab.vecslmbdainv[0].conj()).imag, [1, -1])
def test_zero_hopping(): h_cell = np.identity(2) h_hop = np.zeros((2, 1)) expected = (leads.PropagatingModes(np.zeros((2, 0)), np.zeros((0,)), np.zeros((0,))), leads.StabilizedModes(np.zeros((0, 0)), np.zeros((0, 0)), 0, np.zeros((1, 0)))) actual = leads.modes(h_cell, h_hop) assert all(np.alltrue(getattr(actual[1], attr) == getattr(expected[1], attr)) for attr in ('vecs', 'vecslmbdainv', 'nmodes', 'sqrt_hop')) assert all(np.alltrue(getattr(actual[0], attr) == getattr(expected[0], attr)) for attr in ('wave_functions', 'velocities', 'momenta'))
def check_equivalence(h, t, n, sym='', particle_hole=None, chiral=None, time_reversal=None): """Compare modes stabilization algorithms for a given Hamiltonian.""" u, s, vh = la.svd(t) u, v = u * np.sqrt(s), vh.T.conj() * np.sqrt(s) prop_vecs = [] evan_vecs = [] algos = [None, (True, True), (True, False), (False, True), (False, False)] for algo in algos: result = leads.modes(h, t, stabilization=algo, chiral=chiral, particle_hole=particle_hole, time_reversal=time_reversal)[1] current_conserving(result, (sym, algo, n)) vecs, vecslmbdainv = result.vecs, result.vecslmbdainv # Bring the calculated vectors to real space if algo is not None: vecs = np.dot(v, vecs) np.testing.assert_almost_equal(result.sqrt_hop, v) else: vecslmbdainv = (np.dot(v.T.conj(), vecslmbdainv) / np.sqrt(np.linalg.norm(t))) vecs = vecs * np.sqrt(np.linalg.norm(t)) full_vecs = np.r_[vecslmbdainv, vecs] prop_vecs.append(full_vecs[:, : 2 * result.nmodes]) evan_vecs.append(full_vecs[:, 2 * result.nmodes :]) msg = 'Stabilization {0} failed.in symmetry class {1}' for vecs, algo in zip(prop_vecs, algos): # Propagating modes should have identical ordering, and only vary # By a phase np.testing.assert_allclose(np.abs(np.sum(vecs/prop_vecs[0], axis=0)), vecs.shape[0], err_msg=msg.format(algo, sym)) for vecs, algo in zip(evan_vecs, algos): # Evanescent modes must span the same linear space. mat = np.c_[vecs, evan_vecs[0]] # Scale largest singular value to 1 if the array is not empty mat = mat/np.linalg.norm(mat, ord=2) # As a tolerance, take the square root of machine precision times the # largest matrix dimension. tol = np.abs(np.sqrt(max(mat.shape)*np.finfo(mat.dtype).eps)) assert (np.linalg.matrix_rank(mat, tol=tol) == vecs.shape[1]), msg.format(algo)+' in symmetry class '+sym
def check_equivalence(h, t, n, sym='', particle_hole=None, chiral=None, time_reversal=None): """Compare modes stabilization algorithms for a given Hamiltonian.""" u, s, vh = np.linalg.svd(t) u, v = u * np.sqrt(s), vh.T.conj() * np.sqrt(s) prop_vecs = [] evan_vecs = [] algos = [None, (True, True), (True, False), (False, True), (False, False)] for algo in algos: result = leads.modes(h, t, stabilization=algo, chiral=chiral, particle_hole=particle_hole, time_reversal=time_reversal)[1] vecs, vecslmbdainv = result.vecs, result.vecslmbdainv # Bring the calculated vectors to real space if algo is not None: vecs = np.dot(v, vecs) np.testing.assert_almost_equal(result.sqrt_hop, v) else: vecslmbdainv = (np.dot(v.T.conj(), vecslmbdainv) / np.sqrt(np.linalg.norm(t))) vecs = vecs * np.sqrt(np.linalg.norm(t)) full_vecs = np.r_[vecslmbdainv, vecs] prop_vecs.append(full_vecs[:, : 2 * result.nmodes]) evan_vecs.append(full_vecs[:, 2 * result.nmodes :]) msg = 'Stabilization {0} failed.' for vecs, algo in zip(prop_vecs, algos): # Propagating modes should have identical ordering, and only vary # By a phase np.testing.assert_allclose(np.abs(np.sum(vecs/prop_vecs[0], axis=0)), vecs.shape[0], err_msg=msg.format(algo)+' in symmetry class '+sym) for vecs, algo in zip(evan_vecs, algos): # Evanescent modes must span the same linear space. mat = np.c_[vecs, evan_vecs[0]] # Scale largest singular value to 1 if the array is not empty mat = mat/np.linalg.norm(mat, ord=2) # As a tolerance, take the square root of machine precision times the largest # matrix dimension. tol = np.abs(np.sqrt(max(mat.shape)*np.finfo(mat.dtype).eps)) assert (np.linalg.matrix_rank(mat, tol=tol) == vecs.shape[1]), msg.format(algo)+' in symmetry class '+sym
def test_algorithm_equivalence(): np.random.seed(400) n = 12 h = np.random.randn(n, n) + 1j * np.random.randn(n, n) h += h.T.conj() t = np.random.randn(n, n) + 1j * np.random.randn(n, n) u, s, vh = np.linalg.svd(t) u, v = u * np.sqrt(s), vh.T.conj() * np.sqrt(s) prop_vecs = [] evan_vecs = [] algos = [None, (True, True), (True, False), (False, True), (False, False)] for algo in algos: result = leads.modes(h, t, stabilization=algo)[1] vecs, vecslmbdainv = result.vecs, result.vecslmbdainv # Bring the calculated vectors to real space if algo is not None: vecs = np.dot(v, vecs) np.testing.assert_almost_equal(result.sqrt_hop, v) else: vecslmbdainv = (np.dot(v.T.conj(), vecslmbdainv) / np.sqrt(np.linalg.norm(t))) vecs = vecs * np.sqrt(np.linalg.norm(t)) full_vecs = np.r_[vecslmbdainv, vecs] prop_vecs.append(full_vecs[:, : 2 * result.nmodes]) evan_vecs.append(full_vecs[:, 2 * result.nmodes :]) msg = 'Stabilization {0} failed.' for vecs, algo in zip(prop_vecs, algos): # Propagating modes should have identical ordering, and only vary # By a phase np.testing.assert_allclose(np.abs(np.sum(vecs/prop_vecs[0], axis=0)), vecs.shape[0], err_msg=msg.format(algo)) for vecs, algo in zip(evan_vecs, algos): # Evanescent modes must span the same linear space. assert (np.linalg.matrix_rank(np.c_[vecs, evan_vecs[0]], tol=1e-12) == vecs.shape[1]), msg.format(algo)
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 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_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 ### np.random.seed(42) dims = (4, 10, 20) ts = (1.0, 1.7, 13.8) rand_hop = 1j*(0.1+np.random.rand()) hop = la.block_diag(*[t*rand_hop*np.eye(dim) for t, dim in zip(ts, dims)]) # Particle-hole operator pmat = np.eye(sum(dims)) onsite = np.zeros(hop.shape, dtype=complex) prop, stab = leads.modes(onsite, hop, particle_hole=pmat) # All momenta are either 0 or -pi. assert np.all([np.any(ele - np.array([0, -np.pi])) for ele in prop.momenta]) # All modes are eigenmodes of P. assert np.all([np.allclose(wf, pmat.dot(wf.conj())) for wf in prop.wave_functions.T]) ########### ### P squares to -1 ### np.random.seed(1337) dims = (1, 4, 16) ts = (1.0, 17.2, 13.4) hop_mat = np.kron(sz, 1j*(0.1+np.random.rand())*np.eye(2)) blocks = [] for t, dim in zip(ts, dims): blocks += dim*[t*hop_mat] hop = la.block_diag(*blocks) # Particle-hole operator pmat = np.kron(np.eye(sum(dims)), 1j*np.kron(sz, sy)) # P squares to -1 assert np.allclose(pmat.dot(pmat.conj()), -np.eye(pmat.shape[0])) # The Hamiltonian anticommutes with P assert np.allclose(pmat.dot(hop.conj()).dot(npl.inv(pmat)), -hop) onsite = np.zeros(hop.shape, dtype=complex) prop, stab = leads.modes(onsite, hop, particle_hole=pmat) # By design, all momenta are either 0 or -pi. assert np.all([np.any(ele - np.array([0, -np.pi])) for ele 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_modes_symmetries(): np.random.seed(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) # 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)[: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) 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. in_positive_k = (np.pi > momenta[:nmodes]) * (momenta[:nmodes] > 0) out_positive_k = (np.pi > momenta[nmodes:]) * (momenta[nmodes:] > 0) assert_almost_equal(wave_functions[:, :nmodes][:, in_positive_k[::-1]], p_mat.dot((wave_functions[:, :nmodes][:, in_positive_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym) assert_almost_equal(wave_functions[:, nmodes:][:, out_positive_k[::-1]], p_mat.dot((wave_functions[:, nmodes:][:, 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 < momenta[:nmodes]) * (momenta[:nmodes] < 0) out_neg_k = (-np.pi < momenta[nmodes:]) * (momenta[nmodes:] < 0) assert_almost_equal(p_squared_sign*wave_functions[:, :nmodes][:, in_neg_k[::-1]], p_mat.dot((wave_functions[:, :nmodes][:, in_neg_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym) assert_almost_equal(p_squared_sign*wave_functions[:, nmodes:][:, out_neg_k[::-1]], p_mat.dot((wave_functions[:, nmodes:][:, out_neg_k][:, ::-1]).conj()), err_msg='PHS broken in ' + sym)
def test_cons_blocks_sizes(): # Hamiltonian with a conservation law consisting of three blocks of # different size. n = 8 # Three blocks of different sizes. cs = np.diag(n*[-1] + 2*n*[1] + 3*n*[2]) # Onsite and hopping hc, hh = random_onsite_hop(6*n) hc[:n, n:] = 0 hc[n:, :n] = 0 hc[n:3*n, 3*n:] = 0 hc[3*n:, n:3*n] = 0 assert_almost_equal(cs.dot(hc) - hc.dot(cs), 0) hh[:n, n:] = 0 hh[n:, :n] = 0 hh[n:3*n, 3*n:] = 0 hh[3*n:, n:3*n] = 0 assert_almost_equal(cs.dot(hh) - hh.dot(cs), 0) # Mix them with a random unitary U = kwant.rmt.circular(6*n, 'A', rng=23) cs_t = U.T.conj().dot(cs).dot(U) hc_t = U.T.conj().dot(hc).dot(U) hh_t = U.T.conj().dot(hh).dot(U) assert_almost_equal(cs_t.dot(hc_t) - hc_t.dot(cs_t), 0) assert_almost_equal(cs_t.dot(hh_t) - hh_t.dot(cs_t), 0) # Get the projectors. There are three of them, composed of # the eigenvectors of the conservation law. evals, evecs = np.linalg.eigh(cs_t) # Make sure the ordering is correct. assert_almost_equal(evals-np.array(n*[-1] + 2*n*[1] + 3*n*[2]), 0) # First projector projects onto block with # eigenvalue -1 of size n, second to eigenvalue 1 of # size 2n, third onto block with eigenvalue 2 of size 3n. projectors = [np.reshape(evecs[:,:n], (6*n, n)), np.reshape(evecs[:,n:3*n], (6*n, 2*n)), np.reshape(evecs[:,3*n:6*n], (6*n, 3*n))] # Check that projectors are correctly defined. assert_almost_equal(evecs, np.hstack(projectors)) ######## # Compute self energy with and without specifying projectors. # Make the projectors sparse. projectors = [sparse.csr_matrix(p) for p in projectors] # Modes without projectors modes1 = leads.modes(hc_t, hh_t) current_conserving(modes1[1]) # With projectors modes2 = leads.modes(hc_t, hh_t, projectors=projectors) current_conserving(modes2[1]) assert_almost_equal(modes1[1].selfenergy(), modes2[1].selfenergy()) ######## # Check that the number of modes per block with projectors matches the # total number of modes, with and without projectors. prop1, stab1 = modes1 # No projectors prop2, stab2 = modes2 # Projectors assert_almost_equal(stab1.nmodes, sum(prop1.block_nmodes)) assert_almost_equal(stab2.nmodes, sum(prop2.block_nmodes)) assert_almost_equal(stab1.nmodes, sum(prop2.block_nmodes)) ######## # Check that with projectors specified, in/out propagating modes and # evanescent modes in vecs and vecslmbdainv are block diagonal. # Do this by checking explicitly that the blocks are nonzero, and that # if all blocks are set to zero manually, the stabilized modes only contain # zeros (meaning that anything off the block diagonal is zero). nmodes = stab2.nmodes block_nmodes = prop2.block_nmodes vecs, vecslmbdainv = stab2.vecs, stab2.vecslmbdainv # Row indices for the blocks. # The first block is of size n, second of size 2n # and the third of size 3n. block_rows = [slice(0, n), slice(n, 3*n), slice(3*n, 6*n)] ### Check 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_bdiag_modes(prop_modes, block_rows, block_cols) ### Check 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, [n, 2*n, 3*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_bdiag_modes(ev_modes, block_rows, block_cols)