diff --git a/tests/lemon/Makefile b/tests/lemon/Makefile new file mode 100644 index 0000000..cdb1a35 --- /dev/null +++ b/tests/lemon/Makefile @@ -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 + diff --git a/tests/lemon/lemon_matching.cc b/tests/lemon/lemon_matching.cc new file mode 100644 index 0000000..7c69818 --- /dev/null +++ b/tests/lemon/lemon_matching.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +namespace { // anonymous namespace + +typedef long long IntWeight; +typedef double FloatWeight; + + +/* Weighted edge. */ +template +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 +using WeightedEdgeList = std::vector>; + + +/* Wrapper that holds an edge list with either integer or float weights. */ +struct WeightedEdgeListVariant +{ + bool is_int_weight; + WeightedEdgeList int_edges; + WeightedEdgeList float_edges; +}; + + +/* Matching defined by a list of matched edges. */ +struct Matching +{ + bool is_int_weight; + IntWeight int_weight; + FloatWeight float_weight; + std::vector> pairs; + + Matching() = default; + + Matching(IntWeight weight, std::vector>&& pairs) + : is_int_weight(true), + int_weight(weight), + float_weight(0), + pairs(pairs) + { } + + Matching(FloatWeight weight, std::vector>&& 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 +struct LemonGraph +{ + lemon::SmartGraph graph; + lemon::SmartGraph::NodeMap node_index; + lemon::SmartGraph::EdgeMap edge_weight; + + /* Construct a LEMON graph from a list of weighted edges. */ + explicit LemonGraph(const WeightedEdgeList& edges) + : graph(), + node_index(graph), + edge_weight(graph) + { + std::unordered_map node_map; + + for (const WeightedEdge& 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& 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(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(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 +Matching run_matching(const WeightedEdgeList& edges) +{ + LemonGraph lemon_graph(edges); + + lemon::MaxWeightedMatching> + match(lemon_graph.graph, lemon_graph.edge_weight); + match.run(); + + std::vector> 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; +}