souffle  2.0.2-371-g6315b36
Aggregate.cpp
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2020, 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 Ground.cpp
12  *
13  * Implements Aggregate Analysis methods to determine scope of variables
14  *
15  ***********************************************************************/
16 
17 #include "ast/analysis/Aggregate.h"
18 #include "ast/Aggregator.h"
19 #include "ast/Atom.h"
20 #include "ast/Clause.h"
21 #include "ast/Relation.h"
22 #include "ast/TranslationUnit.h"
23 #include "ast/Variable.h"
24 #include "ast/analysis/Ground.h"
25 #include "ast/utility/NodeMapper.h"
26 #include "ast/utility/Utils.h"
27 #include "ast/utility/Visitor.h"
29 
30 #include <algorithm>
31 #include <map>
32 #include <memory>
33 #include <ostream>
34 #include <set>
35 #include <string>
36 #include <utility>
37 #include <vector>
38 
40 
41 /**
42  * Computes the set of local variables
43  * in an aggregate expression.
44  * This is just the set of all variables occurring in the aggregate
45  * MINUS the injected variables
46  * MINUS the witness variables. :)
47  *
48  **/
49 std::set<std::string> getLocalVariables(
50  const TranslationUnit& tu, const Clause& clause, const Aggregator& aggregate) {
51  std::set<std::string> allVariablesInAggregate;
52  visitDepthFirst(aggregate, [&](const Variable& v) { allVariablesInAggregate.insert(v.getName()); });
53  std::set<std::string> injectedVariables = getInjectedVariables(tu, clause, aggregate);
54  std::set<std::string> witnessVariables = getWitnessVariables(tu, clause, aggregate);
55  std::set<std::string> localVariables;
56  for (const std::string& name : allVariablesInAggregate) {
57  if (injectedVariables.find(name) == injectedVariables.end() &&
58  witnessVariables.find(name) == witnessVariables.end()) {
59  localVariables.insert(name);
60  }
61  }
62  return localVariables;
63 }
64 /**
65  * Computes the set of witness variables that are used in the aggregate
66  * A variable is a witness if it occurs in the aggregate body (but not in an inner aggregate)
67  * and also occurs ungrounded in the outer scope.
68  **/
69 std::set<std::string> getWitnessVariables(
70  const TranslationUnit& tu, const Clause& clause, const Aggregator& aggregate) {
71  // 1. Create an aggregatorless clause so that we can analyse the groundings
72  // in the rule scope. This is one where we replace every aggregator with
73  // a variable, and then provide a grounding for that variable.
74  struct M : public NodeMapper {
75  // Variables introduced to replace aggregators
76  mutable std::set<std::string> aggregatorVariables;
77 
78  const std::set<std::string>& getAggregatorVariables() {
79  return aggregatorVariables;
80  }
81 
82  std::unique_ptr<Node> operator()(std::unique_ptr<Node> node) const override {
83  static int numReplaced = 0;
84  if (dynamic_cast<Aggregator*>(node.get()) != nullptr) {
85  // Replace the aggregator with a variable
86  std::stringstream newVariableName;
87  newVariableName << "+aggr_var_" << numReplaced++;
88 
89  // Keep track of which variables are bound to aggregators
90  aggregatorVariables.insert(newVariableName.str());
91 
92  return std::make_unique<Variable>(newVariableName.str());
93  }
94  node->apply(*this);
95  return node;
96  }
97  };
98 
99  auto aggregatorlessClause = std::make_unique<Clause>();
100  aggregatorlessClause->setHead(std::make_unique<Atom>("*"));
101  for (Literal* lit : clause.getBodyLiterals()) {
102  aggregatorlessClause->addToBody(souffle::clone(lit));
103  }
104 
105  auto negatedHead = std::make_unique<Negation>(souffle::clone(clause.getHead()));
106  aggregatorlessClause->addToBody(std::move(negatedHead));
107 
108  // Replace all aggregates with variables
109  M update;
110  aggregatorlessClause->apply(update);
111  auto groundingAtom = std::make_unique<Atom>("+grounding_atom");
112  for (std::string variableName : update.getAggregatorVariables()) {
113  groundingAtom->addArgument(std::make_unique<Variable>(variableName));
114  }
115  aggregatorlessClause->addToBody(std::move(groundingAtom));
116  // 2. Create an aggregate clause so that we can check
117  // that it IS this aggregate giving a grounding to the candidate variable.
118  auto aggregateSubclause = std::make_unique<Clause>();
119  aggregateSubclause->setHead(mk<Atom>("*"));
120  for (const auto& lit : aggregate.getBodyLiterals()) {
121  aggregateSubclause->addToBody(souffle::clone(lit));
122  }
123 
124  std::set<std::string> witnessVariables;
125  auto isGroundedInAggregateSubclause = analysis::getGroundedTerms(tu, *aggregateSubclause);
126  // 3. Calculate all the witness variables
127  // A witness will occur ungrounded in the aggregatorlessClause
128  for (const auto& argPair : analysis::getGroundedTerms(tu, *aggregatorlessClause)) {
129  if (const auto* variable = dynamic_cast<const Variable*>(argPair.first)) {
130  bool variableIsGrounded = argPair.second;
131  if (!variableIsGrounded) {
132  // then we expect it to be grounded in the aggregate subclause
133  // if it's a witness!!
134  for (const auto& aggArgPair : isGroundedInAggregateSubclause) {
135  if (const auto* var = dynamic_cast<const Variable*>(aggArgPair.first)) {
136  bool aggVariableIsGrounded = aggArgPair.second;
137  if (var->getName() == variable->getName() && aggVariableIsGrounded) {
138  witnessVariables.insert(variable->getName());
139  }
140  }
141  }
142  }
143  }
144  }
145  // 4. A witness variable may actually "originate" from an outer scope and
146  // just have been injected into this inner aggregate. Just check the set of injected variables
147  // and quickly minus them out.
148  std::set<std::string> injectedVariables = analysis::getInjectedVariables(tu, clause, aggregate);
149  for (const std::string& injected : injectedVariables) {
150  witnessVariables.erase(injected);
151  }
152 
153  return witnessVariables;
154 }
155 /**
156  * Computes the set of variables occurring outside the aggregate
157  **/
158 std::set<std::string> getVariablesOutsideAggregate(const Clause& clause, const Aggregator& aggregate) {
159  std::map<std::string, int> variableOccurrences;
160  visitDepthFirst(clause, [&](const Variable& var) { variableOccurrences[var.getName()]++; });
161  visitDepthFirst(aggregate, [&](const Variable& var) { variableOccurrences[var.getName()]--; });
162  std::set<std::string> variablesOutsideAggregate;
163  for (auto const& pair : variableOccurrences) {
164  std::string v = pair.first;
165  int numOccurrences = pair.second;
166  if (numOccurrences > 0) {
167  variablesOutsideAggregate.insert(v);
168  }
169  }
170  return variablesOutsideAggregate;
171 }
172 
173 std::string findUniqueVariableName(const Clause& clause, std::string base) {
174  std::set<std::string> variablesInClause;
175  visitDepthFirst(clause, [&](const Variable& v) { variablesInClause.insert(v.getName()); });
176  int varNum = 0;
177  std::string candidate = base;
178  while (variablesInClause.find(candidate) != variablesInClause.end()) {
179  candidate = base + toString(varNum++);
180  }
181  return candidate;
182 }
183 
184 std::string findUniqueRelationName(const Program& program, std::string base) {
185  int counter = 0;
186  auto candidate = base;
187  while (getRelation(program, candidate) != nullptr) {
188  candidate = base + toString(counter++);
189  }
190  return candidate;
191 }
192 
193 /**
194  * Given an aggregate and a clause, we find all the variables that have been
195  * injected into the aggregate.
196  * This means that the variable occurs grounded in an outer scope.
197  * BUT does not occur in the target expression.
198  **/
199 std::set<std::string> getInjectedVariables(
200  const TranslationUnit& tu, const Clause& clause, const Aggregator& aggregate) {
201  /**
202  * Imagine we have this:
203  *
204  * A(letter, x) :-
205  * a(letter, _),
206  * x = min y : { a(l, y),
207  * y < max y : { a(letter, y) } },
208  * z = max y : {B(y) }.
209  *
210  * We need to figure out the base literal in which this aggregate occurs,
211  * so that we don't delete them
212  * Other aggregates occurring in the rule are irrelevant, so we'll replace
213  * the aggregate with a dummy aggregate variable. Then after that,
214  *
215  * 2. A(letter, x) :-
216  * a(letter, _),
217  * x = min y : { a(l, y),
218  * y < max y : { a(letter, y) } },
219  * z = aggr_var_0.
220  *
221  * We should also ground all the aggr_vars so that this grounding
222  * transfers to whatever variable they might be assigned to.
223  *
224  * A(letter, x) :-
225  * a(letter, _),
226  * x = min y : { a(l, y),
227  * y < max y : { a(letter, y) } },
228  * z = aggr_var_0,
229  * grounding_atom(aggr_var_0).
230  *
231  * Remember to negate the head atom and add it to the body. The clause we deal with will have a
232  * dummy head (*())
233  *
234  * *() :- !A(letter, x), a(letter, _), x = min y : { a(l, y), y < max y : { a(letter, y) } },
235  * z = aggr_var_0, grounding_atom(aggr_var_0).
236  *
237  * Replace the original aggregate with the same shit because we can't be counting local variables n shit
238  *
239  * *() :- !A(letter, x), a(letter, _), x = min y : { a(l, y), y < aggr_var_1 } }, z = aggr_var_0,
240  * grounding_atom(aggr_var_0).
241  *
242  * Now, as long as we recorded all of the variables that occurred inside the aggregate, the intersection
243  * of these two sets (variables in an outer scope) and (variables occurring in t
244  **/
245 
246  /**
247  * A variable is considered *injected* into an aggregate if it occurs both within the aggregate AS WELL
248  * AS *grounded* in the outer scope. An outer scope could be an outer aggregate, or also just the
249  *base/rule scope. (i.e. not inside of another aggregate that is not an ancestor of this one).
250  *
251  * So then in order to calculate the set of variables that have been injected into an aggregate, we
252  * perform the following steps:
253  *
254  * 1. Find the set of variables occurring within the aggregate "aggregate"
255  * 2. Find the set of variables that occur grounded in the outside scope
256  * 2a. Replace all non-ancestral aggregates with variables so that we can ignore their variable
257  * contents (it is not relevant). 2b. Replace the target aggregate with a variable as well 2c. Ground all
258  * occurrences of the non-ancestral aggregate variable replacements (i.e. grounding_atom(+aggr_var_0)) 2d.
259  * Visit all variables occurring in this tweaked clause that we made from steps 2a-2c, and check if they
260  * are grounded, and also occur in the set of target aggregate variables. Then it is an injected variable.
261  **/
262  // Step 1
263  std::set<std::string> variablesInTargetAggregate;
264  visitDepthFirst(aggregate,
265  [&](const Variable& variable) { variablesInTargetAggregate.insert(variable.getName()); });
266 
267  std::set<Own<Aggregator>> ancestorAggregates;
268 
269  visitDepthFirst(clause, [&](const Aggregator& ancestor) {
270  visitDepthFirst(ancestor, [&](const Aggregator& agg) {
271  if (agg == aggregate) {
272  ancestorAggregates.insert(souffle::clone(&ancestor));
273  }
274  });
275  });
276 
277  // 1. Replace non-ancestral aggregates with variables
278  struct ReplaceAggregatesWithVariables : public NodeMapper {
279  // Variables introduced to replace aggregators
280  mutable std::set<std::string> aggregatorVariables;
281  // ancestors are never replaced so don't need to clone, but actually
282  // it will have a child that will become invalid at one point. This is a concern. Need to clone these
283  // bastards too.
284  std::set<Own<Aggregator>> ancestors;
285  // make sure you clone the agg first.
286  Own<Aggregator> targetAggregate;
287 
288  const std::set<std::string>& getAggregatorVariables() {
289  return aggregatorVariables;
290  }
291 
292  ReplaceAggregatesWithVariables(std::set<Own<Aggregator>> ancestors, Own<Aggregator> targetAggregate)
293  : ancestors(std::move(ancestors)), targetAggregate(std::move(targetAggregate)) {}
294 
295  std::unique_ptr<Node> operator()(std::unique_ptr<Node> node) const override {
296  static int numReplaced = 0;
297  if (auto* aggregate = dynamic_cast<Aggregator*>(node.get())) {
298  // If we come across an aggregate that is NOT an ancestor of
299  // the target aggregate, or that IS itself the target aggregate,
300  // we should replace it with a dummy variable.
301  bool isAncestor = false;
302  for (auto& ancestor : ancestors) {
303  if (*ancestor == *aggregate) {
304  isAncestor = true;
305  break;
306  }
307  }
308  if (!isAncestor || *aggregate == *targetAggregate) {
309  // Replace the aggregator with a variable
310  std::stringstream newVariableName;
311  newVariableName << "+aggr_var_" << numReplaced++;
312  // Keep track of which variables are bound to aggregators
313  // (only applicable to non-ancestral aggregates)
314  if (!isAncestor) {
315  // we don't want to ground the target aggregate
316  // (why?) because these variables could be injected
317  // and that is fine.
318  aggregatorVariables.insert(newVariableName.str());
319  // but we are not supposed to be judging the aggregate as grounded and injected into
320  // the aggregate, that wouldn't make sense
321  }
322  return mk<Variable>(newVariableName.str());
323  }
324  }
325  node->apply(*this);
326  return node;
327  }
328  };
329  // 2. make a clone of the clause and then apply that mapper onto it
330  auto clauseCopy = souffle::clone(&clause);
331  auto tweakedClause = mk<Clause>();
332  // put a fake head here
333  tweakedClause->setHead(mk<Atom>("*"));
334  // copy in all the old body literals
335  for (Literal* lit : clause.getBodyLiterals()) {
336  tweakedClause->addToBody(souffle::clone(lit));
337  }
338  // copy in the head as a negated atom
339  tweakedClause->addToBody(mk<Negation>(souffle::clone(clause.getHead())));
340  // copy in body literals and also add the old head as a negated atom
341  ReplaceAggregatesWithVariables update(std::move(ancestorAggregates), souffle::clone(&aggregate));
342  tweakedClause->apply(update);
343  // the update will now tell us which variables we need to ground!
344  auto groundingAtom = mk<Atom>("+grounding_atom");
345  for (std::string variableName : update.getAggregatorVariables()) {
346  groundingAtom->addArgument(mk<Variable>(variableName));
347  }
348  // add the newly created grounding atom to the body
349  tweakedClause->addToBody(std::move(groundingAtom));
350 
351  std::set<std::string> injectedVariables;
352  // Search through the tweakedClause to find groundings!
353  for (const auto& argPair : analysis::getGroundedTerms(tu, *tweakedClause)) {
354  if (const auto* variable = dynamic_cast<const Variable*>(argPair.first)) {
355  bool varIsGrounded = argPair.second;
356  if (varIsGrounded && variablesInTargetAggregate.find(variable->getName()) !=
357  variablesInTargetAggregate.end()) {
358  // then we have found an injected variable, lovely!!
359  injectedVariables.insert(variable->getName());
360  }
361  }
362  }
363  // Remove any variables that occur in the target expression of the aggregate
364  if (aggregate.getTargetExpression() != nullptr) {
366  [&](const Variable& v) { injectedVariables.erase(v.getName()); });
367  }
368 
369  return injectedVariables;
370 }
371 
372 } // namespace souffle::ast::analysis
TranslationUnit.h
souffle::ast::analysis::getLocalVariables
std::set< std::string > getLocalVariables(const TranslationUnit &tu, const Clause &clause, const Aggregator &aggregate)
Computes the set of local variables in an aggregate expression.
Definition: Aggregate.cpp:55
souffle::ast::Clause
Intermediate representation of a horn clause.
Definition: Clause.h:51
base
T & base
Definition: Reader.h:60
souffle::ast::analysis::Variable
A variable to be utilized within constraints to be handled by the constraint solver.
Definition: ConstraintSystem.h:41
Relation.h
Aggregate.h
souffle::ast::Clause::getHead
Atom * getHead() const
Return the atom that represents the head of the clause.
Definition: Clause.h:74
souffle::ast::analysis::getGroundedTerms
std::map< const Argument *, bool > getGroundedTerms(const TranslationUnit &tu, const Clause &clause)
Analyse the given clause and computes for each contained argument whether it is a grounded value or n...
Definition: Ground.cpp:278
Utils.h
NodeMapper.h
souffle::toString
const std::string & toString(const std::string &str)
A generic function converting strings into strings (trivial case).
Definition: StringUtil.h:234
Ground.h
souffle::clone
auto clone(const std::vector< A * > &xs)
Definition: ContainerUtil.h:172
souffle::ast::getRelation
Relation * getRelation(const Program &program, const QualifiedName &name)
Returns the relation with the given name in the program.
Definition: Utils.cpp:101
souffle::ast::analysis::getVariablesOutsideAggregate
std::set< std::string > getVariablesOutsideAggregate(const Clause &clause, const Aggregator &aggregate)
Computes the set of variables occurring outside the aggregate.
Definition: Aggregate.cpp:164
StringUtil.h
Atom.h
souffle::ast::TranslationUnit
Translation unit class for the translation pipeline.
Definition: TranslationUnit.h:51
souffle::ast::analysis::findUniqueRelationName
std::string findUniqueRelationName(const Program &program, std::string base)
Find a new relation name.
Definition: Aggregate.cpp:190
Aggregator.h
souffle::ast::Aggregator
Defines the aggregator class.
Definition: Aggregator.h:53
std
Definition: Brie.h:3053
souffle::ast::analysis::findUniqueVariableName
std::string findUniqueVariableName(const Clause &clause, std::string base)
Find a variable name using base to form a string like base1 Use this when you need to limit the scope...
Definition: Aggregate.cpp:179
souffle::ast::Aggregator::getTargetExpression
const Argument * getTargetExpression() const
Return target expression.
Definition: Aggregator.h:66
Visitor.h
Clause.h
souffle::ast::analysis
Definition: Aggregate.cpp:39
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::analysis::getWitnessVariables
std::set< std::string > getWitnessVariables(const TranslationUnit &tu, const Clause &clause, const Aggregator &aggregate)
Computes the set of witness variables that are used in the aggregate A variable is a witness if it oc...
Definition: Aggregate.cpp:75
souffle::ast::Clause::getBodyLiterals
std::vector< Literal * > getBodyLiterals() const
Obtains a copy of the internally maintained body literals.
Definition: Clause.h:79
Variable.h
souffle::ast::analysis::getInjectedVariables
std::set< std::string > getInjectedVariables(const TranslationUnit &tu, const Clause &clause, const Aggregator &aggregate)
Given an aggregate and a clause, we find all the variables that have been injected into the aggregate...
Definition: Aggregate.cpp:205