Repository: PennyLaneAI/pennylane
Pull request #7904 (2025-07-17 15:04:32)

Deprecate unnecessary methods from `CircuitGraph`
The `CircuitGraph` class is currently unwieldy, with a lot of excess and duplicate methods. This PR aims to:

- [x] Turn `.print_contents` into `__str__`
- [x] Deprecate `.observables_in_order` in favor of only having `.observables`
- [x] Deprecate `.operations_in_order` in favor of only having `.operations`
- [x] Deprecate `.ancestors_in_order` in favor of `.ancestors(obj, sort=True)`
- [x] Deprecate `.descendants_in_order` in favor of `.descendants(obj, sort=True)`

[sc-94040]


Patch:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 3ca592201..46dae889e 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -9,6 +9,17 @@ deprecations are listed below.
 Pending deprecations
 --------------------
 
+* Some unnecessary methods of the ``qml.CircuitGraph`` class are deprecated and will be removed in version v0.44:
+
+    - ``print_contents`` in favor of ``print(obj)``
+    - ``observables_in_order`` in favor of ``observables``
+    - ``operations_in_order`` in favor of ``operations``
+    - ``ancestors_in_order`` in favor of ``ancestors(obj, sort=True)``
+    - ``descendants_in_order`` in favore of ``.descendants(obj, sort=True)``
+
+  - Deprecated in v0.43
+  - Will be removed in v0.44
+
 * The ``QuantumScript.to_openqasm`` method is deprecated and will be removed in version v0.44.
   Instead, the ``qml.to_openqasm`` function should be used.
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 9a44132d4..eabcf9b08 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -166,6 +166,15 @@
 
 <h3>Deprecations 👋</h3>
 
