SignalForge 0.0.2__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.
- SignalForge/__init__.py +6 -0
- SignalForge/nonstationary_nongaussian.py +522 -0
- SignalForge/single_chan_signal.py +1083 -0
- SignalForge/stationary_gaussian.py +110 -0
- SignalForge/stationary_nongaussian.py +934 -0
- SignalForge/utils.py +526 -0
- signalforge-0.0.2.dist-info/METADATA +23 -0
- signalforge-0.0.2.dist-info/RECORD +11 -0
- signalforge-0.0.2.dist-info/WHEEL +5 -0
- signalforge-0.0.2.dist-info/licenses/LICENSE +21 -0
- signalforge-0.0.2.dist-info/top_level.txt +1 -0
SignalForge/__init__.py
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy as sp
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
|
|
6
|
+
from .utils import *
|
|
7
|
+
from .single_chan_signal import SingleChanSignal
|
|
8
|
+
|
|
9
|
+
'''
|
|
10
|
+
supporting functions / 'static methods' (no need of self object)
|
|
11
|
+
- get_beta_amplitude_modulation: adds amplitude modulated non-stationarity to gaussian signal via beta distribution
|
|
12
|
+
- get_rayleigh_amplitude_modulation: adds amplitude modulated non-stationarity to gaussian signal via rayleigh distribution
|
|
13
|
+
- get_trapp_amplitude_modulation: adds amplitude modulated non-stationarity to gaussian signal via gaussian distribution
|
|
14
|
+
- get_frequency_modulation: designes frequency modulate non-stationary process from given carrier function and starting psd
|
|
15
|
+
'''
|
|
16
|
+
|
|
17
|
+
def _add_am_carrier(noise:np.ndarray, carrier:np.ndarray):
|
|
18
|
+
"""
|
|
19
|
+
private function for adding amplitude modulation carrier to gaussian noise standardizing rms to the noise
|
|
20
|
+
"""
|
|
21
|
+
nonstat_sign = noise*carrier
|
|
22
|
+
rms_correction = np.std(noise) / np.std(nonstat_sign)
|
|
23
|
+
nonstat_sign = nonstat_sign * rms_correction
|
|
24
|
+
return nonstat_sign
|
|
25
|
+
|
|
26
|
+
def _oneband_am_opt_fun(
|
|
27
|
+
params:list,
|
|
28
|
+
gauss_signal:np.ndarray,
|
|
29
|
+
time_vector:np.ndarray,
|
|
30
|
+
carrier_fun:np.ndarray,
|
|
31
|
+
input_kurtosis:float,
|
|
32
|
+
seed:int
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
optimization function for oneband ampiltude modulated non stationary signals
|
|
36
|
+
"""
|
|
37
|
+
carrier = carrier_fun(*params, time_vector, seed)
|
|
38
|
+
nonstat_sign = _add_am_carrier(gauss_signal, carrier)
|
|
39
|
+
transformed_kurtosis = sp.stats.kurtosis(nonstat_sign) + 3
|
|
40
|
+
return np.abs(transformed_kurtosis - input_kurtosis)
|
|
41
|
+
|
|
42
|
+
def _get_banded_am_opt(
|
|
43
|
+
gauss_signal:np.ndarray,
|
|
44
|
+
T:float,
|
|
45
|
+
input_kurtosis:float,
|
|
46
|
+
flims:list,
|
|
47
|
+
amplitude_modulation_fun:np.ndarray,
|
|
48
|
+
seed:int,
|
|
49
|
+
opt_settings:dict
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
optimization function for multiband ampiltude modulated non stationary signals
|
|
53
|
+
"""
|
|
54
|
+
time_vector = np.linspace(0,1,len(gauss_signal))*T
|
|
55
|
+
fs = T/len(gauss_signal)
|
|
56
|
+
Nfw = len(input_kurtosis)
|
|
57
|
+
flims = np.linspace(flims[0],flims[1],Nfw+1)
|
|
58
|
+
fft_os = np.fft.rfft(gauss_signal)/fs
|
|
59
|
+
fvec = np.fft.rfftfreq(len(gauss_signal), fs)
|
|
60
|
+
nonstat_sign = np.zeros(len(gauss_signal))
|
|
61
|
+
carrier = np.zeros((Nfw,len(gauss_signal)))
|
|
62
|
+
for i in tqdm(range(Nfw)):
|
|
63
|
+
Xos_win = np.zeros(len(fft_os), dtype=np.complex_)
|
|
64
|
+
fwindow = (fvec >= flims[i]) & (fvec < flims[i+1])
|
|
65
|
+
Xos_win[fwindow] = fft_os[fwindow]
|
|
66
|
+
banded_signal = np.fft.irfft(Xos_win*fs)
|
|
67
|
+
optim_res = sp.optimize.minimize(_oneband_am_opt_fun, args = (banded_signal, time_vector, amplitude_modulation_fun, input_kurtosis[i], seed),**opt_settings)
|
|
68
|
+
carrier[i,:] = amplitude_modulation_fun(*optim_res.x, time_vector, seed)
|
|
69
|
+
nonstat_sign += _add_am_carrier(banded_signal, carrier[i,:])
|
|
70
|
+
return nonstat_sign, carrier
|
|
71
|
+
|
|
72
|
+
def get_beta_amplitude_modulation(
|
|
73
|
+
gauss_signal: np.ndarray,
|
|
74
|
+
T: float,
|
|
75
|
+
input_kurtosis: float,
|
|
76
|
+
flims: list = None,
|
|
77
|
+
seed : int = None
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Apply beta-distributed amplitude modulation to a Gaussian signal.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
gauss_signal : array-like
|
|
85
|
+
Input Gaussian signal.
|
|
86
|
+
T : float
|
|
87
|
+
Signal duration in seconds.
|
|
88
|
+
input_kurtosis : float or array-like
|
|
89
|
+
Desired kurtosis of the output signal.
|
|
90
|
+
flims : tuple, optional
|
|
91
|
+
Frequency limits for banded modulation.
|
|
92
|
+
seed : int, optional
|
|
93
|
+
Seed for random number generation.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
nonstat_signal : np.ndarray
|
|
98
|
+
Non stationary resulting signal
|
|
99
|
+
carrier : dict
|
|
100
|
+
Dictionary containing "name" of carrier and "carrier" time history
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
Source
|
|
104
|
+
------
|
|
105
|
+
D. Smallwood, «Vibration with Non-Gaussian Noise», J. IEST, vol. 52, fasc. 2, pp. 13–30, ott. 2009, doi: 10.17764/jiet.52.2.gh0444564n8765k1.
|
|
106
|
+
"""
|
|
107
|
+
def get_beta_carrier(alpha, Ntw, time_vector, seed = None):
|
|
108
|
+
"""
|
|
109
|
+
Generate a beta-distributed amplitude modulation carrier.
|
|
110
|
+
|
|
111
|
+
Parameters:
|
|
112
|
+
alpha : float
|
|
113
|
+
Shape parameter for the beta distribution.
|
|
114
|
+
Ntw : int
|
|
115
|
+
Number of time windows.
|
|
116
|
+
time_vector : array-like
|
|
117
|
+
Time axis for the signal.
|
|
118
|
+
seed : int, optional
|
|
119
|
+
Seed for reproducibility.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
carrier : array-like
|
|
123
|
+
Normalized amplitude modulation carrier.
|
|
124
|
+
"""
|
|
125
|
+
beta = alpha
|
|
126
|
+
t_mod = np.linspace(0,max(time_vector), round(Ntw))
|
|
127
|
+
if seed: np.random.seed(seed=seed)
|
|
128
|
+
beta_points = np.random.beta(alpha, beta, round(Ntw))
|
|
129
|
+
beta_points = 2*beta_points
|
|
130
|
+
beta_points[[-1,0]] = 0
|
|
131
|
+
np.random.seed(seed=None)
|
|
132
|
+
spline_interpolator = sp.interpolate.CubicSpline(t_mod, beta_points)
|
|
133
|
+
beta_carrier = spline_interpolator(time_vector)/np.max(beta_points)
|
|
134
|
+
return beta_carrier
|
|
135
|
+
if np.isscalar(input_kurtosis):
|
|
136
|
+
time_vector = np.linspace(0,1,len(gauss_signal))*T
|
|
137
|
+
optim_res = sp.optimize.minimize(
|
|
138
|
+
_oneband_am_opt_fun,
|
|
139
|
+
args = (gauss_signal, time_vector, get_beta_carrier, input_kurtosis, seed),
|
|
140
|
+
x0=[1,40],
|
|
141
|
+
bounds=[(0.1,5), (30,len(gauss_signal)//2)]
|
|
142
|
+
)
|
|
143
|
+
carrier = get_beta_carrier(*optim_res.x, time_vector, seed)
|
|
144
|
+
nonstat_sign = _add_am_carrier(gauss_signal, carrier)
|
|
145
|
+
carrier = [carrier]
|
|
146
|
+
else:
|
|
147
|
+
opt_settings = {'x0':[1,40], 'bounds':[(0.5,5), (30,len(gauss_signal)//2)]}
|
|
148
|
+
nonstat_sign, carrier = _get_banded_am_opt(
|
|
149
|
+
gauss_signal,
|
|
150
|
+
T,
|
|
151
|
+
input_kurtosis,
|
|
152
|
+
flims,
|
|
153
|
+
get_beta_carrier,
|
|
154
|
+
seed,
|
|
155
|
+
opt_settings)
|
|
156
|
+
return nonstat_sign, {'name': 'Beta amplitude modulation [-]', 'carrier': carrier}
|
|
157
|
+
|
|
158
|
+
def get_rayleigh_amplitude_modulation(
|
|
159
|
+
gauss_signal: np.ndarray,
|
|
160
|
+
T: float,
|
|
161
|
+
input_kurtosis: float,
|
|
162
|
+
flims: list = None,
|
|
163
|
+
seed: int = None
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Apply Rayleigh-distributed amplitude modulation to a Gaussian signal.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
gauss_signal : array-like
|
|
171
|
+
Input Gaussian signal.
|
|
172
|
+
T : float
|
|
173
|
+
Signal duration in seconds.
|
|
174
|
+
input_kurtosis : float or array-like
|
|
175
|
+
Desired kurtosis of the output signal.
|
|
176
|
+
flims : tuple, optional
|
|
177
|
+
Frequency limits for banded modulation.
|
|
178
|
+
seed : int, optional
|
|
179
|
+
Seed for random number generation.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
nonstat_signal : np.ndarray
|
|
184
|
+
Non stationary resulting signal
|
|
185
|
+
carrier : dict
|
|
186
|
+
Dictionary containing "name" of carrier and "carrier" time history
|
|
187
|
+
|
|
188
|
+
Source
|
|
189
|
+
------
|
|
190
|
+
D. Smallwood, «Vibration with Non-Gaussian Noise», J. IEST, vol. 52, fasc. 2, pp. 13–30, ott. 2009, doi: 10.17764/jiet.52.2.gh0444564n8765k1.
|
|
191
|
+
"""
|
|
192
|
+
def get_ray_carrier(sigma, Ntw, time_vector, seed = None):
|
|
193
|
+
"""
|
|
194
|
+
Generate a Rayleigh-distributed amplitude modulation carrier.
|
|
195
|
+
|
|
196
|
+
Parameters:
|
|
197
|
+
sigma : float
|
|
198
|
+
Scale parameter of the Rayleigh distribution.
|
|
199
|
+
Ntw : int
|
|
200
|
+
Number of time windows.
|
|
201
|
+
time_vector : array-like
|
|
202
|
+
Time axis for the signal.
|
|
203
|
+
seed : int, optional
|
|
204
|
+
Seed for reproducibility.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
carrier : array-like
|
|
208
|
+
Normalized amplitude modulation carrier.
|
|
209
|
+
"""
|
|
210
|
+
t_mod = np.linspace(0,max(time_vector), round(Ntw))
|
|
211
|
+
if seed: np.random.seed(seed=seed)
|
|
212
|
+
ray_points = np.random.rayleigh(sigma, round(Ntw))
|
|
213
|
+
ray_points[[-1,0]] = 0
|
|
214
|
+
np.random.seed(seed=None)
|
|
215
|
+
spline_interpolator = sp.interpolate.CubicSpline(t_mod, ray_points)
|
|
216
|
+
ray_carrier = spline_interpolator(time_vector)/np.max(ray_points)
|
|
217
|
+
return ray_carrier
|
|
218
|
+
|
|
219
|
+
if np.isscalar(input_kurtosis):
|
|
220
|
+
time_vector = np.linspace(0,1,len(gauss_signal))*T
|
|
221
|
+
optim_res = sp.optimize.minimize(
|
|
222
|
+
_oneband_am_opt_fun,
|
|
223
|
+
args = (gauss_signal, time_vector, get_ray_carrier, input_kurtosis, seed),
|
|
224
|
+
x0=[2,40],
|
|
225
|
+
bounds=[(0.1,10), (30,len(gauss_signal)//2)]
|
|
226
|
+
)
|
|
227
|
+
carrier = get_ray_carrier(*optim_res.x, time_vector, seed)
|
|
228
|
+
nonstat_sign = _add_am_carrier(gauss_signal, carrier)
|
|
229
|
+
carrier = [carrier]
|
|
230
|
+
else:
|
|
231
|
+
if flims is None: raise KeyError('Parameter "flims" must be defined to get a specified spectral kurtosis')
|
|
232
|
+
opt_settings = {'x0':[2,40], 'bounds':[(0.1,10), (30,len(gauss_signal)//2)]}
|
|
233
|
+
nonstat_sign, carrier = _get_banded_am_opt(gauss_signal, T, input_kurtosis, flims, get_ray_carrier, seed, opt_settings)
|
|
234
|
+
return nonstat_sign, {'name': 'Rayleigh amplitude modulation [-]', 'carrier': carrier}
|
|
235
|
+
|
|
236
|
+
def get_trapp_amplitude_modulation(
|
|
237
|
+
gauss_signal: np.ndarray,
|
|
238
|
+
T: float,
|
|
239
|
+
input_kurtosis:float,
|
|
240
|
+
flims:list = None,
|
|
241
|
+
seed : int = None
|
|
242
|
+
): # TODO: ADD MULTIBAND MODULATION
|
|
243
|
+
"""
|
|
244
|
+
Apply Gaussian-envelope-based amplitude modulation using a transformed power-law Rayleigh carrier.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
gauss_signal : array-like
|
|
249
|
+
Input Gaussian signal.
|
|
250
|
+
T : float
|
|
251
|
+
Duration in seconds.
|
|
252
|
+
input_kurtosis : float
|
|
253
|
+
Desired signal kurtosis.
|
|
254
|
+
flims : tuple, optional
|
|
255
|
+
Placeholder for banded modulation (not yet implemented).
|
|
256
|
+
seed : int, optional
|
|
257
|
+
Random seed for reproducibility.
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
nonstat_signal : np.ndarray
|
|
262
|
+
Non stationary resulting signal
|
|
263
|
+
carrier : dict
|
|
264
|
+
Dictionary containing "name" of carrier and "carrier" time history
|
|
265
|
+
Source
|
|
266
|
+
------
|
|
267
|
+
A. Trapp, M. J. Makua, e P. Wolfsteiner, «Fatigue assessment of amplitude-modulated non-stationary random vibration loading», Procedia Struct. Integr., vol. 17, pp. 379–386, 2019, doi: 10.1016/j.prostr.2019.08.050.
|
|
268
|
+
"""
|
|
269
|
+
def get_trapp_carrier(p, delta_m, gaussian_carier):
|
|
270
|
+
"""Generate amplitude modulation carrier based on power-law transformation."""
|
|
271
|
+
ray_carrier = np.abs(gaussian_carier)**p+delta_m
|
|
272
|
+
return ray_carrier
|
|
273
|
+
|
|
274
|
+
def trapp_am_opt_fun(params, gauss_carrier, input_kurtosis):
|
|
275
|
+
"""Optimization objective: match output signal kurtosis and reduce skew."""
|
|
276
|
+
carrier = get_trapp_carrier(*params, gaussian_carier=gauss_carrier)
|
|
277
|
+
nonstat_sign = _add_am_carrier(gauss_signal, carrier)
|
|
278
|
+
transformed_kurtosis = sp.stats.kurtosis(nonstat_sign) + 3
|
|
279
|
+
transformed_skew = sp.stats.skew(nonstat_sign)
|
|
280
|
+
return np.abs(transformed_kurtosis - input_kurtosis)+np.abs(transformed_skew)
|
|
281
|
+
|
|
282
|
+
if flims:
|
|
283
|
+
raise UserWarning('Multiband amplitude modulation not implemented yet for this method.')
|
|
284
|
+
if seed: np.random.seed(seed=seed)
|
|
285
|
+
fs = len(gauss_signal)/T
|
|
286
|
+
fpsd = np.array([0,.2,.3,.4,.5,fs/2])
|
|
287
|
+
psd = np.array([0,0,1,1,0,0])*2/(fpsd[4]+fpsd[3]-fpsd[2]-fpsd[1])
|
|
288
|
+
_,gauss_carrier = get_stationary_gaussian(fpsd, psd, T, seed = None)
|
|
289
|
+
gauss_carrier = sp.signal.windows.tukey(len(gauss_carrier),alpha = 0.01)*gauss_carrier
|
|
290
|
+
|
|
291
|
+
if np.isscalar(input_kurtosis):
|
|
292
|
+
optim_res = sp.optimize.minimize(
|
|
293
|
+
trapp_am_opt_fun,
|
|
294
|
+
args = (gauss_carrier, input_kurtosis),
|
|
295
|
+
x0=[2,0],
|
|
296
|
+
bounds=[(0.1,100), (0,10)]
|
|
297
|
+
)
|
|
298
|
+
carrier = get_trapp_carrier(*optim_res.x, gaussian_carier = gauss_carrier)
|
|
299
|
+
nonstat_sign = _add_am_carrier(gauss_signal, carrier)
|
|
300
|
+
carrier = [carrier]
|
|
301
|
+
# else:
|
|
302
|
+
# if flims is None: raise KeyError('Parameter "flims" must be defined to get a specified spectral kurtosis')
|
|
303
|
+
# opt_settings = {'x0':[2,0], 'bounds':[(0.1,100), (0,10)]}
|
|
304
|
+
# nonstat_sign = _get_banded_am_opt(gauss_signal, T, input_kurtosis, flims, get_trapp_carrier, seed, opt_settings)
|
|
305
|
+
|
|
306
|
+
return nonstat_sign, {'name': 'Gaussian amplitude modulation [-]', 'carrier': carrier}
|
|
307
|
+
|
|
308
|
+
def get_frequency_modulation(
|
|
309
|
+
Sx:np.ndarray,
|
|
310
|
+
SFT: sp.signal.ShortTimeFFT,
|
|
311
|
+
modulation_function:np.ndarray
|
|
312
|
+
):
|
|
313
|
+
"""
|
|
314
|
+
Apply frequency shift modulation to a spectrogram Sx using a given modulation function.
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
---------
|
|
318
|
+
Sx : np.ndarray
|
|
319
|
+
Input spectrogram (complex-valued).
|
|
320
|
+
SFT : sp.signal.ShortTimeFFT
|
|
321
|
+
Object with STFT/ISTFT functionality and attributes `delta_t`, `f`.
|
|
322
|
+
modulation_function : array-like
|
|
323
|
+
Frequency shift values over time.
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
nonstat_signal : np.ndarray
|
|
328
|
+
Non stationary resulting signal
|
|
329
|
+
carrier : dict
|
|
330
|
+
Dictionary containing "name" of carrier and "carrier" time history
|
|
331
|
+
Source
|
|
332
|
+
------
|
|
333
|
+
M. Clerc e S. Mallat, «Estimating deformations of stationary processes», Ann. Stat., vol. 31, fasc. 6, dic. 2003, doi: 10.1214/aos/1074290327.
|
|
334
|
+
"""
|
|
335
|
+
if not isinstance(Sx,np.ndarray): Sx = np.asarray(Sx)
|
|
336
|
+
base_fft_window = np.pad(np.mean(np.abs(Sx), axis=1),(np.size(Sx,0), np.size(Sx,0)))
|
|
337
|
+
time_bins = np.size(Sx,axis = 1)
|
|
338
|
+
T = (time_bins-1)*SFT.delta_t
|
|
339
|
+
original_times = np.linspace(0,T, len(modulation_function))
|
|
340
|
+
sft_times = np.arange(0,time_bins) * SFT.delta_t
|
|
341
|
+
df_sft = np.mean(np.diff(SFT.f))
|
|
342
|
+
sft_modulation = np.interp(sft_times, original_times, modulation_function)/df_sft
|
|
343
|
+
Sx_angle = np.angle(Sx)
|
|
344
|
+
Sx_angle =np.random.uniform(-np.pi, np.pi, (np.size(Sx,0), np.size(Sx,1)))
|
|
345
|
+
Sx_mag_modulated = np.zeros((np.size(Sx,0), np.size(Sx,1)))
|
|
346
|
+
for i, nf in enumerate(sft_modulation):
|
|
347
|
+
Sx_mag_modulated[:,i] = np.roll(base_fft_window, round(nf))[np.size(Sx,0):2*np.size(Sx,0)]
|
|
348
|
+
Sx_modulated = Sx_mag_modulated*np.exp(1j * Sx_angle)
|
|
349
|
+
nonstat_sign = SFT.istft(Sx_modulated)
|
|
350
|
+
carrier = np.interp(
|
|
351
|
+
np.linspace(0,1,len(nonstat_sign)),
|
|
352
|
+
np.linspace(0,1,len(modulation_function)),
|
|
353
|
+
modulation_function)
|
|
354
|
+
return nonstat_sign, {'name': 'Frequency shift [Hz]', 'carrier': [carrier]}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class NonStationaryNonGaussian(SingleChanSignal):
|
|
358
|
+
"""
|
|
359
|
+
Generate a non-stationary, non-Gaussian signal based on a given PSD.
|
|
360
|
+
NonStationaryNonGaussian is a child class of SingleChanSignal.
|
|
361
|
+
|
|
362
|
+
Parameters:
|
|
363
|
+
fpsd : np.ndarray
|
|
364
|
+
Frequency vector for the input PSD.
|
|
365
|
+
psd : np.ndarray
|
|
366
|
+
Power spectral density values.
|
|
367
|
+
T : float
|
|
368
|
+
Total duration of the signal [s].
|
|
369
|
+
fs : float, optional
|
|
370
|
+
Wanted sampling frequency. If None -> 2*fpsd[-1]
|
|
371
|
+
dfpsd : float, optional
|
|
372
|
+
Frequency resolution [Hz]. Default is 0.5.
|
|
373
|
+
method : str, optional
|
|
374
|
+
Modulation method: 'beta_am', 'ray_am', 'trapp_am', or 'fm'. Default is 'beta_am'.
|
|
375
|
+
params : dict, optional
|
|
376
|
+
Dictionary of parameters for the modulation method.
|
|
377
|
+
name : str, optional
|
|
378
|
+
Name of the signal.
|
|
379
|
+
var str, optional
|
|
380
|
+
Variable symbol, e.g., 'x'.
|
|
381
|
+
unit : str, optional
|
|
382
|
+
Signal unit. Default is '$m/s^2$'.
|
|
383
|
+
seed : int, optional
|
|
384
|
+
Random seed for reproducibility.
|
|
385
|
+
interp : str, optional
|
|
386
|
+
PSD interpolation method: 'lin' or 'log'. Default is 'lin'.
|
|
387
|
+
"""
|
|
388
|
+
def __init__(
|
|
389
|
+
self,
|
|
390
|
+
fpsd:np.ndarray,
|
|
391
|
+
psd:np.ndarray,
|
|
392
|
+
T:float,
|
|
393
|
+
fs:float = None,
|
|
394
|
+
dfpsd:float = 0.5,
|
|
395
|
+
method:str = 'beta_am',
|
|
396
|
+
params = None,
|
|
397
|
+
name:str="",
|
|
398
|
+
var:str='x',
|
|
399
|
+
unit:str="$m/s^2$",
|
|
400
|
+
seed:int=None,
|
|
401
|
+
interp:str ='lin'):
|
|
402
|
+
|
|
403
|
+
method = method.lower()
|
|
404
|
+
interp = interp.lower()
|
|
405
|
+
interps = {'lin': lin_interp_psd,
|
|
406
|
+
'log': log_interp_psd}
|
|
407
|
+
|
|
408
|
+
method_existance_check(interp, interps)
|
|
409
|
+
|
|
410
|
+
if fs is None:
|
|
411
|
+
self.fs = fpsd[-1] * 2 # Nyquist frequency
|
|
412
|
+
else:
|
|
413
|
+
self.fs = fs
|
|
414
|
+
|
|
415
|
+
if len(fpsd)!=len(psd):
|
|
416
|
+
raise AttributeError('The frequency vector and the PSD vector are not the same size.')
|
|
417
|
+
self.dfpsd = dfpsd
|
|
418
|
+
self.fpsd,self.psd = interps[interp](fpsd, psd, n_points = int(fpsd[-1]/dfpsd), fs = self.fs)
|
|
419
|
+
self.T = T
|
|
420
|
+
self.x, self.carrier = self._get_nonstationary_timehistory(method = method, params = params, seed_gauss = seed)
|
|
421
|
+
self.signal_type = f'Nonstationary - NonGaussian ({method})'
|
|
422
|
+
super().__init__(
|
|
423
|
+
x = self.x,
|
|
424
|
+
fs = self.fs,
|
|
425
|
+
dfpsd = dfpsd,
|
|
426
|
+
name = name,
|
|
427
|
+
var = var,
|
|
428
|
+
unit = unit,
|
|
429
|
+
signal_type=self.signal_type
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _get_gaussian_timehistory(self, seed = None):
|
|
434
|
+
"""
|
|
435
|
+
Internal function called by __init__
|
|
436
|
+
Generate a stationary Gaussian signal from the given PSD.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
signal : np.ndarray
|
|
440
|
+
The generated Gaussian signal.
|
|
441
|
+
"""
|
|
442
|
+
_, signal = get_stationary_gaussian(self.fpsd, self.psd,self.T, seed)
|
|
443
|
+
return signal
|
|
444
|
+
|
|
445
|
+
def _get_nonstationary_timehistory(self, params, method = 'beta_am', seed_gauss = None, seed_mod = None):
|
|
446
|
+
"""
|
|
447
|
+
Internal function called by __init__
|
|
448
|
+
Designes a non-stationary process based off the parameters and methods imposed by the user.
|
|
449
|
+
|
|
450
|
+
Parameters:
|
|
451
|
+
params : dict
|
|
452
|
+
Parameters for the modulation method.
|
|
453
|
+
method : str, optional
|
|
454
|
+
Modulation method.
|
|
455
|
+
seed_gauss : int, optional
|
|
456
|
+
Seed for Gaussian signal.
|
|
457
|
+
seed_mod : int, optional
|
|
458
|
+
Seed for modulation carrier.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
tuple: Non-stationary signal and modulation carrier info.
|
|
462
|
+
"""
|
|
463
|
+
stationary_gaussian_signal = self._get_gaussian_timehistory(seed = seed_gauss)
|
|
464
|
+
methods = {'beta_am': get_beta_amplitude_modulation,
|
|
465
|
+
'ray_am': get_rayleigh_amplitude_modulation,
|
|
466
|
+
'trapp_am': get_trapp_amplitude_modulation,
|
|
467
|
+
'fm' : get_frequency_modulation,
|
|
468
|
+
}
|
|
469
|
+
method_existance_check(method, methods)
|
|
470
|
+
print(f'Estimating non-stationary signal from given parameters with "{method}" method')
|
|
471
|
+
|
|
472
|
+
if method in ['fm']:
|
|
473
|
+
nperseg = round(self.fs/2/self.dfpsd)
|
|
474
|
+
self.x = stationary_gaussian_signal
|
|
475
|
+
Sx, SFT = self.get_sftf(nperseg = nperseg, hop = nperseg//2, nargout=2)
|
|
476
|
+
nonstationary_signal, carrier = methods[method](Sx, SFT, modulation_function = params)
|
|
477
|
+
else:
|
|
478
|
+
nonstationary_signal, carrier = methods[method](stationary_gaussian_signal, self.T, **params, seed=seed_mod)
|
|
479
|
+
|
|
480
|
+
output_skewness = sp.stats.skew(nonstationary_signal)
|
|
481
|
+
output_kurtosis = sp.stats.kurtosis(nonstationary_signal)+3
|
|
482
|
+
print(f'Non-stationary signal generated with "{method}" method: skew = {output_skewness:.1f}, kurt = {output_kurtosis:.1f}')
|
|
483
|
+
return nonstationary_signal, carrier
|
|
484
|
+
|
|
485
|
+
def plot_carrier(self, ax = None, ylims = None, xlims = None):
|
|
486
|
+
"""
|
|
487
|
+
Plot the modulation carrier used to transform the Gaussian signal.
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
ax : matplotlib.axes.Axes, optional
|
|
491
|
+
Axis to plot on. Creates a new one if None.
|
|
492
|
+
ylims : tuple, optional
|
|
493
|
+
y-axis limits.
|
|
494
|
+
xlims : tuple, optional
|
|
495
|
+
x-axis limits.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
ax : matplotlib.axes.Axes
|
|
500
|
+
The axes with the plotted carrier.
|
|
501
|
+
"""
|
|
502
|
+
print(f'Plotting Timehistory')
|
|
503
|
+
if ax is None:
|
|
504
|
+
_, ax = plt.subplots(figsize=(10, 4))
|
|
505
|
+
color_list = ['k']+[f'C{i:d}' for i in range(len(self.carrier['carrier']))]
|
|
506
|
+
for i, band_carrier in enumerate(self.carrier['carrier']):
|
|
507
|
+
ax.plot(self.t,band_carrier,color_list[i], label = f"Carrier n.{i+1}")
|
|
508
|
+
ax.set_xlabel("Time [s]")
|
|
509
|
+
ax.set_ylabel(self.carrier['name'])
|
|
510
|
+
ax.legend()
|
|
511
|
+
ax.grid(True,which = 'both')
|
|
512
|
+
ax.minorticks_on()
|
|
513
|
+
ax.set_title(f'Carrier function {self.name}')
|
|
514
|
+
if xlims is not None:
|
|
515
|
+
ax.set_xlim(xlims)
|
|
516
|
+
if ylims is not None:
|
|
517
|
+
ax.set_ylim(ylims)
|
|
518
|
+
return ax
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if __name__ == "__main__":
|
|
522
|
+
pass
|