/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PlacementSimulatedAnnealing.java
* Written by Team 6: Sebastian Roether, Jochen Lutz
*
* 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.simulatedAnnealing2;
import com.sun.electric.database.geometry.Orientation;
import com.sun.electric.tool.placement.PlacementFrame;
import com.sun.electric.tool.placement.simulatedAnnealing2.PositionIndex.AreaSnapshot;
import java.awt.geom.Point2D;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* Implementation of the simulated annealing placement algorithm
*/
public class PlacementSimulatedAnnealing extends PlacementFrame
{
// Benchmarking properties
String teamName = "Team 6";
String studentName1 = "Sebastian";
String studentName2 = "Jochen";
String algorithmType = "simulated annealing";
public int numThreads = 2;
public int maxRuntime = 0; // in seconds, 0 means no time limit
public boolean printDebugInformation = false;
// Temperature control
private int iterationsPerTemperatureStep; // if maximum runtime is > 0 this is calculated each temperature step
private double startingTemperature = 0;
private double temperature;
private int temperatureSteps = 0; // how often will the temperature be decreased
private int temperatureStep;
private int perturbations_tried;
private long stepStartTime;
private long timestampStart = 0;
private double maxChipLength = 0;
private double minArea = 0;
private ArrayList<ProxyNode> nodesToPlace;
private List<PlacementNetwork> allNetworks;
private BoundingBoxMetric metric = null;
private Map<PlacementNode, ProxyNode> proxyMap;
private Map<PlacementNetwork, Double> netLengths = null;
private PositionIndex posIndex;
// Debug and performance
private final boolean performance_log = false;
private final String performance_log_filename = "placement.log";
private int accepts = 0; // moves accepted
private int conflicts = 0; // moves that were accepted but not made because of interference from other threads
double[] lengthLog = new double[1000000]; // TODO: write a class that collects performance data
double[] overlapLog = new double[1000000]; // TODO: allocate with appropriate size (temperatureSteps)
double[] timestampLog = new double[1000000];
double[] temperatureLog = new double[1000000];
double[] areaLog = new double[1000000];
double[] stepsizeLog = new double[1000000];
double[] acceptLog = new double[1000000];
double[] conflictLog = new double[1000000];
double[] stepdurationLog= new double[1000000];
int stepsPerUpdate = 50;
// Tweaking Parameter
private final double OVERLAP_WEIGHT = 100;
private final double MANHATTAN_WEIGHT = 0.1;
private final double AREA_WEIGHT = 10000;
/**
* Method that creates a map that hashes a node to its proxy node
* This is mainly for working with the net topology because nodes belonging to
* a net are of type <Node> not its <ProxyNode>
* @param nodesToPlace a list of proxy nodes
* @return the map that maps a node to its proxy
*/
private HashMap<PlacementNode, ProxyNode> createProxyHashmap( List<ProxyNode>nodesToPlace ) {
HashMap<PlacementNode, ProxyNode> proxyMap = new HashMap<PlacementNode, ProxyNode>();
for(ProxyNode p : nodesToPlace) {
proxyMap.put( p.getNode(), p );
}
return proxyMap;
}
/**
* Method to return the name of this placement algorithm.
* @return the name of this placement algorithm.
*/
public String getAlgorithmName() { return "Simulated-Annealing-2"; }
/**
* Method that counts how often the temperature will be decreased before going below 1
*/
private int countTemperatureSteps(double startingTemperature) {
double temperature = startingTemperature;
int steps = 0;
while(temperature > 1) {
steps++;
temperature = coolDown(temperature);
}
return steps;
}
/**
* Method to do placement by simulated annealing.
* @param nodesToPlace a list of all nodes that are to be placed.
* @param allNetworks a list of all networks that connect the nodes.
* @param cellName the name of the cell being placed.
*/
public void runPlacement( List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, String cellName )
{
timestampStart = System.currentTimeMillis();
this.allNetworks = allNetworks;
metric = new BoundingBoxMetric();
temperatureStep = 0;
iterationsPerTemperatureStep = maxRuntime > 0 ? 100 : getDesiredMoves( nodesToPlace );
perturbations_tried = 0;
accepts = 0;
conflicts = 0;
minArea = 0;
ArrayList<PlacementNetwork> ignoredNets = new ArrayList<PlacementNetwork>();
for(PlacementNetwork net: allNetworks)
if(net.getPortsOnNet().size() >= nodesToPlace.size() * 0.4 && net.getPortsOnNet().size() > 100)
ignoredNets.add(net);
// create an initial layout to be improved by simulated annealing
initLayout(nodesToPlace);
netLengths = new HashMap<PlacementNetwork, Double>();
// create proxies for placement nodes
this.nodesToPlace = new ArrayList<ProxyNode> ( nodesToPlace.size() );
for(PlacementNode p : nodesToPlace) {
ProxyNode proxy = new ProxyNode(p, ignoredNets);
this.nodesToPlace.add( proxy );
}
proxyMap = createProxyHashmap(this.nodesToPlace);
// Sum up the total node area. This is used as reference value for the area metric
for ( ProxyNode node : this.nodesToPlace ) {
minArea += node.width * node.height;
}
// Calculate the working area for the process (maximum chip area)
// nodes will not be placed outside the working area
maxChipLength = getMaxChipLength(nodesToPlace);
posIndex = new PositionIndex( maxChipLength, this.nodesToPlace );
// calculate the starting temperature
startingTemperature = getStartingTemperature(nodesToPlace, maxChipLength, maxRuntime);
temperature = startingTemperature;
temperatureSteps = countTemperatureSteps(startingTemperature);
for ( ProxyNode p : this.nodesToPlace )
p.apply();
// precalculate an hash net lengths
for(PlacementNetwork net : allNetworks)
netLengths.put(net, new Double(metric.netLength( net, proxyMap )));
stepStartTime = System.nanoTime();
SimulatedAnnealing[] threads = new SimulatedAnnealing[numThreads];
for(int n = 0; n < numThreads; n++)
{
threads[n] = new SimulatedAnnealing();
threads[n].start();
}
for (int i = 0; i < numThreads; i++) {
try {
threads[i].join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
// cleanup overlap
cleanup();
// Apply the placement of the proxies to the actual nodes
for(ProxyNode p : this.nodesToPlace)
p.apply();
if(performance_log) {
writeLog(performance_log_filename);
}
}
/**
* Method to calculate the initial temperature.
* It uses the standard deviation of the net length
* for random placements to derive the starting temperature
* @param length maximum length of the chip
* @return the initial temperature
*/
private double getStartingTemperature(List<PlacementNode> nodes, double length, double runtime)
{
double[] metrics = new double[1000]; // sample size
double sigma = 0;
double average = 0;
Random r = new Random();
double width = length;
double height = length;
// collect samples of the cost function
for(int i = 0; i < metrics.length; i++)
{
for(PlacementNode p : nodes) {
p.setPlacement(r.nextDouble() * width, r.nextDouble() * height);
}
metrics[i] = metric.netLength(allNetworks);
}
// calculate average
for(int i = 0; i < metrics.length; i++)
{
average += metrics[i];
}
average /= metrics.length;
// calculate standard deviation
for(int i = 0; i < metrics.length; i++)
{
sigma += (metrics[i] - average) * (metrics[i] - average);
}
sigma = Math.sqrt(sigma / metrics.length);
// set starting temperature so that the acceptance function
// accepts worsening of -sigma with a probability of 0.3
// e^(-sigma /startingTemperature) = 0.3 # solve for startingTemperature
// TODO THIS IS TWEAKING DATA
double startingTemperature = sigma * (-1 / (2 *Math.log(0.3)));
// If a maximum runtime is set, only the last temperature steps are done
if ( maxRuntime > 0 ) {
double msPerMove = guessMillisecondsPerMove();
int desired_moves = getDesiredMoves(nodes);
int possibleSteps = Math.max((int)((runtime * 1000) / (msPerMove * desired_moves)), 5);
int stepsUntilStop = countTemperatureSteps(startingTemperature);
for(int i = 0; i < stepsUntilStop - possibleSteps; i++)
startingTemperature = coolDown(startingTemperature);
}
return startingTemperature;
}
/**
* Method that calculates how many moves per temperature step are necessary
* @param nodes
* @return
*/
private int getDesiredMoves(List<PlacementNode> nodes) {
// TODO THIS IS TWEAKING DATA
return Math.max((int)(nodes.size() * Math.sqrt(nodes.size())), 8719);
}
/**
* Method that estimates how many moves can be evaluated with the current settings
* @return
*/
private double guessMillisecondsPerMove()
{
double time = System.currentTimeMillis();
double sampleSize = 20000;
Thread gatherers[] = new Thread[numThreads];
// start the threads
for(int i = 0; i < numThreads; i++)
{
gatherers[i] = new SampleGatherer((int)(sampleSize / numThreads));
gatherers[i].start();
}
// wait until they have finished
for(int i = 0; i < numThreads; i++)
{
try {
gatherers[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// A move actually takes more time than we measured
return (System.currentTimeMillis() - time) * 2 / sampleSize;
}
/**
* This class is used to guess how many moves per
* second can be calculated
*
* @author Basti
*
*/
class SampleGatherer extends Thread {
int samplesCount = 0;
public SampleGatherer(int samplesCount)
{
this.samplesCount = samplesCount;
}
public void run()
{
Random r = new Random();
for(int i = 0; i < samplesCount; i++)
{
ProxyNode proxy = nodesToPlace.get(r.nextInt(nodesToPlace.size()));
metric.netLength(proxy.getNets());
}
}
}
/**
* Method to calculate the cooling of the simulated annealing process
* @param temp the current temperature
* @return the lowered temperature
*/
private double coolDown(double temp)
{
//if(temp < 100) return temp * 0.95 - 0.1;
return temp * 0.99 - 0.1;
}
/**
* Synchronized method that does temperature and time control. Threads should call this periodically
* @param tries how many perturbation the thread tried since last calling update
* @param acceptCount how many of the perturbations that thread tried since last
* calling update were accepted
* @param conflictCount how many of the perturbations that were accepted since last calling
* update were dropped due to conflicts with other threads
*/
public synchronized void update( int tries, int acceptCount, int conflictCount )
{
this.perturbations_tried += tries;
this.accepts += acceptCount;
this.conflicts += conflictCount;
// decrease temperature if enough iterations were done
if(this.perturbations_tried >= iterationsPerTemperatureStep)
{
long stepDuration = System.nanoTime() - stepStartTime;
stepStartTime = System.nanoTime();
// this will hopefully be useful when optimizing
if(performance_log)
{
lengthLog[temperatureStep] = metric.netLength( allNetworks, proxyMap);
//overlapLog[temperatureStep] = metric.overlap(nodesToPlace);
timestampLog[temperatureStep] = System.currentTimeMillis() - timestampStart;
temperatureLog[temperatureStep] = temperature;
areaLog[temperatureStep] = posIndex.area.getArea() / minArea;
stepsizeLog[temperatureStep] = iterationsPerTemperatureStep;
acceptLog[temperatureStep] = this.accepts;
conflictLog[temperatureStep] = this.conflicts;
stepdurationLog[temperatureStep]= stepDuration;
}
// adjust how many moves to try before the next temperature decrease
if ( maxRuntime > 0 )
{
// use the observed time from the last step to calculate
// how many move to do
long elapsedTime = System.currentTimeMillis() - timestampStart;
long remainingTime = maxRuntime * 1000 - elapsedTime;
long remainingSteps = temperatureSteps - temperatureStep;
double allowedTimePerStep = 1e6 * ((double)remainingTime / remainingSteps);
iterationsPerTemperatureStep *= allowedTimePerStep/stepDuration;
iterationsPerTemperatureStep = Math.max(stepsPerUpdate, iterationsPerTemperatureStep);
}
temperatureStep++;
temperature = coolDown(temperature);
this.perturbations_tried = 0;
this.accepts = 0;
this.conflicts = 0;
}
}
/**
* Method that generates an initial node placement.
* @param nodesToPlace a list of nodes to place
*/
private void initLayout( List<PlacementNode> nodesToPlace )
{
// TODO replace initial random node placement if useful
int SPACING = 20;
int numRows = (int)Math.round(Math.sqrt(nodesToPlace.size()));
double xPos = 0, yPos = 0;
double maxHeight = 0;
// place the nodes in sqrt(n) rows * sqrt(n) nodes with no overlaps
for(int i=0; i<nodesToPlace.size(); i++)
{
PlacementNode plNode = nodesToPlace.get(i);
xPos += plNode.getWidth() / 2;
plNode.setPlacement(xPos, yPos + plNode.getHeight() / 2);
xPos += plNode.getWidth() / 2 + SPACING;
maxHeight = Math.max(maxHeight, plNode.getHeight());
if ((i%numRows) == numRows-1)
{
yPos += maxHeight + SPACING;
maxHeight = 0;
xPos = 0;
}
}
// for our metric it is desirable that the node are placed around (0,0)
// so move the center to (0,0)
double x = 0, y = 0;
for(PlacementNode node : nodesToPlace)
{
x += node.getPlacementX();
y += node.getPlacementY();
}
double x_m = x / nodesToPlace.size();
double y_m = y / nodesToPlace.size();
for(PlacementNode node : nodesToPlace)
{
node.setPlacement(node.getPlacementX() - x_m,
node.getPlacementY() - y_m);
}
}
/**
* Method that cleans up overlap. Beginning with the node closes to the origin nodes that overlap
* with nodes mor closer are moved outwards.
*/
private void cleanup()
{
ProxyNode nodes[] = new ProxyNode[nodesToPlace.size()];
nodesToPlace.toArray(nodes);
Arrays.sort(nodes);
// For all nodes, beginning with the one closes to the origin
for(int i = 0; i < nodes.length; i++)
{
// Get all nodes closer to the origin OR nodes that overlap but are already finalized
List<ProxyNode> co = posIndex.getPossibleOverlaps(nodes[i]);
List<ProxyNode> ct = new ArrayList<ProxyNode>();
for(ProxyNode node : co)
if((node.compareTo(nodes[i]) < 0 && node != nodes[i]) || node.finalized)
ct.add(node);
// move the node outwards depending on how big the overlap is until the overlap is gone
double overlap = 0;
while((overlap = metric.overlap(nodes[i], ct)) != 0)
{
double d = Math.sqrt(nodes[i].getPlacementX() * nodes[i].getPlacementX() + nodes[i].getPlacementY() * nodes[i].getPlacementY());
double x = nodes[i].getPlacementX() / d * Math.sqrt(overlap) * 0.1;
double y = nodes[i].getPlacementY() / d * Math.sqrt(overlap) * 0.1;
posIndex.move(nodes[i], nodes[i].getPlacementX() + x , nodes[i].getPlacementY() + y);
// Overlapping with nodes that are finalized is never allowed
List<ProxyNode> co2 = posIndex.getPossibleOverlaps(nodes[i]);
for(ProxyNode node : co2)
if(node.finalized && ct.contains(node) == false)
ct.add(node);
}
nodes[i].finalized = true;
}
}
/**
* Method that calculates the maximum working area
* This is because there is no use in moving nodes
* "miles" away ...
* @param nodesToPlace
* @return
*/
private double getMaxChipLength(List<PlacementNode> nodesToPlace)
{
double totalArea = 0;
for(PlacementNode p : nodesToPlace) {
totalArea += p.getHeight() * p.getWidth();
}
return Math.sqrt(totalArea) * 4;
}
/**
* writes collected performance data
* @param filename
*/
private void writeLog(String filename)
{
try {
FileWriter f = new FileWriter(filename);
for(int i = 0; i < temperatureStep; i++)
f.append( (long)timestampLog[i] + ","
+ (int)lengthLog[i] + ","
+ (int)temperatureLog[i] + ","
+ (int)overlapLog[i] + ","
+ areaLog[i] + ","
+ stepsizeLog[i] + ","
+ acceptLog[i] + ","
+ conflictLog[i] + ","
+ stepdurationLog[i] +"\n");
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Class that does the actual simulated annealing
* All instances of this class share the same data set
* in short simulated annealing goes like this:
*
* - find a measure of how good a placement is (eg. the total net length)
* - define a "starting temperature"
* - given a placement, do something that could change this measure
* (moving nodes around, rotating them...)
* - if the measure is better fine, if not the probability of accepting this
* depends on the temperature and on how much the placement is worse than before.
* the higher the temperature the higher the probability of bad moves beeing accepted
* - decrease temperature
* - repeat until temperature reaches 0
*
* To achieve a good balance of speedup, complexity and overhead we did...nothing ;-)
* Okay thats not the truth but we kept it very simple for now.
* Every perturbation is evaluated without any locking first and when its accepted
* its partially evaluated a second time in a synchronized block.
* This is because in the meantime the placement and the data used in the calculations
* may be outdated rendering the result useless
*
* The effects of this implementations are:
* - The less likely it is that a move is accepted, the better the speedup
* - The more threads started the more likely it is moves are conflicting when accepted
* because they work with the same data
* - starting more threads that there are cores is most likely useless (?)
*
* @author Basti
*
*/
class SimulatedAnnealing extends Thread
{
private static final int MUTATIONSWAP = 1;
private static final int MUTATIONMOVE = 2;
private static final int MUTATIONORIENTATE = 3;
Orientation[] orientations = {
Orientation.IDENT, Orientation.R, Orientation.RR, Orientation.RRR,
Orientation.X, Orientation.XR, Orientation.XRR, Orientation.XRRR,
Orientation.Y, Orientation.YR, Orientation.YRR, Orientation.YRRR,
Orientation.XY, Orientation.XYR, Orientation.XYRR, Orientation.XYRRR };
Random rand = new Random(); // TODO: thread-aware seed
/**
* Method that finds a random node
* @return a random node
*/
private ProxyNode getRandomNode()
{
return nodesToPlace.get( rand.nextInt( nodesToPlace.size() ) );
}
/**
* Method that calculates the net metric for a node in the placement using node
* information provided by a dummy
* This is typically used with <original> being a node in the current placement
* and <dummy> being a clone of that node with another location or rotation
* @param original a nodes in the current placement
* @param dummy a nodes not in the placement that replaces <original> for this calculation
* @param lengths the resulting individual net lengths
* @return the sum of the net lengths
*/
private double metricForDummy(ProxyNode original, ProxyNode dummy, HashMap<PlacementNetwork, Double>lengths)
{
double sum = 0;
for(PlacementNetwork net : dummy.getNets())
{
double length = metric.netLength( net, proxyMap, new ProxyNode[] {original}, new ProxyNode[] {dummy});
lengths.put(dummy.getOriginalNet(net), new Double(length));
sum += length;
}
return sum;
}
/**
* Method that calculates the net lengths for a placement in which some of the
* node are replaced by other nodes.
* This is typically used with <originals> being a list of nodes in the current placement
* and <dummies> being a list of clones of these nodes with another locations or rotations
* @param originals a list of nodes in the current placement
* @param dummies a list of nodes not in the placement that replace the nodes in <originals> for this calculation
* @param lengths the resulting individual net lengths
* @return the sum of the net lengths
*/
private double metricForDummies(ProxyNode[] originals, ProxyNode[] dummies, HashMap<PlacementNetwork, Double>lengths)
{
double sum = 0;
ArrayList<PlacementNetwork> nets = new ArrayList<PlacementNetwork>();
// create a set of nets connected to one of these nodes
for(int i = 0; i < originals.length; i++)
for(PlacementNetwork net : originals[i].getNets())
if(!nets.contains(net))
nets.add(net);
// sum up the net lengths
for(PlacementNetwork net : nets) {
double length = metric.netLength(net, proxyMap, originals, dummies);
lengths.put(net, new Double(length));
sum += length;
}
return sum;
}
/**
* Method that calculates how much overlap there would be in the current placement
* if one node is replaced by another
* This is typically used with <original> being a node in the current placement
* and <dummy> being a clone of that node with another location or rotation
* @param original
* @param dummy replacement of <original>
* @return
*/
private double overlapForDummy(ProxyNode original, ProxyNode dummy)
{
// in the list of nodes in the proximity of the dummy,
// we remove node that the dummy replaced an then calculate the
List<ProxyNode> candidates = posIndex.getPossibleOverlaps( dummy );
while(candidates.remove(original));
return metric.overlap(dummy, candidates );
}
/**
* Method that calculates how much overlap there would be in the current placement
* if two nodes are replaced by another two nodes
* @param original1
* @param original2
* @param dummy1 replacement of <original1>
* @param dummy2 replacement of <original2>
* @return
*/
private double overlapForDummy(ProxyNode original1, ProxyNode original2, ProxyNode dummy1, ProxyNode dummy2)
{
double overlap = 0;
List<ProxyNode> candidates1 = null;
List<ProxyNode> candidates2 = null;
candidates1 = posIndex.getPossibleOverlaps( dummy1 );
candidates2 = posIndex.getPossibleOverlaps( dummy2 );
// in the list of nodes in the proximity of dummy1,
// we remove the nodes that are replaced and add
// the other dummy
candidates1.add( dummy2 );
while(candidates1.remove(original1));
while(candidates1.remove(original2));
overlap += metric.overlap( dummy1, candidates1);
// in the list of nodes in the proximity of dummy2,
// we remove the nodes that are replaced and add
// the other dummy
candidates2.add( dummy1 );
while(candidates2.remove(original1));
while(candidates2.remove(original2));
overlap += metric.overlap( dummy2, candidates2);
return overlap;
}
public void run()
{
// Thread wont stop until temperature is below 1
while(temperature > 1)
{
int acceptCount = 0;
int conflictCount = 0;
// Try some perturbations before checking if temperature has to be lowered
for ( int i = 0; i < stepsPerUpdate; i++ )
{
ProxyNode node1 = null;
ProxyNode node2 = null;
ProxyNode dummy1 = null;
ProxyNode dummy2 = null;
double networkMetricBefore = 0, networkMetricAfter = 0;
double overlapMetricBefore = 0, overlapMetricAfter = 0;
double areaMetricBefore = 0, areaMetricAfter = 0;
double manhattanRadiusBefore = 0, manhattanRadiusAfter = 0;
HashMap<PlacementNetwork, Double> newNetLengths = new HashMap<PlacementNetwork, Double>();
int mutationType = randomPerturbationType();
AreaSnapshot area = posIndex.area;
areaMetricBefore = area.getArea();
// given a perturbation type, find nodes to apply the perturbation to and
// calculate the cost function for the altered layout
switch(mutationType)
{
// Find and swap two nodes
case MUTATIONSWAP:
node1 = getRandomNode();
while((node2 = getRandomNode()) == node1);
networkMetricBefore = metricForNodes( node1, node2 );
overlapMetricBefore = metric.overlap(node1, posIndex.getPossibleOverlaps( node1 ) ) + metric.overlap( node2, posIndex.getPossibleOverlaps( node2 ) ); // TODO Overlap of 1 and 2 is counted twice
// Create two dummies of the nodes that have the same size
// Move the clone of node1 to the position of node2 and vice versa
dummy1 = node1.clone();
dummy2 = node2.clone();
dummy1.setPlacement(node2.getPlacementX(), node2.getPlacementY());
dummy2.setPlacement(node1.getPlacementX(), node1.getPlacementY());
// calculate the metrics for a layout where
// the two nodes are replaced by the dummies
networkMetricAfter = metricForDummies ( new ProxyNode[] { node1, node2 }, new ProxyNode[] { dummy1, dummy2 }, newNetLengths);
overlapMetricAfter = overlapForDummy(node1, node2, dummy1, dummy2);
areaMetricAfter = area.areaForDummy(node1, node2, dummy1, dummy2);
break;
case MUTATIONMOVE:
node1 = getRandomNode();
networkMetricBefore = metricForNode( node1 );
overlapMetricBefore = metric.overlap(node1, posIndex.getPossibleOverlaps( node1 ) );
manhattanRadiusBefore = Math.max( Math.abs(node1.getPlacementX() ), Math.abs( node1.getPlacementY() ) );
// add a random translation vector to the nodes location
// but don't move it outside the "working area"
Point2D position_t = randomMove( node1 );
double new_x = (node1.getPlacementX() + position_t.getX() * (0.1 + 0.1 * temperature / startingTemperature)) % maxChipLength;
double new_y = (node1.getPlacementY() + position_t.getY() * (0.1 + 0.1 * temperature / startingTemperature)) % maxChipLength;
dummy1 = node1.clone();
dummy1.setPlacement(new_x, new_y);
// calculate the metrics for a layout where
// the node is moved to another location
networkMetricAfter = metricForDummy( node1, dummy1, newNetLengths );
overlapMetricAfter = overlapForDummy( node1, dummy1 );
areaMetricAfter = area.areaForDummy( node1, dummy1 );
manhattanRadiusAfter = Math.max( Math.abs(dummy1.getPlacementX() ), Math.abs( dummy1.getPlacementY() ) );
break;
case MUTATIONORIENTATE:
node1 = getRandomNode();
networkMetricBefore = metricForNode( node1 );
overlapMetricBefore = metric.overlap(node1, posIndex.getPossibleOverlaps( node1 ) );
dummy1 = node1.clone();
dummy1.setPlacementOrientation(orientations[rand.nextInt(orientations.length)], true);
// calculate the metrics for a layout where
// the node is rotated
networkMetricAfter = metricForDummy( node1, dummy1, newNetLengths );
overlapMetricAfter = overlapForDummy(node1, dummy1 );
areaMetricAfter = area.areaForDummy( node1, dummy1 );
}
// TODO area metric is obsolete because of the manhattan geometry metric
// which give much more compact placements
double networkGain = networkMetricBefore - networkMetricAfter;
double overlapGain = overlapMetricBefore - overlapMetricAfter;
double areaGain = (areaMetricBefore - areaMetricAfter) / minArea;
double manhattanRadiusGain = manhattanRadiusBefore - manhattanRadiusAfter;
double gain = networkGain
+ overlapGain * OVERLAP_WEIGHT
+ areaGain * AREA_WEIGHT
+ manhattanRadiusGain * MANHATTAN_WEIGHT;
// the worse the gain of a perturbation the lower the
// probability of this perturbation to actually be applied
// (positive gains are always accepted)
if ( Math.exp(gain / temperature) >= Math.random() )
{
acceptCount++;
// Before we actually apply the perturbation, we have to check if the overlap
// has changed. This is because we haven't locked the area we are occupying
// and another thread could have placed a node that would now overlap.
// The same goes for nets that could also be altered by another thread
//
// Note:
// There is still a logical race condition because it is possible that
// two moves are already accepted but after the first one has been written,
// the second one would be most likely be rejected. it just not very likely
// and with thousands of moves per seconds no big deal
//
// Performance:
// The overlap has to be calculated twice (nets are hashed values).
// The synchronized block is quite coarse as everything is locked.
// It could be replaced by a smaller synchronized block that only
// checks and locks altered parts of the placement (target areas,
// nets)
synchronized(posIndex)
{
switch(mutationType)
{
case MUTATIONMOVE:
if(overlapForDummy(node1, dummy1) == overlapMetricAfter &&
metricForNode(node1) == networkMetricBefore )
{
posIndex.move( node1 , dummy1.getPlacementX() , dummy1.getPlacementY() );
for(PlacementNetwork net : newNetLengths.keySet())
netLengths.put(net, newNetLengths.get(net));
}
else
conflictCount++;
break;
case MUTATIONSWAP:
if(overlapForDummy(node1, node2, dummy1, dummy2) == overlapMetricAfter &&
metricForNodes(node1, node2) == networkMetricBefore )
{
posIndex.swap( node1, node2 );
for(PlacementNetwork net : newNetLengths.keySet())
netLengths.put(net, newNetLengths.get(net));
}
else
conflictCount++;
break;
case MUTATIONORIENTATE:
if(overlapForDummy(node1, dummy1) == overlapMetricAfter &&
metricForNode(node1) == networkMetricBefore )
{
posIndex.rotate(node1, dummy1.getPlacementOrientation());
for(PlacementNetwork net : newNetLengths.keySet())
netLengths.put(net, newNetLengths.get(net));
}
else
conflictCount++;
}
}
//node1.apply(); // TODO REMOVE
//if(node2 != null) node2.apply(); // TODO REMOVE
}
}
update( stepsPerUpdate, acceptCount, conflictCount );
}
}
/**
* Method that generates a random translation vector for a given node
* @param node
* @return a translation vector
*/
private Point2D randomMove( ProxyNode node )
{
double maxBoundingBox = 1;
// the length of the vector is a function of the maximum bounding box
// the further away connected nodes are, the further the node may move
ArrayList<PlacementNetwork> nets = node.getNets();
if ( nets.size() > 0 ) {
for ( PlacementNetwork net : nets )
{
double metric = netLengths.get(net).doubleValue();
if ( metric > maxBoundingBox )
maxBoundingBox = metric;
}
}
else {
maxBoundingBox = maxChipLength;
}
// shorter vectors are more likely
// don't move nodes too far away if they are already good positioned
double offsetX = Math.min( Math.max( rand.nextGaussian(), -1 ), 1) * maxBoundingBox;
double offsetY = Math.min( Math.max( rand.nextGaussian(), -1 ), 1) * maxBoundingBox;
return new Point2D.Double( offsetX, offsetY );
}
/**
* Returns a random perturbation type.
* @return
*/
private int randomPerturbationType()
{
int r = rand.nextInt(100);
// TODO: replace with variables
// THIS IS TWEAKING DATA
if ( r < 10 ) return MUTATIONSWAP;
if ( r < 90 ) return MUTATIONMOVE;
return MUTATIONORIENTATE;
}
/**
* Method that estimates the net lengths of all nets connected to two nodes
* @param n1
* @param n2
* @return
*/
private double metricForNodes(ProxyNode n1, ProxyNode n2)
{
// TODO Change so that it matches the method used in metricForDummies
// swaps check if the net has changed by comparing two doubles that
// are calculated in different ways for the same data
double metric = metricForNode(n1) + metricForNode(n2);
for(PlacementNetwork p: n1.getNets())
if(n2.getNets().contains(p))
metric -= netLengths.get(p).doubleValue();
return metric;
}
/**
* Method that estimates the net lengths of all nets connected to a given node
* @param node the node to evaluate
* @return a value for the current position. The lower the value, the better.
*/
private double metricForNode(ProxyNode node)
{
double metric = 0;
for(PlacementNetwork net : node.getNets())
metric += netLengths.get(net).doubleValue();
return metric;
}
}
}