#include <sstream>
#include "SimpleCongruenceClosure.hpp"
#include "Lib/ArrayMap.hpp"
#include "Lib/IntUnionFind.hpp"
#include "Lib/DynamicHeap.hpp"
#include "Kernel/SortHelper.hpp"
#include "Shell/DistinctProcessor.hpp"
namespace DP
{
using namespace std;
const unsigned SimpleCongruenceClosure::NO_SIG_SYMBOL = 0xFFFFFFFF;
std::string SimpleCongruenceClosure::CEq::toString() const
{
std::ostringstream res;
res << c1<<"="<<c2<<" implied by ";
if(foOrigin) {
if(foPremise) {
res << (*foPremise);
}
else {
res << "built-in true!=false";
}
}
else {
res << "congruence";
}
return res.str();
}
std::string SimpleCongruenceClosure::CEq::toString(SimpleCongruenceClosure& parent) const
{
std::ostringstream res;
res << c1<<"="<<c2<<" implied by ";
if(foOrigin) {
if(foPremise) {
res << (*foPremise);
}
else {
res << "built-in true!=false";
}
}
else {
CPair p1= parent._cInfos[c1].namedPair;
CPair p2= parent._cInfos[c2].namedPair;
res << "congruence of ("<<p1.first<<","<<p1.second<<") and ("<<p2.first<<","<<p2.second<<")";
}
return res.str();
}
void SimpleCongruenceClosure::ConstInfo::init() {
sigSymbol = NO_SIG_SYMBOL;
term = TermList::empty();
lit = 0;
namedPair = CPair(0,0);
reprConst = 0;
proofPredecessor = 0;
predecessorPremise = CEq(0,0);
classList.reset();
useList.reset();
}
void SimpleCongruenceClosure::ConstInfo::resetEquivalences(SimpleCongruenceClosure& parent, unsigned selfIndex) {
reprConst = 0;
proofPredecessor = 0;
predecessorPremise = CEq(0,0);
classList.reset();
static ArraySet seen;
seen.ensure(parent.getMaxConst()+1);
seen.reset();
Stack<unsigned>::DelIterator ulit(useList);
while(ulit.hasNext()) {
unsigned p = ulit.next();
ConstInfo& pInfo = parent._cInfos[p];
if(pInfo.namedPair.first!=selfIndex && pInfo.namedPair.second!=selfIndex) {
ulit.del();
continue;
}
if(seen.find(p)) {
ulit.del();
continue;
}
seen.insert(p);
}
}
#if VDEBUG
void SimpleCongruenceClosure::ConstInfo::assertValid(SimpleCongruenceClosure& parent, unsigned selfIndex) const
{
if(reprConst==0) {
Stack<unsigned>::ConstIterator ulit(useList);
while(ulit.hasNext()) {
unsigned p = ulit.next();
ConstInfo& pInfo = parent._cInfos[p];
CPair pPair = pInfo.namedPair;
ASS_NEQ(pPair.first,0);
ASS_NEQ(pPair.second,0);
CPair derefPair = parent.deref(pPair);
ASS(derefPair.first==selfIndex || derefPair.second==selfIndex);
}
}
}
#endif
SimpleCongruenceClosure::SimpleCongruenceClosure(Ordering* ord) :
_ord(ord)
{
_cInfos.ensure(1);
_posLitConst = getFreshConst();
_negLitConst = getFreshConst();
_negEqualities.push(CEq(_posLitConst, _negLitConst, 0));
_hadPropagated = false;
}
void SimpleCongruenceClosure::reset()
{
unsigned maxConst = getMaxConst();
for(unsigned i=1; i<=maxConst; i++) {
_cInfos[i].resetEquivalences(*this, i);
}
PairMap::DelIterator pmit(_pairNames);
while(pmit.hasNext()) {
CPair namePair;
unsigned nameConst;
pmit.next(namePair, nameConst);
if(_cInfos[nameConst].namedPair!=namePair) {
pmit.del();
}
}
_negEqualities.truncate(1);
ASS_EQ(_negEqualities.top().c1,_posLitConst);
ASS_EQ(_negEqualities.top().c2,_negLitConst);
_unsatEqs.reset();
_pendingEqualities.reset();
_distinctConstraints.reset();
_negDistinctConstraints.reset();
_hadPropagated = false;
}
unsigned SimpleCongruenceClosure::getFreshConst()
{
unsigned res = _cInfos.size();
_cInfos.expand(res+1);
_cInfos[res].init();
return res;
}
unsigned SimpleCongruenceClosure::getSignatureConst(unsigned symbol, SignatureKind kind)
{
unsigned* pRes;
if(!_sigConsts.getValuePtr(make_pair(symbol, kind), pRes)) {
return *pRes;
}
unsigned res = getFreshConst();
_cInfos[res].sigSymbol = symbol;
_cInfos[res].sigSymKind = kind;
*pRes = res;
return res;
}
unsigned SimpleCongruenceClosure::getPairName(CPair p)
{
unsigned* pRes;
if(!_pairNames.getValuePtr(p, pRes)) {
return *pRes;
}
unsigned res = getFreshConst();
_cInfos[res].namedPair = p;
*pRes = res;
_cInfos[p.first].useList.push(res);
if(_cInfos[p.first].reprConst!=0) {
unsigned fRepr = _cInfos[p.first].reprConst;
_cInfos[fRepr].useList.push(res);
}
_cInfos[p.second].useList.push(res);
if(_cInfos[p.second].reprConst!=0) {
unsigned sRepr = _cInfos[p.second].reprConst;
_cInfos[sRepr].useList.push(res);
}
return res;
}
struct Memo {
Option<unsigned> get(TermList t) {
unsigned cached;
return termNames.find(t, cached) ? some(cached) : none<unsigned>();
}
template<class Init> unsigned getOrInit(TermList orig, Init init) {
unsigned cached;
if(termNames.find(orig, cached))
return cached;
unsigned result = init();
termNames.insert(orig, result);
return result;
}
DHMap<TermList, unsigned> &termNames;
};
unsigned SimpleCongruenceClosure::convertFO(TermList trm)
{
unsigned cached;
if(_termNames.find(trm, cached))
return cached;
return BottomUpEvaluation<TermList, unsigned>()
.context(TermListContext {.ignoreTypeArgs = false})
.function([&](TermList t, unsigned *children) {
unsigned res;
if(t.isVar()) {
res = getSignatureConst(t.var(), SignatureKind::VARIABLE);
}
else {
ASS(t.isTerm());
Term *trm = t.term();
SignatureKind sk = trm->isSort() ? SignatureKind::TYPECON : SignatureKind::FUNCTION;
res = getSignatureConst(trm->functor(), sk);
for(size_t i = 0; i < trm->arity(); i++) {
res = getPairName(CPair(res, children[i]));
}
}
_cInfos[res].term = t;
return res;
})
.memo(Memo {_termNames})
.apply(trm);
}
unsigned SimpleCongruenceClosure::convertFONonEquality(Literal* lit)
{
ASS(!lit->isEquality());
unsigned res;
if(_litNames.find(lit, res)) {
return res;
}
if(_litNames.find(Literal::complementaryLiteral(lit), res)) {
_litNames.insert(lit, res);
return res;
}
res = getSignatureConst(lit->functor(), SignatureKind::PREDICATE);
Term::Iterator ait(lit);
while(ait.hasNext()) {
TermList a = ait.next();
unsigned argConst = convertFO(a);
res = getPairName(CPair(res, argConst));
}
_cInfos[res].lit = lit;
_litNames.insert(lit, res);
return res;
}
bool SimpleCongruenceClosure::isDistinctPred(Literal* l)
{
return Shell::DistinctProcessor::isDistinctPred(l);
}
void SimpleCongruenceClosure::readDistinct(Literal* lit)
{
bool pos = lit->isPositive();
DistinctStack& tgtDStack = pos ? _distinctConstraints : _negDistinctConstraints;
tgtDStack.push(DistinctEntry(lit));
Stack<unsigned>& tgtStack = tgtDStack.top()._consts;
Literal::Iterator ait(lit);
while(ait.hasNext()) {
TermList arg = ait.next();
unsigned cNum = convertFO(arg);
tgtStack.push(cNum);
}
}
SimpleCongruenceClosure::CEq SimpleCongruenceClosure::convertFOEquality(Literal* equality)
{
ASS(equality->isEquality());
unsigned arg1 = convertFO(*equality->nthArgument(0));
unsigned arg2 = convertFO(*equality->nthArgument(1));
return CEq(arg1, arg2, equality);
}
void SimpleCongruenceClosure::addLiterals(LiteralIterator lits, bool onlyEqualites)
{
ASS(!_hadPropagated);
while(lits.hasNext()) {
Literal* l = lits.next();
if(!l->ground()) {
continue;
}
if (!onlyEqualites || (l->isEquality() && l->isPositive())) {
addLiteral(l);
}
}
}
void SimpleCongruenceClosure::addLiteral(Literal* lit)
{
if (lit->isEquality()) {
CEq eq = convertFOEquality(lit);
if (lit->isPositive()) {
addPendingEquality(eq);
} else {
_negEqualities.push(eq);
}
} else if(isDistinctPred(lit)) {
readDistinct(lit);
} else {
unsigned predConst = convertFONonEquality(lit);
CEq eq;
if(lit->isPositive()) {
eq = CEq(predConst, _posLitConst, lit);
}
else {
eq = CEq(predConst, _negLitConst, lit);
}
addPendingEquality(eq);
}
}
void SimpleCongruenceClosure::addPendingEquality(CEq eq) {
ASS_G(eq.c1,0);
ASS_G(eq.c2,0);
_pendingEqualities.push_back(eq);
}
void SimpleCongruenceClosure::makeProofRepresentant(unsigned c)
{
if(_cInfos[c].proofPredecessor==0) {
return;
}
CEq transfPrem; unsigned prevC = 0;
do{
unsigned newC = _cInfos[c].proofPredecessor;
_cInfos[c].proofPredecessor = prevC;
swap(_cInfos[c].predecessorPremise, transfPrem);
prevC = c;
c = newC;
} while(c!=0);
ASS(transfPrem.isInvalid()); }
void SimpleCongruenceClosure::propagate()
{
_hadPropagated = true;
while(_pendingEqualities.isNonEmpty()) {
CEq curr0 = _pendingEqualities.pop_back();
CPair curr = deref(curr0);
if(curr.first==curr.second) {
continue;
}
if(getClassSize(curr.first)>getClassSize(curr.second)) {
std::swap(curr0.c1, curr0.c2);
std::swap(curr.first, curr.second);
}
{
unsigned aProofRep = curr0.c1;
unsigned bProofRep = curr0.c2;
makeProofRepresentant(aProofRep);
ConstInfo& aProofInfo = _cInfos[aProofRep];
ASS_EQ(aProofInfo.proofPredecessor,0);
aProofInfo.proofPredecessor = bProofRep;
aProofInfo.predecessorPremise = curr0;
}
unsigned aRep = curr.first;
unsigned bRep = curr.second;
ConstInfo& aInfo = _cInfos[aRep];
ConstInfo& bInfo = _cInfos[bRep];
ASS_EQ(aInfo.reprConst,0); ASS_EQ(bInfo.reprConst,0);
DEBUG_CODE( aInfo.assertValid(*this, aRep); );
DEBUG_CODE( bInfo.assertValid(*this, bRep); );
aInfo.reprConst = bRep;
bInfo.classList.push(aRep);
Stack<unsigned>::Iterator aChildIt(aInfo.classList);
while(aChildIt.hasNext()) {
unsigned aChild = aChildIt.next();
bInfo.classList.push(aChild);
_cInfos[aChild].reprConst = bRep;
}
Stack<unsigned>::Iterator aUseIt(aInfo.useList);
while(aUseIt.hasNext()) {
unsigned usePairConst = aUseIt.next();
CPair usedPair = _cInfos[usePairConst].namedPair;
ASS(usedPair!=CPair(0,0)); CPair derefPair = deref(usedPair);
ASS(usedPair!=derefPair);
unsigned* pDerefPairName;
if(!_pairNames.getValuePtr(derefPair, pDerefPairName)) {
addPendingEquality(CEq(*pDerefPairName, usePairConst));
}
else {
*pDerefPairName = usePairConst;
bInfo.useList.push(usePairConst);
}
}
}
}
bool SimpleCongruenceClosure::checkPositiveDistincts(bool retrieveMultipleCores)
{
static ArrayMap<unsigned> reprs;
reprs.ensure(getMaxConst()+1);
bool foundConflict = false;
DistinctStack::BottomFirstIterator distIt(_distinctConstraints);
while(distIt.hasNext()) {
const DistinctEntry& grp = distIt.next();
reprs.reset();
Stack<unsigned>::ConstIterator git(grp._consts);
while(git.hasNext()) {
unsigned c = git.next();
unsigned rep = deref(c);
unsigned c2;
if(reprs.find(rep, c2)) {
_unsatEqs.push(CEq(c, c2, grp._lit));
if(!retrieveMultipleCores) {
return false;
}
foundConflict = true;
}
else {
reprs.insert(rep, c);
}
}
}
return !foundConflict;
}
DecisionProcedure::Status SimpleCongruenceClosure::checkNegativeDistincts(bool retrieveMultipleCores)
{
static ArrayMap<unsigned> reprs;
reprs.ensure(getMaxConst()+1);
DistinctStack::BottomFirstIterator distIt(_negDistinctConstraints);
while(distIt.hasNext()) {
const DistinctEntry& grp = distIt.next();
reprs.reset();
bool isFalse = false;
Stack<unsigned>::ConstIterator git(grp._consts);
while(git.hasNext()) {
unsigned c = git.next();
unsigned rep = deref(c);
if(reprs.find(rep)) {
isFalse = true;
continue; }
reprs.insert(rep, c);
}
if(!isFalse) {
return DecisionProcedure::UNKNOWN;
}
}
return DecisionProcedure::SATISFIABLE;
}
DecisionProcedure::Status SimpleCongruenceClosure::getStatus(bool retrieveMultipleCores)
{
propagate();
if(!checkPositiveDistincts(retrieveMultipleCores)) {
if(!retrieveMultipleCores) {
return DecisionProcedure::UNSATISFIABLE;
}
ASS(_unsatEqs.isNonEmpty());
}
Stack<CEq>::BottomFirstIterator neqIt(_negEqualities);
while(neqIt.hasNext()) {
CEq neq = neqIt.next();
CPair derNEq = deref(neq);
if(derNEq.first==derNEq.second) {
_unsatEqs.push(neq);
if(!retrieveMultipleCores) {
return DecisionProcedure::UNSATISFIABLE;
}
}
}
DecisionProcedure::Status ndStatus = checkNegativeDistincts(retrieveMultipleCores);
if(_unsatEqs.isNonEmpty()) {
return DecisionProcedure::UNSATISFIABLE;
}
if(ndStatus==DecisionProcedure::UNKNOWN) {
return DecisionProcedure::UNKNOWN;
}
return DecisionProcedure::SATISFIABLE;
}
unsigned SimpleCongruenceClosure::getProofDepth(unsigned c)
{
unsigned res = 0;
while(_cInfos[c].proofPredecessor!=0) {
c = _cInfos[c].proofPredecessor;
res++;
}
return res;
}
void SimpleCongruenceClosure::collectUnifyingPath(unsigned c1, unsigned c2, Stack<unsigned>& path)
{
ASS_EQ(deref(c1), deref(c2));
unsigned depth1 = getProofDepth(c1);
unsigned depth2 = getProofDepth(c2);
if(depth1<depth2) {
swap(c1,c2);
swap(depth1,depth2);
}
while(depth1>depth2) {
path.push(c1);
c1 = _cInfos[c1].proofPredecessor;
depth1--;
}
#if VDEBUG
unsigned depth=depth1;
#endif
while(c1!=c2) {
#if VDEBUG
ASS_G(depth,0);
depth--;
#endif
path.push(c1);
c1 = _cInfos[c1].proofPredecessor;
path.push(c2);
c2 = _cInfos[c2].proofPredecessor;
}
}
void SimpleCongruenceClosure::getUnsatCore(LiteralStack& res, unsigned coreIndex)
{
ASS(res.isEmpty());
ASS_L(coreIndex,_unsatEqs.size());
CEq unsatEq = _unsatEqs[coreIndex];
ASS(unsatEq.foOrigin);
if(unsatEq.foPremise) {
res.push(unsatEq.foPremise);
}
static Stack<CPair> toExplain;
toExplain.push(CPair(unsatEq.c1, unsatEq.c2));
ASS_EQ(deref(toExplain.top().first), deref(toExplain.top().second));
IntUnionFind explained(getMaxConst()+1);
static Stack<unsigned> pathStack;
while(toExplain.isNonEmpty()) {
CPair curr = toExplain.pop();
ASS_EQ(deref(curr.first), deref(curr.second));
if(explained.root(curr.first)==explained.root(curr.second)) {
continue;
}
pathStack.reset();
collectUnifyingPath(curr.first, curr.second, pathStack);
while(pathStack.isNonEmpty()) {
unsigned proofStepConst = pathStack.pop();
CEq& prem = _cInfos[proofStepConst].predecessorPremise;
if(explained.root(prem.c1)==explained.root(prem.c2)) {
continue;
}
if(prem.foOrigin) {
if(prem.foPremise) {
res.push(prem.foPremise);
} }
else {
CPair cp1 = _cInfos[prem.c1].namedPair;
CPair cp2 = _cInfos[prem.c2].namedPair;
ASS_NEQ(cp1.first,0);
ASS_NEQ(cp1.second,0);
ASS_NEQ(cp2.first,0);
ASS_NEQ(cp2.second,0);
toExplain.push(CPair(cp1.first, cp2.first));
ASS_EQ(deref(toExplain.top().first), deref(toExplain.top().second));
toExplain.push(CPair(cp1.second, cp2.second));
ASS_EQ(deref(toExplain.top().first), deref(toExplain.top().second));
}
explained.doUnion(prem.c1, prem.c2);
}
}
}
struct SimpleCongruenceClosure::ConstOrderingComparator {
ConstOrderingComparator(DArray<ConstInfo>& cInfos, Ordering& ord)
: _cInfos(cInfos), _ord(ord) {}
Comparison compare(unsigned c1, unsigned c2)
{
TermList c1NF = _cInfos[c1].normalForm;
TermList c2NF = _cInfos[c2].normalForm;
if (c1NF.isEmpty()) {
if (c2NF.isEmpty()) {
return EQUAL;
} else {
return LESS;
}
} else {
if (c2NF.isEmpty()) {
return GREATER;
} else {
if(c1NF.term()->isSort() && !c2NF.term()->isSort()){
return LESS;
} else if(c2NF.term()->isSort() && !c1NF.term()->isSort()) {
return GREATER;
}
switch(_ord.compare(c1NF,c2NF)) {
case Ordering::Result::GREATER:
return GREATER;
case Ordering::Result::LESS:
return LESS;
case Ordering::Result::EQUAL:
return EQUAL;
default:
ASSERTION_VIOLATION;
}
}
}
}
DArray<ConstInfo>& _cInfos;
Ordering& _ord;
};
void SimpleCongruenceClosure::computeConstsNormalForm(unsigned c, NFMap& normalForms)
{
ConstInfo& cInfo = _cInfos[c];
if (!cInfo.term.isEmpty() && cInfo.normalForm.isEmpty()) {
Term* t = cInfo.term.term();
unsigned idx = t->arity();
unsigned d = c;
static DArray<TermList> args(8);
args.ensure(idx);
while (idx-->0) {
CPair pair = _cInfos[d].namedPair;
ASS(pair !=CPair(0,0));
args[idx] = normalForms.get(deref(pair.second));
ASS(args[idx].isTerm());
d = pair.first;
}
ASS_EQ(_cInfos[d].sigSymbol,t->functor());
if(t->isSort()){
cInfo.normalForm = TermList(AtomicSort::create(static_cast<AtomicSort*>(t),args.array()));
} else {
cInfo.normalForm = TermList(Term::create(t,args.array()));
}
}
}
void SimpleCongruenceClosure::getModel(LiteralStack& model)
{
static DynamicHeap<unsigned, ConstOrderingComparator, ArrayMap<unsigned> >
candidates(ConstOrderingComparator(_cInfos,*_ord));
ASS(candidates.isEmpty());
static NFMap normalForms;
normalForms.reset();
unsigned maxConst = getMaxConst();
candidates.elMap().expand(maxConst+1);
for (unsigned c = 3; c <= maxConst; c++) {
ConstInfo& cInfo = _cInfos[c];
cInfo.processed = false;
cInfo.half_normalized = false;
cInfo.normalForm = TermList::empty();
if (cInfo.sigSymbol != NO_SIG_SYMBOL) {
cInfo.normalForm = cInfo.term;
candidates.insert(c);
} else { ASS_NEQ(cInfo.namedPair.first,0);
ASS_NEQ(cInfo.namedPair.second,0);
unsigned lRep = deref(cInfo.namedPair.first);
unsigned rRep = deref(cInfo.namedPair.second);
ASS_NEQ(lRep,c); _cInfos[lRep].upEdges.push(c);
if (rRep != deref(c)) { _cInfos[rRep].upEdges.push(c);
}
}
}
while (!candidates.isEmpty()) {
unsigned c = candidates.pop();
unsigned r = deref(c);
ConstInfo& rInfo = _cInfos[r];
if (!rInfo.processed) {
ConstInfo& cInfo = _cInfos[c];
rInfo.processed = true;
if (!cInfo.normalForm.isEmpty()) { ALWAYS(normalForms.insert(r,cInfo.normalForm));
}
Stack<unsigned>::Iterator rUseIt(rInfo.upEdges);
while(rUseIt.hasNext()) {
unsigned s = rUseIt.next();
if (_cInfos[deref(s)].processed) {
continue;
}
ConstInfo& sInfo = _cInfos[s];
ASS(sInfo.namedPair!=CPair(0,0));
if (sInfo.half_normalized) {
computeConstsNormalForm(s,normalForms);
candidates.insert(s);
} else {
sInfo.half_normalized = true;
}
}
rInfo.upEdges.reset();
}
}
NFMap::Iterator nfIt(normalForms);
while(nfIt.hasNext()) {
unsigned r;
TermList nf;
nfIt.next(r,nf);
static DHSet<TermList> seen;
seen.reset();
seen.insert(nf);
computeConstsNormalForm(r,normalForms); if (!seen.contains(_cInfos[r].normalForm)) {
model.push(Literal::createEquality(true,_cInfos[r].normalForm,nf,SortHelper::getResultSort(nf.term())));
seen.insert(_cInfos[r].normalForm);
}
Stack<unsigned>::Iterator classIt(_cInfos[r].classList);
while (classIt.hasNext()) {
unsigned c = classIt.next();
computeConstsNormalForm(c,normalForms); if (!seen.contains(_cInfos[c].normalForm)) {
model.push(Literal::createEquality(true,_cInfos[c].normalForm,nf,SortHelper::getResultSort(nf.term())));
seen.insert(_cInfos[c].normalForm);
}
}
}
DEBUG_CODE( assertModelInfoClean(); );
}
#if VDEBUG
void SimpleCongruenceClosure::assertModelInfoClean() const
{
unsigned maxConst = getMaxConst();
for (unsigned c = 0; c <= maxConst; c++) {
const ConstInfo& cInfo = _cInfos[c];
ASS(cInfo.upEdges.isEmpty());
}
}
#endif
}