waveforms 2.2.3__tar.gz → 2.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 (31) hide show
  1. {waveforms-2.2.3 → waveforms-2.3.0}/MANIFEST.in +1 -1
  2. {waveforms-2.2.3 → waveforms-2.3.0}/PKG-INFO +1 -1
  3. {waveforms-2.2.3 → waveforms-2.3.0}/tests/test_waveform.py +38 -0
  4. {waveforms-2.2.3 → waveforms-2.3.0}/tests/test_wavevstack.py +10 -0
  5. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/_waveform.pyx +176 -80
  6. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/version.py +1 -1
  7. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms.egg-info/PKG-INFO +1 -1
  8. {waveforms-2.2.3 → waveforms-2.3.0}/LICENSE +0 -0
  9. {waveforms-2.2.3 → waveforms-2.3.0}/README.md +0 -0
  10. {waveforms-2.2.3 → waveforms-2.3.0}/pyproject.toml +0 -0
  11. {waveforms-2.2.3 → waveforms-2.3.0}/setup.cfg +0 -0
  12. {waveforms-2.2.3 → waveforms-2.3.0}/setup.py +0 -0
  13. {waveforms-2.2.3 → waveforms-2.3.0}/src/waveform.h +0 -0
  14. {waveforms-2.2.3 → waveforms-2.3.0}/tests/test_multi_drag.py +0 -0
  15. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/Waveform.g4 +0 -0
  16. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/WaveformLexer.py +0 -0
  17. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/WaveformListener.py +0 -0
  18. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/WaveformParser.py +0 -0
  19. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/__init__.py +0 -0
  20. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/__main__.py +0 -0
  21. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/_waveform.pyi +0 -0
  22. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/distortion.py +0 -0
  23. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/multy_drag.py +0 -0
  24. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/utils.py +0 -0
  25. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/waveform.py +0 -0
  26. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms/waveform_parser.py +0 -0
  27. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms.egg-info/SOURCES.txt +0 -0
  28. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms.egg-info/dependency_links.txt +0 -0
  29. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms.egg-info/entry_points.txt +0 -0
  30. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms.egg-info/requires.txt +0 -0
  31. {waveforms-2.2.3 → waveforms-2.3.0}/waveforms.egg-info/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
1
  include src/*.h
2
2
  include waveforms/_waveform.pyx
3
3
  include waveforms/Waveform.g4
4
- include MANIFEST.in
4
+ include MANIFEST.in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waveforms
3
- Version: 2.2.3
3
+ Version: 2.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>
@@ -138,6 +138,44 @@ def test_chirp():
138
138
  assert np.allclose(wav3(t), _chirp(t, 1, 2, 10, 4, 'hyperbolic'))
139
139
 
140
140
 
141
+ def test_gaussian_derivative_and_mollifier():
142
+ x = np.linspace(-1.999, 1.999, 4097)
143
+
144
+ width = 4.0
145
+ std_sq2 = width / 3.3302184446307908
146
+ n = 8
147
+ expected = ((-1)**n / std_sq2**n
148
+ * special.eval_hermite(n, x / std_sq2)
149
+ * np.exp(-(x / std_sq2)**2))
150
+ assert np.allclose(gaussian(width, d=n)(x), expected)
151
+
152
+ d = 6
153
+ r = width / 2
154
+ scaled = x / r
155
+ xx_1 = scaled * scaled - 1
156
+ p = np.poly1d([-2, 0])
157
+ for order in range(1, d):
158
+ p = (np.poly1d([1, 0, -2, 0, 1]) * p.deriv()
159
+ + np.poly1d([-4 * order, 0, 4 * order - 2, 0]) * p)
160
+ expected = (np.exp(1 / xx_1 + 1) / (-xx_1)**(2 * d)
161
+ * p(scaled) / r**d)
162
+ assert np.allclose(mollifier(width, d=d)(x), expected)
163
+
164
+
165
+ def test_sampling_points_and_clipping():
166
+ x = np.linspace(-2.0, 2.0, 4096, endpoint=False)
167
+ points = tuple(np.sin(np.linspace(0.0, 3.0, 65)))
168
+ wav = samplingPoints(-2.0, 2.0, points)
169
+ assert np.allclose(wav(x), np.interp(x, np.linspace(-2.0, 2.0, 65),
170
+ points))
171
+
172
+ wav = 2.0 * cos(2.1) + 0.3 * sin(0.7)
173
+ wav.min = -0.4
174
+ wav.max = 0.6
175
+ assert np.allclose(wav(x), np.clip(2.0 * np.cos(2.1 * x)
176
+ + 0.3 * np.sin(0.7 * x), -0.4, 0.6))
177
+
178
+
141
179
  def test_parser():
142
180
  assert wave_eval("one()") == one()
143
181
  assert wave_eval("zero()") == zero()
@@ -141,3 +141,13 @@ def test_wave_sum():
141
141
  assert wave_sum([((-1.0, np.inf), (((), ()), ((((), ()), ), (0.02, )))),
142
142
  ((-1.0, np.inf), (((), ()), ((((), ()), ), (-0.02, ))))
143
143
  ]) == ((np.inf, ), (((), ()), ))
144
+
145
+
146
+ def test_wave_sum_preserves_value_before_a_new_earliest_bound():
147
+ source = (square(1.0) >> 5.0, const(2.0), square(1.0))
148
+ waves = [(wav.bounds, wav.seq) for wav in source]
149
+ combined = Waveform(*wave_sum(waves))
150
+ x = np.array([-10.0, 0.0, 2.0, 5.0, 10.0])
151
+ expected = sum(wav(x) for wav in source)
152
+
153
+ assert np.allclose(combined(x), expected)
@@ -1,5 +1,6 @@
1
1
  import pickle
2
2
  from bisect import bisect_left
3
+ from functools import lru_cache
3
4
  from itertools import chain, product
4
5
 
5
6
  import numpy as np
@@ -127,90 +128,174 @@ def pow(x, n):
127
128
  return ret
128
129
 
129
130
 
130
- def _apply(function_lib, func_id, x, shift, *args):
131
- return function_lib[func_id](x - shift, *args)
131
+ cdef object _calc_impl(object wav, object x, object function_lib):
132
+ cdef dict value_cache = {}
133
+ cdef object term_list = wav[0]
134
+ cdef object coefficient_list = wav[1]
135
+ cdef object monomial
136
+ cdef object base_list
137
+ cdef object power_list
138
+ cdef object base
139
+ cdef object value
140
+ cdef object product_value
141
+ cdef object term_value
142
+ cdef object total = None
143
+ cdef object coefficient
144
+ cdef object func
145
+ cdef object shift
146
+ cdef object args
147
+ cdef Py_ssize_t i, j
148
+ cdef Py_ssize_t term_count = len(term_list)
149
+ cdef Py_ssize_t base_count
150
+
151
+ for i in range(term_count):
152
+ monomial = term_list[i]
153
+ base_list = monomial[0]
154
+ power_list = monomial[1]
155
+ coefficient = coefficient_list[i]
156
+ product_value = None
157
+ base_count = len(base_list)
158
+
159
+ for j in range(base_count):
160
+ base = base_list[j]
161
+ if base in value_cache:
162
+ value = value_cache[base]
163
+ else:
164
+ func = function_lib[base[0]]
165
+ shift = base[-1]
166
+ args = base[1:-1]
167
+ if shift == 0:
168
+ value = func(x, *args)
169
+ else:
170
+ value = func(x - shift, *args)
171
+ value_cache[base] = value
132
172
 
173
+ if power_list[j] != 1:
174
+ value = value**power_list[j]
133
175
 
134
- def _calc(wav, x, function_lib):
135
- lru_cache = {}
136
-
137
- def _calc_m(t, x):
138
- ret = 1
139
- for mt, n in zip(*t):
140
- if mt not in lru_cache:
141
- func_id, *args, shift = mt
142
- lru_cache[mt] = _apply(function_lib, func_id, x, shift, *args)
143
- if n == 1:
144
- ret = ret * lru_cache[mt]
176
+ if product_value is None:
177
+ product_value = value
145
178
  else:
146
- ret = ret * lru_cache[mt]**n
147
- return ret
179
+ product_value = product_value * value
180
+
181
+ if product_value is None:
182
+ term_value = coefficient
183
+ elif coefficient == 1:
184
+ term_value = product_value
185
+ else:
186
+ term_value = coefficient * product_value
187
+
188
+ if total is None:
189
+ total = term_value
190
+ else:
191
+ total = total + term_value
192
+
193
+ if total is None:
194
+ return 0
195
+ return total
148
196
 
149
- ret = 0
150
- for t, v in zip(*wav):
151
- ret = ret + v * _calc_m(t, x)
152
- return ret
197
+
198
+ def _calc(wav, x, function_lib):
199
+ return _calc_impl(wav, x, function_lib)
153
200
 
154
201
 
155
202
  def calc_parts(bounds, seq, x, function_lib, min=-inf, max=inf):
156
- range_list = np.searchsorted(x, bounds)
157
- parts = []
158
- start, stop = 0, 0
159
- dtype = float
160
- for i, stop in enumerate(range_list):
203
+ cdef object range_list = np.searchsorted(x, bounds)
204
+ cdef list parts = []
205
+ cdef object part
206
+ cdef object dtype = float
207
+ cdef Py_ssize_t i
208
+ cdef Py_ssize_t start = 0
209
+ cdef Py_ssize_t stop
210
+ cdef Py_ssize_t count = len(range_list)
211
+ cdef bint should_clip = min != -inf or max != inf
212
+
213
+ for i in range(count):
214
+ stop = range_list[i]
161
215
  if start < stop and seq[i] != _zero:
162
- part = np.clip(_calc(seq[i], x[start:stop], function_lib), min,
163
- max)
164
- if (isinstance(part, complex) or isinstance(part, np.ndarray)
165
- and isinstance(part[0], complex)):
216
+ part = _calc_impl(seq[i], x[start:stop], function_lib)
217
+ if should_clip:
218
+ part = np.clip(part, min, max)
219
+ if np.iscomplexobj(part):
166
220
  dtype = complex
167
221
  parts.append((start, stop, part))
168
222
  start = stop
169
223
  return parts, dtype
170
224
 
171
225
 
172
- def wave_sum(waves):
173
- if not waves:
174
- return ((+inf, ), (_zero, ))
175
-
176
- bounds, seq = waves[0]
177
- if not waves[1:]:
178
- return bounds, seq
179
- bounds, seq = list(bounds), list(seq)
180
-
181
- for bounds_, seq_ in waves[1:]:
182
- if len(bounds_) == 1:
183
- for i, s in enumerate(seq):
184
- seq[i] = add(s, seq_[0])
185
- elif len(bounds) == 1:
186
- bounds = list(bounds_)
187
- seq = [add(seq[0], s) for s in seq_]
188
- else:
189
- lo = 0
190
- for b, s in zip(bounds_, seq_):
191
- i = bisect_left(bounds, b, lo=lo)
192
- if bounds[i] > b:
193
- bounds.insert(i, b)
194
- if i == 0:
195
- seq.insert(i, s)
196
- else:
197
- seq.insert(i, add(s, seq[i]))
198
- up = i - 1
199
- else:
200
- up = i
201
- for j in range(lo + 1, up + 1):
202
- seq[j] = add(seq[j], s)
203
- lo = i
204
-
205
- i = 0
206
- while i < len(bounds) - 1:
207
- if seq[i] == seq[i + 1]:
208
- del seq[i]
209
- del bounds[i]
226
+ cdef void _accumulate_expr(dict accumulator, object expr, int sign):
227
+ cdef object term_list = expr[0]
228
+ cdef object value_list = expr[1]
229
+ cdef object term
230
+ cdef object value
231
+ cdef Py_ssize_t i
232
+ cdef Py_ssize_t count = len(term_list)
233
+
234
+ for i in range(count):
235
+ term = term_list[i]
236
+ value = accumulator.get(term, 0) + sign * value_list[i]
237
+ if value == 0:
238
+ accumulator.pop(term, None)
210
239
  else:
211
- i += 1
240
+ accumulator[term] = value
212
241
 
213
- return tuple(bounds), tuple(seq)
242
+
243
+ cdef object _snapshot_expr(dict accumulator):
244
+ cdef list items
245
+ if not accumulator:
246
+ return _zero
247
+ items = sorted(accumulator.items())
248
+ return (tuple(item[0] for item in items),
249
+ tuple(item[1] for item in items))
250
+
251
+
252
+ def wave_sum(waves):
253
+ cdef dict accumulator = {}
254
+ cdef dict events = {}
255
+ cdef list output_bounds = []
256
+ cdef list output_seq
257
+ cdef object bounds
258
+ cdef object seq
259
+ cdef object changes
260
+ cdef object old_expr
261
+ cdef object new_expr
262
+ cdef object expr
263
+ cdef object boundary
264
+ cdef Py_ssize_t i, j
265
+ cdef Py_ssize_t wave_count
266
+ cdef Py_ssize_t bound_count
267
+
268
+ if not waves:
269
+ return ((inf, ), (_zero, ))
270
+ if len(waves) == 1:
271
+ return waves[0]
272
+
273
+ wave_count = len(waves)
274
+ for i in range(wave_count):
275
+ bounds, seq = waves[i]
276
+ _accumulate_expr(accumulator, seq[0], 1)
277
+ bound_count = len(bounds)
278
+ for j in range(bound_count - 1):
279
+ boundary = bounds[j]
280
+ changes = events.get(boundary)
281
+ if changes is None:
282
+ changes = []
283
+ events[boundary] = changes
284
+ changes.append((seq[j], seq[j + 1]))
285
+
286
+ output_seq = [_snapshot_expr(accumulator)]
287
+ for boundary in sorted(events):
288
+ changes = events[boundary]
289
+ for old_expr, new_expr in changes:
290
+ _accumulate_expr(accumulator, old_expr, -1)
291
+ _accumulate_expr(accumulator, new_expr, 1)
292
+ expr = _snapshot_expr(accumulator)
293
+ if expr != output_seq[-1]:
294
+ output_bounds.append(boundary)
295
+ output_seq.append(expr)
296
+
297
+ output_bounds.append(inf)
298
+ return tuple(output_bounds), tuple(output_seq)
214
299
 
215
300
 
216
301
  def merge_waveform(b1, s1, b2, s2, oper):
@@ -296,8 +381,9 @@ def _GAUSSIAN(t, std_sq2):
296
381
 
297
382
 
298
383
  def _D_GAUSSIAN(t, std_sq2, n):
299
- return (-1)**n / std_sq2**n * special.hermite(n)(
300
- t / std_sq2) * np.exp(-(t / std_sq2)**2)
384
+ x = t / std_sq2
385
+ return ((-1)**n / std_sq2**n * special.eval_hermite(n, x)
386
+ * np.exp(-(x**2)))
301
387
 
302
388
 
303
389
  def _ERF(t, std_sq2):
@@ -316,8 +402,13 @@ def _EXP(t, alpha):
316
402
  return np.exp(alpha * t)
317
403
 
318
404
 
405
+ @lru_cache(maxsize=256)
406
+ def _interp_grid(start, stop, size):
407
+ return np.linspace(start, stop, size)
408
+
409
+
319
410
  def _INTERP(t, start, stop, points):
320
- return np.interp(t, np.linspace(start, stop, len(points)), points)
411
+ return np.interp(t, _interp_grid(start, stop, len(points)), points)
321
412
 
322
413
 
323
414
  def _LINEARCHIRP(t, f0, f1, T, phi0):
@@ -356,19 +447,24 @@ def _drag(t: np.ndarray, t0: float, freq: float, width: float, delta: float,
356
447
  return Omega_x * np.cos(wt) + Omega_y * np.sin(wt)
357
448
 
358
449
 
450
+ @lru_cache(maxsize=64)
451
+ def _mollifier_poly(d):
452
+ p = np.poly1d([-2, 0])
453
+ for n in range(1, d):
454
+ p = (np.poly1d([1, 0, -2, 0, 1]) * p.deriv()
455
+ + np.poly1d([-4 * n, 0, 4 * n - 2, 0]) * p)
456
+ return p
457
+
458
+
359
459
  def _mollifier(t: np.ndarray, r: float, d: int):
360
460
  x = t / r
361
- xx_1 = np.abs(x)**2 - 1
461
+ xx_1 = x * x - 1
362
462
  if d == 0:
363
463
  return np.where(xx_1 >= 0, 0, np.exp(1 / xx_1 + 1))
364
- else:
365
- p = np.poly1d([-2, 0])
366
- for n in range(1, d):
367
- p = np.poly1d([1, 0, -2, 0, 1]) * p.deriv() + np.poly1d(
368
- [-4 * n, 0, 4 * n - 2, 0]) * p
369
- return np.where(xx_1 >= 0, 0,
370
- np.exp(1 / xx_1 + 1) /
371
- (-xx_1)**(2 * d)) * p(x) / r**d
464
+ p = _mollifier_poly(d)
465
+ return (np.where(xx_1 >= 0, 0,
466
+ np.exp(1 / xx_1 + 1) / (-xx_1)**(2 * d))
467
+ * p(x) / r**d)
372
468
 
373
469
 
374
470
  LINEAR = registerBaseFunc(_LINEAR)
@@ -1,2 +1,2 @@
1
1
  """Define version number here and read it from setup.py automatically"""
2
- __version__ = "2.2.3"
2
+ __version__ = "2.3.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waveforms
3
- Version: 2.2.3
3
+ Version: 2.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>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes