waveforms 3.1.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 (37) hide show
  1. {waveforms-3.1.0 → waveforms-3.3.0}/MANIFEST.in +3 -1
  2. {waveforms-3.1.0/waveforms.egg-info → waveforms-3.3.0}/PKG-INFO +44 -11
  3. waveforms-3.3.0/README.md +117 -0
  4. {waveforms-3.1.0 → waveforms-3.3.0}/pyproject.toml +9 -1
  5. {waveforms-3.1.0 → waveforms-3.3.0}/setup.py +13 -2
  6. waveforms-3.3.0/tests/test_core.py +250 -0
  7. {waveforms-3.1.0 → waveforms-3.3.0}/tests/test_waveform.py +291 -26
  8. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/__init__.py +7 -1
  9. waveforms-3.3.0/waveforms/_cwaveform.c +3780 -0
  10. waveforms-3.3.0/waveforms/_cwaveform.h +176 -0
  11. waveforms-3.3.0/waveforms/_cwaveform.md +85 -0
  12. waveforms-3.3.0/waveforms/_waveform.pyi +131 -0
  13. waveforms-3.3.0/waveforms/_waveform.pyx +1152 -0
  14. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/distortion.py +2 -2
  15. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/version.py +1 -1
  16. waveforms-3.3.0/waveforms/waveform.py +2241 -0
  17. {waveforms-3.1.0 → waveforms-3.3.0/waveforms.egg-info}/PKG-INFO +44 -11
  18. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/SOURCES.txt +4 -4
  19. waveforms-3.1.0/README.md +0 -84
  20. waveforms-3.1.0/src/waveform.h +0 -171
  21. waveforms-3.1.0/waveforms/WaveformLexer.py +0 -134
  22. waveforms-3.1.0/waveforms/WaveformListener.py +0 -228
  23. waveforms-3.1.0/waveforms/WaveformParser.py +0 -1241
  24. waveforms-3.1.0/waveforms/_waveform.pyi +0 -75
  25. waveforms-3.1.0/waveforms/_waveform.pyx +0 -1563
  26. waveforms-3.1.0/waveforms/waveform.py +0 -988
  27. {waveforms-3.1.0 → waveforms-3.3.0}/LICENSE +0 -0
  28. {waveforms-3.1.0 → waveforms-3.3.0}/setup.cfg +0 -0
  29. {waveforms-3.1.0 → waveforms-3.3.0}/tests/test_wavevstack.py +0 -0
  30. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/Waveform.g4 +0 -0
  31. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/__main__.py +0 -0
  32. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/utils.py +0 -0
  33. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/waveform_parser.py +0 -0
  34. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/dependency_links.txt +0 -0
  35. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/entry_points.txt +0 -0
  36. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/requires.txt +0 -0
  37. {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/top_level.txt +0 -0
@@ -1,4 +1,6 @@
1
- include src/*.h
1
+ include waveforms/_cwaveform.h
2
+ include waveforms/_cwaveform.c
3
+ include waveforms/_cwaveform.md
2
4
  include waveforms/_waveform.pyx
3
5
  include waveforms/_waveform.pyi
4
6
  include waveforms/Waveform.g4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waveforms
3
- Version: 3.1.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,27 +98,60 @@ 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
109
111
 
110
- # Optional; call this once, before constructing or loading waveform objects.
111
- wf.set_time_resolution(1e-12)
112
+ # The default tick is already the period of 120 GHz (1 / 120e9 seconds).
113
+ # To use another global tick, override it before constructing/loading waveforms:
114
+ # wf.set_time_resolution(1e-12)
112
115
 
113
116
  pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
114
117
  data = pulse.to_bytes()
115
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)
124
+ ```
125
+
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.
130
+
131
+ `sample()` has integer-grid fast paths for 500 MHz, 1 GHz, 1.2 GHz, 2 GHz,
132
+ 2.4 GHz, 2.5 GHz, 4 GHz, 6 GHz, 8 GHz, and 10 GHz. Real waveforms can be
133
+ quantized directly to signed DAC buffers:
134
+
135
+ ```python
136
+ pulse.start = 0
137
+ pulse.stop = 100e-9
138
+ dac16 = pulse.sample(2_400_000_000, dtype=np.int16, full_scale=1.0)
139
+ ```
140
+
141
+ The low-level cores store and evaluate real-valued signals only. Complex signals
142
+ are represented in Python as independent real and imaginary channels:
143
+
144
+ ```python
145
+ z = (1 + 0.25j) * wf.gaussian(12e-9)
146
+ assert isinstance(z, wf.ComplexWaveform)
147
+
148
+ stack = wf.ComplexWaveVStack([z, z >> 20e-9])
149
+ samples = stack(t) # complex NumPy array
116
150
  ```
117
151
 
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.
152
+ Real waveforms and stacks therefore avoid complex storage and arithmetic for
153
+ the common real-valued case. `ComplexWaveform.real` and `.imag` expose the two
154
+ real channel waveforms.
122
155
 
123
156
  ## Reporting Issues
124
157
  Please report all issues [on github](https://github.com/feihoo87/waveforms/issues).
@@ -0,0 +1,117 @@
1
+ # waveforms
2
+ [![View build status](https://github.com/feihoo87/waveforms/actions/workflows/workflow.yml/badge.svg)](https://github.com/feihoo87/waveforms/)
3
+ [![Coverage Status](https://coveralls.io/repos/github/feihoo87/waveforms/badge.svg?branch=master)](https://coveralls.io/github/feihoo87/waveforms?branch=master)
4
+ [![PyPI version](https://badge.fury.io/py/waveforms.svg)](https://pypi.org/project/waveforms/)
5
+
6
+ Form waveforms used in experiment.
7
+
8
+ ## Installation
9
+ We encourage installing waveforms via the pip tool (a python package manager):
10
+ ```bash
11
+ python -m pip install waveforms
12
+ ```
13
+
14
+ To install from the latest source, you need to clone the GitHub repository on your machine.
15
+ ```bash
16
+ git clone https://github.com/feihoo87/waveforms.git
17
+ ```
18
+
19
+ Then dependencies and `waveforms` can be installed in this way:
20
+ ```bash
21
+ cd waveforms
22
+ python -m pip install numpy
23
+ python -m pip install -e .
24
+ ```
25
+
26
+ ## Usage
27
+ ```python
28
+ import numpy as np
29
+ import matplotlib.pyplot as plt
30
+
31
+ from waveforms import *
32
+
33
+ pulse = cosPulse(20e-9)
34
+
35
+ x_wav = zero()
36
+ y_wav = zero()
37
+
38
+ I, Q = mixing(0.5*pulse, freq=-20e6, DRAGScaling=0.2)
39
+ x_wav += I
40
+ y_wav += Q
41
+
42
+ I, Q = mixing(pulse >> 1e-6, freq=-20e6, phase=np.pi/2, DRAGScaling=0.2)
43
+ x_wav += I
44
+ y_wav += Q
45
+
46
+ I, Q = mixing((0.5 * pulse) >> 2e-6, freq=-20e6, DRAGScaling=0.2)
47
+ x_wav += I
48
+ y_wav += Q
49
+
50
+
51
+ t = np.linspace(-1e-6, 9e-6, 10001)
52
+ plt.plot(t, x_wav(t))
53
+ plt.plot(t, y_wav(t))
54
+ plt.show()
55
+ ```
56
+
57
+ ### Unified binary representation
58
+
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:
64
+
65
+ ```python
66
+ import waveforms as wf
67
+
68
+ # The default tick is already the period of 120 GHz (1 / 120e9 seconds).
69
+ # To use another global tick, override it before constructing/loading waveforms:
70
+ # wf.set_time_resolution(1e-12)
71
+
72
+ pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
73
+ data = pulse.to_bytes()
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)
80
+ ```
81
+
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.
86
+
87
+ `sample()` has integer-grid fast paths for 500 MHz, 1 GHz, 1.2 GHz, 2 GHz,
88
+ 2.4 GHz, 2.5 GHz, 4 GHz, 6 GHz, 8 GHz, and 10 GHz. Real waveforms can be
89
+ quantized directly to signed DAC buffers:
90
+
91
+ ```python
92
+ pulse.start = 0
93
+ pulse.stop = 100e-9
94
+ dac16 = pulse.sample(2_400_000_000, dtype=np.int16, full_scale=1.0)
95
+ ```
96
+
97
+ The low-level cores store and evaluate real-valued signals only. Complex signals
98
+ are represented in Python as independent real and imaginary channels:
99
+
100
+ ```python
101
+ z = (1 + 0.25j) * wf.gaussian(12e-9)
102
+ assert isinstance(z, wf.ComplexWaveform)
103
+
104
+ stack = wf.ComplexWaveVStack([z, z >> 20e-9])
105
+ samples = stack(t) # complex NumPy array
106
+ ```
107
+
108
+ Real waveforms and stacks therefore avoid complex storage and arithmetic for
109
+ the common real-valued case. `ComplexWaveform.real` and `.imag` expose the two
110
+ real channel waveforms.
111
+
112
+ ## Reporting Issues
113
+ Please report all issues [on github](https://github.com/feihoo87/waveforms/issues).
114
+
115
+ ## License
116
+
117
+ [MIT](https://opensource.org/licenses/MIT)
@@ -71,7 +71,15 @@ license-files = ["LICENSE"]
71
71
  include-package-data = true
72
72
 
73
73
  [tool.setuptools.package-data]
74
- waveforms = ["WaveformLexer.py", "WaveformParser.py", "WaveformListener.py", "*.pyi"]
74
+ waveforms = [
75
+ "WaveformLexer.py",
76
+ "WaveformParser.py",
77
+ "WaveformListener.py",
78
+ "*.pyi",
79
+ "_cwaveform.h",
80
+ "_cwaveform.c",
81
+ "_cwaveform.md",
82
+ ]
75
83
 
76
84
  [tool.setuptools.dynamic]
77
85
  version = {attr = "waveforms.version.__version__"}
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
 
3
4
  from Cython.Build import cythonize
4
5
  from setuptools import Extension, find_packages, setup
@@ -35,9 +36,19 @@ def get_extensions():
35
36
  for dirpath, dirnames, filenames in os.walk('waveforms'):
36
37
  for filename in filenames:
37
38
  if filename.endswith('.pyx'):
39
+ sources = [os.path.join(dirpath, filename)]
40
+ include_dirs = []
41
+ extra_link_args = []
42
+ if filename == '_waveform.pyx':
43
+ sources.append(os.path.join(
44
+ 'waveforms', '_cwaveform.c'))
45
+ include_dirs.append('waveforms')
46
+ if sys.platform == 'darwin':
47
+ extra_link_args.extend(['-framework', 'Accelerate'])
38
48
  extensions.append(
39
- Extension(module_name(dirpath, filename),
40
- [os.path.join(dirpath, filename)]))
49
+ Extension(module_name(dirpath, filename), sources,
50
+ include_dirs=include_dirs,
51
+ extra_link_args=extra_link_args))
41
52
 
42
53
  return extensions
43
54
 
@@ -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()