numexpr 2.10.0__tar.gz → 2.10.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {numexpr-2.10.0 → numexpr-2.10.2}/ANNOUNCE.rst +14 -15
  2. {numexpr-2.10.0 → numexpr-2.10.2}/MANIFEST.in +1 -1
  3. {numexpr-2.10.0 → numexpr-2.10.2}/PKG-INFO +13 -11
  4. {numexpr-2.10.0 → numexpr-2.10.2}/RELEASE_NOTES.rst +30 -0
  5. numexpr-2.10.2/VERSION +1 -0
  6. numexpr-2.10.2/bench/large_array_vs_numpy.py +152 -0
  7. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/__init__.py +0 -2
  8. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/interpreter.cpp +6 -0
  9. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/necompiler.py +4 -6
  10. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/tests/test_numexpr.py +88 -15
  11. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/utils.py +85 -4
  12. numexpr-2.10.2/numexpr/version.py +5 -0
  13. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr.egg-info/PKG-INFO +13 -11
  14. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr.egg-info/SOURCES.txt +2 -2
  15. numexpr-2.10.2/numexpr.egg-info/requires.txt +1 -0
  16. numexpr-2.10.2/pyproject.toml +50 -0
  17. numexpr-2.10.2/requirements.txt +1 -0
  18. numexpr-2.10.2/setup.cfg +4 -0
  19. {numexpr-2.10.0 → numexpr-2.10.2}/setup.py +15 -6
  20. numexpr-2.10.0/numexpr/version.py +0 -4
  21. numexpr-2.10.0/numexpr.egg-info/not-zip-safe +0 -1
  22. numexpr-2.10.0/numexpr.egg-info/requires.txt +0 -1
  23. numexpr-2.10.0/pyproject.toml +0 -7
  24. numexpr-2.10.0/requirements.txt +0 -1
  25. numexpr-2.10.0/setup.cfg +0 -34
  26. {numexpr-2.10.0 → numexpr-2.10.2}/AUTHORS.txt +0 -0
  27. {numexpr-2.10.0 → numexpr-2.10.2}/LICENSE.txt +0 -0
  28. {numexpr-2.10.0 → numexpr-2.10.2}/README.rst +0 -0
  29. {numexpr-2.10.0 → numexpr-2.10.2}/bench/boolean_timing.py +0 -0
  30. {numexpr-2.10.0 → numexpr-2.10.2}/bench/issue-36.py +0 -0
  31. {numexpr-2.10.0 → numexpr-2.10.2}/bench/issue-47.py +0 -0
  32. {numexpr-2.10.0 → numexpr-2.10.2}/bench/multidim.py +0 -0
  33. {numexpr-2.10.0 → numexpr-2.10.2}/bench/poly.py +0 -0
  34. {numexpr-2.10.0 → numexpr-2.10.2}/bench/timing.py +0 -0
  35. {numexpr-2.10.0 → numexpr-2.10.2}/bench/unaligned-simple.py +0 -0
  36. {numexpr-2.10.0 → numexpr-2.10.2}/bench/varying-expr.py +0 -0
  37. {numexpr-2.10.0 → numexpr-2.10.2}/bench/vml_timing.py +0 -0
  38. {numexpr-2.10.0 → numexpr-2.10.2}/bench/vml_timing2.py +0 -0
  39. {numexpr-2.10.0 → numexpr-2.10.2}/bench/vml_timing3.py +0 -0
  40. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/complex_functions.hpp +0 -0
  41. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/cpuinfo.py +0 -0
  42. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/expressions.py +0 -0
  43. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/functions.hpp +0 -0
  44. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/interp_body.cpp +0 -0
  45. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/interpreter.hpp +0 -0
  46. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/missing_posix_functions.hpp +0 -0
  47. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/module.cpp +0 -0
  48. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/module.hpp +0 -0
  49. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/msvc_function_stubs.hpp +0 -0
  50. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/numexpr_config.hpp +0 -0
  51. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/numexpr_object.cpp +0 -0
  52. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/numexpr_object.hpp +0 -0
  53. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/opcodes.hpp +0 -0
  54. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/str-two-way.hpp +0 -0
  55. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/tests/__init__.py +0 -0
  56. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/win32/pthread.c +0 -0
  57. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/win32/pthread.h +0 -0
  58. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr/win32/stdint.h +0 -0
  59. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr.egg-info/dependency_links.txt +0 -0
  60. {numexpr-2.10.0 → numexpr-2.10.2}/numexpr.egg-info/top_level.txt +0 -0
  61. {numexpr-2.10.0 → numexpr-2.10.2}/site.cfg.example +0 -0
@@ -1,29 +1,28 @@
1
- ========================
2
- Announcing NumExpr 2.9.0
3
- ========================
1
+ =========================
2
+ Announcing NumExpr 2.10.2
3
+ =========================
4
4
 
5
5
  Hi everyone,
6
6
 
7
- NumExpr 2.9.0 is a release offering support for latest versions of PyPy.
8
- The full test suite should pass now, at least for the Python 3.10 version.
9
- Thanks to @27rabbitlt for most of the work and @mgorny and @mattip for
10
- providing help and additional fixes.
7
+ NumExpr 2.10.2 provides wheels for Python 2.13 for first time.
8
+ Also, there is better support for CPUs that do not have a power
9
+ of 2 number of cores. Finally, numexpr is allowed to run with
10
+ the multithreading package in Python.
11
11
 
12
12
  Project documentation is available at:
13
13
 
14
14
  http://numexpr.readthedocs.io/
15
15
 
16
- Changes from 2.8.8 to 2.9.0
17
- ---------------------------
16
+ Changes from 2.10.1 to 2.10.2
17
+ -----------------------------
18
18
 
19
- * Support for PyPy (see PRs #467 and #740). The full test suite
20
- should pass now, at least for the 3.10 version. Thanks to
21
- @27rabbitlt for most of the work and @mgorny and @mattip for
22
- providing help and additional fixes. Fixes #463.
19
+ * Better support for CPUs that do not have a power of 2 number of
20
+ cores. See #479 and #490. Thanks to @avalentino.
23
21
 
24
- * Fixed more sanitizer issues (see PR #469). Thanks to @27rabbitlt.
22
+ * Allow numexpr to run with the multithreading package in Python.
23
+ See PR #496. Thanks to @emmaai
25
24
 
26
- * Modernized the test suite to avoid some warnings.
25
+ * Wheels for Python 3.13 are now provided.
27
26
 
28
27
  What's Numexpr?
29
28
  ---------------
@@ -1,4 +1,4 @@
1
- include MANIFEST.in
1
+ include MANIFEST.in VERSION
2
2
 
3
3
  include *.rst *.txt *.cfg site.cfg.example
4
4
 
@@ -1,29 +1,31 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numexpr
3
- Version: 2.10.0
3
+ Version: 2.10.2
4
4
  Summary: Fast numerical expression evaluator for NumPy
5
- Home-page: https://github.com/pydata/numexpr
6
- Author: David M. Cooke, Francesc Alted, and others
7
- Maintainer: Francesc Alted
8
- Maintainer-email: faltet@gmail.com
9
- License: MIT
5
+ Author-email: "David M. Cooke, Francesc Alted, and others" <blosc@blosc.org>
6
+ Maintainer-email: Blosc Development Team <blosc@blosc.org>
7
+ Project-URL: homepage, https://github.com/pydata/numexpr
8
+ Project-URL: documentation, https://numexpr.readthedocs.io
9
+ Project-URL: repository, https://github.com/pydata/numexpr
10
10
  Classifier: Development Status :: 6 - Mature
11
- Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Information Technology
12
13
  Classifier: Intended Audience :: Science/Research
13
14
  Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: Unix
14
19
  Classifier: Programming Language :: Python :: 3
15
20
  Classifier: Programming Language :: Python :: 3.9
16
21
  Classifier: Programming Language :: Python :: 3.10
17
22
  Classifier: Programming Language :: Python :: 3.11
18
23
  Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Operating System :: Microsoft :: Windows
20
- Classifier: Operating System :: POSIX
21
- Classifier: Operating System :: MacOS
22
24
  Requires-Python: >=3.9
23
25
  Description-Content-Type: text/x-rst
24
26
  License-File: LICENSE.txt
25
27
  License-File: AUTHORS.txt
26
- Requires-Dist: numpy>=1.19.3
28
+ Requires-Dist: numpy>=1.23.0
27
29
 
28
30
  ======================================================
29
31
  NumExpr: Fast numerical expression evaluator for NumPy
@@ -2,6 +2,36 @@
2
2
  Release notes for NumExpr 2.10 series
3
3
  =====================================
4
4
 
5
+ Changes from 2.10.1 to 2.10.2
6
+ -----------------------------
7
+
8
+ * Better support for CPUs that do not have a power of 2 number of
9
+ cores. See #479 and #490. Thanks to @avalentino.
10
+
11
+ * Allow numexpr to run with the multithreading package in Python.
12
+ See PR #496. Thanks to @emmaai
13
+
14
+ * Wheels for Python 3.13 are now provided.
15
+
16
+
17
+ Changes from 2.10.0 to 2.10.1
18
+ -----------------------------
19
+
20
+ * The default number of 'safe' threads has been upgraded to 16 (instead of
21
+ previous 8). That means that if your CPU has > 16 cores, the default is
22
+ to use 16. You can always override this with the "NUMEXPR_MAX_THREADS"
23
+ environment variable.
24
+
25
+ * NumPy 1.23 is now the minimum supported.
26
+
27
+ * Preliminary support for Python 3.13. Thanks to Karolina Surma.
28
+
29
+ * Fix tests on nthreads detection (closes: #479). Thanks to @avalentino.
30
+
31
+ * The build process has been modernized and now uses the `pyproject.toml`
32
+ file for more of the configuration options.
33
+
34
+
5
35
  Changes from 2.9.0 to 2.10.0
6
36
  ----------------------------
7
37
 
numexpr-2.10.2/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.10.2
@@ -0,0 +1,152 @@
1
+ #################################################################################
2
+ # To mimic the scenario that computation is i/o bound and constrained by memory
3
+ #
4
+ # It's a much simplified version that the chunk is computed in a loop,
5
+ # and expression is evaluated in a sequence, which is not true in reality.
6
+ # Neverthless, numexpr outperforms numpy.
7
+ #################################################################################
8
+ """
9
+ Benchmarking Expression 1:
10
+ NumPy time (threaded over 32 chunks with 2 threads): 4.612313 seconds
11
+ numexpr time (threaded with re_evaluate over 32 chunks with 2 threads): 0.951172 seconds
12
+ numexpr speedup: 4.85x
13
+ ----------------------------------------
14
+ Benchmarking Expression 2:
15
+ NumPy time (threaded over 32 chunks with 2 threads): 23.862752 seconds
16
+ numexpr time (threaded with re_evaluate over 32 chunks with 2 threads): 2.182058 seconds
17
+ numexpr speedup: 10.94x
18
+ ----------------------------------------
19
+ Benchmarking Expression 3:
20
+ NumPy time (threaded over 32 chunks with 2 threads): 20.594895 seconds
21
+ numexpr time (threaded with re_evaluate over 32 chunks with 2 threads): 2.927881 seconds
22
+ numexpr speedup: 7.03x
23
+ ----------------------------------------
24
+ Benchmarking Expression 4:
25
+ NumPy time (threaded over 32 chunks with 2 threads): 12.834101 seconds
26
+ numexpr time (threaded with re_evaluate over 32 chunks with 2 threads): 5.392480 seconds
27
+ numexpr speedup: 2.38x
28
+ ----------------------------------------
29
+ """
30
+
31
+ import os
32
+
33
+ os.environ["NUMEXPR_NUM_THREADS"] = "16"
34
+ import numpy as np
35
+ import numexpr as ne
36
+ import timeit
37
+ import threading
38
+
39
+ array_size = 10**8
40
+ num_runs = 10
41
+ num_chunks = 32 # Number of chunks
42
+ num_threads = 2 # Number of threads constrained by how many chunks memory can hold
43
+
44
+ a = np.random.rand(array_size).reshape(10**4, -1)
45
+ b = np.random.rand(array_size).reshape(10**4, -1)
46
+ c = np.random.rand(array_size).reshape(10**4, -1)
47
+
48
+ chunk_size = array_size // num_chunks
49
+
50
+ expressions_numpy = [
51
+ lambda a, b, c: a + b * c,
52
+ lambda a, b, c: a**2 + b**2 - 2 * a * b * np.cos(c),
53
+ lambda a, b, c: np.sin(a) + np.log(b) * np.sqrt(c),
54
+ lambda a, b, c: np.exp(a) + np.tan(b) - np.sinh(c),
55
+ ]
56
+
57
+ expressions_numexpr = [
58
+ "a + b * c",
59
+ "a**2 + b**2 - 2 * a * b * cos(c)",
60
+ "sin(a) + log(b) * sqrt(c)",
61
+ "exp(a) + tan(b) - sinh(c)",
62
+ ]
63
+
64
+
65
+ def benchmark_numpy_chunk(func, a, b, c, results, indices):
66
+ for index in indices:
67
+ start = index * chunk_size
68
+ end = (index + 1) * chunk_size
69
+ time_taken = timeit.timeit(
70
+ lambda: func(a[start:end], b[start:end], c[start:end]), number=num_runs
71
+ )
72
+ results.append(time_taken)
73
+
74
+
75
+ def benchmark_numexpr_re_evaluate(expr, a, b, c, results, indices):
76
+ for index in indices:
77
+ start = index * chunk_size
78
+ end = (index + 1) * chunk_size
79
+ if index == 0:
80
+ # Evaluate the first chunk with evaluate
81
+ time_taken = timeit.timeit(
82
+ lambda: ne.evaluate(
83
+ expr,
84
+ local_dict={
85
+ "a": a[start:end],
86
+ "b": b[start:end],
87
+ "c": c[start:end],
88
+ },
89
+ ),
90
+ number=num_runs,
91
+ )
92
+ else:
93
+ # Re-evaluate subsequent chunks with re_evaluate
94
+ time_taken = timeit.timeit(
95
+ lambda: ne.re_evaluate(
96
+ local_dict={"a": a[start:end], "b": b[start:end], "c": c[start:end]}
97
+ ),
98
+ number=num_runs,
99
+ )
100
+ results.append(time_taken)
101
+
102
+
103
+ def run_benchmark_threaded():
104
+ chunk_indices = list(range(num_chunks))
105
+
106
+ for i in range(len(expressions_numpy)):
107
+ print(f"Benchmarking Expression {i+1}:")
108
+
109
+ results_numpy = []
110
+ results_numexpr = []
111
+
112
+ threads_numpy = []
113
+ for j in range(num_threads):
114
+ indices = chunk_indices[j::num_threads] # Distribute chunks across threads
115
+ thread = threading.Thread(
116
+ target=benchmark_numpy_chunk,
117
+ args=(expressions_numpy[i], a, b, c, results_numpy, indices),
118
+ )
119
+ threads_numpy.append(thread)
120
+ thread.start()
121
+
122
+ for thread in threads_numpy:
123
+ thread.join()
124
+
125
+ numpy_time = sum(results_numpy)
126
+ print(
127
+ f"NumPy time (threaded over {num_chunks} chunks with {num_threads} threads): {numpy_time:.6f} seconds"
128
+ )
129
+
130
+ threads_numexpr = []
131
+ for j in range(num_threads):
132
+ indices = chunk_indices[j::num_threads] # Distribute chunks across threads
133
+ thread = threading.Thread(
134
+ target=benchmark_numexpr_re_evaluate,
135
+ args=(expressions_numexpr[i], a, b, c, results_numexpr, indices),
136
+ )
137
+ threads_numexpr.append(thread)
138
+ thread.start()
139
+
140
+ for thread in threads_numexpr:
141
+ thread.join()
142
+
143
+ numexpr_time = sum(results_numexpr)
144
+ print(
145
+ f"numexpr time (threaded with re_evaluate over {num_chunks} chunks with {num_threads} threads): {numexpr_time:.6f} seconds"
146
+ )
147
+ print(f"numexpr speedup: {numpy_time / numexpr_time:.2f}x")
148
+ print("-" * 40)
149
+
150
+
151
+ if __name__ == "__main__":
152
+ run_benchmark_threaded()
@@ -28,8 +28,6 @@ is_cpu_amd_intel = False # DEPRECATION WARNING: WILL BE REMOVED IN FUTURE RELEAS
28
28
  # cpuinfo imports were moved into the test submodule function that calls them
29
29
  # to improve import times.
30
30
 
31
- import os, os.path
32
- import platform
33
31
  from numexpr.expressions import E
34
32
  from numexpr.necompiler import (NumExpr, disassemble, evaluate, re_evaluate,
35
33
  validate)
@@ -47,6 +47,12 @@
47
47
  #define AVAILABLE(Haystack, Haystack_Len, J, Needle_Len) \
48
48
  ((Haystack_Len) >= (J) + (Needle_Len))
49
49
 
50
+ // To allow building with NumPy<2 locally define the new NumPy macros:
51
+ #if NPY_ABI_VERSION < 0x02000000
52
+ #define PyDataType_ELSIZE(descr) ((descr)->elsize)
53
+ #define PyDataType_SET_ELSIZE(descr, size) (descr)->elsize = size
54
+ #endif
55
+
50
56
  #include "str-two-way.hpp"
51
57
 
52
58
  #ifdef DEBUG
@@ -19,7 +19,7 @@ import numpy
19
19
 
20
20
  is_cpu_amd_intel = False # DEPRECATION WARNING: WILL BE REMOVED IN FUTURE RELEASE
21
21
  from numexpr import interpreter, expressions, use_vml
22
- from numexpr.utils import CacheDict
22
+ from numexpr.utils import CacheDict, ContextDict
23
23
 
24
24
  # Declare a double type that does not exist in Python space
25
25
  double = numpy.double
@@ -767,7 +767,7 @@ def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
767
767
  # If we generated local_dict via an explicit reference to f_locals,
768
768
  # clear the dict to prevent creating extra ref counts in the caller's scope
769
769
  # See https://github.com/pydata/numexpr/issues/310
770
- if clear_local_dict:
770
+ if clear_local_dict and hasattr(local_dict, 'clear'):
771
771
  local_dict.clear()
772
772
 
773
773
  return arguments
@@ -776,11 +776,9 @@ def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
776
776
  # Dictionaries for caching variable names and compiled expressions
777
777
  _names_cache = CacheDict(256)
778
778
  _numexpr_cache = CacheDict(256)
779
- _numexpr_last = {}
779
+ _numexpr_last = ContextDict()
780
780
  evaluate_lock = threading.Lock()
781
781
 
782
- # MAYBE: decorate this function to add attributes instead of having the
783
- # _numexpr_last dictionary?
784
782
  def validate(ex: str,
785
783
  local_dict: Optional[Dict] = None,
786
784
  global_dict: Optional[Dict] = None,
@@ -887,7 +885,7 @@ def validate(ex: str,
887
885
  compiled_ex = _numexpr_cache[numexpr_key] = NumExpr(ex, signature, sanitize=sanitize, **context)
888
886
  kwargs = {'out': out, 'order': order, 'casting': casting,
889
887
  'ex_uses_vml': ex_uses_vml}
890
- _numexpr_last = dict(ex=compiled_ex, argnames=names, kwargs=kwargs)
888
+ _numexpr_last.set(ex=compiled_ex, argnames=names, kwargs=kwargs)
891
889
  except Exception as e:
892
890
  return e
893
891
  return None
@@ -41,6 +41,7 @@ TestCase = unittest.TestCase
41
41
 
42
42
  double = np.double
43
43
  long = int
44
+ MAX_THREADS = 16
44
45
 
45
46
 
46
47
  class test_numexpr(TestCase):
@@ -1128,7 +1129,7 @@ class test_threading_config(TestCase):
1128
1129
  "if 'NUMEXPR_MAX_THREADS' in os.environ: os.environ.pop('NUMEXPR_MAX_THREADS')",
1129
1130
  "if 'OMP_NUM_THREADS' in os.environ: os.environ.pop('OMP_NUM_THREADS')",
1130
1131
  "import numexpr",
1131
- "assert(numexpr.nthreads <= 8)",
1132
+ f"assert(numexpr.nthreads <= {MAX_THREADS})",
1132
1133
  "exit(0)"])
1133
1134
  subprocess.check_call([sys.executable, '-c', script])
1134
1135
 
@@ -1164,14 +1165,14 @@ class test_threading_config(TestCase):
1164
1165
  if 'sparc' in platform.machine():
1165
1166
  self.assertEqual(1, numexpr._init_num_threads())
1166
1167
  else:
1167
- self.assertEqual(detect_number_of_cores(), numexpr._init_num_threads())
1168
+ self.assertEqual(min(detect_number_of_cores(), MAX_THREADS), numexpr._init_num_threads())
1168
1169
 
1169
1170
  def test_numexpr_max_threads_empty_string(self):
1170
1171
  with _environment('NUMEXPR_MAX_THREADS', ''):
1171
1172
  if 'sparc' in platform.machine():
1172
1173
  self.assertEqual(1, numexpr._init_num_threads())
1173
1174
  else:
1174
- self.assertEqual(detect_number_of_cores(), numexpr._init_num_threads())
1175
+ self.assertEqual(min(detect_number_of_cores(), MAX_THREADS), numexpr._init_num_threads())
1175
1176
 
1176
1177
  def test_vml_threads_round_trip(self):
1177
1178
  n_threads = 3
@@ -1200,6 +1201,7 @@ class test_threading(TestCase):
1200
1201
  test.join()
1201
1202
 
1202
1203
  def test_multithread(self):
1204
+
1203
1205
  import threading
1204
1206
 
1205
1207
  # Running evaluate() from multiple threads shouldn't crash
@@ -1217,6 +1219,77 @@ class test_threading(TestCase):
1217
1219
  for t in threads:
1218
1220
  t.join()
1219
1221
 
1222
+ def test_thread_safety(self):
1223
+ """
1224
+ Expected output
1225
+
1226
+ When not safe (before the pr this test is commited)
1227
+ AssertionError: Thread-0 failed: result does not match expected
1228
+
1229
+ When safe (after the pr this test is commited)
1230
+ Should pass without failure
1231
+ """
1232
+ import threading
1233
+ import time
1234
+
1235
+ barrier = threading.Barrier(4)
1236
+
1237
+ # Function that each thread will run with different expressions
1238
+ def thread_function(a_value, b_value, expression, expected_result, results, index):
1239
+ validate(expression, local_dict={"a": a_value, "b": b_value})
1240
+ # Wait for all threads to reach this point
1241
+ # such that they all set _numexpr_last
1242
+ barrier.wait()
1243
+
1244
+ # Simulate some work or a context switch delay
1245
+ time.sleep(0.1)
1246
+
1247
+ result = re_evaluate(local_dict={"a": a_value, "b": b_value})
1248
+ results[index] = np.array_equal(result, expected_result)
1249
+
1250
+ def test_thread_safety_with_numexpr():
1251
+ num_threads = 4
1252
+ array_size = 1000000
1253
+
1254
+ expressions = [
1255
+ "a + b",
1256
+ "a - b",
1257
+ "a * b",
1258
+ "a / b"
1259
+ ]
1260
+
1261
+ a_value = [np.full(array_size, i + 1) for i in range(num_threads)]
1262
+ b_value = [np.full(array_size, (i + 1) * 2) for i in range(num_threads)]
1263
+
1264
+ expected_results = [
1265
+ a_value[i] + b_value[i] if expr == "a + b" else
1266
+ a_value[i] - b_value[i] if expr == "a - b" else
1267
+ a_value[i] * b_value[i] if expr == "a * b" else
1268
+ a_value[i] / b_value[i] if expr == "a / b" else None
1269
+ for i, expr in enumerate(expressions)
1270
+ ]
1271
+
1272
+ results = [None] * num_threads
1273
+ threads = []
1274
+
1275
+ # Create and start threads with different expressions
1276
+ for i in range(num_threads):
1277
+ thread = threading.Thread(
1278
+ target=thread_function,
1279
+ args=(a_value[i], b_value[i], expressions[i], expected_results[i], results, i)
1280
+ )
1281
+ threads.append(thread)
1282
+ thread.start()
1283
+
1284
+ for thread in threads:
1285
+ thread.join()
1286
+
1287
+ for i in range(num_threads):
1288
+ if not results[i]:
1289
+ self.fail(f"Thread-{i} failed: result does not match expected")
1290
+
1291
+ test_thread_safety_with_numexpr()
1292
+
1220
1293
 
1221
1294
  # The worker function for the subprocess (needs to be here because Windows
1222
1295
  # has problems pickling nested functions with the multiprocess module :-/)
@@ -1315,28 +1388,28 @@ def suite():
1315
1388
  add_method(func)
1316
1389
 
1317
1390
  for n in range(niter):
1318
- theSuite.addTest(unittest.makeSuite(test_numexpr))
1391
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_numexpr))
1319
1392
  if 'sparc' not in platform.machine():
1320
- theSuite.addTest(unittest.makeSuite(test_numexpr2))
1321
- theSuite.addTest(unittest.makeSuite(test_evaluate))
1322
- theSuite.addTest(unittest.makeSuite(TestExpressions))
1323
- theSuite.addTest(unittest.makeSuite(test_int32_int64))
1324
- theSuite.addTest(unittest.makeSuite(test_uint32_int64))
1325
- theSuite.addTest(unittest.makeSuite(test_strings))
1393
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_numexpr2))
1394
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_evaluate))
1395
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestExpressions))
1396
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_int32_int64))
1397
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_uint32_int64))
1398
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_strings))
1326
1399
  theSuite.addTest(
1327
- unittest.makeSuite(test_irregular_stride))
1328
- theSuite.addTest(unittest.makeSuite(test_zerodim))
1329
- theSuite.addTest(unittest.makeSuite(test_threading_config))
1400
+ unittest.defaultTestLoader.loadTestsFromTestCase(test_irregular_stride))
1401
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_zerodim))
1402
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_threading_config))
1330
1403
 
