|
21 | 21 | construct, and simulate quantum circuits using a performant state-vector |
22 | 22 | simulator. |
23 | 23 |
|
24 | | -## Circuits and operations |
| 24 | +## Circuits and Operations |
25 | 25 | Begin with two necessary imports: The `Circuit` class, which will contain the |
26 | 26 | full quantum circuit with all the operations, measurements, qubits and bits, and |
27 | | -the operations module. The operations, or gates, contain information related to |
28 | | -a particular operation, including its matrix representation, potential |
29 | | -decompositions, and how to apply it to a circuit. |
| 27 | +the `operations` module. |
30 | 28 | """ |
31 | 29 |
|
32 | 30 | from dwave.gate.circuit import Circuit |
33 | 31 | import dwave.gate.operations as ops |
34 | 32 |
|
35 | 33 | ############################################################################### |
36 | | -# The `Circuit` keeps track of the operations and the logical flow of their |
37 | | -# excecutions. It also stores the qubits, bits and measurments. |
| 34 | +# The operations, or gates, contain information related to a particular operation, |
| 35 | +# including its matrix representation, potential decompositions, and how to apply |
| 36 | +# it to a circuit. |
38 | 37 | # |
39 | | -# When initializing a circuit, the number of qubits and (optionally) the number of |
40 | | -# bits, i.e., classical measurement results containers, need to be declared. More |
41 | | -# qubits and bits can be added using the `Circuit.add_qubit` and `Circuit.add_bit` |
| 38 | +# The `Circuit` keeps track of the operations and the logical |
| 39 | +# flow of their executions. It also stores the qubits, bits and measurements. |
| 40 | +# |
| 41 | +# When initializing a circuit, you need to declare the number of qubits and, |
| 42 | +# optionally, the number of bits (i.e., classical measurement results containers). |
| 43 | +# You can add more qubits and bits using the `Circuit.add_qubit` and `Circuit.add_bit` |
42 | 44 | # methods. |
43 | 45 |
|
44 | 46 | circuit = Circuit(num_qubits=2, num_bits=2) |
45 | 47 |
|
46 | 48 | ############################################################################### |
47 | 49 | # You can use the `operations` module (shortened above to `ops`) to access a |
48 | | -# variety of quantum gates; for example, a Pauli X operator. |
| 50 | +# variety of quantum gates; for example, the Pauli X operator. Note that you can |
| 51 | +# instantiate an operation without declaring which qubits it should be applied |
| 52 | +# to. |
49 | 53 |
|
50 | 54 | ops.X() |
51 | 55 |
|
52 | 56 | ############################################################################### |
53 | | -# Notice above that an operation can be instantiated without declaring which |
54 | | -# qubits it should be applied to. |
55 | | -# |
56 | | -# The matrix property can be accessed either via the operation class itself or an |
| 57 | +# You can access the matrix property via either the operation class itself or an |
57 | 58 | # instance of the operation, which additionally can contain parameters and qubit |
58 | 59 | # information. |
59 | 60 |
|
60 | 61 | ops.X.matrix |
61 | 62 |
|
62 | 63 | ############################################################################### |
63 | | -# If the matrix representation is dependent on parameters (e.g., the rotation operation `ops.RX`) |
64 | | -# it can only be retrieved from an instance. |
| 64 | +# If the matrix representation of an operator is dependent on parameters (e.g., |
| 65 | +# the rotation operation `ops.RX`) it can only be retrieved from an instance. |
65 | 66 |
|
66 | 67 | ops.RZ(4.2).matrix |
67 | 68 |
|
68 | 69 | ############################################################################### |
69 | | -# ## The circuit context |
| 70 | +# ## The Circuit Context |
70 | 71 | # |
71 | | -# Operations are applied by calling either a class or an instance of a class |
72 | | -# within the context of the circuit. |
| 72 | +# Operations are applied by calling either an operation class, or an instance of |
| 73 | +# an operation class, within the context of the circuit, passing along the |
| 74 | +# qubits on which the operation is applied. |
73 | 75 | # |
74 | 76 | # ```python |
75 | 77 | # with circuit.context: |
|
78 | 80 | # |
79 | 81 |
|
80 | 82 | ############################################################################### |
81 | | -# When activating the context, a named tuple containing reference registers to the |
82 | | -# circuit's qubits and classical bits is returned. You can also access the qubit |
83 | | -# registers directly via the `Circuit.qregisters` property, or the reference |
84 | | -# registers containing all the qubits via `Circuit.qubits`. |
| 83 | +# When you activate a context, a named tuple containing reference registers to |
| 84 | +# the circuit's qubits and classical bits is returned. You can also access the |
| 85 | +# qubit registers directly, via the `Circuit.qregisters` property, or the |
| 86 | +# reference registers containing all the qubits, via `Circuit.qubits`. |
85 | 87 | # |
86 | 88 | # In the example below, the circuit contains a single qubit register with two |
87 | 89 | # qubits; it could contain any number of qubit registers. You can |
88 | 90 | # |
89 | 91 | # * add another register with the `Circuit.add_qregister` method, where an argument `n` |
90 | | -# is the number of qubits in the new register |
| 92 | +# is the number of qubits in the new register. |
91 | 93 | # * add a qubit with `Circuit.add_qubit`, optionally passing a qubit object |
92 | 94 | # and/or a register to which to add it. |
93 | 95 |
|
|
97 | 99 | ops.X(reg.q[0]) |
98 | 100 |
|
99 | 101 | ############################################################################### |
100 | | -# This example created a circuit object with two qubits in its register, applying |
| 102 | +# This has created a circuit object with two qubits in its register, applying |
101 | 103 | # a single X gate to the first qubit. Print the circuit to see general information |
102 | 104 | # about it: type of circuit, number of qubits/bits, and number of operations. |
103 | 105 |
|
104 | 106 | print(circuit) |
105 | 107 |
|
106 | 108 | ############################################################################### |
107 | | -# ## Applying gates to circuits |
| 109 | +# ## Applying Gates to Circuits |
108 | 110 | # |
109 | 111 | # You can apply operations to a circuit in several different ways, as demonstrated |
110 | 112 | # in the example below. You can pass both qubits and parameters as either single |
111 | | -# values (when supported by the gate) or sequences. Note that different types of |
| 113 | +# values (when supported by the gate) or as sequences. Note that different types of |
112 | 114 | # gates accept slightly different arguments, although you can _always_ pass the |
113 | 115 | # qubits as sequences via the keyword argument `qubits`. |
114 | 116 | # |
115 | 117 | # :::note |
116 | 118 | # Always apply any operations you instantiate within a circuit context to |
117 | 119 | # specific qubits in the circuit's qubit register. You can access the qubit |
118 | | -# register via the named tuple returned by the context manager as `q`, indexing |
| 120 | +# register via the named tuple, returned by the context manager as `q`, indexing |
119 | 121 | # into it to retrieve the corresponding qubit. |
120 | 122 | # ::: |
121 | 123 |
|
|
147 | 149 |
|
148 | 150 | ############################################################################### |
149 | 151 | # You can access all the operations in a circuit using the `Circuit.circuit` |
150 | | -# property. The code below iterates over the returned list of all operations that |
151 | | -# have been applied to the circuit. |
| 152 | +# property. The code below iterates over a list of all operations that |
| 153 | +# have been applied to the circuit and prints each one separately. |
152 | 154 |
|
153 | 155 | for op in circuit.circuit: |
154 | 156 | print(op) |
|
161 | 163 | # ::: |
162 | 164 |
|
163 | 165 | ############################################################################### |
164 | | -# ## Simulating a circuit |
| 166 | +# ## Simulating a Circuit |
165 | 167 | # |
166 | | -# `dwave-gate` comes with a performant state-vector simulator. It can be called by passing a circuit to the `simulate` method, which will update the quatum state stored in the circuit, accessible via `Circuit.state`. |
| 168 | +# `dwave-gate` comes with a performant state-vector simulator. You can call it by |
| 169 | +# passing a circuit to the `simulate` method, which will update the quantum state |
| 170 | +# stored in the circuit, accessible via `Circuit.state`. |
167 | 171 |
|
168 | 172 | from dwave.gate.simulator import simulate |
169 | 173 |
|
170 | 174 | ############################################################################### |
171 | | -# We create a circuit object with 2 qubits and 1 bit in a quantum and classical registers respectively --- the bit is required to store a single qubit measurement --- and then apply a Hadamard gate and a CNOT gate to the circuit. |
| 175 | +# The following example creates a circuit object with 2 qubits and 1 bit in a |
| 176 | +# quantum and a classical register respectively---the bit is required to store a |
| 177 | +# single qubit measurement---and then applies a Hadamard gate and a CNOT gate to |
| 178 | +# the circuit. |
172 | 179 |
|
173 | 180 | circuit = Circuit(2, 1) |
174 | 181 |
|
|
177 | 184 | ops.CNOT(q[0], q[1]) |
178 | 185 |
|
179 | 186 | ############################################################################### |
180 | | -# We can now simulate the circuit, which will update its stored quantum state. |
| 187 | +# You can now simulate the circuit, updating its stored quantum state. |
181 | 188 |
|
182 | 189 | simulate(circuit) |
183 | 190 |
|
|
192 | 199 | ############################################################################### |
193 | 200 | # ## Measurements |
194 | 201 | # |
195 | | -# Measurements work like any other operation in dwave-gate. The main difference is that the operation generates a measurement value when simulated which can be stored in the classical register by piping it into a classical bit. |
| 202 | +# Measurements work like any other operation in `dwave-gate`. The main difference |
| 203 | +# is that the operation generates a measurement value when simulated, which can be |
| 204 | +# stored in the classical register by piping it into a classical bit. |
196 | 205 | # |
197 | | -# We can reuse the circuit from above by simply unlocking it and appending a `Measurement` to it. |
| 206 | +# The circuit above can be reused by simply unlocking it and appending a `Measurement` to it. |
198 | 207 |
|
199 | 208 | circuit.unlock() |
200 | 209 | with circuit.context as (q, c): |
201 | 210 | m = ops.Measurement(q[1]) | c[0] |
202 | 211 |
|
203 | 212 | ############################################################################### |
204 | 213 | # :::note |
205 | | -# We stored the measurement instance as `m`, which we can use for post-processing. It's also possible to do this with all other operations in the same way, allowing for multiple identical operation applications. |
| 214 | +# This example stored the measurement instance as `m`, which you can use for |
| 215 | +# post-processing. It's also possible to do this with all other operations in |
| 216 | +# the same way, allowing for multiple identical operation applications. |
| 217 | +# |
206 | 218 | # ```python |
207 | 219 | # with circuit.context as q, _: |
208 | 220 | # single_x_op = ops.X(q[0]) |
209 | 221 | # # apply the X-gate again to the second qubit |
210 | 222 | # # using the previously stored operation |
211 | 223 | # single_x_op(q[1]) |
212 | 224 | # ``` |
213 | | -# This procedure can also be shortened into a single line for further convience. |
| 225 | +# |
| 226 | +# This procedure can also be shortened into a single line for further convenience. |
| 227 | +# |
214 | 228 | # ```python |
215 | 229 | # ops.CNOT(q[0], q[1])(q[1], q[2])(q[2], q[3]) |
216 | 230 | # ``` |
217 | 231 | # ::: |
218 | 232 |
|
219 | 233 | ############################################################################### |
220 | | -# The circuit should now contain 3 operations: a Hadamard, a CNOT and a measurment. |
| 234 | +# The circuit should now contain 3 operations: a Hadamard, a CNOT and a measurement. |
221 | 235 |
|
222 | 236 | print(circuit) |
223 | 237 |
|
224 | 238 | ############################################################################### |
225 | | -# When simulating this circuit, the measurement will be applied and the measured value will be stored in the classical register. Since a measurement will affect the quantum state, the resulting state will have collapsed into the expected result dependent on the value which has been measured. |
| 239 | +# When simulating this circuit, the measurement is applied and the measured |
| 240 | +# value is stored in the classical register. Since a measurement affects the |
| 241 | +# quantum state, the resulting state has collapsed into the expected result |
| 242 | +# dependent on the value which has been measured. |
226 | 243 |
|
227 | 244 | simulate(circuit) |
228 | 245 |
|
229 | 246 | ############################################################################### |
230 | | -# If the measurement result is 0 the state should collapse into $\vert00\rangle$, and if the measurement result is 1 the state should collapse into $\vert11\rangle$. Outputting the measurement value and the state reveals that this is indeed the case. |
| 247 | +# If the measurement result is 0 the state should have collapsed into $\vert00\rangle$, |
| 248 | +# and if the measurement result is 1 the state should have collapsed into $\vert11\rangle$. |
| 249 | +# Outputting the measurement value and the state reveals that this is indeed the case. |
231 | 250 |
|
232 | 251 | print(circuit.bits[0].value) |
233 | 252 | print(circuit.state) |
234 | 253 |
|
235 | 254 | ############################################################################### |
236 | | -# ## Measurement post-access |
237 | | -# Since we stored the measurement operation in `m`, we can use it to access the state as it was before the measurement. |
| 255 | +# ## Measurement Post-Access |
| 256 | +# Since the measurement operation has been stored in `m`, you can use it to access the |
| 257 | +# state as it was before the measurement. |
238 | 258 | # |
239 | 259 | # :::note |
240 | | -# Accessing the state of the circuit along with any measurement post-sampling and state-access is only available for simulators. |
| 260 | +# Accessing the state of the circuit, along with any measurement post-sampling and |
| 261 | +# state-access, is only available for simulators. |
241 | 262 | # ::: |
242 | 263 |
|
243 | 264 | m.state |
244 | 265 |
|
245 | 266 | ############################################################################### |
246 | | -# We can also sample that same state again using the `Measurement.sample` method, which by default only samples the state once. Here, we request 10 samples. |
| 267 | +# You can also sample that same state again using the `Measurement.sample` method, |
| 268 | +# which by default only samples the state once. Here, request 10 samples. |
247 | 269 |
|
248 | 270 | m.sample(num_samples=10) |
249 | 271 |
|
250 | 272 | ############################################################################### |
251 | | -# Finally, we can calculate the expected value of the measurment based on a specific number of samples. |
| 273 | +# Finally, you can calculate the expected value of the measurement based on a |
| 274 | +# specific number of samples. |
252 | 275 |
|
253 | 276 | m.expval(num_samples=10000) |
254 | 277 |
|
|
0 commit comments