qiskit-waveform-sim 0.1.1__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.
- qiskit_waveform_sim/__init__.py +42 -0
- qiskit_waveform_sim/annotations.py +195 -0
- qiskit_waveform_sim/core.py +727 -0
- qiskit_waveform_sim/py.typed +0 -0
- qiskit_waveform_sim/visualization/__init__.py +663 -0
- qiskit_waveform_sim/visualization/matplotlib.py +439 -0
- qiskit_waveform_sim-0.1.1.dist-info/METADATA +255 -0
- qiskit_waveform_sim-0.1.1.dist-info/RECORD +11 -0
- qiskit_waveform_sim-0.1.1.dist-info/WHEEL +5 -0
- qiskit_waveform_sim-0.1.1.dist-info/licenses/LICENSE +159 -0
- qiskit_waveform_sim-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qiskit-waveform-sim: Zero-bloat Classical Control Waveform Simulator & Interactive Pulse Sheet Viewer for Qiskit 2.5+
|
|
3
|
+
|
|
4
|
+
This package provides a native, offline, lazy-evaluated waveform simulator that works directly
|
|
5
|
+
with Qiskit's modern Target, op_start_times, BoxOp, fractional gates, and the new
|
|
6
|
+
qiskit.circuit.annotation.Annotation API.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from qiskit_waveform_sim.annotations import (
|
|
10
|
+
PulseAnnotationSerializer,
|
|
11
|
+
PulseEnvelopeAnnotation,
|
|
12
|
+
)
|
|
13
|
+
from qiskit_waveform_sim.core import (
|
|
14
|
+
AnalyticalEnvelopes,
|
|
15
|
+
ChannelEvent,
|
|
16
|
+
TargetWaveformSimulator,
|
|
17
|
+
WaveformSnippet,
|
|
18
|
+
)
|
|
19
|
+
from qiskit_waveform_sim.visualization import (
|
|
20
|
+
PulseSheetConfig,
|
|
21
|
+
create_combined_view,
|
|
22
|
+
create_iq_oscilloscope,
|
|
23
|
+
create_pulse_sheet,
|
|
24
|
+
show_iq_oscilloscope,
|
|
25
|
+
show_pulse_sheet,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
__all__ = [
|
|
30
|
+
"TargetWaveformSimulator",
|
|
31
|
+
"WaveformSnippet",
|
|
32
|
+
"ChannelEvent",
|
|
33
|
+
"AnalyticalEnvelopes",
|
|
34
|
+
"PulseEnvelopeAnnotation",
|
|
35
|
+
"PulseAnnotationSerializer",
|
|
36
|
+
"create_pulse_sheet",
|
|
37
|
+
"create_iq_oscilloscope",
|
|
38
|
+
"show_pulse_sheet",
|
|
39
|
+
"show_iq_oscilloscope",
|
|
40
|
+
"create_combined_view",
|
|
41
|
+
"PulseSheetConfig",
|
|
42
|
+
]
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pulse Envelope Annotations for Qiskit 2.5+.
|
|
3
|
+
|
|
4
|
+
Provides custom Annotation subclasses for attaching parametric pulse specifications
|
|
5
|
+
to BoxOp blocks, with full OpenQASM 3 and QPY serialization support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from qiskit.circuit.annotation import Annotation, OpenQASM3Serializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PulseEnvelopeAnnotation(Annotation):
|
|
17
|
+
"""
|
|
18
|
+
Custom Qiskit 2.x Annotation attaching parametric pulse specs to a BoxOp.
|
|
19
|
+
|
|
20
|
+
This annotation can be attached to a `qc.box()` context to override the default
|
|
21
|
+
pulse envelope for gates within that box. It implements OpenQASM3Serializer
|
|
22
|
+
for round-trip serialization via `qiskit.qasm3.dumps()` and `qiskit.qpy.dump()`.
|
|
23
|
+
|
|
24
|
+
Examples
|
|
25
|
+
--------
|
|
26
|
+
>>> from qiskit import QuantumCircuit
|
|
27
|
+
>>> from qiskit_waveform_sim import PulseEnvelopeAnnotation
|
|
28
|
+
>>>
|
|
29
|
+
>>> qc = QuantumCircuit(1)
|
|
30
|
+
>>> with qc.box(duration=160, unit="dt",
|
|
31
|
+
... annotations=[PulseEnvelopeAnnotation(shape="drag", amp=0.55, beta=0.25)]):
|
|
32
|
+
... qc.sx(0)
|
|
33
|
+
|
|
34
|
+
**QPY Round-trip Example**
|
|
35
|
+
>>> from qiskit import qpy
|
|
36
|
+
>>> from qiskit_waveform_sim import PulseAnnotationSerializer
|
|
37
|
+
>>> import io
|
|
38
|
+
>>>
|
|
39
|
+
>>> qc = QuantumCircuit(1)
|
|
40
|
+
>>> with qc.box(duration=160, unit="dt",
|
|
41
|
+
... annotations=[PulseEnvelopeAnnotation(shape="drag", amp=0.55, beta=0.25)]):
|
|
42
|
+
... qc.sx(0)
|
|
43
|
+
>>>
|
|
44
|
+
>>> serializer = PulseAnnotationSerializer()
|
|
45
|
+
>>> buf = io.BytesIO()
|
|
46
|
+
>>> qpy.dump(qc, buf, annotation_factories={"pulse_sim.envelope": serializer.as_qpy()})
|
|
47
|
+
>>> buf.seek(0)
|
|
48
|
+
>>> loaded = qpy.load(buf, annotation_factories={"pulse_sim.envelope": serializer.as_qpy()})[0]
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
namespace = "pulse_sim.envelope"
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
shape: str = "drag",
|
|
56
|
+
amp: float = 0.5,
|
|
57
|
+
beta: float = 0.08,
|
|
58
|
+
sigma_ratio: float = 0.25,
|
|
59
|
+
risefall_dt: int = 16,
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Initialize pulse envelope annotation.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
shape : str
|
|
67
|
+
Pulse shape: "drag" or "gaussian_square"
|
|
68
|
+
amp : float
|
|
69
|
+
Peak amplitude (normalized, 0.0-1.0)
|
|
70
|
+
beta : float
|
|
71
|
+
DRAG leakage suppression parameter (for drag shape)
|
|
72
|
+
sigma_ratio : float
|
|
73
|
+
Gaussian sigma as ratio of pulse duration (for drag shape)
|
|
74
|
+
risefall_dt : int
|
|
75
|
+
Rise/fall duration in dt (for gaussian_square shape)
|
|
76
|
+
"""
|
|
77
|
+
if shape not in ("drag", "gaussian_square"):
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Unsupported shape: {shape}. Use 'drag' or 'gaussian_square'"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.shape = shape
|
|
83
|
+
self.amp = float(amp)
|
|
84
|
+
self.beta = float(beta)
|
|
85
|
+
self.sigma_ratio = float(sigma_ratio)
|
|
86
|
+
self.risefall_dt = int(risefall_dt)
|
|
87
|
+
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
return (
|
|
90
|
+
f"PulseEnvelopeAnnotation(shape={self.shape!r}, amp={self.amp}, "
|
|
91
|
+
f"beta={self.beta}, sigma_ratio={self.sigma_ratio}, risefall_dt={self.risefall_dt})"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def __eq__(self, other: object) -> bool:
|
|
95
|
+
if not isinstance(other, PulseEnvelopeAnnotation):
|
|
96
|
+
return False
|
|
97
|
+
return (
|
|
98
|
+
self.shape == other.shape
|
|
99
|
+
and self.amp == other.amp
|
|
100
|
+
and self.beta == other.beta
|
|
101
|
+
and self.sigma_ratio == other.sigma_ratio
|
|
102
|
+
and self.risefall_dt == other.risefall_dt
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def __hash__(self) -> int:
|
|
106
|
+
return hash(
|
|
107
|
+
(self.shape, self.amp, self.beta, self.sigma_ratio, self.risefall_dt)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def to_dict(self) -> dict[str, Any]:
|
|
111
|
+
"""Serialize to dictionary for QPY."""
|
|
112
|
+
return {
|
|
113
|
+
"shape": self.shape,
|
|
114
|
+
"amp": self.amp,
|
|
115
|
+
"beta": self.beta,
|
|
116
|
+
"sigma_ratio": self.sigma_ratio,
|
|
117
|
+
"risefall_dt": self.risefall_dt,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def from_dict(cls, data: dict[str, Any]) -> PulseEnvelopeAnnotation:
|
|
122
|
+
"""Deserialize from dictionary (QPY)."""
|
|
123
|
+
return cls(**data)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class PulseAnnotationSerializer(OpenQASM3Serializer):
|
|
127
|
+
"""
|
|
128
|
+
Serializes PulseEnvelopeAnnotation to/from OpenQASM 3 and QPY.
|
|
129
|
+
|
|
130
|
+
OpenQASM 3 Export Format::
|
|
131
|
+
|
|
132
|
+
@pulse_sim.drag(amp=0.5, beta=0.08, sigma_ratio=0.25, risefall_dt=16)
|
|
133
|
+
box {
|
|
134
|
+
sx q[0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
QPY Serialization::
|
|
138
|
+
|
|
139
|
+
Uses the standard QPY annotation mechanism with namespace "pulse_sim.envelope"
|
|
140
|
+
via the `as_qpy()` method. Example:
|
|
141
|
+
|
|
142
|
+
>>> from qiskit import qpy
|
|
143
|
+
>>> from qiskit_waveform_sim import PulseAnnotationSerializer
|
|
144
|
+
>>> import io
|
|
145
|
+
>>>
|
|
146
|
+
>>> serializer = PulseAnnotationSerializer()
|
|
147
|
+
>>> buf = io.BytesIO()
|
|
148
|
+
>>> qpy.dump(qc, buf, annotation_factories={"pulse_sim.envelope": serializer.as_qpy()})
|
|
149
|
+
>>> buf.seek(0)
|
|
150
|
+
>>> loaded = qpy.load(buf, annotation_factories={"pulse_sim.envelope": serializer.as_qpy()})[0]
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def dump(self, annotation: Annotation) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Serialize annotation to OpenQASM 3 pragma string.
|
|
156
|
+
|
|
157
|
+
Returns NotImplemented if namespace doesn't match.
|
|
158
|
+
"""
|
|
159
|
+
if annotation.namespace != "pulse_sim.envelope":
|
|
160
|
+
return NotImplemented # type: ignore
|
|
161
|
+
|
|
162
|
+
if not isinstance(annotation, PulseEnvelopeAnnotation):
|
|
163
|
+
return NotImplemented # type: ignore
|
|
164
|
+
|
|
165
|
+
payload = {
|
|
166
|
+
"shape": annotation.shape,
|
|
167
|
+
"amp": annotation.amp,
|
|
168
|
+
"beta": annotation.beta,
|
|
169
|
+
"sigma_ratio": annotation.sigma_ratio,
|
|
170
|
+
"risefall_dt": annotation.risefall_dt,
|
|
171
|
+
}
|
|
172
|
+
# Use repr for valid Python literal that ast.literal_eval can parse
|
|
173
|
+
return repr(payload)
|
|
174
|
+
|
|
175
|
+
def load(self, namespace: str, payload: str) -> Annotation:
|
|
176
|
+
"""
|
|
177
|
+
Deserialize annotation from OpenQASM 3 pragma string.
|
|
178
|
+
|
|
179
|
+
Returns NotImplemented if namespace doesn't match.
|
|
180
|
+
"""
|
|
181
|
+
if namespace != "pulse_sim.envelope":
|
|
182
|
+
return NotImplemented # type: ignore
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
data = ast.literal_eval(payload)
|
|
186
|
+
return PulseEnvelopeAnnotation(**data)
|
|
187
|
+
except (ValueError, SyntaxError, TypeError) as e:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"Failed to parse PulseEnvelopeAnnotation payload: {payload}"
|
|
190
|
+
) from e
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Register the serializer with Qiskit's annotation system
|
|
194
|
+
# This happens automatically when the module is imported and qiskit.qasm3 is used
|
|
195
|
+
# with annotation_handlers={"pulse_sim.envelope": PulseAnnotationSerializer()}
|