From cb0414db1587ef1d700f071d7a0ea7cdf8b82bf8 Mon Sep 17 00:00:00 2001 From: Marcus Bannerman Date: Fri, 7 May 2021 21:44:00 +0100 Subject: [PATCH] CMake builds seem properly integrated, sub and derivative are working, need to get wheels built --- CMakeLists.txt | 43 +++++- build-wheels.sh | 5 + dev-requirements.txt | 0 install-icc.sh | 234 -------------------------------- pyproject.toml | 1 + pysrc/stator/core.cpp | 2 +- scripts/manylinux_entrypoint.sh | 37 +++++ setup.py | 76 +++++++---- stator/symbolic/runtime.hpp | 123 ++++++++++++++++- tests/stator_test.py | 7 +- tests/symbolic_runtime_test.cpp | 17 +++ 11 files changed, 273 insertions(+), 272 deletions(-) create mode 100755 build-wheels.sh create mode 100644 dev-requirements.txt delete mode 100755 install-icc.sh create mode 100755 scripts/manylinux_entrypoint.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cfb107..d40bcab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ endif() ### First check if the compiler supports C++11 or C++0x and enable the build flag set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) ###################################################################### ########## HEADER DIRECTORIES @@ -125,6 +126,44 @@ stator_test(symbolic_ad_test) ###################################################################### ########## PYTHON INTERFACE ###################################################################### + +# Build a Python extension module using pybind11 +# pybindings_add_module() +# Here should be the fully qualified name for the module, +# e.g. pybindings_add_module(foo.bar._baz) +# becomes the target name in case you wish to do something to it later +# The source for the binding *must* be placed in src/pybindings/{relpath}/py{name}.cc +# E.g. for module=foo.bar._baz -> src/pybindings/bar/py_baz.cc +function(pybindings_add_module module) + set(target_name ${module}) + string(REPLACE "." "/" modpath ${module}) + string(REPLACE "." ";" modlist ${module}) + + # The module name is the last entry + list(GET modlist -1 modname) + + # Remove everything that is not the root or the module name + #list(REMOVE_AT modlist 0) + list(REMOVE_AT modlist -1) + + # Get the relative path + if(modlist) + string(REPLACE ";" "/" relpath "${modlist}") + else() + set(relpath "") + endif() + + # Define the binding source file + set(sources pysrc/${relpath}/${modname}.cpp) + + # Invoke pybind11 and set where the library should go, and what it is called + pybind11_add_module(${target_name} ${sources}) + set(outdir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${relpath}) + set_target_properties(${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${outdir}) + set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${modname}) +endfunction() + add_subdirectory(extern/pybind11) -pybind11_add_module(core pysrc/stator/core.cpp) -target_include_directories(core PRIVATE ${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}) +pybindings_add_module(stator.core) + diff --git a/build-wheels.sh b/build-wheels.sh new file mode 100755 index 0000000..60302ac --- /dev/null +++ b/build-wheels.sh @@ -0,0 +1,5 @@ +#!/bin/bash +DOCKER_IMAGE=quay.io/pypa/manylinux2014_x86_64 +PLAT=manylinux2014_x86_64 +docker run --rm -e PLAT=$PLAT -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD /io/scripts/manylinux_entrypoint.sh +ls wheelhouse/ diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/install-icc.sh b/install-icc.sh deleted file mode 100755 index 75d4e28..0000000 --- a/install-icc.sh +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/sh - -# Install Intel Parallel Studio on Travis CI -# https://github.com/nemequ/icc-travis -# -# Originally written for Squash by -# Evan Nemerson. For documentation, bug reports, support requests, -# etc. please use . -# -# To the extent possible under law, the author(s) of this script have -# waived all copyright and related or neighboring rights to this work. -# See for -# details. - -# Tools - Not very useful in non-interactive mode. -COMPONENTS_VTUNE="intel-vtune-amplifier-xe-2016-cli__x86_64;intel-vtune-amplifier-xe-2016-common__noarch;intel-vtune-amplifier-xe-2016-cli-common__noarch;intel-vtune-amplifier-xe-2016-collector-64linux__x86_64;intel-vtune-amplifier-xe-2016-sep__noarch;intel-vtune-amplifier-xe-2016-gui-common__noarch;intel-vtune-amplifier-xe-2016-gui__x86_64;intel-vtune-amplifier-xe-2016-common-pset__noarch" -COMPONENTS_INSPECTOR="intel-inspector-xe-2016-cli__x86_64;intel-inspector-xe-2016-cli-common__noarch;intel-inspector-xe-2016-gui-common__noarch;intel-inspector-xe-2016-gui__x86_64;intel-inspector-xe-2016-cli-pset__noarch" -COMPONENTS_ADVISOR="intel-advisor-xe-2016-cli__x86_64;intel-advisor-xe-2016-cli-common__noarch;intel-advisor-xe-2016-gui-common__noarch;intel-advisor-xe-2016-gui__x86_64;intel-advisor-xe-2016-cli-pset__noarch" -COMPONENTS_GDB="intel-gdb-gt__x86_64;intel-gdb-gt-src__noarch;intel-gdb-gt-libelfdwarf__x86_64;intel-gdb-gt-devel__x86_64;intel-gdb-gt-common__noarch;intel-gdb-ps-cdt__x86_64;intel-gdb-ps-cdt-source__x86_64;intel-gdb-ps-mic__x86_64;intel-gdb-ps-mpm__x86_64;intel-gdb__x86_64;intel-gdb-source__noarch;intel-gdb-python-source__noarch;intel-gdb-common__noarch;intel-gdb-ps-common__noarch" - -# Parallel libraries -COMPONENTS_MPI="intel-mpi-rt-core__x86_64;intel-mpi-rt-mic__x86_64;intel-mpi-sdk-core__x86_64;intel-mpi-sdk-mic__x86_64;intel-mpi-psxe__x86_64;intel-mpi-rt-psxe__x86_64" -COMPONENTS_TBB="intel-tbb-libs__noarch;intel-mkl-ps-tbb__x86_64;intel-mkl-ps-tbb-devel__x86_64;intel-mkl-ps-tbb-mic__x86_64;intel-mkl-ps-tbb-mic-devel__x86_64;intel-tbb-source__noarch;intel-tbb-devel__noarch;intel-tbb-common__noarch;intel-tbb-ps-common__noarch;intel-tbb-psxe__noarch" -COMPONENTS_MKL="intel-mkl__x86_64;intel-mkl-ps__x86_64;intel-mkl-common__noarch;intel-mkl-ps-common__noarch;intel-mkl-devel__x86_64;intel-mkl-ps-mic-devel__x86_64;intel-mkl-ps-f95-devel__x86_64;intel-mkl-gnu-devel__x86_64;intel-mkl-ps-gnu-devel__x86_64;intel-mkl-ps-pgi-devel__x86_64;intel-mkl-sp2dp-devel__x86_64;intel-mkl-ps-cluster-devel__x86_64;intel-mkl-ps-cluster-common__noarch;intel-mkl-ps-f95-common__noarch;intel-mkl-ps-cluster__x86_64;intel-mkl-gnu__x86_64;intel-mkl-ps-gnu__x86_64;intel-mkl-ps-pgi__x86_64;intel-mkl-sp2dp__x86_64;intel-mkl-ps-mic__x86_64;intel-mkl-ps-tbb__x86_64;intel-mkl-ps-tbb-devel__x86_64;intel-mkl-ps-tbb-mic__x86_64;intel-mkl-ps-tbb-mic-devel__x86_64;intel-mkl-psxe__noarch" -COMPONENTS_IPP="intel-ipp-l-common__noarch;intel-ipp-l-ps-common__noarch;intel-ipp-l-st__x86_64;intel-ipp-l-mt__x86_64;intel-ipp-l-st-devel__x86_64;intel-ipp-l-ps-st-devel__x86_64;intel-ipp-l-mt-devel__x86_64;intel-ipp-psxe__noarch" -COMPONENTS_IPP_CRYPTO="intel-crypto-ipp-st-devel__x86_64;intel-crypto-ipp-ps-st-devel__x86_64;intel-crypto-ipp-st__x86_64;intel-crypto-ipp-mt-devel__x86_64;intel-crypto-ipp-mt__x86_64;intel-crypto-ipp-ss-st-devel__x86_64;intel-crypto-ipp-common__noarch" -COMPONENTS_DAAL="intel-daal__x86_64;intel-daal-common__noarch" - -# Compilers -COMPONENTS_OPENMP="intel-openmp-l-all__x86_64;intel-openmp-l-ps-mic__x86_64;intel-openmp-l-ps__x86_64;intel-openmp-l-ps-ss__x86_64;intel-openmp-l-all-devel__x86_64;intel-openmp-l-ps-mic-devel__x86_64;intel-openmp-l-ps-devel__x86_64;intel-openmp-l-ps-ss-devel__x86_64" -COMPONENTS_COMPILER_COMMON="intel-comp-l-all-vars__noarch;intel-comp-l-all-common;intel-comp-l-ps-common;intel-comp-l-all-devel;intel-comp-l-ps-ss-devel;intel-comp-l-ps-ss-wrapper;intel-comp-l-all-devel;intel-comp-l-ps-devel__x86_64;intel-comp-l-ps-ss-devel__x86_64;intel-psf-intel__x86_64;intel-icsxe-pset;intel-ipsf__noarch;intel-ccompxe__noarch;${COMPONENTS_OPENMP}" -COMPONENTS_IFORT="intel-ifort-l-ps__x86_64;intel-ifort-l-ps-vars__noarch;intel-ifort-l-ps-common__noarch;intel-ifort-l-ps-devel__x86_64;${COMPONENTS_COMPILER_COMMON}" -COMPONENTS_ICC="intel-icc-l-all__x86_64;intel-icc-l-ps-ss__x86_64;intel-icc-l-all-vars__noarch;intel-icc-l-all-common__noarch;intel-icc-l-ps-common__noarch;intel-icc-l-all-devel__x86_64;intel-icc-l-ps-devel__x86_64;intel-icc-l-ps-ss-devel__x86_64;${COMPONENTS_COMPILER_COMMON}" - -DESTINATION="${HOME}/intel" -TEMPORARY_FILES="/tmp" -PHONE_INTEL="no" -COMPONENTS="" - -add_components() { - if [ ! -z "${COMPONENTS}" ]; then - COMPONENTS="${COMPONENTS};$1" - else - COMPONENTS="$1" - fi -} - -while [ $# != 0 ]; do - case "$1" in - "--dest") - DESTINATION="$(realpath "$2")"; shift - ;; - "--tmpdir") - TEMPORARY_FILES="$2"; shift - ;; - "--components") - shift - OLD_IFS="${IFS}" - IFS="," - for component in $1; do - case "$component" in - "icc") - add_components "${COMPONENTS_ICC}" - ;; - "mpi") - add_components "${COMPONENTS_MPI}" - ;; - "vtune") - add_components "${COMPONENTS_VTUNE}" - ;; - "inspector") - add_components "${COMPONENTS_INSPECTOR}" - ;; - "advisor") - add_components "${COMPONENTS_ADVISOR}" - ;; - "tbb") - add_components "${COMPONENTS_TBB}" - ;; - "ifort") - add_components "${COMPONENTS_IFORT}" - ;; - "mkl") - add_components "${COMPONENTS_MKL}" - ;; - "openmp") - # Noop, here for compatability. OpenMP is installed with icc and ifort now. - ;; - "ipp") - add_components "${COMPONENTS_IPP}" - ;; - "ipp-crypto") - add_components "${COMPONENTS_IPP_CRYPTO}" - ;; - "gdb") - add_components "${COMPONENTS_GDB}" - ;; - "daal") - add_components "${COMPONENTS_DAAL}" - ;; - *) - echo "Unknown component '$component'" - exit 1 - ;; - esac - done - IFS="${OLD_IFS}" - ;; - *) - echo "Unrecognized argument '$1'" - exit 1 - ;; - esac - shift -done - -if [ -z "${COMPONENTS}" ]; then - COMPONENTS="${COMPONENTS_ICC}" -fi - -INSTALLER_SCRIPT="parallel_studio_xe_2016_update3_online.sh" -INSTALLER="${TEMPORARY_FILES}/${INSTALLER_SCRIPT}" -INSTALLER_URL="http://registrationcenter-download.intel.com/akdlm/irc_nas/9061/${INSTALLER_SCRIPT}" -SILENT_CFG="${TEMPORARY_FILES}/silent.cfg" -SUCCESS_INDICATOR="${TEMPORARY_FILES}/icc-travis-success" - -if [ ! -e "${TEMPORARY_FILES}" ]; then - echo "${TEMPORARY_FILES} does not exist, creating..." - mkdir -p "${TEMPORARY_FILES}" || (sudo mkdir -p "${TEMPORARY_FILES}" && sudo chown -R "${USER}:${USER}" "${TEMPORARY_FILES}") -fi - -if [ ! -e "${DESTINATION}" ]; then - echo "${DESTINATION} does not exist, creating..." - mkdir -p "${DESTINATION}" || (sudo mkdir -p "${DESTINATION}" && sudo chown -R "${USER}:${USER}" "${DESTINATION}") -fi - -if [ ! -e "${INSTALLER}" ]; then - wget -O "${INSTALLER}" "${INSTALLER_URL}" || exit 1 -fi -chmod u+x "${INSTALLER}" - -# See https://software.intel.com/en-us/articles/intel-composer-xe-2015-silent-installation-guide -echo "# Generated silent configuration file" > "${SILENT_CFG}" -echo "ACCEPT_EULA=accept" >> "${SILENT_CFG}" -echo "INSTALL_MODE=NONRPM" >> "${SILENT_CFG}" -echo "CONTINUE_WITH_OPTIONAL_ERROR=yes" >> "${SILENT_CFG}" -echo "PSET_INSTALL_DIR=${DESTINATION}" >> "${SILENT_CFG}" -echo "CONTINUE_WITH_INSTALLDIR_OVERWRITE=yes" >> "${SILENT_CFG}" -echo "COMPONENTS=${COMPONENTS}" >> "${SILENT_CFG}" -echo "PSET_MODE=install" >> "${SILENT_CFG}" -echo "PHONEHOME_SEND_USAGE_DATA=${PHONE_INTEL}" >> "${SILENT_CFG}" -if [ "x" != "x${INTEL_SERIAL_NUMBER}" ]; then - echo "ACTIVATION_SERIAL_NUMBER=${INTEL_SERIAL_NUMBER}" >> "${SILENT_CFG}" - echo "ACTIVATION_TYPE=serial_number" >> "${SILENT_CFG}" -else - echo "ACTIVATION_TYPE=trial_lic" >> "${SILENT_CFG}" -fi - -attempt=1; -while [ $attempt -le 3 ]; do - if [ ! -e "${TEMPORARY_FILES}/parallel-studio-install-data" ]; then - mkdir -p "${TEMPORARY_FILES}/parallel-studio-install-data" || (sudo mkdir -p "${TEMPORARY_FILES}/parallel-studio-install-data" && sudo chown -R "${USER}:${USER}" "${TEMPORARY_FILES}") - fi - - ("${INSTALLER}" \ - -t "${TEMPORARY_FILES}/parallel-studio-install-data" \ - -s "${SILENT_CFG}" \ - --cli-mode \ - --user-mode && \ - touch "${SUCCESS_INDICATOR}") & - - # So Travis doesn't die in case of a long download/installation. - # - # NOTE: a watched script never terminates. - elapsed=0; - while kill -0 $! 2>/dev/null; do - sleep 1 - elapsed=$(expr $elapsed + 1) - if [ $(expr $elapsed % 60) -eq 0 ]; then - mins_elapsed=$(expr $elapsed / 60) - if [ $mins_elapsed = 1 ]; then - minute_string="minute" - else - minute_string="minutes" - fi - echo "Still running... (about $(expr $elapsed / 60) ${minute_string} so far)." - fi - done - - if [ ! -e "${SUCCESS_INDICATOR}" ]; then - echo "Installation failed." - exit 1 - fi - - if [ ! -e "${DESTINATION}/bin/compilervars.sh" ]; then - # Sometimes the installer returns successfully without actually - # installing anything. Let's try again… - echo "Installation attempt #${attempt} completed, but unable to find compilervars.sh." - find "${DESTINATION}" - else - break - fi - - rm -vrf "${TEMPORARY_FILES}/parallel-studio-install-data" - echo "Trying again..." - - attempt=$(expr $attempt + 1) -done - -if [ ! -e "${DESTINATION}/bin/compilervars.sh" ]; then - echo "Installation failed." - exit 1 -fi - -# Apparently the installer drops the license file in a location it -# doesn't know to check. -ln -s "${DESTINATION}"/licenses ~/Licenses - -# Add configuration information to ~/.bashrc. Unfortunately this will -# not be picked up automatically by Travis, so you'll still need to -# source ~/.bashrc in your .travis.yml -# -# Container-based builds include a `[ -z "$PS1" ] && return` near the -# beginning of the file, so appending won't work, we'll need to -# prepend. -echo "export INTEL_INSTALL_PATH=\"${DESTINATION}\"" >> ~/.bashrc-intel -echo ". \"\${INTEL_INSTALL_PATH}/bin/compilervars.sh\" intel64" >> ~/.bashrc-intel -echo "export LD_LIBRARY_PATH=\"\${INTEL_INSTALL_PATH}/ism/bin/intel64:\${INTEL_INSTALL_PATH}/lib/intel64_lin:\$LD_LIBRARY_PATH\"" >> ~/.bashrc-intel -echo "export PATH=\"\${INTEL_INSTALL_PATH}/bin:\$PATH\"" >> ~/.bashrc-intel -echo "function uninstall_intel_software {" >> ~/.bashrc-intel -echo " find \"\${INTEL_INSTALL_PATH}\" -name 'uninstall.sh' -not -path '*/ism/uninstall.sh' -exec {} -s \;" >> ~/.bashrc-intel -echo "}" >> ~/.bashrc-intel -cat ~/.bashrc >> ~/.bashrc-intel -mv ~/.bashrc-intel ~/.bashrc diff --git a/pyproject.toml b/pyproject.toml index fe3d51d..f803dae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ requires = [ "setuptools>=42", "wheel", "pybind11>=2.6.0", + "cmake>=3.13", ] build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/pysrc/stator/core.cpp b/pysrc/stator/core.cpp index 5f8f85d..08a19be 100644 --- a/pysrc/stator/core.cpp +++ b/pysrc/stator/core.cpp @@ -31,7 +31,7 @@ PYBIND11_MODULE(core, m) m.def("derivative", static_cast(&sym::derivative)); m.def("simplify", static_cast(&sym::simplify)); - m.def("subs", static_cast(&sym::simplify)); + //m.def("sub", static_cast(&sym::sub)); py::implicitly_convertible(); py::implicitly_convertible(); diff --git a/scripts/manylinux_entrypoint.sh b/scripts/manylinux_entrypoint.sh new file mode 100755 index 0000000..dcd184f --- /dev/null +++ b/scripts/manylinux_entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e -u -x + +function repair_wheel { + wheel="$1" + if ! auditwheel show "$wheel"; then + echo "Skipping non-platform wheel $wheel" + else + auditwheel repair "$wheel" --plat "$PLAT" -w /io/wheelhouse/ + fi +} + + +# Install a system package required by our library +yum install -y gtest-devel + +# Compile wheels +ls /opt/python/ +mkdir -p /io/wheelhouse +for VER in cp39-cp39; do #cp36-cp36m cp37-cp37m cp38-cp38 + PYBIN=/opt/python/$VER/bin + "${PYBIN}/pip" install -r /io/dev-requirements.txt + "${PYBIN}/pip" wheel /io/ --no-deps -w /io/wheelhouse/ +done + +# Bundle external shared libraries into the wheels +for whl in /io/wheelhouse/*.whl; do + #auditwheel repair "$whl" + repair_wheel "$whl" +done + +# Install packages and test +for VER in cp39-cp39; do #cp36-cp36m cp37-cp37m cp38-cp38 + PYBIN=/opt/python/$VER/bin + "${PYBIN}/pip" install stator --no-index -f /io/wheelhouse + #(cd "$HOME"; "${PYBIN}/nosetests" pymanylinuxdemo) +done diff --git a/setup.py b/setup.py index d27bc00..12344b3 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ import sysconfig import platform import subprocess +from pathlib import Path from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext @@ -13,52 +14,75 @@ # Setup following advice from # https://www.benjack.io/2017/06/12/python-cpp-tests.html - +# +# In particular here: +# https://gist.github.com/hovren/5b62175731433c741d07ee6f482e2936 class CMakeExtension(Extension): def __init__(self, name, sourcedir=''): Extension.__init__(self, name, sources=[]) self.sourcedir = os.path.abspath(sourcedir) - class CMakeBuild(build_ext): def run(self): try: out = subprocess.check_output(['cmake', '--version']) except OSError: - raise RuntimeError("CMake must be installed to build the following extensions: " + - ", ".join(e.name for e in self.extensions)) + raise RuntimeError( + "CMake must be installed to build the following extensions: " + + ", ".join(e.name for e in self.extensions)) - if platform.system() == "Windows": - cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) - if cmake_version < '3.1.0': - raise RuntimeError("CMake >= 3.1.0 is required on Windows") + build_directory = os.path.abspath(self.build_temp) - for ext in self.extensions: - self.build_extension(ext) - - def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir] + cmake_args = [ + '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + build_directory, + '-DPYTHON_EXECUTABLE=' + sys.executable + ] cfg = 'Debug' if self.debug else 'Release' build_args = ['--config', cfg] - if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] + cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] + # Assuming Makefiles + import multiprocessing + build_args += ['--', '--jobs='+str(multiprocessing.cpu_count())] + self.build_args = build_args + env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), - self.distribution.get_version()) + env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format( + env.get('CXXFLAGS', ''), + self.distribution.get_version()) if not os.path.exists(self.build_temp): os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - import multiprocessing - subprocess.check_call(['cmake', '--build', '.'] + build_args + ['--', '--jobs='+str(multiprocessing.cpu_count())], cwd=self.build_temp) - print() + # CMakeLists.txt is in the same directory as this setup.py file + cmake_list_dir = os.path.abspath(os.path.dirname(__file__)) + print('-'*10, 'Running CMake prepare', '-'*40) + subprocess.check_call(['cmake', cmake_list_dir] + cmake_args, + cwd=self.build_temp, env=env) + + print('-'*10, 'Building extensions', '-'*40) + cmake_cmd = ['cmake', '--build', '.'] + self.build_args + subprocess.check_call(cmake_cmd, + cwd=self.build_temp) + + # Move from build temp to final position + for ext in self.extensions: + self.move_output(ext) + + def move_output(self, ext): + build_temp = Path(self.build_temp).resolve() + dest_path = Path(self.get_ext_fullpath(ext.name)).resolve() + source_path = build_temp / self.get_ext_filename(ext.name) + dest_directory = dest_path.parents[0] + dest_directory.mkdir(parents=True, exist_ok=True) + self.copy_file(source_path, dest_path) + + +ext_modules = [ + CMakeExtension('stator.core'), +] from setuptools.command.test import test as TestCommand class CatchTestCommand(TestCommand): @@ -93,9 +117,7 @@ def run(self): '': 'pysrc', }, packages=find_packages('pysrc'), - ext_modules=[ - CMakeExtension('stator.core') - ], + ext_modules=ext_modules, extras_require={"test": "pytest"}, cmdclass=dict(build_ext=CMakeBuild, test=CatchTestCommand), setup_requires=['pytest-runner'], diff --git a/stator/symbolic/runtime.hpp b/stator/symbolic/runtime.hpp index ba22c7e..4dd486d 100644 --- a/stator/symbolic/runtime.hpp +++ b/stator/symbolic/runtime.hpp @@ -370,11 +370,24 @@ namespace sym { constexpr bool operator==(const RHS&) const { return false; } + + template + auto operator=(const Arg& a) const -> STATOR_AUTORETURN(equal(*this, a)); inline std::string getName() const { return _name; } std::string _name; }; + + template + Expr sub(const VarRT& f, const EqualityOp, Arg>& x) + { + if (f == x._l) + return x._r; + else + return f; + } + } namespace std @@ -552,6 +565,9 @@ namespace sym { public: Dict() {} + typedef Store::key_type key_type; + typedef Store::reference reference; + typedef Store::const_reference const_reference; typedef typename Store::iterator iterator; typedef typename Store::const_iterator const_iterator; @@ -562,9 +578,8 @@ namespace sym { const_iterator end() const {return _store.end();} const_iterator cend() const {return _store.cend();} - typedef Store::key_type key_type; - typedef Store::reference reference; - typedef Store::const_reference const_reference; + iterator find( const key_type& key ) { return _store.find(key); } + const_iterator find( const key_type& key ) const { return _store.find(key); } Expr& operator[](const VarRT& k) { return _store[k]; } Expr& at(const VarRT& k) { return _store.at(k); } @@ -766,10 +781,10 @@ namespace sym { Dict simplify(const Dict& in) { Dict out; - for (const auto& p : in) + for (const auto& p : in) out[p.first] = simplify(p.second); return out; - } + } namespace detail { struct SubstituteRT : VisitorHelper { @@ -786,6 +801,29 @@ namespace sym { return _replacement; return Expr(); } + + //Variable matching + Expr apply(const List& v) { + List ret; + + bool _replaced = false; //We only start regenerating the list when needed + for (size_t i(0); i < v.size(); ++i) { + Expr t = v[i]->visit(*this); + + if (bool(t) && (!_replaced)) { + //We're starting replacements, so copy everything skipped over so far + ret.resize(v.size()); + for (size_t j(0); j < i; ++j) + ret[j] = v[j]; + _replaced = true; + } + + if (_replaced) //continue the copy where required + ret[i] = (t) ? t : v[i]; + } + + return (_replaced) ? ret : Expr(); + } template Expr apply(const UnaryOp& op) { @@ -807,14 +845,87 @@ namespace sym { VarRT _var; Expr _replacement; }; + } - + template Expr sub(const Expr& f, const EqualityOp& rel) { detail::SubstituteRT visitor(rel._l, rel._r); Expr result = f->visit(visitor); return (result) ? result : f; } + + namespace detail { + struct SubstituteDictRT : VisitorHelper { + SubstituteDictRT(const Dict& replacement): _replacement(replacement) {} + + //By default, just return an empty Expr, and let the helper function return the original expression + template + Expr apply(const T& v) + { return Expr(); } + + //Variable matching + Expr apply(const VarRT& v) { + std::cout << "!! Found " << v << std::endl; + auto it = _replacement.find(v); + if (it != _replacement.end()) { + std::cout << "!! Replacing it with " << it->second << std::endl; + return it->second; + } + std::cout << "!! Not replacing it " << v << std::endl; + return Expr(); + } + + //Variable matching + Expr apply(const List& v) { + List ret; + + bool _replaced = false; //We only start regenerating the list when needed + for (size_t i(0); i < v.size(); ++i) { + Expr t = v[i]->visit(*this); + + if (bool(t) && ! _replaced) { + //We're starting replacements, so copy everything skipped over so far + ret.resize(v.size()); + for (size_t j(0); j < i; ++j) + ret[j] = v[j]; + _replaced = true; + } + + if (_replaced) //continue the copy where required + ret[i] = (t) ? t : v[i]; + } + + return (_replaced) ? ret : Expr(); + } + + template + Expr apply(const UnaryOp& op) { + Expr arg = op.getArg()->visit(*this); + return arg ? Expr(Op::apply(arg)) : Expr(); + } + + template + Expr apply(const BinaryOp& op) { + Expr l = op.getLHS()->visit(*this); + Expr r = op.getRHS()->visit(*this); + + if (l || r) + return Expr(Op::apply(l ? l : op._l, r ? r : op._r)); + else + return Expr(); + } + + const Dict& _replacement; + }; + + } + + Expr sub(const Expr& f, const Dict& rep) { + detail::SubstituteDictRT visitor(rep); + Expr result = f->visit(visitor); + return (result) ? result : f; + } namespace detail { struct DerivativeRT : VisitorHelper { diff --git a/tests/stator_test.py b/tests/stator_test.py index 5551ac8..385682d 100644 --- a/tests/stator_test.py +++ b/tests/stator_test.py @@ -22,13 +22,16 @@ def test_array(self): f = Expr("[1, x, 2*x^2]") x = Expr("x") df = derivative(f, x) - self.assertEqual(repr(simplify(df)), "Expr('[0, 1, 4*x]')") - + df = simplify(df) + self.assertEqual(repr(df), "Expr('[0, 1, 4*x]')") + #self.assertEqual(repr(subs(df, x = 2)), "Expr('[0, 1, 8]')") + def test_dict(self): self.assertEqual(repr(Expr("{}")),"Expr('{}')") self.assertEqual(repr(Expr("{x:1}")),"Expr('{x:1}')") + if __name__ == "__main__": unittest.main() diff --git a/tests/symbolic_runtime_test.cpp b/tests/symbolic_runtime_test.cpp index 22cbadf..d7aa807 100644 --- a/tests/symbolic_runtime_test.cpp +++ b/tests/symbolic_runtime_test.cpp @@ -202,3 +202,20 @@ UNIT_TEST( symbolic_runtime_derivative ) UNIT_TEST_CHECK_EQUAL(simplify(derivative(Expr("exp(x)"), Var<>())), Expr("exp(x)")); UNIT_TEST_CHECK_EQUAL(simplify(derivative(Expr("x*exp(x)"), Var<>())), Expr("exp(x)+x*exp(x)")); } + +UNIT_TEST( symbolic_list_basic ) +{ + UNIT_TEST_CHECK_EQUAL(sub(Expr("[1, x, y]"), VarRT("x") = 2), Expr("[1,2,y]")); + UNIT_TEST_CHECK_EQUAL(derivative(Expr("[1, x, y]"), VarRT("x")), Expr("[0,1,0]")); +} +UNIT_TEST( symbolic_dict_basic ) +{ + Dict v; + v[VarRT("x")] = 2; + v[VarRT("y")] = 3; + std::cout << "##1##1 " << v << std::endl; + UNIT_TEST_CHECK_EQUAL(sub(Expr("x"), v), Expr("2")); + UNIT_TEST_CHECK_EQUAL(sub(Expr("y"), v), Expr("3")); + + UNIT_TEST_CHECK_EQUAL(sub(Expr("[1, x, y]"), v), Expr("[1,2,3]")); +}