I'm successfully using boost graph's component finder to assign color, i.e. the component's index to every vertex in my graph like so:
#include <boost/graph/connected_components.hpp>
#include <boost/graph/adjacency_list.hpp>
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> g;
std::vector<int> compon_map(boost::num_vertices(g));
int number_of_components = boost::connected_components(g, &compon_map[0]);
This will then yield a different number_of_components after each iteration in my simulation (not shown) because I do
boost::clear_vertex(v, g);
in between to erase some edges based on some condition.
The thing is that in my simulation I want to write out some property (say weight) of all edges, and the length of the edge iterator needs to stay constant (dataset restrictions).
My question is therefore: Is there a way to pass some edge property like a
int L = boost::num_edges(g);
std::vector<bool> is_still_existent(L); // or
std::vector<double> edge_weights(L);
to the boost::connected_components (that then counts edges only based on that property) or is there another way to trick the edge iterator to stay at the initial length even after having removed edges?
Thanks in advance for any hints :)
Yes. You can use a filtered graph adaptor with an edge filter. I have several answers up on SO showing how to use it, but will see whether I can usefully create an example based on on your snippet.
So I made a self-contained sample¹: Live On Coliru,
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/graphviz.hpp>
#include <random>
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;
using W = double;
int main(int argc, char** argv) {
G g;
auto seed = 9353; // fixed seed for demo
if (argc > 1) {
seed = atol(argv[1]);
std::cerr << "Using PRNG seed: " << seed << "\n";
}
std::mt19937 engine(seed);
auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
boost::generate_random_graph(g, 10, 6, engine);
std::map<E, W> weights;
for (auto e : boost::make_iterator_range(edges(g)))
weights[e] = weight_gen();
std::vector<int> components(boost::num_vertices(g));
auto cmap = boost::make_iterator_vertex_map(components.data());
int n = boost::connected_components(g, cmap);
std::cerr << n << " components\n";
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, g));
dp.property("style", boost::make_constant_property<V>(std::string("filled")));
dp.property("color",
boost::make_transform_value_property_map(
[](int componentid) {
static std::array cc{"red", "green", "yellow",
"blue", "brown", "black",
"orange", "purple"};
return cc[componentid % cc.size()];
},
cmap));
dp.property("label", boost::make_assoc_property_map(weights));
boost::write_graphviz_dp(std::cout, g, dp);
}
which generates a pseudo-random graph:
Let's add some filtering to it:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/graphviz.hpp>
#include <random>
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;
using W = double;
struct NonRemoved {
std::set<E> const* _ref;
bool operator()(E e) const { return not _ref->contains(e); }
};
int main(int argc, char** argv)
{
G g;
auto seed = 9353; // fixed seed for demo
if (argc > 1) {
seed = atol(argv[1]);
std::cerr << "Using PRNG seed: " << seed << "\n";
}
std::mt19937 engine(seed);
auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
boost::generate_random_graph(g, 10, 6, engine);
std::map<E, W> weights;
for (auto e : boost::make_iterator_range(edges(g)))
weights[e] = weight_gen();
std::vector<int> components(boost::num_vertices(g));
auto cmap = boost::make_iterator_vertex_map(components.data());
auto random_edges = [&g] {
auto [f,l] = edges(g);
std::deque<E> re(f,l);
std::random_shuffle(begin(re), end(re));
return re;
}();
std::set<E> removed;
NonRemoved predicate{&removed};
boost::filtered_graph<G, NonRemoved, boost::keep_all> f(g, predicate, {});
do {
int n = boost::connected_components(f, cmap);
std::cerr << n << " components\n";
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, f));
dp.property("style", boost::make_constant_property<V>(std::string("filled")));
dp.property("color",
boost::make_transform_value_property_map(
[](int componentid) {
static std::array cc{"red", "green", "yellow",
"blue", "brown", "black",
"orange", "purple"};
return cc[componentid % cc.size()];
},
cmap));
dp.property("color",
boost::make_function_property_map<E>([&removed](E e) {
return removed.contains(e) ? "red" : "blue";
}));
dp.property("label",
boost::make_function_property_map<E>([&removed, &weights](E e) {
if (removed.contains(e))
return std::string("REMOVED");
return std::to_string(weights.at(e));
}));
std::ofstream ofs("graph_" + std::to_string(random_edges.size()) + ".dot");
boost::write_graphviz_dp(ofs, f, dp);
removed.insert(random_edges.front());
random_edges.pop_front();
} while (not random_edges.empty());
}
Now writes a series of graph_XXX.dot graphs that display as:
¹ (changing the vertex container selector to vecS for simplicity)
Related
I have to write code that can parse a file like this:
digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
}
Each vertex has label necessarily, but also it may have an unknown number of properties. Moreover, these properties can be own for each vertex. With edges everything is simple - only one label. I would like not to write my own parser, but to use read_graphviz. Tell me, please, how can this be done?
It would be perfect if the code worked with such bundled property:
struct Vertex {
std::string label;
std::map<std::string, std::string> attributes;
};
I wrote a code that parses this:
digraph G {
0[label="person" attributes="name=James_Cameron"];
1[label="film" attributes="title=Avatar_2;year=2022"];
0->1 [label="directed"];
}
But this format looks creepy.
My code:
#include <iostream>
#include <vector>
#include <map>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
struct Vertex {
std::string raw_attrs;
std::string label;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string label;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, Vertex, Edge> Graph;
int main(int argc, char** argv) {
Graph graph;
boost::dynamic_properties dp(boost::ignore_other_properties);
dp.property("label", boost::get(&Vertex::label, graph));
dp.property("attributes", boost::get(&Vertex::raw_attrs, graph));
dp.property("label", boost::get(&Edge::label, graph));
std::ifstream f("file.txt");
boost::read_graphviz(f, graph, dp);
// transform raw_attrs to attributes
}
I promised to find the time to demonstrate the support for dynamic attributes in BGL read_graphviz.
I'll do that by implementing a roundtripping
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Note that it's a logical roundtrip. It doesn't promise to preserve formatting or node id numbering.
0. Preliminaries
Some useful definitions:
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
1. Writing The Graph
The simpler part first. We use the overload that takes a writer interface. We can hack it in few lines:
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
We supply the vertex writer as the attributes writer, and write the one edge property (relation) as the label.
2. Reading The Graph
You can pass a generator function to the constructor of dynamic_properties. This will be invoked when a new attribute is found.
G do_read(std::istream& is) {
G g;
// ...
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
Let's use a lambda to define the newattr generator function:
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
Where make_dyn is a simple helper to wrap a concrete property map (like our function property map) in a shared pointer to a dynamic property map:
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
In production code you would probably make this a free function, which would be more re-usable and slightly more readable by getting rid of the decltype expression.
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/function_property_map.hpp>
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
G do_read(std::istream& is) {
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
G g;
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Prints
g++ -std=c++20 -O2 -pedantic -pthread main.cpp -lboost_graph && ./a.out
digraph G {
0[label=person, name="James Cameron"];
1[label=film, title="Avatar 2", year=2022];
0->1 [label=directed];
}
Writing a parser is not difficult. Here is the code for an application that parses the vertices. I hope you do not think it is creepy.
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
class cVertex
{
int userIndex;
std::string label;
std::map<std::string, std::string> attributes;
static std::vector<cVertex> myVertex;
public:
void read(const std::string &fname);
std::string textVertexList();
private:
void parseVertexLine(const std::string &line);
};
std::vector<cVertex> cVertex::myVertex;
void cVertex::read(const std::string &fname)
{
std::ifstream ifs(fname);
if (!ifs.is_open())
{
std::cout << "Cannot open file\n";
exit(1);
}
std::string line;
getline(ifs, line);
while (getline(ifs, line))
{
if (line.find("->") != -1)
break;
parseVertexLine(line);
}
}
std::string cVertex::textVertexList()
{
std::stringstream ss;
for (auto &v : myVertex)
{
ss << "\nvertex " << v.userIndex
<< " label is " << v.label << "\n";
for (auto &a : v.attributes)
{
ss << a.first << " | " << a.second << "\n";
}
}
return ss.str();
}
void cVertex::parseVertexLine(const std::string &line)
{
cVertex v;
v.userIndex = atoi(line.c_str());
int p = line.find("label");
int q = line.find("\"", p + 7);
v.label = line.substr(p + 7, q - p - 7);
while (1)
{
std::pair< std::string,std::string > ap;
p = line.find("=", q);
if (p == -1)
break;
ap.first = line.substr(q + 2, p - q - 2);
p = line.find("\"", p);
q = line.find("\"", p + 1);
ap.second = line.substr(p + 1, q - p - 1);
v.attributes.insert(ap);
int dbg = 0;
}
myVertex.push_back(v);
}
main()
{
cVertex theVertex;
theVertex.read("data.txt");
std::cout << theVertex.textVertexList();
return 0;
}
I'm new to boost::graph (and boost really). I want to use boost::filtered_graph many times on the same original graph, and use the write_graphviz function to let me visualise the results. I think my understanding must be off though because the following code isn't doing what I think it should: to output the same graph with print_graph and write_graphviz.
MWE (compiled with C++14, gcc 9.3 on Ubuntu 20.04; boost version 1.73):
#include <cstdio>
#include <fstream>
#include <iostream>
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
using namespace std;
typedef boost::adjacency_list< boost::vecS, boost::vecS > Graph;
typedef boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<Graph>::vertex_iterator vertex_iterator;
template <typename GraphType>
struct uniform_random_vertex_filter
{
uniform_random_vertex_filter() : prob(1.0) {} // default constructor is required
uniform_random_vertex_filter(float p) : prob(p) {}
bool operator()(const typename boost::graph_traits<GraphType>::vertex_descriptor& v) const
{
return drand48() < prob; // randomly select some vertices
}
private:
float prob;
};
int main(int argn, char **argc) {
unsigned int n = 5;
ofstream of;
Graph g(n);
vertex_iterator vit, uit, vend;
// build a complete graph on n vertices (I'm sure there's a simpler command for this):
for (boost::tie(vit,vend) = vertices(g); vit != vend; ++vit) {
for (uit = vit; uit != vend; ++uit) {
if (uit == vit) { continue; }
add_edge(*vit, *uit, g);
}
}
std::cout << "Original graph (OriginalGraph.dot):" << std::endl;
boost::print_graph(g);
of.open("OriginalGraph.dot", std::ios::trunc);
boost::write_graphviz(of, g);
of.close();
uniform_random_vertex_filter<Graph> vfilter(0.5);
boost::filtered_graph<Graph, boost::keep_all, uniform_random_vertex_filter<Graph> >
filteredGraph(g, boost::keep_all(), vfilter);
std::cout << "Filtered graph -- random selection of vertices (RandomVertexSelection.dot):" << std::endl;
boost::print_graph(filteredGraph);
Graph F;
boost::copy_graph(filteredGraph,F);
of.open("RandomVertexSelection.dot", std::ios::trunc);
boost::write_graphviz(of, F);
of.close();
return 0;
}
Which produces this output:
> Debug/BoostGraphFilter
Original graph:
0 --> 1 2 3 4
1 --> 2 3 4
2 --> 3 4
3 --> 4
4 -->
Filtered graph -- random selection of vertices (RandomVertexSelection.dot):
0 --> 1 2 3 4
1 --> 2 3
2 --> 3
>
--- which is fine, but the dot files are:
> cat OriginalGraph.dot
digraph G {
0;
1;
2;
3;
4;
0->1 ;
0->2 ;
0->3 ;
0->4 ;
1->2 ;
1->3 ;
1->4 ;
2->3 ;
2->4 ;
3->4 ;
}
> cat RandomVertexSelection.dot
digraph G {
0;
1;
2;
}
Hence the filtered_graph that's printed isn't the same as that written to .dot file (which has lost all the edges in this case).
Can someone please help me understand what I've done wrong?
Your filter is random. And since you didn't retain any state to make it transparent or deterministic, the results are random. Simple as that.
Ironically, at the same time you managed to get completely deterministic results across runs because you fail to use random correctly (e.g. seeding the generator).
In your case, the simplest would be to copy before first use: Live On Coliru
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
struct uniform_random_vertex_filter {
float prob = 1.0f;
bool operator()(auto v) const {
return drand48() < prob;
}
};
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
for (int pct = 30; pct < 100; pct+=10) {
Graph F;
boost::copy_graph(boost::filtered_graph( //
g, //
boost::keep_all{},
uniform_random_vertex_filter{pct / 100.0f}),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
If you want a stable random "cut" of a graph, make the filter stateful. This could be handy e.g. if you have a graph too large to copy.
Fixing The Random
As a sidenote, fixing the random to actually be seeded and reliably uniform:
Live On Coliru
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
#include <fstream>
#include <iostream>
#include <random>
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
using Filter = std::function<bool(Graph::vertex_descriptor)>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
std::mt19937 urbg{std::random_device{}()};
for (int pct = 30; pct < 100; pct += 10) {
Graph F;
std::bernoulli_distribution dist(pct / 100.);
boost::copy_graph(
boost::filtered_graph(g, boost::keep_all{},
Filter([&](auto) { return dist(urbg); })),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
Now prints different random cuts every run.
I'm studying BGL now and I found a tutorial for it. Everything worked fine from it until I reached function to add_named_vertex. Here is a piece of code I have, that does not work as I (and tutorial) expect:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <type_traits>
#include <iostream>
#include <sstream>
#include <string>
boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
boost::property<boost::vertex_name_t, std::string>
>
create_empty_graph() { return {}; }
template<typename graph, typename name_type>
typename boost::graph_traits<graph>::vertex_descriptor
add_named_vertex(const name_type& vertex_name, graph& g) noexcept {
const auto vd = boost::add_vertex(g);
auto vertex_name_map = get(boost::vertex_name, g);
put(vertex_name_map, vd, vertex_name);
return vd;
}
int main()
{
auto g = create_empty_graph();
const auto va = add_named_vertex("One", g);
const auto vb = add_named_vertex("Two", g);
boost::add_edge(va,vb, g);
std::stringstream f;
boost::write_graphviz(f, g);
std::cout << f.str() << std::endl;
return 0;
}
I expect:
digraph G {
0[label=One];
1[label=Two];
0->1;
}
But here is what I got:
digraph G {
0;
1;
0->1;
}
As you can see, there are no labels in the output of this code. Could you please tell me, what am I missing? Is it expected behavior?
Tried both clang++ and gcc and for range of Boost version (1.69 - 1.71).
Yes it is expectd behavior. To print the labels, add a property writer:
auto vlw = boost::make_label_writer(boost::get(boost::vertex_name, g));
boost::write_graphviz(f, g, vlw);
See it Live on Coliru
Or, as I prefer, use write_graphviz_dp to use dynamic_properties:
boost::dynamic_properties dp;
dp.property("node_id", boost::get(boost::vertex_index, g));
dp.property("label", boost::get(boost::vertex_name, g));
boost::write_graphviz_dp(f, g, dp);
See it Live on Coliru
It may seem like more work, but it is easy and flexible with many vertex/edge properties. You can search my answers for good examples of this.
Both the above solutions print
digraph G {
0[label=One];
1[label=Two];
0->1 ;
}
BONUS
You don't need the add_named_vertex function. You can initialize the property directly with boost::add_vertex:
const auto va = add_vertex({"One"}, g);
const auto vb = add_vertex({"Two"}, g);
add_edge(va, vb, g);
The main question:
I am able to create a graph implementation with information structs assigned to the vertices and edges:
struct vertex_info {std::string name;};
struct edge_info {std::string name;};
typedef boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::undirectedS,
vertex_info,
edge_info> UndirectedGraph;
And for an instance of UndirectedGraph, g, I can easily iterate over the vertices, and access their information:
for(size_t i=0; i<boost::num_vertices(g); i++){
std::cout << g[i].name << std::endl;
}
but I am unable to figure out how to do the same for the edges. I have come across some iterators to loop over all the edges, but I cannot access these edges as some kind of object or something with properties. How can I access the edge information of g?
A minimal working demonstration:
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include "boost/graph/graph_traits.hpp"
#include "boost/graph/adjacency_list.hpp"
int main(int argc, char *argv[])
{
//Add vertex information struct
struct vertex_info {
std::string name;
};
//Add edge information struct
struct edge_info {
std::string name;
};
//Typedef my graph implimentation
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, vertex_info, edge_info> UndirectedGraph;
//Our set of edges, and count N: (0-7) and 8
enum {C, D, G, I, S, J, L, H, N};
const char *name = "CDGISJLH";
//Create a vector of edges
typedef std::pair<int, int> Edge;
std::vector<Edge> edgeVec;
edgeVec.push_back(Edge(C,D));
edgeVec.push_back(Edge(D,G));
edgeVec.push_back(Edge(I,G));
edgeVec.push_back(Edge(G,L));
edgeVec.push_back(Edge(H,G));
edgeVec.push_back(Edge(I,S));
edgeVec.push_back(Edge(S,J));
edgeVec.push_back(Edge(L,J));
edgeVec.push_back(Edge(H,J));
//Now we can initialize our graph using iterators from our above vector
UndirectedGraph g(edgeVec.begin(), edgeVec.end(), N);
std::cout << num_edges(g) << "\n"; //Outputs: 9
//loop over vertices, access "name" property
for(size_t i=0; i<boost::num_vertices(g); i++){
//And add information to the edges
g[i].name = "foo";
}
//We can access the first vertice and print the property
std::cout << g[0].name << std::endl; //Outputs: foo
//Edge iterator for or graph
typedef boost::graph_traits<UndirectedGraph>::edge_iterator edge_iterator;
//Iterate through all the edges
std::pair<edge_iterator, edge_iterator> ei = boost::edges(g);
for(edge_iterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter) {
//How can I access the edge property???
}
}
I have figure out the problem by walking through this example: https://www.boost.org/doc/libs/1_71_0/libs/graph/doc/bundles.html
The fix:
Although I still don't exactly understand how it all works. It seems like you have to use edge_iter as some kind of index into g:
//Edge iterator for or graph
typedef boost::graph_traits<MRFGraph>::edge_iterator edge_iterator;
//Iterate through all the edges
std::pair<edge_iterator, edge_iterator> ei = boost::edges(g);
for(edge_iterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter) {
g[*edge_iter].name = "bar";
std::cout << *edge_iter << ": " << g[*edge_iter].name << std::endl;
}
Output:
If I add this to the minimal working demonstration, it produces the following output:
9
foo
(0,1): bar
(1,2): bar
(3,2): bar
(2,6): bar
(7,2): bar
(3,4): bar
(4,5): bar
(6,5): bar
(7,5): bar
May not be exactly what you are looking for but does achieve what you are after
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
namespace boost {
enum edge_myname_t { edge_myname };
BOOST_INSTALL_PROPERTY(boost::edge, myname);
}
int main(int argc, char* argv[]) {
// Add vertex information struct
struct vertex_info {
std::string name;
};
// Add edge information struct
//struct edge_info {
//std::string name;
//};
using EdgeName = boost::property<boost::edge_myname_t, std::string>;
// Typedef my graph implimentation
using UndirectedGraph =
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
vertex_info, EdgeName>;
// Our set of edges, and count N: (0-7) and 8
enum { C, D, G, I, S, J, L, H, N };
const char* name = "CDGISJLH";
// Create a vector of edges
//using Edge = std::pair<int, int>;
//std::vector<Edge> edgeVec;
//edgeVec.push_back(Edge(C, D));
//edgeVec.push_back(Edge(D, G));
//edgeVec.push_back(Edge(I, G));
//edgeVec.push_back(Edge(G, L));
//edgeVec.push_back(Edge(H, G));
//edgeVec.push_back(Edge(I, S));
//edgeVec.push_back(Edge(S, J));
//edgeVec.push_back(Edge(L, J));
//edgeVec.push_back(Edge(H, J));
// Now we can initialize our graph using iterators from our above vector
UndirectedGraph g(N);
//UndirectedGraph g(edgeVec.begin(), edgeVec.end(), N);
boost::add_edge(C, D, EdgeName("#1"), g);
boost::add_edge(D, G, EdgeName("#2"), g);
boost::add_edge(I, G, EdgeName("#3"), g);
boost::add_edge(G, L, EdgeName("#4"), g);
boost::add_edge(H, G, EdgeName("#5"), g);
boost::add_edge(I, S, EdgeName("#6"), g);
boost::add_edge(S, J, EdgeName("#7"), g);
boost::add_edge(L, J, EdgeName("#8"), g);
boost::add_edge(H, J, EdgeName("#9"), g);
boost::property_map<UndirectedGraph, boost::edge_myname_t>::type get_name =
boost::get(boost::edge_myname, g);
std::cout << num_edges(g) << "\n"; // Outputs: 9
// loop over vertices, access "name" property
for (size_t i = 0; i < boost::num_vertices(g); i++) {
// And add information to the edges
g[i].name = "foo";
}
// We can access the first vertice and print the property
std::cout << g[0].name << std::endl; // Outputs: foo
// Edge iterator for or graph
using EdgeIterator = boost::graph_traits<UndirectedGraph>::edge_iterator;
// Iterate through all the edges
std::pair<EdgeIterator, EdgeIterator> ei = boost::edges(g);
for (EdgeIterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter) {
// How can I access the edge property???
std::cout << get_name[*edge_iter] << "\n";
}
}
I just slightly modified some of the code for my own readability issues.
For reference, check this out.
I'm new to boost graphs and are researching the graph that best fits my need. I need to create a dependency graph and given a vertex, I need access to in and out edges. An adjacency_list with Directed=bidirectionalS is what I'm thinking.
But I need to make sure when I call add_edge and that causes a circular reference then it has to error out. I can't seem to find how to do this.
In general, there's only one way to discover whether a graph is a-cyclic: traverse all nodes.
So you'd just need to check whether the graph is still a-cyclic after adding each edge.
However, depending on how you are adding the nodes, you can optimize. If, e.g. you add edges by traversing nodes from a source in DFS order, you can just keep track of nodes "seen" in the current path and refuse to add an out edge to those.
Simplistic example based on topological_sort Live On Coliru:
#include <iostream> // for std::cout
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/function_output_iterator.hpp>
using namespace boost;
int main()
{
srand(time(0));
typedef adjacency_list<vecS, vecS, bidirectionalS> Graph;
const int num_vertices = 10;
Graph g(num_vertices);
// add random edges to the graph object
for (size_t i = 0; i < 10; ++i)
{
auto f = rand()%num_vertices,
s = rand()%num_vertices;
add_edge(f, s, g);
try {
topological_sort(g, boost::make_function_output_iterator([](int){}));
} catch(not_a_dag const& e)
{
remove_edge(f, s, g);
std::cerr << "dropped edge: " << e.what() << "\n";
}
}
write_graphviz(std::cout, g);
}
Creates random DAGs like
In boost graph BidirectinalS indicates that the edge will be having soruce and target vertices both.
Here is the example for it:
#include <QtCore/QCoreApplication>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/subgraph.hpp>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
using namespace std;
using namespace boost;
typedef boost::subgraph<boost::adjacency_list< boost::listS,
boost::vecS,
boost::bidirectionalS,
boost::property<boost::vertex_index_t, int , property<boost::vertex_color_t, boost::default_color_type > > ,
boost::property<boost::edge_index_t,int, property<boost::edge_color_t , default_color_type> > > >
Graph;
const int num_vertices = 5;
Graph g(num_vertices);
add_edge(0, 1, g);
add_edge(1, 2, g);
add_edge(1, 3, g);
add_edge(2, 4, g);
add_edge(3, 4, g);
boost::graph_traits<Graph>::vertex_iterator VertexItr, VertexItr_end;
boost::graph_traits<Graph>::in_edge_iterator in, in_end;
boost::graph_traits<Graph>::out_edge_iterator out,out_end;
typedef boost::graph_traits < Graph >::adjacency_iterator adjacency_iterator;
// This loop is for getting in edges at vertex
cout<<"In Edge :- "<<endl;
for(boost::tie(VertexItr,VertexItr_end) = vertices(g); VertexItr != VertexItr_end; ++VertexItr) {
cout << *VertexItr << " <-- ";
for (boost::tie(in,in_end) = in_edges(*VertexItr, g); in != in_end; ++in)
cout << source(*in, g) << " ";
cout << endl;
}
// This loop is for getting out edges from vertex
cout<<endl<<"Out Edge :- "<<endl;
for(boost::tie(VertexItr,VertexItr_end) = vertices(g); VertexItr != VertexItr_end; ++VertexItr) {
cout<<*VertexItr<<"--->";
for (boost::tie(out,out_end) = out_edges(*VertexItr, g); out != out_end; ++out)
cout << target(*out, g) << " ";
cout << endl;
}
// This loop is for getting the neighbour vertices of vertex
typedef boost::property_map<Graph, boost::vertex_index_t>::type IndexMap;
IndexMap index = get(boost::vertex_index, g);
cout<<"Adjacent vertices"<<endl;
for(boost::tie(VertexItr,VertexItr_end) = vertices(g); VertexItr != VertexItr_end; ++VertexItr) {
cout<<*VertexItr<<"--->";
std::pair<adjacency_iterator, adjacency_iterator> neighbors =
boost::adjacent_vertices(vertex(*VertexItr,g), g);
for(; neighbors.first != neighbors.second; ++neighbors.first)
{
std::cout << index[*neighbors.first] << " ";
}
cout<<endl;
}
return a.exec();
}
I found this section on the boost documentation discussing how to detect dependencies:
http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/file_dependency_example.html#sec:cycles
But for the adjacency_list the VertexList and EdgeList have to be of type vecS. There's discussion about this here:
How to print a boost graph in graphviz with one of the properties displayed?