Created
February 23, 2019 12:32
-
-
Save gonzaloruizdevilla/40628235b382b67a1c7c65c775dc6882 to your computer and use it in GitHub Desktop.
Implement Hybrid A* in C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <math.h> | |
#include <algorithm> | |
#include <iostream> | |
#include <vector> | |
#include "hybrid_breadth_first.h" | |
// Initializes HBF | |
HBF::HBF() {} | |
HBF::~HBF() {} | |
bool HBF::compare_maze_s(const HBF::maze_s &lhs, const HBF::maze_s &rhs) { | |
return lhs.f < rhs.f; | |
} | |
double HBF::heuristic(double x, double y, vector<int> &goal){ | |
return fabs(y - goal[0]) + fabs(x - goal[1]); //return grid distance to goal | |
} | |
int HBF::theta_to_stack_number(double theta){ | |
// Takes an angle (in radians) and returns which "stack" in the 3D | |
// configuration space this angle corresponds to. Angles near 0 go in the | |
// lower stacks while angles near 2 * pi go in the higher stacks. | |
double new_theta = fmod((theta + 2 * M_PI),(2 * M_PI)); | |
int stack_number = (int)(round(new_theta * NUM_THETA_CELLS / (2*M_PI))) | |
% NUM_THETA_CELLS; | |
return stack_number; | |
} | |
int HBF::idx(double float_num) { | |
// Returns the index into the grid for continuous position. So if x is 3.621, | |
// then this would return 3 to indicate that 3.621 corresponds to array | |
// index 3. | |
return int(floor(float_num)); | |
} | |
vector<HBF::maze_s> HBF::expand(HBF::maze_s &state, vector<int> &goal) { | |
int g = state.g; | |
double x = state.x; | |
double y = state.y; | |
double theta = state.theta; | |
int g2 = g+1; | |
vector<HBF::maze_s> next_states; | |
for(double delta_i = -35; delta_i < 40; delta_i+=5) { | |
double delta = M_PI / 180.0 * delta_i; | |
double omega = SPEED / LENGTH * tan(delta); | |
double theta2 = theta + omega; | |
if(theta2 < 0) { | |
theta2 += 2*M_PI; | |
} | |
double x2 = x + SPEED * cos(theta); | |
double y2 = y + SPEED * sin(theta); | |
HBF::maze_s state2; | |
state2.f = g2 + heuristic(x2, y2, goal); | |
state2.g = g2; | |
state2.x = x2; | |
state2.y = y2; | |
state2.theta = theta2; | |
next_states.push_back(state2); | |
} | |
return next_states; | |
} | |
vector< HBF::maze_s> HBF::reconstruct_path( | |
vector<vector<vector<HBF::maze_s>>> &came_from, vector<double> &start, | |
HBF::maze_s &final) { | |
vector<maze_s> path = {final}; | |
int stack = theta_to_stack_number(final.theta); | |
maze_s current = came_from[stack][idx(final.x)][idx(final.y)]; | |
stack = theta_to_stack_number(current.theta); | |
double x = current.x; | |
double y = current.y; | |
while(x != start[0] || y != start[1]) { | |
path.push_back(current); | |
current = came_from[stack][idx(x)][idx(y)]; | |
x = current.x; | |
y = current.y; | |
stack = theta_to_stack_number(current.theta); | |
} | |
return path; | |
} | |
HBF::maze_path HBF::search(vector< vector<int> > &grid, vector<double> &start, | |
vector<int> &goal) { | |
// Working Implementation of breadth first search. Does NOT use a heuristic | |
// and as a result this is pretty inefficient. Try modifying this algorithm | |
// into hybrid A* by adding heuristics appropriately. | |
/** | |
* TODO: Add heuristics and convert this function into hybrid A* | |
*/ | |
vector<vector<vector<int>>> closed( | |
NUM_THETA_CELLS, vector<vector<int>>(grid[0].size(), vector<int>(grid.size()))); | |
vector<vector<vector<maze_s>>> came_from( | |
NUM_THETA_CELLS, vector<vector<maze_s>>(grid[0].size(), vector<maze_s>(grid.size()))); | |
double theta = start[2]; | |
int stack = theta_to_stack_number(theta); | |
int g = 0; | |
maze_s state; | |
state.g = g; | |
state.x = start[0]; | |
state.y = start[1]; | |
state.f = g + heuristic(state.x, state.y, goal); | |
state.theta = theta; | |
closed[stack][idx(state.x)][idx(state.y)] = 1; | |
came_from[stack][idx(state.x)][idx(state.y)] = state; | |
int total_closed = 1; | |
vector<maze_s> opened = {state}; | |
bool finished = false; | |
while(!opened.empty()) { | |
sort(opened.begin(), opened.end(), compare_maze_s); | |
maze_s current = opened[0]; //grab first elment | |
opened.erase(opened.begin()); //pop first element | |
int x = current.x; | |
int y = current.y; | |
if(idx(x) == goal[0] && idx(y) == goal[1]) { | |
std::cout << "found path to goal in " << total_closed << " expansions" | |
<< std::endl; | |
maze_path path; | |
path.came_from = came_from; | |
path.closed = closed; | |
path.final = current; | |
return path; | |
} | |
vector<maze_s> next_state = expand(current, goal); | |
for(int i = 0; i < next_state.size(); ++i) { | |
int g2 = next_state[i].g; | |
double x2 = next_state[i].x; | |
double y2 = next_state[i].y; | |
double theta2 = next_state[i].theta; | |
if((x2 < 0 || x2 >= grid.size()) || (y2 < 0 || y2 >= grid[0].size())) { | |
// invalid cell | |
continue; | |
} | |
int stack2 = theta_to_stack_number(theta2); | |
if(closed[stack2][idx(x2)][idx(y2)] == 0 && grid[idx(x2)][idx(y2)] == 0) { | |
opened.push_back(next_state[i]); | |
closed[stack2][idx(x2)][idx(y2)] = 1; | |
came_from[stack2][idx(x2)][idx(y2)] = current; | |
++total_closed; | |
} | |
} | |
} | |
std::cout << "no valid path." << std::endl; | |
HBF::maze_path path; | |
path.came_from = came_from; | |
path.closed = closed; | |
path.final = state; | |
return path; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef HYBRID_BREADTH_FIRST_H_ | |
#define HYBRID_BREADTH_FIRST_H_ | |
#include <vector> | |
using std::vector; | |
class HBF { | |
public: | |
// Constructor | |
HBF(); | |
// Destructor | |
virtual ~HBF(); | |
// HBF structs | |
struct maze_s { | |
int g; // iteration | |
int f; | |
double x; | |
double y; | |
double theta; | |
}; | |
struct maze_path { | |
vector<vector<vector<int>>> closed; | |
vector<vector<vector<maze_s>>> came_from; | |
maze_s final; | |
}; | |
// HBF functions | |
int theta_to_stack_number(double theta); | |
int idx(double float_num); | |
double heuristic(double x, double y, vector<int> &goal); | |
static bool compare_maze_s(const HBF::maze_s &lhs, const HBF::maze_s &rhs); | |
vector<maze_s> expand(maze_s &state, vector<int> &goal); | |
vector<maze_s> reconstruct_path(vector<vector<vector<maze_s>>> &came_from, | |
vector<double> &start, HBF::maze_s &final); | |
maze_path search(vector<vector<int>> &grid, vector<double> &start, | |
vector<int> &goal); | |
private: | |
const int NUM_THETA_CELLS = 90; | |
const double SPEED = 1.45; | |
const double LENGTH = 0.5; | |
}; | |
#endif // HYBRID_BREADTH_FIRST_H_ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include "hybrid_breadth_first.h" | |
using std::cout; | |
using std::endl; | |
// Sets up maze grid | |
int X = 1; | |
int _ = 0; | |
/** | |
* TODO: You can change up the grid maze to test different expansions. | |
*/ | |
vector<vector<int>> GRID = { | |
{_,X,X,_,_,_,_,_,_,_,X,X,_,_,_,_,}, | |
{_,X,X,_,_,_,_,_,_,X,X,_,_,_,_,_,}, | |
{_,X,X,_,_,_,_,_,X,X,_,_,_,_,_,_,}, | |
{_,X,X,_,_,_,_,X,X,_,_,_,X,X,X,_,}, | |
{_,X,X,_,_,_,X,X,_,_,_,X,X,X,_,_,}, | |
{_,X,X,_,_,X,X,_,_,_,X,X,X,_,_,_,}, | |
{_,X,X,_,X,X,_,_,_,X,X,X,_,_,_,_,}, | |
{_,X,X,X,X,_,_,_,X,X,X,_,_,_,_,_,}, | |
{_,X,X,X,_,_,_,X,X,X,_,_,_,_,_,_,}, | |
{_,X,X,_,_,_,X,X,X,_,_,X,X,X,X,X,}, | |
{_,X,_,_,_,X,X,X,_,_,X,X,X,X,X,X,}, | |
{_,_,_,_,X,X,X,_,_,X,X,X,X,X,X,X,}, | |
{_,_,_,X,X,X,_,_,X,X,X,X,X,X,X,X,}, | |
{_,_,X,X,X,_,_,X,X,X,X,X,X,X,X,X,}, | |
{_,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,}, | |
{X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,}}; | |
vector<double> START = {0.0,0.0,0.0}; | |
vector<int> GOAL = {(int)GRID.size()-1, (int)GRID[0].size()-1}; | |
int main() { | |
cout << "Finding path through grid:" << endl; | |
// Creates an Empty Maze and for testing the number of expansions with it | |
for(int i = 0; i < GRID.size(); ++i) { | |
cout << GRID[i][0]; | |
for(int j = 1; j < GRID[0].size(); ++j) { | |
cout << "," << GRID[i][j]; | |
} | |
cout << endl; | |
} | |
HBF hbf = HBF(); | |
HBF::maze_path get_path = hbf.search(GRID,START,GOAL); | |
vector<HBF::maze_s> show_path = hbf.reconstruct_path(get_path.came_from, | |
START, get_path.final); | |
cout << "show path from start to finish" << endl; | |
for(int i = show_path.size()-1; i >= 0; --i) { | |
HBF::maze_s step = show_path[i]; | |
cout << "##### step " << step.g << " #####" << endl; | |
cout << "x " << step.x << endl; | |
cout << "y " << step.y << endl; | |
cout << "theta " << step.theta << endl; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment