Skip to content

Commit

Permalink
Fixed bug for large gates and added CZ & Swap gate specialization for…
Browse files Browse the repository at this point in the history
… latex drawer.
  • Loading branch information
thomashaener committed Apr 20, 2017
1 parent bd8ef09 commit 2e364df
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 5 deletions.
97 changes: 95 additions & 2 deletions projectq/backends/_circuits/_to_latex.py
Expand Up @@ -11,7 +11,7 @@
# limitations under the License.

import json
from projectq.ops import Measure, Allocate, Deallocate, X, Z
from projectq.ops import Measure, Allocate, Deallocate, X, Z, Swap


def to_latex(circuit):
Expand Down Expand Up @@ -86,6 +86,8 @@ def get_default_settings():
'pre_offset': .1},
'XGate': {'width': .35, 'height': .35,
'offset': .1},
'SwapGate': {'width': .35, 'height': .35,
'offset': .1},
'Rx': {'width': 1., 'height': .8, 'pre_offset': .2,
'offset': .3},
'Ry': {'width': 1., 'height': .8, 'pre_offset': .2,
Expand Down Expand Up @@ -158,7 +160,12 @@ def _header(settings):
"{linestyle}]\n"
).format(x_rad=x_gate_radius,
linestyle=settings['lines']['style'])

if settings['gate_shadow']:
gate_style += ("\\tikzset{\nshadowed/.style={preaction={transform "
"canvas={shift={(0.5pt,-0.5pt)}}, draw=gray, opacity="
"0.4}},\n}\n")
gate_style += "\\tikzstyle{swapstyle}=["
gate_style += "inner sep=-1pt, outer sep=-1pt, minimum width=0pt]\n"
edge_style = ("\\tikzstyle{edgestyle}=[" + settings['lines']['style'] +
"]\n")

Expand Down Expand Up @@ -275,6 +282,10 @@ def to_tikz(self, line, circuit, end=None):
if not self.is_quantum[lines[0]]:
if sum([self.is_quantum[i] for i in ctrl_lines]) > 0:
self.is_quantum[lines[0]] = True
elif gate == Z and len(ctrl_lines) > 0:
add_str = self._cz_gate(lines + ctrl_lines)
elif gate == Swap:
add_str = self._swap_gate(lines, ctrl_lines)
elif gate == Measure:
# draw measurement gate
for l in lines:
Expand Down Expand Up @@ -325,6 +336,8 @@ def to_tikz(self, line, circuit, end=None):
self.pos[line] += (self._gate_width(gate) +
self._gate_offset(gate))
else:
# regular gate must draw the lines it does not act upon
# if it spans multiple qubits
add_str = self._regular_gate(gate, lines, ctrl_lines)
for l in lines:
self.is_quantum[l] = True
Expand Down Expand Up @@ -354,6 +367,57 @@ def _gate_name(self, gate):
except AttributeError:
name = str(gate)
return name

def _swap_gate(self, lines, ctrl_lines):
"""
Return the TikZ code for a Swap-gate.
Args:
lines (list<int>): List of length 2 denoting the target qubit of
the Swap gate.
ctrl_lines (list<int>): List of qubit lines which act as controls.
"""
assert(len(lines) == 2) # NOT gate only acts on 1 qubit
delta_pos = self._gate_offset(Swap)
gate_width = self._gate_width(Swap)
lines.sort()

gate_str = ""
for line in lines:
op = self._op(line)
w = "{}cm".format(.5*gate_width)
s1 = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=w, op=op)
s2 = "[xshift={w},yshift={w}]{op}.center".format(w=w, op=op)
s3 = "[xshift=-{w},yshift={w}]{op}.center".format(w=w, op=op)
s4 = "[xshift={w},yshift=-{w}]{op}.center".format(w=w, op=op)
swap_style = "swapstyle,edgestyle"
if self.settings['gate_shadow']:
swap_style += ",shadowed"
gate_str += ("\n\\node[swapstyle] ({op}) at ({pos},-{line}) {{}};"
"\n\\draw[{swap_style}] ({s1})--({s2});\n"
"\\draw[{swap_style}] ({s3})--({s4});"
).format(op=op, s1=s1, s2=s2, s3=s3, s4=s4,
line=line, pos=self.pos[line],
swap_style=swap_style)
gate_str += self._line(lines[0], lines[1])

if len(ctrl_lines) > 0:
for ctrl in ctrl_lines:
gate_str += self._phase(ctrl, self.pos[lines[0]])
if ctrl > lines[1] or ctrl < lines[0]:
closer_line = lines[0]
if ctrl > lines[1]:
closer_line = lines[1]
gate_str += self._line(ctrl, closer_line)

all_lines = ctrl_lines + lines
new_pos = self.pos[lines[0]] + delta_pos + gate_width
for i in all_lines:
self.op_count[i] += 1
for i in range(min(all_lines), max(all_lines)+1):
self.pos[i] = new_pos
return gate_str

def _x_gate(self, lines, ctrl_lines):
"""
Expand Down Expand Up @@ -387,6 +451,30 @@ def _x_gate(self, lines, ctrl_lines):
for i in range(min(all_lines), max(all_lines)+1):
self.pos[i] = new_pos
return gate_str

def _cz_gate(self, lines):
"""
Return the TikZ code for an n-controlled Z-gate.
Args:
lines (list<int>): List of all qubits involved.
"""
assert len(lines) > 1
line = lines[0]
delta_pos = self._gate_offset(Z)
gate_width = self._gate_width(Z)
gate_str = self._phase(line, self.pos[line])

for ctrl in lines[1:]:
gate_str += self._phase(ctrl, self.pos[line])
gate_str += self._line(ctrl, line)

new_pos = self.pos[line] + delta_pos + gate_width
for i in lines:
self.op_count[i] += 1
for i in range(min(lines), max(lines)+1):
self.pos[i] = new_pos
return gate_str

def _gate_width(self, gate):
"""
Expand Down Expand Up @@ -540,6 +628,8 @@ def _regular_gate(self, gate, lines, ctrl_lines):
"""
imax = max(lines)
imin = min(lines)

gate_lines = lines + ctrl_lines

delta_pos = self._gate_offset(gate)
gate_width = self._gate_width(gate)
Expand All @@ -562,6 +652,9 @@ def _regular_gate(self, gate, lines, ctrl_lines):
node3 = node_str.format(self._op(l, offset=2),
pos + gate_width, l)
tex_str += node1 + node2 + node3
if not l in gate_lines:
tex_str += self._line(self.op_count[l] - 1, self.op_count[l],
line=l)

tex_str += ("\n\\draw[operator,edgestyle,outer sep={width}cm] (["
"yshift={half_height}cm]{op1}) rectangle ([yshift=-"
Expand Down
45 changes: 42 additions & 3 deletions projectq/backends/_circuits/_to_latex_test.py
Expand Up @@ -19,11 +19,14 @@

from projectq import MainEngine
from projectq.cengines import LastEngineException
from projectq.ops import (H,
from projectq.ops import (BasicGate,
H,
X,
CNOT,
Measure,
Z)
Z,
Swap,
C)
from projectq.meta import Control
from projectq.backends import CircuitDrawer

Expand Down Expand Up @@ -90,6 +93,38 @@ def test_header():
assert 'minimum height=0cm' in header


def test_large_gates():
drawer = _drawer.CircuitDrawer()
eng = MainEngine(drawer, [])
old_tolatex = _drawer.to_latex
_drawer.to_latex = lambda x: x

qubit1 = eng.allocate_qubit()
qubit2 = eng.allocate_qubit()
qubit3 = eng.allocate_qubit()

class MyLargeGate(BasicGate):
def __str__(self):
return "large_gate"

H | qubit2
MyLargeGate() | (qubit1, qubit3)
H | qubit2
eng.flush()

circuit_lines = drawer.get_latex()
_drawer.to_latex = old_tolatex

settings = _to_latex.get_default_settings()
settings['gates']['AllocateQubitGate']['draw_id'] = True
code = _to_latex._body(circuit_lines, settings)

assert code.count("large_gate") == 1 # 1 large gate was applied
# check that large gate draws lines, also for qubits it does not act upon
assert code.count("edge[") == 5
assert code.count("{H};") == 2


def test_body():
drawer = _drawer.CircuitDrawer()
eng = MainEngine(drawer, [])
Expand All @@ -106,7 +141,9 @@ def test_body():
Measure | qubit2
CNOT | (qubit2, qubit1)
Z | qubit2

C(Z) | (qubit1, qubit2)
Swap | (qubit1, qubit2)

del qubit1
eng.flush()

Expand All @@ -117,6 +154,8 @@ def test_body():
settings['gates']['AllocateQubitGate']['draw_id'] = True
code = _to_latex._body(circuit_lines, settings)

assert code.count("swapstyle") == 6 # swap draws 2 nodes + 2 lines each
assert code.count("phase") == 4 # CZ is two phases plus 2 from CNOTs
assert code.count("{{{}}}".format(str(H))) == 2 # 2 hadamard gates
assert code.count("{$\Ket{0}") == 2 # two qubits allocated
assert code.count("xstyle") == 3 # 1 cnot, 1 not gate
Expand Down

0 comments on commit 2e364df

Please sign in to comment.