diff --git a/CMakeLists.txt b/CMakeLists.txt index 3da0599..37a8da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,197 +1,201 @@ # ============================================================================= # file CMakeLists.txt # # @author Till Junge # # @date 08 Jan 2018 # # @brief Main configuration file # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # ============================================================================= cmake_minimum_required(VERSION 3.5.0) project(µSpectre) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(BUILD_SHARED_LIBS ON) add_compile_options(-Wall -Wextra -Weffc++) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) set(MAKE_DOC_TARGET "OFF" CACHE BOOL "If on, a target dev_doc (which builds the documentation) is added") set(MAKE_TESTS "ON" CACHE BOOL "If on, several ctest targets will be built automatically") set(MAKE_EXAMPLES "ON" CACHE BOOL "If on, the executables in the bin folder will be compiled") set(MAKE_BENCHMARKS "ON" CACHE BOOL "If on, the benchmarks will be compiled") set(MPI_PARALLEL "OFF" CACHE BOOL "If on, MPI-parallel solvers become available") if(${MAKE_TESTS}) enable_testing() find_package(Boost COMPONENTS unit_test_framework REQUIRED) endif(${MAKE_TESTS}) if(${MPI_PARALLEL}) add_definitions(-DWITH_MPI) find_package(MPI) if (NOT ${MPI_FOUND}) message(SEND_ERROR "You chose MPI but CMake cannot find the MPI package") endif(NOT ${MPI_FOUND}) endif(${MPI_PARALLEL}) include(muspectreTools) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") # using Clang add_compile_options(-Wno-missing-braces) elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # using GCC add_compile_options(-Wno-non-virtual-dtor) string( TOLOWER "${CMAKE_BUILD_TYPE}" build_type ) if ("release" STREQUAL "${build_type}" ) add_compile_options(-march=native) endif() if ("debug" STREQUAL "${build_type}" ) add_compile_options(-O0) endif() elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") # using Intel C++ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # using Visual Studio C++ endif() # Do not trust old gcc. the std::optional has memory bugs if(${CMAKE_COMPILER_IS_GNUCC}) if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6.0.0) add_definitions(-DNO_EXPERIMENTAL) endif() endif() add_external_package(Eigen3 VERSION 3.3.0 CONFIG) add_external_package(pybind11 VERSION 2.2 CONFIG) include_directories( ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR} ) if(APPLE) include_directories(${CMAKE_INSTALL_PREFIX}/include ${Boost_INCLUDE_DIRS}) endif() #build tests (these are before we add -Werror to the compile options) if (${MAKE_TESTS}) ############################################################################## # build tests file( GLOB TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/test_*.cc") add_executable(main_test_suite tests/main_test_suite.cc ${TEST_SRCS}) target_link_libraries(main_test_suite ${Boost_LIBRARIES} muSpectre) add_test(main_test_suite main_test_suite --report_level=detailed --build_info=TRUE) ############################################################################## # copy python test file( GLOB PY_TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/python_*.py") foreach(pytest ${PY_TEST_SRCS}) get_filename_component(pytest_name ${pytest} NAME) configure_file( ${pytest} "${CMAKE_BINARY_DIR}/${pytest_name}" COPYONLY) endforeach(pytest ${PY_TEST_SRCS}) + find_package(PythonInterp 3 REQUIRED) add_test(python_binding_test python_binding_tests.py) if(${MPI_PARALLEL}) ############################################################################ # add MPI tests file( GLOB TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/mpi_test_*.cc") add_executable(mpi_main_test_suite tests/mpi_main_test_suite.cc ${TEST_SRCS}) target_link_libraries(mpi_main_test_suite ${Boost_LIBRARIES} muSpectre) add_test(mpi_main_test_suite1 ${MPIEXEC_EXECUTABLE} ${MPIEXEC_PREFLAGS} ${MPIEXEC_NUMPROC_FLAG} 1 mpi_main_test_suite --report_level=detailed --build_info=TRUE) add_test(mpi_main_test_suite2 ${MPIEXEC_EXECUTABLE} ${MPIEXEC_PREFLAGS} ${MPIEXEC_NUMPROC_FLAG} 2 mpi_main_test_suite --report_level=detailed --build_info=TRUE) + + add_test(python_mpi_binding_test1 ${MPIEXEC} ${MPIEXEC_PREFLAGS} ${MPIEXEC_NUMPROC_FLAG} 1 ${PYTHON_EXECUTABLE} python_mpi_binding_tests.py) + add_test(python_mpi_binding_test2 ${MPIEXEC} ${MPIEXEC_PREFLAGS} ${MPIEXEC_NUMPROC_FLAG} 2 ${PYTHON_EXECUTABLE} python_mpi_binding_tests.py) endif(${MPI_PARALLEL}) endif(${MAKE_TESTS}) ################################################################################ # compile the library add_compile_options( -Werror) add_subdirectory( ${CMAKE_SOURCE_DIR}/src/ ) add_subdirectory( ${CMAKE_SOURCE_DIR}/language_bindings/ ) if (${MAKE_DOC_TARGET}) add_subdirectory( ${CMAKE_SOURCE_DIR}/doc/ ) endif() ################################################################################ if (${MAKE_EXAMPLES}) #compile executables set(binaries ${CMAKE_SOURCE_DIR}/bin/demonstrator1.cc ${CMAKE_SOURCE_DIR}/bin/demonstrator2.cc ${CMAKE_SOURCE_DIR}/bin/hyper-elasticity.cc ${CMAKE_SOURCE_DIR}/bin/small_case.cc) if (${MPI_PARALLEL}) set (binaries ${binaries} ${CMAKE_SOURCE_DIR}/bin/demonstrator_mpi.cc ) endif (${MPI_PARALLEL}) foreach(binaryfile ${binaries}) get_filename_component(binaryname ${binaryfile} NAME_WE) add_executable(${binaryname} ${binaryfile}) target_link_libraries(${binaryname} ${Boost_LIBRARIES} muSpectre) endforeach(binaryfile ${binaries}) #or copy them file (GLOB pybins "${CMAKE_SOURCE_DIR}/bin/*.py") foreach(pybin ${pybins}) get_filename_component(binaryname ${pybin} NAME_WE) configure_file( ${pybin} "${CMAKE_BINARY_DIR}/${binaryname}.py" COPYONLY) endforeach(pybin ${pybins}) endif (${MAKE_EXAMPLES}) ################################################################################ # compile benchmarks if(${MAKE_BENCHMARKS}) file(GLOB benchmarks "${CMAKE_SOURCE_DIR}/benchmarks/benchmark*cc") foreach(benchmark ${benchmarks}) get_filename_component(benchmark_name ${benchmark} NAME_WE) add_executable(${benchmark_name} ${benchmark}) target_link_libraries(${benchmark_name} ${BOOST_LIBRARIES} muSpectre) endforeach(benchmark ${benchmark}) endif(${MAKE_BENCHMARKS}) diff --git a/cmake/FindFFTW.cmake b/cmake/FindFFTW.cmake index 75dc797..30430a0 100644 --- a/cmake/FindFFTW.cmake +++ b/cmake/FindFFTW.cmake @@ -1,88 +1,88 @@ # - Find the FFTW library # # Usage: # find_package(FFTW [REQUIRED] [QUIET] ) # # It sets the following variables: # FFTW_FOUND ... true if fftw is found on the system # FFTW_LIBRARIES ... full path to fftw library # FFTW_INCLUDES ... fftw include directory # # The following variables will be checked by the function # FFTW_USE_STATIC_LIBS ... if true, only static libraries are found # FFTW_ROOT ... if set, the libraries are exclusively searched # under this path # FFTW_LIBRARY ... fftw library to use # FFTW_INCLUDE_DIR ... fftw include directory # #If environment variable FFTWDIR is specified, it has same effect as FFTW_ROOT if( NOT FFTW_ROOT AND ENV{FFTWDIR} ) set( FFTW_ROOT $ENV{FFTWDIR} ) endif() # Check if we can use PkgConfig find_package(PkgConfig) #Determine from PKG if( PKG_CONFIG_FOUND AND NOT FFTW_ROOT ) pkg_check_modules( PKG_FFTW QUIET "fftw3" ) endif() #Check whether to search static or dynamic libs set( CMAKE_FIND_LIBRARY_SUFFIXES_SAV ${CMAKE_FIND_LIBRARY_SUFFIXES} ) if( ${FFTW_USE_STATIC_LIBS} ) set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} ) else() set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX} ) endif() #find libs find_library( FFTW_LIB NAMES "fftw3" PATHS ${FFTW_ROOT} ${PKG_FFTW_PREFIX} ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} PATH_SUFFIXES "lib" "lib64" ) find_library( FFTWF_LIB NAMES "fftw3f" PATHS ${FFTW_ROOT} ${PKG_FFTW_PREFIX} ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} PATH_SUFFIXES "lib" "lib64" ) find_library( FFTWL_LIB NAMES "fftw3l" PATHS ${FFTW_ROOT} ${PKG_FFTW_PREFIX} ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} PATH_SUFFIXES "lib" "lib64" ) #find includes find_path( FFTW_INCLUDES NAMES "fftw3.h" PATHS ${FFTW_ROOT} ${PKG_FFTW_PREFIX} ${PKG_FFTW_INCLUDE_DIRS} ${INCLUDE_INSTALL_DIR} PATH_SUFFIXES "include" ) set(FFTW_LIBRARIES ${FFTW_LIB}) if(FFTWF_LIB) - set(FFTW_LIBRARIES ${FFTW_LIBRARIES} ${FFTWF_LIB}) + list(APPEND FFTW_LIBRARIES ${FFTWF_LIB}) endif() if(FFTWL_LIB) - set(FFTW_LIBRARIES ${FFTW_LIBRARIES} ${FFTWL_LIB}) + list(APPEND FFTW_LIBRARIES ${FFTWL_LIB}) endif() set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_SAV} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(FFTW DEFAULT_MSG FFTW_INCLUDES FFTW_LIBRARIES) mark_as_advanced(FFTW_INCLUDES FFTW_LIBRARIES FFTW_LIB FFTWF_LIB FFTWL_LIB) diff --git a/cmake/FindFFTWMPI.cmake b/cmake/FindFFTWMPI.cmake index 46ed76e..2383be1 100644 --- a/cmake/FindFFTWMPI.cmake +++ b/cmake/FindFFTWMPI.cmake @@ -1,88 +1,87 @@ # - Find the FFTWMPI library # # Usage: # find_package(FFTWMPI [REQUIRED] [QUIET] ) # # It sets the following variables: # FFTWMPI_FOUND ... true if fftw is found on the system # FFTWMPI_LIBRARIES ... full path to fftw library # FFTWMPI_INCLUDES ... fftw include directory # # The following variables will be checked by the function # FFTWMPI_USE_STATIC_LIBS ... if true, only static libraries are found # FFTWMPI_ROOT ... if set, the libraries are exclusively searched # under this path # FFTWMPI_LIBRARY ... fftw library to use # FFTWMPI_INCLUDE_DIR ... fftw include directory # #If environment variable FFTWMPIDIR is specified, it has same effect as FFTWMPI_ROOT if( NOT FFTWMPI_ROOT AND ENV{FFTWDIR} ) set( FFTWMPI_ROOT $ENV{FFTWDIR} ) endif() # Check if we can use PkgConfig find_package(PkgConfig) #Determine from PKG if( PKG_CONFIG_FOUND AND NOT FFTWMPI_ROOT ) pkg_check_modules( PKG_FFTWMPI QUIET "fftw3" ) endif() #Check whether to search static or dynamic libs set( CMAKE_FIND_LIBRARY_SUFFIXES_SAV ${CMAKE_FIND_LIBRARY_SUFFIXES} ) if( ${FFTWMPI_USE_STATIC_LIBS} ) set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} ) else() set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX} ) endif() #find libs find_library( FFTWMPI_LIB NAMES "fftw3_mpi" PATHS ${FFTWMPI_ROOT} ${PKG_FFTWMPI_PREFIX} ${PKG_FFTWMPI_LIBRARY_DIRS} ${LIB_INSTALL_DIR} PATH_SUFFIXES "lib" "lib64" ) find_library( FFTWMPIF_LIB NAMES "fftw3f_mpi" PATHS ${FFTWMPI_ROOT} ${PKG_FFTWMPI_PREFIX} ${PKG_FFTWMPI_LIBRARY_DIRS} ${LIB_INSTALL_DIR} PATH_SUFFIXES "lib" "lib64" ) find_library( FFTWMPIL_LIB NAMES "fftw3l_mpi" PATHS ${FFTWMPI_ROOT} ${PKG_FFTWMPI_PREFIX} ${PKG_FFTWMPI_LIBRARY_DIRS} ${LIB_INSTALL_DIR} PATH_SUFFIXES "lib" "lib64" ) #find includes find_path( FFTWMPI_INCLUDES NAMES "fftw3-mpi.h" PATHS ${FFTWMPI_ROOT} ${PKG_FFTWMPI_PREFIX} ${PKG_FFTWMPI_INCLUDE_DIRS} ${INCLUDE_INSTALL_DIR} PATH_SUFFIXES "include" ) set(FFTWMPI_LIBRARIES ${FFTWMPI_LIB}) if(FFTWMPIF_LIB) - set(FFTWMPI_LIBRARIES ${FFTWMPI_LIBRARIES} ${FFTWMPIF_LIB}) + list(APPEND FFTWMPI_LIBRARIES ${FFTWMPIF_LIB}) endif() if(FFTWMPIL_LIB) - set(FFTWMPI_LIBRARIES ${FFTWMPI_LIBRARIES} ${FFTWMPIL_LIB}) + list(APPEND FFTWMPI_LIBRARIES ${FFTWMPIL_LIB}) endif() set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_SAV} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(FFTWMPI DEFAULT_MSG FFTWMPI_INCLUDES FFTWMPI_LIBRARIES) mark_as_advanced(FFTWMPI_INCLUDES FFTWMPI_LIBRARIES FFTWMPI_LIB FFTWMPIF_LIB FFTWMPIL_LIB) - diff --git a/cmake/FindPythonLibsNew.cmake b/cmake/FindPythonLibsNew.cmake new file mode 100644 index 0000000..b29b287 --- /dev/null +++ b/cmake/FindPythonLibsNew.cmake @@ -0,0 +1,195 @@ +# - Find python libraries +# This module finds the libraries corresponding to the Python interpreter +# FindPythonInterp provides. +# This code sets the following variables: +# +# PYTHONLIBS_FOUND - have the Python libs been found +# PYTHON_PREFIX - path to the Python installation +# PYTHON_LIBRARIES - path to the python library +# PYTHON_INCLUDE_DIRS - path to where Python.h is found +# PYTHON_MODULE_EXTENSION - lib extension, e.g. '.so' or '.pyd' +# PYTHON_MODULE_PREFIX - lib name prefix: usually an empty string +# PYTHON_SITE_PACKAGES - path to installation site-packages +# PYTHON_IS_DEBUG - whether the Python interpreter is a debug build +# +# Thanks to talljimbo for the patch adding the 'LDVERSION' config +# variable usage. + +#============================================================================= +# Copyright 2001-2009 Kitware, Inc. +# Copyright 2012 Continuum Analytics, Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +# Checking for the extension makes sure that `LibsNew` was found and not just `Libs`. +if(PYTHONLIBS_FOUND AND PYTHON_MODULE_EXTENSION) + return() +endif() + +# Use the Python interpreter to find the libs. +if(PythonLibsNew_FIND_REQUIRED) + find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} REQUIRED) +else() + find_package(PythonInterp ${PythonLibsNew_FIND_VERSION}) +endif() + +if(NOT PYTHONINTERP_FOUND) + set(PYTHONLIBS_FOUND FALSE) + return() +endif() + +# According to http://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter +# testing whether sys has the gettotalrefcount function is a reliable, cross-platform +# way to detect a CPython debug interpreter. +# +# The library suffix is from the config var LDVERSION sometimes, otherwise +# VERSION. VERSION will typically be like "2.7" on unix, and "27" on windows. +execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" + "from distutils import sysconfig as s;import sys;import struct; +print('.'.join(str(v) for v in sys.version_info)); +print(sys.prefix); +print(s.get_python_inc(plat_specific=True)); +print(s.get_python_lib(plat_specific=True)); +print(s.get_config_var('SO')); +print(hasattr(sys, 'gettotalrefcount')+0); +print(struct.calcsize('@P')); +print(s.get_config_var('LDVERSION') or s.get_config_var('VERSION')); +print(s.get_config_var('LIBDIR') or ''); +print(s.get_config_var('MULTIARCH') or ''); +" + RESULT_VARIABLE _PYTHON_SUCCESS + OUTPUT_VARIABLE _PYTHON_VALUES + ERROR_VARIABLE _PYTHON_ERROR_VALUE) + +if(NOT _PYTHON_SUCCESS MATCHES 0) + if(PythonLibsNew_FIND_REQUIRED) + message(FATAL_ERROR + "Python config failure:\n${_PYTHON_ERROR_VALUE}") + endif() + set(PYTHONLIBS_FOUND FALSE) + return() +endif() + +# Convert the process output into a list +string(REGEX REPLACE ";" "\\\\;" _PYTHON_VALUES ${_PYTHON_VALUES}) +string(REGEX REPLACE "\n" ";" _PYTHON_VALUES ${_PYTHON_VALUES}) +list(GET _PYTHON_VALUES 0 _PYTHON_VERSION_LIST) +list(GET _PYTHON_VALUES 1 PYTHON_PREFIX) +list(GET _PYTHON_VALUES 2 PYTHON_INCLUDE_DIR) +list(GET _PYTHON_VALUES 3 PYTHON_SITE_PACKAGES) +list(GET _PYTHON_VALUES 4 PYTHON_MODULE_EXTENSION) +list(GET _PYTHON_VALUES 5 PYTHON_IS_DEBUG) +list(GET _PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P) +list(GET _PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX) +list(GET _PYTHON_VALUES 8 PYTHON_LIBDIR) +list(GET _PYTHON_VALUES 9 PYTHON_MULTIARCH) + +# Make sure the Python has the same pointer-size as the chosen compiler +# Skip if CMAKE_SIZEOF_VOID_P is not defined +if(CMAKE_SIZEOF_VOID_P AND (NOT "${PYTHON_SIZEOF_VOID_P}" STREQUAL "${CMAKE_SIZEOF_VOID_P}")) + if(PythonLibsNew_FIND_REQUIRED) + math(EXPR _PYTHON_BITS "${PYTHON_SIZEOF_VOID_P} * 8") + math(EXPR _CMAKE_BITS "${CMAKE_SIZEOF_VOID_P} * 8") + message(FATAL_ERROR + "Python config failure: Python is ${_PYTHON_BITS}-bit, " + "chosen compiler is ${_CMAKE_BITS}-bit") + endif() + set(PYTHONLIBS_FOUND FALSE) + return() +endif() + +# The built-in FindPython didn't always give the version numbers +string(REGEX REPLACE "\\." ";" _PYTHON_VERSION_LIST ${_PYTHON_VERSION_LIST}) +list(GET _PYTHON_VERSION_LIST 0 PYTHON_VERSION_MAJOR) +list(GET _PYTHON_VERSION_LIST 1 PYTHON_VERSION_MINOR) +list(GET _PYTHON_VERSION_LIST 2 PYTHON_VERSION_PATCH) + +# Make sure all directory separators are '/' +string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX ${PYTHON_PREFIX}) +string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}) +string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES ${PYTHON_SITE_PACKAGES}) + +if(CMAKE_HOST_WIN32) + set(PYTHON_LIBRARY + "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") + + # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the + # original python installation. They may be found relative to PYTHON_INCLUDE_DIR. + if(NOT EXISTS "${PYTHON_LIBRARY}") + get_filename_component(_PYTHON_ROOT ${PYTHON_INCLUDE_DIR} DIRECTORY) + set(PYTHON_LIBRARY + "${_PYTHON_ROOT}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") + endif() + + # raise an error if the python libs are still not found. + if(NOT EXISTS "${PYTHON_LIBRARY}") + message(FATAL_ERROR "Python libraries not found") + endif() + +else() + if(PYTHON_MULTIARCH) + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}/${PYTHON_MULTIARCH}" "${PYTHON_LIBDIR}") + else() + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}") + endif() + #message(STATUS "Searching for Python libs in ${_PYTHON_LIBS_SEARCH}") + # Probably this needs to be more involved. It would be nice if the config + # information the python interpreter itself gave us were more complete. + find_library(PYTHON_LIBRARY + NAMES "python${PYTHON_LIBRARY_SUFFIX}" + PATHS ${_PYTHON_LIBS_SEARCH} + NO_DEFAULT_PATH) + + # If all else fails, just set the name/version and let the linker figure out the path. + if(NOT PYTHON_LIBRARY) + set(PYTHON_LIBRARY python${PYTHON_LIBRARY_SUFFIX}) + endif() +endif() + +MARK_AS_ADVANCED( + PYTHON_LIBRARY + PYTHON_INCLUDE_DIR +) + +# We use PYTHON_INCLUDE_DIR, PYTHON_LIBRARY and PYTHON_DEBUG_LIBRARY for the +# cache entries because they are meant to specify the location of a single +# library. We now set the variables listed by the documentation for this +# module. +SET(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") +SET(PYTHON_LIBRARIES "${PYTHON_LIBRARY}") +SET(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") + +find_package_message(PYTHON + "Found PythonLibs: ${PYTHON_LIBRARY}" + "${PYTHON_EXECUTABLE}${PYTHON_VERSION}") + +set(PYTHONLIBS_FOUND TRUE) diff --git a/language_bindings/python/CMakeLists.txt b/language_bindings/python/CMakeLists.txt index 3b0b94d..e09c7dd 100644 --- a/language_bindings/python/CMakeLists.txt +++ b/language_bindings/python/CMakeLists.txt @@ -1,66 +1,74 @@ #============================================================================== # file CMakeLists.txt # # @author Till Junge # # @date 08 Jan 2018 # # @brief configuration for python binding using pybind11 # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # # FIXME! The user should have a choice to configure this path. execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-m" "site" "--user-site" RESULT_VARIABLE _PYTHON_SUCCESS OUTPUT_VARIABLE PYTHON_USER_SITE ERROR_VARIABLE _PYTHON_ERROR_VALUE) if(NOT _PYTHON_SUCCESS MATCHES 0) message(FATAL_ERROR "Python config failure:\n${_PYTHON_ERROR_VALUE}") endif() string(REGEX REPLACE "\n" "" PYTHON_USER_SITE ${PYTHON_USER_SITE}) set (PY_BINDING_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_module.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_common.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_cell.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_solvers.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_fftengine.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_projections.cc ) -find_package(PythonLibs REQUIRED) +if (${USE_FFTWMPI}) + add_definitions(-DWITH_FFTWMPI) +endif(${USE_FFTWMPI}) + +if (${USE_PFFT}) + add_definitions(-DWITH_PFFT) +endif(${USE_PFFT}) + +find_package(PythonLibsNew 3 MODULE REQUIRED) pybind11_add_module(pyMuSpectreLib ${PY_BINDING_SRCS}) target_link_libraries(pyMuSpectreLib PRIVATE muSpectre) # Want to rename the output, so that the python module is called muSpectre set_target_properties(pyMuSpectreLib PROPERTIES OUTPUT_NAME _muSpectre) target_include_directories(pyMuSpectreLib PUBLIC ${PYTHON_INCLUDE_DIRS}) -add_custom_target(pyMuSpectre ALL SOURCES muSpectre/__init__.py) +add_custom_target(pyMuSpectre ALL SOURCES muSpectre/__init__.py muSpectre/fft.py) add_custom_command(TARGET pyMuSpectre POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/language_bindings/python/muSpectre $/muSpectre) install(TARGETS pyMuSpectreLib LIBRARY DESTINATION ${PYTHON_USER_SITE}) install(FILES muSpectre/__init__.py DESTINATION ${PYTHON_USER_SITE}/muSpectre) diff --git a/language_bindings/python/bind_py_cell.cc b/language_bindings/python/bind_py_cell.cc index a119cc6..c17f496 100644 --- a/language_bindings/python/bind_py_cell.cc +++ b/language_bindings/python/bind_py_cell.cc @@ -1,209 +1,213 @@ /** * @file bind_py_cell.cc * * @author Till Junge * * @date 09 Jan 2018 * * @brief Python bindings for the cell factory function * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Emacs; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "common/common.hh" #include "common/ccoord_operations.hh" #include "cell/cell_factory.hh" #include "cell/cell_base.hh" #ifdef WITH_FFTWMPI #include "fft/fftwmpi_engine.hh" #endif #ifdef WITH_PFFT #include "fft/pfft_engine.hh" #endif #include #include #include "pybind11/eigen.h" #include #include using namespace muSpectre; namespace py=pybind11; using namespace pybind11::literals; /** * cell factory for specific FFT engine */ #ifdef WITH_MPI template void add_parallel_cell_factory_helper(py::module & mod, const char *name) { using Ccoord = Ccoord_t; using Rcoord = Rcoord_t; mod.def (name, [](Ccoord res, Rcoord lens, Formulation form, size_t comm) { return make_parallel_cell , FFTEngine> (std::move(res), std::move(lens), std::move(form), std::move(Communicator(MPI_Comm(comm)))); }, "resolutions"_a, "lengths"_a=CcoordOps::get_cube(1.), "formulation"_a=Formulation::finite_strain, "communicator"_a=size_t(MPI_COMM_SELF)); } #endif /** * the cell factory is only bound for default template parameters */ template void add_cell_factory_helper(py::module & mod) { using Ccoord = Ccoord_t; using Rcoord = Rcoord_t; mod.def ("CellFactory", [](Ccoord res, Rcoord lens, Formulation form) { return make_cell(std::move(res), std::move(lens), std::move(form)); }, "resolutions"_a, "lengths"_a=CcoordOps::get_cube(1.), "formulation"_a=Formulation::finite_strain); #ifdef WITH_FFTWMPI add_parallel_cell_factory_helper>( mod, "FFTWMPICellFactory"); #endif #ifdef WITH_PFFT add_parallel_cell_factory_helper>( mod, "PFFTCellFactory"); #endif } void add_cell_factory(py::module & mod) { add_cell_factory_helper(mod); add_cell_factory_helper(mod); } /** * CellBase for which the material and spatial dimension are identical */ template void add_cell_base_helper(py::module & mod) { std::stringstream name_stream{}; name_stream << "CellBase" << dim << 'd'; const std::string name = name_stream.str(); using sys_t = CellBase; py::class_(mod, name.c_str()) .def("__len__", &sys_t::size) .def("__iter__", [](sys_t & s) { return py::make_iterator(s.begin(), s.end()); }) .def("initialise", &sys_t::initialise, "flags"_a=FFT_PlanFlags::estimate) .def("directional_stiffness", [](sys_t& cell, py::EigenDRef& v) { if ((size_t(v.cols()) != cell.size() || size_t(v.rows()) != dim*dim)) { std::stringstream err{}; err << "need array of shape (" << dim*dim << ", " << cell.size() << ") but got (" << v.rows() << ", " << v.cols() << ")."; throw std::runtime_error(err.str()); } if (!cell.is_initialised()) { cell.initialise(); } const std::string out_name{"temp output for directional stiffness"}; const std::string in_name{"temp input for directional stiffness"}; constexpr bool create_tangent{true}; auto & K = cell.get_tangent(create_tangent); auto & input = cell.get_managed_field(in_name); auto & output = cell.get_managed_field(out_name); input.eigen() = v; cell.directional_stiffness(K, input, output); return output.eigen(); }, "δF"_a) .def("project", [](sys_t& cell, py::EigenDRef& v) { if ((size_t(v.cols()) != cell.size() || size_t(v.rows()) != dim*dim)) { std::stringstream err{}; err << "need array of shape (" << dim*dim << ", " << cell.size() << ") but got (" << v.rows() << ", " << v.cols() << ")."; throw std::runtime_error(err.str()); } if (!cell.is_initialised()) { cell.initialise(); } const std::string in_name{"temp input for projection"}; auto & input = cell.get_managed_field(in_name); input.eigen() = v; cell.project(input); return input.eigen(); }, "field"_a) .def("get_strain",[](sys_t & s) { return Eigen::ArrayXXd(s.get_strain().eigen()); }) .def("get_stress",[](sys_t & s) { return Eigen::ArrayXXd(s.get_stress().eigen()); }) .def("size", &sys_t::size) .def("evaluate_stress_tangent", [](sys_t& cell, py::EigenDRef& v ) { if ((size_t(v.cols()) != cell.size() || size_t(v.rows()) != dim*dim)) { std::stringstream err{}; err << "need array of shape (" << dim*dim << ", " << cell.size() << ") but got (" << v.rows() << ", " << v.cols() << ")."; throw std::runtime_error(err.str()); } auto & strain{cell.get_strain()}; strain.eigen() = v; cell.evaluate_stress_tangent(strain); }, "strain"_a) - .def("get_G", - &sys_t::get_projection); + .def("get_projection", + &sys_t::get_projection) + .def("get_subdomain_resolutions", &sys_t::get_subdomain_resolutions) + .def("get_subdomain_locations", &sys_t::get_subdomain_locations) + .def("get_domain_resolutions", &sys_t::get_domain_resolutions) + .def("get_domain_lengths", &sys_t::get_domain_resolutions); } void add_cell_base(py::module & mod) { add_cell_base_helper (mod); add_cell_base_helper(mod); } void add_cell(py::module & mod) { add_cell_factory(mod); auto cell{mod.def_submodule("cell")}; cell.doc() = "bindings for cells and cell factories"; cell.def("scale_by_2", [](py::EigenDRef& v) { v *= 2; }); add_cell_base(cell); } diff --git a/language_bindings/python/bind_py_fftengine.cc b/language_bindings/python/bind_py_fftengine.cc index fa29e07..ec76db3 100644 --- a/language_bindings/python/bind_py_fftengine.cc +++ b/language_bindings/python/bind_py_fftengine.cc @@ -1,105 +1,110 @@ /** * @file bind_py_fftengine.cc * * @author Till Junge * * @date 17 Jan 2018 * * @brief Python bindings for the FFT engines * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Emacs; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "fft/fftw_engine.hh" #ifdef WITH_FFTWMPI #include "fft/fftwmpi_engine.hh" #endif #ifdef WITH_PFFT #include "fft/pfft_engine.hh" #endif #include "bind_py_declarations.hh" #include #include #include using namespace muSpectre; namespace py=pybind11; using namespace pybind11::literals; -template +template void add_engine_helper(py::module & mod, std::string name) { using Ccoord = Ccoord_t; using ArrayXXc = Eigen::Array; py::class_(mod, name.c_str()) #ifdef WITH_MPI .def(py::init([](Ccoord res, size_t comm) { return new Engine(res, std::move(Communicator(MPI_Comm(comm)))); }), "resolutions"_a, "communicator"_a=size_t(MPI_COMM_SELF)) #else .def(py::init()) #endif .def("fft", [](Engine & eng, py::EigenDRef v) { using Coll_t = typename Engine::GFieldCollection_t; using Field_t = typename Engine::Field_t; Coll_t coll{}; coll.initialise(eng.get_subdomain_resolutions(), eng.get_subdomain_locations()); Field_t & temp{make_field("temp_field", coll)}; temp.eigen() = v; return ArrayXXc{eng.fft(temp).eigen()}; }, "array"_a) .def("ifft", [](Engine & eng, py::EigenDRef v) { using Coll_t = typename Engine::GFieldCollection_t; using Field_t = typename Engine::Field_t; Coll_t coll{}; coll.initialise(eng.get_subdomain_resolutions(), eng.get_subdomain_locations()); Field_t & temp{make_field("temp_field", coll)}; eng.get_work_space().eigen() = v; eng.ifft(temp); return Eigen::ArrayXXd{temp.eigen()}; }, "array"_a) .def("initialise", &Engine::initialise, "flags"_a=FFT_PlanFlags::estimate) - .def("normalisation", &Engine::normalisation); + .def("normalisation", &Engine::normalisation) + .def("get_subdomain_resolutions", &Engine::get_subdomain_resolutions) + .def("get_subdomain_locations", &Engine::get_subdomain_locations) + .def("get_fourier_resolutions", &Engine::get_fourier_resolutions) + .def("get_fourier_locations", &Engine::get_fourier_locations) + .def("get_domain_resolutions", &Engine::get_domain_resolutions); } void add_fft_engines(py::module & mod) { auto fft{mod.def_submodule("fft")}; fft.doc() = "bindings for µSpectre's fft engines"; - add_engine_helper< twoD, FFTWEngine< twoD, twoD>>(fft, "FFTW_2d"); - add_engine_helper>(fft, "FFTW_3d"); + add_engine_helper, twoD>(fft, "FFTW_2d"); + add_engine_helper, threeD>(fft, "FFTW_3d"); #ifdef WITH_FFTWMPI - add_engine_helper< twoD, FFTWMPIEngine< twoD, twoD>>(fft, "FFTWMPI_2d"); - add_engine_helper>(fft, "FFTWMPI_3d"); + add_engine_helper, twoD>(fft, "FFTWMPI_2d"); + add_engine_helper, threeD>(fft, "FFTWMPI_3d"); #endif #ifdef WITH_PFFT - add_engine_helper< twoD, PFFTEngine< twoD, twoD>>(fft, "PFFT_2d"); - add_engine_helper>(fft, "PFFT_3d"); + add_engine_helper, twoD>(fft, "PFFT_2d"); + add_engine_helper, threeD>(fft, "PFFT_3d"); #endif add_projections(fft); } diff --git a/language_bindings/python/bind_py_projections.cc b/language_bindings/python/bind_py_projections.cc index 1dc0ab8..5f02dc4 100644 --- a/language_bindings/python/bind_py_projections.cc +++ b/language_bindings/python/bind_py_projections.cc @@ -1,140 +1,194 @@ /** * @file bind_py_projections.cc * * @author Till Junge * * @date 18 Jan 2018 * * @brief Python bindings for the Projection operators * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Emacs; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "fft/projection_small_strain.hh" #include "fft/projection_finite_strain.hh" #include "fft/projection_finite_strain_fast.hh" #include "fft/fftw_engine.hh" +#ifdef WITH_FFTWMPI +#include "fft/fftwmpi_engine.hh" +#endif +#ifdef WITH_PFFT +#include "fft/pfft_engine.hh" +#endif #include #include #include #include #include using namespace muSpectre; namespace py=pybind11; using namespace pybind11::literals; /** * "Trampoline" class for handling the pure virtual methods, see * [http://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python] * for details */ template class PyProjectionBase: public ProjectionBase { public: //! base class using Parent = ProjectionBase; //! field type on which projection is applied using Field_t = typename Parent::Field_t; void apply_projection(Field_t & field) override { PYBIND11_OVERLOAD_PURE (void, Parent, apply_projection, field ); } Eigen::Map get_operator() override { PYBIND11_OVERLOAD_PURE (Eigen::Map, Parent, get_operator ); } }; template void add_proj_helper(py::module & mod, std::string name_start) { using Ccoord = Ccoord_t; using Rcoord = Rcoord_t; - using Engine = FFTWEngine; using Field_t = typename Proj::Field_t; static_assert(DimS == DimM, "currently only for DimS==DimM"); std::stringstream name{}; name << name_start << '_' << DimS << 'd'; py::class_(mod, name.str().c_str()) - .def(py::init([](Ccoord res, Rcoord lengths) { - auto engine = std::make_unique(res); - return Proj(std::move(engine), lengths); - })) +#ifdef WITH_MPI + .def(py::init([](Ccoord res, Rcoord lengths, const std::string & fft, + size_t comm) { + if (fft == "fftw") { + auto engine = std::make_unique> + (res, std::move(Communicator(MPI_Comm(comm)))); + return Proj(std::move(engine), lengths); + } +#else + .def(py::init([](Ccoord res, Rcoord lengths, const std::string & fft) { + if (fft == "fftw") { + auto engine = std::make_unique>(res); + return Proj(std::move(engine), lengths); + } +#endif +#ifdef WITH_FFTWMPI + else if (fft == "fftwmpi") { + auto engine = std::make_unique> + (res, std::move(Communicator(MPI_Comm(comm)))); + return Proj(std::move(engine), lengths); + } +#endif +#ifdef WITH_PFFT + else if (fft == "pfft") { + auto engine = std::make_unique> + (res, std::move(Communicator(MPI_Comm(comm)))); + return Proj(std::move(engine), lengths); + } +#endif + else { + throw std::runtime_error("Unknown FFT engine '"+fft+"' specified."); + } + }), + "resolutions"_a, + "lengths"_a, +#ifdef WITH_MPI + "fft"_a="fftw", + "communicator"_a=size_t(MPI_COMM_SELF)) +#else + "fft"_a="fftw") +#endif .def("initialise", &Proj::initialise, "flags"_a=FFT_PlanFlags::estimate, "initialises the fft engine (plan the transform)") .def("apply_projection", [](Proj & proj, py::EigenDRef v){ - typename Engine::GFieldCollection_t coll{}; + typename FFTEngineBase::GFieldCollection_t coll{}; + Eigen::Index subdomain_size = + CcoordOps::get_size(proj.get_subdomain_resolutions()); + if (v.rows() != DimS*DimM || v.cols() != subdomain_size) { + throw std::runtime_error("Expected input array of shape ("+ + std::to_string(DimS*DimM)+", "+ + std::to_string(subdomain_size)+ + "), but input array has shape ("+ + std::to_string(v.rows())+", "+ + std::to_string(v.cols())+")."); + } coll.initialise(proj.get_subdomain_resolutions(), proj.get_subdomain_locations()); Field_t & temp{make_field("temp_field", coll)}; temp.eigen() = v; proj.apply_projection(temp); return Eigen::ArrayXXd{temp.eigen()}; }) .def("get_operator", &Proj::get_operator) .def("get_formulation", &Proj::get_formulation, "return a Formulation enum indicating whether the projection is small" - " or finite strain"); + " or finite strain") + .def("get_subdomain_resolutions", &Proj::get_subdomain_resolutions) + .def("get_subdomain_locations", &Proj::get_subdomain_locations) + .def("get_domain_resolutions", &Proj::get_domain_resolutions) + .def("get_domain_lengths", &Proj::get_domain_resolutions); } void add_proj_dispatcher(py::module & mod) { add_proj_helper< ProjectionSmallStrain< twoD, twoD>, twoD>(mod, "ProjectionSmallStrain"); add_proj_helper< ProjectionSmallStrain, threeD>(mod, "ProjectionSmallStrain"); add_proj_helper< ProjectionFiniteStrain< twoD, twoD>, twoD>(mod, "ProjectionFiniteStrain"); add_proj_helper< ProjectionFiniteStrain, threeD>(mod, "ProjectionFiniteStrain"); add_proj_helper< ProjectionFiniteStrainFast< twoD, twoD>, twoD>(mod, "ProjectionFiniteStrainFast"); add_proj_helper< ProjectionFiniteStrainFast, threeD>(mod, "ProjectionFiniteStrainFast"); - } void add_projections(py::module & mod) { add_proj_dispatcher(mod); - } diff --git a/language_bindings/python/muSpectre/fft.py b/language_bindings/python/muSpectre/fft.py index 2b11eca..a191b50 100644 --- a/language_bindings/python/muSpectre/fft.py +++ b/language_bindings/python/muSpectre/fft.py @@ -1,89 +1,140 @@ # # @file fft.py # # @author Lars Pastewka # # @date 27 Mar 2018 # # @brief Wrapper for muSpectre's FFT engines # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # try: from mpi4py import MPI except ImportError: MPI = None import _muSpectre +# This is a list of FFT engines that are potentially available. _factories = {'fftw': ('FFTW_2d', 'FFTW_3d', False), 'fftwmpi': ('FFTWMPI_2d', 'FFTWMPI_3d', True), 'pfft': ('PFFT_2d', 'PFFT_3d', True), 'p3dfft': ('P3DFFT_2d', 'P3DFFT_3d', True)} +_projections = {_muSpectre.Formulation.finite_strain: 'FiniteStrainFast', + _muSpectre.Formulation.small_strain: 'SmallStrain'} + + +# Detect FFT engines. This is a convenience dictionary that allows enumeration +# of all engines that have been compiled into the library. +fft_engines = [] +for fft, (factory_name_2d, factory_name_3d, is_parallel) in _factories.items(): + if factory_name_2d in _muSpectre.fft.__dict__ and \ + factory_name_3d in _muSpectre.fft.__dict__: + fft_engines += [(fft, is_parallel)] + def FFT(resolutions, fft='fftw', communicator=None): """ Instantiate a muSpectre FFT class. Parameters ---------- resolutions: list Grid resolutions in the Cartesian directions. fft: string FFT engine to use. Options are 'fftw', 'fftwmpi', 'pfft' and 'p3dfft'. Default is 'fftw'. communicator: mpi4py communicator mpi4py communicator object passed to parallel FFT engines. Note that the default 'fftw' engine does not support parallel execution. Returns ------- cell: object Return a muSpectre Cell object. """ try: factory_name_2d, factory_name_3d, is_parallel = _factories[fft] except KeyError: raise KeyError("Unknown FFT engine '{}'.".format(fft)) if len(resolutions) == 2: factory_name = factory_name_2d elif len(resolutions) == 3: factory_name = factory_name_3d else: raise ValueError('{}-d transforms are not supported' .format(len(resolutions))) try: factory = _muSpectre.fft.__dict__[factory_name] except KeyError: raise KeyError("FFT engine '{}' has not been compiled into the " - "muSpectre library.".format(fft)) + "muSpectre library.".format(factory_name)) if is_parallel: if MPI is None: raise RuntimeError('Parallel solver requested but mpi4py could' ' not be imported.') if communicator is None: communicator = MPI.COMM_SELF return factory(resolutions, MPI._handleof(communicator)) else: if communicator is not None: raise ValueError("FFT engine '{}' does not support parallel " "execution.".format(fft)) return factory(resolutions) + + +def Projection(resolutions, lengths, + formulation=_muSpectre.Formulation.finite_strain, + fft='fftw', communicator=None): + """ + Instantiate a muSpectre Projection class. + + Parameters + ---------- + resolutions: list + Grid resolutions in the Cartesian directions. + formulation: muSpectre.Formulation + Determines whether to use finite or small strain formulation. + fft: string + FFT engine to use. Options are 'fftw', 'fftwmpi', 'pfft' and 'p3dfft'. + Default is 'fftw'. + communicator: mpi4py communicator + mpi4py communicator object passed to parallel FFT engines. Note that + the default 'fftw' engine does not support parallel execution. + + + Returns + ------- + cell: object + Return a muSpectre Cell object. + """ + factory_name = 'Projection{}_{}d'.format(_projections[formulation], + len(resolutions)) + try: + factory = _muSpectre.fft.__dict__[factory_name] + except KeyError: + raise KeyError("Projection engine '{}' has not been compiled into the " + "muSpectre library.".format(factory_name)) + if communicator is None: + communicator = MPI.COMM_SELF + return factory(resolutions, lengths, fft, + MPI._handleof(communicator)) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2953630..41967f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,90 +1,81 @@ # ============================================================================= # file CMakeLists.txt # # @author Till Junge # # @date 08 Jan 2018 # # @brief Configuration for libmuSpectre # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # ============================================================================= add_library(muSpectre "") set("PRIVATE_MUSPECTRE_LIBS" "") add_subdirectory(common) add_subdirectory(materials) add_subdirectory(fft) add_subdirectory(cell) add_subdirectory(solver) if (${MPI_PARALLEL}) target_link_libraries(muSpectre PUBLIC ${MPI_LIBRARIES}) target_include_directories(muSpectre SYSTEM PUBLIC ${MPI_C_INCLUDE_PATH}) endif(${MPI_PARALLEL}) find_package(FFTW REQUIRED) # The following checks whether std::optional exists and replaces it by # boost::optional if necessary include(CheckCXXSourceCompiles) check_cxx_source_compiles( "#include int main() { std::experimental::optional A{}; }" HAS_STD_OPTIONAL) add_definitions(-DBAR) if( NOT HAS_STD_OPTIONAL) add_definitions(-DNO_EXPERIMENTAL) endif() file(GLOB_RECURSE _headers RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.hh") list(APPEND muSpectre_SRC ${_headers}) -if(PFFT_FOUND) - set(muSpectre_INCLUDES - ${muSpectre_INCLUDES} - ${PFFT_INCLUDES}) - set(muSpectre_LIBRARIES - ${muSpectre_LIBRARIES} - ${PFFT_LIBRARIES}) -endif() - set(muSpectre_INCLUDES ${FFTW_INCLUDES}) target_include_directories(muSpectre INTERFACE ${muSpectre_INCLUDES}) target_link_libraries(muSpectre PRIVATE ${FFTW_LIBRARIES} ${PRIVATE_MUSPECTRE_LIBS}) target_link_libraries(muSpectre PUBLIC Eigen3::Eigen) target_compile_definitions(muSpectre PRIVATE ${-DNO_EXPERIMENTAL}) set_property(TARGET muSpectre PROPERTY PUBLIC_HEADER ${_headers}) install(TARGETS muSpectre LIBRARY DESTINATION lib PUBLIC_HEADER DESTINATION include) diff --git a/src/fft/CMakeLists.txt b/src/fft/CMakeLists.txt index 62633bb..cae556c 100644 --- a/src/fft/CMakeLists.txt +++ b/src/fft/CMakeLists.txt @@ -1,108 +1,106 @@ # ============================================================================= # file CMakeLists.txt # # @author Till Junge # # @date 08 Jan 2018 # # @brief configuration for fft-related files # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # ============================================================================= ################################################################################ if (${MPI_PARALLEL}) set(USE_FFTWMPI "ON" CACHE BOOL "If on, the mpi-parallel FFTW engine is built") set(USE_PFFT "OFF" CACHE BOOL "If on, the mpi-parallel PFFT engine is built") ############################################################################## if (${USE_FFTWMPI}) find_package(FFTWMPI) if (NOT ${FFTWMPI_FOUND}) message (SEND_ERROR "You chose FFTWMPI but CMake cannot find it") endif (NOT ${FFTWMPI_FOUND}) - set (PRIVATE_MUSPECTRE_LIBS - ${PRIVATE_MUSPECTRE_LIBS} - ${FFTWMPI_LIBRARIES} - PARENT_SCOPE) + list(APPEND PRIVATE_MUSPECTRE_LIBS + ${FFTWMPI_LIBRARIES}) target_include_directories(muSpectre PRIVATE ${FFTWMPI_INCLUDES}) endif (${USE_FFTWMPI}) ############################################################################## if (${USE_PFFT}) find_package(PFFT) if (NOT ${PFFT_FOUND}) message (SEND_ERROR "You chose PFFT but CMake cannot find it") endif (NOT ${PFFT_FOUND}) - set (PRIVATE_MUSPECTRE_LIBS - ${PRIVATE_MUSPECTRE_LIBS} - ${PFFT_LIBRARIES} - PARENT_SCOPE) + list(APPEND PRIVATE_MUSPECTRE_LIBS + ${PFFT_LIBRARIES}) target_include_directories(muSpectre PRIVATE ${PFFT_INCLUDES}) endif (${USE_PFFT}) ############################################################################## if (NOT ${USE_FFTWMPI} AND NOT ${USE_PFFT}) message (SEND_ERROR "You activated MPI but turned on none of the MPI-parallel FFT engines") endif (NOT ${USE_FFTWMPI} AND NOT ${USE_PFFT}) endif(${MPI_PARALLEL}) +set(PRIVATE_MUSPECTRE_LIBS ${PRIVATE_MUSPECTRE_LIBS} PARENT_SCOPE) + if (${USE_FFTWMPI}) add_definitions(-DWITH_FFTWMPI) endif(${USE_FFTWMPI}) if (${USE_PFFT}) add_definitions(-DWITH_PFFT) endif(${USE_PFFT}) set (fft_engine_SRC ${CMAKE_CURRENT_SOURCE_DIR}/fft_utils.cc ${CMAKE_CURRENT_SOURCE_DIR}/fft_engine_base.cc ${CMAKE_CURRENT_SOURCE_DIR}/fftw_engine.cc ${CMAKE_CURRENT_SOURCE_DIR}/projection_base.cc ${CMAKE_CURRENT_SOURCE_DIR}/projection_default.cc ${CMAKE_CURRENT_SOURCE_DIR}/projection_finite_strain.cc ${CMAKE_CURRENT_SOURCE_DIR}/projection_small_strain.cc ${CMAKE_CURRENT_SOURCE_DIR}/projection_finite_strain_fast.cc ) if(${USE_FFTWMPI}) set(fft_engine_SRC ${fft_engine_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/fftwmpi_engine.cc ) endif(${USE_FFTWMPI}) if (${USE_PFFT}) set(fft_engine_SRC ${fft_engine_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/pfft_engine.cc ) endif(${USE_PFFT}) target_sources(muSpectre PRIVATE ${fft_engine_SRC}) diff --git a/tests/python_mpi_binding_tests.py b/tests/python_mpi_binding_tests.py new file mode 100755 index 0000000..fe11bd9 --- /dev/null +++ b/tests/python_mpi_binding_tests.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +""" +file python_mpi_binding_tests.py + +@author Till Junge + +@date 09 Jan 2018 + +@brief Unit tests for python bindings with MPI support + +@section LICENCE + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +""" + +import unittest +import numpy as np + +from python_test_imports import µ + +from python_mpi_projection_tests import * +from python_mpi_material_linear_elastic4_test import * + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python_mpi_material_linear_elastic4_test.py b/tests/python_mpi_material_linear_elastic4_test.py new file mode 100644 index 0000000..9b0d6c0 --- /dev/null +++ b/tests/python_mpi_material_linear_elastic4_test.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +""" +@file python_mpi_material_linear_elastic4_test.py + +@author Richard Leute + +@date 27 Mar 2018 + +@brief test MPI-parallel linear elastic material + +@section LICENSE + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +""" + +try: + from mpi4py import MPI +except ImportError: + MPI = None + +import unittest +import numpy as np + +from python_test_imports import µ + + +def build_test_classes(fft): + class MaterialLinearElastic4_Check(unittest.TestCase): + """ + Check the implementation of storing the first and second Lame constant in + each cell. Assign the same Youngs modulus and Poisson ratio to each cell, + from which the two Lame constants are internally computed. Then calculate + the stress and compare the result with stress=2*mu*Del0 (Hooke law for small + symmetric strains). + """ + def setUp(self): + self.resolution = [7,7] + self.lengths = [2.3, 3.9] + self.formulation = µ.Formulation.small_strain + self.sys = µ.Cell(self.resolution, + self.lengths, + self.formulation, + fft=fft, + communicator=MPI.COMM_WORLD) + self.mat = µ.material.MaterialLinearElastic4_2d.make( + self.sys, "material") + + def test_solver(self): + Youngs_modulus = 10. + Poisson_ratio = 0.3 + + for i, pixel in enumerate(self.sys): + self.mat.add_pixel(pixel, Youngs_modulus, Poisson_ratio) + + self.sys.initialise() + tol = 1e-6 + Del0 = np.array([[0, 0.025], + [0.025, 0]]) + maxiter = 100 + verbose = 1 + + solver=µ.solvers.SolverCG(self.sys, tol, maxiter, verbose) + r = µ.solvers.newton_cg(self.sys, Del0, + solver, tol, tol, verbose) + + #compare the computed stress with the trivial by hand computed stress + mu = (Youngs_modulus/(2*(1+Poisson_ratio))) + stress = 2*mu*Del0 + + self.assertLess(np.linalg.norm(r.stress-stress.reshape(-1,1)), 1e-8) + + return MaterialLinearElastic4_Check + +linear_elastic4 = {} +for fft, is_parallel in µ.fft.fft_engines: + if is_parallel: + linear_elastic4[fft] = build_test_classes(fft) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/python_mpi_projection_tests.py b/tests/python_mpi_projection_tests.py new file mode 100644 index 0000000..7e32223 --- /dev/null +++ b/tests/python_mpi_projection_tests.py @@ -0,0 +1,143 @@ +#! /usr/bin/env python3 +# -*- coding:utf-8 -*- +""" +file python_mpi_projection_tests.py + +@author Till Junge + +@date 18 Jan 2018 + +@brief compare µSpectre's MPI-parallel projection operators to GooseFFT + +@section LICENSE + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +""" + +import unittest +import numpy as np +import itertools + +from mpi4py import MPI + +from python_test_imports import µ +from python_goose_ref import (SmallStrainProjectionGooseFFT, + FiniteStrainProjectionGooseFFT) +from muSpectre import Formulation +from muSpectre.fft import Projection, fft_engines + + +def build_test_classes(formulation, RefProjection, fft): + class ProjectionCheck(unittest.TestCase): + def __init__(self, methodName='runTest'): + super().__init__(methodName) + + def setUp(self): + self.ref = RefProjection + self.resolution = self.ref.resolution + self.ndim = self.ref.ndim + self.shape = list((self.resolution for _ in range(self.ndim))) + self.projection = Projection(self.shape, self.shape, formulation, + fft, MPI.COMM_WORLD) + self.projection.initialise() + self.tol = 1e-12 * np.prod(self.shape) + + def test_projection_result(self): + # create a bogus strain field in GooseFFT format + # dim × dim × N × N (× N) + strain_shape = (self.ndim, self.ndim, *self.shape) + strain = np.arange(np.prod(strain_shape)).reshape(strain_shape) + # if we're testing small strain projections, it needs to be symmetric + if self.projection.get_formulation() == µ.Formulation.small_strain: + strain += strain.transpose(1, 0, *range(2, len(strain.shape))) + strain_g = strain.copy() + b_g = self.ref.G(strain_g).reshape(strain_g.shape) + strain_µ = np.zeros((*self.shape, self.ndim, self.ndim)) + for ijk in itertools.product(range(self.resolution), repeat=self.ndim): + index_µ = tuple((*ijk, slice(None), slice(None))) + index_g = tuple((slice(None), slice(None), *ijk)) + strain_µ[index_µ] = strain_g[index_g].T + res = self.projection.get_subdomain_resolutions() + loc = self.projection.get_subdomain_locations() + if self.ref.ndim == 2: + resx, resy = res + locx, locy = loc + subdomain_strain_µ = strain_µ[locx:locx+resx, locy:locy+resy] + else: + resx, resy, resz = res + locx, locy, locz = loc + subdomain_strain_µ = strain_µ[locx:locx+resx, locy:locy+resy, + locz:locz+resz] + b_µ = self.projection.apply_projection(subdomain_strain_µ.reshape( + np.prod(res), self.ndim**2).T).T.reshape(subdomain_strain_µ.shape) + for l in range(np.prod(res)): + ijk = µ.get_domain_ccoord(res, l) + index_µ = tuple((*ijk, slice(None), slice(None))) + ijk = loc + np.array(ijk) + index_g = tuple((slice(None), slice(None), *ijk)) + b_µ_sl = b_µ[index_µ].T + b_g_sl = b_g[index_g] + error = np.linalg.norm(b_µ_sl - b_g_sl) + condition = error < self.tol + slice_printer = lambda tup: "({})".format( + ", ".join("{}".format(":" if val == slice(None) else val) for val in tup)) + if not condition: + print("error = {}, tol = {}".format(error, self.tol)) + print("b_µ{} =\n{}".format(slice_printer(index_µ), b_µ_sl)) + print("b_g{} =\n{}".format(slice_printer(index_g), b_g_sl)) + self.assertTrue(condition) + + return ProjectionCheck + +get_goose = lambda ndim, proj_type: proj_type( + ndim, 5, 2, 70e9, .33, 3.) +get_finite_goose = lambda ndim: get_goose(ndim, FiniteStrainProjectionGooseFFT) +get_small_goose = lambda ndim: get_goose(ndim, SmallStrainProjectionGooseFFT) + +if ("fftwmpi", True) in fft_engines: + small_default_fftwmpi_3 = build_test_classes(Formulation.small_strain, + get_small_goose(3), + "fftwmpi") + small_default_fftwmpi_2 = build_test_classes(Formulation.small_strain, + get_small_goose(2), + "fftwmpi") + + finite_fast_fftwmpi_3 = build_test_classes(Formulation.finite_strain, + get_finite_goose(3), + "fftwmpi") + finite_fast_fftwmpi_2 = build_test_classes(Formulation.finite_strain, + get_finite_goose(2), + "fftwmpi") + +if ("pfft", True) in fft_engines: + small_default_pfft_3 = build_test_classes(Formulation.small_strain, + get_small_goose(3), + "pfft") + small_default_pfft_2 = build_test_classes(Formulation.small_strain, + get_small_goose(2), + "pfft") + + finite_fast_pfft_3 = build_test_classes(Formulation.finite_strain, + get_finite_goose(3), + "pfft") + finite_fast_pfft_2 = build_test_classes(Formulation.finite_strain, + get_finite_goose(2), + "pfft") + +if __name__ == "__main__": + unittest.main()