From b18c4f3fa99f89eccbbdd311e84e3a8873a61091 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 10 Nov 2019 14:08:08 +0100 Subject: [PATCH 01/21] version bump 1.1.0.dev0 [skip ci] --- pentapy/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pentapy/_version.py b/pentapy/_version.py index 03fcee7..d76b129 100644 --- a/pentapy/_version.py +++ b/pentapy/_version.py @@ -1,2 +1,2 @@ """Provide a central version.""" -__version__ = "1.0.3" +__version__ = "1.1.0.dev0" From 44918073ce4229ec46547e7d7e36c042122f2dc2 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sat, 21 Mar 2020 23:12:25 +0100 Subject: [PATCH 02/21] py3 only; scm versioning; travis update --- .travis.yml | 198 ++++++++++++++-------------------- appveyor.yml | 22 ---- docs/requirements.txt | 7 +- docs/requirements_doc.txt | 1 + pentapy/__init__.py | 9 +- pentapy/_version.py | 2 - pentapy/core.py | 2 - pentapy/py_solver.py | 2 - pentapy/tools.py | 2 - requirements.txt | 1 + requirements_setup.txt | 4 + requirements_test.txt | 2 + setup.py | 218 ++++++++++++-------------------------- 13 files changed, 166 insertions(+), 304 deletions(-) delete mode 100644 appveyor.yml create mode 100755 docs/requirements_doc.txt delete mode 100644 pentapy/_version.py create mode 100644 requirements.txt create mode 100755 requirements_setup.txt create mode 100755 requirements_test.txt diff --git a/.travis.yml b/.travis.yml index 8067aa8..8762823 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,137 +1,99 @@ language: python +python: 3.8 -matrix: - include: - - name: "Linux py27" - sudo: required - language: python - python: 2.7 - services: docker - env: - - PIP=pip - - CIBW_BUILD="cp27-*" - - COVER="off" +# setuptools-scm needs all tags in order to obtain a proper version +git: + depth: false + +env: + global: + - TWINE_USERNAME=geostatframework + - CIBW_BEFORE_BUILD="pip install numpy==1.17.3 cython==0.29.14 setuptools" + - CIBW_TEST_REQUIRES=pytest + - CIBW_TEST_COMMAND="pytest -v {project}/tests" - - name: "Linux py34" - sudo: required - language: python - python: 3.4 +before_install: + - | + if [[ "$TRAVIS_OS_NAME" = windows ]]; then + choco install python --version 3.8.0 + export PATH="/c/Python38:/c/Python38/Scripts:$PATH" + # make sure it's on PATH as 'python3' + ln -s /c/Python38/python.exe /c/Python38/python3.exe + fi + +script: + - python3 -m pip install cibuildwheel==1.3.0 + - python3 -m cibuildwheel --output-dir dist + +after_success: + - | + if [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then + python3 -m pip install twine + python3 -m twine upload --verbose --skip-existing --repository-url https://test.pypi.org/legacy/ dist/* + if [[ $TRAVIS_TAG ]]; then python3 -m twine upload --verbose --skip-existing dist/*; fi + fi + +notifications: + email: + recipients: + - info@geostat-framework.org + +jobs: + include: + - name: "sdist and coverage" services: docker - env: - - PIP=pip - - CIBW_BUILD="cp34-*" - - COVER="off" + script: + - python3 -m pip install -U setuptools pytest-cov coveralls + - python3 -m pip install -U numpy==1.17.3 cython==0.29.14 + - python3 -m pip install -r requirements.txt + - python3 setup.py sdist -d dist + - python3 setup.py build_ext --inplace + - python3 -m pytest --cov pentapy --cov-report term-missing -v tests/ + - python3 -m coveralls - name: "Linux py35" - sudo: required - language: python - python: 3.5 services: docker - env: - - PIP=pip - - CIBW_BUILD="cp35-*" - - COVER="off" - - # py36 for coverage and sdist + env: CIBW_BUILD="cp35-*" - name: "Linux py36" - sudo: required - language: python - python: 3.6 services: docker - env: - - PIP=pip - - CIBW_BUILD="cp36-*" - - COVER="on" - - # https://github.com/travis-ci/travis-ci/issues/9815 + env: CIBW_BUILD="cp36-*" - name: "Linux py37" - sudo: required - language: python - python: 3.7 - dist: xenial services: docker - env: - - PIP=pip - - CIBW_BUILD="cp37-*" - - COVER="off" - - - name: "MacOS py27" - os: osx - language: generic - env: - - PIP=pip2 - - CIBW_BUILD="cp27-*" - - COVER="off" - - - name: "MacOS py34" - os: osx - language: generic - env: - - PIP=pip2 - - CIBW_BUILD="cp34-*" - - COVER="off" + env: CIBW_BUILD="cp37-*" + - name: "Linux py38" + services: docker + env: CIBW_BUILD="cp38-*" - name: "MacOS py35" os: osx - language: generic - env: - - PIP=pip2 - - CIBW_BUILD="cp35-*" - - COVER="off" - + language: shell + env: CIBW_BUILD="cp35-*" - name: "MacOS py36" os: osx - language: generic - env: - - PIP=pip2 - - CIBW_BUILD="cp36-*" - - COVER="off" - + language: shell + env: CIBW_BUILD="cp36-*" - name: "MacOS py37" os: osx - language: generic - env: - - PIP=pip2 - - CIBW_BUILD="cp37-*" - - COVER="off" - -env: - global: - - TWINE_USERNAME=geostatframework - - CIBW_BEFORE_BUILD="pip install numpy==1.14.5 cython==0.28.3" - - CIBW_TEST_REQUIRES=pytest-cov - # inplace cython build and test run - - CIBW_TEST_COMMAND="cd {project} && python setup.py build_ext --inplace && py.test --cov pentapy --cov-report term-missing -v {project}/tests" - -script: - # create wheels - - $PIP install cibuildwheel==0.11.1 - - cibuildwheel --output-dir wheelhouse - # create source dist for pypi and create coverage (only once for linux py3.6) - - | - if [[ $COVER == "on" ]]; then - rm -rf dist - python -m pip install -U numpy==1.14.5 cython==0.28.3 setuptools - python -m pip install pytest-cov coveralls - python -m pip install -r docs/requirements.txt - python setup.py sdist - python setup.py build_ext --inplace - python -m pytest --cov pentapy --cov-report term-missing -v tests/ - python -m coveralls - fi - -after_success: - # pypi upload ("test" allways and "official" on TAG) - - python -m pip install twine - - python -m twine upload --verbose --skip-existing --repository-url https://test.pypi.org/legacy/ wheelhouse/*.whl - - python -m twine upload --verbose --skip-existing --repository-url https://test.pypi.org/legacy/ dist/*.tar.gz - - | - if [[ $TRAVIS_TAG ]]; then - python -m twine upload --verbose --skip-existing wheelhouse/*.whl - python -m twine upload --verbose --skip-existing dist/*.tar.gz - fi + language: shell + env: CIBW_BUILD="cp37-*" + - name: "MacOS py38" + os: osx + language: shell + env: CIBW_BUILD="cp38-*" -notifications: - email: - recipients: - - info@geostat-framework.org \ No newline at end of file + - name: "Win py35" + os: windows + language: shell + env: CIBW_BUILD="cp35-*" + - name: "Win py36" + os: windows + language: shell + env: CIBW_BUILD="cp36-*" + - name: "Win py37" + os: windows + language: shell + env: CIBW_BUILD="cp37-*" + - name: "Win py38" + os: windows + language: shell + env: CIBW_BUILD="cp38-*" diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 660c23a..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,22 +0,0 @@ -environment: - global: - TWINE_USERNAME: geostatframework - CIBW_BEFORE_BUILD: pip install numpy==1.14.5 cython==0.28.3 - CIBW_TEST_REQUIRES: pytest-cov - CIBW_TEST_COMMAND: cd {project} && python setup.py build_ext --inplace && py.test --cov pentapy --cov-report term-missing -v {project}/tests - -build_script: - - pip install cibuildwheel==0.11.1 - - cibuildwheel --output-dir wheelhouse - - python -m pip install twine - - python -m twine upload --skip-existing --repository-url https://test.pypi.org/legacy/ wheelhouse/*.whl - - > - IF "%APPVEYOR_REPO_TAG%" == "true" - ( - python -m pip install twine - && - python -m twine upload --skip-existing wheelhouse/*.whl - ) -artifacts: - - path: "wheelhouse\\*.whl" - name: Wheels \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index e5d78c0..c5a6a23 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,3 @@ -#required for readthedocs.org -cython>=0.28.3 -numpy>=1.14.5 -numpydoc +-r requirements_doc.txt +-r ../requirements_setup.txt +-r ../requirements.txt diff --git a/docs/requirements_doc.txt b/docs/requirements_doc.txt new file mode 100755 index 0000000..a2954e3 --- /dev/null +++ b/docs/requirements_doc.txt @@ -0,0 +1 @@ +numpydoc diff --git a/pentapy/__init__.py b/pentapy/__init__.py index bc5760b..0d35ba0 100644 --- a/pentapy/__init__.py +++ b/pentapy/__init__.py @@ -29,9 +29,6 @@ create_banded create_full """ -from __future__ import absolute_import - -from pentapy._version import __version__ from pentapy.core import solve from pentapy.tools import ( create_banded, @@ -40,6 +37,12 @@ diag_indices, ) +try: + from pentapy._version import __version__ +except ModuleNotFoundError: # pragma: nocover + # package is not installed + __version__ = "0.0.0.dev0" + __all__ = ["__version__"] __all__ += ["solve"] __all__ += ["create_banded", "create_full", "shift_banded", "diag_indices"] diff --git a/pentapy/_version.py b/pentapy/_version.py deleted file mode 100644 index 03fcee7..0000000 --- a/pentapy/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Provide a central version.""" -__version__ = "1.0.3" diff --git a/pentapy/core.py b/pentapy/core.py index 337ef2f..37e1aa9 100644 --- a/pentapy/core.py +++ b/pentapy/core.py @@ -9,8 +9,6 @@ .. autosummary:: solve """ -from __future__ import division, absolute_import, print_function - import warnings import numpy as np from pentapy.tools import shift_banded, create_banded, _check_penta diff --git a/pentapy/py_solver.py b/pentapy/py_solver.py index 11a4e09..b94ea5d 100644 --- a/pentapy/py_solver.py +++ b/pentapy/py_solver.py @@ -13,8 +13,6 @@ penta_solver2 """ -from __future__ import division, absolute_import, print_function - import numpy as np diff --git a/pentapy/tools.py b/pentapy/tools.py index 4af4b26..1c08011 100644 --- a/pentapy/tools.py +++ b/pentapy/tools.py @@ -12,8 +12,6 @@ create_banded create_full """ -from __future__ import division, absolute_import, print_function - import numpy as np diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1d6790e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +numpy>=1.14.5 diff --git a/requirements_setup.txt b/requirements_setup.txt new file mode 100755 index 0000000..7329b9c --- /dev/null +++ b/requirements_setup.txt @@ -0,0 +1,4 @@ +setuptools>=41.0.1 +setuptools_scm>=3.5.0 +cython>=0.28.3 +numpy>=1.14.5 \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100755 index 0000000..be10813 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,2 @@ +pytest-cov>=2.8.0 +pytest>=5.3.0 diff --git a/setup.py b/setup.py index d580e61..bc80151 100644 --- a/setup.py +++ b/setup.py @@ -1,99 +1,49 @@ # -*- coding: utf-8 -*- """pentapy: A toolbox for pentadiagonal matrizes.""" -from __future__ import division, absolute_import, print_function import os -import codecs -import re -import logging -from distutils.errors import ( - CCompilerError, - DistutilsExecError, - DistutilsPlatformError, -) -from setuptools import setup, find_packages -from setuptools.extension import Extension -from setuptools.command.build_ext import build_ext -import numpy - -logging.basicConfig() -log = logging.getLogger(__file__) - -ext_errors = ( - CCompilerError, - DistutilsExecError, - DistutilsPlatformError, - IOError, -) +from setuptools import setup, find_packages, Extension +from Cython.Build import cythonize +import numpy as np HERE = os.path.abspath(os.path.dirname(__file__)) +# cython extensions ########################################################### -# version finder ############################################################## - - -def read(*parts): - """Read file data.""" - with codecs.open(os.path.join(HERE, *parts), "r") as fp: - return fp.read() - - -def find_version(*file_paths): - """Find version without importing module.""" - version_file = read(*file_paths) - version_match = re.search( - r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M +CY_MODULES = [] +CY_MODULES.append( + Extension( + "pentapy.solver", + [os.path.join("pentapy", "solver.pyx")], + include_dirs=[np.get_include()], ) - if version_match: - return version_match.group(1) - raise RuntimeError("Unable to find version string.") - - -# cython handler ############################################################## - - -class BuildFailed(Exception): - """Exeption for Cython build failed""" - - pass - - -def construct_build_ext(build_ext_base): - """Construct a wrapper class for build_ext""" - - class WrappedBuildExt(build_ext_base): - """This class allows C extension building to fail.""" - - def run(self): - """overridden run with try-except""" - try: - build_ext_base.run(self) - except DistutilsPlatformError as x: - raise BuildFailed(x) - - def build_extension(self, ext): - """overridden build_extension with try-except""" - try: - build_ext_base.build_extension(self, ext) - except ext_errors as x: - raise BuildFailed(x) - - return WrappedBuildExt +) +EXT_MODULES = cythonize(CY_MODULES) # annotate=True +# This is an important part. By setting this compiler directive, cython will +# embed signature information in docstrings. Sphinx then knows how to extract +# and use those signatures. +# python setup.py build_ext --inplace --> then sphinx build +for ext_m in EXT_MODULES: + ext_m.cython_directives = {"embedsignature": True} # setup ####################################################################### -try: - from Cython.Build import cythonize -except ImportError: - print("## pentapy setup: cython not used") - USE_CYTHON = False -else: - print("## pentapy setup: cython used") - USE_CYTHON = True +with open(os.path.join(HERE, "README.md"), encoding="utf-8") as f: + README = f.read() +with open(os.path.join(HERE, "requirements.txt"), encoding="utf-8") as f: + REQ = f.read().splitlines() +with open(os.path.join(HERE, "requirements_setup.txt"), encoding="utf-8") as f: + REQ_SETUP = f.read().splitlines() +with open(os.path.join(HERE, "requirements_test.txt"), encoding="utf-8") as f: + REQ_TEST = f.read().splitlines() +with open( + os.path.join(HERE, "docs", "requirements_doc.txt"), encoding="utf-8" +) as f: + REQ_DOC = f.read().splitlines() + +REQ_DEV = REQ_SETUP + REQ_TEST + REQ_DOC -VERSION = find_version("pentapy", "_version.py") DOCLINE = __doc__.split("\n")[0] -README = open(os.path.join(HERE, "README.md")).read() CLASSIFIERS = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -103,79 +53,49 @@ def build_extension(self, ext): "Natural Language :: English", "Operating System :: Unix", "Programming Language :: Python", - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3 :: Only", "Topic :: Scientific/Engineering", "Topic :: Utilities", ] -EXT_MODULES = [] -if USE_CYTHON: - EXT_MODULES = cythonize(os.path.join("pentapy", "solver.pyx")) -else: - EXT_MODULES.append( - Extension( - "pentapy.solver", - [os.path.join("pentapy", "solver.c")], - include_dirs=[numpy.get_include()], - ) - ) -# This is the important part. By setting this compiler directive, cython will -# embed signature information in docstrings. Sphinx then knows how to extract -# and use those signatures. -# python setup.py build_ext --inplace --> then sphinx build -for ext_m in EXT_MODULES: - ext_m.cython_directives = {"embedsignature": True} -# version import not possible due to cython -# see: https://packaging.python.org/guides/single-sourcing-package-version/ -setup_kw = { - "name": "pentapy", - "version": VERSION, - "maintainer": "Sebastian Mueller", - "maintainer_email": "info@geostat-framework.org", - "description": DOCLINE, - "long_description": README, - "long_description_content_type": "text/markdown", - "author": "Sebastian Mueller", - "author_email": "info@geostat-framework.org", - "url": "https://github.com/GeoStat-Framework/pentapy", - "license": "MIT", - "classifiers": CLASSIFIERS, - "platforms": ["Windows", "Linux", "Mac OS-X"], - "include_package_data": True, - "setup_requires": ["numpy>=1.14.5"], # numpy imported in setup.py - "install_requires": ["numpy>=1.14.5"], - "packages": find_packages(exclude=["tests*", "docs*"]), - "ext_modules": EXT_MODULES, - "include_dirs": [numpy.get_include()], - "extras_require": { +setup( + name="pentapy", + description=DOCLINE, + long_description=README, + long_description_content_type="text/markdown", + maintainer="Sebastian Mueller", + maintainer_email="info@geostat-framework.org", + author="Sebastian Mueller", + author_email="info@geostat-framework.org", + url="https://github.com/GeoStat-Framework/pentapy", + license="MIT", + classifiers=CLASSIFIERS, + platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"], + include_package_data=True, + python_requires=">=3.5", + use_scm_version={ + "relative_to": __file__, + "write_to": "pentapy/_version.py", + "write_to_template": "__version__ = '{version}'", + "local_scheme": "no-local-version", + "fallback_version": "0.0.0.dev0", + }, + setup_requires=REQ_SETUP, + install_requires=REQ, + extras_require={ "scipy": ["scipy"], "umfpack": ["scikit-umfpack"], "all": ["scipy", "scikit-umfpack"], + "doc": REQ_DOC, + "test": REQ_TEST, + "dev": REQ_DEV, }, -} - -cmd_classes = setup_kw.setdefault("cmdclass", {}) - -try: - print("## pentapy setup: try build with c code") - # try building with c code : - setup_kw["cmdclass"]["build_ext"] = construct_build_ext(build_ext) - setup(**setup_kw) -except BuildFailed as ex: - print("The C extension could not be compiled") - log.warn(ex) - log.warn("The C extension could not be compiled") - - ## Retry to install the module without C extensions : - # Remove any previously defined build_ext command class. - setup_kw["cmdclass"].pop("build_ext", None) - cmd_classes.pop("build_ext", None) - setup_kw.pop("ext_modules", None) - - # If this new 'setup' call doesn't fail, the module - # will be successfully installed, without the C extensions - setup(**setup_kw) - print("## pentapy setup: Plain-Python installation successful.") -else: - print("## pentapy setup: cython installation successful.") + packages=find_packages(exclude=["tests*", "docs*"]), + ext_modules=EXT_MODULES, + include_dirs=[np.get_include()], +) From fe1c561a4c548aa0bf937183882fe8f980f6d0d6 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sat, 21 Mar 2020 23:26:51 +0100 Subject: [PATCH 03/21] py35: ModuleNotFoundError not defined --- pentapy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pentapy/__init__.py b/pentapy/__init__.py index 0d35ba0..cffaefd 100644 --- a/pentapy/__init__.py +++ b/pentapy/__init__.py @@ -39,7 +39,7 @@ try: from pentapy._version import __version__ -except ModuleNotFoundError: # pragma: nocover +except ImportError: # pragma: nocover # package is not installed __version__ = "0.0.0.dev0" From ea361b040b8b72a0d17df8388d0bd902efcf800f Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sat, 21 Mar 2020 23:41:04 +0100 Subject: [PATCH 04/21] remove version from develop --- pentapy/_version.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 pentapy/_version.py diff --git a/pentapy/_version.py b/pentapy/_version.py deleted file mode 100644 index d76b129..0000000 --- a/pentapy/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Provide a central version.""" -__version__ = "1.1.0.dev0" From ebadf2c5362fd01cff132ec1de610f813dadf0d6 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sat, 21 Mar 2020 23:47:02 +0100 Subject: [PATCH 05/21] update cython routines --- pentapy/solver.pyx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pentapy/solver.pyx b/pentapy/solver.pyx index a629fa8..4dc1555 100644 --- a/pentapy/solver.pyx +++ b/pentapy/solver.pyx @@ -1,19 +1,15 @@ -# cython: language_level=2 +#cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True # -*- coding: utf-8 -*- """ This is a solver linear equation systems with a penta-diagonal matrix, implemented in cython. """ -from __future__ import division, absolute_import, print_function - import numpy as np cimport cython cimport numpy as np -@cython.boundscheck(False) # turn off bounds-checking for entire function -@cython.wraparound(False) # turn off negative index wrapping for entire function def penta_solver1(double[:,:] mat_flat, double[:] rhs): cdef int mat_j = mat_flat.shape[1] @@ -66,8 +62,6 @@ def penta_solver1(double[:,:] mat_flat, double[:] rhs): return np.asarray(result) -@cython.boundscheck(False) # turn off bounds-checking for entire function -@cython.wraparound(False) # turn off negative index wrapping for entire function def penta_solver2(double[:,:] mat_flat, double[:] rhs): cdef int mat_j = mat_flat.shape[1] @@ -110,7 +104,7 @@ def penta_solver2(double[:,:] mat_flat, double[:] rhs): we[1] = (rhs[1] - we[3] * mat_flat[0, 1] - we[2] * ro[1]) / ps[1] we[0] = (rhs[0] - we[2] * mat_flat[0, 0] - we[1] * ro[0]) / ps[0] - # Backward substitution + # Foreward substitution result[0] = we[0] result[1] = we[1] - si[1] * result[0] From 5cf054dc91ae2cca48f67ba402d8b851ec609e82 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sat, 21 Mar 2020 23:56:39 +0100 Subject: [PATCH 06/21] README: link to stable doc --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22b1fd8..929fe1d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Build Status](https://travis-ci.org/GeoStat-Framework/pentapy.svg?branch=master)](https://travis-ci.org/GeoStat-Framework/pentapy) [![Build status](https://ci.appveyor.com/api/projects/status/yyfgn9dgxcoolp97/branch/master?svg=true)](https://ci.appveyor.com/project/GeoStat-Framework/pentapy/branch/master) [![Coverage Status](https://coveralls.io/repos/github/GeoStat-Framework/pentapy/badge.svg?branch=master)](https://coveralls.io/github/GeoStat-Framework/pentapy?branch=master) -[![Documentation Status](https://readthedocs.org/projects/pentapy/badge/?version=latest)](https://geostat-framework.readthedocs.io/projects/pentapy/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/pentapy/badge/?version=stable)](https://geostat-framework.readthedocs.io/projects/pentapy/en/stable/?badge=stable) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) @@ -121,4 +121,4 @@ You can contact us via . [pip_link]: https://pypi.org/project/pentapy [winpy_link]: https://winpython.github.io/ [licence_link]: https://github.com/GeoStat-Framework/pentapy/blob/master/LICENSE -[doc_link]: https://geostat-framework.readthedocs.io/projects/pentapy/en/latest/ +[doc_link]: https://pentapy.readthedocs.org \ No newline at end of file From 4438c24bc0a571849a148112eab0253dae81099c Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sat, 21 Mar 2020 23:57:03 +0100 Subject: [PATCH 07/21] tests: skip test for ZeroDev in cython --- tests/test_pentapy.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/test_pentapy.py b/tests/test_pentapy.py index 3a18d74..b5d48fd 100755 --- a/tests/test_pentapy.py +++ b/tests/test_pentapy.py @@ -4,7 +4,7 @@ """ from __future__ import division, absolute_import, print_function -import platform +# import platform import warnings import unittest import numpy as np @@ -129,18 +129,18 @@ def test_error(self): ) self.err_rhs = np.array([6, 3, 9, 6]) # there is a cython bug for 32bit where no ZeroDivisionError is raised - if platform.architecture()[0] == "64bit": - # https://stackoverflow.com/a/32089134/6696397 - with warnings.catch_warnings(record=True) as wrn: - sol_1 = pp.solve( - self.err_mat, self.err_rhs, is_flat=False, solver=1 - ) - self.assertTrue(wrn) - self.assertTrue(np.all(np.isnan(sol_1))) - self.assertTrue( - str(wrn[0].message) - == "pentapy: PTRANS-I not suitable for input-matrix." - ) + # if platform.architecture()[0] == "64bit": + # # https://stackoverflow.com/a/32089134/6696397 + # with warnings.catch_warnings(record=True) as wrn: + # sol_1 = pp.solve( + # self.err_mat, self.err_rhs, is_flat=False, solver=1 + # ) + # self.assertTrue(wrn) + # self.assertTrue(np.all(np.isnan(sol_1))) + # self.assertTrue( + # str(wrn[0].message) + # == "pentapy: PTRANS-I not suitable for input-matrix." + # ) sol_2 = pp.solve(self.err_mat, self.err_rhs, is_flat=False, solver=2) diff_2 = np.max(np.abs(np.dot(self.err_mat, sol_2) - self.err_rhs)) self.assertAlmostEqual(diff_2, 0.0) From 280515e16c6d48dceab9e1b9446c1eb68ff0ef21 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 00:01:46 +0100 Subject: [PATCH 08/21] DOC: update year --- README.md | 2 +- docs/source/conf.py | 4 +++- docs/source/index.rst | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 929fe1d..facee19 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ You can contact us via . ## License -[MIT][licence_link] © 2019 +[MIT][licence_link] © 2020 [ref_link]: http://dx.doi.org/10.1155/2015/232456 [pip_link]: https://pypi.org/project/pentapy diff --git a/docs/source/conf.py b/docs/source/conf.py index 59ea761..c4b8ab4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,6 +20,7 @@ # NOTE: # pip install sphinx_rtd_theme # is needed in order to build the documentation +import datetime import os import sys @@ -94,8 +95,9 @@ def setup(app): master_doc = "contents" # General information about the project. +curr_year = datetime.datetime.now().year project = "pentapy" -copyright = "2019, Sebastian Mueller" +copyright = "2019 - {}, Sebastian Mueller".format(curr_year) author = "Sebastian Mueller" # The version info for the project you're documenting, acts as replacement for diff --git a/docs/source/index.rst b/docs/source/index.rst index c0d5902..80915e9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -109,4 +109,4 @@ Optional License ======= -`MIT `_ © 2019 +`MIT `_ From bfe99144c2732c42122cd466ae18a018a305b823 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 00:41:24 +0100 Subject: [PATCH 09/21] add changelog; setup.cfg --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 3 +++ 2 files changed, 57 insertions(+) create mode 100755 CHANGELOG.md create mode 100644 setup.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..d3a9cf5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog + +All notable changes to **Pentapy** will be documented in this file. + + +## [Unreleased] + +### Enhancements +- Python 3.8 support + +### Changes +- python only builds are no longer available +- Python 2 support dropped + + +## [1.0.3] - 2019-11-10 + +### Enhancements +- the algorithms `PTRANS-I` and `PTRANS-II` now raise a warning when they can not solve the given system +- there are now switches to install scipy and umf solvers as extra requirements + +### Bugfixes +- multiple minor bugfixes + + +## [1.0.0] - 2019-09-18 + +### Enhancements +- the second algorithm `PTRANS-II` from *Askar et al. 2015* is now implemented and can be used by `solver=2` +- the package is now tested and a coverage is calculated +- there are now pre-built binaries for Python 3.7 +- the documentation is now available under https://geostat-framework.readthedocs.io/projects/pentapy + +### Changes +- pentapy is now licensed under the MIT license + + +## [0.1.1] - 2019-03-08 + +### Bugfixes +- MANIFEST.in was missing in the 0.1.0 version + + +## [0.1.0] - 2019-03-07 + +This is the first release of pentapy, a python toolbox for solving pentadiagonal linear equation systems. +The solver is implemented in cython, which makes it really fast. + + +[Unreleased]: https://github.com/GeoStat-Framework/gstools/compare/v1.0.3...HEAD +[1.0.3]: https://github.com/GeoStat-Framework/gstools/compare/v1.0.0...v1.0.3 +[1.0.0]: https://github.com/GeoStat-Framework/gstools/compare/v0.1.1...v1.0.0 +[0.1.1]: https://github.com/GeoStat-Framework/gstools/compare/v0.1...v0.1.1 +[0.1.0]: https://github.com/GeoStat-Framework/gstools/releases/tag/v0.1 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f48fdad --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[metadata] +description-file = README.md +license_file = LICENSE From 27c0d06676a9813f15e76578e837a9f7911d0dab Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 01:11:46 +0100 Subject: [PATCH 10/21] Zenodo update --- .zenodo.json | 19 +++++++++++++++++++ README.md | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100755 .zenodo.json diff --git a/.zenodo.json b/.zenodo.json new file mode 100755 index 0000000..da64769 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,19 @@ +{ + "license": "MIT", + "language": "eng", + "keywords": [ + "linear algebra", + "equation systems", + "pentadiagonal matrices", + "math", + "Python", + "GeoStat-Framework" + ], + "creators": [ + { + "orcid": "0000-0001-9060-4008", + "affiliation": "Helmholtz Centre for Environmental Research - UFZ", + "name": "Sebastian M\u00fcller" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index facee19..1eac257 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ You can contact us via . ## License -[MIT][licence_link] © 2020 +[MIT][licence_link] © 2019 - 2020 [ref_link]: http://dx.doi.org/10.1155/2015/232456 [pip_link]: https://pypi.org/project/pentapy From 512e4a5c5230cf1aa5336f87b730af033eeeee62 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 01:19:27 +0100 Subject: [PATCH 11/21] README: add citation note --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 1eac257..feaa8de 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,15 @@ following extra argument: Instead of "all" you can also typ "scipy" or "umfpack" to get one of these specific packages. +## Citation + +If you use `pentapy` in your publication, please cite it: + +> Müller, (2019). pentapy: A Python toolbox for pentadiagonal linear systems. Journal of Open Source Software, 4(42), 1759, https://doi.org/10.21105/joss.01759 + +To cite a certain release, have a look at the Zenodo site: https://doi.org/10.5281/zenodo.2587158 + + ## References The solver is based on the algorithms PTRANS-I and PTRANS-II From 9e202c56ce81c93fd719e4d033ea6bdf1a3776c4 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 01:24:46 +0100 Subject: [PATCH 12/21] Changelog update for 1.1.0 --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a9cf5..0a35881 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ # Changelog -All notable changes to **Pentapy** will be documented in this file. +All notable changes to **pentapy** will be documented in this file. -## [Unreleased] +## [1.1.0] - 2020-03-22 ### Enhancements - Python 3.8 support @@ -47,7 +47,7 @@ This is the first release of pentapy, a python toolbox for solving pentadiagonal The solver is implemented in cython, which makes it really fast. -[Unreleased]: https://github.com/GeoStat-Framework/gstools/compare/v1.0.3...HEAD +[1.1.0]: https://github.com/GeoStat-Framework/gstools/compare/v1.0.3...v1.1.0 [1.0.3]: https://github.com/GeoStat-Framework/gstools/compare/v1.0.0...v1.0.3 [1.0.0]: https://github.com/GeoStat-Framework/gstools/compare/v0.1.1...v1.0.0 [0.1.1]: https://github.com/GeoStat-Framework/gstools/compare/v0.1...v0.1.1 From 05ec59f3de93ab4e894b736f55ca1f783cbf1bc3 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 02:18:47 +0100 Subject: [PATCH 13/21] Test against installed version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8762823..d89af7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: - TWINE_USERNAME=geostatframework - CIBW_BEFORE_BUILD="pip install numpy==1.17.3 cython==0.29.14 setuptools" - CIBW_TEST_REQUIRES=pytest - - CIBW_TEST_COMMAND="pytest -v {project}/tests" + - CIBW_TEST_COMMAND="python -m pytest -v {project}/tests" before_install: - | From c6efd74724f8e6e63e50ba6d1b34113fb4f79b54 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 10:31:34 +0100 Subject: [PATCH 14/21] Demand cython modules --- pentapy/core.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pentapy/core.py b/pentapy/core.py index 37e1aa9..c1ce415 100644 --- a/pentapy/core.py +++ b/pentapy/core.py @@ -20,13 +20,14 @@ USE_CY = True PtransError = ZeroDivisionError except ImportError: # pragma: no cover - from pentapy.py_solver import penta_solver1, penta_solver2 - - np.seterr(divide="raise") - warnings.simplefilter("always", ImportWarning) - warnings.warn("pentapy: No Cython functions imported", ImportWarning) - USE_CY = False - PtransError = FloatingPointError + # from pentapy.py_solver import penta_solver1, penta_solver2 + + # np.seterr(divide="raise") + # warnings.simplefilter("always", ImportWarning) + # warnings.warn("pentapy: No Cython functions imported", ImportWarning) + # USE_CY = False + # PtransError = FloatingPointError + raise ImportError("pentapy: could not load cython modules.") def solve(mat, rhs, is_flat=False, index_row_wise=True, solver=1): From 5572f9807c7b5cf7192b7978c7a2a3a84ea2dd19 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 10:53:44 +0100 Subject: [PATCH 15/21] Update package for correct testing part 1 --- .travis.yml | 2 +- MANIFEST.in | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d89af7f..8762823 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: - TWINE_USERNAME=geostatframework - CIBW_BEFORE_BUILD="pip install numpy==1.17.3 cython==0.29.14 setuptools" - CIBW_TEST_REQUIRES=pytest - - CIBW_TEST_COMMAND="python -m pytest -v {project}/tests" + - CIBW_TEST_COMMAND="pytest -v {project}/tests" before_install: - | diff --git a/MANIFEST.in b/MANIFEST.in index e119764..37644ec 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,9 @@ +include README.md include MANIFEST.in include setup.py +include setup.cfg recursive-include pentapy *.py *.pyx *.c +recursive-include tests *.py recursive-include docs/source * include docs/Makefile docs/requirements.txt include LICENSE From 7e34442ddb7463411144b6fdf840f18694337f0f Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 11:15:07 +0100 Subject: [PATCH 16/21] Tests: bugfix to run against installed package --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 From 672fdd973c3d0a309e7fbb0c2c14accb9ac0ea95 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 11:28:52 +0100 Subject: [PATCH 17/21] remove py-only implementations; update examples accordingly --- .gitignore | 3 + CHANGELOG.md | 2 +- README.md | 2 +- examples/{03_compare.py => 02_compare.py} | 2 +- examples/02_perform.py | 90 -------- ...perform_simple.py => 03_perform_simple.py} | 2 +- ...orm_full_mat.py => 04_perform_full_mat.py} | 2 +- .../{06_solve_error.py => 05_solve_error.py} | 2 +- examples/perfplot.png | Bin 60185 -> 0 bytes pentapy/core.py | 21 +- pentapy/py_solver.py | 207 ------------------ 11 files changed, 12 insertions(+), 321 deletions(-) rename examples/{03_compare.py => 02_compare.py} (98%) delete mode 100644 examples/02_perform.py rename examples/{04_perform_simple.py => 03_perform_simple.py} (99%) rename examples/{05_perform_full_mat.py => 04_perform_full_mat.py} (99%) rename examples/{06_solve_error.py => 05_solve_error.py} (97%) delete mode 100644 examples/perfplot.png delete mode 100644 pentapy/py_solver.py diff --git a/.gitignore b/.gitignore index 2289d60..c322562 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,6 @@ info/ # JOSS stuff paper/compile + +# setuptools_scm generated version files +pentapy/_version.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a35881..df61c68 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ All notable changes to **pentapy** will be documented in this file. ### Changes - python only builds are no longer available -- Python 2 support dropped +- Python 2.7 and 3.4 support dropped ## [1.0.3] - 2019-11-10 diff --git a/README.md b/README.md index feaa8de..aa5e70b 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ The implementations of pentapy are almost one order of magnitude faster than the scipy algorithms for banded or sparse matrices. The performance plot was created with [``perfplot``](https://github.com/nschloe/perfplot). -Have a look at the script: [``examples/04_perform_simple.py``](https://github.com/GeoStat-Framework/pentapy/blob/master/examples/04_perform_simple.py). +Have a look at the script: [``examples/03_perform_simple.py``](https://github.com/GeoStat-Framework/pentapy/blob/master/examples/03_perform_simple.py). diff --git a/examples/03_compare.py b/examples/02_compare.py similarity index 98% rename from examples/03_compare.py rename to examples/02_compare.py index 8d6a090..867e3f7 100644 --- a/examples/03_compare.py +++ b/examples/02_compare.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -3. Example. +2. Example. Here we compare the outcome of the PTRANS-I and PTRANS-II algorithm for a random input. diff --git a/examples/02_perform.py b/examples/02_perform.py deleted file mode 100644 index 1c591fa..0000000 --- a/examples/02_perform.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -""" -2. Example. - -Here we compare different algorithms for solving pentadiagonal systems. - -To use this script you need to have the following packages installed: - - * scipy - * scikit-umfpack - * perfplot - * matplotlib -""" -from __future__ import division, absolute_import, print_function - -import numpy as np -from scipy import sparse as sps -from scipy.sparse.linalg import spsolve -from scipy.linalg import solve_banded -from pentapy.py_solver import penta_solver1 as ps1 -from pentapy.core import solve as ps2 -from pentapy import tools -from scipy.linalg.lapack import dgbsv -import perfplot - - -def get_les(size): - mat = (np.random.random((5, size)) - 0.5) * 1e-5 - V = np.array(np.random.random(size) * 1e5) - return mat, V - - -def solve_1(in_val): - mat, V = in_val - size = mat.shape[1] - M = sps.spdiags(mat, [2, 1, 0, -1, -2], size, size, format="csc") - return spsolve(M, V, use_umfpack=False) - - -def solve_2(in_val): - mat, V = in_val - size = mat.shape[1] - M = sps.spdiags(mat, [2, 1, 0, -1, -2], size, size, format="csc") - return spsolve(M, V, use_umfpack=True) - - -def solve_3(in_val): - mat, V = in_val - M = tools.shift_banded(mat, col_to_row=True) - return ps1(M, V) - - -def solve_4(in_val): - mat, V = in_val - return ps2(mat, V, is_flat=True, index_row_wise=False) - - -def solve_5(in_val): - mat, V = in_val - M = np.vstack((np.zeros((2, mat.shape[1])), mat)) - return dgbsv(2, 2, M, V)[2] - - -def solve_6(in_val): - mat, V = in_val - return solve_banded((2, 2), mat, V) - - -def solve_7(in_val): - mat, V = in_val - M = tools.create_full(mat) - return np.linalg.solve(M, V) - - -perfplot.show( - setup=get_les, - kernels=[solve_1, solve_2, solve_3, solve_4, solve_5, solve_6, solve_7], - labels=[ - "scipy sparse", - "umfpack", - "penta_py", - "penta_cy", - "lapack dgbsv", - "scipy solve_banded", - "numpy", - ], - n_range=[2 ** k for k in range(2, 13)], - xlabel="Size [n]", - logy=True, -) diff --git a/examples/04_perform_simple.py b/examples/03_perform_simple.py similarity index 99% rename from examples/04_perform_simple.py rename to examples/03_perform_simple.py index cfe2b9d..a45b9d4 100644 --- a/examples/04_perform_simple.py +++ b/examples/03_perform_simple.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -4. Example. +3. Example. Here we compare all algorithms for solving pentadiagonal systems provided by pentapy (except umf). diff --git a/examples/05_perform_full_mat.py b/examples/04_perform_full_mat.py similarity index 99% rename from examples/05_perform_full_mat.py rename to examples/04_perform_full_mat.py index b4eaea0..86a63cf 100644 --- a/examples/05_perform_full_mat.py +++ b/examples/04_perform_full_mat.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -5. Example. +4. Example. Here we compare all algorithms for solving pentadiagonal systems provided by pentapy (except umf) using a full quadratic matrix as input. diff --git a/examples/06_solve_error.py b/examples/05_solve_error.py similarity index 97% rename from examples/06_solve_error.py rename to examples/05_solve_error.py index 4858dbc..4c64ebc 100755 --- a/examples/06_solve_error.py +++ b/examples/05_solve_error.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -6. Example. +5. Example. Here we demonstrate that the solver PTRANS-I can fail to solve a given system. A warning is given in that case and the output will be a nan-array. diff --git a/examples/perfplot.png b/examples/perfplot.png deleted file mode 100644 index e9395540c6816777a2f416244fbdb4254c09c079..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60185 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 V-7$R59dAaW+8^9kvgg=8{{wd(YeE13 diff --git a/pentapy/core.py b/pentapy/core.py index c1ce415..fc9aaaa 100644 --- a/pentapy/core.py +++ b/pentapy/core.py @@ -12,22 +12,7 @@ import warnings import numpy as np from pentapy.tools import shift_banded, create_banded, _check_penta - - -try: - from pentapy.solver import penta_solver1, penta_solver2 - - USE_CY = True - PtransError = ZeroDivisionError -except ImportError: # pragma: no cover - # from pentapy.py_solver import penta_solver1, penta_solver2 - - # np.seterr(divide="raise") - # warnings.simplefilter("always", ImportWarning) - # warnings.warn("pentapy: No Cython functions imported", ImportWarning) - # USE_CY = False - # PtransError = FloatingPointError - raise ImportError("pentapy: could not load cython modules.") +from pentapy.solver import penta_solver1, penta_solver2 def solve(mat, rhs, is_flat=False, index_row_wise=True, solver=1): @@ -101,7 +86,7 @@ def solve(mat, rhs, is_flat=False, index_row_wise=True, solver=1): rhs = np.array(rhs, dtype=np.double) try: return penta_solver1(mat_flat, rhs) - except PtransError: + except ZeroDivisionError: warnings.warn("pentapy: PTRANS-I not suitable for input-matrix.") return np.full_like(rhs, np.nan) elif solver in [2, "2", "PTRANS-II"]: @@ -117,7 +102,7 @@ def solve(mat, rhs, is_flat=False, index_row_wise=True, solver=1): rhs = np.array(rhs, dtype=np.double) try: return penta_solver2(mat_flat, rhs) - except PtransError: + except ZeroDivisionError: warnings.warn("pentapy: PTRANS-II not suitable for input-matrix.") return np.full_like(rhs, np.nan) elif solver in [3, "3", "lapack", "solve_banded"]: # pragma: no cover diff --git a/pentapy/py_solver.py b/pentapy/py_solver.py deleted file mode 100644 index b94ea5d..0000000 --- a/pentapy/py_solver.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- -""" -A solver for linear equation systems with a penta-diagonal matrix. - -This is the python implementation. - -The following functions are provided - -.. currentmodule:: pentapy.py_solver - -.. autosummary:: - penta_solver1 - penta_solver2 - -""" -import numpy as np - - -def penta_solver1(mat_flat, rhs): - """ - Solver for a pentadiagonal system. - - The Matrix has to be given in a row-wise flattend form:: - - [[Dup2[0] Dup2[1] Dup2[2] ... Dup2[N-2] 0 0 ] - [Dup1[0] Dup1[1] Dup1[2] ... Dup1[N-2] Dup1[N-1] 0 ] - [Diag[0] Diag[1] Diag[2] ... Diag[N-2] Diag[N-1] Diag[N] ] - [0 Dlow1[1] Dlow1[2] ... Dlow1[N-2] Dlow1[N-1] Dlow1[N]] - [0 0 Dlow2[2] ... Dlow2[N-2] Dlow2[N-2] Dlow2[N]]] - - This routine was presented in [Askar2015]_. - - Parameters - ---------- - mat_flat : :class:`numpy.ndarray` - The flattened Version of the pentadiagonal matrix. - rhs : :class:`numpy.ndarray` - The right hand side of the equation system. - - Returns - ------- - result : :class:`numpy.ndarray` - Result of the equation system - - References - ---------- - .. [Askar2015] S. S. Askar and A. A. Karawia, - ''On Solving Pentadiagonal Linear Systems via Transformations'' - Mathematical Problems in Engineering, vol. 2015, Article ID 232456, - 9 pages, 2015. https://doi.org/10.1155/2015/232456. - """ - mat_j = mat_flat.shape[1] - result = np.zeros(mat_j) - - al = np.zeros(mat_j) - be = np.zeros(mat_j) - ze = np.zeros(mat_j) - ga = np.zeros(mat_j) - mu = np.zeros(mat_j) - - mu[0] = mat_flat[2, 0] - al[0] = mat_flat[1, 0] / mu[0] - be[0] = mat_flat[0, 0] / mu[0] - ze[0] = rhs[0] / mu[0] - - ga[1] = mat_flat[3, 1] - mu[1] = mat_flat[2, 1] - al[0] * ga[1] - al[1] = (mat_flat[1, 1] - be[0] * ga[1]) / mu[1] - be[1] = mat_flat[0, 1] / mu[1] - ze[1] = (rhs[1] - ze[0] * ga[1]) / mu[1] - - for i in range(2, mat_j - 2): - ga[i] = mat_flat[3, i] - al[i - 2] * mat_flat[4, i] - mu[i] = mat_flat[2, i] - be[i - 2] * mat_flat[4, i] - al[i - 1] * ga[i] - al[i] = (mat_flat[1, i] - be[i - 1] * ga[i]) / mu[i] - be[i] = mat_flat[0, i] / mu[i] - ze[i] = (rhs[i] - ze[i - 2] * mat_flat[4, i] - ze[i - 1] * ga[i]) / mu[ - i - ] - - ga[mat_j - 2] = ( - mat_flat[3, mat_j - 2] - al[mat_j - 4] * mat_flat[4, mat_j - 2] - ) - mu[mat_j - 2] = ( - mat_flat[2, mat_j - 2] - - be[mat_j - 4] * mat_flat[4, mat_j - 2] - - al[mat_j - 3] * ga[mat_j - 2] - ) - al[mat_j - 2] = ( - mat_flat[1, mat_j - 2] - be[mat_j - 3] * ga[mat_j - 2] - ) / mu[mat_j - 2] - - ga[mat_j - 1] = ( - mat_flat[3, mat_j - 1] - al[mat_j - 3] * mat_flat[4, mat_j - 1] - ) - mu[mat_j - 1] = ( - mat_flat[2, mat_j - 1] - - be[mat_j - 3] * mat_flat[4, mat_j - 1] - - al[mat_j - 2] * ga[mat_j - 1] - ) - - ze[mat_j - 2] = ( - rhs[mat_j - 2] - - ze[mat_j - 4] * mat_flat[4, mat_j - 2] - - ze[mat_j - 3] * ga[mat_j - 2] - ) / mu[mat_j - 2] - ze[mat_j - 1] = ( - rhs[mat_j - 1] - - ze[mat_j - 3] * mat_flat[4, mat_j - 1] - - ze[mat_j - 2] * ga[mat_j - 1] - ) / mu[mat_j - 1] - - # Backward substitution - result[mat_j - 1] = ze[mat_j - 1] - result[mat_j - 2] = ze[mat_j - 2] - al[mat_j - 2] * result[mat_j - 1] - - for i in range(mat_j - 3, -1, -1): - result[i] = ze[i] - al[i] * result[i + 1] - be[i] * result[i + 2] - - return np.asarray(result) - - -def penta_solver2(mat_flat, rhs): - """ - Solver for a pentadiagonal system. - - The Matrix has to be given in a row-wise flattend form:: - - [[Dup2[0] Dup2[1] Dup2[2] ... Dup2[N-2] 0 0 ] - [Dup1[0] Dup1[1] Dup1[2] ... Dup1[N-2] Dup1[N-1] 0 ] - [Diag[0] Diag[1] Diag[2] ... Diag[N-2] Diag[N-1] Diag[N] ] - [0 Dlow1[1] Dlow1[2] ... Dlow1[N-2] Dlow1[N-1] Dlow1[N]] - [0 0 Dlow2[2] ... Dlow2[N-2] Dlow2[N-2] Dlow2[N]]] - - This routine was presented in [Askar2015]_. - - Parameters - ---------- - mat_flat : :class:`numpy.ndarray` - The flattened Version of the pentadiagonal matrix. - rhs : :class:`numpy.ndarray` - The right hand side of the equation system. - - Returns - ------- - result : :class:`numpy.ndarray` - Result of the equation system - - References - ---------- - .. [Askar2015] S. S. Askar and A. A. Karawia, - ''On Solving Pentadiagonal Linear Systems via Transformations'' - Mathematical Problems in Engineering, vol. 2015, Article ID 232456, - 9 pages, 2015. https://doi.org/10.1155/2015/232456. - """ - mat_j = mat_flat.shape[1] - - result = np.zeros(mat_j) - - ps = np.zeros(mat_j) # psi - si = np.zeros(mat_j) # sigma - ph = np.zeros(mat_j) # phi - ro = np.zeros(mat_j) # rho - we = np.zeros(mat_j) # w - - ps[mat_j - 1] = mat_flat[2, mat_j - 1] - si[mat_j - 1] = mat_flat[3, mat_j - 1] / ps[mat_j - 1] - ph[mat_j - 1] = mat_flat[4, mat_j - 1] / ps[mat_j - 1] - we[mat_j - 1] = rhs[mat_j - 1] / ps[mat_j - 1] - - ro[mat_j - 2] = mat_flat[1, mat_j - 2] - ps[mat_j - 2] = mat_flat[2, mat_j - 2] - si[mat_j - 1] * ro[mat_j - 2] - si[mat_j - 2] = ( - mat_flat[3, mat_j - 2] - ph[mat_j - 1] * ro[mat_j - 2] - ) / ps[mat_j - 2] - ph[mat_j - 2] = mat_flat[4, mat_j - 2] / ps[mat_j - 2] - we[mat_j - 2] = (rhs[mat_j - 2] - we[mat_j - 1] * ro[mat_j - 2]) / ps[ - mat_j - 2 - ] - - for i in range(mat_j - 3, 1, -1): - ro[i] = mat_flat[1, i] - si[i + 2] * mat_flat[0, i] - ps[i] = mat_flat[2, i] - ph[i + 2] * mat_flat[0, i] - si[i + 1] * ro[i] - si[i] = (mat_flat[3, i] - ph[i + 1] * ro[i]) / ps[i] - ph[i] = mat_flat[4, i] / ps[i] - we[i] = (rhs[i] - we[i + 2] * mat_flat[0, i] - we[i + 1] * ro[i]) / ps[ - i - ] - - ro[1] = mat_flat[1, 1] - si[3] * mat_flat[0, 1] - ps[1] = mat_flat[2, 1] - ph[3] * mat_flat[0, 1] - si[2] * ro[1] - si[1] = (mat_flat[3, 1] - ph[2] * ro[1]) / ps[1] - - ro[0] = mat_flat[1, 0] - si[2] * mat_flat[0, 0] - ps[0] = mat_flat[2, 0] - ph[2] * mat_flat[0, 0] - si[1] * ro[0] - - we[1] = (rhs[1] - we[3] * mat_flat[0, 1] - we[2] * ro[1]) / ps[1] - we[0] = (rhs[0] - we[2] * mat_flat[0, 0] - we[1] * ro[0]) / ps[0] - - # Backward substitution - result[0] = we[0] - result[1] = we[1] - si[1] * result[0] - - for i in range(2, mat_j): - result[i] = we[i] - si[i] * result[i - 1] - ph[i] * result[i - 2] - - return np.asarray(result) From 10a8e47342bde5400b2df4ba13d0e996de8ae342 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 11:33:51 +0100 Subject: [PATCH 18/21] Tests: remove py_solver tests --- tests/test_pentapy.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/test_pentapy.py b/tests/test_pentapy.py index b5d48fd..3fe4dc4 100755 --- a/tests/test_pentapy.py +++ b/tests/test_pentapy.py @@ -9,8 +9,6 @@ import unittest import numpy as np import pentapy as pp -from pentapy.py_solver import penta_solver1 as ps1 -from pentapy.py_solver import penta_solver2 as ps2 warnings.simplefilter("always") @@ -78,12 +76,10 @@ def test_solve1(self): solver=1, ) sol_ful = pp.solve(self.mat_ful, self.rhs, solver=1) - sol_pyt = ps1(self.mat, self.rhs) diff_row = np.max(np.abs(np.dot(self.mat_ful, sol_row) - self.rhs)) diff_col = np.max(np.abs(np.dot(self.mat_ful, sol_col) - self.rhs)) diff_ful = np.max(np.abs(np.dot(self.mat_ful, sol_ful) - self.rhs)) - diff_pyt = np.max(np.abs(np.dot(self.mat_ful, sol_pyt) - self.rhs)) diff_row_col = np.max( np.abs(self.mat_ful - pp.create_full(self.mat_col)) @@ -91,7 +87,6 @@ def test_solve1(self): self.assertAlmostEqual(diff_row * 1e-5, 0.0) self.assertAlmostEqual(diff_col * 1e-5, 0.0) self.assertAlmostEqual(diff_ful * 1e-5, 0.0) - self.assertAlmostEqual(diff_pyt * 1e-5, 0.0) self.assertAlmostEqual(diff_row_col * 1e5, 0.0) def test_solve2(self): @@ -107,12 +102,10 @@ def test_solve2(self): solver=2, ) sol_ful = pp.solve(self.mat_ful, self.rhs, solver=2) - sol_pyt = ps2(self.mat, self.rhs) diff_row = np.max(np.abs(np.dot(self.mat_ful, sol_row) - self.rhs)) diff_col = np.max(np.abs(np.dot(self.mat_ful, sol_col) - self.rhs)) diff_ful = np.max(np.abs(np.dot(self.mat_ful, sol_ful) - self.rhs)) - diff_pyt = np.max(np.abs(np.dot(self.mat_ful, sol_pyt) - self.rhs)) diff_row_col = np.max( np.abs(self.mat_ful - pp.create_full(self.mat_col)) @@ -120,7 +113,6 @@ def test_solve2(self): self.assertAlmostEqual(diff_row * 1e-5, 0.0) self.assertAlmostEqual(diff_col * 1e-5, 0.0) self.assertAlmostEqual(diff_ful * 1e-5, 0.0) - self.assertAlmostEqual(diff_pyt * 1e-5, 0.0) self.assertAlmostEqual(diff_row_col * 1e5, 0.0) def test_error(self): @@ -128,19 +120,6 @@ def test_error(self): [[3, 2, 1, 0], [-3, -2, 7, 1], [3, 2, -1, 5], [0, 1, 2, 3]] ) self.err_rhs = np.array([6, 3, 9, 6]) - # there is a cython bug for 32bit where no ZeroDivisionError is raised - # if platform.architecture()[0] == "64bit": - # # https://stackoverflow.com/a/32089134/6696397 - # with warnings.catch_warnings(record=True) as wrn: - # sol_1 = pp.solve( - # self.err_mat, self.err_rhs, is_flat=False, solver=1 - # ) - # self.assertTrue(wrn) - # self.assertTrue(np.all(np.isnan(sol_1))) - # self.assertTrue( - # str(wrn[0].message) - # == "pentapy: PTRANS-I not suitable for input-matrix." - # ) sol_2 = pp.solve(self.err_mat, self.err_rhs, is_flat=False, solver=2) diff_2 = np.max(np.abs(np.dot(self.err_mat, sol_2) - self.err_rhs)) self.assertAlmostEqual(diff_2, 0.0) From ba702e260d4bd80fa474eb6d1c8a95633b5ff7e2 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 12:06:11 +0100 Subject: [PATCH 19/21] remove appveyor badge --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index aa5e70b..0e2f12b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ [![status](https://joss.theoj.org/papers/57c3bbdd7b7f3068dd1e669ccbcf107c/status.svg)](https://joss.theoj.org/papers/57c3bbdd7b7f3068dd1e669ccbcf107c) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.2587158.svg)](https://doi.org/10.5281/zenodo.2587158) [![PyPI version](https://badge.fury.io/py/pentapy.svg)](https://badge.fury.io/py/pentapy) -[![Build Status](https://travis-ci.org/GeoStat-Framework/pentapy.svg?branch=master)](https://travis-ci.org/GeoStat-Framework/pentapy) -[![Build status](https://ci.appveyor.com/api/projects/status/yyfgn9dgxcoolp97/branch/master?svg=true)](https://ci.appveyor.com/project/GeoStat-Framework/pentapy/branch/master) +[![Build Status](https://travis-ci.com/GeoStat-Framework/pentapy.svg?branch=master)](https://travis-ci.com/GeoStat-Framework/pentapy) [![Coverage Status](https://coveralls.io/repos/github/GeoStat-Framework/pentapy/badge.svg?branch=master)](https://coveralls.io/github/GeoStat-Framework/pentapy?branch=master) [![Documentation Status](https://readthedocs.org/projects/pentapy/badge/?version=stable)](https://geostat-framework.readthedocs.io/projects/pentapy/en/stable/?badge=stable) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) From 9adee3c90ddf40ff9cd41f0ba66b38af8aa90bc5 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 12:20:00 +0100 Subject: [PATCH 20/21] Travis: reduce job count --- .travis.yml | 47 +++++++---------------------------------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8762823..5126698 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ env: - CIBW_BEFORE_BUILD="pip install numpy==1.17.3 cython==0.29.14 setuptools" - CIBW_TEST_REQUIRES=pytest - CIBW_TEST_COMMAND="pytest -v {project}/tests" + - CIBW_SKIP="cp27-*" before_install: - | @@ -21,8 +22,10 @@ before_install: ln -s /c/Python38/python.exe /c/Python38/python3.exe fi -script: +install: - python3 -m pip install cibuildwheel==1.3.0 + +script: - python3 -m cibuildwheel --output-dir dist after_success: @@ -51,49 +54,13 @@ jobs: - python3 -m pytest --cov pentapy --cov-report term-missing -v tests/ - python3 -m coveralls - - name: "Linux py35" - services: docker - env: CIBW_BUILD="cp35-*" - - name: "Linux py36" - services: docker - env: CIBW_BUILD="cp36-*" - - name: "Linux py37" + - name: "Linux py35-py38" services: docker - env: CIBW_BUILD="cp37-*" - - name: "Linux py38" - services: docker - env: CIBW_BUILD="cp38-*" - - name: "MacOS py35" - os: osx - language: shell - env: CIBW_BUILD="cp35-*" - - name: "MacOS py36" - os: osx - language: shell - env: CIBW_BUILD="cp36-*" - - name: "MacOS py37" - os: osx - language: shell - env: CIBW_BUILD="cp37-*" - - name: "MacOS py38" + - name: "MacOS py35-py38" os: osx language: shell - env: CIBW_BUILD="cp38-*" - - name: "Win py35" - os: windows - language: shell - env: CIBW_BUILD="cp35-*" - - name: "Win py36" - os: windows - language: shell - env: CIBW_BUILD="cp36-*" - - name: "Win py37" - os: windows - language: shell - env: CIBW_BUILD="cp37-*" - - name: "Win py38" + - name: "Win py35-py38" os: windows language: shell - env: CIBW_BUILD="cp38-*" From 24cd4482c8de14b2b2b384132205cad46737bd94 Mon Sep 17 00:00:00 2001 From: MuellerSeb Date: Sun, 22 Mar 2020 12:43:06 +0100 Subject: [PATCH 21/21] Travis: explicit build version setting --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5126698..d95a277 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ env: - CIBW_BEFORE_BUILD="pip install numpy==1.17.3 cython==0.29.14 setuptools" - CIBW_TEST_REQUIRES=pytest - CIBW_TEST_COMMAND="pytest -v {project}/tests" - - CIBW_SKIP="cp27-*" + - CIBW_BUILD="cp35-* cp36-* cp37-* cp38-*" before_install: - |