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.26k stars 2.37k forks source link

Add measure_all(..., new_creg=False) to avoid creating new clbits in the circuit #4158

Closed sportwagon closed 2 years ago

sportwagon commented 4 years ago

Information

What is the current behavior?

If you use measure_all rather than measure the count values returned by get_counts have extra data in the key values. This seems to be directly related to measure_all inserting a barrier before the measurement, as this is the only difference in the resulting circuits. See section on reproducing the bug for the exact behavior

Steps to reproduce the problem

The following code will reproduce the problem

qc = QuantumCircuit(2,2)
qc.measure([0,1], [0,1])
print ("Run doing measures without barrier")
print(qc.qasm())
job = execute(qc, backend, shots=8000)
res = job.result()
my_counts = res.get_counts()
print ("Counts returned by get_counts:")
print (my_counts)
print ("data in result object")
print (res.data())

qc = QuantumCircuit(2,2)
qc.measure_all()
print ("Run doing measures with barrier [measure_all]")
print(qc.qasm())
job = execute(qc, backend, shots=8000)
res = job.result()
my_counts = res.get_counts()
print ("Counts returned by get_counts:")
print (my_counts)
print ("data in result object")
print (res.data())

When run on ibmq_almaden it generates the following output

Run doing measures without barrier
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
measure q[0] -> c[0];
measure q[1] -> c[1];

Counts returned by get_counts:
{'10': 95, '01': 41, '00': 7864}
data in result object
{'counts': {'0x2': 95, '0x1': 41, '0x0': 7864}}

Run doing measures with barrier [measure_all]
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
creg meas[2];
barrier q[0],q[1];
measure q[0] -> meas[0];
measure q[1] -> meas[1];

Counts returned by get_counts:
{'11 00': 1, '00 00': 7794, '10 00': 152, '01 00': 53}
data in result object
{'counts': {'0xc': 1, '0x0': 7794, '0x8': 152, '0x4': 53}}

As can be seen from the counts object in the result, the values returned from the backend are correct, but the keys in the dictionary returned by get_counts have an extra set of 00s in them. That spurious field is as long as the number of qubits in the circuit (e.g. if we do the same thing with 3 qubits we would see 000 included at the end of each key).

What is the expected behavior?

measure_all or any measure with a barrier in front should return counts correctly.

Suggested solutions

kdk commented 4 years ago

This is because measure_all/measure_active add a new creg to the circuit, so the returned results are over 4 cbits instead of the expected 2. This is documented in the docstrings, but even still may be unexpected behavior.

What would the expected behavior be here? If no creg is present? If a creg exists? But its size is larger/smaller than num_qubits/num_active_qubits?

1ucian0 commented 4 years ago

ping @sportwagon

sclang16 commented 4 years ago

I observed similar behavior in a 2 qreg, 2 creg circuit using the circuit below:

qc = QuantumCircuit(2,2)
qc.h([0,1])
qc.measure_all()

print("measure_all() circuit, correct # cregs declared: \n")
print(qc.qasm())
job = execute(qc, backend, shots=4000)
res = job.result()
my_counts = res.get_counts()
print ("Counts returned by get_counts:")
print (my_counts)
print ("data in result object")
print (res.data(), '\n')

Output when run on qasm_simulator:

measure_all() circuit, correct # cregs declared: 

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
creg meas[2];
h q[0];
h q[1];
barrier q[0],q[1];
measure q[0] -> meas[0];
measure q[1] -> meas[1];

Counts returned by get_counts:
{'00 00': 1028, '01 00': 969, '10 00': 1004, '11 00': 999}
data in result object
{'counts': {'0x0': 1028, '0x4': 969, '0x8': 1004, '0xc': 999}} 

If this circuit is declared without a creg and relies on the one in measure_all, it behaves no differently than if it were explicitly measured.

qc2 = QuantumCircuit(2,2)
qc2.h([0,1])
qc2.barrier()
qc2.measure([0,1],[0,1])

print("Normal measure circuit, correct # cregs declared: \n")
print(qc2.qasm())
job2 = execute(qc2, backend, shots=4000)
res2 = job2.result()
my_counts2 = res2.get_counts()
print ("Counts returned by get_counts:")
print (my_counts2)
print ("data in result object")
print (res2.data(), '\n')

qc3 = QuantumCircuit(2)
qc3.h([0,1])
qc3.measure_all()

print("measure_all() circuit, cregs not declared: \n")
print(qc3.qasm())
job3 = execute(qc3, backend, shots=4000)
res3 = job3.result()
my_counts3 = res3.get_counts()
print ("Counts returned by get_counts:")
print (my_counts3)
print ("data in result object")
print (res3.data(), '\n')

Both versions' outputs when run on qasm_simulator:

Normal measure circuit, correct # cregs declared: 

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
h q[1];
barrier q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];

Counts returned by get_counts:
{'00': 999, '01': 989, '10': 1033, '11': 979}
data in result object
{'counts': {'0x0': 999, '0x1': 989, '0x2': 1033, '0x3': 979}} 

measure_all() circuit, cregs not declared: 

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg meas[2];
h q[0];
h q[1];
barrier q[0],q[1];
measure q[0] -> meas[0];
measure q[1] -> meas[1];

