Manually set MPS tensors with QN conservation features enabled

,

Hi all,
I need some help on the following problem. I have an MPS with the Fermion site type, representing the state \psi of a many-body fermionic system, and I’d like to use it to create an Electron-type MPS where both spin degrees of freedom are described by \psi.
The procedure is analogous to creating the projector \ket{\psi}\bra{\psi}, and in fact I can do it by hand with the following function, in which I build the MPS matrices manually.

function proj_electron(v::MPS; kwargs...)
    T = ITensors.scalartype(v)
    N = length(v)
    blocks = Vector{Array{T}}(undef, N)
    d = dim(siteind(v, 1))^2  # == 4

    t = Array{T}(undef, d, linkdim(v, 1)^2);
    a1 = array(v[1], siteind(v, 1), linkind(v, 1))
    t[1, :] .= kron(a1[1, :], conj(a1[1, :]))
    t[2, :] .= kron(a1[2, :], conj(a1[1, :]))
    t[3, :] .= kron(a1[1, :], conj(a1[2, :]))
    t[4, :] .= kron(a1[2, :], conj(a1[2, :]))
    blocks[1] = t

    for i in 2:(length(v) - 1)
        t = Array{T}(undef, d, linkdim(v, i-1)^2, linkdim(v, i)^2)
        ai = array(v[i], siteind(v, i), linkind(v, i-1), linkind(v, i))
        t[1, :, :] .= kron(ai[1, :, :], conj(ai[1, :, :]))
        t[2, :, :] .= kron(ai[2, :, :], conj(ai[1, :, :]))
        t[3, :, :] .= kron(ai[1, :, :], conj(ai[2, :, :]))
        t[4, :, :] .= kron(ai[2, :, :], conj(ai[2, :, :]))
        blocks[i] = t
    end

    t = Array{T}(undef, d, linkdim(v, N-1)^2);
    aN = array(v[N], siteind(v, N), linkind(v, N-1))
    t[1, :] .= kron(aN[1, :], conj(aN[1, :]))
    t[2, :] .= kron(aN[2, :], conj(aN[1, :]))
    t[3, :] .= kron(aN[1, :], conj(aN[2, :]))
    t[4, :] .= kron(aN[2, :], conj(aN[2, :]))
    blocks[N] = t

    linds_dim = getindex.(size.(blocks), 2)[2:end]

    sites = siteinds("Electron", N; kwargs...)
    v = Vector{ITensor}(undef, N)

    l = [Index(linds_dim[i], "Link,l=$i") for i in 1:(N - 1)]
    v[1] = ITensor(blocks[1], sites[1], l[1])
    for i in 2:(N - 1)
        v[i] = ITensor(blocks[i], sites[i], dag(l[i - 1]), l[i])
    end
    v[N] = ITensor(blocks[N], sites[N], dag(l[N - 1]))
    return MPS(v)
end

This works, and yields the expected results.

The problem lies in the fact that I would like to enable QN conservation in the resulting Electron MPS (it will be used as the starting point of TDVP with a Hamiltonian that conserves the total number of electrons), but I’m completely lost on how to do this. Basically, my way of setting the individual tensors as in the function above doesn’t work as the arrays are now sparse, and I couldn’t find a way to set values of sparse tensors in the ITensor documentation…
Can someone point me in the right direction (or to an useful page of the documentation that I might have missed)?

Thanks for your patience on the slow reply –

The relevant documentation is here: QN ITensor Constructor
The description is: "Create a block sparse ITensor from the input Array, and collection of QN indices. Zeros are dropped and nonzero blocks are determined from the zero values of the array.

Optionally, you can set a tolerance such that elements less than or equal to the tolerance are dropped.

By default, this will check that the flux of the nonzero blocks are consistent with each other. You can disable this check by setting checkflux=false."

The example just below the description also should show you what you need.

Basically, the data that you pass into a QN-conserving ITensor needs to be non-zero only in the places that have a compatible “QN flux” (the arrow-weighted sum of QN values corresponding to the setting of each index corresponding to each non-zero entry). If your array data for your non-QN or dense case already corresponds to, say, well-defined particle and spin sectors, then you should be able to change your indices to have QN block structure and then your code should continue to work the same. You can put calls to flux(T) for an ITensor T to check what QN flux it has (flux returns nothing for a non-QN-conserving or dense ITensor).