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/grids.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # ------------------------------------------------------------------------------
2
2
  # Copyright (c) Acoular Development Team.
3
3
  # ------------------------------------------------------------------------------
4
- """Implements support for two- and threedimensional grids.
4
+ """
5
+ Implement support for multidimensional grids and integration sectors.
5
6
 
6
7
  .. autosummary::
7
8
  :toctree: generated/
@@ -20,12 +21,13 @@
20
21
  PolySector
21
22
  ConvexSector
22
23
  MultiSector
23
-
24
+ Polygon
25
+ in_hull
24
26
  """
25
27
 
26
28
  # imports from other packages
27
- from os import path
28
- from warnings import warn
29
+ import xml.dom.minidom
30
+ from abc import abstractmethod
29
31
 
30
32
  from numpy import (
31
33
  absolute,
@@ -45,7 +47,7 @@ from numpy import (
45
47
  ones,
46
48
  ones_like,
47
49
  s_,
48
- sum,
50
+ sum, # noqa: A004
49
51
  tile,
50
52
  unique,
51
53
  where,
@@ -56,61 +58,83 @@ from scipy.linalg import norm
56
58
  # from matplotlib.path import Path
57
59
  from scipy.spatial import Delaunay
58
60
  from traits.api import (
61
+ ABCHasStrictTraits,
59
62
  Bool,
60
63
  CArray,
61
64
  File,
62
65
  Float,
63
- HasPrivateTraits,
64
66
  Instance,
65
67
  Int,
66
68
  List,
67
69
  Property,
70
+ Str,
68
71
  Tuple,
69
72
  Union,
70
73
  cached_property,
74
+ observe,
71
75
  on_trait_change,
72
76
  property_depends_on,
73
77
  )
74
78
  from traits.trait_errors import TraitError
75
79
 
76
- from .internal import digest
80
+ # acoular imports
81
+ from .deprecation import deprecated_alias
82
+ from .internal import digest, ldigest
77
83
 
78
84
 
79
85
  def in_hull(p, hull, border=True, tol=0):
80
- """Test if points in `p` are in `hull`
81
- `p` should be a `NxK` coordinates of `N` points in `K` dimensions
82
- `hull` is either a scipy.spatial.Delaunay object or the `MxK` array of the
83
- coordinates of `M` points in `K`dimensions for which Delaunay triangulation
84
- will be computed.
85
86
  """
86
- if not isinstance(hull, Delaunay):
87
- hull = Delaunay(hull)
88
-
89
- if border:
90
- return hull.find_simplex(p, tol=tol) >= 0
91
- return hull.find_simplex(p, tol=tol) > 0
87
+ Test if points in ``p`` are in ``hull``, in- or excluding the border.
92
88
 
89
+ Parameters
90
+ ----------
91
+ p : :class:`numpy.ndarray` of :class:`floats<float>`, shape `(N, K)`
92
+ Coordinates of `N` points in `K` dimensions.
93
93
 
94
- def _det(xvert, yvert):
95
- """Compute twice the area of the triangle defined by points with using
96
- determinant formula.
94
+ hull : :class:`numpy.ndarray` of :class:`floats<float>`, shape `(M, K)`, or :class:`~scipy.spatial.Delaunay` object
95
+ Coordinates of `M` points in `K` dimensions for which Delaunay triangulation will be
96
+ computed.
97
97
 
98
- Input parameters:
98
+ border : bool, optional
99
+ Points in :attr:`p` on the border of :attr:`hull` will be kept in the return if `True`. If
100
+ `False`, only points inside :attr:`hull` will be kept. Default is `True`.
99
101
 
100
- xvert -- A vector of nodal x-coords (array-like).
101
- yvert -- A vector of nodal y-coords (array-like).
102
+ tol : :class:`float`, optional
103
+ Tolerance allowed in the :meth:`inside-triangle check<scipy.spatial.Delaunay.find_simplex>`.
104
+ Default is ``0``.
102
105
 
103
- Output parameters:
104
- Twice the area of the triangle defined by the points.
106
+ Returns
107
+ -------
108
+ :class:`numpy.ndarray` of :class:`bools<bool>`
109
+ An array of boolean values indicating which points in ``p`` are inside the hull, same
110
+ shape as ``p``. Each entry is ``True`` if the corresponding point is inside the hull (or
111
+ on the border, if ``border=True``), and ``False`` otherwise.
105
112
 
106
113
  Notes
107
114
  -----
108
- _det is positive if points define polygon in anticlockwise order.
109
- _det is negative if points define polygon in clockwise order.
110
- _det is zero if at least two of the points are concident or if
111
- all points are collinear.
115
+ This function uses Delaunay triangulation to determine if a point is inside the convex hull,
116
+ which is efficient and robust for arbitrary shapes in higher-dimensional spaces.
117
+
118
+ Examples
119
+ --------
120
+ >>> from acoular.grids import in_hull
121
+ >>> import numpy as np
122
+ >>> from scipy.spatial import Delaunay
123
+ >>> points = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
124
+ >>> hull = Delaunay(points)
125
+ >>> p = np.array([[0.5, 0.5], [2, 2]])
126
+ >>> in_hull(p, hull)
127
+ array([ True, False])
128
+ """ # noqa W505
129
+ if not isinstance(hull, Delaunay):
130
+ hull = Delaunay(hull)
112
131
 
113
- """
132
+ if border:
133
+ return hull.find_simplex(p, tol=tol) >= 0
134
+ return hull.find_simplex(p, tol=tol) > 0
135
+
136
+
137
+ def _det(xvert, yvert):
114
138
  xvert = asarray(xvert, dtype=float)
115
139
  yvert = asarray(yvert, dtype=float)
116
140
  x_prev = concatenate(([xvert[-1]], xvert[:-1]))
@@ -119,10 +143,30 @@ def _det(xvert, yvert):
119
143
 
120
144
 
121
145
  class Polygon:
122
- """Polygon object.
123
- Input parameters:
124
- x -- A sequence of nodal x-coords.
125
- y -- A sequence of nodal y-coords.
146
+ """
147
+ Create an object representing a general polygon in a 2D plane.
148
+
149
+ This class allows defining a polygon by specifying the coordinates of its vertices and provides
150
+ methods for checking whether a set of points lies inside the polygon, or if a point is closer to
151
+ a side or vertex of the polygon.
152
+
153
+ Parameters
154
+ ----------
155
+ x : array_like
156
+ Array of x-coordinates of the vertices that define the polygon. These coordinates should
157
+ form a closed shape (i.e., the last point should be the same as the first point).
158
+
159
+ y : array_like
160
+ Array of y-coordinates of the vertices that define the polygon. These coordinates should
161
+ correspond to the x-coordinates, forming a closed shape.
162
+
163
+ Attributes
164
+ ----------
165
+ x : :class:`numpy.ndarray`
166
+ Array of x-coordinates of the polygon vertices.
167
+
168
+ y : :class:`numpy.ndarray`
169
+ Array of y-coordinates of the polygon vertices.
126
170
  """
127
171
 
128
172
  def __init__(self, x, y):
@@ -143,29 +187,34 @@ class Polygon:
143
187
  self.y = self.y[::-1]
144
188
 
145
189
  def is_inside(self, xpoint, ypoint, smalld=1e-12):
146
- """Check if point is inside a general polygon.
190
+ """
191
+ Check if a point or set of points are inside the polygon.
147
192
 
148
- Input parameters:
149
- xpoint -- The x-coord of the point to be tested.
150
- ypoint -- The y-coords of the point to be tested.
151
- smalld -- A small float number.
193
+ Parameters
194
+ ----------
195
+ xpoint : :class:`float` or array_like
196
+ Array of x-coordinates of the points to be tested.
152
197
 
153
- xpoint and ypoint could be scalars or array-like sequences.
198
+ ypoint : :class:`float` or array_like
199
+ Array of y-coordinates of the points to be tested.
154
200
 
155
- Output parameters:
201
+ smalld : :class:`float`, optional
202
+ Tolerance used for floating point comparisons when checking if a point is exactly on a
203
+ polygon's edge. The default value is ``1e-12``.
156
204
 
157
- mindst -- The distance from the point to the nearest point of the
158
- polygon.
159
- If mindst < 0 then point is outside the polygon.
160
- If mindst = 0 then point in on a side of the polygon.
161
- If mindst > 0 then point is inside the polygon.
205
+ Returns
206
+ -------
207
+ :class:`float` or array_like
208
+ The distance from the point to the nearest point on the polygon. The values returned
209
+ have the following meanings:
210
+ - ``mindst < 0``: Point is outside the polygon.
211
+ - ``mindst = 0``: Point is on an edge of the polygon.
212
+ - ``mindst > 0``: Point is inside the polygon.
162
213
 
163
214
  Notes
164
215
  -----
165
- An improved version of the algorithm of Nordbeck and Rydstedt.
166
- REF: SLOAN, S.W. (1985): A point-in-polygon program. Adv. Eng.
167
- Software, Vol 7, No. 1, pp 45-47.
168
-
216
+ The method uses an improved algorithm based on Nordbeck and Rydstedt for determining
217
+ whether a point is inside a polygon :cite:`SLOAN198545`.
169
218
  """
170
219
  xpoint = asarray(xpoint, dtype=float)
171
220
  ypoint = asarray(ypoint, dtype=float)
@@ -252,142 +301,135 @@ class Polygon:
252
301
  return mindst
253
302
 
254
303
 
255
- class Grid(HasPrivateTraits):
256
- """Virtual base class for grid geometries.
304
+ @deprecated_alias({'gpos': 'pos'})
305
+ class Grid(ABCHasStrictTraits):
306
+ """
307
+ Abstract base class for grid geometries.
257
308
 
