diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index fd767086..dd1b83be 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,15 +1,6 @@ name: Python Build -on: - push: - branches: - - master - tags: - - v* - - py_v* - pull_request: - branches: - - master +on: workflow_dispatch jobs: Linux: diff --git a/.github/workflows/python_cmake.yml b/.github/workflows/python_cmake.yml new file mode 100644 index 00000000..fad4d9ca --- /dev/null +++ b/.github/workflows/python_cmake.yml @@ -0,0 +1,35 @@ +name: Python cmake build test + +on: + push: + branches: + - master + tags: + - v* + - py_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 gfortran + run: | + yum install -y gcc-toolset-12.x86_64 + + - name: Compile and install python bindings + run: | + scl enable gcc-toolset-12 bash + /opt/python/cp311-cp311/bin/python -m pip install . + + - name: Test python bindings + run: | + /opt/python/cp311-cp311/bin/python -m pip install pytest + /opt/python/cp311-cp311/bin/python -m pytest -s python/test + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..fb04e383 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,180 @@ +# set minimum cmake version +cmake_minimum_required(VERSION 3.18) + +# project name and language +project(fmm3d LANGUAGES C Fortran) + +# verbose makefile +set(CMAKE_VERBOSE_MAKEFILE ON) + +# Safety net +if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message( + FATAL_ERROR + "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n" + ) +endif() + +# Grab Python +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module NumPy) +# Grab OpenMP +find_package(OpenMP REQUIRED) +if (OpenMP_Fortran_FOUND) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") +endif() + +# Grab the variables from a local Python installation +# F2PY headers +execute_process( + COMMAND "${Python_EXECUTABLE}" + -c "import numpy.f2py; print(numpy.f2py.get_include())" + OUTPUT_VARIABLE F2PY_INCLUDE_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# build static lib libfmm3d.a +# source files for libfmm3d.a +file(GLOB_RECURSE source_list "src/*.f" "src/*.f90") +#message(${source_list}) +# remove fast kernels and tree_lr_3d.f +list(FILTER source_list EXCLUDE REGEX ".*_fast\\.f$|.*tree_lr_3d\\.f") +#message(${source_list}) +# add fmm3d static lib +add_library(fmm3d STATIC ${source_list}) +#compiler options +target_compile_options(fmm3d PRIVATE -fPIC -O3 -march=native -funroll-loops -std=legacy -w) + +# Print out the discovered paths +include(CMakePrintHelpers) +cmake_print_variables(Python_INCLUDE_DIRS) +cmake_print_variables(F2PY_INCLUDE_DIR) +cmake_print_variables(Python_NumPy_INCLUDE_DIRS) + +# extensions variables +# hfmm3d_fortran +set(hfmm_module_name "hfmm3d_fortran") +set(hfmm_fortran_src_file ${CMAKE_SOURCE_DIR}/src/Helmholtz/hfmm3dwrap.f + ${CMAKE_SOURCE_DIR}/src/Helmholtz/hfmm3dwrap_vec.f + ${CMAKE_SOURCE_DIR}/src/Helmholtz/helmkernels.f) +set(f2py_helm_module_c "${hfmm_module_name}module.c") +# lfmm3d_fortran +set(lfmm_module_name "lfmm3d_fortran") +set(lfmm_fortran_src_file ${CMAKE_SOURCE_DIR}/src/Laplace/lfmm3dwrap.f + ${CMAKE_SOURCE_DIR}/src/Laplace/lfmm3dwrap_vec.f + ${CMAKE_SOURCE_DIR}/src/Laplace/lapkernels.f) +set(f2py_lap_module_c "${lfmm_module_name}module.c") +# emfmm3d_fortran +set(emfmm_module_name "emfmm3d_fortran") +set(emfmm_fortran_src_file ${CMAKE_SOURCE_DIR}/src/Helmholtz/hfmm3dwrap.f + ${CMAKE_SOURCE_DIR}/src/Helmholtz/hfmm3dwrap_vec.f + ${CMAKE_SOURCE_DIR}/src/Helmholtz/helmkernels.f + ${CMAKE_SOURCE_DIR}/src/Maxwell/emfmm3d.f90) +set(f2py_em_module_c "${emfmm_module_name}module.c") +# stfmm3d_fortran +set(stfmm_module_name "stfmm3d_fortran") +set(stfmm_fortran_src_file ${CMAKE_SOURCE_DIR}/src/Laplace/lfmm3dwrap.f + ${CMAKE_SOURCE_DIR}/src/Laplace/lfmm3dwrap_vec.f + ${CMAKE_SOURCE_DIR}/src/Laplace/lapkernels.f + ${CMAKE_SOURCE_DIR}/src/Stokes/stfmm3d.f + ${CMAKE_SOURCE_DIR}/src/Stokes/stokkernels.f) +set(f2py_st_module_c "${stfmm_module_name}module.c") + +# Generate extensions' sources +# hfmm3d_fortran +add_custom_target( + hfmm_genpyf + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${f2py_helm_module_c}" +) +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_helm_module_c}" "${CMAKE_CURRENT_BINARY_DIR}/${hfmm_module_name}-f2pywrappers2.f90" + COMMAND ${Python_EXECUTABLE} -m "numpy.f2py" + -m "hfmm3d_fortran" + ${hfmm_fortran_src_file} + --lower + DEPENDS ${hfmm_fortran_src_file} # Fortran source +) +# lfmm3d_fortran +add_custom_target( + lfmm_genpyf + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${f2py_lap_module_c}" +) +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_lap_module_c}" "${CMAKE_CURRENT_BINARY_DIR}/${lfmm_module_name}-f2pywrappers2.f90" + COMMAND ${Python_EXECUTABLE} -m "numpy.f2py" + -m "lfmm3d_fortran" + ${lfmm_fortran_src_file} + --lower + DEPENDS ${lfmm_fortran_src_file} # Fortran source +) +# emfmm3d_fortran +add_custom_target( + emfmm_genpyf + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${f2py_em_module_c}" +) +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_em_module_c}" "${CMAKE_CURRENT_BINARY_DIR}/${emfmm_module_name}-f2pywrappers2.f90" + COMMAND ${Python_EXECUTABLE} -m "numpy.f2py" + -m "emfmm3d_fortran" + ${emfmm_fortran_src_file} + --lower + DEPENDS ${emfmm_fortran_src_file} # Fortran source +) +# stfmm3d_fortran +add_custom_target( + stfmm_genpyf + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${f2py_st_module_c}" +) +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_st_module_c}" "${CMAKE_CURRENT_BINARY_DIR}/${stfmm_module_name}-f2pywrappers2.f90" + COMMAND ${Python_EXECUTABLE} -m "numpy.f2py" + -m "stfmm3d_fortran" + ${stfmm_fortran_src_file} + --lower + DEPENDS ${stfmm_fortran_src_file} # Fortran source +) + +# Set up extensions targets +# hfmm3d_fortran +Python_add_library(hfmm3d_fortran MODULE WITH_SOABI + "${CMAKE_CURRENT_BINARY_DIR}/${f2py_helm_module_c}" # Generated + "${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy + "${hfmm_fortran_src_file}" # Fortran source(s) +) +# lfmm3d_fortran +Python_add_library(lfmm3d_fortran MODULE WITH_SOABI + "${CMAKE_CURRENT_BINARY_DIR}/${f2py_lap_module_c}" # Generated + "${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy + "${lfmm_fortran_src_file}" # Fortran source(s) +) +# emfmm3d_fortran +Python_add_library(emfmm3d_fortran MODULE WITH_SOABI + "${CMAKE_CURRENT_BINARY_DIR}/${f2py_em_module_c}" # Generated + "${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy + "${emfmm_fortran_src_file}" # Fortran source(s) +) +# stfmm3d_fortran +Python_add_library(stfmm3d_fortran MODULE WITH_SOABI + "${CMAKE_CURRENT_BINARY_DIR}/${f2py_st_module_c}" # Generated + "${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy + "${stfmm_fortran_src_file}" # Fortran source(s) +) + +# Dependencies for extensions +# hfmm3d_fortran +target_link_libraries(hfmm3d_fortran PRIVATE Python::NumPy fmm3d) +add_dependencies(hfmm3d_fortran hfmm_genpyf) +target_include_directories(hfmm3d_fortran PRIVATE "${F2PY_INCLUDE_DIR}") +# lfmm3d_fortran +target_link_libraries(lfmm3d_fortran PRIVATE Python::NumPy fmm3d) +add_dependencies(lfmm3d_fortran lfmm_genpyf) +target_include_directories(lfmm3d_fortran PRIVATE "${F2PY_INCLUDE_DIR}") +# emfmm3d_fortran +target_link_libraries(emfmm3d_fortran PRIVATE Python::NumPy fmm3d) +add_dependencies(emfmm3d_fortran emfmm_genpyf) +target_include_directories(emfmm3d_fortran PRIVATE "${F2PY_INCLUDE_DIR}") +# stfmm3d_fortran +target_link_libraries(stfmm3d_fortran PRIVATE Python::NumPy fmm3d) +add_dependencies(stfmm3d_fortran stfmm_genpyf) +target_include_directories(stfmm3d_fortran PRIVATE "${F2PY_INCLUDE_DIR}") + +add_subdirectory(python) diff --git a/README.md b/README.md index 2443302c..d5d08f40 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Flatiron Institute Fast Multipole Libraries -[![Actions status](https://github.com/flatironinstitute/FMM3D/workflows/Python%20Build/badge.svg)](https://github.com/flatironinstitute/FMM3D/actions) +[![Actions status](https://github.com/flatironinstitute/FMM3D/actions/workflows/python_cmake.yml/badge.svg)](https://github.com/flatironinstitute/FMM3D/actions) This codebase is a set of libraries to compute N-body interactions governed by the Laplace and Helmholtz equations, to a specified diff --git a/julia/README.md b/julia/README.md index a96b7850..f47d2faa 100644 --- a/julia/README.md +++ b/julia/README.md @@ -6,7 +6,20 @@ Flatiron Institute's `FMM3D` library. ## Using FMM3D.jl -For now, FMM3D.jl can be obtained by downloading this +`FMM3D.jl` is now registered in the Julia General registry, so you can install it in Julia 1.6 or later. +Press `]` in Julia REPL to enter the package manager and then enter: +```julia +pkg> add FMM3D +``` +then the package should be installed automatically. + +You can then use the package with + +```julia +julia> using FMM3D +``` + +It can also be obtained by downloading this git repository and a recent version of julia (1.6 or later is required). Then, from the root directory (the parent directory of the @@ -14,13 +27,15 @@ julia subdirectory) run the following in julia: ```julia - Pkg.add(url=".",subdir="julia") +julia> using Pkg + +julia> Pkg.add(url=".",subdir="julia") ``` You can test the package with ```julia -Pkg.test("FMM3D") +julia> Pkg.test("FMM3D") ``` ## Contributing diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d1e59aeb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[build-system] +requires = [ + "scikit-build-core >= 0.4.3", + "cmake >= 3.19", + "numpy >= 1.12.0", +] + +build-backend = "scikit_build_core.build" + +[project] +name = "fmm3dpy" +description = "Python bindings for the FMM3D library" +readme = "README.md" +requires-python = ">=3.8" +dependencies = ["numpy >= 1.12.0"] +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}" + +cmake.targets = ["fmm3d", "hfmm3d_fortran", "lfmm3d_fortran", "emfmm3d_fortran", "stfmm3d_fortran"] + +wheel.packages = ["python/fmm3dpy"] + +[tool.scikit-build.metadata.version] +provider = "scikit_build_core.metadata.regex" +input = "python/fmm3dpy/__init__.py" + +[tool.cibuildwheel] +# Necessary to see build output from the actual compilation +build-verbosity = 1 diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 00000000..e5a90d69 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,5 @@ +if (WIN32) + install(TARGETS hfmm3d_fortran lfmm3d_fortran emfmm3d_fortran stfmm3d_fortran DESTINATION fmm3dpy RUNTIME DESTINATION fmm3dpy) +else () + install(TARGETS hfmm3d_fortran lfmm3d_fortran emfmm3d_fortran stfmm3d_fortran DESTINATION fmm3dpy) +endif () diff --git a/python/fmm3dpy/__init__.py b/python/fmm3dpy/__init__.py index 0218d447..f4ceedda 100644 --- a/python/fmm3dpy/__init__.py +++ b/python/fmm3dpy/__init__.py @@ -1 +1,2 @@ from .fmm3d import hfmm3d,lfmm3d,emfmm3d,stfmm3d,h3ddir,l3ddir,em3ddir,st3ddir,comperr,Output +__version__ = '1.0.4' diff --git a/src/Laplace/lapkernels.f b/src/Laplace/lapkernels.f index 2295df85..cf7637ae 100644 --- a/src/Laplace/lapkernels.f +++ b/src/Laplace/lapkernels.f @@ -7,8 +7,8 @@ c for a collection of charge sources to a c collection of targets c -c l3ddirectch: direct calculation of potential and gradients -c for a collection of charge sources to a +c l3ddirectch: direct calculation of potential, gradients and +c hessians for a collection of charge sources to a c collection of targets c c l3ddirectdp: direct calculation of potential for a collection @@ -18,21 +18,21 @@ c for a collection of dipole sources to a c collection of targets c -c l3ddirectdh: direct calculation of potential and gradients -c for a collection of dipole sources to a +c l3ddirectdh: direct calculation of potential, gradients and +c hessians for a collection of dipole sources to a c collection of targets c c l3ddirectcdp: direct calculation of potential for a collection c of charge and dipole sources to a collection c of targets c -c l3ddirectdg: direct calculation of potential and gradients +c l3ddirectcdg: direct calculation of potential and gradients c for a collection of charge and dipole sources to c a collection of targets c -c l3ddirectdh: direct calculation of potential and gradients -c for a collection of charge and dipole sources to -c a collection of targets +c l3ddirectcdh: direct calculation of potential, gradients and +c hessians for a collection of charge and dipole +c sources to a collection of targets c c c diff --git a/src/Laplace/lapkernels_fast.f b/src/Laplace/lapkernels_fast.f index 0c2f005c..ffd4ee4e 100644 --- a/src/Laplace/lapkernels_fast.f +++ b/src/Laplace/lapkernels_fast.f @@ -8,6 +8,10 @@ c for a collection of charge sources to a c collection of targets c +c l3ddirectch: direct calculation of potential, gradients and +c hessians for a collection of charge sources to a +c collection of targets +c c l3ddirectdp: direct calculation of potential for a collection c of dipole sources to a collection of targets c @@ -15,14 +19,22 @@ c for a collection of dipole sources to a c collection of targets c +c l3ddirectdh: direct calculation of potential, gradients and +c hessians for a collection of dipole sources to a +c collection of targets +c c l3ddirectcdp: direct calculation of potential for a collection c of charge and dipole sources to a collection c of targets c -c l3ddirectdg: direct calculation of potential and gradients +c l3ddirectcdg: direct calculation of potential and gradients c for a collection of charge and dipole sources to c a collection of targets c +c l3ddirectcdh: direct calculation of potential, gradients and +c hessians for a collection of charge and dipole +c sources to a collection of targets +c c c c