package jadex.rules.tools.reteviewer; import jadex.rules.rulesystem.rete.nodes.BetaNode; import jadex.rules.rulesystem.rete.nodes.INode; import jadex.rules.rulesystem.rete.nodes.ITupleConsumerNode; import jadex.rules.rulesystem.rete.nodes.TerminalNode; import java.awt.Dimension; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import edu.uci.ics.jung.algorithms.layout.AbstractLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.Graph; /** * Jung layout for a Rete network. */ public class ReteLayout extends AbstractLayout implements Layout { /** The size. */ // Managed directly, because jung's abstractlayout introduces // it's own (incompatible) offsets. protected Dimension size; /** The nodes, sorted in layers. */ protected List layers; /** Flag to indicate when positions are up-to-date. */ protected boolean positions; /** Flag to enable layout optimization. * With layout optimization, not only edge lengths are optimized, * but also left/right directions of connected alpha and beta nodes. */ protected boolean layout; /** Flag to enable simulated annealing (SA). * With SA, the layout algorithm will take longer, * but can escape sub optimal local minima. */ protected boolean annealing; //-------- constructors -------- /** * Create a new Rete layout. */ public ReteLayout(Graph g) { super(g); this.layout = true; this.annealing = true; } //-------- methods -------- /** * Called when a (re-)layout is needed. */ public void initialize() { // Method required by Layout interface. // Nothing to do here: use lazy evaluation triggered by getGraph(). } /** * Called when ? */ public void reset() { // Method required by Layout interface. // Nothing to do here? } /** * Get the graph to be layouted. */ public Graph getGraph() { if(layers==null) layoutLayers(); if(!positions) setPositions(); return super.getGraph(); } /** * Called, when the component is resized. * New positions will be calculated on next redrawn. */ public void setSize(Dimension size) { positions = false; this.size = size; } /** * Get the size. */ public Dimension getSize() { return this.size; } /** * Called, when the graph structure has changed. * New layer structure will be calculated on next redraw. */ public void graphChanged() { layers = null; positions = false; } //-------- helper methods -------- /** * Called when a (re-)layout is needed. * Arrange node in layers structure (lists of lists of nodes), * which is independent of component size. */ protected void layoutLayers() { // Find root/leaf node(s). Graph graph = super.getGraph(); List rootnodes = new ArrayList(); List leafnodes = new ArrayList(); for(Iterator it=graph.getVertices().iterator(); it.hasNext(); ) { Object next = it.next(); if(graph.getInEdges(next).isEmpty()) { rootnodes.add(next); } if(graph.getOutEdges(next).isEmpty()) { leafnodes.add(next); } } // Add nodes to layers according to depth in rete network. this.layers = new ArrayList(); layers.add(rootnodes); for(int l=0; l<layers.size(); l++) { Object[] nodes = ((List)layers.get(l)).toArray(); for(int n=0; n<nodes.length; n++) { Object[] edges = graph.getOutEdges(nodes[n]).toArray(); for(int e=0; e<edges.length; e++) { Object child = ((ReteEdge)edges[e]).getEnd(); // Add child nodes (except leaf nodes, which are added in separate layer). if(!leafnodes.contains(child)) { // Remove previous occurrence (if any). for(int l2=0; l2<layers.size(); l2++) ((List)layers.get(l2)).remove(child); // Add to next layer. if(l==layers.size()-1) layers.add(new ArrayList()); ((List)layers.get(l+1)).add(child); } } } } // Add final layer for leaf nodes. layers.add(leafnodes); // Set all layers to same length. // When annealing and layouting, add some more space for finding better solutions. int maxsize = 0; for(int l=0; l<layers.size(); l++) { // System.out.println("Layer "+l+": "+layers.get(l)); maxsize = Math.max(maxsize, ((List)layers.get(l)).size()); } maxsize = annealing && layout ? (int)Math.sqrt(layers.size()*layers.size()+maxsize*maxsize) : maxsize; for(int l=0; l<layers.size(); l++) { List layer = (List)layers.get(l); while(layer.size()<maxsize) layer.add(null); } // Optimize node positions. boolean swap = true; double threshold = annealing ? graph.getVertexCount()*graph.getVertexCount() : 0; while(swap) { swap = false; for(int l=0; l<layers.size(); l++) { List layer = (List)layers.get(l); for(int i=0; i<layer.size(); i++) { for(int j=i+1; j<layer.size(); j++) { INode node1 = (INode)layer.get(i); INode node2 = (INode)layer.get(j); if(node1!=null || node2!=null) { double pre = (node1!=null?calcEdgeLengths(graph, node1, l, i):0) + (node2!=null?calcEdgeLengths(graph, node2, l, j):0); double post = (node1!=null?calcEdgeLengths(graph, node1, l, j):0) + (node2!=null?calcEdgeLengths(graph, node2, l , i):0); if(post-pre < threshold) { layer.set(i, node2); layer.set(j, node1); swap = true; // System.out.println("Gain: "+(pre-post)+", pre="+pre+", post="+post+", threshold="+threshold); } } } } } threshold *= 0.9; if(threshold<0.1) threshold = 0; } } /** * Called, when the component has been resized. * Use layer structure to place nodes inside component bounds. */ protected void setPositions() { positions = true; Dimension dim = getSize(); int height = dim.height / layers.size(); // Find offsets to ignore extra space added for optimization. int minleft = Integer.MAX_VALUE; int maxright = 0; for(int l=0; l<layers.size(); l++) { boolean found = false; List layer = (List)layers.get(l); for(int i=0; i<layer.size(); i++) { if(layer.get(i)!=null // Ignore beta and terminal nodes, which are layouted according to their parents. && !(layer.get(i) instanceof BetaNode) && !(layer.get(i) instanceof TerminalNode)) { if(!found) { minleft = Math.min(i, minleft); } maxright = Math.max(i, maxright); found = true; } } } // System.out.println("minleft="+minleft+", maxright="+maxright); // Place nodes on available space. int width = dim.width / (maxright+1-minleft); for(int l=0; l<layers.size(); l++) { INode[] nodes = (INode[])((List)layers.get(l)).toArray(new INode[0]); for(int n=0; n<nodes.length; n++) { if(nodes[n]!=null) { boolean finished = false; // Place a beta node centered below left/right parent. if(nodes[n] instanceof BetaNode) { INode left = ((BetaNode)nodes[n]).getTupleSource(); INode right = ((BetaNode)nodes[n]).getObjectSource(); if(left!=null && right!=null) { Point2D leftpos = transform(left);//getLocation(left); Point2D rightpos = transform(right);//getLocation(right); if(leftpos!=null && rightpos!=null) { setLocation(nodes[n], (leftpos.getX()+rightpos.getX())/2, l*height+ height/2); finished = true; } } } // Place a terminal node centered below parent. else if(nodes[n] instanceof TerminalNode) { INode parent = ((ITupleConsumerNode)nodes[n]).getTupleSource(); if(parent!=null) { Point2D parentpos = transform(parent);//getLocation(parent); if(parentpos!=null) { setLocation(nodes[n], parentpos.getX(), (layers.size()-1)*height+ height/2); finished = true; } } } // Place other nodes according to location in layer. if(!finished) { setLocation(nodes[n], (n-minleft)*width + width/2, l*height+ height/2); } } } } } //-------- helper methods -------- /** * Calculate the edge lengths between a node and its parents/children. */ protected double calcEdgeLengths(Graph graph, INode node, int layer, int pos) { double length = 0; for(Iterator it=graph.getInEdges(node).iterator(); it.hasNext(); ) { ReteEdge edge = (ReteEdge)it.next(); // System.out.println(edge); length += calcEdgeLength(edge, node, layer, pos); } for(Iterator it=graph.getOutEdges(node).iterator(); it.hasNext(); ) { ReteEdge edge = (ReteEdge)it.next(); // System.out.println(edge); length += calcEdgeLength(edge, node, layer, pos); } return length; } /** * Calculate the edge length of the given edge. * If layout is enabled, the edge length is rated according * to layout constraints (shorter length = better layout). */ protected double calcEdgeLength(ReteEdge edge, INode node, int layer, int pos) { // Find node positions. int layer1, pos1; int layer2, pos2; if(node==edge.getStart()) { layer1 = layer; pos1 = pos; pos2 = -1; for(layer2=0; pos2==-1 && layer2<layers.size(); layer2++) { pos2 = ((List)layers.get(layer2)).indexOf(edge.getEnd()); } if(pos2==-1) throw new RuntimeException("Node not found: "+edge.getEnd()); } else { pos1 = -1; for(layer1=0; pos1==-1 && layer1<layers.size(); layer1++) { pos1 = ((List)layers.get(layer1)).indexOf(edge.getStart()); } if(pos1==-1) throw new RuntimeException("Node not found: "+edge.getStart()); layer2 = layer; pos2 = pos; } double a = layer1 - layer2; double b = pos1 - pos2; // 1 field left-to-right adjustment and higher priority for beta edges. if(layout && edge.isTuple()) { b = (b + 1) * 2.5; } // 1 field right-to-left adjustment and slightly higher priority for alpha-to-beta edges. else if(layout && edge.getEnd() instanceof BetaNode) { b = (b - 1) * 1.9; } return Math.sqrt(a*a + b*b); } }