About operations on a state

Hi, I am trying to simulate quantum circuits in my school project. I’ve had difficulties with understanding the documentation as I still have no certainty on which is the best approach in a case that we have N qubits, and I wish to operate on each qubit with various operators.

Next I will present a couple of different methods inspired by the documentation code examples. The test case will be a simple N=2 circuit. For the sake of simplicity, let us use only rotation operators. The example circuit will consist of a total of four operators, two operators for each qubit. First operation will rotate the state to some other state, but the following operator should rotate it back to the initial state for debugging purposes.

The first method is using the contraction operators:

  using ITensors, ITensorMPS
  
  let
      layer1 = ITensor[]
      s1 = siteinds("Qubit", 2)
      states1 = ["Up" for n=1:2]
      
      psi1 = MPS(s1, states1)
      psi0 = MPS(s1, states1)
      
      layer1 = op("Rx", s1[1], θ=π/5)*op("Ry", s1[2], θ=π/5)+op("Rx", s1[1], θ=-π/5)*op("Ry", s1[2], θ=-π/5)
      
      psi1 = apply(layer1, psi1)
      
      normalize!(psi1)
      orthogonalize!(psi1,1)
      
      @show abs(inner(psi1, psi0))^2
  end

The result will be \approx 0.988977

As the probability was not 1 even for this rather simple test case, I decided to test another approach. However, this does not actually form a MPO.

The second method simply appends the operators:

using ITensors, ITensorMPS

let
    layer2 = ITensor[]
    s2 = siteinds("Qubit", 2)
    states2 = ["Up" for n=1:2]
    
    psi2 = MPS(s2, states2)
    psi0 = MPS(s2, states2)
    
    push!(layer2, op("Rx", s2[1], θ=π/5))
    push!(layer2, op("Ry", s2[2], θ=π/5))
    push!(layer2, op("Rx", s2[1], θ=-π/5))
    push!(layer2, op("Ry", s2[2], θ=-π/5))
    
    psi2 = apply(layer2, psi2)
    
    
    normalize!(psi2)
    orthogonalize!(psi2,1)
    
    @show abs(inner(psi2, psi0))^2
end

Now the probability is exactly correct, i.e. 1.0.

I tried a third method using OpSum, but could not figure out how to sum operators that require parameters in their definitions. This approach would create a MPO.

Questions:

  1. Are the two first methods I provided equivalent? There is a difference in the inner product, but is it only due to the fact that the first example uses a MPO, which leads to some numerical errors?

  2. Is there a way to use the second method, but still create a MPO from the list of operators? Unfortunately, I could not find such a MPO constructor from the documentation.

  3. Is it possible to use the third method, i.e. the OpSum feature for these purposes? If so, what is the syntax of adding operators with parameters to OpSum?

  4. In case that I would like to play with circuits of N=100 or more, and use multiple different gates, possibly even two qubit gates, what approach would you suggest me to use? The second option, however, probably does not allow to vary the bond dimension, as the operator is not a MPO. Naturally the efficiency and speed is highly important factor in these larger tests, which means (to my understanding) that a MPO should be formed for the circuit.

Thanks in advance!

Glad you’re trying this out and experimenting with different ways of writing your code.

I think the main answer I can give you that might cover a few of your questions (especially question 1) is that your first method is incorrect. (And using OpSum the way you are suggesting would be incorrect for the same reason.) The reason is that a layer of gates does not mathematically mean that the gates are added. So in code #1 when you add the operators, that is not mathematically the correct operation in terms of what a layer of gates corresponds to, and in particular when adding two unitary matrices or operators, the resulting matrix or operator will not in general be unitary. (In other words, unitary matrices are not closed under addition.)

The second code or method looks correct to me, however, and there you have understood correctly what a layer of gates means both mathematically and in terms of how to pass such a layer to the apply function which accepts an array of ITensors and then has the behavior of applying them one after another to the state.

Regarding question #4, you should be able to scale your second code to N=100 qubits just fine, if you simply increase the qubit number and make the appropriate gates that you want. A very important thing, though, will be that you will also need to pass some truncation parameters to the apply function for that code to run well beyond one or two layers. The parameters are maxdim and cutoff and we discuss them in the documentation but let me know if you have questions about them. A reasonable choice would be something like maxdim=500, cutoff=1E-8.

Finally, about making the gates into an MPO, I don’t know in general that that will work well or be a good idea. The resulting MPO may have a very large “bond dimension” so it may be quite expensive to do, or on the other hand if you truncate it a lot to keep the bond dimension low, the MPO may no longer represent the gates very well. What’s the reason you are wanting to make the gates into an MPO?

Hi! Thanks for the great answer.

My impression is that building an MPO would be more computationally effective, in comparison to this list of appended operators. Correct me if I have misunderstood, but a list of operators operating each gate one by one on the state is slow, whereas the MPO should execute a more straightforward and thus fast operation, as these gates have been already multiplied together. This fact accompanied with the possibility to vary the bond dimension made me interested in testing an approach that uses an MPO. Is it possible to make the gates into an MPO? Especially in the case considered in my first post, the MPO should reduce to an identity operator, right?

After implementing a working MPO, I could test its effectiveness in the calculations and also try what are the limits of truncation that can be allowed. However, I would also like to know if it is equally fast to use list of operators and control the truncation only in the apply command.

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

Hi, thanks for your patience regarding the slow reply.

I think the answer to your question of which is faster, applying gates to a state (MPS) or combining them into an MPO is that “it depends”. And you already gave an example in your question above. The most obvious example would be when combining the gates results in them cancelling out back into an identity. In that case, then combining them first into an MPO (which would be the identity MPO) would then of course lead to the most efficient method in applying them to an MPS afterward, because the bond dimension of the MPS would not grow at all.

On the other hand, there are very likely (and more commonly) sets of gates where if you combine them into an MPO the bond dimension will get very large, whereas if you had acted them on a state the entanglement growth could be lower, depending on what the initial state was and details of those gates.

So it is a hard question to know the answer to in general, and the only way to answer it is case-by-case, for specific examples or families of gates or operators and initial states.