diff options
| author | Jaron Kent-Dobias <jaron@kent-dobias.com> | 2022-09-29 17:35:53 +0200 | 
|---|---|---|
| committer | Jaron Kent-Dobias <jaron@kent-dobias.com> | 2022-09-29 17:35:53 +0200 | 
| commit | efad10c1fbcdab34e50f577ca0478de5632c8bbc (patch) | |
| tree | 02c7c5238dc654bc720944b83ce2e871eca22b15 /hungarian.cpp | |
| parent | 291d2ea1bf6994f4c58be5c177ec566fa94f1b64 (diff) | |
| download | code-efad10c1fbcdab34e50f577ca0478de5632c8bbc.tar.gz code-efad10c1fbcdab34e50f577ca0478de5632c8bbc.tar.bz2 code-efad10c1fbcdab34e50f577ca0478de5632c8bbc.zip  | |
Working implementation of the hungarian algorithm.
Diffstat (limited to 'hungarian.cpp')
| -rw-r--r-- | hungarian.cpp | 283 | 
1 files changed, 283 insertions, 0 deletions
diff --git a/hungarian.cpp b/hungarian.cpp new file mode 100644 index 0000000..00cc91d --- /dev/null +++ b/hungarian.cpp @@ -0,0 +1,283 @@ +#include <iostream> +#include <cmath> +#include <functional> +#include <random> +#include <vector> +#include <limits> +#include <queue> +#include <stack> + +#include "randutils/randutils.hpp" +#include "pcg-cpp/include/pcg_random.hpp" + +using Rng = randutils::random_generator<pcg32>; + +class Edge; + +class Vertex { +public: +  unsigned index; +  double y; +  bool inZ; +  std::vector<std::reference_wrapper<Edge>> neighbors; +  std::array<unsigned, 2> coordinate; + +  Vertex() : y(0), inZ(false) {}; + +  void addEdge(Edge& e) { +    neighbors.push_back(e); +  } + +  bool inMatching() const; +}; + +class Edge { +public: +  Vertex* s; +  Vertex* t; +  bool orientation; +  double weight; +  bool inPath; + +  Edge() : orientation(false) {}; +  void setVertices(Vertex& red, Vertex& blue) { +    s = &red; +    t = &blue; +    red.addEdge(*this); +    blue.addEdge(*this); +  } +  Vertex& tail()  { +    if (orientation) { +      return *t; +    } else { +      return *s; +    } +  } +  Vertex& head() { +    if (orientation) { +      return *s; +    } else { +      return *t; +    } +  } +  const Vertex& tail() const { +    if (orientation) { +      return *t; +    } else { +      return *s; +    } +  } +  const Vertex& head() const { +    if (orientation) { +      return *s; +    } else { +      return *t; +    } +  } +  bool isTight() const { +    return std::abs(s->y + t->y - weight) < 1e-10; +  } +}; + +bool Vertex::inMatching() const { +  for (const Edge& e : neighbors) { +    if (e.orientation) { +      return true; +    } +  } +  return false; +} + +class Graph { +public: +  std::vector<Vertex> S; +  std::vector<Vertex> T; +  std::vector<Edge> E; + +  Graph(unsigned n, Rng& r) : S(n * (n + 1)), T(n * (n + 1)), E(pow(2 * n, 2)) { +    for (unsigned i = 0; i < S.size(); i++) { +      S[i].index = i; +      S[i].coordinate = {2 * (i % (n + 1)), 2 * (i / (n + 1)) + 1}; +      T[i].index = S.size() + i; +      T[i].coordinate = {2 * (i % n) + 1, 2 * (i / n)}; +    } +    for (unsigned i = 0; i < E.size(); i++) { +      Vertex& redVertex = S[(1 + (i % (2 * n))) / 2 + (n + 1) * ((i / 4) / n)]; +      Vertex& blueVertex = T[(i % (2 * n)) / 2 + n * (((i + 2 * n) / 4) / n)]; +      E[i].setVertices(redVertex, blueVertex); +      E[i].weight = r.variate<double, std::exponential_distribution>(1); +    } +  } + +  void hungarian() { +    std::queue<std::reference_wrapper<Vertex>> q; + +    for (Vertex &v : T) { +      v.inZ = false; +    } + +    for (Vertex &v : S) { +      v.inZ = false; + +      if (!v.inMatching()) { +        q.push(v); +      } +    } + +    while (!q.empty()) { +      Vertex& v = q.front(); +      q.pop(); + +      v.inZ = true; + +      for (Edge& e : v.neighbors) { +        if (e.isTight() && (e.tail().index == v.index && !e.head().inZ)) { +          q.push(e.head()); +        } +      } +    } + +    bool reversedPath = false; + +    for (Vertex& v : T) { +      if (v.inZ && !v.inMatching()) { +        std::stack<std::tuple<std::vector<std::reference_wrapper<Edge>>::iterator, std::vector<std::reference_wrapper<Edge>>::iterator, unsigned>> path; + +        for (Edge& e : E) { +          e.inPath = false; +        } + +        path.push({v.neighbors.begin(), v.neighbors.end(), v.index}); + +        while (!path.empty()) { +          auto [it, itEnd, i] = path.top(); +          if (it == itEnd) { +            path.pop(); +            if (path.empty()) { +              exit(1); +            } +            std::tie(it, itEnd, i) = path.top(); +            it->get().inPath = false; +            it++; +            path.top() = {it, itEnd, i}; +          } else { +            Edge& e = it->get(); + +            if (e.isTight() && (e.head().index == i && !e.inPath)) { +              if (!e.tail().inMatching()) { +                break; +              } + +              e.inPath= true; +              path.push({e.tail().neighbors.begin(), e.tail().neighbors.end(), e.tail().index}); +            } else { +              it++; +              path.top() = {it, itEnd, i}; +            } +          } +        } + + +        if (path.empty()) { +          exit(1); +        } + + +        while (!path.empty()) { +          auto [it, itEnd, i] = path.top(); +          Edge& e = it->get(); +          path.pop(); +          e.orientation = !e.orientation; +        } + +        reversedPath = true; +        break; +      } +    } + +    if (!reversedPath) { +      double Δ = std::numeric_limits<double>::infinity(); + +      for (Edge& e : E) { +        if (e.s->inZ && !e.t->inZ) { +          Δ = std::min(Δ, e.weight - e.s->y - e.t->y); +        } +      } + +      for (Vertex& v : S) { +        if (v.inZ) { +          v.y += Δ; +        } +      } + +      for (Vertex& v : T) { +        if (v.inZ) { +          v.y -= Δ; +        } +      } + +    } +  } + +  bool isPerfect() const { +    for (const Vertex& v : S) { +      if (!v.inMatching()) { +        return  false; +      } +    } +    for (const Vertex& v : T) { +      if (!v.inMatching()) { +        return  false; +      } +    } + +    return true; +  } + +}; + +int main(int argc, char* argv[]) { +  unsigned n = 100; +  unsigned maxSteps = 1e8; +  double beliefThreshold = 1; + +  int opt; + +  while ((opt = getopt(argc, argv, "n:m:t:")) != -1) { +    switch (opt) { +    case 'n': +      n = atoi(optarg); +      break; +    case 'm': +      maxSteps = (unsigned)atof(optarg); +      break; +    case 't': +      beliefThreshold = atof(optarg); +      break; +    default: +      exit(1); +    } +  } + +  Rng r; +  Graph G(n, r); + +  unsigned steps = 0; +  double minBelief = 0; + +  while (!G.isPerfect()) { +    G.hungarian(); +  } + +  for (const Edge& e : G.E) { +    if (e.orientation) { +      std::cout +        << e.tail().coordinate[0] << " " +        << e.tail().coordinate[1] << " " +        << e.head().coordinate[0] << " " +        << e.head().coordinate[1] << std::endl; +    } +  } + +  return 0; +}  | 
