1

I'm trying to make a nice print of the verticies proprieties of both Bidder and Item. This is my structure.

struct Graph{
    std::vector<int>bidder2item;
    std::vector<int>item2bidder;
};

namespace Nodes {
    struct Bidder {
        int id;
        int best_item = -1;
        double val_first_best_item = -1.;
        double val_second_best_item = -1.;
    };

    struct Item {
        int id;
        double cost = 0.;
        int high_bidder = -1;
        double high_bid = -1.;
    };

    static inline std::ostream& operator<<(std::ostream& os, Bidder const& b) {
        return os << "BIDDER: id:\t" << b.id << "\tbest_item:\t" << b.best_item << "\tval_first_best_item:\t" << b.val_first_best_item << "\tval_second_best_item\t" << b.val_second_best_item;
    }
    static inline std::ostream& operator<<(std::ostream& os, Item const& i) {
        return os << "BIDDER: id:\t" << i.id << "\cost:\t" << i.cost << "\high_bidder:\t" << i.high_bidder << "\high_bid\t" << i.high_bid;
    }
}

struct Edge {
    double weight;
};

using Nodes::Bidder;
using Nodes::Item;

using Vertex = boost::variant<Bidder, Item>;
using UndirectedGraph = boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS, Vertex, Edge, Graph>; // Vertex Graph
using edge_iterator = boost::graph_traits<UndirectedGraph>::edge_iterator;
using vertex_iterator = boost::graph_traits<UndirectedGraph>::vertex_iterator;

The next function is used to randomly generate a bipartite graph an the second one is to visualize the vertices proprieties.

UndirectedGraph return_graph(std::vector<std::vector<float>> *cost_matrix, const int *n_bidders_items) {

    std::uniform_real_distribution<float> distribution(0., 20.);

    UndirectedGraph random_graph;

    for (int i = 0; i < *n_bidders_items * 2; ++i) {
        if (i < *n_bidders_items) boost::add_vertex(Bidder{i}, random_graph);
        else boost::add_vertex(Item{i}, random_graph);
    }

    random_graph[boost::graph_bundle].bidder2item = *new std::vector<int>(*n_bidders_items, -1);
    random_graph[boost::graph_bundle].item2bidder = *new std::vector<int>(*n_bidders_items, -1);


    // Every left nodes has a connection to every right nodes
    for (int i = 0; i < *n_bidders_items; ++i) {
        for (int j = *n_bidders_items; j < *n_bidders_items * 2; ++j) {
            if (i != j) {
                float value = float(distribution(generator));
                (*cost_matrix)[i][j % (* n_bidders_items)] = value;
                boost::add_edge(i, j, Edge{value}, random_graph);
            }
        }
    }
    printGraph(&random_graph);
    return random_graph;
}

void printGraph(UndirectedGraph const *g) {
    boost::dynamic_properties dp;

    using V = UndirectedGraph::vertex_descriptor;
    using E = UndirectedGraph::edge_descriptor;

    using VertexFilter = std::function<bool(V)>;
    using EdgeFilter = std::function<bool(E)>;
    using FMap = boost::filtered_graph<UndirectedGraph, EdgeFilter, VertexFilter>;


    EdgeFilter any_interconnect = boost::keep_all{};
    VertexFilter bidders = [&g](V v) -> bool { return boost::get<Bidder>(&(*g)[v]); };
    VertexFilter items = [&g](V v) -> bool { return boost::get<Item>(&(*g)[v]); };

    FMap map_bidder = FMap(*g, any_interconnect, bidders);
    FMap map_items = FMap(*g, any_interconnect, items);

    dp.property("map_bidder", map_bidder);
    dp.property("map_items", map_items);
    boost::write_graphviz_dp(std::cout, *g, dp);

}

I have taken ispiration from the following previous posts:

But I coudn't figure out how to make thigs work.

I would like to have something like so:

Bidder: ...proprieties... Edge Weight: weight Item: ...proprieties...

zulle99
  • 51
  • 7

1 Answers1

1

There's a big problem here:

using FMap = boost::filtered_graph<UndirectedGraph, EdgeFilter, VertexFilter>;

No matter what you call FMap, a filtered graph does not become a property map. That's why

dp.property("map_bidder", map_bidder);
dp.property("map_items", map_items);

makes no sense.

If you just want to print the bundle types, no need to complicate with filters:

void printGraph(Graph* g) {
    boost::dynamic_properties dp;
    dp.property("node_id", get(boost::vertex_index,  *g));
    dp.property("label",   get(boost::vertex_bundle, *g));
    dp.property("weight",  get(&EdgeProp::weight,    *g));
    boost::write_graphviz_dp(std::cout, *g, dp);
}

Now there are some warts because dynamic_properties requires that the maps are read & write, so we have to complete the set of IO operations to make things compile:

using VertexProp = boost::variant<Bidder, Item>;
static inline std::istream& operator>>(std::istream& is, VertexProp&) { return is; }

see boost::dynamic_properties and immutable graph object and Boost::Graph: how to import graphviz with custom Vertex class for more background

That should already be enough:

graph G {
0 [label="BIDDER: id:   0       best_item:      -1      val_first_best_item:    -1      val_second_best_item    -1"];
1 [label="BIDDER: id:   1       best_item:      -1      val_first_best_item:    -1      val_second_best_item    -1"];
2 [label="BIDDER: id:   2       best_item:      -1      val_first_best_item:    -1      val_second_best_item    -1"];
3 [label="BIDDER: id:   3       best_item:      -1      val_first_best_item:    -1      val_second_best_item    -1"];
4 [label="BIDDER: id:   4       best_item:      -1      val_first_best_item:    -1      val_second_best_item    -1"];
5 [label="BIDDER: id:   0       cost:   0       high_bidder:    -1      high_bid        -1"];
6 [label="BIDDER: id:   1       cost:   0       high_bidder:    -1      high_bid        -1"];
7 [label="BIDDER: id:   2       cost:   0       high_bidder:    -1      high_bid        -1"];
8 [label="BIDDER: id:   3       cost:   0       high_bidder:    -1      high_bid        -1"];
9 [label="BIDDER: id:   4       cost:   0       high_bidder:    -1      high_bid        -1"];
0--5  [weight=7.42609];
0--6  [weight=6.42883];
0--7  [weight=1.64521];
0--8  [weight=15.1427];
0--9  [weight=13.3676];
1--5  [weight=17.7843];
1--6  [weight=18.2666];
1--7  [weight=2.03375];
1--8  [weight=15.3638];
1--9  [weight=14.0291];
2--5  [weight=15.3731];
2--6  [weight=15.6312];
2--7  [weight=16.2417];
2--8  [weight=14.2055];
2--9  [weight=15.9913];
3--5  [weight=13.4127];
3--6  [weight=18.6377];
3--7  [weight=18.6832];
3--8  [weight=9.63378];
3--9  [weight=4.45048];
4--5  [weight=2.89091];
4--6  [weight=16.1538];
4--7  [weight=17.5303];
4--8  [weight=10.4171];
4--9  [weight=10.4213];
}

OTHER ISSUES

  • Don't write the memory leak operator (*new X)

  • In fact don't write new or delete

  • Also, stop using pointers instead references

  • the condition i != j was never true. This was the result of your overcomplicated index-calculations, simplify and use readable names:

     for (int bidder = 0; bidder < N; ++bidder) {
         for (int item = 0; item < N; ++item) {
             if (bidder != item) {
                 float value = distribution(generator);
    
                 data.cost_matrix[bidder][item] = value;
                 boost::add_edge(bidder, N + item, EdgeProp{value}, g);
             }
         }
     }
    
  • a function named return_graph is poorly named, prefer generateRandomGraph or something

  • a function named generateGraph should not be printing a graph. What use is the printGraph function then?

     return_graph(...); // also prints the graph by the way
    

    Should be

     auto g = generateGraph();
     // ...
     printGraph(g);
    
  • The generation function "takes" a const_matrix. But in reality it returns all the values. Instead of burdening the caller with creating the matrix ahead of time, and making sure the n_bidders_items matches its dimensions (!), just return both!

     using Matrix = std::vector<std::vector<float>>;
     struct Data {
         Matrix cost_matrix;
         Graph  graph;
     };
    
    Data generateData(int N) {
         Data data;
         auto& [cm, g] = data;
    
         data.cost_matrix.assign(N, Matrix::value_type(N, 0));
         std::uniform_real_distribution<float> distribution(0., 20.);
    
         for (int i = 0; i < N; ++i)
             add_vertex(Bidder{i}, g);
         for (int i = 0; i < N; ++i)
             add_vertex(Item{N + i}, g);
    
         GraphProp& gp = g[boost::graph_bundle];
         gp.bidder2item.assign(N, -1);
         gp.item2bidder.assign(N, -1);
    
         // Every left nodes has a connection to every right nodes
         for (int bidder = 0; bidder < N; ++bidder) {
             for (int item = 0; item < N; ++item) {
                 if (bidder != item) {
                     float value = distribution(generator);
    
                     data.cost_matrix[bidder][item] = value;
                     add_edge(bidder, N + item, EdgeProp{value}, g);
                 }
             }
         }
    
         return data;
     }
    

