waveforms 2.1.1__tar.gz → 2.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.
Files changed (30) hide show
  1. {waveforms-2.1.1 → waveforms-2.2.0}/PKG-INFO +1 -1
  2. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/__init__.py +4 -3
  3. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/_waveform.pyi +3 -2
  4. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/_waveform.pyx +18 -0
  5. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/version.py +1 -1
  6. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/waveform.py +126 -40
  7. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/waveform_parser.py +11 -12
  8. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms.egg-info/PKG-INFO +1 -1
  9. {waveforms-2.1.1 → waveforms-2.2.0}/LICENSE +0 -0
  10. {waveforms-2.1.1 → waveforms-2.2.0}/MANIFEST.in +0 -0
  11. {waveforms-2.1.1 → waveforms-2.2.0}/README.md +0 -0
  12. {waveforms-2.1.1 → waveforms-2.2.0}/pyproject.toml +0 -0
  13. {waveforms-2.1.1 → waveforms-2.2.0}/setup.cfg +0 -0
  14. {waveforms-2.1.1 → waveforms-2.2.0}/setup.py +0 -0
  15. {waveforms-2.1.1 → waveforms-2.2.0}/src/waveform.h +0 -0
  16. {waveforms-2.1.1 → waveforms-2.2.0}/tests/test_multi_drag.py +0 -0
  17. {waveforms-2.1.1 → waveforms-2.2.0}/tests/test_waveform.py +0 -0
  18. {waveforms-2.1.1 → waveforms-2.2.0}/tests/test_wavevstack.py +0 -0
  19. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/WaveformLexer.py +0 -0
  20. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/WaveformListener.py +0 -0
  21. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/WaveformParser.py +0 -0
  22. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/__main__.py +0 -0
  23. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/distortion.py +0 -0
  24. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/multy_drag.py +0 -0
  25. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms/utils.py +0 -0
  26. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms.egg-info/SOURCES.txt +0 -0
  27. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms.egg-info/dependency_links.txt +0 -0
  28. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms.egg-info/entry_points.txt +0 -0
  29. {waveforms-2.1.1 → waveforms-2.2.0}/waveforms.egg-info/requires.txt +0 -0
  30. {waveforms-2.1.1 → waveforms-2.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: 2.1.1
3
+ Version: 2.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>
@@ -4,7 +4,8 @@ from .multy_drag import drag_sin, drag_sinx
4
4
  from .version import __version__
5
5
  from .waveform import (D, Waveform, WaveVStack, chirp, const, cos, cosh,
6
6
  coshPulse, cosPulse, cut, drag, exp, function, gaussian,
7
- general_cosine, hanning, interp, mixing, one, poly,
8
- registerBaseFunc, registerDerivative, samplingPoints,
9
- sign, sin, sinc, sinh, square, step, t, zero)
7
+ general_cosine, hanning, interp, mixing, mollifier, one,
8
+ poly, registerBaseFunc, registerDerivative,
9
+ samplingPoints, sign, sin, sinc, sinh, square, step, t,
10
+ zero)
10
11
  from .waveform_parser import wave_eval
@@ -28,7 +28,7 @@ def is_const(x: tuple[tuple, tuple]) -> bool:
28
28
  pass
29
29
 
30
30
 
31
- def basic_wave(Type, *args, shift=0) -> tuple[tuple, tuple]:
31
+ def basic_wave(Type: int, *args, shift: float = 0) -> tuple[tuple, tuple]:
32
32
  pass
33
33
 
34
34
 
@@ -53,7 +53,7 @@ def calc_parts(bounds: tuple,
53
53
  x: np.ndarray,
54
54
  function_lib: dict,
55
55
  min=-inf,
56
- max=inf) -> tuple[list[np.ndarray], type]:
56
+ max=inf) -> tuple[list[tuple[int, int, np.ndarray]], type]:
57
57
  pass
58
58
 
59
59
 
@@ -103,6 +103,7 @@ HYPERBOLICCHIRP: int = ...
103
103
  COSH: int = ...
104
104
  SINH: int = ...
105
105
  DRAG: int = ...
106
+ MOLLIFIER: int = ...
106
107
 
107
108
 
108
109
  def simplify(expr: tuple[tuple, tuple], eps: float) -> tuple[tuple, tuple]:
@@ -351,6 +351,18 @@ def _drag(t: np.ndarray, t0: float, freq: float, width: float, delta: float,
351
351
  return Omega_x * np.cos(wt) + Omega_y * np.sin(wt)
352
352
 
353
353
 
354
+ def _mollifier(t: np.ndarray, r: float, d: int):
355
+ x = t / r
356
+ if d == 0:
357
+ return np.exp(1 / (np.abs(x)**2 - 1) + 1)
358
+ else:
359
+ p = np.poly1d([-2, 0])
360
+ for n in range(1, d):
361
+ p = np.poly1d([1, 0, -2, 0, 1]) * p.deriv() + np.poly1d(
362
+ [-4 * n, 0, 4 * n - 2, 0]) * p
363
+ return np.exp(1 / (np.abs(x)**2 - 1) + 1) * p(x) / (1 - x**2)**(2 * d) / r**d
364
+
365
+
354
366
  LINEAR = registerBaseFunc(_LINEAR)
355
367
  GAUSSIAN = registerBaseFunc(_GAUSSIAN)
356
368
  ERF = registerBaseFunc(_ERF)
@@ -364,6 +376,7 @@ HYPERBOLICCHIRP = registerBaseFunc(_HYPERBOLICCHIRP)
364
376
  COSH = registerBaseFunc(_COSH)
365
377
  SINH = registerBaseFunc(_SINH)
366
378
  DRAG = registerBaseFunc(_drag)
379
+ MOLLIFIER = registerBaseFunc(_mollifier)
367
380
 
368
381
 
369
382
  def _d_LINEAR(shift, *args):
@@ -433,6 +446,10 @@ def _d_HYPERBOLICCHIRP(shift, f0, k, phi0):
433
446
  shift)), (-1, 1)), ), (2 * pi * f0, ))
434
447
 
435
448
 
449
+ def _d_MOLLIFIER(shift, r, d):
450
+ return (((((MOLLIFIER, r, d+1, shift), ), (1, )), ), (1, ))
451
+
452
+
436
453
  # register derivative
437
454
  registerDerivative(LINEAR, _d_LINEAR)
438
455
  registerDerivative(GAUSSIAN, _d_GAUSSIAN)
@@ -446,6 +463,7 @@ registerDerivative(SINH, _d_SINH)
446
463
  registerDerivative(LINEARCHIRP, _d_LINEARCHIRP)
447
464
  registerDerivative(EXPONENTIALCHIRP, _d_EXPONENTIALCHIRP)
448
465
  registerDerivative(HYPERBOLICCHIRP, _d_HYPERBOLICCHIRP)
466
+ registerDerivative(MOLLIFIER, _d_MOLLIFIER)
449
467
 
450
468
 
451
469
  def _cos_power_n(x, n):
@@ -1,2 +1,2 @@
1
1
  """Define version number here and read it from setup.py automatically"""
2
- __version__ = "2.1.1"
2
+ __version__ = "2.2.0"
@@ -1,16 +1,17 @@
1
1
  from fractions import Fraction
2
+ from typing import Generator, Iterable, cast
2
3
 
3
4
  import numpy as np
4
5
  from numpy import e, inf, pi
6
+ from numpy.typing import NDArray
5
7
  from scipy.signal import sosfilt
6
8
 
7
- from ._waveform import (_D, COS, COSH, DRAG, ERF, EXP, EXPONENTIALCHIRP,
8
- GAUSSIAN, HYPERBOLICCHIRP, INTERP, LINEAR, LINEARCHIRP,
9
- NDIGITS, SINC, SINH, _baseFunc, _baseFunc_latex,
10
- _const, _half, _one, _zero, add, basic_wave,
11
- calc_parts, filter, is_const, merge_waveform, mul, pow,
12
- registerBaseFunc, registerBaseFuncLatex,
13
- registerDerivative, shift, simplify, wave_sum)
9
+ from ._waveform import (
10
+ _D, COS, COSH, DRAG, ERF, EXP, EXPONENTIALCHIRP, GAUSSIAN, HYPERBOLICCHIRP,
11
+ INTERP, LINEAR, LINEARCHIRP, MOLLIFIER, NDIGITS, SINC, SINH, _baseFunc,
12
+ _baseFunc_latex, _const, _half, _one, _zero, add, basic_wave, calc_parts,
13
+ filter, is_const, merge_waveform, mul, pow, registerBaseFunc,
14
+ registerBaseFuncLatex, registerDerivative, shift, simplify, wave_sum)
14
15
 
15
16
 
16
17
  def _test_spec_num(num, spec):
@@ -124,7 +125,7 @@ class Waveform:
124
125
  self.start = None
125
126
  self.stop = None
126
127
  self.sample_rate = None
127
- self.filters = None
128
+ self.filters: tuple[np.ndarray, float] | None = None
128
129
  self.label = None
129
130
 
130
131
  @staticmethod
@@ -160,12 +161,14 @@ class Waveform:
160
161
  else:
161
162
  return min(self.stop, self._end(self.bounds, self.seq))
162
163
 
163
- def sample(self,
164
- sample_rate=None,
165
- out=None,
166
- chunk_size=None,
167
- function_lib=None,
168
- filters=None):
164
+ def sample(
165
+ self,
166
+ sample_rate=None,
167
+ out: np.ndarray | None = None,
168
+ chunk_size=None,
169
+ function_lib=None,
170
+ filters: tuple[np.ndarray, float] | None = None
171
+ ) -> np.ndarray | Iterable[np.ndarray]:
169
172
  if sample_rate is None:
170
173
  sample_rate = self.sample_rate
171
174
  if self.start is None or self.stop is None or sample_rate is None:
@@ -184,27 +187,34 @@ class Waveform:
184
187
  elif not sos.flags.writeable:
185
188
  sos = sos.copy()
186
189
  if initial:
187
- sig = sosfilt(sos, sig - initial) + initial
190
+ sig = cast(np.ndarray, sosfilt(sos,
191
+ sig - initial)) + initial
188
192
  else:
189
- sig = sosfilt(sos, sig)
190
- return sig
193
+ sig = cast(np.ndarray, sosfilt(sos, sig))
194
+ return cast(np.ndarray, sig)
191
195
  else:
192
196
  return self._sample_iter(sample_rate, chunk_size, out,
193
197
  function_lib, filters)
194
198
 
195
- def _sample_iter(self, sample_rate, chunk_size, out, function_lib,
196
- filters):
197
- start = self.start
199
+ def _sample_iter(
200
+ self, sample_rate, chunk_size, out: np.ndarray | None, function_lib,
201
+ filters: tuple[np.ndarray, float] | None
202
+ ) -> Generator[np.ndarray, None, None]:
203
+ start = cast(float, self.start)
198
204
  start_n = 0
199
205
  if filters is not None:
200
206
  sos, initial = filters
207
+ if not isinstance(sos, np.ndarray):
208
+ sos = np.array(sos)
209
+ elif not sos.flags.writeable:
210
+ sos = sos.copy()
201
211
  # zi = sosfilt_zi(sos)
202
212
  zi = np.zeros((sos.shape[0], 2))
203
213
  length = chunk_size / sample_rate
204
- while start < self.stop:
205
- if start + length > self.stop:
206
- length = self.stop - start
207
- stop = self.stop
214
+ while start < cast(float, self.stop):
215
+ if start + length > cast(float, self.stop):
216
+ length = cast(float, self.stop) - start
217
+ stop = cast(float, self.stop)
208
218
  size = round((stop - start) * sample_rate)
209
219
  else:
210
220
  stop = start + length
@@ -213,13 +223,17 @@ class Waveform:
213
223
 
214
224
  if filters is None:
215
225
  if out is not None:
216
- yield self.__call__(x,
217
- out=out[start_n:],
218
- function_lib=function_lib)
226
+ yield cast(
227
+ np.ndarray,
228
+ self.__call__(x,
229
+ out=out[start_n:],
230
+ function_lib=function_lib))
219
231
  else:
220
- yield self.__call__(x, function_lib=function_lib)
232
+ yield cast(np.ndarray,
233
+ self.__call__(x, function_lib=function_lib))
221
234
  else:
222
- sig = self.__call__(x, function_lib=function_lib)
235
+ sig = cast(np.ndarray,
236
+ self.__call__(x, function_lib=function_lib))
223
237
  if initial:
224
238
  sig -= initial
225
239
  sig, zi = sosfilt(sos, sig, zi=zi)
@@ -227,7 +241,7 @@ class Waveform:
227
241
  sig += initial
228
242
  if out is not None:
229
243
  out[start_n:start_n + size] = sig
230
- yield sig
244
+ yield cast(np.ndarray, sig)
231
245
 
232
246
  start = stop
233
247
  start_n += chunk_size
