C++ command line tool to run matching
This commit is contained in:
parent
8d7d1a537a
commit
da0040ba27
|
@ -1,4 +1,7 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.coverage
|
.coverage
|
||||||
|
|
||||||
|
cpp/test_mwmatching
|
||||||
|
cpp/run_matching
|
||||||
|
cpp/run_matching_dbg
|
||||||
tests/lemon/lemon_matching
|
tests/lemon/lemon_matching
|
||||||
|
|
23
cpp/Makefile
23
cpp/Makefile
|
@ -1,12 +1,27 @@
|
||||||
|
|
||||||
CXX = g++
|
CXX = g++
|
||||||
CXXFLAGS = -std=c++11 -Wall -O2 -fsanitize=address -fsanitize=undefined
|
OPTFLAGS = -O2
|
||||||
LDLIBS = -l:libboost_unit_test_framework.a
|
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
|
test_mwmatching: test_mwmatching.cpp mwmatching.h
|
||||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
|
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LIB_BOOST_TEST)
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
$(RM) test_mwmatching
|
$(RM) run_matching run_matching_dbg test_mwmatching
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
* Calculate maximum weighted matching of graphs in DIMACS format.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "mwmatching.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace { // anonymous namespace
|
||||||
|
|
||||||
|
typedef long long IntWeight;
|
||||||
|
typedef double FloatWeight;
|
||||||
|
|
||||||
|
|
||||||
|
template <typename WeightType>
|
||||||
|
using WeightedEdgeList = std::vector<mwmatching::Edge<WeightType>>;
|
||||||
|
|
||||||
|
using EdgeList = std::vector<mwmatching::VertexPair>;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Graph defined by a list of weighted edges.
|
||||||
|
* Weights are either integers or floating point numbers.
|
||||||
|
*/
|
||||||
|
struct Graph
|
||||||
|
{
|
||||||
|
bool is_int_weight;
|
||||||
|
WeightedEdgeList<IntWeight> int_edges;
|
||||||
|
WeightedEdgeList<FloatWeight> 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 <typename WeightType>
|
||||||
|
EdgeList run_matching(const WeightedEdgeList<WeightType>& 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 <typename WeightType>
|
||||||
|
WeightType matching_weight(const WeightedEdgeList<WeightType>& edges,
|
||||||
|
const EdgeList& pairs)
|
||||||
|
{
|
||||||
|
unsigned int num_vertex = 0;
|
||||||
|
for (const mwmatching::Edge<WeightType>& 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<mwmatching::VertexId> 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<WeightType>& 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;
|
||||||
|
}
|
Loading…
Reference in New Issue