Skip to content

Dashboard Core — Circuit Diagram

Native circuit-diagram renderer — pure matplotlib, never a Qiskit QuantumCircuit. Replaces qiskit.circuit.draw(output='mpl') for exactly the gate set dense_evolution actually supports, so Composer never depends on Qiskit just to draw a picture.

from dashboard_core.circuit_diagram import draw_native_circuit_diagram

fig = draw_native_circuit_diagram([("h", 0), ("cx", 0, 1)], n_qubits=2)
fig.savefig("bell_pair.png")  # a matplotlib.figure.Figure, save/show it like any other

circuit_diagram

Native circuit-diagram renderer -- pure matplotlib, never a Qiskit QuantumCircuit. Replaces qiskit's circuit.draw(output='mpl') for exactly the reason documented in dashboard_core/engine.py's module docstring: qiskit.circuit.QuantumCircuit.init itself segfaults (SIGSEGV) on macOS CI runners, on the simplest possible call (QuantumCircuit(3) alone, no QASM, no methods called on it) -- see tests/integration/test_interop.py:: TestQiskitInterop for the full reproduction story. There is no way to keep using Qiskit's own drawer without constructing that object, so this module draws directly from the same (name, *qubits[, param]) gate-tuple format every other dense_evolution entry point already uses.

Gate vocabulary comes from dashboard_core._gate_tables (_ONE_QUBIT_STATIC / _ONE_QUBIT_PARAM / _TWO_QUBIT_STATIC / _TWO_QUBIT_PARAM / _THREE_QUBIT_STATIC), shared with qasm_library.py -- the same gate set QASMParser can ever hand back, so nothing here can see a name it doesn't recognize. Was a second, independently hand-copied set of these 5 constants until this shared import (prog.txt, dashboard_core audit point 2): this copy was missing the 'u1'/'phase' aliases the other one had, so a circuit using either of those two real gate names crashed this function with "unsupported gate for native circuit diagram" -- a real bug the duplication caused, not just a divergence risk.

draw_native_circuit_diagram

draw_native_circuit_diagram(
    ops, n_qubits: int, add_measure: bool = True
)

Draws a circuit diagram figure directly from dense_evolution gate tuples -- no Qiskit QuantumCircuit ever constructed. Qubit 0 is drawn at the top, increasing downward, matching Qiskit's own drawer convention so this is a drop-in replacement for dashboard_core.visuals.draw_circuit_figure's panel.

Returns:

Type Description
Figure

Examples:

>>> from dashboard_core.circuit_diagram import draw_native_circuit_diagram
>>> fig = draw_native_circuit_diagram([('h', 0), ('cx', 0, 1)], n_qubits=2)
>>> type(fig).__name__
'Figure'
>>> fig.savefig('bell_pair.png')
Source code in tools/dashboard/core/circuit_diagram.py
def draw_native_circuit_diagram(ops, n_qubits: int, add_measure: bool = True):
    """Draws a circuit diagram figure directly from dense_evolution gate
    tuples -- no Qiskit QuantumCircuit ever constructed. Qubit 0 is drawn
    at the top, increasing downward, matching Qiskit's own drawer
    convention so this is a drop-in replacement for
    dashboard_core.visuals.draw_circuit_figure's panel.

    Returns
    -------
    matplotlib.figure.Figure

    Examples
    --------
    >>> from dashboard_core.circuit_diagram import draw_native_circuit_diagram
    >>> fig = draw_native_circuit_diagram([('h', 0), ('cx', 0, 1)], n_qubits=2)
    >>> type(fig).__name__
    'Figure'
    >>> fig.savefig('bell_pair.png')  # doctest: +SKIP
    """
    scheduled, n_columns = _schedule_columns(ops)
    n_display_columns = n_columns + (1 if add_measure else 0)

    fig_width = max(3.0, 1.1 * (n_display_columns + 1))
    fig_height = max(2.0, 0.9 * n_qubits + 0.5)
    fig, ax = plt.subplots(figsize=(fig_width, fig_height))

    def row_y(qubit):
        return n_qubits - 1 - qubit

    wire_x_end = n_display_columns + 0.7
    for q in range(n_qubits):
        y = row_y(q)
        ax.plot([0, wire_x_end], [y, y], color=_WIRE_COLOR, linewidth=1.2, zorder=1)
        ax.text(-0.35, y, f"q{q}", ha="right", va="center", fontsize=10)

    for op, col in scheduled:
        name = op[0]
        x = col + 1.0
        qubits = _op_qubits(op, name)
        rows = [row_y(q) for q in qubits]
        params = list(op[1 + len(qubits):])

        if name in _ONE_QUBIT_STATIC:
            _draw_one_qubit_box(ax, x, rows[0], _BOX_LABEL.get(name, name.upper()))
        elif name in _ONE_QUBIT_PARAM:
            _draw_one_qubit_box(ax, x, rows[0], _param_label(name, params))
        elif name == "swap":
            _draw_vertical_link(ax, x, rows[0], rows[1])
            _draw_swap_x(ax, x, rows[0])
            _draw_swap_x(ax, x, rows[1])
        elif name in _TWO_QUBIT_STATIC:
            # cx/cy/cz: qubits[0] is control, qubits[1] is target.
            _draw_vertical_link(ax, x, rows[0], rows[1])
            _draw_control_dot(ax, x, rows[0])
            if name == "cx":
                _draw_target_plus(ax, x, rows[1])
            else:
                _draw_one_qubit_box(ax, x, rows[1], _BOX_LABEL.get(name, name.upper()))
        elif name in _TWO_QUBIT_PARAM:
            _draw_vertical_link(ax, x, rows[0], rows[1])
            _draw_control_dot(ax, x, rows[0])
            _draw_one_qubit_box(ax, x, rows[1], _param_label(name, params))
        elif name in _THREE_QUBIT_STATIC:
            # ccx (Toffoli): qubits[0], qubits[1] control, qubits[2] target.
            _draw_vertical_link(ax, x, min(rows), max(rows))
            _draw_control_dot(ax, x, rows[0])
            _draw_control_dot(ax, x, rows[1])
            _draw_target_plus(ax, x, rows[2])
        else:
            raise ValueError(f"unsupported gate for native circuit diagram: {name!r}")

    if add_measure:
        x = n_columns + 1.0
        for q in range(n_qubits):
            y = row_y(q)
            box = Rectangle((x - 0.3, y - 0.3), 0.6, 0.6, facecolor=_MEASURE_FACE, edgecolor=_BOX_EDGE, zorder=3)
            ax.add_patch(box)
            ax.text(x, y, "M", ha="center", va="center", fontsize=9, zorder=4)

    ax.set_xlim(-1.2, wire_x_end + 0.3)
    ax.set_ylim(-0.7, n_qubits - 0.3)
    ax.set_aspect("equal")
    ax.axis("off")
    fig.tight_layout()
    return fig

See also: dashboard_core.visuals, which aggregates this with state_visuals.