waveforms 3.3.0__tar.gz → 3.4.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.3.0/waveforms.egg-info → waveforms-3.4.0}/PKG-INFO +1 -1
- {waveforms-3.3.0 → waveforms-3.4.0}/setup.py +4 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/tests/test_core.py +72 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/tests/test_waveform.py +38 -5
- {waveforms-3.3.0 → waveforms-3.4.0}/tests/test_wavevstack.py +47 -1
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/__init__.py +9 -2
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/_cwaveform.c +1168 -253
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/_cwaveform.h +5 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/_waveform.pyx +35 -50
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/version.py +1 -1
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/waveform.py +86 -35
- {waveforms-3.3.0 → waveforms-3.4.0/waveforms.egg-info}/PKG-INFO +1 -1
- {waveforms-3.3.0 → waveforms-3.4.0}/LICENSE +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/MANIFEST.in +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/README.md +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/pyproject.toml +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/setup.cfg +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/Waveform.g4 +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/__main__.py +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/_cwaveform.md +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/_waveform.pyi +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/distortion.py +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/utils.py +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms/waveform_parser.py +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms.egg-info/SOURCES.txt +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms.egg-info/dependency_links.txt +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms.egg-info/entry_points.txt +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms.egg-info/requires.txt +0 -0
- {waveforms-3.3.0 → waveforms-3.4.0}/waveforms.egg-info/top_level.txt +0 -0
|
@@ -39,15 +39,19 @@ def get_extensions():
|
|
|
39
39
|
sources = [os.path.join(dirpath, filename)]
|
|
40
40
|
include_dirs = []
|
|
41
41
|
extra_link_args = []
|
|
42
|
+
libraries = []
|
|
42
43
|
if filename == '_waveform.pyx':
|
|
43
44
|
sources.append(os.path.join(
|
|
44
45
|
'waveforms', '_cwaveform.c'))
|
|
45
46
|
include_dirs.append('waveforms')
|
|
46
47
|
if sys.platform == 'darwin':
|
|
47
48
|
extra_link_args.extend(['-framework', 'Accelerate'])
|
|
49
|
+
elif sys.platform.startswith('linux'):
|
|
50
|
+
libraries.append('m')
|
|
48
51
|
extensions.append(
|
|
49
52
|
Extension(module_name(dirpath, filename), sources,
|
|
50
53
|
include_dirs=include_dirs,
|
|
54
|
+
libraries=libraries,
|
|
51
55
|
extra_link_args=extra_link_args))
|
|
52
56
|
|
|
53
57
|
return extensions
|
|
@@ -80,6 +80,78 @@ def test_c_symbolic_frequency_filter():
|
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def test_c_symbolic_filter_reduces_trigonometric_products_and_powers():
|
|
84
|
+
x = np.linspace(-1.0, 1.0, 4097)
|
|
85
|
+
envelope = wf.gaussian(2.0)
|
|
86
|
+
first_frequency = 9.0
|
|
87
|
+
second_frequency = 7.0
|
|
88
|
+
first_phase = 0.31
|
|
89
|
+
second_phase = -0.27
|
|
90
|
+
cutoff = 5.0
|
|
91
|
+
|
|
92
|
+
first_cos = wf.cos(first_frequency, first_phase)
|
|
93
|
+
first_sin = wf.sin(first_frequency, first_phase)
|
|
94
|
+
second_cos = wf.cos(second_frequency, second_phase)
|
|
95
|
+
second_sin = wf.sin(second_frequency, second_phase)
|
|
96
|
+
difference = first_frequency - second_frequency
|
|
97
|
+
phase_difference = first_phase - second_phase
|
|
98
|
+
|
|
99
|
+
assert np.allclose(
|
|
100
|
+
(2 * envelope * first_cos * second_cos).filter(high=cutoff)(x),
|
|
101
|
+
(envelope * wf.cos(difference, phase_difference))(x),
|
|
102
|
+
atol=2e-11,
|
|
103
|
+
)
|
|
104
|
+
assert np.allclose(
|
|
105
|
+
(2 * envelope * first_cos * second_sin).filter(high=cutoff)(x),
|
|
106
|
+
(-envelope * wf.sin(difference, phase_difference))(x),
|
|
107
|
+
atol=2e-11,
|
|
108
|
+
)
|
|
109
|
+
assert np.allclose(
|
|
110
|
+
(2 * envelope * first_sin * second_sin).filter(high=cutoff)(x),
|
|
111
|
+
(envelope * wf.cos(difference, phase_difference))(x),
|
|
112
|
+
atol=2e-11,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
powered = wf.cos(first_frequency, first_phase) ** 2
|
|
116
|
+
assert np.allclose(powered.filter(high=cutoff)(x), 0.5, atol=2e-11)
|
|
117
|
+
assert np.allclose(
|
|
118
|
+
powered.filter(2 * first_frequency, np.inf)(x),
|
|
119
|
+
(0.5 * wf.cos(2 * first_frequency, 2 * first_phase))(x),
|
|
120
|
+
atol=2e-11,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
retained = (1.5e-15 * first_cos).filter(eps=1e-15)
|
|
124
|
+
discarded = (0.5e-15 * first_cos).filter(eps=1e-15)
|
|
125
|
+
assert np.max(np.abs(retained(x))) > 1e-15
|
|
126
|
+
assert np.array_equal(discarded(x), np.zeros_like(x))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_down_conversion_filter_matches_historic_symbolic_behavior():
|
|
130
|
+
x = np.linspace(-100.0, 100.0, 10001)
|
|
131
|
+
envelope = wf.gaussian(100.0)
|
|
132
|
+
radio_frequency = 92.0451
|
|
133
|
+
local_frequency = 92.0
|
|
134
|
+
phase = 0.32
|
|
135
|
+
rf, _ = wf.mixing(
|
|
136
|
+
envelope, freq=radio_frequency, phase=phase, DRAGScaling=0.0,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
i = (2 * rf * wf.cos(-2 * np.pi * local_frequency)).filter(
|
|
140
|
+
high=2 * np.pi * local_frequency,
|
|
141
|
+
)
|
|
142
|
+
q = (2 * rf * wf.sin(-2 * np.pi * local_frequency)).filter(
|
|
143
|
+
high=2 * np.pi * local_frequency,
|
|
144
|
+
)
|
|
145
|
+
difference = 2 * np.pi * (radio_frequency - local_frequency)
|
|
146
|
+
|
|
147
|
+
assert np.allclose(
|
|
148
|
+
i(x), (envelope * wf.cos(difference, -phase))(x), atol=2e-9,
|
|
149
|
+
)
|
|
150
|
+
assert np.allclose(
|
|
151
|
+
q(x), (envelope * wf.sin(difference, -phase))(x), atol=2e-9,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
83
155
|
def test_wave_block_roundtrip_pickle_and_numerics():
|
|
84
156
|
actual = _pulse()
|
|
85
157
|
actual.start = -20e-9
|
|
@@ -22,8 +22,9 @@ PUBLIC_NAMES = {
|
|
|
22
22
|
"exp", "function",
|
|
23
23
|
"gaussian", "general_cosine", "get_time_resolution", "hanning", "interp", "mixing",
|
|
24
24
|
"mollifier", "one", "poly", "registerBaseFunc", "registerDerivative",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"quantize_time", "sample_clock", "sample_grid", "samplingPoints",
|
|
26
|
+
"set_time_resolution", "sign", "sin", "sinc", "sinh", "square",
|
|
27
|
+
"step", "t", "tick_to_time", "time_to_tick", "wave_eval", "zero",
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
|
|
@@ -34,6 +35,9 @@ def test_public_api_and_basic_sampling():
|
|
|
34
35
|
assert np.allclose(wf.sin(0.7)(x), np.sin(0.7 * x))
|
|
35
36
|
assert np.allclose(wf.poly([1, -0.5, 0.25])(x),
|
|
36
37
|
1 - 0.5 * x + 0.25 * x**2)
|
|
38
|
+
assert wf.zero().is_zero()
|
|
39
|
+
assert (0 * wf.gaussian(1)).is_zero()
|
|
40
|
+
assert not wf.one().is_zero()
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
def test_binary_roundtrip_is_zero_copy_for_bytes_input():
|
|
@@ -461,7 +465,7 @@ def test_device_sample_clocks_and_rational_fallback():
|
|
|
461
465
|
assert np.all(np.diff(grid) > 0)
|
|
462
466
|
|
|
463
467
|
|
|
464
|
-
def
|
|
468
|
+
def test_fixed_width_quantization_and_rational_sampling():
|
|
465
469
|
values = np.array([-2, -1, -0.5, 0, 0.5, 1, 2.0])
|
|
466
470
|
assert np.array_equal(
|
|
467
471
|
quantize_samples(values, 16),
|
|
@@ -473,6 +477,15 @@ def test_fixed_width_quantization_supported_sampling_and_fallback():
|
|
|
473
477
|
1073741824, 2147483647, 2147483647],
|
|
474
478
|
)
|
|
475
479
|
|
|
480
|
+
# Exercise the fused SIMD path, including its historic half-away-from-zero
|
|
481
|
+
# rounding rule and multidimensional ``out`` handling.
|
|
482
|
+
half_steps = np.array([0.5, -0.5, 1.5, -1.5] * 8) / 32768.0
|
|
483
|
+
target = np.empty((8, 4), dtype=np.int16)
|
|
484
|
+
assert quantize_samples(half_steps.reshape(8, 4), 16, out=target) is target
|
|
485
|
+
assert np.array_equal(
|
|
486
|
+
target.reshape(-1), np.array([1, -1, 2, -2] * 8, dtype=np.int16)
|
|
487
|
+
)
|
|
488
|
+
|
|
476
489
|
wav = 0.8 * wf.gaussian(20e-9)
|
|
477
490
|
wav.start = -20e-9
|
|
478
491
|
wav.stop = 20e-9
|
|
@@ -489,8 +502,28 @@ def test_fixed_width_quantization_supported_sampling_and_fallback():
|
|
|
489
502
|
assert np.array_equal(chunks, whole)
|
|
490
503
|
|
|
491
504
|
odd_rate = 7_000_000_000
|
|
492
|
-
|
|
493
|
-
|
|
505
|
+
rational_samples = wav.sample(odd_rate)
|
|
506
|
+
numerator, denominator = sample_clock(odd_rate)
|
|
507
|
+
rational_grid = sample_grid(
|
|
508
|
+
time_to_tick(wav.start), len(rational_samples),
|
|
509
|
+
numerator, denominator,
|
|
510
|
+
)
|
|
511
|
+
assert np.allclose(rational_samples, wav(rational_grid), rtol=0, atol=1e-15)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def test_simd_node_evaluation_matches_scalar_boundaries():
|
|
515
|
+
positions = np.linspace(-2.0, 2.0, 513)
|
|
516
|
+
waves = (
|
|
517
|
+
wf.t(),
|
|
518
|
+
wf.exp(0.2),
|
|
519
|
+
wf.sinc(1.3),
|
|
520
|
+
wf.cosh(0.3),
|
|
521
|
+
wf.sinh(0.3),
|
|
522
|
+
(wf.gaussian(4.0, 1.5) * wf.cos(2.2)) ** 2,
|
|
523
|
+
)
|
|
524
|
+
for wave in waves:
|
|
525
|
+
expected = np.array([wave(float(position)) for position in positions])
|
|
526
|
+
assert np.allclose(wave(positions), expected, rtol=1e-12, atol=1e-13)
|
|
494
527
|
|
|
495
528
|
|
|
496
529
|
def test_integer_sampling_fast_paths_are_bit_exact_and_pickle_safe():
|
|
@@ -3,7 +3,10 @@ import pickle
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from scipy.signal import butter, lfilter, lfiltic, tf2sos
|
|
5
5
|
|
|
6
|
-
from waveforms import
|
|
6
|
+
from waveforms import (
|
|
7
|
+
Waveform, WaveVStack, cos, gaussian, pi, poly, sin, step,
|
|
8
|
+
time_to_tick, zero,
|
|
9
|
+
)
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
def _waves():
|
|
@@ -59,3 +62,46 @@ def test_wavevstack_binary_template_sharing_and_pickle():
|
|
|
59
62
|
restored = WaveVStack.from_bytes(stack.to_bytes())
|
|
60
63
|
assert restored == stack
|
|
61
64
|
assert pickle.loads(pickle.dumps(stack)) == stack
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_wavevstack_content_deduplication_and_constant_time_support():
|
|
68
|
+
base = gaussian(20e-9) * cos(2 * pi * 5e9)
|
|
69
|
+
waves = [
|
|
70
|
+
Waveform.from_bytes(base.to_bytes()) >> (index * 80e-9)
|
|
71
|
+
for index in range(100)
|
|
72
|
+
]
|
|
73
|
+
stack = WaveVStack([zero(), *waves])
|
|
74
|
+
|
|
75
|
+
assert len(stack) == stack.event_count == 100
|
|
76
|
+
assert stack.template_count == 1
|
|
77
|
+
assert np.isclose(stack.begin, base.begin)
|
|
78
|
+
assert np.isclose(stack.end, base.end + 99 * 80e-9)
|
|
79
|
+
|
|
80
|
+
restored = WaveVStack.from_bytes(stack.to_bytes())
|
|
81
|
+
shifted = restored >> 2e-9
|
|
82
|
+
assert restored.template_count == 1
|
|
83
|
+
assert restored.event_count == 100
|
|
84
|
+
assert np.isclose(shifted.begin, stack.begin + 2e-9)
|
|
85
|
+
assert np.isclose(shifted.end, stack.end + 2e-9)
|
|
86
|
+
|
|
87
|
+
empty = WaveVStack()
|
|
88
|
+
assert empty.begin == -np.inf
|
|
89
|
+
assert empty.end == np.inf
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_wavevstack_compiler_event_columns():
|
|
93
|
+
template = gaussian(20e-9) * cos(2 * pi * 5e9)
|
|
94
|
+
delays = np.arange(100, dtype=np.int64) * time_to_tick(80e-9)
|
|
95
|
+
stack = WaveVStack.from_events(
|
|
96
|
+
(template,), np.zeros(100, dtype=np.uint32), delays,
|
|
97
|
+
np.full(100, 0.25),
|
|
98
|
+
)
|
|
99
|
+
expected = WaveVStack([
|
|
100
|
+
0.25 * template >> (index * 80e-9) for index in range(100)
|
|
101
|
+
])
|
|
102
|
+
|
|
103
|
+
assert stack.template_count == 1
|
|
104
|
+
assert stack.event_count == 100
|
|
105
|
+
assert stack.to_bytes() == expected.to_bytes()
|
|
106
|
+
positions = np.linspace(-20e-9, 8e-6, 1000)
|
|
107
|
+
assert np.allclose(stack(positions), expected(positions))
|
|
@@ -30,9 +30,12 @@ from .waveform import (
|
|
|
30
30
|
mollifier,
|
|
31
31
|
one,
|
|
32
32
|
poly,
|
|
33
|
+
quantize_time,
|
|
33
34
|
registerBaseFunc,
|
|
34
35
|
registerDerivative,
|
|
35
36
|
samplingPoints,
|
|
37
|
+
sample_clock,
|
|
38
|
+
sample_grid,
|
|
36
39
|
set_time_resolution,
|
|
37
40
|
sign,
|
|
38
41
|
sin,
|
|
@@ -41,6 +44,8 @@ from .waveform import (
|
|
|
41
44
|
square,
|
|
42
45
|
step,
|
|
43
46
|
t,
|
|
47
|
+
tick_to_time,
|
|
48
|
+
time_to_tick,
|
|
44
49
|
wave_eval,
|
|
45
50
|
zero,
|
|
46
51
|
)
|
|
@@ -53,7 +58,9 @@ __all__ = [
|
|
|
53
58
|
"e", "exp", "function",
|
|
54
59
|
"gaussian", "general_cosine", "get_time_resolution", "hanning",
|
|
55
60
|
"interp", "mixing", "mollifier", "one", "pi", "poly",
|
|
56
|
-
"
|
|
61
|
+
"quantize_time", "registerBaseFunc", "registerDerivative",
|
|
62
|
+
"sample_clock", "sample_grid", "samplingPoints",
|
|
57
63
|
"set_time_resolution", "sign", "sin", "sinc", "sinh", "square",
|
|
58
|
-
"step", "t", "
|
|
64
|
+
"step", "t", "tick_to_time", "time_to_tick", "wave_eval", "zero",
|
|
65
|
+
"__version__",
|
|
59
66
|
]
|