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.
- {waveforms-3.1.0 → waveforms-3.3.0}/MANIFEST.in +3 -1
- {waveforms-3.1.0/waveforms.egg-info → waveforms-3.3.0}/PKG-INFO +44 -11
- waveforms-3.3.0/README.md +117 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/pyproject.toml +9 -1
- {waveforms-3.1.0 → waveforms-3.3.0}/setup.py +13 -2
- waveforms-3.3.0/tests/test_core.py +250 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/tests/test_waveform.py +291 -26
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/__init__.py +7 -1
- waveforms-3.3.0/waveforms/_cwaveform.c +3780 -0
- waveforms-3.3.0/waveforms/_cwaveform.h +176 -0
- waveforms-3.3.0/waveforms/_cwaveform.md +85 -0
- waveforms-3.3.0/waveforms/_waveform.pyi +131 -0
- waveforms-3.3.0/waveforms/_waveform.pyx +1152 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/distortion.py +2 -2
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/version.py +1 -1
- waveforms-3.3.0/waveforms/waveform.py +2241 -0
- {waveforms-3.1.0 → waveforms-3.3.0/waveforms.egg-info}/PKG-INFO +44 -11
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/SOURCES.txt +4 -4
- waveforms-3.1.0/README.md +0 -84
- waveforms-3.1.0/src/waveform.h +0 -171
- waveforms-3.1.0/waveforms/WaveformLexer.py +0 -134
- waveforms-3.1.0/waveforms/WaveformListener.py +0 -228
- waveforms-3.1.0/waveforms/WaveformParser.py +0 -1241
- waveforms-3.1.0/waveforms/_waveform.pyi +0 -75
- waveforms-3.1.0/waveforms/_waveform.pyx +0 -1563
- waveforms-3.1.0/waveforms/waveform.py +0 -988
- {waveforms-3.1.0 → waveforms-3.3.0}/LICENSE +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/setup.cfg +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/tests/test_wavevstack.py +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/Waveform.g4 +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/__main__.py +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/utils.py +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms/waveform_parser.py +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/dependency_links.txt +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/entry_points.txt +0 -0
- {waveforms-3.1.0 → waveforms-3.3.0}/waveforms.egg-info/requires.txt +0 -0
- {waveforms-3.1.0 → waveforms-3.3.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.
|
|
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
|
-
###
|
|
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
|
|
109
111
|
|
|
110
|
-
#
|
|
111
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
[](https://github.com/feihoo87/waveforms/)
|
|
3
|
+
[](https://coveralls.io/github/feihoo87/waveforms?branch=master)
|
|
4
|
+
[](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 = [
|
|
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
|
-
|
|
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()
|