diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index 2ea2e872cddd..b02f3962799d 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -20,6 +20,7 @@ from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit import Instruction, Parameter, ParameterVector, ParameterExpression from qiskit.circuit.parametertable import ParameterTable +from qiskit.utils.deprecation import deprecate_arguments from ..blueprintcircuit import BlueprintCircuit @@ -736,9 +737,12 @@ def add_layer(self, return self - def assign_parameters(self, param_dict: Union[dict, List[float], List[Parameter], + @deprecate_arguments({'param_dict': 'parameters'}) + def assign_parameters(self, parameters: Union[dict, List[float], List[Parameter], ParameterVector], - inplace: bool = False) -> Optional[QuantumCircuit]: + inplace: bool = False, + param_dict: Optional[dict] = None # pylint: disable=unused-argument + ) -> Optional[QuantumCircuit]: """Assign parameters to the n-local circuit. This method also supports passing a list instead of a dictionary. If a list @@ -756,31 +760,31 @@ def assign_parameters(self, param_dict: Union[dict, List[float], List[Parameter] if self._data is None: self._build() - if not isinstance(param_dict, dict): - if len(param_dict) != self.num_parameters: + if not isinstance(parameters, dict): + if len(parameters) != self.num_parameters: raise AttributeError('If the parameters are provided as list, the size must match ' 'the number of parameters ({}), but {} are given.'.format( - self.num_parameters, len(param_dict) + self.num_parameters, len(parameters) )) - unbound_params = [param for param in self._ordered_parameters if - isinstance(param, ParameterExpression)] + unbound_parameters = [param for param in self._ordered_parameters if + isinstance(param, ParameterExpression)] # to get a sorted list of unique parameters, keep track of the already used parameters # in a set and add the parameters to the unique list only if not existing in the set used = set() - unbound_unique_params = [] - for param in unbound_params: + unbound_unique_parameters = [] + for param in unbound_parameters: if param not in used: - unbound_unique_params.append(param) + unbound_unique_parameters.append(param) used.add(param) - param_dict = dict(zip(unbound_unique_params, param_dict)) + parameters = dict(zip(unbound_unique_parameters, parameters)) if inplace: - new = [param_dict.get(param, param) for param in self.ordered_parameters] + new = [parameters.get(param, param) for param in self.ordered_parameters] self._ordered_parameters = new - return super().assign_parameters(param_dict, inplace=inplace) + return super().assign_parameters(parameters, inplace=inplace) def _parameterize_block(self, block, param_iter=None, rep_num=None, block_num=None, indices=None, params=None): diff --git a/qiskit/circuit/parametertable.py b/qiskit/circuit/parametertable.py index 8b1a4ec1e347..3b8ea3e59cd9 100644 --- a/qiskit/circuit/parametertable.py +++ b/qiskit/circuit/parametertable.py @@ -12,7 +12,9 @@ """ Look-up table for variable parameters in QuantumCircuit. """ -from collections.abc import MutableMapping +import warnings +import functools +from collections.abc import MutableMapping, MappingView from .instruction import Instruction @@ -79,3 +81,208 @@ def __len__(self): def __repr__(self): return 'ParameterTable({})'.format(repr(self._table)) + + +def _deprecated_set_method(): + def deprecate(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + # warn only once + if not wrapper._warned: + warnings.warn(f'The ParameterView.{func.__name__} method is deprecated as of ' + 'Qiskit Terra 0.17.0 and will be removed no sooner than 3 months ' + 'after the release date. Circuit parameters are returned as View ' + 'object, not set. To use set methods you can explicitly cast to a ' + 'set.', DeprecationWarning, stacklevel=2) + wrapper._warned = True + return func(*args, **kwargs) + wrapper._warned = False + return wrapper + return deprecate + + +class ParameterView(MappingView): + """Temporary class to transition from a set return-type to list. + + Derives from a list but implements all set methods, but all set-methods emit deprecation + warnings. + """ + + def __init__(self, iterable=None): + if iterable is not None: + self.data = list(iterable) + else: + self.data = [] + + super().__init__(self.data) + + @_deprecated_set_method() + def add(self, x): + """Add a new element.""" + if x not in self.data: + self.data.append(x) + + def copy(self): + """Copy the ParameterView.""" + return self.__class__(self.data.copy()) + + @_deprecated_set_method() + def difference(self, *s): + """Get the difference between self and the input.""" + return self.__sub__(s) + + @_deprecated_set_method() + def difference_update(self, *s): + """Get the difference between self and the input in-place.""" + for element in self: + if element in s: + self.remove(element) + + @_deprecated_set_method() + def discard(self, x): + """Remove an element from self.""" + if x in self: + self.remove(x) + + @_deprecated_set_method() + def intersection(self, *x): + """Get the intersection between self and the input.""" + return self.__and__(x) + + @_deprecated_set_method() + def intersection_update(self, *x): + """Get the intersection between self and the input in-place.""" + return self.__iand__(x) + + def isdisjoint(self, x): + """Check whether self and the input are disjoint.""" + return not any(element in self for element in x) + + @_deprecated_set_method() + def issubset(self, x): + """Check whether self is a subset of the input.""" + return self.__le__(x) + + @_deprecated_set_method() + def issuperset(self, x): + """Check whether self is a superset of the input.""" + return self.__ge__(x) + + @_deprecated_set_method() + def symmetric_difference(self, x): + """Get the symmetric difference of self and the input.""" + return self.__xor__(x) + + @_deprecated_set_method() + def symmetric_difference_update(self, x): + """Get the symmetric difference of self and the input in-place.""" + backward = x.difference(self) + self.difference_update(x) + self.update(backward) + + @_deprecated_set_method() + def union(self, *x): + """Get the union of self and the input.""" + return self.__or__(x) + + @_deprecated_set_method() + def update(self, *x): + """Update self with the input.""" + for element in x: + self.add(element) + + def remove(self, x): + """Remove an existing element from the view.""" + self.data.remove(x) + + def __repr__(self): + """Format the class as string.""" + return f'ParameterView({self.data})' + + def __getitem__(self, index): + """Get items.""" + return self.data[index] + + def __and__(self, x): + """Get the intersection between self and the input.""" + inter = [] + for element in self: + if element in x: + inter.append(element) + + return self.__class__(inter) + + def __rand__(self, x): + """Get the intersection between self and the input.""" + return self.__and__(x) + + def __iand__(self, x): + """Get the intersection between self and the input in-place.""" + for element in self: + if element not in x: + self.remove(element) + return self + + def __len__(self): + """Get the length.""" + return len(self.data) + + def __or__(self, x): + """Get the union of self and the input.""" + return set(self) | set(x) + + def __ior__(self, x): + """Update self with the input.""" + self.update(*x) + return self + + def __sub__(self, x): + """Get the difference between self and the input.""" + return set(self) - set(x) + + @_deprecated_set_method() + def __isub__(self, x): + """Get the difference between self and the input in-place.""" + return self.difference_update(*x) + + def __xor__(self, x): + """Get the symmetric difference between self and the input.""" + return set(self) ^ set(x) + + @_deprecated_set_method() + def __ixor__(self, x): + """Get the symmetric difference between self and the input in-place.""" + self.symmetric_difference_update(x) + return self + + def __ne__(self, other): + return set(other) != set(self) + + def __eq__(self, other): + return set(other) == set(self) + + def __le__(self, x): + return all(element in x for element in self) + + def __lt__(self, x): + if x != self: + return self <= x + return False + + def __ge__(self, x): + return all(element in self for element in x) + + def __gt__(self, x): + if x != self: + return self >= x + return False + + def __iter__(self): + return iter(self.data) + + def __contains__(self, x): + return x in self.data + + __hash__: None # type: ignore + __rand__ = __and__ + __ror__ = __or__ diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py index fd1ad32d84b9..bc8f4a0d0f4e 100644 --- a/qiskit/circuit/parametervector.py +++ b/qiskit/circuit/parametervector.py @@ -12,9 +12,45 @@ """Parameter Vector Class to simplify management of parameter lists.""" +from uuid import uuid4 + from .parameter import Parameter +class ParameterVectorElement(Parameter): + """An element of a ParameterVector.""" + + def __new__(cls, vector, index, uuid=None): # pylint:disable=unused-argument + obj = object.__new__(cls) + + if uuid is None: + obj._uuid = uuid4() + else: + obj._uuid = uuid + + obj._hash = hash(obj._uuid) + return obj + + def __getnewargs__(self): + return (self.vector, self.index, self._uuid) + + def __init__(self, vector, index): + name = f'{vector.name}[{index}]' + super().__init__(name) + self._vector = vector + self._index = index + + @property + def index(self): + """Get the index of this element in the parent vector.""" + return self._index + + @property + def vector(self): + """Get the parent vector instance.""" + return self._vector + + class ParameterVector: """ParameterVector class to quickly generate lists of parameters.""" @@ -23,7 +59,7 @@ def __init__(self, name, length=0): self._params = [] self._size = length for i in range(length): - self._params += [Parameter('{}[{}]'.format(self._name, i))] + self._params += [ParameterVectorElement(self, i)] @property def name(self): @@ -69,5 +105,5 @@ def resize(self, length): """ if length > len(self._params): for i in range(len(self._params), length): - self._params += [Parameter('{}[{}]'.format(self._name, i))] + self._params += [ParameterVectorElement(self, i)] self._size = length diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 27ba30f3e66c..436392bc2d3b 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -16,6 +16,7 @@ import copy import itertools +import functools import sys import warnings import numbers @@ -31,12 +32,12 @@ from qiskit.qasm.qasm import Qasm from qiskit.qasm.exceptions import QasmError from qiskit.circuit.exceptions import CircuitError -from qiskit.utils.deprecation import deprecate_function +from qiskit.utils.deprecation import deprecate_function, deprecate_arguments from .parameterexpression import ParameterExpression from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit from .classicalregister import ClassicalRegister, Clbit -from .parametertable import ParameterTable -from .parametervector import ParameterVector +from .parametertable import ParameterTable, ParameterView +from .parametervector import ParameterVector, ParameterVectorElement from .instructionset import InstructionSet from .register import Register from .bit import Bit @@ -727,8 +728,14 @@ def compose(self, other, qubits=None, clbits=None, front=False, inplace=False): else: dest._data += mapped_instrs - for instr, _, _ in mapped_instrs: - dest._update_parameter_table(instr) + if front: + dest._parameter_table.clear() + for instr, _, _ in dest._data: + dest._update_parameter_table(instr) + else: + # just append new parameters + for instr, _, _ in mapped_instrs: + dest._update_parameter_table(instr) for gate, cals in other.calibrations.items(): dest._calibrations[gate].update(cals) @@ -1880,16 +1887,22 @@ def parameters(self): # parameters in global phase if isinstance(self.global_phase, ParameterExpression): - return params.union(self.global_phase.parameters) + params = params.union(self.global_phase.parameters) + + # sort the parameters + sorted_by_name = sorted(list(params), key=functools.cmp_to_key(_compare_parameters)) - return params + # return as parameter view, which implements the set and list interface + return ParameterView(sorted_by_name) @property def num_parameters(self): """Convenience function to get the number of parameter objects in the circuit.""" return len(self.parameters) - def assign_parameters(self, param_dict, inplace=False): + @deprecate_arguments({'param_dict': 'parameters'}) + def assign_parameters(self, parameters, inplace=False, + param_dict=None): # pylint: disable=unused-argument """Assign parameters to new parameters or values. The keys of the parameter dictionary must be Parameter instances in the current circuit. The @@ -1897,14 +1910,21 @@ def assign_parameters(self, param_dict, inplace=False): The values can be assigned to the current circuit object or to a copy of it. Args: - param_dict (dict): A dictionary specifying the mapping from ``current_parameter`` - to ``new_parameter``, where ``new_parameter`` can be a new parameter object - or a numeric value. + parameters (dict or iterable): Either a dictionary or iterable specifying the new + parameter values. If a dict, it specifies the mapping from ``current_parameter`` to + ``new_parameter``, where ``new_parameter`` can be a new parameter object or a + numeric value. If an iterable, the elements are assigned to the existing parameters + in the order they were inserted. You can call ``QuantumCircuit.parameters`` to check + this order. inplace (bool): If False, a copy of the circuit with the bound parameters is returned. If True the circuit instance itself is modified. + param_dict (dict): Deprecated, use ``parameters`` instead. Raises: - CircuitError: If param_dict contains parameters not present in the circuit + CircuitError: If parameters is a dict and contains parameters not present in the + circuit. + ValueError: If parameters is a list/array and the length mismatches the number of free + parameters in the circuit. Returns: Optional(QuantumCircuit): A copy of the circuit with bound parameters, if @@ -1953,41 +1973,56 @@ def assign_parameters(self, param_dict, inplace=False): # replace in self or in a copy depending on the value of in_place bound_circuit = self if inplace else self.copy() - # unroll the parameter dictionary (needed if e.g. it contains a ParameterVector) - unrolled_param_dict = self._unroll_param_dict(param_dict) + if isinstance(parameters, dict): + # unroll the parameter dictionary (needed if e.g. it contains a ParameterVector) + unrolled_param_dict = self._unroll_param_dict(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 self.parameters] - if len(params_not_in_circuit) > 0: - raise CircuitError('Cannot bind parameters ({}) not present in the circuit.'.format( - ', '.join(map(str, params_not_in_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) + # 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 self.parameters] + if len(params_not_in_circuit) > 0: + raise CircuitError('Cannot bind parameters ({}) not present in the circuit.'.format( + ', '.join(map(str, params_not_in_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) + else: + if len(parameters) != self.num_parameters: + raise ValueError('Mismatching number of values and parameters. For partial binding ' + 'please pass a dictionary of {parameter: value} pairs.') + for i, value in enumerate(parameters): + bound_circuit._assign_parameter(self.parameters[i], value) return None if inplace else bound_circuit - def bind_parameters(self, value_dict): + @deprecate_arguments({'value_dict': 'values'}) + def bind_parameters(self, values, value_dict=None): # pylint: disable=unused-argument """Assign numeric parameters to values yielding a new circuit. To assign new Parameter objects or bind the values in-place, without yielding a new circuit, use the :meth:`assign_parameters` method. Args: - value_dict (dict): {parameter: value, ...} + values (dict or iterable): {parameter: value, ...} or [value1, value2, ...] + value_dict (dict): Deprecated, use ``values`` instead. Raises: - CircuitError: If value_dict contains parameters not present in the circuit - TypeError: If value_dict contains a ParameterExpression in the values. + CircuitError: If values is a dict and contains parameters not present in the circuit. + TypeError: If values contains a ParameterExpression. Returns: QuantumCircuit: copy of self with assignment substitution. """ - if any(isinstance(value, ParameterExpression) for value in value_dict.values()): - raise TypeError('Found ParameterExpression in values; use assign_parameters() instead.') - return self.assign_parameters(value_dict) + if isinstance(values, dict): + if any(isinstance(value, ParameterExpression) for value in values.values()): + raise TypeError( + 'Found ParameterExpression in values; use assign_parameters() instead.') + return self.assign_parameters(values) + else: + if any(isinstance(value, ParameterExpression) for value in values): + raise TypeError( + 'Found ParameterExpression in values; use assign_parameters() instead.') + return self.assign_parameters(values) def _unroll_param_dict(self, value_dict): unrolled_value_dict = {} @@ -2625,3 +2660,21 @@ def _circuit_from_qasm(qasm): ast = qasm.parse() dag = ast_to_dag(ast) return dag_to_circuit(dag) + + +def _standard_compare(value1, value2): + if value1 < value2: + return -1 + if value1 > value2: + return 1 + return 0 + + +def _compare_parameters(param1, param2): + 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) + + # else sort by name + return _standard_compare(param1.name, param2.name) diff --git a/qiskit/converters/circuit_to_gate.py b/qiskit/converters/circuit_to_gate.py index a2b0893a53e6..21a6235b07b4 100644 --- a/qiskit/converters/circuit_to_gate.py +++ b/qiskit/converters/circuit_to_gate.py @@ -67,7 +67,7 @@ def circuit_to_gate(circuit, parameter_map=None, equivalence_library=None, label gate = Gate(name=circuit.name, num_qubits=sum([qreg.size for qreg in circuit.qregs]), - params=sorted(parameter_dict.values(), key=lambda p: p.name), + params=[*parameter_dict.values()], label=label) gate.condition = None diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index d2eaca6d4487..9e840d029d56 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -74,7 +74,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None instruction = Instruction(name=circuit.name, num_qubits=sum([qreg.size for qreg in circuit.qregs]), num_clbits=sum([creg.size for creg in circuit.cregs]), - params=sorted(parameter_dict.values(), key=lambda p: p.name)) + params=[*parameter_dict.values()]) instruction.condition = None target = circuit.assign_parameters(parameter_dict, inplace=False) diff --git a/releasenotes/notes/sorted-circuit-parameters-7607dd7ef4e6cc0e.yaml b/releasenotes/notes/sorted-circuit-parameters-7607dd7ef4e6cc0e.yaml new file mode 100644 index 000000000000..bf1bb44d8f9b --- /dev/null +++ b/releasenotes/notes/sorted-circuit-parameters-7607dd7ef4e6cc0e.yaml @@ -0,0 +1,26 @@ +--- +features: + - | + Return unbound parameters in a circuit in an order sorted by insertion and + allow to bind/assign parameters by a list of values or new parameters instead of + only by a dictionary. For example + + .. parsed-literal:: + + from qiskit.circuit import QuantumCircuit, Parameter + + circuit = QuantumCircuit(1) + circuit.rx(Parameter('x'), 0) + circuit.ry(Parameter('y'), 0) + + print(circuit.parameters) # ParameterView([x, y]) + + bound = circuit.bind_parameters([1, 2]) +deprecations: + - | + The ``QuantumCircuit.parameters`` method returns a sorted object instead of set. + All set methods on the return object are still available, but deprecated. + - | + The positional arguments ``param_dict`` in ``QuantumCircuit.assign_parameters`` is + deprecated in favor of ``parameters`` and ``value_dict`` in ``QuantumCircuit.bind_parameters`` + for ``values``. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 16637dc61afa..9b4cb848493a 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -26,6 +26,7 @@ from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import (Gate, Instruction, Parameter, ParameterExpression, ParameterVector) +from qiskit.circuit.parametertable import ParameterView from qiskit.circuit.exceptions import CircuitError from qiskit.compiler import assemble, transpile from qiskit.execute_function import execute @@ -139,6 +140,129 @@ def test_get_parameters(self): self.assertIs(theta, next(iter(vparams))) self.assertEqual(rxg, vparams[theta][0][0]) + def test_get_parameters_by_index(self): + """Test getting parameters by index""" + x = Parameter('x') + y = Parameter('y') + z = Parameter('z') + v = ParameterVector('v', 3) + qc = QuantumCircuit(1) + qc.rx(x, 0) + qc.rz(z, 0) + qc.ry(y, 0) + qc.u(*v, 0) + self.assertEqual(x, qc.parameters[3]) + self.assertEqual(y, qc.parameters[4]) + self.assertEqual(z, qc.parameters[5]) + for i, vi in enumerate(v): + self.assertEqual(vi, qc.parameters[i]) + + def test_bind_parameters_anonymously(self): + """Test setting parameters by insertion order anonymously""" + phase = Parameter('phase') + x = Parameter('x') + y = Parameter('y') + z = Parameter('z') + v = ParameterVector('v', 3) + qc = QuantumCircuit(1, global_phase=phase) + qc.rx(x, 0) + qc.rz(z, 0) + qc.ry(y, 0) + qc.u(*v, 0) + params = [0.1 * i for i in range(len(qc.parameters))] + + order = [phase] + v[:] + [x, y, z] + param_dict = dict(zip(order, params)) + for assign_fun in ['bind_parameters', 'assign_parameters']: + with self.subTest(assign_fun=assign_fun): + bqc_anonymous = getattr(qc, assign_fun)(params) + bqc_list = getattr(qc, assign_fun)(param_dict) + self.assertEqual(bqc_anonymous, bqc_list) + + def test_parameter_order(self): + """Test the parameters are sorted by name but parameter vector order takes precedence. + + This means that the following set of parameters + + {a, z, x[0], x[1], x[2], x[3], x[10], x[11]} + + will be sorted as + + [a, x[0], x[1], x[2], x[3], x[10], x[11], z] + + """ + a, b, some_name, z = [Parameter(name) for name in ['a', 'b', 'some_name', 'z']] + x = ParameterVector('x', 12) + a_vector = ParameterVector('a_vector', 15) + + qc = QuantumCircuit(2) + qc.p(z, 0) + for i, x_i in enumerate(reversed(x)): + qc.rx(x_i, i % 2) + qc.cry(a, 0, 1) + qc.crz(some_name, 1, 0) + for v_i in a_vector[::2]: + qc.p(v_i, 0) + for v_i in a_vector[1::2]: + qc.p(v_i, 1) + qc.p(b, 0) + + expected_order = [a] + a_vector[:] + [b, some_name] + x[:] + [z] + actual_order = qc.parameters + + self.assertListEqual(expected_order, list(actual_order)) + + @data(True, False) + def test_parameter_order_compose(self, front): + """Test the parameter order is correctly maintained upon composing circuits.""" + x = Parameter('x') + y = Parameter('y') + qc1 = QuantumCircuit(1) + qc1.p(x, 0) + qc2 = QuantumCircuit(1) + qc2.rz(y, 0) + + order = [x, y] + + composed = qc1.compose(qc2, front=front) + + self.assertListEqual(list(composed.parameters), order) + + def test_parameter_order_append(self): + """Test the parameter order is correctly maintained upon appending circuits.""" + x = Parameter('x') + y = Parameter('y') + qc1 = QuantumCircuit(1) + qc1.p(x, 0) + qc2 = QuantumCircuit(1) + qc2.rz(y, 0) + + qc1.append(qc2, [0]) + + self.assertListEqual(list(qc1.parameters), [x, y]) + + def test_parameter_order_composing_nested_circuit(self): + """Test the parameter order after nesting circuits and instructions.""" + x = ParameterVector('x', 5) + inner = QuantumCircuit(1) + inner.rx(x[0], [0]) + + mid = QuantumCircuit(2) + mid.p(x[1], 1) + mid.append(inner, [0]) + mid.p(x[2], 0) + mid.append(inner, [0]) + + outer = QuantumCircuit(2) + outer.compose(mid, inplace=True) + outer.ryy(x[3], 0, 1) + outer.compose(inner, inplace=True) + outer.rz(x[4], 0) + + order = [x[0], x[1], x[2], x[3], x[4]] + + self.assertListEqual(list(outer.parameters), order) + def test_is_parameterized(self): """Test checking if a gate is parameterized (bound/unbound)""" from qiskit.circuit.library.standard_gates.h import HGate @@ -588,20 +712,25 @@ def test_instruction_ryrz_vector(self): for param in vec: self.assertIn(param, qc_aer.parameters) - def test_parameter_equality_through_serialization(self): + @data('single', 'vector') + def test_parameter_equality_through_serialization(self, ptype): """Verify parameters maintain their equality after serialization.""" - x = Parameter('x') - x1 = Parameter('x') + if ptype == 'single': + x1 = Parameter('x') + x2 = Parameter('x') + else: + x1 = ParameterVector('x', 2)[0] + x2 = ParameterVector('x', 2)[0] - x_p = pickle.loads(pickle.dumps(x)) x1_p = pickle.loads(pickle.dumps(x1)) + x2_p = pickle.loads(pickle.dumps(x2)) - self.assertEqual(x, x_p) self.assertEqual(x1, x1_p) + self.assertEqual(x2, x2_p) - self.assertNotEqual(x, x1_p) - self.assertNotEqual(x1, x_p) + self.assertNotEqual(x1, x2_p) + self.assertNotEqual(x2, x1_p) def test_binding_parameterized_circuits_built_in_multiproc(self): """Verify subcircuits built in a subprocess can still be bound.""" @@ -1359,7 +1488,7 @@ def test_to_instruction_expression_parameter_map(self, target_type, order): elif target_type == 'instruction': gate = qc1.to_instruction(parameter_map={theta: theta_p, phi: phi_p}) - self.assertEqual(gate.params, [phi_p, theta_p]) + self.assertListEqual(gate.params, [theta_p, phi_p]) delta = Parameter('delta') qr2 = QuantumRegister(3, name='qr2') @@ -1367,7 +1496,7 @@ def test_to_instruction_expression_parameter_map(self, target_type, order): qc2.ry(delta, qr2[0]) qc2.append(gate, qargs=[qr2[1]]) - self.assertEqual(qc2.parameters, {delta, theta_p, phi_p}) + self.assertListEqual(list(qc2.parameters), [delta, phi_p, theta_p]) binds = {delta: 1, theta_p: 2, phi_p: 3} expected_qc = QuantumCircuit(qr2) @@ -1584,5 +1713,61 @@ def test_parameter_symbol_equal_after_ufunc(self): self.assertEqual(phi._parameter_symbols, cos_phi._parameter_symbols) +class TestParameterView(QiskitTestCase): + """Test the ParameterView object.""" + + def setUp(self): + super().setUp() + x, y, z = Parameter('x'), Parameter('y'), Parameter('z') + self.params = [x, y, z] + self.view1 = ParameterView([x, y]) + self.view2 = ParameterView([y, z]) + self.view3 = ParameterView([x]) + + def test_and(self): + """Test __and__.""" + self.assertEqual(self.view1 & self.view2, {self.params[1]}) + + def test_or(self): + """Test __or__.""" + self.assertEqual(self.view1 | self.view2, set(self.params)) + + def test_xor(self): + """Test __xor__.""" + self.assertEqual(self.view1 ^ self.view2, {self.params[0], self.params[2]}) + + def test_len(self): + """Test __len__.""" + self.assertEqual(len(self.view1), 2) + + def test_le(self): + """Test __le__.""" + self.assertTrue(self.view1 <= self.view1) + self.assertFalse(self.view1 <= self.view3) + + def test_lt(self): + """Test __lt__.""" + self.assertTrue(self.view3 < self.view1) + + def test_ge(self): + """Test __ge__.""" + self.assertTrue(self.view1 >= self.view1) + self.assertFalse(self.view3 >= self.view1) + + def test_gt(self): + """Test __lt__.""" + self.assertTrue(self.view1 > self.view3) + + def test_eq(self): + """Test __eq__.""" + self.assertTrue(self.view1 == self.view1) + self.assertFalse(self.view3 == self.view1) + + def test_ne(self): + """Test __eq__.""" + self.assertTrue(self.view1 != self.view2) + self.assertFalse(self.view3 != self.view3) + + if __name__ == '__main__': unittest.main()