diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 5d69d81..6b4a85f 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -69,7 +69,9 @@ jobs: CIBW_ARCHS_LINUX: "auto aarch64" CIBW_BEFORE_BUILD_LINUX: "yum install -y openblas-devel || apt-get install -y libopenblas-dev" CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel && vcpkg install openblas:x64-windows" - CIBW_ENVIRONMENT_WINDOWS: "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + # The engine turns MSVC /WX off under scikit-build for downstream + # builds; keep it on for our own wheels. + CIBW_ENVIRONMENT_WINDOWS: "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake SKBUILD_CMAKE_ARGS=-DSDE_WARNINGS_AS_ERRORS=ON" # Bundle openblas.dll into the wheel (auditwheel/delocate handle this on Linux/macOS) CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel} --add-path C:/vcpkg/installed/x64-windows/bin" # On free-threaded builds, assert the import did not re-enable the GIL. diff --git a/.github/workflows/conda-recipe.yml b/.github/workflows/conda-recipe.yml new file mode 100644 index 0000000..6493098 --- /dev/null +++ b/.github/workflows/conda-recipe.yml @@ -0,0 +1,37 @@ +name: conda-forge recipe + +# Builds conda.recipe/ with rattler-build against conda-forge, using the subset +# of conda-forge's pinning in conda.recipe/variants.yaml (e.g. netlib +# libblas/libcblas on Linux), as the feedstock would. + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + group: conda-recipe-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, macos-14, windows-latest ] + + steps: + - uses: actions/checkout@v5 + with: + submodules: recursive + + - uses: prefix-dev/rattler-build-action@v0.2.39 + with: + recipe-path: conda.recipe/recipe.yaml + rattler-build-version: v0.76.1 + # --experimental enables load_from_file() for the version + build-args: --experimental --channel conda-forge + artifact-name: conda-package-${{ matrix.os }} diff --git a/SparseDiffEngine b/SparseDiffEngine index d8cb9b8..9ec1164 160000 --- a/SparseDiffEngine +++ b/SparseDiffEngine @@ -1 +1 @@ -Subproject commit d8cb9b88453dae339c74755f7c3082dafaccbecc +Subproject commit 9ec1164079db7abecc1051513d84ca4da65e87be diff --git a/conda.recipe/recipe.yaml b/conda.recipe/recipe.yaml new file mode 100644 index 0000000..70394b3 --- /dev/null +++ b/conda.recipe/recipe.yaml @@ -0,0 +1,83 @@ +# Mirror of the conda-forge recipe (conda-forge/staged-recipes#34978), built +# from this checkout instead of the PyPI sdist and without the BLAS patch, so +# CI catches changes that would break the conda-forge feedstock. +context: + name: sparsediffpy + version: ${{ load_from_file("pyproject.toml").project.version }} + +package: + name: ${{ name }} + version: ${{ version }} + +source: + path: ../ + +build: + number: 0 + skip: + - match(python, "<3.11") + script: + - if: linux + then: + - export SKBUILD_CMAKE_ARGS="-DBLA_VENDOR=Generic" + # scikit-build-core otherwise falls back to "Unix Makefiles", and make + # isn't in the build env + - export CMAKE_GENERATOR=Ninja + - ${{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation + +requirements: + build: + - if: build_platform != target_platform + then: + - python + - cross-python_${{ target_platform }} + - numpy + - ${{ compiler("c") }} + - ${{ stdlib("c") }} + - cmake + - ninja + host: + - python + - pip + - scikit-build-core >=0.10 + - numpy + # macOS links the Accelerate framework + - if: linux + then: + - libblas + - libcblas + - blas-devel + - if: win + then: + - openblas + # openblas has no run_exports; libopenblas (which ships openblas.dll) + # does, so listing it here adds the runtime dependency + - libopenblas + run: + - python + - numpy >=2 + +tests: + - python: + imports: + - sparsediffpy + - sparsediffpy._sparsediffengine + pip_check: true + - files: + recipe: + - test_sparsediffpy.py + requirements: + run: + - python + - numpy + script: + - python test_sparsediffpy.py + +about: + homepage: https://github.com/SparseDifferentiation/SparseDiffPy + repository: https://github.com/SparseDifferentiation/SparseDiffPy + summary: Python bindings for SparseDiffEngine algorithmic differentiation + license: Apache-2.0 + license_file: + - LICENSE + - SparseDiffEngine/LICENSE diff --git a/conda.recipe/test_sparsediffpy.py b/conda.recipe/test_sparsediffpy.py new file mode 100644 index 0000000..49dbd09 --- /dev/null +++ b/conda.recipe/test_sparsediffpy.py @@ -0,0 +1,31 @@ +# Smoke test: value, gradient and Hessian of small expressions through the +# C bindings. The dense quad_form path goes through CBLAS, so this also +# checks that BLAS is linked and callable. +import numpy as np + +from sparsediffpy import _sparsediffengine as de + + +def check(expr, u, value, grad, hess): + prob = de.make_problem(expr, [], False) + de.problem_init_jacobian_coo(prob) + de.problem_init_hessian_coo_lower_triangular(prob) + np.testing.assert_allclose(de.problem_objective_forward(prob, u), value) + np.testing.assert_allclose(de.problem_gradient(prob), grad) + rows, cols, _ = de.get_problem_hessian_sparsity_coo(prob) + vals = de.problem_eval_hessian_vals_coo(prob, 1.0, np.zeros(0)) + H = np.zeros((u.size, u.size)) + H[rows, cols] = vals + np.testing.assert_allclose(np.tril(H), np.tril(hess)) + + +u = np.array([1.0, -2.0, 0.5]) + +x = de.make_variable(3, 1, 0, 3) +check(de.make_sum(de.make_exp(x), -1), u, np.exp(u).sum(), np.exp(u), np.diag(np.exp(u))) + +P = np.array([[2.0, 1.0, 0.0], [1.0, 3.0, 0.0], [0.0, 0.0, 4.0]]) +x = de.make_variable(3, 1, 0, 3) +check(de.make_quad_form(None, x, "dense", P.flatten(order="F"), 3), u, u @ P @ u, 2 * P @ u, 2 * P) + +print("sparsediffpy smoke test passed") diff --git a/conda.recipe/variants.yaml b/conda.recipe/variants.yaml new file mode 100644 index 0000000..2c59b55 --- /dev/null +++ b/conda.recipe/variants.yaml @@ -0,0 +1,36 @@ +# Subset of conda-forge's global pinning (conda-forge-pinning-feedstock +# recipe/conda_build_config.yaml), which rattler-build can't load directly. +# Only the oldest and newest supported Pythons are built. +c_compiler: + - if: linux + then: gcc + - if: osx + then: clang + - if: win + then: vs2022 +c_compiler_version: + - if: linux + then: "15" + - if: osx + then: "21" +c_stdlib: + - if: linux + then: sysroot + - if: osx + then: macosx_deployment_target + - if: win + then: vs +c_stdlib_version: + - if: linux + then: "2.17" + - if: osx + then: "11.0" +python: + - 3.11.* *_cpython + - 3.14.* *_cp314 +# conda-forge builds against netlib so packages link the switchable +# libblas.so.3/libcblas.so.3 +libblas: + - 3.9.* *netlib +libcblas: + - 3.9.* *netlib