Open MattePalte opened 2 years ago
The problem seems related to the transformation to qasm format (qc.qasm()), because the two machine use different decomposers TwoQubitBasisDecomposer
, which in turn come from two different TwoQubitWeylDecomposition
.
The TwoQubitBasisDecomposer
is initialized here in my qiskit version:
https://github.com/Qiskit/qiskit-terra/blob/a8a0d1be26b5df097e7a21016fa21550a3c4efce/qiskit/quantum_info/synthesis/two_qubit_decompose.py#L1405
But it is similar in the latest version: https://github.com/Qiskit/qiskit-terra/blob/fe8ab43f1218d6997458fb3bf4011045f40932b0/qiskit/quantum_info/synthesis/two_qubit_decompose.py#L1449 https://github.com/Qiskit/qiskit-terra/blob/fe8ab43f1218d6997458fb3bf4011045f40932b0/qiskit/quantum_info/synthesis/two_qubit_decompose.py#L1422
The difference can be exposed with this even minimal example.
from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition
from qiskit.quantum_info.operators import Operator
from qiskit.circuit.library.standard_gates import CXGate
gate = CXGate()
weyl = TwoQubitWeylDecomposition(Operator(gate).data)
weyl.__dict__
Output SETUP A:
{'_original_decomposition': TwoQubitWeylDecomposition.from_bytes(
# TwoQubitWeylDecomposition(
# global phase: π/4
# ┌──────────┐┌──────────┐ ┌────────────┐┌────────┐┌──────────┐
# q_0: ┤ Rz(-π/2) ├┤ Ry(-π/2) ├────────────┤0 ├┤ Rz(-π) ├┤ Ry(-π/2) ├
# ├──────────┤├─────────┬┘┌──────────┐│ Rxx(-π/2) │├───────┬┘└──────────┘
# q_1: ┤ Rz(-π/2) ├┤ Ry(π/2) ├─┤ Rz(-π/2) ├┤1 ├┤ Ry(π) ├─────────────
# └──────────┘└─────────┘ └──────────┘└────────────┘└───────┘
# )
b'k05VTVBZAQB2AHsnZGVzY3InOiAnPGMxNicsICdmb3J0cmFuX29yZGVyJzogRmFsc2UsICdzaGFw'
b'ZSc6ICg0LCA0KSwgfSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg'
b'ICAgICAgICAgICAgIAoAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'AAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'AAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
requested_fidelity=0.999999999,
calculated_fidelity=1.0,
actual_fidelity=0.9999999999999997,
abc=(0.7853981633974483, 0.0, 0.0)),
'a': 0.7853981633974483,
'b': 0,
'c': 0,
'K1l': array([[-1.05226633e-16+3.82683432e-01j, 9.23879533e-01-5.36741351e-17j],
[-9.23879533e-01+6.41070206e-18j, 3.98309960e-17-3.82683432e-01j]]),
'K1r': array([[-0.5-0.5j, 0.5+0.5j],
[-0.5+0.5j, -0.5+0.5j]]),
'K2l': array([[ 5.65713056e-17+3.82683432e-01j, -9.23879533e-01-2.34326020e-17j],
[ 9.23879533e-01-2.34326020e-17j, 5.65713056e-17-3.82683432e-01j]]),
'K2r': array([[ 4.32978028e-17-0.70710678j, -4.32978028e-17+0.70710678j],
[ 4.32978028e-17+0.70710678j, 4.32978028e-17+0.70710678j]]),
'global_phase': -2.356194490192345,
'unitary_matrix': array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]]),
'requested_fidelity': None,
'_is_flipped_from_original': False,
'calculated_fidelity': 1.0}
Output SETUP B:
{'_original_decomposition': TwoQubitWeylDecomposition.from_bytes(
# TwoQubitWeylDecomposition(
# global phase: π/4
# ┌────────────┐ ┌─────────┐ ┌────────────┐┌──────────┐┌─────────────┐
# q_0: ┤ Rz(3.1389) ├───┤ Ry(π/2) ├──────────────┤0 ├┤ Ry(-π/2) ├┤ Rz(-1.5681) ├────────────
# └┬──────────┬┘┌──┴─────────┴──┐┌─────────┐│ Rxx(-π/2) │├─────────┬┘└┬────────────┤┌──────────┐
# q_1: ─┤ Rz(-π/2) ├─┤ Ry(0.0026466) ├┤ Rz(π/2) ├┤1 ├┤ Rz(π/2) ├──┤ Ry(1.5734) ├┤ Rz(-π/2) ├
# └──────────┘ └───────────────┘└─────────┘└────────────┘└─────────┘ └────────────┘└──────────┘
# )
b'k05VTVBZAQB2AHsnZGVzY3InOiAnPGMxNicsICdmb3J0cmFuX29yZGVyJzogRmFsc2UsICdzaGFw'
b'ZSc6ICg0LCA0KSwgfSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg'
b'ICAgICAgICAgICAgIAoAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'AAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'AAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
b'8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
requested_fidelity=0.999999999,
calculated_fidelity=1.0,
actual_fidelity=1.0,
abc=(0.7853981633974483, 0.0, 0.0)),
'a': 0.7853981633974483,
'b': 0,
'c': 0,
'K1l': array([[ 9.99999781e-01-3.92003950e-17j, -7.84266235e-17-6.61646830e-04j],
[-7.84785659e-17-6.61646830e-04j, 9.99999781e-01+3.93042108e-17j]]),
'K1r': array([[ 0.5-0.5j, 0.5-0.5j],
[-0.5-0.5j, 0.5+0.5j]]),
'K2l': array([[ 7.07574481e-01+4.04613999e-20j, -4.05149776e-20-7.06638771e-01j],
[ 4.05149776e-20-7.06638771e-01j, 7.07574481e-01-4.04613999e-20j]]),
'K2r': array([[ 0.70710678+0.j, -0.70710678+0.j],
[ 0.70710678+0.j, 0.70710678+0.j]]),
'global_phase': 0.7853981633974487,
'unitary_matrix': array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]]),
'requested_fidelity': None,
'_is_flipped_from_original': False,
'calculated_fidelity': 1.0}
This method contains a bit of randomness, which might be the source of the divergence, and can be also a good moment to review it (given the comment): https://github.com/Qiskit/qiskit-terra/blob/fe8ab43f1218d6997458fb3bf4011045f40932b0/qiskit/quantum_info/synthesis/two_qubit_decompose.py#L174-L175
Looking forward to know which result is the given code on your machine with the qiskit version used by me. Thanks in advance
To further check that the two circuits are far from each other, I got their unitary:
Setup A:
from math import pi
qr = QuantumRegister(2, name='qr')
cr = ClassicalRegister(2, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
# SECTION
# NAME: MEASUREMENT
qc.append(RZGate(-pi/2), qargs=[qr[0]], cargs=[])
qc.append(RYGate(-pi/2), qargs=[qr[0]], cargs=[])
qc.append(RZGate(-pi/2), qargs=[qr[1]], cargs=[])
qc.append(RYGate(pi/2), qargs=[qr[1]], cargs=[])
qc.append(RZGate(-pi/2), qargs=[qr[1]], cargs=[])
qc.append(RXXGate(-pi/2), qargs=[qr[0], qr[1]], cargs=[])
qc.append(RZGate(-pi), qargs=[qr[0]], cargs=[])
qc.append(RYGate(pi), qargs=[qr[1]], cargs=[])
qc.append(RYGate(-pi/2), qargs=[qr[0]], cargs=[])
qc.save_unitary()
#qc.measure(qr, cr)
qc.draw(fold=-1)
simulator = Aer.get_backend('aer_simulator')
qc_trans = transpile(qc, simulator)
# Run and get unitary
unitary = simulator.run(qc_trans).result().get_unitary(qc)
unitary
Output:
┌──────────┐┌──────────┐ ┌────────────┐┌────────┐┌──────────┐ ░
qr_0: ┤ Rz(-π/2) ├┤ Ry(-π/2) ├────────────┤0 ├┤ Rz(-π) ├┤ Ry(-π/2) ├─░─
├──────────┤├─────────┬┘┌──────────┐│ Rxx(-π/2) │├───────┬┘└──────────┘ ░
qr_1: ┤ Rz(-π/2) ├┤ Ry(π/2) ├─┤ Rz(-π/2) ├┤1 ├┤ Ry(π) ├──────────────░─
└──────────┘└─────────┘ └──────────┘└────────────┘└───────┘ ░
cr: 2/═══════════════════════════════════════════════════════════════════════════
array([[ 7.07106781e-01-7.07106781e-01j, 9.75980418e-34+6.70078871e-17j,
5.18672754e-17-2.35174251e-16j, -6.50353591e-17+7.85046229e-17j],
[ 6.62912745e-17-1.62588398e-17j, -4.23815048e-17+7.20465508e-17j,
4.73817313e-17-1.62464020e-32j, 7.07106781e-01-7.07106781e-01j],
[-2.67754988e-16+1.31296464e-17j, -5.55111512e-17+5.55111512e-17j,
7.07106781e-01-7.07106781e-01j, -1.02350467e-32+2.77555756e-17j],
[ 2.77555756e-17+1.16264517e-33j, 7.07106781e-01-7.07106781e-01j,
3.37735950e-17-6.50353591e-17j, 7.48991843e-17-6.53119189e-17j]])
Setup B:
from math import pi
qr = QuantumRegister(2, name='qr')
cr = ClassicalRegister(2, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
# SECTION
# NAME: MEASUREMENT
qc.append(RZGate(3.1389), qargs=[qr[0]], cargs=[])
qc.append(RYGate(pi/2), qargs=[qr[0]], cargs=[])
qc.append(RZGate(-pi/2), qargs=[qr[1]], cargs=[])
qc.append(RYGate(0.0026466), qargs=[qr[1]], cargs=[])
qc.append(RZGate(pi/2), qargs=[qr[1]], cargs=[])
qc.append(RXXGate(-pi/2), qargs=[qr[0], qr[1]], cargs=[])
qc.append(RYGate(-pi/2), qargs=[qr[0]], cargs=[])
qc.append(RZGate(-1.5681), qargs=[qr[0]], cargs=[])
qc.append(RZGate(pi/2), qargs=[qr[1]], cargs=[])
qc.append(RYGate(1.5734), qargs=[qr[1]], cargs=[])
qc.append(RZGate(-pi/2), qargs=[qr[1]], cargs=[])
qc.save_unitary()
#qc.measure(qr, cr)
qc.draw(fold=-1)
simulator = Aer.get_backend('aer_simulator')
qc_trans = transpile(qc, simulator)
# Run and get unitary
unitary = simulator.run(qc_trans).result().get_unitary(qc)
unitary
Output B:
┌────────────┐ ┌─────────┐ ┌────────────┐┌──────────┐┌─────────────┐ ░
qr_0: ┤ Rz(3.1389) ├───┤ Ry(π/2) ├──────────────┤0 ├┤ Ry(-π/2) ├┤ Rz(-1.5681) ├─────────────░─
└┬──────────┬┘┌──┴─────────┴──┐┌─────────┐│ Rxx(-π/2) │├─────────┬┘└┬────────────┤┌──────────┐ ░
qr_1: ─┤ Rz(-π/2) ├─┤ Ry(0.0026466) ├┤ Rz(π/2) ├┤1 ├┤ Rz(π/2) ├──┤ Ry(1.5734) ├┤ Rz(-π/2) ├─░─
└──────────┘ └───────────────┘└─────────┘└────────────┘└─────────┘ └────────────┘└──────────┘ ░
cr: 2/══════════════════════════════════════════════════════════════════════════════════════════════════
array([[ 7.07105482e-01-7.07108080e-01j, -6.65772178e-17+1.30397053e-17j,
1.51769418e-05+1.51768860e-05j, -1.27068414e-16-1.80165683e-17j],
[-2.97362235e-17-8.14854106e-17j, 1.51768860e-05+1.51769418e-05j,
5.36602501e-17-9.77166068e-17j, 7.07108080e-01-7.07105482e-01j],
[ 1.51769418e-05+1.51768860e-05j, -1.27170614e-16-1.80165683e-17j,
7.07105482e-01-7.07108080e-01j, -6.65772178e-17+9.15442617e-17j],
[ 5.36602501e-17-1.25433065e-16j, 7.07108080e-01-7.07105482e-01j,
-1.94750686e-18-8.14854106e-17j, 1.51768860e-05+1.51769418e-05j]])
And by checking the difference, they are far as per numpy standard threshold:
import numpy as np
mat_a = np.array([[ 7.07106781e-01-7.07106781e-01j, 9.75980418e-34+6.70078871e-17j,
5.18672754e-17-2.35174251e-16j, -6.50353591e-17+7.85046229e-17j],
[ 6.62912745e-17-1.62588398e-17j, -4.23815048e-17+7.20465508e-17j,
4.73817313e-17-1.62464020e-32j, 7.07106781e-01-7.07106781e-01j],
[-2.67754988e-16+1.31296464e-17j, -5.55111512e-17+5.55111512e-17j,
7.07106781e-01-7.07106781e-01j, -1.02350467e-32+2.77555756e-17j],
[ 2.77555756e-17+1.16264517e-33j, 7.07106781e-01-7.07106781e-01j,
3.37735950e-17-6.50353591e-17j, 7.48991843e-17-6.53119189e-17j]])
mat_b = np.array([[ 7.07105482e-01-7.07108080e-01j, -6.65772178e-17+1.30397053e-17j,
1.51769418e-05+1.51768860e-05j, -1.27068414e-16-1.80165683e-17j],
[-2.97362235e-17-8.14854106e-17j, 1.51768860e-05+1.51769418e-05j,
5.36602501e-17-9.77166068e-17j, 7.07108080e-01-7.07105482e-01j],
[ 1.51769418e-05+1.51768860e-05j, -1.27170614e-16-1.80165683e-17j,
7.07105482e-01-7.07108080e-01j, -6.65772178e-17+9.15442617e-17j],
[ 5.36602501e-17-1.25433065e-16j, 7.07108080e-01-7.07105482e-01j,
-1.94750686e-18-8.14854106e-17j, 1.51768860e-05+1.51769418e-05j]])
np.allclose(mat_a, mat_b)
# Output: False
Looking for help, I tag some of those who introduced the FIXME according to git blame, feel free to involve the specific code owners if you are not responsible, Thanks in advance to everybody. @levbishop @georgios-ts @mtreinish
Thanks @MattePalte for reporting and diving into the details. I'll try to figure out what's going on here.
There's a couple things going on here. First, the reason your mat_a
and mat_b
count as different to np.allclose
is just the rounding for display in the parameters 3.1389
etc. Those matrices are within 1.E-5 of each other and would be even closer if not for that rounding. A separate problem is that the decompositions look different because there is a kind o f "gimbal lock" for decomposing a CNOT gate - the specializations to TwoQubitControlledEquiv
etc. were supposed to make decompositions unique, and I covered a lot of the special cases, but this shows I did miss one or two. In particular for this case it can decompose an XX-equivalent gate into:
(Rx(the1l).Ry(lam1l) ⊗ Rx(the2l).Ry(lam2l)).XX.(Rx(phi1r) Ry(the1r) Rx(lam1r) ⊗ Rx(phi2r).Ry(the2r).Rx(lam2r))
which is unique unless lam1l==0
or (as in this case) lam2l==0
, at which point only the sum the1l+phi1r
or the2l+phi2r
will matter. I should make a PR to spot this case and pick a canonical representation.
Can you check does the qiskit test-suite pass on your machines? The main motivation for making all these canonical choices was to simplify writing tests...
I think none of this can by itself explain your original issue from the first message here - these are different but still valid decompositions of CNOT. There is something else that may be triggered by the differences, or all of this stuff with the decomposition non-uniqueness might turn out to be a red herring (still worth fixing though, so thanks for taking the time to investigate).
I pulled out the non-uniqueness of the decomposition into a different issue. I can't reproduce the original problem here on my machines. I agree that your generated QASM in the failing case generates the problem even for running on my machine, but I can't see what went wrong in generating that QASM.
Not sure if im reproducing this correctly on 0.46.1.dev0+5150003
:
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.circuit.library.standard_gates import *
from qiskit import Aer, transpile, execute
qr = QuantumRegister(5, name='qr')
cr = ClassicalRegister(5, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
subcircuit = QuantumCircuit(qr, cr, name='subcircuit')
subcircuit.append(CSwapGate(), qargs=[qr[0], qr[2], qr[3]], cargs=[])
qc.append(subcircuit, qargs=qr, cargs=cr)
qc.append(subcircuit.inverse(), qargs=qr, cargs=cr)
qc.measure(qr, cr)
qc = transpile(qc, optimization_level=3, seed_transpiler=44)
qc = QuantumCircuit.from_qasm_str(qc.qasm())
counts_B = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1024).result().get_counts(qc)
IndexError: list index out of range
It seems to also happen in with qiskit.qasm2
:
from qiskit import qasm2
qc = qasm2.loads(qasm2.dumps(qc), custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS)
counts_B = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1024).result().get_counts(qc)
IndexError: list index out of range
Environment
What is happening?
Running two supposedly equivalent variants of the same circuit leads to surprisingly discordant results. The divergence is very clear, it is reproducible on my machine, but not yet on another laptop (with Ubuntu 20.04, python 3.8, same conda packages).
How can we reproduce the issue?
Unfortunately this issue seems very much tied with some specific characteristic of my machine, since it doesn't happen with my other laptop. But it is yet unclear to me what is the specific problem. Anyway, on my machine I run the following setup and it always produce the failure:
Program A:
Program B: (equivalent because I add a circuit and its inverse).
Output:
Output:
Execute the program B:
What should happen?
The second program B, should create the same output of the first one: # OUTPUT: {'00000': 1024}
Any suggestions?
I tried two other setups other than the failing one on the remote machine.
SETUP A: (FAIL) as describe above + entire environment yaml below.
STEUP B: (NO FAIL) local system (aka laptop) with Ubuntu 20.04, python 3.8 and the same conda packages (except for few that were not available anymore, not sure why, marked below), Conda 4.11.0.
SETUP C: (NO FAIL) Dockerfile with Ubutnu 18.04.6 LTS, Miniconda3 with conda version 4.11.0, same conda packages, (except for few that were not available anymore, not sure why, marked below).
Hint: the error must be in the transpiler or qasm exporter, because when using the produced qasm file (visible above) it leads to failure (ALSO IN SETUP B and C)
I attach here the list of dump created via
conda env export > environment.yml
: (Note that six pip packages were not installed in the second laptop, because they led to pip exceptions, they are marked with# LEADING TO PIP EXCEPTION
in the list).I am really looking forward to your feedback since I have no clue on why this happen and more importantly how to reproduce it on another machine, which is crucial for all of us to understand this bug.