cvc5-cvc5-1.2.0.src.theory.sort_inference.cpp Maven / Gradle / Ivy
The newest version!
/******************************************************************************
* Top contributors (to current version):
* Andrew Reynolds, Paul Meng, Aina Niemetz
*
* 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.
* ****************************************************************************
*
* Sort inference module.
*
* This class implements sort inference, based on a simple algorithm:
* First, we assume all functions and predicates have distinct uninterpreted
* types.
* One pass is made through the input assertions, while a union-find data
* structure maintains necessary information regarding constraints on these
* types.
*/
#include "theory/sort_inference.h"
#include
#include
#include "expr/skolem_manager.h"
#include "options/quantifiers_options.h"
#include "options/smt_options.h"
#include "options/uf_options.h"
#include "smt/env.h"
#include "theory/quantifiers/quant_util.h"
#include "theory/rewriter.h"
using namespace cvc5::internal;
using namespace cvc5::internal::kind;
using namespace std;
namespace cvc5::internal {
namespace theory {
void SortInference::UnionFind::print(const char * c){
for( std::map< int, int >::iterator it = d_eqc.begin(); it != d_eqc.end(); ++it ){
Trace(c) << "s_" << it->first << " = s_" << it->second << ", ";
}
for( unsigned i=0; i::iterator it = c.d_eqc.begin(); it != c.d_eqc.end(); ++it ){
d_eqc[ it->first ] = it->second;
}
d_deq.insert( d_deq.end(), c.d_deq.begin(), c.d_deq.end() );
}
int SortInference::UnionFind::getRepresentative( int t ){
std::map< int, int >::iterator it = d_eqc.find( t );
if( it==d_eqc.end() || it->second==t ){
return t;
}else{
int rt = getRepresentative( it->second );
d_eqc[t] = rt;
return rt;
}
}
void SortInference::UnionFind::setEqual( int t1, int t2 ){
if( t1!=t2 ){
int rt1 = getRepresentative( t1 );
int rt2 = getRepresentative( t2 );
if( rt1>rt2 ){
d_eqc[rt1] = rt2;
}else{
d_eqc[rt2] = rt1;
}
}
}
bool SortInference::UnionFind::isValid() {
for( unsigned i=0; i& assertions)
{
Trace("sort-inference-proc") << "Calculating sort inference..." << std::endl;
// process all assertions
std::map visited;
NodeManager* nm = nodeManager();
int btId = getIdForType( nm->booleanType() );
for (const Node& a : assertions)
{
Trace("sort-inference-debug") << "Process " << a << std::endl;
std::map var_bound;
int pid = process(a, var_bound, visited);
// the type of the topmost term must be Boolean
setEqual( pid, btId );
}
Trace("sort-inference-proc") << "...done" << std::endl;
for (const std::pair& rt : d_op_return_types)
{
Trace("sort-inference") << rt.first << " : ";
TypeNode retTn = rt.first.getType();
if (!d_op_arg_types[rt.first].empty())
{
Trace("sort-inference") << "( ";
for (size_t i = 0; i < d_op_arg_types[rt.first].size(); i++)
{
recordSubsort(retTn[i], d_op_arg_types[rt.first][i]);
printSort("sort-inference", d_op_arg_types[rt.first][i]);
Trace("sort-inference") << " ";
}
Trace("sort-inference") << ") -> ";
retTn = retTn[(int)retTn.getNumChildren() - 1];
}
recordSubsort(retTn, rt.second);
printSort("sort-inference", rt.second);
Trace("sort-inference") << std::endl;
}
for (std::pair >& vt : d_var_types)
{
Trace("sort-inference")
<< "Quantified formula : " << vt.first << " : " << std::endl;
for (const Node& v : vt.first[0])
{
recordSubsort(v.getType(), vt.second[v]);
printSort("sort-inference", vt.second[v]);
Trace("sort-inference") << std::endl;
}
Trace("sort-inference") << std::endl;
}
// determine monotonicity of sorts
Trace("sort-inference-proc")
<< "Calculating monotonicty for subsorts..." << std::endl;
std::map > visitedm;
for (const Node& a : assertions)
{
Trace("sort-inference-debug")
<< "Process monotonicity for " << a << std::endl;
std::map var_bound;
processMonotonic(a, true, true, var_bound, visitedm);
}
Trace("sort-inference-proc") << "...done" << std::endl;
Trace("sort-inference") << "We have " << d_sub_sorts.size()
<< " sub-sorts : " << std::endl;
for (unsigned i = 0, size = d_sub_sorts.size(); i < size; i++)
{
printSort("sort-inference", d_sub_sorts[i]);
if (d_type_types.find(d_sub_sorts[i]) != d_type_types.end())
{
Trace("sort-inference") << " is interpreted." << std::endl;
}
else if (d_non_monotonic_sorts.find(d_sub_sorts[i])
== d_non_monotonic_sorts.end())
{
Trace("sort-inference") << " is monotonic." << std::endl;
}
else
{
Trace("sort-inference") << " is not monotonic." << std::endl;
}
}
}
Node SortInference::simplify(Node n,
std::map& model_replace_f,
std::map >& visited)
{
Trace("sort-inference-debug") << "Simplify " << n << std::endl;
std::map var_bound;
TypeNode tnn;
Node ret = simplifyNode(n, var_bound, tnn, model_replace_f, visited);
ret = rewrite(ret);
return ret;
}
void SortInference::getNewAssertions(std::vector& new_asserts)
{
NodeManager* nm = nodeManager();
// now, ensure constants are distinct
for (const std::pair >& cm : d_const_map)
{
std::vector consts;
for (const std::pair& c : cm.second)
{
Assert(c.first.isConst());
consts.push_back(c.second);
}
// add lemma enforcing introduced constants to be distinct
if (consts.size() > 1)
{
Node distinct_const = nm->mkNode(Kind::DISTINCT, consts);
Trace("sort-inference-rewrite")
<< "Add the constant distinctness lemma: " << std::endl;
Trace("sort-inference-rewrite") << " " << distinct_const << std::endl;
new_asserts.push_back(distinct_const);
}
}
// enforce constraints based on monotonicity
Trace("sort-inference-proc") << "Enforce monotonicity..." << std::endl;
for (const std::pair >& tss :
d_type_sub_sorts)
{
int nmonSort = -1;
unsigned nsorts = tss.second.size();
for (unsigned i = 0; i < nsorts; i++)
{
if (d_non_monotonic_sorts.find(tss.second[i])
!= d_non_monotonic_sorts.end())
{
nmonSort = tss.second[i];
break;
}
}
if (nmonSort != -1)
{
std::vector injections;
TypeNode base_tn = getOrCreateTypeForId(nmonSort, tss.first);
for (unsigned i = 0; i < nsorts; i++)
{
if (tss.second[i] != nmonSort)
{
TypeNode new_tn = getOrCreateTypeForId(tss.second[i], tss.first);
// make injection to nmonSort
Node a1 = mkInjection(new_tn, base_tn);
injections.push_back(a1);
if (d_non_monotonic_sorts.find(tss.second[i])
!= d_non_monotonic_sorts.end())
{
// also must make injection from nmonSort to this
Node a2 = mkInjection(base_tn, new_tn);
injections.push_back(a2);
}
}
}
if (TraceIsOn("sort-inference-rewrite"))
{
Trace("sort-inference-rewrite")
<< "Add the following injections for " << tss.first
<< " to ensure consistency wrt non-monotonic sorts : " << std::endl;
for (const Node& i : injections)
{
Trace("sort-inference-rewrite") << " " << i << std::endl;
}
}
new_asserts.insert(
new_asserts.end(), injections.begin(), injections.end());
}
}
Trace("sort-inference-proc") << "...done" << std::endl;
// no sub-sort information is stored
reset();
Trace("sort-inference-debug") << "Finished sort inference" << std::endl;
}
void SortInference::computeMonotonicity(const std::vector& assertions)
{
std::map > visitedmt;
Trace("sort-inference-proc")
<< "Calculating monotonicty for types..." << std::endl;
for (const Node& a : assertions)
{
Trace("sort-inference-debug")
<< "Process type monotonicity for " << a << std::endl;
std::map var_bound;
processMonotonic(a, true, true, var_bound, visitedmt, true);
}
Trace("sort-inference-proc") << "...done" << std::endl;
}
void SortInference::setEqual( int t1, int t2 ){
if( t1!=t2 ){
int rt1 = d_type_union_find.getRepresentative( t1 );
int rt2 = d_type_union_find.getRepresentative( t2 );
if( rt1!=rt2 ){
Trace("sort-inference-debug") << "Set equal : ";
printSort( "sort-inference-debug", rt1 );
Trace("sort-inference-debug") << " ";
printSort( "sort-inference-debug", rt2 );
Trace("sort-inference-debug") << std::endl;
/*
d_type_eq_class[rt1].insert( d_type_eq_class[rt1].end(), d_type_eq_class[rt2].begin(), d_type_eq_class[rt2].end() );
d_type_eq_class[rt2].clear();
Trace("sort-inference-debug") << "EqClass : { ";
for( int i=0; i<(int)d_type_eq_class[rt1].size(); i++ ){
Trace("sort-inference-debug") << d_type_eq_class[rt1][i] << ", ";
}
Trace("sort-inference-debug") << "}" << std::endl;
*/
if( rt2>rt1 ){
//swap
int swap = rt1;
rt1 = rt2;
rt2 = swap;
}
std::map< int, TypeNode >::iterator it1 = d_type_types.find( rt1 );
if( it1!=d_type_types.end() ){
if( d_type_types.find( rt2 )==d_type_types.end() ){
d_type_types[rt2] = it1->second;
d_type_types.erase( rt1 );
}else{
Trace("sort-inference-debug") << "...fail : associated with types " << d_type_types[rt1] << " and " << d_type_types[rt2] << std::endl;
return;
}
}
d_type_union_find.d_eqc[rt1] = rt2;
}
}
}
int SortInference::getIdForType( TypeNode tn ){
//register the return type
std::map< TypeNode, int >::iterator it = d_id_for_types.find( tn );
if( it==d_id_for_types.end() ){
int sc = d_sortCount;
d_type_types[d_sortCount] = tn;
d_id_for_types[tn] = d_sortCount;
d_sortCount++;
return sc;
}else{
return it->second;
}
}
int SortInference::process( Node n, std::map< Node, Node >& var_bound, std::map< Node, int >& visited ){
std::map< Node, int >::iterator itv = visited.find( n );
if( itv!=visited.end() ){
return itv->second;
}else{
//add to variable bindings
bool use_new_visited = false;
std::map< Node, int > new_visited;
if (n.getKind() == Kind::FORALL || n.getKind() == Kind::EXISTS)
{
if( d_var_types.find( n )!=d_var_types.end() ){
return getIdForType( n.getType() );
}else{
//apply sort inference to quantified variables
for( size_t i=0; i children;
std::vector< int > child_types;
for( size_t i=0; i= 1;
}
if( processChild ){
children.push_back( n[i] );
child_types.push_back( process( n[i], var_bound, use_new_visited ? new_visited : visited ) );
}
}
//remove from variable bindings
if (n.getKind() == Kind::FORALL || n.getKind() == Kind::EXISTS)
{
//erase from variable bound
for( size_t i=0; i::iterator it = var_bound.find( n );
if( it!=var_bound.end() ){
Trace("sort-inference-debug") << n << " is a bound variable." << std::endl;
//the return type was specified while binding
retType = d_var_types[it->second][n];
}else if( n.isVar() ){
Trace("sort-inference-debug") << n << " is a variable." << std::endl;
if( d_op_return_types.find( n )==d_op_return_types.end() ){
//assign arbitrary sort
d_op_return_types[n] = d_sortCount;
d_sortCount++;
// d_type_eq_class[d_sortCount].push_back( n );
}
retType = d_op_return_types[n];
}else if( n.isConst() ){
Trace("sort-inference-debug") << n << " is a constant." << std::endl;
//can be any type we want
retType = d_sortCount;
d_sortCount++;
}else{
Trace("sort-inference-debug") << n << " is a interpreted symbol." << std::endl;
//it is an interpreted term
for( size_t i=0; i& var_bound, std::map< Node, std::map< int, bool > >& visited, bool typeMode ) {
int pindex = hasPol ? ( pol ? 1 : -1 ) : 0;
if( visited[n].find( pindex )==visited[n].end() ){
visited[n][pindex] = true;
Trace("sort-inference-debug") << "...Process monotonic " << pol << " " << hasPol << " " << n << std::endl;
if (n.getKind() == Kind::FORALL)
{
//only consider variables universally if it is possible this quantified formula is asserted positively
if( !hasPol || pol ){
for( unsigned i=0; imkSort(ss.str());
}
Trace("sort-inference") << "-> Make type " << retType << " to correspond to ";
printSort("sort-inference", t );
Trace("sort-inference") << std::endl;
d_id_for_types[ retType ] = rt;
d_type_types[ rt ] = retType;
return retType;
}
}
TypeNode SortInference::getTypeForId( int t ){
int rt = d_type_union_find.getRepresentative( t );
if( d_type_types.find( rt )!=d_type_types.end() ){
return d_type_types[rt];
}else{
return TypeNode::null();
}
}
Node SortInference::getNewSymbol( Node old, TypeNode tn ){
NodeManager* nm = nodeManager();
SkolemManager* sm = nm->getSkolemManager();
// if no sort was inferred for this node, return original
if (tn.isNull() || tn == old.getType())
{
return old;
}
else if (old.isConst())
{
//must make constant of type tn
if( d_const_map[tn].find( old )==d_const_map[tn].end() ){
std::stringstream ss;
ss << "ic_" << tn << "_" << old;
d_const_map[tn][old] = sm->mkDummySkolem(
ss.str(),
tn,
"constant created during sort inference"); // use mkConst???
}
return d_const_map[tn][ old ];
}
else if (old.getKind() == Kind::BOUND_VARIABLE)
{
std::stringstream ss;
ss << "b_" << old;
return nm->mkBoundVar(ss.str(), tn);
}
std::stringstream ss;
ss << "i_" << old;
return sm->mkDummySkolem(ss.str(), tn, "created during sort inference");
}
Node SortInference::simplifyNode(
Node n,
std::map& var_bound,
TypeNode tnn,
std::map& model_replace_f,
std::map >& visited)
{
std::map< TypeNode, Node >::iterator itv = visited[n].find( tnn );
if( itv!=visited[n].end() ){
return itv->second;
}else{
NodeManager* nm = nodeManager();
SkolemManager* sm = nm->getSkolemManager();
Trace("sort-inference-debug2") << "Simplify " << n << ", type context=" << tnn << std::endl;
std::vector< Node > children;
std::map< Node, std::map< TypeNode, Node > > new_visited;
bool use_new_visited = false;
if (n.getKind() == Kind::FORALL || n.getKind() == Kind::EXISTS)
{
//recreate based on types of variables
std::vector< Node > new_children;
for( size_t i=0; imkNode(n[0].getKind(), new_children));
use_new_visited = true;
}
//process children
if( n.getMetaKind() == kind::metakind::PARAMETERIZED ){
children.push_back( n.getOperator() );
}
Node op;
if( n.hasOperator() ){
op = n.getOperator();
}
bool childChanged = false;
TypeNode tnnc;
for( size_t i=0; i= 1;
}
if( processChild ){
if (isHandledApplyUf(n.getKind()))
{
Assert(d_op_arg_types.find(op) != d_op_arg_types.end());
tnnc = getOrCreateTypeForId( d_op_arg_types[op][i], n[i].getType() );
Assert(!tnnc.isNull());
}
else if (n.getKind() == Kind::EQUAL
&& !isCardinalityClassFinite(
n[0].getType().getCardinalityClass(), false)
&& i == 0)
{
Assert(d_equality_types.find(n) != d_equality_types.end());
tnnc = getOrCreateTypeForId( d_equality_types[n], n[0].getType() );
Assert(!tnnc.isNull());
}
Node nc = simplifyNode(n[i],
var_bound,
tnnc,
model_replace_f,
use_new_visited ? new_visited : visited);
Trace("sort-inference-debug2") << "Simplify " << i << " " << n[i] << " returned " << nc << std::endl;
children.push_back( nc );
childChanged = childChanged || nc!=n[i];
}
}
//remove from variable bindings
Node ret;
if (n.getKind() == Kind::FORALL || n.getKind() == Kind::EXISTS)
{
//erase from variable bound
for( size_t i=0; imkNode(n.getKind(), children);
}
else if (n.getKind() == Kind::EQUAL)
{
TypeNode tn1 = children[0].getType();
TypeNode tn2 = children[1].getType();
if (tn1 != tn2)
{
Trace("sort-inference-warn") << "Sort inference created bad equality: " << children[0] << " = " << children[1] << std::endl;
Trace("sort-inference-warn") << " Types : " << children[0].getType() << " " << children[1].getType() << std::endl;
Assert(false);
}
ret = nm->mkNode(Kind::EQUAL, children);
}
else if (isHandledApplyUf(n.getKind()))
{
if( d_symbol_map.find( op )==d_symbol_map.end() ){
//make the new operator if necessary
bool opChanged = false;
std::vector< TypeNode > argTypes;
for( size_t i=0; imkFunctionType(argTypes, retType);
d_symbol_map[op] = sm->mkDummySkolem(
ss.str(), typ, "op created during sort inference");
Trace("setp-model") << "Function " << op << " is replaced with " << d_symbol_map[op] << std::endl;
model_replace_f[op] = d_symbol_map[op];
}else{
d_symbol_map[op] = op;
}
}
children[0] = d_symbol_map[op];
// make sure all children have been given proper types
for (size_t i = 0, size = n.getNumChildren(); i < size; i++)
{
TypeNode tn = children[i+1].getType();
TypeNode tna = getTypeForId( d_op_arg_types[op][i] );
if (tn != tna)
{
Trace("sort-inference-warn") << "Sort inference created bad child: " << n << " " << n[i] << " " << tn << " " << tna << std::endl;
Assert(false);
}
}
ret = nm->mkNode(Kind::APPLY_UF, children);
}else{
std::map< Node, Node >::iterator it = var_bound.find( n );
if( it!=var_bound.end() ){
ret = it->second;
}
else if (n.isVar() && n.getKind() != Kind::BOUND_VARIABLE)
{
if( d_symbol_map.find( n )==d_symbol_map.end() ){
TypeNode tn = getOrCreateTypeForId( d_op_return_types[n], n.getType() );
d_symbol_map[n] = getNewSymbol( n, tn );
}
ret = d_symbol_map[n];
}
else if (n.isConst())
{
//type is determined by context
ret = getNewSymbol( n, tnn );
}
else if (childChanged)
{
ret = nm->mkNode(n.getKind(), children);
}
else
{
ret = n;
}
}
visited[n][tnn] = ret;
return ret;
}
}
Node SortInference::mkInjection( TypeNode tn1, TypeNode tn2 ) {
NodeManager* nm = nodeManager();
SkolemManager* sm = nm->getSkolemManager();
std::vector< TypeNode > tns;
tns.push_back( tn1 );
TypeNode typ = nm->mkFunctionType(tns, tn2);
Node f =
sm->mkDummySkolem("inj", typ, "injection for monotonicity constraint");
Trace("sort-inference") << "-> Make injection " << f << " from " << tn1 << " to " << tn2 << std::endl;
Node v1 = nm->mkBoundVar("?x", tn1);
Node v2 = nm->mkBoundVar("?y", tn1);
Node ret =
nm->mkNode(Kind::FORALL,
nm->mkNode(Kind::BOUND_VAR_LIST, v1, v2),
nm->mkNode(Kind::OR,
nm->mkNode(Kind::APPLY_UF, f, v1)
.eqNode(nm->mkNode(Kind::APPLY_UF, f, v2))
.negate(),
v1.eqNode(v2)));
ret = rewrite(ret);
return ret;
}
int SortInference::getSortId( Node n ) {
Node op = n.getKind() == Kind::APPLY_UF ? n.getOperator() : n;
if( d_op_return_types.find( op )!=d_op_return_types.end() ){
return d_type_union_find.getRepresentative( d_op_return_types[op] );
}else{
return 0;
}
}
int SortInference::getSortId( Node f, Node v ) {
if( d_var_types.find( f )!=d_var_types.end() ){
return d_type_union_find.getRepresentative( d_var_types[f][v] );
}else{
return 0;
}
}
void SortInference::setSkolemVar( Node f, Node v, Node sk ){
Trace("sort-inference-temp") << "Set skolem var for " << f << ", variable " << v << std::endl;
if( isWellSortedFormula( f ) && d_var_types.find( f )==d_var_types.end() ){
//calculate the sort for variables if not done so already
std::map< Node, Node > var_bound;
std::map< Node, int > visited;
process( f, var_bound, visited );
}
d_op_return_types[sk] = getSortId( f, v );
Trace("sort-inference-temp") << "Set skolem sort id for " << sk << " to " << d_op_return_types[sk] << std::endl;
}
bool SortInference::isWellSortedFormula( Node n ) {
if (n.getType().isBoolean() && !isHandledApplyUf(n.getKind()))
{
for( unsigned i=0; i