Solve slow maintanance of blossom list
This commit is contained in:
parent
61524990d7
commit
1a98624f2b
|
@ -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(
|
||||||
|
|
Loading…
Reference in New Issue