Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
5.11k stars 2.35k forks source link

Making a circuit from a transpiled circuit data returning the wrong unitary #12163

Closed ACE07-Sev closed 5 months ago

ACE07-Sev commented 5 months ago

Environment

What is happening?

So, I am trying to make a function where the user would pass a unitary matrix, and the function would return the circuit that implements the unitary matrix. This is for a package I am creating, so I have to do so as follows: 1) Define a circuit, and extract its unitary (this is the circuit we are trying to implement). 2) Define a new circuit, which transpiles the unitary into a circuit. 3) Define another circuit, which applies the gates from (2) using .data attribute from circuit (2).

So, the issue is that apparently the resulting circuit from (3), whilst being the same circuit as (2), returns a vastly different unitary matrix, ergo not approximating the result correctly. I imagine this is being caused from the global phase difference, though even as I am writing this, it feels wrong to say given phase does not impact the unitary matrix (circuit definition) itself.

I would immensely appreciate getting this code to run as expected as it is a crucial feature for my package for the time being until I implement a unitary synthesis algorithm from scratch myself.

How can we reproduce the issue?

from qiskit import QuantumCircuit, transpile
from qiskit.circuit.library import XGate, U3Gate
from qiskit.quantum_info import Operator

qc = QuantumCircuit(4, 4)
mcx = XGate().control(2)

qc.append(mcx, [0, 1, 2])
qc.append(mcx, [0, 1, 3])

unitary_matrix = Operator(qc).data

transpiled_qc = QuantumCircuit(4, 4)
transpiled_qc.unitary(unitary_matrix, [0, 1, 2, 3])
transpiled_qc = transpile(transpiled_qc, basis_gates=['cx', 'u3'], optimization_level=3, seed_transpiler=0)

result_qc = QuantumCircuit(4, 4)

for gate in transpiled_qc.data:
    if gate[0].name == 'u3':
        u3 = U3Gate(theta=gate[0].params[0], phi=gate[0].params[1], lam=gate[0].params[2])
        result_qc.append(u3, [gate[1][0]._index])

    # Add the CX gate
    else:
        result_qc.cx(gate[1][0]._index, gate[1][1]._index)

print(Operator(qc).data) # The original matrix
print(Operator(transpiled_qc).data) # The correctly approximated matrix
print(Operator(result_qc).data) # The wrong circuit

What should happen?

I expect the result_qc to return the exact same matrix as transpiled_qc, given both are the same circuits. I also believe even if the global phase difference is not the source of the problem, the resulting circuit should not have a different global phase.

Any suggestions?

UPDATE : I manually set the transpiled_qc which returns the correct result to have a global phase of 0, and was able to reproduce the issue seen in result_qc, hence I think if there's a way to do the initial transpilation for transpiled_qc with a global phase of 0, it should be fixed.

jakelishman commented 5 months ago

I'm not quite sure what you're saying the problem here is in the "update". If you set the global phase to an incorrect value, the matrices will of course appear different.

In the main body: your result_qc is not the same as your transpiled_qc, because you're only copying across the gates and not the global phase of the circuit. You should set result_qc.global_phase = transpiled_qc.global_phase, but also, I'm very unclear what you're trying to do with result_qc, since transpiled_qc already appears to be the object you wanted.

You can make a faithful copy of a QuantumCircuit using QuantumCircuit.copy(), or if you must use QuantumCircuit.data for some reason:

ACE07-Sev commented 5 months ago

So, I have made a wrapper for many qc frameworks, and for my implementation to work, as I mentioned above I need to do it like so (I make the result_qc differently, this was just to show the issue, I make calls to my own .U3 and .CX methods).

So, is there a way to apply a global phase gate on the transpiled circuit to get my correct unitary matrix whilst having a global phase of 0? Or to perform the transpilation with a global phase of 0?

ACE07-Sev commented 5 months ago

I can share my full code for you, but I doubt it'll be relevant as this is as I can understand just a global phase thing, not a code implementation issue thing. My package is basically a dedicated wrapper which allows for using all these different packages (currently qiskit, cirq, pennylane, and pytket) to be usable with a single syntax, and the user can convert back and forth between these frameworks.

jakelishman commented 5 months ago

There's a GlobalPhaseGate in qiskit.circuit.library that you can append to a circuit (it takes 0 qubits and 0 clbits). I'd strongly suggest making sure you're always handling the global_phase field of QuantumCircuit properly, though - it's a core part of the QuantumCircuit class. tket also has a Circuit.phase property.

GlobalPhaseGate isn't a special gate or anything, it's just a gate whose "definition" is the empty circuit with QuantumCircuit.global_phase set to the right value. Any Qiskit optimisation or synthesis pass will remove it and replace it with a modification of the QuantumCircuit.global_phase, since GlobalPhaseGate is not in the instruction sets of hardware, so the most efficient "implementation" of it is just to take a note of what it should be, then do nothing.

ACE07-Sev commented 5 months ago

Technically, I could do everything with a global phase of 0 right? Meaning, could I just add a GlobalPhaseGate to get the correct qc that implements the unitary with a global phase of 0.

By the way, if it's actually nothing, then why does transpile change it, and why does it impact the overall unitary?

ACE07-Sev commented 5 months ago

So for clarity, I would like to transpile the unitary to a circuit which would have a global phase of 0, and when I extract the unitary, it would be basically the same.

jakelishman commented 5 months ago

Yes, you could do result_qc.append(GlobalPhaseGate(transpiled_qc.global_phase), (), ()) and it'd be the same as setting result_qc.global_phase = transpiled_qc.global_phase.

