waveforms 3.1.0__tar.gz → 3.2.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.2.0}/MANIFEST.in +4 -0
- {waveforms-3.1.0/waveforms.egg-info → waveforms-3.2.0}/PKG-INFO +29 -3
- {waveforms-3.1.0 → waveforms-3.2.0}/README.md +28 -2
- {waveforms-3.1.0 → waveforms-3.2.0}/pyproject.toml +9 -1
- {waveforms-3.1.0 → waveforms-3.2.0}/setup.py +13 -2
- waveforms-3.2.0/src/waveform.c +248 -0
- waveforms-3.2.0/tests/test_native.py +242 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/tests/test_waveform.py +296 -20
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/__init__.py +18 -1
- waveforms-3.2.0/waveforms/_cwaveform.c +2034 -0
- waveforms-3.2.0/waveforms/_cwaveform.h +129 -0
- waveforms-3.2.0/waveforms/_cwaveform.md +74 -0
- waveforms-3.2.0/waveforms/_waveform.pyi +171 -0
- waveforms-3.2.0/waveforms/_waveform.pyx +2823 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/distortion.py +2 -2
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/version.py +1 -1
- waveforms-3.2.0/waveforms/waveform.py +2581 -0
- {waveforms-3.1.0 → waveforms-3.2.0/waveforms.egg-info}/PKG-INFO +29 -3
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms.egg-info/SOURCES.txt +5 -3
- 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.2.0}/LICENSE +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/setup.cfg +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/src/waveform.h +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/tests/test_wavevstack.py +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/Waveform.g4 +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/__main__.py +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/utils.py +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms/waveform_parser.py +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms.egg-info/dependency_links.txt +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms.egg-info/entry_points.txt +0 -0
- {waveforms-3.1.0 → waveforms-3.2.0}/waveforms.egg-info/requires.txt +0 -0
- {waveforms-3.1.0 → waveforms-3.2.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.2.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>
|
|
@@ -107,8 +107,9 @@ serialization.
|
|
|
107
107
|
```python
|
|
108
108
|
import waveforms as wf
|
|
109
109
|
|
|
110
|
-
#
|
|
111
|
-
|
|
110
|
+
# The default tick is already the period of 120 GHz (1 / 120e9 seconds).
|
|
111
|
+
# To use another global tick, override it before constructing/loading waveforms:
|
|
112
|
+
# wf.set_time_resolution(1e-12)
|
|
112
113
|
|
|
113
114
|
pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
|
|
114
115
|
data = pulse.to_bytes()
|
|
@@ -120,6 +121,31 @@ process loading a block must therefore use the same resolution as the process
|
|
|
120
121
|
that created it. The setting is locked when the first waveform object is
|
|
121
122
|
created or loaded.
|
|
122
123
|
|
|
124
|
+
`sample()` has integer-grid fast paths for 500 MHz, 1 GHz, 1.2 GHz, 2 GHz,
|
|
125
|
+
2.4 GHz, 2.5 GHz, 4 GHz, 6 GHz, 8 GHz, and 10 GHz. Real waveforms can be
|
|
126
|
+
quantized directly to signed DAC buffers:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
pulse.start = 0
|
|
130
|
+
pulse.stop = 100e-9
|
|
131
|
+
dac16 = pulse.sample(2_400_000_000, dtype=np.int16, full_scale=1.0)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The packed core stores and evaluates real-valued signals only. Complex signals
|
|
135
|
+
are represented in Python as independent real and imaginary channels:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
z = (1 + 0.25j) * wf.gaussian(12e-9)
|
|
139
|
+
assert isinstance(z, wf.ComplexWaveform)
|
|
140
|
+
|
|
141
|
+
stack = wf.ComplexWaveVStack([z, z >> 20e-9])
|
|
142
|
+
samples = stack(t) # complex NumPy array
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`Waveform` and `WaveVStack` therefore avoid complex storage and arithmetic for
|
|
146
|
+
the common real-valued case. `ComplexWaveform.real` and `.imag` expose the two
|
|
147
|
+
real channel waveforms.
|
|
148
|
+
|
|
123
149
|
## Reporting Issues
|
|
124
150
|
Please report all issues [on github](https://github.com/feihoo87/waveforms/issues).
|
|
125
151
|
|
|
@@ -63,8 +63,9 @@ serialization.
|
|
|
63
63
|
```python
|
|
64
64
|
import waveforms as wf
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
|
|
66
|
+
# The default tick is already the period of 120 GHz (1 / 120e9 seconds).
|
|
67
|
+
# To use another global tick, override it before constructing/loading waveforms:
|
|
68
|
+
# wf.set_time_resolution(1e-12)
|
|
68
69
|
|
|
69
70
|
pulse = (wf.gaussian(12e-9) >> 20e-9) * wf.cos(2 * wf.pi * 5e9)
|
|
70
71
|
data = pulse.to_bytes()
|
|
@@ -76,6 +77,31 @@ process loading a block must therefore use the same resolution as the process
|
|
|
76
77
|
that created it. The setting is locked when the first waveform object is
|
|
77
78
|
created or loaded.
|
|
78
79
|
|
|
80
|
+
`sample()` has integer-grid fast paths for 500 MHz, 1 GHz, 1.2 GHz, 2 GHz,
|
|
81
|
+
2.4 GHz, 2.5 GHz, 4 GHz, 6 GHz, 8 GHz, and 10 GHz. Real waveforms can be
|
|
82
|
+
quantized directly to signed DAC buffers:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
pulse.start = 0
|
|
86
|
+
pulse.stop = 100e-9
|
|
87
|
+
dac16 = pulse.sample(2_400_000_000, dtype=np.int16, full_scale=1.0)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The packed core stores and evaluates real-valued signals only. Complex signals
|
|
91
|
+
are represented in Python as independent real and imaginary channels:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
z = (1 + 0.25j) * wf.gaussian(12e-9)
|
|
95
|
+
assert isinstance(z, wf.ComplexWaveform)
|
|
96
|
+
|
|
97
|
+
stack = wf.ComplexWaveVStack([z, z >> 20e-9])
|
|
98
|
+
samples = stack(t) # complex NumPy array
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`Waveform` and `WaveVStack` therefore avoid complex storage and arithmetic for
|
|
102
|
+
the common real-valued case. `ComplexWaveform.real` and `.imag` expose the two
|
|
103
|
+
real channel waveforms.
|
|
104
|
+
|
|
79
105
|
## Reporting Issues
|
|
80
106
|
Please report all issues [on github](https://github.com/feihoo87/waveforms/issues).
|
|
81
107
|
|
|
@@ -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,248 @@
|
|
|
1
|
+
#include <Python.h>
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <math.h>
|
|
4
|
+
#include <stdint.h>
|
|
5
|
+
#include "waveform.h"
|
|
6
|
+
|
|
7
|
+
#define FUNC_TAB_SIZE 128
|
|
8
|
+
|
|
9
|
+
static size_t wave_func_count = 0;
|
|
10
|
+
FuncPtr wave_function_table[FUNC_TAB_SIZE];
|
|
11
|
+
|
|
12
|
+
static inline size_t bisect_left(const void *array, const void *const value,
|
|
13
|
+
size_t lo, size_t hi,
|
|
14
|
+
size_t type_size, int (*cmp)(const void *, const void *))
|
|
15
|
+
{
|
|
16
|
+
while (lo < hi)
|
|
17
|
+
{
|
|
18
|
+
size_t mid = (lo + hi) / 2;
|
|
19
|
+
void *p = (uint8_t *)array + mid * type_size;
|
|
20
|
+
if (cmp(p, value) < 0)
|
|
21
|
+
{
|
|
22
|
+
lo = mid + 1;
|
|
23
|
+
}
|
|
24
|
+
else
|
|
25
|
+
{
|
|
26
|
+
hi = mid;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return lo;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static int cmp_basic_fun(uint32_t func, const Wave1 *a, const Wave1 *b)
|
|
33
|
+
{
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static int cmp_wav1(const void *a, const void *b)
|
|
38
|
+
{
|
|
39
|
+
Wave1 *wa = (Wave1 *)a;
|
|
40
|
+
Wave1 *wb = (Wave1 *)b;
|
|
41
|
+
if (wa->func > wb->func)
|
|
42
|
+
{
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
else if (wa->func < wb->func)
|
|
46
|
+
{
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
49
|
+
return cmp_basic_fun(wa->func, wa, wb);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static int cmp_wav2(const void *a, const void *b)
|
|
53
|
+
{
|
|
54
|
+
Wave2 *wa = (Wave2 *)a;
|
|
55
|
+
Wave2 *wb = (Wave2 *)b;
|
|
56
|
+
for (size_t i = 0; i < wa->size && i < wb->size; i++)
|
|
57
|
+
{
|
|
58
|
+
if (wa->waves[i].func > wb->waves[i].func)
|
|
59
|
+
{
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
else if (wa->waves[i].func < wb->waves[i].func)
|
|
63
|
+
{
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return wa->size > wb->size ? 1 : wa->size < wb->size ? -1
|
|
68
|
+
: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static int cmp_wav3(const void *a, const void *b)
|
|
72
|
+
{
|
|
73
|
+
Wave3 *wa = (Wave3 *)a;
|
|
74
|
+
Wave3 *wb = (Wave3 *)b;
|
|
75
|
+
return wa->upper_bound > wb->upper_bound ? 1 : wa->upper_bound < wb->upper_bound ? -1
|
|
76
|
+
: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static double wave_basic_func_linear(double t, size_t argc, const Number *argv)
|
|
80
|
+
{
|
|
81
|
+
return t;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static double wave_basic_func_gaussian(double t, size_t argc, const Number *argv)
|
|
85
|
+
{
|
|
86
|
+
double x = t / argv[0].real;
|
|
87
|
+
return exp(-x * x);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static double wave_basic_func_erf(double t, size_t argc, const Number *argv)
|
|
91
|
+
{
|
|
92
|
+
double x = t / argv[0].real;
|
|
93
|
+
return erf(x);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static double wave_basic_func_cos(double t, size_t argc, const Number *argv)
|
|
97
|
+
{
|
|
98
|
+
double x = t * argv[0].real;
|
|
99
|
+
return cos(x);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static double wave_basic_func_sinc(double t, size_t argc, const Number *argv)
|
|
103
|
+
{
|
|
104
|
+
double x = t * argv[0].real;
|
|
105
|
+
if (x == 0.0)
|
|
106
|
+
{
|
|
107
|
+
return 0.0;
|
|
108
|
+
}
|
|
109
|
+
return sin(x) / x;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static double wave_basic_func_exp(double t, size_t argc, const Number *argv)
|
|
113
|
+
{
|
|
114
|
+
double x = t * argv[0].real;
|
|
115
|
+
return exp(x);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static double wave_apply_wave1(Time t, const Wave1 *wave)
|
|
119
|
+
{
|
|
120
|
+
double x = time_2_double(t - wave->shift);
|
|
121
|
+
double y = wave_function_table[wave->func](x, wave->argc, wave->argv);
|
|
122
|
+
if (wave->n != 1 || wave->d != 1)
|
|
123
|
+
{
|
|
124
|
+
y = pow(y, (double)wave->n / wave->d);
|
|
125
|
+
}
|
|
126
|
+
return y;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static double wave_apply_wave2(Time t, const Wave2 *wave)
|
|
130
|
+
{
|
|
131
|
+
double y = wave->amplitude;
|
|
132
|
+
for (size_t i = 0; i < wave->size; i++)
|
|
133
|
+
{
|
|
134
|
+
y *= wave_apply_wave1(t, wave->waves + i);
|
|
135
|
+
}
|
|
136
|
+
return y;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static double wave_apply_wave3(Time t, const Wave3 *wave)
|
|
140
|
+
{
|
|
141
|
+
double y = 0.0;
|
|
142
|
+
for (size_t i = 0; i < wave->size; i++)
|
|
143
|
+
{
|
|
144
|
+
y += wave_apply_wave2(t, wave->waves + i);
|
|
145
|
+
}
|
|
146
|
+
return y;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static double wave_apply_waveform(Time t, const Waveform *wave)
|
|
150
|
+
{
|
|
151
|
+
size_t i = bisect_left(wave->waves, &t, 0, wave->size, sizeof(Wave3), cmp_wav3);
|
|
152
|
+
if (i == wave->size)
|
|
153
|
+
{
|
|
154
|
+
return 0.0;
|
|
155
|
+
}
|
|
156
|
+
return wave_apply_wave3(t, wave->waves + i);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static void wave_sample_waveform(const Waveform *wave, double *samples, size_t size, Time start, Time dt)
|
|
160
|
+
{
|
|
161
|
+
Time t = start;
|
|
162
|
+
size_t i = 0;
|
|
163
|
+
for (size_t j = 0; j < size; j++)
|
|
164
|
+
{
|
|
165
|
+
i = bisect_left(wave->waves, &t, i, wave->size, sizeof(Wave3), cmp_wav3);
|
|
166
|
+
if (i == wave->size)
|
|
167
|
+
{
|
|
168
|
+
for (; j < size; j++)
|
|
169
|
+
{
|
|
170
|
+
samples[j] = 0.0;
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
samples[j] = wave_apply_wave3(t, wave->waves + i);
|
|
175
|
+
t += dt;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static void wave_sample_waveform_tlist(const Waveform *wave, size_t size, const Time *tlist, double *samples)
|
|
180
|
+
{
|
|
181
|
+
Time t = tlist[0];
|
|
182
|
+
size_t i = 0;
|
|
183
|
+
for (size_t j = 0; j < size; j++)
|
|
184
|
+
{
|
|
185
|
+
i = bisect_left(wave->waves, &t, i, wave->size, sizeof(Wave3), cmp_wav3);
|
|
186
|
+
if (i == wave->size)
|
|
187
|
+
{
|
|
188
|
+
for (; j < size; j++)
|
|
189
|
+
{
|
|
190
|
+
samples[j] = 0.0;
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
samples[j] = wave_apply_wave3(t, wave->waves + i);
|
|
195
|
+
t = tlist[j + 1];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static size_t wave_register_basic_func(FuncPtr func)
|
|
200
|
+
{
|
|
201
|
+
wave_function_table[wave_func_count] = func;
|
|
202
|
+
return wave_func_count++;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static void wave_init_basic_func_table()
|
|
206
|
+
{
|
|
207
|
+
wave_register_basic_func(wave_basic_func_linear);
|
|
208
|
+
wave_register_basic_func(wave_basic_func_gaussian);
|
|
209
|
+
wave_register_basic_func(wave_basic_func_erf);
|
|
210
|
+
wave_register_basic_func(wave_basic_func_cos);
|
|
211
|
+
wave_register_basic_func(wave_basic_func_sinc);
|
|
212
|
+
wave_register_basic_func(wave_basic_func_exp);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static PyObject *method_fputs(PyObject *self, PyObject *args)
|
|
216
|
+
{
|
|
217
|
+
char *str, *filename = NULL;
|
|
218
|
+
int bytes_copied = -1;
|
|
219
|
+
|
|
220
|
+
/* Parse arguments */
|
|
221
|
+
if (!PyArg_ParseTuple(args, "ss", &str, &filename))
|
|
222
|
+
{
|
|
223
|
+
return NULL;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// FILE *fp = fopen(filename, "w");
|
|
227
|
+
// bytes_copied = fputs(str, fp);
|
|
228
|
+
// fclose(fp);
|
|
229
|
+
|
|
230
|
+
return PyLong_FromLong(bytes_copied);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static PyMethodDef Methods[] = {
|
|
234
|
+
{"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"},
|
|
235
|
+
{NULL, NULL, 0, NULL}};
|
|
236
|
+
|
|
237
|
+
static struct PyModuleDef module = {
|
|
238
|
+
PyModuleDef_HEAD_INIT,
|
|
239
|
+
"_waveform",
|
|
240
|
+
"Python interface for the _waveform C library",
|
|
241
|
+
-1,
|
|
242
|
+
Methods};
|
|
243
|
+
|
|
244
|
+
PyMODINIT_FUNC PyInit__waveform(void)
|
|
245
|
+
{
|
|
246
|
+
wave_init_basic_func_table();
|
|
247
|
+
return PyModule_Create(&module);
|
|
248
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import waveforms as wf
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
RATE = 2_400_000_000
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _pulse(native=False):
|
|
14
|
+
gaussian = wf.native_gaussian if native else wf.gaussian
|
|
15
|
+
cosine = wf.native_cos if native else wf.cos
|
|
16
|
+
sine = wf.native_sin if native else wf.sin
|
|
17
|
+
square = wf.native_square if native else wf.square
|
|
18
|
+
return (
|
|
19
|
+
0.8 * gaussian(20e-9) * cosine(2 * np.pi * 100e6)
|
|
20
|
+
+ 0.15 * gaussian(12e-9) * sine(2 * np.pi * 180e6, 0.2)
|
|
21
|
+
+ 0.05 * square(6e-9)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _stack(count, native=False, spacing=40e-9):
|
|
26
|
+
pulse = (0.8 * (wf.native_gaussian(20e-9)
|
|
27
|
+
if native else wf.gaussian(20e-9))
|
|
28
|
+
* (wf.native_cos(2 * np.pi * 100e6)
|
|
29
|
+
if native else wf.cos(2 * np.pi * 100e6)))
|
|
30
|
+
stack_type = wf.NativeWaveVStack if native else wf.WaveVStack
|
|
31
|
+
stack = stack_type([pulse >> (index * spacing) for index in range(count)])
|
|
32
|
+
stack.start = -20e-9
|
|
33
|
+
stack.stop = count * spacing
|
|
34
|
+
stack.sample_rate = RATE
|
|
35
|
+
return stack
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_native_wave_block_roundtrip_pickle_and_numerics():
|
|
39
|
+
expected = _pulse()
|
|
40
|
+
actual = _pulse(native=True)
|
|
41
|
+
expected.start = actual.start = -20e-9
|
|
42
|
+
expected.stop = actual.stop = 20e-9
|
|
43
|
+
expected.sample_rate = actual.sample_rate = RATE
|
|
44
|
+
actual.label = "native-pulse"
|
|
45
|
+
|
|
46
|
+
data = actual.to_bytes()
|
|
47
|
+
restored = wf.NativeWaveform.from_bytes(data)
|
|
48
|
+
x = np.linspace(-20e-9, 20e-9, 200_000, endpoint=False)
|
|
49
|
+
|
|
50
|
+
assert data[:4] == b"WNF4"
|
|
51
|
+
assert actual.to_bytes() is data
|
|
52
|
+
assert restored.to_bytes() is data
|
|
53
|
+
assert restored == wf.NativeWaveform.from_bytes(data)
|
|
54
|
+
assert np.allclose(actual(x), expected(x), rtol=2e-15, atol=2e-15)
|
|
55
|
+
assert np.array_equal(restored(x), actual(x))
|
|
56
|
+
assert len(data) < len(expected.to_bytes())
|
|
57
|
+
|
|
58
|
+
unpickled = pickle.loads(pickle.dumps(actual, protocol=5))
|
|
59
|
+
assert unpickled == actual
|
|
60
|
+
assert unpickled.start == actual.start
|
|
61
|
+
assert unpickled.stop == actual.stop
|
|
62
|
+
assert unpickled.sample_rate == actual.sample_rate
|
|
63
|
+
assert unpickled.label == actual.label
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize("dtype", [np.float64, np.int16, np.int32])
|
|
67
|
+
def test_native_device_grid_sampling_matches_packed_backend(dtype):
|
|
68
|
+
expected = _pulse()
|
|
69
|
+
actual = _pulse(native=True)
|
|
70
|
+
expected.start = actual.start = -20e-9
|
|
71
|
+
expected.stop = actual.stop = 20e-9
|
|
72
|
+
expected.sample_rate = actual.sample_rate = RATE
|
|
73
|
+
|
|
74
|
+
reference = expected.sample(dtype=dtype)
|
|
75
|
+
result = actual.sample(dtype=dtype)
|
|
76
|
+
if dtype == np.float64:
|
|
77
|
+
assert np.allclose(result, reference, rtol=2e-15, atol=2e-15)
|
|
78
|
+
else:
|
|
79
|
+
assert np.array_equal(result, reference)
|
|
80
|
+
|
|
81
|
+
target = np.empty_like(result)
|
|
82
|
+
returned = actual.sample(dtype=dtype, out=target)
|
|
83
|
+
assert returned is target
|
|
84
|
+
assert np.array_equal(target, result)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_native_complex_block_roundtrip_and_pickle():
|
|
88
|
+
pulse = _pulse(native=True)
|
|
89
|
+
complex_wave = pulse + 0.5j * (pulse >> 2e-9)
|
|
90
|
+
complex_wave.start = -20e-9
|
|
91
|
+
complex_wave.stop = 20e-9
|
|
92
|
+
complex_wave.sample_rate = RATE
|
|
93
|
+
complex_wave.label = "iq"
|
|
94
|
+
x = np.linspace(-20e-9, 20e-9, 8192, endpoint=False)
|
|
95
|
+
expected = pulse(x) + 0.5j * (pulse >> 2e-9)(x)
|
|
96
|
+
|
|
97
|
+
data = complex_wave.to_bytes()
|
|
98
|
+
restored = wf.NativeComplexWaveform.from_bytes(data)
|
|
99
|
+
assert data[:4] == b"WNC4"
|
|
100
|
+
assert np.allclose(restored(x), expected, rtol=2e-15, atol=2e-15)
|
|
101
|
+
unpickled = pickle.loads(pickle.dumps(complex_wave, protocol=5))
|
|
102
|
+
assert np.allclose(unpickled(x), expected, rtol=2e-15, atol=2e-15)
|
|
103
|
+
assert unpickled.start == complex_wave.start
|
|
104
|
+
assert unpickled.stop == complex_wave.stop
|
|
105
|
+
assert unpickled.sample_rate == complex_wave.sample_rate
|
|
106
|
+
assert unpickled.label == complex_wave.label
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_native_stack_sampling_lazy_roundtrip_shift_offset_and_pickle():
|
|
110
|
+
expected = _stack(100)
|
|
111
|
+
actual = _stack(100, native=True)
|
|
112
|
+
|
|
113
|
+
assert np.allclose(actual.sample(), expected.sample(),
|
|
114
|
+
rtol=2e-15, atol=2e-15)
|
|
115
|
+
assert np.array_equal(actual.sample(dtype=np.int16),
|
|
116
|
+
expected.sample(dtype=np.int16))
|
|
117
|
+
|
|
118
|
+
data = actual.to_bytes()
|
|
119
|
+
restored = wf.NativeWaveVStack.from_bytes(data)
|
|
120
|
+
restored.start = actual.start
|
|
121
|
+
restored.stop = actual.stop
|
|
122
|
+
restored.sample_rate = actual.sample_rate
|
|
123
|
+
assert data[:4] == b"WNS4"
|
|
124
|
+
assert restored.to_bytes() is data
|
|
125
|
+
assert np.array_equal(restored.sample(), actual.sample())
|
|
126
|
+
|
|
127
|
+
transformed = (actual >> 5e-9) + 0.125
|
|
128
|
+
transformed_data = transformed.to_bytes()
|
|
129
|
+
transformed_restored = wf.NativeWaveVStack.from_bytes(transformed_data)
|
|
130
|
+
x = np.linspace(-20e-9, 4e-6, 10_000, endpoint=False)
|
|
131
|
+
assert transformed_data[:4] == b"WNS4"
|
|
132
|
+
assert np.allclose(transformed_restored(x), transformed(x),
|
|
133
|
+
rtol=2e-15, atol=2e-15)
|
|
134
|
+
|
|
135
|
+
actual.label = "events"
|
|
136
|
+
unpickled = pickle.loads(pickle.dumps(actual, protocol=5))
|
|
137
|
+
assert unpickled == actual
|
|
138
|
+
assert unpickled.sample_rate == actual.sample_rate
|
|
139
|
+
assert unpickled.label == actual.label
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@pytest.mark.parametrize("dtype,bits", [
|
|
143
|
+
(np.float64, 0),
|
|
144
|
+
(np.int16, 16),
|
|
145
|
+
(np.int32, 32),
|
|
146
|
+
])
|
|
147
|
+
def test_native_stack_sample_plan_matches_direct_sampler(dtype, bits):
|
|
148
|
+
pulse = (wf.native_gaussian(20e-9)
|
|
149
|
+
* wf.native_cos(2 * np.pi * 100e6))
|
|
150
|
+
scales = (0.25, 0.5, 1.0)
|
|
151
|
+
actual = wf.NativeWaveVStack([
|
|
152
|
+
scales[index % len(scales)] * pulse >> (index * 40e-9)
|
|
153
|
+
for index in range(75)
|
|
154
|
+
])
|
|
155
|
+
actual.start = -20e-9
|
|
156
|
+
actual.stop = 75 * 40e-9
|
|
157
|
+
actual.sample_rate = RATE
|
|
158
|
+
actual.offset = 0.071
|
|
159
|
+
|
|
160
|
+
assert actual._sample_plan_cache is None
|
|
161
|
+
first = actual.sample(dtype=dtype)
|
|
162
|
+
cache_key, sample_plan = actual._sample_plan_cache
|
|
163
|
+
target = np.empty_like(first)
|
|
164
|
+
result = actual.sample(dtype=dtype, out=target)
|
|
165
|
+
direct = actual._core.sample(
|
|
166
|
+
cache_key[0], cache_key[1], cache_key[2], cache_key[3],
|
|
167
|
+
cache_key[4], actual.offset, bits, 1.0,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert result is target
|
|
171
|
+
assert sample_plan.count == len(result)
|
|
172
|
+
assert sample_plan.group_count == 1
|
|
173
|
+
assert sample_plan.non_overlapping
|
|
174
|
+
assert np.array_equal(result, direct)
|
|
175
|
+
assert actual._sample_plan_cache[1] is sample_plan
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_native_sample_plan_overlaps_and_rational_fallback():
|
|
179
|
+
actual = _stack(50, native=True, spacing=10e-9)
|
|
180
|
+
result = actual.sample(dtype=np.int16)
|
|
181
|
+
cache_key, sample_plan = actual._sample_plan_cache
|
|
182
|
+
expected = actual._core.sample(
|
|
183
|
+
cache_key[0], cache_key[1], cache_key[2], cache_key[3],
|
|
184
|
+
cache_key[4], actual.offset, 16, 1.0,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert np.array_equal(result, expected)
|
|
188
|
+
assert not sample_plan.non_overlapping
|
|
189
|
+
assert actual._core.prepare_sample(0, 64, 1200, 11, 0) is None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_native_overlapping_stack_quantization_and_empty_stack():
|
|
193
|
+
expected = _stack(50, spacing=10e-9)
|
|
194
|
+
actual = _stack(50, native=True, spacing=10e-9)
|
|
195
|
+
assert np.array_equal(actual.sample(dtype=np.int16),
|
|
196
|
+
expected.sample(dtype=np.int16))
|
|
197
|
+
assert np.array_equal(actual.sample(dtype=np.int32),
|
|
198
|
+
expected.sample(dtype=np.int32))
|
|
199
|
+
|
|
200
|
+
empty = wf.NativeWaveVStack([])
|
|
201
|
+
empty.start = -1e-9
|
|
202
|
+
empty.stop = 1e-9
|
|
203
|
+
empty.sample_rate = RATE
|
|
204
|
+
assert empty.to_bytes() == b"WNS4\x01\x00\x00\x00" + b"\x00" * 8
|
|
205
|
+
assert not np.any(empty.sample())
|
|
206
|
+
assert not np.any(wf.NativeWaveVStack.from_bytes(empty.to_bytes())(
|
|
207
|
+
np.linspace(-1e-9, 1e-9, 17)
|
|
208
|
+
))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_native_blocks_reject_malformed_layouts():
|
|
212
|
+
wave_data = bytearray(_pulse(native=True).to_bytes())
|
|
213
|
+
with pytest.raises(ValueError):
|
|
214
|
+
wf.NativeWaveform.from_bytes(wave_data[:20])
|
|
215
|
+
wave_data[0] = 0
|
|
216
|
+
with pytest.raises(ValueError):
|
|
217
|
+
wf.NativeWaveform.from_bytes(wave_data)
|
|
218
|
+
|
|
219
|
+
wave_data = bytearray(_pulse(native=True).to_bytes())
|
|
220
|
+
wave_data[24] = 255
|
|
221
|
+
with pytest.raises(ValueError):
|
|
222
|
+
wf.NativeWaveform.from_bytes(wave_data)
|
|
223
|
+
|
|
224
|
+
stack_data = bytearray(_stack(2, native=True).to_bytes())
|
|
225
|
+
with pytest.raises(ValueError):
|
|
226
|
+
wf.NativeWaveVStack.from_bytes(stack_data[:-1])
|
|
227
|
+
event_offset = len(stack_data) - 40
|
|
228
|
+
stack_data[event_offset:event_offset + 4] = (99).to_bytes(4, "little")
|
|
229
|
+
malformed = wf.NativeWaveVStack.from_bytes(stack_data)
|
|
230
|
+
with pytest.raises(ValueError):
|
|
231
|
+
_ = malformed._core.event_count
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_native_format_is_language_neutral_and_self_describing():
|
|
235
|
+
description = wf.native_format_description()
|
|
236
|
+
assert "little-endian" in description
|
|
237
|
+
assert "120 GHz" in description
|
|
238
|
+
assert "ABI 1" in description
|
|
239
|
+
assert type(_pulse(native=True)._core).__module__ == "waveforms._waveform"
|
|
240
|
+
package_dir = Path(wf.__file__).resolve().parent
|
|
241
|
+
assert (package_dir / "_cwaveform.c").is_file()
|
|
242
|
+
assert (package_dir / "_cwaveform.h").is_file()
|