#include <map>
#include <set>
#include <utility>
#include <vector>
#include "Forwards.hpp"
#include "Indexing/Index.hpp"
#include "Lib/DHSet.hpp"
#include "Lib/IntUnionFind.hpp"
#include "Lib/Metaiterators.hpp"
#include "Lib/Set.hpp"
#include "Kernel/FormulaUnit.hpp"
#include "Kernel/NumTraits.hpp"
#include "Kernel/RobSubstitution.hpp"
#include "Kernel/TermIterators.hpp"
#include "Kernel/Signature.hpp"
#include "Saturation/SaturationAlgorithm.hpp"
#include "Shell/NewCNF.hpp"
#include "Shell/NNF.hpp"
#include "Shell/Rectify.hpp"
#include "Shell/AnswerLiteralManager.hpp"
#include "Induction.hpp"
using std::pair;
using std::make_pair;
#define INDUCTION_CONTEXT_BANK 0
#define INDUCTION_CLAUSE_BANK 1
#define NUM_SPECIAL_BANKS 2
namespace Inferences
{
using namespace std;
using namespace Kernel;
using namespace Lib;
Term* ActiveOccurrenceIterator::next()
{
Term* t = _stack.pop();
auto templ = _fnDefHandler.getRecursionTemplate(t);
auto actPos = templ ? &templ->inductionPositions() : nullptr;
for (unsigned i = t->numTypeArguments(); i < t->arity(); i++) {
if ((!actPos || (*actPos)[i]) && t->nthArgument(i)->isTerm()) {
_stack.push(t->nthArgument(i)->term());
}
}
return t;
}
Term* getPlaceholderForTerm(const Stack<Term*>& ts, unsigned i)
{
static DHMap<pair<TermList,unsigned>,Term*> placeholders;
TermList srt = SortHelper::getResultSort(ts[i]);
auto p = make_pair(srt,i);
if(!placeholders.find(p)){
unsigned fresh = env.signature->addFreshFunction(0,(srt.toString() + "_placeholder" + Int::toString(i)).c_str());
env.signature->getFunction(fresh)->setType(OperatorType::getConstantsType(srt));
auto res = Term::createConstant(fresh);
placeholders.insert(p,res);
return res;
}
return placeholders.get(p);
}
TermList TermReplacement::transformSubterm(TermList trm)
{
if(trm.isTerm() && _m.count(trm.term())){
return _m.at(trm.term());
}
return trm;
}
TermList InductionTermReplacement::transformSubterm(TermList trm)
{
if (trm.isVar()) {
unsigned v;
if (_renaming.findOrInsert(trm.var(), v, _nextVar)) {
_nextVar++;
_renamedFreeVars.insert(v);
}
return TermList::var(v);
}
if (trm.term()->isSort()) {
return trm;
}
auto t = trm.term();
auto it = _m.find(t);
if (it != _m.end()){
return it->second;
}
if (!_squashSkolems || !env.signature->getFunction(t->functor())->skolem()) {
return trm;
}
unsigned* ptr;
if (_skolemToVarMap.getValuePtr(t, ptr, _nextVar)) {
_varsReplacingSkolems.insert(_nextVar++);
}
return TermList::var(*ptr);
}
void InductionTermReplacement::resetRenaming(RobSubstitution* subst, unsigned bank)
{
if (subst) {
for (const auto& [v1,v2] : iterTraits(_renaming.items())) {
ALWAYS(subst->unify(TermList::var(v1),bank,TermList::var(v2),INDUCTION_CLAUSE_BANK));
}
}
_renaming.reset();
}
VList* InductionTermReplacement::getRenamedFreeVars() const
{
return VList::fromIterator(iterTraits(_renamedFreeVars.iter()));
}
VList* InductionTermReplacement::getVarsReplacingSkolems() const
{
return VList::fromIterator(iterTraits(_varsReplacingSkolems.iter()));
}
Formula* InductionContext::getFormula(
const InductionUnit& unit, const Substitution& typeBinder, unsigned& nextVar, VList** varsReplacingSkolems, RobSubstitution* subst) const
{
auto hyps = FormulaList::empty();
for (const auto& lit : unit.conditions) {
FormulaList::push(new AtomicFormula(SubstHelper::apply(lit, typeBinder)), hyps);
}
auto left = hyps ? JunctionFormula::generalJunction(Connective::AND, hyps) : nullptr;
auto hypVars = VList::fromIterator(unit.condUnivVars.iterFifo());
if (hypVars) {
ASS(left);
left = new QuantifiedFormula(Connective::FORALL, hypVars, SList::empty(), left);
}
vector<TermList> ts;
for (const auto& t : unit.F_terms) {
ts.push_back(SubstHelper::apply(t, typeBinder));
}
auto renamedFreeVars = VList::empty();
auto right = getFormulaWithSquashedSkolems(ts, nextVar, renamedFreeVars, varsReplacingSkolems, subst);
auto f = left ? new BinaryFormula(Connective::IMP, left, right) : right;
if (renamedFreeVars) {
f = new QuantifiedFormula(Connective::EXISTS, renamedFreeVars, SList::empty(), f);
}
return f;
}
Formula* InductionContext::getFormula(InductionTermReplacement& tr, RobSubstitution* subst) const
{
ASS(_cls.isNonEmpty());
auto argLists = FormulaList::empty();
for (unsigned i = 0; i < _cls.size(); i++) {
auto lits = _cls[i].second;
auto argList = FormulaList::empty();
for (const auto& lit : lits) {
auto tlit = tr.transformLiteral(lit);
FormulaList::push(new AtomicFormula(Literal::complementaryLiteral(tlit)), argList);
}
FormulaList::push(JunctionFormula::generalJunction(Connective::AND, argList), argLists);
tr.resetRenaming(subst, NUM_SPECIAL_BANKS + i);
}
return JunctionFormula::generalJunction(Connective::OR, argLists);
}
Formula* InductionContext::getFormulaWithFreeVar(TermList t, unsigned freeVar, unsigned freeVarSub, RobSubstitution* subst) const
{
ASS_EQ(_cls.size(),1);
ASS_EQ(_cls[0].second.size(),1);
auto replacementMap = getReplacementMap({ t }, subst);
TermReplacement tr(replacementMap);
auto tlit = tr.transformLiteral(_cls[0].second[0]);
auto replaced = new AtomicFormula(Literal::complementaryLiteral(tlit));
Substitution s;
s.bindUnbound(freeVar, TermList::var(freeVarSub));
return SubstHelper::apply(replaced, s);
}
Formula* InductionContext::getFormulaWithSquashedSkolems(
const std::vector<TermList>& r, unsigned& nextVar, VList*& renamedFreeVars, VList** varsReplacingSkolems, RobSubstitution* subst) const
{
auto replacementMap = getReplacementMap(r, subst);
InductionTermReplacement tr(replacementMap, env.options->inductionStrengthenHypothesis(), nextVar);
auto res = getFormula(tr, subst);
if (subst) {
for (const auto& [t,v] : iterTraits(tr._skolemToVarMap.items())) {
ALWAYS(subst->unify(TermList::var(v),INDUCTION_CLAUSE_BANK,TermList(t),INDUCTION_CONTEXT_BANK));
}
}
renamedFreeVars = tr.getRenamedFreeVars();
if (varsReplacingSkolems) {
*varsReplacingSkolems = tr.getVarsReplacingSkolems();
}
return res;
}
unsigned InductionContext::getFreeVariable() const {
for (const auto& kv : _cls) {
for (const auto& lit : kv.second) {
VariableIterator vi(lit);
if (vi.hasNext()) {
return vi.next().var();
}
}
}
ASSERTION_VIOLATION;
}
template<typename Fun>
InductionContext InductionContext::transform(Fun fn) const
{
Stack<std::pair<Clause*, LiteralStack>> cls;
for (const auto& [cl, lits] : _cls) {
LiteralStack resLits;
for (const auto& lit : lits) {
auto tlit = fn(lit);
if (tlit && tlit != lit) {
resLits.push(tlit);
}
}
if (resLits.isEmpty()) {
continue;
}
cls.emplace(cl, std::move(resLits));
}
return InductionContext(_indTerms, std::move(cls));
}
std::unordered_map<Term*,TermList> InductionContext::getReplacementMap(const std::vector<TermList>& r, RobSubstitution* subst) const
{
ASS_EQ(r.size(), _indTerms.size());
std::unordered_map<Term*,TermList> replacementMap;
for (unsigned i = 0; i < _indTerms.size(); i++) {
auto ph = getPlaceholderForTerm(_indTerms,i);
replacementMap.insert(make_pair(ph,r[i]));
if (subst) {
ASS(r[i].isVar());
ALWAYS(subst->unify(r[i],INDUCTION_CLAUSE_BANK,TermList(ph),INDUCTION_CONTEXT_BANK));
}
}
return replacementMap;
}
std::unordered_map<Term*,TermList> getContextReplacementMap(const InductionContext& context, bool inverse = false)
{
std::unordered_map<Term*,TermList> m;
for (unsigned i = 0; i < context._indTerms.size(); i++) {
auto ph = getPlaceholderForTerm(context._indTerms,i);
m.insert(make_pair(inverse ? ph : context._indTerms[i], inverse ? context._indTerms[i] : ph));
}
return m;
}
ContextReplacement::ContextReplacement(const InductionContext& context)
: TermReplacement(getContextReplacementMap(context)),
_context(context), _used(false) {}
InductionContext ContextReplacement::next()
{
ASS(hasNext());
_used = true;
return _context.transform([&](Literal* lit) {
return transformLiteral(lit);
});
}
ActiveOccurrenceContextReplacement::ActiveOccurrenceContextReplacement(
const InductionContext& context, const FunctionDefinitionHandler& fnDefHandler)
: ContextReplacement(context),
_fnDefHandler(fnDefHandler),
_iteration(_context._indTerms.size(),0),
_matchCount(_context._indTerms.size(),0),
_hasNonActive(false)
{}
TermList ActiveOccurrenceContextReplacement::transformSubterm(TermList trm)
{
if (trm.isTerm()) {
auto it = std::find(_context._indTerms.begin(), _context._indTerms.end(), trm.term());
if (it != _context._indTerms.end()) {
auto i = it - _context._indTerms.begin();
if (1 & (_iteration[i] >> _matchCount[i]++)) {
return _m.at(trm.term());
}
}
}
return trm;
}
InductionContext ActiveOccurrenceContextReplacement::next()
{
ASS(hasNext());
_used = true;
return _context.transform([&](Literal* lit) {
for (unsigned i = 0; i < _iteration.size(); i++) {
_iteration[i] = 0;
_matchCount[i] = 0;
}
Stack<pair<Term*,bool>> stack(8);
stack.push({ lit, true });
while (stack.isNonEmpty()) {
auto [t,active] = stack.pop();
auto templ = _fnDefHandler.getRecursionTemplate(t);
for (unsigned k = 0; k < t->arity(); k++) {
if (t->nthArgument(k)->isTerm()) {
stack.push({ t->nthArgument(k)->term(),
active && templ ? templ->inductionPositions()[k] : active });
}
}
if (t->ground()) {
auto it = std::find(_context._indTerms.begin(), _context._indTerms.end(), t);
if (it != _context._indTerms.end()) {
auto idx = it - _context._indTerms.begin();
_iteration[idx] = (_iteration[idx] << 1) | active;
if (!active) {
_hasNonActive = true;
}
}
}
}
return transformLiteral(lit);
});
}
VirtualIterator<InductionContext> contextReplacementInstance(const InductionContext& context, const Options& opt, FunctionDefinitionHandler& fnDefHandler)
{
auto ctx = context;
auto res = VirtualIterator<InductionContext>::getEmpty();
if (opt.inductionOnActiveOccurrences()) {
ActiveOccurrenceContextReplacement aor(context, fnDefHandler);
ASS(aor.hasNext());
auto ao_ctx = aor.next();
if (ao_ctx._cls.isNonEmpty()) {
ctx = ao_ctx;
res = pvi(getSingletonIterator(ctx));
if (!aor.hasNonActive()) {
return res;
}
}
}
return pvi(concatIters(std::move(res), vi(opt.inductionGen()
? new ContextSubsetReplacement(ctx, opt.maxInductionGenSubsetSize())
: new ContextReplacement(ctx))));
}
ContextSubsetReplacement::ContextSubsetReplacement(const InductionContext& context, const unsigned maxSubsetSize)
: ContextReplacement(context),
_iteration(context._indTerms.size(),1), _maxIterations(context._indTerms.size(),0),
_matchCount(context._indTerms.size(),0),
_maxSubsetSize(maxSubsetSize),
_ready(false), _done(false)
{
for (unsigned i = 0; i < _context._indTerms.size(); i++) {
unsigned occurrences = 0;
for (const auto& kv : _context._cls) {
for (const auto& lit : kv.second) {
occurrences += lit->countSubtermOccurrences(TermList(_context._indTerms[i]));
}
}
_maxIterations[i] = pow(2, occurrences);
if (_maxIterations[i] > _maxOccurrences) {
_iteration[i] = _maxIterations[i]-1;
}
}
while (!_done && shouldSkipIteration()) {
stepIteration();
}
_ready = true;
}
TermList ContextSubsetReplacement::transformSubterm(TermList trm)
{
if (trm.isTerm()) {
auto it = std::find(_context._indTerms.begin(), _context._indTerms.end(), trm.term());
if (it != _context._indTerms.end()) {
auto i = it - _context._indTerms.begin();
if (1 & (_iteration[i] >> _matchCount[i]++)) {
return _m.at(trm.term());
}
}
}
return trm;
}
bool ContextSubsetReplacement::hasNext()
{
if (_ready) {
return !_done;
}
_ready = true;
do {
stepIteration();
} while (!_done && shouldSkipIteration());
return !_done;
}
InductionContext ContextSubsetReplacement::next()
{
ASS(_ready);
for (unsigned i = 0; i < _context._indTerms.size(); i++) {
_matchCount[i] = 0;
}
_ready = false;
return _context.transform([&](Literal* lit) -> Literal* {
auto tlit = transformLiteral(lit);
for (unsigned i = 0; i < _context._indTerms.size(); i++) {
if (tlit->containsSubterm(TermList(getPlaceholderForTerm(_context._indTerms,i)))) {
return tlit;
}
}
return nullptr;
});
}
bool ContextSubsetReplacement::shouldSkipIteration() const
{
const bool subsetSizeCheck = _maxSubsetSize > 0;
for (unsigned i = 0; i < _iteration.size(); i++) {
unsigned setBits = __builtin_popcount(_iteration[i]);
if (_iteration[i] == _maxIterations[i]-1) {
continue;
}
if (subsetSizeCheck && setBits > _maxSubsetSize) {
return true;
}
}
return false;
}
void ContextSubsetReplacement::stepIteration()
{
for (unsigned i = 0; i < _iteration.size(); i++) {
if (_maxIterations[i] > _maxOccurrences) {
continue;
}
_iteration[i]++;
if (_iteration[i] < _maxIterations[i]) {
return;
} else {
_iteration[i] = 1;
}
}
_done = true;
}
void Induction::attach(SaturationAlgorithm* salg) {
GeneratingInferenceEngine::attach(salg);
if (InductionHelper::isIntInductionOn()) {
_comparisonIndex = salg->getGeneratingIndex<UnitIntegerComparisonLiteralIndex>();
_inductionTermIndex = salg->getGeneratingIndex<InductionTermIndex>();
}
if (InductionHelper::isNonUnitStructInductionOn()) {
_structInductionTermIndex = salg->getGeneratingIndex<StructInductionTermIndex>();
}
}
void Induction::detach() {
_structInductionTermIndex = nullptr;
_comparisonIndex = nullptr;
_inductionTermIndex = nullptr;
GeneratingInferenceEngine::detach();
}
ClauseIterator Induction::generateClauses(Clause* premise)
{
return pvi(InductionClauseIterator(premise, InductionHelper(_comparisonIndex.get(), _inductionTermIndex.get()),
_salg, _structInductionTermIndex.get(), _formulaIndex));
}
void InductionClauseIterator::processClause(Clause* premise)
{
if (InductionHelper::isInductionClause(premise)) {
for (Literal* lit : premise->iterLits()) {
if (!lit->isAnswerLiteral()) {
processLiteral(premise, lit);
}
}
}
if (InductionHelper::isIntInductionOn() && InductionHelper::isIntegerComparison(premise)) {
processIntegerComparison(premise, (*premise)[0]);
}
}
struct InductionContextFn
{
InductionContextFn(Clause* premise, Literal* lit) : _premise(premise), _lit(lit) {}
VirtualIterator<InductionContext> operator()(pair<Stack<Term*>, VirtualIterator<QueryRes<ResultSubstitutionSP, TermLiteralClause>>> arg) {
auto indDepth = _premise->inference().inductionDepth();
if (indDepth) {
auto res = VirtualIterator<InductionContext>::getEmpty();
ASS(arg.first.isNonEmpty());
if (arg.first.size() > 1) {
return res;
}
auto indTerm = arg.first[0];
if (_lit->isEquality() || !indTerm->arity()) {
return res;
}
while (arg.second.hasNext()) {
auto& tqr = *arg.second.next().data;
if (indDepth != tqr.clause->inference().inductionDepth()) {
continue;
}
if (tqr.clause == _premise || _lit->functor() != tqr.literal->functor()) {
continue;
}
bool match = false;
SubtermIterator sti1(_lit);
SubtermIterator sti2(tqr.literal);
while (sti1.hasNext()) {
ALWAYS(sti2.hasNext());
auto st1 = sti1.next();
auto st2 = sti2.next();
if (st1 != st2) {
if (match ||
!((st1.containsSubterm(st2) && st2.term() == indTerm) ||
(st2.containsSubterm(st1) && st1.term() == indTerm)))
{
match = false;
break;
}
sti1.right();
sti2.right();
match = true;
}
}
if (!match) {
continue;
}
InductionContext ctx(arg.first, {
{ _premise, { _lit } },
{ tqr.clause, { tqr.literal } }
});
res = pvi(concatIters(std::move(res), getSingletonIterator(ctx)));
}
return res;
} else {
std::unordered_map<Clause*, LiteralStack> clauseLitMap;
clauseLitMap.insert({ _premise, { _lit } });
Set<Literal*,SharedTermHash> lits;
lits.insert(_lit);
while (arg.second.hasNext()) {
auto& tqr = *arg.second.next().data;
if (lits.contains(tqr.literal) || lits.contains(Literal::complementaryLiteral(tqr.literal))) {
continue;
}
lits.insert(tqr.literal);
if (indDepth != tqr.clause->inference().inductionDepth()) {
continue;
}
clauseLitMap.emplace(tqr.clause, LiteralStack()).first->second.push(tqr.literal);
}
Stack<std::pair<Clause*, LiteralStack>> cls;
for (const auto& kv : clauseLitMap) {
cls.push(kv);
}
return pvi(getSingletonIterator(InductionContext(arg.first, std::move(cls))));
}
}
private:
Clause* _premise;
Literal* _lit;
};
void InductionClauseIterator::processLiteral(Clause* premise, Literal* lit)
{
if(_opt.showInduction()){
std::cout << "[Induction] process " << lit->toString() << " in " << premise->toString() << endl;
}
if (_opt.inductionGroundOnly() && !lit->ground()) {
return;
}
if (_opt.questionAnswering() != Options::QuestionAnsweringMode::SYNTHESIS) {
Set<Term*,SharedTermHash> int_terms;
typedef std::set<const InductionTemplate*> TemplateTypeArgsSet;
std::map<Stack<Term*>,TemplateTypeArgsSet> ta_terms;
VirtualIterator<Term*> it;
if (_opt.inductionOnActiveOccurrences()) {
it = vi(new ActiveOccurrenceIterator(lit, _fnDefHandler));
} else {
it = vi(new NonVariableNonTypeIterator(lit, true));
}
for (const auto& t : iterTraits(std::move(it))) {
if (!t->isLiteral() && InductionHelper::isInductionTerm(t)){
if(InductionHelper::isStructInductionOn() && InductionHelper::isStructInductionTerm(t)){
ta_terms.emplace(Stack<Term*>{ t }, TemplateTypeArgsSet());
}
if(InductionHelper::isIntInductionOn() && InductionHelper::isIntInductionTermListInLiteral(t, lit)){
int_terms.insert(t);
}
}
Stack<Term*> indTerms;
auto templ = _fnDefHandler.matchesTerm(t, indTerms);
if (templ) {
auto it = ta_terms.emplace(std::move(indTerms), TemplateTypeArgsSet()).first;
it->second.emplace(templ);
}
}
if (InductionHelper::isInductionLiteral(lit)) {
Set<Term*,SharedTermHash>::Iterator citer1(int_terms);
while(citer1.hasNext()){
Term* t = citer1.next();
auto leBound = iterTraits(_helper.getLess(t)).collect<Stack>();
auto grBound = iterTraits(_helper.getGreater(t)).collect<Stack>();
auto indLitsIt = contextReplacementInstance(InductionContext({ t }, lit, premise), _opt, _fnDefHandler);
while (indLitsIt.hasNext()) {
auto ctx = indLitsIt.next();
for (const auto& b1 : leBound) {
if (!isValidBound(ctx, b1)) {
continue;
}
if (_helper.isInductionForFiniteIntervalsOn()) {
for (const auto& b2 : grBound) {
if (!isValidBound(ctx, b2)) {
continue;
}
performFinIntInduction(ctx, b1, b2);
}
}
if (_helper.isInductionForInfiniteIntervalsOn()) {
performInfIntInduction(ctx, true, b1);
}
}
if (_helper.isInductionForInfiniteIntervalsOn()) {
for (const auto& b2 : grBound) {
if (!isValidBound(ctx, b2)) {
continue;
}
performInfIntInduction(ctx, false, b2);
}
}
if (_opt.integerInductionDefaultBound()) {
InductionFormulaIndex::Entry* e = nullptr;
static Bound defaultBound = Bound(DefaultBound{ .term = TypedTermList(theory->representConstant(IntegerConstantType(0))) });
if (notDoneInt(ctx, nullptr, nullptr, e) && isValidBound(ctx, defaultBound)) {
performIntInduction(ctx, e, true, defaultBound, nullptr);
performIntInduction(ctx, e, false, defaultBound, nullptr);
}
resolveClauses(ctx, e, nullptr, nullptr);
}
}
}
}
auto sideLitsIt = VirtualIterator<pair<Stack<Term*>, VirtualIterator<QueryRes<ResultSubstitutionSP, TermLiteralClause>>>>::getEmpty();
if (_opt.nonUnitInduction()) {
sideLitsIt = pvi(iterTraits(getSTLIterator(ta_terms.begin(), ta_terms.end()))
.map([](const auto& kv){
return kv.first;
})
.map([this](Stack<Term*> ts) {
auto res = VirtualIterator<QueryRes<ResultSubstitutionSP, TermLiteralClause>>::getEmpty();
for (const auto& t : ts) {
res = pvi(concatIters(std::move(res), _structInductionTermIndex->getGeneralizations(t, false)));
}
return make_pair(ts, std::move(res));
}));
}
auto sideLitsIt2 = iterTraits(std::move(sideLitsIt))
.flatMap(InductionContextFn(premise, lit))
.flatMap([this](const InductionContext& arg) {
return contextReplacementInstance(arg, _opt, _fnDefHandler);
})
.filter([&premise](const InductionContext& arg) {
unsigned cnt = 0;
bool hasPremise = false;
for (const auto& kv : arg._cls) {
if (kv.first == premise) {
hasPremise = true;
}
cnt += kv.second.size();
}
return hasPremise && cnt > 1;
});
auto indCtxSingle = iterTraits(getSTLIterator(ta_terms.begin(), ta_terms.end()))
.map([&lit,&premise](const auto& arg) {
return InductionContext(arg.first, lit, premise);
})
.flatMap([this](const InductionContext& arg) {
return contextReplacementInstance(arg, _opt, _fnDefHandler);
});
auto indCtxIt = concatIters(std::move(sideLitsIt2), std::move(indCtxSingle))
.filter([](const InductionContext& arg) {
for (const auto& kv : arg._cls) {
for (const auto& lit : kv.second) {
if (InductionHelper::isInductionLiteral(lit)) {
return true;
}
}
}
return false;
});
while (indCtxIt.hasNext()) {
auto ctx = indCtxIt.next();
static bool one = _opt.structInduction() == Options::StructuralInductionKind::ONE ||
_opt.structInduction() == Options::StructuralInductionKind::ALL;
static bool two = _opt.structInduction() == Options::StructuralInductionKind::TWO ||
_opt.structInduction() == Options::StructuralInductionKind::ALL;
static bool three = _opt.structInduction() == Options::StructuralInductionKind::THREE ||
_opt.structInduction() == Options::StructuralInductionKind::ALL;
static bool rec = _opt.structInduction() == Options::StructuralInductionKind::RECURSION ||
_opt.structInduction() == Options::StructuralInductionKind::ALL;
InductionFormulaIndex::Entry* e;
if (_formulaIndex.findOrInsert(ctx, e)) {
if (ctx._indTerms.size() == 1) {
TermList sort = SortHelper::getResultSort(ctx._indTerms[0]);
TermAlgebra* ta = env.signature->getTermAlgebraOfSort(sort);
if(one){
performInduction(ctx, ta->getInductionTemplateOne(), e);
}
if(two){
performInduction(ctx, ta->getInductionTemplateTwo(), e);
}
if(three){
performInduction(ctx, ta->getInductionTemplateThree(), e);
}
}
if (rec) {
for (const auto& templ : ta_terms.at(ctx._indTerms)) {
performInduction(ctx, templ, e);
}
}
}
for (auto& indInst : e->getInductionInstances()) {
resolveClauses(indInst, ctx);
}
}
} else {
if (InductionHelper::isStructInductionOn() && InductionHelper::isNonGroundInductionLiteral(lit)) {
NonVariableNonTypeIterator nvi(lit);
while (nvi.hasNext()) {
auto st = nvi.next();
if (InductionHelper::isInductionTerm(st) && InductionHelper::isStructInductionTerm(st)) {
auto indLitsIt = contextReplacementInstance(InductionContext({ st }, lit, premise), _opt, _fnDefHandler);
while (indLitsIt.hasNext()) {
auto ctx = indLitsIt.next();
InductionFormulaIndex::Entry* e;
if (_formulaIndex.findOrInsert(ctx, e)) {
performStructInductionFreeVar(ctx, e);
}
for (auto& indInst : e->getInductionInstances()) {
resolveClauses(indInst, ctx);
}
}
}
}
}
}
}
void InductionClauseIterator::processIntegerComparison(Clause* premise, Literal* lit)
{
ASS((theory->interpretPredicate(lit) == Theory::INT_LESS) && lit->ground());
bool positive = lit->isPositive();
InductionFormulaIndex::Entry* e;
for (unsigned i = 0; i <= 1; i++) {
auto indtl = *lit->nthArgument(positive ? i : 1-i);
auto indt = indtl.term();
auto bound = *lit->nthArgument(positive ? 1-i : i);
auto bound2 = iterTraits(i ? _helper.getGreater(indt) : _helper.getLess(indt)).collect<Stack>();
auto it = iterTraits(_helper.getTQRsForInductionTerm(indt))
.filter([&premise](const auto& tqr) {
return tqr.data->clause != premise;
})
.map([&indt](const auto& tqr) {
return InductionContext({ indt }, tqr.data->literal, tqr.data->clause);
})
.flatMap([this](const InductionContext& arg) {
return contextReplacementInstance(arg, _opt, _fnDefHandler);
});
auto b = TermLiteralClause{ TypedTermList(bound, IntTraits::sort()), lit, premise };
while (it.hasNext()) {
auto ctx = it.next();
if (!isValidBound(ctx, b)) {
continue;
}
if (_helper.isInductionForFiniteIntervalsOn()) {
for (const auto& b2 : bound2) {
if (!isValidBound(ctx, b2)) {
ASS_EQ(ctx._cls.size(), 1);
continue;
}
if (notDoneInt(ctx, i ? lit : b2.literal, i ? b2.literal : lit, e)) {
if (i) {
performIntInduction(ctx, e, true, i ? b : b2, i ? &b2 : &b);
} else {
performIntInduction(ctx, e, false, i ? b2 : b, i ? &b : &b2);
}
}
resolveClauses(ctx, e, i ? &b : &b2, i ? &b2 : &b);
}
}
if (_helper.isInductionForInfiniteIntervalsOn()) {
performInfIntInduction(ctx, i, b);
}
}
}
}
ClauseStack InductionClauseIterator::produceClauses(Formula* hypothesis, InferenceRule rule, const InductionContext& context, Substitution& cnfSubst)
{
NewCNF cnf(0);
cnf.setForInduction();
ClauseStack hyp_clauses;
Inference inf = NonspecificInference0(UnitInputType::AXIOM,rule);
unsigned maxInductionDepth = 0;
for (const auto& kv : context._cls) {
maxInductionDepth = max(maxInductionDepth,kv.first->inference().inductionDepth());
}
inf.setInductionDepth(maxInductionDepth+1);
FormulaUnit* fu = new FormulaUnit(hypothesis,inf);
if(_opt.showInduction()){
std::cout << "[Induction] formula " << fu->toString() << endl;
}
cnf.clausify(NNF::ennf(fu), hyp_clauses, &cnfSubst);
return hyp_clauses;
}
void InductionClauseIterator::resolveClauses(const InductionContext& context, InductionFormulaIndex::Entry* e, const TermLiteralClause* bound1, const TermLiteralClause* bound2)
{
static unsigned less = env.signature->getInterpretingSymbol(Theory::INT_LESS);
ASS_EQ(context._indTerms.size(), 1);
static TypedTermList ph(getPlaceholderForTerm(context._indTerms,0));
auto cls = context._cls;
if (bound1) {
auto lhs = bound1->literal->polarity() ? bound1->term : ph;
auto rhs = bound1->literal->polarity() ? ph : bound1->term;
cls.emplace(bound1->clause,
LiteralStack{ Literal::create2(less, bound1->literal->polarity(), lhs, rhs) });
}
if (bound2) {
auto lhs = bound2->literal->polarity() ? ph : bound2->term;
auto rhs = bound2->literal->polarity() ? bound2->term : ph;
cls.emplace(bound2->clause,
LiteralStack{ Literal::create2(less, bound2->literal->polarity(), lhs, rhs) });
}
InductionContext ctx(context._indTerms, std::move(cls));
for (auto& indInst : e->getInductionInstances()) {
resolveClauses(indInst, ctx);
}
}
IntUnionFind findDistributedVariants(const InductionInstance& indInst, const InductionContext& context)
{
const auto& toResolve = context._cls;
IntUnionFind uf(indInst.cls.size());
for (unsigned i = 0; i < indInst.cls.size(); i++) {
auto cl = indInst.cls[i];
LiteralStack conclusionLits(toResolve.size());
#if VDEBUG
Stack<int> variantCounts(toResolve.size());
#endif
for (unsigned k = 0; k < cl->length(); k++) {
auto clit = indInst.subst.apply(Literal::complementaryLiteral((*cl)[k]), INDUCTION_CLAUSE_BANK);
for (unsigned r_i = 0; r_i < toResolve.size(); r_i++) {
#if VDEBUG
bool found = false;
#endif
for (const auto& lit : toResolve[r_i].second) {
auto slit = indInst.subst.apply(lit, NUM_SPECIAL_BANKS + r_i);
if (slit == clit) {
conclusionLits.push((*cl)[k]);
#if VDEBUG
variantCounts.push(toResolve[r_i].second.size()-1);
ASS(!found);
found = true;
#else
break;
#endif
}
}
}
}
ASS_EQ(conclusionLits.size(), toResolve.size());
for (unsigned k = 0; k < conclusionLits.size(); k++) {
#if VDEBUG
for (unsigned j = 0; j < indInst.cls.size(); j++) {
#else
for (unsigned j = i+1; j < indInst.cls.size(); j++) {
#endif
auto other = indInst.cls[j];
if (i == j || cl->length() != other->length()) {
continue;
}
if (other->contains(conclusionLits[k])) {
continue;
}
unsigned mismatchCnt = 0;
for (unsigned l = 0; l < cl->length(); l++) {
if (!cl->contains((*other)[l])) {
mismatchCnt++;
}
}
if (mismatchCnt == 1) {
#if VDEBUG
variantCounts[k]--;
#endif
uf.doUnion(i,j);
}
}
ASS_LE(variantCounts[k],0);
}
}
uf.evalComponents();
return uf;
}
Clause* resolveClausesHelper(const InductionContext& context, const InductionInstance& indInst, IntUnionFind::ElementIterator eIt)
{
ASS(eIt.hasNext());
auto cl = indInst.cls[eIt.next()];
auto premises = UnitList::singleton(cl);
for (const auto& index : iterTraits(eIt)) {
UnitList::push(indInst.cls[index],premises);
}
TermReplacement tr(getContextReplacementMap(context, true));
RStack<Literal*> resLits;
for (const auto& curr : *cl) {
auto clit = indInst.subst.apply(curr, INDUCTION_CLAUSE_BANK);
bool shouldResolveLit = iterTraits(range(0, context._cls.size()))
.any([&](unsigned i) {
return iterTraits(context._cls[i].second.iter())
.any([&](Literal* lit) {
return Literal::complementaryLiteral(clit) == indInst.subst.apply(lit, NUM_SPECIAL_BANKS + i);
});
});
if (!shouldResolveLit) {
resLits->push(tr.transformLiteral(clit));
}
}
for (unsigned i = 0; i < context._cls.size(); i++) {
const auto& [cl, lits] = context._cls[i];
for (const auto& clit : *cl) {
bool shouldResolveLit = iterTraits(lits.iter())
.any([&](Literal* lit) {
return clit == tr.transformLiteral(lit);
});
if (!shouldResolveLit) {
resLits->push(tr.transformLiteral(indInst.subst.apply(clit, NUM_SPECIAL_BANKS + i)));
}
}
UnitList::push(cl, premises);
}
return Clause::fromStack(*resLits, GeneratingInferenceMany(InferenceRule::INDUCTION_HYPERRESOLUTION, premises));
}
void InductionClauseIterator::resolveClauses(const InductionInstance& indInst, const InductionContext& context)
{
ASS(indInst.cls.isNonEmpty());
env.statistics->inductionApplication++;
auto uf = findDistributedVariants(indInst, context);
IntUnionFind::ComponentIterator cit(uf);
while(cit.hasNext()){
IntUnionFind::ElementIterator eIt = cit.next();
_clauses.push(resolveClausesHelper(context, indInst, eIt));
if(_opt.showInduction()){
std::cout << "[Induction] generate " << _clauses.top()->toString() << endl;
}
}
}
void InductionClauseIterator::performFinIntInduction(const InductionContext& context, const TermLiteralClause& lb, const TermLiteralClause& ub)
{
InductionFormulaIndex::Entry* e = nullptr;
if (notDoneInt(context, lb.literal, ub.literal, e)) {
performIntInduction(context, e, true, lb, &ub);
performIntInduction(context, e, false, ub, &lb);
}
resolveClauses(context, e, &lb, &ub);
}
void InductionClauseIterator::performInfIntInduction(const InductionContext& context, bool increasing, const TermLiteralClause& bound)
{
InductionFormulaIndex::Entry* e = nullptr;
if (notDoneInt(context, increasing ? bound.literal : nullptr, increasing ? nullptr : bound.literal, e)) {
performIntInduction(context, e, increasing, bound, nullptr);
}
resolveClauses(context, e, increasing ? &bound : nullptr, increasing ? nullptr : &bound);
}
std::unique_ptr<InductionTemplate> InductionClauseIterator::getIntegerInductionTemplate(bool increasing, Coproduct<TermLiteralClause, DefaultBound> bound1, const TermLiteralClause* optionalBound2)
{
TermList b1(bound1.apply([](auto x) { return x.term; }));
auto x = TermList::var(0);
auto y = TermList::var(1);
static unsigned less = env.signature->getInterpretingSymbol(Theory::INT_LESS);
LiteralStack stepConds = {
Literal::create2(less,false,(increasing ? x : b1),(increasing ? b1 : x))
};
const bool isBound1Equal = bound1.match(
[](TermLiteralClause const& bound1) { return (bound1.literal->functor() == less && bound1.literal->isNegative()); },
[](DefaultBound) { return true; });
const bool isBound1FirstArg = (increasing != isBound1Equal);
LiteralStack concConds = {
Literal::create2(less, !isBound1Equal, (isBound1FirstArg ? b1 : y), (isBound1FirstArg ? y : b1))
};
const bool hasBound2 = optionalBound2 && optionalBound2->literal;
if (hasBound2) {
TermList b2(optionalBound2->term);
if (b1 == b2) {
return std::unique_ptr<InductionTemplate>();
}
stepConds.push(Literal::create2(less, true, (increasing ? x : b2), (increasing ? b2 : x)));
const bool isBound2Equal = (optionalBound2->literal->functor() == less && optionalBound2->literal->isNegative());
const bool isBound2FirstArg = (increasing == isBound2Equal);
concConds.push(Literal::create2(less, !isBound2Equal, (isBound2FirstArg ? b2 : y), (isBound2FirstArg ? y : b2)));
}
TermList xPlusOne(Term::create2(
env.signature->getInterpretingSymbol(Theory::INT_PLUS), x,
TermList(theory->representConstant(IntegerConstantType(increasing ? 1 : -1))))
);
Stack<InductionCase> cases {
InductionCase(InductionUnit({ b1 })), InductionCase(InductionUnit({ xPlusOne }, std::move(stepConds)), { TermStack{ x } }) };
const bool isDefaultBound = bound1.template is<DefaultBound>();
InferenceRule rule =
isDefaultBound
? (increasing ? InferenceRule::INT_DB_UP_INDUCTION_AXIOM : InferenceRule::INT_DB_DOWN_INDUCTION_AXIOM)
: (increasing ? (hasBound2 ? InferenceRule::INT_FIN_UP_INDUCTION_AXIOM : InferenceRule::INT_INF_UP_INDUCTION_AXIOM)
: (hasBound2 ? InferenceRule::INT_FIN_DOWN_INDUCTION_AXIOM : InferenceRule::INT_INF_DOWN_INDUCTION_AXIOM));
return make_unique<InductionTemplate>(
TermStack{ AtomicSort::intSort() },
std::move(cases),
InductionUnit({ y }, std::move(concConds)),
y.var(), rule
);
}
void InductionClauseIterator::performIntInduction(const InductionContext& context, InductionFormulaIndex::Entry* e, bool increasing, Coproduct<TermLiteralClause, DefaultBound> bound1, const TermLiteralClause* optionalBound2)
{
auto templ = getIntegerInductionTemplate(increasing, bound1, optionalBound2);
if (!templ) {
return;
}
performInduction(context, templ.get(), e);
}
void InductionClauseIterator::performInduction(const InductionContext& context, const InductionTemplate* templ, InductionFormulaIndex::Entry* e)
{
unsigned var = templ->maxVar+1;
auto formulas = FormulaList::empty();
std::vector<TermList> ts(context._indTerms.size(), TermList());
static Substitution typeBinder;
typeBinder.reset();
ASS_EQ(context._indTerms.size(), templ->sorts.size());
for (unsigned i = 0; i < context._indTerms.size(); i++) {
ALWAYS(MatchingUtils::matchTerms(templ->sorts[i], SortHelper::getResultSort(context._indTerms[i]), typeBinder));
}
for (const auto& c : templ->cases) {
auto hyps = FormulaList::empty();
for (const auto& hyp : c.hypotheses) {
auto varsReplacingSkolems = VList::empty();
auto hypF = context.getFormula(hyp, typeBinder, var, &varsReplacingSkolems);
if (varsReplacingSkolems) {
hypF = new QuantifiedFormula(Connective::FORALL, varsReplacingSkolems, SList::empty(), hypF);
}
FormulaList::push(hypF, hyps);
}
auto left = hyps ? JunctionFormula::generalJunction(Connective::AND, hyps) : nullptr;
auto hypVars = VList::fromIterator(c.hypUnivVars.iterFifo());
if (hypVars) {
ASS(left);
left = new QuantifiedFormula(Connective::FORALL, hypVars, SList::empty(), left);
}
auto right = context.getFormula(c.conclusion, typeBinder, var);
FormulaList::push(Formula::quantify(left ? new BinaryFormula(Connective::IMP,left,right) : right), formulas);
}
ASS(formulas);
auto indPremise = JunctionFormula::generalJunction(Connective::AND,formulas);
RobSubstitution subst;
auto conclusion = context.getFormula(templ->conclusion, typeBinder, var, nullptr, &subst);
Formula* induction_formula = new BinaryFormula(Connective::IMP, indPremise, Formula::quantify(conclusion));
Substitution cnfSubst;
auto cls = produceClauses(induction_formula, templ->rule, context, cnfSubst);
for (const auto& [v,t] : iterTraits(cnfSubst.items())) {
ALWAYS(subst.unify(TermList::var(v),INDUCTION_CLAUSE_BANK,t,INDUCTION_CLAUSE_BANK));
}
e->add(std::move(cls), std::move(subst));
}
void InductionClauseIterator::performStructInductionFreeVar(const InductionContext& context, InductionFormulaIndex::Entry* e)
{
ASS_EQ(context._indTerms.size(), 1);
TermList sort = SortHelper::getResultSort(context._indTerms[0]);
TermAlgebra* ta = env.signature->getTermAlgebraOfSort(sort);
SynthesisALManager* synthMan = static_cast<SynthesisALManager*>((env.options->questionAnswering() == Options::QuestionAnsweringMode::SYNTHESIS) ? SynthesisALManager::getInstance() : nullptr);
std::vector<Term*> fnHeads;
vector<Shell::SkolemTracker> skolemTrackers;
unsigned numTypeArgs = sort.term()->arity();
unsigned freeVar = context.getFreeVariable(); unsigned var = freeVar + 1 + (synthMan ? synthMan->numInputSkolems() : 0); for (const auto& kv : context._cls) {
if (kv.first->maxVar() + 1 > var) {
var = kv.first->maxVar() + 1;
}
}
VList* us = VList::empty();
VList* ws = VList::empty();
VList* ys = VList::empty();
FormulaList* formulas = FormulaList::empty();
for (unsigned i = 0; i < ta->nConstructors(); i++){
TermAlgebraConstructor* con = ta->constructor(i);
unsigned arity = con->arity();
TermStack argTerms(arity); argTerms.loadFromIterator(Term::Iterator(sort.term()));
FormulaList* hyps = FormulaList::empty();
for (unsigned j = numTypeArgs; j < arity; j++){
TermList y(var++, false);
argTerms.push(y);
VList::push(y.var(), ys);
if (con->argSort(j) == con->rangeSort()){
unsigned w = var++;
VList::push(w, ws);
Formula* curLit = context.getFormulaWithFreeVar(y, freeVar, w);
FormulaList::push(curLit, hyps); if (synthMan) {
skolemTrackers.emplace_back(Binding(w, nullptr), i, true, j);
}
}
if (synthMan) {
skolemTrackers.emplace_back(Binding(y.var(), nullptr), i, false, j);
}
}
unsigned u = var++;
VList::push(u, us);
Term* tcons = Term::create(con->functor(), (unsigned)argTerms.size(), argTerms.begin());
if (synthMan) {
fnHeads.push_back(tcons);
}
Formula* consequent = context.getFormulaWithFreeVar({ TermList(tcons) }, freeVar, u);
Formula* step = (VList::isEmpty(ws)) ? consequent :
new BinaryFormula(Connective::IMP, JunctionFormula::generalJunction(Connective::AND, hyps), consequent); formulas->push(step, formulas);
}
Formula* formula = new JunctionFormula(Connective::AND, formulas);
TermList z(var++, false);
unsigned x = var++;
RobSubstitution subst;
Formula* conclusion = context.getFormulaWithFreeVar(z, freeVar, x, &subst);
formula = new BinaryFormula(Connective::IMP, formula, conclusion);
formula = new QuantifiedFormula(Connective::EXISTS, VList::singleton(x), SList::empty(), formula);
formula = new QuantifiedFormula(Connective::FORALL, VList::singleton(z.var()), SList::empty(), formula);
formula = new QuantifiedFormula(Connective::FORALL, us, SList::empty(), formula);
if (!VList::isEmpty(ws)) {
formula = new QuantifiedFormula(Connective::EXISTS, ws, SList::empty(), formula);
}
if (!VList::isEmpty(ys)) {
formula = new QuantifiedFormula(Connective::EXISTS, ys, SList::empty(), formula);
}
Substitution cnfSubst;
ClauseStack hyp_clauses = produceClauses(formula, InferenceRule::STRUCT_INDUCTION_AXIOM_ONE, context, cnfSubst);
TermList xSkolem(cnfSubst.apply(x));
ASS(xSkolem.isTerm());
ASS_EQ(*(xSkolem.term()->nthArgument(xSkolem.term()->arity()-1)), z);
ALWAYS(subst.unify(TermList::var(freeVar),NUM_SPECIAL_BANKS,xSkolem,INDUCTION_CLAUSE_BANK));
if (synthMan) {
synthMan->registerSkolemSymbols(xSkolem.term(), cnfSubst, fnHeads, skolemTrackers, us);
}
e->add(std::move(hyp_clauses), std::move(subst));
}
bool InductionClauseIterator::notDoneInt(InductionContext context, Literal* bound1, Literal* bound2, InductionFormulaIndex::Entry*& e)
{
ASS_EQ(context._indTerms.size(), 1);
TermList ph(getPlaceholderForTerm(context._indTerms,0));
Literal* b1 = nullptr;
Literal* b2 = nullptr;
if (bound1) {
b1 = Literal::create2(bound1->functor(), bound1->polarity(),
bound1->polarity() ? *bound1->nthArgument(0) : ph,
bound1->polarity() ? ph : *bound1->nthArgument(1));
}
if (bound2) {
b2 = Literal::create2(bound2->functor(), bound2->polarity(),
bound2->polarity() ? ph : *bound2->nthArgument(0),
bound2->polarity() ? *bound2->nthArgument(1) : ph);
}
return _formulaIndex.findOrInsert(context, e, b1, b2);
}
bool InductionClauseIterator::isValidBound(const InductionContext& context, const Bound& bound)
{
ASS_EQ(context._indTerms.size(), 1);
Term* pt = getPlaceholderForTerm(context._indTerms,0);
for (const auto& kv : context._cls) {
for (const auto& lit : kv.second) {
ASS((lit != nullptr) && (kv.first != nullptr));
Term* otherArg = InductionHelper::getOtherTermFromComparison(lit, pt);
if (!bound.match(
[&](TermLiteralClause const& bound)
{ return InductionHelper::isValidBound(otherArg, kv.first, bound); },
[&](DefaultBound const& bound)
{ return InductionHelper::isValidForDefaultBound(otherArg, kv.first, bound.term); }
)) {
return false;
}
}
}
return true;
}
}