I address all the above in my version below. Things I did not address:

  • It looks a bit like you're on two minds about the type of graph. On the one hand you chose to model as an undirected adjacency_list, but then the use of the properties suggests that the model could actually be an adjacency_matrix or even a grid_graph

Full Demo

Live On Compiler Explorer

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/variant.hpp>
#include <random>

static std::mt19937 generator{std::random_device{}()};

namespace Nodes {
    struct Bidder {
        int    id;
        int    best_item            = -1;
        double val_first_best_item  = -1.;
        double val_second_best_item = -1.;
    };

    struct Item {
        int    id;
        double cost        = 0.;
        int    high_bidder = -1;
        double high_bid    = -1.;
    };

    static inline std::ostream& operator<<(std::ostream& os, Bidder const& b) {
        return os << "BIDDER: id:\t" << b.id << "\tbest_item:\t" << b.best_item
                << "\tval_first_best_item:\t" << b.val_first_best_item
                << "\tval_second_best_item\t" << b.val_second_best_item;
    }
    static inline std::ostream& operator<<(std::ostream& os, Item const& i) {
        return os << "BIDDER: id:\t" << i.id << "\tcost:\t" << i.cost
                << "\thigh_bidder:\t" << i.high_bidder << "\thigh_bid\t"
                << i.high_bid;
    }
    struct EdgeProp {
        double weight;
    };

    using VertexProp = boost::variant<Bidder, Item>;
    static inline std::istream& operator>>(std::istream& is, VertexProp&) { return is; }
} // namespace Nodes

using Nodes::Bidder;
using Nodes::Item;
using Nodes::EdgeProp;
using Nodes::VertexProp;

struct GraphProp {
    std::vector<int> bidder2item;
    std::vector<int> item2bidder;
};

using Graph = boost::adjacency_list<               //
    boost::listS, boost::vecS, boost::undirectedS, //
    VertexProp, EdgeProp, GraphProp>;

using Matrix = std::vector<std::vector<float>>;
struct Data {
    Matrix cost_matrix;
    Graph  graph;
};

Data generateData(int N) {
    Data data;
    auto& [cm, g] = data;

    data.cost_matrix.assign(N, Matrix::value_type(N, 0));
    std::uniform_real_distribution<float> distribution(0., 20.);

    for (int i = 0; i < N; ++i)
        add_vertex(Bidder{i}, g);
    for (int i = 0; i < N; ++i)
        add_vertex(Item{N + i}, g);

    GraphProp& gp = g[boost::graph_bundle];
    gp.bidder2item.assign(N, -1);
    gp.item2bidder.assign(N, -1);

    // Every left nodes has a connection to every right nodes
    for (int bidder = 0; bidder < N; ++bidder) {
        for (int item = 0; item < N; ++item) {
            if (bidder != item) {
                float value = distribution(generator);

                data.cost_matrix[bidder][item] = value;
                add_edge(bidder, N + item, EdgeProp{value}, g);
            }
        }
    }

    return data;
}

void printGraph(Graph& g) {
    boost::dynamic_properties dp;
    dp.property("node_id", get(boost::vertex_index,  g));
    dp.property("label",   get(boost::vertex_bundle, g));
    dp.property("weight",  get(&EdgeProp::weight,    g));
    write_graphviz_dp(std::cout, g, dp);
}

int main() {
    auto [cost_matrix, graph] = generateData(5);
    printGraph(graph);
}

Printed:

graph G {
0 [label="BIDDER: id:   0   best_item:  -1  val_first_best_item:    -1  val_second_best_item    -1"];
1 [label="BIDDER: id:   1   best_item:  -1  val_first_best_item:    -1  val_second_best_item    -1"];
2 [label="BIDDER: id:   2   best_item:  -1  val_first_best_item:    -1  val_second_best_item    -1"];
3 [label="BIDDER: id:   3   best_item:  -1  val_first_best_item:    -1  val_second_best_item    -1"];
4 [label="BIDDER: id:   4   best_item:  -1  val_first_best_item:    -1  val_second_best_item    -1"];
5 [label="BIDDER: id:   5   cost:   0   high_bidder:    -1  high_bid    -1"];
6 [label="BIDDER: id:   6   cost:   0   high_bidder:    -1  high_bid    -1"];
7 [label="BIDDER: id:   7   cost:   0   high_bidder:    -1  high_bid    -1"];
8 [label="BIDDER: id:   8   cost:   0   high_bidder:    -1  high_bid    -1"];
9 [label="BIDDER: id:   9   cost:   0   high_bidder:    -1  high_bid    -1"];
0--6  [weight=5.00229];
0--7  [weight=18.2496];
0--8  [weight=15.024];
0--9  [weight=2.74795];
1--5  [weight=2.54128];
1--7  [weight=5.28732];
1--8  [weight=19.3544];
1--9  [weight=15.8096];
2--5  [weight=1.76579];
2--6  [weight=4.12413];
2--8  [weight=12.156];
2--9  [weight=8.35624];
3--5  [weight=12.4344];
3--6  [weight=1.87166];
3--7  [weight=18.5511];
3--9  [weight=13.3363];
4--5  [weight=6.61118];
4--6  [weight=1.03358];
4--7  [weight=17.1966];
4--8  [weight=2.37933];
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • With minor adjustments: https://godbolt.org/z/na8PMj1YE output can be easily modified to something like tinyurl.com/yc5ats2k – sehe Jul 20 '22 at 16:36
  • Gah. That site doesn't remember the `circo` renderer choice: https://imgur.com/9s9w0KE – sehe Jul 20 '22 at 16:37
  • Thanks a lot again, I removed "if" during the random generation of the bipartite graph since it is possible that Bidder 0 is connected to Item 0 (I remind you that I have to solve the assignemnt problem in a bipartite graph). Further I would ask you how can I obtain an iterator through the boundle proprieties of both Bidders and Items? I saw your answer to [this question](https://stackoverflow.com/a/68937702/19577656), but I can't figure out how to apply that in my case. However thanks a lot for your help! :) – zulle99 Jul 21 '22 at 09:58
  • I don't see a scenario in which iterating over property bundles is useful (you can already iterate over vertex descriptors, and it's trivial to get the bundle from there). So, yeah, if you think you need it that linked answer is exactly how I'd do it (maybe substituting Boost Range with std::ranges if your compiler vendor supports it) – sehe Jul 21 '22 at 10:03
  • Demo [just iterating descriptors](https://godbolt.org/z/fecjh551Y), or using the (not very useful IMO) range adaptors sauce: https://godbolt.org/z/KfdGqzf3Y – sehe Jul 21 '22 at 10:18
  • I have to modify the boundle proprieties in order to store information used by the auction algorithm, that is why I have to iterate through the Bidder and Item verticies – zulle99 Jul 21 '22 at 11:45
  • You don't need anything special to iterate though these. The first example (https://godbolt.org/z/fecjh551Y) shows how – sehe Jul 21 '22 at 11:48
  • Yea but the point is that I want to be able to modify them – zulle99 Jul 21 '22 at 12:00
  • :blinks-eyes: then... modify them? https://godbolt.org/z/n9P5vvTjz – sehe Jul 21 '22 at 13:31
  • I imagine that my question was a little stupid for you, I apologize for this, right now I am struggling a bit to learn the BGL, thus I would like thank you again for your patience! – zulle99 Jul 21 '22 at 16:53
  • It's okay. We get disoriented while learning - that happens to me still. And the questions is what we're here for :) In this case I suspect that you were probably learning more things at the same time than you were aware of. Like, I think the last question was mostly about using boost::variant. Which took a little bit of adjusting perspective for me to realize that's the question. – sehe Jul 21 '22 at 21:36