pyastar2d 1.1.0__cp38-abi3-win32.whl
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.
- pyastar2d/__init__.py +3 -0
- pyastar2d/astar.pyd +0 -0
- pyastar2d/astar_wrapper.py +77 -0
- pyastar2d-1.1.0.dist-info/LICENSE +21 -0
- pyastar2d-1.1.0.dist-info/METADATA +194 -0
- pyastar2d-1.1.0.dist-info/RECORD +8 -0
- pyastar2d-1.1.0.dist-info/WHEEL +5 -0
- pyastar2d-1.1.0.dist-info/top_level.txt +1 -0
pyastar2d/__init__.py
ADDED
pyastar2d/astar.pyd
ADDED
|
Binary file
|
|
@@ -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
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Hendrik Weideman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
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
|
+
|
|
14
|
+
[](https://travis-ci.com/hjweide/pyastar2d)
|
|
15
|
+
[](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
|
|
16
|
+
[](https://badge.fury.io/py/pyastar2d)
|
|
17
|
+
# PyAstar2D
|
|
18
|
+
This is a very simple C++ implementation of the A\* algorithm for pathfinding
|
|
19
|
+
on a two-dimensional grid. The solver itself is implemented in C++, but is
|
|
20
|
+
callable from Python. This combines the speed of C++ with the convenience of
|
|
21
|
+
Python.
|
|
22
|
+
|
|
23
|
+
I have not done any formal benchmarking, but the solver finds the solution to a
|
|
24
|
+
1802 by 1802 maze in 0.29s and a 4008 by 4008 maze in 0.83s when running on my
|
|
25
|
+
nine-year-old Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz. See [Example
|
|
26
|
+
Results](#example-results) for more details.
|
|
27
|
+
|
|
28
|
+
See `src/cpp/astar.cpp` for the core C++ implementation of the A\* shortest
|
|
29
|
+
path search algorithm, `src/pyastar2d/astar_wrapper.py` for the Python wrapper
|
|
30
|
+
and `examples/example.py` for example usage.
|
|
31
|
+
|
|
32
|
+
When determining legal moves, 4-connectivity is the default, but it is possible
|
|
33
|
+
to set `allow_diagonal=True` for 8-connectivity.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
Instructions for installing `pyastar2d` are given below.
|
|
37
|
+
|
|
38
|
+
### From PyPI
|
|
39
|
+
The easiest way to install `pyastar2d` is directly from the Python package index:
|
|
40
|
+
```
|
|
41
|
+
pip install pyastar2d
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### From source
|
|
45
|
+
You can also install `pyastar2d` by cloning this repository and building it
|
|
46
|
+
yourself. If running on Linux or MacOS, simply run
|
|
47
|
+
```bash
|
|
48
|
+
pip install .
|
|
49
|
+
````
|
|
50
|
+
from the root directory. If you are using Windows you may have to install Cython manually first:
|
|
51
|
+
```bash
|
|
52
|
+
pip install Cython
|
|
53
|
+
pip install .
|
|
54
|
+
```
|
|
55
|
+
To check that everything worked, run the example:
|
|
56
|
+
```bash
|
|
57
|
+
python examples/example.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### As a dependency
|
|
61
|
+
Include `pyastar2d` in your `requirements.txt` to install from `pypi`, or add
|
|
62
|
+
this line to `requirements.txt`:
|
|
63
|
+
```
|
|
64
|
+
pyastar2d @ git+git://github.com/hjweide/pyastar2d.git@master#egg=pyastar2d
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
A simple example is given below:
|
|
69
|
+
```python
|
|
70
|
+
import numpy as np
|
|
71
|
+
import pyastar2d
|
|
72
|
+
# The minimum cost must be 1 for the heuristic to be valid.
|
|
73
|
+
# The weights array must have np.float32 dtype to be compatible with the C++ code.
|
|
74
|
+
weights = np.array([[1, 3, 3, 3, 3],
|
|
75
|
+
[2, 1, 3, 3, 3],
|
|
76
|
+
[2, 2, 1, 3, 3],
|
|
77
|
+
[2, 2, 2, 1, 3],
|
|
78
|
+
[2, 2, 2, 2, 1]], dtype=np.float32)
|
|
79
|
+
# The start and goal coordinates are in matrix coordinates (i, j).
|
|
80
|
+
path = pyastar2d.astar_path(weights, (0, 0), (4, 4), allow_diagonal=True)
|
|
81
|
+
print(path)
|
|
82
|
+
# The path is returned as a numpy array of (i, j) coordinates.
|
|
83
|
+
array([[0, 0],
|
|
84
|
+
[1, 1],
|
|
85
|
+
[2, 2],
|
|
86
|
+
[3, 3],
|
|
87
|
+
[4, 4]])
|
|
88
|
+
```
|
|
89
|
+
Note that all grid points are represented as `(i, j)` coordinates. An example
|
|
90
|
+
of using `pyastar2d` to solve a maze is given in `examples/maze_solver.py`.
|
|
91
|
+
|
|
92
|
+
## Example Results
|
|
93
|
+
<a name="example-results"></a>
|
|
94
|
+
To test the implementation, I grabbed two nasty mazes from Wikipedia. They are
|
|
95
|
+
included in the ```mazes``` directory, but are originally from here:
|
|
96
|
+
[Small](https://upload.wikimedia.org/wikipedia/commons/c/cf/MAZE.png) and
|
|
97
|
+
[Large](https://upload.wikimedia.org/wikipedia/commons/3/32/MAZE_2000x2000_DFS.png).
|
|
98
|
+
I load the ```.png``` files as grayscale images, and set the white pixels to 1
|
|
99
|
+
(open space) and the black pixels to `INF` (walls).
|
|
100
|
+
|
|
101
|
+
To run the examples specify the input and output files using the `--input` and
|
|
102
|
+
`--output` flags. For example, the following commands will solve the small and
|
|
103
|
+
large mazes:
|
|
104
|
+
```
|
|
105
|
+
python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
|
|
106
|
+
python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Small Maze (1802 x 1802):
|
|
110
|
+
```bash
|
|
111
|
+
time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
|
|
112
|
+
Loaded maze of shape (1802, 1802) from mazes/maze_small.png
|
|
113
|
+
Found path of length 10032 in 0.292794s
|
|
114
|
+
Plotting path to solns/maze_small.png
|
|
115
|
+
Done
|
|
116
|
+
|
|
117
|
+
real 0m1.214s
|
|
118
|
+
user 0m1.526s
|
|
119
|
+
sys 0m0.606s
|
|
120
|
+
```
|
|
121
|
+
The solution found for the small maze is shown below:
|
|
122
|
+
<img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
|
|
123
|
+
|
|
124
|
+
### Large Maze (4002 x 4002):
|
|
125
|
+
```bash
|
|
126
|
+
time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
|
|
127
|
+
Loaded maze of shape (4002, 4002) from mazes/maze_large.png
|
|
128
|
+
Found path of length 783737 in 0.829181s
|
|
129
|
+
Plotting path to solns/maze_large.png
|
|
130
|
+
Done
|
|
131
|
+
|
|
132
|
+
real 0m29.385s
|
|
133
|
+
user 0m29.563s
|
|
134
|
+
sys 0m0.728s
|
|
135
|
+
```
|
|
136
|
+
The solution found for the large maze is shown below:
|
|
137
|
+
<img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_large_soln.png" alt="Maze Large Solution" style="width: 100%"/>
|
|
138
|
+
|
|
139
|
+
## Motivation
|
|
140
|
+
I recently needed an implementation of the A* algorithm in Python to find the
|
|
141
|
+
shortest path between two points in a cost matrix representing an image.
|
|
142
|
+
Normally I would simply use [networkx](https://networkx.github.io/), but for
|
|
143
|
+
graphs with millions of nodes the overhead incurred to construct the graph can
|
|
144
|
+
be expensive. Considering that I was only interested in graphs that may be
|
|
145
|
+
represented as two-dimensional grids, I decided to implement it myself using
|
|
146
|
+
this special structure of the graph to make various optimizations.
|
|
147
|
+
Specifically, the graph is represented as a one-dimensional array because there
|
|
148
|
+
is no need to store the neighbors. Additionally, the lookup tables for
|
|
149
|
+
previously-explored nodes (their costs and paths) are also stored as
|
|
150
|
+
one-dimensional arrays. The implication of this is that checking the lookup
|
|
151
|
+
table can be done in O(1), at the cost of using O(n) memory. Alternatively, we
|
|
152
|
+
could store only the nodes we traverse in a hash table to reduce the memory
|
|
153
|
+
usage. Empirically I found that replacing the one-dimensional array with a
|
|
154
|
+
hash table (`std::unordered_map`) was about five times slower.
|
|
155
|
+
|
|
156
|
+
## Tests
|
|
157
|
+
The default installation does not include the dependencies necessary to run the
|
|
158
|
+
tests. To install these, first run
|
|
159
|
+
```bash
|
|
160
|
+
pip install -r requirements-dev.txt
|
|
161
|
+
```
|
|
162
|
+
before running
|
|
163
|
+
```bash
|
|
164
|
+
pytest
|
|
165
|
+
```
|
|
166
|
+
The tests are fairly basic but cover some of the
|
|
167
|
+
more common pitfalls. Pull requests for more extensive tests are welcome.
|
|
168
|
+
|
|
169
|
+
## Code Formatting
|
|
170
|
+
|
|
171
|
+
It's recommended that you use `pre-commit` to ensure linting procedures are
|
|
172
|
+
run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
|
|
173
|
+
more information.
|
|
174
|
+
|
|
175
|
+
Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
|
|
176
|
+
for software installation on your OS/platform. After you have the software
|
|
177
|
+
installed, run `pre-commit install` on the command line. Now every time you commit
|
|
178
|
+
to this project's code base the linter procedures will automatically run over the
|
|
179
|
+
changed files. To run pre-commit on files preemtively from the command line use:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install -r requirements-dev.txt
|
|
183
|
+
pre-commit run --all-files
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
|
|
187
|
+
Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
|
|
188
|
+
use `flake8` as its Python linter, but pre-commit will ensure compliance before a
|
|
189
|
+
git commit is completed. This will use the `flake8` configuration within
|
|
190
|
+
`.flake8`, which ignores several errors and stylistic considerations.
|
|
191
|
+
|
|
192
|
+
## References
|
|
193
|
+
1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
|
|
194
|
+
2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pyastar2d/__init__.py,sha256=ZSvUMORHx4Oe9Kc0GtTPikG1xrPOcAuTx6Y5ILLc9Ro,100
|
|
2
|
+
pyastar2d/astar.pyd,sha256=22BnFTa5aYe0u5pDMrmzeLJw4WtnKNBusRz0NWz2YX0,15360
|
|
3
|
+
pyastar2d/astar_wrapper.py,sha256=A2FAv8giImZI7kS6hHv3uhi_lNXltZFRLjkQmd40GpI,2582
|
|
4
|
+
pyastar2d-1.1.0.dist-info/LICENSE,sha256=K_43dNtPdcb9Q_ATac63YlNaMoPT9qCmGRw2Cosaqck,1094
|
|
5
|
+
pyastar2d-1.1.0.dist-info/METADATA,sha256=DNDLPZgXedPhhh_QQW9tjPoEs5C-wInYT_3CQAfz7KU,8155
|
|
6
|
+
pyastar2d-1.1.0.dist-info/WHEEL,sha256=2948yAdMW9J7u21gwLu4map8Xjwppzvwh0_nm01Yank,95
|
|
7
|
+
pyastar2d-1.1.0.dist-info/top_level.txt,sha256=sirwTJoF4I2DthdhahGwb3hQxZRT8XbOhsNRdorJmXM,10
|
|
8
|
+
pyastar2d-1.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyastar2d
|