diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..c176f13e1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,206 @@ +name: Build and test symengine +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.OS }} + strategy: + fail-fast: false + matrix: + include: + - BUILD_TYPE: Debug + WITH_BFD: yes + PYTHON_VERSION: '3.12' + TEST_SYMPY: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Debug + WITH_BFD: yes + PYTHON_VERSION: '3.11' + TEST_SYMPY: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Debug + WITH_BFD: yes + PYTHON_VERSION: '3.10' + TEST_SYMPY: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Debug + WITH_BFD: yes + PYTHON_VERSION: '3.9' + TEST_SYMPY: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.13' + BUILD_SHARED_LIBS: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.13' + WITH_MPFR: yes + INTEGER_CLASS: gmpxx + WITH_NUMPY: no + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.13' + WITH_MPC: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Release + WITH_MPFR: yes + PYTHON_VERSION: '3.13' + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.9' + WITH_MPC: yes + OS: ubuntu-22.04 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.9' + WITH_MPC: yes + INTEGER_CLASS: flint + WITH_FLINT: yes + OS: ubuntu-22.04 + CC: gcc + + #- BUILD_TYPE: Debug + # PYTHON_VERSION: '3.9' + # WITH_BFD: yes + # WITH_PIRANHA: yes + # OS: ubuntu-22.04 + # CC: gcc + + - BUILD_TYPE: Debug + PYTHON_VERSION: '3.13' + WITH_BFD: yes + BUILD_SHARED_LIBS: yes + OS: ubuntu-22.04 + CC: clang + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.13' + WITH_NUMPY: yes + OS: ubuntu-22.04 + CC: clang + + - BUILD_TYPE: Debug + PYTHON_VERSION: '3.12' + WITH_SYMPY: yes + WITH_LLVM: 18 + WITH_SCIPY: yes + WITH_LATEST_GCC: yes + INTEGER_CLASS: 'boostmp' + PYTEST_ADDOPTS: '-k "not integer_nthroot"' + OS: ubuntu-24.04 + CC: gcc # ubuntu nobel uses gcc-13 + #EXTRA_APT_REPOSITORY: 'deb http://apt.llvm.org/jammy/ llvm-toolchain-nobel-18 main' + EXTRA_APT_PACKAGES: 'llvm-18' + + - BUILD_TYPE: Debug + PYTHON_VERSION: '3.13' + WITH_SCIPY: yes + WITH_LLVM: 5.0 + OS: macos-13 + CC: clang + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.9' + WITH_NUMPY: no + OS: macos-13 + CC: clang + + - BUILD_TYPE: Debug + PYTHON_VERSION: '3.13' + WITH_NUMPY: no + OS: macos-13 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.13' + OS: macos-13 + CC: gcc + + - BUILD_TYPE: Release + PYTHON_VERSION: '3.11' + OS: ubuntu-22.04 + WITH_MPC: yes + WITH_MPFR: yes + WITH_FLINT: yes + WITH_FLINT_PY: yes + WITH_SCIPY: yes + WITH_DOCS: yes + INTEGER_CLASS: flint + TEST_SYMPY: yes + CC: gcc + + steps: + + - uses: conda-incubator/setup-miniconda@v3 + if: matrix.MSYS_ENV == '' + with: + activate-environment: symengine + channel-priority: strict + architecture: x86_64 + channels: conda-forge + conda-remove-defaults: "true" + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build and test symengine + shell: bash -el {0} + run: | + source bin/test_symengine_unix.sh + env: + PYTEST_ADDOPTS: ${{ matrix.PYTEST_ADDOPTS }} + USE_GLIBCXX_DEBUG: ${{ matrix.USE_GLIBCXX_DEBUG }} + WITH_MPFR: ${{ matrix.WITH_MPFR }} + BUILD_BENCHMARKS: ${{ matrix.BUILD_BENCHMARKS }} + WITH_LLVM: ${{ matrix.WITH_LLVM }} + WITH_BENCHMARKS_NONIUS: ${{ matrix.WITH_BENCHMARKS_NONIUS }} + WITH_SYMENGINE_RCP: ${{ matrix.WITH_SYMENGINE_RCP }} + TEST_IN_TREE: ${{ matrix.TEST_IN_TREE }} + WITH_SYMENGINE_THREAD_SAFE: ${{ matrix.WITH_SYMENGINE_THREAD_SAFE }} + WITH_PRIMESIEVE: ${{ matrix.WITH_PRIMESIEVE }} + INTEGER_CLASS: ${{ matrix.INTEGER_CLASS }} + WITH_ARB: ${{ matrix.WITH_ARB }} + WITH_PIRANHA: ${{ matrix.WITH_PIRANHA }} + WITH_GCC_6: ${{ matrix.WITH_GCC_6 }} + CONDA_ENV_FILE: ${{ matrix.CONDA_ENV_FILE }} + WITH_BFD: ${{ matrix.WITH_BFD }} + WITH_FLINT: ${{ matrix.WITH_FLINT }} + EXTRA_APT_REPOSITORY: ${{ matrix.EXTRA_APT_REPOSITORY }} + EXTRA_APT_PACKAGES: ${{ matrix.EXTRA_APT_PACKAGES }} + TEST_CLANG_FORMAT: ${{ matrix.TEST_CLANG_FORMAT }} + WITH_ECM: ${{ matrix.WITH_ECM }} + WITH_LATEST_GCC: ${{ matrix.WITH_LATEST_GCC }} + OS: ${{ matrix.OS }} + WITH_FLINT_DEV: ${{ matrix.WITH_FLINT_DEV }} + CC: ${{ matrix.CC }} + WITH_COVERAGE: ${{ matrix.WITH_COVERAGE }} + BUILD_TYPE: ${{ matrix.BUILD_TYPE }} + WITH_SANITIZE: ${{ matrix.WITH_SANITIZE }} + WITH_MPC: ${{ matrix.WITH_MPC }} + MAKEFLAGS: ${{ matrix.MAKEFLAGS }} + BUILD_SHARED_LIBS: ${{ matrix.BUILD_SHARED_LIBS }} + PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} + + - name: Deploy Documentation + if: ${{ (github.ref == 'refs/heads/main' && github.repository == 'Symengine/symengine.py') || (github.ref == 'refs/heads/master' && github.repository == 'Symengine/symengine.py')}} + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./genDocs diff --git a/.gitignore b/.gitignore index db4d60c66..f93c02277 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ cython_test.cpp *.vcxproj *.filters symengine/lib/symengine_wrapper.cpp +symengine/lib/symengine_wrapper.pyx +symengine/lib/symengine_wrapper.pxd # Config Files symengine/lib/config.pxi @@ -35,3 +37,9 @@ symengine.egg-info/ # Temp files *~ .eggs/ + +# Docs +genDocs/ +docs/_build/ +docs/source/ +symengine/lib/version_script_symengine_wrapper.txt diff --git a/.mailmap b/.mailmap index 6918cbecd..11654a9b0 100644 --- a/.mailmap +++ b/.mailmap @@ -11,6 +11,7 @@ Ondřej Čertík Peter Brady Isuru Fernando +Isuru Fernando Alan Hu <31489167+alanlh@users.noreply.github.com> Shivam Vats Shikhar Jaiswal @@ -18,3 +19,7 @@ Sumith Kulal Sushant Hiray Abhinav Agarwal Nilay Pochhi +Björn Dahlgren +Richard Otis richardotis +Firat Bezir +Adrian Ostrowski <81568391+aostrowski-hbn@users.noreply.github.com> diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 45b935650..000000000 --- a/.travis.yml +++ /dev/null @@ -1,128 +0,0 @@ -language: cpp -compiler: - - gcc - - clang -os: - - linux - - osx -sudo: false -osx_image: xcode6.4 -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - binutils-dev - - g++-4.7 - -env: - ## All these variables are sent into the bin/test_travis.sh script. See this - ## script to know how they are used. Most of them are just passed to cmake, - ## so if they are not set, cmake will use a default value. For the rest, the - ## bin/test_travis.sh script usually checks for either "yes" or "no" in an if - ## statement, so if the variable is not set, the other if branch will get - ## executed. - matrix: - # Debug builds: - - BUILD_TYPE="Debug" WITH_BFD="yes" PYTHON_VERSION="2.7" TEST_SYMPY="yes" - - BUILD_TYPE="Debug" WITH_BFD="yes" PYTHON_VERSION="3.6" TEST_SYMPY="yes" - - # Release builds: - - PYTHON_VERSION="2.7" BUILD_SHARED_LIBS="yes" - - PYTHON_VERSION="2.7" WITH_MPFR="yes" INTEGER_CLASS="gmpxx" WITH_NUMPY="no" - - PYTHON_VERSION="3.7" WITH_MPC="yes" - - PYTHON_VERSION="3.7" WITH_MPFR="yes" - - PYTHON_VERSION="3.5" WITH_MPC="yes" - - PYTHON_VERSION="3.6" WITH_MPC="yes" INTEGER_CLASS="flint" WITH_FLINT="yes" - -matrix: - exclude: - - compiler: clang - - os: osx - include: - - env: BUILD_TYPE="Debug" WITH_BFD="yes" WITH_PIRANHA="yes" PYTHON_VERSION="2.7" - compiler: gcc - os: linux - addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - binutils-dev - - g++-4.8 - - env: BUILD_TYPE="Debug" WITH_BFD="yes" PYTHON_VERSION="3.5" BUILD_SHARED_LIBS="yes" - compiler: clang - os: linux - - env: BUILD_TYPE="Release" PYTHON_VERSION="2.7" WITH_NUMPY="no" - compiler: clang - os: linux - - env: BUILD_TYPE="Debug" PYTHON_VERSION="3.6" WITH_SYMPY="no" WITH_LLVM="8.0" WITH_SCIPY="yes" CC="gcc-4.8" CXX="g++-4.8" - compiler: gcc - os: linux - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-8 - packages: - - g++-4.8 - - libstdc++-4.8-dev - - binutils-dev - - llvm-8-dev - - env: BUILD_TYPE="Debug" PYTHON_VERSION="2.7" WITH_LLVM="5.0" WITH_SCIPY="yes" - compiler: clang - os: osx - - env: BUILD_TYPE="Release" PYTHON_VERSION="3.5" WITH_NUMPY="no" - compiler: clang - os: osx - - env: BUILD_TYPE="Debug" PYTHON_VERSION="2.7" WITH_NUMPY="no" - compiler: gcc - os: osx - - env: BUILD_TYPE="Release" PYTHON_VERSION="3.5" - compiler: gcc - os: osx - - env: BUILD_TYPE="Release" WITH_SAGE="yes" WITH_MPC="yes" PYTHON_VERSION="2.7" - compiler: gcc - os: linux - allow_failures: - - env: BUILD_TYPE="Release" WITH_SAGE="yes" WITH_MPC="yes" PYTHON_VERSION="2.7" - -before_install: -- | - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then - command curl -sSL https://rvm.io/mpapis.asc | gpg --import -; - rvm get head || true - fi - -install: - - export PYTHON_SOURCE_DIR=`pwd` - - export TEST_CPP="no" - - export MAKEFLAGS="-j2" - - - git clone https://github.com/symengine/symengine symengine-cpp - - cd symengine-cpp - - export SOURCE_DIR=`pwd` - - git checkout `cat ../symengine_version.txt` - - cd .. - - # Setup travis for C++ library - - cd $SOURCE_DIR - - source bin/install_travis.sh - - # Setup travis for Python wrappers - - cd $PYTHON_SOURCE_DIR - - source bin/install_travis.sh - - # Build C++ library - - cd $SOURCE_DIR - - bin/test_travis.sh - - unset MAKEFLAGS - -script: - # Build Python wrappers and test - - cd $PYTHON_SOURCE_DIR - - bin/test_travis.sh - -notifications: - email: false - diff --git a/AUTHORS b/AUTHORS index 58f002dfe..484d38bee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -26,3 +26,16 @@ Alan Hu Richard Otis Erik Jansson Agnvall Simon Stelter +Jialin Ma +Rikard Nordgren +Rohit Goswami +Matthew Treinish +Michał Górny +Garming Sam +Pieter Eendebak +Ayush Kumar +Christian Clauss +Moraxyc +Aaron Miller <78561124+aaron-skydio@users.noreply.github.com> +Firat Bezir +Adrian Ostrowski diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cabd8ed2..e83c95b18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,19 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12...4.0.0) + +if (POLICY CMP0057) + cmake_policy(SET CMP0057 NEW) # needed for llvm >= 16 +endif () +if (POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) # allow user to set *_ROOT variables +endif() + project(python_wrapper) set(CMAKE_PREFIX_PATH ${SymEngine_DIR} ${CMAKE_PREFIX_PATH}) -find_package(SymEngine 0.6.0 REQUIRED CONFIG + +include(GNUInstallDirs) + +find_package(SymEngine 0.14.0 REQUIRED CONFIG PATH_SUFFIXES lib/cmake/symengine cmake/symengine CMake/) message("SymEngine_DIR : " ${SymEngine_DIR}) message("SymEngine Version : " ${SymEngine_VERSION}) @@ -44,16 +55,20 @@ foreach (PKG MPC MPFR PIRANHA FLINT LLVM) set(HAVE_SYMENGINE_${PKG} False) endif() endforeach() +option(SYMENGINE_INSTALL_PY_FILES "Install python files" ON) -message("CMAKE_BUILD_TYPE : ${CMAKE_BUILD_TYPE}") -message("CMAKE_CXX_FLAGS : ${CMAKE_CXX_FLAGS}") -message("CMAKE_CXX_FLAGS_RELEASE : ${CMAKE_CXX_FLAGS_RELEASE}") -message("CMAKE_CXX_FLAGS_DEBUG : ${CMAKE_CXX_FLAGS_DEBUG}") -message("HAVE_SYMENGINE_MPFR : ${HAVE_SYMENGINE_MPFR}") -message("HAVE_SYMENGINE_MPC : ${HAVE_SYMENGINE_MPC}") -message("HAVE_SYMENGINE_PIRANHA : ${HAVE_SYMENGINE_PIRANHA}") -message("HAVE_SYMENGINE_FLINT : ${HAVE_SYMENGINE_FLINT}") -message("HAVE_SYMENGINE_LLVM : ${HAVE_SYMENGINE_LLVM}") +message("CMAKE_SYSTEM_PROCESSOR : ${CMAKE_SYSTEM_PROCESSOR}") +message("CMAKE_BUILD_TYPE : ${CMAKE_BUILD_TYPE}") +message("CMAKE_CXX_FLAGS : ${CMAKE_CXX_FLAGS}") +message("CMAKE_CXX_FLAGS_RELEASE : ${CMAKE_CXX_FLAGS_RELEASE}") +message("CMAKE_CXX_FLAGS_DEBUG : ${CMAKE_CXX_FLAGS_DEBUG}") +message("HAVE_SYMENGINE_MPFR : ${HAVE_SYMENGINE_MPFR}") +message("HAVE_SYMENGINE_MPC : ${HAVE_SYMENGINE_MPC}") +message("HAVE_SYMENGINE_PIRANHA : ${HAVE_SYMENGINE_PIRANHA}") +message("HAVE_SYMENGINE_FLINT : ${HAVE_SYMENGINE_FLINT}") +message("HAVE_SYMENGINE_LLVM : ${HAVE_SYMENGINE_LLVM}") +message("HAVE_SYMENGINE_LLVM_LONG_DOUBLE : ${HAVE_SYMENGINE_LLVM_LONG_DOUBLE}") +message("SYMENGINE_COPY_EXTENSION : ${SYMENGINE_COPY_EXTENSION}") message("Copying source of python wrappers into: ${CMAKE_CURRENT_BINARY_DIR}") file(COPY symengine/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/symengine) diff --git a/MANIFEST.in b/MANIFEST.in index 5ff1ff6bd..c5d457e29 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include CMakeLists.txt LICENSE README.md symengine_version.txt recursive-include symengine *.cpp *.h *.hpp CMakeLists.txt *.in *.cmake *.pyx *.pxd *.py *.pxi -recursive-include cmake *.cpp *.in *.cmake *.pyx +recursive-include cmake *.cpp *.in *.cmake *.pyx *.py *.txt diff --git a/README.md b/README.md index 412a0ef32..e13eb7272 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ Python wrappers to the C++ library [SymEngine](https://github.com/symengine/symengine), a fast C++ symbolic manipulation library. -[![Build Status](https://travis-ci.org/symengine/symengine.py.svg)](https://travis-ci.org/symengine/symengine.py) [![Build status](https://ci.appveyor.com/api/projects/status/sl189l9ck3gd8qvk/branch/master?svg=true)](https://ci.appveyor.com/project/symengine/symengine-py/branch/master) +[![Build Status](https://travis-ci.org/symengine/symengine.py.svg)](https://travis-ci.org/symengine/symengine.py) +[![Build status](https://ci.appveyor.com/api/projects/status/sl189l9ck3gd8qvk/branch/master?svg=true)](https://ci.appveyor.com/project/symengine/symengine-py/branch/master) ## Installation @@ -11,68 +12,107 @@ a fast C++ symbolic manipulation library. See License section for information about wheels - pip install symengine --user +```bash +pip install symengine --user +``` ### Conda package manager - conda install python-symengine -c symengine -c conda-forge - -optionally, you may choose to install an early [developer preview](https://github.com/symengine/python-symengine-feedstock): - - conda install python-symengine -c symengine/label/dev -c conda-forge +```bash +conda install python-symengine -c conda-forge +``` ### Build from source Install prerequisites. - CMake >= 2.8.7 - Python2 >= 2.7 or Python3 >= 3.4 - Cython >= 0.19.1 - SymEngine >= 0.4.0 +```bash +CMake >= 2.8.12 +Python3 >= 3.8 +Cython >= 0.29.24 +SymEngine >= 0.7.0 +``` -For SymEngine, only a specific commit/tag (see symengine_version.txt) is supported. -Latest git master branch may not work as there may be breaking changes in SymEngine. +For **SymEngine**, only a specific commit/tag (see `symengine_version.txt`) is +supported. The latest git master branch may not work as there may be breaking +changes in **SymEngine**. Python wrappers can be installed by, - python setup.py install +```bash +python setup.py install +``` -Additional options to setup.py are +Additional options to `setup.py` are: - python setup.py install build_ext - --symengine-dir=/path/to/symengine/install/dir # Path to SymEngine install directory or build directory - --compiler=mingw32|msvc|cygwin # Select the compiler for Windows - --generator=cmake-generator # CMake Generator - --build-type=Release|Debug # Set build-type for multi-configuration generators like MSVC - --define="var1=value1;var2=value2" # Give options to CMake - --inplace # Build the extension in source tree +```bash +python setup.py install build_ext + --symengine-dir=/path/to/symengine/install/dir # Path to SymEngine install directory or build directory + --compiler=mingw32|msvc|cygwin # Select the compiler for Windows + --generator=cmake-generator # CMake Generator + --build-type=Release|Debug # Set build-type for multi-configuration generators like MSVC + --define="var1=value1;var2=value2" # Give options to CMake + --inplace # Build the extension in source tree +``` -Standard options to setup.py like `--user`, `--prefix` can be used to -configure install location. NumPy is used if found by default, if you wish +Standard options to `setup.py` like `--user`, `--prefix` can be used to +configure install location. NumPy is used if found by default, if you wish to make your choice of NumPy use explicit: then add -e.g. ``WITH_NUMPY=False`` to ``--define``. - -Use SymEngine from Python as follows: - - >>> from symengine import var - >>> var("x y z") - (x, y, z) - >>> e = (x+y+z)**2 - >>> e.expand() - 2*x*y + 2*x*z + 2*y*z + x**2 + y**2 + z**2 - -You can read Python tests in `symengine/tests` to see what features are -implemented. - +e.g. `WITH_NUMPY=False` to `--define`. + +### Notes on Dependencies + +If you intend to evaluate floating-point expressions (using **lambdify**), +you should consider linking against **LLVM**. Many users might also benefit +from linking against **FLINT**, as it is now LGPL-licensed. + +In general, **sudo** is only required if you are installing to the default +prefix (`/usr/local`). We recommend specifying a custom prefix +(`--prefix=$HOME/.local`) to avoid requiring administrative privileges, +which most users can do without using **sudo**. + +If you're uncomfortable specifying the prefix manually, we suggest using +**Conda** or installing the pre-built wheels via **pip** instead of building +from source. + +## Verification + +You can verify the installation of **SymEngine** by using the provided code +snippet in this README. This snippet ensures that the installation works as +expected and that basic functionality is available. + +```python +from symengine import var +x, y, z = var('x y z') +e = (x + y + z)**2 +expanded_e = e.expand() +print(expanded_e) +``` +This will output: +```python +x**2 + y**2 + z**2 + 2*x*y + 2*x*z + 2*y*z +``` + +Note: The verification code provided above checks the functionality of +SymEngine. For additional verification specific to SymEngine, please refer to +the [official SymEngine Python bindings repository](https://github.com/symengine/symengine.py) +for further tests and examples. ## License -symengine.py is MIT licensed and uses several LGPL, BSD-3 and MIT licensed libraries - -Licenses for the dependencies of pip wheels are as follows, - -pip wheels on Unix use GMP (LGPL v3), MPFR (LGPL v3), MPC (LGPL v3), LLVM (NCSA) and symengine (MIT + BSD-3). -pip wheels on Windows use MPIR (LGPL v3) instead of GMP above. -NumPy (BSD-3) and SymPy (BSD-3) are optional dependencies. -Sources for these binary dependencies can be found on https://github.com/symengine/symengine-wheels/releases +symengine.py is MIT licensed and uses several LGPL, BSD-3, and MIT licensed +libraries. + +Licenses for the dependencies of pip wheels are as follows: + +- pip wheels on Unix use **GMP** (LGPL-3.0-or-later), + **MPFR** (LGPL-3.0-or-later), **MPC** (LGPL-3.0-or-later), + **LLVM** (Apache-2.0), **zlib** (Zlib), **libxml2** (MIT), + **zstd** (BSD-3-Clause), and **symengine** (MIT AND BSD-3-Clause). +- pip wheels on Windows use **MPIR** (LGPL-3.0-or-later) instead of **GMP** + above and **pthreads-win32** (LGPL-3.0-or-later) additionally. +- **NumPy** (BSD-3-Clause) and **SymPy** (BSD-3-Clause) are optional + dependencies. +- Sources for these binary dependencies can be found on + [symengine-wheels](https://github.com/symengine/symengine-wheels/releases). diff --git a/appveyor.yml b/appveyor.yml index b0ddc7f3f..ce1c487ce 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,6 @@ version: '{build}' -# Uncomment this to enable the fast build environment if your account does not -# support it automatically: -#os: Visual Studio 2015 RC +image: "Visual Studio 2019" environment: global: @@ -12,62 +10,80 @@ environment: - BUILD_TYPE: "Release" COMPILER: MSVC15 PLATFORM: "Win32" - PYTHON_VERSION: 27 - CONDA_INSTALL_LOCN: C:\\Miniconda35 + PYTHON_VERSION: 39 + CONDA_INSTALL_LOCN: C:\\Miniconda38-x64 WITH_MPFR: yes WITH_MPC: yes - BUILD_TYPE: "Release" COMPILER: MSVC15 PLATFORM: "x64" - PYTHON_VERSION: 35-x64 - CONDA_INSTALL_LOCN: C:\\Miniconda35-x64 + PYTHON_VERSION: 310-x64 + CONDA_INSTALL_LOCN: C:\\Miniconda38-x64 WITH_MPFR: yes WITH_MPC: yes - WITH_LLVM: yes - BUILD_TYPE: "Release" COMPILER: MSVC15 PLATFORM: "x64" - PYTHON_VERSION: 36-x64 - CONDA_INSTALL_LOCN: C:\\Miniconda36-x64 -# - BUILD_TYPE: "Debug" -# COMPILER: MinGW -# PYTHON_VERSION: 27 -# - BUILD_TYPE: "Release" -# COMPILER: MinGW -# PYTHON_VERSION: 35 + PYTHON_VERSION: 312-x64 + CONDA_INSTALL_LOCN: C:\\Miniconda38-x64 - BUILD_TYPE: "Release" - COMPILER: MinGW-w64 - PYTHON_VERSION: 27-x64 - - BUILD_TYPE: "Debug" - COMPILER: MinGW-w64 - PYTHON_VERSION: 35-x64 - WITH_NUMPY: no - - BUILD_TYPE: "Debug" - COMPILER: MinGW-w64 - PYTHON_VERSION: 36-x64 + COMPILER: MSVC15 + PLATFORM: "x64" + PYTHON_VERSION: 39-x64 WITH_SYMPY: no + CONDA_INSTALL_LOCN: C:\\Miniconda38-x64 + - BUILD_TYPE: "Release" + COMPILER: MSVC15 + PLATFORM: "x64" + PYTHON_VERSION: 311-x64 + WITH_NUMPY: no + CONDA_INSTALL_LOCN: C:\\Miniconda38-x64 + #- BUILD_TYPE: "Debug" + # COMPILER: MinGW-w64 + # PYTHON_VERSION: 39-x64 + # WITH_NUMPY: no + #- BUILD_TYPE: "Release" + # COMPILER: MinGW-w64 + # PYTHON_VERSION: 39-x64 + #- BUILD_TYPE: "Debug" + # COMPILER: MinGW-w64 + # PYTHON_VERSION: 39-x64 + # WITH_SYMPY: no + - BUILD_TYPE: "Release" + COMPILER: MSVC15 + PLATFORM: "x64" + PYTHON_VERSION: 310-x64 + CONDA_INSTALL_LOCN: C:\\Miniconda38-x64 + WITH_MPFR: yes + WITH_MPC: yes + WITH_LLVM: yes install: - set PYTHON_SOURCE_DIR=%CD% - git clone https://github.com/sympy/symengine symengine-cpp +- if [%PLATFORM%]==[Win32] set "CONDA_SUBDIR=win-32" - if [%COMPILER%]==[MSVC15] call %CONDA_INSTALL_LOCN%\Scripts\activate.bat -- if [%COMPILER%]==[MSVC15] conda update --yes --quiet conda -- if [%COMPILER%]==[MSVC15] conda config --add channels conda-forge -- if [%COMPILER%]==[MSVC15] if [%BUILD_TYPE%]==[Debug] conda config --add channels symengine/label/debug -- if [%COMPILER%]==[MSVC15] conda create -n test --yes mpir=3.0.0 vc=14 -- if [%COMPILER%]==[MSVC15] call activate test -- if [%COMPILER%]==[MSVC15] if [%WITH_MPFR%]==[yes] conda install --yes mpfr=3.1.5 -- if [%COMPILER%]==[MSVC15] if [%WITH_MPC%]==[yes] conda install --yes mpc=1.0.3 -- if [%COMPILER%]==[MSVC15] if [%WITH_LLVM%]==[yes] conda install --yes llvmdev=3.9 - -- if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH% +- if [%COMPILER%]==[MSVC15] set "CONDA_DEPS=mpir=3.0.0 vc=14" +- if [%COMPILER%]==[MSVC15] if [%WITH_MPFR%]==[yes] set "CONDA_DEPS=%CONDA_DEPS% mpfr=3.1.5" +- if [%COMPILER%]==[MSVC15] if [%WITH_MPC%]==[yes] set "CONDA_DEPS=%CONDA_DEPS% mpc=1.0.3" +- if [%COMPILER%]==[MSVC15] if [%WITH_LLVM%]==[yes] set "CONDA_DEPS=%CONDA_DEPS% llvmdev=4.0" +- if [%COMPILER%]==[MSVC15] set "CONDA_DEPS=%CONDA_DEPS% -c conda-forge" +- if [%COMPILER%]==[MSVC15] if [%BUILD_TYPE%]==[Debug] set "CONDA_DEPS=%CONDA_DEPS% -c symengine/label/debug" +- if [%COMPILER%]==[MSVC15] conda create -n deps --yes %CONDA_DEPS% +- if [%COMPILER%]==[MSVC15] call conda activate deps +- if [%COMPILER%]==[MSVC15] echo %CONDA_PREFIX% +- if [%COMPILER%]==[MSVC15] echo %PATH% +- if [%COMPILER%]==[MSVC15] set "PATH=%PATH%;%CONDA_PREFIX%\\Library\\bin;%CONDA_PREFIX%" +- if [%COMPILER%]==[MSVC15] echo %PATH% + +- if [%COMPILER%]==[MinGW] set "PATH=C:\MinGW\bin;%PATH%" - if [%COMPILER%]==[MinGW] mingw-get update # workaround for https://github.com/appveyor/ci/issues/996 - if [%COMPILER%]==[MinGW] mingw-get upgrade mingw32-libstdc++ - if [%COMPILER%]==[MinGW] mingw-get install mingw32-gmp -- if [%COMPILER%]==[MinGW-w64] set PATH=C:\mingw64\bin;%PATH% +- if [%COMPILER%]==[MinGW-w64] set "PATH=C:\mingw64\bin;%PATH%" - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe" @@ -79,10 +95,9 @@ install: - if NOT [%COMPILER%]==[MSVC15] call symengine-cpp\bin\appveyor-download.cmd "https://raw.githubusercontent.com/symengine/dependencies/dcc10cce2133e2b57e61c5ced6120139bbcdfa20/python-libs-mingw32.7z" -FileName pylibs.7z - if NOT [%COMPILER%]==[MSVC15] 7z x -aoa -oC:\ pylibs.7z > NUL -- set PATH=C:\Python%PYTHON_VERSION%;C:\Python%PYTHON_VERSION%\Scripts;%PATH% -- pip install nose pytest -- if [%COMPILER%]==[MinGW-w64] pip install --install-option="--no-cython-compile" cython==0.26 -- if NOT [%COMPILER%]==[MinGW-w64] pip install --install-option="--no-cython-compile" cython +- set "PATH=C:\Python%PYTHON_VERSION%;C:\Python%PYTHON_VERSION%\Scripts;%PATH%" +- echo %PATH% +- pip install nose pytest cython setuptools - if NOT [%WITH_NUMPY%]==[no] pip install numpy - if NOT [%WITH_SYMPY%]==[no] pip install sympy @@ -92,22 +107,29 @@ install: - mkdir build - cd build -- if [%COMPILER%]==[MSVC15] if [%PLATFORM%]==[Win32] cmake -G "Visual Studio 14 2015" -DCMAKE_PREFIX_PATH=%CONDA_PREFIX%\Library .. -- if [%COMPILER%]==[MSVC15] if [%PLATFORM%]==[x64] cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_PREFIX_PATH=%CONDA_PREFIX%\Library .. -- if [%COMPILER%]==[MinGW] cmake -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH=C:\MinGW -DCMAKE_BUILD_TYPE=%BUILD_TYPE% .. -- if [%COMPILER%]==[MinGW-w64] cmake -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH=C:\mingw64 -DCMAKE_BUILD_TYPE=%BUILD_TYPE% .. +- if [%COMPILER%]==[MSVC15] if [%PLATFORM%]==[Win32] set "CMAKE_GENERATOR=Visual Studio 14 2015" +- if [%COMPILER%]==[MSVC15] if [%PLATFORM%]==[x64] set "CMAKE_GENERATOR=Visual Studio 14 2015 Win64" +- if [%COMPILER%]==[MinGW] set "CMAKE_GENERATOR=MinGW Makefiles" +- if [%COMPILER%]==[MinGW-w64] set "CMAKE_GENERATOR=MinGW Makefiles" + +- if [%COMPILER%]==[MSVC15] set "CMAKE_ARGS=-DCMAKE_PREFIX_PATH=%CONDA_PREFIX%\\Library" +- if [%COMPILER%]==[MinGW] set "CMAKE_ARGS=-DCMAKE_PREFIX_PATH=C:\MinGW -DCMAKE_BUILD_TYPE=%BUILD_TYPE%" +- if [%COMPILER%]==[MinGW-w64] set "CMAKE_ARGS=-DCMAKE_PREFIX_PATH=C:\mingw64 -DCMAKE_BUILD_TYPE=%BUILD_TYPE%" -- if [%WITH_MPFR%]==[yes] cmake -DWITH_MPFR=yes .. -- if [%WITH_MPC%]==[yes] cmake -DWITH_MPC=yes .. -- if [%WITH_LLVM%]==[yes] cmake -DWITH_LLVM=yes -DMSVC_USE_MT=no .. +- if [%WITH_MPFR%]==[yes] set "CMAKE_ARGS=%CMAKE_ARGS% -DWITH_MPFR=yes" +- if [%WITH_MPC%]==[yes] set "CMAKE_ARGS=%CMAKE_ARGS% -DWITH_MPC=yes" +- if [%WITH_LLVM%]==[yes] set "CMAKE_ARGS=%CMAKE_ARGS% -DWITH_LLVM=yes -DMSVC_USE_MT=no" -- cmake -DBUILD_SHARED_LIBS=yes -DBUILD_TESTS=no -DBUILD_BENCHMARKS=no -DCMAKE_INSTALL_PREFIX=C:\symengine .. +- echo "CMAKE_ARGS=%CMAKE_ARGS%" +- cmake %CMAKE_ARGS% -DBUILD_SHARED_LIBS=yes -DBUILD_TESTS=no -DBUILD_BENCHMARKS=no -DCMAKE_INSTALL_PREFIX=C:\symengine .. - cmake --build . --config %BUILD_TYPE% --target install - cd ../../ build_script: -- set PATH=C:\symengine\bin\;%PATH% +- set "PATH=C:\symengine\bin\;%PATH%" +- set "SYMENGINE_PY_ADD_PATH_TO_SEARCH_DIRS=1" +- echo %PATH% - if [%COMPILER%]==[MSVC15] python setup.py install build_ext --compiler=msvc --build-type=%BUILD_TYPE% - if [%COMPILER%]==[MinGW] python setup.py install build_ext --compiler=mingw --inplace - if [%COMPILER%]==[MinGW-w64] python setup.py install build_ext --compiler=mingw --inplace @@ -120,5 +142,5 @@ test_script: # Enable this to be able to login to the build worker. You can use the # `remmina` program in Ubuntu, use the login information that the line below # prints into the log. -#on_finish: +# #on_finish: #- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/benchmarks/expand1.py b/benchmarks/expand1.py index 881dd4e92..fb910b31b 100644 --- a/benchmarks/expand1.py +++ b/benchmarks/expand1.py @@ -7,4 +7,4 @@ t1 = clock() g = e.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand1_sage.py b/benchmarks/expand1_sage.py index 9d1933153..a7b14ede7 100644 --- a/benchmarks/expand1_sage.py +++ b/benchmarks/expand1_sage.py @@ -5,4 +5,4 @@ t1 = clock() g = e.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand2_sage.py b/benchmarks/expand2_sage.py index fe2ebb673..f29f55ddd 100644 --- a/benchmarks/expand2_sage.py +++ b/benchmarks/expand2_sage.py @@ -3,8 +3,8 @@ var("x y z w") e = (x+y+z+w)**15 f = e*(e+w) -print f +print(f) t1 = clock() g = f.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand2b_sympy.py b/benchmarks/expand2b_sympy.py index 28c585e9c..43dcd77b5 100644 --- a/benchmarks/expand2b_sympy.py +++ b/benchmarks/expand2b_sympy.py @@ -6,5 +6,5 @@ f = e*(e+w) t2 = clock() #print f -print "Total time:", t2-t1, "s" -print "number of terms:", len(f) +print("Total time:", t2-t1, "s") +print("number of terms:", len(f)) diff --git a/benchmarks/expand3.py b/benchmarks/expand3.py index 7288f3517..18f0a9cb8 100644 --- a/benchmarks/expand3.py +++ b/benchmarks/expand3.py @@ -4,8 +4,8 @@ from symengine import var var("x y z") f = (x**y + y**z + z**x)**100 -print f +print(f) t1 = clock() g = f.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand3_sage.py b/benchmarks/expand3_sage.py index 85587001a..75872fa4b 100644 --- a/benchmarks/expand3_sage.py +++ b/benchmarks/expand3_sage.py @@ -2,8 +2,8 @@ from sage.all import var var("x y z") f = (x**y + y**z + z**x)**100 -print f +print(f) t1 = clock() g = f.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand4.py b/benchmarks/expand4.py index 3ff7b7d02..1bcdc6e7f 100644 --- a/benchmarks/expand4.py +++ b/benchmarks/expand4.py @@ -6,8 +6,7 @@ e = 1 for i in range(1, 351): e *= (i+x)**3 -#print e t1 = clock() f = e.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand4_sage.py b/benchmarks/expand4_sage.py index 0b900538e..961f5d3b5 100644 --- a/benchmarks/expand4_sage.py +++ b/benchmarks/expand4_sage.py @@ -1,13 +1,13 @@ -print "import..." +print("import...") from timeit import default_timer as clock from sage.all import var var("x") e = 1 -print "constructing expression..." +print("constructing expression...") for i in range(1, 351): e *= (i+x)**3 -print "running benchmark..." +print("running benchmark...") t1 = clock() f = e.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand5.py b/benchmarks/expand5.py index 047b17bfd..25ab7e7a3 100644 --- a/benchmarks/expand5.py +++ b/benchmarks/expand5.py @@ -5,8 +5,8 @@ var("x y z") e = (x+y+z+1)**15 f = e*(e+1) -print f +print(f) t1 = clock() g = f.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/expand5_sage.py b/benchmarks/expand5_sage.py index 2153a1d46..8fb7ab455 100644 --- a/benchmarks/expand5_sage.py +++ b/benchmarks/expand5_sage.py @@ -3,8 +3,8 @@ var("x y z") e = (x+y+z+1)**15 f = e*(e+1) -print f +print(f) t1 = clock() g = f.expand() t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/heterogeneous_output_Lambdify.py b/benchmarks/heterogeneous_output_Lambdify.py index 7c493c0c8..f54d7ab1b 100644 --- a/benchmarks/heterogeneous_output_Lambdify.py +++ b/benchmarks/heterogeneous_output_Lambdify.py @@ -9,7 +9,7 @@ import warnings src = os.path.join(os.path.dirname(__file__), '6_links_rhs.txt') -serial = open(src, 'tr').read() +serial = open(src).read() parsed = parse_expr(serial, transformations=standard_transformations) vec = sp.Matrix(1, 14, parsed) args = tuple(sorted(vec.free_symbols, key=lambda arg: arg.name)) diff --git a/benchmarks/kane.py b/benchmarks/kane.py index 4a624b234..96d5fb66b 100644 --- a/benchmarks/kane.py +++ b/benchmarks/kane.py @@ -4,29 +4,29 @@ from symengine import var, sympify, function_symbol, Symbol import sympy s = open("expr.txt").read() -print "Converting to SymPy..." +printr("Converting to SymPy...") e = sympy.sympify(s) -print "Converting to SymEngine..." +print("Converting to SymEngine...") ce = sympify(e) -print " Done." -print "SymPy subs:" +print(" Done.") +print("SymPy subs:") t1 = clock() f = e.subs(sympy.Function("q5")(sympy.Symbol("t")), sympy.Symbol("sq5")) t2 = clock() -print "Total time:", t2-t1, "s" -print "SymEngine subs:" +print("Total time:", t2-t1, "s") +print("SymEngine subs:") t1 = clock() cf = ce.subs(function_symbol("q5", Symbol("t")), Symbol("sq5")) t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") -print "SymPy diff:" +print("SymPy diff:") t1 = clock() g = f.diff(sympy.Symbol("sq5")) t2 = clock() -print "Total time:", t2-t1, "s" -print "SymEngine diff:" +print("Total time:", t2-t1, "s") +print("SymEngine diff:") t1 = clock() cg = cf.diff(Symbol("sq5")) t2 = clock() -print "Total time:", t2-t1, "s" +print("Total time:", t2-t1, "s") diff --git a/benchmarks/kane_generate.py b/benchmarks/kane_generate.py index e849c1af2..b3c81284f 100644 --- a/benchmarks/kane_generate.py +++ b/benchmarks/kane_generate.py @@ -188,11 +188,11 @@ def test_bicycle(): PaperForkCgZ = 0.7 FrameLength = evalf.N(PaperWb*sin(HTA)-(rake-(PaperRadFront-PaperRadRear)*cos(HTA))) FrameCGNorm = evalf.N((PaperFrameCgZ - PaperRadRear-(PaperFrameCgX/sin(HTA))*cos(HTA))*sin(HTA)) - FrameCGPar = evalf.N((PaperFrameCgX / sin(HTA) + (PaperFrameCgZ - PaperRadRear - PaperFrameCgX / sin(HTA) * cos(HTA)) * cos(HTA))) - tempa = evalf.N((PaperForkCgZ - PaperRadFront)) - tempb = evalf.N((PaperWb-PaperForkCgX)) + FrameCGPar = evalf.N(PaperFrameCgX / sin(HTA) + (PaperFrameCgZ - PaperRadRear - PaperFrameCgX / sin(HTA) * cos(HTA)) * cos(HTA)) + tempa = evalf.N(PaperForkCgZ - PaperRadFront) + tempb = evalf.N(PaperWb-PaperForkCgX) tempc = evalf.N(sqrt(tempa**2+tempb**2)) - PaperForkL = evalf.N((PaperWb*cos(HTA)-(PaperRadFront-PaperRadRear)*sin(HTA))) + PaperForkL = evalf.N(PaperWb*cos(HTA)-(PaperRadFront-PaperRadRear)*sin(HTA)) ForkCGNorm = evalf.N(rake+(tempc * sin(pi/2-HTA-acos(tempa/tempc)))) ForkCGPar = evalf.N(tempc * cos((pi/2-HTA)-acos(tempa/tempc))-PaperForkL) @@ -253,7 +253,7 @@ def test_bicycle(): #import symengine #print "Converting to symengine..." #f = symengine.sympify(e) - print "Saving to expr.txt" + print("Saving to expr.txt") s = str(e) open("expr.txt", "w").write(s) return diff --git a/benchmarks/legendre1.py b/benchmarks/legendre1.py index 490b1f53b..6fa307397 100644 --- a/benchmarks/legendre1.py +++ b/benchmarks/legendre1.py @@ -20,9 +20,9 @@ def legendre(n, x): var("x") for n in range(10): - print n, legendre(n, x) + print(n, legendre(n, x)) t1 = clock() e = legendre(500, x) t2 = clock() -print "Total time for legendre(500, x):", t2-t1, "s" +print("Total time for legendre(500, x):", t2-t1, "s") diff --git a/benchmarks/legendre1_sage.py b/benchmarks/legendre1_sage.py index 1647dedc9..4cddc8436 100644 --- a/benchmarks/legendre1_sage.py +++ b/benchmarks/legendre1_sage.py @@ -1,7 +1,7 @@ -print "import..." +print("import...") from timeit import default_timer as clock from sage.all import var, Integer -print " done." +print(" done.") def fact(n): if n in [0, 1]: @@ -20,9 +20,9 @@ def legendre(n, x): var("x") for n in range(10): - print n, legendre(n, x) + print(n, legendre(n, x)) t1 = clock() e = legendre(500, x) t2 = clock() -print "Total time for legendre(500, x):", t2-t1, "s" +print("Total time for legendre(500, x):", t2-t1, "s") diff --git a/bin/install_travis.sh b/bin/install_travis.sh index 5b2bdf93f..f63a47479 100644 --- a/bin/install_travis.sh +++ b/bin/install_travis.sh @@ -2,11 +2,7 @@ # symengine's bin/install_travis.sh will install miniconda -export conda_pkgs="python=${PYTHON_VERSION} pip cython pytest gmp mpfr" - -if [[ "${WITH_SYMPY}" != "no" ]]; then - export conda_pkgs="${conda_pkgs} sympy"; -fi +export conda_pkgs="python=${PYTHON_VERSION} pip pytest setuptools gmp mpfr" if [[ "${WITH_NUMPY}" != "no" ]]; then export conda_pkgs="${conda_pkgs} numpy"; @@ -16,6 +12,14 @@ if [[ "${WITH_SCIPY}" == "yes" ]]; then export conda_pkgs="${conda_pkgs} scipy"; fi +if [[ "${WITH_DOCS}" == "yes" ]]; then + export conda_pkgs="${conda_pkgs} sphinx recommonmark"; +fi + +if [[ "${WITH_FLINT_PY}" == "yes" ]]; then + export conda_pkgs="${conda_pkgs} python-flint"; # python-flint affects sympy, see e.g. sympy/sympy#26645 +fi + if [[ "${WITH_SAGE}" == "yes" ]]; then # This is split to avoid the 10 minute limit conda install -q sagelib=8.1 @@ -23,6 +27,10 @@ if [[ "${WITH_SAGE}" == "yes" ]]; then export conda_pkgs="${conda_pkgs} sage=8.1"; fi -conda install -q ${conda_pkgs} -conda clean --all -source activate $our_install_dir; +conda install -q ${conda_pkgs} "cython>=0.29.24" + +if [[ "${WITH_SYMPY}" != "no" ]]; then + pip install sympy; +fi + +conda clean --all \ No newline at end of file diff --git a/bin/test_symengine_unix.sh b/bin/test_symengine_unix.sh new file mode 100644 index 000000000..0c62b7d14 --- /dev/null +++ b/bin/test_symengine_unix.sh @@ -0,0 +1,22 @@ +export PYTHON_SOURCE_DIR=`pwd` +export TEST_CPP="no" +export MAKEFLAGS="-j2" + +git clone https://github.com/symengine/symengine symengine-cpp +cd symengine-cpp +export SOURCE_DIR=`pwd` +git checkout `cat ../symengine_version.txt` +cd .. + +# Setup travis for C++ library +cd $SOURCE_DIR +source bin/test_symengine.sh + +# Setup travis for Python wrappers +cd $PYTHON_SOURCE_DIR +source bin/install_travis.sh + +# Build Python wrappers and test +cd $PYTHON_SOURCE_DIR +bin/test_travis.sh + diff --git a/bin/test_travis.sh b/bin/test_travis.sh index 74dabbaab..d83b27db5 100755 --- a/bin/test_travis.sh +++ b/bin/test_travis.sh @@ -5,17 +5,17 @@ set -e # Echo each command set -x +python setup.py sdist +mkdir dist-extract +cd dist-extract +tar -xvf ../dist/symengine-*.tar.gz +cd symengine-* + # Build inplace so that nosetests can be run inside source directory -python setup.py install build_ext --inplace --symengine-dir=$our_install_dir +python3 setup.py install build_ext --inplace --symengine-dir=$our_install_dir # Test python wrappers -py.test -s -v $PYTHON_SOURCE_DIR/symengine/tests/test_*.py +python3 -m pip install pytest +python3 -m pytest -s -v $PWD/symengine/tests/test_*.py mkdir -p empty && cd empty -python $PYTHON_SOURCE_DIR/bin/test_python.py -cd .. - -if [[ "${TRIGGER_FEEDSTOCK}" == "yes" ]]; then - cd $PYTHON_SOURCE_DIR - ./bin/trigger_feedstock.sh -fi - +python3 $PYTHON_SOURCE_DIR/bin/test_python.py diff --git a/cmake/FindCython.cmake b/cmake/FindCython.cmake index 8d9c47316..beac6c568 100644 --- a/cmake/FindCython.cmake +++ b/cmake/FindCython.cmake @@ -4,8 +4,9 @@ # This finds the "cython" executable in your PATH, and then in some standard # paths: -SET(CYTHON_BIN cython CACHE STRING "Cython executable name") -SET(CYTHON_FLAGS --cplus --fast-fail) + +find_program(CYTHON_BIN NAMES cython cython3 cython2) +SET(CYTHON_FLAGS --cplus --fast-fail -3) SET(Cython_FOUND FALSE) IF (CYTHON_BIN) @@ -13,8 +14,8 @@ IF (CYTHON_BIN) execute_process( COMMAND ${CYTHON_BIN} ${CYTHON_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cython_test.pyx RESULT_VARIABLE CYTHON_RESULT - OUTPUT_QUIET - ERROR_QUIET + OUTPUT_VARIABLE CYTHON_OUTPUT + ERROR_VARIABLE CYTHON_ERROR ) if (CYTHON_RESULT EQUAL 0) # Only if cython exits with the return code 0, we know that all is ok: @@ -27,21 +28,26 @@ ENDIF (CYTHON_BIN) IF (Cython_FOUND) - IF (NOT Cython_FIND_QUIETLY) - MESSAGE(STATUS "Found CYTHON: ${CYTHON_BIN}") - ENDIF (NOT Cython_FIND_QUIETLY) + IF (NOT Cython_FIND_QUIETLY) + MESSAGE(STATUS "Found CYTHON: ${CYTHON_BIN}") + ENDIF (NOT Cython_FIND_QUIETLY) ELSE (Cython_FOUND) - IF (Cython_FIND_REQUIRED) + IF (Cython_FIND_REQUIRED) if(Cython_Compilation_Failed) MESSAGE(STATUS "Found CYTHON: ${CYTHON_BIN}") - # On Win the testing of Cython does not return any accessible value, so the test is not carried out. Fresh Cython install was tested and works. - IF(NOT MSVC) - MESSAGE(FATAL_ERROR "Your Cython version is too old. Please upgrade Cython.") - ENDIF(NOT MSVC) + # On Win the testing of Cython does not return any accessible value, so the test is not carried out. + # Fresh Cython install was tested and works. + IF(NOT MSVC) + MESSAGE(FATAL_ERROR + "Your Cython version is too old. Please upgrade Cython." + "STDOUT: ${CYTHON_OUTPUT}" + "STDERROR: ${CYTHON_ERROR}" + ) + ENDIF(NOT MSVC) else(Cython_Compilation_Failed) MESSAGE(FATAL_ERROR "Could not find Cython. Please install Cython.") endif(Cython_Compilation_Failed) - ENDIF (Cython_FIND_REQUIRED) + ENDIF (Cython_FIND_REQUIRED) ENDIF (Cython_FOUND) @@ -57,26 +63,13 @@ if(NOT CYTHON_INCLUDE_DIRECTORIES) endif(NOT CYTHON_INCLUDE_DIRECTORIES) # Cythonizes the .pyx files into .cpp file (but doesn't compile it) -macro(CYTHON_ADD_MODULE_PYX name) - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.pxd) - set(DEPENDS ${name}.pyx ${name}.pxd) - else(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.pxd) - set(DEPENDS ${name}.pyx) - endif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.pxd) +macro(CYTHON_ADD_MODULE_PYX cpp_name pyx_name) # Allow the user to specify dependencies as optional arguments set(DEPENDS ${DEPENDS} ${ARGN}) add_custom_command( - OUTPUT ${name}.cpp + OUTPUT ${cpp_name} COMMAND ${CYTHON_BIN} - ARGS ${CYTHON_FLAGS} -I ${CYTHON_INCLUDE_DIRECTORIES} -o ${name}.cpp ${CMAKE_CURRENT_SOURCE_DIR}/${name}.pyx - DEPENDS ${DEPENDS} - COMMENT "Cythonizing ${name}.pyx") + ARGS ${CYTHON_FLAGS} -I ${CYTHON_INCLUDE_DIRECTORIES} -o ${cpp_name} ${pyx_name} + DEPENDS ${DEPENDS} ${pyx_name} + COMMENT "Cythonizing ${pyx_name}") endmacro(CYTHON_ADD_MODULE_PYX) - -# Cythonizes and compiles a .pyx file -macro(CYTHON_ADD_MODULE name) - CYTHON_ADD_MODULE_PYX(${name}) - # We need Python for this: - find_package(Python REQUIRED) - add_python_library(${name} ${name}.cpp ${ARGN}) -endmacro(CYTHON_ADD_MODULE) diff --git a/cmake/FindPython.cmake b/cmake/FindPython.cmake index a74ef4ac0..c1f6c4396 100644 --- a/cmake/FindPython.cmake +++ b/cmake/FindPython.cmake @@ -1,7 +1,7 @@ set(PYTHON_BIN python CACHE STRING "Python executable name") execute_process( - COMMAND ${PYTHON_BIN} -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())" + COMMAND ${PYTHON_BIN} -c "from sysconfig import get_paths; print(get_paths()['include'])" OUTPUT_VARIABLE PYTHON_SYS_PATH ) string(STRIP ${PYTHON_SYS_PATH} PYTHON_SYS_PATH) @@ -16,63 +16,74 @@ set(PYTHON_INSTALL_HEADER_PATH ${PYTHON_INCLUDE_PATH}/symengine CACHE BOOL "Python install headers path") execute_process( - COMMAND ${PYTHON_BIN} -c "from distutils.sysconfig import get_config_var; print(get_config_var('LIBDIR'))" + COMMAND ${PYTHON_BIN} -c "from sysconfig import get_config_var; print(get_config_var('LIBDIR'))" OUTPUT_VARIABLE PYTHON_LIB_PATH ) string(STRIP ${PYTHON_LIB_PATH} PYTHON_LIB_PATH) execute_process( - COMMAND ${PYTHON_BIN} -c "import sys; print(sys.prefix)" - OUTPUT_VARIABLE PYTHON_PREFIX_PATH - ) + COMMAND ${PYTHON_BIN} -c "import sys; print(sys.prefix)" + OUTPUT_VARIABLE PYTHON_PREFIX_PATH +) string(STRIP ${PYTHON_PREFIX_PATH} PYTHON_PREFIX_PATH) execute_process( - COMMAND ${PYTHON_BIN} -c "import sys; print('%s.%s' % sys.version_info[:2])" + COMMAND ${PYTHON_BIN} -c "import sys; print('%s.%s' % sys.version_info[:2])" OUTPUT_VARIABLE PYTHON_VERSION - ) +) string(STRIP ${PYTHON_VERSION} PYTHON_VERSION) message(STATUS "Python version: ${PYTHON_VERSION}") string(REPLACE "." "" PYTHON_VERSION_WITHOUT_DOTS ${PYTHON_VERSION}) -FIND_LIBRARY(PYTHON_LIBRARY NAMES - python${PYTHON_VERSION} +execute_process( + COMMAND ${PYTHON_BIN} -c "import sysconfig;print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))" + OUTPUT_VARIABLE PY_GIL_DISABLED +) +string(STRIP ${PY_GIL_DISABLED} PY_GIL_DISABLED) + +if ("${PY_GIL_DISABLED}" STREQUAL "True") + set (PY_THREAD "t") +endif() + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + FIND_LIBRARY(PYTHON_LIBRARY NAMES + python${PYTHON_VERSION}${PY_THREAD} python${PYTHON_VERSION}m - python${PYTHON_VERSION_WITHOUT_DOTS} - PATHS ${PYTHON_LIB_PATH} ${PYTHON_PREFIX_PATH}/lib ${PYTHON_PREFIX_PATH}/libs - PATH_SUFFIXES ${CMAKE_LIBRARY_ARCHITECTURE} - NO_DEFAULT_PATH - NO_SYSTEM_ENVIRONMENT_PATH - ) + python${PYTHON_VERSION_WITHOUT_DOTS}${PY_THREAD} + PATHS ${PYTHON_LIB_PATH} ${PYTHON_PREFIX_PATH}/lib ${PYTHON_PREFIX_PATH}/libs + PATH_SUFFIXES ${CMAKE_LIBRARY_ARCHITECTURE} + NO_DEFAULT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + ) +endif() execute_process( - COMMAND ${PYTHON_BIN} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" - OUTPUT_VARIABLE PYTHON_INSTALL_PATH_tmp - ) + COMMAND ${PYTHON_BIN} -c "from sysconfig import get_paths; print(get_paths()['platlib'])" + OUTPUT_VARIABLE PYTHON_INSTALL_PATH_tmp +) string(STRIP ${PYTHON_INSTALL_PATH_tmp} PYTHON_INSTALL_PATH_tmp) set(PYTHON_INSTALL_PATH ${PYTHON_INSTALL_PATH_tmp} CACHE BOOL "Python install path") message(STATUS "Python install path: ${PYTHON_INSTALL_PATH}") -if (NOT WIN32) - execute_process( - COMMAND ${PYTHON_BIN} -c "from distutils.sysconfig import get_config_var; print(get_config_var('SOABI'))" - OUTPUT_VARIABLE PYTHON_EXTENSION_SOABI_tmp - ) - string(STRIP ${PYTHON_EXTENSION_SOABI_tmp} PYTHON_EXTENSION_SOABI_tmp) - if (NOT "${PYTHON_EXTENSION_SOABI_tmp}" STREQUAL "None") - set(PYTHON_EXTENSION_SOABI_tmp ".${PYTHON_EXTENSION_SOABI_tmp}") - else() - set(PYTHON_EXTENSION_SOABI_tmp "") - endif() -endif() +execute_process( + COMMAND ${PYTHON_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/get_suffix.py + OUTPUT_VARIABLE PYTHON_EXTENSION_SOABI_tmp +) +string(STRIP ${PYTHON_EXTENSION_SOABI_tmp} PYTHON_EXTENSION_SOABI_tmp) + set(PYTHON_EXTENSION_SOABI ${PYTHON_EXTENSION_SOABI_tmp} CACHE STRING "Suffix for python extensions") INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(PYTHON DEFAULT_MSG PYTHON_LIBRARY PYTHON_INCLUDE_PATH PYTHON_INSTALL_PATH) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Python DEFAULT_MSG PYTHON_LIBRARY PYTHON_INCLUDE_PATH PYTHON_INSTALL_PATH) +else () + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Python DEFAULT_MSG PYTHON_INCLUDE_PATH PYTHON_INSTALL_PATH) +endif () # Links a Python extension module. @@ -108,19 +119,29 @@ macro(ADD_PYTHON_LIBRARY name) # on Mac, we need to use the "-bundle" gcc flag, which is what MODULE # does: add_library(${name} MODULE ${ARGN}) - # and "-flat_namespace -undefined suppress" link flags, that we need - # to add by hand: - set_target_properties(${name} PROPERTIES - LINK_FLAGS "-flat_namespace -undefined suppress") - ELSE(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # and "-undefined dynamic_lookup" link flags, that we need to add by hand: + set_property(TARGET ${name} APPEND_STRING PROPERTY + LINK_FLAGS " -undefined dynamic_lookup -Wl,-exported_symbol,_PyInit_${name}") + ELSEIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") # on Linux, we need to use the "-shared" gcc flag, which is what SHARED # does: + set(PYTHON_EXTENSION_NAME ${name}) + add_library(${name} SHARED ${ARGN}) + configure_file(${CMAKE_SOURCE_DIR}/cmake/version_script.txt + ${CMAKE_CURRENT_BINARY_DIR}/version_script_${name}.txt @ONLY) + set_property(TARGET ${name} APPEND_STRING PROPERTY + LINK_FLAGS " \"-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/version_script_${name}.txt\"") + ELSE() add_library(${name} SHARED ${ARGN}) - ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + ENDIF() set_target_properties(${name} PROPERTIES PREFIX "") set_target_properties(${name} PROPERTIES OUTPUT_NAME "${name}${PYTHON_EXTENSION_SOABI}") IF(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") target_link_libraries(${name} ${PYTHON_LIBRARY}) set_target_properties(${name} PROPERTIES SUFFIX ".pyd") + IF("${PY_GIL_DISABLED}" STREQUAL "True") + target_compile_definitions(${name} PRIVATE Py_GIL_DISABLED=1) + ENDIF() ENDIF() + endmacro(ADD_PYTHON_LIBRARY) diff --git a/cmake/cython_test.pyx b/cmake/cython_test.pyx index 22cdb17c6..e97be0b43 100644 --- a/cmake/cython_test.pyx +++ b/cmake/cython_test.pyx @@ -1,6 +1,3 @@ -# Test that numpy works in Cython: -from numpy cimport ndarray - # Test that libcpp module is present: from libcpp.vector cimport vector from libcpp.string cimport string @@ -78,8 +75,8 @@ cdef extern from "" namespace "SymEngine": string get_name() nogil cdef extern from "" namespace "SymEngine": - cdef RCP[Basic] add(RCP[Basic] &a, RCP[Basic] &b) nogil except+ - cdef RCP[Basic] sub(RCP[Basic] &a, RCP[Basic] &b) nogil except+ + cdef RCP[Basic] add(RCP[Basic] &a, RCP[Basic] &b) nogil except + + cdef RCP[Basic] sub(RCP[Basic] &a, RCP[Basic] &b) nogil except + cdef cppclass Add(Basic): void as_two_terms(const Ptr[RCP[Basic]] &a, const Ptr[RCP[Basic]] &b) diff --git a/cmake/get_suffix.py b/cmake/get_suffix.py new file mode 100644 index 000000000..42470fce5 --- /dev/null +++ b/cmake/get_suffix.py @@ -0,0 +1,6 @@ +from sysconfig import get_config_var +extsuffix = get_config_var('EXT_SUFFIX') +if extsuffix is None: + print("") +else: + print(extsuffix[0:].rsplit(".", 1)[0]) diff --git a/cmake/preprocess.py b/cmake/preprocess.py new file mode 100644 index 000000000..a99d24898 --- /dev/null +++ b/cmake/preprocess.py @@ -0,0 +1,38 @@ +import sys + + +def main(input_name, output_name, replacements): + replacements = dict((item.split("=")[0], item.split("=")[1] == "True") for item in replacements) + with open(input_name, "r") as inp: + text = inp.readlines() + + new_text = [] + in_cond = [True] + nspaces = [0] + for i, line in enumerate(text): + if line.strip().startswith("IF"): + s = len(line) - len(line.lstrip()) + while s <= nspaces[-1] and len(in_cond) > 1: + in_cond = in_cond[:-1] + nspaces = nspaces[:-1] + + cond = line.lstrip()[3:-2] + in_cond.append(replacements[cond]) + nspaces.append(s) + elif line.strip().startswith("ELSE"): + in_cond[-1] = not in_cond[-1] and in_cond[-2] + + if len(line) > 1 and not line.strip().startswith(("IF", "ELSE")): + while len(in_cond) > 1 and (len(line) <= nspaces[-1] or not line.startswith(" "*nspaces[-1]) or line[nspaces[-1]] != " "): + in_cond = in_cond[:-1] + nspaces = nspaces[:-1] + if len(line) == 1: + new_text.append(line) + elif in_cond[-1] and not line.strip().startswith(("IF", "ELSE")): + new_text.append(line[4*(len(in_cond) - 1):]) + + with open(output_name, "w") as out: + out.writelines(new_text) + +if __name__ == "__main__": + main(sys.argv[1], sys.argv[2], sys.argv[3:]) diff --git a/cmake/version_script.txt b/cmake/version_script.txt new file mode 100644 index 000000000..0b24ad9fa --- /dev/null +++ b/cmake/version_script.txt @@ -0,0 +1,4 @@ +{ + global: PyInit_@PYTHON_EXTENSION_NAME@; + local: *; +}; diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..8643d4630 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,90 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + +import symengine + +# -- Project information ----------------------------------------------------- + +project = 'symengine' +copyright = '2021, SymEngine development team ' +author = 'SymEngine development team ' + +# The full version, including alpha/beta/rc tags +release = symengine.__version__ + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", # Consumes docstrings + "sphinx.ext.napoleon", # Allows for Google Style Docs + "sphinx.ext.viewcode", # Links to source code + "sphinx.ext.intersphinx", # Connects to other documentation + "sphinx.ext.todo", # Show TODO details + "sphinx.ext.imgconverter", # Handle svg images + "sphinx.ext.duration", # Shows times in the processing pipeline + "sphinx.ext.mathjax", # Need math support + "sphinx.ext.githubpages", # Puts the .nojekyll and CNAME files + "sphinxcontrib.apidoc", # Automatically sets up sphinx-apidoc + # "recommonmark", # Parses markdown + "m2r2", # Parses markdown in rst +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# API Doc settings +apidoc_module_dir = "../" +apidoc_output_dir = "source" +apidoc_excluded_paths = ["tests"] +apidoc_separate_modules = True + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_book_theme" +html_title = "Symengine Python Bindings" +# html_logo = "path/to/logo.png" +# html_favicon = "path/to/favicon.ico" +html_theme_options = { + "repository_url": "https://github.com/symengine/symengine.py", + "use_repository_button": True, + "use_issues_button": True, + "use_edit_page_button": True, + "path_to_docs": "docs", + "use_download_button": True, + "home_page_in_toc": True +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..9a47bd0a4 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +.. symengine documentation master file, created by + sphinx-quickstart on Tue Jan 26 09:42:30 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Symengine Python API Documentation +=================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + source/modules + +.. mdinclude:: ../README.md + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/setup.py b/setup.py index eb8ec3747..23d948ef6 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,12 @@ -from __future__ import print_function from os import getenv, path, makedirs import os import subprocess import sys -from distutils.command.build_ext import build_ext as _build_ext -from distutils.command.build import build as _build +import platform # Make sure the system has the right Python version. -if sys.version_info[:2] < (2, 7): - print("SymEngine requires Python 2.7 or newer. " +if sys.version_info[:2] < (3, 9): + print("SymEngine requires Python 3.9 or newer. " "Python %d.%d detected" % sys.version_info[:2]) sys.exit(-1) @@ -28,12 +26,20 @@ try: from setuptools import setup from setuptools.command.install import install as _install + from setuptools.command.build_ext import build_ext as _build_ext except ImportError: use_setuptools = False + else: + try: + from setuptools.command.build import build as _build + except ImportError: + from distutils.command.build import build as _build if not use_setuptools: from distutils.core import setup from distutils.command.install import install as _install + from distutils.command.build_ext import build_ext as _build_ext + from distutils.command.build import build as _build cmake_opts = [("PYTHON_BIN", sys.executable), ("CMAKE_INSTALL_RPATH_USE_LINK_PATH", "yes")] @@ -110,40 +116,36 @@ def cmake_build(self): os.remove("CMakeCache.txt") cmake_cmd = ["cmake", source_dir, - "-DCMAKE_BUILD_TYPE=" + cmake_build_type[0]] + "-DCMAKE_BUILD_TYPE=" + cmake_build_type[0], + "-DSYMENGINE_INSTALL_PY_FILES=ON", + ] cmake_cmd.extend(process_opts(cmake_opts)) if not path.exists(path.join(build_dir, "CMakeCache.txt")): cmake_cmd.extend(self.get_generator()) if subprocess.call(cmake_cmd, cwd=build_dir) != 0: - raise EnvironmentError("error calling cmake") + raise OSError("error calling cmake") if subprocess.call(["cmake", "--build", ".", "--config", cmake_build_type[0]], cwd=build_dir) != 0: - raise EnvironmentError("error building project") + raise OSError("error building project") def get_generator(self): if cmake_generator[0]: return ["-G", cmake_generator[0]] + elif "CMAKE_GENERATOR" not in os.environ and platform.system() == "Windows": + compiler = str(self.compiler).lower() + if ("msys" in compiler): + return ["-G", "MSYS Makefiles"] + elif ("mingw" in compiler): + return ["-G", "MinGW Makefiles"] + else: + return ["-G", "NMake Makefiles"] else: - import platform - import sys - if (platform.system() == "Windows"): - compiler = str(self.compiler).lower() - if ("msys" in compiler): - return ["-G", "MSYS Makefiles"] - elif ("mingw" in compiler): - return ["-G", "MinGW Makefiles"] - elif sys.maxsize > 2**32: - return ["-G", "Visual Studio 14 2015 Win64"] - else: - return ["-G", "Visual Studio 14 2015"] return [] def run(self): self.cmake_build() - # can't use super() here because - # _build_ext is an old style class in 2.7 _build_ext.run(self) @@ -168,8 +170,6 @@ def finalize_options(self): cmake_opts.extend(self.define) cmake_build_type[0] = self.build_type cmake_opts.extend([('PYTHON_INSTALL_PATH', path.join(os.getcwd(), self.install_platlib))]) - #cmake_opts.extend([('PYTHON_INSTALL_HEADER_PATH', - # path.join(os.getcwd(), self.install_headers))]) def cmake_install(self): source_dir = path.dirname(path.realpath(__file__)) @@ -180,19 +180,18 @@ def cmake_install(self): # CMake has to be called here to update PYTHON_INSTALL_PATH # if build and install were called separately by the user if subprocess.call(cmake_cmd, cwd=build_dir) != 0: - raise EnvironmentError("error calling cmake") + raise OSError("error calling cmake") if subprocess.call(["cmake", "--build", ".", "--config", cmake_build_type[0], "--target", "install"], cwd=build_dir) != 0: - raise EnvironmentError("error installing") + raise OSError("error installing") import compileall compileall.compile_dir(path.join(self.install_platlib, "symengine")) def run(self): - # can't use super() here because _install is an old style class in 2.7 _install.run(self) self.cmake_install() @@ -223,16 +222,17 @@ def finalize_options(self): ''' setup(name="symengine", - version="0.6.1", + version="0.14.1", description="Python library providing wrappers to SymEngine", - setup_requires=['cython>=0.19.1'], + setup_requires=['cython>=0.29.24', 'setuptools'], long_description=long_description, author="SymEngine development team", author_email="symengine@googlegroups.com", license="MIT", url="https://github.com/symengine/symengine.py", - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,<4', + python_requires='>=3.9,<4', zip_safe=False, + packages=[], cmdclass = cmdclass, classifiers=[ 'License :: OSI Approved :: MIT License', @@ -241,10 +241,10 @@ def finalize_options(self): 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Mathematics', 'Topic :: Scientific/Engineering :: Physics', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ] ) diff --git a/symengine/CMakeLists.txt b/symengine/CMakeLists.txt index 224d1c99e..bedec397d 100644 --- a/symengine/CMakeLists.txt +++ b/symengine/CMakeLists.txt @@ -1,7 +1,17 @@ add_subdirectory(lib) -add_subdirectory(tests) -set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine) -install(FILES __init__.py utilities.py compatibility.py sympy_compat.py functions.py printing.py - DESTINATION ${PY_PATH} - ) +if (SYMENGINE_INSTALL_PY_FILES) + add_subdirectory(tests) + set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine) + install( + FILES + __init__.py + functions.py + printing.py + sympy_compat.py + test_utilities.py + utilities.py + DESTINATION + ${PY_PATH} +) +endif () diff --git a/symengine/__init__.py b/symengine/__init__.py index d8e78f31f..e9545baf6 100644 --- a/symengine/__init__.py +++ b/symengine/__init__.py @@ -1,3 +1,16 @@ +import os +import sys + +if sys.platform == 'win32' \ + and 'SYMENGINE_PY_ADD_PATH_TO_SEARCH_DIRS' in os.environ: + for directory in os.environ['PATH'].split(';'): + if os.path.isdir(directory): + os.add_dll_directory(directory) + +del os, sys + +import symengine.lib.symengine_wrapper as wrapper + from .lib.symengine_wrapper import ( have_mpfr, have_mpc, have_flint, have_piranha, have_llvm, have_llvm_long_double, I, E, pi, oo, zoo, nan, Symbol, Dummy, S, sympify, SympifyError, @@ -7,21 +20,31 @@ ImmutableMatrix, ImmutableDenseMatrix, MutableDenseMatrix, MatrixBase, Basic, DictBasic, symarray, series, diff, zeros, eye, diag, ones, Derivative, Subs, expand, has_symbol, - UndefFunction, Function, latex, + UndefFunction, Function, UnevaluatedExpr, latex, have_numpy, true, false, Equality, Unequality, GreaterThan, LessThan, StrictGreaterThan, StrictLessThan, Eq, Ne, Ge, Le, Gt, Lt, And, Or, Not, Nand, Nor, Xor, Xnor, perfect_power, integer_nthroot, isprime, sqrt_mod, Expr, cse, count_ops, ccode, Piecewise, Contains, Interval, FiniteSet, - EmptySet, linsolve, - FunctionSymbol as AppliedUndef, + linsolve, + FunctionSymbol, golden_ratio as GoldenRatio, catalan as Catalan, - eulergamma as EulerGamma + eulergamma as EulerGamma, + unicode ) from .utilities import var, symbols from .functions import * from .printing import init_printing + +AppliedUndef = FunctionSymbol # an alias +EmptySet = wrapper.S.EmptySet +UniversalSet = wrapper.S.UniversalSet +Reals = wrapper.S.Reals +Integers = wrapper.S.Integers +Rationals = wrapper.S.Rationals + + if have_mpfr: from .lib.symengine_wrapper import RealMPFR @@ -33,9 +56,20 @@ def lambdify(args, exprs, **kwargs): return Lambdify(args, *exprs, **kwargs) +else: + def __getattr__(name): + if name == 'lambdify': + raise AttributeError("Cannot import numpy, which is required for `lambdify` to work") + raise AttributeError(f"module 'symengine' has no attribute '{name}'") + + +__version__ = "0.14.1" -__version__ = "0.6.1" +# To not expose internals +del lib.symengine_wrapper +del lib +del wrapper def test(): diff --git a/symengine/compatibility.py b/symengine/compatibility.py deleted file mode 100644 index b28f64ada..000000000 --- a/symengine/compatibility.py +++ /dev/null @@ -1,870 +0,0 @@ -""" -Reimplementations of constructs introduced in later versions of Python than -we support. Also some functions that are needed SymPy-wide and are located -here for easy import. -""" -from __future__ import print_function, division - -import operator -from collections import defaultdict - -""" -Python 2 and Python 3 compatible imports - -String and Unicode compatible changes: - * `unicode()` removed in Python 3, import `unicode` for Python 2/3 - compatible function - * `unichr()` removed in Python 3, import `unichr` for Python 2/3 compatible - function - * Use `u()` for escaped unicode sequences (e.g. u'\u2020' -> u('\u2020')) - * Use `u_decode()` to decode utf-8 formatted unicode strings - * `string_types` gives str in Python 3, unicode and str in Python 2, - equivalent to basestring - -Integer related changes: - * `long()` removed in Python 3, import `long` for Python 2/3 compatible - function - * `integer_types` gives int in Python 3, int and long in Python 2 - -Types related changes: - * `class_types` gives type in Python 3, type and ClassType in Python 2 - -Renamed function attributes: - * Python 2 `.func_code`, Python 3 `.__func__`, access with - `get_function_code()` - * Python 2 `.func_globals`, Python 3 `.__globals__`, access with - `get_function_globals()` - * Python 2 `.func_name`, Python 3 `.__name__`, access with - `get_function_name()` - -Moved modules: - * `reduce()` - * `StringIO()` - * `cStringIO()` (same as `StingIO()` in Python 3) - * Python 2 `__builtins__`, access with Python 3 name, `builtins` - -Iterator/list changes: - * `xrange` removed in Python 3, import `xrange` for Python 2/3 compatible - iterator version of range - -exec: - * Use `exec_()`, with parameters `exec_(code, globs=None, locs=None)` - -Metaclasses: - * Use `with_metaclass()`, examples below - * Define class `Foo` with metaclass `Meta`, and no parent: - class Foo(with_metaclass(Meta)): - pass - * Define class `Foo` with metaclass `Meta` and parent class `Bar`: - class Foo(with_metaclass(Meta, Bar)): - pass -""" - -import sys -PY3 = sys.version_info[0] > 2 - -if PY3: - class_types = type, - integer_types = (int,) - string_types = (str,) - long = int - - # String / unicode compatibility - unicode = str - unichr = chr - def u(x): - return x - def u_decode(x): - return x - - Iterator = object - - # Moved definitions - get_function_code = operator.attrgetter("__code__") - get_function_globals = operator.attrgetter("__globals__") - get_function_name = operator.attrgetter("__name__") - - import builtins - from functools import reduce - from io import StringIO - cStringIO = StringIO - - exec_ = getattr(builtins, "exec") - - xrange = range -else: - import codecs - import types - - class_types = (type, types.ClassType) - integer_types = (int, long) - string_types = (str, unicode) - long = long - - # String / unicode compatibility - unicode = unicode - unichr = unichr - def u(x): - return codecs.unicode_escape_decode(x)[0] - def u_decode(x): - return x.decode('utf-8') - - class Iterator(object): - def next(self): - return type(self).__next__(self) - - # Moved definitions - get_function_code = operator.attrgetter("func_code") - get_function_globals = operator.attrgetter("func_globals") - get_function_name = operator.attrgetter("func_name") - - import __builtin__ as builtins - reduce = reduce - from StringIO import StringIO - from cStringIO import StringIO as cStringIO - - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("exec _code_ in _globs_, _locs_") - - xrange = xrange - -def with_metaclass(meta, *bases): - """ - Create a base class with a metaclass. - - For example, if you have the metaclass - - >>> class Meta(type): - ... pass - - Use this as the metaclass by doing - - >>> from symengine.compatibility import with_metaclass - >>> class MyClass(with_metaclass(Meta, object)): - ... pass - - This is equivalent to the Python 2:: - - class MyClass(object): - __metaclass__ = Meta - - or Python 3:: - - class MyClass(object, metaclass=Meta): - pass - - That is, the first argument is the metaclass, and the remaining arguments - are the base classes. Note that if the base class is just ``object``, you - may omit it. - - >>> MyClass.__mro__ - (, <... 'object'>) - >>> type(MyClass) - - - """ - class metaclass(meta): - __call__ = type.__call__ - __init__ = type.__init__ - def __new__(cls, name, this_bases, d): - if this_bases is None: - return type.__new__(cls, name, (), d) - return meta(name, bases, d) - return metaclass("NewBase", None, {}) - - -# These are in here because telling if something is an iterable just by calling -# hasattr(obj, "__iter__") behaves differently in Python 2 and Python 3. In -# particular, hasattr(str, "__iter__") is False in Python 2 and True in Python 3. -# I think putting them here also makes it easier to use them in the core. - -class NotIterable: - """ - Use this as mixin when creating a class which is not supposed to return - true when iterable() is called on its instances. I.e. avoid infinite loop - when calling e.g. list() on the instance - """ - pass - -def iterable(i, exclude=(string_types, dict, NotIterable)): - """ - Return a boolean indicating whether ``i`` is SymPy iterable. - True also indicates that the iterator is finite, i.e. you e.g. - call list(...) on the instance. - - When SymPy is working with iterables, it is almost always assuming - that the iterable is not a string or a mapping, so those are excluded - by default. If you want a pure Python definition, make exclude=None. To - exclude multiple items, pass them as a tuple. - - See also: is_sequence - - Examples - ======== - - >>> from sympy.utilities.iterables import iterable - >>> from sympy import Tuple - >>> things = [[1], (1,), set([1]), Tuple(1), (j for j in [1, 2]), {1:2}, '1', 1] - >>> for i in things: - ... print('%s %s' % (iterable(i), type(i))) - True <... 'list'> - True <... 'tuple'> - True <... 'set'> - True - True <... 'generator'> - False <... 'dict'> - False <... 'str'> - False <... 'int'> - - >>> iterable({}, exclude=None) - True - >>> iterable({}, exclude=str) - True - >>> iterable("no", exclude=str) - False - - """ - try: - iter(i) - except TypeError: - return False - if exclude: - return not isinstance(i, exclude) - return True - - -def is_sequence(i, include=None): - """ - Return a boolean indicating whether ``i`` is a sequence in the SymPy - sense. If anything that fails the test below should be included as - being a sequence for your application, set 'include' to that object's - type; multiple types should be passed as a tuple of types. - - Note: although generators can generate a sequence, they often need special - handling to make sure their elements are captured before the generator is - exhausted, so these are not included by default in the definition of a - sequence. - - See also: iterable - - Examples - ======== - - >>> from sympy.utilities.iterables import is_sequence - >>> from types import GeneratorType - >>> is_sequence([]) - True - >>> is_sequence(set()) - False - >>> is_sequence('abc') - False - >>> is_sequence('abc', include=str) - True - >>> generator = (c for c in 'abc') - >>> is_sequence(generator) - False - >>> is_sequence(generator, include=(str, GeneratorType)) - True - - """ - return (hasattr(i, '__getitem__') and - iterable(i) or - bool(include) and - isinstance(i, include)) - -try: - from functools import cmp_to_key -except ImportError: # <= Python 2.6 - def cmp_to_key(mycmp): - """ - Convert a cmp= function into a key= function - """ - class K(object): - def __init__(self, obj, *args): - self.obj = obj - - def __lt__(self, other): - return mycmp(self.obj, other.obj) < 0 - - def __gt__(self, other): - return mycmp(self.obj, other.obj) > 0 - - def __eq__(self, other): - return mycmp(self.obj, other.obj) == 0 - - def __le__(self, other): - return mycmp(self.obj, other.obj) <= 0 - - def __ge__(self, other): - return mycmp(self.obj, other.obj) >= 0 - - def __ne__(self, other): - return mycmp(self.obj, other.obj) != 0 - return K - -try: - from itertools import zip_longest -except ImportError: # <= Python 2.7 - from itertools import izip_longest as zip_longest - -try: - from itertools import combinations_with_replacement -except ImportError: # <= Python 2.6 - def combinations_with_replacement(iterable, r): - """Return r length subsequences of elements from the input iterable - allowing individual elements to be repeated more than once. - - Combinations are emitted in lexicographic sort order. So, if the - input iterable is sorted, the combination tuples will be produced - in sorted order. - - Elements are treated as unique based on their position, not on their - value. So if the input elements are unique, the generated combinations - will also be unique. - - See also: combinations - - Examples - ======== - - >>> from sympy.core.compatibility import combinations_with_replacement - >>> list(combinations_with_replacement('AB', 2)) - [('A', 'A'), ('A', 'B'), ('B', 'B')] - """ - pool = tuple(iterable) - n = len(pool) - if not n and r: - return - indices = [0] * r - yield tuple(pool[i] for i in indices) - while True: - for i in reversed(range(r)): - if indices[i] != n - 1: - break - else: - return - indices[i:] = [indices[i] + 1] * (r - i) - yield tuple(pool[i] for i in indices) - - -def as_int(n): - """ - Convert the argument to a builtin integer. - - The return value is guaranteed to be equal to the input. ValueError is - raised if the input has a non-integral value. - - Examples - ======== - - >>> from sympy.core.compatibility import as_int - >>> from sympy import sqrt - >>> 3.0 - 3.0 - >>> as_int(3.0) # convert to int and test for equality - 3 - >>> int(sqrt(10)) - 3 - >>> as_int(sqrt(10)) - Traceback (most recent call last): - ... - ValueError: ... is not an integer - - """ - try: - result = int(n) - if result != n: - raise TypeError - except TypeError: - raise ValueError('%s is not an integer' % n) - return result - - -def default_sort_key(item, order=None): - """Return a key that can be used for sorting. - - The key has the structure: - - (class_key, (len(args), args), exponent.sort_key(), coefficient) - - This key is supplied by the sort_key routine of Basic objects when - ``item`` is a Basic object or an object (other than a string) that - sympifies to a Basic object. Otherwise, this function produces the - key. - - The ``order`` argument is passed along to the sort_key routine and is - used to determine how the terms *within* an expression are ordered. - (See examples below) ``order`` options are: 'lex', 'grlex', 'grevlex', - and reversed values of the same (e.g. 'rev-lex'). The default order - value is None (which translates to 'lex'). - - Examples - ======== - - >>> from sympy import S, I, default_sort_key - >>> from sympy.core.function import UndefinedFunction - >>> from sympy.abc import x - - The following are equivalent ways of getting the key for an object: - - >>> x.sort_key() == default_sort_key(x) - True - - Here are some examples of the key that is produced: - - >>> default_sort_key(UndefinedFunction('f')) - ((0, 0, 'UndefinedFunction'), (1, ('f',)), ((1, 0, 'Number'), - (0, ()), (), 1), 1) - >>> default_sort_key('1') - ((0, 0, 'str'), (1, ('1',)), ((1, 0, 'Number'), (0, ()), (), 1), 1) - >>> default_sort_key(S.One) - ((1, 0, 'Number'), (0, ()), (), 1) - >>> default_sort_key(2) - ((1, 0, 'Number'), (0, ()), (), 2) - - - While sort_key is a method only defined for SymPy objects, - default_sort_key will accept anything as an argument so it is - more robust as a sorting key. For the following, using key= - lambda i: i.sort_key() would fail because 2 doesn't have a sort_key - method; that's why default_sort_key is used. Note, that it also - handles sympification of non-string items likes ints: - - >>> a = [2, I, -I] - >>> sorted(a, key=default_sort_key) - [2, -I, I] - - The returned key can be used anywhere that a key can be specified for - a function, e.g. sort, min, max, etc...: - - >>> a.sort(key=default_sort_key); a[0] - 2 - >>> min(a, key=default_sort_key) - 2 - - Note - ---- - - The key returned is useful for getting items into a canonical order - that will be the same across platforms. It is not directly useful for - sorting lists of expressions: - - >>> a, b = x, 1/x - - Since ``a`` has only 1 term, its value of sort_key is unaffected by - ``order``: - - >>> a.sort_key() == a.sort_key('rev-lex') - True - - If ``a`` and ``b`` are combined then the key will differ because there - are terms that can be ordered: - - >>> eq = a + b - >>> eq.sort_key() == eq.sort_key('rev-lex') - False - >>> eq.as_ordered_terms() - [x, 1/x] - >>> eq.as_ordered_terms('rev-lex') - [1/x, x] - - But since the keys for each of these terms are independent of ``order``'s - value, they don't sort differently when they appear separately in a list: - - >>> sorted(eq.args, key=default_sort_key) - [1/x, x] - >>> sorted(eq.args, key=lambda i: default_sort_key(i, order='rev-lex')) - [1/x, x] - - The order of terms obtained when using these keys is the order that would - be obtained if those terms were *factors* in a product. - - See Also - ======== - - sympy.core.expr.as_ordered_factors, sympy.core.expr.as_ordered_terms - - """ - - from sympy.core import S, Basic - from sympy.core.sympify import sympify, SympifyError - from sympy.core.compatibility import iterable - - if isinstance(item, Basic): - return item.sort_key(order=order) - - if iterable(item, exclude=string_types): - if isinstance(item, dict): - args = item.items() - unordered = True - elif isinstance(item, set): - args = item - unordered = True - else: - # e.g. tuple, list - args = list(item) - unordered = False - - args = [default_sort_key(arg, order=order) for arg in args] - - if unordered: - # e.g. dict, set - args = sorted(args) - - cls_index, args = 10, (len(args), tuple(args)) - else: - if not isinstance(item, string_types): - try: - item = sympify(item) - except SympifyError: - # e.g. lambda x: x - pass - else: - if isinstance(item, Basic): - # e.g int -> Integer - return default_sort_key(item) - # e.g. UndefinedFunction - - # e.g. str - cls_index, args = 0, (1, (str(item),)) - - return (cls_index, 0, item.__class__.__name__ - ), args, S.One.sort_key(), S.One - - -def _nodes(e): - """ - A helper for ordered() which returns the node count of ``e`` which - for Basic objects is the number of Basic nodes in the expression tree - but for other objects is 1 (unless the object is an iterable or dict - for which the sum of nodes is returned). - """ - from .basic import Basic - - if isinstance(e, Basic): - return e.count(Basic) - elif iterable(e): - return 1 + sum(_nodes(ei) for ei in e) - elif isinstance(e, dict): - return 1 + sum(_nodes(k) + _nodes(v) for k, v in e.items()) - else: - return 1 - - -def ordered(seq, keys=None, default=True, warn=False): - """Return an iterator of the seq where keys are used to break ties in - a conservative fashion: if, after applying a key, there are no ties - then no other keys will be computed. - - Two default keys will be applied if 1) keys are not provided or 2) the - given keys don't resolve all ties (but only if `default` is True). The - two keys are `_nodes` (which places smaller expressions before large) and - `default_sort_key` which (if the `sort_key` for an object is defined - properly) should resolve any ties. - - If ``warn`` is True then an error will be raised if there were no - keys remaining to break ties. This can be used if it was expected that - there should be no ties between items that are not identical. - - Examples - ======== - - >>> from sympy.utilities.iterables import ordered - >>> from sympy import count_ops - >>> from sympy.abc import x, y - - The count_ops is not sufficient to break ties in this list and the first - two items appear in their original order (i.e. the sorting is stable): - - >>> list(ordered([y + 2, x + 2, x**2 + y + 3], - ... count_ops, default=False, warn=False)) - ... - [y + 2, x + 2, x**2 + y + 3] - - The default_sort_key allows the tie to be broken: - - >>> list(ordered([y + 2, x + 2, x**2 + y + 3])) - ... - [x + 2, y + 2, x**2 + y + 3] - - Here, sequences are sorted by length, then sum: - - >>> seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], [ - ... lambda x: len(x), - ... lambda x: sum(x)]] - ... - >>> list(ordered(seq, keys, default=False, warn=False)) - [[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]] - - If ``warn`` is True, an error will be raised if there were not - enough keys to break ties: - - >>> list(ordered(seq, keys, default=False, warn=True)) - Traceback (most recent call last): - ... - ValueError: not enough keys to break ties - - - Notes - ===== - - The decorated sort is one of the fastest ways to sort a sequence for - which special item comparison is desired: the sequence is decorated, - sorted on the basis of the decoration (e.g. making all letters lower - case) and then undecorated. If one wants to break ties for items that - have the same decorated value, a second key can be used. But if the - second key is expensive to compute then it is inefficient to decorate - all items with both keys: only those items having identical first key - values need to be decorated. This function applies keys successively - only when needed to break ties. By yielding an iterator, use of the - tie-breaker is delayed as long as possible. - - This function is best used in cases when use of the first key is - expected to be a good hashing function; if there are no unique hashes - from application of a key then that key should not have been used. The - exception, however, is that even if there are many collisions, if the - first group is small and one does not need to process all items in the - list then time will not be wasted sorting what one was not interested - in. For example, if one were looking for the minimum in a list and - there were several criteria used to define the sort order, then this - function would be good at returning that quickly if the first group - of candidates is small relative to the number of items being processed. - - """ - d = defaultdict(list) - if keys: - if not isinstance(keys, (list, tuple)): - keys = [keys] - keys = list(keys) - f = keys.pop(0) - for a in seq: - d[f(a)].append(a) - else: - if not default: - raise ValueError('if default=False then keys must be provided') - d[None].extend(seq) - - for k in sorted(d.keys()): - if len(d[k]) > 1: - if keys: - d[k] = ordered(d[k], keys, default, warn) - elif default: - d[k] = ordered(d[k], (_nodes, default_sort_key,), - default=False, warn=warn) - elif warn: - from sympy.utilities.iterables import uniq - u = list(uniq(d[k])) - if len(u) > 1: - raise ValueError( - 'not enough keys to break ties: %s' % u) - for v in d[k]: - yield v - d.pop(k) - - -# check_output() is new in Python 2.7 -import os - -try: - try: - from subprocess import check_output - except ImportError: # <= Python 2.6 - from subprocess import CalledProcessError, check_call - def check_output(*args, **kwargs): - with open(os.devnull, 'w') as fh: - kwargs['stdout'] = fh - try: - return check_call(*args, **kwargs) - except CalledProcessError as e: - e.output = ("program output is not available for Python 2.6.x") - raise e -except ImportError: - # running on platform like App Engine, no subprocess at all - pass - - -# lru_cache compatible with py2.6->py3.2 copied directly from -# http://code.activestate.com/ -# recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/ -from collections import namedtuple -from functools import update_wrapper -from threading import RLock - -_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) - -class _HashedSeq(list): - __slots__ = 'hashvalue' - - def __init__(self, tup, hash=hash): - self[:] = tup - self.hashvalue = hash(tup) - - def __hash__(self): - return self.hashvalue - -def _make_key(args, kwds, typed, - kwd_mark = (object(),), - fasttypes = set((int, str, frozenset, type(None))), - sorted=sorted, tuple=tuple, type=type, len=len): - 'Make a cache key from optionally typed positional and keyword arguments' - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) - elif len(key) == 1 and type(key[0]) in fasttypes: - return key[0] - return _HashedSeq(key) - -def lru_cache(maxsize=100, typed=False): - """Least-recently-used cache decorator. - - If *maxsize* is set to None, the LRU features are disabled and the cache - can grow without bound. - - If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. - - Arguments to the cached function must be hashable. - - View the cache statistics named tuple (hits, misses, maxsize, currsize) with - f.cache_info(). Clear the cache and statistics with f.cache_clear(). - Access the underlying function with f.__wrapped__. - - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used - - """ - - # Users should only access the lru_cache through its public API: - # cache_info, cache_clear, and f.__wrapped__ - # The internals of the lru_cache are encapsulated for thread safety and - # to allow the implementation to change (including a possible C version). - - def decorating_function(user_function): - - cache = dict() - stats = [0, 0] # make statistics updateable non-locally - HITS, MISSES = 0, 1 # names for the stats fields - make_key = _make_key - cache_get = cache.get # bound method to lookup key or return None - _len = len # localize the global len() function - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - nonlocal_root = [root] # make updateable non-locally - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - - if maxsize == 0: - - def wrapper(*args, **kwds): - # no caching, just do a statistics update after a successful call - result = user_function(*args, **kwds) - stats[MISSES] += 1 - return result - - elif maxsize is None: - - def wrapper(*args, **kwds): - # simple caching without ordering or size limit - key = make_key(args, kwds, typed) - result = cache_get(key, root) # root used here as a unique not-found sentinel - if result is not root: - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - stats[MISSES] += 1 - return result - - else: - - def wrapper(*args, **kwds): - # size limited caching that tracks accesses by recency - try: - key = make_key(args, kwds, typed) if kwds or typed else args - except TypeError: - stats[MISSES] += 1 - return user_function(*args, **kwds) - with lock: - link = cache_get(key) - if link is not None: - # record recent use of the key by moving it to the front of the list - root, = nonlocal_root - link_prev, link_next, key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - with lock: - root, = nonlocal_root - if key in cache: - # getting here means that this same key was added to the - # cache while the lock was released. since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif _len(cache) >= maxsize: - # use the old root to store the new key and result - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # empty the oldest link and make it the new root - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - oldvalue = root[RESULT] - root[KEY] = root[RESULT] = None - # now update the cache dictionary for the new links - del cache[oldkey] - cache[key] = oldroot - else: - # put result in a new link at the front of the list - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - return result - - def cache_info(): - """Report cache statistics""" - with lock: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - with lock: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return update_wrapper(wrapper, user_function) - - return decorating_function -### End of backported lru_cache - -if sys.version_info[:2] >= (3, 3): - # 3.2 has an lru_cache with an incompatible API - from functools import lru_cache diff --git a/symengine/lib/CMakeLists.txt b/symengine/lib/CMakeLists.txt index 53247ea3f..7683d4aa8 100644 --- a/symengine/lib/CMakeLists.txt +++ b/symengine/lib/CMakeLists.txt @@ -1,13 +1,47 @@ set(SRC - symengine_wrapper.cpp + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.cpp pywrapper.cpp ) -configure_file(config.pxi.in config.pxi) +include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}) -include_directories(BEFORE ${python_wrapper_BINARY_DIR}/symengine/lib) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pxd + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/symengine_wrapper.in.pxd + ${PROJECT_SOURCE_DIR}/cmake/preprocess.py + COMMAND ${PYTHON_BIN} ${PROJECT_SOURCE_DIR}/cmake/preprocess.py + ${CMAKE_CURRENT_SOURCE_DIR}/symengine_wrapper.in.pxd + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pxd + HAVE_SYMENGINE_MPFR=${HAVE_SYMENGINE_MPFR} + HAVE_SYMENGINE_MPC=${HAVE_SYMENGINE_MPC} + HAVE_SYMENGINE_PIRANHA=${HAVE_SYMENGINE_PIRANHA} + HAVE_SYMENGINE_FLINT=${HAVE_SYMENGINE_FLINT} + HAVE_SYMENGINE_LLVM=${HAVE_SYMENGINE_LLVM} + HAVE_SYMENGINE_LLVM_LONG_DOUBLE=${HAVE_SYMENGINE_LLVM_LONG_DOUBLE} + COMMENT "Preprocessing symengine_wrapper.in.pxd" +) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pyx + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/symengine_wrapper.in.pyx + ${CMAKE_CURRENT_SOURCE_DIR}/symengine_wrapper.in.pxd + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pxd + ${PROJECT_SOURCE_DIR}/cmake/preprocess.py + COMMAND ${PYTHON_BIN} ${PROJECT_SOURCE_DIR}/cmake/preprocess.py + ${CMAKE_CURRENT_SOURCE_DIR}/symengine_wrapper.in.pyx + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pyx + HAVE_SYMENGINE_MPFR=${HAVE_SYMENGINE_MPFR} + HAVE_SYMENGINE_MPC=${HAVE_SYMENGINE_MPC} + HAVE_SYMENGINE_PIRANHA=${HAVE_SYMENGINE_PIRANHA} + HAVE_SYMENGINE_FLINT=${HAVE_SYMENGINE_FLINT} + HAVE_SYMENGINE_LLVM=${HAVE_SYMENGINE_LLVM} + HAVE_SYMENGINE_LLVM_LONG_DOUBLE=${HAVE_SYMENGINE_LLVM_LONG_DOUBLE} + COMMENT "Preprocessing symengine_wrapper.in.pyx" +) -cython_add_module_pyx(symengine_wrapper symengine.pxd) +cython_add_module_pyx(symengine_wrapper.cpp + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pyx + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pxd + ${CMAKE_CURRENT_SOURCE_DIR}/symengine.pxd) add_python_library(symengine_wrapper ${SRC}) target_link_libraries(symengine_wrapper ${SYMENGINE_LIBRARIES}) if (CMAKE_CXX_COMPILER_ID MATCHES GNU|Clang) @@ -18,20 +52,32 @@ if (CMAKE_CXX_COMPILER_ID MATCHES GNU|Clang) ) endif() -add_custom_target(cython - COMMAND cython symengine_wrapper.pyx - ) - set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine/lib) install(TARGETS symengine_wrapper RUNTIME DESTINATION ${PY_PATH} ARCHIVE DESTINATION ${PY_PATH} LIBRARY DESTINATION ${PY_PATH} ) -install(FILES __init__.py - ${CMAKE_CURRENT_BINARY_DIR}/config.pxi +install(FILES symengine.pxd - symengine_wrapper.pxd + ${CMAKE_CURRENT_BINARY_DIR}/symengine_wrapper.pxd pywrapper.h DESTINATION ${PY_PATH} ) + +if (SYMENGINE_INSTALL_PY_FILES) + install(FILES __init__.py DESTINATION ${PY_PATH}) +endif () + +if (${SYMENGINE_COPY_EXTENSION}) + if ("${PYTHON_EXTENSION_SOABI}" MATCHES "ppc64le") + string(REPLACE "ppc64le" "powerpc64le" COPY_PYTHON_EXTENSION_SOABI "${PYTHON_EXTENSION_SOABI}") + endif () + if ("${PYTHON_EXTENSION_SOABI}" MATCHES "powerpc64le") + string(REPLACE "powerpc64le" "ppc64le" COPY_PYTHON_EXTENSION_SOABI "${PYTHON_EXTENSION_SOABI}") + endif () + message("${PYTHON_EXTENSION_SOABI} ${COPY_PYTHON_EXTENSION_SOABI}") + set(SOURCE_NAME "${PY_PATH}/symengine_wrapper${PYTHON_EXTENSION_SOABI}.so") + set(DEST_NAME "${PY_PATH}/symengine_wrapper${COPY_PYTHON_EXTENSION_SOABI}.so") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${SOURCE_NAME} ${DEST_NAME})") +endif () diff --git a/symengine/lib/config.pxi.in b/symengine/lib/config.pxi.in deleted file mode 100644 index 26a33012e..000000000 --- a/symengine/lib/config.pxi.in +++ /dev/null @@ -1,6 +0,0 @@ -DEF HAVE_SYMENGINE_MPFR = ${HAVE_SYMENGINE_MPFR} -DEF HAVE_SYMENGINE_MPC = ${HAVE_SYMENGINE_MPC} -DEF HAVE_SYMENGINE_PIRANHA = ${HAVE_SYMENGINE_PIRANHA} -DEF HAVE_SYMENGINE_FLINT = ${HAVE_SYMENGINE_FLINT} -DEF HAVE_SYMENGINE_LLVM = ${HAVE_SYMENGINE_LLVM} -DEF HAVE_SYMENGINE_LLVM_LONG_DOUBLE = ${HAVE_SYMENGINE_LLVM_LONG_DOUBLE} diff --git a/symengine/lib/pywrapper.cpp b/symengine/lib/pywrapper.cpp index ca7b7c109..5ab5e5866 100644 --- a/symengine/lib/pywrapper.cpp +++ b/symengine/lib/pywrapper.cpp @@ -1,4 +1,5 @@ #include "pywrapper.h" +#include #if PY_MAJOR_VERSION >= 3 #define PyInt_FromLong PyLong_FromLong @@ -175,17 +176,12 @@ RCP PyNumber::eval(long bits) const { } std::string PyNumber::__str__() const { - PyObject* temp; - std::string str; -#if PY_MAJOR_VERSION > 2 - temp = PyUnicode_AsUTF8String(pyobject_); - str = std::string(PyBytes_AsString(temp)); -#else - temp = PyObject_Str(pyobject_); - str = std::string(PyString_AsString(temp)); -#endif - Py_XDECREF(temp); - return str; + Py_ssize_t size; + PyObject *pystr = PyObject_Str(pyobject_); + const char* data = PyUnicode_AsUTF8AndSize(pystr, &size); + std::string result = std::string(data, size); + Py_XDECREF(pystr); + return result; } // PyFunctionClass @@ -274,4 +270,102 @@ int PyFunction::compare(const Basic &o) const { return unified_compare(get_vec(), s.get_vec()); } +inline PyObject* get_pickle_module() { + static PyObject *module = NULL; + if (module == NULL) { + module = PyImport_ImportModule("pickle"); + } + if (module == NULL) { + throw SymEngineException("error importing pickle module."); + } + return module; +} + +PyObject* pickle_loads(const std::string &pickle_str) { + PyObject *module = get_pickle_module(); + PyObject *pickle_bytes = PyBytes_FromStringAndSize(pickle_str.data(), pickle_str.size()); + PyObject *obj = PyObject_CallMethod(module, "loads", "O", pickle_bytes); + Py_XDECREF(pickle_bytes); + if (obj == NULL) { + throw SerializationError("error when loading pickled symbol subclass object"); + } + return obj; +} + +RCP load_basic(RCPBasicAwareInputArchive &ar, RCP &) +{ + bool is_pysymbol; + bool store_pickle; + std::string name; + ar(is_pysymbol); + ar(name); + if (is_pysymbol) { + std::string pickle_str; + ar(pickle_str); + ar(store_pickle); + PyObject *obj = pickle_loads(pickle_str); + RCP result = make_rcp(name, obj, store_pickle); + Py_XDECREF(obj); + return result; + } else { + return symbol(name); + } +} + +std::string pickle_dumps(const PyObject * obj) { + PyObject *module = get_pickle_module(); + PyObject *pickle_bytes = PyObject_CallMethod(module, "dumps", "O", obj); + if (pickle_bytes == NULL) { + throw SerializationError("error when pickling symbol subclass object"); + } + Py_ssize_t size; + char* buffer; + PyBytes_AsStringAndSize(pickle_bytes, &buffer, &size); + return std::string(buffer, size); +} + +void save_basic(RCPBasicAwareOutputArchive &ar, const Symbol &b) +{ + bool is_pysymbol = is_a_sub(b); + ar(is_pysymbol); + ar(b.__str__()); + if (is_pysymbol) { + RCP p = rcp_static_cast(b.rcp_from_this()); + PyObject *obj = p->get_py_object(); + std::string pickle_str = pickle_dumps(obj); + ar(pickle_str); + ar(p->store_pickle); + Py_XDECREF(obj); + } +} + +std::string wrapper_dumps(const Basic &x) +{ + std::ostringstream oss; + unsigned short major = SYMENGINE_MAJOR_VERSION; + unsigned short minor = SYMENGINE_MINOR_VERSION; + RCPBasicAwareOutputArchive{oss}(major, minor, + x.rcp_from_this()); + return oss.str(); +} + +RCP wrapper_loads(const std::string &serialized) +{ + unsigned short major, minor; + RCP obj; + std::istringstream iss(serialized); + RCPBasicAwareInputArchive iarchive{iss}; + iarchive(major, minor); + if (major != SYMENGINE_MAJOR_VERSION or minor != SYMENGINE_MINOR_VERSION) { + throw SerializationError(StreamFmt() + << "SymEngine-" << SYMENGINE_MAJOR_VERSION + << "." << SYMENGINE_MINOR_VERSION + << " was asked to deserialize an object " + << "created using SymEngine-" << major << "." + << minor << "."); + } + iarchive(obj); + return obj; +} + } // SymEngine diff --git a/symengine/lib/pywrapper.h b/symengine/lib/pywrapper.h index ba5fe70a8..20a6dbee4 100644 --- a/symengine/lib/pywrapper.h +++ b/symengine/lib/pywrapper.h @@ -8,6 +8,9 @@ namespace SymEngine { +std::string pickle_dumps(const PyObject *); +PyObject* pickle_loads(const std::string &); + /* * PySymbol is a subclass of Symbol that keeps a reference to a Python object. * When subclassing a Symbol from Python, the information stored in subclassed @@ -27,16 +30,30 @@ namespace SymEngine { class PySymbol : public Symbol { private: PyObject* obj; + std::string bytes; public: - PySymbol(const std::string& name, PyObject* obj) : Symbol(name), obj(obj) { - Py_INCREF(obj); + const bool store_pickle; + PySymbol(const std::string& name, PyObject* obj, bool store_pickle) : + Symbol(name), obj(obj), store_pickle(store_pickle) { + if (store_pickle) { + bytes = pickle_dumps(obj); + } else { + Py_INCREF(obj); + } } PyObject* get_py_object() const { - return obj; + if (store_pickle) { + return pickle_loads(bytes); + } else { + Py_INCREF(obj); + return obj; + } } virtual ~PySymbol() { - // TODO: This is never called because of the cyclic reference. - Py_DECREF(obj); + if (not store_pickle) { + // TODO: This is never called because of the cyclic reference. + Py_DECREF(obj); + } } }; @@ -195,6 +212,9 @@ class PyFunction : public FunctionWrapper { virtual hash_t __hash__() const; }; +std::string wrapper_dumps(const Basic &x); +RCP wrapper_loads(const std::string &s); + } #endif //SYMENGINE_PYWRAPPER_H diff --git a/symengine/lib/symengine.pxd b/symengine/lib/symengine.pxd index fb4a6cb05..65b3456aa 100644 --- a/symengine/lib/symengine.pxd +++ b/symengine/lib/symengine.pxd @@ -4,8 +4,21 @@ from libcpp.map cimport map from libcpp.vector cimport vector from cpython.ref cimport PyObject from libcpp.pair cimport pair +from libcpp.set cimport set +from libcpp.unordered_map cimport unordered_map -include "config.pxi" +cdef extern from "" namespace "std": + # Cython's libcpp.set does not support multiset in 0.29.x + cdef cppclass multiset[T]: + cppclass iterator: + T& operator*() + iterator operator++() nogil + iterator operator--() nogil + bint operator==(iterator) nogil + bint operator!=(iterator) nogil + iterator begin() nogil + iterator end() nogil + iterator insert(T&) nogil cdef extern from 'symengine/mp_class.h' namespace "SymEngine": ctypedef unsigned long mp_limb_t @@ -24,99 +37,13 @@ cdef extern from 'symengine/mp_class.h' namespace "SymEngine": integer_class(const string &s) except + mpz_t get_mpz_t(integer_class &a) const mpz_t get_mpz_t(const integer_class &a) + string mp_get_hex_str(const integer_class &a) + void mp_set_str(integer_class &a, const string &s) cdef cppclass rational_class: rational_class() rational_class(mpq_t) const mpq_t get_mpq_t(const rational_class &a) -cdef extern from "" namespace "std": -# Cython's libcpp.set does not support two template arguments to set. -# Methods to declare and iterate a set with a custom compare are given here - cdef cppclass set[T, U]: - cppclass iterator: - T& operator*() - iterator operator++() nogil - iterator operator--() nogil - bint operator==(iterator) nogil - bint operator!=(iterator) nogil - iterator begin() nogil - iterator end() nogil - iterator insert(T&) nogil - - cdef cppclass multiset[T, U]: - cppclass iterator: - T& operator*() - iterator operator++() nogil - iterator operator--() nogil - bint operator==(iterator) nogil - bint operator!=(iterator) nogil - iterator begin() nogil - iterator end() nogil - iterator insert(T&) nogil - -cdef extern from "" namespace "std" nogil: - cdef cppclass unordered_map[T, U]: - cppclass iterator: - pair[T, U]& operator*() - iterator operator++() - iterator operator--() - bint operator==(iterator) - bint operator!=(iterator) - cppclass reverse_iterator: - pair[T, U]& operator*() - iterator operator++() - iterator operator--() - bint operator==(reverse_iterator) - bint operator!=(reverse_iterator) - cppclass const_iterator(iterator): - pass - cppclass const_reverse_iterator(reverse_iterator): - pass - unordered_map() except + - unordered_map(unordered_map&) except + - #unordered_map(key_compare&) - U& operator[](T&) - #unordered_map& operator=(unordered_map&) - bint operator==(unordered_map&, unordered_map&) - bint operator!=(unordered_map&, unordered_map&) - bint operator<(unordered_map&, unordered_map&) - bint operator>(unordered_map&, unordered_map&) - bint operator<=(unordered_map&, unordered_map&) - bint operator>=(unordered_map&, unordered_map&) - U& at(T&) - iterator begin() - const_iterator const_begin "begin"() - void clear() - size_t count(T&) - bint empty() - iterator end() - const_iterator const_end "end"() - pair[iterator, iterator] equal_range(T&) - #pair[const_iterator, const_iterator] equal_range(key_type&) - void erase(iterator) - void erase(iterator, iterator) - size_t erase(T&) - iterator find(T&) - const_iterator const_find "find"(T&) - pair[iterator, bint] insert(pair[T, U]) # XXX pair[T,U]& - iterator insert(iterator, pair[T, U]) # XXX pair[T,U]& - #void insert(input_iterator, input_iterator) - #key_compare key_comp() - iterator lower_bound(T&) - const_iterator const_lower_bound "lower_bound"(T&) - size_t max_size() - reverse_iterator rbegin() - const_reverse_iterator const_rbegin "rbegin"() - reverse_iterator rend() - const_reverse_iterator const_rend "rend"() - size_t size() - void swap(unordered_map&) - iterator upper_bound(T&) - const_iterator const_upper_bound "upper_bound"(T&) - #value_compare value_comp() - void max_load_factor(float) - float max_load_factor() - cdef extern from "" namespace "SymEngine": cdef enum ENull: null @@ -134,7 +61,12 @@ cdef extern from "" namespace "SymEngine": cdef extern from "" namespace "SymEngine": ctypedef Basic const_Basic "const SymEngine::Basic" - ctypedef RCP[const Basic] rcp_const_basic "SymEngine::RCP" + # RCP[const_Basic] instead of RCP[const Basic] is because of https://github.com/cython/cython/issues/5478 + ctypedef RCP[const_Basic] rcp_const_basic "SymEngine::RCP" + #cdef cppclass rcp_const_basic "SymEngine::RCP": + # Basic& operator*() nogil + # void reset() nogil except + + # pass # Cython has broken support for the following: # ctypedef map[rcp_const_basic, rcp_const_basic] map_basic_basic # So instead we replicate the map features we need here @@ -176,13 +108,15 @@ cdef extern from "" namespace "SymEngine": ctypedef map[RCP[Integer], unsigned] map_integer_uint "SymEngine::map_integer_uint" cdef struct RCPIntegerKeyLess cdef struct RCPBasicKeyLess - ctypedef set[rcp_const_basic, RCPBasicKeyLess] set_basic "SymEngine::set_basic" - ctypedef multiset[rcp_const_basic, RCPBasicKeyLess] multiset_basic "SymEngine::multiset_basic" + ctypedef set[rcp_const_basic] set_basic "SymEngine::set_basic" + ctypedef multiset[rcp_const_basic] multiset_basic "SymEngine::multiset_basic" + cdef cppclass Basic: string __str__() nogil except + unsigned int hash() nogil except + vec_basic get_args() nogil int __cmp__(const Basic &o) nogil + ctypedef RCP[const Number] rcp_const_number "SymEngine::RCP" ctypedef unordered_map[int, rcp_const_basic] umap_int_basic "SymEngine::umap_int_basic" ctypedef unordered_map[int, rcp_const_basic].iterator umap_int_basic_iterator "SymEngine::umap_int_basic::iterator" @@ -193,9 +127,8 @@ cdef extern from "" namespace "SymEngine": bool eq(const Basic &a, const Basic &b) nogil except + bool neq(const Basic &a, const Basic &b) nogil except + - RCP[const Symbol] rcp_static_cast_Symbol "SymEngine::rcp_static_cast"(rcp_const_basic &b) nogil - RCP[const PySymbol] rcp_static_cast_PySymbol "SymEngine::rcp_static_cast"(rcp_const_basic &b) nogil + RCP[const PySymbol] rcp_static_cast_PySymbol "SymEngine::rcp_static_cast"(rcp_const_basic &b) nogil except + RCP[const Integer] rcp_static_cast_Integer "SymEngine::rcp_static_cast"(rcp_const_basic &b) nogil RCP[const Rational] rcp_static_cast_Rational "SymEngine::rcp_static_cast"(rcp_const_basic &b) nogil RCP[const Complex] rcp_static_cast_Complex "SymEngine::rcp_static_cast"(rcp_const_basic &b) nogil @@ -227,102 +160,17 @@ cdef extern from "" namespace "SymEngine": Ptr[RCP[Basic]] outArg(rcp_const_basic &arg) nogil Ptr[RCP[Integer]] outArg_Integer "SymEngine::outArg>"(RCP[const Integer] &arg) nogil - bool is_a_Add "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Mul "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Pow "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Integer "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Rational "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Complex "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Symbol "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Dummy "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Constant "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Infty "SymEngine::is_a"(const Basic &b) nogil - bool is_a_NaN "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Sin "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Cos "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Tan "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Cot "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Csc "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Sec "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ASin "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ACos "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ATan "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ACot "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ACsc "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ASec "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Sinh "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Cosh "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Tanh "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Coth "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Csch "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Sech "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ASinh "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ACosh "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ATanh "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ACoth "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ACsch "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ASech "SymEngine::is_a"(const Basic &b) nogil - bool is_a_FunctionSymbol "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Abs "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Max "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Min "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Gamma "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Derivative "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Subs "SymEngine::is_a"(const Basic &b) nogil - bool is_a_PyFunction "SymEngine::is_a"(const Basic &b) nogil - bool is_a_RealDouble "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ComplexDouble "SymEngine::is_a"(const Basic &b) nogil - bool is_a_RealMPFR "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ComplexMPC "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Log "SymEngine::is_a"(const Basic &b) nogil - bool is_a_BooleanAtom "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Equality "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Unequality "SymEngine::is_a"(const Basic &b) nogil - bool is_a_LessThan "SymEngine::is_a"(const Basic &b) nogil - bool is_a_StrictLessThan "SymEngine::is_a"(const Basic &b) nogil - bool is_a_PyNumber "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ATan2 "SymEngine::is_a"(const Basic &b) nogil - bool is_a_LambertW "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Zeta "SymEngine::is_a"(const Basic &b) nogil - bool is_a_DirichletEta "SymEngine::is_a"(const Basic &b) nogil - bool is_a_KroneckerDelta "SymEngine::is_a"(const Basic &b) nogil - bool is_a_LeviCivita "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Erf "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Erfc "SymEngine::is_a"(const Basic &b) nogil - bool is_a_LowerGamma "SymEngine::is_a"(const Basic &b) nogil - bool is_a_UpperGamma "SymEngine::is_a"(const Basic &b) nogil - bool is_a_LogGamma "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Beta "SymEngine::is_a"(const Basic &b) nogil - bool is_a_PolyGamma "SymEngine::is_a"(const Basic &b) nogil - bool is_a_PySymbol "SymEngine::is_a_sub"(const Basic &b) nogil - bool is_a_Sign "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Floor "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Ceiling "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Conjugate "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Interval "SymEngine::is_a"(const Basic &b) nogil - bool is_a_EmptySet "SymEngine::is_a"(const Basic &b) nogil - bool is_a_UniversalSet "SymEngine::is_a"(const Basic &b) nogil - bool is_a_FiniteSet "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Union "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Complement "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ConditionSet "SymEngine::is_a"(const Basic &b) nogil - bool is_a_ImageSet "SymEngine::is_a"(const Basic &b) nogil - - bool is_a_Piecewise "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Contains "SymEngine::is_a"(const Basic &b) nogil - bool is_a_And "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Not "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Or "SymEngine::is_a"(const Basic &b) nogil - bool is_a_Xor "SymEngine::is_a"(const Basic &b) nogil + bool is_a[T] (const Basic &b) nogil + bool is_a_sub[T] (const Basic &b) nogil rcp_const_basic expand(rcp_const_basic &o, bool deep) nogil except + void as_numer_denom(rcp_const_basic &x, const Ptr[RCP[Basic]] &numer, const Ptr[RCP[Basic]] &denom) nogil void as_real_imag(rcp_const_basic &x, const Ptr[RCP[Basic]] &real, const Ptr[RCP[Basic]] &imag) nogil void cse(vec_pair &replacements, vec_basic &reduced_exprs, const vec_basic &exprs) nogil except + cdef extern from "" namespace "SymEngine": - rcp_const_basic msubs (rcp_const_basic &x, const map_basic_basic &x) nogil - rcp_const_basic ssubs (rcp_const_basic &x, const map_basic_basic &x) nogil - rcp_const_basic xreplace (rcp_const_basic &x, const map_basic_basic &x) nogil + rcp_const_basic msubs (rcp_const_basic &x, const map_basic_basic &x) nogil except + + rcp_const_basic ssubs (rcp_const_basic &x, const map_basic_basic &x) nogil except + + rcp_const_basic xreplace (rcp_const_basic &x, const map_basic_basic &x) nogil except + cdef extern from "" namespace "SymEngine": rcp_const_basic diff "SymEngine::sdiff"(rcp_const_basic &arg, rcp_const_basic &x) nogil except + @@ -345,6 +193,12 @@ cdef extern from "" namespace "SymEngine": pass cdef cppclass NumberWrapper(Basic): pass + cdef tribool is_zero(const Basic &x) nogil + cdef tribool is_positive(const Basic &x) nogil + cdef tribool is_negative(const Basic &x) nogil + cdef tribool is_nonnegative(const Basic &x) nogil + cdef tribool is_nonpositive(const Basic &x) nogil + cdef tribool is_real(const Basic &x) nogil cdef extern from "pywrapper.h" namespace "SymEngine": cdef cppclass PyNumber(NumberWrapper): @@ -358,8 +212,11 @@ cdef extern from "pywrapper.h" namespace "SymEngine": cdef extern from "pywrapper.h" namespace "SymEngine": cdef cppclass PySymbol(Symbol): - PySymbol(string name, PyObject* pyobj) - PyObject* get_py_object() + PySymbol(string name, PyObject* pyobj, bool use_pickle) except + + PyObject* get_py_object() except + + + string wrapper_dumps(const Basic &x) nogil except + + rcp_const_basic wrapper_loads(const string &s) nogil except + cdef extern from "" namespace "SymEngine": cdef cppclass Integer(Number): @@ -367,6 +224,7 @@ cdef extern from "" namespace "SymEngine": Integer(integer_class i) nogil int compare(const Basic &o) nogil integer_class as_integer_class() nogil + RCP[Number] divint(const Integer &other) nogil cdef long mp_get_si(integer_class &i) nogil cdef double mp_get_d(integer_class &i) nogil cdef RCP[const Integer] integer(int i) nogil @@ -378,6 +236,8 @@ cdef extern from "" namespace "SymEngine": cdef extern from "" namespace "SymEngine": cdef cppclass Rational(Number): rational_class as_rational_class() nogil + @staticmethod + RCP[const Number] from_two_ints(const long n, const long d) nogil cdef double mp_get_d(rational_class &i) nogil cdef RCP[const Number] from_mpq "SymEngine::Rational::from_mpq"(rational_class r) nogil cdef void get_num_den(const Rational &rat, const Ptr[RCP[Integer]] &num, @@ -426,9 +286,9 @@ cdef extern from "" namespace "SymEngine": pass cdef extern from "" namespace "SymEngine": - cdef rcp_const_basic add(rcp_const_basic &a, rcp_const_basic &b) nogil except+ - cdef rcp_const_basic sub(rcp_const_basic &a, rcp_const_basic &b) nogil except+ - cdef rcp_const_basic add(const vec_basic &a) nogil except+ + cdef rcp_const_basic add(rcp_const_basic &a, rcp_const_basic &b) nogil except + + cdef rcp_const_basic sub(rcp_const_basic &a, rcp_const_basic &b) nogil except + + cdef rcp_const_basic add(const vec_basic &a) nogil except + cdef cppclass Add(Basic): void as_two_terms(const Ptr[RCP[Basic]] &a, const Ptr[RCP[Basic]] &b) @@ -436,21 +296,21 @@ cdef extern from "" namespace "SymEngine": const umap_basic_num &get_dict() cdef extern from "" namespace "SymEngine": - cdef rcp_const_basic mul(rcp_const_basic &a, rcp_const_basic &b) nogil except+ - cdef rcp_const_basic div(rcp_const_basic &a, rcp_const_basic &b) nogil except+ - cdef rcp_const_basic neg(rcp_const_basic &a) nogil except+ - cdef rcp_const_basic mul(const vec_basic &a) nogil except+ + cdef rcp_const_basic mul(rcp_const_basic &a, rcp_const_basic &b) nogil except + + cdef rcp_const_basic div(rcp_const_basic &a, rcp_const_basic &b) nogil except + + cdef rcp_const_basic neg(rcp_const_basic &a) nogil except + + cdef rcp_const_basic mul(const vec_basic &a) nogil except + cdef cppclass Mul(Basic): void as_two_terms(const Ptr[RCP[Basic]] &a, const Ptr[RCP[Basic]] &b) RCP[const Number] get_coef() const map_basic_basic &get_dict() - cdef RCP[const Mul] mul_from_dict "SymEngine::Mul::from_dict"(RCP[const Number] coef, map_basic_basic &&d) nogil + cdef RCP[const Mul] mul_from_dict "SymEngine::Mul::from_dict"(RCP[const Number] coef, map_basic_basic &d) nogil cdef extern from "" namespace "SymEngine": - cdef rcp_const_basic pow(rcp_const_basic &a, rcp_const_basic &b) nogil except+ - cdef rcp_const_basic sqrt(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic exp(rcp_const_basic &x) nogil except+ + cdef rcp_const_basic pow(rcp_const_basic &a, rcp_const_basic &b) nogil except + + cdef rcp_const_basic sqrt(rcp_const_basic &x) nogil except + + cdef rcp_const_basic exp(rcp_const_basic &x) nogil except + cdef cppclass Pow(Basic): rcp_const_basic get_base() nogil @@ -462,7 +322,7 @@ cdef extern from "" namespace "SymEngine": rcp_const_basic make_rcp_Symbol "SymEngine::make_rcp"(string name) nogil rcp_const_basic make_rcp_Dummy "SymEngine::make_rcp"() nogil rcp_const_basic make_rcp_Dummy "SymEngine::make_rcp"(string name) nogil - rcp_const_basic make_rcp_PySymbol "SymEngine::make_rcp"(string name, PyObject * pyobj) nogil + rcp_const_basic make_rcp_PySymbol "SymEngine::make_rcp"(string name, PyObject * pyobj, bool use_pickle) except + rcp_const_basic make_rcp_Constant "SymEngine::make_rcp"(string name) nogil rcp_const_basic make_rcp_Infty "SymEngine::make_rcp"(RCP[const Number] i) nogil rcp_const_basic make_rcp_NaN "SymEngine::make_rcp"() nogil @@ -474,9 +334,9 @@ cdef extern from "" namespace "SymEngine": void (*dec_ref)(void *), int (*comp)(void *, void *)) nogil rcp_const_basic make_rcp_RealDouble "SymEngine::make_rcp"(double x) nogil rcp_const_basic make_rcp_ComplexDouble "SymEngine::make_rcp"(double complex x) nogil - RCP[const PyModule] make_rcp_PyModule "SymEngine::make_rcp"(PyObject* (*) (rcp_const_basic x), \ - rcp_const_basic (*)(PyObject*), RCP[const Number] (*)(PyObject*, long bits), - rcp_const_basic (*)(PyObject*, rcp_const_basic)) nogil + RCP[const PyModule] make_rcp_PyModule "SymEngine::make_rcp"(PyObject* (*) (rcp_const_basic x) except +, \ + rcp_const_basic (*)(PyObject*) except +, RCP[const Number] (*)(PyObject*, long bits) except +, + rcp_const_basic (*)(PyObject*, rcp_const_basic) except +) except + rcp_const_basic make_rcp_PyNumber "SymEngine::make_rcp"(PyObject*, RCP[const PyModule] x) nogil RCP[const PyFunctionClass] make_rcp_PyFunctionClass "SymEngine::make_rcp"(PyObject* pyobject, string name, RCP[const PyModule] pymodule) nogil @@ -484,57 +344,58 @@ cdef extern from "" namespace "SymEngine": RCP[const PyFunctionClass] pyfunc_class, const PyObject* pyobject) nogil cdef extern from "" namespace "SymEngine": - cdef rcp_const_basic sin(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic cos(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic tan(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic cot(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic csc(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic sec(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic asin(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic acos(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic atan(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic acot(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic acsc(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic asec(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic sinh(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic cosh(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic tanh(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic coth(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic csch(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic sech(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic asinh(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic acosh(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic atanh(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic acoth(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic acsch(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic asech(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic function_symbol(string name, const vec_basic &arg) nogil except+ - cdef rcp_const_basic abs(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic max(const vec_basic &arg) nogil except+ - cdef rcp_const_basic min(const vec_basic &arg) nogil except+ - cdef rcp_const_basic gamma(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic atan2(rcp_const_basic &num, rcp_const_basic &den) nogil except+ - cdef rcp_const_basic lambertw(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic zeta(rcp_const_basic &s) nogil except+ - cdef rcp_const_basic zeta(rcp_const_basic &s, rcp_const_basic &a) nogil except+ - cdef rcp_const_basic dirichlet_eta(rcp_const_basic &s) nogil except+ - cdef rcp_const_basic kronecker_delta(rcp_const_basic &i, rcp_const_basic &j) nogil except+ - cdef rcp_const_basic levi_civita(const vec_basic &arg) nogil except+ - cdef rcp_const_basic erf(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic erfc(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic lowergamma(rcp_const_basic &s, rcp_const_basic &x) nogil except+ - cdef rcp_const_basic uppergamma(rcp_const_basic &s, rcp_const_basic &x) nogil except+ - cdef rcp_const_basic loggamma(rcp_const_basic &arg) nogil except+ - cdef rcp_const_basic beta(rcp_const_basic &x, rcp_const_basic &y) nogil except+ - cdef rcp_const_basic polygamma(rcp_const_basic &n, rcp_const_basic &x) nogil except+ - cdef rcp_const_basic digamma(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic trigamma(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic sign(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic floor(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic ceiling(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic conjugate(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic log(rcp_const_basic &x) nogil except+ - cdef rcp_const_basic log(rcp_const_basic &x, rcp_const_basic &y) nogil except+ + cdef rcp_const_basic sin(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic cos(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic tan(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic cot(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic csc(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic sec(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic asin(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic acos(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic atan(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic acot(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic acsc(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic asec(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic sinh(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic cosh(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic tanh(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic coth(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic csch(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic sech(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic asinh(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic acosh(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic atanh(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic acoth(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic acsch(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic asech(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic function_symbol(string name, const vec_basic &arg) nogil except + + cdef rcp_const_basic abs(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic max(const vec_basic &arg) nogil except + + cdef rcp_const_basic min(const vec_basic &arg) nogil except + + cdef rcp_const_basic gamma(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic atan2(rcp_const_basic &num, rcp_const_basic &den) nogil except + + cdef rcp_const_basic lambertw(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic zeta(rcp_const_basic &s) nogil except + + cdef rcp_const_basic zeta(rcp_const_basic &s, rcp_const_basic &a) nogil except + + cdef rcp_const_basic dirichlet_eta(rcp_const_basic &s) nogil except + + cdef rcp_const_basic kronecker_delta(rcp_const_basic &i, rcp_const_basic &j) nogil except + + cdef rcp_const_basic levi_civita(const vec_basic &arg) nogil except + + cdef rcp_const_basic erf(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic erfc(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic lowergamma(rcp_const_basic &s, rcp_const_basic &x) nogil except + + cdef rcp_const_basic uppergamma(rcp_const_basic &s, rcp_const_basic &x) nogil except + + cdef rcp_const_basic loggamma(rcp_const_basic &arg) nogil except + + cdef rcp_const_basic beta(rcp_const_basic &x, rcp_const_basic &y) nogil except + + cdef rcp_const_basic polygamma(rcp_const_basic &n, rcp_const_basic &x) nogil except + + cdef rcp_const_basic digamma(rcp_const_basic &x) nogil except + + cdef rcp_const_basic trigamma(rcp_const_basic &x) nogil except + + cdef rcp_const_basic sign(rcp_const_basic &x) nogil except + + cdef rcp_const_basic floor(rcp_const_basic &x) nogil except + + cdef rcp_const_basic ceiling(rcp_const_basic &x) nogil except + + cdef rcp_const_basic conjugate(rcp_const_basic &x) nogil except + + cdef rcp_const_basic log(rcp_const_basic &x) nogil except + + cdef rcp_const_basic log(rcp_const_basic &x, rcp_const_basic &y) nogil except + + cdef rcp_const_basic unevaluated_expr(rcp_const_basic &x) nogil except + cdef cppclass Function(Basic): pass @@ -702,71 +563,68 @@ cdef extern from "" namespace "SymEngine": cdef cppclass Conjugate(OneArgFunction): pass + cdef cppclass UnevaluatedExpr(OneArgFunction): + pass + cdef cppclass Log(Function): pass -IF HAVE_SYMENGINE_MPFR: - cdef extern from "mpfr.h": - ctypedef struct __mpfr_struct: - pass - ctypedef __mpfr_struct mpfr_t[1] - ctypedef __mpfr_struct* mpfr_ptr - ctypedef const __mpfr_struct* mpfr_srcptr - ctypedef long mpfr_prec_t - ctypedef enum mpfr_rnd_t: - MPFR_RNDN - MPFR_RNDZ - MPFR_RNDU - MPFR_RNDD - MPFR_RNDA - MPFR_RNDF - MPFR_RNDNA - - cdef extern from "" namespace "SymEngine": - cdef cppclass mpfr_class: - mpfr_class() nogil - mpfr_class(mpfr_prec_t prec) nogil - mpfr_class(string s, mpfr_prec_t prec, unsigned base) nogil - mpfr_class(mpfr_t m) nogil - mpfr_ptr get_mpfr_t() nogil - - cdef cppclass RealMPFR(Number): - RealMPFR(mpfr_class) nogil - mpfr_class as_mpfr() nogil - mpfr_prec_t get_prec() nogil - - RCP[const RealMPFR] real_mpfr(mpfr_class t) nogil -ELSE: - cdef extern from "" namespace "SymEngine": - cdef cppclass RealMPFR(Number): - pass - -IF HAVE_SYMENGINE_MPC: - cdef extern from "mpc.h": - ctypedef struct __mpc_struct: - pass - ctypedef __mpc_struct mpc_t[1] - ctypedef __mpc_struct* mpc_ptr - ctypedef const __mpc_struct* mpc_srcptr - - cdef extern from "" namespace "SymEngine": - cdef cppclass mpc_class: - mpc_class() nogil - mpc_class(mpfr_prec_t prec) nogil - mpc_class(mpc_t m) nogil - mpc_ptr get_mpc_t() nogil - mpc_class(string s, mpfr_prec_t prec, unsigned base) nogil - - cdef cppclass ComplexMPC(ComplexBase): - ComplexMPC(mpc_class) nogil - mpc_class as_mpc() nogil - mpfr_prec_t get_prec() nogil - - RCP[const ComplexMPC] complex_mpc(mpc_class t) nogil -ELSE: - cdef extern from "" namespace "SymEngine": - cdef cppclass ComplexMPC(Number): - pass +cdef extern from "": + # These come from mpfr.h, but don't include mpfr.h to not break + # builds without mpfr + ctypedef struct __mpfr_struct: + pass + ctypedef __mpfr_struct mpfr_t[1] + ctypedef __mpfr_struct* mpfr_ptr + ctypedef const __mpfr_struct* mpfr_srcptr + ctypedef long mpfr_prec_t + ctypedef enum mpfr_rnd_t: + MPFR_RNDN + MPFR_RNDZ + MPFR_RNDU + MPFR_RNDD + MPFR_RNDA + MPFR_RNDF + MPFR_RNDNA + +cdef extern from "" namespace "SymEngine": + cdef cppclass mpfr_class: + mpfr_class() nogil + mpfr_class(mpfr_prec_t prec) nogil + mpfr_class(string s, mpfr_prec_t prec, unsigned base) nogil + mpfr_class(mpfr_t m) nogil + mpfr_ptr get_mpfr_t() nogil + + cdef cppclass RealMPFR(Number): + RealMPFR(mpfr_class) nogil + mpfr_class as_mpfr() nogil + mpfr_prec_t get_prec() nogil + + RCP[const RealMPFR] real_mpfr(mpfr_class t) nogil + +cdef extern from "": + # These come from mpc.h, but don't include mpc.h to not break + # builds without mpc + ctypedef struct __mpc_struct: + pass + ctypedef __mpc_struct mpc_t[1] + ctypedef __mpc_struct* mpc_ptr + ctypedef const __mpc_struct* mpc_srcptr + +cdef extern from "" namespace "SymEngine": + cdef cppclass mpc_class: + mpc_class() nogil + mpc_class(mpfr_prec_t prec) nogil + mpc_class(mpc_t m) nogil + mpc_ptr get_mpc_t() nogil + mpc_class(string s, mpfr_prec_t prec, unsigned base) nogil + + cdef cppclass ComplexMPC(ComplexBase): + ComplexMPC(mpc_class) nogil + mpc_class as_mpc() nogil + mpfr_prec_t get_prec() nogil + + RCP[const ComplexMPC] complex_mpc(mpc_class t) nogil cdef extern from "" namespace "SymEngine": cdef cppclass MatrixBase: @@ -774,12 +632,16 @@ cdef extern from "" namespace "SymEngine": const unsigned ncols() nogil rcp_const_basic get(unsigned i, unsigned j) nogil rcp_const_basic set(unsigned i, unsigned j, rcp_const_basic e) nogil - string __str__() nogil except+ + string __str__() nogil except + bool eq(const MatrixBase &) nogil rcp_const_basic det() nogil void inv(MatrixBase &) + bool is_square() nogil void add_matrix(const MatrixBase &other, MatrixBase &result) nogil void mul_matrix(const MatrixBase &other, MatrixBase &result) nogil + void elementwise_mul_matrix(const MatrixBase &other, MatrixBase &result) nogil + void conjugate(MatrixBase &result) nogil + void conjugate_transpose(MatrixBase &result) nogil void add_scalar(rcp_const_basic k, MatrixBase &result) nogil void mul_scalar(rcp_const_basic k, MatrixBase &result) nogil void transpose(MatrixBase &result) nogil @@ -806,12 +668,20 @@ cdef extern from "" namespace "SymEngine": void col_insert(const DenseMatrix &B, unsigned pos) nogil void row_del(unsigned k) nogil void col_del(unsigned k) nogil + rcp_const_basic trace() nogil + tribool is_zero() nogil + tribool is_real() nogil + tribool is_diagonal() nogil + tribool is_symmetric() nogil + tribool is_hermitian() nogil + tribool is_weakly_diagonally_dominant() nogil + tribool is_strictly_diagonally_dominant() nogil + tribool is_positive_definite() nogil + tribool is_negative_definite() nogil - bool is_a_DenseMatrix "SymEngine::is_a"(const MatrixBase &b) nogil DenseMatrix* static_cast_DenseMatrix "static_cast"(const MatrixBase *a) void inverse_FFLU "SymEngine::inverse_fraction_free_LU"(const DenseMatrix &A, DenseMatrix &B) nogil except + - void pivoted_LU (const DenseMatrix &A, DenseMatrix &L, DenseMatrix &U, vector[int] &P) nogil except + void pivoted_LU_solve (const DenseMatrix &A, const DenseMatrix &b, DenseMatrix &x) nogil except + void inverse_GJ "SymEngine::inverse_gauss_jordan"(const DenseMatrix &A, DenseMatrix &B) nogil except + @@ -836,6 +706,9 @@ cdef extern from "" namespace "SymEngine": void dot(const DenseMatrix &A, const DenseMatrix &B, DenseMatrix &C) nogil void cross(const DenseMatrix &A, const DenseMatrix &B, DenseMatrix &C) nogil +cdef extern from "": + void pivoted_LU (const DenseMatrix &A, DenseMatrix &L, DenseMatrix &U, vector[pair[int, int]] &P) nogil except + + cdef extern from "" namespace "SymEngine": int probab_prime_p(const Integer &a, int reps) RCP[const Integer] nextprime (const Integer &a) nogil @@ -887,6 +760,7 @@ cdef extern from "" namespace "SymEngine": void powermod_list(vec_integer &powm, RCP[const Integer] a, RCP[const Number] b, RCP[const Integer] m) nogil +cdef extern from "" namespace "SymEngine": void sieve_generate_primes "SymEngine::Sieve::generate_primes"(vector[unsigned] &primes, unsigned limit) nogil cdef cppclass sieve_iterator "SymEngine::Sieve::iterator": @@ -895,7 +769,7 @@ cdef extern from "" namespace "SymEngine": unsigned next_prime() nogil cdef extern from "" namespace "SymEngine": - bool has_symbol(const Basic &b, const Symbol &x) nogil except + + bool has_symbol(const Basic &b, const Basic &x) nogil except + rcp_const_basic coeff(const Basic &b, const Basic &x, const Basic &n) nogil except + set_basic free_symbols(const Basic &b) nogil except + set_basic free_symbols(const MatrixBase &b) nogil except + @@ -903,7 +777,7 @@ cdef extern from "" namespace "SymEngine": cdef extern from "" namespace "SymEngine": cdef cppclass Boolean(Basic): - RCP[const Boolean] logical_not() nogil except+ + RCP[const Boolean] logical_not() nogil except + cdef cppclass BooleanAtom(Boolean): bool get_val() nogil cdef cppclass Relational(Boolean): @@ -931,38 +805,28 @@ cdef extern from "" namespace "SymEngine": rcp_const_basic boolTrue rcp_const_basic boolFalse - bool is_a_Relational(const Basic &b) nogil - cdef RCP[const Boolean] Eq(rcp_const_basic &lhs) nogil except+ - cdef RCP[const Boolean] Eq(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except+ - cdef RCP[const Boolean] Ne(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except+ - cdef RCP[const Boolean] Ge(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except+ - cdef RCP[const Boolean] Gt(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except+ - cdef RCP[const Boolean] Le(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except+ - cdef RCP[const Boolean] Lt(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except+ + cdef RCP[const Boolean] Eq(rcp_const_basic &lhs) nogil except + + cdef RCP[const Boolean] Eq(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except + + cdef RCP[const Boolean] Ne(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except + + cdef RCP[const Boolean] Ge(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except + + cdef RCP[const Boolean] Gt(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except + + cdef RCP[const Boolean] Le(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except + + cdef RCP[const Boolean] Lt(rcp_const_basic &lhs, rcp_const_basic &rhs) nogil except + ctypedef Boolean const_Boolean "const SymEngine::Boolean" ctypedef vector[pair[rcp_const_basic, RCP[const_Boolean]]] PiecewiseVec; ctypedef vector[RCP[Boolean]] vec_boolean "SymEngine::vec_boolean" - ctypedef set[RCP[Boolean], RCPBasicKeyLess] set_boolean "SymEngine::set_boolean" - cdef RCP[const Boolean] logical_and(set_boolean &s) nogil except+ - cdef RCP[const Boolean] logical_nand(set_boolean &s) nogil except+ - cdef RCP[const Boolean] logical_or(set_boolean &s) nogil except+ - cdef RCP[const Boolean] logical_not(RCP[const Boolean] &s) nogil except+ - cdef RCP[const Boolean] logical_nor(set_boolean &s) nogil except+ - cdef RCP[const Boolean] logical_xor(vec_boolean &s) nogil except+ - cdef RCP[const Boolean] logical_xnor(vec_boolean &s) nogil except+ + ctypedef set[RCP[Boolean]] set_boolean "SymEngine::set_boolean" + cdef RCP[const Boolean] logical_and(set_boolean &s) nogil except + + cdef RCP[const Boolean] logical_nand(set_boolean &s) nogil except + + cdef RCP[const Boolean] logical_or(set_boolean &s) nogil except + + cdef RCP[const Boolean] logical_not(RCP[const Boolean] &s) nogil except + + cdef RCP[const Boolean] logical_nor(set_boolean &s) nogil except + + cdef RCP[const Boolean] logical_xor(vec_boolean &s) nogil except + + cdef RCP[const Boolean] logical_xnor(vec_boolean &s) nogil except + cdef rcp_const_basic piecewise(PiecewiseVec vec) nogil except + cdef RCP[const Boolean] contains(rcp_const_basic &expr, RCP[const Set] &set) nogil -cdef extern from "" namespace "std": - cdef integer_class std_move_mpz "std::move" (integer_class) nogil - IF HAVE_SYMENGINE_MPFR: - cdef mpfr_class std_move_mpfr "std::move" (mpfr_class) nogil - IF HAVE_SYMENGINE_MPC: - cdef mpc_class std_move_mpc "std::move" (mpc_class) nogil - cdef map_basic_basic std_move_map_basic_basic "std::move" (map_basic_basic) nogil - cdef PiecewiseVec std_move_PiecewiseVec "std::move" (PiecewiseVec) nogil - cdef extern from "" namespace "SymEngine": cdef cppclass EvalfDomain: pass @@ -1010,13 +874,11 @@ cdef extern from "" namespace "SymEngine": ctypedef RCP[const SeriesCoeffInterface] rcp_const_seriescoeffinterface "SymEngine::RCP" rcp_const_seriescoeffinterface series "SymEngine::series"(rcp_const_basic &ex, RCP[const Symbol] &var, unsigned int prec) nogil except + -IF HAVE_SYMENGINE_MPFR: - cdef extern from "" namespace "SymEngine": - void eval_mpfr(mpfr_t result, const Basic &b, mpfr_rnd_t rnd) nogil except + +cdef extern from "" namespace "SymEngine": + void eval_mpfr(mpfr_t result, const Basic &b, mpfr_rnd_t rnd) nogil except + -IF HAVE_SYMENGINE_MPC: - cdef extern from "" namespace "SymEngine": - void eval_mpc(mpc_t result, const Basic &b, mpfr_rnd_t rnd) nogil except + +cdef extern from "" namespace "SymEngine": + void eval_mpc(mpc_t result, const Basic &b, mpfr_rnd_t rnd) nogil except + cdef extern from "" namespace "SymEngine": rcp_const_basic parse(const string &n) nogil except + @@ -1031,6 +893,12 @@ cdef extern from "" namespace "SymEngine": pass cdef cppclass EmptySet(Set): pass + cdef cppclass Reals(Set): + pass + cdef cppclass Rationals(Set): + pass + cdef cppclass Integers(Set): + pass cdef cppclass UniversalSet(Set): pass cdef cppclass FiniteSet(Set): @@ -1043,9 +911,12 @@ cdef extern from "" namespace "SymEngine": pass cdef cppclass ImageSet(Set): pass - ctypedef set[RCP[Set], RCPBasicKeyLess] set_set "SymEngine::set_set" + ctypedef set[RCP[Set]] set_set "SymEngine::set_set" cdef rcp_const_basic interval(RCP[const Number] &start, RCP[const Number] &end, bool l, bool r) nogil except + cdef RCP[const EmptySet] emptyset() nogil except + + cdef RCP[const Reals] reals() nogil except + + cdef RCP[const Rationals] rationals() nogil except + + cdef RCP[const Integers] integers() nogil except + cdef RCP[const UniversalSet] universalset() nogil except + cdef RCP[const Set] finiteset(set_basic &container) nogil except + cdef RCP[const Set] set_union(set_set &a) nogil except + @@ -1060,6 +931,25 @@ cdef extern from "" namespace "SymEngine": cdef RCP[const Set] solve(rcp_const_basic &f, RCP[const Symbol] &sym, RCP[const Set] &domain) nogil except + cdef vec_basic linsolve(const vec_basic &eqs, const vec_sym &syms) nogil except + +cdef extern from "symengine/tribool.h" namespace "SymEngine": + cdef cppclass tribool: + pass # tribool is an enum class + + cdef bool is_true(tribool) nogil + cdef bool is_false(tribool) nogil + cdef bool is_indeterminate(tribool) nogil + +cdef extern from "symengine/tribool.h" namespace "SymEngine::tribool": + cdef tribool indeterminate + cdef tribool trifalse + cdef tribool tritrue + cdef extern from "" namespace "SymEngine": string ccode(const Basic &x) nogil except + string latex(const Basic &x) nogil except + + string latex(const DenseMatrix &x, unsigned max_rows, unsigned max_cols) nogil except + + string unicode(const Basic &x) nogil except + + +## Defined in 'symengine/cwrapper.cpp' +cdef struct CRCPBasic: + rcp_const_basic m diff --git a/symengine/lib/symengine_wrapper.pxd b/symengine/lib/symengine_wrapper.in.pxd similarity index 87% rename from symengine/lib/symengine_wrapper.pxd rename to symengine/lib/symengine_wrapper.in.pxd index 653854dc0..03dbf1f01 100644 --- a/symengine/lib/symengine_wrapper.pxd +++ b/symengine/lib/symengine_wrapper.in.pxd @@ -1,11 +1,12 @@ +#cython: language_level=3 + cimport symengine from symengine cimport RCP, map_basic_basic, rcp_const_basic +from libcpp.memory cimport unique_ptr from libcpp.vector cimport vector from libcpp.string cimport string from libcpp cimport bool as cppbool -include "config.pxi" - cdef class Basic(object): cdef rcp_const_basic thisptr @@ -44,7 +45,7 @@ cdef class _Lambdify(object): cpdef unsafe_eval(sef, inp, out, unsigned nbroadcast=*) cdef class LambdaDouble(_Lambdify): - cdef vector[symengine.LambdaRealDoubleVisitor] lambda_double + cdef unique_ptr[symengine.LambdaRealDoubleVisitor] lambda_visitor cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse) cpdef unsafe_real(self, double[::1] inp, double[::1] out, int inp_offset=*, int out_offset=*) cpdef as_scipy_low_level_callable(self) @@ -54,7 +55,7 @@ cdef class LambdaDouble(_Lambdify): int inp_offset=*, int out_offset=*) cdef class LambdaComplexDouble(_Lambdify): - cdef vector[symengine.LambdaComplexDoubleVisitor] lambda_double + cdef unique_ptr[symengine.LambdaComplexDoubleVisitor] lambda_visitor cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse) cpdef unsafe_complex(self, double complex[::1] inp, double complex[::1] out, int inp_offset=*, int out_offset=*) @@ -63,7 +64,7 @@ IF HAVE_SYMENGINE_LLVM: cdef int opt_level cdef class LLVMDouble(_LLVMLambdify): - cdef vector[symengine.LLVMDoubleVisitor] lambda_double + cdef unique_ptr[symengine.LLVMDoubleVisitor] lambda_visitor cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse) cdef _load(self, const string &s) cpdef unsafe_real(self, double[::1] inp, double[::1] out, int inp_offset=*, int out_offset=*) @@ -71,14 +72,14 @@ IF HAVE_SYMENGINE_LLVM: cpdef as_ctypes(self) cdef class LLVMFloat(_LLVMLambdify): - cdef vector[symengine.LLVMFloatVisitor] lambda_double + cdef unique_ptr[symengine.LLVMFloatVisitor] lambda_visitor cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse) cdef _load(self, const string &s) cpdef unsafe_real(self, float[::1] inp, float[::1] out, int inp_offset=*, int out_offset=*) IF HAVE_SYMENGINE_LLVM_LONG_DOUBLE: cdef class LLVMLongDouble(_LLVMLambdify): - cdef vector[symengine.LLVMLongDoubleVisitor] lambda_double + cdef unique_ptr[symengine.LLVMLongDoubleVisitor] lambda_visitor cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse) cdef _load(self, const string &s) cpdef unsafe_real(self, long double[::1] inp, long double[::1] out, int inp_offset=*, int out_offset=*) diff --git a/symengine/lib/symengine_wrapper.pyx b/symengine/lib/symengine_wrapper.in.pyx similarity index 84% rename from symengine/lib/symengine_wrapper.pyx rename to symengine/lib/symengine_wrapper.in.pyx index cdf075045..6fe0ffa5e 100644 --- a/symengine/lib/symengine_wrapper.pyx +++ b/symengine/lib/symengine_wrapper.in.pyx @@ -3,8 +3,9 @@ cimport symengine from symengine cimport (RCP, pair, map_basic_basic, umap_int_basic, umap_int_basic_iterator, umap_basic_num, umap_basic_num_iterator, rcp_const_basic, std_pair_short_rcp_const_basic, - rcp_const_seriescoeffinterface) + rcp_const_seriescoeffinterface, CRCPBasic, tribool, is_indeterminate, is_true, is_false) from libcpp cimport bool as cppbool +from libcpp.utility cimport move from libcpp.string cimport string from libcpp.vector cimport vector from cpython cimport PyObject, Py_XINCREF, Py_XDECREF, \ @@ -17,14 +18,11 @@ from operator import mul from functools import reduce import collections import warnings -from symengine.compatibility import is_sequence +from symengine.utilities import is_sequence import os import sys - -if sys.version_info[0] == 2: - from collections import MutableMapping -else: - from collections.abc import MutableMapping +from cpython.pycapsule cimport PyCapsule_GetPointer +from collections.abc import MutableMapping try: import numpy as np @@ -33,20 +31,29 @@ try: except ImportError: have_numpy = False -include "config.pxi" class SympifyError(Exception): pass +cpdef object capsule_to_basic(object capsule): + cdef CRCPBasic *p = PyCapsule_GetPointer(capsule, NULL) + return c2py(p.m) + +cpdef void assign_to_capsule(object capsule, object value): + cdef CRCPBasic *p_cap = PyCapsule_GetPointer(capsule, NULL) + cdef Basic v = sympify(value) + p_cap.m = v.thisptr + cdef object c2py(rcp_const_basic o): cdef Basic r - if (symengine.is_a_Add(deref(o))): + cdef PyObject *obj + if (symengine.is_a[symengine.Add](deref(o))): r = Expr.__new__(Add) - elif (symengine.is_a_Mul(deref(o))): + elif (symengine.is_a[symengine.Mul](deref(o))): r = Expr.__new__(Mul) - elif (symengine.is_a_Pow(deref(o))): + elif (symengine.is_a[symengine.Pow](deref(o))): r = Expr.__new__(Pow) - elif (symengine.is_a_Integer(deref(o))): + elif (symengine.is_a[symengine.Integer](deref(o))): if (deref(symengine.rcp_static_cast_Integer(o)).is_zero()): return S.Zero elif (deref(symengine.rcp_static_cast_Integer(o)).is_one()): @@ -54,23 +61,26 @@ cdef object c2py(rcp_const_basic o): elif (deref(symengine.rcp_static_cast_Integer(o)).is_minus_one()): return S.NegativeOne r = Number.__new__(Integer) - elif (symengine.is_a_Rational(deref(o))): + elif (symengine.is_a[symengine.Rational](deref(o))): r = S.Half if (symengine.eq(deref(o), deref(r.thisptr))): return S.Half r = Number.__new__(Rational) - elif (symengine.is_a_Complex(deref(o))): + elif (symengine.is_a[symengine.Complex](deref(o))): r = S.ImaginaryUnit if (symengine.eq(deref(o), deref(r.thisptr))): return S.ImaginaryUnit r = Complex.__new__(Complex) - elif (symengine.is_a_Dummy(deref(o))): - r = Symbol.__new__(Dummy) - elif (symengine.is_a_Symbol(deref(o))): - if (symengine.is_a_PySymbol(deref(o))): - return (deref(symengine.rcp_static_cast_PySymbol(o)).get_py_object()) + elif (symengine.is_a[symengine.Dummy](deref(o))): + r = Dummy.__new__(Dummy) + elif (symengine.is_a[symengine.Symbol](deref(o))): + if (symengine.is_a_sub[symengine.PySymbol](deref(o))): + obj = deref(symengine.rcp_static_cast_PySymbol(o)).get_py_object() + result = (obj) + Py_XDECREF(obj); + return result r = Symbol.__new__(Symbol) - elif (symengine.is_a_Constant(deref(o))): + elif (symengine.is_a[symengine.Constant](deref(o))): r = S.Pi if (symengine.eq(deref(o), deref(r.thisptr))): return S.Pi @@ -87,164 +97,172 @@ cdef object c2py(rcp_const_basic o): if (symengine.eq(deref(o), deref(r.thisptr))): return S.EulerGamma r = Constant.__new__(Constant) - elif (symengine.is_a_Infty(deref(o))): + elif (symengine.is_a[symengine.Infty](deref(o))): if (deref(symengine.rcp_static_cast_Infty(o)).is_positive()): return S.Infinity elif (deref(symengine.rcp_static_cast_Infty(o)).is_negative()): return S.NegativeInfinity return S.ComplexInfinity - elif (symengine.is_a_NaN(deref(o))): + elif (symengine.is_a[symengine.NaN](deref(o))): return S.NaN - elif (symengine.is_a_PyFunction(deref(o))): + elif (symengine.is_a[symengine.PyFunction](deref(o))): r = PyFunction.__new__(PyFunction) - elif (symengine.is_a_FunctionSymbol(deref(o))): + elif (symengine.is_a[symengine.FunctionSymbol](deref(o))): r = FunctionSymbol.__new__(FunctionSymbol) - elif (symengine.is_a_Abs(deref(o))): + elif (symengine.is_a[symengine.Abs](deref(o))): r = Function.__new__(Abs) - elif (symengine.is_a_Max(deref(o))): + elif (symengine.is_a[symengine.Max](deref(o))): r = Function.__new__(Max) - elif (symengine.is_a_Min(deref(o))): + elif (symengine.is_a[symengine.Min](deref(o))): r = Function.__new__(Min) - elif (symengine.is_a_BooleanAtom(deref(o))): + elif (symengine.is_a[symengine.BooleanAtom](deref(o))): if (deref(symengine.rcp_static_cast_BooleanAtom(o)).get_val()): return S.true return S.false - elif (symengine.is_a_Equality(deref(o))): + elif (symengine.is_a[symengine.Equality](deref(o))): r = Relational.__new__(Equality) - elif (symengine.is_a_Unequality(deref(o))): + elif (symengine.is_a[symengine.Unequality](deref(o))): r = Relational.__new__(Unequality) - elif (symengine.is_a_LessThan(deref(o))): + elif (symengine.is_a[symengine.LessThan](deref(o))): r = Relational.__new__(LessThan) - elif (symengine.is_a_StrictLessThan(deref(o))): + elif (symengine.is_a[symengine.StrictLessThan](deref(o))): r = Relational.__new__(StrictLessThan) - elif (symengine.is_a_Gamma(deref(o))): + elif (symengine.is_a[symengine.Gamma](deref(o))): r = Function.__new__(Gamma) - elif (symengine.is_a_Derivative(deref(o))): + elif (symengine.is_a[symengine.Derivative](deref(o))): r = Expr.__new__(Derivative) - elif (symengine.is_a_Subs(deref(o))): + elif (symengine.is_a[symengine.Subs](deref(o))): r = Expr.__new__(Subs) - elif (symengine.is_a_RealDouble(deref(o))): + elif (symengine.is_a[symengine.RealDouble](deref(o))): r = Number.__new__(RealDouble) - elif (symengine.is_a_ComplexDouble(deref(o))): + elif (symengine.is_a[symengine.ComplexDouble](deref(o))): r = ComplexDouble.__new__(ComplexDouble) - elif (symengine.is_a_RealMPFR(deref(o))): + elif (symengine.is_a[symengine.RealMPFR](deref(o))): r = Number.__new__(RealMPFR) - elif (symengine.is_a_ComplexMPC(deref(o))): + elif (symengine.is_a[symengine.ComplexMPC](deref(o))): r = ComplexMPC.__new__(ComplexMPC) - elif (symengine.is_a_Log(deref(o))): + elif (symengine.is_a[symengine.Log](deref(o))): r = Function.__new__(Log) - elif (symengine.is_a_Sin(deref(o))): + elif (symengine.is_a[symengine.Sin](deref(o))): r = Function.__new__(Sin) - elif (symengine.is_a_Cos(deref(o))): + elif (symengine.is_a[symengine.Cos](deref(o))): r = Function.__new__(Cos) - elif (symengine.is_a_Tan(deref(o))): + elif (symengine.is_a[symengine.Tan](deref(o))): r = Function.__new__(Tan) - elif (symengine.is_a_Cot(deref(o))): + elif (symengine.is_a[symengine.Cot](deref(o))): r = Function.__new__(Cot) - elif (symengine.is_a_Csc(deref(o))): + elif (symengine.is_a[symengine.Csc](deref(o))): r = Function.__new__(Csc) - elif (symengine.is_a_Sec(deref(o))): + elif (symengine.is_a[symengine.Sec](deref(o))): r = Function.__new__(Sec) - elif (symengine.is_a_ASin(deref(o))): + elif (symengine.is_a[symengine.ASin](deref(o))): r = Function.__new__(ASin) - elif (symengine.is_a_ACos(deref(o))): + elif (symengine.is_a[symengine.ACos](deref(o))): r = Function.__new__(ACos) - elif (symengine.is_a_ATan(deref(o))): + elif (symengine.is_a[symengine.ATan](deref(o))): r = Function.__new__(ATan) - elif (symengine.is_a_ACot(deref(o))): + elif (symengine.is_a[symengine.ACot](deref(o))): r = Function.__new__(ACot) - elif (symengine.is_a_ACsc(deref(o))): + elif (symengine.is_a[symengine.ACsc](deref(o))): r = Function.__new__(ACsc) - elif (symengine.is_a_ASec(deref(o))): + elif (symengine.is_a[symengine.ASec](deref(o))): r = Function.__new__(ASec) - elif (symengine.is_a_Sinh(deref(o))): + elif (symengine.is_a[symengine.Sinh](deref(o))): r = Function.__new__(Sinh) - elif (symengine.is_a_Cosh(deref(o))): + elif (symengine.is_a[symengine.Cosh](deref(o))): r = Function.__new__(Cosh) - elif (symengine.is_a_Tanh(deref(o))): + elif (symengine.is_a[symengine.Tanh](deref(o))): r = Function.__new__(Tanh) - elif (symengine.is_a_Coth(deref(o))): + elif (symengine.is_a[symengine.Coth](deref(o))): r = Function.__new__(Coth) - elif (symengine.is_a_Csch(deref(o))): + elif (symengine.is_a[symengine.Csch](deref(o))): r = Function.__new__(Csch) - elif (symengine.is_a_Sech(deref(o))): + elif (symengine.is_a[symengine.Sech](deref(o))): r = Function.__new__(Sech) - elif (symengine.is_a_ASinh(deref(o))): + elif (symengine.is_a[symengine.ASinh](deref(o))): r = Function.__new__(ASinh) - elif (symengine.is_a_ACosh(deref(o))): + elif (symengine.is_a[symengine.ACosh](deref(o))): r = Function.__new__(ACosh) - elif (symengine.is_a_ATanh(deref(o))): + elif (symengine.is_a[symengine.ATanh](deref(o))): r = Function.__new__(ATanh) - elif (symengine.is_a_ACoth(deref(o))): + elif (symengine.is_a[symengine.ACoth](deref(o))): r = Function.__new__(ACoth) - elif (symengine.is_a_ACsch(deref(o))): + elif (symengine.is_a[symengine.ACsch](deref(o))): r = Function.__new__(ACsch) - elif (symengine.is_a_ASech(deref(o))): + elif (symengine.is_a[symengine.ASech](deref(o))): r = Function.__new__(ASech) - elif (symengine.is_a_ATan2(deref(o))): + elif (symengine.is_a[symengine.ATan2](deref(o))): r = Function.__new__(ATan2) - elif (symengine.is_a_LambertW(deref(o))): + elif (symengine.is_a[symengine.LambertW](deref(o))): r = Function.__new__(LambertW) - elif (symengine.is_a_Zeta(deref(o))): + elif (symengine.is_a[symengine.Zeta](deref(o))): r = Function.__new__(zeta) - elif (symengine.is_a_DirichletEta(deref(o))): + elif (symengine.is_a[symengine.Dirichlet_eta](deref(o))): r = Function.__new__(dirichlet_eta) - elif (symengine.is_a_KroneckerDelta(deref(o))): + elif (symengine.is_a[symengine.KroneckerDelta](deref(o))): r = Function.__new__(KroneckerDelta) - elif (symengine.is_a_LeviCivita(deref(o))): + elif (symengine.is_a[symengine.LeviCivita](deref(o))): r = Function.__new__(LeviCivita) - elif (symengine.is_a_Erf(deref(o))): + elif (symengine.is_a[symengine.Erf](deref(o))): r = Function.__new__(erf) - elif (symengine.is_a_Erfc(deref(o))): + elif (symengine.is_a[symengine.Erfc](deref(o))): r = Function.__new__(erfc) - elif (symengine.is_a_LowerGamma(deref(o))): + elif (symengine.is_a[symengine.LowerGamma](deref(o))): r = Function.__new__(lowergamma) - elif (symengine.is_a_UpperGamma(deref(o))): + elif (symengine.is_a[symengine.UpperGamma](deref(o))): r = Function.__new__(uppergamma) - elif (symengine.is_a_LogGamma(deref(o))): + elif (symengine.is_a[symengine.LogGamma](deref(o))): r = Function.__new__(loggamma) - elif (symengine.is_a_Beta(deref(o))): + elif (symengine.is_a[symengine.Beta](deref(o))): r = Function.__new__(beta) - elif (symengine.is_a_PolyGamma(deref(o))): + elif (symengine.is_a[symengine.PolyGamma](deref(o))): r = Function.__new__(polygamma) - elif (symengine.is_a_Sign(deref(o))): + elif (symengine.is_a[symengine.Sign](deref(o))): r = Function.__new__(sign) - elif (symengine.is_a_Floor(deref(o))): + elif (symengine.is_a[symengine.Floor](deref(o))): r = Function.__new__(floor) - elif (symengine.is_a_Ceiling(deref(o))): + elif (symengine.is_a[symengine.Ceiling](deref(o))): r = Function.__new__(ceiling) - elif (symengine.is_a_Conjugate(deref(o))): + elif (symengine.is_a[symengine.Conjugate](deref(o))): r = Function.__new__(conjugate) - elif (symengine.is_a_PyNumber(deref(o))): + elif (symengine.is_a[symengine.PyNumber](deref(o))): r = PyNumber.__new__(PyNumber) - elif (symengine.is_a_Piecewise(deref(o))): + elif (symengine.is_a[symengine.Piecewise](deref(o))): r = Function.__new__(Piecewise) - elif (symengine.is_a_Contains(deref(o))): + elif (symengine.is_a[symengine.Contains](deref(o))): r = Boolean.__new__(Contains) - elif (symengine.is_a_Interval(deref(o))): + elif (symengine.is_a[symengine.Interval](deref(o))): r = Set.__new__(Interval) - elif (symengine.is_a_EmptySet(deref(o))): + elif (symengine.is_a[symengine.EmptySet](deref(o))): r = Set.__new__(EmptySet) - elif (symengine.is_a_UniversalSet(deref(o))): + elif (symengine.is_a[symengine.Reals](deref(o))): + r = Set.__new__(Reals) + elif (symengine.is_a[symengine.Integers](deref(o))): + r = Set.__new__(Integers) + elif (symengine.is_a[symengine.Rationals](deref(o))): + r = Set.__new__(Rationals) + elif (symengine.is_a[symengine.UniversalSet](deref(o))): r = Set.__new__(UniversalSet) - elif (symengine.is_a_FiniteSet(deref(o))): + elif (symengine.is_a[symengine.FiniteSet](deref(o))): r = Set.__new__(FiniteSet) - elif (symengine.is_a_Union(deref(o))): + elif (symengine.is_a[symengine.Union](deref(o))): r = Set.__new__(Union) - elif (symengine.is_a_Complement(deref(o))): + elif (symengine.is_a[symengine.Complement](deref(o))): r = Set.__new__(Complement) - elif (symengine.is_a_ConditionSet(deref(o))): + elif (symengine.is_a[symengine.ConditionSet](deref(o))): r = Set.__new__(ConditionSet) - elif (symengine.is_a_ImageSet(deref(o))): + elif (symengine.is_a[symengine.ImageSet](deref(o))): r = Set.__new__(ImageSet) - elif (symengine.is_a_And(deref(o))): + elif (symengine.is_a[symengine.And](deref(o))): r = Boolean.__new__(And) - elif (symengine.is_a_Not(deref(o))): + elif (symengine.is_a[symengine.Not](deref(o))): r = Boolean.__new__(Not) - elif (symengine.is_a_Or(deref(o))): + elif (symengine.is_a[symengine.Or](deref(o))): r = Boolean.__new__(Or) - elif (symengine.is_a_Xor(deref(o))): + elif (symengine.is_a[symengine.Xor](deref(o))): r = Boolean.__new__(Xor) + elif (symengine.is_a[symengine.UnevaluatedExpr](deref(o))): + r = Function.__new__(UnevaluatedExpr) else: raise Exception("Unsupported SymEngine class.") r.thisptr = o @@ -280,9 +298,9 @@ def sympy2symengine(a, raise_error=False): if a._prec > 53: return RealMPFR(str(a), a._prec) else: - return RealDouble(float(str(a))) + return RealDouble(float(a)) ELSE: - return RealDouble(float(str(a))) + return RealDouble(float(a)) elif a is sympy.I: return I elif a is sympy.E: @@ -431,12 +449,18 @@ def sympy2symengine(a, raise_error=False): return function_symbol(name, *(a.args)) elif isinstance(a, (sympy.Piecewise)): return piecewise(*(a.args)) + elif a is sympy.S.Reals: + return S.Reals + elif a is sympy.S.Integers: + return S.Integers + elif a is sympy.S.Rationals: + return S.Rationals elif isinstance(a, sympy.Interval): return interval(*(a.args)) elif a is sympy.S.EmptySet: - return emptyset() + return S.EmptySet elif a is sympy.S.UniversalSet: - return universalset() + return S.UniversalSet elif isinstance(a, sympy.FiniteSet): return finiteset(*(a.args)) elif isinstance(a, sympy.Contains): @@ -451,6 +475,8 @@ def sympy2symengine(a, raise_error=False): return imageset(*(a.args)) elif isinstance(a, sympy.Function): return PyFunction(a, a.args, a.func, sympy_module) + elif isinstance(a, sympy.UnevaluatedExpr): + return UnevaluatedExpr(a.args[0]) elif isinstance(a, sympy.MatrixBase): row, col = a.shape v = [] @@ -540,6 +566,8 @@ def _sympify(a, raise_error=True): return Integer(a) elif isinstance(a, float): return RealDouble(a) + elif have_numpy and isinstance(a, (np.float16, np.float32)): + return RealDouble(a) elif isinstance(a, complex): return ComplexDouble(a) elif hasattr(a, '_symengine_'): @@ -635,6 +663,26 @@ class Singleton(object): def false(self): return false + @property + def EmptySet(self): + return empty_set_singleton + + @property + def UniversalSet(self): + return universal_set_singleton + + @property + def Integers(self): + return integers_singleton + + @property + def Rationals(self): + return rationals_singleton + + @property + def Reals(self): + return reals_singleton + S = Singleton() @@ -782,6 +830,10 @@ cdef list vec_pair_to_list(symengine.vec_pair& vec): return result +def load_basic(bytes s): + return c2py(symengine.wrapper_loads(s)) + + repr_latex=[False] cdef class Basic(object): @@ -792,6 +844,10 @@ cdef class Basic(object): def __repr__(self): return self.__str__() + def __reduce__(self): + cdef bytes s = symengine.wrapper_dumps(deref(self.thisptr)) + return (load_basic, (s,)) + def _repr_latex_(self): if repr_latex[0]: return "${}$".format(latex(self)) @@ -814,6 +870,12 @@ cdef class Basic(object): cdef Basic B = B_ return c2py(symengine.add(A.thisptr, B.thisptr)) + def __radd__(Basic self, b): + B_ = _sympify(b, False) + if B_ is None or isinstance(B_, MatrixBase): return NotImplemented + cdef Basic B = B_ + return c2py(symengine.add(B.thisptr, self.thisptr)) + def __sub__(a, b): cdef Basic A = _sympify(a, False) B_ = _sympify(b, False) @@ -821,6 +883,12 @@ cdef class Basic(object): cdef Basic B = B_ return c2py(symengine.sub(A.thisptr, B.thisptr)) + def __rsub__(Basic self, b): + B_ = _sympify(b, False) + if B_ is None or isinstance(B_, MatrixBase): return NotImplemented + cdef Basic B = B_ + return c2py(symengine.sub(B.thisptr, self.thisptr)) + def __mul__(a, b): cdef Basic A = _sympify(a, False) B_ = _sympify(b, False) @@ -828,18 +896,44 @@ cdef class Basic(object): cdef Basic B = B_ return c2py(symengine.mul(A.thisptr, B.thisptr)) + def __rmul__(Basic self, b): + B_ = _sympify(b, False) + if B_ is None or isinstance(B_, MatrixBase): return NotImplemented + cdef Basic B = B_ + return c2py(symengine.mul(B.thisptr, self.thisptr)) + def __truediv__(a, b): cdef Basic A = _sympify(a, False) - cdef Basic B = _sympify(b, False) - if A is None or B is None: return NotImplemented + B_ = _sympify(b, False) + if A is None or B_ is None or isinstance(B_, MatrixBase): return NotImplemented + cdef Basic B = B_ return c2py(symengine.div(A.thisptr, B.thisptr)) - # This is for Python 2.7 compatibility only: - def __div__(a, b): - cdef Basic A = _sympify(a, False) - cdef Basic B = _sympify(b, False) - if A is None or B is None: return NotImplemented - return c2py(symengine.div(A.thisptr, B.thisptr)) + def __rtruediv__(Basic self, b): + B_ = _sympify(b, False) + if B_ is None or isinstance(B_, MatrixBase): return NotImplemented + cdef Basic B = B_ + return c2py(symengine.div(B.thisptr, self.thisptr)) + + def __floordiv__(x, y): + return floor(x/y) + + def __rfloordiv__(y, x): + return floor(x/y) + + def __mod__(x, y): + return x - y * floor(x/y) + + def __rmod__(y, x): + return x - y * floor(x/y) + + def __divmod__(x, y): + f = floor(x/y) + return f, x - y * f + + def __rdivmod__(y, x): + f = floor(x/y) + return f, x - y * f def __pow__(a, b, c): if c is not None: @@ -849,9 +943,17 @@ cdef class Basic(object): if A is None or B is None: return NotImplemented return c2py(symengine.pow(A.thisptr, B.thisptr)) + def __rpow__(Basic self, b): + cdef Basic B = _sympify(b, False) + if B is None: return NotImplemented + return c2py(symengine.pow(B.thisptr, self.thisptr)) + def __neg__(Basic self not None): return c2py(symengine.neg(self.thisptr)) + def __pos__(self): + return self + def __abs__(Basic self not None): return c2py(symengine.abs(self.thisptr)) @@ -893,7 +995,7 @@ cdef class Basic(object): if (len(f) != 1): raise RuntimeError("Variable w.r.t should be given") return self._diff(f.pop()) - return diff(self, *args) + return _diff(self, *args) def subs_dict(Basic self not None, *args): warnings.warn("subs_dict() is deprecated. Use subs() instead", DeprecationWarning) @@ -1030,6 +1132,30 @@ cdef class Basic(object): def is_Matrix(self): return False + @property + def is_zero(self): + return is_zero(self) + + @property + def is_positive(self): + return is_positive(self) + + @property + def is_negative(self): + return is_negative(self) + + @property + def is_nonpositive(self): + return is_nonpositive(self) + + @property + def is_nonnegative(self): + return is_nonnegative(self) + + @property + def is_real(self): + return is_real(self) + def copy(self): return self @@ -1087,15 +1213,17 @@ cdef class Basic(object): def __int__(self): return int(float(self)) - def __long__(self): - return long(float(self)) - def __complex__(self): f = self.n(real=False) if not isinstance(f, (ComplexDouble, RealDouble)): raise TypeError("Can't convert expression to float") return complex(f) + def as_powers_dict(self): + d = collections.defaultdict(int) + d[self] = 1 + return d + def series(ex, x=None, x0=0, n=6, as_deg_coef_pair=False): # TODO: check for x0 an infinity, see sympy/core/expr.py @@ -1147,21 +1275,37 @@ cdef class Expr(Basic): cdef class Symbol(Expr): - """ Symbol is a class to store a symbolic variable with a given name. + Subclassing Symbol leads to a memory leak due to a cycle in reference counting. + To avoid this with a performance penalty, set the kwarg store_pickle=True + in the constructor and support the pickle protocol in the subclass by + implmenting __reduce__. """ def __init__(Basic self, name, *args, **kwargs): + cdef cppbool store_pickle; if type(self) == Symbol: self.thisptr = symengine.make_rcp_Symbol(name.encode("utf-8")) else: - self.thisptr = symengine.make_rcp_PySymbol(name.encode("utf-8"), self) + store_pickle = kwargs.pop("store_pickle", False) + if store_pickle: + # First set the pointer to a regular symbol so that when pickle.dumps + # is called when the PySymbol is created, methods like name works. + self.thisptr = symengine.make_rcp_Symbol(name.encode("utf-8")) + self.thisptr = symengine.make_rcp_PySymbol(name.encode("utf-8"), self, + store_pickle) def _sympy_(self): import sympy return sympy.Symbol(str(self)) + def __reduce__(self): + if type(self) == Symbol: + return Basic.__reduce__(self) + else: + raise NotImplementedError("pickling for Symbol subclass not implemented") + def _sage_(self): import sage.all as sage return sage.SR.symbol(str(self)) @@ -1201,7 +1345,7 @@ cdef class Dummy(Symbol): def _sympy_(self): import sympy - return sympy.Dummy(str(self)) + return sympy.Dummy(str(self)[1:]) @property def is_Dummy(self): @@ -1256,6 +1400,11 @@ cdef class ImaginaryUnit(Complex): def __cinit__(Basic self): self.thisptr = symengine.I + def as_powers_dict(self): + d = collections.defaultdict(int) + d[minus_one] = half + return d + I = ImaginaryUnit() @@ -1344,6 +1493,9 @@ cdef class Boolean(Expr): def logical_not(self): return c2py((deref(symengine.rcp_static_cast_Boolean(self.thisptr)).logical_not())) + def __bool__(self): + raise TypeError("cannot determine truth value of Boolean") + cdef class BooleanAtom(Boolean): @@ -1368,9 +1520,6 @@ cdef class BooleanTrue(BooleanAtom): def _sage_(self): return True - def __nonzero__(self): - return True - def __bool__(self): return True @@ -1449,6 +1598,10 @@ class Relational(Boolean): def is_Relational(self): return True + def __bool__(self): + raise TypeError("cannot determine truth value of Relational") + + Rel = Relational @@ -1471,7 +1624,9 @@ class Equality(Relational): def is_Equality(self): return True - func = __class__ + @property + def func(self): + return self.__class__ Eq = Equality @@ -1492,7 +1647,9 @@ class Unequality(Relational): s = self.args_as_sage() return sage.ne(*s) - func = __class__ + @property + def func(self): + return self.__class__ Ne = Unequality @@ -1561,10 +1718,6 @@ cdef class Number(Expr): def is_negative(Basic self): return deref(symengine.rcp_static_cast_Number(self.thisptr)).is_negative() - @property - def is_zero(Basic self): - return deref(symengine.rcp_static_cast_Number(self.thisptr)).is_zero() - @property def is_nonzero(self): return not (self.is_complex or self.is_zero) @@ -1593,7 +1746,25 @@ cdef class Number(Expr): class Rational(Number): def __new__(cls, p, q): - return Integer(p)/q + p = int(p) + q = int(q) + cdef int p_ + cdef int q_ + cdef symengine.integer_class p__ + cdef symengine.integer_class q__ + cdef string tmp + try: + # Try to convert p and q to int + p_ = p + q_ = q + return c2py(symengine.Rational.from_two_ints(p_, q_)); + except OverflowError: + # Too big, need to use mpz + tmp = hex(p).encode("utf-8") + symengine.mp_set_str(p__, tmp) + tmp = hex(q).encode("utf-8") + symengine.mp_set_str(q__, tmp) + return c2py(symengine.Integer(p__).divint(symengine.Integer(q__))) @property def is_Rational(self): @@ -1660,8 +1831,8 @@ class Integer(Rational): except OverflowError: # Too big, need to use mpz int_ok = False - tmp = str(i).encode("utf-8") - i__ = symengine.integer_class(tmp) + tmp = hex(i).encode("utf-8") + symengine.mp_set_str(i__, tmp) # Note: all other exceptions are left intact if int_ok: return c2py(symengine.integer(i_)) @@ -1710,18 +1881,9 @@ class Integer(Rational): else: return NotImplemented - def __floordiv__(x, y): - return quotient(x, y) - - def __mod__(x, y): - return mod(x, y) - - def __divmod__(x, y): - return quotient_mod(x, y) - def _sympy_(Basic self): import sympy - return sympy.Integer(deref(self.thisptr).__str__().decode("utf-8")) + return sympy.Integer(int(self)) def _sage_(Basic self): try: @@ -1732,7 +1894,9 @@ class Integer(Rational): return sage.Integer(str(self)) def __int__(Basic self): - return int(str(self)) + cdef string s = symengine.mp_get_hex_str( + deref(symengine.rcp_static_cast_Integer(self.thisptr)).as_integer_class()) + return int(s.decode("utf-8"), base=16) @property def p(self): @@ -1813,7 +1977,7 @@ class RealDouble(Float): def _sympy_(Basic self): import sympy - return sympy.Float(deref(self.thisptr).__str__().decode("utf-8")) + return sympy.Float(float(self)) def _sage_(Basic self): import sage.all as sage @@ -1872,7 +2036,7 @@ class RealMPFR(Float): cdef string i_ = str(i).encode("utf-8") cdef symengine.mpfr_class m m = symengine.mpfr_class(i_, prec, base) - return c2py(symengine.real_mpfr(symengine.std_move_mpfr(m))) + return c2py(symengine.real_mpfr(move[symengine.mpfr_class](m))) def get_prec(Basic self): return Integer(deref(symengine.rcp_static_cast_RealMPFR(self.thisptr)).get_prec()) @@ -1901,7 +2065,7 @@ cdef class ComplexMPC(ComplexBase): return cdef string i_ = ("(" + str(i) + " " + str(j) + ")").encode("utf-8") cdef symengine.mpc_class m = symengine.mpc_class(i_, prec, base) - self.thisptr = symengine.complex_mpc(symengine.std_move_mpc(m)) + self.thisptr = symengine.complex_mpc(move[symengine.mpc_class](m)) def _sympy_(self): import sympy @@ -1966,6 +2130,13 @@ cdef class NegativeInfinity(Number): import sage.all as sage return -sage.oo + def as_powers_dict(self): + d = collections.defaultdict(int) + d[minus_one] = 1 + d[oo] = 1 + return d + + minus_oo = NegativeInfinity() @@ -2160,10 +2331,32 @@ class Mul(AssocOp): d = collections.defaultdict(int) d[c2py(symengine.mul_from_dict(\ (one), - symengine.std_move_map_basic_basic(dict)))] =\ + move[symengine.map_basic_basic](dict)))] =\ c2py(deref(X).get_coef()) return d + def as_powers_dict(Basic self): + cdef RCP[const symengine.Mul] X = symengine.rcp_static_cast_Mul(self.thisptr) + cdef map_basic_basic m = deref(X).get_dict() + coef = c2py((deref(X).get_coef())) + if coef == 1: + d = collections.defaultdict(int) + else: + d = coef.as_powers_dict() + + it = m.begin() + it_end = m.end() + while it != it_end: + base = c2py((deref(it).first)) + exp = c2py((deref(it).second)) + if base.is_Rational and base.p < base.q and base.p > 0: + d[1/base] -= exp + else: + d[base] += exp + inc(it) + + return d + class Pow(Expr): @@ -2207,12 +2400,28 @@ class Pow(Expr): def func(self): return self.__class__ + def as_powers_dict(Basic self): + d = collections.defaultdict(int) + base, exp = self.as_base_exp() + if base.is_Rational and base.p < base.q and base.p > 0: + d[1/base] = -exp + else: + d[base] = exp + return d + class Function(Expr): def __new__(cls, *args, **kwargs): - if cls == Function and len(args) == 1: - return UndefFunction(args[0]) + if cls == Function: + nargs = len(args) + if nargs == 0: + raise TypeError("Required at least one argument to Function") + elif nargs == 1: + return UndefFunction(args[0]) + elif nargs > 1: + raise TypeError(f"Unexpected extra arguments {args[1:]}.") + return super(Function, cls).__new__(cls) @property @@ -2537,6 +2746,14 @@ class atan2(Function): cdef Basic Y = sympify(y) return c2py(symengine.atan2(X.thisptr, Y.thisptr)) + def _sympy_(self): + import sympy + return sympy.atan2(*self.args_as_sympy()) + + def _sage_(self): + import sage.all as sage + return sage.atan2(*self.args_as_sage()) + # For backwards compatibility Sin = sin @@ -2571,6 +2788,24 @@ add = Add mul = Mul +class UnevaluatedExpr(OneArgFunction): + def __new__(cls, x): + cdef Basic X = sympify(x) + return c2py(symengine.unevaluated_expr(X.thisptr)) + + @property + def is_number(self): + return self.args[0].is_number + + @property + def is_integer(self): + return self.args[0].is_integer + + @property + def is_finite(self): + return self.args[0].is_finite + + class Abs(OneArgFunction): @property @@ -2605,9 +2840,12 @@ class FunctionSymbol(Function): cdef RCP[const symengine.FunctionSymbol] X = \ symengine.rcp_static_cast_FunctionSymbol(self.thisptr) name = deref(X).get_name().decode("utf-8") - # In Python 2.7, function names cannot be unicode: return str(name) + @property + def name(Basic self): + return self.get_name() + def _sympy_(self): import sympy name = self.get_name() @@ -2616,7 +2854,7 @@ class FunctionSymbol(Function): def _sage_(self): import sage.all as sage name = self.get_name() - return sage.function(name, *self.args_as_sage()) + return sage.function(name)(*self.args_as_sage()) def func(self, *values): name = self.get_name() @@ -2637,7 +2875,7 @@ cdef rcp_const_basic pynumber_to_symengine(PyObject* o1): cdef PyObject* symengine_to_sage(rcp_const_basic o1): import sage.all as sage - t = sage.SR(c2py(o1)._sage_()) + t = c2py(o1)._sage_() Py_XINCREF(t) return (t) @@ -2691,7 +2929,10 @@ cdef class PyNumber(Number): def _sage_(self): import sage.all as sage - return sage.SR(self.pyobject()) + res = self.pyobject() + if hasattr(res, '_sage_'): + return res._sage_() + return res def pyobject(self): return deref(symengine.rcp_static_cast_PyNumber(self.thisptr)).get_py_object() @@ -2908,8 +3149,23 @@ cdef class Set(Expr): class Interval(Set): - def __new__(self, *args): - return interval(*args) + def __new__(self, *args, left_open=None, right_open=None): + list_args = list(args) + if ((left_open is not None) and (right_open is None)) or ((left_open is None) and (right_open is not None)): + raise ValueError("Both (or neither) keyword arguments for Interval should be specified") + if left_open is not None: + list_args.append(left_open) + if right_open is not None: + list_args.append(right_open) + return interval(*list_args) + + @property + def start(self): + return self.args[0] + + @property + def end(self): + return self.args[1] def _sympy_(self): import sympy @@ -2930,6 +3186,48 @@ class EmptySet(Set): return self.__class__ +class Reals(Set): + + def __new__(self): + return reals() + + def _sympy_(self): + import sympy + return sympy.S.Reals + + @property + def func(self): + return self.__class__ + + +class Rationals(Set): + + def __new__(self): + return rationals() + + def _sympy_(self): + import sympy + return sympy.S.Rationals + + @property + def func(self): + return self.__class__ + + +class Integers(Set): + + def __new__(self): + return integers() + + def _sympy_(self): + import sympy + return sympy.S.Integers + + @property + def func(self): + return self.__class__ + + class UniversalSet(Set): def __new__(self): @@ -3078,7 +3376,7 @@ cdef class DenseMatrixBase(MatrixBase): self.thisptr = new symengine.DenseMatrix(row, col) return if col is None: - v = row + v = sympify(row) row = 0 cdef symengine.vec_basic v_ cdef DenseMatrixBase A @@ -3114,6 +3412,8 @@ cdef class DenseMatrixBase(MatrixBase): raise ValueError("sizes don't match.") else: self.thisptr = new symengine.DenseMatrix(0, 0, v_) + elif col is not None and (row*col != v_.size()): + raise ValueError("Number of elements should equal rows*columns.") else: self.thisptr = new symengine.DenseMatrix(row, v_.size() / row, v_) @@ -3123,6 +3423,12 @@ cdef class DenseMatrixBase(MatrixBase): def __str__(self): return deref(self.thisptr).__str__().decode("utf-8") + def _repr_latex_(self): + if repr_latex[0]: + return "${}$".format(latex(self)) + else: + return None + def __add__(a, b): a = _sympify(a, False) b = _sympify(b, False) @@ -3138,6 +3444,19 @@ cdef class DenseMatrixBase(MatrixBase): raise ShapeError("Invalid shapes for matrix addition. Got %s %s" % (a_.shape, b_.shape)) return a_.add_matrix(b_) + def __radd__(MatrixBase self, a): + a = _sympify(a, False) + if not isinstance(a, MatrixBase): + return NotImplemented + cdef MatrixBase a_ = a + if (a_.shape == (0, 0)): + return self + if (self.shape == (0, 0)): + return a_ + if (self.shape != a_.shape): + raise ShapeError("Invalid shapes for matrix addition. Got %s %s" % (a_.shape, self.shape)) + return a_.add_matrix(self) + def __mul__(a, b): a = _sympify(a, False) b = _sympify(b, False) @@ -3155,11 +3474,49 @@ cdef class DenseMatrixBase(MatrixBase): else: return NotImplemented - def __div__(a, b): - return div_matrices(a, b) + def __rmul__(Basic self, a): + a = _sympify(a, False) + if isinstance(a, MatrixBase): + if (a.ncols() != self.nrows()): + raise ShapeError("Invalid shapes for matrix multiplication. Got %s %s" % (a.shape, self.shape)) + return a.mul_matrix(self) + elif isinstance(a, Basic): + return self.mul_scalar(a) + else: + return NotImplemented + + def __matmul__(a, b): + a = _sympify(a, False) + b = _sympify(b, False) + if (a.ncols() != b.nrows()): + raise ShapeError("Invalid shapes for matrix multiplication. Got %s %s" % (a.shape, b.shape)) + return a.mul_matrix(b) + + def __rmatmul__(Basic self, a): + a = _sympify(a, False) + if (a.ncols() != self.nrows()): + raise ShapeError("Invalid shapes for matrix multiplication. Got %s %s" % (a.shape, self.shape)) + return a.mul_matrix(self) def __truediv__(a, b): - return div_matrices(a, b) + a = _sympify(a, False) + b = _sympify(b, False) + if isinstance(a, MatrixBase): + if isinstance(b, MatrixBase): + return a.mul_matrix(b.inv()) + elif isinstance(b, Basic): + return a.mul_scalar(1/b) + else: + return NotImplemented + else: + return NotImplemented + + def __rtruediv__(Basic self, a): + a = _sympify(a, False) + if isinstance(a, MatrixBase): + return a.mul_matrix(self.inv()) + else: + return NotImplemented def __sub__(a, b): a = _sympify(a, False) @@ -3172,9 +3529,21 @@ cdef class DenseMatrixBase(MatrixBase): raise ShapeError("Invalid shapes for matrix subtraction. Got %s %s" % (a.shape, b.shape)) return a_.add_matrix(-b_) + def __rsub__(MatrixBase self, a): + a = _sympify(a, False) + if not isinstance(a, MatrixBase): + return NotImplemented + cdef MatrixBase a_ = a + if (a_.shape != self.shape): + raise ShapeError("Invalid shapes for matrix subtraction. Got %s %s" % (a.shape, self.shape)) + return a_.add_matrix(-self) + def __neg__(self): return self.mul_scalar(-1) + def __abs__(self): + return self.applyfunc(abs) + def __getitem__(self, item): if isinstance(item, slice): if (self.ncols() == 0 or self.nrows() == 0): @@ -3349,7 +3718,7 @@ cdef class DenseMatrixBase(MatrixBase): @property def is_square(self): - return self.rows == self.cols + return deref(self.thisptr).is_square() def nrows(self): return deref(self.thisptr).nrows() @@ -3445,16 +3814,34 @@ cdef class DenseMatrixBase(MatrixBase): def add_matrix(self, A): cdef MatrixBase A_ = sympify(A) - cdef DenseMatrixBase result = self.__class__(self.nrows(), self.ncols()) + if isinstance(A, ImmutableDenseMatrix): + cls = A.__class__ + else: + cls = self.__class__ + cdef DenseMatrixBase result = cls(self.nrows(), self.ncols()) deref(self.thisptr).add_matrix(deref(A_.thisptr), deref(result.thisptr)) return result def mul_matrix(self, A): cdef MatrixBase A_ = sympify(A) - cdef DenseMatrixBase result = self.__class__(self.nrows(), A.ncols()) + if isinstance(A, ImmutableDenseMatrix): + cls = A.__class__ + else: + cls = self.__class__ + cdef DenseMatrixBase result = cls(self.nrows(), A.ncols()) deref(self.thisptr).mul_matrix(deref(A_.thisptr), deref(result.thisptr)) return result + def multiply_elementwise(self, A): + cdef MatrixBase A_ = sympify(A) + if isinstance(A, ImmutableDenseMatrix): + cls = A.__class__ + else: + cls = self.__class__ + cdef DenseMatrixBase result = cls(self.nrows(), self.ncols()) + deref(self.thisptr).elementwise_mul_matrix(deref(A_.thisptr), deref(result.thisptr)) + return result + def add_scalar(self, k): cdef Basic k_ = sympify(k) cdef DenseMatrixBase result = self.__class__(self.nrows(), self.ncols()) @@ -3472,6 +3859,59 @@ cdef class DenseMatrixBase(MatrixBase): deref(self.thisptr).transpose(deref(result.thisptr)) return result + def conjugate(self): + cdef DenseMatrixBase result = self.__class__(self.nrows(), self.ncols()) + deref(self.thisptr).conjugate(deref(result.thisptr)) + return result + + def conjugate_transpose(self): + cdef DenseMatrixBase result = self.__class__(self.nrows(), self.ncols()) + deref(self.thisptr).conjugate_transpose(deref(result.thisptr)) + return result + + @property + def H(self): + return self.conjugate_transpose() + + def trace(self): + return c2py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).trace()) + + @property + def is_zero_matrix(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_zero()) + + @property + def is_real_matrix(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_real()) + + @property + def is_diagonal(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_diagonal()) + + @property + def is_symmetric(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_symmetric()) + + @property + def is_hermitian(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_hermitian()) + + @property + def is_weakly_diagonally_dominant(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_weakly_diagonally_dominant()) + + @property + def is_strongly_diagonally_dominant(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_strictly_diagonally_dominant()) + + @property + def is_positive_definite(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_positive_definite()) + + @property + def is_negative_definite(self): + return tribool_py(deref(symengine.static_cast_DenseMatrix(self.thisptr)).is_negative_definite()) + @property def T(self): return self.transpose() @@ -3506,7 +3946,7 @@ cdef class DenseMatrixBase(MatrixBase): return R def diff(self, *args): - return diff(self, *args) + return _diff(self, *args) #TODO: implement this in C++ def subs(self, *args): @@ -3536,6 +3976,19 @@ cdef class DenseMatrixBase(MatrixBase): deref(self.thisptr).LU(deref(L.thisptr), deref(U.thisptr)) return L, U + def LUdecomposition(self): + if self.rows != self.cols: + raise NotImplementedError("LU decomposition not implemented for non-square matrices yet.") + cdef DenseMatrixBase L = self.__class__(self.nrows(), self.ncols()) + cdef DenseMatrixBase U = self.__class__(self.nrows(), self.ncols()) + cdef vector[pair[int, int]] perm + symengine.pivoted_LU( + deref(symengine.static_cast_DenseMatrix(self.thisptr)), + deref(symengine.static_cast_DenseMatrix(L.thisptr)), + deref(symengine.static_cast_DenseMatrix(U.thisptr)), + perm) + return L, U, perm + def LDL(self): cdef DenseMatrixBase L = self.__class__(self.nrows(), self.ncols()) cdef DenseMatrixBase D = self.__class__(self.nrows(), self.ncols()) @@ -3624,7 +4077,7 @@ cdef class DenseMatrixBase(MatrixBase): l.append(c2py(A.get(i, j))._sympy_()) s.append(l) import sympy - return sympy.Matrix(s) + return sympy.ImmutableMatrix(s) def _sage_(self): s = [] @@ -3635,7 +4088,7 @@ cdef class DenseMatrixBase(MatrixBase): l.append(c2py(A.get(i, j))._sage_()) s.append(l) import sage.all as sage - return sage.Matrix(s) + return sage.Matrix(s, immutable=True) def dump_real(self, double[::1] out): cdef size_t ri, ci, nr, nc @@ -3696,19 +4149,6 @@ cdef class DenseMatrixBase(MatrixBase): return self.applyfunc(lambda x : x.expand(*args, **kwargs)) -def div_matrices(a, b): - a = _sympify(a, False) - b = _sympify(b, False) - if isinstance(a, MatrixBase): - if isinstance(b, MatrixBase): - return a.mul_matrix(b.inv()) - elif isinstance(b, Basic): - return a.mul_scalar(1/b) - else: - return NotImplemented - else: - return NotImplemented - class DenseMatrixBaseIter(object): def __init__(self, d): @@ -3775,6 +4215,12 @@ cdef class ImmutableDenseMatrix(DenseMatrixBase): def __setitem__(self, key, value): raise TypeError("Cannot set values of {}".format(self.__class__)) + def _applyfunc(self, f): + res = DenseMatrix(self) + res._applyfunc(f) + return ImmutableDenseMatrix(res) + + ImmutableMatrix = ImmutableDenseMatrix @@ -3869,21 +4315,51 @@ def module_cleanup(): import atexit atexit.register(module_cleanup) -def diff(ex, *args): - ex = sympify(ex) - prev = 0 + +def diff(expr, *args): + if isinstance(expr, MatrixBase): + # Don't sympify matrices so that mutable matrices + # return mutable matrices + return _diff(expr, *args) + return _diff(sympify(expr), *args) + + +def _diff(expr, *args): + cdef Basic prev cdef Basic b cdef size_t i - for x in args: - b = sympify(x) - if isinstance(b, Integer): - i = int(b) - 1 - for j in range(i): - ex = ex._diff(prev) + cdef size_t length = len(args) + + if not length: + return expr + + cdef size_t l = 0 + cdef Basic cur_arg, next_arg + cur_arg = sympify(args[l]) + + while l < length: + if isinstance(cur_arg, Integer): + raise ValueError("Unexpected integer argument") + + if l + 1 == length: + # No next argument, differentiate with no integer argument + return expr._diff(cur_arg) + + next_arg = sympify(args[l + 1]) + # Check if the next arg was derivative order + if isinstance(next_arg, Integer): + i = int(next_arg) + for _ in range(i): + expr = expr._diff(cur_arg) + l += 2 + if l == length: + return expr + cur_arg = sympify(args[l]) else: - ex = ex._diff(b) - prev = b - return ex + expr = expr._diff(cur_arg) + l += 1 + cur_arg = next_arg + def expand(x, deep=True): return sympify(x).expand(deep) @@ -4468,12 +4944,11 @@ def powermod_list(a, b, m): def has_symbol(obj, symbol=None): cdef Basic b = _sympify(obj) cdef Basic s = _sympify(symbol) - require(s, Symbol) + require(s, (Symbol, FunctionSymbol)) if (not symbol): return not b.free_symbols.empty() else: - return symengine.has_symbol(deref(b.thisptr), - deref(symengine.rcp_static_cast_Symbol(s.thisptr))) + return symengine.has_symbol(deref(b.thisptr), deref(s.thisptr)) cdef class _Lambdify(object): @@ -4654,24 +5129,24 @@ cdef class _Lambdify(object): return result -cdef double _scipy_callback_lambda_real(int n, double *x, void *user_data) nogil: +cdef double _scipy_callback_lambda_real(int n, double *x, void *user_data) noexcept nogil: cdef symengine.LambdaRealDoubleVisitor* lamb = user_data cdef double result deref(lamb).call(&result, x) return result -cdef void _ctypes_callback_lambda_real(double *output, const double *input, void *user_data) nogil: +cdef void _ctypes_callback_lambda_real(double *output, const double *input, void *user_data) noexcept nogil: cdef symengine.LambdaRealDoubleVisitor* lamb = user_data deref(lamb).call(output, input) IF HAVE_SYMENGINE_LLVM: - cdef double _scipy_callback_llvm_real(int n, double *x, void *user_data) nogil: + cdef double _scipy_callback_llvm_real(int n, double *x, void *user_data) noexcept nogil: cdef symengine.LLVMDoubleVisitor* lamb = user_data cdef double result deref(lamb).call(&result, x) return result - cdef void _ctypes_callback_llvm_real(double *output, const double *input, void *user_data) nogil: + cdef void _ctypes_callback_llvm_real(double *output, const double *input, void *user_data) noexcept nogil: cdef symengine.LLVMDoubleVisitor* lamb = user_data deref(lamb).call(output, input) @@ -4692,11 +5167,11 @@ cdef class LambdaDouble(_Lambdify): pass cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse): - self.lambda_double.resize(1) - self.lambda_double[0].init(args_, outs_, cse) + self.lambda_visitor.reset(new symengine.LambdaRealDoubleVisitor()) + deref(self.lambda_visitor).init(args_, outs_, cse) cpdef unsafe_real(self, double[::1] inp, double[::1] out, int inp_offset=0, int out_offset=0): - self.lambda_double[0].call(&out[out_offset], &inp[inp_offset]) + deref(self.lambda_visitor).call(&out[out_offset], &inp[inp_offset]) cpdef unsafe_eval(self, inp, out, unsigned nbroadcast=1): cdef double[::1] c_inp, c_out @@ -4704,7 +5179,7 @@ cdef class LambdaDouble(_Lambdify): c_inp = np.ascontiguousarray(inp.ravel(order=self.order), dtype=self.numpy_dtype) c_out = out for idx in range(nbroadcast): - self.lambda_double[0].call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) + deref(self.lambda_visitor).call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) cpdef as_scipy_low_level_callable(self): from ctypes import c_double, c_void_p, c_int, cast, POINTER, CFUNCTYPE @@ -4712,14 +5187,14 @@ cdef class LambdaDouble(_Lambdify): raise RuntimeError("SciPy LowLevelCallable supports only functions with 1 output") addr1 = cast(&_scipy_callback_lambda_real, CFUNCTYPE(c_double, c_int, POINTER(c_double), c_void_p)) - addr2 = cast(&self.lambda_double[0], c_void_p) + addr2 = cast(self.lambda_visitor.get(), c_void_p) return create_low_level_callable(self, addr1, addr2) cpdef as_ctypes(self): """ Returns a tuple with first element being a ctypes function with signature - void func(double * output, const double *input, void *user_data) + void func(double \*output, const double \*input, void \*user_data) and second element being a ctypes void pointer. This void pointer needs to be passed as input to the function as the third argument `user_data`. @@ -4727,7 +5202,7 @@ cdef class LambdaDouble(_Lambdify): from ctypes import c_double, c_void_p, c_int, cast, POINTER, CFUNCTYPE addr1 = cast(&_ctypes_callback_lambda_real, CFUNCTYPE(c_void_p, POINTER(c_double), POINTER(c_double), c_void_p)) - addr2 = cast(&self.lambda_double[0], c_void_p) + addr2 = cast(self.lambda_visitor.get(), c_void_p) return addr1, addr2 @@ -4737,11 +5212,11 @@ cdef class LambdaComplexDouble(_Lambdify): pass cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse): - self.lambda_double.resize(1) - self.lambda_double[0].init(args_, outs_, cse) + self.lambda_visitor.reset(new symengine.LambdaComplexDoubleVisitor()) + deref(self.lambda_visitor).init(args_, outs_, cse) cpdef unsafe_complex(self, double complex[::1] inp, double complex[::1] out, int inp_offset=0, int out_offset=0): - self.lambda_double[0].call(&out[out_offset], &inp[inp_offset]) + deref(self.lambda_visitor).call(&out[out_offset], &inp[inp_offset]) cpdef unsafe_eval(self, inp, out, unsigned nbroadcast=1): cdef double complex[::1] c_inp, c_out @@ -4749,7 +5224,7 @@ cdef class LambdaComplexDouble(_Lambdify): c_inp = np.ascontiguousarray(inp.ravel(order=self.order), dtype=self.numpy_dtype) c_out = out for idx in range(nbroadcast): - self.lambda_double[0].call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) + deref(self.lambda_visitor).call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) IF HAVE_SYMENGINE_LLVM: @@ -4758,23 +5233,23 @@ IF HAVE_SYMENGINE_LLVM: self.opt_level = opt_level cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse): - self.lambda_double.resize(1) - self.lambda_double[0].init(args_, outs_, cse, self.opt_level) + self.lambda_visitor.reset(new symengine.LLVMDoubleVisitor()) + deref(self.lambda_visitor).init(args_, outs_, cse, self.opt_level) cdef _load(self, const string &s): - self.lambda_double.resize(1) - self.lambda_double[0].loads(s) + self.lambda_visitor.reset(new symengine.LLVMDoubleVisitor()) + deref(self.lambda_visitor).loads(s) def __reduce__(self): """ Interface for pickle. Note that the resulting object is platform dependent. """ - cdef bytes s = self.lambda_double[0].dumps() + cdef bytes s = deref(self.lambda_visitor).dumps() return llvm_loading_func, (self.args_size, self.tot_out_size, self.out_shapes, self.real, \ self.n_exprs, self.order, self.accum_out_sizes, self.numpy_dtype, s) cpdef unsafe_real(self, double[::1] inp, double[::1] out, int inp_offset=0, int out_offset=0): - self.lambda_double[0].call(&out[out_offset], &inp[inp_offset]) + deref(self.lambda_visitor).call(&out[out_offset], &inp[inp_offset]) cpdef unsafe_eval(self, inp, out, unsigned nbroadcast=1): cdef double[::1] c_inp, c_out @@ -4782,7 +5257,7 @@ IF HAVE_SYMENGINE_LLVM: c_inp = np.ascontiguousarray(inp.ravel(order=self.order), dtype=self.numpy_dtype) c_out = out for idx in range(nbroadcast): - self.lambda_double[0].call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) + deref(self.lambda_visitor).call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) cpdef as_scipy_low_level_callable(self): from ctypes import c_double, c_void_p, c_int, cast, POINTER, CFUNCTYPE @@ -4792,7 +5267,7 @@ IF HAVE_SYMENGINE_LLVM: raise RuntimeError("SciPy LowLevelCallable supports only functions with 1 output") addr1 = cast(&_scipy_callback_llvm_real, CFUNCTYPE(c_double, c_int, POINTER(c_double), c_void_p)) - addr2 = cast(&self.lambda_double[0], c_void_p) + addr2 = cast(self.lambda_visitor.get(), c_void_p) return create_low_level_callable(self, addr1, addr2) cpdef as_ctypes(self): @@ -4809,7 +5284,7 @@ IF HAVE_SYMENGINE_LLVM: raise RuntimeError("Lambda function has to be real") addr1 = cast(&_ctypes_callback_llvm_real, CFUNCTYPE(c_void_p, POINTER(c_double), POINTER(c_double), c_void_p)) - addr2 = cast(&self.lambda_double[0], c_void_p) + addr2 = cast(self.lambda_visitor.get(), c_void_p) return addr1, addr2 cdef class LLVMFloat(_LLVMLambdify): @@ -4817,23 +5292,23 @@ IF HAVE_SYMENGINE_LLVM: self.opt_level = opt_level cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse): - self.lambda_double.resize(1) - self.lambda_double[0].init(args_, outs_, cse, self.opt_level) + self.lambda_visitor.reset(new symengine.LLVMFloatVisitor()) + deref(self.lambda_visitor).init(args_, outs_, cse, self.opt_level) cdef _load(self, const string &s): - self.lambda_double.resize(1) - self.lambda_double[0].loads(s) + self.lambda_visitor.reset(new symengine.LLVMFloatVisitor()) + deref(self.lambda_visitor).loads(s) def __reduce__(self): """ Interface for pickle. Note that the resulting object is platform dependent. """ - cdef bytes s = self.lambda_double[0].dumps() + cdef bytes s = deref(self.lambda_visitor).dumps() return llvm_float_loading_func, (self.args_size, self.tot_out_size, self.out_shapes, self.real, \ self.n_exprs, self.order, self.accum_out_sizes, self.numpy_dtype, s) cpdef unsafe_real(self, float[::1] inp, float[::1] out, int inp_offset=0, int out_offset=0): - self.lambda_double[0].call(&out[out_offset], &inp[inp_offset]) + deref(self.lambda_visitor).call(&out[out_offset], &inp[inp_offset]) cpdef unsafe_eval(self, inp, out, unsigned nbroadcast=1): cdef float[::1] c_inp, c_out @@ -4841,7 +5316,7 @@ IF HAVE_SYMENGINE_LLVM: c_inp = np.ascontiguousarray(inp.ravel(order=self.order), dtype=self.numpy_dtype) c_out = out for idx in range(nbroadcast): - self.lambda_double[0].call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) + deref(self.lambda_visitor).call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) IF HAVE_SYMENGINE_LLVM_LONG_DOUBLE: cdef class LLVMLongDouble(_LLVMLambdify): @@ -4849,23 +5324,23 @@ IF HAVE_SYMENGINE_LLVM: self.opt_level = opt_level cdef _init(self, symengine.vec_basic& args_, symengine.vec_basic& outs_, cppbool cse): - self.lambda_double.resize(1) - self.lambda_double[0].init(args_, outs_, cse, self.opt_level) + self.lambda_visitor.reset(new symengine.LLVMLongDoubleVisitor()) + deref(self.lambda_visitor).init(args_, outs_, cse, self.opt_level) cdef _load(self, const string &s): - self.lambda_double.resize(1) - self.lambda_double[0].loads(s) + self.lambda_visitor.reset(new symengine.LLVMLongDoubleVisitor()) + deref(self.lambda_visitor).loads(s) def __reduce__(self): """ Interface for pickle. Note that the resulting object is platform dependent. """ - cdef bytes s = self.lambda_double[0].dumps() + cdef bytes s = deref(self.lambda_visitor).dumps() return llvm_long_double_loading_func, (self.args_size, self.tot_out_size, self.out_shapes, self.real, \ self.n_exprs, self.order, self.accum_out_sizes, self.numpy_dtype, s) cpdef unsafe_real(self, long double[::1] inp, long double[::1] out, int inp_offset=0, int out_offset=0): - self.lambda_double[0].call(&out[out_offset], &inp[inp_offset]) + deref(self.lambda_visitor).call(&out[out_offset], &inp[inp_offset]) cpdef unsafe_eval(self, inp, out, unsigned nbroadcast=1): cdef long double[::1] c_inp, c_out @@ -4873,7 +5348,7 @@ IF HAVE_SYMENGINE_LLVM: c_inp = np.ascontiguousarray(inp.ravel(order=self.order), dtype=self.numpy_dtype) c_out = out for idx in range(nbroadcast): - self.lambda_double[0].call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) + deref(self.lambda_visitor).call(&c_out[idx*self.tot_out_size], &c_inp[idx*self.args_size]) def llvm_loading_func(*args): return LLVMDouble(args, _load=True) @@ -4902,7 +5377,7 @@ def Lambdify(args, *exprs, cppbool real=True, backend=None, order='C', Whether datatype is ``double`` (``double complex`` otherwise). backend : str 'llvm' or 'lambda'. When ``None`` the environment variable - 'SYMENGINE_LAMBDIFY_BACKEND' is used (taken as 'lambda' if unset). + 'SYMENGINE_LAMBDIFY_BACKEND' is used (taken as 'llvm' if available, otherwise 'lambda'). order : 'C' or 'F' C- or Fortran-contiguous memory layout. Note that this affects broadcasting: e.g. a (m, n) matrix taking 3 arguments and given a @@ -4934,7 +5409,11 @@ def Lambdify(args, *exprs, cppbool real=True, backend=None, order='C', """ if backend is None: - backend = os.getenv('SYMENGINE_LAMBDIFY_BACKEND', "lambda") + IF HAVE_SYMENGINE_LLVM: + backend_default = 'llvm' if real else 'lambda' + ELSE: + backend_default = 'lambda' + backend = os.getenv('SYMENGINE_LAMBDIFY_BACKEND', backend_default) if backend == "llvm": IF HAVE_SYMENGINE_LLVM: if dtype == None: @@ -4950,7 +5429,7 @@ def Lambdify(args, *exprs, cppbool real=True, backend=None, order='C', raise ValueError("Long double not supported on this platform") else: raise ValueError("Unknown numpy dtype.") - + if as_scipy: return ret.as_scipy_low_level_callable() return ret @@ -4993,7 +5472,7 @@ def piecewise(*v): p.first = e.thisptr p.second = symengine.rcp_static_cast_Boolean(b.thisptr) vec.push_back(p) - return c2py(symengine.piecewise(symengine.std_move_PiecewiseVec(vec))) + return c2py(symengine.piecewise(move[symengine.PiecewiseVec](vec))) def interval(start, end, left_open=False, right_open=False): @@ -5018,6 +5497,18 @@ def universalset(): return c2py((symengine.universalset())) +def reals(): + return c2py((symengine.reals())) + + +def rationals(): + return c2py((symengine.rationals())) + + +def integers(): + return c2py((symengine.integers())) + + def finiteset(*args): cdef symengine.set_basic s cdef Basic e_ @@ -5034,6 +5525,53 @@ def contains(expr, sset): return c2py((symengine.contains(expr_.thisptr, s))) +cdef tribool_py(tribool value): + if is_indeterminate(value): + return None + elif is_true(value): + return True + elif is_false(value): + return False + else: + raise RuntimeError("Internal error in symengine.py, tribool got a fourth value.") + + +def is_zero(expr): + cdef Basic expr_ = sympify(expr) + cdef tribool tbool = symengine.is_zero(deref(expr_.thisptr)) + return tribool_py(tbool) + + +def is_positive(expr): + cdef Basic expr_ = sympify(expr) + cdef tribool tbool = symengine.is_positive(deref(expr_.thisptr)) + return tribool_py(tbool) + + +def is_negative(expr): + cdef Basic expr_ = sympify(expr) + cdef tribool tbool = symengine.is_negative(deref(expr_.thisptr)) + return tribool_py(tbool) + + +def is_nonpositive(expr): + cdef Basic expr_ = sympify(expr) + cdef tribool tbool = symengine.is_nonpositive(deref(expr_.thisptr)) + return tribool_py(tbool) + + +def is_nonnegative(expr): + cdef Basic expr_ = sympify(expr) + cdef tribool tbool = symengine.is_nonnegative(deref(expr_.thisptr)) + return tribool_py(tbool) + + +def is_real(expr): + cdef Basic expr_ = sympify(expr) + cdef tribool tbool = symengine.is_real(deref(expr_.thisptr)) + return tribool_py(tbool) + + def set_union(*args): cdef symengine.set_set s cdef Set e_ @@ -5083,6 +5621,13 @@ def imageset(sym, expr, base): return c2py((symengine.imageset(sym_.thisptr, expr_.thisptr, b))) +universal_set_singleton = UniversalSet() +integers_singleton = Integers() +rationals_singleton = Rationals() +reals_singleton = Reals() +empty_set_singleton = EmptySet() + + def solve(f, sym, domain=None): cdef Basic f_ = sympify(f) cdef Basic sym_ = sympify(sym) @@ -5126,8 +5671,19 @@ def cse(exprs): return (vec_pair_to_list(replacements), vec_basic_to_list(reduced_exprs)) def latex(expr): - cdef Basic expr_ = sympify(expr) - return symengine.latex(deref(expr_.thisptr)).decode("utf-8") + cdef DenseMatrixBase mat_expr + cdef Basic basic_expr + if isinstance(expr, DenseMatrixBase): + mat_expr = expr + return symengine.latex(deref(symengine.static_cast_DenseMatrix(mat_expr.thisptr)), 20, 12).decode("utf-8") + else: + basic_expr = sympify(expr) + return symengine.latex(deref(basic_expr.thisptr)).decode("utf-8") + +def unicode(expr): + cdef Basic basic_expr + basic_expr = sympify(expr) + return symengine.unicode(deref(basic_expr.thisptr)).decode("utf-8") cdef _flattened_vec(symengine.vec_basic &vec, exprs): cdef Basic b diff --git a/symengine/printing.py b/symengine/printing.py index c6a06ef91..4d16d0f26 100644 --- a/symengine/printing.py +++ b/symengine/printing.py @@ -1,11 +1,10 @@ -from symengine.lib.symengine_wrapper import ccode, sympify, Basic -import symengine.lib.symengine_wrapper +from .lib.symengine_wrapper import ccode, sympify, Basic, repr_latex as _repr_latex class CCodePrinter: def doprint(self, expr, assign_to=None): if not isinstance(assign_to, (Basic, type(None), str)): - raise TypeError("{0} cannot assign to object of type {1}".format( + raise TypeError("{} cannot assign to object of type {}".format( type(self).__name__, type(assign_to))) expr = sympify(expr) @@ -16,11 +15,11 @@ def doprint(self, expr, assign_to=None): assign_to = str(assign_to) if not expr.is_Matrix: - return "{} = {};".format(assign_to, ccode(expr)) + return f"{assign_to} = {ccode(expr)};" code_lines = [] for i, element in enumerate(expr): - code_line = '{}[{}] = {};'.format(assign_to, i, element) + code_line = f'{assign_to}[{i}] = {element};' code_lines.append(code_line) return '\n'.join(code_lines) @@ -29,6 +28,6 @@ def init_printing(pretty_print=True, use_latex=True): if pretty_print: if not use_latex: raise RuntimeError("Only latex is supported for pretty printing") - symengine.lib.symengine_wrapper.repr_latex[0] = True + _repr_latex[0] = True else: - symengine.lib.symengine_wrapper.repr_latex[0] = False + _repr_latex[0] = False diff --git a/symengine/test_utilities.py b/symengine/test_utilities.py new file mode 100644 index 000000000..cd3eeaa4c --- /dev/null +++ b/symengine/test_utilities.py @@ -0,0 +1,95 @@ +import sys + +try: + import py + from py.test import skip, raises + USE_PYTEST = getattr(sys, '_running_pytest', False) +except ImportError: + USE_PYTEST = False + +if not USE_PYTEST: + def raises(expectedException, code=None): + """ + Tests that ``code`` raises the exception ``expectedException``. + + ``code`` may be a callable, such as a lambda expression or function + name. + + If ``code`` is not given or None, ``raises`` will return a context + manager for use in ``with`` statements; the code to execute then + comes from the scope of the ``with``. + + ``raises()`` does nothing if the callable raises the expected + exception, otherwise it raises an AssertionError. + + Examples + ======== + + >>> from symengine.pytest import raises + + >>> raises(ZeroDivisionError, lambda: 1/0) + >>> raises(ZeroDivisionError, lambda: 1/2) + Traceback (most recent call last): + ... + AssertionError: DID NOT RAISE + + >>> with raises(ZeroDivisionError): + ... n = 1/0 + >>> with raises(ZeroDivisionError): + ... n = 1/2 + Traceback (most recent call last): + ... + AssertionError: DID NOT RAISE + + Note that you cannot test multiple statements via + ``with raises``: + + >>> with raises(ZeroDivisionError): + ... n = 1/0 # will execute and raise, aborting the ``with`` + ... n = 9999/0 # never executed + + This is just what ``with`` is supposed to do: abort the + contained statement sequence at the first exception and let + the context manager deal with the exception. + + To test multiple statements, you'll need a separate ``with`` + for each: + + >>> with raises(ZeroDivisionError): + ... n = 1/0 # will execute and raise + >>> with raises(ZeroDivisionError): + ... n = 9999/0 # will also execute and raise + + """ + if code is None: + return RaisesContext(expectedException) + elif callable(code): + try: + code() + except expectedException: + return + raise AssertionError("DID NOT RAISE") + elif isinstance(code, str): + raise TypeError( + '\'raises(xxx, "code")\' has been phased out; ' + 'change \'raises(xxx, "expression")\' ' + 'to \'raises(xxx, lambda: expression)\', ' + '\'raises(xxx, "statement")\' ' + 'to \'with raises(xxx): statement\'') + else: + raise TypeError( + 'raises() expects a callable for the 2nd argument.') + + class RaisesContext: + def __init__(self, expectedException): + self.expectedException = expectedException + + def __enter__(self): + return None + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None: + raise AssertionError("DID NOT RAISE") + return issubclass(exc_type, self.expectedException) + + diff --git a/symengine/tests/CMakeLists.txt b/symengine/tests/CMakeLists.txt index ebd4dfaa2..4f19093b7 100644 --- a/symengine/tests/CMakeLists.txt +++ b/symengine/tests/CMakeLists.txt @@ -1,13 +1,19 @@ set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine/tests) -install(FILES __init__.py +install( + FILES + __init__.py test_arit.py + test_cse.py test_dict_basic.py test_eval.py test_expr.py test_functions.py - test_number.py + test_lambdify.py + test_logic.py test_matrices.py test_ntheory.py + test_number.py + test_pickling.py test_printing.py test_sage.py test_series_expansion.py @@ -16,10 +22,9 @@ install(FILES __init__.py test_subs.py test_symbol.py test_sympify.py + test_sympy_compat.py test_sympy_conv.py test_var.py - test_lambdify.py - test_sympy_compat.py - test_logic.py - DESTINATION ${PY_PATH} - ) + DESTINATION + ${PY_PATH} +) diff --git a/symengine/tests/test_arit.py b/symengine/tests/test_arit.py index 65f8062e4..931b8adb5 100644 --- a/symengine/tests/test_arit.py +++ b/symengine/tests/test_arit.py @@ -1,7 +1,7 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine import (Symbol, Integer, Add, Mul, Pow, Rational, sqrt, - symbols, S, I, count_ops) + symbols, S, I, count_ops, floor) def test_arit1(): @@ -95,6 +95,12 @@ def test_arit8(): assert (2*y**(-2*x**2)) * (3*y**(2*x**2)) == 6 +def test_unary(): + x = Symbol("x") + assert -x == 0 - x + assert +x == x + + def test_expand1(): x = Symbol("x") y = Symbol("y") @@ -133,27 +139,27 @@ def test_args(): y = Symbol("y") assert (x**2).args == (x, 2) assert (x**2 + 5).args == (5, x**2) - assert set((x**2 + 2*x*y + 5).args) == set((x**2, 2*x*y, Integer(5))) + assert set((x**2 + 2*x*y + 5).args) == {x**2, 2*x*y, Integer(5)} assert (2*x**2).args == (2, x**2) - assert set((2*x**2*y).args) == set((Integer(2), x**2, y)) + assert set((2*x**2*y).args) == {Integer(2), x**2, y} def test_atoms(): x = Symbol("x") y = Symbol("y") z = Symbol("z") - assert (x**2).atoms() == set([x]) - assert (x**2).atoms(Symbol) == set([x]) - assert (x ** y + z).atoms() == set([x, y, z]) - assert (x**y + z).atoms(Symbol) == set([x, y, z]) + assert (x**2).atoms() == {x} + assert (x**2).atoms(Symbol) == {x} + assert (x ** y + z).atoms() == {x, y, z} + assert (x**y + z).atoms(Symbol) == {x, y, z} def test_free_symbols(): x = Symbol("x") y = Symbol("y") z = Symbol("z") - assert (x**2).free_symbols == set([x]) - assert (x**y + z).free_symbols == set([x, y, z]) + assert (x**2).free_symbols == {x} + assert (x**y + z).free_symbols == {x, y, z} def test_as_numer_denom(): @@ -165,12 +171,23 @@ def test_as_numer_denom(): assert x == Integer(-5) assert y == Integer(1) + +def test_floor(): + exprs = [Symbol("x"), Symbol("y"), Integer(2), Rational(-3, 5), Integer(-3)] + + for x in exprs: + for y in exprs: + assert x // y == floor(x / y) + assert x == y * (x // y) + x % y + + def test_as_real_imag(): x, y = (5 + 6 * I).as_real_imag() assert x == 5 assert y == 6 + def test_from_args(): x = Symbol("x") y = Symbol("y") diff --git a/symengine/tests/test_dict_basic.py b/symengine/tests/test_dict_basic.py index 7a399a25a..d1e5ab633 100644 --- a/symengine/tests/test_dict_basic.py +++ b/symengine/tests/test_dict_basic.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine import symbols, DictBasic, sin, Integer @@ -18,11 +18,11 @@ def test_DictBasic(): assert d[2*z] == x if 2*z not in d: assert False - assert set(d.items()) == set([(2*z, x), (x, Integer(2)), (y, z)]) + assert set(d.items()) == {(2*z, x), (x, Integer(2)), (y, z)} del d[x] - assert set(d.keys()) == set([2*z, y]) - assert set(d.values()) == set([x, z]) + assert set(d.keys()) == {2*z, y} + assert set(d.values()) == {x, z} e = y + sin(2*z) assert e.subs(d) == z + sin(x) diff --git a/symengine/tests/test_eval.py b/symengine/tests/test_eval.py index 1ea2b51f0..24b062f11 100644 --- a/symengine/tests/test_eval.py +++ b/symengine/tests/test_eval.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine import (Symbol, sin, cos, Integer, Add, I, RealDouble, ComplexDouble, sqrt) from unittest.case import SkipTest @@ -16,7 +16,7 @@ def test_eval_double2(): x = Symbol("x") e = sin(x)**2 + sqrt(2) raises(RuntimeError, lambda: e.n(real=True)) - assert abs(e.n() - x**2 - 1.414) < 1e-3 + assert abs(e.n() - sin(x)**2.0 - 1.414) < 1e-3 def test_n(): x = Symbol("x") diff --git a/symengine/tests/test_expr.py b/symengine/tests/test_expr.py index 8c46f1d8c..8cbf4ab7b 100644 --- a/symengine/tests/test_expr.py +++ b/symengine/tests/test_expr.py @@ -1,5 +1,5 @@ -from symengine import Add, Mul, Symbol, Integer -from symengine.utilities import raises +from symengine import Symbol, Integer, oo +from symengine.test_utilities import raises def test_as_coefficients_dict(): @@ -12,3 +12,17 @@ def test_as_coefficients_dict(): [0, 0, 3, 0] assert (3.0*x*y).as_coefficients_dict()[3.0*x*y] == 0 assert (3.0*x*y).as_coefficients_dict()[x*y] == 3.0 + + +def test_as_powers_dict(): + x = Symbol('x') + y = Symbol('y') + + assert (2*x**y).as_powers_dict() == {2: 1, x: y} + assert (2*x**2*y**3).as_powers_dict() == {2: 1, x: 2, y: 3} + assert (-oo).as_powers_dict() == {Integer(-1): 1, oo: 1} + assert (x**y).as_powers_dict() == {x: y} + assert ((1/Integer(2))**y).as_powers_dict() == {Integer(2): -y} + assert (x*(1/Integer(2))**y).as_powers_dict() == {x: Integer(1), Integer(2): -y} + assert (2**y).as_powers_dict() == {2: y} + assert (2**-y).as_powers_dict() == {2: -y} diff --git a/symengine/tests/test_functions.py b/symengine/tests/test_functions.py index 9c016a23b..207add989 100644 --- a/symengine/tests/test_functions.py +++ b/symengine/tests/test_functions.py @@ -3,8 +3,9 @@ Rational, EulerGamma, Function, Subs, Derivative, LambertW, zeta, dirichlet_eta, zoo, pi, KroneckerDelta, LeviCivita, erf, erfc, oo, lowergamma, uppergamma, exp, loggamma, beta, polygamma, digamma, trigamma, sign, floor, ceiling, conjugate, - nan, Float + nan, Float, UnevaluatedExpr ) +from symengine.test_utilities import raises import unittest @@ -62,6 +63,11 @@ def test_derivative(): assert f.diff(y) == 0 assert f.diff(x).args == (f, x) assert f.diff(x).diff(x).args == (f, x, x) + assert f.diff(x, 0) == f + assert f.diff(x, 0) == Derivative(function_symbol("f", x), x, 0) + raises(ValueError, lambda: f.diff(0)) + raises(ValueError, lambda: f.diff(x, 0, 0)) + raises(ValueError, lambda: f.diff(x, y, 0, 0, x)) g = function_symbol("f", y) assert g.diff(x) == 0 @@ -79,11 +85,35 @@ def test_derivative(): assert s.variables == (x,) fxy = Function("f")(x, y) + assert (1+fxy).has(fxy) g = Derivative(Function("f")(x, y), x, 2, y, 1) assert g == fxy.diff(x, x, y) assert g == fxy.diff(y, 1, x, 2) assert g == fxy.diff(y, x, 2) + h = Derivative(Function("f")(x, y), x, 0, y, 1) + assert h == fxy.diff(x, 0, y) + assert h == fxy.diff(y, x, 0) + + i = Derivative(Function("f")(x, y), x, 0, y, 1, x, 1) + assert i == fxy.diff(x, 0, y, x, 1) + assert i == fxy.diff(x, 0, y, x) + assert i == fxy.diff(y, x) + assert i == fxy.diff(y, 1, x, 1) + assert i == fxy.diff(y, 1, x) + + +def test_function(): + x = Symbol("x") + fx = Function("f")(x) + assert fx == function_symbol("f", x) + + raises(TypeError, lambda: Function("f", "x")) + raises(TypeError, lambda: Function("f", x)) + raises(TypeError, lambda: Function()) + + assert fx.name == "f" + def test_abs(): x = Symbol("x") @@ -386,3 +416,17 @@ def test_ceiling(): def test_conjugate(): assert conjugate(pi) == pi assert conjugate(I) == -I + + +def test_unevaluated_expr(): + x = Symbol("x") + t = UnevaluatedExpr(x) + assert x + t != 2 * x + assert not t.is_number + assert not t.is_integer + assert not t.is_finite + + t = UnevaluatedExpr(1) + assert t.is_number + assert t.is_integer + assert t.is_finite diff --git a/symengine/tests/test_lambdify.py b/symengine/tests/test_lambdify.py index b2ad57c0a..fa628a363 100644 --- a/symengine/tests/test_lambdify.py +++ b/symengine/tests/test_lambdify.py @@ -1,17 +1,12 @@ -# -*- coding: utf-8 -*- -from __future__ import (absolute_import, division, print_function) - - import array import cmath from functools import reduce import itertools from operator import mul import math -import sys import symengine as se -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine import have_numpy import unittest from unittest.case import SkipTest diff --git a/symengine/tests/test_logic.py b/symengine/tests/test_logic.py index 0a64de6fa..3d0c33911 100644 --- a/symengine/tests/test_logic.py +++ b/symengine/tests/test_logic.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine.lib.symengine_wrapper import (true, false, Eq, Ne, Ge, Gt, Le, Lt, Symbol, I, And, Or, Not, Nand, Nor, Xor, Xnor, Piecewise, Contains, Interval, FiniteSet, oo, log) @@ -27,6 +27,10 @@ def test_relationals(): assert Ge(1, 1) == true assert Eq(I, 2) == false assert Ne(I, 2) == true + eq = Eq(x, y) + assert eq.func(*eq.args) == eq + ne = Ne(x, y) + assert ne.func(*ne.args) == ne def test_rich_cmp(): @@ -44,6 +48,7 @@ def test_And(): assert And(True, False) == false assert And(False, False) == false assert And(True, True, True) == true + raises(TypeError, lambda: x < y and y < 1) def test_Or(): @@ -54,6 +59,7 @@ def test_Or(): assert Or(True, False) == true assert Or(False, False) == false assert Or(True, False, False) == true + raises(TypeError, lambda: x < y or y < 1) def test_Nor(): @@ -116,4 +122,3 @@ def test_Contains(): assert Contains(x, Interval(1, 1)) != false assert Contains(oo, Interval(-oo, oo)) == false assert Contains(-oo, Interval(-oo, oo)) == false - \ No newline at end of file diff --git a/symengine/tests/test_matrices.py b/symengine/tests/test_matrices.py index 375fbef84..9733e10b1 100644 --- a/symengine/tests/test_matrices.py +++ b/symengine/tests/test_matrices.py @@ -1,15 +1,29 @@ -from symengine import symbols +from symengine import symbols, init_printing from symengine.lib.symengine_wrapper import (DenseMatrix, Symbol, Integer, Rational, function_symbol, I, NonSquareMatrixError, ShapeError, zeros, ones, eye, ImmutableMatrix) -from symengine.utilities import raises +from symengine.test_utilities import raises +import unittest try: import numpy as np - HAVE_NUMPY = True + have_numpy = True except ImportError: - HAVE_NUMPY = False + have_numpy = False + +try: + import sympy + from sympy.core.cache import clear_cache + import atexit + atexit.register(clear_cache) + have_sympy = True +except ImportError: + have_sympy = False + + +def test_init(): + raises(ValueError, lambda: DenseMatrix(2, 1, [0]*4)) def test_get(): @@ -229,6 +243,8 @@ def test_mul_matrix(): assert A.mul_matrix(B) == DenseMatrix(2, 2, [a + b, 0, c + d, 0]) assert A * B == DenseMatrix(2, 2, [a + b, 0, c + d, 0]) + assert A @ B == DenseMatrix(2, 2, [a + b, 0, c + d, 0]) + assert (A @ DenseMatrix(2, 1, [0]*2)).shape == (2, 1) C = DenseMatrix(2, 3, [1, 2, 3, 2, 3, 4]) D = DenseMatrix(3, 2, [3, 4, 4, 5, 5, 6]) @@ -238,6 +254,13 @@ def test_mul_matrix(): raises(ShapeError, lambda: A*D) +def test_multiply_elementwise(): + A = DenseMatrix(2, 2, [1, 2, 3, 4]) + B = DenseMatrix(2, 2, [1, 0, 0, 1]) + + assert A.multiply_elementwise(B) == DenseMatrix(2, 2, [1, 0, 0, 4]) + + def test_add_scalar(): A = DenseMatrix(2, 2, [1, 2, 3, 4]) @@ -263,11 +286,13 @@ def test_mul_scalar(): assert a * A == DenseMatrix(2, 2, [a, 2*a, 3*a, 4*a]) -def test_neg(): +def test_neg_abs(): A = DenseMatrix(2, 3, [1, 2, 3, 4, 5, 6]) B = DenseMatrix(2, 3, [-1, -2, -3, -4, -5, -6]) assert -A == B + assert A == abs(B) + def test_sub(): A = DenseMatrix(2, 2, [1, 2, 3, 4]) @@ -304,6 +329,94 @@ def test_transpose(): assert A.transpose() == A +def test_conjugate(): + A = DenseMatrix(2, 2, [1, 2, 3, I]) + assert A.conjugate() == DenseMatrix(2, 2, [1, 2, 3, -I]) + + +def test_conjugate_transpose(): + A = DenseMatrix(2, 2, [1, 2, 3, I]) + assert A.conjugate_transpose() == DenseMatrix(2, 2, [1, 3, 2, -I]) + + +def test_trace(): + A = DenseMatrix(2, 2, [1, 2, 3, 4]) + assert A.trace() == 5 + + +def test_is_zero_matrix(): + A = DenseMatrix(2, 2, [1, 2, 3, I]) + assert not A.is_zero_matrix + B = DenseMatrix(1, 1, [Symbol('x')]) + assert B.is_zero_matrix is None + C = DenseMatrix(3, 3, [0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert C.is_zero_matrix + + +def test_is_real_matrix(): + A = DenseMatrix(2, 2, [1, 2, 3, I]) + assert not A.is_real_matrix + B = DenseMatrix(1, 1, [Symbol('x')]) + assert B.is_real_matrix is None + C = DenseMatrix(3, 3, [0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert C.is_real_matrix + + +def test_is_diagonal(): + A = DenseMatrix(2, 2, [1, 0, 0, I]) + assert A.is_diagonal + B = DenseMatrix(1, 1, [Symbol('x')]) + assert B.is_diagonal + C = DenseMatrix(3, 3, [0, 0, 0, 0, 0, 0, 0, 2, 0]) + assert not C.is_diagonal + + +def test_is_symmetric(): + A = DenseMatrix(2, 2, [1, 3, 2, I]) + assert not A.is_symmetric + B = DenseMatrix(1, 1, [Symbol('x')]) + assert B.is_symmetric + C = DenseMatrix(3, 3, [0, 0, 0, 0, 0, 0, 0, 2, 0]) + assert not C.is_symmetric + + +def test_is_hermitian(): + A = DenseMatrix(2, 2, [1, 3, 2, I]) + assert not A.is_hermitian + B = DenseMatrix(1, 1, [Symbol('x')]) + assert B.is_hermitian is None + C = DenseMatrix(3, 3, [0, I, 0, 0, 0, 0, 0, 2, 0]) + assert not C.is_hermitian + + +def test_is_weakly_diagonally_dominant(): + A = DenseMatrix(2, 2, [2, 1, 1, 2]) + assert A.is_weakly_diagonally_dominant + C = DenseMatrix(3, 3, [Symbol('x'), 0, 0, 0, 3, 0, 0, 0, 4]) + assert C.is_weakly_diagonally_dominant is None + + +def test_is_strongly_diagonally_dominant(): + A = DenseMatrix(2, 2, [2, 1, 1, 2]) + assert A.is_strongly_diagonally_dominant + C = DenseMatrix(3, 3, [Symbol('x'), 2, 0, 0, 4, 0, 0, 0, 4]) + assert C.is_strongly_diagonally_dominant is None + + +def test_is_positive_definite(): + A = DenseMatrix(2, 2, [2, 1, 1, 2]) + assert A.is_positive_definite + C = DenseMatrix(3, 3, [Symbol('x'), 2, 0, 0, 4, 0, 0, 0, 4]) + assert C.is_positive_definite is None + + +def test_is_negative_definite(): + A = DenseMatrix(2, 2, [-2, -1, -1, -2]) + assert A.is_negative_definite + C = DenseMatrix(3, 3, [Symbol('x'), -2, 0, 0, -4, 0, 0, 0, -4]) + assert C.is_negative_definite is None + + def test_LU(): A = DenseMatrix(3, 3, [1, 3, 5, 2, 5, 6, 8, 3, 1]) L, U = A.LU() @@ -419,10 +532,8 @@ def test_reshape(): assert C != A -# @pytest.mark.skipif(not HAVE_NUMPY, reason='requires numpy') +@unittest.skipIf(not have_numpy, 'requires numpy') def test_dump_real(): - if not HAVE_NUMPY: # nosetests work-around - return ref = [1, 2, 3, 4] A = DenseMatrix(2, 2, ref) out = np.empty(4) @@ -430,10 +541,9 @@ def test_dump_real(): assert np.allclose(out, ref) -# @pytest.mark.skipif(not HAVE_NUMPY, reason='requires numpy') + +@unittest.skipIf(not have_numpy, 'requires numpy') def test_dump_complex(): - if not HAVE_NUMPY: # nosetests work-around - return ref = [1j, 2j, 3j, 4j] A = DenseMatrix(2, 2, ref) out = np.empty(4, dtype=np.complex128) @@ -554,6 +664,14 @@ def test_cross(): DenseMatrix(1, 2, [1, 1]).cross(DenseMatrix(1, 2, [1, 1]))) +def test_diff(): + x = symbols("x") + M = DenseMatrix(1, 2, [x**2, x]) + result = M.diff(x) + assert isinstance(result, DenseMatrix) + assert result == DenseMatrix(1, 2, [2*x, 1]) + + def test_immutablematrix(): A = ImmutableMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) assert A.shape == (3, 3) @@ -599,8 +717,41 @@ def test_immutablematrix(): assert isinstance(Z, ImmutableMatrix) assert Z == ImmutableMatrix([[1, 2], [3, 4], [5, 6]]) + # Operations of one immutable and one mutable matrix should give immutable result + X = ImmutableMatrix([1]) + Y = DenseMatrix([1]) + assert type(X + Y) == ImmutableMatrix + assert type(Y + X) == ImmutableMatrix + assert type(X * Y) == ImmutableMatrix + assert type(Y * X) == ImmutableMatrix + + def test_atoms(): a = Symbol("a") b = Symbol("b") X = DenseMatrix([[a, 2], [b, 4]]) - assert X.atoms(Symbol) == set([a, b]) + assert X.atoms(Symbol) == {a, b} + + +def test_LUdecomp(): + testmat = DenseMatrix([[0, 2, 5, 3], + [3, 3, 7, 4], + [8, 4, 0, 2], + [-2, 6, 3, 4]]) + L, U, p = testmat.LUdecomposition() + res = L*U + for orig, new in p: + res.row_swap(orig, new) + assert res - testmat == zeros(4) + +def test_repr_latex(): + testmat = DenseMatrix([[0, 2]]) + init_printing(True) + latex_string = testmat._repr_latex_() + assert isinstance(latex_string, str) + init_printing(False) + +@unittest.skipIf(not have_sympy, "SymPy not installed") +def test_simplify(): + A = ImmutableMatrix([1]) + assert type(A.simplify()) == type(A) diff --git a/symengine/tests/test_ntheory.py b/symengine/tests/test_ntheory.py index 1b5435fe1..68b9a23f1 100644 --- a/symengine/tests/test_ntheory.py +++ b/symengine/tests/test_ntheory.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine.lib.symengine_wrapper import (isprime, nextprime, gcd, lcm, gcd_ext, mod, quotient, quotient_mod, mod_inverse, crt, fibonacci, diff --git a/symengine/tests/test_number.py b/symengine/tests/test_number.py index 40649b005..14b20af42 100644 --- a/symengine/tests/test_number.py +++ b/symengine/tests/test_number.py @@ -1,6 +1,6 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises -from symengine import Integer, I, S, pi +from symengine import Integer, I, S, Symbol, pi, Rational from symengine.lib.symengine_wrapper import (perfect_power, is_square, integer_nthroot) @@ -151,3 +151,36 @@ def test_integer_nthroot(): assert integer_nthroot(c2, 2) == (c, True) assert integer_nthroot(c2 + 1, 2) == (c, False) assert integer_nthroot(c2 - 1, 2) == (c - 1, False) + + +def test_is_zero(): + assert Symbol('x').is_zero is None + + +def test_is_positive(): + assert Rational(1, 2).is_positive + assert not Rational(-2, 3).is_positive + assert Symbol('x').is_positive is None + + +def test_is_negative(): + assert not Rational(1, 2).is_negative + assert Rational(-2, 3).is_negative + assert Symbol('x').is_negative is None + + +def test_is_nonpositive(): + assert not Rational(1, 2).is_nonpositive + assert Rational(-2, 3).is_nonpositive + assert Symbol('x').is_nonpositive is None + + +def test_is_nonnegative(): + assert Rational(1, 2).is_nonnegative + assert not Rational(-2, 3).is_nonnegative + assert Symbol('x').is_nonnegative is None + + +def test_is_real(): + assert Rational(1, 2).is_real + assert Symbol('x').is_real is None diff --git a/symengine/tests/test_pickling.py b/symengine/tests/test_pickling.py index b7f14a181..5ae64a75a 100644 --- a/symengine/tests/test_pickling.py +++ b/symengine/tests/test_pickling.py @@ -1,7 +1,50 @@ -from symengine import symbols, sin, sinh, have_numpy, have_llvm +from symengine import symbols, sin, sinh, have_numpy, have_llvm, cos, Symbol +from symengine.test_utilities import raises import pickle import unittest + +def test_basic(): + x, y, z = symbols('x y z') + expr = sin(cos(x + y)/z)**2 + s = pickle.dumps(expr) + expr2 = pickle.loads(s) + assert expr == expr2 + + +class MySymbolBase(Symbol): + def __init__(self, name, attr): + super().__init__(name=name) + self.attr = attr + + def __eq__(self, other): + if not isinstance(other, MySymbolBase): + return False + return self.name == other.name and self.attr == other.attr + + +class MySymbol(MySymbolBase): + def __reduce__(self): + return (self.__class__, (self.name, self.attr)) + + +def test_pysymbol(): + a = MySymbol("hello", attr=1) + b = pickle.loads(pickle.dumps(a + 2)) - 2 + try: + assert a == b + finally: + a._unsafe_reset() + b._unsafe_reset() + + a = MySymbolBase("hello", attr=1) + try: + raises(NotImplementedError, lambda: pickle.dumps(a)) + raises(NotImplementedError, lambda: pickle.dumps(a + 2)) + finally: + a._unsafe_reset() + + @unittest.skipUnless(have_llvm, "No LLVM support") @unittest.skipUnless(have_numpy, "Numpy not installed") def test_llvm_double(): @@ -14,4 +57,3 @@ def test_llvm_double(): ll = pickle.loads(ss) inp = [1, 2, 3] assert np.allclose(l(inp), ll(inp)) - diff --git a/symengine/tests/test_printing.py b/symengine/tests/test_printing.py index 2365dbc98..e0c428169 100644 --- a/symengine/tests/test_printing.py +++ b/symengine/tests/test_printing.py @@ -1,6 +1,6 @@ -from symengine import (ccode, Symbol, sqrt, Pow, Max, sin, Integer, MutableDenseMatrix) -from symengine.utilities import raises -from symengine.printing import CCodePrinter +from symengine import (ccode, unicode, Symbol, sqrt, Pow, Max, sin, Integer, MutableDenseMatrix) +from symengine.test_utilities import raises +from symengine.printing import CCodePrinter, init_printing def test_ccode(): x = Symbol("x") @@ -24,3 +24,15 @@ def test_CCodePrinter(): assert myprinter.doprint(MutableDenseMatrix(1, 2, [x, y]), "larry") == "larry[0] = x;\nlarry[1] = y;" raises(TypeError, lambda: myprinter.doprint(sin(x), Integer)) raises(RuntimeError, lambda: myprinter.doprint(MutableDenseMatrix(1, 2, [x, y]))) + +def test_init_printing(): + x = Symbol("x") + assert x._repr_latex_() is None + init_printing() + assert x._repr_latex_() == '$x$' + + +def test_unicode(): + x = Symbol("x") + y = Integer(2) + assert unicode(x / 2) == "x\n―\n2" diff --git a/symengine/tests/test_sage.py b/symengine/tests/test_sage.py index 3b994abba..e364bd6d9 100644 --- a/symengine/tests/test_sage.py +++ b/symengine/tests/test_sage.py @@ -66,9 +66,9 @@ def test_sage_conversions(): assert cos(x1)._sage_() == sage.cos(x) assert cos(x1) == sympify(sage.cos(x)) - assert function_symbol('f', x1, y1)._sage_() == sage.function('f', x, y) + assert function_symbol('f', x1, y1)._sage_() == sage.function('f')(x, y) assert (function_symbol('f', 2 * x1, x1 + y1).diff(x1)._sage_() == - sage.function('f', 2 * x, x + y).diff(x)) + sage.function('f')(2 * x, x + y).diff(x)) assert LambertW(x1) == LambertW(x) assert LambertW(x1)._sage_() == sage.lambert_w(x) @@ -142,11 +142,7 @@ def test_sage_conversions(): b = b + 8 assert isinstance(b, PyNumber) assert b._sage_() == a - - a = a + x - b = b + x - assert isinstance(b, Add) - assert b._sage_() == a + assert str(a) == str(b) # Sage Function e = x1 + wrap_sage_function(sage.log_gamma(x)) diff --git a/symengine/tests/test_series_expansion.py b/symengine/tests/test_series_expansion.py index ed1dee518..651e36302 100644 --- a/symengine/tests/test_series_expansion.py +++ b/symengine/tests/test_series_expansion.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine.lib.symengine_wrapper import (series, have_piranha, have_flint, Symbol, Integer, sin, cos, exp, sqrt, E) diff --git a/symengine/tests/test_sets.py b/symengine/tests/test_sets.py index 8260ae42d..bfa4ecd4e 100644 --- a/symengine/tests/test_sets.py +++ b/symengine/tests/test_sets.py @@ -1,12 +1,14 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine.lib.symengine_wrapper import (Interval, EmptySet, UniversalSet, - FiniteSet, Union, Complement, ImageSet, ConditionSet, - And, Or, oo, Symbol, true, Ge, Eq, Gt) + FiniteSet, Union, Complement, ImageSet, ConditionSet, Reals, Rationals, + Integers, And, Or, oo, Symbol, true, Ge, Eq, Gt) def test_Interval(): assert Interval(0, oo) == Interval(0, oo, False, True) + assert Interval(0, oo) == Interval(0, oo, left_open=False, right_open=True) assert Interval(-oo, 0) == Interval(-oo, 0, True, False) + assert Interval(-oo, 0) == Interval(-oo, 0, left_open=True, right_open=False) assert Interval(oo, -oo) == EmptySet() assert Interval(oo, oo) == EmptySet() assert Interval(-oo, -oo) == EmptySet() @@ -18,6 +20,9 @@ def test_Interval(): assert Interval(1, 1, True, True) == EmptySet() assert Interval(1, 2).union(Interval(2, 3)) == Interval(1, 3) + assert Interval(-oo, 0).start == -oo + assert Interval(-oo, 0).end == 0 + def test_EmptySet(): E = EmptySet() @@ -32,6 +37,24 @@ def test_UniversalSet(): assert U.contains(0) == true +def test_Reals(): + R = Reals() + assert R.union(Interval(2, 4)) == R + assert R.contains(0) == true + + +def test_Rationals(): + Q = Rationals() + assert Q.union(FiniteSet(2, 3)) == Q + assert Q.contains(0) == true + + +def test_Integers(): + Z = Integers() + assert Z.union(FiniteSet(2, 4)) == Z + assert Z.contains(0) == true + + def test_FiniteSet(): x = Symbol("x") A = FiniteSet(1, 2, 3) diff --git a/symengine/tests/test_solve.py b/symengine/tests/test_solve.py index 8a1a65244..675f1a9fe 100644 --- a/symengine/tests/test_solve.py +++ b/symengine/tests/test_solve.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine import (Interval, EmptySet, FiniteSet, I, oo, Eq, Symbol, linsolve) from symengine.lib.symengine_wrapper import solve @@ -7,10 +7,10 @@ def test_solve(): x = Symbol("x") reals = Interval(-oo, oo) - assert solve(1, x, reals) == EmptySet() + assert solve(1, x, reals) == EmptySet assert solve(0, x, reals) == reals assert solve(x + 3, x, reals) == FiniteSet(-3) - assert solve(x + 3, x, Interval(0, oo)) == EmptySet() + assert solve(x + 3, x, Interval(0, oo)) == EmptySet assert solve(x, x, reals) == FiniteSet(0) assert solve(x**2 + 1, x) == FiniteSet(-I, I) assert solve(x**2 - 2*x + 1, x) == FiniteSet(1) diff --git a/symengine/tests/test_subs.py b/symengine/tests/test_subs.py index 2f762ddf6..b346659bf 100644 --- a/symengine/tests/test_subs.py +++ b/symengine/tests/test_subs.py @@ -1,5 +1,7 @@ -from symengine.utilities import raises -from symengine import Symbol, sin, cos, sqrt, Add, function_symbol +import unittest + +from symengine.test_utilities import raises +from symengine import Symbol, sin, cos, sqrt, Add, function_symbol, have_numpy, log def test_basic(): @@ -22,6 +24,12 @@ def test_sin(): assert e.subs(x, 0) == 1 +def test_subs_exception(): + x = Symbol("x") + expr = sin(log(x)) + raises(RuntimeError, lambda: expr.subs({x: 0})) + + def test_args(): x = Symbol("x") e = cos(x) @@ -56,3 +64,19 @@ def test_xreplace(): y = Symbol("y") f = sin(cos(x)) assert f.xreplace({x: y}) == sin(cos(y)) + + +@unittest.skipUnless(have_numpy, "Numpy not installed") +def test_float32(): + import numpy as np + x = Symbol("x") + expr = x * 2 + assert expr.subs({x: np.float32(2)}) == 4.0 + + +@unittest.skipUnless(have_numpy, "Numpy not installed") +def test_float16(): + import numpy as np + x = Symbol("x") + expr = x * 2 + assert expr.subs({x: np.float16(2)}) == 4.0 diff --git a/symengine/tests/test_symbol.py b/symengine/tests/test_symbol.py index bd0243285..498259141 100644 --- a/symengine/tests/test_symbol.py +++ b/symengine/tests/test_symbol.py @@ -1,5 +1,7 @@ from symengine import Symbol, symbols, symarray, has_symbol, Dummy -from symengine.utilities import raises +from symengine.test_utilities import raises +import unittest +import platform def test_symbol(): @@ -8,9 +10,6 @@ def test_symbol(): assert str(x) == "x" assert str(x) != "y" assert repr(x) == str(x) - # Verify the successful use of slots. - assert not hasattr(x, "__dict__") - assert not hasattr(x, "__weakref__") def test_symbols(): @@ -49,7 +48,7 @@ def test_symbols(): assert symbols(('x', 'y', 'z')) == (x, y, z) assert symbols(['x', 'y', 'z']) == [x, y, z] - assert symbols(set(['x', 'y', 'z'])) == set([x, y, z]) + assert symbols({'x', 'y', 'z'}) == {x, y, z} raises(ValueError, lambda: symbols('')) raises(ValueError, lambda: symbols(',')) @@ -105,13 +104,13 @@ def sym(s): assert sym('a0:4') == '(a0, a1, a2, a3)' assert sym('a2:4,b1:3') == '(a2, a3, b1, b2)' assert sym('a1(2:4)') == '(a12, a13)' - assert sym(('a0:2.0:2')) == '(a0.0, a0.1, a1.0, a1.1)' - assert sym(('aa:cz')) == '(aaz, abz, acz)' + assert sym('a0:2.0:2') == '(a0.0, a0.1, a1.0, a1.1)' + assert sym('aa:cz') == '(aaz, abz, acz)' assert sym('aa:c0:2') == '(aa0, aa1, ab0, ab1, ac0, ac1)' assert sym('aa:ba:b') == '(aaa, aab, aba, abb)' assert sym('a:3b') == '(a0b, a1b, a2b)' assert sym('a-1:3b') == '(a-1b, a-2b)' - assert sym('a:2\,:2' + chr(0)) == '(a0,0%s, a0,1%s, a1,0%s, a1,1%s)' % ( + assert sym(r'a:2\,:2' + chr(0)) == '(a0,0%s, a0,1%s, a1,0%s, a1,1%s)' % ( (chr(0),)*4) assert sym('x(:a:3)') == '(x(a0), x(a1), x(a2))' assert sym('x(:c):1') == '(xa0, xb0, xc0)' @@ -160,9 +159,21 @@ def test_dummy(): assert x1 == x2 assert x1 != xdummy1 + assert xdummy1 == (xdummy1 + 1) - 1 assert xdummy1 != xdummy2 assert Dummy() != Dummy() assert Dummy('x') != Dummy('x') + +# Cython cdef classes on PyPy has a __dict__ attribute always +# __slots__ on PyPy are useless anyways. https://stackoverflow.com/a/23077685/4768820 +@unittest.skipUnless(platform.python_implementation()=="CPython", "__slots__ are useless on PyPy") +def test_slots(): + x = Dummy('x') # Verify the successful use of slots. - assert not hasattr(xdummy1, "__dict__") - assert not hasattr(xdummy1, "__weakref__") + assert not hasattr(x, "__dict__") + assert not hasattr(x, "__weakref__") + + x1 = Symbol('x') + # Verify the successful use of slots. + assert not hasattr(x, "__dict__") + assert not hasattr(x, "__weakref__") diff --git a/symengine/tests/test_sympify.py b/symengine/tests/test_sympify.py index 09dcc759e..86c59785c 100644 --- a/symengine/tests/test_sympify.py +++ b/symengine/tests/test_sympify.py @@ -1,4 +1,4 @@ -from symengine.utilities import raises +from symengine.test_utilities import raises from symengine import (Symbol, Integer, sympify, SympifyError, true, false, pi, nan, oo, zoo, E, I, GoldenRatio, Catalan, Rational, sqrt, Eq) @@ -44,7 +44,7 @@ def test_S(): def test_sympify_error1a(): - class Test(object): + class Test: pass raises(SympifyError, lambda: sympify(Test())) diff --git a/symengine/tests/test_sympy_compat.py b/symengine/tests/test_sympy_compat.py index 0d1bf50e3..6316d548f 100644 --- a/symengine/tests/test_sympy_compat.py +++ b/symengine/tests/test_sympy_compat.py @@ -1,7 +1,7 @@ from symengine.sympy_compat import (Integer, Rational, S, Basic, Add, Mul, Pow, symbols, Symbol, log, sin, cos, sech, csch, zeros, atan2, nan, Number, Float, Min, Max, RealDouble, have_mpfr, Abs) -from symengine.utilities import raises +from symengine.test_utilities import raises def test_Integer(): @@ -183,7 +183,7 @@ def __new__(cls, name, extra_attribute): return Symbol.__new__(cls, name) def __init__(self, name, extra_attribute): - super(Wrapper, self).__init__(name) + super().__init__(name) self.extra_attribute = extra_attribute # Instantiate the subclass diff --git a/symengine/tests/test_sympy_conv.py b/symengine/tests/test_sympy_conv.py index c58b9f713..5d173dc4f 100644 --- a/symengine/tests/test_sympy_conv.py +++ b/symengine/tests/test_sympy_conv.py @@ -2,14 +2,14 @@ function_symbol, I, E, pi, oo, zoo, nan, true, false, exp, gamma, have_mpfr, have_mpc, DenseMatrix, sin, cos, tan, cot, csc, sec, asin, acos, atan, acot, acsc, asec, sinh, cosh, tanh, coth, - asinh, acosh, atanh, acoth, Add, Mul, Pow, diff, GoldenRatio, - Catalan, EulerGamma) + asinh, acosh, atanh, acoth, atan2, Add, Mul, Pow, diff, GoldenRatio, + Catalan, EulerGamma, UnevaluatedExpr, RealDouble) from symengine.lib.symengine_wrapper import (Subs, Derivative, RealMPFR, ComplexMPC, PyNumber, Function, LambertW, zeta, dirichlet_eta, KroneckerDelta, LeviCivita, erf, erfc, lowergamma, uppergamma, loggamma, beta, polygamma, sign, floor, ceiling, conjugate, And, Or, Not, Xor, Piecewise, Interval, EmptySet, FiniteSet, Contains, - Union, Complement, UniversalSet) + Union, Complement, UniversalSet, Reals, Rationals, Integers) import unittest # Note: We test _sympy_() for SymEngine -> SymPy conversion, as those are @@ -171,6 +171,7 @@ def test_conv7(): assert acot(x/3) == acot(sympy.Symbol("x") / 3) assert acsc(x/3) == acsc(sympy.Symbol("x") / 3) assert asec(x/3) == asec(sympy.Symbol("x") / 3) + assert atan2(x/3, y) == atan2(sympy.Symbol("x") / 3, sympy.Symbol("y")) assert sin(x/3)._sympy_() == sympy.sin(sympy.Symbol("x") / 3) assert sin(x/3)._sympy_() != sympy.cos(sympy.Symbol("x") / 3) @@ -185,6 +186,22 @@ def test_conv7(): assert acot(x/3)._sympy_() == sympy.acot(sympy.Symbol("x") / 3) assert acsc(x/3)._sympy_() == sympy.acsc(sympy.Symbol("x") / 3) assert asec(x/3)._sympy_() == sympy.asec(sympy.Symbol("x") / 3) + assert atan2(x/3, y)._sympy_() == sympy.atan2(sympy.Symbol("x") / 3, sympy.Symbol("y")) + + assert sympy.sympify(sin(x/3)) == sympy.sin(sympy.Symbol("x") / 3) + assert sympy.sympify(sin(x/3)) != sympy.cos(sympy.Symbol("x") / 3) + assert sympy.sympify(cos(x/3)) == sympy.cos(sympy.Symbol("x") / 3) + assert sympy.sympify(tan(x/3)) == sympy.tan(sympy.Symbol("x") / 3) + assert sympy.sympify(cot(x/3)) == sympy.cot(sympy.Symbol("x") / 3) + assert sympy.sympify(csc(x/3)) == sympy.csc(sympy.Symbol("x") / 3) + assert sympy.sympify(sec(x/3)) == sympy.sec(sympy.Symbol("x") / 3) + assert sympy.sympify(asin(x/3)) == sympy.asin(sympy.Symbol("x") / 3) + assert sympy.sympify(acos(x/3)) == sympy.acos(sympy.Symbol("x") / 3) + assert sympy.sympify(atan(x/3)) == sympy.atan(sympy.Symbol("x") / 3) + assert sympy.sympify(acot(x/3)) == sympy.acot(sympy.Symbol("x") / 3) + assert sympy.sympify(acsc(x/3)) == sympy.acsc(sympy.Symbol("x") / 3) + assert sympy.sympify(asec(x/3)) == sympy.asec(sympy.Symbol("x") / 3) + assert sympy.sympify(atan2(x/3, y)) == sympy.atan2(sympy.Symbol("x") / 3, sympy.Symbol("y")) @unittest.skipIf(not have_sympy, "SymPy not installed") @@ -204,6 +221,7 @@ def test_conv7b(): assert sympify(sympy.acot(x/3)) == acot(Symbol("x") / 3) assert sympify(sympy.acsc(x/3)) == acsc(Symbol("x") / 3) assert sympify(sympy.asec(x/3)) == asec(Symbol("x") / 3) + assert sympify(sympy.atan2(x/3, y)) == atan2(Symbol("x") / 3, Symbol("y")) @unittest.skipIf(not have_sympy, "SymPy not installed") @@ -515,7 +533,7 @@ def test_zeta(): e1 = sympy.zeta(sympy.Symbol("x"), sympy.Symbol("y")) e2 = zeta(x, y) assert sympify(e1) == e2 - assert e2._sympy_() == e1 + assert e2._sympy_() == e1 @unittest.skipIf(not have_sympy, "SymPy not installed") @@ -651,6 +669,15 @@ def test_conjugate(): assert e2._sympy_() == e1 +@unittest.skipIf(not have_sympy, "SymPy not installed") +def test_unevaluated_expr(): + x = Symbol("x") + e1 = sympy.UnevaluatedExpr(sympy.Symbol("x")) + e2 = UnevaluatedExpr(x) + assert sympify(e1) == e2 + assert e2._sympy_() == e1 + + @unittest.skipIf(not have_sympy, "SymPy not installed") def test_logic(): x = true @@ -709,6 +736,15 @@ def test_sets(): assert sympify(sympy.S.UniversalSet) == UniversalSet() assert sympy.S.UniversalSet == UniversalSet()._sympy_() + assert sympify(sympy.S.Reals) == Reals() + assert sympy.S.Reals == Reals()._sympy_() + + assert sympify(sympy.S.Rationals) == Rationals() + assert sympy.S.Rationals == Rationals()._sympy_() + + assert sympify(sympy.S.Integers) == Integers() + assert sympy.S.Integers == Integers()._sympy_() + assert FiniteSet(x, y) == FiniteSet(x1, y1) assert FiniteSet(x1, y) == FiniteSet(x1, y1) assert FiniteSet(x, y)._sympy_() == sympy.FiniteSet(x1, y1) @@ -742,6 +778,7 @@ def test_pynumber(): assert isinstance(b, PyNumber) assert b == a # Check equality via SymEngine assert a == b # Check equality via SymPy + assert str(a) == str(b) a = 1 - a b = 1 - b @@ -768,3 +805,31 @@ def test_pynumber(): b = b / x assert isinstance(b, PyNumber) + + +@unittest.skipIf(not have_sympy, "SymPy not installed") +def test_construct_dense_matrix(): + # Test for issue #347 + A = sympy.Matrix([[1, 2], [3, 5]]) + B = DenseMatrix(A) + assert B.shape == (2, 2) + assert list(B) == [1, 2, 3, 5] + + +@unittest.skipIf(not have_sympy, "SymPy not installed") +def test_conv_doubles(): + f = 4.347249999999999 + a = sympify(f) + assert isinstance(a, RealDouble) + assert sympify(a._sympy_()) == a + assert float(a) == f + assert float(a._sympy_()) == f + +def test_conv_large_integers(): + a = Integer(10)**10000 + # check that convert to python int does not throw + b = int(a) + # check that convert to sympy int does not throw + if have_sympy: + c = a._sympy_() + d = sympify(c) diff --git a/symengine/tests/test_var.py b/symengine/tests/test_var.py index 35498c47a..d82d4fab1 100644 --- a/symengine/tests/test_var.py +++ b/symengine/tests/test_var.py @@ -1,7 +1,7 @@ # Tests for var are in their own file, because var pollutes global namespace. from symengine import Symbol, var -from symengine.utilities import raises +from symengine.test_utilities import raises # make z1 with call-depth = 1 diff --git a/symengine/utilities.py b/symengine/utilities.py index e25c12b10..a64ea15e3 100644 --- a/symengine/utilities.py +++ b/symengine/utilities.py @@ -1,5 +1,4 @@ from .lib.symengine_wrapper import Symbol, Basic -from .compatibility import string_types from itertools import combinations, permutations, product, product as cartes import re as _re import string @@ -10,7 +9,7 @@ def symbols(names, **args): - """ + r""" Transform strings into instances of :class:`Symbol` class. :func:`symbols` function returns a sequence of symbols with names taken from ``names`` argument, which can be a comma or whitespace delimited @@ -85,9 +84,9 @@ def symbols(names, **args): """ result = [] - if isinstance(names, string_types): + if isinstance(names, str): marker = 0 - literals = ['\,', '\:', '\ '] + literals = [r'\,', r'\:', r'\ '] for i in range(len(literals)): lit = literals.pop(0) if lit in names: @@ -242,94 +241,40 @@ def traverse(symbols, frame): return syms -try: - import py - from py.test import skip, raises - USE_PYTEST = getattr(sys, '_running_pytest', False) -except ImportError: - USE_PYTEST = False - -if not USE_PYTEST: - def raises(expectedException, code=None): - """ - Tests that ``code`` raises the exception ``expectedException``. - - ``code`` may be a callable, such as a lambda expression or function - name. - - If ``code`` is not given or None, ``raises`` will return a context - manager for use in ``with`` statements; the code to execute then - comes from the scope of the ``with``. - - ``raises()`` does nothing if the callable raises the expected - exception, otherwise it raises an AssertionError. - - Examples - ======== - - >>> from symengine.pytest import raises - - >>> raises(ZeroDivisionError, lambda: 1/0) - >>> raises(ZeroDivisionError, lambda: 1/2) - Traceback (most recent call last): - ... - AssertionError: DID NOT RAISE - - >>> with raises(ZeroDivisionError): - ... n = 1/0 - >>> with raises(ZeroDivisionError): - ... n = 1/2 - Traceback (most recent call last): - ... - AssertionError: DID NOT RAISE - - Note that you cannot test multiple statements via - ``with raises``: - - >>> with raises(ZeroDivisionError): - ... n = 1/0 # will execute and raise, aborting the ``with`` - ... n = 9999/0 # never executed - - This is just what ``with`` is supposed to do: abort the - contained statement sequence at the first exception and let - the context manager deal with the exception. - - To test multiple statements, you'll need a separate ``with`` - for each: - - >>> with raises(ZeroDivisionError): - ... n = 1/0 # will execute and raise - >>> with raises(ZeroDivisionError): - ... n = 9999/0 # will also execute and raise - - """ - if code is None: - return RaisesContext(expectedException) - elif callable(code): - try: - code() - except expectedException: - return - raise AssertionError("DID NOT RAISE") - elif isinstance(code, str): - raise TypeError( - '\'raises(xxx, "code")\' has been phased out; ' - 'change \'raises(xxx, "expression")\' ' - 'to \'raises(xxx, lambda: expression)\', ' - '\'raises(xxx, "statement")\' ' - 'to \'with raises(xxx): statement\'') - else: - raise TypeError( - 'raises() expects a callable for the 2nd argument.') - - class RaisesContext(object): - def __init__(self, expectedException): - self.expectedException = expectedException - - def __enter__(self): - return None - - def __exit__(self, exc_type, exc_value, traceback): - if exc_type is None: - raise AssertionError("DID NOT RAISE") - return issubclass(exc_type, self.expectedException) +class NotIterable: + """ + Use this as mixin when creating a class which is not supposed to return + true when iterable() is called on its instances. I.e. avoid infinite loop + when calling e.g. list() on the instance + """ + pass + + +def iterable(i, exclude=(str, dict, NotIterable)): + """ + Return a boolean indicating whether ``i`` is SymPy iterable. + True also indicates that the iterator is finite, i.e. you e.g. + call list(...) on the instance. + + When SymPy is working with iterables, it is almost always assuming + that the iterable is not a string or a mapping, so those are excluded + by default. If you want a pure Python definition, make exclude=None. To + exclude multiple items, pass them as a tuple. + """ + try: + iter(i) + except TypeError: + return False + if exclude: + return not isinstance(i, exclude) + return True + + +def is_sequence(i): + """ + Return a boolean indicating whether ``i`` is a sequence in the SymPy + sense. If anything that fails the test below should be included as + being a sequence for your application, set 'include' to that object's + type; multiple types should be passed as a tuple of types. + """ + return hasattr(i, '__getitem__') and iterable(i) diff --git a/symengine_version.txt b/symengine_version.txt index 60f634328..e49372bea 100644 --- a/symengine_version.txt +++ b/symengine_version.txt @@ -1 +1 @@ -v0.6.0 +c9510fb4b5c30b84adb993573a51f2a9a38a4cfe