@@ -502,16 +516,21 @@ class Waveform:
502
516
  for start, stop, part in parts:
503
517
  out[start:stop] += part
504
518
 
505
- def __call__(self,
506
- x,
507
- frag=False,
508
- out=None,
509
- accumulate=False,
510
- function_lib=None):
519
+ def __call__(
520
+ self,
521
+ x,
522
+ frag=False,
523
+ out: np.ndarray | None = None,
524
+ accumulate=False,
525
+ function_lib=None
526
+ ) -> NDArray[np.float64] | list[tuple[int, int,
527
+ NDArray[np.float64]]] | np.float64:
511
528
  if function_lib is None:
512
529
  function_lib = _baseFunc
513
530
  if isinstance(x, (int, float, complex)):
514
- return self.__call__(np.array([x]), function_lib=function_lib)[0]
531
+ return cast(
532
+ NDArray[np.float64],
533
+ self.__call__(np.array([x]), function_lib=function_lib))[0]
515
534
  parts, dtype = calc_parts(self.bounds, self.seq, x, function_lib,
516
535
  self.min, self.max)
517
536
  if not frag:
@@ -961,6 +980,25 @@ def _format_DRAG(shift, *args):
961
980
  return f"DRAG(...)"
962
981
 
963
982
 
983
+ def _format_MOLLIFIER(shift, *args):
984
+ r = _num_latex(args[0])
985
+ d = _num_latex(args[1])
986
+ shift_str = _num_latex(-shift)
987
+ if shift_str == '0':
988
+ shift_str = ''
989
+ elif shift_str[0] != '-':
990
+ shift_str = '+' + shift_str
991
+
992
+ if d == '0':
993
+ return f"\\mathrm{{Mollifier}}\\left(t{shift_str}, r={r}\\right)"
994
+ elif d == '1':
995
+ return f"\\mathrm{{Mollifier}}'\\left(t{shift_str}, r={r}\\right)"
996
+ elif d == '2':
997
+ return f"\\mathrm{{Mollifier}}''\\left(t{shift_str}, r={r}\\right)"
998
+ else:
999
+ return f"\\mathrm{{Mollifier}}^{{({d})}}\\left(t{shift_str}, r={r}\\right)"
1000
+
1001
+
964
1002
  registerBaseFuncLatex(LINEAR, _format_LINEAR)
965
1003
  registerBaseFuncLatex(GAUSSIAN, _format_GAUSSIAN)
966
1004
  registerBaseFuncLatex(ERF, _format_ERF)
@@ -970,12 +1008,26 @@ registerBaseFuncLatex(EXP, _format_EXP)
970
1008
  registerBaseFuncLatex(COSH, _format_COSH)
971
1009
  registerBaseFuncLatex(SINH, _format_SINH)
972
1010
  registerBaseFuncLatex(DRAG, _format_DRAG)
1011
+ registerBaseFuncLatex(MOLLIFIER, _format_MOLLIFIER)
973
1012
 
974
1013
 
975
- def D(wav):
1014
+ def D(wav: Waveform, d: int = 1) -> Waveform:
976
1015
  """derivative
1016
+
1017
+ Parameters
1018
+ ----------
1019
+ wav : Waveform
1020
+ The waveform to take the derivative of.
1021
+ d : int, optional
1022
+ The order of the derivative, by default 1.
977
1023
  """
978
- return Waveform(bounds=wav.bounds, seq=tuple(_D(x) for x in wav.seq))
1024
+ assert d >= 0 and isinstance(d, int), "d must be a non-negative integer"
1025
+ if d == 0:
1026
+ return wav
1027
+ elif d == 1:
1028
+ return Waveform(bounds=wav.bounds, seq=tuple(_D(x) for x in wav.seq))
1029
+ else:
1030
+ return D(D(wav, d - 1), 1)
979
1031
 
980
1032
 
981
1033
  def convolve(a, b):
@@ -1185,6 +1237,40 @@ def slepian(duration, *arg):
1185
1237
  return wav * square(duration)
1186
1238
 
1187
1239
 
