"""mesh, md_map, flatten work with multidimensional data."""
from __future__ import print_function
from itertools import count, islice
[docs]def flatten(array):
"""Flatten an *array* of arbitrary dimension.
*array* must be list or a tuple (can be nested).
Depth-first flattening is used.
Return an iterator over the flattened array.
Examples:
>>> arr = [1, 2, 3]
>>> list(flatten(arr)) == arr
True
>>> arr = [[1, 2, 3, [4]], 5, [[6]], 7]
>>> list(flatten(arr))
[1, 2, 3, 4, 5, 6, 7]
>>> arr = [[1, 2, [3], 4], 5, [[6]], 7]
>>> list(flatten(arr))
[1, 2, 3, 4, 5, 6, 7]
"""
for el in array:
if isinstance(el, (list, tuple)):
for val in flatten(el):
yield val
else:
yield el
[docs]def md_map(f, array):
"""Multidimensional map.
Return function *f* mapped to contents
of a multidimensional *array*.
*f* is a function of one argument.
*Array* must be a list of (possibly nested) lists.
Its contents remain unchanged.
Returned array has same dimensions as the initial one.
If *array* is not a list, :exc:`.LenaTypeError`
is raised.
>>> from lena.math import md_map
>>> arr = [-1, 1, 0]
>>> md_map(abs, arr)
[1, 1, 0]
>>> arr = [[0, -1], [2, 3]]
>>> md_map(abs, arr)
[[0, 1], [2, 3]]
"""
# multidimensional map with iterables is pretty useless,
# because it iterables can be used in a simple 1-dimensional map.
# All containers must be materialized.
# This idea abandoned, because Lena vector3 has len.
# if hasattr(arr_0, '__len__'):
## numpy.array has length, but not always:
## >>> len(numpy.array(0))
## Traceback (most recent call last):
## File "<stdin>", line 1, in <module>
## TypeError: len() of unsized object
## >>> len(numpy.array([0])) works fine.
## strings have length too, which is probably not so good,
## but there are also bytes, unicode,
## and we don't want to deal with all of that zoo - that's why
## we don't check for those types explicitly.
## Python complex numbers don't have len, fortunately.
if not isinstance(array, list):
raise lena.core.LenaTypeError(
"array must be a list, {} provided".format(array)
)
# If *array* was empty, empty list is returned.
# In this implementation we explicitly check that array is a list.
## We don't check for exception here, because it will be raised
## in case of any problems, and it will be simple to understand.
if not len(array):
return []
arr_0 = array[0]
# Tuples can be (data, context) pairs,
# they should not be expanded in ReduceBinContent.
# if isinstance(arr_0, (list, tuple)):
if isinstance(arr_0, list):
return [md_map(f, ar) for ar in array]
else:
# return list(map(f, array))
return [f(val) for val in array]
[docs]def mesh(ranges, nbins):
"""Generate equally spaced mesh of *nbins* cells in the given range.
Parameters:
ranges: a pair of (min, max) values for 1-dimensional range,
or a list of ranges in corresponding dimensions.
nbins: number of bins for 1-dimensional range,
or a list of number of bins in corresponding dimensions.
>>> from lena.math import mesh
>>> mesh((0, 1), 2)
[0, 0.5, 1]
>>> mesh(((0, 1), (10, 12)), (1, 2))
[[0, 1], [10, 11.0, 12]]
Note that because of rounding errors
two meshes should not be naively compared,
they will probably appear different.
One should use :ref:`isclose <isclose_label>` for comparison.
>>> from lena.math import isclose
>>> isclose(mesh((0, 1), 10),
... [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
True
"""
# >>> mesh((0, 1), 10) ==
# ... [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
# False, because of 0.30000000000000004
def mesh_1d(nbins, range_):
step = float(range_[1] - range_[0]) / nbins
res = list(islice(count(range_[0], step), nbins)) + [range_[1]]
# res = [range_[0] + val * step for val in range(nbins + 1)]
return res
if not isinstance(nbins, (tuple, list)):
return mesh_1d(nbins, ranges)
dim = len(nbins)
edges = []
for i in range(dim):
edges.append(mesh_1d(nbins[i], ranges[i]))
return edges
[docs]def refine_mesh(arr, refinement):
"""Refine (subdivide) one-dimensional mesh *arr*.
*refinement* is the number of subdivisions.
It must be not less than 1.
Note that to create a new mesh may be faster.
Use this function only for convenience.
"""
# *arr* must be one-dimensional.
new_mesh = [arr[0]]
for low, up in zip(arr, arr[1:]):
new_mesh.extend(mesh((low, up), refinement)[1:])
return new_mesh