From 63536d97dd3217c84b522d83d5cb3307015e1b59 Mon Sep 17 00:00:00 2001 From: Pietropaolo Frisoni Date: Wed, 20 Nov 2024 11:51:40 -0500 Subject: [PATCH] [BugFix] `KeyError` when running `QNode(qfun, dev)(graph)` with graph an instance of `networkx` Graph object (#6600) **Context:** Issue #6585 indicates that we don't currently allow arguments with types defined in libraries not listed in the supported interfaces. In #6225, the convention of treating internally as `interface=None` parameters of the `numpy` interface has been introduced. The idea of this PR is to allow non-trainable `qnode` inputs to "just work" as arguments, without any special consideration or treatment. We want to rely on the default `numpy` interface for libraries we know nothing about. **Description of the Change:** By default, we set `None` as the value of the interface if the latter is not found in the list of supported interfaces. According to the convention introduced in #6225, this should automatically map the interface to `numpy`. **Benefits:** Now the `qnode` accepts arguments with types defined in libraries that are not necessarily in the list of supported interfaces, such as the `Graph` class defined in `networkx`. **Possible Drawbacks:** None that I can think of. **Related GitHub Issues:** #6585 **Related Shortcut Stories:** [sc-78344] --------- Co-authored-by: Christina Lee --- doc/releases/changelog-dev.md | 9 +++++--- pennylane/workflow/execution.py | 4 ++-- pennylane/workflow/qnode.py | 6 ++--- tests/test_qnode.py | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index e85622b060d..f5440c6436c 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -45,7 +45,6 @@ * Added `qml.devices.qubit_mixed` module for mixed-state qubit device support [(#6379)](https://github.com/PennyLaneAI/pennylane/pull/6379). This module introduces an `apply_operation` helper function that features: - * Two density matrix contraction methods using `einsum` and `tensordot` * Optimized handling of special cases including: Diagonal operators, Identity operators, CX (controlled-X), Multi-controlled X gates, Grover operators @@ -154,7 +153,6 @@ following 4 sets of functions have been either moved or removed[(#6588)](https:/ * The `expand_depth` argument for `qml.compile` has been removed. [(#6531)](https://github.com/PennyLaneAI/pennylane/pull/6531) - * The `qml.shadows.shadow_expval` transform has been removed. Instead, please use the `qml.shadow_expval` measurement process. [(#6530)](https://github.com/PennyLaneAI/pennylane/pull/6530) @@ -187,14 +185,19 @@ same information. [(#6549)](https://github.com/PennyLaneAI/pennylane/pull/6549)

Documentation 📝

+ * Add reporting of test warnings as failures. [(#6217)](https://github.com/PennyLaneAI/pennylane/pull/6217) -* Add a warning message to Gradients and training documentation about ComplexWarnings +* Add a warning message to Gradients and training documentation about ComplexWarnings. [(#6543)](https://github.com/PennyLaneAI/pennylane/pull/6543)

Bug fixes 🐛

+* `qml.QNode` now accepts arguments with types defined in libraries that are not necessarily + in the list of supported interfaces, such as the `Graph` class defined in `networkx`. + [(#6600)](https://github.com/PennyLaneAI/pennylane/pull/6600) + * `qml.math.get_deep_interface` now works properly for autograd arrays. [(#6557)](https://github.com/PennyLaneAI/pennylane/pull/6557) diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index f8bf84b218c..6c23cb3af94 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -19,7 +19,7 @@ # pylint: disable=import-outside-toplevel,too-many-branches,not-callable,unexpected-keyword-arg # pylint: disable=unused-argument,unnecessary-lambda-assignment,inconsistent-return-statements # pylint: disable=invalid-unary-operand-type,isinstance-second-argument-not-valid-type -# pylint: disable=too-many-arguments,too-many-statements,function-redefined,too-many-function-args +# pylint: disable=too-many-arguments,too-many-statements,function-redefined,too-many-function-args,too-many-positional-arguments import inspect import logging @@ -269,7 +269,7 @@ def _get_interface_name(tapes, interface): params.extend(tape.get_parameters(trainable_only=False)) interface = qml.math.get_interface(*params) if interface != "numpy": - interface = INTERFACE_MAP[interface] + interface = INTERFACE_MAP.get(interface, None) if interface == "tf" and _use_tensorflow_autograph(): interface = "tf-autograph" if interface == "jax": diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 34838579aaa..8c0fdeacd6f 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -14,7 +14,7 @@ """ This module contains the QNode class and qnode decorator. """ -# pylint: disable=too-many-instance-attributes,too-many-arguments,protected-access,unnecessary-lambda-assignment, too-many-branches, too-many-statements, unused-argument +# pylint: disable=too-many-instance-attributes,too-many-arguments,protected-access,unnecessary-lambda-assignment, too-many-branches, too-many-statements, unused-argument, too-many-positional-arguments import copy import functools import inspect @@ -75,7 +75,7 @@ def _convert_to_interface(res, interface): "tf-autograph": "tensorflow", } - interface_name = interface_conversion_map[interface] + interface_name = interface_conversion_map.get(interface, None) return qml.math.asarray(res, like=interface_name) @@ -984,7 +984,7 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: else qml.math.get_interface(*args, *list(kwargs.values())) ) if interface != "numpy": - interface = INTERFACE_MAP[interface] + interface = INTERFACE_MAP.get(interface, None) self._interface = interface try: diff --git a/tests/test_qnode.py b/tests/test_qnode.py index f5a15e6de3c..bfd4b9e2501 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -1030,6 +1030,47 @@ def circuit(x): res = circuit(x) assert qml.math.get_interface(res) == "numpy" + def test_qnode_default_interface(self): + """Tests that the default interface is set correctly for a QNode.""" + + # pylint: disable=import-outside-toplevel + import networkx as nx + + @qml.qnode(qml.device("default.qubit")) + def circuit(graph: nx.Graph): + for a in graph.nodes: + qml.Hadamard(wires=a) + for a, b in graph.edges: + qml.CZ(wires=[a, b]) + return qml.expval(qml.PauliZ(0)) + + graph = nx.complete_graph(3) + res = circuit(graph) + assert qml.math.get_interface(res) == "numpy" + + def test_qscript_default_interface(self): + """Tests that the default interface is set correctly for a QuantumScript.""" + + # pylint: disable=import-outside-toplevel + import networkx as nx + + dev = qml.device("default.qubit") + + # pylint: disable=too-few-public-methods + class DummyCustomGraphOp(qml.operation.Operation): + """Dummy custom operation for testing purposes.""" + + def __init__(self, graph: nx.Graph): + super().__init__(graph, wires=graph.nodes) + + def decomposition(self) -> list: + return [] + + graph = nx.complete_graph(3) + tape = qml.tape.QuantumScript([DummyCustomGraphOp(graph)], [qml.expval(qml.PauliZ(0))]) + res = qml.execute([tape], dev) + assert qml.math.get_interface(res) == "numpy" + class TestShots: """Unit tests for specifying shots per call."""