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 +5 -0
- pywavelet/_version.py +16 -0
- pywavelet/fft_funcs.py +16 -0
- pywavelet/likelihood/__init__.py +0 -0
- pywavelet/likelihood/likelihood_base.py +9 -0
- pywavelet/likelihood/whittle.py +24 -0
- pywavelet/logger.py +15 -0
- pywavelet/transforms/__init__.py +10 -0
- pywavelet/transforms/common.py +77 -0
- pywavelet/transforms/from_wavelets/__init__.py +25 -0
- pywavelet/transforms/from_wavelets/inverse_wavelet_freq_funcs.py +76 -0
- pywavelet/transforms/from_wavelets/inverse_wavelet_time_funcs.py +133 -0
- pywavelet/transforms/to_wavelets/__init__.py +52 -0
- pywavelet/transforms/to_wavelets/transform_freq_funcs.py +84 -0
- pywavelet/transforms/to_wavelets/transform_time_funcs.py +63 -0
- pywavelet/utils/__init__.py +0 -0
- pywavelet/utils/fisher_matrix.py +6 -0
- pywavelet/utils/snr.py +37 -0
- pywavelet/waveform_generator/__init__.py +0 -0
- pywavelet/waveform_generator/build_lookup_table.py +0 -0
- pywavelet/waveform_generator/generators/__init__.py +2 -0
- pywavelet/waveform_generator/generators/functional_waveform_generator.py +33 -0
- pywavelet/waveform_generator/generators/lookuptable_waveform_generator.py +15 -0
- pywavelet/waveform_generator/generators/rom_waveform_generator.py +0 -0
- pywavelet/waveform_generator/waveform_generator.py +14 -0
- pywavelet-0.0.1b0.dist-info/METADATA +35 -0
- pywavelet-0.0.1b0.dist-info/RECORD +29 -0
- pywavelet-0.0.1b0.dist-info/WHEEL +5 -0
- pywavelet-0.0.1b0.dist-info/top_level.txt +1 -0
pywavelet/__init__.py
ADDED
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,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,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
|
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,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
|
File without changes
|
@@ -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 @@
|
|
1
|
+
pywavelet
|