• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

feihoo87 / waveforms / 7089397776

04 Dec 2023 04:10PM UTC coverage: 42.666% (-0.5%) from 43.134%
7089397776

push

github

feihoo87
update

17 of 23 new or added lines in 3 files covered. (73.91%)

2 existing lines in 1 file now uncovered.

7255 of 17004 relevant lines covered (42.67%)

3.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.01
/waveforms/waveform.py
1
from fractions import Fraction
9✔
2

3
import numpy as np
9✔
4
from numpy import e, inf, pi
9✔
5
from scipy.signal import sosfilt
9✔
6

7
from ._waveform import (_D, COS, COSH, DRAG, ERF, EXP, EXPONENTIALCHIRP,
9✔
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)
14

15

16
def _test_spec_num(num, spec):
9✔
17
    x = Fraction(num / spec).limit_denominator(1000000000)
×
18
    if x.denominator <= 24:
×
19
        return True, x, 1
×
20
    x = Fraction(spec * num).limit_denominator(1000000000)
×
21
    if x.denominator <= 24:
×
22
        return True, x, -1
×
23
    return False, x, 0
×
24

25

26
def _spec_num_latex(num):
9✔
27
    for spec, spec_latex in [(1, ''), (np.sqrt(2), '\\sqrt{2}'),
×
28
                             (np.sqrt(3), '\\sqrt{3}'),
29
                             (np.sqrt(5), '\\sqrt{5}'),
30
                             (np.log(2), '\\log{2}'), (np.log(3), '\\log{3}'),
31
                             (np.log(5), '\\log{5}'), (np.e, 'e'),
32
                             (np.pi, '\\pi'), (np.pi**2, '\\pi^2'),
33
                             (np.sqrt(np.pi), '\\sqrt{\\pi}')]:
34
        flag, x, sign = _test_spec_num(num, spec)
×
35
        if flag:
×
36
            if sign < 0:
×
37
                spec_latex = f"\\frac{{{1}}}{{{spec_latex}}}"
×
38
            if x.denominator == 1:
×
39
                if x.numerator == 1:
×
40
                    return f"{spec_latex}"
×
41
                else:
42
                    return f"{x.numerator:g}{spec_latex}"
×
43
            else:
44
                if x.numerator < 0:
×
45
                    return f"-\\frac{{{-x.numerator}}}{{{x.denominator}}}{spec_latex}"
×
46
                else:
47
                    return f"\\frac{{{x.numerator}}}{{{x.denominator}}}{spec_latex}"
×
48
    return f"{num:g}"
×
49

50

51
def _num_latex(num):
9✔
52
    if num == -np.inf:
×
53
        return r"-\infty"
×
54
    elif num == np.inf:
×
55
        return r"\infty"
×
56
    if num.imag > 0:
×
57
        return f"\\left({_num_latex(num.real)}+{_num_latex(num.imag)}j\\right)"
×
58
    elif num.imag < 0:
×
59
        return f"\\left({_num_latex(num.real)}-{_num_latex(-num.imag)}j\\right)"
×
60
    s = _spec_num_latex(num.real)
×
61
    if s == '' and round(num.real) == 1:
×
62
        return '1'
×
63
    if "e" in s:
×
64
        a, n = s.split("e")
×
65
        n = float(n)
×
66
        s = f"{a} \\times 10^{{{n:g}}}"
×
67
    return s
×
68

69

70
def _fun_latex(fun):
9✔
71
    funID, *args, shift = fun
×
72
    if _baseFunc_latex[funID] is None:
×
73
        shift = _num_latex(shift)
×
74
        if shift == "0":
×
75
            shift = ""
×
76
        elif shift[0] != '-':
×
77
            shift = "+" + shift
×
78
        return r"\mathrm{Func}" + f"{funID}(t{shift}, ...)"
×
79
    return _baseFunc_latex[funID](shift, *args)
×
80

81

82
def _wav_latex(wav):
9✔
83

UNCOV
84
    if wav == _zero:
×
85
        return "0"
×
NEW
86
    elif is_const(wav):
×
87
        return f"{wav[1][0]}"
×
88

89
    sum_expr = []
×
90
    for mul, amp in zip(*wav):
×
91
        if mul == ((), ()):
×
92
            sum_expr.append(_num_latex(amp))
×
93
            continue
×
94
        mul_expr = []
×
95
        amp = _num_latex(amp)
×
96
        if amp != "1":
×
97
            mul_expr.append(amp)
×
98
        for fun, n in zip(*mul):
×
99
            fun_expr = _fun_latex(fun)
×
100
            if n != 1:
×
101
                mul_expr.append(fun_expr + "^{" + f"{n}" + "}")
×
102
            else:
103
                mul_expr.append(fun_expr)
×
104
        sum_expr.append(''.join(mul_expr))
×
105

106
    ret = sum_expr[0]
×
107
    for expr in sum_expr[1:]:
×
108
        if expr[0] == '-':
×
109
            ret += expr
×
110
        else:
111
            ret += "+" + expr
×
112
    return ret
×
113

114

115
class Waveform:
9✔
116
    __slots__ = ('bounds', 'seq', 'max', 'min', 'start', 'stop', 'sample_rate',
9✔
117
                 'filters', 'label')
118

119
    def __init__(self, bounds=(+inf, ), seq=(_zero, ), min=-inf, max=inf):
9✔
120
        self.bounds = bounds
9✔
121
        self.seq = seq
9✔
122
        self.max = max
9✔
123
        self.min = min
9✔
124
        self.start = None
9✔
125
        self.stop = None
9✔
126
        self.sample_rate = None
9✔
127
        self.filters = None
9✔
128
        self.label = None
9✔
129

130
    def _begin(self):
9✔
131
        for i, s in enumerate(self.seq):
×
132
            if s is not _zero:
×
133
                if i == 0:
×
134
                    return -inf
×
135
                return self.bounds[i - 1]
×
136
        return inf
×
137

138
    def _end(self):
9✔
139
        N = len(self.bounds)
×
140
        for i, s in enumerate(self.seq[::-1]):
×
141
            if s is not _zero:
×
142
                if i == 0:
×
143
                    return inf
×
144
                return self.bounds[N - i - 1]
×
145
        return -inf
×
146

147
    @property
9✔
148
    def begin(self):
9✔
149
        if self.start is None:
×
150
            return self._begin()
×
151
        else:
152
            return max(self.start, self._begin())
×
153

154
    @property
9✔
155
    def end(self):
9✔
156
        if self.stop is None:
×
157
            return self._end()
×
158
        else:
159
            return min(self.stop, self._end())
×
160

161
    def sample(self,
9✔
162
               sample_rate=None,
163
               out=None,
164
               chunk_size=None,
165
               function_lib=None,
166
               filters=None):
167
        if sample_rate is None:
9✔
168
            sample_rate = self.sample_rate
9✔
169
        if self.start is None or self.stop is None or sample_rate is None:
9✔
170
            raise ValueError(
×
171
                f'Waveform is not initialized. {self.start=}, {self.stop=}, {sample_rate=}'
172
            )
173
        if filters is None:
9✔
174
            filters = self.filters
9✔
175
        if chunk_size is None:
9✔
176
            x = np.arange(self.start, self.stop, 1 / sample_rate)
9✔
177
            sig = self.__call__(x, out=out, function_lib=function_lib)
9✔
178
            if filters is not None:
9✔
179
                sos, initial = filters
9✔
180
                if initial:
9✔
181
                    sig = sosfilt(sos, sig - initial) + initial
×
182
                else:
183
                    sig = sosfilt(sos, sig)
9✔
184
            return sig
9✔
185
        else:
186
            return self._sample_iter(sample_rate, chunk_size, out,
×
187
                                     function_lib, filters)
188

189
    def _sample_iter(self, sample_rate, chunk_size, out, function_lib,
9✔
190
                     filters):
191
        start = self.start
×
192
        start_n = 0
×
193
        if filters is not None:
×
194
            sos, initial = filters
×
195
            # zi = sosfilt_zi(sos)
196
            zi = np.zeros((sos.shape[0], 2))
×
197
        length = chunk_size / sample_rate
×
198
        while start < self.stop:
×
199
            if start + length > self.stop:
×
200
                length = self.stop - start
×
201
                stop = self.stop
×
202
                size = round((stop - start) * sample_rate)
×
203
            else:
204
                stop = start + length
×
205
                size = chunk_size
×
206
            x = np.linspace(start, stop, size, endpoint=False)
×
207
            if out is not None:
×
208
                if filters is None:
×
209
                    yield self.__call__(x,
×
210
                                        out=out[start_n:],
211
                                        function_lib=function_lib)
212
                else:
213
                    if initial:
×
214
                        sig -= initial
×
215
                    sig, zi = sosfilt(sos,
×
216
                                      self.__call__(x,
217
                                                    function_lib=function_lib),
218
                                      zi=zi)
219
                    if initial:
×
220
                        sig += initial
×
221
                    out[start_n:start_n + size] = sig
×
222
                    yield sig
×
223
            else:
224
                if filters is None:
×
225
                    yield self.__call__(x, function_lib=function_lib)
×
226
                else:
227
                    if initial:
×
228
                        sig -= initial
×
229
                    sig, zi = sosfilt(sos,
×
230
                                      self.__call__(x,
231
                                                    function_lib=function_lib),
232
                                      zi=zi)
233
                    if initial:
×
234
                        sig += initial
×
235
                    yield sig
×
236
            start = stop
×
237
            start_n += chunk_size
×
238

239
    @staticmethod
9✔
240
    def _tolist(bounds, seq, ret=None):
9✔
241
        if ret is None:
9✔
242
            ret = []
×
243
        ret.append(len(bounds))
9✔
244
        for seq, b in zip(seq, bounds):
9✔
245
            ret.append(b)
9✔
246
            tlist, amplist = seq
9✔
247
            ret.append(len(amplist))
9✔
248
            for t, amp in zip(tlist, amplist):
9✔
249
                ret.append(amp)
9✔
250
                mtlist, nlist = t
9✔
251
                ret.append(len(nlist))
9✔
252
                for fun, n in zip(mtlist, nlist):
9✔
253
                    ret.append(n)
9✔
254
                    ret.append(len(fun))
9✔
255
                    ret.extend(fun)
9✔
256
        return ret
9✔
257

258
    @staticmethod
9✔
259
    def _fromlist(l, pos=0):
9✔
260

261
        def _read(l, pos, size):
9✔
262
            try:
9✔
263
                return tuple(l[pos:pos + size]), pos + size
9✔
264
            except:
×
265
                raise ValueError('Invalid waveform format')
×
266

267
        (nseg, ), pos = _read(l, pos, 1)
9✔
268
        bounds = []
9✔
269
        seq = []
9✔
270
        for _ in range(nseg):
9✔
271
            (b, nsum), pos = _read(l, pos, 2)
9✔
272
            bounds.append(b)
9✔
273
            amp = []
9✔
274
            t = []
9✔
275
            for _ in range(nsum):
9✔
276
                (a, nmul), pos = _read(l, pos, 2)
9✔
277
                amp.append(a)
9✔
278
                nlst = []
9✔
279
                mt = []
9✔
280
                for _ in range(nmul):
9✔
281
                    (n, nfun), pos = _read(l, pos, 2)
9✔
282
                    nlst.append(n)
9✔
283
                    fun, pos = _read(l, pos, nfun)
9✔
284
                    mt.append(fun)
9✔
285
                t.append((tuple(mt), tuple(nlst)))
9✔
286
            seq.append((tuple(t), tuple(amp)))
9✔
287

288
        return tuple(bounds), tuple(seq), pos
9✔
289

290
    def tolist(self):
9✔
291
        l = [self.max, self.min, self.start, self.stop, self.sample_rate]
9✔
292
        if self.filters is None:
9✔
293
            l.append(None)
9✔
294
        else:
295
            sos, initial = self.filters
9✔
296
            sos = list(sos.reshape(-1))
9✔
297
            l.append(len(sos))
9✔
298
            l.extend(sos)
9✔
299
            l.append(initial)
9✔
300

301
        return self._tolist(self.bounds, self.seq, l)
9✔
302

303
    @classmethod
9✔
304
    def fromlist(cls, l):
9✔
305
        w = cls()
9✔
306
        pos = 6
9✔
307
        (w.max, w.min, w.start, w.stop, w.sample_rate, sos_size) = l[:pos]
9✔
308
        if sos_size is not None:
9✔
309
            sos = np.array(l[pos:pos + sos_size]).reshape(-1, 6)
9✔
310
            pos += sos_size
9✔
311
            initial = l[pos]
9✔
312
            pos += 1
9✔
313
            w.filters = sos, initial
9✔
314

315
        w.bounds, w.seq, pos = cls._fromlist(l, pos)
9✔
316
        return w
9✔
317

318
    def totree(self):
9✔
319
        if self.filters is None:
9✔
320
            header = (self.max, self.min, self.start, self.stop,
9✔
321
                      self.sample_rate, None)
322
        else:
323
            header = (self.max, self.min, self.start, self.stop,
9✔
324
                      self.sample_rate, self.filters)
325
        body = []
9✔
326

327
        for seq, b in zip(self.seq, self.bounds):
9✔
328
            tlist, amplist = seq
9✔
329
            new_seq = []
9✔
330
            for t, amp in zip(tlist, amplist):
9✔
331
                mtlist, nlist = t
9✔
332
                new_t = []
9✔
333
                for fun, n in zip(mtlist, nlist):
9✔
334
                    new_t.append((n, fun))
9✔
335
                new_seq.append((amp, tuple(new_t)))
9✔
336
            body.append((b, tuple(new_seq)))
9✔
337
        return header, tuple(body)
9✔
338

339
    @staticmethod
9✔
340
    def fromtree(tree):
9✔
341
        w = Waveform()
9✔
342
        header, body = tree
9✔
343

344
        (w.max, w.min, w.start, w.stop, w.sample_rate, w.filters) = header
9✔
345
        bounds = []
9✔
346
        seqs = []
9✔
347
        for b, seq in body:
9✔
348
            bounds.append(b)
9✔
349
            amp_list = []
9✔
350
            t_list = []
9✔
351
            for amp, t in seq:
9✔
352
                amp_list.append(amp)
9✔
353
                n_list = []
9✔
354
                mt_list = []
9✔
355
                for n, mt in t:
9✔
356
                    n_list.append(n)
