pywavelet 0.0.1b0__py3-none-any.whl → 0.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pywavelet/__init__.py +1 -1
- pywavelet/_version.py +2 -2
- pywavelet/logger.py +6 -7
- pywavelet/transforms/__init__.py +10 -10
- pywavelet/transforms/forward/__init__.py +4 -0
- pywavelet/transforms/forward/from_freq.py +80 -0
- pywavelet/transforms/forward/from_time.py +66 -0
- pywavelet/transforms/forward/main.py +128 -0
- pywavelet/transforms/forward/wavelet_bins.py +58 -0
- pywavelet/transforms/inverse/__init__.py +3 -0
- pywavelet/transforms/inverse/main.py +96 -0
- pywavelet/transforms/{from_wavelets/inverse_wavelet_freq_funcs.py → inverse/to_freq.py} +43 -32
- pywavelet/transforms/{from_wavelets/inverse_wavelet_time_funcs.py → inverse/to_time.py} +49 -21
- pywavelet/transforms/phi_computer.py +152 -0
- pywavelet/transforms/types/__init__.py +4 -0
- pywavelet/transforms/types/common.py +53 -0
- pywavelet/transforms/types/frequencyseries.py +237 -0
- pywavelet/transforms/types/plotting.py +341 -0
- pywavelet/transforms/types/timeseries.py +280 -0
- pywavelet/transforms/types/wavelet.py +374 -0
- pywavelet/transforms/types/wavelet_mask.py +34 -0
- pywavelet/utils.py +76 -0
- pywavelet-0.1.0.dist-info/METADATA +35 -0
- pywavelet-0.1.0.dist-info/RECORD +26 -0
- {pywavelet-0.0.1b0.dist-info → pywavelet-0.1.0.dist-info}/WHEEL +1 -1
- pywavelet/fft_funcs.py +0 -16
- pywavelet/likelihood/__init__.py +0 -0
- pywavelet/likelihood/likelihood_base.py +0 -9
- pywavelet/likelihood/whittle.py +0 -24
- pywavelet/transforms/common.py +0 -77
- pywavelet/transforms/from_wavelets/__init__.py +0 -25
- pywavelet/transforms/to_wavelets/__init__.py +0 -52
- pywavelet/transforms/to_wavelets/transform_freq_funcs.py +0 -84
- pywavelet/transforms/to_wavelets/transform_time_funcs.py +0 -63
- pywavelet/utils/__init__.py +0 -0
- pywavelet/utils/fisher_matrix.py +0 -6
- pywavelet/utils/snr.py +0 -37
- pywavelet/waveform_generator/__init__.py +0 -0
- pywavelet/waveform_generator/build_lookup_table.py +0 -0
- pywavelet/waveform_generator/generators/__init__.py +0 -2
- pywavelet/waveform_generator/generators/functional_waveform_generator.py +0 -33
- pywavelet/waveform_generator/generators/lookuptable_waveform_generator.py +0 -15
- pywavelet/waveform_generator/generators/rom_waveform_generator.py +0 -0
- pywavelet/waveform_generator/waveform_generator.py +0 -14
- pywavelet-0.0.1b0.dist-info/METADATA +0 -35
- pywavelet-0.0.1b0.dist-info/RECORD +0 -29
- {pywavelet-0.0.1b0.dist-info → pywavelet-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,374 @@
|
|
1
|
+
import matplotlib.pyplot as plt
|
2
|
+
from typing import Optional, Tuple
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
|
6
|
+
from .common import is_documented_by, xp, fmt_timerange
|
7
|
+
from .plotting import plot_wavelet_grid, plot_wavelet_trend
|
8
|
+
|
9
|
+
|
10
|
+
class Wavelet:
|
11
|
+
"""
|
12
|
+
A class to represent a wavelet transform result, with methods for plotting and accessing key properties like duration, sample rate, and grid size.
|
13
|
+
|
14
|
+
Attributes
|
15
|
+
----------
|
16
|
+
data : xp.ndarray
|
17
|
+
2D array representing the wavelet coefficients (frequency x time).
|
18
|
+
time : xp.ndarray
|
19
|
+
Array of time points.
|
20
|
+
freq : xp.ndarray
|
21
|
+
Array of corresponding frequency points.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
data: xp.ndarray,
|
27
|
+
time: xp.ndarray,
|
28
|
+
freq: xp.ndarray,
|
29
|
+
):
|
30
|
+
"""
|
31
|
+
Initialize the Wavelet object with data, time, and frequency arrays.
|
32
|
+
|
33
|
+
Parameters
|
34
|
+
----------
|
35
|
+
data : xp.ndarray
|
36
|
+
2D array representing the wavelet coefficients (frequency x time).
|
37
|
+
time : xp.ndarray
|
38
|
+
Array of time points.
|
39
|
+
freq : xp.ndarray
|
40
|
+
Array of corresponding frequency points.
|
41
|
+
|
42
|
+
Raises
|
43
|
+
------
|
44
|
+
AssertionError
|
45
|
+
If the length of the time array does not match the number of time bins in `data`.
|
46
|
+
If the length of the frequency array does not match the number of frequency bins in `data`.
|
47
|
+
"""
|
48
|
+
nf, nt = data.shape
|
49
|
+
assert len(time) == nt, f"len(time)={len(time)} != nt={nt}"
|
50
|
+
assert len(freq) == nf, f"len(freq)={len(freq)} != nf={nf}"
|
51
|
+
|
52
|
+
self.data = data
|
53
|
+
self.time = time
|
54
|
+
self.freq = freq
|
55
|
+
|
56
|
+
@is_documented_by(plot_wavelet_grid)
|
57
|
+
def plot(self, ax=None, *args, **kwargs) -> Tuple[plt.Figure, plt.Axes]:
|
58
|
+
kwargs["time_grid"] = kwargs.get("time_grid", self.time)
|
59
|
+
kwargs["freq_grid"] = kwargs.get("freq_grid", self.freq)
|
60
|
+
return plot_wavelet_grid(wavelet_data=self.data, ax=ax, *args, **kwargs)
|
61
|
+
|
62
|
+
@is_documented_by(plot_wavelet_trend)
|
63
|
+
def plot_trend(self, ax=None, *args, **kwargs) -> Tuple[plt.Figure, plt.Axes]:
|
64
|
+
kwargs["time_grid"] = kwargs.get("time_grid", self.time)
|
65
|
+
kwargs["freq_grid"] = kwargs.get("freq_grid", self.freq)
|
66
|
+
return plot_wavelet_trend(wavelet_data=self.data, ax=ax, *args, **kwargs)
|
67
|
+
|
68
|
+
@property
|
69
|
+
def Nt(self) -> int:
|
70
|
+
"""
|
71
|
+
Number of time bins.
|
72
|
+
|
73
|
+
Returns
|
74
|
+
-------
|
75
|
+
int
|
76
|
+
Length of the time array.
|
77
|
+
"""
|
78
|
+
return len(self.time)
|
79
|
+
|
80
|
+
@property
|
81
|
+
def Nf(self) -> int:
|
82
|
+
"""
|
83
|
+
Number of frequency bins.
|
84
|
+
|
85
|
+
Returns
|
86
|
+
-------
|
87
|
+
int
|
88
|
+
Length of the frequency array.
|
89
|
+
"""
|
90
|
+
return len(self.freq)
|
91
|
+
|
92
|
+
@property
|
93
|
+
def ND(self) -> int:
|
94
|
+
"""
|
95
|
+
Total number of data points in the wavelet grid.
|
96
|
+
|
97
|
+
Returns
|
98
|
+
-------
|
99
|
+
int
|
100
|
+
The product of `Nt` and `Nf`.
|
101
|
+
"""
|
102
|
+
return self.Nt * self.Nf
|
103
|
+
|
104
|
+
@property
|
105
|
+
def delta_T(self) -> float:
|
106
|
+
"""
|
107
|
+
Time resolution (ΔT) of the wavelet grid.
|
108
|
+
|
109
|
+
Returns
|
110
|
+
-------
|
111
|
+
float
|
112
|
+
Difference between consecutive time points.
|
113
|
+
"""
|
114
|
+
return self.time[1] - self.time[0]
|
115
|
+
|
116
|
+
@property
|
117
|
+
def delta_F(self) -> float:
|
118
|
+
"""
|
119
|
+
Frequency resolution (ΔF) of the wavelet grid.
|
120
|
+
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
float
|
124
|
+
Inverse of twice the time resolution.
|
125
|
+
"""
|
126
|
+
return 1 / (2 * self.delta_T)
|
127
|
+
|
128
|
+
@property
|
129
|
+
def duration(self) -> float:
|
130
|
+
"""
|
131
|
+
Duration of the wavelet grid.
|
132
|
+
|
133
|
+
Returns
|
134
|
+
-------
|
135
|
+
float
|
136
|
+
Total duration in seconds.
|
137
|
+
"""
|
138
|
+
return float(self.Nt * self.delta_T)
|
139
|
+
|
140
|
+
@property
|
141
|
+
def delta_t(self) -> float:
|
142
|
+
"""
|
143
|
+
Time resolution of the wavelet grid, normalized by the total number of data points.
|
144
|
+
|
145
|
+
Returns
|
146
|
+
-------
|
147
|
+
float
|
148
|
+
Time resolution per data point.
|
149
|
+
"""
|
150
|
+
return float(self.duration / self.ND)
|
151
|
+
|
152
|
+
@property
|
153
|
+
def delta_f(self) -> float:
|
154
|
+
"""
|
155
|
+
Frequency resolution of the wavelet grid, normalized by the total number of data points.
|
156
|
+
|
157
|
+
Returns
|
158
|
+
-------
|
159
|
+
float
|
160
|
+
Frequency resolution per data point.
|
161
|
+
"""
|
162
|
+
return 1 / (2 * self.delta_t)
|
163
|
+
|
164
|
+
@property
|
165
|
+
def t0(self) -> float:
|
166
|
+
"""
|
167
|
+
Initial time point of the wavelet grid.
|
168
|
+
|
169
|
+
Returns
|
170
|
+
-------
|
171
|
+
float
|
172
|
+
First time point in the time array.
|
173
|
+
"""
|
174
|
+
return float(self.time[0])
|
175
|
+
|
176
|
+
@property
|
177
|
+
def tend(self) -> float:
|
178
|
+
"""
|
179
|
+
Final time point of the wavelet grid.
|
180
|
+
|
181
|
+
Returns
|
182
|
+
-------
|
183
|
+
float
|
184
|
+
Last time point in the time array.
|
185
|
+
"""
|
186
|
+
return float(self.time[-1])
|
187
|
+
|
188
|
+
@property
|
189
|
+
def shape(self) -> Tuple[int, int]:
|
190
|
+
"""
|
191
|
+
Shape of the wavelet grid.
|
192
|
+
|
193
|
+
Returns
|
194
|
+
-------
|
195
|
+
Tuple[int, int]
|
196
|
+
Tuple representing the shape of the data array (Nf, Nt).
|
197
|
+
"""
|
198
|
+
return self.data.shape
|
199
|
+
|
200
|
+
@property
|
201
|
+
def sample_rate(self) -> float:
|
202
|
+
"""
|
203
|
+
Sample rate of the wavelet grid.
|
204
|
+
|
205
|
+
Returns
|
206
|
+
-------
|
207
|
+
float
|
208
|
+
Sample rate calculated as the inverse of the time resolution.
|
209
|
+
"""
|
210
|
+
return 1 / self.delta_t
|
211
|
+
|
212
|
+
@property
|
213
|
+
def fs(self) -> float:
|
214
|
+
"""
|
215
|
+
Sample rate (fs) of the wavelet grid.
|
216
|
+
|
217
|
+
Returns
|
218
|
+
-------
|
219
|
+
float
|
220
|
+
The sample rate.
|
221
|
+
"""
|
222
|
+
return self.sample_rate
|
223
|
+
|
224
|
+
@property
|
225
|
+
def nyquist_frequency(self) -> float:
|
226
|
+
"""
|
227
|
+
Nyquist frequency of the wavelet grid.
|
228
|
+
|
229
|
+
Returns
|
230
|
+
-------
|
231
|
+
float
|
232
|
+
Nyquist frequency, which is half of the sample rate.
|
233
|
+
"""
|
234
|
+
return self.sample_rate / 2
|
235
|
+
|
236
|
+
def to_timeseries(self, nx: float = 4.0, mult: int = 32) -> "TimeSeries":
|
237
|
+
"""
|
238
|
+
Convert the wavelet grid to a time-domain signal.
|
239
|
+
|
240
|
+
Returns
|
241
|
+
-------
|
242
|
+
TimeSeries
|
243
|
+
A `TimeSeries` object representing the time-domain signal.
|
244
|
+
"""
|
245
|
+
from ..inverse import from_wavelet_to_time
|
246
|
+
return from_wavelet_to_time(self, dt=self.delta_t, nx=nx, mult=mult)
|
247
|
+
|
248
|
+
def to_frequencyseries(self, nx: float = 4.0) -> "FrequencySeries":
|
249
|
+
"""
|
250
|
+
Convert the wavelet grid to a frequency-domain signal.
|
251
|
+
|
252
|
+
Returns
|
253
|
+
-------
|
254
|
+
FrequencySeries
|
255
|
+
A `FrequencySeries` object representing the frequency-domain signal.
|
256
|
+
"""
|
257
|
+
from ..inverse import from_wavelet_to_freq
|
258
|
+
return from_wavelet_to_freq(self, dt=self.delta_t, nx=nx)
|
259
|
+
|
260
|
+
def __repr__(self) -> str:
|
261
|
+
"""
|
262
|
+
Return a string representation of the Wavelet object.
|
263
|
+
|
264
|
+
Returns
|
265
|
+
-------
|
266
|
+
str
|
267
|
+
String containing information about the shape of the wavelet grid.
|
268
|
+
"""
|
269
|
+
|
270
|
+
frange = ",".join([f"{f:.2e}" for f in (self.freq[0], self.freq[-1])])
|
271
|
+
trange = fmt_timerange((self.t0, self.tend))
|
272
|
+
Nfpow2 = int(np.log2(self.shape[0]))
|
273
|
+
Ntpow2 = int(np.log2(self.shape[1]))
|
274
|
+
shapef = f"NfxNf=[2^{Nfpow2}, 2^{Ntpow2}]"
|
275
|
+
return f"Wavelet({shapef}, [{frange}]Hz, {trange})"
|
276
|
+
|
277
|
+
def __add__(self, other):
|
278
|
+
"""Element-wise addition of two Wavelet objects."""
|
279
|
+
if isinstance(other, Wavelet):
|
280
|
+
return Wavelet(data=self.data + other.data, time=self.time, freq=self.freq)
|
281
|
+
elif isinstance(other, float):
|
282
|
+
return Wavelet(data=self.data + other, time=self.time, freq=self.freq)
|
283
|
+
|
284
|
+
def __sub__(self, other):
|
285
|
+
"""Element-wise subtraction of two Wavelet objects."""
|
286
|
+
if isinstance(other, Wavelet):
|
287
|
+
return Wavelet(data=self.data - other.data, time=self.time, freq=self.freq)
|
288
|
+
elif isinstance(other, float):
|
289
|
+
return Wavelet(data=self.data - other, time=self.time, freq=self.freq)
|
290
|
+
|
291
|
+
def __mul__(self, other):
|
292
|
+
"""Element-wise multiplication of two Wavelet objects."""
|
293
|
+
if isinstance(other, Wavelet):
|
294
|
+
return Wavelet(data=self.data * other.data, time=self.time, freq=self.freq)
|
295
|
+
elif isinstance(other, float):
|
296
|
+
return Wavelet(data=self.data * other, time=self.time, freq=self.freq)
|
297
|
+
|
298
|
+
def __truediv__(self, other):
|
299
|
+
"""Element-wise division of two Wavelet objects."""
|
300
|
+
if isinstance(other, Wavelet):
|
301
|
+
return Wavelet(data=self.data / other.data, time=self.time, freq=self.freq)
|
302
|
+
elif isinstance(other, float):
|
303
|
+
return Wavelet(data=self.data / other, time=self.time, freq=self.freq)
|
304
|
+
|
305
|
+
def __eq__(self, other:"Wavelet") -> bool:
|
306
|
+
"""Element-wise comparison of two Wavelet objects."""
|
307
|
+
data_all_same = xp.isclose(xp.nansum(self.data - other.data), 0)
|
308
|
+
time_same = (self.time == other.time).all()
|
309
|
+
freq_same = (self.freq == other.freq).all()
|
310
|
+
return data_all_same and time_same and freq_same
|
311
|
+
|
312
|
+
|
313
|
+
def noise_weighted_inner_product(self, other:"Wavelet", psd:"Wavelet") -> float:
|
314
|
+
"""
|
315
|
+
Compute the noise-weighted inner product of two wavelet grids given a PSD.
|
316
|
+
|
317
|
+
Parameters
|
318
|
+
----------
|
319
|
+
other : Wavelet
|
320
|
+
A `Wavelet` object representing the other wavelet grid.
|
321
|
+
psd : Wavelet
|
322
|
+
A `Wavelet` object representing the power spectral density.
|
323
|
+
|
324
|
+
Returns
|
325
|
+
-------
|
326
|
+
float
|
327
|
+
The noise-weighted inner product.
|
328
|
+
"""
|
329
|
+
from ...utils import noise_weighted_inner_product
|
330
|
+
return noise_weighted_inner_product(self, other, psd)
|
331
|
+
|
332
|
+
|
333
|
+
def matched_filter_snr(self, template:"Wavelet", psd:"Wavelet") -> float:
|
334
|
+
"""
|
335
|
+
Compute the matched filter SNR of the wavelet grid given a template.
|
336
|
+
|
337
|
+
Parameters
|
338
|
+
----------
|
339
|
+
template : Wavelet
|
340
|
+
A `Wavelet` object representing the template.
|
341
|
+
|
342
|
+
Returns
|
343
|
+
-------
|
344
|
+
float
|
345
|
+
The matched filter signal-to-noise ratio.
|
346
|
+
"""
|
347
|
+
mf = self.noise_weighted_inner_product(template, psd)
|
348
|
+
return mf / self.optimal_snr(psd)
|
349
|
+
|
350
|
+
def optimal_snr(self, psd:"Wavelet") -> float:
|
351
|
+
"""
|
352
|
+
Compute the optimal SNR of the wavelet grid given a PSD.
|
353
|
+
|
354
|
+
Parameters
|
355
|
+
----------
|
356
|
+
psd : Wavelet
|
357
|
+
A `Wavelet` object representing the power spectral density.
|
358
|
+
|
359
|
+
Returns
|
360
|
+
-------
|
361
|
+
float
|
362
|
+
The optimal signal-to-noise ratio.
|
363
|
+
"""
|
364
|
+
return xp.sqrt(self.noise_weighted_inner_product(self, psd))
|
365
|
+
|
366
|
+
def __copy__(self):
|
367
|
+
return Wavelet(
|
368
|
+
data=self.data.copy(),
|
369
|
+
time=self.time.copy(),
|
370
|
+
freq=self.freq.copy()
|
371
|
+
)
|
372
|
+
|
373
|
+
def copy(self):
|
374
|
+
return self.__copy__()
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from .common import fmt_timerange, is_documented_by, xp
|
4
|
+
|
5
|
+
|
6
|
+
class WaveletMask:
|
7
|
+
def __init__(
|
8
|
+
self,
|
9
|
+
mask: xp.ndarray,
|
10
|
+
time: xp.ndarray,
|
11
|
+
freq: xp.ndarray,
|
12
|
+
):
|
13
|
+
self.mask = mask
|
14
|
+
self.time = time
|
15
|
+
self.freq = freq
|
16
|
+
|
17
|
+
def __repr__(self):
|
18
|
+
return f"WaveletMask({self.mask.shape}, {fmt_timerange(self.time)}, {self.freq})"
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def from_grid(cls, time_grid: xp.ndarray, freq_grid: xp.ndarray):
|
22
|
+
nt, nf = len(time_grid), len(freq_grid)
|
23
|
+
mask = xp.zeros((nf, nt), dtype=bool)
|
24
|
+
return cls(mask, time_grid, freq_grid)
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def from_frange(
|
28
|
+
cls, time_grid: xp.ndarray, freq_grid: xp.ndarray, frange: List[float]
|
29
|
+
):
|
30
|
+
self = cls.from_grid(time_grid, freq_grid)
|
31
|
+
self.mask[
|
32
|
+
(freq_grid >= frange[0]) & (freq_grid <= frange[1]), :
|
33
|
+
] = True
|
34
|
+
return self
|
pywavelet/utils.py
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
from typing import Union
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
from scipy.interpolate import interp1d
|
5
|
+
|
6
|
+
from .transforms.types import FrequencySeries, TimeSeries, Wavelet, WaveletMask
|
7
|
+
|
8
|
+
DATA_TYPE = Union[TimeSeries, FrequencySeries, Wavelet]
|
9
|
+
|
10
|
+
|
11
|
+
def evolutionary_psd_from_stationary_psd(
|
12
|
+
psd: np.ndarray,
|
13
|
+
psd_f: np.ndarray,
|
14
|
+
f_grid: np.ndarray,
|
15
|
+
t_grid: np.ndarray,
|
16
|
+
dt: float,
|
17
|
+
) -> Wavelet:
|
18
|
+
"""
|
19
|
+
PSD[ti,fi] = PSD[fi] / dt
|
20
|
+
"""
|
21
|
+
Nt = len(t_grid)
|
22
|
+
delta_f = f_grid[1] - f_grid[0]
|
23
|
+
nan_val = np.max(psd)
|
24
|
+
psd_grid = interp1d(
|
25
|
+
psd_f,
|
26
|
+
psd,
|
27
|
+
kind="nearest",
|
28
|
+
fill_value=nan_val,
|
29
|
+
bounds_error=False,
|
30
|
+
)(f_grid)
|
31
|
+
|
32
|
+
# repeat the PSD for each time bin
|
33
|
+
psd_grid = np.repeat(psd_grid[None, :], Nt, axis=0) / dt
|
34
|
+
return Wavelet(psd_grid.T, time=t_grid, freq=f_grid)
|
35
|
+
|
36
|
+
|
37
|
+
def noise_weighted_inner_product(
|
38
|
+
d: Wavelet, h: Wavelet, PSD: Wavelet
|
39
|
+
) -> float:
|
40
|
+
return np.nansum((d.data * h.data) / PSD.data)
|
41
|
+
|
42
|
+
|
43
|
+
def compute_snr(d: Wavelet, h: Wavelet, PSD: Wavelet) -> float:
|
44
|
+
"""Compute the SNR of a model h[ti,fi] given freqseries d[ti,fi] and PSD[ti,fi].
|
45
|
+
|
46
|
+
SNR(h) = Sum_{ti,fi} [ h_hat[ti,fi] d[ti,fi] / PSD[ti,fi]
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
h : np.ndarray
|
51
|
+
The model in the wavelet domain (binned in [ti,fi]).
|
52
|
+
d : np.ndarray
|
53
|
+
The freqseries in the wavelet domain (binned in [ti,fi]).
|
54
|
+
PSD : np.ndarray
|
55
|
+
The PSD in the wavelet domain (binned in [ti,fi]).
|
56
|
+
|
57
|
+
Returns
|
58
|
+
-------
|
59
|
+
float
|
60
|
+
The SNR of the model h given freqseries d and PSD.
|
61
|
+
|
62
|
+
"""
|
63
|
+
return np.sqrt(noise_weighted_inner_product(d, h, PSD))
|
64
|
+
|
65
|
+
|
66
|
+
def compute_likelihood(
|
67
|
+
data: Wavelet, template: Wavelet, psd: Wavelet, mask: WaveletMask = None
|
68
|
+
) -> float:
|
69
|
+
d = data.data
|
70
|
+
h = template.data
|
71
|
+
p = psd.data
|
72
|
+
if mask is not None:
|
73
|
+
m = mask.mask
|
74
|
+
d, h, p = d * m, h * m, p * m
|
75
|
+
|
76
|
+
return -0.5 * np.nansum((d - h) ** 2 / p)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: pywavelet
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: WDM wavelet transform your time/freq series!
|
5
|
+
Author-email: Pywavelet Team <avi.vajpeyi@gmail.com>
|
6
|
+
Project-URL: Homepage, https://pywavelet.github.io/pywavelet/
|
7
|
+
Project-URL: Bug Reports, https://pywavelet.com/pywavelet/pywavelet/issues
|
8
|
+
Project-URL: Source, https://github.com/pywavelet/pywavelet/
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
14
|
+
Requires-Python: >=3.8
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
Requires-Dist: numpy
|
17
|
+
Requires-Dist: numba
|
18
|
+
Requires-Dist: scipy
|
19
|
+
Requires-Dist: matplotlib
|
20
|
+
Requires-Dist: tqdm
|
21
|
+
Requires-Dist: rich
|
22
|
+
Requires-Dist: rocket-fft
|
23
|
+
Requires-Dist: astropy>=5.2.1
|
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: black[jupyter]>=22.12.0; extra == "dev"
|
31
|
+
Requires-Dist: isort; extra == "dev"
|
32
|
+
Requires-Dist: mypy; extra == "dev"
|
33
|
+
Requires-Dist: jupyter-book; extra == "dev"
|
34
|
+
Requires-Dist: GitPython; extra == "dev"
|
35
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
pywavelet/__init__.py,sha256=zcK3Qj4wTrGZF1rU3aT6yA9LvliAOD4DVOY7gNfHhCI,53
|
2
|
+
pywavelet/_version.py,sha256=IMl2Pr_Sy4LVRKy_Sm4CdwUl1Gryous6ncL96EMYsnM,411
|
3
|
+
pywavelet/logger.py,sha256=u5Et6QLUU0IuTnPnxfev5-4GYmihTo7_yBCTf-rVUsA,377
|
4
|
+
pywavelet/utils.py,sha256=-fZULV8ze1cxs0SP03Di-naYPgNvHbADsFrJ6Vzi7aE,1918
|
5
|
+
pywavelet/transforms/__init__.py,sha256=FolK8WiVEJmGDC9xMupYVI_essXaXS4LYWKbqEqGx6o,289
|
6
|
+
pywavelet/transforms/phi_computer.py,sha256=vo1PK9Z70kKV-1lfyRoxWdhSYqwIgJK5CRCCJVei3xI,4545
|
7
|
+
pywavelet/transforms/forward/__init__.py,sha256=Yq4Tg3Ze98-17C9FIkOqMUdiLHe9x_YoyuRvxOxMOP0,176
|
8
|
+
pywavelet/transforms/forward/from_freq.py,sha256=HWADciv746P5zWNhnyRKDEDACTQnwDTR4usf2k0VIWk,2663
|
9
|
+
pywavelet/transforms/forward/from_time.py,sha256=zfpvmq7UHnynzAnsM_Pf4MXpO1GJ0TaCCERQKNsaBNU,2340
|
10
|
+
pywavelet/transforms/forward/main.py,sha256=FJSLUUsk3I91lUeB2YFiXQdX8gB9maFW60e4tgrd45A,4053
|
11
|
+
pywavelet/transforms/forward/wavelet_bins.py,sha256=43CHVSjNgC59Af1_h9t33VBBqGHJmIUaOUhJgekhMnc,1419
|
12
|
+
pywavelet/transforms/inverse/__init__.py,sha256=J4KIzPzbHNg_8fV_c1MpPq3slSqHQV0j3VFrjfd1Nog,121
|
13
|
+
pywavelet/transforms/inverse/main.py,sha256=l5yFvzmWObrO5Xt_8KYp62w829ab0pQLxcv0-QxkvG0,3015
|
14
|
+
pywavelet/transforms/inverse/to_freq.py,sha256=SExZMax-8A-tJpIA86pYY61X2qvlZ2MrZY27uzCQSV0,2778
|
15
|
+
pywavelet/transforms/inverse/to_time.py,sha256=BAYvrr41QHIbzwYMPyMnzv5mqSx40YigmBruWBwtZwc,5041
|
16
|
+
pywavelet/transforms/types/__init__.py,sha256=bNw6gb1S7aA1-wl38LEL7j0BhJBJveYDt2ERn-0kU4k,147
|
17
|
+
pywavelet/transforms/types/common.py,sha256=sLBn2d9cuL6NYeIv6NIogDjY6rYPZAPzCtWGwMlAwkI,1290
|
18
|
+
pywavelet/transforms/types/frequencyseries.py,sha256=Rtwt486UL0-TgMAdcMMVpyfi5PSLzxFcdE_RsYuyxQk,7463
|
19
|
+
pywavelet/transforms/types/plotting.py,sha256=aEIFoSuQRYwYc2639yLkbujXx_aav5A5tXG29rOSMOQ,10275
|
20
|
+
pywavelet/transforms/types/timeseries.py,sha256=Nl1tiKZ7kwu-EZ5JtubwgzgyjQZR86eGU3C3kElIPNg,9296
|
21
|
+
pywavelet/transforms/types/wavelet.py,sha256=raODhyegBd1_esuv2YP6tK_Nj9fYaLQ6O4pxpiFAVZU,10790
|
22
|
+
pywavelet/transforms/types/wavelet_mask.py,sha256=Qst9NQmvr6vx0Tg8TMABRnDdD3iQgiwgaQKqyf0lwfU,937
|
23
|
+
pywavelet-0.1.0.dist-info/METADATA,sha256=sCAmMS5b2zEqsetgkkFgb7DopcPjn_V1o1y3uzIAEEE,1307
|
24
|
+
pywavelet-0.1.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
25
|
+
pywavelet-0.1.0.dist-info/top_level.txt,sha256=g0Ezt0Rg0X-nrd-a0pAXKVRkuWNsF2M9Ynsjb9b2UYQ,10
|
26
|
+
pywavelet-0.1.0.dist-info/RECORD,,
|
pywavelet/fft_funcs.py
DELETED
@@ -1,16 +0,0 @@
|
|
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
|
pywavelet/likelihood/__init__.py
DELETED
File without changes
|
pywavelet/likelihood/whittle.py
DELETED
@@ -1,24 +0,0 @@
|
|
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/transforms/common.py
DELETED
@@ -1,77 +0,0 @@
|
|
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
|