acoular 24.7__py3-none-any.whl → 25.1__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 +21 -9
- acoular/aiaa/__init__.py +12 -0
- acoular/{tools → aiaa}/aiaa.py +26 -31
- acoular/base.py +332 -0
- acoular/calib.py +129 -34
- acoular/configuration.py +13 -11
- acoular/demo/__init__.py +1 -0
- acoular/demo/acoular_demo.py +30 -17
- acoular/deprecation.py +85 -0
- acoular/environments.py +38 -24
- acoular/fastFuncs.py +90 -84
- acoular/fbeamform.py +342 -387
- acoular/fprocess.py +376 -0
- acoular/grids.py +122 -150
- acoular/h5cache.py +29 -40
- acoular/h5files.py +2 -6
- acoular/microphones.py +50 -59
- acoular/process.py +771 -0
- acoular/sdinput.py +35 -21
- acoular/signals.py +120 -113
- acoular/sources.py +208 -234
- acoular/spectra.py +59 -254
- acoular/tbeamform.py +280 -280
- acoular/tfastfuncs.py +21 -21
- acoular/tools/__init__.py +3 -7
- acoular/tools/helpers.py +218 -4
- acoular/tools/metrics.py +5 -5
- acoular/tools/utils.py +116 -0
- acoular/tprocess.py +416 -741
- acoular/traitsviews.py +15 -13
- acoular/trajectory.py +7 -10
- acoular/version.py +2 -2
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/METADATA +63 -21
- acoular-25.1.dist-info/RECORD +56 -0
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/WHEEL +1 -1
- acoular-24.7.dist-info/RECORD +0 -50
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/licenses/LICENSE +0 -0
acoular/tfastfuncs.py
CHANGED
|
@@ -41,14 +41,14 @@ def _delayandsum4(data, offsets, ifactor2, steeramp, out, autopower):
|
|
|
41
41
|
None : as the inputs out and autopower get overwritten.
|
|
42
42
|
|
|
43
43
|
"""
|
|
44
|
-
gridsize,
|
|
44
|
+
gridsize, num_channels = offsets.shape
|
|
45
45
|
num = out.shape[0]
|
|
46
46
|
zero_constant = data.dtype.type(0.0)
|
|
47
47
|
for n in nb.prange(num):
|
|
48
48
|
for gi in nb.prange(gridsize):
|
|
49
49
|
out[n, gi] = zero_constant
|
|
50
50
|
autopower[n, gi] = zero_constant
|
|
51
|
-
for mi in range(
|
|
51
|
+
for mi in range(num_channels):
|
|
52
52
|
ind = (gi, mi)
|
|
53
53
|
r = (
|
|
54
54
|
data[offsets[ind] + n, mi] * (1.0 - ifactor2[ind]) + data[offsets[ind] + n + 1, mi] * ifactor2[ind]
|
|
@@ -99,7 +99,7 @@ def _delayandsum5(data, offsets, ifactor2, steeramp, out, autopower):
|
|
|
99
99
|
None : as the inputs out and autopower get overwritten.
|
|
100
100
|
|
|
101
101
|
"""
|
|
102
|
-
num, gridsize,
|
|
102
|
+
num, gridsize, num_channels = offsets.shape
|
|
103
103
|
num = out.shape[0]
|
|
104
104
|
# ZERO = data.dtype.type(0.)
|
|
105
105
|
one_constant = data.dtype.type(1.0)
|
|
@@ -107,7 +107,7 @@ def _delayandsum5(data, offsets, ifactor2, steeramp, out, autopower):
|
|
|
107
107
|
for gi in nb.prange(gridsize):
|
|
108
108
|
out[n, gi] = 0
|
|
109
109
|
autopower[n, gi] = 0
|
|
110
|
-
for mi in range(
|
|
110
|
+
for mi in range(num_channels):
|
|
111
111
|
ind = offsets[n, gi, mi] + n
|
|
112
112
|
r = (
|
|
113
113
|
data[ind, mi] * (one_constant - ifactor2[n, gi, mi]) + data[ind + 1, mi] * ifactor2[n, gi, mi]
|
|
@@ -130,12 +130,12 @@ def _delayandsum5(data, offsets, ifactor2, steeramp, out, autopower):
|
|
|
130
130
|
fastmath=True,
|
|
131
131
|
)
|
|
132
132
|
def _steer_I(rm, r0, amp): # noqa: ARG001, N802
|
|
133
|
-
num, gridsize,
|
|
134
|
-
amp[0, 0, 0] = 1.0 /
|
|
133
|
+
num, gridsize, num_channels = rm.shape
|
|
134
|
+
amp[0, 0, 0] = 1.0 / num_channels # to get the same type for rm2 as for rm
|
|
135
135
|
nr = amp[0, 0, 0]
|
|
136
136
|
for n in nb.prange(num):
|
|
137
137
|
for gi in nb.prange(gridsize):
|
|
138
|
-
for mi in nb.prange(
|
|
138
|
+
for mi in nb.prange(num_channels):
|
|
139
139
|
amp[n, gi, mi] = nr
|
|
140
140
|
|
|
141
141
|
|
|
@@ -149,13 +149,13 @@ def _steer_I(rm, r0, amp): # noqa: ARG001, N802
|
|
|
149
149
|
fastmath=True,
|
|
150
150
|
)
|
|
151
151
|
def _steer_II(rm, r0, amp): # noqa: N802
|
|
152
|
-
num, gridsize,
|
|
153
|
-
amp[0, 0, 0] = 1.0 /
|
|
152
|
+
num, gridsize, num_channels = rm.shape
|
|
153
|
+
amp[0, 0, 0] = 1.0 / num_channels # to get the same type for rm2 as for rm
|
|
154
154
|
nr = amp[0, 0, 0]
|
|
155
155
|
for n in nb.prange(num):
|
|
156
156
|
for gi in nb.prange(gridsize):
|
|
157
157
|
rm2 = np.divide(nr, r0[n, gi])
|
|
158
|
-
for mi in nb.prange(
|
|
158
|
+
for mi in nb.prange(num_channels):
|
|
159
159
|
amp[n, gi, mi] = rm[n, gi, mi] * rm2
|
|
160
160
|
|
|
161
161
|
|
|
@@ -169,16 +169,16 @@ def _steer_II(rm, r0, amp): # noqa: N802
|
|
|
169
169
|
fastmath=True,
|
|
170
170
|
)
|
|
171
171
|
def _steer_III(rm, r0, amp): # noqa: N802
|
|
172
|
-
num, gridsize,
|
|
172
|
+
num, gridsize, num_channels = rm.shape
|
|
173
173
|
rm20 = rm[0, 0, 0] - rm[0, 0, 0] # to get the same type for rm2 as for rm
|
|
174
174
|
rm1 = rm[0, 0, 0] / rm[0, 0, 0]
|
|
175
175
|
for n in nb.prange(num):
|
|
176
176
|
for gi in nb.prange(gridsize):
|
|
177
177
|
rm2 = rm20
|
|
178
|
-
for mi in nb.prange(
|
|
178
|
+
for mi in nb.prange(num_channels):
|
|
179
179
|
rm2 += np.divide(rm1, np.square(rm[n, gi, mi]))
|
|
180
180
|
rm2 *= r0[n, gi]
|
|
181
|
-
for mi in nb.prange(
|
|
181
|
+
for mi in nb.prange(num_channels):
|
|
182
182
|
amp[n, gi, mi] = np.divide(rm1, rm[n, gi, mi] * rm2)
|
|
183
183
|
|
|
184
184
|
|
|
@@ -192,18 +192,18 @@ def _steer_III(rm, r0, amp): # noqa: N802
|
|
|
192
192
|
fastmath=True,
|
|
193
193
|
)
|
|
194
194
|
def _steer_IV(rm, r0, amp): # noqa: ARG001, N802
|
|
195
|
-
num, gridsize,
|
|
196
|
-
amp[0, 0, 0] = np.sqrt(1.0 /
|
|
195
|
+
num, gridsize, num_channels = rm.shape
|
|
196
|
+
amp[0, 0, 0] = np.sqrt(1.0 / num_channels) # to get the same type for rm2 as for rm
|
|
197
197
|
nr = amp[0, 0, 0]
|
|
198
198
|
rm1 = rm[0, 0, 0] / rm[0, 0, 0]
|
|
199
199
|
rm20 = rm[0, 0, 0] - rm[0, 0, 0] # to get the same type for rm2 as for rm
|
|
200
200
|
for n in nb.prange(num):
|
|
201
201
|
for gi in nb.prange(gridsize):
|
|
202
202
|
rm2 = rm20
|
|
203
|
-
for mi in nb.prange(
|
|
203
|
+
for mi in nb.prange(num_channels):
|
|
204
204
|
rm2 += np.divide(rm1, np.square(rm[n, gi, mi]))
|
|
205
205
|
rm2 = np.sqrt(rm2)
|
|
206
|
-
for mi in nb.prange(
|
|
206
|
+
for mi in nb.prange(num_channels):
|
|
207
207
|
amp[n, gi, mi] = np.divide(nr, rm[n, gi, mi] * rm2)
|
|
208
208
|
|
|
209
209
|
|
|
@@ -217,12 +217,12 @@ def _steer_IV(rm, r0, amp): # noqa: ARG001, N802
|
|
|
217
217
|
fastmath=True,
|
|
218
218
|
)
|
|
219
219
|
def _delays(rm, c, interp2, index):
|
|
220
|
-
num, gridsize,
|
|
220
|
+
num, gridsize, num_channels = rm.shape
|
|
221
221
|
invc = 1 / c
|
|
222
222
|
intt = index.dtype.type
|
|
223
223
|
for n in nb.prange(num):
|
|
224
224
|
for gi in nb.prange(gridsize):
|
|
225
|
-
for mi in nb.prange(
|
|
225
|
+
for mi in nb.prange(num_channels):
|
|
226
226
|
delays = invc * rm[n, gi, mi]
|
|
227
227
|
index[n, gi, mi] = intt(delays)
|
|
228
228
|
interp2[n, gi, mi] = delays - nb.int64(delays)
|
|
@@ -238,10 +238,10 @@ def _delays(rm, c, interp2, index):
|
|
|
238
238
|
fastmath=True,
|
|
239
239
|
)
|
|
240
240
|
def _modf(delays, interp2, index):
|
|
241
|
-
num, gridsize,
|
|
241
|
+
num, gridsize, num_channels = delays.shape
|
|
242
242
|
for n in nb.prange(num):
|
|
243
243
|
for gi in nb.prange(gridsize):
|
|
244
|
-
for mi in nb.prange(
|
|
244
|
+
for mi in nb.prange(num_channels):
|
|
245
245
|
index[n, gi, mi] = int(delays[n, gi, mi])
|
|
246
246
|
interp2[n, gi, mi] = delays[n, gi, mi] - index[n, gi, mi]
|
|
247
247
|
|
acoular/tools/__init__.py
CHANGED
|
@@ -6,20 +6,16 @@
|
|
|
6
6
|
.. autosummary::
|
|
7
7
|
:toctree: generated/
|
|
8
8
|
|
|
9
|
-
aiaa
|
|
10
9
|
helpers
|
|
11
10
|
metrics
|
|
11
|
+
utils
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from .aiaa import (
|
|
15
|
-
CsmAIAABenchmark,
|
|
16
|
-
MicAIAABenchmark,
|
|
17
|
-
TimeSamplesAIAABenchmark,
|
|
18
|
-
TriggerAIAABenchmark,
|
|
19
|
-
)
|
|
20
14
|
from .helpers import (
|
|
21
15
|
bardata,
|
|
22
16
|
barspectrum,
|
|
17
|
+
c_air,
|
|
23
18
|
return_result,
|
|
24
19
|
)
|
|
25
20
|
from .metrics import MetricEvaluator
|
|
21
|
+
from .utils import find_basename, get_file_basename
|
acoular/tools/helpers.py
CHANGED
|
@@ -6,31 +6,134 @@
|
|
|
6
6
|
.. autosummary::
|
|
7
7
|
:toctree: generated/
|
|
8
8
|
|
|
9
|
+
synthetic
|
|
9
10
|
return_result
|
|
10
11
|
barspectrum
|
|
11
12
|
bardata
|
|
13
|
+
c_air
|
|
12
14
|
"""
|
|
13
15
|
|
|
16
|
+
from warnings import warn
|
|
17
|
+
|
|
14
18
|
from numpy import (
|
|
15
19
|
array,
|
|
16
20
|
concatenate,
|
|
21
|
+
isscalar,
|
|
17
22
|
newaxis,
|
|
23
|
+
searchsorted,
|
|
24
|
+
sum, # noqa A004
|
|
18
25
|
where,
|
|
26
|
+
zeros_like,
|
|
19
27
|
)
|
|
20
28
|
from numpy.ma import masked_where
|
|
21
29
|
|
|
22
|
-
from acoular.
|
|
30
|
+
from acoular.tools.utils import mole_fraction_of_water_vapor
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def synthetic(data, freqs, f, num=3):
|
|
34
|
+
"""Returns synthesized frequency band values of spectral data.
|
|
35
|
+
|
|
36
|
+
If used with :meth:`Beamformer.result()<acoular.fbeamform.BeamformerBase.result>`
|
|
37
|
+
and only one frequency band, the output is identical to the result of the intrinsic
|
|
38
|
+
:meth:`Beamformer.synthetic<acoular.fbeamform.BeamformerBase.synthetic>` method.
|
|
39
|
+
It can, however, also be used with the
|
|
40
|
+
:meth:`Beamformer.integrate<acoular.fbeamform.BeamformerBase.integrate>`
|
|
41
|
+
output and more frequency bands.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
data : array of floats
|
|
46
|
+
The spectral data (squared sound pressures in Pa^2) in an array with one value
|
|
47
|
+
per frequency line.
|
|
48
|
+
The number of entries must be identical to the number of
|
|
49
|
+
grid points.
|
|
50
|
+
freqs : array of floats
|
|
51
|
+
The frequencies that correspond to the input *data* (as yielded by
|
|
52
|
+
the :meth:`PowerSpectra.fftfreq<acoular.spectra.PowerSpectra.fftfreq>`
|
|
53
|
+
method).
|
|
54
|
+
f : float or list of floats
|
|
55
|
+
Band center frequency/frequencies for which to return the results.
|
|
56
|
+
num : integer
|
|
57
|
+
Controls the width of the frequency bands considered; defaults to
|
|
58
|
+
3 (third-octave band).
|
|
59
|
+
|
|
60
|
+
=== =====================
|
|
61
|
+
num frequency band width
|
|
62
|
+
=== =====================
|
|
63
|
+
0 single frequency line
|
|
64
|
+
1 octave band
|
|
65
|
+
3 third-octave band
|
|
66
|
+
n 1/n-octave band
|
|
67
|
+
=== =====================
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
array of floats
|
|
72
|
+
Synthesized frequency band values of the beamforming result at
|
|
73
|
+
each grid point (the sum of all values that are contained in the band).
|
|
74
|
+
Note that the frequency resolution and therefore the bandwidth
|
|
75
|
+
represented by a single frequency line depends on
|
|
76
|
+
the :attr:`sampling frequency<acoular.base.SamplesGenerator.sample_freq>`
|
|
77
|
+
and used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
if isscalar(f):
|
|
81
|
+
f = (f,)
|
|
82
|
+
if num == 0:
|
|
83
|
+
# single frequency lines
|
|
84
|
+
res = []
|
|
85
|
+
for i in f:
|
|
86
|
+
ind = searchsorted(freqs, i)
|
|
87
|
+
if ind >= len(freqs):
|
|
88
|
+
warn(
|
|
89
|
+
f'Queried frequency ({i:g} Hz) not in resolved frequency range. Returning zeros.',
|
|
90
|
+
Warning,
|
|
91
|
+
stacklevel=2,
|
|
92
|
+
)
|
|
93
|
+
h = zeros_like(data[0])
|
|
94
|
+
else:
|
|
95
|
+
if freqs[ind] != i:
|
|
96
|
+
warn(
|
|
97
|
+
f'Queried frequency ({i:g} Hz) not in set of '
|
|
98
|
+
'discrete FFT sample frequencies. '
|
|
99
|
+
f'Using frequency {freqs[ind]:g} Hz instead.',
|
|
100
|
+
Warning,
|
|
101
|
+
stacklevel=2,
|
|
102
|
+
)
|
|
103
|
+
h = data[ind]
|
|
104
|
+
res += [h]
|
|
105
|
+
else:
|
|
106
|
+
# fractional octave bands
|
|
107
|
+
res = []
|
|
108
|
+
for i in f:
|
|
109
|
+
f1 = i * 2.0 ** (-0.5 / num)
|
|
110
|
+
f2 = i * 2.0 ** (+0.5 / num)
|
|
111
|
+
ind1 = searchsorted(freqs, f1)
|
|
112
|
+
ind2 = searchsorted(freqs, f2)
|
|
113
|
+
if ind1 == ind2:
|
|
114
|
+
warn(
|
|
115
|
+
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
116
|
+
'include any discrete FFT sample frequencies. '
|
|
117
|
+
'Returning zeros.',
|
|
118
|
+
Warning,
|
|
119
|
+
stacklevel=2,
|
|
120
|
+
)
|
|
121
|
+
h = zeros_like(data[0])
|
|
122
|
+
else:
|
|
123
|
+
h = sum(data[ind1:ind2], 0)
|
|
124
|
+
res += [h]
|
|
125
|
+
return array(res)
|
|
23
126
|
|
|
24
127
|
|
|
25
128
|
def return_result(source, nmax=-1, num=128):
|
|
26
129
|
"""Collects the output from a
|
|
27
|
-
:meth:`SamplesGenerator.result()<acoular.
|
|
130
|
+
:meth:`SamplesGenerator.result()<acoular.base.SamplesGenerator.result>`
|
|
28
131
|
generator and returns an assembled array with all the data.
|
|
29
132
|
|
|
30
133
|
Parameters
|
|
31
134
|
----------
|
|
32
135
|
source: SamplesGenerator or derived object.
|
|
33
|
-
This is the :class:`SamplesGenerator<acoular.
|
|
136
|
+
This is the :class:`SamplesGenerator<acoular.base.SamplesGenerator>` data source.
|
|
34
137
|
nmax: integer
|
|
35
138
|
With this parameter, a maximum number of output samples can be set
|
|
36
139
|
(first dimension of array). If set to -1 (default), samples are
|
|
@@ -41,7 +144,7 @@ def return_result(source, nmax=-1, num=128):
|
|
|
41
144
|
|
|
42
145
|
Returns
|
|
43
146
|
-------
|
|
44
|
-
array of floats (number of samples, source.
|
|
147
|
+
array of floats (number of samples, source.num_channels)
|
|
45
148
|
Array that holds all the data.
|
|
46
149
|
|
|
47
150
|
"""
|
|
@@ -187,3 +290,114 @@ def bardata(data, fc, num=3, bar=True, xoffset=0.0, masked=-360):
|
|
|
187
290
|
if masked > -360:
|
|
188
291
|
plist = masked_where(plist <= masked, plist)
|
|
189
292
|
return (flulist, plist)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def c_air(t, h, p=101325, co2=0.04):
|
|
296
|
+
r"""
|
|
297
|
+
Calculates the speed of sound in air according to Eq.(15) in :cite:`Cramer1993`.
|
|
298
|
+
|
|
299
|
+
This function calculates the speed of sound in air based on temperature, pressure, relative
|
|
300
|
+
humidity, and CO\ :sub:`2` concentration. To calculate the mole fraction of water vapor in
|
|
301
|
+
the air, :meth:`~acoular.tools.utils.mole_fraction_of_water_vapor` uses the more recent work
|
|
302
|
+
of :cite:`Davis1992` to obtain the saturation vapor pressure.
|
|
303
|
+
|
|
304
|
+
The function is only valid over the temperature range from 0°C to 30°C (273.15 K to 303.15 K),
|
|
305
|
+
for the pressure range 60 to 110 kPa, a water vapor mole fraction up to 0.06, and CO2
|
|
306
|
+
concentrations up to 1%.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
t : float
|
|
311
|
+
Temperature in (°C).
|
|
312
|
+
h : float
|
|
313
|
+
Humidity in percent (0 to 100).
|
|
314
|
+
p : float
|
|
315
|
+
Atmospheric pressure in Pa (default is the standard pressure 101325 Pa).
|
|
316
|
+
co2 : float
|
|
317
|
+
Carbon dioxide concentration in percent (default is 0.04%).
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
float
|
|
322
|
+
Speed of sound in air in m/s.
|
|
323
|
+
|
|
324
|
+
Raises
|
|
325
|
+
------
|
|
326
|
+
ValueError
|
|
327
|
+
If the temperature is out of range (0°C to 30°C), the pressure is out of range
|
|
328
|
+
(60 kPa to 110 kPa), the water vapor mole fraction is out of range (up to 0.06),
|
|
329
|
+
or the CO\ :sub:`2` concentration is out of range (up to 1%).
|
|
330
|
+
|
|
331
|
+
Notes
|
|
332
|
+
-----
|
|
333
|
+
The speed of sound in air is calculated using the following equation:
|
|
334
|
+
|
|
335
|
+
.. math::
|
|
336
|
+
|
|
337
|
+
\begin{aligned}
|
|
338
|
+
c(t, p, x_w, c_{CO_2}) = & a_0 + a_1 t + a_2 t^2 + \left(a_3 + a_4 t + a_5 t^2\right) x_w \\
|
|
339
|
+
& + \left(a_6 + a_7 t + a_8 t^2\right) p + \left(a_9 + a_{10} t + a_{11} t^2\right) x_c \\
|
|
340
|
+
& + a_{12} x_w^2 + a_{13} p^2 + a_{14} x_c^2 + a_{15} x_w p x_c
|
|
341
|
+
\end{aligned}
|
|
342
|
+
|
|
343
|
+
where:
|
|
344
|
+
- :math:`t` is the temperature in c.
|
|
345
|
+
- :math:`x_w` is the water vapor mole fraction.
|
|
346
|
+
- :math:`x_c` is the carbon dioxide mole fraction (:math:`x_c = c_{CO_2} / 100`).
|
|
347
|
+
- :math:`p` is the atmospheric pressure in Pa.
|
|
348
|
+
|
|
349
|
+
Examples
|
|
350
|
+
--------
|
|
351
|
+
Code for reproducing Fig.1 from :cite:`Cramer1993`
|
|
352
|
+
|
|
353
|
+
.. plot:: plots/c_air.py
|
|
354
|
+
"""
|
|
355
|
+
if t < 0 or t > 30:
|
|
356
|
+
msg = 'Temperature out of range (0°C to 30°C)'
|
|
357
|
+
raise ValueError(msg)
|
|
358
|
+
if p < 60000 or p > 110000:
|
|
359
|
+
msg = 'Pressure out of range (60 kPa to 110 kPa)'
|
|
360
|
+
raise ValueError(msg)
|
|
361
|
+
|
|
362
|
+
# Calculate water vapor mole fraction
|
|
363
|
+
x_w = mole_fraction_of_water_vapor(h / 100, t + 273.15, p)
|
|
364
|
+
if x_w > 0.06:
|
|
365
|
+
msg = 'Water vapor mole fraction out of range (up to 0.06)'
|
|
366
|
+
raise ValueError(msg)
|
|
367
|
+
|
|
368
|
+
if co2 > 1.0:
|
|
369
|
+
msg = 'CO2 concentration out of range (up to 1%)'
|
|
370
|
+
raise ValueError(msg)
|
|
371
|
+
|
|
372
|
+
# Convert CO2 concentration from percent to mole fraction
|
|
373
|
+
x_c = co2 / 100
|
|
374
|
+
|
|
375
|
+
# Coefficients from Eq.(15)
|
|
376
|
+
a0 = 331.5024
|
|
377
|
+
a1 = 0.603055
|
|
378
|
+
a2 = -0.000528
|
|
379
|
+
a3 = 51.471935
|
|
380
|
+
a4 = 0.1495874
|
|
381
|
+
a5 = -0.000782
|
|
382
|
+
a6 = -1.82 * 10**-7
|
|
383
|
+
a7 = 3.73 * 10**-8
|
|
384
|
+
a8 = -2.93 * 10**-10
|
|
385
|
+
a9 = -85.20931
|
|
386
|
+
a10 = -0.228525
|
|
387
|
+
a11 = 5.91 * 10**-5
|
|
388
|
+
a12 = -2.835149
|
|
389
|
+
a13 = -2.15 * 10**-13
|
|
390
|
+
a14 = 29.179762
|
|
391
|
+
a15 = 0.000486
|
|
392
|
+
return (
|
|
393
|
+
a0
|
|
394
|
+
+ a1 * t
|
|
395
|
+
+ a2 * t**2
|
|
396
|
+
+ (a3 + a4 * t + a5 * t**2) * x_w
|
|
397
|
+
+ (a6 + a7 * t + a8 * t**2) * p
|
|
398
|
+
+ (a9 + a10 * t + a11 * t**2) * x_c
|
|
399
|
+
+ a12 * x_w**2
|
|
400
|
+
+ a13 * p**2
|
|
401
|
+
+ a14 * x_c**2
|
|
402
|
+
+ a15 * x_w * p * x_c
|
|
403
|
+
)
|
acoular/tools/metrics.py
CHANGED
|
@@ -18,17 +18,17 @@ from numpy import (
|
|
|
18
18
|
ones,
|
|
19
19
|
)
|
|
20
20
|
from scipy.spatial.distance import cdist
|
|
21
|
-
from traits.api import Bool, CArray,
|
|
21
|
+
from traits.api import Bool, CArray, HasStrictTraits, Instance, Property
|
|
22
22
|
|
|
23
23
|
from acoular.fbeamform import L_p, integrate
|
|
24
24
|
from acoular.grids import CircSector, Grid
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class MetricEvaluator(
|
|
27
|
+
class MetricEvaluator(HasStrictTraits):
|
|
28
28
|
"""Evaluate the reconstruction performance of source mapping methods.
|
|
29
29
|
|
|
30
30
|
This class can be used to calculate the following performance metrics
|
|
31
|
-
according
|
|
31
|
+
according :cite:`Herold2017`:
|
|
32
32
|
* Specific level error
|
|
33
33
|
* Overall level error
|
|
34
34
|
* Inverse level error
|
|
@@ -84,7 +84,7 @@ class MetricEvaluator(HasPrivateTraits):
|
|
|
84
84
|
ns = self.target_data.shape[1]
|
|
85
85
|
radii = ones(ns) * self.sector.r
|
|
86
86
|
if self.adaptive_sector_size:
|
|
87
|
-
locs = self.target_grid.
|
|
87
|
+
locs = self.target_grid.pos.T
|
|
88
88
|
intersrcdist = cdist(locs, locs)
|
|
89
89
|
intersrcdist[intersrcdist == 0] = inf
|
|
90
90
|
intersrcdist = intersrcdist.min(0) / 2
|
|
@@ -97,7 +97,7 @@ class MetricEvaluator(HasPrivateTraits):
|
|
|
97
97
|
ns = self.target_data.shape[1]
|
|
98
98
|
sectors = []
|
|
99
99
|
for i in range(ns):
|
|
100
|
-
loc = self.target_grid.
|
|
100
|
+
loc = self.target_grid.pos[:, i]
|
|
101
101
|
sector = copy(self.sector)
|
|
102
102
|
sector.r = r[i]
|
|
103
103
|
sector.x = loc[0]
|
acoular/tools/utils.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Utility classes intended for internal use in Acoular.
|
|
2
|
+
|
|
3
|
+
.. autosummary::
|
|
4
|
+
:toctree: generated/
|
|
5
|
+
|
|
6
|
+
get_file_basename
|
|
7
|
+
find_basename
|
|
8
|
+
mole_fraction_of_water_vapor
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_file_basename(file, alternative_basename='void'):
|
|
17
|
+
"""Return the basename of the file.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
file : str
|
|
22
|
+
File path.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
str
|
|
27
|
+
Basename of the file.
|
|
28
|
+
"""
|
|
29
|
+
basename = Path(file).stem
|
|
30
|
+
return basename if basename else alternative_basename
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_basename(source, alternative_basename='void'):
|
|
34
|
+
"""Return the basename of the original source.
|
|
35
|
+
|
|
36
|
+
Traverses the source chain of the object and returns the basename of the original source.
|
|
37
|
+
If the source object does not have a basename, uses the alternative basename.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
source : instance
|
|
42
|
+
:class:`~acoular.base.Generator` derived object
|
|
43
|
+
alternative_basename : str
|
|
44
|
+
Alternative basename to use if the source object does not have a basename.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
str
|
|
50
|
+
Basename of the original source.
|
|
51
|
+
"""
|
|
52
|
+
while source:
|
|
53
|
+
basename = getattr(source, 'basename', None)
|
|
54
|
+
if basename is not None:
|
|
55
|
+
return basename
|
|
56
|
+
source = getattr(source, 'source', None)
|
|
57
|
+
return alternative_basename
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def mole_fraction_of_water_vapor(h, t, p=101325):
|
|
61
|
+
r"""Mole fraction of water vapor in the air for real gases.
|
|
62
|
+
|
|
63
|
+
Calculates the mole fraction of water vapor in air from the relative humidity,
|
|
64
|
+
based on the equations provided in the appendix of :cite:`Cramer1993` and
|
|
65
|
+
the enhancement factors from :cite:`Davis1992`.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
h : float
|
|
70
|
+
Relative humidity as a fraction [0,1].
|
|
71
|
+
t : float
|
|
72
|
+
Thermodynamic temperature in K.
|
|
73
|
+
p : float
|
|
74
|
+
Atmospheric pressure in Pa (default is the standard pressure 101325 Pa).
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
float
|
|
79
|
+
Mole fraction of water vapor.
|
|
80
|
+
|
|
81
|
+
Notes
|
|
82
|
+
-----
|
|
83
|
+
The mole fraction is calculated as:
|
|
84
|
+
|
|
85
|
+
.. math::
|
|
86
|
+
x_w = h \cdot f \cdot \frac{p_{sv}}{p},
|
|
87
|
+
|
|
88
|
+
where:
|
|
89
|
+
- :math:`h` is the relative humidity as a fraction [0,1].
|
|
90
|
+
- :math:`f` is the enhancement factor:
|
|
91
|
+
|
|
92
|
+
.. math::
|
|
93
|
+
f = 1.00062 + 3.14 \times 10^{-8} \cdot p + 5.6 \times 10^{-7} \cdot t^2.
|
|
94
|
+
|
|
95
|
+
- :math:`p_{sv}` is the saturation vapor pressure of water vapor in air:
|
|
96
|
+
|
|
97
|
+
.. math::
|
|
98
|
+
p_{sv} = \exp(A \cdot t^2 + B \cdot t + C + \frac{D}{t}),
|
|
99
|
+
|
|
100
|
+
with the updated coefficients from :cite:`Davis1992`:
|
|
101
|
+
|
|
102
|
+
.. math::
|
|
103
|
+
A = 1.2378847 \times 10^{-5}, \\
|
|
104
|
+
B = -1.9121316 \times 10^{-2}, \\
|
|
105
|
+
C = 33.93711047, \\
|
|
106
|
+
D = -6.3431645 \times 10^3.
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
f = 1.00062 + 3.14 * 10 ** (-8) * p + 5.6 * 10 ** (-7) * t**2 # enhancement factor
|
|
110
|
+
# Saturation vapor pressure using updated coefficients from Davis (1992)
|
|
111
|
+
A = 1.2378847 * 10 ** (-5) # noqa: N806
|
|
112
|
+
B = -1.9121316 * 10 ** (-2) # noqa: N806
|
|
113
|
+
C = 33.93711047 # noqa: N806
|
|
114
|
+
D = -6.3431645 * 10**3 # noqa: N806
|
|
115
|
+
p_sv = np.exp(A * t**2 + B * t + C + D / t) # p_sv in Pa
|
|
116
|
+
return h * f * p_sv / p
|