• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sandialabs / sdynpy / 13036987848

29 Jan 2025 05:23PM UTC coverage: 15.675%. Remained the same
13036987848

push

github

dprohe
Bugfix due to breaking change in qtpy/PyQt5

1 of 2 new or added lines in 2 files covered. (50.0%)

1 existing line in 1 file now uncovered.

2606 of 16625 relevant lines covered (15.68%)

0.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.95
/src/sdynpy/signal_processing/sdynpy_frf.py
1
# -*- coding: utf-8 -*-
2
"""
3
Tools for computing frequency response functions
4

5
Copyright 2022 National Technology & Engineering Solutions of Sandia,
6
LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
7
Government retains certain rights in this software.
8

9
This program is free software: you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation, either version 3 of the License, or
12
(at your option) any later version.
13

14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
GNU General Public License for more details.
18

19
You should have received a copy of the GNU General Public License
20
along with this program.  If not, see <https://www.gnu.org/licenses/>.
21
"""
22

23
import numpy as np
3✔
24
import scipy.signal as sig
3✔
25
import matplotlib.pyplot as plt
3✔
26
from .sdynpy_lrm import frf_local_model
3✔
27

28

29
def sysmat2frf(frequencies, M, C, K, frf_type='disp'):
3✔
30
    '''Compute Frequency Response Functions given system matrices M, C, and K
31

32
    This function computes frequency response functions (FRFs) given the system
33
    mass matrix, M, damping matrix, C, and stiffness matrix, K, in the equation
34
    of motion
35

36
    M x_dd + C x_d + K x = F
37

38
    This will return the frequency response function
39

40
    H = x/F
41

42
    as a function of frequency.
43

44
    Parameters
45
    ----------
46
    frequencies : ndarray
47
        frequencies should be a 1D np.ndarray consisting of the frequency lines
48
        at which to evaluate the frequency response function.  They should be
49
        in units of cycles per second or hertz (Hz), rather than in angular
50
        frequency of radians/s.
51
    M : ndarray
52
        M should be a 2D np.ndarray consisting of the system mass matrix.
53
    C : ndarray
54
        C should be a 2D np.ndarray consisting of the system damping matrix.
55
    K : ndarray
56
        K should be a 2D np.ndarray consisting of the system stiffness matrix.
57
    frf_type : str
58
        frf_type should be one of ['disp','vel','accel'] or ['displacement',
59
        'velocity','acceleration'] to specify which "type" of frequency
60
        response function to compute.  By default it computes a displacement or
61
        "receptance" FRF.  However, if an acceleration or "Accelerance" FRF is
62
        desired, specify 'accel' instead.  The displacement, velocity, and
63
        acceleration FRFs differ by a factor of 1j*omega where omega is the
64
        angular frequency at a given frequency line.
65

66
    Returns
67
    -------
68
    H : ndarray
69
        A 3D np array with shape (nf,no,ni), where nf is the number of
70
        frequency lines, no is the number of outputs, and ni is the number of
71
        inputs.  Since M, C, and K should be square, ni should equal no.
72
        Values in H are complex.
73

74
    Notes
75
    -----
76
    This performs a direct inversion of the system matrix, therefore it is not
77
    advisable to compute FRFs of large systems using this method.  An
78
    alternative would be to compute modes first then compute the FRFs from
79
    the modes.
80
    '''
81
    if not frf_type in ['displacement', 'velocity', 'acceleration', 'disp', 'vel',
×
82
                        'accel']:
83
        raise ValueError('frf_type must be one of {:}'.format(['displacement',
×
84
                         'velocity', 'acceleration', 'disp', 'vel', 'accel']))
85
    Z = (-(2 * np.pi * frequencies[:, np.newaxis, np.newaxis])**2 * M
×
86
         + 1j * (2 * np.pi * frequencies[:, np.newaxis, np.newaxis]) * C
87
         + K)
88
    H = np.linalg.inv(Z)
×
89
    if frf_type in ['vel', 'velocity']:
×
90
        H = 1j * (2 * np.pi * frequencies[:, np.newaxis, np.newaxis]) * H
×
91
    elif frf_type in ['accel', 'acceleration']:
×
92
        H = -(2 * np.pi * frequencies[:, np.newaxis, np.newaxis])**2 * H
×
93

94
    return H
×
95

96

97
def modes2frf(frequencies, natural_frequencies, damping_ratios, mode_shapes=None,
3✔
98
              input_mode_shapes=None, frf_type='disp'):
99
    '''Compute Frequency Response Functions given modal properties
100

101
    This function computes frequency responses given modal parameters.
102

103
    Parameters
104
    ----------
105
    frequencies : ndarray
106
        frequencies should be a 1D np.ndarray consisting of the frequency lines
107
        at which to evaluate the frequency response function.  They should be
108
        in units of cycles per second or hertz (Hz), rather than in angular
109
        frequency of radians/s.
110
    natural_frequencies : ndarray
111
        Natural frequencies of the structure in cycles per second or hertz (Hz)
112
        rather than in angular frequency of radians/s.
113
    damping_ratios : ndarray
114
        Critical damping ratios of the structure in ratio form rather than
115
        percentange, e.g. 2% damping would be specified as 0.02 rather than 2.
116
    mode_shapes : ndarray, optional
117
        A 2D mode shape matrix with shape (no,nm) where no is the number of
118
        output responses and nm is the number of modes.  If the optional
119
        argument input_mode_shapes is not specified, this mode shape matrix
120
        will also be used for the inputs, resulting in a square FRF matrix.
121
        If mode_shapes is not specified, the values of the modal FRF matrix
122
        are returned as 2D array.
123
    input_mode_shapes : ndarray, optional
124
        A 2D mode shape matrix with shape (ni,nm) where ni is the number of
125
        input forces and nm is the number of modes.  If the optional argument 
126
        input_mode_shapes is specified, it be used for the inputs, resulting in
127
        a potentially nonsquare FRF matrix.
128
    frf_type : str, optional
129
        frf_type should be one of ['disp','vel','accel'] or ['displacement',
130
        'velocity','acceleration'] to specify which "type" of frequency
131
        response function to compute.  By default it computes a displacement or
132
        "receptance" FRF.  However, if an acceleration or "Accelerance" FRF is
133
        desired, specify 'accel' instead.  The displacement, velocity, and
134
        acceleration FRFs differ by a factor of 1j*omega where omega is the
135
        angular frequency at a given frequency line.
136

137
    Returns
138
    -------
139
    H : ndarray
140
        A 3D (or 2D) np array depending on whether or not mode_shapes is 
141
        defined with shape (nf,no,ni) or (nf,nm), where nf is the number of
142
        frequency lines, no is the number of outputs, ni is the number of
143
        inputs, and nm is the number of modes.  Values in H are complex.
144

145
    Notes
146
    -----
147
    This function assumes mass normalized mode shapes.
148

149
    '''
150
    if not frf_type in ['displacement', 'velocity', 'acceleration', 'disp', 'vel',
×
151
                        'accel']:
152
        raise ValueError('frf_type must be one of {:}'.format(['displacement',
×
153
                         'velocity', 'acceleration', 'disp', 'vel', 'accel']))
154

155
    Z = (-(2 * np.pi * frequencies[:, np.newaxis])**2
×
156
         + 1j * (2 * np.pi * frequencies[:, np.newaxis]) * 2 *
157
         damping_ratios * (2 * np.pi * natural_frequencies)
158
         + (2 * np.pi * natural_frequencies)**2)
159
    if mode_shapes is not None:
×
160
        if input_mode_shapes is None:
×
161
            input_mode_shapes = mode_shapes
×
162
        H = np.einsum('ij,fj,lj->fil', mode_shapes, 1 / Z, input_mode_shapes)
×
163
    else:
164
        H = 1/Z
×
165

166
    if frf_type in ['vel', 'velocity']:
×
167
        if np.ndim(H) == 3:
×
168
            H = 1j * (2 * np.pi * frequencies[:, np.newaxis, np.newaxis]) * H
×
169
        elif np.ndim(H) == 2:
×
170
            H = 1j * (2 * np.pi * frequencies[:, np.newaxis]) * H
×
171
        # num = [1,0]
172
    elif frf_type in ['accel', 'acceleration']:
×
173
        if np.ndim(H) == 3:
×
174
            H = -(2 * np.pi * frequencies[:, np.newaxis, np.newaxis])**2 * H
×
175
        elif np.ndim(H) == 2:
×
176
            H = -(2 * np.pi * frequencies[:, np.newaxis])**2 * H
×
177
    return H
×
178

179
def timedata2frf(references, responses, dt=1, samples_per_average=None,
3✔
180
                 overlap=0.0, method='H1', window=np.array((1.0,)),
181
                 response_fft=lambda array: np.fft.rfft(array, axis=-1),
182
                 reference_fft=lambda array: np.fft.rfft(array, axis=-1),
183
                 response_fft_array=None, reference_fft_array=None,
184
                 return_model_data=False, **lrm_kwargs):
