org.jgrapht.alg.matching.blossom.v5.BlossomVPrimalUpdater Maven / Gradle / Ivy
/*
* (C) Copyright 2018-2023, by Timofey Chudakov and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.matching.blossom.v5;
import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.*;
import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.DEBUG;
/**
* This class is used by {@link KolmogorovWeightedPerfectMatching} for performing primal operations:
* grow, augment, shrink and expand. This class operates on alternating trees, blossom structures,
* and node states. It changes them after applying any primal operation. Also, this class can add
* and subtract some values from nodes' dual variables; it never changes their actual dual
* variables.
*
* The augment operation is used to increase the cardinality of the matching. It is applied to a
* tight (+, +) cross-tree edge. Its main purpose is to alter the matching on the simple path
* between tree roots through the tight edge, destroy the previous tree structures, update the state
* of the node, and change the presence of edges in the priority queues. This operation doesn't
* destroy the tree structure; this technique is called lazy tree structure destroying. The
* information of the nodes from the tree structure block is overwritten when a node is being added
* to another tree. This operation doesn't change the matching in the contracted blossoms.
*
* The grow operation is used to add new nodes to a given tree. This operation is applied only to
* tight infinity edges. It always adds even number of nodes. This operation can grow the tree
* recursively in the depth-first order. If it encounters a tight (+, +) cross-tree edge, it stops
* growing and performs immediate augmentation.
*
* The shrink operation contracts an odd node circuit and introduces a new pseudonode. It is applied
* to tight (+, +) in-tree edges. It changes the state so than the contracted nodes don't appear in
* the surface graph. If during the changing of the endpoints of boundary edge a tight (+, +)
* cross-tree edge is encountered, an immediate augmentation is performed.
*
* The expand operation brings the contracted blossom nodes to the surface graph. It is applied only
* to a "-" blossom with zero dual variable. The operation determines the two branches of a blossom:
* an even and an odd one. The formercontains an even number of edges and can be empty, the latter
* contains an odd number of edges and necessarily contains at least one edge. An even branch is
* inserted into the tree. The state of the algorithm is changes respectively (node duals, tree
* structure, etc.). If some boundary edge in a tight (+, +) cross-tree edge, an immediate
* augmentation is performed.
*
* The immediate augmentations are used to speed up the algorithm. More detailed description of the
* primal operations can be found in their Javadoc.
*
* @param the graph vertex type
* @param the graph edge type
* @author Timofey Chudakov
* @see KolmogorovWeightedPerfectMatching
* @see BlossomVDualUpdater
*/
class BlossomVPrimalUpdater
{
/**
* State information needed for the algorithm
*/
private BlossomVState state;
/**
* Constructs a new instance of BlossomVPrimalUpdater
*
* @param state contains the graph and associated information
*/
public BlossomVPrimalUpdater(BlossomVState state)
{
this.state = state;
}
/**
* Performs grow operation. This is invoked on the plus-infinity {@code growEdge}, which
* connects a "+" node in the tree and an infinity matched node. The {@code growEdge} and the
* matched free edge are added to the tree structure. Two new nodes are added to the tree: minus
* node and plus node. Let's call the node incident to the {@code growEdge} and opposite to the
* minusNode the "tree node".
*
* As the result, following actions are performed:
*
* - Add new child to the children of tree node and minus node
* - Set parent edges of minus and plus nodes
* - If minus node is a blossom, add it to the heap of "-" blossoms
* - Remove growEdge from the heap of infinity edges
* - Remove former infinity edges and add new (+, +) in-tree and cross-tree edges, (+, -)
* cross tree edges to the appropriate heaps (due to the changes of the labels of the minus and
* plus nodes)
* - Add new infinity edge from the plus node
* - Add new tree edges is necessary
* - Subtract tree.eps from the slacks of all edges incident to the minus node
* - Add tree.eps to the slacks of all edges incident to the plus node
*
*
* If the {@code manyGrows} flag is true, performs recursive growing of the tree.
*
* @param growEdge the tight edge between node in the tree and minus node
* @param recursiveGrow specifies whether to perform recursive growing
* @param immediateAugment a flag that indicates whether to perform immediate augmentation if a
* tight (+, +) cross-tree edge is encountered
*/
public void grow(BlossomVEdge growEdge, boolean recursiveGrow, boolean immediateAugment)
{
if (DEBUG) {
System.out.println("Growing edge " + growEdge);
}
long start = System.nanoTime();
int initialTreeNum = state.treeNum;
int dirToMinusNode = growEdge.head[0].isInfinityNode() ? 0 : 1;
BlossomVNode nodeInTheTree = growEdge.head[1 - dirToMinusNode];
BlossomVNode minusNode = growEdge.head[dirToMinusNode];
BlossomVNode plusNode = minusNode.getOppositeMatched();
nodeInTheTree.addChild(minusNode, growEdge, true);
minusNode.addChild(plusNode, minusNode.matched, true);
BlossomVNode stop = plusNode;
while (true) {
minusNode.label = MINUS;
plusNode.label = PLUS;
minusNode.isMarked = plusNode.isMarked = false;
processMinusNodeGrow(minusNode);
processPlusNodeGrow(plusNode, recursiveGrow, immediateAugment);
if (initialTreeNum != state.treeNum) {
break;
}
if (plusNode.firstTreeChild != null) {
minusNode = plusNode.firstTreeChild;
plusNode = minusNode.getOppositeMatched();
} else {
while (plusNode != stop && plusNode.treeSiblingNext == null) {
plusNode = plusNode.getTreeParent();
}
if (plusNode.isMinusNode()) {
minusNode = plusNode.treeSiblingNext;
plusNode = minusNode.getOppositeMatched();
} else {
break;
}
}
}
state.statistics.growTime += System.nanoTime() - start;
}
/**
* Performs augment operation. This is invoked on a tight (+, +) cross-tree edge. It increases
* the matching by 1, converts the trees on both sides into the set of free matched edges, and
* applies lazy delta spreading.
*
* For each tree the following actions are performed:
*
* - Labels of all nodes change to INFINITY
* - tree.eps is subtracted from "-" nodes' duals and added to the "+" nodes' duals
* - tree.eps is subtracted from all edges incident to "+" nodes and added to all edges
* incident to "-" nodes. Consecutively, the slacks of the (+, -) in-tree edges stay
* unchanged
* - Former (-, +) and (+, +) are substituted with the (+, inf) edges (removed and added to
* appropriate heaps).
* - The cardinality of the matching is increased by 1
* - Tree structure references are set to null
* - Tree roots are removed from the linked list of tree roots
*
*
* These actions change only the surface graph. They don't change the nodes and edges in the
* pseudonodes.
*
* @param augmentEdge the edge to augment
*/
public void augment(BlossomVEdge augmentEdge)
{
if (DEBUG) {
System.out.println("Augmenting edge " + augmentEdge);
}
long start = System.nanoTime();
// augment trees on both sides
for (int dir = 0; dir < 2; dir++) {
BlossomVNode node = augmentEdge.head[dir];
augmentBranch(node, augmentEdge);
node.matched = augmentEdge;
}
state.statistics.augmentTime += System.nanoTime() - start;
}
/**
* Performs shrink operation. This is invoked on a tight (+, +) in-tree edge. The result of this
* operation is the substitution of an odd circuit with a single node. This means that we
* consider the set of nodes of odd cardinality as a single node.
*
* In the shrink operation the following main actions are performed:
*
* - Lazy dual updates are applied to all inner edges and nodes on the circuit. Thus, the
* inner edges and nodes in the pseudonodes have valid slacks and dual variables
* - The endpoints of the boundary edges are moved to the new blossom node, which has label
* {@link BlossomVNode.Label#PLUS}
*
- Lazy dual updates are applied to boundary edges and newly created blossom
* - Children of blossom nodes are moved to the blossom, their parent edges are changed
* respectively
* - The blossomSibling references are set so that they form a circular linked list
* - If the blossom becomes a tree root, it substitutes the previous tree's root in the linked
* list of tree roots
* - Since the newly created blossom with "+" label can change the classification of edges,
* their presence in heaps is updated
*
*
* @param blossomFormingEdge the tight (+, +) in-tree edge
* @param immediateAugment a flag that indicates whether to perform immediate augmentation if a
* tight (+, +) cross-tree edge is encountered
* @return the newly created blossom
*/
public BlossomVNode shrink(BlossomVEdge blossomFormingEdge, boolean immediateAugment)
{
long start = System.nanoTime();
if (DEBUG) {
System.out.println("Shrinking edge " + blossomFormingEdge);
}
BlossomVNode blossomRoot = findBlossomRoot(blossomFormingEdge);
BlossomVTree tree = blossomRoot.tree;
/*
* We don't actually need position of the blossom node since blossom nodes aren't stored in
* the state.nodes array. We use blossom's position as its id for debug purposes.
*/
BlossomVNode blossom = new BlossomVNode(state.nodeNum + state.blossomNum);
// initialize blossom node
blossom.tree = tree;
blossom.isBlossom = true;
blossom.isOuter = true;
blossom.isTreeRoot = blossomRoot.isTreeRoot;
blossom.dual = -tree.eps;
if (blossom.isTreeRoot) {
tree.root = blossom;
} else {
blossom.matched = blossomRoot.matched;
}
// mark all blossom nodes
for (BlossomVEdge.BlossomNodesIterator iterator =
blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();)
{
iterator.next().isMarked = true;
}
// move edges and children, change slacks if necessary
BlossomVEdge augmentEdge = updateTreeStructure(blossomRoot, blossomFormingEdge, blossom);
// create circular linked list of circuit nodes
setBlossomSiblings(blossomRoot, blossomFormingEdge);
// reset marks of blossom nodes
blossomRoot.isMarked = false;
blossomRoot.isProcessed = false;
for (BlossomVNode current = blossomRoot.blossomSibling.getOpposite(blossomRoot);
current != blossomRoot; current = current.blossomSibling.getOpposite(current))
{
current.isMarked = false;
current.isProcessed = false;
}
blossomRoot.matched = null; // now new blossom is matched (used when finishing the matching
state.statistics.shrinkNum++;
state.blossomNum++;
state.statistics.shrinkTime += System.nanoTime() - start;
if (augmentEdge != null && immediateAugment) {
if (DEBUG) {
System.out.println("Bingo shrink");
}
augment(augmentEdge);
}
return blossom;
}
/**
* Performs expand operation. This is invoked on a previously contracted pseudonode. The result
* of this operation is bringing the nodes in the blossom to the surface graph. An even branch
* of the blossom is inserted into the tree structure. Endpoints of the edges incident to the
* blossom are moved one layer down. The slack of the inner and boundary edges are update
* according to the lazy delta spreading technique.
*
* Note: only "-" blossoms can be expanded. At that moment their dual variables are
* always zero. This is the reason why they don't need to be stored to compute the dual
* solution.
*
* In the expand operation the following actions are performed:
*
* - Endpoints of the boundary edges are updated
* - The matching in the blossom is changed. Note: the resulting matching doesn't
* depend on the previous matching
* - isOuter flags are updated
* - node.tree are updated
* - Tree structure is updated including parent edges and tree children of the nodes on the
* even branch
* - The endpoints of some edges change their labels to "+" => their slacks are changed
* according to the lazy delta spreading and their presence in heaps also changes
*
*
* @param blossom the blossom to expand
* @param immediateAugment a flag that indicates whether to perform immediate augmentation if a
* tight (+, +) cross-tree edge is encountered
*/
public void expand(BlossomVNode blossom, boolean immediateAugment)
{
if (DEBUG) {
System.out.println("Expanding blossom " + blossom);
}
long start = System.nanoTime();
BlossomVTree tree = blossom.tree;
double eps = tree.eps;
blossom.dual -= eps;
blossom.tree.removeMinusBlossom(blossom); // it doesn't belong to the tree no more
BlossomVNode branchesEndpoint =
blossom.parentEdge.getCurrentOriginal(blossom).getPenultimateBlossom();
if (DEBUG) {
printBlossomNodes(branchesEndpoint);
}
// the node which is matched to the node from outside
BlossomVNode blossomRoot =
blossom.matched.getCurrentOriginal(blossom).getPenultimateBlossom();
// mark blossom nodes
BlossomVNode current = blossomRoot;
do {
current.isMarked = true;
current = current.blossomSibling.getOpposite(current);
} while (current != blossomRoot);
// move all edge from blossom to penultimate children
blossom.removeFromChildList();
for (BlossomVNode.IncidentEdgeIterator iterator = blossom.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode penultimateChild = edge.headOriginal[1 - iterator.getDir()]
.getPenultimateBlossomAndFixBlossomGrandparent();
edge.moveEdgeTail(blossom, penultimateChild);
}
// reverse the circular blossomSibling references so that the first branch in even branch
if (!forwardDirection(blossomRoot, branchesEndpoint)) {
reverseBlossomSiblings(blossomRoot);
}
// change the matching, the labeling and the dual information on the odd branch
expandOddBranch(blossomRoot, branchesEndpoint, tree);
// change the matching, the labeling and dual information on the even branch
BlossomVEdge augmentEdge = expandEvenBranch(blossomRoot, branchesEndpoint, blossom);
// reset marks of blossom nodes
current = blossomRoot;
do {
current.isMarked = false;
current.isProcessed = false;
current = current.blossomSibling.getOpposite(current);
} while (current != blossomRoot);
state.statistics.expandNum++;
state.removedNum++;
if (DEBUG) {
tree.printTreeNodes();
}
state.statistics.expandTime += System.nanoTime() - start;
if (immediateAugment && augmentEdge != null) {
if (DEBUG) {
System.out.println("Bingo expand");
}
augment(augmentEdge);
}
}
/**
* Processes a minus node in the grow operation. Applies lazy delta spreading, adds new (-,+)
* cross-tree edges, removes former (+, inf) edges.
*
* @param minusNode a minus endpoint of the matched edge that is being appended to the tree
*/
private void processMinusNodeGrow(BlossomVNode minusNode)
{
double eps = minusNode.tree.eps;
minusNode.dual += eps;
// maintain heap of "-" blossoms
if (minusNode.isBlossom) {
minusNode.tree.addMinusBlossom(minusNode);
}
// maintain minus-plus edges in the minus-plus heaps in the tree edges
for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
edge.slack -= eps;
if (opposite.isPlusNode()) {
if (opposite.tree != minusNode.tree) {
// encountered (-,+) cross-tree edge
if (opposite.tree.currentEdge == null) {
BlossomVTree.addTreeEdge(minusNode.tree, opposite.tree);
}
opposite.tree.removePlusInfinityEdge(edge);
opposite.tree.currentEdge
.addToCurrentMinusPlusHeap(edge, opposite.tree.currentDirection);
} else if (opposite != minusNode.getOppositeMatched()) {
// encountered a former (+, inf) edge
minusNode.tree.removePlusInfinityEdge(edge);
}
}
}
}
/**
* Processes a plus node during the grow operation. Applies lazy delta spreading, removes former
* (+, inf) edges, adds new (+, +) in-tree and cross-tree edges, new (+, -) cross-tree edges.
* When the {@code manyGrows} flag is on, collects the tight (+, inf) edges on grows them as
* well.
*
* Note: the recursive grows must be done ofter the grow operation on the current edge is
* over. This ensures correct state of the heaps and the edges' slacks.
*
* @param node a plus endpoint of the matched edge that is being appended to the tree
* @param recursiveGrow a flag that indicates whether to grow the tree recursively
* @param immediateAugment a flag that indicates whether to perform immediate augmentation if a
* tight (+, +) cross-tree edge is encountered
*/
private void processPlusNodeGrow(
BlossomVNode node, boolean recursiveGrow, boolean immediateAugment)
{
double eps = node.tree.eps;
node.dual -= eps;
BlossomVEdge augmentEdge = null;
for (BlossomVNode.IncidentEdgeIterator iterator = node.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
// maintain heap of plus-infinity edges
edge.slack += eps;
if (opposite.isPlusNode()) {
// this is a (+,+) edge
if (opposite.tree == node.tree) {
// this is blossom-forming edge
node.tree.removePlusInfinityEdge(edge);
node.tree.addPlusPlusEdge(edge);
} else {
// this is plus-plus edge to another trees
if (opposite.tree.currentEdge == null) {
BlossomVTree.addTreeEdge(node.tree, opposite.tree);
}
opposite.tree.removePlusInfinityEdge(edge);
opposite.tree.currentEdge.addPlusPlusEdge(edge);
if (edge.slack <= node.tree.eps + opposite.tree.eps) {
augmentEdge = edge;
}
}
} else if (opposite.isMinusNode()) {
// this is a (+,-) edge
if (opposite.tree != node.tree) {
// this is (+,-) edge to another trees
if (opposite.tree.currentEdge == null) {
BlossomVTree.addTreeEdge(node.tree, opposite.tree);
}
opposite.tree.currentEdge
.addToCurrentPlusMinusHeap(edge, opposite.tree.currentDirection);
}
} else if (opposite.isInfinityNode()) {
node.tree.addPlusInfinityEdge(edge);
// this edge can be grown as well
// it can be the case when this edge can't be grown because opposite vertex is
// already added
// to this tree via some other grow operation
if (recursiveGrow && edge.slack <= eps && !edge.getOpposite(node).isMarked) {
if (DEBUG) {
System.out.println("Growing edge " + edge);
}
BlossomVNode minusNode = edge.getOpposite(node);
BlossomVNode plusNode = minusNode.getOppositeMatched();
minusNode.isMarked = plusNode.isMarked = true;
node.addChild(minusNode, edge, true);
minusNode.addChild(plusNode, minusNode.matched, true);
}
}
}
if (immediateAugment && augmentEdge != null) {
if (DEBUG) {
System.out.println("Bingo grow");
}
augment(augmentEdge);
}
state.statistics.growNum++;
}
/**
* Expands an even branch of the blossom. Here it is assumed that the blossomSiblings are
* directed in the way that the even branch goes from {@code blossomRoot} to
* {@code branchesEndpoint}.
*
* The method traverses the nodes twice: first it changes the tree structure, updates the
* labeling and flags, adds children, and changes the matching. After that it changes the slacks
* of the edges according to the lazy delta spreading and their presence in heaps. This
* operation is done in two steps because the later step requires correct labeling of the nodes
* on the branch.
*
* Note: this branch may consist of only one node. In this case {@code blossomRoot} and
* {@code branchesEndpoint} are the same nodes
*
* @param blossomRoot the node of the blossom which is matched from the outside
* @param branchesEndpoint the common endpoint of the even and odd branches
* @param blossom the node that is being expanded
* @return a tight (+, +) cross-tree edge if it is encountered, null otherwise
*/
private BlossomVEdge expandEvenBranch(
BlossomVNode blossomRoot, BlossomVNode branchesEndpoint, BlossomVNode blossom)
{
BlossomVEdge augmentEdge = null;
BlossomVTree tree = blossom.tree;
blossomRoot.matched = blossom.matched;
blossomRoot.tree = tree;
blossomRoot.addChild(blossom.matched.getOpposite(blossomRoot), blossomRoot.matched, false);
BlossomVNode current = blossomRoot;
BlossomVNode prevNode = current;
current.label = MINUS;
current.isOuter = true;
current.parentEdge = blossom.parentEdge;
// first traversal. It is done from blossomRoot to branchesEndpoint, i.e. from higher
// layers of the tree to the lower
while (current != branchesEndpoint) {
// process "+" node
current = current.blossomSibling.getOpposite(current);
current.label = PLUS;
current.isOuter = true;
current.tree = tree;
current.matched = current.blossomSibling;
BlossomVEdge prevMatched = current.blossomSibling;
current.addChild(prevNode, prevNode.blossomSibling, false);
prevNode = current;
// process "-" node
current = current.blossomSibling.getOpposite(current);
current.label = MINUS;
current.isOuter = true;
current.tree = tree;
current.matched = prevMatched;
current.addChild(prevNode, prevNode.blossomSibling, false);
prevNode = current;
}
blossom.parentEdge
.getOpposite(branchesEndpoint).addChild(branchesEndpoint, blossom.parentEdge, false);
// second traversal, update edge slacks and their presence in heaps
current = blossomRoot;
expandMinusNode(current);
while (current != branchesEndpoint) {
current = current.blossomSibling.getOpposite(current);
BlossomVEdge edge = expandPlusNode(current);
if (edge != null) {
augmentEdge = edge;
}
current.isProcessed = true; // this is needed for correct processing of (+, +) edges
// connecting two node on the branch
current = current.blossomSibling.getOpposite(current);
expandMinusNode(current);
}
return augmentEdge;
}
/**
* Expands the nodes on an odd branch. Here it is assumed that the blossomSiblings are directed
* in the way the odd branch goes from {@code branchesEndpoint} to {@code blossomRoot}.
*
* The method traverses the nodes only once setting the labels, flags, updating the matching,
* removing former (+, -) edges and creating new (+, inf) edges in the corresponding heaps. The
* method doesn't process the {@code blossomRoot} and {@code branchesEndpoint} as they belong to
* the even branch.
*
* @param blossomRoot the node that is matched from the outside
* @param branchesEndpoint the common node of the even and odd branches
* @param tree the tree the blossom was previously in
*/
private void expandOddBranch(
BlossomVNode blossomRoot, BlossomVNode branchesEndpoint, BlossomVTree tree)
{
BlossomVNode current = branchesEndpoint.blossomSibling.getOpposite(branchesEndpoint);
// the traversal is done from branchesEndpoint to blossomRoot, i.e. from
// lower layers to higher
while (current != blossomRoot) {
current.label = BlossomVNode.Label.INFINITY;
current.isOuter = true;
current.tree = null;
current.matched = current.blossomSibling;
BlossomVEdge prevMatched = current.blossomSibling;
expandInfinityNode(current, tree);
current = current.blossomSibling.getOpposite(current);
current.label = BlossomVNode.Label.INFINITY;
current.isOuter = true;
current.tree = null;
current.matched = prevMatched;
expandInfinityNode(current, tree);
current = current.blossomSibling.getOpposite(current);
}
}
/**
* Changes dual information of the {@code plusNode} and edge incident to it. This method relies
* on the labeling produced by the first traversal of the
* {@link BlossomVPrimalUpdater#expandEvenBranch(BlossomVNode, BlossomVNode, BlossomVNode)} and
* on the isProcessed flags of the nodes on the even branch that have been traversed already. It
* also assumes that all blossom nodes are marked.
*
* Since one of endpoints of the edges previously incident to the blossom changes its label, we
* have to update the slacks of the boundary edges incindent to the {@code plusNode}.
*
* @param plusNode the "+" node from the even branch
* @return a tight (+, +) cross-tree edge if it is encountered, null otherwise
*/
private BlossomVEdge expandPlusNode(BlossomVNode plusNode)
{
BlossomVEdge augmentEdge = null;
double eps = plusNode.tree.eps; // the plusNode.tree is assumed to be correct
plusNode.dual -= eps; // apply lazy delta spreading
for (BlossomVNode.IncidentEdgeIterator iterator = plusNode.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
// update slack of the edge
if (opposite.isMarked && opposite.isPlusNode()) {
// this is an inner (+, +) edge
if (!opposite.isProcessed) {
// we encounter this edge for the first time
edge.slack += 2 * eps;
}
} else if (!opposite.isMarked) {
// this is boundary edge
edge.slack += 2 * eps; // the endpoint changes its label to "+"
} else if (!opposite.isMinusNode()) {
// this edge is inner edge between even and odd branches or it is an inner (+, +)
// edge
edge.slack += eps;
}
// update its presence in the heap of edges
if (opposite.isPlusNode()) {
if (opposite.tree == plusNode.tree) {
// this edge becomes a (+, +) in-tree edge
if (!opposite.isProcessed) {
// if opposite.isProcessed = true => this is an inner (+, +) edge => its
// slack has been
// updated already and it has been added to the plus-plus edges heap already
plusNode.tree.addPlusPlusEdge(edge);
}
} else {
// opposite is from another tree since it's label is "+"
opposite.tree.currentEdge.removeFromCurrentMinusPlusHeap(edge);
opposite.tree.currentEdge.addPlusPlusEdge(edge);
if (edge.slack <= eps + opposite.tree.eps) {
augmentEdge = edge;
}
}
} else if (opposite.isMinusNode()) {
if (opposite.tree != plusNode.tree) {
// this edge becomes a (+, -) cross-tree edge
if (opposite.tree.currentEdge == null) {
BlossomVTree.addTreeEdge(plusNode.tree, opposite.tree);
}
opposite.tree.currentEdge
.addToCurrentPlusMinusHeap(edge, opposite.tree.currentDirection);
}
} else {
// this is either an inner edge, that becomes a (+, inf) edge, or it is a former (-,
// +) edge,
// that also becomes a (+, inf) edge
plusNode.tree.addPlusInfinityEdge(edge); // updating edge's key
}
}
return augmentEdge;
}
/**
* Expands a minus node from the odd branch. Changes the slacks of inner (-,-) and (-, inf)
* edges.
*
* @param minusNode a "-" node from the even branch
*/
private void expandMinusNode(BlossomVNode minusNode)
{
double eps = minusNode.tree.eps; // the minusNode.tree is assumed to be correct
minusNode.dual += eps;
if (minusNode.isBlossom) {
minusNode.tree.addMinusBlossom(minusNode);
}
for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
if (opposite.isMarked && !opposite.isPlusNode()) {
// this is a (-, inf) or (-, -) inner edge
edge.slack -= eps;
}
}
}
/**
* Expands an infinity node from the odd branch
*
* @param infinityNode a node from the odd branch
* @param tree the tree the blossom was previously in
*/
private void expandInfinityNode(BlossomVNode infinityNode, BlossomVTree tree)
{
double eps = tree.eps;
for (BlossomVNode.IncidentEdgeIterator iterator = infinityNode.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
if (!opposite.isMarked) {
edge.slack += eps; // since edge's label changes to inf and this is a boundary edge
if (opposite.isPlusNode()) {
// if this node is marked => it's a blossom node => this edge has been processed
// already
if (opposite.tree != tree) {
opposite.tree.currentEdge.removeFromCurrentMinusPlusHeap(edge);
}
opposite.tree.addPlusInfinityEdge(edge);
}
}
}
}
/**
* Converts a tree into a set of free matched edges. Changes the matching starting from
* {@code firstNode} all the way up to the firstNode.tree.root. It changes the labeling of the
* nodes, applies lazy delta spreading, updates edges' presence in the heaps. This method also
* deletes unnecessary tree edges.
*
* This method doesn't change the nodes and edge contracted in the blossoms.
*
* @param firstNode an endpoint of the {@code augmentEdge} which belongs to the tree to augment
* @param augmentEdge a tight (+, +) cross tree edge
*/
private void augmentBranch(BlossomVNode firstNode, BlossomVEdge augmentEdge)
{
BlossomVTree tree = firstNode.tree;
double eps = tree.eps;
BlossomVNode root = tree.root;
// set currentEdge and currentDirection of all opposite trees connected via treeEdge
tree.setCurrentEdges();
// apply tree.eps to all tree nodes and updating slacks of all incident edges
for (BlossomVTree.TreeNodeIterator treeNodeIterator = tree.treeNodeIterator();
treeNodeIterator.hasNext();)
{
BlossomVNode node = treeNodeIterator.next();
if (!node.isMarked) {
// apply lazy delta spreading
if (node.isPlusNode()) {
node.dual += eps;
} else {
node.dual -= eps;
}
for (BlossomVNode.IncidentEdgeIterator incidentEdgeIterator =
node.incidentEdgesIterator(); incidentEdgeIterator.hasNext();)
{
BlossomVEdge edge = incidentEdgeIterator.next();
int dir = incidentEdgeIterator.getDir();
BlossomVNode opposite = edge.head[dir];
BlossomVTree oppositeTree = opposite.tree;
if (node.isPlusNode()) {
edge.slack -= eps;
if (oppositeTree != null && oppositeTree != tree) {
// if this edge is a cross-tree edge
BlossomVTreeEdge treeEdge = oppositeTree.currentEdge;
if (opposite.isPlusNode()) {
// this is a (+,+) cross-tree edge
treeEdge.removeFromPlusPlusHeap(edge);
oppositeTree.addPlusInfinityEdge(edge);
} else if (opposite.isMinusNode()) {
// this is a (+,-) cross-tree edge
treeEdge.removeFromCurrentPlusMinusHeap(edge);
}
}
} else {
// current node is a "-" node
edge.slack += eps;
if (oppositeTree != null && oppositeTree != tree && opposite.isPlusNode()) {
// this is a (-,+) cross-tree edge
BlossomVTreeEdge treeEdge = oppositeTree.currentEdge;
treeEdge.removeFromCurrentMinusPlusHeap(edge);
oppositeTree.addPlusInfinityEdge(edge);
}
}
}
node.label = INFINITY;
} else {
// this node was added to the tree by the grow operation,
// but it hasn't been processed, so we don't need to process it here
node.isMarked = false;
}
}
// add all elements from the (-,+) and (+,+) heaps to (+, inf) heaps of the opposite trees
// and
// delete tree edges
for (BlossomVTree.TreeEdgeIterator treeEdgeIterator = tree.treeEdgeIterator();
treeEdgeIterator.hasNext();)
{
BlossomVTreeEdge treeEdge = treeEdgeIterator.next();
int dir = treeEdgeIterator.getCurrentDirection();
BlossomVTree opposite = treeEdge.head[dir];
opposite.currentEdge = null;
opposite.plusPlusEdges.meld(treeEdge.plusPlusEdges);
opposite.plusPlusEdges.meld(treeEdge.getCurrentMinusPlusHeap(dir));
treeEdge.removeFromTreeEdgeList();
}
// update the matching
BlossomVEdge matchedEdge = augmentEdge;
BlossomVNode plusNode = firstNode;
BlossomVNode minusNode = plusNode.getTreeParent();
while (minusNode != null) {
plusNode.matched = matchedEdge;
matchedEdge = minusNode.parentEdge;
minusNode.matched = matchedEdge;
plusNode = minusNode.getTreeParent();
minusNode = plusNode.getTreeParent();
}
root.matched = matchedEdge;
// remove root from the linked list of roots;
root.removeFromChildList();
root.isTreeRoot = false;
state.treeNum--;
}
/**
* Updates the tree structure in the shrink operation. Moves the endpoints of the boundary edges
* to the {@code blossom}, moves the children of the nodes on the circuit to the blossom,
* updates edges's slacks and presence in heaps accordingly.
*
* @param blossomRoot the node that is matched from the outside or is a tree root
* @param blossomFormingEdge a tight (+, +) edge
* @param blossom the node that is being inserted into the tree structure
* @return a tight (+, +) cross-tree edge if it is encountered, null otherwise
*/
private BlossomVEdge updateTreeStructure(
BlossomVNode blossomRoot, BlossomVEdge blossomFormingEdge, BlossomVNode blossom)
{
BlossomVEdge augmentEdge = null;
BlossomVTree tree = blossomRoot.tree;
/**
* Go through every vertex in the blossom and move its child list to blossom child list.
* Handle all blossom nodes except for the blossom root. The reason is that we can't move
* root's correctly to the blossom until both children from the circuit are removed from the
* its children list
*/
for (BlossomVEdge.BlossomNodesIterator iterator =
blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();)
{
BlossomVNode blossomNode = iterator.next();
if (blossomNode != blossomRoot) {
if (blossomNode.isPlusNode()) {
// substitute varNode with the blossom in the tree structure
blossomNode.removeFromChildList();
blossomNode.moveChildrenTo(blossom);
BlossomVEdge edge = shrinkPlusNode(blossomNode, blossom);
if (edge != null) {
augmentEdge = edge;
}
blossomNode.isProcessed = true;
} else {
if (blossomNode.isBlossom) {
tree.removeMinusBlossom(blossomNode);
}
blossomNode.removeFromChildList(); // minus node have only one child and this
// child belongs to the circuit
shrinkMinusNode(blossomNode, blossom);
}
}
blossomNode.blossomGrandparent = blossomNode.blossomParent = blossom;
}
// substitute varNode with the blossom in the tree structure
blossomRoot.removeFromChildList();
if (!blossomRoot.isTreeRoot) {
blossomRoot.getTreeParent().addChild(blossom, blossomRoot.parentEdge, false);
} else {
// substitute blossomRoot with blossom in the linked list of tree roots
blossom.treeSiblingNext = blossomRoot.treeSiblingNext;
blossom.treeSiblingPrev = blossomRoot.treeSiblingPrev;
blossomRoot.treeSiblingPrev.treeSiblingNext = blossom;
if (blossomRoot.treeSiblingNext != null) {
blossomRoot.treeSiblingNext.treeSiblingPrev = blossom;
}
}
// finally process blossomRoot
blossomRoot.moveChildrenTo(blossom);
BlossomVEdge edge = shrinkPlusNode(blossomRoot, blossom);
if (edge != null) {
augmentEdge = edge;
}
blossomRoot.isTreeRoot = false;
return augmentEdge;
}
/**
* Processes a plus node on an odd circuit in the shrink operation. Moves endpoints of the
* boundary edges, updates slacks of incident edges.
*
* @param plusNode a plus node from an odd circuit
* @param blossom a newly created pseudonode
* @return a tight (+, +) cross-tree edge if it is encountered, null otherwise
*/
private BlossomVEdge shrinkPlusNode(BlossomVNode plusNode, BlossomVNode blossom)
{
BlossomVEdge augmentEdge = null;
BlossomVTree tree = plusNode.tree;
double eps = tree.eps;
plusNode.dual += eps;
for (BlossomVNode.IncidentEdgeIterator iterator = plusNode.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
if (!opposite.isMarked) {
// opposite isn't a node inside the blossom
edge.moveEdgeTail(plusNode, blossom);
if (opposite.tree != tree && opposite.isPlusNode()
&& edge.slack <= eps + opposite.tree.eps)
{
augmentEdge = edge;
}
} else if (opposite.isPlusNode()) {
// inner edge, subtract eps only in the case the opposite node is a "+" node
if (!opposite.isProcessed) { // here we rely on the proper setting of the
// isProcessed flag
// remove this edge when it is encountered for the first time
tree.removePlusPlusEdge(edge);
}
edge.slack -= eps;
}
}
return augmentEdge;
}
/**
* Processes a minus node from an odd circuit in the shrink operation. Moves the endpoints of
* the boundary edges, updates their slacks
*
* @param minusNode a minus node from an odd circuit
* @param blossom a newly create pseudonode
*/
private void shrinkMinusNode(BlossomVNode minusNode, BlossomVNode blossom)
{
BlossomVTree tree = minusNode.tree;
double eps = tree.eps;
minusNode.dual -= eps;
for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator();
iterator.hasNext();)
{
BlossomVEdge edge = iterator.next();
BlossomVNode opposite = edge.head[iterator.getDir()];
BlossomVTree oppositeTree = opposite.tree;
if (!opposite.isMarked) {
// opposite isn't a node inside the blossom
edge.moveEdgeTail(minusNode, blossom);
edge.slack += 2 * eps;
if (opposite.tree == tree) {
// edge to the node from the same tree, need only to add it to "++" heap if
// opposite is "+" node
if (opposite.isPlusNode()) {
tree.addPlusPlusEdge(edge);
}
} else {
// cross-tree edge or infinity edge
if (opposite.isPlusNode()) {
oppositeTree.currentEdge.removeFromCurrentMinusPlusHeap(edge);
oppositeTree.currentEdge.addPlusPlusEdge(edge);
} else if (opposite.isMinusNode()) {
if (oppositeTree.currentEdge == null) {
BlossomVTree.addTreeEdge(tree, oppositeTree);
}
oppositeTree.currentEdge
.addToCurrentPlusMinusHeap(edge, oppositeTree.currentDirection);
} else {
tree.addPlusInfinityEdge(edge);
}
}
} else if (opposite.isMinusNode()) {
// this is an inner edge
edge.slack += eps;
}
}
}
/**
* Creates a circular linked list of blossom nodes.
*
* Note: this method heavily relies on the property of the
* {@link BlossomVEdge.BlossomNodesIterator} that it returns the blossomRoot while processing
* the first branch (with direction 0).
*
* @param blossomRoot the common endpoint of two branches
* @param blossomFormingEdge a tight (+, +) in-tree edge
*/
private void setBlossomSiblings(BlossomVNode blossomRoot, BlossomVEdge blossomFormingEdge)
{
// set blossom sibling nodes
BlossomVEdge prevEdge = blossomFormingEdge;
for (BlossomVEdge.BlossomNodesIterator iterator =
blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();)
{
BlossomVNode current = iterator.next();
if (iterator.getCurrentDirection() == 0) {
current.blossomSibling = prevEdge;
prevEdge = current.parentEdge;
} else {
current.blossomSibling = current.parentEdge;
}
}
}
/**
* Finds a blossom root of the circuit created by the {@code edge}. More precisely, finds an lca
* of edge.head[0] and edge.head[1].
*
* @param blossomFormingEdge a tight (+, +) in-tree edge
* @return the lca of edge.head[0] and edge.head[1]
*/
BlossomVNode findBlossomRoot(BlossomVEdge blossomFormingEdge)
{
BlossomVNode root, upperBound; // need to be scoped outside of the loop
BlossomVNode[] endPoints = new BlossomVNode[2];
endPoints[0] = blossomFormingEdge.head[0];
endPoints[1] = blossomFormingEdge.head[1];
int branch = 0;
while (true) {
if (endPoints[branch].isMarked) {
root = endPoints[branch];
upperBound = endPoints[1 - branch];
break;
}
endPoints[branch].isMarked = true;
if (endPoints[branch].isTreeRoot) {
upperBound = endPoints[branch];
BlossomVNode jumpNode = endPoints[1 - branch];
while (!jumpNode.isMarked) {
jumpNode = jumpNode.getTreeGrandparent();
}
root = jumpNode;
break;
}
endPoints[branch] = endPoints[branch].getTreeGrandparent();
branch = 1 - branch;
}
BlossomVNode jumpNode = root;
while (jumpNode != upperBound) {
jumpNode = jumpNode.getTreeGrandparent();
jumpNode.isMarked = false;
}
clearIsMarkedAndSetIsOuter(root, blossomFormingEdge.head[0]);
clearIsMarkedAndSetIsOuter(root, blossomFormingEdge.head[1]);
return root;
}
/**
* Traverses the nodes in the tree from {@code start} to {@code root} and sets isMarked and
* isOuter to false
*/
private void clearIsMarkedAndSetIsOuter(BlossomVNode root, BlossomVNode start)
{
while (start != root) {
start.isMarked = false;
start.isOuter = false;
start = start.getTreeParent();
start.isOuter = false;
start = start.getTreeParent();
}
root.isOuter = false;
root.isMarked = false;
}
/**
* Reverses the direction of blossomSibling references
*
* @param blossomNode some node on an odd circuit
*/
private void reverseBlossomSiblings(BlossomVNode blossomNode)
{
BlossomVEdge prevEdge = blossomNode.blossomSibling;
BlossomVNode current = blossomNode;
do {
current = prevEdge.getOpposite(current);
BlossomVEdge tmpEdge = prevEdge;
prevEdge = current.blossomSibling;
current.blossomSibling = tmpEdge;
} while (current != blossomNode);
}
/**
* Checks whether the direction of blossomSibling references is suitable for the expand
* operation, i.e. an even branch goes from {@code blossomRoot} to {@code branchesEndpoint}.
*
* @param blossomRoot a node on an odd circuit that is matched from the outside
* @param branchesEndpoint a node common to both branches
* @return true if the condition described above holds, false otherwise
*/
private boolean forwardDirection(BlossomVNode blossomRoot, BlossomVNode branchesEndpoint)
{
int hops = 0;
BlossomVNode current = blossomRoot;
while (current != branchesEndpoint) {
++hops;
current = current.blossomSibling.getOpposite(current);
}
return (hops & 1) == 0;
}
/**
* Prints {@code blossomNode} and all its blossom siblings. This method is for debug purposes.
*
* @param blossomNode the node to start from
*/
public void printBlossomNodes(BlossomVNode blossomNode)
{
System.out.println("Printing blossom nodes");
BlossomVNode current = blossomNode;
do {
System.out.println(current);
current = current.blossomSibling.getOpposite(current);
} while (current != blossomNode);
}
}