Skip to content
Empty file added __init__.py
Empty file.
58 changes: 58 additions & 0 deletions cut_and_eval_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'''
Title: cut_and_eval.py
Description: Example of how to cut and evaluate for the purposes of
distributed reconstruction
'''

import os, logging

logging.disable(logging.WARNING)
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "1"
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

from cutqc.main import CutQC
from helper_functions.benchmarks import generate_circ


if __name__ == "__main__":
filename = "adder_example.pkl"
circ_type= 'adder'
circ_size=10
max_width=10

# Generate Example Circuit and Initialize CutQC
circuit = generate_circ(
num_qubits=circ_size,
depth=1,
circuit_type=circ_type,
reg_name="q",
connected_only=True,
seed=None,
)

cutqc = CutQC(
name="%s_%d" % (circ_type, circ_size),
circuit=circuit,
cutter_constraints={
"max_subcircuit_width": max_width,
"max_subcircuit_cuts": 10,
"subcircuit_size_imbalance": 2,
"max_cuts": 10,
"num_subcircuits": [2, 3, 4, 5, 6, 8],
},
)

print ("--- Cut --- ")
cutqc.cut()

if not cutqc.has_solution:
raise Exception("The input circuit and constraints have no viable cuts")

print ("--- Evaluate ---")
cutqc.evaluate(eval_mode="sv", num_shots_fn=None)

print ("--- Dumping CutQC Object into {} ---".format (filename))
cutqc.save_cutqc_obj (filename)

print ("Completed")

16 changes: 16 additions & 0 deletions cut_and_eval_example.slurm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
#SBATCH --output='cut_and_eval_example.out'
#SBATCH --nodes=1 # node count
#SBATCH --ntasks-per-node=1 # total number of tasks across all nodes
#SBATCH --cpus-per-task=12 # cpu-cores per task (>1 if multi-threaded tasks)
#SBATCH --mem=40G # memory per cpu-core (4G is default)
#SBATCH --time=00:00:55 # total run time limit (HH:MM:SS)

# Load Modules
module purge
module load anaconda3/2024.2
conda activate CutQCSummer2025
module load gurobi/12.0.0

python3 cut_and_eval_example.py

58 changes: 58 additions & 0 deletions cutqc/abstract_graph_contractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from abc import ABC, abstractmethod
from time import perf_counter
import numpy as np
from cutqc.post_process_helper import ComputeGraph

class AbstractGraphContractor(ABC):

@abstractmethod
def _compute(self):
pass

def reconstruct(self, compute_graph: ComputeGraph, subcircuit_entry_probs: dict, num_cuts: int) -> None:
self.compute_graph = compute_graph
self.subcircuit_entry_probs = subcircuit_entry_probs
self.overhead = {"additions": 0, "multiplications": 0}
self.num_cuts = num_cuts
self._set_smart_order()

start_time = perf_counter()
res = self._compute()
end_time = perf_counter() - start_time
self.times['compute'] = end_time

return res

def _set_smart_order(self) -> None:
"""
Sets the order in which Kronecker products are computed (greedy subcircuit order).
"""
subcircuit_entry_lengths = {}
for subcircuit_idx in self.subcircuit_entry_probs:
first_entry_init_meas = list(self.subcircuit_entry_probs[subcircuit_idx].keys())[0]
length = len(self.subcircuit_entry_probs[subcircuit_idx][first_entry_init_meas])
subcircuit_entry_lengths[subcircuit_idx] = length

# Sort according to subcircuit lengths (greedy-subcircuit-order)
self.smart_order = sorted(
subcircuit_entry_lengths.keys(),
key=lambda subcircuit_idx: subcircuit_entry_lengths[subcircuit_idx],
)

self.subcircuit_entry_lengths = [subcircuit_entry_lengths[i] for i in self.smart_order]
print(f"subcircuit_entry_length: {self.subcircuit_entry_lengths}", flush=True)
self.result_size = np.prod(self.subcircuit_entry_lengths)


def _get_subcircuit_entry_prob(self, subcircuit_idx: int):
"""
Returns The subcircuit Entry Probability for the subcircuit at index
'SUBCIRCUIT_IDX'
"""

subcircuit_entry_init_meas = self.compute_graph.get_init_meas(subcircuit_idx)
return self.subcircuit_entry_probs[subcircuit_idx][subcircuit_entry_init_meas]

@abstractmethod
def _get_paulibase_probability(self, edge_bases: tuple, edges: list):
pass
132 changes: 124 additions & 8 deletions cutqc/cutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,10 @@ def _add_constraints(self):
"""
for v in range(self.n_vertices):
self.model.addConstr(

gp.quicksum(
[self.vertex_var[i][v] for i in range(self.num_subcircuit)]
),
gp.GRB.EQUAL,
1,
) == 1
)

"""
Expand Down Expand Up @@ -310,41 +309,158 @@ def solve(self):
def read_circ(circuit):
dag = circuit_to_dag(circuit)
edges = []

node_name_ids = {}
id_node_names = {}
vertex_ids = {}

topological_node_id = 0
qubit_gate_counter = {}

for qubit in dag.qubits:
qubit_gate_counter[qubit] = 0

# Assign vertices a unique id w.r.t. their topological order and a unique name
# given from its op and qubits
for vertex in dag.topological_op_nodes():
if len(vertex.qargs) != 2:
raise Exception("vertex does not have 2 qargs!")

arg0, arg1 = vertex.qargs
vertex_name = "%s[%d]%d %s[%d]%d" % (
arg0._register.name,
arg0._index,
qubit_gate_counter[arg0],
arg1._register.name,
arg1._index,
qubit_gate_counter[arg1],
)

qubit_gate_counter[arg0] += 1
qubit_gate_counter[arg1] += 1


if vertex_name not in node_name_ids and vertex._node_id not in vertex_ids:
node_name_ids[vertex_name] = topological_node_id
id_node_names[topological_node_id] = vertex_name
vertex_ids[vertex._node_id] = topological_node_id

topological_node_id += 1

# Collect edge ids
for u, v, _ in dag.edges():

if type(u) == DAGOpNode and type(v) == DAGOpNode:
u_id = vertex_ids[u._node_id]
v_id = vertex_ids[v._node_id]

edges.append((u_id, v_id))

n_vertices = dag.size()

return n_vertices, edges, node_name_ids, id_node_names


def read_circ_2(circuit):

dag = circuit_to_dag(circuit)
edges = []

node_name_ids = {}
id_node_names = {}
vertex_ids = {}

curr_node_id = 0
qubit_gate_counter = {}

all_nodes_sanity = []
all_nodes_sanity_name = []

for qubit in dag.qubits:
qubit_gate_counter[qubit] = 0
i = 0
for vertex in dag.topological_op_nodes():

all_nodes_sanity.append (id(vertex))

if len(vertex.qargs) != 2:
raise Exception("vertex does not have 2 qargs!")

arg0, arg1 = vertex.qargs

vertex_name = "%s[%d]%d %s[%d]%d" % (
arg0._register.name,
arg0._index,
qubit_gate_counter[arg0],

arg1._register.name,
arg1._index,
qubit_gate_counter[arg1],
)

all_nodes_sanity_name.append (vertex_name)

qubit_gate_counter[arg0] += 1
qubit_gate_counter[arg1] += 1
# print(vertex.op.label,vertex_name,curr_node_id)


## Add
if vertex_name not in node_name_ids and id(vertex) not in vertex_ids:
i = i + 1
print (f"vertex name Executed! {id(vertex)}")
node_name_ids[vertex_name] = curr_node_id
id_node_names[curr_node_id] = vertex_name
vertex_ids[id(vertex)] = curr_node_id
curr_node_id += 1

for u, v, _ in dag.edges():
if isinstance(u, DAGOpNode) and isinstance(v, DAGOpNode):
i = 0
for u, v, _ in dag.edges():
# if isinstance(u, DAGOpNode) and isinstance(v, DAGOpNode):
print (f"u is in list: {id(u) in all_nodes_sanity }")
print (f"v is in list: {id(v) in all_nodes_sanity }")

print (f"Type(u){type(u)}")
print (f"Type(v){type(v)}")

if type(u) == DAGOpNode and type(v) == DAGOpNode:
i = i + 1

print (u.op.name)
print (v.op.name)
print (f"Line Executed! {i}")

arg0, arg1 = u.qargs

vertex_name_u = "%s[%d]%d %s[%d]%d" % (
arg0._register.name,
arg0._index,
qubit_gate_counter[arg0],

arg1._register.name,
arg1._index,
qubit_gate_counter[arg1],
)
arg0, arg1 = v.qargs
vertex_name_v = "%s[%d]%d %s[%d]%d" % (
arg0._register.name,
arg0._index,
qubit_gate_counter[arg0],

arg1._register.name,
arg1._index,
qubit_gate_counter[arg1],
)
print (f"u name is in list: {id(u) in all_nodes_sanity }")
print (f"v name is in list: {id(v) in all_nodes_sanity }")

print (id(u))
print (id(v))

## Ensure end nodes are in the all node list

u_id = vertex_ids[id(u)]
v_id = vertex_ids[id(v)]
edges.append((u_id, v_id))
edges.append((u_id, v_id))

n_vertices = dag.size()
return n_vertices, edges, node_name_ids, id_node_names
Expand Down
Loading