pywavelet 0.0.1b0__py3-none-any.whl → 0.1.0__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.
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__()