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/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, numchannels = offsets.shape
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(numchannels):
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, numchannels = offsets.shape
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(numchannels):
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, numchannels = rm.shape
134
- amp[0, 0, 0] = 1.0 / numchannels # to get the same type for rm2 as for rm
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(numchannels):
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, numchannels = rm.shape
153
- amp[0, 0, 0] = 1.0 / numchannels # to get the same type for rm2 as for rm
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(numchannels):
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, numchannels = rm.shape
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(numchannels):
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(numchannels):
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, numchannels = rm.shape
196
- amp[0, 0, 0] = np.sqrt(1.0 / numchannels) # to get the same type for rm2 as for rm
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(numchannels):
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(numchannels):
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, numchannels = rm.shape
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(numchannels):
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, numchannels = delays.shape
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(numchannels):
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.spectra import synthetic
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.tprocess.SamplesGenerator.result>`
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.tprocess.SamplesGenerator>` data source.
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.numchannels)
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, HasPrivateTraits, Instance, Property
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(HasPrivateTraits):
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 to Herold and Sarradj (2017):
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.gpos.T
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.gpos[:, i]
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