Interacting Kitaev Chain Tutorial

In this example, we study the interacting Kitaev chain. We show how the Hamiltonian can be constructed symbolically or by using matrix representations of the fermionic operators, and how to restrict the Hilbert space to a subspace of a given parity. We solve for the ground states and characterize the locality of the many-body Majoranas.

We start by importing the necessary packages.

using FermionicHilbertSpaces, LinearAlgebra, Plots

Then we define the Hilbert space with N sites and parity conservation.

N = 10
H = hilbert_space(1:N, ParityConservation())
1024⨯1024 SymmetricFockHilbertSpace:
modes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ParityConservation([-1, 1])

Fermions are defined either as symbolic fermions f using the @fermions macro,

@fermions fsym
(FermionicHilbertSpaces.SymbolicFermionBasis(:fsym, 0xaab5e7db942062a0),)

or as sparse matrix representations fmat using the fermions function, taking H as argument.

fmat = fermions(H)
OrderedCollections.OrderedDict{Int64, SparseArrays.SparseMatrixCSC{Int64, Int64}} with 10 entries:
  1  => sparse([513, 516, 518, 519, 522, 523, 525, 528, 530, 531  …  494, 495, …
  2  => sparse([513, 515, 517, 519, 521, 523, 525, 527, 529, 531  …  493, 495, …
  3  => sparse([513, 514, 517, 518, 521, 522, 525, 526, 529, 530  …  493, 494, …
  4  => sparse([513, 514, 515, 516, 521, 522, 523, 524, 529, 530  …  491, 492, …
  5  => sparse([513, 514, 515, 516, 517, 518, 519, 520, 529, 530  …  487, 488, …
  6  => sparse([513, 514, 515, 516, 517, 518, 519, 520, 521, 522  …  487, 488, …
  7  => sparse([513, 514, 515, 516, 517, 518, 519, 520, 521, 522  …  471, 472, …
  8  => sparse([513, 514, 515, 516, 517, 518, 519, 520, 521, 522  …  439, 440, …
  9  => sparse([513, 514, 515, 516, 517, 518, 519, 520, 521, 522  …  375, 376, …
  10 => sparse([513, 514, 515, 516, 517, 518, 519, 520, 521, 522  …  247, 248, …

Let's define the interacting Kitaev chain Hamiltonian. It is a function of the fermions f and parameters N, μ, t, Δ, and U, representing the number of sites, chemical potential, hopping amplitude, pairing amplitude, and interaction strength, respectively. Here, f can be either symbolic (fsym) or a matrix representation (fmat). Note the use of the Hermitian conjugate hc, which simplifies the expression for the Hamiltonian.

kitaev_chain(f, N, μ, t, Δ, U) = sum(t * f[i]' * f[i+1] + hc for i in 1:N-1) +
                                 sum(Δ * f[i] * f[i+1] + hc for i in 1:N-1) +
                                 sum(U * f[i]' * f[i] * f[i+1]' * f[i+1] for i in 1:N-1) +
                                 sum(μ[i] * f[i]' * f[i] for i in 1:N)
kitaev_chain (generic function with 1 method)

Define parameters close to the sweet spot with perfectly localized Majoranas.

U = 4.0
t = 1.0
δΔ = 0.4
Δ = t + U / 2 - δΔ # slightly detuned from the sweet spot
μ = fill(-U / 2, N) # edge chemical potential
μ[2:N-1] .= -U # bulk chemical potential
params = (μ, t, Δ, U)
([-2.0, -4.0, -4.0, -4.0, -4.0, -4.0, -4.0, -4.0, -4.0, -2.0], 1.0, 2.6, 4.0)

We can now construct the Hamiltonian using symbolic fermions for a symbolic representation

hsym = kitaev_chain(fsym, N, μ, t, Δ, U)
Sum with 55 terms: 
 - 2.6*fsym†[1]*fsym†[2] - 4.0*fsym†[1]*fsym†[2]*fsym[1]*fsym[2] - 2.0*fsym†[1]*fsym[1] + ...

or using matrix representations for the matrix representation.

hmat = kitaev_chain(fmat, N, μ, t, Δ, U)
1024×1024 SparseArrays.SparseMatrixCSC{Float64, Int64} with 10238 stored entries:
⎡⢿⣷⣷⡈⠳⠄⠀⠘⢦⡀⠀⠀⠀⠀⠀⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⡙⠻⣿⣿⣴⠦⡀⠀⠀⠙⠂⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠙⠆⠰⡟⠿⣧⣌⣦⠠⣄⣄⠀⠀⠀⠀⠀⠀⠀⠈⠳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⣀⠀⠀⠈⠢⣽⣿⣿⣦⢈⠉⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠈⠳⣄⠀⠀⢦⡈⢛⣿⣿⡅⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠈⠀⠀⠙⢧⡀⠁⠉⣿⣿⣷⡈⠻⠅⠀⠘⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⡙⠻⣿⣿⣶⠦⡀⠀⠀⠙⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⢤⡀⠀⠀⠀⠀⠀⠀⠀⠙⠟⠆⠸⡟⠿⣧⣬⣦⠠⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠈⠢⣿⣿⣿⣦⣈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠈⠳⣄⠀⠀⢦⡈⢻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣧⡈⠳⠀⠀⠙⢦⡀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡉⠻⣿⣿⣿⠢⡀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠂⠻⡛⢻⣶⣼⡆⠰⣴⣄⠀⠀⠀⠀⠀⠀⠀⠈⠓⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠀⠈⠲⠿⣿⣿⣦⣌⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⡄⠀⢐⣦⡈⢿⣿⣿⣀⢀⠈⢳⣄⠀⠀⡀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⢘⣿⣿⣥⡈⠳⠀⠀⠙⢦⡀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⣀⡁⠻⣿⣿⣟⠢⡀⠀⠀⠉⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢦⡀⠀⠀⠀⠀⠀⠀⠀⠙⠙⠂⠻⡙⢻⣶⣼⠆⠰⣄⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠠⣄⠀⠀⠈⠲⠟⣿⣿⣦⣌⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠀⠀⠀⠀⠀⠈⠳⡄⠀⠐⢦⡈⢿⢿⣷⎦

To convert the symbolic Hamiltonian to a matrix representation, we can use the matrix_representation function.

matrix_representation(hsym, H) ≈ hmat # true
true

Now, let's diagonalize the system. Since parity is conserved, we can work in the even and odd parity sectors separately. To do this, we can create two new Hilbert spaces for the even and odd sectors

Heven = hilbert_space(1:N, ParityConservation(1))
Hodd = hilbert_space(1:N, ParityConservation(-1))
heven = matrix_representation(hsym, Heven)
hodd = matrix_representation(hsym, Hodd)
512×512 SparseArrays.SparseMatrixCSC{Float64, Int64} with 5120 stored entries:
⎡⢿⣷⣷⡈⠳⠄⠀⠘⢦⡀⠀⠀⠀⠀⠀⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⡙⠻⣿⣿⣴⠦⡀⠀⠀⠙⠂⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⎥
⎢⠙⠆⠰⡟⠿⣧⣜⣦⠠⣄⣄⠀⠀⠀⠀⠀⠀⠀⠈⠳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⎥
⎢⣀⠀⠀⠈⠲⣽⣿⣿⣦⢈⠉⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⎥
⎢⠈⠳⣄⠀⠀⢦⡈⢛⣿⣿⡅⠀⠀⠓⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⎥
⎢⠀⠀⠈⠀⠀⠙⢧⡀⠁⠉⣿⣿⣷⡈⠳⠅⠀⠘⢦⡀⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠙⢤⠀⡙⠻⣿⣿⣶⠦⡀⠀⠀⠙⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⢤⡀⠀⠀⠀⠀⠀⠀⠀⠙⠝⠆⠸⡟⠿⣧⣭⡆⠠⣄⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠈⠣⠿⣿⢟⣦⣈⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠈⠳⣄⠀⠀⢦⡈⢻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣧⡈⠳⠀⠀⠙⢦⡀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⡉⠻⣵⣿⣶⢢⡀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠙⠂⠸⣛⢻⣶⣼⡆⠰⣔⣄⠀⠀⠀⠀⠀⠀⠀⠈⠓⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⣄⠀⠀⠈⠲⠿⣿⣿⣦⣌⠀⠓⣄⠀⠀⠀⠀⠀⠀⠀⎥
⎢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠈⠳⡄⠀⢐⢦⡈⢿⣿⣿⣀⢀⠈⢳⣄⠀⠀⡀⠀⠀⎥
⎢⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢤⠀⠀⢘⣿⣿⣥⡈⠳⠀⠀⠙⢦⡀⎥
⎢⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⣀⡁⠻⣿⣿⣟⠦⡀⠀⠀⠉⎥
⎢⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢦⡀⠀⠀⠀⠀⠀⠀⠀⠙⠙⠂⠻⡝⢻⣶⣼⠆⠰⣄⎥
⎢⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠠⣄⠀⠀⠈⠲⠟⣿⣿⣦⣌⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠀⠀⠀⠀⠀⠈⠳⡄⠀⠐⢦⡈⢿⢿⣷⎦

and then diagonalize each sector separately.

oddvec = eigvecs(Matrix(hodd))[:, 1] # odd ground state
evenvec = eigvecs(Matrix(heven))[:, 1] # even ground state
512-element Vector{Float64}:
  0.03288119401730312
  0.033835134895431766
 -0.04205167644775886
  0.037632282939188876
  0.03982007966438107
 -0.04508591792174278
  0.03516280415418946
  0.034213395560383386
 -0.03989305953152109
  0.042787162289959324
  ⋮
 -0.03989305953152123
  0.03421339556038334
  0.03516280415418962
 -0.04508591792174299
  0.039820079664381336
  0.03763228293918863
 -0.042051676447758896
  0.03383513489543187
  0.03288119401730323

The ground states are almost degenerate, as expected.

first(eigvals(Matrix(hodd))) - first(eigvals(Matrix(heven))) # ≈ 10^-4
-0.00014249880423022887

Now, we construct the ground state Majoranas. First, we need to embed the lowest energy odd and even states into the full Hilbert space. We can do this by defining a DirectSum type that holds the spaces of the odd and even sectors.

struct DirectSum{HS}
    spaces::HS
end

Then, we can extend the odd and even vectors to the full Hilbert space.

function extend(v, p=Pair{<:AbstractHilbertSpace,<:DirectSum})
    mapreduce(H -> H == first(p) ? v : zeros(size(H, 1)), vcat, last(p).spaces)
end

Hsum = DirectSum((Hodd, Heven))
o = extend(oddvec, Hodd => Hsum) # odd ground state in the full Hilbert space
e = extend(evenvec, Heven => Hsum) # even ground state in the full Hilbert space
1024-element Vector{Float64}:
  0.0
  0.0
  0.0
  0.0
  0.0
  0.0
  0.0
  0.0
  0.0
  0.0
  ⋮
 -0.03989305953152123
  0.03421339556038334
  0.03516280415418962
 -0.04508591792174299
  0.039820079664381336
  0.03763228293918863
 -0.042051676447758896
  0.03383513489543187
  0.03288119401730323

Then, we can construct the ground state Majorana operators as

γ = o * e' + hc
γ̃ = 1im * o * e' + hc
1024×1024 Matrix{ComplexF64}:
 -0.0+0.0im         0.0-0.0im         …  0.0-0.00122576im  0.0-0.00119121im
  0.0+0.0im         0.0+0.0im            0.0+0.00132197im  0.0+0.0012847im
 -0.0+0.0im         0.0-0.0im            0.0-0.00120366im  0.0-0.00116973im
 -0.0+0.0im         0.0-0.0im            0.0-0.00115405im  0.0-0.00112152im
  0.0+0.0im         0.0+0.0im            0.0+0.00122173im  0.0+0.00118728im
  0.0+0.0im         0.0+0.0im         …  0.0+0.0012806im   0.0+0.0012445im
 -0.0+0.0im         0.0-0.0im            0.0-0.00136883im  0.0-0.00133024im
  0.0+0.0im         0.0+0.0im            0.0+0.00127393im  0.0+0.00123801im
 -0.0+0.0im         0.0-0.0im            0.0-0.00122773im  0.0-0.00119311im
 -0.0+0.0im         0.0-0.0im            0.0-0.00126104im  0.0-0.00122549im
     ⋮                                ⋱                    
  0.0-0.00144523im  0.0+0.00155866im  …  0.0+0.0im         0.0+0.0im
  0.0+0.00123947im  0.0-0.00133675im     0.0+0.0im         0.0+0.0im
  0.0+0.00127386im  0.0-0.00137384im     0.0+0.0im         0.0+0.0im
  0.0-0.00163335im  0.0+0.00176155im     0.0+0.0im         0.0+0.0im
  0.0+0.00144258im  0.0-0.0015558im      0.0+0.0im         0.0+0.0im
  0.0+0.00136333im  0.0-0.00147033im  …  0.0+0.0im         0.0+0.0im
  0.0-0.00152343im  0.0+0.001643im       0.0+0.0im         0.0+0.0im
  0.0+0.00122576im  0.0-0.00132197im     0.0+0.0im         0.0+0.0im
  0.0+0.00119121im  0.0-0.0012847im      0.0+0.0im         0.0+0.0im

Finally, we can check the locality of the Majorana operators. This is done by tracing down the Majorana operators to each mode. So let's first define the Hilbert space for each mode.

Hmodes = [hilbert_space(i:i) for i in 1:N]
10-element Vector{SimpleFockHilbertSpace{Int64}}:
 2⨯2 SimpleFockHilbertSpace:
modes: [1]
 2⨯2 SimpleFockHilbertSpace:
modes: [2]
 2⨯2 SimpleFockHilbertSpace:
modes: [3]
 2⨯2 SimpleFockHilbertSpace:
modes: [4]
 2⨯2 SimpleFockHilbertSpace:
modes: [5]
 2⨯2 SimpleFockHilbertSpace:
modes: [6]
 2⨯2 SimpleFockHilbertSpace:
modes: [7]
 2⨯2 SimpleFockHilbertSpace:
modes: [8]
 2⨯2 SimpleFockHilbertSpace:
modes: [9]
 2⨯2 SimpleFockHilbertSpace:
modes: [10]

Now we can compute the reduction of the Majorana operators to each mode.

γ_reductions = [norm(partial_trace(γ, H => Hmode)) for Hmode in Hmodes]
γ̃_reductions = [norm(partial_trace(γ̃, H => Hmode)) for Hmode in Hmodes]
10-element Vector{Float64}:
 1.401663147878615
 3.7964344997080343e-16
 0.17777260969374328
 2.082212459856022e-16
 0.024489122150747833
 6.470498218403839e-17
 0.00337629285483671
 3.986562883376773e-17
 0.0004621281414238482
 1.5608926981836748e-16

We can plot the reductions to visualize the localization of the Majorana modes.

plot(xlabel="Site", ylabel="||γᵢ|| / √2", title="Majorana Locality", frame=:box, size=(500, 300))
plot!(1:N, γ_reductions / sqrt(2), label="γ", lw=2)
plot!(1:N, γ̃_reductions / sqrt(2), label="γ̃", lw=2)
Example block output

This page was generated using Literate.jl.