From 238f38a52b84a066dcf681059910b1a80eabb1dd Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 19 Dec 2023 18:02:07 +0000 Subject: [PATCH] Remove `QuantumCircuit.bind_parameters` (#11434) * Remove `QuantumCircuit.bind_parameters` This expires the deprecation on `bind_parameters` in favour of `assign_parameters`, which is a drop-in replacement with its defaults. * Fix lint --- qiskit/circuit/quantumcircuit.py | 35 --- ...move-bind-parameters-13da38072c28a881.yaml | 6 + test/python/circuit/test_parameters.py | 218 +++++++----------- 3 files changed, 86 insertions(+), 173 deletions(-) create mode 100644 releasenotes/notes/remove-bind-parameters-13da38072c28a881.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 91cfac5c4264..15a77a35c25e 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3163,41 +3163,6 @@ def _unroll_param_dict( out[parameter] = value return out - @deprecate_func(additional_msg=("Use assign_parameters() instead"), since="0.45.0") - def bind_parameters( - self, values: Union[Mapping[Parameter, float], Sequence[float]] - ) -> "QuantumCircuit": - """Assign numeric parameters to values yielding a new circuit. - - If the values are given as list or array they are bound to the circuit in the order - of :attr:`parameters` (see the docstring for more details). - - To assign new Parameter objects or bind the values in-place, without yielding a new - circuit, use the :meth:`assign_parameters` method. - - Args: - values: ``{parameter: value, ...}`` or ``[value1, value2, ...]`` - - Raises: - CircuitError: If values is a dict and contains parameters not present in the circuit. - TypeError: If values contains a ParameterExpression. - - Returns: - Copy of self with assignment substitution. - """ - 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 barrier(self, *qargs: QubitSpecifier, label=None) -> InstructionSet: """Apply :class:`~.library.Barrier`. If ``qargs`` is empty, applies to all qubits in the circuit. diff --git a/releasenotes/notes/remove-bind-parameters-13da38072c28a881.yaml b/releasenotes/notes/remove-bind-parameters-13da38072c28a881.yaml new file mode 100644 index 000000000000..7e2552d8c0df --- /dev/null +++ b/releasenotes/notes/remove-bind-parameters-13da38072c28a881.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The method ``QuantumCircuit.bind_parameters`` has been removed, following its deprecation in + Qiskit 0.45. You can use :meth:`.QuantumCircuit.assign_parameters` as a drop-in replacement + with all its defaults, and it also exposes additional features over the old method. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 78b67c6bc4be..5c6e9a15bdf9 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -12,7 +12,6 @@ """Test circuits with variable parameters.""" import unittest -import warnings import cmath import math import copy @@ -109,14 +108,6 @@ def raise_if_parameter_table_invalid(circuit): class TestParameters(QiskitTestCase): """Test Parameters.""" - def setUp(self): - super().setUp() - # TODO: delete once bind_parameters is removed from the codebase - # and related tests are also removed. - warnings.filterwarnings( - "ignore", category=DeprecationWarning, module=r"test\.python\.circuit\.test_parameters" - ) - def test_gate(self): """Test instantiating gate with variable parameters""" theta = Parameter("θ") @@ -189,11 +180,9 @@ def test_bind_parameters_anonymously(self): 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) + bqc_anonymous = qc.assign_parameters(params) + bqc_list = qc.assign_parameters(param_dict) + self.assertEqual(bqc_anonymous, bqc_list) def test_bind_parameters_allow_unknown(self): """Test binding parameters allowing unknown parameters.""" @@ -202,15 +191,14 @@ def test_bind_parameters_allow_unknown(self): 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): + def test_bind_parameters_custom_definition_global_phase(self): """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})) + test = Operator(base.assign_parameters({x: math.pi})) expected = Operator(numpy.array([[-1, 0], [0, -1]])) self.assertEqual(test, expected) @@ -336,15 +324,12 @@ def test_fix_variable(self): qc.rx(theta, qr) qc.u(0, theta, 0, qr) - # test for both `bind_parameters` and `assign_parameters` - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - bqc = getattr(qc, assign_fun)({theta: 0.5}) - self.assertEqual(float(bqc.data[0].operation.params[0]), 0.5) - self.assertEqual(float(bqc.data[1].operation.params[1]), 0.5) - bqc = getattr(qc, assign_fun)({theta: 0.6}) - self.assertEqual(float(bqc.data[0].operation.params[0]), 0.6) - self.assertEqual(float(bqc.data[1].operation.params[1]), 0.6) + bqc = qc.assign_parameters({theta: 0.5}) + self.assertEqual(float(bqc.data[0].operation.params[0]), 0.5) + self.assertEqual(float(bqc.data[1].operation.params[1]), 0.5) + bqc = qc.assign_parameters({theta: 0.6}) + self.assertEqual(float(bqc.data[0].operation.params[0]), 0.6) + self.assertEqual(float(bqc.data[1].operation.params[1]), 0.6) def test_multiple_parameters(self): """Test setting multiple parameters""" @@ -393,15 +378,12 @@ def test_partial_binding(self): qc.rx(theta, qr) qc.u(0, theta, x, qr) - # test for both `bind_parameters` and `assign_parameters` - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - pqc = getattr(qc, assign_fun)({theta: 2}) + pqc = qc.assign_parameters({theta: 2}) - self.assertEqual(pqc.parameters, {x}) + self.assertEqual(pqc.parameters, {x}) - self.assertEqual(float(pqc.data[0].operation.params[0]), 2) - self.assertEqual(float(pqc.data[1].operation.params[1]), 2) + self.assertEqual(float(pqc.data[0].operation.params[0]), 2) + self.assertEqual(float(pqc.data[1].operation.params[1]), 2) @data(True, False) def test_mixed_binding(self, inplace): @@ -429,21 +411,18 @@ def test_expression_partial_binding(self): qc = QuantumCircuit(qr) qc.rx(theta + phi, qr) - # test for both `bind_parameters` and `assign_parameters` - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - pqc = getattr(qc, assign_fun)({theta: 2}) + pqc = qc.assign_parameters({theta: 2}) - self.assertEqual(pqc.parameters, {phi}) + self.assertEqual(pqc.parameters, {phi}) - self.assertTrue(isinstance(pqc.data[0].operation.params[0], ParameterExpression)) - self.assertEqual(str(pqc.data[0].operation.params[0]), "phi + 2") + self.assertTrue(isinstance(pqc.data[0].operation.params[0], ParameterExpression)) + self.assertEqual(str(pqc.data[0].operation.params[0]), "phi + 2") - fbqc = getattr(pqc, assign_fun)({phi: 1.0}) + fbqc = pqc.assign_parameters({phi: 1.0}) - self.assertEqual(fbqc.parameters, set()) - self.assertIsInstance(fbqc.data[0].operation.params[0], float) - self.assertEqual(float(fbqc.data[0].operation.params[0]), 3) + self.assertEqual(fbqc.parameters, set()) + self.assertIsInstance(fbqc.data[0].operation.params[0], float) + self.assertEqual(float(fbqc.data[0].operation.params[0]), 3) def test_two_parameter_expression_binding(self): """Verify that for a circuit with parameters theta and phi that @@ -473,21 +452,18 @@ def test_expression_partial_binding_zero(self): qc = QuantumCircuit(1) qc.p(theta * phi, 0) - # test for both `bind_parameters` and `assign_parameters` - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - pqc = getattr(qc, assign_fun)({theta: 0}) + pqc = qc.assign_parameters({theta: 0}) - self.assertEqual(pqc.parameters, {phi}) + self.assertEqual(pqc.parameters, {phi}) - self.assertTrue(isinstance(pqc.data[0].operation.params[0], ParameterExpression)) - self.assertEqual(str(pqc.data[0].operation.params[0]), "0") + self.assertTrue(isinstance(pqc.data[0].operation.params[0], ParameterExpression)) + self.assertEqual(str(pqc.data[0].operation.params[0]), "0") - fbqc = getattr(pqc, assign_fun)({phi: 1}) + fbqc = pqc.assign_parameters({phi: 1}) - self.assertEqual(fbqc.parameters, set()) - self.assertIsInstance(fbqc.data[0].operation.params[0], int) - self.assertEqual(float(fbqc.data[0].operation.params[0]), 0) + self.assertEqual(fbqc.parameters, set()) + self.assertIsInstance(fbqc.data[0].operation.params[0], int) + self.assertEqual(float(fbqc.data[0].operation.params[0]), 0) def test_raise_if_assigning_params_not_in_circuit(self): """Verify binding parameters which are not present in the circuit raises an error.""" @@ -496,21 +472,20 @@ def test_raise_if_assigning_params_not_in_circuit(self): z = ParameterVector("z", 3) qr = QuantumRegister(1) - # test for both `bind_parameters` and `assign_parameters` - for assign_fun in ["bind_parameters", "assign_parameters"]: - qc = QuantumCircuit(qr) - with self.subTest(assign_fun=assign_fun): - # TODO: delete once bind_parameters is removed from the codebase - warnings.filterwarnings("ignore", category=DeprecationWarning) - - qc.p(0.1, qr[0]) - self.assertRaises(CircuitError, getattr(qc, assign_fun), {x: 1}) - qc.p(x, qr[0]) - self.assertRaises(CircuitError, getattr(qc, assign_fun), {x: 1, y: 2}) - qc.p(z[1], qr[0]) - self.assertRaises(CircuitError, getattr(qc, assign_fun), {z: [3, 4, 5]}) - self.assertRaises(CircuitError, getattr(qc, assign_fun), {"a_str": 6}) - self.assertRaises(CircuitError, getattr(qc, assign_fun), {None: 7}) + qc = QuantumCircuit(qr) + qc.p(0.1, qr[0]) + with self.assertRaises(CircuitError): + qc.assign_parameters({x: 1}) + qc.p(x, qr[0]) + with self.assertRaises(CircuitError): + qc.assign_parameters({x: 1, y: 2}) + qc.p(z[1], qr[0]) + with self.assertRaises(CircuitError): + qc.assign_parameters({z: [3, 4, 5]}) + with self.assertRaises(CircuitError): + qc.assign_parameters({"a_str": 6}) + with self.assertRaises(CircuitError): + qc.assign_parameters({None: 7}) def test_gate_multiplicity_binding(self): """Test binding when circuit contains multiple references to same gate""" @@ -520,13 +495,10 @@ def test_gate_multiplicity_binding(self): gate = RZGate(theta) qc.append(gate, [0], []) qc.append(gate, [0], []) - # test for both `bind_parameters` and `assign_parameters` - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - qc2 = getattr(qc, assign_fun)({theta: 1.0}) - self.assertEqual(len(qc2._parameter_table), 0) - for instruction in qc2.data: - self.assertEqual(float(instruction.operation.params[0]), 1.0) + qc2 = qc.assign_parameters({theta: 1.0}) + self.assertEqual(len(qc2._parameter_table), 0) + for instruction in qc2.data: + self.assertEqual(float(instruction.operation.params[0]), 1.0) def test_calibration_assignment(self): """That that calibration mapping and the schedules they map are assigned together.""" @@ -673,16 +645,14 @@ def test_circuit_generation(self): qc_aer = transpile(qc, backend) # generate list of circuits - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - circs = [] - theta_list = numpy.linspace(0, numpy.pi, 20) - for theta_i in theta_list: - circs.append(getattr(qc_aer, assign_fun)({theta: theta_i})) - qobj = assemble(circs) - for index, theta_i in enumerate(theta_list): - res = float(qobj.experiments[index].instructions[0].params[0]) - self.assertTrue(math.isclose(res, theta_i), f"{res} != {theta_i}") + circs = [] + theta_list = numpy.linspace(0, numpy.pi, 20) + for theta_i in theta_list: + circs.append(qc_aer.assign_parameters({theta: theta_i})) + qobj = assemble(circs) + for index, theta_i in enumerate(theta_list): + res = float(qobj.experiments[index].instructions[0].params[0]) + self.assertTrue(math.isclose(res, theta_i), f"{res} != {theta_i}") def test_circuit_composition(self): """Test preservation of parameters when combining circuits.""" @@ -747,12 +717,10 @@ def test_bind_ryrz_vector(self): qc.barrier() theta_vals = numpy.linspace(0, 1, len(theta)) * numpy.pi self.assertEqual(set(qc.parameters), set(theta.params)) - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - bqc = getattr(qc, assign_fun)({theta: theta_vals}) - for instruction in bqc.data: - if hasattr(instruction.operation, "params") and instruction.operation.params: - self.assertIn(float(instruction.operation.params[0]), theta_vals) + bqc = qc.assign_parameters({theta: theta_vals}) + for instruction in bqc.data: + if hasattr(instruction.operation, "params") and instruction.operation.params: + self.assertIn(float(instruction.operation.params[0]), theta_vals) def test_compile_vector(self): """Test compiling a circuit with an unbound ParameterVector""" @@ -963,10 +931,8 @@ def test_repeated_gates_to_dag_and_back(self): double_qc = qc.compose(qc) test_qc = dag_to_circuit(circuit_to_dag(double_qc)) - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - bound_test_qc = getattr(test_qc, assign_fun)({theta: 1}) - self.assertEqual(len(bound_test_qc.parameters), 0) + bound_test_qc = test_qc.assign_parameters({theta: 1}) + self.assertEqual(len(bound_test_qc.parameters), 0) def test_rebinding_instruction_copy(self): """Test rebinding a copied instruction does not modify the original.""" @@ -980,19 +946,17 @@ def test_rebinding_instruction_copy(self): qc1 = QuantumCircuit(1) qc1.append(instr, [0]) - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - output1 = getattr(qc1, assign_fun)({theta: 0.1}).decompose() - output2 = getattr(qc1, assign_fun)({theta: 0.2}).decompose() + output1 = qc1.assign_parameters({theta: 0.1}).decompose() + output2 = qc1.assign_parameters({theta: 0.2}).decompose() - expected1 = QuantumCircuit(1) - expected1.rx(0.1, 0) + expected1 = QuantumCircuit(1) + expected1.rx(0.1, 0) - expected2 = QuantumCircuit(1) - expected2.rx(0.2, 0) + expected2 = QuantumCircuit(1) + expected2.rx(0.2, 0) - self.assertEqual(expected1, output1) - self.assertEqual(expected2, output2) + self.assertEqual(expected1, output1) + self.assertEqual(expected2, output2) def test_sign_of_parameter(self): """Test returning the sign of the value of the parameter""" @@ -1117,13 +1081,11 @@ def test_executing_parameterized_instruction_bound_early(self, target_type): unbound_qc.append(sub_inst, [0, 1], []) unbound_qc.measure(0, 0) - for assign_fun in ["bind_parameters", "assign_parameters"]: - with self.subTest(assign_fun=assign_fun): - bound_qc = getattr(unbound_qc, assign_fun)({theta: numpy.pi / 2}) + bound_qc = unbound_qc.assign_parameters({theta: numpy.pi / 2}) - shots = 1024 - job = execute(bound_qc, backend=BasicAer.get_backend("qasm_simulator"), shots=shots) - self.assertDictAlmostEqual(job.result().get_counts(), {"1": shots}, 0.05 * shots) + shots = 1024 + job = execute(bound_qc, backend=BasicAer.get_backend("qasm_simulator"), shots=shots) + self.assertDictAlmostEqual(job.result().get_counts(), {"1": shots}, 0.05 * shots) def test_num_parameters(self): """Test the num_parameters property.""" @@ -1263,7 +1225,7 @@ def test_compile_with_ufunc(self): for x in theta: self.assertIn(x, transpiled.parameters) - bound = transpiled.bind_parameters({theta: [-1, pi, pi, pi, 1, 1, 1]}) + bound = transpiled.assign_parameters({theta: [-1, pi, pi, pi, 1, 1, 1]}) expected = QuantumCircuit(7) expected.rx(1.0, 0) @@ -1341,14 +1303,6 @@ class TestParameterExpressions(QiskitTestCase): pow: 1e-12, } - def setUp(self): - super().setUp() - # TODO: delete once bind_parameters is removed from the codebase - # and related tests are also removed. - warnings.filterwarnings( - "ignore", category=DeprecationWarning, module=r"test\.python\.circuit\.test_parameters" - ) - def test_compare_to_value_when_bound(self): """Verify expression can be compared to a fixed value when fully bound.""" @@ -1545,7 +1499,7 @@ def test_complex_parameter_bound_to_real(self): with self.subTest("simple 1j * x"): qc = QuantumCircuit(1) qc.rx(1j * x, 0) - bound = qc.bind_parameters({x: 1j}) + bound = qc.assign_parameters({x: 1j}) ref = QuantumCircuit(1) ref.rx(-1, 0) self.assertEqual(bound, ref) @@ -1553,7 +1507,7 @@ def test_complex_parameter_bound_to_real(self): with self.subTest("more complex expression"): qc = QuantumCircuit(1) qc.rx(0.5j * x - y * y + 2 * y, 0) - bound = qc.bind_parameters({x: -4, y: 1j}) + bound = qc.assign_parameters({x: -4, y: 1j}) ref = QuantumCircuit(1) ref.rx(1, 0) self.assertEqual(bound, ref) @@ -1565,7 +1519,7 @@ def test_complex_angle_raises_when_not_supported(self): qc.r(x, 1j * x, 0) with self.subTest("binding x to 0 yields real parameters"): - bound = qc.bind_parameters({x: 0}) + bound = qc.assign_parameters({x: 0}) ref = QuantumCircuit(1) ref.r(0, 0, 0) self.assertEqual(bound, ref) @@ -1573,7 +1527,7 @@ def test_complex_angle_raises_when_not_supported(self): with self.subTest("binding x to 1 yields complex parameters"): # RGate does not support complex parameters with self.assertRaises(CircuitError): - bound = qc.bind_parameters({x: 1}) + bound = qc.assign_parameters({x: 1}) def test_operating_on_a_parameter_with_a_non_float_will_raise(self): """Verify operations between a Parameter and a non-float will raise.""" @@ -2301,17 +2255,5 @@ def test_ne(self): self.assertFalse(self.view3 != self.view3) -class TestBindParametersDeprecation(QiskitTestCase): - """Test deprecation of bind_parameters().""" - - def test_circuit_bind_parameters_raises(self): - """Test that the deprecated bind_parameters method raises a deprecation warning.""" - qc = QuantumCircuit(1) - qc.rx(Parameter("x"), 0) - - with self.assertWarns(DeprecationWarning): - _ = qc.bind_parameters([1]) - - if __name__ == "__main__": unittest.main()