1
0
Fork 0

Add program for matching with LEMON

This commit is contained in:
Joris van Rantwijk 2023-02-19 13:36:07 +01:00
parent 1e81129476
commit 80dd53c736
2 changed files with 380 additions and 0 deletions

11
tests/lemon/Makefile Normal file
View File

@ -0,0 +1,11 @@
CXX = g++
CXXFLAGS = -std=c++11 -Wall -O2
LDLIBS = -llemon
lemon_matching: lemon_matching.cc
.PHONY: clean
clean:
$(RM) lemon_matching

View File

@ -0,0 +1,369 @@
/*
* Use LEMON to solve a maximum-weight matching problem.
*
* This program is designed for LEMON version 1.3.1.
*
* Download LEMON from https://lemon.cs.elte.hu/trac/lemon
* or install Debian package "liblemon-dev".
*
* This program reads input from stdin or from a specified filename.
* Input should be a weighted graph in DIMACS edge-list format.
*
* This program writes output to stdout.
* The output is a matching in DIMACS matching format.
*/
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <lemon/smart_graph.h>
#include <lemon/matching.h>
namespace { // anonymous namespace
typedef long long IntWeight;
typedef double FloatWeight;
/* Weighted edge. */
template <typename WeightType>
struct WeightedEdge
{
int x, y;
WeightType w;
WeightedEdge(int x, int y, WeightType w)
: x(x), y(y), w(w)
{ }
};
/* Graph defined by a list of weighted edges. */
template <typename WeightType>
using WeightedEdgeList = std::vector<WeightedEdge<WeightType>>;
/* Wrapper that holds an edge list with either integer or float weights. */
struct WeightedEdgeListVariant
{
bool is_int_weight;
WeightedEdgeList<IntWeight> int_edges;
WeightedEdgeList<FloatWeight> float_edges;
};
/* Matching defined by a list of matched edges. */
struct Matching
{
bool is_int_weight;
IntWeight int_weight;
FloatWeight float_weight;
std::vector<std::pair<int, int>> pairs;
Matching() = default;
Matching(IntWeight weight, std::vector<std::pair<int, int>>&& pairs)
: is_int_weight(true),
int_weight(weight),
float_weight(0),
pairs(pairs)
{ }
Matching(FloatWeight weight, std::vector<std::pair<int, int>>&& pairs)
: is_int_weight(false),
int_weight(0),
float_weight(weight),
pairs(pairs)
{ }
};
/* Wrapper for a LEMON graph with edge weights and node indices. */
template <typename WeightType>
struct LemonGraph
{
lemon::SmartGraph graph;
lemon::SmartGraph::NodeMap<int> node_index;
lemon::SmartGraph::EdgeMap<WeightType> edge_weight;
/* Construct a LEMON graph from a list of weighted edges. */
explicit LemonGraph(const WeightedEdgeList<WeightType>& edges)
: graph(),
node_index(graph),
edge_weight(graph)
{
std::unordered_map<int, lemon::SmartGraph::Node> node_map;
for (const WeightedEdge<WeightType>& e : edges) {
lemon::SmartGraph::Node node_x = node_from_index(node_map, e.x);
lemon::SmartGraph::Node node_y = node_from_index(node_map, e.y);
lemon::SmartGraph::Edge edge = graph.addEdge(node_x, node_y);
edge_weight.set(edge, e.w);
}
}
private:
/* Return the Node that corresponds to the specified vertex index. */
lemon::SmartGraph::Node
node_from_index(std::unordered_map<int, lemon::SmartGraph::Node>& node_map,
int x)
{
auto it = node_map.find(x);
if (it == node_map.end()) {
lemon::SmartGraph::Node node = graph.addNode();
node_index.set(node, x);
node_map[x] = node;
return node;
} else {
return it->second;
}
}
};
/* Parse an integer edge weight. */
bool parse_int_weight(const std::string& s, long long& v)
{
const char *p = s.c_str();
char *q;
errno = 0;
v = std::strtoll(p, &q, 10);
return (q != p && q[0] == '\0' && errno == 0);
}
/* Parse a floating point edge weight. */
bool parse_float_weight(const std::string& s, double& v)
{
const char *p = s.c_str();
char *q;
errno = 0;
v = std::strtod(p, &q);
return (q != p && q[0] == '\0' && errno == 0);
}
/* Read a graph in DIMACS edge list format. */
int read_dimacs_graph(std::istream& input, WeightedEdgeListVariant& res)
{
res.is_int_weight = true;
res.int_edges.clear();
res.float_edges.clear();
while (!input.eof()) {
std::string line;
std::getline(input, line);
if (!input) {
if (input.eof()) {
break;
} else {
return -1;
}
}
line.erase(0, line.find_first_not_of(" \n\r\t"));
line.erase(line.find_last_not_of(" \n\r\t") + 1);
if (line.empty()) {
// skip empty line
} else if (line[0] == 'c') {
// skip comment line
} else if (line[0] == 'p') {
// handle problem line
std::istringstream is(line);
std::string cmd, fmt;
is >> cmd >> fmt;
if (!is || cmd != "p" || fmt != "edge") {
std::cerr << "ERROR: Expected DIMACS edge format but got '"
<< line << "'" << std::endl;
return -1;
}
} else if (line[0] == 'e') {
// handle edge
std::istringstream is(line);
std::string cmd, weight;
unsigned int x, y;
IntWeight wi;
FloatWeight wf;
is >> cmd >> x >> y >> weight;
if (!is || cmd != "e" || x < 1 || y < 1) {
std::cerr << "ERROR: Expected edge but got '"
<< line << "'" << std::endl;
return -1;
}
if (res.is_int_weight && parse_int_weight(weight, wi)) {
wf = wi;
res.int_edges.push_back(WeightedEdge<IntWeight>(x, y, wi));
} else {
if (!parse_float_weight(weight, wf)) {
std::cerr << "ERROR: Expected edge but got '"
<< line << "'" << std::endl;
return -1;
}
res.is_int_weight = false;
}
res.float_edges.push_back(WeightedEdge<FloatWeight>(x, y, wf));
} else {
std::cerr << "ERROR: Unknown line format '" << line << "'"
<< std::endl;
return -1;
}
}
if (res.is_int_weight) {
res.float_edges.clear();
} else {
res.int_edges.clear();
}
return 0;
}
/* Write matching in DIMACS format. */
void write_dimacs_matching(std::ostream& f, const Matching& matching)
{
f << "s ";
if (matching.is_int_weight) {
f << matching.int_weight;
} else {
f.precision(12);
f << matching.float_weight;
}
f << std::endl;
for (auto pair : matching.pairs) {
f << "m " << pair.first << " " << pair.second << std::endl;
}
}
/* Solve a maximum-weight-matching problem. */
template <typename WeightType>
Matching run_matching(const WeightedEdgeList<WeightType>& edges)
{
LemonGraph<WeightType> lemon_graph(edges);
lemon::MaxWeightedMatching<lemon::SmartGraph,
lemon::SmartGraph::EdgeMap<WeightType>>
match(lemon_graph.graph, lemon_graph.edge_weight);
match.run();
std::vector<std::pair<int, int>> pairs;
for (lemon::SmartGraph::NodeIt node_x(lemon_graph.graph);
node_x != lemon::INVALID;
++node_x) {
lemon::SmartGraph::Node node_y = match.mate(node_x);
if (node_y != lemon::INVALID) {
int x = lemon_graph.node_index[node_x];
int y = lemon_graph.node_index[node_y];
if (x < y) {
pairs.push_back(std::make_pair(x, y));
}
}
}
return Matching(match.matchingWeight(), std::move(pairs));
}
void usage()
{
std::cerr
<< std::endl
<< "Solves a maximum-weight matching problem with LEMON."
<< std::endl << std::endl
<< "Usage: lemon_matching < inputfile.gr" << std::endl
<< " or: lemon_matching inputfile.gr" << std::endl
<< std::endl
<< " inputfile.gr Input file in DIMACS edge-list format"
<< std::endl << std::endl;
}
}; // anonymous namespace
int main(int argc, const char **argv)
{
std::string input_file;
bool end_options = false;
for (int i = 1; i < argc; i++) {
if (!end_options && argv[i][0] == '-') {
if (std::string("--help") == argv[i]
|| std::string("-h") == argv[i]) {
usage();
return 0;
} else if (std::string("--") == argv[i]) {
end_options = true;
} else {
std::cerr << "ERROR: Unknown option " << argv[i] << std::endl;
usage();
return 1;
}
} else {
if (!input_file.empty()) {
std::cerr << "ERROR: Multiple input files not supported."
<< std::endl;
usage();
return 1;
}
input_file = argv[i];
}
}
WeightedEdgeListVariant edges;
if (input_file.empty()) {
if (read_dimacs_graph(std::cin, edges) != 0) {
if (!std::cin) {
std::cerr << "ERROR: Can not read from stdin ("
<< std::strerror(errno) << ")" << std::endl;
}
return 1;
}
} else {
std::ifstream f(input_file);
if (!f) {
std::cerr << "ERROR: Can not open '" << input_file << "' ("
<< std::strerror(errno) << ")" << std::endl;
return 1;
}
if (read_dimacs_graph(f, edges) != 0) {
if (!f) {
std::cerr << "ERROR: Can not read from '" << input_file
<< "' (" << std::strerror(errno) << ")" << std::endl;
}
return 1;
}
}
Matching matching;
if (edges.is_int_weight) {
matching = run_matching(edges.int_edges);
} else {
matching = run_matching(edges.float_edges);
}
write_dimacs_matching(std::cout, matching);
return 0;
}