Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docs tests #11

Merged
merged 2 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.6.0

- Theta* algorithm added
- Line of sight algorithm added
- Minor improvements in code quality

## 0.5.1

- Minor bug fixes
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ Currently there are 7 path-finders bundled in this library, namely:
- Breadth First Search (BFS)
- Iterative Deeping A\* (IDA\*): Memory efficient algorithm for large graphs.
- Minimum Spanning Tree (MSP)
- Theta\*: Almost A\* with path smoothing.

Dijkstra, A\* and Bi-directional A\* take the weight of the fields on the map into account.
Theta\* is a variant of A\* but with any angle of movement allowed.

## Installation

Expand Down
6 changes: 6 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Version History

## 0.6.0

- Theta* algorithm added
- Line of sight algorithm added
- Minor improvements in code quality

## 0.5.1

- Minor bug fixes
Expand Down
5 changes: 5 additions & 0 deletions docs/INTRO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Pathfinding3D

[![MIT License](https://img.shields.io/github/license/harisankar95/pathfinding3d)](https://github.com/harisankar95/pathfinding3D/blob/main/LICENSE)
[![PyPI](https://img.shields.io/pypi/v/pathfinding3d)](https://pypi.org/project/pathfinding3d/)

## Introduction

Pathfinding algorithms for python3 froked from [python-pathfinding](https://github.com/brean/python-pathfinding) by [@brean](https://github.com/brean).
Expand All @@ -15,5 +18,7 @@ Currently there are 7 path-finders bundled in this library, namely:
- Breadth First Search (BFS)
- Iterative Deeping A\* (IDA\*): Memory efficient algorithm for large graphs.
- Minimum Spanning Tree (MSP)
- Theta\*: Almost A\* with path smoothing.

Dijkstra, A\* and Bi-directional A\* take the weight of the fields on the map into account.
Theta\* is a variant of A\* but with any angle of movement allowed.
238 changes: 236 additions & 2 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ Here is the whole example which you can copy-and-paste to play with it:

```

---

## Steps/Portals/Bridges

With *pathfinding3d*, you can seamlessly connect multiple grids. This feature is invaluable for simulating multi-story structures connected by staircases, bridges between different buildings, or even magical portals linking disparate locations.
Expand Down Expand Up @@ -212,6 +214,8 @@ Let's consider an example where we want to connect the second storey of two adja
print("path through world 1:", path1)
```

---

## Visualizing the Path

Sometimes it is helpful to visualize the path to better understand the algorithm. Here is a simple example using the `open3d` library for this purpose.
Expand Down Expand Up @@ -245,6 +249,7 @@ Sometimes it is helpful to visualize the path to better understand the algorithm
# Load the map
matrix = np.load("sample_map.npy")
```

`sample_map.npy` is a numpy array of shape (42, 42, 42), where each element indicates an obstacle or free space. This file can be downloaded from the provided [GitHub link](https://github.com/harisankar95/pathfinding3D/blob/main/examples/sample_map.npy).

3. Create the Grid and define the start and end nodes:
Expand All @@ -259,6 +264,7 @@ Sometimes it is helpful to visualize the path to better understand the algorithm
start = grid.node(*start_pt)
end = grid.node(*end_pt)
```

Note: The `*` operator unpacks the list into individual arguments.
Here, we define the start and end points of our path and create a grid representation of our 3D space.

Expand All @@ -275,7 +281,7 @@ Sometimes it is helpful to visualize the path to better understand the algorithm
path_cost = end.g
print(f"path cost: {path_cost:.4f}, path length: {len(path)}, runs: {runs}")
```

This will output:

```bash
Expand Down Expand Up @@ -344,5 +350,233 @@ Sometimes it is helpful to visualize the path to better understand the algorithm

![voxel_grid](https://github.com/harisankar95/pathfinding3D/blob/main/examples/resources/open3d.png)

The full code is available [here](https://github.com/harisankar95/pathfinding3D/blob/main/examples/03_view_map.py)

---

### Example with any angle of movement

Often, it is desirable to allow movement in any direction rather than being restricted to the 26 directions in a 3D grid. This can be achieved by using the `ThetaStarFinder` class. The `ThetaStarFinder` class is a subclass of the `AStarFinder` class and can be used in the same way.

Let's cut to the chase and see how it works:

1. As usual, import the required libraries:

```python
import numpy as np

from pathfinding3d.core.grid import Grid
from pathfinding3d.finder.theta_star import ThetaStarFinder
```

2. For this example, we will use a simple 3D grid with a single obstacle in the middle. The start point is at the bottom left corner and the end point is at the top right corner.

```python
# Define the 3D grid
matrix = np.ones((10, 10, 10), dtype=np.int8)
matrix[5, 5, 5] = 0 # Setting an obstacle at the center

# Create the grid representation
grid = Grid(matrix=matrix)

# Define start and end points
start = grid.node(0, 0, 0) # Bottom left corner
end = grid.node(9, 9, 9) # Top right corner
```

3. Instantiate the `ThetaStarFinder` class and find the path:

```python
# Instantiate the finder
finder = ThetaStarFinder()
path, runs = finder.find_path(start, end, grid)
# Convert the path to a list of coordinate tuples
path = [p.identifier for p in path]
```

Note: The `ThetaStarFinder` will always have diagonal movements enabled.

4. Output the results:

```python
# Output the results
print("operations:", runs, "path length:", len(path))
print("path:", path)
```

This will output:

```bash
operations: 12 path length: 3
path: [(0, 0, 0), (9, 8, 8), (9, 9, 9)]
```

You will notice that the path does not have all the waypoints as other algorithms. This is because the `ThetaStarFinder` algorithm will smooth the path by checking whether there is a direct path between two waypoints. If there is a direct path, the intermediate waypoints are removed. This is useful for applications where the path needs to be traversed by a vehicle. The vehicle can move in any direction and does not need to follow a grid. The path can be smoothed to reduce the number of waypoints to be traversed.

5. For a quantitative analysis let's compare the number of waypoints in the path for the `AStarFinder` and `ThetaStarFinder` algorithms:

```python
from pathfinding3d.finder.a_star import AStarFinder

# Instantiate the finder
finder = AStarFinder()
# Cleanup the grid
grid.cleanup()
# Find the path using AStarFinder
astar_path, runs = finder.find_path(start, end, grid)
# Convert the path to a list of coordinate tuples
astar_path = [p.identifier for p in astar_path]
print("AStarFinder operations:", runs, "AStarFinder path length:", len(path))
print("AStarFinder path:", path)
```

This will output:

```bash
AStarFinder operations: 52 AStarFinder path length: 11
AStarFinder path: [(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 4, 4), (6, 5, 5), (7, 6, 6), (8, 7, 7), (9, 8, 8), (9, 9, 9)]
```

6. As you can see, the `AStarFinder` algorithm has 11 waypoints in the path. Let's compare the cost of the path for both algorithms:

```python
# Function to calculate the cost of the path
def calculate_path_cost(path):
cost = 0
for pt, pt_next in zip(path[:-1], path[1:]):
dx, dy, dz = pt_next[0] - pt[0], pt_next[1] - pt[1], pt_next[2] - pt[2]
cost += (dx**2 + dy**2 + dz**2) ** 0.5
return cost

# Calculate the cost of the path for ThetaStarFinder
theta_star_cost = calculate_path_cost(path)
# Calculate the cost of the path for AStarFinder
astar_cost = calculate_path_cost(astar_path)

# Output the results
print("ThetaStarFinder path cost:", theta_star_cost, "\nAStarFinder path cost:", astar_cost)
```

This will output:

```bash
ThetaStarFinder path cost: 15.871045857174057
AStarFinder path cost: 16.27062002292411
```

As you can see, the `ThetaStarFinder` algorithm has a lower cost than the `AStarFinder` algorithm. Thus the `ThetaStarFinder` algorithm can be more efficient for certain applications.

7. We can visualize the paths using `plotly` this time:

```python
import plotly.graph_objects as go

# Create a plotly figure to visualize the path
fig = go.Figure(
data=[
go.Scatter3d(
x=[pt[0] + 0.5 for pt in path],
y=[pt[1] + 0.5 for pt in path],
z=[pt[2] + 0.5 for pt in path],
mode="lines + markers",
line=dict(color="blue", width=4),
marker=dict(size=4, color="blue"),
name="Theta* path",
hovertext=["Theta* path point"] * len(path),
),
go.Scatter3d(
x=[pt[0] + 0.5 for pt in astar_path],
y=[pt[1] + 0.5 for pt in astar_path],
z=[pt[2] + 0.5 for pt in astar_path],
mode="lines + markers",
line=dict(color="red", width=4),
marker=dict(size=4, color="red"),
name="A* path",
hovertext=["A* path point"] * len(astar_path),
),
go.Scatter3d(
x=[5.5],
y=[5.5],
z=[5.5],
mode="markers",
marker=dict(color="black", size=7.5),
name="Obstacle",
hovertext=["Obstacle point"],
),
go.Scatter3d(
x=[0.5],
y=[0.5],
z=[0.5],
mode="markers",
marker=dict(color="green", size=7.5),
name="Start",
hovertext=["Start point"],
),
go.Scatter3d(
x=[9.5],
y=[9.5],
z=[9.5],
mode="markers",
marker=dict(color="orange", size=7.5),
name="End",
hovertext=["End point"],
),
]
)

# Define the camera position
camera = {
"up": {"x": 0, "y": 0, "z": 1},
"center": {"x": 0.1479269806756467, "y": 0.06501594452841505, "z": -0.0907033779622012},
"eye": {"x": 1.3097359159706334, "y": 0.4710974884501846, "z": 2.095154166796815},
"projection": {"type": "perspective"},
}

# Update the layout of the figure
fig.update_layout(
scene=dict(
xaxis=dict(
title="x - axis",
backgroundcolor="white",
gridcolor="lightgrey",
showbackground=True,
zerolinecolor="white",
),
yaxis=dict(
title="y - axis",
backgroundcolor="white",
gridcolor="lightgrey",
showbackground=True,
zerolinecolor="white",
),
zaxis=dict(
title="z - axis",
backgroundcolor="white",
gridcolor="lightgrey",
showbackground=True,
zerolinecolor="white",
),
),
legend=dict(
yanchor="top",
y=0.99,
xanchor="left",
x=0.01,
bgcolor="rgba(255, 255, 255, 0.7)",
),
title=dict(text="Theta* vs A*"),
scene_camera=camera,
)

# Save the figure as a html file
fig.write_html("theta_star.html")
# Show the figure in a new tab
fig.show()
```

This will open a new tab in your browser with the following visualization:
<iframe src="https://github.com/harisankar95/pathfinding3D/blob/main/docs/theta_star.html" width="100%" height="600px"></iframe>

You can rotate the figure by clicking and dragging the mouse. You can also zoom in and out using the mouse wheel.

The full code is available [here](https://github.com/harisankar95/pathfinding3D/blob/main/examples/03_view_map.py)
The full code is available [here](https://github.com/harisankar95/pathfinding3D/blob/main/examples/04_theta_star.py)
14 changes: 14 additions & 0 deletions docs/theta_star.html

Large diffs are not rendered by default.

Loading