9✔
357
                    mt_list.append(mt)
9✔
358
                t_list.append((tuple(mt_list), tuple(n_list)))
9✔
359
            seqs.append((tuple(t_list), tuple(amp_list)))
9✔
360
        w.bounds = tuple(bounds)
9✔
361
        w.seq = tuple(seqs)
9✔
362
        return w
9✔
363

364
    def simplify(self, eps=1e-15):
9✔
365
        seq = [simplify(self.seq[0], eps)]
9✔
366
        bounds = [self.bounds[0]]
9✔
367
        for expr, b in zip(self.seq[1:], self.bounds[1:]):
9✔
368
            expr = simplify(expr, eps)
9✔
369
            if expr == seq[-1]:
9✔
370
                seq.pop()
×
371
                bounds.pop()
×
372
            seq.append(expr)
9✔
373
            bounds.append(b)
9✔
374
        return Waveform(tuple(bounds), tuple(seq))
9✔
375

376
    def filter(self, low=0, high=inf, eps=1e-15):
9✔
377
        seq = []
9✔
378
        for expr in self.seq:
9✔
379
            seq.append(filter(expr, low, high, eps))
9✔
380
        return Waveform(self.bounds, tuple(seq))
9✔
381

382
    def _comb(self, other, oper):
9✔
383
        return Waveform(*merge_waveform(self.bounds, self.seq, other.bounds,
9✔
384
                                        other.seq, oper))
385

386
    def __pow__(self, n):
9✔
387
        return Waveform(self.bounds, tuple(pow(w, n) for w in self.seq))
9✔
388

389
    def __add__(self, other):
9✔
390
        if isinstance(other, Waveform):
9✔
391
            return self._comb(other, add)
9✔
392
        else:
393
            return self + const(other)
9✔
394

395
    def __radd__(self, v):
9✔
396
        return const(v) + self
×
397

398
    def __ior__(self, other):
9✔
399
        return self | other
×
400

401
    def __or__(self, other):
9✔
402
        if isinstance(other, (int, float, complex)):
×
403
            other = const(other)
×
404
        w = self.marker + other.marker
×
405

406
        def _or(a, b):
×
407
            if a != _zero or b != _zero:
×
408
                return _one
×
409
            else:
410
                return _zero
×
411

412
        return self._comb(other, _or)
×
413

414
    def __iand__(self, other):
9✔
415
        return self & other
×
416

417
    def __and__(self, other):
9✔
418
        if isinstance(other, (int, float, complex)):
×
419
            other = const(other)
×
420
        w = self.marker + other.marker
×
421

422
        def _and(a, b):
×
423
            if a != _zero and b != _zero:
×
424
                return _one
×
425
            else:
426
                return _zero
×
427

428
        return self._comb(other, _and)
×
429

430
    @property
9✔
431
    def marker(self):
9✔
432
        w = self.simplify()
×
433
        return Waveform(w.bounds,
×
434
                        tuple(_zero if s == _zero else _one for s in w.seq))
435

436
    def mask(self, edge=0):
9✔
437
        w = self.marker
×
438
        in_wave = w.seq[0] == _zero
×
439
        bounds = []
×
440
        seq = []
×
441

442
        if w.seq[0] == _zero:
×
443
            in_wave = False
×
444
            b = w.bounds[0] - edge
×
445
            bounds.append(b)
×
446
            seq.append(_zero)
×
447

448
        for b, s in zip(w.bounds[1:], w.seq[1:]):
×
449
            if not in_wave and s != _zero:
×
450
                in_wave = True
×
451
                bounds.append(b + edge)
×
452
                seq.append(_one)
×
453
            elif in_wave and s == _zero:
×
454
                in_wave = False
×
455
                b = b - edge
×
456
                if b > bounds[-1]:
×
457
                    bounds.append(b)
×
458
                    seq.append(_zero)
×
459
                else:
460
                    bounds.pop()
×
461
                    bounds.append(b)
×
462
        return Waveform(tuple(bounds), tuple(seq))
×
463

464
    def __mul__(self, other):
9✔
465
        if isinstance(other, Waveform):
9✔
466
            return self._comb(other, mul)
9✔
467
        else:
468
            return self * const(other)
9✔
469

470
    def __rmul__(self, v):
9✔
471
        return const(v) * self
9✔
472

473
    def __truediv__(self, other):
9✔
474
        if isinstance(other, Waveform):
9✔
475
            raise TypeError('division by waveform')
×
476
        else:
477
            return self * const(1 / other)
9✔
478

479
    def __neg__(self):
9✔
480
        return -1 * self
9✔
481

482
    def __sub__(self, other):
9✔
483
        return self + (-other)
9✔
484

485
    def __rsub__(self, v):
9✔
486
        return v + (-self)
×
487

488
    def __rshift__(self, time):
9✔
489
        return Waveform(
9✔
490
            tuple(round(bound + time, NDIGITS) for bound in self.bounds),
491
            tuple(shift(expr, time) for expr in self.seq))
492

493
    def __lshift__(self, time):
9✔
494
        return self >> (-time)
9✔
495

496
    @staticmethod
9✔
497
    def _merge_parts(
9✔
498
        parts: list[tuple[int, int, np.ndarray | int | float | complex]],
499
        out: list[tuple[int, int, np.ndarray | int | float | complex]]
500
    ) -> list[tuple[int, int, np.ndarray | int | float | complex]]:
501
        # TODO: merge parts
502
        raise NotImplementedError
×
503

504
    @staticmethod
9✔
505
    def _fill_parts(parts, out):
9✔
506
        for start, stop, part in parts:
9✔
507
            out[start:stop] += part
9✔
508

509
    def __call__(self,
9✔
510
                 x,
511
                 frag=False,
512
                 out=None,
513
                 accumulate=False,
514
                 function_lib=None):
515
        if function_lib is None:
9✔
516
            function_lib = _baseFunc
9✔
517
        if isinstance(x, (int, float, complex)):
9✔
518
            return self.__call__(np.array([x]), function_lib=function_lib)[0]
×
519
        parts, dtype = calc_parts(self.bounds, self.seq, x, function_lib,
9✔
520
                                  self.min, self.max)
521
        if not frag:
9✔
522
            if out is None:
9✔
523
                out = np.zeros_like(x, dtype=dtype)
9✔
524
            elif not accumulate:
×
525
                out *= 0
×
526
            self._fill_parts(parts, out)
9✔
527
        else:
528
            if out is None:
×
529
                return parts
×
530
            else:
531
                if not accumulate:
×
532
                    out.clear()
×
533
                    out.extend(parts)
×
534
                else:
535
                    self._merge_parts(parts, out)
×
536
        return out
9✔
537

538
    def __hash__(self):
9✔
539
        return hash((self.max, self.min, self.start, self.stop,
×
540
                     self.sample_rate, self.bounds, self.seq))
541

542
    def __eq__(self, o: object) -> bool:
9✔
543
        if isinstance(o, (int, float, complex)):
9✔
544
            return self == const(o)
×
545
        elif isinstance(o, Waveform):
9✔
546
            a = self.simplify()
9✔
547
            b = o.simplify()
9✔
548
            return a.seq == b.seq and a.bounds == b.bounds and (
9✔
549
                a.max, a.min, a.start, a.stop) == (b.max, b.min, b.start,
550
                                                   b.stop)
