/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: SimulatedAnnealing.java
 * Written by Team 2: Jan Barth, Iskandar Abudiab
 *
 * This code has been developed at the Karlsruhe Institute of Technology (KIT), Germany,
 * as part of the course "Multicore Programming in Practice: Tools, Models, and Languages".
 * Contact instructor: Dr. Victor Pankratius (pankratius@ipd.uka.de)
 *
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.placement.simulatedAnnealing1;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.sun.electric.tool.Job;
import com.sun.electric.tool.placement.PlacementFrame;
import com.sun.electric.tool.placement.PlacementAdapter.PlacementExport;
import com.sun.electric.tool.placement.PlacementFrame.PlacementParameter;
import com.sun.electric.tool.placement.simulatedAnnealing1.metrics.AreaOverlapMetric;
import com.sun.electric.tool.placement.simulatedAnnealing1.metrics.MSTMetric;
import com.sun.electric.util.math.MutableInteger;
import com.sun.electric.util.math.Orientation;

/**
 * Parallel Placement
 **/
public class SimulatedAnnealing extends PlacementFrame {

	/**
	 * Number of iteration for each thread to do per temperature change.
	 */
	public final int STEP_THREAD = 20;
	/**
	 * Total number of iterations for the inner loop.
	 */
	public final int INNER_LOOP_TOTAL = 800;

	public PlacementParameter maxThreadsParam = new PlacementParameter("threads", "Number of threads:", 4);
	public PlacementParameter maxRuntimeParam = new PlacementParameter("runtime", "Runtime (seconds):", 240);

	private Temperature temp;
	private Random rand = new Random();

	// Connectivity Map (transitive hull)
	private Map<PlacementNode, Map<PlacementNode, MutableInteger>> connectivityMap;

	/**
	 * @see PlacementFrame#getAlgorithmName()
	 */
	@Override
	public String getAlgorithmName() {
		return "Simulated-Annealing-1";
	}

	/**
	 * @see PlacementFrame#runPlacement(List, List, String);
	 */
	public void runPlacement(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, List<PlacementExport> exportsToPlace,
			String cellName, Job job) {

		this.setParamterValues(this.maxThreadsParam.getIntValue(), this.maxRuntimeParam.getIntValue());

		System.out.println("Simulated annealing started.");

		// Step 1: Random Placement
		// for (int i = 0; i < nodesToPlace.size(); i++ ) {
		// PlacementNode n = nodesToPlace.get(i);
		// n.setPlacement(i * 1.0 % Math.sqrt(nodesToPlace.size()) * 200,
		// Math.round(i * 1.0 / Math.sqrt(nodesToPlace.size()))* 200);
		// }

		createAndFillConnectivityMap(nodesToPlace, allNetworks);

		// Step 2: Create a Temperature
		temp = new Temperature(nodesToPlace);
		System.out.println("Temperature = " + temp.getTemperature());

		// step 3: Iteration
		int numInnerSteps = INNER_LOOP_TOTAL / (numOfThreads * STEP_THREAD); // nodesToPlace.size()
																				// /
																				// 7
																				// +
																				// 100;
		System.out.println("Inner Steps = " + numInnerSteps);
		System.out.println("Running placement, please wait...");
		// step4: Initialize Thread Pool
		ExecutorService threadPool = Executors.newFixedThreadPool(numOfThreads);

		// step5: create workers
		LinkedList<Callable<Double>> workforce = new LinkedList<Callable<Double>>();
		for (int i = 0; i < numOfThreads; i++) {
			PlacementThread worker = new PlacementThread(numInnerSteps, nodesToPlace, allNetworks);
			workforce.add(worker);
		}

		while (temp.nextIteration()) {
			// outer loop

			// test
			/*
			 * double BeforeScore = incState.getScore(); for (PlacementNode n:
			 * nodesToPlace) { double Bscore = incState.getScore();
			 * PlacementNodePosition p = new PlacementNodePosition(nodesToPlace,
			 * nodesToPlace.indexOf(n)); p.setPlacementY(p.getPlacementY() +
			 * 1000); incState.addNode(nodesToPlace.indexOf(n), p); double
			 * Ascore = incState.getScore(); System.out.println("Bscore = " +
			 * Bscore + ", AScore = " + Ascore); }
			 *
			 *
			 *
			 * incState.makeGlobal();
			 *
			 * if (true) return;
			 */

//			System.out.println("Temperature=" + temp.getTemperature());
			for (int middleStep = 0; middleStep < numInnerSteps; middleStep++) {
				// System.out.println("New Temperature: " +
				// temp.getTemperature());

				List<Future<Double>> results = null;

				// reset the incremental state of all threads
				for (int i = 0; i < numOfThreads; i++) {
					((PlacementThread) workforce.get(i)).reset();
				}

				// start the calculation and get the results
				double minScore = Double.MAX_VALUE;
				int bestThread = -1;

				// System.out.println("Before invokeAll");

				try {
					results = threadPool.invokeAll(workforce);
					// System.out.println("After invokeAll");

					minScore = Double.MAX_VALUE;
					bestThread = -1;
					for (int i = 0; i < numOfThreads; i++) {
						// retrieve the score
						Double threadScore = results.get(i).get();

						// System.out.println("Thread score: " + threadScore);

						if (threadScore.doubleValue() < minScore) {
							minScore = threadScore.doubleValue();
							bestThread = i;
						}
					}

					// make the best result global
					((PlacementThread) workforce.get(bestThread)).getIncState().makeGlobal();

//					System.out.println("C1Score = "
//							+ ((PlacementThread) workforce.get(bestThread)).getIncState().getC1());

				} catch (Exception e) {
					System.out.println("An error occured. Aborting. Message:" + e.getMessage());
					e.printStackTrace(System.out);
					return;
				}
				// System.out.println("Current score = " + minScore);
			}
		}

//		IncrementalState finalState = new IncrementalState(nodesToPlace, allNetworks);
//		BoundingBoxMetric bbm = new BoundingBoxMetric(nodesToPlace, allNetworks, finalState);
//		System.out.println("Final bounding box metric: " + bbm.init(allNetworks));
//		System.out.println("Done");

	}

