acoular 24.10__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/tools/utils.py CHANGED
@@ -3,208 +3,114 @@
3
3
  .. autosummary::
4
4
  :toctree: generated/
5
5
 
6
- SamplesBuffer
6
+ get_file_basename
7
+ find_basename
8
+ mole_fraction_of_water_vapor
7
9
  """
8
10
 
11
+ from pathlib import Path
12
+
9
13
  import numpy as np
10
- from traits.api import Any, Array, Bool, Enum, Int, Property, Union
11
-
12
- from acoular.process import InOut
13
-
14
-
15
- class SamplesBuffer(InOut):
16
- """Handles buffering of samples from a source.
17
-
18
- This class is used to buffer samples from a source and provide them in blocks
19
- of a specified size. There are several usecases for this class, as demonstrated in
20
- the following.
21
-
22
- Examples
23
- --------
24
- Let us assume we want to draw blocks of 16 samples from our source, but we want to make sure
25
- that we always have twice the number of samples in the buffer. We can achieve this simple behaviour
26
- by using the following code:
27
-
28
- >>> import acoular as ac
29
- >>> import numpy as np
30
- >>> # create a white noise source with 512 samples
31
- >>> source = ac.TimeSamples(
32
- ... data=ac.WNoiseGenerator(
33
- ... sample_freq=64,
34
- ... numsamples=512,
35
- ... ).signal()[:, np.newaxis],
36
- ... sample_freq=64,
37
- ... )
38
- >>> # create a buffer with a size of 32 samples
39
- >>> buffer = ac.tools.SamplesBuffer(source=source, length=32)
40
- >>> # get the first block of 16 samples
41
- >>> block = next(buffer.result(num=16))
42
- >>> np.testing.assert_array_equal(block, source.data[:16])
43
-
44
- Here, on the first call to the result method, the buffer will fill up by collecting blocks with same size
45
- from the source. The buffer will then return the first block of 16 samples. On the next call to the result
46
- method, the buffer will be filled again and returns the next block of 16 samples.
47
-
48
- In some cases, we might want to draw a different number of samples from the source than we want to return.
49
- This can be achieved by setting the `source_num` trait of the buffer. A special case is the return of a variable
50
- number of samples. This is the case, for example, in the class :class:`~acoular.tbeamform.BeamformerTimeTraj`,
51
- in which a different number of time samples is required from the buffer for further delay-and-sum processing
52
- depending on the expected delay, which can be vary for moving sources. At the same time, however, only 'num'
53
- samples should be written to and removed from the buffer. This behavior can be achieved by setting the
54
- `shift_index_by` trait to 'num' and by setting the `result_num` trait to the number of samples that should be
55
- returned by the result function.
56
-
57
- >>> buffer = ac.tools.SamplesBuffer(source=source, length=32, result_num=20, shift_index_by='num')
58
- >>> block_sizes = []
59
- >>> block_sizes.append(
60
- ... next(buffer.result(num=16)).shape[0]
61
- ... ) # this time, the buffer will return 20 samples, but the buffer will only forget the first 16 samples
62
- >>> buffer.result_num = 24
63
- >>> block_sizes.append(
64
- ... next(buffer.result(num=16)).shape[0]
65
- ... ) # this time, the buffer will return 24 samples, but the buffer will only forget the first 16 samples
66
- >>> np.testing.assert_array_equal(block_sizes, [20, 24])
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.
67
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
+
68
59
 
