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
using Arpack

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

N = 12
H = hilbert_space(1:N, ParityConservation())
4096-dimensional SymmetricFockHilbertSpace:
12 fermions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
FockSymmetry(ParityConservation([-1, 1]))

Symbolic fermions can be defined using the @fermions macro,

@fermions f
(FermionicHilbertSpaces.SymbolicFermionBasis(:f, 0x39415815c723b896),)

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. 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
10-element view(::Vector{Float64}, 2:11) with eltype Float64:
 -4.0
 -4.0
 -4.0
 -4.0
 -4.0
 -4.0
 -4.0
 -4.0
 -4.0
 -4.0

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

hsym = kitaev_chain(f, N, μ, t, Δ, U)
Sum with 67 terms: 
-4.0*f†[8]*f†[9]*f[8]*f[9]+f†[4]*f[3]+2.6*f[4]*f[5] + ...

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

matrix_representation(hsym, H)
4096×4096 SparseArrays.SparseMatrixCSC{Float64, Int64} with 49151 stored entries:
⎡⢿⣷⣷⡈⠳⠄⠀⠘⢦⡀⠀⠀⠀⠀⠀⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⡙⠻⣿⣿⣴⢦⡀⠀⠀⠙⠂⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠙⠆⠰⣟⠿⣧⣝⣦⠠⣄⣄⠀⠀⠀⠀⠀⠀⠀⠈⠳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⣀⠀⠀⠈⠳⣽⣿⣿⣦⢈⠉⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠈⠳⣄⠀⠀⢦⡈⢛⣿⣿⡅⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠈⠀⠀⠙⢧⡀⠁⠉⣿⣿⣷⡈⠻⠅⠀⠘⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⡙⠻⣿⣿⣶⢦⡀⠀⠀⠙⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⢤⡀⠀⠀⠀⠀⠀⠀⠀⠙⠟⠆⠸⣟⠿⣧⣽⣦⠠⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠈⠳⣿⣿⣿⣦⣈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠈⠳⣄⠀⠀⢦⡈⢻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣧⡈⠳⠀⠀⠙⢦⡀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡉⠻⣿⣿⣿⢦⡀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠂⠻⣟⢻⣶⣽⡆⠰⣴⣄⠀⠀⠀⠀⠀⠀⠀⠈⠓⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠀⠈⠳⠿⣿⣿⣦⣌⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⡄⠀⢐⣦⡈⢿⣿⣿⣀⢀⠈⢳⣄⠀⠀⡀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⢘⣿⣿⣥⡈⠳⠀⠀⠙⢦⡀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⣀⡁⠻⣿⣿⣟⢦⡀⠀⠀⠉⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢦⡀⠀⠀⠀⠀⠀⠀⠀⠙⠙⠂⠻⣝⢻⣶⣽⠆⠰⣄⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠠⣄⠀⠀⠈⠳⠟⣿⣿⣦⣌⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠀⠀⠀⠀⠀⠈⠳⡄⠀⠐⢦⡈⢿⢿⣷⎦

Now, let's diagonalize the system. Since parity is conserved, we can work in the even and odd parity sectors separately.

import FermionicHilbertSpaces: indices, sector, quantumnumbers
(Eo, o), (Ee, e) = map(quantumnumbers(H)) do parity
    Hsec = sector(parity, H)
    ham = matrix_representation(hsym, Hsec)
    vals, vecs = eigs(ham; nev=1, which=:SR)
    inds = indices(Hsec, H)
    ground_state = zeros(eltype(vecs), dim(H))
    ground_state[inds] = vecs[:, 1]
    (; energy=first(vals), ground_state)
