diff --git a/.github/workflows/cygwinccompiler.py b/.github/workflows/cygwinccompiler.py deleted file mode 100755 index 72d8558ae..000000000 --- a/.github/workflows/cygwinccompiler.py +++ /dev/null @@ -1,408 +0,0 @@ -"""distutils.cygwinccompiler - -Provides the CygwinCCompiler class, a subclass of UnixCCompiler that -handles the Cygwin port of the GNU C compiler to Windows. It also contains -the Mingw32CCompiler class which handles the mingw32 port of GCC (same as -cygwin in no-cygwin mode). -""" - -# problems: -# -# * if you use a msvc compiled python version (1.5.2) -# 1. you have to insert a __GNUC__ section in its config.h -# 2. you have to generate an import library for its dll -# - create a def-file for python??.dll -# - create an import library using -# dlltool --dllname python15.dll --def python15.def \ -# --output-lib libpython15.a -# -# see also http://starship.python.net/crew/kernr/mingw32/Notes.html -# -# * We put export_symbols in a def-file, and don't use -# --export-all-symbols because it doesn't worked reliable in some -# tested configurations. And because other windows compilers also -# need their symbols specified this no serious problem. -# -# tested configurations: -# -# * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works -# (after patching python's config.h and for C++ some other include files) -# see also http://starship.python.net/crew/kernr/mingw32/Notes.html -# * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works -# (ld doesn't support -shared, so we use dllwrap) -# * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now -# - its dllwrap doesn't work, there is a bug in binutils 2.10.90 -# see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html -# - using gcc -mdll instead dllwrap doesn't work without -static because -# it tries to link against dlls instead their import libraries. (If -# it finds the dll first.) -# By specifying -static we force ld to link against the import libraries, -# this is windows standard and there are normally not the necessary symbols -# in the dlls. -# *** only the version of June 2000 shows these problems -# * cygwin gcc 3.2/ld 2.13.90 works -# (ld supports -shared) -# * mingw gcc 3.2/ld 2.13 works -# (ld supports -shared) - -import os -import sys -import copy -from subprocess import Popen, PIPE, check_output -import re - -from distutils.ccompiler import gen_preprocess_options, gen_lib_options -from distutils.unixccompiler import UnixCCompiler -from distutils.file_util import write_file -from distutils.errors import (DistutilsExecError, CCompilerError, - CompileError, UnknownFileError) -from distutils import log -from distutils.version import LooseVersion -from distutils.spawn import find_executable - -def get_msvcr(): - """Include the appropriate MSVC runtime library if Python was built - with MSVC 7.0 or later. - """ - msc_pos = sys.version.find('MSC v.') - if msc_pos != -1: - msc_ver = sys.version[msc_pos+6:msc_pos+10] - if msc_ver == '1300': - # MSVC 7.0 - return ['msvcr70'] - elif msc_ver == '1310': - # MSVC 7.1 - return ['msvcr71'] - elif msc_ver == '1400': - # VS2005 / MSVC 8.0 - return ['msvcr80'] - elif msc_ver == '1500': - # VS2008 / MSVC 9.0 - return ['msvcr90'] - elif msc_ver == '1600': - # VS2010 / MSVC 10.0 - return ['msvcr100'] - elif msc_ver == '1900': - return ['vcruntime140'] - else: - return ['vcruntime140'] - #raise ValueError("Unknown MS Compiler version %s " % msc_ver) - - -class CygwinCCompiler(UnixCCompiler): - """ Handles the Cygwin port of the GNU C compiler to Windows. - """ - compiler_type = 'cygwin' - obj_extension = ".o" - static_lib_extension = ".a" - shared_lib_extension = ".dll" - static_lib_format = "lib%s%s" - shared_lib_format = "%s%s" - exe_extension = ".exe" - - def __init__(self, verbose=0, dry_run=0, force=0): - - UnixCCompiler.__init__(self, verbose, dry_run, force) - - status, details = check_config_h() - self.debug_print("Python's GCC status: %s (details: %s)" % - (status, details)) - if status is not CONFIG_H_OK: - self.warn( - "Python's pyconfig.h doesn't seem to support your compiler. " - "Reason: %s. " - "Compiling may fail because of undefined preprocessor macros." - % details) - - self.gcc_version, self.ld_version, self.dllwrap_version = \ - get_versions() - self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" % - (self.gcc_version, - self.ld_version, - self.dllwrap_version) ) - - # ld_version >= "2.10.90" and < "2.13" should also be able to use - # gcc -mdll instead of dllwrap - # Older dllwraps had own version numbers, newer ones use the - # same as the rest of binutils ( also ld ) - # dllwrap 2.10.90 is buggy - if self.ld_version >= "2.10.90": - self.linker_dll = "gcc" - else: - self.linker_dll = "dllwrap" - - # ld_version >= "2.13" support -shared so use it instead of - # -mdll -static - if self.ld_version >= "2.13": - shared_option = "-shared" - else: - shared_option = "-mdll -static" - - # Hard-code GCC because that's what this is all about. - # XXX optimization, warnings etc. should be customizable. - self.set_executables(compiler='gcc -mcygwin -O -Wall', - compiler_so='gcc -mcygwin -mdll -O -Wall', - compiler_cxx='g++ -mcygwin -O -Wall', - linker_exe='gcc -mcygwin', - linker_so=('%s -mcygwin %s' % - (self.linker_dll, shared_option))) - - # cygwin and mingw32 need different sets of libraries - if self.gcc_version == "2.91.57": - # cygwin shouldn't need msvcrt, but without the dlls will crash - # (gcc version 2.91.57) -- perhaps something about initialization - self.dll_libraries=["msvcrt"] - self.warn( - "Consider upgrading to a newer version of gcc") - else: - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. - self.dll_libraries = get_msvcr() - - def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): - """Compiles the source by spawning GCC and windres if needed.""" - if ext == '.rc' or ext == '.res': - # gcc needs '.res' and '.rc' compiled to object files !!! - try: - self.spawn(["windres", "-i", src, "-o", obj]) - except DistutilsExecError as msg: - raise CompileError(msg) - else: # for other files use the C-compiler - try: - self.spawn(self.compiler_so + cc_args + [src, '-o', obj] + - extra_postargs) - except DistutilsExecError as msg: - raise CompileError(msg) - - def link(self, target_desc, objects, output_filename, output_dir=None, - libraries=None, library_dirs=None, runtime_library_dirs=None, - export_symbols=None, debug=0, extra_preargs=None, - extra_postargs=None, build_temp=None, target_lang=None): - """Link the objects.""" - # use separate copies, so we can modify the lists - extra_preargs = copy.copy(extra_preargs or []) - libraries = copy.copy(libraries or []) - objects = copy.copy(objects or []) - - # Additional libraries - libraries.extend(self.dll_libraries) - - # handle export symbols by creating a def-file - # with executables this only works with gcc/ld as linker - if ((export_symbols is not None) and - (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): - # (The linker doesn't do anything if output is up-to-date. - # So it would probably better to check if we really need this, - # but for this we had to insert some unchanged parts of - # UnixCCompiler, and this is not what we want.) - - # we want to put some files in the same directory as the - # object files are, build_temp doesn't help much - # where are the object files - temp_dir = os.path.dirname(objects[0]) - # name of dll to give the helper files the same base name - (dll_name, dll_extension) = os.path.splitext( - os.path.basename(output_filename)) - - # generate the filenames for these files - def_file = os.path.join(temp_dir, dll_name + ".def") - lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a") - - # Generate .def file - contents = [ - "LIBRARY %s" % os.path.basename(output_filename), - "EXPORTS"] - for sym in export_symbols: - contents.append(sym) - self.execute(write_file, (def_file, contents), - "writing %s" % def_file) - - # next add options for def-file and to creating import libraries - - # dllwrap uses different options than gcc/ld - if self.linker_dll == "dllwrap": - extra_preargs.extend(["--output-lib", lib_file]) - # for dllwrap we have to use a special option - extra_preargs.extend(["--def", def_file]) - # we use gcc/ld here and can be sure ld is >= 2.9.10 - else: - # doesn't work: bfd_close build\...\libfoo.a: Invalid operation - #extra_preargs.extend(["-Wl,--out-implib,%s" % lib_file]) - # for gcc/ld the def-file is specified as any object files - objects.append(def_file) - - #end: if ((export_symbols is not None) and - # (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): - - # who wants symbols and a many times larger output file - # should explicitly switch the debug mode on - # otherwise we let dllwrap/ld strip the output file - # (On my machine: 10KB < stripped_file < ??100KB - # unstripped_file = stripped_file + XXX KB - # ( XXX=254 for a typical python extension)) - if not debug: - extra_preargs.append("-s") - - UnixCCompiler.link(self, target_desc, objects, output_filename, - output_dir, libraries, library_dirs, - runtime_library_dirs, - None, # export_symbols, we do this in our def-file - debug, extra_preargs, extra_postargs, build_temp, - target_lang) - - # -- Miscellaneous methods ----------------------------------------- - - def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): - """Adds supports for rc and res files.""" - if output_dir is None: - output_dir = '' - obj_names = [] - for src_name in source_filenames: - # use normcase to make sure '.rc' is really '.rc' and not '.RC' - base, ext = os.path.splitext(os.path.normcase(src_name)) - if ext not in (self.src_extensions + ['.rc','.res']): - raise UnknownFileError("unknown file type '%s' (from '%s')" % \ - (ext, src_name)) - if strip_dir: - base = os.path.basename (base) - if ext in ('.res', '.rc'): - # these need to be compiled to object files - obj_names.append (os.path.join(output_dir, - base + ext + self.obj_extension)) - else: - obj_names.append (os.path.join(output_dir, - base + self.obj_extension)) - return obj_names - -# the same as cygwin plus some additional parameters -class Mingw32CCompiler(CygwinCCompiler): - """ Handles the Mingw32 port of the GNU C compiler to Windows. - """ - compiler_type = 'mingw32' - - def __init__(self, verbose=0, dry_run=0, force=0): - - CygwinCCompiler.__init__ (self, verbose, dry_run, force) - - # ld_version >= "2.13" support -shared so use it instead of - # -mdll -static - if self.ld_version >= "2.13": - shared_option = "-shared" - else: - shared_option = "-mdll -static" - - # A real mingw32 doesn't need to specify a different entry point, - # but cygwin 2.91.57 in no-cygwin-mode needs it. - if self.gcc_version <= "2.91.57": - entry_point = '--entry _DllMain@12' - else: - entry_point = '' - - if is_cygwingcc(): - raise CCompilerError( - 'Cygwin gcc cannot be used with --compiler=mingw32') - - self.set_executables(compiler='gcc -O -Wall', - compiler_so='gcc -mdll -O -Wall', - compiler_cxx='g++ -O -Wall', - linker_exe='gcc', - linker_so='%s %s %s' - % (self.linker_dll, shared_option, - entry_point)) - # Maybe we should also append -mthreads, but then the finished - # dlls need another dll (mingwm10.dll see Mingw32 docs) - # (-mthreads: Support thread-safe exception handling on `Mingw32') - - # no additional libraries needed - self.dll_libraries=[] - - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. - self.dll_libraries = get_msvcr() - -# Because these compilers aren't configured in Python's pyconfig.h file by -# default, we should at least warn the user if he is using an unmodified -# version. - -CONFIG_H_OK = "ok" -CONFIG_H_NOTOK = "not ok" -CONFIG_H_UNCERTAIN = "uncertain" - -def check_config_h(): - """Check if the current Python installation appears amenable to building - extensions with GCC. - - Returns a tuple (status, details), where 'status' is one of the following - constants: - - - CONFIG_H_OK: all is well, go ahead and compile - - CONFIG_H_NOTOK: doesn't look good - - CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h - - 'details' is a human-readable string explaining the situation. - - Note there are two ways to conclude "OK": either 'sys.version' contains - the string "GCC" (implying that this Python was built with GCC), or the - installed "pyconfig.h" contains the string "__GNUC__". - """ - - # XXX since this function also checks sys.version, it's not strictly a - # "pyconfig.h" check -- should probably be renamed... - - from distutils import sysconfig - - # if sys.version contains GCC then python was compiled with GCC, and the - # pyconfig.h file should be OK - if "GCC" in sys.version: - return CONFIG_H_OK, "sys.version mentions 'GCC'" - - # let's see if __GNUC__ is mentioned in python.h - fn = sysconfig.get_config_h_filename() - try: - config_h = open(fn) - try: - if "__GNUC__" in config_h.read(): - return CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn - else: - return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn - finally: - config_h.close() - except OSError as exc: - return (CONFIG_H_UNCERTAIN, - "couldn't read '%s': %s" % (fn, exc.strerror)) - -RE_VERSION = re.compile(br'(\d+\.\d+(\.\d+)*)') - -def _find_exe_version(cmd): - """Find the version of an executable by running `cmd` in the shell. - - If the command is not found, or the output does not match - `RE_VERSION`, returns None. - """ - executable = cmd.split()[0] - if find_executable(executable) is None: - return None - out = Popen(cmd, shell=True, stdout=PIPE).stdout - try: - out_string = out.read() - finally: - out.close() - result = RE_VERSION.search(out_string) - if result is None: - return None - # LooseVersion works with strings - # so we need to decode our bytes - return LooseVersion(result.group(1).decode()) - -def get_versions(): - """ Try to find out the versions of gcc, ld and dllwrap. - - If not possible it returns None for it. - """ - commands = ['gcc -dumpversion', 'ld -v', 'dllwrap --version'] - return tuple([_find_exe_version(cmd) for cmd in commands]) - -def is_cygwingcc(): - '''Try to determine if the gcc that would be used is from cygwin.''' - out_string = check_output(['gcc', '-dumpmachine']) - return out_string.strip().endswith(b'cygwin') diff --git a/.github/workflows/generate_matrix.py b/.github/workflows/generate_matrix.py new file mode 100644 index 000000000..29049e864 --- /dev/null +++ b/.github/workflows/generate_matrix.py @@ -0,0 +1,36 @@ +import json + +matrix = { + "include": [] +} + +python_versions = ["3.8", "3.11"] + +combinations = { + "ubuntu-22.04": { + "compiler": ["llvm", "gcc"], + "arch_flags": ["-march=native", "-march=x86-64"] + }, + "windows-2022": { + "compiler": ["msvc", "llvm"], + "arch_flags": ["/arch:AVX2", "/arch:SSE2"] + }, + "macos-13": { + "compiler": ["llvm", "gcc-14"], + "arch_flags": ["-march=native", "-march=x86-64"] + } +} + +for platform in combinations.keys(): + for python_version in python_versions: + for compiler in combinations[platform]["compiler"]: + for arch_flag in combinations[platform]["arch_flags"]: + matrix["include"].append({ + "os": platform, + "python-version": python_version, + "compiler": compiler, + "arch_flags": arch_flag + }) + +json_str = json.dumps(matrix, ensure_ascii=False) +print(json_str) diff --git a/.github/workflows/libvcruntime140.a b/.github/workflows/libvcruntime140.a deleted file mode 100755 index 3075d72b0..000000000 Binary files a/.github/workflows/libvcruntime140.a and /dev/null differ diff --git a/.github/workflows/python_build_wheels.yml b/.github/workflows/python_build_wheels.yml new file mode 100644 index 000000000..4d8a2f549 --- /dev/null +++ b/.github/workflows/python_build_wheels.yml @@ -0,0 +1,97 @@ +name: Build and test Python wheels + +on: [push, pull_request] + +jobs: + build_wheels_unix: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-13] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.19.2 + with: + package-dir: 'python/finufft' + env: + CIBW_BEFORE_ALL_MACOS: brew install gcc@14 fftw + CIBW_ARCHS_MACOS: "x86_64" + # Need following versions of GCC for compatibility with fftw + # installed by homebrew. Similarly, we set the macOS version + # for compatibility with those libraries. + CIBW_ENVIRONMENT_MACOS: > + CC=gcc-14 + CXX=g++-14 + MACOSX_DEPLOYMENT_TARGET=13 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }} + path: ./wheelhouse/*.whl + + build_wheels_macos_arm64: + name: Build wheels on macos-14 + runs-on: macos-14 + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.19.2 + with: + package-dir: 'python/finufft' + env: + CIBW_ARCHS_MACOS: "arm64" + # Make sure to install the ARM64-specific versions of FFTW and GCC. + # Perhaps this is done automatically on the macos-14 image. We should + # look into this further. + CIBW_BEFORE_ALL_MACOS: | + pkg=$(brew fetch --force --bottle-tag=arm64_ventura fftw | grep 'Downloaded to' | cut -d' ' -f3) + brew install $pkg + pkg=$(brew fetch --force --bottle-tag=arm64_ventura gcc | grep 'Downloaded to' | cut -d' ' -f3) + brew install $pkg + CIBW_ENVIRONMENT_MACOS: > + CC=gcc-14 + CXX=g++-14 + MACOSX_DEPLOYMENT_TARGET=14 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-macos-arm64 + path: ./wheelhouse/*.whl + + build_wheels_win: + name: Build wheels on windows + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + # Here we install the mingw64 versions of gcc and FFTW that we will + # use to compile the library. We also need pkg-config so that cmake + # can easily find FFTW when configurating the build. + c:\msys64\usr\bin\pacman.exe -Sy --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-fftw mingw-w64-x86_64-pkgconf + # This particular install of mingw64 *is not* in the path by default + # (another one at c:\mingw64 is, however), so we add it to the path. + echo "c:\msys64\mingw64\bin;" >> $env:GITHUB_PATH + + - name: Build wheels + uses: pypa/cibuildwheel@v2.19.2 + with: + package-dir: 'python/finufft' + env: + # This is required to force cmake to avoid using MSVC (the default). + # By setting the generator to Ninja, cmake will pick gcc (mingw64) + # as the compiler. + CIBW_CONFIG_SETTINGS: "cmake.args='-G Ninja'" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-windows + path: ./wheelhouse/*.whl diff --git a/.github/workflows/python_build_win.ps1 b/.github/workflows/python_build_win.ps1 deleted file mode 100644 index 9e59f342b..000000000 --- a/.github/workflows/python_build_win.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -$ErrorActionPreference = "Stop" -Set-Variable -Name PYTHON -Value (Get-Command python).definition -Set-Variable -Name MSYSTEM -Value MINGW64 - -# setup setup.cfg -New-Item -Force -Path .\python\finufft -Name "setup.cfg" -ItemType "file" -Value "[build]`r`ncompiler=mingw32`r`n[build_ext]`r`ncompiler=mingw32" - -# Setup the make.inc file -Copy-Item -Path make.inc.windows_mingw -Destination make.inc -Add-Content -Path make.inc -Value "PYTHON=""$PYTHON""" -Add-Content -Path make.inc -Value "FFLAGS+= -fallow-argument-mismatch -march=x86-64" -Add-Content -Path make.inc -Value "CFLAGS+= -march=x86-64" -Add-Content -Path make.inc -Value "CXXFLAGS+= -march=x86-64" - -Set-Variable libvcruntime140_a -Value ([IO.Path]::Combine((Split-Path -Path $PYTHON), "libs", 'libvcruntime140.a')) -Copy-Item -Path .\.github\workflows\libvcruntime140.a -Destination $libvcruntime140_a - -python -m pip install --upgrade setuptools==70.1.1 wheel numpy pip -if (-not $?) {throw "Failed pip install"} - -# mingw gcc compiler pacth to work with python -Set-Variable cygwinccompiler_py -Value ([IO.Path]::Combine((Split-Path -Path $PYTHON), "lib", "site-packages", "setuptools", "_distutils", "cygwinccompiler.py")) -Remove-Item -Path $cygwinccompiler_py -Force -Copy-Item -Path .\.github\workflows\cygwinccompiler.py -Destination $cygwinccompiler_py - -# Setup the distutils.cfg file -Set-Variable distutils_cfg -Value ([IO.Path]::Combine((Split-Path -Path $PYTHON), "lib", "site-packages", "setuptools", "_distutils", "distutils.cfg")) -Set-Content -Path $distutils_cfg -Value "[build]`r`ncompiler=mingw32`r`n[build_ext]`r`ncompiler=mingw32" - -# call make -Set-Variable repo_root -Value ([IO.Path]::Combine($PSScriptRoot, '..', '..')) -c:\msys64\usr\bin\env MSYSTEM=MINGW64 c:\msys64\usr\bin\bash.exe -lc "cd '$repo_root' && make python-dist" -if (-not $?) {throw "Failed make python-dist"} - -# Move the required DLLs inside the wheel -wheel.exe unpack (get-item .\python\finufft\wheelhouse\finufft*.whl).FullName -d .\tpm -if (-not $?) {throw "Failed unpack wheel"} -Set-Variable unpacked_wheel -Value (get-item .\tpm\finufft-*).FullName -Copy-Item -Path .\lib\libfinufft.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libstdc++-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libgcc_s_seh-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libgomp-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libwinpthread-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libfftw3-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libfftw3f-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libfftw3_omp-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -Copy-Item -Path C:\msys64\mingw64\bin\libfftw3f_omp-*.dll -Destination ([IO.Path]::Combine($unpacked_wheel, 'finufft')) -New-Item -Path .\wheelhouse -ItemType Directory -Force -wheel.exe pack $unpacked_wheel -d .\wheelhouse -if (-not $?) {throw "Failed pack wheel"} - -# Cleanup -Remove-Item -Path .\python\finufft\wheelhouse -Force -Recurse -Remove-Item -Path $unpacked_wheel -Force -Recurse diff --git a/.github/workflows/python_cmake.yml b/.github/workflows/python_cmake.yml new file mode 100644 index 000000000..3079f852a --- /dev/null +++ b/.github/workflows/python_cmake.yml @@ -0,0 +1,63 @@ +name: Test python skbuild with CMake + +on: [push, pull_request] + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate_matrix.outputs.matrix }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Generate matrix + id: generate_matrix + run: | + echo "MACOSX_DEPLOYMENT_TARGET=11.0" >> $GITHUB_ENV + MATRIX=$(python3 ${{ github.workspace }}/.github/workflows/generate_matrix.py) + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + build: + name: Build with Pip + runs-on: ${{ matrix.os }} + needs: prepare + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + vcvarsall: ${{ contains(matrix.os, 'windows') }} + cmake: false + ninja: false + vcpkg: false + cppcheck: false + clangtidy: false + - name: Set min macOS version and install fftw + if: runner.os == 'macOS' + run: | + brew install fftw + - name: Install fftw + if: runner.os == 'linux' + run: | + sudo apt update + sudo apt install -y libfftw3-dev + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install pytest + run: | + python3 -m pip install --upgrade pip + python3 -m pip install pytest + - name: Set compiler flags + run: | + echo CMAKE_ARGS="-DFINUFFT_ARCH_FLAGS=${{ matrix.arch_flags }}" >> $GITHUB_ENV + shell: bash + - name: Build + run: python3 -m pip install ${{ github.workspace }}/python/finufft --verbose + - name: Test + run: python3 -m pytest ${{ github.workspace }}/python/finufft/test diff --git a/.github/workflows/python_test_win.ps1 b/.github/workflows/python_test_win.ps1 deleted file mode 100644 index 949a6906d..000000000 --- a/.github/workflows/python_test_win.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -python -m pip install --pre finufft -f .\wheelhouse\ -if (-not $?) {throw "Failed to pip install finufft"} -python python/finufft/test/run_accuracy_tests.py -if (-not $?) {throw "Tests failed"} -python python/finufft/examples/simple1d1.py -if (-not $?) {throw "Simple1d1 test failed"} -python -m pip install pytest -python -m pytest python/finufft/test -if (-not $?) {throw "Pytest suite failed"} diff --git a/.github/workflows/python_wheel.yml b/.github/workflows/python_wheel.yml deleted file mode 100644 index 5f25feca5..000000000 --- a/.github/workflows/python_wheel.yml +++ /dev/null @@ -1,242 +0,0 @@ -name: Python Wheel Build - -on: - push: - branches: - - master - tags: - - v* - pull_request: - branches: - - master - -jobs: - Linux: - runs-on: ubuntu-latest - container: quay.io/pypa/manylinux_2_28_x86_64:latest - - steps: - - uses: actions/checkout@v4 - - - name: Install fftw - run: | - yum install -y fftw3-devel - - - name: Install ffi - run: | - yum install -y libffi-devel - - - name: Compile python bindings - run: | - tools/finufft/build-wheels-linux.sh - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: linux-wheels - path: python/finufft/wheelhouse/finufft*manylinux*.whl - - MacOS: - runs-on: macos-13 - env: - MACOSX_DEPLOYMENT_TARGET: 10.15 - - steps: - - uses: actions/checkout@v4 - - - name: Unlink gcc - run: | - brew unlink gcc - continue-on-error: true - - - name: Install gcc@13 and fftw - run: | - brew install gcc@13 fftw - cp make.inc.macosx_gcc-12 make.inc - echo "FC=gfortran-13" >> make.inc - echo "CC=gcc-13" >> make.inc - echo "CXX=g++-13" >> make.inc - echo "FFLAGS += -march=x86-64" >> make.inc - echo "CFLAGS += -march=x86-64" >> make.inc - echo "CXXFLAGS += -march=x86-64" >> make.inc - # link statically to libgcc, libgfortran and libquadmath - # otherwise binaries are incompatible with older systems - echo "LIBS += -static-libgfortran -static-libgcc -static-libstdc++" >> make.inc - # hack to make libquadmath link statically - sudo rm /usr/local/opt/gcc@13/lib/gcc/13/libquadmath.*dylib - - # Download and install Python instead of using the setup_python - # as the python interpreters in the Github machines - # were compiled in 10.14, the wheels built with them - # are incompatible with older MacOS versions - - if: steps.cache-python.outputs.cache-hit != 'true' - name: Download and install Python - run: | - curl \ - https://www.python.org/ftp/python/3.6.8/python-3.6.8-macosx10.9.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - curl \ - https://www.python.org/ftp/python/3.7.9/python-3.7.9-macosx10.9.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - curl \ - https://www.python.org/ftp/python/3.8.10/python-3.8.10-macosx10.9.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - curl \ - https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - curl \ - https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - curl \ - https://www.python.org/ftp/python/3.11.7/python-3.11.7-macos11.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - curl \ - https://www.python.org/ftp/python/3.12.1/python-3.12.1-macos11.pkg \ - --output python_installer.pkg - sudo installer -pkg python_installer.pkg -target / - - - name: Compile python bindings - run: | - make lib - export FINUFFT_DIR=`pwd` - export CC=gcc-13 - export CXX=g++-13 - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 -m pip wheel python/finufft -w wheelhouse - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 -m pip wheel python/finufft -w wheelhouse - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m pip wheel python/finufft -w wheelhouse - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip wheel python/finufft -w wheelhouse - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m pip wheel python/finufft -w wheelhouse - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pip wheel python/finufft -w wheelhouse - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -m pip install --upgrade setuptools wheel numpy pip - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -m pip install -U wheel --user - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -m pip wheel python/finufft -w wheelhouse - - PYTHON_BIN=/Library/Frameworks/Python.framework/Versions/3.12/bin/ - $PYTHON_BIN/python3 -m pip install delocate==0.10.7 - ls wheelhouse/finufft*.whl | xargs -n1 $PYTHON_BIN/delocate-wheel -w fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 -m pytest python/finufft/test - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.7/bin/python3 -m pytest python/finufft/test - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m pytest python/finufft/test - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m pytest python/finufft/test - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m pytest python/finufft/test - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pytest python/finufft/test - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -m pip install --pre finufft -f fixed_wheel/ - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 python/finufft/test/run_accuracy_tests.py - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 python/finufft/examples/simple1d1.py - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -m pip install pytest - /Library/Frameworks/Python.framework/Versions/3.12/bin/python3 -m pytest python/finufft/test - - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: macos-wheels - path: fixed_wheel/*.whl - - Windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install GCC and make - run: C:\msys64\usr\bin\bash.exe -lc "pacman -Sy --noconfirm make mingw-w64-x86_64-toolchain mingw-w64-x86_64-fftw git" - - - name: Build and Test Python 3.8 - uses: actions/setup-python@v5 - with: - python-version: '3.8' - architecture: 'x64' - - run: | - .\.github\workflows\python_build_win.ps1 - .\.github\workflows\python_test_win.ps1 - - - name: Build and Test Python 3.9 - uses: actions/setup-python@v5 - with: - python-version: '3.9' - architecture: 'x64' - - run: | - .\.github\workflows\python_build_win.ps1 - .\.github\workflows\python_test_win.ps1 - - - name: Build and Test Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: '3.10' - architecture: 'x64' - - run: | - .\.github\workflows\python_build_win.ps1 - .\.github\workflows\python_test_win.ps1 - - - name: Build and Test Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: '3.11' - architecture: 'x64' - - run: | - .\.github\workflows\python_build_win.ps1 - .\.github\workflows\python_test_win.ps1 - - - name: Build and Test Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: '3.12' - architecture: 'x64' - - run: | - .\.github\workflows\python_build_win.ps1 - .\.github\workflows\python_test_win.ps1 - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: windows-wheels - path: wheelhouse\*.whl diff --git a/.github/workflows/python_wheel_macos_arm64.yml b/.github/workflows/python_wheel_macos_arm64.yml deleted file mode 100644 index 4bde22f3e..000000000 --- a/.github/workflows/python_wheel_macos_arm64.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Python Wheel Build MacOS Arm64 - -on: - push: - branches: - - master - tags: - - v* - pull_request: - branches: - - master - -jobs: - MacOS: - runs-on: macos-13 - env: - MACOSX_DEPLOYMENT_TARGET: 11.0 - - steps: - - uses: actions/checkout@v4 - - - name: Install libomp and fftw - run: | - pkg=$(brew fetch --force --bottle-tag=arm64_ventura fftw | grep 'Downloaded to' | cut -d' ' -f3) - brew install $pkg - pkg=$(brew fetch --force --bottle-tag=arm64_ventura libomp | grep 'Downloaded to' | cut -d' ' -f3) - brew install $pkg - - - name: Compile libfinufft - run: | - cp make.inc.macosx_arm64 make.inc - make lib - - - name: Build wheels - uses: pypa/cibuildwheel@v2.17.0 - env: - FINUFFT_DIR: ${{ github.workspace }} - CC: Clang - CXX: Clang++ - CIBW_ARCHS_MACOS: arm64 - CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* - with: - package-dir: ./python/finufft - output-dir: wheelhouse - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: macos-arm64-wheels - path: wheelhouse/*.whl - - - name: Setup tmate session - if: ${{ failure() }} - uses: mxschmitt/action-tmate@v3 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d600fe3b..8196ee662 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.23) -project(finufft VERSION 2.2.0 LANGUAGES C CXX) +project(FINUFFT VERSION 2.2.0 LANGUAGES C CXX) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(CheckCXXCompilerFlag) @@ -100,6 +102,7 @@ option(FINUFFT_BUILD_EXAMPLES "Whether to build the FINUFFT examples" OFF) option(FINUFFT_BUILD_TESTS "Whether to build the FINUFFT tests" OFF) option(FINUFFT_BUILD_FORTRAN "Whether to build the FINUFFT Fortran examples" OFF) option(FINUFFT_BUILD_MATLAB "Whether to build the FINUFFT Matlab interface" OFF) +option(FINUFFT_BUILD_PYTHON "Whether the Python wrapper should be built." OFF) option(FINUFFT_ENABLE_SANITIZERS "Whether to enable sanitizers, only effective for Debug configuration." ON) option(FINUFFT_USE_OPENMP "Whether to use OpenMP for parallelization. If disabled, the finufft library will be single threaded. This does not affect the choice of FFTW library." ON) option(FINUFFT_USE_CUDA "Whether to build CUDA accelerated FINUFFT library (libcufinufft). This is completely independent of the main FINUFFT library" OFF) @@ -107,17 +110,10 @@ option(FINUFFT_USE_CPU "Whether to build the ordinary FINUFFT library (libfinuff option(FINUFFT_STATIC_LINKING "Whether to link the static FINUFFT library (libfinufft_static)." ON) option(FINUFFT_BUILD_DEVEL "Whether to build development executables" OFF) option(FINUFFT_USE_DUCC0 "Whether to use DUCC0 (instead of FFTW) for CPU FFTs" OFF) +option(FINUFFT_BUILD_DEVEL "Whether to build developement executables" OFF) +option(FINUFFT_ENABLE_INSTALL "Whether to enable installation of FINUFFT library" ON) # sphinx tag (don't remove): @cmake_opts_end -# Check if FINUFFT_STATIC_LINKING is true -if(NOT FINUFFT_STATIC_LINKING) - # If true, set FINUFFT_SHARED_LINKING to false - set(FINUFFT_SHARED_LINKING TRUE) -else() - # If not true, set FINUFFT_SHARED_LINKING to true - set(FINUFFT_SHARED_LINKING FALSE) -endif() - if (FINUFFT_USE_CPU) # suppress Windows warnings about "unsafe" functions if (WIN32) @@ -170,7 +166,7 @@ if (FINUFFT_BUILD_MATLAB) else () # For non-matlab builds, find system OpenMP if (FINUFFT_USE_OPENMP) - find_package(OpenMP REQUIRED) + find_package(OpenMP COMPONENTS CXX REQUIRED) endif () endif () @@ -192,6 +188,7 @@ endfunction() # Utility function to link static/dynamic lib function(finufft_link_test target) + if (FINUFFT_USE_DUCC0) target_compile_definitions(${target} PRIVATE FINUFFT_USE_DUCC0) endif () @@ -308,30 +305,62 @@ if (FINUFFT_BUILD_DEVEL) add_subdirectory(devel) endif () -include(GNUInstallDirs) -install(TARGETS ${INSTALL_TARGETS} PUBLIC_HEADER) -install(FILES ${PROJECT_SOURCE_DIR}/LICENSE - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/licenses/finufft) +if (FINUFFT_BUILD_PYTHON) + add_subdirectory(python) +endif () + +message(STATUS " CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS "FINUFFT configuration summary:") +message(STATUS " FINUFFT_USE_CPU: ${FINUFFT_USE_CPU}") +message(STATUS " FINUFFT_USE_CUDA: ${FINUFFT_USE_CUDA}") +message(STATUS " FINUFFT_USE_OPENMP: ${FINUFFT_USE_OPENMP}") +message(STATUS " FINUFFT_STATIC_LINKING: ${FINUFFT_STATIC_LINKING}") +message(STATUS " FINUFFT_ENABLE_INSTALL: ${FINUFFT_ENABLE_INSTALL}") +message(STATUS " FINUFFT_BUILD_EXAMPLES: ${FINUFFT_BUILD_EXAMPLES}") +message(STATUS " FINUFFT_BUILD_TESTS: ${FINUFFT_BUILD_TESTS}") +message(STATUS " FINUFFT_BUILD_FORTRAN: ${FINUFFT_BUILD_FORTRAN}") +message(STATUS " FINUFFT_BUILD_MATLAB: ${FINUFFT_BUILD_MATLAB}") +message(STATUS " FINUFFT_BUILD_PYTHON: ${FINUFFT_BUILD_PYTHON}") +message(STATUS " FINUFFT_ENABLE_SANITIZERS: ${FINUFFT_ENABLE_SANITIZERS}") +message(STATUS " FINUFFT_FFTW_SUFFIX: ${FINUFFT_FFTW_SUFFIX}") +message(STATUS " FINUFFT_FFTW_LIBRARIES: ${FINUFFT_FFTW_LIBRARIES}") +message(STATUS " FINUFFT_ARCH_FLAGS: ${FINUFFT_ARCH_FLAGS}") + if (FINUFFT_USE_CPU) - install(DIRECTORY ${PROJECT_SOURCE_DIR}/examples - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/finufft - PATTERN "CMakeLists.txt" EXCLUDE - PATTERN "README" EXCLUDE - PATTERN "examples/cuda" EXCLUDE - ) - if (FINUFFT_BUILD_FORTRAN) - install(DIRECTORY ${PROJECT_SOURCE_DIR}/fortran/examples - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/finufft/fortran - ) - install(FILES ${PROJECT_SOURCE_DIR}/include/finufft.fh - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ) + if (FINUFFT_STATIC_LINKING) + get_target_property(FINUFFT_COMPILE_OPTIONS finufft_static COMPILE_OPTIONS) + else () + get_target_property(FINUFFT_COMPILE_OPTIONS finufft COMPILE_OPTIONS) endif () + message("Compile options for finufft: ${MY_COMPILE_OPTIONS}") endif () -if (FINUFFT_USE_CUDA) - install(DIRECTORY ${PROJECT_SOURCE_DIR}/examples/cuda - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/finufft/examples - PATTERN "README" EXCLUDE - PATTERN "CMakeLists.txt" EXCLUDE - ) + +if (FINUFFT_ENABLE_INSTALL) + include(GNUInstallDirs) + install(TARGETS ${INSTALL_TARGETS} PUBLIC_HEADER) + install(FILES ${PROJECT_SOURCE_DIR}/LICENSE + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/licenses/finufft) + if (FINUFFT_USE_CPU) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/examples + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/finufft + PATTERN "CMakeLists.txt" EXCLUDE + PATTERN "README" EXCLUDE + PATTERN "examples/cuda" EXCLUDE + ) + if (FINUFFT_BUILD_FORTRAN) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/fortran/examples + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/finufft/fortran + ) + install(FILES ${PROJECT_SOURCE_DIR}/include/finufft.fh + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + endif() + endif () + if (FINUFFT_USE_CUDA) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/examples/cuda + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/finufft/examples + PATTERN "README" EXCLUDE + PATTERN "CMakeLists.txt" EXCLUDE + ) + endif() endif () diff --git a/CMakePresets.json b/CMakePresets.json index b04204500..32229d1c5 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -110,6 +110,18 @@ "FINUFFT_BUILD_MATLAB": "ON", "FINUFFT_ENABLE_SANITIZERS": "OFF" } + }, + { + "name": "python", + "binaryDir": "build/python", + "displayName": "python", + "description": "Build with the python interface", + "generator": "Ninja Multi-Config", + "cacheVariables": { + "FINUFFT_BUILD_PYTHON": "ON", + "FINUFFT_ENABLE_SANITIZERS": "OFF", + "CMAKE_INSTALL_DATAROOTDIR": "." + } } ], "buildPresets": [ diff --git a/docs/install.rst b/docs/install.rst index 05d4f6f65..bb0bd22cc 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -59,14 +59,14 @@ The basic quick download, building, and test is then: cd finufft mkdir build cd build - cmake .. -D FINUFFT_BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Release --install-prefix /path/to/install + cmake .. -D FINUFFT_BUILD_TESTS=ON --install-prefix /path/to/install cmake --build . -j ctest cmake --install . +.. note:: -Note: `Release` here is essential to get the best performing version. Also, if you don't supply `--install-prefix`, it will default to ``/usr/local`` on most systems. If you don't -have root access, you must supply a prefix you can write to such as ``$HOME/local``. Now... + If you don't supply `--install-prefix`, it will default to ``/usr/local`` on most systems. If you don't have root access, you must supply a prefix you can write to such as ``$HOME/local``. In ``build``, this creates ``libfinufft_static.a`` and ``libfinufft.so``, and runs a test that should take a few seconds and report ``100% tests passed, 0 tests failed out of 17``. To use the library, link against diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 000000000..e1eb698fb --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,11 @@ +if(FINUFFT_USE_CPU) + if (WIN32) + install(TARGETS finufft LIBRARY DESTINATION finufft RUNTIME DESTINATION finufft) + else () + install(TARGETS finufft LIBRARY DESTINATION finufft) + endif () +endif() + +if(FINUFFT_USE_GPU) + install(TARGETS finufft_gpu LIBRARY DESTINATION cufinufft) +endif() diff --git a/python/finufft/finufft/_finufft.py b/python/finufft/finufft/_finufft.py index 96308db11..fe2748d70 100644 --- a/python/finufft/finufft/_finufft.py +++ b/python/finufft/finufft/_finufft.py @@ -5,20 +5,18 @@ Seperate bindings are provided for single and double precision libraries, differentiated by 'f' suffix. """ - import ctypes -import os -import warnings -import platform -import importlib.util - -import numpy as np - +import pathlib +from ctypes.util import find_library from ctypes import c_double -from ctypes import c_int from ctypes import c_float -from ctypes import c_void_p +from ctypes import c_int from ctypes import c_longlong +from ctypes import c_void_p + +import numpy as np +import os +import platform from numpy.ctypeslib import ndpointer c_int_p = ctypes.POINTER(c_int) @@ -28,32 +26,31 @@ # TODO: See if there is a way to improve this so it is less hacky. lib = None -# Try to load a local library directly. -try: - lib = ctypes.cdll.LoadLibrary('libfinufft.so') -except OSError: - pass - -# Should that not work, try to find the full path of a packaged lib. -# The packaged lib should have a py/platform decorated name, -# and be rpath'ed the true FINUFFT library through the Extension and wheel -# systems. -try: - if lib is None: - # Find the library. - lib_path = importlib.util.find_spec('finufft.finufftc').origin - # Get the full path for the ctypes loader. - if platform.system() == 'Windows': - os.environ["PATH"] += os.pathsep + os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(lib_path))),'finufft') - full_lib_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(lib_path))),'finufft','libfinufft.dll') - else: - full_lib_path = os.path.realpath(lib_path) - - # Load the library, - # which rpaths the libraries we care about. - lib = ctypes.cdll.LoadLibrary(full_lib_path) -except Exception: - raise ImportError('Failed to find a suitable finufft library') +# Try to load finufft installed from the python package. +path = pathlib.Path(__file__).parent.resolve() +# Ignoring the exceptions to avoid the print +# exception, during the process of an exception another exception occurred +# unix systems have lib prefix, non unix systems do not +library_names = ['libfinufft', 'finufft'] +for lib_name in library_names: + try: + lib = np.ctypeslib.load_library(lib_name, path) + break + except OSError: + # Paranoid, in case lib is set to something and then an exception is thrown + lib = None + +if lib is None: + # If that fails, try to load the library from the system path. + libname = find_library('finufft') + if libname is not None: + lib = ctypes.cdll.LoadLibrary(libname) + # we probably should add a version check and trow a warning if the version is different + else: + # if that does not work, finufft is not installed correctly. + raise ImportError('Failed to find a suitable finufft library. ' + 'Please check your installation, ' + 'finufft does not seem to be installed correctly.') class FinufftOpts(ctypes.Structure): diff --git a/python/finufft/pyproject.toml b/python/finufft/pyproject.toml new file mode 100644 index 000000000..f9c74fc75 --- /dev/null +++ b/python/finufft/pyproject.toml @@ -0,0 +1,71 @@ +[build-system] +requires = [ + "scikit-build-core >= 0.4.3", + "cmake >= 3.19", + "ninja >= 1.9.0", +] + +build-backend = "scikit_build_core.build" + +[project] +name = "finufft" +readme = "README.md" +requires-python = ">=3.8" +dependencies = ["numpy >= 1.12.0"] +authors = [ + {name = "Jeremy Magland"}, + {name = "Daniel Foreman-Mackey"}, + {name = "Joakim Anden"}, + {name = "Libin Lu"}, + {name = "Alex Barnett"}] +maintainers = [{name = "Alex Barnett", email = "abarnett@flatironinstitute.org"}] +description = "Python interface to FINUFFT" +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: C++", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows" + ] +dynamic = ["version"] + +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "0.4" +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# Tell skbuild to look for the CMakeLists.txt file two directories up. +cmake.source-dir = "../../" +cmake.targets = ["finufft"] +cmake.define = {"FINUFFT_BUILD_PYTHON" = "ON", "FINUFFT_ENABLE_INSTALL" = "OFF"} + +wheel.packages = ["finufft"] + +# Indicate that we don't depend on the CPython API +wheel.py-api = "py3" + +[tool.scikit-build.metadata.version] +# Instead of hardcoding the version here, extract it from the source files. +provider = "scikit_build_core.metadata.regex" +input = "finufft/__init__.py" + +[tool.cibuildwheel] +# Necessary to see build output from the actual compilation +build-verbosity = 1 +# Not building for PyPy and musllinux for now. +skip = "pp* *musllinux*" +test-requires = "pytest" +test-command = "pytest {project}/python/finufft/test" + +[tool.cibuildwheel.linux] +archs = "x86_64" +before-all = "yum install -y fftw3-devel" + +[tool.cibuildwheel.windows] +archs = "AMD64" +before-build = "pip install delvewheel" +# CIBW doesn't do vendoring of DLLs on Windows by default, so we have to +# install delvewheel and run it. +repair-wheel-command = "delvewheel repair -v --analyze-existing -w {dest_dir} {wheel}" diff --git a/python/finufft/setup.py b/python/finufft/setup.py deleted file mode 100644 index c2bf12f8d..000000000 --- a/python/finufft/setup.py +++ /dev/null @@ -1,99 +0,0 @@ -# This defines the Python module installation. - -# Barnett 3/1/18. Updates by Yu-Hsuan Shih, June 2018. -# win32 mingw patch by Vineet Bansal, Feb 2019. -# attempt ../make.inc reading (failed) and default finufftdir. 2/25/20 -# Barnett trying to get sphinx.ext.autodoc to work w/ this, 10/5/20 - -__version__ = '2.2.0' - -from setuptools import setup, Extension -import os -import platform -from pathlib import Path - -from tempfile import mkstemp - -finufft_dir = os.environ.get('FINUFFT_DIR') - -# Note: This will not work if run through pip install since setup.py is copied -# to a different location. -if finufft_dir == None or finufft_dir == '': - finufft_dir = Path(__file__).resolve().parents[2] - -# Set include and library paths relative to FINUFFT root directory. -inc_dir = os.path.join(finufft_dir, 'include') -lib_dir = os.path.join(finufft_dir, 'lib') -lib_dir_cmake = os.path.join(finufft_dir, 'build') # lib may be only here - -# Read in long description from README.md. -with open(os.path.join(finufft_dir, 'python', 'finufft', 'README.md'), 'r') as f: - long_description = f.read() - -finufft_dlib = 'finufft' - -# Windows does not have the concept of rpath and as a result, MSVC crashes if -# supplied with one. -if platform.system() != "Windows": - runtime_library_dirs = [lib_dir, lib_dir_cmake] -else: - runtime_library_dirs = [] - -# For certain platforms (e.g. Ubuntu 20.04), we need to create a dummy source -# that calls one of the functions in the FINUFFT dynamic library. The reason -# is that these platforms override the default --no-as-needed flag for ld, -# which means that the linker will only link to those dynamic libraries for -# which there are unresolved symbols in the object files. Since we do not have -# a real source, the result is that no dynamic libraries are linked. To -# prevent this, we create a dummy source so that the library will link as -# expected. -fd, source_filename = mkstemp(suffix='.c', text=True) - -with open(fd, 'w') as f: - f.write( \ -""" -#include - -void PyInit_finufftc(void) { - finufft_opts opt; - - finufft_default_opts(&opt); -} -""") - - -########## SETUP ########### -setup( - name='finufft', - version=__version__, - author='Python interfaces by: Jeremy Magland, Daniel Foreman-Mackey, Joakim Anden, Libin Lu, and Alex Barnett', - author_email='abarnett@flatironinstitute.org', - url='https://github.com/flatironinstitute/finufft', - description='Python interface to FINUFFT', - long_description=long_description, - long_description_content_type='text/markdown', - license="Apache 2", - packages=['finufft'], - classifiers=[ - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3', - 'Programming Language :: C++', - 'Operating System :: POSIX :: Linux', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - ], - install_requires=['numpy>=1.12.0'], - python_requires='>=3.6', - zip_safe=False, - py_modules=['finufft.finufftc'], - ext_modules=[ - Extension(name='finufft.finufftc', - sources=[source_filename], - include_dirs=[inc_dir, '/usr/local/include'], - library_dirs=[lib_dir, lib_dir_cmake, '/usr/local/lib'], - libraries=[finufft_dlib], - runtime_library_dirs=runtime_library_dirs) - ] -) - -os.unlink(source_filename) diff --git a/src/spreadinterp.cpp b/src/spreadinterp.cpp index 4374c6e9d..3f03c79bb 100644 --- a/src/spreadinterp.cpp +++ b/src/spreadinterp.cpp @@ -24,6 +24,8 @@ namespace { // anonymous namespace for internal structs equivalent to declaring // static struct zip_low; struct zip_hi; +template struct reverse_index; +template struct shuffle_index; struct select_even; struct select_odd; // forward declaration to clean up the code and be able to use this everywhere in the file @@ -777,23 +779,80 @@ Two upsampfacs implemented. Params must match ref formula. Barnett 4/24/18 */ const FLT z = std::fma(FLT(2.0), x, FLT(w - 1)); // scale so local grid offset z in // [-1,1] if (opts.upsampfac == 2.0) { // floating point equality is fine here - static constexpr auto alignment = simd_type::arch_type::alignment(); + using arch_t = typename simd_type::arch_type; + static constexpr auto alignment = arch_t::alignment(); static constexpr auto simd_size = simd_type::size; static constexpr auto padded_ns = (w + simd_size - 1) & ~(simd_size - 1); static constexpr auto nc = nc200(); static constexpr auto horner_coeffs = get_horner_coeffs_200(); + static constexpr auto use_ker_sym = (simd_size < w); alignas(alignment) static constexpr auto padded_coeffs = pad_2D_array_with_zeros(horner_coeffs); - const simd_type zv(z); - for (uint8_t i = 0; i < w; i += simd_size) { - auto k = simd_type::load_aligned(padded_coeffs[0].data() + i); - for (uint8_t j = 1; j < nc; ++j) { - const auto cji = simd_type::load_aligned(padded_coeffs[j].data() + i); - k = xsimd::fma(k, zv, cji); + // use kernel symmetry trick if w > simd_size + if constexpr (use_ker_sym) { + static constexpr uint8_t tail = w % simd_size; + static constexpr uint8_t if_odd_degree = ((nc + 1) % 2); + static constexpr uint8_t offset_start = tail ? w - tail : w - simd_size; + static constexpr uint8_t end_idx = (w + (tail > 0)) / 2; + const simd_type zv{z}; + const auto z2v = zv * zv; + + // some xsimd constant for shuffle or inverse + static constexpr auto shuffle_batch = []() constexpr noexcept { + if constexpr (tail) { + return xsimd::make_batch_constant, arch_t, + shuffle_index>(); + } else { + return xsimd::make_batch_constant, arch_t, + reverse_index>(); + } + }(); + + // process simd vecs + simd_type k_prev, k_sym{0}; + for (uint8_t i{0}, offset = offset_start; i < end_idx; + i += simd_size, offset -= simd_size) { + auto k_odd = [i]() constexpr noexcept { + if constexpr (if_odd_degree) { + return simd_type::load_aligned(padded_coeffs[0].data() + i); + } else { + return simd_type{0}; + } + }(); + auto k_even = simd_type::load_aligned(padded_coeffs[if_odd_degree].data() + i); + for (uint8_t j{1 + if_odd_degree}; j < nc; j += 2) { + const auto cji_odd = simd_type::load_aligned(padded_coeffs[j].data() + i); + const auto cji_even = simd_type::load_aligned(padded_coeffs[j + 1].data() + i); + k_odd = xsimd::fma(k_odd, z2v, cji_odd); + k_even = xsimd::fma(k_even, z2v, cji_even); + } + // left part + xsimd::fma(k_odd, zv, k_even).store_aligned(ker + i); + // right part symmetric to the left part + if (offset >= end_idx) { + if constexpr (tail) { + // to use aligned store, we need shuffle the previous k_sym and current k_sym + k_prev = k_sym; + k_sym = xsimd::fnma(k_odd, zv, k_even); + xsimd::shuffle(k_sym, k_prev, shuffle_batch).store_aligned(ker + offset); + } else { + xsimd::swizzle(xsimd::fnma(k_odd, zv, k_even), shuffle_batch) + .store_aligned(ker + offset); + } + } + } + } else { + const simd_type zv(z); + for (uint8_t i = 0; i < w; i += simd_size) { + auto k = simd_type::load_aligned(padded_coeffs[0].data() + i); + for (uint8_t j = 1; j < nc; ++j) { + const auto cji = simd_type::load_aligned(padded_coeffs[j].data() + i); + k = xsimd::fma(k, zv, cji); + } + k.store_aligned(ker + i); } - k.store_aligned(ker + i); } return; } @@ -2168,6 +2227,16 @@ struct zip_hi { return (size + index) / 2; } }; +template struct reverse_index { + static constexpr unsigned get(unsigned index, const unsigned size) { + return index < cap ? (cap - 1 - index) : index; + } +}; +template struct shuffle_index { + static constexpr unsigned get(unsigned index, const unsigned size) { + return index < cap ? (cap - 1 - index) : size + size + cap - 1 - index; + } +}; struct select_even { static constexpr unsigned get(unsigned index, unsigned /*size*/) { return index * 2; } diff --git a/tools/finufft/build-wheels-linux.sh b/tools/finufft/build-wheels-linux.sh deleted file mode 100755 index 5ea8d9303..000000000 --- a/tools/finufft/build-wheels-linux.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -set -e -x - -# Replace native compilation flags with more generic ones. -cp make.inc.manylinux make.inc - -# Clean up the build and make the library. -make clean -make lib - -# Test to make sure everything is ok. -make test - -# Needed for pip install to work -export FINUFFT_DIR=$(pwd) -# Needed for auditwheel to find the dynamic libraries -export LD_LIBRARY_PATH=${FINUFFT_DIR}/lib:${LD_LIBRARY_PATH} - -# Explicitly list Python versions to build -versions=("cp36-cp36m" - "cp37-cp37m" - "cp38-cp38" - "cp39-cp39" - "cp310-cp310" - "cp311-cp311" - "cp312-cp312" - "pp39-pypy39_pp73") - -pys=() -for version in "${versions[@]}"; do - pys+=("/opt/python/${version}/bin") -done - -# build wheel -for pybin in "${pys[@]}"; do - "${pybin}/pip" install --upgrade pip - "${pybin}/pip" install auditwheel wheel numpy - "${pybin}/pip" wheel ./python/finufft -w python/finufft/wheelhouse -done - -# fix wheel -for whl in python/finufft/wheelhouse/finufft-*.whl; do - auditwheel repair "$whl" -w python/finufft/wheelhouse/ -done - -# test wheel -for pybin in "${pys[@]}"; do - "${pybin}/pip" install --pre finufft -f ./python/finufft/wheelhouse/ - "${pybin}/python" ./python/finufft/test/run_accuracy_tests.py - "${pybin}/python" ./python/finufft/examples/simple1d1.py - "${pybin}/pip" install pytest - "${pybin}/pytest" python/finufft/test -done