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) :]
ctrl_bits = op[2]
target_qubits = op[1]
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
elif isinstance(op[0], SetBits):
int_targets = [
target if type(target) is int else seq.bits.index(target)
for target in op[2]
]
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
if not isinstance(op[0], Measure):
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
# store all valid BasicGate names
valid_names = []
for op in seq.gatesAndTargets:
if (
isinstance(
op[0],
(
BasicGate,
InverseBasicGate,
Sequence,
SetBits,
SetQubits,
SetDensityMatrix,
),
)
and op[0].name not in valid_names
):
name = op[0].name
if valid_name(name):
valid_names.append(name)
elif isinstance(op[0], (QuantumControl, ClassicalControl)):
if (
isinstance(op[0].qop, (BasicGate, InverseBasicGate, Sequence))
and op[0].qop.name not in valid_names
):
name = op[0].qop.name
if valid_name(name):
valid_names.append(name)
invalid_names = [] # store all invalid BasicGate names
name_map = {} # store invalid BasicGate names and their modified names
duplicate_initials = {}
for op in seq.gatesAndTargets:
if (
isinstance(
op[0],
(
BasicGate,
InverseBasicGate,
Sequence,
SetBits,
SetQubits,
SetDensityMatrix,
),
)
and op[0].name not in invalid_names
):
name = op[0].name
if not valid_name(name):
invalid_names.append(name)
elif isinstance(op[0], (QuantumControl, ClassicalControl)):
if (
isinstance(op[0].qop, (BasicGate, InverseBasicGate, Sequence))
and op[0].qop.name not in invalid_names
):
name = op[0].qop.name
if not valid_name(name):
invalid_names.append(name)
for name in invalid_names:
inverse_seq = False
if name.startswith("$"): # for inverse Sequence
inverse_seq = True
name = name[1 : -len("^\\dagger$")]
if name not in list(name_map.keys()) and name[:3] in [
v[:3] for v in name_map.values()
]:
duplicate_initials[name[:3]] += 1
new_name = name[:3] + str(duplicate_initials[name[:3]])
if not inverse_seq:
name_map[name] = new_name
else:
name_map[f"${name}^\\dagger$"] = f"${new_name}^\\dagger$"
elif name not in list(name_map.keys()) and name[:3] not in [
v[:3] for v in name_map.values()
]:
duplicate_initials[name[:3]] = 0
new_name = name[:3]
if new_name in valid_names:
duplicate_initials[new_name] += 1
new_name = new_name + str(duplicate_initials[new_name])
if not inverse_seq:
name_map[name] = new_name
else:
name_map[f"${name}^\\dagger$"] = f"${new_name}^\\dagger$"
# 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
if not valid_name(name):
print(f"gate name {name} not valid")
name = name_map[name]
if name.startswith("$"): # for inverse Sequence
base = name[1 : -len("^\\dagger$")]
name = r"{}^\dagger".format(base)
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,
)
if not valid_name(name):
name = name_map[name]
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else:
name = gates[i].name
if not valid_name(name):
name = name_map[name]
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{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,
)
if not valid_name(name):
name = name_map[name]
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
else:
name = gates[i].name
if not valid_name(name):
name = name_map[name]
lines[targets[i][0]].append(
f"\\gate[{gate_style}]{{{name}}}&"
)
elif isinstance(gates[i], SetDensityMatrix):
name = gates[i].name
if not valid_name(name):
name = name_map[name]
name = r"\rho[{}]".format(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
if not valid_name(name):
name = name_map[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
if not valid_name(name):
name = name_map[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,
)
if not valid_name(name):
name = name_map[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:
if not valid_name(name):
name = name_map[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], 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,
)
if not valid_name(name):
name = name_map[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:
if not valid_name(name):
name = name_map[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], SetDensityMatrix):
name = gates[i].name
if not valid_name(name):
name = name_map[name]
name = r"$\rho[{}]$".format(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}")
if len(name_map) != 0:
print("The sequence involves long gate labels. They are renamed as:")
for key, item in name_map.items():
print(f"{key} -> {item}")
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