pyastar2d 1.0.6__tar.gz → 1.1.1__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.
@@ -1,19 +1,26 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyastar2d
3
- Version: 1.0.6
3
+ Version: 1.1.1
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
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
8
+ Requires-Python: >=3.9
12
9
  Description-Content-Type: text/markdown
13
10
  License-File: LICENSE
14
-
15
- [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
16
- [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
11
+ Requires-Dist: imageio
12
+ Requires-Dist: numpy>=2.0.0
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
+
23
+ [![Build Status](https://github.com/hjweide/pyastar2d/actions/workflows/python-publish.yml/badge.svg)](https://github.com/hjweide/pyastar2d/actions/workflows/python-publish.yml)
17
24
  [![PyPI version](https://badge.fury.io/py/pyastar2d.svg)](https://badge.fury.io/py/pyastar2d)
18
25
  # PyAstar2D
19
26
  This is a very simple C++ implementation of the A\* algorithm for pathfinding
@@ -107,7 +114,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
107
114
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
108
115
  ```
109
116
 
110
- ### Small Maze (1802 x 1802):
117
+ ### Small Maze (1802 x 1802):
111
118
  ```bash
112
119
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
113
120
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -122,7 +129,7 @@ sys 0m0.606s
122
129
  The solution found for the small maze is shown below:
123
130
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
124
131
 
125
- ### Large Maze (4002 x 4002):
132
+ ### Large Maze (4002 x 4002):
126
133
  ```bash
127
134
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
128
135
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -162,12 +169,34 @@ pip install -r requirements-dev.txt
162
169
  ```
163
170
  before running
164
171
  ```bash
165
- py.test
172
+ pytest
166
173
  ```
167
174
  The tests are fairly basic but cover some of the
168
175
  more common pitfalls. Pull requests for more extensive tests are welcome.
169
176
 
177
+ ## Code Formatting
178
+
179
+ It's recommended that you use `pre-commit` to ensure linting procedures are
180
+ run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
181
+ more information.
182
+
183
+ Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
184
+ for software installation on your OS/platform. After you have the software
185
+ installed, run `pre-commit install` on the command line. Now every time you commit
186
+ to this project's code base the linter procedures will automatically run over the
187
+ changed files. To run pre-commit on files preemtively from the command line use:
188
+
189
+ ```bash
190
+ pip install -r requirements-dev.txt
191
+ pre-commit run --all-files
192
+ ```
193
+
194
+ The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
195
+ Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
196
+ use `flake8` as its Python linter, but pre-commit will ensure compliance before a
197
+ git commit is completed. This will use the `flake8` configuration within
198
+ `.flake8`, which ignores several errors and stylistic considerations.
199
+
170
200
  ## References
171
201
  1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
172
202
  2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
173
-
@@ -1,19 +1,4 @@
1
- Metadata-Version: 2.1
2
- Name: pyastar2d
3
- Version: 1.0.6
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
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
-
15
- [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
16
- [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
1
+ [![Build Status](https://github.com/hjweide/pyastar2d/actions/workflows/python-publish.yml/badge.svg)](https://github.com/hjweide/pyastar2d/actions/workflows/python-publish.yml)
17
2
  [![PyPI version](https://badge.fury.io/py/pyastar2d.svg)](https://badge.fury.io/py/pyastar2d)
18
3
  # PyAstar2D
19
4
  This is a very simple C++ implementation of the A\* algorithm for pathfinding
@@ -107,7 +92,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
107
92
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
108
93
  ```
109
94
 
110
- ### Small Maze (1802 x 1802):
95
+ ### Small Maze (1802 x 1802):
111
96
  ```bash
112
97
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
113
98
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -122,7 +107,7 @@ sys 0m0.606s
122
107
  The solution found for the small maze is shown below:
123
108
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
124
109
 
125
- ### Large Maze (4002 x 4002):
110
+ ### Large Maze (4002 x 4002):
126
111
  ```bash
127
112
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
128
113
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -162,12 +147,34 @@ pip install -r requirements-dev.txt
162
147
  ```
163
148
  before running
164
149
  ```bash
165
- py.test
150
+ pytest
166
151
  ```
167
152
  The tests are fairly basic but cover some of the
168
153
  more common pitfalls. Pull requests for more extensive tests are welcome.
169
154
 
155
+ ## Code Formatting
156
+
157
+ It's recommended that you use `pre-commit` to ensure linting procedures are
158
+ run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
159
+ more information.
160
+
161
+ Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
162
+ for software installation on your OS/platform. After you have the software
163
+ installed, run `pre-commit install` on the command line. Now every time you commit
164
+ to this project's code base the linter procedures will automatically run over the
165
+ changed files. To run pre-commit on files preemtively from the command line use:
166
+
167
+ ```bash
168
+ pip install -r requirements-dev.txt
169
+ pre-commit run --all-files
170
+ ```
171
+
172
+ The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
173
+ Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
174
+ use `flake8` as its Python linter, but pre-commit will ensure compliance before a
175
+ git commit is completed. This will use the `flake8` configuration within
176
+ `.flake8`, which ignores several errors and stylistic considerations.
177
+
170
178
  ## References
171
179
  1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
172
180
  2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
173
-
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel", "numpy>=2.0.0"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,2 @@
1
+ imageio
2
+ numpy>=2.0.0
@@ -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,59 @@
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', '0x03090000'),
31
+ ('NPY_NO_DEPRECATED_API', 'NPY_2_0_API_VERSION'),
32
+ ('NPY_TARGET_VERSION', 'NPY_2_0_API_VERSION'),
33
+ ],
34
+ py_limited_api=True,
35
+ include_dirs=[
36
+ 'src/cpp',
37
+ get_numpy_include(),
38
+ ],
39
+ extra_compile_args=['-O3', '-fpic', '-Wall'],
40
+ language='c++',
41
+ )
42
+
43
+ # Define package metadata
44
+ setup(
45
+ name='pyastar2d',
46
+ version='1.1.1',
47
+ author='Hendrik Weideman',
48
+ author_email='hjweide@gmail.com',
49
+ description='A simple implementation of the A* algorithm for path-finding on a two-dimensional grid.',
50
+ long_description=long_description,
51
+ long_description_content_type='text/markdown',
52
+ url='https://github.com/hjweide/pyastar2d',
53
+ packages=find_packages(where='src', exclude=('tests',)),
54
+ package_dir={'': 'src'},
55
+ install_requires=install_requires,
56
+ python_requires='>=3.9',
57
+ ext_modules=[astar_module],
58
+ options={'bdist_wheel': {'py_limited_api': 'cp39'}},
59
+ )
@@ -44,7 +44,7 @@ inline float l1_norm(int i0, int j0, int i1, int j1) {
44
44
  // diag_ok: if true, allows diagonal moves (8-conn.)
45
45
  // paths (output): for each node, stores previous node in path
46
46
  static PyObject *astar(PyObject *self, PyObject *args) {
47
- const PyArrayObject* weights_object;
47
+ PyArrayObject* weights_object;
48
48
  int h;
49
49
  int w;
50
50
  int start;
@@ -61,7 +61,7 @@ static PyObject *astar(PyObject *self, PyObject *args) {
61
61
  ))
62
62
  return NULL;
63
63
 
64
- float* weights = (float*) weights_object->data;
64
+ float* weights = (float*) PyArray_DATA(weights_object);
65
65
  int* paths = new int[h * w];
66
66
  int path_length = -1;
67
67
 
@@ -76,7 +76,7 @@ static PyObject *astar(PyObject *self, PyObject *args) {
76
76
  nodes_to_visit.push(start_node);
77
77
 
78
78
  int* nbrs = new int[8];
79
-
79
+
80
80
  int goal_i = goal / w;
81
81
  int goal_j = goal % w;
82
82
  int start_i = start / w;
@@ -141,11 +141,13 @@ static PyObject *astar(PyObject *self, PyObject *args) {
141
141
  if (path_length >= 0) {
142
142
  npy_intp dims[2] = {path_length, 2};
143
143
  PyArrayObject* path = (PyArrayObject*) PyArray_SimpleNew(2, dims, NPY_INT32);
144
+ char* data = (char*) PyArray_BYTES(path);
145
+ npy_intp* strides = PyArray_STRIDES(path);
144
146
  npy_int32 *iptr, *jptr;
145
147
  int idx = goal;
146
148
  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]);
149
+ iptr = (npy_int32*) (data + i * strides[0]);
150
+ jptr = (npy_int32*) (data + i * strides[0] + strides[1]);
149
151
 
150
152
  *iptr = idx / w;
151
153
  *jptr = idx % w;
@@ -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,5 +1,26 @@
1
- [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
2
- [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
1
+ Metadata-Version: 2.4
2
+ Name: pyastar2d
3
+ Version: 1.1.1
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.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: imageio
12
+ Requires-Dist: numpy>=2.0.0
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
+
23
+ [![Build Status](https://github.com/hjweide/pyastar2d/actions/workflows/python-publish.yml/badge.svg)](https://github.com/hjweide/pyastar2d/actions/workflows/python-publish.yml)
3
24
  [![PyPI version](https://badge.fury.io/py/pyastar2d.svg)](https://badge.fury.io/py/pyastar2d)
4
25
  # PyAstar2D
5
26
  This is a very simple C++ implementation of the A\* algorithm for pathfinding
@@ -93,7 +114,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
93
114
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
94
115
  ```
95
116
 
96
- ### Small Maze (1802 x 1802):
117
+ ### Small Maze (1802 x 1802):
97
118
  ```bash
98
119
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
99
120
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -108,7 +129,7 @@ sys 0m0.606s
108
129
  The solution found for the small maze is shown below:
109
130
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
110
131
 
111
- ### Large Maze (4002 x 4002):
132
+ ### Large Maze (4002 x 4002):
112
133
  ```bash
113
134
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
114
135
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -148,12 +169,34 @@ pip install -r requirements-dev.txt
148
169
  ```
149
170
  before running
150
171
  ```bash
151
- py.test
172
+ pytest
152
173
  ```
153
174
  The tests are fairly basic but cover some of the
154
175
  more common pitfalls. Pull requests for more extensive tests are welcome.
155
176
 
177
+ ## Code Formatting
178
+
179
+ It's recommended that you use `pre-commit` to ensure linting procedures are
180
+ run on any code you write. See [pre-commit.com](https://pre-commit.com/) for
181
+ more information.
182
+
183
+ Reference [pre-commit's installation instructions](https://pre-commit.com/#install)
184
+ for software installation on your OS/platform. After you have the software
185
+ installed, run `pre-commit install` on the command line. Now every time you commit
186
+ to this project's code base the linter procedures will automatically run over the
187
+ changed files. To run pre-commit on files preemtively from the command line use:
188
+
189
+ ```bash
190
+ pip install -r requirements-dev.txt
191
+ pre-commit run --all-files
192
+ ```
193
+
194
+ The code base has been formatted by [Black](https://black.readthedocs.io/en/stable/).
195
+ Furthermore, try to conform to `PEP8`. You should set up your preferred editor to
196
+ use `flake8` as its Python linter, but pre-commit will ensure compliance before a
197
+ git commit is completed. This will use the `flake8` configuration within
198
+ `.flake8`, which ignores several errors and stylistic considerations.
199
+
156
200
  ## References
157
201
  1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
158
202
  2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
159
-
@@ -1,7 +1,9 @@
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
@@ -12,4 +14,5 @@ src/pyastar2d.egg-info/PKG-INFO
12
14
  src/pyastar2d.egg-info/SOURCES.txt
13
15
  src/pyastar2d.egg-info/dependency_links.txt
14
16
  src/pyastar2d.egg-info/requires.txt
15
- src/pyastar2d.egg-info/top_level.txt
17
+ src/pyastar2d.egg-info/top_level.txt
18
+ tests/test_astar.py
@@ -0,0 +1,2 @@
1
+ imageio
2
+ numpy>=2.0.0
@@ -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,2 +0,0 @@
1
- imageio
2
- numpy
pyastar2d-1.0.6/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
pyastar2d-1.0.6/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.6",
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
@@ -1,2 +0,0 @@
1
- imageio
2
- numpy
File without changes
File without changes