258
- Defines the common interface for all grid classes and
259
- provides facilities to query grid properties and related data. This class
260
- may be used as a base for specialized grid implementaions. It should not
261
- be used directly as it contains no real functionality.
309
+ This class defines a common interface for all grid geometries and provides tools to query grid
310
+ properties and related data. It is intended to serve as a base class for specialized grid
311
+ implementations and should not be instantiated directly as it lacks concrete functionality.
262
312
  """
263
313
 
264
- #: Overall number of grid points. Readonly; is set automatically when
265
- #: other grid defining properties are set
314
+ #: The total number of grid points. This property is automatically calculated based on other
315
+ #: defining attributes of the grid. (read-only)
266
316
  size = Property(desc='overall number of grid points')
267
317
 
268
- #: Shape of grid. Readonly, gives the shape as tuple, useful for cartesian
269
- #: grids
318
+ #: The shape of the grid, represented as a tuple. Primarily useful for Cartesian grids.
319
+ #: (read-only)
270
320
  shape = Property(desc='grid shape as tuple')
271
321
 
272
- #: Grid positions as (3, :attr:`size`) array of floats, without invalid
273
- #: microphones; readonly.
274
- gpos = Property(desc='x, y, z positions of grid points')
322
+ #: The grid positions represented as a (3, :attr:`size`) array of :class:`floats<float>`.
323
+ #: (read-only)
324
+ pos = Property(desc='x, y, z positions of grid points')
275
325
 
276
- # internal identifier
326
+ #: A unique identifier for the grid, based on its properties. (read-only)
277
327
  digest = Property
278
328
 
329
+ @abstractmethod
279
330
  def _get_digest(self):
280
- return ''
331
+ """Generate a unique digest for the grid."""
281
332
 
282
- # 'digest' is a placeholder for other properties in derived classes,
283
- # necessary to trigger the depends on mechanism
284
- @property_depends_on('digest')
333
+ # 'digest' is a placeholder for other properties in derived classes, necessary to trigger the
334
+ # depends on mechanism
335
+ @property_depends_on(['digest'])
336
+ @abstractmethod
285
337
  def _get_size(self):
286
- return 1
338
+ """Return the number of grid points."""
287
339
 
288
340
  # 'digest' is a placeholder for other properties in derived classes
289
- @property_depends_on('digest')
341
+ @property_depends_on(['digest'])
342
+ @abstractmethod
290
343
  def _get_shape(self):
291
- return (1, 1)
292
-
293
- @property_depends_on('digest')
294
- def _get_gpos(self):
295
- return array([[0.0], [0.0], [0.0]])
296
-
297
- def pos(self):
298
- """Calculates grid co-ordinates.
299
- Deprecated; use :attr:`gpos` attribute instead.
300
- The :meth:`pos` method will be removed in version 25.01.
301
-
302
- Returns
303
- -------
304
- array of floats of shape (3, :attr:`size`)
305
- The grid point x, y, z-coordinates in one array.
344
+ """Return the shape of the grid as a Tuple."""
306
345
 
307
- """
308
- msg = (
309
- "The 'pos' method is deprecated and will be removed in version 25.01. " "Use the 'gpos' attribute instead."
310
- )
311
- warn(msg, DeprecationWarning, stacklevel=2)
312
- return self.gpos # array([[0.], [0.], [0.]])
346
+ @property_depends_on(['digest'])
347
+ @abstractmethod
348
+ def _get_pos(self):
349
+ """Return the grid positions as array of floats, shape (3, :attr:`size`)."""
313
350
 
314
351
  def subdomain(self, sector):
315
- """Queries the indices for a subdomain in the grid.
352
+ """
353
+ Return the indices for a subdomain in the grid.
316
354
 
317
- Allows arbitrary subdomains of type :class:`Sector`
355
+ Allows arbitrary subdomains of type :class:`Sector`.
318
356
 
319
357
  Parameters
320
358
  ----------
321
- sector : :class:`Sector`
359
+ sector : :class:`Sector` object
322
360
  Sector describing the subdomain.
323
361
 
324
362
  Returns
325
363
  -------
326
- 2-tuple of arrays of integers or of numpy slice objects
327
- The indices that can be used to mask/select the grid subdomain from
328
- an array with the same shape as the grid.
364
+ :class:`tuple`
365
+ A 2-tuple of arrays of integers or :obj:`numpy.s_` objects that can be used to mask or
366
+ select the specified subdomain from a grid-shaped array.
329
367
 
368
+ Notes
369
+ -----
370
+ The :func:`numpy.where` method is used to determine the the indices.
330
371
  """
331
- xpos = self.gpos
372
+ xpos = self.pos
332
373
  # construct grid-shaped array with "True" entries where sector is
333
374
  xyi = sector.contains(xpos).reshape(self.shape)
334
375
  # return indices of "True" entries
335
376
  return where(xyi)
336
377
 
337
378
 
379
+ @deprecated_alias({'gpos': 'pos'}, read_only=True)
338
380
  class RectGrid(Grid):
339
- """Provides a cartesian 2D grid for the beamforming results.
381
+ """
382
+ Provides a 2D Cartesian grid for beamforming results.
340
383
 
341
- The grid has square or nearly square cells and is on a plane perpendicular
342
- to the z-axis. It is defined by lower and upper x- and y-limits and the
343
- z co-ordinate.
384
+ This grid is composed of square or nearly square cells and lies on a plane perpendicular
385
+ to the z-axis. It is defined by the lower and upper x- and y-limits and a constant z-coordinate.
344
386
  """
345
387
 
346
- #: The lower x-limit that defines the grid, defaults to -1.
388
+ #: The lower x-limit that defines the grid. Default is ``-1``.
347
389
  x_min = Float(-1.0, desc='minimum x-value')
348
390
 
349
- #: The upper x-limit that defines the grid, defaults to 1.
391
+ #: The upper x-limit that defines the grid. Default is ``1``.
350
392
  x_max = Float(1.0, desc='maximum x-value')
351
393
 
352
- #: The lower y-limit that defines the grid, defaults to -1.
394
+ #: The lower y-limit that defines the grid. Default is ``-1``.
353
395
  y_min = Float(-1.0, desc='minimum y-value')
354
396
 
355
- #: The upper y-limit that defines the grid, defaults to 1.
397
+ #: The upper y-limit that defines the grid. Default is ``1``.
356
398
  y_max = Float(1.0, desc='maximum y-value')
357
399
 
358
- #: The z co-ordinate that defines the grid, defaults to 1.
400
+ #: The constant z-coordinate of the grid plane. Default is ``1.0``.
359
401
  z = Float(1.0, desc='position on z-axis')
360
402
 
361
- #: The cell side length for the grid, defaults to 0.1.
403
+ #: The side length of each cell. Default is ``0.1``.
362
404
  increment = Float(0.1, desc='step size')
363
405
 
364
- #: Number of grid points along x-axis, readonly.
406
+ #: Number of grid points along x-axis. (read-only)
365
407
  nxsteps = Property(desc='number of grid points along x-axis')
366
408
 
367
- #: Number of grid points along y-axis, readonly.
409
+ #: Number of grid points along y-axis. (read-only)
368
410
  nysteps = Property(desc='number of grid points along y-axis')
369
411
 
370
- # internal identifier
412
+ #: A unique identifier for the grid, based on its properties. (read-only)
371
413
  digest = Property(
372
414
  depends_on=['x_min', 'x_max', 'y_min', 'y_max', 'z', 'increment'],
373
415
  )
374
416
 
375
- @property_depends_on('nxsteps, nysteps')
417
+ @property_depends_on(['nxsteps', 'nysteps'])
376
418
  def _get_size(self):
377
419
  return self.nxsteps * self.nysteps
378
420
 
379
- @property_depends_on('nxsteps, nysteps')
421
+ @property_depends_on(['nxsteps', 'nysteps'])
380
422
  def _get_shape(self):
381
423
  return (self.nxsteps, self.nysteps)
382
424
 
383
- @property_depends_on('x_min, x_max, increment')
425
+ @property_depends_on(['x_min', 'x_max', 'increment'])
384
426
  def _get_nxsteps(self):
385
427
  i = abs(self.increment)
386
428
  if i != 0:
387
429
  return int(round((abs(self.x_max - self.x_min) + i) / i))
388
430
  return 1
389
431
 
390
- @property_depends_on('y_min, y_max, increment')
432
+ @property_depends_on(['y_min', 'y_max', 'increment'])
391
433
  def _get_nysteps(self):
392
434
  i = abs(self.increment)
393
435
  if i != 0:
@@ -398,16 +440,8 @@ class RectGrid(Grid):
398
440
  def _get_digest(self):
399
441
  return digest(self)
400
442
 
401
- @property_depends_on('x_min, x_max, y_min, y_max, increment')
402
- def _get_gpos(self):
403
- """Calculates grid co-ordinates.
404
-
405
- Returns
406
- -------
407
- array of floats of shape (3, :attr:`~Grid.size`)
408
- The grid point x, y, z-coordinates in one array.
409
-
410
- """
443
+ @property_depends_on(['x_min', 'x_max', 'y_min', 'y_max', 'increment'])
444
+ def _get_pos(self):
411
445
  bpos = mgrid[
412
446
  self.x_min : self.x_max : self.nxsteps * 1j,
413
447
  self.y_min : self.y_max : self.nysteps * 1j,
@@ -417,22 +451,25 @@ class RectGrid(Grid):
417
451
  return bpos
418
452
 
419
453
  def index(self, x, y):
420
- """Queries the indices for a grid point near a certain co-ordinate.
421
-
422
- This can be used to query results or co-ordinates at/near a certain
423
- co-ordinate.
454
+ """
455
+ Find the indices of a grid point near a given coordinate.
424
456
 
425
457
  Parameters
426
458
  ----------
427
- x, y : float
428
- The co-ordinates for which the indices are queried.
459
+ x : :class:`float`
460
+ The x coordinate of interest.
461
+ y : :class:`float`
462
+ The y coordinate of interest.
429
463
 
430
464
  Returns
431
465
  -------
432
- 2-tuple of integers
433
- The indices that give the grid point nearest to the given x, y
434
- co-ordinates from an array with the same shape as the grid.
466
+ :class:`tuple` of :class:`ints<int>`
467
+ Indices corresponding to the nearest grid point.
435
468
 
469
+ Raises
470
+ ------
471
+ ValueError
472
+ If the coordinates are outside the grid boundaries.
436
473
  """
