pyuff 1.0__tar.gz
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.
- pyuff-1.0/LICENSE +21 -0
- pyuff-1.0/PKG-INFO +76 -0
- pyuff-1.0/fft_tools.py +235 -0
- pyuff-1.0/pyFRF.py +988 -0
- pyuff-1.0/pyuff.egg-info/PKG-INFO +76 -0
- pyuff-1.0/pyuff.egg-info/SOURCES.txt +9 -0
- pyuff-1.0/pyuff.egg-info/dependency_links.txt +1 -0
- pyuff-1.0/pyuff.egg-info/requires.txt +8 -0
- pyuff-1.0/pyuff.egg-info/top_level.txt +2 -0
- pyuff-1.0/setup.cfg +4 -0
- pyuff-1.0/setup.py +47 -0
pyuff-1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) acknowledgement: from 2014-2017 this package was part of the OpenModal project
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyuff-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyuff
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Frequency response function as used in structural dynamics.
|
|
5
|
+
Home-page: https://github.com/ladisk/pyFRF
|
|
6
|
+
Author: Janko Slavič, Luka Novak, Martin Česnik, et al.
|
|
7
|
+
Author-email: janko.slavic@fs.uni-lj.si
|
|
8
|
+
Keywords: FRF,MIMO,SIMO,ODS
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
|
|
11
|
+
pyFRF
|
|
12
|
+
=====
|
|
13
|
+
|
|
14
|
+
Frequency response function as used in structural dynamics.
|
|
15
|
+
-----------------------------------------------------------
|
|
16
|
+
For more information check out the showcase examples and see documentation_.
|
|
17
|
+
|
|
18
|
+
Basic ``pyFRF`` usage:
|
|
19
|
+
----------------------
|
|
20
|
+
|
|
21
|
+
Make an instance of ``FRF`` class:
|
|
22
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
23
|
+
|
|
24
|
+
.. code:: python
|
|
25
|
+
|
|
26
|
+
a = pyFRF.FRF(
|
|
27
|
+
sampling_freq,
|
|
28
|
+
exc=None,
|
|
29
|
+
resp=None,
|
|
30
|
+
exc_type='f', resp_type='a',
|
|
31
|
+
window='none',
|
|
32
|
+
weighting='linear',
|
|
33
|
+
fft_len=None,
|
|
34
|
+
nperseg=None,
|
|
35
|
+
noverlap=None,
|
|
36
|
+
archive_time_data=False,
|
|
37
|
+
frf_type='H1',
|
|
38
|
+
copy=True
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Adding data:
|
|
42
|
+
~~~~~~~~~~~~
|
|
43
|
+
We can add the excitation and response data at the beginning through ``exc`` and ``resp`` arguments, otherwise, the excitation and response
|
|
44
|
+
data can be added later via ``add_data()`` method:
|
|
45
|
+
|
|
46
|
+
.. code:: python
|
|
47
|
+
|
|
48
|
+
a.add_data(exc, resp)
|
|
49
|
+
|
|
50
|
+
Computing FRF:
|
|
51
|
+
~~~~~~~~~~~~~~
|
|
52
|
+
Preferable way to get the frequency response functions is via ``get_FRF()`` method:
|
|
53
|
+
|
|
54
|
+
.. code:: python
|
|
55
|
+
|
|
56
|
+
frf = a.get_FRF(type="default", form="receptance")
|
|
57
|
+
|
|
58
|
+
We can also directly get the requested FRF via other methods: ``get_H1()``, ``get_H2()``, ``get_Hv()`` and, ``get_ods_frf()``:
|
|
59
|
+
|
|
60
|
+
.. code:: python
|
|
61
|
+
|
|
62
|
+
H1 = a.get_H1()
|
|
63
|
+
H2 = a.get_H2()
|
|
64
|
+
Hv = a.get_Hv()
|
|
65
|
+
ods_frf = a.get_ods_frf()
|
|
66
|
+
|
|
67
|
+
.. _documentation: https://pyfrf.readthedocs.io/en/latest/
|
|
68
|
+
|
|
69
|
+
|pytest|
|
|
70
|
+
|
|
71
|
+
|binder| to test the *Showcase.ipynb*.
|
|
72
|
+
|
|
73
|
+
.. |binder| image:: https://mybinder.org/badge_logo.svg
|
|
74
|
+
:target: https://mybinder.org/v2/gh/ladisk/pyFRF/main
|
|
75
|
+
.. |pytest| image:: https://github.com/ladisk/pyFRF/actions/workflows/python-package.yml/badge.svg
|
|
76
|
+
:target: https://github.com/ladisk/pyFRF/actions
|
pyuff-1.0/fft_tools.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tools to work wit fft data (this file is part of the pyFRF package);
|
|
3
|
+
especially, frequency response functions
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
_FRF_TYPES = {'a': 2, 'v': 1, 'd': 0} # accelerance, mobility, receptance
|
|
10
|
+
|
|
11
|
+
def multiply(ffts, m):
|
|
12
|
+
"""Multiplies ffts*m. ffts can be a single fft or an array of ffts.
|
|
13
|
+
|
|
14
|
+
:param ffts: array of fft data
|
|
15
|
+
:param m: multiplication vector
|
|
16
|
+
:return: multiplied array of fft data
|
|
17
|
+
"""
|
|
18
|
+
out = np.zeros_like(ffts, dtype='complex')
|
|
19
|
+
if len(np.shape(ffts)) == 2: # list of
|
|
20
|
+
n = np.shape(ffts)[0] # number of frfs
|
|
21
|
+
for j in range(n):
|
|
22
|
+
out[j, :] = ffts[j, :] * m
|
|
23
|
+
else:
|
|
24
|
+
out[:] = ffts[:] * m
|
|
25
|
+
|
|
26
|
+
return out
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def frequency_integration(ffts, omega, order=1):
|
|
30
|
+
"""Integrates ffts (one or many) in the frequency domain.
|
|
31
|
+
|
|
32
|
+
:param ffts: [rad/s] : angular frequency vector
|
|
33
|
+
:param omega: angular frequency
|
|
34
|
+
:param order: order of integration
|
|
35
|
+
:return: integrated array of fft data
|
|
36
|
+
"""
|
|
37
|
+
return multiply(ffts, np.power(-1.j / omega, order))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def frequency_derivation(ffts, omega, order=1):
|
|
41
|
+
"""Derivates ffts (one or many) in the frequency domain.
|
|
42
|
+
|
|
43
|
+
:param ffts: array of fft data
|
|
44
|
+
:param omega: [rad/s] angular frequency vector
|
|
45
|
+
:param order: order of derivation
|
|
46
|
+
:return: derivated array of fft data
|
|
47
|
+
"""
|
|
48
|
+
with np.errstate(invalid='ignore'):
|
|
49
|
+
return multiply(ffts, np.power(1.j * omega, order))
|
|
50
|
+
|
|
51
|
+
def convert_frf(input_frfs, omega, input_frf_type, output_frf_type):
|
|
52
|
+
""" Converting the frf accelerance/mobility/receptance
|
|
53
|
+
|
|
54
|
+
The most general case is when `input_frfs` is of shape:
|
|
55
|
+
`nr_inputs` * `nr_outputs` * `frf_len`
|
|
56
|
+
|
|
57
|
+
:param input_frfs: frequency response function vector (of dim 1, 2 or 3)
|
|
58
|
+
:param omega: [rad/s] angular frequency vector
|
|
59
|
+
:param input_frf_type: 'd' receptance, 'v' mobility, 'a' accelerance (of dim 0, 1, 2)
|
|
60
|
+
:param output_frf_type: 'd' receptance, 'v' mobility, 'a' accelerance (of dim 0, 1, 2)
|
|
61
|
+
:return: frequency response function vector (of dim 1, 2 or 3)
|
|
62
|
+
"""
|
|
63
|
+
# put all data to 3D frf type (nr_inputs * nr_outputs * frf_len)
|
|
64
|
+
ini_shape = input_frfs.shape
|
|
65
|
+
if 1 <=len(ini_shape) > 3 :
|
|
66
|
+
raise Exception('Input frf should be if dimension 3 or smaller')
|
|
67
|
+
elif len(ini_shape) == 2:
|
|
68
|
+
input_frfs = np.expand_dims(input_frfs, axis=0)
|
|
69
|
+
|
|
70
|
+
if type(input_frf_type) == str:
|
|
71
|
+
input_frf_type = [ini_shape[0]*[input_frf_type]]
|
|
72
|
+
else:
|
|
73
|
+
input_frf_type = [input_frf_type]
|
|
74
|
+
|
|
75
|
+
if type(output_frf_type) == str:
|
|
76
|
+
output_frf_type = [ini_shape[0]*[output_frf_type]]
|
|
77
|
+
else:
|
|
78
|
+
output_frf_type = [output_frf_type]
|
|
79
|
+
elif len(ini_shape) == 1:
|
|
80
|
+
input_frfs = np.expand_dims(np.expand_dims(input_frfs, axis=0), axis=0)
|
|
81
|
+
input_frf_type = [[input_frf_type]]
|
|
82
|
+
output_frf_type = [[output_frf_type]]
|
|
83
|
+
|
|
84
|
+
# reshaping of frfs
|
|
85
|
+
(nr_inputs, nr_outputs, frf_len) = input_frfs.shape
|
|
86
|
+
nr_frfs = nr_inputs * nr_outputs
|
|
87
|
+
input_frfs = input_frfs.reshape(nr_frfs,-1)
|
|
88
|
+
|
|
89
|
+
# reshaping of input and output frf types
|
|
90
|
+
input_frf_type = np.asarray(input_frf_type)
|
|
91
|
+
output_frf_type = np.asarray(output_frf_type)
|
|
92
|
+
if len(input_frf_type.shape) != 2 or len(output_frf_type.shape) !=2:
|
|
93
|
+
raise Exception('Input and output frf type should be of dimension 2.')
|
|
94
|
+
input_frf_type = input_frf_type.flatten()
|
|
95
|
+
output_frf_type = output_frf_type.flatten()
|
|
96
|
+
if len(input_frf_type) != nr_frfs or len(output_frf_type) != nr_frfs:
|
|
97
|
+
raise Exception('Input and output frf type length should correspond to the number frfs.')
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
input_frf_type = [_FRF_TYPES[_] for _ in input_frf_type]
|
|
101
|
+
output_frf_type = [_FRF_TYPES[_] for _ in output_frf_type]
|
|
102
|
+
except:
|
|
103
|
+
raise('Only frf types: d, v and a are supported.')
|
|
104
|
+
|
|
105
|
+
# do the conversion
|
|
106
|
+
output_frfs = np.zeros_like(input_frfs)
|
|
107
|
+
for i in range(nr_frfs):
|
|
108
|
+
order = output_frf_type[i] - input_frf_type[i]
|
|
109
|
+
if (order > 2) or (order <-2):
|
|
110
|
+
raise Exception('FRF conversion not supported.')
|
|
111
|
+
output_frfs[i, :] = frequency_derivation(input_frfs[i, :], omega, order=order)
|
|
112
|
+
|
|
113
|
+
#reshape back to original shape
|
|
114
|
+
if len(ini_shape) == 3:
|
|
115
|
+
return output_frfs.reshape((nr_inputs, nr_outputs, -1))
|
|
116
|
+
elif len(ini_shape) == 2:
|
|
117
|
+
return output_frfs.reshape((nr_outputs, -1))
|
|
118
|
+
elif len(ini_shape) == 1:
|
|
119
|
+
return output_frfs[0]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def correct_time_delay(fft, w, time_delay):
|
|
123
|
+
"""
|
|
124
|
+
Corrects the ``fft`` with regards to the ``time_delay``.
|
|
125
|
+
|
|
126
|
+
:param fft: fft array
|
|
127
|
+
:param w: angular frequency [rad/s]
|
|
128
|
+
:param time_delay: time dalay in seconds
|
|
129
|
+
:return: corrected fft array
|
|
130
|
+
"""
|
|
131
|
+
return fft / (np.exp(1j * w * time_delay))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def PSD(x, dt=1):
|
|
135
|
+
""" Power spectral density
|
|
136
|
+
|
|
137
|
+
:param x: time domain data
|
|
138
|
+
:param dt: delta time
|
|
139
|
+
:return: PSD, freq
|
|
140
|
+
"""
|
|
141
|
+
X = np.fft.rfft(x)
|
|
142
|
+
freq = np.fft.rfftfreq(len(x), d=dt)
|
|
143
|
+
X = 2 * dt * np.abs(X.conj() * X)/ len(x)
|
|
144
|
+
|
|
145
|
+
return X, freq
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def fft_adjusted_lower_limit(x, lim, nr):
|
|
149
|
+
"""
|
|
150
|
+
Compute the fft of complex matrix x with adjusted summation limits:
|
|
151
|
+
|
|
152
|
+
y(j) = sum[k=-n-1, -n-2, ... , -low_lim-1, low_lim, low_lim+1, ... n-2,
|
|
153
|
+
n-1] x[k] * exp(-sqrt(-1)*j*k* 2*pi/n),
|
|
154
|
+
j = -n-1, -n-2, ..., -low_limit-1, low_limit, low_limit+1, ... n-2, n-1
|
|
155
|
+
|
|
156
|
+
:param x: Single-sided complex array to Fourier transform.
|
|
157
|
+
:param lim: lower limit index of the array x.
|
|
158
|
+
:param nr: number of points of interest
|
|
159
|
+
:return: Fourier transformed two-sided array x with adjusted lower limit.
|
|
160
|
+
Retruns [0, -1, -2, ..., -nr+1] and [0, 1, 2, ... , nr-1] values.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
nf = 2 * (len(x) - lim) - 1
|
|
164
|
+
|
|
165
|
+
n = np.arange(-nr + 1, nr)
|
|
166
|
+
|
|
167
|
+
a = np.fft.fft(x, n=nf).real[n]
|
|
168
|
+
b = np.fft.fft(x[:lim], n=nf).real[n]
|
|
169
|
+
c = x[lim].conj() * np.exp(1j * 2 * np.pi * n * lim / nf)
|
|
170
|
+
|
|
171
|
+
res = 2 * (a - b) - c
|
|
172
|
+
|
|
173
|
+
return res[:nr][::-1], res[nr - 1:]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def check_fft_for_speed(data_length, exception_if_prime_above=20):
|
|
177
|
+
"""To avoid slow FFT, raises an exception if largest prime above `exception_if_prime_above`.
|
|
178
|
+
|
|
179
|
+
See: http://stackoverflow.com/questions/23287/largest-prime-factor-of-a-number/
|
|
180
|
+
|
|
181
|
+
:param data_length: length of data for frf
|
|
182
|
+
:param exception_if_prime_above: raise exception if the largest prime number is above
|
|
183
|
+
:return: none
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def prime_factors(n):
|
|
187
|
+
"""Returns all prime factors of a positive integer
|
|
188
|
+
|
|
189
|
+
See: http://stackoverflow.com/questions/23287/largest-prime-factor-of-a-number/412942#412942
|
|
190
|
+
|
|
191
|
+
:param n: lenght
|
|
192
|
+
:return: array of prime numbers
|
|
193
|
+
"""
|
|
194
|
+
factors = []
|
|
195
|
+
d = 2
|
|
196
|
+
while n > 1:
|
|
197
|
+
while n % d == 0:
|
|
198
|
+
factors.append(d)
|
|
199
|
+
n /= d
|
|
200
|
+
d += 1
|
|
201
|
+
if d * d > n:
|
|
202
|
+
if n > 1:
|
|
203
|
+
factors.append(n)
|
|
204
|
+
break
|
|
205
|
+
return factors
|
|
206
|
+
|
|
207
|
+
if np.max(prime_factors(data_length)) > exception_if_prime_above:
|
|
208
|
+
raise Exception('Change the number of time/frequency points or the FFT will run slow.')
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def irfft_adjusted_lower_limit(x, low_lim, indices):
|
|
212
|
+
"""
|
|
213
|
+
Compute the ifft of real matrix x with adjusted summation limits:
|
|
214
|
+
|
|
215
|
+
y(j) = sum[k=-n-2, ... , -low_lim-1, low_lim, low_lim+1, ... n-2,
|
|
216
|
+
n-1] x[k] * exp(sqrt(-1)*j*k* 2*pi/n),
|
|
217
|
+
j =-n-2, ..., -low_limit-1, low_limit, low_limit+1, ... n-2, n-1
|
|
218
|
+
|
|
219
|
+
:param x: Single-sided real array to Fourier transform.
|
|
220
|
+
:param low_lim: lower limit index of the array x.
|
|
221
|
+
:param indices: list of indices of interest
|
|
222
|
+
:return: Fourier transformed two-sided array x with adjusted lower limit.
|
|
223
|
+
Retruns values.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
nf = 2 * (x.shape[1] - 1)
|
|
227
|
+
a = (np.fft.irfft(x, n=nf)[:, indices]) * nf
|
|
228
|
+
b = (np.fft.irfft(x[:, :low_lim], n=nf)[:, indices]) * nf
|
|
229
|
+
return a - b
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
if __name__ == '__main__':
|
|
233
|
+
plot_figure = False
|
|
234
|
+
# check_fft_for_speed(4) #fast
|
|
235
|
+
# check_fft_for_speed(59612) #slow
|