#
# Copyright © 2022-2023 University of Strasbourg. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import mimiqcircuits as mc
import shutil
import math
def _string_with_square(arr, sep):
return f"[{sep.join(map(str, arr))}]"
def _find_unit_range(arr):
if len(arr) < 2:
return arr
narr = []
rangestart = arr[0]
rangestop = arr[0]
for v in arr[0:]:
if v == rangestop + 1:
rangestop = v
elif rangestart == rangestop:
narr.append(rangestart)
rangestart = v
rangestop = v
else:
narr.append(range(rangestart, rangestop + 1))
rangestart = v
rangestop = v
if rangestart == rangestop:
narr.append(rangestart)
else:
narr.append(range(rangestart, rangestop + 1))
return narr
def _gate_name_padding(qubits, bits):
nq = len(qubits)
qubits_padding = 0 if nq == 1 else math.floor(math.log10(nq)) + 2
if not bits:
return qubits_padding
bits_padding = len(str(bits)) + 1
return max(qubits_padding, bits_padding)
[docs]
class AsciiCanvas:
def __init__(self, width=None):
if width is None:
_, self.width = 0, shutil.get_terminal_size().columns
self.width = width
self.data = []
[docs]
def get_rows(self):
return len(self.data)
[docs]
def get_cols(self):
return self.width
[docs]
def push_line(self):
self.data.append([" "] * self.width)
def __getitem__(self, position):
row, col = position
if row >= len(self.data):
return " "
return self.data[row][col]
def __setitem__(self, position, value):
row, col = position
while row >= self.get_rows():
self.push_line()
while col >= len(self.data[row]):
self.data[row].append(" ")
self.data[row][col] = value
def __str__(self):
return "\n".join("".join(row) for row in self.data if row)
[docs]
def draw_hline(self, row, col, width):
start_col, stop_col = min(col, col + width - 1), max(col, col + width - 1)
for i in range(start_col, stop_col + 1):
current_char = self[row, i]
# Check and replace characters based on conditions
if current_char == "│":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "├", "┼", "┤"
)
elif current_char == "┤":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "┼", "┼", "┤"
)
elif current_char == "├":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "├", "┼", "┼"
)
elif current_char == "╵":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "└", "┴", "┘"
)
elif current_char == "└":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "└", "┴", "┴"
)
elif current_char == "┘":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "┴", "┴", "┘"
)
elif current_char == "╷":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "┌", "┬", "┐"
)
elif current_char == "║":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╟", "╫", "╢"
)
elif current_char == "╟":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╟", "╫", "╫"
)
elif current_char == "╢":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╫", "╫", "╢"
)
elif current_char == "╶":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╶", "─", "─"
)
elif current_char == "╴":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "─", "─", "╴"
)
else:
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╶", "─", "╴"
)
return self
def _start_mid_stop(self, current, start, stop, start_char, mid_char, stop_char):
if current == start:
return start_char
elif current == stop:
return stop_char
return mid_char
[docs]
def draw_vline(self, row, col, height):
start_row, stop_row = min(row, row + height - 1), max(row, row + height - 1)
for i in range(start_row, stop_row + 1):
current_char = self[i, col]
if current_char == "─":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┬", "┼", "┴"
)
elif current_char == "┴":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┼", "┼", "┴"
)
elif current_char == "┬":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┬", "┼", "┼"
)
elif current_char == "╴":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┐", "┤", "┘"
)
elif current_char == "┐":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┐", "┤", "┤"
)
elif current_char == "┘":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┤", "┤", "┘"
)
elif current_char == "╶":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┌", "├", "└"
)
elif current_char == "┌":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "┌", "├", "├"
)
elif current_char == "└":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "├", "├", "└"
)
elif current_char == "╵":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "│", "│", "╵"
)
elif current_char == "╷":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╷", "│", "│"
)
elif current_char == "═":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╤", "╪", "╧"
)
elif current_char == "╤":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╤", "╪", "╪"
)
elif current_char == "╧":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╪", "╪", "╧"
)
else:
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╷", "│", "╵"
)
return self
[docs]
def draw_double_hline(self, row, col, width):
start_col, stop_col = min(col, col + width - 1), max(col, col + width - 1)
for i in range(start_col, stop_col + 1):
if self[row, i] == "│":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╞", "╪", "╡"
)
elif self[row, i] == "╞":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╞", "╪", "╪"
)
elif self[row, i] == "╡":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╪", "╪", "╡"
)
elif self[row, i] == "╵":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╘", "╧", "╛"
)
elif self[row, i] == "╘":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╘", "╧", "╧"
)
elif self[row, i] == "╛":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╧", "╧", "╛"
)
elif self[row, i] == "╷":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╒", "╤", "╕"
)
elif self[row, i] == "╒":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╒", "╤", "╤"
)
elif self[row, i] == "╕":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╤", "╤", "╕"
)
elif self[row, i] == "║":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╠", "╬", "╣"
)
elif self[row, i] == "╠":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╠", "╬", "╣"
)
elif self[row, i] == "╣":
self[row, i] = self._start_mid_stop(
i, start_col, stop_col, "╠", "╬", "╣"
)
else:
self[row, i] = "═"
[docs]
def draw_double_vline(self, row, col, height):
start_row, stop_row = min(row, row + height - 1), max(row, row + height - 1)
for i in range(start_row, stop_row + 1):
if self[i, col] == "─":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╥", "╫", "╨"
)
elif self[i, col] == "╥":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╥", "╫", "╫"
)
elif self[i, col] == "╨":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╫", "╫", "╨"
)
elif self[i, col] == "╶":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╓", "╟", "╙"
)
elif self[i, col] == "╓":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╓", "╟", "╟"
)
elif self[i, col] == "╙":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╟", "╟", "╙"
)
elif self[i, col] == "╴":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╖", "╢", "╜"
)
elif self[i, col] == "╖":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╖", "╢", "╢"
)
elif self[i, col] == "╜":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╢", "╢", "╜"
)
elif self[i, col] == "═":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╦", "╬", "╩"
)
elif self[i, col] == "╩":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╬", "╬", "╩"
)
elif self[i, col] == "╦":
self[i, col] = self._start_mid_stop(
i, start_row, stop_row, "╦", "╬", "╬"
)
else:
self[i, col] = "║"
[docs]
def draw_fill(self, char, row, col, width, height):
for i in range(row, row + height):
for j in range(col, col + width):
self[i, j] = char
[docs]
def draw_empty(self, row, col, width, height):
for i in range(row, row + height):
while i >= len(self.data):
self.push_line()
for j in range(col, col + width):
while j >= len(self.data[i]):
self.data[i].append(" ")
if j == col and self.data[i][j] == "─":
self.data[i][j] = "╴"
elif j == col + width - 1 and self.data[i][j] == "─":
self.data[i][j] = "╶"
elif i == row and self.data[i][j] == "│":
self.data[i][j] = "╵"
elif i == row + height - 1 and self.data[i][j] == "│":
self.data[i][j] = "╷"
else:
self.data[i][j] = " "
[docs]
def draw_box(self, row, col, width, height, clean=False):
if clean:
self.draw_empty(row, col, width, height)
self.draw_hline(row, col, width)
self.draw_hline(row + height - 1, col, width)
self.draw_vline(row, col, height)
self.draw_vline(row, col + width - 1, height)
return self
[docs]
def draw_text(self, text, row, col):
for i, char in enumerate(text):
self[row, col + i] = char
return self
[docs]
def draw_vtext(self, text, row, col):
for i, char in enumerate(text):
self[row + i, col] = char
return self
[docs]
def reset(self):
self.data = []
return self
[docs]
class AsciiCircuit:
def __init__(self, width=None):
if width is None:
_, width = 0, shutil.get_terminal_size().columns
self.canvas = AsciiCanvas(width)
self.qubitrow = {}
self.bitrow = None
self.currentcol = 0
[docs]
def set_current_col(self, col):
self.currentcol = max(self.currentcol, col)
[docs]
def get_current_col(self):
return self.currentcol
[docs]
def get_qubit_row(self, qubit):
return self.qubitrow.get(qubit)
[docs]
def get_bit_row(self):
return self.bitrow
[docs]
def reset(self):
self.canvas.reset()
self.qubitrow = {}
self.bitrow = None
self.currentcol = 1
return self
[docs]
def draw_wires(self, qubits, bits):
# Draw qubit wires and labels
for i, q in enumerate(qubits):
row = i * 2 + 1
qubitstr = f"q[{q}]: "
self.canvas.draw_text(qubitstr, row, 1)
self.qubitrow[q] = row
self.set_current_col(len(qubitstr) + 1)
if len(bits) > 0:
row = len(qubits) * 2 + 3
bitstr = "c: "
self.canvas.draw_text(bitstr, row, 1)
self.bitrow = row
self.set_current_col(len(bitstr) + 1)
ccol = self.get_current_col()
# Fill space and draw horizontal line for qubits
for i in range(len(qubits)):
row = i * 2
self.canvas.draw_fill(" ", row, ccol, self.canvas.get_cols() - ccol, 1)
self.canvas.draw_hline(row + 1, ccol, self.canvas.get_cols() - ccol)
self.canvas.draw_fill(
" ", (len(qubits) - 1) * 2 + 3, ccol, self.canvas.get_cols() - ccol, 1
)
# Prepare space and draw double horizontal line for bits if more than one bit
if len(bits) > 0:
row = len(qubits) * 2 + 2
self.canvas.draw_fill(" ", row, ccol, self.canvas.get_cols() - ccol, 1)
self.canvas.draw_double_hline(row + 1, ccol, self.canvas.get_cols() - ccol)
self.set_current_col(ccol + 1)
return self
[docs]
def draw_operation(self, operation, qubits, bits):
if not isinstance(operation, mc.Operation):
raise TypeError("operation must be an instance of Operation")
namepadding = _gate_name_padding(qubits, bits)
ccol = self.get_current_col()
qubitrow = [self.get_qubit_row(q) for q in qubits]
bitrow = [self.get_bit_row()]
startrow = min(qubitrow) if not bits else min(qubitrow, bitrow) - 1
stoprow = max(qubitrow) if not bits else max(qubitrow, bitrow) + 1
gateheight = stoprow - startrow + 1
midrow = startrow + gateheight // 2
operation_str = str(operation)
gw = operation.asciiwidth(qubits, bits)
# Draw the box dynamically based on content size
self.canvas.draw_box(startrow - 1, ccol, gw, gateheight + 2, clean=True)
# Center the text within the box
text_start_col = ccol + (gw - len(operation_str)) // 2
self.canvas.draw_text(operation_str, midrow, ccol + namepadding + 1)
# Draw indices for qubits if there are more than one
if len(qubits) > 1:
for i, qr in enumerate(qubitrow):
self.canvas.draw_text(str(i), qr, ccol + 1)
# Draw indices for bits if there are more than one
if len(bits) > 1:
bitsstr = len(bits)
self.canvas.draw_text(bitsstr, bitrow, ccol + 2)
self.set_current_col(ccol + gw)
[docs]
def draw_control(self, operation, qubits, _):
if not isinstance(operation, mc.Control):
raise TypeError("operation must be an instance of Operation")
if operation.op.num_qubits == 1:
target_row = self.get_qubit_row(qubits[-1])
control_rows = [self.get_qubit_row(q) for q in qubits[:-1]]
max_row = max(control_rows + [target_row])
min_row = min(control_rows + [target_row])
current_col = self.get_current_col() - 1
gate_width = operation.asciiwidth([qubits[-1]], [])
middle_column = current_col + gate_width // 2
self.canvas.draw_vline(min_row, middle_column, max_row - min_row + 1)
for row in control_rows:
self.canvas[row, middle_column] = "●"
self.draw_operation(operation.get_operation(), [qubits[-1]], [])
else:
self.draw_operation(operation, qubits, [])
[docs]
def draw_measure(self, qubits, bits):
if not qubits or not bits:
raise ValueError("Qubits and bits must be provided for measurement.")
qubit = qubits[0]
bit = bits[0]
qubit_row = self.get_qubit_row(qubit)
bit_row = self.get_bit_row()
middle_column = self.get_current_col() + 1
self.canvas.draw_box(qubit_row - 1, middle_column - 1, 3, 3, clean=True)
self.canvas.draw_text("M", qubit_row, middle_column)
self.set_current_col(middle_column + 2)
if bit_row > qubit_row:
self.canvas.draw_double_vline(
qubit_row + 1, middle_column, bit_row - qubit_row
)
bit_str = str(bit)
self.canvas.draw_text(bit_str, bit_row + 1, middle_column)
self.set_current_col(middle_column + len(bit_str))
return self
[docs]
def draw_barrier(self, barrier, qubits):
for qubit in qubits:
qubit_row = self.get_qubit_row(qubit)
current_col = self.get_current_col()
self.canvas.draw_text("░", qubit_row, current_col)
self.canvas.draw_text("░", qubit_row, current_col)
self.canvas.draw_text("░", qubit_row + 1, current_col)
self.set_current_col(current_col + 1)
return self
[docs]
def draw_ifstatement(self, if_statement, qubits, bits):
if not isinstance(if_statement, mc.IfStatement):
raise TypeError("must be an Ifstatement")
brow = self.get_bit_row()
val = if_statement.get_unwrapped_value()
bstr = _string_with_square(_find_unit_range(bits), ",")
btext = f"c{bstr} == 0x{val}"
ccol = self.get_current_col()
self.canvas.draw_box(brow - 1, ccol, len(btext) + 2, 3, clean=True)
self.canvas.draw_text(btext, brow, ccol + 1)
self.draw_operation(if_statement.get_operation(), qubits, [])
qrow = max(self.get_qubit_row(q) for q in qubits)
self.canvas.draw_double_vline(qrow + 1, ccol + 1, brow - qrow - 1)
self.set_current_col(ccol + len(btext) + 2)
[docs]
def draw_reset(self, reset, qubits, _):
if not isinstance(reset, mc.Reset):
raise TypeError("Must be a Reset Operation")
qubit_row = self.get_qubit_row(qubits[0])
current_col = self.get_current_col()
self.canvas.draw_box(qubit_row - 1, current_col, 5, 3, clean=True)
# Draw the reset symbol (|0⟩) in the center of the box
self.canvas.draw_text("|0⟩", qubit_row, current_col + 1)
# Move the current column index forward to prevent overlapping with future elements
self.set_current_col(current_col + 5)
return self
[docs]
def draw_parallel(self, parallel, qubits, _):
if not isinstance(parallel, mc.Parallel):
raise TypeError("Must be a Parallel")
op = parallel.get_operation()
nq = op.num_qubits
nb = op.num_bits
ccol = self.get_current_col()
for i in range(parallel.num_repeats):
self.currentcol = ccol
self.draw_operation(op, qubits[nq * i : nq * (i + 1)], [])
return self
[docs]
def draw_instruction(self, instruction):
if not isinstance(instruction, mc.Instruction):
raise TypeError("Must be an Instruction")
return self.draw_operation(
instruction.get_operation(), instruction._qubits, instruction._bits
)
[docs]
def draw_measurereset(self, qubits, bits):
if not qubits or not bits:
raise ValueError("Qubits and bits must be provided for measurement.")
qubit = qubits[0]
bit = bits[0]
qubit_row = self.get_qubit_row(qubit)
bit_row = self.get_bit_row()
middle_column = self.get_current_col() + 1
self.canvas.draw_box(qubit_row - 1, middle_column - 1, 4, 3, clean=True)
self.canvas.draw_text("MR", qubit_row, middle_column)
self.set_current_col(middle_column + 3)
if bit_row > qubit_row:
self.canvas.draw_double_vline(
qubit_row + 1, middle_column, bit_row - qubit_row
)
bit_str = str(bit)
self.canvas.draw_text(bit_str, bit_row + 1, middle_column)
self.set_current_col(middle_column + len(bit_str))
return self
__all__ = ["AsciiCircuit", "AsciiCanvas"]