acoular 24.10__py3-none-any.whl → 25.3__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/environments.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # ------------------------------------------------------------------------------
2
2
  # Copyright (c) Acoular Development Team.
3
3
  # ------------------------------------------------------------------------------
4
- """Implements acoustic environments with and without flow.
4
+ """
5
+ Implements acoustic environments with and without flow.
5
6
 
6
7
  .. autosummary::
7
8
  :toctree: generated/
@@ -13,9 +14,15 @@
13
14
  OpenJet
14
15
  RotatingFlow
15
16
  SlotJet
16
-
17
+ dist_mat
18
+ cylToCart
19
+ cartToCyl
20
+ spiral_sphere
17
21
  """
18
22
 
23
+ from abc import abstractmethod
24
+ from warnings import warn
25
+
19
26
  import numba as nb
20
27
  from numpy import (
21
28
  arange,
@@ -39,7 +46,7 @@ from numpy import (
39
46
  sign,
40
47
  sin,
41
48
  sqrt,
42
- sum,
49
+ sum, # noqa: A004
43
50
  vstack,
44
51
  zeros_like,
45
52
  )
@@ -47,7 +54,18 @@ from scipy.integrate import ode
47
54
  from scipy.interpolate import LinearNDInterpolator
48
55
  from scipy.linalg import norm
49
56
  from scipy.spatial import ConvexHull
50
- from traits.api import CArray, Dict, Float, HasPrivateTraits, Int, Property, Trait, cached_property
57
+ from traits.api import (
58
+ ABCHasStrictTraits,
59
+ CArray,
60
+ Dict,
61
+ Float,
62
+ HasStrictTraits,
63
+ Instance,
64
+ Int,
65
+ Property,
66
+ Union,
67
+ cached_property,
68
+ )
51
69
 
52
70
  from .internal import digest
53
71
 
@@ -56,18 +74,29 @@ f32ro = nb.types.Array(nb.types.float32, 2, 'C', readonly=True)
56
74
 
57
75
 
58
76
  @nb.njit([(f64ro, f64ro), (f64ro, f32ro), (f32ro, f64ro), (f32ro, f32ro)], cache=True, fastmath=True)
59
- def dist_mat(gpos, mpos):
60
- """Computes distance matrix, accelerated with numba.
77
+ def dist_mat(gpos, mpos): # pragma: no cover
78
+ """
79
+ Compute distance matrix. (accelerated with numba).
80
+
81
+ Given an `(3, N)` array of the locations of points in the beamforming map grid in 3D cartesian
82
+ coordinates and `(3, M)` array of the locations of microphones in 3D cartesian coordinates, the
83
+ `(N, M)` matrix of the distances between each microphone and each point in the beamforming map
84
+ grip.
61
85
 
62
- Args:
63
- ----
64
- gpos (3,N)
65
- mpos (3,M)
86
+ Parameters
87
+ ----------
88
+ gpos : :class:`numpy.ndarray` of :class:`floats<float>`
89
+ The locations of `N` points in the beamforming map grid in 3D cartesian coordinates,
90
+ shape `(3, N)`.
91
+
92
+ mpos : :class:`numpy.ndarray` of :class:`floats<float>`
93
+ The locations of `M` microphones in 3D cartesian coordinates, shape `(3, M)`.
66
94
 
67
95
  Returns
68
96
  -------
69
- (N,M) distance matrix
70
-
97
+ :class:`numpy.ndarray` of :class:`floats<float>`
98
+ Matrix of the distances between each microphone and each point in the beamforming map grid,
99
+ shape `(N, M)`.
71
100
  """
72
101
  _, M = mpos.shape
73
102
  _, N = gpos.shape
@@ -86,24 +115,27 @@ def dist_mat(gpos, mpos):
86
115
 
87
116
 
88
117
  def cartToCyl(x, Q=None): # noqa: N802, N803
89
- """Returns the cylindrical coordinate representation of a input position
90
- which was before transformed into a modified cartesian coordinate, which
91
- has flow into positive z direction.
118
+ r"""
119
+ Return cylindrical coordinate representation of an input array in cartesian coordinate.
120
+
121
+ Return the cylindrical coordinate representation of an input position which was before
122
+ transformed into a modified cartesian coordinate, which has flow into positive z direction.
92
123
 
93
124
  Parameters
94
125
  ----------
95
- x : float[3, nPoints]
96
- cartesian coordinates of n points
97
- Q : float[3,3]
98
- Orthogonal transformation matrix. If provided, the pos vectors are
99
- transformed via posiMod = Q * x, before transforming those modified
100
- coordinates into cylindrical ones. Default is identity matrix.
126
+ x : :class:`numpy.ndarray` of :class:`floats<float>`
127
+ Cartesian coordinates of `N` points, shape `(3, N)`.
128
+
129
+ Q : :class:`numpy.ndarray` of :class:`floats<float>`, optional
130
+ Orthogonal transformation matrix, shape `(3, 3)`. If provided, the positional vectors are
131
+ transformed via ``new_x = Q * x``, before transforming those modified coordinates into
132
+ cylindrical ones. Default is the identity matrix.
101
133
 
102
134
  Returns
103
135
  -------
