mirror of
				https://github.com/Z3Prover/z3
				synced 2025-11-04 13:29:11 +00:00 
			
		
		
		
	additional robustness check for incremental sat solver core when it recieves interpreted constants, added PB equality to interface and special handling of equalities to adddress performance gap documented in #755
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
		
							parent
							
								
									fefd00aa49
								
							
						
					
					
						commit
						461e88e34c
					
				
					 21 changed files with 430 additions and 84 deletions
				
			
		| 
						 | 
				
			
			@ -5,6 +5,7 @@ z3_add_component(solver
 | 
			
		|||
    mus.cpp
 | 
			
		||||
    solver.cpp
 | 
			
		||||
    solver_na2as.cpp
 | 
			
		||||
    solver2tactic.cpp
 | 
			
		||||
    tactic2solver.cpp
 | 
			
		||||
  COMPONENT_DEPENDENCIES
 | 
			
		||||
    model
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,5 +57,23 @@ extern "C" {
 | 
			
		|||
        Z3_CATCH_RETURN(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Z3_ast Z3_API Z3_mk_pbeq(Z3_context c, unsigned num_args, 
 | 
			
		||||
                             Z3_ast const args[], int _coeffs[],
 | 
			
		||||
                             int k) {
 | 
			
		||||
        Z3_TRY;
 | 
			
		||||
        LOG_Z3_mk_pble(c, num_args, args, _coeffs, k);
 | 
			
		||||
        RESET_ERROR_CODE();
 | 
			
		||||
        pb_util util(mk_c(c)->m());
 | 
			
		||||
        vector<rational> coeffs;
 | 
			
		||||
        for (unsigned i = 0; i < num_args; ++i) {
 | 
			
		||||
            coeffs.push_back(rational(_coeffs[i]));
 | 
			
		||||
        }
 | 
			
		||||
        ast* a = util.mk_eq(num_args, coeffs.c_ptr(), to_exprs(args), rational(k));
 | 
			
		||||
        mk_c(c)->save_ast_trail(a);
 | 
			
		||||
        check_sorts(c, a);
 | 
			
		||||
        RETURN_Z3(of_ast(a));
 | 
			
		||||
        Z3_CATCH_RETURN(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2640,6 +2640,21 @@ namespace Microsoft.Z3
 | 
			
		|||
                                                          AST.ArrayToNative(args),
 | 
			
		||||
                                                          coeffs, k));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Create a pseudo-Boolean equal constraint.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public BoolExpr MkPBEq(int[] coeffs, BoolExpr[] args, int k)
 | 
			
		||||
        {
 | 
			
		||||
           Contract.Requires(args != null);
 | 
			
		||||
           Contract.Requires(coeffs != null);
 | 
			
		||||
           Contract.Requires(args.Length == coeffs.Length);
 | 
			
		||||
           Contract.Requires(Contract.Result<BoolExpr[]>() != null);
 | 
			
		||||
           CheckContextMatch<BoolExpr>(args);
 | 
			
		||||
           return new BoolExpr(this, Native.Z3_mk_pbeq(nCtx, (uint) args.Length,
 | 
			
		||||
                                                          AST.ArrayToNative(args),
 | 
			
		||||
                                                          coeffs, k));
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Numerals
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -258,11 +258,14 @@ namespace Microsoft.Z3
 | 
			
		|||
	/// <summary>
 | 
			
		||||
	/// Return a string the describes why the last to check returned unknown
 | 
			
		||||
	/// </summary>	
 | 
			
		||||
    	public String getReasonUnknown()
 | 
			
		||||
    	public String ReasonUnknown
 | 
			
		||||
    	{
 | 
			
		||||
            get 
 | 
			
		||||
            {
 | 
			
		||||
                Contract.Ensures(Contract.Result<string>() != null);
 | 
			
		||||
                return Native.Z3_optimize_get_reason_unknown(Context.nCtx, NativeObject);
 | 
			
		||||
            }
 | 
			
		||||
    	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7654,6 +7654,26 @@ def PbLe(args, k):
 | 
			
		|||
        _coeffs[i] = coeffs[i]
 | 
			
		||||
    return BoolRef(Z3_mk_pble(ctx.ref(), sz, _args, _coeffs, k), ctx)
 | 
			
		||||
 | 
			
		||||
def PbEq(args, k):
 | 
			
		||||
    """Create a Pseudo-Boolean inequality k constraint.
 | 
			
		||||
 | 
			
		||||
    >>> a, b, c = Bools('a b c')
 | 
			
		||||
    >>> f = PbEq(((a,1),(b,3),(c,2)), 3)
 | 
			
		||||
    """
 | 
			
		||||
    args  = _get_args(args)
 | 
			
		||||
    args, coeffs = zip(*args)
 | 
			
		||||
    if __debug__:
 | 
			
		||||
        _z3_assert(len(args) > 0, "Non empty list of arguments expected")
 | 
			
		||||
    ctx   = _ctx_from_ast_arg_list(args)
 | 
			
		||||
    if __debug__:
 | 
			
		||||
        _z3_assert(ctx is not None, "At least one of the arguments must be a Z3 expression")
 | 
			
		||||
    args = _coerce_expr_list(args, ctx)
 | 
			
		||||
    _args, sz = _to_ast_array(args)
 | 
			
		||||
    _coeffs = (ctypes.c_int * len(coeffs))()
 | 
			
		||||
    for i in range(len(coeffs)):
 | 
			
		||||
        _coeffs[i] = coeffs[i]
 | 
			
		||||
    return BoolRef(Z3_mk_pbeq(ctx.ref(), sz, _args, _coeffs, k), ctx)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def solve(*args, **keywords):
 | 
			
		||||
    """Solve the constraints `*args`.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -861,6 +861,9 @@ typedef enum
 | 
			
		|||
      - Z3_OP_PB_GE: Generalized Pseudo-Boolean cardinality constraint.
 | 
			
		||||
              Example  2*x + 3*y + 2*z >= 4
 | 
			
		||||
 | 
			
		||||
      - Z3_OP_PB_EQ: Generalized Pseudo-Boolean equality constraint.
 | 
			
		||||
              Example  2*x + 1*y + 2*z + 1*u = 4
 | 
			
		||||
 | 
			
		||||
      - Z3_OP_FPA_RM_NEAREST_TIES_TO_EVEN: Floating-point rounding mode RNE
 | 
			
		||||
 | 
			
		||||
      - Z3_OP_FPA_RM_NEAREST_TIES_TO_AWAY: Floating-point rounding mode RNA
 | 
			
		||||
| 
						 | 
				
			
			@ -1166,6 +1169,7 @@ typedef enum {
 | 
			
		|||
    Z3_OP_PB_AT_MOST=0x900,
 | 
			
		||||
    Z3_OP_PB_LE,
 | 
			
		||||
    Z3_OP_PB_GE,
 | 
			
		||||
    Z3_OP_PB_EQ,
 | 
			
		||||
 | 
			
		||||
    // Floating-Point Arithmetic
 | 
			
		||||
    Z3_OP_FPA_RM_NEAREST_TIES_TO_EVEN,
 | 
			
		||||
| 
						 | 
				
			
			@ -3913,6 +3917,18 @@ extern "C" {
 | 
			
		|||
                             Z3_ast const args[], int coeffs[],
 | 
			
		||||
                             int k);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
       \brief Pseudo-Boolean relations.
 | 
			
		||||
 | 
			
		||||
       Encode k1*p1 + k2*p2 + ... + kn*pn = k
 | 
			
		||||
 | 
			
		||||
       def_API('Z3_mk_pbeq', AST, (_in(CONTEXT), _in(UINT), _in_array(1,AST), _in_array(1,INT), _in(INT)))
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    Z3_ast Z3_API Z3_mk_pbeq(Z3_context c, unsigned num_args,
 | 
			
		||||
                             Z3_ast const args[], int coeffs[],
 | 
			
		||||
                             int k);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
       \brief Convert a \c Z3_func_decl into \c Z3_ast. This is just type casting.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -371,8 +371,18 @@ private:
 | 
			
		|||
            return l_undef;
 | 
			
		||||
        }
 | 
			
		||||
        g = m_subgoals[0];
 | 
			
		||||
        expr_ref_vector atoms(m);
 | 
			
		||||
        TRACE("sat", g->display_with_dependencies(tout););
 | 
			
		||||
        m_goal2sat(*g, m_params, m_solver, m_map, dep2asm, true);
 | 
			
		||||
        m_goal2sat.get_interpreted_atoms(atoms);
 | 
			
		||||
        if (!atoms.empty()) {
 | 
			
		||||
            std::stringstream strm;
 | 
			
		||||
            strm << "interpreted atoms sent to SAT solver " << atoms;
 | 
			
		||||
            TRACE("sat", std::cout << strm.str() << "\n";);
 | 
			
		||||
            IF_VERBOSE(1, verbose_stream() << strm.str() << "\n";);
 | 
			
		||||
            set_reason_unknown(strm.str().c_str());
 | 
			
		||||
            return l_undef;
 | 
			
		||||
        }
 | 
			
		||||
        return l_true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,7 @@ struct goal2sat::imp {
 | 
			
		|||
    bool                        m_ite_extra;
 | 
			
		||||
    unsigned long long          m_max_memory;
 | 
			
		||||
    expr_ref_vector             m_trail;
 | 
			
		||||
    expr_ref_vector             m_interpreted_atoms;
 | 
			
		||||
    bool                        m_default_external;
 | 
			
		||||
    
 | 
			
		||||
    imp(ast_manager & _m, params_ref const & p, sat::solver & s, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external):
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +68,7 @@ struct goal2sat::imp {
 | 
			
		|||
        m_map(map),
 | 
			
		||||
        m_dep2asm(dep2asm),
 | 
			
		||||
        m_trail(m),
 | 
			
		||||
        m_interpreted_atoms(m),
 | 
			
		||||
        m_default_external(default_external) {
 | 
			
		||||
        updt_params(p);
 | 
			
		||||
        m_true = sat::null_bool_var;
 | 
			
		||||
| 
						 | 
				
			
			@ -128,6 +130,9 @@ struct goal2sat::imp {
 | 
			
		|||
                m_map.insert(t, v);
 | 
			
		||||
                l = sat::literal(v, sign);
 | 
			
		||||
                TRACE("goal2sat", tout << "new_var: " << v << "\n" << mk_ismt2_pp(t, m) << "\n";);
 | 
			
		||||
                if (ext && !is_uninterp_const(t)) {
 | 
			
		||||
                    m_interpreted_atoms.push_back(t);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
| 
						 | 
				
			
			@ -474,7 +479,7 @@ bool goal2sat::has_unsupported_bool(goal const & g) {
 | 
			
		|||
    return test<unsupported_bool_proc>(g);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
goal2sat::goal2sat():m_imp(0) {
 | 
			
		||||
goal2sat::goal2sat():m_imp(0), m_interpreted_atoms(0) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void goal2sat::collect_param_descrs(param_descrs & r) {
 | 
			
		||||
| 
						 | 
				
			
			@ -492,10 +497,20 @@ struct goal2sat::scoped_set_imp {
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void goal2sat::operator()(goal const & g, params_ref const & p, sat::solver & t, atom2bool_var & m, dep2asm_map& dep2asm, bool default_external) {
 | 
			
		||||
    imp proc(g.m(), p, t, m, dep2asm, default_external);
 | 
			
		||||
    scoped_set_imp set(this, &proc);
 | 
			
		||||
    proc(g);
 | 
			
		||||
    dealloc(m_interpreted_atoms);
 | 
			
		||||
    m_interpreted_atoms = alloc(expr_ref_vector, g.m());
 | 
			
		||||
    m_interpreted_atoms->append(proc.m_interpreted_atoms);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void goal2sat::get_interpreted_atoms(expr_ref_vector& atoms) {
 | 
			
		||||
    if (m_interpreted_atoms) {
 | 
			
		||||
        atoms.append(*m_interpreted_atoms);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,8 +38,11 @@ class goal2sat {
 | 
			
		|||
    struct imp;
 | 
			
		||||
    imp *  m_imp;
 | 
			
		||||
    struct scoped_set_imp;
 | 
			
		||||
    expr_ref_vector* m_interpreted_atoms;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    goal2sat();
 | 
			
		||||
    ~goal2sat() { dealloc(m_interpreted_atoms); }
 | 
			
		||||
 | 
			
		||||
    typedef obj_map<expr, sat::literal> dep2asm_map;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +62,7 @@ public:
 | 
			
		|||
    */
 | 
			
		||||
    void operator()(goal const & g, params_ref const & p, sat::solver & t, atom2bool_var & m, dep2asm_map& dep2asm, bool default_external = false);
 | 
			
		||||
 | 
			
		||||
    void get_interpreted_atoms(expr_ref_vector& atoms);
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,63 +24,10 @@ Notes:
 | 
			
		|||
#include"rewriter_types.h"
 | 
			
		||||
#include"filter_model_converter.h"
 | 
			
		||||
#include"ast_util.h"
 | 
			
		||||
#include"solver2tactic.h"
 | 
			
		||||
 | 
			
		||||
typedef obj_map<expr, expr *> expr2expr_map;
 | 
			
		||||
 | 
			
		||||
void extract_clauses_and_dependencies(goal_ref const& g, expr_ref_vector& clauses, ptr_vector<expr>& assumptions, expr2expr_map& bool2dep, ref<filter_model_converter>& fmc) {
 | 
			
		||||
    expr2expr_map dep2bool;
 | 
			
		||||
    ptr_vector<expr> deps;
 | 
			
		||||
    ast_manager& m = g->m();
 | 
			
		||||
    expr_ref_vector clause(m);
 | 
			
		||||
    unsigned sz = g->size();
 | 
			
		||||
    for (unsigned i = 0; i < sz; i++) {
 | 
			
		||||
        expr * f            = g->form(i);
 | 
			
		||||
        expr_dependency * d = g->dep(i);
 | 
			
		||||
        if (d == 0 || !g->unsat_core_enabled()) {
 | 
			
		||||
            clauses.push_back(f);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // create clause (not d1 \/ ... \/ not dn \/ f) when the d's are the assumptions/dependencies of f.
 | 
			
		||||
            clause.reset();
 | 
			
		||||
            clause.push_back(f);
 | 
			
		||||
            deps.reset();
 | 
			
		||||
            m.linearize(d, deps);
 | 
			
		||||
            SASSERT(!deps.empty()); // d != 0, then deps must not be empty
 | 
			
		||||
            ptr_vector<expr>::iterator it  = deps.begin();
 | 
			
		||||
            ptr_vector<expr>::iterator end = deps.end();
 | 
			
		||||
            for (; it != end; ++it) {
 | 
			
		||||
                expr * d = *it;
 | 
			
		||||
                if (is_uninterp_const(d) && m.is_bool(d)) {
 | 
			
		||||
                    // no need to create a fresh boolean variable for d
 | 
			
		||||
                    if (!bool2dep.contains(d)) {
 | 
			
		||||
                        assumptions.push_back(d);
 | 
			
		||||
                        bool2dep.insert(d, d);
 | 
			
		||||
                    }
 | 
			
		||||
                    clause.push_back(m.mk_not(d));
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    // must normalize assumption
 | 
			
		||||
                    expr * b = 0;
 | 
			
		||||
                    if (!dep2bool.find(d, b)) {
 | 
			
		||||
                        b = m.mk_fresh_const(0, m.mk_bool_sort());
 | 
			
		||||
                        dep2bool.insert(d, b);
 | 
			
		||||
                        bool2dep.insert(b, d);
 | 
			
		||||
                        assumptions.push_back(b);
 | 
			
		||||
                        if (!fmc) {
 | 
			
		||||
                            fmc = alloc(filter_model_converter, m);
 | 
			
		||||
                        }
 | 
			
		||||
                        fmc->insert(to_app(b)->get_decl());
 | 
			
		||||
                    }
 | 
			
		||||
                    clause.push_back(m.mk_not(b));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            SASSERT(clause.size() > 1);
 | 
			
		||||
            expr_ref cls(m);
 | 
			
		||||
            cls = mk_or(m, clause.size(), clause.c_ptr());
 | 
			
		||||
            clauses.push_back(cls);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class smt_tactic : public tactic {
 | 
			
		||||
    smt_params                   m_params;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,8 +32,6 @@ tactic * mk_smt_tactic(params_ref const & p = params_ref());
 | 
			
		|||
tactic * mk_smt_tactic_using(bool auto_config = true, params_ref const & p = params_ref());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void extract_clauses_and_dependencies(goal_ref const& g, expr_ref_vector& clauses, ptr_vector<expr>& assumptions, obj_map<expr, expr*>& bool2dep, ref<filter_model_converter>& fmc);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  ADD_TACTIC("smt", "apply a SAT based SMT solver.", "mk_smt_tactic(p)") 
 | 
			
		||||
*/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -196,6 +196,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    virtual lbool get_consequences(expr_ref_vector const& asms, expr_ref_vector const& vars, expr_ref_vector& consequences) {
 | 
			
		||||
        switch_inc_mode();
 | 
			
		||||
        m_use_solver1_results = false;
 | 
			
		||||
        return m_solver2->get_consequences(asms, vars, consequences);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										179
									
								
								src/solver/solver2tactic.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/solver/solver2tactic.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,179 @@
 | 
			
		|||
/*++
 | 
			
		||||
Copyright (c) 2016 Microsoft Corporation
 | 
			
		||||
 | 
			
		||||
Module Name:
 | 
			
		||||
 | 
			
		||||
    solver2tactic.cpp
 | 
			
		||||
 | 
			
		||||
Abstract:
 | 
			
		||||
 | 
			
		||||
    Convert solver to a tactic.
 | 
			
		||||
 | 
			
		||||
Author:
 | 
			
		||||
 | 
			
		||||
    Nikolaj Bjorner (nbjorner) 2016-10-17
 | 
			
		||||
 | 
			
		||||
Notes:
 | 
			
		||||
   
 | 
			
		||||
--*/
 | 
			
		||||
 | 
			
		||||
#include "solver.h"
 | 
			
		||||
#include "tactic.h"
 | 
			
		||||
#include"filter_model_converter.h"
 | 
			
		||||
#include "solver2tactic.h"
 | 
			
		||||
#include "ast_util.h"
 | 
			
		||||
 | 
			
		||||
typedef obj_map<expr, expr *> expr2expr_map;
 | 
			
		||||
 | 
			
		||||
void extract_clauses_and_dependencies(goal_ref const& g, expr_ref_vector& clauses, ptr_vector<expr>& assumptions, expr2expr_map& bool2dep, ref<filter_model_converter>& fmc) {
 | 
			
		||||
    expr2expr_map dep2bool;
 | 
			
		||||
    ptr_vector<expr> deps;
 | 
			
		||||
    ast_manager& m = g->m();
 | 
			
		||||
    expr_ref_vector clause(m);
 | 
			
		||||
    unsigned sz = g->size();
 | 
			
		||||
    for (unsigned i = 0; i < sz; i++) {
 | 
			
		||||
        expr * f            = g->form(i);
 | 
			
		||||
        expr_dependency * d = g->dep(i);
 | 
			
		||||
        if (d == 0 || !g->unsat_core_enabled()) {
 | 
			
		||||
            clauses.push_back(f);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // create clause (not d1 \/ ... \/ not dn \/ f) when the d's are the assumptions/dependencies of f.
 | 
			
		||||
            clause.reset();
 | 
			
		||||
            clause.push_back(f);
 | 
			
		||||
            deps.reset();
 | 
			
		||||
            m.linearize(d, deps);
 | 
			
		||||
            SASSERT(!deps.empty()); // d != 0, then deps must not be empty
 | 
			
		||||
            ptr_vector<expr>::iterator it  = deps.begin();
 | 
			
		||||
            ptr_vector<expr>::iterator end = deps.end();
 | 
			
		||||
            for (; it != end; ++it) {
 | 
			
		||||
                expr * d = *it;
 | 
			
		||||
                if (is_uninterp_const(d) && m.is_bool(d)) {
 | 
			
		||||
                    // no need to create a fresh boolean variable for d
 | 
			
		||||
                    if (!bool2dep.contains(d)) {
 | 
			
		||||
                        assumptions.push_back(d);
 | 
			
		||||
                        bool2dep.insert(d, d);
 | 
			
		||||
                    }
 | 
			
		||||
                    clause.push_back(m.mk_not(d));
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    // must normalize assumption
 | 
			
		||||
                    expr * b = 0;
 | 
			
		||||
                    if (!dep2bool.find(d, b)) {
 | 
			
		||||
                        b = m.mk_fresh_const(0, m.mk_bool_sort());
 | 
			
		||||
                        dep2bool.insert(d, b);
 | 
			
		||||
                        bool2dep.insert(b, d);
 | 
			
		||||
                        assumptions.push_back(b);
 | 
			
		||||
                        if (!fmc) {
 | 
			
		||||
                            fmc = alloc(filter_model_converter, m);
 | 
			
		||||
                        }
 | 
			
		||||
                        fmc->insert(to_app(b)->get_decl());
 | 
			
		||||
                    }
 | 
			
		||||
                    clause.push_back(m.mk_not(b));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            SASSERT(clause.size() > 1);
 | 
			
		||||
            expr_ref cls(m);
 | 
			
		||||
            cls = mk_or(m, clause.size(), clause.c_ptr());
 | 
			
		||||
            clauses.push_back(cls);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class solver2tactic : public tactic {
 | 
			
		||||
    ast_manager& m;
 | 
			
		||||
    ref<solver> m_solver;
 | 
			
		||||
    params_ref m_params;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    solver2tactic(solver* s):
 | 
			
		||||
        m(s->get_manager()),
 | 
			
		||||
        m_solver(s)
 | 
			
		||||
    {}
 | 
			
		||||
    
 | 
			
		||||
    virtual void updt_params(params_ref const & p) {
 | 
			
		||||
        m_solver->updt_params(p);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    virtual void collect_param_descrs(param_descrs & r) {
 | 
			
		||||
        m_solver->collect_param_descrs(r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    virtual void operator()(/* in */  goal_ref const & in, 
 | 
			
		||||
                            /* out */ goal_ref_buffer & result, 
 | 
			
		||||
                            /* out */ model_converter_ref & mc, 
 | 
			
		||||
                            /* out */ proof_converter_ref & pc,
 | 
			
		||||
                            /* out */ expr_dependency_ref & core) {
 | 
			
		||||
        expr_ref_vector clauses(m);
 | 
			
		||||
        expr2expr_map               bool2dep;
 | 
			
		||||
        ptr_vector<expr>            assumptions;
 | 
			
		||||
        ref<filter_model_converter> fmc;
 | 
			
		||||
        extract_clauses_and_dependencies(in, clauses, assumptions, bool2dep, fmc);
 | 
			
		||||
        m_solver->push();
 | 
			
		||||
        m_solver->assert_expr(clauses);
 | 
			
		||||
        lbool r = m_solver->check_sat(assumptions.size(), assumptions.c_ptr()); 
 | 
			
		||||
        switch (r) {
 | 
			
		||||
        case l_true: 
 | 
			
		||||
            if (in->models_enabled()) {
 | 
			
		||||
                model_ref mdl;
 | 
			
		||||
                m_solver->get_model(mdl);
 | 
			
		||||
                mc = model2model_converter(mdl.get());
 | 
			
		||||
                mc = concat(fmc.get(), mc.get());
 | 
			
		||||
            }
 | 
			
		||||
            in->reset();
 | 
			
		||||
            result.push_back(in.get());
 | 
			
		||||
            pc = 0;
 | 
			
		||||
            core = 0;
 | 
			
		||||
            break;
 | 
			
		||||
        case l_false: {
 | 
			
		||||
            in->reset();
 | 
			
		||||
            proof* pr = 0;
 | 
			
		||||
            expr_dependency* lcore = 0;
 | 
			
		||||
            if (in->proofs_enabled()) {
 | 
			
		||||
                pr = m_solver->get_proof();
 | 
			
		||||
            }
 | 
			
		||||
            if (in->unsat_core_enabled()) {
 | 
			
		||||
                ptr_vector<expr> core;
 | 
			
		||||
                m_solver->get_unsat_core(core);
 | 
			
		||||
                for (unsigned i = 0; i < core.size(); ++i) {
 | 
			
		||||
                    lcore = m.mk_join(lcore, m.mk_leaf(bool2dep.find(core[i])));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            in->assert_expr(m.mk_false(), pr, lcore);
 | 
			
		||||
            result.push_back(in.get());
 | 
			
		||||
            mc = 0;
 | 
			
		||||
            pc = 0;
 | 
			
		||||
            core = 0;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case l_undef:
 | 
			
		||||
            if (m.canceled()) {
 | 
			
		||||
                throw tactic_exception(Z3_CANCELED_MSG);
 | 
			
		||||
            }
 | 
			
		||||
            throw tactic_exception(m_solver->reason_unknown().c_str());
 | 
			
		||||
        }
 | 
			
		||||
        m_solver->pop(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    virtual void collect_statistics(statistics & st) const {
 | 
			
		||||
        m_solver->collect_statistics(st);
 | 
			
		||||
    }
 | 
			
		||||
    virtual void reset_statistics() {}
 | 
			
		||||
 | 
			
		||||
    virtual void cleanup() {
 | 
			
		||||
        m_solver = m_solver->translate(m, m_params);
 | 
			
		||||
    }
 | 
			
		||||
    virtual void reset() { cleanup(); }
 | 
			
		||||
 | 
			
		||||
    virtual void set_logic(symbol const & l) {}
 | 
			
		||||
 | 
			
		||||
    virtual void set_progress_callback(progress_callback * callback) {
 | 
			
		||||
        m_solver->set_progress_callback(callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    virtual tactic * translate(ast_manager & m) {
 | 
			
		||||
        return alloc(solver2tactic, m_solver->translate(m, m_params));
 | 
			
		||||
    }    
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
tactic* mk_solver2tactic(solver* s) { return alloc(solver2tactic, s); }
 | 
			
		||||
							
								
								
									
										30
									
								
								src/solver/solver2tactic.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/solver/solver2tactic.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
/*++
 | 
			
		||||
Copyright (c) 2016 Microsoft Corporation
 | 
			
		||||
 | 
			
		||||
Module Name:
 | 
			
		||||
 | 
			
		||||
    solver2tactic.h
 | 
			
		||||
 | 
			
		||||
Abstract:
 | 
			
		||||
 | 
			
		||||
    Convert solver to a tactic.
 | 
			
		||||
 | 
			
		||||
Author:
 | 
			
		||||
 | 
			
		||||
    Nikolaj Bjorner (nbjorner) 2016-10-17
 | 
			
		||||
 | 
			
		||||
Notes:
 | 
			
		||||
   
 | 
			
		||||
--*/
 | 
			
		||||
#ifndef SOLVER2TACTIC_H_
 | 
			
		||||
#define SOLVER2TACTIC_H_
 | 
			
		||||
 | 
			
		||||
#include "tactic.h"
 | 
			
		||||
#include "filter_model_converter.h"
 | 
			
		||||
class solver;
 | 
			
		||||
 | 
			
		||||
tactic * mk_solver2tactic(solver* s);
 | 
			
		||||
 | 
			
		||||
void extract_clauses_and_dependencies(goal_ref const& g, expr_ref_vector& clauses, ptr_vector<expr>& assumptions, obj_map<expr, expr*>& bool2dep, ref<filter_model_converter>& fmc);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -160,6 +160,7 @@ lbool tactic2solver::check_sat_core(unsigned num_assumptions, expr * const * ass
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
    catch (z3_error & ex) {
 | 
			
		||||
        TRACE("tactic2solver", tout << "exception: " << ex.msg() << "\n";);
 | 
			
		||||
        throw ex;
 | 
			
		||||
    }
 | 
			
		||||
    catch (z3_exception & ex) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,12 +79,23 @@ static bool is_strict(decl_kind k) {
 | 
			
		|||
    return k == OP_LT || k == OP_GT;
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
bool bound_manager::is_numeral(expr* v, numeral& n, bool& is_int) {
 | 
			
		||||
    expr* w;
 | 
			
		||||
    if (m_util.is_uminus(v, w) && is_numeral(w, n, is_int)) {
 | 
			
		||||
        n.neg();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return m_util.is_numeral(v, n, is_int);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void bound_manager::operator()(expr * f, expr_dependency * d) {
 | 
			
		||||
    TRACE("bound_manager", tout << "processing:\n" << mk_ismt2_pp(f, m()) << "\n";);
 | 
			
		||||
    expr * v;
 | 
			
		||||
    numeral n;
 | 
			
		||||
    if (is_disjunctive_bound(f, d))
 | 
			
		||||
        return;
 | 
			
		||||
    if (is_equality_bound(f, d))
 | 
			
		||||
        return;
 | 
			
		||||
    bool pos = true;    
 | 
			
		||||
    while (m().is_not(f, f))
 | 
			
		||||
        pos = !pos;
 | 
			
		||||
| 
						 | 
				
			
			@ -99,10 +110,10 @@ void bound_manager::operator()(expr * f, expr_dependency * d) {
 | 
			
		|||
    expr * lhs = t->get_arg(0);
 | 
			
		||||
    expr * rhs = t->get_arg(1);
 | 
			
		||||
    bool is_int;
 | 
			
		||||
    if (is_uninterp_const(lhs) && m_util.is_numeral(rhs, n, is_int)) {
 | 
			
		||||
    if (is_uninterp_const(lhs) && is_numeral(rhs, n, is_int)) {
 | 
			
		||||
        v = lhs;
 | 
			
		||||
    }
 | 
			
		||||
    else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs, n, is_int)) {
 | 
			
		||||
    else if (is_uninterp_const(rhs) && is_numeral(lhs, n, is_int)) {
 | 
			
		||||
        v = rhs;
 | 
			
		||||
        k = swap_decl(k);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -165,6 +176,26 @@ void bound_manager::insert_lower(expr * v, bool strict, numeral const & n, expr_
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool bound_manager::is_equality_bound(expr * f, expr_dependency * d) {
 | 
			
		||||
    expr* x, *y;
 | 
			
		||||
    if (!m().is_eq(f, x, y)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (!is_uninterp_const(x)) {
 | 
			
		||||
        std::swap(x, y);
 | 
			
		||||
    }
 | 
			
		||||
    numeral n;
 | 
			
		||||
    bool is_int;
 | 
			
		||||
    if (is_uninterp_const(x) && is_numeral(y, n, is_int)) {
 | 
			
		||||
        insert_lower(x, false, n, d);
 | 
			
		||||
        insert_upper(x, false, n, d);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool bound_manager::is_disjunctive_bound(expr * f, expr_dependency * d) {
 | 
			
		||||
    numeral lo, hi, n;
 | 
			
		||||
    if (!m().is_or(f)) return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -176,14 +207,14 @@ bool bound_manager::is_disjunctive_bound(expr * f, expr_dependency * d) {
 | 
			
		|||
        expr * e = to_app(f)->get_arg(i);
 | 
			
		||||
        if (!m().is_eq(e, x, y)) return false;
 | 
			
		||||
        if (is_uninterp_const(x) && 
 | 
			
		||||
            m_util.is_numeral(y, n, is_int) && is_int && 
 | 
			
		||||
            is_numeral(y, n, is_int) && is_int && 
 | 
			
		||||
            (x == v || v == 0)) {
 | 
			
		||||
            if (v == 0) { v = x; lo = hi = n; }
 | 
			
		||||
            if (n < lo) lo = n;
 | 
			
		||||
            if (n > hi) hi = n;
 | 
			
		||||
        }
 | 
			
		||||
        else if (is_uninterp_const(y) && 
 | 
			
		||||
                 m_util.is_numeral(x, n, is_int) && is_int && 
 | 
			
		||||
                 is_numeral(x, n, is_int) && is_int && 
 | 
			
		||||
                 (y == v || v == 0)) {
 | 
			
		||||
            if (v == 0) { v = y; lo = hi = n; }
 | 
			
		||||
            if (n < lo) lo = n;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,8 @@ private:
 | 
			
		|||
    obj_map<expr, expr_dependency*> m_upper_deps;
 | 
			
		||||
    ptr_vector<expr>     m_bounded_vars;
 | 
			
		||||
    bool is_disjunctive_bound(expr * f, expr_dependency * d);
 | 
			
		||||
    bool is_equality_bound(expr * f, expr_dependency * d);
 | 
			
		||||
    bool is_numeral(expr* v, rational& n, bool& is_int);
 | 
			
		||||
    void insert_lower(expr * v, bool strict, numeral const & n, expr_dependency * d);
 | 
			
		||||
    void insert_upper(expr * v, bool strict, numeral const & n, expr_dependency * d);
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,7 @@ Revision History:
 | 
			
		|||
#include "model_smt2_pp.h"
 | 
			
		||||
#include "expr_safe_replace.h"
 | 
			
		||||
#include "ast_util.h"
 | 
			
		||||
#include "solver2tactic.h"
 | 
			
		||||
 | 
			
		||||
class nl_purify_tactic : public tactic {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -251,6 +251,16 @@ private:
 | 
			
		|||
                }
 | 
			
		||||
                sub.insert(e, t);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                IF_VERBOSE(1, 
 | 
			
		||||
                           verbose_stream() << "unprocessed entry: " << mk_pp(e, m) << "\n";
 | 
			
		||||
                           if (bm.has_lower(e, lo, s1)) {
 | 
			
		||||
                               verbose_stream() << "lower: " << lo << " " << s1 << "\n";
 | 
			
		||||
                           } 
 | 
			
		||||
                           if (bm.has_upper(e, hi, s2)) {
 | 
			
		||||
                               verbose_stream() << "upper: " << hi << " " << s2 << "\n";
 | 
			
		||||
                           });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,7 @@ Notes:
 | 
			
		|||
#include"inc_sat_solver.h"
 | 
			
		||||
#include"fd_solver.h"
 | 
			
		||||
#include"bv_rewriter.h"
 | 
			
		||||
#include"solver2tactic.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
tactic * mk_tactic_for_logic(ast_manager & m, params_ref const & p, symbol const & logic) {
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +90,8 @@ tactic * mk_tactic_for_logic(ast_manager & m, params_ref const & p, symbol const
 | 
			
		|||
        return mk_qffpbv_tactic(m, p);
 | 
			
		||||
    else if (logic=="HORN")
 | 
			
		||||
        return mk_horn_tactic(m, p);
 | 
			
		||||
    else if (logic == "QF_FD")
 | 
			
		||||
        return mk_solver2tactic(mk_fd_solver(m, p));
 | 
			
		||||
    //else if (logic=="QF_UFNRA")
 | 
			
		||||
    //    return mk_qfufnra_tactic(m, p);
 | 
			
		||||
    else 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -202,7 +202,8 @@ Notes:
 | 
			
		|||
                return ge(full, k, n, in.c_ptr());
 | 
			
		||||
            }
 | 
			
		||||
            else if (k == 1) {
 | 
			
		||||
                return mk_at_most_1(full, n, xs);
 | 
			
		||||
                literal_vector ors;
 | 
			
		||||
                return mk_at_most_1(full, n, xs, ors);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                SASSERT(2*k <= n);
 | 
			
		||||
| 
						 | 
				
			
			@ -221,6 +222,9 @@ Notes:
 | 
			
		|||
            if (dualize(k, n, xs, in)) {
 | 
			
		||||
                return eq(k, n, in.c_ptr());
 | 
			
		||||
            }
 | 
			
		||||
            else if (k == 1) {
 | 
			
		||||
                return mk_exactly_1(true, n, xs);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                SASSERT(2*k <= n);
 | 
			
		||||
                m_t = EQ;
 | 
			
		||||
| 
						 | 
				
			
			@ -238,12 +242,56 @@ Notes:
 | 
			
		|||
        
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
        literal mk_at_most_1(bool full, unsigned n, literal const* xs) {
 | 
			
		||||
 | 
			
		||||
        literal mk_and(literal l1, literal l2) {
 | 
			
		||||
            literal result = fresh();
 | 
			
		||||
            add_clause(ctx.mk_not(result), l1);
 | 
			
		||||
            add_clause(ctx.mk_not(result), l2);
 | 
			
		||||
            add_clause(ctx.mk_not(l1), ctx.mk_not(l2), result);
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void mk_implies_or(literal l, unsigned n, literal const* xs) {
 | 
			
		||||
            literal_vector lits(n, xs);
 | 
			
		||||
            lits.push_back(ctx.mk_not(l));
 | 
			
		||||
            add_clause(lits);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void mk_or_implies(literal l, unsigned n, literal const* xs) {
 | 
			
		||||
            for (unsigned j = 0; j < n; ++j) {
 | 
			
		||||
                add_clause(ctx.mk_not(xs[j]), l);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        literal mk_or(literal_vector const& ors) {
 | 
			
		||||
            if (ors.size() == 1) {
 | 
			
		||||
                return ors[0];
 | 
			
		||||
            }
 | 
			
		||||
            literal result = fresh();
 | 
			
		||||
            mk_implies_or(result, ors.size(), ors.c_ptr());
 | 
			
		||||
            mk_or_implies(result, ors.size(), ors.c_ptr());
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        literal mk_exactly_1(bool full, unsigned n, literal const* xs) {
 | 
			
		||||
            literal_vector ors;
 | 
			
		||||
            literal r1 = mk_at_most_1(full, n, xs, ors);
 | 
			
		||||
 | 
			
		||||
            if (full) {
 | 
			
		||||
                r1 = mk_and(r1, mk_or(ors));
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                mk_implies_or(r1, ors.size(), ors.c_ptr());
 | 
			
		||||
            }
 | 
			
		||||
            return r1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        literal mk_at_most_1(bool full, unsigned n, literal const* xs, literal_vector& ors) {
 | 
			
		||||
            TRACE("pb", tout << (full?"full":"partial") << " ";
 | 
			
		||||
                  for (unsigned i = 0; i < n; ++i) tout << xs[i] << " ";
 | 
			
		||||
                  tout << "\n";);
 | 
			
		||||
 | 
			
		||||
            if (!full && n >= 4) {
 | 
			
		||||
            if (false && !full && n >= 4) {
 | 
			
		||||
                return mk_at_most_1_bimander(n, xs);
 | 
			
		||||
            }
 | 
			
		||||
            literal_vector in(n, xs);
 | 
			
		||||
| 
						 | 
				
			
			@ -252,9 +300,10 @@ Notes:
 | 
			
		|||
            literal_vector ands;
 | 
			
		||||
            ands.push_back(result);
 | 
			
		||||
            while (!in.empty()) {
 | 
			
		||||
                literal_vector ors;
 | 
			
		||||
                ors.reset();
 | 
			
		||||
                unsigned i = 0;
 | 
			
		||||
                unsigned n = in.size();
 | 
			
		||||
                if (n + 1 == inc_size) ++inc_size;
 | 
			
		||||
                bool last = n <= inc_size;
 | 
			
		||||
                for (; i + inc_size < n; i += inc_size) {                    
 | 
			
		||||
                    mk_at_most_1_small(full, last, inc_size, in.c_ptr() + i, result, ands, ors);
 | 
			
		||||
| 
						 | 
				
			
			@ -267,7 +316,6 @@ Notes:
 | 
			
		|||
                }
 | 
			
		||||
                in.reset();
 | 
			
		||||
                in.append(ors);
 | 
			
		||||
                ors.reset();                
 | 
			
		||||
            }
 | 
			
		||||
            if (full) {
 | 
			
		||||
                add_clause(ands);
 | 
			
		||||
| 
						 | 
				
			
			@ -278,23 +326,16 @@ Notes:
 | 
			
		|||
        void mk_at_most_1_small(bool full, bool last, unsigned n, literal const* xs, literal result, literal_vector& ands, literal_vector& ors) {
 | 
			
		||||
            SASSERT(n > 0);
 | 
			
		||||
            if (n == 1) {
 | 
			
		||||
                if (!last) {
 | 
			
		||||
                ors.push_back(xs[0]);                
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!last) {
 | 
			
		||||
            literal ex = fresh();
 | 
			
		||||
                for (unsigned j = 0; j < n; ++j) {
 | 
			
		||||
                    add_clause(ctx.mk_not(xs[j]), ex);
 | 
			
		||||
                }
 | 
			
		||||
            mk_or_implies(ex, n, xs);
 | 
			
		||||
            if (full) {
 | 
			
		||||
                    literal_vector lits(n, xs);
 | 
			
		||||
                    lits.push_back(ctx.mk_not(ex));
 | 
			
		||||
                    add_clause(lits.size(), lits.c_ptr());
 | 
			
		||||
                mk_implies_or(ex, n, xs);
 | 
			
		||||
            }
 | 
			
		||||
            ors.push_back(ex);                
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // result => xs[0] + ... + xs[n-1] <= 1
 | 
			
		||||
            for (unsigned i = 0; i < n; ++i) {
 | 
			
		||||
                for (unsigned j = i + 1; j < n; ++j) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue