Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. ย
See raw diff
- .gitattributes +26 -0
- scipy/__config__.py +161 -0
- scipy/__init__.py +169 -0
- scipy/__pycache__/__config__.cpython-39.pyc +0 -0
- scipy/__pycache__/__init__.cpython-39.pyc +0 -0
- scipy/__pycache__/_distributor_init.cpython-39.pyc +0 -0
- scipy/__pycache__/conftest.cpython-39.pyc +0 -0
- scipy/__pycache__/version.cpython-39.pyc +0 -0
- scipy/_distributor_init.py +32 -0
- scipy/_lib/__init__.py +14 -0
- scipy/_lib/__pycache__/__init__.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_array_api.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_bunch.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_ccallback.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_disjoint_set.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_docscrape.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_finite_differences.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_gcutils.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_pep440.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_testutils.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_threadsafety.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_tmpdirs.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/_util.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/decorator.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/deprecation.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/doccer.cpython-39.pyc +0 -0
- scipy/_lib/__pycache__/uarray.cpython-39.pyc +0 -0
- scipy/_lib/_array_api.py +353 -0
- scipy/_lib/_bunch.py +225 -0
- scipy/_lib/_ccallback.py +251 -0
- scipy/_lib/_ccallback_c.cp39-win_amd64.dll.a +0 -0
- scipy/_lib/_ccallback_c.cp39-win_amd64.pyd +0 -0
- scipy/_lib/_disjoint_set.py +254 -0
- scipy/_lib/_docscrape.py +679 -0
- scipy/_lib/_finite_differences.py +145 -0
- scipy/_lib/_fpumode.cp39-win_amd64.dll.a +0 -0
- scipy/_lib/_fpumode.cp39-win_amd64.pyd +0 -0
- scipy/_lib/_gcutils.py +105 -0
- scipy/_lib/_pep440.py +487 -0
- scipy/_lib/_test_ccallback.cp39-win_amd64.dll.a +0 -0
- scipy/_lib/_test_ccallback.cp39-win_amd64.pyd +0 -0
- scipy/_lib/_test_deprecation_call.cp39-win_amd64.dll.a +0 -0
- scipy/_lib/_test_deprecation_call.cp39-win_amd64.pyd +0 -0
- scipy/_lib/_test_deprecation_def.cp39-win_amd64.dll.a +0 -0
- scipy/_lib/_test_deprecation_def.cp39-win_amd64.pyd +0 -0
- scipy/_lib/_testutils.py +257 -0
- scipy/_lib/_threadsafety.py +58 -0
- scipy/_lib/_tmpdirs.py +86 -0
- scipy/_lib/_uarray/LICENSE +29 -0
- scipy/_lib/_uarray/__init__.py +116 -0
.gitattributes
CHANGED
@@ -33,3 +33,29 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
scipy/fft/_pocketfft/pypocketfft.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
37 |
+
scipy/interpolate/_rbfinterp_pythran.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
38 |
+
scipy/io/_fast_matrix_market/_fmm_core.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
39 |
+
scipy/linalg/_flapack.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
40 |
+
scipy/misc/face.dat filter=lfs diff=lfs merge=lfs -text
|
41 |
+
scipy/optimize/_group_columns.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
42 |
+
scipy/optimize/_highs/_highs_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
43 |
+
scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
44 |
+
scipy/signal/_spectral.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
45 |
+
scipy/sparse/_sparsetools.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
46 |
+
scipy/spatial/_ckdtree.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
47 |
+
scipy/spatial/_distance_pybind.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
48 |
+
scipy/spatial/_qhull.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
49 |
+
scipy/special/_ufuncs.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
50 |
+
scipy/special/_ufuncs_cxx.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
51 |
+
scipy/special/cython_special.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
52 |
+
scipy/stats/_boost/beta_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
53 |
+
scipy/stats/_boost/binom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
54 |
+
scipy/stats/_boost/hypergeom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
55 |
+
scipy/stats/_boost/invgauss_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
56 |
+
scipy/stats/_boost/nbinom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
57 |
+
scipy/stats/_boost/ncf_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
58 |
+
scipy/stats/_boost/nct_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
59 |
+
scipy/stats/_boost/ncx2_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
60 |
+
scipy/stats/_stats_pythran.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
61 |
+
scipy/stats/_unuran/unuran_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
scipy/__config__.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This file is generated by SciPy's build process
|
2 |
+
# It contains system_info results at the time of building this package.
|
3 |
+
from enum import Enum
|
4 |
+
|
5 |
+
__all__ = ["show"]
|
6 |
+
_built_with_meson = True
|
7 |
+
|
8 |
+
|
9 |
+
class DisplayModes(Enum):
|
10 |
+
stdout = "stdout"
|
11 |
+
dicts = "dicts"
|
12 |
+
|
13 |
+
|
14 |
+
def _cleanup(d):
|
15 |
+
"""
|
16 |
+
Removes empty values in a `dict` recursively
|
17 |
+
This ensures we remove values that Meson could not provide to CONFIG
|
18 |
+
"""
|
19 |
+
if isinstance(d, dict):
|
20 |
+
return { k: _cleanup(v) for k, v in d.items() if v != '' and _cleanup(v) != '' }
|
21 |
+
else:
|
22 |
+
return d
|
23 |
+
|
24 |
+
|
25 |
+
CONFIG = _cleanup(
|
26 |
+
{
|
27 |
+
"Compilers": {
|
28 |
+
"c": {
|
29 |
+
"name": "gcc",
|
30 |
+
"linker": "ld.bfd",
|
31 |
+
"version": "10.3.0",
|
32 |
+
"commands": "cc",
|
33 |
+
"args": "",
|
34 |
+
"linker args": "",
|
35 |
+
},
|
36 |
+
"cython": {
|
37 |
+
"name": "cython",
|
38 |
+
"linker": "cython",
|
39 |
+
"version": "3.0.8",
|
40 |
+
"commands": "cython",
|
41 |
+
"args": "",
|
42 |
+
"linker args": "",
|
43 |
+
},
|
44 |
+
"c++": {
|
45 |
+
"name": "gcc",
|
46 |
+
"linker": "ld.bfd",
|
47 |
+
"version": "10.3.0",
|
48 |
+
"commands": "c++",
|
49 |
+
"args": "",
|
50 |
+
"linker args": "",
|
51 |
+
},
|
52 |
+
"fortran": {
|
53 |
+
"name": "gcc",
|
54 |
+
"linker": "ld.bfd",
|
55 |
+
"version": "10.3.0",
|
56 |
+
"commands": "gfortran",
|
57 |
+
"args": "",
|
58 |
+
"linker args": "",
|
59 |
+
},
|
60 |
+
"pythran": {
|
61 |
+
"version": "0.15.0",
|
62 |
+
"include directory": r"C:\Users\runneradmin\AppData\Local\Temp\pip-build-env-rikezz0v\overlay\Lib\site-packages/pythran"
|
63 |
+
},
|
64 |
+
},
|
65 |
+
"Machine Information": {
|
66 |
+
"host": {
|
67 |
+
"cpu": "x86_64",
|
68 |
+
"family": "x86_64",
|
69 |
+
"endian": "little",
|
70 |
+
"system": "windows",
|
71 |
+
},
|
72 |
+
"build": {
|
73 |
+
"cpu": "x86_64",
|
74 |
+
"family": "x86_64",
|
75 |
+
"endian": "little",
|
76 |
+
"system": "windows",
|
77 |
+
},
|
78 |
+
"cross-compiled": bool("False".lower().replace('false', '')),
|
79 |
+
},
|
80 |
+
"Build Dependencies": {
|
81 |
+
"blas": {
|
82 |
+
"name": "openblas",
|
83 |
+
"found": bool("True".lower().replace('false', '')),
|
84 |
+
"version": "0.3.21.dev",
|
85 |
+
"detection method": "pkgconfig",
|
86 |
+
"include directory": r"/c/opt/64/include",
|
87 |
+
"lib directory": r"/c/opt/64/lib",
|
88 |
+
"openblas configuration": "USE_64BITINT= DYNAMIC_ARCH=1 DYNAMIC_OLDER= NO_CBLAS= NO_LAPACK= NO_LAPACKE= NO_AFFINITY=1 USE_OPENMP= SKYLAKEX MAX_THREADS=2",
|
89 |
+
"pc file directory": r"c:/opt/64/lib/pkgconfig",
|
90 |
+
},
|
91 |
+
"lapack": {
|
92 |
+
"name": "openblas",
|
93 |
+
"found": bool("True".lower().replace('false', '')),
|
94 |
+
"version": "0.3.21.dev",
|
95 |
+
"detection method": "pkgconfig",
|
96 |
+
"include directory": r"/c/opt/64/include",
|
97 |
+
"lib directory": r"/c/opt/64/lib",
|
98 |
+
"openblas configuration": "USE_64BITINT= DYNAMIC_ARCH=1 DYNAMIC_OLDER= NO_CBLAS= NO_LAPACK= NO_LAPACKE= NO_AFFINITY=1 USE_OPENMP= SKYLAKEX MAX_THREADS=2",
|
99 |
+
"pc file directory": r"c:/opt/64/lib/pkgconfig",
|
100 |
+
},
|
101 |
+
"pybind11": {
|
102 |
+
"name": "pybind11",
|
103 |
+
"version": "2.11.1",
|
104 |
+
"detection method": "config-tool",
|
105 |
+
"include directory": r"unknown",
|
106 |
+
},
|
107 |
+
},
|
108 |
+
"Python Information": {
|
109 |
+
"path": r"C:\Users\runneradmin\AppData\Local\Temp\cibw-run-ee1litsm\cp39-win_amd64\build\venv\Scripts\python.exe",
|
110 |
+
"version": "3.9",
|
111 |
+
},
|
112 |
+
}
|
113 |
+
)
|
114 |
+
|
115 |
+
|
116 |
+
def _check_pyyaml():
|
117 |
+
import yaml
|
118 |
+
|
119 |
+
return yaml
|
120 |
+
|
121 |
+
|
122 |
+
def show(mode=DisplayModes.stdout.value):
|
123 |
+
"""
|
124 |
+
Show libraries and system information on which SciPy was built
|
125 |
+
and is being used
|
126 |
+
|
127 |
+
Parameters
|
128 |
+
----------
|
129 |
+
mode : {`'stdout'`, `'dicts'`}, optional.
|
130 |
+
Indicates how to display the config information.
|
131 |
+
`'stdout'` prints to console, `'dicts'` returns a dictionary
|
132 |
+
of the configuration.
|
133 |
+
|
134 |
+
Returns
|
135 |
+
-------
|
136 |
+
out : {`dict`, `None`}
|
137 |
+
If mode is `'dicts'`, a dict is returned, else None
|
138 |
+
|
139 |
+
Notes
|
140 |
+
-----
|
141 |
+
1. The `'stdout'` mode will give more readable
|
142 |
+
output if ``pyyaml`` is installed
|
143 |
+
|
144 |
+
"""
|
145 |
+
if mode == DisplayModes.stdout.value:
|
146 |
+
try: # Non-standard library, check import
|
147 |
+
yaml = _check_pyyaml()
|
148 |
+
|
149 |
+
print(yaml.dump(CONFIG))
|
150 |
+
except ModuleNotFoundError:
|
151 |
+
import warnings
|
152 |
+
import json
|
153 |
+
|
154 |
+
warnings.warn("Install `pyyaml` for better output", stacklevel=1)
|
155 |
+
print(json.dumps(CONFIG, indent=2))
|
156 |
+
elif mode == DisplayModes.dicts.value:
|
157 |
+
return CONFIG
|
158 |
+
else:
|
159 |
+
raise AttributeError(
|
160 |
+
f"Invalid `mode`, use one of: {', '.join([e.value for e in DisplayModes])}"
|
161 |
+
)
|
scipy/__init__.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
SciPy: A scientific computing package for Python
|
3 |
+
================================================
|
4 |
+
|
5 |
+
Documentation is available in the docstrings and
|
6 |
+
online at https://docs.scipy.org.
|
7 |
+
|
8 |
+
Subpackages
|
9 |
+
-----------
|
10 |
+
Using any of these subpackages requires an explicit import. For example,
|
11 |
+
``import scipy.cluster``.
|
12 |
+
|
13 |
+
::
|
14 |
+
|
15 |
+
cluster --- Vector Quantization / Kmeans
|
16 |
+
constants --- Physical and mathematical constants and units
|
17 |
+
datasets --- Dataset methods
|
18 |
+
fft --- Discrete Fourier transforms
|
19 |
+
fftpack --- Legacy discrete Fourier transforms
|
20 |
+
integrate --- Integration routines
|
21 |
+
interpolate --- Interpolation Tools
|
22 |
+
io --- Data input and output
|
23 |
+
linalg --- Linear algebra routines
|
24 |
+
misc --- Utilities that don't have another home.
|
25 |
+
ndimage --- N-D image package
|
26 |
+
odr --- Orthogonal Distance Regression
|
27 |
+
optimize --- Optimization Tools
|
28 |
+
signal --- Signal Processing Tools
|
29 |
+
sparse --- Sparse Matrices
|
30 |
+
spatial --- Spatial data structures and algorithms
|
31 |
+
special --- Special functions
|
32 |
+
stats --- Statistical Functions
|
33 |
+
|
34 |
+
Public API in the main SciPy namespace
|
35 |
+
--------------------------------------
|
36 |
+
::
|
37 |
+
|
38 |
+
__version__ --- SciPy version string
|
39 |
+
LowLevelCallable --- Low-level callback function
|
40 |
+
show_config --- Show scipy build configuration
|
41 |
+
test --- Run scipy unittests
|
42 |
+
|
43 |
+
"""
|
44 |
+
|
45 |
+
|
46 |
+
# start delvewheel patch
|
47 |
+
def _delvewheel_patch_1_5_2():
|
48 |
+
import ctypes
|
49 |
+
import os
|
50 |
+
import platform
|
51 |
+
import sys
|
52 |
+
libs_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'scipy.libs'))
|
53 |
+
is_conda_cpython = platform.python_implementation() == 'CPython' and (hasattr(ctypes.pythonapi, 'Anaconda_GetVersion') or 'packaged by conda-forge' in sys.version)
|
54 |
+
if sys.version_info[:2] >= (3, 8) and not is_conda_cpython or sys.version_info[:2] >= (3, 10):
|
55 |
+
if os.path.isdir(libs_dir):
|
56 |
+
os.add_dll_directory(libs_dir)
|
57 |
+
else:
|
58 |
+
load_order_filepath = os.path.join(libs_dir, '.load-order-scipy-1.12.0')
|
59 |
+
if os.path.isfile(load_order_filepath):
|
60 |
+
with open(os.path.join(libs_dir, '.load-order-scipy-1.12.0')) as file:
|
61 |
+
load_order = file.read().split()
|
62 |
+
for lib in load_order:
|
63 |
+
lib_path = os.path.join(os.path.join(libs_dir, lib))
|
64 |
+
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
65 |
+
if os.path.isfile(lib_path) and not kernel32.LoadLibraryExW(ctypes.c_wchar_p(lib_path), None, 0x00000008):
|
66 |
+
raise OSError('Error loading {}; {}'.format(lib, ctypes.FormatError(ctypes.get_last_error())))
|
67 |
+
|
68 |
+
|
69 |
+
_delvewheel_patch_1_5_2()
|
70 |
+
del _delvewheel_patch_1_5_2
|
71 |
+
# end delvewheel patch
|
72 |
+
|
73 |
+
import importlib as _importlib
|
74 |
+
|
75 |
+
from numpy import __version__ as __numpy_version__
|
76 |
+
|
77 |
+
|
78 |
+
try:
|
79 |
+
from scipy.__config__ import show as show_config
|
80 |
+
except ImportError as e:
|
81 |
+
msg = """Error importing SciPy: you cannot import SciPy while
|
82 |
+
being in scipy source directory; please exit the SciPy source
|
83 |
+
tree first and relaunch your Python interpreter."""
|
84 |
+
raise ImportError(msg) from e
|
85 |
+
|
86 |
+
|
87 |
+
from scipy.version import version as __version__
|
88 |
+
|
89 |
+
|
90 |
+
# Allow distributors to run custom init code
|
91 |
+
from . import _distributor_init
|
92 |
+
del _distributor_init
|
93 |
+
|
94 |
+
|
95 |
+
from scipy._lib import _pep440
|
96 |
+
# In maintenance branch, change to np_maxversion N+3 if numpy is at N
|
97 |
+
np_minversion = '1.22.4'
|
98 |
+
np_maxversion = '1.29.0'
|
99 |
+
if (_pep440.parse(__numpy_version__) < _pep440.Version(np_minversion) or
|
100 |
+
_pep440.parse(__numpy_version__) >= _pep440.Version(np_maxversion)):
|
101 |
+
import warnings
|
102 |
+
warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
|
103 |
+
f" is required for this version of SciPy (detected "
|
104 |
+
f"version {__numpy_version__})",
|
105 |
+
UserWarning, stacklevel=2)
|
106 |
+
del _pep440
|
107 |
+
|
108 |
+
|
109 |
+
# This is the first import of an extension module within SciPy. If there's
|
110 |
+
# a general issue with the install, such that extension modules are missing
|
111 |
+
# or cannot be imported, this is where we'll get a failure - so give an
|
112 |
+
# informative error message.
|
113 |
+
try:
|
114 |
+
from scipy._lib._ccallback import LowLevelCallable
|
115 |
+
except ImportError as e:
|
116 |
+
msg = "The `scipy` install you are using seems to be broken, " + \
|
117 |
+
"(extension modules cannot be imported), " + \
|
118 |
+
"please try reinstalling."
|
119 |
+
raise ImportError(msg) from e
|
120 |
+
|
121 |
+
|
122 |
+
from scipy._lib._testutils import PytestTester
|
123 |
+
test = PytestTester(__name__)
|
124 |
+
del PytestTester
|
125 |
+
|
126 |
+
|
127 |
+
submodules = [
|
128 |
+
'cluster',
|
129 |
+
'constants',
|
130 |
+
'datasets',
|
131 |
+
'fft',
|
132 |
+
'fftpack',
|
133 |
+
'integrate',
|
134 |
+
'interpolate',
|
135 |
+
'io',
|
136 |
+
'linalg',
|
137 |
+
'misc',
|
138 |
+
'ndimage',
|
139 |
+
'odr',
|
140 |
+
'optimize',
|
141 |
+
'signal',
|
142 |
+
'sparse',
|
143 |
+
'spatial',
|
144 |
+
'special',
|
145 |
+
'stats'
|
146 |
+
]
|
147 |
+
|
148 |
+
__all__ = submodules + [
|
149 |
+
'LowLevelCallable',
|
150 |
+
'test',
|
151 |
+
'show_config',
|
152 |
+
'__version__',
|
153 |
+
]
|
154 |
+
|
155 |
+
|
156 |
+
def __dir__():
|
157 |
+
return __all__
|
158 |
+
|
159 |
+
|
160 |
+
def __getattr__(name):
|
161 |
+
if name in submodules:
|
162 |
+
return _importlib.import_module(f'scipy.{name}')
|
163 |
+
else:
|
164 |
+
try:
|
165 |
+
return globals()[name]
|
166 |
+
except KeyError:
|
167 |
+
raise AttributeError(
|
168 |
+
f"Module 'scipy' has no attribute '{name}'"
|
169 |
+
)
|
scipy/__pycache__/__config__.cpython-39.pyc
ADDED
Binary file (3.63 kB). View file
|
|
scipy/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (4.75 kB). View file
|
|
scipy/__pycache__/_distributor_init.cpython-39.pyc
ADDED
Binary file (930 Bytes). View file
|
|
scipy/__pycache__/conftest.cpython-39.pyc
ADDED
Binary file (5.76 kB). View file
|
|
scipy/__pycache__/version.cpython-39.pyc
ADDED
Binary file (299 Bytes). View file
|
|
scipy/_distributor_init.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
'''
|
3 |
+
Helper to preload windows dlls to prevent dll not found errors.
|
4 |
+
Once a DLL is preloaded, its namespace is made available to any
|
5 |
+
subsequent DLL. This file originated in the numpy-wheels repo,
|
6 |
+
and is created as part of the scripts that build the wheel.
|
7 |
+
'''
|
8 |
+
import os
|
9 |
+
import glob
|
10 |
+
if os.name == 'nt':
|
11 |
+
# convention for storing / loading the DLL from
|
12 |
+
# numpy/.libs/, if present
|
13 |
+
try:
|
14 |
+
from ctypes import WinDLL
|
15 |
+
basedir = os.path.dirname(__file__)
|
16 |
+
except:
|
17 |
+
pass
|
18 |
+
else:
|
19 |
+
libs_dir = os.path.abspath(os.path.join(basedir, '.libs'))
|
20 |
+
DLL_filenames = []
|
21 |
+
if os.path.isdir(libs_dir):
|
22 |
+
for filename in glob.glob(os.path.join(libs_dir,
|
23 |
+
'*openblas*dll')):
|
24 |
+
# NOTE: would it change behavior to load ALL
|
25 |
+
# DLLs at this path vs. the name restriction?
|
26 |
+
WinDLL(os.path.abspath(filename))
|
27 |
+
DLL_filenames.append(filename)
|
28 |
+
if len(DLL_filenames) > 1:
|
29 |
+
import warnings
|
30 |
+
warnings.warn("loaded more than 1 DLL from .libs:"
|
31 |
+
"\n%s" % "\n".join(DLL_filenames),
|
32 |
+
stacklevel=1)
|
scipy/_lib/__init__.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Module containing private utility functions
|
3 |
+
===========================================
|
4 |
+
|
5 |
+
The ``scipy._lib`` namespace is empty (for now). Tests for all
|
6 |
+
utilities in submodules of ``_lib`` can be run with::
|
7 |
+
|
8 |
+
from scipy import _lib
|
9 |
+
_lib.test()
|
10 |
+
|
11 |
+
"""
|
12 |
+
from scipy._lib._testutils import PytestTester
|
13 |
+
test = PytestTester(__name__)
|
14 |
+
del PytestTester
|
scipy/_lib/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (520 Bytes). View file
|
|
scipy/_lib/__pycache__/_array_api.cpython-39.pyc
ADDED
Binary file (9.52 kB). View file
|
|
scipy/_lib/__pycache__/_bunch.cpython-39.pyc
ADDED
Binary file (6.88 kB). View file
|
|
scipy/_lib/__pycache__/_ccallback.cpython-39.pyc
ADDED
Binary file (7.03 kB). View file
|
|
scipy/_lib/__pycache__/_disjoint_set.cpython-39.pyc
ADDED
Binary file (6.36 kB). View file
|
|
scipy/_lib/__pycache__/_docscrape.cpython-39.pyc
ADDED
Binary file (18.9 kB). View file
|
|
scipy/_lib/__pycache__/_finite_differences.cpython-39.pyc
ADDED
Binary file (4.04 kB). View file
|
|
scipy/_lib/__pycache__/_gcutils.cpython-39.pyc
ADDED
Binary file (3.02 kB). View file
|
|
scipy/_lib/__pycache__/_pep440.cpython-39.pyc
ADDED
Binary file (13.1 kB). View file
|
|
scipy/_lib/__pycache__/_testutils.cpython-39.pyc
ADDED
Binary file (7.94 kB). View file
|
|
scipy/_lib/__pycache__/_threadsafety.cpython-39.pyc
ADDED
Binary file (2.27 kB). View file
|
|
scipy/_lib/__pycache__/_tmpdirs.cpython-39.pyc
ADDED
Binary file (2.72 kB). View file
|
|
scipy/_lib/__pycache__/_util.cpython-39.pyc
ADDED
Binary file (26.4 kB). View file
|
|
scipy/_lib/__pycache__/decorator.cpython-39.pyc
ADDED
Binary file (11.4 kB). View file
|
|
scipy/_lib/__pycache__/deprecation.cpython-39.pyc
ADDED
Binary file (7.37 kB). View file
|
|
scipy/_lib/__pycache__/doccer.cpython-39.pyc
ADDED
Binary file (7.78 kB). View file
|
|
scipy/_lib/__pycache__/uarray.cpython-39.pyc
ADDED
Binary file (772 Bytes). View file
|
|
scipy/_lib/_array_api.py
ADDED
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Utility functions to use Python Array API compatible libraries.
|
2 |
+
|
3 |
+
For the context about the Array API see:
|
4 |
+
https://data-apis.org/array-api/latest/purpose_and_scope.html
|
5 |
+
|
6 |
+
The SciPy use case of the Array API is described on the following page:
|
7 |
+
https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy
|
8 |
+
"""
|
9 |
+
from __future__ import annotations
|
10 |
+
|
11 |
+
import os
|
12 |
+
import warnings
|
13 |
+
|
14 |
+
import numpy as np
|
15 |
+
from numpy.testing import assert_
|
16 |
+
import scipy._lib.array_api_compat.array_api_compat as array_api_compat
|
17 |
+
from scipy._lib.array_api_compat.array_api_compat import size
|
18 |
+
import scipy._lib.array_api_compat.array_api_compat.numpy as array_api_compat_numpy
|
19 |
+
|
20 |
+
__all__ = ['array_namespace', 'as_xparray', 'size']
|
21 |
+
|
22 |
+
|
23 |
+
# To enable array API and strict array-like input validation
|
24 |
+
SCIPY_ARRAY_API: str | bool = os.environ.get("SCIPY_ARRAY_API", False)
|
25 |
+
# To control the default device - for use in the test suite only
|
26 |
+
SCIPY_DEVICE = os.environ.get("SCIPY_DEVICE", "cpu")
|
27 |
+
|
28 |
+
_GLOBAL_CONFIG = {
|
29 |
+
"SCIPY_ARRAY_API": SCIPY_ARRAY_API,
|
30 |
+
"SCIPY_DEVICE": SCIPY_DEVICE,
|
31 |
+
}
|
32 |
+
|
33 |
+
|
34 |
+
def compliance_scipy(arrays):
|
35 |
+
"""Raise exceptions on known-bad subclasses.
|
36 |
+
|
37 |
+
The following subclasses are not supported and raise and error:
|
38 |
+
- `numpy.ma.MaskedArray`
|
39 |
+
- `numpy.matrix`
|
40 |
+
- NumPy arrays which do not have a boolean or numerical dtype
|
41 |
+
- Any array-like which is neither array API compatible nor coercible by NumPy
|
42 |
+
- Any array-like which is coerced by NumPy to an unsupported dtype
|
43 |
+
"""
|
44 |
+
for i in range(len(arrays)):
|
45 |
+
array = arrays[i]
|
46 |
+
if isinstance(array, np.ma.MaskedArray):
|
47 |
+
raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.")
|
48 |
+
elif isinstance(array, np.matrix):
|
49 |
+
raise TypeError("Inputs of type `numpy.matrix` are not supported.")
|
50 |
+
if isinstance(array, (np.ndarray, np.generic)):
|
51 |
+
dtype = array.dtype
|
52 |
+
if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)):
|
53 |
+
raise TypeError(f"An argument has dtype `{dtype!r}`; "
|
54 |
+
f"only boolean and numerical dtypes are supported.")
|
55 |
+
elif not array_api_compat.is_array_api_obj(array):
|
56 |
+
try:
|
57 |
+
array = np.asanyarray(array)
|
58 |
+
except TypeError:
|
59 |
+
raise TypeError("An argument is neither array API compatible nor "
|
60 |
+
"coercible by NumPy.")
|
61 |
+
dtype = array.dtype
|
62 |
+
if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)):
|
63 |
+
message = (
|
64 |
+
f"An argument was coerced to an unsupported dtype `{dtype!r}`; "
|
65 |
+
f"only boolean and numerical dtypes are supported."
|
66 |
+
)
|
67 |
+
raise TypeError(message)
|
68 |
+
arrays[i] = array
|
69 |
+
return arrays
|
70 |
+
|
71 |
+
|
72 |
+
def _check_finite(array, xp):
|
73 |
+
"""Check for NaNs or Infs."""
|
74 |
+
msg = "array must not contain infs or NaNs"
|
75 |
+
try:
|
76 |
+
if not xp.all(xp.isfinite(array)):
|
77 |
+
raise ValueError(msg)
|
78 |
+
except TypeError:
|
79 |
+
raise ValueError(msg)
|
80 |
+
|
81 |
+
|
82 |
+
def array_namespace(*arrays):
|
83 |
+
"""Get the array API compatible namespace for the arrays xs.
|
84 |
+
|
85 |
+
Parameters
|
86 |
+
----------
|
87 |
+
*arrays : sequence of array_like
|
88 |
+
Arrays used to infer the common namespace.
|
89 |
+
|
90 |
+
Returns
|
91 |
+
-------
|
92 |
+
namespace : module
|
93 |
+
Common namespace.
|
94 |
+
|
95 |
+
Notes
|
96 |
+
-----
|
97 |
+
Thin wrapper around `array_api_compat.array_namespace`.
|
98 |
+
|
99 |
+
1. Check for the global switch: SCIPY_ARRAY_API. This can also be accessed
|
100 |
+
dynamically through ``_GLOBAL_CONFIG['SCIPY_ARRAY_API']``.
|
101 |
+
2. `compliance_scipy` raise exceptions on known-bad subclasses. See
|
102 |
+
it's definition for more details.
|
103 |
+
|
104 |
+
When the global switch is False, it defaults to the `numpy` namespace.
|
105 |
+
In that case, there is no compliance check. This is a convenience to
|
106 |
+
ease the adoption. Otherwise, arrays must comply with the new rules.
|
107 |
+
"""
|
108 |
+
if not _GLOBAL_CONFIG["SCIPY_ARRAY_API"]:
|
109 |
+
# here we could wrap the namespace if needed
|
110 |
+
return array_api_compat_numpy
|
111 |
+
|
112 |
+
arrays = [array for array in arrays if array is not None]
|
113 |
+
|
114 |
+
arrays = compliance_scipy(arrays)
|
115 |
+
|
116 |
+
return array_api_compat.array_namespace(*arrays)
|
117 |
+
|
118 |
+
|
119 |
+
def as_xparray(
|
120 |
+
array, dtype=None, order=None, copy=None, *, xp=None, check_finite=False
|
121 |
+
):
|
122 |
+
"""SciPy-specific replacement for `np.asarray` with `order` and `check_finite`.
|
123 |
+
|
124 |
+
Memory layout parameter `order` is not exposed in the Array API standard.
|
125 |
+
`order` is only enforced if the input array implementation
|
126 |
+
is NumPy based, otherwise `order` is just silently ignored.
|
127 |
+
|
128 |
+
`check_finite` is also not a keyword in the array API standard; included
|
129 |
+
here for convenience rather than that having to be a separate function
|
130 |
+
call inside SciPy functions.
|
131 |
+
"""
|
132 |
+
if xp is None:
|
133 |
+
xp = array_namespace(array)
|
134 |
+
if xp.__name__ in {"numpy", "scipy._lib.array_api_compat.array_api_compat.numpy"}:
|
135 |
+
# Use NumPy API to support order
|
136 |
+
if copy is True:
|
137 |
+
array = np.array(array, order=order, dtype=dtype)
|
138 |
+
else:
|
139 |
+
array = np.asarray(array, order=order, dtype=dtype)
|
140 |
+
|
141 |
+
# At this point array is a NumPy ndarray. We convert it to an array
|
142 |
+
# container that is consistent with the input's namespace.
|
143 |
+
array = xp.asarray(array)
|
144 |
+
else:
|
145 |
+
try:
|
146 |
+
array = xp.asarray(array, dtype=dtype, copy=copy)
|
147 |
+
except TypeError:
|
148 |
+
coerced_xp = array_namespace(xp.asarray(3))
|
149 |
+
array = coerced_xp.asarray(array, dtype=dtype, copy=copy)
|
150 |
+
|
151 |
+
if check_finite:
|
152 |
+
_check_finite(array, xp)
|
153 |
+
|
154 |
+
return array
|
155 |
+
|
156 |
+
|
157 |
+
def atleast_nd(x, *, ndim, xp=None):
|
158 |
+
"""Recursively expand the dimension to have at least `ndim`."""
|
159 |
+
if xp is None:
|
160 |
+
xp = array_namespace(x)
|
161 |
+
x = xp.asarray(x)
|
162 |
+
if x.ndim < ndim:
|
163 |
+
x = xp.expand_dims(x, axis=0)
|
164 |
+
x = atleast_nd(x, ndim=ndim, xp=xp)
|
165 |
+
return x
|
166 |
+
|
167 |
+
|
168 |
+
def copy(x, *, xp=None):
|
169 |
+
"""
|
170 |
+
Copies an array.
|
171 |
+
|
172 |
+
Parameters
|
173 |
+
----------
|
174 |
+
x : array
|
175 |
+
|
176 |
+
xp : array_namespace
|
177 |
+
|
178 |
+
Returns
|
179 |
+
-------
|
180 |
+
copy : array
|
181 |
+
Copied array
|
182 |
+
|
183 |
+
Notes
|
184 |
+
-----
|
185 |
+
This copy function does not offer all the semantics of `np.copy`, i.e. the
|
186 |
+
`subok` and `order` keywords are not used.
|
187 |
+
"""
|
188 |
+
# Note: xp.asarray fails if xp is numpy.
|
189 |
+
if xp is None:
|
190 |
+
xp = array_namespace(x)
|
191 |
+
|
192 |
+
return as_xparray(x, copy=True, xp=xp)
|
193 |
+
|
194 |
+
|
195 |
+
def is_numpy(xp):
|
196 |
+
return xp.__name__ == 'scipy._lib.array_api_compat.array_api_compat.numpy'
|
197 |
+
|
198 |
+
|
199 |
+
def is_cupy(xp):
|
200 |
+
return xp.__name__ == 'scipy._lib.array_api_compat.array_api_compat.cupy'
|
201 |
+
|
202 |
+
|
203 |
+
def is_torch(xp):
|
204 |
+
return xp.__name__ == 'scipy._lib.array_api_compat.array_api_compat.torch'
|
205 |
+
|
206 |
+
|
207 |
+
def _strict_check(actual, desired, xp,
|
208 |
+
check_namespace=True, check_dtype=True, check_shape=True):
|
209 |
+
if check_namespace:
|
210 |
+
_assert_matching_namespace(actual, desired)
|
211 |
+
|
212 |
+
desired = xp.asarray(desired)
|
213 |
+
|
214 |
+
if check_dtype:
|
215 |
+
assert_(actual.dtype == desired.dtype,
|
216 |
+
"dtypes do not match.\n"
|
217 |
+
f"Actual: {actual.dtype}\n"
|
218 |
+
f"Desired: {desired.dtype}")
|
219 |
+
|
220 |
+
if check_shape:
|
221 |
+
assert_(actual.shape == desired.shape,
|
222 |
+
"Shapes do not match.\n"
|
223 |
+
f"Actual: {actual.shape}\n"
|
224 |
+
f"Desired: {desired.shape}")
|
225 |
+
_check_scalar(actual, desired, xp)
|
226 |
+
|
227 |
+
desired = xp.broadcast_to(desired, actual.shape)
|
228 |
+
return desired
|
229 |
+
|
230 |
+
|
231 |
+
def _assert_matching_namespace(actual, desired):
|
232 |
+
actual = actual if isinstance(actual, tuple) else (actual,)
|
233 |
+
desired_space = array_namespace(desired)
|
234 |
+
for arr in actual:
|
235 |
+
arr_space = array_namespace(arr)
|
236 |
+
assert_(arr_space == desired_space,
|
237 |
+
"Namespaces do not match.\n"
|
238 |
+
f"Actual: {arr_space.__name__}\n"
|
239 |
+
f"Desired: {desired_space.__name__}")
|
240 |
+
|
241 |
+
|
242 |
+
def _check_scalar(actual, desired, xp):
|
243 |
+
# Shape check alone is sufficient unless desired.shape == (). Also,
|
244 |
+
# only NumPy distinguishes between scalars and arrays.
|
245 |
+
if desired.shape != () or not is_numpy(xp):
|
246 |
+
return
|
247 |
+
# We want to follow the conventions of the `xp` library. Libraries like
|
248 |
+
# NumPy, for which `np.asarray(0)[()]` returns a scalar, tend to return
|
249 |
+
# a scalar even when a 0D array might be more appropriate:
|
250 |
+
# import numpy as np
|
251 |
+
# np.mean([1, 2, 3]) # scalar, not 0d array
|
252 |
+
# np.asarray(0)*2 # scalar, not 0d array
|
253 |
+
# np.sin(np.asarray(0)) # scalar, not 0d array
|
254 |
+
# Libraries like CuPy, for which `cp.asarray(0)[()]` returns a 0D array,
|
255 |
+
# tend to return a 0D array in scenarios like those above.
|
256 |
+
# Therefore, regardless of whether the developer provides a scalar or 0D
|
257 |
+
# array for `desired`, we would typically want the type of `actual` to be
|
258 |
+
# the type of `desired[()]`. If the developer wants to override this
|
259 |
+
# behavior, they can set `check_shape=False`.
|
260 |
+
desired = desired[()]
|
261 |
+
assert_((xp.isscalar(actual) and xp.isscalar(desired)
|
262 |
+
or (not xp.isscalar(actual) and not xp.isscalar(desired))),
|
263 |
+
"Types do not match:\n"
|
264 |
+
f"Actual: {type(actual)}\n"
|
265 |
+
f"Desired: {type(desired)}")
|
266 |
+
|
267 |
+
|
268 |
+
def xp_assert_equal(actual, desired, check_namespace=True, check_dtype=True,
|
269 |
+
check_shape=True, err_msg='', xp=None):
|
270 |
+
if xp is None:
|
271 |
+
xp = array_namespace(actual)
|
272 |
+
desired = _strict_check(actual, desired, xp, check_namespace=check_namespace,
|
273 |
+
check_dtype=check_dtype, check_shape=check_shape)
|
274 |
+
if is_cupy(xp):
|
275 |
+
return xp.testing.assert_array_equal(actual, desired, err_msg=err_msg)
|
276 |
+
elif is_torch(xp):
|
277 |
+
# PyTorch recommends using `rtol=0, atol=0` like this
|
278 |
+
# to test for exact equality
|
279 |
+
err_msg = None if err_msg == '' else err_msg
|
280 |
+
return xp.testing.assert_close(actual, desired, rtol=0, atol=0, equal_nan=True,
|
281 |
+
check_dtype=False, msg=err_msg)
|
282 |
+
return np.testing.assert_array_equal(actual, desired, err_msg=err_msg)
|
283 |
+
|
284 |
+
|
285 |
+
def xp_assert_close(actual, desired, rtol=1e-07, atol=0, check_namespace=True,
|
286 |
+
check_dtype=True, check_shape=True, err_msg='', xp=None):
|
287 |
+
if xp is None:
|
288 |
+
xp = array_namespace(actual)
|
289 |
+
desired = _strict_check(actual, desired, xp, check_namespace=check_namespace,
|
290 |
+
check_dtype=check_dtype, check_shape=check_shape)
|
291 |
+
if is_cupy(xp):
|
292 |
+
return xp.testing.assert_allclose(actual, desired, rtol=rtol,
|
293 |
+
atol=atol, err_msg=err_msg)
|
294 |
+
elif is_torch(xp):
|
295 |
+
err_msg = None if err_msg == '' else err_msg
|
296 |
+
return xp.testing.assert_close(actual, desired, rtol=rtol, atol=atol,
|
297 |
+
equal_nan=True, check_dtype=False, msg=err_msg)
|
298 |
+
return np.testing.assert_allclose(actual, desired, rtol=rtol,
|
299 |
+
atol=atol, err_msg=err_msg)
|
300 |
+
|
301 |
+
|
302 |
+
def xp_assert_less(actual, desired, check_namespace=True, check_dtype=True,
|
303 |
+
check_shape=True, err_msg='', verbose=True, xp=None):
|
304 |
+
if xp is None:
|
305 |
+
xp = array_namespace(actual)
|
306 |
+
desired = _strict_check(actual, desired, xp, check_namespace=check_namespace,
|
307 |
+
check_dtype=check_dtype, check_shape=check_shape)
|
308 |
+
if is_cupy(xp):
|
309 |
+
return xp.testing.assert_array_less(actual, desired,
|
310 |
+
err_msg=err_msg, verbose=verbose)
|
311 |
+
elif is_torch(xp):
|
312 |
+
if actual.device.type != 'cpu':
|
313 |
+
actual = actual.cpu()
|
314 |
+
if desired.device.type != 'cpu':
|
315 |
+
desired = desired.cpu()
|
316 |
+
return np.testing.assert_array_less(actual, desired,
|
317 |
+
err_msg=err_msg, verbose=verbose)
|
318 |
+
|
319 |
+
|
320 |
+
def cov(x, *, xp=None):
|
321 |
+
if xp is None:
|
322 |
+
xp = array_namespace(x)
|
323 |
+
|
324 |
+
X = copy(x, xp=xp)
|
325 |
+
dtype = xp.result_type(X, xp.float64)
|
326 |
+
|
327 |
+
X = atleast_nd(X, ndim=2, xp=xp)
|
328 |
+
X = xp.asarray(X, dtype=dtype)
|
329 |
+
|
330 |
+
avg = xp.mean(X, axis=1)
|
331 |
+
fact = X.shape[1] - 1
|
332 |
+
|
333 |
+
if fact <= 0:
|
334 |
+
warnings.warn("Degrees of freedom <= 0 for slice",
|
335 |
+
RuntimeWarning, stacklevel=2)
|
336 |
+
fact = 0.0
|
337 |
+
|
338 |
+
X -= avg[:, None]
|
339 |
+
X_T = X.T
|
340 |
+
if xp.isdtype(X_T.dtype, 'complex floating'):
|
341 |
+
X_T = xp.conj(X_T)
|
342 |
+
c = X @ X_T
|
343 |
+
c /= fact
|
344 |
+
axes = tuple(axis for axis, length in enumerate(c.shape) if length == 1)
|
345 |
+
return xp.squeeze(c, axis=axes)
|
346 |
+
|
347 |
+
|
348 |
+
def xp_unsupported_param_msg(param):
|
349 |
+
return f'Providing {param!r} is only supported for numpy arrays.'
|
350 |
+
|
351 |
+
|
352 |
+
def is_complex(x, xp):
|
353 |
+
return xp.isdtype(x.dtype, 'complex floating')
|
scipy/_lib/_bunch.py
ADDED
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys as _sys
|
2 |
+
from keyword import iskeyword as _iskeyword
|
3 |
+
|
4 |
+
|
5 |
+
def _validate_names(typename, field_names, extra_field_names):
|
6 |
+
"""
|
7 |
+
Ensure that all the given names are valid Python identifiers that
|
8 |
+
do not start with '_'. Also check that there are no duplicates
|
9 |
+
among field_names + extra_field_names.
|
10 |
+
"""
|
11 |
+
for name in [typename] + field_names + extra_field_names:
|
12 |
+
if not isinstance(name, str):
|
13 |
+
raise TypeError('typename and all field names must be strings')
|
14 |
+
if not name.isidentifier():
|
15 |
+
raise ValueError('typename and all field names must be valid '
|
16 |
+
f'identifiers: {name!r}')
|
17 |
+
if _iskeyword(name):
|
18 |
+
raise ValueError('typename and all field names cannot be a '
|
19 |
+
f'keyword: {name!r}')
|
20 |
+
|
21 |
+
seen = set()
|
22 |
+
for name in field_names + extra_field_names:
|
23 |
+
if name.startswith('_'):
|
24 |
+
raise ValueError('Field names cannot start with an underscore: '
|
25 |
+
f'{name!r}')
|
26 |
+
if name in seen:
|
27 |
+
raise ValueError(f'Duplicate field name: {name!r}')
|
28 |
+
seen.add(name)
|
29 |
+
|
30 |
+
|
31 |
+
# Note: This code is adapted from CPython:Lib/collections/__init__.py
|
32 |
+
def _make_tuple_bunch(typename, field_names, extra_field_names=None,
|
33 |
+
module=None):
|
34 |
+
"""
|
35 |
+
Create a namedtuple-like class with additional attributes.
|
36 |
+
|
37 |
+
This function creates a subclass of tuple that acts like a namedtuple
|
38 |
+
and that has additional attributes.
|
39 |
+
|
40 |
+
The additional attributes are listed in `extra_field_names`. The
|
41 |
+
values assigned to these attributes are not part of the tuple.
|
42 |
+
|
43 |
+
The reason this function exists is to allow functions in SciPy
|
44 |
+
that currently return a tuple or a namedtuple to returned objects
|
45 |
+
that have additional attributes, while maintaining backwards
|
46 |
+
compatibility.
|
47 |
+
|
48 |
+
This should only be used to enhance *existing* functions in SciPy.
|
49 |
+
New functions are free to create objects as return values without
|
50 |
+
having to maintain backwards compatibility with an old tuple or
|
51 |
+
namedtuple return value.
|
52 |
+
|
53 |
+
Parameters
|
54 |
+
----------
|
55 |
+
typename : str
|
56 |
+
The name of the type.
|
57 |
+
field_names : list of str
|
58 |
+
List of names of the values to be stored in the tuple. These names
|
59 |
+
will also be attributes of instances, so the values in the tuple
|
60 |
+
can be accessed by indexing or as attributes. At least one name
|
61 |
+
is required. See the Notes for additional restrictions.
|
62 |
+
extra_field_names : list of str, optional
|
63 |
+
List of names of values that will be stored as attributes of the
|
64 |
+
object. See the notes for additional restrictions.
|
65 |
+
|
66 |
+
Returns
|
67 |
+
-------
|
68 |
+
cls : type
|
69 |
+
The new class.
|
70 |
+
|
71 |
+
Notes
|
72 |
+
-----
|
73 |
+
There are restrictions on the names that may be used in `field_names`
|
74 |
+
and `extra_field_names`:
|
75 |
+
|
76 |
+
* The names must be unique--no duplicates allowed.
|
77 |
+
* The names must be valid Python identifiers, and must not begin with
|
78 |
+
an underscore.
|
79 |
+
* The names must not be Python keywords (e.g. 'def', 'and', etc., are
|
80 |
+
not allowed).
|
81 |
+
|
82 |
+
Examples
|
83 |
+
--------
|
84 |
+
>>> from scipy._lib._bunch import _make_tuple_bunch
|
85 |
+
|
86 |
+
Create a class that acts like a namedtuple with length 2 (with field
|
87 |
+
names `x` and `y`) that will also have the attributes `w` and `beta`:
|
88 |
+
|
89 |
+
>>> Result = _make_tuple_bunch('Result', ['x', 'y'], ['w', 'beta'])
|
90 |
+
|
91 |
+
`Result` is the new class. We call it with keyword arguments to create
|
92 |
+
a new instance with given values.
|
93 |
+
|
94 |
+
>>> result1 = Result(x=1, y=2, w=99, beta=0.5)
|
95 |
+
>>> result1
|
96 |
+
Result(x=1, y=2, w=99, beta=0.5)
|
97 |
+
|
98 |
+
`result1` acts like a tuple of length 2:
|
99 |
+
|
100 |
+
>>> len(result1)
|
101 |
+
2
|
102 |
+
>>> result1[:]
|
103 |
+
(1, 2)
|
104 |
+
|
105 |
+
The values assigned when the instance was created are available as
|
106 |
+
attributes:
|
107 |
+
|
108 |
+
>>> result1.y
|
109 |
+
2
|
110 |
+
>>> result1.beta
|
111 |
+
0.5
|
112 |
+
"""
|
113 |
+
if len(field_names) == 0:
|
114 |
+
raise ValueError('field_names must contain at least one name')
|
115 |
+
|
116 |
+
if extra_field_names is None:
|
117 |
+
extra_field_names = []
|
118 |
+
_validate_names(typename, field_names, extra_field_names)
|
119 |
+
|
120 |
+
typename = _sys.intern(str(typename))
|
121 |
+
field_names = tuple(map(_sys.intern, field_names))
|
122 |
+
extra_field_names = tuple(map(_sys.intern, extra_field_names))
|
123 |
+
|
124 |
+
all_names = field_names + extra_field_names
|
125 |
+
arg_list = ', '.join(field_names)
|
126 |
+
full_list = ', '.join(all_names)
|
127 |
+
repr_fmt = ''.join(('(',
|
128 |
+
', '.join(f'{name}=%({name})r' for name in all_names),
|
129 |
+
')'))
|
130 |
+
tuple_new = tuple.__new__
|
131 |
+
_dict, _tuple, _zip = dict, tuple, zip
|
132 |
+
|
133 |
+
# Create all the named tuple methods to be added to the class namespace
|
134 |
+
|
135 |
+
s = f"""\
|
136 |
+
def __new__(_cls, {arg_list}, **extra_fields):
|
137 |
+
return _tuple_new(_cls, ({arg_list},))
|
138 |
+
|
139 |
+
def __init__(self, {arg_list}, **extra_fields):
|
140 |
+
for key in self._extra_fields:
|
141 |
+
if key not in extra_fields:
|
142 |
+
raise TypeError("missing keyword argument '%s'" % (key,))
|
143 |
+
for key, val in extra_fields.items():
|
144 |
+
if key not in self._extra_fields:
|
145 |
+
raise TypeError("unexpected keyword argument '%s'" % (key,))
|
146 |
+
self.__dict__[key] = val
|
147 |
+
|
148 |
+
def __setattr__(self, key, val):
|
149 |
+
if key in {repr(field_names)}:
|
150 |
+
raise AttributeError("can't set attribute %r of class %r"
|
151 |
+
% (key, self.__class__.__name__))
|
152 |
+
else:
|
153 |
+
self.__dict__[key] = val
|
154 |
+
"""
|
155 |
+
del arg_list
|
156 |
+
namespace = {'_tuple_new': tuple_new,
|
157 |
+
'__builtins__': dict(TypeError=TypeError,
|
158 |
+
AttributeError=AttributeError),
|
159 |
+
'__name__': f'namedtuple_{typename}'}
|
160 |
+
exec(s, namespace)
|
161 |
+
__new__ = namespace['__new__']
|
162 |
+
__new__.__doc__ = f'Create new instance of {typename}({full_list})'
|
163 |
+
__init__ = namespace['__init__']
|
164 |
+
__init__.__doc__ = f'Instantiate instance of {typename}({full_list})'
|
165 |
+
__setattr__ = namespace['__setattr__']
|
166 |
+
|
167 |
+
def __repr__(self):
|
168 |
+
'Return a nicely formatted representation string'
|
169 |
+
return self.__class__.__name__ + repr_fmt % self._asdict()
|
170 |
+
|
171 |
+
def _asdict(self):
|
172 |
+
'Return a new dict which maps field names to their values.'
|
173 |
+
out = _dict(_zip(self._fields, self))
|
174 |
+
out.update(self.__dict__)
|
175 |
+
return out
|
176 |
+
|
177 |
+
def __getnewargs_ex__(self):
|
178 |
+
'Return self as a plain tuple. Used by copy and pickle.'
|
179 |
+
return _tuple(self), self.__dict__
|
180 |
+
|
181 |
+
# Modify function metadata to help with introspection and debugging
|
182 |
+
for method in (__new__, __repr__, _asdict, __getnewargs_ex__):
|
183 |
+
method.__qualname__ = f'{typename}.{method.__name__}'
|
184 |
+
|
185 |
+
# Build-up the class namespace dictionary
|
186 |
+
# and use type() to build the result class
|
187 |
+
class_namespace = {
|
188 |
+
'__doc__': f'{typename}({full_list})',
|
189 |
+
'_fields': field_names,
|
190 |
+
'__new__': __new__,
|
191 |
+
'__init__': __init__,
|
192 |
+
'__repr__': __repr__,
|
193 |
+
'__setattr__': __setattr__,
|
194 |
+
'_asdict': _asdict,
|
195 |
+
'_extra_fields': extra_field_names,
|
196 |
+
'__getnewargs_ex__': __getnewargs_ex__,
|
197 |
+
}
|
198 |
+
for index, name in enumerate(field_names):
|
199 |
+
|
200 |
+
def _get(self, index=index):
|
201 |
+
return self[index]
|
202 |
+
class_namespace[name] = property(_get)
|
203 |
+
for name in extra_field_names:
|
204 |
+
|
205 |
+
def _get(self, name=name):
|
206 |
+
return self.__dict__[name]
|
207 |
+
class_namespace[name] = property(_get)
|
208 |
+
|
209 |
+
result = type(typename, (tuple,), class_namespace)
|
210 |
+
|
211 |
+
# For pickling to work, the __module__ variable needs to be set to the
|
212 |
+
# frame where the named tuple is created. Bypass this step in environments
|
213 |
+
# where sys._getframe is not defined (Jython for example) or sys._getframe
|
214 |
+
# is not defined for arguments greater than 0 (IronPython), or where the
|
215 |
+
# user has specified a particular module.
|
216 |
+
if module is None:
|
217 |
+
try:
|
218 |
+
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
219 |
+
except (AttributeError, ValueError):
|
220 |
+
pass
|
221 |
+
if module is not None:
|
222 |
+
result.__module__ = module
|
223 |
+
__new__.__module__ = module
|
224 |
+
|
225 |
+
return result
|
scipy/_lib/_ccallback.py
ADDED
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from . import _ccallback_c
|
2 |
+
|
3 |
+
import ctypes
|
4 |
+
|
5 |
+
PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]
|
6 |
+
|
7 |
+
ffi = None
|
8 |
+
|
9 |
+
class CData:
|
10 |
+
pass
|
11 |
+
|
12 |
+
def _import_cffi():
|
13 |
+
global ffi, CData
|
14 |
+
|
15 |
+
if ffi is not None:
|
16 |
+
return
|
17 |
+
|
18 |
+
try:
|
19 |
+
import cffi
|
20 |
+
ffi = cffi.FFI()
|
21 |
+
CData = ffi.CData
|
22 |
+
except ImportError:
|
23 |
+
ffi = False
|
24 |
+
|
25 |
+
|
26 |
+
class LowLevelCallable(tuple):
|
27 |
+
"""
|
28 |
+
Low-level callback function.
|
29 |
+
|
30 |
+
Some functions in SciPy take as arguments callback functions, which
|
31 |
+
can either be python callables or low-level compiled functions. Using
|
32 |
+
compiled callback functions can improve performance somewhat by
|
33 |
+
avoiding wrapping data in Python objects.
|
34 |
+
|
35 |
+
Such low-level functions in SciPy are wrapped in `LowLevelCallable`
|
36 |
+
objects, which can be constructed from function pointers obtained from
|
37 |
+
ctypes, cffi, Cython, or contained in Python `PyCapsule` objects.
|
38 |
+
|
39 |
+
.. seealso::
|
40 |
+
|
41 |
+
Functions accepting low-level callables:
|
42 |
+
|
43 |
+
`scipy.integrate.quad`, `scipy.ndimage.generic_filter`,
|
44 |
+
`scipy.ndimage.generic_filter1d`, `scipy.ndimage.geometric_transform`
|
45 |
+
|
46 |
+
Usage examples:
|
47 |
+
|
48 |
+
:ref:`ndimage-ccallbacks`, :ref:`quad-callbacks`
|
49 |
+
|
50 |
+
Parameters
|
51 |
+
----------
|
52 |
+
function : {PyCapsule, ctypes function pointer, cffi function pointer}
|
53 |
+
Low-level callback function.
|
54 |
+
user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
|
55 |
+
User data to pass on to the callback function.
|
56 |
+
signature : str, optional
|
57 |
+
Signature of the function. If omitted, determined from *function*,
|
58 |
+
if possible.
|
59 |
+
|
60 |
+
Attributes
|
61 |
+
----------
|
62 |
+
function
|
63 |
+
Callback function given.
|
64 |
+
user_data
|
65 |
+
User data given.
|
66 |
+
signature
|
67 |
+
Signature of the function.
|
68 |
+
|
69 |
+
Methods
|
70 |
+
-------
|
71 |
+
from_cython
|
72 |
+
Class method for constructing callables from Cython C-exported
|
73 |
+
functions.
|
74 |
+
|
75 |
+
Notes
|
76 |
+
-----
|
77 |
+
The argument ``function`` can be one of:
|
78 |
+
|
79 |
+
- PyCapsule, whose name contains the C function signature
|
80 |
+
- ctypes function pointer
|
81 |
+
- cffi function pointer
|
82 |
+
|
83 |
+
The signature of the low-level callback must match one of those expected
|
84 |
+
by the routine it is passed to.
|
85 |
+
|
86 |
+
If constructing low-level functions from a PyCapsule, the name of the
|
87 |
+
capsule must be the corresponding signature, in the format::
|
88 |
+
|
89 |
+
return_type (arg1_type, arg2_type, ...)
|
90 |
+
|
91 |
+
For example::
|
92 |
+
|
93 |
+
"void (double)"
|
94 |
+
"double (double, int *, void *)"
|
95 |
+
|
96 |
+
The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
|
97 |
+
if an explicit value for ``user_data`` was not given.
|
98 |
+
|
99 |
+
"""
|
100 |
+
|
101 |
+
# Make the class immutable
|
102 |
+
__slots__ = ()
|
103 |
+
|
104 |
+
def __new__(cls, function, user_data=None, signature=None):
|
105 |
+
# We need to hold a reference to the function & user data,
|
106 |
+
# to prevent them going out of scope
|
107 |
+
item = cls._parse_callback(function, user_data, signature)
|
108 |
+
return tuple.__new__(cls, (item, function, user_data))
|
109 |
+
|
110 |
+
def __repr__(self):
|
111 |
+
return f"LowLevelCallable({self.function!r}, {self.user_data!r})"
|
112 |
+
|
113 |
+
@property
|
114 |
+
def function(self):
|
115 |
+
return tuple.__getitem__(self, 1)
|
116 |
+
|
117 |
+
@property
|
118 |
+
def user_data(self):
|
119 |
+
return tuple.__getitem__(self, 2)
|
120 |
+
|
121 |
+
@property
|
122 |
+
def signature(self):
|
123 |
+
return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))
|
124 |
+
|
125 |
+
def __getitem__(self, idx):
|
126 |
+
raise ValueError()
|
127 |
+
|
128 |
+
@classmethod
|
129 |
+
def from_cython(cls, module, name, user_data=None, signature=None):
|
130 |
+
"""
|
131 |
+
Create a low-level callback function from an exported Cython function.
|
132 |
+
|
133 |
+
Parameters
|
134 |
+
----------
|
135 |
+
module : module
|
136 |
+
Cython module where the exported function resides
|
137 |
+
name : str
|
138 |
+
Name of the exported function
|
139 |
+
user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
|
140 |
+
User data to pass on to the callback function.
|
141 |
+
signature : str, optional
|
142 |
+
Signature of the function. If omitted, determined from *function*.
|
143 |
+
|
144 |
+
"""
|
145 |
+
try:
|
146 |
+
function = module.__pyx_capi__[name]
|
147 |
+
except AttributeError as e:
|
148 |
+
message = "Given module is not a Cython module with __pyx_capi__ attribute"
|
149 |
+
raise ValueError(message) from e
|
150 |
+
except KeyError as e:
|
151 |
+
message = f"No function {name!r} found in __pyx_capi__ of the module"
|
152 |
+
raise ValueError(message) from e
|
153 |
+
return cls(function, user_data, signature)
|
154 |
+
|
155 |
+
@classmethod
|
156 |
+
def _parse_callback(cls, obj, user_data=None, signature=None):
|
157 |
+
_import_cffi()
|
158 |
+
|
159 |
+
if isinstance(obj, LowLevelCallable):
|
160 |
+
func = tuple.__getitem__(obj, 0)
|
161 |
+
elif isinstance(obj, PyCFuncPtr):
|
162 |
+
func, signature = _get_ctypes_func(obj, signature)
|
163 |
+
elif isinstance(obj, CData):
|
164 |
+
func, signature = _get_cffi_func(obj, signature)
|
165 |
+
elif _ccallback_c.check_capsule(obj):
|
166 |
+
func = obj
|
167 |
+
else:
|
168 |
+
raise ValueError("Given input is not a callable or a "
|
169 |
+
"low-level callable (pycapsule/ctypes/cffi)")
|
170 |
+
|
171 |
+
if isinstance(user_data, ctypes.c_void_p):
|
172 |
+
context = _get_ctypes_data(user_data)
|
173 |
+
elif isinstance(user_data, CData):
|
174 |
+
context = _get_cffi_data(user_data)
|
175 |
+
elif user_data is None:
|
176 |
+
context = 0
|
177 |
+
elif _ccallback_c.check_capsule(user_data):
|
178 |
+
context = user_data
|
179 |
+
else:
|
180 |
+
raise ValueError("Given user data is not a valid "
|
181 |
+
"low-level void* pointer (pycapsule/ctypes/cffi)")
|
182 |
+
|
183 |
+
return _ccallback_c.get_raw_capsule(func, signature, context)
|
184 |
+
|
185 |
+
|
186 |
+
#
|
187 |
+
# ctypes helpers
|
188 |
+
#
|
189 |
+
|
190 |
+
def _get_ctypes_func(func, signature=None):
|
191 |
+
# Get function pointer
|
192 |
+
func_ptr = ctypes.cast(func, ctypes.c_void_p).value
|
193 |
+
|
194 |
+
# Construct function signature
|
195 |
+
if signature is None:
|
196 |
+
signature = _typename_from_ctypes(func.restype) + " ("
|
197 |
+
for j, arg in enumerate(func.argtypes):
|
198 |
+
if j == 0:
|
199 |
+
signature += _typename_from_ctypes(arg)
|
200 |
+
else:
|
201 |
+
signature += ", " + _typename_from_ctypes(arg)
|
202 |
+
signature += ")"
|
203 |
+
|
204 |
+
return func_ptr, signature
|
205 |
+
|
206 |
+
|
207 |
+
def _typename_from_ctypes(item):
|
208 |
+
if item is None:
|
209 |
+
return "void"
|
210 |
+
elif item is ctypes.c_void_p:
|
211 |
+
return "void *"
|
212 |
+
|
213 |
+
name = item.__name__
|
214 |
+
|
215 |
+
pointer_level = 0
|
216 |
+
while name.startswith("LP_"):
|
217 |
+
pointer_level += 1
|
218 |
+
name = name[3:]
|
219 |
+
|
220 |
+
if name.startswith('c_'):
|
221 |
+
name = name[2:]
|
222 |
+
|
223 |
+
if pointer_level > 0:
|
224 |
+
name += " " + "*"*pointer_level
|
225 |
+
|
226 |
+
return name
|
227 |
+
|
228 |
+
|
229 |
+
def _get_ctypes_data(data):
|
230 |
+
# Get voidp pointer
|
231 |
+
return ctypes.cast(data, ctypes.c_void_p).value
|
232 |
+
|
233 |
+
|
234 |
+
#
|
235 |
+
# CFFI helpers
|
236 |
+
#
|
237 |
+
|
238 |
+
def _get_cffi_func(func, signature=None):
|
239 |
+
# Get function pointer
|
240 |
+
func_ptr = ffi.cast('uintptr_t', func)
|
241 |
+
|
242 |
+
# Get signature
|
243 |
+
if signature is None:
|
244 |
+
signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')
|
245 |
+
|
246 |
+
return func_ptr, signature
|
247 |
+
|
248 |
+
|
249 |
+
def _get_cffi_data(data):
|
250 |
+
# Get pointer
|
251 |
+
return ffi.cast('uintptr_t', data)
|
scipy/_lib/_ccallback_c.cp39-win_amd64.dll.a
ADDED
Binary file (1.6 kB). View file
|
|
scipy/_lib/_ccallback_c.cp39-win_amd64.pyd
ADDED
Binary file (86.5 kB). View file
|
|
scipy/_lib/_disjoint_set.py
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Disjoint set data structure
|
3 |
+
"""
|
4 |
+
|
5 |
+
|
6 |
+
class DisjointSet:
|
7 |
+
""" Disjoint set data structure for incremental connectivity queries.
|
8 |
+
|
9 |
+
.. versionadded:: 1.6.0
|
10 |
+
|
11 |
+
Attributes
|
12 |
+
----------
|
13 |
+
n_subsets : int
|
14 |
+
The number of subsets.
|
15 |
+
|
16 |
+
Methods
|
17 |
+
-------
|
18 |
+
add
|
19 |
+
merge
|
20 |
+
connected
|
21 |
+
subset
|
22 |
+
subset_size
|
23 |
+
subsets
|
24 |
+
__getitem__
|
25 |
+
|
26 |
+
Notes
|
27 |
+
-----
|
28 |
+
This class implements the disjoint set [1]_, also known as the *union-find*
|
29 |
+
or *merge-find* data structure. The *find* operation (implemented in
|
30 |
+
`__getitem__`) implements the *path halving* variant. The *merge* method
|
31 |
+
implements the *merge by size* variant.
|
32 |
+
|
33 |
+
References
|
34 |
+
----------
|
35 |
+
.. [1] https://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
36 |
+
|
37 |
+
Examples
|
38 |
+
--------
|
39 |
+
>>> from scipy.cluster.hierarchy import DisjointSet
|
40 |
+
|
41 |
+
Initialize a disjoint set:
|
42 |
+
|
43 |
+
>>> disjoint_set = DisjointSet([1, 2, 3, 'a', 'b'])
|
44 |
+
|
45 |
+
Merge some subsets:
|
46 |
+
|
47 |
+
>>> disjoint_set.merge(1, 2)
|
48 |
+
True
|
49 |
+
>>> disjoint_set.merge(3, 'a')
|
50 |
+
True
|
51 |
+
>>> disjoint_set.merge('a', 'b')
|
52 |
+
True
|
53 |
+
>>> disjoint_set.merge('b', 'b')
|
54 |
+
False
|
55 |
+
|
56 |
+
Find root elements:
|
57 |
+
|
58 |
+
>>> disjoint_set[2]
|
59 |
+
1
|
60 |
+
>>> disjoint_set['b']
|
61 |
+
3
|
62 |
+
|
63 |
+
Test connectivity:
|
64 |
+
|
65 |
+
>>> disjoint_set.connected(1, 2)
|
66 |
+
True
|
67 |
+
>>> disjoint_set.connected(1, 'b')
|
68 |
+
False
|
69 |
+
|
70 |
+
List elements in disjoint set:
|
71 |
+
|
72 |
+
>>> list(disjoint_set)
|
73 |
+
[1, 2, 3, 'a', 'b']
|
74 |
+
|
75 |
+
Get the subset containing 'a':
|
76 |
+
|
77 |
+
>>> disjoint_set.subset('a')
|
78 |
+
{'a', 3, 'b'}
|
79 |
+
|
80 |
+
Get the size of the subset containing 'a' (without actually instantiating
|
81 |
+
the subset):
|
82 |
+
|
83 |
+
>>> disjoint_set.subset_size('a')
|
84 |
+
3
|
85 |
+
|
86 |
+
Get all subsets in the disjoint set:
|
87 |
+
|
88 |
+
>>> disjoint_set.subsets()
|
89 |
+
[{1, 2}, {'a', 3, 'b'}]
|
90 |
+
"""
|
91 |
+
def __init__(self, elements=None):
|
92 |
+
self.n_subsets = 0
|
93 |
+
self._sizes = {}
|
94 |
+
self._parents = {}
|
95 |
+
# _nbrs is a circular linked list which links connected elements.
|
96 |
+
self._nbrs = {}
|
97 |
+
# _indices tracks the element insertion order in `__iter__`.
|
98 |
+
self._indices = {}
|
99 |
+
if elements is not None:
|
100 |
+
for x in elements:
|
101 |
+
self.add(x)
|
102 |
+
|
103 |
+
def __iter__(self):
|
104 |
+
"""Returns an iterator of the elements in the disjoint set.
|
105 |
+
|
106 |
+
Elements are ordered by insertion order.
|
107 |
+
"""
|
108 |
+
return iter(self._indices)
|
109 |
+
|
110 |
+
def __len__(self):
|
111 |
+
return len(self._indices)
|
112 |
+
|
113 |
+
def __contains__(self, x):
|
114 |
+
return x in self._indices
|
115 |
+
|
116 |
+
def __getitem__(self, x):
|
117 |
+
"""Find the root element of `x`.
|
118 |
+
|
119 |
+
Parameters
|
120 |
+
----------
|
121 |
+
x : hashable object
|
122 |
+
Input element.
|
123 |
+
|
124 |
+
Returns
|
125 |
+
-------
|
126 |
+
root : hashable object
|
127 |
+
Root element of `x`.
|
128 |
+
"""
|
129 |
+
if x not in self._indices:
|
130 |
+
raise KeyError(x)
|
131 |
+
|
132 |
+
# find by "path halving"
|
133 |
+
parents = self._parents
|
134 |
+
while self._indices[x] != self._indices[parents[x]]:
|
135 |
+
parents[x] = parents[parents[x]]
|
136 |
+
x = parents[x]
|
137 |
+
return x
|
138 |
+
|
139 |
+
def add(self, x):
|
140 |
+
"""Add element `x` to disjoint set
|
141 |
+
"""
|
142 |
+
if x in self._indices:
|
143 |
+
return
|
144 |
+
|
145 |
+
self._sizes[x] = 1
|
146 |
+
self._parents[x] = x
|
147 |
+
self._nbrs[x] = x
|
148 |
+
self._indices[x] = len(self._indices)
|
149 |
+
self.n_subsets += 1
|
150 |
+
|
151 |
+
def merge(self, x, y):
|
152 |
+
"""Merge the subsets of `x` and `y`.
|
153 |
+
|
154 |
+
The smaller subset (the child) is merged into the larger subset (the
|
155 |
+
parent). If the subsets are of equal size, the root element which was
|
156 |
+
first inserted into the disjoint set is selected as the parent.
|
157 |
+
|
158 |
+
Parameters
|
159 |
+
----------
|
160 |
+
x, y : hashable object
|
161 |
+
Elements to merge.
|
162 |
+
|
163 |
+
Returns
|
164 |
+
-------
|
165 |
+
merged : bool
|
166 |
+
True if `x` and `y` were in disjoint sets, False otherwise.
|
167 |
+
"""
|
168 |
+
xr = self[x]
|
169 |
+
yr = self[y]
|
170 |
+
if self._indices[xr] == self._indices[yr]:
|
171 |
+
return False
|
172 |
+
|
173 |
+
sizes = self._sizes
|
174 |
+
if (sizes[xr], self._indices[yr]) < (sizes[yr], self._indices[xr]):
|
175 |
+
xr, yr = yr, xr
|
176 |
+
self._parents[yr] = xr
|
177 |
+
self._sizes[xr] += self._sizes[yr]
|
178 |
+
self._nbrs[xr], self._nbrs[yr] = self._nbrs[yr], self._nbrs[xr]
|
179 |
+
self.n_subsets -= 1
|
180 |
+
return True
|
181 |
+
|
182 |
+
def connected(self, x, y):
|
183 |
+
"""Test whether `x` and `y` are in the same subset.
|
184 |
+
|
185 |
+
Parameters
|
186 |
+
----------
|
187 |
+
x, y : hashable object
|
188 |
+
Elements to test.
|
189 |
+
|
190 |
+
Returns
|
191 |
+
-------
|
192 |
+
result : bool
|
193 |
+
True if `x` and `y` are in the same set, False otherwise.
|
194 |
+
"""
|
195 |
+
return self._indices[self[x]] == self._indices[self[y]]
|
196 |
+
|
197 |
+
def subset(self, x):
|
198 |
+
"""Get the subset containing `x`.
|
199 |
+
|
200 |
+
Parameters
|
201 |
+
----------
|
202 |
+
x : hashable object
|
203 |
+
Input element.
|
204 |
+
|
205 |
+
Returns
|
206 |
+
-------
|
207 |
+
result : set
|
208 |
+
Subset containing `x`.
|
209 |
+
"""
|
210 |
+
if x not in self._indices:
|
211 |
+
raise KeyError(x)
|
212 |
+
|
213 |
+
result = [x]
|
214 |
+
nxt = self._nbrs[x]
|
215 |
+
while self._indices[nxt] != self._indices[x]:
|
216 |
+
result.append(nxt)
|
217 |
+
nxt = self._nbrs[nxt]
|
218 |
+
return set(result)
|
219 |
+
|
220 |
+
def subset_size(self, x):
|
221 |
+
"""Get the size of the subset containing `x`.
|
222 |
+
|
223 |
+
Note that this method is faster than ``len(self.subset(x))`` because
|
224 |
+
the size is directly read off an internal field, without the need to
|
225 |
+
instantiate the full subset.
|
226 |
+
|
227 |
+
Parameters
|
228 |
+
----------
|
229 |
+
x : hashable object
|
230 |
+
Input element.
|
231 |
+
|
232 |
+
Returns
|
233 |
+
-------
|
234 |
+
result : int
|
235 |
+
Size of the subset containing `x`.
|
236 |
+
"""
|
237 |
+
return self._sizes[self[x]]
|
238 |
+
|
239 |
+
def subsets(self):
|
240 |
+
"""Get all the subsets in the disjoint set.
|
241 |
+
|
242 |
+
Returns
|
243 |
+
-------
|
244 |
+
result : list
|
245 |
+
Subsets in the disjoint set.
|
246 |
+
"""
|
247 |
+
result = []
|
248 |
+
visited = set()
|
249 |
+
for x in self:
|
250 |
+
if x not in visited:
|
251 |
+
xset = self.subset(x)
|
252 |
+
visited.update(xset)
|
253 |
+
result.append(xset)
|
254 |
+
return result
|
scipy/_lib/_docscrape.py
ADDED
@@ -0,0 +1,679 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Extract reference documentation from the NumPy source tree.
|
2 |
+
|
3 |
+
"""
|
4 |
+
# copied from numpydoc/docscrape.py
|
5 |
+
import inspect
|
6 |
+
import textwrap
|
7 |
+
import re
|
8 |
+
import pydoc
|
9 |
+
from warnings import warn
|
10 |
+
from collections import namedtuple
|
11 |
+
from collections.abc import Callable, Mapping
|
12 |
+
import copy
|
13 |
+
import sys
|
14 |
+
|
15 |
+
|
16 |
+
def strip_blank_lines(l):
|
17 |
+
"Remove leading and trailing blank lines from a list of lines"
|
18 |
+
while l and not l[0].strip():
|
19 |
+
del l[0]
|
20 |
+
while l and not l[-1].strip():
|
21 |
+
del l[-1]
|
22 |
+
return l
|
23 |
+
|
24 |
+
|
25 |
+
class Reader:
|
26 |
+
"""A line-based string reader.
|
27 |
+
|
28 |
+
"""
|
29 |
+
def __init__(self, data):
|
30 |
+
"""
|
31 |
+
Parameters
|
32 |
+
----------
|
33 |
+
data : str
|
34 |
+
String with lines separated by '\\n'.
|
35 |
+
|
36 |
+
"""
|
37 |
+
if isinstance(data, list):
|
38 |
+
self._str = data
|
39 |
+
else:
|
40 |
+
self._str = data.split('\n') # store string as list of lines
|
41 |
+
|
42 |
+
self.reset()
|
43 |
+
|
44 |
+
def __getitem__(self, n):
|
45 |
+
return self._str[n]
|
46 |
+
|
47 |
+
def reset(self):
|
48 |
+
self._l = 0 # current line nr
|
49 |
+
|
50 |
+
def read(self):
|
51 |
+
if not self.eof():
|
52 |
+
out = self[self._l]
|
53 |
+
self._l += 1
|
54 |
+
return out
|
55 |
+
else:
|
56 |
+
return ''
|
57 |
+
|
58 |
+
def seek_next_non_empty_line(self):
|
59 |
+
for l in self[self._l:]:
|
60 |
+
if l.strip():
|
61 |
+
break
|
62 |
+
else:
|
63 |
+
self._l += 1
|
64 |
+
|
65 |
+
def eof(self):
|
66 |
+
return self._l >= len(self._str)
|
67 |
+
|
68 |
+
def read_to_condition(self, condition_func):
|
69 |
+
start = self._l
|
70 |
+
for line in self[start:]:
|
71 |
+
if condition_func(line):
|
72 |
+
return self[start:self._l]
|
73 |
+
self._l += 1
|
74 |
+
if self.eof():
|
75 |
+
return self[start:self._l+1]
|
76 |
+
return []
|
77 |
+
|
78 |
+
def read_to_next_empty_line(self):
|
79 |
+
self.seek_next_non_empty_line()
|
80 |
+
|
81 |
+
def is_empty(line):
|
82 |
+
return not line.strip()
|
83 |
+
|
84 |
+
return self.read_to_condition(is_empty)
|
85 |
+
|
86 |
+
def read_to_next_unindented_line(self):
|
87 |
+
def is_unindented(line):
|
88 |
+
return (line.strip() and (len(line.lstrip()) == len(line)))
|
89 |
+
return self.read_to_condition(is_unindented)
|
90 |
+
|
91 |
+
def peek(self, n=0):
|
92 |
+
if self._l + n < len(self._str):
|
93 |
+
return self[self._l + n]
|
94 |
+
else:
|
95 |
+
return ''
|
96 |
+
|
97 |
+
def is_empty(self):
|
98 |
+
return not ''.join(self._str).strip()
|
99 |
+
|
100 |
+
|
101 |
+
class ParseError(Exception):
|
102 |
+
def __str__(self):
|
103 |
+
message = self.args[0]
|
104 |
+
if hasattr(self, 'docstring'):
|
105 |
+
message = f"{message} in {self.docstring!r}"
|
106 |
+
return message
|
107 |
+
|
108 |
+
|
109 |
+
Parameter = namedtuple('Parameter', ['name', 'type', 'desc'])
|
110 |
+
|
111 |
+
|
112 |
+
class NumpyDocString(Mapping):
|
113 |
+
"""Parses a numpydoc string to an abstract representation
|
114 |
+
|
115 |
+
Instances define a mapping from section title to structured data.
|
116 |
+
|
117 |
+
"""
|
118 |
+
|
119 |
+
sections = {
|
120 |
+
'Signature': '',
|
121 |
+
'Summary': [''],
|
122 |
+
'Extended Summary': [],
|
123 |
+
'Parameters': [],
|
124 |
+
'Returns': [],
|
125 |
+
'Yields': [],
|
126 |
+
'Receives': [],
|
127 |
+
'Raises': [],
|
128 |
+
'Warns': [],
|
129 |
+
'Other Parameters': [],
|
130 |
+
'Attributes': [],
|
131 |
+
'Methods': [],
|
132 |
+
'See Also': [],
|
133 |
+
'Notes': [],
|
134 |
+
'Warnings': [],
|
135 |
+
'References': '',
|
136 |
+
'Examples': '',
|
137 |
+
'index': {}
|
138 |
+
}
|
139 |
+
|
140 |
+
def __init__(self, docstring, config={}):
|
141 |
+
orig_docstring = docstring
|
142 |
+
docstring = textwrap.dedent(docstring).split('\n')
|
143 |
+
|
144 |
+
self._doc = Reader(docstring)
|
145 |
+
self._parsed_data = copy.deepcopy(self.sections)
|
146 |
+
|
147 |
+
try:
|
148 |
+
self._parse()
|
149 |
+
except ParseError as e:
|
150 |
+
e.docstring = orig_docstring
|
151 |
+
raise
|
152 |
+
|
153 |
+
def __getitem__(self, key):
|
154 |
+
return self._parsed_data[key]
|
155 |
+
|
156 |
+
def __setitem__(self, key, val):
|
157 |
+
if key not in self._parsed_data:
|
158 |
+
self._error_location("Unknown section %s" % key, error=False)
|
159 |
+
else:
|
160 |
+
self._parsed_data[key] = val
|
161 |
+
|
162 |
+
def __iter__(self):
|
163 |
+
return iter(self._parsed_data)
|
164 |
+
|
165 |
+
def __len__(self):
|
166 |
+
return len(self._parsed_data)
|
167 |
+
|
168 |
+
def _is_at_section(self):
|
169 |
+
self._doc.seek_next_non_empty_line()
|
170 |
+
|
171 |
+
if self._doc.eof():
|
172 |
+
return False
|
173 |
+
|
174 |
+
l1 = self._doc.peek().strip() # e.g. Parameters
|
175 |
+
|
176 |
+
if l1.startswith('.. index::'):
|
177 |
+
return True
|
178 |
+
|
179 |
+
l2 = self._doc.peek(1).strip() # ---------- or ==========
|
180 |
+
return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1))
|
181 |
+
|
182 |
+
def _strip(self, doc):
|
183 |
+
i = 0
|
184 |
+
j = 0
|
185 |
+
for i, line in enumerate(doc):
|
186 |
+
if line.strip():
|
187 |
+
break
|
188 |
+
|
189 |
+
for j, line in enumerate(doc[::-1]):
|
190 |
+
if line.strip():
|
191 |
+
break
|
192 |
+
|
193 |
+
return doc[i:len(doc)-j]
|
194 |
+
|
195 |
+
def _read_to_next_section(self):
|
196 |
+
section = self._doc.read_to_next_empty_line()
|
197 |
+
|
198 |
+
while not self._is_at_section() and not self._doc.eof():
|
199 |
+
if not self._doc.peek(-1).strip(): # previous line was empty
|
200 |
+
section += ['']
|
201 |
+
|
202 |
+
section += self._doc.read_to_next_empty_line()
|
203 |
+
|
204 |
+
return section
|
205 |
+
|
206 |
+
def _read_sections(self):
|
207 |
+
while not self._doc.eof():
|
208 |
+
data = self._read_to_next_section()
|
209 |
+
name = data[0].strip()
|
210 |
+
|
211 |
+
if name.startswith('..'): # index section
|
212 |
+
yield name, data[1:]
|
213 |
+
elif len(data) < 2:
|
214 |
+
yield StopIteration
|
215 |
+
else:
|
216 |
+
yield name, self._strip(data[2:])
|
217 |
+
|
218 |
+
def _parse_param_list(self, content, single_element_is_type=False):
|
219 |
+
r = Reader(content)
|
220 |
+
params = []
|
221 |
+
while not r.eof():
|
222 |
+
header = r.read().strip()
|
223 |
+
if ' : ' in header:
|
224 |
+
arg_name, arg_type = header.split(' : ')[:2]
|
225 |
+
else:
|
226 |
+
if single_element_is_type:
|
227 |
+
arg_name, arg_type = '', header
|
228 |
+
else:
|
229 |
+
arg_name, arg_type = header, ''
|
230 |
+
|
231 |
+
desc = r.read_to_next_unindented_line()
|
232 |
+
desc = dedent_lines(desc)
|
233 |
+
desc = strip_blank_lines(desc)
|
234 |
+
|
235 |
+
params.append(Parameter(arg_name, arg_type, desc))
|
236 |
+
|
237 |
+
return params
|
238 |
+
|
239 |
+
# See also supports the following formats.
|
240 |
+
#
|
241 |
+
# <FUNCNAME>
|
242 |
+
# <FUNCNAME> SPACE* COLON SPACE+ <DESC> SPACE*
|
243 |
+
# <FUNCNAME> ( COMMA SPACE+ <FUNCNAME>)+ (COMMA | PERIOD)? SPACE*
|
244 |
+
# <FUNCNAME> ( COMMA SPACE+ <FUNCNAME>)* SPACE* COLON SPACE+ <DESC> SPACE*
|
245 |
+
|
246 |
+
# <FUNCNAME> is one of
|
247 |
+
# <PLAIN_FUNCNAME>
|
248 |
+
# COLON <ROLE> COLON BACKTICK <PLAIN_FUNCNAME> BACKTICK
|
249 |
+
# where
|
250 |
+
# <PLAIN_FUNCNAME> is a legal function name, and
|
251 |
+
# <ROLE> is any nonempty sequence of word characters.
|
252 |
+
# Examples: func_f1 :meth:`func_h1` :obj:`~baz.obj_r` :class:`class_j`
|
253 |
+
# <DESC> is a string describing the function.
|
254 |
+
|
255 |
+
_role = r":(?P<role>\w+):"
|
256 |
+
_funcbacktick = r"`(?P<name>(?:~\w+\.)?[a-zA-Z0-9_\.-]+)`"
|
257 |
+
_funcplain = r"(?P<name2>[a-zA-Z0-9_\.-]+)"
|
258 |
+
_funcname = r"(" + _role + _funcbacktick + r"|" + _funcplain + r")"
|
259 |
+
_funcnamenext = _funcname.replace('role', 'rolenext')
|
260 |
+
_funcnamenext = _funcnamenext.replace('name', 'namenext')
|
261 |
+
_description = r"(?P<description>\s*:(\s+(?P<desc>\S+.*))?)?\s*$"
|
262 |
+
_func_rgx = re.compile(r"^\s*" + _funcname + r"\s*")
|
263 |
+
_line_rgx = re.compile(
|
264 |
+
r"^\s*" +
|
265 |
+
r"(?P<allfuncs>" + # group for all function names
|
266 |
+
_funcname +
|
267 |
+
r"(?P<morefuncs>([,]\s+" + _funcnamenext + r")*)" +
|
268 |
+
r")" + # end of "allfuncs"
|
269 |
+
# Some function lists have a trailing comma (or period) '\s*'
|
270 |
+
r"(?P<trailing>[,\.])?" +
|
271 |
+
_description)
|
272 |
+
|
273 |
+
# Empty <DESC> elements are replaced with '..'
|
274 |
+
empty_description = '..'
|
275 |
+
|
276 |
+
def _parse_see_also(self, content):
|
277 |
+
"""
|
278 |
+
func_name : Descriptive text
|
279 |
+
continued text
|
280 |
+
another_func_name : Descriptive text
|
281 |
+
func_name1, func_name2, :meth:`func_name`, func_name3
|
282 |
+
|
283 |
+
"""
|
284 |
+
|
285 |
+
items = []
|
286 |
+
|
287 |
+
def parse_item_name(text):
|
288 |
+
"""Match ':role:`name`' or 'name'."""
|
289 |
+
m = self._func_rgx.match(text)
|
290 |
+
if not m:
|
291 |
+
raise ParseError("%s is not a item name" % text)
|
292 |
+
role = m.group('role')
|
293 |
+
name = m.group('name') if role else m.group('name2')
|
294 |
+
return name, role, m.end()
|
295 |
+
|
296 |
+
rest = []
|
297 |
+
for line in content:
|
298 |
+
if not line.strip():
|
299 |
+
continue
|
300 |
+
|
301 |
+
line_match = self._line_rgx.match(line)
|
302 |
+
description = None
|
303 |
+
if line_match:
|
304 |
+
description = line_match.group('desc')
|
305 |
+
if line_match.group('trailing') and description:
|
306 |
+
self._error_location(
|
307 |
+
'Unexpected comma or period after function list at '
|
308 |
+
'index %d of line "%s"' % (line_match.end('trailing'),
|
309 |
+
line),
|
310 |
+
error=False)
|
311 |
+
if not description and line.startswith(' '):
|
312 |
+
rest.append(line.strip())
|
313 |
+
elif line_match:
|
314 |
+
funcs = []
|
315 |
+
text = line_match.group('allfuncs')
|
316 |
+
while True:
|
317 |
+
if not text.strip():
|
318 |
+
break
|
319 |
+
name, role, match_end = parse_item_name(text)
|
320 |
+
funcs.append((name, role))
|
321 |
+
text = text[match_end:].strip()
|
322 |
+
if text and text[0] == ',':
|
323 |
+
text = text[1:].strip()
|
324 |
+
rest = list(filter(None, [description]))
|
325 |
+
items.append((funcs, rest))
|
326 |
+
else:
|
327 |
+
raise ParseError("%s is not a item name" % line)
|
328 |
+
return items
|
329 |
+
|
330 |
+
def _parse_index(self, section, content):
|
331 |
+
"""
|
332 |
+
.. index:: default
|
333 |
+
:refguide: something, else, and more
|
334 |
+
|
335 |
+
"""
|
336 |
+
def strip_each_in(lst):
|
337 |
+
return [s.strip() for s in lst]
|
338 |
+
|
339 |
+
out = {}
|
340 |
+
section = section.split('::')
|
341 |
+
if len(section) > 1:
|
342 |
+
out['default'] = strip_each_in(section[1].split(','))[0]
|
343 |
+
for line in content:
|
344 |
+
line = line.split(':')
|
345 |
+
if len(line) > 2:
|
346 |
+
out[line[1]] = strip_each_in(line[2].split(','))
|
347 |
+
return out
|
348 |
+
|
349 |
+
def _parse_summary(self):
|
350 |
+
"""Grab signature (if given) and summary"""
|
351 |
+
if self._is_at_section():
|
352 |
+
return
|
353 |
+
|
354 |
+
# If several signatures present, take the last one
|
355 |
+
while True:
|
356 |
+
summary = self._doc.read_to_next_empty_line()
|
357 |
+
summary_str = " ".join([s.strip() for s in summary]).strip()
|
358 |
+
compiled = re.compile(r'^([\w., ]+=)?\s*[\w\.]+\(.*\)$')
|
359 |
+
if compiled.match(summary_str):
|
360 |
+
self['Signature'] = summary_str
|
361 |
+
if not self._is_at_section():
|
362 |
+
continue
|
363 |
+
break
|
364 |
+
|
365 |
+
if summary is not None:
|
366 |
+
self['Summary'] = summary
|
367 |
+
|
368 |
+
if not self._is_at_section():
|
369 |
+
self['Extended Summary'] = self._read_to_next_section()
|
370 |
+
|
371 |
+
def _parse(self):
|
372 |
+
self._doc.reset()
|
373 |
+
self._parse_summary()
|
374 |
+
|
375 |
+
sections = list(self._read_sections())
|
376 |
+
section_names = {section for section, content in sections}
|
377 |
+
|
378 |
+
has_returns = 'Returns' in section_names
|
379 |
+
has_yields = 'Yields' in section_names
|
380 |
+
# We could do more tests, but we are not. Arbitrarily.
|
381 |
+
if has_returns and has_yields:
|
382 |
+
msg = 'Docstring contains both a Returns and Yields section.'
|
383 |
+
raise ValueError(msg)
|
384 |
+
if not has_yields and 'Receives' in section_names:
|
385 |
+
msg = 'Docstring contains a Receives section but not Yields.'
|
386 |
+
raise ValueError(msg)
|
387 |
+
|
388 |
+
for (section, content) in sections:
|
389 |
+
if not section.startswith('..'):
|
390 |
+
section = (s.capitalize() for s in section.split(' '))
|
391 |
+
section = ' '.join(section)
|
392 |
+
if self.get(section):
|
393 |
+
self._error_location("The section %s appears twice"
|
394 |
+
% section)
|
395 |
+
|
396 |
+
if section in ('Parameters', 'Other Parameters', 'Attributes',
|
397 |
+
'Methods'):
|
398 |
+
self[section] = self._parse_param_list(content)
|
399 |
+
elif section in ('Returns', 'Yields', 'Raises', 'Warns',
|
400 |
+
'Receives'):
|
401 |
+
self[section] = self._parse_param_list(
|
402 |
+
content, single_element_is_type=True)
|
403 |
+
elif section.startswith('.. index::'):
|
404 |
+
self['index'] = self._parse_index(section, content)
|
405 |
+
elif section == 'See Also':
|
406 |
+
self['See Also'] = self._parse_see_also(content)
|
407 |
+
else:
|
408 |
+
self[section] = content
|
409 |
+
|
410 |
+
def _error_location(self, msg, error=True):
|
411 |
+
if hasattr(self, '_obj'):
|
412 |
+
# we know where the docs came from:
|
413 |
+
try:
|
414 |
+
filename = inspect.getsourcefile(self._obj)
|
415 |
+
except TypeError:
|
416 |
+
filename = None
|
417 |
+
msg = msg + (f" in the docstring of {self._obj} in {filename}.")
|
418 |
+
if error:
|
419 |
+
raise ValueError(msg)
|
420 |
+
else:
|
421 |
+
warn(msg, stacklevel=3)
|
422 |
+
|
423 |
+
# string conversion routines
|
424 |
+
|
425 |
+
def _str_header(self, name, symbol='-'):
|
426 |
+
return [name, len(name)*symbol]
|
427 |
+
|
428 |
+
def _str_indent(self, doc, indent=4):
|
429 |
+
out = []
|
430 |
+
for line in doc:
|
431 |
+
out += [' '*indent + line]
|
432 |
+
return out
|
433 |
+
|
434 |
+
def _str_signature(self):
|
435 |
+
if self['Signature']:
|
436 |
+
return [self['Signature'].replace('*', r'\*')] + ['']
|
437 |
+
else:
|
438 |
+
return ['']
|
439 |
+
|
440 |
+
def _str_summary(self):
|
441 |
+
if self['Summary']:
|
442 |
+
return self['Summary'] + ['']
|
443 |
+
else:
|
444 |
+
return []
|
445 |
+
|
446 |
+
def _str_extended_summary(self):
|
447 |
+
if self['Extended Summary']:
|
448 |
+
return self['Extended Summary'] + ['']
|
449 |
+
else:
|
450 |
+
return []
|
451 |
+
|
452 |
+
def _str_param_list(self, name):
|
453 |
+
out = []
|
454 |
+
if self[name]:
|
455 |
+
out += self._str_header(name)
|
456 |
+
for param in self[name]:
|
457 |
+
parts = []
|
458 |
+
if param.name:
|
459 |
+
parts.append(param.name)
|
460 |
+
if param.type:
|
461 |
+
parts.append(param.type)
|
462 |
+
out += [' : '.join(parts)]
|
463 |
+
if param.desc and ''.join(param.desc).strip():
|
464 |
+
out += self._str_indent(param.desc)
|
465 |
+
out += ['']
|
466 |
+
return out
|
467 |
+
|
468 |
+
def _str_section(self, name):
|
469 |
+
out = []
|
470 |
+
if self[name]:
|
471 |
+
out += self._str_header(name)
|
472 |
+
out += self[name]
|
473 |
+
out += ['']
|
474 |
+
return out
|
475 |
+
|
476 |
+
def _str_see_also(self, func_role):
|
477 |
+
if not self['See Also']:
|
478 |
+
return []
|
479 |
+
out = []
|
480 |
+
out += self._str_header("See Also")
|
481 |
+
out += ['']
|
482 |
+
last_had_desc = True
|
483 |
+
for funcs, desc in self['See Also']:
|
484 |
+
assert isinstance(funcs, list)
|
485 |
+
links = []
|
486 |
+
for func, role in funcs:
|
487 |
+
if role:
|
488 |
+
link = f':{role}:`{func}`'
|
489 |
+
elif func_role:
|
490 |
+
link = f':{func_role}:`{func}`'
|
491 |
+
else:
|
492 |
+
link = "`%s`_" % func
|
493 |
+
links.append(link)
|
494 |
+
link = ', '.join(links)
|
495 |
+
out += [link]
|
496 |
+
if desc:
|
497 |
+
out += self._str_indent([' '.join(desc)])
|
498 |
+
last_had_desc = True
|
499 |
+
else:
|
500 |
+
last_had_desc = False
|
501 |
+
out += self._str_indent([self.empty_description])
|
502 |
+
|
503 |
+
if last_had_desc:
|
504 |
+
out += ['']
|
505 |
+
out += ['']
|
506 |
+
return out
|
507 |
+
|
508 |
+
def _str_index(self):
|
509 |
+
idx = self['index']
|
510 |
+
out = []
|
511 |
+
output_index = False
|
512 |
+
default_index = idx.get('default', '')
|
513 |
+
if default_index:
|
514 |
+
output_index = True
|
515 |
+
out += ['.. index:: %s' % default_index]
|
516 |
+
for section, references in idx.items():
|
517 |
+
if section == 'default':
|
518 |
+
continue
|
519 |
+
output_index = True
|
520 |
+
out += [' :{}: {}'.format(section, ', '.join(references))]
|
521 |
+
if output_index:
|
522 |
+
return out
|
523 |
+
else:
|
524 |
+
return ''
|
525 |
+
|
526 |
+
def __str__(self, func_role=''):
|
527 |
+
out = []
|
528 |
+
out += self._str_signature()
|
529 |
+
out += self._str_summary()
|
530 |
+
out += self._str_extended_summary()
|
531 |
+
for param_list in ('Parameters', 'Returns', 'Yields', 'Receives',
|
532 |
+
'Other Parameters', 'Raises', 'Warns'):
|
533 |
+
out += self._str_param_list(param_list)
|
534 |
+
out += self._str_section('Warnings')
|
535 |
+
out += self._str_see_also(func_role)
|
536 |
+
for s in ('Notes', 'References', 'Examples'):
|
537 |
+
out += self._str_section(s)
|
538 |
+
for param_list in ('Attributes', 'Methods'):
|
539 |
+
out += self._str_param_list(param_list)
|
540 |
+
out += self._str_index()
|
541 |
+
return '\n'.join(out)
|
542 |
+
|
543 |
+
|
544 |
+
def indent(str, indent=4):
|
545 |
+
indent_str = ' '*indent
|
546 |
+
if str is None:
|
547 |
+
return indent_str
|
548 |
+
lines = str.split('\n')
|
549 |
+
return '\n'.join(indent_str + l for l in lines)
|
550 |
+
|
551 |
+
|
552 |
+
def dedent_lines(lines):
|
553 |
+
"""Deindent a list of lines maximally"""
|
554 |
+
return textwrap.dedent("\n".join(lines)).split("\n")
|
555 |
+
|
556 |
+
|
557 |
+
def header(text, style='-'):
|
558 |
+
return text + '\n' + style*len(text) + '\n'
|
559 |
+
|
560 |
+
|
561 |
+
class FunctionDoc(NumpyDocString):
|
562 |
+
def __init__(self, func, role='func', doc=None, config={}):
|
563 |
+
self._f = func
|
564 |
+
self._role = role # e.g. "func" or "meth"
|
565 |
+
|
566 |
+
if doc is None:
|
567 |
+
if func is None:
|
568 |
+
raise ValueError("No function or docstring given")
|
569 |
+
doc = inspect.getdoc(func) or ''
|
570 |
+
NumpyDocString.__init__(self, doc, config)
|
571 |
+
|
572 |
+
def get_func(self):
|
573 |
+
func_name = getattr(self._f, '__name__', self.__class__.__name__)
|
574 |
+
if inspect.isclass(self._f):
|
575 |
+
func = getattr(self._f, '__call__', self._f.__init__)
|
576 |
+
else:
|
577 |
+
func = self._f
|
578 |
+
return func, func_name
|
579 |
+
|
580 |
+
def __str__(self):
|
581 |
+
out = ''
|
582 |
+
|
583 |
+
func, func_name = self.get_func()
|
584 |
+
|
585 |
+
roles = {'func': 'function',
|
586 |
+
'meth': 'method'}
|
587 |
+
|
588 |
+
if self._role:
|
589 |
+
if self._role not in roles:
|
590 |
+
print("Warning: invalid role %s" % self._role)
|
591 |
+
out += '.. {}:: {}\n \n\n'.format(roles.get(self._role, ''),
|
592 |
+
func_name)
|
593 |
+
|
594 |
+
out += super().__str__(func_role=self._role)
|
595 |
+
return out
|
596 |
+
|
597 |
+
|
598 |
+
class ClassDoc(NumpyDocString):
|
599 |
+
|
600 |
+
extra_public_methods = ['__call__']
|
601 |
+
|
602 |
+
def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
|
603 |
+
config={}):
|
604 |
+
if not inspect.isclass(cls) and cls is not None:
|
605 |
+
raise ValueError("Expected a class or None, but got %r" % cls)
|
606 |
+
self._cls = cls
|
607 |
+
|
608 |
+
if 'sphinx' in sys.modules:
|
609 |
+
from sphinx.ext.autodoc import ALL
|
610 |
+
else:
|
611 |
+
ALL = object()
|
612 |
+
|
613 |
+
self.show_inherited_members = config.get(
|
614 |
+
'show_inherited_class_members', True)
|
615 |
+
|
616 |
+
if modulename and not modulename.endswith('.'):
|
617 |
+
modulename += '.'
|
618 |
+
self._mod = modulename
|
619 |
+
|
620 |
+
if doc is None:
|
621 |
+
if cls is None:
|
622 |
+
raise ValueError("No class or documentation string given")
|
623 |
+
doc = pydoc.getdoc(cls)
|
624 |
+
|
625 |
+
NumpyDocString.__init__(self, doc)
|
626 |
+
|
627 |
+
_members = config.get('members', [])
|
628 |
+
if _members is ALL:
|
629 |
+
_members = None
|
630 |
+
_exclude = config.get('exclude-members', [])
|
631 |
+
|
632 |
+
if config.get('show_class_members', True) and _exclude is not ALL:
|
633 |
+
def splitlines_x(s):
|
634 |
+
if not s:
|
635 |
+
return []
|
636 |
+
else:
|
637 |
+
return s.splitlines()
|
638 |
+
for field, items in [('Methods', self.methods),
|
639 |
+
('Attributes', self.properties)]:
|
640 |
+
if not self[field]:
|
641 |
+
doc_list = []
|
642 |
+
for name in sorted(items):
|
643 |
+
if (name in _exclude or
|
644 |
+
(_members and name not in _members)):
|
645 |
+
continue
|
646 |
+
try:
|
647 |
+
doc_item = pydoc.getdoc(getattr(self._cls, name))
|
648 |
+
doc_list.append(
|
649 |
+
Parameter(name, '', splitlines_x(doc_item)))
|
650 |
+
except AttributeError:
|
651 |
+
pass # method doesn't exist
|
652 |
+
self[field] = doc_list
|
653 |
+
|
654 |
+
@property
|
655 |
+
def methods(self):
|
656 |
+
if self._cls is None:
|
657 |
+
return []
|
658 |
+
return [name for name, func in inspect.getmembers(self._cls)
|
659 |
+
if ((not name.startswith('_')
|
660 |
+
or name in self.extra_public_methods)
|
661 |
+
and isinstance(func, Callable)
|
662 |
+
and self._is_show_member(name))]
|
663 |
+
|
664 |
+
@property
|
665 |
+
def properties(self):
|
666 |
+
if self._cls is None:
|
667 |
+
return []
|
668 |
+
return [name for name, func in inspect.getmembers(self._cls)
|
669 |
+
if (not name.startswith('_') and
|
670 |
+
(func is None or isinstance(func, property) or
|
671 |
+
inspect.isdatadescriptor(func))
|
672 |
+
and self._is_show_member(name))]
|
673 |
+
|
674 |
+
def _is_show_member(self, name):
|
675 |
+
if self.show_inherited_members:
|
676 |
+
return True # show all class members
|
677 |
+
if name not in self._cls.__dict__:
|
678 |
+
return False # class member is inherited, we do not show it
|
679 |
+
return True
|
scipy/_lib/_finite_differences.py
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from numpy import arange, newaxis, hstack, prod, array
|
2 |
+
|
3 |
+
|
4 |
+
def _central_diff_weights(Np, ndiv=1):
|
5 |
+
"""
|
6 |
+
Return weights for an Np-point central derivative.
|
7 |
+
|
8 |
+
Assumes equally-spaced function points.
|
9 |
+
|
10 |
+
If weights are in the vector w, then
|
11 |
+
derivative is w[0] * f(x-ho*dx) + ... + w[-1] * f(x+h0*dx)
|
12 |
+
|
13 |
+
Parameters
|
14 |
+
----------
|
15 |
+
Np : int
|
16 |
+
Number of points for the central derivative.
|
17 |
+
ndiv : int, optional
|
18 |
+
Number of divisions. Default is 1.
|
19 |
+
|
20 |
+
Returns
|
21 |
+
-------
|
22 |
+
w : ndarray
|
23 |
+
Weights for an Np-point central derivative. Its size is `Np`.
|
24 |
+
|
25 |
+
Notes
|
26 |
+
-----
|
27 |
+
Can be inaccurate for a large number of points.
|
28 |
+
|
29 |
+
Examples
|
30 |
+
--------
|
31 |
+
We can calculate a derivative value of a function.
|
32 |
+
|
33 |
+
>>> def f(x):
|
34 |
+
... return 2 * x**2 + 3
|
35 |
+
>>> x = 3.0 # derivative point
|
36 |
+
>>> h = 0.1 # differential step
|
37 |
+
>>> Np = 3 # point number for central derivative
|
38 |
+
>>> weights = _central_diff_weights(Np) # weights for first derivative
|
39 |
+
>>> vals = [f(x + (i - Np/2) * h) for i in range(Np)]
|
40 |
+
>>> sum(w * v for (w, v) in zip(weights, vals))/h
|
41 |
+
11.79999999999998
|
42 |
+
|
43 |
+
This value is close to the analytical solution:
|
44 |
+
f'(x) = 4x, so f'(3) = 12
|
45 |
+
|
46 |
+
References
|
47 |
+
----------
|
48 |
+
.. [1] https://en.wikipedia.org/wiki/Finite_difference
|
49 |
+
|
50 |
+
"""
|
51 |
+
if Np < ndiv + 1:
|
52 |
+
raise ValueError(
|
53 |
+
"Number of points must be at least the derivative order + 1."
|
54 |
+
)
|
55 |
+
if Np % 2 == 0:
|
56 |
+
raise ValueError("The number of points must be odd.")
|
57 |
+
from scipy import linalg
|
58 |
+
|
59 |
+
ho = Np >> 1
|
60 |
+
x = arange(-ho, ho + 1.0)
|
61 |
+
x = x[:, newaxis]
|
62 |
+
X = x**0.0
|
63 |
+
for k in range(1, Np):
|
64 |
+
X = hstack([X, x**k])
|
65 |
+
w = prod(arange(1, ndiv + 1), axis=0) * linalg.inv(X)[ndiv]
|
66 |
+
return w
|
67 |
+
|
68 |
+
|
69 |
+
def _derivative(func, x0, dx=1.0, n=1, args=(), order=3):
|
70 |
+
"""
|
71 |
+
Find the nth derivative of a function at a point.
|
72 |
+
|
73 |
+
Given a function, use a central difference formula with spacing `dx` to
|
74 |
+
compute the nth derivative at `x0`.
|
75 |
+
|
76 |
+
Parameters
|
77 |
+
----------
|
78 |
+
func : function
|
79 |
+
Input function.
|
80 |
+
x0 : float
|
81 |
+
The point at which the nth derivative is found.
|
82 |
+
dx : float, optional
|
83 |
+
Spacing.
|
84 |
+
n : int, optional
|
85 |
+
Order of the derivative. Default is 1.
|
86 |
+
args : tuple, optional
|
87 |
+
Arguments
|
88 |
+
order : int, optional
|
89 |
+
Number of points to use, must be odd.
|
90 |
+
|
91 |
+
Notes
|
92 |
+
-----
|
93 |
+
Decreasing the step size too small can result in round-off error.
|
94 |
+
|
95 |
+
Examples
|
96 |
+
--------
|
97 |
+
>>> def f(x):
|
98 |
+
... return x**3 + x**2
|
99 |
+
>>> _derivative(f, 1.0, dx=1e-6)
|
100 |
+
4.9999999999217337
|
101 |
+
|
102 |
+
"""
|
103 |
+
if order < n + 1:
|
104 |
+
raise ValueError(
|
105 |
+
"'order' (the number of points used to compute the derivative), "
|
106 |
+
"must be at least the derivative order 'n' + 1."
|
107 |
+
)
|
108 |
+
if order % 2 == 0:
|
109 |
+
raise ValueError(
|
110 |
+
"'order' (the number of points used to compute the derivative) "
|
111 |
+
"must be odd."
|
112 |
+
)
|
113 |
+
# pre-computed for n=1 and 2 and low-order for speed.
|
114 |
+
if n == 1:
|
115 |
+
if order == 3:
|
116 |
+
weights = array([-1, 0, 1]) / 2.0
|
117 |
+
elif order == 5:
|
118 |
+
weights = array([1, -8, 0, 8, -1]) / 12.0
|
119 |
+
elif order == 7:
|
120 |
+
weights = array([-1, 9, -45, 0, 45, -9, 1]) / 60.0
|
121 |
+
elif order == 9:
|
122 |
+
weights = array([3, -32, 168, -672, 0, 672, -168, 32, -3]) / 840.0
|
123 |
+
else:
|
124 |
+
weights = _central_diff_weights(order, 1)
|
125 |
+
elif n == 2:
|
126 |
+
if order == 3:
|
127 |
+
weights = array([1, -2.0, 1])
|
128 |
+
elif order == 5:
|
129 |
+
weights = array([-1, 16, -30, 16, -1]) / 12.0
|
130 |
+
elif order == 7:
|
131 |
+
weights = array([2, -27, 270, -490, 270, -27, 2]) / 180.0
|
132 |
+
elif order == 9:
|
133 |
+
weights = (
|
134 |
+
array([-9, 128, -1008, 8064, -14350, 8064, -1008, 128, -9])
|
135 |
+
/ 5040.0
|
136 |
+
)
|
137 |
+
else:
|
138 |
+
weights = _central_diff_weights(order, 2)
|
139 |
+
else:
|
140 |
+
weights = _central_diff_weights(order, n)
|
141 |
+
val = 0.0
|
142 |
+
ho = order >> 1
|
143 |
+
for k in range(order):
|
144 |
+
val += weights[k] * func(x0 + (k - ho) * dx, *args)
|
145 |
+
return val / prod((dx,) * n, axis=0)
|
scipy/_lib/_fpumode.cp39-win_amd64.dll.a
ADDED
Binary file (1.55 kB). View file
|
|
scipy/_lib/_fpumode.cp39-win_amd64.pyd
ADDED
Binary file (15.9 kB). View file
|
|
scipy/_lib/_gcutils.py
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Module for testing automatic garbage collection of objects
|
3 |
+
|
4 |
+
.. autosummary::
|
5 |
+
:toctree: generated/
|
6 |
+
|
7 |
+
set_gc_state - enable or disable garbage collection
|
8 |
+
gc_state - context manager for given state of garbage collector
|
9 |
+
assert_deallocated - context manager to check for circular references on object
|
10 |
+
|
11 |
+
"""
|
12 |
+
import weakref
|
13 |
+
import gc
|
14 |
+
|
15 |
+
from contextlib import contextmanager
|
16 |
+
from platform import python_implementation
|
17 |
+
|
18 |
+
__all__ = ['set_gc_state', 'gc_state', 'assert_deallocated']
|
19 |
+
|
20 |
+
|
21 |
+
IS_PYPY = python_implementation() == 'PyPy'
|
22 |
+
|
23 |
+
|
24 |
+
class ReferenceError(AssertionError):
|
25 |
+
pass
|
26 |
+
|
27 |
+
|
28 |
+
def set_gc_state(state):
|
29 |
+
""" Set status of garbage collector """
|
30 |
+
if gc.isenabled() == state:
|
31 |
+
return
|
32 |
+
if state:
|
33 |
+
gc.enable()
|
34 |
+
else:
|
35 |
+
gc.disable()
|
36 |
+
|
37 |
+
|
38 |
+
@contextmanager
|
39 |
+
def gc_state(state):
|
40 |
+
""" Context manager to set state of garbage collector to `state`
|
41 |
+
|
42 |
+
Parameters
|
43 |
+
----------
|
44 |
+
state : bool
|
45 |
+
True for gc enabled, False for disabled
|
46 |
+
|
47 |
+
Examples
|
48 |
+
--------
|
49 |
+
>>> with gc_state(False):
|
50 |
+
... assert not gc.isenabled()
|
51 |
+
>>> with gc_state(True):
|
52 |
+
... assert gc.isenabled()
|
53 |
+
"""
|
54 |
+
orig_state = gc.isenabled()
|
55 |
+
set_gc_state(state)
|
56 |
+
yield
|
57 |
+
set_gc_state(orig_state)
|
58 |
+
|
59 |
+
|
60 |
+
@contextmanager
|
61 |
+
def assert_deallocated(func, *args, **kwargs):
|
62 |
+
"""Context manager to check that object is deallocated
|
63 |
+
|
64 |
+
This is useful for checking that an object can be freed directly by
|
65 |
+
reference counting, without requiring gc to break reference cycles.
|
66 |
+
GC is disabled inside the context manager.
|
67 |
+
|
68 |
+
This check is not available on PyPy.
|
69 |
+
|
70 |
+
Parameters
|
71 |
+
----------
|
72 |
+
func : callable
|
73 |
+
Callable to create object to check
|
74 |
+
\\*args : sequence
|
75 |
+
positional arguments to `func` in order to create object to check
|
76 |
+
\\*\\*kwargs : dict
|
77 |
+
keyword arguments to `func` in order to create object to check
|
78 |
+
|
79 |
+
Examples
|
80 |
+
--------
|
81 |
+
>>> class C: pass
|
82 |
+
>>> with assert_deallocated(C) as c:
|
83 |
+
... # do something
|
84 |
+
... del c
|
85 |
+
|
86 |
+
>>> class C:
|
87 |
+
... def __init__(self):
|
88 |
+
... self._circular = self # Make circular reference
|
89 |
+
>>> with assert_deallocated(C) as c: #doctest: +IGNORE_EXCEPTION_DETAIL
|
90 |
+
... # do something
|
91 |
+
... del c
|
92 |
+
Traceback (most recent call last):
|
93 |
+
...
|
94 |
+
ReferenceError: Remaining reference(s) to object
|
95 |
+
"""
|
96 |
+
if IS_PYPY:
|
97 |
+
raise RuntimeError("assert_deallocated is unavailable on PyPy")
|
98 |
+
|
99 |
+
with gc_state(False):
|
100 |
+
obj = func(*args, **kwargs)
|
101 |
+
ref = weakref.ref(obj)
|
102 |
+
yield obj
|
103 |
+
del obj
|
104 |
+
if ref() is not None:
|
105 |
+
raise ReferenceError("Remaining reference(s) to object")
|
scipy/_lib/_pep440.py
ADDED
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Utility to compare pep440 compatible version strings.
|
2 |
+
|
3 |
+
The LooseVersion and StrictVersion classes that distutils provides don't
|
4 |
+
work; they don't recognize anything like alpha/beta/rc/dev versions.
|
5 |
+
"""
|
6 |
+
|
7 |
+
# Copyright (c) Donald Stufft and individual contributors.
|
8 |
+
# All rights reserved.
|
9 |
+
|
10 |
+
# Redistribution and use in source and binary forms, with or without
|
11 |
+
# modification, are permitted provided that the following conditions are met:
|
12 |
+
|
13 |
+
# 1. Redistributions of source code must retain the above copyright notice,
|
14 |
+
# this list of conditions and the following disclaimer.
|
15 |
+
|
16 |
+
# 2. Redistributions in binary form must reproduce the above copyright
|
17 |
+
# notice, this list of conditions and the following disclaimer in the
|
18 |
+
# documentation and/or other materials provided with the distribution.
|
19 |
+
|
20 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
23 |
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
24 |
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25 |
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26 |
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
27 |
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
28 |
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
29 |
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
30 |
+
# POSSIBILITY OF SUCH DAMAGE.
|
31 |
+
|
32 |
+
import collections
|
33 |
+
import itertools
|
34 |
+
import re
|
35 |
+
|
36 |
+
|
37 |
+
__all__ = [
|
38 |
+
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN",
|
39 |
+
]
|
40 |
+
|
41 |
+
|
42 |
+
# BEGIN packaging/_structures.py
|
43 |
+
|
44 |
+
|
45 |
+
class Infinity:
|
46 |
+
def __repr__(self):
|
47 |
+
return "Infinity"
|
48 |
+
|
49 |
+
def __hash__(self):
|
50 |
+
return hash(repr(self))
|
51 |
+
|
52 |
+
def __lt__(self, other):
|
53 |
+
return False
|
54 |
+
|
55 |
+
def __le__(self, other):
|
56 |
+
return False
|
57 |
+
|
58 |
+
def __eq__(self, other):
|
59 |
+
return isinstance(other, self.__class__)
|
60 |
+
|
61 |
+
def __ne__(self, other):
|
62 |
+
return not isinstance(other, self.__class__)
|
63 |
+
|
64 |
+
def __gt__(self, other):
|
65 |
+
return True
|
66 |
+
|
67 |
+
def __ge__(self, other):
|
68 |
+
return True
|
69 |
+
|
70 |
+
def __neg__(self):
|
71 |
+
return NegativeInfinity
|
72 |
+
|
73 |
+
|
74 |
+
Infinity = Infinity()
|
75 |
+
|
76 |
+
|
77 |
+
class NegativeInfinity:
|
78 |
+
def __repr__(self):
|
79 |
+
return "-Infinity"
|
80 |
+
|
81 |
+
def __hash__(self):
|
82 |
+
return hash(repr(self))
|
83 |
+
|
84 |
+
def __lt__(self, other):
|
85 |
+
return True
|
86 |
+
|
87 |
+
def __le__(self, other):
|
88 |
+
return True
|
89 |
+
|
90 |
+
def __eq__(self, other):
|
91 |
+
return isinstance(other, self.__class__)
|
92 |
+
|
93 |
+
def __ne__(self, other):
|
94 |
+
return not isinstance(other, self.__class__)
|
95 |
+
|
96 |
+
def __gt__(self, other):
|
97 |
+
return False
|
98 |
+
|
99 |
+
def __ge__(self, other):
|
100 |
+
return False
|
101 |
+
|
102 |
+
def __neg__(self):
|
103 |
+
return Infinity
|
104 |
+
|
105 |
+
|
106 |
+
# BEGIN packaging/version.py
|
107 |
+
|
108 |
+
|
109 |
+
NegativeInfinity = NegativeInfinity()
|
110 |
+
|
111 |
+
_Version = collections.namedtuple(
|
112 |
+
"_Version",
|
113 |
+
["epoch", "release", "dev", "pre", "post", "local"],
|
114 |
+
)
|
115 |
+
|
116 |
+
|
117 |
+
def parse(version):
|
118 |
+
"""
|
119 |
+
Parse the given version string and return either a :class:`Version` object
|
120 |
+
or a :class:`LegacyVersion` object depending on if the given version is
|
121 |
+
a valid PEP 440 version or a legacy version.
|
122 |
+
"""
|
123 |
+
try:
|
124 |
+
return Version(version)
|
125 |
+
except InvalidVersion:
|
126 |
+
return LegacyVersion(version)
|
127 |
+
|
128 |
+
|
129 |
+
class InvalidVersion(ValueError):
|
130 |
+
"""
|
131 |
+
An invalid version was found, users should refer to PEP 440.
|
132 |
+
"""
|
133 |
+
|
134 |
+
|
135 |
+
class _BaseVersion:
|
136 |
+
|
137 |
+
def __hash__(self):
|
138 |
+
return hash(self._key)
|
139 |
+
|
140 |
+
def __lt__(self, other):
|
141 |
+
return self._compare(other, lambda s, o: s < o)
|
142 |
+
|
143 |
+
def __le__(self, other):
|
144 |
+
return self._compare(other, lambda s, o: s <= o)
|
145 |
+
|
146 |
+
def __eq__(self, other):
|
147 |
+
return self._compare(other, lambda s, o: s == o)
|
148 |
+
|
149 |
+
def __ge__(self, other):
|
150 |
+
return self._compare(other, lambda s, o: s >= o)
|
151 |
+
|
152 |
+
def __gt__(self, other):
|
153 |
+
return self._compare(other, lambda s, o: s > o)
|
154 |
+
|
155 |
+
def __ne__(self, other):
|
156 |
+
return self._compare(other, lambda s, o: s != o)
|
157 |
+
|
158 |
+
def _compare(self, other, method):
|
159 |
+
if not isinstance(other, _BaseVersion):
|
160 |
+
return NotImplemented
|
161 |
+
|
162 |
+
return method(self._key, other._key)
|
163 |
+
|
164 |
+
|
165 |
+
class LegacyVersion(_BaseVersion):
|
166 |
+
|
167 |
+
def __init__(self, version):
|
168 |
+
self._version = str(version)
|
169 |
+
self._key = _legacy_cmpkey(self._version)
|
170 |
+
|
171 |
+
def __str__(self):
|
172 |
+
return self._version
|
173 |
+
|
174 |
+
def __repr__(self):
|
175 |
+
return f"<LegacyVersion({repr(str(self))})>"
|
176 |
+
|
177 |
+
@property
|
178 |
+
def public(self):
|
179 |
+
return self._version
|
180 |
+
|
181 |
+
@property
|
182 |
+
def base_version(self):
|
183 |
+
return self._version
|
184 |
+
|
185 |
+
@property
|
186 |
+
def local(self):
|
187 |
+
return None
|
188 |
+
|
189 |
+
@property
|
190 |
+
def is_prerelease(self):
|
191 |
+
return False
|
192 |
+
|
193 |
+
@property
|
194 |
+
def is_postrelease(self):
|
195 |
+
return False
|
196 |
+
|
197 |
+
|
198 |
+
_legacy_version_component_re = re.compile(
|
199 |
+
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
|
200 |
+
)
|
201 |
+
|
202 |
+
_legacy_version_replacement_map = {
|
203 |
+
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
|
204 |
+
}
|
205 |
+
|
206 |
+
|
207 |
+
def _parse_version_parts(s):
|
208 |
+
for part in _legacy_version_component_re.split(s):
|
209 |
+
part = _legacy_version_replacement_map.get(part, part)
|
210 |
+
|
211 |
+
if not part or part == ".":
|
212 |
+
continue
|
213 |
+
|
214 |
+
if part[:1] in "0123456789":
|
215 |
+
# pad for numeric comparison
|
216 |
+
yield part.zfill(8)
|
217 |
+
else:
|
218 |
+
yield "*" + part
|
219 |
+
|
220 |
+
# ensure that alpha/beta/candidate are before final
|
221 |
+
yield "*final"
|
222 |
+
|
223 |
+
|
224 |
+
def _legacy_cmpkey(version):
|
225 |
+
# We hardcode an epoch of -1 here. A PEP 440 version can only have an epoch
|
226 |
+
# greater than or equal to 0. This will effectively put the LegacyVersion,
|
227 |
+
# which uses the defacto standard originally implemented by setuptools,
|
228 |
+
# as before all PEP 440 versions.
|
229 |
+
epoch = -1
|
230 |
+
|
231 |
+
# This scheme is taken from pkg_resources.parse_version setuptools prior to
|
232 |
+
# its adoption of the packaging library.
|
233 |
+
parts = []
|
234 |
+
for part in _parse_version_parts(version.lower()):
|
235 |
+
if part.startswith("*"):
|
236 |
+
# remove "-" before a prerelease tag
|
237 |
+
if part < "*final":
|
238 |
+
while parts and parts[-1] == "*final-":
|
239 |
+
parts.pop()
|
240 |
+
|
241 |
+
# remove trailing zeros from each series of numeric parts
|
242 |
+
while parts and parts[-1] == "00000000":
|
243 |
+
parts.pop()
|
244 |
+
|
245 |
+
parts.append(part)
|
246 |
+
parts = tuple(parts)
|
247 |
+
|
248 |
+
return epoch, parts
|
249 |
+
|
250 |
+
|
251 |
+
# Deliberately not anchored to the start and end of the string, to make it
|
252 |
+
# easier for 3rd party code to reuse
|
253 |
+
VERSION_PATTERN = r"""
|
254 |
+
v?
|
255 |
+
(?:
|
256 |
+
(?:(?P<epoch>[0-9]+)!)? # epoch
|
257 |
+
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
258 |
+
(?P<pre> # pre-release
|
259 |
+
[-_\.]?
|
260 |
+
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
261 |
+
[-_\.]?
|
262 |
+
(?P<pre_n>[0-9]+)?
|
263 |
+
)?
|
264 |
+
(?P<post> # post release
|
265 |
+
(?:-(?P<post_n1>[0-9]+))
|
266 |
+
|
|
267 |
+
(?:
|
268 |
+
[-_\.]?
|
269 |
+
(?P<post_l>post|rev|r)
|
270 |
+
[-_\.]?
|
271 |
+
(?P<post_n2>[0-9]+)?
|
272 |
+
)
|
273 |
+
)?
|
274 |
+
(?P<dev> # dev release
|
275 |
+
[-_\.]?
|
276 |
+
(?P<dev_l>dev)
|
277 |
+
[-_\.]?
|
278 |
+
(?P<dev_n>[0-9]+)?
|
279 |
+
)?
|
280 |
+
)
|
281 |
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
282 |
+
"""
|
283 |
+
|
284 |
+
|
285 |
+
class Version(_BaseVersion):
|
286 |
+
|
287 |
+
_regex = re.compile(
|
288 |
+
r"^\s*" + VERSION_PATTERN + r"\s*$",
|
289 |
+
re.VERBOSE | re.IGNORECASE,
|
290 |
+
)
|
291 |
+
|
292 |
+
def __init__(self, version):
|
293 |
+
# Validate the version and parse it into pieces
|
294 |
+
match = self._regex.search(version)
|
295 |
+
if not match:
|
296 |
+
raise InvalidVersion(f"Invalid version: '{version}'")
|
297 |
+
|
298 |
+
# Store the parsed out pieces of the version
|
299 |
+
self._version = _Version(
|
300 |
+
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
|
301 |
+
release=tuple(int(i) for i in match.group("release").split(".")),
|
302 |
+
pre=_parse_letter_version(
|
303 |
+
match.group("pre_l"),
|
304 |
+
match.group("pre_n"),
|
305 |
+
),
|
306 |
+
post=_parse_letter_version(
|
307 |
+
match.group("post_l"),
|
308 |
+
match.group("post_n1") or match.group("post_n2"),
|
309 |
+
),
|
310 |
+
dev=_parse_letter_version(
|
311 |
+
match.group("dev_l"),
|
312 |
+
match.group("dev_n"),
|
313 |
+
),
|
314 |
+
local=_parse_local_version(match.group("local")),
|
315 |
+
)
|
316 |
+
|
317 |
+
# Generate a key which will be used for sorting
|
318 |
+
self._key = _cmpkey(
|
319 |
+
self._version.epoch,
|
320 |
+
self._version.release,
|
321 |
+
self._version.pre,
|
322 |
+
self._version.post,
|
323 |
+
self._version.dev,
|
324 |
+
self._version.local,
|
325 |
+
)
|
326 |
+
|
327 |
+
def __repr__(self):
|
328 |
+
return f"<Version({repr(str(self))})>"
|
329 |
+
|
330 |
+
def __str__(self):
|
331 |
+
parts = []
|
332 |
+
|
333 |
+
# Epoch
|
334 |
+
if self._version.epoch != 0:
|
335 |
+
parts.append(f"{self._version.epoch}!")
|
336 |
+
|
337 |
+
# Release segment
|
338 |
+
parts.append(".".join(str(x) for x in self._version.release))
|
339 |
+
|
340 |
+
# Pre-release
|
341 |
+
if self._version.pre is not None:
|
342 |
+
parts.append("".join(str(x) for x in self._version.pre))
|
343 |
+
|
344 |
+
# Post-release
|
345 |
+
if self._version.post is not None:
|
346 |
+
parts.append(f".post{self._version.post[1]}")
|
347 |
+
|
348 |
+
# Development release
|
349 |
+
if self._version.dev is not None:
|
350 |
+
parts.append(f".dev{self._version.dev[1]}")
|
351 |
+
|
352 |
+
# Local version segment
|
353 |
+
if self._version.local is not None:
|
354 |
+
parts.append(
|
355 |
+
"+{}".format(".".join(str(x) for x in self._version.local))
|
356 |
+
)
|
357 |
+
|
358 |
+
return "".join(parts)
|
359 |
+
|
360 |
+
@property
|
361 |
+
def public(self):
|
362 |
+
return str(self).split("+", 1)[0]
|
363 |
+
|
364 |
+
@property
|
365 |
+
def base_version(self):
|
366 |
+
parts = []
|
367 |
+
|
368 |
+
# Epoch
|
369 |
+
if self._version.epoch != 0:
|
370 |
+
parts.append(f"{self._version.epoch}!")
|
371 |
+
|
372 |
+
# Release segment
|
373 |
+
parts.append(".".join(str(x) for x in self._version.release))
|
374 |
+
|
375 |
+
return "".join(parts)
|
376 |
+
|
377 |
+
@property
|
378 |
+
def local(self):
|
379 |
+
version_string = str(self)
|
380 |
+
if "+" in version_string:
|
381 |
+
return version_string.split("+", 1)[1]
|
382 |
+
|
383 |
+
@property
|
384 |
+
def is_prerelease(self):
|
385 |
+
return bool(self._version.dev or self._version.pre)
|
386 |
+
|
387 |
+
@property
|
388 |
+
def is_postrelease(self):
|
389 |
+
return bool(self._version.post)
|
390 |
+
|
391 |
+
|
392 |
+
def _parse_letter_version(letter, number):
|
393 |
+
if letter:
|
394 |
+
# We assume there is an implicit 0 in a pre-release if there is
|
395 |
+
# no numeral associated with it.
|
396 |
+
if number is None:
|
397 |
+
number = 0
|
398 |
+
|
399 |
+
# We normalize any letters to their lower-case form
|
400 |
+
letter = letter.lower()
|
401 |
+
|
402 |
+
# We consider some words to be alternate spellings of other words and
|
403 |
+
# in those cases we want to normalize the spellings to our preferred
|
404 |
+
# spelling.
|
405 |
+
if letter == "alpha":
|
406 |
+
letter = "a"
|
407 |
+
elif letter == "beta":
|
408 |
+
letter = "b"
|
409 |
+
elif letter in ["c", "pre", "preview"]:
|
410 |
+
letter = "rc"
|
411 |
+
elif letter in ["rev", "r"]:
|
412 |
+
letter = "post"
|
413 |
+
|
414 |
+
return letter, int(number)
|
415 |
+
if not letter and number:
|
416 |
+
# We assume that if we are given a number but not given a letter,
|
417 |
+
# then this is using the implicit post release syntax (e.g., 1.0-1)
|
418 |
+
letter = "post"
|
419 |
+
|
420 |
+
return letter, int(number)
|
421 |
+
|
422 |
+
|
423 |
+
_local_version_seperators = re.compile(r"[\._-]")
|
424 |
+
|
425 |
+
|
426 |
+
def _parse_local_version(local):
|
427 |
+
"""
|
428 |
+
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
429 |
+
"""
|
430 |
+
if local is not None:
|
431 |
+
return tuple(
|
432 |
+
part.lower() if not part.isdigit() else int(part)
|
433 |
+
for part in _local_version_seperators.split(local)
|
434 |
+
)
|
435 |
+
|
436 |
+
|
437 |
+
def _cmpkey(epoch, release, pre, post, dev, local):
|
438 |
+
# When we compare a release version, we want to compare it with all of the
|
439 |
+
# trailing zeros removed. So we'll use a reverse the list, drop all the now
|
440 |
+
# leading zeros until we come to something non-zero, then take the rest,
|
441 |
+
# re-reverse it back into the correct order, and make it a tuple and use
|
442 |
+
# that for our sorting key.
|
443 |
+
release = tuple(
|
444 |
+
reversed(list(
|
445 |
+
itertools.dropwhile(
|
446 |
+
lambda x: x == 0,
|
447 |
+
reversed(release),
|
448 |
+
)
|
449 |
+
))
|
450 |
+
)
|
451 |
+
|
452 |
+
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
|
453 |
+
# We'll do this by abusing the pre-segment, but we _only_ want to do this
|
454 |
+
# if there is no pre- or a post-segment. If we have one of those, then
|
455 |
+
# the normal sorting rules will handle this case correctly.
|
456 |
+
if pre is None and post is None and dev is not None:
|
457 |
+
pre = -Infinity
|
458 |
+
# Versions without a pre-release (except as noted above) should sort after
|
459 |
+
# those with one.
|
460 |
+
elif pre is None:
|
461 |
+
pre = Infinity
|
462 |
+
|
463 |
+
# Versions without a post-segment should sort before those with one.
|
464 |
+
if post is None:
|
465 |
+
post = -Infinity
|
466 |
+
|
467 |
+
# Versions without a development segment should sort after those with one.
|
468 |
+
if dev is None:
|
469 |
+
dev = Infinity
|
470 |
+
|
471 |
+
if local is None:
|
472 |
+
# Versions without a local segment should sort before those with one.
|
473 |
+
local = -Infinity
|
474 |
+
else:
|
475 |
+
# Versions with a local segment need that segment parsed to implement
|
476 |
+
# the sorting rules in PEP440.
|
477 |
+
# - Alphanumeric segments sort before numeric segments
|
478 |
+
# - Alphanumeric segments sort lexicographically
|
479 |
+
# - Numeric segments sort numerically
|
480 |
+
# - Shorter versions sort before longer versions when the prefixes
|
481 |
+
# match exactly
|
482 |
+
local = tuple(
|
483 |
+
(i, "") if isinstance(i, int) else (-Infinity, i)
|
484 |
+
for i in local
|
485 |
+
)
|
486 |
+
|
487 |
+
return epoch, release, pre, post, dev, local
|
scipy/_lib/_test_ccallback.cp39-win_amd64.dll.a
ADDED
Binary file (1.64 kB). View file
|
|
scipy/_lib/_test_ccallback.cp39-win_amd64.pyd
ADDED
Binary file (52.2 kB). View file
|
|
scipy/_lib/_test_deprecation_call.cp39-win_amd64.dll.a
ADDED
Binary file (1.72 kB). View file
|
|
scipy/_lib/_test_deprecation_call.cp39-win_amd64.pyd
ADDED
Binary file (34.8 kB). View file
|
|
scipy/_lib/_test_deprecation_def.cp39-win_amd64.dll.a
ADDED
Binary file (1.71 kB). View file
|
|
scipy/_lib/_test_deprecation_def.cp39-win_amd64.pyd
ADDED
Binary file (27.1 kB). View file
|
|
scipy/_lib/_testutils.py
ADDED
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Generic test utilities.
|
3 |
+
|
4 |
+
"""
|
5 |
+
|
6 |
+
import os
|
7 |
+
import re
|
8 |
+
import sys
|
9 |
+
import numpy as np
|
10 |
+
import inspect
|
11 |
+
import sysconfig
|
12 |
+
|
13 |
+
|
14 |
+
__all__ = ['PytestTester', 'check_free_memory', '_TestPythranFunc', 'IS_MUSL']
|
15 |
+
|
16 |
+
|
17 |
+
IS_MUSL = False
|
18 |
+
try:
|
19 |
+
# Note that packaging is not a dependency, hence we need this try-except:
|
20 |
+
from packaging.tags import sys_tags
|
21 |
+
_tags = list(sys_tags())
|
22 |
+
if 'musllinux' in _tags[0].platform:
|
23 |
+
IS_MUSL = True
|
24 |
+
except ImportError:
|
25 |
+
# fallback to sysconfig (might be flaky)
|
26 |
+
v = sysconfig.get_config_var('HOST_GNU_TYPE') or ''
|
27 |
+
if 'musl' in v:
|
28 |
+
IS_MUSL = True
|
29 |
+
|
30 |
+
|
31 |
+
class FPUModeChangeWarning(RuntimeWarning):
|
32 |
+
"""Warning about FPU mode change"""
|
33 |
+
pass
|
34 |
+
|
35 |
+
|
36 |
+
class PytestTester:
|
37 |
+
"""
|
38 |
+
Run tests for this namespace
|
39 |
+
|
40 |
+
``scipy.test()`` runs tests for all of SciPy, with the default settings.
|
41 |
+
When used from a submodule (e.g., ``scipy.cluster.test()``, only the tests
|
42 |
+
for that namespace are run.
|
43 |
+
|
44 |
+
Parameters
|
45 |
+
----------
|
46 |
+
label : {'fast', 'full'}, optional
|
47 |
+
Whether to run only the fast tests, or also those marked as slow.
|
48 |
+
Default is 'fast'.
|
49 |
+
verbose : int, optional
|
50 |
+
Test output verbosity. Default is 1.
|
51 |
+
extra_argv : list, optional
|
52 |
+
Arguments to pass through to Pytest.
|
53 |
+
doctests : bool, optional
|
54 |
+
Whether to run doctests or not. Default is False.
|
55 |
+
coverage : bool, optional
|
56 |
+
Whether to run tests with code coverage measurements enabled.
|
57 |
+
Default is False.
|
58 |
+
tests : list of str, optional
|
59 |
+
List of module names to run tests for. By default, uses the module
|
60 |
+
from which the ``test`` function is called.
|
61 |
+
parallel : int, optional
|
62 |
+
Run tests in parallel with pytest-xdist, if number given is larger than
|
63 |
+
1. Default is 1.
|
64 |
+
|
65 |
+
"""
|
66 |
+
def __init__(self, module_name):
|
67 |
+
self.module_name = module_name
|
68 |
+
|
69 |
+
def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False,
|
70 |
+
coverage=False, tests=None, parallel=None):
|
71 |
+
import pytest
|
72 |
+
|
73 |
+
module = sys.modules[self.module_name]
|
74 |
+
module_path = os.path.abspath(module.__path__[0])
|
75 |
+
|
76 |
+
pytest_args = ['--showlocals', '--tb=short']
|
77 |
+
|
78 |
+
if doctests:
|
79 |
+
raise ValueError("Doctests not supported")
|
80 |
+
|
81 |
+
if extra_argv:
|
82 |
+
pytest_args += list(extra_argv)
|
83 |
+
|
84 |
+
if verbose and int(verbose) > 1:
|
85 |
+
pytest_args += ["-" + "v"*(int(verbose)-1)]
|
86 |
+
|
87 |
+
if coverage:
|
88 |
+
pytest_args += ["--cov=" + module_path]
|
89 |
+
|
90 |
+
if label == "fast":
|
91 |
+
pytest_args += ["-m", "not slow"]
|
92 |
+
elif label != "full":
|
93 |
+
pytest_args += ["-m", label]
|
94 |
+
|
95 |
+
if tests is None:
|
96 |
+
tests = [self.module_name]
|
97 |
+
|
98 |
+
if parallel is not None and parallel > 1:
|
99 |
+
if _pytest_has_xdist():
|
100 |
+
pytest_args += ['-n', str(parallel)]
|
101 |
+
else:
|
102 |
+
import warnings
|
103 |
+
warnings.warn('Could not run tests in parallel because '
|
104 |
+
'pytest-xdist plugin is not available.',
|
105 |
+
stacklevel=2)
|
106 |
+
|
107 |
+
pytest_args += ['--pyargs'] + list(tests)
|
108 |
+
|
109 |
+
try:
|
110 |
+
code = pytest.main(pytest_args)
|
111 |
+
except SystemExit as exc:
|
112 |
+
code = exc.code
|
113 |
+
|
114 |
+
return (code == 0)
|
115 |
+
|
116 |
+
|
117 |
+
class _TestPythranFunc:
|
118 |
+
'''
|
119 |
+
These are situations that can be tested in our pythran tests:
|
120 |
+
- A function with multiple array arguments and then
|
121 |
+
other positional and keyword arguments.
|
122 |
+
- A function with array-like keywords (e.g. `def somefunc(x0, x1=None)`.
|
123 |
+
Note: list/tuple input is not yet tested!
|
124 |
+
|
125 |
+
`self.arguments`: A dictionary which key is the index of the argument,
|
126 |
+
value is tuple(array value, all supported dtypes)
|
127 |
+
`self.partialfunc`: A function used to freeze some non-array argument
|
128 |
+
that of no interests in the original function
|
129 |
+
'''
|
130 |
+
ALL_INTEGER = [np.int8, np.int16, np.int32, np.int64, np.intc, np.intp]
|
131 |
+
ALL_FLOAT = [np.float32, np.float64]
|
132 |
+
ALL_COMPLEX = [np.complex64, np.complex128]
|
133 |
+
|
134 |
+
def setup_method(self):
|
135 |
+
self.arguments = {}
|
136 |
+
self.partialfunc = None
|
137 |
+
self.expected = None
|
138 |
+
|
139 |
+
def get_optional_args(self, func):
|
140 |
+
# get optional arguments with its default value,
|
141 |
+
# used for testing keywords
|
142 |
+
signature = inspect.signature(func)
|
143 |
+
optional_args = {}
|
144 |
+
for k, v in signature.parameters.items():
|
145 |
+
if v.default is not inspect.Parameter.empty:
|
146 |
+
optional_args[k] = v.default
|
147 |
+
return optional_args
|
148 |
+
|
149 |
+
def get_max_dtype_list_length(self):
|
150 |
+
# get the max supported dtypes list length in all arguments
|
151 |
+
max_len = 0
|
152 |
+
for arg_idx in self.arguments:
|
153 |
+
cur_len = len(self.arguments[arg_idx][1])
|
154 |
+
if cur_len > max_len:
|
155 |
+
max_len = cur_len
|
156 |
+
return max_len
|
157 |
+
|
158 |
+
def get_dtype(self, dtype_list, dtype_idx):
|
159 |
+
# get the dtype from dtype_list via index
|
160 |
+
# if the index is out of range, then return the last dtype
|
161 |
+
if dtype_idx > len(dtype_list)-1:
|
162 |
+
return dtype_list[-1]
|
163 |
+
else:
|
164 |
+
return dtype_list[dtype_idx]
|
165 |
+
|
166 |
+
def test_all_dtypes(self):
|
167 |
+
for type_idx in range(self.get_max_dtype_list_length()):
|
168 |
+
args_array = []
|
169 |
+
for arg_idx in self.arguments:
|
170 |
+
new_dtype = self.get_dtype(self.arguments[arg_idx][1],
|
171 |
+
type_idx)
|
172 |
+
args_array.append(self.arguments[arg_idx][0].astype(new_dtype))
|
173 |
+
self.pythranfunc(*args_array)
|
174 |
+
|
175 |
+
def test_views(self):
|
176 |
+
args_array = []
|
177 |
+
for arg_idx in self.arguments:
|
178 |
+
args_array.append(self.arguments[arg_idx][0][::-1][::-1])
|
179 |
+
self.pythranfunc(*args_array)
|
180 |
+
|
181 |
+
def test_strided(self):
|
182 |
+
args_array = []
|
183 |
+
for arg_idx in self.arguments:
|
184 |
+
args_array.append(np.repeat(self.arguments[arg_idx][0],
|
185 |
+
2, axis=0)[::2])
|
186 |
+
self.pythranfunc(*args_array)
|
187 |
+
|
188 |
+
|
189 |
+
def _pytest_has_xdist():
|
190 |
+
"""
|
191 |
+
Check if the pytest-xdist plugin is installed, providing parallel tests
|
192 |
+
"""
|
193 |
+
# Check xdist exists without importing, otherwise pytests emits warnings
|
194 |
+
from importlib.util import find_spec
|
195 |
+
return find_spec('xdist') is not None
|
196 |
+
|
197 |
+
|
198 |
+
def check_free_memory(free_mb):
|
199 |
+
"""
|
200 |
+
Check *free_mb* of memory is available, otherwise do pytest.skip
|
201 |
+
"""
|
202 |
+
import pytest
|
203 |
+
|
204 |
+
try:
|
205 |
+
mem_free = _parse_size(os.environ['SCIPY_AVAILABLE_MEM'])
|
206 |
+
msg = '{} MB memory required, but environment SCIPY_AVAILABLE_MEM={}'.format(
|
207 |
+
free_mb, os.environ['SCIPY_AVAILABLE_MEM'])
|
208 |
+
except KeyError:
|
209 |
+
mem_free = _get_mem_available()
|
210 |
+
if mem_free is None:
|
211 |
+
pytest.skip("Could not determine available memory; set SCIPY_AVAILABLE_MEM "
|
212 |
+
"variable to free memory in MB to run the test.")
|
213 |
+
msg = f'{free_mb} MB memory required, but {mem_free/1e6} MB available'
|
214 |
+
|
215 |
+
if mem_free < free_mb * 1e6:
|
216 |
+
pytest.skip(msg)
|
217 |
+
|
218 |
+
|
219 |
+
def _parse_size(size_str):
|
220 |
+
suffixes = {'': 1e6,
|
221 |
+
'b': 1.0,
|
222 |
+
'k': 1e3, 'M': 1e6, 'G': 1e9, 'T': 1e12,
|
223 |
+
'kb': 1e3, 'Mb': 1e6, 'Gb': 1e9, 'Tb': 1e12,
|
224 |
+
'kib': 1024.0, 'Mib': 1024.0**2, 'Gib': 1024.0**3, 'Tib': 1024.0**4}
|
225 |
+
m = re.match(r'^\s*(\d+)\s*({})\s*$'.format('|'.join(suffixes.keys())),
|
226 |
+
size_str,
|
227 |
+
re.I)
|
228 |
+
if not m or m.group(2) not in suffixes:
|
229 |
+
raise ValueError("Invalid size string")
|
230 |
+
|
231 |
+
return float(m.group(1)) * suffixes[m.group(2)]
|
232 |
+
|
233 |
+
|
234 |
+
def _get_mem_available():
|
235 |
+
"""
|
236 |
+
Get information about memory available, not counting swap.
|
237 |
+
"""
|
238 |
+
try:
|
239 |
+
import psutil
|
240 |
+
return psutil.virtual_memory().available
|
241 |
+
except (ImportError, AttributeError):
|
242 |
+
pass
|
243 |
+
|
244 |
+
if sys.platform.startswith('linux'):
|
245 |
+
info = {}
|
246 |
+
with open('/proc/meminfo') as f:
|
247 |
+
for line in f:
|
248 |
+
p = line.split()
|
249 |
+
info[p[0].strip(':').lower()] = float(p[1]) * 1e3
|
250 |
+
|
251 |
+
if 'memavailable' in info:
|
252 |
+
# Linux >= 3.14
|
253 |
+
return info['memavailable']
|
254 |
+
else:
|
255 |
+
return info['memfree'] + info['cached']
|
256 |
+
|
257 |
+
return None
|
scipy/_lib/_threadsafety.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import threading
|
2 |
+
|
3 |
+
import scipy._lib.decorator
|
4 |
+
|
5 |
+
|
6 |
+
__all__ = ['ReentrancyError', 'ReentrancyLock', 'non_reentrant']
|
7 |
+
|
8 |
+
|
9 |
+
class ReentrancyError(RuntimeError):
|
10 |
+
pass
|
11 |
+
|
12 |
+
|
13 |
+
class ReentrancyLock:
|
14 |
+
"""
|
15 |
+
Threading lock that raises an exception for reentrant calls.
|
16 |
+
|
17 |
+
Calls from different threads are serialized, and nested calls from the
|
18 |
+
same thread result to an error.
|
19 |
+
|
20 |
+
The object can be used as a context manager or to decorate functions
|
21 |
+
via the decorate() method.
|
22 |
+
|
23 |
+
"""
|
24 |
+
|
25 |
+
def __init__(self, err_msg):
|
26 |
+
self._rlock = threading.RLock()
|
27 |
+
self._entered = False
|
28 |
+
self._err_msg = err_msg
|
29 |
+
|
30 |
+
def __enter__(self):
|
31 |
+
self._rlock.acquire()
|
32 |
+
if self._entered:
|
33 |
+
self._rlock.release()
|
34 |
+
raise ReentrancyError(self._err_msg)
|
35 |
+
self._entered = True
|
36 |
+
|
37 |
+
def __exit__(self, type, value, traceback):
|
38 |
+
self._entered = False
|
39 |
+
self._rlock.release()
|
40 |
+
|
41 |
+
def decorate(self, func):
|
42 |
+
def caller(func, *a, **kw):
|
43 |
+
with self:
|
44 |
+
return func(*a, **kw)
|
45 |
+
return scipy._lib.decorator.decorate(func, caller)
|
46 |
+
|
47 |
+
|
48 |
+
def non_reentrant(err_msg=None):
|
49 |
+
"""
|
50 |
+
Decorate a function with a threading lock and prevent reentrant calls.
|
51 |
+
"""
|
52 |
+
def decorator(func):
|
53 |
+
msg = err_msg
|
54 |
+
if msg is None:
|
55 |
+
msg = "%s is not re-entrant" % func.__name__
|
56 |
+
lock = ReentrancyLock(msg)
|
57 |
+
return lock.decorate(func)
|
58 |
+
return decorator
|
scipy/_lib/_tmpdirs.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
''' Contexts for *with* statement providing temporary directories
|
2 |
+
'''
|
3 |
+
import os
|
4 |
+
from contextlib import contextmanager
|
5 |
+
from shutil import rmtree
|
6 |
+
from tempfile import mkdtemp
|
7 |
+
|
8 |
+
|
9 |
+
@contextmanager
|
10 |
+
def tempdir():
|
11 |
+
"""Create and return a temporary directory. This has the same
|
12 |
+
behavior as mkdtemp but can be used as a context manager.
|
13 |
+
|
14 |
+
Upon exiting the context, the directory and everything contained
|
15 |
+
in it are removed.
|
16 |
+
|
17 |
+
Examples
|
18 |
+
--------
|
19 |
+
>>> import os
|
20 |
+
>>> with tempdir() as tmpdir:
|
21 |
+
... fname = os.path.join(tmpdir, 'example_file.txt')
|
22 |
+
... with open(fname, 'wt') as fobj:
|
23 |
+
... _ = fobj.write('a string\\n')
|
24 |
+
>>> os.path.exists(tmpdir)
|
25 |
+
False
|
26 |
+
"""
|
27 |
+
d = mkdtemp()
|
28 |
+
yield d
|
29 |
+
rmtree(d)
|
30 |
+
|
31 |
+
|
32 |
+
@contextmanager
|
33 |
+
def in_tempdir():
|
34 |
+
''' Create, return, and change directory to a temporary directory
|
35 |
+
|
36 |
+
Examples
|
37 |
+
--------
|
38 |
+
>>> import os
|
39 |
+
>>> my_cwd = os.getcwd()
|
40 |
+
>>> with in_tempdir() as tmpdir:
|
41 |
+
... _ = open('test.txt', 'wt').write('some text')
|
42 |
+
... assert os.path.isfile('test.txt')
|
43 |
+
... assert os.path.isfile(os.path.join(tmpdir, 'test.txt'))
|
44 |
+
>>> os.path.exists(tmpdir)
|
45 |
+
False
|
46 |
+
>>> os.getcwd() == my_cwd
|
47 |
+
True
|
48 |
+
'''
|
49 |
+
pwd = os.getcwd()
|
50 |
+
d = mkdtemp()
|
51 |
+
os.chdir(d)
|
52 |
+
yield d
|
53 |
+
os.chdir(pwd)
|
54 |
+
rmtree(d)
|
55 |
+
|
56 |
+
|
57 |
+
@contextmanager
|
58 |
+
def in_dir(dir=None):
|
59 |
+
""" Change directory to given directory for duration of ``with`` block
|
60 |
+
|
61 |
+
Useful when you want to use `in_tempdir` for the final test, but
|
62 |
+
you are still debugging. For example, you may want to do this in the end:
|
63 |
+
|
64 |
+
>>> with in_tempdir() as tmpdir:
|
65 |
+
... # do something complicated which might break
|
66 |
+
... pass
|
67 |
+
|
68 |
+
But, indeed, the complicated thing does break, and meanwhile, the
|
69 |
+
``in_tempdir`` context manager wiped out the directory with the
|
70 |
+
temporary files that you wanted for debugging. So, while debugging, you
|
71 |
+
replace with something like:
|
72 |
+
|
73 |
+
>>> with in_dir() as tmpdir: # Use working directory by default
|
74 |
+
... # do something complicated which might break
|
75 |
+
... pass
|
76 |
+
|
77 |
+
You can then look at the temporary file outputs to debug what is happening,
|
78 |
+
fix, and finally replace ``in_dir`` with ``in_tempdir`` again.
|
79 |
+
"""
|
80 |
+
cwd = os.getcwd()
|
81 |
+
if dir is None:
|
82 |
+
yield cwd
|
83 |
+
return
|
84 |
+
os.chdir(dir)
|
85 |
+
yield dir
|
86 |
+
os.chdir(cwd)
|
scipy/_lib/_uarray/LICENSE
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
BSD 3-Clause License
|
2 |
+
|
3 |
+
Copyright (c) 2018, Quansight-Labs
|
4 |
+
All rights reserved.
|
5 |
+
|
6 |
+
Redistribution and use in source and binary forms, with or without
|
7 |
+
modification, are permitted provided that the following conditions are met:
|
8 |
+
|
9 |
+
* Redistributions of source code must retain the above copyright notice, this
|
10 |
+
list of conditions and the following disclaimer.
|
11 |
+
|
12 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13 |
+
this list of conditions and the following disclaimer in the documentation
|
14 |
+
and/or other materials provided with the distribution.
|
15 |
+
|
16 |
+
* Neither the name of the copyright holder nor the names of its
|
17 |
+
contributors may be used to endorse or promote products derived from
|
18 |
+
this software without specific prior written permission.
|
19 |
+
|
20 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
24 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
scipy/_lib/_uarray/__init__.py
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
.. note:
|
3 |
+
If you are looking for overrides for NumPy-specific methods, see the
|
4 |
+
documentation for :obj:`unumpy`. This page explains how to write
|
5 |
+
back-ends and multimethods.
|
6 |
+
|
7 |
+
``uarray`` is built around a back-end protocol, and overridable multimethods.
|
8 |
+
It is necessary to define multimethods for back-ends to be able to override them.
|
9 |
+
See the documentation of :obj:`generate_multimethod` on how to write multimethods.
|
10 |
+
|
11 |
+
|
12 |
+
|
13 |
+
Let's start with the simplest:
|
14 |
+
|
15 |
+
``__ua_domain__`` defines the back-end *domain*. The domain consists of period-
|
16 |
+
separated string consisting of the modules you extend plus the submodule. For
|
17 |
+
example, if a submodule ``module2.submodule`` extends ``module1``
|
18 |
+
(i.e., it exposes dispatchables marked as types available in ``module1``),
|
19 |
+
then the domain string should be ``"module1.module2.submodule"``.
|
20 |
+
|
21 |
+
|
22 |
+
For the purpose of this demonstration, we'll be creating an object and setting
|
23 |
+
its attributes directly. However, note that you can use a module or your own type
|
24 |
+
as a backend as well.
|
25 |
+
|
26 |
+
>>> class Backend: pass
|
27 |
+
>>> be = Backend()
|
28 |
+
>>> be.__ua_domain__ = "ua_examples"
|
29 |
+
|
30 |
+
It might be useful at this point to sidetrack to the documentation of
|
31 |
+
:obj:`generate_multimethod` to find out how to generate a multimethod
|
32 |
+
overridable by :obj:`uarray`. Needless to say, writing a backend and
|
33 |
+
creating multimethods are mostly orthogonal activities, and knowing
|
34 |
+
one doesn't necessarily require knowledge of the other, although it
|
35 |
+
is certainly helpful. We expect core API designers/specifiers to write the
|
36 |
+
multimethods, and implementors to override them. But, as is often the case,
|
37 |
+
similar people write both.
|
38 |
+
|
39 |
+
Without further ado, here's an example multimethod:
|
40 |
+
|
41 |
+
>>> import uarray as ua
|
42 |
+
>>> from uarray import Dispatchable
|
43 |
+
>>> def override_me(a, b):
|
44 |
+
... return Dispatchable(a, int),
|
45 |
+
>>> def override_replacer(args, kwargs, dispatchables):
|
46 |
+
... return (dispatchables[0], args[1]), {}
|
47 |
+
>>> overridden_me = ua.generate_multimethod(
|
48 |
+
... override_me, override_replacer, "ua_examples"
|
49 |
+
... )
|
50 |
+
|
51 |
+
Next comes the part about overriding the multimethod. This requires
|
52 |
+
the ``__ua_function__`` protocol, and the ``__ua_convert__``
|
53 |
+
protocol. The ``__ua_function__`` protocol has the signature
|
54 |
+
``(method, args, kwargs)`` where ``method`` is the passed
|
55 |
+
multimethod, ``args``/``kwargs`` specify the arguments and ``dispatchables``
|
56 |
+
is the list of converted dispatchables passed in.
|
57 |
+
|
58 |
+
>>> def __ua_function__(method, args, kwargs):
|
59 |
+
... return method.__name__, args, kwargs
|
60 |
+
>>> be.__ua_function__ = __ua_function__
|
61 |
+
|
62 |
+
The other protocol of interest is the ``__ua_convert__`` protocol. It has the
|
63 |
+
signature ``(dispatchables, coerce)``. When ``coerce`` is ``False``, conversion
|
64 |
+
between the formats should ideally be an ``O(1)`` operation, but it means that
|
65 |
+
no memory copying should be involved, only views of the existing data.
|
66 |
+
|
67 |
+
>>> def __ua_convert__(dispatchables, coerce):
|
68 |
+
... for d in dispatchables:
|
69 |
+
... if d.type is int:
|
70 |
+
... if coerce and d.coercible:
|
71 |
+
... yield str(d.value)
|
72 |
+
... else:
|
73 |
+
... yield d.value
|
74 |
+
>>> be.__ua_convert__ = __ua_convert__
|
75 |
+
|
76 |
+
Now that we have defined the backend, the next thing to do is to call the multimethod.
|
77 |
+
|
78 |
+
>>> with ua.set_backend(be):
|
79 |
+
... overridden_me(1, "2")
|
80 |
+
('override_me', (1, '2'), {})
|
81 |
+
|
82 |
+
Note that the marked type has no effect on the actual type of the passed object.
|
83 |
+
We can also coerce the type of the input.
|
84 |
+
|
85 |
+
>>> with ua.set_backend(be, coerce=True):
|
86 |
+
... overridden_me(1, "2")
|
87 |
+
... overridden_me(1.0, "2")
|
88 |
+
('override_me', ('1', '2'), {})
|
89 |
+
('override_me', ('1.0', '2'), {})
|
90 |
+
|
91 |
+
Another feature is that if you remove ``__ua_convert__``, the arguments are not
|
92 |
+
converted at all and it's up to the backend to handle that.
|
93 |
+
|
94 |
+
>>> del be.__ua_convert__
|
95 |
+
>>> with ua.set_backend(be):
|
96 |
+
... overridden_me(1, "2")
|
97 |
+
('override_me', (1, '2'), {})
|
98 |
+
|
99 |
+
You also have the option to return ``NotImplemented``, in which case processing moves on
|
100 |
+
to the next back-end, which in this case, doesn't exist. The same applies to
|
101 |
+
``__ua_convert__``.
|
102 |
+
|
103 |
+
>>> be.__ua_function__ = lambda *a, **kw: NotImplemented
|
104 |
+
>>> with ua.set_backend(be):
|
105 |
+
... overridden_me(1, "2")
|
106 |
+
Traceback (most recent call last):
|
107 |
+
...
|
108 |
+
uarray.BackendNotImplementedError: ...
|
109 |
+
|
110 |
+
The last possibility is if we don't have ``__ua_convert__``, in which case the job is
|
111 |
+
left up to ``__ua_function__``, but putting things back into arrays after conversion
|
112 |
+
will not be possible.
|
113 |
+
"""
|
114 |
+
|
115 |
+
from ._backend import *
|
116 |
+
__version__ = '0.8.8.dev0+aa94c5a4.scipy'
|