@@ -98,20 +98,6 @@ def asarray_of_rows(a, **kwargs):
9898 return result
9999
100100
101- def asarray_of_arrays (a , ** kwargs ):
102- """Convert the input to an array consisting of arrays.
103-
104- A one-dimensional :class:`numpy.ndarray` with `dtype=object` is
105- returned, containing the elements of `a` as :class:`numpy.ndarray`
106- (whose `dtype` and other options can be specified with `**kwargs`).
107-
108- """
109- result = np .empty (len (a ), dtype = object )
110- for i , element in enumerate (a ):
111- result [i ] = np .asarray (element , ** kwargs )
112- return result
113-
114-
115101def strict_arange (start , stop , step = 1 , endpoint = False , dtype = None , ** kwargs ):
116102 """Like :func:`numpy.arange`, but compensating numeric errors.
117103
@@ -202,7 +188,7 @@ def normalize(p, grid, xnorm):
202188
203189def probe (p , grid , x ):
204190 """Determine the value at position `x` in the sound field `p`."""
205- grid = asarray_of_arrays (grid )
191+ grid = XyzComponents (grid )
206192 x = asarray_1d (x )
207193 r = np .linalg .norm (grid - x )
208194 idx = np .unravel_index (r .argmin (), r .shape )
@@ -228,7 +214,7 @@ def displacement(v, omega):
228214 d(x, t) = \int_0^t v(x, t) dt
229215
230216 """
231- v = asarray_of_arrays (v )
217+ v = XyzComponents (v )
232218 return v / (1j * omega )
233219
234220
@@ -246,3 +232,91 @@ def db(x, power=False):
246232 """
247233 with np .errstate (divide = 'ignore' ):
248234 return 10 if power else 20 * np .log10 (np .abs (x ))
235+
236+
237+ class XyzComponents (np .ndarray ):
238+ """See __init__()."""
239+
240+ def __init__ (self , arg , ** kwargs ):
241+ """Triple (or pair) of arrays: x, y, and optionally z.
242+
243+ Instances of this class can be used to store coordinate grids
244+ (either regular grids like in :func:`sfs.util.xyz_grid` or
245+ arbitrary point clouds) or vector fields (e.g. particle
246+ velocity).
247+
248+ This class is a subclass of :class:`numpy.ndarray`. It is
249+ one-dimensional (like a plain :class:`list`) and has a length of
250+ 3 (or 2, if no z-component is available). It uses
251+ `dtype=object` in order to be able to store other
252+ `numpy.ndarray`\s of arbitrary shapes but also scalars, if
253+ needed. Because it is a NumPy array subclass, it can be used in
254+ operations with scalars and "normal" NumPy arrays, as long as
255+ they have a compatible shape. Like any NumPy array, instances
256+ of this class are iterable and can be used, e.g., in for-loops
257+ and tuple unpacking. If slicing or broadcasting leads to an
258+ incompatible shape, a plain `numpy.ndarray` with `dtype=object`
259+ is returned.
260+
261+ Parameters
262+ ----------
263+ arg : triple or pair of array_like
264+ The values to be used as X, Y and Z arrays. Z is optional.
265+ **kwargs
266+ All further arguments are forwarded to
267+ :func:`numpy.asarray`.
268+
269+ """
270+ # This method does nothing, it's only here for the documentation!
271+
272+ def __new__ (cls , arg , ** kwargs ):
273+ # object arrays cannot be created and populated in a single step:
274+ obj = np .ndarray .__new__ (cls , len (arg ), dtype = object )
275+ for i , component in enumerate (arg ):
276+ obj [i ] = np .asarray (component , ** kwargs )
277+ return obj
278+
279+ def __array_finalize__ (self , obj ):
280+ if self .ndim == 0 :
281+ pass # this is allowed, e.g. for np.inner()
282+ elif self .ndim > 1 or len (self ) not in (2 , 3 ):
283+ raise ValueError ("XyzComponents can only have 2 or 3 components" )
284+
285+ def __array_prepare__ (self , obj , context = None ):
286+ if obj .ndim == 1 and len (obj ) in (2 , 3 ):
287+ return obj .view (XyzComponents )
288+ return obj
289+
290+ def __array_wrap__ (self , obj , context = None ):
291+ if obj .ndim != 1 or len (obj ) not in (2 , 3 ):
292+ return obj .view (np .ndarray )
293+ return obj
294+
295+ def __getitem__ (self , index ):
296+ if isinstance (index , slice ):
297+ start , stop , step = index .indices (len (self ))
298+ if start == 0 and stop in (2 , 3 ) and step == 1 :
299+ return np .ndarray .__getitem__ (self , index )
300+ # Slices other than xy and xyz are "downgraded" to ndarray
301+ return np .ndarray .__getitem__ (self .view (np .ndarray ), index )
302+
303+ def __repr__ (self ):
304+ return 'XyzComponents(\n ' + ',\n ' .join (
305+ ' {0}={1}' .format (name , repr (data ).replace ('\n ' , '\n ' ))
306+ for name , data in zip ('xyz' , self )) + ')'
307+
308+ def make_property (index , doc ):
309+
310+ def getter (self ):
311+ return self [index ]
312+
313+ def setter (self , value ):
314+ self [index ] = value
315+
316+ return property (getter , setter , doc = doc )
317+
318+ x = make_property (0 , doc = 'x-component.' )
319+ y = make_property (1 , doc = 'y-component.' )
320+ z = make_property (2 , doc = 'z-component (optional).' )
321+
322+ del make_property
0 commit comments