πŸ““ Qdislib Example Notebook with PyCOMPSsΒΆ

This document explains each section of the Qdislib Jupyter notebook, showcasing how to apply gate and wire cutting techniques to large quantum circuits using Qdislib, Qibo, Qiskit, and PyCOMPSs.

Import the PyCOMPSs library

[1]:
import pycompss.interactive as ipycompss

πŸ”§ 1. Environment Setup with PyCOMPSsΒΆ

Goal: Start the PyCOMPSs runtime for distributed execution.

What Happens:

  • Loads required COMPSs XML config files: project.xml and resources.xml.

  • Enables optional flags like graph, monitor, debug, and trace.

Why: PyCOMPSs enables parallel task execution β€” crucial for evaluating subcircuits in distributed environments.

[2]:
ipycompss.start(graph=True, monitor=1000)  # debug=True, trace=True
********************************************************
**************** PyCOMPSs Interactive ******************
********************************************************
*          .-~~-.--.           ______         ______   *
*         :         )         |____  \       |____  \  *
*   .~ ~ -.\       /.- ~~ .      __) |          __) |  *
*   >       `.   .'       <     |__  |         |__  |  *
*  (         .- -.         )   ____) |   _    ____) |  *
*   `- -.-~  `- -'  ~-.- -'   |______/  |_|  |______/  *
*     (        :        )           _ _ .-:            *
*      ~--.    :    .--~        .-~  .-~  }            *
*          ~-.-^-.-~ \_      .~  .-~   .~              *
*                   \ \ '     \ '_ _ -~                *
*                    \`.\`.    //                      *
*           . - ~ ~-.__\`.\`-.//                       *
*       .-~   . - ~  }~ ~ ~-.~-.                       *
*     .' .-~      .-~       :/~-.~-./:                 *
*    /_~_ _ . - ~                 ~-.~-._              *
*                                     ~-.<             *
********************************************************
* - Starting COMPSs runtime...                         *
* - Log path : /home/mtejedor/.COMPSs/Interactive_248/
* - PyCOMPSs Runtime started... Have fun!              *
********************************************************

Import task and compss_wait_on module before annotating functions or methods

[3]:
from pycompss.api.task import task
from pycompss.api.api import compss_wait_on

🧠 2. Importing Required Modules¢

Modules:

  • qibo.models, qibo.gates, qibo.hamiltonians: for defining and manipulating quantum circuits.

  • qd: likely refers to Qdislib, which contains the circuit cutting functions.

Setup:

  • Sets the backend for Qibo (β€œnumpy”) to run locally on CPU.

[4]:
import matplotlib.pyplot as plt
import numpy as np
import qibo
from qibo import models, gates, hamiltonians, callbacks
from qibo.models import Circuit
from qibo.symbols import X, Y, Z, I
from qibo.ui import plot_circuit

qibo.__version__
qibo.set_backend("numpy")
[Qibo 0.2.16|INFO|2025-05-26 15:19:29]: Using numpy backend on /CPU:0

Import Qdislib where the circuit cutting is implemented

[5]:
import Qdislib.api as qd

βš™οΈ 3. Define the Main CircuitΒΆ

Function entire_circuit(): builds a 10-qubit circuit with:

  • Single-qubit gates: H, RX, RY, RZ.

  • Two-qubit gates: CZ.

Circuit Objective: Simulates a non-trivial entangled circuit useful for demonstrating cutting algorithms.

[6]:
def entire_circuit():
    nqubits = 10
    circuit = models.Circuit(nqubits)

    circuit.add(gates.H(0))
    circuit.add(gates.CZ(0, 1))
    circuit.add(gates.CZ(2, 6))
    circuit.add(gates.RZ(8, np.pi / 3))

    circuit.add(gates.RY(3, np.pi / 5))
    circuit.add(gates.RX(4, np.pi / 5))
    circuit.add(gates.CZ(0, 2))
    circuit.add(gates.CZ(5, 9))

    circuit.add(gates.CZ(3, 5))
    circuit.add(gates.CZ(3, 4))
    circuit.add(gates.CZ(6, 7))
    circuit.add(gates.RY(7, np.pi / 5))
    circuit.add(gates.RZ(1, np.pi / 5))

    circuit.add(gates.CZ(1, 5))
    circuit.add(gates.RX(6, np.pi / 5))
    circuit.add(gates.CZ(7, 8))

    circuit.add(gates.H(9))
    return circuit


circuit = entire_circuit()
# print(circuit.draw())
plot_circuit(circuit, scale=0.5)
../_images/notebooks_Qdislib_Notebook_12_0.png

βœ‚οΈ 4. Gate Cutting ExampleΒΆ

qd.find_cut(circuit): Automatically identifies gates suitable for cutting.

  • Example result: [β€˜CZ_2’]

qd.gate_cutting(circuit, cut):

  • Applies the gate cutting algorithm.

  • Cuts the circuit at the specified gate.

  • Evaluates subcircuits and reconstructs the expectation value.

  • Output: A reconstructed value (e.g., 0.0084…).

[7]:
circuit = entire_circuit()

cut = qd.find_cut(circuit)
print(cut)
['CZ_2']
[8]:
reconstruction = qd.gate_cutting(circuit, cut)
print(reconstruction)
0.01041412353515625

πŸ”Œ 5. Wire Cutting ExampleΒΆ

find_cut(…, gate_cut=False):

  • Finds cuts (pairs of gates) between which a wire cut is possible.

  • Force the algorithm to only finde wire cuts (setting gate_cut=False)

  • Example: [(β€˜CZ_2’, β€˜CZ_7’)]

qd.wire_cutting(…):

  • Applies the wire cutting method across the selected gates.

  • Calculates the reconstructed expectation value.

[9]:
circuit = entire_circuit()

cut = qd.find_cut(circuit, gate_cut=False)
print(cut)
[('CZ_2', 'CZ_7')]
[10]:
reconstruction = qd.wire_cutting(circuit, cut)
print(reconstruction)
0.0036580199999999953

πŸ“Š 6. Exact Expected ValueΒΆ

qd.analytical_solution(circuit, β€œZ”*nqubits):

  • Computes the expected value using a symbolic statevector.

  • Used as a ground-truth comparison.

  • Observable can be modified

Observation: Returns exact expected value β€” allows evaluation of gate cutting accuracy.

[11]:
circuit = entire_circuit()

analytic = qd.analytical_solution(circuit, "Z" * circuit.nqubits)
print(analytic)
0.0

🧩 7. Gate Cutting with Subcircuits¢

qd.gate_cutting_subcircuits(…):

  • Performs gate cutting like before but also returns the subcircuits used for reconstruction.

compss_wait_on(subcircuits): Synchronizes and retrieves the results (used with PyCOMPSs).

Printed output: Visual representation of generated subcircuits using Qiskit’s circuit print.

[12]:
circuit = entire_circuit()

cut = qd.find_cut(circuit)
print(cut)
subcircuits = qd.gate_cutting_subcircuits(circuit, cut, "qiskit")

subcircuits = compss_wait_on(subcircuits)

for subcirc in subcircuits:
    display(subcirc.draw(output="mpl"))
['CZ_2']
../_images/notebooks_Qdislib_Notebook_22_1.png
../_images/notebooks_Qdislib_Notebook_22_2.png
../_images/notebooks_Qdislib_Notebook_22_3.png
../_images/notebooks_Qdislib_Notebook_22_4.png
../_images/notebooks_Qdislib_Notebook_22_5.png
../_images/notebooks_Qdislib_Notebook_22_6.png
../_images/notebooks_Qdislib_Notebook_22_7.png
../_images/notebooks_Qdislib_Notebook_22_8.png
../_images/notebooks_Qdislib_Notebook_22_9.png
../_images/notebooks_Qdislib_Notebook_22_10.png
../_images/notebooks_Qdislib_Notebook_22_11.png
../_images/notebooks_Qdislib_Notebook_22_12.png

🧩 7.b Reconstruction Gate Cutting with Subcircuits¢

In this section, we demonstrate the reconstruction of the expectation value of a quantum circuit that has been partitioned into subcircuits using gate cutting. Each subcircuit is simulated independently, and their results are combined to approximate the expectation value of the original (uncut) circuit.

The process follows these main steps:

  • Execute each subcircuit individually to obtain its contribution to the total expectation value.

  • Use the reconstruction algorithm to combine the individual results and approximate the expectation value of the full circuit.

[13]:
from Qdislib.core.cutting_algorithms.gate_cutting import _expec_value_qiskit

results = []
for subcircuit in subcircuits:
    # Execute individually each subcircuit
    result = _expec_value_qiskit(subcircuit)
    results.append(result)

results = compss_wait_on(results)

# Array with the individual expectation values of each subcircuit
print(results)

# Reconstruction of the original circuit expected value from the array of results
recons = qd.gate_cutting_subcircuit_reconstruction(results,number_cuts=1)
print(recons)
[-0.00390625, 0.044921875, 0.01171875, -0.009765625, -0.658203125, 0.01953125, -0.6484375, -0.09765625, -0.005859375, 0.013671875, -0.009765625, -0.01953125]
-0.03837013244628906

🧩 8. Wire Cutting with Subcircuits¢

Same as gate cutting, but uses wire cutting logic.

Subcircuits generated are more complex and may include measurements and resets.

Visuals: Many circuit renderings show various Qiskit circuits built from the wire cut portions.

[14]:
circuit = entire_circuit()

cut = qd.find_cut(circuit, gate_cut=False)
print(cut)
subcircuits = qd.wire_cutting_subcircuits(circuit, cut, "qibo")

subcircuits = compss_wait_on(subcircuits)

for subcirc in subcircuits:
    # print(subcirc)
    plot_circuit(subcirc, scale=0.5);
[('CZ_2', 'RZ_13')]
../_images/notebooks_Qdislib_Notebook_26_1.png
../_images/notebooks_Qdislib_Notebook_26_2.png
../_images/notebooks_Qdislib_Notebook_26_3.png
../_images/notebooks_Qdislib_Notebook_26_4.png
../_images/notebooks_Qdislib_Notebook_26_5.png
../_images/notebooks_Qdislib_Notebook_26_6.png
../_images/notebooks_Qdislib_Notebook_26_7.png
../_images/notebooks_Qdislib_Notebook_26_8.png
../_images/notebooks_Qdislib_Notebook_26_9.png
../_images/notebooks_Qdislib_Notebook_26_10.png
../_images/notebooks_Qdislib_Notebook_26_11.png
../_images/notebooks_Qdislib_Notebook_26_12.png
../_images/notebooks_Qdislib_Notebook_26_13.png
../_images/notebooks_Qdislib_Notebook_26_14.png
../_images/notebooks_Qdislib_Notebook_26_15.png
../_images/notebooks_Qdislib_Notebook_26_16.png

🧩 8.b Reconstruction Wire Cutting with Subcircuits¢

In this section, we demonstrate the reconstruction of the expectation value of a quantum circuit that has been partitioned into subcircuits using wire cutting. Each subcircuit is simulated independently, and their results are combined to approximate the expectation value of the original (uncut) circuit.

The process follows these main steps:

  • Execute each subcircuit individually to obtain its contribution to the total expectation value.

  • Use the reconstruction algorithm to combine the individual results and approximate the expectation value of the full circuit.

[15]:
from Qdislib.core.cutting_algorithms.wire_cutting import _expec_value_qibo

results = []
for subcircuit in subcircuits:
    # Execute individually each subcircuit
    result = _expec_value_qibo(subcircuit)
    results.append(result)

results = compss_wait_on(results)

# Array with the individual expectation values of each subcircuit
print(results)

# Reconstruction of the original circuit expected value from the array of results
recons = qd.wire_cutting_subcircuit_reconstruction(results,number_cuts=1)
print(recons)
[-0.02734375, -0.0546875, 0.015625, 0.029296875, -0.087890625, -0.02734375, -0.01171875, -0.072265625, -0.015625, 0.068359375, 0.03515625, -0.04296875, -0.052734375, 0.0, 0.02734375, 0.03125]
0.00154876708984375

⏹️ 9. Shutting Down PyCOMPSs¢

ipycompss.stop(sync=True):

  • Stops the COMPSs runtime.

  • Synchronizes any unresolved futures (e.g., cut, subcircuits).

Why it matters: Proper shutdown is required in PyCOMPSs to finalize all asynchronous tasks cleanly.

[16]:
ipycompss.stop(sync=True)
********************************************************
***************** STOPPING PyCOMPSs ********************
********************************************************
Checking if any issue happened.
Synchronizing all future objects left on the user scope.
Found a list to synchronize: cut
Found a list to synchronize: subcircuits
Found a list to synchronize: results
Found a future object: result
         - Could not retrieve object: result
********************************************************
[17]:
ipycompss.complete_task_graph(fit=True)
../_images/notebooks_Qdislib_Notebook_31_0.jpg