From c915fe452baaf495b7d3e450eaed549936ce5b1e Mon Sep 17 00:00:00 2001 From: Julien Schueller Date: Mon, 13 May 2024 22:30:06 +0200 Subject: [PATCH] Turn to scikit-build-core Proof-of-concept to support Python 3.12 Supports parallel build --- .github/workflows/build.yml | 4 +- CMakeLists.txt | 254 ++++++++++++++++++++++++++++++++++++ cmake/FindSuperLU_MT.cmake | 58 ++++++++ pyproject.toml | 17 +++ 4 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 cmake/FindSuperLU_MT.cmake create mode 100644 pyproject.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c216f33..370cf211 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,6 +41,8 @@ jobs: make -j4 sudo make install - name: Build - run: python3 setup.py install --user --sundials-home=/usr --blas-home=/usr/lib/x86_64-linux-gnu/ --lapack-home=/usr/lib/x86_64-linux-gnu/ --superlu-home=/usr --extra-fortran-compile-flags="-fallow-argument-mismatch" + run: | + cmake -B build -DCMAKE_INSTALL_PREFIX=~/.local . + cmake --build build --target install --parallel 1 - name: Test run: python3 -m nose --verbose tests/* tests/solvers/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ee2b468e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,254 @@ +cmake_minimum_required (VERSION 3.17) + +project (assimulo LANGUAGES C) + +option(USE_FORTRAN "build Fortran modules" ON) +option(USE_SUNDIALS "build SUNDIALS modules" ON) +option(USE_SUPERLU "use SUPERLU lib" ON) +if (USE_FORTRAN) + enable_language(Fortran) +endif () + +find_package (Python COMPONENTS Interpreter Development.Module NumPy REQUIRED) + +find_package(LAPACK) + +list (APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +if (USE_SUPERLU) + find_package (SuperLU_MT) +endif () + +if (USE_SUNDIALS) + find_package (SUNDIALS CONFIG) + if (SUNDIALS_FOUND) + message(STATUS "Found SUNDIALS: ${SUNDIALS_DIR} (found version \"${SUNDIALS_VERSION}\")") + endif () +endif () + +include(GNUInstallDirs) +if (NOT DEFINED PYTHON_SITE_PACKAGES) + if (WIN32) + set (PYTHON_SITE_PACKAGES Lib/site-packages CACHE PATH "site-packages dir") + else () + set (PYTHON_SITE_PACKAGES ${CMAKE_INSTALL_LIBDIR}/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages CACHE PATH "site-packages dir") + endif () +endif () + +set (cython_clones) +# copy /src tree into CMAKE_BINARY_DIR/assimulo +file (GLOB_RECURSE cython_sources src/*.pyx src/*.pxd src/*.py) +foreach (file ${cython_sources}) + if (EXISTS ${file}) + file (RELATIVE_PATH rel_file "${CMAKE_SOURCE_DIR}/src" "${file}") + get_filename_component (rel_path ${rel_file} PATH) + file (MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/assimulo/${rel_path}) + set (cython_clone ${CMAKE_BINARY_DIR}/assimulo/${rel_file}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${file} ${cython_clone}) + add_custom_command (OUTPUT ${cython_clone} + COMMENT "Copying ${file}" + COMMAND ${CMAKE_COMMAND} -E copy ${file} ${cython_clone} + DEPENDS ${file} + ) + list (APPEND cython_clones ${cython_clone}) + endif () +endforeach () + +# copy /thirdparty tree into CMAKE_BINARY_DIR +file (GLOB_RECURSE cython_sources thirdparty/*.pyx thirdparty/*.pxd thirdparty/*.py) +foreach (file ${cython_sources}) + if (EXISTS ${file}) + file (RELATIVE_PATH rel_file "${CMAKE_SOURCE_DIR}/thirdparty" "${file}") + get_filename_component (rel_path ${rel_file} PATH) + file (MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/thirdparty/${rel_path}) + set (cython_clone ${CMAKE_BINARY_DIR}/thirdparty/${rel_file}) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${file} ${cython_clone}) + add_custom_command (OUTPUT ${cython_clone} + COMMENT "Copying ${file}" + COMMAND ${CMAKE_COMMAND} -E copy ${file} ${cython_clone} + DEPENDS ${file} + ) + list (APPEND cython_clones ${cython_clone}) + endif () +endforeach () + +macro(assimulo_add_cython_module pyx_file) + cmake_parse_arguments(CMOD "" "DESTINATION" "SOURCES" ${ARGN}) + if (NOT DEFINED CMOD_DESTINATION) + set (CMOD_DESTINATION assimulo) + endif () + get_filename_component(name "${pyx_file}" NAME_WE) + get_filename_component(subdir "${pyx_file}" DIRECTORY) + add_custom_command( + OUTPUT ${name}.c + COMMENT "Making ${name}.c from ${name}.pyx" + COMMAND Python::Interpreter -m cython -o ${name}.c --3str --fast-fail + -I ${CMAKE_CURRENT_SOURCE_DIR}/src + -I ${CMAKE_CURRENT_SOURCE_DIR}/src/lib + ${CMAKE_CURRENT_BINARY_DIR}/${pyx_file} + DEPENDS ${cython_clones} VERBATIM) + + python_add_library(${name} MODULE ${name}.c ${CMOD_SOURCES} WITH_SOABI) + target_include_directories(${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}) + target_link_libraries(${name} PRIVATE Python::NumPy) + target_compile_definitions(${name} PRIVATE NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) + if (CMAKE_C_COMPILER_ID MATCHES "GNU") + target_compile_options(${name} PRIVATE $<$:-O2 -fno-strict-aliasing>) + endif () + install(TARGETS ${name} DESTINATION ${PYTHON_SITE_PACKAGES}/${CMOD_DESTINATION}) +endmacro() + +assimulo_add_cython_module(assimulo/explicit_ode.pyx + SOURCES src/ode_event_locator.c) +assimulo_add_cython_module(assimulo/algebraic.pyx) +assimulo_add_cython_module(assimulo/implicit_ode.pyx) +assimulo_add_cython_module(assimulo/ode.pyx) +assimulo_add_cython_module(assimulo/problem.pyx) +assimulo_add_cython_module(assimulo/special_systems.pyx) +assimulo_add_cython_module(assimulo/support.pyx) +assimulo_add_cython_module(assimulo/solvers/euler.pyx + DESTINATION assimulo/solvers) +#if (SUNDIALS_FOUND) +# assimulo_add_cython_module(solvers/sundials.pyx) +#endif () + + +assimulo_add_cython_module(thirdparty/radau5/radau5ode.pyx + SOURCES thirdparty/radau5/radau5.c thirdparty/radau5/radau5_io.c + DESTINATION assimulo/lib) + +if (SuperLU_MT_FOUND) + target_sources(radau5ode PRIVATE thirdparty/radau5/superlu_double.c thirdparty/radau5/superlu_complex.c thirdparty/radau5/superlu_util.c) + target_link_libraries(radau5ode PRIVATE SuperLU_MT::SuperLU_MT) + target_compile_definitions(radau5ode PRIVATE __RADAU5_WITH_SUPERLU) +endif() + +if (USE_FORTRAN) + + find_program(F2PY_EXECUTABLE NAMES f2py f2py3 REQUIRED) + + if (NOT DEFINED F2PY_INCLUDE_DIR) + execute_process(COMMAND ${Python_EXECUTABLE} -c "from numpy import f2py; print(f2py.get_include())" + OUTPUT_VARIABLE F2PY_INCLUDE_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + message(STATUS "Using f2py include dir: ${F2PY_INCLUDE_DIR}") + + # object library with common f2py symbols + add_library(fortranobject OBJECT ${F2PY_INCLUDE_DIR}/fortranobject.c) + target_link_libraries(fortranobject PUBLIC Python::NumPy) + target_compile_definitions(fortranobject PUBLIC NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) + target_include_directories(fortranobject PUBLIC ${F2PY_INCLUDE_DIR}) + set_target_properties(fortranobject PROPERTIES POSITION_INDEPENDENT_CODE TRUE) + + macro(assimulo_add_fortran_module name pyf_file) + cmake_parse_arguments(FMOD "" "" "SOURCES" ${ARGN}) + add_custom_command( + OUTPUT ${name}module.c + COMMENT "Making ${name}module.c from ${pyf_file}" + COMMAND ${F2PY_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${pyf_file} + DEPENDS ${pyf_file} VERBATIM) + + # may not be created depending on numpy version + file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/${name}-f2pywrappers.f) + + python_add_library(${name} MODULE ${FMOD_SOURCES} + ${CMAKE_CURRENT_BINARY_DIR}/${name}module.c + ${CMAKE_CURRENT_BINARY_DIR}/${name}-f2pywrappers.f + WITH_SOABI) + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${name}module.c ${CMAKE_CURRENT_BINARY_DIR}/${name}-f2pywrappers.f PROPERTIES GENERATED TRUE) + + target_link_libraries(${name} PRIVATE fortranobject) + if (CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + target_compile_options(${name} PRIVATE $<$:-O2 -fallow-argument-mismatch>) + endif () + install(TARGETS ${name} DESTINATION ${PYTHON_SITE_PACKAGES}/assimulo/lib) + endmacro() + + assimulo_add_fortran_module(dopri5 thirdparty/hairer/dopri5.pyf + SOURCES thirdparty/hairer/dopri5.f) + + assimulo_add_fortran_module(rodas thirdparty/hairer/rodas_decsol.pyf + SOURCES thirdparty/hairer/rodas_decsol.f) + + assimulo_add_fortran_module(radau5 thirdparty/hairer/radau_decsol.pyf + SOURCES thirdparty/hairer/radau_decsol.f) + + assimulo_add_fortran_module(radar5 thirdparty/hairer/radar5.pyf + SOURCES + thirdparty/hairer/contr5.f90 + thirdparty/hairer/radar5_int.f90 + thirdparty/hairer/radar5.f90 + thirdparty/hairer/dontr5.f90 + thirdparty/hairer/decsol.f90 + thirdparty/hairer/dc_decdel.f90) + + assimulo_add_fortran_module(odepack thirdparty/odepack/odepack.pyf + SOURCES + thirdparty/odepack/opkdmain.f + thirdparty/odepack/opkda1.f + thirdparty/odepack/opkda2.f + thirdparty/odepack/odepack_aux.f90) + + assimulo_add_fortran_module(odassl thirdparty/odassl/odassl.pyf + SOURCES + thirdparty/odassl/odassl.f + thirdparty/odassl/odastp.f + thirdparty/odassl/odacor.f + thirdparty/odassl/odajac.f + thirdparty/odassl/d1mach.f + thirdparty/odassl/daxpy.f + thirdparty/odassl/ddanrm.f + thirdparty/odassl/ddatrp.f + thirdparty/odassl/ddot.f + thirdparty/odassl/ddwats.f + thirdparty/odassl/dgefa.f + thirdparty/odassl/dgesl.f + thirdparty/odassl/dscal.f + thirdparty/odassl/idamax.f + thirdparty/odassl/xerrwv.f) + if (CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + target_compile_options(odassl PRIVATE $<$:-fdefault-double-8 -fdefault-real-8>) + endif () + + assimulo_add_fortran_module(dasp3dp thirdparty/dasp3/dasp3dp.pyf + SOURCES + thirdparty/dasp3/DASP3.f + thirdparty/dasp3/ANORM.f + thirdparty/dasp3/CTRACT.f + thirdparty/dasp3/DECOMP.f + thirdparty/dasp3/HMAX.f + thirdparty/dasp3/INIVAL.f + thirdparty/dasp3/JACEST.f + thirdparty/dasp3/PDERIV.f + thirdparty/dasp3/PREPOL.f + thirdparty/dasp3/SOLVE.f + thirdparty/dasp3/SPAPAT.f) + + if (LAPACK_FOUND) + assimulo_add_fortran_module(glimda thirdparty/glimda/glimda_complete.pyf + SOURCES + thirdparty/glimda/glimda_complete.f) + target_link_libraries(glimda PRIVATE ${LAPACK_LIBRARIES}) + endif () +endif () + +install(FILES src/__init__.py + src/exception.py + src/problem_algebraic.py + DESTINATION ${PYTHON_SITE_PACKAGES}/assimulo) +install(FILES src/lib/__init__.py + src/lib/radau_core.py + DESTINATION ${PYTHON_SITE_PACKAGES}/assimulo/lib) +install(FILES src/solvers/__init__.py + src/solvers/dasp3.py + src/solvers/glimda.py + src/solvers/odassl.py + src/solvers/odepack.py + src/solvers/radar5.py + src/solvers/radau5.py + src/solvers/rosenbrock.py + src/solvers/runge_kutta.py + DESTINATION ${PYTHON_SITE_PACKAGES}/assimulo/solvers) +install(DIRECTORY examples + DESTINATION ${PYTHON_SITE_PACKAGES}/assimulo) diff --git a/cmake/FindSuperLU_MT.cmake b/cmake/FindSuperLU_MT.cmake new file mode 100644 index 00000000..d613e281 --- /dev/null +++ b/cmake/FindSuperLU_MT.cmake @@ -0,0 +1,58 @@ +# - Find SuperLU_MT +# SuperLU_MT contains a set of subroutines to solve a sparse linear system +# https://github.com/xiaoyeli/superlu_mt +# +# The module defines the following variables: +# SUPERLUMT_INCLUDE_DIRS, where to find mpc.h, etc. +# SUPERLUMT_LIBRARIES, the libraries needed to use MPC. +# SUPERLUMT_FOUND, If false, do not try to use MPC. +# also defined, but not for general use are +# SUPERLUMT_LIBRARY, where to find the MPC library. +# + +find_path (SUPERLUMT_INCLUDE_DIR slu_mt_cdefs.h + PATH_SUFFIXES superlu_mt +) + +find_library (SUPERLUMT_LIBRARY + NAMES superlu_mt_OPENMP superlu_mt_THREAD +) + +set (SUPERLUMT_LIBRARIES ${SUPERLUMT_LIBRARY}) +set (SUPERLUMT_INCLUDE_DIRS ${SUPERLUMT_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SuperLU_MT DEFAULT_MSG SUPERLUMT_LIBRARY SUPERLUMT_INCLUDE_DIRS) + +mark_as_advanced ( + SUPERLUMT_LIBRARY + SUPERLUMT_LIBRARIES + SUPERLUMT_INCLUDE_DIR + SUPERLUMT_INCLUDE_DIRS) + + + +if(NOT TARGET SuperLU_MT::SuperLU_MT) + add_library(SuperLU_MT::SuperLU_MT UNKNOWN IMPORTED) + set_target_properties(SuperLU_MT::SuperLU_MT + PROPERTIES + IMPORTED_LOCATION ${SUPERLUMT_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${SUPERLUMT_INCLUDE_DIR}") + + find_package(BLAS QUIET) + if (BLAS_LIBRARIES) + target_link_libraries(SuperLU_MT::SuperLU_MT INTERFACE ${BLAS_LIBRARIES}) + endif () + + if (SUPERLUMT_LIBRARY MATCHES OPENMP) + find_package(OpenMP QUIET) + if (OpenMP_FOUND) + target_link_libraries(SuperLU_MT::SuperLU_MT INTERFACE OpenMP::OpenMP_C) + endif () + endif () + + find_library(MATH_LIBRARY NAMES m) + if (MATH_LIBRARY) + target_link_libraries(SuperLU_MT::SuperLU_MT INTERFACE ${MATH_LIBRARY}) + endif () +endif() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..36d8cdae --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "assimulo" +version = "3.5.0" +dependencies = [ + "scipy", + "cython", +] + +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +logging.level = "DEBUG" + +[tool.scikit-build.cmake.define] +CMAKE_VERBOSE_MAKEFILE = "ON"