437
474
  if x < self.x_min or x > self.x_max:
438
475
  msg = 'x-value out of range'
@@ -445,31 +482,27 @@ class RectGrid(Grid):
445
482
  return xi, yi
446
483
 
447
484
  def indices(self, *r):
448
- """Queries the indices for a subdomain in the grid.
485
+ """
486
+ Find the indices of a subdomain in the grid.
449
487
 
450
- Allows either rectangular, circular or polygonial subdomains.
451
- This can be used to mask or to query results from a certain
452
- sector or subdomain.
488
+ Supports rectangular, circular, and polygonal subdomains.
453
489
 
454
490
  Parameters
455
491
  ----------
456
- x1, y1, x2, y2, ... : float
457
- If three parameters are given, then a circular sector is assumed
458
- that is given by its center (x1, y1) and the radius x2.
459
- If four paramters are given, then a rectangular sector is
460
- assumed that is given by two corners (x1, y1) and (x2, y2).
461
- If more parameters are given, the subdomain is assumed to have
462
- polygonial shape with corners at (x_n, y_n).
492
+ r : :class:`tuple` of :class:`floats<float>`
493
+ Defines the subdomain shape and dimensions:
494
+ - If 3 values are provided: center ``(x1, y1)`` and radius ``r2`` define a circle.
495
+ - If 4 values are provided: corners ``(x1, y1)`` and ``(x2, y2)`` define a
496
+ rectangle.
497
+ - If more than 4 values are provided: vertices ``(xn, yn)`` define a polygon.
463
498
 
464
499
  Returns
465
500
  -------
466
- 2-tuple of arrays of integers or of numpy slice objects
467
- The indices that can be used to mask/select the grid subdomain from
468
- an array with the same shape as the grid.
469
-
501
+ :class:`tuple`
502
+ A 2-tuple of indices or slices corresponding to the subdomain.
470
503
  """
471
504
  if len(r) == 3: # only 3 values given -> use x,y,radius method
472
- xpos = self.gpos
505
+ xpos = self.pos
473
506
  xis = []
474
507
  yis = []
475
508
  dr2 = (xpos[0, :] - r[0]) ** 2 + (xpos[1, :] - r[1]) ** 2
@@ -486,7 +519,7 @@ class RectGrid(Grid):
486
519
  xi1, yi1 = self.index(min(r[0], r[2]), min(r[1], r[3]))
487
520
  xi2, yi2 = self.index(max(r[0], r[2]), max(r[1], r[3]))
488
521
  return s_[xi1 : xi2 + 1], s_[yi1 : yi2 + 1]
489
- xpos = self.gpos
522
+ xpos = self.pos
490
523
  xis = []
491
524
  yis = []
492
525
  # replaced matplotlib Path by numpy
@@ -507,46 +540,62 @@ class RectGrid(Grid):
507
540
  # return arange(self.size)[inds]
508
541
 
509
542
  def extend(self):
510
- """The extension of the grid in pylab.imshow compatible form.
543
+ """
544
+ Return the grid's extension in :obj:`matplotlib.pyplot.imshow` compatible form.
511
545
 
512
546
  Returns
513
547
  -------
514
- 4-tuple of floats
515
- The extent of the grid as a tuple of x_min, x_max, y_min, y_max)
548
+ :class:`tuple` of :class:`floats<float>`
549
+ (:attr:`x_min`, :attr:`x_max`, :attr:`y_min`, :attr:`y_max`) representing the grid's
550
+ extent.
516
551
 
552
+ Notes
553
+ -----
554
+ - ``pylab.imhow`` is the same as :obj:`matplotlib.pyplot.imshow`. It's only using a
555
+ different namespace.
556
+ - The return of the method is ment for the ``extent`` parameter of
557
+ :obj:`matplotlib.pyplot.imshow`.
558
+
559
+ Examples
560
+ --------
561
+ >>> from acoular import RectGrid
562
+ >>> grid = RectGrid()
563
+ >>> grid.y_min = -5
564
+ >>> grid.y_max = 5
565
+ >>> grid.extend()
566
+ (-1.0, 1.0, -5.0, 5.0)
517
567
  """
518
568
  return (self.x_min, self.x_max, self.y_min, self.y_max)
519
569
 
520
570
 
521
571
  class RectGrid3D(RectGrid):
522
- """Provides a cartesian 3D grid for the beamforming results.
572
+ """
573
+ Provide a cartesian 3D grid for the beamforming results.
523
574
 
524
- The grid has cubic or nearly cubic cells. It is defined by lower and upper
525
- x-, y- and z-limits.
575
+ The grid has cubic or nearly cubic cells. It is defined by lower and upper x-, y- and z-limits.
526
576
  """
527
577
 
528
- #: The lower z-limit that defines the grid, defaults to -1.
578
+ #: The lower z-limit that defines the grid. Default is ``-1``.
529
579
  z_min = Float(-1.0, desc='minimum z-value')
530
580
 
531
- #: The upper z-limit that defines the grid, defaults to 1.
581
+ #: The upper z-limit that defines the grid. Default is ``1``.
532
582
  z_max = Float(1.0, desc='maximum z-value')
533
583
 
534
- #: Number of grid points along x-axis, readonly.
584
+ #: Number of grid points along x-axis. (read-only)
535
585
  nxsteps = Property(desc='number of grid points along x-axis')
536
586
 
537
- #: Number of grid points along y-axis, readonly.
587
+ #: Number of grid points along y-axis. (read-only)
538
588
  nysteps = Property(desc='number of grid points along y-axis')
539
589
 
540
- #: Number of grid points along x-axis, readonly.
541
- nzsteps = Property(desc='number of grid points along x-axis')
590
+ #: Number of grid points along z-axis. (read-only)
591
+ nzsteps = Property(desc='number of grid points along z-axis')
542
592
 
543
593
  # Private trait for increment handling
544
594
  _increment = Union(Float(), CArray(shape=(3,), dtype=float), default_value=0.1, desc='step size')
545
595
 
546
- #: The cell side length for the grid. This can either be a scalar (same
547
- #: increments in all 3 dimensions) or a (3,) array of floats with
548
- #: respective increments in x,y, and z-direction (in m).
549
- #: Defaults to 0.1.
596
+ #: The cell side length for the grid. This can either be a scalar (same increments in all 3
597
+ #: dimensions) or a (3,) array of :class:`tuple` of :class:`floats<float>` with respective
598
+ #: increments in x-, y-, and z-direction. Default is ``0.1``.
550
599
  increment = Property(desc='step size')
551
600
 
552
601
  def _get_increment(self):
@@ -563,53 +612,34 @@ class RectGrid3D(RectGrid):
563
612
  else:
564
613
  raise (TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment))
565
614
 
566
- # Respective increments in x,y, and z-direction (in m).
567
- # Deprecated: Use :attr:`~RectGrid.increment` for this functionality
568
- increment3D = Property(desc='3D step sizes') # noqa N815
569
-
570
- def _get_increment3D(self): # noqa N802
571
- msg = "Using 'increment3D' is deprecated and will be removed in version 25.01." "Use 'increment' instead."
572
- warn(msg, DeprecationWarning, stacklevel=2)
573
- if isscalar(self._increment):
574
- return array([self._increment, self._increment, self._increment])
575
- return self._increment
576
-
577
- def _set_increment3D(self, inc): # noqa N802
578
- msg = "Using 'increment3D' is deprecated and will be removed in version 25.01." "Use 'increment' instead."
579
- warn(msg, DeprecationWarning, stacklevel=2)
580
- if not isscalar(inc) and len(inc) == 3:
581
- self._increment = array(inc, dtype=float)
582
- else:
583
- raise (TraitError(args=self, name='increment3D', info='CArray(3,)', value=inc))
584
-
585
- # internal identifier
615
+ #: A unique identifier for the grid, based on its properties. (read-only)
586
616
  digest = Property(
587
617
  depends_on=['x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max', '_increment'],
588
618
  )
589
619
 
590
- @property_depends_on('nxsteps, nysteps, nzsteps')
620
+ @property_depends_on(['nxsteps', 'nysteps', 'nzsteps'])
591
621
  def _get_size(self):
592
622
  return self.nxsteps * self.nysteps * self.nzsteps
593
623
 
594
- @property_depends_on('nxsteps, nysteps, nzsteps')
624
+ @property_depends_on(['nxsteps', 'nysteps', 'nzsteps'])
595
625
  def _get_shape(self):
596
626
  return (self.nxsteps, self.nysteps, self.nzsteps)
597
627
 
598
- @property_depends_on('x_min, x_max, _increment')
628
+ @property_depends_on(['x_min', 'x_max', '_increment'])
599
629
  def _get_nxsteps(self):
600
630
  i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[0])
601
631
  if i != 0:
602
632
  return int(round((abs(self.x_max - self.x_min) + i) / i))
603
633
  return 1
604
634
 
605
- @property_depends_on('y_min, y_max, _increment')
635
+ @property_depends_on(['y_min', 'y_max', '_increment'])
606
636
  def _get_nysteps(self):
607
637
  i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[1])
608
638
  if i != 0:
609
639
  return int(round((abs(self.y_max - self.y_min) + i) / i))
610
640
  return 1
611
641
 
612
- @property_depends_on('z_min, z_max, _increment')
642
+ @property_depends_on(['z_min', 'z_max', '_increment'])
613
643
  def _get_nzsteps(self):
614
644
  i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[2])
615
645
  if i != 0:
@@ -617,15 +647,7 @@ class RectGrid3D(RectGrid):
617
647
  return 1
618
648
 
619
649
  @property_depends_on('digest')
620
- def _get_gpos(self):
621
- """Calculates grid co-ordinates.
622
-
623
- Returns
624
- -------
625
- array of floats of shape (3, :attr:`~Grid.size`)
626
- The grid point x, y, z-coordinates in one array.
627
-
628
- """
650
+ def _get_pos(self):
629
651
  bpos = mgrid[
630
652
  self.x_min : self.x_max : self.nxsteps * 1j,
631
653
  self.y_min : self.y_max : self.nysteps * 1j,
@@ -639,22 +661,39 @@ class RectGrid3D(RectGrid):
639
661
  return digest(self)
640
662
 
641
663
  def index(self, x, y, z):
642
- """Queries the indices for a grid point near a certain co-ordinate.
664
+ """
665
+ Return the indices for a grid point near a certain coordinate.
643
666
 
644
- This can be used to query results or co-ordinates at/near a certain
645
- co-ordinate.
667
+ This can be used to query results or coordinates at or near a certain coordinate. Raises an
668
+ exception if the given coordinate is outside the grid.
646
669
 
647
670
  Parameters
648
671
  ----------
649
- x, y, z : float
650
- The co-ordinates for which the indices is queried.
672
+ x, y, z : :class:`float`
673
+ The coordinates for which the indices is queried.
651
674
 
652
675
  Returns
653
676
  -------
654
- 3-tuple of integers
655
- The indices that give the grid point nearest to the given x, y, z
656
- co-ordinates from an array with the same shape as the grid.
657
-
677
+ 3-:class:`tuple` of :class:`ints<int>`
678
+ The indices that give the grid point nearest to the given x, y, z coordinates from an
679
+ array with the same shape as the grid.
680
+
681
+ Examples
682
+ --------
683
+ Check which of the points in a simple 8-point rectangular grid is closest to the point
684
+ ``(0.5, 0.5, 1.0)``.
685
+
686
+ >>> import acoular as ac
687
+ >>>
688
+ >>> grid = ac.RectGrid3D()
689
+ >>> grid.increment = 2
690
+ >>> grid.pos
691
+ array([[-1., -1., -1., -1., 1., 1., 1., 1.],
692
+ [-1., -1., 1., 1., -1., -1., 1., 1.],
693
+ [-1., 1., -1., 1., -1., 1., -1., 1.]])
694
+ >>>
695
+ >>> grid.index(0.5, 0.5, 1.0)
696
+ (1, 1, 1)
658
697
  """
