1
0
Fork 0

Solve slow maintanance of blossom list

This commit is contained in:
Joris van Rantwijk 2024-06-25 21:01:52 +02:00
parent 61524990d7
commit 1a98624f2b
1 changed files with 34 additions and 52 deletions

View File

@ -5,6 +5,7 @@ Algorithm for finding a maximum weight matching in general graphs.
from __future__ import annotations from __future__ import annotations
import sys import sys
import itertools
import math import math
from collections.abc import Sequence from collections.abc import Sequence
from typing import NamedTuple, Optional from typing import NamedTuple, Optional
@ -404,7 +405,7 @@ class _Blossom:
self.vertex_dual_offset: float = 0 self.vertex_dual_offset: float = 0
# "marker" is a temporary variable used to discover common # "marker" is a temporary variable used to discover common
# ancestors in the blossom tree. It is normally False, except # ancestors in the alternating tree. It is normally False, except
# when used by "trace_alternating_paths()". # when used by "trace_alternating_paths()".
self.marker: bool = False self.marker: bool = False
@ -549,7 +550,7 @@ class _MatchingContext:
# the course of the algorithm. # the course of the algorithm.
# #
# Initially there are no non-trivial blossoms. # Initially there are no non-trivial blossoms.
self.nontrivial_blossom: list[_NonTrivialBlossom] = [] self.nontrivial_blossom: set[_NonTrivialBlossom] = set()
# "vertex_set_node[x]" represents the vertex "x" inside the # "vertex_set_node[x]" represents the vertex "x" inside the
# union-find datastructure of its top-level blossom. # union-find datastructure of its top-level blossom.
@ -616,12 +617,8 @@ class _MatchingContext:
def __del__(self) -> None: def __del__(self) -> None:
"""Delete reference cycles during cleanup of the matching context.""" """Delete reference cycles during cleanup of the matching context."""
for blossom in self.trivial_blossom: for blossom in itertools.chain(self.trivial_blossom,
blossom.vertex_set.clear() self.nontrivial_blossom):
del blossom.vertex_set
blossom.tree_blossoms = None
for blossom in self.nontrivial_blossom:
blossom.vertex_set.clear() blossom.vertex_set.clear()
del blossom.vertex_set del blossom.vertex_set
blossom.tree_blossoms = None blossom.tree_blossoms = None
@ -774,10 +771,6 @@ class _MatchingContext:
# T-vertices. # T-vertices.
self.vertex_sedge_queue[x].clear() self.vertex_sedge_queue[x].clear()
for e in self.graph.adjacent_edges[x]: for e in self.graph.adjacent_edges[x]:
# TODO -- Postpone this cleanup step to the scanning of
# S-vertex incident edges.
# It will be more efficient, and also simpler to
# reason about final time complexity.
self.vertex_sedge_node[e] = None self.vertex_sedge_node[e] = None
self.vertex_set_node[x].set_prio(math.inf) self.vertex_set_node[x].set_prio(math.inf)
@ -925,7 +918,8 @@ class _MatchingContext:
def _check_alternating_tree_consistency(self) -> None: def _check_alternating_tree_consistency(self) -> None:
"""TODO -- remove this function, only for debugging""" """TODO -- remove this function, only for debugging"""
for blossom in self.trivial_blossom + self.nontrivial_blossom: for blossom in itertools.chain(self.trivial_blossom,
self.nontrivial_blossom):
if (blossom.parent is None) and (blossom.label != _LABEL_NONE): if (blossom.parent is None) and (blossom.label != _LABEL_NONE):
assert blossom.tree_blossoms is not None assert blossom.tree_blossoms is not None
assert blossom in blossom.tree_blossoms assert blossom in blossom.tree_blossoms
@ -938,38 +932,6 @@ class _MatchingContext:
assert blossom.tree_edge is None assert blossom.tree_edge is None
assert blossom.tree_blossoms is None assert blossom.tree_blossoms is None
def reset_stage(self) -> None:
"""Reset data which are only valid during a stage.
Marks all blossoms as unlabeled, applies delayed delta updates,
and resets tracking of least-slack edges.
This function takes time O((n + m) * log(n)).
"""
assert not self.scan_queue
for blossom in self.trivial_blossom + self.nontrivial_blossom:
# Remove blossom label.
if (blossom.parent is None) and (blossom.label != _LABEL_NONE):
self.reset_blossom_label(blossom)
assert blossom.label == _LABEL_NONE
# Remove blossom from alternating tree.
blossom.tree_edge = None
blossom.tree_blossoms = None
# Unwind lazy delta updates to vertex dual variables.
if blossom.vertex_dual_offset != 0:
for x in blossom.vertices():
self.vertex_dual_2x[x] += blossom.vertex_dual_offset
blossom.vertex_dual_offset = 0
assert self.delta2_queue.empty()
assert self.delta3_queue.empty()
assert self.delta4_queue.empty()
def remove_alternating_tree( def remove_alternating_tree(
self, self,
tree_blossoms: set[_Blossom] tree_blossoms: set[_Blossom]
@ -1123,7 +1085,7 @@ class _MatchingContext:
tree_blossoms.add(blossom) tree_blossoms.add(blossom)
# Insert into the blossom array. # Insert into the blossom array.
self.nontrivial_blossom.append(blossom) self.nontrivial_blossom.add(blossom)
# Link the subblossoms to the their new parent. # Link the subblossoms to the their new parent.
for sub in subblossoms: for sub in subblossoms:
@ -1251,7 +1213,6 @@ class _MatchingContext:
tree_blossoms.add(sub) tree_blossoms.add(sub)
# Delete the expanded blossom. # Delete the expanded blossom.
# TODO -- list manipulation is too slow
self.nontrivial_blossom.remove(blossom) self.nontrivial_blossom.remove(blossom)
def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None: def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None:
@ -1291,7 +1252,6 @@ class _MatchingContext:
sub.delta2_node = self.delta2_queue.insert(prio, sub) sub.delta2_node = self.delta2_queue.insert(prio, sub)
# Delete the expanded blossom. # Delete the expanded blossom.
# TODO -- list manipulation is too slow
self.nontrivial_blossom.remove(blossom) self.nontrivial_blossom.remove(blossom)
# #
@ -1747,7 +1707,7 @@ class _MatchingContext:
# This loop runs through at most O(n) iterations per stage. # This loop runs through at most O(n) iterations per stage.
while True: while True:
# self._check_alternating_tree_consistency() # TODO -- remove this self._check_alternating_tree_consistency() # TODO -- remove this
# Consider the incident edges of newly labeled S-vertices. # Consider the incident edges of newly labeled S-vertices.
self.substage_scan() self.substage_scan()
@ -1807,10 +1767,32 @@ class _MatchingContext:
Also resets tracking of least-slack edges. Also resets tracking of least-slack edges.
This function takes time O(n * log(n)). This function takes time O(n * log(n)).
It is called only once, at the end of the algorithm. It is called once, at the end of the algorithm.
""" """
# TODO -- move that function in here
self.reset_stage() assert not self.scan_queue
for blossom in itertools.chain(self.trivial_blossom,
self.nontrivial_blossom):
# Remove blossom label.
if (blossom.parent is None) and (blossom.label != _LABEL_NONE):
self.reset_blossom_label(blossom)
assert blossom.label == _LABEL_NONE
# Remove blossom from alternating tree.
blossom.tree_edge = None
blossom.tree_blossoms = None
# Unwind lazy delta updates to vertex dual variables.
if blossom.vertex_dual_offset != 0:
for x in blossom.vertices():
self.vertex_dual_2x[x] += blossom.vertex_dual_offset
blossom.vertex_dual_offset = 0
assert self.delta2_queue.empty()
assert self.delta3_queue.empty()
assert self.delta4_queue.empty()
def _verify_blossom_edges( def _verify_blossom_edges(