Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gravsearch): use layer info for topological order permutations (DSP-1389) #1872

Merged
merged 5 commits into from Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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