souffle  2.0.2-371-g6315b36
MinimiseProgram.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 MinimiseProgram.cpp
12  *
13  * Define classes and functionality related to program minimisation.
14  *
15  ***********************************************************************/
16 
18 #include "ast/Atom.h"
19 #include "ast/Clause.h"
20 #include "ast/Literal.h"
21 #include "ast/Node.h"
22 #include "ast/Program.h"
23 #include "ast/QualifiedName.h"
24 #include "ast/Relation.h"
25 #include "ast/TranslationUnit.h"
27 #include "ast/analysis/IOType.h"
28 #include "ast/utility/NodeMapper.h"
29 #include "ast/utility/Utils.h"
32 #include <algorithm>
33 #include <cassert>
34 #include <cstddef>
35 #include <map>
36 #include <memory>
37 #include <set>
38 #include <stack>
39 #include <string>
40 #include <utility>
41 #include <vector>
42 
43 namespace souffle::ast::transform {
44 
45 using namespace analysis;
46 
47 bool MinimiseProgramTransformer::existsValidPermutation(const NormalisedClause& left,
48  const NormalisedClause& right, const std::vector<std::vector<unsigned int>>& permutationMatrix) {
49  size_t clauseSize = permutationMatrix.size();
50  // keep track of the possible end-positions of each atom in the first clause
51  std::vector<std::vector<unsigned int>> validMoves;
52  for (size_t i = 0; i < clauseSize; i++) {
53  std::vector<unsigned int> currentRow;
54  for (size_t j = 0; j < clauseSize; j++) {
55  if (permutationMatrix[i][j] == 1) {
56  currentRow.push_back(j);
57  }
58  }
59  validMoves.push_back(currentRow);
60  }
61 
62  // extract the possible permutations, DFS style
63  std::vector<unsigned int> seen(clauseSize);
64  std::vector<unsigned int> currentPermutation;
65  std::stack<std::vector<unsigned int>> todoStack;
66 
67  todoStack.push(validMoves[0]);
68 
69  size_t currentIdx = 0;
70  while (!todoStack.empty()) {
71  if (currentIdx == clauseSize) {
72  // permutation is complete, check if it's valid
73  if (isValidPermutation(left, right, currentPermutation)) {
74  // valid permutation with valid mapping
75  // therefore, the two clauses are equivalent!
76  return true;
77  }
78 
79  // Not valid yet, keep checking for more permutations
80  if (currentIdx == 0) {
81  // already at starting position, so no more permutations possible
82  break;
83  }
84 
85  // undo the last number added to the permutation
86  currentIdx--;
87  seen[currentPermutation[currentIdx]] = 0;
88  currentPermutation.pop_back();
89 
90  // see if we can pick up other permutations
91  continue;
92  }
93 
94  // pull out the possibilities for the current point of the permutation
95  std::vector<unsigned int> possibilities = todoStack.top();
96  todoStack.pop();
97  if (possibilities.empty()) {
98  // no more possibilities at this point, so undo our last move
99 
100  if (currentIdx == 0) {
101  // already at starting position, so no more permutations possible
102  break;
103  }
104 
105  currentIdx--;
106  seen[currentPermutation[currentIdx]] = 0;
107  currentPermutation.pop_back();
108 
109  // continue looking for permutations
110  continue;
111  }
112 
113  // try the next possibility
114  unsigned int nextNum = possibilities[0];
115 
116  // update the possibility vector for the current position
117  possibilities.erase(possibilities.begin());
118  todoStack.push(possibilities);
119 
120  if (seen[nextNum] != 0u) {
121  // number already seen in this permutation
122  continue;
123  } else {
124  // number can be used
125  seen[nextNum] = 1;
126  currentPermutation.push_back(nextNum);
127  currentIdx++;
128 
129  // if we havent reached the end of the permutation,
130  // push up the valid moves for the next position
131  if (currentIdx < clauseSize) {
132  todoStack.push(validMoves[currentIdx]);
133  }
134  }
135  }
136 
137  // checked all permutations, none were valid
138  return false;
139 }
140 
141 bool MinimiseProgramTransformer::isValidPermutation(const NormalisedClause& left,
142  const NormalisedClause& right, const std::vector<unsigned int>& permutation) {
143  const auto& leftElements = left.getElements();
144  const auto& rightElements = right.getElements();
145 
146  assert(leftElements.size() == rightElements.size() && "clauses should have equal size");
147  size_t size = leftElements.size();
148 
149  std::map<std::string, std::string> variableMap;
150 
151  // Constants should be fixed to the identically-named constant
152  for (const auto& cst : left.getConstants()) {
153  variableMap[cst] = cst;
154  }
155 
156  // Variables start off mapping to nothing
157  for (const auto& var : left.getVariables()) {
158  variableMap[var] = "";
159  }
160 
161  // Pass through the all arguments in the first clause in sequence, mapping each to the corresponding
162  // argument in the second clause under the literal permutation
163  for (size_t i = 0; i < size; i++) {
164  const auto& leftArgs = leftElements[i].params;
165  const auto& rightArgs = rightElements[permutation[i]].params;
166  for (size_t j = 0; j < leftArgs.size(); j++) {
167  auto leftArg = leftArgs[j];
168  auto rightArg = rightArgs[j];
169  std::string currentMap = variableMap[leftArg];
170  if (currentMap.empty()) {
171  // unassigned yet, so assign it appropriately
172  variableMap[leftArg] = rightArg;
173  } else if (currentMap != rightArg) {
174  // inconsistent mapping!
175  // clauses cannot be equivalent under this permutation
176  return false;
177  }
178  }
179  }
180 
181  return true;
182 }
183 
185  const NormalisedClause& left, const NormalisedClause& right) {
186  const auto& leftElements = left.getElements();
187  const auto& rightElements = right.getElements();
188 
189  const auto& leftVars = left.getVariables();
190  const auto& rightVars = right.getVariables();
191 
192  // rules must be fully normalised
193  if (!left.isFullyNormalised() || !right.isFullyNormalised()) {
194  return false;
195  }
196 
197  // rules must be the same length to be equal
198  if (leftElements.size() != rightElements.size()) {
199  return false;
200  }
201 
202  // head atoms must have the same arity (names do not matter)
203  if (leftElements[0].params.size() != rightElements[0].params.size()) {
204  return false;
205  }
206 
207  // rules must have the same number of distinct variables
208  if (leftVars.size() != rightVars.size()) {
209  return false;
210  }
211 
212  // rules must have the exact same set of constants
213  if (left.getConstants() != right.getConstants()) {
214  return false;
215  }
216 
217  // set up the n x n permutation matrix, where n is the number of clause elements
218  size_t size = leftElements.size();
219  auto permutationMatrix = std::vector<std::vector<unsigned int>>(size);
220  for (auto& i : permutationMatrix) {
221  i = std::vector<unsigned int>(size);
222  }
223 
224  // create permutation matrix
225  for (size_t i = 0; i < size; i++) {
226  for (size_t j = 0; j < size; j++) {
227  if (leftElements[i].name == rightElements[j].name) {
228  permutationMatrix[i][j] = 1;
229  }
230  }
231  }
232 
233  // check if any of these permutations have valid variable mappings
234  return existsValidPermutation(left, right, permutationMatrix);
235 }
236 
237 bool MinimiseProgramTransformer::reduceLocallyEquivalentClauses(TranslationUnit& translationUnit) {
238  Program& program = translationUnit.getProgram();
239  const auto& normalisations = *translationUnit.getAnalysis<analysis::ClauseNormalisationAnalysis>();
240 
241  std::vector<Clause*> clausesToDelete;
242 
243  // split up each relation's rules into equivalence classes
244  // TODO (azreika): consider turning this into an ast analysis instead
245  for (Relation* rel : program.getRelations()) {
246  std::vector<std::vector<Clause*>> equivalenceClasses;
247 
248  for (Clause* clause : getClauses(program, *rel)) {
249  bool added = false;
250 
251  for (std::vector<Clause*>& eqClass : equivalenceClasses) {
252  const auto& normedRep = normalisations.getNormalisation(eqClass[0]);
253  const auto& normedClause = normalisations.getNormalisation(clause);
254  if (areBijectivelyEquivalent(normedRep, normedClause)) {
255  // clause belongs to an existing equivalence class, so delete it
256  eqClass.push_back(clause);
257  clausesToDelete.push_back(clause);
258  added = true;
259  break;
260  }
261  }
262 
263  if (!added) {
264  // clause does not belong to any existing equivalence class, so keep it
265  std::vector<Clause*> clauseToAdd = {clause};
266  equivalenceClasses.push_back(clauseToAdd);
267  }
268  }
269  }
270 
271  // remove non-representative clauses
272  for (auto clause : clausesToDelete) {
273  program.removeClause(clause);
274  }
275 
276  // changed iff any clauses were deleted
277  return !clausesToDelete.empty();
278 }
279 
280 bool MinimiseProgramTransformer::reduceSingletonRelations(TranslationUnit& translationUnit) {
281  // Note: This reduction is particularly useful in conjunction with the
282  // body-partitioning transformation
283  Program& program = translationUnit.getProgram();
284  const auto& ioTypes = *translationUnit.getAnalysis<analysis::IOTypeAnalysis>();
285  const auto& normalisations = *translationUnit.getAnalysis<analysis::ClauseNormalisationAnalysis>();
286 
287  // Find all singleton relations to consider
288  std::vector<Clause*> singletonRelationClauses;
289  for (Relation* rel : program.getRelations()) {
290  if (!ioTypes.isIO(rel) && getClauses(program, *rel).size() == 1) {
291  Clause* clause = getClauses(program, *rel)[0];
292  singletonRelationClauses.push_back(clause);
293  }
294  }
295 
296  // Keep track of clauses found to be redundant
297  std::set<const Clause*> redundantClauses;
298 
299  // Keep track of canonical relation name for each redundant clause
300  std::map<QualifiedName, QualifiedName> canonicalName;
301 
302  // Check pairwise equivalence of each singleton relation
303  for (size_t i = 0; i < singletonRelationClauses.size(); i++) {
304  const auto* first = singletonRelationClauses[i];
305  if (redundantClauses.find(first) != redundantClauses.end()) {
306  // Already found to be redundant, no need to check
307  continue;
308  }
309 
310  for (size_t j = i + 1; j < singletonRelationClauses.size(); j++) {
311  const auto* second = singletonRelationClauses[j];
312 
313  // Note: Bijective-equivalence check does not care about the head relation name
314  const auto& normedFirst = normalisations.getNormalisation(first);
315  const auto& normedSecond = normalisations.getNormalisation(second);
316  if (areBijectivelyEquivalent(normedFirst, normedSecond)) {
317  QualifiedName firstName = first->getHead()->getQualifiedName();
318  QualifiedName secondName = second->getHead()->getQualifiedName();
319  redundantClauses.insert(second);
320  canonicalName.insert(std::pair(secondName, firstName));
321  }
322  }
323  }
324 
325  // Remove redundant relation definitions
326  for (const auto* clause : redundantClauses) {
327  auto relName = clause->getHead()->getQualifiedName();
328  Relation* rel = getRelation(program, relName);
329  assert(rel != nullptr && "relation does not exist in program");
330  removeRelation(translationUnit, relName);
331  }
332 
333  // Replace each redundant relation appearance with its canonical name
334  struct replaceRedundantRelations : public NodeMapper {
335  const std::map<QualifiedName, QualifiedName>& canonicalName;
336 
337  replaceRedundantRelations(const std::map<QualifiedName, QualifiedName>& canonicalName)
338  : canonicalName(canonicalName) {}
339 
340  Own<Node> operator()(Own<Node> node) const override {
341  // Remove appearances from children nodes
342  node->apply(*this);
343 
344  if (auto* atom = dynamic_cast<Atom*>(node.get())) {
345  auto pos = canonicalName.find(atom->getQualifiedName());
346  if (pos != canonicalName.end()) {
347  auto newAtom = souffle::clone(atom);
348  newAtom->setQualifiedName(pos->second);
349  return newAtom;
350  }
351  }
352 
353  return node;
354  }
355  };
356  replaceRedundantRelations update(canonicalName);
357  program.apply(update);
358 
359  // Program was changed iff a relation was replaced
360  return !canonicalName.empty();
361 }
362 
363 bool MinimiseProgramTransformer::removeRedundantClauses(TranslationUnit& translationUnit) {
364  Program& program = translationUnit.getProgram();
365  auto isRedundant = [&](const Clause* clause) {
366  const auto* head = clause->getHead();
367  for (const auto* lit : clause->getBodyLiterals()) {
368  if (*head == *lit) {
369  return true;
370  }
371  }
372  return false;
373  };
374 
375  std::set<Own<Clause>> clausesToRemove;
376  for (const auto* clause : program.getClauses()) {
377  if (isRedundant(clause)) {
378  clausesToRemove.insert(souffle::clone(clause));
379  }
380  }
381 
382  for (auto& clause : clausesToRemove) {
383  program.removeClause(clause.get());
384  }
385  return !clausesToRemove.empty();
386 }
387 
388 bool MinimiseProgramTransformer::reduceClauseBodies(TranslationUnit& translationUnit) {
389  Program& program = translationUnit.getProgram();
390  std::set<Own<Clause>> clausesToAdd;
391  std::set<Own<Clause>> clausesToRemove;
392 
393  for (const auto* clause : program.getClauses()) {
394  auto bodyLiterals = clause->getBodyLiterals();
395  std::set<size_t> redundantPositions;
396  for (size_t i = 0; i < bodyLiterals.size(); i++) {
397  for (size_t j = 0; j < i; j++) {
398  if (*bodyLiterals[i] == *bodyLiterals[j]) {
399  redundantPositions.insert(j);
400  break;
401  }
402  }
403  }
404 
405  if (!redundantPositions.empty()) {
406  auto minimisedClause = mk<Clause>();
407  minimisedClause->setHead(souffle::clone(clause->getHead()));
408  for (size_t i = 0; i < bodyLiterals.size(); i++) {
409  if (!contains(redundantPositions, i)) {
410  minimisedClause->addToBody(souffle::clone(bodyLiterals[i]));
411  }
412  }
413  clausesToAdd.insert(std::move(minimisedClause));
414  clausesToRemove.insert(souffle::clone(clause));
415  }
416  }
417 
418  for (auto& clause : clausesToRemove) {
419  program.removeClause(clause.get());
420  }
421  for (auto& clause : clausesToAdd) {
422  program.addClause(souffle::clone(clause));
423  }
424 
425  return !clausesToAdd.empty();
426 }
427 
428 bool MinimiseProgramTransformer::transform(TranslationUnit& translationUnit) {
429  bool changed = false;
430  changed |= reduceClauseBodies(translationUnit);
431  if (changed) translationUnit.invalidateAnalyses();
432  changed |= removeRedundantClauses(translationUnit);
433  if (changed) translationUnit.invalidateAnalyses();
434  changed |= reduceLocallyEquivalentClauses(translationUnit);
435  if (changed) translationUnit.invalidateAnalyses();
436  changed |= reduceSingletonRelations(translationUnit);
437  return changed;
438 }
439 
440 } // namespace souffle::ast::transform
souffle::ast::transform::MinimiseProgramTransformer::removeRedundantClauses
static bool removeRedundantClauses(TranslationUnit &translationUnit)
Remove clauses that are only satisfied if they are already satisfied.
Definition: MinimiseProgram.cpp:369
TCB_SPAN_NAMESPACE_NAME::detail::size
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.h:198
TranslationUnit.h
souffle::ast::removeRelation
void removeRelation(TranslationUnit &tu, const QualifiedName &name)
Remove relation and all its clauses from the program.
Definition: Utils.cpp:105
IOType.h
souffle::ast::transform::MinimiseProgramTransformer::reduceClauseBodies
static bool reduceClauseBodies(TranslationUnit &translationUnit)
Remove redundant literals within a clause.
Definition: MinimiseProgram.cpp:394
souffle::contains
bool contains(const C &container, const typename C::value_type &element)
A utility to check generically whether a given element is contained in a given container.
Definition: ContainerUtil.h:75
MiscUtil.h
souffle::ast::Relation
Defines a relation with a name, attributes, qualifiers, and internal representation.
Definition: Relation.h:49
souffle::ast::transform::MinimiseProgramTransformer::transform
bool transform(TranslationUnit &translationUnit) override
Definition: MinimiseProgram.cpp:434
souffle::ast::Clause
Intermediate representation of a horn clause.
Definition: Clause.h:51
Relation.h
j
var j
Definition: htmlJsChartistMin.h:15
Utils.h
NodeMapper.h
ClauseNormalisation.h
souffle::clone
auto clone(const std::vector< A * > &xs)
Definition: ContainerUtil.h:172
i
size_t i
Definition: json11.h:663
ContainerUtil.h
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::transform::MinimiseProgramTransformer::reduceLocallyEquivalentClauses
static bool reduceLocallyEquivalentClauses(TranslationUnit &translationUnit)
– Sub-Transformations –
Definition: MinimiseProgram.cpp:243
Atom.h
Literal.h
souffle::ast::transform
Definition: Program.h:45
souffle::ast::getClauses
std::vector< Clause * > getClauses(const Program &program, const QualifiedName &relationName)
Returns a vector of clauses in the program describing the relation with the given name.
Definition: Utils.cpp:77
Node.h
souffle::ast::transform::MinimiseProgramTransformer::isValidPermutation
static bool isValidPermutation(const analysis::NormalisedClause &left, const analysis::NormalisedClause &right, const std::vector< unsigned int > &permutation)
– Bijective Equivalence Helper Methods –
Definition: MinimiseProgram.cpp:147
QualifiedName.h
souffle::ast::transform::MinimiseProgramTransformer::areBijectivelyEquivalent
static bool areBijectivelyEquivalent(const analysis::NormalisedClause &left, const analysis::NormalisedClause &right)
Definition: MinimiseProgram.cpp:190
Program.h
MinimiseProgram.h
Clause.h
souffle::ast::QualifiedName
Qualified Name class defines fully/partially qualified names to identify objects in components.
Definition: QualifiedName.h:39
rel
void rel(size_t limit, bool showLimit=true)
Definition: Tui.h:1086
souffle::ast::transform::MinimiseProgramTransformer::existsValidPermutation
static bool existsValidPermutation(const analysis::NormalisedClause &left, const analysis::NormalisedClause &right, const std::vector< std::vector< unsigned int >> &permutationMatrix)
Definition: MinimiseProgram.cpp:53
souffle::ast::transform::MinimiseProgramTransformer::reduceSingletonRelations
static bool reduceSingletonRelations(TranslationUnit &translationUnit)
Removes redundant singleton relations.
Definition: MinimiseProgram.cpp:286