104
- cylCoord : [3, nPoints]
105
- cylindrical representation of those n points with (phi, r, z)
106
-
136
+ :class:`numpy.ndarray` of :class:`floats<float>`
137
+ Cylindrical representation of given `N` points in cartesian coodrinates as
138
+ an array of shape `(3, N)` with new coordinates :math:`(\phi, r, z)`.
107
139
  """
108
140
  Q = identity(3) if Q is None else Q
109
141
  if not (Q == identity(3)).all(): # noqa: SIM300
@@ -112,27 +144,27 @@ def cartToCyl(x, Q=None): # noqa: N802, N803
112
144
 
113
145
 
114
146
  def cylToCart(x, Q=None): # noqa: N802, N803
115
- """Returns the cartesian coordinate representation of a input position
116
- which was before transformed into a cylindrical coordinate, which
117
- has flow into positive z direction.
147
+ r"""
148
+ Return cartesian coordinate representation of an input array in cylindrical coordinate.
149
+
150
+ Return the cartesian coordinate representation of a input position which was before transformed
151
+ into a cylindrical coordinate, which has flow into positive z direction.
118
152
 
119
153
  Parameters
120
154
  ----------
121
- x : float[3, nPoints]
122
- cylindrical representation of those n points with (phi, r, z)
123
- cartesian coordinates of n points
124
-
125
- Q : float[3,3]
126
- Orthogonal transformation matrix. If provided, the pos vectors are
127
- transformed via posiMod = Q * x, before transforming those modified
128
- coordinates into cylindrical ones. Default is identity matrix.
155
+ x : :class:`numpy.ndarray` of :class:`floats<float>`
156
+ Cylindrical coordinates of `N` points, shape `(3, N)`.
129
157
 
158
+ Q : :class:`numpy.ndarray` of :class:`floats<float>`, optional
159
+ Orthogonal transformation matrix, shape `(3, 3)`. If provided, the positional vectors are
160
+ transformed via ``new_x = Q * x`` before transforming those modified coordinates into
161
+ cartesian ones. Default is the identity matrix.
130
162
 
131
163
  Returns
132
164
  -------
133
- CartCoord : [3, nPoints]
134
- cartesian coordinates of n points
135
-
165
+ :class:`numpy.ndarray` of :class:`floats<float>`
166
+ Cartesian representation of given `N` points in cylindrical coodrinates as
167
+ an array of shape `(3, N)` with coodinates :math:`(x, y, z)`.
136
168
  """
137
169
  Q = identity(3) if Q is None else Q
138
170
  if not (Q == identity(3)).all(): # noqa: SIM300
@@ -140,48 +172,65 @@ def cylToCart(x, Q=None): # noqa: N802, N803
140
172
  return array([x[1] * sin(x[0]), x[1] * cos(x[0]), x[2]])
141
173
 
142
174
 
143
- class Environment(HasPrivateTraits):
144
- """A simple acoustic environment without flow.
175
+ class Environment(HasStrictTraits):
176
+ """
177
+ A simple acoustic environment without flow.
178
+
179
+ This class models an acoustic environment where the propagation of sound is considered to occur
180
+ in a homogeneous medium without any flow effects (e.g., wind). It provides functionality for
181
+ computing the travel time or distances between grid point locations and microphone locations.
145
182
 
146
- This class provides the facilities to calculate the travel time (distances)
147
- between grid point locations and microphone locations.
183
+ Notes
184
+ -----
185
+ - The :func:`dist_mat` function is used internally to compute the pairwise distances
186
+ between grid points and microphone positions efficiently.
187
+ - This class assumes a static, homogeneous environment without accounting
188
+ for factors like temperature gradients, humidity, or atmospheric turbulence.
148
189
  """
149
190
 
150
- # internal identifier
151
- digest = Property(
152
- depends_on=['c'],
153
- )
191
+ #: A unique identifier based on the environment properties. (read-only)
192
+ digest = Property(depends_on=['c'])
154
193
 
155
- #: The speed of sound, defaults to 343 m/s
194
+ #: The speed of sound in the environment. Default is ``343.0``, which corresponds to the
195
+ #: approximate speed of sound at 20°C in dry air at sea level, if the unit is m/s.
156
196
  c = Float(343.0, desc='speed of sound')
157
197
 
158
- #: The region of interest (ROI), not needed for most types of environment
159
- roi = Trait(None, (None, CArray))
198
+ #: The region of interest (ROI) for calculations. (Not needed for most types of environment.)
199
+ #: Default is :obj:`None`.
200
+ roi = Union(None, CArray)
160
201
 
161
202
  def _get_digest(self):
162
203
  return digest(self)
163
204
 
164
205
  def _r(self, gpos, mpos=0.0):
165
- """Calculates distances between grid point locations and microphone
166
- locations or the origin. Functionality may change in the future.
167
-
168
- Parameters
169
- ----------
170
- gpos : array of floats of shape (3, N)
171
- The locations of points in the beamforming map grid in 3D cartesian
172
- co-ordinates.
173
- mpos : array of floats of shape (3, M), optional
174
- The locations of microphones in 3D cartesian co-ordinates. If not
175
- given, then only one microphone at the origin (0, 0, 0) is
176
- considered.
177
-
178
- Returns
179
- -------
180
- r : array of floats
181
- The distances in a twodimensional (N, M) array of floats. If M==1,
182
- then only a one-dimensional array is returned.
183
-
184
- """
206
+ # Compute the distance between two sets of points.
207
+ #
208
+ # The distance for each of the `N` points in ``gpos`` in 3-D space to each of the `M` points
209
+ # in ``mpos``.
210
+ #
211
+ # See Also
212
+ # --------
213
+ # :func:`dist_mat`: Compute distance matrix.
214
+ #
215
+ # Parameters
216
+ # ----------
217
+ # gpos : :class:`numpy.ndarray` of :class:`floats<float>`
218
+ # The coordinates of the first set of points. Should be of shape `(N, 3)`,
219
+ # where `N` is the number of points.
220
+ #
221
+ # mpos : :class:`float` or :class:`numpy.ndarray` of :class:`floats<float>`, optional
222
+ # The coordinates of the second set of points. If a scalar is provided,
223
+ # it is treated as the origin ``(0, 0, 0)``. If an array is given,
224
+ # it should have shape `(M, 3)`, where `M` is the number of points.
225
+ #
226
+ # Returns
227
+ # -------
228
+ # rm : :class:`numpy.ndarray` of :class:`floats<float>`
229
+ # The distances between each point in ``gpos`` and ``mpos``.
230
+ # The result is an array of
231
+ #
232
+ # - shape `(N,)` if ``mpos`` is a single point, or
233
+ # - shape `(N, M)` if ``mpos`` consists of multiple points.
185
234
  if isscalar(mpos):
186
235
  mpos = array((0, 0, 0), dtype=float64)[:, newaxis]
187
236
  rm = dist_mat(ascontiguousarray(gpos), ascontiguousarray(mpos))
@@ -194,21 +243,31 @@ class Environment(HasPrivateTraits):
194
243
 
195
244
 
196
245
  class UniformFlowEnvironment(Environment):
197
- """An acoustic environment with uniform flow.
246
+ """
247
+ An acoustic environment with uniform flow.
248
+
249
+ This class models an acoustic environment where sound propagates in a medium with uniform flow.
250
+ It extends the :class:`Environment` class to account for the effects of flow on sound
251
+ propagation, such as changes in travel times and distances due to advection by the flow field.
252
+
253
+ The flow is assumed to be uniform and steady, characterized by its Mach number (:attr:`ma`)
254
+ and direction (:attr:`fdv`).
198
255
 
199
- This class provides the facilities to calculate the travel time (distances)
200
- between grid point locations and microphone locations in a uniform flow
201
- field.
256
+ Notes
257
+ -----
258
+ The effective distance is adjusted by solving a flow-dependent relationship that accounts
259
+ for the cosine of the angle between the flow direction and the propagation path.
202
260
  """
