acoular 24.5__py3-none-any.whl → 24.10__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.
- acoular/__init__.py +17 -11
- acoular/base.py +312 -0
- acoular/configuration.py +23 -16
- acoular/demo/acoular_demo.py +28 -35
- acoular/environments.py +20 -15
- acoular/fastFuncs.py +40 -40
- acoular/fbeamform.py +1100 -1130
- acoular/fprocess.py +368 -0
- acoular/grids.py +36 -22
- acoular/h5cache.py +34 -34
- acoular/h5files.py +13 -13
- acoular/internal.py +3 -3
- acoular/process.py +464 -0
- acoular/sdinput.py +24 -4
- acoular/signals.py +20 -6
- acoular/sources.py +140 -56
- acoular/spectra.py +34 -53
- acoular/tbeamform.py +264 -142
- acoular/tfastfuncs.py +17 -18
- acoular/tools/__init__.py +2 -0
- acoular/tools/aiaa.py +7 -8
- acoular/tools/helpers.py +2 -2
- acoular/tools/metrics.py +1 -1
- acoular/tools/utils.py +210 -0
- acoular/tprocess.py +168 -532
- acoular/traitsviews.py +5 -3
- acoular/version.py +2 -2
- {acoular-24.5.dist-info → acoular-24.10.dist-info}/METADATA +49 -8
- acoular-24.10.dist-info/RECORD +54 -0
- {acoular-24.5.dist-info → acoular-24.10.dist-info}/WHEEL +1 -1
- acoular-24.5.dist-info/RECORD +0 -50
- {acoular-24.5.dist-info → acoular-24.10.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.5.dist-info → acoular-24.10.dist-info}/licenses/LICENSE +0 -0
acoular/fprocess.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) Acoular Development Team.
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
4
|
+
"""Implements blockwise processing methods in the frequency domain.
|
|
5
|
+
|
|
6
|
+
.. autosummary::
|
|
7
|
+
:toctree: generated/
|
|
8
|
+
|
|
9
|
+
RFFT
|
|
10
|
+
IRFFT
|
|
11
|
+
AutoPowerSpectra
|
|
12
|
+
CrossPowerSpectra
|
|
13
|
+
FFTSpectra
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from warnings import warn
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
from scipy import fft
|
|
20
|
+
from traits.api import Bool, CArray, Enum, Instance, Int, Property, Trait, Union, cached_property
|
|
21
|
+
|
|
22
|
+
from .base import SamplesGenerator, SpectraGenerator, SpectraOut, TimeOut
|
|
23
|
+
from .fastFuncs import calcCSM
|
|
24
|
+
from .internal import digest
|
|
25
|
+
from .spectra import BaseSpectra
|
|
26
|
+
from .tools.utils import SamplesBuffer
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RFFT(BaseSpectra, SpectraOut):
|
|
30
|
+
"""Provides the one-sided Fast Fourier Transform (FFT) for real-valued multichannel time data.
|
|
31
|
+
|
|
32
|
+
The FFT is calculated block-wise, i.e. the input data is divided into blocks of length
|
|
33
|
+
:attr:`block_size` and the FFT is calculated for each block. Optionally, a window function
|
|
34
|
+
can be applied to the data before the FFT calculation via the :attr:`window` attribute.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
#: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
|
|
38
|
+
source = Instance(SamplesGenerator)
|
|
39
|
+
|
|
40
|
+
#: Number of workers to use for the FFT calculation. If negative values are used,
|
|
41
|
+
#: all available logical CPUs will be considered (``scipy.fft.rfft`` implementation wraps around from ``os.cpu_count()``).
|
|
42
|
+
#: Default is `None` (handled by scipy)
|
|
43
|
+
workers = Union(Int(), None, default_value=None, desc='number of workers to use')
|
|
44
|
+
|
|
45
|
+
#: Scaling method, either 'amplitude', 'energy' or :code:`none`.
|
|
46
|
+
#: Default is :code:`none`.
|
|
47
|
+
#: 'energy': compensates for the energy loss due to truncation of the FFT result. The resulting
|
|
48
|
+
#: one-sided spectrum is multiplied by 2.0, except for the DC and Nyquist frequency.
|
|
49
|
+
#: 'amplitude': scales the one-sided spectrum so that the amplitude of discrete tones does not depend
|
|
50
|
+
#: on the block size.
|
|
51
|
+
scaling = Enum('none', 'energy', 'amplitude')
|
|
52
|
+
|
|
53
|
+
#: block size of the FFT. Default is 1024.
|
|
54
|
+
block_size = Property()
|
|
55
|
+
|
|
56
|
+
#: Number of frequencies in the output.
|
|
57
|
+
numfreqs = Property(depends_on='_block_size')
|
|
58
|
+
|
|
59
|
+
#: Number of snapshots in the output.
|
|
60
|
+
numsamples = Property(depends_on='source.numsamples, _block_size')
|
|
61
|
+
|
|
62
|
+
#: 1-D array of FFT sample frequencies.
|
|
63
|
+
freqs = Property()
|
|
64
|
+
|
|
65
|
+
# internal block size variable
|
|
66
|
+
_block_size = Int(1024, desc='block size of the FFT')
|
|
67
|
+
|
|
68
|
+
# internal identifier
|
|
69
|
+
digest = Property(depends_on=['source.digest', 'scaling', 'precision', '_block_size', 'window', 'overlap'])
|
|
70
|
+
|
|
71
|
+
@cached_property
|
|
72
|
+
def _get_digest(self):
|
|
73
|
+
return digest(self)
|
|
74
|
+
|
|
75
|
+
@cached_property
|
|
76
|
+
def _get_numfreqs(self):
|
|
77
|
+
return int(self.block_size / 2 + 1)
|
|
78
|
+
|
|
79
|
+
@cached_property
|
|
80
|
+
def _get_numsamples(self):
|
|
81
|
+
if self.source.numsamples >= 0:
|
|
82
|
+
return int(np.floor(self.source.numsamples / self.block_size))
|
|
83
|
+
return -1
|
|
84
|
+
|
|
85
|
+
def _get_block_size(self):
|
|
86
|
+
return self._block_size
|
|
87
|
+
|
|
88
|
+
def _set_block_size(self, value):
|
|
89
|
+
if value % 2 != 0:
|
|
90
|
+
msg = 'Block size must be even'
|
|
91
|
+
raise ValueError(msg)
|
|
92
|
+
self._block_size = value
|
|
93
|
+
|
|
94
|
+
def _scale(self, data, scaling_value):
|
|
95
|
+
"""Corrects the energy of the one-sided FFT data."""
|
|
96
|
+
if self.scaling == 'amplitude' or self.scaling == 'energy':
|
|
97
|
+
data[1:-1] *= 2.0
|
|
98
|
+
data *= scaling_value
|
|
99
|
+
return data
|
|
100
|
+
|
|
101
|
+
def _get_freqs(self):
|
|
102
|
+
"""Return the Discrete Fourier Transform sample frequencies.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
f : ndarray
|
|
107
|
+
1-D Array of length *block_size/2+1* containing the sample frequencies.
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
if self.source is not None:
|
|
111
|
+
return abs(fft.fftfreq(self.block_size, 1.0 / self.source.sample_freq)[: int(self.block_size / 2 + 1)])
|
|
112
|
+
return np.array([])
|
|
113
|
+
|
|
114
|
+
def result(self, num=1):
|
|
115
|
+
"""Python generator that yields the output block-wise.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
num : integer
|
|
120
|
+
This parameter defines the number of multi-channel spectra (i.e. snapshots) per block
|
|
121
|
+
returned by the generator.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
Spectra block of shape (num, :attr:`numchannels`*:attr:`numfreqs`).
|
|
126
|
+
The last block may be shorter than num.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
wind = self.window_(self.block_size)
|
|
130
|
+
if self.scaling == 'none' or self.scaling == 'energy': # only compensate for the window
|
|
131
|
+
svalue = 1 / np.sqrt(np.dot(wind, wind) / self.block_size)
|
|
132
|
+
elif self.scaling == 'amplitude': # compensates for the window and the energy loss
|
|
133
|
+
svalue = 1 / wind.sum()
|
|
134
|
+
wind = wind[:, np.newaxis]
|
|
135
|
+
fftdata = np.zeros((num, self.numchannels * self.numfreqs), dtype=self.precision)
|
|
136
|
+
j = 0
|
|
137
|
+
for i, data in enumerate(self._get_source_data()): # yields one block of time data
|
|
138
|
+
j = i % num
|
|
139
|
+
fftdata[j] = self._scale(
|
|
140
|
+
fft.rfft(data * wind, n=self.block_size, axis=0, workers=self.workers).astype(self.precision),
|
|
141
|
+
scaling_value=svalue,
|
|
142
|
+
).reshape(-1)
|
|
143
|
+
if j == num - 1:
|
|
144
|
+
yield fftdata
|
|
145
|
+
if j < num - 1: # yield remaining fft spectra
|
|
146
|
+
yield fftdata[: j + 1]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class IRFFT(TimeOut):
|
|
150
|
+
"""Calculates the inverse Fast Fourier Transform (IFFT) for one-sided multi-channel spectra."""
|
|
151
|
+
|
|
152
|
+
#: Data source; :class:`~acoular.base.SpectraGenerator` or derived object.
|
|
153
|
+
source = Instance(SpectraGenerator)
|
|
154
|
+
|
|
155
|
+
#: Number of workers to use for the FFT calculation. If negative values are used,
|
|
156
|
+
#: all available logical CPUs will be considered (``scipy.fft.rfft`` implementation wraps around from ``os.cpu_count()``).
|
|
157
|
+
#: Default is `None` (handled by scipy)
|
|
158
|
+
workers = Union(Int(), None, default_value=None, desc='number of workers to use')
|
|
159
|
+
|
|
160
|
+
#: The floating-number-precision of the resulting time signals, corresponding to numpy dtypes.
|
|
161
|
+
#: Default is 64 bit.
|
|
162
|
+
precision = Trait('float64', 'float32', desc='precision of the time signal after the ifft')
|
|
163
|
+
|
|
164
|
+
#: Number of time samples in the output.
|
|
165
|
+
numsamples = Property(depends_on='source.numsamples, source._block_size')
|
|
166
|
+
|
|
167
|
+
# internal time signal buffer to handle arbitrary output block sizes
|
|
168
|
+
_buffer = CArray(desc='signal buffer')
|
|
169
|
+
|
|
170
|
+
# internal identifier
|
|
171
|
+
digest = Property(depends_on=['source.digest', 'scaling', 'precision', '_block_size', 'window', 'overlap'])
|
|
172
|
+
|
|
173
|
+
def _get_numsamples(self):
|
|
174
|
+
if self.source.numsamples >= 0:
|
|
175
|
+
return int(self.source.numsamples * self.source.block_size)
|
|
176
|
+
return -1
|
|
177
|
+
|
|
178
|
+
@cached_property
|
|
179
|
+
def _get_digest(self):
|
|
180
|
+
return digest(self)
|
|
181
|
+
|
|
182
|
+
def _validate(self):
|
|
183
|
+
if not self.source.block_size or self.source.block_size < 0:
|
|
184
|
+
msg = (
|
|
185
|
+
f'Source of class {self.__class__.__name__} has an unknown blocksize: {self.source.block_size}.'
|
|
186
|
+
'This is likely due to incomplete spectral data from which the inverse FFT cannot be calculated.'
|
|
187
|
+
)
|
|
188
|
+
raise ValueError(msg)
|
|
189
|
+
if (self.source.numfreqs - 1) * 2 != self.source.block_size:
|
|
190
|
+
msg = (
|
|
191
|
+
f'Block size must be 2*(numfreqs-1) but is {self.source.block_size}.'
|
|
192
|
+
'This is likely due to incomplete spectral data from which the inverse FFT cannot be calculated.'
|
|
193
|
+
)
|
|
194
|
+
raise ValueError(msg)
|
|
195
|
+
if self.source.block_size % 2 != 0:
|
|
196
|
+
msg = f'Block size must be even but is {self.source.block_size}.'
|
|
197
|
+
raise ValueError(msg)
|
|
198
|
+
|
|
199
|
+
def result(self, num):
|
|
200
|
+
"""Python generator that yields the output block-wise.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
num : integer
|
|
205
|
+
This parameter defines the size of the blocks to be yielded
|
|
206
|
+
(i.e. the number of samples per block). The last block may be shorter than num.
|
|
207
|
+
|
|
208
|
+
Yields
|
|
209
|
+
------
|
|
210
|
+
numpy.ndarray
|
|
211
|
+
Yields blocks of shape (num, numchannels).
|
|
212
|
+
"""
|
|
213
|
+
self._validate()
|
|
214
|
+
bs = self.source.block_size
|
|
215
|
+
if num != bs:
|
|
216
|
+
buffer_length = (int(np.ceil(num / bs)) + 1) * bs
|
|
217
|
+
buffer = SamplesBuffer(source=self, source_num=bs, length=buffer_length, dtype=self.precision)
|
|
218
|
+
yield from buffer.result(num)
|
|
219
|
+
else:
|
|
220
|
+
for spectra in self.source.result(1):
|
|
221
|
+
yield fft.irfft(
|
|
222
|
+
spectra.reshape(self.source.numfreqs, self.numchannels), n=num, axis=0, workers=self.workers
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class AutoPowerSpectra(SpectraOut):
|
|
227
|
+
"""Calculates the real-valued auto-power spectra."""
|
|
228
|
+
|
|
229
|
+
#: Data source; :class:`~acoular.base.SpectraGenerator` or derived object.
|
|
230
|
+
source = Instance(SpectraGenerator)
|
|
231
|
+
|
|
232
|
+
#: Scaling method, either 'power' or 'psd' (Power Spectral Density).
|
|
233
|
+
#: Only relevant if the source is a :class:`~acoular.fprocess.FreqInOut` object.
|
|
234
|
+
scaling = Enum('power', 'psd')
|
|
235
|
+
|
|
236
|
+
#: Determines if the spectra yielded by the source are single-sided spectra.
|
|
237
|
+
single_sided = Bool(True, desc='single sided spectrum')
|
|
238
|
+
|
|
239
|
+
#: The floating-number-precision of entries, corresponding to numpy dtypes. Default is 64 bit.
|
|
240
|
+
precision = Trait('float64', 'float32', desc='floating-number-precision')
|
|
241
|
+
|
|
242
|
+
# internal identifier
|
|
243
|
+
digest = Property(depends_on=['source.digest', 'precision', 'scaling', 'single_sided'])
|
|
244
|
+
|
|
245
|
+
@cached_property
|
|
246
|
+
def _get_digest(self):
|
|
247
|
+
return digest(self)
|
|
248
|
+
|
|
249
|
+
def _get_scaling_value(self):
|
|
250
|
+
scale = 1 / self.block_size**2
|
|
251
|
+
if self.single_sided:
|
|
252
|
+
scale *= 2
|
|
253
|
+
if self.scaling == 'psd':
|
|
254
|
+
scale *= self.block_size * self.source.sample_freq
|
|
255
|
+
return scale
|
|
256
|
+
|
|
257
|
+
def result(self, num=1):
|
|
258
|
+
"""Python generator that yields the real-valued auto-power spectra.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
num : integer
|
|
263
|
+
This parameter defines the number of snapshots within each output data block.
|
|
264
|
+
|
|
265
|
+
Yields
|
|
266
|
+
------
|
|
267
|
+
numpy.ndarray
|
|
268
|
+
Yields blocks of shape (num, numchannels*numfreqs).
|
|
269
|
+
The last block may be shorter than num.
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
scale = self._get_scaling_value()
|
|
273
|
+
for temp in self.source.result(num):
|
|
274
|
+
yield ((temp * temp.conjugate()).real * scale).astype(self.precision)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class CrossPowerSpectra(AutoPowerSpectra):
|
|
278
|
+
"""Calculates the complex-valued auto- and cross-power spectra.
|
|
279
|
+
|
|
280
|
+
Receives the complex-valued spectra from the source and returns the cross-spectral matrix (CSM) in a flattened
|
|
281
|
+
representation (i.e. the auto- and cross-power spectra are concatenated along the last axis).
|
|
282
|
+
If :attr:`calc_mode` is 'full', the full CSM is calculated, if 'upper', only the upper triangle is calculated.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
#: Data source; :class:`~acoular.base.SpectraGenerator` or derived object.
|
|
286
|
+
source = Trait(SpectraGenerator)
|
|
287
|
+
|
|
288
|
+
#: The floating-number-precision of entries of csm, eigenvalues and
|
|
289
|
+
#: eigenvectors, corresponding to numpy dtypes. Default is 64 bit.
|
|
290
|
+
precision = Trait('complex128', 'complex64', desc='precision of the fft')
|
|
291
|
+
|
|
292
|
+
#: Calculation mode, either 'full' or 'upper'.
|
|
293
|
+
#: 'full' calculates the full cross-spectral matrix, 'upper' calculates
|
|
294
|
+
# only the upper triangle. Default is 'full'.
|
|
295
|
+
calc_mode = Trait('full', 'upper', 'lower', desc='calculation mode')
|
|
296
|
+
|
|
297
|
+
#: Number of channels in output. If :attr:`calc_mode` is 'full', then
|
|
298
|
+
#: :attr:`numchannels` is :math:`n^2`, where :math:`n` is the number of
|
|
299
|
+
#: channels in the input. If :attr:`calc_mode` is 'upper', then
|
|
300
|
+
#: :attr:`numchannels` is :math:`n + n(n-1)/2`.
|
|
301
|
+
numchannels = Property(depends_on='source.numchannels')
|
|
302
|
+
|
|
303
|
+
# internal identifier
|
|
304
|
+
digest = Property(depends_on=['source.digest', 'precision', 'scaling', 'single_sided', 'calc_mode'])
|
|
305
|
+
|
|
306
|
+
@cached_property
|
|
307
|
+
def _get_numchannels(self):
|
|
308
|
+
n = self.source.numchannels
|
|
309
|
+
return n**2 if self.calc_mode == 'full' else int(n + n * (n - 1) / 2)
|
|
310
|
+
|
|
311
|
+
@cached_property
|
|
312
|
+
def _get_digest(self):
|
|
313
|
+
return digest(self)
|
|
314
|
+
|
|
315
|
+
def result(self, num=1):
|
|
316
|
+
"""Python generator that yields the output block-wise.
|
|
317
|
+
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
num : integer
|
|
321
|
+
This parameter defines the size of the blocks to be yielded
|
|
322
|
+
(i.e. the number of samples per block).
|
|
323
|
+
|
|
324
|
+
Yields
|
|
325
|
+
------
|
|
326
|
+
numpy.ndarray
|
|
327
|
+
Yields blocks of shape (num, numchannels*numfreq).
|
|
328
|
+
"""
|
|
329
|
+
nc_src = self.source.numchannels
|
|
330
|
+
nc = self.numchannels
|
|
331
|
+
nf = self.numfreqs
|
|
332
|
+
scale = self._get_scaling_value()
|
|
333
|
+
|
|
334
|
+
csm_flat = np.zeros((num, nc * nf), dtype=self.precision)
|
|
335
|
+
csm_upper = np.zeros((nf, nc_src, nc_src), dtype=self.precision)
|
|
336
|
+
for data in self.source.result(num):
|
|
337
|
+
for i in range(data.shape[0]):
|
|
338
|
+
calcCSM(csm_upper, data[i].astype(self.precision).reshape(nf, nc_src))
|
|
339
|
+
if self.calc_mode == 'full':
|
|
340
|
+
csm_lower = csm_upper.conj().transpose(0, 2, 1)
|
|
341
|
+
[np.fill_diagonal(csm_lower[cntFreq, :, :], 0) for cntFreq in range(csm_lower.shape[0])]
|
|
342
|
+
csm_flat[i] = (csm_lower + csm_upper).reshape(-1)
|
|
343
|
+
elif self.calc_mode == 'upper':
|
|
344
|
+
csm_flat[i] = csm_upper[:, :nc].reshape(-1)
|
|
345
|
+
else: # lower
|
|
346
|
+
csm_lower = csm_upper.conj().transpose(0, 2, 1)
|
|
347
|
+
csm_flat[i] = csm_lower[:, :nc].reshape(-1)
|
|
348
|
+
csm_upper[...] = 0 # calcCSM adds cummulative
|
|
349
|
+
yield csm_flat[: i + 1] * scale
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class FFTSpectra(RFFT):
|
|
353
|
+
"""Provides the one-sided Fast Fourier Transform (FFT) for multichannel time data.
|
|
354
|
+
|
|
355
|
+
Alias for :class:`~acoular.fprocess.RFFT`.
|
|
356
|
+
|
|
357
|
+
.. deprecated:: 24.10
|
|
358
|
+
Using :class:`~acoular.fprocess.FFTSpectra` is deprecated and will be removed in Acoular
|
|
359
|
+
version 25.07. Use :class:`~acoular.fprocess.RFFT` instead.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __init__(self, *args, **kwargs):
|
|
363
|
+
super().__init__(*args, **kwargs)
|
|
364
|
+
warn(
|
|
365
|
+
'Using FFTSpectra is deprecated and will be removed in Acoular version 25.07. Use class RFFT instead.',
|
|
366
|
+
DeprecationWarning,
|
|
367
|
+
stacklevel=2,
|
|
368
|
+
)
|
acoular/grids.py
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
# imports from other packages
|
|
27
27
|
from os import path
|
|
28
|
+
from warnings import warn
|
|
28
29
|
|
|
29
30
|
from numpy import (
|
|
30
31
|
absolute,
|
|
@@ -32,7 +33,7 @@ from numpy import (
|
|
|
32
33
|
arange,
|
|
33
34
|
argmin,
|
|
34
35
|
array,
|
|
35
|
-
|
|
36
|
+
asarray,
|
|
36
37
|
concatenate,
|
|
37
38
|
copysign,
|
|
38
39
|
fabs,
|
|
@@ -50,12 +51,11 @@ from numpy import (
|
|
|
50
51
|
where,
|
|
51
52
|
zeros,
|
|
52
53
|
)
|
|
53
|
-
from
|
|
54
|
+
from scipy.linalg import norm
|
|
54
55
|
|
|
55
56
|
# from matplotlib.path import Path
|
|
56
57
|
from scipy.spatial import Delaunay
|
|
57
58
|
from traits.api import (
|
|
58
|
-
Any,
|
|
59
59
|
Bool,
|
|
60
60
|
CArray,
|
|
61
61
|
File,
|
|
@@ -66,6 +66,7 @@ from traits.api import (
|
|
|
66
66
|
List,
|
|
67
67
|
Property,
|
|
68
68
|
Tuple,
|
|
69
|
+
Union,
|
|
69
70
|
cached_property,
|
|
70
71
|
on_trait_change,
|
|
71
72
|
property_depends_on,
|
|
@@ -110,8 +111,8 @@ def _det(xvert, yvert):
|
|
|
110
111
|
all points are collinear.
|
|
111
112
|
|
|
112
113
|
"""
|
|
113
|
-
xvert =
|
|
114
|
-
yvert =
|
|
114
|
+
xvert = asarray(xvert, dtype=float)
|
|
115
|
+
yvert = asarray(yvert, dtype=float)
|
|
115
116
|
x_prev = concatenate(([xvert[-1]], xvert[:-1]))
|
|
116
117
|
y_prev = concatenate(([yvert[-1]], yvert[:-1]))
|
|
117
118
|
return sum(yvert * x_prev - xvert * y_prev, axis=0)
|
|
@@ -128,8 +129,8 @@ class Polygon:
|
|
|
128
129
|
if len(x) != len(y):
|
|
129
130
|
msg = 'x and y must be equally sized.'
|
|
130
131
|
raise IndexError(msg)
|
|
131
|
-
self.x =
|
|
132
|
-
self.y =
|
|
132
|
+
self.x = asarray(x, dtype=float)
|
|
133
|
+
self.y = asarray(y, dtype=float)
|
|
133
134
|
# Closes the polygon if were open
|
|
134
135
|
x1, y1 = x[0], y[0]
|
|
135
136
|
xn, yn = x[-1], y[-1]
|
|
@@ -166,8 +167,8 @@ class Polygon:
|
|
|
166
167
|
Software, Vol 7, No. 1, pp 45-47.
|
|
167
168
|
|
|
168
169
|
"""
|
|
169
|
-
xpoint =
|
|
170
|
-
ypoint =
|
|
170
|
+
xpoint = asarray(xpoint, dtype=float)
|
|
171
|
+
ypoint = asarray(ypoint, dtype=float)
|
|
171
172
|
# Scalar to array
|
|
172
173
|
if xpoint.shape == ():
|
|
173
174
|
xpoint = array([xpoint], dtype=float)
|
|
@@ -296,6 +297,7 @@ class Grid(HasPrivateTraits):
|
|
|
296
297
|
def pos(self):
|
|
297
298
|
"""Calculates grid co-ordinates.
|
|
298
299
|
Deprecated; use :attr:`gpos` attribute instead.
|
|
300
|
+
The :meth:`pos` method will be removed in version 25.01.
|
|
299
301
|
|
|
300
302
|
Returns
|
|
301
303
|
-------
|
|
@@ -303,6 +305,10 @@ class Grid(HasPrivateTraits):
|
|
|
303
305
|
The grid point x, y, z-coordinates in one array.
|
|
304
306
|
|
|
305
307
|
"""
|
|
308
|
+
msg = (
|
|
309
|
+
"The 'pos' method is deprecated and will be removed in version 25.01. " "Use the 'gpos' attribute instead."
|
|
310
|
+
)
|
|
311
|
+
warn(msg, DeprecationWarning, stacklevel=2)
|
|
306
312
|
return self.gpos # array([[0.], [0.], [0.]])
|
|
307
313
|
|
|
308
314
|
def subdomain(self, sector):
|
|
@@ -535,7 +541,7 @@ class RectGrid3D(RectGrid):
|
|
|
535
541
|
nzsteps = Property(desc='number of grid points along x-axis')
|
|
536
542
|
|
|
537
543
|
# Private trait for increment handling
|
|
538
|
-
_increment =
|
|
544
|
+
_increment = Union(Float(), CArray(shape=(3,), dtype=float), default_value=0.1, desc='step size')
|
|
539
545
|
|
|
540
546
|
#: The cell side length for the grid. This can either be a scalar (same
|
|
541
547
|
#: increments in all 3 dimensions) or a (3,) array of floats with
|
|
@@ -550,8 +556,8 @@ class RectGrid3D(RectGrid):
|
|
|
550
556
|
if isscalar(increment):
|
|
551
557
|
try:
|
|
552
558
|
self._increment = absolute(float(increment))
|
|
553
|
-
except:
|
|
554
|
-
raise TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment)
|
|
559
|
+
except ValueError as ve:
|
|
560
|
+
raise TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment) from ve
|
|
555
561
|
elif len(increment) == 3:
|
|
556
562
|
self._increment = array(increment, dtype=float)
|
|
557
563
|
else:
|
|
@@ -559,14 +565,18 @@ class RectGrid3D(RectGrid):
|
|
|
559
565
|
|
|
560
566
|
# Respective increments in x,y, and z-direction (in m).
|
|
561
567
|
# Deprecated: Use :attr:`~RectGrid.increment` for this functionality
|
|
562
|
-
increment3D = Property(desc='3D step sizes')
|
|
568
|
+
increment3D = Property(desc='3D step sizes') # noqa N815
|
|
563
569
|
|
|
564
|
-
def _get_increment3D(self):
|
|
570
|
+
def _get_increment3D(self): # noqa N802
|
|
571
|
+
msg = "Using 'increment3D' is deprecated and will be removed in version 25.01." "Use 'increment' instead."
|
|
572
|
+
warn(msg, DeprecationWarning, stacklevel=2)
|
|
565
573
|
if isscalar(self._increment):
|
|
566
574
|
return array([self._increment, self._increment, self._increment])
|
|
567
575
|
return self._increment
|
|
568
576
|
|
|
569
|
-
def _set_increment3D(self, inc):
|
|
577
|
+
def _set_increment3D(self, inc): # noqa N802
|
|
578
|
+
msg = "Using 'increment3D' is deprecated and will be removed in version 25.01." "Use 'increment' instead."
|
|
579
|
+
warn(msg, DeprecationWarning, stacklevel=2)
|
|
570
580
|
if not isscalar(inc) and len(inc) == 3:
|
|
571
581
|
self._increment = array(inc, dtype=float)
|
|
572
582
|
else:
|
|
@@ -587,21 +597,21 @@ class RectGrid3D(RectGrid):
|
|
|
587
597
|
|
|
588
598
|
@property_depends_on('x_min, x_max, _increment')
|
|
589
599
|
def _get_nxsteps(self):
|
|
590
|
-
i = abs(self.
|
|
600
|
+
i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[0])
|
|
591
601
|
if i != 0:
|
|
592
602
|
return int(round((abs(self.x_max - self.x_min) + i) / i))
|
|
593
603
|
return 1
|
|
594
604
|
|
|
595
605
|
@property_depends_on('y_min, y_max, _increment')
|
|
596
606
|
def _get_nysteps(self):
|
|
597
|
-
i = abs(self.
|
|
607
|
+
i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[1])
|
|
598
608
|
if i != 0:
|
|
599
609
|
return int(round((abs(self.y_max - self.y_min) + i) / i))
|
|
600
610
|
return 1
|
|
601
611
|
|
|
602
612
|
@property_depends_on('z_min, z_max, _increment')
|
|
603
613
|
def _get_nzsteps(self):
|
|
604
|
-
i = abs(self.
|
|
614
|
+
i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[2])
|
|
605
615
|
if i != 0:
|
|
606
616
|
return int(round((abs(self.z_max - self.z_min) + i) / i))
|
|
607
617
|
return 1
|
|
@@ -655,9 +665,13 @@ class RectGrid3D(RectGrid):
|
|
|
655
665
|
if z < self.z_min or z > self.z_max:
|
|
656
666
|
msg = f'z-value out of range {z:f} ({self.z_min:f}, {self.z_max:f})'
|
|
657
667
|
raise ValueError(msg)
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
668
|
+
if isscalar(self.increment):
|
|
669
|
+
incx = incy = incz = self.increment
|
|
670
|
+
else:
|
|
671
|
+
incx, incy, incz = self.increment
|
|
672
|
+
xi = int(round((x - self.x_min) / incx))
|
|
673
|
+
yi = int(round((y - self.y_min) / incy))
|
|
674
|
+
zi = int(round((z - self.z_min) / incz))
|
|
661
675
|
return xi, yi, zi
|
|
662
676
|
|
|
663
677
|
def indices(self, x1, y1, z1, x2, y2, z2):
|
|
@@ -792,7 +806,7 @@ class LineGrid(Grid):
|
|
|
792
806
|
def _get_gpos(self):
|
|
793
807
|
dist = self.length / (self.numpoints - 1)
|
|
794
808
|
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
795
|
-
direc_n = self.direction / norm(self.direction)
|
|
809
|
+
direc_n = array(self.direction) / norm(self.direction)
|
|
796
810
|
pos = zeros((self.numpoints, 3))
|
|
797
811
|
for s in range(self.numpoints):
|
|
798
812
|
pos[s] = loc.T + direc_n * dist * s
|
acoular/h5cache.py
CHANGED
|
@@ -13,7 +13,7 @@ from .configuration import Config, config
|
|
|
13
13
|
from .h5files import _get_cachefile_class
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class HDF5Cache(HasPrivateTraits):
|
|
17
17
|
"""Cache class that handles opening and closing 'tables.File' objects."""
|
|
18
18
|
|
|
19
19
|
config = Instance(Config)
|
|
@@ -24,23 +24,23 @@ class H5cache_class(HasPrivateTraits):
|
|
|
24
24
|
|
|
25
25
|
open_files = WeakValueDictionary()
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
open_file_reference = Dict()
|
|
28
28
|
|
|
29
29
|
def _idle_if_busy(self):
|
|
30
30
|
while self.busy:
|
|
31
31
|
pass
|
|
32
32
|
|
|
33
|
-
def open_cachefile(self,
|
|
34
|
-
|
|
35
|
-
return
|
|
33
|
+
def open_cachefile(self, filename, mode):
|
|
34
|
+
file = _get_cachefile_class()
|
|
35
|
+
return file(path.join(self.cache_dir, filename), mode)
|
|
36
36
|
|
|
37
37
|
def close_cachefile(self, cachefile):
|
|
38
|
-
self.
|
|
38
|
+
self.open_file_reference.pop(get_basename(cachefile))
|
|
39
39
|
cachefile.close()
|
|
40
40
|
|
|
41
41
|
def get_filename(self, file):
|
|
42
|
-
|
|
43
|
-
if isinstance(file,
|
|
42
|
+
file_class = _get_cachefile_class()
|
|
43
|
+
if isinstance(file, file_class):
|
|
44
44
|
return get_basename(file)
|
|
45
45
|
return 0
|
|
46
46
|
|
|
@@ -51,13 +51,13 @@ class H5cache_class(HasPrivateTraits):
|
|
|
51
51
|
return iter(self.open_files.values())
|
|
52
52
|
|
|
53
53
|
def close_unreferenced_cachefiles(self):
|
|
54
|
-
for
|
|
55
|
-
if not self.is_reference_existent(
|
|
56
|
-
# print("close unreferenced File:",get_basename(
|
|
57
|
-
self.close_cachefile(
|
|
54
|
+
for cachefile in self.get_open_cachefiles():
|
|
55
|
+
if not self.is_reference_existent(cachefile):
|
|
56
|
+
# print("close unreferenced File:",get_basename(cachefile))
|
|
57
|
+
self.close_cachefile(cachefile)
|
|
58
58
|
|
|
59
59
|
def is_reference_existent(self, file):
|
|
60
|
-
|
|
60
|
+
exist_flag = False
|
|
61
61
|
# inspect all refererres to the file object
|
|
62
62
|
gc.collect() # clear garbage before collecting referrers
|
|
63
63
|
for ref in gc.get_referrers(file):
|
|
@@ -65,41 +65,41 @@ class H5cache_class(HasPrivateTraits):
|
|
|
65
65
|
# attribute?
|
|
66
66
|
if isinstance(ref, dict) and 'h5f' in ref:
|
|
67
67
|
# file is still referred, must not be closed
|
|
68
|
-
|
|
68
|
+
exist_flag = True
|
|
69
69
|
break
|
|
70
|
-
return
|
|
70
|
+
return exist_flag
|
|
71
71
|
|
|
72
|
-
def is_cachefile_existent(self,
|
|
73
|
-
if
|
|
72
|
+
def is_cachefile_existent(self, filename):
|
|
73
|
+
if filename in listdir(self.cache_dir):
|
|
74
74
|
return True
|
|
75
75
|
return False
|
|
76
76
|
|
|
77
|
-
def _increase_file_reference_counter(self,
|
|
78
|
-
self.
|
|
77
|
+
def _increase_file_reference_counter(self, filename):
|
|
78
|
+
self.open_file_reference[filename] = self.open_file_reference.get(filename, 0) + 1
|
|
79
79
|
|
|
80
|
-
def _decrease_file_reference_counter(self,
|
|
81
|
-
self.
|
|
80
|
+
def _decrease_file_reference_counter(self, filename):
|
|
81
|
+
self.open_file_reference[filename] = self.open_file_reference[filename] - 1
|
|
82
82
|
|
|
83
83
|
def _print_open_files(self):
|
|
84
|
-
print(list(self.
|
|
84
|
+
print(list(self.open_file_reference.items()))
|
|
85
85
|
|
|
86
86
|
def get_cache_file(self, obj, basename, mode='a'):
|
|
87
87
|
"""Returns pytables .h5 file to h5f trait of calling object for caching."""
|
|
88
88
|
self._idle_if_busy() #
|
|
89
89
|
self.busy = True
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
filename = basename + '_cache.h5'
|
|
92
|
+
obj_filename = self.get_filename(obj.h5f)
|
|
93
93
|
|
|
94
|
-
if
|
|
95
|
-
if
|
|
94
|
+
if obj_filename:
|
|
95
|
+
if obj_filename == filename:
|
|
96
96
|
self.busy = False
|
|
97
97
|
return
|
|
98
|
-
self._decrease_file_reference_counter(
|
|
98
|
+
self._decrease_file_reference_counter(obj_filename)
|
|
99
99
|
|
|
100
|
-
if
|
|
100
|
+
if filename not in self.open_files: # or tables.file._open_files.filenames
|
|
101
101
|
if config.global_caching == 'readonly' and not self.is_cachefile_existent(
|
|
102
|
-
|
|
102
|
+
filename,
|
|
103
103
|
): # condition ensures that cachefile is not created in readonly mode
|
|
104
104
|
obj.h5f = None
|
|
105
105
|
self.busy = False
|
|
@@ -107,11 +107,11 @@ class H5cache_class(HasPrivateTraits):
|
|
|
107
107
|
return
|
|
108
108
|
if config.global_caching == 'readonly':
|
|
109
109
|
mode = 'r'
|
|
110
|
-
f = self.open_cachefile(
|
|
111
|
-
self.open_files[
|
|
110
|
+
f = self.open_cachefile(filename, mode)
|
|
111
|
+
self.open_files[filename] = f
|
|
112
112
|
|
|
113
|
-
obj.h5f = self.open_files[
|
|
114
|
-
self._increase_file_reference_counter(
|
|
113
|
+
obj.h5f = self.open_files[filename]
|
|
114
|
+
self._increase_file_reference_counter(filename)
|
|
115
115
|
|
|
116
116
|
# garbage collection
|
|
117
117
|
self.close_unreferenced_cachefiles()
|
|
@@ -120,7 +120,7 @@ class H5cache_class(HasPrivateTraits):
|
|
|
120
120
|
self._print_open_files()
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
H5cache =
|
|
123
|
+
H5cache = HDF5Cache(config=config)
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
def get_basename(file):
|