diff --git a/.gitignore b/.gitignore index 7fde96a..63beb16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ __pycache__/ .coverage +cpp/test_mwmatching +cpp/run_matching +cpp/run_matching_dbg tests/lemon/lemon_matching diff --git a/cpp/Makefile b/cpp/Makefile index 67df584..0075e80 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -1,12 +1,27 @@ CXX = g++ -CXXFLAGS = -std=c++11 -Wall -O2 -fsanitize=address -fsanitize=undefined -LDLIBS = -l:libboost_unit_test_framework.a +OPTFLAGS = -O2 +DBGFLAGS = +CXXFLAGS = -std=c++11 -Wall -Wextra $(OPTFLAGS) $(DBGFLAGS) +LIB_BOOST_TEST = -l:libboost_unit_test_framework.a +.PHONY: all +all: run_matching run_matching_dbg test_mwmatching + +run_matching: run_matching.cpp mwmatching.h + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< + +run_matching_dbg: OPTFLAGS = -Og +run_matching_dbg: DBGFLAGS = -g -fsanitize=address -fsanitize=undefined +run_matching_dbg: run_matching.cpp mwmatching.h + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< + +test_mwmatching: OPTFLAGS = -O1 +test_mwmatching: DBGFLAGS = -fsanitize=address -fsanitize=undefined test_mwmatching: test_mwmatching.cpp mwmatching.h - $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LIB_BOOST_TEST) .PHONY: clean clean: - $(RM) test_mwmatching + $(RM) run_matching run_matching_dbg test_mwmatching diff --git a/cpp/run_matching.cpp b/cpp/run_matching.cpp new file mode 100644 index 0000000..899cd63 --- /dev/null +++ b/cpp/run_matching.cpp @@ -0,0 +1,315 @@ +/* + * Calculate maximum weighted matching of graphs in DIMACS format. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mwmatching.h" + + +namespace { // anonymous namespace + +typedef long long IntWeight; +typedef double FloatWeight; + + +template +using WeightedEdgeList = std::vector>; + +using EdgeList = std::vector; + + +/* + * Graph defined by a list of weighted edges. + * Weights are either integers or floating point numbers. + */ +struct Graph +{ + bool is_int_weight; + WeightedEdgeList int_edges; + WeightedEdgeList float_edges; +}; + + +/* + * Matching defined by a list of matched edges. + * The weight of the matching is either an integer or floating point number. + */ +struct Matching +{ + bool is_int_weight; + IntWeight int_weight; + FloatWeight float_weight; + EdgeList pairs; +}; + + +/* 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, Graph& graph) +{ + graph.is_int_weight = true; + graph.int_edges.clear(); + graph.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 (graph.is_int_weight && parse_int_weight(weight, wi)) { + wf = wi; + graph.int_edges.emplace_back(x, y, wi); + } else { + if (!parse_float_weight(weight, wf)) { + std::cerr << "ERROR: Expected edge but got '" + << line << "'" << std::endl; + return -1; + } + graph.is_int_weight = false; + } + graph.float_edges.emplace_back(x, y, wf); + + } else { + std::cerr << "ERROR: Unknown line format '" << line << "'" + << std::endl; + return -1; + } + } + + if (graph.is_int_weight) { + graph.float_edges.clear(); + } else { + graph.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 +EdgeList run_matching(const WeightedEdgeList& edges, bool maxcard) +{ + if (maxcard) { + return mwmatching::maximum_weight_matching( + mwmatching::adjust_weights_for_maximum_cardinality_matching(edges)); + } else { + return mwmatching::maximum_weight_matching(edges); + } +} + + +/* Calculate the total weight of the matched edges. */ +template +WeightType matching_weight(const WeightedEdgeList& edges, + const EdgeList& pairs) +{ + unsigned int num_vertex = 0; + for (const mwmatching::Edge& edge : edges) { + if (edge.vt.first >= num_vertex) { + num_vertex = edge.vt.first + 1; + } + if (edge.vt.second >= num_vertex) { + num_vertex = edge.vt.second + 1; + } + } + + std::vector mate(num_vertex); + for (const mwmatching::VertexPair& pair : pairs) { + assert(mate[pair.first] == 0); + assert(mate[pair.second] == 0); + mate[pair.first] = pair.second; + mate[pair.second] = pair.first; + } + + WeightType weight = 0; + for (const mwmatching::Edge& edge : edges) { + if (mate[edge.vt.first] == edge.vt.second) { + weight += edge.weight; + } + } + + return weight; +} + + +void usage() +{ + std::cerr + << std::endl + << "Calculate maximum weighted matching of a graph in DIMACS format." + << std::endl << std::endl + << "Usage: run_matching [--maxcard] < inputfile.edge" << std::endl + << " or: run_matching [--maxcard] inputfile.edge" << std::endl + << std::endl + << " --maxcard Calculate a maximum cardinality matching" + << std::endl + << " inputfile.edge 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 maxcard = false; + 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("--maxcard") == argv[i]) { + maxcard = true; + } 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]; + } + } + + Graph graph; + + if (input_file.empty()) { + if (read_dimacs_graph(std::cin, graph) != 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, graph) != 0) { + if (!f) { + std::cerr << "ERROR: Can not read from '" << input_file + << "' (" << std::strerror(errno) << ")" << std::endl; + } + return 1; + } + } + + Matching matching; + matching.is_int_weight = graph.is_int_weight; + if (graph.is_int_weight) { + matching.pairs = run_matching(graph.int_edges, maxcard); + matching.int_weight = matching_weight(graph.int_edges, + matching.pairs); + } else { + matching.pairs = run_matching(graph.float_edges, maxcard); + matching.float_weight = matching_weight(graph.float_edges, + matching.pairs); + } + + write_dimacs_matching(std::cout, matching); + + return 0; +}