diff --git a/doc/releases/changelog-0.32.0.md b/doc/releases/changelog-0.32.0.md index b359a784a3b..72f3f9bc225 100644 --- a/doc/releases/changelog-0.32.0.md +++ b/doc/releases/changelog-0.32.0.md @@ -240,11 +240,17 @@ array([False, False]) [(#4465)](https://github.com/PennyLaneAI/pennylane/pull/4465/) [(#4478)](https://github.com/PennyLaneAI/pennylane/pull/4478/) +* The label for `ParametrizedEvolution` can display parameters with the requested format as set by the + kwarg `decimals`. Array-like parameters are displayed in the same format as matrices and stored in the + cache. + [(#4151)](https://github.com/PennyLaneAI/pennylane/pull/4151) + * CI now runs tests with Tensorflow 2.13.0 [(#4472)](https://github.com/PennyLaneAI/pennylane/pull/4472) * `draw_mpl` accepts `style='pennylane'` to draw PennyLane-style circuit diagrams, and `style.use` in `matplotlib.pyplot` accepts `pennylane.drawer.plot` to create PennyLane-style plots. If the font Quicksand Bold isn't available, an available default font is used instead. [(#3950)](https://github.com/PennyLaneAI/pennylane/pull/3950) +

Breaking changes 💔

* Applying gradient transforms to broadcasted/batched tapes was deactivated until it is consistently diff --git a/pennylane/pulse/parametrized_evolution.py b/pennylane/pulse/parametrized_evolution.py index 838a878dbe6..f1a4cea2177 100644 --- a/pennylane/pulse/parametrized_evolution.py +++ b/pennylane/pulse/parametrized_evolution.py @@ -505,6 +505,67 @@ def fun(y, t): mat = mat[-1] return qml.math.expand_matrix(mat, wires=self.wires, wire_order=wire_order) + def label(self, decimals=None, base_label=None, cache=None): + r"""A customizable string representation of the operator. + + Args: + decimals=None (int): If ``None``, no parameters are included. Else, + specifies how to round the parameters. + base_label=None (str): overwrite the non-parameter component of the label + cache=None (dict): dictionary that carries information between label calls + in the same drawing + + Returns: + str: label to use in drawings + + **Example:** + + >>> H = qml.PauliX(1) + qml.pulse.constant * qml.PauliY(0) + jnp.polyval * qml.PauliY(1) + >>> params = [0.2, [1, 2, 3]] + >>> op = qml.evolve(H)(params, t=2) + >>> cache = {'matrices': []} + + >>> op.label() + "Parametrized\nEvolution" + >>> op.label(decimals=2, cache=cache) + "Parametrized\nEvolution\n(p=[0.20,M0], t=[0. 2.])" + >>> op.label(base_label="my_label") + "my_label" + >>> op.label(decimals=2, base_label="my_label", cache=cache) + "my_label\n(p=[0.20,M0], t=[0. 2.])" + + Array-like parameters are stored in ``cache['matrices']``. + """ + op_label = base_label or "Parametrized\nEvolution" + + if self.num_params == 0: + return op_label + + if decimals is None: + return op_label + + params = self.parameters + has_cache = cache and isinstance(cache.get("matrices", None), list) + + if any(qml.math.ndim(p) for p in params) and not has_cache: + return op_label + + def _format_number(x): + return format(qml.math.toarray(x), f".{decimals}f") + + def _format_arraylike(x): + for i, mat in enumerate(cache["matrices"]): + if qml.math.shape(x) == qml.math.shape(mat) and qml.math.allclose(x, mat): + return f"M{i}" + mat_num = len(cache["matrices"]) + cache["matrices"].append(x) + return f"M{mat_num}" + + param_strings = [_format_arraylike(p) if p.shape else _format_number(p) for p in params] + + p = ",".join(s for s in param_strings) + return f"{op_label}\n(p=[{p}], t={self.t})" + @functions.bind_new_parameters.register def _bind_new_parameters_parametrized_evol(op: ParametrizedEvolution, params: Sequence[TensorLike]): diff --git a/tests/pulse/test_parametrized_evolution.py b/tests/pulse/test_parametrized_evolution.py index 7ebcfc57bda..29d153a9189 100644 --- a/tests/pulse/test_parametrized_evolution.py +++ b/tests/pulse/test_parametrized_evolution.py @@ -202,6 +202,7 @@ def test_update_attributes(self): H = ParametrizedHamiltonian(coeffs, ops) ev = ParametrizedEvolution(H=H, mxstep=10) + # pylint:disable = use-implicit-booleaness-not-comparison assert ev.parameters == [] assert ev.num_params == 0 assert ev.t is None @@ -304,6 +305,87 @@ def test_hash_with_data(self): assert compare_to.hash != diff_ret_intmdt.hash assert compare_to.hash != diff_complementary.hash + @pytest.mark.parametrize( + "params", + [ + [0.2, [1, 2, 3], [4, 5, 6, 7]], + [0.2, np.array([1, 2, 3]), np.array([4, 5, 6, 7])], + [0.2, (1, 2, 3), (4, 5, 6, 7)], + ], + ) + def test_label(self, params): + """Test that the label displays correctly with and without decimal and base_label""" + H = ( + qml.PauliX(1) + + qml.pulse.constant * qml.PauliY(0) + + np.polyval * qml.PauliY(1) + + np.polyval * qml.PauliY(1) + ) + op = qml.evolve(H)(params, 2) + cache = {"matrices": []} + + assert op.label() == "Parametrized\nEvolution" + assert op.label(decimals=2) == "Parametrized\nEvolution" + assert ( + op.label(decimals=2, cache=cache) + == "Parametrized\nEvolution\n(p=[0.20,M0,M1], t=[0. 2.])" + ) + assert op.label(base_label="my_label") == "my_label" + assert ( + op.label(base_label="my_label", decimals=2, cache=cache) + == "my_label\n(p=[0.20,M0,M1], t=[0. 2.])" + ) + + def test_label_no_params(self): + """Test that the label displays correctly with and without decimal and base_label""" + H = ( + qml.PauliX(1) + + qml.pulse.constant * qml.PauliY(0) + + np.polyval * qml.PauliY(1) + + np.polyval * qml.PauliY(1) + ) + op = qml.evolve(H) + cache = {"matrices": []} + + assert op.label() == "Parametrized\nEvolution" + assert op.label(decimals=2) == "Parametrized\nEvolution" + assert op.label(decimals=2, cache=cache) == "Parametrized\nEvolution" + assert op.label(base_label="my_label") == "my_label" + assert op.label(base_label="my_label", decimals=2, cache=cache) + + def test_label_reuses_cached_matrices(self): + """Test that the matrix is reused if it already exists in the cache, instead + of being added to the cache a second time""" + + H = ( + qml.PauliX(1) + + qml.pulse.constant * qml.PauliY(0) + + np.polyval * qml.PauliY(1) + + np.polyval * qml.PauliY(2) + ) + cache = {"matrices": []} + + params1 = [3, np.array([0.23, 0.47, 5]), np.array([3.4, 6.8])] + params2 = [5.67, np.array([0.23, 0.47, 5]), np.array([[3.7, 6.2], [1.2, 4.6]])] + op1 = qml.evolve(H)(params1, 2) + op2 = qml.evolve(H)(params2, 2) + + assert ( + op1.label(decimals=2, cache=cache) + == "Parametrized\nEvolution\n(p=[3.00,M0,M1], t=[0. 2.])" + ) + assert len(cache["matrices"]) == 2 + assert np.all(cache["matrices"][0] == params1[1]) + assert np.all(cache["matrices"][1] == params1[2]) + + assert ( + op2.label(decimals=2, cache=cache) + == "Parametrized\nEvolution\n(p=[5.67,M0,M2], t=[0. 2.])" + ) + assert len(cache["matrices"]) == 3 + assert np.all(cache["matrices"][0] == params2[1]) + assert np.all(cache["matrices"][2] == params2[2]) + def test_raises_wrong_number_of_params(self): """Test that an error is raised when instantiating (or calling) a ParametrizedEvolution with the wrong number of parameters."""