/* * Copyright 2025 Casper Dalgaard Nielsen * Adam Yasser Tallouzi * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */////// This is based on the paper "Automatic Index Selection for Large-Scale Datalog/// Computation" by Pavle Subotić, Herbert Jordan, Lijun Chang, Alan Fekete,/// Bernhard Scholz (https://www.vldb.org/pvldb/vol12/p141-subotic.pdf)///pubmod Fixpoint3.Phase.IndexSelection.AutomaticIndexSelection {use Fixpoint3.Ast.Ram.SearchtypealiasVertex = Int32typealiasEdge = (Vertex, Vertex)// Matching is a set but we use a `Map` here so we can call `rangeQueryWith`typealiasMatching = Map[Edge, Unit]typealiasAdjacencyList = MultiMap[Vertex, Vertex]////// Find the minimum set of indexes that covers `searches`.////// Using the example from earlier `S={{0, 1}, {0, 1, 2}, {0, 2}}` can be covered/// by `{0 < 1 < 2, 0 < 2}`. `{0, 1, 2}` and `{0, 2}` are trivially covered as they/// are explicitly in the set. `{0, 1}` is covered as the index `{0, 1, 2}` allows/// prefix searches.////// `{0 < 1 < 2}` does not cover `{0, 2}` as no permutation of `{0, 2}`/// is a prefix of `{0 < 1 < 2}`.///pubdefminIndex(searches: List[Search]): List[Search] = matchsearches {case Nil => Nilcase_ :: Nil => searchescase_ =>minChainCover(searches) |> List.map(chain -> List.zip(chain, Vector.empty() :: chain)) |> List.map(List.map(match (s1, s2) -> Vector.filter(x -> notVector.memberOf(x, s2), s1))) |> List.map(List.fold) }////// Find the maximum matching in the bipartite graph `(us, vs, edges)`.///defmaximumMatching(us: List[Vertex], vs: List[Vertex], adjList: AdjacencyList): Matching = regionrc {letmatching = Array.repeat(rc, List.length(vs), -1);letmatchingRev = Array.empty(rc, List.length(us));defgetMatching(u: Vertex, seen: Array[Bool, rc]): Bool \ rc = {letcond = v ->if (notArray.get(v, seen)) {Array.put(true, v, seen);if (Array.get(v, matching) <0orgetMatching(Array.get(v, matching), seen)) {Array.put(u, v, matching);Array.put(v, u, matchingRev);true } elsefalse } elsefalse;Set.exists(cond, MultiMap.get(u, adjList)) };List.filter(u -> getMatching(u, Array.empty(rc, List.length(us))), us) |> List.foldLeft(acc -> u -> Map.insert((u, Array.get(u, matchingRev)), (), acc), Map.empty()) }////// Find the maximal path starting from the vertex `from_` in `matching`.///defmaximalPath(from_: Vertex, matching: Matching): List[Edge] = regionrc {letallPaths = MutMap.empty(rc);deff(u: Vertex): Unit \ rc = matchMutMap.get(u, allPaths) {case None => Map.rangeQueryWith(edge -> fst(edge) <=>u,edge -> _ -> {let (u0, v0) = edge;f(v0); // Compute the longest path of v0 and store in allPathsletlargest = l1 -> l2 -> if (List.length(l1) <=List.length(l2)) l2elsel1;MutMap.putWith(largest,u0,edge :: Option.getWithDefault(List.empty(), MutMap.get(v0, allPaths)),allPaths ) },matching )case_ => () };f(from_);matchMutMap.get(from_, allPaths) {case Some(x) => xcase None => List.empty() } }////// Create a graph from the list of searches `searches`.///defgraphFromSearches(searches: List[Search]): (List[Vertex], AdjacencyList) =// The vertices are are simply the indices of the searches in the listletvertices = List.range(0, List.length(searches));letzipped = searches |> List.map(Vector.toSet) |> List.zip(vertices);letadjList = zipped |> List.foldMap(match (u, s) ->List.filter(p -> Set.isProperSubsetOf(s, snd(p)), zipped) |> List.map(p -> (u, fst(p))) ) |> List.foldLeft(a -> match (u, v) -> MultiMap.insert(u, v, a), MultiMap.empty()); (vertices, adjList)////// Find the minimum chain cover of `searches`.///defminChainCover(searches: List[Search]): List[List[Search]] =let (vertices, adjList) = graphFromSearches(searches);letsearchesVec = List.toVector(searches);letmatching = maximumMatching(vertices, vertices, adjList);lettoSearch = i -> Vector.get(i, searchesVec);List.filter(v -> notMap.exists(edge -> _ -> snd(edge) ==v, matching), vertices) |> List.foldLeft(acc -> u1 -> {letchain = toSearch(u1) :: List.map(e -> toSearch(snd(e)), maximalPath(u1, matching));chain :: acc },List.empty() )}