+* Some unnecessary methods of the `qml.CircuitGraph` class are deprecated and will be removed in version v0.44:
+  [(#7904)](https://github.com/PennyLaneAI/pennylane/pull/7904)
+
+    - `print_contents` in favor of `print(obj)`
+    - `observables_in_order` in favor of `observables`
+    - `operations_in_order` in favor of `operations`
+    - `ancestors_in_order` in favor of `ancestors(obj, sort=True)`
+    - `descendants_in_order` in favore of `descendants(obj, sort=True)`
+
 * The `QuantumScript.to_openqasm` method is deprecated and will be removed in version v0.44.
   Instead, the `qml.to_openqasm` function should be used.
   [(#7909)](https://github.com/PennyLaneAI/pennylane/pull/7909)
diff --git a/pennylane/circuit_graph.py b/pennylane/circuit_graph.py
index 7ccb10c1b..d6ea5c61c 100644
--- a/pennylane/circuit_graph.py
+++ b/pennylane/circuit_graph.py
@@ -15,6 +15,7 @@
 This module contains the CircuitGraph class which is used to generate a DAG (directed acyclic graph)
 representation of a quantum circuit from an Operator queue.
 """
+import warnings
 from collections import defaultdict, namedtuple
 from collections.abc import Sequence
 from functools import cached_property
@@ -22,6 +23,7 @@ from functools import cached_property
 import numpy as np
 import rustworkx as rx
 
+from pennylane.exceptions import PennyLaneDeprecationWarning
 from pennylane.measurements import MeasurementProcess
 from pennylane.operation import Operator
 from pennylane.ops.identity import I
@@ -124,18 +126,25 @@ class CircuitGraph:
         # observables per wire
         self._max_simultaneous_measurements = None
 
-    def print_contents(self):
-        """Prints the contents of the quantum circuit."""
+    def __str__(self):
+        """The string representation of the class."""
+        string = "Operations\n==========\n"
+        string += "\n".join(repr(op) for op in self.operations)
 
-        print("Operations")
-        print("==========")
-        for op in self.operations:
-            print(repr(op))
+        string += "\n\nObservables\n===========\n"
+        string += "\n".join(repr(obs) for obs in self.observables)
+        string += "\n"
 
-        print("\nObservables")
-        print("===========")
-        for op in self.observables:
-            print(repr(op))
+        return string
+
+    def print_contents(self):
+        """Prints the contents of the quantum circuit."""
+        warnings.warn(
+            "``CircuitGraph.print_contents`` is deprecated and will be removed in v0.44. "
+            "Instead, please use ``print(circuit_graph_obj)``.",
+            PennyLaneDeprecationWarning,
+        )
+        print(self)
 
     def serialize(self) -> str:
         """Serialize the quantum circuit graph based on the operations and
@@ -150,7 +159,7 @@ class CircuitGraph:
         serialization_string = ""
         delimiter = "!"
 
-        for op in self.operations_in_order:
+        for op in self.operations:
             serialization_string += op.name
 
             for param in op.data:
@@ -164,7 +173,7 @@ class CircuitGraph:
         # name of the operation and wires
         serialization_string += "|||"
 
-        for mp in self.observables_in_order:
+        for mp in self.observables:
             obs = mp.obs or mp
             data, name = ([], "Identity") if obs is mp else (obs.data, str(obs.name))
             serialization_string += mp.__class__.__name__
@@ -198,6 +207,11 @@ class CircuitGraph:
         Returns:
             list[Union[MeasurementProcess, Operator]]: observables
         """
+        warnings.warn(
+            "``CircuitGraph.observables_in_order`` is deprecated and will be removed in v0.44. "
+            "Instead, please use ``CircuitGraph.observables``",
+            PennyLaneDeprecationWarning,
+        )
         return self._observables
 
     @property
@@ -217,6 +231,11 @@ class CircuitGraph:
         Returns:
             list[Operation]: operations
         """
+        warnings.warn(
+            "``CircuitGraph.operations_in_order`` is deprecated and will be removed in v0.44. "
+            "Instead, please use ``CircuitGraph.operations``",
+            PennyLaneDeprecationWarning,
+        )
         return self._operations
 
     @property
@@ -352,6 +371,11 @@ class CircuitGraph:
         Returns:
             list[Operator]: ancestors of the given operators, topologically ordered
         """
+        warnings.warn(
+            "``CircuitGraph.ancestors_in_order`` is deprecated and will be removed in v0.44. "
+            "Instead, please use ``CircuitGraph.ancestors(ops, sort=True)``",
+            PennyLaneDeprecationWarning,
+        )
         return self.ancestors(ops, sort=True)
 
     def descendants_in_order(self, ops):
@@ -365,6 +389,11 @@ class CircuitGraph:
         Returns:
             list[Operator]: descendants of the given operators, topologically ordered
         """
+        warnings.warn(
+            "``CircuitGraph.descendants_in_order`` is deprecated and will be removed in v0.44. "
+            "Instead, please use ``CircuitGraph.descendants(ops, sort=True)``",
+            PennyLaneDeprecationWarning,
+        )
         return self.descendants(ops, sort=True)
 
     def nodes_between(self, a, b):
diff --git a/pennylane/gradients/parameter_shift_cv.py b/pennylane/gradients/parameter_shift_cv.py
index 4b8448290..70f477dbb 100644
--- a/pennylane/gradients/parameter_shift_cv.py
+++ b/pennylane/gradients/parameter_shift_cv.py
@@ -379,7 +379,7 @@ def second_order_param_shift(tape, dev_wires, argnum=None, shifts=None, gradient
         B = np.eye(1 + 2 * len(dev_wires))
         B_inv = B.copy()
 
-        succ = tape.graph.descendants_in_order((op,))
+        succ = tape.graph.descendants((op,), sort=True)
         operation_descendents = itertools.filterfalse(
             lambda obj: isinstance(obj, MeasurementProcess), succ
         )


Test patch:
diff --git a/tests/circuit_graph/test_circuit_graph.py b/tests/circuit_graph/test_circuit_graph.py
index 28197c0ad..1ba528165 100644
--- a/tests/circuit_graph/test_circuit_graph.py
+++ b/tests/circuit_graph/test_circuit_graph.py
@@ -16,15 +16,13 @@ Unit tests for the :mod:`pennylane.circuit_graph` module.
 """
 # pylint: disable=no-self-use,too-many-arguments,protected-access
 
-import contextlib
-import io
-
 import numpy as np
 import pytest
 
 import pennylane as qml
 from pennylane import numpy as pnp
 from pennylane.circuit_graph import CircuitGraph
+from pennylane.exceptions import PennyLaneDeprecationWarning
 from pennylane.resource import Resources, ResourcesOperation
 from pennylane.wires import Wires
 
@@ -117,6 +115,7 @@ class CustomOpDepth4(ResourcesOperation):
         return Resources(num_wires=self.num_wires, depth=4)
 
 
+# pylint: disable=too-many-public-methods
 class TestCircuitGraph:
     """Test conversion of queues to DAGs"""
 
@@ -174,7 +173,8 @@ class TestCircuitGraph:
         assert descendants == [queue[8]]
         assert descendants_index == [queue[8]]
 
-    def test_ancestors_and_descendents_repeated_op(self):
+    @pytest.mark.parametrize("sort", [True, False])
+    def test_ancestors_and_descendents_repeated_op(self, sort):
         """Test ancestors and descendents raises a ValueError is the requested operation occurs more than once."""
 
         op = qml.X(0)
@@ -182,15 +182,12 @@ class TestCircuitGraph:
         graph = CircuitGraph(ops, [], qml.wires.Wires([0, 1, 2]))
 
         with pytest.raises(ValueError, match=r"operator that occurs multiple times."):
-            graph.ancestors([op])
-        with pytest.raises(ValueError, match=r"operator that occurs multiple times."):
-            graph.descendants([op])
+            graph.ancestors([op], sort=sort)
         with pytest.raises(ValueError, match=r"operator that occurs multiple times."):
-            graph.ancestors_in_order([op])
-        with pytest.raises(ValueError, match=r"operator that occurs multiple times."):
-            graph.descendants_in_order([op])
+            graph.descendants([op], sort=sort)
 
-    def test_ancestors_and_descendents_single_op_error(self):
+    @pytest.mark.parametrize("sort", [True, False])
+    def test_ancestors_and_descendents_single_op_error(self, sort):
         """Test ancestors and descendents raises a ValueError is the requested operation occurs more than once."""
 
         op = qml.Z(0)
@@ -199,19 +196,11 @@ class TestCircuitGraph:
         with pytest.raises(
             ValueError, match=r"CircuitGraph.ancestors accepts an iterable of operators"
         ):
-            graph.ancestors(op)
+            graph.ancestors(op, sort=sort)
         with pytest.raises(
             ValueError, match=r"CircuitGraph.descendants accepts an iterable of operators"
         ):
-            graph.descendants(op)
-        with pytest.raises(
-            ValueError, match=r"CircuitGraph.ancestors accepts an iterable of operators"
-        ):
-            graph.ancestors_in_order(op)
-        with pytest.raises(
-            ValueError, match=r"CircuitGraph.descendants accepts an iterable of operators"
-        ):
-            graph.descendants_in_order(op)
+            graph.descendants(op, sort=sort)
 
     def test_update_node(self, ops, obs):
         """Changing nodes in the graph."""
@@ -240,6 +229,42 @@ class TestCircuitGraph:
         """Test that the `operations` property returns the list of operations in the circuit."""
         assert str(circuit.operations) == str(ops)
 
+    def test_observables_in_order_deprecation(self, circuit, obs):
+        """Test that a deprecation warning is raised."""
+        with pytest.warns(
+            PennyLaneDeprecationWarning, match="``CircuitGraph.observables_in_order`` is deprecated"
+        ):
+            assert str(circuit.observables_in_order) == str(obs)
+
+    def test_operations_in_order_deprecation(self, circuit, ops):
+        """Test that a deprecation warning is raised."""
+        with pytest.warns(
+            PennyLaneDeprecationWarning, match="``CircuitGraph.operations_in_order`` is deprecated"
+        ):
+            assert str(circuit.operations_in_order) == str(ops)
+
+    def test_ancestors_in_order_deprecation(self):
+        """Test that a deprecation warning is raised."""
+        op = qml.X(0)
+        ops = [op, qml.Y(0), qml.Z(0)]
+        graph = CircuitGraph(ops, [], qml.wires.Wires([0, 1, 2]))
+
+        with pytest.warns(
+            PennyLaneDeprecationWarning, match="``CircuitGraph.ancestors_in_order`` is deprecated"
+        ):
+            graph.ancestors_in_order([op])
+
+    def test_descendants_in_order_deprecation(self):
+        """Test that a deprecation warning is raised."""
+        op = qml.X(0)
+        ops = [op, qml.Y(0), qml.Z(0)]
+        graph = CircuitGraph(ops, [], qml.wires.Wires([0, 1, 2]))
+
+        with pytest.warns(
+            PennyLaneDeprecationWarning, match="``CircuitGraph.descendants_in_order`` is deprecated"
+        ):
+            graph.descendants_in_order([op])
+
     def test_op_indices(self, circuit):
         """Test that for the given circuit, this method will fetch the correct operation indices for
         a given wire"""
@@ -306,13 +331,13 @@ class TestCircuitGraph:
         result = list(circuit.iterate_parametrized_layers())
 
         assert len(result) == 3
-        assert set(result[0][0]) == set([])
+        assert set(result[0][0]) == set()
         assert set(result[0][1]) == set(circuit.operations[:3])
         assert result[0][2] == (0, 1, 2)
         assert set(result[0][3]) == set(circuit.operations[3:] + circuit.observables)
 
         assert set(result[1][0]) == set(circuit.operations[:2])
-        assert set(result[1][1]) == set([circuit.operations[3]])
+        assert set(result[1][1]) == {circuit.operations[3]}
         assert result[1][2] == (3,)
         assert set(result[1][3]) == set(circuit.operations[4:6] + circuit.observables[:2])
 
@@ -339,20 +364,26 @@ class TestCircuitGraph:
         circuit = tape.graph
         assert circuit.max_simultaneous_measurements == expected
 
-    def test_print_contents(self):
+    def test_str_print(self):
         """Tests if the circuit prints correct."""
         ops = [qml.Hadamard(wires=0), qml.CNOT(wires=[0, 1])]
         obs_w_wires = [qml.measurements.sample(op=None, wires=[0, 1, 2])]
 
         circuit_w_wires = CircuitGraph(ops, obs_w_wires, wires=Wires([0, 1, 2]))
+        expected = """Operations\n==========\nH(0)\nCNOT(wires=[0, 1])\n\nObservables\n===========\nsample(wires=[0, 1, 2])\n"""
+        assert str(circuit_w_wires) == expected
 
-        f = io.StringIO()
-        with contextlib.redirect_stdout(f):
-            circuit_w_wires.print_contents()
-        out = f.getvalue().strip()
+    def test_print_contents_deprecation(self):
+        """Test that a deprecation warning is raised."""
+        ops = [qml.Hadamard(wires=0), qml.CNOT(wires=[0, 1])]
+        obs_w_wires = [qml.measurements.sample(op=None, wires=[0, 1, 2])]
 
-        expected = """Operations\n==========\nH(0)\nCNOT(wires=[0, 1])\n\nObservables\n===========\nsample(wires=[0, 1, 2])"""
-        assert out == expected
+        circuit_w_wires = CircuitGraph(ops, obs_w_wires, wires=Wires([0, 1, 2]))
+
+        with pytest.warns(
+            PennyLaneDeprecationWarning, match="``CircuitGraph.print_contents`` is deprecated"
+        ):
+            circuit_w_wires.print_contents()
 
     tape_depth = (
         ([qml.PauliZ(0), qml.CNOT([0, 1]), qml.RX(1.23, 2)], 2),