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.24k stars 2.36k forks source link

Bound parameters is back under certain conditions in QuantumCircuit. #7486

Open ikkoham opened 2 years ago

ikkoham commented 2 years ago

Environment

What is happening?

If you decompose the parameterized quantum circuit with control and inverse, the parameters will be back.

How can we reproduce the issue?

Code

from qiskit.circuit import Parameter
from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
param = Parameter("old")
qc.p(param, 0)
controlled = qc.control()
new_param = Parameter("new")
qc = QuantumCircuit(2)
qc.append(controlled.assign_parameters({param: new_param}).inverse(), qc.qubits)

print(qc)
print(qc.decompose().decompose())

Output

     ┌─────────────────────────┐
q_0: ┤0                        ├
     │  c_circuit-0-11_dg(new) │
q_1: ┤1                        ├
     └─────────────────────────┘

q_0: ─■────────────
      │P(-1.0*old) 
q_1: ─■────────────

What should happen?

The parameters of both circuits should be new.

Any suggestions?

Cryoris commented 2 years ago

I think the problem is that the ControlledGate object inside controlled uses the ControlledGate.base_gate to reconstruct the inverse, but we don't propagate the parameter update down to ControlledGate.base_gate.

from qiskit.circuit import Parameter
from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
param = Parameter("old")
qc.p(param, 0)
controlled = qc.control()
new_param = Parameter("new")
assigned = controlled.assign_parameters({param: new_param})

cgate = assigned.data[0][0]
print(cgate)
# prints: Instruction(name='ccircuit-0', num_qubits=2, num_clbits=0, params=[Parameter(new)])
# meaning that ControlledGate.params is updated to the new parameters

print(cgate.base_gate.definition.parameters)
# prints: ParameterView([Parameter(old)])
# to construct the inverse, the ControlledGate.base_gate is used which still contains the old parameter.

print(cgate.base_gate.definition.draw())
#    ┌────────┐
# q: ┤ P(old) ├
#    └────────┘

Hence to fix this, we would have to bind parameters also in the base_gate if we hit a ControlledGate in the QuantumCircuit._assign_parameter method.

woodsp-ibm commented 2 years ago

Coincidentally I have been working with someone this week who had come across what I think is the same issue, when using the machine learning SVC and QuantumKernel. (Update: seems they have been working also with the author of this issue so it is the same issue). They had created a controlled ZZfeatureMap and used that as the feature map for the kernel. That fails when running the SVC with a message about "par_y" not being found when binding. These happen to be the parameters of the inverse part of the circuit the kernel uses, which it creates from the given feature map and re-assigned the parameters to par_y.

Now printing the circuit and its parameters, after it was created, it had par_x and par_y as expected. It was after transpilation in the case of the QuantumKernel that the circuit 'lost' the par_y and then failed when binding; and printing the parameters showed par_x and x, rather than par_x and par_y.

I had been going to create an issue after I created a smaller working sample. But it seems this describes the same issue and the person I was working with on their problem noticed this a few mins ago. Hence for now I think this is good and we can check with the QuantumKernel when a solution is found.

Update: I applied the lines as per the referenced PR above and the issue with the feature map and quantum kernel is corrected.