Skip to content

Commit 963bff3

Browse files
Update compiler tutorial (ProjectQ-Framework#239)
1 parent fc7b683 commit 963bff3

1 file changed

Lines changed: 202 additions & 36 deletions

File tree

examples/compiler_tutorial.ipynb

Lines changed: 202 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"cell_type": "markdown",
1414
"metadata": {},
1515
"source": [
16-
"The aim of this short tutorial is to give a first introduction to the ProjectQ compiler. In particular, we will show how to specify the gate set to which the compiler should translate a quantum program. A more extended tutorial will follow soon. Please check out our [ProjectQ paper](http://arxiv.org/abs/1612.08091) for an introduction to the basic concepts behind our compiler."
16+
"The aim of this short tutorial is to give a first introduction to the ProjectQ compiler and show the use different preconfigured setups. In particular, we will show how to specify the gate set to which the compiler should translate a quantum program. A more extended tutorial will follow soon. Please check out our [ProjectQ paper](http://arxiv.org/abs/1612.08091) for an introduction to the basic concepts behind our compiler. If you are interested how to compile to a restricted hardware with, e.g., only nearest neighbour connectivity, please have a look at the `mapper_tutorial.ipynb` afterwards."
1717
]
1818
},
1919
{
@@ -148,14 +148,15 @@
148148
"cell_type": "markdown",
149149
"metadata": {},
150150
"source": [
151-
"## Specifying a particular gate set"
151+
"## Using a provided setup and specifying a particular gate set"
152152
]
153153
},
154154
{
155155
"cell_type": "markdown",
156156
"metadata": {},
157157
"source": [
158-
"In this short example, we want to explore how to specify a particular gate set, which may be even more restrictive than what the backend naturally supports. This is useful, e.g., to obtain resource estimates for running a given program on actual quantum hardware which, in this example, can only perform CNOT and single qubit gates. All one has to do is insert an `InstructionFilter` into the `engine_list`:"
158+
"ProjectQ's compiler is fully modular, so one can easily build a special purpose compiler. All one has to do is compose a list of [compiler engines](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/cengines) through which the individual operations will pass in a serial order and give this compiler list to the `MainEngine` as the `engine_list` parameter.\n",
159+
"For common compiler needs we try to provide predefined \"setups\" which contain a function `get_engine_list` which returns a suitable list of compiler engines for the `MainEngine`. All of our current setups can be found in [projectq.setups](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups). For example there is a setup called `restrictedgateset` which allows to compile to common restricted gate sets. This is useful, for example, to obtain resource estimates for running a given program on actual quantum hardware which does not support every quantum gate. Let's look at an example:"
159160
]
160161
},
161162
{
@@ -168,41 +169,203 @@
168169
"output_type": "stream",
169170
"text": [
170171
"Allocate | Qureg[3]\n",
171-
"Allocate | Qureg[2]\n",
172-
"Rx(1.5707963268) | Qureg[2]\n",
173172
"Allocate | Qureg[1]\n",
174-
"H | Qureg[1]\n",
175-
"CX | ( Qureg[1], Qureg[2] )\n",
173+
"Allocate | Qureg[2]\n",
174+
"CCX | ( Qureg[1-2], Qureg[3] )\n",
175+
"H | Qureg[3]\n",
176+
"Rz(0.785398163398) | Qureg[3]\n",
177+
"R(0.785398163398) | Qureg[2]\n",
176178
"CX | ( Qureg[2], Qureg[3] )\n",
177-
"Rz(0.05) | Qureg[3]\n",
178-
"Allocate | Qureg[0]\n",
179-
"CX | ( Qureg[0], Qureg[3] )\n",
180-
"Rz(12.5163706144) | Qureg[3]\n",
181-
"CX | ( Qureg[0], Qureg[3] )\n",
179+
"Rz(11.780972451) | Qureg[3]\n",
182180
"CX | ( Qureg[2], Qureg[3] )\n",
181+
"R(0.392699081698) | Qureg[1]\n",
182+
"Rz(0.392699081698) | Qureg[3]\n",
183+
"CX | ( Qureg[1], Qureg[3] )\n",
184+
"H | Qureg[2]\n",
185+
"Rz(12.1736715327) | Qureg[3]\n",
186+
"CX | ( Qureg[1], Qureg[3] )\n",
187+
"R(0.785398163398) | Qureg[1]\n",
188+
"Rz(0.785398163398) | Qureg[2]\n",
189+
"CX | ( Qureg[1], Qureg[2] )\n",
190+
"Rz(11.780972451) | Qureg[2]\n",
183191
"CX | ( Qureg[1], Qureg[2] )\n",
184192
"H | Qureg[1]\n",
185-
"R(0.392699081698) | Qureg[1]\n",
193+
"Measure | Qureg[1]\n",
194+
"Measure | Qureg[2]\n",
195+
"Measure | Qureg[3]\n",
196+
"Allocate | Qureg[0]\n",
197+
"H | Qureg[0]\n",
198+
"Rx(0.3) | Qureg[0]\n",
199+
"Measure | Qureg[0]\n",
200+
"Deallocate | Qureg[0]\n",
201+
"Deallocate | Qureg[3]\n",
202+
"Deallocate | Qureg[2]\n",
203+
"Deallocate | Qureg[1]\n"
204+
]
205+
}
206+
],
207+
"source": [
208+
"import projectq\n",
209+
"from projectq.setups import restrictedgateset\n",
210+
"from projectq.ops import All, H, Measure, Rx, Ry, Rz, Toffoli\n",
211+
"engine_list3 = restrictedgateset.get_engine_list(one_qubit_gates=\"any\",\n",
212+
" two_qubit_gates=(CNOT,),\n",
213+
" other_gates=(Toffoli,))\n",
214+
"eng3 = projectq.MainEngine(backend=CommandPrinter(accept_input=False),\n",
215+
" engine_list=engine_list3)\n",
216+
"\n",
217+
"def my_second_program(eng):\n",
218+
" qubit = eng3.allocate_qubit()\n",
219+
" qureg = eng3.allocate_qureg(3)\n",
220+
" H | qubit\n",
221+
" Rx(0.3) | qubit\n",
222+
" Toffoli | (qureg[:-1], qureg[2])\n",
223+
" QFT | qureg\n",
224+
" All(Measure) | qureg\n",
225+
" Measure | qubit\n",
226+
" eng.flush()\n",
227+
"my_second_program(eng3)"
228+
]
229+
},
230+
{
231+
"cell_type": "markdown",
232+
"metadata": {},
233+
"source": [
234+
"Please have a look at the documention of the [restrictedgateset](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.restrictedgateset) for details. The above compiler compiles the circuit to gates consisting of any single qubit gate, the `CNOT` and `Toffoli` gate. The gate specifications can either be a gate class, e.g., `Rz` or a specific instance `Rz(math.pi)`. A smaller but still universal gate set would be for example `CNOT` and `Rz, Ry`:"
235+
]
236+
},
237+
{
238+
"cell_type": "code",
239+
"execution_count": 4,
240+
"metadata": {},
241+
"outputs": [
242+
{
243+
"name": "stdout",
244+
"output_type": "stream",
245+
"text": [
246+
"Allocate | Qureg[7]\n",
247+
"Allocate | Qureg[5]\n",
248+
"Allocate | Qureg[6]\n",
249+
"CCX | ( Qureg[5-6], Qureg[7] )\n",
250+
"H | Qureg[7]\n",
251+
"Rz(0.785398163398) | Qureg[7]\n",
252+
"R(0.785398163398) | Qureg[6]\n",
253+
"CX | ( Qureg[6], Qureg[7] )\n",
254+
"Rz(11.780972451) | Qureg[7]\n",
255+
"CX | ( Qureg[6], Qureg[7] )\n",
256+
"R(0.392699081698) | Qureg[5]\n",
257+
"Rz(0.392699081698) | Qureg[7]\n",
258+
"CX | ( Qureg[5], Qureg[7] )\n",
259+
"H | Qureg[6]\n",
260+
"Rz(12.1736715327) | Qureg[7]\n",
261+
"CX | ( Qureg[5], Qureg[7] )\n",
262+
"R(0.785398163398) | Qureg[5]\n",
263+
"Rz(0.785398163398) | Qureg[6]\n",
264+
"CX | ( Qureg[5], Qureg[6] )\n",
265+
"Rz(11.780972451) | Qureg[6]\n",
266+
"CX | ( Qureg[5], Qureg[6] )\n",
267+
"H | Qureg[5]\n",
268+
"Measure | Qureg[5]\n",
269+
"Measure | Qureg[6]\n",
270+
"Measure | Qureg[7]\n",
271+
"Allocate | Qureg[4]\n",
272+
"H | Qureg[4]\n",
273+
"Rx(0.3) | Qureg[4]\n",
274+
"Measure | Qureg[4]\n",
275+
"Deallocate | Qureg[4]\n",
276+
"Deallocate | Qureg[7]\n",
277+
"Deallocate | Qureg[6]\n",
278+
"Deallocate | Qureg[5]\n"
279+
]
280+
}
281+
],
282+
"source": [
283+
"engine_list4 = restrictedgateset.get_engine_list(one_qubit_gates=(Rz, Ry),\n",
284+
" two_qubit_gates=(CNOT,),\n",
285+
" other_gates=())\n",
286+
"eng4 = projectq.MainEngine(backend=CommandPrinter(accept_input=False),\n",
287+
" engine_list=engine_list4)\n",
288+
"my_second_program(eng4)"
289+
]
290+
},
291+
{
292+
"cell_type": "markdown",
293+
"metadata": {},
294+
"source": [
295+
"As mentioned in the documention of [this setup](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.restrictedgateset), one cannot (yet) choose an arbitrary gate set but there is a limited choice. If it doesn't work for a specified gate set, the compiler will either raises a `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...` which means that for this particular choice of gate set, one would be required to write more [decomposition rules](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups/decompositions) to make it work. Also for some choice of gate set there might be compiler engines producing more optimal code."
296+
]
297+
},
298+
{
299+
"cell_type": "markdown",
300+
"metadata": {},
301+
"source": [
302+
"## Error messages\n",
303+
"By default the `MainEngine` shortens error messages as most often this is enough information to find the error. To see the full error message one can to set `verbose=True`, i.e.:\n",
304+
"`MainEngine(verbose=True)`"
305+
]
306+
},
307+
{
308+
"cell_type": "markdown",
309+
"metadata": {},
310+
"source": [
311+
"## DIY: Build a compiler engine list for a specific gate set\n",
312+
"In this short example, we want to look at how to build an own compiler `engine_list` for compiling to a restricted gate set. Please have a look at the [predefined setups](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups) for guidance.\n",
313+
"\n",
314+
"One of the important compiler engines to change the gate set is the `AutoReplacer`. It queries the following engines to check if a particular gate is supported and if not, it will use decomposition rules to change this gate to supported ones. Most engines just forward this query to the next engine until the backend is reached. The engine after an `AutoReplacer` is usually a `TagRemover` which removes previous tags in commands such as, e.g., `ComputeTag` which allows a following `LocalOptimizer` to perform more optimizations (otherwise it would only optimize within a \"compute\" section and not over the boundaries).\n",
315+
"\n",
316+
"To specify different intermediate gate sets, one can insert an `InstructionFilter` into the `engine_list` after the `AutoReplacer` in order to return `True` or `False` for the queries of the `AutoReplacer` asking if a specific gate is supported. \n",
317+
"\n",
318+
"Here is a minimal example of a compiler which compiles to CNOT and single qubit gates but doesn't perform optimizations (which could be achieved using the `LocalOptimizer`). For the more optimal versions, have a look at the [`restrictricedgateset` setup](https://github.com/ProjectQ-Framework/ProjectQ/blob/develop/projectq/setups/restrictedgateset.py#L63):"
319+
]
320+
},
321+
{
322+
"cell_type": "code",
323+
"execution_count": 5,
324+
"metadata": {
325+
"scrolled": true
326+
},
327+
"outputs": [
328+
{
329+
"name": "stdout",
330+
"output_type": "stream",
331+
"text": [
332+
"Allocate | Qureg[0]\n",
333+
"Allocate | Qureg[1]\n",
334+
"Allocate | Qureg[2]\n",
335+
"Allocate | Qureg[3]\n",
336+
"H | Qureg[3]\n",
337+
"CX | ( Qureg[1], Qureg[3] )\n",
338+
"T | Qureg[1]\n",
339+
"T^\\dagger | Qureg[3]\n",
340+
"CX | ( Qureg[2], Qureg[3] )\n",
341+
"CX | ( Qureg[2], Qureg[1] )\n",
342+
"T^\\dagger | Qureg[1]\n",
343+
"T | Qureg[3]\n",
344+
"CX | ( Qureg[2], Qureg[1] )\n",
345+
"CX | ( Qureg[1], Qureg[3] )\n",
346+
"T^\\dagger | Qureg[3]\n",
347+
"CX | ( Qureg[2], Qureg[3] )\n",
348+
"T | Qureg[3]\n",
349+
"T | Qureg[2]\n",
350+
"H | Qureg[3]\n",
186351
"H | Qureg[3]\n",
187-
"Rz(0.785398163398) | Qureg[3]\n",
188-
"Rx(10.9955742876) | Qureg[2]\n",
189352
"R(0.785398163398) | Qureg[2]\n",
353+
"Rz(0.785398163398) | Qureg[3]\n",
190354
"CX | ( Qureg[2], Qureg[3] )\n",
191355
"Rz(11.780972451) | Qureg[3]\n",
192356
"CX | ( Qureg[2], Qureg[3] )\n",
357+
"R(0.392699081698) | Qureg[1]\n",
193358
"Rz(0.392699081698) | Qureg[3]\n",
194359
"CX | ( Qureg[1], Qureg[3] )\n",
195360
"Rz(12.1736715327) | Qureg[3]\n",
196361
"CX | ( Qureg[1], Qureg[3] )\n",
197-
"R(0.785398163398) | Qureg[1]\n",
198362
"H | Qureg[2]\n",
363+
"R(0.785398163398) | Qureg[1]\n",
199364
"Rz(0.785398163398) | Qureg[2]\n",
200365
"CX | ( Qureg[1], Qureg[2] )\n",
201366
"Rz(11.780972451) | Qureg[2]\n",
202367
"CX | ( Qureg[1], Qureg[2] )\n",
203368
"H | Qureg[1]\n",
204-
"Rx(0.1) | Qureg[0]\n",
205-
"CX | ( Qureg[0], Qureg[1] )\n",
206369
"Measure | Qureg[1]\n",
207370
"Measure | Qureg[2]\n",
208371
"Measure | Qureg[3]\n",
@@ -215,8 +378,11 @@
215378
}
216379
],
217380
"source": [
218-
"from projectq.cengines import InstructionFilter\n",
219-
"from projectq.ops import ClassicalInstructionGate\n",
381+
"import projectq\n",
382+
"from projectq.backends import CommandPrinter\n",
383+
"from projectq.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter\n",
384+
"from projectq.ops import All, ClassicalInstructionGate, Measure, Toffoli, X\n",
385+
"import projectq.setups.decompositions\n",
220386
"\n",
221387
"# Write a function which, given a Command object, returns whether the command is supported:\n",
222388
"def is_supported(eng, cmd):\n",
@@ -234,28 +400,28 @@
234400
" else:\n",
235401
" return False\n",
236402
"\n",
237-
"supported_gate_set_filter = InstructionFilter(is_supported)\n",
403+
"#is_supported(\"test\", \"eng\")\n",
238404
"\n",
239-
"# Append the instruction filter to the list of compiler engines:\n",
240-
"engines3 = get_engine_list() + [supported_gate_set_filter]\n",
405+
"rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions])\n",
406+
"engine_list5 = [AutoReplacer(rule_set), InstructionFilter(is_supported)]\n",
407+
"eng5 = projectq.MainEngine(backend=CommandPrinter(accept_input=False),\n",
408+
" engine_list=engine_list5)\n",
241409
"\n",
242-
"eng3 = projectq.MainEngine(backend=CommandPrinter(accept_input=False), engine_list=engines3)\n",
243-
"my_quantum_program(eng3)"
244-
]
245-
},
246-
{
247-
"cell_type": "markdown",
248-
"metadata": {},
249-
"source": [
250-
"As we can see, the compiler now needs to do a little more work. In some cases, the compiler does not know how to translate a command according to the specified gate set and it will throw a `NoGateDecompositionError`. This means one needs to implement a rule specifying how to decompose said command. See projectq/setups/decompositions for a few examples. This will be explained in a more extended tutorial."
410+
"def my_third_program(eng):\n",
411+
" qubit = eng5.allocate_qubit()\n",
412+
" qureg = eng5.allocate_qureg(3)\n",
413+
" Toffoli | (qureg[:2], qureg[2])\n",
414+
" QFT | qureg\n",
415+
" All(Measure) | qureg\n",
416+
" Measure | qubit\n",
417+
" eng5.flush()\n",
418+
"my_third_program(eng5)"
251419
]
252420
},
253421
{
254422
"cell_type": "code",
255423
"execution_count": null,
256-
"metadata": {
257-
"collapsed": true
258-
},
424+
"metadata": {},
259425
"outputs": [],
260426
"source": []
261427
}
@@ -276,7 +442,7 @@
276442
"name": "python",
277443
"nbconvert_exporter": "python",
278444
"pygments_lexer": "ipython2",
279-
"version": "2.7.6"
445+
"version": "2.7.15"
280446
}
281447
},
282448
"nbformat": 4,

0 commit comments

Comments
 (0)