Improve parameter-binding performance of large instructions (#10284)

* Improve parameter-binding performance of large instructions

Previously, the parameter-assignment methods of `QuantumCircuit` had
poor performance when an instruction had a complex definition that
involved many of the parameters being bound.  The strategy of binding
each parameter separately led to each definition being copied and
rebound multiple times, with each rebinding being recursive all the way
down.

This commit makes the definition rebinding happen only once per
instruction, and updates the data model used to make it a complete
recursion through `QuantumCircuit.assign_parameters`.  This has the side
effect of fixing an issue where internal global phases would not be
updated.

The algorithmic change that enables this (just rebind the definition at
the end) is rather simpler than the length of this patch suggests.  This
is just because the previous structure of separating out a single
`_assign_parameter` method made it harder to restructure the logic
without introducing unpleasant stateful coupling between the driver and
helper methods.  Instead, I inlined most of the helper functions into
the driver body, so we can treat some components of the binding in a
per-parameter way and some in a per-operation way, in whatever way is
better.

* Fix lint

* Reduce overhead from input normalisation

This reduces several costs in the input normalisation, and makes the
calculation of some properties lazy for the sequence-like inputs; for
many close-to-hardware circuits such as those used in PEC, the sequence
form of parameter input is more natural, and almost no instructions will
have internally parametrised definitions, nor will there be a
parametrised global phase or calibrations.  In these cases, we can avoid
overhead from eagerly normalising the input into the forms that's easier
for these less-common assignment operations to use.

As a side-effect of the abstraction, we can also avoid making several
dictionary copies, and just use the mapping abstraction to filter the
dictionary during iteration on the fly.

This also takes the opportunity to improve the performance of sorting
large vectors of parameters.  In practice, I don't think this had a huge
impact on performance, but in principle it's rather more efficient now
and results in many fewer Python function calls during a sort.

* Address Ruff's generator concern

* Add comment on new keyword arguments
This commit is contained in:
Jake Lishman 2023-07-20 15:46:47 +01:00 committed by GitHub
parent 2616602a54
commit 7c1b8ee4fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 249 additions and 197 deletions

View File

@ -16,6 +16,19 @@ This module contains utility functions for circuits.
import numpy
from qiskit.exceptions import QiskitError
from qiskit.circuit.exceptions import CircuitError
from .parametervector import ParameterVectorElement
def sort_parameters(parameters):
"""Sort an iterable of :class:`.Parameter` instances into a canonical order, respecting the
ordering relationships between elements of :class:`.ParameterVector`\\ s."""
def key(parameter):
if isinstance(parameter, ParameterVectorElement):
return (parameter.vector.name, parameter.index)
return (parameter.name,)
return sorted(parameters, key=key)
def _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=None):

View File

@ -20,6 +20,7 @@ import rustworkx as rx
from qiskit.exceptions import InvalidFileError
from .exceptions import CircuitError
from .parameter import Parameter
from .parameterexpression import ParameterExpression
Key = namedtuple("Key", ["name", "num_qubits"])
@ -284,7 +285,7 @@ def _raise_if_shape_mismatch(gate, circuit):
def _rebind_equiv(equiv, query_params):
equiv_params, equiv_circuit = equiv
param_map = dict(zip(equiv_params, query_params))
equiv = equiv_circuit.assign_parameters(param_map, inplace=False)
param_map = {x: y for x, y in zip(equiv_params, query_params) if isinstance(x, Parameter)}
equiv = equiv_circuit.assign_parameters(param_map, inplace=False, flat_input=True)
return equiv

View File

@ -779,6 +779,7 @@ class NLocal(BlueprintCircuit):
parameters: Mapping[Parameter, ParameterExpression | float]
| Sequence[ParameterExpression | float],
inplace: bool = False,
**kwargs,
) -> QuantumCircuit | None:
"""Assign parameters to the n-local circuit.
@ -800,7 +801,7 @@ class NLocal(BlueprintCircuit):
if not self._is_built:
self._build()
return super().assign_parameters(parameters, inplace=inplace)
return super().assign_parameters(parameters, inplace=inplace, **kwargs)
def _parameterize_block(
self, block, param_iter=None, rep_num=None, block_num=None, indices=None, params=None

View File

@ -18,7 +18,6 @@ from __future__ import annotations
import collections.abc
import copy
import itertools
import functools
import multiprocessing as mp
import string
import re
@ -49,12 +48,13 @@ from qiskit.circuit.parameter import Parameter
from qiskit.circuit.exceptions import CircuitError
from qiskit.utils import optionals as _optionals
from . import _classical_resource_map
from ._utils import sort_parameters
from .classical import expr
from .parameterexpression import ParameterExpression, ParameterValueType
from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
from .classicalregister import ClassicalRegister, Clbit
from .parametertable import ParameterReferences, ParameterTable, ParameterView
from .parametervector import ParameterVector, ParameterVectorElement
from .parametervector import ParameterVector
from .instructionset import InstructionSet
from .operation import Operation
from .register import Register
@ -2614,9 +2614,7 @@ class QuantumCircuit:
"""
# parameters from gates
if self._parameters is None:
unsorted = self._unsorted_parameters()
self._parameters = sorted(unsorted, key=functools.cmp_to_key(_compare_parameters))
self._parameters = sort_parameters(self._unsorted_parameters())
# return as parameter view, which implements the set and list interface
return ParameterView(self._parameters)
@ -2638,6 +2636,9 @@ class QuantumCircuit:
self,
parameters: Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]],
inplace: Literal[False] = ...,
*,
flat_input: bool = ...,
strict: bool = ...,
) -> "QuantumCircuit":
...
@ -2646,13 +2647,19 @@ class QuantumCircuit:
self,
parameters: Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]],
inplace: Literal[True] = ...,
*,
flat_input: bool = ...,
strict: bool = ...,
) -> None:
...
def assign_parameters(
def assign_parameters( # pylint: disable=missing-raises-doc
self,
parameters: Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]],
inplace: bool = False,
*,
flat_input: bool = False,
strict: bool = True,
) -> Optional["QuantumCircuit"]:
"""Assign parameters to new parameters or values.
@ -2670,6 +2677,13 @@ class QuantumCircuit:
parameters: Either a dictionary or iterable specifying the new parameter values.
inplace: If False, a copy of the circuit with the bound parameters is returned.
If True the circuit instance itself is modified.
flat_input: If ``True`` and ``parameters`` is a mapping type, it is assumed to be
exactly a mapping of ``{parameter: value}``. By default (``False``), the mapping
may also contain :class:`.ParameterVector` keys that point to a corresponding
sequence of values, and these will be unrolled during the mapping.
strict: If ``False``, any parameters given in the mapping that are not used in the
circuit will be ignored. If ``True`` (the default), an error will be raised
indicating a logic error.
Raises:
CircuitError: If parameters is a dict and contains parameters not present in the
@ -2678,7 +2692,7 @@ class QuantumCircuit:
parameters in the circuit.
Returns:
A copy of the circuit with bound parameters, if ``inplace`` is False, otherwise None.
A copy of the circuit with bound parameters if ``inplace`` is False, otherwise None.
Examples:
@ -2715,47 +2729,154 @@ class QuantumCircuit:
circuit.draw('mpl')
"""
# replace in self or in a copy depending on the value of in_place
if inplace:
bound_circuit = self
target = self
else:
bound_circuit = self.copy()
self._increment_instances()
bound_circuit._name_update()
target = self.copy()
target._increment_instances()
target._name_update()
# Normalise the inputs into simple abstract interfaces, so we've dispatched the "iteration"
# logic in one place at the start of the function. This lets us do things like calculate
# and cache expensive properties for (e.g.) the sequence format only if they're used; for
# many large, close-to-hardware circuits, we won't need the extra handling for
# `global_phase` or recursive definition binding.
#
# During normalisation, be sure to reference 'parameters' and related things from 'self' not
# 'target' so we can take advantage of any caching we might be doing.
if isinstance(parameters, dict):
# unroll the parameter dictionary (needed if e.g. it contains a ParameterVector)
unrolled_param_dict = self._unroll_param_dict(parameters)
unsorted_parameters = self._unsorted_parameters()
# check that all param_dict items are in the _parameter_table for this circuit
params_not_in_circuit = [
param_key
for param_key in unrolled_param_dict
if param_key not in unsorted_parameters
]
if len(params_not_in_circuit) > 0:
raw_mapping = parameters if flat_input else self._unroll_param_dict(parameters)
our_parameters = self._unsorted_parameters()
if strict and (extras := raw_mapping.keys() - our_parameters):
raise CircuitError(
"Cannot bind parameters ({}) not present in the circuit.".format(
", ".join(map(str, params_not_in_circuit))
)
f"Cannot bind parameters ({', '.join(str(x) for x in extras)}) not present in"
" the circuit."
)
# replace the parameters with a new Parameter ("substitute") or numeric value ("bind")
for parameter, value in unrolled_param_dict.items():
bound_circuit._assign_parameter(parameter, value)
parameter_binds = _ParameterBindsDict(raw_mapping, our_parameters)
else:
if len(parameters) != self.num_parameters:
our_parameters = self.parameters
if len(parameters) != len(our_parameters):
raise ValueError(
"Mismatching number of values and parameters. For partial binding "
"please pass a dictionary of {parameter: value} pairs."
)
# use a copy of the parameters, to ensure we don't change the contents of
# self.parameters while iterating over them
fixed_parameters_copy = self.parameters.copy()
for i, value in enumerate(parameters):
bound_circuit._assign_parameter(fixed_parameters_copy[i], value)
return None if inplace else bound_circuit
parameter_binds = _ParameterBindsSequence(our_parameters, parameters)
# Clear out the parameter table for the relevant entries, since we'll be binding those.
# Any new references to parameters are reinserted as part of the bind.
target._parameters = None
# This is deliberately eager, because we want the side effect of clearing the table.
all_references = [
(parameter, value, target._parameter_table.pop(parameter, ()))
for parameter, value in parameter_binds.items()
]
seen_operations = {}
# The meat of the actual binding for regular operations.
for to_bind, bound_value, references in all_references:
update_parameters = (
tuple(bound_value.parameters)
if isinstance(bound_value, ParameterExpression)
else ()
)
for operation, index in references:
seen_operations[id(operation)] = operation
assignee = operation.params[index]
if isinstance(assignee, ParameterExpression):
new_parameter = assignee.assign(to_bind, bound_value)
for parameter in update_parameters:
if parameter not in target._parameter_table:
target._parameter_table[parameter] = ParameterReferences(())
target._parameter_table[parameter].add((operation, index))
if not new_parameter.parameters:
if new_parameter.is_real():
new_parameter = (
int(new_parameter)
if new_parameter._symbol_expr.is_integer
else float(new_parameter)
)
else:
new_parameter = complex(new_parameter)
new_parameter = operation.validate_parameter(new_parameter)
elif isinstance(assignee, QuantumCircuit):
new_parameter = assignee.assign_parameters(
{to_bind: bound_value}, inplace=False, flat_input=True
)
else:
raise RuntimeError( # pragma: no cover
f"Saw an unknown type during symbolic binding: {assignee}."
" This may indicate an internal logic error in symbol tracking."
)
operation.params[index] = new_parameter
# After we've been through everything at the top level, make a single visit to each
# operation we've seen, rebinding its definition if necessary.
for operation in seen_operations.values():
if (
definition := getattr(operation, "_definition", None)
) is not None and definition.num_parameters:
definition.assign_parameters(
parameter_binds.mapping, inplace=True, flat_input=True, strict=False
)
if isinstance(target.global_phase, ParameterExpression):
new_phase = target.global_phase
for parameter in new_phase.parameters & parameter_binds.mapping.keys():
new_phase = new_phase.assign(parameter, parameter_binds.mapping[parameter])
target.global_phase = new_phase
# Finally, assign the parameters inside any of the calibrations. We don't track these in
# the `ParameterTable`, so we manually reconstruct things.
def map_calibration(qubits, parameters, schedule):
modified = False
new_parameters = list(parameters)
for i, parameter in enumerate(new_parameters):
if not isinstance(parameter, ParameterExpression):
continue
if not (contained := parameter.parameters & parameter_binds.mapping.keys()):
continue
for to_bind in contained:
parameter = parameter.assign(to_bind, parameter_binds.mapping[to_bind])
if not parameter.parameters:
parameter = (
int(parameter) if parameter._symbol_expr.is_integer else float(parameter)
)
new_parameters[i] = parameter
modified = True
if modified:
schedule.assign_parameters(parameter_binds.mapping)
return (qubits, tuple(new_parameters)), schedule
target._calibrations = defaultdict(
dict,
(
(
gate,
dict(
map_calibration(qubits, parameters, schedule)
for (qubits, parameters), schedule in calibrations.items()
),
)
for gate, calibrations in target._calibrations.items()
),
)
return None if inplace else target
@staticmethod
def _unroll_param_dict(
parameter_binds: Mapping[Parameter, ParameterValueType]
) -> Mapping[Parameter, ParameterValueType]:
out = {}
for parameter, value in parameter_binds.items():
if isinstance(parameter, ParameterVector):
if len(parameter) != len(value):
raise CircuitError(
f"Parameter vector '{parameter.name}' has length {len(parameter)},"
f" but was assigned to {len(value)} values."
)
out.update(zip(parameter, value))
else:
out[parameter] = value
return out
def bind_parameters(
self, values: Union[Mapping[Parameter, float], Sequence[float]]
@ -2791,136 +2912,6 @@ class QuantumCircuit:
)
return self.assign_parameters(values)
def _unroll_param_dict(
self, value_dict: Mapping[Parameter, ParameterValueType]
) -> dict[Parameter, ParameterValueType]:
unrolled_value_dict: dict[Parameter, ParameterValueType] = {}
for (param, value) in value_dict.items():
if isinstance(param, ParameterVector):
if not len(param) == len(value):
raise CircuitError(
"ParameterVector {} has length {}, which "
"differs from value list {} of "
"len {}".format(param, len(param), value, len(value))
)
unrolled_value_dict.update(zip(param, value))
# pass anything else except number through. error checking is done in assign_parameter
elif isinstance(param, (ParameterExpression, str)) or param is None:
unrolled_value_dict[param] = value
return unrolled_value_dict
def _assign_parameter(self, parameter: Parameter, value: ParameterValueType) -> None:
"""Update this circuit where instances of ``parameter`` are replaced by ``value``, which
can be either a numeric value or a new parameter expression.
Args:
parameter (ParameterExpression): Parameter to be bound
value (Union(ParameterExpression, float, int)): A numeric or parametric expression to
replace instances of ``parameter``.
Raises:
RuntimeError: if some internal logic error has caused the circuit instruction sequence
and the parameter table to become out of sync, and the table now contains a
reference to a value that cannot be assigned.
"""
# parameter might be in global phase only
if parameter in self._parameter_table:
for instr, param_index in self._parameter_table[parameter]:
assignee = instr.params[param_index]
# Normal ParameterExpression.
if isinstance(assignee, ParameterExpression):
new_param = assignee.assign(parameter, value)
# if fully bound, validate
if len(new_param.parameters) == 0:
if new_param._symbol_expr.is_integer and new_param.is_real():
val = int(new_param)
elif new_param.is_real():
val = float(new_param)
else:
# complex values may no longer be supported but we
# defer raising an exception to validdate_parameter
# below for now.
val = complex(new_param)
instr.params[param_index] = instr.validate_parameter(val)
else:
instr.params[param_index] = new_param
self._rebind_definition(instr, parameter, value)
# Scoped block of a larger instruction.
elif isinstance(assignee, QuantumCircuit):
# It's possible that someone may re-use a loop body, so we need to mutate the
# parameter vector with a new circuit, rather than mutating the body.
instr.params[param_index] = assignee.assign_parameters({parameter: value})
else:
raise RuntimeError( # pragma: no cover
"The ParameterTable or data of this QuantumCircuit have become out-of-sync."
f"\nParameterTable: {self._parameter_table}"
f"\nData: {self.data}"
)
if isinstance(value, ParameterExpression):
entry = self._parameter_table.pop(parameter)
for new_parameter in value.parameters:
if new_parameter in self._parameter_table:
self._parameter_table[new_parameter] |= entry
else:
self._parameter_table[new_parameter] = entry
else:
del self._parameter_table[parameter] # clear evaluated expressions
if (
isinstance(self.global_phase, ParameterExpression)
and parameter in self.global_phase.parameters
):
self.global_phase = self.global_phase.assign(parameter, value)
# clear parameter cache
self._parameters = None
self._assign_calibration_parameters(parameter, value)
def _assign_calibration_parameters(
self, parameter: Parameter, value: ParameterValueType
) -> None:
"""Update parameterized pulse gate calibrations, if there are any which contain
``parameter``. This updates the calibration mapping as well as the gate definition
``Schedule``s, which also may contain ``parameter``.
"""
new_param: ParameterValueType
for cals in self.calibrations.values():
for (qubit, cal_params), schedule in copy.copy(cals).items():
if any(
isinstance(p, ParameterExpression) and parameter in p.parameters
for p in cal_params
):
del cals[(qubit, cal_params)]
new_cal_params = []
for p in cal_params:
if isinstance(p, ParameterExpression) and parameter in p.parameters:
new_param = p.assign(parameter, value)
if not new_param.parameters:
if new_param._symbol_expr.is_integer:
new_param = int(new_param)
else:
new_param = float(new_param)
new_cal_params.append(new_param)
else:
new_cal_params.append(p)
schedule.assign_parameters({parameter: value})
cals[(qubit, tuple(new_cal_params))] = schedule
def _rebind_definition(
self, instruction: Instruction, parameter: Parameter, value: ParameterValueType
) -> None:
if instruction._definition:
for inner in instruction._definition:
for idx, param in enumerate(inner.operation.params):
if isinstance(param, ParameterExpression) and parameter in param.parameters:
if isinstance(value, ParameterExpression):
inner.operation.params[idx] = param.subs({parameter: value})
else:
inner.operation.params[idx] = param.bind({parameter: value})
self._rebind_definition(inner.operation, parameter, value)
def barrier(self, *qargs: QubitSpecifier, label=None) -> InstructionSet:
"""Apply :class:`~.library.Barrier`. If ``qargs`` is empty, applies to all qubits
in the circuit.
@ -4970,22 +4961,40 @@ class QuantumCircuit:
return 0 # If there are no instructions over bits
def _standard_compare(value1, value2):
if value1 < value2:
return -1
if value1 > value2:
return 1
return 0
class _ParameterBindsDict:
__slots__ = ("mapping", "allowed_keys")
def __init__(self, mapping, allowed_keys):
self.mapping = mapping
self.allowed_keys = allowed_keys
def items(self):
"""Iterator through all the keys in the mapping that we care about. Wrapping the main
mapping allows us to avoid reconstructing a new 'dict', but just use the given 'mapping'
without any copy / reconstruction."""
for parameter, value in self.mapping.items():
if parameter in self.allowed_keys:
yield parameter, value
def _compare_parameters(param1: Parameter, param2: Parameter) -> int:
if isinstance(param1, ParameterVectorElement) and isinstance(param2, ParameterVectorElement):
# if they belong to a vector with the same name, sort by index
if param1.vector.name == param2.vector.name:
return _standard_compare(param1.index, param2.index)
class _ParameterBindsSequence:
__slots__ = ("parameters", "values", "mapping_cache")
# else sort by name
return _standard_compare(param1.name, param2.name)
def __init__(self, parameters, values):
self.parameters = parameters
self.values = values
self.mapping_cache = None
def items(self):
"""Iterator through all the keys in the mapping that we care about."""
return zip(self.parameters, self.values)
@property
def mapping(self):
"""Cached version of a mapping. This is only generated on demand."""
if self.mapping_cache is None:
self.mapping_cache = dict(zip(self.parameters, self.values))
return self.mapping_cache
# Used by the OQ2 exporter. Just needs to have enough parameters to support the largest standard

View File

@ -13,11 +13,10 @@
"""The base interface for Opflow's gradient."""
from typing import Union, List, Optional
import functools
import numpy as np
from qiskit.circuit.quantumcircuit import _compare_parameters
from qiskit.circuit import ParameterExpression, ParameterVector
from qiskit.circuit._utils import sort_parameters
from qiskit.utils import optionals as _optionals
from qiskit.utils.deprecation import deprecate_func
from .circuit_gradients.circuit_gradient import CircuitGradient
@ -67,7 +66,7 @@ class Gradient(GradientBase):
if len(operator.parameters) == 0:
raise ValueError("The operator we are taking the gradient of is not parameterized!")
if params is None:
params = sorted(operator.parameters, key=functools.cmp_to_key(_compare_parameters))
params = sort_parameters(operator.parameters)
if isinstance(params, (ParameterVector, list)):
param_grads = [self.convert(operator, param) for param in params]
absent_params = [

View File

@ -13,11 +13,10 @@
"""The module to compute Hessians."""
from typing import Union, List, Tuple, Optional
import functools
import numpy as np
from qiskit.circuit.quantumcircuit import _compare_parameters
from qiskit.circuit import ParameterVector, ParameterExpression
from qiskit.circuit._utils import sort_parameters
from qiskit.utils import optionals as _optionals
from qiskit.utils.deprecation import deprecate_func
from ..operator_globals import Zero, One
@ -115,7 +114,7 @@ class Hessian(HessianBase):
if len(operator.parameters) == 0:
raise ValueError("The operator we are taking the gradient of is not parameterized!")
if params is None:
params = sorted(operator.parameters, key=functools.cmp_to_key(_compare_parameters))
params = sort_parameters(operator.parameters)
# if input is a tuple instead of a list, wrap it into a list
if isinstance(params, (ParameterVector, list)):
# Case: a list of parameters were given, compute the Hessian for all param pairs

View File

@ -14,11 +14,10 @@
from collections.abc import Iterable
from typing import List, Tuple, Callable, Optional, Union
import functools
import numpy as np
from qiskit.circuit.quantumcircuit import _compare_parameters
from qiskit.circuit import ParameterVector, ParameterExpression
from qiskit.circuit._utils import sort_parameters
from qiskit.utils import optionals as _optionals
from qiskit.utils.deprecation import deprecate_func
from ..operator_base import OperatorBase
@ -122,7 +121,7 @@ class NaturalGradient(GradientBase):
if len(operator.parameters) == 0:
raise ValueError("The operator we are taking the gradient of is not parameterized!")
if params is None:
params = sorted(operator.parameters, key=functools.cmp_to_key(_compare_parameters))
params = sort_parameters(operator.parameters)
if not isinstance(params, Iterable):
params = [params]
# Instantiate the gradient

View File

@ -13,10 +13,9 @@
"""The module for Quantum the Fisher Information."""
from typing import List, Union, Optional
import functools
from qiskit.circuit.quantumcircuit import _compare_parameters
from qiskit.circuit import ParameterExpression, ParameterVector
from qiskit.circuit._utils import sort_parameters
from qiskit.utils.deprecation import deprecate_func
from ..list_ops.list_op import ListOp
from ..expectations.pauli_expectation import PauliExpectation
@ -71,5 +70,5 @@ class QFI(QFIBase):
cleaned_op = self._factor_coeffs_out_of_composed_op(expec_op)
if params is None:
params = sorted(operator.parameters, key=functools.cmp_to_key(_compare_parameters))
params = sort_parameters(operator.parameters)
return self.qfi_method.convert(cleaned_op, params)

View File

@ -0,0 +1,20 @@
---
features:
- |
The performance of :meth:`.QuantumCircuit.assign_parameters` and :meth:`~.QuantumCircuit.bind_parameters`
has significantly increased for large circuits with structures typical of applications uses.
This includes most circuits based on the :class:`.NLocal` structure, such as
:class:`.EfficientSU2`. See `#10282 <https://github.com/Qiskit/qiskit-terra/issues/10282>`__ for more
detail.
- |
The method :meth:`.QuantumCircuit.assign_parameters` has gained two new keywords arguments: ``flat_input``
and ``strict``. These are advanced options that can be used to speed up the method when passing the
parameter bindings as a dictionary; ``flat_input=True`` is a guarantee that the dictionary keys contain
only :class:`.Parameter` instances (not :class:`.ParameterVector`\ s), and ``strict=False`` allows the
dictionary to contain parameters that are not present in the circuit. Using these two options can
reduce the overhead of input normalisation in this function.
fixes:
- |
A parametrised circuit that contains a custom gate whose definition has a parametrised global phase
can now successfully bind the parameter in the inner global phase.
See `#10283 <https://github.com/Qiskit/qiskit-terra/issues/10283>`__ for more detail.

View File

@ -193,6 +193,18 @@ class TestParameters(QiskitTestCase):
c = a.bind({a: 1, b: 1}, allow_unknown_parameters=True)
self.assertEqual(c, a.bind({a: 1}))
@data(QuantumCircuit.assign_parameters, QuantumCircuit.bind_parameters)
def test_bind_parameters_custom_definition_global_phase(self, assigner):
"""Test that a custom gate with a parametrised `global_phase` is assigned correctly."""
x = Parameter("x")
custom = QuantumCircuit(1, global_phase=x).to_gate()
base = QuantumCircuit(1)
base.append(custom, [0], [])
test = Operator(assigner(base, {x: math.pi}))
expected = Operator(numpy.array([[-1, 0], [0, -1]]))
self.assertEqual(test, expected)
def test_bind_half_single_precision(self):
"""Test binding with 16bit and 32bit floats."""
phase = Parameter("phase")

View File

@ -432,7 +432,7 @@ class TestCircuitQASM3(QiskitTestCase):
[
"OPENQASM 3;",
'include "stdgates.inc";',
"gate custom(p0, p1) _gate_q_0, _gate_q_1 {",
"gate custom(_gate_p_0, _gate_p_0) _gate_q_0, _gate_q_1 {",
" rz(pi) _gate_q_0;",
" rz(pi/4) _gate_q_1;",
"}",
@ -1670,7 +1670,7 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
[
"OPENQASM 3;",
'include "stdgates.inc";',
f"gate custom_{custom_id}(p0, p1) _gate_q_0, _gate_q_1 {{",
f"gate custom_{custom_id}(_gate_p_0, _gate_p_1) _gate_q_0, _gate_q_1 {{",
" rz(pi) _gate_q_0;",
" rz(pi/4) _gate_q_1;",
"}",