diff options
Diffstat (limited to 'src/silx/math/combo.pyx')
-rw-r--r-- | src/silx/math/combo.pyx | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/src/silx/math/combo.pyx b/src/silx/math/combo.pyx new file mode 100644 index 0000000..e24edda --- /dev/null +++ b/src/silx/math/combo.pyx @@ -0,0 +1,329 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2018 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This module provides combination of statistics as single operation. + +For now it provides min/max (and optionally positive min) and indices +of first occurrences (i.e., argmin/argmax) in a single pass. +""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "24/04/2018" + +cimport cython +from .math_compatibility cimport isnan, isfinite, INFINITY + + +import numpy + + +# All supported types +ctypedef fused _number: + float + double + long double + signed char + signed short + signed int + signed long + signed long long + unsigned char + unsigned short + unsigned int + unsigned long + unsigned long long + +# All supported floating types: +# cython.floating + long double +ctypedef fused _floating: + float + double + long double + + +class _MinMaxResult(object): + """Object storing result from :func:`min_max`""" + + def __init__(self, minimum, min_pos, maximum, + argmin, argmin_pos, argmax): + self._minimum = minimum + self._min_positive = min_pos + self._maximum = maximum + + self._argmin = argmin + self._argmin_positive = argmin_pos + self._argmax = argmax + + minimum = property( + lambda self: self._minimum, + doc="Minimum value of the array") + maximum = property( + lambda self: self._maximum, + doc="Maximum value of the array") + + argmin = property( + lambda self: self._argmin, + doc="Index of the first occurrence of the minimum value") + argmax = property( + lambda self: self._argmax, + doc="Index of the first occurrence of the maximum value") + + min_positive = property( + lambda self: self._min_positive, + doc="""Strictly positive minimum value + + It is None if no value is strictly positive. + """) + argmin_positive = property( + lambda self: self._argmin_positive, + doc="""Index of the strictly positive minimum value. + + It is None if no value is strictly positive. + It is the index of the first occurrence.""") + + def __getitem__(self, key): + if key == 0: + return self.minimum + elif key == 1: + return self.maximum + else: + raise IndexError("Index out of range") + + +@cython.initializedcheck(False) +@cython.boundscheck(False) +@cython.wraparound(False) +def _min_max(_number[::1] data, bint min_positive=False): + """:func:`min_max` implementation including infinite values + + See :func:`min_max` for documentation. + """ + cdef: + _number value, minimum, min_pos, maximum + unsigned int length + unsigned int index = 0 + unsigned int min_index = 0 + unsigned int min_pos_index = 0 + unsigned int max_index = 0 + + length = len(data) + + if length == 0: + raise ValueError('Zero-size array') + + with nogil: + # Init starting values + value = data[0] + minimum = value + maximum = value + if min_positive and value > 0: + min_pos = value + else: + min_pos = 0 + + if _number in _floating: + # For floating, loop until first not NaN value + for index in range(length): + value = data[index] + if not isnan(value): + minimum = value + min_index = index + maximum = value + max_index = index + break + + if not min_positive: + for index in range(index, length): + value = data[index] + if value > maximum: + maximum = value + max_index = index + elif value < minimum: + minimum = value + min_index = index + + else: + # Loop until min_pos is defined + for index in range(index, length): + value = data[index] + if value > maximum: + maximum = value + max_index = index + elif value < minimum: + minimum = value + min_index = index + + if value > 0: + min_pos = value + min_pos_index = index + break + + # Loop until the end + for index in range(index + 1, length): + value = data[index] + if value > maximum: + maximum = value + max_index = index + else: + if value < minimum: + minimum = value + min_index = index + + if 0 < value < min_pos: + min_pos = value + min_pos_index = index + + return _MinMaxResult(minimum, + min_pos if min_pos > 0 else None, + maximum, + min_index, + min_pos_index if min_pos > 0 else None, + max_index) + + +@cython.initializedcheck(False) +@cython.boundscheck(False) +@cython.wraparound(False) +def _finite_min_max(_floating[::1] data, bint min_positive=False): + """:func:`min_max` implementation for floats skipping infinite values + + See :func:`min_max` for documentation. + """ + cdef: + _floating value, minimum, min_pos, maximum + unsigned int length + unsigned int index = 0 + unsigned int min_index = 0 + unsigned int min_pos_index = 0 + unsigned int max_index = 0 + + length = len(data) + + if length == 0: + raise ValueError('Zero-size array') + + with nogil: + minimum = INFINITY + maximum = -INFINITY + min_pos = INFINITY + + if not min_positive: + for index in range(length): + value = data[index] + if isfinite(value): + if value > maximum: + maximum = value + max_index = index + if value < minimum: + minimum = value + min_index = index + + else: + for index in range(index, length): + value = data[index] + if isfinite(value): + if value > maximum: + maximum = value + max_index = index + if value < minimum: + minimum = value + min_index = index + + if 0. < value < min_pos: + min_pos = value + min_pos_index = index + + return _MinMaxResult(minimum if isfinite(minimum) else None, + min_pos if isfinite(min_pos) else None, + maximum if isfinite(maximum) else None, + min_index if isfinite(minimum) else None, + min_pos_index if isfinite(min_pos) else None, + max_index if isfinite(maximum) else None) + + +def min_max(data not None, bint min_positive=False, bint finite=False): + """Returns min, max and optionally strictly positive min of data. + + It also computes the indices of first occurrence of min/max. + + NaNs are ignored while computing min/max unless all data is NaNs, + in which case returned min/max are NaNs. + + The result data type is that of the input data, except for the following cases. + For input using non-native bytes order, the result is returned as native + floating-point or integers. For input using 16-bits floating-point, + the result is returned as 32-bits floating-point. + + Examples: + + >>> import numpy + >>> data = numpy.arange(10) + + Usage as a function returning min and max: + + >>> min_, max_ = min_max(data) + + Usage as a function returning a result object to access all information: + + >>> result = min_max(data) # Do not get positive min + >>> result.minimum, result.argmin + 0, 0 + >>> result.maximum, result.argmax + 9, 10 + >>> result.min_positive, result.argmin_positive # Not computed + None, None + + Getting strictly positive min information: + + >>> result = min_max(data, min_positive=True) + >>> result.min_positive, result.argmin_positive # Computed + 1, 1 + + If *finite* is True, min/max information is computed only from finite data. + Then, all result fields (include minimum and maximum) can be None + when all data is infinity or NaN. + + :param data: Array-like dataset + :param bool min_positive: True to compute the positive min and argmin + Default: False. + :param bool finite: True to compute min/max from finite data only + Default: False. + :returns: An object with minimum, maximum and min_positive attributes + and the indices of first occurrence in the flattened data: + argmin, argmax and argmin_positive attributes. + If all data is <= 0 or min_positive argument is False, then + min_positive and argmin_positive are None. + :raises: ValueError if data is empty + """ + data = numpy.array(data, copy=False) + native_endian_dtype = data.dtype.newbyteorder('N') + if native_endian_dtype.kind == 'f' and native_endian_dtype.itemsize == 2: + # Use native float32 instead of float16 + native_endian_dtype = "=f4" + data = numpy.ascontiguousarray(data, dtype=native_endian_dtype).ravel() + if finite and data.dtype.kind == 'f': + return _finite_min_max(data, min_positive) + else: + return _min_max(data, min_positive) |