# coding: utf-8 # /*########################################################################## # # Copyright (c) 2016-2017 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)