├── MAINTAINERS.md ├── doc ├── example.png ├── go-git-mapping.png ├── go-git-original.png ├── go-git-shuffled.png ├── contributing.md └── code_of_conduct.md ├── requirements.txt ├── .flake8 ├── requirements-lint.txt ├── setup.py ├── .travis.yml ├── .gitignore ├── test_seriate.py ├── README.md ├── seriate.py └── LICENSE.md /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Vadim Markovtsev -------------------------------------------------------------------------------- /doc/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/src-d/seriate/HEAD/doc/example.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.15.1 2 | scipy==1.0.1 3 | ortools==6.9.5824 4 | packaging==16.8 -------------------------------------------------------------------------------- /doc/go-git-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/src-d/seriate/HEAD/doc/go-git-mapping.png -------------------------------------------------------------------------------- /doc/go-git-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/src-d/seriate/HEAD/doc/go-git-original.png -------------------------------------------------------------------------------- /doc/go-git-shuffled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/src-d/seriate/HEAD/doc/go-git-shuffled.png -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore=D301 3 | max-line-length=99 4 | exclude= 5 | .git 6 | doc 7 | inline-quotes=" 8 | import-order-style=appnexus 9 | application-package-names=seriate 10 | per-file-ignores= 11 | test_seriate.py:D 12 | setup.py:D 13 | -------------------------------------------------------------------------------- /requirements-lint.txt: -------------------------------------------------------------------------------- 1 | flake8==3.5.0 2 | flake8-bugbear==18.8.0 3 | flake8-docstrings==1.3.0 4 | flake8-import-order==0.18 5 | flake8-quotes==1.0.0 6 | flake8-commas==2.0.0 7 | flake8-per-file-ignores==0.6 8 | pydocstyle==3.0.0 # remove when https://github.com/PyCQA/pydocstyle/issues/375 is solved 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup 4 | 5 | 6 | with open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8") as f: 7 | long_description = f.read() 8 | 9 | 10 | setup( 11 | name="seriate", 12 | description="Implementation of the Seriation sequence ordering algorithm.", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | version="1.1.2", 16 | license="Apache-2.0", 17 | author="source{d}", 18 | author_email="machine-learning@sourced.tech", 19 | url="https://github.com/src-d/seriate", 20 | download_url="https://github.com/src-d/seriate", 21 | py_modules=["seriate"], 22 | keywords=["seriation"], 23 | install_requires=["numpy>=1.0", "ortools>=6.7.4973,<8", "packaging>=16.0"], 24 | tests_require=["scipy>=1.0"], 25 | package_data={"": ["LICENSE.md", "README.md", "requirements.txt"]}, 26 | classifiers=[ 27 | "Development Status :: 5 - Production/Stable", 28 | "Intended Audience :: Developers", 29 | "License :: OSI Approved :: Apache Software License", 30 | "Programming Language :: Python :: 3.5", 31 | "Programming Language :: Python :: 3.6", 32 | "Programming Language :: Python :: 3.7", 33 | "Programming Language :: Python :: 3.8", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: true 3 | dist: xenial 4 | cache: pip 5 | before_cache: 6 | - chown -R travis:travis $HOME/.cache/pip 7 | stages: 8 | - style 9 | - test 10 | - name: deploy 11 | if: tag =~ .* 12 | _install: &_install 13 | - pip install --upgrade pip cython codecov 14 | - pip install ortools$ortools 15 | - pip install .[test] 16 | _coverage: &_coverage 17 | - coverage run setup.py test 18 | matrix: 19 | fast_finish: true 20 | include: 21 | - stage: style 22 | python: 3.7 23 | script: 24 | - flake8 25 | install: 26 | - pip install -r requirements-lint.txt 27 | - stage: test 28 | python: 3.5 29 | script: *_coverage 30 | install: *_install 31 | - stage: test 32 | python: 3.6 33 | env: 34 | - ortools="==6.7.4973" 35 | script: *_coverage 36 | install: *_install 37 | - stage: test 38 | python: 3.6 39 | script: *_coverage 40 | install: *_install 41 | - stage: test 42 | python: 3.7 43 | env: 44 | - ortools="==6.9.5824" 45 | script: *_coverage 46 | install: *_install 47 | - stage: test 48 | python: 3.7 49 | script: *_coverage 50 | install: *_install 51 | after_success: 52 | - codecov 53 | - stage: deploy 54 | python: 3.5 55 | install: 56 | - pip install --upgrade pip 57 | - pip install twine pyopenssl 58 | script: 59 | - test $(python3 setup.py --version) == $TRAVIS_TAG 60 | - python3 setup.py bdist_wheel 61 | deploy: 62 | provider: script 63 | script: twine upload dist/*py3-none-any* -u $PYPI_LOGIN -p $PYPI_PASS 64 | skip_cleanup: true 65 | on: 66 | tags: true 67 | notifications: 68 | email: false 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac files 2 | *.DS_Store 3 | 4 | .idea 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | doc/_build 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | 111 | # CI 112 | .ci -------------------------------------------------------------------------------- /test_seriate.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy 4 | from scipy.spatial.distance import pdist, squareform 5 | 6 | from seriate import IncompleteSolutionError, InvalidDistanceValues, seriate 7 | 8 | 9 | class SeriateTests(unittest.TestCase): 10 | def setUp(self): 11 | self.elements = numpy.ones((5, 3)) * numpy.arange(5, 0, -1)[:, None] 12 | 13 | def test_pdist(self): 14 | dists = pdist(self.elements) 15 | seq = seriate(dists) 16 | self.assertEqual(seq, [4, 3, 2, 1, 0]) 17 | 18 | def test_squareform(self): 19 | dists = squareform(pdist(self.elements)) 20 | seq = seriate(dists) 21 | self.assertEqual(seq, [4, 3, 2, 1, 0]) 22 | 23 | def test_empty_pdist(self): 24 | dists = numpy.empty((0,)) 25 | seq = seriate(dists) 26 | self.assertEqual(seq, [0]) 27 | 28 | def test_empty_squareform(self): 29 | dists = numpy.empty((0, 0)) 30 | seq = seriate(dists) 31 | self.assertEqual(seq, []) 32 | 33 | def test_single(self): 34 | dists = numpy.ones((1, 1)) 35 | seq = seriate(dists) 36 | self.assertEqual(seq, [0]) 37 | 38 | def test_timeout_error(self): 39 | with self.assertRaises(IncompleteSolutionError): 40 | seriate(pdist(self.elements), timeout=1e-4) 41 | 42 | def test_timeout_0(self): 43 | numpy.random.seed(160290) 44 | big_dist = numpy.random.random((2000, 2000)) # This matrix will increase the timeout once 45 | seq = seriate(big_dist, timeout=0) 46 | self.assertIsInstance(seq, list) 47 | self.assertIsInstance(seq[0], int) 48 | 49 | def test_errors(self): 50 | with self.assertRaises(InvalidDistanceValues): 51 | seriate(numpy.array([numpy.inf, 0, 0, 0])) 52 | 53 | with self.assertRaises(InvalidDistanceValues): 54 | seriate(numpy.array([numpy.nan, 0, 0, 0])) 55 | 56 | with self.assertRaises(InvalidDistanceValues): 57 | seriate(numpy.array([None, 0, 0, 0])) 58 | 59 | 60 | if __name__ == "__main__": 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seriate 2 | Optimal ordering of elements in a set given their distance matrix. 3 | 4 | [![Travis build status](https://travis-ci.com/src-d/seriate.svg?branch=master)](https://travis-ci.com/src-d/seriate) 5 | [![Code coverage](https://codecov.io/github/src-d/seriate/coverage.svg)](https://codecov.io/github/src-d/seriate) 6 | [![PyPi package status](https://img.shields.io/pypi/v/seriate.svg)](https://pypi.python.org/pypi/seriate) 7 | ![stability: stable](https://svg-badge.appspot.com/badge/stability/stable?color=007ec6) 8 | [![Apache 2.0 license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 9 | 10 | ![example](doc/example.png) 11 | 12 | [Overview](#overview) • [How To Use](#how-to-use) • [Contributions](#contributions) • [License](#license) 13 | 14 | ## Overview 15 | 16 | This is a Python implementation of [Seriation](http://nicolas.kruchten.com/content/2018/02/seriation/) 17 | algorithm. Seriation is an approach for ordering elements in a set so that the 18 | sum of the sequential pairwise distances is minimal. We state this task 19 | as a Travelling Salesman Problem (TSP) and leverage the powerful [Google's or-tools](https://github.com/google/or-tools) 20 | to do heavy-lifting. Since TSP is NP-hard, it is not possible to calculate 21 | the precise solution for a big number of elements. However, the or-tools' 22 | heuristics work very well in practice, and they are used in e.g. Google Maps. 23 | 24 | Any [`numpy.roll`-ed](https://docs.scipy.org/doc/numpy-1.16.0/reference/generated/numpy.roll.html) 25 | result is equivalent. 26 | 27 | ## How To Use 28 | 29 | ```python 30 | import numpy 31 | from scipy.spatial.distance import pdist 32 | from seriate import seriate 33 | 34 | elements = numpy.array([ 35 | [3, 3, 3], 36 | [5, 5, 5], 37 | [4, 4, 4], 38 | [2, 2, 2], 39 | [1, 1, 1] 40 | ]) 41 | 42 | print(seriate(pdist(elements))) 43 | 44 | # Output: [4, 3, 0, 2, 1] 45 | ``` 46 | 47 | The example above shows how we order 5 elements: `[3, 3, 3]`, 48 | `[5, 5, 5]`, `[4, 4, 4]`, `[2, 2, 2]` and `[1, 1, 1]`. The result 49 | is expected: 50 | 51 | 1. `[1, 1, 1]` 52 | 2. `[2, 2, 2]` 53 | 3. `[3, 3, 3]` 54 | 4. `[4, 4, 4]` 55 | 5. `[5, 5, 5]` 56 | 57 | `pdist` from [`scipy.spatial.distance`](https://docs.scipy.org/doc/scipy/reference/spatial.distance.html) 58 | uses Euclidean (L2) dstance metric by default, so the distance between 59 | `[x, x, x]` and `[x + 1, x + 1, x + 1]` is constant: √3. Any other distance 60 | is bigger, so the optimal ordering is to list our elements in the increasing 61 | norm order. 62 | 63 | ## Contributions 64 | 65 | Contributions are very welcome and desired! Please follow the [code of conduct](doc/code_of_conduct.md) 66 | and read the [contribution guidelines](doc/contributing.md). 67 | 68 | ## License 69 | 70 | Apache-2.0, see [LICENSE.md](LICENSE.md). 71 | -------------------------------------------------------------------------------- /doc/contributing.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | seriate project is [Apache-2.0 licensed](../LICENSE.md) and accepts contributions via GitHub pull requests. This document outlines some of the conventions on development workflow, commit message formatting, contact points, and other resources to make it easier to get your contribution accepted. 4 | 5 | ## Certificate of Origin 6 | 7 | By contributing to this project you agree to the [Developer Certificate of Origin \(DCO\)](https://github.com/src-d/ml/tree/d1f13d079f57caa6338bb7eb8acb9062e011eda9/DCO/README.md). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the contribution. 8 | 9 | In order to show your agreement with the DCO you should include at the end of commit message, the following line: `Signed-off-by: John Doe `, using your real name. 10 | 11 | This can be done easily using the [`-s`](https://github.com/git/git/blob/b2c150d3aa82f6583b9aadfecc5f8fa1c74aca09/Documentation/git-commit.txt#L154-L161) flag on the `git commit`. 12 | 13 | ## Support Channels 14 | 15 | The official support channels, for both users and contributors, are: 16 | 17 | * GitHub [issues](https://github.com/src-d/seriate/issues)\* 18 | * Slack: \#machine-learning room in the [source{d} Slack](https://join.slack.com/t/sourced-community/shared_invite/enQtMjc4Njk5MzEyNzM2LTFjNzY4NjEwZGEwMzRiNTM4MzRlMzQ4MmIzZjkwZmZlM2NjODUxZmJjNDI1OTcxNDAyMmZlNmFjODZlNTg0YWM) 19 | 20 | \*Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of. 21 | 22 | ## How to Contribute 23 | 24 | Pull Requests \(PRs\) are the main and exclusive way to contribute to the official seriate project. In order for a PR to be accepted it needs to pass a list of requirements: 25 | 26 | * Code Coverage does not decrease. 27 | * All the tests pass. 28 | * The code is formatted according to [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/). 29 | * If the PR is a bug fix, it has to include a new unit test that fails before the patch is merged. 30 | * If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality. 31 | * In any case, all the PRs have to pass the personal evaluation of at least one of the [maintainers](../maintainers.md). 32 | 33 | ### Format of the commit message 34 | 35 | The commit summary must start with a capital letter and with a verb in present tense. No dot in the end. 36 | 37 | ```text 38 | Add a feature 39 | Remove unused code 40 | Fix a bug 41 | ``` 42 | 43 | Every commit details should describe what was changed, under which context and, if applicable, the GitHub issue it relates to. 44 | 45 | -------------------------------------------------------------------------------- /doc/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # CODE OF CONDUCT 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or 20 | 21 | advances 22 | 23 | * Trolling, insulting/derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or electronic 26 | 27 | address, without explicit permission 28 | 29 | * Other conduct which could reasonably be considered inappropriate in a 30 | 31 | professional setting 32 | 33 | ## Our Responsibilities 34 | 35 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 36 | 37 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 38 | 39 | ## Scope 40 | 41 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 42 | 43 | ## Enforcement 44 | 45 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct@sourced.tech. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 46 | 47 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 48 | 49 | ## Attribution 50 | 51 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) 52 | 53 | -------------------------------------------------------------------------------- /seriate.py: -------------------------------------------------------------------------------- 1 | """Seriation - NP-hard ordering of elements in a set given the distance matrix.""" 2 | from typing import List 3 | 4 | import numpy 5 | import ortools 6 | from ortools.constraint_solver import pywrapcp, routing_enums_pb2 7 | from packaging.version import Version 8 | 9 | 10 | __version__ = "1.0.1" 11 | ortools_version = Version(ortools.__version__) 12 | ortools6 = Version("6.0.0") <= ortools_version < Version("7") 13 | ortools7 = Version("7.0.0") <= ortools_version < Version("8") 14 | if not ortools6 and not ortools7: 15 | raise ImportError("No valid version of ortools installed. Please install ortools 6 or 7.") 16 | 17 | 18 | class IncompleteSolutionError(Exception): 19 | """Indicate that a solution for the TSP problem was not found.""" 20 | 21 | pass 22 | 23 | 24 | class InvalidDistanceValues(ValueError): 25 | """Indicate that the distance array contains invalid values.""" 26 | 27 | 28 | def _validate_data(dists: numpy.ndarray): 29 | """Check dists contains valid values.""" 30 | try: 31 | isinf = numpy.isinf(dists).any() 32 | isnan = numpy.isnan(dists).any() 33 | except Exception as e: 34 | raise InvalidDistanceValues() from e 35 | if isinf: 36 | raise InvalidDistanceValues("Data contains inf values.") 37 | if isnan: 38 | raise InvalidDistanceValues("Data contains NaN values.") 39 | 40 | 41 | def seriate(dists: numpy.ndarray, approximation_multiplier: int = 1000, 42 | timeout: float = 2.0) -> List[int]: 43 | """ 44 | Order the elements of a set so that the sum of sequential pairwise distances is minimal. 45 | 46 | We solve the Travelling Salesman Problem (TSP) under the hood. 47 | Reference: http://nicolas.kruchten.com/content/2018/02/seriation/ 48 | 49 | :param dists: Either a condensed pdist-like or a symmetric square distance matrix. 50 | :param approximation_multiplier: Multiply by this number before converting distances \ 51 | to integers. 52 | :param timeout: Maximum amount of time allowed to spend for solving the TSP, in seconds. \ 53 | This value cannot be less than 0. If timeout is 0 it will try to solve \ 54 | the problem with timeout = 1, and double the timeout every time it fails \ 55 | until a valid solution is found. 56 | :return: List with ordered element indexes, the same length as the number of elements \ 57 | involved in calculating `dists`. 58 | """ 59 | _validate_data(dists) 60 | if timeout > 0: 61 | return _seriate(dists=dists, approximation_multiplier=approximation_multiplier, 62 | timeout=timeout) 63 | elif timeout < 0: 64 | raise ValueError("timeout cannot be negative.") 65 | timeout = 1. 66 | route = None 67 | while route is None: 68 | try: 69 | route = _seriate(dists=dists, approximation_multiplier=approximation_multiplier, 70 | timeout=timeout) 71 | except IncompleteSolutionError: 72 | timeout *= 2 73 | return route 74 | 75 | 76 | def _seriate(dists: numpy.ndarray, approximation_multiplier=1000, timeout=2.0) -> List[int]: 77 | """ 78 | Order the elements of a set so that the sum of sequential pairwise distances is minimal. 79 | 80 | We solve the Travelling Salesman Problem (TSP) under the hood. 81 | Reference: http://nicolas.kruchten.com/content/2018/02/seriation/ 82 | 83 | :param dists: Either a condensed pdist-like or a symmetric square distance matrix. 84 | :param approximation_multiplier: Multiply by this number before converting distances \ 85 | to integers. 86 | :param timeout: Maximum amount of time allowed to spend for solving the TSP, in seconds. 87 | :return: List with ordered element indexes, the same length as the number of elements \ 88 | involved in calculating `dists`. 89 | """ 90 | assert dists[dists < 0].size == 0, "distances must be non-negative" 91 | assert timeout > 0 92 | squareform = len(dists.shape) == 2 93 | if squareform: 94 | assert dists.shape[0] == dists.shape[1] 95 | size = dists.shape[0] 96 | else: 97 | # dists.shape[0] = (m * (m - 1)) // 2 98 | assert len(dists.shape) == 1 99 | assert int(numpy.round(numpy.sqrt(1 + 8 * dists.shape[0]))) ** 2 == 1 + 8 * dists.shape[0] 100 | size = int(numpy.round((1 + numpy.sqrt(1 + 8 * dists.shape[0])) / 2)) 101 | 102 | if ortools6: 103 | routing = pywrapcp.RoutingModel(size + 1, 1, size) 104 | elif ortools7: 105 | manager = pywrapcp.RoutingIndexManager(size + 1, 1, size) 106 | routing = pywrapcp.RoutingModel(manager) 107 | 108 | def dist_callback(x, y): 109 | if ortools7: 110 | x = manager.IndexToNode(x) 111 | y = manager.IndexToNode(y) 112 | if x == size or y == size or x == y: 113 | return 0 114 | if squareform: 115 | dist = dists[x, y] 116 | else: 117 | # convert to the condensed index 118 | if x < y: 119 | x, y = y, x 120 | dist = dists[size * y - y * (y + 1) // 2 + x - y - 1] 121 | # ortools wants integers, so we approximate here 122 | return int(dist * approximation_multiplier) 123 | 124 | if ortools6: 125 | routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) 126 | search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() 127 | search_parameters.time_limit_ms = int(timeout * 1000) 128 | elif ortools7: 129 | routing.SetArcCostEvaluatorOfAllVehicles(routing.RegisterTransitCallback(dist_callback)) 130 | search_parameters = pywrapcp.DefaultRoutingSearchParameters() 131 | search_parameters.time_limit.FromMilliseconds(int(timeout * 1000)) 132 | search_parameters.local_search_metaheuristic = \ 133 | routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH 134 | search_parameters.first_solution_strategy = \ 135 | routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC 136 | assignment = routing.SolveWithParameters(search_parameters) 137 | if assignment is None: 138 | raise IncompleteSolutionError("No solution was found. Please increase the " 139 | "timeout value or set it to 0.") 140 | index = routing.Start(0) 141 | route = [] 142 | while not routing.IsEnd(index): 143 | if ortools6: 144 | node = routing.IndexToNode(index) 145 | elif ortools7: 146 | node = manager.IndexToNode(index) 147 | if node < size: 148 | route.append(node) 149 | index = assignment.Value(routing.NextVar(index)) 150 | return route 151 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # LICENSE 2 | 3 | _Version 2.0, January 2004_ 4 | _<_[http://www.apache.org/licenses/](http://www.apache.org/licenses/)_>_ 5 | 6 | ## Terms and Conditions for use, reproduction, and distribution 7 | 8 | ### 1. Definitions 9 | 10 | “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 13 | 14 | “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **\(i\)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **\(ii\)** ownership of fifty percent \(50%\) or more of the outstanding shares, or **\(iii\)** beneficial ownership of such entity. 15 | 16 | “You” \(or “Your”\) shall mean an individual or Legal Entity exercising permissions granted by this License. 17 | 18 | “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 19 | 20 | “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 21 | 22 | “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work \(an example is provided in the Appendix below\). 23 | 24 | “Derivative Works” shall mean any work, whether in Source or Object form, that is based on \(or derived from\) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link \(or bind by name\) to the interfaces of, the Work and Derivative Works thereof. 25 | 26 | “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” 27 | 28 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 29 | 30 | ### 2. Grant of Copyright License 31 | 32 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 33 | 34 | ### 3. Grant of Patent License 35 | 36 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable \(except as stated in this section\) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution\(s\) alone or by combination of their Contribution\(s\) with the Work to which such Contribution\(s\) was submitted. If You institute patent litigation against any entity \(including a cross-claim or counterclaim in a lawsuit\) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 37 | 38 | ### 4. Redistribution 39 | 40 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 41 | 42 | * **\(a\)** You must give any other recipients of the Work or Derivative Works a copy of 43 | 44 | this License; and 45 | 46 | * **\(b\)** You must cause any modified files to carry prominent notices stating that You 47 | 48 | changed the files; and 49 | 50 | * **\(c\)** You must retain, in the Source form of any Derivative Works that You distribute, 51 | 52 | all copyright, patent, trademark, and attribution notices from the Source form 53 | 54 | of the Work, excluding those notices that do not pertain to any part of the 55 | 56 | Derivative Works; and 57 | 58 | * **\(d\)** If the Work includes a “NOTICE” text file as part of its distribution, then any 59 | 60 | Derivative Works that You distribute must include a readable copy of the 61 | 62 | attribution notices contained within such NOTICE file, excluding those notices 63 | 64 | that do not pertain to any part of the Derivative Works, in at least one of the 65 | 66 | following places: within a NOTICE text file distributed as part of the 67 | 68 | Derivative Works; within the Source form or documentation, if provided along 69 | 70 | with the Derivative Works; or, within a display generated by the Derivative 71 | 72 | Works, if and wherever such third-party notices normally appear. The contents of 73 | 74 | the NOTICE file are for informational purposes only and do not modify the 75 | 76 | License. You may add Your own attribution notices within Derivative Works that 77 | 78 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 79 | 80 | provided that such additional attribution notices cannot be construed as 81 | 82 | modifying the License. 83 | 84 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 85 | 86 | ### 5. Submission of Contributions 87 | 88 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 89 | 90 | ### 6. Trademarks 91 | 92 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 93 | 94 | ### 7. Disclaimer of Warranty 95 | 96 | Unless required by applicable law or agreed to in writing, Licensor provides the Work \(and each Contributor provides its Contributions\) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 97 | 98 | ### 8. Limitation of Liability 99 | 100 | In no event and under no legal theory, whether in tort \(including negligence\), contract, or otherwise, unless required by applicable law \(such as deliberate and grossly negligent acts\) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work \(including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses\), even if such Contributor has been advised of the possibility of such damages. 101 | 102 | ### 9. Accepting Warranty or Additional Liability 103 | 104 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 105 | 106 | _END OF TERMS AND CONDITIONS_ 107 | 108 | ## APPENDIX: How to apply the Apache License to your work 109 | 110 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. \(Don't include the brackets!\) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. 111 | 112 | ```text 113 | Copyright 2018 source{d} 114 | 115 | Licensed under the Apache License, Version 2.0 (the "License"); 116 | you may not use this file except in compliance with the License. 117 | You may obtain a copy of the License at 118 | 119 | http://www.apache.org/licenses/LICENSE-2.0 120 | 121 | Unless required by applicable law or agreed to in writing, software 122 | distributed under the License is distributed on an "AS IS" BASIS, 123 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 124 | See the License for the specific language governing permissions and 125 | limitations under the License. 126 | ``` 127 | 128 | --------------------------------------------------------------------------------