diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 6c463098b..7514e877e 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -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): @@ -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, @@ -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") @@ -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: @@ -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 @@ -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): List of length 2 denoting the target qubit of + the Swap gate. + ctrl_lines (list): 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): """ @@ -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): 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): """ @@ -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) @@ -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=-" diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index 4e8ed3d4d..b97d7eff2 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -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 @@ -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, []) @@ -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() @@ -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