Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions recipes/recipes_emscripten/numpy/build.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#!/bin/bash

set -ex

echo "PYTHON"

rm -r -f branding

# necessary for cross-compilation to point to the right env
export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig

export CFLAGS="$CFLAGS -Wno-return-type -Wno-implicit-function-declaration -msimd128 -fwasm-exceptions -s SUPPORT_LONGJMP"
export MESON_CROSS_FILE=$RECIPE_DIR/emscripten.meson.cross
export MESON_CROSS_FILE=$RECIPE_DIR/emscripten.meson.cross
export LDFLAGS="$LDFLAGS -sWASM_BIGINT -s WASM_BIGINT -fwasm-exceptions -s SUPPORT_LONGJMP"

cp $RECIPE_DIR/config/config.h.in numpy/_core/config.h.in
#
#

# otherwise "cython" is not properly executable
echo "add shebang to cython file"
Expand All @@ -22,5 +27,6 @@ sed -i 's/-fexceptions/-fwasm-exceptions/g' numpy/_core/meson.build


MESON_ARGS="-Dhave_backtrace=false" ${PYTHON} -m pip install . -vvv --no-deps --no-build-isolation \
-Csetup-args="-Dallow-noblas=true" \
-Csetup-args="-Dblas=openblas" \
-Csetup-args="-Dlapack=openblas" \
-Csetup-args="--cross-file=$MESON_CROSS_FILE"
6 changes: 5 additions & 1 deletion recipes/recipes_emscripten/numpy/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ source:
url: https://github.com/numpy/numpy/releases/download/v${{ version }}/numpy-${{ version }}.tar.gz
sha256: a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3
build:
number: 0
number: 1

files:
exclude:
Expand All @@ -35,6 +35,10 @@ requirements:
- pkg-config
- python
host:
- openblas
- python
run:
- openblas
- python
run_exports:
- numpy >=${{ default_abi_level }},<3
Expand Down
49 changes: 46 additions & 3 deletions recipes/recipes_emscripten/numpy/test_numpy.py

@jjerphan jjerphan Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using threadpoolctl to test that OpenBLAS DSO are loaded at runtime, but I am getting errors.

test_numpy.py .F....
 │ =================================== FAILURES ===================================_______________________ test_openblas_linked_at_runtime ________________________def test_openblas_linked_at_runtime():
 │         # Load NumPy's BLAS/LAPACK-backed extensions before introspecting.
 │         _ = np.dot(np.eye(2), np.ones(2))
 │     
 │         info = threadpool_info()
 │         openblas = [lib for lib in info if lib["internal_api"] == "openblas"]
 │ >       assert openblas, f"OpenBLAS not detected by threadpoolctl: {info}"E       AssertionError: OpenBLAS not detected by threadpoolctl: []
 │ E       assert []
 │ test_numpy.py:17: AssertionError=============================== warnings summary ===============================test_numpy.py::test_openblas_linked_at_runtime/lib/python3.13/site-packages/threadpoolctl.py:1129: UserWarning: Unable to import LDSO from pyodide_js._module. This should never happen.
 │     warnings.warn(

Given the UserWarning reported, I think threadpoolctl first needs to be adapted for this environment before it can report DSO and other pieces of information at runtime.

Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
import numpy as np
from numpy.testing import assert_allclose


def test_numpy():
import numpy
ones = np.ones(shape=[2, 3])
assert ones.shape == (2, 3)


# Large enough that OpenBLAS blocked kernels are exercised (not tiny fallbacks).
N = 300


def test_blas_matmul():
rng = np.random.default_rng(0)
a = rng.standard_normal((N, N))
b = rng.standard_normal((N, N))
c = a @ b
assert c.shape == (N, N)
assert_allclose(c, np.dot(a, b))
assert_allclose(c.T, b.T @ a.T)


def test_lapack_cholesky():
rng = np.random.default_rng(1)
x = rng.standard_normal((N, N))
a = x @ x.T + N * np.eye(N)
l = np.linalg.cholesky(a)
assert_allclose(l @ l.T, a)


def test_lapack_solve():
rng = np.random.default_rng(2)
a = rng.standard_normal((N, N))
b = rng.standard_normal(N)
x = np.linalg.solve(a, b)
assert_allclose(a @ x, b)


ones = numpy.ones(shape=[2,3])
assert ones.shape == (2,3)
def test_lapack_eigh():
rng = np.random.default_rng(3)
x = rng.standard_normal((N, N))
a = x @ x.T
evals, evecs = np.linalg.eigh(a)
assert evals.shape == (N,)
assert evecs.shape == (N, N)
assert_allclose(evecs.T @ evecs, np.eye(N), atol=1e-8)
assert_allclose(evecs @ np.diag(evals) @ evecs.T, a)
Loading