qiskit
qiskit copied to clipboard
Controlled UGate with a parameterized angle fails with 4 or more controls
Environment
- Qiskit version: 1.0.0
- Python version: 3.11.7
- Operating system: macOS 14.4.1
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
Perhaps related to #11993
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.
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)
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
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))
closing this issue as #12752 is merged