zena-sdk 0.1.4__cp38-abi3-win_amd64.whl
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.
- qsys/__init__.py +31 -0
- qsys/__pycache__/__init__.cpython-313.pyc +0 -0
- qsys/backends/base.py +39 -0
- qsys/backends/local5q.py +8 -0
- qsys/circuit/__init__.py +20 -0
- qsys/circuit/__pycache__/__init__.cpython-313.pyc +0 -0
- qsys/circuit/__pycache__/quantum_circuit.cpython-313.pyc +0 -0
- qsys/circuit/quantum_circuit.py +103 -0
- qsys/cli/__init__.py +2 -0
- qsys/cli/io_cli.py +45 -0
- qsys/errors/__init__.py +32 -0
- qsys/errors/__pycache__/__init__.cpython-313.pyc +0 -0
- qsys/io/__init__.py +21 -0
- qsys/io/json_io.py +84 -0
- qsys/io/text_io.py +58 -0
- qsys/ir/__init__.py +1 -0
- qsys/ir/__pycache__/__init__.cpython-313.pyc +0 -0
- qsys/ir/__pycache__/types.cpython-313.pyc +0 -0
- qsys/ir/from_payload.py +27 -0
- qsys/ir/types.py +9 -0
- qsys/logging.py +14 -0
- qsys/runtime/__init__.py +4 -0
- qsys/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- qsys/runtime/__pycache__/execute.cpython-313.pyc +0 -0
- qsys/runtime/execute.py +155 -0
- qsys/target.py +44 -0
- qsys/targets.py +63 -0
- qsys/transpiler/__init__.py +6 -0
- qsys/transpiler/basis.py +39 -0
- qsys/transpiler/opt1q.py +101 -0
- qsys/transpiler/passes.py +57 -0
- qsys/transpiler/routing.py +136 -0
- qsys/transpiler/validate.py +132 -0
- qsys/viz/__init__.py +1 -0
- qsys/viz/text_drawer.py +89 -0
- simulator_statevector/__init__.py +5 -0
- simulator_statevector/__pycache__/__init__.cpython-313.pyc +0 -0
- simulator_statevector/simulator_statevector.pyd +0 -0
- zena/__init__.py +7 -0
- zena/__pycache__/__init__.cpython-312.pyc +0 -0
- zena/__pycache__/__init__.cpython-313.pyc +0 -0
- zena/__pycache__/execute.cpython-312.pyc +0 -0
- zena/__pycache__/execute.cpython-313.pyc +0 -0
- zena/circuit/__init__.py +2 -0
- zena/circuit/__pycache__/__init__.cpython-312.pyc +0 -0
- zena/circuit/__pycache__/__init__.cpython-313.pyc +0 -0
- zena/circuit/__pycache__/quantum_circuit.cpython-312.pyc +0 -0
- zena/circuit/__pycache__/quantum_circuit.cpython-313.pyc +0 -0
- zena/circuit/__pycache__/register.cpython-312.pyc +0 -0
- zena/circuit/__pycache__/register.cpython-313.pyc +0 -0
- zena/circuit/quantum_circuit.py +218 -0
- zena/circuit/register.py +28 -0
- zena/compiler/__init__.py +72 -0
- zena/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
- zena/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
- zena/dist/zena_sdk-0.1.0-py3-none-any.whl +0 -0
- zena/dist/zena_sdk-0.1.0.tar.gz +0 -0
- zena/execute.py +35 -0
- zena/providers/__init__.py +5 -0
- zena/providers/__pycache__/__init__.cpython-312.pyc +0 -0
- zena/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- zena/providers/__pycache__/aer.cpython-312.pyc +0 -0
- zena/providers/__pycache__/aer.cpython-313.pyc +0 -0
- zena/providers/__pycache__/backend.cpython-312.pyc +0 -0
- zena/providers/__pycache__/backend.cpython-313.pyc +0 -0
- zena/providers/__pycache__/job.cpython-312.pyc +0 -0
- zena/providers/__pycache__/job.cpython-313.pyc +0 -0
- zena/providers/aer.py +71 -0
- zena/providers/backend.py +18 -0
- zena/providers/job.py +24 -0
- zena/visualization/__init__.py +28 -0
- zena/visualization/__pycache__/__init__.cpython-312.pyc +0 -0
- zena/visualization/__pycache__/__init__.cpython-313.pyc +0 -0
- zena_sdk-0.1.4.dist-info/METADATA +70 -0
- zena_sdk-0.1.4.dist-info/RECORD +76 -0
- zena_sdk-0.1.4.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ..backends.base import Backend
|
|
6
|
+
from ..errors import ValidationError
|
|
7
|
+
from ..ir.types import Instruction
|
|
8
|
+
|
|
9
|
+
# Spec for supported ops in our current IR
|
|
10
|
+
# name -> (num_qubits, num_params, allows_clbits)
|
|
11
|
+
GATE_SPECS = {
|
|
12
|
+
"x": (1, 0, False),
|
|
13
|
+
"sx": (1, 0, False),
|
|
14
|
+
"rz": (1, 1, False),
|
|
15
|
+
"cx": (2, 0, False),
|
|
16
|
+
"measure": (1, 0, True), # (q, c) pair
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ValidationReport:
|
|
22
|
+
ok: bool
|
|
23
|
+
needs_routing: (
|
|
24
|
+
bool # True if there exists a CX that is not directly allowed by coupling map
|
|
25
|
+
)
|
|
26
|
+
messages: list[str]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _check_gate_shape(inst: Instruction) -> None:
|
|
30
|
+
name = inst.name
|
|
31
|
+
if name not in GATE_SPECS:
|
|
32
|
+
raise ValidationError(f"Unknown operation: {name}")
|
|
33
|
+
|
|
34
|
+
q_needed, p_needed, allow_c = GATE_SPECS[name]
|
|
35
|
+
|
|
36
|
+
if len(inst.qubits) != q_needed:
|
|
37
|
+
raise ValidationError(
|
|
38
|
+
f"Gate '{name}' expects {q_needed} qubit(s), got {len(inst.qubits)}: {inst.qubits}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if len(inst.params) != p_needed:
|
|
42
|
+
raise ValidationError(
|
|
43
|
+
f"Gate '{name}' expects {p_needed} parameter(s), got {len(inst.params)}: {inst.params}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if inst.clbits is not None and not allow_c:
|
|
47
|
+
raise ValidationError(
|
|
48
|
+
f"Gate '{name}' does not take classical bits (got {inst.clbits})"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if inst.clbits is None and allow_c:
|
|
52
|
+
# measure must provide clbits (we encode them in Instruction.clbits)
|
|
53
|
+
raise ValidationError("Measure must specify destination classical bit(s)")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _check_index_bounds(inst: Instruction, n_qubits: int, n_clbits: int) -> None:
|
|
57
|
+
# Qubit indices must be within 0..n_qubits-1 and distinct for multi-qubit ops
|
|
58
|
+
for q in inst.qubits:
|
|
59
|
+
if q < 0 or q >= n_qubits:
|
|
60
|
+
raise ValidationError(f"Qubit index out of range in op {inst}: q={q}")
|
|
61
|
+
|
|
62
|
+
if len(inst.qubits) != len(set(inst.qubits)):
|
|
63
|
+
raise ValidationError(f"Repeated qubit index in op {inst}")
|
|
64
|
+
|
|
65
|
+
# Classical indices (if any) must be within 0..n_clbits-1 and distinct
|
|
66
|
+
if inst.clbits is not None:
|
|
67
|
+
for c in inst.clbits:
|
|
68
|
+
if c < 0 or c >= n_clbits:
|
|
69
|
+
raise ValidationError(
|
|
70
|
+
f"Classical bit index out of range in op {inst}: c={c}"
|
|
71
|
+
)
|
|
72
|
+
if len(inst.clbits) != len(set(inst.clbits)):
|
|
73
|
+
raise ValidationError(f"Repeated classical bit index in op {inst}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _cx_needs_routing(
|
|
77
|
+
control: int, target: int, coupling_map: list[tuple[int, int]]
|
|
78
|
+
) -> bool:
|
|
79
|
+
"""Return True if (control,target) is not directly supported by coupling_map (undirected)."""
|
|
80
|
+
if not coupling_map:
|
|
81
|
+
# If backend provides no map, assume fully connected (dev convenience)
|
|
82
|
+
return False
|
|
83
|
+
return (control, target) not in coupling_map and (
|
|
84
|
+
target,
|
|
85
|
+
control,
|
|
86
|
+
) not in coupling_map
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def validate(circuit, backend: Backend) -> ValidationReport:
|
|
90
|
+
"""
|
|
91
|
+
Validate a circuit against basic rules and the backend's size/basis.
|
|
92
|
+
|
|
93
|
+
- Checks gate names, arity, parameter counts.
|
|
94
|
+
- Checks qubit/clbit index ranges and duplicates.
|
|
95
|
+
- Checks that every op name is in backend.target.basis_gates.
|
|
96
|
+
- Does NOT fail for CX connectivity; instead marks needs_routing=True when a CX
|
|
97
|
+
doesn't match the coupling map (routing pass will handle it in 5.3).
|
|
98
|
+
"""
|
|
99
|
+
msgs: list[str] = []
|
|
100
|
+
n_qubits = circuit.n_qubits
|
|
101
|
+
n_clbits = getattr(circuit, "n_clbits", 0)
|
|
102
|
+
tgt = backend.target
|
|
103
|
+
|
|
104
|
+
if n_qubits > tgt.n_qubits:
|
|
105
|
+
raise ValidationError(
|
|
106
|
+
f"Circuit uses {n_qubits} qubits but backend supports only {tgt.n_qubits}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
needs_routing = False
|
|
110
|
+
basis = set(tgt.basis_gates)
|
|
111
|
+
|
|
112
|
+
for inst in circuit.instructions:
|
|
113
|
+
# Name is supported by our IR and by backend basis
|
|
114
|
+
_check_gate_shape(inst)
|
|
115
|
+
if inst.name not in basis:
|
|
116
|
+
raise ValidationError(
|
|
117
|
+
f"Gate '{inst.name}' not in backend basis_gates: {sorted(basis)}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Index ranges / duplicates
|
|
121
|
+
_check_index_bounds(inst, n_qubits, n_clbits)
|
|
122
|
+
|
|
123
|
+
# Connectivity hint (don’t fail; mark for routing)
|
|
124
|
+
if inst.name == "cx":
|
|
125
|
+
c, t = inst.qubits
|
|
126
|
+
if _cx_needs_routing(c, t, tgt.coupling_map):
|
|
127
|
+
needs_routing = True
|
|
128
|
+
|
|
129
|
+
if needs_routing:
|
|
130
|
+
msgs.append("Routing required: at least one CX not on a coupling edge.")
|
|
131
|
+
|
|
132
|
+
return ValidationReport(ok=True, needs_routing=needs_routing, messages=msgs)
|
qsys/viz/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# viz package
|
qsys/viz/text_drawer.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import List
|
|
3
|
+
from ..ir.types import Instruction
|
|
4
|
+
|
|
5
|
+
# Conventions
|
|
6
|
+
# - Qubits are rendered top (q0) → bottom (q{n-1}) for readability.
|
|
7
|
+
# - Columns advance in time, one column per instruction (simple MVP).
|
|
8
|
+
# - Supported ops: x, sx, rz(theta), cx, measure.
|
|
9
|
+
# - For measure, we annotate the destination classical bit as M[c].
|
|
10
|
+
# - Little-endian execution still applies (this is only visualization).
|
|
11
|
+
|
|
12
|
+
CELL_W = 5 # width per column (room for labels like "rz(π/2)" short form later)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _empty_canvas(n_qubits: int, n_cols: int) -> List[List[str]]:
|
|
16
|
+
rows = []
|
|
17
|
+
for _ in range(n_qubits):
|
|
18
|
+
row = ["─" * CELL_W for _ in range(n_cols)]
|
|
19
|
+
rows.append(row)
|
|
20
|
+
return rows
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _place_label(cell: str, label: str) -> str:
|
|
24
|
+
# Center label within CELL_W using monospaced assumptions
|
|
25
|
+
pad = max(0, CELL_W - len(label))
|
|
26
|
+
left = pad // 2
|
|
27
|
+
right = pad - left
|
|
28
|
+
return " " * left + label + " " * right
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _rz_label(theta: float) -> str:
|
|
32
|
+
# Keep it compact; 3 significant digits
|
|
33
|
+
return f"rz({theta:.3g})"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def draw_ascii(n_qubits: int, instructions: List[Instruction]) -> str:
|
|
37
|
+
n_cols = max(1, len(instructions))
|
|
38
|
+
canvas = _empty_canvas(n_qubits, n_cols)
|
|
39
|
+
|
|
40
|
+
# Helper to map logical qubit index to row (q0 at top)
|
|
41
|
+
def row_of(q: int) -> int:
|
|
42
|
+
return q # q0 at row 0, q1 at row 1, ...
|
|
43
|
+
|
|
44
|
+
for col, inst in enumerate(instructions):
|
|
45
|
+
name, qs, ps, cs = inst.name, inst.qubits, inst.params, inst.clbits
|
|
46
|
+
|
|
47
|
+
if name in ("x", "sx", "rz", "measure"):
|
|
48
|
+
q = qs[0]
|
|
49
|
+
r = row_of(q)
|
|
50
|
+
if name == "x":
|
|
51
|
+
canvas[r][col] = _place_label(canvas[r][col], "X")
|
|
52
|
+
elif name == "sx":
|
|
53
|
+
canvas[r][col] = _place_label(canvas[r][col], "SX")
|
|
54
|
+
elif name == "rz":
|
|
55
|
+
canvas[r][col] = _place_label(canvas[r][col], _rz_label(float(ps[0])))
|
|
56
|
+
elif name == "measure":
|
|
57
|
+
lbl = "M" if cs is None or len(cs) == 0 else f"M[{cs[0]}]"
|
|
58
|
+
canvas[r][col] = _place_label(canvas[r][col], lbl)
|
|
59
|
+
# leave other rows in this column as wires
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
if name == "cx":
|
|
63
|
+
c, t = qs
|
|
64
|
+
rc = row_of(c)
|
|
65
|
+
rt = row_of(t)
|
|
66
|
+
top, bot = sorted((rc, rt))
|
|
67
|
+
# Place control and target symbols
|
|
68
|
+
canvas[rc][col] = _place_label(canvas[rc][col], "●") # control
|
|
69
|
+
canvas[rt][col] = _place_label(
|
|
70
|
+
canvas[rt][col], "X"
|
|
71
|
+
) # target (CNOT symbolized as X)
|
|
72
|
+
# Draw vertical wire between them in this column
|
|
73
|
+
for r in range(top + 1, bot):
|
|
74
|
+
canvas[r][col] = _place_label(canvas[r][col], "│")
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# Unknown op: show as name
|
|
78
|
+
for q in qs:
|
|
79
|
+
r = row_of(q)
|
|
80
|
+
canvas[r][col] = _place_label(canvas[r][col], name.upper())
|
|
81
|
+
|
|
82
|
+
# Build header with qubit labels
|
|
83
|
+
lines: List[str] = []
|
|
84
|
+
for q in range(n_qubits):
|
|
85
|
+
label = f"q{q}: "
|
|
86
|
+
line = label + "".join(canvas[q])
|
|
87
|
+
lines.append(line)
|
|
88
|
+
|
|
89
|
+
return "\n".join(lines)
|
|
Binary file
|
|
Binary file
|
zena/__init__.py
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
zena/circuit/__init__.py
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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.")
|
zena/circuit/register.py
ADDED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
zena/execute.py
ADDED
|
@@ -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)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|