acoular 25.1__py3-none-any.whl → 25.3.post1__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,10 +14,14 @@
13
14
  OpenJet
14
15
  RotatingFlow
15
16
  SlotJet
16
-
17
+ dist_mat
18
+ cylToCart
19
+ cartToCyl
20
+ spiral_sphere
17
21
  """
18
22
 
19
23
  from abc import abstractmethod
24
+ from warnings import warn
20
25
 
21
26
  import numba as nb
22
27
  from numpy import (
@@ -70,17 +75,28 @@ f32ro = nb.types.Array(nb.types.float32, 2, 'C', readonly=True)
70
75
 
71
76
  @nb.njit([(f64ro, f64ro), (f64ro, f32ro), (f32ro, f64ro), (f32ro, f32ro)], cache=True, fastmath=True)
72
77
  def dist_mat(gpos, mpos): # pragma: no cover
73
- """Computes distance matrix, accelerated with numba.
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.
85
+
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)`.
74
91
 
75
- Args:
76
- ----
77
- gpos (3,N)
78
- mpos (3,M)
92
+ mpos : :class:`numpy.ndarray` of :class:`floats<float>`
93
+ The locations of `M` microphones in 3D cartesian coordinates, shape `(3, M)`.
79
94
 
80
95
  Returns
81
96
  -------
82
- (N,M) distance matrix
83
-
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)`.
84
100
  """
85
101
  _, M = mpos.shape
86
102
  _, N = gpos.shape
@@ -99,24 +115,27 @@ def dist_mat(gpos, mpos): # pragma: no cover
99
115
 
100
116
 
101
117
  def cartToCyl(x, Q=None): # noqa: N802, N803
102
- """Returns the cylindrical coordinate representation of a input position
103
- which was before transformed into a modified cartesian coordinate, which
104
- 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.
105
123
 
106
124
  Parameters
107
125
  ----------
108
- x : float[3, nPoints]
109
- cartesian coordinates of n points
110
- Q : float[3,3]
111
- Orthogonal transformation matrix. If provided, the pos vectors are
112
- transformed via posiMod = Q * x, before transforming those modified
113
- 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.
114
133
 
115
134
  Returns
116
135
  -------
117
- cylCoord : [3, nPoints]
118
- cylindrical representation of those n points with (phi, r, z)
119
-
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)`.
120
139
  """
121
140
  Q = identity(3) if Q is None else Q
122
141
  if not (Q == identity(3)).all(): # noqa: SIM300
@@ -125,27 +144,27 @@ def cartToCyl(x, Q=None): # noqa: N802, N803
125
144
 
126
145
 
127
146
  def cylToCart(x, Q=None): # noqa: N802, N803
128
- """Returns the cartesian coordinate representation of a input position
129
- which was before transformed into a cylindrical coordinate, which
130
- 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.
131
152
 
132
153
  Parameters
133
154
  ----------
134
- x : float[3, nPoints]
135
- cylindrical representation of those n points with (phi, r, z)
136
- cartesian coordinates of n points
137
-
138
- Q : float[3,3]
139
- Orthogonal transformation matrix. If provided, the pos vectors are
140
- transformed via posiMod = Q * x, before transforming those modified
141
- 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)`.
142
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.
143
162
 
144
163
  Returns
145
164
  -------
146
- CartCoord : [3, nPoints]
147
- cartesian coordinates of n points
148
-
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)`.
149
168
  """
150
169
  Q = identity(3) if Q is None else Q
151
170
  if not (Q == identity(3)).all(): # noqa: SIM300
@@ -154,47 +173,64 @@ def cylToCart(x, Q=None): # noqa: N802, N803
154
173
 
155
174
 
156
175
  class Environment(HasStrictTraits):
157
- """A simple acoustic environment without flow.
176
+ """
177
+ A simple acoustic environment without flow.
158
178
 
159
- This class provides the facilities to calculate the travel time (distances)
160
- between grid point locations and microphone locations.
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.
182
+
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.
161
189
  """
162
190
 
163
- # internal identifier
164
- digest = Property(
165
- depends_on=['c'],
166
- )
191
+ #: A unique identifier based on the environment properties. (read-only)
192
+ digest = Property(depends_on=['c'])
167
193
 
168
- #: 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.
169
196
  c = Float(343.0, desc='speed of sound')
170
197
 
171
- #: The region of interest (ROI), not needed for most types of environment
198
+ #: The region of interest (ROI) for calculations. (Not needed for most types of environment.)
199
+ #: Default is :obj:`None`.
172
200
  roi = Union(None, CArray)
173
201
 
174
202
  def _get_digest(self):
175
203
  return digest(self)
176
204
 
177
205
  def _r(self, gpos, mpos=0.0):
178
- """Calculates distances between grid point locations and microphone
179
- locations or the origin. Functionality may change in the future.
180
-
181
- Parameters
182
- ----------
183
- gpos : array of floats of shape (3, N)
184
- The locations of points in the beamforming map grid in 3D cartesian
185
- co-ordinates.
186
- mpos : array of floats of shape (3, M), optional
187
- The locations of microphones in 3D cartesian co-ordinates. If not
188
- given, then only one microphone at the origin (0, 0, 0) is
189
- considered.
190
-
191
- Returns
192
- -------
193
- r : array of floats
194
- The distances in a twodimensional (N, M) array of floats. If M==1,
195
- then only a one-dimensional array is returned.
196
-
197
- """
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.
198
234
  if isscalar(mpos):
199
235
  mpos = array((0, 0, 0), dtype=float64)[:, newaxis]
200
236
  rm = dist_mat(ascontiguousarray(gpos), ascontiguousarray(mpos))
@@ -207,21 +243,31 @@ class Environment(HasStrictTraits):
207
243
 
208
244
 
209
245
  class UniformFlowEnvironment(Environment):
210
- """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.
211
252
 
212
- This class provides the facilities to calculate the travel time (distances)
213
- between grid point locations and microphone locations in a uniform flow
214
- field.
253
+ The flow is assumed to be uniform and steady, characterized by its Mach number (:attr:`ma`)
254
+ and direction (:attr:`fdv`).
255
+
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.
215
260
  """
216
261
 
217
- #: 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.
218
264
  ma = Float(0.0, desc='flow mach number')
219
265
 
220
- #: The unit vector that gives the direction of the flow, defaults to
221
- #: 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.
222
268
  fdv = CArray(dtype=float64, shape=(3,), value=array((1.0, 0, 0)), desc='flow direction')
223
269
 
224
- # internal identifier
270
+ #: A unique identifier based on the environment properties. (read-only)
225
271
  digest = Property(
226
272
  depends_on=['c', 'ma', 'fdv'],
227
273
  )
@@ -231,27 +277,30 @@ class UniformFlowEnvironment(Environment):
231
277
  return digest(self)
232
278
 
233
279
  def _r(self, gpos, mpos=0.0):
234
- """Calculates the virtual distances between grid point locations and
235
- microphone locations or the origin. These virtual distances correspond
236
- to travel times of the sound. Functionality may change in the future.
237
-
238
- Parameters
239
- ----------
240
- gpos : array of floats of shape (3, N)
241
- The locations of points in the beamforming map grid in 3D cartesian
242
- co-ordinates.
243
- mpos : array of floats of shape (3, M), optional
244
- The locations of microphones in 3D cartesian co-ordinates. If not
245
- given, then only one microphone at the origin (0, 0, 0) is
246
- considered.
247
-
248
- Returns
249
- -------
250
- array of floats
251
- The distances in a twodimensional (N, M) array of floats. If M==1,
252
- then only a one-dimensional array is returned.
253
-
254
- """
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.
255
304
  if isscalar(mpos):
256
305
  mpos = array((0, 0, 0), dtype=float32)[:, newaxis]
257
306
  fdv = self.fdv / sqrt((self.fdv * self.fdv).sum())
@@ -268,6 +317,7 @@ class UniformFlowEnvironment(Environment):
268
317
  class FlowField(ABCHasStrictTraits):
269
318
  """An abstract base class for a spatial flow field."""
270
319
 
320
+ #: A unique identifier based on the field properties. (read-only)
271
321
  digest = Property
272
322
 
273
323
  @abstractmethod
@@ -276,50 +326,55 @@ class FlowField(ABCHasStrictTraits):
276
326
 
277
327
  @abstractmethod
278
328
  def v(self, xx): # noqa: ARG002
279
- """Provides the flow field as a function of the location.
329
+ """
330
+ Provide the flow field as a function of the location.
280
331
 
281
332
  Parameters
282
333
  ----------
283
- xx : array of floats of shape (3, )
284
- Location in the fluid for which to provide the data.
285
-
286
- Returns
287
- -------
288
- tuple
289
- A tuple with two elements:
290
- - velocity_vector : array of floats of shape (3, )
291
- The velocity vector at the given location.
292
- - jacobian_matrix : array of floats of shape (3, 3)
293
- The Jacobian matrix of the velocity vector field at the given location.
334
+ xx : :class:`numpy.ndarray` of :class:`floats<float>`
335
+ Location in the fluid for which to provide the data, shape (3,).
294
336
  """
295
337
 
296
338
 
297
339
  class SlotJet(FlowField):
298
- """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.
299
347
 
300
- 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.
301
355
  """
302
356
 
303
- #: 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``.
304
358
  v0 = Float(0.0, desc='exit velocity')
305
359
 
306
- #: Location of a point at the slot center line,
307
- #: defaults to the co-ordinate origin.
360
+ #: The location of the slot nozzle center. Default is ``(0.0, 0.0, 0.0)``.
308
361
  origin = CArray(dtype=float64, shape=(3,), value=array((0.0, 0.0, 0.0)), desc='center of nozzle')
309
362
 
310
- #: 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)``.
311
364
  flow = CArray(dtype=float64, shape=(3,), value=array((1.0, 0.0, 0.0)), desc='flow direction')
312
365
 
313
- #: 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)``.
314
368
  plane = CArray(dtype=float64, shape=(3,), value=array((0.0, 1.0, 0.0)), desc='slot center line direction')
315
369
 
316
- #: Width of the slot, defaults to 0.2 .
370
+ #: Width of the slot (slot diameter). Default is ``0.2``.
317
371
  B = Float(0.2, desc='nozzle diameter')
318
372
 
319
- #: 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``.
320
375
  l = Float(5.2, desc='flow establishment length') # noqa: E741
321
376
 
322
- # internal identifier
377
+ #: A unique identifier based on the field properties. (read-only)
323
378
  digest = Property(
324
379
  depends_on=['v0', 'origin', 'flow', 'plane', 'B', 'l'],
325
380
  )
@@ -329,32 +384,45 @@ class SlotJet(FlowField):
329
384
  return digest(self)
330
385
 
331
386
  def v(self, xx):
332
- """Provides the flow field as a function of the location. This is
333
- implemented here only for the component in the direction of :attr:`flow`;
334
- 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.
335
393
 
336
394
  Parameters
337
395
  ----------
338
- xx : array of floats of shape (3, )
339
- 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,)`.
340
399
 
341
400
  Returns
342
401
  -------
343
- tuple with two elements
344
- The first element in the tuple is the velocity vector and the
345
- second is the Jacobian of the velocity vector field, both at the
346
- given location.
347
-
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.
348
416
  """
349
417
  # normalize
350
418
  flow = self.flow / norm(self.flow)
351
419
  plane = self.plane / norm(self.plane)
352
- # additional axes of global co-ordinate system
420
+ # additional axes of global coordinate system
353
421
  yy = -cross(flow, plane)
354
422
  zz = cross(flow, yy)
355
423
  # distance from slot exit plane
356
424
  xx1 = xx - self.origin
357
- # local co-ordinate system
425
+ # local coordinate system
358
426
  x = dot(flow, xx1)
359
427
  y = dot(yy, xx1)
360
428
  x1 = 0.5668 / self.l * x # C1 in Albertson1950
@@ -377,31 +445,50 @@ class SlotJet(FlowField):
377
445
 
378
446
 
379
447
  class OpenJet(FlowField):
380
- """Provides an analytical approximation of the flow field of an open jet.
448
+ """
449
+ Analytical approximation of the flow field of an open jet.
381
450
 
382
- 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.
383
454
 
384
455
  Notes
385
456
  -----
386
- This is not a fully generic implementation and is limited to flow in the
387
- x-direction only. No other directions are possible at the moment and flow
388
- components in the other direction are zero.
389
-
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. ]])
390
476
  """
391
477
 
392
- #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0.
478
+ #: Exit velocity at the jet origin (nozzle). Default is ``0.0``.
393
479
  v0 = Float(0.0, desc='exit velocity')
394
480
 
395
- #: 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)``.
396
482
  origin = CArray(dtype=float64, shape=(3,), value=array((0.0, 0.0, 0.0)), desc='center of nozzle')
397
483
 
398
- #: Diameter of the nozzle, defaults to 0.2 .
484
+ #: Diameter of the nozzle. Default is ``0.2``.
399
485
  D = Float(0.2, desc='nozzle diameter')
400
486
 
401
- #: 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`
402
489
  l = Float(6.2, desc='flow establishment length') # noqa: E741
403
490
 
404
- # internal identifier
491
+ #: A unique identifier based on the field properties. (read-only)
405
492
  digest = Property(
406
493
  depends_on=['v0', 'origin', 'D', 'l'],
407
494
  )
@@ -411,22 +498,36 @@ class OpenJet(FlowField):
411
498
  return digest(self)
412
499
 
413
500
  def v(self, xx):
414
- """Provides the flow field as a function of the location. This is
415
- implemented here only for a jet in `x`-direction and the `y`- and
416
- `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.
417
507
 
418
508
  Parameters
419
509
  ----------
420
- xx : array of floats of shape (3, )
421
- 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,)`.
422
513
 
423
514
  Returns
424
515
  -------
425
- tuple with two elements
426
- The first element in the tuple is the velocity vector and the
427
- second is the Jacobian of the velocity vector field, both at the
428
- given location.
429
-
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.
430
531
  """
431
532
  x, y, z = xx - self.origin
432
533
  r = sqrt(y * y + z * z)
@@ -455,71 +556,201 @@ class OpenJet(FlowField):
455
556
 
456
557
 
457
558
  class RotatingFlow(FlowField):
458
- """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.
459
562
 
460
- #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0.
461
- rpm = Float(0.0, desc='revolutions per minute of the virtual array; negative values for clockwise rotation')
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.
462
566
 
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
+ """
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``.
463
617
  v0 = Float(0.0, desc='flow velocity')
464
618
 
465
- #: Location of the nozzle center, defaults to the co-ordinate origin.
466
- 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')
467
622
 
468
- # internal identifier
623
+ #: A unique identifier based on the field properties. (read-only)
469
624
  digest = Property(
470
- depends_on=['v0', 'origin', 'rpm'],
625
+ depends_on=['v0', 'origin', 'rps'],
471
626
  )
472
627
 
473
- # internal identifier
628
+ #: Angular velocity (in radians per second) of the rotation.
629
+ #: This is a derived property based on :attr:`rps`.
474
630
  omega = Property(
475
- depends_on=['rpm'],
631
+ depends_on=['rps'],
476
632
  )
477
633
 
478
634
  @cached_property
479
635
  def _get_omega(self):
480
- return 2 * pi * self.rpm / 60
636
+ return 2 * pi * self.rps
481
637
 
482
638
  @cached_property
483
639
  def _get_digest(self):
484
640
  return digest(self)
485
641
 
486
642
  def v(self, xx):
487
- """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.
488
649
 
489
650
  Parameters
490
651
  ----------
491
- xx : array of floats of shape (3, )
492
- 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,)`.
493
655
 
494
656
  Returns
495
657
  -------
496
- tuple with two elements
497
- The first element in the tuple is the velocity vector and the
498
- second is the Jacobian of the velocity vector field, both at the
499
- given location.
500
-
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.
501
672
  """
502
673
  x, y, z = xx - self.origin
503
674
 
504
- # rotational speed
675
+ # angular velocity
505
676
  omega = self.omega
506
677
 
507
678
  # velocity vector
508
- U = omega * y
509
- V = -omega * x
679
+ U = omega * -y
680
+ V = omega * x
510
681
  W = self.v0
511
682
 
512
683
  # flow field
513
684
  v = array((U, V, W))
514
685
  # Jacobi matrix
515
- 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
516
687
  return v, dv
517
688
 
518
689
 
519
690
  def spiral_sphere(N, Om=None, b=None): # noqa: N803 # change to 4*pi
520
- """Internal helper function for the raycasting that returns an array of
521
- unit vectors (N, 3) giving equally distributed directions on a part of
522
- 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)
523
754
  """
524
755
  Om = 2 * pi if Om is None else Om
525
756
  b = array((0, 0, 1)) if b is None else b
@@ -545,32 +776,54 @@ def spiral_sphere(N, Om=None, b=None): # noqa: N803 # change to 4*pi
545
776
 
546
777
 
547
778
  class GeneralFlowEnvironment(Environment):
548
- """An acoustic environment with a generic flow field.
549
-
550
- This class provides the facilities to calculate the travel time (distances)
551
- between grid point locations and microphone locations in a generic flow
552
- field with non-uniform velocities that depend on the location. The
553
- algorithm for the calculation uses a ray-tracing approach that bases on
554
- rays cast from every microphone position in multiple directions and traced
555
- backwards in time. The result is interpolated within a tetrahedal grid
556
- 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
+ ... )
557
807
  """
558
808
 
559
- #: The flow field, must be of type :class:`~acoular.environments.FlowField`.
809
+ #: The flow field object describing the velocity field,
810
+ #: which must be an instance of :class:`~acoular.environments.FlowField`.
560
811
  ff = Instance(FlowField, desc='flow field')
561
812
 
562
- #: 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``.
563
814
  N = Int(200, desc='number of rays per Om')
564
815
 
565
- #: 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`.
566
818
  Om = Float(pi, desc='maximum solid angle')
567
819
 
568
- # internal identifier
820
+ #: A unique identifier based on the environment properties. (read-only)
569
821
  digest = Property(
570
822
  depends_on=['c', 'ff.digest', 'N', 'Om'],
571
823
  )
572
824
 
573
- # internal dictionary of interpolators
825
+ #: A dictionary for storing precomputed interpolators to optimize repeated calculations.
826
+ #: (internal use)
574
827
  idict = Dict
575
828
 
576
829
  @cached_property
@@ -578,28 +831,6 @@ class GeneralFlowEnvironment(Environment):
578
831
  return digest(self)
579
832
 
580
833
  def _r(self, gpos, mpos=0.0):
581
- """Calculates the virtual distances between grid point locations and
582
- microphone locations or the origin. These virtual distances correspond
583
- to travel times of the sound along a ray that is traced through the
584
- medium. Functionality may change in the future.
585
-
586
- Parameters
587
- ----------
588
- gpos : array of floats of shape (3, N)
589
- The locations of points in the beamforming map grid in 3D cartesian
590
- co-ordinates.
591
- mpos : array of floats of shape (3, M), optional
592
- The locations of microphones in 3D cartesian co-ordinates. If not
593
- given, then only one microphone at the origin (0, 0, 0) is
594
- considered.
595
-
596
- Returns
597
- -------
598
- array of floats
599
- The distances in a twodimensional (N, M) array of floats. If M==1,
600
- then only a one-dimensional array is returned.
601
-
602
- """
603
834
  c = self.c
604
835
 
605
836
  if isscalar(mpos):
@@ -624,21 +855,30 @@ class GeneralFlowEnvironment(Environment):
624
855
  return c * gt # return distance along ray
625
856
 
626
857
  def get_interpolator(self, roi, x0):
627
- """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.
628
866
 
629
867
  Parameters
630
868
  ----------
631
- roi : array of floats of shape (3, N)
632
- The locations of points in the region of interest in 3D cartesian
633
- co-ordinates. Used to estimate the maximum distance and ROI
634
- extension and center.
635
- x0 : array of floats of shape (3)
636
- 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,)`.
637
876
 
638
877
  Returns
639
878
  -------
640
- LinearNDInterpolator object
641
-
879
+ :class:`scipy.interpolate.LinearNDInterpolator` object
880
+ A linear interpolator object for estimating travel times for 3D positions
881
+ within the computed ray trajectories.
642
882
  """
643
883
  c = self.c
644
884