551
        else:
552
            return False
×
553

554
    def _repr_latex_(self):
9✔
555
        parts = []
×
556
        start = -np.inf
×
557
        for end, wav in zip(self.bounds, self.seq):
×
558
            e_str = _wav_latex(wav)
×
559
            start_str = _num_latex(start)
×
560
            end_str = _num_latex(end)
×
561
            parts.append(e_str + r",~~&t\in" + f"({start_str},{end_str}" +
×
562
                         (']' if end < np.inf else ')'))
563
            start = end
×
564
        if len(parts) == 1:
×
565
            expr = ''.join(['f(t)=', *parts[0].split('&')])
×
566
        else:
567
            expr = '\n'.join([
×
568
                r"f(t)=\begin{cases}", (r"\\" + '\n').join(parts),
569
                r"\end{cases}"
570
            ])
571
        return "$$\n{}\n$$".format(expr)
×
572

573
    def _play(self, time_unit, volume=1.0):
9✔
574
        import pyaudio
×
575

576
        CHUNK = 1024
×
577
        RATE = 48000
×
578

579
        dynamic_volume = 1.0
×
580
        amp = 2**15 * 0.999 * volume * dynamic_volume
×
581

582
        p = pyaudio.PyAudio()
×
583
        try:
×
584
            stream = p.open(format=pyaudio.paInt16,
×
585
                            channels=1,
586
                            rate=RATE,
587
                            output=True)
588
            try:
×
589
                for data in self.sample(sample_rate=RATE / time_unit,
×
590
                                        chunk_size=CHUNK):
591
                    lim = np.abs(data).max()
×
592
                    if lim > 0 and dynamic_volume > 1.0 / lim:
×
593
                        dynamic_volume = 1.0 / lim
×
594
                        amp = 2**15 * 0.99 * volume * dynamic_volume
×
595
                    data = (amp * data).astype(np.int16)
×
596
                    stream.write(bytes(data.data))
×
597
            finally:
598
                stream.stop_stream()
×
599
                stream.close()
×
600
        finally:
601
            p.terminate()
×
602

603
    def play(self, time_unit=1, volume=1.0):
9✔
604
        import multiprocessing as mp
×
605
        p = mp.Process(target=self._play,
×
606
                       args=(time_unit, volume),
607
                       daemon=True)
608
        p.start()
×
609

610

611
class WaveVStack(Waveform):
9✔
612

613
    def __init__(self, wlist: list[Waveform] = []):
9✔
614
        self.wlist = [(w.bounds, w.seq) for w in wlist]
9✔
615
        self.start = None
9✔
616
        self.stop = None
9✔
617
        self.sample_rate = None
9✔
618
        self.offset = 0
9✔
619
        self.shift = 0
9✔
620
        self.filters = None
9✔
621
        self.label = None
9✔
622
        self.function_lib = None
9✔
623

624
    def __call__(self, x, frag=False, out=None, function_lib=None):
9✔
625
        assert frag is False, 'WaveVStack does not support frag mode'
9✔
626
        out = np.full_like(x, self.offset, dtype=complex)
9✔
627
        if self.shift != 0:
9✔
628
            x = x - self.shift
9✔
629
        if function_lib is None:
9✔
630
            if self.function_lib is None:
9✔
631
                function_lib = _baseFunc
9✔
632
            else:
633
                function_lib = self.function_lib
×
634
        for bounds, seq in self.wlist:
9✔
635
            parts, dtype = calc_parts(bounds, seq, x, function_lib)
9✔
636
            self._fill_parts(parts, out)
9✔
637
        return out.real
9✔
638

639
    def tolist(self):
9✔
640
        l = [
9✔
641
            self.start,
642
            self.stop,
643
            self.offset,
644
            self.shift,
645
            self.sample_rate,
646
        ]
647
        if self.filters is None:
9✔
648
            l.append(None)
9✔
649
        else:
650
            sos, initial = self.filters
9✔
651
            sos = list(sos.reshape(-1))
9✔
652
            l.append(len(sos))
9✔
653
            l.extend(sos)
9✔
654
            l.append(initial)
9✔
655
        l.append(len(self.wlist))
9✔
656
        for bounds, seq in self.wlist:
9✔
657
            self._tolist(bounds, seq, l)
9✔
658
        return l
9✔
659

660
    @classmethod
9✔
661
    def fromlist(cls, l):
9✔
662
        w = cls()
9✔
663
        pos = 6
9✔
664
        w.start, w.stop, w.offset, w.shift, w.sample_rate, sos_size = l[:pos]
9✔
665
        if sos_size is not None:
9✔
666
            sos = np.array(l[pos:pos + sos_size]).reshape(-1, 6)
9✔
667
            pos += sos_size
9✔
668
            initial = l[pos]
9✔
669
            pos += 1
9✔
670
            w.filters = sos, initial
9✔
671
        n = l[pos]
9✔
672
        pos += 1
9✔
673
        for _ in range(n):
9✔
674
            bounds, seq, pos = cls._fromlist(l, pos)
9✔
675
            w.wlist.append((bounds, seq))
9✔
676
        return w
9✔
677

678
    def simplify(self, eps=1e-15):
9✔
679
        if not self.wlist:
9✔
680
            return zero()
9✔
681
        bounds, seq = wave_sum(self.wlist)
9✔
682
        wav = Waveform(bounds=bounds, seq=seq)
9✔
683
        if self.offset != 0:
9✔
684
            wav += self.offset
×
685
        if self.shift != 0:
9✔
686
            wav >>= self.shift
×
687
        wav = wav.simplify(eps)
9✔
688
        wav.start = self.start
9✔
689
        wav.stop = self.stop
9✔
690
        wav.sample_rate = self.sample_rate
9✔
691
        return wav
9✔
692

693
    @staticmethod
9✔
694
    def _rshift(wlist, time):
9✔
695
        if time == 0:
×
696
            return wlist
×
697
        return [(tuple(round(bound + time, NDIGITS) for bound in bounds),
×
698
                 tuple(shift(expr, time) for expr in seq))
699
                for bounds, seq in wlist]
700

701
    def __rshift__(self, time):
9✔
702
        ret = WaveVStack()
9✔
703
        ret.wlist = self.wlist
9✔
704
        ret.sample_rate = self.sample_rate
9✔
705
        ret.start = self.start
9✔
706
        ret.stop = self.stop
9✔
707
        ret.shift = self.shift + time
9✔
708
        ret.offset = self.offset
9✔
709
        return ret
9✔
710

711
    def __add__(self, other):
9✔
712
        ret = WaveVStack()
9✔
713
        ret.wlist.extend(self.wlist)
9✔
714
        if isinstance(other, WaveVStack):
9✔
715
            if other.shift != self.shift:
×
716
                ret.wlist = self._rshift(ret.wlist, self.shift)
×
717
                ret.wlist.extend(self._rshift(other.wlist, other.shift))
×
718
            else:
719
                ret.wlist.extend(other.wlist)
×
720
            ret.offset = self.offset + other.offset
×
721
        elif isinstance(other, Waveform):
9✔
722
            other <<= self.shift
9✔
723
            ret.wlist.append((other.bounds, other.seq))
9✔
724
        else:
725
            # ret.wlist.append(((+inf, ), (_const(1.0 * other), )))
