As I cited in previous question:
- Is it possible to generate multiple custom vertices using the Bundle Properties from Boost Graph Library?
- Boost Maximum Weighted Matching in undirected bipartite random graphs hangs in an infinite loop
I'm working on an application benchmark that compare the performance of the boost maximum weighted matching and auction algorithm for the transportation problem on solving the assignment problem for bipartite graphs.
Currently I've implemented a version of the auction algorithm using the bundle proprieties of boost graph library, this implementation is inspired by a vector version from github. I've done this in order to put on the same level both algorithms, to make a fair benchmark. Here it is:
#include "../include/Auction.h"
#include "../include/BipartiteGraph.h"
void auction_algorithm(Graph& graph, const int& n, duration& elapsed) {
const Weight eps = 1;
int unassigned_bidders = n;
GraphProp& gp = graph[boost::graph_bundle];
EdgeFilter any_interconnect = boost::keep_all{};
VertexFilter bidders = [graph](V v) -> bool { return boost::get<Bidder>(&(graph)[v]); };
VertexFilter items = [graph](V v) -> bool { return boost::get<Item>(&(graph)[v]); };
FMap map_bidders = FMap(graph, any_interconnect, bidders);
FMap map_items = FMap(graph, any_interconnect, items);
auto iterator_bidder = boost::make_iterator_range(boost::vertices(map_bidders));
auto iterator_item = boost::make_iterator_range(boost::vertices(map_items));
auto t_start = now();
while (unassigned_bidders > 0) {
for (auto uncasted_bidder : iterator_bidder) {
if (gp.bidder2item[static_cast<int>(uncasted_bidder)] != -1) continue;
Bidder* bidder = boost::get<Bidder>(&graph[uncasted_bidder]);
// 1 Bid
int id_item1 = -1;
Weight val_item1 = -1;
Weight val_item2 = -1;
for (auto uncasted_item : iterator_item) {
Item* item = boost::get<Item>(&graph[static_cast<int>(uncasted_item)]);
Weight val = boost::get(boost::edge_weight_t(), graph, (boost::edge(uncasted_bidder, uncasted_item, graph)).first) - item->cost;
if (val > val_item1) {
val_item2 = val_item1;
val_item1 = val;
id_item1 = item->id;
}
else if (val > val_item2) {
val_item2 = val;
}
}
bidder->best_item = id_item1 + n;
bidder->val_first_best_item = val_item1;
bidder->val_second_best_item = val_item2;
// 2 Compete
Weight bid = bidder->val_first_best_item - bidder->val_second_best_item + eps;
auto best_item = boost::get<Item>(&graph[bidder->best_item]);
if (bid > best_item->high_bid) {
best_item->high_bid = bid;
best_item->high_bidder = bidder->id;
}
}
// 3 Assign
for (auto uncasted_item : iterator_item) {
Item* item = boost::get<Item>(&graph[uncasted_item]);
if (item->high_bid == -1) continue;
item->cost += item->high_bid;
if (gp.item2bidder[item->id] != -1) {
gp.bidder2item[gp.item2bidder[item->id]] = -1;
unassigned_bidders++;
}
gp.item2bidder[item->id] = item->high_bidder;
gp.bidder2item[gp.item2bidder[item->id]] = item->id;
unassigned_bidders--;
}
}
elapsed = now() - t_start;
}
Weight perform_au(Graph& graph, duration& elapsed) {
int n = int(boost::num_vertices(graph) / 2);
Weight total_cost_auction = 0;
auction_algorithm(graph, n, elapsed);
std::cout << "\nThe matching is: ";
for (int bidder = 0; bidder < n; ++bidder) {
std::cout << "(" << bidder << "," << graph[boost::graph_bundle].bidder2item[bidder] << ")";
int item = graph[boost::graph_bundle].bidder2item[bidder];
total_cost_auction += boost::get(boost::edge_weight_t(), graph, (boost::edge(bidder, item + n, graph)).first);
}
std::cout << "\n";
return total_cost_auction;
}
I have compared this to the vector implementation and notice that the latter is much faster than mine (however they return the same amount of total cost). Is it due to the complexity of the boost::get? If so, why is it so heavy?
I'm using the g++ compiler on a Ubuntu machine and to compile the application I run the following line in my console:
g++ -std=c++2a -o ../bin/app BipartiteGraph.cpp MaximumWeightedMatching.cpp Auction.cpp AuctionArray.cpp Main.cpp
I share the link of my github repository so you can have a look at the whole project.
PS: If you have any suggestions for speeding up the algorithm, that would be great!
UPDATE: 09/08/2022
Requirement: Make the auction algorithm generic like the style of the Boost Graph Library. This is the last implementation that I've made.
UPDATE: 10/08/2022
I've made a class that maintain the all stuff like it was before with the Bundle Properties:
UPDATE: 14/08/2022
Actual version
Weight perform_au(const Graph& graph, Duration& elapsed, int& n_iteration_au, bool verbose)
{
int n = int(boost::num_vertices(graph) / 2);
std::vector<int> assignments(n);
Auction<Graph, Weight> auction_problem(n);
auto t_start = now();
auction_problem.auction_algorithm(graph, assignments);
elapsed = now() - t_start;
std::cout << " Finished \nThe matching is: ";
for (int bidder = 0; bidder < n; ++bidder)
std::cout << "(" << bidder << "," << assignments[bidder] << ")";
std::cout << "\n";
if (verbose) auction_problem.printProprieties();
n_iteration_au = auction_problem.getNIterationAu();
return auction_problem.getTotalCost(graph);
}
#ifndef _AA_H
#define _AA_H
#include <vector>
#include <unordered_map>
#include <boost/graph/adjacency_list.hpp>
template<typename T>
using AdjacencyIterator = boost::graph_traits<T>::adjacency_iterator;
template<typename Graph, typename Type>
class Auction
{
private:
struct Bidder {
int best_item = -1;
double val_first_best_item = -1;
double val_second_best_item = -1;
};
struct Item {
double cost = 0;
int high_bidder = -1;
double high_bid = -1;
};
int n_iteration_au = 0;
int vertices = 0;
std::unordered_map<int, Bidder> unassigned_bidder;
std::unordered_map<int, Bidder> assigned_bidder;
std::unordered_map<int, Item> item_map;
bool is_assignment_problem(const Graph& graph);
void auctionRound(const Graph& graph, const double& eps, const auto& vertex_idMap);
public:
void auction_algorithm(const Graph& graph, std::vector<int>& ass);
int getNIterationAu();
Type getTotalCost(const Graph& graph);
void printProprieties();
Type getMaximumEdge(const Graph& graph);
void reset();
Auction(int vertices)
{
this->vertices = vertices;
for (int i : boost::irange(0, vertices))
{
this->unassigned_bidder.insert(std::make_pair(i, Bidder{}));
this->item_map.insert(std::make_pair(i, Item{}));
}
}
};
template<typename Graph, typename Type>
inline int Auction<Graph, Type>::getNIterationAu() { return n_iteration_au; }
template<typename Graph, typename Type>
Type Auction<Graph, Type>::getMaximumEdge(const Graph& graph)
{
Type max = 0;
typedef boost::graph_traits<Graph>::edge_iterator edge_iterator;
std::pair<edge_iterator, edge_iterator> ei = boost::edges(graph);
for (edge_iterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter)
if (boost::get(boost::edge_weight_t(), graph, *edge_iter) > max)
max = boost::get(boost::edge_weight_t(), graph, *edge_iter);
return max;
}
template<typename Graph, typename Type>
inline Type Auction<Graph, Type>::getTotalCost(const Graph& graph)
{
Type total_cost_auction = 0;
for (int bidder = 0; bidder < vertices; ++bidder)
total_cost_auction += boost::get(boost::edge_weight_t(), graph, (boost::edge(bidder, assigned_bidder[bidder].best_item + vertices, graph)).first);
return total_cost_auction;
}
template<typename Graph, typename Type>
bool Auction<Graph, Type>::is_assignment_problem(const Graph& graph)
{
for (auto v1 : boost::make_iterator_range(boost::vertices(graph)))
{
AdjacencyIterator<Graph> ai, a_end;
boost::tie(ai, a_end) = boost::adjacent_vertices(v1, graph);
if (ai == a_end) return false;
else
for (auto v2 : boost::make_iterator_range(ai, a_end))
if ((v1 < vertices && v2 < vertices) || (v1 > vertices && v2 > vertices))
return false;
}
return true;
}
template<typename Graph, typename Type>
inline void Auction<Graph, Type>::printProprieties()
{
for (auto& bidder : assigned_bidder)
std::cout << "|Bidder:" << bidder.first << "|Best item:" << bidder.second.best_item << "|Value first best item:" << bidder.second.val_first_best_item << "|Value second best item:" << bidder.second.val_second_best_item << "|\n";
for (auto& item : item_map)
std::cout << "|Item:" << item.first << "|Cost:" << item.second.cost << "|Higher bidder:" << item.second.high_bidder << "|Higher bid:" << item.second.high_bid << "|\n";
}
template<typename Graph, typename Type>
void Auction<Graph, Type>::auctionRound(const Graph& graph, const double& eps, const auto& vertex_idMap)
{
for (auto& bidder : unassigned_bidder)
{
int id_item1 = -1;
double val_item1 = -1;
double val_item2 = -1;
AdjacencyIterator<Graph> ai, a_end;
boost::tie(ai, a_end) = boost::adjacent_vertices(vertex_idMap[bidder.first], graph);
for (auto item : boost::make_iterator_range(ai, a_end)) // itero iniziando da quelli che hanno meno vertici?
{
double val = (boost::get(boost::edge_weight_t(), graph, (boost::edge(bidder.first, static_cast<int>(item), graph)).first)) // * (vertices))
- item_map[static_cast<int>(item) - vertices].cost;
if (val > val_item1)
{
val_item2 = val_item1;
val_item1 = val;
id_item1 = static_cast<int>(item) - vertices;
}
else if (val > val_item2) val_item2 = val;
}
bidder.second.best_item = id_item1;
bidder.second.val_second_best_item = val_item2;
bidder.second.val_first_best_item = val_item1;
double bid = bidder.second.val_first_best_item - bidder.second.val_second_best_item + eps;
if (item_map.find(bidder.second.best_item) != item_map.end())
{
if (bid > item_map[bidder.second.best_item].high_bid)
{
item_map[bidder.second.best_item].high_bid = bid;
item_map[bidder.second.best_item].high_bidder = bidder.first;
}
}
}
for (auto& item : item_map)
{
if (item.second.high_bid == -1) continue;
item.second.cost += item.second.high_bid;
int id_to_remove = -1;
for (auto& ass_bidr : assigned_bidder)
{
if (ass_bidr.second.best_item == item.first)
{
id_to_remove = ass_bidr.first;
break;
}
}
if (id_to_remove != -1)
{
unassigned_bidder.insert(std::make_pair(id_to_remove, assigned_bidder[id_to_remove]));
assigned_bidder.erase(id_to_remove);
}
assigned_bidder.insert(std::make_pair(item.second.high_bidder, unassigned_bidder[item.second.high_bidder]));
unassigned_bidder.erase(item.second.high_bidder);
}
}
template<typename Graph, typename Type>
void Auction<Graph, Type>::auction_algorithm(const Graph& graph, std::vector<int>& ass)
{
if (!is_assignment_problem(graph)) throw("Not an assignment problem");
auto vertex_idMap = boost::get(boost::vertex_index, graph);
double eps = static_cast<double>(1.0 / (vertices + 1));
while (unassigned_bidder.size() > 0)
{
auctionRound(graph, eps, vertex_idMap);
n_iteration_au += 1;
}
for (auto& a : assigned_bidder) ass[a.first] = a.second.best_item;
}
#endif