Commit a5c4a110 authored by LEFEBVREJP email's avatar LEFEBVREJP email
Browse files

Updating radixmath python bindings to us SWIG.

parent 1fb12c9f
Pipeline #34938 failed with stages
in 16 minutes and 3 seconds
##---------------------------------------------------------------------------##
## FindPythonLibsFromExe.cmake
## --------------------------------------------------------------------------##
# FindPythonLibsFromExe is loosely adapted from Trilinos' FindPythonInclude
#
# This module uses the Python executable (if already loaded with
# FindPythonInterp) to determine the default library and include paths if not
# already set
#
# - PYTHON_LIBRARY
# - PYTHON_INCLUDE_DIR
#
# It then calls FindPythonLibs, which will set all the other required variables,
# and set the PYTHON_XXX variables if we had trouble finding them in this script
IF(PYTHON_EXECUTABLE AND (NOT PYTHON_LIBRARY OR NOT PYTHON_INCLUDE_DIR))
# Get the Python installation prefix and version
IF(NOT EXISTS "${PYTHON_EXECUTABLE}")
MESSAGE(FATAL_ERROR "Python executable specified "
"at '${PYTHON_EXECUTABLE}' does not exist")
ENDIF()
EXECUTE_PROCESS(COMMAND
${PYTHON_EXECUTABLE} -c
"import sys; sys.stdout.write(sys.prefix + ';' + sys.version[:3])"
OUTPUT_VARIABLE PREFIX_AND_VERSION
)
LIST(GET "${PREFIX_AND_VERSION}" 0 _PY_PREFIX)
LIST(GET "${PREFIX_AND_VERSION}" 1 _PY_VERSION)
# Attempt to find the include directories
SET(_PYINC "${_PY_PREFIX}/include/python${_PY_VERSION}" )
IF(EXISTS "${_PYINC}" AND EXISTS "${_PYINC}/Python.h")
SET(PYTHON_INCLUDE_DIR ${_PYINC} CACHE FILEPATH
"Path to the Python include directory" FORCE)
MESSAGE(STATUS "Found Python include directory: ${PYTHON_INCLUDE_DIR}")
ENDIF()
# Attempt to find the library
FOREACH(_PYLIB_BASE
"${_PY_PREFIX}/lib"
"${_PY_PREFIX}/lib64"
"${_PY_PREFIX}/lib/python${_PY_VERSION}/config" )
FOREACH(SUFFIX dll dylib so a)
SET(_PYLIB "${_PYLIB_BASE}/libpython${_PY_VERSION}.${SUFFIX}")
IF(EXISTS "${_PYLIB}")
SET(PYTHON_LIBRARY ${_PYLIB} CACHE FILEPATH
"Path to the Python Library" FORCE)
MESSAGE(STATUS "Found Python library: ${PYTHON_LIBRARY}")
BREAK()
ENDIF()
ENDFOREACH()
IF(PYTHON_LIBRARY)
BREAK()
ENDIF()
ENDFOREACH()
ENDIF()
# Set the version for FindPythonLibs to match the one we found
# The PYTHON_LIBRARY and PYTHON_INCLUDE_DIR values, if set, will be used
# Otherwise, standard CMake logic will work
SET(PythonLibs_FIND_VERSION ${_PY_VERSION})
FIND_PACKAGE(PythonLibs ${ARGN})
INCLUDE(FindPackageHandleStandardArgs)
# Process REQUIRED etc.
FIND_PACKAGE_HANDLE_STANDARD_ARGS(PythonLibs
REQUIRED_VARS PYTHON_LIBRARIES PYTHON_INCLUDE_DIRS
VERSION_VAR PYTHONLIBS_VERSION_STRING)
##---------------------------------------------------------------------------##
## end of FindPythonLibsFromExe.cmake
##---------------------------------------------------------------------------##
##---------------------------------------------------------------------------##
## RadixSWIGPyModules.cmake
## --------------------------------------------------------------------------##
#
# Find required Python libraries
FIND_PACKAGE(PythonLibsFromExe REQUIRED)
#
# Find the SWIG package for language bindings
FIND_PACKAGE(SWIG REQUIRED)
#
# Ensure that PYTHON wrappers are enabled
IF (ENABLE_PYTHON_WRAPPERS)
IF (NOT DEFINED SWIG_DIR)
MESSAGE(FATAL_ERROR "SWIG not loaded.")
ENDIF()
# Load SWIG and other modules we need
INCLUDE(${SWIG_USE_FILE})
INCLUDE(CMakeParseArguments)
INCLUDE(CheckCXXCompilerFlag)
# Prepare to turn off some warnings that commonly show up in SWIG wrapper code
SET(SWIG_CXX_FLAGS)
IF(CMAKE_COMPILER_IS_GNUCXX)
SET(WARNING_FLAGS
-Wno-unused-label
-Wno-unused-parameter
-Wno-unused-but-set-variable
-Wno-maybe-uninitialized
-Wno-uninitialized
-Wno-unused-value
)
FOREACH(COMPILER_FLAG ${WARNING_FLAGS})
# The ${COMPILER_FLAG} variable is cached, so the variable must be
# unique between flags.
check_cxx_compiler_flag("${COMPILER_FLAG}" RADIXSWIG_USE_FLAG_${COMPILER_FLAG})
IF(RADIXSWIG_USE_FLAG_${COMPILER_FLAG})
SET(SWIG_CXX_FLAGS "${SWIG_CXX_FLAGS} ${COMPILER_FLAG}")
ENDIF()
ENDFOREACH()
ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "Intel" AND NOT WIN32)
SET(SWIG_CXX_FLAGS "${SWIG_CXX_FLAGS} -diag-disable 955")
ENDIF()
# Tell SWIG to use modern Python code
LIST(APPEND CMAKE_SWIG_FLAGS "-modern" "-noproxydel")
# If python version is high enough, add -py3 flag
IF(PYTHON_VERSION_STRING VERSION_GREATER 3.0)
LIST(APPEND CMAKE_SWIG_FLAGS "-py3")
ENDIF()
ENDIF()
##---------------------------------------------------------------------------##
# Look through a header/SWIG file and find dependencies
MACRO(get_swig_dependencies _RESULT_VAR _SOURCE)
# Search for dependencies in the SWIG file
FILE(STRINGS ${_SOURCE} HEADER_FILES
REGEX "^[ \t]*%include *\""
)
LIST(REMOVE_DUPLICATES HEADER_FILES)
# Set up test directories
SET(TEST_DIRS
${CMAKE_CURRENT_SOURCE_DIR}
${SCALE_SOURCE_DIR}/Exnihilo/packages
${SCALE_SOURCE_DIR}/packages
)
IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/python")
LIST(APPEND TEST_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/python")
ENDIF()
# Get just the file names inside each "include"
SET(${_RESULT_VAR})
FOREACH(THEFILE ${HEADER_FILES})
STRING(REGEX REPLACE "^.*\"([^\"]+)\".*$" "\\1" THEFILE ${THEFILE})
IF( THEFILE )
FOREACH(TESTDIR ${TEST_DIRS})
IF(EXISTS ${TESTDIR}/${THEFILE})
LIST(APPEND ${_RESULT_VAR} ${TESTDIR}/${THEFILE})
BREAK()
ENDIF()
ENDFOREACH()
ENDIF()
ENDFOREACH()
ENDMACRO()
##---------------------------------------------------------------------------##
## ADDING SWIG MODULES
##---------------------------------------------------------------------------##
# RADIX_ADD_SWIG(
# MODULE module
# [SOURCE src.i]
# [DEPLIBS lib1 [lib2 ...]]
# [DEPMODULES module1 [module2 ...]]
# [EXTRASRC file1 [file2 ...]]
# )
#
# Create a SWIG-generated python module and shared object.
#
# The MODULE argument is the name of the resulting module file. By default it
# assumes the name "module.i", but that can be overriden with the SOURCE
# argument.
#
# All libraries in DEPLIBS will be linked against each target.
#
# Because TriBITS doesn't handle intra-SWIG module dependencies, we add a
# provision for extra dependency generation using the DEPMODULES list. For
# example, kba_utils will have "DEPMODULES denovo_utils ".
#
# The EXTRASRC argument allows additional sources to be compiled into the SWIG
# module target.
IF(ENABLE_PYTHON_WRAPPERS)
MACRO(RADIX_ADD_SWIG)
_RADIX_ADD_SWIG(${ARGN})
ENDMACRO()
# Bug fix: version of CMake older than 2.8.8 don't support the
# INCLUDE_DIRECTORIES property
IF(CMAKE_MAJOR_VERSION LESS 3
AND CMAKE_MINOR_VERSION LESS 9
AND CMAKE_PATCH_VERSION LESS 9)
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR})
ENDIF()
ELSE()
MACRO(RADIX_ADD_SWIG)
# no-op
MESSAGE(STATUS "Skipping python module setup for ${SUBPACKAGE_FULLNAME}")
ENDMACRO()
ENDIF()
##---------------------------------------------------------------------------##
FUNCTION(_RADIX_ADD_SWIG)
cmake_parse_arguments(MKSWIG "NO_ADD_OMPI" "MODULE;SOURCE"
"DEPLIBS;DEPMODULES;EXTRASRC" ${ARGN})
IF(NOT MKSWIG_MODULE)
MESSAGE(SEND_ERROR "Cannot call RADIX_ADD_SWIG without TARGET")
ENDIF()
SET(MODULE_NAME ${MKSWIG_MODULE})
IF(MKSWIG_SOURCE)
SET(SRC_FILE "${MKSWIG_SOURCE}")
ELSE()
SET(SRC_FILE "${MODULE_NAME}.i")
ENDIF()
# Let SWIG know that we're compiling C++ files
SET_SOURCE_FILES_PROPERTIES(${SRC_FILE} PROPERTIES CPLUSPLUS TRUE)
# Get dependencies of main SWIG source file and the files it includes
# we can't do recursive
SET(SUBDEPS ${SRC_FILE})
SET(DEPENDENCIES)
FOREACH(RECURSION 0 1 2)
SET(OLD_SUBDEPS ${SUBDEPS})
SET(SUBDEPS)
FOREACH(DEPENDENCY ${OLD_SUBDEPS})
IF(DEPENDENCY MATCHES "\\.i$")
get_swig_dependencies(SUBSUBDEPS ${DEPENDENCY})
LIST(APPEND SUBDEPS ${SUBSUBDEPS})
ENDIF()
ENDFOREACH()
LIST(APPEND DEPENDENCIES ${SUBDEPS})
ENDFOREACH()
SET(SWIG_MODULE_${MODULE_NAME}_EXTRA_DEPS ${DEPENDENCIES} )
# Add the new python target
IF("${CMAKE_VERSION}" VERSION_LESS "3.8.0")
SWIG_ADD_MODULE(${MODULE_NAME} python
${SRC_FILE} ${MKSWIG_EXTRASRC})
ELSE()
SWIG_ADD_LIBRARY(${MODULE_NAME}
LANGUAGE python
TYPE MODULE
SOURCES ${SRC_FILE} ${MKSWIG_EXTRASRC})
ENDIF()
# Mangled name of the SWIG target
SET(MKSWIG_TARGET ${SWIG_MODULE_${MODULE_NAME}_REAL_NAME})
# It's not always necessary to link against SWIG libraries, but doing so
# can turn unfortunate run-time errors (dlopen) into link-time errors.
IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
IF(NOT ${PYTHON_LIBRARIES} MATCHES ".framework")
# Turn off undefined symbol errors on Mac systems where
# Python is not installed as a framework
# see http://bugs.python.org/issue1602133 about _initposix issues
SET_TARGET_PROPERTIES(${MKSWIG_TARGET}
PROPERTIES LINK_FLAGS "-undefined suppress -flat_namespace")
ELSE()
# Otherwise, link against Python libraries
SWIG_LINK_LIBRARIES(${MODULE_NAME} ${PYTHON_LIBRARIES})
ENDIF()
ENDIF()
# Link against other dependent libraries
SWIG_LINK_LIBRARIES(${MODULE_NAME} ${MKSWIG_DEPLIBS})
# Add intra-module dependencies
FOREACH(DEPMODULE ${MKSWIG_DEPMODULES})
ADD_DEPENDENCIES(${MKSWIG_TARGET} _${DEPMODULE})
ENDFOREACH()
# Include the Python directory and current source directory for the SWIG
# targets (SWIG processor and compilation of the resulting CXX file.) We don't
# use the INCLUDE_DIRECTORIES command because because TriBITS will
# propagate the path if we use INCLUDE_DIRECTORIES.
# Apply SWIG_CXX_FLAGS to hide warnings and such.
GET_TARGET_PROPERTY(INCL_DIR _${MODULE_NAME} INCLUDE_DIRECTORIES)
LIST(APPEND INCL_DIR ${PYTHON_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR})
LIST(REMOVE_DUPLICATES INCL_DIR)
SET_TARGET_PROPERTIES(_${MODULE_NAME} PROPERTIES
INCLUDE_DIRECTORIES "${INCL_DIR}"
COMPILE_FLAGS "${SWIG_CXX_FLAGS}")
# define the install targets
INSTALL(TARGETS ${MKSWIG_TARGET}
DESTINATION python)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}.py
DESTINATION python)
# clean up after SWIG (in cmake 2.6 - 2.8)
SET(swig_extra_generated_files)
ENDFUNCTION()
##---------------------------------------------------------------------------##
## end of RadixSWIGPyModules.cmake
##---------------------------------------------------------------------------##
......@@ -43,5 +43,9 @@ TRIBITS_ADD_LIBRARY(radixmath
# Add test directory
TRIBITS_ADD_TEST_DIRECTORIES(tests)
IF(ENABLE_PYTHON_WRAPPERS)
ADD_SUBDIRECTORY(python)
ENDIF()
INSTALL(FILES ${HEADERS} DESTINATION "include/radixmath/")
TRIBITS_SUBPACKAGE_POSTPROCESS()
......@@ -5,8 +5,8 @@
#ifndef RADIX_RADIXMATH_INTEPOLATE_HH_
#define RADIX_RADIXMATH_INTEPOLATE_HH_
#include "../../build/radix/radixcore/visibility.hh"
//#include "radixcore/visibility.hh"
//#include "../../build/radix/radixcore/visibility.hh"
#include "radixcore/visibility.hh"
#include <vector>
......
INCLUDE(RadixSWIGPyModules)
RADIX_ADD_SWIG(
MODULE radixmath
DEPLIBS radixmath
)
%module radixmath
%{
#include "../interpolate.hh"
%}
%include "std_vector.i"
namespace std
{
%template(vectorf) vector<float>;
%template(vectord) vector<double>;
}
namespace radix
{
template<typename T>
std::vector<T> interpolateValues(std::vector<T> const & baseValues, std::vector<T> const & valuesToInterpolate, const bool circular, const T missingValue);
template<typename T>
std::vector<T> interpolateToOtherBaseValues(std::vector<T> const & baseValues, std::vector<T> const & newBaseValues, std::vector<T> const & valuesToInterpolate, const bool circular, const T missingValue);
%template(interpolate_values) interpolateValues<double>;
%template(interpolate_to_other_base_values) interpolateToOtherBaseValues<double>;
}
import unittest
import interpolate
import radixmath
missing_value = -9999.0
tolerance = 0.0001
......@@ -11,12 +11,12 @@ class TestInterpolateBindings(unittest.TestCase):
def test_interpolate_basic(self):
# Basic test of interpolate_values
print("Testing interpolate_values...")
base_values = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0]
interp_values = [1.0, missing_value, 3.0, missing_value, 6.0,
missing_value, 1.0]
expect_values = [1.0, 2.0, 3.0, 4.5, 6.0, 3.5, 1.0]
base_values = (10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0)
interp_values = (1.0, missing_value, 3.0, missing_value, 6.0,
missing_value, 1.0)
expect_values = (1.0, 2.0, 3.0, 4.5, 6.0, 3.5, 1.0)
test_values = interpolate.interpolate_values(
test_values = radixmath.interpolate_values(
base_values, interp_values, circular, missing_value
)
......@@ -24,14 +24,13 @@ class TestInterpolateBindings(unittest.TestCase):
def test_interpolate_to_other_base_basic(self):
print("Testing interpolate_to_other_base_values")
base_values = [1000.0, 2000.0, 3000.0]
new_base_values = [500.0, 1500.0, 2500.0, 3500.0]
interp_values = [10.5, missing_value, 30.5]
expect_values = [10.5, 15.5, 25.5, 30.5]
test_values = interpolate.interpolate_to_other_base_values(
base_values, new_base_values, interp_values, circular,
missing_value
base_values = (1000.0, 2000.0, 3000.0)
new_base_values = (500.0, 1500.0, 2500.0, 3500.0)
interp_values = (10.5, missing_value, 30.5)
expect_values = (10.5, 15.5, 25.5, 30.5)
test_values = radixmath.interpolate_to_other_base_values(
base_values, new_base_values, interp_values, circular, missing_value
)
self.assertEqual(expect_values, test_values)
......
TRIBITS_SUBPACKAGE(py)
# we need Python 3 for this
FIND_PACKAGE(Python3 COMPONENTS Interpreter Development)
IF(Python3_FOUND)
MESSAGE(STATUS "Found Python 3 - version: ${Python3_VERSION}")
MESSAGE(STATUS "Executable: ${Python3_EXECUTABLE}")
MESSAGE(STATUS "Include dirs: ${Python3_INCLUDE_DIRS}")
MESSAGE(STATUS "Root dir: ${Python3_ROOT_DIR}")
MESSAGE(STATUS "${Python_VERSION}")
MESSAGE(STATUS "${Python_EXECUTABLE}")
MESSAGE(STATUS "${Python_INCLUDE_DIRS}")
INCLUDE_DIRECTORIES(${Python3_INCLUDE_DIRS})
# Set up the Python setup script
SET(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/radixmathpy/setup.py.in")
SET(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/radixmathpy/setup.py")
CONFIGURE_FILE(${SETUP_PY_IN} ${SETUP_PY})
# Set the commands to build and install the python package
SET(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/radixmathpy/build_output")
ADD_CUSTOM_COMMAND(
OUTPUT ${OUTPUT}
COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} build
COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT}
DEPENDS ""
)
ADD_CUSTOM_TARGET(target ALL DEPENDS ${OUTPUT})
INSTALL(CODE "execute_process(COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --prefix=${CMAKE_INSTALL_PREFIX})")
# Add the tests
TRIBITS_ADD_TEST_DIRECTORIES("radixmathpy/tests")
ELSE()
MESSAGE(WARNING "Not found Python 3: not including bindings")
ENDIF()
TRIBITS_SUBPACKAGE_POSTPROCESS()
TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
LIB_REQUIRED_PACKAGES radixmath
LIB_OPTIONAL_PACKAGES
TEST_REQUIRED_PACKAGES
TEST_OPTIONAL_PACKAGES
LIB_REQUIRED_TPLS
LIB_OPTIONAL_TPLS
TEST_REQUIRED_TPLS
TEST_OPTIONAL_TPLS
)
#include <Python.h>
#include <stdexcept>
#include <vector>
/**
* Conversion from PyObject to double vector.
* Adapted from https://gist.github.com/rjzak/5681680
*/
static inline std::vector<double> pyObjectToDoubleVector(PyObject* object)
{
std::cout << "object to vector called" << std::endl;
std::vector<double> vector;
// Different conversion methods for list and tuple objects
if (PyList_Check(object))
{
std::cout << "Object is list" << std::endl;
for (Py_ssize_t i = 0; i < PyList_Size(object); ++i)
{
std::cout << i << "; ";
PyObject* value = PyList_GetItem(object, i);
std::cout << PyFloat_AsDouble(value) << std::endl;
vector.push_back(PyFloat_AsDouble(value));
}
}
else if (PyTuple_Check(object))
{
std::cout << "Object is tuple" << std::endl;
for (Py_ssize_t i = 0; i < PyTuple_Size(object); ++i)
{
PyObject* value = PyTuple_GetItem(object, i);
vector.push_back(PyFloat_AsDouble(value));
}
}
else
{
std::cout << "exception" << std::endl;
throw std::logic_error(
"Input PyObject was not a list or tuple - cannot convert");
}
return vector;
}
/**
* Conversion from double vector to PyObject list.
* Adapted from https://gist.github.com/rjzak/5681680
*/
static inline PyObject* doubleVectorToListPyObject(
const std::vector<double>& vector)
{
PyObject* list = PyList_New(vector.size());
if (!list)
{
throw std::logic_error("Unable to initialise Python list - cannot convert");
}
for (size_t i = 0; i < vector.size(); ++i)
{
PyObject* value = PyFloat_FromDouble(vector[i]);
if (!value)
{
Py_DECREF(list);
throw std::logic_error("Unable to convert list member - cannot convert");
}
PyList_SetItem(list, i, value);
}
return list;
}
\ No newline at end of file
#include <Python.h>
#include <iostream>
#include <vector>
#include "../../radixmath/interpolate.hh"
#include "../python_utils.hh"
static PyObject* interpolate_values(PyObject* self, PyObject* args)
{
PyObject* baseValuesObj;
PyObject* valuesToInterpObj;
bool circular;
double missingValue;
std::cout << "Getting arguments from call" << std::endl;
// Get the arguments from the call
if (!PyArg_ParseTuple(args, "OOpd", &baseValuesObj, &valuesToInterpObj,
&circular, &missingValue))
{
return NULL;
}
std::cout << "circular: " << circular << std::endl;
std::cout << "missingValue: " << missingValue << std::endl;
// std::cout << "converting array" << std::endl;
// PyObject* objectsRepresentation = PyObject_Repr(baseValuesObj);
// std::cout << "converting array 2" << std::endl;
// PyObject* str = PyUnicode_AsEncodedString(objectsRepresentation, "utf-8",
// "~E~"); std::cout << "converting array 3" << std::endl; const char *bytes =
// PyBytes_AS_STRING(str); std::cout << bytes << std::endl;
std::cout << "Parsing arguments" << std::endl;
// Parse the vector argument s
std::vector<double> baseValuesVec = pyObjectToDoubleVector(baseValuesObj);
std::cout << "Parsing arguments 2" << std::endl;
std::vector<double> valuesToInterpVec =
pyObjectToDoubleVector(valuesToInterpObj);
std::cout << "Carrying out interpolation" << std::endl;
// Carry out the interpolation
std::vector<double> interpolatedValuesVec = radix::interpolateValues(
baseValuesVec, valuesToInterpVec, circular, missingValue);
std::cout << "Converting results" << std::endl;
// Convert the result back into a Python object
PyObject* interpolatedValuesObj =
doubleVectorToListPyObject(interpolatedValuesVec);
std::cout << "Returning" << std::endl;
return interpolatedValuesObj;
}
static PyObject* interpolate_to_other_base_values(PyObject* self,
PyObject* args)
{
PyObject* baseValuesObj;
PyObject* newBaseValuesObj;
PyObject* valuesToInterpObj;
bool circular;
double missingValue;
// Get the arguments from the call
if (!PyArg_ParseTuple(args, "OOOpd", &baseValuesObj, &newBaseValuesObj,
&valuesToInterpObj, &circular, &missingValue))
{
return NULL;
}
// Parse the vector arguments
std::vector<double> baseValuesVec = pyObjectToDoubleVector(baseValuesObj);