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
4.9k stars 2.3k forks source link

Problems with global phase #7167

Open burgholzer opened 2 years ago

burgholzer commented 2 years ago

Information

What is the current behavior?

If the Optimize1qGatesDecomposition optimization pass is applied to the sequence

qc = QuantumCircuit(1)
qc.x(0)
qc.p(np.pi/2, 0)

a global phase is introduced in the circuit. The old Optimize1qGates optimization pass does not introduce such a global phase. Is this intended? If not, then the following problem might rather be a separate issue.

Upon dumping a QASM representation of the circuit via qc.qasm() the information about the global phase is lost. This means once the resulting file is used to create a circuit via QuantumCircuit.from_qasm_str(qasm_str), both circuits are no longer functionally equivalent, but differ by the global phase.

Steps to reproduce the problem

from qiskit import QuantumCircuit, Aer, execute
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.optimization import Optimize1qGatesDecomposition, Optimize1qGates

backend = Aer.get_backend('aer_simulator')

qc = QuantumCircuit(1)
qc.x(0)
qc.p(np.pi/2, 0)

qc_opt = PassManager(Optimize1qGatesDecomposition(basis=['u1', 'u2', 'u3', 'cx'])).run(qc)
qasm_str = qc_opt.qasm()
print('Optimize1qGatesDecomposition global phase: ', qc_opt.global_phase)

qc_opt2 = PassManager(Optimize1qGates(basis=['u1', 'u2', 'u3', 'cx'])).run(qc)
qasm_str2 = qc_opt2.qasm()
print('Optimize1qGates global phase: ', qc_opt2.global_phase)

qc.save_unitary()
unitary = execute(qc, backend).result().get_unitary(qc)

qc_opt.save_unitary()
unitary_opt = execute(qc_opt, backend).result().get_unitary(qc_opt)

qc_qasm = QuantumCircuit.from_qasm_str(qasm_str)
qc_qasm.save_unitary()
unitary_qasm = execute(qc_qasm, backend).result().get_unitary(qc_qasm)

qc_qasm2 = QuantumCircuit.from_qasm_str(qasm_str2)
qc_qasm2.save_unitary()
unitary_qasm2 = execute(qc_qasm2, backend).result().get_unitary(qc_qasm2)

print(unitary == unitary_opt)
print(unitary == unitary_qasm)
print(unitary == unitary_qasm2)

yields

Optimize1qGatesDecomposition global phase:  5.497787143782138
Optimize1qGates global phase:  0
[[ True  True]
 [ True  True]]
[[False False]
 [False False]]
[[ True  True]
 [ True  True]]

What is the expected behavior?

For once, I would have not expected the optimization pass to introduce a global phase. In addition, dumping the circuit to QASM should not alter the circuit's functionality.

Suggested solutions

Potentially fix the global phase issue in the Optimize1qGatesDecomposition optimization pass. Factor in global phase in QASM dump.

jakelishman commented 2 years ago

The reason Optimize1qGatesDecomposition "adds" a global phase in this case is actually to ensure that the circuit is not changed by the optimisation pass - if we didn't have it, it wouldn't be the same circuit. The optimise pass turns the X - P(pi/2) circuit into a single U3 gate, but because of the definition of the U3 gate, you need a global phase to make sure the matrix representations are exactly equal.

OpenQASM 2 doesn't support global phase declarations at all, so really that's just a hard limitation imposed by the language. Serialisation to/from OpenQASM 2 is not designed to be lossless, and because Terra can represent more than OpenQASM 2, simply can't be done losslessly in all cases. The upcoming OpenQASM 3, which Terra is gaining some support for in the next release, can represent global phases, so we'll be able to add that there.

mtreinish commented 2 years ago

I will add on to @jakelishman's response here that if you need a lossless serialization format for your circuit for just usage with Qiskit, qpy is a good option: https://qiskit.org/documentation/apidoc/qpy.html

burgholzer commented 2 years ago

@jakelishman I see. Without the global phase you could not express the combination of the two gates as a single U3 gate. Should have looked at the circuit produced by the old optimization pass (which still consists of two gates). Sorry for that.

I am not so sure I agree with you on that second part. At least if I am not mistaken completely, given a global phase theta the following (valid OpenQASM 2) gate declaration can be used to apply that global phase to any state

gate g(theta) q {
  x q;
  p(theta) q;
  x q;
  p(theta) q; 
}