659
698
  if x < self.x_min or x > self.x_max:
660
699
  msg = f'x-value out of range {x:f} ({self.x_min:f}, {self.x_max:f})'
@@ -675,284 +714,510 @@ class RectGrid3D(RectGrid):
675
714
  return xi, yi, zi
676
715
 
677
716
  def indices(self, x1, y1, z1, x2, y2, z2):
678
- """Queries the indices for a subdomain in the grid.
717
+ """
718
+ Return the indices for a subdomain in the grid.
679
719
 
680
- Allows box-shaped subdomains. This can be used to
681
- mask or to query results from a certain sector or subdomain.
720
+ Allows box-shaped subdomains. This can be used to mask or to query results from a certain
721
+ sector or subdomain.
682
722
 
683
723
  Parameters
684
724
  ----------
685
- x1, y1, z1, x2, y2, z2 : float
686
- A box-shaped sector is assumed that is given by two corners
687
- (x1,y1,z1) and (x2,y2,z2).
725
+ x1, y1, z1, x2, y2, z2 : :class:`float`
726
+ A box-shaped sector is assumed that is given by two corners ``(x1,y1,z1)`` and
727
+ ``(x2,y2,z2)``.
688
728
 
689
729
  Returns
690
730
  -------
691
- 3-tuple of numpy slice objects
692
- The indices that can be used to mask/select the grid subdomain from
693
- an array with the same shape as the grid.
694
-
731
+ 3-:class:`tuple` of :obj:`numpy.s_` objects
732
+ The indices that can be used to mask/select the grid subdomain from an array with the
733
+ same shape as the grid.
734
+
735
+ Examples
736
+ --------
737
+ Get the indices of the grid points of a simple 27-point rectangular grid which are located
738
+ inside the first octant.
739
+
740
+ >>> import acoular as ac
741
+ >>>
742
+ >>> grid = ac.RectGrid3D()
743
+ >>> grid.increment = 1
744
+ >>> grid.pos
745
+ array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., 0., 0., 0., 0.,
746
+ 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.,
747
+ 1.],
748
+ [-1., -1., -1., 0., 0., 0., 1., 1., 1., -1., -1., -1., 0.,
749
+ 0., 0., 1., 1., 1., -1., -1., -1., 0., 0., 0., 1., 1.,
750
+ 1.],
751
+ [-1., 0., 1., -1., 0., 1., -1., 0., 1., -1., 0., 1., -1.,
752
+ 0., 1., -1., 0., 1., -1., 0., 1., -1., 0., 1., -1., 0.,
753
+ 1.]])
754
+ >>>
755
+ >>> grid.indices(0, 0, 0, 1, 1, 1)
756
+ (slice(1, 3, None), slice(1, 3, None), slice(1, 3, None))
695
757
  """
696
758
  xi1, yi1, zi1 = self.index(min(x1, x2), min(y1, y2), min(z1, z2))
697
759
  xi2, yi2, zi2 = self.index(max(x1, x2), max(y1, y2), max(z1, z2))
698
760
  return s_[xi1 : xi2 + 1], s_[yi1 : yi2 + 1], s_[zi1 : zi2 + 1]
699
761
 
700
762
 
763
+ @deprecated_alias({'from_file': 'file', 'gpos_file': 'pos'})
701
764
  class ImportGrid(Grid):
702
- """Loads a 3D grid from xml file."""
765
+ """
766
+ Load a 3D grid from an XML file.
703
767
 
704
- #: Name of the .xml-file from wich to read the data.
705
- from_file = File(filter=['*.xml'], desc='name of the xml file to import')
768
+ This class is used to import a 3D grid defined in an XML file. The grid's
769
+ positions and subgrid names are parsed and stored for further processing.
770
+ """
706
771
 
707
- gpos_file = CArray(dtype=float, desc='x, y, z position of all Grid Points')
772
+ #: Name of the .xml-file from which to read the data.
773
+ file = File(filter=['*.xml'], exists=True, desc='name of the xml file to import')
708
774
 
709
- #: Basename of the .xml-file, without the extension; is set automatically / readonly.
710
- basename = Property(depends_on='from_file', desc='basename of xml file')
775
+ _gpos = CArray(dtype=float, desc='x, y, z position of all Grid Points')
711
776
 
712
- # internal identifier
713
- digest = Property(
714
- depends_on=['from_file'],
715
- )
777
+ #: Names of subgrids for each point.
778
+ #: This is an optional property, typically used when grids are divided into named subregions.
779
+ subgrids = CArray(desc='names of subgrids for each point')
716
780
 
717
- @cached_property
718
- def _get_basename(self):
719
- return path.splitext(path.basename(self.from_file))[0]
781
+ #: A unique identifier for the grid, based on its properties. (read-only)
782
+ digest = Property(depends_on=['_gpos'])
720
783
 
721
784
  @cached_property
722
785
  def _get_digest(self):
723
786
  return digest(self)
724
787
 
725
- # 'digest' is a placeholder for other properties in derived classes,
726
- # necessary to trigger the depends on mechanism
727
- @property_depends_on('basename')
788
+ @property_depends_on(['_gpos'])
728
789
  def _get_size(self):
729
- return self.gpos.shape[-1]
790
+ return self.pos.shape[-1]
730
791
 
731
- # 'digest' is a placeholder for other properties in derived classes
732
- @property_depends_on('basename')
792
+ @property_depends_on(['_gpos'])
733
793
  def _get_shape(self):
734
- return (self.gpos.shape[-1],)
794
+ return (self.pos.shape[-1],)
735
795
 
736
- @property_depends_on('basename')
737
- def _get_gpos(self):
738
- return self.gpos_file
796
+ @property_depends_on(['_gpos'])
797
+ def _get_pos(self):
798
+ return self._gpos
739
799
 
740
- subgrids = CArray(desc='names of subgrids for each point')
800
+ def _set_pos(self, pos):
801
+ self._gpos = pos
741
802
 
742
- @on_trait_change('basename')
803
+ @on_trait_change('file')
743
804
  def import_gpos(self):
744
- """Import the the grid point locations from .xml file.
745
- Called when :attr:`basename` changes.
746
805
  """
747
- if not path.isfile(self.from_file):
748
- # no file there
749
- self.gpos_file = array([], 'd')
750
- return
751
- import xml.dom.minidom
806
+ Import the grid point locations and subgrid names from an XML file.
807
+
808
+ This method is automatically called whenever the :attr:`file` attribute changes.
752
809
 
753
- doc = xml.dom.minidom.parse(self.from_file)
810
+ Notes
811
+ -----
812
+ The XML file should have elements with tag ``<pos>``, where each ``<pos>`` element
813
+ contains attributes for ``x``, ``y``, ``z``, and optionally ``subgrid``.
814
+
815
+ Examples
816
+ --------
817
+ Generate the positional data of two microphone grids:
818
+
819
+ >>> import numpy as np
820
+ >>>
821
+ >>> # Grid 1: ten points aranged in a circle in the x-y plane at z=0
822
+ >>> args = 2 * np.pi * np.arange(10) / 10
823
+ >>> x1 = np.cos(args)
824
+ >>> y1 = np.sin(args)
825
+ >>> z1 = np.zeros_like(x1)
826
+ >>> grid1 = np.vstack([x1, y1, z1]).T
827
+ >>>
828
+ >>> # Grid 2: nine points aranged in a mesh grid the the x-y plane at z=1
829
+ >>> a = np.linspace(-1, 1, 3)
830
+ >>> x2, y2 = np.meshgrid(a, a)
831
+ >>> z2 = np.ones_like(x2)
832
+ >>> grid2 = np.vstack([x2, y2, z2])
833
+
834
+ Save the generated data in an XML file:
835
+
836
+ >>> import xml.etree.cElementTree as ET
837
+ >>>
838
+ >>> # Create the root element for the XML document
839
+ >>> root = ET.Element('root')
840
+ >>>
841
+ >>> # Loop over both grid 1 and grid 2, and create XML elements for each of their points
842
+ >>> for num, grid in enumerate([grid1, grid2]): # doctest: +SKIP
843
+ ... for x, y, z in grid:
844
+ ... # For each point in the grid, create a 'pos' XML element
845
+ ... ET.SubElement(root, 'pos', subgrid=str(num), x=str(x), y=str(y), z=str(z))
846
+ >>>
847
+ >>> # Create the final XML tree
848
+ >>> tree = ET.ElementTree(root)
849
+ >>>
850
+ >>> # Write the XML tree to a file named 'two_grids.xml'
851
+ >>> tree.write('two_grids.xml') # doctest: +SKIP
852
+
853
+ The ``two_grids.xml`` file will look like this:
854
+
855
+ .. code-block:: xml
856
+
857
+ <root>
858
+ <pos subgrid="0" x="1.0" y="0.0" z="0.0" />
859
+ <pos subgrid="0" x="0.8090169943749475" y="0.5877852522924731" z="0.0" />
860
+ <pos subgrid="0" x="0.30901699437494745" y="0.9510565162951535" z="0.0" />
861
+ <pos subgrid="0" x="-0.30901699437494734" y="0.9510565162951536" z="0.0" />
862
+ <pos subgrid="0" x="-0.8090169943749473" y="0.5877852522924732" z="0.0" />
863
+ <pos subgrid="0" x="-1.0" y="1.2246467991473532e-16" z="0.0" />
864
+ <pos subgrid="0" x="-0.8090169943749475" y="-0.587785252292473" z="0.0" />
865
+ <pos subgrid="0" x="-0.30901699437494756" y="-0.9510565162951535" z="0.0" />
866
+ <pos subgrid="0" x="0.30901699437494723" y="-0.9510565162951536" z="0.0" />
867
+ <pos subgrid="0" x="0.8090169943749473" y="-0.5877852522924732" z="0.0" />
868
+ <pos subgrid="1" x="-1.0" y="0.0" z="1.0" />
869
+ <pos subgrid="1" x="-1.0" y="0.0" z="1.0" />
870
+ <pos subgrid="1" x="-1.0" y="0.0" z="1.0" />
871
+ <pos subgrid="1" x="-1.0" y="-1.0" z="-1.0" />
872
+ <pos subgrid="1" x="0.0" y="0.0" z="0.0" />
873
+ <pos subgrid="1" x="1.0" y="1.0" z="1.0" />
874
+ <pos subgrid="1" x="1.0" y="1.0" z="1.0" />
875
+ <pos subgrid="1" x="1.0" y="1.0" z="1.0" />
876
+ <pos subgrid="1" x="1.0" y="1.0" z="1.0" />
877
+ </root>
878
+
879
+ Importing the ``two_grids.xml`` file:
880
+
881
+ >>> import acoular as ac
882
+ >>>
883
+ >>> grids = ac.ImportGrid(file='two_grids.xml') # doctest: +SKIP
884
+ >>> grids.size # doctest: +SKIP
885
+ 19
886
+ >>> grids.subgrids # doctest: +SKIP
887
+ array(['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1',
888
+ '1', '1', '1', '1', '1', '1'], dtype='<U1')
889
+ >>> grids.gpos.T # doctest: +SKIP
890
+ array([[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00],
891
+ [ 8.09016994e-01, 5.87785252e-01, 0.00000000e+00],
892
+ [ 3.09016994e-01, 9.51056516e-01, 0.00000000e+00],
893
+ [-3.09016994e-01, 9.51056516e-01, 0.00000000e+00],
894
+ [-8.09016994e-01, 5.87785252e-01, 0.00000000e+00],
895
+ [-1.00000000e+00, 1.22464680e-16, 0.00000000e+00],
896
+ [-8.09016994e-01, -5.87785252e-01, 0.00000000e+00],
897
+ [-3.09016994e-01, -9.51056516e-01, 0.00000000e+00],
898
+ [ 3.09016994e-01, -9.51056516e-01, 0.00000000e+00],
899
+ [ 8.09016994e-01, -5.87785252e-01, 0.00000000e+00],
900
+ [-1.00000000e+00, 0.00000000e+00, 1.00000000e+00],
901
+ [-1.00000000e+00, 0.00000000e+00, 1.00000000e+00],
902
+ [-1.00000000e+00, 0.00000000e+00, 1.00000000e+00],
903
+ [-1.00000000e+00, -1.00000000e+00, -1.00000000e+00],
904
+ [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
905
+ [ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
906
+ [ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
907
+ [ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
908
+ [ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00]])
909
+
910
+ Consider two XML files, ``grid1.xml`` and ``grid2.xml`` containing different grids.
911
+
912
+ >>> import acoular as ac
913
+ >>>
914
+ >>> grid = ac.ImportGrid(file='grid1.xml') # doctest: +SKIP
915
+ >>> grid.size # doctest: +SKIP
916
+ 8
917
+ >>> grid.file = 'grid2.xml' # doctest: +SKIP
918
+ >>> grid.size # doctest: +SKIP
919
+ 12
920
+ """
921
+ doc = xml.dom.minidom.parse(self.file)
754
922
  names = []
755
923
  xyz = []
756
924
  for el in doc.getElementsByTagName('pos'):
757
925
  names.append(el.getAttribute('subgrid'))
758
926
  xyz.append([float(el.getAttribute(a)) for a in 'xyz'])
759
- self.gpos_file = array(xyz, 'd').swapaxes(0, 1)
927
+ self._gpos = array(xyz, 'd').swapaxes(0, 1)
760
928
  self.subgrids = array(names)
761
929
 
762
930
 
931
+ @deprecated_alias({'gpos': 'pos', 'numpoints': 'num_points'}, read_only=['gpos'])
763
932
  class LineGrid(Grid):
764
- """Class for Line grid geometries."""
933
+ """
934
+ Define a 3D grid for a line geometry.
935
+
936
+ The :class:`LineGrid` class represents a grid where points are arranged linearly in 3D space.
937
+ The grid is defined by a starting location (:attr:`loc`), a direction vector
938
+ (:attr:`direction`), a total length (:attr:`length`), and the number of points
939
+ (:attr:`num_points`) along the line.
765
940
 
766
- #: Staring point of the Grid
941
+ Notes
942
+ -----
943
+ - The distance between points is :attr:`length` ``/ (`` :attr:`num_points` ``- 1)``.
944
+ - The direction vector is normalized to ensure consistency.
945
+
946
+ Examples
947
+ --------
948
+ Create a line grid with 5 points along the x-axis, starting at (0, 0, 0), with a length of 4
949
+ meters:
950
+
951
+ >>> import acoular as ac
952
+ >>> grid = ac.LineGrid()
953
+ >>> grid.loc = (0.0, 0.0, 0.0)
954
+ >>> grid.direction = (1.0, 0.0, 0.0)
955
+ >>> grid.length = 4
956
+ >>> grid.num_points = 5
957
+ >>> grid.pos
958
+ array([[0., 1., 2., 3., 4.],
959
+ [0., 0., 0., 0., 0.],
960
+ [0., 0., 0., 0., 0.]])
961
+ """
962
+
963
+ #: Starting point of the grid in 3D space. Default is ``(0.0, 0.0, 0.0)``.
767
964
  loc = Tuple((0.0, 0.0, 0.0))
768
965
 
769
- #: Vector to define the orientation of the line source
966
+ #: A vector defining the orientation of the line in 3D space. Default is ``(1.0, 0.0, 0.0)``.
770
967
  direction = Tuple((1.0, 0.0, 0.0), desc='Line orientation ')
771
968
 
772
- #: Vector to define the length of the line source in meter
969
+ #: Total length of the line. Default is ``1.0``.
773
970
  length = Float(1, desc='length of the line source')
774
971
 
775
- #:number of grid points.
776
- numpoints = Int(1, desc='length of the line source')
972
+ #: Number of grid points along the line. Default is ``1``.
973
+ num_points = Int(1, desc='length of the line source')
777
974
 
778
- #: Overall number of grid points. Readonly; is set automatically when
779
- #: other grid defining properties are set
975
+ #: The total number of grid points. Automatically updated when other grid-defining attributes
976
+ #: are set. (read-only)
780
977
  size = Property(desc='overall number of grid points')
781
978
 
782
- #: Grid positions as (3, :attr:`size`) array of floats, without invalid
783
- #: microphones; readonly.
784
- gpos = Property(desc='x, y, z positions of grid points')
979
+ #: A (3, :attr:`size`) array containing the x, y, and z positions
980
+ #: of the grid points. (read-only)
981
+ pos = Property(desc='x, y, z positions of grid points')
785
982
 
983
+ #: A unique identifier for the grid, based on its properties. (read-only)
786
984
  digest = Property(
787
- depends_on=['loc', 'direction', 'length', 'numpoints', 'size'],
985
+ depends_on=['loc', 'direction', 'length', 'num_points', 'size'],
788
986
  )
789
987
 
790
988
  @cached_property
791
989
  def _get_digest(self):
792
990
  return digest(self)
793
991
 
794
- # 'digest' is a placeholder for other properties in derived classes,
795
- # necessary to trigger the depends on mechanism
796
- @property_depends_on('numpoints')
992
+ @property_depends_on(['num_points'])
797
993
  def _get_size(self):
798
- return self.gpos.shape[-1]
994
+ return self.pos.shape[-1]
799
995
 
800
- # 'digest' is a placeholder for other properties in derived classes
801
- @property_depends_on('numpoints')
996
+ @property_depends_on(['num_points'])
802
997
  def _get_shape(self):
803
- return self.gpos.shape[-1]
998
+ return self.pos.shape[-1]
804
999
 
805
- @property_depends_on('numpoints,length,direction,loc')
806
- def _get_gpos(self):
807
- dist = self.length / (self.numpoints - 1)
1000
+ @property_depends_on(['num_points', 'length', 'direction', 'loc'])
1001
+ def _get_pos(self):
1002
+ dist = self.length / (self.num_points - 1)
808
1003
  loc = array(self.loc, dtype=float).reshape((3, 1))
809
1004
  direc_n = array(self.direction) / norm(self.direction)
810
- pos = zeros((self.numpoints, 3))
811
- for s in range(self.numpoints):
1005
+ pos = zeros((self.num_points, 3))
1006
+ for s in range(self.num_points):
812
1007
  pos[s] = loc.T + direc_n * dist * s
813
1008
  return pos.T
814
1009
 
815
1010
 
1011
+ @deprecated_alias({'gpos': 'pos'}, read_only=True)
816
1012
  class MergeGrid(Grid):
817
- """Base class for merging different grid geometries."""
1013
+ """
1014
+ Base class for merging multiple grid geometries.
1015
+
1016
+ The `MergeGrid` class allows the combination of multiple grid geometries into a single unified
1017
+ grid. Each input grid is assigned a subdomain in the resulting grid, and all properties, such as
1018
+ positions and identifiers, are appropriately merged.
818
1019
 
819
- #: List of Grids to be merged
820
- #: each grid gets a new subdomain in the new grid
821
- #: other grid defining properties are set
1020
+ Notes
1021
+ -----
1022
+ - The merged grid eliminates duplicate points based on their positions.
1023
+ - Each subgrid retains its original grid properties, such as digest and size.
1024
+
1025
+ Examples
1026
+ --------
1027
+ Merging two simple grids:
1028
+
1029
+ >>> import acoular as ac
1030
+ >>> grid1 = ac.LineGrid(loc=(0, 0, 0), direction=(1, 0, 0), length=1, num_points=3)
1031
+ >>> grid2 = ac.LineGrid(loc=(0, 0, 0), direction=(0, 1, 0), length=1, num_points=3)
1032
+ >>> merged_grid = ac.MergeGrid()
1033
+ >>> merged_grid.grids = [grid1, grid2]
1034
+ >>> merged_grid.size
1035
+ 5
1036
+ >>> merged_grid.pos
1037
+ array([[0. , 0. , 0. , 0.5, 1. ],
1038
+ [0. , 0.5, 1. , 0. , 0. ],
1039
+ [0. , 0. , 0. , 0. , 0. ]])
1040
+ """
1041
+
1042
+ #: A list of :class:`Grid` objects to be merged. Each grid is treated as a subdomain in the
1043
+ #: resulting merged grid.
822
1044
  grids = List(desc='list of grids')
823
1045
 
824
- grid_digest = Property(desc='digest of the merged grids')
1046
+ #: A list of unique digests for each grid being merged. (read-only)
1047
+ grid_digest = Str(desc='digest of the merged grids')
825
1048
 
1049
+ #: Names of subgrids corresponding to each point in the merged grid. (read-only)
826
1050
  subgrids = Property(desc='names of subgrids for each point')
827
1051
 
828
- # internal identifier
829
- digest = Property(
830
- depends_on=['grids', 'grid_digest'],
831
- )
1052
+ #: A unique identifier for the grid, based on its properties. (read-only)
1053
+ digest = Property(depends_on=['grids', 'grid_digest'])
832
1054
 
833
1055
  @cached_property
834
1056
  def _get_digest(self):
835
1057
  return digest(self)
836
1058
 
837
- @cached_property
838
- def _get_grid_digest(self):
839
- griddigest = []
840
- for grid in self.grids:
841
- griddigest.append(grid.digest)
842
- return griddigest
1059
+ @observe('grids.items.digest')
1060
+ def _set_sourcesdigest(self, event): # noqa ARG002
1061
+ self.grid_digest = ldigest(self.grids)
843
1062
 
844
- # 'digest' is a placeholder for other properties in derived classes,
845
- # necessary to trigger the depends on mechanism
846
- @property_depends_on('digest')
1063
+ @property_depends_on(['digest'])
847
1064
  def _get_size(self):
848
- return self.gpos.shape[-1]
1065
+ return self.pos.shape[-1]
849
1066
 
850
- # 'digest' is a placeholder for other properties in derived classes
851
- @property_depends_on('digest')
1067
+ @property_depends_on(['digest'])
852
1068
  def _get_shape(self):
853
- return self.gpos.shape[-1]
1069
+ return self.pos.shape[-1]
854
1070
 
855
- @property_depends_on('digest')
1071
+ @property_depends_on(['digest'])
856
1072
  def _get_subgrids(self):
857
1073
  subgrids = zeros((1, 0), dtype=str)
858
1074
  for grid in self.grids:
859
1075
  subgrids = append(subgrids, tile(grid.__class__.__name__ + grid.digest, grid.size))
860
1076
  return subgrids[:, newaxis].T
861
1077
 
862
- @property_depends_on('digest')
863
- def _get_gpos(self):
1078
+ @property_depends_on(['digest'])
1079
+ def _get_pos(self):
864
1080
  bpos = zeros((3, 0))
865
- # subgrids = zeros((1,0))
866
1081
  for grid in self.grids:
867
- bpos = append(bpos, grid.gpos, axis=1)
868
- # subgrids = append(subgrids,str(grid))
1082
+ bpos = append(bpos, grid.pos, axis=1)
869
1083
  return unique(bpos, axis=1)
870
1084
 
871
1085
 
872
- class Sector(HasPrivateTraits):
873
- """Base class for all sector types.
1086
+ class Sector(ABCHasStrictTraits):
1087
+ """
1088
+ Abstract base class for all sector types.
1089
+
1090
+ The :class:`Sector` class defines the common interface for all sector implementations. It serves
1091
+ as the base class for creating diverse sector geometries, each capable of determining whether
1092
+ specific grid points fall within its bounds.
1093
+
1094
+ When used directly, this class represents a sector encompassing the entire grid, meaning all
1095
+ positions are considered valid.
1096
+
1097
+ Notes
1098
+ -----
1099
+ This class is designed to be subclassed. Derived classes should override the :meth:`contains`
1100
+ method to implement specific sector geometries (e.g., circular, rectangular, or polygon shapes).
874
1101
 
875
- Defines the common interface for all tbdsector classes. This class
876
- may be used as a base for diverse sector implementaions. If used
877
- directly, it implements a sector encompassing the whole grid.
1102
+ Examples
1103
+ --------
1104
+ Load example data and set different Sectors for integration in the
1105
+ :ref:`sector integration example<sector_integration_example>`.
878
1106
  """
879
1107
 
880
1108
  def contains(self, pos):
881
- """Queries whether the coordinates in a given array lie within the
882
- defined sector.
883
- For this sector type, any position is valid.
1109
+ """
1110
+ Check whether the given coordinates lie within the sector's bounds.
1111
+
1112
+ This method determines if each column of the input array :attr:`pos` corresponds to a point
1113
+ that falls within the sector. For this base class, all points are considered within the
1114
+ sector.
884
1115
 
885
1116
  Parameters
886
1117
  ----------
887
- pos : array of floats
888
- Array with the shape 3x[number of gridpoints] containing the
889
- grid positions
1118
+ pos : :class:`numpy.ndarray`
1119
+ A 2D array with shape `(3, N)`, where `N` is the number of grid points. Each column
1120
+ represents the ``x, y, z`` coordinates of a grid point.
890
1121
 
891
1122
  Returns
892
1123
  -------
893
- array of bools with as many entries as columns in pos
894
- Array indicating which of the given positions lie within the
895
- given sector
896
-
1124
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1125
+ A 1D array of length `N`, where each entry indicates whether the corresponding
1126
+ column in :attr:`pos` lies within the sector's bounds.
1127
+
1128
+ Examples
1129
+ --------
1130
+ >>> import numpy as np
1131
+ >>> import acoular as ac
1132
+ >>> sector = ac.Sector()
1133
+ >>> positions = np.array([[0, 1], [0, 0], [0, 0]]) # Two grid points
1134
+ >>> sector.contains(positions)
1135
+ array([ True, True])
897
1136
  """
898
1137
  return ones(pos.shape[1], dtype=bool)
899
1138
 
900
1139
 
901
1140
  class SingleSector(Sector):
902
- """Base class for single sector types.
1141
+ """
1142
+ Base class for single sector types.
1143
+
1144
+ Defines the common interface for all single sector classes. This class can serve as a base for
1145
+ various single sector implementations. When used directly, it defines a sector that encompasses
1146
+ the whole grid. It includes attributes for handling border inclusion, tolerance for sector
1147
+ borders, and default behavior when no points are inside the sector.
903
1148
 
904
- Defines the common interface for all single sector classes. This class
905
- may be used as a base for diverse single sector implementaions. If used
906
- directly, it implements a sector encompassing the whole grid.
1149
+ Examples
1150
+ --------
1151
+ Load example data and set diffrent Sectors for intergration in the
1152
+ :ref:`sector integration example<sector_integration_example>`.
907
1153
  """
908
1154
 
909
- #: Boolean flag, if 'True' (default), grid points lying on the sector border are included.
1155
+ #: If ``True``, grid points lying on the sector border are included in the sector. Default is
1156
+ #: ``True``.
910
1157
  include_border = Bool(True, desc='include points on the border')
911
1158
 
912
- #: Absolute tolerance for sector border
1159
+ #: The absolute tolerance to apply when determining if a grid point lies on the sector border.
1160
+ #: Default is ``1e-12``.
913
1161
  abs_tol = Float(1e-12, desc='absolute tolerance for sector border')
914
1162
 
915
- #: Boolean flag, if 'True' (default), the nearest grid point is returned if none is inside the sector.
916
- default_nearest = Bool(True, desc='return nearest grid point to center of none inside sector')
1163
+ #: If ``True``, the ``contains`` method (as in :meth:`RectSector.contains`,
1164
+ #: :meth:`RectSector3D.contains`, :meth:`CircSector.contains`, and :meth:`PolySector.contains`)
1165
+ #: returns the nearest grid point if no grid points are inside the sector. Default is ``True``.
1166
+ default_nearest = Bool(True, desc='``contains`` method return nearest grid point to center if none inside sector')
917
1167
 
918
1168
 
919
1169
  class RectSector(SingleSector):
920
- """Class for defining a rectangular sector.
1170
+ """
1171
+ Class for defining a rectangular sector.
1172
+
1173
+ Defines a rectangular sector either for 2D grids (rectangle in the XY-plane) or for 3D grids
1174
+ (rectangular cylindrical sector parallel to the z-axis). The sector is bounded by the
1175
+ specified :attr:`x_min`, :attr:`x_max`, :attr:`y_min`, and :attr:`y_max` positions, defining the
1176
+ lower and upper bounds of the rectangle along the x and y axes.
921
1177
 
922
- Can be used for 2D Grids for definining a rectangular sector or
923
- for 3D grids for a rectangular cylinder sector parallel to the z-axis.
1178
+ Examples
1179
+ --------
1180
+ Load example data and set diffrent Sectors for intergration in the
1181
+ :ref:`sector integration example<sector_integration_example>`.
924
1182
  """
925
1183
 
926
- #: The lower x position of the rectangle
1184
+ #: The minimum x position of the rectangle. Default is ``-1.0``.
927
1185
  x_min = Float(-1.0, desc='minimum x position of the rectangle')
928
1186
 
929
- #: The upper x position of the rectangle
1187
+ #: The maximum x position of the rectangle. Default is ``1.0``.
930
1188
  x_max = Float(1.0, desc='maximum x position of the rectangle')
931
1189
 
932
- #: The lower y position of the rectangle
1190
+ #: The minimum y position of the rectangle. Default is ``-1.0``.
933
1191
  y_min = Float(-1.0, desc='minimum y position of the rectangle')
934
1192
 
935
- #: The upper y position of the rectangle
1193
+ #: The maximum y position of the rectangle. Default is ``1.0``.
936
1194
  y_max = Float(1.0, desc='maximum y position of the rectangle')
937
1195
 
938
1196
  def contains(self, pos):
939
- """Queries whether the coordinates in a given array lie within the
940
- rectangular sector.
941
- If no coordinate is inside, the nearest one to the rectangle center
942
- is returned if :attr:`~Sector.default_nearest` is True.
1197
+ """
1198
+ Check if the coordinates in a given array lie within the rectangular sector.
1199
+
1200
+ If no coordinate is inside, the nearest one to the rectangle center is returned if
1201
+ :attr:`~SingleSector.default_nearest` is ``True``.
943
1202
 
944
1203
  Parameters
945
1204
  ----------
946
- pos : array of floats
947
- Array with the shape 3x[number of gridpoints] containing the
948
- grid positions
1205
+ pos : array of :class:`floats<float>`
1206
+ A `(3, N)` array containing the positions of `N` grid points.
949
1207
 
950
1208
  Returns
951
1209
  -------
952
- array of bools with as many entries as columns in pos
953
- Array indicating which of the given positions lie within the
954
- given sector
955
-
1210
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1211
+ An array of shape (N,) indicating which of the given positions
1212
+ lie within the given sector.
1213
+
1214
+ Examples
1215
+ --------
1216
+ >>> import acoular as ac
1217
+ >>> grid = ac.RectGrid(increment=2)
1218
+ >>> sec = ac.RectSector(x_min=0, y_min=0)
1219
+ >>> sec.contains(grid.pos)
1220
+ array([False, False, False, True])
956
1221
  """
957
1222
  # make sure xmin is minimum etc
958
1223
  xmin = min(self.x_min, self.x_max)
@@ -988,35 +1253,53 @@ class RectSector(SingleSector):
988
1253
 
989
1254
 
990
1255
  class RectSector3D(RectSector):
991
- """Class for defining a cuboid sector.
1256
+ """
1257
+ Class for defining a cuboid sector.
1258
+
1259
+ This class extends the :class:`RectSector` class to define a cuboid sector, which can be used
1260
+ for 3D grids. The cuboid sector is defined by its bounds along the x, y, and z axes.
992
1261
 
993
- Can be used for 3D Grids for definining a cuboid sector.
1262
+ Examples
1263
+ --------
1264
+ Load example data and set diffrent Sectors for intergration in the
1265
+ :ref:`sector integration example<sector_integration_example>`.
994
1266
  """
995
1267
 
996
- #: The lower z position of the cuboid
1268
+ #: The lower z position of the cuboid. Default is ``-1.0``.
997
1269
  z_min = Float(-1.0, desc='minimum z position of the cuboid')
998
1270
 
999
- #: The upper z position of the cuboid
1271
+ #: The upper z position of the cuboid. Default is ``1.0``.
1000
1272
  z_max = Float(1.0, desc='maximum z position of the cuboid')
1001
1273
 
1002
1274
  def contains(self, pos):
1003
- """Queries whether the coordinates in a given array lie within the
1004
- rectangular sector.
1005
- If no coordinate is inside, the nearest one to the rectangle center
1006
- is returned if :attr:`~Sector.default_nearest` is True.
1275
+ """
1276
+ Check if the coordinates in a given array lie within the cuboid sector.
1277
+
1278
+ The method checks if the points in the provided position array are within the cuboid
1279
+ defined by the bounds along the x, y, and z axes. If no point is inside the sector, and if
1280
+ :attr:`~SingleSector.default_nearest` is ``True``, the nearest point to the center of the
1281
+ cuboid is returned.
1007
1282
 
1008
1283
  Parameters
1009
1284
  ----------
1010
- pos : array of floats
1011
- Array with the shape 3x[number of gridpoints] containing the
1012
- grid positions
1285
+ pos : array of :class:`floats<float>`
1286
+ A (3, N) array containing the positions of N grid points, where each point is
1287
+ represented by its x, y, and z coordinates.
1013
1288
 
1014
1289
  Returns
1015
1290
  -------
1016
- array of bools with as many entries as columns in pos
1017
- Array indicating which of the given positions lie within the
1018
- given sector
1019
-
1291
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1292
+ A boolean array of shape shape ``(N,)`` indicating which of the given positions lie
1293
+ within the cuboid sector. ``True`` if the grid point is inside the cuboid,
1294
+ otherwise ``False``.
1295
+
1296
+ Examples
1297
+ --------
1298
+ >>> import acoular as ac
1299
+ >>> grid = ac.RectGrid3D(increment=2)
1300
+ >>> sec = ac.RectSector3D(x_min=0, y_min=0, z_min=0)
1301
+ >>> sec.contains(grid.pos)
1302
+ array([False, False, False, False, False, False, False, True])
1020
1303
  """
1021
1304
  # make sure xmin is minimum etc
1022
1305
  xmin = min(self.x_min, self.x_max)
@@ -1058,45 +1341,67 @@ class RectSector3D(RectSector):
1058
1341
 
1059
1342
 
1060
1343
  class CircSector(SingleSector):
1061
- """Class for defining a circular sector.
1344
+ """
1345
+ Class for defining a circular sector.
1346
+
1347
+ Defines a circular sector, which can be used for both 2D grids (as a circle in the XY-plane) or
1348
+ for 3D grids (as a cylindrical sector parallel to the z-axis). The sector is defined by its
1349
+ center position (:attr:`x`, :attr:`y`) and its radius :attr:`r`.
1062
1350
 
1063
- Can be used for 2D Grids for definining a circular sector or
1064
- for 3D grids for a cylindrical sector parallel to the z-axis.
1351
+ Examples
1352
+ --------
1353
+ Load example data and set diffrent Sectors for intergration in the
1354
+ :ref:`sector integration example<sector_integration_example>`.
1065
1355
  """
1066
1356
 
1067
- #: x position of the circle center
1357
+ #: The x position of the circle center. Default is ``0.0``.
1068
1358
  x = Float(0.0, desc='x position of the circle center')
1069
1359
 
1070
- #: y position of the circle center
1360
+ #: The y position of the circle center. Default is ``0.0``.
1071
1361
  y = Float(0.0, desc='y position of the circle center')
1072
1362
 
1073
- #: radius of the circle
1363
+ #: Radius of the circle. Default is ``1.0``.
1074
1364
  r = Float(1.0, desc='radius of the circle')
1075
1365
 
1076
1366
  def contains(self, pos):
1077
- """Queries whether the coordinates in a given array lie within the
1078
- circular sector.
1079
- If no coordinate is inside, the nearest one outside is returned
1080
- if :attr:`~Sector.default_nearest` is True.
1367
+ """
1368
+ Check if the coordinates in a given array lie within the circular sector.
1369
+
1370
+ The method calculates the squared distance of each point from the center of the circle and
1371
+ checks if it lies within the sector, considering the sector's radius :attr:`r`. If no point
1372
+ is inside and :attr:`~SingleSector.default_nearest` is ``True``, the nearest point outside
1373
+ the sector will be returned.
1081
1374
 
1082
1375
  Parameters
1083
1376
  ----------
1084
- pos : array of floats
1085
- Array with the shape 3x[number of gridpoints] containing the
1086
- grid positions
1377
+ pos : array of :class:`floats<float>`
1378
+ A (3, N) array containing the positions of N grid points, where each point is
1379
+ represented by its x, y, and z coordinates.
1087
1380
 
1088
1381
  Returns
1089
1382
  -------
1090
- array of bools with as many entries as columns in pos
1091
- Array indicating which of the given positions lie within the
1092
- given sector
1093
-
1383
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1384
+ A boolean array of shape shape (N,) indicating which of the given positions lie within
1385
+ the circular sector. ``True`` if the grid point is inside the circular sector,
1386
+ otherwise ``False``.
1387
+
1388
+ Examples
1389
+ --------
1390
+ >>> import acoular as ac
1391
+ >>> grid = ac.RectGrid(increment=1)
1392
+ >>> grid.pos
1393
+ array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
1394
+ [-1., 0., 1., -1., 0., 1., -1., 0., 1.],
1395
+ [ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
1396
+ >>> sec = ac.CircSector(x=1, y=1, r=0.5)
1397
+ >>> sec.contains(grid.pos)
1398
+ array([False, False, False, False, False, False, False, False, True])
1094
1399
  """
1095
1400
  dr2 = (pos[0, :] - self.x) ** 2 + (pos[1, :] - self.y) ** 2
1096
1401
  # which points are in the circle?
1097
1402
  inds = dr2 - self.r**2 < self.abs_tol if self.include_border else dr2 - self.r**2 < -self.abs_tol
1098
1403
 
1099
- # if there's no poit inside
1404
+ # if there's no point inside
1100
1405
  if ~inds.any() and self.default_nearest:
1101
1406
  inds[argmin(dr2)] = True
1102
1407
 
@@ -1104,32 +1409,56 @@ class CircSector(SingleSector):
1104
1409
 
1105
1410
 
1106
1411
  class PolySector(SingleSector):
1107
- """Class for defining a polygon sector.
1412
+ """
1413
+ Class for defining a polygon sector.
1414
+
1415
+ Inherits from :class:`SingleSector` and provides functionality to define a polygonal sector on a
1416
+ 2D grid.
1417
+
1418
+ Notes
1419
+ -----
1420
+ The polygon is specified by the :class:`Polygon` class.
1108
1421
 
1109
- Can be used for 2D Grids for definining a polygon sector.
1422
+ Examples
1423
+ --------
1424
+ Load example data and set diffrent Sectors for intergration in the
1425
+ :ref:`sector integration example<sector_integration_example>`.
1110
1426
  """
1111
1427
 
1112
- # x1, y1, x2, y2, ... xn, yn :
1428
+ #: List of coordinates representing the polygon's vertices. The coordinates must define a closed
1429
+ #: polygon like ``x1, y1, x2, y2, ... xn, yn``.
1113
1430
  edges = List(Float)
1114
1431
 
1115
1432
  def contains(self, pos):
1116
- """Queries whether the coordinates in a given array lie within the
1117
- ploygon sector.
1118
- If no coordinate is inside, the nearest one to the rectangle center
1119
- is returned if :attr:`~Sector.default_nearest` is True.
1433
+ """
1434
+ Check if the coordinates in a given array lie within the polygon sector.
1435
+
1436
+ If no coordinate is inside, the nearest one to the rectangle center is returned if
1437
+ :attr:`~SingleSector.default_nearest` is ``True``.
1120
1438
 
1121
1439
  Parameters
1122
1440
  ----------
1123
- pos : array of floats
1124
- Array with the shape 3x[number of gridpoints] containing the
1125
- grid positions
1441
+ pos : array of :class:`floats<float>`
1442
+ A (3, N) array containing the positions of N grid points where each point is represented
1443
+ by its x, y, and z coordinates.
1126
1444
 
1127
1445
  Returns
1128
1446
  -------
1129
- array of bools with as many entries as columns in pos
1130
- Array indicating which of the given positions lie within the
1131
- given sector
1132
-
1447
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1448
+ A boolean array of shape `(N,)` indicating which of the given positions lie within the
1449
+ polygon sector. ``True`` if the grid point is inside the polygon, otherwise ``False``.
1450
+
1451
+ Examples
1452
+ --------
1453
+ >>> import acoular as ac
1454
+ >>> grid = ac.RectGrid(increment=1)
1455
+ >>> grid.pos
1456
+ array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
1457
+ [-1., 0., 1., -1., 0., 1., -1., 0., 1.],
1458
+ [ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
1459
+ >>> sec = ac.PolySector(edges=[0, 0, 1, 0, 1, 1, 0, 1])
1460
+ >>> sec.contains(grid.pos)
1461
+ array([False, False, False, False, True, True, False, True, True])
1133
1462
  """
1134
1463
  poly = Polygon(array(self.edges).reshape(-1, 2)[:, 0], array(self.edges).reshape(-1, 2)[:, 1])
1135
1464
  dists = poly.is_inside(pos[0, :], pos[1, :])
@@ -1144,71 +1473,111 @@ class PolySector(SingleSector):
1144
1473
 
1145
1474
 
1146
1475
  class ConvexSector(SingleSector):
1147
- """Class for defining a convex hull sector.
1476
+ """
1477
+ Class for defining a convex hull sector.
1148
1478
 
1149
- Can be used for 2D Grids for definining a convex hull sector.
1479
+ This class defines a convex hull sector for 2D grids. The sector is created using a list of edge
1480
+ coordinates :attr:`edges` which represent the vertices of a polygon. The convex hull is the
1481
+ smallest convex shape that contains all the given vertices.
1482
+
1483
+ Examples
1484
+ --------
1485
+ Load example data and set diffrent Sectors for intergration in the
1486
+ :ref:`sector integration example<sector_integration_example>`.
1150
1487
  """
1151
1488
 
1152
- # x1, y1, x2, y2, ... xn, yn :
1489
+ #: List of edge coordinates that define the convex hull. The coordinates must define a closed
1490
+ #: polygon that forms the convex hull like `x1, y1, x2, y2, ... xn, yn`.
1153
1491
  edges = List(Float)
1154
1492
 
1155
1493
  def contains(self, pos):
1156
- """Queries whether the coordinates in a given array lie within the
1157
- convex sector.
1158
- If no coordinate is inside, the nearest one to the rectangle center
1159
- is returned if :attr:`~Sector.default_nearest` is True.
1494
+ """
1495
+ Check if the coordinates in a given array lie within the convex sector.
1496
+
1497
+ If no coordinate is inside, the nearest one to the rectangle center is returned if
1498
+ :attr:`~SingleSector.default_nearest` is ``True``.
1160
1499
 
1161
1500
  Parameters
1162
1501
  ----------
1163
- pos : array of floats
1164
- Array with the shape 3x[number of gridpoints] containing the
1165
- grid positions
1502
+ pos : array of :class:`floats<float>`
1503
+ Array containing the positions of N grid points, shape `(3, N)`.
1166
1504
 
1167
1505
  Returns
1168
1506
  -------
1169
- array of bools with as many entries as columns in pos
1170
- Array indicating which of the given positions lie within the
1171
- given sector
1172
-
1507
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1508
+ An array of shape `(N,)` indicating which of the given positions
1509
+ lie within the given sector.
1510
+
1511
+ Examples
1512
+ --------
1513
+ >>> import acoular as ac
1514
+ >>> grid = ac.RectGrid(increment=1)
1515
+ >>> grid.pos
1516
+ array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
1517
+ [-1., 0., 1., -1., 0., 1., -1., 0., 1.],
1518
+ [ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
1519
+ >>> sec = ac.ConvexSector(edges=[0, 0, 1, 0, 1, 1, 0, 1])
1520
+ >>> sec.contains(grid.pos)
1521
+ array([False, False, False, False, True, True, False, True, True])
1173
1522
  """
1174
1523
  inds = in_hull(pos[:2, :].T, array(self.edges).reshape(-1, 2), border=self.include_border, tol=self.abs_tol)
1175
1524
 
1176
1525
  # if none inside, take nearest
1177
1526
  if ~inds.any() and self.default_nearest:
1178
- dr2 = array(self.edges).reshape(-1, 2).mean(0)
1527
+ dr2 = array(self.edges).reshape(-1, 2).mean(0) # Use the centroid of the polygon as the "center"
1179
1528
  inds[argmin(dr2)] = True
1180
1529
 
1181
1530
  return inds
1182
1531
 
1183
1532
 
1184
1533
  class MultiSector(Sector):
1185
- """Class for defining a sector consisting of multiple sectors.
1534
+ """
1535
+ Class for defining a sector consisting of multiple sectors.
1186
1536
 
1187
- Can be used to sum over different sectors. Takes a list of sectors
1188
- and returns the points contained in each sector.
1537
+ This class allows the combination of several individual sectors into one.
1189
1538
 
1539
+ Examples
1540
+ --------
1541
+ Load example data and set diffrent Sectors for intergration in the
1542
+ :ref:`sector integration example<sector_integration_example>`.
1190
1543
  """
1191
1544
 
1192
- #: List of :class:`acoular.grids.Sector` objects
1193
- #: to be mixed.
1545
+ #: List of :class:`Sector` objects to be mixed, each defining a different sector.
1194
1546
  sectors = List(Instance(Sector))
1195
1547
 
1196
1548
  def contains(self, pos):
1197
- """Queries whether the coordinates in a given array lie within any
1198
- of the sub-sectors.
1549
+ """
1550
+ Check if the coordinates in a given array lie within any of the sub-sectors.
1551
+
1552
+ This method iterates over the list of sectors, checking if each point in the given position
1553
+ array lies within any of the defined sectors.
1199
1554
 
1200
1555
  Parameters
1201
1556
  ----------
1202
- pos : array of floats
1203
- Array with the shape 3x[number of gridpoints] containing the
1204
- grid positions
1557
+ pos : array of :class:`floats<float>`
1558
+ A (3, N) array containing the positions of N grid points, where each point is
1559
+ represented by its x, y, and z coordinates.
1205
1560
 
1206
1561
  Returns
1207
1562
  -------
1208
- array of bools with as many entries as columns in pos
1209
- Array indicating which of the given positions lie within the
1210
- sectors
1211
-
1563
+ :class:`numpy.ndarray` of :class:`bools<bool>`
1564
+ A boolean array of shape `(N,)` indicating which of the given positions lie within any
1565
+ of the defined sectors. ``True`` if the grid point is inside the circular sector,
1566
+ ``False`` if otherwise.
1567
+
1568
+ Examples
1569
+ --------
1570
+ >>> import acoular as ac
1571
+ >>> grid = ac.RectGrid(increment=1)
1572
+ >>> grid.pos
1573
+ array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
1574
+ [-1., 0., 1., -1., 0., 1., -1., 0., 1.],
1575
+ [ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
1576
+ >>> sec1 = ac.RectSector(x_min=0, y_min=0)
1577
+ >>> sec2 = ac.CircSector(x=1, y=1, r=0.5)
1578
+ >>> multi_sec = ac.MultiSector(sectors=[sec1, sec2])
1579
+ >>> multi_sec.contains(grid.pos)
1580
+ array([False, False, False, False, True, True, False, True, True])
1212
1581
  """
1213
1582
  # initialize with only "False" entries
1214
1583
  inds = zeros(pos.shape[1], dtype=bool)