726
            ret.offset += other
9✔
727
        return ret
9✔
728

729
    def __radd__(self, v):
9✔
730
        return self + v
×
731

732
    def __mul__(self, other):
9✔
733
        if isinstance(other, Waveform):
9✔
734
            other = other.simplify() << self.shift
9✔
735
            ret = WaveVStack([Waveform(*w) * other for w in self.wlist])
9✔
736
            if self.offset != 0:
9✔
737
                w = other * self.offset
×
738
                ret.wlist.append((w.bounds, w.seq))
×
739
            return ret
9✔
740
        else:
741
            ret = WaveVStack([Waveform(*w) * other for w in self.wlist])
×
742
            ret.offset = self.offset * other
×
743
            return ret
×
744

745
    def __rmul__(self, v):
9✔
746
        return self * v
×
747

748
    def __eq__(self, other):
9✔
749
        if self.wlist:
×
750
            return False
×
751
        else:
752
            return zero() == other
×
753

754
    def _repr_latex_(self):
9✔
755
        return r"\sum_{i=1}^{" + f"{len(self.wlist)}" + r"}" + r"f_i(t)"
×
756

757
    def __getstate__(self) -> tuple:
9✔
758
        function_lib = self.function_lib
9✔
759
        if function_lib:
9✔
760
            try:
×
761
                import dill
×
762
                function_lib = dill.dumps(function_lib)
×
763
            except:
×
764
                function_lib = None
×
765
        return (self.wlist, self.start, self.stop, self.sample_rate,
9✔
766
                self.offset, self.shift, self.filters, self.label,
767
                function_lib)
768

769
    def __setstate__(self, state: tuple) -> None:
9✔
770
        (self.wlist, self.start, self.stop, self.sample_rate, self.offset,
9✔
771
         self.shift, self.filters, self.label, function_lib) = state
772
        if function_lib:
9✔
773
            try:
×
774
                import dill
×
775
                function_lib = dill.loads(function_lib)
×
776
            except:
×
777
                function_lib = None
×
778
        self.function_lib = function_lib
9✔
779

780

781
def play(data, rate=48000):
9✔
782
    import io
×
783

784
    import pyaudio
×
785

786
    CHUNK = 1024
×
787

788
    max_amp = np.max(np.abs(data))
×
789

790
    if max_amp > 1:
×
791
        data /= max_amp
×
792

793
    data = np.array(2**15 * 0.999 * data, dtype=np.int16)
×
794
    buff = io.BytesIO(data.data)
×
795
    p = pyaudio.PyAudio()
×
796

797
    try:
×
798
        stream = p.open(format=pyaudio.paInt16,
×
799
                        channels=1,
800
                        rate=rate,
801
                        output=True)
802
        try:
×
803
            while True:
×
804
                data = buff.read(CHUNK)
×
805
                if data:
×
806
                    stream.write(data)
×
807
                else:
808
                    break
×
809
        finally:
810
            stream.stop_stream()
×
811
            stream.close()
×
812
    finally:
813
        p.terminate()
×
814

815

816
_zero_waveform = Waveform()
9✔
817
_one_waveform = Waveform(seq=(_one, ))
9✔
818

819

820
def zero():
9✔
821
    return _zero_waveform
9✔
822

823

824
def one():
9✔
825
    return _one_waveform
×
826

827

828
def const(c):
9✔
829
    return Waveform(seq=(_const(1.0 * c), ))
9✔
830

831

832
# register base function
833
def _format_LINEAR(shift, *args):
9✔
834
    if shift != 0:
×
835
        shift = _num_latex(-shift)
×
836
        if shift[0] == '-':
×
837
            return f"(t{shift})"
×
838
        else:
839
            return f"(t+{shift})"
×
840
    else:
841
        return 't'
×
842

843

844
def _format_GAUSSIAN(shift, *args):
9✔
845
    sigma = _num_latex(args[0] / np.sqrt(2))
×
846
    shift = _num_latex(-shift)
×
847
    if shift != '0':
×
848
        if shift[0] != '-':
×
849
            shift = '+' + shift
×
850
        if sigma == '1':
×
851
            return ('\\exp\\left[-\\frac{\\left(t' + shift +
×
852
                    '\\right)^2}{2}\\right]')
853
        else:
854
            return ('\\exp\\left[-\\frac{1}{2}\\left(\\frac{t' + shift + '}{' +
×
855
                    sigma + '}\\right)^2\\right]')
856
    else:
857
        if sigma == '1':
×
858
            return ('\\exp\\left(-\\frac{t^2}{2}\\right)')
×
859
        else:
860
            return ('\\exp\\left[-\\frac{1}{2}\\left(\\frac{t}{' + sigma +
×
861
                    '}\\right)^2\\right]')
862

863

864
def _format_SINC(shift, *args):
9✔
865
    shift = _num_latex(-shift)
×
866
    bw = _num_latex(args[0])
×
867
    if shift != '0':
×
868
        if shift[0] != '-':
×
869
            shift = '+' + shift
×
870
        if bw == '1':
×
871
            return '\\mathrm{sinc}(t' + shift + ')'
×
872
        else:
873
            return '\\mathrm{sinc}[' + bw + '(t' + shift + ')]'
×
874
    else:
875
        if bw == '1':
×
876
            return '\\mathrm{sinc}(t)'
×
877
        else:
878
            return '\\mathrm{sinc}(' + bw + 't)'
×
879

880

881
def _format_COSINE(shift, *args):
9✔
882
    freq = args[0] / 2 / np.pi
×
883
    phase = -shift * freq
×
884
    freq = _num_latex(freq)
×
885
    if freq == '1':
×
886
        freq = ''
×
887
    phase = _num_latex(phase)
×
888
    if phase == '0':
×
889
        phase = ''
×
890
    elif phase[0] != '-':
×
891
        phase = '+' + phase
×
892
    if phase != '':
×
893
        return f'\\cos\\left[2\\pi\\left({freq}t{phase}\\right)\\right]'
×
894
    elif freq != '':
×
895
        return f'\\cos\\left(2\\pi\\times {freq}t\\right)'
×
896
    else:
897
        return '\\cos\\left(2\\pi t\\right)'
×
898

899

900
def _format_ERF(shift, *args):
9✔
901
    if shift > 0:
×
902
        return '\\mathrm{erf}(\\frac{t-' + f"{_num_latex(shift)}" + '}{' + f'{args[0]:g}' + '})'
×
903
    elif shift < 0:
×
904
        return '\\mathrm{erf}(\\frac{t+' + f"{_num_latex(-shift)}" + '}{' + f'{args[0]:g}' + '})'
×
905
    else:
906
        return '\\mathrm{erf}(\\frac{t}{' + f'{args[0]:g}' + '})'
×
907

908

909
def _format_COSH(shift, *args):
9✔
910
    if shift > 0:
×
911
        return '\\cosh(\\frac{t-' + f"{_num_latex(shift)}" + '}{' + f'{1/args[0]:g}' + '})'
×
912
    elif shift < 0:
×
913
        return '\\cosh(\\frac{t+' + f"{_num_latex(-shift)}" + '}{' + f'{1/args[0]:g}' + '})'
×
914
    else:
915
        return '\\cosh(\\frac{t}{' + f'{1/args[0]:g}' + '})'
