Giter Club home page Giter Club logo

pyastar2d's Introduction

Build Status Coverage Status PyPI version

PyAstar2D

This is a very simple C++ implementation of the A* algorithm for pathfinding on a two-dimensional grid. The solver itself is implemented in C++, but is callable from Python. This combines the speed of C++ with the convenience of Python.

I have not done any formal benchmarking, but the solver finds the solution to a 1802 by 1802 maze in 0.29s and a 4008 by 4008 maze in 0.83s when running on my nine-year-old Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz. See Example Results for more details.

See src/cpp/astar.cpp for the core C++ implementation of the A* shortest path search algorithm, src/pyastar2d/astar_wrapper.py for the Python wrapper and examples/example.py for example usage.

When determining legal moves, 4-connectivity is the default, but it is possible to set allow_diagonal=True for 8-connectivity.

Installation

Instructions for installing pyastar2d are given below.

From PyPI

The easiest way to install pyastar2d is directly from the Python package index:

pip install pyastar2d

From source

You can also install pyastar2d by cloning this repository and building it yourself. If running on Linux or MacOS, simply run

pip install .

from the root directory. If you are using Windows you may have to install Cython manually first:

pip install Cython
pip install .

To check that everything worked, run the example:

python examples/example.py

As a dependency

Include pyastar2d in your requirements.txt to install from pypi, or add this line to requirements.txt:

pyastar2d @ git+git://github.com/hjweide/pyastar2d.git@master#egg=pyastar2d

Usage

A simple example is given below:

import numpy as np
import pyastar2d
# The minimum cost must be 1 for the heuristic to be valid.
# The weights array must have np.float32 dtype to be compatible with the C++ code.
weights = np.array([[1, 3, 3, 3, 3],
                    [2, 1, 3, 3, 3],
                    [2, 2, 1, 3, 3],
                    [2, 2, 2, 1, 3],
                    [2, 2, 2, 2, 1]], dtype=np.float32)
# The start and goal coordinates are in matrix coordinates (i, j).
path = pyastar2d.astar_path(weights, (0, 0), (4, 4), allow_diagonal=True)
print(path)
# The path is returned as a numpy array of (i, j) coordinates.
array([[0, 0],
       [1, 1],
       [2, 2],
       [3, 3],
       [4, 4]])

Note that all grid points are represented as (i, j) coordinates. An example of using pyastar2d to solve a maze is given in examples/maze_solver.py.

Example Results

To test the implementation, I grabbed two nasty mazes from Wikipedia. They are included in the mazes directory, but are originally from here: Small and Large. I load the .png files as grayscale images, and set the white pixels to 1 (open space) and the black pixels to INF (walls).

To run the examples specify the input and output files using the --input and --output flags. For example, the following commands will solve the small and large mazes:

python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png

Small Maze (1802 x 1802):

time python examples/maze_solver.py --input mazes/maze_small.png --output solns/maze_small.png
Loaded maze of shape (1802, 1802) from mazes/maze_small.png
Found path of length 10032 in 0.292794s
Plotting path to solns/maze_small.png
Done

real	0m1.214s
user	0m1.526s
sys	0m0.606s

The solution found for the small maze is shown below: Maze Small Solution

Large Maze (4002 x 4002):

time python examples/maze_solver.py --input mazes/maze_large.png --output solns/maze_large.png
Loaded maze of shape (4002, 4002) from mazes/maze_large.png
Found path of length 783737 in 0.829181s
Plotting path to solns/maze_large.png
Done

real	0m29.385s
user	0m29.563s
sys	0m0.728s

The solution found for the large maze is shown below: Maze Large Solution

Motivation

I recently needed an implementation of the A* algorithm in Python to find the shortest path between two points in a cost matrix representing an image. Normally I would simply use networkx, but for graphs with millions of nodes the overhead incurred to construct the graph can be expensive. Considering that I was only interested in graphs that may be represented as two-dimensional grids, I decided to implement it myself using this special structure of the graph to make various optimizations. Specifically, the graph is represented as a one-dimensional array because there is no need to store the neighbors. Additionally, the lookup tables for previously-explored nodes (their costs and paths) are also stored as one-dimensional arrays. The implication of this is that checking the lookup table can be done in O(1), at the cost of using O(n) memory. Alternatively, we could store only the nodes we traverse in a hash table to reduce the memory usage. Empirically I found that replacing the one-dimensional array with a hash table (std::unordered_map) was about five times slower.

Tests

