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.
Files changed (47) hide show
  1. pywavelet/__init__.py +1 -1
  2. pywavelet/_version.py +2 -2
  3. pywavelet/logger.py +6 -7
  4. pywavelet/transforms/__init__.py +10 -10
  5. pywavelet/transforms/forward/__init__.py +4 -0
  6. pywavelet/transforms/forward/from_freq.py +80 -0
  7. pywavelet/transforms/forward/from_time.py +66 -0
  8. pywavelet/transforms/forward/main.py +128 -0
  9. pywavelet/transforms/forward/wavelet_bins.py +58 -0
  10. pywavelet/transforms/inverse/__init__.py +3 -0
  11. pywavelet/transforms/inverse/main.py +96 -0
  12. pywavelet/transforms/{from_wavelets/inverse_wavelet_freq_funcs.py → inverse/to_freq.py} +43 -32
  13. pywavelet/transforms/{from_wavelets/inverse_wavelet_time_funcs.py → inverse/to_time.py} +49 -21
  14. pywavelet/transforms/phi_computer.py +152 -0
  15. pywavelet/transforms/types/__init__.py +4 -0
  16. pywavelet/transforms/types/common.py +53 -0
  17. pywavelet/transforms/types/frequencyseries.py +237 -0
  18. pywavelet/transforms/types/plotting.py +341 -0
  19. pywavelet/transforms/types/timeseries.py +280 -0
  20. pywavelet/transforms/types/wavelet.py +374 -0
  21. pywavelet/transforms/types/wavelet_mask.py +34 -0
  22. pywavelet/utils.py +76 -0
  23. pywavelet-0.1.0.dist-info/METADATA +35 -0
  24. pywavelet-0.1.0.dist-info/RECORD +26 -0
  25. {pywavelet-0.0.1b0.dist-info → pywavelet-0.1.0.dist-info}/WHEEL +1 -1
  26. pywavelet/fft_funcs.py +0 -16
  27. pywavelet/likelihood/__init__.py +0 -0
  28. pywavelet/likelihood/likelihood_base.py +0 -9
  29. pywavelet/likelihood/whittle.py +0 -24
  30. pywavelet/transforms/common.py +0 -77
  31. pywavelet/transforms/from_wavelets/__init__.py +0 -25
  32. pywavelet/transforms/to_wavelets/__init__.py +0 -52
  33. pywavelet/transforms/to_wavelets/transform_freq_funcs.py +0 -84
  34. pywavelet/transforms/to_wavelets/transform_time_funcs.py +0 -63
  35. pywavelet/utils/__init__.py +0 -0
  36. pywavelet/utils/fisher_matrix.py +0 -6
  37. pywavelet/utils/snr.py +0 -37
  38. pywavelet/waveform_generator/__init__.py +0 -0
  39. pywavelet/waveform_generator/build_lookup_table.py +0 -0
  40. pywavelet/waveform_generator/generators/__init__.py +0 -2
  41. pywavelet/waveform_generator/generators/functional_waveform_generator.py +0 -33
  42. pywavelet/waveform_generator/generators/lookuptable_waveform_generator.py +0 -15
  43. pywavelet/waveform_generator/generators/rom_waveform_generator.py +0 -0
  44. pywavelet/waveform_generator/waveform_generator.py +0 -14
  45. pywavelet-0.0.1b0.dist-info/METADATA +0 -35
  46. pywavelet-0.0.1b0.dist-info/RECORD +0 -29
  47. {pywavelet-0.0.1b0.dist-info → pywavelet-0.1.0.dist-info}/top_level.txt +0 -0
@@ -1,39 +1,48 @@
1
1
  """functions for computing the inverse wavelet transforms"""
2
2
  import numpy as np
3
3
  from numba import njit
4
+ from numpy import fft
4
5
 
5
- from ... import fft_funcs as fft
6
6
 
7
-
8
- def inverse_wavelet_time_helper_fast(wave_in, phi, Nf, Nt, mult):
7
+ def inverse_wavelet_time_helper_fast(
8
+ wave_in: np.ndarray, phi: np.ndarray, Nf: int, Nt: int, mult: int
9
+ ) -> np.ndarray:
9
10
  """helper loop for fast inverse wavelet transform"""
10
11
  ND = Nf * Nt
11
12
  K = mult * 2 * Nf
12
- # res = np.zeros(ND)
13
13
 
14
14
  # extend this array, we can use wrapping boundary conditions at end
15
15
  res = np.zeros(ND + K + Nf)
16
16
 
17
17
  afins = np.zeros(2 * Nf, dtype=np.complex128)
18
18
 
19
+ __core(Nf, Nt, K, ND, wave_in, phi, res, afins)
20
+
21
+ return res[:ND]
22
+
23
+
24
+ def __core(Nf: int, Nt: int, K: int, ND: int, wave_in: np.ndarray, phi: np.ndarray, res: np.ndarray, afins:np.ndarray) -> None:
19
25
  for n in range(0, Nt):
20
26
  if n % 2 == 0:
21
27
  pack_wave_time_helper_compact(n, Nf, Nt, wave_in, afins)
22
- ffts_fin = fft.fft(afins)
28
+ ffts_fin = np.fft.fft(afins)
23
29
  unpack_time_wave_helper_compact(n, Nf, Nt, K, phi, ffts_fin, res)
24
30
 
25
31
  # wrap boundary conditions
26
- res[: min(K + Nf, ND)] += res[ND : min(ND + K + Nf, 2 * ND)]
32
+ res[: min(K + Nf, ND)] += res[ND: min(ND + K + Nf, 2 * ND)]
27
33
  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):
34
+ res[: K + Nf - ND] += res[2 * ND: ND + K * Nf]
35
+
36
+
37
+ def unpack_time_wave_helper(
38
+ n: int,
39
+ Nf: int,
40
+ Nt: int,
41
+ K: int,
42
+ phis: np.ndarray,
43
+ fft_fin_real: np.ndarray,
44
+ res: np.ndarray,
45
+ ) -> None:
37
46
  """helper for time domain wavelet transform to unpack wavelet domain coefficients"""
38
47
  ND = Nf * Nt
39
48
 
@@ -52,10 +61,19 @@ def unpack_time_wave_helper(n, Nf, Nt, K, phis, fft_fin_real, res):
52
61
  k = 0
53
62
 
54
63
 
55
- @njit()
56
- def unpack_time_wave_helper_compact(n, Nf, Nt, K, phis, fft_fin, res):
64
+ def unpack_time_wave_helper_compact(
65
+ n: int,
66
+ Nf: int,
67
+ Nt: int,
68
+ K: int,
69
+ phis: np.ndarray,
70
+ fft_fin: np.ndarray,
71
+ res: np.ndarray,
72
+ ):
57
73
  """helper for time domain wavelet transform to unpack wavelet domain coefficients
58
74
  in compact representation where cosine and sine parts are real and imaginary parts
75
+
76
+ IN-PLACE EDITS res
59
77
  """
60
78
  ND = Nf * Nt
61
79
  fft_fin_real = np.zeros(4 * Nf)
@@ -79,8 +97,13 @@ def unpack_time_wave_helper_compact(n, Nf, Nt, K, phis, fft_fin, res):
79
97
 
80
98
 
81
99
  @njit()
82
- def pack_wave_time_helper(n, Nf, Nt, wave_in, afins):
83
- """helper for time domain transform to pack wavelet domain coefficients"""
100
+ def pack_wave_time_helper(
101
+ n: int, Nf: int, Nt: int, wave_in: np.ndarray, afins: np.ndarray
102
+ ) -> None:
103
+ """helper for time domain transform to pack wavelet domain coefficients
104
+
105
+ IN-PLACE EDITS afins
106
+ """
84
107
  if n % 2 == 0:
85
108
  # assign highest and lowest bin correctly
86
109
  afins[0] = np.sqrt(2) * wave_in[n, 0]
@@ -108,9 +131,14 @@ def pack_wave_time_helper(n, Nf, Nt, wave_in, afins):
108
131
 
109
132
 
110
133
  @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
134
+ def pack_wave_time_helper_compact(
135
+ n: int, Nf: int, Nt: int, wave_in: np.ndarray, afins: np.ndarray
136
+ ) -> None:
137
+ """
138
+ Helper for time domain transform to pack wavelet domain coefficients
113
139
  in packed representation with odd and even coefficients in real and imaginary pars
140
+
141
+ IN-PLACE EDITS afins
114
142
  """
115
143
  afins[0] = np.sqrt(2) * wave_in[n, 0]
116
144
  if n + 1 < Nt:
@@ -0,0 +1,152 @@
1
+ import numpy as np
2
+ from numpy import fft
3
+ from scipy.special import betainc
4
+
5
+ PI = np.pi
6
+
7
+
8
+ def phitilde_vec(
9
+ omega: np.ndarray, Nf: int, dt: float, d: float = 4.0
10
+ ) -> np.ndarray:
11
+ """Compute phi_tilde(omega_i) array, nx is filter steepness, defaults to 4.
12
+
13
+ Eq 11 of https://arxiv.org/pdf/2009.00043.pdf (Cornish et al. 2020)
14
+
15
+ phi(omega_i) =
16
+ 1/sqrt(2π∆F) if |omega_i| < A
17
+ 1/sqrt(2π∆F) cos(nu_d π/2 * |omega|-A / B) if A < |omega_i| < A + B
18
+
19
+ Where nu_d = normalized incomplete beta function
20
+
21
+
22
+
23
+ Parameters
24
+ ----------
25
+ ω : np.ndarray
26
+ Array of angular frequencies
27
+ Nf : int
28
+ Number of frequency bins
29
+ d : float, optional
30
+ Number of standard deviations for the gaussian wavelet, by default 4.
31
+
32
+ Returns
33
+ -------
34
+ np.ndarray
35
+ Array of phi_tilde(omega_i) values
36
+
37
+ """
38
+ dF = 1.0 / (2 * Nf) # NOTE: missing 1/dt?
39
+ dOmega = 2 * PI * dF # Near Eq 10 # 2 pi times DF
40
+ inverse_sqrt_dOmega = 1.0 / np.sqrt(dOmega)
41
+
42
+ A = dOmega / 4
43
+ B = dOmega - 2 * A # Cannot have B \leq 0.
44
+ if B <= 0:
45
+ raise ValueError("B must be greater than 0")
46
+
47
+ phi = np.zeros(omega.size)
48
+ mask = (A <= np.abs(omega)) & (np.abs(omega) < A + B) # Minor changes
49
+ vd = (PI / 2.0) * __nu_d(omega[mask], A, B, d=d) # different from paper
50
+ phi[mask] = inverse_sqrt_dOmega * np.cos(vd)
51
+ phi[np.abs(omega) < A] = inverse_sqrt_dOmega
52
+ return phi
53
+
54
+
55
+ def __nu_d(
56
+ omega: np.ndarray, A: float, B: float, d: float = 4.0
57
+ ) -> np.ndarray:
58
+ """Compute the normalized incomplete beta function.
59
+
60
+ Parameters
61
+ ----------
62
+ ω : np.ndarray
63
+ Array of angular frequencies
64
+ A : float
65
+ Lower bound for the beta function
66
+ B : float
67
+ Upper bound for the beta function
68
+ d : float, optional
69
+ Number of standard deviations for the gaussian wavelet, by default 4.
70
+
71
+ Returns
72
+ -------
73
+ np.ndarray
74
+ Array of ν_d values
75
+
76
+ scipy.special.betainc
77
+ https://docs.scipy.org/doc/scipy-1.7.1/reference/reference/generated/scipy.special.betainc.html
78
+
79
+ """
80
+ x = (np.abs(omega) - A) / B
81
+ return betainc(d, d, x) / betainc(d, d, 1)
82
+
83
+
84
+ def phitilde_vec_norm(Nf: int, Nt: int, dt: float, d: float) -> np.ndarray:
85
+ """Normalize phitilde for inverse frequency domain transform."""
86
+
87
+ # Calculate the frequency values
88
+ ND = Nf * Nt
89
+ omegas = 2 * np.pi / ND * np.arange(0, Nt // 2 + 1)
90
+
91
+ # Calculate the unnormalized phitilde (u_phit)
92
+ u_phit = phitilde_vec(omegas, Nf, dt, d)
93
+
94
+ # Normalize the phitilde
95
+ normalising_factor = np.pi ** (-1 / 2) # Ollie's normalising factor
96
+
97
+ # Notes: this is the overall normalising factor that is different from Cornish's paper
98
+ # It is the only way I can force this code to be consistent with our work in the
99
+ # frequency domain. First note that
100
+
101
+ # old normalising factor -- This factor is absolutely ridiculous. Why!?
102
+ # Matt_normalising_factor = np.sqrt(
103
+ # (2 * np.sum(u_phit[1:] ** 2) + u_phit[0] ** 2) * 2 * PI / ND
104
+ # )
105
+ # Matt_normalising_factor /= PI**(3/2)/PI
106
+
107
+ # The expression above is equal to np.pi**(-1/2) after working through the maths.
108
+ # I have pulled (2/Nf) from __init__.py (from freq to wavelet) into the normalsiing
109
+ # factor here. I thnk it's cleaner to have ONE normalising constant. Avoids confusion
110
+ # and it is much easier to track.
111
+
112
+ # TODO: understand the following:
113
+ # (2 * np.sum(u_phit[1:] ** 2) + u_phit[0] ** 2) = 0.5 * Nt / dOmega
114
+ # Matt_normalising_factor is equal to 1/sqrt(pi)... why is this computed?
115
+ # in such a stupid way?
116
+
117
+ return u_phit / (normalising_factor)
118
+
119
+
120
+ def phi_vec(Nf: int, dt, d: float = 4.0, q: int = 16) -> np.ndarray:
121
+ """get time domain phi as fourier transform of phitilde_vec"""
122
+ insDOM = 1.0 / np.sqrt(PI / Nf)
123
+ K = q * 2 * Nf
124
+ half_K = q * Nf # np.int64(K/2)
125
+
126
+ dom = 2 * PI / K # max frequency is K/2*dom = pi/dt = OM
127
+
128
+ DX = np.zeros(K, dtype=np.complex128)
129
+
130
+ # zero frequency
131
+ DX[0] = insDOM
132
+
133
+ DX = DX.copy()
134
+ # postive frequencies
135
+ DX[1 : half_K + 1] = phitilde_vec(
136
+ dom * np.arange(1, half_K + 1), Nf, dt, d
137
+ )
138
+ # negative frequencies
139
+ DX[half_K + 1 :] = phitilde_vec(
140
+ -dom * np.arange(half_K - 1, 0, -1), Nf, dt, d
141
+ )
142
+ DX = K * fft.ifft(DX, K)
143
+
144
+ phi = np.zeros(K)
145
+ phi[0:half_K] = np.real(DX[half_K:K])
146
+ phi[half_K:] = np.real(DX[0:half_K])
147
+
148
+ nrm = np.sqrt(K / dom) # *np.linalg.norm(phi)
149
+
150
+ fac = np.sqrt(2.0) / nrm
151
+ phi *= fac
152
+ return phi
@@ -0,0 +1,4 @@
1
+ from .frequencyseries import FrequencySeries
2
+ from .timeseries import TimeSeries
3
+ from .wavelet import Wavelet
4
+ from .wavelet_mask import WaveletMask
@@ -0,0 +1,53 @@
1
+ from typing import Literal, Tuple
2
+
3
+ import numpy as xp
4
+ from numpy.fft import irfft, fft, rfft, rfftfreq
5
+
6
+ from ...logger import logger
7
+
8
+
9
+ def _len_check(d):
10
+ if not xp.log2(len(d)).is_integer():
11
+ logger.warning(f"Data length {len(d)} is suggested to be a power of 2")
12
+
13
+
14
+ def is_documented_by(original):
15
+ def wrapper(target):
16
+ target.__doc__ = original.__doc__
17
+ return target
18
+
19
+ return wrapper
20
+
21
+
22
+ def fmt_time(seconds: float, units=False) -> Tuple[str, str]:
23
+ """Returns formatted time and units [ms, s, min, hr, day]"""
24
+ t, u = "", ""
25
+ if seconds < 1e-3:
26
+ t, u = f"{seconds * 1e6:.2f}", "µs"
27
+ elif seconds < 1:
28
+ t, u = f"{seconds * 1e3:.2f}", "ms"
29
+ elif seconds < 60:
30
+ t, u = f"{seconds:.2f}", "s"
31
+ elif seconds < 60 * 60:
32
+ t, u = f"{seconds / 60:.2f}", "min"
33
+ elif seconds < 60 * 60 * 24:
34
+ t, u = f"{seconds / 3600:.2f}", "hr"
35
+ else:
36
+ t, u = f"{seconds / 86400:.2f}", "day"
37
+
38
+ if units:
39
+ return t, u
40
+ return t
41
+
42
+
43
+ def fmt_timerange(trange):
44
+ t0 = fmt_time(trange[0])
45
+ tend, units = fmt_time(trange[1], units = True)
46
+ return f"[{t0}, {tend}] {units}"
47
+
48
+
49
+ def fmt_pow2(n:float)->str:
50
+ pow2 = xp.log2(n)
51
+ if pow2.is_integer():
52
+ return f"2^{int(pow2)}"
53
+ return f"{n:,}"
@@ -0,0 +1,237 @@
1
+ import matplotlib.pyplot as plt
2
+ from typing import Tuple, Union, Optional
3
+
4
+ from .common import is_documented_by, xp, irfft, fmt_time, fmt_pow2
5
+ from .plotting import plot_freqseries, plot_periodogram
6
+
7
+ __all__ = ["FrequencySeries"]
8
+
9
+ class FrequencySeries:
10
+ """
11
+ A class to represent a one-sided frequency series, with various methods for
12
+ plotting and converting the series to a time-domain representation.
13
+
14
+ Attributes
15
+ ----------
16
+ data : xp.ndarray
17
+ Frequency domain data.
18
+ freq : xp.ndarray
19
+ Corresponding frequencies (must be non-negative).
20
+ """
21
+
22
+ def __init__(self, data: xp.ndarray, freq: xp.ndarray, t0: float = 0):
23
+ """
24
+ Initialize the FrequencySeries with data and frequencies.
25
+
26
+ Parameters
27
+ ----------
28
+ data : xp.ndarray
29
+ Frequency domain data.
30
+ freq : xp.ndarray
31
+ Array of frequencies. Must be non-negative.
32
+ t0 : float, optional
33
+ Initial time of the time domain signal (default is 0).
34
+ (This is not used in this class, but is included for compatibility with TimeSeries.)
35
+
36
+ Raises
37
+ ------
38
+ ValueError
39
+ If any frequency is negative or if `data` and `freq` do not have the same length.
40
+ """
41
+ if xp.any(freq < 0):
42
+ raise ValueError("FrequencySeries must be one-sided (only non-negative frequencies)")
43
+ if len(data) != len(freq):
44
+ raise ValueError(f"data and freq must have the same length ({len(data)} != {len(freq)})")
45
+ self.data = data
46
+ self.freq = freq
47
+ self.t0 = t0
48
+
49
+ @is_documented_by(plot_freqseries)
50
+ def plot(self, ax=None, **kwargs) -> Tuple[plt.Figure, plt.Axes]:
51
+ return plot_freqseries(
52
+ self.data, self.freq, self.nyquist_frequency, ax=ax, **kwargs
53
+ )
54
+
55
+ @is_documented_by(plot_periodogram)
56
+ def plot_periodogram(self, ax=None, **kwargs) -> Tuple[plt.Figure, plt.Axes]:
57
+ return plot_periodogram(
58
+ self.data, self.freq, self.fs, ax=ax, **kwargs
59
+ )
60
+
61
+ def __len__(self):
62
+ """Return the length of the frequency series."""
63
+ return len(self.data)
64
+
65
+ def __getitem__(self, item):
66
+ """Return the data point at the specified index."""
67
+ return self.data[item]
68
+
69
+ @property
70
+ def df(self) -> float:
71
+ """Return the frequency resolution (Δf)."""
72
+ return float(self.freq[1] - self.freq[0])
73
+
74
+ @property
75
+ def dt(self) -> float:
76
+ """Return the time resolution (Δt)."""
77
+ return 1 / self.fs
78
+
79
+ @property
80
+ def sample_rate(self) -> float:
81
+ """Return the sample rate (fs)."""
82
+ return 2 * float(self.freq[-1])
83
+
84
+ @property
85
+ def fs(self) -> float:
86
+ """Return the sample rate (fs)."""
87
+ return self.sample_rate
88
+
89
+ @property
90
+ def nyquist_frequency(self) -> float:
91
+ """Return the Nyquist frequency (fs/2)."""
92
+ return self.sample_rate / 2
93
+
94
+ @property
95
+ def duration(self) -> float:
96
+ """Return the duration of the time domain signal."""
97
+ return (2 * (len(self) - 1)) / self.fs
98
+
99
+ @property
100
+ def minimum_frequency(self) -> float:
101
+ """Return the minimum frequency in the frequency series."""
102
+ return float(xp.abs(self.freq).min())
103
+
104
+ @property
105
+ def maximum_frequency(self) -> float:
106
+ """Return the maximum frequency in the frequency series."""
107
+ return float(xp.abs(self.freq).max())
108
+
109
+ @property
110
+ def range(self) -> Tuple[float, float]:
111
+ """Return the frequency range (minimum and maximum frequencies)."""
112
+ return (self.minimum_frequency, self.maximum_frequency)
113
+
114
+ @property
115
+ def shape(self) -> Tuple[int, ...]:
116
+ """Return the shape of the data array."""
117
+ return self.data.shape
118
+
119
+ @property
120
+ def ND(self) -> int:
121
+ """Return the number of data points in the time domain signal."""
122
+ return 2 * (len(self) - 1)
123
+
124
+ def __repr__(self) -> str:
125
+ """Return a string representation of the FrequencySeries."""
126
+ dur = fmt_time(self.duration)
127
+ n = fmt_pow2(len(self))
128
+ return f"FrequencySeries(n={n}, frange=[{self.range[0]:.2f}, {self.range[1]:.2f}] Hz, T={dur}, fs={self.fs:.2f} Hz)"
129
+
130
+ def noise_weighted_inner_product(self, other: "FrequencySeries", psd:"FrequencySeries") -> float:
131
+ """
132
+ Compute the noise-weighted inner product of two FrequencySeries.
133
+
134
+ Parameters
135
+ ----------
136
+ other : FrequencySeries
137
+ The other FrequencySeries.
138
+ psd : FrequencySeries
139
+ The power spectral density (PSD) of the noise.
140
+
141
+ Returns
142
+ -------
143
+ float
144
+ The noise-weighted inner product of the two FrequencySeries.
145
+ """
146
+ integrand = xp.real(xp.conj(self.data) * other.data / psd.data)
147
+ return (4 * self.dt/self.ND) * xp.nansum(integrand)
148
+
149
+ def matched_filter_snr(self, other: "FrequencySeries", psd: "FrequencySeries") -> float:
150
+ """
151
+ Compute the signal-to-noise ratio (SNR) of a matched filter.
152
+
153
+ Parameters
154
+ ----------
155
+ other : FrequencySeries
156
+ The other FrequencySeries.
157
+ psd : FrequencySeries
158
+ The power spectral density (PSD) of the noise.
159
+
160
+ Returns
161
+ -------
162
+ float
163
+ The SNR of the matched filter.
164
+ """
165
+ return xp.sqrt(self.noise_weighted_inner_product(other, psd))
166
+
167
+ def optimal_snr(self, psd: "FrequencySeries") -> float:
168
+ """
169
+ Compute the optimal signal-to-noise ratio (SNR) of a FrequencySeries.
170
+
171
+ Parameters
172
+ ----------
173
+ psd : FrequencySeries
174
+ The power spectral density (PSD) of the noise.
175
+
176
+ Returns
177
+ -------
178
+ float
179
+ The optimal SNR of the FrequencySeries.
180
+ """
181
+ return xp.sqrt(self.noise_weighted_inner_product(self, psd))
182
+
183
+ def to_timeseries(self) -> "TimeSeries":
184
+ """
185
+ Convert the frequency series to a time series using inverse Fourier transform.
186
+
187
+ Returns
188
+ -------
189
+ TimeSeries
190
+ The corresponding time domain signal.
191
+ """
192
+ # Perform the inverse FFT
193
+ time_data = irfft(self.data, n=2 * (len(self) - 1))
194
+
195
+ # Calculate the time array
196
+ dt = 1 / (2 * self.nyquist_frequency)
197
+ time = xp.arange(len(time_data)) * dt
198
+ time += self.t0
199
+
200
+ # Create and return a TimeSeries object
201
+ from .timeseries import TimeSeries
202
+ return TimeSeries(time_data, time)
203
+
204
+
205
+ def to_wavelet(
206
+ self,
207
+ Nf: Union[int, None] = None,
208
+ Nt: Union[int, None] = None,
209
+ nx: Optional[float] = 4.0,
210
+ )->"Wavelet":
211
+ """
212
+ Convert the frequency series to a wavelet using inverse Fourier transform.
213
+
214
+ Returns
215
+ -------
216
+ Wavelet
217
+ The corresponding wavelet.
218
+ """
219
+ from ..forward import from_freq_to_wavelet
220
+ return from_freq_to_wavelet(self, Nf=Nf, Nt=Nt, nx=nx)
221
+
222
+
223
+ def __eq__(self, other):
224
+ """Check if two FrequencySeries objects are equal."""
225
+ data_same = xp.allclose(self.data, other.data)
226
+ freq_same = xp.allclose(self.freq, other.freq)
227
+ return data_same and freq_same
228
+
229
+ def __copy__(self):
230
+ return FrequencySeries(
231
+ xp.copy(self.data),
232
+ xp.copy(self.freq),
233
+ t0=self.t0
234
+ )
235
+
236
+ def copy(self):
237
+ return self.__copy__()