Skip to content

Instantly share code, notes, and snippets.

@gonzaloruizdevilla
Created February 23, 2019 12:32
Show Gist options
  • Save gonzaloruizdevilla/40628235b382b67a1c7c65c775dc6882 to your computer and use it in GitHub Desktop.
Save gonzaloruizdevilla/40628235b382b67a1c7c65c775dc6882 to your computer and use it in GitHub Desktop.
Implement Hybrid A* in C++
#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;
}
#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_
#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