1331
1404
  # multiprocessing module is not supported on Hurd/kFreeBSD
1332
1405
  if (pl.system().lower() not in ('gnu', 'gnu/kfreebsd')):
1333
- theSuite.addTest(unittest.makeSuite(test_subprocess))
1406
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_subprocess))
1334
1407
 
1335
1408
  # I need to put this test after test_subprocess because
1336
1409
  # if not, the test suite locks immediately before test_subproces.
1337
1410
  # This only happens with Windows, so I suspect of a subtle bad
1338
1411
  # interaction with threads and subprocess :-/
1339
- theSuite.addTest(unittest.makeSuite(test_threading))
1412
+ theSuite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_threading))
1340
1413
 
1341
1414
  return theSuite
1342
1415
 
@@ -13,6 +13,7 @@ log = logging.getLogger(__name__)
13
13
 
14
14
  import os
15
15
  import subprocess
16
+ import contextvars
16
17
 
17
18
  from numexpr.interpreter import _set_num_threads, _get_num_threads, MAX_THREADS
18
19
  from numexpr import use_vml
@@ -143,10 +144,10 @@ def _init_num_threads():
143
144
  # configured NumExpr as desired, so we emit info logs.
144
145
  if n_cores > MAX_THREADS:
145
146
  log.info('Note: detected %d virtual cores but NumExpr set to maximum of %d, check "NUMEXPR_MAX_THREADS" environment variable.'%(n_cores, MAX_THREADS))
146
- if n_cores > 8:
147
- # The historical 'safety' limit.
148
- log.info('Note: NumExpr detected %d cores but "NUMEXPR_MAX_THREADS" not set, so enforcing safe limit of 8.'%n_cores)
149
- n_cores = 8
147
+ if n_cores > 16:
148
+ # Back in 2019, 8 threads would be considered safe for performance. We are in 2024 now, so adjusting.
149
+ log.info('Note: NumExpr detected %d cores but "NUMEXPR_MAX_THREADS" not set, so enforcing safe limit of 16.'%n_cores)
150
+ n_cores = 16
150
151
 
151
152
  # Now we check for 'NUMEXPR_NUM_THREADS' or 'OMP_NUM_THREADS' to set the
152
153
  # actual number of threads used.
@@ -226,3 +227,83 @@ class CacheDict(dict):
226
227
  super(CacheDict, self).__delitem__(k)
227
228
  super(CacheDict, self).__setitem__(key, value)
228
229
 
230
+
231
+ class ContextDict:
232
+ """
233
+ A context aware version dictionary
234
+ """
235
+ def __init__(self):
236
+ self._context_data = contextvars.ContextVar('context_data', default={})
237
+
238
+ def set(self, key=None, value=None, **kwargs):
239
+ data = self._context_data.get().copy()
240
+
241
+ if key is not None:
242
+ data[key] = value
243
+
244
+ for k, v in kwargs.items():
245
+ data[k] = v
246
+
247
+ self._context_data.set(data)
248
+
249
+ def get(self, key, default=None):
250
+ data = self._context_data.get()
251
+ return data.get(key, default)
252
+
253
+ def delete(self, key):
254
+ data = self._context_data.get().copy()
255
+ if key in data:
256
+ del data[key]
257
+ self._context_data.set(data)
258
+
259
+ def clear(self):
260
+ self._context_data.set({})
261
+
262
+ def all(self):
263
+ return self._context_data.get()
264
+
265
+ def update(self, *args, **kwargs):
266
+ data = self._context_data.get().copy()
267
+
268
+ if args:
269
+ if len(args) > 1:
270
+ raise TypeError(f"update() takes at most 1 positional argument ({len(args)} given)")
271
+ other = args[0]
272
+ if isinstance(other, dict):
273
+ data.update(other)
274
+ else:
275
+ for k, v in other:
276
+ data[k] = v
277
+
278
+ data.update(kwargs)
279
+ self._context_data.set(data)
280
+
281
+ def keys(self):
282
+ return self._context_data.get().keys()
283
+
284
+ def values(self):
285
+ return self._context_data.get().values()
286
+
287
+ def items(self):
288
+ return self._context_data.get().items()
289
+
290
+ def __getitem__(self, key):
291
+ return self.get(key)
292
+
293
+ def __setitem__(self, key, value):
294
+ self.set(key, value)
295
+
296
+ def __delitem__(self, key):
297
+ self.delete(key)
298
+
299
+ def __contains__(self, key):
300
+ return key in self._context_data.get()
301
+
302
+ def __len__(self):
303
+ return len(self._context_data.get())
304
+
305
+ def __iter__(self):
306
+ return iter(self._context_data.get())
307
+
308
+ def __repr__(self):
309
+ return repr(self._context_data.get())
@@ -0,0 +1,5 @@
1
+ # THIS FILE IS GENERATED BY `setup.py`
2
+ __version__ = '2.10.2'
3
+ version = '2.10.2'
4
+ numpy_build_version = '2.1.3'
5
+ platform_machine = 'AMD64'
@@ -1,29 +1,31 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numexpr
3
- Version: 2.10.0
3
+ Version: 2.10.2
4
4
  Summary: Fast numerical expression evaluator for NumPy
5
- Home-page: https://github.com/pydata/numexpr
6
- Author: David M. Cooke, Francesc Alted, and others
7
- Maintainer: Francesc Alted
8
- Maintainer-email: faltet@gmail.com
9
- License: MIT
5
+ Author-email: "David M. Cooke, Francesc Alted, and others" <blosc@blosc.org>
6
+ Maintainer-email: Blosc Development Team <blosc@blosc.org>
7
+ Project-URL: homepage, https://github.com/pydata/numexpr
8
+ Project-URL: documentation, https://numexpr.readthedocs.io
9
+ Project-URL: repository, https://github.com/pydata/numexpr
10
10
  Classifier: Development Status :: 6 - Mature
11
- Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Information Technology
12
13
  Classifier: Intended Audience :: Science/Research
13
14
  Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: Unix
14
19
  Classifier: Programming Language :: Python :: 3
15
20
  Classifier: Programming Language :: Python :: 3.9
16
21
  Classifier: Programming Language :: Python :: 3.10
17
22
  Classifier: Programming Language :: Python :: 3.11
18
23
  Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Operating System :: Microsoft :: Windows
20
- Classifier: Operating System :: POSIX
21
- Classifier: Operating System :: MacOS
22
24
  Requires-Python: >=3.9
23
25
  Description-Content-Type: text/x-rst
24
26
  License-File: LICENSE.txt
25
27
  License-File: AUTHORS.txt
26
- Requires-Dist: numpy>=1.19.3
28
+ Requires-Dist: numpy>=1.23.0
27
29
 
28
30
  ======================================================
29
31
  NumExpr: Fast numerical expression evaluator for NumPy
@@ -4,14 +4,15 @@ LICENSE.txt
4
4
  MANIFEST.in
5
5
  README.rst
6
6
  RELEASE_NOTES.rst
7
+ VERSION
7
8
  pyproject.toml
8
9
  requirements.txt
9
- setup.cfg
10
10
  setup.py
11
11
  site.cfg.example
12
12
  bench/boolean_timing.py
13
13
  bench/issue-36.py
14
14
  bench/issue-47.py
15
+ bench/large_array_vs_numpy.py
15
16
  bench/multidim.py
16
17
  bench/poly.py
17
18
  bench/timing.py
@@ -43,7 +44,6 @@ numexpr/version.py
43
44
  numexpr.egg-info/PKG-INFO
44
45
  numexpr.egg-info/SOURCES.txt
45
46
  numexpr.egg-info/dependency_links.txt
46
- numexpr.egg-info/not-zip-safe
47
47
  numexpr.egg-info/requires.txt
48
48
  numexpr.egg-info/top_level.txt
49
49
  numexpr/win32/pthread.c
@@ -0,0 +1 @@
1
+ numpy>=1.23.0
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools",
4
+ "wheel",
5
+ "numpy>=2.0.0",
6
+ ]
7
+ build-backend = "setuptools.build_meta"
8
+
9
+ [tool.setuptools.packages.find]
10
+ include = ["numexpr"]
11
+
12
+ [project]
13
+ name = "numexpr"
14
+ description = "Fast numerical expression evaluator for NumPy"
15
+ readme = "README.rst"
16
+ authors = [{name = "David M. Cooke, Francesc Alted, and others", email = "blosc@blosc.org"}]
17
+ maintainers = [{ name = "Blosc Development Team", email = "blosc@blosc.org"}]
18
+ classifiers = [
19
+ "Development Status :: 6 - Mature",
20
+ "Intended Audience :: Developers",
21
+ "Intended Audience :: Information Technology",
22
+ "Intended Audience :: Science/Research",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Operating System :: Microsoft :: Windows",
27
+ "Operating System :: Unix",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ ]
34
+ requires-python = ">=3.9"
35
+ # Follow guidelines from https://scientific-python.org/specs/spec-0000/
36
+ dependencies = [
37
+ "numpy>=1.23.0",
38
+ ]
39
+ dynamic = ["version"]
40
+
41
+ [project.urls]
42
+ homepage = "https://github.com/pydata/numexpr"
43
+ documentation = "https://numexpr.readthedocs.io"
44
+ repository = "https://github.com/pydata/numexpr"
45
+
46
+ [tool.cibuildwheel]
47
+ skip = "cp36-* cp37-* pp37-* cp38-* pp* *-manylinux_i686 *_ppc64le *_s390x"
48
+ # Let's use a more recent version of the manylinux image for more modern compilers
49
+ manylinux-x86_64-image = "manylinux_2_28"
50
+ manylinux-aarch64-image = "manylinux_2_28"
@@ -0,0 +1 @@
1
+ numpy >= 1.23.0 # keep in sync with NPY_TARGET_VERSION (setup.py)
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -10,32 +10,38 @@
10
10
  ####################################################################
