pyastar2d 1.0.6__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.
@@ -1,16 +1,24 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyastar2d
3
- Version: 1.0.6
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
- 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.8
12
9
  Description-Content-Type: text/markdown
13
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
14
22
 
15
23
  [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
16
24
  [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
@@ -107,7 +115,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
107
115
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
108
116
  ```
109
117
 
110
- ### Small Maze (1802 x 1802):
118
+ ### Small Maze (1802 x 1802):
111
119
  ```bash
112
120
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
113
121
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -122,7 +130,7 @@ sys 0m0.606s
122
130
  The solution found for the small maze is shown below:
123
131
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
124
132
 
125
- ### Large Maze (4002 x 4002):
133
+ ### Large Maze (4002 x 4002):
126
134
  ```bash
127
135
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
128
136
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -162,12 +170,34 @@ pip install -r requirements-dev.txt
162
170
  ```
163
171
  before running
164
172
  ```bash
165
- py.test
173
+ pytest
166
174
  ```
167
175
  The tests are fairly basic but cover some of the
168
176
  more common pitfalls. Pull requests for more extensive tests are welcome.
169
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
+
170
201
  ## References
171
202
  1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
172
203
  2. [Pathfinding with A* on Red Blob Games](http://www.redblobgames.com/pathfinding/a-star/introduction.html)
173
-
@@ -1,17 +1,3 @@
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
1
  [![Build Status](https://travis-ci.com/hjweide/pyastar2d.svg?branch=master)](https://travis-ci.com/hjweide/pyastar2d)
16
2
  [![Coverage Status](https://coveralls.io/repos/github/hjweide/pyastar2d/badge.svg?branch=master)](https://coveralls.io/github/hjweide/pyastar2d?branch=master)
17
3
  [![PyPI version](https://badge.fury.io/py/pyastar2d.svg)](https://badge.fury.io/py/pyastar2d)
@@ -107,7 +93,7 @@ python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_
107
93
  python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
108
94
  ```
109
95
 
110
- ### Small Maze (1802 x 1802):
96
+ ### Small Maze (1802 x 1802):
111
97
  ```bash
112
98
  time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
113
99
  Loaded maze of shape (1802, 1802) from mazes/maze_small.png
@@ -122,7 +108,7 @@ sys 0m0.606s
122
108
  The solution found for the small maze is shown below:
123
109
  <img src="https://github.com/hjweide/pyastar2d/raw/master/solns/maze_small_soln.png" alt="Maze Small Solution" style="width: 100%"/>
124
110
 
125
- ### Large Maze (4002 x 4002):
111
+ ### Large Maze (4002 x 4002):
126
112
  ```bash
127
113
  time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
128
114
  Loaded maze of shape (4002, 4002) from mazes/maze_large.png
@@ -162,12 +148,34 @@ pip install -r requirements-dev.txt
162
148
  ```
163
149
  before running
164
150
  ```bash
165
- py.test
151
+ pytest
166
152
  ```
167
153
  The tests are fairly basic but cover some of the
168
154
  more common pitfalls. Pull requests for more extensive tests are welcome.
169
155
 
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:
167
+
168
+ ```bash
169
+ pip install -r requirements-dev.txt
170
+ pre-commit run --all-files
171
+ ```
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.
178
+
170
179
  ## References
171
180
  1. [A\* search algorithm on Wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode)
172
181
  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"]
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,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,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,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)
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
File without changes
File without changes
File without changes