203
261
 
204
- #: The Mach number, defaults to 0.
262
+ #: The Mach number of the flow, defined as the ratio of the flow velocity to the speed of sound.
263
+ #: Default is ``0.0``, which corresponds to no flow.
205
264
  ma = Float(0.0, desc='flow mach number')
206
265
 
207
- #: The unit vector that gives the direction of the flow, defaults to
208
- #: flow in x-direction.
266
+ #: A unit vector specifying the direction of the flow in 3D Cartesian coordinates.
267
+ #: Default is ``(1.0, 0, 0)``, which corresponds to flow in the x-direction.
209
268
  fdv = CArray(dtype=float64, shape=(3,), value=array((1.0, 0, 0)), desc='flow direction')
210
269
 
211
- # internal identifier
270
+ #: A unique identifier based on the environment properties. (read-only)
212
271
  digest = Property(
213
272
  depends_on=['c', 'ma', 'fdv'],
214
273
  )
@@ -218,27 +277,30 @@ class UniformFlowEnvironment(Environment):
218
277
  return digest(self)
219
278
 
220
279
  def _r(self, gpos, mpos=0.0):
221
- """Calculates the virtual distances between grid point locations and
222
- microphone locations or the origin. These virtual distances correspond
223
- to travel times of the sound. Functionality may change in the future.
224
-
225
- Parameters
226
- ----------
227
- gpos : array of floats of shape (3, N)
228
- The locations of points in the beamforming map grid in 3D cartesian
229
- co-ordinates.
230
- mpos : array of floats of shape (3, M), optional
231
- The locations of microphones in 3D cartesian co-ordinates. If not
232
- given, then only one microphone at the origin (0, 0, 0) is
233
- considered.
234
-
235
- Returns
236
- -------
237
- array of floats
238
- The distances in a twodimensional (N, M) array of floats. If M==1,
239
- then only a one-dimensional array is returned.
240
-
241
- """
280
+ # Compute the distance between two sets of points.
281
+ #
282
+ # This method calculates the distance between points ``gpos`` and ``mpos`` in a 3-D space,
283
+ # with an additional adjustment based on a mass term and force vector. The result is
284
+ # affected by the angle between the direction of movement and the force vector.
285
+ #
286
+ # Parameters
287
+ # ----------
288
+ # gpos : :class:`numpy.ndarray` of :class:`floats<float>`
289
+ # The 3-D coordinates of the first set of points, shape `(N, 3)`.
290
+ #
291
+ # mpos : :class:`float` or :class:`numpy.ndarray` of :class:`floats<float>`, optional
292
+ # The 3-D coordinates of the second set of points. If a scalar is provided, it is
293
+ # treated as the origin ``(0, 0, 0)``. If an array is given, it should have shape
294
+ # `(M, 3)`, where `M` is the number of points.
295
+ #
296
+ # Returns
297
+ # -------
298
+ # rm : :class:`numpy.ndarray` of :class:`floats<float>`
299
+ # The distances between each point in ``gpos`` and ``mpos``.
300
+ # The result is an array of
301
+ #
302
+ # - shape `(N,)` if ``mpos`` is a single point, or
303
+ # - shape `(N, M)` if ``mpos`` consists of multiple points.
242
304
  if isscalar(mpos):
243
305
  mpos = array((0, 0, 0), dtype=float32)[:, newaxis]
244
306
  fdv = self.fdv / sqrt((self.fdv * self.fdv).sum())
@@ -252,62 +314,67 @@ class UniformFlowEnvironment(Environment):
252
314
  return rm
253
315
 
254
316
 
255
- class FlowField(HasPrivateTraits):
317
+ class FlowField(ABCHasStrictTraits):
256
318
  """An abstract base class for a spatial flow field."""
257
319
 
320
+ #: A unique identifier based on the field properties. (read-only)
258
321
  digest = Property
259
322
 
323
+ @abstractmethod
260
324
  def _get_digest(self):
261
- return ''
325
+ pass
262
326
 
327
+ @abstractmethod
263
328
  def v(self, xx): # noqa: ARG002
