/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PlacementMinCut.java
*
* Copyright (c) 2009, 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;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sun.electric.util.math.GenMath.MutableInteger;
import com.sun.electric.util.math.Orientation;
/**
* Placement algorithm to do Min-Cut placement.
*/
public class PlacementMinCut extends PlacementFrame
{
private final double PADDING = 5;
private final boolean DEBUG = false;
private Map<PlacementNode,Map<PlacementNode,MutableInteger>> connectivityMap;
/**
* Method to return the name of this placement algorithm.
* @return the name of this placement algorithm.
*/
public String getAlgorithmName() { return "Min-Cut"; }
/**
* Method to do Min-Cut Placement.
* @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.
*/
protected void runPlacement(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, String cellName)
{
// build the connectivity map with the number of connections between any two PlacementNodes
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);
}
}
}
// make initial partition with all nodes
List<PlacementNode> singletons = new ArrayList<PlacementNode>();
Partition topPart = new Partition();
topPart.depth = 0;
for(PlacementNode plNode : nodesToPlace)
{
boolean connected = false;
for(PlacementPort plPort : plNode.getPorts())
{
PlacementNetwork net = plPort.getPlacementNetwork();
if (net == null) continue;
if (net.getPortsOnNet().size() > 1) { connected = true; break; }
}
if (connected) topPart.allNodes.add(plNode); else
singletons.add(plNode);
}
// build list of partitions to organize
List<Partition> partitionsToOrganize = new ArrayList<Partition>();
partitionsToOrganize.add(topPart);
// iteratively go through list and organize them
while (partitionsToOrganize.size() > 0)
{
// get partition
Partition part = partitionsToOrganize.get(0);
partitionsToOrganize.remove(0);
if (part.allNodes.size() <= 2) continue;
// split the partition randomly
part.splitRandomly();
if (DEBUG)
{
System.out.print("INITIAL NODES IN GROUP 1:");
for(PlacementNode plNode : part.part1.allNodes) System.out.print(" "+plNode);
System.out.println();
System.out.print("INITIAL NODES IN GROUP 2:");
for(PlacementNode plNode : part.part2.allNodes) System.out.print(" "+plNode);
System.out.println();
}
// organize the two halves properly
part.organize();
if (DEBUG)
{
System.out.print("FINAL NODES IN GROUP 1:");
for(PlacementNode plNode : part.part1.allNodes) System.out.print(" "+plNode);
System.out.println();
System.out.print("FINAL NODES IN GROUP 2:");
for(PlacementNode plNode : part.part2.allNodes) System.out.print(" "+plNode);
System.out.println();
}
if (part.part1.allNodes.size() > 2) partitionsToOrganize.add(part.part1);
if (part.part2.allNodes.size() > 2) partitionsToOrganize.add(part.part2);
}
Point2D lastOffset = placePartitions(topPart, new Point2D.Double(0, 0));
double x = lastOffset.getX(), y = lastOffset.getY();
for(PlacementNode plNode : singletons)
{
plNode.setPlacement(x, y);
x += PADDING;
}
}
private Point2D placePartitions(Partition part, Point2D offset)
{
String indent = "";
if (DEBUG)
{
for(int i=0; i<part.depth; i++) indent += " ";
indent += part.depth + ": ";
System.out.println(indent+"PARTITION AT OFFSET ("+offset.getX()+","+offset.getY()+")...");
}
Point2D off;
if (part.part1 != null && part.part2 != null)
{
if (DEBUG) System.out.println(indent+"PLACING SUBPART 1 AT ("+offset.getX()+","+offset.getY()+")");
Point2D off1 = placePartitions(part.part1, offset);
if ((part.depth&1) == 0)
{
Point2D nextOff = new Point2D.Double(offset.getX(), offset.getY() + off1.getY());
if (DEBUG) System.out.println(indent+"PLACING SUBPART 2 AT ("+nextOff.getX()+","+nextOff.getY()+")");
Point2D off2 = placePartitions(part.part2, nextOff);
off = new Point2D.Double(Math.max(off1.getX(), off2.getX()), off1.getY() + off2.getY());
} else
{
Point2D nextOff = new Point2D.Double(offset.getX() + off1.getX(), offset.getY());
if (DEBUG) System.out.println(indent+"PLACING SUBPART 2 AT ("+nextOff.getX()+","+nextOff.getY()+")");
Point2D off2 = placePartitions(part.part2, nextOff);
off = new Point2D.Double(off1.getX() + off2.getX(), Math.max(off1.getY(), off2.getY()));
}
} else
{
double widestX = 0, widestY = 0;
double placeX = offset.getX(), placeY = offset.getY();
for(PlacementNode plNode : part.allNodes)
{
double width = plNode.getWidth();
double height = plNode.getHeight();
width = height = Math.max(width, height);
Point2D thisOff = new Point2D.Double(placeX, placeY);
if ((part.depth&1) != 0)
{
widestX += width + PADDING;
placeX += width + PADDING;
widestY = Math.max(widestY, height + PADDING);
} else
{
widestX = Math.max(widestX, width + PADDING);
widestY += height + PADDING;
placeY += height + PADDING;
}
plNode.setPlacement(thisOff.getX(), thisOff.getY());
}
Map<PlacementNode,Orientation> properOrientation = findOrientations(part.allNodes);
for(PlacementNode plNode : part.allNodes)
{
Orientation or = properOrientation.get(plNode);
if (or != null)
plNode.setOrientation(or);
}
off = new Point2D.Double(widestX, widestY);
}
if (DEBUG) System.out.println(indent+"NEW OFFSET ("+off.getX()+","+off.getY()+")");
return off;
}
private static class OrientationConnection
{
PlacementPort thisPP;
PlacementNode otherPN;
PlacementPort otherPP;
}
/**
* Method to find the ideal orientation for all of the nodes at the bottom point.
* @param allNodes a List of PlacementNodes that have location, but not ideal orientation.
* @return a Map assigning orientation to each of the PlacementNodes in the list.
*/
private Map<PlacementNode,Orientation> findOrientations(List<PlacementNode> allNodes)
{
Map<PlacementNode,Orientation> properOrientation = new HashMap<PlacementNode,Orientation>();
if (allNodes.size() > 1)
{
//boolean debug = currentCellName.equals("spiceHier{sch}");
//if (debug) System.out.println("FINDING ORIENTATIONS FOR PARTITION WITH "+allNodes.size()+" NODES");
Map<PlacementNode,List<OrientationConnection>> allPossibilities = new HashMap<PlacementNode,List<OrientationConnection>>();
for(PlacementNode plNode : allNodes)
{
// create a List of OrientationConnection objects for this NodeInst
List<OrientationConnection> oc = new ArrayList<OrientationConnection>();
allPossibilities.put(plNode, oc);
}
// add all of the connections to other nodes in the partition
for(PlacementNode plNode : allNodes)
{
List<OrientationConnection> oc = allPossibilities.get(plNode);
for(PlacementPort plPort : plNode.getPorts())
{
PlacementNetwork plNet = plPort.getPlacementNetwork();
if (plNet == null) continue;
for(PlacementPort otherPlPort : plNet.getPortsOnNet())
{
PlacementNode otherPlNode = otherPlPort.getPlacementNode();
if (otherPlNode == plNode) continue;
if (allPossibilities.get(otherPlNode) == null) continue;
OrientationConnection orc = new OrientationConnection();
orc.thisPP = plPort;
orc.otherPN = otherPlNode;
orc.otherPP = otherPlPort;
oc.add(orc);
}
}
}
// now find the optimal orientation choice for each NodeInst
Orientation [] standardEight = new Orientation[] {Orientation.IDENT, Orientation.R, Orientation.RR, Orientation.RRR,
Orientation.X, Orientation.XR, Orientation.XRR, Orientation.XRRR};
// if (allNodes.size() == 2)
// {
// // try all combinations of the two
// PlacementNode plNode1 = allNodes.get(0);
// PlacementNode plNode2 = allNodes.get(1);
// List<OrientationConnection> oc = allPossibilities.get(plNode1);
// Orientation betterOrientation1 = null;
// Orientation betterOrientation2 = null;
// double bestDist = Double.MAX_VALUE;
// for(int i=0; i<standardEight.length; i++)
// {
// plNode1.setOrientation(standardEight[i]);
// for(int j=0; j<standardEight.length; j++)
// {
// plNode2.setOrientation(standardEight[j]);
// double length = 0;
// for(OrientationConnection con : oc)
// {
// Point2D pt1 = new Point2D.Double(plNode1.getPlacementX() + con.thisPP.getRotatedOffX(),
// plNode1.getPlacementY() + con.thisPP.getRotatedOffY());
// Point2D pt2 = new Point2D.Double(plNode2.getPlacementX() + con.otherPP.getRotatedOffX(),
// plNode2.getPlacementY() + con.otherPP.getRotatedOffY());
// double dist = pt1.distance(pt2);
// length += dist;
// }
// if (betterOrientation1 == null || length < bestDist)
// {
// bestDist = length;
// betterOrientation1 = standardEight[i];
// betterOrientation2 = standardEight[j];
// }
// }
// }
// if (betterOrientation1 != null)
// {
// plNode1.setOrientation(betterOrientation1);
// properOrientation.put(plNode1, betterOrientation1);
// plNode2.setOrientation(betterOrientation2);
// properOrientation.put(plNode2, betterOrientation2);
// }
// } else
{
for(PlacementNode plNode : allNodes)
{
List<OrientationConnection> oc = allPossibilities.get(plNode);
double bestDist = Double.MAX_VALUE;
Orientation betterOrientation = null;
for(int i=0; i<standardEight.length; i++)
{
plNode.setOrientation(standardEight[i]);
//if (debug) System.out.println("COMPUTING LENGTH FOR NODE "+plNode+" ORIENTATION "+standardEight[i]);
double length = 0;
for(OrientationConnection con : oc)
{
Point2D pt = new Point2D.Double(plNode.getPlacementX() + con.thisPP.getRotatedOffX(),
plNode.getPlacementY() + con.thisPP.getRotatedOffY());
Point2D otherPt = new Point2D.Double(con.otherPN.getPlacementX() + con.otherPP.getRotatedOffX(),
con.otherPN.getPlacementY() + con.otherPP.getRotatedOffY());
double dist = pt.distance(otherPt);
//if (debug) System.out.println(" ("+pt.getX()+","+pt.getY()+") TO ("+otherPt.getX()+","+otherPt.getY()+") = "+dist);
length += dist;
}
//if (debug) System.out.println(" ======= LENGTH="+length);
if (betterOrientation == null || length < bestDist)
{
bestDist = length;
betterOrientation = standardEight[i];
}
}
if (betterOrientation != null)
{
plNode.setOrientation(betterOrientation);
properOrientation.put(plNode, betterOrientation);
}
}
}
}
return properOrientation;
}
private class Partition
{
List<PlacementNode> allNodes;
Partition part1, part2;
int depth;
Partition()
{
allNodes = new ArrayList<PlacementNode>();
}
void splitRandomly()
{
part1 = new Partition();
part2 = new Partition();
part1.depth = depth+1;
part2.depth = depth+1;
boolean putIn1 = true;
for(PlacementNode plNode : allNodes)
{
if (putIn1) part1.allNodes.add(plNode); else
part2.allNodes.add(plNode);
putIn1 = !putIn1;
}
}
void organize()
{
// make a list of swaps
List<SwapNodes> allSwaps = new ArrayList<SwapNodes>();
for(;;)
{
// determine the baseline connection count
int startingCuts = findNumCuts();
// now try swapping all nodes
int bestGain = 0;
int group1Member = -1, group2Member = -1;
for(int i=0; i<part1.allNodes.size(); i++)
{
PlacementNode plNode1 = part1.allNodes.get(i);
for(int j=0; j<part2.allNodes.size(); j++)
{
PlacementNode plNode2 = part2.allNodes.get(j);
// swap them
part1.allNodes.set(i, plNode2);
part2.allNodes.set(j, plNode1);
// see if the swap produces a gain
int newCuts = findNumCuts();
int gain = startingCuts - newCuts;
if (gain > bestGain)
{
// gain found
bestGain = gain;
group1Member = i;
group2Member = j;
}
// restore order
part1.allNodes.set(i, plNode1);
part2.allNodes.set(j, plNode2);
}
}
// stop now if no gain was found
if (bestGain == 0) break;
// enter the gain, remove these nodes, and continue to search
PlacementNode plNode1 = part1.allNodes.get(group1Member);
PlacementNode plNode2 = part2.allNodes.get(group2Member);
if (DEBUG) System.out.println("SWAPPING NODES "+plNode1+" AND "+plNode2+" FOR A GAIN OF "+bestGain);
part1.allNodes.remove(group1Member);
part2.allNodes.remove(group2Member);
SwapNodes g = new SwapNodes(plNode1, plNode2);
allSwaps.add(g);
}
// put the swaps back in, swapped
for(SwapNodes g : allSwaps)
{
part1.allNodes.add(g.plNode2);
part2.allNodes.add(g.plNode1);
}
}
/**
* Method to scan two halves of the Partition and return the total number of
* connections that separate them.
* @return the number of connections between the two groups.
*/
private int findNumCuts()
{
int cuts = 0;
for(PlacementNode plNode1 : part1.allNodes)
{
for(PlacementNode plNode2 : part2.allNodes)
cuts += getConnectivity(plNode1, plNode2);
}
return cuts;
}
}
private static class SwapNodes
{
PlacementNode plNode1, plNode2;
SwapNodes(PlacementNode n1, PlacementNode n2)
{
plNode1 = n1;
plNode2 = n2;
}
}
/**
* 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();
}
/**
* 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();
}
}