×
916

917

918
def _format_SINH(shift, *args):
9✔
919
    if shift > 0:
×
920
        return '\\sinh(\\frac{t-' + f"{_num_latex(shift)}" + '}{' + f'{args[0]:g}' + '})'
×
921
    elif shift < 0:
×
922
        return '\\sinh(\\frac{t+' + f"{_num_latex(-shift)}" + '}{' + f'{args[0]:g}' + '})'
×
923
    else:
924
        return '\\sinh(\\frac{t}{' + f'{args[0]:g}' + '})'
×
925

926

927
def _format_EXP(shift, *args):
9✔
928
    if _num_latex(shift) and shift > 0:
×
929
        return '\\exp\\left(-' + f'{args[0]:g}' + '\\left(t-' + f"{_num_latex(shift)}" + '\\right)\\right)'
×
930
    elif _num_latex(-shift) and shift < 0:
×
931
        return '\\exp\\left(-' + f'{args[0]:g}' + '\\left(t+' + f"{_num_latex(-shift)}" + '\\right)\\right)'
×
932
    else:
933
        return '\\exp\\left(-' + f'{args[0]:g}' + 't\\right)'
×
934

935

936
def _format_DRAG(shift, *args):
9✔
NEW
937
    return f"DRAG(...)"
×
938

939

940
registerBaseFuncLatex(LINEAR, _format_LINEAR)
9✔
941
registerBaseFuncLatex(GAUSSIAN, _format_GAUSSIAN)
9✔
942
registerBaseFuncLatex(ERF, _format_ERF)
9✔
943
registerBaseFuncLatex(COS, _format_COSINE)
9✔
944
registerBaseFuncLatex(SINC, _format_SINC)
9✔
945
registerBaseFuncLatex(EXP, _format_EXP)
9✔
946
registerBaseFuncLatex(COSH, _format_COSH)
9✔
947
registerBaseFuncLatex(SINH, _format_SINH)
9✔
948
registerBaseFuncLatex(DRAG, _format_DRAG)
9✔
949

950

951
def D(wav):
9✔
952
    """derivative
953
    """
954
    return Waveform(bounds=wav.bounds, seq=tuple(_D(x) for x in wav.seq))
×
955

956

957
def convolve(a, b):
9✔
958
    pass
×
959

960

961
def sign():
9✔
962
    return Waveform(bounds=(0, +inf), seq=(_const(-1), _one))
×
963

964

965
def step(edge, type='erf'):
9✔
966
    """
967
    type: "erf", "cos", "linear"
968
    """
969
    if edge == 0:
9✔
970
        return Waveform(bounds=(0, +inf), seq=(_zero, _one))
9✔
971
    if type == 'cos':
9✔
NEW
972
        rise = add(_half,
×
973
                   mul(_half, basic_wave(COS, pi / edge, shift=0.5 * edge)))
UNCOV
974
        return Waveform(bounds=(round(-edge / 2,
×
975
                                      NDIGITS), round(edge / 2,
976
                                                      NDIGITS), +inf),
977
                        seq=(_zero, rise, _one))
978
    elif type == 'linear':
9✔
979
        rise = add(_half, mul(_const(1 / edge), basic_wave(LINEAR)))
9✔
980
        return Waveform(bounds=(round(-edge / 2,
9✔
981
                                      NDIGITS), round(edge / 2,
982
                                                      NDIGITS), +inf),
983
                        seq=(_zero, rise, _one))
984
    else:
985
        std_sq2 = edge / 5
9✔
986
        # rise = add(_half, mul(_half, basic_wave(ERF, std_sq2)))
987
        rise = ((((), ()), (((ERF, std_sq2, 0), ), (1, ))), (0.5, 0.5))
9✔
988
        return Waveform(bounds=(-round(edge, NDIGITS), round(edge,
9✔
989
                                                             NDIGITS), +inf),
990
                        seq=(_zero, rise, _one))
991

992

993
def square(width, edge=0, type='erf'):
9✔
994
    if width <= 0:
9✔
995
        return zero()
×
996
    if edge == 0:
9✔
997
        return Waveform(bounds=(round(-0.5 * width,
9✔
998
                                      NDIGITS), round(0.5 * width,
999
                                                      NDIGITS), +inf),
1000
                        seq=(_zero, _one, _zero))
1001
    else:
1002
        return ((step(edge, type=type) << width / 2) -
9✔
1003
                (step(edge, type=type) >> width / 2))
1004

1005

1006
def gaussian(width, plateau=0.0):
9✔
1007
    if width <= 0 and plateau <= 0.0:
9✔
1008
        return zero()
×
1009
    # width is two times FWHM
1010
    # std_sq2 = width / (4 * np.sqrt(np.log(2)))
1011
    std_sq2 = width / 3.3302184446307908
9✔
1012
    # std is set to give total pulse area same as a square
1013
    # std_sq2 = width/np.sqrt(np.pi)
1014
    if round(0.5 * plateau, NDIGITS) <= 0.0:
9✔
1015
        return Waveform(bounds=(round(-0.75 * width,
9✔
1016
                                      NDIGITS), round(0.75 * width,
1017
                                                      NDIGITS), +inf),
1018
                        seq=(_zero, basic_wave(GAUSSIAN, std_sq2), _zero))
1019
    else:
1020
        return Waveform(bounds=(round(-0.75 * width - 0.5 * plateau,
×
1021
                                      NDIGITS), round(-0.5 * plateau, NDIGITS),
1022
                                round(0.5 * plateau, NDIGITS),
1023
                                round(0.75 * width + 0.5 * plateau,
1024
                                      NDIGITS), +inf),
1025
                        seq=(_zero,
1026
                             basic_wave(GAUSSIAN,
1027
                                        std_sq2,
1028
                                        shift=-0.5 * plateau), _one,
1029
                             basic_wave(GAUSSIAN, std_sq2,
1030
                                        shift=0.5 * plateau), _zero))
1031

1032

1033
def cos(w, phi=0):
9✔
1034
    if w == 0:
9✔
1035
        return const(np.cos(phi))
×
1036
    if w < 0:
9✔
1037
        phi = -phi
9✔
1038
        w = -w
9✔
1039
    return Waveform(seq=(basic_wave(COS, w, shift=-phi / w), ))
9✔
1040

1041

1042
def sin(w, phi=0):
9✔
1043
    if w == 0:
9✔
1044
        return const(np.sin(phi))
×
1045
    if w < 0:
9✔
1046
        phi = -phi + pi
9✔
1047
        w = -w
9✔
1048
    return Waveform(seq=(basic_wave(COS, w, shift=(pi / 2 - phi) / w), ))
9✔
1049

1050

1051
def exp(alpha):
9✔
1052
    if isinstance(alpha, complex):
9✔
1053
        if alpha.real == 0:
9✔
1054
            return cos(alpha.imag) + 1j * sin(alpha.imag)
×
1055
        else:
1056
            return exp(alpha.real) * (cos(alpha.imag) + 1j * sin(alpha.imag))
9✔
1057
    else:
1058
        return Waveform(seq=(basic_wave(EXP, alpha), ))
9✔
1059

1060

1061
def sinc(bw):
9✔
1062
    if bw <= 0:
×
1063
        return zero()
×
1064
    width = 100 / bw
