pyastar2d 1.0.5__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ include requirements.txt
2
+ include src/cpp/experimental_heuristics.h
@@ -1,18 +1,24 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyastar2d
3
- Version: 1.0.5
3
+ Version: 1.1.0
4
4
  Summary: A simple implementation of the A* algorithm for path-finding on a two-dimensional grid.
5
5
  Home-page: https://github.com/hjweide/pyastar2d
6
6
  Author: Hendrik Weideman
7
7
  Author-email: hjweide@gmail.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.7
8
+ Requires-Python: >=3.8
14
9
  Description-Content-Type: text/markdown
15
10
  License-File: LICENSE
11
+ Requires-Dist: imageio
12
+ Requires-Dist: numpy
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
16
22
 
17
23
  [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
18
24
  [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
@@ -109,7 +115,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
109
115
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
110
116
  ```
111
117
 
112
- ### Small Maze (1802 x 1802):
118
+ ### Small Maze (1802 x 1802):
113
119
  ```bash
114
120
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
115
121
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -124,7 +130,7 @@ sys 0m0.606s
124
130
  The solution found for the small maze is shown below:
125
131
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
126
132
 
127
- ### Large Maze (4002 x 4002):
133
+ ### Large Maze (4002 x 4002):
128
134
  ```bash
129
135
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
130
136
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -164,14 +170,34 @@ pip install -r requirements-dev.txt
164
170
  ```
165
171
  before running
166
172
  ```bash
167
- py.test
173
+ pytest
168
174
  ```
169
175
  The tests are fairly basic but cover some of the
170
176
  more common pitfalls. Pull requests for more extensive tests are welcome.
171
177
 
172
- ## References
173
- 1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
174
- 2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
178
+ ## Code Formatting
175
179
 
180
+ It's recommended that you use `pre-commit` to ensure linting procedures are
181
+ run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
182
+ more information.
176
183
 
184
+ Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
185
+ for software installation on your OS/platform. After you have the software
186
+ installed, run `pre-commit install` on the command line. Now every time you commit
187
+ to this project's code base the linter procedures will automatically run over the
188
+ changed files. To run pre-commit on files preemtively from the command line use:
177
189
 
190
+ ```bash
191
+ pip install -r requirements-dev.txt
192
+ pre-commit run --all-files
193
+ ```
194
+
195
+ The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
196
+ Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
197
+ use `flake8` as its Python linter, but pre-commit will ensure compliance before a
198
+ git commit is completed. This will use the `flake8` configuration within
199
+ `.flake8`, which ignores several errors and stylistic considerations.
200
+
201
+ ## References
202
+ 1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
203
+ 2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: pyastar2d
3
- Version: 1.0.5
4
- Summary: A simple implementation of the A* algorithm for path-finding on a two-dimensional grid.
5
- Home-page: https://github.com/hjweide/pyastar2d
6
- Author: Hendrik Weideman
7
- Author-email: hjweide@gmail.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.7
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
-
17
1
  [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
18
2
  [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
19
3
  [![PyPI version](https://badge.fury.io/py/pyastar2d.svg)](https://badge.fury.io/py/pyastar2d)
@@ -109,7 +93,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
109
93
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
110
94
  ```
111
95
 
112
- ### Small Maze (1802 x 1802):
96
+ ### Small Maze (1802 x 1802):
113
97
  ```bash
114
98
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
115
99
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -124,7 +108,7 @@ sys 0m0.606s
124
108
  The solution found for the small maze is shown below:
125
109
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
126
110
 
127
- ### Large Maze (4002 x 4002):
111
+ ### Large Maze (4002 x 4002):
128
112
  ```bash
129
113
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
130
114
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -164,14 +148,34 @@ pip install -r requirements-dev.txt
164
148
  ```
165
149
  before running
166
150
  ```bash
167
- py.test
151
+ pytest
168
152
  ```
169
153
  The tests are fairly basic but cover some of the
170
154
  more common pitfalls. Pull requests for more extensive tests are welcome.
171
155
 
172
- ## References
173
- 1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
174
- 2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
156
+ ## Code Formatting
157
+
158
+ It's recommended that you use `pre-commit` to ensure linting procedures are
159
+ run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
160
+ more information.
161
+
162
+ Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
163
+ for software installation on your OS/platform. After you have the software
164
+ installed, run `pre-commit install` on the command line. Now every time you commit
165
+ to this project's code base the linter procedures will automatically run over the
166
+ changed files. To run pre-commit on files preemtively from the command line use:
175
167
 
168
+ ```bash
169
+ pip install -r requirements-dev.txt
170
+ pre-commit run --all-files
171
+ ```
176
172
 
173
+ The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
174
+ Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
175
+ use `flake8` as its Python linter, but pre-commit will ensure compliance before a
176
+ git commit is completed. This will use the `flake8` configuration within
177
+ `.flake8`, which ignores several errors and stylistic considerations.
177
178
 
179
+ ## References
180
+ 1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
181
+ 2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel", "numpy"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,16 @@
1
+ [flake8]
2
+ exclude = .git
3
+
4
+ [tool:isort]
5
+ profile = black
6
+ line_length = 90
7
+ multi_line_output = 3
8
+ include_trailing_comma = true
9
+ force_grid_wrap = 0
10
+ use_parentheses = true
11
+ ensure_newline_before_comments = true
12
+
13
+ [egg_info]
14
+ tag_build =
15
+ tag_date = 0
16
+
@@ -0,0 +1,58 @@
1
+ import pathlib
2
+
3
+ from setuptools import Extension, find_packages, setup
4
+
5
+ # Use pathlib for paths
6
+ here = pathlib.Path(__file__).parent.resolve()
7
+
8
+ # Read README and requirements
9
+ long_description = (here / 'README.md').read_text(encoding='utf-8')
10
+ install_requires = (here / 'requirements.txt').read_text().splitlines()
11
+
12
+
13
+ class get_numpy_include:
14
+ """Defer numpy import until it is actually installed."""
15
+
16
+ def __str__(self):
17
+ import numpy
18
+
19
+ return numpy.get_include()
20
+
21
+
22
+ # Define the C++ extension
23
+ astar_module = Extension(
24
+ name='pyastar2d.astar',
25
+ sources=[
26
+ 'src/cpp/astar.cpp',
27
+ 'src/cpp/experimental_heuristics.cpp',
28
+ ],
29
+ define_macros=[
30
+ ('Py_LIMITED_API', '0x03080000'),
31
+ ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'),
32
+ ],
33
+ py_limited_api=True,
34
+ include_dirs=[
35
+ 'src/cpp',
36
+ get_numpy_include(),
37
+ ],
38
+ extra_compile_args=['-O3', '-fpic', '-Wall'],
39
+ language='c++',
40
+ )
41
+
42
+ # Define package metadata
43
+ setup(
44
+ name='pyastar2d',
45
+ version='1.1.0',
46
+ author='Hendrik Weideman',
47
+ author_email='hjweide@gmail.com',
48
+ description='A simple implementation of the A* algorithm for path-finding on a two-dimensional grid.',
49
+ long_description=long_description,
50
+ long_description_content_type='text/markdown',
51
+ url='https://github.com/hjweide/pyastar2d',
52
+ packages=find_packages(where='src', exclude=('tests',)),
53
+ package_dir={'': 'src'},
54
+ install_requires=install_requires,
55
+ python_requires='>=3.8',
56
+ ext_modules=[astar_module],
57
+ options={'bdist_wheel': {'py_limited_api': 'cp38'}},
58
+ )
@@ -2,6 +2,7 @@
2
2
  #include <limits>
3
3
  #include <cmath>
4
4
  #include <Python.h>
5
+ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
5
6
  #include <numpy/arrayobject.h>
6
7
  #include <iostream>
7
8
  #include <experimental_heuristics.h>
@@ -44,7 +45,7 @@ inline float l1_norm(int i0, int j0, int i1, int j1) {
44
45
  // diag_ok: if true, allows diagonal moves (8-conn.)
45
46
  // paths (output): for each node, stores previous node in path
46
47
  static PyObject *astar(PyObject *self, PyObject *args) {
47
- const PyArrayObject* weights_object;
48
+ PyArrayObject* weights_object;
48
49
  int h;
49
50
  int w;
50
51
  int start;
@@ -61,7 +62,7 @@ static PyObject *astar(PyObject *self, PyObject *args) {
61
62
  ))
62
63
  return NULL;
63
64
 
64
- float* weights = (float*) weights_object->data;
65
+ float* weights = (float*) PyArray_DATA(weights_object);
65
66
  int* paths = new int[h * w];
66
67
  int path_length = -1;
67
68
 
@@ -76,7 +77,7 @@ static PyObject *astar(PyObject *self, PyObject *args) {
76
77
  nodes_to_visit.push(start_node);
77
78
 
78
79
  int* nbrs = new int[8];
79
-
80
+
80
81
  int goal_i = goal / w;
81
82
  int goal_j = goal % w;
82
83
  int start_i = start / w;
@@ -141,11 +142,13 @@ static PyObject *astar(PyObject *self, PyObject *args) {
141
142
  if (path_length >= 0) {
142
143
  npy_intp dims[2] = {path_length, 2};
143
144
  PyArrayObject* path = (PyArrayObject*) PyArray_SimpleNew(2, dims, NPY_INT32);
145
+ char* data = (char*) PyArray_BYTES(path);
146
+ npy_intp* strides = PyArray_STRIDES(path);
144
147
  npy_int32 *iptr, *jptr;
145
148
  int idx = goal;
146
149
  for (npy_intp i = dims[0] - 1; i >= 0; --i) {
147
- iptr = (npy_int32*) (path->data + i * path->strides[0]);
148
- jptr = (npy_int32*) (path->data + i * path->strides[0] + path->strides[1]);
150
+ iptr = (npy_int32*) (data + i * strides[0]);
151
+ jptr = (npy_int32*) (data + i * strides[0] + strides[1]);
149
152
 
150
153
  *iptr = idx / w;
151
154
  *jptr = idx % w;
@@ -0,0 +1,20 @@
1
+ // Please note below heuristics are experimental and only for pretty lines.
2
+ // They may not take the shortest path and require additional cpu cycles.
3
+
4
+ #ifndef EXPERIMENTAL_HEURISTICS_H_
5
+ #define EXPERIMENTAL_HEURISTICS_H_
6
+
7
+
8
+ enum Heuristic { DEFAULT, ORTHOGONAL_X, ORTHOGONAL_Y };
9
+
10
+ typedef float (*heuristic_ptr)(int, int, int, int, int, int);
11
+
12
+ heuristic_ptr select_heuristic(int);
13
+
14
+ // Orthogonal x (moves by x first, then half way by y)
15
+ float orthogonal_x(int, int, int, int, int, int);
16
+
17
+ // Orthogonal y (moves by y first, then half way by x)
18
+ float orthogonal_y(int, int, int, int, int, int);
19
+
20
+ #endif
@@ -0,0 +1,3 @@
1
+ from pyastar2d.astar_wrapper import Heuristic, astar_path
2
+
3
+ __all__ = ['astar_path', 'Heuristic']
@@ -0,0 +1,77 @@
1
+ import ctypes
2
+ from enum import IntEnum
3
+ from typing import Optional, Tuple
4
+
5
+ import numpy as np
6
+
7
+ import pyastar2d.astar
8
+
9
+ # Define array types
10
+ ndmat_f_type = np.ctypeslib.ndpointer(dtype=np.float32, ndim=1, flags='C_CONTIGUOUS')
11
+ ndmat_i2_type = np.ctypeslib.ndpointer(dtype=np.int32, ndim=2, flags='C_CONTIGUOUS')
12
+
13
+ # Define input/output types
14
+ pyastar2d.astar.restype = ndmat_i2_type # Nx2 (i, j) coordinates or None
15
+ pyastar2d.astar.argtypes = [
16
+ ndmat_f_type, # weights
17
+ ctypes.c_int, # height
18
+ ctypes.c_int, # width
19
+ ctypes.c_int, # start index in flattened grid
20
+ ctypes.c_int, # goal index in flattened grid
21
+ ctypes.c_bool, # allow diagonal
22
+ ctypes.c_int, # heuristic_override
23
+ ]
24
+
25
+
26
+ class Heuristic(IntEnum):
27
+ """The supported heuristics."""
28
+
29
+ DEFAULT = 0
30
+ ORTHOGONAL_X = 1
31
+ ORTHOGONAL_Y = 2
32
+
33
+
34
+ def astar_path(
35
+ weights: np.ndarray,
36
+ start: Tuple[int, int],
37
+ goal: Tuple[int, int],
38
+ allow_diagonal: bool = False,
39
+ heuristic_override: Heuristic = Heuristic.DEFAULT,
40
+ ) -> Optional[np.ndarray]:
41
+ """
42
+ Run astar algorithm on 2d weights.
43
+
44
+ param np.ndarray weights: A grid of weights e.g. np.ones((10, 10), dtype=np.float32)
45
+ param Tuple[int, int] start: (i, j)
46
+ param Tuple[int, int] goal: (i, j)
47
+ param bool allow_diagonal: Whether to allow diagonal moves
48
+ param Heuristic heuristic_override: Override heuristic, see Heuristic(IntEnum)
49
+
50
+ """
51
+ assert (
52
+ weights.dtype == np.float32
53
+ ), f'weights must have np.float32 data type, but has {weights.dtype}'
54
+ # For the heuristic to be valid, each move must cost at least 1.
55
+ if weights.min(axis=None) < 1.0:
56
+ raise ValueError('Minimum cost to move must be 1, but got %f' % (weights.min(axis=None)))
57
+ # Ensure start is within bounds.
58
+ if start[0] < 0 or start[0] >= weights.shape[0] or start[1] < 0 or start[1] >= weights.shape[1]:
59
+ raise ValueError(f'Start of {start} lies outside grid.')
60
+ # Ensure goal is within bounds.
61
+ if goal[0] < 0 or goal[0] >= weights.shape[0] or goal[1] < 0 or goal[1] >= weights.shape[1]:
62
+ raise ValueError(f'Goal of {goal} lies outside grid.')
63
+
64
+ height, width = weights.shape
65
+ start_idx = np.ravel_multi_index(start, (height, width))
66
+ goal_idx = np.ravel_multi_index(goal, (height, width))
67
+
68
+ path = pyastar2d.astar.astar(
69
+ weights.flatten(),
70
+ height,
71
+ width,
72
+ start_idx,
73
+ goal_idx,
74
+ allow_diagonal,
75
+ int(heuristic_override),
76
+ )
77
+ return path
@@ -1,3 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyastar2d
3
+ Version: 1.1.0
4
+ Summary: A simple implementation of the A* algorithm for path-finding on a two-dimensional grid.
5
+ Home-page: https://github.com/hjweide/pyastar2d
6
+ Author: Hendrik Weideman
7
+ Author-email: hjweide@gmail.com
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: imageio
12
+ Requires-Dist: numpy
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
1
23
  [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
2
24
  [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
3
25
  [![PyPI version](https://badge.fury.io/py/pyastar2d.svg)](https://badge.fury.io/py/pyastar2d)
@@ -93,7 +115,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
93
115
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
94
116
  ```
95
117
 
96
- ### Small Maze (1802 x 1802):
118
+ ### Small Maze (1802 x 1802):
97
119
  ```bash
98
120
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
99
121
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -108,7 +130,7 @@ sys 0m0.606s
108
130
  The solution found for the small maze is shown below:
109
131
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
110
132
 
111
- ### Large Maze (4002 x 4002):
133
+ ### Large Maze (4002 x 4002):
112
134
  ```bash
113
135
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
114
136
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -148,12 +170,34 @@ pip install -r requirements-dev.txt
148
170
  ```
149
171
  before running
150
172
  ```bash
151
- py.test
173
+ pytest
152
174
  ```
153
175
  The tests are fairly basic but cover some of the
154
176
  more common pitfalls. Pull requests for more extensive tests are welcome.
155
177
 
178
+ ## Code Formatting
179
+
180
+ It's recommended that you use `pre-commit` to ensure linting procedures are
181
+ run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
182
+ more information.
183
+
184
+ Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
185
+ for software installation on your OS/platform. After you have the software
186
+ installed, run `pre-commit install` on the command line. Now every time you commit
187
+ to this project's code base the linter procedures will automatically run over the
188
+ changed files. To run pre-commit on files preemtively from the command line use:
189
+
190
+ ```bash
191
+ pip install -r requirements-dev.txt
192
+ pre-commit run --all-files
193
+ ```
194
+
195
+ The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
196
+ Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
197
+ use `flake8` as its Python linter, but pre-commit will ensure compliance before a
198
+ git commit is completed. This will use the `flake8` configuration within
199
+ `.flake8`, which ignores several errors and stylistic considerations.
200
+
156
201
  ## References
157
202
  1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
158
203
  2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
159
-
@@ -1,14 +1,18 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
+ pyproject.toml
4
5
  requirements.txt
6
+ setup.cfg
5
7
  setup.py
6
8
  src/cpp/astar.cpp
7
9
  src/cpp/experimental_heuristics.cpp
10
+ src/cpp/experimental_heuristics.h
8
11
  src/pyastar2d/__init__.py
9
12
  src/pyastar2d/astar_wrapper.py
10
13
  src/pyastar2d.egg-info/PKG-INFO
11
14
  src/pyastar2d.egg-info/SOURCES.txt
12
15
  src/pyastar2d.egg-info/dependency_links.txt
13
16
  src/pyastar2d.egg-info/requires.txt
14
- src/pyastar2d.egg-info/top_level.txt
17
+ src/pyastar2d.egg-info/top_level.txt
18
+ tests/test_astar.py
@@ -0,0 +1,156 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ import pyastar2d
5
+ from pyastar2d import Heuristic
6
+
7
+
8
+ def test_small():
9
+ weights = np.array(
10
+ [
11
+ [1, 3, 3, 3, 3],
12
+ [2, 1, 3, 3, 3],
13
+ [2, 2, 1, 3, 3],
14
+ [2, 2, 2, 1, 3],
15
+ [2, 2, 2, 2, 1],
16
+ ],
17
+ dtype=np.float32,
18
+ )
19
+ # Run down the diagonal.
20
+ path = pyastar2d.astar_path(weights, (0, 0), (4, 4), allow_diagonal=True)
21
+ expected = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]])
22
+
23
+ assert np.all(path == expected)
24
+
25
+ # Down, right, down, right, etc.
26
+ path = pyastar2d.astar_path(weights, (0, 0), (4, 4), allow_diagonal=False)
27
+ expected = np.array([[0, 0], [1, 0], [1, 1], [2, 1], [2, 2], [3, 2], [3, 3], [4, 3], [4, 4]])
28
+
29
+ assert np.all(path == expected)
30
+
31
+
32
+ def test_no_solution():
33
+ # Vertical wall.
34
+ weights = np.ones((5, 5), dtype=np.float32)
35
+ weights[:, 2] = np.inf
36
+
37
+ path = pyastar2d.astar_path(weights, (0, 0), (4, 4), allow_diagonal=True)
38
+ assert not path
39
+
40
+ # Horizontal wall.
41
+ weights = np.ones((5, 5), dtype=np.float32)
42
+ weights[2, :] = np.inf
43
+
44
+ path = pyastar2d.astar_path(weights, (0, 0), (4, 4), allow_diagonal=True)
45
+ assert not path
46
+
47
+
48
+ def test_match_reverse():
49
+ # Might fail if there are multiple paths, but this should be rare.
50
+ h, w = 25, 25
51
+ weights = (1.0 + 5.0 * np.random.random((h, w))).astype(np.float32)
52
+
53
+ fwd = pyastar2d.astar_path(weights, (0, 0), (h - 1, w - 1))
54
+ rev = pyastar2d.astar_path(weights, (h - 1, w - 1), (0, 0))
55
+
56
+ assert np.all(fwd[::-1] == rev)
57
+
58
+ fwd = pyastar2d.astar_path(weights, (0, 0), (h - 1, w - 1), allow_diagonal=True)
59
+ rev = pyastar2d.astar_path(weights, (h - 1, w - 1), (0, 0), allow_diagonal=True)
60
+
61
+ assert np.all(fwd[::-1] == rev)
62
+
63
+
64
+ def test_narrow():
65
+ # Column weights.
66
+ weights = np.ones((5, 1), dtype=np.float32)
67
+ path = pyastar2d.astar_path(weights, (0, 0), (4, 0))
68
+
69
+ expected = np.array([[0, 0], [1, 0], [2, 0], [3, 0], [4, 0]])
70
+
71
+ assert np.all(path == expected)
72
+
73
+ # Row weights.
74
+ weights = np.ones((1, 5), dtype=np.float32)
75
+ path = pyastar2d.astar_path(weights, (0, 0), (0, 4))
76
+
77
+ expected = np.array([[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]])
78
+
79
+ assert np.all(path == expected)
80
+
81
+
82
+ def test_bad_heuristic():
83
+ # For valid heuristics, the cost to move must be at least 1.
84
+ weights = (1.0 + 5.0 * np.random.random((10, 10))).astype(np.float32)
85
+ # An element smaller than 1 should raise a ValueError.
86
+ bad_cost = np.random.random() / 2.0
87
+ weights[4, 4] = bad_cost
88
+
89
+ with pytest.raises(ValueError) as exc:
90
+ pyastar2d.astar_path(weights, (0, 0), (9, 9))
91
+ assert '.f' % bad_cost in exc.value.args[0]
92
+
93
+
94
+ def test_invalid_start_and_goal():
95
+ weights = (1.0 + 5.0 * np.random.random((10, 10))).astype(np.float32)
96
+ # Test bad start indices.
97
+ with pytest.raises(ValueError) as exc:
98
+ pyastar2d.astar_path(weights, (-1, 0), (9, 9))
99
+ assert '-1' in exc.value.args[0]
100
+ with pytest.raises(ValueError) as exc:
101
+ pyastar2d.astar_path(weights, (10, 0), (9, 9))
102
+ assert '10' in exc.value.args[0]
103
+ with pytest.raises(ValueError) as exc:
104
+ pyastar2d.astar_path(weights, (0, -1), (9, 9))
105
+ assert '-1' in exc.value.args[0]
106
+ with pytest.raises(ValueError) as exc:
107
+ pyastar2d.astar_path(weights, (0, 10), (9, 9))
108
+ assert '10' in exc.value.args[0]
109
+ # Test bad goal indices.
110
+ with pytest.raises(ValueError) as exc:
111
+ pyastar2d.astar_path(weights, (0, 0), (-1, 9))
112
+ assert '-1' in exc.value.args[0]
113
+ with pytest.raises(ValueError) as exc:
114
+ pyastar2d.astar_path(weights, (0, 0), (10, 9))
115
+ assert '10' in exc.value.args[0]
116
+ with pytest.raises(ValueError) as exc:
117
+ pyastar2d.astar_path(weights, (0, 0), (0, -1))
118
+ assert '-1' in exc.value.args[0]
119
+ with pytest.raises(ValueError) as exc:
120
+ pyastar2d.astar_path(weights, (0, 0), (0, 10))
121
+ assert '10' in exc.value.args[0]
122
+
123
+
124
+ def test_bad_weights_dtype():
125
+ weights = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]], dtype=np.float64)
126
+ with pytest.raises(AssertionError) as exc:
127
+ pyastar2d.astar_path(weights, (0, 0), (2, 2))
128
+ assert 'float64' in exc.value.args[0]
129
+
130
+
131
+ def test_orthogonal_x():
132
+ weights = np.ones((5, 5), dtype=np.float32)
133
+ path = pyastar2d.astar_path(
134
+ weights,
135
+ (0, 0),
136
+ (4, 4),
137
+ allow_diagonal=False,
138
+ heuristic_override=Heuristic.ORTHOGONAL_X,
139
+ )
140
+ expected = np.array([[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [3, 4], [4, 4]])
141
+
142
+ assert np.all(path == expected)
143
+
144
+
145
+ def test_orthogonal_y():
146
+ weights = np.ones((5, 5), dtype=np.float32)
147
+ path = pyastar2d.astar_path(
148
+ weights,
149
+ (0, 0),
150
+ (4, 4),
151
+ allow_diagonal=False,
152
+ heuristic_override=Heuristic.ORTHOGONAL_Y,
153
+ )
154
+ expected = np.array([[0, 0], [0, 1], [0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [4, 3], [4, 4]])
155
+
156
+ assert np.all(path == expected)
@@ -1 +0,0 @@
1
- include requirements.txt
pyastar2d-1.0.5/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
pyastar2d-1.0.5/setup.py DELETED
@@ -1,44 +0,0 @@
1
- import setuptools
2
- from distutils.core import Extension
3
- from setuptools import dist
4
- dist.Distribution().fetch_build_eggs(["numpy"])
5
- import numpy
6
-
7
- astar_module = Extension(
8
- 'pyastar2d.astar', sources=['src/cpp/astar.cpp', 'src/cpp/experimental_heuristics.cpp'],
9
- include_dirs=[
10
- numpy.get_include(), # for numpy/arrayobject.h
11
- 'src/cpp' # for experimental_heuristics.h
12
- ],
13
- extra_compile_args=["-O3", "-Wall", "-shared", "-fpic"],
14
- )
15
-
16
-
17
- with open("requirements.txt", "r") as fh:
18
- install_requires = fh.readlines()
19
-
20
- with open("README.md", "r") as fh:
21
- long_description = fh.read()
22
-
23
- setuptools.setup(
24
- name="pyastar2d",
25
- version="1.0.5",
26
- author="Hendrik Weideman",
27
- author_email="hjweide@gmail.com",
28
- description=(
29
- "A simple implementation of the A* algorithm for "
30
- "path-finding on a two-dimensional grid."),
31
- long_description=long_description,
32
- long_description_content_type="text/markdown",
33
- url="https://github.com/hjweide/pyastar2d",
34
- install_requires=install_requires,
35
- packages=setuptools.find_packages(where="src", exclude=("tests",)),
36
- package_dir={"": "src"},
37
- ext_modules=[astar_module],
38
- classifiers=[
39
- "Programming Language :: Python :: 3",
40
- "License :: OSI Approved :: MIT License",
41
- "Operating System :: OS Independent",
42
- ],
43
- python_requires='>=3.7',
44
- )
@@ -1,2 +0,0 @@
1
- from pyastar2d.astar_wrapper import astar_path, Heuristic
2
- __all__ = ["astar_path", "Heuristic"]
@@ -1,73 +0,0 @@
1
- import ctypes
2
- import numpy as np
3
- import pyastar2d.astar
4
- from enum import IntEnum
5
- from typing import Optional, Tuple
6
-
7
-
8
- # Define array types
9
- ndmat_f_type = np.ctypeslib.ndpointer(
10
- dtype=np.float32, ndim=1, flags="C_CONTIGUOUS")
11
- ndmat_i2_type = np.ctypeslib.ndpointer(
12
- dtype=np.int32, ndim=2, flags="C_CONTIGUOUS")
13
-
14
- # Define input/output types
15
- pyastar2d.astar.restype = ndmat_i2_type # Nx2 (i, j) coordinates or None
16
- pyastar2d.astar.argtypes = [
17
- ndmat_f_type, # weights
18
- ctypes.c_int, # height
19
- ctypes.c_int, # width
20
- ctypes.c_int, # start index in flattened grid
21
- ctypes.c_int, # goal index in flattened grid
22
- ctypes.c_bool, # allow diagonal
23
- ctypes.c_int, # heuristic_override
24
- ]
25
-
26
- class Heuristic(IntEnum):
27
- """The supported heuristics."""
28
-
29
- DEFAULT = 0
30
- ORTHOGONAL_X = 1
31
- ORTHOGONAL_Y = 2
32
-
33
- def astar_path(
34
- weights: np.ndarray,
35
- start: Tuple[int, int],
36
- goal: Tuple[int, int],
37
- allow_diagonal: bool = False,
38
- heuristic_override: Heuristic = Heuristic.DEFAULT) -> Optional[np.ndarray]:
39
- """
40
- Run astar algorithm on 2d weights.
41
-
42
- param np.ndarray weights: A grid of weights e.g. np.ones((10, 10), dtype=np.float32)
43
- param Tuple[int, int] start: (i, j)
44
- param Tuple[int, int] goal: (i, j)
45
- param bool allow_diagonal: Whether to allow diagonal moves
46
- param Heuristic heuristic_override: Override heuristic, see Heuristic(IntEnum)
47
-
48
- """
49
- assert weights.dtype == np.float32, (
50
- f"weights must have np.float32 data type, but has {weights.dtype}"
51
- )
52
- # For the heuristic to be valid, each move must cost at least 1.
53
- if weights.min(axis=None) < 1.:
54
- raise ValueError("Minimum cost to move must be 1, but got %f" % (
55
- weights.min(axis=None)))
56
- # Ensure start is within bounds.
57
- if (start[0] < 0 or start[0] >= weights.shape[0] or
58
- start[1] < 0 or start[1] >= weights.shape[1]):
59
- raise ValueError(f"Start of {start} lies outside grid.")
60
- # Ensure goal is within bounds.
61
- if (goal[0] < 0 or goal[0] >= weights.shape[0] or
62
- goal[1] < 0 or goal[1] >= weights.shape[1]):
63
- raise ValueError(f"Goal of {goal} lies outside grid.")
64
-
65
- height, width = weights.shape
66
- start_idx = np.ravel_multi_index(start, (height, width))
67
- goal_idx = np.ravel_multi_index(goal, (height, width))
68
-
69
- path = pyastar2d.astar.astar(
70
- weights.flatten(), height, width, start_idx, goal_idx, allow_diagonal,
71
- int(heuristic_override)
72
- )
73
- return path
File without changes
File without changes