hdh 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hdh-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Maria Gragera Garces
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hdh-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.3
2
+ Name: hdh
3
+ Version: 0.0.1
4
+ Summary: Hybrid Dependency Hypergraphs for quantum computation: translation, visualization, and partitioning.
5
+ License: MIT
6
+ Keywords: quantum,compiler,partitioning,hdh,hypergraph
7
+ Author: Maria Gragera Garces
8
+ Author-email: m.gragera.gaces@gmail.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: matplotlib (>=3.0,<4.0)
17
+ Requires-Dist: networkx (>=3.0,<4.0)
18
+ Requires-Dist: qiskit (>=1.0,<2.0)
19
+ Project-URL: Homepage, https://pypi.org/project/hdh/
20
+ Project-URL: Repository, https://github.com/grageragarces/hdh
21
+ Description-Content-Type: text/markdown
22
+
23
+ # hdh
24
+
25
+ **Hybrid Dependency Hypergraphs for Quantum Computation**
26
+
27
+ 🚧 **Work in Progress**
28
+ This library translates quantum computations into Hybrid Dependency Hypergraph (HDH) representations for analysis, visualization, and partitioning.
29
+
30
+ ## Project Goals
31
+
32
+ - Convert quantum circuits into HDH format
33
+ - Support for popular SDKs (e.g., Qiskit)
34
+ - Direct encoding from algorithmic structures
35
+ - Print HDHs in a readable, canonical format
36
+ - Apply graph partitioning (e.g., METIS, KaHyPar) for distribution-aware analysis
37
+
hdh-0.0.1/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # hdh
2
+
3
+ **Hybrid Dependency Hypergraphs for Quantum Computation**
4
+
5
+ 🚧 **Work in Progress**
6
+ This library translates quantum computations into Hybrid Dependency Hypergraph (HDH) representations for analysis, visualization, and partitioning.
7
+
8
+ ## Project Goals
9
+
10
+ - Convert quantum circuits into HDH format
11
+ - Support for popular SDKs (e.g., Qiskit)
12
+ - Direct encoding from algorithmic structures
13
+ - Print HDHs in a readable, canonical format
14
+ - Apply graph partitioning (e.g., METIS, KaHyPar) for distribution-aware analysis
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ from qiskit import QuantumCircuit
2
+ from hdh.converters.convert_from_qiskit import from_qiskit # your existing converter
3
+
4
+ def from_qasm(input_type: str, qasm: str):
5
+ if input_type == 'file':
6
+ circuit = QuantumCircuit.from_qasm_file(qasm)
7
+ elif input_type == 'string':
8
+ circuit = QuantumCircuit.from_qasm_str(qasm)
9
+ else:
10
+ raise ValueError("Unsupported type. Use 'file' or 'string'.")
11
+
12
+ return from_qiskit(circuit)
@@ -0,0 +1,39 @@
1
+ from qiskit import QuantumCircuit
2
+ from typing import Set
3
+ import sys
4
+ import os
5
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
6
+ from hdh.hdh import HDH
7
+ from models.circuit import Circuit
8
+
9
+ def from_qiskit(circuit: QuantumCircuit) -> HDH:
10
+ CONTROL_GATES = {
11
+ "cx": [0],
12
+ "ccx": [0, 1],
13
+ }
14
+
15
+ qc_adapter = Circuit()
16
+
17
+ for instruction in circuit.data:
18
+ gate = instruction.operation
19
+ qargs = instruction.qubits
20
+ cargs = instruction.clbits
21
+ name = gate.name.lower()
22
+
23
+ if name in {"barrier", "snapshot", "delay", "label"}:
24
+ continue
25
+
26
+ q_indices = [circuit.find_bit(q).index for q in qargs]
27
+ c_indices = [circuit.find_bit(c).index for c in cargs]
28
+
29
+ if name == "measure":
30
+ modifies_flags = [False] * len(q_indices)
31
+ elif name == "reset":
32
+ modifies_flags = [True] * len(q_indices)
33
+ else:
34
+ ctrl = CONTROL_GATES.get(name, list(range(len(q_indices) - 1)))
35
+ modifies_flags = [i not in ctrl for i in range(len(q_indices))]
36
+
37
+ qc_adapter.add_instruction(name, q_indices, c_indices, modifies_flags)
38
+
39
+ return qc_adapter.build_hdh()
hdh-0.0.1/hdh/graph.py ADDED
@@ -0,0 +1,33 @@
1
+
2
+ class HDHNode:
3
+ def __init__(self, id, type, time):
4
+ self.id = id
5
+ self.type = type # e.g., 'qubit', 'classical', 'measurement'
6
+ self.time = time
7
+
8
+ def __repr__(self):
9
+ return f"HDHNode(id={self.id}, type={self.type}, time={self.time})"
10
+
11
+ class HDHEdge:
12
+ def __init__(self, id, operation, inputs, outputs):
13
+ self.id = id
14
+ self.operation = operation # e.g., 'H', 'CX', 'M', etc.
15
+ self.inputs = inputs
16
+ self.outputs = outputs
17
+
18
+ def __repr__(self):
19
+ return f"HDHEdge(id={self.id}, op={self.operation}, in={self.inputs}, out={self.outputs})"
20
+
21
+ class HDHGraph:
22
+ def __init__(self):
23
+ self.nodes = {}
24
+ self.edges = []
25
+
26
+ def add_node(self, node):
27
+ self.nodes[node.id] = node
28
+
29
+ def add_edge(self, edge):
30
+ self.edges.append(edge)
31
+
32
+ def __repr__(self):
33
+ return f"HDHGraph(nodes={len(self.nodes)}, edges={len(self.edges)})"
hdh-0.0.1/hdh/hdh.py ADDED
@@ -0,0 +1,59 @@
1
+ from collections import defaultdict
2
+ from typing import Dict, Set, Tuple, List, Literal, Union
3
+
4
+ NodeType = Literal["q", "c"] # quantum or classical
5
+ EdgeType = Literal["q", "c"]
6
+ NodeID = str
7
+ TimeStep = int
8
+
9
+ class HDH:
10
+ def __init__(self):
11
+ self.S: Set[NodeID] = set()
12
+ self.C: Set[frozenset] = set()
13
+ self.T: Set[TimeStep] = set()
14
+ self.sigma: Dict[NodeID, NodeType] = {} # node types
15
+ self.tau: Dict[frozenset, EdgeType] = {} # hyperedge types
16
+ self.time_map: Dict[NodeID, TimeStep] = {} # f: S -> T
17
+
18
+ def add_node(self, node_id: NodeID, node_type: NodeType, time: TimeStep):
19
+ self.S.add(node_id)
20
+ self.sigma[node_id] = node_type
21
+ self.time_map[node_id] = time
22
+ self.T.add(time)
23
+
24
+ def add_hyperedge(self, node_ids: Set[NodeID], edge_type: EdgeType):
25
+ edge = frozenset(node_ids)
26
+ self.C.add(edge)
27
+ self.tau[edge] = edge_type
28
+
29
+ def get_ancestry(self, node: NodeID) -> Set[NodeID]:
30
+ """Return nodes with paths ending at `node` and earlier time steps."""
31
+ return {
32
+ s for s in self.S
33
+ if self.time_map[s] <= self.time_map[node] and self._path_exists(s, node)
34
+ }
35
+
36
+ def get_lineage(self, node: NodeID) -> Set[NodeID]:
37
+ """Return nodes reachable from `node` with later time steps."""
38
+ return {
39
+ s for s in self.S
40
+ if self.time_map[s] >= self.time_map[node] and self._path_exists(node, s)
41
+ }
42
+
43
+ def _path_exists(self, start: NodeID, end: NodeID) -> bool:
44
+ """DFS to find a time-respecting path from `start` to `end`."""
45
+ visited = set()
46
+ stack = [start]
47
+ while stack:
48
+ current = stack.pop()
49
+ if current == end:
50
+ return True
51
+ visited.add(current)
52
+ neighbors = {
53
+ neighbor
54
+ for edge in self.C if current in edge
55
+ for neighbor in edge
56
+ if neighbor != current and self.time_map[neighbor] > self.time_map[current]
57
+ }
58
+ stack.extend(neighbors - visited)
59
+ return False
File without changes
@@ -0,0 +1,39 @@
1
+ import networkx as nx
2
+ import networkx.algorithms.community as nx_comm
3
+ import metis
4
+ from typing import List, Set
5
+ from hdh.hdh import HDH
6
+
7
+ def compute_cut(hdh: HDH, num_parts: int) -> List[Set[str]]:
8
+ """
9
+ Use METIS to partition HDH nodes into disjoint blocks.
10
+
11
+ Returns a list of disjoint sets of node IDs.
12
+ """
13
+ # 1. Build undirected graph from HDH
14
+ G = nx.Graph()
15
+ G.add_nodes_from(hdh.S)
16
+
17
+ for edge in hdh.C:
18
+ edge_nodes = list(edge)
19
+ for i in range(len(edge_nodes)):
20
+ for j in range(i + 1, len(edge_nodes)):
21
+ G.add_edge(edge_nodes[i], edge_nodes[j])
22
+
23
+ # 2. Convert to METIS-compatible graph (requires contiguous integer node IDs)
24
+ node_list = list(G.nodes)
25
+ node_idx_map = {node: idx for idx, node in enumerate(node_list)}
26
+ idx_node_map = {idx: node for node, idx in node_idx_map.items()}
27
+
28
+ metis_graph = nx.relabel_nodes(G, node_idx_map, copy=True)
29
+
30
+ # 3. Call METIS
31
+ _, parts = metis.part_graph(metis_graph, nparts=num_parts)
32
+
33
+ # 4. Build partition sets
34
+ partition = [set() for _ in range(num_parts)]
35
+ for idx, part in enumerate(parts):
36
+ node_id = idx_node_map[idx]
37
+ partition[part].add(node_id)
38
+
39
+ return partition
@@ -0,0 +1,175 @@
1
+ import matplotlib.pyplot as plt
2
+ import networkx as nx
3
+ import numpy as np
4
+ import re
5
+ import sys
6
+ import os
7
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
8
+ from hdh.hdh import HDH
9
+
10
+ def plot_hdh(hdh, save_path=None):
11
+ nodes = list(hdh.S)
12
+ edges = [tuple(e) for e in hdh.C]
13
+
14
+ node_positions = {}
15
+ node_timesteps = {}
16
+ node_qubits = {}
17
+ qubit_labels = set()
18
+ timesteps = set()
19
+
20
+ for node in nodes:
21
+ if node.startswith("q") or node.startswith("c"):
22
+ match = re.match(r"[qc](\d+)_t(\d+)", node)
23
+ if match:
24
+ index, timestep = map(int, match.groups())
25
+ node_timesteps[node] = timestep
26
+ node_qubits[node] = index
27
+ qubit_labels.add(index)
28
+ timesteps.add(timestep)
29
+ else:
30
+ print(f"Skipping node due to unrecognized format: {node}")
31
+
32
+ if not qubit_labels:
33
+ print("No valid nodes found with q{{index}}_t{{step}} or c{{index}}_t{{step}} format.")
34
+ return
35
+
36
+ max_index = max(qubit_labels)
37
+
38
+ for node in nodes:
39
+ if node in node_timesteps and node in node_qubits:
40
+ timestep = node_timesteps[node]
41
+ flipped_index = max_index - node_qubits[node]
42
+ node_positions[node] = (timestep, flipped_index)
43
+
44
+ qubit_ticks = sorted(qubit_labels)
45
+ flipped_ticks = [max_index - i for i in qubit_ticks]
46
+ timestep_ticks = sorted(timesteps)
47
+
48
+ fig, ax = plt.subplots(figsize=(10, 4))
49
+ ax.set_xlabel("Timestep")
50
+ ax.set_ylabel("Qubit/Clbit Index")
51
+ ax.set_xticks(timestep_ticks)
52
+ ax.set_yticks(flipped_ticks)
53
+ ax.set_yticklabels(qubit_ticks)
54
+ ax.set_ylim(min(flipped_ticks) - 1, max(flipped_ticks) + 1)
55
+
56
+ involved_nodes = set()
57
+ for edge in edges:
58
+ involved_nodes.update(edge)
59
+
60
+ for node in involved_nodes:
61
+ if node in node_positions:
62
+ x, y = node_positions[node]
63
+ node_type = hdh.sigma.get(node, "q")
64
+ color = {
65
+ "q": "black",
66
+ "ctrl": "black",
67
+ "c": "orange"
68
+ }.get(node_type, "black")
69
+ ax.plot(x, y, 'o', markersize=10, color=color)
70
+ ax.text(x, y + 0.15, node, ha='center')
71
+
72
+ seen_pairs = set()
73
+ for edge in edges:
74
+ edge_nodes = [n for n in edge if n in node_positions]
75
+
76
+ edge_type = hdh.tau.get(frozenset(edge))
77
+ if edge_type is None:
78
+ node_types = [hdh.sigma.get(n, "q") for n in edge]
79
+ edge_type = "c" if all(t == "c" for t in node_types) else "q"
80
+
81
+ color = "orange" if edge_type == "c" else "black"
82
+
83
+ for i in range(len(edge_nodes)):
84
+ for j in range(i + 1, len(edge_nodes)):
85
+ n1, n2 = edge_nodes[i], edge_nodes[j]
86
+ t1, t2 = node_timesteps[n1], node_timesteps[n2]
87
+
88
+ if t1 == t2:
89
+ continue
90
+
91
+ type1 = hdh.sigma.get(n1, "q")
92
+ type2 = hdh.sigma.get(n2, "q")
93
+ if type1 == "ctrl" and type2 == "ctrl":
94
+ continue
95
+
96
+ if t1 > t2:
97
+ n1, n2 = n2, n1
98
+ t1, t2 = t2, t1
99
+
100
+ pair = (n1, n2)
101
+ if pair in seen_pairs:
102
+ continue
103
+ seen_pairs.add(pair)
104
+
105
+ x0, y0 = node_positions[n1]
106
+ x1, y1 = node_positions[n2]
107
+
108
+ print(f"Edge {edge} has type {edge_type}")
109
+
110
+ if edge_type == "c":
111
+ dx = x1 - x0
112
+ dy = y1 - y0
113
+ dist = np.hypot(dx, dy)
114
+ if dist == 0:
115
+ continue
116
+ t = np.linspace(0, 1, 200)
117
+ x_line = x0 + dx * t
118
+ y_line = y0 + dy * t
119
+ nx_vec = -dy / dist
120
+ ny_vec = dx / dist
121
+ displacement = 0.08 * np.sin(6 * 2 * np.pi * t)
122
+ x_vals = x_line + displacement * nx_vec
123
+ y_vals = y_line + displacement * ny_vec
124
+ ax.plot(x_vals, y_vals, color=color, linewidth=2)
125
+ else:
126
+ ax.plot([x0, x1], [y0, y1], color=color, linewidth=1.5)
127
+
128
+ if save_path:
129
+ plt.savefig(save_path)
130
+ else:
131
+ plt.show()
132
+
133
+
134
+ def plot_hdh_networkx(hdh: HDH):
135
+ G = nx.DiGraph()
136
+
137
+ for node in hdh.S:
138
+ node_type = hdh.sigma[node]
139
+ color = {
140
+ "q": "skyblue",
141
+ "ctrl": "orange",
142
+ "c": "lightgreen"
143
+ }.get(node_type, "orange")
144
+ G.add_node(node, color=color)
145
+
146
+ for edge in hdh.C:
147
+ edge_type = hdh.tau.get(edge)
148
+ if edge_type is None:
149
+ node_types = [hdh.sigma.get(n, "q") for n in edge]
150
+ edge_type = "c" if all(t == "c" for t in node_types) else "q"
151
+ edge_nodes = list(edge)
152
+ for i in range(len(edge_nodes)):
153
+ for j in range(i + 1, len(edge_nodes)):
154
+ G.add_edge(edge_nodes[i], edge_nodes[j], type=edge_type)
155
+
156
+ pos = nx.spring_layout(G, seed=42)
157
+ node_colors = [G.nodes[n]["color"] for n in G.nodes()]
158
+
159
+ edge_colors = []
160
+ edge_styles = []
161
+
162
+ for u, v, d in G.edges(data=True):
163
+ if d.get("type") == "c":
164
+ edge_colors.append("orange")
165
+ edge_styles.append("dashed")
166
+ else:
167
+ edge_colors.append("orange")
168
+ edge_styles.append("solid")
169
+
170
+ for (u, v), color, style in zip(G.edges(), edge_colors, edge_styles):
171
+ nx.draw_networkx_edges(G, pos, edgelist=[(u, v)], edge_color=color, style=style)
172
+
173
+ nx.draw_networkx_nodes(G, pos, node_color=node_colors)
174
+ nx.draw_networkx_labels(G, pos)
175
+ plt.show()
@@ -0,0 +1,24 @@
1
+ [tool.poetry]
2
+ name = "hdh"
3
+ version = "0.0.1"
4
+ description = "Hybrid Dependency Hypergraphs for quantum computation: translation, visualization, and partitioning."
5
+ authors = ["Maria Gragera Garces <m.gragera.gaces@gmail.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ packages = [{ include = "hdh" }]
9
+ homepage = "https://pypi.org/project/hdh/"
10
+ repository = "https://github.com/grageragarces/hdh"
11
+ keywords = ["quantum", "compiler", "partitioning", "hdh", "hypergraph"]
12
+
13
+ [tool.poetry.dependencies]
14
+ python = "^3.10"
15
+ qiskit = "^1.0"
16
+ networkx = "^3.0"
17
+ matplotlib = "^3.0"
18
+
19
+ [tool.poetry.dev-dependencies]
20
+ pytest = "^8.0"
21
+
22
+ [build-system]
23
+ requires = ["poetry-core>=1.0.0"]
24
+ build-backend = "poetry.core.masonry.api"