# Manually set MPS tensors with QN conservation features enabled

**URL:** <https://itensor.discourse.group/t/manually-set-mps-tensors-with-qn-conservation-features-enabled/2504>\
**Category:** ITensor Julia Questions\
**Tags:** mps, qn\
**Created:** [October 4, 2025, 10:28am UTC](https://itensor.discourse.group/t/manually-set-mps-tensors-with-qn-conservation-features-enabled/2504 "2025-10-04T10:28:09Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![phx](https://yyz2.discourse-cdn.com/free1/user_avatar/itensor.discourse.group/phx/32/673_2.png) [@phx](https://itensor.discourse.group/u/phx)\
**Post date:** [October 4, 2025, 10:28am UTC](https://itensor.discourse.group/t/manually-set-mps-tensors-with-qn-conservation-features-enabled/2504/1 "2025-10-04T10:28:09Z")

</div>

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.

```julia
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)?

---

<div class="post-metadata">

**Author:** ![miles](https://yyz2.discourse-cdn.com/free1/user_avatar/itensor.discourse.group/miles/32/6_2.png) [@miles](https://itensor.discourse.group/u/miles)\
**Post date:** [November 10, 2025, 3:51pm UTC](https://itensor.discourse.group/t/manually-set-mps-tensors-with-qn-conservation-features-enabled/2504/2 "2025-11-10T15:51:31Z")

</div>

Thanks for your patience on the slow reply –

The relevant documentation is here: [QN ITensor Constructor](https://docs.itensor.org/ITensors/stable/ITensorType.html#ITensors.ITensor-Tuple%7BNDTensors.AliasStyle,%20Type%7B%3C:Number%7D,%20Array%7B%3C:Number%7D,%20Union%7BTuple%7BVararg%7BIndex%7BVector%7BPair%7BQN,%20Int64%7D%7D%7D%7D%7D,%20Vector%7BIndex%7BVector%7BPair%7BQN,%20Int64%7D%7D%7D%7D%7D%7D)  
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).
