souffle  2.0.2-371-g6315b36
PartitionBodyLiterals.cpp
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2018, The Souffle Developers. All rights reserved
4  * Licensed under the Universal Permissive License v 1.0 as shown at:
5  * - https://opensource.org/licenses/UPL
6  * - <souffle root>/licenses/SOUFFLE-UPL.txt
7  */
8 
9 /************************************************************************
10  *
11  * @file PartitionBodyLiterals.cpp
12  *
13  ***********************************************************************/
14 
16 #include "GraphUtils.h"
17 #include "ast/Atom.h"
18 #include "ast/Clause.h"
19 #include "ast/Literal.h"
20 #include "ast/Program.h"
21 #include "ast/QualifiedName.h"
22 #include "ast/Relation.h"
23 #include "ast/TranslationUnit.h"
24 #include "ast/Variable.h"
25 #include "ast/utility/Visitor.h"
27 #include <algorithm>
28 #include <functional>
29 #include <memory>
30 #include <ostream>
31 #include <set>
32 #include <utility>
33 #include <vector>
34 
35 namespace souffle::ast::transform {
36 
37 bool PartitionBodyLiteralsTransformer::transform(TranslationUnit& translationUnit) {
38  bool changed = false;
39  Program& program = translationUnit.getProgram();
40 
41  /* Process:
42  * Go through each clause and construct a variable dependency graph G.
43  * The nodes of G are the variables. A path between a and b exists iff
44  * a and b appear in a common body literal.
45  *
46  * Using the graph, we can extract the body literals that are not associated
47  * with the arguments in the head atom into new relations. Depending on
48  * variable dependencies among these body literals, the literals can
49  * be partitioned into multiple separate new propositional clauses.
50  *
51  * E.g. a(x) <- b(x), c(y), d(y), e(z), f(z). can be transformed into:
52  * - a(x) <- b(x), newrel0(), newrel1().
53  * - newrel0() <- c(y), d(y).
54  * - newrel1() <- e(z), f(z).
55  *
56  * Note that only one pass through the clauses is needed:
57  * - All arguments in the body literals of the transformed clause cannot be
58  * independent of the head arguments (by construction)
59  * - The new relations holding the disconnected body literals are propositional,
60  * hence having no head arguments, and so the transformation does not apply.
61  */
62 
63  // Store clauses to add and remove after analysing the program
64  std::vector<Clause*> clausesToAdd;
65  std::vector<const Clause*> clausesToRemove;
66 
67  // The transformation is local to each rule, so can visit each independently
68  visitDepthFirst(program, [&](const Clause& clause) {
69  // Create the variable dependency graph G
70  Graph<std::string> variableGraph = Graph<std::string>();
71  std::set<std::string> ruleVariables;
72 
73  // Add in the nodes
74  // The nodes of G are the variables in the rule
75  visitDepthFirst(clause, [&](const ast::Variable& var) {
76  variableGraph.insert(var.getName());
77  ruleVariables.insert(var.getName());
78  });
79 
80  // Add in the edges
81  // Since we are only looking at reachability in the final graph, it is
82  // enough to just add in an (undirected) edge from the first variable
83  // in the literal to each of the other variables.
84  std::vector<Literal*> literalsToConsider = clause.getBodyLiterals();
85  literalsToConsider.push_back(clause.getHead());
86 
87  for (Literal* clauseLiteral : literalsToConsider) {
88  std::set<std::string> literalVariables;
89 
90  // Store all variables in the literal
91  visitDepthFirst(*clauseLiteral,
92  [&](const ast::Variable& var) { literalVariables.insert(var.getName()); });
93 
94  // No new edges if only one variable is present
95  if (literalVariables.size() > 1) {
96  std::string firstVariable = *literalVariables.begin();
97  literalVariables.erase(literalVariables.begin());
98 
99  // Create the undirected edge
100  for (const std::string& var : literalVariables) {
101  variableGraph.insert(firstVariable, var);
102  variableGraph.insert(var, firstVariable);
103  }
104  }
105  }
106 
107  // Find the connected components of G
108  std::set<std::string> seenNodes;
109 
110  // Find the connected component associated with the head
111  std::set<std::string> headComponent;
113  *clause.getHead(), [&](const ast::Variable& var) { headComponent.insert(var.getName()); });
114 
115  if (!headComponent.empty()) {
116  variableGraph.visitDepthFirst(*headComponent.begin(), [&](const std::string& var) {
117  headComponent.insert(var);
118  seenNodes.insert(var);
119  });
120  }
121 
122  // Compute all other connected components in the graph G
123  std::set<std::set<std::string>> connectedComponents;
124 
125  for (std::string var : ruleVariables) {
126  if (seenNodes.find(var) != seenNodes.end()) {
127  // Node has already been added to a connected component
128  continue;
129  }
130 
131  // Construct the connected component
132  std::set<std::string> component;
133  variableGraph.visitDepthFirst(var, [&](const std::string& child) {
134  component.insert(child);
135  seenNodes.insert(child);
136  });
137  connectedComponents.insert(component);
138  }
139 
140  if (connectedComponents.empty()) {
141  // No separate connected components, so no point partitioning
142  return;
143  }
144 
145  // Need to extract some disconnected lits!
146  changed = true;
147  std::vector<Atom*> replacementAtoms;
148 
149  // Construct the new rules
150  for (const std::set<std::string>& component : connectedComponents) {
151  // Come up with a unique new name for the relation
152  static int disconnectedCount = 0;
153  std::stringstream nextName;
154  nextName << "+disconnected" << disconnectedCount;
155  QualifiedName newRelationName = nextName.str();
156  disconnectedCount++;
157 
158  // Create the extracted relation and clause for the component
159  // newrelX() <- disconnectedLiterals(x).
160  auto newRelation = mk<Relation>();
161  newRelation->setQualifiedName(newRelationName);
162  program.addRelation(std::move(newRelation));
163 
164  auto* disconnectedClause = new Clause();
165  disconnectedClause->setSrcLoc(clause.getSrcLoc());
166  disconnectedClause->setHead(mk<Atom>(newRelationName));
167 
168  // Find the body literals for this connected component
169  std::vector<Literal*> associatedLiterals;
170  for (Literal* bodyLiteral : clause.getBodyLiterals()) {
171  bool associated = false;
172  visitDepthFirst(*bodyLiteral, [&](const ast::Variable& var) {
173  if (component.find(var.getName()) != component.end()) {
174  associated = true;
175  }
176  });
177  if (associated) {
178  disconnectedClause->addToBody(souffle::clone(bodyLiteral));
179  }
180  }
181 
182  // Create the atom to replace all these literals
183  replacementAtoms.push_back(new Atom(newRelationName));
184 
185  // Add the clause to the program
186  clausesToAdd.push_back(disconnectedClause);
187  }
188 
189  // Create the replacement clause
190  // a(x) <- b(x), c(y), d(z). --> a(x) <- newrel0(), newrel1(), b(x).
191  auto* replacementClause = new Clause();
192  replacementClause->setSrcLoc(clause.getSrcLoc());
193  replacementClause->setHead(souffle::clone(clause.getHead()));
194 
195  // Add the new propositions to the clause first
196  for (Atom* newAtom : replacementAtoms) {
197  replacementClause->addToBody(Own<Literal>(newAtom));
198  }
199 
200  // Add the remaining body literals to the clause
201  for (Literal* bodyLiteral : clause.getBodyLiterals()) {
202  bool associated = false;
203  bool hasVariables = false;
204  visitDepthFirst(*bodyLiteral, [&](const ast::Variable& var) {
205  hasVariables = true;
206  if (headComponent.find(var.getName()) != headComponent.end()) {
207  associated = true;
208  }
209  });
210  if (associated || !hasVariables) {
211  replacementClause->addToBody(souffle::clone(bodyLiteral));
212  }
213  }
214 
215  // Replace the old clause with the new one
216  clausesToRemove.push_back(&clause);
217  clausesToAdd.push_back(replacementClause);
218  });
219 
220  // Adjust the program
221  for (Clause* newClause : clausesToAdd) {
222  program.addClause(Own<Clause>(newClause));
223  }
224 
225  for (const Clause* oldClause : clausesToRemove) {
226  program.removeClause(oldClause);
227  }
228 
229  return changed;
230 }
231 
232 } // namespace souffle::ast::transform
TranslationUnit.h
souffle::Own
std::unique_ptr< A > Own
Definition: ContainerUtil.h:42
MiscUtil.h
PartitionBodyLiterals.h
souffle::ast::Clause
Intermediate representation of a horn clause.
Definition: Clause.h:51
souffle::ast::analysis::Variable
A variable to be utilized within constraints to be handled by the constraint solver.
Definition: ConstraintSystem.h:41
Relation.h
souffle::ast::Atom
An atom class.
Definition: Atom.h:51
souffle::ast::Clause::getHead
Atom * getHead() const
Return the atom that represents the head of the clause.
Definition: Clause.h:74
souffle::ast::transform::PartitionBodyLiteralsTransformer::transform
bool transform(TranslationUnit &translationUnit) override
Definition: PartitionBodyLiterals.cpp:41
souffle::clone
auto clone(const std::vector< A * > &xs)
Definition: ContainerUtil.h:172
souffle::Graph
A simple graph structure for graph-based operations.
Definition: GraphUtils.h:40
Atom.h
Literal.h
GraphUtils.h
souffle::ast::Literal
Defines an abstract class for literals in a horn clause.
Definition: Literal.h:36
souffle::ast::transform
Definition: Program.h:45
QualifiedName.h
Program.h
Visitor.h
Clause.h
souffle::ast::visitDepthFirst
void visitDepthFirst(const Node &root, Visitor< R, Ps... > &visitor, Args &... args)
A utility function visiting all nodes within the ast rooted by the given node recursively in a depth-...
Definition: Visitor.h:273
souffle::ast::Node::getSrcLoc
const SrcLocation & getSrcLoc() const
Return source location of the Node.
Definition: Node.h:46
souffle::ast::QualifiedName
Qualified Name class defines fully/partially qualified names to identify objects in components.
Definition: QualifiedName.h:39
souffle::ast::Clause::getBodyLiterals
std::vector< Literal * > getBodyLiterals() const
Obtains a copy of the internally maintained body literals.
Definition: Clause.h:79
Variable.h