pywavelet 0.0.1b0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
pywavelet/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ WDM Wavelet transform
3
+ """
4
+
5
+ __version__ = "0.0.1"
pywavelet/_version.py ADDED
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.0.1b0'
16
+ __version_tuple__ = version_tuple = (0, 0, 1)
pywavelet/fft_funcs.py ADDED
@@ -0,0 +1,16 @@
1
+ """helper to make sure available fft functions are consistent across modules depending on install
2
+ mkl-fft is faster so it is the default, but numpy fft is probably more commonly installed to it is the fallback"""
3
+ try:
4
+ import mkl_fft
5
+
6
+ rfft = mkl_fft.rfft_numpy
7
+ irfft = mkl_fft.irfft_numpy
8
+ fft = mkl_fft.fft
9
+ ifft = mkl_fft.ifft
10
+ except ImportError:
11
+ import numpy
12
+
13
+ rfft = numpy.fft.rfft
14
+ irfft = numpy.fft.irfft
15
+ fft = numpy.fft.fft
16
+ ifft = numpy.fft.ifft
File without changes
@@ -0,0 +1,9 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class LikelihoodBase(ABC):
5
+ def __init__(self):
6
+ pass
7
+
8
+ def log_likelihood(self, data, model):
9
+ pass
@@ -0,0 +1,24 @@
1
+ """Whittle Likelihood (in the wavelet domain)
2
+ See eq 18, Cornish 2020 "Time-Frequency Analysis of Gravitational Wave Data"
3
+
4
+ LnL(d|h) = -1/2 * Sum_{ti,fi} [ ln(2pi) + ln(PSD[ti,fi]) + (d[ti,fi]-h[ti,fi])^2/PSD[ti,fi]]
5
+
6
+ where
7
+ - d[ti,fi] is the data in the wavelet domain,
8
+ - h[ti,fi] is the model in the wavelet domain,
9
+ - PSD[ti,fi] is the _evolutionary_ power spectral density in the wavelet domain.
10
+
11
+ For stationary noise:
12
+ PSD[ti,fi] = PSD[fi * Nt/2] Delta_f,
13
+ where
14
+ - fi * Nt/2 is the frequency index in the wavelet domain,
15
+ - Delta_f is the frequency bin width in the wavelet domain.
16
+
17
+
18
+ """
19
+
20
+ from .likelihood_base import LikelihoodBase
21
+
22
+
23
+ class WhittleLikelihood(LikelihoodBase):
24
+ pass
pywavelet/logger.py ADDED
@@ -0,0 +1,15 @@
1
+ import sys
2
+ import warnings
3
+
4
+ from loguru import logger
5
+
6
+ logger.add(
7
+ sys.stderr,
8
+ format="|<blue>pywavelet</>|{time:DD/MM HH:mm:ss}|{level}| {message} ",
9
+ colorize=True,
10
+ level="INFO",
11
+ )
12
+
13
+
14
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
15
+ warnings.filterwarnings("ignore", category=UserWarning)
@@ -0,0 +1,10 @@
1
+ from .from_wavelets import (
2
+ from_wavelet_to_freq,
3
+ from_wavelet_to_freq_to_time,
4
+ from_wavelet_to_time,
5
+ )
6
+ from .to_wavelets import (
7
+ from_freq_to_wavelet,
8
+ from_time_to_freq_to_wavelet,
9
+ from_time_to_wavelet,
10
+ )
@@ -0,0 +1,77 @@
1
+ import numpy as np
2
+ import scipy
3
+
4
+ from .. import fft_funcs as fft
5
+
6
+ PI = np.pi
7
+
8
+
9
+ def phitilde_vec(om: np.ndarray, Nf: int, nx=4.0) -> np.ndarray:
10
+ """compute phitilde, om i array, nx is filter steepness, defaults to 4."""
11
+ # Note: Pi is Nyquist angular frequency
12
+ DOM = PI / Nf # 2 pi times DF
13
+ insDOM = 1.0 / np.sqrt(DOM)
14
+ B = PI / (2 * Nf)
15
+ A = (DOM - B) / 2
16
+ z = np.zeros(om.size)
17
+
18
+ mask = (np.abs(om) >= A) & (np.abs(om) < A + B)
19
+
20
+ x = (np.abs(om[mask]) - A) / B
21
+ y = scipy.special.betainc(nx, nx, x)
22
+ z[mask] = insDOM * np.cos(PI / 2.0 * y)
23
+
24
+ z[np.abs(om) < A] = insDOM
25
+ return z
26
+
27
+
28
+ def phitilde_vec_norm(Nf: int, Nt: int, nx: int) -> np.ndarray:
29
+ """Normalize phitilde for inverse frequency domain transform."""
30
+
31
+ # Calculate the frequency values
32
+ ND = Nf * Nt
33
+ omegas = 2 * np.pi / ND * np.arange(0, Nt // 2 + 1)
34
+
35
+ # Calculate the unnormalized phitilde (u_phit)
36
+ u_phit = phitilde_vec(omegas, Nf, nx)
37
+
38
+ # Normalize the phitilde
39
+ nrm_fctor = np.sqrt(
40
+ (2 * np.sum(u_phit[1:] ** 2) + u_phit[0] ** 2) * 2 * PI / ND
41
+ )
42
+ nrm_fctor /= PI ** (3 / 2) / PI
43
+
44
+ return u_phit / nrm_fctor
45
+
46
+
47
+ def phi_vec(Nf: int, nx: int = 4.0, mult: int = 16) -> np.ndarray:
48
+ """get time domain phi as fourier transform of phitilde_vec"""
49
+ insDOM = 1.0 / np.sqrt(PI / Nf)
50
+ K = mult * 2 * Nf
51
+ half_K = mult * Nf # np.int64(K/2)
52
+
53
+ dom = 2 * PI / K # max frequency is K/2*dom = pi/dt = OM
54
+
55
+ DX = np.zeros(K, dtype=np.complex128)
56
+
57
+ # zero frequency
58
+ DX[0] = insDOM
59
+
60
+ DX = DX.copy()
61
+ # postive frequencies
62
+ DX[1 : half_K + 1] = phitilde_vec(dom * np.arange(1, half_K + 1), Nf, nx)
63
+ # negative frequencies
64
+ DX[half_K + 1 :] = phitilde_vec(
65
+ -dom * np.arange(half_K - 1, 0, -1), Nf, nx
66
+ )
67
+ DX = K * fft.ifft(DX, K)
68
+
69
+ phi = np.zeros(K)
70
+ phi[0:half_K] = np.real(DX[half_K:K])
71
+ phi[half_K:] = np.real(DX[0:half_K])
72
+
73
+ nrm = np.sqrt(K / dom) # *np.linalg.norm(phi)
74
+
75
+ fac = np.sqrt(2.0) / nrm
76
+ phi *= fac
77
+ return phi
@@ -0,0 +1,25 @@
1
+ from pywavelet import fft_funcs as fft
2
+ from pywavelet.transforms.common import phi_vec, phitilde_vec_norm
3
+
4
+ from .inverse_wavelet_freq_funcs import inverse_wavelet_freq_helper_fast
5
+ from .inverse_wavelet_time_funcs import inverse_wavelet_time_helper_fast
6
+
7
+
8
+ def from_wavelet_to_time(wave_in, Nf, Nt, nx=4.0, mult=32):
9
+ """fast inverse wavelet transform to time domain"""
10
+ mult = min(mult, Nt // 2) # make sure K isn't bigger than ND
11
+ phi = phi_vec(Nf, nx=nx, mult=mult) / 2
12
+
13
+ return inverse_wavelet_time_helper_fast(wave_in, phi, Nf, Nt, mult)
14
+
15
+
16
+ def from_wavelet_to_freq_to_time(wave_in, Nf, Nt, nx=4.0):
17
+ """inverse wavlet transform to time domain via fourier transform of frequency domain"""
18
+ res_f = from_wavelet_to_freq(wave_in, Nf, Nt, nx)
19
+ return fft.irfft(res_f)
20
+
21
+
22
+ def from_wavelet_to_freq(wave_in, Nf, Nt, nx=4.0):
23
+ """inverse wavelet transform to freq domain signal"""
24
+ phif = phitilde_vec_norm(Nf, Nt, nx)
25
+ return inverse_wavelet_freq_helper_fast(wave_in, phif, Nf, Nt)
@@ -0,0 +1,76 @@
1
+ """functions for computing the inverse wavelet transforms"""
2
+ import numpy as np
3
+ from numba import njit
4
+
5
+ from ... import fft_funcs as fft
6
+
7
+
8
+ # @njit()
9
+ def inverse_wavelet_freq_helper_fast(wave_in, phif, Nf, Nt):
10
+ """jit compatible loop for inverse_wavelet_freq"""
11
+ ND = Nf * Nt
12
+
13
+ prefactor2s = np.zeros(Nt, np.complex128)
14
+ res = np.zeros(ND // 2 + 1, dtype=np.complex128)
15
+
16
+ for m in range(0, Nf + 1):
17
+ pack_wave_inverse(m, Nt, Nf, prefactor2s, wave_in)
18
+ # with numba.objmode(fft_prefactor2s="complex128[:]"):
19
+ fft_prefactor2s = fft.fft(prefactor2s)
20
+ unpack_wave_inverse(m, Nt, Nf, phif, fft_prefactor2s, res)
21
+
22
+ return res
23
+
24
+
25
+ @njit()
26
+ def unpack_wave_inverse(m, Nt, Nf, phif, fft_prefactor2s, res):
27
+ """helper for unpacking results of frequency domain inverse transform"""
28
+
29
+ if m == 0 or m == Nf:
30
+ for i_ind in range(0, Nt // 2):
31
+ i = np.abs(m * Nt // 2 - i_ind) # i_off+i_min2
32
+ ind3 = (2 * i) % Nt
33
+ res[i] += fft_prefactor2s[ind3] * phif[i_ind]
34
+ if m == Nf:
35
+ i_ind = Nt // 2
36
+ i = np.abs(m * Nt // 2 - i_ind) # i_off+i_min2
37
+ ind3 = 0
38
+ res[i] += fft_prefactor2s[ind3] * phif[i_ind]
39
+ else:
40
+ ind31 = (Nt // 2 * m) % Nt
41
+ ind32 = (Nt // 2 * m) % Nt
42
+ for i_ind in range(0, Nt // 2):
43
+ i1 = Nt // 2 * m - i_ind
44
+ i2 = Nt // 2 * m + i_ind
45
+ # assert ind31 == i1%Nt
46
+ # assert ind32 == i2%Nt
47
+ res[i1] += fft_prefactor2s[ind31] * phif[i_ind]
48
+ res[i2] += fft_prefactor2s[ind32] * phif[i_ind]
49
+ ind31 -= 1
50
+ ind32 += 1
51
+ if ind31 < 0:
52
+ ind31 = Nt - 1
53
+ if ind32 == Nt:
54
+ ind32 = 0
55
+
56
+ res[Nt // 2 * m] = fft_prefactor2s[(Nt // 2 * m) % Nt] * phif[0]
57
+
58
+
59
+ @njit()
60
+ def pack_wave_inverse(m, Nt, Nf, prefactor2s, wave_in):
61
+ """helper for fast frequency domain inverse transform to prepare for fourier transform"""
62
+ if m == 0:
63
+ for n in range(0, Nt):
64
+ prefactor2s[n] = 1 / np.sqrt(2) * wave_in[(2 * n) % Nt, 0]
65
+ elif m == Nf:
66
+ for n in range(0, Nt):
67
+ prefactor2s[n] = 1 / np.sqrt(2) * wave_in[(2 * n) % Nt + 1, 0]
68
+ else:
69
+ for n in range(0, Nt):
70
+ val = wave_in[n, m]
71
+ if (n + m) % 2:
72
+ mult2 = -1j
73
+ else:
74
+ mult2 = 1
75
+
76
+ prefactor2s[n] = mult2 * val
@@ -0,0 +1,133 @@
1
+ """functions for computing the inverse wavelet transforms"""
2
+ import numpy as np
3
+ from numba import njit
4
+
5
+ from ... import fft_funcs as fft
6
+
7
+
8
+ def inverse_wavelet_time_helper_fast(wave_in, phi, Nf, Nt, mult):
9
+ """helper loop for fast inverse wavelet transform"""
10
+ ND = Nf * Nt
11
+ K = mult * 2 * Nf
12
+ # res = np.zeros(ND)
13
+
14
+ # extend this array, we can use wrapping boundary conditions at end
15
+ res = np.zeros(ND + K + Nf)
16
+
17
+ afins = np.zeros(2 * Nf, dtype=np.complex128)
18
+
19
+ for n in range(0, Nt):
20
+ if n % 2 == 0:
21
+ pack_wave_time_helper_compact(n, Nf, Nt, wave_in, afins)
22
+ ffts_fin = fft.fft(afins)
23
+ unpack_time_wave_helper_compact(n, Nf, Nt, K, phi, ffts_fin, res)
24
+
25
+ # wrap boundary conditions
26
+ res[: min(K + Nf, ND)] += res[ND : min(ND + K + Nf, 2 * ND)]
27
+ if K + Nf > ND:
28
+ res[: K + Nf - ND] += res[2 * ND : ND + K * Nf]
29
+
30
+ res = res[:ND]
31
+
32
+ return res
33
+
34
+
35
+ @njit()
36
+ def unpack_time_wave_helper(n, Nf, Nt, K, phis, fft_fin_real, res):
37
+ """helper for time domain wavelet transform to unpack wavelet domain coefficients"""
38
+ ND = Nf * Nt
39
+
40
+ idxf = (-K // 2 + n * Nf + ND) % (2 * Nf)
41
+ k = (-K // 2 + n * Nf) % ND
42
+
43
+ for k_ind in range(0, K):
44
+ res_loc = fft_fin_real[idxf]
45
+ res[k] += phis[k_ind] * res_loc
46
+ idxf += 1
47
+ k += 1
48
+
49
+ if idxf == 2 * Nf:
50
+ idxf = 0
51
+ if k == ND:
52
+ k = 0
53
+
54
+
55
+ @njit()
56
+ def unpack_time_wave_helper_compact(n, Nf, Nt, K, phis, fft_fin, res):
57
+ """helper for time domain wavelet transform to unpack wavelet domain coefficients
58
+ in compact representation where cosine and sine parts are real and imaginary parts
59
+ """
60
+ ND = Nf * Nt
61
+ fft_fin_real = np.zeros(4 * Nf)
62
+ fft_fin_imag = np.zeros(4 * Nf)
63
+ for itrf in range(0, 2 * Nf):
64
+ fft_fin_real[itrf] = np.real(fft_fin[itrf])
65
+ fft_fin_real[itrf + 2 * Nf] = fft_fin_real[itrf]
66
+ fft_fin_imag[itrf] = np.imag(fft_fin[(itrf + Nf) % (2 * Nf)])
67
+ fft_fin_imag[itrf + 2 * Nf] = fft_fin_imag[itrf]
68
+
69
+ idxf1_base = (-K // 2 + n * Nf + ND) % (2 * Nf)
70
+ k1_base = (-K // 2 + n * Nf) % ND
71
+ for k_ind in range(0, K, 2 * Nf):
72
+ for idxf1_add in range(0, 2 * Nf):
73
+ idxf1 = idxf1_base + idxf1_add # k_ind%(2*Nf)
74
+ k_ind_loc = k_ind + idxf1_add
75
+ k1 = k1_base + k_ind_loc
76
+
77
+ res[k1] += phis[k_ind_loc] * fft_fin_real[idxf1]
78
+ res[k1 + Nf] += phis[k_ind_loc] * fft_fin_imag[idxf1]
79
+
80
+
81
+ @njit()
82
+ def pack_wave_time_helper(n, Nf, Nt, wave_in, afins):
83
+ """helper for time domain transform to pack wavelet domain coefficients"""
84
+ if n % 2 == 0:
85
+ # assign highest and lowest bin correctly
86
+ afins[0] = np.sqrt(2) * wave_in[n, 0]
87
+ if n + 1 < Nt:
88
+ afins[Nf] = np.sqrt(2) * wave_in[n + 1, 0]
89
+ else:
90
+ afins[0] = 0.0
91
+ afins[Nf] = 0.0
92
+
93
+ for idxm in range(0, Nf // 2 - 1):
94
+ if n % 2:
95
+ afins[2 * idxm + 2] = 1j * wave_in[n, 2 * idxm + 2]
96
+ afins[2 * Nf - 2 * idxm - 2] = -1j * wave_in[n, 2 * idxm + 2]
97
+ else:
98
+ afins[2 * idxm + 2] = 1 * wave_in[n, 2 * idxm + 2]
99
+ afins[2 * Nf - 2 * idxm - 2] = 1 * wave_in[n, 2 * idxm + 2]
100
+
101
+ for idxm in range(0, Nf // 2):
102
+ if n % 2:
103
+ afins[2 * idxm + 1] = -1 * wave_in[n, 2 * idxm + 1]
104
+ afins[2 * Nf - 2 * idxm - 1] = -1 * wave_in[n, 2 * idxm + 1]
105
+ else:
106
+ afins[2 * idxm + 1] = 1j * wave_in[n, 2 * idxm + 1]
107
+ afins[2 * Nf - 2 * idxm - 1] = -1j * wave_in[n, 2 * idxm + 1]
108
+
109
+
110
+ @njit()
111
+ def pack_wave_time_helper_compact(n, Nf, Nt, wave_in, afins):
112
+ """helper for time domain transform to pack wavelet domain coefficients
113
+ in packed representation with odd and even coefficients in real and imaginary pars
114
+ """
115
+ afins[0] = np.sqrt(2) * wave_in[n, 0]
116
+ if n + 1 < Nt:
117
+ afins[Nf] = np.sqrt(2) * wave_in[n + 1, 0]
118
+
119
+ for idxm in range(0, Nf - 2, 2):
120
+ afins[idxm + 2] = wave_in[n, idxm + 2] - wave_in[n + 1, idxm + 2]
121
+ afins[2 * Nf - idxm - 2] = (
122
+ wave_in[n, idxm + 2] + wave_in[n + 1, idxm + 2]
123
+ )
124
+
125
+ afins[idxm + 1] = 1j * (
126
+ wave_in[n, idxm + 1] - wave_in[n + 1, idxm + 1]
127
+ )
128
+ afins[2 * Nf - idxm - 1] = -1j * (
129
+ wave_in[n, idxm + 1] + wave_in[n + 1, idxm + 1]
130
+ )
131
+
132
+ afins[Nf - 1] = 1j * (wave_in[n, Nf - 1] - wave_in[n + 1, Nf - 1])
133
+ afins[Nf + 1] = -1j * (wave_in[n, Nf - 1] + wave_in[n + 1, Nf - 1])
@@ -0,0 +1,52 @@
1
+ import numpy as np
2
+
3
+ from ... import fft_funcs as fft
4
+ from ...logger import logger
5
+ from ..common import phi_vec, phitilde_vec_norm
6
+ from .transform_freq_funcs import transform_wavelet_freq_helper
7
+ from .transform_time_funcs import transform_wavelet_time_helper
8
+
9
+
10
+ def from_time_to_wavelet(data, Nf, Nt, nx=4.0, mult=32):
11
+ """From time domain data to wavelet domain
12
+
13
+ Warning: there can be significant leakage if mult is too small and the
14
+ transform is only approximately exact if mult=Nt/2
15
+
16
+ Parameters
17
+ ----------
18
+ data : array_like
19
+ Time domain data
20
+ Nf : int
21
+ Number of frequency bins
22
+ Nt : int
23
+ Number of time bins
24
+ nx : float, optional
25
+ Number of standard deviations for the gaussian wavelet, by default 4.
26
+ mult : int, optional
27
+ Number of time bins to use for the wavelet transform, by default 32
28
+ """
29
+
30
+ if mult > Nt / 2:
31
+ logger.warning(
32
+ f"mult={mult} is too large for Nt={Nt}. This may lead to bogus results."
33
+ )
34
+
35
+ mult = min(mult, Nt // 2) # make sure K isn't bigger than ND
36
+ phi = phi_vec(Nf, nx, mult)
37
+ wave = transform_wavelet_time_helper(data, Nf, Nt, phi, mult)
38
+
39
+ return wave
40
+
41
+
42
+ def from_time_to_freq_to_wavelet(data, Nf, Nt, nx=4.0):
43
+ """transform time domain data into wavelet domain via fft and then frequency transform"""
44
+ data_fft = fft.rfft(data)
45
+
46
+ return from_freq_to_wavelet(data_fft, Nf, Nt, nx)
47
+
48
+
49
+ def from_freq_to_wavelet(data, Nf, Nt, nx=4.0):
50
+ """do the wavelet transform using the fast wavelet domain transform"""
51
+ phif = 2 / Nf * phitilde_vec_norm(Nf, Nt, nx)
52
+ return transform_wavelet_freq_helper(data, Nf, Nt, phif)
@@ -0,0 +1,84 @@
1
+ """helper functions for transform_freq"""
2
+ import numpy as np
3
+ from numba import njit
4
+
5
+ from pywavelet import fft_funcs as fft
6
+
7
+
8
+ @njit()
9
+ def tukey(data, alpha, N):
10
+ """apply tukey window function to data"""
11
+ imin = np.int64(alpha * (N - 1) / 2)
12
+ imax = np.int64((N - 1) * (1 - alpha / 2))
13
+ Nwin = N - imax
14
+
15
+ for i in range(0, N):
16
+ f_mult = 1.0
17
+ if i < imin:
18
+ f_mult = 0.5 * (1.0 + np.cos(np.pi * (i / imin - 1.0)))
19
+ if i > imax:
20
+ f_mult = 0.5 * (1.0 + np.cos(np.pi / Nwin * (i - imax)))
21
+ data[i] *= f_mult
22
+
23
+
24
+ def transform_wavelet_freq_helper(data, Nf, Nt, phif):
25
+ """helper to do the wavelet transform using the fast wavelet domain transform"""
26
+ wave = np.zeros((Nt, Nf)) # wavelet wavepacket transform of the signal
27
+
28
+ DX = np.zeros(Nt, dtype=np.complex128)
29
+ for m in range(0, Nf + 1):
30
+ DX_assign_loop(m, Nt, Nf, DX, data, phif)
31
+ DX_trans = fft.ifft(DX, Nt)
32
+ DX_unpack_loop(m, Nt, Nf, DX_trans, wave)
33
+ return wave
34
+
35
+
36
+ @njit()
37
+ def DX_assign_loop(m, Nt, Nf, DX, data, phif):
38
+ """helper for assigning DX in the main loop"""
39
+ i_base = Nt // 2
40
+ jj_base = m * Nt // 2
41
+
42
+ if m == 0 or m == Nf:
43
+ # NOTE this term appears to be needed to recover correct constant (at least for m=0), but was previously missing
44
+ DX[Nt // 2] = phif[0] * data[m * Nt // 2] / 2.0
45
+ DX[Nt // 2] = phif[0] * data[m * Nt // 2] / 2.0
46
+ else:
47
+ DX[Nt // 2] = phif[0] * data[m * Nt // 2]
48
+ DX[Nt // 2] = phif[0] * data[m * Nt // 2]
49
+
50
+ for jj in range(jj_base + 1 - Nt // 2, jj_base + Nt // 2):
51
+ j = np.abs(jj - jj_base)
52
+ i = i_base - jj_base + jj
53
+ if m == Nf and jj > jj_base:
54
+ DX[i] = 0.0
55
+ elif m == 0 and jj < jj_base:
56
+ DX[i] = 0.0
57
+ elif j == 0:
58
+ continue
59
+ else:
60
+ DX[i] = phif[j] * data[jj]
61
+
62
+
63
+ @njit()
64
+ def DX_unpack_loop(m, Nt, Nf, DX_trans, wave):
65
+ """helper for unpacking fftd DX in main loop"""
66
+ if m == 0:
67
+ # half of lowest and highest frequency bin pixels are redundant, so store them in even and odd components of m=0 respectively
68
+ for n in range(0, Nt, 2):
69
+ wave[n, 0] = np.real(DX_trans[n] * np.sqrt(2))
70
+ elif m == Nf:
71
+ for n in range(0, Nt, 2):
72
+ wave[n + 1, 0] = np.real(DX_trans[n] * np.sqrt(2))
73
+ else:
74
+ for n in range(0, Nt):
75
+ if m % 2:
76
+ if (n + m) % 2:
77
+ wave[n, m] = -np.imag(DX_trans[n])
78
+ else:
79
+ wave[n, m] = np.real(DX_trans[n])
80
+ else:
81
+ if (n + m) % 2:
82
+ wave[n, m] = np.imag(DX_trans[n])
83
+ else:
84
+ wave[n, m] = np.real(DX_trans[n])
@@ -0,0 +1,63 @@
1
+ """helper functions for transform_time.py"""
2
+ import numpy as np
3
+ from numba import njit
4
+
5
+ from ... import fft_funcs as fft
6
+
7
+
8
+ def transform_wavelet_time_helper(
9
+ data, Nf: int, Nt: int, phi, mult: int
10
+ ) -> np.ndarray:
11
+ """helper function to do the wavelet transform in the time domain"""
12
+ # the time domain data stream
13
+ ND = Nf * Nt
14
+
15
+ K = mult * 2 * Nf
16
+
17
+ assert len(data) == ND, f"len(data)={len(data)} != Nf*Nt={ND}"
18
+
19
+ # windowed data packets
20
+ wdata = np.zeros(K)
21
+ wave = np.zeros((Nt, Nf)) # wavelet wavepacket transform of the signal
22
+ data_pad = np.concatenate((data, data[:K]))
23
+
24
+ for i in range(0, Nt):
25
+ assign_wdata(i, K, ND, Nf, wdata, data_pad, phi)
26
+ wdata_trans = fft.rfft(wdata, K)
27
+ pack_wave(i, mult, Nf, wdata_trans, wave)
28
+
29
+ return wave
30
+
31
+
32
+ @njit()
33
+ def assign_wdata(
34
+ i: int,
35
+ K: int,
36
+ ND: int,
37
+ Nf: int,
38
+ wdata: np.ndarray,
39
+ data_pad: np.ndarray,
40
+ phi: np.ndarray,
41
+ ):
42
+ """Assign wdata to be FFT'd in a loop with K extra values on the right to loop."""
43
+ jj = (i * Nf - K // 2) % ND # Periodically wrap the data
44
+ for j in range(K):
45
+ wdata[j] = data_pad[jj] * phi[j] # Apply the window
46
+ jj = (jj + 1) % ND # Periodically wrap the data
47
+
48
+
49
+ @njit()
50
+ def pack_wave(
51
+ i: int, mult: int, Nf: int, wdata_trans: np.ndarray, wave: np.ndarray
52
+ ):
53
+ """pack fftd wdata into wave array"""
54
+ if i % 2 == 0 and i < wave.shape[0] - 1:
55
+ # m=0 value at even Nt and
56
+ wave[i, 0] = np.real(wdata_trans[0]) / np.sqrt(2)
57
+ wave[i + 1, 0] = np.real(wdata_trans[Nf * mult]) / np.sqrt(2)
58
+
59
+ for j in range(1, Nf):
60
+ if (i + j) % 2:
61
+ wave[i, j] = -np.imag(wdata_trans[j * mult])
62
+ else:
63
+ wave[i, j] = np.real(wdata_trans[j * mult])
File without changes
@@ -0,0 +1,6 @@
1
+ """ Fisher information matrix in the wavelet domain:
2
+
3
+ F[ij] = Sum_{i,j} h_hat[ti, fi][i] h_hat[ti, fi][j] / PSD[ti, fi]
4
+
5
+
6
+ """
pywavelet/utils/snr.py ADDED
@@ -0,0 +1,37 @@
1
+ """Wavelet domain SNR
2
+
3
+ SNR(h) = Sum_{ti,fi} [ h_hat[ti,fi] d[ti,fi] / PSD[ti,fi] ],
4
+
5
+ where h_hat[ti,fi] is the unit normalized wavelet transform of the model:
6
+ h_hat[ti,fi] = h[ti,fi] / sqrt(<h[ti,fi] | h[ti,fi] >)
7
+
8
+ NOTE: to maximize over masses and spins we require some additional steps....
9
+
10
+
11
+ """
12
+
13
+ import numpy as np
14
+
15
+
16
+ def compute_snr(h: np.ndarray, d: np.ndarray, PSD: np.ndarray) -> float:
17
+ """Compute the SNR of a model h[ti,fi] given data d[ti,fi] and PSD[ti,fi].
18
+
19
+ SNR(h) = Sum_{ti,fi} [ h_hat[ti,fi] d[ti,fi] / PSD[ti,fi]
20
+
21
+ Parameters
22
+ ----------
23
+ h : np.ndarray
24
+ The model in the wavelet domain (binned in [ti,fi]).
25
+ d : np.ndarray
26
+ The data in the wavelet domain (binned in [ti,fi]).
27
+ PSD : np.ndarray
28
+ The PSD in the wavelet domain (binned in [ti,fi]).
29
+
30
+ Returns
31
+ -------
32
+ float
33
+ The SNR of the model h given data d and PSD.
34
+
35
+ """
36
+ h_hat = h / np.sqrt(np.tensordot(h.T, h))
37
+ return np.tensordot(h_hat.T, d / PSD)
File without changes
File without changes
@@ -0,0 +1,2 @@
1
+ from .functional_waveform_generator import FunctionalWaveformGenerator
2
+ from .lookuptable_waveform_generator import LookupTableWaveformGenerator
@@ -0,0 +1,33 @@
1
+ import numpy as np
2
+
3
+ from ...transforms import from_time_to_wavelet
4
+ from ..waveform_generator import WaveformGenerator
5
+
6
+
7
+ class FunctionalWaveformGenerator(WaveformGenerator):
8
+ def __init__(self, func, Nf=1024, Nt=1024, mult=32):
9
+ super().__init__("Functional")
10
+ self.func = func
11
+ self.Nf = Nf
12
+ self.Nt = Nt
13
+ self.mult = mult
14
+
15
+ def __call__(self, **params) -> np.ndarray:
16
+ """
17
+ Generate a waveform from a functional form.
18
+
19
+ Parameters
20
+ ----------
21
+ params: dict
22
+ A dictionary of parameters to pass to the functional form.
23
+
24
+ Returns
25
+ -------
26
+ wavelet_signal: np.ndarray
27
+ The waveform in the wavelet domain matrix of (Nt, Nf).
28
+ """
29
+ ht = self.func(**params)
30
+ wavelet_signal = from_time_to_wavelet(
31
+ ht, Nf=self.Nf, Nt=self.Nt, mult=self.mult
32
+ )
33
+ return wavelet_signal
@@ -0,0 +1,15 @@
1
+ from ...transforms import from_time_to_wavelet
2
+ from ..waveform_generator import WaveformGenerator
3
+
4
+
5
+ class LookupTableWaveformGenerator(WaveformGenerator):
6
+ def __init__(self, name, func, Nf=1024, Nt=1024):
7
+ super().__init__(name)
8
+ self.func = func
9
+ self.Nf = Nf
10
+ self.Nt = Nt
11
+
12
+ def __call__(self, **params):
13
+ time_signal = self.func(**params)
14
+ wavelet_signal = from_time_to_wavelet(time_signal, Nf=1024, Nt=1024)
15
+ return wavelet_signal
@@ -0,0 +1,14 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class WaveformGenerator(ABC):
5
+ def __init__(self, name):
6
+ self.name = name
7
+
8
+ @abstractmethod
9
+ def __call__(self, **params):
10
+ """Call the waveform generator (using the lookup table) with the given parameters."""
11
+ pass
12
+
13
+ def __repr__(self):
14
+ return f"{self.__class__.__name__}(name={self.name})"
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.1
2
+ Name: pywavelet
3
+ Version: 0.0.1b0
4
+ Summary: WDM wavelet transform your timeseries!
5
+ Author-email: Pywavelet Team <pywavelet@gmail.com>
6
+ Project-URL: Homepage, https://github.com/pypa/pywavelet
7
+ Project-URL: Bug Reports, https://github.com/pypa/pywavelet/issues
8
+ Project-URL: Funding, https://donate.pypi.org
9
+ Project-URL: Say Thanks!, http://saythanks.io/to/example
10
+ Project-URL: Source, https://github.com/pypa/pywavelet/
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy
19
+ Requires-Dist: numba
20
+ Requires-Dist: matplotlib
21
+ Requires-Dist: tqdm
22
+ Requires-Dist: loguru
23
+ Requires-Dist: bilby
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest >=6.0 ; extra == 'dev'
26
+ Requires-Dist: pytest-cov >=4.1.0 ; extra == 'dev'
27
+ Requires-Dist: pre-commit ; extra == 'dev'
28
+ Requires-Dist: flake8 >=5.0.4 ; extra == 'dev'
29
+ Requires-Dist: black >=22.12.0 ; extra == 'dev'
30
+ Requires-Dist: isort ; extra == 'dev'
31
+ Requires-Dist: mypy ; extra == 'dev'
32
+ Requires-Dist: pycbc ; extra == 'dev'
33
+ Requires-Dist: bilby ; extra == 'dev'
34
+ Requires-Dist: jupyter-book ; extra == 'dev'
35
+
@@ -0,0 +1,29 @@
1
+ pywavelet/__init__.py,sha256=k153Uh9uMwWTbdIUTBwDQ8okniciXJrIMXbVrQUA05A,53
2
+ pywavelet/_version.py,sha256=8kGCB5GtL1bKeIoFN7UDyb7dIVhe1uN9mad-aDiInQs,413
3
+ pywavelet/fft_funcs.py,sha256=5Vwugv4iGnHAy2gQwiHRsKby0Ml7HEidrCh5raKs0LE,488
4
+ pywavelet/logger.py,sha256=1kOyFvC86npJZUF2sajymxnSF2CrBjWiqsKP858_1xs,315
5
+ pywavelet/likelihood/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pywavelet/likelihood/likelihood_base.py,sha256=8IA7CV6SuVmTRiYfGmujLQoaUNqpWn_c9EDpMT3sVHQ,159
7
+ pywavelet/likelihood/whittle.py,sha256=cEMA1CxiNv3p-InTLJoKXJIesqoZgcTitlOMZFeqceY,685
8
+ pywavelet/transforms/__init__.py,sha256=xOvUrH1d0zWzZlxr713_ONwXGJ0JUt1qkUGjfQgG8nc,232
9
+ pywavelet/transforms/common.py,sha256=EXJZEwADzhMKFYgSpCYlup3zHbM75_35jc0cIqTPrEw,2034
10
+ pywavelet/transforms/from_wavelets/__init__.py,sha256=crkC_i_Rr_lnuK26D9_4j0VIc5fCB0RrZN7qboCIVSE,1008
11
+ pywavelet/transforms/from_wavelets/inverse_wavelet_freq_funcs.py,sha256=oMWe7YM6o98NBEgoJuolkwzSotDp-8flg5KLqVeUNLo,2454
12
+ pywavelet/transforms/from_wavelets/inverse_wavelet_time_funcs.py,sha256=yrKTcETVzX3BAGovtSSTrMR8BL-bePCeugAf3mpqdYA,4493
13
+ pywavelet/transforms/to_wavelets/__init__.py,sha256=pmGgx6zOMKmZBRcpznqoBKkiw6cgLp91X0k7Q8eemVQ,1669
14
+ pywavelet/transforms/to_wavelets/transform_freq_funcs.py,sha256=PsFV6wJedJmbZIZ40-47igPxzkxEp0o5aSgQl0pMosA,2759
15
+ pywavelet/transforms/to_wavelets/transform_time_funcs.py,sha256=Ijb_od_daKJ3TO2GkfABGnHNhVVkUZfo2RaDIRGQmzw,1775
16
+ pywavelet/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ pywavelet/utils/fisher_matrix.py,sha256=QYYlr1K-NR_MWVAfRm1gNVq5iun49ZQ0fNns-PkLEnY,127
18
+ pywavelet/utils/snr.py,sha256=qgKu0BgW1MKA5UkI352r_kHFdoOQoLVdmacoc9Ewaoo,990
19
+ pywavelet/waveform_generator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ pywavelet/waveform_generator/build_lookup_table.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ pywavelet/waveform_generator/waveform_generator.py,sha256=IDicy9-IM6u4z60EbAU7tTDDdNk2yty6PU_simFsyJc,372
22
+ pywavelet/waveform_generator/generators/__init__.py,sha256=sUuWkQMsJ-cydLioiKUkIG1S0OhJsFTMwIdOJwDF0yo,144
23
+ pywavelet/waveform_generator/generators/functional_waveform_generator.py,sha256=1Yte9aWEvZENh8u7XrlAFq5ghJVbSgjAxJgjUhy2nM8,928
24
+ pywavelet/waveform_generator/generators/lookuptable_waveform_generator.py,sha256=HC1bJuDa33clb52xu1MM7DFnHwb0qdv0uJM-r0uTxgk,491
25
+ pywavelet/waveform_generator/generators/rom_waveform_generator.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ pywavelet-0.0.1b0.dist-info/METADATA,sha256=akrtZZGTKNkBolvoJp1JWxJQmb1tE-yHQla32Fubp2A,1324
27
+ pywavelet-0.0.1b0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
28
+ pywavelet-0.0.1b0.dist-info/top_level.txt,sha256=g0Ezt0Rg0X-nrd-a0pAXKVRkuWNsF2M9Ynsjb9b2UYQ,10
29
+ pywavelet-0.0.1b0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.41.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pywavelet