Circuit analysis¶
Download this notebook - circuit_analysis_example.ipynb
This notebook will introduce the basic methods of analysis and visualization of circuits available in pytket
.
It makes use of the modules pytket_qiskit
and pytket_cirq
for visualization; these need to be installed (with pip
) in addition to pytket
.
We’ll start by generating a small circuit to use as an example, and give it a name.
from pytket.circuit import Circuit, OpType
c = Circuit(4, name="example")
c.add_gate(OpType.CU1, 0.5, [0, 1])
c.H(0).X(1).Y(2).Z(3)
c.X(0).CX(1, 2).Y(1).Z(2).H(3)
c.Y(0).Z(1)
c.add_gate(OpType.CU1, 0.5, [2, 3])
c.H(2).X(3)
c.Z(0).H(1).X(2).Y(3).CX(3, 0)
[CU1(0.5) q[0], q[1]; Y q[2]; Z q[3]; H q[0]; X q[1]; H q[3]; X q[0]; CX q[1], q[2]; Y q[0]; Y q[1]; Z q[2]; Z q[0]; Z q[1]; CU1(0.5) q[2], q[3]; H q[1]; H q[2]; X q[3]; X q[2]; Y q[3]; CX q[3], q[0]; ]
Basic statistics¶
From the circuit we can easily read off the number of qubits …
c.n_qubits
4
… the name …
c.name
'example'
… the overall depth of the circuit …
c.depth()
8
… and the depth by type of gate:
from pytket.circuit import OpType
c.depth_by_type(OpType.CU1), c.depth_by_type(OpType.H)
(2, 2)
This last method counts the number of instances of the specified gate type that cannot be parallelized. Notice that although there are 4 H gates in the circuit, the H-depth is 2 because pairs of them can be parallelized (as will be clear from the visualizations below).
Visualization¶
There are several ways to produce useful visualizations of circuits from pytket
: we can use the methods in the pytket.circuit.display
class; we can use the built-in Graph
class to visualize the circuit as a directed acyclic graph (DAG); we can convert the circuit to either Qiskit or Cirq and use the tools provided by those modules; or we can export to Latex.
Via the render_circuit_jupyter
method¶
from pytket.circuit.display import render_circuit_jupyter
render_circuit_jupyter(c)
Notice that although the render_circuit_jupyter
method is the recommended way to render a circuit as jupyter cell output, one of the other methods should be used when working with scripts or python shells.
Via the Graph
class¶
from pytket.utils import Graph
G = Graph(c)
G.get_DAG()
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/execute.py:76, in run_check(cmd, input_lines, encoding, quiet, **kwargs)
75 kwargs['stdout'] = kwargs['stderr'] = subprocess.PIPE
---> 76 proc = _run_input_lines(cmd, input_lines, kwargs=kwargs)
77 else:
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/execute.py:96, in _run_input_lines(cmd, input_lines, kwargs)
95 def _run_input_lines(cmd, input_lines, *, kwargs):
---> 96 popen = subprocess.Popen(cmd, stdin=subprocess.PIPE, **kwargs)
98 stdin_write = popen.stdin.write
File /usr/lib/python3.11/subprocess.py:1024, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1021 self.stderr = io.TextIOWrapper(self.stderr,
1022 encoding=encoding, errors=errors)
-> 1024 self._execute_child(args, executable, preexec_fn, close_fds,
1025 pass_fds, cwd, env,
1026 startupinfo, creationflags, shell,
1027 p2cread, p2cwrite,
1028 c2pread, c2pwrite,
1029 errread, errwrite,
1030 restore_signals,
1031 gid, gids, uid, umask,
1032 start_new_session, process_group)
1033 except:
1034 # Cleanup if the child failed starting.
File /usr/lib/python3.11/subprocess.py:1901, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1900 err_msg = os.strerror(errno_num)
-> 1901 raise child_exception_type(errno_num, err_msg, err_filename)
1902 raise child_exception_type(err_msg)
FileNotFoundError: [Errno 2] No such file or directory: PosixPath('dot')
The above exception was the direct cause of the following exception:
ExecutableNotFound Traceback (most recent call last)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/IPython/core/formatters.py:1036, in MimeBundleFormatter.__call__(self, obj, include, exclude)
1033 method = get_real_method(obj, self.print_method)
1035 if method is not None:
-> 1036 return method(include=include, exclude=exclude)
1037 return None
1038 else:
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/jupyter_integration.py:98, in JupyterIntegration._repr_mimebundle_(self, include, exclude, **_)
96 include = set(include) if include is not None else {self._jupyter_mimetype}
97 include -= set(exclude or [])
---> 98 return {mimetype: getattr(self, method_name)()
99 for mimetype, method_name in MIME_TYPES.items()
100 if mimetype in include}
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/jupyter_integration.py:98, in <dictcomp>(.0)
96 include = set(include) if include is not None else {self._jupyter_mimetype}
97 include -= set(exclude or [])
---> 98 return {mimetype: getattr(self, method_name)()
99 for mimetype, method_name in MIME_TYPES.items()
100 if mimetype in include}
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/jupyter_integration.py:112, in JupyterIntegration._repr_image_svg_xml(self)
110 def _repr_image_svg_xml(self) -> str:
111 """Return the rendered graph as SVG string."""
--> 112 return self.pipe(format='svg', encoding=SVG_ENCODING)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/piping.py:104, in Pipe.pipe(self, format, renderer, formatter, neato_no_op, quiet, engine, encoding)
55 def pipe(self,
56 format: typing.Optional[str] = None,
57 renderer: typing.Optional[str] = None,
(...)
61 engine: typing.Optional[str] = None,
62 encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]:
63 """Return the source piped through the Graphviz layout command.
64
65 Args:
(...)
102 '<?xml version='
103 """
--> 104 return self._pipe_legacy(format,
105 renderer=renderer,
106 formatter=formatter,
107 neato_no_op=neato_no_op,
108 quiet=quiet,
109 engine=engine,
110 encoding=encoding)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/_tools.py:171, in deprecate_positional_args.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
162 wanted = ', '.join(f'{name}={value!r}'
163 for name, value in deprecated.items())
164 warnings.warn(f'The signature of {func.__name__} will be reduced'
165 f' to {supported_number} positional args'
166 f' {list(supported)}: pass {wanted}'
167 ' as keyword arg(s)',
168 stacklevel=stacklevel,
169 category=category)
--> 171 return func(*args, **kwargs)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/piping.py:121, in Pipe._pipe_legacy(self, format, renderer, formatter, neato_no_op, quiet, engine, encoding)
112 @_tools.deprecate_positional_args(supported_number=2)
113 def _pipe_legacy(self,
114 format: typing.Optional[str] = None,
(...)
119 engine: typing.Optional[str] = None,
120 encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]:
--> 121 return self._pipe_future(format,
122 renderer=renderer,
123 formatter=formatter,
124 neato_no_op=neato_no_op,
125 quiet=quiet,
126 engine=engine,
127 encoding=encoding)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/piping.py:149, in Pipe._pipe_future(self, format, renderer, formatter, neato_no_op, quiet, engine, encoding)
146 if encoding is not None:
147 if codecs.lookup(encoding) is codecs.lookup(self.encoding):
148 # common case: both stdin and stdout need the same encoding
--> 149 return self._pipe_lines_string(*args, encoding=encoding, **kwargs)
150 try:
151 raw = self._pipe_lines(*args, input_encoding=self.encoding, **kwargs)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/piping.py:212, in pipe_lines_string(engine, format, input_lines, encoding, renderer, formatter, neato_no_op, quiet)
206 cmd = dot_command.command(engine, format,
207 renderer=renderer,
208 formatter=formatter,
209 neato_no_op=neato_no_op)
210 kwargs = {'input_lines': input_lines, 'encoding': encoding}
--> 212 proc = execute.run_check(cmd, capture_output=True, quiet=quiet, **kwargs)
213 return proc.stdout
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/execute.py:81, in run_check(cmd, input_lines, encoding, quiet, **kwargs)
79 except OSError as e:
80 if e.errno == errno.ENOENT:
---> 81 raise ExecutableNotFound(cmd) from e
82 raise
84 if not quiet and proc.stderr:
ExecutableNotFound: failed to execute PosixPath('dot'), make sure the Graphviz executables are on your systems' PATH
<graphviz.graphs.Digraph at 0x7f91ce5cf4d0>
The small numbers (0 and 1) shown at the entry to and exit from the two-qubit gates represent “port numbers” on the gates; these allow us to track individual qubits, which may be placed in a different order on entry and exit in order to simplify the layout.
The Graph
class also has methods to save this image to a file and to open it in a PDF viewer.
We can also view the qubit connectivity graph of a circuit:
G.get_qubit_graph()
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/execute.py:76, in run_check(cmd, input_lines, encoding, quiet, **kwargs)
75 kwargs['stdout'] = kwargs['stderr'] = subprocess.PIPE
---> 76 proc = _run_input_lines(cmd, input_lines, kwargs=kwargs)
77 else:
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/execute.py:96, in _run_input_lines(cmd, input_lines, kwargs)
95 def _run_input_lines(cmd, input_lines, *, kwargs):
---> 96 popen = subprocess.Popen(cmd, stdin=subprocess.PIPE, **kwargs)
98 stdin_write = popen.stdin.write
File /usr/lib/python3.11/subprocess.py:1024, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1021 self.stderr = io.TextIOWrapper(self.stderr,
1022 encoding=encoding, errors=errors)
-> 1024 self._execute_child(args, executable, preexec_fn, close_fds,
1025 pass_fds, cwd, env,
1026 startupinfo, creationflags, shell,
1027 p2cread, p2cwrite,
1028 c2pread, c2pwrite,
1029 errread, errwrite,
1030 restore_signals,
1031 gid, gids, uid, umask,
1032 start_new_session, process_group)
1033 except:
1034 # Cleanup if the child failed starting.
File /usr/lib/python3.11/subprocess.py:1901, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1900 err_msg = os.strerror(errno_num)
-> 1901 raise child_exception_type(errno_num, err_msg, err_filename)
1902 raise child_exception_type(err_msg)
FileNotFoundError: [Errno 2] No such file or directory: PosixPath('dot')
The above exception was the direct cause of the following exception:
ExecutableNotFound Traceback (most recent call last)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/IPython/core/formatters.py:1036, in MimeBundleFormatter.__call__(self, obj, include, exclude)
1033 method = get_real_method(obj, self.print_method)
1035 if method is not None:
-> 1036 return method(include=include, exclude=exclude)
1037 return None
1038 else:
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/jupyter_integration.py:98, in JupyterIntegration._repr_mimebundle_(self, include, exclude, **_)
96 include = set(include) if include is not None else {self._jupyter_mimetype}
97 include -= set(exclude or [])
---> 98 return {mimetype: getattr(self, method_name)()
99 for mimetype, method_name in MIME_TYPES.items()
100 if mimetype in include}
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/jupyter_integration.py:98, in <dictcomp>(.0)
96 include = set(include) if include is not None else {self._jupyter_mimetype}
97 include -= set(exclude or [])
---> 98 return {mimetype: getattr(self, method_name)()
99 for mimetype, method_name in MIME_TYPES.items()
100 if mimetype in include}
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/jupyter_integration.py:112, in JupyterIntegration._repr_image_svg_xml(self)
110 def _repr_image_svg_xml(self) -> str:
111 """Return the rendered graph as SVG string."""
--> 112 return self.pipe(format='svg', encoding=SVG_ENCODING)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/piping.py:104, in Pipe.pipe(self, format, renderer, formatter, neato_no_op, quiet, engine, encoding)
55 def pipe(self,
56 format: typing.Optional[str] = None,
57 renderer: typing.Optional[str] = None,
(...)
61 engine: typing.Optional[str] = None,
62 encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]:
63 """Return the source piped through the Graphviz layout command.
64
65 Args:
(...)
102 '<?xml version='
103 """
--> 104 return self._pipe_legacy(format,
105 renderer=renderer,
106 formatter=formatter,
107 neato_no_op=neato_no_op,
108 quiet=quiet,
109 engine=engine,
110 encoding=encoding)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/_tools.py:171, in deprecate_positional_args.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
162 wanted = ', '.join(f'{name}={value!r}'
163 for name, value in deprecated.items())
164 warnings.warn(f'The signature of {func.__name__} will be reduced'
165 f' to {supported_number} positional args'
166 f' {list(supported)}: pass {wanted}'
167 ' as keyword arg(s)',
168 stacklevel=stacklevel,
169 category=category)
--> 171 return func(*args, **kwargs)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/piping.py:121, in Pipe._pipe_legacy(self, format, renderer, formatter, neato_no_op, quiet, engine, encoding)
112 @_tools.deprecate_positional_args(supported_number=2)
113 def _pipe_legacy(self,
114 format: typing.Optional[str] = None,
(...)
119 engine: typing.Optional[str] = None,
120 encoding: typing.Optional[str] = None) -> typing.Union[bytes, str]:
--> 121 return self._pipe_future(format,
122 renderer=renderer,
123 formatter=formatter,
124 neato_no_op=neato_no_op,
125 quiet=quiet,
126 engine=engine,
127 encoding=encoding)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/piping.py:149, in Pipe._pipe_future(self, format, renderer, formatter, neato_no_op, quiet, engine, encoding)
146 if encoding is not None:
147 if codecs.lookup(encoding) is codecs.lookup(self.encoding):
148 # common case: both stdin and stdout need the same encoding
--> 149 return self._pipe_lines_string(*args, encoding=encoding, **kwargs)
150 try:
151 raw = self._pipe_lines(*args, input_encoding=self.encoding, **kwargs)
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/piping.py:212, in pipe_lines_string(engine, format, input_lines, encoding, renderer, formatter, neato_no_op, quiet)
206 cmd = dot_command.command(engine, format,
207 renderer=renderer,
208 formatter=formatter,
209 neato_no_op=neato_no_op)
210 kwargs = {'input_lines': input_lines, 'encoding': encoding}
--> 212 proc = execute.run_check(cmd, capture_output=True, quiet=quiet, **kwargs)
213 return proc.stdout
File ~/.cache/pypoetry/virtualenvs/pytket-docs-YIeXjjpk-py3.11/lib/python3.11/site-packages/graphviz/backend/execute.py:81, in run_check(cmd, input_lines, encoding, quiet, **kwargs)
79 except OSError as e:
80 if e.errno == errno.ENOENT:
---> 81 raise ExecutableNotFound(cmd) from e
82 raise
84 if not quiet and proc.stderr:
ExecutableNotFound: failed to execute PosixPath('dot'), make sure the Graphviz executables are on your systems' PATH
<graphviz.graphs.Graph at 0x7f91ce146a50>
Via Qiskit¶
from pytket.extensions.qiskit import tk_to_qiskit
print(tk_to_qiskit(c))
┌───┐┌───┐┌───┐ ┌───┐ ┌───┐
q_0: ─■────────┤ H ├┤ X ├┤ Y ├──┤ Z ├─────────────┤ X ├
│U1(π/2) ├───┤└───┘├───┤ ├───┤ ┌───┐ └─┬─┘
q_1: ─■────────┤ X ├──■──┤ Y ├──┤ Z ├───┤ H ├───────┼──
┌───┐ └───┘┌─┴─┐├───┤ └───┘ ├───┤┌───┐ │
q_2: ──┤ Y ├────────┤ X ├┤ Z ├─■────────┤ H ├┤ X ├──┼──
├───┤ ┌───┐└───┘└───┘ │U1(π/2) ├───┤├───┤ │
q_3: ──┤ Z ├───┤ H ├───────────■────────┤ X ├┤ Y ├──■──
└───┘ └───┘ └───┘└───┘
Via Cirq¶
from pytket.extensions.cirq import tk_to_cirq
print(tk_to_cirq(c))
0: ───@───────H───X───Y───Z───────────────X───
│ │
1: ───@^0.5───X───@───Y───Z───────H───────┼───
│ │
2: ───Y───────────X───Z───@───────H───X───┼───
│ │
3: ───Z───────H───────────@^0.5───X───Y───@───
(Note that Cirq cannot represent all gates diagrammatically.)
Via Latex¶
We can create a Latex document containing a diagram of the circuit using the to_latex_file()
method. This uses the quantikz
library. The document can be viewed on its own or the Latex can easily be copied and pasted into a larger document.
c.to_latex_file("c.tex")
Commands¶
We can retrieve a list of operations comprising a circuit, each represented as a Command
object:
cmds = c.get_commands()
print(cmds)
[CU1(0.5) q[0], q[1];, Y q[2];, Z q[3];, H q[0];, X q[1];, H q[3];, X q[0];, CX q[1], q[2];, Y q[0];, Y q[1];, Z q[2];, Z q[0];, Z q[1];, CU1(0.5) q[2], q[3];, H q[1];, H q[2];, X q[3];, X q[2];, Y q[3];, CX q[3], q[0];]
Each Command
is defined by an operation and the qubits it operates on (as well as the classical bits and conditions, if any). For example:
cmd0 = cmds[0]
op0 = cmd0.op
print(op0)
qubits0 = cmd0.args
print(qubits0)
CU1(0.5)
[q[0], q[1]]
From the Op
we can read off the string representation (in normal or Latex form), the parameters and the type:
op0.get_name() # normal form
'CU1(0.5)'
op0.get_name(latex=True) # Latex form
'CU1(0.5)'
op0.type, op0.params
(<OpType.CU1: 58>, [0.5])
Note that some compilation passes introduce implicit wire swaps at the end of the circuit, which are not represented in the command list. (The internal representation of the circuit as a directed acyclic graph reduces explicit permutations of qubits to implicit features of the graph.)