ソースを参照

skeleton of A* search algorithm made.

Claudio Gomes 5 年 前
コミット
f2748b390b

+ 1 - 0
HintCOEngine/.classpath

@@ -25,5 +25,6 @@
 			<attribute name="javadoc_location" value="jar:platform:/resource/HintCOEngine/lib/jgrapht-core-1.3.1-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
+	<classpathentry kind="lib" path="lib/jheaps-0.13.jar"/>
 	<classpathentry kind="output" path="output"/>
 </classpath>

BIN
HintCOEngine/lib/jheaps-0.13.jar


+ 31 - 0
HintCOEngine/src/ua/ansymo/hintco/AlgebraicLoopVariantProcessor.xtend

@@ -2,8 +2,11 @@ package ua.ansymo.hintco
 
 import java.util.Deque
 import org.eclipse.core.runtime.Assert
+import org.jgrapht.alg.interfaces.AStarAdmissibleHeuristic
+import org.jgrapht.alg.shortestpath.AStarShortestPath
 import org.jgrapht.graph.DirectedMultigraph
 import org.jgrapht.util.SupplierUtil
+import org.jgrapht.graph.DefaultWeightedEdge
 
 class AlgebraicLoopVariantProcessor implements IVariantProcessor {
 	
@@ -52,6 +55,28 @@ class AlgebraicLoopVariantProcessor implements IVariantProcessor {
 		return resultGraph
 	}
 	
+	/*
+	 * Solves the optimization problem for a given strong component.
+	 * 1. Builds the search graph.
+	 * 2. Uses AStar algorithm (from JGraph lib) to find the optimal solution.
+	 */
+	def findOptimalTriggerSequenceStrongComponent(DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> sc) {
+		val searchGraph = new LazySearchTree(sc)
+		val alg = new AStarShortestPath(searchGraph, new BasicHeuristic())
+		alg.getPath(searchGraph.root, searchGraph.bottom)
+	}
+	
+	/*
+	 * Solves the optimization problem for each strong component in the given graph.
+	 * 1. Compute strong components.
+	 * 2. For each component SC:
+	 * 2.1 Find the optimal trigger sequence
+	 * 3. Join the trigger sequences found for each component, and return
+	 */
+	def findOptimalTriggerSequence(DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> graph) {
+		// TODO
+	}
+	
 	/*
 	 * This method will perform roughly the following steps:
 	 * 1. Transform the scenario (assumed to be non-hierarchical) into a graph where each node represents a cosim unit, and each connection between cosim units represents the cost of executing the latter after executing the former.
@@ -70,4 +95,10 @@ class AlgebraicLoopVariantProcessor implements IVariantProcessor {
 		
 	}
 	
+}
+
+class BasicHeuristic implements AStarAdmissibleHeuristic<LazySearchNode> {
+	override getCostEstimate(LazySearchNode sourceVertex, LazySearchNode targetVertex) {
+		0.0 // TODO Better heuristics might be to get the min weight of possible edges
+	}
 }

+ 127 - 0
HintCOEngine/src/ua/ansymo/hintco/LazySearchTree.xtend

@@ -0,0 +1,127 @@
+package ua.ansymo.hintco
+
+import org.eclipse.core.runtime.Assert
+import org.jgrapht.graph.DefaultWeightedEdge
+import org.jgrapht.graph.DirectedMultigraph
+import org.jgrapht.graph.SimpleDirectedWeightedGraph
+import org.jgrapht.util.SupplierUtil
+import org.eclipse.xtend.lib.annotations.Accessors
+
+/*
+ * Represents the search space used in the algebraic loop optimizer. Lazily expands the graph as the search algorithm works it's way towards the optimal solution.
+ */
+class LazySearchTree extends SimpleDirectedWeightedGraph<LazySearchNode, DefaultWeightedEdge> {
+	
+	DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> triggerGraph
+	
+	@Accessors(PUBLIC_GETTER, NONE) EmptyTriggerSequence root = new EmptyTriggerSequence
+	@Accessors(PUBLIC_GETTER, NONE) CompleteTriggerSequence bottom = new CompleteTriggerSequence
+	
+	
+	new(DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> tg) {
+		super(SupplierUtil.createSupplier(LazySearchNode), SupplierUtil.createSupplier(DefaultWeightedEdge))
+		Assert.isTrue(!tg.vertexSet.empty)
+		triggerGraph = tg
+		// Define the default start and end nodes.
+		this.addVertex(root)
+		this.addVertex(bottom)
+	}
+	
+	/*
+	 * Computes the edges and vertices that are outgoing from vertex, and adds them to the graph.
+	 * It considers three kinds of LazySearchNode: the EmptyTriggerSequence, the PartialTriggerSequence, and the CompleteTriggerSequence.
+	 * In the case of EmptyTriggerSequence, then the successors are all possible vertices on the graph.
+	 * In the case of PartialTriggerSequence, then the successors are all possible vertices on the graph, except for those that are already part of the trigger sequence (this is naive, see below).
+	 * In the case of CompleteTriggerSequence, then there are no successors, and an exception should be thrown.
+	 * I think it is possible to reduce the successors in the case of a PartialTriggerSequence. Essentially, one would expect the optimal trigger sequence to be a path in the triggerGraph. 
+	 * This is because whenever we pick a cosim unit A, any cosim units that depend on A have their cost reduced, so it would make sense that the optimal choice will be among them. 
+	 * However, for cosim scenarios that results in non strongly connected graphs, one may be forced to chose a successor which is not a successor of A in triggerGraph.
+	 * Therefore I'm going for the most naive approach to just enum all, and let A* select the best choice.
+	 */
+	override outgoingEdgesOf(LazySearchNode vertex) {
+		if (vertex instanceof EmptyTriggerSequence) {
+			Assert.isTrue(vertex.inDegreeOf == 0, "This vertex should be the root of the search tree.")
+			for (v : triggerGraph.vertexSet) {
+				createNewEdge(vertex, v)
+			}
+		} else if (vertex instanceof CompleteTriggerSequence) {
+			throw new IllegalArgumentException()
+		} else if (vertex instanceof PartialTriggerSequence) {
+			val potentialSuccessors = triggerGraph.vertexSet.filter[v | !recIsAncestorOf(v, vertex)]
+			if (potentialSuccessors.empty) {
+				val edge = this.addEdge(vertex, bottom)
+				Assert.isNotNull(edge) // Otherwise, this vertex has been expanded before, the solution found, and then the search problem got back here.
+				this.setEdgeWeight(edge, 0.0) // No cost for the edge that reaches bottom
+			} else {
+				for (v : potentialSuccessors) {
+					createNewEdge(vertex, v)
+				}
+			}
+			
+		}
+		return super.outgoingEdgesOf(vertex)
+	}
+	
+	private def createNewEdge(LazySearchNode vertex, CosimUnitInstance v) {
+		val newV = new PartialTriggerSequence(v)
+		val newEdge = this.addEdge(vertex, newV)
+		this.setEdgeWeight(newEdge, costOfEdge(newV))
+	}
+	
+	def boolean recIsAncestorOf(CosimUnitInstance ancestorUnit, PartialTriggerSequence vertex) {
+		// Base case
+		val predecessors = vertex.incomingEdgesOf
+		Assert.isTrue(!predecessors.empty)
+		if (predecessors.size == 1) {
+			Assert.isTrue(predecessors.head.edgeSource instanceof EmptyTriggerSequence)
+			return false
+		}
+		for (p : predecessors) {
+			val partialP = p.edgeSource as PartialTriggerSequence
+			if (partialP.unit == ancestorUnit) {
+				return true
+			}
+			if (recIsAncestorOf(ancestorUnit, partialP)){
+				return true
+			}
+		}
+		return false
+	}
+	
+	/*
+	 * Defines the cost of an edge A->B. 
+	 * This is given by the sum of the weights of incoming connections to B (in the triggerGraph) that are not ancestors of B in the search tree.
+	 */
+	def costOfEdge(PartialTriggerSequence B){
+		val Bunit = B.unit
+		val incommingEdges = triggerGraph.incomingEdgesOf(Bunit).filter[inc | recIsAncestorOf(triggerGraph.getEdgeSource(inc), B)]
+		val cost = incommingEdges.map[edge|triggerGraph.getEdgeWeight(edge)].reduce[p1, p2|p1+p2]
+		return cost
+	}
+}
+
+abstract class LazySearchNode {
+	
+}
+
+/*
+ * Represents a partial trigger sequence of a graph.
+ * 
+ */
+class PartialTriggerSequence extends LazySearchNode {
+	@Accessors(PUBLIC_GETTER, PROTECTED_SETTER) CosimUnitInstance unit
+	new (CosimUnitInstance u){
+		unit = u	
+	}
+	override toString() {
+		unit.toString
+	}
+}
+
+/*
+ * The following two classes are used to identify the start and end of the search
+ */
+class EmptyTriggerSequence extends LazySearchNode {}
+class CompleteTriggerSequence extends LazySearchNode {}
+
+