@mtreinish thanks for pointing that out. Unfortunately we are rather thinking of generating and transpiling circuits using Qiskit and then using those circuits in other tools that only accept OpenQASM as input.

jakelishman commented 2 years ago

That's fair, we could add a global phase by the application of another gate. However, the point of various optimisation routines in the transpilation step is to optimise the output so that the actual circuits run by something consuming the OpenQASM code is as efficient as possible - including unnecessary gates in them runs counter to that. When we're running on physical hardware and the phase is truly global, it surely has no meaning, right?

The two unitaries being unequal here at the end is more because the comparison isn't quite fair: at the point we export to an OpenQASM 2 file, we're saying "this is a complete circuit", so the global phase is unobservable. The observable action of the two circuits before and after import is still equivalent, which is the most we guarantee when you export from Terra into OpenQASM 2.

burgholzer commented 2 years ago

I would just use this "workaround" for exporting circuits from Qiskit to potentially use them in other tools. Of course you are right, that a truly global phase is completely unobservable when running on the actual machine. Maybe something like an additional parameter include_global_phase that defaults to False would do the trick.

Our use case is the following: We want to verify the results of the compilation/transpilation flow in Qiskit. For that we take an initial circuit description and use various optimization levels to create transpiled versions of the circuit. In order to benchmark various equivalence checking tools we need some common ground for the benchmarks. As it turns out, the only possible way is to dump the respective circuits to OpenQASM, as this is supported almost in every tool. However, in doing so, global phase information is lost. Some tools for equivalence checking have issues with this global phase mismatch.

I see your point, that you regard the exported circuit as "complete". Yet it somehow feels wrong, because the information is available. I guess the following is a rather contrived example, but imagine you have a circuit for some building block like modular exponentiation in Shor's algorithm. You transpile that building block in order to optimise it and a global phase is introduced. You dump the respective circuits to .qasm. For Shor's algorithm you want to create a controlled version of the respective functionality. As soon as you add a control to the version of the building block with the global phase, that phase becomes relative. So the controlled versions of these building blocks are no longer equivalent. Which means that you can't simply replace one with the other during, e.g., circuit optimization.

levbishop commented 2 years ago

I think it would be a useful enhancement for qasm exporter, to optionally emit the global phase gate even in qasm 2 mode. It could be defined using the gate definition @burgholzer suggested above.

jakelishman commented 2 years ago

These gate definitions can be a bit of a problem when running on actual hardware, though - in the example here, the transpilation step first converts to the ['u3', 'cx'] basis, but then the global phase gate is with x and p. Converting bases is outside the responsibility of the OpenQASM 2 emitter (or at least it should be!).

To me, this isn't something that the OpenQASM 2 output should be handling. If a user specifically wants to include it, I feel like it should be added to the circuit manually by them. I'm ok with supporting a "translate circuit phase to gate definition" function or transpiler pass (completely external to the OpenQASM 2 emitter), but I don't think it should be within the OpenQASM 2 conversion - logically it's nothing to do with OpenQASM 2, in the same way that other transpilation operations aren't.

I'm not certain right now if our basis conversion steps could handle trying to find a sequence of unitaries from an arbitrary basis that apply a global phase, but leave the state of the system untouched. On the surface, it looks like an operation we'll be explicitly excluding from our synthesis searches for efficiency - for ['u', 'cx'], I guess it'd be something like U(pi/2, 0, 0) . U(0, 0, theta) . U(pi/2, 0, 0) . U(0, 0, theta). Do we have something that will work already (@levbishop)?

burgholzer commented 2 years ago

@jakelishman providing a transpiler pass for this sounds like a good compromise to me. Then users can just apply it on top of the whole transpilation process whenever they need it.

For ['u', 'cx'] you are right. that's just the p -- x -- p -- x sequence from above. I would guess that it is not possible for an arbitrary basis to obtain such a sequence. At the very least you need some sort of rotation gate with an arbitrary angle. But I am also not quite sure whether allowing for an arbitrary basis is actually a requirement, given that this would be an optional transpiler pass.

jakelishman commented 2 years ago

Yeah, I'm completely happy to support it as a transpiler pass - what you've got here is already a strong use case for it.

I guess if the basis is complete, it should always easily be possible; at worst, we can use our existing synthesis methods to produce a single X flip - RZ(theta) operation on an arbitrary qubit, and then just apply it twice.