185
    # TODO Update DOCSTRING with changes after they are all made.
186
    '''Creates an FRF matrix given time histories of responses and references
187

188
    This function creates a nf x no x ni FRF matrix from the time histories
189
    provided.
190

191
    Parameters
192
    ----------
193
    references : ndarray
194
        A ni x nt or nt array where ni is the number of references and nt is
195
        the number of time steps in the signal.  If averaging is specified, nt
196
        should be divisible by the number of averages.
197
    responses : ndarray
198
        A no x nt or nt array where no is the number of responses and nt is the
199
        number of time steps in the signal.  If averaging is specified, nt
200
        should be divisible by the number of averages.
201
    dt : float
202
        The time between samples
203
    samples_per_average : int
204
        The number of time samples per average.  If not specified, it is set
205
        to the number of samples in the time signal, and no averaging is 
206
        performed
207
    overlap : float
208
        The overlap as a fraction of the frame (e.g. 0.5 specifies 50% overlap).
209
        If not specified, no overlap is used.
210
    method : str in ['H1','H2','Hv','Hs','LRM']
211
        The method for creating the frequency response function. 'H1' is 
212
        default if not specified.
213
    window : ndarray or str
214
        A 1D ndarray with length samples_per_average that specifies the
215
        coefficients of the window.  No window is applied if not specified.
216
        If a string is specified, then the window will be obtained from scipy
217
    fft : function
218
        FFT Function that should be used.  FFT must take the fft over axis -1.
219
    response_fft_array : np.ndarray
220
        Array to store the data into before taking the FFT.  Should be
221
        size number_of_responses, n_averages, samples_per_average
222
    reference_fft_array : np.ndarray
223
        Array to store the data into before taking the FFT.  Should be
224
        size number_of_references, n_averages, samples_per_average
225
    return_model_data : boolean
226
        Wheter to return selected model orders used in the 'LRM' method.
227
        default is False        
228
    **lrm_kwargs
229
        Additional keyword arguments specify parameters if method == 'LRM'.
230
        Possible arguments are: f_out (ndarray), bandwidth (float), transient
231
        (boolean), modelset (iterable of (3,1) ndarray), export_ratio (float), 
232
        max_parallel (int), print_time (bool). See sdynpy_lrm.frf_local_model.
233
        samples_per_average, overlap, and window are void if method=='LRM'.
234

235
    Returns
236
    -------
237
    frequencies : ndarray
238
        A nf array of the frequency values associated with H
239
    H : ndarray
240
        A nf x no x ni array where nf is the number of frequency lines, no is
241
        the number of outputs, and ni is the number of inputs.
242
    model_data: dict
243
        Contains None if method != 'LRM'. See sdynpy_lrm.frf_local_model.
244
        
245
    Notes
246
    -----
247
    There are requirements for the shapes of references and responses for some
248
    FRF computations.
249

250
    '''
251
    if references.shape[-1] != responses.shape[-1]:
×
252
        raise ValueError(
×
253
            'reference and responses must have the same number of time steps (last dimension)!')
254
    if not method in ['H1', 'H2', 'H3', 'Hs', 'Hv', 'Hcd','LRM']:
×
255
        raise ValueError('method parameter must be one of ["H1","H2","H3","Hv","Hcd"]')
×
256
    if references.ndim == 1:
×
257
        references = references[np.newaxis, :]
×
258
    if responses.ndim == 1:
×
259
        responses = responses[np.newaxis, :]
×
260
    # Set up time indices
261
    ntimes_total = references.shape[-1]
×
262
    if samples_per_average is None or method == 'LRM':
×
263
        samples_per_average = ntimes_total
×
264
    overlap_samples = int(samples_per_average * overlap)
×
265
    time_starts = np.arange(0, ntimes_total - samples_per_average +
×
266
                            1, samples_per_average - overlap_samples)
267
    time_indices_for_averages = time_starts[:, np.newaxis] + np.arange(samples_per_average)
×
268
    if method == 'LRM':
×
269
        window=np.array((1.0,)) # local modeling does not use windowing
×
270
    if isinstance(window, str):
×
271
        window = sig.windows.get_window(window.lower(), samples_per_average, fftbins=True)
×
272
    # Sort the references and responses into their time arrays
273
    if response_fft_array is None:
×
274
        response_fft_array = responses[..., time_indices_for_averages] * window
×
275
    else:
276
        response_fft_array[...] = responses[:, time_indices_for_averages] * window
×
277
    if reference_fft_array is None:
×
278
        reference_fft_array = references[:, time_indices_for_averages] * window
×
279
    else:
280
        reference_fft_array[...] = references[:, time_indices_for_averages] * window
×
281
    # Compute FFTs
282
    frequencies = np.fft.rfftfreq(samples_per_average, dt)
×
283
    response_ffts = response_fft(response_fft_array)
×
284
    reference_ffts = reference_fft(reference_fft_array)
×
285
    # Now start computing FRFs
286
    if method == 'H1':
×
287
        # We want to compute X*F^H = [X1;X2;X3][F1^H F2^H F3^H]
288
        Gxf = np.einsum('...iaf,...jaf->...fij', response_ffts, np.conj(reference_ffts))
×
289
        Gff = np.einsum('...iaf,...jaf->...fij', reference_ffts, np.conj(reference_ffts))
×
290
        # Add small values to any matrices that are singular
291
        singular_matrices = np.abs(np.linalg.det(Gff)) < 2 * np.finfo(Gff.dtype).eps
×
292
        Gff[singular_matrices] += np.eye(Gff.shape[-1]) * np.finfo(Gff.dtype).eps
×
293
        H = np.moveaxis(np.linalg.solve(np.moveaxis(Gff, -2, -1), np.moveaxis(Gxf, -2, -1)), -2, -1)
×
294
    elif method == 'H2':
×
295
        if (response_ffts.shape != reference_ffts.shape):
×
296
            raise ValueError('For H2, Number of inputs must equal number of outputs')
×
297
        Gxx = np.einsum('...iaf,...jaf->...fij', response_ffts, np.conj(response_ffts))
×
298
        Gfx = np.einsum('...iaf,...jaf->...fij', reference_ffts, np.conj(response_ffts))
×
299
        singular_matrices = np.abs(np.linalg.det(Gfx)) < 2 * np.finfo(Gfx.dtype).eps
×
300
        Gfx[singular_matrices] += np.eye(Gfx.shape[-1]) * np.finfo(Gfx.dtype).eps
×
301
        H = np.moveaxis(np.linalg.solve(np.moveaxis(Gfx, -2, -1), np.moveaxis(Gxx, -2, -1)), -2, -1)
×
302
    elif method == 'H3':
×
303
        if (response_ffts.shape != reference_ffts.shape):
×
304
            raise ValueError('For H3, Number of inputs must equal number of outputs')
×
305
        Gxf = np.einsum('...iaf,...jaf->...fij', response_ffts, np.conj(reference_ffts))
×
306
        Gff = np.einsum('...iaf,...jaf->...fij', reference_ffts, np.conj(reference_ffts))
×
307
        # Add small values to any matrices that are singular
308
        singular_matrices = np.abs(np.linalg.det(Gff)) < 2 * np.finfo(Gff.dtype).eps
×
309
        Gff[singular_matrices] += np.eye(Gff.shape[-1]) * np.finfo(Gff.dtype).eps
×
310
        Gxx = np.einsum('...iaf,...jaf->...fij', response_ffts, np.conj(response_ffts))
×
311
        Gfx = np.einsum('...iaf,...jaf->...fij', reference_ffts, np.conj(response_ffts))
×
312
        singular_matrices = np.abs(np.linalg.det(Gfx)) < 2 * np.finfo(Gfx.dtype).eps
×
313
        Gfx[singular_matrices] += np.eye(Gfx.shape[-1]) * np.finfo(Gfx.dtype).eps
×
314
        H = (np.moveaxis(np.linalg.solve(np.moveaxis(Gfx, -2, -1), np.moveaxis(Gxx, -2, -1)), -2, -1) +
×
315
             np.moveaxis(np.linalg.solve(np.moveaxis(Gff, -2, -1), np.moveaxis(Gxf, -2, -1)), -2, -1)) / 2
316
    elif method == 'Hcd':
×
317
        Gxf = np.einsum('...iaf,...jaf->...fij', response_ffts, np.conj(reference_ffts))
×
318
        Gff = np.einsum('...iaf,...jaf->...fij', reference_ffts, np.conj(reference_ffts))
×
319
        # Add small values to any matrices that are singular
320
        singular_matrices = np.abs(np.linalg.det(Gff)) < 2 * np.finfo(Gff.dtype).eps
×
321
        Gff[singular_matrices] += np.eye(Gff.shape[-1]) * np.finfo(Gff.dtype).eps
×
322
        Lfz = np.linalg.cholesky(Gff)
×
323
        Lzf = np.conj(np.moveaxis(Lfz, -2, -1))
×
324
        Gxz = np.moveaxis(np.linalg.solve(np.moveaxis(
×
325
            Lzf, -2, -1), np.moveaxis(Gxf, -2, -1)), -2, -1)
326
        H = np.moveaxis(np.linalg.solve(np.moveaxis(Lfz, -2, -1), np.moveaxis(Gxz, -2, -1)), -2, -1)
×
327
    elif method == 'Hv':
×
328
        Gxx = np.einsum('...iaf,...iaf->...if', response_ffts,
×
329
                        np.conj(response_ffts))[..., np.newaxis, np.newaxis]
330
        Gxf = np.einsum('...iaf,...jaf->...ifj', response_ffts,
×
331
                        np.conj(reference_ffts))[..., np.newaxis, :]
332
        Gff = np.einsum('...iaf,...jaf->...fij', reference_ffts,
×
333
                        np.conj(reference_ffts))[..., np.newaxis, :, :, :]
334
        # Broadcast over all responses
335
        Gff = np.broadcast_to(Gff,Gxx.shape[:-2]+Gff.shape[-2:])
×
336
        Gffx = np.block([[Gff, np.conj(np.moveaxis(Gxf, -2, -1))],
×
337
                         [Gxf, Gxx]])
338
        # Compute eigenvalues
339
        lam, evect = np.linalg.eigh(np.moveaxis(Gffx, -2, -1))
×
340
        # Get the evect corresponding to the minimum eigenvalue
341
        evect = evect[..., 0]  # Assumes evals are sorted ascending
×
342
        H = np.moveaxis(-evect[..., :-1] / evect[..., -1:],  # Scale so last value is -1
×
343
                        -3, -2)
344
    elif method == 'LRM':
×
345
        frequencies, H, model_data = frf_local_model(reference_ffts,response_ffts,
×
346
                                                         frequencies,**lrm_kwargs)
347
    else:
348
        raise NotImplementedError('Method {:} has not been implemented yet!'.format(method))
×
349
        
350
    if return_model_data:
×
351
        if method != 'LRM':
×
352
            model_data = {'info': None,'modelset': None,'model_selected': None}
×
353
        return frequencies, H, model_data
×
354
    else:
355
        return frequencies, H
×
356

357

358
def fft2frf(references, responses, method='H1',freqs_in=None,**kwargs):
3✔
359
    '''Creates an FRF matrix given ffts of responses and references
360

361
    This function creates a nf x no x ni FRF matrix from the ffts
362
    provided.
363

364
    Parameters
365
    ----------
366
    references : ndarray
367
        A ni x nf or nf array where ni is the number of references and nt is
368
        the number of frequencies in the fft.
369
    responses : ndarray
370
        A no x nf or nf array where no is the number of responses and nf is the
371
        number of time frequencies in the fft.
372
    method : str in ['H1','H2','Hv','Hs','LRM']
373
        The method for creating the frequency response function.
374
    freqs_in : ndarray
375
        Only used if method == 'LRM'. A nf or nf x 1 array of frquency values.
376
    **lrm_kwargs
377
        Additional keyword arguments specify parameters if method == 'LRM'.
378
        Possible arguments are: f_out (ndarray), bandwidth (float), transient
379
        (boolean), modelset (iterable of (3,1) ndarray), max_parallel (int), 
380
        export_ratio (float). See sdynpy_lrm.frf_local_model.
381

382
    Returns
383
    -------
384
    H : ndarray
385
        A nf x no x ni array where nf is the number of frequency lines, no is
386
        the number of outputs, and ni is the number of inputs.  The output
387
        frequency lines will correspond to the frequency lines in the ffts.
388
    freqs_out : None or ndarray
389
        None unless method == 'LRM'. See sdynpy_lrm.frf_local_model.
390
    model_data: None dict
391
        None unless method == 'LRM'. See sdynpy_lrm.frf_local_model.
392

393
    Notes
394
    -----
395
    There are requirements for the shapes of references and responses for some
396
    FRF computation methods.  No averaging is performed by this function.
397

398
    '''
399
    if references.ndim == 1:
×
400
        references = references[np.newaxis, :]
×
401
    elif references.ndim > 2:
×
402
        raise ValueError('references should be at maximum a 2 dimensional array')
×
403
    if responses.ndim == 1:
×
404
        responses = responses[np.newaxis, :]
×
405
    elif responses.ndim > 2:
×
406
        raise ValueError('responses should be at maximum a 2 dimensional array')
×
407
    if not method in ['H1', 'H2', 'Hs', 'Hv', 'LRM']:
×
408
        raise ValueError('method parameter must be one of ["H1","H2","Hs","Hv","LRM"]')
×
409
#    H = np.zeros((references.shape[1],responses.shape[0],references.shape[0]),dtype=complex)
410
#    for i in range(H.shape[1]):
411
#            for j in range(H.shape[2]):
412
#                if method == 'H1':
413
#                    H[:,i,j] = (responses[i,:]*references[j,:].conj())/(references[j,:]*references[j,:].conj())
414
#                else:
415
#                    raise NotImplementedError('Method {:} has not been implemented'.format(method))
416
    if method == 'H1':
×
417
        Gxf = np.einsum('if,jf->fij', responses, references.conj())
×
418
        Gff = np.einsum('if,jf->fij', references, references.conj())
×
419
        H = np.linalg.solve(Gff.transpose(0, 2, 1), Gxf.transpose((0, 2, 1))).transpose((0, 2, 1))
×
420
    elif method == 'LRM':
×
421
        if freqs_in is None:
×
422
            raise ValueError('if method == \'LRM\', freqs_in must be specified')
×
423
        import sdynpy.signal_processing.sdynpy_lrm as frflm
×
424
        freqs_out, H, model_data = frflm.frf_local_model(references, responses, 
×
425
                                                         freqs_in, **lrm_kwargs)
426
    else:
427
        raise NotImplementedError('Method {:} has not been implemented'.format(method))
×
428
    if method != 'LRM':
×
429
        freqs_out = None
×
430
        model_data = None
×
431
    return H, freqs_out, model_data
×
432

433

434
def plot(H, f, responses=None, references=None, real_imag=False):
3✔
435
    fig = plt.figure()
×
436
    phase_axis = fig.add_subplot(2, 1, 1)
×
437
    mag_axis = fig.add_subplot(2, 1, 2)
×
438

439
    if responses is None:
×
440
        responses = np.arange(H.shape[1])[:, np.newaxis]
×
441
    if references is None:
×
442
        references = np.arange(H.shape[2])
×
443

444
    H_to_plot = H[:, responses, references]
×
445
    H_to_plot = H_to_plot.transpose(*np.arange(1, H_to_plot.ndim), 0)
×
446

447
    for index in np.ndindex(H_to_plot.shape[:-1]):
×
448
        if real_imag:
×
449
            phase_axis.plot(f, np.imag(H_to_plot[index]))
×
450
            mag_axis.plot(f, np.real(H_to_plot[index]))
×
451
        else:
452
            phase_axis.plot(f, np.angle(H_to_plot[index]) * 180 / np.pi)
×
453
            mag_axis.plot(f, np.abs(H_to_plot[index]))
×
454

455
    if real_imag:
×
456
        phase_axis.set_ylabel('Imag(H)')
×
457
        mag_axis.set_ylabel('Real(H)')
×
458
    else:
459
        phase_axis.set_ylabel('Angle(H)')
×
460
        mag_axis.set_ylabel('Abs(H)')
×
461
        mag_axis.set_yscale('log')
×
462
    mag_axis.set_xlabel('Frequency')
×
463
    fig.tight_layout()
×
464

465

466
def delay_signal(times, signal, dt):
3✔
467
    '''Delay a time signal by the specified amount
468

469
    This function takes a signal and delays it by a specified amount of time
470
    that need not be an integer number of samples.  It does this by adjusting
471
    the phaes of the signal's FFT.
472

473
    Parameters
474
    ----------
475
    times : np.ndarray
476
        A signal specifying the time values at which the samples in signal
477
        occur.
478
    signal : np.ndarray
479
        A signal that is to be delayed (n_signals x len(times))
480
    dt : float
481
        The amount of time to delay the signal
482

483
    Returns
484
    -------
485
    updated_signal : np.ndarray
486
        The time-shifted signal.
487
    '''
488
    fft_omegas = np.fft.fftfreq(len(times), np.mean(np.diff(times))) * 2 * np.pi
×
489
    signal_fft = np.fft.fft(signal, axis=-1)
×
490
    signal_fft *= np.exp(-1j * fft_omegas * dt)
×
491
    return np.fft.ifft(signal_fft, axis=-1).real.astype(signal.dtype)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc