/* -*- 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, 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.simulatedAnnealing2;
import com.sun.electric.tool.placement.PlacementFrame;
import com.sun.electric.tool.placement.PlacementFrame.PlacementParameter;
import com.sun.electric.tool.placement.simulatedAnnealing2.PositionIndex.AreaSnapshot;
import com.sun.electric.util.math.Orientation;
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 PlacementParameter numThreadsParam = new PlacementParameter("threads", "Number of threads:", 2);
public PlacementParameter maxRuntimeParam = new PlacementParameter("runtime",
"Runtime (seconds, 0 means no time limit):", 240);
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; // TODO: write a class that collects performance data
double[] overlapLog; // TODO: allocate with appropriate size
// (temperatureSteps)
double[] timestampLog;
double[] temperatureLog;
double[] areaLog;
double[] stepsizeLog;
double[] acceptLog;
double[] conflictLog;
double[] stepdurationLog;
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) {
this.setParamterValues(this.numThreadsParam.getIntValue(), this.maxRuntimeParam.getIntValue());
this.initializeParameters();
timestampStart = System.currentTimeMillis();
this.allNetworks = allNetworks;
metric = new BoundingBoxMetric();
temperatureStep = 0;
iterationsPerTemperatureStep = runtime > 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, runtime);
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();
int numThreads = numOfThreads;
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);
}
}
private void initializeParameters() {
lengthLog = new double[1000000];
overlapLog = new double[1000000];
timestampLog = new double[1000000];
temperatureLog = new double[1000000];
areaLog = new double[1000000];
stepsizeLog = new double[1000000];
acceptLog = new double[1000000];
conflictLog = new double[1000000];
stepdurationLog = new double[1000000];
}
/**
* 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 (runtime > 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;
int numThreads = numOfThreads;
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 (runtime > 0) {
// use the observed time from the last step to calculate
// how many move to do
long elapsedTime = System.currentTimeMillis() - timestampStart;
long remainingTime = runtime * 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;
}
}
}