mirror of https://github.com/Qiskit/qiskit.git
Change Parameter equality from python id() to internal uuid(). (#2947)
Parameters were defined such that they would only ever __eq__ self. That way, if a user defined a Parameter('theta') and wanted to compose in a subcircuit defined elsewhere which contained a different Parameter('theta'), we would be able to detect that the parameter names overlapped and raise an error. However, if a user sent a Parameter to a different python instance though (say through multiprocessing.Pool or qiskit.tools.parallel_map), it would be instantiated as a different python object and so no longer be considered equal to the original parameter. This PR changes Parameter to instead generate a random UUID on instantiation and use that for equality testing. Unlike id(self), self._uuid will be preserved across pickling and de-pickling, but should otherwise preserve Parameter's equality behavior. Fixes #2429 Fixes #2864
This commit is contained in:
parent
85ec21f6db
commit
bb2ea373b2
|
@ -145,6 +145,9 @@ The format is based on [Keep a Changelog].
|
|||
- Correctly serialize complex numbers with a nonzero real part
|
||||
- Fixed bug in measure sampling for `BasicAer` Qasm simulator if only a
|
||||
subset of qubits are measured (\#2790)
|
||||
- Parameter objects can be serialized and communicated between
|
||||
sub-processes (\#2429)
|
||||
- Parameterized circuits no longer need to be transpiled individually (\#2864)
|
||||
|
||||
|
||||
## [0.8.2] - 2019-06-14
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
Parameter Class for variable parameters.
|
||||
"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import sympy
|
||||
|
||||
from .parameterexpression import ParameterExpression
|
||||
|
@ -22,6 +24,27 @@ from .parameterexpression import ParameterExpression
|
|||
|
||||
class Parameter(ParameterExpression):
|
||||
"""Parameter Class for variable parameters"""
|
||||
|
||||
def __new__(cls, _, uuid=None):
|
||||
# Parameter relies on self._uuid being set prior to other attributes
|
||||
# (e.g. symbol_map) which may depend on self._uuid for Parameter's hash
|
||||
# or __eq__ functions.
|
||||
|
||||
obj = object.__new__(cls)
|
||||
|
||||
if uuid is None:
|
||||
obj._uuid = uuid4()
|
||||
else:
|
||||
obj._uuid = uuid
|
||||
|
||||
return obj
|
||||
|
||||
def __getnewargs__(self):
|
||||
# Unpickling won't in general call __init__ but will always call
|
||||
# __new__. Specify arguments to be passed to __new__ when unpickling.
|
||||
|
||||
return (self.name, self._uuid)
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
|
@ -48,3 +71,9 @@ class Parameter(ParameterExpression):
|
|||
|
||||
def __repr__(self):
|
||||
return '{}({})'.format(self.__class__.__name__, self.name)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Parameter) and self._uuid == other._uuid
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._uuid)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
"""Test circuits with variable parameters."""
|
||||
|
||||
import pickle
|
||||
from operator import add, sub, mul, truediv
|
||||
|
||||
import numpy
|
||||
|
@ -21,9 +22,10 @@ import numpy
|
|||
from qiskit import BasicAer
|
||||
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
|
||||
from qiskit.circuit import Gate, Parameter, ParameterVector, ParameterExpression
|
||||
from qiskit.compiler import transpile
|
||||
from qiskit.compiler import assemble
|
||||
from qiskit.compiler import assemble, transpile
|
||||
from qiskit.execute import execute
|
||||
from qiskit.test import QiskitTestCase
|
||||
from qiskit.tools import parallel_map
|
||||
from qiskit.exceptions import QiskitError
|
||||
|
||||
|
||||
|
@ -296,6 +298,84 @@ class TestParameters(QiskitTestCase):
|
|||
for param in vec:
|
||||
self.assertIn(param, qc_aer.parameters)
|
||||
|
||||
def test_parameter_equality_through_serialization(self):
|
||||
"""Verify parameters maintain their equality after serialization."""
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
x = Parameter('x')
|
||||
x1 = Parameter('x')
|
||||
|
||||
x_p = pickle.loads(pickle.dumps(x))
|
||||
x1_p = pickle.loads(pickle.dumps(x1))
|
||||
|
||||
self.assertEqual(x, x_p)
|
||||
self.assertEqual(x1, x1_p)
|
||||
|
||||
self.assertNotEqual(x, x1_p)
|
||||
self.assertNotEqual(x1, x_p)
|
||||
|
||||
def test_binding_parameterized_circuits_built_in_multiproc(self):
|
||||
"""Verify subcircuits built in a subprocess can still be bound."""
|
||||
# ref: https://github.com/Qiskit/qiskit-terra/issues/2429
|
||||
|
||||
num_processes = 4
|
||||
|
||||
qr = QuantumRegister(3)
|
||||
cr = ClassicalRegister(3)
|
||||
|
||||
circuit = QuantumCircuit(qr, cr)
|
||||
parameters = [Parameter('x{}'.format(i))
|
||||
for i in range(num_processes)]
|
||||
|
||||
results = parallel_map(_construct_circuit,
|
||||
[(param) for param in parameters],
|
||||
task_args=(qr,),
|
||||
num_processes=num_processes)
|
||||
|
||||
for qc in results:
|
||||
circuit += qc
|
||||
|
||||
parameter_values = [{x: 1 for x in parameters}]
|
||||
|
||||
qobj = assemble(circuit,
|
||||
backend=BasicAer.get_backend('qasm_simulator'),
|
||||
parameter_binds=parameter_values)
|
||||
|
||||
self.assertEqual(len(qobj.experiments), 1)
|
||||
self.assertEqual(len(qobj.experiments[0].instructions), 4)
|
||||
self.assertTrue(all(len(inst.params) == 1
|
||||
and isinstance(inst.params[0], ParameterExpression)
|
||||
and float(inst.params[0]) == 1
|
||||
for inst in qobj.experiments[0].instructions))
|
||||
|
||||
def test_transpiling_multiple_parameterized_circuits(self):
|
||||
"""Verify several parameterized circuits can be transpiled at once."""
|
||||
# ref: https://github.com/Qiskit/qiskit-terra/issues/2864
|
||||
|
||||
qr = QuantumRegister(1)
|
||||
qc1 = QuantumCircuit(qr)
|
||||
qc2 = QuantumCircuit(qr)
|
||||
|
||||
theta = Parameter('theta')
|
||||
|
||||
qc1.u3(theta, 0, 0, qr[0])
|
||||
qc2.u3(theta, 3.14, 0, qr[0])
|
||||
|
||||
circuits = [qc1, qc2]
|
||||
|
||||
job = execute(circuits,
|
||||
BasicAer.get_backend('unitary_simulator'),
|
||||
shots=512,
|
||||
parameter_binds=[{theta: 1}])
|
||||
|
||||
self.assertTrue(len(job.result().results), 2)
|
||||
|
||||
|
||||
def _construct_circuit(param, qr):
|
||||
qc = QuantumCircuit(qr)
|
||||
qc.ry(param, qr[0])
|
||||
return qc
|
||||
|
||||
|
||||
class TestParameterExpressions(QiskitTestCase):
|
||||
"""Test expressions of Parameters."""
|
||||
|
|
Loading…
Reference in New Issue