1240
+ def mollifier(width, plateau: float = 0.0, d: int = 0):
1241
+ """
1242
+ Mollifier function is a smooth function that is 1 at the origin and 0 outside a certain radius.
1243
+ It is defined as:
1244
+
1245
+ f(x) = exp(1 / ((x / r) ^ 2 - 1) + 1) in case |x| < r
1246
+ = 0 in case |x| >= r
1247
+ where r = width / 2 is the radius of the mollifier.
1248
+
1249
+ The parameter plateau is the width of the plateau.
1250
+ The parameter d is the order of the derivative.
1251
+ """
1252
+ assert d >= 0 and isinstance(d, int), "d must be a non-negative integer"
1253
+ assert width > 0, "width must be positive"
1254
+
1255
+ if plateau <= 0:
1256
+ return Waveform(bounds=(-0.5 * width, 0.5 * width, inf),
1257
+ seq=(_zero, basic_wave(MOLLIFIER, width / 2,
1258
+ d), _zero))
1259
+ else:
1260
+ return Waveform(bounds=(-0.5 * width - 0.5 * plateau, -0.5 * plateau,
1261
+ 0.5 * plateau, 0.5 * width + 0.5 * plateau,
1262
+ inf),
1263
+ seq=(_zero,
1264
+ basic_wave(MOLLIFIER,
1265
+ width / 2,
1266
+ d,
1267
+ shift=-0.5 * plateau), _one,
1268
+ basic_wave(MOLLIFIER,
1269
+ width / 2,
1270
+ d,
1271
+ shift=0.5 * plateau), _zero))
1272
+
1273
+
1188
1274
  def _poly(*a):
1189
1275
  """
1190
1276
  a[0] + a[1] * t + a[2] * t**2 + ...
@@ -1380,7 +1466,7 @@ def mixing(I,
1380
1466
  __all__ = [
1381
1467
  'D', 'Waveform', 'chirp', 'const', 'cos', 'cosh', 'coshPulse', 'cosPulse',
1382
1468
  'cut', 'drag', 'exp', 'function', 'gaussian', 'general_cosine', 'hanning',
1383
- 'interp', 'mixing', 'one', 'poly', 'registerBaseFunc',
1469
+ 'interp', 'mixing', 'mollifier', 'one', 'poly', 'registerBaseFunc',
1384
1470
  'registerDerivative', 'samplingPoints', 'sign', 'sin', 'sinc', 'sinh',
1385
1471
  'square', 'step', 't', 'zero'
1386
1472
  ]
@@ -30,9 +30,9 @@ class WaveformVisitor:
30
30
  self.functions = [
31
31
  'D', 'chirp', 'const', 'cos', 'cosh', 'coshPulse', 'cosPulse',
32
32
  'cut', 'drag', 'drag_sin', 'drag_sinx', 'exp', 'gaussian',
33
- 'general_cosine', 'hanning', 'interp', 'mixing', 'one', 'poly',
34
- 'samplingPoints', 'sign', 'sin', 'sinc', 'sinh', 'square', 'step',
35
- 't', 'zero'
33
+ 'general_cosine', 'hanning', 'interp', 'mixing', 'mollifier',
34
+ 'one', 'poly', 'samplingPoints', 'sign', 'sin', 'sinc', 'sinh',
35
+ 'square', 'step', 't', 'zero'
36
36
  ]
37
37
  self.constants = {
38
38
  'pi': waveform.pi,
@@ -226,14 +226,13 @@ def _generate_antlr_parser():
226
226
 
227
227
  # Generate ANTLR files
228
228
  try:
229
- result = subprocess.run([
230
- "antlr4", "-Dlanguage=Python3",
231
- str(grammar_file)
232
- ],
233
- cwd=str(current_dir),
234
- capture_output=True,
235
- text=True,
236
- check=True)
229
+ result = subprocess.run(
230
+ ["antlr4", "-Dlanguage=Python3",
231
+ str(grammar_file)],
232
+ cwd=str(current_dir),
233
+ capture_output=True,
234
+ text=True,
235
+ check=True)
237
236
  except (subprocess.CalledProcessError, FileNotFoundError) as e:
238
237
  # Fall back to java command if antlr4 command is not available
239
238
  try:
@@ -258,7 +257,7 @@ def parse_waveform_expression(expr: str) -> waveform.Waveform:
258
257
  try:
259
258
  # Generate parser files if they don't exist
260
259
  # _generate_antlr_parser()
261
-
260
+
262
261
  # Import generated ANTLR classes
263
262
  from .WaveformLexer import WaveformLexer
264
263
  from .WaveformParser import WaveformParser
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waveforms
3
- Version: 2.1.1
3
+ Version: 2.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>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes