Skip to content

Commit a6aea62

Browse files
authored
Implement circuit_to_latex_using_qcircuit (#295)
1 parent e21932a commit a6aea62

File tree

5 files changed

+297
-1
lines changed

5 files changed

+297
-1
lines changed

cirq/circuits/circuit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def to_unitary_matrix(
335335
self,
336336
qubit_order_key: Callable[[QubitId], Any] = None,
337337
qubits_that_should_be_present: Iterable[QubitId] = (),
338-
ignore_terminal_measurements = True,
338+
ignore_terminal_measurements: bool = True,
339339
ext: Extensions = None) -> np.ndarray:
340340
"""Converts the circuit into a unitary matrix, if possible.
341341

cirq/contrib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
"""
2020

2121
from cirq.contrib import jobs
22+
from cirq.contrib.qcircuit_diagram import circuit_to_latex_using_qcircuit

cirq/contrib/qcircuit_diagram.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Copyright 2018 Google LLC
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+
# https://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+
from typing import Any, Callable, Optional
16+
17+
from cirq import abc, circuits, extension, ops
18+
from cirq.contrib.qcircuit_diagrammable_gate import (
19+
QCircuitDiagrammableGate,
20+
fallback_qcircuit_extensions,
21+
)
22+
23+
24+
class _QCircuitQubit(ops.QubitId):
25+
def __init__(self, sub: ops.QubitId) -> None:
26+
self.sub = sub
27+
28+
def __str__(self):
29+
# TODO: If qubit name ends with digits, turn them into subscripts.
30+
return '\\lstick{\\text{' + str(self.sub) + '}}&'
31+
32+
def __eq__(self, other):
33+
if not isinstance(other, _QCircuitQubit):
34+
return NotImplemented
35+
return self.sub == other.sub
36+
37+
def __ne__(self, other):
38+
return not self == other
39+
40+
def __hash__(self):
41+
return hash((_QCircuitQubit, self.sub))
42+
43+
44+
class _QCircuitGate(ops.TextDiagrammableGate, metaclass=abc.ABCMeta):
45+
def __init__(self, sub: QCircuitDiagrammableGate) -> None:
46+
self.sub = sub
47+
48+
def text_diagram_exponent(self):
49+
return 1
50+
51+
def text_diagram_wire_symbols(self,
52+
qubit_count: Optional[int] = None,
53+
use_unicode_characters: bool = True):
54+
return self.sub.qcircuit_wire_symbols(qubit_count)
55+
56+
57+
def _render(diagram: circuits.TextDiagramDrawer) -> str:
58+
w = diagram.width()
59+
h = diagram.height()
60+
61+
qwx = {(x, y + 1)
62+
for x, y1, y2 in diagram.vertical_lines
63+
for y in range(y1, y2)}
64+
65+
qw = {(x, y)
66+
for y, x1, x2 in diagram.horizontal_lines
67+
for x in range(x1, x2)}
68+
69+
rows = []
70+
for y in range(h):
71+
row = []
72+
for x in range(w):
73+
cell = []
74+
key = (x, y)
75+
v = diagram.entries.get(key)
76+
if v is not None:
77+
cell.append(' ' + v + ' ')
78+
if key in qw:
79+
cell.append('\\qw ')
80+
if key in qwx:
81+
cell.append('\\qwx ')
82+
row.append(''.join(cell))
83+
rows.append('&'.join(row) + '\qw')
84+
85+
grid = '\\\\\n'.join(rows)
86+
87+
output = '\Qcircuit @R=1em @C=0.75em { \\\\ \n' + grid + ' \\\\ \n \\\\ }'
88+
89+
return output
90+
91+
92+
def _wrap_operation(op: ops.Operation,
93+
ext: extension.Extensions) -> ops.Operation:
94+
new_qubits = [_QCircuitQubit(e) for e in op.qubits]
95+
new_gate = ext.try_cast(op.gate, QCircuitDiagrammableGate)
96+
if new_gate is None:
97+
new_gate = fallback_qcircuit_extensions.cast(op.gate,
98+
QCircuitDiagrammableGate)
99+
return ops.Operation(_QCircuitGate(new_gate), new_qubits)
100+
101+
102+
def _wrap_moment(moment: circuits.Moment,
103+
ext: extension.Extensions) -> circuits.Moment:
104+
return circuits.Moment(_wrap_operation(op, ext)
105+
for op in moment.operations)
106+
107+
108+
def _wrap_circuit(circuit: circuits.Circuit,
109+
ext: extension.Extensions) -> circuits.Circuit:
110+
return circuits.Circuit(_wrap_moment(moment, ext)
111+
for moment in circuit.moments)
112+
113+
114+
def circuit_to_latex_using_qcircuit(
115+
circuit: circuits.Circuit,
116+
ext: extension.Extensions = None,
117+
qubit_order_key: Callable[[ops.QubitId], Any] = None) -> str:
118+
"""Returns a QCircuit-based latex diagram of the given circuit.
119+
120+
Args:
121+
circuit: The circuit to represent in latex.
122+
ext: Extensions used when attempting to cast gates into
123+
QCircuitDiagrammableGate instances (before falling back to the
124+
default wrapping methods).
125+
qubit_order_key: Determines the order of qubit wires in the diagram.
126+
127+
Returns:
128+
Latex code for the diagram.
129+
"""
130+
if ext is None:
131+
ext = extension.Extensions()
132+
qcircuit = _wrap_circuit(circuit, ext)
133+
diagram = qcircuit.to_text_diagram_drawer(
134+
ext,
135+
qubit_name_suffix='',
136+
qubit_order_key=(None
137+
if qubit_order_key is None
138+
else lambda e: qubit_order_key(e.sub)))
139+
return _render(diagram)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright 2018 Google LLC
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+
# https://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+
from typing import Optional
16+
17+
import cirq
18+
from cirq import Extensions, ops
19+
from cirq import abc
20+
21+
22+
class QCircuitDiagrammableGate(ops.Gate, metaclass=abc.ABCMeta):
23+
@abc.abstractmethod
24+
def qcircuit_wire_symbols(self, qubit_count: Optional[int] = None):
25+
pass
26+
27+
28+
def _escape_text_for_latex(text):
29+
escaped = (text
30+
.replace('\\', '\\textbackslash{}')
31+
.replace('^', '\\textasciicircum{}')
32+
.replace('~', '\\textasciitilde{}')
33+
.replace('_', '\\_')
34+
.replace('{', '\\{')
35+
.replace('}', '\\}')
36+
.replace('$', '\\$')
37+
.replace('%', '\\%')
38+
.replace('&', '\\&')
39+
.replace('#', '\\#'))
40+
return '\\text{' + escaped + '}'
41+
42+
43+
class _HardcodedQCircuitSymbolsGate(QCircuitDiagrammableGate):
44+
def __init__(self, *symbols) -> None:
45+
self.symbols = symbols
46+
47+
def qcircuit_wire_symbols(self, qubit_count=None):
48+
return self.symbols
49+
50+
51+
class _WrappedSymbolsQCircuitGate(QCircuitDiagrammableGate):
52+
def __init__(self, sub: ops.TextDiagrammableGate) -> None:
53+
self.sub = sub
54+
55+
def qcircuit_wire_symbols(self, qubit_count=None):
56+
s = self.sub.text_diagram_wire_symbols()
57+
s = [_escape_text_for_latex(e) for e in s]
58+
e = self.sub.text_diagram_exponent()
59+
if e != 1:
60+
s[0] += '^{' + str(e) + '}'
61+
return tuple('\\gate{' + e + '}' for e in s)
62+
63+
64+
class _FallbackQCircuitSymbolsGate(QCircuitDiagrammableGate):
65+
def __init__(self, sub: ops.Gate) -> None:
66+
self.sub = sub
67+
68+
def qcircuit_wire_symbols(self, qubit_count=None):
69+
name = str(self.sub)
70+
if qubit_count is None:
71+
qubit_count = 1
72+
return tuple(_escape_text_for_latex('{}:{}'.format(name, i))
73+
for i in range(qubit_count))
74+
75+
76+
fallback_qcircuit_extensions = Extensions()
77+
fallback_qcircuit_extensions.add_cast(
78+
QCircuitDiagrammableGate,
79+
ops.TextDiagrammableGate,
80+
_WrappedSymbolsQCircuitGate)
81+
fallback_qcircuit_extensions.add_cast(
82+
QCircuitDiagrammableGate,
83+
ops.RotXGate,
84+
lambda gate:
85+
_HardcodedQCircuitSymbolsGate('\\targ')
86+
if gate.half_turns == 1
87+
else None)
88+
fallback_qcircuit_extensions.add_cast(
89+
QCircuitDiagrammableGate,
90+
ops.MeasurementGate,
91+
lambda gate: _HardcodedQCircuitSymbolsGate('\\meter'))
92+
fallback_qcircuit_extensions.add_cast(
93+
QCircuitDiagrammableGate,
94+
cirq.google.ExpWGate,
95+
lambda gate:
96+
_HardcodedQCircuitSymbolsGate('\\targ')
97+
if gate.half_turns == 1 and gate.axis_half_turns == 0
98+
else None)
99+
fallback_qcircuit_extensions.add_cast(
100+
QCircuitDiagrammableGate,
101+
ops.Rot11Gate,
102+
lambda gate:
103+
_HardcodedQCircuitSymbolsGate('\\control', '\\control')
104+
if gate.half_turns == 1
105+
else None)
106+
fallback_qcircuit_extensions.add_cast(
107+
QCircuitDiagrammableGate,
108+
ops.CNotGate,
109+
lambda gate: _HardcodedQCircuitSymbolsGate('\\control', '\\targ'))
110+
fallback_qcircuit_extensions.add_cast(
111+
QCircuitDiagrammableGate,
112+
ops.Gate,
113+
_FallbackQCircuitSymbolsGate)

cirq/contrib/qcircuit_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2018 Google LLC
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+
# https://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+
from cirq import ops, Circuit
16+
from cirq.contrib import circuit_to_latex_using_qcircuit
17+
18+
19+
def test_teleportation_diagram():
20+
ali = ops.NamedQubit('alice')
21+
car = ops.NamedQubit('carrier')
22+
bob = ops.NamedQubit('bob')
23+
24+
circuit = Circuit.from_ops(
25+
ops.H(car),
26+
ops.CNOT(car, bob),
27+
ops.X(ali)**0.5,
28+
ops.CNOT(ali, car),
29+
ops.H(ali),
30+
[ops.MeasurementGate()(ali), ops.MeasurementGate()(car)],
31+
ops.CNOT(car, bob),
32+
ops.CZ(ali, bob))
33+
34+
diagram = circuit_to_latex_using_qcircuit(
35+
circuit,
36+
qubit_order_key=[ali, car, bob].index)
37+
assert diagram.strip() == """
38+
\\Qcircuit @R=1em @C=0.75em { \\\\
39+
\\lstick{\\text{alice}}& \\qw &\\qw & \\gate{\\text{X}^{0.5}} \\qw & \\control \\qw & \\gate{\\text{H}} \\qw & \\meter \\qw &\\qw & \\control \\qw &\\qw\\\\
40+
\\lstick{\\text{carrier}}& \\qw & \\gate{\\text{H}} \\qw & \\control \\qw & \\targ \\qw \\qwx &\\qw & \\meter \\qw & \\control \\qw &\\qw \\qwx &\\qw\\\\
41+
\\lstick{\\text{bob}}& \\qw &\\qw & \\targ \\qw \\qwx &\\qw &\\qw &\\qw & \\targ \\qw \\qwx & \\control \\qw \\qwx &\\qw \\\\
42+
\\\\ }
43+
""".strip()

0 commit comments

Comments
 (0)