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
4.84k stars 2.29k forks source link

Controlled UGate with a parameterized angle fails with 4 or more controls #12135

Open nepomechie opened 3 months ago

nepomechie commented 3 months ago

Environment

What is happening?

A controlled UGate with a parametrized angle k and with 4 (or more) controls throws an error.

Such controlled parametrized gates are useful for constructing certain custom trial states for vqe.

How can we reproduce the issue?

from qiskit import *
from qiskit import QuantumRegister
from qiskit import QuantumCircuit
from qiskit.circuit.library import UGate
from qiskit.circuit import Parameter

k=Parameter("k")
q=QuantumRegister(5)
qc = QuantumCircuit(q)
ccU = UGate(k,0,0).control(4)
qc.append(ccU,[0,1,2,3,4])```

TypeError Traceback (most recent call last) Cell In[3], line 10 8 q=QuantumRegister(5) 9 qc = QuantumCircuit(q) ---> 10 ccU = UGate(k,0,0).control(4) 11 qc.append(ccU,[0,1,2,3,4])

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/library/standard_gates/u.py:130, in UGate.control(self, num_ctrl_qubits, label, ctrl_state, annotated) 128 gate.base_gate.label = self.label 129 else: --> 130 gate = super().control( 131 num_ctrl_qubits=num_ctrl_qubits, 132 label=label, 133 ctrl_state=ctrl_state, 134 annotated=annotated, 135 ) 136 return gate

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/gate.py:121, in Gate.control(self, num_ctrl_qubits, label, ctrl_state, annotated) 117 if not annotated: 118 # pylint: disable=cyclic-import 119 from .add_control import add_control --> 121 return add_control(self, num_ctrl_qubits, label, ctrl_state) 123 else: 124 return AnnotatedOperation( 125 self, ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state) 126 )

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/add_control.py:61, in add_control(operation, num_ctrl_qubits, label, ctrl_state) 58 if isinstance(operation, UnitaryGate): 59 # attempt decomposition 60 operation._define() ---> 61 cgate = control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) 62 if operation.label is not None: 63 cgate.base_gate = cgate.base_gate.to_mutable()

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/add_control.py:187, in control(operation, num_ctrl_qubits, label, ctrl_state) 183 controlled_circ.mcrx( 184 theta, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True 185 ) 186 elif phi == 0 and lamb == 0: --> 187 controlled_circ.mcry( 188 theta, 189 q_control, 190 q_target[bit_indices[qargs[0]]], 191 q_ancillae, 192 use_basis_gates=True, 193 ) 194 elif theta == 0 and phi == 0: 195 controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]])

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py:331, in mcry(self, theta, q_controls, q_target, q_ancillae, mode, use_basis_gates) 320 _apply_mcu_graycode( 321 self, 322 theta_step, (...) 327 use_basis_gates=use_basis_gates, 328 ) 329 else: 330 cgate = _mcsu2_real_diagonal( --> 331 RYGate(theta).to_matrix(), 332 num_controls=len(control_qubits), 333 use_basis_gates=use_basis_gates, 334 ) 335 self.compose(cgate, control_qubits + [target_qubit], inplace=True) 336 else:

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/gate.py:62, in Gate.to_matrix(self) 52 """Return a Numpy.array for the gate unitary matrix. 53 54 Returns: (...) 59 exception will be raised when this base class method is called. 60 """ 61 if hasattr(self, "array"): ---> 62 return self.array(dtype=complex) 63 raise CircuitError(f"to_matrix not defined for this {type(self)}")

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/library/standard_gates/ry.py:124, in RYGate.array(self, dtype) 122 def array(self, dtype=None): 123 """Return a numpy.array for the RY gate.""" --> 124 cos = math.cos(self.params[0] / 2) 125 sin = math.sin(self.params[0] / 2) 126 return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype)

File ~/Library/Python/3.12/lib/python/site-packages/qiskit/circuit/parameterexpression.py:415, in ParameterExpression.float(self) 413 except (TypeError, RuntimeError) as exc: 414 if self.parameters: --> 415 raise TypeError( 416 "ParameterExpression with unbound parameters ({}) " 417 "cannot be cast to a float.".format(self.parameters) 418 ) from None 419 # In symengine, if an expression was complex at any time, its type is likely to have 420 # stayed "complex" even when the imaginary part symbolically (i.e. exactly) 421 # cancelled out. Sympy tends to more aggressively recognise these as symbolically 422 # real. This second attempt at a cast is a way of unifying the behaviour to the 423 # more expected form for our users. 424 cval = complex(self)

TypeError: ParameterExpression with unbound parameters (dict_keys([Parameter(k)])) cannot be cast to a float.```

What should happen?

If the number of controls is less than 4, then no error is thrown:

ccU = UGate(k,0,0).control(3)
qc.append(ccU,[0,1,2,3])```

<qiskit.circuit.instructionset.InstructionSet at 0x107709330>```

Similar behavior is expected for 4 or more controls.

Any suggestions?

Thanks for your help.

CC:@diemilio

levbishop commented 3 months ago

Perhaps related to #11993

jakelishman commented 3 months ago

This is because of the eager attempts to synthesise controlled gates in arbitrary controls - chances are that before 4 controls, we have a particular path through the synthesis that simplifies everything down to something we can do symbolically, but not in this case.

As an immediate workaround, depending on when you will call assign_parameters, you might be able to set annotated=True in the call to control - if you assign the parameters before transpilation, that may well work for you. Fwiw, no matter how we improve this in the future, the synthesis of parametric multi-controlled gates is likely never going to work efficiently in a symbolic manner, because the decomposition algorithms for multicontrolled boxes usually make heavy use of various numerics of them to produce more efficient syntheses.

ShellyGarion commented 2 months ago

Perhaps related to #11993

This bug cannot be related to #11993 since this error already occurs in Qiskit 1.0.2 while this PR was merged only last week (and will be released in Qiksit 1.1)

ShellyGarion commented 2 months ago

I think it may be related to this PR: https://github.com/Qiskit/qiskit/pull/9688

in the code of mcrz (as well as mcrx and mcry) this argument: lam: ParameterValueType https://github.com/Qiskit/qiskit/blob/9f228fa4fe5e8f4de4b53cf77400e25242ba43c3/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py#L345 is overridden by: lam (float): angle lambda https://github.com/Qiskit/qiskit/blob/9f228fa4fe5e8f4de4b53cf77400e25242ba43c3/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py#L355

In fact, mcrz never actually had ParameterExpression, it was always overridden by float.

I also mentioned it in issue https://github.com/Qiskit/qiskit/issues/12048

king-p3nguin commented 2 months ago

Perhaps this is because the function _mcsu2_real_diagonal generates gates from a numpy array.

https://github.com/Qiskit/qiskit/blob/71753689ec7d72aa5ed3f513069d6644cb977277/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py#L87-L92

If you try to use parameters to construct MCRX gate, it fails here

https://github.com/Qiskit/qiskit/blob/71753689ec7d72aa5ed3f513069d6644cb977277/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py#L262

How to reproduce:

from qiskit import QuantumCircuit
from qiskit.circuit.parameter import Parameter

qc = QuantumCircuit(4)

th = Parameter('θ')

qc.mcrx(th, [0, 1, 2], 3, use_basis_gates=False)

qcinv = qc.inverse()

from qiskit.quantum_info import Operator
from qiskit.quantum_info.operators.predicates import is_identity_matrix

print(is_identity_matrix(Operator(qc).dot(qcinv.inverse()).data))