Fermionic hopping dressed with quantized Peierls phase and conservation of fermions

Hello again,

I am working on the one dimensional extended Hubbard model for spinless fermions and a light matter coupling that is introduced via the quantized Peierls phase. I want to implement this for open and periodic boundary conditions. This is the corresponding full Hamiltonian for open boundary conditions:

H = \sum_{j=1}^{L-1} -t\left( e^{i\frac{g}{\sqrt{L}}(a + a^\dagger)} c^\dagger_j c_{j+1} + e^{-i\frac{g}{\sqrt{L}}(a + a^\dagger)} c^\dagger_{j+1} c_{j} \right) + \sum_{j=1}^{L-1} U n_j n_{j+1} + \Omega N_{\text{ph}}

I am trying to implement this using OpSum(), where I compute the matrix form of Peierls phase on a truncated Hilbert space.

function build_peierls_phase(g::Real, dim_ph::Int)::Matrix{ComplexF64}
    # zeros on diagonal
    d = zeros(Float64, dim_ph)

    # off-diagonal entries: sqrt(1), …, sqrt(dim_ph-1)
    e = sqrt.(collect(1:dim_ph-1)) # i.e. from 1 to N_ph-1

    # diagonalize a + a^\dagger =: A which is tridiagonal in the number basis
    A = SymTridiagonal(d, e)
    eigenvals, eigenvecs = eigen(A)

    # write A = V D V^\dagger with D diagonal matrix of eigenvals and V matrix of eigenvecs
    phases = exp.(1im * g .* eigenvals) # .* element-wise multiplication
    # U = V * diag(phases) * V†
    U = eigenvecs * Diagonal(phases) * eigenvecs'

    return ComplexF64.(U) # ensure complex type
end

function xxz_cavity(
    sites::Vector{<:Index},
    t::Real=1.0,
    U::Real=1.0,
    g::Real=1.0,
    omega::Real=1.0
)::MPO
    # unpack sites with convention that the last one is the bosonic one
    f_sites = sites[1:end-1]
    b_site = sites[end]

    L = length(f_sites) # number of fermionic sites
    L ≥ 2 || throw(ArgumentError("Need at least two lattice sites"))

    P = build_peierls_phase(g, dim(b_site))
    os = OpSum()
    b = L + 1 # boson site index
    for j in 1:(L-1)
        # dressed hopping
        os += -t, P, b, "c†", j, "c", j+1
        os += -t, P', b, "c†", j+1, "c", j
        # interaction term
        os += U, "n", j, "n", j+1
    end

    # add boson energy term
    os += omega, "N", b

    return MPO(os, sites)
end

which can be used for example like this

function main()
    L = 4
    N_ph = 5
    conserve_qns = false
    f_sites = siteinds("Fermion", L; conserve_qns=conserve_qns)
    b_site = siteind("Boson"; dim=N_ph+1, conserve_qns=conserve_qns)
    sites = vcat(f_sites, [b_site])

    H = xxz_cavity(sites)
    f_state = [isodd(n) ? "1" : "0" for n=1:L]
    b_state = "0"
    state = vcat(f_state, [b_state])
    psi0 = MPS(sites,state)

    @show inner(psi0', H, psi0)
end

main()  # yields inner(psi0', H, psi0) = 0.0 + 0.0im

The problem is that I want the number of fermions to be conserved, but not the number of bosons (as can be seen in the Hamiltonian). Setting conserve_qns = true yields “ERROR: LoadError: Fluxes not all equal”, since the quantum number (QN) structure of the fermion and boson site are not meant for this.

I tried defining a custom site “CavityMode“ that is in all accounts the same as the “Boson” site except for its QN structure:

using ITensors
using ITensorMPS

using LinearAlgebra

alias(::SiteType"CavityMode") = SiteType"Boson"()

"""
    space(::SiteType"CavityMode";
          dim = 2,
          conserve_qns = false,
          conserve_number = false,
          qnname_number = "N_f")

Create the Hilbert space for a site of type "CavityMode".

Optionally specify the conserved symmetries and their quantum number labels.
"""
function ITensors.space(
        ::SiteType"CavityMode";
        dim = 2,
        conserve_qns = false,
        conserve_number = conserve_qns,
        qnname_number = "N_f",
    )
    if conserve_number
        return [QN(qnname_number, 0) => dim] # one large QN block with QN=0
    end
    return dim
end


ITensors.val(vn::ValName, st::SiteType"CavityMode") = val(vn, alias(st))

function ITensors.state(sn::StateName, st::SiteType"CavityMode", s::Index; kwargs...)
    return state(sn, alias(st), s; kwargs...)
end

function ITensors.op(on::OpName, st::SiteType"CavityMode", ds::Int...; kwargs...)
    return op(on, alias(st), ds...; kwargs...)
end


function ITensors.op(on::OpName, st::SiteType"CavityMode", s1::Index, s_tail::Index...; kwargs...)
    rs = reverse((s1, s_tail...))
    ds = dim.(rs)
    opmat = op(on, st, ds...; kwargs...)
    return itensor(opmat, prime.(rs)..., dag.(rs)...)
end

Now, however, I can’t figure out how to implement the Peierls phase. Everything I try leads to errors (defining it again as a matrix fails when conserve_qns = true, trying to implement it as an operator with an extra argument g does not work with OpSum, defining it as an operator where the argument g is inferred from its name does not work when I try to implement it and manually defining it will probably at the latest run into problems when going to periodic boundary conditions).

I found another topic that deals with this exact problem ( Customized Operator Error: "Fluxes not all equal" - #2 by VinceNeede ), but sadly, the final working code is not provided, and I can’t figure it out myself so I am happy for every support that you can give me!

Okay, I found a solution that works using global parameters. I am sure there is a better, more elegant solution to this problem. I ran DMRG for different system sizes and boundary conditions, comparing different ground state observables (energy, photon number, and entanglement entropy between fermions and the photon mode) at half filling with exact diagonalization (ED) calculations. The results are in good agreement, so I am quite confident that my solution is correct. Here is all the corresponding code, including an example of usage:

using ITensors
using ITensorMPS
using LinearAlgebra


"""
    photon.jl

Custom site type and operators for photonic degrees of freedom in fermion-cavity systems.

# Overview
This file defines a custom "Photon" site type for ITensor that represents quantized light modes
coupled to a system of particles (e.g. spinless fermions) on a lattice. The photon site is
essential for handling systems where:

- A lattice of L sites contains particles (e.g. fermions) with particle number conservation
- The system couples to a single cavity mode (photon field)
- Total particle number is conserved, but photon number is not
- The interaction is described by quantized Peierls phase factors

# Design Rationale

## Custom Site Type
The "Photon" site type uses a trivial quantum number structure (no QN conservation by default)
despite being coupled to a fermionic system that does conserve particle number. This is necessary
because ITensors' `OpSum` requires homogeneous quantum number structures across all sites: either
all sites conserve QNs or none do (at least in the general use case). Since we cannot mix
QN-conserving fermion sites with non-QN photon sites, the photon site must have a dummy QN structure.

## Global Parameters and Custom Operators
Custom operators like "PeierlsPhase" are defined with global parameter passing via `CAVITY_PARAMS`.
This workaround was necessary because ITensors does not provide a native mechanism for passing
arguments to custom operators when building `OpSum` expressions (at least I, a complete beginner
in Julia and ITensors did not find a better way). The coupling strength `g` is
stored globally and retrieved when operators are instantiated.

# Key Components

- `CAVITY_PARAMS`: Reference container holding coupling parameters (e.g., photon-fermion coupling)
- `set_cavity_params!()`: A setter function to update global cavity parameters
- `space()`: Hilbert space specification for photon sites
- `PeierlsPhase` and `PeierlsPhaseDag`: Operators implementing photon-assisted tunneling

# References

- Code on which this is based: https://github.com/ITensor/ITensors.jl/blob/main/src/lib/SiteTypes/src/sitetypes/boson.jl
- ITensors discourse forum: https://itensor.discourse.group/t/fermionic-hopping-dressed-with-quantized-peierls-phase-and-conservation-of-fermions/2574
"""

# Global parameter container
const CAVITY_PARAMS = Ref((g = 0.0,))

# Setter for cavity parameters
@inline set_cavity_params!(; g) = (CAVITY_PARAMS[] = (g = g,))

alias(::SiteType"Photon") = SiteType"Boson"()

# Key difference between this photon site and a standard boson site
"""
    space(::SiteType"Photon";
          dim = 2,
          conserve_qns = false,
          conserve_number = false,
          qnname_number = "dummy")

Create the Hilbert space for a site of type "Photon".

Optionally specify the conserved symmetries and their quantum number labels.
"""
function ITensors.space(
        ::SiteType"Photon";
        dim = 2,
        conserve_qns = false,
        conserve_number = conserve_qns,
        qnname_number = "dummy"
    )
    if conserve_number
        return [QN(qnname_number, 0) => dim]
    end
    return dim
end

############################################################################################
##### Everything as in boson.jl except for "Boson" -> "Photon" and prefix "ITensors." ######
############################################################################################

ITensors.val(vn::ValName, st::SiteType"Photon") = val(vn, alias(st))

# Everything as in boson.jl except for "Boson" -> "Photon" and prefix "ITensors."
function ITensors.state(sn::StateName, st::SiteType"Photon", s::Index; kwargs...)
    return state(sn, alias(st), s; kwargs...)
end

# Everything as in boson.jl except for "Boson" -> "Photon" and prefix "ITensors."
function ITensors.op(on::OpName, st::SiteType"Photon", ds::Int...; kwargs...)
    return op(on, alias(st), ds...; kwargs...)
end

# Everything as in boson.jl except for "Boson" -> "Photon" and prefix "ITensors."
function ITensors.op(on::OpName, st::SiteType"Photon", s1::Index, s_tail::Index...; kwargs...)
    rs = reverse((s1, s_tail...))
    ds = dim.(rs)
    opmat = op(on, st, ds...; kwargs...)
    return itensor(opmat, prime.(rs)..., dag.(rs)...)
end

############################################################################################
######## Definition of quantized Peierls phase matrix and its operators for OpSum ##########
############################################################################################

"""
    build_peierls_phase(g::Real, dim_ph::Int) -> Matrix{ComplexF64}

Construct the Peierls phase matrix ``\\exp(ig(a + a^\\dagger))`` for a photon
site with given dimension `dim_ph`

# Arguments
- `g::Real`: Coupling strength.
- `dim_ph::Int`: Dimension of the photon site.

# Returns
- `Matrix{ComplexF64}`: The Peierls phase matrix of size `dim_ph x dim_ph`.

# Throws
- `ArgumentError`: If `dim_ph` is less than 1.
"""
function build_peierls_phase(g::Real, dim_ph::Int)::Matrix{ComplexF64}
    dim_ph >= 1 || error("Photon site dimension must be at least 1")

    # zeros on diagonal
    d = zeros(Float64, dim_ph)

    # off-diagonal entries: sqrt(1), …, sqrt(dim_ph-1)
    e = sqrt.(collect(1:dim_ph-1)) # i.e. from 1 to N_ph-1

    # diagonalize a + a^\dagger =: A which is tridiagonal in the number basis
    A = SymTridiagonal(d, e)
    eigenvals, eigenvecs = eigen(A)

    # write A = V D V^\dagger with D diagonal matrix of eigenvals and V matrix of eigenvecs
    phases = exp.(1im * g .* eigenvals) # .* element-wise multiplication
    # U = V * diag(phases) * V^†
    U = eigenvecs * Diagonal(phases) * eigenvecs'

    return ComplexF64.(U) # ensure complex type
end

"""
    ITensors.op(::OpName"PeierlsPhase", ::SiteType"Photon", d::Int) -> Matrix{ComplexF64}

Construct the Peierls phase operator for a photon site of dimension `d` using the global
coupling parameter `g` from `CAVITY_PARAMS`.

# Arguments
- `d::Int`: Dimension of the photon site.

# Returns
- `Matrix{ComplexF64}`: The Peierls phase operator matrix of size `d x d`.
"""
function ITensors.op(::OpName"PeierlsPhase", ::SiteType"Photon", d::Int)
    p = CAVITY_PARAMS[]
    g = p.g # assumes that g is already scaled by sqrt(L)
    mat = build_peierls_phase(g, d)
    return mat
end

"""
    ITensors.op(::OpName"PeierlsPhaseDag", ::SiteType"Photon", d::Int) -> Matrix{ComplexF64}

Construct the Hermitian adjoint of the Peierls phase operator for a photon site of
dimension `d` using the global coupling parameter `g` from `CAVITY_PARAMS`.

# Arguments
- `d::Int`: Dimension of the photon site.

# Returns
- `Matrix{ComplexF64}`: The Hermitian adjoint of the Peierls phase operator matrix of size `d x d`.
"""
function ITensors.op(::OpName"PeierlsPhaseDag", ::SiteType"Photon", d::Int)
    p = CAVITY_PARAMS[]
    g = p.g # assumes that g is already scaled by sqrt(L)
    mat = build_peierls_phase(g, d)
    return adjoint(mat)
end


let
    L = 10
    N_ph = 5
    g = 3.0 / sqrt(L)
    conserve_qns = true

    # construct sites with convention: first site is photon, remaining sites are fermions
    f_sites = siteinds("Fermion", L; conserve_qns=conserve_qns)
    ph_site = siteinds("Photon", 1; dim=N_ph+1, conserve_qns=conserve_qns) # +1 because we count from 0 to N_ph
    sites = vcat(ph_site, f_sites)

    # ensure that the global cavity parameters are set before constructing the MPO
    set_cavity_params!(g=g)
    ph_ind = 1
    os = OpSum()
    for j in (ph_ind+1):length(sites)-1
        os += -1.0, "PeierlsPhase", ph_ind, "c†", j, "c", j+1
        os += -1.0, "PeierlsPhaseDag", ph_ind, "c†", j+1, "c", j
    end

    H_hop = MPO(os, sites)
end

nothing

If you have something to add or improve, feel free to reply to this post :slight_smile:

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.