Source code for geqo.visualization.latex
import os
import re
import subprocess
import tempfile
from IPython.display import display
from PIL import Image
from sympy import Symbol
import geqo.algorithms as Algorithms
import geqo.gates as Gates
from geqo.__logger__ import get_logger
from geqo.algorithms.algorithms import PermuteQubits, QubitReversal
from geqo.core.basic import BasicGate, InverseBasicGate
from geqo.core.quantum_circuit import Sequence
from geqo.core.quantum_operation import QuantumOperation
from geqo.initialization.state import SetBits, SetDensityMatrix, SetQubits
from geqo.operations.controls import (
ClassicalControl,
QuantumControl,
)
from geqo.operations.measurement import DropQubits, Measure
from geqo.simulators.base import Simulator
from geqo.utils._base_.helpers import embedSequences
from geqo.visualization.common import (
get_gate_name,
greek_letters,
pack_gates,
valid_angle,
valid_name,
)
logger = get_logger(__name__)
def phase_name(
gate: QuantumOperation,
string: str,
greek_string: str,
backend: Simulator,
greek_symbol: bool,
non_pccm: bool = True,
):
r"""Return the correct LaTeX string format of the name of a phase-related gate,
used in the quantikz command like `\\gate{gate_name}`.
Args:
gate (QuantumOperation): The target phase-related gate.
string (str): The LaTeX string template (e.g., `r"P({})"`) for formatting the gate name.
greek_string (str): A template string that turns `gate.name` into a Greek letter
in LaTeX (e.g., `r"P(\\{})"` or `r"Rx($\\{}$)"`).
backend (Simulator): The simulator instance that might store the phase value.
greek_symbol (bool): Whether to convert `gate.name` into a Greek letter.
non_pccm (bool, optional): Set to `True` if the gate is not `PCCM()` or `InversePCCM()`. Defaults to `True`.
Returns:
str: The formatted LaTeX string for use in quantikz diagrams.
"""
name = gate.name if not isinstance(gate, Algorithms.InversePCCM) else gate.pccm.name
if backend is not None:
try: # if backend exists, try retrieving the assinged phase value
if isinstance(backend.values[name], Symbol):
symbol = str(backend.values[name])
if re.search(
r"[^a-zA-Z0-9 _\-]", symbol
): # if the resulting name is not the regular text accepted by quantikz (i.e. θ)
symbol = name
if (symbol in greek_letters) and (greek_symbol):
name = greek_string.format(symbol)
else:
name = valid_angle(symbol, non_pccm)
name = string.format(name)
else:
angle = round(backend.values[name], 2)
name = string.format(angle)
except KeyError:
logger.exception("Phase angle '%s' is not specified in the backend", name)
if greek_symbol is True:
if name in greek_letters: # if gate.name is a valid greek letter
name = greek_string.format(name)
else: # if gate.name is not a valid greek letter
name = valid_angle(name, non_pccm)
name = string.format(name)
else:
name = valid_angle(name, non_pccm)
name = string.format(name)
else: # backend does not exist
if greek_symbol is True:
if name in greek_letters:
name = greek_string.format(name)
else:
name = valid_angle(name, non_pccm)
name = string.format(name)
else:
name = valid_angle(name, non_pccm)
name = string.format(name)
return name
[docs]
def tolatex(
seq: Sequence,
backend: Simulator = None,
decompose_subseq: bool = False,
pack=True,
fold: int = 9,
greek_symbol: bool = True,
**kwargs,
) -> str:
"""Convert a `Sequence` object into LaTeX quantikz code for visualizing quantum circuits.
Args:
seq (Sequence): The geqo Sequence to convert.
backend (Simulator, optional): The simulator instance used for resolving symbolic or numeric values in the circuit.
decompose_subseq (bool, optional): Whether to decompose any subsequences inside the main sequence. Defaults to `False`.
pack (bool, optional): Whether to compactify the circuit by placing non-conflicting operations in the same column. Defaults to `True`.
fold (int, optional): The maximum number of columns to display before folding the circuit. Defaults to 9.
greek_symbol (bool, optional): Whether to convert gate names into Greek letters. Defaults to `True`.
**kwargs: Additional styling parameters for customizing the output.
Returns:
str: The LaTeX (quantikz) code representing the circuit diagram.
"""
# decompose the subsequences if requested
if decompose_subseq:
seq = embedSequences(seq)
bits = seq.bits
qubits = seq.qubits
operations = []
for op in seq.gatesAndTargets:
if isinstance(op[0], ClassicalControl):
ctrl_bits = op[1][: len(op[0].onoff)]
target_qubits = op[1][len(op[0].onoff) :]
int_ctargets = [
target if type(target) is int else seq.bits.index(target)
for target in ctrl_bits
]
int_qtargets = [
target if type(target) is int else seq.qubits.index(target)
for target in target_qubits
]
int_targets = int_ctargets + int_qtargets
else:
int_targets = [
target if type(target) is int else seq.qubits.index(target)
for target in op[1]
]
if len(op) == 2: # non measurement
operations.append((op[0], int_targets))
else: # measurement
int_ctargets = [
target if type(target) is int else bits.index(target)
for target in op[2]
]
operations.append((op[0], int_targets, int_ctargets))
# compactify the sequence into columns for visualization if requested
if pack:
columns = pack_gates(operations)
else:
columns = [
[op] for op in operations
] # each column contains one operation if not packed.
# set up styling parameters
gate_style = f"style={{draw={kwargs.get('edgecolor', 'black')},fill={kwargs.get('facecolor', 'white')}!{kwargs.get('facecolor_intensity', '20')}}},label style={kwargs.get('gate_label_color', 'black')}"
target_label_style = f"label style={kwargs.get('target_label_color', 'black')}"
measure_style = f"style={{draw={kwargs.get('measure_edgecolor', 'black')},fill={kwargs.get('measure_facecolor', 'white')}!{kwargs.get('facecolor_intensity', '20')}}}"
# initialize the list for storing constituent quantikz code strings, starting with setting the global styling.
circuit = [
f"\\begin{{quantikz}}[color={kwargs.get('edgecolor', 'black')},background color={kwargs.get('facecolor', 'white')}]"
]
# calculate the number of circuit rows given the fold value
if len(columns) % fold == 0:
num_rows = len(columns) // fold
else:
num_rows = len(columns) // fold + 1
# iterate over each circuit row
for row in range(num_rows):
# wrap up all the qubit wires in a dictionary, with each qubit wire's quantikz commands stored in a list
# lines = {q: [f"\\lstick{{$q_{q}$}}&"] for q in range(len(qubits))}
lines = {q: [f"\\lstick{{${qname}$}}&"] for q, qname in enumerate(qubits)}
# calculate the number of columns included in a row
if row < len(columns) // fold:
num = fold
else:
num = len(columns) % fold
# iterate over each column in the given row
for column_index in range(row * fold, row * fold + num):
targets = [
pair[1] for idx, pair in enumerate(columns[column_index])
] # target qubits of a certain column
gates = [
pair[0] for idx, pair in enumerate(columns[column_index])
] # gates applied to a certain column
measures = [
pair[2] if isinstance(pair[0], Measure) else None
for pair in columns[column_index]
]
# non-targeted qubits
nontargets = list(
set([qubits.index(q) for q in qubits])
- set([qubit for subtargets in targets for qubit in subtargets])
)
for n in nontargets:
lines[n].append(
r"\qw &"
) # insert empty quantum wire for non-targeted qubits
for i in range(
len(columns[column_index])
): # iterate over all the operations in the same column
if len(targets[i]) == 1: # single-qubit gate
if isinstance(
gates[i], (BasicGate, InverseBasicGate, Sequence)
): # self-defined gate or Sequence
valid_name(gates[i].name) # check if the name is valid
name = gates[i].name
name = r"{}".format(name)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
) # append the self-defined single-qubit gate
if isinstance(gates[i], QuantumOperation) and not isinstance(
gates[i], (BasicGate, InverseBasicGate, Sequence)
): # single-qubit geqo gates defined in geqo_gates.py
# specify the name formats of phase-related gates and S-gate, then append the resulting gates to the quantum wires
if isinstance(gates[i], (Gates.SGate, Gates.InverseSGate)):
name = (
r"S"
if isinstance(gates[i], Gates.SGate)
else r"S^\dagger"
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
elif isinstance(gates[i], (Gates.Phase, Gates.InversePhase)):
string = (
r"P({})"
if isinstance(gates[i], Gates.Phase)
else r"P^\dagger({})"
)
greek_string = (
r"P(\{})"
if isinstance(gates[i], Gates.Phase)
else r"P^\dagger(\{})"
)
name = phase_name(
gates[i],
string,
greek_string,
backend,
greek_symbol,
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
elif isinstance(gates[i], (Gates.Rx, Gates.InverseRx)):
string = (
r"Rx({})"
if isinstance(gates[i], Gates.Rx)
else r"Rx^\dagger({})"
)
greek_string = (
r"Rx(\{})"
if isinstance(gates[i], Gates.Rx)
else r"Rx^\dagger(\{})"
)
name = phase_name(
gates[i],
string,
greek_string,
backend,
greek_symbol,
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
elif isinstance(gates[i], (Gates.Ry, Gates.InverseRy)):
string = (
r"Ry({})"
if isinstance(gates[i], Gates.Ry)
else r"Ry^\dagger({})"
)
greek_string = (
r"Ry(\{})"
if isinstance(gates[i], Gates.Ry)
else r"Ry^\dagger(\{})"
)
name = phase_name(
gates[i],
string,
greek_string,
backend,
greek_symbol,
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
elif isinstance(gates[i], (Gates.Rz, Gates.InverseRz)):
string = (
r"Rz({})"
if isinstance(gates[i], Gates.Rz)
else r"Rz^\dagger({})"
)
greek_string = (
r"Rz(\{})"
if isinstance(gates[i], Gates.Rz)
else r"Rz^\dagger(\{})"
)
name = phase_name(
gates[i],
string,
greek_string,
backend,
greek_symbol,
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
elif isinstance(gates[i], Measure):
lines[targets[i][0]].append(
f"\\meter[{measure_style}]{{{measures[i][0]}}}&"
)
elif isinstance(gates[i], DropQubits):
lines[targets[i][0]].append(
f"\\push{{\\text{{\\Large \\textcolor{{{kwargs.get('edgecolor', 'black')}}}{{X}}}}}}&"
)
elif isinstance(gates[i], SetBits):
if backend is not None:
name = gates[i].name
try:
bits = backend.values[name]
bit = bits[0]
lines[targets[i][0]].append(
f"\\push{{\\textcolor{{{kwargs.get('edgecolor', 'black')}}}{{\\textcircled{{\\raisebox{{-0.2ex}}{{{bit}}}}}}}}}&"
)
except KeyError:
logger.exception(
"SetBits value for '%s' is not specified in the backend",
name,
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else:
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{gates[i].name}}}&"
)
elif isinstance(gates[i], SetQubits):
if backend is not None:
name = gates[i].name
try:
bits = backend.values[name]
bit = bits[0]
lines[targets[i][0]].append(
f"\\push{{\\text{{\\Large \\textcolor{{{kwargs.get('edgecolor', 'black')}}}{{$\\ket{{{bit}}}$}}}}}}&"
)
except KeyError:
logger.exception(
"SetQubits value for '%s' is not specified in the backend",
name,
)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else:
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{gates[i].name}}}&"
)
elif isinstance(gates[i], SetDensityMatrix):
name = r"\rho[{}]".format(gates[i].name)
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else:
name = get_gate_name(gates[i])
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else: # multi-qubit gates (controlled/ non-controlled)
if isinstance(
gates[i], (BasicGate, InverseBasicGate, Sequence)
): # multi-qubit self-defined gate or Sequence
valid_name(gates[i].name)
name = gates[i].name
for qubit in list(range(min(targets[i]), max(targets[i]) + 1)):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
if (
isinstance(gates[i], (QuantumControl, ClassicalControl))
and not isinstance(
gates[i].qop,
(
Gates.SwapQubits,
Gates.CNOT,
Gates.Toffoli,
QuantumControl,
),
)
): # multi-controlled gates (excluding CSwap CNOT, Toffoli, and QuantumControl)
onoff = gates[i].onoff
gate = gates[i].qop
if isinstance(
gate,
(
Measure,
SetBits,
SetQubits,
SetDensityMatrix,
ClassicalControl,
DropQubits,
),
):
raise TypeError(
"Non-unitary operations are not eligible targets for Quantum or ClassicalControl"
)
elif isinstance(gate, (BasicGate, InverseBasicGate, Sequence)):
name = gate.name
valid_name(name)
else: # geqo gates defined in geqo_gates.py
if isinstance(gate, (Gates.SGate, Gates.InverseSGate)):
if isinstance(gates[i], QuantumControl):
name = (
r"S"
if isinstance(gate, Gates.SGate)
else r"S^\dagger"
)
else: # ClassicalControl syntax is different.
name = (
r"S"
if isinstance(gate, Gates.SGate)
else r"S$^\dagger$"
)
elif isinstance(gate, (Gates.Phase, Gates.InversePhase)):
if isinstance(gates[i], QuantumControl):
string = (
r"P({})"
if isinstance(gate, Gates.Phase)
else r"P^\dagger({})"
)
greek_string = (
r"P(\{})"
if isinstance(gate, Gates.Phase)
else r"P^\dagger(\{})"
)
else: # ClassicalControl
string = (
r"P({})"
if isinstance(gate, Gates.Phase)
else r"P^$\dagger$({})"
)
greek_string = (
r"P($\{}$)"
if isinstance(gate, Gates.Phase)
else r"P^$\dagger$($\{}$)"
)
name = phase_name(
gate,
string,
greek_string,
backend,
greek_symbol,
)
elif isinstance(gate, (Gates.Rx, Gates.InverseRx)):
if isinstance(gates[i], QuantumControl):
string = (
r"Rx({})"
if isinstance(gate, Gates.Rx)
else r"Rx^\dagger({})"
)
greek_string = (
r"Rx(\{})"
if isinstance(gate, Gates.Rx)
else r"Rx^\dagger(\{})"
)
else:
string = (
r"Rx({})"
if isinstance(gate, Gates.Rx)
else r"Rx$^\dagger$({})"
)
greek_string = (
r"Rx($\{}$)"
if isinstance(gate, Gates.Rx)
else r"Rx$^\dagger$($\{}$)"
)
name = phase_name(
gate,
string,
greek_string,
backend,
greek_symbol,
)
elif isinstance(gate, (Gates.Ry, Gates.InverseRy)):
if isinstance(gates[i], QuantumControl):
string = (
r"Ry({})"
if isinstance(gate, Gates.Ry)
else r"Ry^\dagger({})"
)
greek_string = (
r"Ry(\{})"
if isinstance(gate, Gates.Ry)
else r"Ry^\dagger(\{})"
)
else:
string = (
r"Ry({})"
if isinstance(gate, Gates.Ry)
else r"Ry$^\dagger$({})"
)
greek_string = (
r"Ry($\{}$)"
if isinstance(gate, Gates.Ry)
else r"Ry^$\dagger$($\{}$)"
)
name = phase_name(
gate,
string,
greek_string,
backend,
greek_symbol,
)
elif isinstance(gate, (Gates.Rz, Gates.InverseRz)):
if isinstance(gates[i], QuantumControl):
string = (
r"Rz({})"
if isinstance(gate, Gates.Rz)
else r"Rz^\dagger({})"
)
greek_string = (
r"Rz(\{})"
if isinstance(gate, Gates.Rz)
else r"Rz^\dagger(\{})"
)
else:
string = (
r"Rz({})"
if isinstance(gate, Gates.Rz)
else r"Rz$^\dagger$({})"
)
greek_string = (
r"Rz($\{}$)"
if isinstance(gate, Gates.Rz)
else r"Rz$^\dagger$($\{}$)"
)
name = phase_name(
gate,
string,
greek_string,
backend,
greek_symbol,
)
elif isinstance(
gate, (Gates.Rzz, Gates.InverseRzz)
): # multi-qubit target has different syntax (math mode must be enclosed in $$)
string = (
r"Rzz({})"
if isinstance(gate, Gates.Rzz)
else r"Rzz$^\dagger$({})"
)
greek_string = (
r"Rzz($\{}$)"
if isinstance(gate, Gates.Rzz)
else r"Rzz$^\dagger$($\{}$)"
)
name = phase_name(
gate,
string,
greek_string,
backend,
greek_symbol,
)
elif isinstance(
gate, (Algorithms.PCCM, Algorithms.InversePCCM)
):
string = (
r"PCCM({})"
if isinstance(gate, Algorithms.PCCM)
else r"PCCM$^\dagger$({})"
)
greek_string = (
r"PCCM($\{}$)"
if isinstance(gate, Algorithms.PCCM)
else r"PCCM$^\dagger$($\{}$)"
)
name = phase_name(
gate,
string,
greek_string,
backend,
greek_symbol,
non_pccm=False,
)
else: # Other geqo gates (QubitReversal,QFT,InverseQFT,PermuteQubits)
name = get_gate_name(gate, backend, greek_symbol)
num_targets = gate.getNumberQubits()
target_qubits = targets[i][-num_targets:]
ctrl_qubits = targets[i][:-num_targets]
if isinstance(gate, PermuteQubits):
perm = gate.targetOrder
permq_label = {}
for j in range(len(perm)):
permq_label[target_qubits[perm[j]]] = (
f"${seq.qubits[target_qubits[j]]}$"
)
order = sorted(
targets[i]
) # sort the controlled and target qubits from top to bottom
if num_targets == 1: # single target
if isinstance(gates[i], QuantumControl):
for index, qubit in enumerate(order):
if (
qubit < targets[i][-1]
): # controlled qubits before the target qubit
if (
onoff[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index + 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index + 1] - order[index]}}}&"
)
elif qubit == targets[i][-1]: # target qubit
if not isinstance(gates[i].qop, Gates.PauliX):
lines[qubit].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else:
lines[qubit].append(r"\targ{}&")
else: # controlled qubits after the target qubit
if (
onoff[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index - 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index - 1] - order[index]}}}&"
)
else: # ClassicalControl
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if (
qubit == min(targets[i])
and qubit in target_qubits
and qubit in ctrl_qubits
):
lines[qubit].append(
# f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{0}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[4.5em]{{{name}}}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit == min(targets[i])
and qubit in target_qubits
):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{0}}&"
)
elif (
qubit == min(targets[i])
and qubit in ctrl_qubits
):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[targets[i][:-1].index(qubit)]}}}}}}}&"
)
# elif (
# qubit in target_qubits and qubit in ctrl_qubits
# ):
# lines[qubit].append(
# f"\\qw \\gateinput[{target_label_style}]{{0}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
# )
elif (
qubit in target_qubits
): # qubit that the unitary gate acts on
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{0}}&"
)
else:
if (
qubit in ctrl_qubits
): # classical control bits
lines[qubit].append(
f"\\qw \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
else: # multiple targets
if isinstance(gates[i], QuantumControl):
for index, qubit in enumerate(order):
if qubit < min(
target_qubits
): # controlled qubits before the target qubit
if (
onoff[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index + 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index + 1] - order[index]}}}&"
)
if qubit == min(
target_qubits
): # first target qubit
width = (
8.5
if isinstance(
gate,
(
Algorithms.PCCM,
Algorithms.InversePCCM,
),
)
else 3.5
)
if isinstance(gate, PermuteQubits):
width = 6
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\gate[{max(target_qubits) - min(target_qubits) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}&"
)
if (
min(target_qubits) < qubit <= max(target_qubits)
): # other qubits within the target gate
if qubit in target_qubits: # target qubits
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}} &"
)
else: # controlled qubits
if onoff[targets[i].index(qubit)] == 0:
lines[qubit].append(
r"\qw \gateinput{$\circ$} &"
)
else:
lines[qubit].append(
r"\qw \gateinput{$\bullet$} &"
)
if qubit > max(
target_qubits
): # controlled qubits after the target qubit
if (
onoff[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index - 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index - 1] - order[index]}}}&"
)
else: # ClassicalControl
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
width = (
8.5
if isinstance(
gate,
(
Algorithms.PCCM,
Algorithms.InversePCCM,
),
)
else 3.5
)
if (
qubit == min(targets[i])
and qubit in target_qubits
and qubit in ctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit == min(targets[i])
and qubit in target_qubits
):
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}&"
)
elif (
qubit == min(targets[i])
and qubit in ctrl_qubits
):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits and qubit in ctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}} \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits
): # qubit that the unitary gate acts on
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}}&"
)
else:
if (
qubit in ctrl_qubits
): # classical control bits
lines[qubit].append(
f"\\qw \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
if isinstance(
gates[i], (QuantumControl, ClassicalControl)
) and isinstance(
gates[i].qop, (Gates.CNOT, Gates.Toffoli)
): # controlled-CNOT and Toffoli
onoff = gates[i].onoff
gate = gates[i].qop
num_targets = gate.getNumberQubits()
target_qubits = targets[i][-num_targets:]
ctrl_qubits = targets[i][:-num_targets]
order = sorted(targets[i])
onoff2 = list(onoff) + [1] * (gate.getNumberQubits() - 1)
if isinstance(gates[i], QuantumControl):
for index, qubit in enumerate(order):
if (
qubit < targets[i][-1]
): # controlled qubits before the target qubit
if (
onoff2[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index + 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index + 1] - order[index]}}}&"
)
elif qubit == targets[i][-1]: # target qubit
lines[qubit].append(r"\targ{}&")
else: # controlled qubits after the target qubit
if (
onoff2[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index - 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index - 1] - order[index]}}}&"
)
# raise TypeError(
# "CNOT and Toffoli are not eligible targets for Quantum or ClassicalControl. Please define a mulit-controlled X gate instead."
# )
else: # ClassicalControl CNOT and Toffoli
name = (
"CX" if isinstance(gates[i].qop, Gates.CNOT) else "CCX"
)
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
width = 3.5
if (
qubit == min(targets[i])
and qubit in target_qubits
and qubit in ctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
target_label = target_qubits.index(qubit)
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit == min(targets[i]) and qubit in target_qubits
):
target_label = target_qubits.index(qubit)
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}&"
)
elif qubit == min(targets[i]) and qubit in ctrl_qubits:
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits and qubit in ctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
target_label = target_qubits.index(qubit)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}} \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits
): # qubit that the unitary gate acts on
target_label = target_qubits.index(qubit)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}}&"
)
else:
if qubit in ctrl_qubits: # classical control bits
lines[qubit].append(
f"\\qw \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
if (
isinstance(gates[i], (QuantumControl, ClassicalControl))
and isinstance(gates[i].qop, QuantumControl)
): # quantum controlled-QuantumControl and classically controlled-QuantumControl
if isinstance(gates[i], QuantumControl):
onoff = gates[i].onoff
gate = gates[i].qop
onoff1 = gate.onoff
onoff = onoff + onoff1
gate = gate.qop # unitary gate of the inner QuantumControl
num_targets = gate.getNumberQubits()
target_qubits = targets[i][-num_targets:]
ctrl_qubits = targets[i][:-num_targets]
if isinstance(gate, PermuteQubits):
perm = gate.targetOrder
permq_label = {}
for j in range(len(perm)):
permq_label[target_qubits[perm[j]]] = (
f"${seq.qubits[target_qubits[j]]}$"
)
order = sorted(targets[i])
name = (
get_gate_name(gate, backend, greek_symbol)
if not isinstance(gate, (BasicGate, InverseBasicGate))
else gate.name
)
logger.info("name: %s gate: %s, name, gate")
if "\n" in name:
parts = name.split("\n")
gate_initials = parts[0].strip()
angle = parts[1].strip()
name = f"{gate_initials}({angle})"
for index, qubit in enumerate(order):
if qubit < min(
target_qubits
): # controlled qubits before the target qubit
if (
onoff[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index + 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index + 1] - order[index]}}}&"
)
if qubit == min(target_qubits): # first target qubit
width = (
8.5
if isinstance(
gate,
(
Algorithms.PCCM,
Algorithms.InversePCCM,
),
)
else 3.5
)
if isinstance(gate, PermuteQubits):
width = 6
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
if num_targets > 1:
lines[qubit].append(
f"\\gate[{max(target_qubits) - min(target_qubits) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}&"
)
else: # single target
lines[qubit].append(
f"\\gate[{1},{gate_style}]{{\\makebox[{1}em]{{{name}}}}}&"
)
if (
min(target_qubits) < qubit <= max(target_qubits)
): # other qubits within the target gate
if qubit in target_qubits: # target qubits
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}} &"
)
else: # controlled qubits
if onoff[targets[i].index(qubit)] == 0:
lines[qubit].append(
r"\qw \gateinput{$\circ$} &"
)
else:
lines[qubit].append(
r"\qw \gateinput{$\bullet$} &"
)
if qubit > max(
target_qubits
): # controlled qubits after the target qubit
if (
onoff[targets[i].index(qubit)] == 0
): # controlled on |0> state
lines[qubit].append(
f"\\octrl{{{order[index - 1] - order[index]}}}&"
)
else: # controlled on |1> state
lines[qubit].append(
f"\\ctrl{{{order[index - 1] - order[index]}}}&"
)
else: # ClassicalControl (gates[i] is Classical Control on QuantumCOntrol)
onoff = gates[i].onoff # classical control onoff
gate = gates[i].qop # QuantumControl
onoff_q = gate.onoff # QuantumControl onoff
gate = gate.qop # unitary gate of the inner QuantumControl
num_targets = gate.getNumberQubits()
target_qubits = targets[i][-num_targets:]
qctrl_qubits = targets[i][
len(onoff) : -num_targets
] # quantum control qubits
cctrl_qubits = targets[i][
: len(onoff)
] # classical control bits
if isinstance(gate, PermuteQubits):
perm = gate.targetOrder
permq_label = {}
for j in range(len(perm)):
permq_label[target_qubits[perm[j]]] = (
f"${seq.qubits[target_qubits[j]]}$"
)
order = sorted(targets[i])
name = (
get_gate_name(gate, backend, greek_symbol)
if not isinstance(gate, (BasicGate, InverseBasicGate))
else gate.name
)
if "\n" in name:
parts = name.split("\n")
gate_initials = parts[0].strip()
angle = parts[1].strip()
name = f"{gate_initials}({angle})"
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
width = (
8.5
if isinstance(
gate,
(
Algorithms.PCCM,
Algorithms.InversePCCM,
),
)
else 3.5
)
if (
qubit == min(targets[i])
and qubit in target_qubits
and qubit in cctrl_qubits
): # qubit index is both presented in the classical control bits and the target qubits
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[cctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit == min(targets[i])
and qubit in cctrl_qubits
and qubit in qctrl_qubits
):
if onoff_q[qctrl_qubits.index(qubit)] == 0:
ctrl_condition = r"$\circ$"
else:
ctrl_condition = r"$\bullet$"
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{ctrl_condition}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[cctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit == min(targets[i]) and qubit in target_qubits
):
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_label}}}&"
)
elif qubit == min(targets[i]) and qubit in qctrl_qubits:
if onoff_q[qctrl_qubits.index(qubit)] == 0:
ctrl_condition = r"$\circ$"
else:
ctrl_condition = r"$\bullet$"
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{ctrl_condition}}}&"
)
elif qubit == min(targets[i]) and qubit in cctrl_qubits:
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[cctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits and qubit in cctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}} \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[cctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in qctrl_qubits and qubit in cctrl_qubits
): # qubit index is both presented in the classical control bits and the quantum control qubits
if onoff_q[qctrl_qubits.index(qubit)] == 0:
ctrl_condition = r"$\circ$"
else:
ctrl_condition = r"$\bullet$"
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{ctrl_condition}}} \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[cctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits
): # qubit that the unitary gate acts on
target_label = (
permq_label[qubit]
if isinstance(gate, PermuteQubits)
else target_qubits.index(qubit)
)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_label}}}&"
)
elif qubit in qctrl_qubits: # control qubits
if onoff_q[qctrl_qubits.index(qubit)] == 0:
ctrl_condition = r"$\circ$"
else:
ctrl_condition = r"$\bullet$"
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{ctrl_condition}}}&"
)
else:
if qubit in cctrl_qubits: # classical control bits
lines[qubit].append(
f"\\qw \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[cctrl_qubits.index(qubit)]}}}}}}}&"
)
if isinstance(
gates[i], (QuantumControl, ClassicalControl)
) and isinstance(
gates[i].qop, Gates.SwapQubits
): # controlled-swap gate
if isinstance(gates[i], QuantumControl):
onoff = gates[i].onoff
ctrl_qubits = targets[i][:-2]
order = sorted(targets[i])
# draw vertical line from the first to the last qubit
if min(targets[i]) in ctrl_qubits:
if onoff[ctrl_qubits.index(min(targets[i]))] == 0:
lines[min(targets[i])].append(
f"\\octrl{{{max(targets[i]) - min(targets[i])}}}&"
)
else:
lines[min(targets[i])].append(
f"\\ctrl{{{max(targets[i]) - min(targets[i])}}}&"
)
else:
lines[min(targets[i])].append(
f"\\swap{{{max(targets[i]) - min(targets[i])}}}&"
)
# draw either circle or cross
for qubit in order[1:]:
if qubit in ctrl_qubits:
if onoff[ctrl_qubits.index(qubit)] == 0:
lines[qubit].append(r"\ocontrol{}&")
else:
lines[qubit].append(r"\control{}&")
else:
lines[qubit].append("\\targX{}&")
else: # ClassicalControl
onoff = gates[i].onoff
target_qubits = targets[i][-2:]
ctrl_qubits = targets[i][:-2]
name = r"SWAP"
width = 6
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if (
qubit == min(targets[i])
and qubit in target_qubits
and qubit in ctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_qubits.index(qubit)}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit == min(targets[i]) and qubit in target_qubits
):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateinput[{target_label_style}]{{{target_qubits.index(qubit)}}}&"
)
elif qubit == min(targets[i]) and qubit in ctrl_qubits:
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[{width}em]{{{name}}}}}\\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits and qubit in ctrl_qubits
): # qubit index is both presented in the control bits and the target qubits
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_qubits.index(qubit)}}} \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
elif (
qubit in target_qubits
): # qubit that the unitary gate acts on
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{target_qubits.index(qubit)}}}&"
)
else:
if qubit in ctrl_qubits: # classical control bits
lines[qubit].append(
f"\\qw \\gateoutput[{target_label_style}]{{\\textcircled{{\\raisebox{{-0.2ex}}{{{onoff[ctrl_qubits.index(qubit)]}}}}}}}&"
)
if isinstance(gates[i], QuantumOperation) and not isinstance(
gates[i], QuantumControl
): # multi-qubit geqo gates
if isinstance(gates[i], Gates.CNOT):
lines[targets[i][0]].append(
f"\\ctrl{{{targets[i][-1] - targets[i][0]}}}&"
)
lines[targets[i][-1]].append(r"\targ{}&")
if isinstance(gates[i], Gates.Toffoli):
order = sorted(targets[i])
for index, qubit in enumerate(order):
if (
qubit < targets[i][-1]
): # controlled qubits before the target qubit
lines[qubit].append(
f"\\ctrl{{{order[index + 1] - order[index]}}}&"
)
elif qubit == targets[i][-1]: # target qubit
lines[qubit].append(r"\targ{}&")
else: # controlled qubits after the target qubit
lines[qubit].append(
f"\\ctrl{{{order[index - 1] - order[index]}}}&"
)
if isinstance(
gates[i], (Algorithms.QFT, Algorithms.InverseQFT)
):
name = get_gate_name(gates[i])
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
if isinstance(gates[i], (Gates.Rzz, Gates.InverseRzz)):
string = (
r"Rzz({})"
if isinstance(gates[i], Gates.Rzz)
else r"Rzz^\dagger({})"
)
greek_string = (
r"Rzz(\{})"
if isinstance(gates[i], Gates.Rzz)
else r"Rzz^\dagger(\{})"
)
name = phase_name(
gates[i],
string,
greek_string,
backend,
greek_symbol,
)
lines[min(targets[i])].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{{name}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
lines[max(targets[i])].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(max(targets[i]))}}}&"
)
if isinstance(
gates[i], (Algorithms.PCCM, Algorithms.InversePCCM)
):
string = (
r"PCCM({})"
if isinstance(gates[i], Algorithms.PCCM)
else r"PCCM^\dagger({})"
)
greek_string = (
r"PCCM(\{})"
if isinstance(gates[i], Algorithms.PCCM)
else r"PCCM^\dagger(\{})"
)
name = phase_name(
gates[i],
string,
greek_string,
backend,
greek_symbol,
non_pccm=False,
)
lines[min(targets[i])].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{{name}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
lines[max(targets[i])].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(max(targets[i]))}}}&"
)
if isinstance(gates[i], Measure):
for j in range(len(targets[i])):
lines[targets[i][j]].append(
f"\\meter[{measure_style}]{{{measures[i][j]}}}&"
)
if isinstance(gates[i], DropQubits):
for qubit in targets[i]:
lines[qubit].append(
f"\\push{{\\text{{\\Large \\textcolor{{{kwargs.get('edgecolor', 'black')}}}{{X}}}}}}&"
)
if isinstance(gates[i], SetBits):
name = gates[i].name
if backend is not None:
try:
bits = backend.values[name]
for index, bit in enumerate(bits):
lines[targets[i][index]].append(
f"\\push{{\\textcolor{{{kwargs.get('edgecolor', 'black')}}}{{\\textcircled{{\\raisebox{{-0.2ex}}{{{bit}}}}}}}}}&"
)
except KeyError:
logger.exception(
"SetBits values for '%s' are not specified in the backend",
name,
)
for qubit in list(
range(
min(targets[i]),
max(targets[i]) + 1,
)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
else:
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
if isinstance(gates[i], SetQubits):
name = gates[i].name
if backend is not None:
try:
bits = backend.values[name]
for index, bit in enumerate(bits):
lines[targets[i][index]].append(
f"\\push{{\\text{{\\Large \\textcolor{{{kwargs.get('edgecolor', 'black')}}}{{$\\ket{{{bit}}}$}}}}}}&"
)
except KeyError:
logger.exception(
"SetQubits values for '%s' are not specified in the backend",
name,
)
for qubit in list(
range(
min(targets[i]),
max(targets[i]) + 1,
)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
else:
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
if isinstance(gates[i], SetDensityMatrix):
name = r"$\rho[{}]$".format(gates[i].name)
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[3.5em]{{{name}}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min(targets[i]))}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{targets[i].index(qubit)}}}&"
)
if isinstance(gates[i], Gates.SwapQubits):
lines[min(targets[i])].append(
f"\\swap{{{max(targets[i]) - min(targets[i])}}}&"
)
lines[max(targets[i])].append("\\targX{}&")
if isinstance(gates[i], QubitReversal):
min_tq, max_tq = min(targets[i]), max(targets[i])
num_qubits = max_tq - min_tq + 1 # Total span of the gate
# First qubit in the gate range gets the main gate box
lines[min_tq].append(
f"\\gate[{num_qubits},{gate_style}]{{\\makebox[3.5em]{{$\\mathbb{{R}}\\text{{vrs}}$}}}}\\gateinput[{target_label_style}]{{{targets[i].index(min_tq)}}}&"
)
# Loop over all target qubits
for qubit in targets[i]:
if qubit != min_tq:
input_index = targets[i].index(qubit)
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{input_index}}} &"
)
if isinstance(gates[i], PermuteQubits):
perm = gates[i].targetOrder
permq_label = {}
for j in range(len(perm)):
permq_label[targets[i][perm[j]]] = (
f"${seq.qubits[targets[i][j]]}$"
)
name = get_gate_name(gates[i])
for qubit in list(
range(min(targets[i]), max(targets[i]) + 1)
):
if qubit == min(targets[i]):
lines[qubit].append(
f"\\gate[{max(targets[i]) - min(targets[i]) + 1},{gate_style}]{{\\makebox[6em]{{{name}}}}}\\gateinput[{target_label_style}]{{{permq_label[qubit]}}}&"
)
else:
if qubit in targets[i]:
lines[qubit].append(
f"\\qw \\gateinput[{target_label_style}]{{{permq_label[qubit]}}}&"
)
# assemble circuit
circuit.extend("".join(lines[q]) + r"\qw & \\" for q in sorted(lines.keys()))
if row < num_rows - 1:
circuit.append(r"\\") # row break for a new row of circuit
circuit.append(r"\end{quantikz}")
return "\n".join(circuit)
def render_latex_to_image(latex_code: str, filename: str = None):
"""Render LaTeX code (quantikz) to a PDF, then convert it to a PNG image for visualization.
Args:
latex_code (str): The LaTeX code to render.
filename (str, optional): If provided, the PNG image will be saved with this filename. Defaults to `None`.
Returns:
PIL.Image: The rendered image. Displayed inline (e.g., in Jupyter) and optionally saved as a PNG file.
"""
if filename is None: # circuit diagram not saved if the filename is not specified
filename = "circuit_diagram"
save_png = False
else:
save_png = True
# create temporary files (automatically deleted afterwards)
with tempfile.TemporaryDirectory() as temp_dir:
tex_filename = os.path.join(temp_dir, f"{filename}.tex")
pdf_filename = os.path.join(temp_dir, f"{filename}.pdf")
png_filename = os.path.join(temp_dir, f"{filename}.png")
# wrap Latex content into a standalone document
latex_document = f"""
\\documentclass{{standalone}}
\\usepackage{{quantikz}}
\\usepackage{{amssymb}}
\\usepackage{{xcolor}}
\\usepackage[dvipsnames]{{xcolor}}
\\begin{{document}}
{latex_code}
\\end{{document}}
"""
# save the Latex code to a .tex file
with open(tex_filename, "w") as f:
f.write(latex_document)
# render Latex to PDF (runs pdflatex twice to ensure references are resolved)
subprocess.run(
["pdflatex", "-interaction=nonstopmode", tex_filename],
cwd=temp_dir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
subprocess.run(
["pdflatex", "-interaction=nonstopmode", tex_filename],
cwd=temp_dir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# convert PDF to PNG (Ghostscript)
subprocess.run(
[
"gs",
"-q",
"-dNOPAUSE",
"-dBATCH",
"-sDEVICE=pngalpha",
"-r300",
f"-sOutputFile={png_filename}",
pdf_filename,
],
cwd=temp_dir,
)
logger.debug(
"Checking if PNG exists before opening: %s", os.path.exists(png_filename)
)
image = Image.open(png_filename)
display(image)
# save PNG if the filename is specified
if save_png:
output_png = f"{filename}.png"
image.save(output_png)
logger.info("Circuit diagram saved as %s", output_png)
[docs]
def plot_latex(
seq: Sequence,
backend: Simulator = None,
decompose_subseq: bool = False,
pack: bool = True,
fold: int = 9,
greek_symbol: bool = True,
filename: str = None,
return_quantikz: bool = False,
**kwargs,
):
"""Plot a LaTeX-style (quantikz) quantum circuit diagram from a geqo `Sequence`.
Args:
seq (Sequence): The geqo `Sequence` to visualize.
backend (Simulator, optional): The simulator backend used to resolve parameter values. Defaults to `None`.
decompose_subseq (bool, optional): Whether to decompose subsequences within the main sequence. Defaults to `False`.
pack (bool, optional): Whether to compactify the circuit by placing non-conflicting gates in the same column. Defaults to `True`.
fold (int, optional): Maximum number of lines to show before folding the circuit. Defaults to 9.
greek_symbol (bool, optional): Whether to render gate names using Greek letters. Defaults to `True`.
filename (str, optional): If provided, the output PNG will be saved with this filename. Defaults to `None`.
return_quantikz (bool, optional): If `True`, the quantikz LaTeX code will be returned and printed. Defaults to `False`.
**kwargs: Additional styling options passed to the LaTeX generator.
Returns:
PIL.Image: The rendered quantum circuit image.
str (optional): The LaTeX (quantikz) code, if `return_quantikz` is `True`.
"""
latex_code = tolatex(
seq, backend, decompose_subseq, pack, fold, greek_symbol, **kwargs
)
render_latex_to_image(latex_code, filename)
if return_quantikz is True:
return latex_code