From ab0af2fdf201354a71a6afb7633ea7317de40c5b Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 19 Dec 2023 12:29:08 +0000 Subject: [PATCH] Allow string keys in `QuantumCircuit.assign_parameters` These are looked up using the new `get_parameter` method to convert them into an actual `Parameter` instance. This is not available in the fast path (which is allowed to assume that the caller has taken care of providing the most efficient inputs). --- qiskit/circuit/quantumcircuit.py | 21 +++++++++++++++---- .../assign-by-name-305f2bbf89099174.yaml | 14 +++++++++++++ test/python/circuit/test_parameters.py | 14 ++++++++++++- 3 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/assign-by-name-305f2bbf89099174.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 27c01d38bd22..51d785b05b59 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3075,7 +3075,7 @@ def assign_parameters( # pylint: disable=missing-raises-doc ) -> Optional["QuantumCircuit"]: """Assign parameters to new parameters or values. - If ``parameters`` is passed as a dictionary, the keys must be :class:`.Parameter` + If ``parameters`` is passed as a dictionary, the keys should be :class:`.Parameter` instances in the current circuit. The values of the dictionary can either be numeric values or new parameter objects. @@ -3085,6 +3085,16 @@ def assign_parameters( # pylint: disable=missing-raises-doc The values can be assigned to the current circuit object or to a copy of it. + .. note:: + When ``parameters`` is given as a mapping, it is permissible to have keys that are + strings of the parameter names; these will be looked up using :meth:`get_parameter`. + You can also have keys that are :class:`.ParameterVector` instances, and in this case, + the dictionary value should be a sequence of values of the same length as the vector. + + If you use either of these cases, you must leave the setting ``flat_input=False``; + changing this to ``True`` enables the fast path, where all keys must be + :class:`.Parameter` instances. + Args: 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. @@ -3092,7 +3102,9 @@ def assign_parameters( # pylint: disable=missing-raises-doc 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. + sequence of values, and these will be unrolled during the mapping, or string keys, + which will be converted to :class:`.Parameter` instances using + :meth:`get_parameter`. 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. @@ -3270,9 +3282,8 @@ def map_calibration(qubits, parameters, schedule): ) return None if inplace else target - @staticmethod def _unroll_param_dict( - parameter_binds: Mapping[Parameter, ParameterValueType] + self, parameter_binds: Mapping[Parameter, ParameterValueType] ) -> Mapping[Parameter, ParameterValueType]: out = {} for parameter, value in parameter_binds.items(): @@ -3283,6 +3294,8 @@ def _unroll_param_dict( f" but was assigned to {len(value)} values." ) out.update(zip(parameter, value)) + elif isinstance(parameter, str): + out[self.get_parameter(parameter)] = value else: out[parameter] = value return out diff --git a/releasenotes/notes/assign-by-name-305f2bbf89099174.yaml b/releasenotes/notes/assign-by-name-305f2bbf89099174.yaml new file mode 100644 index 000000000000..a675dd08be86 --- /dev/null +++ b/releasenotes/notes/assign-by-name-305f2bbf89099174.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + :meth:`.QuantumCircuit.assign_parameters` now accepts string keys in the mapping form of input. + These names are used to look up the corresponding :class:`.Parameter` instance using + :meth:`~.QuantumCircuit.get_parameter`. This lets you do:: + + from qiskit.circuit import QuantumCircuit, Parameter + + a = Parameter("a") + qc = QuantumCircuit(1) + qc.rx(a, 0) + + qc.assign_parameters({"a": 1}) == qc.assign_parameters({a: 1}) diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index a25082df0ab2..900f5641cffb 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -244,6 +244,18 @@ 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})) + def test_assign_parameters_by_name(self): + """Test that parameters can be assigned by name as well as value.""" + a = Parameter("a") + b = Parameter("b") + c = Parameter("c") + qc = QuantumCircuit(2, global_phase=a * 2) + qc.rx(b + 0.125 * c, 0) + + self.assertEqual( + qc.assign_parameters({a: 1, b: 2, c: 3}), qc.assign_parameters({"a": 1, "b": 2, "c": 3}) + ) + 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") @@ -536,7 +548,7 @@ def test_raise_if_assigning_params_not_in_circuit(self): with self.assertRaises(CircuitError): qc.assign_parameters({z: [3, 4, 5]}) with self.assertRaises(CircuitError): - qc.assign_parameters({"a_str": 6}) + qc.assign_parameters({6: 6}) with self.assertRaises(CircuitError): qc.assign_parameters({None: 7})