CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
fixes.py428 linesDownload Raw Back to utils
1"""Compatibility fixes for older version of python, numpy and scipy
2
3If you add content to this file, please give the version of the package
4at which the fix is no longer needed.
5"""
6
7# Authors: The scikit-learn developers
8# SPDX-License-Identifier: BSD-3-Clause
9
10import platform
11import struct
12
13import numpy as np
14import scipy
15import scipy.sparse.linalg
16import scipy.stats
17from scipy import optimize
18
19try:
20    import pandas as pd
21except ImportError:
22    pd = None
23
24from ..externals._packaging.version import parse as parse_version
25from .parallel import _get_threadpool_controller
26
27_IS_32BIT = 8 * struct.calcsize("P") == 32
28_IS_WASM = platform.machine() in ["wasm32", "wasm64"]
29
30np_version = parse_version(np.__version__)
31np_base_version = parse_version(np_version.base_version)
32sp_version = parse_version(scipy.__version__)
33sp_base_version = parse_version(sp_version.base_version)
34
35# TODO: We can consider removing the containers and importing
36# directly from SciPy when sparse matrices will be deprecated.
37CSR_CONTAINERS = [scipy.sparse.csr_matrix, scipy.sparse.csr_array]
38CSC_CONTAINERS = [scipy.sparse.csc_matrix, scipy.sparse.csc_array]
39COO_CONTAINERS = [scipy.sparse.coo_matrix, scipy.sparse.coo_array]
40LIL_CONTAINERS = [scipy.sparse.lil_matrix, scipy.sparse.lil_array]
41DOK_CONTAINERS = [scipy.sparse.dok_matrix, scipy.sparse.dok_array]
42BSR_CONTAINERS = [scipy.sparse.bsr_matrix, scipy.sparse.bsr_array]
43DIA_CONTAINERS = [scipy.sparse.dia_matrix, scipy.sparse.dia_array]
44
45# Remove when minimum scipy version is 1.11.0
46try:
47    from scipy.sparse import sparray  # noqa: F401
48
49    SPARRAY_PRESENT = True
50except ImportError:
51    SPARRAY_PRESENT = False
52
53
54def _object_dtype_isnan(X):
55    return X != X
56
57
58# TODO: Remove when SciPy 1.11 is the minimum supported version
59def _mode(a, axis=0):
60    if sp_version >= parse_version("1.9.0"):
61        mode = scipy.stats.mode(a, axis=axis, keepdims=True)
62        if sp_version >= parse_version("1.10.999"):
63            # scipy.stats.mode has changed returned array shape with axis=None
64            # and keepdims=True, see https://github.com/scipy/scipy/pull/17561
65            if axis is None:
66                mode = np.ravel(mode)
67        return mode
68    return scipy.stats.mode(a, axis=axis)
69
70
71# TODO: Remove when Scipy 1.12 is the minimum supported version
72if sp_base_version >= parse_version("1.12.0"):
73    _sparse_linalg_cg = scipy.sparse.linalg.cg
74else:
75
76    def _sparse_linalg_cg(A, b, **kwargs):
77        if "rtol" in kwargs:
78            kwargs["tol"] = kwargs.pop("rtol")
79        if "atol" not in kwargs:
80            kwargs["atol"] = "legacy"
81        return scipy.sparse.linalg.cg(A, b, **kwargs)
82
83
84# TODO : remove this when required minimum version of scipy >= 1.9.0
85def _yeojohnson_lambda(_neg_log_likelihood, x):
86    """Estimate the optimal Yeo-Johnson transformation parameter (lambda).
87
88    This function provides a compatibility workaround for versions of SciPy
89    older than 1.9.0, where `scipy.stats.yeojohnson` did not return
90    the estimated lambda directly.
91
92    Parameters
93    ----------
94    _neg_log_likelihood : callable
95        A function that computes the negative log-likelihood of the Yeo-Johnson
96        transformation for a given lambda. Used only for SciPy versions < 1.9.0.
97
98    x : array-like
99        Input data to estimate the Yeo-Johnson transformation parameter.
100
101    Returns
102    -------
103    lmbda : float
104        The estimated lambda parameter for the Yeo-Johnson transformation.
105    """
106    min_scipy_version = "1.9.0"
107
108    if sp_version < parse_version(min_scipy_version):
109        # choosing bracket -2, 2 like for boxcox
110        return optimize.brent(_neg_log_likelihood, brack=(-2, 2))
111
112    _, lmbda = scipy.stats.yeojohnson(x, lmbda=None)
113    return lmbda
114
115
116# TODO: Fuse the modern implementations of _sparse_min_max and _sparse_nan_min_max
117# into the public min_max_axis function when Scipy 1.11 is the minimum supported
118# version and delete the backport in the else branch below.
119if sp_base_version >= parse_version("1.11.0"):
120
121    def _sparse_min_max(X, axis):
122        the_min = X.min(axis=axis)
123        the_max = X.max(axis=axis)
124
125        if axis is not None:
126            the_min = the_min.toarray().ravel()
127            the_max = the_max.toarray().ravel()
128
129        return the_min, the_max
130
131    def _sparse_nan_min_max(X, axis):
132        the_min = X.nanmin(axis=axis)
133        the_max = X.nanmax(axis=axis)
134
135        if axis is not None:
136            the_min = the_min.toarray().ravel()
137            the_max = the_max.toarray().ravel()
138
139        return the_min, the_max
140
141else:
142    # This code is mostly taken from scipy 0.14 and extended to handle nans, see
143    # https://github.com/scikit-learn/scikit-learn/pull/11196
144    def _minor_reduce(X, ufunc):
145        major_index = np.flatnonzero(np.diff(X.indptr))
146
147        # reduceat tries casts X.indptr to intp, which errors
148        # if it is int64 on a 32 bit system.
149        # Reinitializing prevents this where possible, see #13737
150        X = type(X)((X.data, X.indices, X.indptr), shape=X.shape)
151        value = ufunc.reduceat(X.data, X.indptr[major_index])
152        return major_index, value
153
154    def _min_or_max_axis(X, axis, min_or_max):
155        N = X.shape[axis]
156        if N == 0:
157            raise ValueError("zero-size array to reduction operation")
158        M = X.shape[1 - axis]
159        mat = X.tocsc() if axis == 0 else X.tocsr()
160        mat.sum_duplicates()
161        major_index, value = _minor_reduce(mat, min_or_max)
162        not_full = np.diff(mat.indptr)[major_index] < N
163        value[not_full] = min_or_max(value[not_full], 0)
164        mask = value != 0
165        major_index = np.compress(mask, major_index)
166        value = np.compress(mask, value)
167
168        if axis == 0:
169            res = scipy.sparse.coo_matrix(
170                (value, (np.zeros(len(value)), major_index)),
171                dtype=X.dtype,
172                shape=(1, M),
173            )
174        else:
175            res = scipy.sparse.coo_matrix(
176                (value, (major_index, np.zeros(len(value)))),
177                dtype=X.dtype,
178                shape=(M, 1),
179            )
180        return res.toarray().ravel()
181
182    def _sparse_min_or_max(X, axis, min_or_max):
183        if axis is None:
184            if 0 in X.shape:
185                raise ValueError("zero-size array to reduction operation")
186            zero = X.dtype.type(0)
187            if X.nnz == 0:
188                return zero
189            m = min_or_max.reduce(X.data.ravel())
190            if X.nnz != np.prod(X.shape):
191                m = min_or_max(zero, m)
192            return m
193        if axis < 0:
194            axis += 2
195        if (axis == 0) or (axis == 1):
196            return _min_or_max_axis(X, axis, min_or_max)
197        else:
198            raise ValueError("invalid axis, use 0 for rows, or 1 for columns")
199
200    def _sparse_min_max(X, axis):
201        return (
202            _sparse_min_or_max(X, axis, np.minimum),
203            _sparse_min_or_max(X, axis, np.maximum),
204        )
205
206    def _sparse_nan_min_max(X, axis):
207        return (
208            _sparse_min_or_max(X, axis, np.fmin),
209            _sparse_min_or_max(X, axis, np.fmax),
210        )
211
212
213# For +1.25 NumPy versions exceptions and warnings are being moved
214# to a dedicated submodule.
215if np_version >= parse_version("1.25.0"):
216    from numpy.exceptions import ComplexWarning, VisibleDeprecationWarning
217else:
218    from numpy import (  # noqa: F401
219        ComplexWarning,
220        VisibleDeprecationWarning,
221    )
222
223
224# TODO: Adapt when Pandas > 2.2 is the minimum supported version
225def pd_fillna(pd, frame):
226    pd_version = parse_version(pd.__version__).base_version
227    if parse_version(pd_version) < parse_version("2.2"):
228        frame = frame.fillna(value=np.nan)
229    else:
230        infer_objects_kwargs = (
231            {} if parse_version(pd_version) >= parse_version("3") else {"copy": False}
232        )
233        with pd.option_context("future.no_silent_downcasting", True):
234            frame = frame.fillna(value=np.nan).infer_objects(**infer_objects_kwargs)
235    return frame
236
237
238# TODO: remove when SciPy 1.12 is the minimum supported version
239def _preserve_dia_indices_dtype(
240    sparse_container, original_container_format, requested_sparse_format
241):
242    """Preserve indices dtype for SciPy < 1.12 when converting from DIA to CSR/CSC.
243
244    For SciPy < 1.12, DIA arrays indices are upcasted to `np.int64` that is
245    inconsistent with DIA matrices. We downcast the indices dtype to `np.int32` to
246    be consistent with DIA matrices.
247
248    The converted indices arrays are affected back inplace to the sparse container.
249
250    Parameters
251    ----------
252    sparse_container : sparse container
253        Sparse container to be checked.
254    requested_sparse_format : str or bool
255        The type of format of `sparse_container`.
256
257    Notes
258    -----
259    See https://github.com/scipy/scipy/issues/19245 for more details.
260    """
261    if original_container_format == "dia_array" and requested_sparse_format in (
262        "csr",
263        "coo",
264    ):
265        if requested_sparse_format == "csr":
266            index_dtype = _smallest_admissible_index_dtype(
267                arrays=(sparse_container.indptr, sparse_container.indices),
268                maxval=max(sparse_container.nnz, sparse_container.shape[1]),
269                check_contents=True,
270            )
271            sparse_container.indices = sparse_container.indices.astype(
272                index_dtype, copy=False
273            )
274            sparse_container.indptr = sparse_container.indptr.astype(
275                index_dtype, copy=False
276            )
277        else:  # requested_sparse_format == "coo"
278            index_dtype = _smallest_admissible_index_dtype(
279                maxval=max(sparse_container.shape)
280            )
281            sparse_container.row = sparse_container.row.astype(index_dtype, copy=False)
282            sparse_container.col = sparse_container.col.astype(index_dtype, copy=False)
283
284
285# TODO: remove when SciPy 1.12 is the minimum supported version
286def _smallest_admissible_index_dtype(arrays=(), maxval=None, check_contents=False):
287    """Based on input (integer) arrays `a`, determine a suitable index data
288    type that can hold the data in the arrays.
289
290    This function returns `np.int64` if it either required by `maxval` or based on the
291    largest precision of the dtype of the arrays passed as argument, or by their
292    contents (when `check_contents is True`). If none of the condition requires
293    `np.int64` then this function returns `np.int32`.
294
295    Parameters
296    ----------
297    arrays : ndarray or tuple of ndarrays, default=()
298        Input arrays whose types/contents to check.
299
300    maxval : float, default=None
301        Maximum value needed.
302
303    check_contents : bool, default=False
304        Whether to check the values in the arrays and not just their types.
305        By default, check only the types.
306
307    Returns
308    -------
309    dtype : {np.int32, np.int64}
310        Suitable index data type (int32 or int64).
311    """
312
313    int32min = np.int32(np.iinfo(np.int32).min)
314    int32max = np.int32(np.iinfo(np.int32).max)
315
316    if maxval is not None:
317        if maxval > np.iinfo(np.int64).max:
318            raise ValueError(
319                f"maxval={maxval} is to large to be represented as np.int64."
320            )
321        if maxval > int32max:
322            return np.int64
323
324    if isinstance(arrays, np.ndarray):
325        arrays = (arrays,)
326
327    for arr in arrays:
328        if not isinstance(arr, np.ndarray):
329            raise TypeError(
330                f"Arrays should be of type np.ndarray, got {type(arr)} instead."
331            )
332        if not np.issubdtype(arr.dtype, np.integer):
333            raise ValueError(
334                f"Array dtype {arr.dtype} is not supported for index dtype. We expect "
335                "integral values."
336            )
337        if not np.can_cast(arr.dtype, np.int32):
338            if not check_contents:
339                # when `check_contents` is False, we stay on the safe side and return
340                # np.int64.
341                return np.int64
342            if arr.size == 0:
343                # a bigger type not needed yet, let's look at the next array
344                continue
345            else:
346                maxval = arr.max()
347                minval = arr.min()
348                if minval < int32min or maxval > int32max:
349                    # a big index type is actually needed
350                    return np.int64
351
352    return np.int32
353
354
355# TODO: Remove when Scipy 1.12 is the minimum supported version
356if sp_version < parse_version("1.12"):
357    from ..externals._scipy.sparse.csgraph import laplacian
358else:
359    from scipy.sparse.csgraph import (
360        laplacian,  # noqa: F401  # pragma: no cover
361    )
362
363
364# TODO: Remove when Python min version >= 3.12.
365def tarfile_extractall(tarfile, path):
366    try:
367        # Use filter="data" to prevent the most dangerous security issues.
368        # For more details, see
369        # https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall
370        tarfile.extractall(path, filter="data")
371    except TypeError:
372        tarfile.extractall(path)
373
374
375def _in_unstable_openblas_configuration():
376    """Return True if in an unstable configuration for OpenBLAS"""
377
378    # Import libraries which might load OpenBLAS.
379    import numpy  # noqa: F401
380    import scipy  # noqa: F401
381
382    modules_info = _get_threadpool_controller().info()
383
384    open_blas_used = any(info["internal_api"] == "openblas" for info in modules_info)
385    if not open_blas_used:
386        return False
387
388    # OpenBLAS 0.3.16 fixed instability for arm64, see:
389    # https://github.com/xianyi/OpenBLAS/blob/1b6db3dbba672b4f8af935bd43a1ff6cff4d20b7/Changelog.txt#L56-L58
390    openblas_arm64_stable_version = parse_version("0.3.16")
391    for info in modules_info:
392        if info["internal_api"] != "openblas":
393            continue
394        openblas_version = info.get("version")
395        openblas_architecture = info.get("architecture")
396        if openblas_version is None or openblas_architecture is None:
397            # Cannot be sure that OpenBLAS is good enough. Assume unstable:
398            return True  # pragma: no cover
399        if (
400            openblas_architecture == "neoversen1"
401            and parse_version(openblas_version) < openblas_arm64_stable_version
402        ):
403            # See discussions in https://github.com/numpy/numpy/issues/19411
404            return True  # pragma: no cover
405    return False
406
407
408# TODO: Remove when Scipy 1.15 is the minimum supported version. In scipy 1.15,
409# the internal info details (via 'iprint' and 'disp' options) were dropped,
410# following the LBFGS rewrite from Fortran to C, see
411# https://github.com/scipy/scipy/issues/23186#issuecomment-2987801035. For
412# scipy 1.15, 'iprint' and 'disp' have no effect and for scipy >= 1.16 a
413# DeprecationWarning is emitted.
414def _get_additional_lbfgs_options_dict(key, value):
415    return {} if sp_version >= parse_version("1.15") else {key: value}
416
417
418# TODO(pyarrow): Remove when minimum pyarrow version is 17.0.0
419PYARROW_VERSION_BELOW_17 = False
420try:
421    import pyarrow
422
423    pyarrow_version = parse_version(pyarrow.__version__)
424    if pyarrow_version < parse_version("17.0.0"):
425        PYARROW_VERSION_BELOW_17 = True
426except ModuleNotFoundError:  # pragma: no cover
427    pass
428 
Aluode/PerceptionLabPortable · CoolFace