Skip to content

Commit 65219d8

Browse files
Changes due to new numpy scalar promotion rules
1. Changed autocaster due to new promotion rules With "weak promotion" of python types in Numpy 2.0, the statement `1.1 == np.asarray(1.1).astype('float32')` is True, whereas in Numpy 1.26, it was false. However, in numpy 1.26, `1.1 == np.asarray([1.1]).astype('float32')` was true, so the scalar behavior and array behavior are the same in Numpy 2.0, while they were different in numpy 1.26. Essentially, in Numpy 2.0, if python floats are used in operations with numpy floats or arrays, then the type of the numpy object will be used (i.e. the python value will be treated as the type of the numpy objects). To preserve the behavior of `NumpyAutocaster` from numpy <= 1.26, I've added an explicit conversion of the value to be converted to a numpy type using `np.asarray` during the check that decides what dtype to cast to. 2. Updates due to new numpy conversion rules for out-of-bounds python ints In numpy 2.0, out of bounds python ints will not be automatically converted, and will raise an `OverflowError` instead. For instance, converting 255 to int8 will raise an error, instead of returning -1. To explicitly force conversion, we must use `np.asarray(value).astype(dtype)`, rather than `np.asarray(value, dtype=dtype)`. The code in `TensorType.filter` has been changed to the new recommended way to downcast, and the error type caught by some tests has been changed to OverflowError from TypeError
1 parent c84309c commit 65219d8

File tree

6 files changed

+26
-12
lines changed

6 files changed

+26
-12
lines changed

pytensor/scalar/basic.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ def __call__(self, x):
184184

185185
for dtype in try_dtypes:
186186
x_ = np.asarray(x).astype(dtype=dtype)
187-
if np.all(x == x_):
187+
if np.all(
188+
np.asarray(x) == x_
189+
): # use np.asarray(x) to match TensorType.filter
188190
break
189191
# returns either an exact x_==x, or the last cast x_
190192
return x_

pytensor/tensor/type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def filter(self, data, strict=False, allow_downcast=None) -> np.ndarray:
177177
else:
178178
if allow_downcast:
179179
# Convert to self.dtype, regardless of the type of data
180-
data = np.asarray(data, dtype=self.dtype)
180+
data = np.asarray(data).astype(self.dtype)
181181
# TODO: consider to pad shape with ones to make it consistent
182182
# with self.broadcastable... like vector->row type thing
183183
else:

tests/compile/function/test_function.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
floatX = "float32"
2727

2828

29+
if np.lib.NumpyVersion(np.__version__) >= "2.0.0rc1":
30+
uint_overflow_error = OverflowError
31+
else:
32+
uint_overflow_error = TypeError
33+
34+
2935
def test_function_dump():
3036
v = vector()
3137
fct1 = function([v], v + 1)
@@ -166,12 +172,12 @@ def test_in_allow_downcast_int(self):
166172
# Value too big for a, silently ignored
167173
assert np.array_equal(f([2**20], np.ones(1, dtype="int8"), 1), [2])
168174

169-
# Value too big for b, raises TypeError
170-
with pytest.raises(TypeError):
175+
# Value too big for b, raises OverflowError (in numpy >= 2.0... TypeError in numpy < 2.0)
176+
with pytest.raises(uint_overflow_error):
171177
f([3], [312], 1)
172178

173-
# Value too big for c, raises TypeError
174-
with pytest.raises(TypeError):
179+
# Value too big for c, raises OverflowError
180+
with pytest.raises(uint_overflow_error):
175181
f([3], [6], 806)
176182

177183
def test_in_allow_downcast_floatX(self):

tests/compile/function/test_pfunc.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ def data_of(s):
3434
return s.container.storage[0]
3535

3636

37+
if np.lib.NumpyVersion(np.__version__) >= "2.0.0rc1":
38+
uint_overflow_error = OverflowError
39+
else:
40+
uint_overflow_error = TypeError
41+
42+
3743
class TestPfunc:
3844
def test_errors(self):
3945
a = lscalar()
@@ -237,12 +243,12 @@ def test_param_allow_downcast_int(self):
237243
# Value too big for a, silently ignored
238244
assert np.all(f([2**20], np.ones(1, dtype="int8"), 1) == 2)
239245

240-
# Value too big for b, raises TypeError
241-
with pytest.raises(TypeError):
246+
# Value too big for b, raises OverflowError in numpy >= 2.0, TypeError in numpy <2.0
247+
with pytest.raises(uint_overflow_error):
242248
f([3], [312], 1)
243249

244-
# Value too big for c, raises TypeError
245-
with pytest.raises(TypeError):
250+
# Value too big for c, raises OverflowError in numpy >= 2.0, TypeError in numpy <2.0
251+
with pytest.raises(uint_overflow_error):
246252
f([3], [6], 806)
247253

248254
def test_param_allow_downcast_floatX(self):
@@ -334,6 +340,7 @@ def test_allow_input_downcast_int(self):
334340
h = pfunc([a, b, c], (a + b + c)) # Default: allow_input_downcast=None
335341
# Everything here should behave like with False
336342
assert np.all(h([3], [6], 0) == 9)
343+
337344
with pytest.raises(TypeError):
338345
h([3], np.array([6], dtype="int16"), 0)
339346

tests/tensor/test_basic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3198,7 +3198,6 @@ def test_autocast_custom():
31983198
assert (dvector() + 1.1).dtype == "float64"
31993199
assert (fvector() + np.float32(1.1)).dtype == "float32"
32003200
assert (fvector() + np.float64(1.1)).dtype == "float64"
3201-
assert (fvector() + 1.1).dtype == config.floatX
32023201
assert (lvector() + np.int64(1)).dtype == "int64"
32033202
assert (lvector() + np.int32(1)).dtype == "int64"
32043203
assert (lvector() + np.int16(1)).dtype == "int64"

tests/tensor/test_io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_memmap(self):
4949
path = Variable(Generic(), None)
5050
x = load(path, "int32", (None,), mmap_mode="c")
5151
fn = function([path], x)
52-
assert type(fn(self.filename)) == np.memmap
52+
assert isinstance(fn(self.filename), np.memmap)
5353

5454
def teardown_method(self):
5555
(pytensor.config.compiledir / "_test.npy").unlink()

0 commit comments

Comments
 (0)