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.04k stars 2.32k forks source link

TypeError: label expects a string or None when loading a circuit using default label #11686

Open jiannanWang opened 7 months ago

jiannanWang commented 7 months ago

Environment

What is happening?

Running the below code results in TypeError: label expects a string or None. However, I use the default label which is None.

How can we reproduce the issue?

from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import CUGate

qr = QuantumRegister(3)
qc = QuantumCircuit(qr, name='qc')
qc.append(CUGate(1, 1, 1, 1).control(1), qr)

from qiskit import qpy
with open('circuit.qpy', 'wb') as fd:
    qpy.dump(qc, fd)
with open('circuit.qpy', 'rb') as fd:
    qc = qpy.load(fd)[0]  # TypeError: label expects a string or None

What should happen?

I expect the code to run without error.

Any suggestions?

No response

Waliur003 commented 7 months ago

@jiannanWang I learned That qpy works with QuantumCircuit Modules not with qiskit.circuit.library. So I checked the documents listed below and modify your code using

qc.cu(theta, phi, lam, gamma, control_qubit, target_qubit, label=None, ctrl_state=None)

supprted with QuantumCircuit and it works correctly with my code editor. here is the code..

from qiskit import QuantumCircuit
from qiskit import qpy

qc = QuantumCircuit(3, name='circuit')
qc.cu(1, 1, 1, 1, 1, 0)

with open('circuit.qpy', 'wb') as fd:
    qpy.dump(qc, fd)

with open('circuit.qpy', 'rb') as fd:
    new_qc = qpy.load(fd)[0]

Here is the documentation of Cu gate and qpy https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.CUGate https://docs.quantum.ibm.com/api/qiskit/qpy

I Hope It works with you as well.

jiannanWang commented 7 months ago

Hope you are doing well! It's been a while since I submitted this issue. I wonder if there's any update? Thanks!

I revisited this issue and checked the stack track (copied below). From the traceback, it seems during loading, the qpy somehow loaded UGate instead of the CUGate. So the fourth parameter (gamma) of CUGate is passed to the fourth parameter (label) of UGate, which eventually causes this crash.

Traceback (most recent call last):
  File "/mnt/test_crash.py", line 12, in <module>
    qc = qpy.load(fd)[0]  # TypeError: label expects a string or None
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/qpy/interface.py", line 301, in load
    loader(
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/qpy/binary_io/circuits.py", line 1189, in read_circuit
    _read_instruction(
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/qpy/binary_io/circuits.py", line 273, in _read_instruction
    inst_obj = _parse_custom_operation(
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/qpy/binary_io/circuits.py", line 396, in _parse_custom_operation
    base_gate = _read_instruction(
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/qpy/binary_io/circuits.py", line 351, in _read_instruction
    gate = gate_class(*params)
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/circuit/library/standard_gates/u.py", line 81, in __init__
    super().__init__("u", 1, [theta, phi, lam], label=label, duration=duration, unit=unit)
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/circuit/gate.py", line 45, in __init__
    super().__init__(name, num_qubits, 0, params, label=label, duration=duration, unit=unit)
  File "/root/anaconda3/envs/qiskit/lib/python3.10/site-packages/qiskit/circuit/instruction.py", line 95, in __init__
    raise TypeError("label expects a string or None")
TypeError: label expects a string or None
alexanderivrii commented 7 months ago

After experimenting with the code, the problem seems to boil down to the following. The code

gate = CUGate(1, 1, 1, 1)
print(f"{gate = }")
cgate = gate.control(1)
print(f"{cgate = }")
bgate = cgate.base_gate
print(f"{bgate = }")

outputs

gate = Instruction(name='cu', num_qubits=2, num_clbits=0, params=[1, 1, 1, 1])
cgate = Instruction(name='ccu', num_qubits=3, num_clbits=0, params=[1, 1, 1, 1])
bgate = Instruction(name='u', num_qubits=1, num_clbits=0, params=[1, 1, 1, 1])

That is, we now have a UGate with the wrong number of parameters. I believe this is due to the discrepancy in handling params for generic controlled gates vs. CUGates, see https://github.com/Qiskit/qiskit/blob/9157c04230943d5470e5bb6c31ce0f4c8dedecf6/qiskit/circuit/library/standard_gates/u.py#L148-L157

Having written this, I have no idea how to fix this, leaving the fix to the experts. :)

atharva-satpute commented 3 months ago

After experimenting with the code, the problem seems to boil down to the following. The code

gate = CUGate(1, 1, 1, 1)
print(f"{gate = }")
cgate = gate.control(1)
print(f"{cgate = }")
bgate = cgate.base_gate
print(f"{bgate = }")

outputs

gate = Instruction(name='cu', num_qubits=2, num_clbits=0, params=[1, 1, 1, 1])
cgate = Instruction(name='ccu', num_qubits=3, num_clbits=0, params=[1, 1, 1, 1])
bgate = Instruction(name='u', num_qubits=1, num_clbits=0, params=[1, 1, 1, 1])

Considering this as an example, after creating the cu instruction, we create a controlled gate ccu line 240 by passing in operation.params = [1,1,1,1] (which is params of cu instruction above) and base_gate = u gate (which is the base gate of cu). During which we call the constructor of Instruction class and assign self.params = params which causes the failure. IMO, we should first check whether or not self.params is empty and then move forward The following code https://github.com/Qiskit/qiskit/blob/9157c04230943d5470e5bb6c31ce0f4c8dedecf6/qiskit/circuit/instruction.py#L108 should be

if not self.params:
self.params = params

With this change, the output of the above code snippet is

gate = Instruction(name='cu', num_qubits=2, num_clbits=0, params=[1, 1, 1, 1])
cgate = Instruction(name='ccu', num_qubits=3, num_clbits=0, params=[1, 1, 1])
bgate = Instruction(name='u', num_qubits=1, num_clbits=0, params=[1, 1, 1])