Skip to content

Circuit parameter binding does not bind global_phase in instruction definitions #10283

@jakelishman

Description

@jakelishman

Environment

  • Qiskit Terra version: 0.24.1
  • Python version: 3.10
  • Operating system: macOS 13.3.1

What is happening?

When a custom gate with a set definition contains a parametrised global phase, any circuit that contains that custom gate will fail to bind the global phase during parameter assignment.

How can we reproduce the issue?

import math

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import Operator

x = Parameter("x")

custom = QuantumCircuit(1, global_phase=x).to_gate()
base = QuantumCircuit(1)
base.append(custom, [0], [])

Operator(base.assign_parameters({x: math.pi}))
TypeError                                 Traceback (most recent call last)
Cell In[1], line 12
      9 base = QuantumCircuit(1)
     10 base.append(custom, [0], [])
---> 12 Operator(base.assign_parameters({x: math.pi}))

File ~/code/qiskit/terra/qiskit/quantum_info/operators/operator.py:85, in Operator.__init__(self, data, input_dims, output_dims)
     76     self._data = np.asarray(data, dtype=complex)
     77 elif isinstance(data, (QuantumCircuit, Operation)):
     78     # If the input is a Terra QuantumCircuit or Operation we
     79     # perform a simulation to construct the unitary operator.
   (...)
     83     # conditional gates, measure, or reset will cause an
     84     # exception to be raised.
---> 85     self._data = self._init_instruction(data).data
     86 elif hasattr(data, "to_operator"):
     87     # If the data object has a 'to_operator' attribute this is given
     88     # higher preference than the 'to_matrix' method for initializing
     89     # an Operator object.
     90     data = data.to_operator()

File ~/code/qiskit/terra/qiskit/quantum_info/operators/operator.py:614, in Operator._init_instruction(cls, instruction)
    612 if isinstance(instruction, QuantumCircuit):
    613     instruction = instruction.to_instruction()
--> 614 op._append_instruction(instruction)
    615 return op

File ~/code/qiskit/terra/qiskit/quantum_info/operators/operator.py:691, in Operator._append_instruction(self, obj, qargs)
    689 else:
    690     new_qargs = [qargs[bit_indices[tup]] for tup in instruction.qubits]
--> 691 self._append_instruction(instruction.operation, qargs=new_qargs)

File ~/code/qiskit/terra/qiskit/quantum_info/operators/operator.py:669, in Operator._append_instruction(self, obj, qargs)
    666 if obj.definition.global_phase:
    667     dimension = 2**obj.num_qubits
    668     op = self.compose(
--> 669         ScalarOp(dimension, np.exp(1j * float(obj.definition.global_phase))),
    670         qargs=qargs,
    671     )
    672     self._data = op.data
    673 flat_instr = obj.definition

File ~/code/qiskit/terra/qiskit/circuit/parameterexpression.py:480, in ParameterExpression.__float__(self)
    478 except (TypeError, RuntimeError) as exc:
    479     if self.parameters:
--> 480         raise TypeError(
    481             "ParameterExpression with unbound parameters ({}) "
    482             "cannot be cast to a float.".format(self.parameters)
    483         ) from None
    484     try:
    485         # In symengine, if an expression was complex at any time, its type is likely to have
    486         # stayed "complex" even when the imaginary part symbolically (i.e. exactly)
    487         # cancelled out.  Sympy tends to more aggressively recognise these as symbolically
    488         # real.  This second attempt at a cast is a way of unifying the behaviour to the
    489         # more expected form for our users.
    490         cval = complex(self)

TypeError: ParameterExpression with unbound parameters ({Parameter(x)}) cannot be cast to a float.

What should happen?

All parameter usage in custom definitions should be bound.

Any suggestions?

This is due to ad-hoc recursion/iteration in QuantumCircuit._rebind_definition. It treats Instruction._definition like a list[tuple[Instruction, qubits, clbits]], which (a long time ago) was a possibility. Since Instruction._definition is now required to be a QuantumCircuit, the simplest fix is to make the definition-rebinding an explicit recursion via QuantumCircuit.assign_parameters.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions