-
-
Notifications
You must be signed in to change notification settings - Fork 144
/
executors.myst
185 lines (124 loc) · 5.62 KB
/
executors.myst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
---
jupytext:
text_representation:
extension: .myst
format_name: myst
format_version: 0.13
jupytext_version: 1.12.0
kernelspec:
display_name: Python 3 (ipykernel)
language: python
name: python3
---
# Executors
+++
Error mitigation methods can involve running many circuits. The `mitiq.Executor` class is a tool for efficiently running many circuits and storing the results.
```{code-cell} ipython3
from mitiq import Executor, Observable, PauliString, QPROGRAM, QuantumResult
```
## The input function
+++
To instantiate an `Executor`, provide a function which either:
1. Inputs a `mitiq.QPROGRAM` and outputs a `mitiq.QuantumResult`.
2. Inputs a sequence of `mitiq.QPROGRAM`s and outputs a sequence of `mitiq.QuantumResult`s.
**The function must be [annotated](https://www.python.org/dev/peps/pep-3107/) to tell Mitiq which type of `QuantumResult` it returns. Functions with no annotations are assumed to return `float`s.**
A `QPROGRAM` is "something which a quantum computer inputs" and a `QuantumResult` is "something which a quantum computer outputs." The latter is canonically a bitstring for real quantum hardware, but can be other objects for testing, e.g. a density matrix.
```{code-cell} ipython3
print(QPROGRAM)
```
```{code-cell} ipython3
print(QuantumResult)
```
## Creating an `Executor`
+++
The function `mitiq_cirq.compute_density_matrix` inputs a Cirq circuit and returns a density matrix as an `np.ndarray`.
```{code-cell} ipython3
import inspect
from mitiq.interface import mitiq_cirq
print(inspect.getfullargspec(mitiq_cirq.compute_density_matrix).annotations["return"])
```
We can instantiate an `Executor` with it as follows.
```{code-cell} ipython3
executor = Executor(mitiq_cirq.compute_density_matrix)
```
## Running circuits
+++
When first created, the executor hasn't been called yet and has no executed circuits and no computed results in memory.
```{code-cell} ipython3
print("Calls to executor:", executor.calls_to_executor)
print("\nExecuted circuits:\n", *executor.executed_circuits, sep="\n")
print("\nQuantum results:\n", *executor.quantum_results, sep="\n")
```
To run a circuit of sequence of circuits, use the `Executor.evaluate` method.
```{code-cell} ipython3
import cirq
q = cirq.GridQubit(0, 0)
circuit = cirq.Circuit(cirq.H.on(q))
obs = Observable(PauliString("Z"))
results = executor.evaluate(circuit, obs)
print("Results:", results)
```
The `executor` has now been called and has results in memory. Note that `mitiq_cirq.compute_density_matrix` simulates the circuit with noise by default, so the resulting state (density matrix) is noisy.
```{code-cell} ipython3
print("Calls to executor:", executor.calls_to_executor)
print("\nExecuted circuits:\n", *executor.executed_circuits, sep="\n")
print("\nQuantum results:\n", *executor.quantum_results, sep="\n")
```
The interface for running a sequence of circuits is the same.
```{code-cell} ipython3
circuits = [cirq.Circuit(pauli.on(q)) for pauli in (cirq.X, cirq.Y, cirq.Z)]
results = executor.evaluate(circuits, obs)
print("Results:", results)
```
In addition to the results of running these circuits we have the full history.
```{code-cell} ipython3
print("Calls to executor:", executor.calls_to_executor)
print("\nExecuted circuits:\n", *executor.executed_circuits, sep="\n")
print("\nQuantum results:\n", *executor.quantum_results, sep="\n")
```
### Batched execution
+++
Notice in the above output that the executor has been called once for each circuit it has executed. This is because `mitiq_cirq.compute_density_matrix` inputs one circuit and outputs one `QuantumResult`.
Several quantum computing services allow running a sequence, or "batch," of circuits at once. This is important for error mitigation when running many circuits to speed up the computation.
+++
To define a batched executor, annotate it with `Sequence[T]`, `List[T]`, `Tuple[T]`, or `Iterable[T]` where `T` is a `QuantumResult`. Here is an example:
```{code-cell} ipython3
from typing import List
import numpy as np
def batch_compute_density_matrix(circuits: List[cirq.Circuit]) -> List[np.ndarray]:
return [mitiq_cirq.compute_density_matrix(circuit) for circuit in circuits]
batched_executor = Executor(batch_compute_density_matrix, max_batch_size=10)
```
You can check if Mitiq detected the ability to batch as follows.
```{code-cell} ipython3
batched_executor.can_batch
```
Now when running a batch of circuits, the executor will be called as few times as possible.
```{code-cell} ipython3
circuits = [cirq.Circuit(pauli.on(q)) for pauli in (cirq.X, cirq.Y, cirq.Z)]
results = batched_executor.evaluate(circuits, obs)
print("Results:", results)
print("\nCalls to executor:", batched_executor.calls_to_executor)
print("\nExecuted circuits:\n", *batched_executor.executed_circuits, sep="\n")
print("\nQuantum results:\n", *batched_executor.quantum_results, sep="\n")
```
## Using `Executor`s in error mitigation techniques
+++
You can provide a function or an `Executor` to the `executor` argument of error mitigation techniques, but **providing an `Executor` is strongly recommended** for seeing the history of results.
```{code-cell} ipython3
from mitiq import zne
```
```{code-cell} ipython3
batched_executor = Executor(batch_compute_density_matrix, max_batch_size=10)
zne_value = zne.execute_with_zne(
cirq.Circuit(cirq.H.on(q) for _ in range(6)),
executor=batched_executor,
observable=obs
)
print(f"ZNE value: {zne_value :g}")
```
```{code-cell} ipython3
print("Calls to executor:", batched_executor.calls_to_executor)
print("\nExecuted circuits:\n", *batched_executor.executed_circuits, sep="\n")
print("\nQuantum results:\n", *batched_executor.quantum_results, sep="\n")
```