/* -*- 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 Sun Microsystems and Static Free Software * * 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 com.sun.electric.database.geometry.Orientation; import com.sun.electric.database.geometry.GenMath.MutableInteger; import com.sun.electric.tool.placement.PlacementFrame; 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 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; /** Parallel Placement **/ public class SimulatedAnnealing extends PlacementFrame { /** * Number of threads. */ private final int NUM_THREADS = 2; /** * 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; 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) { 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 / (NUM_THREADS * STEP_THREAD); //nodesToPlace.size() / 7 + 100; //TODO: Exponential Growth Function System.out.println("Inner Steps = " + numInnerSteps); System.out.println("Running placement, please wait..."); //step4: Initialize Thread Pool ExecutorService threadPool = Executors.newFixedThreadPool( NUM_THREADS ); //step5: create workers LinkedList<Callable<Double>> workforce = new LinkedList<Callable<Double>>(); for (int i = 0; i < NUM_THREADS; 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 < NUM_THREADS; 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 < NUM_THREADS; 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(); } } }