I am trying to use Dijkstra to find the cheapest path between two pixels in an image.
Implementation:
#include <vector>
#include <queue>
#include <map>
#include <cmath>
#include <numbers>
#include <array>
#include <algorithm>
using vec2i_t [[gnu::vector_size(16)]] = int64_t;
using vec2f_t [[gnu::vector_size(16)]] = double;
template<class T, auto tag>
struct tagged_value
{
public:
tagged_value() = default;
explicit tagged_value(T value):m_value{value}{}
T value() const { return m_value; }
private:
T m_value;
};
template<class T>
using from = tagged_value<T, 0>;
template<class T>
using to = tagged_value<T, 1>;
template<class T, auto tag>
inline tagged_value<T, tag> operator+(tagged_value<T, tag> a, T b)
{
return tagged_value<T, tag>{a.value() + b};
}
template< auto tag>
inline tagged_value<int64_t, tag> operator*(int64_t b, tagged_value<vec2i_t, tag> a)
{
return tagged_value<int64_t, tag>{b*a.value()};
}
struct pending_route_node
{
to<vec2i_t> loc;
double total_cost;
};
bool is_cheaper(pending_route_node const& a, pending_route_node const& b)
{
return a.total_cost < b.total_cost;
}
struct route_node
{
from<vec2i_t> loc;
double total_cost = std::numeric_limits<double>::infinity();
};
constexpr auto scale_factor = 1;
template<auto tag>
constexpr auto scale_by_factor(tagged_value<vec2i_t, tag> val)
{
auto const x = val.value();
return tagged_value<vec2f_t, tag>
{vec2f_t{static_cast<double>(x[0]), static_cast<double>(x[1])}/ static_cast<double>(scale_factor)};
}
constexpr auto gen_neigbour_offset_table()
{
std::array<vec2i_t, 8> ret{};
constexpr auto r = static_cast<double>(scale_factor);
for(size_t k = 0; k != std::size(ret); ++k)
{
auto const theta = k*2.0*std::numbers::pi/std::size(ret);
ret[k] = vec2i_t{static_cast<int64_t>(std::round(r*std::cos(theta))),
static_cast<int64_t>(std::round(r*std::sin(theta)))};
}
return ret;
}
constexpr auto neigbour_offsets = gen_neigbour_offset_table();
template<class CostFunction>
auto search(from<vec2i_t> origin, CostFunction&& f)
{
auto cmp = [](pending_route_node const& a, pending_route_node const& b)
{ return is_cheaper(b, a); };
std::priority_queue<pending_route_node, std::vector<pending_route_node>, decltype(cmp)> nodes_to_visit;
nodes_to_visit.push(pending_route_node{to<vec2i_t>{scale_factor*origin.value()}, 0.0});
auto loc_cmp=[](to<vec2i_t> p1, to<vec2i_t> p2) {
auto const a = p1.value();
auto const b = p2.value();
return (a[0] == b[0]) ? a[1] < b[1] : a[0] < b[0];
};
std::map<to<vec2i_t>, std::pair<route_node, bool>, decltype(loc_cmp)> cost_table;
while(!nodes_to_visit.empty())
{
auto current = nodes_to_visit.top();
nodes_to_visit.pop();
auto& cost_item = cost_table[current.loc];
cost_item.second = true;
for(auto item : neigbour_offsets)
{
auto const next_loc = current.loc + item;
auto const cost_increment = f(scale_by_factor(from{current.loc.value()}), scale_by_factor(next_loc));
static_assert(std::is_same_v<std::decay_t<decltype(cost_increment)>, double>);
if(cost_increment == std::numeric_limits<double>::infinity())
{ break; }
auto& new_cost_item = cost_table[next_loc];
if(new_cost_item.second)
{ break; }
auto const new_cost = current.total_cost + cost_increment;
if(new_cost < new_cost_item.first.total_cost)
{
new_cost_item.first.total_cost = new_cost;
new_cost_item.first.loc = from<vec2i_t>{current.loc.value()};
nodes_to_visit.push(pending_route_node{next_loc, new_cost});
}
}
}
return cost_table;
}
Example usage:
auto result = search(cheapest_route::from{cheapest_route::vec2i_t{0, 0}},
[](cheapest_route::from<cheapest_route::vec2f_t> a, cheapest_route::to<cheapest_route::vec2f_t> b) {
auto const v1 = a.value();
auto const v2 = b.value();
constexpr auto size = 4.0;
if((v2[0] < -size || v2[0] > size) || (v2[1] < -size || v2[1] > size))
{ return std::numeric_limits<double>::infinity(); }
// Use Euclidian distance as a simple test case
auto delta = v1 - v2;
auto d2 = delta*delta;
return d2[0] + d2[1];
});
std::ranges::for_each(result, [](auto const& item) {
auto [to, node_info] = item;
printf("to = (%ld, %ld), from = (%ld, %ld). total_cost = %.8g\n",
to.value()[0], to.value()[1],
node_info.first.loc.value()[0], node_info.first.loc.value()[1],
node_info.first.total_cost);
});
Output:
... to = (-1, -1), from = (0, 0). total_cost = 2
... to = (-1, 0), from = (0, 0). total_cost = 1
... to = (-1, 1), from = (0, 0). total_cost = 2
... to = (-1, 2), from = (0, 1). total_cost = 3
... to = (-1, 3), from = (0, 2). total_cost = 4
... to = (-1, 4), from = (0, 3). total_cost = 5
... to = (0, -1), from = (0, 0). total_cost = 1
... to = (0, 0), from = (0, 0). total_cost = inf
... to = (0, 1), from = (0, 0). total_cost = 1
... to = (0, 2), from = (0, 1). total_cost = 2
... to = (0, 3), from = (0, 2). total_cost = 3
... to = (0, 4), from = (0, 3). total_cost = 4
... to = (1, -1), from = (0, 0). total_cost = 2
... to = (1, 0), from = (0, 0). total_cost = 1
... to = (1, 1), from = (0, 0). total_cost = 2
... to = (1, 2), from = (0, 1). total_cost = 3
... to = (1, 3), from = (0, 2). total_cost = 4
... to = (1, 4), from = (0, 3). total_cost = 5
... to = (2, -1), from = (1, -1). total_cost = 3
... to = (2, 0), from = (1, 0). total_cost = 2
... to = (2, 1), from = (1, 0). total_cost = 3
... to = (2, 2), from = (1, 1). total_cost = 4
... to = (2, 3), from = (1, 2). total_cost = 5
... to = (2, 4), from = (1, 3). total_cost = 6
... to = (3, -1), from = (2, -1). total_cost = 4
... to = (3, 0), from = (2, 0). total_cost = 3
... to = (3, 1), from = (2, 0). total_cost = 4
... to = (3, 2), from = (2, 1). total_cost = 5
... to = (3, 3), from = (2, 2). total_cost = 6
... to = (3, 4), from = (2, 3). total_cost = 7
... to = (4, -1), from = (3, -1). total_cost = 5
... to = (4, 0), from = (3, 0). total_cost = 4
... to = (4, 1), from = (3, 0). total_cost = 5
... to = (4, 2), from = (3, 1). total_cost = 6
... to = (4, 3), from = (3, 2). total_cost = 7
... to = (4, 4), from = (3, 3). total_cost = 8
I think the output is correct, but if I increase the domain size beyond anything but a very small value, the algorithm starts to consume tons of RAM, and I cannot run it to completion, because I would run out of RAM.
- Is it expected to take that much system resources, or is there something that I have missed?
- I do not need the cost between start point and any other point. How can I terminate early?
nodes_to_visit
is the cheapest way to get there (because it is sorted). Once you have been there once you don't ever need to consider it again so any subsequent values innodes_to_visit
for that value can be immediately discarded without looking at children. \$\endgroup\$