Counts returned by get_counts:
{'00': 939, '01': 1035, '10': 1039, '11': 987}
data in result object
{'counts': {'0x0': 939, '0x1': 1035, '0x2': 1039, '0x3': 987}} 

As stated earlier, the discrepancy is definitely not the result of the barrier but instead the arbitrary addition of two classical registers.

sclang16 commented 4 years ago

If a QuantumCircuit is declared with a creg size not equal to num_active_qubits, the behavior of measure_all does not change. It continues to add a creg of the correct size and outputs measurements larger than expected.

qc4 = QuantumCircuit(2,1)
qc4.h([0,1])
qc4.measure_all()

print("measure_all() circuit, too few cregs declared: \n")
print(qc4.qasm())
job4 = execute(qc4, backend, shots=4000)
res4 = job4.result()
my_counts4 = res4.get_counts()
print ("Counts returned by get_counts:")
print (my_counts4)
print ("data in result object")
print (res4.data(), '\n')

qc5 = QuantumCircuit(2,3)
qc5.h([0,1])
qc5.measure_all()

print("measure_all() circuit, too many cregs declared: \n")
print(qc5.qasm())
job5 = execute(qc5, backend, shots=4000)
res5 = job5.result()
my_counts5 = res5.get_counts()
print ("Counts returned by get_counts:")
print (my_counts5)
print ("data in result object")
print (res5.data(), '\n')

Output on qasm_simulator:

measure_all() circuit, too few cregs declared: 

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[1];
creg meas[2];
h q[0];
h q[1];
barrier q[0],q[1];
measure q[0] -> meas[0];
measure q[1] -> meas[1];

Counts returned by get_counts:
{'00 0': 987, '01 0': 1023, '10 0': 998, '11 0': 992}
data in result object
{'counts': {'0x0': 987, '0x2': 1023, '0x4': 998, '0x6': 992}} 

measure_all() circuit, too many cregs declared: 

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[3];
creg meas[2];
h q[0];
h q[1];
barrier q[0],q[1];
measure q[0] -> meas[0];
measure q[1] -> meas[1];

Counts returned by get_counts:
{'00 000': 1000, '10 000': 1017, '11 000': 1027, '01 000': 956}
data in result object
{'counts': {'0x0': 1000, '0x10': 1017, '0x18': 1027, '0x8': 956}}

I think a possible solution is to give measure_all an option to include the extra creg, but exclude it by default if one is already declared in the QuantumCircuit. The elongated binary data may be confusing to beginners, but it may have functionality that we are unaware of here.

nonhermitian commented 4 years ago

Measure_all adds its own classical register, so you should not define one in QuantumCircuit.

sclang16 commented 4 years ago

Am aware, but there ought to be error checking for those who aren't.

1ucian0 commented 4 years ago

Would this solution be acceptable?

If same size:

from qiskit import *

qc = QuantumCircuit(2, 2)
qc.h([0,1])
qc.measure_all(new_creg=False)

qc.draw()
        ┌───┐ ░ ┌─┐   
   q_0: ┤ H ├─░─┤M├───
        ├───┤ ░ └╥┘┌─┐
   q_1: ┤ H ├─░──╫─┤M├
        └───┘ ░  ║ └╥┘
   c: 2/═════════╩══╩═
                 0  1 

If smaller size (or maybe just error?):

qc = QuantumCircuit(2, 1)
qc.h([0,1])
qc.measure_all(new_creg=False)

print(qc)
        ┌───┐ ░ ┌─┐   
   q_0: ┤ H ├─░─┤M├───
        ├───┤ ░ └╥┘┌─┐
   q_1: ┤ H ├─░──╫─┤M├
        └───┘ ░  ║ └╥┘
   c: 1/═════════╩══╬═
                 0  ║ 
                    ║ 
meas: 1/════════════╩═
                    0 

If larger size (or maybe just error?):

qc = QuantumCircuit(2, 3)
qc.h([0,1])
qc.measure_all(new_creg=False)

print(qc)
     ┌───┐ ░ ┌─┐   
q_0: ┤ H ├─░─┤M├───
     ├───┤ ░ └╥┘┌─┐
q_1: ┤ H ├─░──╫─┤M├
     └───┘ ░  ║ └╥┘
c: 3/═════════╩══╩═
              0  1 

Default is new_creg=True to keep current behavior.

1ucian0 commented 3 years ago

ping @sclang16 @sportwagon ?

sclang16 commented 3 years ago

Yes, this looks to be a good solution.

1ucian0 commented 3 years ago

Changing to feature request and renaming it. Thanks!

dani-guijo commented 2 years ago

I like the proposed solution by @1ucian0 and I'd be willing to fix this bug if it's possible. Can I get assigned to it?

jakelishman commented 2 years ago

Thanks! I've assigned you (after a false start...).

Given that the last discussion didn't seem to finish completely, I'd suggest that it might be best just to implement the behaviour of new_creg=False be to work if the number of clbits in the circuit is exactly equal to the number of qubits, and raise CircuitError if not, at least at the beginning. If others further respond in this issue or your forthcoming PR, you could always add extra functionality. After a very quick read over the discussion, this only seems to make sense to me with new_creg=False if there's an exact match, and maybe if there are more clbits than qubits, but then you could easily argue about which clbits you should use. Better just to raise an error in those cases, since the original issue was about "why is the number of clbits different after this function"?