Skip to content

Commit a80fa84

Browse files
Matplotlib drawer backend (#352)
* Adding tutorials directory * test * BV Algorithm * add Matplotlib circuit drawer backend, this version works for H, CNOT, and Multi-CNOT. * Delete the added unnecessary attributes in Command object * Create the CircuitDrawerMatplotlib Class to handles drawing with matplotlib * Deleted tutorials/.gitkeep * update * fix measurement gate * Delete unrelated files. * fix Toffoli gate position issue and change the qubit position from 'str' to 'int' * Pytest for drawer_mpl * Tests for _plot function * Fix the R(angle) gate drawing * added test for is_available and QFT gate * fix drawing distance between gates when gate_length >2 * new test png for pytest mpl * added Swap gates and CSwap gate with multi-control and multi-target. * update test and comments * Address comments in _drawer.py * Reindent and reformat parts of _drawer.py * Address comments in _plot.py - Minor tweaks, code cleanup, rewrites, etc. * Reindent and reformat _plot.py * update tests * Move matplotlib drawer into its own file + add test coverage * Use regular expressions to rewrite and shorten gate names * Change internal storage format for CircuitDrawerMatplotlib * Better graphics and adapt plot functions to new internal format - Support for new internal format - Resulting quantum circuit figure whould work better with scaling - Large quantum circuits will now result in wider figure instead of squeezing everything into the default matplotlib size - Some support for multi-target qubit gates - General code cleanup - Dropped support for double lines when qubit is in classical state * Complete test coverage + add some checks for to_draw() inputs * Compatibility with matplotlib 2.2.3 * Remove compatibility code for MacOSX. Use local matplotlibrc if necessary instead. * Add matplotlib dependency to requirements.txt * Fix non-UTF8 character in file * Fix .travis.yml * Remove unnecessary PNG files * Add CircuitDrawerMatplotlib to documentation and minor code fix * Fix docstring for CircuitDrawerMatplotlib Co-authored-by: Nguyen Damien <[email protected]>
1 parent 93f2d79 commit a80fa84

File tree

12 files changed

+1319
-5
lines changed

12 files changed

+1319
-5
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ install:
4242
- if [ "${PYTHON:0:1}" = "3" ]; then pip$PY install dormouse; fi
4343
- pip$PY install -e .
4444

45+
before_script:
46+
- "echo 'backend: Agg' > matplotlibrc"
47+
4548
# command to run tests
4649
script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq
4750

docs/projectq.backends.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ backends
55

66
projectq.backends.CommandPrinter
77
projectq.backends.CircuitDrawer
8+
projectq.backends.CircuitDrawerMatplotlib
89
projectq.backends.Simulator
910
projectq.backends.ClassicalSimulator
1011
projectq.backends.ResourceCounter

projectq/backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* an interface to the IBM Quantum Experience chip (and simulator).
2727
"""
2828
from ._printer import CommandPrinter
29-
from ._circuits import CircuitDrawer
29+
from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib
3030
from ._sim import Simulator, ClassicalSimulator
3131
from ._resource import ResourceCounter
3232
from ._ibm import IBMBackend

projectq/backends/_circuits/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@
1313
# limitations under the License.
1414

1515
from ._to_latex import to_latex
16+
from ._plot import to_draw
17+
1618
from ._drawer import CircuitDrawer
19+
from ._drawer_matplotlib import CircuitDrawerMatplotlib
20+

projectq/backends/_circuits/_drawer.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
Contains a compiler engine which generates TikZ Latex code describing the
1616
circuit.
1717
"""
18-
import sys
19-
2018
from builtins import input
2119

2220
from projectq.cengines import LastEngineException, BasicEngine
@@ -223,12 +221,13 @@ def _print_cmd(self, cmd):
223221
self._free_lines.append(qubit_id)
224222

225223
if self.is_last_engine and cmd.gate == Measure:
226-
assert (get_control_count(cmd) == 0)
224+
assert get_control_count(cmd) == 0
225+
227226
for qureg in cmd.qubits:
228227
for qubit in qureg:
229228
if self._accept_input:
230229
m = None
231-
while m != '0' and m != '1' and m != 1 and m != 0:
230+
while m not in ('0', '1', 1, 0):
232231
prompt = ("Input measurement result (0 or 1) for "
233232
"qubit " + str(qubit) + ": ")
234233
m = input(prompt)
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Copyright 2020 ProjectQ-Framework (www.projectq.ch)
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""
15+
Contains a compiler engine which generates matplotlib figures describing the
16+
circuit.
17+
"""
18+
19+
from builtins import input
20+
import re
21+
import itertools
22+
23+
from projectq.cengines import LastEngineException, BasicEngine
24+
from projectq.ops import (FlushGate, Measure, Allocate, Deallocate)
25+
from projectq.meta import get_control_count
26+
from projectq.backends._circuits import to_draw
27+
28+
# ==============================================================================
29+
30+
31+
def _format_gate_str(cmd):
32+
param_str = ''
33+
gate_name = str(cmd.gate)
34+
if '(' in gate_name:
35+
(gate_name, param_str) = re.search(r'(.+)\((.*)\)', gate_name).groups()
36+
params = re.findall(r'([^,]+)', param_str)
37+
params_str_list = []
38+
for param in params:
39+
try:
40+
params_str_list.append('{0:.2f}'.format(float(param)))
41+
except ValueError:
42+
if len(param) < 8:
43+
params_str_list.append(param)
44+
else:
45+
params_str_list.append(param[:5] + '...')
46+
47+
gate_name += '(' + ','.join(params_str_list) + ')'
48+
return gate_name
49+
50+
51+
# ==============================================================================
52+
53+
54+
class CircuitDrawerMatplotlib(BasicEngine):
55+
"""
56+
CircuitDrawerMatplotlib is a compiler engine which using Matplotlib library
57+
for drawing quantum circuits
58+
"""
59+
def __init__(self, accept_input=False, default_measure=0):
60+
"""
61+
Initialize a circuit drawing engine(mpl)
62+
Args:
63+
accept_input (bool): If accept_input is true, the printer queries
64+
the user to input measurement results if the CircuitDrawerMPL
65+
is the last engine. Otherwise, all measurements yield the
66+
result default_measure (0 or 1).
67+
default_measure (bool): Default value to use as measurement
68+
results if accept_input is False and there is no underlying
69+
backend to register real measurement results.
70+
"""
71+
BasicEngine.__init__(self)
72+
self._accept_input = accept_input
73+
self._default_measure = default_measure
74+
self._map = dict()
75+
self._qubit_lines = {}
76+
77+
def is_available(self, cmd):
78+
"""
79+
Specialized implementation of is_available: Returns True if the
80+
CircuitDrawerMatplotlib is the last engine
81+
(since it can print any command).
82+
83+
Args:
84+
cmd (Command): Command for which to check availability (all
85+
Commands can be printed).
86+
87+
Returns:
88+
availability (bool): True, unless the next engine cannot handle
89+
the Command (if there is a next engine).
90+
"""
91+
try:
92+
# Multi-qubit gates may fail at drawing time if the target qubits
93+
# are not right next to each other on the output graphic.
94+
return BasicEngine.is_available(self, cmd)
95+
except LastEngineException:
96+
return True
97+
98+
def _process(self, cmd):
99+
"""
100+
Process the command cmd and stores it in the internal storage
101+
102+
Queries the user for measurement input if a measurement command
103+
arrives if accept_input was set to True. Otherwise, it uses the
104+
default_measure parameter to register the measurement outcome.
105+
106+
Args:
107+
cmd (Command): Command to add to the circuit diagram.
108+
"""
109+
if cmd.gate == Allocate:
110+
qubit_id = cmd.qubits[0][0].id
111+
if qubit_id not in self._map:
112+
self._map[qubit_id] = qubit_id
113+
self._qubit_lines[qubit_id] = []
114+
return
115+
116+
if cmd.gate == Deallocate:
117+
return
118+
119+
if self.is_last_engine and cmd.gate == Measure:
120+
assert get_control_count(cmd) == 0
121+
for qureg in cmd.qubits:
122+
for qubit in qureg:
123+
if self._accept_input:
124+
measurement = None
125+
while measurement not in ('0', '1', 1, 0):
126+
prompt = ("Input measurement result (0 or 1) for "
127+
"qubit " + str(qubit) + ": ")
128+
measurement = input(prompt)
129+
else:
130+
measurement = self._default_measure
131+
self.main_engine.set_measurement_result(
132+
qubit, int(measurement))
133+
134+
targets = [qubit.id for qureg in cmd.qubits for qubit in qureg]
135+
controls = [qubit.id for qubit in cmd.control_qubits]
136+
137+
ref_qubit_id = targets[0]
138+
gate_str = _format_gate_str(cmd)
139+
140+
# First find out what is the maximum index that this command might
141+
# have
142+
max_depth = max(
143+
len(self._qubit_lines[qubit_id])
144+
for qubit_id in itertools.chain(targets, controls))
145+
146+
# If we have a multi-qubit gate, make sure that all the qubit axes
147+
# have the same depth. We do that by recalculating the maximum index
148+
# over all the known qubit axes.
149+
# This is to avoid the possibility of a multi-qubit gate overlapping
150+
# with some other gates. This could potentially be improved by only
151+
# considering the qubit axes that are between the topmost and
152+
# bottommost qubit axes of the current command.
153+
if len(targets) + len(controls) > 1:
154+
max_depth = max(
155+
len(self._qubit_lines[qubit_id])
156+
for qubit_id in self._qubit_lines)
157+
158+
for qubit_id in itertools.chain(targets, controls):
159+
depth = len(self._qubit_lines[qubit_id])
160+
self._qubit_lines[qubit_id] += [None] * (max_depth - depth)
161+
162+
if qubit_id == ref_qubit_id:
163+
self._qubit_lines[qubit_id].append(
164+
(gate_str, targets, controls))
165+
else:
166+
self._qubit_lines[qubit_id].append(None)
167+
168+
def receive(self, command_list):
169+
"""
170+
Receive a list of commands from the previous engine, print the
171+
commands, and then send them on to the next engine.
172+
173+
Args:
174+
command_list (list<Command>): List of Commands to print (and
175+
potentially send on to the next engine).
176+
"""
177+
for cmd in command_list:
178+
if not isinstance(cmd.gate, FlushGate):
179+
self._process(cmd)
180+
181+
if not self.is_last_engine:
182+
self.send([cmd])
183+
184+
def draw(self, qubit_labels=None, drawing_order=None):
185+
"""
186+
Generates and returns the plot of the quantum circuit stored so far
187+
188+
Args:
189+
qubit_labels (dict): label for each wire in the output figure.
190+
Keys: qubit IDs, Values: string to print out as label for
191+
that particular qubit wire.
192+
drawing_order (dict): position of each qubit in the output
193+
graphic. Keys: qubit IDs, Values: position of qubit on the
194+
qubit line in the graphic.
195+
196+
Returns:
197+
A tuple containing the matplotlib figure and axes objects
198+
"""
199+
max_depth = max(
200+
len(self._qubit_lines[qubit_id]) for qubit_id in self._qubit_lines)
201+
for qubit_id in self._qubit_lines:
202+
depth = len(self._qubit_lines[qubit_id])
203+
if depth < max_depth:
204+
self._qubit_lines[qubit_id] += [None] * (max_depth - depth)
205+
206+
return to_draw(self._qubit_lines,
207+
qubit_labels=qubit_labels,
208+
drawing_order=drawing_order)

0 commit comments

Comments
 (0)