anthem/src/anthem/Completion.cpp
Patrick Lühne e85807accb
Fix handling of rules with multielement head
The code responsible for completing formulas made the assumption that
all head variables could be safely removed from the list of free
variables of each formula. This is only correct given the current
limitation that only rules with singleton heads are supported.

Because of this assumption, code with multiple elements in the head were
completed to an incorrect result instead of issuing an error that such
rules aren’t supported yet.

This commit improves the code by excluding only variables that are
actually replaced from the list of free variables and not all head
variables. Still, other places will need to be adjusted for full support
of rules with multiple elements in the head. For this reason, this also
adds an error message indicating that only rules with singleton heads
are supported as of now.

Finally, multiple test cases are added to check that the supported
features related to the issues outlined above are translated without
exceptions, while errors are returned when attempting to use unsupported
features.
2018-05-04 15:13:36 +02:00

230 lines
8.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <anthem/Completion.h>
#include <anthem/AST.h>
#include <anthem/ASTCopy.h>
#include <anthem/ASTUtils.h>
#include <anthem/ASTVisitors.h>
#include <anthem/Exception.h>
#include <anthem/HiddenPredicateElimination.h>
#include <anthem/Utils.h>
namespace anthem
{
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Completion
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// Builds the conjunction within the completed formula for a given predicate
ast::Formula buildCompletedFormulaDisjunction(const ast::Predicate &predicate, const ast::VariableDeclarationPointers &parameters, std::vector<ast::ScopedFormula> &scopedFormulas)
{
auto disjunction = ast::Formula::make<ast::Or>();
assert(predicate.arguments.size() == parameters.size());
// Build the disjunction of all formulas with the predicate as consequent
for (auto &scopedFormula : scopedFormulas)
{
assert(scopedFormula.formula.is<ast::Implies>());
auto &implies = scopedFormula.formula.get<ast::Implies>();
if (!implies.consequent.is<ast::Predicate>())
continue;
auto &otherPredicate = implies.consequent.get<ast::Predicate>();
if (predicate.declaration != otherPredicate.declaration)
continue;
assert(otherPredicate.arguments.size() == parameters.size());
auto &freeVariables = scopedFormula.freeVariables;
// Each formula with the predicate as its consequent currently has its own copy of the predicates parameters
// These need to be linked to the new, unique set of parameters
// First, remove the free variables whose occurrences will be relinked, which is why they are no longer needed
const auto isFreeVariableUnneeded =
[&](const auto &freeVariable)
{
const auto matchesVariableToBeReplaced = std::find_if(otherPredicate.arguments.cbegin(), otherPredicate.arguments.cend(),
[&](const ast::Term &argument)
{
assert(argument.is<ast::Variable>());
const auto &otherVariable = argument.get<ast::Variable>();
return (freeVariable.get() == otherVariable.declaration);
});
return (matchesVariableToBeReplaced != otherPredicate.arguments.cend());
};
freeVariables.erase(std::remove_if(freeVariables.begin(), freeVariables.end(), isFreeVariableUnneeded), freeVariables.end());
// Currently, only rules with singleton heads are supported
// Rules with multiple elements in the head are not yet handled correctly by the head variable detection mechanism
for (const auto &freeVariable : freeVariables)
if (freeVariable->type == ast::VariableDeclaration::Type::Head)
throw CompletionException("cannot perform completion, only singleton rule heads supported currently");
// Second, link all occurrences of the deleted free variable to the new, unique parameter
for (size_t i = 0; i < parameters.size(); i++)
{
assert(otherPredicate.arguments[i].is<ast::Variable>());
const auto &otherVariable = otherPredicate.arguments[i].get<ast::Variable>();
scopedFormula.formula.accept(ast::ReplaceVariableInFormulaVisitor(), scopedFormula.formula, otherVariable.declaration, parameters[i].get());
}
if (freeVariables.empty())
disjunction.get<ast::Or>().arguments.emplace_back(std::move(implies.antecedent));
else
{
auto exists = ast::Formula::make<ast::Exists>(std::move(freeVariables), std::move(implies.antecedent));
disjunction.get<ast::Or>().arguments.emplace_back(std::move(exists));
}
}
return disjunction;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Builds the quantified inner part of the completed formula
ast::Formula buildCompletedFormulaQuantified(ast::Predicate &&predicate, ast::Formula &&innerFormula)
{
assert(innerFormula.is<ast::Or>());
if (innerFormula.get<ast::Or>().arguments.empty())
return ast::Formula::make<ast::Not>(std::move(predicate));
if (innerFormula.get<ast::Or>().arguments.size() == 1)
innerFormula = std::move(innerFormula.get<ast::Or>().arguments.front());
if (innerFormula.is<ast::Boolean>())
{
const auto &boolean = innerFormula.get<ast::Boolean>();
if (boolean.value == true)
return std::move(predicate);
else
return ast::Formula::make<ast::Not>(std::move(predicate));
}
return ast::Formula::make<ast::Biconditional>(std::move(predicate), std::move(innerFormula));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ast::Formula completePredicate(ast::PredicateDeclaration &predicateDeclaration, std::vector<ast::ScopedFormula> &scopedFormulas)
{
// Create new set of parameters for the completed definition for the predicate
ast::VariableDeclarationPointers parameters;
parameters.reserve(predicateDeclaration.arity());
std::vector<ast::Term> arguments;
arguments.reserve(predicateDeclaration.arity());
for (size_t i = 0; i < predicateDeclaration.arity(); i++)
{
parameters.emplace_back(std::make_unique<ast::VariableDeclaration>(ast::VariableDeclaration::Type::Head));
arguments.emplace_back(ast::Term::make<ast::Variable>(parameters.back().get()));
}
ast::Predicate predicateCopy(&predicateDeclaration, std::move(arguments));
auto completedFormulaDisjunction = buildCompletedFormulaDisjunction(predicateCopy, parameters, scopedFormulas);
auto completedFormulaQuantified = buildCompletedFormulaQuantified(std::move(predicateCopy), std::move(completedFormulaDisjunction));
if (parameters.empty())
return completedFormulaQuantified;
return ast::Formula::make<ast::ForAll>(std::move(parameters), std::move(completedFormulaQuantified));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ast::Formula completeIntegrityConstraint(ast::ScopedFormula &scopedFormula)
{
assert(scopedFormula.formula.is<ast::Implies>());
auto &implies = scopedFormula.formula.get<ast::Implies>();
assert(implies.consequent.is<ast::Boolean>());
assert(implies.consequent.get<ast::Boolean>().value == false);
auto argument = ast::Formula::make<ast::Not>(std::move(implies.antecedent));
auto &freeVariables = scopedFormula.freeVariables;
if (freeVariables.empty())
return argument;
return ast::Formula::make<ast::ForAll>(std::move(freeVariables), std::move(argument));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
std::vector<ast::Formula> complete(std::vector<ast::ScopedFormula> &&scopedFormulas, Context &context)
{
// Check whether formulas are in normal form
for (const auto &scopedFormula : scopedFormulas)
{
if (!scopedFormula.formula.is<ast::Implies>())
throw CompletionException("cannot perform completion, formula not in normal form");
auto &implies = scopedFormula.formula.get<ast::Implies>();
if (!implies.consequent.is<ast::Predicate>() && !implies.consequent.is<ast::Boolean>())
throw CompletionException("cannot perform completion, only single predicates and Booleans supported as formula consequent currently");
}
std::sort(context.predicateDeclarations.begin(), context.predicateDeclarations.end(),
[](const auto &lhs, const auto &rhs)
{
const auto order = std::strcmp(lhs->name.c_str(), rhs->name.c_str());
if (order != 0)
return (order < 0);
return lhs->arity() < rhs->arity();
});
std::vector<ast::Formula> completedFormulas;
// Complete predicates
for (auto &predicateDeclaration : context.predicateDeclarations)
{
if (!predicateDeclaration->isUsed || predicateDeclaration->isExternal)
continue;
completedFormulas.emplace_back(completePredicate(*predicateDeclaration, scopedFormulas));
}
// Complete integrity constraints
for (auto &scopedFormula : scopedFormulas)
{
auto &implies = scopedFormula.formula.get<ast::Implies>();
if (!implies.consequent.is<ast::Boolean>())
continue;
const auto &boolean = implies.consequent.get<ast::Boolean>();
// Rules of the form “F -> #true” are useless
if (boolean.value == true)
continue;
completedFormulas.emplace_back(completeIntegrityConstraint(scopedFormula));
}
// Eliminate all predicates that should not be visible in the output
eliminateHiddenPredicates(completedFormulas, context);
return completedFormulas;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
}