If you want the matrix forms to match exactly, you have to ensure that you include the global-phase factor in them as well. A global phase is not observable under physical measurements (${\bigl\lvert \langle\psi\rvert U\lvert\phi\rangle \bigr\rvert}^2$ is invariant under the map $U \to e^{i\theta}U$ for real scalar $\theta$) but if you want to examine the raw matrix elements, you can see a difference. The transpiler tracks any global-phase difference that its synthesis induces in the output specifically so that you can account for it if you want to retrieve the matrices afterwards.

ACE07-Sev commented 5 months ago

You mean inverse of GlobalPhaseGate right? I want to set the global phase to 0, and ensure the unitary is still correct. So, I want the result_qc.global_phase to be 0, and when I call Operator(result_qc).data to get the initial unitary as expected.

This will allow me to ignore global phase as all the other gates (X, Y, Z, RX, RY, RZ, H, S, T, SWAP, and their controlled and multi-controlled variations) don't change the global phase, so it's only being changed here because of transpile.

jakelishman commented 5 months ago

No, you'd need the explicit gate to advance result_qc.global_phase by the same amount as transpiled_qc.global_phase if you want the matrices to match (whether explicitly by modifying the property, or implicitly by a GlobalPhaseGate), and both GlobalPhaseGate and QuantumCircuit.global_phase represent an additive phase, so there's no inverse.

I'm not sure what you mean by "other gates don't change the global phase". Global phase is only a relative thing. Qiskit's matrix definition of $R_z(\theta)$ differs from $P(\theta)$ by a phase term $e^{i\theta/2}$, so, for example, if transpile is given a circuit containing $P$ gates and the backend only claims to support $R_z$ gates, then we can make a one-to-one translation from $P$ to $R_z$, but we then track the induced difference in global phase in QuantumCircuit.global_phase. The same thing is happening in your circuit during the unitary synthesis; we happened to synthesise your matrix in an efficient way using the ${U_3,CX}$ basis you asked for, including a global-phase term. It would be wasteful synthesis for us to insert additional gates to make the global phase exactly equal when it's unobservable (and for certain gate sets, would potentially be impossible), so we don't, and we just store the factor out-of-band. Things like Operator will (as you've found) read that off correctly when building a matrix representation, and any handler of QuantumCircuit should do the same, if it cares about it.

ACE07-Sev commented 5 months ago

I see, so I should make global phase in all of the frameworks, i.e., TKET, Pennylane, and cirq then?

jakelishman commented 5 months ago

They will also all have some representation of global phase, if they have can preserve the exact matrix definitions of unitaries. If there's any framework you're targetting that only defines circuits up-to-phase (which is typically allowed for circuits representing only complete physical programs), then the global phase is irrelevant and unobservable.

ACE07-Sev commented 5 months ago

I see. At the risk of sounding stupid, could I implement the global phase difference using RZ gate or P gate applied on all qubits? The reason I ask is because I am not sure what GlobalPhaseGate actually is, hence I can't find the equivalents of the gate in the other frameworks, i.e., TKET, Pennylane and cirq.

jakelishman commented 5 months ago

In Cirq it's called GlobalPhaseGate like us, and in tket it's a method on Circuit called add_phase which (I think - I'm not sure) is equivalent in spirit to qc.global_phase += phase in Qiskit, i.e. there's no associated Op. PennyLane appears to call it GlobalPhase.

Applying $P$ or $R_z$ on all qubits isn't the same as a global phase; those gates advance the $\lvert1\rangle$ state relative to the $\lvert0\rangle$ state for the targeted qubit.

jakelishman commented 5 months ago

Oh, but if you want, you can apply a global phase by doing a $P(\theta)$ followed by an $R_z(-\theta)$ on the same qubit. That unnecessarily targets a qubit (global phase is equivalent to the identity operation on all qubits up to phase(!), so doesn't need an explicit target). Since $P(\theta) = e^{i\theta/2}R_z(\theta)$, $R_z(-\theta)P(\theta)$ advances the global phase by $\theta/2$ but is locally equivalent to the identity (i.e. it's equal to $e^{i\theta/2}I$).

I'd recommend against doing that though, because any simulation or unitary synthesis would have to either do two unnecessary matrix multiplications to apply a phase like that (rather than a simple multiplication by a scalar) or otherwise re-simplify the expression back to the identity up-to-phase.

ACE07-Sev commented 5 months ago

Thank you so so much! I'll test them out now, and see if the issue persists.

ACE07-Sev commented 5 months ago

Quick question, in Cirq it only allows for a value between 0 to 1. How would the phase value from for instance qiskit translate to cirq?

jakelishman commented 5 months ago

I'm not familiar with Cirq, but the global phase is generally understood as an angle (including in Qiskit) with a period of $2\pi$. The other thing Cirq might be doing is storing the value as the complex number $e^{i\theta}$ (which is what you actually multiply the matrices by), whereas Qiskit stores the angle $\theta$ directly (which is faster and more precise to update and for humans to interpret).

ACE07-Sev commented 5 months ago

I see. Quick question, in operations like adjoint of a circuit (where we reverse the order of the gates, and flip the sign of the angles), would global phase also be flipped? I apologize if this is a stupid question, still trying to fully understand the details.

jakelishman commented 5 months ago

Yes, phase would be inverted too.

Since there's no problem with Qiskit here, I'm going to close this issue now. It might be better to ask the more mathematical on the Qiskit slack, where there are more people are to help.