/* -*- 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();
}
}
}