Source code for qulacsvis.visualization.latex

from typing import List

import numpy as np

from qulacsvis.models.circuit import CircuitData, ControlQubitInfo, GateData
from qulacsvis.utils.gate import grouping_adjacent_gates, to_latex_style


[docs]class LatexSourceGenerator: """Generate latex source from CircuitData Parameters ---------- circuit : CircuitData A quantum circuit to be drawn. Attributes ---------- _circuit_data : CircuitData The data of the quantum circuit. _circuit : numpy.ndarray A matrix containing strings converted from CircuitData for Qcircuit. Each element and its position corresponds to one of GateData. Quantum circuit only, input values are not contained. _head : str The head of the latex source containing preamble. _tail : str The tail of the latex source. Examples -------- >>> from qulacs import QuantumCircuit >>> from qulacsvis.qulacs.circuit import to_model >>> from qulacsvis.visualization import LatexSourceGenerator >>> >>> circuit = QuantumCircuit(3) >>> circuit.add_X_gate(0) >>> circuit.add_Y_gate(1) >>> circuit.add_Z_gate(2) >>> >>> generator = LatexSourceGenerator(to_model(circuit)) >>> latex_source = generator.generate() >>> print(latex_source) """ def __init__(self, circuit: CircuitData): self._circuit_data = circuit self._circuit = np.array([[]]) self._head = r""" \documentclass[border={-2pt 5pt 5pt -7pt}]{standalone} \usepackage[braket, qm]{qcircuit} \usepackage{graphicx} \begin{document} \Qcircuit @C=1.0em @R=0.7em @!R{ \\ """ self._tail = r" }" + "\n" + r"\end{document}"
[docs] def generate(self) -> str: """Generate latex source from QuantumCircuit Returns ------- latex_source : str String of latex source generated """ qubit_count = self._circuit_data.qubit_count circuit_layer_count = self._circuit_data.layer_count input_label = np.array( [ [ # nghost reserves drawing area for input label, # adjusts the spacing between rows. r"\nghost{ q_{" + str(i) + "} : }", r"\lstick{ q_{" + str(i) + "} : }", ] for i in range(qubit_count) ] ) self._circuit = np.array([[] for _ in range(qubit_count)]) for layer in range(circuit_layer_count): # This is an array containing strings. # The string for each element is the string for the Qcircuit of the gate # corresponding to each qubit row of the layer currently of interest. current_layer_latex = [to_latex_style("wire") for _ in range(qubit_count)] for qubit in range(qubit_count): gate = self._circuit_data.gates[qubit][layer] if gate.name == "ghost": continue elif gate.name == "wire": continue elif gate.name == "CNOT": self._cnot(current_layer_latex, gate) elif gate.name == "Toffoli": self._cnot(current_layer_latex, gate) elif gate.name == "SWAP": self._swap(current_layer_latex, gate) elif len(gate.target_bits) > 1: self._multi_gate(current_layer_latex, gate) else: self._gate(current_layer_latex, gate) self._circuit = np.column_stack([self._circuit, current_layer_latex]) # type: ignore wires = np.array([[r"\qw"] for _ in range(qubit_count)]) circuit_with_label = np.column_stack([input_label, self._circuit, wires]) # type: ignore body = self._matrix_to_qcircuit_style(circuit_with_label) return self._head + body + self._tail
def _matrix_to_qcircuit_style(self, matrix: List[List[str]]) -> str: """Generate from matrix to string for Qcircuit Parameters ---------- matrix : List[List[str]] A matrix containing strings converted from GateDataSeq for Qcircuit. Returns ------- res : str Generated string for Qcircuit from matrix """ lines = [" & ".join(line) for line in matrix] # add indent for latex source file indent = " " lines = [indent + line for line in lines] res = (r"\\" + "\n").join(lines) res += r"\\" + "\n" return res def _cnot(self, layer_latex: List[str], gate: GateData) -> None: """Generate CNOT gate for Qcircuit Parameters ---------- layer_latex : List[str] Array containing the string of the gate of the layer currently of interest gate : GateData The gate data to be drawn. """ cnot_qcircuit_style = to_latex_style(gate.name) target_bit = gate.target_bits[0] layer_latex[target_bit] = cnot_qcircuit_style self._control_bits(layer_latex, gate.control_bit_infos, target_bit) def _control_bits( self, layer_latex: List[str], control_bit_infos: List[ControlQubitInfo], target_bit: int, ) -> None: """Generate control bits for Qcircuit Parameters ---------- layer_latex : List[str] Array containing the string of the gate of the layer currently of interest control_bits : List[int] The control bits of the gate to be drawn. target_bit : int A target bit of the gate. This value is used to generate the line connecting the target bit and control bits. """ if len(control_bit_infos) == 0: return merged_bit_infos = [ControlQubitInfo(target_bit, 1)] merged_bit_infos.extend(control_bit_infos) merged_bit_infos.sort(key=lambda x: x.index) for info, next_info in zip(merged_bit_infos, merged_bit_infos[1:]): if info.index >= target_bit: info, next_info = next_info, info control_bit = info.index if info.control_value == 0: ctrl_cmd = r"\ctrlo{" else: ctrl_cmd = r"\ctrl{" layer_latex[control_bit] = ( ctrl_cmd + str(next_info.index - info.index) + "}" ) def _swap(self, layer_latex: List[str], gate: GateData) -> None: """Generate SWAP gate for Qcircuit Parameters ---------- layer_latex : List[str] Array containing the string of the gate of the layer currently of interest gate : GateData The gate data to be drawn. """ swap_qcircuit_style = to_latex_style(gate.name) target_index_list = gate.target_bits swap = (target_index_list[0], target_index_list[-1]) layer_latex[swap[0]] = swap_qcircuit_style layer_latex[swap[0]] += r" \qwx[" + str(swap[1] - swap[0]) + r"]" layer_latex[swap[1]] = swap_qcircuit_style def _multi_gate(self, layer_latex: List[str], gate: GateData) -> None: """Generate multi gate for Qcircuit (e.g., DensityMatrixGate) Parameters ---------- layer_latex : List[str] Array containing the string of the gate of the layer currently of interest gate : GateData The gate data to be drawn. """ gate_name_qcircuit_style = to_latex_style(gate.name) groups_adjacent_gates = grouping_adjacent_gates(gate.target_bits) self._control_bits(layer_latex, gate.control_bit_infos, gate.target_bits[0]) for adjacent_gates in groups_adjacent_gates: size = len(adjacent_gates) - 1 target_bit = adjacent_gates[0] # The "\multi_gate" should be placed in one location and "\ghost" in the other. layer_latex[target_bit] = ( r"\multigate{" + str(size) + "}{" + gate_name_qcircuit_style + "}" ) for target_bit in adjacent_gates[1:]: layer_latex[target_bit] = r"\ghost{" + gate_name_qcircuit_style + "}" if len(groups_adjacent_gates) > 1: # Generate the line connecting the each group for group1, group2 in zip(groups_adjacent_gates, groups_adjacent_gates[1:]): from_ = group1[-1] to_ = group2[0] size = to_ - from_ layer_latex[from_] += r" \qwx[" + str(size) + "]" def _gate(self, layer_latex: List[str], gate: GateData) -> None: """Generate a gate for Qcircuit Parameters ---------- layer_latex : List[str] Array containing the string of the gate of the layer currently of interest gate : GateData The gate data to be drawn. """ try: gate_qcircuit_style = r"\gate{" + to_latex_style(gate.name) + "}" except KeyError: gate_qcircuit_style = r"\gate{UDF}" target_bit = gate.target_bits[0] layer_latex[target_bit] = gate_qcircuit_style self._control_bits(layer_latex, gate.control_bit_infos, target_bit)