qledger 0.1.0__py3-none-any.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.
- qledger/__init__.py +32 -0
- qledger/adapters/__init__.py +20 -0
- qledger/adapters/base.py +174 -0
- qledger/adapters/cirq_adapter.py +368 -0
- qledger/adapters/pennylane_adapter.py +306 -0
- qledger/adapters/qiskit_adapter.py +407 -0
- qledger/adapters/registry.py +104 -0
- qledger/benchmarks/__init__.py +22 -0
- qledger/benchmarks/algorithmic.py +198 -0
- qledger/benchmarks/clops.py +143 -0
- qledger/benchmarks/quantum_volume.py +297 -0
- qledger/benchmarks/suite.py +217 -0
- qledger/cli/__init__.py +1 -0
- qledger/cli/main.py +289 -0
- qledger/core/__init__.py +5 -0
- qledger/core/engine.py +455 -0
- qledger/noise/__init__.py +5 -0
- qledger/noise/profiler.py +186 -0
- qledger/schema/__init__.py +26 -0
- qledger/schema/circuit.py +266 -0
- qledger/schema/gates.py +381 -0
- qledger/schema/noise.py +256 -0
- qledger/schema/result.py +159 -0
- qledger/storage/__init__.py +5 -0
- qledger/storage/database.py +544 -0
- qledger/versioning/__init__.py +5 -0
- qledger/versioning/tracker.py +260 -0
- qledger-0.1.0.dist-info/METADATA +293 -0
- qledger-0.1.0.dist-info/RECORD +32 -0
- qledger-0.1.0.dist-info/WHEEL +4 -0
- qledger-0.1.0.dist-info/entry_points.txt +2 -0
- qledger-0.1.0.dist-info/licenses/LICENSE +21 -0
qledger/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""QLedger — The Universal Quantum Experiment Lifecycle Platform.
|
|
2
|
+
|
|
3
|
+
Execute circuits across Qiskit, Cirq, and PennyLane. Track noise.
|
|
4
|
+
Benchmark hardware. Version circuits. Persist everything.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from qledger.core.engine import QLedger
|
|
8
|
+
from qledger.schema.circuit import Instruction, Measurement, UniversalCircuit
|
|
9
|
+
from qledger.schema.noise import (
|
|
10
|
+
GateFidelity,
|
|
11
|
+
NoiseSnapshot,
|
|
12
|
+
QubitProperties,
|
|
13
|
+
ReadoutError,
|
|
14
|
+
)
|
|
15
|
+
from qledger.schema.result import ExecutionResult
|
|
16
|
+
from qledger.storage.database import DatabaseError, QLedgerStore
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"DatabaseError",
|
|
20
|
+
"ExecutionResult",
|
|
21
|
+
"GateFidelity",
|
|
22
|
+
"Instruction",
|
|
23
|
+
"Measurement",
|
|
24
|
+
"NoiseSnapshot",
|
|
25
|
+
"QLedger",
|
|
26
|
+
"QLedgerStore",
|
|
27
|
+
"QubitProperties",
|
|
28
|
+
"ReadoutError",
|
|
29
|
+
"UniversalCircuit",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Cross-framework adapters for quantum circuit interoperability.
|
|
2
|
+
|
|
3
|
+
Each adapter implements the ``BaseAdapter`` interface, providing:
|
|
4
|
+
* Circuit conversion (native ↔ ``UniversalCircuit``)
|
|
5
|
+
* Circuit execution on the framework's backends
|
|
6
|
+
* Noise profile extraction from hardware calibration data
|
|
7
|
+
|
|
8
|
+
Adapters are loaded lazily — importing ``qledger`` does not require any
|
|
9
|
+
quantum framework to be installed. Only when you instantiate an adapter
|
|
10
|
+
does it import the corresponding library.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .base import AdapterError, BaseAdapter
|
|
14
|
+
from .registry import AdapterRegistry
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AdapterError",
|
|
18
|
+
"AdapterRegistry",
|
|
19
|
+
"BaseAdapter",
|
|
20
|
+
]
|
qledger/adapters/base.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Abstract base class for all framework adapters.
|
|
2
|
+
|
|
3
|
+
Every adapter must implement this interface. The contract is deliberately
|
|
4
|
+
narrow so that adding support for a new framework (e.g. Amazon Braket,
|
|
5
|
+
Azure Quantum) requires minimal boilerplate.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from qledger.schema.circuit import UniversalCircuit
|
|
14
|
+
from qledger.schema.noise import NoiseSnapshot
|
|
15
|
+
from qledger.schema.result import ExecutionResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AdapterError(Exception):
|
|
19
|
+
"""Raised when an adapter operation fails."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseAdapter(ABC):
|
|
23
|
+
"""Interface that every framework adapter must implement.
|
|
24
|
+
|
|
25
|
+
Subclasses must define
|
|
26
|
+
----------------------
|
|
27
|
+
* ``framework_name`` — identifier used in gate alias lookups and storage.
|
|
28
|
+
* ``from_native`` — convert a native circuit to ``UniversalCircuit``.
|
|
29
|
+
* ``to_native`` — convert a ``UniversalCircuit`` back to a native circuit.
|
|
30
|
+
* ``execute`` — run a circuit on a backend and return an ``ExecutionResult``.
|
|
31
|
+
* ``get_noise_snapshot`` — extract calibration data from a backend.
|
|
32
|
+
* ``list_backends`` — enumerate available backends.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def framework_name(self) -> str:
|
|
38
|
+
"""Short lowercase identifier (e.g. ``"qiskit"``, ``"cirq"``)."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def framework_version(self) -> str:
|
|
44
|
+
"""Installed version string of the framework."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
# Circuit conversion
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def from_native(self, circuit: Any, name: str = "") -> UniversalCircuit:
|
|
53
|
+
"""Convert a native circuit object to ``UniversalCircuit``.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
circuit : Any
|
|
58
|
+
Framework-specific circuit object.
|
|
59
|
+
name : str
|
|
60
|
+
Optional label for the circuit.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
UniversalCircuit
|
|
65
|
+
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
AdapterError
|
|
69
|
+
If the circuit contains gates that cannot be mapped.
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def to_native(self, circuit: UniversalCircuit) -> Any:
|
|
75
|
+
"""Convert a ``UniversalCircuit`` to the framework's native type.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
circuit : UniversalCircuit
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
Any
|
|
84
|
+
Native circuit object.
|
|
85
|
+
|
|
86
|
+
Raises
|
|
87
|
+
------
|
|
88
|
+
AdapterError
|
|
89
|
+
If a gate in the universal circuit has no mapping in this framework.
|
|
90
|
+
"""
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
# Execution
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def execute(
|
|
99
|
+
self,
|
|
100
|
+
circuit: UniversalCircuit,
|
|
101
|
+
backend: Any | None = None,
|
|
102
|
+
*,
|
|
103
|
+
shots: int = 1024,
|
|
104
|
+
seed_simulator: int | None = None,
|
|
105
|
+
optimization_level: int | None = None,
|
|
106
|
+
transpiler_seed: int | None = None,
|
|
107
|
+
save_statevector: bool = False,
|
|
108
|
+
save_memory: bool = False,
|
|
109
|
+
extra_run_options: dict[str, Any] | None = None,
|
|
110
|
+
) -> ExecutionResult:
|
|
111
|
+
"""Execute a universal circuit and return a structured result.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
circuit : UniversalCircuit
|
|
116
|
+
Circuit in the universal IR.
|
|
117
|
+
backend : Any, optional
|
|
118
|
+
Backend to execute on. If None, the adapter should provide
|
|
119
|
+
a sensible default (e.g. local simulator).
|
|
120
|
+
shots : int
|
|
121
|
+
Number of measurement shots.
|
|
122
|
+
seed_simulator : int, optional
|
|
123
|
+
Seed for the simulator's random number generator.
|
|
124
|
+
optimization_level : int, optional
|
|
125
|
+
Transpiler optimisation level.
|
|
126
|
+
transpiler_seed : int, optional
|
|
127
|
+
Seed for transpiler stochastic passes.
|
|
128
|
+
save_statevector : bool
|
|
129
|
+
Request statevector output (simulator-only).
|
|
130
|
+
save_memory : bool
|
|
131
|
+
Request per-shot measurement memory.
|
|
132
|
+
extra_run_options : dict, optional
|
|
133
|
+
Additional framework-specific options passed to the runner.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
ExecutionResult
|
|
138
|
+
"""
|
|
139
|
+
...
|
|
140
|
+
|
|
141
|
+
# ------------------------------------------------------------------
|
|
142
|
+
# Noise profiling
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def get_noise_snapshot(self, backend: Any) -> NoiseSnapshot:
|
|
147
|
+
"""Extract current calibration / noise data from a backend.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
backend : Any
|
|
152
|
+
A backend instance (real hardware or simulator with a noise model).
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
NoiseSnapshot
|
|
157
|
+
"""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
# ------------------------------------------------------------------
|
|
161
|
+
# Backend discovery
|
|
162
|
+
# ------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
@abstractmethod
|
|
165
|
+
def list_backends(self, **filters: Any) -> list[dict[str, Any]]:
|
|
166
|
+
"""Enumerate available backends.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
list[dict[str, Any]]
|
|
171
|
+
Each dict contains at least ``{"name": str, "num_qubits": int,
|
|
172
|
+
"simulator": bool}``.
|
|
173
|
+
"""
|
|
174
|
+
...
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Cirq adapter — converts between Google Cirq circuits and UniversalCircuit.
|
|
2
|
+
|
|
3
|
+
Supports cirq-core >= 1.3.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from qledger.schema.circuit import Instruction, Measurement, UniversalCircuit
|
|
14
|
+
from qledger.schema.gates import StandardGates
|
|
15
|
+
from qledger.schema.noise import NoiseSnapshot
|
|
16
|
+
from qledger.schema.result import ExecutionResult
|
|
17
|
+
|
|
18
|
+
from .base import AdapterError, BaseAdapter
|
|
19
|
+
from .registry import AdapterRegistry
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Cirq gate class name → canonical name (None = handled via decomposition)
|
|
24
|
+
_CIRQ_TO_CANONICAL: dict[str, str | None] = {
|
|
25
|
+
"HPowGate": "h",
|
|
26
|
+
"XPowGate": "x",
|
|
27
|
+
"YPowGate": "y",
|
|
28
|
+
"ZPowGate": "z",
|
|
29
|
+
"CXPowGate": "cx",
|
|
30
|
+
"CNotPowGate": "cx",
|
|
31
|
+
"CZPowGate": "cz",
|
|
32
|
+
"SwapPowGate": "swap",
|
|
33
|
+
"ISwapPowGate": "iswap",
|
|
34
|
+
"CCXPowGate": "ccx",
|
|
35
|
+
"CCZPowGate": "ccx",
|
|
36
|
+
"MeasurementGate": "measure",
|
|
37
|
+
"_InverseCompositeGate": None, # handled via decomposition
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@AdapterRegistry.register
|
|
42
|
+
class CirqAdapter(BaseAdapter):
|
|
43
|
+
"""Adapter for Google Cirq."""
|
|
44
|
+
|
|
45
|
+
_FRAMEWORK_NAME = "cirq"
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
import cirq
|
|
49
|
+
|
|
50
|
+
self._cirq = cirq
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def framework_name(self) -> str:
|
|
54
|
+
return "cirq"
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def framework_version(self) -> str:
|
|
58
|
+
return str(self._cirq.__version__)
|
|
59
|
+
|
|
60
|
+
# ------------------------------------------------------------------
|
|
61
|
+
# Cirq → UniversalCircuit
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def from_native(self, circuit: Any, name: str = "") -> UniversalCircuit:
|
|
65
|
+
cirq = self._cirq
|
|
66
|
+
|
|
67
|
+
if not isinstance(circuit, cirq.Circuit):
|
|
68
|
+
raise AdapterError(f"Expected cirq.Circuit, got {type(circuit).__name__}")
|
|
69
|
+
|
|
70
|
+
# Collect all qubits and assign contiguous indices
|
|
71
|
+
all_qubits = sorted(circuit.all_qubits())
|
|
72
|
+
qubit_map = {q: i for i, q in enumerate(all_qubits)}
|
|
73
|
+
|
|
74
|
+
instructions: list[Instruction] = []
|
|
75
|
+
measurements: list[Measurement] = []
|
|
76
|
+
clbit_counter = 0
|
|
77
|
+
|
|
78
|
+
for moment in circuit:
|
|
79
|
+
for op in moment:
|
|
80
|
+
gate = op.gate
|
|
81
|
+
qubit_indices = tuple(qubit_map[q] for q in op.qubits)
|
|
82
|
+
|
|
83
|
+
# Handle measurements
|
|
84
|
+
if isinstance(gate, cirq.MeasurementGate):
|
|
85
|
+
for qi in qubit_indices:
|
|
86
|
+
measurements.append(Measurement(qubit=qi, clbit=clbit_counter))
|
|
87
|
+
clbit_counter += 1
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# Resolve gate name
|
|
91
|
+
canonical = self._resolve_cirq_gate(gate)
|
|
92
|
+
params = self._extract_cirq_params(gate)
|
|
93
|
+
|
|
94
|
+
instructions.append(Instruction(
|
|
95
|
+
gate=canonical,
|
|
96
|
+
qubits=qubit_indices,
|
|
97
|
+
params=params,
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
num_qubits = len(all_qubits)
|
|
101
|
+
num_clbits = clbit_counter if clbit_counter > 0 else num_qubits
|
|
102
|
+
|
|
103
|
+
return UniversalCircuit(
|
|
104
|
+
num_qubits=num_qubits,
|
|
105
|
+
num_clbits=num_clbits,
|
|
106
|
+
instructions=instructions,
|
|
107
|
+
measurements=measurements,
|
|
108
|
+
name=name,
|
|
109
|
+
metadata={"source_framework": "cirq", "cirq_version": self.framework_version},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def _resolve_cirq_gate(self, gate: Any) -> str:
|
|
113
|
+
"""Map a Cirq gate to its canonical name."""
|
|
114
|
+
gate_type = type(gate).__name__
|
|
115
|
+
|
|
116
|
+
# Check our mapping table first
|
|
117
|
+
canonical = _CIRQ_TO_CANONICAL.get(gate_type)
|
|
118
|
+
if canonical is not None:
|
|
119
|
+
# For power gates, check if the exponent is 1.0 (standard gate)
|
|
120
|
+
if hasattr(gate, "exponent"):
|
|
121
|
+
exp = gate.exponent
|
|
122
|
+
if gate_type == "HPowGate" and exp == 1.0:
|
|
123
|
+
return "h"
|
|
124
|
+
elif gate_type == "XPowGate":
|
|
125
|
+
if exp == 1.0:
|
|
126
|
+
return "x"
|
|
127
|
+
elif exp == 0.5:
|
|
128
|
+
return "sx"
|
|
129
|
+
else:
|
|
130
|
+
return "rx" # treat as rotation
|
|
131
|
+
elif gate_type == "YPowGate":
|
|
132
|
+
return "y" if exp == 1.0 else "ry"
|
|
133
|
+
elif gate_type == "ZPowGate":
|
|
134
|
+
if exp == 1.0: # noqa: SIM116
|
|
135
|
+
return "z"
|
|
136
|
+
elif exp == 0.5:
|
|
137
|
+
return "s"
|
|
138
|
+
elif exp == 0.25:
|
|
139
|
+
return "t"
|
|
140
|
+
else:
|
|
141
|
+
return "rz"
|
|
142
|
+
elif gate_type == "CXPowGate" and exp == 1.0:
|
|
143
|
+
return "cx"
|
|
144
|
+
elif gate_type == "CZPowGate" and exp == 1.0:
|
|
145
|
+
return "cz"
|
|
146
|
+
elif gate_type == "SwapPowGate" and exp == 1.0:
|
|
147
|
+
return "swap"
|
|
148
|
+
return canonical
|
|
149
|
+
|
|
150
|
+
# Try string matching
|
|
151
|
+
gate_str = str(gate).upper()
|
|
152
|
+
resolved = StandardGates.resolve(gate_str)
|
|
153
|
+
if resolved is not None:
|
|
154
|
+
return resolved.canonical_name
|
|
155
|
+
|
|
156
|
+
# Fallback: use the type name in lowercase
|
|
157
|
+
logger.warning("Unknown Cirq gate %s — storing as-is.", gate_type)
|
|
158
|
+
return gate_type.lower()
|
|
159
|
+
|
|
160
|
+
def _extract_cirq_params(self, gate: Any) -> tuple[float, ...]:
|
|
161
|
+
"""Extract continuous parameters from a Cirq gate."""
|
|
162
|
+
import math
|
|
163
|
+
|
|
164
|
+
if hasattr(gate, "exponent"):
|
|
165
|
+
exp = gate.exponent
|
|
166
|
+
if isinstance(exp, (int, float)):
|
|
167
|
+
# Cirq stores rotations as exponents of π
|
|
168
|
+
# RX(θ) in Qiskit = X^(θ/π) in Cirq
|
|
169
|
+
# Only return params for parametric gates
|
|
170
|
+
gate_type = type(gate).__name__
|
|
171
|
+
if (
|
|
172
|
+
gate_type in ("HPowGate", "CXPowGate", "CZPowGate", "SwapPowGate")
|
|
173
|
+
and exp == 1.0
|
|
174
|
+
):
|
|
175
|
+
return () # standard gate, no params
|
|
176
|
+
if gate_type in ("XPowGate", "YPowGate", "ZPowGate"):
|
|
177
|
+
if exp in (0.0, 0.25, 0.5, 1.0):
|
|
178
|
+
return () # known fixed gates
|
|
179
|
+
return (float(exp) * math.pi,)
|
|
180
|
+
if "Pow" in gate_type:
|
|
181
|
+
return (float(exp) * math.pi,)
|
|
182
|
+
if hasattr(gate, "rads"):
|
|
183
|
+
return (float(gate.rads),)
|
|
184
|
+
return ()
|
|
185
|
+
|
|
186
|
+
# ------------------------------------------------------------------
|
|
187
|
+
# UniversalCircuit → Cirq
|
|
188
|
+
# ------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def to_native(self, circuit: UniversalCircuit) -> Any:
|
|
191
|
+
cirq = self._cirq
|
|
192
|
+
import math
|
|
193
|
+
|
|
194
|
+
qubits = cirq.LineQubit.range(circuit.num_qubits)
|
|
195
|
+
|
|
196
|
+
ops: list[Any] = []
|
|
197
|
+
|
|
198
|
+
gate_map: dict[str, Any] = {
|
|
199
|
+
"id": cirq.I,
|
|
200
|
+
"h": cirq.H,
|
|
201
|
+
"x": cirq.X,
|
|
202
|
+
"y": cirq.Y,
|
|
203
|
+
"z": cirq.Z,
|
|
204
|
+
"s": cirq.S,
|
|
205
|
+
"t": cirq.T,
|
|
206
|
+
"cx": cirq.CNOT,
|
|
207
|
+
"cz": cirq.CZ,
|
|
208
|
+
"swap": cirq.SWAP,
|
|
209
|
+
"iswap": cirq.ISWAP,
|
|
210
|
+
"ccx": cirq.CCX,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for inst in circuit.instructions:
|
|
214
|
+
target_qubits = [qubits[q] for q in inst.qubits]
|
|
215
|
+
|
|
216
|
+
if inst.gate in gate_map:
|
|
217
|
+
ops.append(gate_map[inst.gate].on(*target_qubits))
|
|
218
|
+
elif inst.gate == "rx" and inst.params:
|
|
219
|
+
ops.append(cirq.rx(inst.params[0]).on(*target_qubits))
|
|
220
|
+
elif inst.gate == "ry" and inst.params:
|
|
221
|
+
ops.append(cirq.ry(inst.params[0]).on(*target_qubits))
|
|
222
|
+
elif inst.gate == "rz" and inst.params:
|
|
223
|
+
ops.append(cirq.rz(inst.params[0]).on(*target_qubits))
|
|
224
|
+
elif inst.gate == "sx":
|
|
225
|
+
ops.append((cirq.X ** 0.5).on(*target_qubits))
|
|
226
|
+
elif inst.gate == "sdg":
|
|
227
|
+
ops.append((cirq.S ** -1).on(*target_qubits))
|
|
228
|
+
elif inst.gate == "tdg":
|
|
229
|
+
ops.append((cirq.T ** -1).on(*target_qubits))
|
|
230
|
+
elif inst.gate == "p" and inst.params:
|
|
231
|
+
ops.append(cirq.ZPowGate(exponent=inst.params[0] / math.pi).on(*target_qubits))
|
|
232
|
+
elif inst.gate == "cp" and inst.params:
|
|
233
|
+
ops.append(cirq.CZPowGate(exponent=inst.params[0] / math.pi).on(*target_qubits))
|
|
234
|
+
else:
|
|
235
|
+
raise AdapterError(
|
|
236
|
+
f"Gate {inst.gate!r} has no Cirq mapping."
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Add measurements
|
|
240
|
+
if circuit.measurements:
|
|
241
|
+
measured_qubits = [qubits[m.qubit] for m in circuit.measurements]
|
|
242
|
+
ops.append(cirq.measure(*measured_qubits, key="result"))
|
|
243
|
+
|
|
244
|
+
return cirq.Circuit(ops)
|
|
245
|
+
|
|
246
|
+
# ------------------------------------------------------------------
|
|
247
|
+
# Execution
|
|
248
|
+
# ------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def execute(
|
|
251
|
+
self,
|
|
252
|
+
circuit: UniversalCircuit,
|
|
253
|
+
backend: Any | None = None,
|
|
254
|
+
*,
|
|
255
|
+
shots: int = 1024,
|
|
256
|
+
seed_simulator: int | None = None,
|
|
257
|
+
optimization_level: int | None = None,
|
|
258
|
+
transpiler_seed: int | None = None,
|
|
259
|
+
save_statevector: bool = False,
|
|
260
|
+
save_memory: bool = False,
|
|
261
|
+
extra_run_options: dict[str, Any] | None = None,
|
|
262
|
+
) -> ExecutionResult:
|
|
263
|
+
cirq = self._cirq
|
|
264
|
+
|
|
265
|
+
native_circuit = self.to_native(circuit)
|
|
266
|
+
|
|
267
|
+
# Resolve simulator
|
|
268
|
+
if backend is None:
|
|
269
|
+
backend = cirq.Simulator(seed=seed_simulator)
|
|
270
|
+
backend_name = type(backend).__name__
|
|
271
|
+
|
|
272
|
+
t0 = time.perf_counter()
|
|
273
|
+
try:
|
|
274
|
+
if save_statevector and not circuit.measurements:
|
|
275
|
+
sim_result = backend.simulate(native_circuit)
|
|
276
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
277
|
+
sv = list(sim_result.final_state_vector)
|
|
278
|
+
return ExecutionResult(
|
|
279
|
+
counts={},
|
|
280
|
+
shots=0,
|
|
281
|
+
backend_name=backend_name,
|
|
282
|
+
success=True,
|
|
283
|
+
statevector=sv,
|
|
284
|
+
execution_time_ms=elapsed_ms,
|
|
285
|
+
seed_simulator=seed_simulator,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
result = backend.run(native_circuit, repetitions=shots)
|
|
289
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
290
|
+
|
|
291
|
+
# Extract counts from Cirq result
|
|
292
|
+
counts: dict[str, int] = {}
|
|
293
|
+
hist = result.histogram(key="result")
|
|
294
|
+
num_bits = circuit.num_clbits or circuit.num_qubits
|
|
295
|
+
for val, count in hist.items():
|
|
296
|
+
bitstring = format(val, f"0{num_bits}b")
|
|
297
|
+
counts[bitstring] = count
|
|
298
|
+
|
|
299
|
+
memory_data = None
|
|
300
|
+
if save_memory:
|
|
301
|
+
measurements_array = result.measurements.get("result")
|
|
302
|
+
if measurements_array is not None:
|
|
303
|
+
memory_data = [
|
|
304
|
+
"".join(str(b) for b in row) for row in measurements_array
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
return ExecutionResult(
|
|
308
|
+
counts=counts,
|
|
309
|
+
shots=shots,
|
|
310
|
+
backend_name=backend_name,
|
|
311
|
+
success=True,
|
|
312
|
+
memory=memory_data,
|
|
313
|
+
execution_time_ms=elapsed_ms,
|
|
314
|
+
seed_simulator=seed_simulator,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
except Exception as exc:
|
|
318
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
319
|
+
logger.error("Cirq execution failed: %s", exc)
|
|
320
|
+
return ExecutionResult(
|
|
321
|
+
counts={},
|
|
322
|
+
shots=shots,
|
|
323
|
+
backend_name=backend_name,
|
|
324
|
+
success=False,
|
|
325
|
+
error_message=str(exc),
|
|
326
|
+
execution_time_ms=elapsed_ms,
|
|
327
|
+
seed_simulator=seed_simulator,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# ------------------------------------------------------------------
|
|
331
|
+
# Noise profiling
|
|
332
|
+
# ------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
def get_noise_snapshot(self, backend: Any) -> NoiseSnapshot:
|
|
335
|
+
backend_name = type(backend).__name__
|
|
336
|
+
|
|
337
|
+
snapshot = NoiseSnapshot(
|
|
338
|
+
backend_name=backend_name,
|
|
339
|
+
timestamp=datetime.now(timezone.utc),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Cirq noise models expose properties differently depending on device
|
|
343
|
+
if hasattr(backend, "metadata"):
|
|
344
|
+
meta = backend.metadata
|
|
345
|
+
if hasattr(meta, "qubit_set"):
|
|
346
|
+
snapshot.num_qubits = len(meta.qubit_set)
|
|
347
|
+
|
|
348
|
+
return snapshot
|
|
349
|
+
|
|
350
|
+
# ------------------------------------------------------------------
|
|
351
|
+
# Backend discovery
|
|
352
|
+
# ------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
def list_backends(self, **filters: Any) -> list[dict[str, Any]]:
|
|
355
|
+
return [
|
|
356
|
+
{
|
|
357
|
+
"name": "Simulator",
|
|
358
|
+
"num_qubits": 30,
|
|
359
|
+
"simulator": True,
|
|
360
|
+
"framework": "cirq",
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"name": "DensityMatrixSimulator",
|
|
364
|
+
"num_qubits": 20,
|
|
365
|
+
"simulator": True,
|
|
366
|
+
"framework": "cirq",
|
|
367
|
+
},
|
|
368
|
+
]
|