boost A* visitor with a custom edge weight penalty? - c++

I am playing with boost A* algorithm, started with the example found at: http://www.boost.org/doc/libs/1_37_0/libs/graph/example/astar-cities.cpp
I see that you can override its heuristics and its visitor to have some sort of custom tweaks, just that I don't quite get the concept yet for such a thing like the following, as a learning example, I'd like the algorithm to NOT pick a edge city - city, if travel time (edge weight) is greater than X, for example 100 minutes. (only if possible, if no other path is found, then that city should be chosed instead of not path found)
I tried a custom heuristic class which returns a greater time than reality, to "trick" it not to chose that city, however the problem is that with this trick, the penaltied city gets discarded, even for further interactions. (The following example explains it: B->D is discarded as a better path is found, but city D is not discarded (you see it's picked in a following iteration)
So I simplified the problem furter:
enum nodes {
A, B, C, D, E, N
};
const char *name[] = {
"A", "B", "C", "D", "E", "F"
};
edge edge_array[] = {
edge(A,B), edge(B,C),
edge(C,D), edge(D,E),
edge(B,D) // B to D is the problem here, it should be excluded
};
cost weights[] = { // estimated travel time (mins)
// 107, 174, 283, 71, 436
35, 58, 94, 23, 145
};
With this example (taking the code from original as base), I get a route:
Start vertex: A
Goal vertex: E
Shortest path from A to E: A -> B -> D -> E
Total travel time: 204.5
The problem is the B -> D path, which is such a long distance (supposing a threshold of 100, for example, that would be preferable a path like: A -> B -> C -> D -> E, this way, the distance between 2 cities is not superior to 100 (of course only if possible, if no other path, any have to be chosen)
I solved it in a suboptimal way: A custom function once adding the edge, that, (or setting manually weight) return travelTime > 100 ? travelTime * 2 : travelTime, which can be achieved for testing with:
cost weights[] = { // estimated travel time (mins)
// 107, 174, 283, 71, 436
(35 > 100) ? 35*2 : 35, (58 > 100) ? 58*2 : 58, (94>100) ? 94*2 : 94, 23, (145 > 100) ? 145*2 : 145
}; // The comparisons are not needed, just there to better explain what the custom add_edge function will do.
With this method, I get the desired A -> B -> C -> D -> E, but this way, is just a hack/workaround the problem and modifies input data internally, which I think it is not the best solution.
Is there any better way to achieve this without having to manually change distances/travel time?

What you're trying to has nothing to do with heuristics. The A* search algorithm is breadth-first search with benefits. The heuristic is there to add a lower-bound to what the final cost will be. For a map doing street directions, straight-line distance is the perfect heuristic. The point of the heuristic is to ensure that you expand your best likely candidates before your worst likely candidates. Again, in a map sense, breadth-first search would basically circle outwards until you find your destination - whereas the heuristic makes it so that you would ooze towards your destination directly and have way fewer paths worth considering. From a different perspective - the heuristic is a function that takes the current last point in the path and the destination point and returns a cost. You cannot use it to exclude edges, as it does not know about the path. It only knows about two points.
Back to your problem. You want:
I'd like the algorithm to NOT pick a edge city - city, if travel time (edge weight) is greater than X, for example 100 minutes. (only if possible, if no other path is found, then that city should be chosed instead of not path found)
A heuristic has nothing to do with specific graph nodes or edges. It's just an estimate for the final cost and likely shouldn't be dependent on the graph itself. What you're asking for has to do with the weights. A* is all about finding minimum-weight path. If you want an edge to NOT be taken... simply up its weight!
The example you linked to has these weights:
cost weights[] = { // estimated travel time (mins)
96, 134, 143, 65, 115, 133, 117, 116, 74, 56,
84, 73, 69, 70, 116, 147, 173, 183, 74, 71, 124
};
You want new weights, basically:
auto new_weights = at_most(weights, 100); // I don't want to use any edges
// that take 100 minutes
Which we could write thusly:
template <size_t N>
std::array<cost, N> at_most(cost (&old)[N], cost max_cost) {
cost total = std::accumulate(old, old+N, 0.0f) * N;
std::array<cost, N> new_weights;
for (size_t i = 0; i < N; ++i) {
new_weights[i] = old[i] < max_cost ? old[i] : old[i] + total;
}
return new_weights;
}
Basically, we just sum ALL the weights and replace all the edges that have cost larger than what you stipulated as your maximum with a new weight that is larger than taking ALL the edges. The end result of this is that if there exists a path that does not use any of the >100-weight edges, it will definitely have a lower total cost than this new path. The specific new weight we use isn't important, it just needs to be sufficiently large to guarantee the truth of the previous statement.
We do not need to change the heuristic. Just the weights.

I think you just wanted shortest paths here (dijkstra would be alright).
The key is to realize that you can use a custom edge_weight property map. This could be e.g. boost::property_map::transform_value_property_map<> like so:
auto my_custom_weight_map =
boost::make_transform_value_property_map(
[](float w) { return w>100? 10*w : w; },
boost::get(boost::edge_weight, g));
You see that any edge weight above 100 will be increased tenfold.
Then, you're basically already done:
astar_search_tree(g, start,
distance_heuristic<mygraph_t, cost>(goal),
boost::weight_map(my_custom_weight_map) // THIS LINE ADDED
.predecessor_map(make_iterator_property_map(p.begin(), get(boost::vertex_index, g)))
.distance_map(make_iterator_property_map(d.begin(), get(boost::vertex_index, g)))
.visitor(astar_goal_visitor<vertex>(goal))
);
And the result is:
Start vertex: A
Goal vertex: E
Shortest path from A to E: A -> B -> C -> D -> E
Total travel time: 210
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <iostream>
#include <list>
// auxiliary types
struct location {
float y, x; // lat, long
};
typedef float cost;
// euclidean distance heuristic
template <class Graph, class CostType>
class distance_heuristic : public boost::astar_heuristic<Graph, CostType> {
public:
typedef typename boost::graph_traits<Graph>::vertex_descriptor Vertex;
distance_heuristic(Vertex goal) : m_goal(goal) {}
CostType operator()(Vertex /*u*/) {
return 0; // Not really needed here
}
private:
Vertex m_goal;
};
struct found_goal {}; // exception for termination
// visitor that terminates when we find the goal
template <class Vertex> class astar_goal_visitor : public boost::default_astar_visitor {
public:
astar_goal_visitor(Vertex goal) : m_goal(goal) {}
template <class Graph> void examine_vertex(Vertex u, Graph &/*g*/) {
if (u == m_goal)
throw found_goal();
}
private:
Vertex m_goal;
};
int main() {
// specify some types
typedef boost::adjacency_list<boost::listS, boost::vecS,
boost::undirectedS, boost::no_property,
boost::property<boost::edge_weight_t, cost>
> mygraph_t;
typedef boost::property_map<mygraph_t, boost::edge_weight_t>::type WeightMap;
typedef mygraph_t::vertex_descriptor vertex;
typedef mygraph_t::edge_descriptor edge_descriptor;
typedef std::pair<int, int> edge;
enum nodes { A, B, C, D, E, N };
const char *name[] = { "A", "B", "C", "D", "E", "F" };
edge edge_array[] = {
edge(A, B), edge(B, C), edge(C, D), edge(D, E), edge(B, D) // B to D is the problem here, it should be excluded
};
cost weights[] = { // estimated travel time (mins)
// 107, 174, 283, 71, 436
35, 58, 94, 23, 145
};
unsigned int num_edges = sizeof(edge_array) / sizeof(edge);
// create graph
mygraph_t g(N);
WeightMap weightmap = get(boost::edge_weight, g);
for (std::size_t j = 0; j < num_edges; ++j) {
edge_descriptor e;
bool inserted;
boost::tie(e, inserted) = add_edge(edge_array[j].first, edge_array[j].second, g);
weightmap[e] = weights[j];
}
// pick start/goal
vertex start = A;
vertex goal = E;
std::cout << "Start vertex: " << name[start] << std::endl;
std::cout << "Goal vertex: " << name[goal] << std::endl;
std::vector<mygraph_t::vertex_descriptor> p(num_vertices(g));
using boost::get;
// do a real edit
std::vector<cost> d(num_vertices(g));
auto my_custom_weight_map =
boost::make_transform_value_property_map(
[](float w) { return w>100? 10*w : w; },
boost::get(boost::edge_weight, g));
try {
// call astar named parameter interface
astar_search_tree(g, start,
distance_heuristic<mygraph_t, cost>(goal),
boost::weight_map(my_custom_weight_map)
.predecessor_map(make_iterator_property_map(p.begin(), get(boost::vertex_index, g)))
.distance_map(make_iterator_property_map(d.begin(), get(boost::vertex_index, g)))
.visitor(astar_goal_visitor<vertex>(goal))
);
} catch (found_goal fg) { // found a path to the goal
std::list<vertex> shortest_path;
for (vertex v = goal;; v = p[v]) {
shortest_path.push_front(v);
if (p[v] == v)
break;
}
std::cout << "Shortest path from " << name[start] << " to " << name[goal] << ": ";
std::list<vertex>::iterator spi = shortest_path.begin();
std::cout << name[start];
for (++spi; spi != shortest_path.end(); ++spi)
std::cout << " -> " << name[*spi];
std::cout << std::endl << "Total travel time: " << d[goal] << std::endl;
return 0;
}
std::cout << "Didn't find a path from " << name[start] << "to" << name[goal] << "!" << std::endl;
return 0;
}

Related

How to create a boost property map between rdkit molecular bond graph edges and BGL biconnected component identifiers?

The rdkit library provides a molecule class ROMol that provides a member function getTopology that returns a BGL graph of type adjacency_list<vecS, vecS, undirectedS, Atom *, Bond *>. I understand that the rdkit type Bond defines the edge properties of the graph. I know that Bond provides a member function getIdx that returns a unique integer identifying the bond, so the graph must have a notion of edge numbering.
To make use of the BGL algorithm for biconnected_components one requires a component property map that maps objects of the edge descriptor type of the graph (I understand this type is Bond?) to component identifying integers. As rdkit does not deal in biconnected components, I conclude this property map must be exterior to the graph. In this case, the BGL documentation suggests to use the iterator_property_map adaptor class (see section "Exterior Properties"). I struggle to determine the correct template arguments for iterator_property_map to get the required property map. If this is the correct approach what are the missing template arguments?
Unfortunately I did not get very far with my code before getting lost in the BGL documentation:
void my_function(const RDKit::ROMol& molecule) {
const RDKit::MolGraph& molecule_graph{molecule.getTopology()};
using EdgesSize =
boost::graph_traits<RDKit::MolGraph>::edges_size_type; // map value type
using Edge =
boost::graph_traits<RDKit::MolGraph>::edge_descriptor; // map key type
std::vector<EdgesSize> component_map(boost::num_edges(molecule_graph)
); // range, begin() yields random access iterator
// boost::iterator_property_map<?>;
// boost::biconnected_components(molecule_graph, ?);
}
one requires a component property map that maps objects of the edge descriptor type of the graph (I understand this type is Bond?)
Edge descriptors are internal descriptors, sort of like stable iterators. E.g.
edge_descriptor e = *edges(g).begin();
Edge properties are entirely separate notions. You get the edge properties from an edge descriptor like so:
Bond* e_props = g[e]; // equivalent to:
e_props = get(boost::edge_bundle, g, e);
Unsurprisingly the same goes for vertex descriptors vs. properties:
Atom* first_a_prop = g[vertex(0, g)];
The Complication
A notable difference between the two descriptor types is that - only because the graph type is using vecS as the vertex container selector - the vertex descriptor is guaranteed to be integral, where edge descriptor is opaque (similar to a void*).
Hence, the edge-decriptor cannot be the key type to a vector-based property map (because it would require an integral indexer).
Instead make an associative property-map:
// OUT: ComponentMap c
// must be a model of Writable Property Map. The value type should be
// an integer type, preferably the same as the edges_size_type of the
// graph. The key type must be the graph's edge descriptor type.
std::map<edge_descriptor, int> component_map;
auto c = boost::make_assoc_property_map(component_map);
Sample
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/biconnected_components.hpp>
#include <iostream>
struct Atom {
};
struct Bond {
int idx_ = 42;
int getIdx() const { return idx_; }
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Atom*, Bond*>;
using vertex_descriptor = G::vertex_descriptor;
using edge_descriptor = G::edge_descriptor;
int main() {
std::array<Atom, 4> atoms;
std::array<Bond, 2> bonds{Bond{111}, Bond{222}};
G g;
add_vertex(atoms.data()+0, g);
add_vertex(atoms.data()+1, g);
add_vertex(atoms.data()+2, g);
add_vertex(atoms.data()+3, g);
// using the fact that vertex_descriptor is 0..3 here:
add_edge(0, 2, bonds.data()+0, g);
add_edge(2, 3, bonds.data()+1, g);
{
// OUT: ComponentMap c
// must be a model of Writable Property Map. The value type should be
// an integer type, preferably the same as the edges_size_type of the
// graph. The key type must be the graph's edge descriptor type.
std::map<edge_descriptor, int> component_map;
auto c = boost::make_assoc_property_map(component_map);
// Now use it:
size_t n = boost::biconnected_components(g, c);
for (auto [e, component_id] : component_map) {
std::cout << "Edge " << e << " (idx:" << g[e]->getIdx()
<< ") mapped to component " << component_id << " out of "
<< n << "\n";
}
}
{
std::map<edge_descriptor, int> component_map;
auto c = boost::make_assoc_property_map(component_map);
// also writing articulation points:
[[maybe_unused]] auto [n, out] = boost::biconnected_components(
g, c,
std::ostream_iterator<vertex_descriptor>(
std::cout << "articulation points: ", " "));
std::cout << "\n";
}
}
Prints
Edge (0,2) (idx:111) mapped to component 1 out of 2
Edge (2,3) (idx:222) mapped to component 0 out of 2
articulation points: 2
Advanced Example
You can force a vector as mapping storage, but that requires you to map the edges (bonds) to a contiguous integral range [0..num_edges(g)).
I couldn't assume that getIdx() satisfies the criterion, but if it did:
// first map edges to 0..num_edges using getIdx
auto edge_index = boost::make_function_property_map<edge_descriptor>(
[&g](edge_descriptor e) { return g[e]->getIdx(); });
// provide num_edges storage for component-ids:
std::vector<int> component_ids(num_edges(g));
// project the vector through edge_index to make a Writable Property
// Map indexed by edge_descriptor;
auto c = boost::make_safe_iterator_property_map(component_ids.begin(),
component_ids.size(), edge_index);
Let's apply it to the graph from the documentation:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/biconnected_components.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <iostream>
enum letter : char { A, B, C, D, E, F, G, H, I };
struct Atom {
Atom(letter) {}
};
struct Bond {
int idx_;
int getIdx() const { return idx_; }
};
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Atom*, Bond*>;
using vertex_descriptor = Graph::vertex_descriptor;
using edge_descriptor = Graph::edge_descriptor;
int main() {
std::array<Atom, 9> atoms{A, B, C, D, E, F, G, H, I};
std::array<Bond, 11> bonds{
{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}}};
Graph g;
for (auto& atom : atoms)
add_vertex(&atom, g);
// using the fact that vertex_descriptor is vertex index:
add_edge(A, B, &bonds.at(0), g);
add_edge(A, F, &bonds.at(1), g);
add_edge(A, G, &bonds.at(2), g);
add_edge(B, C, &bonds.at(3), g);
add_edge(B, D, &bonds.at(4), g);
add_edge(B, E, &bonds.at(5), g);
add_edge(C, D, &bonds.at(6), g);
add_edge(E, F, &bonds.at(7), g);
add_edge(G, H, &bonds.at(8), g);
add_edge(G, I, &bonds.at(9), g);
add_edge(H, I, &bonds.at(10), g);
// OUT: ComponentMap c
// must be a model of Writable Property Map. The value type should be
// an integer type, preferably the same as the edges_size_type of the
// graph. The key type must be the graph's edge descriptor type.
// first map edges to 0..10 using getIdx
auto edge_index = boost::make_function_property_map<edge_descriptor>(
[&g](edge_descriptor e) { return g[e]->getIdx(); });
// provide num_edges storage for component-ids:
std::vector<int> component_ids(num_edges(g));
// project the vector through edge_index to make a Writable Property
// Map indexed by edge_descriptor;
auto c = boost::make_safe_iterator_property_map(component_ids.begin(),
component_ids.size(), edge_index);
// Now use it:
size_t n = boost::biconnected_components(g, c);
for (auto e : boost::make_iterator_range(edges(g))) {
// edge_index or getIdx, equivalent here:
assert(edge_index[e] == g[e]->getIdx());
auto idx =edge_index[e];
auto cid = component_ids.at(idx);
std::cout << "Edge " << e << " (idx:" << idx << ") mapped to component "
<< cid << " out of " << n << "\n";
}
}
Which prints prints the expected mapping
Edge (0,1) (idx:0) mapped to component 1 out of 4
Edge (0,5) (idx:1) mapped to component 1 out of 4
Edge (0,6) (idx:2) mapped to component 3 out of 4
Edge (1,2) (idx:3) mapped to component 0 out of 4
Edge (1,3) (idx:4) mapped to component 0 out of 4
Edge (1,4) (idx:5) mapped to component 1 out of 4
Edge (2,3) (idx:6) mapped to component 0 out of 4
Edge (4,5) (idx:7) mapped to component 1 out of 4
Edge (6,7) (idx:8) mapped to component 2 out of 4
Edge (6,8) (idx:9) mapped to component 2 out of 4
Edge (7,8) (idx:10) mapped to component 2 out of 4
In fact, if we add a little bit of bonus wizardry, we can render that:

Boost Graph Library: Deleting an edge while using an iterator

Currently I am trying to implement a Girvan Newman Algorithm for a class project using boost graph library in c++.
At the moment, edges and vertices are being added fine, but I am having an issue removing edges. I am using an iterator to add my edges, and am now trying to remove them. Everything functions as expected until I try to remove an edge, and then I hit a segfault. I have tried removing the edge using the conditional remove_edge_if() and tried some of the other boost options - still having the same issue.
enum {A, B, C, D, E};
num_vertices = 5;
//writing out edges in graph
typedef std::pair<int, int> Edge;
Edge edge_array[] = {
Edge(A,B),
Edge(A,D),
Edge(C,A),
Edge(D,C),
Edge(C,E),
Edge(D,E)
};
const int num_edges = sizeof(edge_array)/sizeof(edge_array[0]);
//declare a graph object
Graph g(num_vertices);
//add the edges to the graph object
for(int i=0;i<num_edges;i++){
add_edge(edge_array[i].first, edge_array[i].second, g);
}
//actual algorithm starts here
//get property map for vertex indices
index = get(boost::vertex_index, g);
//calculate all centrality values and store in vector betweenness
calcCentrality();
//set up iterators to run through all edges
boost::graph_traits<Graph>::edge_iterator ei, ei_end;
boost::graph_traits<Graph>::edge_iterator loc;
std::tie(ei, ei_end)=edges(g);
for(int i=0;i<num_edges;i++){
std::cout << "(" << index[source(*ei, g)] << "," << index[target(*ei, g)] << ") ";
ei++;
}
std::cout << std::endl;
std::tie(ei, ei_end)=edges(g);
int max_B = 0;
boost::graph_traits<Graph>::vertex_descriptor max_B_edge1, max_B_edge2;
//iterate through all edges-1
for(int i=0;i<num_edges-1;i++){
//check betweenness centrality of edge i
int BC = betweeness[i];
if(BC > max_B){
max_B = BC;
max_B_edge1 = source(*ei, g);
max_B_edge2 = target(*ei, g);
}
ei++;
}
boost::remove_edge(max_B_edge1, max_B_edge2, g);
std::cout << std::endl;
std::tie(ei, ei_end)=edges(g);
for(int i=0;i<num_edges;i++){
std::cout << "(" << index[source(*ei, g)] << "," << index[target(*ei, g)] << ") ";
ei++;
}
std::cout << std::endl;
}
Any guidance on how to resolve this, or a new approach would be extremely helpful. I think the issue is with my iterator
The bigger problem is that you are indexing betweenness by an integer [0, num_edges). This doesn't make a lot of sense unless betweenness is actually an edge centrality map (not the vertex centrality map, or just "centrality map").
But that is making things weirder, because that would suggest an associative container like map<edge_descriptor, double> instead of a vector<>, because edge_descriptor is not an integral type.
Of course, one can paper over this by creating an explicit edge_index property/map and projecting through that to a integral-indexed container, e.g.
std::vector<double> betweenness(num_edges(g));
auto edge_id_map = get(boost::edge_index, g); // or assumed externally supplied?
auto edge_centrality_map = boost::make_safe_iterator_property_map(
betweenness.begin(),
betweenness.size(),
edge_id_map);
Now, if that were the case, one has to wonder why the same map is not used to access the betweenness by descriptor... instead of implicitly recalculating an edge index again in a loop-variable (int i). That is unsafe especially since you're modifying the edges, so re-running the loop will calculate different i for each edge! Oops.
Instead, I'd make betweenness associative, indexed by edge-descriptor. Of course, I can't show the relevant changes, because calcCentrality is missing. Even betweenness is missing (which by the way suggests that it must be a global variable? Not a very robust idea if so).
Let's assume for a moment that you have edge_centrality_map defined as above, or, in my preferred approach, like this:
// more stable idea:
std::map<Graph::edge_descriptor, double> betweenness;
auto edge_centrality_map = boost::make_assoc_property_map(betweenness);
Now the whole... mess with the loop could be replace with:
std::map<Graph::edge_descriptor, double> betweenness;
auto edge_centrality_map = boost::make_assoc_property_map(betweenness);
while (num_edges(g)) {
auto ee = edges(g);
auto [min, max] = std::minmax_element(
ee.first, ee.second, [ecm = edge_centrality_map](auto a, auto b) {
return ecm[a] < ecm[b];
});
remove_edge(*max, g);
print_edges();
}
For good measure I used minmax_element to show off the convenience of using standard algorithms here. We use the lambda to "simply" project through the centrality map. No manual juggling with loop variables and translating to vector indexes. This is less work, but more importantly less room for error.
Here's the code presented modernized and showing the suggested approach:
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/betweenness_centrality.hpp>
#include <iostream>
using Graph = boost::adjacency_list<>;
int main() {
enum { A, B, C, D, E, N /*==5*/ };
Graph g(N);
auto print_edges = [&g] { // helper to avoid repeating code
for (auto e : boost::make_iterator_range(edges(g)))
std::cout << e << " ";
std::cout << std::endl;
};
// add the edges to the graph object
for (auto [s, t] :
std::array{std::pair{A, B}, std::pair{A, D}, std::pair{C, A},
std::pair{D, C}, std::pair{C, E}, std::pair{D, E}}) //
{
add_edge(s, t, g);
}
print_edges();
// actual algorithm starts here
// get property map for vertex indices
auto index = get(boost::vertex_index, g);
// calculate all centrality values and store in vector betweenness
// calcCentrality();
//std::vector<int> betweenness(num_edges(g)); // JUST GUESSING
// more stable idea:
std::map<Graph::edge_descriptor, double> betweenness;
auto edge_centrality_map = boost::make_assoc_property_map(betweenness);
while (num_edges(g)) {
auto ee = edges(g);
auto [min, max] = std::minmax_element(
ee.first, ee.second, [ecm = edge_centrality_map](auto a, auto b) {
return ecm[a] < ecm[b];
});
boost::remove_edge(*max, g);
print_edges();
}
}
Prints dummy output (because the betweenness is defaulted to 0.0 for each edge):
(0,1) (0,3) (2,0) (2,4) (3,2) (3,4)
(0,1) (0,3) (2,0) (2,4) (3,2)
(0,1) (0,3) (2,0) (2,4)
(0,1) (0,3) (2,0)
(0,1) (0,3)
(0,1)
BONUS
In fact, the loop is not very optimal, because instead of repeatedly finding the maximum element of a container, just to remove them can be thought of as sorting, and then removing edges in order.
Live On Compiler Explorer
auto [e_begin, e_end] = edges(g);
std::vector ascending(e_begin, e_end);
std::stable_sort(begin(ascending), end(ascending), edge_compare);
while (not ascending.empty()) {
remove_edge(ascending.back(), g);
ascending.pop_back();
print_edges();
}
Which gives the same output, just much quicker.
Also note that if your chosen edge_centrality_map suffers from
index/iterator/descriptor or reference invalidation in some way on the
remove_edge operation, this is safer, because it only depends on
the stability of edge descriptors in your chosen Graph model.
In fact, you might not even need/want to remove the edges "physically" to save time and work. After all, you know which edges to leave out when printing.

Boost graph max flow algorithm -- question about role of reverse arcs if they are already present in the original input graph

Consider the following original input graph on which the max flow algorithm is applied:
The following code (thanks to user sehe) compiles and runs fine and gives the correct output. Code is reproduced below for completion:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#include <boost/range/adaptors.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
using boost::adaptors::filtered;
using Traits = boost::adjacency_list_traits<boost::vecS, boost::vecS, boost::directedS>;
using V = Traits::vertex_descriptor;
using E = Traits::edge_descriptor;
using Capacity = double;
using Color = boost::default_color_type;
struct VertexProps {
// std::string name;
Color color;
Capacity distance;
E preedcessor;
};
struct EdgeProps {
int id;
Capacity weight, residual;
E reverse;
};
using Graph = boost::adjacency_list<
boost::vecS, boost::vecS, boost::directedS,
VertexProps,
// see https://stackoverflow.com/a/64744086/85371 :(
boost::property<boost::edge_capacity_t, Capacity, EdgeProps>>;
struct MyGraph {
MyGraph(size_t nnodes) : _g(nnodes) {}
void runSimulation(std::vector<std::pair<V, V>> const& arcs,
std::vector<Capacity> const& capacities)
{
reconfigure(arcs, capacities);
Capacity maxflow = solve_max_flow(0, 3);
auto cap = get(boost::edge_capacity, _g);
auto is_source = [this](V v) { return _g[v].color == Color::black_color; };
fmt::print("Max flow {}\nNodes {} are in source subset\n", maxflow,
vertices(_g) | filtered(is_source));
for (E e : boost::make_iterator_range(edges(_g))) {
bool st_cut =
is_source(source(e, _g)) and
not is_source(target(e, _g));
fmt::print("Edge {} (id #{:2}), capacity {:3} {}\n", e, _g[e].id,
cap[e], st_cut ? "(ST Cut)" : "");
}
}
private:
Graph _g;
void reconfigure(auto const& arcs, auto const& capacities)
{
assert(arcs.size() == capacities.size());
for (auto v : boost::make_iterator_range(vertices(_g))) {
// boost::clear_out_edges(v, g);
boost::clear_vertex(v, _g);
}
auto cap = get(boost::edge_capacity, _g);
auto eidx = get(&EdgeProps::id, _g);
auto rev = get(&EdgeProps::reverse, _g);
auto eindex = 0;
for (auto [fr, to] : arcs) {
auto edf = add_edge(fr, to, _g).first;
auto edr = add_edge(to, fr, _g).first;
eidx[edf] = 2 * eindex;
eidx[edr] = eidx[edf] + 1;
cap[edf] = cap[edr] = capacities[eindex];
rev[edf] = edr;
rev[edr] = edf;
++eindex;
}
}
Capacity solve_max_flow(V src, V sink)
{
return boykov_kolmogorov_max_flow(
_g, src, sink,
// named arguments
boost::reverse_edge_map(get(&EdgeProps::reverse, _g))
.residual_capacity_map(get(&EdgeProps::residual, _g))
.vertex_color_map(get(&VertexProps::color, _g))
.predecessor_map(get(&VertexProps::preedcessor, _g))
.distance_map(get(&VertexProps::distance, _g))
// end named arguments
);
}
};
int main() {
MyGraph g{4};
g.runSimulation({{0, 1}, {0, 2}, {1, 2}, {2, 1}, {1, 3}, {2, 3}},
{10, 1, 10, 1, 1, 10});
}
My questions relate particularly to arcs (1->2) and (2->1).
(a) Boost documentation for the max flow algorithm requires the user to explicitly provide the reverse arcs for each of the arcs in the original input graph. So, in the example above, the directed arcs (1 -> 2) and (2 -> 1) are added to the graph object twice in the following code snippet:
auto edf = add_edge(fr, to, _g).first; //Arc (1->2) added, Arc (2->1) added
auto edr = add_edge(to, fr, _g).first; //Arc (2->1) added, Arc (1->2) added
eidx[edf] = 2 * eindex;
eidx[edr] = eidx[edf] + 1;
cap[edf] = cap[edr] = capacities[eindex];
rev[edf] = edr;
rev[edr] = edf;
While in this particular example the correctness of the solution could be seen, is this guaranteed in all cases? That is, can the repeated arc addition of the same arc multiple times (once as the forward arc, and once as the reverse arc) to the graph object cause any internal stuff to break in the boost algorithm for this problem?
(b) The boost documentation states the following:
Remarks: While the push-relabel method states that each edge in E^T has
to have capacity of 0, the reverse edges for this algorithm ARE
allowed to carry capacities. If there are already reverse edges in the
input Graph G, those can be used. This can halve the amount of edges
and will noticeably increase the performance.
Does this mean that in this particular case, when I add the forward arc (1->2), I need NOT explicitly add the reverse arc (2->1) and likewise when I add the forward arc (2->1), I need NOT explicitly add the reverse arc (1->2) as happened in the code snippet above?

can boost vf2 deal with muti-graph like this situation?

I want to use vf2 in this situation.
Graph gsmall,glarge;
add_vertex(vertex_prop('a'),gsmall);
add_vertex(vertex_prop('b'),gsmall);
add_edge(0, 1, edge_prop('m'), gsmall);
add_vertex(vertex_prop('a'),glarge);
add_vertex(vertex_prop('b'),glarge);
add_edge(0, 1, edge_prop('m'), glarge);
add_edge(0, 1, edge_prop('n'), glarge);
std::cout << is_subgraph_isomorphic(gsmall,glarge) << std::endl;
If the pattern's property of edge can match with graph's part of properties of edge, then return true, but now it must match all. That example returns false. I want to make it true, so how?
Edit:
I solved this question. Use vector and overload operator "=="
http://coliru.stacked-crooked.com/a/6307210b2861bc63
But I found another problem. It will give wrong results when there is self-loops in graph.
http://coliru.stacked-crooked.com/a/46d336ecfddbbab9 is true
but http://coliru.stacked-crooked.com/a/413d56146ceffd42 is false.
I think they are both ture. I can't understand how it could be like this.
Please help me again! Thanks!
Boost can deal with it. However, you're not looking for a isomorphism in the sense of the library:
An isomorphism between two graphs G1=(V1, E1) and G2=(V2, E2) is a bijective mapping M of the vertices of one graph to vertices of the other graph that preserves the edge structure of the graphs
So, for all corresponding vertices, the same edges need to be present. In other words, the subgraph may be smaller (lower order) but each vertex must have equivalent structure (this implies the same number of edges).
In your case the small graph is structurally different because the large graph has a self loop, but the small doesn't. (The self loop is significant because both vertices exist in the subgraph).
If you really think for your purpose you need to ignore self loops, you'll have to filter them out.
Here's an example that employs the filtered_graph adaptor to achieve that:
Live On Coliru
#include <vector>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/vf2_sub_graph_iso.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/container/small_vector.hpp>
template <typename SortedRange1, typename SortedRange2,
typename V = std::common_type_t<typename boost::range_value<SortedRange1>::type, typename boost::range_value<SortedRange2>::type>,
typename Cmp = std::less<V> >
static inline bool has_intersection(SortedRange1 const& a, SortedRange2 const& b, Cmp cmp = {}) {
auto equivalent = [cmp](V const& a, V const& b)
{ return !cmp(a,b) && !cmp(b,a); };
auto ai = a.begin();
auto bi = b.begin();
while (ai != a.end() && (bi = b.lower_bound(*ai)) != b.end())
if (equivalent(*ai++, *bi))
return true;
return false;
}
// Define graph type
using Label = char;
struct EdgeProperties {
using Labels = boost::container::flat_set<char, std::less<>, boost::container::small_vector<char, 3> >;
EdgeProperties(std::initializer_list<Label> elabels = {}) :_elabels(elabels) {}
bool operator==(EdgeProperties const& other) const {
return has_intersection(_elabels, other._elabels);
}
Labels _elabels;
};
typedef boost::property<boost::edge_name_t, EdgeProperties> edge_prop;
typedef boost::property<boost::vertex_name_t, long/*, boost::property<boost::vertex_index_t, int>*/ > vertex_prop;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, vertex_prop, edge_prop> Graph;
int main()
{
Graph gsmall, glarge;
add_vertex(vertex_prop('a'),gsmall);
add_vertex(vertex_prop('b'),gsmall);
add_edge(0, 1, edge_prop({'m'}), gsmall);
//add_edge(0, 0, edge_prop({'n'}), gsmall);
add_vertex(vertex_prop('a'),glarge);
add_vertex(vertex_prop('b'),glarge);
add_vertex(vertex_prop('c'),glarge);
add_edge(0, 1, edge_prop({'m'}), glarge);
add_edge(0, 0, edge_prop({'n'}), glarge);
add_edge(0, 2, edge_prop({'o'}), glarge);
// Create predicate of edge
auto edge_comp = make_property_map_equivalent(
get(boost::edge_name, gsmall),
get(boost::edge_name, glarge));
// Create callback
boost::vf2_print_callback<Graph, Graph> callback(gsmall, glarge);
struct FilterSelfEdges {
Graph const* _g;
bool operator()(Graph::edge_descriptor ed) const {
return source(ed, *_g) != target(ed, *_g);
}
};
using Filtered = boost::filtered_graph<Graph, FilterSelfEdges>;
// Execute
const bool result = boost::vf2_subgraph_iso(
gsmall, Filtered(glarge, FilterSelfEdges{&glarge}), callback, boost::vertex_order_by_mult(gsmall),
boost::edges_equivalent(edge_comp));
std::cout << "subgraph isomorphic? " << std::boolalpha << result << std::endl;
}
Prints
(0, 0) (1, 1)
subgraph isomorphic? true

Augment custom weights to edge descriptors in boost::grid_graph

I am using BGL for a custom AStar search. Basically, the nodes of the graph correspond to the cells of a map, and each cell has an elevation.
I've created an cell traversal score function stepTime which takes in the elevation of two cells, and outputs a cost function. I wanna add this cost function to the edge weights in my boost graph.
How do I go about this? I've seen functions using
auto weightmap = make_transform_value_property_map
to create a weight map, but how do I update the weights according to the output of:
double stepTime(const vertex_descriptor& source, const vertex_descriptor& target, const std::vector<uint8_t>& elevation)
but how do I update the weights according to the output of:
double stepTime(const vertex_descriptor& source, const vertex_descriptor& target, const std::vector<uint8_t>& elevation)
I have no clue where you get the elevation vector from, but I guess that's your problem.
The source and target vertices are easily gotten from the graph itself, so here goes:
auto custom = boost::make_function_property_map<Graph::edge_descriptor>(
[&g,&elevation](Graph::edge_descriptor e) {
return stepTime(boost::source(e, g), boost::target(e, g), elevation);
});
Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <iostream>
using Graph = boost::adjacency_list<>;
double stepTime(const Graph::vertex_descriptor& source, const Graph::vertex_descriptor& target, const std::vector<uint8_t>& elevation) {
std::cout << __FUNCTION__ << "(" << source << ", " << target << ", {" << elevation.size() << " elements})\n";
return 42;
}
int main() {
Graph g(10);
add_edge(4, 5, g);
add_edge(2, 8, g);
add_edge(5, 1, g);
add_edge(1, 3, g);
std::vector<uint8_t> const elevation { 1,2,3,4,5,6 }; // or whatevs
// custom weight map
auto custom = boost::make_function_property_map<Graph::edge_descriptor>(
[&g,&elevation](Graph::edge_descriptor e) {
return stepTime(boost::source(e, g), boost::target(e, g), elevation);
});
// pass it to an algorithm directly, or wrap it in a named-parameter object:
auto param = boost::weight_map(custom);
param.weight_map2(custom); // or as the alternative weight map
}