Repository: PennyLaneAI/pennylane
Pull request #5465 (2024-04-02 23:50:02)

Add algorithmic errors tracking to `qml.Tracker`
**Context:** Update `qml.Tracker` to track and combine error

**Description of the Change:** Add algorithmic error tracking in the `simulator_tracking` device modifier.

**Benefits:** `tracker` will track algorithmic errors. 

**Possible Drawbacks:** N/A

**Related GitHub Issues:** N/A

obliviateandsurrender: [sc-55445]
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5465?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.67%. Comparing base [(`1a2895d`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/1a2895d2cc912b38e8fd38c473d009c06eeeab13?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`8b2a76f`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5465?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 1 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5465      +/-   ##
==========================================
- Coverage   99.68%   99.67%   -0.01%     
==========================================
  Files         402      402              
  Lines       37541    37264     -277     
==========================================
- Hits        37421    37143     -278     
- Misses        120      121       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5465?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index ecda32c06..6cba45117 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -209,6 +209,10 @@
   [stim](https://github.com/quantumlib/Stim) `v1.13.0`.
   [(#5409)](https://github.com/PennyLaneAI/pennylane/pull/5409)
 
+* `qml.specs` and `qml.Tracker` now return information about algorithmic errors for the qnode as well.
+  [(#5464)](https://github.com/PennyLaneAI/pennylane/pull/5464)
+  [(#5465)](https://github.com/PennyLaneAI/pennylane/pull/5465)
+
 * `qml.specs` now returns information regarding algorithmic errors for the qnode as well.
   [(#5464)](https://github.com/PennyLaneAI/pennylane/pull/5464)
 
diff --git a/pennylane/devices/modifiers/simulator_tracking.py b/pennylane/devices/modifiers/simulator_tracking.py
index 72f86d935..0ebde382e 100644
--- a/pennylane/devices/modifiers/simulator_tracking.py
+++ b/pennylane/devices/modifiers/simulator_tracking.py
@@ -46,6 +46,7 @@ def _track_execute(untracked_execute):
                         results=r,
                         shots=shots,
                         resources=c.specs["resources"],
+                        errors=c.specs["errors"],
                     )
                 else:
                     self.tracker.update(
@@ -53,6 +54,7 @@ def _track_execute(untracked_execute):
                         executions=qpu_executions,
                         results=r,
                         resources=c.specs["resources"],
+                        errors=c.specs["errors"],
                     )
                 self.tracker.record()
         return results
@@ -85,7 +87,7 @@ def _track_execute_and_compute_derivatives(untracked_execute_and_compute_derivat
         if self.tracker.active:
             batch = (circuits,) if isinstance(circuits, QuantumScript) else circuits
             for c in batch:
-                self.tracker.update(resources=c.specs["resources"])
+                self.tracker.update(resources=c.specs["resources"], errors=c.specs["errors"])
             self.tracker.update(
                 execute_and_derivative_batches=1,
                 executions=len(batch),
@@ -119,7 +121,7 @@ def _track_execute_and_compute_jvp(untracked_execute_and_compute_jvp):
         if self.tracker.active:
             batch = (circuits,) if isinstance(circuits, QuantumScript) else circuits
             for c in batch:
-                self.tracker.update(resources=c.specs["resources"])
+                self.tracker.update(resources=c.specs["resources"], errors=c.specs["errors"])
             self.tracker.update(execute_and_jvp_batches=1, executions=len(batch), jvps=len(batch))
             self.tracker.record()
 
@@ -153,7 +155,7 @@ def _track_execute_and_compute_vjp(untracked_execute_and_compute_vjp):
         if self.tracker.active:
             batch = (circuits,) if isinstance(circuits, QuantumScript) else circuits
             for c in batch:
-                self.tracker.update(resources=c.specs["resources"])
+                self.tracker.update(resources=c.specs["resources"], errors=c.specs["errors"])
             self.tracker.update(execute_and_vjp_batches=1, executions=len(batch), vjps=len(batch))
             self.tracker.record()
         return untracked_execute_and_compute_vjp(self, circuits, cotangents, execution_config)
@@ -176,6 +178,7 @@ def simulator_tracking(cls: type) -> type:
     * ``executions``: the number of unique circuits that would be required on quantum hardware
     * ``shots``: the number of shots
     * ``resources``: the :class:`~.resource.Resources` for the executed circuit.
+    * ``"errors"``: combined algorithmic errors from the quantum operations executed by the qnode.
     * ``simulations``: the number of simulations performed. One simulation can cover multiple QPU executions,
       such as for non-commuting measurements and batched parameters.
     * ``batches``: The number of times :meth:`~pennylane.devices.Device.execute` is called.
@@ -218,7 +221,8 @@ def simulator_tracking(cls: type) -> type:
     'shots': [100],
     'resources': [Resources(num_wires=1, num_gates=1, gate_types=defaultdict(<class 'int'>, {'S': 1}),
     gate_sizes=defaultdict(<class 'int'>, {1: 1}), depth=1, shots=Shots(total_shots=50,
-    shot_vector=(ShotCopies(50 shots x 1),)))]}
+    shot_vector=(ShotCopies(50 shots x 1),)))],
+    'errors': {}}
 
     """
     if not issubclass(cls, Device):
diff --git a/pennylane/tracker.py b/pennylane/tracker.py
index 5b07867fe..24aeda55d 100644
--- a/pennylane/tracker.py
+++ b/pennylane/tracker.py
@@ -82,7 +82,9 @@ class Tracker:
                             gate_types=defaultdict(<class 'int'>, {'RX': 1}),
                             gate_sizes=defaultdict(<class 'int'>, {1: 1}),
                             depth=1,
-                            shots=Shots(total_shots=100, shot_vector=(ShotCopies(100 shots x 1),)))}
+                            shots=Shots(total_shots=100, shot_vector=(ShotCopies(100 shots x 1),))),
+     'errors': {}
+    }
     >>> tracker.history.keys()
     dict_keys(['batches', 'simulations', 'executions', 'results', 'shots', 'resources'])
     >>> tracker.history['results']


Test patch:
diff --git a/tests/devices/default_qubit/test_default_qubit_tracking.py b/tests/devices/default_qubit/test_default_qubit_tracking.py
index 3975a4214..ff52c6e72 100644
--- a/tests/devices/default_qubit/test_default_qubit_tracking.py
+++ b/tests/devices/default_qubit/test_default_qubit_tracking.py
@@ -59,6 +59,7 @@ class TestTracking:
             "resources": [Resources(num_wires=1), Resources(num_wires=1), Resources(num_wires=1)],
             "derivative_batches": [1],
             "derivatives": [1],
+            "errors": [{}, {}, {}],
         }
         assert tracker.totals == {
             "batches": 2,
@@ -73,6 +74,7 @@ class TestTracking:
             "simulations": 1,
             "results": 1,
             "resources": Resources(num_wires=1),
+            "errors": {},
         }
 
     def test_tracking_execute_and_derivatives(self):
@@ -103,6 +105,7 @@ class TestTracking:
             "vjp_batches": [1],
             "execute_and_vjp_batches": [1],
             "resources": [Resources(num_wires=1)] * 12,
+            "errors": [{}] * 12,
         }
 
     def test_tracking_resources(self):
diff --git a/tests/devices/modifiers/test_all_modifiers.py b/tests/devices/modifiers/test_all_modifiers.py
index 69a40f3cf..fa315dbbc 100644
--- a/tests/devices/modifiers/test_all_modifiers.py
+++ b/tests/devices/modifiers/test_all_modifiers.py
@@ -44,7 +44,7 @@ def test_chained_modifiers():
     # result unwrapped
     assert out == 0.0
 
-    assert len(dev.tracker.history) == 6
+    assert len(dev.tracker.history) == 7
     assert dev.tracker.history["batches"] == [1]
     assert dev.tracker.history["simulations"] == [1]
     assert dev.tracker.history["executions"] == [1]
diff --git a/tests/devices/modifiers/test_simulator_tracking.py b/tests/devices/modifiers/test_simulator_tracking.py
index 2d889c2d4..88109eed8 100644
--- a/tests/devices/modifiers/test_simulator_tracking.py
+++ b/tests/devices/modifiers/test_simulator_tracking.py
@@ -44,7 +44,7 @@ def test_tracking_execute():
         out = dev.execute((tape1, tape2))
 
     assert out == ((0.0, 0.0), 0.0)
-    assert len(dev.tracker.history) == 6
+    assert len(dev.tracker.history) == 7
     assert dev.tracker.history["batches"] == [1]
     assert dev.tracker.history["simulations"] == [1, 1]
     assert dev.tracker.history["executions"] == [2, 2]
diff --git a/tests/devices/test_default_clifford.py b/tests/devices/test_default_clifford.py
index 9ceb1fdb4..1ff88630e 100644
--- a/tests/devices/test_default_clifford.py
+++ b/tests/devices/test_default_clifford.py
@@ -484,6 +484,7 @@ def test_tracker():
         "batches": [1, 1],
         "simulations": [1, 1],
         "executions": [1, 1],
+        "errors": [{}, {}],
     }
 
 
diff --git a/tests/devices/test_null_qubit.py b/tests/devices/test_null_qubit.py
index 775ae7add..507fc9f2e 100644
--- a/tests/devices/test_null_qubit.py
+++ b/tests/devices/test_null_qubit.py
@@ -106,6 +106,7 @@ def test_tracking():
             )
         ]
         * 13,
+        "errors": [{}] * 13,
     }
 
 
diff --git a/tests/resource/test_error/test_error.py b/tests/resource/test_error/test_error.py
index d1b171bab..4b690e49a 100644
--- a/tests/resource/test_error/test_error.py
+++ b/tests/resource/test_error/test_error.py
@@ -282,3 +282,16 @@ class TestSpecAndTracker:
         assert algo_errors["MultiplicativeError"].error == 0.31 * 0.24
         assert algo_errors["AdditiveError"].error == 0.73 + 0.12
         assert algo_errors["SpectralNormError"].error == 0.25 + 0.17998560822421455
+
+    def test_tracker(self):
+        """Test that tracker are tracking errors as expected."""
+
+        with qml.Tracker(self.dev) as tracker:
+            self.circuit()
+
+        algo_errors = tracker.latest["errors"]
+        assert len(algo_errors) == 3
+        assert all(error in algo_errors for error in self.errors_types)
+        assert algo_errors["MultiplicativeError"].error == 0.31 * 0.24
+        assert algo_errors["AdditiveError"].error == 0.73 + 0.12
+        assert algo_errors["SpectralNormError"].error == 0.25 + 0.17998560822421455