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.3k stars 2.38k forks source link

Broadcast implementation when append multi-control bit gate #13387

Open 11D-Beyonder opened 4 weeks ago

11D-Beyonder commented 4 weeks ago

Environment

What is happening?

Can't append a multi-control gate using append because broadcast.

How can we reproduce the issue?

Suppose we construct an empty circuit and then use the append function to add a CRY gate. The code is as follows:

circuit = QuantumCircuit(
  QuantumRegister(2, "control"), QuantumRegister(1, "target")
)
circuit.append(CRYGate(np.pi / 4), circuit.qregs)

The quantum register named "control" serves as the control qubits, while the register named "target" serves as the target qubit. Since two control qubits are used here, but a CRY gate only requires one control qubit, Qiskit employs a broadcasting mechanism, interpreting the two bits in "control" as individual control qubits for separate CRY gates. Therefore, the resulting circuit appears as follows, with both serving as control qubits, resulting in two CRY gates.

control_0: ─────■────────────────
                │                
control_1: ─────┼──────────■─────
           ┌────┴────┐┌────┴────┐
   target: ┤ Ry(π/4) ├┤ Ry(π/4) ├
           └─────────┘└─────────┘

However, if we want to add a multi-control gate, such as UCRYGate.

circuit = QuantumCircuit(
    QuantumRegister(2, "control"), QuantumRegister(1, "target")
)
circuit.append(UCRYGate(np.ones(4).tolist()), circuit.qregs)

I get the following error.

Traceback (most recent call last):
  File ".../test.py", line 100, in test_append_ucry
    circuit.append(UCRYGate(np.ones(4).tolist()), circuit.qregs)
  File ".../lib/python3.12/site-packages/qiskit/circuit/quantumcircuit.py", line 2523, in append
    operation.broadcast_arguments(expanded_qargs, expanded_cargs)
  File ".../lib/python3.12/site-packages/qiskit/circuit/gate.py", line 223, in broadcast_arguments
    raise CircuitError(
qiskit.circuit.exceptions.CircuitError: 'The amount of qubit(2)/clbit(0) arguments does not match the gate expectation (3).'

What should happen?

The expected result should be two qubits in register "control" as the control qubits and "target" as the target qubit, constructing a 2-control qubit UCRY gate.

Any suggestions?

The error comes from the broadcast_arguments function of class Gate.

operation.broadcast_arguments begins with a check:

if len(qargs) != self.num_qubits:
    raise CircuitError(
        f"The amount of qubit arguments {len(qargs)} does not match"
        f" the instruction expectation ({self.num_qubits})."
    )

If we insert CRYGate, then len(qargs) and self.num_qubits are both 2 and will not report an error. If we insert UCRYGate, then len(qargs) is still 2, and self_num_qubits is the number of control qubits dependent on UCRYGate, which in the example is 3, so an error will be reported.

Due to the existence of the broadcast mechanism, the multi-control gates such as UCRYGate cannot be merged simply by append when qargs is a list of QuantumRegister. Although we can set qargs using the specific qubit index, this loses the point of using a quantum register to manage a set of qubits. I don't know if this is a Bug or Qiskit for some reason not to let us append multi-control gate through QuantumRegister?

gadial commented 3 weeks ago

I am not sure this can be classified as bug, since the input you give in the second case corresponds to the broadcasting conventions (instead of a 1-dimensional list of qubits, you pass a list containing another list). It's relatively simple to use the UCRYGate in this case in a pythonic way:

circuit.append(UCRYGate(np.ones(4).tolist()), [qubit for reg in circuit.qregs for qubit in reg])

We can add such "flattening" at the beginning of broadcast_arguments and check whether the obtained number of qubits corresponds to the total number of arguments of the gate; however, I need to think if it plays well and avoids ambiguity with multiple nesting levels, and I'm not sure it's worth the convention breaking.

11D-Beyonder commented 2 weeks ago

I am not sure this can be classified as bug, since the input you give in the second case corresponds to the broadcasting conventions (instead of a 1-dimensional list of qubits, you pass a list containing another list). It's relatively simple to use the UCRYGate in this case in a pythonic way:

circuit.append(UCRYGate(np.ones(4).tolist()), [qubit for reg in circuit.qregs for qubit in reg]) We can add such "flattening" at the beginning of broadcast_arguments and check whether the obtained number of qubits corresponds to the total number of arguments of the gate; however, I need to think if it plays well and avoids ambiguity with multiple nesting levels, and I'm not sure it's worth the convention breaking.

In my opinion, circuit.append(UCRYGate(np.ones(4).tolist()), [qubit for reg in circuit.qregs for qubit in reg]) is not elegant enough. The broadcasting conventions makes extending a gate like UCRYGate a little counter-intuitive.

Cryoris commented 2 weeks ago

@gadial I agree flattening and checking whether (1) the number of arguments fits or (2) we have to try broadcasting would make sense. By reading the docs it should be possible to append the UCRYGate in the above setting 🤔 Regarding nesting: we only support one level of nesting (i.e. Sequence[QuantumRegister]), right?