diff --git a/.github/workflows/CI_C_API_Python.yml b/.github/workflows/CI_C_API_Python.yml index 171c24f9..2e0aef65 100644 --- a/.github/workflows/CI_C_API_Python.yml +++ b/.github/workflows/CI_C_API_Python.yml @@ -9,15 +9,23 @@ on: jobs: test: name: Test - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: + os: [ubuntu-latest, macos-latest] python-version: - "3.10" - "3.11" - "3.12" steps: + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: sudo apt update && sudo apt install -y libeigen3-dev libopenblas-dev + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: brew install eigen openblas - uses: actions/checkout@v4 - name: Install uv and set the python version uses: astral-sh/setup-uv@v6 @@ -33,8 +41,12 @@ jobs: - name: Run uv sync run: uv sync working-directory: python + env: + SPARSEIR_USE_BLAS: 1 - name: Run tests # For example, using `pytest` run: uv run pytest tests working-directory: python + env: + SPARSEIR_USE_BLAS: 1 diff --git a/.github/workflows/PublishPyPI.yml b/.github/workflows/PublishPyPI.yml index 4ac3e56c..ce6ac2b7 100644 --- a/.github/workflows/PublishPyPI.yml +++ b/.github/workflows/PublishPyPI.yml @@ -3,6 +3,8 @@ name: Python Build & Publish (Trusted) on: push: tags: ["v*"] # 例: v0.1.0 を push で発火 + #pull_request: # for debugging + # branches: [main] # for debugging workflow_dispatch: jobs: @@ -11,14 +13,69 @@ jobs: strategy: matrix: include: + # Linux builds - os: ubuntu-latest cibw_archs: "auto" - #- os: windows-latest - # cibw_archs: "AMD64" + deployment_target: "" + openblas_path: "" + py_tag: "cp310" + - os: ubuntu-latest + cibw_archs: "auto" + deployment_target: "" + openblas_path: "" + py_tag: "cp311" + - os: ubuntu-latest + cibw_archs: "auto" + deployment_target: "" + openblas_path: "" + py_tag: "cp312" + - os: ubuntu-latest + cibw_archs: "auto" + deployment_target: "" + openblas_path: "" + py_tag: "cp313" + # macOS Intel builds + - os: macos-13 # Intel Mac + cibw_archs: "x86_64" + deployment_target: "13.0" + openblas_path: "/usr/local/opt/openblas" + py_tag: "cp310" + - os: macos-13 # Intel Mac + cibw_archs: "x86_64" + deployment_target: "13.0" + openblas_path: "/usr/local/opt/openblas" + py_tag: "cp311" + - os: macos-13 # Intel Mac + cibw_archs: "x86_64" + deployment_target: "13.0" + openblas_path: "/usr/local/opt/openblas" + py_tag: "cp312" - os: macos-13 # Intel Mac cibw_archs: "x86_64" + deployment_target: "13.0" + openblas_path: "/usr/local/opt/openblas" + py_tag: "cp313" + # macOS Apple Silicon builds + - os: macos-latest # Apple Silicon Mac + cibw_archs: "arm64" + deployment_target: "15.0" + openblas_path: "/opt/homebrew/opt/openblas" + py_tag: "cp310" + - os: macos-latest # Apple Silicon Mac + cibw_archs: "arm64" + deployment_target: "15.0" + openblas_path: "/opt/homebrew/opt/openblas" + py_tag: "cp311" + - os: macos-latest # Apple Silicon Mac + cibw_archs: "arm64" + deployment_target: "15.0" + openblas_path: "/opt/homebrew/opt/openblas" + py_tag: "cp312" - os: macos-latest # Apple Silicon Mac cibw_archs: "arm64" + deployment_target: "15.0" + openblas_path: "/opt/homebrew/opt/openblas" + py_tag: "cp313" steps: - uses: actions/checkout@v4 - name: remove python/.gitignore @@ -30,16 +87,23 @@ jobs: CIBW_DEPENDENCY_VERSIONS: "latest" CIBW_BUILD_VERBOSITY: 1 CIBW_MANYLINUX_X86_64_IMAGE: "manylinux_2_28" - CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=11.0" + CIBW_BUILD: "${{ matrix.py_tag }}-*" + CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=${{ matrix.deployment_target || '11.0' }} SPARSEIR_USE_BLAS=1" CIBW_ARCHS: ${{ matrix.cibw_archs }} CIBW_SKIP: "*-manylinux_i686 *-musllinux_i686" - # CIBW_BUILD: "cp312-*" + # Install OpenBLAS using micromamba (conda-forge) - separate for manylinux and musllinux + CIBW_BEFORE_ALL_MANYLINUX: "curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba && ./bin/micromamba create -y -p /opt/openblas && ./bin/micromamba install -y -c conda-forge openblas -p /opt/openblas --no-deps" + CIBW_BEFORE_ALL_MUSLLINUX: "apk add --no-cache bzip2 && curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba && ./bin/micromamba create -y -p /opt/openblas && ./bin/micromamba install -y -c conda-forge openblas -p /opt/openblas --no-deps" + CIBW_BEFORE_ALL_MACOS: "brew install openblas" + # Set environment variables to help CMake find OpenBLAS + CIBW_ENVIRONMENT_MANYLINUX: "SPARSEIR_USE_BLAS=1" + CIBW_ENVIRONMENT_MUSLLINUX: "SPARSEIR_USE_BLAS=1" with: package-dir: ./python output-dir: dist - uses: actions/upload-artifact@v4 with: - name: wheels-${{ matrix.os }}-${{ matrix.cibw_archs }} + name: wheels-${{ matrix.os }}-${{ matrix.cibw_archs }}-${{ matrix.py_tag }} path: dist/* publish-testpypi: diff --git a/python/README.md b/python/README.md index 0e5feee5..9fea65cf 100644 --- a/python/README.md +++ b/python/README.md @@ -9,6 +9,13 @@ This is a low-level binding for the [libsparseir](https://github.com/SpM-lab/lib - C++11 compatible compiler - numpy +### Optional Dependencies + +- **OpenBLAS** (recommended for better performance) + - macOS: `brew install openblas` + - Ubuntu/Debian: `sudo apt install libopenblas-dev` + - CentOS/RHEL: `sudo yum install openblas-devel` + ## Build ### Install Dependencies and Build @@ -22,6 +29,30 @@ This will: - Build the C++ libsparseir library using CMake - Install the Python package in development mode +### Build with OpenBLAS Support + +To enable OpenBLAS support for improved performance: + +```bash +# Set environment variable to enable BLAS +export SPARSEIR_USE_BLAS=1 +uv sync +``` + +Or for a single build: + +```bash +SPARSEIR_USE_BLAS=1 uv sync +``` + +The build system will automatically detect OpenBLAS if it's installed in standard locations. If OpenBLAS is installed in a custom location, you may need to set additional environment variables: + +```bash +export CMAKE_PREFIX_PATH="/path/to/openblas" +export SPARSEIR_USE_BLAS=1 +uv sync +``` + ### Clean Build Artifacts To remove build artifacts and files copied from the parent directory: @@ -35,3 +66,45 @@ This will remove: - Copied source files: `include/`, `src/`, `fortran/`, `cmake/`, `CMakeLists.txt` - Compiled libraries: `pylibsparseir/*.so`, `pylibsparseir/*.dylib`, `pylibsparseir/*.dll` - Cache directories: `pylibsparseir/__pycache__` + +## Performance Notes + +### BLAS Support + +This package supports BLAS libraries for improved linear algebra performance: + +- **With OpenBLAS**: Significant performance improvements for matrix operations +- **Without BLAS**: Uses Eigen's built-in implementations (still efficient, but slower for large matrices) + +The build system will automatically detect and use OpenBLAS if available. You can verify BLAS support by checking the build output for messages like: + +``` +BLAS support enabled +Found OpenBLAS at: /opt/homebrew/opt/openblas +``` + +### Troubleshooting + +**Build fails with "Could NOT find BLAS":** +```bash +# Install OpenBLAS first +brew install openblas # macOS +sudo apt install libopenblas-dev # Ubuntu + +# Then force BLAS detection +SPARSEIR_USE_BLAS=1 uv sync +``` + +**OpenBLAS not detected automatically:** +```bash +# Set CMake prefix path manually +export CMAKE_PREFIX_PATH="/usr/local/opt/openblas" # or your OpenBLAS path +export SPARSEIR_USE_BLAS=1 +uv sync +``` + +**Verify BLAS support in built package:** +```python +import pylibsparseir +# Check build logs or library dependencies to confirm BLAS linking +``` diff --git a/python/pyproject.toml b/python/pyproject.toml index 1f01cae9..e405df2a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pylibsparseir" -version = "0.1.1" +version = "0.1.2" description = "Python bindings for the libsparseir library, providing efficient sparse intermediate representation for many-body physics calculations" readme = "README.md" requires-python = ">=3.10" diff --git a/python/setup.py b/python/setup.py index 732e39ef..ec4a788a 100644 --- a/python/setup.py +++ b/python/setup.py @@ -114,8 +114,85 @@ def run(self): '-DCMAKE_CXX_STANDARD_REQUIRED=ON', ] - # Add architecture-specific flags for macOS + # Import platform module here before using it import platform + + # Only enable BLAS if we can find it or if explicitly requested + enable_blas = os.environ.get('SPARSEIR_USE_BLAS', '').lower() in ('1', 'on', 'true', 'yes') + if not enable_blas: + # Try to detect if BLAS is available + blas_paths = [ + '/usr/lib64/openblas', + '/usr/lib/openblas', + '/usr/local/lib64/openblas', + '/usr/local/lib/openblas', + '/opt/homebrew/opt/openblas/lib', + '/usr/local/opt/openblas/lib' + ] + + for path in blas_paths: + if os.path.exists(path): + enable_blas = True + print(f"Found BLAS library path: {path}", flush=True) + break + + if enable_blas: + cmake_args.append('-DSPARSEIR_USE_BLAS=ON') + print("BLAS support enabled", flush=True) + + # Try to detect and prefer OpenBLAS + openblas_found = False + + # Check common OpenBLAS installation paths + openblas_paths = [] + if platform.system() == 'Darwin': + openblas_paths = [ + '/opt/homebrew/opt/openblas', + '/usr/local/opt/openblas', + '/opt/local' # MacPorts + ] + else: # Linux and others + openblas_paths = [ + '/usr/local', + '/usr', + '/opt/openblas', + '/usr/lib64/openblas', + '/usr/lib/openblas' + ] + + # Look for OpenBLAS + for path in openblas_paths: + if path.endswith('openblas'): + # Direct path to OpenBLAS directory + lib_path = path + include_path = path.replace('lib', 'include').replace('openblas', '') + else: + lib_path = os.path.join(path, 'lib') + include_path = os.path.join(path, 'include') + + # Check for OpenBLAS library + possible_libs = [ + os.path.join(lib_path, 'libopenblas.dylib'), # macOS + os.path.join(lib_path, 'libopenblas.so'), # Linux + os.path.join(lib_path, 'libopenblas.a'), # Static + ] + + if any(os.path.exists(lib) for lib in possible_libs): + print(f"Found OpenBLAS at: {path}", flush=True) + cmake_args.extend([ + f'-DCMAKE_PREFIX_PATH={path}', + f'-DBLAS_ROOT={path}', + ]) + openblas_found = True + break + + if not openblas_found: + print("OpenBLAS not found in standard locations, using system BLAS", flush=True) + else: + print("BLAS support disabled - no BLAS libraries found", flush=True) + openblas_found = False + + # Add architecture-specific flags for macOS if platform.system() == 'Darwin': # Get the target architecture from environment or system target_arch = os.environ.get('ARCHFLAGS', '').replace('-arch ', '')