Source code for braket.circuits.text_diagram_builders.unicode_circuit_diagram

# Copyright Amazon.com Inc. or its affiliates. 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. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file 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.

from __future__ import annotations

from functools import reduce
from typing import Literal

import braket.circuits.circuit as cir
from braket.circuits.compiler_directive import CompilerDirective
from braket.circuits.gate import Gate
from braket.circuits.instruction import Instruction
from braket.circuits.result_type import ResultType
from braket.circuits.text_diagram_builders.text_circuit_diagram import TextCircuitDiagram
from braket.registers.qubit import Qubit
from braket.registers.qubit_set import QubitSet


[docs] class UnicodeCircuitDiagram(TextCircuitDiagram): """Builds string circuit diagrams using box-drawing characters."""
[docs] @staticmethod def build_diagram(circuit: cir.Circuit) -> str: """Build a text circuit diagram. Args: circuit (Circuit): Circuit for which to build a diagram. Returns: str: string circuit diagram. """ return UnicodeCircuitDiagram._build(circuit)
@classmethod def _vertical_delimiter(cls) -> str: """Character that connects qubits of multi-qubit gates.""" return "│" @classmethod def _qubit_line_character(cls) -> str: """Character used for the qubit line.""" return "─" @classmethod def _box_pad(cls) -> int: """number of blank space characters around the gate name.""" return 4 @classmethod def _qubit_line_spacing_above(cls) -> int: """number of empty lines above the qubit line.""" return 1 @classmethod def _qubit_line_spacing_below(cls) -> int: """number of empty lines below the qubit line.""" return 1 @classmethod def _duplicate_time_at_bottom(cls, lines: list) -> None: # Do not add a line after the circuit # It is safe to do because the last line is empty: _qubit_line_spacing["after"] = 1 lines[-1] = lines[0] @classmethod def _create_diagram_column( cls, circuit_qubits: QubitSet, items: list[Instruction | ResultType], global_phase: float | None = None, ) -> str: """Return a column in the string diagram of the circuit for a given list of items. Args: circuit_qubits (QubitSet): qubits in circuit items (list[Instruction | ResultType]): list of instructions or result types global_phase (float | None): the integrated global phase up to this column Returns: str: a string diagram for the specified moment in time for a column. """ symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} connections = {qubit: "none" for qubit in circuit_qubits} for item in items: ( target_qubits, control_qubits, qubits, connections, ascii_symbols, map_control_qubit_states, ) = cls._build_parameters(circuit_qubits, item, connections) for qubit in qubits: # Determine if the qubit is part of the item or in the middle of a # multi qubit item. if qubit in target_qubits: item_qubit_index = [ index for index, q in enumerate(target_qubits) if q == qubit ][0] power_string = ( f"^{power}" if ( (power := getattr(item, "power", 1)) != 1 # this has the limitation of not printing the power # when a user has a gate genuinely named C, but # is necessary to enable proper printing of custom # gates with built-in control qubits and ascii_symbols[item_qubit_index] != "C" ) else "" ) symbols[qubit] = ( f"{ascii_symbols[item_qubit_index]}{power_string}" if power_string else ascii_symbols[item_qubit_index] ) elif qubit in control_qubits: symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" else: symbols[qubit] = "┼" output = cls._create_output(symbols, connections, circuit_qubits, global_phase) return output @classmethod def _build_parameters( cls, circuit_qubits: QubitSet, item: ResultType | Instruction, connections: dict[Qubit, str] ) -> tuple: map_control_qubit_states = {} if (isinstance(item, ResultType) and not item.target) or ( isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) ): target_qubits = circuit_qubits control_qubits = QubitSet() qubits = circuit_qubits ascii_symbols = [item.ascii_symbols[0]] * len(qubits) cls._update_connections(qubits, connections) elif ( isinstance(item, Instruction) and isinstance(item.operator, Gate) and item.operator.name == "GPhase" ): target_qubits = circuit_qubits control_qubits = QubitSet() qubits = circuit_qubits ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) else: if isinstance(item.target, list): target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) else: target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) map_control_qubit_states = dict(zip(control_qubits, control_state)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) ascii_symbols = item.ascii_symbols cls._update_connections(qubits, connections) return ( target_qubits, control_qubits, qubits, connections, ascii_symbols, map_control_qubit_states, ) @staticmethod def _update_connections(qubits: QubitSet, connections: dict[Qubit, str]) -> None: if len(qubits) > 1: connections |= {qubit: "both" for qubit in qubits[1:-1]} connections[qubits[-1]] = "above" connections[qubits[0]] = "below" # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] # flake8: noqa: BCS005 @classmethod def _draw_symbol( cls, symbol: str, symbols_width: int, connection: Literal["above", "below", "both", "none"], ) -> str: """Create a string representing the symbol inside a box. Args: symbol (str): the gate name symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): specifies if a connection will be drawn above and/or below the box. Returns: str: a string representing the symbol. """ top = "" bottom = "" if symbol in {"C", "N", "SWAP"}: if connection in ["above", "both"]: top = _fill_symbol(cls._vertical_delimiter(), " ") if connection in ["below", "both"]: bottom = _fill_symbol(cls._vertical_delimiter(), " ") new_symbol = {"C": "●", "N": "◯", "SWAP": "x"} # replace SWAP by x # the size of the moment remains as if there was a box with 4 characters inside symbol = _fill_symbol(new_symbol[symbol], cls._qubit_line_character()) elif symbol in ["StartVerbatim", "EndVerbatim"]: top, symbol, bottom = cls._build_verbatim_box(symbol, connection) elif symbol == "┼": top = bottom = _fill_symbol(cls._vertical_delimiter(), " ") symbol = _fill_symbol(f"{symbol}", cls._qubit_line_character()) elif symbol != cls._qubit_line_character(): top, symbol, bottom = cls._build_box(symbol, connection) output = f"{_fill_symbol(top, ' ', symbols_width)} \n" output += f"{_fill_symbol(symbol, cls._qubit_line_character(), symbols_width)}{cls._qubit_line_character()}\n" output += f"{_fill_symbol(bottom, ' ', symbols_width)} \n" return output @staticmethod def _build_box( symbol: str, connection: Literal["above", "below", "both", "none"] ) -> tuple[str, str, str]: top_edge_symbol = "┴" if connection in ["above", "both"] else "─" top = f"┌─{_fill_symbol(top_edge_symbol, '─', len(symbol))}─┐" bottom_edge_symbol = "┬" if connection in ["below", "both"] else "─" bottom = f"└─{_fill_symbol(bottom_edge_symbol, '─', len(symbol))}─┘" symbol = f"┤ {symbol} ├" return top, symbol, bottom @classmethod def _build_verbatim_box( cls, symbol: Literal["StartVerbatim", "EndVerbatim"], connection: Literal["above", "below", "both", "none"], ) -> str: top = "" bottom = "" if connection == "below": bottom = "║" elif connection == "both": top = bottom = "║" symbol = "║" elif connection == "above": top = "║" symbol = "╨" top = _fill_symbol(top, " ") symbol = _fill_symbol(symbol, cls._qubit_line_character()) bottom = _fill_symbol(bottom, " ") return top, symbol, bottom
def _fill_symbol(symbol: str, filler: str, width: int | None = None) -> str: return "{0:{fill}{align}{width}}".format( symbol, fill=filler, align="^", width=width if width is not None else len(symbol), )