1
0
Fork 0

Implement heap-based tracking for delta3

This commit is contained in:
Joris van Rantwijk 2024-11-09 00:11:07 +01:00
parent b17ca1a364
commit 55a98238aa
1 changed files with 157 additions and 201 deletions

View File

@ -18,6 +18,8 @@
#include <utility>
#include <vector>
#include "datastruct.h"
namespace mwmatching {
@ -67,6 +69,11 @@ namespace impl {
/** Value used to mark an invalid or undefined vertex. */
constexpr VertexId NO_VERTEX = std::numeric_limits<VertexId>::max();
/** Type representing an index in the edge list. */
using EdgeIndex = unsigned int;
/** Value used to mark an invalid or undefined vertex. */
constexpr EdgeIndex NO_EDGE = std::numeric_limits<EdgeIndex>::max();
/** Top-level blossoms may be labeled "S" or "T" or unlabeled. */
enum BlossomLabel { LABEL_NONE = 0, LABEL_S = 1, LABEL_T = 2 };
@ -99,6 +106,10 @@ void check_input_graph(const std::vector<Edge<WeightType>>& edges)
{
const VertexId max_num_vertex = std::numeric_limits<VertexId>::max();
if (edges.size() >= NO_EDGE) {
throw std::invalid_argument("Too many edges");
}
for (const Edge<WeightType>& edge : edges) {
// Check that vertex IDs are valid.
@ -167,7 +178,7 @@ struct Graph
const VertexId num_vertex;
/** For each vertex, a vector of pointers to its incident edges. */
const std::vector<std::vector<const EdgeT*>> adjacent_edges;
const std::vector<std::vector<EdgeIndex>> adjacent_edges;
/**
* Initialize the graph representation and prepare adjacent edge lists.
@ -233,7 +244,7 @@ struct Graph
* @param edges List of edges in the graph.
* @return Vector of incident edges for each vertex.
*/
static std::vector<std::vector<const EdgeT*>> build_adjacent_edges(
static std::vector<std::vector<EdgeIndex>> build_adjacent_edges(
const std::vector<EdgeT>& edges,
VertexId num_vertex)
{
@ -247,13 +258,14 @@ struct Graph
}
// Build adjacency lists.
std::vector<std::vector<const EdgeT*>> adjacent_edges(num_vertex);
std::vector<std::vector<EdgeIndex>> adjacent_edges(num_vertex);
for (VertexId i = 0; i < num_vertex; ++i) {
adjacent_edges[i].reserve(vertex_degree[i]);
}
for (const EdgeT& edge : edges) {
adjacent_edges[edge.vt.first].push_back(&edge);
adjacent_edges[edge.vt.second].push_back(&edge);
for (EdgeIndex e = 0; e < edges.size(); e++) {
const EdgeT& edge = edges[e];
adjacent_edges[edge.vt.first].push_back(e);
adjacent_edges[edge.vt.second].push_back(e);
}
return adjacent_edges;
@ -306,6 +318,17 @@ struct Blossom
/** Optional edge that attaches this blossom to the alternating tree. */
VertexPair tree_edge;
// TODO -- tree_blossoms
// TOOD -- vertex_queue
// TODO -- delta2_node
// TODO -- vertex_dual_offset
// TODO -- marker
// TODO -- remove
/**
* In case of a top-level S-blossom, "best_edge" is the least-slack edge
* that links to a different S-blossom, or "nullptr" if no such edge
@ -393,14 +416,11 @@ struct NonTrivialBlossom : public Blossom<WeightType>
*/
std::list<SubBlossom> subblossoms;
// TODO -- description
/** Dual LPP variable for this blossom. */
WeightType dual_var;
/**
* In case of a top-level S-blossom, "best_edge_set" is a list of
* least-slack edges between this blossom and other S-blossoms.
*/
std::list<const Edge<WeightType>*> best_edge_set;
// TODO -- delta4_node
/** Initialize a non-trivial blossom. */
NonTrivialBlossom(
@ -552,6 +572,9 @@ public:
// NOTE - this MUST be a list, because we delete items from it while keeping pointers to other items
std::list<NonTrivialBlossomT> nontrivial_blossom;
// TODO -- vertex_queue_node
// TODO -- remove
/**
* Every vertex is contained in exactly one top-level blossom
* (possibly the trivial blossom that contains just that vertex).
@ -561,6 +584,9 @@ public:
*/
std::vector<BlossomT*> vertex_top_blossom;
// TODO -- start_vertex_dual
// TODO -- description
/**
* Every vertex has a variable in the dual LPP.
*
@ -568,6 +594,24 @@ public:
*/
std::vector<WeightType> vertex_dual;
/** Running sum of applied delta steps. */
WeightType delta_sum;
// TODO -- delta2_queue
/** Queue of edges between S-vertices in different top-level blossoms. */
typedef PriorityQueue<WeightType, EdgeIndex> EdgeQueue;
EdgeQueue delta3_queue;
/** For each edge, a node in delta3_queue. */
std::vector<typename EdgeQueue::Node> delta3_node;
// TODO -- delta4_queue
// TODO -- vertex_sedge_queue
// TODO -- vertex_sedge_node
// TODO -- delete
/**
* In case of T-vertex or unlabeled vertex "x",
* "vertex_best_edge[x]" is the least-slack edge to any S-vertex,
@ -575,12 +619,15 @@ public:
*/
std::vector<const EdgeT*> vertex_best_edge;
// TODO -- rename to scan_queue
/** Queue of S-vertices to be scanned. */
std::deque<VertexId> queue;
// TODO -- delete
/** Markers placed while tracing an alternating path. */
std::vector<bool> vertex_marker;
// TODO -- delete
/** Vertices marked while tracing an alternating path. */
std::vector<VertexId> marked_vertex;
@ -590,7 +637,8 @@ public:
* This function takes time O(n + m).
*/
explicit MatchingContext(const std::vector<EdgeT>& edges_in)
: graph(edges_in)
: graph(edges_in),
delta3_node(edges_in.size())
{
// Initially all vertices are unmatched.
vertex_mate.resize(graph.num_vertex, NO_VERTEX);
@ -615,6 +663,8 @@ public:
WeightType init_vertex_dual = max_weight * (weight_factor / 2);
vertex_dual.resize(graph.num_vertex, init_vertex_dual);
delta_sum = 0;
// Initialize "vertex_best_edge".
vertex_best_edge.resize(graph.num_vertex, nullptr);
@ -627,48 +677,44 @@ public:
MatchingContext(const MatchingContext&) = delete;
MatchingContext& operator=(const MatchingContext&) = delete;
/* ********** General support routines: ********** */
/* ********** Find top-level blossom: ********** */
/** Calculate edge slack. */
/**
* Find the top-level blossom that contains vertex "x".
*
* This function takes time O(log(n)).
*/
BlossomT* top_level_blossom(VertexId x)
{
// TODO
return vertex_top_blossom[x];
}
/* ********** Least slack edge tracking: ********** */
/**
* Return the modified edge slack of the specified edge.
*
* Modified edge slack is related to true edge slack, but adjusted
* to make it invariant under delta steps.
*/
WeightType edge_pseudo_slack(EdgeIndex e) const
{
const EdgeT& edge = graph.edges[e];
VertexId x = edge.vt.first;
VertexId y = edge.vt.second;
// TODO -- remove delta_sum here
return vertex_dual[x] + vertex_dual[y] - weight_factor * edge.weight + 2 * delta_sum;
}
// TODO -- delete
WeightType edge_slack(const EdgeT& edge) const
{
VertexId x = edge.vt.first;
VertexId y = edge.vt.second;
assert(vertex_top_blossom[x] != vertex_top_blossom[y]);
return vertex_dual[x] + vertex_dual[y] - weight_factor * edge.weight;
}
/*
* Least-slack edge tracking:
*
* To calculate delta steps, the matching algorithm needs to find
* - the least-slack edge between any S-vertex and an unlabeled vertex;
* - the least-slack edge between any pair of top-level S-blossoms.
*
* For each unlabeled vertex and each T-vertex, we keep track of the
* least-slack edge to any S-vertex. Tracking for unlabeled vertices
* serves to provide the least-slack edge for the delta step.
* Tracking for T-vertices is done because such vertices can turn into
* unlabeled vertices if they are part of a T-blossom that gets expanded.
*
* For each top-level S-blossom, we keep track of the least-slack edge
* to any S-vertex not in the same blossom.
*
* Furthermore, for each top-level S-blossom, we keep a list of
* least-slack edges to other top-level S-blossoms. For any pair of
* top-level S-blossoms, the least-slack edge between them is contained
* in the edge list of at least one of the blossoms. An edge list may
* contain multiple edges to the same S-blossom. Such redundant edges are
* pruned during blossom merging to limit the number of tracked edges.
*
* Note: For a given vertex or blossom, the identity of the least-slack
* edge to any S-blossom remains unchanged during a delta step.
* Although the delta step changes edge slacks, it changes the slack
* of every edge to an S-vertex by the same amount. Therefore the edge
* that had least slack before the delta step, will still have least slack
* after the delta step.
*/
/**
* Reset least-slack edge tracking.
*
@ -686,8 +732,9 @@ public:
for (NonTrivialBlossomT& blossom : nontrivial_blossom) {
blossom.best_edge = nullptr;
blossom.best_edge_set.clear();
}
delta3_queue.clear();
}
/**
@ -736,162 +783,72 @@ public:
return std::make_pair(best_edge, best_slack);
}
/** Start tracking edges for a new S-blossom. */
void lset_new_blossom(BlossomT* blossom)
{
assert(blossom->best_edge == nullptr);
assert((blossom->nontrivial() == nullptr)
|| blossom->nontrivial()->best_edge_set.empty());
}
/**
* Add edge "e" between the specified S-blossom and another S-blossom.
* Add specified edge for delta3 tracking.
*
* This function takes time O(1) per call.
* This function is called O(m) times per stage.
* This function is called if a vertex becomes an S-vertex and edge "e"
* connects it to an S-vertex in a different top-level blossom.
*
* This function takes time O(log(n)).
*/
void lset_add_blossom_edge(BlossomT* blossom,
const EdgeT* edge,
WeightType slack)
void delta3_add_edge(EdgeIndex e)
{
const EdgeT* cur_best_edge = blossom->best_edge;
if ((cur_best_edge == nullptr)
|| (slack < edge_slack(*cur_best_edge))) {
blossom->best_edge = edge;
}
NonTrivialBlossomT* ntb = blossom->nontrivial();
if (ntb) {
ntb->best_edge_set.push_back(edge);
// The edge may already be in the delta3 queue, if it was previously
// iscovered in the opposite direction. In that case do nothing.
if (! delta3_node[e].valid()) {
// Insert edge. Use modified edge slack as priority.
WeightType prio = edge_pseudo_slack(e);
delta3_queue.insert(&delta3_node[e], prio, e);
}
}
/**
* Update least-slack edge tracking after merging sub-blossoms
* into a new S-blossom.
* Remove specified edge from delta3 tracking.
*
* This function takes total time O(n**2) per stage.
* This function is called if a former S-vertex becomes unlabeled
* and edge "e" connects it to another S-vertex.
*
* This function takes time O(log(n)).
*/
void lset_merge_blossoms(NonTrivialBlossomT* blossom)
void delta3_remove_edge(EdgeIndex e)
{
assert(blossom->best_edge == nullptr);
assert(blossom->best_edge_set.empty());
// Collect edges from the sub-blossoms that used to be S-blossoms.
for (auto& subblossom_item : blossom->subblossoms) {
BlossomT* sub = subblossom_item.blossom;
if (sub->label == LABEL_S) {
NonTrivialBlossomT* ntb = sub->nontrivial();
if (ntb) {
// Take least-slack edge set from this subblossom.
blossom->best_edge_set.splice(
blossom->best_edge_set.end(),
ntb->best_edge_set);
} else {
// Trivial blossoms don't maintain a least-slack edge set.
// Just consider all incident edges.
for (const EdgeT* edge : graph.adjacent_edges[sub->base_vertex]) {
// Only take edges between different S-blossoms.
VertexId x = edge->vt.first;
VertexId y = edge->vt.second;
BlossomT* bx = vertex_top_blossom[x];
BlossomT* by = vertex_top_blossom[y];
if ((bx != by)
&& (bx->label == LABEL_S)
&& (by->label == LABEL_S)) {
blossom->best_edge_set.push_back(edge);
}
}
}
}
if (delta3_node[e].valid()) {
delta3_queue.remove(&delta3_node[e]);
}
}
// Build a temporary array holding the least-slack edge index to
// each top-level S-blossom. This array is indexed by the base vertex
// of the blossoms.
std::vector<std::pair<const EdgeT*, WeightType>>
best_edge_to_blossom(graph.num_vertex);
for (const EdgeT* edge : blossom->best_edge_set) {
BlossomT* bx = vertex_top_blossom[edge->vt.first];
BlossomT* by = vertex_top_blossom[edge->vt.second];
assert(bx == blossom || by == blossom);
// Ignore internal edges.
/**
* Find the least-slack edge between any pair of S-vertices
* in different top-level blossoms.
*
* This function takes time O(1 + k * log(n)),
* where "k" is the number of intra-blossom edges removed from the queue.
*
* @return Tuple (edge_index, slack) if there is an S-to-S edge,
* or (NO_EDGE, 0) if there is no suitable edge.
*/
std::tuple<EdgeIndex, WeightType> delta3_get_min_edge()
{
while (! delta3_queue.empty()) {
EdgeIndex e = delta3_queue.min_elem();
const EdgeT& edge = graph.edges[e];
BlossomT* bx = top_level_blossom(edge.vt.first);
BlossomT* by = top_level_blossom(edge.vt.second);
assert(bx->label == LABEL_S && by->label == LABEL_S);
if (bx != by) {
bx = (bx == blossom) ? by : bx;
// Only consider edges to S-blossoms.
if (bx->label == LABEL_S) {
// Keep only the least-slack edge to blossom "bx".
WeightType slack = edge_slack(*edge);
VertexId bx_base = bx->base_vertex;
auto& best_edge_item = best_edge_to_blossom[bx_base];
if ((best_edge_item.first == nullptr)
|| (slack < best_edge_item.second)) {
best_edge_item.first = edge;
best_edge_item.second = slack;
}
}
WeightType slack = delta3_node[e].prio() - 2 * delta_sum;
return std::make_tuple(e, slack);
}
};
// Rebuild a compact list of least-slack edges.
// Also find the overall least-slack edge to any other S-blossom.
WeightType best_slack = 0;
blossom->best_edge_set.clear();
for (auto& best_edge_item : best_edge_to_blossom) {
const EdgeT* edge = best_edge_item.first;
if (edge != nullptr) {
blossom->best_edge_set.push_back(edge);
WeightType slack = best_edge_item.second;
if ((blossom->best_edge == nullptr) || (slack < best_slack)) {
blossom->best_edge = edge;
best_slack = slack;
}
}
}
}
/**
* Return the index and slack of the least-slack edge between
* any pair of top-level S-blossoms.
*
* This function takes time O(n) per call.
* This function takes total time O(n**2) per stage.
*
* @return Pair (edge, slack) if there is a least-slack edge,
* or (nullptr, 0) if there is no suitable edge.
*/
std::pair<const EdgeT*, WeightType> lset_get_best_blossom_edge()
{
const EdgeT* best_edge = nullptr;
WeightType best_slack = 0;
auto consider_blossom =
[this,&best_edge,&best_slack](BlossomT* blossom)
{
if ((blossom->parent == nullptr) && (blossom->label == LABEL_S)) {
const EdgeT* edge = blossom->best_edge;
if (edge != nullptr) {
WeightType slack = edge_slack(*edge);
if ((best_edge == nullptr) || (slack < best_slack)) {
best_edge = edge;
best_slack = slack;
}
}
}
};
for (BlossomT& blossom : trivial_blossom) {
consider_blossom(&blossom);
}
for (BlossomT& blossom : nontrivial_blossom) {
consider_blossom(&blossom);
// Reject edges between vertices in the same top-level blossom.
// Although such edges are never inserted into the queue,
// existing edges in the queue may become intra-blossom when
// blossoms are merged.
delta3_queue.remove(&delta3_node[e]);
}
return std::make_pair(best_edge, best_slack);
// Queue empty; no suitable edge exists.
return std::make_tuple(NO_EDGE, 0);
}
/* ********** Creating and expanding blossoms: ********** */
@ -1052,9 +1009,6 @@ public:
});
}
}
// Merge least-slack edges for the S-sub-blossoms.
lset_merge_blossoms(blossom);
}
/** Erase the specified non-trivial blossom. */
@ -1386,9 +1340,6 @@ public:
bx->tree_edge = std::make_pair(y, x);
}
// Start least-slack edge tracking for the S-blossom.
lset_new_blossom(bx);
// Add all vertices inside the newly labeled S-blossom to the queue.
for_vertices_in_blossom(bx,
[this](VertexId v) {
@ -1495,9 +1446,10 @@ public:
// Scan the edges that are incident on "x".
// This loop runs through O(m) iterations per stage.
for (const EdgeT* edge : graph.adjacent_edges[x]) {
VertexId y = (edge->vt.first != x) ? edge->vt.first
: edge->vt.second;
for (EdgeIndex e : graph.adjacent_edges[x]) {
const EdgeT& edge = graph.edges[e];
VertexId y = (edge.vt.first != x) ? edge.vt.first
: edge.vt.second;
// Note: The top-level blossom of vertex "x" may change
// during this loop, so we need to refresh it in each pass.
@ -1512,7 +1464,7 @@ public:
// Check whether this edge is tight (has zero slack).
// Only tight edges may be part of an alternating tree.
WeightType slack = edge_slack(*edge);
WeightType slack = edge_slack(edge);
if (slack <= 0) {
if (ylabel == LABEL_NONE) {
// Found a tight edge to an unlabeled blossom.
@ -1529,14 +1481,14 @@ public:
} else if (ylabel == LABEL_S) {
// Found a non-tight edge between two S-blossoms.
// Pass it to the least-slack edge tracker.
lset_add_blossom_edge(bx, edge, slack);
delta3_add_edge(e);
}
if (ylabel != LABEL_S) {
// Found an to a T-vertex or unlabeled vertex "y".
// Pass it to the least-slack edge tracker.
// Tight edges must also be tracked in this way.
lset_add_vertex_edge(y, edge, slack);
lset_add_vertex_edge(y, &edge, slack);
}
}
}
@ -1587,11 +1539,12 @@ public:
// Compute delta3: half minimum slack of any edge between two
// top-level S-blossoms.
std::tie(edge, slack) = lset_get_best_blossom_edge();
if ((edge != nullptr) && (slack / 2 <= delta.value)) {
EdgeIndex e;
std::tie(e, slack) = delta3_get_min_edge();
if ((e != NO_EDGE) && (slack / 2 <= delta.value)) {
delta.kind = 3;
delta.value = slack / 2;
delta.edge = edge->vt;
delta.edge = graph.edges[e].vt;
}
// Compute delta4: half minimum dual of a top-level T-blossom.
@ -1635,6 +1588,8 @@ public:
}
}
}
delta_sum += delta;
}
/* ********** Main algorithm: ********** */
@ -1962,7 +1917,8 @@ private:
// For each incident edge, find the smallest blossom
// that contains it.
VertexId x = sub->base_vertex;
for (const EdgeT* edge : graph.adjacent_edges[x]) {
for (EdgeIndex e : graph.adjacent_edges[x]) {
const EdgeT* edge = &graph.edges[e];
// Only consider edges pointing out from "x".
if (edge->vt.first == x) {
VertexId y = edge->vt.second;