LazySearchTree.xtend 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package ua.ansymo.hintco
  2. import org.eclipse.core.runtime.Assert
  3. import org.jgrapht.graph.DefaultWeightedEdge
  4. import org.jgrapht.graph.DirectedMultigraph
  5. import org.jgrapht.graph.SimpleDirectedWeightedGraph
  6. import org.jgrapht.util.SupplierUtil
  7. import org.eclipse.xtend.lib.annotations.Accessors
  8. /*
  9. * 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.
  10. */
  11. class LazySearchTree extends SimpleDirectedWeightedGraph<LazySearchNode, DefaultWeightedEdge> {
  12. DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> triggerGraph
  13. @Accessors(PUBLIC_GETTER, NONE) EmptyTriggerSequence root = new EmptyTriggerSequence
  14. @Accessors(PUBLIC_GETTER, NONE) CompleteTriggerSequence bottom = new CompleteTriggerSequence
  15. new(DirectedMultigraph<CosimUnitInstance, DefaultWeightedEdge> tg) {
  16. super(SupplierUtil.createSupplier(LazySearchNode), SupplierUtil.createSupplier(DefaultWeightedEdge))
  17. Assert.isTrue(!tg.vertexSet.empty)
  18. triggerGraph = tg
  19. // Define the default start and end nodes.
  20. this.addVertex(root)
  21. this.addVertex(bottom)
  22. }
  23. /*
  24. * Computes the edges and vertices that are outgoing from vertex, and adds them to the graph.
  25. * It considers three kinds of LazySearchNode: the EmptyTriggerSequence, the PartialTriggerSequence, and the CompleteTriggerSequence.
  26. * In the case of EmptyTriggerSequence, then the successors are all possible vertices on the graph.
  27. * 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).
  28. * In the case of CompleteTriggerSequence, then there are no successors, and an exception should be thrown.
  29. * 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.
  30. * 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.
  31. * 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.
  32. * Therefore I'm going for the most naive approach to just enum all, and let A* select the best choice.
  33. */
  34. override outgoingEdgesOf(LazySearchNode vertex) {
  35. if (vertex instanceof EmptyTriggerSequence) {
  36. Assert.isTrue(vertex.inDegreeOf == 0, "This vertex should be the root of the search tree.")
  37. for (v : triggerGraph.vertexSet) {
  38. createNewEdge(vertex, v)
  39. }
  40. } else if (vertex instanceof CompleteTriggerSequence) {
  41. throw new IllegalArgumentException()
  42. } else if (vertex instanceof PartialTriggerSequence) {
  43. val potentialSuccessors = triggerGraph.vertexSet.filter[v | !recIsAncestorOf(v, vertex)]
  44. if (potentialSuccessors.empty) {
  45. val edge = this.addEdge(vertex, bottom)
  46. Assert.isNotNull(edge) // Otherwise, this vertex has been expanded before, the solution found, and then the search problem got back here.
  47. this.setEdgeWeight(edge, 0.0) // No cost for the edge that reaches bottom
  48. } else {
  49. for (v : potentialSuccessors) {
  50. createNewEdge(vertex, v)
  51. }
  52. }
  53. }
  54. return super.outgoingEdgesOf(vertex)
  55. }
  56. private def createNewEdge(LazySearchNode vertex, CosimUnitInstance v) {
  57. val newV = new PartialTriggerSequence(v)
  58. val newEdge = this.addEdge(vertex, newV)
  59. this.setEdgeWeight(newEdge, costOfEdge(newV))
  60. }
  61. def boolean recIsAncestorOf(CosimUnitInstance ancestorUnit, PartialTriggerSequence vertex) {
  62. // Base case
  63. val predecessors = vertex.incomingEdgesOf
  64. Assert.isTrue(!predecessors.empty)
  65. if (predecessors.size == 1) {
  66. Assert.isTrue(predecessors.head.edgeSource instanceof EmptyTriggerSequence)
  67. return false
  68. }
  69. for (p : predecessors) {
  70. val partialP = p.edgeSource as PartialTriggerSequence
  71. if (partialP.unit == ancestorUnit) {
  72. return true
  73. }
  74. if (recIsAncestorOf(ancestorUnit, partialP)){
  75. return true
  76. }
  77. }
  78. return false
  79. }
  80. /*
  81. * Defines the cost of an edge A->B.
  82. * 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.
  83. */
  84. def costOfEdge(PartialTriggerSequence B){
  85. val Bunit = B.unit
  86. val incommingEdges = triggerGraph.incomingEdgesOf(Bunit).filter[inc | recIsAncestorOf(triggerGraph.getEdgeSource(inc), B)]
  87. val cost = incommingEdges.map[edge|triggerGraph.getEdgeWeight(edge)].reduce[p1, p2|p1+p2]
  88. return cost
  89. }
  90. }
  91. abstract class LazySearchNode {
  92. }
  93. /*
  94. * Represents a partial trigger sequence of a graph.
  95. *
  96. */
  97. class PartialTriggerSequence extends LazySearchNode {
  98. @Accessors(PUBLIC_GETTER, PROTECTED_SETTER) CosimUnitInstance unit
  99. new (CosimUnitInstance u){
  100. unit = u
  101. }
  102. override toString() {
  103. unit.toString
  104. }
  105. }
  106. /*
  107. * The following two classes are used to identify the start and end of the search
  108. */
  109. class EmptyTriggerSequence extends LazySearchNode {}
  110. class CompleteTriggerSequence extends LazySearchNode {}