waveforms 3.2.0__tar.gz → 3.3.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.
Files changed (36) hide show
  1. {waveforms-3.2.0 → waveforms-3.3.0}/MANIFEST.in +0 -2
  2. {waveforms-3.2.0/waveforms.egg-info → waveforms-3.3.0}/PKG-INFO +18 -11
  3. {waveforms-3.2.0 → waveforms-3.3.0}/README.md +17 -10
  4. waveforms-3.3.0/tests/test_core.py +250 -0
  5. {waveforms-3.2.0 → waveforms-3.3.0}/tests/test_waveform.py +25 -36
  6. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/__init__.py +4 -15
  7. waveforms-3.3.0/waveforms/_cwaveform.c +3780 -0
  8. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/_cwaveform.h +47 -0
  9. waveforms-3.3.0/waveforms/_cwaveform.md +85 -0
  10. waveforms-3.3.0/waveforms/_waveform.pyi +131 -0
  11. waveforms-3.3.0/waveforms/_waveform.pyx +1152 -0
  12. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/version.py +1 -1
  13. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/waveform.py +662 -1002
  14. {waveforms-3.2.0 → waveforms-3.3.0/waveforms.egg-info}/PKG-INFO +18 -11
  15. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms.egg-info/SOURCES.txt +1 -3
  16. waveforms-3.2.0/src/waveform.c +0 -248
  17. waveforms-3.2.0/src/waveform.h +0 -171
  18. waveforms-3.2.0/tests/test_native.py +0 -242
  19. waveforms-3.2.0/waveforms/_cwaveform.c +0 -2034
  20. waveforms-3.2.0/waveforms/_cwaveform.md +0 -74
  21. waveforms-3.2.0/waveforms/_waveform.pyi +0 -171
  22. waveforms-3.2.0/waveforms/_waveform.pyx +0 -2823
  23. {waveforms-3.2.0 → waveforms-3.3.0}/LICENSE +0 -0
  24. {waveforms-3.2.0 → waveforms-3.3.0}/pyproject.toml +0 -0
  25. {waveforms-3.2.0 → waveforms-3.3.0}/setup.cfg +0 -0
  26. {waveforms-3.2.0 → waveforms-3.3.0}/setup.py +0 -0
  27. {waveforms-3.2.0 → waveforms-3.3.0}/tests/test_wavevstack.py +0 -0
  28. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/Waveform.g4 +0 -0
  29. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/__main__.py +0 -0
  30. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/distortion.py +0 -0
  31. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/utils.py +0 -0
  32. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms/waveform_parser.py +0 -0
  33. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms.egg-info/dependency_links.txt +0 -0
  34. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms.egg-info/entry_points.txt +0 -0
  35. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms.egg-info/requires.txt +0 -0
  36. {waveforms-3.2.0 → waveforms-3.3.0}/waveforms.egg-info/top_level.txt +0 -0