69
- #: number of samples that fit in the buffer
70
- length = Int(desc='number of samples that fit in the buffer')
71
-
72
- #: number of samples per block to obtain from the source. If 'None', use 'num' argument of result method
73
- source_num = Union(
74
- None,
75
- Int(),
76
- default_value=None,
77
- desc='number of samples to return from the source. If "None", use "num" argument of result method',
78
- )
79
-
80
- #: number of samples to return from the buffer. If 'None', use 'num' argument of result method
81
- result_num = Union(
82
- None,
83
- Int(),
84
- default_value=None,
85
- desc="number of samples to return from the buffer. If 'None', use 'num' argument of result method",
86
- )
87
-
88
- #: index shift value for the buffer. If "result_num", buffer will return and forget 'result_num' samples.
89
- #: If "num", buffer will return 'result_num' samples but will forget 'num' samples
90
- shift_index_by = Enum(
91
- ('result_num', 'num'),
92
- desc=(
93
- 'index shift value for the buffer. If "result_num", use "result_num" trait.'
94
- ' If "num", use "num" argument of result method'
95
- ),
96
- )
97
-
98
- #: current filling level of buffer
99
- level = Property(desc='current filling level of buffer')
100
-
101
- #: data type of the buffer elements
102
- dtype = Any(desc='data type of the buffer')
103
-
104
- # flag to indicate that the source is empty, for internal use
105
- _empty_source = Bool(False, desc='flag to indicate that the source is empty')
106
-
107
- # the buffer for processing
108
- _buffer = Array(shape=(None, None), desc='buffer for block processing')
109
-
110
- # current index in buffer
111
- _index = Int(desc='current index in buffer')
112
-
113
- def _get_level(self):
114
- return self._buffer.shape[0] - self._index
115
-
116
- def _create_new_buffer(self):
117
- self._buffer = np.zeros((self.length, self.numchannels), dtype=self.dtype)
118
- self._index = self.length
119
- self._empty_source = False
120
-
121
- def _write_to_buffer(self, data):
122
- ns = data.shape[0]
123
- self._buffer[0 : (self.length - ns)] = self._buffer[-(self.length - ns) :]
124
- self._buffer[-ns:, :] = data.astype(self.dtype)
125
- self._index -= ns
126
-
127
- def increase_buffer(self, num):
128
- """Increase the buffer by 'num' samples.
129
-
130
- Returns
131
- -------
132
- None
133
- """
134
- ar = np.zeros((num, self.numchannels), dtype=self._buffer.dtype)
135
- self._buffer = np.concatenate((ar, self._buffer), axis=0)
136
- self._index += num
137
- self.length += num
138
-
139
- def read_from_buffer(self, num):
140
- """Read samples from the buffer.
141
-
142
- Parameters
143
- ----------
144
- num : int
145
- number of samples to read from the buffer.
146
-
147
- Returns
148
- -------
149
- numpy.ndarray
150
- block of samples from the buffer
151
-
152
- """
153
- rnum = num if self.result_num is None else self.result_num
154
- rnum = rnum if self.level >= rnum else self.level
155
- data = self._buffer[self._index : self._index + rnum]
156
- if self.shift_index_by == 'result_num':
157
- self._index += rnum
158
- else:
159
- self._index += num
160
- return data
161
-
162
- def fill_buffer(self, snum):
163
- """Fill the buffer with samples from the source.
164
-
165
- Parameters
166
- ----------
167
- snum : int
168
- number of samples to return from the source.
169
-
170
- Yields
171
- ------
172
- None
173
- """
174
- source_generator = self.source.result(snum)
175
- while not self._empty_source:
176
- while self._index >= snum:
177
- if self.result_num is not None:
178
- while self.result_num > self.length:
179
- self.increase_buffer(snum)
180
- try:
181
- self._write_to_buffer(next(source_generator))
182
- except StopIteration:
183
- self._empty_source = True
184
- break
185
- yield
186
-
187
- def result(self, num):
188
- """Return blocks of samples from the buffer.
189
-
190
- Parameters
191
- ----------
192
- num : int
193
- number of samples to return.
194
-
195
- Yields
196
- ------
197
- numpy.ndarray
198
- block of samples from the buffer
199
- """
200
- self._create_new_buffer()
201
- snum = num
202
- if self.source_num is not None:
203
- snum = self.source_num
204
- for _ in self.fill_buffer(snum):
205
- if self.level > 0:
206
- yield self.read_from_buffer(num)
207
- else:
208
- break
209
- while self.level > 0:
210
- yield self.read_from_buffer(num)
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