mirror of
https://github.com/Z3Prover/z3
synced 2025-04-13 04:28:17 +00:00
2800 lines
88 KiB
C++
Executable file
2800 lines
88 KiB
C++
Executable file
/*++
|
|
Copyright (c) 2012 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
duality_solver.h
|
|
|
|
Abstract:
|
|
|
|
implements relational post-fixedpoint problem
|
|
(RPFP) solver
|
|
|
|
Author:
|
|
|
|
Ken McMillan (kenmcmil)
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(disable:4996)
|
|
#pragma warning(disable:4800)
|
|
#pragma warning(disable:4267)
|
|
#endif
|
|
|
|
#include "duality.h"
|
|
#include "duality_profiling.h"
|
|
|
|
#include <stdio.h>
|
|
#include <set>
|
|
#include <map>
|
|
#include <list>
|
|
#include <iterator>
|
|
|
|
// TODO: make these official options or get rid of them
|
|
|
|
#define NEW_CAND_SEL
|
|
// #define LOCALIZE_CONJECTURES
|
|
// #define CANDS_FROM_UPDATES
|
|
#define CANDS_FROM_COVER_FAIL
|
|
#define DEPTH_FIRST_EXPAND
|
|
#define MINIMIZE_CANDIDATES
|
|
// #define MINIMIZE_CANDIDATES_HARDER
|
|
#define BOUNDED
|
|
// #define CHECK_CANDS_FROM_IND_SET
|
|
#define UNDERAPPROX_NODES
|
|
#define NEW_EXPAND
|
|
#define EARLY_EXPAND
|
|
// #define TOP_DOWN
|
|
// #define EFFORT_BOUNDED_STRAT
|
|
#define SKIP_UNDERAPPROX_NODES
|
|
// #define KEEP_EXPANSIONS
|
|
// #define USE_CACHING_RPFP
|
|
// #define PROPAGATE_BEFORE_CHECK
|
|
|
|
#define USE_RPFP_CLONE
|
|
#define USE_NEW_GEN_CANDS
|
|
|
|
//#define NO_PROPAGATE
|
|
//#define NO_GENERALIZE
|
|
//#define NO_DECISIONS
|
|
|
|
namespace Duality {
|
|
|
|
// TODO: must be a better place for this...
|
|
static char string_of_int_buffer[20];
|
|
|
|
static const char *string_of_int(int n){
|
|
sprintf(string_of_int_buffer,"%d",n);
|
|
return string_of_int_buffer;
|
|
}
|
|
|
|
/** Generic object for producing diagnostic output. */
|
|
|
|
class Reporter {
|
|
protected:
|
|
RPFP *rpfp;
|
|
public:
|
|
Reporter(RPFP *_rpfp){
|
|
rpfp = _rpfp;
|
|
}
|
|
virtual void Extend(RPFP::Node *node){}
|
|
virtual void Update(RPFP::Node *node, const RPFP::Transformer &update){}
|
|
virtual void Bound(RPFP::Node *node){}
|
|
virtual void Expand(RPFP::Edge *edge){}
|
|
virtual void AddCover(RPFP::Node *covered, std::vector<RPFP::Node *> &covering){}
|
|
virtual void RemoveCover(RPFP::Node *covered, RPFP::Node *covering){}
|
|
virtual void Conjecture(RPFP::Node *node, const RPFP::Transformer &t){}
|
|
virtual void Forcing(RPFP::Node *covered, RPFP::Node *covering){}
|
|
virtual void Dominates(RPFP::Node *node, RPFP::Node *other){}
|
|
virtual void InductionFailure(RPFP::Edge *edge, const std::vector<RPFP::Node *> &children){}
|
|
virtual void UpdateUnderapprox(RPFP::Node *node, const RPFP::Transformer &update){}
|
|
virtual void Reject(RPFP::Edge *edge, const std::vector<RPFP::Node *> &Children){}
|
|
virtual void Message(const std::string &msg){}
|
|
virtual ~Reporter(){}
|
|
};
|
|
|
|
Reporter *CreateStdoutReporter(RPFP *rpfp);
|
|
|
|
/** Object we throw in case of catastrophe. */
|
|
|
|
struct InternalError {
|
|
std::string msg;
|
|
InternalError(const std::string _msg)
|
|
: msg(_msg) {}
|
|
};
|
|
|
|
|
|
/** This is the main solver. It takes anarbitrary (possibly cyclic)
|
|
RPFP and either annotates it with a solution, or returns a
|
|
counterexample derivation in the form of an embedd RPFP tree. */
|
|
|
|
class Duality : public Solver {
|
|
|
|
public:
|
|
Duality(RPFP *_rpfp)
|
|
: ctx(_rpfp->ctx),
|
|
slvr(_rpfp->slvr()),
|
|
nodes(_rpfp->nodes),
|
|
edges(_rpfp->edges)
|
|
{
|
|
rpfp = _rpfp;
|
|
reporter = 0;
|
|
heuristic = 0;
|
|
FullExpand = false;
|
|
NoConj = false;
|
|
FeasibleEdges = true;
|
|
UseUnderapprox = true;
|
|
Report = false;
|
|
StratifiedInlining = false;
|
|
RecursionBound = -1;
|
|
{
|
|
scoped_no_proof no_proofs_please(ctx.m());
|
|
#ifdef USE_RPFP_CLONE
|
|
clone_rpfp = new RPFP_caching(rpfp->ls);
|
|
clone_rpfp->Clone(rpfp);
|
|
#endif
|
|
#ifdef USE_NEW_GEN_CANDS
|
|
gen_cands_rpfp = new RPFP_caching(rpfp->ls);
|
|
gen_cands_rpfp->Clone(rpfp);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
~Duality(){
|
|
#ifdef USE_RPFP_CLONE
|
|
delete clone_rpfp;
|
|
#endif
|
|
#ifdef USE_NEW_GEN_CANDS
|
|
delete gen_cands_rpfp;
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_RPFP_CLONE
|
|
RPFP_caching *clone_rpfp;
|
|
#endif
|
|
#ifdef USE_NEW_GEN_CANDS
|
|
RPFP_caching *gen_cands_rpfp;
|
|
#endif
|
|
|
|
|
|
typedef RPFP::Node Node;
|
|
typedef RPFP::Edge Edge;
|
|
|
|
/** This struct represents a candidate for extending the
|
|
unwinding. It consists of an edge to instantiate
|
|
and a vector of children for the new instance. */
|
|
|
|
struct Candidate {
|
|
Edge *edge; std::vector<Node *>
|
|
Children;
|
|
};
|
|
|
|
/** Comparison operator, allowing us to sort Nodes
|
|
by their number field. */
|
|
|
|
struct lnode
|
|
{
|
|
bool operator()(const Node* s1, const Node* s2) const
|
|
{
|
|
return s1->number < s2->number;
|
|
}
|
|
};
|
|
|
|
typedef std::set<Node *, lnode> Unexpanded; // sorted set of Nodes
|
|
|
|
/** This class provides a heuristic for expanding a derivation
|
|
tree. */
|
|
|
|
class Heuristic {
|
|
RPFP *rpfp;
|
|
|
|
/** Heuristic score for unwinding nodes. Currently this
|
|
counts the number of updates. */
|
|
struct score {
|
|
int updates;
|
|
score() : updates(0) {}
|
|
};
|
|
hash_map<RPFP::Node *,score> scores;
|
|
|
|
public:
|
|
Heuristic(RPFP *_rpfp){
|
|
rpfp = _rpfp;
|
|
}
|
|
|
|
virtual ~Heuristic(){}
|
|
|
|
virtual void Update(RPFP::Node *node){
|
|
scores[node].updates++;
|
|
}
|
|
|
|
/** Heuristic choice of nodes to expand. Takes a set "choices"
|
|
and returns a subset "best". We currently choose the
|
|
nodes with the fewest updates.
|
|
*/
|
|
#if 0
|
|
virtual void ChooseExpand(const std::set<RPFP::Node *> &choices, std::set<RPFP::Node *> &best){
|
|
int best_score = INT_MAX;
|
|
for(std::set<Node *>::iterator it = choices.begin(), en = choices.end(); it != en; ++it){
|
|
Node *node = (*it)->map;
|
|
int score = scores[node].updates;
|
|
best_score = std::min(best_score,score);
|
|
}
|
|
for(std::set<Node *>::iterator it = choices.begin(), en = choices.end(); it != en; ++it)
|
|
if(scores[(*it)->map].updates == best_score)
|
|
best.insert(*it);
|
|
}
|
|
#else
|
|
virtual void ChooseExpand(const std::set<RPFP::Node *> &choices, std::set<RPFP::Node *> &best, bool high_priority=false, bool best_only=false){
|
|
if(high_priority) return;
|
|
int best_score = INT_MAX;
|
|
int worst_score = 0;
|
|
for(std::set<Node *>::iterator it = choices.begin(), en = choices.end(); it != en; ++it){
|
|
Node *node = (*it)->map;
|
|
int score = scores[node].updates;
|
|
best_score = std::min(best_score,score);
|
|
worst_score = std::max(worst_score,score);
|
|
}
|
|
int cutoff = best_only ? best_score : (best_score + (worst_score-best_score)/2);
|
|
for(std::set<Node *>::iterator it = choices.begin(), en = choices.end(); it != en; ++it)
|
|
if(scores[(*it)->map].updates <= cutoff)
|
|
best.insert(*it);
|
|
}
|
|
#endif
|
|
|
|
/** Called when done expanding a tree */
|
|
virtual void Done() {}
|
|
};
|
|
|
|
|
|
class Covering; // see below
|
|
|
|
// These members represent the state of the algorithm.
|
|
|
|
RPFP *rpfp; // the input RPFP
|
|
Reporter *reporter; // object for logging
|
|
Heuristic *heuristic; // expansion heuristic
|
|
context &ctx; // Z3 context
|
|
solver &slvr; // Z3 solver
|
|
std::vector<RPFP::Node *> &nodes; // Nodes of input RPFP
|
|
std::vector<RPFP::Edge *> &edges; // Edges of input RPFP
|
|
std::vector<RPFP::Node *> leaves; // leaf nodes of unwinding (unused)
|
|
Unexpanded unexpanded; // unexpanded nodes
|
|
std::list<Candidate> candidates; // candidates for expansion
|
|
// maps children to edges in input RPFP
|
|
hash_map<Node *, std::vector<Edge *> > edges_by_child;
|
|
// maps each node in input RPFP to its expanded instances
|
|
hash_map<Node *, std::vector<Node *> > insts_of_node;
|
|
// maps each node in input RPFP to all its instances
|
|
hash_map<Node *, std::vector<Node *> > all_of_node;
|
|
RPFP *unwinding; // the unwinding
|
|
Covering *indset; // proposed inductive subset
|
|
Counterexample cex; // counterexample
|
|
std::list<Node *> to_expand;
|
|
hash_set<Node *> updated_nodes;
|
|
hash_map<Node *, Node *> underapprox_map; // maps underapprox nodes to the nodes they approximate
|
|
int last_decisions;
|
|
hash_set<Node *> overapproxes;
|
|
|
|
#ifdef BOUNDED
|
|
struct Counter {
|
|
int val;
|
|
Counter(){val = 0;}
|
|
};
|
|
typedef std::map<Node *,Counter> NodeToCounter;
|
|
hash_map<Node *,NodeToCounter> back_edges; // counts of back edges
|
|
#endif
|
|
|
|
/** Solve the problem. */
|
|
virtual bool Solve(){
|
|
reporter = Report ? CreateStdoutReporter(rpfp) : new Reporter(rpfp);
|
|
#ifndef LOCALIZE_CONJECTURES
|
|
heuristic = !cex.tree ? new Heuristic(rpfp) : new ReplayHeuristic(rpfp,cex);
|
|
#else
|
|
heuristic = !cex.tree ? (Heuristic *)(new LocalHeuristic(rpfp))
|
|
: (Heuristic *)(new ReplayHeuristic(rpfp,cex));
|
|
#endif
|
|
cex.tree = 0; // heuristic now owns it
|
|
unwinding = new RPFP(rpfp->ls);
|
|
unwinding->HornClauses = rpfp->HornClauses;
|
|
indset = new Covering(this);
|
|
last_decisions = 0;
|
|
CreateEdgesByChildMap();
|
|
CreateLeaves();
|
|
#ifndef TOP_DOWN
|
|
if(!StratifiedInlining){
|
|
if(FeasibleEdges)NullaryCandidates();
|
|
else InstantiateAllEdges();
|
|
}
|
|
#else
|
|
for(unsigned i = 0; i < leaves.size(); i++)
|
|
if(!SatisfyUpperBound(leaves[i]))
|
|
return false;
|
|
#endif
|
|
StratifiedLeafCount = -1;
|
|
timer_start("SolveMain");
|
|
bool res = SolveMain(); // does the actual work
|
|
timer_stop("SolveMain");
|
|
// print_profile(std::cout);
|
|
delete indset;
|
|
delete heuristic;
|
|
delete unwinding;
|
|
delete reporter;
|
|
return res;
|
|
}
|
|
|
|
void Cancel(){
|
|
// TODO
|
|
}
|
|
|
|
#if 0
|
|
virtual void Restart(RPFP *_rpfp){
|
|
rpfp = _rpfp;
|
|
delete unwinding;
|
|
nodes = _rpfp->nodes;
|
|
edges = _rpfp->edges;
|
|
leaves.clear();
|
|
unexpanded.clear(); // unexpanded nodes
|
|
candidates.clear(); // candidates for expansion
|
|
edges_by_child.clear();
|
|
insts_of_node.clear();
|
|
all_of_node.clear();
|
|
to_expand.clear();
|
|
}
|
|
#endif
|
|
|
|
virtual void LearnFrom(Counterexample &old_cex){
|
|
cex = old_cex;
|
|
}
|
|
|
|
/** Return the counterexample */
|
|
virtual Counterexample GetCounterexample(){
|
|
Counterexample res = cex;
|
|
cex.tree = 0; // Cex now belongs to caller
|
|
return res;
|
|
}
|
|
|
|
// options
|
|
bool FullExpand; // do not use partial expansion of derivation tree
|
|
bool NoConj; // do not use conjectures (no forced covering)
|
|
bool FeasibleEdges; // use only feasible edges in unwinding
|
|
bool UseUnderapprox; // use underapproximations
|
|
bool Report; // spew on stdout
|
|
bool StratifiedInlining; // Do stratified inlining as preprocessing step
|
|
int RecursionBound; // Recursion bound for bounded verification
|
|
|
|
bool SetBoolOption(bool &opt, const std::string &value){
|
|
if(value == "0") {
|
|
opt = false;
|
|
return true;
|
|
}
|
|
if(value == "1") {
|
|
opt = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SetIntOption(int &opt, const std::string &value){
|
|
opt = atoi(value.c_str());
|
|
return true;
|
|
}
|
|
|
|
/** Set options (not currently used) */
|
|
virtual bool SetOption(const std::string &option, const std::string &value){
|
|
if(option == "full_expand"){
|
|
return SetBoolOption(FullExpand,value);
|
|
}
|
|
if(option == "no_conj"){
|
|
return SetBoolOption(NoConj,value);
|
|
}
|
|
if(option == "feasible_edges"){
|
|
return SetBoolOption(FeasibleEdges,value);
|
|
}
|
|
if(option == "use_underapprox"){
|
|
return SetBoolOption(UseUnderapprox,value);
|
|
}
|
|
if(option == "report"){
|
|
return SetBoolOption(Report,value);
|
|
}
|
|
if(option == "stratified_inlining"){
|
|
return SetBoolOption(StratifiedInlining,value);
|
|
}
|
|
if(option == "recursion_bound"){
|
|
return SetIntOption(RecursionBound,value);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Create an instance of a node in the unwinding. Set its
|
|
annotation to true, and mark it unexpanded. */
|
|
Node* CreateNodeInstance(Node *node, int number = 0){
|
|
RPFP::Node *inst = unwinding->CloneNode(node);
|
|
inst->Annotation.SetFull();
|
|
if(number < 0) inst->number = number;
|
|
unexpanded.insert(inst);
|
|
all_of_node[node].push_back(inst);
|
|
return inst;
|
|
}
|
|
|
|
/** Create an instance of an edge in the unwinding, with given
|
|
parent and children. */
|
|
void CreateEdgeInstance(Edge *edge, Node *parent, const std::vector<Node *> &children){
|
|
RPFP::Edge *inst = unwinding->CreateEdge(parent,edge->F,children);
|
|
inst->map = edge;
|
|
}
|
|
|
|
void MakeLeaf(Node *node, bool do_not_expand = false){
|
|
node->Annotation.SetEmpty();
|
|
Edge *e = unwinding->CreateLowerBoundEdge(node);
|
|
#ifdef TOP_DOWN
|
|
node->Annotation.SetFull(); // allow this node to cover others
|
|
#endif
|
|
if(StratifiedInlining)
|
|
node->Annotation.SetFull(); // allow this node to cover others
|
|
else
|
|
updated_nodes.insert(node);
|
|
e->map = 0;
|
|
reporter->Extend(node);
|
|
#ifdef EARLY_EXPAND
|
|
if(!do_not_expand)
|
|
TryExpandNode(node);
|
|
#endif
|
|
// e->F.SetEmpty();
|
|
}
|
|
|
|
void MakeOverapprox(Node *node){
|
|
node->Annotation.SetFull();
|
|
Edge *e = unwinding->CreateLowerBoundEdge(node);
|
|
overapproxes.insert(node);
|
|
e->map = 0;
|
|
}
|
|
|
|
/** We start the unwinding with leaves that under-approximate
|
|
each relation with false. */
|
|
void CreateLeaves(){
|
|
unexpanded.clear();
|
|
leaves.clear();
|
|
for(unsigned i = 0; i < nodes.size(); i++){
|
|
RPFP::Node *node = CreateNodeInstance(nodes[i]);
|
|
if(0 && nodes[i]->Outgoing->Children.size() == 0)
|
|
CreateEdgeInstance(nodes[i]->Outgoing,node,std::vector<Node *>());
|
|
else {
|
|
if(!StratifiedInlining)
|
|
MakeLeaf(node);
|
|
else {
|
|
MakeOverapprox(node);
|
|
LeafMap[nodes[i]] = node;
|
|
}
|
|
}
|
|
leaves.push_back(node);
|
|
}
|
|
}
|
|
|
|
/** Create the map from children to edges in the input RPFP. This
|
|
is used to generate candidates for expansion. */
|
|
void CreateEdgesByChildMap(){
|
|
edges_by_child.clear();
|
|
for(unsigned i = 0; i < edges.size(); i++){
|
|
Edge *e = edges[i];
|
|
std::set<Node *> done;
|
|
for(unsigned j = 0; j < e->Children.size(); j++){
|
|
Node *c = e->Children[j];
|
|
if(done.find(c) == done.end()) // avoid duplicates
|
|
edges_by_child[c].push_back(e);
|
|
done.insert(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NullaryCandidates(){
|
|
for(unsigned i = 0; i < edges.size(); i++){
|
|
RPFP::Edge *edge = edges[i];
|
|
if(edge->Children.size() == 0){
|
|
Candidate cand;
|
|
cand.edge = edge;
|
|
candidates.push_back(cand);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InstantiateAllEdges(){
|
|
hash_map<Node *, Node *> leaf_map;
|
|
for(unsigned i = 0; i < leaves.size(); i++){
|
|
leaf_map[leaves[i]->map] = leaves[i];
|
|
insts_of_node[leaves[i]->map].push_back(leaves[i]);
|
|
}
|
|
unexpanded.clear();
|
|
for(unsigned i = 0; i < edges.size(); i++){
|
|
Edge *edge = edges[i];
|
|
Candidate c; c.edge = edge;
|
|
c.Children.resize(edge->Children.size());
|
|
for(unsigned j = 0; j < c.Children.size(); j++)
|
|
c.Children[j] = leaf_map[edge->Children[j]];
|
|
Extend(c);
|
|
}
|
|
for(Unexpanded::iterator it = unexpanded.begin(), en = unexpanded.end(); it != en; ++it)
|
|
indset->Add(*it);
|
|
for(unsigned i = 0; i < leaves.size(); i++){
|
|
std::vector<Node *> &foo = insts_of_node[leaves[i]->map];
|
|
foo.erase(foo.begin());
|
|
}
|
|
}
|
|
|
|
bool ProducedBySI(Edge *edge, std::vector<Node *> &children){
|
|
if(LeafMap.find(edge->Parent) == LeafMap.end()) return false;
|
|
Node *other = LeafMap[edge->Parent];
|
|
if(other->Outgoing->map != edge) return false;
|
|
std::vector<Node *> &ochs = other->Outgoing->Children;
|
|
for(unsigned i = 0; i < children.size(); i++)
|
|
if(ochs[i] != children[i]) return false;
|
|
return true;
|
|
}
|
|
|
|
/** Add a candidate for expansion, but not if Stratified inlining has already
|
|
produced it */
|
|
|
|
void AddCandidate(Edge *edge, std::vector<Node *> &children){
|
|
if(StratifiedInlining && ProducedBySI(edge,children))
|
|
return;
|
|
candidates.push_back(Candidate());
|
|
candidates.back().edge = edge;
|
|
candidates.back().Children = children;
|
|
}
|
|
|
|
/** Generate candidates for expansion, given a vector of candidate
|
|
sets for each argument position. This recursively produces
|
|
the cross product.
|
|
*/
|
|
void GenCandidatesRec(int pos, Edge *edge,
|
|
const std::vector<std::vector<Node *> > &vec,
|
|
std::vector<Node *> &children){
|
|
if(pos == (int)vec.size()){
|
|
AddCandidate(edge,children);
|
|
}
|
|
else {
|
|
for(unsigned i = 0; i < vec[pos].size(); i++){
|
|
children[pos] = vec[pos][i];
|
|
GenCandidatesRec(pos+1,edge,vec,children);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Setup for above recursion. */
|
|
void GenCandidates(int pos, Edge *edge,
|
|
const std::vector<std::vector<Node *> > &vec){
|
|
std::vector<Node *> children(vec.size());
|
|
GenCandidatesRec(0,edge,vec,children);
|
|
}
|
|
|
|
/** Expand a node. We find all the candidates for expansion using
|
|
this node and other already expanded nodes. This is a little
|
|
tricky, since a node may be used for multiple argument
|
|
positions of an edge, and we don't want to produce duplicates.
|
|
*/
|
|
|
|
#ifndef NEW_EXPAND
|
|
void ExpandNode(Node *node){
|
|
std::vector<Edge *> &nedges = edges_by_child[node->map];
|
|
for(unsigned i = 0; i < nedges.size(); i++){
|
|
Edge *edge = nedges[i];
|
|
for(unsigned npos = 0; npos < edge->Children.size(); ++npos){
|
|
if(edge->Children[npos] == node->map){
|
|
std::vector<std::vector<Node *> > vec(edge->Children.size());
|
|
vec[npos].push_back(node);
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
if(j != npos){
|
|
std::vector<Node *> &insts = insts_of_node[edge->Children[j]];
|
|
for(unsigned k = 0; k < insts.size(); k++)
|
|
if(indset->Candidate(insts[k]))
|
|
vec[j].push_back(insts[k]);
|
|
}
|
|
if(j < npos && edge->Children[j] == node->map)
|
|
vec[j].push_back(node);
|
|
}
|
|
GenCandidates(0,edge,vec);
|
|
}
|
|
}
|
|
}
|
|
unexpanded.erase(node);
|
|
insts_of_node[node->map].push_back(node);
|
|
}
|
|
#else
|
|
/** If the current proposed solution is not inductive,
|
|
use the induction failure to generate candidates for extension. */
|
|
void ExpandNode(Node *node){
|
|
unexpanded.erase(node);
|
|
insts_of_node[node->map].push_back(node);
|
|
timer_start("GenCandIndFailUsing");
|
|
std::vector<Edge *> &nedges = edges_by_child[node->map];
|
|
for(unsigned i = 0; i < nedges.size(); i++){
|
|
Edge *edge = nedges[i];
|
|
slvr.push();
|
|
RPFP *checker = new RPFP(rpfp->ls);
|
|
Node *root = CheckerJustForEdge(edge,checker,true);
|
|
if(root){
|
|
expr using_cond = ctx.bool_val(false);
|
|
for(unsigned npos = 0; npos < edge->Children.size(); ++npos)
|
|
if(edge->Children[npos] == node->map)
|
|
using_cond = using_cond || checker->Localize(root->Outgoing->Children[npos]->Outgoing,NodeMarker(node));
|
|
slvr.add(using_cond);
|
|
if(checker->Check(root) != unsat){
|
|
Candidate candidate;
|
|
ExtractCandidateFromCex(edge,checker,root,candidate);
|
|
reporter->InductionFailure(edge,candidate.Children);
|
|
candidates.push_back(candidate);
|
|
}
|
|
}
|
|
slvr.pop(1);
|
|
delete checker;
|
|
}
|
|
timer_stop("GenCandIndFailUsing");
|
|
}
|
|
#endif
|
|
|
|
void ExpandNodeFromOther(Node *node, Node *other){
|
|
std::vector<Edge *> &in = other->Incoming;
|
|
for(unsigned i = 0; i < in.size(); i++){
|
|
Edge *edge = in[i];
|
|
Candidate cand;
|
|
cand.edge = edge->map;
|
|
cand.Children = edge->Children;
|
|
for(unsigned j = 0; j < cand.Children.size(); j++)
|
|
if(cand.Children[j] == other)
|
|
cand.Children[j] = node;
|
|
candidates.push_front(cand);
|
|
}
|
|
// unexpanded.erase(node);
|
|
// insts_of_node[node->map].push_back(node);
|
|
}
|
|
|
|
/** Expand a node based on some uncovered node it dominates.
|
|
This pushes cahdidates onto the *front* of the candidate
|
|
queue, so these expansions are done depth-first. */
|
|
bool ExpandNodeFromCoverFail(Node *node){
|
|
if(!node->Outgoing || node->Outgoing->Children.size() == 0)
|
|
return false;
|
|
Node *other = indset->GetSimilarNode(node);
|
|
if(!other)
|
|
return false;
|
|
#ifdef UNDERAPPROX_NODES
|
|
Node *under_node = CreateUnderapproxNode(node);
|
|
underapprox_map[under_node] = node;
|
|
indset->CoverByNode(node,under_node);
|
|
ExpandNodeFromOther(under_node,other);
|
|
ExpandNode(under_node);
|
|
#else
|
|
ExpandNodeFromOther(node,other);
|
|
unexpanded.erase(node);
|
|
insts_of_node[node->map].push_back(node);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
/** Make a boolean variable to act as a "marker" for a node. */
|
|
expr NodeMarker(Node *node){
|
|
std::string name = std::string("@m_") + string_of_int(node->number);
|
|
return ctx.constant(name.c_str(),ctx.bool_sort());
|
|
}
|
|
|
|
/** Union the annotation of dst into src. If with_markers is
|
|
true, we conjoin the annotation formula of dst with its
|
|
marker. This allows us to discover which disjunct is
|
|
true in a satisfying assignment. */
|
|
void UnionAnnotations(RPFP::Transformer &dst, Node *src, bool with_markers = false){
|
|
if(!with_markers)
|
|
dst.UnionWith(src->Annotation);
|
|
else {
|
|
RPFP::Transformer t = src->Annotation;
|
|
t.Formula = t.Formula && NodeMarker(src);
|
|
dst.UnionWith(t);
|
|
}
|
|
}
|
|
|
|
void GenNodeSolutionFromIndSet(Node *node, RPFP::Transformer &annot, bool with_markers = false){
|
|
annot.SetEmpty();
|
|
std::vector<Node *> &insts = insts_of_node[node];
|
|
for(unsigned j = 0; j < insts.size(); j++)
|
|
if(indset->Contains(insts[j]))
|
|
UnionAnnotations(annot,insts[j],with_markers);
|
|
annot.Simplify();
|
|
}
|
|
|
|
/** Generate a proposed solution of the input RPFP from
|
|
the unwinding, by unioning the instances of each node. */
|
|
void GenSolutionFromIndSet(bool with_markers = false){
|
|
for(unsigned i = 0; i < nodes.size(); i++){
|
|
Node *node = nodes[i];
|
|
GenNodeSolutionFromIndSet(node,node->Annotation,with_markers);
|
|
}
|
|
}
|
|
|
|
#ifdef BOUNDED
|
|
bool NodePastRecursionBound(Node *node){
|
|
if(RecursionBound < 0) return false;
|
|
NodeToCounter &backs = back_edges[node];
|
|
for(NodeToCounter::iterator it = backs.begin(), en = backs.end(); it != en; ++it){
|
|
if(it->second.val > RecursionBound)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/** Test whether a given extension candidate actually represents
|
|
an induction failure. Right now we approximate this: if
|
|
the resulting node in the unwinding could be labeled false,
|
|
it clearly is not an induction failure. */
|
|
|
|
bool CandidateFeasible(const Candidate &cand){
|
|
if(!FeasibleEdges) return true;
|
|
timer_start("CandidateFeasible");
|
|
RPFP *checker = new RPFP(rpfp->ls);
|
|
// std::cout << "Checking feasibility of extension " << cand.edge->Parent->number << std::endl;
|
|
checker->Push();
|
|
std::vector<Node *> chs(cand.Children.size());
|
|
Node *root = checker->CloneNode(cand.edge->Parent);
|
|
#ifdef BOUNDED
|
|
for(unsigned i = 0; i < cand.Children.size(); i++)
|
|
if(NodePastRecursionBound(cand.Children[i])){
|
|
timer_stop("CandidateFeasible");
|
|
return false;
|
|
}
|
|
#endif
|
|
#ifdef NEW_CAND_SEL
|
|
GenNodeSolutionFromIndSet(cand.edge->Parent,root->Bound);
|
|
#else
|
|
root->Bound.SetEmpty();
|
|
#endif
|
|
checker->AssertNode(root);
|
|
for(unsigned i = 0; i < cand.Children.size(); i++)
|
|
chs[i] = checker->CloneNode(cand.Children[i]);
|
|
Edge *e = checker->CreateEdge(root,cand.edge->F,chs);
|
|
checker->AssertEdge(e,0,true);
|
|
// std::cout << "Checking SAT: " << e->dual << std::endl;
|
|
bool res = checker->Check(root) != unsat;
|
|
// std::cout << "Result: " << res << std::endl;
|
|
if(!res)reporter->Reject(cand.edge,cand.Children);
|
|
checker->Pop(1);
|
|
delete checker;
|
|
timer_stop("CandidateFeasible");
|
|
return res;
|
|
}
|
|
|
|
|
|
/* For stratified inlining, we need a topological sort of the
|
|
nodes. */
|
|
|
|
hash_map<Node *, int> TopoSort;
|
|
int TopoSortCounter;
|
|
|
|
void DoTopoSortRec(Node *node){
|
|
if(TopoSort.find(node) != TopoSort.end())
|
|
return;
|
|
TopoSort[node] = TopoSortCounter++; // just to break cycles
|
|
Edge *edge = node->Outgoing; // note, this is just *one* outgoing edge
|
|
if(edge){
|
|
std::vector<Node *> &chs = edge->Children;
|
|
for(unsigned i = 0; i < chs.size(); i++)
|
|
DoTopoSortRec(chs[i]);
|
|
}
|
|
TopoSort[node] = TopoSortCounter++;
|
|
}
|
|
|
|
void DoTopoSort(){
|
|
TopoSort.clear();
|
|
TopoSortCounter = 0;
|
|
for(unsigned i = 0; i < nodes.size(); i++)
|
|
DoTopoSortRec(nodes[i]);
|
|
}
|
|
|
|
/** In stratified inlining, we build the unwinding from the bottom
|
|
down, trying to satisfy the node bounds. We do this as a pre-pass,
|
|
limiting the expansion. If we get a counterexample, we are done,
|
|
else we continue as usual expanding the unwinding upward.
|
|
*/
|
|
|
|
int StratifiedLeafCount;
|
|
|
|
bool DoStratifiedInlining(){
|
|
timer_start("StratifiedInlining");
|
|
DoTopoSort();
|
|
for(unsigned i = 0; i < leaves.size(); i++){
|
|
Node *node = leaves[i];
|
|
bool res = SatisfyUpperBound(node);
|
|
if(!res){
|
|
timer_stop("StratifiedInlining");
|
|
return false;
|
|
}
|
|
}
|
|
// don't leave any dangling nodes!
|
|
#ifndef EFFORT_BOUNDED_STRAT
|
|
for(unsigned i = 0; i < leaves.size(); i++)
|
|
if(!leaves[i]->Outgoing)
|
|
MakeLeaf(leaves[i],true);
|
|
#endif
|
|
timer_stop("StratifiedInlining");
|
|
return true;
|
|
}
|
|
|
|
/** Here, we do the downward expansion for stratified inlining */
|
|
|
|
hash_map<Node *, Node *> LeafMap, StratifiedLeafMap;
|
|
|
|
Edge *GetNodeOutgoing(Node *node, int last_decs = 0){
|
|
if(overapproxes.find(node) == overapproxes.end()) return node->Outgoing; /* already expanded */
|
|
overapproxes.erase(node);
|
|
#ifdef EFFORT_BOUNDED_STRAT
|
|
if(last_decs > 5000){
|
|
// RPFP::Transformer save = node->Annotation;
|
|
node->Annotation.SetEmpty();
|
|
Edge *e = unwinding->CreateLowerBoundEdge(node);
|
|
// node->Annotation = save;
|
|
insts_of_node[node->map].push_back(node);
|
|
// std::cout << "made leaf: " << node->number << std::endl;
|
|
return e;
|
|
}
|
|
#endif
|
|
Edge *edge = node->map->Outgoing;
|
|
std::vector<Node *> &chs = edge->Children;
|
|
|
|
// make sure we don't create a covered node in this process!
|
|
|
|
for(unsigned i = 0; i < chs.size(); i++){
|
|
Node *child = chs[i];
|
|
if(TopoSort[child] < TopoSort[node->map]){
|
|
Node *leaf = LeafMap[child];
|
|
if(!indset->Contains(leaf)){
|
|
node->Outgoing->F.Formula = ctx.bool_val(false); // make this a proper leaf, else bogus cex
|
|
return node->Outgoing;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<Node *> nchs(chs.size());
|
|
for(unsigned i = 0; i < chs.size(); i++){
|
|
Node *child = chs[i];
|
|
if(TopoSort[child] < TopoSort[node->map]){
|
|
Node *leaf = LeafMap[child];
|
|
nchs[i] = leaf;
|
|
if(unexpanded.find(leaf) != unexpanded.end()){
|
|
unexpanded.erase(leaf);
|
|
insts_of_node[child].push_back(leaf);
|
|
}
|
|
}
|
|
else {
|
|
if(StratifiedLeafMap.find(child) == StratifiedLeafMap.end()){
|
|
RPFP::Node *nchild = CreateNodeInstance(child,StratifiedLeafCount--);
|
|
MakeLeaf(nchild);
|
|
nchild->Annotation.SetEmpty();
|
|
StratifiedLeafMap[child] = nchild;
|
|
indset->SetDominated(nchild);
|
|
}
|
|
nchs[i] = StratifiedLeafMap[child];
|
|
}
|
|
}
|
|
CreateEdgeInstance(edge,node,nchs);
|
|
reporter->Extend(node);
|
|
return node->Outgoing;
|
|
}
|
|
|
|
void SetHeuristicOldNode(Node *node){
|
|
LocalHeuristic *h = dynamic_cast<LocalHeuristic *>(heuristic);
|
|
if(h)
|
|
h->SetOldNode(node);
|
|
}
|
|
|
|
/** This does the actual solving work. We try to generate
|
|
candidates for extension. If we succed, we extend the
|
|
unwinding. If we fail, we have a solution. */
|
|
bool SolveMain(){
|
|
if(StratifiedInlining && !DoStratifiedInlining())
|
|
return false;
|
|
#ifdef BOUNDED
|
|
DoTopoSort();
|
|
#endif
|
|
while(true){
|
|
timer_start("ProduceCandidatesForExtension");
|
|
ProduceCandidatesForExtension();
|
|
timer_stop("ProduceCandidatesForExtension");
|
|
if(candidates.empty()){
|
|
GenSolutionFromIndSet();
|
|
return true;
|
|
}
|
|
Candidate cand = candidates.front();
|
|
candidates.pop_front();
|
|
if(CandidateFeasible(cand))
|
|
if(!Extend(cand))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// hack: put something local into the underapproximation formula
|
|
// without this, interpolants can be pretty bad
|
|
void AddThing(expr &conj){
|
|
std::string name = "@thing";
|
|
expr thing = ctx.constant(name.c_str(),ctx.bool_sort());
|
|
if(conj.is_app() && conj.decl().get_decl_kind() == And){
|
|
std::vector<expr> conjs(conj.num_args()+1);
|
|
for(unsigned i = 0; i+1 < conjs.size(); i++)
|
|
conjs[i] = conj.arg(i);
|
|
conjs[conjs.size()-1] = thing;
|
|
conj = rpfp->conjoin(conjs);
|
|
}
|
|
}
|
|
|
|
|
|
Node *CreateUnderapproxNode(Node *node){
|
|
// cex.tree->ComputeUnderapprox(cex.root,0);
|
|
RPFP::Node *under_node = CreateNodeInstance(node->map /* ,StratifiedLeafCount-- */);
|
|
under_node->Annotation.IntersectWith(cex.root->Underapprox);
|
|
AddThing(under_node->Annotation.Formula);
|
|
Edge *e = unwinding->CreateLowerBoundEdge(under_node);
|
|
under_node->Annotation.SetFull(); // allow this node to cover others
|
|
back_edges[under_node] = back_edges[node];
|
|
e->map = 0;
|
|
reporter->Extend(under_node);
|
|
return under_node;
|
|
}
|
|
|
|
/** Try to prove a conjecture about a node. If successful
|
|
update the unwinding annotation appropriately. */
|
|
bool ProveConjecture(Node *node, const RPFP::Transformer &t,Node *other = 0, Counterexample *_cex = 0){
|
|
reporter->Conjecture(node,t);
|
|
timer_start("ProveConjecture");
|
|
RPFP::Transformer save = node->Bound;
|
|
node->Bound.IntersectWith(t);
|
|
|
|
#ifndef LOCALIZE_CONJECTURES
|
|
bool ok = SatisfyUpperBound(node);
|
|
#else
|
|
SetHeuristicOldNode(other);
|
|
bool ok = SatisfyUpperBound(node);
|
|
SetHeuristicOldNode(0);
|
|
#endif
|
|
|
|
if(ok){
|
|
timer_stop("ProveConjecture");
|
|
return true;
|
|
}
|
|
#ifdef UNDERAPPROX_NODES
|
|
if(UseUnderapprox && last_decisions > 500){
|
|
std::cout << "making an underapprox\n";
|
|
ExpandNodeFromCoverFail(node);
|
|
}
|
|
#endif
|
|
if(_cex) *_cex = cex;
|
|
else delete cex.tree; // delete the cex if not required
|
|
cex.tree = 0;
|
|
node->Bound = save; // put back original bound
|
|
timer_stop("ProveConjecture");
|
|
return false;
|
|
}
|
|
|
|
/** If a node is part of the inductive subset, expand it.
|
|
We ask the inductive subset to exclude the node if possible.
|
|
*/
|
|
void TryExpandNode(RPFP::Node *node){
|
|
if(indset->Close(node)) return;
|
|
if(!NoConj && indset->Conjecture(node)){
|
|
#ifdef UNDERAPPROX_NODES
|
|
/* TODO: temporary fix. this prevents an infinite loop in case
|
|
the node is covered by multiple others. This should be
|
|
removed when covering by a set is implemented.
|
|
*/
|
|
if(indset->Contains(node)){
|
|
unexpanded.erase(node);
|
|
insts_of_node[node->map].push_back(node);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
#ifdef UNDERAPPROX_NODES
|
|
if(!indset->Contains(node))
|
|
return; // could be covered by an underapprox node
|
|
#endif
|
|
indset->Add(node);
|
|
#if defined(CANDS_FROM_COVER_FAIL) && !defined(UNDERAPPROX_NODES)
|
|
if(ExpandNodeFromCoverFail(node))
|
|
return;
|
|
#endif
|
|
ExpandNode(node);
|
|
}
|
|
|
|
/** Make the conjunction of markers for all (expanded) instances of
|
|
a node in the input RPFP. */
|
|
expr AllNodeMarkers(Node *node){
|
|
expr res = ctx.bool_val(true);
|
|
std::vector<Node *> &insts = insts_of_node[node];
|
|
for(int k = insts.size()-1; k >= 0; k--)
|
|
res = res && NodeMarker(insts[k]);
|
|
return res;
|
|
}
|
|
|
|
void RuleOutNodesPastBound(Node *node, RPFP::Transformer &t){
|
|
#ifdef BOUNDED
|
|
if(RecursionBound < 0)return;
|
|
std::vector<Node *> &insts = insts_of_node[node];
|
|
for(unsigned i = 0; i < insts.size(); i++)
|
|
if(NodePastRecursionBound(insts[i]))
|
|
t.Formula = t.Formula && !NodeMarker(insts[i]);
|
|
#endif
|
|
}
|
|
|
|
|
|
void GenNodeSolutionWithMarkersAux(Node *node, RPFP::Transformer &annot, expr &marker_disjunction){
|
|
#ifdef BOUNDED
|
|
if(RecursionBound >= 0 && NodePastRecursionBound(node))
|
|
return;
|
|
#endif
|
|
RPFP::Transformer temp = node->Annotation;
|
|
expr marker = NodeMarker(node);
|
|
temp.Formula = (!marker || temp.Formula);
|
|
annot.IntersectWith(temp);
|
|
marker_disjunction = marker_disjunction || marker;
|
|
}
|
|
|
|
bool GenNodeSolutionWithMarkers(Node *node, RPFP::Transformer &annot, bool expanded_only = false){
|
|
bool res = false;
|
|
annot.SetFull();
|
|
expr marker_disjunction = ctx.bool_val(false);
|
|
std::vector<Node *> &insts = expanded_only ? insts_of_node[node] : all_of_node[node];
|
|
for(unsigned j = 0; j < insts.size(); j++){
|
|
Node *node = insts[j];
|
|
if(indset->Contains(insts[j])){
|
|
GenNodeSolutionWithMarkersAux(node, annot, marker_disjunction); res = true;
|
|
}
|
|
}
|
|
annot.Formula = annot.Formula && marker_disjunction;
|
|
annot.Simplify();
|
|
return res;
|
|
}
|
|
|
|
/** Make a checker to determine if an edge in the input RPFP
|
|
is satisfied. */
|
|
Node *CheckerJustForEdge(Edge *edge, RPFP *checker, bool expanded_only = false){
|
|
Node *root = checker->CloneNode(edge->Parent);
|
|
GenNodeSolutionFromIndSet(edge->Parent, root->Bound);
|
|
if(root->Bound.IsFull())
|
|
return 0;
|
|
checker->AssertNode(root);
|
|
std::vector<Node *> cs;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *oc = edge->Children[j];
|
|
Node *nc = checker->CloneNode(oc);
|
|
if(!GenNodeSolutionWithMarkers(oc,nc->Annotation,expanded_only))
|
|
return 0;
|
|
Edge *e = checker->CreateLowerBoundEdge(nc);
|
|
checker->AssertEdge(e);
|
|
cs.push_back(nc);
|
|
}
|
|
checker->AssertEdge(checker->CreateEdge(root,edge->F,cs));
|
|
return root;
|
|
}
|
|
|
|
#ifndef MINIMIZE_CANDIDATES_HARDER
|
|
|
|
#if 0
|
|
/** Make a checker to detheermine if an edge in the input RPFP
|
|
is satisfied. */
|
|
Node *CheckerForEdge(Edge *edge, RPFP *checker){
|
|
Node *root = checker->CloneNode(edge->Parent);
|
|
root->Bound = edge->Parent->Annotation;
|
|
root->Bound.Formula = (!AllNodeMarkers(edge->Parent)) || root->Bound.Formula;
|
|
checker->AssertNode(root);
|
|
std::vector<Node *> cs;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *oc = edge->Children[j];
|
|
Node *nc = checker->CloneNode(oc);
|
|
nc->Annotation = oc->Annotation;
|
|
RuleOutNodesPastBound(oc,nc->Annotation);
|
|
Edge *e = checker->CreateLowerBoundEdge(nc);
|
|
checker->AssertEdge(e);
|
|
cs.push_back(nc);
|
|
}
|
|
checker->AssertEdge(checker->CreateEdge(root,edge->F,cs));
|
|
return root;
|
|
}
|
|
|
|
#else
|
|
/** Make a checker to determine if an edge in the input RPFP
|
|
is satisfied. */
|
|
Node *CheckerForEdge(Edge *edge, RPFP *checker){
|
|
Node *root = checker->CloneNode(edge->Parent);
|
|
GenNodeSolutionFromIndSet(edge->Parent, root->Bound);
|
|
#if 0
|
|
if(root->Bound.IsFull())
|
|
return = 0;
|
|
#endif
|
|
checker->AssertNode(root);
|
|
std::vector<Node *> cs;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *oc = edge->Children[j];
|
|
Node *nc = checker->CloneNode(oc);
|
|
GenNodeSolutionWithMarkers(oc,nc->Annotation,true);
|
|
Edge *e = checker->CreateLowerBoundEdge(nc);
|
|
checker->AssertEdge(e);
|
|
cs.push_back(nc);
|
|
}
|
|
checker->AssertEdge(checker->CreateEdge(root,edge->F,cs));
|
|
return root;
|
|
}
|
|
#endif
|
|
|
|
/** If an edge is not satisfied, produce an extension candidate
|
|
using instances of its children that violate the parent annotation.
|
|
We find these using the marker predicates. */
|
|
void ExtractCandidateFromCex(Edge *edge, RPFP *checker, Node *root, Candidate &candidate){
|
|
candidate.edge = edge;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *node = root->Outgoing->Children[j];
|
|
Edge *lb = node->Outgoing;
|
|
std::vector<Node *> &insts = insts_of_node[edge->Children[j]];
|
|
#ifndef MINIMIZE_CANDIDATES
|
|
for(int k = insts.size()-1; k >= 0; k--)
|
|
#else
|
|
for(unsigned k = 0; k < insts.size(); k++)
|
|
#endif
|
|
{
|
|
Node *inst = insts[k];
|
|
if(indset->Contains(inst)){
|
|
if(checker->Empty(node) ||
|
|
eq(lb ? checker->Eval(lb,NodeMarker(inst)) : checker->dualModel.eval(NodeMarker(inst)),ctx.bool_val(true))){
|
|
candidate.Children.push_back(inst);
|
|
goto next_child;
|
|
}
|
|
}
|
|
}
|
|
throw InternalError("No candidate from induction failure");
|
|
next_child:;
|
|
}
|
|
}
|
|
#else
|
|
|
|
|
|
/** Make a checker to determine if an edge in the input RPFP
|
|
is satisfied. */
|
|
Node *CheckerForEdge(Edge *edge, RPFP *checker){
|
|
Node *root = checker->CloneNode(edge->Parent);
|
|
GenNodeSolutionFromIndSet(edge->Parent, root->Bound);
|
|
if(root->Bound.IsFull())
|
|
return = 0;
|
|
checker->AssertNode(root);
|
|
std::vector<Node *> cs;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *oc = edge->Children[j];
|
|
Node *nc = checker->CloneNode(oc);
|
|
GenNodeSolutionWithMarkers(oc,nc->Annotation,true);
|
|
Edge *e = checker->CreateLowerBoundEdge(nc);
|
|
checker->AssertEdge(e);
|
|
cs.push_back(nc);
|
|
}
|
|
checker->AssertEdge(checker->CreateEdge(root,edge->F,cs));
|
|
return root;
|
|
}
|
|
|
|
/** If an edge is not satisfied, produce an extension candidate
|
|
using instances of its children that violate the parent annotation.
|
|
We find these using the marker predicates. */
|
|
void ExtractCandidateFromCex(Edge *edge, RPFP *checker, Node *root, Candidate &candidate){
|
|
candidate.edge = edge;
|
|
std::vector<expr> assumps;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Edge *lb = root->Outgoing->Children[j]->Outgoing;
|
|
std::vector<Node *> &insts = insts_of_node[edge->Children[j]];
|
|
for(unsigned k = 0; k < insts.size(); k++)
|
|
{
|
|
Node *inst = insts[k];
|
|
expr marker = NodeMarker(inst);
|
|
if(indset->Contains(inst)){
|
|
if(checker->Empty(lb->Parent) ||
|
|
eq(checker->Eval(lb,marker),ctx.bool_val(true))){
|
|
candidate.Children.push_back(inst);
|
|
assumps.push_back(checker->Localize(lb,marker));
|
|
goto next_child;
|
|
}
|
|
assumps.push_back(checker->Localize(lb,marker));
|
|
if(checker->CheckUpdateModel(root,assumps) != unsat){
|
|
candidate.Children.push_back(inst);
|
|
goto next_child;
|
|
}
|
|
assumps.pop_back();
|
|
}
|
|
}
|
|
throw InternalError("No candidate from induction failure");
|
|
next_child:;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
Node *CheckerForEdgeClone(Edge *edge, RPFP_caching *checker){
|
|
Edge *gen_cands_edge = checker->GetEdgeClone(edge);
|
|
Node *root = gen_cands_edge->Parent;
|
|
root->Outgoing = gen_cands_edge;
|
|
GenNodeSolutionFromIndSet(edge->Parent, root->Bound);
|
|
#if 0
|
|
if(root->Bound.IsFull())
|
|
return = 0;
|
|
#endif
|
|
checker->AssertNode(root);
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *oc = edge->Children[j];
|
|
Node *nc = gen_cands_edge->Children[j];
|
|
GenNodeSolutionWithMarkers(oc,nc->Annotation,true);
|
|
}
|
|
checker->AssertEdge(gen_cands_edge,1,true);
|
|
return root;
|
|
}
|
|
|
|
/** If the current proposed solution is not inductive,
|
|
use the induction failure to generate candidates for extension. */
|
|
void GenCandidatesFromInductionFailure(bool full_scan = false){
|
|
timer_start("GenCandIndFail");
|
|
GenSolutionFromIndSet(true /* add markers */);
|
|
for(unsigned i = 0; i < edges.size(); i++){
|
|
Edge *edge = edges[i];
|
|
if(!full_scan && updated_nodes.find(edge->Parent) == updated_nodes.end())
|
|
continue;
|
|
#ifndef USE_NEW_GEN_CANDS
|
|
slvr.push();
|
|
RPFP *checker = new RPFP(rpfp->ls);
|
|
Node *root = CheckerForEdge(edge,checker);
|
|
if(checker->Check(root) != unsat){
|
|
Candidate candidate;
|
|
ExtractCandidateFromCex(edge,checker,root,candidate);
|
|
reporter->InductionFailure(edge,candidate.Children);
|
|
candidates.push_back(candidate);
|
|
}
|
|
slvr.pop(1);
|
|
delete checker;
|
|
#else
|
|
RPFP_caching::scoped_solver_for_edge ssfe(gen_cands_rpfp,edge,true /* models */, true /*axioms*/);
|
|
gen_cands_rpfp->Push();
|
|
Node *root = CheckerForEdgeClone(edge,gen_cands_rpfp);
|
|
if(gen_cands_rpfp->Check(root) != unsat){
|
|
Candidate candidate;
|
|
ExtractCandidateFromCex(edge,gen_cands_rpfp,root,candidate);
|
|
reporter->InductionFailure(edge,candidate.Children);
|
|
candidates.push_back(candidate);
|
|
}
|
|
gen_cands_rpfp->Pop(1);
|
|
#endif
|
|
}
|
|
updated_nodes.clear();
|
|
timer_stop("GenCandIndFail");
|
|
#ifdef CHECK_CANDS_FROM_IND_SET
|
|
for(std::list<Candidate>::iterator it = candidates.begin(), en = candidates.end(); it != en; ++it){
|
|
if(!CandidateFeasible(*it))
|
|
throw "produced infeasible candidate";
|
|
}
|
|
#endif
|
|
if(!full_scan && candidates.empty()){
|
|
reporter->Message("No candidates from updates. Trying full scan.");
|
|
GenCandidatesFromInductionFailure(true);
|
|
}
|
|
}
|
|
|
|
#ifdef CANDS_FROM_UPDATES
|
|
/** If the given edge is not inductive in the current proposed solution,
|
|
use the induction failure to generate candidates for extension. */
|
|
void GenCandidatesFromEdgeInductionFailure(RPFP::Edge *edge){
|
|
GenSolutionFromIndSet(true /* add markers */);
|
|
for(unsigned i = 0; i < edges.size(); i++){
|
|
slvr.push();
|
|
Edge *edge = edges[i];
|
|
RPFP *checker = new RPFP(rpfp->ls);
|
|
Node *root = CheckerForEdge(edge,checker);
|
|
if(checker->Check(root) != unsat){
|
|
Candidate candidate;
|
|
ExtractCandidateFromCex(edge,checker,root,candidate);
|
|
reporter->InductionFailure(edge,candidate.Children);
|
|
candidates.push_back(candidate);
|
|
}
|
|
slvr.pop(1);
|
|
delete checker;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/** Find the unexpanded nodes in the inductive subset. */
|
|
void FindNodesToExpand(){
|
|
for(Unexpanded::iterator it = unexpanded.begin(), en = unexpanded.end(); it != en; ++it){
|
|
Node *node = *it;
|
|
if(indset->Candidate(node))
|
|
to_expand.push_back(node);
|
|
}
|
|
}
|
|
|
|
/** Try to create some extension candidates from the unexpanded
|
|
nodes. */
|
|
void ProduceSomeCandidates(){
|
|
while(candidates.empty() && !to_expand.empty()){
|
|
Node *node = to_expand.front();
|
|
to_expand.pop_front();
|
|
TryExpandNode(node);
|
|
}
|
|
}
|
|
|
|
std::list<Candidate> postponed_candidates;
|
|
|
|
/** Try to produce some extension candidates, first from unexpanded
|
|
nides, and if this fails, from induction failure. */
|
|
void ProduceCandidatesForExtension(){
|
|
if(candidates.empty())
|
|
ProduceSomeCandidates();
|
|
while(candidates.empty()){
|
|
FindNodesToExpand();
|
|
if(to_expand.empty()) break;
|
|
ProduceSomeCandidates();
|
|
}
|
|
if(candidates.empty()){
|
|
#ifdef DEPTH_FIRST_EXPAND
|
|
if(postponed_candidates.empty()){
|
|
GenCandidatesFromInductionFailure();
|
|
postponed_candidates.swap(candidates);
|
|
}
|
|
if(!postponed_candidates.empty()){
|
|
candidates.push_back(postponed_candidates.front());
|
|
postponed_candidates.pop_front();
|
|
}
|
|
#else
|
|
GenCandidatesFromInductionFailure();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool UpdateNodeToNode(Node *node, Node *top){
|
|
if(!node->Annotation.SubsetEq(top->Annotation)){
|
|
reporter->Update(node,top->Annotation);
|
|
indset->Update(node,top->Annotation);
|
|
updated_nodes.insert(node->map);
|
|
node->Annotation.IntersectWith(top->Annotation);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Update the unwinding solution, using an interpolant for the
|
|
derivation tree. */
|
|
void UpdateWithInterpolant(Node *node, RPFP *tree, Node *top){
|
|
if(top->Outgoing)
|
|
for(unsigned i = 0; i < top->Outgoing->Children.size(); i++)
|
|
UpdateWithInterpolant(node->Outgoing->Children[i],tree,top->Outgoing->Children[i]);
|
|
UpdateNodeToNode(node, top);
|
|
heuristic->Update(node);
|
|
}
|
|
|
|
/** Update unwinding lower bounds, using a counterexample. */
|
|
|
|
void UpdateWithCounterexample(Node *node, RPFP *tree, Node *top){
|
|
if(top->Outgoing)
|
|
for(unsigned i = 0; i < top->Outgoing->Children.size(); i++)
|
|
UpdateWithCounterexample(node->Outgoing->Children[i],tree,top->Outgoing->Children[i]);
|
|
if(!top->Underapprox.SubsetEq(node->Underapprox)){
|
|
reporter->UpdateUnderapprox(node,top->Underapprox);
|
|
// indset->Update(node,top->Annotation);
|
|
node->Underapprox.UnionWith(top->Underapprox);
|
|
heuristic->Update(node);
|
|
}
|
|
}
|
|
|
|
/** Try to update the unwinding to satisfy the upper bound of a
|
|
node. */
|
|
bool SatisfyUpperBound(Node *node){
|
|
if(node->Bound.IsFull()) return true;
|
|
#ifdef PROPAGATE_BEFORE_CHECK
|
|
Propagate();
|
|
#endif
|
|
reporter->Bound(node);
|
|
int start_decs = rpfp->CumulativeDecisions();
|
|
DerivationTree *dtp = new DerivationTreeSlow(this,unwinding,reporter,heuristic,FullExpand);
|
|
DerivationTree &dt = *dtp;
|
|
bool res = dt.Derive(unwinding,node,UseUnderapprox);
|
|
int end_decs = rpfp->CumulativeDecisions();
|
|
// std::cout << "decisions: " << (end_decs - start_decs) << std::endl;
|
|
last_decisions = end_decs - start_decs;
|
|
if(res){
|
|
cex.tree = dt.tree;
|
|
cex.root = dt.top;
|
|
if(UseUnderapprox){
|
|
UpdateWithCounterexample(node,dt.tree,dt.top);
|
|
}
|
|
}
|
|
else {
|
|
UpdateWithInterpolant(node,dt.tree,dt.top);
|
|
delete dt.tree;
|
|
}
|
|
delete dtp;
|
|
return !res;
|
|
}
|
|
|
|
/* If the counterexample derivation is partial due to
|
|
use of underapproximations, complete it. */
|
|
|
|
void BuildFullCex(Node *node){
|
|
DerivationTree dt(this,unwinding,reporter,heuristic,FullExpand);
|
|
bool res = dt.Derive(unwinding,node,UseUnderapprox,true); // build full tree
|
|
if(!res) throw "Duality internal error in BuildFullCex";
|
|
if(cex.tree)
|
|
delete cex.tree;
|
|
cex.tree = dt.tree;
|
|
cex.root = dt.top;
|
|
}
|
|
|
|
void UpdateBackEdges(Node *node){
|
|
#ifdef BOUNDED
|
|
std::vector<Node *> &chs = node->Outgoing->Children;
|
|
for(unsigned i = 0; i < chs.size(); i++){
|
|
Node *child = chs[i];
|
|
bool is_back = TopoSort[child->map] >= TopoSort[node->map];
|
|
NodeToCounter &nov = back_edges[node];
|
|
NodeToCounter chv = back_edges[child];
|
|
if(is_back)
|
|
chv[child->map].val++;
|
|
for(NodeToCounter::iterator it = chv.begin(), en = chv.end(); it != en; ++it){
|
|
Node *back = it->first;
|
|
Counter &c = nov[back];
|
|
c.val = std::max(c.val,it->second.val);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Extend the unwinding, keeping it solved. */
|
|
bool Extend(Candidate &cand){
|
|
timer_start("Extend");
|
|
Node *node = CreateNodeInstance(cand.edge->Parent);
|
|
CreateEdgeInstance(cand.edge,node,cand.Children);
|
|
UpdateBackEdges(node);
|
|
reporter->Extend(node);
|
|
bool res = SatisfyUpperBound(node);
|
|
if(res) indset->CloseDescendants(node);
|
|
else {
|
|
#ifdef UNDERAPPROX_NODES
|
|
ExpandUnderapproxNodes(cex.tree, cex.root);
|
|
#endif
|
|
if(UseUnderapprox) BuildFullCex(node);
|
|
timer_stop("Extend");
|
|
return res;
|
|
}
|
|
#ifdef EARLY_EXPAND
|
|
TryExpandNode(node);
|
|
#endif
|
|
timer_stop("Extend");
|
|
return res;
|
|
}
|
|
|
|
void ExpandUnderapproxNodes(RPFP *tree, Node *root){
|
|
Node *node = root->map;
|
|
if(underapprox_map.find(node) != underapprox_map.end()){
|
|
RPFP::Transformer cnst = root->Annotation;
|
|
tree->EvalNodeAsConstraint(root, cnst);
|
|
cnst.Complement();
|
|
Node *orig = underapprox_map[node];
|
|
RPFP::Transformer save = orig->Bound;
|
|
orig->Bound = cnst;
|
|
DerivationTree dt(this,unwinding,reporter,heuristic,FullExpand);
|
|
bool res = dt.Derive(unwinding,orig,UseUnderapprox,true,tree);
|
|
if(!res){
|
|
UpdateWithInterpolant(orig,dt.tree,dt.top);
|
|
throw "bogus underapprox!";
|
|
}
|
|
ExpandUnderapproxNodes(tree,dt.top);
|
|
}
|
|
else if(root->Outgoing){
|
|
std::vector<Node *> &chs = root->Outgoing->Children;
|
|
for(unsigned i = 0; i < chs.size(); i++)
|
|
ExpandUnderapproxNodes(tree,chs[i]);
|
|
}
|
|
}
|
|
|
|
// Propagate conjuncts up the unwinding
|
|
void Propagate(){
|
|
reporter->Message("beginning propagation");
|
|
timer_start("Propagate");
|
|
std::vector<Node *> sorted_nodes = unwinding->nodes;
|
|
std::sort(sorted_nodes.begin(),sorted_nodes.end(),std::less<Node *>()); // sorts by sequence number
|
|
hash_map<Node *,std::set<expr> > facts;
|
|
for(unsigned i = 0; i < sorted_nodes.size(); i++){
|
|
Node *node = sorted_nodes[i];
|
|
std::set<expr> &node_facts = facts[node->map];
|
|
if(!(node->Outgoing && indset->Contains(node)))
|
|
continue;
|
|
std::vector<expr> conj_vec;
|
|
unwinding->CollectConjuncts(node->Annotation.Formula,conj_vec);
|
|
std::set<expr> conjs;
|
|
std::copy(conj_vec.begin(),conj_vec.end(),std::inserter(conjs,conjs.begin()));
|
|
if(!node_facts.empty()){
|
|
RPFP *checker = new RPFP(rpfp->ls);
|
|
slvr.push();
|
|
Node *root = checker->CloneNode(node);
|
|
Edge *edge = node->Outgoing;
|
|
// checker->AssertNode(root);
|
|
std::vector<Node *> cs;
|
|
for(unsigned j = 0; j < edge->Children.size(); j++){
|
|
Node *oc = edge->Children[j];
|
|
Node *nc = checker->CloneNode(oc);
|
|
nc->Annotation = oc->Annotation; // is this needed?
|
|
cs.push_back(nc);
|
|
}
|
|
Edge *checker_edge = checker->CreateEdge(root,edge->F,cs);
|
|
checker->AssertEdge(checker_edge, 0, true, false);
|
|
std::vector<expr> propagated;
|
|
for(std::set<expr> ::iterator it = node_facts.begin(), en = node_facts.end(); it != en;){
|
|
const expr &fact = *it;
|
|
if(conjs.find(fact) == conjs.end()){
|
|
root->Bound.Formula = fact;
|
|
slvr.push();
|
|
checker->AssertNode(root);
|
|
check_result res = checker->Check(root);
|
|
slvr.pop();
|
|
if(res != unsat){
|
|
std::set<expr> ::iterator victim = it;
|
|
++it;
|
|
node_facts.erase(victim); // if it ain't true, nix it
|
|
continue;
|
|
}
|
|
propagated.push_back(fact);
|
|
}
|
|
++it;
|
|
}
|
|
slvr.pop();
|
|
for(unsigned i = 0; i < propagated.size(); i++){
|
|
root->Annotation.Formula = propagated[i];
|
|
UpdateNodeToNode(node,root);
|
|
}
|
|
delete checker;
|
|
}
|
|
for(std::set<expr> ::iterator it = conjs.begin(), en = conjs.end(); it != en; ++it){
|
|
expr foo = *it;
|
|
node_facts.insert(foo);
|
|
}
|
|
}
|
|
timer_stop("Propagate");
|
|
}
|
|
|
|
/** This class represents a derivation tree. */
|
|
class DerivationTree {
|
|
public:
|
|
|
|
DerivationTree(Duality *_duality, RPFP *rpfp, Reporter *_reporter, Heuristic *_heuristic, bool _full_expand)
|
|
: slvr(rpfp->slvr()),
|
|
ctx(rpfp->ctx)
|
|
{
|
|
duality = _duality;
|
|
reporter = _reporter;
|
|
heuristic = _heuristic;
|
|
full_expand = _full_expand;
|
|
}
|
|
|
|
Duality *duality;
|
|
Reporter *reporter;
|
|
Heuristic *heuristic;
|
|
solver &slvr;
|
|
context &ctx;
|
|
RPFP *tree;
|
|
RPFP::Node *top;
|
|
std::list<RPFP::Node *> leaves;
|
|
bool full_expand;
|
|
bool underapprox;
|
|
bool constrained;
|
|
bool false_approx;
|
|
std::vector<Node *> underapprox_core;
|
|
int start_decs, last_decs;
|
|
|
|
/* We build derivation trees in one of three modes:
|
|
|
|
1) In normal mode, we build the full tree without considering
|
|
underapproximations.
|
|
|
|
2) In underapprox mode, we use underapproximations to cut off
|
|
the tree construction. THis means the resulting tree may not
|
|
be complete.
|
|
|
|
3) In constrained mode, we build the full tree but use
|
|
underapproximations as upper bounds. This mode is used to
|
|
complete the partial derivation constructed in underapprox
|
|
mode.
|
|
*/
|
|
|
|
bool Derive(RPFP *rpfp, RPFP::Node *root, bool _underapprox, bool _constrained = false, RPFP *_tree = 0){
|
|
underapprox = _underapprox;
|
|
constrained = _constrained;
|
|
false_approx = true;
|
|
timer_start("Derive");
|
|
#ifndef USE_CACHING_RPFP
|
|
tree = _tree ? _tree : new RPFP(rpfp->ls);
|
|
#else
|
|
RPFP::LogicSolver *cache_ls = new RPFP::iZ3LogicSolver(ctx);
|
|
cache_ls->slvr->push();
|
|
tree = _tree ? _tree : new RPFP_caching(cache_ls);
|
|
#endif
|
|
tree->HornClauses = rpfp->HornClauses;
|
|
tree->Push(); // so we can clear out the solver later when finished
|
|
top = CreateApproximatedInstance(root);
|
|
tree->AssertNode(top); // assert the negation of the top-level spec
|
|
timer_start("Build");
|
|
bool res = Build();
|
|
heuristic->Done();
|
|
timer_stop("Build");
|
|
timer_start("Pop");
|
|
tree->Pop(1);
|
|
timer_stop("Pop");
|
|
#ifdef USE_CACHING_RPFP
|
|
cache_ls->slvr->pop(1);
|
|
delete cache_ls;
|
|
tree->ls = rpfp->ls;
|
|
#endif
|
|
timer_stop("Derive");
|
|
return res;
|
|
}
|
|
|
|
#define WITH_CHILDREN
|
|
|
|
void InitializeApproximatedInstance(RPFP::Node *to){
|
|
to->Annotation = to->map->Annotation;
|
|
#ifndef WITH_CHILDREN
|
|
tree->CreateLowerBoundEdge(to);
|
|
#endif
|
|
leaves.push_back(to);
|
|
}
|
|
|
|
Node *CreateApproximatedInstance(RPFP::Node *from){
|
|
Node *to = tree->CloneNode(from);
|
|
InitializeApproximatedInstance(to);
|
|
return to;
|
|
}
|
|
|
|
bool CheckWithUnderapprox(){
|
|
timer_start("CheckWithUnderapprox");
|
|
std::vector<Node *> leaves_vector(leaves.size());
|
|
std::copy(leaves.begin(),leaves.end(),leaves_vector.begin());
|
|
check_result res = tree->Check(top,leaves_vector);
|
|
timer_stop("CheckWithUnderapprox");
|
|
return res != unsat;
|
|
}
|
|
|
|
virtual bool Build(){
|
|
#ifdef EFFORT_BOUNDED_STRAT
|
|
start_decs = tree->CumulativeDecisions();
|
|
#endif
|
|
while(ExpandSomeNodes(true)); // do high-priority expansions
|
|
while (true)
|
|
{
|
|
#ifndef WITH_CHILDREN
|
|
timer_start("asserting leaves");
|
|
timer_start("pushing");
|
|
tree->Push();
|
|
timer_stop("pushing");
|
|
for(std::list<RPFP::Node *>::iterator it = leaves.begin(), en = leaves.end(); it != en; ++it)
|
|
tree->AssertEdge((*it)->Outgoing,1); // assert the overapproximation, and keep it past pop
|
|
timer_stop("asserting leaves");
|
|
lbool res = tree->Solve(top, 2); // incremental solve, keep interpolants for two pops
|
|
timer_start("popping leaves");
|
|
tree->Pop(1);
|
|
timer_stop("popping leaves");
|
|
#else
|
|
lbool res;
|
|
if((underapprox || false_approx) && top->Outgoing && CheckWithUnderapprox()){
|
|
if(constrained) goto expand_some_nodes; // in constrained mode, keep expanding
|
|
goto we_are_sat; // else if underapprox is sat, we stop
|
|
}
|
|
// tree->Check(top);
|
|
res = tree->Solve(top, 1); // incremental solve, keep interpolants for one pop
|
|
#endif
|
|
if (res == l_false)
|
|
return false;
|
|
|
|
expand_some_nodes:
|
|
if(ExpandSomeNodes())
|
|
continue;
|
|
|
|
we_are_sat:
|
|
if(underapprox && !constrained){
|
|
timer_start("ComputeUnderapprox");
|
|
tree->ComputeUnderapprox(top,1);
|
|
timer_stop("ComputeUnderapprox");
|
|
}
|
|
else {
|
|
#ifdef UNDERAPPROX_NODES
|
|
#ifndef SKIP_UNDERAPPROX_NODES
|
|
timer_start("ComputeUnderapprox");
|
|
tree->ComputeUnderapprox(top,1);
|
|
timer_stop("ComputeUnderapprox");
|
|
#endif
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
virtual void ExpandNode(RPFP::Node *p){
|
|
// tree->RemoveEdge(p->Outgoing);
|
|
Edge *ne = p->Outgoing;
|
|
if(ne) {
|
|
// reporter->Message("Recycling edge...");
|
|
std::vector<RPFP::Node *> &cs = ne->Children;
|
|
for(unsigned i = 0; i < cs.size(); i++)
|
|
InitializeApproximatedInstance(cs[i]);
|
|
// ne->dual = expr();
|
|
}
|
|
else {
|
|
Edge *edge = duality->GetNodeOutgoing(p->map,last_decs);
|
|
std::vector<RPFP::Node *> &cs = edge->Children;
|
|
std::vector<RPFP::Node *> children(cs.size());
|
|
for(unsigned i = 0; i < cs.size(); i++)
|
|
children[i] = CreateApproximatedInstance(cs[i]);
|
|
ne = tree->CreateEdge(p, p->map->Outgoing->F, children);
|
|
ne->map = p->map->Outgoing->map;
|
|
}
|
|
#ifndef WITH_CHILDREN
|
|
tree->AssertEdge(ne); // assert the edge in the solver
|
|
#else
|
|
tree->AssertEdge(ne,0,!full_expand,(underapprox || false_approx)); // assert the edge in the solver
|
|
#endif
|
|
reporter->Expand(ne);
|
|
}
|
|
|
|
#define UNDERAPPROXCORE
|
|
#ifndef UNDERAPPROXCORE
|
|
void ExpansionChoices(std::set<Node *> &best){
|
|
std::set<Node *> choices;
|
|
for(std::list<RPFP::Node *>::iterator it = leaves.begin(), en = leaves.end(); it != en; ++it)
|
|
if (!tree->Empty(*it)) // if used in the counter-model
|
|
choices.insert(*it);
|
|
heuristic->ChooseExpand(choices, best);
|
|
}
|
|
#else
|
|
#if 0
|
|
|
|
void ExpansionChoices(std::set<Node *> &best){
|
|
std::vector <Node *> unused_set, used_set;
|
|
std::set<Node *> choices;
|
|
for(std::list<RPFP::Node *>::iterator it = leaves.begin(), en = leaves.end(); it != en; ++it){
|
|
Node *n = *it;
|
|
if (!tree->Empty(n))
|
|
used_set.push_back(n);
|
|
else
|
|
unused_set.push_back(n);
|
|
}
|
|
if(tree->Check(top,unused_set) == unsat)
|
|
throw "error in ExpansionChoices";
|
|
for(unsigned i = 0; i < used_set.size(); i++){
|
|
Node *n = used_set[i];
|
|
unused_set.push_back(n);
|
|
if(!top->Outgoing || tree->Check(top,unused_set) == unsat){
|
|
unused_set.pop_back();
|
|
choices.insert(n);
|
|
}
|
|
else
|
|
std::cout << "Using underapprox of " << n->number << std::endl;
|
|
}
|
|
heuristic->ChooseExpand(choices, best);
|
|
}
|
|
#else
|
|
void ExpansionChoicesFull(std::set<Node *> &best, bool high_priority, bool best_only = false){
|
|
std::set<Node *> choices;
|
|
for(std::list<RPFP::Node *>::iterator it = leaves.begin(), en = leaves.end(); it != en; ++it)
|
|
if (high_priority || !tree->Empty(*it)) // if used in the counter-model
|
|
choices.insert(*it);
|
|
heuristic->ChooseExpand(choices, best, high_priority, best_only);
|
|
}
|
|
|
|
void ExpansionChoicesRec(std::vector <Node *> &unused_set, std::vector <Node *> &used_set,
|
|
std::set<Node *> &choices, int from, int to){
|
|
if(from == to) return;
|
|
int orig_unused = unused_set.size();
|
|
unused_set.resize(orig_unused + (to - from));
|
|
std::copy(used_set.begin()+from,used_set.begin()+to,unused_set.begin()+orig_unused);
|
|
if(!top->Outgoing || tree->Check(top,unused_set) == unsat){
|
|
unused_set.resize(orig_unused);
|
|
if(to - from == 1){
|
|
#if 1
|
|
std::cout << "Not using underapprox of " << used_set[from] ->number << std::endl;
|
|
#endif
|
|
choices.insert(used_set[from]);
|
|
}
|
|
else {
|
|
int mid = from + (to - from)/2;
|
|
ExpansionChoicesRec(unused_set, used_set, choices, from, mid);
|
|
ExpansionChoicesRec(unused_set, used_set, choices, mid, to);
|
|
}
|
|
}
|
|
else {
|
|
#if 1
|
|
std::cout << "Using underapprox of ";
|
|
for(int i = from; i < to; i++){
|
|
std::cout << used_set[i]->number << " ";
|
|
if(used_set[i]->map->Underapprox.IsEmpty())
|
|
std::cout << "(false!) ";
|
|
}
|
|
std::cout << std::endl;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
std::set<Node *> old_choices;
|
|
|
|
void ExpansionChoices(std::set<Node *> &best, bool high_priority, bool best_only = false){
|
|
if(!underapprox || constrained || high_priority){
|
|
ExpansionChoicesFull(best, high_priority,best_only);
|
|
return;
|
|
}
|
|
std::vector <Node *> unused_set, used_set;
|
|
std::set<Node *> choices;
|
|
for(std::list<RPFP::Node *>::iterator it = leaves.begin(), en = leaves.end(); it != en; ++it){
|
|
Node *n = *it;
|
|
if (!tree->Empty(n)){
|
|
if(old_choices.find(n) != old_choices.end() || n->map->Underapprox.IsEmpty())
|
|
choices.insert(n);
|
|
else
|
|
used_set.push_back(n);
|
|
}
|
|
else
|
|
unused_set.push_back(n);
|
|
}
|
|
if(tree->Check(top,unused_set) == unsat)
|
|
throw "error in ExpansionChoices";
|
|
ExpansionChoicesRec(unused_set, used_set, choices, 0, used_set.size());
|
|
old_choices = choices;
|
|
heuristic->ChooseExpand(choices, best, high_priority);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
bool ExpandSomeNodes(bool high_priority = false, int max = INT_MAX){
|
|
#ifdef EFFORT_BOUNDED_STRAT
|
|
last_decs = tree->CumulativeDecisions() - start_decs;
|
|
#endif
|
|
timer_start("ExpandSomeNodes");
|
|
timer_start("ExpansionChoices");
|
|
std::set<Node *> choices;
|
|
ExpansionChoices(choices,high_priority,max != INT_MAX);
|
|
timer_stop("ExpansionChoices");
|
|
std::list<RPFP::Node *> leaves_copy = leaves; // copy so can modify orig
|
|
leaves.clear();
|
|
int count = 0;
|
|
for(std::list<RPFP::Node *>::iterator it = leaves_copy.begin(), en = leaves_copy.end(); it != en; ++it){
|
|
if(choices.find(*it) != choices.end() && count < max){
|
|
count++;
|
|
ExpandNode(*it);
|
|
}
|
|
else leaves.push_back(*it);
|
|
}
|
|
timer_stop("ExpandSomeNodes");
|
|
return !choices.empty();
|
|
}
|
|
|
|
void RemoveExpansion(RPFP::Node *p){
|
|
Edge *edge = p->Outgoing;
|
|
Node *parent = edge->Parent;
|
|
#ifndef KEEP_EXPANSIONS
|
|
std::vector<RPFP::Node *> cs = edge->Children;
|
|
tree->DeleteEdge(edge);
|
|
for(unsigned i = 0; i < cs.size(); i++)
|
|
tree->DeleteNode(cs[i]);
|
|
#endif
|
|
leaves.push_back(parent);
|
|
}
|
|
|
|
// remove all the descendants of tree root (but not root itself)
|
|
void RemoveTree(RPFP *tree, RPFP::Node *root){
|
|
Edge *edge = root->Outgoing;
|
|
std::vector<RPFP::Node *> cs = edge->Children;
|
|
tree->DeleteEdge(edge);
|
|
for(unsigned i = 0; i < cs.size(); i++){
|
|
RemoveTree(tree,cs[i]);
|
|
tree->DeleteNode(cs[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
class DerivationTreeSlow : public DerivationTree {
|
|
public:
|
|
|
|
struct stack_entry {
|
|
unsigned level; // SMT solver stack level
|
|
std::vector<Node *> expansions;
|
|
};
|
|
|
|
std::vector<stack_entry> stack;
|
|
|
|
hash_map<Node *, expr> updates;
|
|
|
|
DerivationTreeSlow(Duality *_duality, RPFP *rpfp, Reporter *_reporter, Heuristic *_heuristic, bool _full_expand)
|
|
: DerivationTree(_duality, rpfp, _reporter, _heuristic, _full_expand) {
|
|
stack.push_back(stack_entry());
|
|
}
|
|
|
|
virtual bool Build(){
|
|
|
|
stack.back().level = tree->slvr().get_scope_level();
|
|
bool was_sat = true;
|
|
|
|
while (true)
|
|
{
|
|
lbool res;
|
|
|
|
unsigned slvr_level = tree->slvr().get_scope_level();
|
|
if(slvr_level != stack.back().level)
|
|
throw "stacks out of sync!";
|
|
|
|
// res = tree->Solve(top, 1); // incremental solve, keep interpolants for one pop
|
|
check_result foo = tree->Check(top);
|
|
res = foo == unsat ? l_false : l_true;
|
|
|
|
if (res == l_false) {
|
|
if (stack.empty()) // should never happen
|
|
return false;
|
|
|
|
{
|
|
std::vector<Node *> &expansions = stack.back().expansions;
|
|
int update_count = 0;
|
|
for(unsigned i = 0; i < expansions.size(); i++){
|
|
Node *node = expansions[i];
|
|
tree->SolveSingleNode(top,node);
|
|
#ifdef NO_GENERALIZE
|
|
node->Annotation.Formula = tree->RemoveRedundancy(node->Annotation.Formula).simplify();
|
|
#else
|
|
if(expansions.size() == 1 && NodeTooComplicated(node))
|
|
SimplifyNode(node);
|
|
else
|
|
node->Annotation.Formula = tree->RemoveRedundancy(node->Annotation.Formula).simplify();
|
|
Generalize(node);
|
|
#endif
|
|
if(RecordUpdate(node))
|
|
update_count++;
|
|
else
|
|
heuristic->Update(node->map); // make it less likely to expand this node in future
|
|
}
|
|
if(update_count == 0){
|
|
if(was_sat)
|
|
throw Incompleteness();
|
|
reporter->Message("backtracked without learning");
|
|
}
|
|
}
|
|
tree->ComputeProofCore(); // need to compute the proof core before popping solver
|
|
bool propagated = false;
|
|
while(1) {
|
|
std::vector<Node *> &expansions = stack.back().expansions;
|
|
bool prev_level_used = LevelUsedInProof(stack.size()-2); // need to compute this before pop
|
|
tree->Pop(1);
|
|
hash_set<Node *> leaves_to_remove;
|
|
for(unsigned i = 0; i < expansions.size(); i++){
|
|
Node *node = expansions[i];
|
|
// if(node != top)
|
|
// tree->ConstrainParent(node->Incoming[0],node);
|
|
std::vector<Node *> &cs = node->Outgoing->Children;
|
|
for(unsigned i = 0; i < cs.size(); i++){
|
|
leaves_to_remove.insert(cs[i]);
|
|
UnmapNode(cs[i]);
|
|
if(std::find(updated_nodes.begin(),updated_nodes.end(),cs[i]) != updated_nodes.end())
|
|
throw "help!";
|
|
}
|
|
}
|
|
RemoveLeaves(leaves_to_remove); // have to do this before actually deleting the children
|
|
for(unsigned i = 0; i < expansions.size(); i++){
|
|
Node *node = expansions[i];
|
|
RemoveExpansion(node);
|
|
}
|
|
stack.pop_back();
|
|
if(stack.size() == 1)break;
|
|
if(prev_level_used){
|
|
Node *node = stack.back().expansions[0];
|
|
#ifndef NO_PROPAGATE
|
|
if(!Propagate(node)) break;
|
|
#endif
|
|
if(!RecordUpdate(node)) break; // shouldn't happen!
|
|
RemoveUpdateNodesAtCurrentLevel(); // this level is about to be deleted -- remove its children from update list
|
|
propagated = true;
|
|
continue;
|
|
}
|
|
if(propagated) break; // propagation invalidates the proof core, so disable non-chron backtrack
|
|
RemoveUpdateNodesAtCurrentLevel(); // this level is about to be deleted -- remove its children from update list
|
|
std::vector<Node *> &unused_ex = stack.back().expansions;
|
|
for(unsigned i = 0; i < unused_ex.size(); i++)
|
|
heuristic->Update(unused_ex[i]->map); // make it less likely to expand this node in future
|
|
}
|
|
HandleUpdatedNodes();
|
|
if(stack.size() == 1){
|
|
if(top->Outgoing)
|
|
tree->DeleteEdge(top->Outgoing); // in case we kept the tree
|
|
return false;
|
|
}
|
|
was_sat = false;
|
|
}
|
|
else {
|
|
was_sat = true;
|
|
tree->Push();
|
|
|
|
std::vector<Node *> &expansions = stack.back().expansions;
|
|
#ifndef NO_DECISIONS
|
|
for(unsigned i = 0; i < expansions.size(); i++){
|
|
tree->FixCurrentState(expansions[i]->Outgoing);
|
|
}
|
|
#endif
|
|
#if 0
|
|
if(tree->slvr().check() == unsat)
|
|
throw "help!";
|
|
#endif
|
|
stack.push_back(stack_entry());
|
|
stack.back().level = tree->slvr().get_scope_level();
|
|
if(ExpandSomeNodes(false,1)){
|
|
continue;
|
|
}
|
|
while(stack.size() > 1){
|
|
tree->Pop(1);
|
|
stack.pop_back();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NodeTooComplicated(Node *node){
|
|
int ops = tree->CountOperators(node->Annotation.Formula);
|
|
if(ops > 10) return true;
|
|
node->Annotation.Formula = tree->RemoveRedundancy(node->Annotation.Formula).simplify();
|
|
return tree->CountOperators(node->Annotation.Formula) > 3;
|
|
}
|
|
|
|
void SimplifyNode(Node *node){
|
|
// have to destroy the old proof to get a new interpolant
|
|
timer_start("SimplifyNode");
|
|
tree->PopPush();
|
|
tree->InterpolateByCases(top,node);
|
|
timer_stop("SimplifyNode");
|
|
}
|
|
|
|
bool LevelUsedInProof(unsigned level){
|
|
std::vector<Node *> &expansions = stack[level].expansions;
|
|
for(unsigned i = 0; i < expansions.size(); i++)
|
|
if(tree->EdgeUsedInProof(expansions[i]->Outgoing))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void RemoveUpdateNodesAtCurrentLevel() {
|
|
for(std::list<Node *>::iterator it = updated_nodes.begin(), en = updated_nodes.end(); it != en;){
|
|
Node *node = *it;
|
|
if(AtCurrentStackLevel(node->Incoming[0]->Parent)){
|
|
std::list<Node *>::iterator victim = it;
|
|
++it;
|
|
updated_nodes.erase(victim);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void RemoveLeaves(hash_set<Node *> &leaves_to_remove){
|
|
std::list<RPFP::Node *> leaves_copy;
|
|
leaves_copy.swap(leaves);
|
|
for(std::list<RPFP::Node *>::iterator it = leaves_copy.begin(), en = leaves_copy.end(); it != en; ++it){
|
|
if(leaves_to_remove.find(*it) == leaves_to_remove.end())
|
|
leaves.push_back(*it);
|
|
}
|
|
}
|
|
|
|
hash_map<Node *, std::vector<Node *> > node_map;
|
|
std::list<Node *> updated_nodes;
|
|
|
|
virtual void ExpandNode(RPFP::Node *p){
|
|
stack.back().expansions.push_back(p);
|
|
DerivationTree::ExpandNode(p);
|
|
std::vector<Node *> &new_nodes = p->Outgoing->Children;
|
|
for(unsigned i = 0; i < new_nodes.size(); i++){
|
|
Node *n = new_nodes[i];
|
|
node_map[n->map].push_back(n);
|
|
}
|
|
}
|
|
|
|
bool RecordUpdate(Node *node){
|
|
bool res = duality->UpdateNodeToNode(node->map,node);
|
|
if(res){
|
|
std::vector<Node *> to_update = node_map[node->map];
|
|
for(unsigned i = 0; i < to_update.size(); i++){
|
|
Node *node2 = to_update[i];
|
|
// maintain invariant that no nodes on updated list are created at current stack level
|
|
if(node2 == node || !(node->Incoming.size() > 0 && AtCurrentStackLevel(node2->Incoming[0]->Parent))){
|
|
updated_nodes.push_back(node2);
|
|
if(node2 != node)
|
|
node2->Annotation = node->Annotation;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void HandleUpdatedNodes(){
|
|
for(std::list<Node *>::iterator it = updated_nodes.begin(), en = updated_nodes.end(); it != en;){
|
|
Node *node = *it;
|
|
node->Annotation = node->map->Annotation;
|
|
if(node->Incoming.size() > 0)
|
|
tree->ConstrainParent(node->Incoming[0],node);
|
|
if(AtCurrentStackLevel(node->Incoming[0]->Parent)){
|
|
std::list<Node *>::iterator victim = it;
|
|
++it;
|
|
updated_nodes.erase(victim);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
bool AtCurrentStackLevel(Node *node){
|
|
std::vector<Node *> vec = stack.back().expansions;
|
|
for(unsigned i = 0; i < vec.size(); i++)
|
|
if(vec[i] == node)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void UnmapNode(Node *node){
|
|
std::vector<Node *> &vec = node_map[node->map];
|
|
for(unsigned i = 0; i < vec.size(); i++){
|
|
if(vec[i] == node){
|
|
std::swap(vec[i],vec.back());
|
|
vec.pop_back();
|
|
return;
|
|
}
|
|
}
|
|
throw "can't unmap node";
|
|
}
|
|
|
|
void Generalize(Node *node){
|
|
#ifndef USE_RPFP_CLONE
|
|
tree->Generalize(top,node);
|
|
#else
|
|
RPFP_caching *clone_rpfp = duality->clone_rpfp;
|
|
if(!node->Outgoing->map) return;
|
|
Edge *clone_edge = clone_rpfp->GetEdgeClone(node->Outgoing->map);
|
|
Node *clone_node = clone_edge->Parent;
|
|
clone_node->Annotation = node->Annotation;
|
|
for(unsigned i = 0; i < clone_edge->Children.size(); i++)
|
|
clone_edge->Children[i]->Annotation = node->map->Outgoing->Children[i]->Annotation;
|
|
clone_rpfp->GeneralizeCache(clone_edge);
|
|
node->Annotation = clone_node->Annotation;
|
|
#endif
|
|
}
|
|
|
|
bool Propagate(Node *node){
|
|
#ifdef USE_RPFP_CLONE
|
|
RPFP_caching *clone_rpfp = duality->clone_rpfp;
|
|
Edge *clone_edge = clone_rpfp->GetEdgeClone(node->Outgoing->map);
|
|
Node *clone_node = clone_edge->Parent;
|
|
clone_node->Annotation = node->map->Annotation;
|
|
for(unsigned i = 0; i < clone_edge->Children.size(); i++)
|
|
clone_edge->Children[i]->Annotation = node->map->Outgoing->Children[i]->Annotation;
|
|
bool res = clone_rpfp->PropagateCache(clone_edge);
|
|
if(res)
|
|
node->Annotation = clone_node->Annotation;
|
|
return res;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
};
|
|
|
|
|
|
class Covering {
|
|
|
|
struct cover_info {
|
|
Node *covered_by;
|
|
std::list<Node *> covers;
|
|
bool dominated;
|
|
std::set<Node *> dominates;
|
|
cover_info(){
|
|
covered_by = 0;
|
|
dominated = false;
|
|
}
|
|
};
|
|
|
|
typedef hash_map<Node *,cover_info> cover_map;
|
|
cover_map cm;
|
|
Duality *parent;
|
|
bool some_updates;
|
|
|
|
#define NO_CONJ_ON_SIMPLE_LOOPS
|
|
#ifdef NO_CONJ_ON_SIMPLE_LOOPS
|
|
hash_set<Node *> simple_loops;
|
|
#endif
|
|
|
|
Node *&covered_by(Node *node){
|
|
return cm[node].covered_by;
|
|
}
|
|
|
|
std::list<Node *> &covers(Node *node){
|
|
return cm[node].covers;
|
|
}
|
|
|
|
std::vector<Node *> &insts_of_node(Node *node){
|
|
return parent->insts_of_node[node];
|
|
}
|
|
|
|
Reporter *reporter(){
|
|
return parent->reporter;
|
|
}
|
|
|
|
std::set<Node *> &dominates(Node *x){
|
|
return cm[x].dominates;
|
|
}
|
|
|
|
bool dominates(Node *x, Node *y){
|
|
std::set<Node *> &d = cm[x].dominates;
|
|
return d.find(y) != d.end();
|
|
}
|
|
|
|
bool &dominated(Node *x){
|
|
return cm[x].dominated;
|
|
}
|
|
|
|
public:
|
|
|
|
Covering(Duality *_parent){
|
|
parent = _parent;
|
|
some_updates = false;
|
|
|
|
#ifdef NO_CONJ_ON_SIMPLE_LOOPS
|
|
hash_map<Node *,std::vector<Edge *> > outgoing;
|
|
for(unsigned i = 0; i < parent->rpfp->edges.size(); i++)
|
|
outgoing[parent->rpfp->edges[i]->Parent].push_back(parent->rpfp->edges[i]);
|
|
for(unsigned i = 0; i < parent->rpfp->nodes.size(); i++){
|
|
Node * node = parent->rpfp->nodes[i];
|
|
std::vector<Edge *> &outs = outgoing[node];
|
|
if(outs.size() == 2){
|
|
for(int j = 0; j < 2; j++){
|
|
Edge *loop_edge = outs[j];
|
|
if(loop_edge->Children.size() == 1 && loop_edge->Children[0] == loop_edge->Parent)
|
|
simple_loops.insert(node);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
bool IsCoveredRec(hash_set<Node *> &memo, Node *node){
|
|
if(memo.find(node) != memo.end())
|
|
return false;
|
|
memo.insert(node);
|
|
if(covered_by(node)) return true;
|
|
for(unsigned i = 0; i < node->Outgoing->Children.size(); i++)
|
|
if(IsCoveredRec(memo,node->Outgoing->Children[i]))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool IsCovered(Node *node){
|
|
hash_set<Node *> memo;
|
|
return IsCoveredRec(memo,node);
|
|
}
|
|
|
|
#ifndef UNDERAPPROX_NODES
|
|
void RemoveCoveringsBy(Node *node){
|
|
std::list<Node *> &cs = covers(node);
|
|
for(std::list<Node *>::iterator it = cs.begin(), en = cs.end(); it != en; it++){
|
|
covered_by(*it) = 0;
|
|
reporter()->RemoveCover(*it,node);
|
|
}
|
|
cs.clear();
|
|
}
|
|
#else
|
|
void RemoveCoveringsBy(Node *node){
|
|
std::vector<Node *> &cs = parent->all_of_node[node->map];
|
|
for(std::vector<Node *>::iterator it = cs.begin(), en = cs.end(); it != en; it++){
|
|
Node *other = *it;
|
|
if(covered_by(other) && CoverOrder(node,other)){
|
|
covered_by(other) = 0;
|
|
reporter()->RemoveCover(*it,node);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void RemoveAscendantCoveringsRec(hash_set<Node *> &memo, Node *node){
|
|
if(memo.find(node) != memo.end())
|
|
return;
|
|
memo.insert(node);
|
|
RemoveCoveringsBy(node);
|
|
for(std::vector<Edge *>::iterator it = node->Incoming.begin(), en = node->Incoming.end(); it != en; ++it)
|
|
RemoveAscendantCoveringsRec(memo,(*it)->Parent);
|
|
}
|
|
|
|
void RemoveAscendantCoverings(Node *node){
|
|
hash_set<Node *> memo;
|
|
RemoveAscendantCoveringsRec(memo,node);
|
|
}
|
|
|
|
bool CoverOrder(Node *covering, Node *covered){
|
|
#ifdef UNDERAPPROX_NODES
|
|
if(parent->underapprox_map.find(covered) != parent->underapprox_map.end())
|
|
return false;
|
|
if(parent->underapprox_map.find(covering) != parent->underapprox_map.end())
|
|
return covering->number < covered->number || parent->underapprox_map[covering] == covered;
|
|
#endif
|
|
return covering->number < covered->number;
|
|
}
|
|
|
|
bool CheckCover(Node *covered, Node *covering){
|
|
return
|
|
CoverOrder(covering,covered)
|
|
&& covered->Annotation.SubsetEq(covering->Annotation)
|
|
&& !IsCovered(covering);
|
|
}
|
|
|
|
bool CoverByNode(Node *covered, Node *covering){
|
|
if(CheckCover(covered,covering)){
|
|
covered_by(covered) = covering;
|
|
covers(covering).push_back(covered);
|
|
std::vector<Node *> others; others.push_back(covering);
|
|
reporter()->AddCover(covered,others);
|
|
RemoveAscendantCoverings(covered);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
#ifdef UNDERAPPROX_NODES
|
|
bool CoverByAll(Node *covered){
|
|
RPFP::Transformer all = covered->Annotation;
|
|
all.SetEmpty();
|
|
std::vector<Node *> &insts = parent->insts_of_node[covered->map];
|
|
std::vector<Node *> others;
|
|
for(unsigned i = 0; i < insts.size(); i++){
|
|
Node *covering = insts[i];
|
|
if(CoverOrder(covering,covered) && !IsCovered(covering)){
|
|
others.push_back(covering);
|
|
all.UnionWith(covering->Annotation);
|
|
}
|
|
}
|
|
if(others.size() && covered->Annotation.SubsetEq(all)){
|
|
covered_by(covered) = covered; // anything non-null will do
|
|
reporter()->AddCover(covered,others);
|
|
RemoveAscendantCoverings(covered);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool Close(Node *node){
|
|
if(covered_by(node))
|
|
return true;
|
|
#ifndef UNDERAPPROX_NODES
|
|
std::vector<Node *> &insts = insts_of_node(node->map);
|
|
for(unsigned i = 0; i < insts.size(); i++)
|
|
if(CoverByNode(node,insts[i]))
|
|
return true;
|
|
#else
|
|
if(CoverByAll(node))
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool CloseDescendantsRec(hash_set<Node *> &memo, Node *node){
|
|
if(memo.find(node) != memo.end())
|
|
return false;
|
|
for(unsigned i = 0; i < node->Outgoing->Children.size(); i++)
|
|
if(CloseDescendantsRec(memo,node->Outgoing->Children[i]))
|
|
return true;
|
|
if(Close(node))
|
|
return true;
|
|
memo.insert(node);
|
|
return false;
|
|
}
|
|
|
|
bool CloseDescendants(Node *node){
|
|
timer_start("CloseDescendants");
|
|
hash_set<Node *> memo;
|
|
bool res = CloseDescendantsRec(memo,node);
|
|
timer_stop("CloseDescendants");
|
|
return res;
|
|
}
|
|
|
|
bool Contains(Node *node){
|
|
timer_start("Contains");
|
|
bool res = !IsCovered(node);
|
|
timer_stop("Contains");
|
|
return res;
|
|
}
|
|
|
|
bool Candidate(Node *node){
|
|
timer_start("Candidate");
|
|
bool res = !IsCovered(node) && !dominated(node);
|
|
timer_stop("Candidate");
|
|
return res;
|
|
}
|
|
|
|
void SetDominated(Node *node){
|
|
dominated(node) = true;
|
|
}
|
|
|
|
bool CouldCover(Node *covered, Node *covering){
|
|
#ifdef NO_CONJ_ON_SIMPLE_LOOPS
|
|
// Forsimple loops, we rely on propagation, not covering
|
|
if(simple_loops.find(covered->map) != simple_loops.end())
|
|
return false;
|
|
#endif
|
|
#ifdef UNDERAPPROX_NODES
|
|
// if(parent->underapprox_map.find(covering) != parent->underapprox_map.end())
|
|
// return parent->underapprox_map[covering] == covered;
|
|
#endif
|
|
if(CoverOrder(covering,covered)
|
|
&& !IsCovered(covering)){
|
|
RPFP::Transformer f(covering->Annotation); f.SetEmpty();
|
|
#if defined(TOP_DOWN) || defined(EFFORT_BOUNDED_STRAT)
|
|
if(parent->StratifiedInlining)
|
|
return true;
|
|
#endif
|
|
return !covering->Annotation.SubsetEq(f);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ContainsCex(Node *node, Counterexample &cex){
|
|
expr val = cex.tree->Eval(cex.root->Outgoing,node->Annotation.Formula);
|
|
return eq(val,parent->ctx.bool_val(true));
|
|
}
|
|
|
|
/** We conjecture that the annotations of similar nodes may be
|
|
true of this one. We start with later nodes, on the
|
|
principle that their annotations are likely weaker. We save
|
|
a counterexample -- if annotations of other nodes are true
|
|
in this counterexample, we don't need to check them.
|
|
*/
|
|
|
|
#ifndef UNDERAPPROX_NODES
|
|
bool Conjecture(Node *node){
|
|
std::vector<Node *> &insts = insts_of_node(node->map);
|
|
Counterexample cex;
|
|
for(int i = insts.size() - 1; i >= 0; i--){
|
|
Node *other = insts[i];
|
|
if(CouldCover(node,other)){
|
|
reporter()->Forcing(node,other);
|
|
if(cex.tree && !ContainsCex(other,cex))
|
|
continue;
|
|
if(cex.tree) {delete cex.tree; cex.tree = 0;}
|
|
if(parent->ProveConjecture(node,other->Annotation,other,&cex))
|
|
if(CloseDescendants(node))
|
|
return true;
|
|
}
|
|
}
|
|
if(cex.tree) {delete cex.tree; cex.tree = 0;}
|
|
return false;
|
|
}
|
|
#else
|
|
bool Conjecture(Node *node){
|
|
std::vector<Node *> &insts = insts_of_node(node->map);
|
|
Counterexample cex;
|
|
RPFP::Transformer Bound = node->Annotation;
|
|
Bound.SetEmpty();
|
|
bool some_other = false;
|
|
for(int i = insts.size() - 1; i >= 0; i--){
|
|
Node *other = insts[i];
|
|
if(CouldCover(node,other)){
|
|
reporter()->Forcing(node,other);
|
|
Bound.UnionWith(other->Annotation);
|
|
some_other = true;
|
|
}
|
|
}
|
|
if(some_other && parent->ProveConjecture(node,Bound)){
|
|
CloseDescendants(node);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void Update(Node *node, const RPFP::Transformer &update){
|
|
RemoveCoveringsBy(node);
|
|
some_updates = true;
|
|
}
|
|
|
|
#ifndef UNDERAPPROX_NODES
|
|
Node *GetSimilarNode(Node *node){
|
|
if(!some_updates)
|
|
return 0;
|
|
std::vector<Node *> &insts = insts_of_node(node->map);
|
|
for(int i = insts.size()-1; i >= 0; i--){
|
|
Node *other = insts[i];
|
|
if(dominates(node,other))
|
|
if(CoverOrder(other,node)
|
|
&& !IsCovered(other))
|
|
return other;
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
Node *GetSimilarNode(Node *node){
|
|
if(!some_updates)
|
|
return 0;
|
|
std::vector<Node *> &insts = insts_of_node(node->map);
|
|
for(int i = insts.size() - 1; i >= 0; i--){
|
|
Node *other = insts[i];
|
|
if(CoverOrder(other,node)
|
|
&& !IsCovered(other))
|
|
return other;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
bool Dominates(Node * node, Node *other){
|
|
if(node == other) return false;
|
|
if(other->Outgoing->map == 0) return true;
|
|
if(node->Outgoing->map == other->Outgoing->map){
|
|
assert(node->Outgoing->Children.size() == other->Outgoing->Children.size());
|
|
for(unsigned i = 0; i < node->Outgoing->Children.size(); i++){
|
|
Node *nc = node->Outgoing->Children[i];
|
|
Node *oc = other->Outgoing->Children[i];
|
|
if(!(nc == oc || oc->Outgoing->map ==0 || dominates(nc,oc)))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Add(Node *node){
|
|
std::vector<Node *> &insts = insts_of_node(node->map);
|
|
for(unsigned i = 0; i < insts.size(); i++){
|
|
Node *other = insts[i];
|
|
if(Dominates(node,other)){
|
|
cm[node].dominates.insert(other);
|
|
cm[other].dominated = true;
|
|
reporter()->Dominates(node, other);
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
/* This expansion heuristic makes use of a previuosly obtained
|
|
counterexample as a guide. This is for use in abstraction
|
|
refinement schemes.*/
|
|
|
|
class ReplayHeuristic : public Heuristic {
|
|
|
|
Counterexample old_cex;
|
|
public:
|
|
ReplayHeuristic(RPFP *_rpfp, Counterexample &_old_cex)
|
|
: Heuristic(_rpfp), old_cex(_old_cex)
|
|
{
|
|
}
|
|
|
|
~ReplayHeuristic(){
|
|
if(old_cex.tree)
|
|
delete old_cex.tree;
|
|
}
|
|
|
|
// Maps nodes of derivation tree into old cex
|
|
hash_map<Node *, Node*> cex_map;
|
|
|
|
void Done() {
|
|
cex_map.clear();
|
|
if(old_cex.tree)
|
|
delete old_cex.tree;
|
|
old_cex.tree = 0; // only replay once!
|
|
}
|
|
|
|
void ShowNodeAndChildren(Node *n){
|
|
std::cout << n->Name.name() << ": ";
|
|
std::vector<Node *> &chs = n->Outgoing->Children;
|
|
for(unsigned i = 0; i < chs.size(); i++)
|
|
std::cout << chs[i]->Name.name() << " " ;
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
// HACK: When matching relation names, we drop suffixes used to
|
|
// make the names unique between runs. For compatibility
|
|
// with boggie, we drop suffixes beginning with @@
|
|
std::string BaseName(const std::string &name){
|
|
int pos = name.find("@@");
|
|
if(pos >= 1)
|
|
return name.substr(0,pos);
|
|
return name;
|
|
}
|
|
|
|
virtual void ChooseExpand(const std::set<RPFP::Node *> &choices, std::set<RPFP::Node *> &best, bool high_priority, bool best_only){
|
|
if(!high_priority || !old_cex.tree){
|
|
Heuristic::ChooseExpand(choices,best,false);
|
|
return;
|
|
}
|
|
// first, try to match the derivatino tree nodes to the old cex
|
|
std::set<Node *> matched, unmatched;
|
|
for(std::set<Node *>::iterator it = choices.begin(), en = choices.end(); it != en; ++it){
|
|
Node *node = (*it);
|
|
if(cex_map.empty())
|
|
cex_map[node] = old_cex.root; // match the root nodes
|
|
if(cex_map.find(node) == cex_map.end()){ // try to match an unmatched node
|
|
Node *parent = node->Incoming[0]->Parent; // assumes we are a tree!
|
|
if(cex_map.find(parent) == cex_map.end())
|
|
throw "catastrophe in ReplayHeuristic::ChooseExpand";
|
|
Node *old_parent = cex_map[parent];
|
|
std::vector<Node *> &chs = parent->Outgoing->Children;
|
|
if(old_parent && old_parent->Outgoing){
|
|
std::vector<Node *> &old_chs = old_parent->Outgoing->Children;
|
|
for(unsigned i = 0, j=0; i < chs.size(); i++){
|
|
if(j < old_chs.size() && BaseName(chs[i]->Name.name().str()) == BaseName(old_chs[j]->Name.name().str()))
|
|
cex_map[chs[i]] = old_chs[j++];
|
|
else {
|
|
std::cerr << "WARNING: duality: unmatched child: " << chs[i]->Name.name() << std::endl;
|
|
cex_map[chs[i]] = 0;
|
|
}
|
|
}
|
|
goto matching_done;
|
|
}
|
|
for(unsigned i = 0; i < chs.size(); i++)
|
|
cex_map[chs[i]] = 0;
|
|
}
|
|
matching_done:
|
|
Node *old_node = cex_map[node];
|
|
if(!old_node)
|
|
unmatched.insert(node);
|
|
else if(old_cex.tree->Empty(old_node))
|
|
unmatched.insert(node);
|
|
else
|
|
matched.insert(node);
|
|
}
|
|
if (matched.empty() && !high_priority)
|
|
Heuristic::ChooseExpand(unmatched,best,false);
|
|
else
|
|
Heuristic::ChooseExpand(matched,best,false);
|
|
}
|
|
};
|
|
|
|
|
|
class LocalHeuristic : public Heuristic {
|
|
|
|
RPFP::Node *old_node;
|
|
public:
|
|
LocalHeuristic(RPFP *_rpfp)
|
|
: Heuristic(_rpfp)
|
|
{
|
|
old_node = 0;
|
|
}
|
|
|
|
void SetOldNode(RPFP::Node *_old_node){
|
|
old_node = _old_node;
|
|
cex_map.clear();
|
|
}
|
|
|
|
// Maps nodes of derivation tree into old subtree
|
|
hash_map<Node *, Node*> cex_map;
|
|
|
|
virtual void ChooseExpand(const std::set<RPFP::Node *> &choices, std::set<RPFP::Node *> &best){
|
|
if(old_node == 0){
|
|
Heuristic::ChooseExpand(choices,best);
|
|
return;
|
|
}
|
|
// first, try to match the derivatino tree nodes to the old cex
|
|
std::set<Node *> matched, unmatched;
|
|
for(std::set<Node *>::iterator it = choices.begin(), en = choices.end(); it != en; ++it){
|
|
Node *node = (*it);
|
|
if(cex_map.empty())
|
|
cex_map[node] = old_node; // match the root nodes
|
|
if(cex_map.find(node) == cex_map.end()){ // try to match an unmatched node
|
|
Node *parent = node->Incoming[0]->Parent; // assumes we are a tree!
|
|
if(cex_map.find(parent) == cex_map.end())
|
|
throw "catastrophe in ReplayHeuristic::ChooseExpand";
|
|
Node *old_parent = cex_map[parent];
|
|
std::vector<Node *> &chs = parent->Outgoing->Children;
|
|
if(old_parent && old_parent->Outgoing){
|
|
std::vector<Node *> &old_chs = old_parent->Outgoing->Children;
|
|
if(chs.size() == old_chs.size()){
|
|
for(unsigned i = 0; i < chs.size(); i++)
|
|
cex_map[chs[i]] = old_chs[i];
|
|
goto matching_done;
|
|
}
|
|
else
|
|
std::cout << "derivation tree does not match old cex" << std::endl;
|
|
}
|
|
for(unsigned i = 0; i < chs.size(); i++)
|
|
cex_map[chs[i]] = 0;
|
|
}
|
|
matching_done:
|
|
Node *old_node = cex_map[node];
|
|
if(!old_node)
|
|
unmatched.insert(node);
|
|
else if(old_node != node->map)
|
|
unmatched.insert(node);
|
|
else
|
|
matched.insert(node);
|
|
}
|
|
Heuristic::ChooseExpand(unmatched,best);
|
|
}
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
class StreamReporter : public Reporter {
|
|
std::ostream &s;
|
|
public:
|
|
StreamReporter(RPFP *_rpfp, std::ostream &_s)
|
|
: Reporter(_rpfp), s(_s) {event = 0;}
|
|
int event;
|
|
void ev(){
|
|
s << "[" << event++ << "]" ;
|
|
}
|
|
virtual void Extend(RPFP::Node *node){
|
|
ev(); s << "node " << node->number << ": " << node->Name.name();
|
|
std::vector<RPFP::Node *> &rps = node->Outgoing->Children;
|
|
for(unsigned i = 0; i < rps.size(); i++)
|
|
s << " " << rps[i]->number;
|
|
s << std::endl;
|
|
}
|
|
virtual void Update(RPFP::Node *node, const RPFP::Transformer &update){
|
|
ev(); s << "update " << node->number << " " << node->Name.name() << ": ";
|
|
rpfp->Summarize(update.Formula);
|
|
std::cout << std::endl;
|
|
}
|
|
virtual void Bound(RPFP::Node *node){
|
|
ev(); s << "check " << node->number << std::endl;
|
|
}
|
|
virtual void Expand(RPFP::Edge *edge){
|
|
RPFP::Node *node = edge->Parent;
|
|
ev(); s << "expand " << node->map->number << " " << node->Name.name() << std::endl;
|
|
}
|
|
virtual void AddCover(RPFP::Node *covered, std::vector<RPFP::Node *> &covering){
|
|
ev(); s << "cover " << covered->Name.name() << ": " << covered->number << " by ";
|
|
for(unsigned i = 0; i < covering.size(); i++)
|
|
std::cout << covering[i]->number << " ";
|
|
std::cout << std::endl;
|
|
}
|
|
virtual void RemoveCover(RPFP::Node *covered, RPFP::Node *covering){
|
|
ev(); s << "uncover " << covered->Name.name() << ": " << covered->number << " by " << covering->number << std::endl;
|
|
}
|
|
virtual void Forcing(RPFP::Node *covered, RPFP::Node *covering){
|
|
ev(); s << "forcing " << covered->Name.name() << ": " << covered->number << " by " << covering->number << std::endl;
|
|
}
|
|
virtual void Conjecture(RPFP::Node *node, const RPFP::Transformer &t){
|
|
ev(); s << "conjecture " << node->number << " " << node->Name.name() << ": ";
|
|
rpfp->Summarize(t.Formula);
|
|
std::cout << std::endl;
|
|
}
|
|
virtual void Dominates(RPFP::Node *node, RPFP::Node *other){
|
|
ev(); s << "dominates " << node->Name.name() << ": " << node->number << " > " << other->number << std::endl;
|
|
}
|
|
virtual void InductionFailure(RPFP::Edge *edge, const std::vector<RPFP::Node *> &children){
|
|
ev(); s << "induction failure: " << edge->Parent->Name.name() << ", children =";
|
|
for(unsigned i = 0; i < children.size(); i++)
|
|
s << " " << children[i]->number;
|
|
s << std::endl;
|
|
}
|
|
virtual void UpdateUnderapprox(RPFP::Node *node, const RPFP::Transformer &update){
|
|
ev(); s << "underapprox " << node->number << " " << node->Name.name() << ": " << update.Formula << std::endl;
|
|
}
|
|
virtual void Reject(RPFP::Edge *edge, const std::vector<RPFP::Node *> &children){
|
|
ev(); s << "reject " << edge->Parent->number << " " << edge->Parent->Name.name() << ": ";
|
|
for(unsigned i = 0; i < children.size(); i++)
|
|
s << " " << children[i]->number;
|
|
s << std::endl;
|
|
}
|
|
virtual void Message(const std::string &msg){
|
|
ev(); s << "msg " << msg << std::endl;
|
|
}
|
|
|
|
};
|
|
|
|
Solver *Solver::Create(const std::string &solver_class, RPFP *rpfp){
|
|
Duality *s = alloc(Duality,rpfp);
|
|
return s;
|
|
}
|
|
|
|
Reporter *CreateStdoutReporter(RPFP *rpfp){
|
|
return new StreamReporter(rpfp, std::cout);
|
|
}
|
|
}
|