The default installation does not include the dependencies necessary to run the tests. To install these, first run

pip install -r requirements-dev.txt

before running

py.test

The tests are fairly basic but cover some of the more common pitfalls. Pull requests for more extensive tests are welcome.

References

  1. A* search algorithm on Wikipedia
  2. Pathfinding with A* on Red Blob Games

pyastar2d's People

Contributors

eladyaniv01 avatar hjweide avatar peterchenadded avatar pyrooka avatar user123-source avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pyastar2d's Issues

Binaries for windows

This is great, from what I could see.

Appreciate if we can get windows wheels uploaded into pypi.org

How to build it as a package of another lib

first of all, I would like to say, you have done amazing work!

I am making use of your pyastar here https://github.com/eladyaniv01/SC2MapAnalysis

however I want to give credit where credit is due, and I cannot find a way to just add it to the installation process

something gets missed in the build, (and since it's not published a build must be done on the install , right ? )
the only solution I have found so far is to create python installation scripts

https://github.com/eladyaniv01/SC2MapAnalysis/blob/master/install.py

the current solution works,
but I had to edit some of your code and place it in my project directory,
I would much rather install it as is, and give you full credit for the great pathfinding engine that is being used.

Let me know if you know how to solve this <3

How to make it run on Windows, not on Linux only

I came back for this algorithm. It doesn't run on windows because most of the users use python 64 bit, but it requires python 32 bit version. Linux usually use 32bit by default, but windows 64 and that was the reason, so If anyone want to use it on windows just use python32bit version (separate conda envs will help). You can add this info to the readme for windows users.

Automating the creation of the weights matrix given an image of a 2D binarised map

Dear @hjweide

This is amazing work, I have been looking for a C++ A* implementation that is callable from Python. However, it seems that the code is reliant on the manual creation of a weights matrix for it to work.

Can I ask how would you utilise your package to traverse through an image of a 2D binarised map (consists of 0's and 255's only)?

Thank you so much!

Ryan

How can I run this in windows

I ran nmake to make astar.so.

When I tried to run the example.py, i got this error -

     File "pyastar.py", line 9, in <module>
        lib = ctypes.cdll.LoadLibrary(join(dirname(fname), 'astar.so'))
      File "C:\Program Files\Python37\lib\ctypes\__init__.py", line 442, in LoadLibrary
        return self._dlltype(name)
      File "C:\Program Files\Python37\lib\ctypes\__init__.py", line 364, in __init__
        self._handle = _dlopen(self._name, mode)
    OSError: [WinError 193] %1 is not a valid Win32 application

My robot can pass through obstacles

Hello, when I use pystar2d on complex maps, the obtained path can go across obstacles. All test code provided in the project is passed.

In the figure below, s is the start position, e is the end position, # represents the obstacle, and x represents the result path. The red line represents the position where robot go across obstalces.

Thank you very much for answering my question.

Snipaste_2021-08-23_18-41-51

L2-norm for diagonal paths

Suggestion:
Please add the option to use the L2-norm (euclidean distance) for diagonal paths. It does not matter in those maze examples. But for more open environments, as shown in the example below, the path is not optimal on the L2-norm.
astar1

Numpy 1.20.2 is not compatible.

How about adding the numpy version in requirement.txt?
When I use the numpy==1.20.2 and try to run the example code in README, it returns the following log.

RuntimeError: module compiled against API version 0xf but this version of numpy is 0xe
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    import pyastar2d
  File "/Users/jiyoonlim/.pyenv/versions/asic/lib/python3.8/site-packages/pyastar2d/__init__.py", line 1, in <module>
    from pyastar2d.astar_wrapper import astar_path, Heuristic
  File "/Users/jiyoonlim/.pyenv/versions/asic/lib/python3.8/site-packages/pyastar2d/astar_wrapper.py", line 3, in <module>
    import pyastar2d.astar
ImportError: numpy.core.multiarray failed to import

When I tried it with numpy==1.22.4, It works well.

How do I make Makefile in win64

When I type in cmd
make Makefile
it returns
make: Nothing to be done for 'Makefile'. How to use makefile for creating .so object ?

What should I do for creating .so file, could you help?

Path is not straightforward when allow_diagonal is set to True, and to extract additional information from the pathfinding object.

Dear @hjweide

This is the result when allow_diagonal is set to True, and the output shown below is not desired from the program.
image

This is the result when allow_diagonal is set to False, and the output shown below is what was desired from the program.
image

I also noticed that the path infiltrates the walls for some unknown reasons.

Update: It does not seem like an infiltration but it seems to 'glow' around the path.
image

The presence of imperfect grayscale pixels were taken into account for in the code below:

maze = img.astype(np.float32)
maze[img < 240] = np.inf
maze[img >= 240] = 1.0

Could I ask you for your recommendation as to how I should go about optimising this? Could it be that diagonal movements are less costly than straight movements?

On the other hand, I would also like to ask if I am able to extract the path cost (this takes into account diagonal movements that cost sqrt(2))? If not, could you recommend how I should go about modifying the package contents? (refer to code snippet below)

for i in range(len(self.PATH)-1): #calculates the cost of the path
            cur = self.PATH[i] 
            nxt = self.PATH[i+1] 
            
            if (cur.x_pos == nxt.x_pos and cur.y_pos != nxt.y_pos): #if vertical movement
                self.COST+=1
            
            elif (cur.x_pos != nxt.x_pos and cur.y_pos == nxt.y_pos): #if horizontal movement
                self.COST+=1
            
            elif (cur.x_pos != nxt.x_pos and cur.y_pos != nxt.y_pos): #if diagonal movement
                self.COST += math.sqrt(2) 

suboptimal paths with allow_diagonal=True

Hi, thanks a lot for this implementation.

Unfortunately I am running into some weird paths if I set allow_diagonal to true. My map is 100x100 with walls around the edges with cost 1001, one obstacle, also cost 1001, and all other tiles set to weight 1.

Below you can see the output with allow_diagonal=False (left) which is as expected and allow_diagonal=True (right) which is suboptimal. Furthermore changing the weight of the empty space to 2 or other small integers results in even worse paths. Starting point is in the middle, goal is the one achieved by the planner.

Screenshot from 2021-02-08 18-05-17

Do you have an idea what the issue could be or if I am using it incorretly?

How I am calling the library:

        weights = np.asarray(self._map._floorplan_img).copy().astype(np.float)
        # weights need to be at least 1 for pyastar -> set walls to high value, all other to low values
        weights[weights.astype(np.bool)] = 1000
        weights += 1
        self._weights = weights
        path_pixels = pyastar.astar_path(self._weights, astar_start, astar_goal_wrist, allow_diagonal=True)

A case for no path found

Hi @hjweide,

Thank you for answering my questions in the previous issue!

I would like to ask if there is a checker for the case of "No Path Found", where it will return a value that denotes that the two points cannot be connected to each other.

I have tried the utility with a point outside an outlined box, and the other being located inside the box's whitespace. This is my result:

(149, 260)
None
Traceback (most recent call last):
  File "/home/rllyryan/Documents/GitHub/multi_agent_reinforcement_learning/development/1_project_preamble/prototype/Sandboxing/pyastar2d_test.py", line 46, in <module>
    maze[path[:, 0], path[:, 1]] = (0, 255, 0)
TypeError: 'NoneType' object is not subscriptable

May I ask what should I do to go about this?

Thank you!

Moving imageio from requirements.txt to requirements-dev.txt?

Since the "imageio" package is only used in maze_solver.py example, maybe it is a good idea to ask the user to install the "imageio" individually.
On the other hand, if a user is trying to use a Python version that is not supported by "imageio" (for instance, python 2 or 3.6), he/she may have trouble installing this package, and I did not see any reason this package cannot correctly run on most python versions.

Renaming to pyastar2d

I've renamed the package to pyastar2d so that I can publish to pypi under this name. pyastar was already taken. Please update imports accordingly.

Feature : How can I add obstacles !

Your lib working good and perfect, but I can't add 0 or None as obstacles

Can you quick do it ?

when no path found it should return False or []

Support to select different heuristics

Thinking to use this implementation to draw lines between connectors, which means we favour nice looking lines.

At the moment I can see it is hard coded to use Manhattan distance when diagonal support is disabled.

Is it possible to also add below heuristics and update to allow selecting the heuristic from python?

  1. only y diff

abs(current.y - goal.y)

  1. only x diff

abs(current.x - goal.x)

  1. Breaking ties

dx1 = current.x - goal.x
dy1 = current.y - goal.y
dx2 = start.x - goal.x
dy2 = start.y - goal.y
cross = abs(dx1dy2 - dx2dy1)
heuristic += cross*0.001

See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html

Not sure if possible to even allow defining the heuristic in python without affecting performance.

The code change is minimal as you would know.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.