From c44dbb101ebeb09ae8fe268c32c45d3f08b2f65b Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sun, 28 Jan 2018 22:18:54 -0600 Subject: [PATCH 01/61] Makefile: Add `sign-dist` target and rename others --- Makefile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ddd77c9..8d3bfe8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: default test build clean dist test-dist check-dist build-dist clean-dist +.PHONY: default test build clean upload-dist test-dist sign-dist check-dist build-dist clean-dist src = pyemd dist_dir = dist @@ -14,13 +14,17 @@ build: clean clean: rm -f pyemd/*.so -dist: build-dist check-dist +upload-dist: sign-dist twine upload $(dist_dir)/* -test-dist: build-dist check-dist +test-dist: check-dist twine upload --repository-url https://test.pypi.org/legacy/ $(dist_dir)/* -check-dist: +sign-dist: check-dist + gpg --detach-sign -a dist/*.tar.gz + gpg --detach-sign -a dist/*.whl + +check-dist: build-dist python setup.py check --restructuredtext --strict build-dist: clean-dist From 8ef13daed86c91fa350a460331f968bca8690a26 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 12 Feb 2019 17:46:41 -0600 Subject: [PATCH 02/61] README.rst: Fix links to papers --- README.rst | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 9ea95d8..cf0c78f 100644 --- a/README.rst +++ b/README.rst @@ -199,30 +199,15 @@ Credit ------ - All credit for the actual algorithm and implementation goes to `Ofir Pele - `_ and `Michael Werman + `_ and `Michael Werman `_. See the `relevant paper - `_. + `_. - Thanks to the Cython developers for making this kind of wrapper relatively easy to write. Please cite these papers if you use this code: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Ofir Pele and Michael Werman. A linear time histogram metric for improved SIFT -matching. *Computer Vision - ECCV 2008*, Marseille, France, 2008, pp. 495-508. - -.. code-block:: latex - - @INPROCEEDINGS{pele2008, - title={A linear time histogram metric for improved sift matching}, - author={Pele, Ofir and Werman, Michael}, - booktitle={Computer Vision--ECCV 2008}, - pages={495--508}, - year={2008}, - month={October}, - publisher={Springer} - } - Ofir Pele and Michael Werman. Fast and robust earth mover's distances. *Proc. 2009 IEEE 12th Int. Conf. on Computer Vision*, Kyoto, Japan, 2009, pp. 460-467. @@ -237,3 +222,18 @@ Ofir Pele and Michael Werman. Fast and robust earth mover's distances. *Proc. month={September}, organization={IEEE} } + +Ofir Pele and Michael Werman. A linear time histogram metric for improved SIFT +matching. *Computer Vision - ECCV 2008*, Marseille, France, 2008, pp. 495-508. + +.. code-block:: latex + + @INPROCEEDINGS{pele2008, + title={A linear time histogram metric for improved sift matching}, + author={Pele, Ofir and Werman, Michael}, + booktitle={Computer Vision--ECCV 2008}, + pages={495--508}, + year={2008}, + month={October}, + publisher={Springer} + } From d74a5ff5b2312b998678b9cba40a1f83775c75b9 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 12 Feb 2019 17:47:11 -0600 Subject: [PATCH 03/61] README.rst: Emphasize no metric check --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index cf0c78f..12bf3b2 100644 --- a/README.rst +++ b/README.rst @@ -75,8 +75,8 @@ emd() *N*. - ``distance_matrix`` *(np.ndarray)*: A 2D array of ``np.float64,`` of size at least *N* × *N*. This defines the underlying metric, or ground distance, by - giving the pairwise distances between the histogram bins. It must represent a - metric; there is no warning if it doesn't. + giving the pairwise distances between the histogram bins. + **NOTE: It must represent a metric; there is no warning if it doesn't.** *Keyword Arguments:* From 61e746b2d1376fa689abaca258847bca46752943 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 12 Feb 2019 17:52:56 -0600 Subject: [PATCH 04/61] Use `np.histogram_bin_edges` with NumPy >= 1.15.0 Before that function was exposed, finding the bin edges required an unnecessary calculation of the combined histogram. We now detect the NumPy version and use `np.histogram_bin_edges()` if it's available. --- README.rst | 9 +++++---- pyemd/emd.pyx | 24 ++++++++++++++++-------- tox.ini | 9 +++++++-- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 12bf3b2..1ceb20a 100644 --- a/README.rst +++ b/README.rst @@ -172,11 +172,12 @@ Limitations and Caveats - ``emd_samples()``: - - Using the default ``bins='auto'`` results in an extra call to - ``np.histogram()`` to determine the bin lengths, since `the NumPy - bin-selectors are not exposed in the public API + - With ``numpy < 1.15.0``, using the default ``bins='auto'`` results in an + extra call to ``np.histogram()`` to determine the bin lengths, since `the + NumPy bin-selectors are not exposed in the public API `_. For performance, you may - want to set the bins yourself. + want to set the bins yourself. If ``numpy >= 1.15`` is available, + ``np.histogram_bin_edges()`` is called instead, which is more efficient. Contributing diff --git a/pyemd/emd.pyx b/pyemd/emd.pyx index 398503a..77381b9 100644 --- a/pyemd/emd.pyx +++ b/pyemd/emd.pyx @@ -3,6 +3,8 @@ # distutils: language = c++ # emd.pyx +from pkg_resources import parse_version + from libcpp.pair cimport pair from libcpp.vector cimport vector import cython @@ -139,6 +141,16 @@ def euclidean_pairwise_distance_matrix(x): return distance_matrix.reshape(len(x), len(x)) +# Use `np.histogram_bin_edges` if available (since NumPy version 1.15.0) +if parse_version(np.__version__) >= parse_version('1.15.0'): + get_bins = np.histogram_bin_edges +else: + def get_bins(a, bins=10, **kwargs): + if isinstance(bins, str): + hist, bins = np.histogram(a, bins=bins, **kwargs) + return bins + + def emd_samples(first_array, second_array, extra_mass_penalty=DEFAULT_EXTRA_MASS_PENALTY, @@ -196,14 +208,10 @@ def emd_samples(first_array, if range is None: range = (min(np.min(first_array), np.min(second_array)), max(np.max(first_array), np.max(second_array))) - # Use automatic binning from `np.histogram()` - # TODO: Use `np.histogram_bin_edges()` when it's available; - # see https://github.com/numpy/numpy/issues/10183 - if isinstance(bins, str): - hist, _ = np.histogram(np.concatenate([first_array, second_array]), - range=range, - bins=bins) - bins = len(hist) + # Get bin edges using both arrays + bins = get_bins(np.concatenate([first_array, second_array]), + range=range, + bins=bins) # Compute histograms first_histogram, bin_edges = np.histogram(first_array, range=range, diff --git a/tox.ini b/tox.ini index b035c93..6f78336 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,12 @@ [tox] -envlist = py{27,34,35,36} +envlist = py{27,34,35,36}-numpy{114,115} [testenv] -deps = -r{toxinidir}/test_requirements.txt +deps = + -r{toxinidir}/test_requirements.txt + # Use NumPy < 1.14 and NumPy >= 1.15 for `get_bins()` switch + numpy114: numpy<1.15 + numpy115: numpy>=1.15 + commands = make test whitelist_externals = make From b0dde3b6d0a614cc1452093959a5fd65590b4d74 Mon Sep 17 00:00:00 2001 From: Timothy Fitz Date: Wed, 20 Mar 2019 20:59:06 -0400 Subject: [PATCH 05/61] Use Scipy's numpy behavior in setup.py. --- setup.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 65bc605..f3785d6 100644 --- a/setup.py +++ b/setup.py @@ -92,9 +92,24 @@ def finalize_options(self): exec(f.read(), ABOUT) -REQUIRES = [ - 'numpy >=1.9.0, <2.0.0' -] +NUMPY_REQUIREMENT = ['numpy >=1.9.0, <2.0.0'] + +# Copied from scipy's installer, to solve the same issues they saw: + +# Figure out whether to add ``*_requires = ['numpy']``. +# We don't want to do that unconditionally, because we risk updating +# an installed numpy which fails too often. Just if it's not installed, we +# may give it a try. See scipy gh-3379. +try: + import numpy +except ImportError: # We do not have numpy installed + REQUIRES = NUMPY_REQUIREMENT +else: + # If we're building a wheel, assume there already exist numpy wheels + # for this platform, so it is safe to add numpy to build requirements. + # See scipy gh-5184. + REQUIRES = (NUMPY_REQUIREMENT if 'bdist_wheel' in sys.argv[1:] + else []) setup( name=ABOUT['__title__'], From 629fd0d7355ef06227cab2cdb42af7524d07d5a6 Mon Sep 17 00:00:00 2001 From: Brett Randall Date: Mon, 10 Jun 2019 18:39:25 +1000 Subject: [PATCH 06/61] Fixed compilation on MacOS platforms where Python default target is <10.9. Modified setup.py to set env MACOSX_DEPLOYMENT_TARGET=10.9 when the build MacOS is that or newer, but the default MACOSX_DEPLOYMENT_TARGET (when absent) is <10.9, which happens when the running Python targets <10.9 min. This happens for example when running a current Anaconda Python, which has target 10.7. This fix taken verbatim from similar in Pandas, https://github.com/pandas-dev/pandas/pull/24274 . Fixed #39. --- setup.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/setup.py b/setup.py index f3785d6..0fd0c2f 100644 --- a/setup.py +++ b/setup.py @@ -3,19 +3,35 @@ import io import os +import platform import sys from warnings import warn from setuptools import Extension, setup from setuptools.command.build_ext import build_ext as _build_ext from setuptools.command.sdist import sdist as _sdist +from distutils.sysconfig import get_config_var +from distutils.version import LooseVersion +def is_platform_mac(): + return sys.platform == 'darwin' # Alias ModuleNotFound for Python <= 3.5 if (sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6)): ModuleNotFoundError = ImportError +# For mac, ensure extensions are built for macos 10.9 when compiling on a +# 10.9 system or above, overriding distuitls behaviour which is to target +# the version that python was built for. This may be overridden by setting +# MACOSX_DEPLOYMENT_TARGET before calling setup.py +if is_platform_mac(): + if 'MACOSX_DEPLOYMENT_TARGET' not in os.environ: + current_system = LooseVersion(platform.mac_ver()[0]) + python_target = LooseVersion( + get_config_var('MACOSX_DEPLOYMENT_TARGET')) + if python_target < '10.9' and current_system >= '10.9': + os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.9' try: from Cython.Build import cythonize as _cythonize From e1a6e5367c7433647588f0c302ac6f1c99224dc2 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Wed, 21 Aug 2019 11:03:34 -0500 Subject: [PATCH 07/61] emd_samples(): Clarify docstring --- pyemd/emd.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyemd/emd.pyx b/pyemd/emd.pyx index 77381b9..4cfff41 100644 --- a/pyemd/emd.pyx +++ b/pyemd/emd.pyx @@ -166,9 +166,9 @@ def emd_samples(first_array, Pairwise ground distances are taken from the center of the bins. Arguments: - first_array (Iterable): A 1D array of samples used to generate a + first_array (Iterable): An array of samples used to generate a histogram. - second_array (Iterable): A 1D array of samples used to generate a + second_array (Iterable): An array of samples used to generate a histogram. Keyword Arguments: From cf78540097f33ac3c80ccb86c15b392ded86ea84 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Wed, 21 Aug 2019 11:57:31 -0500 Subject: [PATCH 08/61] README: Update docs for `emd_samples()` --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1ceb20a..d4379cd 100644 --- a/README.rst +++ b/README.rst @@ -123,9 +123,9 @@ emd_samples() *Arguments:* -- ``first_array`` *(Iterable)*: A 1D array of samples used to generate a +- ``first_array`` *(Iterable)*: An array of samples used to generate a histogram. -- ``second_array`` *(Iterable)*: A 1D array of samples used to generate a +- ``second_array`` *(Iterable)*: An array of samples used to generate a histogram. *Keyword Arguments:* From d02ecdc1a41de43fbdfd7188f4c492eef48ab143 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sat, 22 Feb 2020 16:12:54 -0600 Subject: [PATCH 09/61] travis: Test on macOS and linux --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 025a4ad..489060f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ sudo: false +os: + - linux + - osx language: python python: - '2.7' From dc8d2ccad0f341fc78b13f7bb4938d89bf3857c0 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sat, 22 Feb 2020 16:22:05 -0600 Subject: [PATCH 10/61] travis: Test on Python 3.7 and 3.8 --- .travis.yml | 3 ++- pytest.ini | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 489060f..b7d1883 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,11 @@ os: - osx language: python python: - - '2.7' - '3.4' - '3.5' - '3.6' + - '3.7' + - '3.8' install: - pip install -r dev_requirements.txt - make build diff --git a/pytest.ini b/pytest.ini index facd2f7..1a744be 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,11 @@ [pytest] -addopts = --color=yes --tb=auto --doctest-glob='*.rst' --doctest-modules -vv +addopts = + --color=yes + --tb=auto + --doctest-glob='*.rst' + --doctest-modules -vv +norecursedirs = + dist + build + .tox + .eggs From 24c3143baac35f1b35b64b6a99bd20b5219a2615 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sat, 22 Feb 2020 16:29:16 -0600 Subject: [PATCH 11/61] travis: Try new macOS testing strategy --- .travis.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b7d1883..b3c00bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,10 +15,31 @@ install: - pip uninstall --yes -r dev_requirements.txt - pip install tox-travis script: tox +jobs: + include: + - stage: test + os: osx + language: sh + env: + - TOXENV=py3 + - HOMEBREW_NO_INSTALL_CLEANUP=1 + - HOMEBREW_NO_ANALYTICS=1 + before_cache: + - rm -f "$HOME/Library/Caches/pip/log/debug.log" + cache: + directories: + - "$HOME/Library/Caches/pip" + addons: + homebrew: + packages: python3 + before_install: + - python3 -m pip install --upgrade virtualenv + - virtualenv -p python3 --system-site-packages "$HOME/venv" + - source "$HOME/venv/bin/activate" notifications: email: false slack: rooms: secure: rxQsNRK9XBkBV0pdYuJG+tsN2tky+JUEF5ayDIUAzSaPeB//VVNNofJhcmfNgG1WiEEi6fe0dR/Y6UDsoVyQrbCHO2q2bIVQp6A/63vgz3DcVQzMahB/QVwte7gy02nLf6rS2g3VetVXrTW6OO4Cv7NQrQb58biVFx/yBtQ3qzI= on_success: never - on_failure: always + on_failure: always \ No newline at end of file From 435f3e2230a47ee9c8c39e33522b934e9072549d Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sat, 22 Feb 2020 16:33:19 -0600 Subject: [PATCH 12/61] travis: Remove "os: osx" --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3c00bd..785a032 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ sudo: false os: - linux - - osx language: python python: - '3.4' @@ -17,7 +16,7 @@ install: script: tox jobs: include: - - stage: test + - name: "macOS tests" os: osx language: sh env: From 162d8c7dcc769681c129e9c2056a58385f2b35ca Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sat, 22 Feb 2020 17:01:46 -0600 Subject: [PATCH 13/61] travis: Prefer python -m pip --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 785a032..fddc68b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,10 @@ python: - '3.7' - '3.8' install: - - pip install -r dev_requirements.txt + - python -m pip install -r dev_requirements.txt - make build - - pip uninstall --yes -r dev_requirements.txt - - pip install tox-travis + - python -m pip uninstall --yes -r dev_requirements.txt + - python -m pip install tox-travis script: tox jobs: include: From c5d754fdceb1c3f02a279672b0a8b1031a74d3e5 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 24 Feb 2020 18:18:07 -0600 Subject: [PATCH 14/61] travis: Try installing tox explicitly --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fddc68b..2990567 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,12 @@ install: - python -m pip install -r dev_requirements.txt - make build - python -m pip uninstall --yes -r dev_requirements.txt - - python -m pip install tox-travis + - python -m pip install --upgrade tox tox-travis + - python -m pip freeze script: tox jobs: + # See https://github.com/pyload/pyload/blob/master/.travis.yml + # for a workaround for testing on macOS with Travis include: - name: "macOS tests" os: osx From c8ff5c7ff140a497c9cdf206bab3187780e02d18 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 24 Feb 2020 18:37:19 -0600 Subject: [PATCH 15/61] travis: Try `brew update` --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2990567..c54ce4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ jobs: - "$HOME/Library/Caches/pip" addons: homebrew: + update: true packages: python3 before_install: - python3 -m pip install --upgrade virtualenv From 2e4d51024ee3470140042a4cdaca515d6237e4a5 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 24 Feb 2020 18:49:53 -0600 Subject: [PATCH 16/61] travis: Use `python` instead of `python3` --- .travis.yml | 4 ++-- tox.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c54ce4f..56714c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,8 @@ jobs: update: true packages: python3 before_install: - - python3 -m pip install --upgrade virtualenv - - virtualenv -p python3 --system-site-packages "$HOME/venv" + - python -m pip install --upgrade virtualenv + - virtualenv -p python --system-site-packages "$HOME/venv" - source "$HOME/venv/bin/activate" notifications: email: false diff --git a/tox.ini b/tox.ini index 6f78336..76f9105 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,34,35,36}-numpy{114,115} +envlist = py{34,35,36,37,38}-numpy{114,115} [testenv] deps = From 564ca774d8e5077f058e69d33f414c8be6e796ed Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 24 Feb 2020 19:08:35 -0600 Subject: [PATCH 17/61] travis: tox: Don't set TOXENV --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 56714c5..6dd93eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ jobs: os: osx language: sh env: - - TOXENV=py3 - HOMEBREW_NO_INSTALL_CLEANUP=1 - HOMEBREW_NO_ANALYTICS=1 before_cache: From 1c49ab02d9e1346cee6e5af558f6fd39e7b1dc87 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 13:04:19 +0800 Subject: [PATCH 18/61] Fix typos and capitalisation Suggested-by: codespell & spellintian --- pyemd/__init__.py | 2 +- pyemd/lib/emd_hat_impl.hpp | 4 ++-- setup.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyemd/__init__.py b/pyemd/__init__.py index 1957848..df15fcb 100644 --- a/pyemd/__init__.py +++ b/pyemd/__init__.py @@ -63,7 +63,7 @@ `_ and `Michael Werman `_. See the `relevant paper `_. -- Thanks to the Cython devlopers for making this kind of wrapper relatively +- Thanks to the Cython developers for making this kind of wrapper relatively easy to write. diff --git a/pyemd/lib/emd_hat_impl.hpp b/pyemd/lib/emd_hat_impl.hpp index 9e49770..197e857 100644 --- a/pyemd/lib/emd_hat_impl.hpp +++ b/pyemd/lib/emd_hat_impl.hpp @@ -126,7 +126,7 @@ struct emd_hat_impl_integral_types { } //if (needToSwapFlow) cout << "needToSwapFlow" << endl; - // creating the b vector that contains all vertexes + // creating the b vector that contains all vertices std::vector b(2*N+2); const NODE_T THRESHOLD_NODE= 2*N; const NODE_T ARTIFICIAL_NODE= 2*N+1; // need to be last ! @@ -218,7 +218,7 @@ struct emd_hat_impl_integral_types { //==================================================== // remove nodes with supply demand of 0 - // and vertexes that are connected only to the + // and vertices that are connected only to the // threshold vertex //==================================================== NODE_T current_node_name= 0; diff --git a/setup.py b/setup.py index 0fd0c2f..d75ce81 100644 --- a/setup.py +++ b/setup.py @@ -21,9 +21,9 @@ def is_platform_mac(): (sys.version_info[0] == 3 and sys.version_info[1] < 6)): ModuleNotFoundError = ImportError -# For mac, ensure extensions are built for macos 10.9 when compiling on a -# 10.9 system or above, overriding distuitls behaviour which is to target -# the version that python was built for. This may be overridden by setting +# For macOS, ensure extensions are built for macOS 10.9 when compiling on a +# 10.9 system or above, overriding distutils behaviour which is to target +# the version that Python was built for. This may be overridden by setting # MACOSX_DEPLOYMENT_TARGET before calling setup.py if is_platform_mac(): if 'MACOSX_DEPLOYMENT_TARGET' not in os.environ: From dcc8bbcb17b4ae00fc7b50f8c74e63b44ab260cf Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 13:32:10 +0800 Subject: [PATCH 19/61] Reformat long lines Suggested-by: doc8 --- README.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index d4379cd..f6da96a 100644 --- a/README.rst +++ b/README.rst @@ -84,8 +84,8 @@ emd() resulting distance to be a metric, it should be at least half the diameter of the space (maximum possible distance between any two points). If you want partial matching you can set it to zero (but then the resulting distance is - not guaranteed to be a metric). The default value is ``-1.0``, which means the - maximum value in the distance matrix is used. + not guaranteed to be a metric). The default value is ``-1.0``, which means + the maximum value in the distance matrix is used. *Returns:* *(float)* The EMD value. @@ -132,9 +132,9 @@ emd_samples() - ``extra_mass_penalty`` *(float)*: Same as for ``emd()``. - ``distance`` *(string or function)*: A string or function implementing - a metric on a 1D ``np.ndarray``. Defaults to the Euclidean distance. Currently - limited to 'euclidean' or your own function, which must take a 1D array and - return a square 2D array of pairwise distances. + a metric on a 1D ``np.ndarray``. Defaults to the Euclidean distance. + Currently limited to 'euclidean' or your own function, which must take + a 1D array and return a square 2D array of pairwise distances. - ``normalized`` (*boolean*): If true (default), treat histograms as fractions of the dataset. If false, treat histograms as counts. In the latter case the EMD will vary greatly by array length. @@ -147,8 +147,8 @@ emd_samples() ``first_array`` and ``second_array``. Note: if the given range is not a superset of the default range, no warning will be given. -*Returns:* *(float)* The EMD value between the histograms of ``first_array`` and -``second_array``. +*Returns:* *(float)* The EMD value between the histograms of ``first_array`` +and ``second_array``. ---- @@ -163,8 +163,8 @@ Limitations and Caveats - The histograms and distance matrix must be numpy arrays of type ``np.float64``. The original C++ template function can accept any numerical C++ type, but this wrapper only instantiates the template with ``double`` - (Cython converts ``np.float64`` to ``double``). If there's demand, I can add - support for other types. + (Cython converts ``np.float64`` to ``double``). If there's demand, I can + add support for other types. - ``emd_with_flow()``: From fc4fa37159b98bbd9d502311082510f3cd9d6ad6 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 13:50:50 +0800 Subject: [PATCH 20/61] Fix broken links pyload dropped travis due to the cost changes. Ofir Pele's website and paper download locations changed. Fixes: commit 8ef13daed86c91fa350a460331f968bca8690a26 --- .travis.yml | 2 +- pyemd/__init__.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6dd93eb..465b4fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: - python -m pip freeze script: tox jobs: - # See https://github.com/pyload/pyload/blob/master/.travis.yml + # See https://github.com/pyload/pyload/blob/458fa33b42/.travis.yml # for a workaround for testing on macOS with Travis include: - name: "macOS tests" diff --git a/pyemd/__init__.py b/pyemd/__init__.py index df15fcb..91c280e 100644 --- a/pyemd/__init__.py +++ b/pyemd/__init__.py @@ -8,7 +8,7 @@ PyEMD is a Python wrapper for `Ofir Pele and Michael Werman's implementation of the Earth Mover's Distance -`_ that allows it to be +`_ that allows it to be used with NumPy. **If you use this code, please cite the papers listed at the end of the @@ -60,9 +60,9 @@ ~~~~~~ - All credit for the actual algorithm and implementation goes to `Ofir Pele - `_ and `Michael Werman + `_ and `Michael Werman `_. See the `relevant paper - `_. + `_. - Thanks to the Cython developers for making this kind of wrapper relatively easy to write. From 1837a85c229431a42ae8f53b0842aba97f6d628f Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 13:44:23 +0800 Subject: [PATCH 21/61] Update links from http to https Don't update links that don't support https. Suggested-by: check-all-the-things --- README.rst | 8 ++++---- pyemd/__about__.py | 2 +- pyemd/__init__.py | 8 ++++---- pyemd/lib/emd_hat_signatures_interface.hpp | 2 +- setup.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index f6da96a..88a87b6 100644 --- a/README.rst +++ b/README.rst @@ -8,8 +8,8 @@ PyEMD: Fast EMD for Python ========================== PyEMD is a Python wrapper for `Ofir Pele and Michael Werman's implementation -`_ of the `Earth Mover's -Distance `_ that allows +`_ of the `Earth Mover's +Distance `_ that allows it to be used with NumPy. **If you use this code, please cite the papers listed at the end of this document.** @@ -200,8 +200,8 @@ Credit ------ - All credit for the actual algorithm and implementation goes to `Ofir Pele - `_ and `Michael Werman - `_. See the `relevant paper + `_ and `Michael Werman + `_. See the `relevant paper `_. - Thanks to the Cython developers for making this kind of wrapper relatively easy to write. diff --git a/pyemd/__about__.py b/pyemd/__about__.py index 40bffc2..48408e8 100644 --- a/pyemd/__about__.py +++ b/pyemd/__about__.py @@ -13,7 +13,7 @@ __author_website__ = 'http://willmayner.com' __license__ = 'MIT' __copyright__ = 'Copyright (c) 2014-2017 Will Mayner' -__url__ = 'http://github.com/wmayner/pyemd' +__url__ = 'https://github.com/wmayner/pyemd' __all__ = ['__title__', '__version__', '__description__', '__author__', '__author_email__', '__author_website__', '__license__', diff --git a/pyemd/__init__.py b/pyemd/__init__.py index 91c280e..9882444 100644 --- a/pyemd/__init__.py +++ b/pyemd/__init__.py @@ -8,7 +8,7 @@ PyEMD is a Python wrapper for `Ofir Pele and Michael Werman's implementation of the Earth Mover's Distance -`_ that allows it to be +`_ that allows it to be used with NumPy. **If you use this code, please cite the papers listed at the end of the @@ -60,9 +60,9 @@ ~~~~~~ - All credit for the actual algorithm and implementation goes to `Ofir Pele - `_ and `Michael Werman - `_. See the `relevant paper - `_. + `_ and `Michael Werman + `_. See the `relevant paper + `_. - Thanks to the Cython developers for making this kind of wrapper relatively easy to write. diff --git a/pyemd/lib/emd_hat_signatures_interface.hpp b/pyemd/lib/emd_hat_signatures_interface.hpp index d42b87f..c725b43 100644 --- a/pyemd/lib/emd_hat_signatures_interface.hpp +++ b/pyemd/lib/emd_hat_signatures_interface.hpp @@ -6,7 +6,7 @@ //============================================================================= // This interface is similar to Rubner's interface. See: -// http://www.cs.duke.edu/~tomasi/software/emd.htm +// https://www.cs.duke.edu/~tomasi/software/emd.htm // With the following changes; // 1. Weights of signature should be of type NUM_T (see emd_hat.hpp) // 2. Return value of the distance function (func) should be of type NUM_T diff --git a/setup.py b/setup.py index d75ce81..9469ab3 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def run(self): _sdist.run(self) -# See http://stackoverflow.com/a/21621689/1085344 +# See https://stackoverflow.com/a/21621689/1085344 class build_ext(_build_ext): def finalize_options(self): _build_ext.finalize_options(self) From 1e6fa464c13fa1eec93d7a6929b66d75a864c0a0 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 14:14:38 +0800 Subject: [PATCH 22/61] Adjust code to align with Python PEP-8 more closely Suggested-by: pycodestyle --- pyemd/__init__.py | 4 ++-- setup.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyemd/__init__.py b/pyemd/__init__.py index 9882444..333e37a 100644 --- a/pyemd/__init__.py +++ b/pyemd/__init__.py @@ -8,8 +8,8 @@ PyEMD is a Python wrapper for `Ofir Pele and Michael Werman's implementation of the Earth Mover's Distance -`_ that allows it to be -used with NumPy. +`_ +that allows it to be used with NumPy. **If you use this code, please cite the papers listed at the end of the README.** diff --git a/setup.py b/setup.py index 9469ab3..6c9c398 100644 --- a/setup.py +++ b/setup.py @@ -13,12 +13,13 @@ from distutils.sysconfig import get_config_var from distutils.version import LooseVersion + def is_platform_mac(): return sys.platform == 'darwin' + # Alias ModuleNotFound for Python <= 3.5 -if (sys.version_info[0] < 3 or - (sys.version_info[0] == 3 and sys.version_info[1] < 6)): +if sys.version_info < (3, 6): ModuleNotFoundError = ImportError # For macOS, ensure extensions are built for macOS 10.9 when compiling on a @@ -71,7 +72,7 @@ def cythonize(extensions, **_ignore): class sdist(_sdist): def run(self): - # Make sure the compiled Cython files in the distribution are up-to-date + # Ensure the compiled Cython files in the distribution are up-to-date if USE_CYTHON: _cythonize(EXTENSIONS) else: @@ -124,8 +125,7 @@ def finalize_options(self): # If we're building a wheel, assume there already exist numpy wheels # for this platform, so it is safe to add numpy to build requirements. # See scipy gh-5184. - REQUIRES = (NUMPY_REQUIREMENT if 'bdist_wheel' in sys.argv[1:] - else []) + REQUIRES = (NUMPY_REQUIREMENT if 'bdist_wheel' in sys.argv[1:] else []) setup( name=ABOUT['__title__'], From 710c4f93b2c2fd3c34ddd64cecc083a19f8bce28 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 14:27:44 +0800 Subject: [PATCH 23/61] Add missing trailing newline Suggested-by: yamllint --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 465b4fd..6f0bd0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,4 +44,4 @@ notifications: rooms: secure: rxQsNRK9XBkBV0pdYuJG+tsN2tky+JUEF5ayDIUAzSaPeB//VVNNofJhcmfNgG1WiEEi6fe0dR/Y6UDsoVyQrbCHO2q2bIVQp6A/63vgz3DcVQzMahB/QVwte7gy02nLf6rS2g3VetVXrTW6OO4Cv7NQrQb58biVFx/yBtQ3qzI= on_success: never - on_failure: always \ No newline at end of file + on_failure: always From b9403d7b33693ac097ebb085790b28b80a10fac7 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 14:35:45 +0800 Subject: [PATCH 24/61] Remove trailing whitespace --- pyemd/lib/EMD_DEFS.hpp | 4 +- pyemd/lib/emd_hat_impl.hpp | 102 ++++++++++---------- pyemd/lib/emd_hat_signatures_interface.hpp | 14 +-- pyemd/lib/flow_utils.hpp | 6 +- pyemd/lib/min_cost_flow.hpp | 104 ++++++++++----------- 5 files changed, 115 insertions(+), 115 deletions(-) diff --git a/pyemd/lib/EMD_DEFS.hpp b/pyemd/lib/EMD_DEFS.hpp index 27ae75f..b118379 100644 --- a/pyemd/lib/EMD_DEFS.hpp +++ b/pyemd/lib/EMD_DEFS.hpp @@ -7,14 +7,14 @@ typedef int NODE_T; //------------------------------------------------------------------------------ -#endif +#endif // Copyright (c) 2009-2012, Ofir Pele // All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are -// met: +// met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright diff --git a/pyemd/lib/emd_hat_impl.hpp b/pyemd/lib/emd_hat_impl.hpp index 197e857..d265cf9 100644 --- a/pyemd/lib/emd_hat_impl.hpp +++ b/pyemd/lib/emd_hat_impl.hpp @@ -20,8 +20,8 @@ void fillFWithZeros(std::vector< std::vector >& F) { } } } - -// Forward declarations + +// Forward declarations template struct emd_hat_impl; template @@ -31,12 +31,12 @@ NUM_T emd_hat_gd_metric::operator()(const std::vector& P std::vector< std::vector >* F) { if (FLOW_TYPE!=NO_FLOW) fillFWithZeros(*F); - + assert( (F!=NULL) || (FLOW_TYPE==NO_FLOW) ); - + std::vector P= Pc; std::vector Q= Qc; - + // Assuming metric property we can pre-flow 0-cost edges {for (NODE_T i=0; i::operator()(const std::vector& P }} return emd_hat_impl()(Pc,Qc,P,Q,C,extra_mass_penalty,F); - + } // emd_hat_gd_metric template @@ -77,7 +77,7 @@ NUM_T emd_hat::operator()(const std::vector& P, const st // Blocking instantiation for a non-overloaded template param template struct emd_hat_impl { - + }; // emd_hat_impl @@ -125,7 +125,7 @@ struct emd_hat_impl_integral_types { abs_diff_sum_P_sum_Q= sum_P-sum_Q; } //if (needToSwapFlow) cout << "needToSwapFlow" << endl; - + // creating the b vector that contains all vertices std::vector b(2*N+2); const NODE_T THRESHOLD_NODE= 2*N; @@ -136,16 +136,16 @@ struct emd_hat_impl_integral_types { {for (NODE_T i=N; i<2*N; ++i) { b[i]= (Q[i-N]); }} - + // remark*) I put here a deficit of the extra mass, as mass that flows to the threshold node // can be absorbed from all sources with cost zero (this is in reverse order from the paper, // where incoming edges to the threshold node had the cost of the threshold and outgoing // edges had the cost of zero) // This also makes sum of b zero. - b[THRESHOLD_NODE]= -abs_diff_sum_P_sum_Q; + b[THRESHOLD_NODE]= -abs_diff_sum_P_sum_Q; b[ARTIFICIAL_NODE]= 0; //------------------------------------------------------- - + //------------------------------------------------------- NUM_T maxC= 0; {for (NODE_T i=0; i sources_that_flow_not_only_to_thresh; - std::set< NODE_T > sinks_that_get_flow_not_only_from_thresh; + std::set< NODE_T > sources_that_flow_not_only_to_thresh; + std::set< NODE_T > sinks_that_get_flow_not_only_from_thresh; NUM_T pre_flow_cost= 0; //============================================================= - + //============================================================= // regular edges between sinks and sources without threshold edges std::vector< std::list< edge > > c(b.size()); @@ -193,8 +193,8 @@ struct emd_hat_impl_integral_types { {for (NODE_T i=N; i<2*N; ++i) { b[i]= -b[i]; }} - - + + // add edges from/to threshold node, // note that costs are reversed to the paper (see also remark* above) // It is important that it will be this way because of remark* above. @@ -203,8 +203,8 @@ struct emd_hat_impl_integral_types { }} {for (NODE_T j=0; j(j+N, maxC) ); - }} - + }} + // artificial arcs - Note the restriction that only one edge i,j is artificial so I ignore it... {for (NODE_T i=0; i(ARTIFICIAL_NODE, maxC + 1 ) ); @@ -212,15 +212,15 @@ struct emd_hat_impl_integral_types { }} //============================================================= - - - - //==================================================== + + + + //==================================================== // remove nodes with supply demand of 0 // and vertices that are connected only to the // threshold vertex - //==================================================== + //==================================================== NODE_T current_node_name= 0; // Note here it should be vector and not vector // as I'm using -1 as a special flag !!! @@ -230,7 +230,7 @@ struct emd_hat_impl_integral_types { nodes_old_names.reserve(b.size()); {for (NODE_T i=0; i=N) - } + } } }} //i nodes_new_names[THRESHOLD_NODE]= current_node_name; @@ -258,7 +258,7 @@ struct emd_hat_impl_integral_types { ++j; } }} - + std::vector< std::list< edge > > cc(bb.size()); {for (NODE_T i=0; i mcf; - + NUM_T my_dist; - + std::vector< std::list< edge0 > > flows(bb.size()); //std::cout << bb.size() << std::endl; @@ -301,7 +301,7 @@ struct emd_hat_impl_integral_types { bool reverseEdge= it->_to_to]-N; + j= nodes_old_names[it->_to]-N; } else { i= nodes_old_names[it->_to]; j= nodes_old_names[new_name_from]-N; @@ -318,18 +318,18 @@ struct emd_hat_impl_integral_types { } } } - + if (FLOW_TYPE==WITHOUT_EXTRA_MASS_FLOW) transform_flow_to_regular(*F,POrig,QOrig); - + my_dist= pre_flow_cost + // pre-flowing on cases where it was possible mcf_dist + // solution of the transportation problem (abs_diff_sum_P_sum_Q*extra_mass_penalty); // emd-hat extra mass penalty - + return my_dist; //------------------------------------------------------- - + } // emd_hat_impl_integral_types (main implementation) operator() }; //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -342,7 +342,7 @@ template struct emd_hat_impl { typedef int NUM_T; - + NUM_T operator()( const std::vector& POrig, const std::vector& QOrig, const std::vector& P, const std::vector& Q, @@ -351,14 +351,14 @@ struct emd_hat_impl { std::vector< std::vector >* F) { return emd_hat_impl_integral_types()(POrig,QOrig,P,Q,C,extra_mass_penalty,F); } - + }; // emd_hat_impl template struct emd_hat_impl { typedef long int NUM_T; - + NUM_T operator()( const std::vector& POrig, const std::vector& QOrig, const std::vector& P, const std::vector& Q, @@ -368,14 +368,14 @@ struct emd_hat_impl { return emd_hat_impl_integral_types()(POrig,QOrig,P,Q,C,extra_mass_penalty,F); } - + }; // emd_hat_impl template struct emd_hat_impl { typedef long long int NUM_T; - + NUM_T operator()( const std::vector& POrig, const std::vector& QOrig, const std::vector& P, const std::vector& Q, @@ -384,7 +384,7 @@ struct emd_hat_impl { std::vector< std::vector >* F) { return emd_hat_impl_integral_types()(POrig,QOrig,P,Q,C,extra_mass_penalty,F); } - + }; // emd_hat_impl //---------------------------------------------------------------------------------------- @@ -397,23 +397,23 @@ struct emd_hat_impl { typedef double NUM_T; typedef long long int CONVERT_TO_T; - + NUM_T operator()( const std::vector& POrig, const std::vector& QOrig, const std::vector& P, const std::vector& Q, const std::vector< std::vector >& C, NUM_T extra_mass_penalty, std::vector< std::vector >* F) { - + // TODO: static assert assert(sizeof(CONVERT_TO_T)>=8); - + // This condition should hold: // ( 2^(sizeof(CONVERT_TO_T*8)) >= ( MULT_FACTOR^2 ) // Note that it can be problematic to check it because // of overflow problems. I simply checked it with Linux calc // which has arbitrary precision. - const double MULT_FACTOR= 1000000; + const double MULT_FACTOR= 1000000; // Constructing the input const NODE_T N= P.size(); @@ -457,11 +457,11 @@ struct emd_hat_impl { // unnormalize dist= dist/PQnormFactor; dist= dist/CnormFactor; - + // adding extra mass penalty if (extra_mass_penalty==-1) extra_mass_penalty= maxC; dist+= (maxSum-minSum)*extra_mass_penalty; - + // converting flow to double if (FLOW_TYPE!=NO_FLOW) { for (NODE_T i= 0; i { } } } - + return dist; } - + }; // emd_hat_impl //---------------------------------------------------------------------------------------- #endif @@ -483,7 +483,7 @@ struct emd_hat_impl { // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are -// met: +// met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright diff --git a/pyemd/lib/emd_hat_signatures_interface.hpp b/pyemd/lib/emd_hat_signatures_interface.hpp index c725b43..1e08a9c 100644 --- a/pyemd/lib/emd_hat_signatures_interface.hpp +++ b/pyemd/lib/emd_hat_signatures_interface.hpp @@ -9,12 +9,12 @@ // https://www.cs.duke.edu/~tomasi/software/emd.htm // With the following changes; // 1. Weights of signature should be of type NUM_T (see emd_hat.hpp) -// 2. Return value of the distance function (func) should be of type NUM_T -// 3. Return value of the emd_hat_signature_interface function is NUM_T +// 2. Return value of the distance function (func) should be of type NUM_T +// 3. Return value of the emd_hat_signature_interface function is NUM_T // 4. The function does not return a flow (I may add this in future, if needed) // 5. The function also gets the penalty for extra mass - if you want metric property // should be at least half the diameter of the space (maximum possible distance -// between any two points). In Rubner's code this is implicitly 0. +// between any two points). In Rubner's code this is implicitly 0. // 6. The result is not normalized with the flow. // // To get the same results as Rubner's code you should set extra_mass_penalty to 0, @@ -59,16 +59,16 @@ template NUM_T emd_hat_signature_interface(signature_tt* Signature1, signature_tt* Signature2, NUM_T (*func)(feature_tt*, feature_tt*), NUM_T extra_mass_penalty) { - + std::vector P(Signature1->n + Signature2->n , 0); - std::vector Q(Signature1->n + Signature2->n , 0); + std::vector Q(Signature1->n + Signature2->n , 0); for (int i=0; in; ++i) { P[i]= Signature1->Weights[i]; } for (int j=0; jn; ++j) { Q[j+Signature1->n]= Signature2->Weights[j]; } - + std::vector< std::vector > C(P.size(), std::vector(P.size(), 0) ); {for (int i=0; in; ++i) { {for (int j=0; jn; ++j) { @@ -90,7 +90,7 @@ NUM_T emd_hat_signature_interface(signature_tt* Signature1, signature_tt< // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are -// met: +// met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright diff --git a/pyemd/lib/flow_utils.hpp b/pyemd/lib/flow_utils.hpp index b056dd9..d9dba6a 100644 --- a/pyemd/lib/flow_utils.hpp +++ b/pyemd/lib/flow_utils.hpp @@ -46,7 +46,7 @@ void transform_flow_to_regular(std::vector< std::vector >& F, return_flow_from_to_transhipment_vertex(F,P,Q, flow_from_P_to_transhipment, flow_from_transhipment_to_Q); - + NODE_T i= 0; NODE_T j= 0; while( true ) { @@ -54,7 +54,7 @@ void transform_flow_to_regular(std::vector< std::vector >& F, while (i >& F, // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are -// met: +// met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright diff --git a/pyemd/lib/min_cost_flow.hpp b/pyemd/lib/min_cost_flow.hpp index 57bc037..39fef40 100644 --- a/pyemd/lib/min_cost_flow.hpp +++ b/pyemd/lib/min_cost_flow.hpp @@ -84,13 +84,13 @@ class min_cost_flow { //for (NODE_T i=0; i >::const_iterator it= c[from].begin(); it!=c[from].end(); ++it) { @@ -98,7 +98,7 @@ class min_cost_flow { x[it->_to].push_back( edge0 (from, -it->_cost,0) ); }} // it }} // from - + // reduced costs for forward edges (c[i,j]-pi[i]+pi[j]) // Note that for forward edges the residual capacity is infinity std::vector< std::list< edge1 > > r_cost_forward(_num_nodes); @@ -107,7 +107,7 @@ class min_cost_flow { r_cost_forward[from].push_back( edge1(it->_to,it->_cost) ); }} }} - + // reduced costs and capacity for backward edges (c[j,i]-pi[j]+pi[i]) // Since the flow at the beginning is 0, the residual capacity is also zero std::vector< std::list< edge2 > > r_cost_cap_backward(_num_nodes); @@ -116,7 +116,7 @@ class min_cost_flow { r_cost_cap_backward[ it->_to ].push_back( edge2(from,-it->_cost,0) ); }} // it }} // from - + // Max supply TODO:demand?, given U?, optimization-> min out of demand,supply NUM_T U= 0; {for (NODE_T i=0; i<_num_nodes; ++i) { @@ -124,26 +124,26 @@ class min_cost_flow { }} NUM_T delta= static_cast(pow(2.0l,ceil(log(static_cast(U))/log(2.0)))); - + std::vector< NUM_T > d(_num_nodes); std::vector< NODE_T > prev(_num_nodes); delta= 1; //while (delta>=1) { - + // delta-scaling phase //cout << "delta==" << delta << endl; - + //tictoc_while_true.tic(); while (true) { //until we break when S or T is empty - + NUM_T maxSupply= 0; NODE_T k=0; for (NODE_T i=0; i<_num_nodes; ++i) { if (e[i]>0) { if (maxSupply >::iterator itccb= r_cost_cap_backward[from].begin(); while ( (itccb!=r_cost_cap_backward[from].end()) && (itccb->_to!=to) ) { @@ -174,7 +174,7 @@ class min_cost_flow { if (itccb!=r_cost_cap_backward[from].end()) { if (itccb->_residual_capacity_residual_capacity; } - + to= from; } while (to!=k); //--------------------------------------------------------------- @@ -185,14 +185,14 @@ class min_cost_flow { do { NODE_T from= prev[to]; assert(from!=to); - + // TODO - might do here O(n) can be done in O(1) typename std::list< edge0 >::iterator itx= x[from].begin(); while (itx->_to!=to) { ++itx; } itx->_flow+= delta; - + // update residual for backward edges typename std::list< edge2 >::iterator itccb= r_cost_cap_backward[to].begin(); while ( (itccb!=r_cost_cap_backward[to].end()) && (itccb->_to!=from) ) { @@ -212,21 +212,21 @@ class min_cost_flow { // update e e[to]+= delta; e[from]-= delta; - + to= from; } while (to!=k); //--------------------------------------------------------------------------------- - - + + } // while true (until we break when S or T is empty) //tictoc_while_true.toc(); //cout << "while true== " << tictoc_while_true.totalTimeSec() << endl; - + //delta= delta/2; //} // (delta-scaling phase) - - + + // compute distance from x //cout << endl << endl; NUM_T dist= 0; @@ -236,8 +236,8 @@ class min_cost_flow { dist+= (it->_cost*it->_flow); }} // it }} // from - - + + //tictoc_all_function.toc(); //cout << "operator() time==" << tictoc_all_function.totalTimeSec() << endl; //cout << "compute_shortest_path_time==" << tictoc_shortest_path.totalTimeSec() << endl; @@ -260,24 +260,24 @@ class min_cost_flow { void compute_shortest_path(std::vector< NUM_T >& d, std::vector< NODE_T >& prev, - + NODE_T from, std::vector< std::list< edge1 > >& cost_forward, std::vector< std::list< edge2 > >& cost_backward, const std::vector& e, NODE_T& l) { - - + + //---------------------------------------------------------------- // Making heap (all inf except 0, so we are saving comparisons...) //---------------------------------------------------------------- std::vector< edge3 > Q(_num_nodes); - + Q[0]._to= from; _nodes_to_Q[from]= 0; - Q[0]._dist= 0; - + Q[0]._dist= 0; + NODE_T j=1; // TODO: both of these into a function? {for (NODE_T i=0; i finalNodesFlg(_num_nodes, false); do { NODE_T u= Q[0]._to; - + d[u]= Q[0]._dist; // final distance finalNodesFlg[u]= true; if (e[u]<0) { l= u; break; } - + heap_remove_first(Q, _nodes_to_Q); - - - // neighbors of u + + + // neighbors of u {for (typename std::list< edge1 >::const_iterator it= cost_forward[u].begin(); it!=cost_forward[u].end(); ++it) { assert (it->_reduced_cost>=0); NUM_T alt= d[u]+it->_reduced_cost; @@ -339,7 +339,7 @@ class min_cost_flow { } while (!Q.empty()); - + //tmp_tic_toc.tic(); //--------------------------------------------------------------------------------- // reduced costs for forward edges (c[i,j]-pi[i]+pi[j]) @@ -354,7 +354,7 @@ class min_cost_flow { } } } }} - + // reduced costs and capacity for backward edges (c[j,i]-pi[j]+pi[i]) {for (NODE_T from=0; from<_num_nodes; ++from) { { for (typename std::list< edge2 >::iterator it= cost_backward[from].begin(); @@ -365,15 +365,15 @@ class min_cost_flow { if (finalNodesFlg[it->_to]) { it->_reduced_cost-= d[it->_to] - d[l]; } - + } }// it }} //--------------------------------------------------------------------------------- //tmp_tic_toc.toc(); - + //---------------------------------------------------------------- - - + + } // compute_shortest_path void heap_decrease_key(std::vector< edge3 >& Q, std::vector& nodes_to_Q, @@ -385,7 +385,7 @@ class min_cost_flow { i= PARENT(i); } } // heap_decrease_key - + void heap_remove_first(std::vector< edge3 >& Q, std::vector& nodes_to_Q) { swap_heap(Q, nodes_to_Q, 0, Q.size()-1); Q.pop_back(); @@ -415,9 +415,9 @@ class min_cost_flow { swap_heap(Q, nodes_to_Q, i, smallest); i= smallest; - + } while (true); - + } // end heapify @@ -430,10 +430,10 @@ class min_cost_flow { nodes_to_Q[ Q[j]._to ]= j; nodes_to_Q[ Q[i]._to ]= i; } // swap_heapify - + NODE_T LEFT(NODE_T i) { return 2*(i+1)-1; - } + } NODE_T RIGHT(NODE_T i) { return 2*(i+1); // 2*(i+1)+1-1 @@ -442,7 +442,7 @@ class min_cost_flow { NODE_T PARENT(NODE_T i) { return (i-1)/2; } - + }; // end min_cost_flow @@ -453,7 +453,7 @@ class min_cost_flow { // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are -// met: +// met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright From 34631658ae0cc555001b692623c23c02ed8d5611 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Mon, 2 Aug 2021 14:55:06 +0800 Subject: [PATCH 25/61] Mark uses of ragged nested sequences in NumPy arrays as intented for two tests The tests check that emd() raises a ValueError when passed incorrect matrices. The ragged sequences are now deprecated in NumPy unless marked as intended: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray. --- test/test_pyemd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_pyemd.py b/test/test_pyemd.py index 96c7ce2..9345cb8 100644 --- a/test/test_pyemd.py +++ b/test/test_pyemd.py @@ -130,7 +130,7 @@ def test_emd_validate_symmetric_distance_matrix(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0]) distance_matrix = np.array([[0.0, 0.5, 3.0], - [0.5, 0.0]]) + [0.5, 0.0]], dtype=object) with pytest.raises(ValueError): emd(first_signature, second_signature, distance_matrix) @@ -276,7 +276,7 @@ def test_emd_with_flow_validate_square_distance_matrix(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0]) distance_matrix = np.array([[0.0, 0.5, 3.0], - [0.5, 0.0]]) + [0.5, 0.0]], dtype=object) with pytest.raises(ValueError): emd_with_flow(first_signature, second_signature, distance_matrix) From fefe1298fc9f93101e608e85c04d8282b0501c4f Mon Sep 17 00:00:00 2001 From: Simon Swan Date: Tue, 24 Aug 2021 02:48:13 +0100 Subject: [PATCH 26/61] Fix numpy requirement for python3.6 (<1.20.0) --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c9c398..9e52eeb 100644 --- a/setup.py +++ b/setup.py @@ -109,7 +109,10 @@ def finalize_options(self): exec(f.read(), ABOUT) -NUMPY_REQUIREMENT = ['numpy >=1.9.0, <2.0.0'] +NUMPY_REQUIREMENT = [ + "numpy >=1.9.0, <1.20.0; python_version<='3.6'" + "numpy >=1.9.0, <2.0.0; python_version>'3.6'" +] # Copied from scipy's installer, to solve the same issues they saw: @@ -150,5 +153,7 @@ def finalize_options(self): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', ], ) From 9e8807ee69812f682fe30d5944a48b7789f3ab4e Mon Sep 17 00:00:00 2001 From: Simon Swan Date: Tue, 24 Aug 2021 02:59:37 +0100 Subject: [PATCH 27/61] Add classifer for version 3.9 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 9e52eeb..184d40b 100644 --- a/setup.py +++ b/setup.py @@ -155,5 +155,6 @@ def finalize_options(self): 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ], ) From 09c2bf1acad5238c61fae76195af0c203b3fd58e Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Sun, 22 Jan 2023 12:31:59 +0800 Subject: [PATCH 28/61] Replace list of classifier Python versions with just 2 and 3 Python version releases happen more often than PyEMD releases and there are usually no new compatibility issues for PyEMD. --- setup.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 184d40b..9b000d9 100644 --- a/setup.py +++ b/setup.py @@ -149,12 +149,7 @@ def finalize_options(self): 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - '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 :: 2', + 'Programming Language :: Python :: 3', ], ) From 05a5fb7c27c3e1c7bbc9312f6a6537a871cd0d39 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Sun, 22 Jan 2023 11:59:56 +0800 Subject: [PATCH 29/61] Add support for up to Python 3.11 Add to travis and tox envlist. --- .travis.yml | 3 +++ tox.ini | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6f0bd0f..170a6d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,9 @@ python: - '3.6' - '3.7' - '3.8' + - '3.9' + - '3.10' + - '3.11' install: - python -m pip install -r dev_requirements.txt - make build diff --git a/tox.ini b/tox.ini index 76f9105..f610e9f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{34,35,36,37,38}-numpy{114,115} +envlist = py{34,35,36,37,38,39,310,311}-numpy{114,115} [testenv] deps = From 95375dad287544d36ccc9397ac7cc14525cc7a36 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Sun, 22 Jan 2023 12:10:16 +0800 Subject: [PATCH 30/61] Select the test PyPI repository by name instead of URL Suggested-in: https://packaging.python.org/en/latest/tutorials/packaging-projects/#uploading-your-project-to-pypi --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8d3bfe8..cf0938f 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ upload-dist: sign-dist twine upload $(dist_dir)/* test-dist: check-dist - twine upload --repository-url https://test.pypi.org/legacy/ $(dist_dir)/* + twine upload --repository testpypi $(dist_dir)/* sign-dist: check-dist gpg --detach-sign -a dist/*.tar.gz From 93957651a14ff4b1bfb391e828c383f765d0ead1 Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Sun, 22 Jan 2023 12:46:05 +0800 Subject: [PATCH 31/61] Sync PyEMD long description changes from the README to the pyemd docstring Fixes: 8ef13daed86c91fa350a460331f968bca8690a26 Fixes: 1e6fa464c13fa1eec93d7a6929b66d75a864c0a0 Fixes: 1837a85c229431a42ae8f53b0842aba97f6d628f Fixes: fc4fa37159b98bbd9d502311082510f3cd9d6ad6 --- pyemd/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyemd/__init__.py b/pyemd/__init__.py index 333e37a..b0245c0 100644 --- a/pyemd/__init__.py +++ b/pyemd/__init__.py @@ -6,10 +6,10 @@ PyEMD ===== -PyEMD is a Python wrapper for `Ofir Pele and Michael Werman's implementation of -the Earth Mover's Distance -`_ -that allows it to be used with NumPy. +PyEMD is a Python wrapper for `Ofir Pele and Michael Werman's implementation +`_ of the `Earth Mover's +Distance `_ that allows +it to be used with NumPy. **If you use this code, please cite the papers listed at the end of the README.** From 7f58e782d51f371534f7628a97711a0e1a1fc60b Mon Sep 17 00:00:00 2001 From: Paul Wise Date: Sun, 22 Jan 2023 13:05:01 +0800 Subject: [PATCH 32/61] Adjust code to align with Python PEP-8 more closely Suggested-by: pycodestyle --- test/test_pyemd.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/test_pyemd.py b/test/test_pyemd.py index 9345cb8..5c784d6 100644 --- a/test/test_pyemd.py +++ b/test/test_pyemd.py @@ -310,7 +310,8 @@ def test_emd_samples_1_not_normalized(): def test_emd_samples_1_custom_distance(): - dist = lambda x: np.array([[0.0 if i == j else 1.0 for i in x] for j in x]) + def dist(x): + return np.array([[0.0 if i == j else 1.0 for i in x] for j in x]) first_array = [1, 2, 3, 4] second_array = [2, 3, 4, 5] emd_assert(emd_samples(first_array, second_array, distance=dist), 0.25) @@ -318,9 +319,8 @@ def test_emd_samples_1_custom_distance(): def test_emd_samples_all_kwargs(): # Regression only; not checked by hand - dist = lambda x: [ - [(i - j)**3 for i in range(len(x))] for j in range(len(x)) - ] + def dist(x): + return [[(i - j)**3 for i in range(len(x))] for j in range(len(x))] first_array = [1, 2, 3, 4, 5] second_array = [2, 3, 4, 5] emd_assert( @@ -368,7 +368,8 @@ def test_emd_samples_validate_empty(): def test_emd_samples_validate_distance_matrix_square(): - dist = lambda x: [[1, 2, 3]] + def dist(x): + return [[1, 2, 3]] first_array = [1, 2, 3] second_array = [1, 2, 3] with pytest.raises(ValueError): @@ -376,8 +377,8 @@ def test_emd_samples_validate_distance_matrix_square(): def test_emd_samples_validate_distance_matrix_size(): - dist = lambda x: [[0, 1], - [1, 0]] + def dist(x): + return [[0, 1], [1, 0]] first_array = [1, 2, 3, 4] second_array = [1, 2, 3, 4] with pytest.raises(ValueError): From 802fa86401e369a15ce53359a54037de2c771696 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sun, 22 Jan 2023 23:55:12 -0600 Subject: [PATCH 33/61] setup.py: Add missing comma Add a missing comma in NUMPY_REQUIREMENT that was causing errors installing when numpy is not already available. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b000d9..d6dd053 100644 --- a/setup.py +++ b/setup.py @@ -110,7 +110,7 @@ def finalize_options(self): NUMPY_REQUIREMENT = [ - "numpy >=1.9.0, <1.20.0; python_version<='3.6'" + "numpy >=1.9.0, <1.20.0; python_version<='3.6'", "numpy >=1.9.0, <2.0.0; python_version>'3.6'" ] From 27e1e986c844121964c634fdc49f52a5e310a504 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 24 Jan 2023 17:10:12 -0600 Subject: [PATCH 34/61] gitignore: Add `ignore` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4731ce8..710f5b5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ MANIFEST build dist pyemd/emd.cpp +ignore From 1fc1c53b1509009bff6a13027862e292d6436135 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 24 Jan 2023 17:36:49 -0600 Subject: [PATCH 35/61] setup.py: Format code --- setup.py | 83 +++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/setup.py b/setup.py index d6dd053..0fa517d 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def is_platform_mac(): - return sys.platform == 'darwin' + return sys.platform == "darwin" # Alias ModuleNotFound for Python <= 3.5 @@ -27,15 +27,15 @@ def is_platform_mac(): # the version that Python was built for. This may be overridden by setting # MACOSX_DEPLOYMENT_TARGET before calling setup.py if is_platform_mac(): - if 'MACOSX_DEPLOYMENT_TARGET' not in os.environ: + if "MACOSX_DEPLOYMENT_TARGET" not in os.environ: current_system = LooseVersion(platform.mac_ver()[0]) - python_target = LooseVersion( - get_config_var('MACOSX_DEPLOYMENT_TARGET')) - if python_target < '10.9' and current_system >= '10.9': - os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.9' + python_target = LooseVersion(get_config_var("MACOSX_DEPLOYMENT_TARGET")) + if python_target < "10.9" and current_system >= "10.9": + os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.9" try: from Cython.Build import cythonize as _cythonize + USE_CYTHON = True except (ImportError, ModuleNotFoundError): USE_CYTHON = False @@ -50,22 +50,18 @@ def cythonize(extensions, **_ignore): sources = [] for sfile in extension.sources: path, ext = os.path.splitext(sfile) - if ext in ('.pyx', '.py'): - if extension.language == 'c++': - ext = '.cpp' + if ext in (".pyx", ".py"): + if extension.language == "c++": + ext = ".cpp" else: - ext = '.c' + ext = ".c" sfile = path + ext sources.append(sfile) extension.sources[:] = sources return extensions -EXTENSIONS = [ - Extension('pyemd.emd', - sources=['pyemd/emd.pyx'], - language="c++") -] +EXTENSIONS = [Extension("pyemd.emd", sources=["pyemd/emd.pyx"], language="c++")] EXT_MODULES = cythonize(EXTENSIONS) @@ -76,11 +72,13 @@ def run(self): if USE_CYTHON: _cythonize(EXTENSIONS) else: - warn('\n\n\033[91m\033[1m WARNING: ' - 'IF YOU A PREPARING A DISTRIBUTION: Cython is not available! ' - 'The cythonized `*.cpp` files may be out of date. Please ' - 'install Cython and run `sdist` again.' - '\033[0m\n') + warn( + "\n\n\033[91m\033[1m WARNING: " + "IF YOU A PREPARING A DISTRIBUTION: Cython is not available! " + "The cythonized `*.cpp` files may be out of date. Please " + "install Cython and run `sdist` again." + "\033[0m\n" + ) _sdist.run(self) @@ -89,29 +87,27 @@ class build_ext(_build_ext): def finalize_options(self): _build_ext.finalize_options(self) # Prevent numpy from thinking it is still in its setup process: - if hasattr(__builtins__, '__NUMPY_SETUP__'): + if hasattr(__builtins__, "__NUMPY_SETUP__"): __builtins__.__NUMPY_SETUP__ = False import numpy + self.include_dirs.append(numpy.get_include()) -CMDCLASS = { - 'sdist': sdist, - 'build_ext': build_ext -} +CMDCLASS = {"sdist": sdist, "build_ext": build_ext} -with io.open('README.rst', encoding='utf-8') as f: +with io.open("README.rst", encoding="utf-8") as f: README = f.read() ABOUT = {} -with open('./pyemd/__about__.py') as f: +with open("./pyemd/__about__.py") as f: exec(f.read(), ABOUT) NUMPY_REQUIREMENT = [ "numpy >=1.9.0, <1.20.0; python_version<='3.6'", - "numpy >=1.9.0, <2.0.0; python_version>'3.6'" + "numpy >=1.9.0, <2.0.0; python_version>'3.6'", ] # Copied from scipy's installer, to solve the same issues they saw: @@ -128,28 +124,29 @@ def finalize_options(self): # If we're building a wheel, assume there already exist numpy wheels # for this platform, so it is safe to add numpy to build requirements. # See scipy gh-5184. - REQUIRES = (NUMPY_REQUIREMENT if 'bdist_wheel' in sys.argv[1:] else []) + REQUIRES = NUMPY_REQUIREMENT if "bdist_wheel" in sys.argv[1:] else [] setup( - name=ABOUT['__title__'], - version=ABOUT['__version__'], - description=ABOUT['__description__'], + name=ABOUT["__title__"], + version=ABOUT["__version__"], + description=ABOUT["__description__"], long_description=README, - author=ABOUT['__author__'], - author_email=ABOUT['__author_email__'], - url=ABOUT['__url__'], - license=ABOUT['__license__'], - packages=['pyemd'], + long_description_content_type="text/x-rst", + author=ABOUT["__author__"], + author_email=ABOUT["__author_email__"], + url=ABOUT["__url__"], + license=ABOUT["__license__"], + packages=["pyemd"], install_requires=REQUIRES, cmdclass=CMDCLASS, setup_requires=REQUIRES, ext_modules=EXT_MODULES, classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", ], ) From 9fa4993ce1fe9e93ac5b52e5b2ed5b400641660d Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 24 Jan 2023 17:37:06 -0600 Subject: [PATCH 36/61] Makefile: use `twine check` --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cf0938f..783adb6 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ sign-dist: check-dist gpg --detach-sign -a dist/*.whl check-dist: build-dist - python setup.py check --restructuredtext --strict + twine check --strict dist/* build-dist: clean-dist python setup.py sdist bdist_wheel --dist-dir=$(dist_dir) From 12862c31d22141f4e5a1074603f49d4238423db1 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Wed, 25 Jan 2023 15:38:46 -0600 Subject: [PATCH 37/61] WIP --- LICENSE | 2 +- Makefile | 11 ++++++++--- pyproject.toml | 24 ++++++++++++++++++++++++ setup.py | 31 ++++++++++--------------------- 4 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 pyproject.toml diff --git a/LICENSE b/LICENSE index 797a2c5..e3fb122 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2017 Will Mayner +Copyright (c) 2014-2023 Will Mayner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 783adb6..80d8457 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,14 @@ test: build py.test build: clean - python setup.py build_ext -b . + python -m pip install -e . + # setup.py build_ext -b . clean: rm -f pyemd/*.so + rm -rf **/__pycache__ + rm -rf build + rm -rf pyemd.egg-info upload-dist: sign-dist twine upload $(dist_dir)/* @@ -28,7 +32,8 @@ check-dist: build-dist twine check --strict dist/* build-dist: clean-dist - python setup.py sdist bdist_wheel --dist-dir=$(dist_dir) + python -m build + # python -m setup.py sdist bdist_wheel --dist-dir=$(dist_dir) clean-dist: - rm -rf $(dist_dir) + rm -r $(dist_dir) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..44c8c55 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=61.0", "cython", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyemd" +version = "0.5.1" +authors = [{ name = "Will Mayner", email = "wmayner@gmail.com" }] +description = "A Python wrapper for Ofir Pele and Michael Werman's implementation of the Earth Mover's Distance." +readme = "README.md" +license = { file = "LICENSE" } +classifiers = [ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', +] +dynamic = ['dependencies'] + +[project.urls] +"Homepage" = "https://github.com/wmayner/pyemd" +"Bug Tracker" = "https://github.com/wmayner/pyemd/issues" diff --git a/setup.py b/setup.py index 0fa517d..138b6b6 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,8 @@ def is_platform_mac(): # the version that Python was built for. This may be overridden by setting # MACOSX_DEPLOYMENT_TARGET before calling setup.py if is_platform_mac(): + import packaging + if "MACOSX_DEPLOYMENT_TARGET" not in os.environ: current_system = LooseVersion(platform.mac_ver()[0]) python_target = LooseVersion(get_config_var("MACOSX_DEPLOYMENT_TARGET")) @@ -105,6 +107,8 @@ def finalize_options(self): exec(f.read(), ABOUT) +REQUIRES = ["packaging"] + NUMPY_REQUIREMENT = [ "numpy >=1.9.0, <1.20.0; python_version<='3.6'", "numpy >=1.9.0, <2.0.0; python_version>'3.6'", @@ -119,34 +123,19 @@ def finalize_options(self): try: import numpy except ImportError: # We do not have numpy installed - REQUIRES = NUMPY_REQUIREMENT + REQUIRES += NUMPY_REQUIREMENT else: # If we're building a wheel, assume there already exist numpy wheels # for this platform, so it is safe to add numpy to build requirements. # See scipy gh-5184. - REQUIRES = NUMPY_REQUIREMENT if "bdist_wheel" in sys.argv[1:] else [] + if "bdist_wheel" in sys.argv[1:]: + REQUIRES += NUMPY_REQUIREMENT + setup( - name=ABOUT["__title__"], - version=ABOUT["__version__"], - description=ABOUT["__description__"], - long_description=README, - long_description_content_type="text/x-rst", - author=ABOUT["__author__"], - author_email=ABOUT["__author_email__"], - url=ABOUT["__url__"], - license=ABOUT["__license__"], - packages=["pyemd"], + packages=["pyemd", "pyemd.lib"], install_requires=REQUIRES, - cmdclass=CMDCLASS, setup_requires=REQUIRES, ext_modules=EXT_MODULES, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Natural Language :: English", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 3", - ], + cmdclass=CMDCLASS, ) From 3e85f78642d176cffac1934240f8275b7ecca340 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 31 Jan 2023 15:53:54 -0600 Subject: [PATCH 38/61] Makefile: make test commands more specific --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 783adb6..309a7c3 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ dist_dir = dist default: build test: build - py.test + py.test pyemd + py.test test build: clean python setup.py build_ext -b . From c35a511c845c178938817108db1013624f1ea587 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:05:31 -0600 Subject: [PATCH 39/61] Modern packaging Restructure the package to use modern Python packaging methods. - Drop support for Python < 3.7 - Use pyproject.toml - Use cibuildwheel to build wheels for many linux distros - Introduce automation with GitHub actions for builds - Use setuptools_scm to automate versioning - Assume Cython is available when preparing a source distribution --- .github/workflows/build_wheels.yml | 25 +++ .gitignore | 5 +- MANIFEST.in | 2 +- Makefile | 59 +++--- conftest.py | 6 - dev_requirements.txt | 1 - dist_requirements.txt | 4 - pyemd/__about__.py | 20 -- pyproject.toml | 35 +++- pytest.ini | 6 +- requirements.txt | 4 - setup.py | 104 +++------- {pyemd => src/pyemd}/__init__.py | 6 +- {pyemd => src/pyemd}/emd.pyx | 0 {pyemd => src/pyemd}/lib/EMD_DEFS.hpp | 0 {pyemd => src/pyemd}/lib/emd_hat.hpp | 0 {pyemd => src/pyemd}/lib/emd_hat_impl.hpp | 0 .../lib/emd_hat_signatures_interface.hpp | 0 {pyemd => src/pyemd}/lib/flow_utils.hpp | 0 {pyemd => src/pyemd}/lib/min_cost_flow.hpp | 0 test/test_pyemd.py | 192 +++++++++--------- test_requirements.txt | 2 - 22 files changed, 226 insertions(+), 245 deletions(-) create mode 100644 .github/workflows/build_wheels.yml delete mode 100644 conftest.py delete mode 100644 dev_requirements.txt delete mode 100644 dist_requirements.txt delete mode 100644 pyemd/__about__.py delete mode 100644 requirements.txt rename {pyemd => src/pyemd}/__init__.py (95%) rename {pyemd => src/pyemd}/emd.pyx (100%) rename {pyemd => src/pyemd}/lib/EMD_DEFS.hpp (100%) rename {pyemd => src/pyemd}/lib/emd_hat.hpp (100%) rename {pyemd => src/pyemd}/lib/emd_hat_impl.hpp (100%) rename {pyemd => src/pyemd}/lib/emd_hat_signatures_interface.hpp (100%) rename {pyemd => src/pyemd}/lib/flow_utils.hpp (100%) rename {pyemd => src/pyemd}/lib/min_cost_flow.hpp (100%) delete mode 100644 test_requirements.txt diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000..668973d --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,25 @@ +name: Build + +on: [push, pull_request] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, windows-2019, macos-11] + + steps: + - uses: actions/checkout@v3 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.12.0 + with: + package-dir: . + output-dir: wheelhouse + config-file: "{package}/pyproject.toml" + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl diff --git a/.gitignore b/.gitignore index 710f5b5..7fa6859 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ __pycache__ .gitconfig .cache +.pytest_cache .tox .env .ropeproject @@ -8,7 +9,9 @@ __pycache__ *.pyc MANIFEST *.egg* +src/pyemd/emd.cpp +src/pyemd/_version.py build dist -pyemd/emd.cpp +wheelhouse ignore diff --git a/MANIFEST.in b/MANIFEST.in index 966751d..09593c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -graft pyemd +graft src/pyemd graft test include README.rst diff --git a/Makefile b/Makefile index 80d8457..cbcb750 100644 --- a/Makefile +++ b/Makefile @@ -1,39 +1,44 @@ -.PHONY: default test build clean upload-dist test-dist sign-dist check-dist build-dist clean-dist +.PHONY: default clean develop test dist-clean build-local build dist-upload dist-test-upload dist-sign dist-check -src = pyemd -dist_dir = dist +src = src/pyemd +test = test +dist = wheelhouse +readme = README.rst -default: build +default: develop -test: build - py.test +clean: + rm -rf $(shell find . -name '__pycache__') + rm -rf $(shell find . -name '*.so') + rm -rf .eggs + rm -rf pyemd.egg-info + rm -rf dist + rm -rf build -build: clean +develop: clean python -m pip install -e . - # setup.py build_ext -b . -clean: - rm -f pyemd/*.so - rm -rf **/__pycache__ - rm -rf build - rm -rf pyemd.egg-info +test: develop + py.test -upload-dist: sign-dist - twine upload $(dist_dir)/* +build-local: clean + python -m build -test-dist: check-dist - twine upload --repository testpypi $(dist_dir)/* +dist-clean: + rm -rf $(dist) -sign-dist: check-dist - gpg --detach-sign -a dist/*.tar.gz - gpg --detach-sign -a dist/*.whl +build: dist-clean + cibuildwheel --platform linux --config-file pyproject.toml --output-dir $(dist) -check-dist: build-dist - twine check --strict dist/* +dist-upload: dist-sign + twine upload $(dist)/* -build-dist: clean-dist - python -m build - # python -m setup.py sdist bdist_wheel --dist-dir=$(dist_dir) +dist-test-upload: dist-check + twine upload --repository testpypi $(dist)/* + +dist-sign: dist-check + gpg --detach-sign -a $(dist)/*.tar.gz + gpg --detach-sign -a $(dist)/*.whl -clean-dist: - rm -r $(dist_dir) +dist-check: dist-build + twine check --strict $(dist)/* diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 24326d9..0000000 --- a/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# conftest.py - - -collect_ignore = ["setup.py", "build", "dist"] diff --git a/dev_requirements.txt b/dev_requirements.txt deleted file mode 100644 index 142a744..0000000 --- a/dev_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Cython >=0.20.2 diff --git a/dist_requirements.txt b/dist_requirements.txt deleted file mode 100644 index 8409743..0000000 --- a/dist_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -docutils -pygments -twine -wheel diff --git a/pyemd/__about__.py b/pyemd/__about__.py deleted file mode 100644 index 48408e8..0000000 --- a/pyemd/__about__.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# __about__.py - -"""PyEMD metadata""" - -__title__ = 'pyemd' -__version__ = '0.5.1' -__description__ = ("A Python wrapper for Ofir Pele and Michael Werman's " - "implementation of the Earth Mover's Distance.") -__author__ = 'Will Mayner' -__author_email__ = 'wmayner@gmail.com' -__author_website__ = 'http://willmayner.com' -__license__ = 'MIT' -__copyright__ = 'Copyright (c) 2014-2017 Will Mayner' -__url__ = 'https://github.com/wmayner/pyemd' - -__all__ = ['__title__', '__version__', '__description__', '__author__', - '__author_email__', '__author_website__', '__license__', - '__copyright__', '__url__'] diff --git a/pyproject.toml b/pyproject.toml index 44c8c55..cff1c25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,43 @@ [build-system] -requires = ["setuptools>=61.0", "cython", "wheel"] +requires = [ + "cython", + "oldest-supported-numpy", + "setuptools >= 45", + "setuptools_scm", + "wheel", +] build-backend = "setuptools.build_meta" [project] name = "pyemd" -version = "0.5.1" -authors = [{ name = "Will Mayner", email = "wmayner@gmail.com" }] -description = "A Python wrapper for Ofir Pele and Michael Werman's implementation of the Earth Mover's Distance." -readme = "README.md" license = { file = "LICENSE" } +description = "A Python wrapper for Ofir Pele and Michael Werman's implementation of the Earth Mover's Distance." +authors = [{ name = "Will Mayner", email = "wmayner@gmail.com" }] +requires-python = ">=3.7" +dependencies = ["numpy >= 1.9.0"] +readme = "README.rst" classifiers = [ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', ] -dynamic = ['dependencies'] +dynamic = ['version'] + +[project.optional-dependencies] +test = ['pytest'] +dist = ['cibuildwheel', 'setuptools_scm'] [project.urls] "Homepage" = "https://github.com/wmayner/pyemd" "Bug Tracker" = "https://github.com/wmayner/pyemd/issues" + +[tool.setuptools_scm] +write_to = "src/pyemd/_version.py" + +[tool.cibuildwheel] +skip = "cp36*" +build-verbosity = 2 +test-requires = ["pytest"] +test-command = "py.test {project}" diff --git a/pytest.ini b/pytest.ini index 1a744be..0254159 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,8 +4,12 @@ addopts = --tb=auto --doctest-glob='*.rst' --doctest-modules -vv + --ignore setup.py norecursedirs = + src dist build + wheelhouse + ignore + .git .tox - .eggs diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e0a2d12..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ --e . --r test_requirements.txt --r dev_requirements.txt --r dist_requirements.txt diff --git a/setup.py b/setup.py index 138b6b6..3fe6fd4 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,34 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import io import os import platform import sys -from warnings import warn +from distutils.sysconfig import get_config_var from setuptools import Extension, setup from setuptools.command.build_ext import build_ext as _build_ext -from setuptools.command.sdist import sdist as _sdist -from distutils.sysconfig import get_config_var -from distutils.version import LooseVersion + +from packaging.version import Version, parse as parse_version def is_platform_mac(): return sys.platform == "darwin" -# Alias ModuleNotFound for Python <= 3.5 -if sys.version_info < (3, 6): - ModuleNotFoundError = ImportError - # For macOS, ensure extensions are built for macOS 10.9 when compiling on a # 10.9 system or above, overriding distutils behaviour which is to target # the version that Python was built for. This may be overridden by setting # MACOSX_DEPLOYMENT_TARGET before calling setup.py -if is_platform_mac(): - import packaging - - if "MACOSX_DEPLOYMENT_TARGET" not in os.environ: - current_system = LooseVersion(platform.mac_ver()[0]) - python_target = LooseVersion(get_config_var("MACOSX_DEPLOYMENT_TARGET")) - if python_target < "10.9" and current_system >= "10.9": - os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.9" +if is_platform_mac() and "MACOSX_DEPLOYMENT_TARGET" not in os.environ: + current_system = parse_version(platform.mac_ver()[0]) + python_target = parse_version(get_config_var("MACOSX_DEPLOYMENT_TARGET")) + mac_deployment_target = Version("10.9") + if ( + python_target < mac_deployment_target + and current_system >= mac_deployment_target + ): + os.environ["MACOSX_DEPLOYMENT_TARGET"] = str(mac_deployment_target) try: from Cython.Build import cythonize as _cythonize @@ -43,10 +38,10 @@ def is_platform_mac(): USE_CYTHON = False -def cythonize(extensions, **_ignore): +def cythonize(extensions, **kwargs): # Attempt to use Cython if USE_CYTHON: - return _cythonize(extensions) + return _cythonize(extensions, **kwargs) # Cython is not available for extension in extensions: sources = [] @@ -63,25 +58,15 @@ def cythonize(extensions, **_ignore): return extensions -EXTENSIONS = [Extension("pyemd.emd", sources=["pyemd/emd.pyx"], language="c++")] - -EXT_MODULES = cythonize(EXTENSIONS) - +EXTENSIONS = [ + Extension( + "pyemd.emd", + sources=["src/pyemd/emd.pyx"], + language="c++", + ) +] -class sdist(_sdist): - def run(self): - # Ensure the compiled Cython files in the distribution are up-to-date - if USE_CYTHON: - _cythonize(EXTENSIONS) - else: - warn( - "\n\n\033[91m\033[1m WARNING: " - "IF YOU A PREPARING A DISTRIBUTION: Cython is not available! " - "The cythonized `*.cpp` files may be out of date. Please " - "install Cython and run `sdist` again." - "\033[0m\n" - ) - _sdist.run(self) +EXT_MODULES = cythonize(EXTENSIONS, language_level=3) # See https://stackoverflow.com/a/21621689/1085344 @@ -96,46 +81,21 @@ def finalize_options(self): self.include_dirs.append(numpy.get_include()) -CMDCLASS = {"sdist": sdist, "build_ext": build_ext} - +CMDCLASS = {"build_ext": build_ext} -with io.open("README.rst", encoding="utf-8") as f: - README = f.read() - -ABOUT = {} -with open("./pyemd/__about__.py") as f: - exec(f.read(), ABOUT) - - -REQUIRES = ["packaging"] - -NUMPY_REQUIREMENT = [ - "numpy >=1.9.0, <1.20.0; python_version<='3.6'", - "numpy >=1.9.0, <2.0.0; python_version>'3.6'", -] - -# Copied from scipy's installer, to solve the same issues they saw: - -# Figure out whether to add ``*_requires = ['numpy']``. -# We don't want to do that unconditionally, because we risk updating -# an installed numpy which fails too often. Just if it's not installed, we -# may give it a try. See scipy gh-3379. -try: - import numpy -except ImportError: # We do not have numpy installed - REQUIRES += NUMPY_REQUIREMENT -else: - # If we're building a wheel, assume there already exist numpy wheels - # for this platform, so it is safe to add numpy to build requirements. - # See scipy gh-5184. - if "bdist_wheel" in sys.argv[1:]: - REQUIRES += NUMPY_REQUIREMENT +SETUP_REQUIRES = ["setuptools_scm", "packaging"] setup( + name="pyemd", packages=["pyemd", "pyemd.lib"], - install_requires=REQUIRES, - setup_requires=REQUIRES, + package_dir={ + "pyemd": "src/pyemd", + "pyemd.lib": "src/pyemd/lib", + }, + include_package_data=True, ext_modules=EXT_MODULES, cmdclass=CMDCLASS, + setup_requires=SETUP_REQUIRES, + use_scm_version=True, ) diff --git a/pyemd/__init__.py b/src/pyemd/__init__.py similarity index 95% rename from pyemd/__init__.py rename to src/pyemd/__init__.py index b0245c0..713ecb0 100644 --- a/pyemd/__init__.py +++ b/src/pyemd/__init__.py @@ -71,5 +71,9 @@ :license: See the LICENSE file. """ -from .__about__ import * from .emd import emd, emd_with_flow, emd_samples + +try: + from ._version import version as __version__ +except ImportError: + __version__ = "unknown version" diff --git a/pyemd/emd.pyx b/src/pyemd/emd.pyx similarity index 100% rename from pyemd/emd.pyx rename to src/pyemd/emd.pyx diff --git a/pyemd/lib/EMD_DEFS.hpp b/src/pyemd/lib/EMD_DEFS.hpp similarity index 100% rename from pyemd/lib/EMD_DEFS.hpp rename to src/pyemd/lib/EMD_DEFS.hpp diff --git a/pyemd/lib/emd_hat.hpp b/src/pyemd/lib/emd_hat.hpp similarity index 100% rename from pyemd/lib/emd_hat.hpp rename to src/pyemd/lib/emd_hat.hpp diff --git a/pyemd/lib/emd_hat_impl.hpp b/src/pyemd/lib/emd_hat_impl.hpp similarity index 100% rename from pyemd/lib/emd_hat_impl.hpp rename to src/pyemd/lib/emd_hat_impl.hpp diff --git a/pyemd/lib/emd_hat_signatures_interface.hpp b/src/pyemd/lib/emd_hat_signatures_interface.hpp similarity index 100% rename from pyemd/lib/emd_hat_signatures_interface.hpp rename to src/pyemd/lib/emd_hat_signatures_interface.hpp diff --git a/pyemd/lib/flow_utils.hpp b/src/pyemd/lib/flow_utils.hpp similarity index 100% rename from pyemd/lib/flow_utils.hpp rename to src/pyemd/lib/flow_utils.hpp diff --git a/pyemd/lib/min_cost_flow.hpp b/src/pyemd/lib/min_cost_flow.hpp similarity index 100% rename from pyemd/lib/min_cost_flow.hpp rename to src/pyemd/lib/min_cost_flow.hpp diff --git a/test/test_pyemd.py b/test/test_pyemd.py index 5c784d6..b3f211b 100644 --- a/test/test_pyemd.py +++ b/test/test_pyemd.py @@ -8,6 +8,7 @@ from pyemd import emd, emd_samples, emd_with_flow + EMD_PRECISION = 5 FLOW_PRECISION = 4 @@ -30,60 +31,52 @@ def emd_flow_assert(got, expected): def test_emd_1(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) - emd_assert( - emd(first_signature, second_signature, distance_matrix), - 3.5 - ) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) + emd_assert(emd(first_signature, second_signature, distance_matrix), 3.5) def test_emd_2(): first_signature = np.array([1.0, 1.0]) second_signature = np.array([1.0, 1.0]) - distance_matrix = np.array([[0.0, 1.0], - [1.0, 0.0]]) - emd_assert( - emd(first_signature, second_signature, distance_matrix), - 0.0 - ) + distance_matrix = np.array([[0.0, 1.0], [1.0, 0.0]]) + emd_assert(emd(first_signature, second_signature, distance_matrix), 0.0) def test_emd_3(): first_signature = np.array([6.0, 1.0]) second_signature = np.array([1.0, 7.0]) - distance_matrix = np.array([[0.0, 0.0], - [0.0, 0.0]]) - emd_assert( - emd(first_signature, second_signature, distance_matrix), - 0.0 - ) + distance_matrix = np.array([[0.0, 0.0], [0.0, 0.0]]) + emd_assert(emd(first_signature, second_signature, distance_matrix), 0.0) def test_emd_4(): first_signature = np.array([1.0, 2.0, 1.0, 2.0]) second_signature = np.array([2.0, 1.0, 2.0, 1.0]) - distance_matrix = np.array([[0.0, 1.0, 1.0, 2.0], - [1.0, 0.0, 2.0, 1.0], - [1.0, 2.0, 0.0, 1.0], - [2.0, 1.0, 1.0, 0.0]]) - emd_assert( - emd(first_signature, second_signature, distance_matrix), - 2.0 + distance_matrix = np.array( + [ + [0.0, 1.0, 1.0, 2.0], + [1.0, 0.0, 2.0, 1.0], + [1.0, 2.0, 0.0, 1.0], + [2.0, 1.0, 1.0, 0.0], + ] ) + emd_assert(emd(first_signature, second_signature, distance_matrix), 2.0) def test_emd_extra_mass_penalty(): first_signature = np.array([0.0, 2.0, 1.0, 2.0]) second_signature = np.array([2.0, 1.0, 2.0, 1.0]) - distance_matrix = np.array([[0.0, 1.0, 1.0, 2.0], - [1.0, 0.0, 2.0, 1.0], - [1.0, 2.0, 0.0, 1.0], - [2.0, 1.0, 1.0, 0.0]]) + distance_matrix = np.array( + [ + [0.0, 1.0, 1.0, 2.0], + [1.0, 0.0, 2.0, 1.0], + [1.0, 2.0, 0.0, 1.0], + [2.0, 1.0, 1.0, 0.0], + ] + ) emd_assert( - emd(first_signature, second_signature, distance_matrix, - extra_mass_penalty=2.5), - 4.5 + emd(first_signature, second_signature, distance_matrix, extra_mass_penalty=2.5), + 4.5, ) @@ -93,8 +86,7 @@ def test_emd_extra_mass_penalty(): def test_emd_validate_larger_signatures_1(): first_signature = np.array([0.0, 1.0, 2.0]) second_signature = np.array([5.0, 3.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) with pytest.raises(ValueError): emd(first_signature, second_signature, distance_matrix) @@ -102,8 +94,7 @@ def test_emd_validate_larger_signatures_1(): def test_emd_validate_larger_signatures_2(): first_signature = np.array([0.0, 1.0, 2.0]) second_signature = np.array([5.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) with pytest.raises(ValueError): emd_with_flow(first_signature, second_signature, distance_matrix) @@ -119,9 +110,7 @@ def test_emd_validate_larger_signatures_3(): def test_emd_validate_different_signature_dims(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5, 0.0], - [0.5, 0.0, 0.0], - [0.5, 0.0, 0.0]]) + distance_matrix = np.array([[0.0, 0.5, 0.0], [0.5, 0.0, 0.0], [0.5, 0.0, 0.0]]) with pytest.raises(ValueError): emd(first_signature, second_signature, distance_matrix) @@ -129,8 +118,7 @@ def test_emd_validate_different_signature_dims(): def test_emd_validate_symmetric_distance_matrix(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5, 3.0], - [0.5, 0.0]], dtype=object) + distance_matrix = np.array([[0.0, 0.5, 3.0], [0.5, 0.0]], dtype=object) with pytest.raises(ValueError): emd(first_signature, second_signature, distance_matrix) @@ -142,93 +130,102 @@ def test_emd_validate_symmetric_distance_matrix(): def test_emd_with_flow_1(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) emd_flow_assert( emd_with_flow(first_signature, second_signature, distance_matrix), - (3.5, [[0.0, 0.0], - [0.0, 1.0]]) + (3.5, [[0.0, 0.0], [0.0, 1.0]]), ) def test_emd_with_flow_2(): first_signature = np.array([1.0, 1.0]) second_signature = np.array([1.0, 1.0]) - distance_matrix = np.array([[0.0, 1.0], - [1.0, 0.0]]) + distance_matrix = np.array([[0.0, 1.0], [1.0, 0.0]]) emd_flow_assert( emd_with_flow(first_signature, second_signature, distance_matrix), - (0.0, [[1.0, 0.0], - [0.0, 1.0]]) + (0.0, [[1.0, 0.0], [0.0, 1.0]]), ) def test_emd_with_flow_3(): first_signature = np.array([6.0, 1.0]) second_signature = np.array([1.0, 7.0]) - distance_matrix = np.array([[0.0, 0.0], - [0.0, 0.0]]) + distance_matrix = np.array([[0.0, 0.0], [0.0, 0.0]]) emd_flow_assert( emd_with_flow(first_signature, second_signature, distance_matrix), - (0.0, [[1.0, 5.0], - [0.0, 1.0]]) + (0.0, [[1.0, 5.0], [0.0, 1.0]]), ) def test_emd_with_flow_4(): first_signature = np.array([1.0, 7.0]) second_signature = np.array([6.0, 1.0]) - distance_matrix = np.array([[0.0, 0.0], - [0.0, 0.0]]) + distance_matrix = np.array([[0.0, 0.0], [0.0, 0.0]]) emd_flow_assert( emd_with_flow(first_signature, second_signature, distance_matrix), - (0.0, [[1.0, 0.0], - [5.0, 1.0]]) + (0.0, [[1.0, 0.0], [5.0, 1.0]]), ) def test_emd_with_flow_5(): first_signature = np.array([3.0, 5.0]) second_signature = np.array([6.0, 2.0]) - distance_matrix = np.array([[0.0, 0.0], - [0.0, 0.0]]) + distance_matrix = np.array([[0.0, 0.0], [0.0, 0.0]]) emd_flow_assert( emd_with_flow(first_signature, second_signature, distance_matrix), - (0.0, [[3.0, 0.0], - [3.0, 2.0]]) + (0.0, [[3.0, 0.0], [3.0, 2.0]]), ) def test_emd_with_flow_6(): first_signature = np.array([1.0, 2.0, 1.0, 2.0]) second_signature = np.array([2.0, 1.0, 2.0, 1.0]) - distance_matrix = np.array([[0.0, 1.0, 1.0, 2.0], - [1.0, 0.0, 2.0, 1.0], - [1.0, 2.0, 0.0, 1.0], - [2.0, 1.0, 1.0, 0.0]]) + distance_matrix = np.array( + [ + [0.0, 1.0, 1.0, 2.0], + [1.0, 0.0, 2.0, 1.0], + [1.0, 2.0, 0.0, 1.0], + [2.0, 1.0, 1.0, 0.0], + ] + ) emd_flow_assert( emd_with_flow(first_signature, second_signature, distance_matrix), - (2.0, [[1.0, 0.0, 0.0, 0.0], - [1.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 1.0, 1.0]]) + ( + 2.0, + [ + [1.0, 0.0, 0.0, 0.0], + [1.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 1.0, 1.0], + ], + ), ) def test_emd_with_flow_extra_mass_penalty(): first_signature = np.array([0.0, 2.0, 1.0, 2.0]) second_signature = np.array([2.0, 1.0, 2.0, 1.0]) - distance_matrix = np.array([[0.0, 1.0, 1.0, 2.0], - [1.0, 0.0, 2.0, 1.0], - [1.0, 2.0, 0.0, 1.0], - [2.0, 1.0, 1.0, 0.0]]) + distance_matrix = np.array( + [ + [0.0, 1.0, 1.0, 2.0], + [1.0, 0.0, 2.0, 1.0], + [1.0, 2.0, 0.0, 1.0], + [2.0, 1.0, 1.0, 0.0], + ] + ) emd_flow_assert( - emd_with_flow(first_signature, second_signature, distance_matrix, - extra_mass_penalty=2.5), - (4.5, [[0.0, 0.0, 0.0, 0.0], - [1.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 1.0, 1.0]]) + emd_with_flow( + first_signature, second_signature, distance_matrix, extra_mass_penalty=2.5 + ), + ( + 4.5, + [ + [0.0, 0.0, 0.0, 0.0], + [1.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 1.0, 1.0], + ], + ), ) @@ -238,8 +235,7 @@ def test_emd_with_flow_extra_mass_penalty(): def test_emd_with_flow_validate_larger_signatures_1(): first_signature = np.array([0.0, 1.0, 2.0]) second_signature = np.array([5.0, 3.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) with pytest.raises(ValueError): emd_with_flow(first_signature, second_signature, distance_matrix) @@ -247,8 +243,7 @@ def test_emd_with_flow_validate_larger_signatures_1(): def test_emd_with_flow_validate_larger_signatures_2(): first_signature = np.array([0.0, 1.0, 2.0]) second_signature = np.array([5.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) with pytest.raises(ValueError): emd(first_signature, second_signature, distance_matrix) @@ -256,8 +251,7 @@ def test_emd_with_flow_validate_larger_signatures_2(): def test_emd_with_flow_validate_larger_signatures_3(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5], - [0.5, 0.0]]) + distance_matrix = np.array([[0.0, 0.5], [0.5, 0.0]]) with pytest.raises(ValueError): emd_with_flow(first_signature, second_signature, distance_matrix) @@ -265,9 +259,7 @@ def test_emd_with_flow_validate_larger_signatures_3(): def test_emd_with_flow_validate_different_signature_dims(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5, 0.0], - [0.5, 0.0, 0.0], - [0.5, 0.0, 0.0]]) + distance_matrix = np.array([[0.0, 0.5, 0.0], [0.5, 0.0, 0.0], [0.5, 0.0, 0.0]]) with pytest.raises(ValueError): emd_with_flow(first_signature, second_signature, distance_matrix) @@ -275,8 +267,7 @@ def test_emd_with_flow_validate_different_signature_dims(): def test_emd_with_flow_validate_square_distance_matrix(): first_signature = np.array([0.0, 1.0]) second_signature = np.array([5.0, 3.0]) - distance_matrix = np.array([[0.0, 0.5, 3.0], - [0.5, 0.0]], dtype=object) + distance_matrix = np.array([[0.0, 0.5, 3.0], [0.5, 0.0]], dtype=object) with pytest.raises(ValueError): emd_with_flow(first_signature, second_signature, distance_matrix) @@ -312,6 +303,7 @@ def test_emd_samples_1_not_normalized(): def test_emd_samples_1_custom_distance(): def dist(x): return np.array([[0.0 if i == j else 1.0 for i in x] for j in x]) + first_array = [1, 2, 3, 4] second_array = [2, 3, 4, 5] emd_assert(emd_samples(first_array, second_array, distance=dist), 0.25) @@ -320,16 +312,20 @@ def dist(x): def test_emd_samples_all_kwargs(): # Regression only; not checked by hand def dist(x): - return [[(i - j)**3 for i in range(len(x))] for j in range(len(x))] + return [[(i - j) ** 3 for i in range(len(x))] for j in range(len(x))] + first_array = [1, 2, 3, 4, 5] second_array = [2, 3, 4, 5] emd_assert( - emd_samples(first_array, second_array, - bins=30, - normalized=False, - range=(-5, 15), - distance=dist), - 24389.0 + emd_samples( + first_array, + second_array, + bins=30, + normalized=False, + range=(-5, 15), + distance=dist, + ), + 24389.0, ) @@ -370,6 +366,7 @@ def test_emd_samples_validate_empty(): def test_emd_samples_validate_distance_matrix_square(): def dist(x): return [[1, 2, 3]] + first_array = [1, 2, 3] second_array = [1, 2, 3] with pytest.raises(ValueError): @@ -379,6 +376,7 @@ def dist(x): def test_emd_samples_validate_distance_matrix_size(): def dist(x): return [[0, 1], [1, 0]] + first_array = [1, 2, 3, 4] second_array = [1, 2, 3, 4] with pytest.raises(ValueError): diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index 5bbae8f..0000000 --- a/test_requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest -tox From 6f0e28a487778392703706b79a87baccfae8dd07 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:31:16 -0600 Subject: [PATCH 40/61] Add `make_sdist` GitHub workflow --- .github/workflows/make_sdist.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/make_sdist.yml diff --git a/.github/workflows/make_sdist.yml b/.github/workflows/make_sdist.yml new file mode 100644 index 0000000..551f174 --- /dev/null +++ b/.github/workflows/make_sdist.yml @@ -0,0 +1,15 @@ +make_sdist: + name: Make SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Optional, use if you use setuptools_scm + submodules: true # Optional, use if you have submodules + + - name: Build SDist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz From c02fa9acf78952e72dd4aa6c67c9f43d64220548 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:39:53 -0600 Subject: [PATCH 41/61] make develop: install optional dependencies --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cbcb750..981742c 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ clean: rm -rf build develop: clean - python -m pip install -e . + python -m pip install -e ".[test,dist]" test: develop py.test From f0f0761b694da931b82aafd0f678eff2b32c6763 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:40:16 -0600 Subject: [PATCH 42/61] make_sdist workflow: Don't use submodules --- .github/workflows/make_sdist.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/make_sdist.yml b/.github/workflows/make_sdist.yml index 551f174..2c19a9a 100644 --- a/.github/workflows/make_sdist.yml +++ b/.github/workflows/make_sdist.yml @@ -4,8 +4,7 @@ make_sdist: steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 # Optional, use if you use setuptools_scm - submodules: true # Optional, use if you have submodules + fetch-depth: 0 - name: Build SDist run: pipx run build --sdist From 69ffec0042159ac70f67bc84d04954163f5e08ee Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:41:46 -0600 Subject: [PATCH 43/61] cibuildwheel: Skip PyPy builds --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cff1c25..2080f24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dist = ['cibuildwheel', 'setuptools_scm'] write_to = "src/pyemd/_version.py" [tool.cibuildwheel] -skip = "cp36*" +skip = ["cp36*", "pp*"] build-verbosity = 2 test-requires = ["pytest"] test-command = "py.test {project}" From b1c4093684e050fd4902334a52ba281fdc20512f Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:50:24 -0600 Subject: [PATCH 44/61] Run workflows on PRs, main, and release branches --- .github/workflows/build_wheels.yml | 8 +++++++- .github/workflows/make_sdist.yml | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 668973d..284116e 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -1,6 +1,12 @@ name: Build +run-name: Build wheels with `cibuildwheel` -on: [push, pull_request] +on: + push: + branches: + - main + - 'release/**' + pull_request: jobs: build_wheels: diff --git a/.github/workflows/make_sdist.yml b/.github/workflows/make_sdist.yml index 2c19a9a..f426085 100644 --- a/.github/workflows/make_sdist.yml +++ b/.github/workflows/make_sdist.yml @@ -1,3 +1,13 @@ +name: Sdist +run-name: Make a source distribution + +on: + push: + branches: + - main + - 'release/**' + pull_request: + make_sdist: name: Make SDist runs-on: ubuntu-latest From b7ca81a67204f7c40854f7b7e4381904d1859b1e Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:53:39 -0600 Subject: [PATCH 45/61] make_sdist: Fix typo --- .github/workflows/make_sdist.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/make_sdist.yml b/.github/workflows/make_sdist.yml index f426085..0ed267b 100644 --- a/.github/workflows/make_sdist.yml +++ b/.github/workflows/make_sdist.yml @@ -1,5 +1,4 @@ -name: Sdist -run-name: Make a source distribution +name: Make source distribution on: push: @@ -8,17 +7,18 @@ on: - 'release/**' pull_request: -make_sdist: - name: Make SDist - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 +jobs: + make_sdist: + name: Make SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 - - name: Build SDist - run: pipx run build --sdist + - name: Build SDist + run: pipx run build --sdist - - uses: actions/upload-artifact@v3 - with: - path: dist/*.tar.gz + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz From 3f809675462ccfbc05a0149b0fb4e7ac3d5ecb95 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 00:53:50 -0600 Subject: [PATCH 46/61] build_wheels: Rename --- .github/workflows/build_wheels.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 284116e..c1d379f 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -1,5 +1,4 @@ -name: Build -run-name: Build wheels with `cibuildwheel` +name: Build wheels on: push: From 3922a5d8be5fbf370e80da7de0b37493954275cc Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 10:57:49 -0600 Subject: [PATCH 47/61] Run builds on every push --- .github/workflows/build_wheels.yml | 9 ++------- .github/workflows/make_sdist.yml | 7 +------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index c1d379f..49bab4b 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -1,11 +1,6 @@ -name: Build wheels +name: Build wheels & run tests -on: - push: - branches: - - main - - 'release/**' - pull_request: +on: ['push', 'pull_request'] jobs: build_wheels: diff --git a/.github/workflows/make_sdist.yml b/.github/workflows/make_sdist.yml index 0ed267b..7f04b30 100644 --- a/.github/workflows/make_sdist.yml +++ b/.github/workflows/make_sdist.yml @@ -1,11 +1,6 @@ name: Make source distribution -on: - push: - branches: - - main - - 'release/**' - pull_request: +on: ['push', 'pull_request'] jobs: make_sdist: From 6f265cb884228634fbd11f18e22ebc4a99902b9c Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 11:11:27 -0600 Subject: [PATCH 48/61] Add GitHub sponsor link --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..73e3827 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [wmayner] From 29ce92a714c989b817c094fb81ea4d812779cecf Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Tue, 14 Feb 2023 11:14:06 -0600 Subject: [PATCH 49/61] README: Update badges --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 88a87b6..96d4031 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,8 @@ -.. image:: https://img.shields.io/travis/wmayner/pyemd/develop.svg?style=flat-square&maxAge=3600 - :target: https://travis-ci.org/wmayner/pyemd +.. image:: https://img.shields.io/github/actions/workflow/status/wmayner/pyemd/build_wheels.yml?style=flat-square&maxAge=86400 + :target: https://github.com/wmayner/pyemd/actions/workflows/build_wheels.yml + :alt: Build status badge .. image:: https://img.shields.io/pypi/pyversions/pyemd.svg?style=flat-square&maxAge=86400 - :target: https://wiki.python.org/moin/Python2orPython3 + :target: https://pypi.org/project/pyemd/ :alt: Python versions badge PyEMD: Fast EMD for Python From a002a2733c4d98b8f408481d23566c85cb302bd4 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sun, 19 Feb 2023 23:38:48 -0600 Subject: [PATCH 50/61] pyproject.toml: Add twine to dist dependencies --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2080f24..d15f171 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dynamic = ['version'] [project.optional-dependencies] test = ['pytest'] -dist = ['cibuildwheel', 'setuptools_scm'] +dist = ['build', 'cibuildwheel', 'setuptools_scm', 'twine'] [project.urls] "Homepage" = "https://github.com/wmayner/pyemd" From bde4860f5f7360435691c7e1209714b38c61b629 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Sun, 19 Feb 2023 23:39:08 -0600 Subject: [PATCH 51/61] Makefile: reorganize tasks --- Makefile | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 981742c..1ed3103 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,11 @@ src = src/pyemd test = test -dist = wheelhouse +dist = dist +wheelhouse = wheelhouse readme = README.rst -default: develop +default: test clean: rm -rf $(shell find . -name '__pycache__') @@ -21,15 +22,18 @@ develop: clean test: develop py.test -build-local: clean +dist-build-local: python -m build +dist-build-sdist: + python -m build --sdist + +dist-build-wheels: + cibuildwheel --platform linux --config-file pyproject.toml + dist-clean: rm -rf $(dist) -build: dist-clean - cibuildwheel --platform linux --config-file pyproject.toml --output-dir $(dist) - dist-upload: dist-sign twine upload $(dist)/* @@ -42,3 +46,6 @@ dist-sign: dist-check dist-check: dist-build twine check --strict $(dist)/* + +dist-test-install: + pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pyemd From 08ee3ce91e1d689bea9687e396a0512ceeb515d5 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 00:03:20 -0600 Subject: [PATCH 52/61] Remove tox and travis configs --- .travis.yml | 50 -------------------------------------------------- tox.ini | 12 ------------ 2 files changed, 62 deletions(-) delete mode 100644 .travis.yml delete mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 170a6d9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -sudo: false -os: - - linux -language: python -python: - - '3.4' - - '3.5' - - '3.6' - - '3.7' - - '3.8' - - '3.9' - - '3.10' - - '3.11' -install: - - python -m pip install -r dev_requirements.txt - - make build - - python -m pip uninstall --yes -r dev_requirements.txt - - python -m pip install --upgrade tox tox-travis - - python -m pip freeze -script: tox -jobs: - # See https://github.com/pyload/pyload/blob/458fa33b42/.travis.yml - # for a workaround for testing on macOS with Travis - include: - - name: "macOS tests" - os: osx - language: sh - env: - - HOMEBREW_NO_INSTALL_CLEANUP=1 - - HOMEBREW_NO_ANALYTICS=1 - before_cache: - - rm -f "$HOME/Library/Caches/pip/log/debug.log" - cache: - directories: - - "$HOME/Library/Caches/pip" - addons: - homebrew: - update: true - packages: python3 - before_install: - - python -m pip install --upgrade virtualenv - - virtualenv -p python --system-site-packages "$HOME/venv" - - source "$HOME/venv/bin/activate" -notifications: - email: false - slack: - rooms: - secure: rxQsNRK9XBkBV0pdYuJG+tsN2tky+JUEF5ayDIUAzSaPeB//VVNNofJhcmfNgG1WiEEi6fe0dR/Y6UDsoVyQrbCHO2q2bIVQp6A/63vgz3DcVQzMahB/QVwte7gy02nLf6rS2g3VetVXrTW6OO4Cv7NQrQb58biVFx/yBtQ3qzI= - on_success: never - on_failure: always diff --git a/tox.ini b/tox.ini deleted file mode 100644 index f610e9f..0000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -[tox] -envlist = py{34,35,36,37,38,39,310,311}-numpy{114,115} - -[testenv] -deps = - -r{toxinidir}/test_requirements.txt - # Use NumPy < 1.14 and NumPy >= 1.15 for `get_bins()` switch - numpy114: numpy<1.15 - numpy115: numpy>=1.15 - -commands = make test -whitelist_externals = make From f135c0b0739a6a9290d48be0513566ed4f28f95f Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 00:03:41 -0600 Subject: [PATCH 53/61] Makefile: don't clean dist --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 1ed3103..8126af2 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ clean: rm -rf $(shell find . -name '*.so') rm -rf .eggs rm -rf pyemd.egg-info - rm -rf dist rm -rf build develop: clean From 806a19837593ffbc5acf6e53a3d82d5f5df7d8c4 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 00:04:03 -0600 Subject: [PATCH 54/61] No local version with setuptools_scm Currently it seems there's no way to disable local versions without modifying tracked files; see https://github.com/pypa/setuptools_scm/issues/455. For now we just forgo them, but in the future it'd be better to use them to avoid accidental uploading of non-release code. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d15f171..1a2aa91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dist = ['build', 'cibuildwheel', 'setuptools_scm', 'twine'] [tool.setuptools_scm] write_to = "src/pyemd/_version.py" +local_scheme = "no-local-version" [tool.cibuildwheel] skip = ["cp36*", "pp*"] From 138175495cbf91b73dfa291427cc5f44cc780a74 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 00:19:14 -0600 Subject: [PATCH 55/61] Makefile: remove sdist task --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 8126af2..f6b786f 100644 --- a/Makefile +++ b/Makefile @@ -24,9 +24,6 @@ test: develop dist-build-local: python -m build -dist-build-sdist: - python -m build --sdist - dist-build-wheels: cibuildwheel --platform linux --config-file pyproject.toml From ddf361ea59bd9aa961fe6b23f57b1aa037a82721 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 12:34:44 -0600 Subject: [PATCH 56/61] pyproject.toml: `git describe` before building wheels --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 1a2aa91..47b70b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,5 +40,6 @@ local_scheme = "no-local-version" [tool.cibuildwheel] skip = ["cp36*", "pp*"] build-verbosity = 2 +before-build = "git describe" test-requires = ["pytest"] test-command = "py.test {project}" From 5ef113ca6b1f62228722d8a0a37f16e4c192e902 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 13:21:45 -0600 Subject: [PATCH 57/61] Fix `git describe` command --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 47b70b4..f779140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,6 @@ local_scheme = "no-local-version" [tool.cibuildwheel] skip = ["cp36*", "pp*"] build-verbosity = 2 -before-build = "git describe" +before-build = "cd {project} && git describe" test-requires = ["pytest"] test-command = "py.test {project}" From 11263c71d13a30d16dbeebc397855e25e3f526de Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Mon, 20 Feb 2023 13:28:20 -0600 Subject: [PATCH 58/61] build_wheels.yml: Use `fetch-depth: 0` --- .github/workflows/build_wheels.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 49bab4b..28c900d 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Build wheels uses: pypa/cibuildwheel@v2.12.0 From 1fcddf7922e23b0670d044ede6cec3358a9722f5 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Thu, 23 Feb 2023 22:52:06 -0600 Subject: [PATCH 59/61] README.rst: Remove "Contributing" section --- README.rst | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index 96d4031..015d654 100644 --- a/README.rst +++ b/README.rst @@ -55,8 +55,9 @@ You can also calculate the EMD directly from two arrays of observations: >>> emd_samples(first_array, second_array, bins=2) 0.5 -Documentation -------------- + +API Documentation +----------------- emd() ~~~~~ @@ -153,6 +154,7 @@ and ``second_array``. ---- + Limitations and Caveats ----------------------- @@ -181,22 +183,6 @@ Limitations and Caveats ``np.histogram_bin_edges()`` is called instead, which is more efficient. -Contributing ------------- - -To help develop PyEMD, fork the project on GitHub and install the requirements -with ``pip install -r requirements.txt``. - -The ``Makefile`` defines some tasks to help with development: - -- ``test``: Run the test suite -- ``build`` Generate and compile the Cython extension -- ``clean``: Remove the compiled Cython extension -- ``default``: Run ``build`` - -Tests for different Python environments can be run with ``tox``. - - Credit ------ From 72caad27b9116b61137c5ec96d1079bb3be4de83 Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Thu, 23 Feb 2023 22:54:29 -0600 Subject: [PATCH 60/61] Makefile: reorder tasks --- Makefile | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index f6b786f..8ba3cc0 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,15 @@ src = src/pyemd test = test dist = dist wheelhouse = wheelhouse -readme = README.rst default: test +test: develop + py.test + +develop: clean + python -m pip install -e ".[test,dist]" + clean: rm -rf $(shell find . -name '__pycache__') rm -rf $(shell find . -name '*.so') @@ -15,33 +20,30 @@ clean: rm -rf pyemd.egg-info rm -rf build -develop: clean - python -m pip install -e ".[test,dist]" - -test: develop - py.test - dist-build-local: python -m build dist-build-wheels: cibuildwheel --platform linux --config-file pyproject.toml -dist-clean: - rm -rf $(dist) - dist-upload: dist-sign twine upload $(dist)/* + twine upload $(wheelhouse)/* dist-test-upload: dist-check - twine upload --repository testpypi $(dist)/* + twine upload --repository-url https://test.pypi.org/simple/ testpypi $(dist)/* + twine upload --repository-url https://test.pypi.org/simple/ testpypi $(wheelhouse)/* dist-sign: dist-check gpg --detach-sign -a $(dist)/*.tar.gz - gpg --detach-sign -a $(dist)/*.whl + gpg --detach-sign -a $(wheelhouse)/*.whl -dist-check: dist-build +dist-check: twine check --strict $(dist)/* + twine check --strict $(wheelhouse)/* + +dist-clean: + rm -rf $(dist) dist-test-install: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pyemd From bada2067523acd30f202b6cb50ce9bf358e9abba Mon Sep 17 00:00:00 2001 From: Will Mayner Date: Thu, 23 Feb 2023 22:55:11 -0600 Subject: [PATCH 61/61] make_sdist.yml: Rename steps --- .github/workflows/make_sdist.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/make_sdist.yml b/.github/workflows/make_sdist.yml index 7f04b30..b74d383 100644 --- a/.github/workflows/make_sdist.yml +++ b/.github/workflows/make_sdist.yml @@ -4,14 +4,14 @@ on: ['push', 'pull_request'] jobs: make_sdist: - name: Make SDist + name: Make source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Build SDist + - name: Build source distribution run: pipx run build --sdist - uses: actions/upload-artifact@v3