diff --git a/cirq-core/cirq/contrib/qcircuit/pdf_test_files/test_pdf.pdf b/cirq-core/cirq/contrib/qcircuit/pdf_test_files/test_pdf.pdf new file mode 100644 index 00000000000..3483e782e87 Binary files /dev/null and b/cirq-core/cirq/contrib/qcircuit/pdf_test_files/test_pdf.pdf differ diff --git a/cirq-core/cirq/contrib/qcircuit/pdf_test_files/test_pdf.tex b/cirq-core/cirq/contrib/qcircuit/pdf_test_files/test_pdf.tex new file mode 100644 index 00000000000..bfb7163003e --- /dev/null +++ b/cirq-core/cirq/contrib/qcircuit/pdf_test_files/test_pdf.tex @@ -0,0 +1,21 @@ +\documentclass{article}% +\usepackage[T1]{fontenc}% +\usepackage[utf8]{inputenc}% +\usepackage{lmodern}% +\usepackage{textcomp}% +\usepackage{lastpage}% +\usepackage{amsmath}% +\usepackage{qcircuit}% +% +\usepackage[utf8]{inputenc}% +% +\begin{document}% +\normalsize% +\Qcircuit @R=1em @C=0.75em { + \\ + &\lstick{\text{q(0)}}& \qw&\ctrlo{} \qw &\control \qw &\targ \qw &\qswap \qw& \qw&\qw\\ + &\lstick{\text{q(1)}}& \qw&\control \qw\qwx&\targ \qw\qwx&\ctrlo{} \qw\qwx&\qswap\qwx \qw&\qswap \qw&\qw\\ + &\lstick{\text{q(2)}}& \qw&\targ \qw\qwx&\ctrlo{} \qw\qwx&\control \qw\qwx& \qw&\qswap\qwx \qw&\qw\\ + \\ +}% +\end{document} \ No newline at end of file diff --git a/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info.py b/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info.py index 0f6cae2c68f..e792a7b84ff 100644 --- a/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info.py +++ b/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info.py @@ -33,6 +33,10 @@ def escape_text_for_latex(text): return r'\text{' + escaped + '}' +def escape_text_for_latex_custom_control_gate(text): + return text.replace('(0)', r'\ctrlo{}').replace('@', r'\control').replace('X', r'\targ') + + def get_multigate_parameters(args: protocols.CircuitDiagramInfoArgs) -> Optional[Tuple[int, int]]: if (args.label_map is None) or (args.known_qubits is None): return None @@ -67,10 +71,20 @@ def hardcoded_qcircuit_diagram_info(op: ops.Operation) -> Optional[protocols.Cir def convert_text_diagram_info_to_qcircuit_diagram_info( info: protocols.CircuitDiagramInfo, ) -> protocols.CircuitDiagramInfo: - labels = [escape_text_for_latex(e) for e in info.wire_symbols] - if info.exponent != 1: - labels[0] += '^{' + str(info.exponent) + '}' - symbols = tuple(r'\gate{' + l + '}' for l in labels) + symbols = None + if ( + len(info.wire_symbols) == 3 + and '(0)' in info.wire_symbols + and '@' in info.wire_symbols + and 'X' in info.wire_symbols + ): + labels = [escape_text_for_latex_custom_control_gate(e) for e in info.wire_symbols] + symbols = tuple(l for l in labels) + else: + labels = [escape_text_for_latex(e) for e in info.wire_symbols] + if info.exponent != 1: + labels[0] += '^{' + str(info.exponent) + '}' + symbols = tuple(r'\gate{' + l + '}' for l in labels) return protocols.CircuitDiagramInfo(symbols) @@ -89,17 +103,30 @@ def multigate_qcircuit_diagram_info( info = protocols.circuit_diagram_info(op, args, default=None) min_index, n_qubits = multigate_parameters - name = escape_text_for_latex( - str(op.gate).rsplit('**', 1)[0] if isinstance(op, ops.GateOperation) else str(op) - ) - if (info is not None) and (info.exponent != 1): - name += '^{' + str(info.exponent) + '}' - box = r'\multigate{' + str(n_qubits - 1) + '}{' + name + '}' - ghost = r'\ghost{' + name + '}' + + symbols = None assert args.label_map is not None assert args.known_qubits is not None - symbols = tuple(box if (args.label_map[q] == min_index) else ghost for q in args.known_qubits) - # Force exponent=1 to defer to exponent formatting given above. + + if op.gate == ops.SWAP: + box = r'\qswap' + ghost = r'\qswap\qwx' + symbols = tuple( + box if (args.label_map[q] == min_index) else ghost for q in args.known_qubits + ) + else: + name = escape_text_for_latex( + str(op.gate).rsplit('**', 1)[0] if isinstance(op, ops.GateOperation) else str(op) + ) + if (info is not None) and (info.exponent != 1): + name += '^{' + str(info.exponent) + '}' + box = r'\multigate{' + str(n_qubits - 1) + '}{' + name + '}' + ghost = r'\ghost{' + name + '}' + symbols = tuple( + box if (args.label_map[q] == min_index) else ghost for q in args.known_qubits + ) + # Force exponent=1 to defer to exponent formatting given above. + return protocols.CircuitDiagramInfo(symbols, connected=False) diff --git a/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info_test.py b/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info_test.py index 69bbf36ab51..a7a03bde2ed 100644 --- a/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info_test.py +++ b/cirq-core/cirq/contrib/qcircuit/qcircuit_diagram_info_test.py @@ -47,9 +47,7 @@ def test_get_qcircuit_diagram_info(): label_map=qubit_map, ) actual_info = ccq.get_qcircuit_diagram_info(op, args) - expected_info = cirq.CircuitDiagramInfo( - (r'\ghost{\text{SWAP}}', r'\multigate{1}{\text{SWAP}}'), connected=False - ) + expected_info = cirq.CircuitDiagramInfo((r'\qswap\qwx', r'\qswap'), connected=False) assert actual_info == expected_info qubit_map = {q: i for q, i in zip(qubits, (2, 5))} diff --git a/cirq-core/cirq/contrib/qcircuit/qcircuit_test.py b/cirq-core/cirq/contrib/qcircuit/qcircuit_test.py index a6f7f408612..7dc2b826838 100644 --- a/cirq-core/cirq/contrib/qcircuit/qcircuit_test.py +++ b/cirq-core/cirq/contrib/qcircuit/qcircuit_test.py @@ -16,6 +16,8 @@ import cirq.contrib.qcircuit as ccq import cirq.testing as ct +# import cirq.contrib.qcircuit.qcircuit_pdf as pdf + def assert_has_qcircuit_diagram(actual: cirq.Circuit, desired: str, **kwargs) -> None: """Determines if a given circuit has the desired qcircuit diagram. @@ -165,3 +167,66 @@ def test_sqrt_iswap_diagram(): \\ }""".strip() assert_has_qcircuit_diagram(circuit, expected_diagram) + + +def test_latex_formatting(): + # test for proper rendering of failing latex formats + q0, q1, q2 = cirq.LineQubit.range(3) + + # custom gate with a control for zero and one + custom_gate = cirq.X.controlled(2, control_values=(0, 1)) + + circuit = cirq.Circuit( + custom_gate(q0, q1, q2), + custom_gate(q2, q0, q1), + custom_gate(q1, q2, q0), + cirq.SWAP(q0, q1), + cirq.SWAP(q1, q2), + ) + + expected_diagram = r""" +\Qcircuit @R=1em @C=0.75em { + \\ + &\lstick{\text{q(0)}}& \qw&\ctrlo{} \qw &\control \qw &\targ \qw &\qswap \qw& \qw&\qw\\ + &\lstick{\text{q(1)}}& \qw&\control \qw\qwx&\targ \qw\qwx&\ctrlo{} \qw\qwx&\qswap\qwx \qw&\qswap \qw&\qw\\ + &\lstick{\text{q(2)}}& \qw&\targ \qw\qwx&\ctrlo{} \qw\qwx&\control \qw\qwx& \qw&\qswap\qwx \qw&\qw\\ + \\ +}""".strip() + + assert_has_qcircuit_diagram(circuit, expected_diagram) + + +# def test_pdf_custom_gates(): +# # test for proper rendering of failing latex formats in a pdf +# q0, q1, q2 = cirq.LineQubit.range(3) + +# # custom gate with a control for zero and one +# custom_gate = cirq.X.controlled(2, control_values=(0, 1)) + +# circuit = cirq.Circuit( +# custom_gate(q0, q1, q2), +# custom_gate(q2, q0, q1), +# custom_gate(q1, q2, q0), +# cirq.SWAP(q0, q1), +# cirq.SWAP(q1, q2), +# ) + +# pdf_kwargs = { +# 'compiler': 'latexmk', +# 'compiler_args': ['-pdf', '-f', '-pdflatex=xelatex', '-quiet'], +# } + +# remove = ['aux', 'fdb_latexmk', 'fls', 'log'] + +# # This test was facing errors due to technical issues in qcuircuit_pdf.py +# # that are out of scope of my issue (4685) +# # It will remain commented out until a resolution is found, +# # or until it is realized that this test is not necessary + +# pdf.circuit_to_pdf_using_qcircuit_via_tex( +# circuit, +# "./cirq-core/cirq/contrib/qcircuit/pdf_test_files/test", +# pdf_kwargs, +# None, +# remove +# )