end
2-element Vector{@NamedTuple{energy::Float64, ground_state::Vector{Float64}}}:
 (energy = -30.884311784513162, ground_state = [-0.01719924320326297, 0.018548790166372446, -0.016890092086739035, -0.01619378979334392, 0.01714560172625711, 0.01797227143022799, -0.01920782861443476, 0.017876658689792272, -0.017218005481788652, -0.017685368150297053  …  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
 (energy = -30.884331431867, ground_state = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  …  0.020300746748632702, -0.018927094914547606, 0.016242117258605148, 0.016692660286701418, -0.021406546465997045, 0.018905344284311825, 0.017864375792244647, -0.01996274836346016, 0.016060979381801885, 0.015608274175071112])

The ground states are almost degenerate, as expected. Then, we can construct the ground state Majorana operators as γ = o * e' + hc and γ̃ = 1im * o * e' + hc but that takes a lot of memory for large systems. We can use LowRankMatrices.jl to avoid this

using LowRankMatrices
γ = LowRankMatrix(o, e) + hc
γ̃ = 1im * LowRankMatrix(o, e) + hc
δρ = LowRankMatrix(o, o) - LowRankMatrix(e, e)
4096×4096 LowRankMatrices.LowRankMatrix{Float64}:
  0.000295814  -0.000319025   0.000290497  …   0.0           0.0
 -0.000319025   0.000344058  -0.000313291      0.0           0.0
  0.000290497  -0.000313291   0.000285275      0.0           0.0
  0.000278521  -0.000300375   0.000273515      0.0           0.0
 -0.000294891   0.00031803   -0.000289591      0.0           0.0
 -0.000309109   0.000333364  -0.000303553  …   0.0           0.0
  0.00033036   -0.000356282   0.000324422      0.0           0.0
 -0.000307465   0.00033159   -0.000301938      0.0           0.0
  0.000296137  -0.000319373   0.000290814      0.0           0.0
  0.000304175  -0.000328042   0.000298707      0.0           0.0
  ⋮                                        ⋱                 ⋮
  0.0           0.0           0.0              0.000303988   0.000295419
  0.0           0.0           0.0             -0.000260864  -0.000253511
  0.0           0.0           0.0             -0.0002681    -0.000260544
  0.0           0.0           0.0          …   0.00034381    0.000334119
  0.0           0.0           0.0             -0.000303638  -0.00029508
  0.0           0.0           0.0             -0.000286919  -0.000278832
  0.0           0.0           0.0              0.000320621   0.000311584
  0.0           0.0           0.0             -0.000257955  -0.000250684
  0.0           0.0           0.0          …  -0.000250684  -0.000243618

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

Hmodes = [hilbert_space(i:i) for i in 1:N]
γR = [partial_trace(γ, H => Hmode) for Hmode in Hmodes];
γ̃R = [partial_trace(γ̃, H => Hmode) for Hmode in Hmodes];
δρR = [partial_trace(δρ, H => Hmode) for Hmode in Hmodes];
γ_reductions = [norm(svdvals(γ), 1) for γ in γR]
γ̃_reductions = [norm(svdvals(γ̃), 1) for γ̃ in γ̃R]
LD = [norm(svdvals(δρ), 1) for δρ in δρR]
12-element Vector{Float64}:
 6.532101248790667e-15
 3.10775710721245e-15
 1.6837659738699884e-15
 8.996709627284716e-16
 1.1753835751915354e-15
 1.0685896612017132e-15
 3.649424512586208e-16
 3.3837949803272593e-16
 3.5843723822370777e-16
 1.6894580352755373e-15
 3.0939335295132597e-15
 3.6660670159338604e-15

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

#
lw = 4
legendfontsize = 15
marker = true
markerstrokewidth = 2
plot(xlabel="Site", title="Majorana quality measures"; frame=:box, size=(500, 300), xticks=1:3:N, yscale=:identity, legendfontsize, ylims=(-1e-1, 2), legendposition=:top, labelfontsize=15)
plot!(1:N, γ_reductions; label="‖γₙ‖", lw, legendfontsize, marker, markerstrokewidth)
plot!(1:N, γ̃_reductions; label="‖γ̃ₙ‖", lw, legendfontsize, marker, markerstrokewidth)
plot!(1:N, LD; label="‖(iγγ̃)ₙ‖", lw, legendfontsize, marker, markerstrokewidth)
Example block output

This page was generated using Literate.jl.