pywavelet 0.0.1b0__py3-none-any.whl

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.
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