11
11
 
12
12
  import os, os.path as op
13
- from setuptools import setup, Extension
14
13
  import platform
15
14
  import configparser
16
15
  import numpy as np
16
+ from setuptools import setup, Extension
17
+
17
18
 
18
19
  with open('requirements.txt') as f:
19
20
  requirements = f.read().splitlines()
20
21
 
22
+ version = open("VERSION").read().strip()
21
23
  with open('numexpr/version.py', 'w') as fh:
22
- cfg = configparser.ConfigParser()
23
- cfg.read('setup.cfg')
24
- fh.write('# THIS FILE IS GENERATED BY `SETUP.PY`\n')
25
- fh.write("version = '%s'\n" % cfg['metadata']['version'])
24
+ # Create the version.py file
25
+ fh.write("# THIS FILE IS GENERATED BY `setup.py`\n")
26
+ fh.write(f"__version__ = '{version}'\n")
27
+ fh.write(f"version = '{version}'\n")
26
28
  try:
27
29
  fh.write("numpy_build_version = '%s'\n" % np.__version__)
28
30
  except ImportError:
29
31
  pass
30
32
  fh.write("platform_machine = '%s'\n" % platform.machine())
31
33
 
34
+ # Read the contents of your README file
35
+ with open('README.rst', encoding='utf-8') as f:
36
+ long_description = f.read()
37
+
32
38
  lib_dirs = []
33
39
  inc_dirs = [np.get_include()]
34
40
  libs = [] # Pre-built libraries ONLY, like python36.so
35
41
  clibs = []
36
42
  def_macros = [
37
43
  # keep in sync with minimal runtime requirement (requirements.txt)
38
- ('NPY_TARGET_VERSION', 'NPY_1_19_API_VERSION')
44
+ ('NPY_TARGET_VERSION', 'NPY_1_23_API_VERSION')
39
45
  ]
40
46
  sources = ['numexpr/interpreter.cpp',
41
47
  'numexpr/module.cpp',
@@ -98,6 +104,9 @@ def setup_package():
98
104
  )
99
105
 
100
106
  metadata = dict(
107
+ version=version,
108
+ long_description=long_description,
109
+ long_description_content_type='text/x-rst',
101
110
  install_requires=requirements,
102
111
  libraries=clibs,
103
112
  ext_modules=[
@@ -1,4 +0,0 @@
1
- # THIS FILE IS GENERATED BY `SETUP.PY`
2
- version = '2.10.0'
3
- numpy_build_version = '2.0.0rc1'
4
- platform_machine = 'AMD64'
@@ -1 +0,0 @@
1
-
@@ -1 +0,0 @@
1
- numpy>=1.19.3
@@ -1,7 +0,0 @@
1
- [build-system]
2
- requires = [
3
- "setuptools",
4
- "wheel",
5
- "numpy>=2.0.0rc1",
6
- ]
7
- build-backend = "setuptools.build_meta"
@@ -1 +0,0 @@
1
- numpy >= 1.19.3 # keep in sync with NPY_TARGET_VERSION (setup.py)
numexpr-2.10.0/setup.cfg DELETED
@@ -1,34 +0,0 @@
1
- [metadata]
2
- name = numexpr
3
- version = 2.10.0
4
- description = Fast numerical expression evaluator for NumPy
5
- author = David M. Cooke, Francesc Alted, and others
6
- maintainer = Francesc Alted
7
- maintainer_email = faltet@gmail.com
8
- url = https://github.com/pydata/numexpr
9
- long_description = file: README.rst
10
- long_description_content_type = text/x-rst
11
- license = MIT
12
- classifiers =
13
- Development Status :: 6 - Mature
14
- Intended Audience :: Financial and Insurance Industry
15
- Intended Audience :: Science/Research
16
- License :: OSI Approved :: MIT License
17
- Programming Language :: Python :: 3
18
- Programming Language :: Python :: 3.9
19
- Programming Language :: Python :: 3.10
20
- Programming Language :: Python :: 3.11
21
- Programming Language :: Python :: 3.12
22
- Operating System :: Microsoft :: Windows
23
- Operating System :: POSIX
24
- Operating System :: MacOS
25
-
26
- [options]
27
- zip_safe = False
28
- packages = find:
29
- python_requires = >=3.9
30
-
31
- [egg_info]
32
- tag_build =
33
- tag_date = 0
34
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes