zena-sdk 0.1.0__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.
- zena_sdk-0.1.0/.gitignore +59 -0
- zena_sdk-0.1.0/PKG-INFO +71 -0
- zena_sdk-0.1.0/README.md +53 -0
- zena_sdk-0.1.0/__init__.py +7 -0
- zena_sdk-0.1.0/circuit/__init__.py +2 -0
- zena_sdk-0.1.0/circuit/quantum_circuit.py +218 -0
- zena_sdk-0.1.0/circuit/register.py +28 -0
- zena_sdk-0.1.0/compiler/__init__.py +72 -0
- zena_sdk-0.1.0/execute.py +35 -0
- zena_sdk-0.1.0/providers/__init__.py +5 -0
- zena_sdk-0.1.0/providers/aer.py +71 -0
- zena_sdk-0.1.0/providers/backend.py +18 -0
- zena_sdk-0.1.0/providers/job.py +24 -0
- zena_sdk-0.1.0/pyproject.toml +33 -0
- zena_sdk-0.1.0/visualization/__init__.py +28 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# General / IDEs
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
.vscode/
|
|
5
|
+
.idea/
|
|
6
|
+
*.swp
|
|
7
|
+
|
|
8
|
+
# Python
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.pyc
|
|
11
|
+
*.pyo
|
|
12
|
+
*.pyd
|
|
13
|
+
*.egg-info/
|
|
14
|
+
.egg-info
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
.env
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.tox/
|
|
21
|
+
|
|
22
|
+
# Testing and Coverage
|
|
23
|
+
.coverage
|
|
24
|
+
htmlcov/
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.hypothesis/
|
|
27
|
+
junit.xml
|
|
28
|
+
cov.xml
|
|
29
|
+
|
|
30
|
+
# Rust
|
|
31
|
+
target/
|
|
32
|
+
**/*.rs.bk
|
|
33
|
+
|
|
34
|
+
# Node.js / Frontend
|
|
35
|
+
node_modules/
|
|
36
|
+
frontend/.vite/
|
|
37
|
+
frontend/dist/
|
|
38
|
+
npm-debug.log*
|
|
39
|
+
yarn-debug.log*
|
|
40
|
+
yarn-error.log*
|
|
41
|
+
.npm/
|
|
42
|
+
.next/
|
|
43
|
+
|
|
44
|
+
# Generated Artifacts / Logs
|
|
45
|
+
*.log
|
|
46
|
+
*.bak
|
|
47
|
+
test_*.png
|
|
48
|
+
*_results.png
|
|
49
|
+
*.ir
|
|
50
|
+
output.txt
|
|
51
|
+
app.py.local_backup
|
|
52
|
+
|
|
53
|
+
# Documentation
|
|
54
|
+
docs/_build/
|
|
55
|
+
|
|
56
|
+
# Project Specific
|
|
57
|
+
web_app_copy/
|
|
58
|
+
features/
|
|
59
|
+
tests/
|
zena_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zena-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A high-level Python SDK for the Zena Quantum Simulator, inspired by Qiskit.
|
|
5
|
+
Author-email: Zena Team <info@zena.quantum>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Requires-Dist: numpy>=1.20
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Zena SDK
|
|
20
|
+
|
|
21
|
+
**Zena SDK** is a high-level Python library for creating and simulating quantum circuits, designed with a familiar API for users of Qiskit. It serves as the user-facing layer for the powerful Zena Quantum Simulator engine.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install zena-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
*(Note: Requires valid `qsys` and `simulator_statevector` backend installations)*
|
|
30
|
+
|
|
31
|
+
## Basic Usage
|
|
32
|
+
|
|
33
|
+
Zena SDK allows you to build circuits using `QuantumRegister`, `ClassicalRegister`, and `QuantumCircuit` in a way that feels completely natural.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from zena import QuantumCircuit, execute, Aer
|
|
37
|
+
|
|
38
|
+
# 1. Create a Quantum Circuit
|
|
39
|
+
qc = QuantumCircuit(2, 2)
|
|
40
|
+
qc.h(0)
|
|
41
|
+
qc.cx(0, 1)
|
|
42
|
+
qc.measure([0, 1], [0, 1])
|
|
43
|
+
|
|
44
|
+
# 2. visualizae
|
|
45
|
+
print(qc.draw())
|
|
46
|
+
|
|
47
|
+
# 3. Choose a Backend
|
|
48
|
+
backend = Aer.get_backend('statevector_simulator')
|
|
49
|
+
|
|
50
|
+
# 4. Execute
|
|
51
|
+
job = execute(qc, backend, shots=1024)
|
|
52
|
+
result = job.result()
|
|
53
|
+
|
|
54
|
+
# 5. Get Results
|
|
55
|
+
print("Counts:", result.get_counts())
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Familiar API**: Drop-in conceptual replacement for basic Qiskit circuits.
|
|
61
|
+
- **High Performance**: Powered by a Rust-based statevector simulator.
|
|
62
|
+
- **Visualizations**: Built-in methods for circuit drawing and result plotting.
|
|
63
|
+
- **Extensible**: Modular architecture allowing for future backend plugins.
|
|
64
|
+
|
|
65
|
+
## Architecture
|
|
66
|
+
|
|
67
|
+
Zena SDK (`zena`) sits on top of the Core Engine (`qsys`).
|
|
68
|
+
|
|
69
|
+
- **Base Layer**: `qsys` (Intermediate Representation & Composer)
|
|
70
|
+
- **Simulation Layer**: `simulator_statevector` (Rust-based capabilities)
|
|
71
|
+
- **User Layer**: `zena` (High-level Circuit & Provider API)
|
zena_sdk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Zena SDK
|
|
2
|
+
|
|
3
|
+
**Zena SDK** is a high-level Python library for creating and simulating quantum circuits, designed with a familiar API for users of Qiskit. It serves as the user-facing layer for the powerful Zena Quantum Simulator engine.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install zena-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
*(Note: Requires valid `qsys` and `simulator_statevector` backend installations)*
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
Zena SDK allows you to build circuits using `QuantumRegister`, `ClassicalRegister`, and `QuantumCircuit` in a way that feels completely natural.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from zena import QuantumCircuit, execute, Aer
|
|
19
|
+
|
|
20
|
+
# 1. Create a Quantum Circuit
|
|
21
|
+
qc = QuantumCircuit(2, 2)
|
|
22
|
+
qc.h(0)
|
|
23
|
+
qc.cx(0, 1)
|
|
24
|
+
qc.measure([0, 1], [0, 1])
|
|
25
|
+
|
|
26
|
+
# 2. visualizae
|
|
27
|
+
print(qc.draw())
|
|
28
|
+
|
|
29
|
+
# 3. Choose a Backend
|
|
30
|
+
backend = Aer.get_backend('statevector_simulator')
|
|
31
|
+
|
|
32
|
+
# 4. Execute
|
|
33
|
+
job = execute(qc, backend, shots=1024)
|
|
34
|
+
result = job.result()
|
|
35
|
+
|
|
36
|
+
# 5. Get Results
|
|
37
|
+
print("Counts:", result.get_counts())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Familiar API**: Drop-in conceptual replacement for basic Qiskit circuits.
|
|
43
|
+
- **High Performance**: Powered by a Rust-based statevector simulator.
|
|
44
|
+
- **Visualizations**: Built-in methods for circuit drawing and result plotting.
|
|
45
|
+
- **Extensible**: Modular architecture allowing for future backend plugins.
|
|
46
|
+
|
|
47
|
+
## Architecture
|
|
48
|
+
|
|
49
|
+
Zena SDK (`zena`) sits on top of the Core Engine (`qsys`).
|
|
50
|
+
|
|
51
|
+
- **Base Layer**: `qsys` (Intermediate Representation & Composer)
|
|
52
|
+
- **Simulation Layer**: `simulator_statevector` (Rust-based capabilities)
|
|
53
|
+
- **User Layer**: `zena` (High-level Circuit & Provider API)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QuantumCircuit class mimicking Qiskit's API, delegating to qsys.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Union, List, Optional
|
|
5
|
+
from .register import QuantumRegister, ClassicalRegister
|
|
6
|
+
|
|
7
|
+
# Import the core composer from existing codebase
|
|
8
|
+
# We treat qsys as the internal engine
|
|
9
|
+
try:
|
|
10
|
+
from qsys.circuit.quantum_circuit import QuantumCircuit as QSysCircuit
|
|
11
|
+
except ImportError as e:
|
|
12
|
+
# Fallback/Mock for development if qsys is not in path
|
|
13
|
+
print(f"DEBUG: Failed to import qsys: {e}")
|
|
14
|
+
QSysCircuit = None
|
|
15
|
+
|
|
16
|
+
class QuantumCircuit:
|
|
17
|
+
"""
|
|
18
|
+
QuantumCircuit adaptable to Zena SDK.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, *regs, name: Optional[str] = None):
|
|
21
|
+
self.name = name
|
|
22
|
+
self.qregs = []
|
|
23
|
+
self.cregs = []
|
|
24
|
+
self._qubits = []
|
|
25
|
+
self._clbits = []
|
|
26
|
+
|
|
27
|
+
# Parse arguments similar to Qiskit (int or Register)
|
|
28
|
+
if len(regs) == 0:
|
|
29
|
+
# Empty circuit
|
|
30
|
+
pass
|
|
31
|
+
elif all(isinstance(r, int) for r in regs):
|
|
32
|
+
# Case: QuantumCircuit(n_qubits, n_clbits)
|
|
33
|
+
n_qubits = regs[0]
|
|
34
|
+
n_clbits = regs[1] if len(regs) > 1 else 0
|
|
35
|
+
self.add_register(QuantumRegister(n_qubits, 'q'))
|
|
36
|
+
if n_clbits > 0:
|
|
37
|
+
self.add_register(ClassicalRegister(n_clbits, 'c'))
|
|
38
|
+
else:
|
|
39
|
+
# Case: QuantumCircuit(qreg, creg)
|
|
40
|
+
for r in regs:
|
|
41
|
+
if isinstance(r, (QuantumRegister, ClassicalRegister)):
|
|
42
|
+
self.add_register(r)
|
|
43
|
+
else:
|
|
44
|
+
raise TypeError("Arguments must be integers or Registers")
|
|
45
|
+
|
|
46
|
+
# Initialize the underlying qsys Core Circuit
|
|
47
|
+
if QSysCircuit:
|
|
48
|
+
self._core_circuit = QSysCircuit(len(self._qubits), len(self._clbits))
|
|
49
|
+
else:
|
|
50
|
+
self._core_circuit = None
|
|
51
|
+
print("Warning: qsys.circuit not found. Core delegation disabled.")
|
|
52
|
+
|
|
53
|
+
def add_register(self, register: Union[QuantumRegister, ClassicalRegister]):
|
|
54
|
+
if isinstance(register, QuantumRegister):
|
|
55
|
+
self.qregs.append(register)
|
|
56
|
+
# Flatten qubits mapping (simplistic for now)
|
|
57
|
+
start_index = len(self._qubits)
|
|
58
|
+
self._qubits.extend([i for i in range(start_index, start_index + register.size)])
|
|
59
|
+
elif isinstance(register, ClassicalRegister):
|
|
60
|
+
self.cregs.append(register)
|
|
61
|
+
start_index = len(self._clbits)
|
|
62
|
+
self._clbits.extend([i for i in range(start_index, start_index + register.size)])
|
|
63
|
+
|
|
64
|
+
def h(self, qubit):
|
|
65
|
+
"""Apply Hadamard gate."""
|
|
66
|
+
q_idx = self._resolve_index(qubit)
|
|
67
|
+
if self._core_circuit:
|
|
68
|
+
self._core_circuit.h(q_idx)
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def x(self, qubit):
|
|
72
|
+
"""Apply Pauli-X gate."""
|
|
73
|
+
q_idx = self._resolve_index(qubit)
|
|
74
|
+
if self._core_circuit:
|
|
75
|
+
self._core_circuit.x(q_idx)
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def y(self, qubit):
|
|
79
|
+
"""Apply Pauli-Y gate."""
|
|
80
|
+
q_idx = self._resolve_index(qubit)
|
|
81
|
+
if self._core_circuit:
|
|
82
|
+
# Note: qsys uses string name 'y' for Instruction, which execute.py handles
|
|
83
|
+
from qsys.ir.types import Instruction
|
|
84
|
+
self._core_circuit._instructions.append(Instruction(name="y", qubits=(q_idx,)))
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def z(self, qubit):
|
|
88
|
+
"""Apply Pauli-Z gate."""
|
|
89
|
+
q_idx = self._resolve_index(qubit)
|
|
90
|
+
if self._core_circuit:
|
|
91
|
+
from qsys.ir.types import Instruction
|
|
92
|
+
self._core_circuit._instructions.append(Instruction(name="z", qubits=(q_idx,)))
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def s(self, qubit):
|
|
96
|
+
"""Apply S gate."""
|
|
97
|
+
q_idx = self._resolve_index(qubit)
|
|
98
|
+
if self._core_circuit:
|
|
99
|
+
from qsys.ir.types import Instruction
|
|
100
|
+
self._core_circuit._instructions.append(Instruction(name="s", qubits=(q_idx,)))
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def sdg(self, qubit):
|
|
104
|
+
"""Apply S-dagger gate."""
|
|
105
|
+
q_idx = self._resolve_index(qubit)
|
|
106
|
+
if self._core_circuit:
|
|
107
|
+
from qsys.ir.types import Instruction
|
|
108
|
+
self._core_circuit._instructions.append(Instruction(name="sdg", qubits=(q_idx,)))
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def t(self, qubit):
|
|
112
|
+
"""Apply T gate."""
|
|
113
|
+
q_idx = self._resolve_index(qubit)
|
|
114
|
+
if self._core_circuit:
|
|
115
|
+
from qsys.ir.types import Instruction
|
|
116
|
+
self._core_circuit._instructions.append(Instruction(name="t", qubits=(q_idx,)))
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def tdg(self, qubit):
|
|
120
|
+
"""Apply T-dagger gate."""
|
|
121
|
+
q_idx = self._resolve_index(qubit)
|
|
122
|
+
if self._core_circuit:
|
|
123
|
+
from qsys.ir.types import Instruction
|
|
124
|
+
self._core_circuit._instructions.append(Instruction(name="tdg", qubits=(q_idx,)))
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def sx(self, qubit):
|
|
128
|
+
"""Apply sqrt-X gate."""
|
|
129
|
+
q_idx = self._resolve_index(qubit)
|
|
130
|
+
if self._core_circuit:
|
|
131
|
+
self._core_circuit.sx(q_idx)
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def rx(self, theta, qubit):
|
|
135
|
+
"""Apply RX gate."""
|
|
136
|
+
q_idx = self._resolve_index(qubit)
|
|
137
|
+
if self._core_circuit:
|
|
138
|
+
from qsys.ir.types import Instruction
|
|
139
|
+
self._core_circuit._instructions.append(
|
|
140
|
+
Instruction(name="rx", qubits=(q_idx,), params=(float(theta),))
|
|
141
|
+
)
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def ry(self, theta, qubit):
|
|
145
|
+
"""Apply RY gate."""
|
|
146
|
+
q_idx = self._resolve_index(qubit)
|
|
147
|
+
if self._core_circuit:
|
|
148
|
+
from qsys.ir.types import Instruction
|
|
149
|
+
self._core_circuit._instructions.append(
|
|
150
|
+
Instruction(name="ry", qubits=(q_idx,), params=(float(theta),))
|
|
151
|
+
)
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
def rz(self, theta, qubit):
|
|
155
|
+
"""Apply RZ gate."""
|
|
156
|
+
q_idx = self._resolve_index(qubit)
|
|
157
|
+
if self._core_circuit:
|
|
158
|
+
self._core_circuit.rz(q_idx, theta)
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def cx(self, control_qubit, target_qubit):
|
|
162
|
+
"""Apply CNOT gate."""
|
|
163
|
+
c_idx = self._resolve_index(control_qubit)
|
|
164
|
+
t_idx = self._resolve_index(target_qubit)
|
|
165
|
+
if self._core_circuit:
|
|
166
|
+
self._core_circuit.cx(c_idx, t_idx)
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def swap(self, qubit1, qubit2):
|
|
170
|
+
"""Apply SWAP gate."""
|
|
171
|
+
q1_idx = self._resolve_index(qubit1)
|
|
172
|
+
q2_idx = self._resolve_index(qubit2)
|
|
173
|
+
if self._core_circuit:
|
|
174
|
+
from qsys.ir.types import Instruction
|
|
175
|
+
self._core_circuit._instructions.append(Instruction(name="swap", qubits=(q1_idx, q2_idx)))
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def measure(self, qubits, clbits):
|
|
179
|
+
"""Measure quantum bits into classical bits."""
|
|
180
|
+
if isinstance(qubits, int):
|
|
181
|
+
qubits = [qubits]
|
|
182
|
+
if isinstance(clbits, int):
|
|
183
|
+
clbits = [clbits]
|
|
184
|
+
|
|
185
|
+
if len(qubits) != len(clbits):
|
|
186
|
+
raise ValueError("Number of qubits and classical bits must match.")
|
|
187
|
+
|
|
188
|
+
for q, c in zip(qubits, clbits):
|
|
189
|
+
q_idx = self._resolve_index(q)
|
|
190
|
+
c_idx = self._resolve_cbit_index(c)
|
|
191
|
+
if self._core_circuit:
|
|
192
|
+
self._core_circuit.measure(q_idx, c_idx)
|
|
193
|
+
return self
|
|
194
|
+
|
|
195
|
+
def draw(self, output='text'):
|
|
196
|
+
"""Draw the circuit."""
|
|
197
|
+
if self._core_circuit:
|
|
198
|
+
return self._core_circuit.draw(mode=output)
|
|
199
|
+
return "Core circuit not initialized."
|
|
200
|
+
|
|
201
|
+
def _resolve_index(self, qubit) -> int:
|
|
202
|
+
"""
|
|
203
|
+
Resolve user input (int or bit object) to flat integer index for qsys.
|
|
204
|
+
For now, we assume user passes integers as indices.
|
|
205
|
+
"""
|
|
206
|
+
# TODO: Support Qubit object (e.g. qreg[0])
|
|
207
|
+
if isinstance(qubit, int):
|
|
208
|
+
if qubit < 0 or qubit >= len(self._qubits):
|
|
209
|
+
raise IndexError(f"Qubit index {qubit} out of range.")
|
|
210
|
+
return qubit
|
|
211
|
+
raise TypeError("Qubit must be an integer index for now.")
|
|
212
|
+
|
|
213
|
+
def _resolve_cbit_index(self, cbit) -> int:
|
|
214
|
+
if isinstance(cbit, int):
|
|
215
|
+
if cbit < 0 or cbit >= len(self._clbits):
|
|
216
|
+
raise IndexError(f"Clbit index {cbit} out of range.")
|
|
217
|
+
return cbit
|
|
218
|
+
raise TypeError("Clbit must be an integer index for now.")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum and Classical Registers.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
class Register:
|
|
7
|
+
"""Base class for registers."""
|
|
8
|
+
def __init__(self, size: int, name: Optional[str] = None):
|
|
9
|
+
if size <= 0:
|
|
10
|
+
raise ValueError("Register size must be positive")
|
|
11
|
+
self.size = size
|
|
12
|
+
self.name = name
|
|
13
|
+
|
|
14
|
+
def __len__(self):
|
|
15
|
+
return self.size
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f"{self.__class__.__name__}({self.size}, '{self.name}')"
|
|
19
|
+
|
|
20
|
+
class QuantumRegister(Register):
|
|
21
|
+
"""Implement a quantum register."""
|
|
22
|
+
def __init__(self, size: int, name: Optional[str] = "q"):
|
|
23
|
+
super().__init__(size, name)
|
|
24
|
+
|
|
25
|
+
class ClassicalRegister(Register):
|
|
26
|
+
"""Implement a classical register."""
|
|
27
|
+
def __init__(self, size: int, name: Optional[str] = "c"):
|
|
28
|
+
super().__init__(size, name)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zena Compiler module.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Union, List, Optional
|
|
5
|
+
from ..circuit import QuantumCircuit
|
|
6
|
+
from ..providers.backend import Backend
|
|
7
|
+
|
|
8
|
+
# Import qsys transpiler
|
|
9
|
+
try:
|
|
10
|
+
from qsys.transpiler.passes import transpile as qsys_transpile
|
|
11
|
+
except ImportError:
|
|
12
|
+
qsys_transpile = None
|
|
13
|
+
|
|
14
|
+
def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
|
|
15
|
+
backend: Optional[Backend] = None,
|
|
16
|
+
**kwargs):
|
|
17
|
+
"""
|
|
18
|
+
Transpile the circuit(s) for a given backend.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
circuits: Circuit or list of circuits to transpile.
|
|
22
|
+
backend: Backend to target (optional).
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The transpiled circuit(s).
|
|
26
|
+
"""
|
|
27
|
+
if not qsys_transpile:
|
|
28
|
+
print("Warning: qsys transpiler not found. Returning circuit(s) as is.")
|
|
29
|
+
return circuits
|
|
30
|
+
|
|
31
|
+
is_list = isinstance(circuits, list)
|
|
32
|
+
if not is_list:
|
|
33
|
+
circuits = [circuits]
|
|
34
|
+
|
|
35
|
+
transpiled_circuits = []
|
|
36
|
+
for qc in circuits:
|
|
37
|
+
if not hasattr(qc, "_core_circuit") or not qc._core_circuit:
|
|
38
|
+
transpiled_circuits.append(qc)
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
# Call qsys transpiler on the internal core circuit
|
|
42
|
+
# qsys.transpile expects (core_circuit, backend)
|
|
43
|
+
# Note: In Phase 2, we passed use_transpiler=False because our Zena Backends
|
|
44
|
+
# didn't have .target. To make this work, we'll need to pass the wrapped qsys backend
|
|
45
|
+
# if available, or a default one.
|
|
46
|
+
|
|
47
|
+
# For now, let's just use the opt1q if no backend is provided
|
|
48
|
+
from qsys.transpiler.opt1q import optimize_1q
|
|
49
|
+
|
|
50
|
+
core_qc = qc._core_circuit
|
|
51
|
+
|
|
52
|
+
if backend:
|
|
53
|
+
# If backend is provided, we try to use the full qsys transpile
|
|
54
|
+
# We need to extract the underlying qsys backend from Zena backend if possible
|
|
55
|
+
# Or just pass the Zena backend if it has the required properties (target, etc.)
|
|
56
|
+
try:
|
|
57
|
+
# We expect qsys.transpile to handle cases where backend might be None or simplified
|
|
58
|
+
# but if it needs .target, we might need a fallback.
|
|
59
|
+
transpiled_core = qsys_transpile(core_qc, backend, **kwargs)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"Warning: full transpile failed ({e}). Falling back to 1q optimization.")
|
|
62
|
+
transpiled_core = optimize_1q(core_qc)
|
|
63
|
+
else:
|
|
64
|
+
# Default optimization if no backend
|
|
65
|
+
transpiled_core = optimize_1q(core_qc)
|
|
66
|
+
|
|
67
|
+
# Update the Zena circuit with the transpiled core
|
|
68
|
+
# In a real SDK, we'd probably return a NEW Zena circuit
|
|
69
|
+
qc._core_circuit = transpiled_core
|
|
70
|
+
transpiled_circuits.append(qc)
|
|
71
|
+
|
|
72
|
+
return transpiled_circuits[0] if not is_list else transpiled_circuits
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execute function.
|
|
3
|
+
"""
|
|
4
|
+
from .providers.aer import Aer
|
|
5
|
+
from .compiler import transpile
|
|
6
|
+
|
|
7
|
+
def execute(experiments, backend=None, shots=1024, **kwargs):
|
|
8
|
+
"""
|
|
9
|
+
Execute a list of circuits or a single circuit on a backend.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
experiments (QuantumCircuit or list): Circuit(s) to execute.
|
|
13
|
+
backend (Backend): The backend to execute on.
|
|
14
|
+
shots (int): Number of shots (default: 1024).
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Job: A job object containing the result.
|
|
18
|
+
"""
|
|
19
|
+
if backend is None:
|
|
20
|
+
# Default to statevector simulator if none provided
|
|
21
|
+
backend = Aer.get_backend("statevector_simulator")
|
|
22
|
+
|
|
23
|
+
# Transpile the circuits before execution
|
|
24
|
+
experiments = transpile(experiments, backend=backend)
|
|
25
|
+
|
|
26
|
+
if not isinstance(experiments, list):
|
|
27
|
+
experiments = [experiments]
|
|
28
|
+
|
|
29
|
+
# For now, we only support single circuit execution in this simplified wrapper
|
|
30
|
+
# In full implementation, we would loop and aggregate results
|
|
31
|
+
if len(experiments) > 1:
|
|
32
|
+
raise NotImplementedError("Batch execution not yet fully supported in this simplified wrapper.")
|
|
33
|
+
|
|
34
|
+
circuit = experiments[0]
|
|
35
|
+
return backend.run(circuit, shots=shots, **kwargs)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aer Provider for local simulation.
|
|
3
|
+
"""
|
|
4
|
+
from .backend import Backend
|
|
5
|
+
from .job import Job, Result
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
# Import the internal execution logic from qsys
|
|
9
|
+
try:
|
|
10
|
+
from qsys.runtime.execute import execute as qsys_execute
|
|
11
|
+
except ImportError:
|
|
12
|
+
qsys_execute = None
|
|
13
|
+
|
|
14
|
+
class StatevectorSimulator(Backend):
|
|
15
|
+
"""Local statevector simulator backend."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self):
|
|
19
|
+
return "statevector_simulator"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def target(self):
|
|
23
|
+
"""Return a default target for the simulator."""
|
|
24
|
+
try:
|
|
25
|
+
from qsys.backends.base import Target
|
|
26
|
+
# Simulator typically has all-to-all connectivity and standard basis
|
|
27
|
+
return Target(
|
|
28
|
+
n_qubits=32, # Theoretical limit for 8GB RAM approx
|
|
29
|
+
basis_gates={"x", "sx", "rz", "cx", "measure", "h", "y", "z", "s", "sdg", "t", "tdg", "rx", "ry", "swap"},
|
|
30
|
+
coupling_map=[] # Empty list often means all-to-all in these engines, or we can generate it
|
|
31
|
+
)
|
|
32
|
+
except ImportError:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
def run(self, circuit, shots=1024, **kwargs):
|
|
36
|
+
"""Run the circuit using qsys."""
|
|
37
|
+
if not qsys_execute:
|
|
38
|
+
raise ImportError("qsys.runtime.execute not available. Cannot run simulation.")
|
|
39
|
+
|
|
40
|
+
# Extract the core qsys circuit
|
|
41
|
+
if not hasattr(circuit, "_core_circuit") or not circuit._core_circuit:
|
|
42
|
+
raise ValueError("Circuit has no underlying qsys circuit.")
|
|
43
|
+
|
|
44
|
+
core_qc = circuit._core_circuit
|
|
45
|
+
|
|
46
|
+
# Execute using qsys
|
|
47
|
+
# qsys.execute returns a dict with 'counts', 'statevector', etc.
|
|
48
|
+
# We disable the internal qsys transpiler for now to avoid 'backend.target' errors
|
|
49
|
+
# since we are not yet passing a formal qsys.Backend object.
|
|
50
|
+
res_data = qsys_execute(core_qc, shots=shots, use_transpiler=False)
|
|
51
|
+
|
|
52
|
+
# Wrap in Result and Job
|
|
53
|
+
result = Result(res_data)
|
|
54
|
+
job_id = str(uuid.uuid4())
|
|
55
|
+
return Job(self, job_id, result)
|
|
56
|
+
|
|
57
|
+
class AerProvider:
|
|
58
|
+
"""Provider for Aer backends."""
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self._backends = {
|
|
62
|
+
"statevector_simulator": StatevectorSimulator()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def get_backend(self, name="statevector_simulator"):
|
|
66
|
+
if name not in self._backends:
|
|
67
|
+
raise ValueError(f"Backend '{name}' not found.")
|
|
68
|
+
return self._backends[name]
|
|
69
|
+
|
|
70
|
+
# Global instance
|
|
71
|
+
Aer = AerProvider()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Backend Class.
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
class Backend(ABC):
|
|
7
|
+
"""Abstract base class for backends."""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def run(self, circuit, shots=1024, **kwargs):
|
|
11
|
+
"""Run a circuit on the backend."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def name(self):
|
|
17
|
+
"""Backend name."""
|
|
18
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Job Class to handle execution status and results.
|
|
3
|
+
"""
|
|
4
|
+
class Job:
|
|
5
|
+
def __init__(self, backend, job_id, result=None):
|
|
6
|
+
self._backend = backend
|
|
7
|
+
self._job_id = job_id
|
|
8
|
+
self._result = result
|
|
9
|
+
|
|
10
|
+
def result(self):
|
|
11
|
+
"""Return the result of the job."""
|
|
12
|
+
return self._result
|
|
13
|
+
|
|
14
|
+
class Result:
|
|
15
|
+
def __init__(self, backend_result_dict):
|
|
16
|
+
self._data = backend_result_dict
|
|
17
|
+
|
|
18
|
+
def get_counts(self):
|
|
19
|
+
"""Return counts from the result."""
|
|
20
|
+
return self._data.get("counts", {})
|
|
21
|
+
|
|
22
|
+
def get_statevector(self):
|
|
23
|
+
"""Return statevector from the result."""
|
|
24
|
+
return self._data.get("statevector", [])
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zena-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A high-level Python SDK for the Zena Quantum Simulator, inspired by Qiskit."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Zena Team", email = "info@zena.quantum" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: Apache Software License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"numpy>=1.20",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["zena"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
def plot_histogram(data, title=None):
|
|
2
|
+
"""
|
|
3
|
+
Plot a histogram of counts.
|
|
4
|
+
|
|
5
|
+
Args:
|
|
6
|
+
data (dict): Counts dictionary (e.g., {'00': 500, '11': 500})
|
|
7
|
+
title (str): Optional title for the plot.
|
|
8
|
+
"""
|
|
9
|
+
if title:
|
|
10
|
+
print(f"\n--- {title} ---")
|
|
11
|
+
else:
|
|
12
|
+
print("\n--- Histogram ---")
|
|
13
|
+
|
|
14
|
+
if not data:
|
|
15
|
+
print("Empty data.")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
max_val = max(data.values())
|
|
19
|
+
total = sum(data.values())
|
|
20
|
+
|
|
21
|
+
# Sort keys for consistent display
|
|
22
|
+
for key in sorted(data.keys()):
|
|
23
|
+
val = data[key]
|
|
24
|
+
percentage = (val / total) * 100
|
|
25
|
+
bar_length = int((val / max_val) * 20)
|
|
26
|
+
bar = "█" * bar_length
|
|
27
|
+
print(f"{key:5s} | {bar:<20} | {val:4d} ({percentage:>5.1f}%)")
|
|
28
|
+
print("-" * 45)
|