264
- """Provides the flow field as a function of the location. This is
265
- implemented here for the possibly most simple case: a quiescent fluid.
329
+ """
330
+ Provide the flow field as a function of the location.
266
331
 
267
332
  Parameters
268
333
  ----------
269
- xx : array of floats of shape (3, )
270
- Location in the fluid for which to provide the data.
271
-
272
- Returns
273
- -------
274
- tuple with two elements
275
- The first element in the tuple is the velocity vector and the
276
- second is the Jacobian of the velocity vector field, both at the
277
- given location.
278
-
334
+ xx : :class:`numpy.ndarray` of :class:`floats<float>`
335
+ Location in the fluid for which to provide the data, shape (3,).
279
336
  """
280
- v = array((0.0, 0.0, 0.0))
281
- dv = array(((0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)))
282
- return -v, -dv
283
337
 
284
338
 
285
339
  class SlotJet(FlowField):
286
- """Provides an analytical approximation of the flow field of a slot jet.
340
+ """
341
+ Analytical approximation of the flow field of a slot jet.
342
+
343
+ This class provides an analytical model of the velocity field for a slot jet,
344
+ based on the description in :cite:`Albertson1950`. It describes the flow field
345
+ originating from a slot nozzle, including the jet core and shear layer, and
346
+ calculates the velocity field and its Jacobian matrix at a given location.
287
347
 
288
- See :cite:`Albertson1950` for details.
348
+ Notes
349
+ -----
350
+ - The slot jet is divided into two regions: the jet core and the shear layer. The model
351
+ distinguishes between these regions based on the non-dimensionalized distance from
352
+ the slot exit plane.
353
+ - The flow field is aligned with the direction of the :attr:`flow` vector,
354
+ while the :attr:`plane` vector helps define the orientation of the slot.
289
355
  """
290
356
 
291
- #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0.
357
+ #: Exit velocity at the :attr:`slot jet origin<origin>` (nozzle). Default is ``0.0``.
292
358
  v0 = Float(0.0, desc='exit velocity')
293
359
 
294
- #: Location of a point at the slot center line,
295
- #: defaults to the co-ordinate origin.
360
+ #: The location of the slot nozzle center. Default is ``(0.0, 0.0, 0.0)``.
296
361
  origin = CArray(dtype=float64, shape=(3,), value=array((0.0, 0.0, 0.0)), desc='center of nozzle')
297
362
 
298
- #: Unit flow direction of the slot jet, defaults to (1,0,0).
363
+ #: Unit vector representing the flow direction. Default is ``(1.0, 0.0, 0.0)``.
299
364
  flow = CArray(dtype=float64, shape=(3,), value=array((1.0, 0.0, 0.0)), desc='flow direction')
300
365
 
301
- #: Unit vector parallel to slot center plane, defaults to (0,1,0).
366
+ #: Unit vector parallel to the slot center plane, used to define the slot orientation.
367
+ #: Default is ``(0.0, 1.0, 0.0)``.
302
368
  plane = CArray(dtype=float64, shape=(3,), value=array((0.0, 1.0, 0.0)), desc='slot center line direction')
303
369
 
304
- #: Width of the slot, defaults to 0.2 .
370
+ #: Width of the slot (slot diameter). Default is ``0.2``.
305
371
  B = Float(0.2, desc='nozzle diameter')
306
372
 
307
- #: Nondimensional length of zone of flow establishment (jet core length), defaults to 5.2
373
+ #: Non-dimensional length of the zone of flow establishment (jet core length).
374
+ #: Default is ``5.2``.
308
375
  l = Float(5.2, desc='flow establishment length') # noqa: E741
309
376
 
310
- # internal identifier
377
+ #: A unique identifier based on the field properties. (read-only)
311
378
  digest = Property(
312
379
  depends_on=['v0', 'origin', 'flow', 'plane', 'B', 'l'],
313
380
  )
@@ -317,32 +384,45 @@ class SlotJet(FlowField):
317
384
  return digest(self)
318
385
 
319
386
  def v(self, xx):
320
- """Provides the flow field as a function of the location. This is
321
- implemented here only for the component in the direction of :attr:`flow`;
322
- entrainment components are set to zero.
387
+ """
388
+ Compute the velocity field and its Jacobian matrix at a given location.
389
+
390
+ This method provides the velocity vector and its Jacobian matrix at the given location
391
+ ``xx`` in the fluid. The velocity is computed for the flow component in the direction
392
+ of the slot jet, while the entrainment components are assumed to be zero.
323
393
 
324
394
  Parameters
325
395
  ----------
326
- xx : array of floats of shape (3, )
327
- Location in the fluid for which to provide the data.
396
+ xx : :class:`numpy.ndarray` of :class:`floats<float>`
397
+ The 3D Cartesian coordinates of the location in the fluid where the velocity field
398
+ is to be computed, shape `(3,)`.
328
399
 
329
400
  Returns
330
401
  -------
331
- tuple with two elements
332
- The first element in the tuple is the velocity vector and the
333
- second is the Jacobian of the velocity vector field, both at the
334
- given location.
335
-
402
+ velocity_vector : :class:`numpy.ndarray` of :class:`floats<float>`
403
+ The velocity vector at the given location, shape `(3,)`.
404
+ jacobian_matrix : :class:`numpy.ndarray` of :class:`floats<float>`
405
+ The Jacobian matrix of the velocity vector field at the given location, shape `(3,)`.
406
+
407
+ Notes
408
+ -----
409
+ - The velocity field is computed using a local coordinate system aligned with the flow
410
+ direction and the slot orientation.
411
+ - The velocity profile depends on whether the point lies within the jet core region or
412
+ in the shear layer. In the jet core, the velocity is constant, while in the shear layer,
413
+ it decays following a Gaussian distribution.
414
+ - The Jacobian matrix provides the partial derivatives of the velocity vector components
415
+ with respect to the spatial coordinates.
336
416
  """
337
417
  # normalize
338
418
  flow = self.flow / norm(self.flow)
339
419
  plane = self.plane / norm(self.plane)
340
- # additional axes of global co-ordinate system
420
+ # additional axes of global coordinate system
341
421
  yy = -cross(flow, plane)
342
422
  zz = cross(flow, yy)
343
423
  # distance from slot exit plane
344
424
  xx1 = xx - self.origin
345
- # local co-ordinate system
425
+ # local coordinate system
346
426
  x = dot(flow, xx1)
347
427
  y = dot(yy, xx1)
348
428
  x1 = 0.5668 / self.l * x # C1 in Albertson1950
@@ -365,31 +445,50 @@ class SlotJet(FlowField):
365
445
 
366
446
 
367
447
  class OpenJet(FlowField):
368
- """Provides an analytical approximation of the flow field of an open jet.
448
+ """
449
+ Analytical approximation of the flow field of an open jet.
369
450
 
370
- See :cite:`Albertson1950` for details.
451
+ This class provides a simplified analytical model of the velocity field for an open jet, based
452
+ on the description in :cite:`Albertson1950`. It calculates the velocity vector and its
453
+ Jacobian matrix at a given location in the fluid domain, assuming flow in the x-direction only.
371
454
 
372
455
  Notes
373
456
  -----
374
- This is not a fully generic implementation and is limited to flow in the
375
- x-direction only. No other directions are possible at the moment and flow
376
- components in the other direction are zero.
377
-
457
+ - This is not a fully generic implementation, and is limited to flow in the x-direction only.
458
+ No other directions are possible at the moment and flow components in
459
+ the other direction are zero.
460
+ - The flow field transitions from the jet core to the shear layer, with velocity decay
461
+ modeled using a Gaussian profile in the shear layer.
462
+
463
+ Examples
464
+ --------
465
+ >>> import acoular as ac
466
+ >>> import numpy as np
467
+ >>>
468
+ >>> jet = ac.OpenJet(v0=10.0, D=0.4, l=6.2)
469
+ >>> velocity, jacobian = jet.v(np.array((1.0, 0.1, 0.1)))
470
+ >>> velocity
471
+ array([9.62413564, 0. , 0. ])
472
+ >>> jacobian
473
+ array([[ -1.92660591, -23.25619062, -23.25619062],
474
+ [ 0. , 0. , 0. ],
475
+ [ 0. , 0. , 0. ]])
378
476
  """
379
477
 
380
- #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0.
478
+ #: Exit velocity at the jet origin (nozzle). Default is ``0.0``.
381
479
  v0 = Float(0.0, desc='exit velocity')
382
480
 
383
- #: Location of the nozzle center, defaults to the co-ordinate origin.
481
+ #: The location of the nozzle center. Default is ``(0.0, 0.0, 0.0)``.
384
482
  origin = CArray(dtype=float64, shape=(3,), value=array((0.0, 0.0, 0.0)), desc='center of nozzle')
385
483
 
386
- #: Diameter of the nozzle, defaults to 0.2 .
484
+ #: Diameter of the nozzle. Default is ``0.2``.
387
485
  D = Float(0.2, desc='nozzle diameter')
388
486
 
389
- #: Nondimensional length of zone of flow establishment (jet core length), defaults to 6.2
487
+ #: Non-dimensional length of the zone of flow establishment (jet core length).
488
+ #: Default is ``6.2``. :cite:`Albertson1950`
390
489
  l = Float(6.2, desc='flow establishment length') # noqa: E741
391
490
 
392
- # internal identifier
491
+ #: A unique identifier based on the field properties. (read-only)
393
492
  digest = Property(
394
493
  depends_on=['v0', 'origin', 'D', 'l'],
395
494
  )
@@ -399,22 +498,36 @@ class OpenJet(FlowField):
399
498
  return digest(self)
400
499
 
401
500
  def v(self, xx):
402
- """Provides the flow field as a function of the location. This is
403
- implemented here only for a jet in `x`-direction and the `y`- and
404
- `z`-components are set to zero.
501
+ """
502
+ Compute the velocity field and its Jacobian matrix at a given location.
503
+
504
+ This method calculates the velocity vector and its Jacobian matrix at the
505
+ specified location ``xx`` in the fluid domain. The velocity is modeled only for the
506
+ x-component of the flow, while the y- and z-components are assumed to be zero.
405
507
 
406
508
  Parameters
407
509
  ----------
408
- xx : array of floats of shape (3, )
409
- Location in the fluid for which to provide the data.
510
+ xx : :class:`numpy.ndarray` of :class:`floats<float>`
511
+ The 3D Cartesian coordinates of the location in the fluid where the velocity
512
+ field is to be computed, shape `(3,)`.
410
513
 
411
514
  Returns
412
515
  -------
413
- tuple with two elements
414
- The first element in the tuple is the velocity vector and the
415
- second is the Jacobian of the velocity vector field, both at the
416
- given location.
417
-
516
+ velocity_vector : :class:`numpy.ndarray` of :class:`floats<float>`
517
+ The velocity vector at the specified location ``xx``, shape `(3,)`.
518
+ jacobian_matrix : :class:`numpy.ndarray` of :class:`floats<float>`
519
+ The Jacobian matrix of the velocity vector field at the specified location ``xx``,
520
+ shape `(3, 3)`.
521
+
522
+ Notes
523
+ -----
524
+ - The velocity field is determined based on whether the location is within the jet core
525
+ region or in the shear layer. Within the jet core, the velocity is constant and equal
526
+ to :attr:`v0`. In the shear layer, the velocity decays following a Gaussian distribution.
527
+ - The Jacobian matrix provides the partial derivatives of the velocity components
528
+ with respect to the spatial coordinates.
529
+ - If the radial distance `r` from the jet axis is zero, the derivatives with respect
530
+ to `y` and `z` are set to zero to avoid division by zero.
418
531
  """
419
532
  x, y, z = xx - self.origin
420
533
  r = sqrt(y * y + z * z)
@@ -443,71 +556,201 @@ class OpenJet(FlowField):
443
556
 
444
557
 
445
558
  class RotatingFlow(FlowField):
446
- """Provides an analytical approximation of the flow field of a rotating fluid with constant flow."""
559
+ """
560
+ Analytical approximation of a rotating flow field with additional velocity component
561
+ in z-direction.
562
+
563
+ This class provides an analytical model for a fluid flow field with a
564
+ rigid-body-like rotation about the z-axis. The flow combines rotational motion
565
+ in the x-y plane and a constant velocity component in the z-direction.
447
566
 
448
- #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0.
449
- rpm = Float(0.0, desc='revolutions per minute of the virtual array; negative values for clockwise rotation')
567
+ Notes
568
+ -----
569
+ - The rotation is assumed to be about the z-axis. The velocity components in the x-y plane are
570
+ determined by the angular velocity :attr:`omega`, while the z-component is constant
571
+ and set by :attr:`v0`.
572
+ - The angular velocity :attr:`omega` is computed as: ``omega = 2 * pi * rps``,
573
+ with the :attr:`rps` given in revolutions per second (i.e. Hz).
574
+
575
+ Examples
576
+ --------
577
+ >>> import acoular as ac
578
+ >>> import numpy as np
579
+ >>>
580
+ >>> flow = RotatingFlow(rps=1, v0=1.0)
581
+ >>> velocity, jacobian = flow.v(array((1.0, 1.0, 0.0)))
582
+ >>> velocity
583
+ array([-6.28318531, 6.28318531, 1. ])
584
+ >>> jacobian
585
+ array([[ 0. , -6.28318531, 0. ],
586
+ [ 6.28318531, 0. , 0. ],
587
+ [ 0. , 0. , 0. ]])
588
+ """
450
589
 
590
+ # Revolutions per minute (RPM). Default is ``0.0``.
591
+ # Positive values indicate clockwise rotation of the flow.
592
+ # This is contrary to the usual definition of the direction of rotation.
593
+ # Deprecated! Please use the differently defined :attr:`rps` attribute instead.
594
+ rpm = Property(desc='revolutions per minute of the flow; positive values for clockwise rotation')
595
+
596
+ def _get_rpm(self):
597
+ warn(
598
+ 'Deprecated use of "rpm" trait. Please use the "rps" trait instead.',
599
+ DeprecationWarning,
600
+ stacklevel=2,
601
+ )
602
+ return -60 * self.rps
603
+
604
+ def _set_rpm(self, rpm):
605
+ warn(
606
+ 'Deprecated use of "rpm" trait. Please use the "rps" trait instead (divide rpm value by -60).',
607
+ DeprecationWarning,
608
+ stacklevel=2,
609
+ )
610
+ self.rps = -rpm / 60
611
+
612
+ #: Rotational speed in revolutions per second. Negative values indicate clockwise
613
+ #: rigid-body-like rotation of the flow. Default is ``0.0``.
614
+ rps = Float(0.0, desc='rotational speed of the flow in Hz')
615
+
616
+ #: Constant flow velocity in the z-direction. Default is ``0.0``.
451
617
  v0 = Float(0.0, desc='flow velocity')
452
618
 
453
- #: Location of the nozzle center, defaults to the co-ordinate origin.
454
- origin = CArray(dtype=float64, shape=(3,), value=array((0.0, 0.0, 0.0)), desc='center of nozzle')
619
+ #: The location of the center of rotation.
620
+ #: Default is ``(0.0, 0.0, 0.0)``.
621
+ origin = CArray(dtype=float64, shape=(3,), value=array((0.0, 0.0, 0.0)), desc='center of rotation')
455
622
 
456
- # internal identifier
623
+ #: A unique identifier based on the field properties. (read-only)
457
624
  digest = Property(
458
- depends_on=['v0', 'origin', 'rpm'],
625
+ depends_on=['v0', 'origin', 'rps'],
459
626
  )
460
627
 
461
- # internal identifier
628
+ #: Angular velocity (in radians per second) of the rotation.
629
+ #: This is a derived property based on :attr:`rps`.
462
630
  omega = Property(
463
- depends_on=['rpm'],
631
+ depends_on=['rps'],
464
632
  )
465
633
 
466
634
  @cached_property
467
635
  def _get_omega(self):
468
- return 2 * pi * self.rpm / 60
636
+ return 2 * pi * self.rps
469
637
 
470
638
  @cached_property
471
639
  def _get_digest(self):
472
640
  return digest(self)
473
641
 
474
642
  def v(self, xx):
475
- """Provides the rotating flow field around the z-Axis as a function of the location.
643
+ """
644
+ Compute the rotating flow field and its Jacobian matrix at a given location.
645
+
646
+ This method calculates the velocity vector and its Jacobian matrix at the specified location
647
+ ``xx`` in the fluid domain. The flow field consists of rotational components in the
648
+ x-y plane and a constant velocity component in the z-direction.
476
649
 
477
650
  Parameters
478
651
  ----------
479
- xx : array of floats of shape (3, )
480
- Location in the fluid for which to provide the data.
652
+ xx : :class:`numpy.ndarray` of :class:`floats<float>`
653
+ The 3D Cartesian coordinates of the location in the fluid where the velocity
654
+ field is to be computed, shape `(3,)`.
481
655
 
482
656
  Returns
483
657
  -------
484
- tuple with two elements
485
- The first element in the tuple is the velocity vector and the
486
- second is the Jacobian of the velocity vector field, both at the
487
- given location.
488
-
658
+ velocity_vector : :class:`numpy.ndarray` of :class:`floats<float>`
659
+ The velocity vector at the specified location ``xx``, shape `(3,)`. The components are:
660
+ - U: Velocity in the x-direction (dependent on y-coordinate and :attr:`omega`).
661
+ - V: Velocity in the y-direction (dependent on x-coordinate and :attr:`omega`).
662
+ - W: Constant velocity in the z-direction (set by :attr:`v0`).
663
+ jacobian_matrix : :class:`numpy.ndarray` of :class:`floats<float>`
664
+ The Jacobian matrix of the velocity vector field at the specified location ``xx``.
665
+ The matrix contains partial derivatives of each velocity component with
666
+ respect to the spatial coordinates :math:`(x, y, z)`, shape `(3, 3)`.
667
+
668
+ Notes
669
+ -----
670
+ The Jacobian matrix is constant for this flow field and represents the linear relationship
671
+ between the velocity components and spatial coordinates in the x-y plane.
489
672
  """
490
673
  x, y, z = xx - self.origin
491
674
 
492
- # rotational speed
675
+ # angular velocity
493
676
  omega = self.omega
494
677
 
495
678
  # velocity vector
496
- U = omega * y
497
- V = -omega * x
679
+ U = omega * -y
680
+ V = omega * x
498
681
  W = self.v0
499
682
 
500
683
  # flow field
501
684
  v = array((U, V, W))
502
685
  # Jacobi matrix
503
- dv = array(((0, -omega, 0.0), (omega, 0, 0.0), (0.0, 0.0, 0.0))).T
686
+ dv = array(((0.0, omega, 0.0), (-omega, 0.0, 0.0), (0.0, 0.0, 0.0))).T
504
687
  return v, dv
505
688
 
506
689
 
507
690
  def spiral_sphere(N, Om=None, b=None): # noqa: N803 # change to 4*pi
508
- """Internal helper function for the raycasting that returns an array of
509
- unit vectors (N, 3) giving equally distributed directions on a part of
510
- sphere given by the center direction b and the solid angle Om.
691
+ """
692
+ Generate unit vectors equally distributed over a sphere or a portion of it.
693
+
694
+ Internal helper function for the raycasting that returns an array of unit
695
+ vectors of shape `(N, 3)` giving equally distributed directions on a part
696
+ of sphere given by the center direction ``b`` and the solid angle ``Om``.
697
+
698
+ The function uses spherical coordinates to distribute the points, the converts them to Cartesian
699
+ coordinates. It also applies a transformation to reflect the points about a plane so that the
700
+ direction defined by the vector ``b`` points toward the center of the sphere.
701
+
702
+ Parameters
703
+ ----------
704
+ N : :class:`int`
705
+ The number of points to generate on the sphere.
706
+
707
+ Om : :class:`float`, optional
708
+ The solid angle in steradians to cover on the sphere. Default is ``2 * pi``,
709
+ which corresponds to a hemisphere. Smaller values result in covering
710
+ a smaller portion of the hemisphere.
711
+
712
+ b : :class:`numpy.ndarray` of :class:`floats<float>`, optional
713
+ A 3D unit vector specifying the desired center direction of the distribution.
714
+ Points are mirrored such that this vector points toward the center of the sphere.
715
+ Default is ``[0, 0, 1]``, which corresponds to the z-axis.
716
+
717
+ Returns
718
+ -------
719
+ :class:`numpy.ndarray` of :class:`floats<float>`
720
+ An array of unit vectors representing points on the sphere, shape `(3, N)`.
721
+ Each column corresponds to a 3D Cartesian coordinate of a point.
722
+
723
+ Notes
724
+ -----
725
+ - The points are initially distributed using a spiral pattern in spherical coordinates.
726
+ This ensures an approximately equal spacing between points over the specified portion
727
+ of the sphere.
728
+ - If a vector ``b`` is provided, the function mirrors the distribution using a
729
+ Householder reflection so that ``b`` points toward the center.
730
+ - The function avoids generating singularities at the poles by adjusting the spiral
731
+ distribution formula.
732
+
733
+ Examples
734
+ --------
735
+ Generate 100 points over a hemisphere:
736
+
737
+ >>> from acoular.environments import spiral_sphere
738
+ >>> points = spiral_sphere(100)
739
+ >>> points.shape
740
+ (3, 100)
741
+
742
+ Generate 50 points over half a hemisphere with the z-axis as the center direction:
743
+
744
+ >>> import numpy as np
745
+ >>> points = spiral_sphere(50, Om=np.pi, b=array((0, 0, 1)))
746
+ >>> points.shape
747
+ (3, 50)
748
+
749
+ Generate 200 points with a different direction vector:
750
+
751
+ >>> points = spiral_sphere(200, b=array((1, 0, 0)))
752
+ >>> points.shape
753
+ (3, 200)
511
754
  """
512
755
  Om = 2 * pi if Om is None else Om
513
756
  b = array((0, 0, 1)) if b is None else b
@@ -533,32 +776,54 @@ def spiral_sphere(N, Om=None, b=None): # noqa: N803 # change to 4*pi
533
776
 
534
777
 
535
778
  class GeneralFlowEnvironment(Environment):
536
- """An acoustic environment with a generic flow field.
537
-
538
- This class provides the facilities to calculate the travel time (distances)
539
- between grid point locations and microphone locations in a generic flow
540
- field with non-uniform velocities that depend on the location. The
541
- algorithm for the calculation uses a ray-tracing approach that bases on
542
- rays cast from every microphone position in multiple directions and traced
543
- backwards in time. The result is interpolated within a tetrahedal grid
544
- spanned between these rays.
779
+ """
780
+ An acoustic environment with a generic flow field.
781
+
782
+ This class provides the facilities to calculate the travel time (distances) between grid point
783
+ locations and microphone locations in a generic flow field with non-uniform velocities that
784
+ depend on the location. The algorithm for the calculation uses a ray-tracing approach that bases
785
+ on rays cast from every microphone position in multiple directions and traced backwards in time.
786
+ The result is interpolated within a tetrahedal grid spanned between these rays.
787
+
788
+ See Also
789
+ --------
790
+ :class:`scipy.interpolate.LinearNDInterpolator` :
791
+ Piecewise linear interpolator in N > 1 dimensions.
792
+
793
+ Examples
794
+ --------
795
+ >>> import numpy as np
796
+ >>> import acoular as ac
797
+ >>>
798
+ >>> # Instantiate the flow field
799
+ >>> flow_field = ac.OpenJet(v0=10.0, D=0.4, l=3.121)
800
+ >>>
801
+ >>> # Create an instance of GeneralFlowEnvironment
802
+ >>> environment = ac.GeneralFlowEnvironment(
803
+ ... ff=flow_field, # Use the custom flow field
804
+ ... N=300, # Number of rays
805
+ ... Om=np.pi, # Maximum solid angle
806
+ ... )
545
807
  """
546
808
 
547
- #: The flow field, must be of type :class:`~acoular.environments.FlowField`.
548
- ff = Trait(FlowField, desc='flow field')
809
+ #: The flow field object describing the velocity field,
810
+ #: which must be an instance of :class:`~acoular.environments.FlowField`.
811
+ ff = Instance(FlowField, desc='flow field')
549
812
 
550
- #: Number of rays used per solid angle :math:`\Omega`, defaults to 200.
813
+ #: The number of rays used per solid angle :math:`\Omega`. Defaults to ``200``.
551
814
  N = Int(200, desc='number of rays per Om')
552
815
 
553
- #: The maximum solid angle used in the algorithm, defaults to :math:`\pi`.
816
+ #: The maximum solid angle (in steradians) used in the ray-tracing algorithm.
817
+ #: Default is :obj:`numpy.pi`.
554
818
  Om = Float(pi, desc='maximum solid angle')
555
819
 
556
- # internal identifier
820
+ #: A unique identifier based on the environment properties. (read-only)
557
821
  digest = Property(
558
822
  depends_on=['c', 'ff.digest', 'N', 'Om'],
559
823
  )
560
824
 
561
- # internal dictionary of interpolators
825
+ #: A dictionary for storing precomputed interpolators to optimize repeated calculations.
826
+ #: (internal use)
562
827
  idict = Dict
563
828
 
564
829
  @cached_property
@@ -566,28 +831,6 @@ class GeneralFlowEnvironment(Environment):
566
831
  return digest(self)
567
832
 
568
833
  def _r(self, gpos, mpos=0.0):
569
- """Calculates the virtual distances between grid point locations and
570
- microphone locations or the origin. These virtual distances correspond
571
- to travel times of the sound along a ray that is traced through the
572
- medium. Functionality may change in the future.
573
-
574
- Parameters
575
- ----------
576
- gpos : array of floats of shape (3, N)
577
- The locations of points in the beamforming map grid in 3D cartesian
578
- co-ordinates.
579
- mpos : array of floats of shape (3, M), optional
580
- The locations of microphones in 3D cartesian co-ordinates. If not
581
- given, then only one microphone at the origin (0, 0, 0) is
582
- considered.
583
-
584
- Returns
585
- -------
586
- array of floats
587
- The distances in a twodimensional (N, M) array of floats. If M==1,
588
- then only a one-dimensional array is returned.
589
-
590
- """
591
834
  c = self.c
592
835
 
593
836
  if isscalar(mpos):
@@ -612,21 +855,30 @@ class GeneralFlowEnvironment(Environment):
612
855
  return c * gt # return distance along ray
613
856
 
614
857
  def get_interpolator(self, roi, x0):
615
- """Gets an LinearNDInterpolator object.
858
+ """
859
+ Generate an interpolator for ray travel times based on a region of interest.
860
+
861
+ This method computes the ray trajectories starting from a given microphone position (``x0``)
862
+ through a region of interest (``roi``). The rays' paths are integrated numerically using a
863
+ system of differential equations, and the resulting points are used to construct a
864
+ convex hull. A linear interpolator is then created to estimate travel times for arbitrary
865
+ points within the region.
616
866
 
617
867
  Parameters
618
868
  ----------
619
- roi : array of floats of shape (3, N)
620
- The locations of points in the region of interest in 3D cartesian
621
- co-ordinates. Used to estimate the maximum distance and ROI
622
- extension and center.
623
- x0 : array of floats of shape (3)
624
- The location of the microphone in 3D cartesian co-ordinates.
869
+ roi : :class:`numpy.ndarray` of :class:`floats<float>`
870
+ Array representing the region of interest (ROI), where each column corresponds
871
+ to a point in the 3D space :math:`(x, y, z)`, shape `(3, M)`.
872
+
873
+ x0 : :class:`numpy.ndarray` of :class:`floats<float>`
874
+ Array representing the location of the microphone in 3D Cartesian coordinates,
875
+ shape `(3,)`.
625
876
 
626
877
  Returns
627
878
  -------
628
- LinearNDInterpolator object
629
-
879
+ :class:`scipy.interpolate.LinearNDInterpolator` object
880
+ A linear interpolator object for estimating travel times for 3D positions
881
+ within the computed ray trajectories.
630
882
  """
631
883
  c = self.c
632
884