	/**
	 * Calculates the minimal chip area. This method simply calculates the sum
	 * of all <code>PlacementNode</code>s.
	 *
	 * @param nodesToPlace
	 *            a list containing all <code>PlacementNode</code>s.
	 * @return the minimal chip area.
	 */
	private double getMinChipArea(List<PlacementNode> nodesToPlace) {
		double area = 0.0;

		for (PlacementNode node : nodesToPlace) {
			area += node.getHeight() * node.getWidth();
		}
		return area;
	}

	/**
	 * This method fills the connectivity by connecting every node to all nodes
	 * in its network
	 *
	 * @param nodesToPlace
	 * @param allNetworks
	 */
	private void createAndFillConnectivityMap(List<PlacementNode> nodesToPlace,
			List<PlacementNetwork> allNetworks) {
		connectivityMap = new HashMap<PlacementNode, Map<PlacementNode, MutableInteger>>();
		for (PlacementNetwork plNet : allNetworks) {
			// add all combinations of the nodes on this net to the connectivity
			// map
			List<PlacementPort> portsInNetwork = plNet.getPortsOnNet();
			for (int i = 0; i < portsInNetwork.size(); i++) {
				PlacementPort plPort1 = portsInNetwork.get(i);
				PlacementNode plNode1 = plPort1.getPlacementNode();
				for (int j = i + 1; j < portsInNetwork.size(); j++) {
					PlacementPort plPort2 = portsInNetwork.get(j);
					PlacementNode plNode2 = plPort2.getPlacementNode();

					incrementMap(plNode1, plNode2);
					incrementMap(plNode2, plNode1);
				}
			}
		}

	}

	/**
	 * Method to build the connectivity map by adding a connection between two
	 * PlacementNodes. This method is usually called twice with the
	 * PlacementNodes in both orders because the mapping is not symmetric.
	 *
	 * @param plNode1
	 *            the first PlacementNode.
	 * @param plNode2
	 *            the second PlacementNode.
	 */
	private void incrementMap(PlacementNode plNode1, PlacementNode plNode2) {
		Map<PlacementNode, MutableInteger> destMap = connectivityMap.get(plNode1);
		if (destMap == null)
			connectivityMap.put(plNode1, destMap = new HashMap<PlacementNode, MutableInteger>());
		MutableInteger mi = destMap.get(plNode2);
		if (mi == null) destMap.put(plNode2, mi = new MutableInteger(0));
		mi.increment();
	}

	/**
	 * Method to return the number of connections between two PlacementNodes.
	 *
	 * @param plNode1
	 *            the first PlacementNode.
	 * @param plNode2
	 *            the second PlacementNode.
	 * @return the number of connections between the PlacementNodes.
	 */
	private int getConnectivity(PlacementNode plNode1, PlacementNode plNode2) {
		Map<PlacementNode, MutableInteger> destMap = connectivityMap.get(plNode1);
		if (destMap == null) return 0;
		MutableInteger mi = destMap.get(plNode2);
		if (mi == null) return 0;
		return mi.intValue();
	}

	/**
	 * Returns the highest connected node to the given node
	 *
	 * @param node
	 *            the node for which the highest connected to be found
	 * @return the highest connected node to the given node
	 */
	public PlacementNode getHighestConnectedNode(PlacementNode node) {
		PlacementNode highestConnected = null;
		MutableInteger highest = new MutableInteger(0);
		Map<PlacementNode, MutableInteger> conn = connectivityMap.get(node);
		if (conn == null) return null;
		for (PlacementNode n : conn.keySet()) {
			if (conn.get(n).intValue() > highest.intValue()) {
				highest = conn.get(n);
				highestConnected = n;
			}
		}
		return highestConnected;
	}

	/**
	 * An implementation of a worker thread that does the moving and swapping of
	 * the placement nodes in parallel.
	 */
	public class PlacementThread implements Callable<Double> {
		private IncrementalState incState = null;
		// private List<PlacementNode> allNodes;
		// private List<PlacementNetwork> allNetworks;
		private int numSteps = 0;

		/**
		 * Creates this.
		 *
		 * @param numSteps
		 *            the number of iterations which this runnable will do.
		 * @param allNodes
		 *            a list of all <code>PlacementNode</code>s
		 * @param allNetworks
		 *            a list of all <code>PlacementNetwork</code>s.
		 */
		public PlacementThread(int numSteps, List<PlacementNode> allNodes, List<PlacementNetwork> allNetworks) {
			// this.allNodes = allNodes;
			// this.allNetworks = allNetworks;
			this.numSteps = numSteps;
			this.incState = new IncrementalState(allNodes, allNetworks);
		}

		/**
		 * @see Callable#call();
		 */
		public Double call() throws Exception {

			for (int i = 0; i < numSteps; i++) {
				// Swap or Move
				double r = rand.nextDouble();
				if (r < 0.2) {
					// we swap
					if (!incState.chooseAndSwapNodes()) {
						// swap failed, do something else
					}
				} else {
					// we displace (move)
					if (!incState.moveNode()) {
						// move failed, do something else
					}
				}
			}

			return new Double(incState.getScore());
		}

		/**
		 * Returns the <code>IncrementalState</code> object of this runnable
		 *
		 * @return the <code>IncrementalState</code> object of this runnable
		 */
		public IncrementalState getIncState() {
			return incState;
		}

		/**
		 * Resets the state.
		 */
		public void reset() {
			incState.reset();
		}

	}

	/**
	 * A representation of temperature
	 */
	public class Temperature {
		private double temperature;
		private final double initialTemperature = 5000.0;
		private final double threshholdTemperature = 0.1;
		private Random rand = null;
		private double initialChipLength;

		// private List<PlacementNode> nodesToPlace;

		/**
		 * Creates this temeperature object
		 *
		 * @param nodesToPlace
		 *            a list of all <code>PlacementNode</code>s to be placed.
		 */
		public Temperature(List<PlacementNode> nodesToPlace) {
			rand = new Random();
			rand.setSeed(System.currentTimeMillis());
			initialChipLength = Math.sqrt(getMinChipArea(nodesToPlace)) * 2;
			System.out.println("Initial Chip Length = " + initialChipLength);
			// this.nodesToPlace = nodesToPlace;
			temperature = initialTemperature;
		}

		/**
		 * Returns the current maximal swapping distance based on the current
		 * temperature.
		 *
		 * @return the current swapping distance.
		 */
		public double getCurrentSwapDistance() {
			double newSwapDisttance = initialChipLength
					* (Math.log(temperature) / Math.log(initialTemperature));
			return newSwapDisttance;
		}

		/**
		 * Sets the temperature.
		 *
		 * @param newTemperature
		 *            the new temperature.
		 */
		public void setTemperature(double newTemperature) {
			this.temperature = newTemperature;
		}

		/**
		 * Gets the temperature.
		 *
		 * @return the current temperature.
		 */
		public double getTemperature() {
			return temperature;
		}

		/**
		 * Resets the temperature.
		 */
		public void reset() {
			temperature = initialTemperature;
		}

		/**
		 * Decreases the temperature and checks for stop condition.
		 *
		 * @return true if current temperature hasn't reached threshold. false
		 *         otherwise
		 */
		public boolean nextIteration() {
			decTempQuadratically();
			return (temperature > threshholdTemperature);
		}

		/**
		 * Decreases the temperature.
		 */
		private void decTempQuadratically() {
			// x > -1 -> 1+
			double x = 1 / Math.log(1 + temperature) * 2 - 1;
			double xSquared = x * x;
			double alpha = 0.95 - xSquared * (0.95 - 0.80);
			// System.out.println("x = " + x);
			temperature = alpha * temperature;
		}

		/**
		 * Decides whether to accept a new state or not
		 *
		 * @param deltaE
		 *            the difference in score between new and old states.
		 * @return true for accept. false otherwise.
		 */
		public boolean accept(double deltaE) {
			if (deltaE < 0) return true;
			return false;
			// double randomNumber = rand.nextDouble();
			// double threshold = Math.exp(-deltaE / temperature);
			// return (randomNumber < temperature / initialTemperature / 10) ;
		}
	}

	/**
	 * A class for storing node positions and rotations
	 */
	public class PlacementNodePosition {
		private double x, y;
		private int index;
		private Orientation orientation = Orientation.IDENT;

		/**
		 * Creates this.
		 *
		 * @param allNodes
		 *            a list of all <code>PlacementNode</code>s.
		 * @param index
		 *            the index of the desired <code>PlacementNode</code> to
		 *            store the coordinates for.
		 */
		public PlacementNodePosition(List<PlacementNode> allNodes, int index) {
			PlacementNode originalNode = allNodes.get(index);
			this.x = originalNode.getPlacementX();
			this.y = originalNode.getPlacementY();
			this.index = index;
			this.orientation = originalNode.getPlacementOrientation();
		}

		// Getters & Setters
		public double getPlacementX() {
			return x;
		}

		public void setPlacementX(double x) {
			this.x = x;
		}

		public double getPlacementY() {
			return y;
		}

		public void setPlacementY(double y) {
			this.y = y;
		}

		public void setPlacement(double x, double y) {
			this.x = x;
			this.y = y;
		}

		public Orientation getPlacementOrientation() {
			return orientation;
		}

		public void setPlacementOrientation(Orientation o) {
			this.orientation = o;
		}

		public int getIndex() {
			return index;
		}

		public void setIndex(int index) {
			this.index = index;
		}

	}

	/**
	 * A representation of a state of the nodes on the chip. Every worker thread
	 * gets a local <code>IncrementalState</code> object on which it apply the
	 * changes. After {@link STEP_THREAD} iterations all
	 * <code>IncrementalState</code>s of all worker threads are compared and the
	 * state with best score becomes the new state for all threads.
	 *
	 * @see IncrementalState#makeGlobal()
	 */
	public class IncrementalState {
		private HashMap<Integer, PlacementNodePosition> changedNodes; // index
																		// ->
																		// NewNode
		private List<PlacementNode> originalNodes;
		private List<PlacementNetwork> allNetworks;

		private double currentC1, currentC2;
		private MSTMetric C1Metric;
		private AreaOverlapMetric C2Metric;

		/**
		 * Creates this.
		 *
		 * @param allNodes
		 *            a list of all <code>PlacementNode</code>s.
		 * @param allNetworks
		 *            a list of all <code>PlacementNetwork</code>s.
		 */
		public IncrementalState(List<PlacementNode> allNodes, List<PlacementNetwork> allNetworks) {
			changedNodes = new HashMap<Integer, PlacementNodePosition>(STEP_THREAD);
			originalNodes = allNodes;
			this.allNetworks = allNetworks;
			C1Metric = new MSTMetric(allNodes, allNetworks, this);
			C2Metric = new AreaOverlapMetric(allNodes, this);
			currentC1 = C1Metric.init(allNetworks);
			currentC2 = C2Metric.init(allNodes);
		}

		/**
		 * Adds a node to the list of changed nodes.
		 *
		 * @param index
		 *            the index of the changed node.
		 * @param newNode
		 *            a new <code>PlacementNodePosition</code> for the changed
		 *            node.
		 * @return metric score for the current change.
		 */
		public double addNode(int index, PlacementNodePosition newNode) {
			changedNodes.put(new Integer(index), newNode);
			currentC1 = C1Metric.update(index);
			currentC2 = C2Metric.update(index);
			return currentC1 + currentC2;
		}

		public double removeNode(int index) {
			changedNodes.remove(new Integer(index));
			currentC1 = C1Metric.update(index);
			currentC2 = C2Metric.update(index);
			return currentC1 + currentC2;
		}

		/**
		 * Moves a node. This method chooses a node at random and tries to place
		 * it near its highest connected node.
		 *
		 * @return true if the move was accepted. false otherwise.
		 */
		public boolean moveNode() {
			int index = (int) Math.round(rand.nextDouble() * (originalNodes.size() - 1));
			PlacementNodePosition theOne = getNodeFromState(index);
			double x = theOne.getPlacementX();
			double y = theOne.getPlacementY();
			double maxDistance = 100;// temp.getCurrentSwapDistance()/10;

			double newX = x - rand.nextDouble() * maxDistance / 2 + rand.nextDouble() * maxDistance;
			double newY = y - rand.nextDouble() * maxDistance / 2 + rand.nextDouble() * maxDistance;

			PlacementNode bestPartner = getHighestConnectedNode(originalNodes.get(index));
			if (bestPartner != null) {
				double pX = bestPartner.getPlacementX();
				double pY = bestPartner.getPlacementY();
				if (rand.nextBoolean()) {
					newX = pX + ((rand.nextBoolean() ? 1 : -1) * bestPartner.getWidth());
					newY = pY;
				} else {
					newX = pX;
					newY = pY + ((rand.nextBoolean() ? 1 : -1) * bestPartner.getHeight());
				}
			} else {
				// Some other calculation
			}
			// newX = newX % (temp.initialChipLength);
			// newY = newY % (temp.initialChipLength);

			theOne.setPlacement(newX, newY);

			// check metric
			double metricBeforeMove = getScore();

			// double networksScoreBeforeMove = C1Metric.getNetworkScoreForNode(
			// originalNodes.get( index ) );

			addNode(index, theOne);

			// double networksScoreAfterMove = C1Metric.getNetworkScoreForNode(
			// originalNodes.get( index ) );

			double metricAfterMove = getScore();
			double deltaE = (metricAfterMove - metricBeforeMove) / metricAfterMove;

			// if( networksScoreAfterMove >= 2 * networksScoreBeforeMove ) {
			// //undo
			// theOne.setPlacement(x,y);
			// addNode(index, theOne);
			// return false;
			// }
			if (temp.accept(deltaE)) {
				return true;
			} else {
				// undo
				theOne.setPlacement(x, y);
				addNode(index, theOne);
				return false;
			}
		}

		/**
		 * Swaps two nodes. This method chooses two nodes at random and swaps
		 * them.
		 *
		 * @return true if the swap was accpeted. fase otherwise.
		 */
		public boolean chooseAndSwapNodes() {
			double maxDistance = temp.getCurrentSwapDistance() / 10;
			double maxDistanceSquared = maxDistance * maxDistance;
			int numNodes = originalNodes.size();
			int index1 = (int) Math.round(rand.nextDouble() * (numNodes - 1));

			// Find a partner to swap
			int index2 = 0;
			boolean partnerFound = false;
			for (int i = 0; i < numNodes / 10; i++) {
				index2 = (int) Math.round(rand.nextDouble() * (numNodes - 1));
				if (index2 == index1) continue;
				if (getConnectivity(originalNodes.get(index1), originalNodes.get(index2)) > 4) continue;

				double node1X = getNodeFromState(index1).getPlacementX();
				double node2X = getNodeFromState(index2).getPlacementX();
				double node1Y = getNodeFromState(index1).getPlacementY();
				double node2Y = getNodeFromState(index2).getPlacementY();
				double distance = (node1X - node2X) * (node1X - node2X) + (node1Y - node2Y)
						* (node1Y - node2Y);
				double distanceSquared = distance * distance;
				if (distanceSquared < maxDistanceSquared) {
					partnerFound = true;
					break;
				}
			}
			if (!partnerFound) return false;

			PlacementNodePosition n1 = getNodeFromState(index1);
			PlacementNodePosition n2 = getNodeFromState(index2);

			double tempX = n1.getPlacementX(), tempY = n1.getPlacementY();
			n1.setPlacementX(n2.getPlacementX());
			n1.setPlacementY(n2.getPlacementY());
			n2.setPlacementX(tempX);
			n2.setPlacementY(tempY);

			double metricBeforeSwap = getScore();

			addNode(index1, n1);
			addNode(index2, n2);

			double metricAfterSwap = getScore();

			double deltaE = (metricAfterSwap - metricBeforeSwap) / metricAfterSwap;
			if (temp.accept(deltaE)) {
				return true;
			} else {
				// unswap nodes
				n2.setPlacementX(n1.getPlacementX());
				n2.setPlacementY(n1.getPlacementY());
				n1.setPlacementX(tempX);
				n1.setPlacementY(tempY);

				addNode(index1, n1);
				addNode(index2, n2);
				return false;
			}
		}

		/**
		 * Returns the <code>PlacementNodePosition</code> for a node given the
		 * index.
		 *
		 * @param index
		 *            the index of the node
		 * @return the <code>PlacementNodePosition</code> for this node.
		 */
		public PlacementNodePosition getNodeFromState(int index) {
			Integer ii = new Integer(index);
			if (changedNodes.containsKey(ii)) {
				return changedNodes.get(ii);
			} else {
				return new PlacementNodePosition(originalNodes, index);
			}
		}

		public boolean isNodeChanged(int index) {
			return changedNodes.containsKey(new Integer(index));
		}

		public List<PlacementNode> getOriginalNodes() {
			return originalNodes;
		}

		public double getC1() {
			return currentC1;
		}

		public double getC2() {
			return currentC2;
		}

		/**
		 * Returns the score of the two metrics.
		 *
		 * @return the sum of both metric scores.
		 */
		public double getScore() {
			return currentC1 + 1000000 * currentC2;
		}

		/**
		 * Resets the state.
		 *
		 * @return the current metric score.
		 */
		public double reset() {
			changedNodes.clear();
			// Recalculate Netscores
			currentC1 = C1Metric.init(allNetworks);
			// Recalculate Overlaps
			currentC2 = C2Metric.init(originalNodes);
			// Recalculate Metrics
			return getScore();
			// return score
		}

		/**
		 * This methods makes this <code>IncrementalState</code> object a global
		 * state, i.e. the changes of this state will be applied to the nodes
		 * that are to be placed.
		 *
		 * @return the current metric score.
		 */
		public double makeGlobal() {
			for (Iterator<Entry<Integer, PlacementNodePosition>> it = changedNodes.entrySet().iterator(); it
					.hasNext();) {
				Entry<Integer, PlacementNodePosition> entry = it.next();
				int index = entry.getKey().intValue();
				PlacementNodePosition n = entry.getValue();
				PlacementNode originalPlacementNode = originalNodes.get(index);
				originalPlacementNode.setPlacement(n.getPlacementX(), n.getPlacementY());
				originalPlacementNode.setOrientation(n.getPlacementOrientation());
			}

			changedNodes.clear();

			return getScore();
		}

	}

}