×
1065
    return Waveform(bounds=(round(-0.5 * width,
×
1066
                                  NDIGITS), round(0.5 * width, NDIGITS), +inf),
1067
                    seq=(_zero, basic_wave(SINC, bw), _zero))
1068

1069

1070
def cosPulse(width, plateau=0.0):
9✔
1071
    # cos = basic_wave(COS, 2*np.pi/width)
1072
    # pulse = mul(add(cos, _one), _half)
1073
    if round(0.5 * plateau, NDIGITS) > 0:
9✔
1074
        return square(plateau + 0.5 * width, edge=0.5 * width, type='cos')
×
1075
    if width <= 0:
9✔
1076
        return zero()
×
1077
    pulse = ((((), ()), (((COS, 6.283185307179586 / width, 0), ), (1, ))),
9✔
1078
             (0.5, 0.5))
1079
    return Waveform(bounds=(round(-0.5 * width,
9✔
1080
                                  NDIGITS), round(0.5 * width, NDIGITS), +inf),
1081
                    seq=(_zero, pulse, _zero))
1082

1083

1084
def hanning(width, plateau=0.0):
9✔
1085
    return cosPulse(width, plateau=plateau)
×
1086

1087

1088
def cosh(w):
9✔
NEW
1089
    return Waveform(seq=(basic_wave(COSH, w), ))
×
1090

1091

1092
def sinh(w):
9✔
NEW
1093
    return Waveform(seq=(basic_wave(SINH, w), ))
×
1094

1095

1096
def coshPulse(width, eps=1.0, plateau=0.0):
9✔
1097
    """Cosine hyperbolic pulse with the following im
1098

1099
    pulse edge shape:
1100
            cosh(eps / 2) - cosh(eps * t / T)
1101
    f(t) = -----------------------------------
1102
                  cosh(eps / 2) - 1
1103
    where T is the pulse width and eps is the pulse edge steepness.
1104
    The pulse is defined for t in [-T/2, T/2].
1105

1106
    In case of plateau > 0, the pulse is defined as:
1107
           | f(t + plateau/2)   if t in [-T/2 - plateau/2, - plateau/2]
1108
    g(t) = | 1                  if t in [-plateau/2, plateau/2]
1109
           | f(t - plateau/2)   if t in [plateau/2, T/2 + plateau/2]
1110

1111
    Parameters
1112
    ----------
1113
    width : float
1114
        Pulse width.
1115
    eps : float
1116
        Pulse edge steepness.
1117
    plateau : float
1118
        Pulse plateau.
1119
    """
1120
    if width <= 0 and plateau <= 0:
×
1121
        return zero()
×
1122
    w = eps / width
×
1123
    A = np.cosh(eps / 2)
×
1124

1125
    if plateau == 0.0 or round(-0.5 * plateau, NDIGITS) == round(
×
1126
            0.5 * plateau, NDIGITS):
1127
        pulse = ((((), ()), (((COSH, w, 0), ), (1, ))), (A / (A - 1),
×
1128
                                                         -1 / (A - 1)))
1129
        return Waveform(bounds=(round(-0.5 * width,
×
1130
                                      NDIGITS), round(0.5 * width,
1131
                                                      NDIGITS), +inf),
1132
                        seq=(_zero, pulse, _zero))
1133
    else:
1134
        raising = ((((), ()), (((COSH, w, -0.5 * plateau), ), (1, ))),
×
1135
                   (A / (A - 1), -1 / (A - 1)))
1136
        falling = ((((), ()), (((COSH, w, 0.5 * plateau), ), (1, ))),
×
1137
                   (A / (A - 1), -1 / (A - 1)))
1138
        return Waveform(bounds=(round(-0.5 * width - 0.5 * plateau,
×
1139
                                      NDIGITS), round(-0.5 * plateau, NDIGITS),
1140
                                round(0.5 * plateau, NDIGITS),
1141
                                round(0.5 * width + 0.5 * plateau,
1142
                                      NDIGITS), +inf),
1143
                        seq=(_zero, raising, _one, falling, _zero))
1144

1145

1146
def general_cosine(duration, *arg):
9✔
1147
    wav = zero()
×
1148
    arg = np.asarray(arg)
×
1149
    arg /= arg[::2].sum()
×
1150
    for i, a in enumerate(arg, start=1):
×
1151
        wav += a / 2 * (1 - (-1)**i * cos(i * 2 * pi / duration))
×
1152
    return wav * square(duration)
×
1153

1154

1155
def slepian(duration, *arg):
9✔
1156
    wav = zero()
×
1157
    arg = np.asarray(arg)
×
1158
    arg /= arg[::2].sum()
×
1159
    for i, a in enumerate(arg, start=1):
×
1160
        wav += a / 2 * (1 - (-1)**i * cos(i * 2 * pi / duration))
×
1161
    return wav * square(duration)
×
1162

1163

1164
def _poly(*a):
9✔
1165
    """
1166
    a[0] + a[1] * t + a[2] * t**2 + ...
1167
    """
1168
    t = []
9✔
1169
    amp = []
9✔
1170
    if a[0] != 0:
9✔
1171
        t.append(((), ()))
9✔
1172
        amp.append(a[0])
9✔
1173
    for n, a_ in enumerate(a[1:], start=1):
9✔
1174
        if a_ != 0:
9✔
1175
            t.append((((LINEAR, 0), ), (n, )))
9✔
1176
            amp.append(a_)
9✔
1177
    return tuple(t), tuple(a)
9✔
1178

1179

1180
def poly(a):
9✔
1181
    """
1182
    a[0] + a[1] * t + a[2] * t**2 + ...
1183
    """
1184
    return Waveform(seq=(_poly(*a), ))
9✔
1185

1186

1187
def t():
9✔
1188
    return Waveform(seq=((((LINEAR, 0), ), (1, )), (1, )))
×
1189

1190

1191
def drag(freq, width, plateau=0, delta=0, block_freq=None, phase=0, t0=0):
9✔
1192
    phase += pi * delta * (width + plateau)
9✔
1193
    if plateau <= 0:
9✔
1194
        return Waveform(seq=(_zero,
9✔
1195
                             basic_wave(DRAG, t0, freq, width, delta,
1196
                                        block_freq, phase), _zero),
1197
                        bounds=(round(t0, NDIGITS), round(t0 + width,
1198
                                                          NDIGITS), +inf))
1199
    elif width <= 0:
×
1200
        w = 2 * pi * (freq + delta)
×
1201
        return Waveform(
×
1202
            seq=(_zero,
1203
                 basic_wave(COS, w,
1204
                            shift=(phase + 2 * pi * delta * t0) / w), _zero),
1205
            bounds=(round(t0, NDIGITS), round(t0 + plateau, NDIGITS), +inf))
1206
    else:
1207
        w = 2 * pi * (freq + delta)
×
1208
        return Waveform(
×
1209
            seq=(_zero,
1210
                 basic_wave(DRAG, t0, freq, width, delta, block_freq, phase),
1211
                 basic_wave(COS, w, shift=(phase + 2 * pi * delta * t0) / w),
1212
                 basic_wave(DRAG, t0 + plateau, freq, width, delta, block_freq,
1213
                            phase - 2 * pi * delta * plateau), _zero),
1214
            bounds=(round(t0, NDIGITS), round(t0 + width / 2, NDIGITS),
1215
                    round(t0 + width / 2 + plateau,
1216
                          NDIGITS), round(t0 + width + plateau,
1217
                                          NDIGITS), +inf))
1218

1219

1220
def chirp(f0, f1, T, phi0=0, type='linear'):
9✔
1221
    """
1222
    A chirp is a signal in which the frequency increases (up-chirp)
1223
    or decreases (down-chirp) with time. In some sources, the term
1224
    chirp is used interchangeably with sweep signal.
1225

1226
    type: "linear", "exponential", "hyperbolic"
1227
    """
1228
    if f0 == f1:
9✔
1229
        return sin(f0, phi0)
×
1230
    if T <= 0:
9✔
1231
        raise ValueError('T must be positive')
×
1232

1233
    if type == 'linear':
9✔
1234
        # f(t) = f1 * (t/T) + f0 * (1 - t/T)
1235
        return Waveform(bounds=(0, round(T, NDIGITS), +inf),
9✔
1236
                        seq=(_zero, basic_wave(LINEARCHIRP, f0, f1, T,
1237
                                               phi0), _zero))
1238
    elif type in ['exp', 'exponential', 'geometric']:
9✔
1239
        # f(t) = f0 * (f1/f0) ** (t/T)
1240
        if f0 == 0:
9✔
1241
            raise ValueError('f0 must be non-zero')
×
1242
        alpha = np.log(f1 / f0) / T
9✔
1243
        return Waveform(bounds=(0, round(T, NDIGITS), +inf),
9✔
1244
                        seq=(_zero,
1245
                             basic_wave(EXPONENTIALCHIRP, f0, alpha,
1246
                                        phi0), _zero))
1247
    elif type in ['hyperbolic', 'hyp']:
9✔
1248
        # f(t) = f0 * f1 / (f0 * (t/T) + f1 * (1-t/T))
1249
        if f0 * f1 == 0:
9✔
1250
            return const(np.sin(phi0))
×
1251
        k = (f0 - f1) / (f1 * T)
9✔
1252
        return Waveform(bounds=(0, round(T, NDIGITS), +inf),
9✔
1253
                        seq=(_zero, basic_wave(HYPERBOLICCHIRP, f0, k,
1254
                                               phi0), _zero))
1255
    else:
1256
        raise ValueError(f'unknown type {type}')
×
1257

1258

1259
def interp(x, y):
9✔
1260
    seq, bounds = [_zero], [x[0]]
×
1261
    for x1, x2, y1, y2 in zip(x[:-1], x[1:], y[:-1], y[1:]):
×
1262
        if x2 == x1:
×
1263
            continue
×
1264
        seq.append(
×
1265
            add(
1266
                mul(_const((y2 - y1) / (x2 - x1)), basic_wave(LINEAR,
1267
                                                              shift=x1)),
1268
                _const(y1)))
1269
        bounds.append(x2)
×
1270
    bounds.append(inf)
×
1271
    seq.append(_zero)
×
1272
    return Waveform(seq=tuple(seq),
×
1273
                    bounds=tuple(round(b, NDIGITS)
1274
                                 for b in bounds)).simplify()
1275

1276

1277
def cut(wav, start=None, stop=None, head=None, tail=None, min=None, max=None):
9✔
1278
    offset = 0
×
1279
    if start is not None and head is not None:
×
1280
        offset = head - wav(np.array([1.0 * start]))[0]
×
1281
    elif stop is not None and tail is not None:
×
1282
        offset = tail - wav(np.array([1.0 * stop]))[0]
×
1283
    wav = wav + offset
×
1284

1285
    if start is not None:
×
1286
        wav = wav * (step(0) >> start)
×
1287
    if stop is not None:
×
1288
        wav = wav * ((1 - step(0)) >> stop)
×
1289
    if min is not None:
×
1290
        wav.min = min
×
1291
    if max is not None:
×
1292
        wav.max = max
×
1293
    return wav
×
1294

1295

1296
def function(fun, *args, start=None, stop=None):
9✔
1297
    TYPEID = registerBaseFunc(fun)
×
NEW
1298
    seq = (basic_wave(TYPEID, *args), )
×
1299
    wav = Waveform(seq=seq)
×
1300
    if start is not None:
×
1301
        wav = wav * (step(0) >> start)
×
1302
    if stop is not None:
×
1303
        wav = wav * ((1 - step(0)) >> stop)
×
1304
    return wav
×
1305

1306

1307
def samplingPoints(start, stop, points):
9✔
1308
    return Waveform(bounds=(round(start, NDIGITS), round(stop, NDIGITS), inf),
×
1309
                    seq=(_zero, basic_wave(INTERP, start, stop,
1310
                                           tuple(points)), _zero))
1311

1312

1313
def mixing(I,
9✔
1314
           Q=None,
1315
           *,
1316
           phase=0.0,
1317
           freq=0.0,
1318
           ratioIQ=1.0,
1319
           phaseDiff=0.0,
1320
           block_freq=None,
1321
           DRAGScaling=None):
1322
    """SSB or envelope mixing
1323
    """
1324
    if Q is None:
9✔
1325
        I = I
9✔
1326
        Q = zero()
9✔
1327

1328
    w = 2 * pi * freq
9✔
1329
    if freq != 0.0:
9✔
1330
        # SSB mixing
1331
        Iout = I * cos(w, -phase) + Q * sin(w, -phase)
9✔
1332
        Qout = -I * sin(w, -phase + phaseDiff) + Q * cos(w, -phase + phaseDiff)
9✔
1333
    else:
1334
        # envelope mixing
1335
        Iout = I * np.cos(-phase) + Q * np.sin(-phase)
9✔
1336
        Qout = -I * np.sin(-phase) + Q * np.cos(-phase)
9✔
1337

1338
    # apply DRAG
1339
    if block_freq is not None and block_freq != freq:
9✔
1340
        a = block_freq / (block_freq - freq)
×
1341
        b = 1 / (block_freq - freq)
×
1342
        I = a * Iout + b / (2 * pi) * D(Qout)
×
1343
        Q = a * Qout - b / (2 * pi) * D(Iout)
×
1344
        Iout, Qout = I, Q
×
1345
    elif DRAGScaling is not None and DRAGScaling != 0:
9✔
1346
        # 2 * pi * scaling * (freq - block_freq) = 1
1347
        I = (1 - w * DRAGScaling) * Iout - DRAGScaling * D(Qout)
×
1348
        Q = (1 - w * DRAGScaling) * Qout + DRAGScaling * D(Iout)
×
1349
        Iout, Qout = I, Q
×
1350

1351
    Qout = ratioIQ * Qout
9✔
1352

1353
    return Iout, Qout
9✔
1354

1355

1356
__all__ = [
9✔
1357
    'D', 'Waveform', 'chirp', 'const', 'cos', 'cosh', 'coshPulse', 'cosPulse',
1358
    'cut', 'drag', 'exp', 'function', 'gaussian', 'general_cosine', 'hanning',
1359
    'interp', 'mixing', 'one', 'poly', 'registerBaseFunc',
1360
    'registerDerivative', 'samplingPoints', 'sign', 'sin', 'sinc', 'sinh',
1361
    'square', 'step', 't', 'zero'
1362
]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc