Skip to content

Commit

Permalink
Merge pull request #112 from QunaSys/circuit_model
Browse files Browse the repository at this point in the history
  • Loading branch information
Hiroya-W authored Jun 20, 2023
2 parents c7f38e6 + 86327e9 commit 79ef096
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 156 deletions.
Empty file added qulacsvis/models/__init__.py
Empty file.
111 changes: 111 additions & 0 deletions qulacsvis/models/circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import dataclasses
from collections import deque
from itertools import chain
from typing import Deque, List, Optional, Sequence, Tuple

import dataclasses_json


@dataclasses_json.dataclass_json
@dataclasses.dataclass
class ControlQubitInfo:
index: int
control_value: int


@dataclasses_json.dataclass_json
@dataclasses.dataclass
class GateData:
name: str
target_bits: List[int] = dataclasses.field(default_factory=list)
control_bit_infos: List[ControlQubitInfo] = dataclasses.field(default_factory=list)

@property
def indices(self) -> Tuple[int, ...]:
return tuple(chain(self.target_bits, (c.index for c in self.control_bit_infos)))

@property
def max_index(self) -> int:
return max(self.indices)

@property
def min_index(self) -> int:
return min(self.indices)


GateDataSeq = Sequence[Sequence[GateData]]


@dataclasses_json.dataclass_json
@dataclasses.dataclass
class CircuitData:
qubit_count: int
layer_count: int
gates: GateDataSeq

@staticmethod
def from_gate_sequence(
gates: Sequence[GateData], qubit_count: Optional[int] = None
) -> "CircuitData":
"""
Construct a CircuitData model from a list of gates.
Parameters
----------
gates : Sequence[GateData]
List of gates
qubit_count : int optional
Number of qubits this quantum circuit has
Returns
-------
CircuitData : CircuitData
Constructed CircuitData model
"""
if qubit_count is None:
qubit_count = max(g.max_index for g in gates)
temp_lines: List[Deque[GateData]] = [deque() for _ in range(qubit_count)]
for gate in gates:
_align_layers(temp_lines, gate.min_index, gate.max_index)
for index in range(gate.min_index, gate.max_index + 1):
line = temp_lines[index]
if index == gate.target_bits[0]:
line.append(gate)
elif index in gate.target_bits:
line.append(GateData("ghost"))
else:
line.append(GateData("wire"))

_align_layers(temp_lines, 0, qubit_count)
layer_count = len(temp_lines[0])
return CircuitData(
qubit_count=qubit_count,
layer_count=layer_count,
gates=[list(queue) for queue in temp_lines],
)


def _align_layers(
lines: Sequence[Deque[GateData]], min_line_index: int, max_line_index: int
) -> None:
"""
Align layer sizes for a specified range of rows.
Parameters
----------
lines: Sequence[Deque[GateData]]
Circuit data during parsing without layer length alignment
min_line_index : int
Minimum row index to be aligned.
max_line_index : int
Maximum row index to be aligned.
"""
if min_line_index > max_line_index:
min_line_index, max_line_index = max_line_index, min_line_index
lines = lines[min_line_index : max_line_index + 1]
layer_counts = [len(queue) for queue in lines]
max_layer_count = max(layer_counts)

for queue, layer_count in zip(lines, layer_counts):
for _ in range(max_layer_count - layer_count):
queue.append(GateData("wire"))
Empty file added qulacsvis/qulacs/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions qulacsvis/qulacs/circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from qulacs import QuantumCircuit

from ..models.circuit import CircuitData, ControlQubitInfo, GateData


def to_model(circuit: QuantumCircuit) -> CircuitData:
qubit_count = circuit.get_qubit_count()
gates = []

for position in range(circuit.get_gate_count()):
gate = circuit.get_gate(position)
target_index_list = gate.get_target_index_list()
control_index_value_list = [
ControlQubitInfo(index, control_value)
for index, control_value in gate.get_control_index_value_list()
]
gate_name = gate.get_name()
gates.append(GateData(gate_name, target_index_list, control_index_value_list))

return CircuitData.from_gate_sequence(gates, qubit_count)
7 changes: 4 additions & 3 deletions qulacsvis/visualization/circuit_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from qulacsvis.utils.latex import _LatexCompiler, _PDFtoImage

from ..qulacs.circuit import to_model
from .latex import LatexSourceGenerator
from .matplotlib import MPLCircuitlDrawer
from .text import TextCircuitDrawer
Expand Down Expand Up @@ -109,7 +110,7 @@ def circuit_drawer(

elif output_method == "latex":
with tempfile.TemporaryDirectory() as tmpdir:
generator = LatexSourceGenerator(circuit)
generator = LatexSourceGenerator(to_model(circuit))
latex_source = generator.generate()
latex = _LatexCompiler()
pdftoimage = _PDFtoImage()
Expand All @@ -124,12 +125,12 @@ def circuit_drawer(
return image

elif output_method == "latex_source":
generator = LatexSourceGenerator(circuit)
generator = LatexSourceGenerator(to_model(circuit))
latex_source = generator.generate()
return latex_source

elif output_method == "mpl":
mpl_drawer = MPLCircuitlDrawer(circuit, dpi=dpi, scale=scale)
mpl_drawer = MPLCircuitlDrawer(to_model(circuit), dpi=dpi, scale=scale)
return mpl_drawer.draw(filename=filename)

else:
Expand Down
99 changes: 5 additions & 94 deletions qulacsvis/visualization/circuit_parser.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,6 @@
import dataclasses
from collections import deque
from typing import Deque, List

import dataclasses_json
from qulacs import QuantumCircuit

GATE_DEFAULT_WIDTH = 1.0
GATE_DEFAULT_HEIGHT = 1.5


@dataclasses_json.dataclass_json
@dataclasses.dataclass
class ControlQubitInfo:
index: int
control_value: int


@dataclasses_json.dataclass_json
@dataclasses.dataclass
class GateData:
name: str
target_bits: List[int] = dataclasses.field(default_factory=list)
control_bit_infos: List[ControlQubitInfo] = dataclasses.field(default_factory=list)


CircuitData = List[List[GateData]]
QueueCircuitData = List[Deque[GateData]]
from ..qulacs.circuit import to_model


class CircuitParser:
Expand All @@ -41,75 +16,11 @@ class CircuitParser:
----------
qubit_count : int
Number of qubits in the circuit.
gate_info : CircuitData
gate_info : GateDataSeq
List of gate data.
layer_width : List[float]
Width of each layer.
"""

def __init__(self, circuit: QuantumCircuit):
self.qubit_count = circuit.get_qubit_count()
self.layer_width: List[float] = []
self.__temp_parsed_circuit: QueueCircuitData = [
deque() for _ in range(self.qubit_count)
]
self.parsed_circuit: CircuitData = [[]]

for position in range(circuit.get_gate_count()):
gate = circuit.get_gate(position)
target_index_list = gate.get_target_index_list()
control_index_list = gate.get_control_index_list()
control_index_value_list = [
ControlQubitInfo(index, control_value)
for index, control_value in gate.get_control_index_value_list()
]
gate_name = gate.get_name()

if len(target_index_list) == 0:
print(
f"WARNING: The {position}-th Gate you added is skipped."
'This gate does not have "target_qubit_list".'
)
continue

merged_index = target_index_list + control_index_list
min_merged_index = min(merged_index)
max_merged_index = max(merged_index)
self._align_layers(min_merged_index, max_merged_index)

for index in range(min_merged_index, max_merged_index + 1):
if index == target_index_list[0]:
self.__temp_parsed_circuit[index].append(
GateData(gate_name, target_index_list, control_index_value_list)
)
elif index in target_index_list:
self.__temp_parsed_circuit[index].append(GateData("ghost"))
else:
self.__temp_parsed_circuit[index].append(GateData("wire"))

self._align_layers(0, self.qubit_count)
self.parsed_circuit = [list(queue) for queue in self.__temp_parsed_circuit]
self.layer_width = [
GATE_DEFAULT_WIDTH for _ in range(len(self.parsed_circuit[0]))
]

def _align_layers(self, min_line_index: int, max_line_index: int) -> None:
"""
Align layer sizes for a specified range of rows.
Parameters
----------
min_line_index : int
Minimum row index to be aligned.
max_line_index : int
Maximum row index to be aligned.
"""
if min_line_index > max_line_index:
min_line_index, max_line_index = max_line_index, min_line_index
lines = self.__temp_parsed_circuit[min_line_index : max_line_index + 1]
layer_counts = [len(queue) for queue in lines]
max_layer_count = max(layer_counts)

for queue, layer_count in zip(lines, layer_counts):
for _ in range(max_layer_count - layer_count):
queue.append(GateData("wire"))
circuit_data = to_model(circuit)
self.qubit_count = circuit_data.qubit_count
self.parsed_circuit = circuit_data.gates
34 changes: 12 additions & 22 deletions qulacsvis/visualization/latex.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
from typing import List

import numpy as np
from qulacs import QuantumCircuit

from qulacsvis.models.circuit import CircuitData, ControlQubitInfo, GateData
from qulacsvis.utils.gate import grouping_adjacent_gates, to_latex_style
from qulacsvis.visualization.circuit_parser import (
CircuitParser,
ControlQubitInfo,
GateData,
)


class LatexSourceGenerator:
"""Generate latex source from QuantumCircuit
"""Generate latex source from CircuitData
Parameters
----------
circuit : QuantumCircuit
circuit : CircuitData
A quantum circuit to be drawn.
Attributes
----------
_quantum_circuit : QuantumCircuit
A quantum circuit to be drawn.
_parser : CircuitParser
The parser of the quantum circuit.
_circuit_data : CircuitData
The data of the quantum circuit after parsing by CircuitParser.
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.
Expand All @@ -39,22 +30,21 @@ class LatexSourceGenerator:
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(circuit)
>>> generator = LatexSourceGenerator(to_model(circuit))
>>> latex_source = generator.generate()
>>> print(latex_source)
"""

def __init__(self, circuit: QuantumCircuit):
self._quantum_circuit = circuit
self._parser = CircuitParser(circuit)
self._circuit_data = self._parser.parsed_circuit
def __init__(self, circuit: CircuitData):
self._circuit_data = circuit
self._circuit = np.array([[]])
self._head = r"""
\documentclass[border={-2pt 5pt 5pt -7pt}]{standalone}
Expand All @@ -74,8 +64,8 @@ def generate(self) -> str:
latex_source : str
String of latex source generated
"""
qubit_count = self._parser.qubit_count
circuit_layer_count = len(self._circuit_data[0])
qubit_count = self._circuit_data.qubit_count
circuit_layer_count = self._circuit_data.layer_count

input_label = np.array(
[
Expand All @@ -96,7 +86,7 @@ def generate(self) -> str:
# 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[qubit][layer]
gate = self._circuit_data.gates[qubit][layer]

if gate.name == "ghost":
continue
Expand Down Expand Up @@ -126,7 +116,7 @@ def _matrix_to_qcircuit_style(self, matrix: List[List[str]]) -> str:
Parameters
----------
matrix : List[List[str]]
A matrix containing strings converted from CircuitData for Qcircuit.
A matrix containing strings converted from GateDataSeq for Qcircuit.
Returns
-------
res : str
Expand Down
Loading

0 comments on commit 79ef096

Please sign in to comment.