cvc5-cvc5-1.2.0.src.theory.uf.lambda_lift.cpp Maven / Gradle / Ivy
The newest version!
/******************************************************************************
* Top contributors (to current version):
* Andrew Reynolds, Aina Niemetz, Hans-Jörg Schurr
*
* This file is part of the cvc5 project.
*
* Copyright (c) 2009-2024 by the authors listed in the file AUTHORS
* in the top-level source directory and their institutional affiliations.
* All rights reserved. See the file COPYING in the top-level source
* directory for licensing information.
* ****************************************************************************
*
* Implementation of lambda lifting.
*/
#include "theory/uf/lambda_lift.h"
#include "expr/node_algorithm.h"
#include "expr/skolem_manager.h"
#include "options/uf_options.h"
#include "smt/env.h"
#include "theory/uf/function_const.h"
#include "expr/sort_type_size.h"
using namespace cvc5::internal::kind;
namespace cvc5::internal {
namespace theory {
namespace uf {
LambdaLift::LambdaLift(Env& env)
: EnvObj(env),
d_lifted(userContext()),
d_lambdaMap(userContext()),
d_epg(env.isTheoryProofProducing()
? new EagerProofGenerator(env, userContext(), "LambdaLift::epg")
: nullptr)
{
}
TrustNode LambdaLift::lift(Node node)
{
if (d_lifted.find(node) != d_lifted.end())
{
return TrustNode::null();
}
d_lifted.insert(node);
Node assertion = getAssertionFor(node);
if (assertion.isNull())
{
return TrustNode::null();
}
// if no proofs, return lemma with no generator
if (d_epg == nullptr)
{
return TrustNode::mkTrustLemma(assertion);
}
return d_epg->mkTrustNode(
assertion, ProofRule::MACRO_SR_PRED_INTRO, {}, {assertion});
}
bool LambdaLift::isLifted(const Node& node) const
{
return d_lifted.find(node)!=d_lifted.end();
}
TrustNode LambdaLift::ppRewrite(Node node, std::vector& lems)
{
Node lam = FunctionConst::toLambda(node);
TNode skolem = getSkolemFor(lam);
if (skolem.isNull())
{
return TrustNode::null();
}
d_lambdaMap[skolem] = lam;
bool shouldLift = true;
if (options().uf.ufHoLazyLambdaLift)
{
Trace("uf-lazy-ll") << "Lift " << lam << "?" << std::endl;
shouldLift = false;
// Model construction considers types in order of their type size
// (SortTypeSize::getTypeSize). If the lambda has a free variable, that
// comes later in the model construction, it must be lifted eagerly.
// As an example, say f : Int -> Int, g : Int x Int -> Int
// The following lambdas require eager lifting:
// - (lambda ((x Int)) (g x x))
// - (lambda ((x Int) (y Int)) (f (g x y)))
// The following lambads do not require eager lifting:
// - (lambda ((x Int)) (+ x 1)), since it has no free symbols.
// - (lambda ((x Int) (y Int)) (f x)), since its free symbol f has a type
// Int -> Int which is processed before the type of the lambda, i.e.
// Int x Int -> Int.
std::unordered_set syms;
expr::getSymbols(lam[1], syms);
SortTypeSize sts;
size_t lsize = sts.getTypeSize(lam.getType());
for (const Node& v : syms)
{
TypeNode tn = v.getType();
if (!tn.isFirstClass())
{
// don't need to worry about constructor/selector/testers/etc.
continue;
}
size_t vsize = sts.getTypeSize(tn);
if (vsize>=lsize)
{
shouldLift = true;
Trace("uf-lazy-ll") << "...yes due to " << v << std::endl;
break;
}
}
}
if (shouldLift)
{
TrustNode trn = lift(lam);
if (!trn.isNull())
{
lems.push_back(SkolemLemma(trn, skolem));
}
}
// if no proofs, return lemma with no generator
if (d_epg == nullptr)
{
return TrustNode::mkTrustRewrite(node, skolem);
}
Node eq = node.eqNode(skolem);
return d_epg->mkTrustedRewrite(
node, skolem, ProofRule::MACRO_SR_PRED_INTRO, {eq});
}
Node LambdaLift::getLambdaFor(TNode skolem) const
{
NodeNodeMap::const_iterator it = d_lambdaMap.find(skolem);
if (it == d_lambdaMap.end())
{
return Node::null();
}
return it->second;
}
bool LambdaLift::isLambdaFunction(TNode n) const
{
return !getLambdaFor(n).isNull();
}
Node LambdaLift::getAssertionFor(TNode node)
{
TNode skolem = getSkolemFor(node);
if (skolem.isNull())
{
return Node::null();
}
Node assertion;
Node lambda = FunctionConst::toLambda(node);
if (!lambda.isNull())
{
NodeManager* nm = NodeManager::currentNM();
// The new assertion
std::vector children;
// bound variable list
children.push_back(lambda[0]);
// body
std::vector skolem_app_c;
skolem_app_c.push_back(skolem);
skolem_app_c.insert(skolem_app_c.end(), lambda[0].begin(), lambda[0].end());
Node skolem_app = nm->mkNode(Kind::APPLY_UF, skolem_app_c);
skolem_app_c[0] = lambda;
Node rhs = nm->mkNode(Kind::APPLY_UF, skolem_app_c);
// For the sake of proofs, we use
// (= (k t1 ... tn) ((lambda (x1 ... xn) s) t1 ... tn)) here. This is instead of
// (= (k t1 ... tn) s); the former is more accurate since
// beta reduction uses capture-avoiding substitution, which implies that
// ((lambda (y1 ... yn) s) t1 ... tn) is alpha-equivalent but not
// necessarily syntactical equal to s.
children.push_back(skolem_app.eqNode(rhs));
// axiom defining skolem
assertion = nm->mkNode(Kind::FORALL, children);
// Lambda lifting is trivial to justify, hence we don't set a proof
// generator here. In particular, replacing the skolem introduced
// here with its original lambda ensures the new assertion rewrites
// to true.
// For example, if (lambda y. t[y]) has skolem k, then this lemma is:
// forall x. k(x)=t[x]
// whose witness form rewrites
// forall x. (lambda y. t[y])(x)=t[x] --> forall x. t[x]=t[x] --> true
}
return assertion;
}
Node LambdaLift::getSkolemFor(TNode node)
{
Node skolem;
Kind k = node.getKind();
if (k == Kind::LAMBDA)
{
// if a lambda, return the purification variable for the node. We ignore
// lambdas with free variables, which can occur beneath quantifiers
// during preprocessing.
if (!expr::hasFreeVar(node))
{
Trace("rtf-proof-debug")
<< "RemoveTermFormulas::run: make LAMBDA skolem" << std::endl;
// Make the skolem to represent the lambda
NodeManager* nm = NodeManager::currentNM();
SkolemManager* sm = nm->getSkolemManager();
skolem = sm->mkPurifySkolem(node);
}
}
return skolem;
}
TrustNode LambdaLift::betaReduce(TNode node) const
{
Kind k = node.getKind();
if (k == Kind::APPLY_UF)
{
Node op = node.getOperator();
Node opl = getLambdaFor(op);
if (!opl.isNull())
{
std::vector args(node.begin(), node.end());
Node app = betaReduce(opl, args);
Trace("uf-lazy-ll") << "Beta reduce: " << node << " -> " << app
<< std::endl;
if (d_epg == nullptr)
{
return TrustNode::mkTrustRewrite(node, app);
}
return d_epg->mkTrustedRewrite(
node, app, ProofRule::MACRO_SR_PRED_INTRO, {node.eqNode(app)});
}
}
// otherwise, unchanged
return TrustNode::null();
}
Node LambdaLift::betaReduce(TNode lam, const std::vector& args) const
{
Assert(lam.getKind() == Kind::LAMBDA);
NodeManager* nm = NodeManager::currentNM();
std::vector betaRed;
betaRed.push_back(lam);
betaRed.insert(betaRed.end(), args.begin(), args.end());
Node app = nm->mkNode(Kind::APPLY_UF, betaRed);
app = rewrite(app);
return app;
}
} // namespace uf
} // namespace theory
} // namespace cvc5::internal