All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.matching.blossom.v5.BlossomVPrimalUpdater Maven / Gradle / Ivy

The newest version!
/*
 * (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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy