/* -*- 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 Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ 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.placement.PlacementFrame; 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.BoundingBoxMetric; import com.sun.electric.tool.placement.simulatedAnnealing1.metrics.MSTMetric; import com.sun.electric.util.math.GenMath.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); */ @Override protected void runPlacement(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, String cellName) { 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(); } } }