diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92014b84..34e5617f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,19 +28,19 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive - - uses: conda-incubator/setup-miniconda@v3 + - uses: prefix-dev/setup-pixi@v0.8.8 with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - miniforge-version: latest - channels: conda-forge,anaconda + cache: false + pixi-version: v0.46.0 + - name: Add .pixi/envs/default to the $PATH + run: echo "$(pwd)/.pixi/envs/default/bin" >> $GITHUB_PATH + shell: bash - name: Install dependencies - run: | - conda install scipy numpy pytest 'setuptools<=60' + run: pixi add scipy numpy pip pytest meson meson-python openblas python==${{ matrix.python-version }} - name: Test run: | - python legacy_setup.py install --scs --openmp - pytest + pixi r python -m pip install -v . --no-build-isolation -Csetup-args=-Duse_openmp=true + pixi r pytest rm -rf build/ build_mkl: @@ -53,7 +53,7 @@ jobs: matrix: # macos-13 runners have intel chips. macos-14 and above # runners have Apple silicon chips. - os: [ ubuntu-latest, macos-13, windows-latest ] + os: [ ubuntu-latest ] python-version: [ 3.9, "3.10", "3.11", "3.12", "3.13"] link_mkl: [true] @@ -70,12 +70,13 @@ jobs: shell: bash run: | echo "PYTHON_SUBVERSION=$(echo $PYTHON_VERSION | cut -c 3-)" >> $GITHUB_ENV - - uses: conda-incubator/setup-miniconda@v3 + - uses: prefix-dev/setup-pixi@v0.8.8 with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - miniforge-version: latest - channels: conda-forge,anaconda + cache: false + pixi-version: v0.46.0 + - name: Add .pixi/envs/default to the $PATH + run: echo "$(pwd)/.pixi/envs/default/bin" >> $GITHUB_PATH + shell: bash - name: Install dependencies run: | if [[ "$LINK_MKL" == "true" ]]; then @@ -83,26 +84,19 @@ jobs: else BLAS_PKGS="blas-devel=*=*openblas" fi - if [[ "$PYTHON_VERSION" == "3.9" ]]; then - conda install scipy=1.5 numpy=1.19 pytest $BLAS_PKGS pkg-config - elif [[ "$PYTHON_VERSION" == "3.10" ]]; then - conda install scipy=1.7 numpy=1.21 pytest $BLAS_PKGS pkg-config - elif [[ "$PYTHON_VERSION" == "3.11" ]]; then - conda install scipy=1.9.3 numpy=1.23.4 pytest $BLAS_PKGS pkg-config - elif [[ "$PYTHON_VERSION" == "3.12" || "$PYTHON_VERSION" == "3.13" ]]; then - conda install scipy numpy pytest $BLAS_PKGS pkg-config - fi + pixi add $BLAS_PKGS pip scipy numpy pytest mkl mkl-devel pkg-config meson meson-python - name: Build run: | - python -c "import numpy as np; print('NUMPY BLAS INFOS'); print(np.show_config())" + pixi r python -c "import numpy as np;print(np.show_config())" if [[ "$LINK_MKL" == "true" ]]; then - python -m pip install --verbose -Csetup-args=-Dlink_mkl=true . + + pixi r python -m pip install --verbose --no-build-isolation -Csetup-args=-Dlink_mkl=true . else - python -m pip install --verbose . + pixi r python -m pip install --verbose --no-build-isolation . fi - name: Test run: | - pytest + pixi r pytest rm -rf build/ # from here to end it's a copy-paste, with few changes, of diff --git a/.gitignore b/.gitignore index 77784e71..7b0df7db 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ out *.jar *.egg-info *.ipynb_checkpoints/ + +# pixi environments +.pixi +*.egg-info diff --git a/meson.build b/meson.build index d428a164..fe0e37c3 100644 --- a/meson.build +++ b/meson.build @@ -3,13 +3,33 @@ project('scs', 'c') py = import('python').find_installation(pure: false) cc = meson.get_compiler('c') -blas_deps = [] +fs = import('fs') +# Check for submodule before doing anything else +if not fs.exists('scs_source/README.md') + error('Missing the `scs_source` submodule! Run `git submodule update --init` to fix this.') +endif + +# Simpler check for numpy +incdir_numpy = run_command(py,['-c', +'''import os +import numpy as np +try: + incdir = os.path.relpath(np.get_include()) +except Exception: + incdir = np.get_include() +print(incdir) +'''], check: true).stdout().strip() +# Get BLAS +blas_deps = [] if get_option('link_mkl') blas_deps = [cc.find_library('mkl_rt', required : false)] if not blas_deps[0].found() blas_deps = [dependency('mkl-sdl', required : false)] endif + if not blas_deps[0].found() + blas_deps = [ dependency('mkl-dynamic-ilp64-iomp', required: false) ] + endif else if host_machine.system() == 'darwin' blas_deps = [dependency('Accelerate')] @@ -49,147 +69,165 @@ if not blas_deps[0].found() and not get_option('sdist_mode') error('OpenBLAS or Netlib BLAS/CBLAS is required on all platforms, and was not found.') endif -fs = import('fs') -if not fs.exists('scs_source/README.md') - error('Missing the `scs_source` submodule! Run `git submodule update --init` to fix this.') +# Common +common_c_args = cc.get_supported_arguments('-Wno-unused-result') + [ + '-DPYTHON', '-DCTRLC=1' +] +common_includes = [ + 'scs', + 'scs_source/include', + 'scs_source/linsys', + incdir_numpy +] +_deps = [blas_deps] +if get_option('use_openmp') + _deps += dependency('openmp') endif -incdir_numpy = run_command(py,['-c', -'''import os -import numpy as np -try: - incdir = os.path.relpath(np.get_include()) -except Exception: - incdir = np.get_include() -print(incdir) -'''], check: true).stdout().strip() +is_linux = host_machine.system() == 'linux' +if is_linux + _deps += cc.find_library('rt', required : true) +endif -# rw.c emits a lot of -Wunused-result warnings, silence them for now: -c_args = cc.get_supported_arguments('-Wno-unused-result') +if get_option('use_lapack') + common_c_args += '-DUSE_LAPACK=1' +endif +if get_option('use_singleprec') + common_c_args += '-DSFLOAT=1' +endif -py.extension_module( - '_scs_direct', +if get_option('use_blas64') + common_c_args += '-DBLAS64=1' +endif - 'scs/scspy.c', - 'scs_source/linsys/cpu/direct/private.c', - - # scs_source/src: - 'scs_source/src/aa.c', - 'scs_source/src/cones.c', - 'scs_source/src/ctrlc.c', - 'scs_source/src/exp_cone.c', - 'scs_source/src/linalg.c', - 'scs_source/src/normalize.c', - 'scs_source/src/rw.c', - 'scs_source/src/scs_version.c', - 'scs_source/src/scs.c', - 'scs_source/src/util.c', - - # scs_source/linsys: - 'scs_source/linsys/scs_matrix.c', - 'scs_source/linsys/csparse.c', - - # scs_source/linsys/external/qdldl: - 'scs_source/linsys/external/qdldl/qdldl.c', - - # scs_source/linsys/external/amd: - 'scs_source/linsys/external/amd/amd_1.c', - 'scs_source/linsys/external/amd/amd_2.c', - 'scs_source/linsys/external/amd/amd_aat.c', - 'scs_source/linsys/external/amd/amd_control.c', - 'scs_source/linsys/external/amd/amd_defaults.c', - 'scs_source/linsys/external/amd/amd_dump.c', - 'scs_source/linsys/external/amd/amd_global.c', - 'scs_source/linsys/external/amd/amd_info.c', - 'scs_source/linsys/external/amd/amd_order.c', - 'scs_source/linsys/external/amd/amd_post_tree.c', - 'scs_source/linsys/external/amd/amd_postorder.c', - 'scs_source/linsys/external/amd/amd_preprocess.c', - 'scs_source/linsys/external/amd/amd_valid.c', - 'scs_source/linsys/external/amd/SuiteSparse_config.c', - - include_directories : [ - 'scs', - 'scs_source/include', - 'scs_source/linsys', - 'scs_source/linsys/cpu/direct', - 'scs_source/linsys/external/qdldl', - 'scs_source/linsys/external/amd', - incdir_numpy], +if get_option('use_extraverbose') + common_c_args += '-DVERBOSITY=999' +endif + +is_gpu_build = get_option('use_gpu') +use_32bit_ints = get_option('int32') + +if is_gpu_build and not use_32bit_ints + error('GPU builds require 32-bit integers. Please re-run Meson with -Dint32=true') +endif + +if not use_32bit_ints + common_c_args += '-DDLONG=1' +endif + +cuda_dep = dependency('cuda', required: is_gpu_build) +if is_gpu_build and not cuda_dep.found() + error('GPU build requested but CUDA toolkit was not found.') +endif + +if is_gpu_build + gpu_c_args = common_c_args + ['-DPY_GPU=1', '-DINDIRECT=1'] + deps += cuda_dep + if get_option('gpu_atrans') + gpu_c_args += '-DGPU_TRANSPOSE_MAT=1' + endif + ext_modules += py.extension_module( + '_scs_gpu', + common_sources, + # In Meson, sources with a `.cu` extension are compiled with nvcc by default. + # To compile `.c` files with nvcc, they must be explicitly targeted. + # It is strongly recommended to rename CUDA-C files to `.cu`. + fs.glob('scs_source/linsys/gpu/*.c'), + fs.glob('scs_source/linsys/gpu/indirect/*.c'), + c_args: gpu_c_args, + dependencies: deps, + include_directories: [ + common_includes, 'scs_source/linsys/gpu/', 'scs_source/linsys/gpu/indirect' + ], install: true, - c_args: c_args + ['-DPYTHON', '-DCTRLC=1', '-DUSE_LAPACK=1', '-DDLONG=1'], - dependencies: blas_deps, + install_dir: scs_dir, + ) +endif + +# Sources +scs_core_sources = files( + 'scs_source/src/aa.c', + 'scs_source/src/cones.c', + 'scs_source/src/ctrlc.c', + 'scs_source/src/exp_cone.c', + 'scs_source/src/linalg.c', + 'scs_source/src/normalize.c', + 'scs_source/src/rw.c', + 'scs_source/src/scs_version.c', + 'scs_source/src/scs.c', + 'scs_source/src/util.c' +) + +common_linsys_sources = files( + 'scs_source/linsys/scs_matrix.c', + 'scs_source/linsys/csparse.c' +) + +amd_sources = files( + 'scs_source/linsys/external/amd/amd_1.c', + 'scs_source/linsys/external/amd/amd_2.c', + 'scs_source/linsys/external/amd/amd_aat.c', + 'scs_source/linsys/external/amd/amd_control.c', + 'scs_source/linsys/external/amd/amd_defaults.c', + 'scs_source/linsys/external/amd/amd_dump.c', + 'scs_source/linsys/external/amd/amd_global.c', + 'scs_source/linsys/external/amd/amd_info.c', + 'scs_source/linsys/external/amd/amd_order.c', + 'scs_source/linsys/external/amd/amd_post_tree.c', + 'scs_source/linsys/external/amd/amd_postorder.c', + 'scs_source/linsys/external/amd/amd_preprocess.c', + 'scs_source/linsys/external/amd/amd_valid.c', + 'scs_source/linsys/external/amd/SuiteSparse_config.c' ) +# Build modules +scs_dir = py.get_install_dir() / 'scs' py.extension_module( - '_scs_indirect', + '_scs_direct', + 'scs/scspy.c', + 'scs_source/linsys/cpu/direct/private.c', + 'scs_source/linsys/external/qdldl/qdldl.c', + common_linsys_sources, + scs_core_sources, + amd_sources, + c_args: common_c_args, + include_directories: common_includes + [ + 'scs_source/linsys/cpu/direct', + 'scs_source/linsys/external/qdldl', + 'scs_source/linsys/external/amd' + ], + dependencies: _deps, + install_dir: scs_dir, + install: true, +) - 'scs/scspy.c', - 'scs_source/linsys/cpu/indirect/private.c', - - # scs_source/src: - 'scs_source/src/aa.c', - 'scs_source/src/cones.c', - 'scs_source/src/ctrlc.c', - 'scs_source/src/exp_cone.c', - 'scs_source/src/linalg.c', - 'scs_source/src/normalize.c', - 'scs_source/src/rw.c', - 'scs_source/src/scs_version.c', - 'scs_source/src/scs.c', - 'scs_source/src/util.c', - - # scs_source/linsys: - 'scs_source/linsys/scs_matrix.c', - 'scs_source/linsys/csparse.c', - - include_directories : [ - 'scs', - 'scs_source/include', - 'scs_source/linsys', - 'scs_source/linsys/cpu/indirect', - incdir_numpy], - install: true, - c_args: c_args + ['-DPYTHON', '-DCTRLC=1', '-DPY_INDIRECT', '-DINDIRECT=1', - '-DUSE_LAPACK=1', '-DDLONG=1'], - dependencies: blas_deps, +py.extension_module( + '_scs_indirect', + 'scs/scspy.c', + 'scs_source/linsys/cpu/indirect/private.c', + common_linsys_sources, + scs_core_sources, + c_args: common_c_args + ['-DPY_INDIRECT', '-DINDIRECT=1'], + include_directories: common_includes + ['scs_source/linsys/cpu/indirect'], + dependencies: _deps, + install_dir: scs_dir, + install: true, ) if get_option('link_mkl') - py.extension_module( - '_scs_mkl', - - 'scs/scspy.c', - 'scs_source/linsys/mkl/direct/private.c', - - # scs_source/src: - 'scs_source/src/aa.c', - 'scs_source/src/cones.c', - 'scs_source/src/ctrlc.c', - 'scs_source/src/exp_cone.c', - 'scs_source/src/linalg.c', - 'scs_source/src/normalize.c', - 'scs_source/src/rw.c', - 'scs_source/src/scs_version.c', - 'scs_source/src/scs.c', - 'scs_source/src/util.c', - - # scs_source/linsys: - 'scs_source/linsys/scs_matrix.c', - 'scs_source/linsys/csparse.c', - - include_directories : [ - 'scs', - 'scs_source/include', - 'scs_source/linsys', - 'scs_source/linsys/mkl/direct', - incdir_numpy], - install: true, - c_args: c_args + ['-DPYTHON', '-DCTRLC=1', '-DPY_MKL', - '-DUSE_LAPACK=1', '-DDLONG=1'], - dependencies: blas_deps, - ) + py.extension_module( + '_scs_mkl', + 'scs/scspy.c', + 'scs_source/linsys/mkl/direct/private.c', + common_linsys_sources, + scs_core_sources, + c_args: common_c_args + ['-DPY_MKL'], + include_directories: common_includes + ['scs_source/linsys/mkl/direct'], + dependencies: _deps, + install_dir: scs_dir, + install: true, + ) endif -py.install_sources('scs/__init__.py', subdir: 'scs') +py.install_sources('scs/py/__init__.py', subdir: 'scs') diff --git a/meson.options b/meson.options index 79b56784..996ccca4 100644 --- a/meson.options +++ b/meson.options @@ -5,5 +5,21 @@ option('link_blas_statically', type: 'boolean', value: false, description: 'copy BLAS compiled object into SCS module(s)') option('link_mkl', type: 'boolean', value: false, description: 'link to mkl-rt library') +option('use_openmp', type: 'boolean', + value: false, description: 'Compile SCS with OpenMP parallelization enabled. This can make SCS faster, but requires a compiler with openMP support, the user must control how many threads OpenMP uses') option('sdist_mode', type: 'boolean', value: false, description: 'Set to true if building an sdist') +option('use_lapack', type: 'boolean', + value: true, description: 'use LAPACK') +option('use_singleprec', type: 'boolean', + value: false, description: 'use single precision floating point') +option('use_extraverbose', type: 'boolean', + value: false, description: 'Enable extra verbose SCS (for debugging).') +option('use_gpu', type: 'boolean', + value: false, description: 'setup the GPU variant') +option('int32', type: 'boolean', + value: false, description: 'Use 32-bit integers (required for GPU).') +option('gpu_atrans', type: 'boolean', + value: false, description: 'transpose matrices for the GPU') +option('use_blas64', type: 'boolean', + value: false, description: 'Use 64-bit convention for BLAS') diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 00000000..999d0f5c --- /dev/null +++ b/pixi.toml @@ -0,0 +1,12 @@ +[workspace] +channels = ["conda-forge"] +name = "scs-python" +platforms = ["linux-64"] +version = "0.1.0" + +[tasks] + +[dependencies] +pkg-config = ">=0.29.2,<0.30" +meson = ">=1.8.1,<2" +meson-python = ">=0.18.0,<0.19" diff --git a/pyproject.toml b/pyproject.toml index 8f2947cd..8e340d29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ [build-system] build-backend = 'mesonpy' requires = [ - "numpy >= 2.0.0; python_version > '3.8'", - "oldest-supported-numpy; python_version <= '3.8'", + "numpy >= 2.0.0", "meson-python" ] @@ -11,7 +10,7 @@ name = 'scs' version = "3.2.7" description = 'Splitting conic solver' readme = 'README.md' -requires-python = '>=3.7' +requires-python = '>=3.9' license = {file = 'LICENSE'} authors = [ {name = "Brendan O'Donoghue", email = "bodonoghue85@gmail.com"}] @@ -80,3 +79,8 @@ inherit.before-all = "append" before-all = [ # "apk update", "apk search -v '*blas*'", "apk add openblas-dev"] + +[tool.pytest.ini_options] +testpaths = [ + "test", +] diff --git a/scs/__init__.py b/scs/py/__init__.py similarity index 97% rename from scs/__init__.py rename to scs/py/__init__.py index 893879ff..f0941cdf 100644 --- a/scs/__init__.py +++ b/scs/py/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from warnings import warn from scipy import sparse -import _scs_direct +from scs import _scs_direct __version__ = _scs_direct.version() __sizeof_int__ = _scs_direct.sizeof_int() @@ -31,7 +31,7 @@ def _select_scs_module(stgs): raise NotImplementedError( "GPU direct solver not yet available, pass `use_indirect=True`." ) - import _scs_gpu + from scs import _scs_gpu return _scs_gpu @@ -40,12 +40,12 @@ def _select_scs_module(stgs): raise NotImplementedError( "MKL indirect solver not yet available, pass `use_indirect=False`." ) - import _scs_mkl + from scs import _scs_mkl return _scs_mkl if stgs.pop("use_indirect", _USE_INDIRECT_DEFAULT): - import _scs_indirect + from scs import _scs_indirect return _scs_indirect