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

org.jgrapht.alg.spanning.AhujaOrlinSharmaCapacitatedMinimumSpanningTree Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2018-2023, by Christoph Grüne 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.spanning;

import org.jgrapht.*;
import org.jgrapht.alg.cycle.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.graph.*;
import org.jgrapht.traverse.*;

import java.util.*;

/**
 * Implementation of an algorithm for the capacitated minimum spanning tree problem using a cyclic
 * exchange neighborhood, based on Ravindra K. Ahuja, James B. Orlin, Dushyant Sharma, A composite
 * very large-scale neighborhood structure for the capacitated minimum spanning tree problem,
 * Operations Research Letters, Volume 31, Issue 3, 2003, Pages 185-194, ISSN 0167-6377,
 * https://doi.org/10.1016/S0167-6377(02)00236-5.
 * (http://www.sciencedirect.com/science/article/pii/S0167637702002365)
 * 

* A Capacitated Minimum * Spanning Tree (CMST) is a rooted minimal cost spanning tree that satisfies the capacity * constrained on all trees that are connected to the designated root. The problem is NP-hard. The * hard part of the problem is the implicit partition defined by the subtrees. If one can find the * correct partition, the MSTs can be calculated in polynomial time. *

* This algorithm is a very large scale neighborhood search algorithm using a cyclic exchange * neighborhood until a local minimum is found. It makes frequently use of a MST algorithm and the * algorithm for subset disjoint cycles by Ahuja et al. That is, the algorithm may run in * exponential time. This algorithm is implemented in two different version: a local search and a * tabu search. In both cases we have to find the best neighbor of the current capacitated spanning * tree. * * @param the vertex type * @param the edge type * @author Christoph Grüne * @since July 11, 2018 */ public class AhujaOrlinSharmaCapacitatedMinimumSpanningTree extends AbstractCapacitatedMinimumSpanningTree { /** * the maximal length of the cycle in the neighborhood */ private final int lengthBound; /** * contains whether the best (if true) or the first improvement (if false) is returned in the * neighborhood search */ private final boolean bestImprovement; /** * the number of the most profitable operations considered in the GRASP procedure for the * initial solution. */ private final int numberOfOperationsParameter; /** * the initial solution */ private CapacitatedSpanningTree initialSolution; /** * contains whether the local search uses the vertex operation */ private final boolean useVertexOperation; /** * contains whether the local search uses the subtree operation */ private final boolean useSubtreeOperation; /** * contains whether a tabu search is used */ private final boolean useTabuSearch; /** * the tabu time that is the number of iterations an element is in the tabu list */ private final int tabuTime; /** * the upper limit of non-improving exchanges, this is the stopping criterion in the tabu search */ private final int upperLimitTabuExchanges; /** * contains whether the algorithm was executed */ private boolean isAlgorithmExecuted; /** * Constructs a new instance of this algorithm. * * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm * @param numberOfOperationsParameter the number of operations that are considered in the * randomized Esau-Williams algorithm * {@link EsauWilliamsCapacitatedMinimumSpanningTree} @see * EsauWilliamsCapacitatedMinimumSpanningTree */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( Graph graph, V root, double capacity, Map demands, int lengthBound, int numberOfOperationsParameter) { this( graph, root, capacity, demands, lengthBound, false, numberOfOperationsParameter, true, true, true, 10, 50); } /** * Constructs a new instance of this algorithm with the proposed initial solution. * * @param initialSolution the initial solution * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( CapacitatedSpanningTree initialSolution, Graph graph, V root, double capacity, Map demands, int lengthBound) { this( initialSolution, graph, root, capacity, demands, lengthBound, false, true, true, true, 10, 50); } /** * Constructs a new instance of this algorithm. * * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm * @param bestImprovement contains whether the best (if true) or the first improvement (if * false) is returned in the neighborhood search * @param numberOfOperationsParameter the number of operations that are considered in the * randomized Esau-Williams algorithm * {@link EsauWilliamsCapacitatedMinimumSpanningTree} @see * EsauWilliamsCapacitatedMinimumSpanningTree * @param useVertexOperation contains whether the local search uses the vertex operation * @param useSubtreeOperation contains whether the local search uses the subtree operation * @param useTabuSearch contains whether a tabu search is used * @param tabuTime the tabu time that is the number of iterations an element is in the tabu list * @param upperLimitTabuExchanges the upper limit of non-improving exchanges, this is the * stopping criterion in the tabu search */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( Graph graph, V root, double capacity, Map demands, int lengthBound, boolean bestImprovement, int numberOfOperationsParameter, boolean useVertexOperation, boolean useSubtreeOperation, boolean useTabuSearch, int tabuTime, int upperLimitTabuExchanges) { super(graph, root, capacity, demands); this.lengthBound = lengthBound; this.bestImprovement = bestImprovement; this.numberOfOperationsParameter = numberOfOperationsParameter; if (!useSubtreeOperation && !useVertexOperation) { throw new IllegalArgumentException( "At least one of the options has to be enabled, otherwise it is not possible to excute the local search: useVertexOperation and useSubtreeOperation."); } this.useVertexOperation = useVertexOperation; this.useSubtreeOperation = useSubtreeOperation; this.useTabuSearch = useTabuSearch; this.tabuTime = tabuTime; this.upperLimitTabuExchanges = upperLimitTabuExchanges; this.isAlgorithmExecuted = false; } /** * Constructs a new instance of this algorithm with the proposed initial solution. * * @param initialSolution the initial solution * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm * @param bestImprovement contains whether the best (if true) or the first improvement (if * false) is returned in the neighborhood search * @param useVertexOperation contains whether the local search uses the vertex operation * @param useSubtreeOperation contains whether the local search uses the subtree operation * @param useTabuSearch contains whether a tabu search is used * @param tabuTime the tabu time that is the number of iterations an element is in the tabu list * @param upperLimitTabuExchanges the upper limit of non-improving exchanges, this is the * stopping criterion in the tabu search */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( CapacitatedSpanningTree initialSolution, Graph graph, V root, double capacity, Map demands, int lengthBound, boolean bestImprovement, boolean useVertexOperation, boolean useSubtreeOperation, boolean useTabuSearch, int tabuTime, int upperLimitTabuExchanges) { this( graph, root, capacity, demands, lengthBound, bestImprovement, 0, useVertexOperation, useSubtreeOperation, useTabuSearch, tabuTime, upperLimitTabuExchanges); if (!initialSolution.isCapacitatedSpanningTree(graph, root, capacity, demands)) { throw new IllegalArgumentException( "The initial solution is not a valid capacitated spanning tree."); } this.initialSolution = initialSolution; } @Override public CapacitatedSpanningTree getCapacitatedSpanningTree() { if (isAlgorithmExecuted) { return bestSolution.calculateResultingSpanningTree(); } // calculates initial solution on which we base the local search bestSolution = getInitialSolution(); // map that contains all spanning trees of the current partition Map> partitionSpanningTrees = new HashMap<>(); // map that contains the subtrees of all vertices Map, Double>> subtrees = new HashMap<>(); // set that contains all part of the partition that were affected by an exchange operation Pair, Set> affected = Pair.of(bestSolution.getLabels(), new HashSet<>()); // the improvement graph ImprovementGraph improvementGraph = new ImprovementGraph(bestSolution); // tabu list Set tabuList = new HashSet<>(); // tabu time list Map> tabuTimeList = new HashMap<>(); // tabu timer int tabuTimer = 0; // number of tabu echanges int numberOfTabuExchanges = 0; // the solution int he current iteration CapacitatedSpanningTreeSolutionRepresentation currentSolution = bestSolution; // the difference from the current solution and the best solution double costDifference = 0; double currentCost; // do local improvement steps while (true) { partitionSpanningTrees = calculateSpanningTrees( currentSolution, partitionSpanningTrees, affected.getFirst()); if (useSubtreeOperation) { subtrees = calculateSubtreesOfVertices( currentSolution, subtrees, partitionSpanningTrees, affected.getFirst()); } improvementGraph.updateImprovementGraph( currentSolution, subtrees, partitionSpanningTrees, affected.getFirst(), tabuList); AhujaOrlinSharmaCyclicExchangeLocalAugmentation< Pair, DefaultWeightedEdge> ahujaOrlinSharmaCyclicExchangeLocalAugmentation = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>( improvementGraph.improvementGraph, lengthBound, improvementGraph.cycleAugmentationLabels, bestImprovement); GraphWalk, DefaultWeightedEdge> cycle = ahujaOrlinSharmaCyclicExchangeLocalAugmentation.getLocalAugmentationCycle(); currentCost = cycle.getWeight(); costDifference += currentCost; if (useTabuSearch) { // do tabu search step if (currentCost < 0) { affected = executeNeighborhoodOperation( currentSolution, improvementGraph.improvementGraphVertexMapping, improvementGraph.pathExchangeVertexMapping, subtrees, cycle); if (costDifference < 0) { bestSolution = currentSolution; costDifference = 0; } } else { if (upperLimitTabuExchanges <= numberOfTabuExchanges) { break; } // clone solution such that a non-improving exchange does not override a good // solution if (currentSolution == bestSolution) { currentSolution = currentSolution.clone(); } affected = executeNeighborhoodOperation( currentSolution, improvementGraph.improvementGraphVertexMapping, improvementGraph.pathExchangeVertexMapping, subtrees, cycle); // update tabu list tabuList.addAll(affected.getSecond()); tabuTimeList.put(tabuTimer, affected.getSecond()); numberOfTabuExchanges++; } // update tabu list Set set = tabuTimeList.remove(tabuTimer - tabuTime - 1); if (set != null) { tabuList.removeAll(set); } tabuTimer++; } else { // do normal local search step if (currentCost < 0) { affected = executeNeighborhoodOperation( currentSolution, improvementGraph.improvementGraphVertexMapping, improvementGraph.pathExchangeVertexMapping, subtrees, cycle); } else { break; } } } this.isAlgorithmExecuted = true; return bestSolution.calculateResultingSpanningTree(); } /** * Calculates an initial solution depending on whether an initial solution was transferred while * construction of the algorithm. If no initial solution was proposed, the algorithm of * Esau-Williams is used. * * @return an initial solution */ private CapacitatedSpanningTreeSolutionRepresentation getInitialSolution() { if (initialSolution != null) { return new CapacitatedSpanningTreeSolutionRepresentation( initialSolution.getLabels(), initialSolution.getPartition()); } return new EsauWilliamsCapacitatedMinimumSpanningTree<>( graph, root, capacity, demands, numberOfOperationsParameter).getSolution(); } /** * Executes the move operations induced by the calculated cycle in the improvement graph. It * returns the set of labels of the subsets that were affected by the move operations. * * @param improvementGraphVertexMapping the mapping from the index of the improvement graph * vertex to the correspondent vertex in the base graph * @param pathExchangeVertexMapping the mapping from the improvement graph pseudo vertices to * their subset that they represent * @param subtrees the map containing the subtree for every vertex * @param cycle the calculated cycle in the improvement graph * @return the set of affected labels of subsets that were affected by the move operations */ private Pair, Set> executeNeighborhoodOperation( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map improvementGraphVertexMapping, Map, Integer> pathExchangeVertexMapping, Map, Double>> subtrees, GraphWalk, DefaultWeightedEdge> cycle) { Set affectedVertices = new HashSet<>(); Set affectedLabels = new HashSet<>(); Iterator> it = cycle.getVertexList().iterator(); if (it.hasNext()) { Pair cur = it.next(); Integer firstLabel; switch (cur.getSecond()) { case SINGLE: firstLabel = currentSolution.getLabel(improvementGraphVertexMapping.get(cur.getFirst())); break; case SUBTREE: firstLabel = currentSolution.getLabel(improvementGraphVertexMapping.get(cur.getFirst())); break; default: firstLabel = -1; } while (it.hasNext()) { Pair next = it.next(); switch (cur.getSecond()) { /* * A vertex is moved form the part of cur to the part of next. Therefore, both parts * are affected. We only consider the label of cur to be affected for now, the label * of next will be add to the affected set in the next iteration. */ case SINGLE: { V curVertex = improvementGraphVertexMapping.get(cur.getFirst()); Integer curLabel = currentSolution.getLabel(curVertex); Integer nextLabel; if (it.hasNext()) { switch (next.getSecond()) { case SINGLE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case SUBTREE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case PSEUDO: nextLabel = pathExchangeVertexMapping.get(next); break; default: throw new IllegalStateException( "This is a bug. There are invalid types of vertices in the cycle."); } } else { nextLabel = firstLabel; } affectedVertices.add(curVertex); affectedLabels.add(curLabel); currentSolution.moveVertex(curVertex, curLabel, nextLabel); break; } /* * A subtree is moved from the part of cur to the part of next. Therefore, the part * of cur is affected. */ case SUBTREE: { V curVertex = improvementGraphVertexMapping.get(cur.getFirst()); Integer curLabel = currentSolution.getLabel(curVertex); Integer nextLabel; if (it.hasNext()) { switch (next.getSecond()) { case SINGLE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case SUBTREE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case PSEUDO: nextLabel = pathExchangeVertexMapping.get(next); break; default: throw new IllegalStateException( "This is a bug. There are invalid types of vertices in the cycle."); } } else { nextLabel = firstLabel; } affectedVertices.add(curVertex); affectedLabels.add(curLabel); // get the whole subtree that has to be moved Set subtreeToMove = subtrees.get(curVertex).getFirst(); currentSolution.moveVertices(subtreeToMove, curLabel, nextLabel); break; } /* * cur is the end of a path exchange. Thus, the part of cur is affected because * vertices were inserted. */ case PSEUDO: { Integer curLabel = pathExchangeVertexMapping.get(cur); affectedLabels.add(curLabel); break; } /* * This is the beginning of a path exchange. We have nothing to do. */ case ORIGIN: { break; } default: throw new IllegalStateException( "This is a bug. There are invalid types of vertices in the cycle."); } cur = next; } } /* * The subsets in the partition may include more than one subtree rooted at root. We create * a subset for all subtrees rooted at root. */ Set moreAffectedLabels = new HashSet<>(); Iterator affectedLabelIterator = affectedLabels.iterator(); while (affectedLabelIterator.hasNext()) { int label = affectedLabelIterator.next(); Set vertexSubset = currentSolution.getPartitionSet(label); if (vertexSubset.isEmpty()) { affectedLabelIterator.remove(); } else { moreAffectedLabels .addAll(currentSolution.partitionSubtreesOfSubset(vertexSubset, label)); } } affectedLabels.addAll(moreAffectedLabels); // clean up the partition such that only current subsets are represented currentSolution.cleanUp(); return Pair.of(affectedLabels, affectedVertices); } /** * Updates the map containing the MSTs for every subset of the partition. * * @param partitionSpanningTrees the map containing the MST for every subset of the partition * @param affectedLabels the labels of the subsets of the partition that were changed due to the * multi-exchange * @return the updated map containing the MST for every subset of the partition */ private Map> calculateSpanningTrees( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map> partitionSpanningTrees, Set affectedLabels) { for (Integer label : affectedLabels) { Set set = currentSolution.getPartitionSet(label); currentSolution.getPartitionSet(label).add(root); partitionSpanningTrees.put( label, new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, set)).getSpanningTree()); currentSolution.getPartitionSet(label).remove(root); } return partitionSpanningTrees; } /** * Updates the map containing the subtrees of all vertices in the graph with respect to the MST * in the partition and returns them in map. * * @param subtrees the subtree map to update * @param partitionSpanningTree the map containing the MST for every subset of the partition * @param affectedLabels the labels of the subsets of the partition that were changed due to the * multi-exchange * @return the updated map of vertices to their subtrees */ private Map, Double>> calculateSubtreesOfVertices( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Map> partitionSpanningTree, Set affectedLabels) { for (Integer label : affectedLabels) { Set modifiableSet = new HashSet<>(currentSolution.getPartitionSet(label)); modifiableSet.add(root); for (V v : currentSolution.getPartitionSet(label)) { Pair, Double> currentSubtree = subtree(currentSolution, modifiableSet, v, partitionSpanningTree); subtrees.put(v, currentSubtree); } } return subtrees; } /** * Calculates the subtree of v with respect to the MST given in * partitionSpanningTree. * * @param v the vertex to calculate the subtree for * @param partitionSpanningTree the map from labels to spanning trees of the partition. * @return the subtree of v with respect to the MST given in * partitionSpanningTree. */ private Pair, Double> subtree( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Set modifiableSet, V v, Map> partitionSpanningTree) { /* * initializes graph that is the MST of the current subset rooted */ SpanningTreeAlgorithm.SpanningTree partSpanningTree = partitionSpanningTree.get(currentSolution.getLabel(v)); Graph spanningTree = new AsSubgraph<>(graph, modifiableSet, partSpanningTree.getEdges()); /* * calculate subtree rooted at v */ Set subtree = new HashSet<>(); double subtreeWeight = 0; Iterator depthFirstIterator = new DepthFirstIterator<>(spanningTree, v); Set currentPath = new HashSet<>(); double currentWeight = 0; boolean storeCurrentPath = true; while (depthFirstIterator.hasNext()) { V next = depthFirstIterator.next(); if (spanningTree.containsEdge(next, v)) { storeCurrentPath = true; subtree.addAll(currentPath); subtreeWeight += currentWeight; currentPath = new HashSet<>(); currentWeight = 0; } /* * This part of the subtree is connected to the root, thus, this particular tree is not * part of the subtree of the current vertex v. */ if (next.equals(root)) { storeCurrentPath = false; currentPath = new HashSet<>(); currentWeight = 0; } if (storeCurrentPath) { currentPath.add(next); currentWeight += demands.get(next); } } return Pair.of(subtree, subtreeWeight); } /** * This enums contains the vertex types of the improvement graph. */ private enum ImprovementGraphVertexType { SINGLE, SUBTREE, PSEUDO, ORIGIN } /** * This class realises the improvement graph for the composite multi-exchange large neighborhood * search. The improvement graph encodes two exchange classes: - cyclic exchange (on vertices * and subtrees) - path exchange (on vertices and subtrees) *

* DEFINITION EXCHANGES Let T[i] be the subtree rooted at i of the MST implicitly defined by the * vertex partition. Cyclic Exchange: A cyclic exchange is defined on vertices i_1, ..., i_r, * i_1, where the vertices represent either itself in the base graph or the subtrees rooted at * i_k for k = 1, ..., r, where T[i_a] != T[i_b] for a != b. The cyclic exchange on i_1, ..., * i_r, i_1 moves the i_a (or T[i_a]) to the subset of i_b, where b = a+1 mod r+1. Such a cyclic * exchange is feasible if the capacity constraint is not violated. We can represent the cost of * the cyclic exchange by the following formulas: Let S[i_k] be the subset of i_k in the * implicitly defined partition. - exchange of vertices: $$ c(T_new) - c(T) = \sum_{a = 1}^{r} * c(\{i_{a - 1}\} \cup S[i_{i_a}] \setminus \{i_a\}] $$ - exchange of rooted subtrees: $$ * c(T_new) - c(T) = \sum_{a = 1}^{r} c(T[i_{a - 1}] \cup S[i_{i_a}] \setminus T[i_a]] $$ where * c is the given edge cost function and T_new is the CMST resulting by executing the cyclic * exchange. Thus, an exchange is profitable if c(T_new) - c(T) < 0. *

* Path Exchange: A path exchange follows the same idea as the cyclic exchange but it does not * end at the same vertex. That is, the path exchange is defined on i_1, ..., i_r. The cost * function has to be adapted at the start and end point of the path. *

* DEFINITION NEIGHBORHOOD Furthermore, we have to define the neighborhood. These are all * capacitated spanning trees that are reachable by using such an exchange as given above. *

* DEFINITION IMPROVEMENT GRAPH The improvement graph is based on a feasible capacitated * spanning tree and uses a one-to-one correspondence between the vertices in the base graph and * the vertices in the improvement graph. We want to define the arc set of the improvement graph * such that each subset disjoint directed cycle (see construction) correspond to a cyclic * exchange (or a path exchange, we come to that later). Furthermore, the cost of the cycle in * the improvement graph and the cost of the corresponding cyclic exchange has to be equal. *

* CONSTRUCTION OF THE IMPROVEMENT GRAPH The improvement graph IG = (V, A) has the vertex set V, * which is equal to the vertex set of the base graph. The arc set A is defined in the * following: A directed arc (i, j) in IG represents that we move the node i (or the subtree * T[i]) to the subset in which vertex j is. That is, vertex i and j are removed from their * subset and i (or the subtree T[i]) is moved to the subset of j. This arc only exists if the * exchange is feasible. Then, the cost can be defined as $$ c(\{T[i]\} \cup S[j] \setminus * \{T[j]\}) - c(S[j]). $$ A directed cycle i_1, ..., i_r, i_1 in this graph subset disjoint if * the subsets of the nodes are pairwise disjoint. By this definition, there is a one-to-one * cost-preserving correspondence between the cyclic exchanges and the subset disjoint directed * cycles in the improvement graph IG. *

* Identifying path exchanges: For the conversion of path exchanges into subset disjoint cycles, * we have to introduce two more node types in the improvement graph: pseudo nodes and a origin * node. On the one hand, pseudo nodes represent a subset of the implicitly defined partition * and mark the end of the end of a path exchange. On the other hand, the origin node marks a * beginning of a path exchange. Therefore, the pseudo node are connected to the origin node to * induce subset disjoint cycles. The costs of the arcs from and to the pseudo nodes and the * origin nodes are defined as follows: We denoted the original nodes in the improvement graph * as regular nodes - c(p, o) = 0 for all pseudo nodes p and origin node o - c(o, r) = c(S[j] * \setminus \{T[j]\}) - c(S[j]) for origin node o and for all regular nodes r - c(r, p) = * c(\{T[i]\} \cup S[j]) - c(S[j]) for all regular nodes r and for all pseudo nodes p Again, * those arc exists only if the exchange is feasible. *

* IDENTIFYING SUBSET DISJOINT CYCLES This is done via a heuristic which can be found here * {@link AhujaOrlinSharmaCyclicExchangeLocalAugmentation} @see * AhujaOrlinSharmaCyclicExchangeLocalAugmentation. */ private class ImprovementGraph { /** * the improvement graph itself */ Graph, DefaultWeightedEdge> improvementGraph; /** * the current solution corresponding to the improvement graph */ CapacitatedSpanningTreeSolutionRepresentation capacitatedSpanningTreeSolutionRepresentation; /** * mapping form all improvement graph vertices to their labels corresponding to the base * graph for the CMST problem */ Map, Integer> cycleAugmentationLabels; /** * mapping from the vertex index in the improvement graph to the vertex in the base graph */ Map improvementGraphVertexMapping; /** * mapping from the base graph vertex to the vertex index in the improvement graph */ Map initialVertexMapping; /** * mapping from the label of the subsets to the corresponding vertex mapping */ Map> pseudoVertexMapping; /** * mapping from the pseudo vertices to the label of the subset they are representing */ Map, Integer> pathExchangeVertexMapping; /** * the origin vertex */ Pair origin; /** * dummy label of the origin vertex */ final Integer originVertexLabel = -1; /** * Constructs an new improvement graph object for this CMST algorithm instance. */ public ImprovementGraph( CapacitatedSpanningTreeSolutionRepresentation capacitatedSpanningTreeSolutionRepresentation) { this.capacitatedSpanningTreeSolutionRepresentation = capacitatedSpanningTreeSolutionRepresentation; this.improvementGraphVertexMapping = new HashMap<>(); this.initialVertexMapping = new HashMap<>(); this.pseudoVertexMapping = new HashMap<>(); this.pathExchangeVertexMapping = new HashMap<>(); /* * We initialize this map such that it can be used in the subset-disjoint cycle * detection algorithm. This map redirects the getters to the corresponding maps in this * improvement graph such that it realises the correct functionality. */ this.cycleAugmentationLabels = getImprovementGraphLabelMap(); this.improvementGraph = createImprovementGraph(); } /** * Initializes the improvement graph, i.e. adds single, subtree and pseudo vertices as well * as the origin vertex. Furthermore, it initializes all mappings. * * @return the improvement graph itself. */ public Graph, DefaultWeightedEdge> createImprovementGraph() { Graph, DefaultWeightedEdge> improvementGraph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); int counter = 0; for (V v : graph.vertexSet()) { if (v.equals(root)) { continue; } if (useVertexOperation) { Pair singleVertex = new Pair<>(counter, ImprovementGraphVertexType.SINGLE); improvementGraph.addVertex(singleVertex); } if (useSubtreeOperation) { Pair subtreeVertex = new Pair<>(counter, ImprovementGraphVertexType.SUBTREE); improvementGraph.addVertex(subtreeVertex); } // we have to add these only once improvementGraphVertexMapping.put(counter, v); initialVertexMapping.put(v, counter); counter++; } Pair origin = new Pair<>(counter, ImprovementGraphVertexType.ORIGIN); improvementGraph.addVertex(origin); this.origin = origin; pathExchangeVertexMapping.put(origin, originVertexLabel); for (Integer label : capacitatedSpanningTreeSolutionRepresentation.getLabels()) { Pair pseudoVertex = new Pair<>(origin.getFirst() + label + 1, ImprovementGraphVertexType.PSEUDO); pseudoVertexMapping.put(label, pseudoVertex); pathExchangeVertexMapping.put(pseudoVertex, label); improvementGraph.addVertex(pseudoVertex); } /* * connection of pseudo nodes and origin node */ for (Pair v : pseudoVertexMapping.values()) { improvementGraph.setEdgeWeight(improvementGraph.addEdge(v, origin), 0); } return improvementGraph; } /** * Updates the improvement graph. It updates the vertices and edges in the parts specified * in labelsToUpdate. * * @param currentSolution the current solution * @param subtrees the mapping from vertices to their subtree * @param partitionSpanningTrees the mapping from labels of subsets to their spanning tree * @param labelsToUpdate the labels of all subsets that has to be updated (because of the * multi-exchange operation) */ public void updateImprovementGraph( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Map> partitionSpanningTrees, Set labelsToUpdate, Set tabuList) { this.capacitatedSpanningTreeSolutionRepresentation = currentSolution; this.cycleAugmentationLabels = getImprovementGraphLabelMap(); updatePseudoNodesOfNewLabels(currentSolution); for (V v1 : graph.vertexSet()) { if (v1.equals(root)) { continue; } Pair vertexOfV1Single = Pair.of(initialVertexMapping.get(v1), ImprovementGraphVertexType.SINGLE); Pair vertexOfV1Subtree = Pair.of(initialVertexMapping.get(v1), ImprovementGraphVertexType.SUBTREE); if (updateTabuVertices(tabuList, v1, vertexOfV1Single, vertexOfV1Subtree)) { continue; } updateOriginNodeConnections( currentSolution, subtrees, partitionSpanningTrees, labelsToUpdate, v1, vertexOfV1Single, vertexOfV1Subtree); /* * update the connections to regular nodes and pseudo nodes */ for (Integer label : currentSolution.getLabels()) { /* * only update if there is a change induced by a changed part. This potentially * saves a lot of time. */ if (label.equals(currentSolution.getLabel(v1)) || (!labelsToUpdate.contains(currentSolution.getLabel(v1)) && !labelsToUpdate.contains(label))) { continue; } Pair pseudoVertex = pseudoVertexMapping.get(label); Set modifiableSet = new HashSet<>(currentSolution.getPartitionSet(label)); // add root to the set for MST calculations modifiableSet.add(root); double oldWeight = partitionSpanningTrees.get(label).getWeight(); updateSingleNode( currentSolution, subtrees, tabuList, label, oldWeight, modifiableSet, pseudoVertex, v1, vertexOfV1Single); updateSubtreeNode( currentSolution, subtrees, tabuList, label, oldWeight, modifiableSet, pseudoVertex, v1, vertexOfV1Subtree); } } } /** * Updates the pseudo nodes corresponding to new subsets in the partition. That is, new * pseudo nodes for new labels in the label set are added and pseudo nodes of labels that * are no more in the label set are removed. * * @param currentSolution the current solution in the iteration */ private void updatePseudoNodesOfNewLabels( CapacitatedSpanningTreeSolutionRepresentation currentSolution) { if (!currentSolution.getLabels().equals(pseudoVertexMapping.keySet())) { for (Integer label : currentSolution.getLabels()) { if (!pseudoVertexMapping.keySet().contains(label)) { Pair pseudoVertex = new Pair<>( origin.getFirst() + label + 1, ImprovementGraphVertexType.PSEUDO); pseudoVertexMapping.put(label, pseudoVertex); pathExchangeVertexMapping.put(pseudoVertex, label); improvementGraph.addVertex(pseudoVertex); DefaultWeightedEdge newEdge = improvementGraph.addEdge(pseudoVertex, origin); improvementGraph.setEdgeWeight(newEdge, 0); } } if (currentSolution.getLabels().size() != pseudoVertexMapping.keySet().size()) { Iterator labelIterator = pseudoVertexMapping.keySet().iterator(); while (labelIterator.hasNext()) { int label = labelIterator.next(); if (!currentSolution.getLabels().contains(label)) { Pair pseudoVertex = new Pair<>( origin.getFirst() + label + 1, ImprovementGraphVertexType.PSEUDO); labelIterator.remove(); pathExchangeVertexMapping.remove(pseudoVertex); improvementGraph.removeVertex(pseudoVertex); } } } } } /** * Updates all nodes that correspond to v1 and returns if the vertex * v1. That is, all incident edges of v1 are removed if * v1 is in the tabu list. * * @param tabuList the tabu list of the current iteration * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Single the node in the improvement graph representing the exchange of * the vertex v1 * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of * the subtree rooted at v1 * @return true iff v1 is in the tabu list */ private boolean updateTabuVertices( Set tabuList, V v1, Pair vertexOfV1Single, Pair vertexOfV1Subtree) { if (tabuList.contains(v1)) { // remove all edges from the vertex if (useVertexOperation) { improvementGraph.removeVertex(vertexOfV1Single); improvementGraph.addVertex(vertexOfV1Single); } if (useSubtreeOperation) { improvementGraph.removeVertex(vertexOfV1Subtree); improvementGraph.addVertex(vertexOfV1Subtree); } return true; } return false; } /** * Updates the edges to the origin vertex. * * @param currentSolution the current solution in the iteration * @param subtrees the mapping from vertices to their subtree * @param partitionSpanningTrees the mapping from labels of subsets to their spanning tree * @param labelsToUpdate the labels of all subsets that has to be updated (because of the * multi-exchange operation) * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Single the node in the improvement graph representing the exchange of * the vertex v1 * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of * the subtree rooted at v1 */ private void updateOriginNodeConnections( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Map> partitionSpanningTrees, Set labelsToUpdate, V v1, Pair vertexOfV1Single, Pair vertexOfV1Subtree) { double newWeight, oldWeight; SpanningTreeAlgorithm.SpanningTree spanningTree; /* * update connections to origin node */ if (labelsToUpdate.contains(currentSolution.getLabel(v1))) { oldWeight = partitionSpanningTrees.get(currentSolution.getLabel(v1)).getWeight(); /* * edge for v1 vertex remove operation */ Set partitionSetOfV1 = currentSolution.getPartitionSet(currentSolution.getLabel(v1)); partitionSetOfV1.add(root); if (useVertexOperation) { partitionSetOfV1.remove(v1); spanningTree = new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, partitionSetOfV1)) .getSpanningTree(); if (spanningTree.getEdges().size() == partitionSetOfV1.size() - 1) { newWeight = spanningTree.getWeight(); } else { newWeight = Double.NaN; } updateImprovementGraphEdge(origin, vertexOfV1Single, 0, newWeight - oldWeight); partitionSetOfV1.add(v1); } /* * edge for v1 subtree remove operation If the subtree of v1 contains only the * vertex itself, it is the same operation as removing v1 as vertex. Thus, do not * add edges. */ if (useSubtreeOperation) { if (subtrees.get(v1).getFirst().size() > 1 || !useVertexOperation) { partitionSetOfV1.removeAll(subtrees.get(v1).getFirst()); spanningTree = new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, partitionSetOfV1)) .getSpanningTree(); if (spanningTree.getEdges().size() == partitionSetOfV1.size() - 1) { newWeight = spanningTree.getWeight(); } else { newWeight = Double.NaN; } updateImprovementGraphEdge( origin, vertexOfV1Subtree, 0, newWeight - oldWeight); partitionSetOfV1.addAll(subtrees.get(v1).getFirst()); } else { improvementGraph.removeVertex(vertexOfV1Subtree); improvementGraph.addVertex(vertexOfV1Subtree); } } partitionSetOfV1.remove(root); } } /** * Updates all edges from vertexOfV1Single to nodes in the subset represented * by label. * * @param currentSolution the current solution in the iteration * @param subtrees the mapping from vertices to their subtree * @param tabuList the tabu list of the current iteration * @param label the current label to update the edges for * @param oldWeight the old weight of the subset * @param modifiableSet a modifiable version of the subset of nodes represented by label * inclusive the root node * @param pseudoVertex the pseudo vertex representing the subset represented by label * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Single the node in the improvement graph representing the exchange of * the vertex v1 */ private void updateSingleNode( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Set tabuList, int label, double oldWeight, Set modifiableSet, Pair pseudoVertex, V v1, Pair vertexOfV1Single) { double newCapacity, newWeight; SpanningTreeAlgorithm.SpanningTree spanningTree; // add v1 to the set for MST calculations modifiableSet.add(v1); /* * Adding of edges for v1 vertex replacing an object in v2. We need to considers this * only if vertex operations should be used. */ if (useVertexOperation) { for (V v2 : currentSolution.getPartitionSet(label)) { if (v2.equals(root)) { throw new IllegalStateException( "The root is in the partition. This is a bug."); } if (tabuList.contains(v2)) { continue; } /* * edge for v1 vertex replacing v2 vertex */ modifiableSet.remove(v2); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + demands.get(v1) - demands.get(v2)); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Single, Pair.of(initialVertexMapping.get(v2), ImprovementGraphVertexType.SINGLE), newCapacity, newWeight - oldWeight); modifiableSet.add(v2); // end edge for v1 vertex replacing v2 vertex /* * edge for v1 vertex replacing v2 subtree If the subtree of v2 contains only * the vertex itself and both operations are used, it is the same operation as * moving v2 as vertex. Thus, do not add edges. */ if (useSubtreeOperation) { if (subtrees.get(v2).getFirst().size() > 1) { modifiableSet.removeAll(subtrees.get(v2).getFirst()); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())) .getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + demands.get(v1) - subtrees.get(v2).getSecond()); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge(vertexOfV1Single, Pair.of( initialVertexMapping.get(v2), ImprovementGraphVertexType.SUBTREE), newCapacity, newWeight - oldWeight); modifiableSet.addAll(subtrees.get(v2).getFirst()); } } // end edge for v1 vertex replacing v2 subtree } /* * edge for v1 vertex replacing no object */ spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + demands.get(v1)); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Single, pseudoVertex, newCapacity, newWeight - oldWeight); // end edge for v1 vertex replacing no object // remove v1 from the set modifiableSet.remove(v1); } } /** * Updates all edges from vertexOfV1Single to nodes in the subset represented * by label. This method does adds the subtree of v1 to * modifiableSet. * * @param currentSolution the current solution in the iteration * @param subtrees the mapping from vertices to their subtree * @param tabuList the tabu list of the current iteration * @param label the current label to update the edges for * @param oldWeight the old weight of the subset * @param modifiableSet a modifiable version of the subset of nodes represented by label * @param pseudoVertex the pseudo vertex representing the subset represented by label * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of * the subtree rooted at v1 */ private void updateSubtreeNode( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Set tabuList, int label, double oldWeight, Set modifiableSet, Pair pseudoVertex, V v1, Pair vertexOfV1Subtree) { double newCapacity, newWeight; SpanningTreeAlgorithm.SpanningTree spanningTree; /* * Adding of edges for v1 subtree replacing an object in v2. We need to considers this * only if subtree operations should be used. * * If the subtree of v1 contains only the vertex itself and both operations are used, it * is the same operation as moving v1 as vertex. Thus, do not add edges. */ if (useSubtreeOperation && (subtrees.get(v1).getFirst().size() > 1 || !useVertexOperation)) { // add the subtree of v1 to the set for MST calculations modifiableSet.addAll(subtrees.get(v1).getFirst()); for (V v2 : currentSolution.getPartitionSet(label)) { if (v2.equals(root)) { throw new IllegalStateException( "The root is in the partition. This is a bug."); } if (tabuList.contains(v2)) { continue; } /* * edge for v1 subtree replacing v2 vertex */ if (useVertexOperation) { modifiableSet.remove(v2); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())) .getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + subtrees.get(v1).getSecond() - demands.get(v2)); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge(vertexOfV1Subtree, Pair.of( initialVertexMapping.get(v2), ImprovementGraphVertexType.SINGLE), newCapacity, newWeight - oldWeight); modifiableSet.add(v2); } // end edge for v1 subtree replacing v2 vertex /* * edge for v1 subtree replacing v2 subtree */ modifiableSet.removeAll(subtrees.get(v2).getFirst()); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(currentSolution.getLabel(v2)) + subtrees.get(v1).getSecond() - subtrees.get(v2).getSecond()); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Subtree, Pair.of(initialVertexMapping.get(v2), ImprovementGraphVertexType.SUBTREE), newCapacity, newWeight - oldWeight); modifiableSet.addAll(subtrees.get(v2).getFirst()); // end edge for v1 subtree replacing v2 subtree } /* * edge for v1 subtree replacing no object */ spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + subtrees.get(v1).getSecond()); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Subtree, pseudoVertex, newCapacity, newWeight - oldWeight); // end edge for v1 subtree replacing no object } } /** * Adds an edge between v1 and v2 to the improvement graph if * newCapacity does not exceed the capacity constraint. The weight of the edge * is newCost. * * @param v1 start vertex (the vertex or subtree induced by v1 that will be * moved to the subset of v2) * @param v2 end vertex (the vertex or subtree induced by v2 that will be * removed from the subset of v2) * @param newCapacity the used capacity by adding the vertex or subtree induced by * v1 to the subset of v2 and deleting the vertex or * subtree induced by v2 * @param newCost the cost of the edge (the cost induced by the operation induced by * v1 and v2) */ public void updateImprovementGraphEdge( Pair v1, Pair v2, double newCapacity, double newCost) { if (!Double.isNaN(newCapacity) && newCapacity <= capacity && !Double.isNaN(newCost)) { DefaultWeightedEdge edge; edge = improvementGraph.getEdge(v1, v2); if (edge == null) { edge = improvementGraph.addEdge(v1, v2); } improvementGraph.setEdgeWeight(edge, newCost); } else { improvementGraph.removeEdge(v1, v2); } } /** * Calculates the maximum demand over all new subtrees induced by the minimum spanning tree * spanningTree. A spanning tree induces more than one subset in the partition * if the root vertex of the base graph connects more than one subtree of the spanning tree. * * @param vertexSubset the vertex subset spanning Tree is defined on * @param spanningTree the spanning tree * @param totalDemand the total demand of the whole spanning tree * @return the maximum demand over all new subtrees induced by the minimum spanning tree * spanningTree */ public double calculateMaximumDemandOfSubtrees( Set vertexSubset, SpanningTreeAlgorithm.SpanningTree spanningTree, double totalDemand) { Graph spanningTreeGraph = new AsSubgraph<>(graph, vertexSubset, spanningTree.getEdges()); /* * The subtree does not evolve to more than 1 partition subsets, thus, we can return the * total demand. */ int degreeOfRoot = spanningTreeGraph.degreeOf(root); if (degreeOfRoot == 1) { return totalDemand; } double maximumDemand = 0; DepthFirstIterator depthFirstIterator = new DepthFirstIterator<>(spanningTreeGraph, root); if (depthFirstIterator.hasNext()) { depthFirstIterator.next(); } int numberOfRootEdgesExplored = 0; double exploredVerticesDemand = 0; double currentDemand = 0; while (depthFirstIterator.hasNext()) { V next = depthFirstIterator.next(); // exploring new subtree if (spanningTreeGraph.containsEdge(root, next)) { exploredVerticesDemand += currentDemand; if (maximumDemand < currentDemand) { maximumDemand = currentDemand; } // we can stop the exploration if (maximumDemand >= 0.5 * totalDemand || exploredVerticesDemand + maximumDemand >= totalDemand) { return maximumDemand; } // we can stop the exploration, all subtrees but one are explored if (numberOfRootEdgesExplored + 1 == degreeOfRoot) { return Math.max(maximumDemand, totalDemand - exploredVerticesDemand); } numberOfRootEdgesExplored++; currentDemand = 0; } currentDemand += demands.get(next); } return maximumDemand; } /** * Returns the mapping that is used in the valid cycle detection algorithm, i.e. the vertex * label map. * * @return the vertex label map used in the valid cycle detection algorithm */ private Map, Integer> getImprovementGraphLabelMap() { return new AbstractMap, Integer>() { @Override public int size() { return improvementGraphVertexMapping.size() + pathExchangeVertexMapping.size() + (origin == null ? 0 : 1); } @Override public boolean isEmpty() { return improvementGraphVertexMapping.isEmpty() && pathExchangeVertexMapping.isEmpty() && origin == null; } @Override public boolean containsKey(Object key) { if (key instanceof Pair) { return improvementGraphVertexMapping.containsKey(((Pair) key).getFirst()) || pathExchangeVertexMapping.containsKey(key) || key.equals(origin); } return false; } @Override public boolean containsValue(Object value) { return improvementGraphVertexMapping.containsValue(value) || pathExchangeVertexMapping.containsValue(value) || value.equals(originVertexLabel); } @Override public Integer get(Object key) { if (key instanceof Pair) { if (improvementGraphVertexMapping.containsKey(((Pair) key).getFirst())) { return capacitatedSpanningTreeSolutionRepresentation.getLabel( improvementGraphVertexMapping.get(((Pair) key).getFirst())); } if (key.equals(origin)) { return originVertexLabel; } } return pathExchangeVertexMapping.get(key); } @Override public Integer put(Pair key, Integer value) { throw new IllegalStateException(); } @Override public Integer remove(Object key) { throw new IllegalStateException(); } @Override public void putAll( Map, ? extends Integer> m) { throw new IllegalStateException(); } @Override public void clear() { throw new IllegalStateException(); } @Override public Set> keySet() { Set> keySet = new HashSet<>(); for (Integer i : improvementGraphVertexMapping.keySet()) { if (useVertexOperation) { keySet.add(Pair.of(i, ImprovementGraphVertexType.SINGLE)); } if (useSubtreeOperation) { keySet.add(Pair.of(i, ImprovementGraphVertexType.SUBTREE)); } } keySet.addAll(pathExchangeVertexMapping.keySet()); keySet.add(origin); return keySet; } @Override public Collection values() { return capacitatedSpanningTreeSolutionRepresentation.getLabels(); } @Override public Set, Integer>> entrySet() { Set, Integer>> entrySet = new HashSet<>(); for (Integer i : improvementGraphVertexMapping.keySet()) { Integer label = capacitatedSpanningTreeSolutionRepresentation .getLabel(improvementGraphVertexMapping.get(i)); if (useVertexOperation) { entrySet.add( new AbstractMap.SimpleEntry<>( Pair.of(i, ImprovementGraphVertexType.SINGLE), label)); } if (useSubtreeOperation) { entrySet.add( new AbstractMap.SimpleEntry<>( Pair.of(i, ImprovementGraphVertexType.SUBTREE), label)); } } for (Pair pseudoVertex : pathExchangeVertexMapping .keySet()) { entrySet.add( new AbstractMap.SimpleEntry<>( pseudoVertex, pathExchangeVertexMapping.get(pseudoVertex))); } entrySet.add(new AbstractMap.SimpleEntry<>(origin, originVertexLabel)); return entrySet; } }; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy