2

In an undirected BGL graph: Can I get the information whether an edge (u,v) == (v,u) was initially added as (u,v) or as (v,u)?

Background:
I created a graph using Pythons graph-tool library which internally uses Boost Graph Library (BGL). Each edge has a "directed attribute" referring to source and target of the edge: (source_range, target_range).

I want to perform an undirected Depth-First search to find all paths between two nodes. I'm using graph-tools get_all_paths() as a basis. I altered the underlying BGL implementation in a way that traversal of the graph depends on the "directed attribute". I got it working for the directed case. However when I switch the graph to undirected I have the problem that I don't know the initial direction of an edge. Thus I don't know the ordering of the edge attribute:

(source_range, target_range) vs (target_range, source_range)

Here is my DFS code with the mentioned stop criterion (// Check overlap part):

template <class Graph, class Yield, class VMap, class EMap>
void get_all_paths(size_t s, size_t t, size_t cutoff, VMap visited,
                   EMap startend, Yield& yield, Graph& g)
{
    typedef typename graph_traits<Graph>::out_edge_iterator eiter_t;
    typedef std::pair<eiter_t, eiter_t> item_t;
    visited[s] = true; // set visited true for source node
    // could also use refrences to startend property map here. meh... 
    uint8_t t_start_e1, t_end_e1, q_start_e1, q_end_e1;
    uint8_t q_start_e2, q_end_e2, t_start_e2, t_end_e2;
    int32_t startend_e1;
    int32_t startend_e2;
    typedef typename property_map<Graph, vertex_index_t>::type IndexMap;
    IndexMap index = get(vertex_index, g);

    vector<size_t> vs = {s}; // vector of indexes
    vector<item_t> stack = {out_edges(s, g)};  // vector of edge_iterator pairs
    while (!stack.empty())
    {
        std::cout << "Stack before check overlap: ";
        for (uint8_t i=0; i<stack.size(); i++) {
            std::cout << " (" << source(*stack[i].first, g) << "," << target(*stack[i].first, g) << ") ";
        }
        std::cout << "\n";
        auto& pos = stack.back(); // last element in eiter vector

        // End of path because of self loop or cutoff is reached
        if (pos.first == pos.second || stack.size() > cutoff)
        {
            visited[vs.back()] = false; // revoke visited flag for last node
            vs.pop_back();
            stack.pop_back();
            if (!stack.empty()) 
                ++stack.back().first; // increment first iterator
            continue;
        }

        // Check overlap
        if (stack.size() > 1)
        {

            auto& pos_prev = *(stack.rbegin() + 1); // second last eiter
            startend_e1 = startend[*pos_prev.first];
            startend_e2 = startend[*pos.first];

            std::cout << "Checking Edges: (" << source(*pos_prev.first, g) << "," << target(*pos_prev.first, g) << ")";
            std::cout << " (" << source(*pos.first, g) << "," << target(*pos.first, g) << "):";

            // take apart 2x int32_t to 8x int8_t (memory optimization)
            // Undirected case:If the edge was added 
            // as (u,v) and (v,u) was detected
            // I need to swap q(uery) and t(arget) values here.
            // --> How can I detect if (u,v) was initially added as (u,v)
            // or (v, u)
            q_start_e1 = startend_e1 & 0xFF;
            q_end_e1 = (startend_e1 >> 8) & 0xFF;
            t_start_e1 = (startend_e1 >> 16) & 0xFF;
            t_end_e1 = (startend_e1 >> 24) & 0xFF;

            q_start_e2 = startend_e2 & 0xFF;
            q_end_e2 = (startend_e2 >> 8) & 0xFF;
            t_start_e2 = (startend_e2 >> 16) & 0xFF;
            t_end_e2 = (startend_e2 >> 24) & 0xFF;

            if ((min(t_end_e1, q_end_e2) - max(t_start_e1, q_start_e2)) < 1)
            {
                std::cout << "Failed\n";
                ++pos.first;
                std::cout << "Stack after check overlap: ";
                for (uint8_t i=0; i<stack.size(); i++) {
                    std::cout << "(" << source(*stack[i].first, g) << "," << target(*stack[i].first, g) << ") ";
                }
                std::cout << "\n";
                continue;
            }  

            std::cout << "Passed\n";


        }

        auto v = target(*pos.first, g); // get target vertex 

        // reached target node
        if (v == t)
        {
            vector<size_t> path = {s}; // path vector 
            for (auto& ei : stack)
                path.push_back(target(*ei.first, g));

            yield(wrap_vector_owned<size_t>(path)); // yield path

            ++pos.first; // increment eiter 
        }
        else
        {
            //check if node was visited
            if (!visited[v]) //not visited
            {
                visited[v] = true;
                vs.push_back(v);
                stack.push_back(out_edges(v, g));
            }
            else // visited
            {
                ++pos.first;
            }
        }
    }
};  

Thank you for your help!

Update:
I came up with the following workaround for my problem. I have an edge property (some_val_ref_u, some_val_ref_v) of the edge (u,v). In an undirected graph the edge (v,u) will still have the edge property (some_val_ref_u, some_val_ref_v). Thus I would assign some_val_ref_u to v and some_val_ref_v to u, which is not correct. I have to take the order into account when dealing with a "reverse edge". The solution I came up with is to set the order dynamically when creating the graph depending on the edge index of v and u.

   if edge_index[v] < edge_index[u]:
       g.ep.myattr[g.edge(v,u)] = (some_val_ref_v, some_val_ref_u)
   else:
       g.ep.myattr[g.edge(v,u)] = (some_val_ref_u, some_val_ref_v)

So the order of the edge property tuple depends on which edge index is smaller. Consequently, when traversing the graph, I can decide the order of the edge attribute by comparing the vertex indices.
This does not directly answer my question but hopefully will be a workaround for my problem.

Leo
  • 71
  • 1
  • 4
  • 1
    "*Can I get the information whether an edge (u,v) == (v,u) was initially added as (u,v) or as (v,u)?*". Short answer: no. But maybe you can add a property to the edge holding that information? – kebs Jul 10 '18 at 11:59
  • Hi! Thanks for your answer! Yes I thought about that. However I think my memory limit won't allow that. My graph has 1.8 billion edges and currently uses ~100G. I'm limited to 125G of memory. graph_tool allows to set a graph undirected and directed on the fly in O(1). This is why i thought the information must still be stored somewhere. Isn't there a way to deduce it from the adjacency list? – Leo Jul 10 '18 at 12:34
  • Why do you care about edge direction if you want to perform a undirected DFS? – Tiago Peixoto Jul 10 '18 at 14:15
  • @TiagoPeixoto: Because i have edge attributes which refer to source and target vertex. But in an undirected graph source and target isn't defined: (u,v) = (v,u). So to interpret the attribute correctly I need to know how the edge was added initially: (u,v) or (v,u) – Leo Jul 10 '18 at 15:40

2 Answers2

0

You can simply iterate:

for (auto ed : boost::make_iterator_range(edges(g))) {
    std::cout << "Added as " << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ")\n";
}

This is due to the adjacency lists storing lists of adjacencies per node.

For a directed graph this loop would effectively be equivalent to doing:

for (auto from : boost::make_iterator_range(vertices(g))) {
    for (auto to : boost::make_iterator_range(adjacent_vertices(from, g)))
        std::cout << "Added as " << from << "->" << to << "\n";
}

However, for undirected graphs this would list all non-self edges duplicate.

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <iostream>

void testcase(int from, int to) {
    using namespace boost;
    adjacency_list<vecS, vecS, undirectedS> g(2);
    add_edge(from, to, g);

    for (auto ed : boost::make_iterator_range(edges(g))) {
        std::cout << "Added as " << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ")\n";
    }
}

int main() {
    testcase(0, 1);
    testcase(1, 0);
}

Prints

Added as (0,1) (so 0->1)
Added as (1,0) (so 1->0)
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you for your answer! However, this means to look up how an edge was added I need to iterate through all edges, right? – Leo Jul 10 '18 at 15:36
  • No. My example already shows that you can get source/target info from any edge descriptor. – sehe Jul 10 '18 at 15:40
0

sehe's answer is incorrect - there is no way of obtaining original source and target vertices from any edge descriptor, as shown below:

#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>

int main() {
    using namespace boost;
    typedef adjacency_list<vecS, vecS, undirectedS> Graph;
    Graph g(2);
    add_edge(0, 1, g);

    for (auto ed : make_iterator_range(edges(g))) {
        std::cout << "Added as " << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ")\n";
    }

    //edge (1,0) exists since the graph is undirected,
    //yet source and target vertices are not the way they were originally specified
    Graph::edge_descriptor ed = edge(1, 0, g).first;
    std::cout << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ')';
}

One solution, as already mentioned, is to iterate through all the edges.

The other one, more efficient, is to seek for vertex v in out edges of vertex u (or the other way around):

#include <iostream>
#include <cassert>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>

int main() {
    using namespace boost;
    typedef adjacency_list<vecS, vecS, undirectedS> Graph;
    Graph g(2);
    add_edge(0, 1, g);

    Graph::EdgeContainer::const_iterator edge1 = std::find(g.out_edge_list(0).begin(), g.out_edge_list(0).end(), Graph::StoredEdge(1))->get_iter();
    Graph::EdgeContainer::const_iterator edge2 = std::find(g.out_edge_list(1).begin(), g.out_edge_list(1).end(), Graph::StoredEdge(0))->get_iter();

    assert(edge1 == edge2);

    std::cout << source(*edge1, g) << "->" << target(*edge1, g);
}
wmamrak
  • 233
  • 3
  • 12