From ea885f5fdbf91eacd77a1f1dedbd302c0949f129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20L=C3=BChne?= Date: Sun, 22 Apr 2018 17:04:15 +0200 Subject: [PATCH] Fix integer detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clingo treats operations that were assumed to be “invalid” not as processing errors but as operations returning an empty set. This changes how formulas have to be evaluated. This commit implements an explicit function for retrieving the return type of an expression, that is, both the domain of the result as well as whether it’s an empty, unit, or general set with multiple values. --- examples/permutations.lp | 2 +- examples/prime.lp | 2 +- examples/schur-numbers.lp | 4 +- include/anthem/Arithmetics.h | 263 ------------------------ include/anthem/Type.h | 166 +++++++++++++++ include/anthem/Utils.h | 18 ++ src/anthem/IntegerVariableDetection.cpp | 70 +++---- src/anthem/Simplification.cpp | 11 +- 8 files changed, 227 insertions(+), 309 deletions(-) delete mode 100644 include/anthem/Arithmetics.h create mode 100644 include/anthem/Type.h diff --git a/examples/permutations.lp b/examples/permutations.lp index 8b7eb5a..6f130f4 100644 --- a/examples/permutations.lp +++ b/examples/permutations.lp @@ -1,5 +1,5 @@ #show p/2. -#external integer(n(0)). +%#external integer(n(0)). {p(1..n, 1..n)}. diff --git a/examples/prime.lp b/examples/prime.lp index a75e0e8..d39a222 100644 --- a/examples/prime.lp +++ b/examples/prime.lp @@ -1,5 +1,5 @@ #show prime/1. -#external integer(n(0)). +%#external integer(n(0)). composite(I * J) :- I = 2..n, J = 2..n. prime(N) :- N = 2..n, not composite(N). diff --git a/examples/schur-numbers.lp b/examples/schur-numbers.lp index 085205a..fd0fd4f 100644 --- a/examples/schur-numbers.lp +++ b/examples/schur-numbers.lp @@ -1,6 +1,6 @@ #show in/2. -#external integer(n(0)). -#external integer(r(0)). +%#external integer(n(0)). +%#external integer(r(0)). {in(1..n, 1..r)}. covered(I) :- in(I, S). diff --git a/include/anthem/Arithmetics.h b/include/anthem/Arithmetics.h deleted file mode 100644 index 0e465b9..0000000 --- a/include/anthem/Arithmetics.h +++ /dev/null @@ -1,263 +0,0 @@ -#ifndef __ANTHEM__ARITHMETICS_H -#define __ANTHEM__ARITHMETICS_H - -#include -#include - -namespace anthem -{ - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Arithmetics -// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -struct DefaultVariableDomainAccessor -{ - Domain operator()(const ast::Variable &variable) - { - return variable.declaration->domain; - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -template -EvaluationResult isArithmetic(const ast::Term &term, Arguments &&...); - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -template -struct IsTermArithmeticVisitor -{ - template - static EvaluationResult visit(const ast::BinaryOperation &binaryOperation, Arguments &&... arguments) - { - const auto isLeftArithemtic = isArithmetic(binaryOperation.left, std::forward(arguments)...); - const auto isRightArithmetic = isArithmetic(binaryOperation.right, std::forward(arguments)...); - - if (isLeftArithemtic == EvaluationResult::Error || isRightArithmetic == EvaluationResult::Error) - return EvaluationResult::Error; - - if (isLeftArithemtic == EvaluationResult::False || isRightArithmetic == EvaluationResult::False) - return EvaluationResult::Error; - - if (isLeftArithemtic == EvaluationResult::Unknown || isRightArithmetic == EvaluationResult::Unknown) - return EvaluationResult::Unknown; - - return EvaluationResult::True; - } - - template - static EvaluationResult visit(const ast::Boolean &, Arguments &&...) - { - return EvaluationResult::False; - } - - template - static EvaluationResult visit(const ast::Function &function, Arguments &&...) - { - switch (function.declaration->domain) - { - case Domain::Noninteger: - return EvaluationResult::False; - case Domain::Integer: - return EvaluationResult::True; - case Domain::Unknown: - return EvaluationResult::Unknown; - } - - return EvaluationResult::Unknown; - } - - template - static EvaluationResult visit(const ast::Integer &, Arguments &&...) - { - return EvaluationResult::True; - } - - template - static EvaluationResult visit(const ast::Interval &interval, Arguments &&... arguments) - { - const auto isFromArithmetic = isArithmetic(interval.from, std::forward(arguments)...); - const auto isToArithmetic = isArithmetic(interval.to, std::forward(arguments)...); - - if (isFromArithmetic == EvaluationResult::Error || isToArithmetic == EvaluationResult::Error) - return EvaluationResult::Error; - - if (isFromArithmetic == EvaluationResult::False || isToArithmetic == EvaluationResult::False) - return EvaluationResult::Error; - - if (isFromArithmetic == EvaluationResult::Unknown || isToArithmetic == EvaluationResult::Unknown) - return EvaluationResult::Unknown; - - return EvaluationResult::True; - } - - template - static EvaluationResult visit(const ast::SpecialInteger &, Arguments &&...) - { - return EvaluationResult::False; - } - - template - static EvaluationResult visit(const ast::String &, Arguments &&...) - { - return EvaluationResult::False; - } - - template - static EvaluationResult visit(const ast::UnaryOperation &unaryOperation, Arguments &&... arguments) - { - const auto isArgumentArithmetic = isArithmetic(unaryOperation.argument, std::forward(arguments)...); - - switch (unaryOperation.operator_) - { - case ast::UnaryOperation::Operator::Absolute: - return (isArgumentArithmetic == EvaluationResult::False ? EvaluationResult::Error : isArgumentArithmetic); - } - - return EvaluationResult::Unknown; - } - - template - static EvaluationResult visit(const ast::Variable &variable, Arguments &&... arguments) - { - const auto domain = VariableDomainAccessor()(variable, std::forward(arguments)...); - - switch (domain) - { - case Domain::Noninteger: - return EvaluationResult::False; - case Domain::Integer: - return EvaluationResult::True; - case Domain::Unknown: - return EvaluationResult::Unknown; - } - - return EvaluationResult::Unknown; - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -template -EvaluationResult isArithmetic(const ast::Term &term, Arguments &&... arguments) -{ - return term.accept(IsTermArithmeticVisitor(), std::forward(arguments)...); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -EvaluationResult isInteger(const ast::Term &term); - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -struct IsTermIntegerVisitor -{ - static EvaluationResult visit(const ast::BinaryOperation &binaryOperation) - { - const auto isLeftArithemtic = isArithmetic(binaryOperation.left); - const auto isRightArithmetic = isArithmetic(binaryOperation.right); - - if (isLeftArithemtic == EvaluationResult::Error || isRightArithmetic == EvaluationResult::Error) - return EvaluationResult::Error; - - if (isLeftArithemtic == EvaluationResult::False || isRightArithmetic == EvaluationResult::False) - return EvaluationResult::Error; - - if (binaryOperation.operator_ == ast::BinaryOperation::Operator::Division) - return EvaluationResult::False; - - if (isLeftArithemtic == EvaluationResult::Unknown || isRightArithmetic == EvaluationResult::Unknown) - return EvaluationResult::Unknown; - - return EvaluationResult::True; - } - - static EvaluationResult visit(const ast::Boolean &) - { - return EvaluationResult::False; - } - - static EvaluationResult visit(const ast::Function &function) - { - switch (function.declaration->domain) - { - case Domain::Noninteger: - return EvaluationResult::False; - case Domain::Integer: - return EvaluationResult::True; - case Domain::Unknown: - return EvaluationResult::Unknown; - } - - return EvaluationResult::Unknown; - } - - static EvaluationResult visit(const ast::Integer &) - { - return EvaluationResult::True; - } - - template - static EvaluationResult visit(const ast::Interval &) - { - return EvaluationResult::False; - } - - template - static EvaluationResult visit(const ast::SpecialInteger &) - { - return EvaluationResult::False; - } - - template - static EvaluationResult visit(const ast::String &) - { - return EvaluationResult::False; - } - - template - static EvaluationResult visit(const ast::UnaryOperation &unaryOperation) - { - const auto isArgumentArithmetic = isArithmetic(unaryOperation.argument); - - switch (unaryOperation.operator_) - { - case ast::UnaryOperation::Operator::Absolute: - return (isArgumentArithmetic == EvaluationResult::False ? EvaluationResult::Error : isArgumentArithmetic); - } - - return EvaluationResult::Unknown; - } - - static EvaluationResult visit(const ast::Variable &variable) - { - switch (variable.declaration->domain) - { - case Domain::Noninteger: - return EvaluationResult::False; - case Domain::Integer: - return EvaluationResult::True; - case Domain::Unknown: - return EvaluationResult::Unknown; - } - - return EvaluationResult::Unknown; - } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -inline EvaluationResult isInteger(const ast::Term &term) -{ - return term.accept(IsTermIntegerVisitor()); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -} - -#endif diff --git a/include/anthem/Type.h b/include/anthem/Type.h new file mode 100644 index 0000000..8a5870f --- /dev/null +++ b/include/anthem/Type.h @@ -0,0 +1,166 @@ +#ifndef __ANTHEM__ARITHMETICS_H +#define __ANTHEM__ARITHMETICS_H + +#include +#include + +namespace anthem +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Arithmetics +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct DefaultVariableDomainAccessor +{ + Domain operator()(const ast::Variable &variable) + { + return variable.declaration->domain; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +Type type(const ast::Term &term, Arguments &&...); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +struct TermTypeVisitor +{ + template + static Type visit(const ast::BinaryOperation &binaryOperation, Arguments &&... arguments) + { + const auto leftType = type(binaryOperation.left, std::forward(arguments)...); + const auto rightType = type(binaryOperation.right, std::forward(arguments)...); + + // Binary operations on empty sets return an empty set (also with division) + if (leftType.setSize == SetSize::Empty || rightType.setSize == SetSize::Empty) + return {Domain::Unknown, SetSize::Empty}; + + // Binary operations on nonintegers return an empty set (also with division) + if (leftType.domain == Domain::Noninteger || rightType.domain == Domain::Noninteger) + return {Domain::Unknown, SetSize::Empty}; + + // Binary operations on unknown types return an unknown set + if (leftType.domain == Domain::Unknown || rightType.domain == Domain::Unknown) + return {Domain::Unknown, SetSize::Unknown}; + + // Divisions return an unknown set + if (binaryOperation.operator_ == ast::BinaryOperation::Operator::Division) + return {Domain::Integer, SetSize::Unknown}; + + // Binary operations on integer sets of unknown size return an integer set of unknown size + if (leftType.setSize == SetSize::Unknown || rightType.setSize == SetSize::Unknown) + return {Domain::Integer, SetSize::Unknown}; + + // Binary operations on integer sets with multiple elements return an integer set with multiple elements + if (leftType.setSize == SetSize::Multi || rightType.setSize == SetSize::Multi) + return {Domain::Integer, SetSize::Multi}; + + // Binary operations on plain integers return a plain integer + return {Domain::Integer, SetSize::Unit}; + } + + template + static Type visit(const ast::Boolean &, Arguments &&...) + { + return {Domain::Noninteger, SetSize::Unit}; + } + + template + static Type visit(const ast::Function &function, Arguments &&...) + { + // TODO: check that functions cannot return sets + + return {function.declaration->domain, SetSize::Unit}; + } + + template + static Type visit(const ast::Integer &, Arguments &&...) + { + return {Domain::Integer, SetSize::Unit}; + } + + template + static Type visit(const ast::Interval &interval, Arguments &&... arguments) + { + const auto fromType = type(interval.from, std::forward(arguments)...); + const auto toType = type(interval.to, std::forward(arguments)...); + + // Intervals with empty sets return an empty set + if (fromType.setSize == SetSize::Empty || toType.setSize == SetSize::Empty) + return {Domain::Unknown, SetSize::Empty}; + + // Intervals with nonintegers return an empty set + if (fromType.domain == Domain::Noninteger || toType.domain == Domain::Noninteger) + return {Domain::Unknown, SetSize::Empty}; + + // Intervals with unknown types return an unknown set + if (fromType.domain == Domain::Unknown || toType.domain == Domain::Unknown) + return {Domain::Unknown, SetSize::Unknown}; + + // Intervals with integers generally return integer sets + // TODO: handle 1-element intervals such as 1..1 and empty intervals such as 2..1 + return {Domain::Integer, SetSize::Unknown}; + } + + template + static Type visit(const ast::SpecialInteger &, Arguments &&...) + { + return {Domain::Noninteger, SetSize::Unit}; + } + + template + static Type visit(const ast::String &, Arguments &&...) + { + return {Domain::Noninteger, SetSize::Unit}; + } + + template + static Type visit(const ast::UnaryOperation &unaryOperation, Arguments &&... arguments) + { + assert(unaryOperation.operator_ == ast::UnaryOperation::Operator::Absolute); + + const auto argumentType = type(unaryOperation.argument, std::forward(arguments)...); + + // Absolute value of an empty set returns an empty set + if (argumentType.setSize == SetSize::Empty) + return {Domain::Unknown, SetSize::Empty}; + + // Absolute value of nonintegers returns an empty set + if (argumentType.domain == Domain::Noninteger) + return {Domain::Unknown, SetSize::Empty}; + + // Absolute value of integers returns the same type + if (argumentType.domain == Domain::Integer) + return argumentType; + + return {Domain::Unknown, SetSize::Unknown}; + } + + template + static Type visit(const ast::Variable &variable, Arguments &&... arguments) + { + const auto domain = VariableDomainAccessor()(variable, std::forward(arguments)...); + + return {domain, SetSize::Unit}; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +template +Type type(const ast::Term &term, Arguments &&... arguments) +{ + return term.accept(TermTypeVisitor(), std::forward(arguments)...); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/include/anthem/Utils.h b/include/anthem/Utils.h index 0a56c2a..cc81102 100644 --- a/include/anthem/Utils.h +++ b/include/anthem/Utils.h @@ -52,6 +52,24 @@ enum class Domain //////////////////////////////////////////////////////////////////////////////////////////////////// +enum class SetSize +{ + Empty, + Unit, + Multi, + Unknown, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct Type +{ + Domain domain{Domain::Unknown}; + SetSize setSize{SetSize::Unknown}; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/anthem/IntegerVariableDetection.cpp b/src/anthem/IntegerVariableDetection.cpp index fcefc33..450caa9 100644 --- a/src/anthem/IntegerVariableDetection.cpp +++ b/src/anthem/IntegerVariableDetection.cpp @@ -1,11 +1,11 @@ #include -#include #include #include #include #include #include +#include #include #include @@ -55,9 +55,9 @@ struct VariableDomainMapAccessor //////////////////////////////////////////////////////////////////////////////////////////////////// -EvaluationResult isArithmetic(const ast::Term &term, VariableDomainMap &variableDomainMap) +Type type(const ast::Term &term, VariableDomainMap &variableDomainMap) { - return isArithmetic(term, variableDomainMap); + return type(term, variableDomainMap); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -122,19 +122,22 @@ struct EvaluateFormulaVisitor static EvaluationResult visit(const ast::Comparison &comparison, VariableDomainMap &variableDomainMap) { - const auto isLeftArithmetic = isArithmetic(comparison.left, variableDomainMap); - const auto isRightArithmetic = isArithmetic(comparison.right, variableDomainMap); + const auto leftType = type(comparison.left, variableDomainMap); + const auto rightType = type(comparison.right, variableDomainMap); - if (isLeftArithmetic == EvaluationResult::Error || isRightArithmetic == EvaluationResult::Error) - return EvaluationResult::Error; + // Comparisons with empty sets always return false + if (leftType.setSize == SetSize::Empty || rightType.setSize == SetSize::Empty) + return EvaluationResult::False; - if (isLeftArithmetic == EvaluationResult::Unknown || isRightArithmetic == EvaluationResult::Unknown) + // If either side has an unknown domain, the result is unknown + if (leftType.domain == Domain::Unknown || rightType.domain == Domain::Unknown) return EvaluationResult::Unknown; - if (isLeftArithmetic == isRightArithmetic) + // If both sides have the same domain, the result is unknown + if (leftType.domain == rightType.domain) return EvaluationResult::Unknown; - // Handle the case where one side is arithmetic but the other one isn’t + // If one side is integer, but the other one isn’t, they are not equal switch (comparison.operator_) { case ast::Comparison::Operator::Equal: @@ -179,19 +182,25 @@ struct EvaluateFormulaVisitor static EvaluationResult visit(const ast::In &in, VariableDomainMap &variableDomainMap) { - const auto isElementArithmetic = isArithmetic(in.element, variableDomainMap); - const auto isSetArithmetic = isArithmetic(in.set, variableDomainMap); + const auto elementType = type(in.element, variableDomainMap); + const auto setType = type(in.set, variableDomainMap); - if (isElementArithmetic == EvaluationResult::Error || isSetArithmetic == EvaluationResult::Error) - return EvaluationResult::Error; + // The element to test shouldn’t be empty or a proper set by itself + assert(elementType.setSize != SetSize::Empty && elementType.setSize != SetSize::Multi); - if (isElementArithmetic == EvaluationResult::Unknown || isSetArithmetic == EvaluationResult::Unknown) + // If the set is empty, no element can be selected + if (setType.setSize == SetSize::Empty) + return EvaluationResult::False; + + // If one of the sides has an unknown type, the result is unknown + if (elementType.domain == Domain::Unknown || setType.domain == Domain::Unknown) return EvaluationResult::Unknown; - if (isElementArithmetic == isSetArithmetic) + // If both sides have the same domain, the result is unknown + if (elementType.domain == setType.domain) return EvaluationResult::Unknown; - // If one side is arithmetic, but the other one isn’t, set inclusion is never satisfied + // If one side is integer, but the other one isn’t, set inclusion is never satisfied return EvaluationResult::False; } @@ -250,10 +259,10 @@ struct EvaluateFormulaVisitor if (parameter.domain != Domain::Integer) continue; - const auto isArgumentArithmetic = isArithmetic(argument, variableDomainMap); + const auto argumentType = type(argument, variableDomainMap); - if (isArgumentArithmetic == EvaluationResult::Error || isArgumentArithmetic == EvaluationResult::False) - return isArgumentArithmetic; + if (argumentType.domain == Domain::Noninteger || argumentType.setSize == SetSize::Empty) + return EvaluationResult::Error; } return EvaluationResult::Unknown; @@ -397,15 +406,10 @@ struct CheckIfDefinitionFalseFunctor clearVariableDomainMap(variableDomainMap); - auto result = evaluate(definition, variableDomainMap); - - if (result == EvaluationResult::Error || result == EvaluationResult::False) - return OperationResult::Unchanged; - // As a hypothesis, make the parameter’s domain noninteger variableDomainMap[&variableDeclaration] = Domain::Noninteger; - result = evaluate(definition, variableDomainMap); + const auto result = evaluate(definition, variableDomainMap); if (result == EvaluationResult::Error || result == EvaluationResult::False) { @@ -430,15 +434,10 @@ struct CheckIfQuantifiedFormulaFalseFunctor clearVariableDomainMap(variableDomainMap); - auto result = evaluate(quantifiedFormula, variableDomainMap); - - if (result == EvaluationResult::Error || result == EvaluationResult::False) - return OperationResult::Unchanged; - // As a hypothesis, make the parameter’s domain noninteger variableDomainMap[&variableDeclaration] = Domain::Noninteger; - result = evaluate(quantifiedFormula, variableDomainMap); + const auto result = evaluate(quantifiedFormula, variableDomainMap); if (result == EvaluationResult::Error || result == EvaluationResult::False) { @@ -463,15 +462,10 @@ struct CheckIfCompletedFormulaTrueFunctor clearVariableDomainMap(variableDomainMap); - auto result = evaluate(completedFormula, variableDomainMap); - - if (result == EvaluationResult::Error || result == EvaluationResult::True) - return OperationResult::Unchanged; - // As a hypothesis, make the parameter’s domain noninteger variableDomainMap[&variableDeclaration] = Domain::Noninteger; - result = evaluate(completedFormula, variableDomainMap); + const auto result = evaluate(completedFormula, variableDomainMap); if (result == EvaluationResult::Error || result == EvaluationResult::True) { diff --git a/src/anthem/Simplification.cpp b/src/anthem/Simplification.cpp index 3d00eaf..33303fc 100644 --- a/src/anthem/Simplification.cpp +++ b/src/anthem/Simplification.cpp @@ -2,10 +2,10 @@ #include -#include #include #include #include +#include #include namespace anthem @@ -524,11 +524,14 @@ struct SimplificationRuleIntegerSetInclusion auto &in = formula.get(); - const auto isElementInteger = isInteger(in.element); - const auto isSetInteger = isInteger(in.set); + const auto elementType = type(in.element); + const auto setType = type(in.set); - if (isElementInteger != EvaluationResult::True || isSetInteger != EvaluationResult::True) + if (elementType.domain != Domain::Integer || setType.domain != Domain::Integer + || elementType.setSize != SetSize::Unit || setType.setSize != SetSize::Unit) + { return OperationResult::Unchanged; + } formula = ast::Formula::make(ast::Comparison::Operator::Equal, std::move(in.element), std::move(in.set));