waveforms 2.2.3__tar.gz → 3.0.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.
- {waveforms-2.2.3 → waveforms-3.0.0}/MANIFEST.in +2 -1
- {waveforms-2.2.3/waveforms.egg-info → waveforms-3.0.0}/PKG-INFO +25 -1
- {waveforms-2.2.3 → waveforms-3.0.0}/README.md +22 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/pyproject.toml +3 -1
- waveforms-3.0.0/tests/test_waveform.py +254 -0
- waveforms-3.0.0/tests/test_wavevstack.py +61 -0
- waveforms-3.0.0/waveforms/__init__.py +50 -0
- waveforms-3.0.0/waveforms/_waveform.pyi +73 -0
- waveforms-3.0.0/waveforms/_waveform.pyx +1354 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/version.py +1 -1
- waveforms-3.0.0/waveforms/waveform.py +950 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/waveform_parser.py +18 -14
- {waveforms-2.2.3 → waveforms-3.0.0/waveforms.egg-info}/PKG-INFO +25 -1
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms.egg-info/SOURCES.txt +0 -2
- waveforms-2.2.3/tests/test_multi_drag.py +0 -78
- waveforms-2.2.3/tests/test_waveform.py +0 -194
- waveforms-2.2.3/tests/test_wavevstack.py +0 -143
- waveforms-2.2.3/waveforms/__init__.py +0 -11
- waveforms-2.2.3/waveforms/_waveform.pyi +0 -120
- waveforms-2.2.3/waveforms/_waveform.pyx +0 -654
- waveforms-2.2.3/waveforms/multy_drag.py +0 -232
- waveforms-2.2.3/waveforms/waveform.py +0 -1536
- {waveforms-2.2.3 → waveforms-3.0.0}/LICENSE +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/setup.cfg +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/setup.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/src/waveform.h +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/Waveform.g4 +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/WaveformLexer.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/WaveformListener.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/WaveformParser.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/__main__.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/distortion.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms/utils.py +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms.egg-info/dependency_links.txt +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms.egg-info/entry_points.txt +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms.egg-info/requires.txt +0 -0
- {waveforms-2.2.3 → waveforms-3.0.0}/waveforms.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waveforms
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Edit waveforms used in experiment
|
|
5
5
|
Author-email: feihoo87 <feihoo87@gmail.com>
|
|
6
6
|
Maintainer-email: feihoo87 <feihoo87@gmail.com>
|
|
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
26
|
Requires-Python: >=3.10
|
|
25
27
|
Description-Content-Type: text/markdown
|
|
26
28
|
License-File: LICENSE
|
|
@@ -96,6 +98,28 @@ plt.plot(t, y_wav(t))
|
|
|
96
98
|
plt.show()
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
### Packed binary representation
|
|
102
|
+
|
|
103
|
+
Starting with version 3, `Waveform` and `WaveVStack` use a packed-binary backend.
|
|
104
|
+
It is intended for workloads with many small pulses and for low-overhead
|
|
105
|
+
serialization.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import waveforms as wf
|
|
109
|
+
|
|
110
|
+
# Optional; call this once, before constructing or loading waveform objects.
|
|
111
|
+
wf.set_time_resolution(1e-12)
|
|
112
|
+
|
|
113
|
+
pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
|
|
114
|
+
data = pulse.to_bytes()
|
|
115
|
+
restored = wf.Waveform.from_bytes(data)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Time resolution is process-wide and is not stored in each binary block. A
|
|
119
|
+
process loading a block must therefore use the same resolution as the process
|
|
120
|
+
that created it. The setting is locked when the first waveform object is
|
|
121
|
+
created or loaded.
|
|
122
|
+
|
|
99
123
|
## Reporting Issues
|
|
100
124
|
Please report all issues [on github](https://github.com/feihoo87/waveforms/issues).
|
|
101
125
|
|
|
@@ -54,6 +54,28 @@ plt.plot(t, y_wav(t))
|
|
|
54
54
|
plt.show()
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
### Packed binary representation
|
|
58
|
+
|
|
59
|
+
Starting with version 3, `Waveform` and `WaveVStack` use a packed-binary backend.
|
|
60
|
+
It is intended for workloads with many small pulses and for low-overhead
|
|
61
|
+
serialization.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import waveforms as wf
|
|
65
|
+
|
|
66
|
+
# Optional; call this once, before constructing or loading waveform objects.
|
|
67
|
+
wf.set_time_resolution(1e-12)
|
|
68
|
+
|
|
69
|
+
pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
|
|
70
|
+
data = pulse.to_bytes()
|
|
71
|
+
restored = wf.Waveform.from_bytes(data)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Time resolution is process-wide and is not stored in each binary block. A
|
|
75
|
+
process loading a block must therefore use the same resolution as the process
|
|
76
|
+
that created it. The setting is locked when the first waveform object is
|
|
77
|
+
created or loaded.
|
|
78
|
+
|
|
57
79
|
## Reporting Issues
|
|
58
80
|
Please report all issues [on github](https://github.com/feihoo87/waveforms/issues).
|
|
59
81
|
|
|
@@ -27,6 +27,8 @@ classifiers = [
|
|
|
27
27
|
"Programming Language :: Python :: 3.10",
|
|
28
28
|
"Programming Language :: Python :: 3.11",
|
|
29
29
|
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Programming Language :: Python :: 3.14",
|
|
30
32
|
]
|
|
31
33
|
requires-python = ">= 3.10"
|
|
32
34
|
dependencies = [
|
|
@@ -69,7 +71,7 @@ license-files = ["LICENSE"]
|
|
|
69
71
|
include-package-data = true
|
|
70
72
|
|
|
71
73
|
[tool.setuptools.package-data]
|
|
72
|
-
waveforms = ["WaveformLexer.py", "WaveformParser.py", "WaveformListener.py"]
|
|
74
|
+
waveforms = ["WaveformLexer.py", "WaveformParser.py", "WaveformListener.py", "*.pyi"]
|
|
73
75
|
|
|
74
76
|
[tool.setuptools.dynamic]
|
|
75
77
|
version = {attr = "waveforms.version.__version__"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
import scipy.special as special
|
|
9
|
+
from scipy.signal import butter, lfilter, lfiltic, tf2sos
|
|
10
|
+
|
|
11
|
+
import waveforms as wf
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
PUBLIC_NAMES = {
|
|
15
|
+
"D", "Waveform", "WaveVStack", "chirp", "const", "cos", "cosh",
|
|
16
|
+
"coshPulse", "cosPulse", "cut", "drag", "exp", "function",
|
|
17
|
+
"gaussian", "general_cosine", "get_time_resolution", "hanning", "interp", "mixing",
|
|
18
|
+
"mollifier", "one", "poly", "registerBaseFunc", "registerDerivative",
|
|
19
|
+
"samplingPoints", "set_time_resolution", "sign", "sin", "sinc", "sinh", "square", "step",
|
|
20
|
+
"t", "wave_eval", "zero",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_public_api_and_basic_sampling():
|
|
25
|
+
assert PUBLIC_NAMES <= set(dir(wf))
|
|
26
|
+
x = np.linspace(-2.0, 2.0, 1001)
|
|
27
|
+
assert np.allclose(wf.cos(1.3)(x), np.cos(1.3 * x))
|
|
28
|
+
assert np.allclose(wf.sin(0.7)(x), np.sin(0.7 * x))
|
|
29
|
+
assert np.allclose(wf.poly([1, -0.5, 0.25])(x),
|
|
30
|
+
1 - 0.5 * x + 0.25 * x**2)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_binary_roundtrip_is_zero_copy_for_bytes_input():
|
|
34
|
+
wav = (wf.gaussian(0.8) >> 1.2) * wf.cos(2.3) + 0.25
|
|
35
|
+
data = wav.to_bytes()
|
|
36
|
+
restored = wf.Waveform.from_bytes(data)
|
|
37
|
+
assert restored._core.to_bytes() is data
|
|
38
|
+
assert restored == wav
|
|
39
|
+
assert pickle.loads(pickle.dumps(wav)) == wav
|
|
40
|
+
assert wf.one() >> 5 == wf.one()
|
|
41
|
+
assert (wf.const(2.5) << 7).to_bytes() == wf.const(2.5).to_bytes()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_operations_simplify_derivative_and_clipping():
|
|
45
|
+
x = np.linspace(-1.9, 1.9, 4097)
|
|
46
|
+
wav = wf.cos(1) * wf.sin(2) * wf.cos(3, 4)
|
|
47
|
+
expected = np.cos(x) * np.sin(2 * x) * np.cos(3 * x + 4)
|
|
48
|
+
assert np.allclose(wav(x), expected)
|
|
49
|
+
assert np.allclose(wav.simplify()(x), expected)
|
|
50
|
+
|
|
51
|
+
width = 4.0
|
|
52
|
+
std_sq2 = width / 3.3302184446307908
|
|
53
|
+
order = 6
|
|
54
|
+
expected_d = ((-1)**order / std_sq2**order
|
|
55
|
+
* special.eval_hermite(order, x / std_sq2)
|
|
56
|
+
* np.exp(-(x / std_sq2)**2))
|
|
57
|
+
assert np.allclose(wf.gaussian(width, d=order)(x), expected_d)
|
|
58
|
+
|
|
59
|
+
clipped = 2 * wf.cos(2.1) + 0.3 * wf.sin(0.7)
|
|
60
|
+
clipped.min = -0.4
|
|
61
|
+
clipped.max = 0.6
|
|
62
|
+
assert np.allclose(clipped(x), np.clip(2 * np.cos(2.1 * x)
|
|
63
|
+
+ 0.3 * np.sin(0.7 * x),
|
|
64
|
+
-0.4, 0.6))
|
|
65
|
+
|
|
66
|
+
width = 1.3
|
|
67
|
+
shift = 0.7
|
|
68
|
+
frequency = 2.1
|
|
69
|
+
phase = 0.4
|
|
70
|
+
std_sq2 = width / 3.3302184446307908
|
|
71
|
+
base = (wf.gaussian(width) >> shift) * wf.cos(frequency, phase)
|
|
72
|
+
inside = (x >= -0.75 * width + shift) & (x < 0.75 * width + shift)
|
|
73
|
+
for order in range(4):
|
|
74
|
+
expected = np.zeros_like(x)
|
|
75
|
+
z = (x[inside] - shift) / std_sq2
|
|
76
|
+
for gaussian_order in range(order + 1):
|
|
77
|
+
carrier_order = order - gaussian_order
|
|
78
|
+
gaussian_part = (
|
|
79
|
+
(-1) ** gaussian_order / std_sq2 ** gaussian_order
|
|
80
|
+
* special.eval_hermite(gaussian_order, z) * np.exp(-(z**2))
|
|
81
|
+
)
|
|
82
|
+
carrier_part = frequency ** carrier_order * np.cos(
|
|
83
|
+
frequency * x[inside] + phase + carrier_order * np.pi / 2
|
|
84
|
+
)
|
|
85
|
+
expected[inside] += (
|
|
86
|
+
special.comb(order, gaussian_order, exact=True)
|
|
87
|
+
* gaussian_part * carrier_part
|
|
88
|
+
)
|
|
89
|
+
assert np.allclose(wf.D(base, order)(x), expected,
|
|
90
|
+
atol=2e-10, rtol=2e-10)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_metadata_pickle_and_removed_experimental_serializers():
|
|
94
|
+
wav = (wf.gaussian(10) >> 5) + (wf.gaussian(10) >> 50)
|
|
95
|
+
wav *= wf.cos(200)
|
|
96
|
+
wav.start = -10
|
|
97
|
+
wav.stop = 70
|
|
98
|
+
wav.sample_rate = 20
|
|
99
|
+
wav.label = "pulse"
|
|
100
|
+
|
|
101
|
+
restored = pickle.loads(pickle.dumps(wav))
|
|
102
|
+
assert restored == wav
|
|
103
|
+
assert restored.start == wav.start
|
|
104
|
+
assert restored.stop == wav.stop
|
|
105
|
+
assert restored.sample_rate == wav.sample_rate
|
|
106
|
+
assert restored.label == wav.label
|
|
107
|
+
for name in ("tolist", "fromlist", "totree", "fromtree", "_as_v1"):
|
|
108
|
+
assert not hasattr(wav, name)
|
|
109
|
+
assert wav.begin == -2.5
|
|
110
|
+
assert wav.end == 57.5
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_sampling_points_chirps_and_mixing():
|
|
114
|
+
x = np.linspace(0, 2, 2048, endpoint=False)
|
|
115
|
+
points = np.sin(np.linspace(0, 3, 65))
|
|
116
|
+
sampled = wf.samplingPoints(0, 2, points)
|
|
117
|
+
assert np.allclose(sampled(x), np.interp(x, np.linspace(0, 2, 65), points))
|
|
118
|
+
|
|
119
|
+
for kind in ("linear", "exponential", "hyperbolic"):
|
|
120
|
+
wav = wf.chirp(1, 2, 2, 0.3, kind)
|
|
121
|
+
if kind == "linear":
|
|
122
|
+
phase = 0.3 + 2 * np.pi * ((2 - 1) / 4 * x**2 + x)
|
|
123
|
+
elif kind == "exponential":
|
|
124
|
+
alpha = np.log(2) / 2
|
|
125
|
+
phase = 0.3 + 2 * np.pi * (np.exp(alpha * x) - 1) / alpha
|
|
126
|
+
else:
|
|
127
|
+
k = (1 - 2) / (2 * 2)
|
|
128
|
+
phase = 0.3 + 2 * np.pi / k * np.log(1 + k * x)
|
|
129
|
+
assert np.allclose(wav(x), np.sin(phase))
|
|
130
|
+
|
|
131
|
+
i, q = wf.mixing(wf.gaussian(0.5), phase=0.2, freq=1.3,
|
|
132
|
+
DRAGScaling=0.01)
|
|
133
|
+
assert np.all(np.isfinite(i(x)))
|
|
134
|
+
assert np.all(np.isfinite(q(x)))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_filters_and_chunked_sampling():
|
|
138
|
+
sample_rate = 1000
|
|
139
|
+
b, a = butter(3, 4.0, "lowpass", fs=sample_rate)
|
|
140
|
+
zi = lfiltic(b, a, [0])
|
|
141
|
+
x = np.linspace(-1, 1, 2000, endpoint=False)
|
|
142
|
+
|
|
143
|
+
wav = wf.step(0)
|
|
144
|
+
wav.start = -1
|
|
145
|
+
wav.stop = 1
|
|
146
|
+
wav.sample_rate = sample_rate
|
|
147
|
+
wav.filters = (tf2sos(b, a), 0)
|
|
148
|
+
expected = lfilter(b, a, np.heaviside(x, 1), zi=zi)[0]
|
|
149
|
+
assert np.allclose(wav.sample(), expected)
|
|
150
|
+
assert np.allclose(np.concatenate(list(wav.sample(chunk_size=137))), expected)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_wavevstack_template_sharing_operations_and_roundtrip():
|
|
154
|
+
template = wf.gaussian(20e-9) * wf.cos(2 * np.pi * 5e9)
|
|
155
|
+
waves = [template >> (i * 80e-9) for i in range(1000)]
|
|
156
|
+
stack = wf.WaveVStack(waves)
|
|
157
|
+
|
|
158
|
+
# One template plus three packed event arrays; substantially smaller than
|
|
159
|
+
# serializing 1000 complete shifted expression trees.
|
|
160
|
+
assert len(stack.to_bytes()) < 32_000
|
|
161
|
+
x = np.linspace(0, 80e-6, 16_000, endpoint=False)
|
|
162
|
+
assert np.allclose(stack(x), stack.simplify()(x))
|
|
163
|
+
|
|
164
|
+
shifted = (stack + 2.0) >> 1e-6
|
|
165
|
+
shifted = shifted * 0.25 + wf.sin(2 * np.pi * 1e6)
|
|
166
|
+
assert np.allclose(shifted(x), shifted.simplify()(x))
|
|
167
|
+
|
|
168
|
+
materialized = wf.WaveVStack.from_bytes(shifted.to_bytes())
|
|
169
|
+
assert np.allclose(materialized(x), shifted(x))
|
|
170
|
+
|
|
171
|
+
restored = wf.WaveVStack.from_bytes(stack.to_bytes())
|
|
172
|
+
assert restored == stack
|
|
173
|
+
assert pickle.loads(pickle.dumps(stack)) == stack
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_notebook_constructors_and_wave_eval():
|
|
177
|
+
x = np.linspace(-20, 60, 4001)
|
|
178
|
+
linear_step = np.where(x < -1, 0, np.where(x < 1, 0.5 + x / 2, 1))
|
|
179
|
+
cosine_step = np.where(
|
|
180
|
+
x < -1, 0, np.where(x < 1, 0.5 + 0.5 * np.sin(np.pi * x / 2), 1)
|
|
181
|
+
)
|
|
182
|
+
erf_step = np.where(
|
|
183
|
+
x < -2, 0, np.where(x < 2, 0.5 + 0.5 * special.erf(x / 0.4), 1)
|
|
184
|
+
)
|
|
185
|
+
assert np.allclose(wf.step(2, "linear")(x), linear_step)
|
|
186
|
+
assert np.allclose(wf.step(2, "cos")(x), cosine_step)
|
|
187
|
+
assert np.allclose(wf.step(2, "erf")(x), erf_step)
|
|
188
|
+
|
|
189
|
+
assert np.allclose(wf.exp(0.03 + 0.2j)(x), np.exp((0.03 + 0.2j) * x))
|
|
190
|
+
inside = np.array([-2.0, -1.0, 0.0, 1.5, 2.999])
|
|
191
|
+
assert np.allclose(wf.interp([-2, 0, 3], [1, -1, 2])(inside),
|
|
192
|
+
np.interp(inside, [-2, 0, 3], [1, -1, 2]))
|
|
193
|
+
|
|
194
|
+
for wav in (wf.square(8, 2), wf.gaussian(8, 3), wf.cosPulse(8, 2),
|
|
195
|
+
wf.coshPulse(8, 2, 3), wf.mollifier(8, 2), wf.sinc(3)):
|
|
196
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
197
|
+
values = wav(x)
|
|
198
|
+
assert np.all(np.isfinite(values))
|
|
199
|
+
|
|
200
|
+
expression = ("(gaussian(12) >> 3) * cos(16.2, 1.63) + "
|
|
201
|
+
"0.5*(gaussian(12) >> 35) * cos(16.2, 2)")
|
|
202
|
+
actual = wf.wave_eval(expression)
|
|
203
|
+
expected = wf.gaussian(12) >> 3
|
|
204
|
+
expected = expected * wf.cos(16.2, 1.63)
|
|
205
|
+
expected += 0.5 * (wf.gaussian(12) >> 35) * wf.cos(16.2, 2)
|
|
206
|
+
assert isinstance(actual, wf.Waveform)
|
|
207
|
+
assert np.allclose(actual(x), expected(x))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_time_is_global_configuration_and_blocks_store_integer_ticks():
|
|
211
|
+
assert wf.get_time_resolution() == 1e-12
|
|
212
|
+
wav = wf.square(4e-9) >> 11e-9
|
|
213
|
+
ticks = wav._core.get_bound_ticks()
|
|
214
|
+
assert ticks.dtype == np.dtype("<i8")
|
|
215
|
+
assert np.array_equal(ticks[:-1], [-2_000, 2_000])
|
|
216
|
+
assert wav._delay == 11e-9
|
|
217
|
+
with pytest.raises(RuntimeError):
|
|
218
|
+
wf.set_time_resolution(1e-15)
|
|
219
|
+
|
|
220
|
+
code = (
|
|
221
|
+
"import waveforms as w; "
|
|
222
|
+
"w.set_time_resolution(1e-15); "
|
|
223
|
+
"x=w.square(4e-12); "
|
|
224
|
+
"print(w.get_time_resolution(), x._core.get_bound_ticks()[0])"
|
|
225
|
+
)
|
|
226
|
+
result = subprocess.run([sys.executable, "-c", code], check=True,
|
|
227
|
+
capture_output=True, text=True)
|
|
228
|
+
assert result.stdout.strip() == "1e-15 -2000"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_single_packed_backend_has_no_waveform2_modules():
|
|
232
|
+
package = Path(wf.__file__).parent
|
|
233
|
+
python_source = (package / "waveform.py").read_text()
|
|
234
|
+
cython_source = (package / "_waveform.pyx").read_text()
|
|
235
|
+
assert "waveform2" not in python_source
|
|
236
|
+
assert "_waveform2" not in cython_source
|
|
237
|
+
assert not (package / "waveform2.py").exists()
|
|
238
|
+
assert not (package / "_waveform2.pyx").exists()
|
|
239
|
+
|
|
240
|
+
code = (
|
|
241
|
+
"import sys; from waveforms import gaussian; gaussian(1); "
|
|
242
|
+
"print('waveforms.waveform2' in sys.modules, "
|
|
243
|
+
"'waveforms._waveform2' in sys.modules)"
|
|
244
|
+
)
|
|
245
|
+
result = subprocess.run([sys.executable, "-c", code], check=True,
|
|
246
|
+
capture_output=True, text=True)
|
|
247
|
+
assert result.stdout.strip() == "False False"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_custom_functions_are_explicitly_unsupported():
|
|
251
|
+
with pytest.raises(NotImplementedError):
|
|
252
|
+
wf.function(lambda x: x)
|
|
253
|
+
with pytest.raises(NotImplementedError):
|
|
254
|
+
wf.registerBaseFunc(lambda x: x)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.signal import butter, lfilter, lfiltic, tf2sos
|
|
5
|
+
|
|
6
|
+
from waveforms import WaveVStack, cos, gaussian, pi, poly, sin, step, zero
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _waves():
|
|
10
|
+
return [cos(1), sin(2), gaussian(3),
|
|
11
|
+
poly([1, -1 / 2, 1 / 6, -1 / 12])]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_wavevstack_sampling_algebra_and_shifts():
|
|
15
|
+
x = np.linspace(-10, 10, 1001)
|
|
16
|
+
waves = _waves()
|
|
17
|
+
expected = zero()
|
|
18
|
+
for wav in waves:
|
|
19
|
+
expected += wav
|
|
20
|
+
stack = WaveVStack(waves)
|
|
21
|
+
|
|
22
|
+
assert stack.simplify() == expected
|
|
23
|
+
assert np.allclose(stack(x), expected(x))
|
|
24
|
+
assert np.allclose((stack + sin(2))(x), (expected + sin(2))(x))
|
|
25
|
+
assert np.allclose((stack - sin(2))(x), (expected - sin(2))(x))
|
|
26
|
+
assert np.allclose((stack * sin(2) + 3)(x),
|
|
27
|
+
(expected * sin(2) + 3)(x))
|
|
28
|
+
assert np.allclose((stack / 2)(x), (expected / 2)(x))
|
|
29
|
+
assert np.allclose((stack >> 0.6)(x), (expected >> 0.6)(x))
|
|
30
|
+
assert np.allclose((stack << 1.4)(x), (expected << 1.4)(x))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_wavevstack_filtering_and_chunked_sampling():
|
|
34
|
+
sample_rate = 1000
|
|
35
|
+
b, a = butter(3, 4.0, "lowpass", fs=sample_rate)
|
|
36
|
+
zi = lfiltic(b, a, [0])
|
|
37
|
+
x = np.linspace(-1, 1, 2000, endpoint=False)
|
|
38
|
+
|
|
39
|
+
stack = WaveVStack([step(0) << 0.5, -step(0)])
|
|
40
|
+
stack.sample_rate = sample_rate
|
|
41
|
+
stack.start = -1
|
|
42
|
+
stack.stop = 1
|
|
43
|
+
stack.filters = (tf2sos(b, a), 0)
|
|
44
|
+
expected = lfilter(
|
|
45
|
+
b, a, np.heaviside(x + 0.5, 1) - np.heaviside(x, 1), zi=zi
|
|
46
|
+
)[0]
|
|
47
|
+
|
|
48
|
+
assert np.allclose(stack.sample(), expected, atol=1e-6)
|
|
49
|
+
chunks = np.concatenate(list(stack.sample(chunk_size=137)))
|
|
50
|
+
assert np.allclose(chunks, expected, atol=1e-6)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_wavevstack_binary_template_sharing_and_pickle():
|
|
54
|
+
template = gaussian(20e-9) * cos(2 * pi * 5e9)
|
|
55
|
+
stack = WaveVStack([template >> (index * 80e-9)
|
|
56
|
+
for index in range(10_000)])
|
|
57
|
+
assert len(stack.to_bytes()) < 300_000
|
|
58
|
+
|
|
59
|
+
restored = WaveVStack.from_bytes(stack.to_bytes())
|
|
60
|
+
assert restored == stack
|
|
61
|
+
assert pickle.loads(pickle.dumps(stack)) == stack
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from numpy import e, pi
|
|
2
|
+
|
|
3
|
+
from .version import __version__
|
|
4
|
+
from .waveform import (
|
|
5
|
+
D,
|
|
6
|
+
Waveform,
|
|
7
|
+
WaveVStack,
|
|
8
|
+
chirp,
|
|
9
|
+
const,
|
|
10
|
+
cos,
|
|
11
|
+
cosh,
|
|
12
|
+
coshPulse,
|
|
13
|
+
cosPulse,
|
|
14
|
+
cut,
|
|
15
|
+
drag,
|
|
16
|
+
exp,
|
|
17
|
+
function,
|
|
18
|
+
gaussian,
|
|
19
|
+
general_cosine,
|
|
20
|
+
get_time_resolution,
|
|
21
|
+
hanning,
|
|
22
|
+
interp,
|
|
23
|
+
mixing,
|
|
24
|
+
mollifier,
|
|
25
|
+
one,
|
|
26
|
+
poly,
|
|
27
|
+
registerBaseFunc,
|
|
28
|
+
registerDerivative,
|
|
29
|
+
samplingPoints,
|
|
30
|
+
set_time_resolution,
|
|
31
|
+
sign,
|
|
32
|
+
sin,
|
|
33
|
+
sinc,
|
|
34
|
+
sinh,
|
|
35
|
+
square,
|
|
36
|
+
step,
|
|
37
|
+
t,
|
|
38
|
+
wave_eval,
|
|
39
|
+
zero,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"D", "Waveform", "WaveVStack", "chirp", "const", "cos", "cosh",
|
|
44
|
+
"coshPulse", "cosPulse", "cut", "drag", "e", "exp", "function",
|
|
45
|
+
"gaussian", "general_cosine", "get_time_resolution", "hanning",
|
|
46
|
+
"interp", "mixing", "mollifier", "one", "pi", "poly",
|
|
47
|
+
"registerBaseFunc", "registerDerivative", "samplingPoints",
|
|
48
|
+
"set_time_resolution", "sign", "sin", "sinc", "sinh", "square",
|
|
49
|
+
"step", "t", "wave_eval", "zero", "__version__",
|
|
50
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
NDIGITS: int
|
|
6
|
+
LINEAR: int
|
|
7
|
+
GAUSSIAN: int
|
|
8
|
+
ERF: int
|
|
9
|
+
COS: int
|
|
10
|
+
SINC: int
|
|
11
|
+
EXP: int
|
|
12
|
+
INTERP: int
|
|
13
|
+
LINEARCHIRP: int
|
|
14
|
+
EXPONENTIALCHIRP: int
|
|
15
|
+
HYPERBOLICCHIRP: int
|
|
16
|
+
COSH: int
|
|
17
|
+
SINH: int
|
|
18
|
+
DRAG: int
|
|
19
|
+
MOLLIFIER: int
|
|
20
|
+
D_GAUSSIAN: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PackedWaveform:
|
|
24
|
+
def __init__(self, data: bytes | bytearray | memoryview = ...) -> None: ...
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_bytes(cls, data: bytes | bytearray | memoryview) -> PackedWaveform: ...
|
|
27
|
+
def to_bytes(self) -> bytes: ...
|
|
28
|
+
def get_bounds(self) -> np.ndarray: ...
|
|
29
|
+
def get_bound_ticks(self) -> np.ndarray: ...
|
|
30
|
+
def shifted(self, time: float) -> PackedWaveform: ...
|
|
31
|
+
def scaled(self, value: int | float | complex) -> PackedWaveform: ...
|
|
32
|
+
def add(self, other: PackedWaveform) -> PackedWaveform: ...
|
|
33
|
+
def mul(self, other: PackedWaveform) -> PackedWaveform: ...
|
|
34
|
+
def power(self, n: int) -> PackedWaveform: ...
|
|
35
|
+
def simplify(self, eps: float = ...) -> PackedWaveform: ...
|
|
36
|
+
def filtered(self, low: float = ..., high: float = ...,
|
|
37
|
+
eps: float = ...) -> PackedWaveform: ...
|
|
38
|
+
def derivative(self, order: int = ...) -> PackedWaveform: ...
|
|
39
|
+
def evaluate(self, x: np.ndarray, lower: float = ...,
|
|
40
|
+
upper: float = ...) -> np.ndarray: ...
|
|
41
|
+
def evaluate_shifted(self, x: np.ndarray, delay: float,
|
|
42
|
+
lower: float = ..., upper: float = ...) -> np.ndarray: ...
|
|
43
|
+
def parts(self, x: np.ndarray, lower: float = ...,
|
|
44
|
+
upper: float = ...) -> tuple[list, type]: ...
|
|
45
|
+
def parts_shifted(self, x: np.ndarray, delay: float,
|
|
46
|
+
lower: float = ..., upper: float = ...) -> tuple[list, type]: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PackedStack:
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_bytes(cls, data: bytes | bytearray | memoryview) -> PackedStack: ...
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_events(cls, events: Iterable[tuple[PackedWaveform, float,
|
|
54
|
+
complex]]) -> PackedStack: ...
|
|
55
|
+
def to_bytes(self) -> bytes: ...
|
|
56
|
+
def events(self) -> tuple[tuple[PackedWaveform, float, complex], ...]: ...
|
|
57
|
+
def evaluate(self, x: np.ndarray, offset: complex = ...,
|
|
58
|
+
shift: float = ...) -> np.ndarray: ...
|
|
59
|
+
def simplified(self, shift: float = ..., offset: complex = ...,
|
|
60
|
+
eps: float = ...) -> PackedWaveform: ...
|
|
61
|
+
def combined(self, other: PackedStack) -> PackedStack: ...
|
|
62
|
+
def scaled(self, value: complex) -> PackedStack: ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def constant(value: int | float | complex) -> PackedWaveform: ...
|
|
66
|
+
def basic(opcode: int, args: tuple = ..., shift: float = ...) -> PackedWaveform: ...
|
|
67
|
+
def piecewise(bounds: tuple, expressions: tuple) -> PackedWaveform: ...
|
|
68
|
+
def sum_cores(cores: Iterable[PackedWaveform]) -> PackedWaveform: ...
|
|
69
|
+
def set_time_resolution(value: float) -> None: ...
|
|
70
|
+
def get_time_resolution() -> float: ...
|
|
71
|
+
def quantize_time(value: float) -> float: ...
|
|
72
|
+
def registerBaseFunc(*args, **kwargs): ...
|
|
73
|
+
def registerDerivative(*args, **kwargs): ...
|