pyodide/packages/numpy/test_numpy.py

346 lines
9.7 KiB
Python

import pytest
def test_numpy(selenium):
selenium.load_package("numpy")
selenium.run(
"""
import numpy
x = numpy.ones((32, 64))
"""
)
selenium.run_js(
"""
let xpy = pyodide.runPython('x');
window.x = xpy.toJs();
xpy.destroy();
"""
)
assert selenium.run_js("return x.length === 32")
for i in range(32):
assert selenium.run_js(f"return x[{i}].length == 64")
for j in range(64):
assert selenium.run_js(f"return x[{i}][{j}] == 1")
def test_typed_arrays(selenium):
selenium.load_package("numpy")
selenium.run("import numpy")
for (jstype, npytype) in (
("Int8Array", "int8"),
("Uint8Array", "uint8"),
("Uint8ClampedArray", "uint8"),
("Int16Array", "int16"),
("Uint16Array", "uint16"),
("Int32Array", "int32"),
("Uint32Array", "uint32"),
("Float32Array", "float32"),
("Float64Array", "float64"),
):
selenium.run_js(f"window.array = new {jstype}([1, 2, 3, 4]);\n")
assert selenium.run(
"from js import array\n"
"npyarray = numpy.asarray(array.to_py())\n"
f'npyarray.dtype.name == "{npytype}" '
"and npyarray == [1, 2, 3, 4]"
)
@pytest.mark.skip_pyproxy_check
@pytest.mark.parametrize("order", ("C", "F"))
@pytest.mark.parametrize(
"dtype",
(
"int8",
"uint8",
"int16",
"uint16",
"int32",
"uint32",
"int64",
"uint64",
"float32",
"float64",
),
)
def test_python2js_numpy_dtype(selenium, order, dtype):
selenium.load_package("numpy")
selenium.run("import numpy as np")
expected_result = [[[0, 1], [2, 3]], [[4, 5], [6, 7]]]
def assert_equal():
# We have to do this an element at a time, since the Selenium driver
# for Firefox does not convert TypedArrays to Python correctly
for i in range(2):
for j in range(2):
for k in range(2):
assert (
selenium.run_js(
f"return Number(pyodide.globals.get('x').toJs()[{i}][{j}][{k}])"
)
== expected_result[i][j][k]
)
selenium.run(
f"""
x = np.arange(8, dtype=np.{dtype})
x = x.reshape((2, 2, 2))
x = x.copy({order!r})
"""
)
assert_equal()
classname = selenium.run_js(
"return pyodide.globals.get('x').toJs()[0][0].constructor.name"
)
# We expect a TypedArray subclass, such as Uint8Array, but not a plain-old
# Array
assert classname.endswith("Array")
assert classname != "Array"
selenium.run(
"""
x = x.byteswap().newbyteorder()
"""
)
assert_equal()
classname = selenium.run_js(
"return pyodide.globals.get('x').toJs()[0][0].constructor.name"
)
assert classname.endswith("Array")
assert classname != "Array"
assert selenium.run("np.array([True, False])") == [True, False]
@pytest.mark.skip_pyproxy_check
def test_py2js_buffer_clear_error_flag(selenium):
selenium.load_package("numpy")
selenium.run("import numpy as np")
selenium.run("x = np.array([['string1', 'string2'], ['string3', 'string4']])")
selenium.run_js(
"""
pyodide.globals.get("x")
// Implicit assertion: this doesn't leave python error indicator set
// (automatically checked in conftest.py)
"""
)
@pytest.mark.skip_pyproxy_check
@pytest.mark.parametrize(
"dtype",
(
"int8",
"uint8",
"int16",
"uint16",
"int32",
"uint32",
"int64",
"uint64",
"float32",
"float64",
),
)
def test_python2js_numpy_scalar(selenium, dtype):
selenium.load_package("numpy")
selenium.run("import numpy as np")
selenium.run(
f"""
x = np.{dtype}(1)
"""
)
assert (
selenium.run_js(
"""
return pyodide.globals.get('x') == 1
"""
)
is True
)
selenium.run(
"""
x = x.byteswap().newbyteorder()
"""
)
assert (
selenium.run_js(
"""
return pyodide.globals.get('x') == 1
"""
)
is True
)
@pytest.mark.skip_pyproxy_check
def test_runpythonasync_numpy(selenium_standalone):
selenium_standalone.run_async(
"""
import numpy as np
x = np.zeros(5)
"""
)
for i in range(5):
assert selenium_standalone.run_js(
f"return pyodide.globals.get('x').toJs()[{i}] == 0"
)
@pytest.mark.driver_timeout(30)
def test_runwebworker_numpy(selenium_webworker_standalone):
if selenium_webworker_standalone.browser == "firefox":
pytest.xfail("Timeout in WebWorker when using numpy in Firefox 87")
output = selenium_webworker_standalone.run_webworker(
"""
import numpy as np
x = np.zeros(5)
str(x)
"""
)
assert output == "[0. 0. 0. 0. 0.]"
@pytest.mark.skip_pyproxy_check
def test_get_buffer(selenium):
selenium.run_js(
"""
await pyodide.loadPackage(['numpy']);
await pyodide.runPython(`
import numpy as np
x = np.arange(24)
z1 = x.reshape([8,3])
z2 = z1[-1::-1]
z3 = z1[::,-1::-1]
z4 = z1[-1::-1,-1::-1]
`);
for(let x of ["z1", "z2", "z3", "z4"]){
let z = pyodide.pyimport(x).getBuffer("u32");
for(let idx1 = 0; idx1 < 8; idx1++) {
for(let idx2 = 0; idx2 < 3; idx2++){
let v1 = z.data[z.offset + z.strides[0] * idx1 + z.strides[1] * idx2];
let v2 = pyodide.runPython(`repr(${x}[${idx1}, ${idx2}])`);
console.log(`${v1}, ${typeof(v1)}, ${v2}, ${typeof(v2)}, ${v1===v2}`);
if(v1.toString() !== v2){
throw new Error(`Discrepancy ${x}[${idx1}, ${idx2}]: ${v1} != ${v2}`);
}
}
}
z.release();
}
"""
)
@pytest.mark.skip_pyproxy_check
@pytest.mark.parametrize(
"arg",
[
"np.arange(6).reshape((2, -1))",
"np.arange(12).reshape((3, -1))[::2, ::2]",
"np.arange(12).reshape((3, -1))[::-1, ::-1]",
"np.arange(12).reshape((3, -1))[::, ::-1]",
"np.arange(12).reshape((3, -1))[::-1, ::]",
"np.arange(12).reshape((3, -1))[::-2, ::-2]",
"np.arange(6).reshape((2, -1)).astype(np.int8, order='C')",
"np.arange(6).reshape((2, -1)).astype(np.int8, order='F')",
"np.arange(6).reshape((2, -1, 1))",
"np.ones((1, 1))[0:0]", # shape[0] == 0
"np.ones(1)", # ndim == 0
]
+ [
f"np.arange(3).astype(np.{type_})"
for type_ in ["int8", "uint8", "int16", "int32", "float32", "float64"]
],
)
def test_get_buffer_roundtrip(selenium, arg):
selenium.run_js(
f"""
await pyodide.loadPackage(['numpy']);
await pyodide.runPython(`
import numpy as np
x = {arg}
`);
window.x_js_buf = pyodide.pyimport("x").getBuffer();
x_js_buf.length = x_js_buf.data.length;
"""
)
selenium.run_js(
"""
pyodide.runPython(`
import itertools
from unittest import TestCase
from js import x_js_buf
assert_equal = TestCase().assertEqual
assert_equal(x_js_buf.ndim, x.ndim)
assert_equal(x_js_buf.shape.to_py(), list(x.shape))
assert_equal(x_js_buf.strides.to_py(), [s/x.itemsize for s in x.data.strides])
assert_equal(x_js_buf.format, x.data.format)
if len(x) == 0:
assert x_js_buf.length == 0
else:
minoffset = 1000
maxoffset = 0
for tup in itertools.product(*[range(n) for n in x.shape]):
offset = x_js_buf.offset + sum(x*y for (x,y) in zip(tup, x_js_buf.strides))
minoffset = min(offset, minoffset)
maxoffset = max(offset, maxoffset)
assert_equal(x[tup], x_js_buf.data[offset])
assert_equal(minoffset, 0)
assert_equal(maxoffset + 1, x_js_buf.length)
x_js_buf.release()
`);
"""
)
def test_get_buffer_big_endian(selenium):
selenium.run_js(
"""
await pyodide.loadPackage(['numpy']);
window.a = await pyodide.runPython(`
import numpy as np
np.arange(24, dtype="int16").byteswap().newbyteorder()
`);
"""
)
with pytest.raises(
Exception, match="Javascript has no native support for big endian buffers"
):
selenium.run_js("a.getBuffer()")
result = selenium.run_js(
"""
let buf = a.getBuffer("i8")
let result = Array.from(buf.data);
buf.release();
a.destroy();
return result;
"""
)
assert len(result) == 48
assert result[:18] == [0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]
def test_get_buffer_error_messages(selenium):
with pytest.raises(Exception, match="Javascript has no Float16 support"):
selenium.run_js(
"""
await pyodide.loadPackage(['numpy']);
await pyodide.runPython(`
import numpy as np
x = np.ones(2, dtype=np.float16)
`);
let x = pyodide.runPython("x");
try {
x.getBuffer();
} finally {
x.destroy();
}
"""
)