Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BLD: Vendor OpenMP binary in wheel #39

Merged
merged 10 commits into from
Feb 25, 2024
1 change: 1 addition & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CompileFlags:
Add: [-Wall -Wextra -Wunused-variable -Wunused-const-variable]
Remove: [-fopenmp]
78 changes: 78 additions & 0 deletions .github/workflows/nonvendored_wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Non-vendored Wheels

on:
workflow_dispatch:
pull_request:
branches:
- stable
release:
types:
- published

jobs:
build_sdist:
name: Build SDist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true

- uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install deps
run: python -m pip install twine build

- name: Build SDist
run: python -m build -s

- name: Check metadata
run: twine check dist/*

- uses: actions/upload-artifact@v4
with:
name: artifact-sdist
path: dist/*.tar.gz


build_wheels:
name: Wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- uses: actions/checkout@v4
with:
submodules: true

- uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install cibuildwheel
run: python -m pip install cibuildwheel

- name: Build wheel
run: python -m cibuildwheel --output-dir wheelhouse
env:
CIBW_ENVIRONMENT: CMAKE_ARGS="-DDIPTEST_ENABLE_ARCH_FLAGS=OFF -DDIPTEST_ENABLE_OPENMP=OFF"
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="10.13" CMAKE_ARGS="-DDIPTEST_ENABLE_ARCH_FLAGS=OFF -DDIPTEST_ENABLE_OPENMP=OFF"

- name: Show files
run: ls -lh wheelhouse
shell: bash

- name: Verify clean directory
run: git diff --exit-code
shell: bash

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: artifact-${{ matrix.os }}
path: wheelhouse/*.whl
104 changes: 71 additions & 33 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
name: Wheels
name: Build and Publish

on:
workflow_dispatch:
pull_request:
push:
branches:
- stable
- main
release:
types:
- published
Expand All @@ -18,18 +19,11 @@ jobs:
with:
submodules: true

- uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install deps
run: python -m pip install twine build

- name: Build SDist
run: python -m build -s
run: pipx run build --sdist

- name: Check metadata
run: twine check dist/*
run: pipx run twine check dist/*

- uses: actions/upload-artifact@v4
with:
Expand All @@ -43,35 +37,26 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
- uses: actions/checkout@v4
with:
submodules: true

- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: actions/setup-python@v3

- name: Install cibuildwheel
run: python -m pip install cibuildwheel
run: python -m pip install cibuildwheel==2.16.5

- name: Build wheel
- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
env:
CIBW_ENVIRONMENT: MACOSX_DEPLOYMENT_TARGET="10.13" SKBUILD_CMAKE_ARGS="-DDIPTEST_ENABLE_ARCH_FLAGS=OFF"
CIBW_BUILD: 'cp38-* cp39-* cp310-* cp311-* cp312-*'
CIBW_TEST_EXTRAS: test
CIBW_TEST_COMMAND: python -m pytest {project}/tests/test_diptest.py
CIBW_ARCHS: "auto64"
CIBW_ARCHS_MACOS: "x86_64 arm64"
# Skip 32-bit builds
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_x86_64"

- name: Show files
run: ls -lh wheelhouse
shell: bash
CIBW_ENVIRONMENT: CMAKE_ARGS="-DDIPTEST_ENABLE_ARCH_FLAGS=OFF -DDIPTEST_ENABLE_OPENMP=ON"
CIBW_TEST_COMMAND: pytest {project}/tests && python -c "from diptest import _has_openmp_support; assert _has_openmp_support"
# only build for x86_64; ARM wheels are build seperately
CIBW_ARCHS_MACOS: "x86_64"
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="10.13" CMAKE_ARGS="-DDIPTEST_ENABLE_ARCH_FLAGS=OFF -DDIPTEST_ENABLE_OPENMP=ON"

- name: Verify clean directory
run: git diff --exit-code
Expand All @@ -83,16 +68,69 @@ jobs:
name: artifact-${{ matrix.os }}
path: wheelhouse/*.whl

build_arm_wheels:
name: MacOS ARM wheels
runs-on: macos-latest
strategy:
fail-fast: false

steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Cache OpenMP repo
id: clone-openmp
uses: actions/cache@v4
with:
path: llvm-project
key: macos-arm-openmp

- name: Clone OpenMP repo
if: steps.clone-openmp.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch llvmorg-17.0.6 https://github.com/llvm/llvm-project

- name: Build OpenMP
shell: bash
run: |
mv llvm-project/openmp ./openmp
mv llvm-project/cmake ./cmake
rm -rf llvm-project
mkdir openmp_build
cmake -S openmp -B openmp_build \
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DCMAKE_OSX_DEPLOYMENT_TARGET="10.13" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_INSTALL_PREFIX="/usr/local"
cmake --build openmp_build --target install --config Release

- uses: pypa/cibuildwheel@v2.16.2
env:
# only build for ARM; x86_64 wheels are build seperately
CIBW_ARCHS: "arm64"
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="10.13" CMAKE_ARGS="-DDIPTEST_ENABLE_ARCH_FLAGS=OFF -DDIPTEST_ENABLE_OPENMP=ON -DOpenMP_ROOT=/usr/local"

- name: Verify clean directory
run: git diff --exit-code
shell: bash

- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: artifact-macos-arm
path: wheelhouse/*.whl

upload_all:
name: Upload if release
needs: [build_wheels, build_sdist]
needs: [build_sdist, build_wheels, build_arm_wheels]
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'

steps:
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: actions/setup-python@v4

- uses: actions/download-artifact@v4
with:
Expand Down
19 changes: 18 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,24 @@ target_compile_options(

if(DIPTEST_ENABLE_OPENMP)
message(STATUS "diptest: Building with OpenMP support")
find_package(OpenMP REQUIRED)
if (APPLE)
find_package(OpenMP)
if (NOT OpenMP_FOUND)
if(NOT DEFINED ${HOMEBREW_PREFIX})
if(DEFINED ENV{HOMEBREW_PREFIX} AND IS_DIRECTORY ENV{HOMEBREW_PREFIX})
set(HOMEBREW_PREFIX ENV{HOMEBREW_PREFIX})
elseif(IS_DIRECTORY /opt/homebrew)
set(HOMEBREW_PREFIX /opt/homebrew)
else()
set(HOMEBREW_PREFIX /usr/local)
endif()
endif()
set(OpenMP_ROOT ${HOMEBREW_PREFIX}/opt/libomp)
find_package(OpenMP REQUIRED)
endif()
else()
find_package(OpenMP REQUIRED)
endif()
target_compile_definitions(_diptest_core
PUBLIC DIPTEST_HAS_OPENMP_SUPPORT=TRUE)
target_link_libraries(_diptest_core PRIVATE OpenMP::OpenMP_CXX)
Expand Down
86 changes: 51 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,35 @@ unimodal distribution function that minimizes that maximum difference. Other
than unimodality, it makes no further assumptions about the form of the null
distribution.

## Usage

This library provides two functions:
* `dipstat`
* `diptest`

The first only computes Hartigan's dip statistic. `diptest` computes both the
statistic and the p-value. The p-value can be computed using interpolation of a
critical value table (default) or by bootstrapping the null hypothesis.
Note that for larger samples (N > 1e5) this is quite compute and memory intensive.

```python3
import numpy as np
import diptest

# generate some bimodal random draws
N = 1000
hN = N // 2
x = np.empty(N, dtype=np.float64)
x[:hN] = np.random.normal(0.4, 1.0, hN)
x[hN:] = np.random.normal(-0.4, 1.0, hN)

# only the dip statistic
dip = diptest.dipstat(x)

# both the dip statistic and p-value
dip, pval = diptest.diptest(x)
```

## Dependencies
* `numpy`
* [Optional] `OpenMP`
Expand All @@ -33,11 +62,26 @@ diptest can be installed from PyPi using:

Wheels containing the pre-compiled extension are available for:

- Windows x84-64 - CPython 3.7 - 3.12
- Linux x84-64 - CPython 3.7 - 3.12
- MacOS x84-64 - CPython 3.7 - 3.12
- Windows x84-64 - CPython 3.8 - 3.12
- Linux x84-64 - CPython 3.8 - 3.12
- MacOS x84-64 - CPython 3.8 - 3.12
- MacOS ARM-64 - CPython 3.8 - 3.12

Note that the wheels vendor/ships OpenMP with the extension to provide parallelisation out-of-the-box.
If you run into issue with multiple versions of OpenMP being loaded you have two options: build from source or install a non-bundled wheel.

### Non-bundled wheels

We provide the same wheels without OpenMP bundled here: https://github.com/RUrlus/diptest/releases
You than install the wheel that corresponds to your Python and OS.
For example, for CPython 3.11 and MacOS ARM:

```shell
pip install diptest-0.8.0-cp311-cp311-macosx_11_0_arm64.whl
```

### Building from source

If you have a C/C++ compiler available it is advised to install without
the wheel as this enables architecture specific optimisations.

Expand All @@ -55,11 +99,12 @@ Compatible compilers through Pybind11:
- NVCC (CUDA 11.0 tested in CI)
- NVIDIA PGI (20.9 tested in CI)

#### Enable OpenMP
#### Disable OpenMP

To disable OpenMP use:

To enable OpenMP use:
```bash
SKBUILD_CMAKE_ARGS="-DDIPTEST_ENABLE_OPENMP=ON" pip install diptest --no-binary diptest
SKBUILD_CMAKE_ARGS="-DDIPTEST_ENABLE_OPENMP=OFF" pip install diptest --no-binary diptest
```

#### Debug installation
Expand All @@ -80,35 +125,6 @@ then call the function with debug argument set to a value greater than zero:
diptest(x, debug=1)
```

## Usage

This library provides two functions:
* `dipstat`
* `diptest`

The first only computes Hartigan's dip statistic. `diptest` computes both the
statistic and the p-value. The p-value can be computed using interpolation of a
critical value table (default) or by bootstrapping the null hypothesis.
Note that for larger samples (N > 1e5) this is quite compute and memory intensive.

```python3
import numpy as np
import diptest

# generate some bimodal random draws
N = 1000
hN = N // 2
x = np.empty(N, dtype=np.float64)
x[:hN] = np.random.normal(0.4, 1.0, hN)
x[hN:] = np.random.normal(-0.4, 1.0, hN)

# only the dip statistic
dip = diptest.dipstat(x)

# both the dip statistic and p-value
dip, pval = diptest.diptest(x)
```

## References

Hartigan, J. A., & Hartigan, P. M. (1985). The Dip Test of Unimodality. The
Expand Down
16 changes: 16 additions & 0 deletions mbuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cmake -S . -G Ninja -B build \
-DSKBUILD_PROJECT_NAME="diptest" \
-DSKBUILD_PROJECT_VERSION="0.8.0" \
-DDIPTEST_MBUILD=ON \
-DDIPTEST_CPP_STANDARD="11" \
-DDIPTEST_ENABLE_DEVMODE=ON \
-DDIPTEST_ENABLE_DEBUG=OFF \
-DDIPTEST_ENABLE_OPENMP=ON \
-DDIPTEST_ENABLE_EXT_TESTS=OFF \
-DDIPTEST_ENABLE_ARCH_FLAGS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DOpenMP_ROOT=$(brew --prefix)/opt/libomp \
-Dpybind11_DIR=$(python3 -c "import pybind11; print(pybind11.get_cmake_dir())") \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON

cmake --build build --target install --config Release --parallel 4
Loading
Loading