@@ -1,5 +1,3 @@
1
- include src/*.h
2
- include src/*.c
3
1
  include waveforms/_cwaveform.h
4
2
  include waveforms/_cwaveform.c
5
3
  include waveforms/_cwaveform.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waveforms
3
- Version: 3.2.0
3
+ Version: 3.3.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>
@@ -98,11 +98,13 @@ plt.plot(t, y_wav(t))
98
98
  plt.show()
99
99
  ```
100
100
 
101
- ### Packed binary representation
101
+ ### Unified binary representation
102
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.
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
- Time resolution is process-wide and is not stored in each binary block. A
120
- process loading a block must therefore use the same resolution as the process
121
- that created it. The setting is locked when the first waveform object is
122
- created or loaded.
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 packed core stores and evaluates real-valued signals only. Complex signals
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
- `Waveform` and `WaveVStack` therefore avoid complex storage and arithmetic for
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
- ### Packed binary representation
57
+ ### Unified binary representation
58
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.
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
- Time resolution is process-wide and is not stored in each binary block. A
76
- process loading a block must therefore use the same resolution as the process
77
- that created it. The setting is locked when the first waveform object is
78
- created or loaded.
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 packed core stores and evaluates real-valued signals only. Complex signals
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
- `Waveform` and `WaveVStack` therefore avoid complex storage and arithmetic for
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
 
@@ -0,0 +1,250 @@
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_wave_block_roundtrip_pickle_and_numerics():
84
+ actual = _pulse()
85
+ actual.start = -20e-9
86
+ actual.stop = 20e-9
87
+ actual.sample_rate = RATE
88
+ actual.label = "pulse"
89
+ data = actual.to_bytes()
90
+ restored = wf.Waveform.from_bytes(data)
91
+ x = np.linspace(-20e-9, 20e-9, 200_000, endpoint=False)
92
+
93
+ assert data[:4] == b"WNF4"
94
+ assert actual.to_bytes() is data
95
+ assert restored.to_bytes() is data
96
+ assert restored == wf.Waveform.from_bytes(data)
97
+ assert wf.RealWaveform.from_bytes(data) == restored
98
+ assert np.array_equal(restored(x), actual(x))
99
+
100
+ unpickled = pickle.loads(pickle.dumps(actual, protocol=5))
101
+ assert unpickled == actual
102
+ assert unpickled.start == actual.start
103
+ assert unpickled.stop == actual.stop
104
+ assert unpickled.sample_rate == actual.sample_rate
105
+ assert unpickled.label == actual.label
106
+
107
+
108
+ @pytest.mark.parametrize("dtype", [np.float64, np.int16, np.int32])
109
+ def test_device_grid_sampling_and_output_buffer(dtype):
110
+ actual = _pulse()
111
+ actual.start = -20e-9
112
+ actual.stop = 20e-9
113
+ actual.sample_rate = RATE
114
+ result = actual.sample(dtype=dtype)
115
+ target = np.empty_like(result)
116
+ assert actual.sample(dtype=dtype, out=target) is target
117
+ assert np.array_equal(target, result)
118
+
119
+
120
+ def test_complex_block_roundtrip_and_pickle():
121
+ pulse = _pulse()
122
+ complex_wave = pulse + 0.5j * (pulse >> 2e-9)
123
+ complex_wave.start = -20e-9
124
+ complex_wave.stop = 20e-9
125
+ complex_wave.sample_rate = RATE
126
+ complex_wave.label = "iq"
127
+ x = np.linspace(-20e-9, 20e-9, 8192, endpoint=False)
128
+ expected = pulse(x) + 0.5j * (pulse >> 2e-9)(x)
129
+
130
+ data = complex_wave.to_bytes()
131
+ restored = wf.Waveform.from_bytes(data)
132
+ assert data[:4] == b"CWF1"
133
+ assert isinstance(restored, wf.ComplexWaveform)
134
+ assert np.allclose(restored(x), expected, rtol=2e-15, atol=2e-15)
135
+ unpickled = pickle.loads(pickle.dumps(complex_wave, protocol=5))
136
+ assert np.allclose(unpickled(x), expected, rtol=2e-15, atol=2e-15)
137
+ assert unpickled.label == complex_wave.label
138
+
139
+
140
+ def test_stack_sampling_roundtrip_shift_offset_and_pickle():
141
+ actual = _stack(100)
142
+ data = actual.to_bytes()
143
+ restored = wf.WaveVStack.from_bytes(data)
144
+ restored.start = actual.start
145
+ restored.stop = actual.stop
146
+ restored.sample_rate = actual.sample_rate
147
+
148
+ assert data[:4] == b"WNS4"
149
+ assert restored.to_bytes() is data
150
+ assert isinstance(wf.RealWaveVStack.from_bytes(data),
151
+ wf.RealWaveVStack)
152
+ assert np.array_equal(restored.sample(), actual.sample())
153
+ assert np.array_equal(restored.sample(dtype=np.int16),
154
+ actual.sample(dtype=np.int16))
155
+
156
+ transformed = (actual >> 5e-9) + 0.125
157
+ transformed_restored = wf.WaveVStack.from_bytes(transformed.to_bytes())
158
+ x = np.linspace(-20e-9, 4e-6, 10_000, endpoint=False)
159
+ assert np.allclose(transformed_restored(x), transformed(x),
160
+ rtol=2e-15, atol=2e-15)
161
+
162
+ actual.label = "events"
163
+ unpickled = pickle.loads(pickle.dumps(actual, protocol=5))
164
+ assert unpickled == actual
165
+ assert unpickled.sample_rate == actual.sample_rate
166
+ assert unpickled.label == actual.label
167
+
168
+
169
+ @pytest.mark.parametrize("dtype,bits", [
170
+ (np.float64, 0),
171
+ (np.int16, 16),
172
+ (np.int32, 32),
173
+ ])
174
+ def test_stack_sample_plan_matches_direct_sampler(dtype, bits):
175
+ pulse = wf.gaussian(20e-9) * wf.cos(2 * np.pi * 100e6)
176
+ scales = (0.25, 0.5, 1.0)
177
+ actual = wf.WaveVStack([
178
+ scales[index % len(scales)] * pulse >> (index * 40e-9)
179
+ for index in range(75)
180
+ ])
181
+ actual.start = -20e-9
182
+ actual.stop = 75 * 40e-9
183
+ actual.sample_rate = RATE
184
+ actual.offset = 0.071
185
+
186
+ first = actual.sample(dtype=dtype)
187
+ cache_key, sample_plan = actual._sample_plan_cache
188
+ target = np.empty_like(first)
189
+ result = actual.sample(dtype=dtype, out=target)
190
+ direct = actual._core.sample(
191
+ cache_key[0], cache_key[1], cache_key[2], cache_key[3],
192
+ cache_key[4], actual.offset, bits, 1.0,
193
+ )
194
+
195
+ assert result is target
196
+ assert sample_plan.count == len(result)
197
+ assert sample_plan.group_count == 1
198
+ assert sample_plan.non_overlapping
199
+ assert np.array_equal(result, direct)
200
+ assert actual._sample_plan_cache[1] is sample_plan
201
+
202
+
203
+ def test_overlapping_stack_quantization_and_empty_stack():
204
+ actual = _stack(50, spacing=10e-9)
205
+ for dtype, bits in ((np.int16, 16), (np.int32, 32)):
206
+ expected = quantize_samples(actual.sample(), bits)
207
+ assert np.array_equal(actual.sample(dtype=dtype), expected)
208
+
209
+ empty = wf.WaveVStack([])
210
+ empty.start = -1e-9
211
+ empty.stop = 1e-9
212
+ empty.sample_rate = RATE
213
+ assert empty.to_bytes() == b"WNS4\x02\x00\x00\x00" + b"\x00" * 8
214
+ assert not np.any(empty.sample())
215
+ assert not np.any(wf.WaveVStack.from_bytes(empty.to_bytes())(
216
+ np.linspace(-1e-9, 1e-9, 17)
217
+ ))
218
+
219
+
220
+ def test_blocks_reject_malformed_layouts():
221
+ wave_data = bytearray(_pulse().to_bytes())
222
+ with pytest.raises(ValueError):
223
+ wf.Waveform.from_bytes(wave_data[:20])
224
+ wave_data[0] = 0
225
+ with pytest.raises(ValueError):
226
+ wf.Waveform.from_bytes(wave_data)
227
+
228
+ wave_data = bytearray(_pulse().to_bytes())
229
+ wave_data[24] = 255
230
+ with pytest.raises(ValueError):
231
+ wf.Waveform.from_bytes(wave_data)
232
+
233
+ stack_data = bytearray(_stack(2).to_bytes())
234
+ with pytest.raises(ValueError):
235
+ wf.WaveVStack.from_bytes(stack_data[:-1])
236
+ event_offset = len(stack_data) - 40
237
+ stack_data[event_offset:event_offset + 4] = (99).to_bytes(4, "little")
238
+ malformed = wf.WaveVStack.from_bytes(stack_data)
239
+ with pytest.raises(ValueError):
240
+ _ = malformed._core.event_count
241
+
242
+
243
+ def test_format_is_language_neutral_and_uses_the_global_clock():
244
+ data = _pulse().to_bytes()
245
+ assert data[:4] == b"WNF4"
246
+ assert int.from_bytes(data[4:6], "little") == 2
247
+ assert wf.get_time_resolution() == 1 / 120_000_000_000
248
+ package_dir = Path(wf.__file__).resolve().parent
249
+ assert (package_dir / "_cwaveform.c").is_file()
250
+ 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", "Waveform", "WaveVStack",
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 actual_add.to_bytes() == eager_add.to_bytes()
64
- assert actual_mul.to_bytes() == eager_mul.to_bytes()
65
- assert np.array_equal(actual_add(x), eager_add(x))
66
- assert np.array_equal(actual_mul(x), eager_mul(x))
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 packed event arrays; substantially smaller than
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 test_packed_backend_rejects_complex_coefficients_and_scales():
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"WFM3"
417
- assert stack.to_bytes()[:4] == b"WVS3"
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
- with pytest.raises(TypeError, match="must be real"):
423
- wav._core.scaled(1j)
424
- with pytest.raises(TypeError, match="must be real"):
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
- ticks = wav._core.get_bound_ticks()
434
- assert ticks.dtype == np.dtype("<i8")
435
- assert np.array_equal(ticks[:-1], [-240, 240])
436
- assert wav._delay == 11e-9
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._core.get_bound_ticks()[0])"
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)
@@ -576,14 +567,8 @@ def test_wavevstack_integer_grid_template_sampling_and_complex_iq():
576
567
  stack.start = -10e-9
577
568
  stack.stop = 110e-9
578
569
  actual = stack.sample(rate)
579
- start_tick = time_to_tick(stack.start)
580
- expected = np.full(len(actual), stack.offset)
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)
570
+ x = sample_grid(time_to_tick(stack.start), len(actual), *step)
571
+ expected = stack(x)
587
572
  assert np.allclose(actual, expected, rtol=2e-15, atol=2e-15)
588
573
 
589
574
  complex_stack = wf.ComplexWaveVStack(stack, -0.5 * stack)
@@ -595,12 +580,16 @@ def test_wavevstack_integer_grid_template_sampling_and_complex_iq():
595
580
  assert np.array_equal(q_data, quantize_samples(complex_samples.imag, 16))
596
581
 
597
582
 
598
- def test_single_packed_backend_has_no_waveform2_modules():
583
+ def test_single_c_backend_has_no_waveform2_or_packed_core():
599
584
  package = Path(wf.__file__).parent
600
585
  python_source = (package / "waveform.py").read_text()
601
586
  cython_source = (package / "_waveform.pyx").read_text()
602
587
  assert "waveform2" not in python_source
603
588
  assert "_waveform2" not in cython_source
589
+ assert "PackedWaveform" not in python_source + cython_source
590
+ assert "PackedStack" not in python_source + cython_source
591
+ assert "WFM3" not in python_source + cython_source
592
+ assert "WVS3" not in python_source + cython_source
604
593
  assert not (package / "waveform2.py").exists()
605
594
  assert not (package / "_waveform2.pyx").exists()
606
595
 
@@ -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", "Waveform", "WaveVStack",
59
- "NativeWaveform", "NativeWaveVStack", "NativeComplexWaveform",
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
  ]