Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(gravsearch): use layer info for topological order permutations (…
…DSP-1389) (#1872)

* feat (gravsearch): use layer info to make permutations

* fix (test): fix the failing test

* refactor(gravsearch): some clean up

* refactor(gravsearch): some docs
  • Loading branch information
SepidehAlassi committed Jun 9, 2021
1 parent 8831323 commit b49d5ba
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 66 deletions.
Expand Up @@ -39,42 +39,63 @@ object TopologicalSortUtil {
def findAllTopologicalOrderPermutations[T](graph: Graph[T, DiHyperEdge]): Set[Vector[Graph[T, DiHyperEdge]#NodeT]] = {
type NodeT = Graph[T, DiHyperEdge]#NodeT

def findPermutations(listOfLists: List[Vector[NodeT]]): List[Vector[NodeT]] = {
def makePermutations(next: Vector[NodeT], acc: List[Vector[NodeT]]): List[Vector[NodeT]] = {
next.permutations.toList.flatMap(i => acc.map(j => j ++ i))
}
/**
* Finds all possible topological order permutations of a graph using layer information. This method considers all
* permutations of the topological order regarding the leaf nodes.
*
* First, for each permutation of leaf nodes, find the correct order of parent nodes w.r.t incoming edges.
* For example, consider a graph that its topological order has 3 layers; i.e. layer 0 contains root nodes, and layer 2 contains leaf nodes.
* Permutations of leaf nodes consist the possible topological orders. Iterate over these set of ordered nodes to
* add nodes of lower layers to each set by considering the edges. That means for nodes of each layer, e.g. layer 1,
* find the outgoing edges to layer 2. If there is an edge for node n in layer 1 to node m in layer 2, then add this
* node to the set of order. After adding these nodes that are origins of edges, add the remaining nodes of layer 1
* to the set of order. Proceed to layer 0 and add nodes of it to the order in the same manner.
*
* @param layeredOrder the topological order of the graph with layer information i.e. (layer number, layer nodes).
* @return a list of all permutations of topological order.
*/
def findPermutations(layeredOrder: graph.LayeredTopologicalOrder[NodeT]): List[Vector[NodeT]] = {
val lastLayerNodes: Vector[NodeT] = layeredOrder.last._2.toVector
val allLowerLayers: Iterable[(Int, Iterable[NodeT])] = layeredOrder.dropRight(1)

@scala.annotation.tailrec
def makePermutationsRec(next: Vector[NodeT],
rest: List[Vector[NodeT]],
acc: List[Vector[NodeT]]): List[Vector[NodeT]] = {
if (rest.isEmpty) {
makePermutations(next, acc)
} else {
makePermutationsRec(rest.head, rest.tail, makePermutations(next, acc))
}
}
// Find all permutations of last layer nodes; i.e leaf nodes.
val permutationsOfLastLayerNodes: List[Vector[NodeT]] = lastLayerNodes.permutations.toList

listOfLists match {
case Nil => Nil
case one :: Nil => one.permutations.toList
case one :: two :: tail => makePermutationsRec(two, tail, one.permutations.toList)
// For each permutation of last layer nodes, add nodes of lower layers in correct order.
val allPermutations: List[Vector[NodeT]] = permutationsOfLastLayerNodes.map {
lastLayerPermutation: Vector[NodeT] =>
// Iterate over the previous layers to add the nodes into the order w.r.t. edges.
val orderedLowerLayerNodes: Vector[NodeT] = allLowerLayers.iterator.foldRight(lastLayerPermutation) {
(layer, acc) =>
val layerNodes: Vector[NodeT] = layer._2.toVector
// Get those nodes within a layer that are origins of outgoing edges to the nodes already in set of ordered nodes.
val origins: Set[NodeT] = acc.foldRight(Set.empty[NodeT]) { (node, originsAcc) =>
val maybeOriginNode: Option[NodeT] =
layerNodes.find(layerNode => graph.edges.contains(DiHyperEdge(layerNode, node)))
// Is there any edge which has its origin in this layer and target in already visited layers?
maybeOriginNode match {
// Yes. Add the origin node to the topological order
case Some(originNode) => Set(originNode) ++ originsAcc
// No. do nothing.
case None => originsAcc
}
}
// Find all nodes of this layer which are not origin of an edge
val notOriginNodes = layerNodes.diff(origins.toVector)
// Prepend the non-origin nodes and origin nodes to those found in higher layers.
notOriginNodes ++ origins ++ acc
}
orderedLowerLayerNodes
}
allPermutations
}

// Accumulates topological orders.
val allOrders: Set[Vector[NodeT]] = graph.topologicalSort match {
// Is there any topological order?
case Right(topOrder) =>
// Yes. Find all valid permutations.
val nodesOfLayers: List[Vector[NodeT]] =
topOrder.toLayered.iterator.foldRight(List.empty[Vector[NodeT]]) { (layer, acc) =>
val layerNodes: Vector[NodeT] = layer._2.toVector
layerNodes +: acc
}

findPermutations(nodesOfLayers).toSet

findPermutations(topOrder.toLayered).toSet
case Left(_) =>
// No, The graph has a cycle, so don't try to sort it.
Set.empty[Vector[NodeT]]
Expand Down
Expand Up @@ -2694,14 +2694,14 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
StatementPattern(
subj = QueryVariable(variableName = "thing"),
pred = IriRef(
iri = "http://www.knora.org/ontology/0001/anything#hasRichtext".toSmartIri,
iri = "http://www.knora.org/ontology/0001/anything#hasInteger".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "richtext"),
obj = QueryVariable(variableName = "int"),
namedGraph = None
),
StatementPattern(
subj = QueryVariable(variableName = "richtext"),
subj = QueryVariable(variableName = "int"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#isDeleted".toSmartIri,
propertyPathOperator = None
Expand All @@ -2716,17 +2716,30 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
propertyPathOperator = None
))
),
StatementPattern(
subj = QueryVariable(variableName = "int"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#valueHasInteger".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "int__valueHasInteger"),
namedGraph = Some(
IriRef(
iri = "http://www.knora.org/explicit".toSmartIri,
propertyPathOperator = None
))
),
StatementPattern(
subj = QueryVariable(variableName = "thing"),
pred = IriRef(
iri = "http://www.knora.org/ontology/0001/anything#hasInteger".toSmartIri,
iri = "http://www.knora.org/ontology/0001/anything#hasRichtext".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "int"),
obj = QueryVariable(variableName = "richtext"),
namedGraph = None
),
StatementPattern(
subj = QueryVariable(variableName = "int"),
subj = QueryVariable(variableName = "richtext"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#isDeleted".toSmartIri,
propertyPathOperator = None
Expand All @@ -2741,19 +2754,6 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
propertyPathOperator = None
))
),
StatementPattern(
subj = QueryVariable(variableName = "int"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#valueHasInteger".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "int__valueHasInteger"),
namedGraph = Some(
IriRef(
iri = "http://www.knora.org/explicit".toSmartIri,
propertyPathOperator = None
))
),
LuceneQueryPattern(
subj = QueryVariable(variableName = "richtext"),
obj = QueryVariable(variableName = "richtext__valueHasString"),
Expand Down Expand Up @@ -2788,14 +2788,14 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
StatementPattern(
subj = QueryVariable(variableName = "thing"),
pred = IriRef(
iri = "http://www.knora.org/ontology/0001/anything#hasText".toSmartIri,
iri = "http://www.knora.org/ontology/0001/anything#hasInteger".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "text"),
obj = QueryVariable(variableName = "int"),
namedGraph = None
),
StatementPattern(
subj = QueryVariable(variableName = "text"),
subj = QueryVariable(variableName = "int"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#isDeleted".toSmartIri,
propertyPathOperator = None
Expand All @@ -2810,17 +2810,30 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
propertyPathOperator = None
))
),
StatementPattern(
subj = QueryVariable(variableName = "int"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#valueHasInteger".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "int__valueHasInteger"),
namedGraph = Some(
IriRef(
iri = "http://www.knora.org/explicit".toSmartIri,
propertyPathOperator = None
))
),
StatementPattern(
subj = QueryVariable(variableName = "thing"),
pred = IriRef(
iri = "http://www.knora.org/ontology/0001/anything#hasInteger".toSmartIri,
iri = "http://www.knora.org/ontology/0001/anything#hasText".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "int"),
obj = QueryVariable(variableName = "text"),
namedGraph = None
),
StatementPattern(
subj = QueryVariable(variableName = "int"),
subj = QueryVariable(variableName = "text"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#isDeleted".toSmartIri,
propertyPathOperator = None
Expand All @@ -2835,19 +2848,6 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
propertyPathOperator = None
))
),
StatementPattern(
subj = QueryVariable(variableName = "int"),
pred = IriRef(
iri = "http://www.knora.org/ontology/knora-base#valueHasInteger".toSmartIri,
propertyPathOperator = None
),
obj = QueryVariable(variableName = "int__valueHasInteger"),
namedGraph = Some(
IriRef(
iri = "http://www.knora.org/explicit".toSmartIri,
propertyPathOperator = None
))
),
LuceneQueryPattern(
subj = QueryVariable(variableName = "text"),
obj = QueryVariable(variableName = "text__valueHasString"),
Expand Down
Expand Up @@ -38,7 +38,7 @@ class TopologicalSortUtilSpec extends CoreSpec() {

"TopologicalSortUtilSpec" should {

"return all topological orders of a graph" in {
"return all topological orders of a graph with one leaf" in {
val graph: Graph[Int, DiHyperEdge] =
Graph[Int, DiHyperEdge](DiHyperEdge[Int](2, 4), DiHyperEdge[Int](2, 7), DiHyperEdge[Int](4, 5))

Expand All @@ -47,13 +47,28 @@ class TopologicalSortUtilSpec extends CoreSpec() {
.findAllTopologicalOrderPermutations(graph))

val expectedOrders = Set(
Vector(2, 4, 7, 5),
Vector(2, 7, 4, 5)
)

assert(allOrders == expectedOrders)
}

"return all topological orders of a graph with multiple leaves" in {
val graph: Graph[Int, DiHyperEdge] =
Graph[Int, DiHyperEdge](DiHyperEdge[Int](2, 4), DiHyperEdge[Int](2, 7), DiHyperEdge[Int](2, 8), DiHyperEdge[Int](4, 5), DiHyperEdge[Int](7, 3))

val allOrders: Set[Vector[Int]] = nodesToValues(
TopologicalSortUtil
.findAllTopologicalOrderPermutations(graph))

val expectedOrders = Set(
Vector(2, 8, 4, 7, 5, 3),
Vector(2, 8, 7, 4, 3, 5)
)

assert(allOrders == expectedOrders)
}

"return an empty set of orders for an empty graph" in {
val graph: Graph[Int, DiHyperEdge] = Graph[Int, DiHyperEdge]()

Expand Down

0 comments on commit b49d5ba

Please sign in to comment.