waveforms 3.2.0__tar.gz → 3.3.2__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-3.2.0 → waveforms-3.3.2}/MANIFEST.in +0 -2
- {waveforms-3.2.0/waveforms.egg-info → waveforms-3.3.2}/PKG-INFO +18 -11
- {waveforms-3.2.0 → waveforms-3.3.2}/README.md +17 -10
- {waveforms-3.2.0 → waveforms-3.3.2}/setup.py +4 -0
- waveforms-3.3.2/tests/test_core.py +322 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/tests/test_waveform.py +49 -36
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/__init__.py +4 -15
- waveforms-3.3.2/waveforms/_cwaveform.c +4563 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/_cwaveform.h +50 -0
- waveforms-3.3.2/waveforms/_cwaveform.md +85 -0
- waveforms-3.3.2/waveforms/_waveform.pyi +131 -0
- waveforms-3.3.2/waveforms/_waveform.pyx +1125 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/version.py +1 -1
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/waveform.py +662 -1002
- {waveforms-3.2.0 → waveforms-3.3.2/waveforms.egg-info}/PKG-INFO +18 -11
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms.egg-info/SOURCES.txt +1 -3
- waveforms-3.2.0/src/waveform.c +0 -248
- waveforms-3.2.0/src/waveform.h +0 -171
- waveforms-3.2.0/tests/test_native.py +0 -242
- waveforms-3.2.0/waveforms/_cwaveform.c +0 -2034
- waveforms-3.2.0/waveforms/_cwaveform.md +0 -74
- waveforms-3.2.0/waveforms/_waveform.pyi +0 -171
- waveforms-3.2.0/waveforms/_waveform.pyx +0 -2823
- {waveforms-3.2.0 → waveforms-3.3.2}/LICENSE +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/pyproject.toml +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/setup.cfg +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/tests/test_wavevstack.py +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/Waveform.g4 +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/__main__.py +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/distortion.py +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/utils.py +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms/waveform_parser.py +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms.egg-info/dependency_links.txt +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms.egg-info/entry_points.txt +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/waveforms.egg-info/requires.txt +0 -0
- {waveforms-3.2.0 → waveforms-3.3.2}/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.2
|
|
3
|
+
Version: 3.3.2
|
|
4
4
|
Summary: Edit waveforms used in experiment
|
|
5
5
|
Author-email: feihoo87 <feihoo87@gmail.com>
|
|
6
6
|
Maintainer-email: feihoo87 <feihoo87@gmail.com>
|
|
@@ -98,11 +98,13 @@ plt.plot(t, y_wav(t))
|
|
|
98
98
|
plt.show()
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
###
|
|
101
|
+
### Unified binary representation
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
`Waveform` is the common base class for every signal object. `RealWaveform`
|
|
104
|
+
and `ComplexWaveform` are concrete waveforms; `WaveVStack` is the common stack
|
|
105
|
+
base, with `RealWaveVStack` and `ComplexWaveVStack` as its concrete forms.
|
|
106
|
+
The original construction style remains unchanged and automatically selects
|
|
107
|
+
the compact C core for common pulses and repeated-pulse stacks:
|
|
106
108
|
|
|
107
109
|
```python
|
|
108
110
|
import waveforms as wf
|
|
@@ -114,12 +116,17 @@ import waveforms as wf
|
|
|
114
116
|
pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
|
|
115
117
|
data = pulse.to_bytes()
|
|
116
118
|
restored = wf.Waveform.from_bytes(data)
|
|
119
|
+
|
|
120
|
+
assert isinstance(pulse, wf.Waveform)
|
|
121
|
+
stack = wf.WaveVStack([pulse, pulse >> 40e-9])
|
|
122
|
+
assert isinstance(stack, wf.RealWaveVStack)
|
|
123
|
+
assert isinstance(stack, wf.WaveVStack)
|
|
117
124
|
```
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
The C block format stores signed 64-bit ticks using a process-wide clock. The
|
|
127
|
+
default is 120 GHz. A different process-wide time resolution may be selected
|
|
128
|
+
before the first waveform is constructed; the same C representation and
|
|
129
|
+
evaluator continue to be used. The setting is locked by the first object.
|
|
123
130
|
|
|
124
131
|
`sample()` has integer-grid fast paths for 500 MHz, 1 GHz, 1.2 GHz, 2 GHz,
|
|
125
132
|
2.4 GHz, 2.5 GHz, 4 GHz, 6 GHz, 8 GHz, and 10 GHz. Real waveforms can be
|
|
@@ -131,7 +138,7 @@ pulse.stop = 100e-9
|
|
|
131
138
|
dac16 = pulse.sample(2_400_000_000, dtype=np.int16, full_scale=1.0)
|
|
132
139
|
```
|
|
133
140
|
|
|
134
|
-
The
|
|
141
|
+
The low-level cores store and evaluate real-valued signals only. Complex signals
|
|
135
142
|
are represented in Python as independent real and imaginary channels:
|
|
136
143
|
|
|
137
144
|
```python
|
|
@@ -142,7 +149,7 @@ stack = wf.ComplexWaveVStack([z, z >> 20e-9])
|
|
|
142
149
|
samples = stack(t) # complex NumPy array
|
|
143
150
|
```
|
|
144
151
|
|
|
145
|
-
|
|
152
|
+
Real waveforms and stacks therefore avoid complex storage and arithmetic for
|
|
146
153
|
the common real-valued case. `ComplexWaveform.real` and `.imag` expose the two
|
|
147
154
|
real channel waveforms.
|
|
148
155
|
|
|
@@ -54,11 +54,13 @@ plt.plot(t, y_wav(t))
|
|
|
54
54
|
plt.show()
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
###
|
|
57
|
+
### Unified binary representation
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
`Waveform` is the common base class for every signal object. `RealWaveform`
|
|
60
|
+
and `ComplexWaveform` are concrete waveforms; `WaveVStack` is the common stack
|
|
61
|
+
base, with `RealWaveVStack` and `ComplexWaveVStack` as its concrete forms.
|
|
62
|
+
The original construction style remains unchanged and automatically selects
|
|
63
|
+
the compact C core for common pulses and repeated-pulse stacks:
|
|
62
64
|
|
|
63
65
|
```python
|
|
64
66
|
import waveforms as wf
|
|
@@ -70,12 +72,17 @@ import waveforms as wf
|
|
|
70
72
|
pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
|
|
71
73
|
data = pulse.to_bytes()
|
|
72
74
|
restored = wf.Waveform.from_bytes(data)
|
|
75
|
+
|
|
76
|
+
assert isinstance(pulse, wf.Waveform)
|
|
77
|
+
stack = wf.WaveVStack([pulse, pulse >> 40e-9])
|
|
78
|
+
assert isinstance(stack, wf.RealWaveVStack)
|
|
79
|
+
assert isinstance(stack, wf.WaveVStack)
|
|
73
80
|
```
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
The C block format stores signed 64-bit ticks using a process-wide clock. The
|
|
83
|
+
default is 120 GHz. A different process-wide time resolution may be selected
|
|
84
|
+
before the first waveform is constructed; the same C representation and
|
|
85
|
+
evaluator continue to be used. The setting is locked by the first object.
|
|
79
86
|
|
|
80
87
|
`sample()` has integer-grid fast paths for 500 MHz, 1 GHz, 1.2 GHz, 2 GHz,
|
|
81
88
|
2.4 GHz, 2.5 GHz, 4 GHz, 6 GHz, 8 GHz, and 10 GHz. Real waveforms can be
|
|
@@ -87,7 +94,7 @@ pulse.stop = 100e-9
|
|
|
87
94
|
dac16 = pulse.sample(2_400_000_000, dtype=np.int16, full_scale=1.0)
|
|
88
95
|
```
|
|
89
96
|
|
|
90
|
-
The
|
|
97
|
+
The low-level cores store and evaluate real-valued signals only. Complex signals
|
|
91
98
|
are represented in Python as independent real and imaginary channels:
|
|
92
99
|
|
|
93
100
|
```python
|
|
@@ -98,7 +105,7 @@ stack = wf.ComplexWaveVStack([z, z >> 20e-9])
|
|
|
98
105
|
samples = stack(t) # complex NumPy array
|
|
99
106
|
```
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
Real waveforms and stacks therefore avoid complex storage and arithmetic for
|
|
102
109
|
the common real-valued case. `ComplexWaveform.real` and `.imag` expose the two
|
|
103
110
|
real channel waveforms.
|
|
104
111
|
|
|
@@ -39,15 +39,19 @@ def get_extensions():
|
|
|
39
39
|
sources = [os.path.join(dirpath, filename)]
|
|
40
40
|
include_dirs = []
|
|
41
41
|
extra_link_args = []
|
|
42
|
+
libraries = []
|
|
42
43
|
if filename == '_waveform.pyx':
|
|
43
44
|
sources.append(os.path.join(
|
|
44
45
|
'waveforms', '_cwaveform.c'))
|
|
45
46
|
include_dirs.append('waveforms')
|
|
46
47
|
if sys.platform == 'darwin':
|
|
47
48
|
extra_link_args.extend(['-framework', 'Accelerate'])
|
|
49
|
+
elif sys.platform.startswith('linux'):
|
|
50
|
+
libraries.append('m')
|
|
48
51
|
extensions.append(
|
|
49
52
|
Extension(module_name(dirpath, filename), sources,
|
|
50
53
|
include_dirs=include_dirs,
|
|
54
|
+
libraries=libraries,
|
|
51
55
|
extra_link_args=extra_link_args))
|
|
52
56
|
|
|
53
57
|
return extensions
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import waveforms as wf
|
|
8
|
+
import waveforms._waveform as core
|
|
9
|
+
from waveforms._waveform import quantize_samples
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RATE = 2_400_000_000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _pulse():
|
|
16
|
+
return (
|
|
17
|
+
0.8 * wf.gaussian(20e-9) * wf.cos(2 * np.pi * 100e6)
|
|
18
|
+
+ 0.15 * wf.gaussian(12e-9) * wf.sin(2 * np.pi * 180e6, 0.2)
|
|
19
|
+
+ 0.05 * wf.square(6e-9)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _stack(count, spacing=40e-9):
|
|
24
|
+
pulse = 0.8 * wf.gaussian(20e-9) * wf.cos(2 * np.pi * 100e6)
|
|
25
|
+
stack = wf.WaveVStack([
|
|
26
|
+
pulse >> (index * spacing) for index in range(count)
|
|
27
|
+
])
|
|
28
|
+
stack.start = -20e-9
|
|
29
|
+
stack.stop = count * spacing
|
|
30
|
+
stack.sample_rate = RATE
|
|
31
|
+
return stack
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_unified_type_hierarchy_and_removed_legacy_api():
|
|
35
|
+
real = wf.gaussian(20e-9)
|
|
36
|
+
complex_wave = real + 0.5j * real
|
|
37
|
+
real_stack = wf.WaveVStack([real])
|
|
38
|
+
complex_stack = wf.ComplexWaveVStack([complex_wave])
|
|
39
|
+
|
|
40
|
+
assert isinstance(real, wf.RealWaveform)
|
|
41
|
+
assert isinstance(real_stack, wf.RealWaveVStack)
|
|
42
|
+
assert isinstance(real_stack, wf.WaveVStack)
|
|
43
|
+
assert isinstance(complex_stack, wf.WaveVStack)
|
|
44
|
+
assert all(isinstance(value, wf.Waveform) for value in (
|
|
45
|
+
real, complex_wave, real_stack, complex_stack
|
|
46
|
+
))
|
|
47
|
+
assert not any(name.startswith("native_") for name in dir(wf))
|
|
48
|
+
assert not any(name.startswith("Native") for name in dir(wf))
|
|
49
|
+
assert not hasattr(core, "PackedWaveform")
|
|
50
|
+
assert not hasattr(core, "PackedStack")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_canonical_real_waveform_equality_and_hash():
|
|
54
|
+
a = wf.gaussian(12e-9)
|
|
55
|
+
b = wf.cos(2 * np.pi * 100e6)
|
|
56
|
+
|
|
57
|
+
assert a + b == b + a
|
|
58
|
+
assert hash(a + b) == hash(b + a)
|
|
59
|
+
assert a + a == 2 * a
|
|
60
|
+
assert a * a == a ** 2
|
|
61
|
+
assert (a + b).simplify().to_bytes() == (b + a).simplify().to_bytes()
|
|
62
|
+
assert wf.WaveVStack([a, b]) == wf.WaveVStack([b, a])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_c_symbolic_frequency_filter():
|
|
66
|
+
envelope = wf.gaussian(20e-9)
|
|
67
|
+
low = envelope * wf.cos(2 * np.pi * 100e6)
|
|
68
|
+
high = 0.25 * envelope * wf.sin(2 * np.pi * 300e6)
|
|
69
|
+
baseband = 0.1 * envelope
|
|
70
|
+
signal = low + high + baseband
|
|
71
|
+
x = np.linspace(-20e-9, 20e-9, 4097)
|
|
72
|
+
|
|
73
|
+
assert np.allclose(
|
|
74
|
+
signal.filter(2 * np.pi * 200e6, 2 * np.pi * 400e6)(x),
|
|
75
|
+
high(x),
|
|
76
|
+
)
|
|
77
|
+
assert np.allclose(
|
|
78
|
+
signal.filter(0, 2 * np.pi * 200e6)(x),
|
|
79
|
+
(low + baseband)(x),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_c_symbolic_filter_reduces_trigonometric_products_and_powers():
|
|
84
|
+
x = np.linspace(-1.0, 1.0, 4097)
|
|
85
|
+
envelope = wf.gaussian(2.0)
|
|
86
|
+
first_frequency = 9.0
|
|
87
|
+
second_frequency = 7.0
|
|
88
|
+
first_phase = 0.31
|
|
89
|
+
second_phase = -0.27
|
|
90
|
+
cutoff = 5.0
|
|
91
|
+
|
|
92
|
+
first_cos = wf.cos(first_frequency, first_phase)
|
|
93
|
+
first_sin = wf.sin(first_frequency, first_phase)
|
|
94
|
+
second_cos = wf.cos(second_frequency, second_phase)
|
|
95
|
+
second_sin = wf.sin(second_frequency, second_phase)
|
|
96
|
+
difference = first_frequency - second_frequency
|
|
97
|
+
phase_difference = first_phase - second_phase
|
|
98
|
+
|
|
99
|
+
assert np.allclose(
|
|
100
|
+
(2 * envelope * first_cos * second_cos).filter(high=cutoff)(x),
|
|
101
|
+
(envelope * wf.cos(difference, phase_difference))(x),
|
|
102
|
+
atol=2e-11,
|
|
103
|
+
)
|
|
104
|
+
assert np.allclose(
|
|
105
|
+
(2 * envelope * first_cos * second_sin).filter(high=cutoff)(x),
|
|
106
|
+
(-envelope * wf.sin(difference, phase_difference))(x),
|
|
107
|
+
atol=2e-11,
|
|
108
|
+
)
|
|
109
|
+
assert np.allclose(
|
|
110
|
+
(2 * envelope * first_sin * second_sin).filter(high=cutoff)(x),
|
|
111
|
+
(envelope * wf.cos(difference, phase_difference))(x),
|
|
112
|
+
atol=2e-11,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
powered = wf.cos(first_frequency, first_phase) ** 2
|
|
116
|
+
assert np.allclose(powered.filter(high=cutoff)(x), 0.5, atol=2e-11)
|
|
117
|
+
assert np.allclose(
|
|
118
|
+
powered.filter(2 * first_frequency, np.inf)(x),
|
|
119
|
+
(0.5 * wf.cos(2 * first_frequency, 2 * first_phase))(x),
|
|
120
|
+
atol=2e-11,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
retained = (1.5e-15 * first_cos).filter(eps=1e-15)
|
|
124
|
+
discarded = (0.5e-15 * first_cos).filter(eps=1e-15)
|
|
125
|
+
assert np.max(np.abs(retained(x))) > 1e-15
|
|
126
|
+
assert np.array_equal(discarded(x), np.zeros_like(x))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_down_conversion_filter_matches_historic_symbolic_behavior():
|
|
130
|
+
x = np.linspace(-100.0, 100.0, 10001)
|
|
131
|
+
envelope = wf.gaussian(100.0)
|
|
132
|
+
radio_frequency = 92.0451
|
|
133
|
+
local_frequency = 92.0
|
|
134
|
+
phase = 0.32
|
|
135
|
+
rf, _ = wf.mixing(
|
|
136
|
+
envelope, freq=radio_frequency, phase=phase, DRAGScaling=0.0,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
i = (2 * rf * wf.cos(-2 * np.pi * local_frequency)).filter(
|
|
140
|
+
high=2 * np.pi * local_frequency,
|
|
141
|
+
)
|
|
142
|
+
q = (2 * rf * wf.sin(-2 * np.pi * local_frequency)).filter(
|
|
143
|
+
high=2 * np.pi * local_frequency,
|
|
144
|
+
)
|
|
145
|
+
difference = 2 * np.pi * (radio_frequency - local_frequency)
|
|
146
|
+
|
|
147
|
+
assert np.allclose(
|
|
148
|
+
i(x), (envelope * wf.cos(difference, -phase))(x), atol=2e-9,
|
|
149
|
+
)
|
|
150
|
+
assert np.allclose(
|
|
151
|
+
q(x), (envelope * wf.sin(difference, -phase))(x), atol=2e-9,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_wave_block_roundtrip_pickle_and_numerics():
|
|
156
|
+
actual = _pulse()
|
|
157
|
+
actual.start = -20e-9
|
|
158
|
+
actual.stop = 20e-9
|
|
159
|
+
actual.sample_rate = RATE
|
|
160
|
+
actual.label = "pulse"
|
|
161
|
+
data = actual.to_bytes()
|
|
162
|
+
restored = wf.Waveform.from_bytes(data)
|
|
163
|
+
x = np.linspace(-20e-9, 20e-9, 200_000, endpoint=False)
|
|
164
|
+
|
|
165
|
+
assert data[:4] == b"WNF4"
|
|
166
|
+
assert actual.to_bytes() is data
|
|
167
|
+
assert restored.to_bytes() is data
|
|
168
|
+
assert restored == wf.Waveform.from_bytes(data)
|
|
169
|
+
assert wf.RealWaveform.from_bytes(data) == restored
|
|
170
|
+
assert np.array_equal(restored(x), actual(x))
|
|
171
|
+
|
|
172
|
+
unpickled = pickle.loads(pickle.dumps(actual, protocol=5))
|
|
173
|
+
assert unpickled == actual
|
|
174
|
+
assert unpickled.start == actual.start
|
|
175
|
+
assert unpickled.stop == actual.stop
|
|
176
|
+
assert unpickled.sample_rate == actual.sample_rate
|
|
177
|
+
assert unpickled.label == actual.label
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@pytest.mark.parametrize("dtype", [np.float64, np.int16, np.int32])
|
|
181
|
+
def test_device_grid_sampling_and_output_buffer(dtype):
|
|
182
|
+
actual = _pulse()
|
|
183
|
+
actual.start = -20e-9
|
|
184
|
+
actual.stop = 20e-9
|
|
185
|
+
actual.sample_rate = RATE
|
|
186
|
+
result = actual.sample(dtype=dtype)
|
|
187
|
+
target = np.empty_like(result)
|
|
188
|
+
assert actual.sample(dtype=dtype, out=target) is target
|
|
189
|
+
assert np.array_equal(target, result)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_complex_block_roundtrip_and_pickle():
|
|
193
|
+
pulse = _pulse()
|
|
194
|
+
complex_wave = pulse + 0.5j * (pulse >> 2e-9)
|
|
195
|
+
complex_wave.start = -20e-9
|
|
196
|
+
complex_wave.stop = 20e-9
|
|
197
|
+
complex_wave.sample_rate = RATE
|
|
198
|
+
complex_wave.label = "iq"
|
|
199
|
+
x = np.linspace(-20e-9, 20e-9, 8192, endpoint=False)
|
|
200
|
+
expected = pulse(x) + 0.5j * (pulse >> 2e-9)(x)
|
|
201
|
+
|
|
202
|
+
data = complex_wave.to_bytes()
|
|
203
|
+
restored = wf.Waveform.from_bytes(data)
|
|
204
|
+
assert data[:4] == b"CWF1"
|
|
205
|
+
assert isinstance(restored, wf.ComplexWaveform)
|
|
206
|
+
assert np.allclose(restored(x), expected, rtol=2e-15, atol=2e-15)
|
|
207
|
+
unpickled = pickle.loads(pickle.dumps(complex_wave, protocol=5))
|
|
208
|
+
assert np.allclose(unpickled(x), expected, rtol=2e-15, atol=2e-15)
|
|
209
|
+
assert unpickled.label == complex_wave.label
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_stack_sampling_roundtrip_shift_offset_and_pickle():
|
|
213
|
+
actual = _stack(100)
|
|
214
|
+
data = actual.to_bytes()
|
|
215
|
+
restored = wf.WaveVStack.from_bytes(data)
|
|
216
|
+
restored.start = actual.start
|
|
217
|
+
restored.stop = actual.stop
|
|
218
|
+
restored.sample_rate = actual.sample_rate
|
|
219
|
+
|
|
220
|
+
assert data[:4] == b"WNS4"
|
|
221
|
+
assert restored.to_bytes() is data
|
|
222
|
+
assert isinstance(wf.RealWaveVStack.from_bytes(data),
|
|
223
|
+
wf.RealWaveVStack)
|
|
224
|
+
assert np.array_equal(restored.sample(), actual.sample())
|
|
225
|
+
assert np.array_equal(restored.sample(dtype=np.int16),
|
|
226
|
+
actual.sample(dtype=np.int16))
|
|
227
|
+
|
|
228
|
+
transformed = (actual >> 5e-9) + 0.125
|
|
229
|
+
transformed_restored = wf.WaveVStack.from_bytes(transformed.to_bytes())
|
|
230
|
+
x = np.linspace(-20e-9, 4e-6, 10_000, endpoint=False)
|
|
231
|
+
assert np.allclose(transformed_restored(x), transformed(x),
|
|
232
|
+
rtol=2e-15, atol=2e-15)
|
|
233
|
+
|
|
234
|
+
actual.label = "events"
|
|
235
|
+
unpickled = pickle.loads(pickle.dumps(actual, protocol=5))
|
|
236
|
+
assert unpickled == actual
|
|
237
|
+
assert unpickled.sample_rate == actual.sample_rate
|
|
238
|
+
assert unpickled.label == actual.label
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@pytest.mark.parametrize("dtype,bits", [
|
|
242
|
+
(np.float64, 0),
|
|
243
|
+
(np.int16, 16),
|
|
244
|
+
(np.int32, 32),
|
|
245
|
+
])
|
|
246
|
+
def test_stack_sample_plan_matches_direct_sampler(dtype, bits):
|
|
247
|
+
pulse = wf.gaussian(20e-9) * wf.cos(2 * np.pi * 100e6)
|
|
248
|
+
scales = (0.25, 0.5, 1.0)
|
|
249
|
+
actual = wf.WaveVStack([
|
|
250
|
+
scales[index % len(scales)] * pulse >> (index * 40e-9)
|
|
251
|
+
for index in range(75)
|
|
252
|
+
])
|
|
253
|
+
actual.start = -20e-9
|
|
254
|
+
actual.stop = 75 * 40e-9
|
|
255
|
+
actual.sample_rate = RATE
|
|
256
|
+
actual.offset = 0.071
|
|
257
|
+
|
|
258
|
+
first = actual.sample(dtype=dtype)
|
|
259
|
+
cache_key, sample_plan = actual._sample_plan_cache
|
|
260
|
+
target = np.empty_like(first)
|
|
261
|
+
result = actual.sample(dtype=dtype, out=target)
|
|
262
|
+
direct = actual._core.sample(
|
|
263
|
+
cache_key[0], cache_key[1], cache_key[2], cache_key[3],
|
|
264
|
+
cache_key[4], actual.offset, bits, 1.0,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
assert result is target
|
|
268
|
+
assert sample_plan.count == len(result)
|
|
269
|
+
assert sample_plan.group_count == 1
|
|
270
|
+
assert sample_plan.non_overlapping
|
|
271
|
+
assert np.array_equal(result, direct)
|
|
272
|
+
assert actual._sample_plan_cache[1] is sample_plan
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_overlapping_stack_quantization_and_empty_stack():
|
|
276
|
+
actual = _stack(50, spacing=10e-9)
|
|
277
|
+
for dtype, bits in ((np.int16, 16), (np.int32, 32)):
|
|
278
|
+
expected = quantize_samples(actual.sample(), bits)
|
|
279
|
+
assert np.array_equal(actual.sample(dtype=dtype), expected)
|
|
280
|
+
|
|
281
|
+
empty = wf.WaveVStack([])
|
|
282
|
+
empty.start = -1e-9
|
|
283
|
+
empty.stop = 1e-9
|
|
284
|
+
empty.sample_rate = RATE
|
|
285
|
+
assert empty.to_bytes() == b"WNS4\x02\x00\x00\x00" + b"\x00" * 8
|
|
286
|
+
assert not np.any(empty.sample())
|
|
287
|
+
assert not np.any(wf.WaveVStack.from_bytes(empty.to_bytes())(
|
|
288
|
+
np.linspace(-1e-9, 1e-9, 17)
|
|
289
|
+
))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_blocks_reject_malformed_layouts():
|
|
293
|
+
wave_data = bytearray(_pulse().to_bytes())
|
|
294
|
+
with pytest.raises(ValueError):
|
|
295
|
+
wf.Waveform.from_bytes(wave_data[:20])
|
|
296
|
+
wave_data[0] = 0
|
|
297
|
+
with pytest.raises(ValueError):
|
|
298
|
+
wf.Waveform.from_bytes(wave_data)
|
|
299
|
+
|
|
300
|
+
wave_data = bytearray(_pulse().to_bytes())
|
|
301
|
+
wave_data[24] = 255
|
|
302
|
+
with pytest.raises(ValueError):
|
|
303
|
+
wf.Waveform.from_bytes(wave_data)
|
|
304
|
+
|
|
305
|
+
stack_data = bytearray(_stack(2).to_bytes())
|
|
306
|
+
with pytest.raises(ValueError):
|
|
307
|
+
wf.WaveVStack.from_bytes(stack_data[:-1])
|
|
308
|
+
event_offset = len(stack_data) - 40
|
|
309
|
+
stack_data[event_offset:event_offset + 4] = (99).to_bytes(4, "little")
|
|
310
|
+
malformed = wf.WaveVStack.from_bytes(stack_data)
|
|
311
|
+
with pytest.raises(ValueError):
|
|
312
|
+
_ = malformed._core.event_count
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_format_is_language_neutral_and_uses_the_global_clock():
|
|
316
|
+
data = _pulse().to_bytes()
|
|
317
|
+
assert data[:4] == b"WNF4"
|
|
318
|
+
assert int.from_bytes(data[4:6], "little") == 2
|
|
319
|
+
assert wf.get_time_resolution() == 1 / 120_000_000_000
|
|
320
|
+
package_dir = Path(wf.__file__).resolve().parent
|
|
321
|
+
assert (package_dir / "_cwaveform.c").is_file()
|
|
322
|
+
assert (package_dir / "_cwaveform.h").is_file()
|
|
@@ -15,7 +15,8 @@ from waveforms._waveform import (
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
PUBLIC_NAMES = {
|
|
18
|
-
"ComplexWaveform", "ComplexWaveVStack", "D", "
|
|
18
|
+
"ComplexWaveform", "ComplexWaveVStack", "D", "RealWaveform",
|
|
19
|
+
"RealWaveVStack", "Waveform", "WaveVStack",
|
|
19
20
|
"chirp", "const", "cos", "cosh",
|
|
20
21
|
"coshPulse", "cosPulse", "cut", "drag", "drag_sin", "drag_sinx",
|
|
21
22
|
"exp", "function",
|
|
@@ -51,19 +52,13 @@ def test_affine_algebra_matches_eager_materialization():
|
|
|
51
52
|
right = -1.25 * (wf.cos(2.3, 0.4) << 0.35)
|
|
52
53
|
|
|
53
54
|
actual_add = left + right
|
|
54
|
-
eager_add = wf.Waveform._from_core(
|
|
55
|
-
left._materialized_core().add(right._materialized_core())
|
|
56
|
-
)
|
|
57
55
|
actual_mul = left * right
|
|
58
|
-
eager_mul = wf.Waveform._from_core(
|
|
59
|
-
left._materialized_core().mul(right._materialized_core())
|
|
60
|
-
)
|
|
61
56
|
|
|
62
57
|
x = np.linspace(-3, 4, 8193)
|
|
63
|
-
assert
|
|
64
|
-
|
|
65
|
-
assert np.
|
|
66
|
-
|
|
58
|
+
assert np.allclose(actual_add(x), left(x) + right(x),
|
|
59
|
+
rtol=2e-15, atol=2e-15)
|
|
60
|
+
assert np.allclose(actual_mul(x), left(x) * right(x),
|
|
61
|
+
rtol=2e-15, atol=2e-15)
|
|
67
62
|
assert wf.Waveform.from_bytes(actual_add.to_bytes()) == actual_add
|
|
68
63
|
assert wf.Waveform.from_bytes(actual_mul.to_bytes()) == actual_mul
|
|
69
64
|
|
|
@@ -272,7 +267,7 @@ def test_wavevstack_template_sharing_operations_and_roundtrip():
|
|
|
272
267
|
waves = [template >> (i * 80e-9) for i in range(1000)]
|
|
273
268
|
stack = wf.WaveVStack(waves)
|
|
274
269
|
|
|
275
|
-
# One template plus three
|
|
270
|
+
# One template plus three contiguous event arrays; substantially smaller than
|
|
276
271
|
# serializing 1000 complete shifted expression trees.
|
|
277
272
|
assert len(stack.to_bytes()) < 32_000
|
|
278
273
|
x = np.linspace(0, 80e-6, 16_000, endpoint=False)
|
|
@@ -410,30 +405,26 @@ def test_complex_interpolation_and_stack_roundtrip():
|
|
|
410
405
|
wf.WaveVStack(waves)
|
|
411
406
|
|
|
412
407
|
|
|
413
|
-
def
|
|
408
|
+
def test_real_backend_promotes_complex_coefficients_and_scales():
|
|
414
409
|
wav = wf.gaussian(1.0)
|
|
415
410
|
stack = wf.WaveVStack([wav, 2 * (wav >> 3)])
|
|
416
|
-
assert wav.to_bytes()[:4] == b"
|
|
417
|
-
assert stack.to_bytes()[:4] == b"
|
|
418
|
-
assert all(isinstance(scale, float)
|
|
419
|
-
for _, _, scale in stack._stack.events())
|
|
411
|
+
assert wav.to_bytes()[:4] == b"WNF4"
|
|
412
|
+
assert stack.to_bytes()[:4] == b"WNS4"
|
|
420
413
|
assert stack(np.linspace(-1, 4, 101)).dtype == np.float64
|
|
421
414
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
with pytest.raises(TypeError,
|
|
425
|
-
stack._stack.scaled(1j)
|
|
426
|
-
with pytest.raises(TypeError, match="positions must be real"):
|
|
415
|
+
assert isinstance(wav * 1j, wf.ComplexWaveform)
|
|
416
|
+
assert isinstance(stack * 1j, wf.ComplexWaveVStack)
|
|
417
|
+
with pytest.raises((TypeError, ValueError)):
|
|
427
418
|
wav(np.array([0.0 + 0.0j]))
|
|
428
419
|
|
|
429
420
|
|
|
430
421
|
def test_time_is_global_configuration_and_blocks_store_integer_ticks():
|
|
431
422
|
assert wf.get_time_resolution() == 1 / 120_000_000_000
|
|
432
423
|
wav = wf.square(4e-9) >> 11e-9
|
|
433
|
-
|
|
434
|
-
assert
|
|
435
|
-
assert
|
|
436
|
-
assert wav.
|
|
424
|
+
data = wav.to_bytes()
|
|
425
|
+
assert data[:4] == b"WNF4"
|
|
426
|
+
assert wav.begin == 9e-9
|
|
427
|
+
assert wav.end == 13e-9
|
|
437
428
|
with pytest.raises(RuntimeError):
|
|
438
429
|
wf.set_time_resolution(1e-15)
|
|
439
430
|
|
|
@@ -441,7 +432,7 @@ def test_time_is_global_configuration_and_blocks_store_integer_ticks():
|
|
|
441
432
|
"import waveforms as w; "
|
|
442
433
|
"w.set_time_resolution(1e-15); "
|
|
443
434
|
"x=w.square(4e-12); "
|
|
444
|
-
"print(w.get_time_resolution(), x.
|
|
435
|
+
"print(w.get_time_resolution(), round(x.begin / 1e-15))"
|
|
445
436
|
)
|
|
446
437
|
result = subprocess.run([sys.executable, "-c", code], check=True,
|
|
447
438
|
capture_output=True, text=True)
|
|
@@ -482,6 +473,15 @@ def test_fixed_width_quantization_supported_sampling_and_fallback():
|
|
|
482
473
|
1073741824, 2147483647, 2147483647],
|
|
483
474
|
)
|
|
484
475
|
|
|
476
|
+
# Exercise the fused SIMD path, including its historic half-away-from-zero
|
|
477
|
+
# rounding rule and multidimensional ``out`` handling.
|
|
478
|
+
half_steps = np.array([0.5, -0.5, 1.5, -1.5] * 8) / 32768.0
|
|
479
|
+
target = np.empty((8, 4), dtype=np.int16)
|
|
480
|
+
assert quantize_samples(half_steps.reshape(8, 4), 16, out=target) is target
|
|
481
|
+
assert np.array_equal(
|
|
482
|
+
target.reshape(-1), np.array([1, -1, 2, -2] * 8, dtype=np.int16)
|
|
483
|
+
)
|
|
484
|
+
|
|
485
485
|
wav = 0.8 * wf.gaussian(20e-9)
|
|
486
486
|
wav.start = -20e-9
|
|
487
487
|
wav.stop = 20e-9
|
|
@@ -502,6 +502,21 @@ def test_fixed_width_quantization_supported_sampling_and_fallback():
|
|
|
502
502
|
assert np.array_equal(wav.sample(odd_rate), wav(legacy_grid))
|
|
503
503
|
|
|
504
504
|
|
|
505
|
+
def test_simd_node_evaluation_matches_scalar_boundaries():
|
|
506
|
+
positions = np.linspace(-2.0, 2.0, 513)
|
|
507
|
+
waves = (
|
|
508
|
+
wf.t(),
|
|
509
|
+
wf.exp(0.2),
|
|
510
|
+
wf.sinc(1.3),
|
|
511
|
+
wf.cosh(0.3),
|
|
512
|
+
wf.sinh(0.3),
|
|
513
|
+
(wf.gaussian(4.0, 1.5) * wf.cos(2.2)) ** 2,
|
|
514
|
+
)
|
|
515
|
+
for wave in waves:
|
|
516
|
+
expected = np.array([wave(float(position)) for position in positions])
|
|
517
|
+
assert np.allclose(wave(positions), expected, rtol=1e-12, atol=1e-13)
|
|
518
|
+
|
|
519
|
+
|
|
505
520
|
def test_integer_sampling_fast_paths_are_bit_exact_and_pickle_safe():
|
|
506
521
|
rate = 2_400_000_000
|
|
507
522
|
pulse = 0.8 * wf.gaussian(20e-9) * wf.cos(2 * np.pi * 100e6)
|
|
@@ -576,14 +591,8 @@ def test_wavevstack_integer_grid_template_sampling_and_complex_iq():
|
|
|
576
591
|
stack.start = -10e-9
|
|
577
592
|
stack.stop = 110e-9
|
|
578
593
|
actual = stack.sample(rate)
|
|
579
|
-
|
|
580
|
-
expected =
|
|
581
|
-
for core, delay, scale in stack._stack.events():
|
|
582
|
-
delay_tick = time_to_tick(delay + stack.shift)
|
|
583
|
-
local_grid = sample_grid(
|
|
584
|
-
start_tick - delay_tick, len(actual), *step
|
|
585
|
-
)
|
|
586
|
-
expected += scale * core.evaluate(local_grid)
|
|
594
|
+
x = sample_grid(time_to_tick(stack.start), len(actual), *step)
|
|
595
|
+
expected = stack(x)
|
|
587
596
|
assert np.allclose(actual, expected, rtol=2e-15, atol=2e-15)
|
|
588
597
|
|
|
589
598
|
complex_stack = wf.ComplexWaveVStack(stack, -0.5 * stack)
|
|
@@ -595,12 +604,16 @@ def test_wavevstack_integer_grid_template_sampling_and_complex_iq():
|
|
|
595
604
|
assert np.array_equal(q_data, quantize_samples(complex_samples.imag, 16))
|
|
596
605
|
|
|
597
606
|
|
|
598
|
-
def
|
|
607
|
+
def test_single_c_backend_has_no_waveform2_or_packed_core():
|
|
599
608
|
package = Path(wf.__file__).parent
|
|
600
609
|
python_source = (package / "waveform.py").read_text()
|
|
601
610
|
cython_source = (package / "_waveform.pyx").read_text()
|
|
602
611
|
assert "waveform2" not in python_source
|
|
603
612
|
assert "_waveform2" not in cython_source
|
|
613
|
+
assert "PackedWaveform" not in python_source + cython_source
|
|
614
|
+
assert "PackedStack" not in python_source + cython_source
|
|
615
|
+
assert "WFM3" not in python_source + cython_source
|
|
616
|
+
assert "WVS3" not in python_source + cython_source
|
|
604
617
|
assert not (package / "waveform2.py").exists()
|
|
605
618
|
assert not (package / "_waveform2.pyx").exists()
|
|
606
619
|
|
|
@@ -5,11 +5,10 @@ from .waveform import (
|
|
|
5
5
|
ComplexWaveform,
|
|
6
6
|
ComplexWaveVStack,
|
|
7
7
|
D,
|
|
8
|
+
RealWaveform,
|
|
9
|
+
RealWaveVStack,
|
|
8
10
|
Waveform,
|
|
9
11
|
WaveVStack,
|
|
10
|
-
NativeWaveform,
|
|
11
|
-
NativeWaveVStack,
|
|
12
|
-
NativeComplexWaveform,
|
|
13
12
|
chirp,
|
|
14
13
|
const,
|
|
15
14
|
cos,
|
|
@@ -44,19 +43,11 @@ from .waveform import (
|
|
|
44
43
|
t,
|
|
45
44
|
wave_eval,
|
|
46
45
|
zero,
|
|
47
|
-
native_zero,
|
|
48
|
-
native_one,
|
|
49
|
-
native_const,
|
|
50
|
-
native_gaussian,
|
|
51
|
-
native_cos,
|
|
52
|
-
native_sin,
|
|
53
|
-
native_square,
|
|
54
|
-
native_format_description,
|
|
55
46
|
)
|
|
56
47
|
|
|
57
48
|
__all__ = [
|
|
58
|
-
"ComplexWaveform", "ComplexWaveVStack", "D", "
|
|
59
|
-
"
|
|
49
|
+
"ComplexWaveform", "ComplexWaveVStack", "D", "RealWaveform",
|
|
50
|
+
"RealWaveVStack", "Waveform", "WaveVStack",
|
|
60
51
|
"chirp", "const", "cos", "cosh",
|
|
61
52
|
"coshPulse", "cosPulse", "cut", "drag", "drag_sin", "drag_sinx",
|
|
62
53
|
"e", "exp", "function",
|
|
@@ -65,6 +56,4 @@ __all__ = [
|
|
|
65
56
|
"registerBaseFunc", "registerDerivative", "samplingPoints",
|
|
66
57
|
"set_time_resolution", "sign", "sin", "sinc", "sinh", "square",
|
|
67
58
|
"step", "t", "wave_eval", "zero", "__version__",
|
|
68
|
-
"native_zero", "native_one", "native_const", "native_gaussian",
|
|
69
|
-
"native_cos", "native_sin", "native_square", "native_format_description",
|
|
70
59
|
]
|