qutip/qutip-qip

View on GitHub
src/qutip_qip/circuit/base_renderer.py

Summary

Maintainability
A
0 mins
Test Coverage
"""
Base class Implementation for Renderers
"""

from dataclasses import dataclass
from typing import Union, Optional, List, Dict

from .color_theme import qutip, light, dark, modern


__all__ = ["BaseRenderer", "StyleConfig"]


@dataclass
class StyleConfig:
    """
    Dataclass to store the style configuration for circuit customization.

    Parameters
    ----------
    dpi : int, optional
        DPI of the figure. The default is 150.

    fontsize : int, optional
        Fontsize control at circuit level, including tile and wire labels. The default is 10.

    end_wire_ext : int, optional
        Extension of the wire at the end of the circuit. The default is 2.
        Available to TextRender and MatRender.

    padding : float, optional
        Padding between the circuit and the figure border. The default is 0.3.

    gate_margin : float, optional
        Margin space left on each side of the gate. The default is 0.15.

    wire_sep : float, optional
        Separation between the wires. The default is 0.5.

    layer_sep : float, optional
        Separation between the layers. The default is 0.5.

    gate_pad : float, optional
        Padding between the gate and the gate label. The default is 0.05.
        Available to TextRender and MatRender.

    label_pad : float, optional
        Padding between the wire label and the wire. The default is 0.1.

    fig_height : float, optional
        Height of the figure. The default is None.

    fig_width : float, optional
        Width of the figure. The default is None.

    bulge : Union[str, bool], optional
        Bulge style of the gate. Renders non-bulge gates if False. The default is True.

    align_layer : bool, optional
        Align the layers of the gates across different wires. The default is False.
        Available to TextRender and MatRender.

    theme : Optional[Union[str, Dict]], optional
        Color theme of the circuit. The default is "qutip".
        The available themes are 'qutip', 'light', 'dark' and 'modern'.

    title : Optional[str], optional
        Title of the circuit. The default is None.

    bgcolor : Optional[str], optional
        Background color of the circuit. The default is None.

    color : Optional[str], optional
        Controls color of acsent elements (eg. cross sign in the target node)
        and set as deafult color of gate-label. Can be overwritten by gate specific color.
        The default is None.

    wire_label : Optional[List], optional
        Labels of the wires. The default is None.
        Available to TextRender and MatRender.

    wire_color : Optional[str], optional
        Color of the wires. The default is None.
    """

    dpi: int = 150
    fontsize: int = 10
    end_wire_ext: int = 2
    padding: float = 0.3
    gate_margin: float = 0.15
    wire_sep: float = 0.5
    layer_sep: float = 0.5
    gate_pad: float = 0.05
    label_pad: float = 0.1
    fig_height: Optional[float] = None
    fig_width: Optional[float] = 10
    bulge: Union[str, bool] = True
    align_layer: bool = False
    theme: Optional[Union[str, Dict]] = "qutip"
    title: Optional[str] = None
    bgcolor: Optional[str] = None
    color: Optional[str] = None
    wire_label: Optional[List] = None
    wire_color: Optional[str] = None

    def __post_init__(self):
        if isinstance(self.bulge, bool):
            self.bulge = "round4" if self.bulge else "square"

        self.measure_color = "#000000"
        if self.theme == "qutip":
            self.theme = qutip
        elif self.theme == "light":
            self.theme = light
        elif self.theme == "dark":
            self.theme = dark
            self.measure_color = "#FFFFFF"
        elif self.theme == "modern":
            self.theme = modern
        else:
            raise ValueError(
                f"""Invalid theme: {self.theme},
                Must be selectec from 'qutip', 'light', 'dark', or 'modern'.
                """
            )

        self.bgcolor = self.bgcolor or self.theme["bgcolor"]
        self.color = self.color or self.theme["color"]
        self.wire_color = self.wire_color or self.theme["wire_color"]


class BaseRenderer:
    """
    Base class for rendering quantum circuits with MatRender and TextRender.
    """

    def __init__(self, style: StyleConfig):
        """
        Initialize the base renderer with default values.
        Both Renderers should override these attributes as needed.
        """

        self._qwires = 0
        self._layer_list = []
        self.style = style

    def _get_xskip(self, wire_list: List[int], layer: int) -> float:
        """
        Get the xskip (horizontal value for getting to requested layer) for the gate to be plotted.

        Parameters
        ----------
        wire_list : List[int]
            The list of wires the gate is acting on (control and target).

        layer : int
            The layer the gate is acting on.

        Returns
        -------
        float
            The maximum xskip value needed to reach the specified layer.
        """

        if self.style.align_layer:
            wire_list = list(range(self._qwires))

        xskip = []
        for wire in wire_list:
            xskip.append(sum(self._layer_list[wire][:layer]))

        return max(xskip)

    def _manage_layers(
        self,
        gate_width: float,
        wire_list: List[int],
        layer: int,
        xskip: float = 0,
    ) -> None:
        """
        Manages and updates the layer widths according to the gate's width just plotted.

        Parameters
        ----------
        gate_width : float
            The width of the gate to be plotted.

        wire_list : list
            The list of wires the gate is acting on (control and target).

        layer : int
            The layer the gate is acting on.

        xskip : float, optional
            The horizontal value for getting to requested layer. The default is 0.
        """

        for wire in wire_list:
            # check if requested layer exists for the wire
            if len(self._layer_list[wire]) > layer:
                # check if the layer width is greater than new layer width
                if (
                    self._layer_list[wire][layer]
                    < gate_width + self.style.gate_margin * 2
                ):
                    # update with new layer width
                    self._layer_list[wire][layer] = (
                        gate_width + self.style.gate_margin * 2
                    )
            else:
                # add layer width: new layer width + missing layer widths if exits
                temp = xskip - sum(self._layer_list[wire]) if xskip != 0 else 0
                self._layer_list[wire].append(
                    temp + gate_width + self.style.gate_margin * 2
                )