From: Andrew Reynolds Date: Tue, 19 Apr 2022 18:05:12 +0000 (-0500) Subject: Move linear arithmetic solver to theory::arith::linear (#8628) X-Git-Tag: cvc5-1.0.1~247 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=f37078447757601cf24eb3ee92970f275a434d82;p=cvc5.git Move linear arithmetic solver to theory::arith::linear (#8628) This moves the current linear arithmetic solver to src/theory/arith/linear, and inside `cvc5::internal::theory::arith::linear`. Some code uses internal utilities from `arith::linear`, although this should be minimized. This is preparation for breaking dependencies with the old code. --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8cf8d516e..f414db97d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -349,8 +349,6 @@ libcvc5_add_sources( smt/witness_form.h smt_util/boolean_simplification.cpp smt_util/boolean_simplification.h - theory/arith/approx_simplex.cpp - theory/arith/approx_simplex.h theory/arith/arith_evaluator.cpp theory/arith/arith_evaluator.h theory/arith/arith_ite_utils.cpp @@ -365,48 +363,66 @@ libcvc5_add_sources( theory/arith/arith_rewriter.h theory/arith/arith_state.cpp theory/arith/arith_state.h - theory/arith/arith_static_learner.cpp - theory/arith/arith_static_learner.h theory/arith/arith_utilities.cpp theory/arith/arith_utilities.h - theory/arith/arithvar.cpp - theory/arith/arithvar.h - theory/arith/attempt_solution_simplex.cpp - theory/arith/attempt_solution_simplex.h - theory/arith/bound_counts.h theory/arith/bound_inference.cpp theory/arith/bound_inference.h theory/arith/branch_and_bound.cpp theory/arith/branch_and_bound.h - theory/arith/callbacks.cpp - theory/arith/callbacks.h - theory/arith/congruence_manager.cpp - theory/arith/congruence_manager.h - theory/arith/constraint.cpp - theory/arith/constraint.h - theory/arith/constraint_forward.h - theory/arith/cut_log.cpp - theory/arith/cut_log.h theory/arith/delta_rational.cpp theory/arith/delta_rational.h - theory/arith/dio_solver.cpp - theory/arith/dio_solver.h - theory/arith/dual_simplex.cpp - theory/arith/dual_simplex.h theory/arith/equality_solver.cpp theory/arith/equality_solver.h - theory/arith/error_set.cpp - theory/arith/error_set.h - theory/arith/fc_simplex.cpp - theory/arith/fc_simplex.h - theory/arith/infer_bounds.cpp - theory/arith/infer_bounds.h theory/arith/inference_manager.cpp theory/arith/inference_manager.h - theory/arith/linear_equality.cpp - theory/arith/linear_equality.h - theory/arith/matrix.cpp - theory/arith/matrix.h + theory/arith/linear/approx_simplex.cpp + theory/arith/linear/approx_simplex.h + theory/arith/linear/arith_static_learner.cpp + theory/arith/linear/arith_static_learner.h + theory/arith/linear/arithvar.cpp + theory/arith/linear/arithvar.h + theory/arith/linear/attempt_solution_simplex.cpp + theory/arith/linear/attempt_solution_simplex.h + theory/arith/linear/bound_counts.h + theory/arith/linear/callbacks.cpp + theory/arith/linear/callbacks.h + theory/arith/linear/congruence_manager.cpp + theory/arith/linear/congruence_manager.h + theory/arith/linear/constraint.cpp + theory/arith/linear/constraint.h + theory/arith/linear/constraint_forward.h + theory/arith/linear/cut_log.cpp + theory/arith/linear/cut_log.h + theory/arith/linear/dio_solver.cpp + theory/arith/linear/dio_solver.h + theory/arith/linear/dual_simplex.cpp + theory/arith/linear/dual_simplex.h + theory/arith/linear/error_set.cpp + theory/arith/linear/error_set.h + theory/arith/linear/fc_simplex.cpp + theory/arith/linear/fc_simplex.h + theory/arith/linear/infer_bounds.cpp + theory/arith/linear/infer_bounds.h + theory/arith/linear/linear_equality.cpp + theory/arith/linear/linear_equality.h + theory/arith/linear/matrix.cpp + theory/arith/linear/matrix.h + theory/arith/linear/normal_form.cpp + theory/arith/linear/normal_form.h + theory/arith/linear/partial_model.cpp + theory/arith/linear/partial_model.h + theory/arith/linear/simplex.cpp + theory/arith/linear/simplex.h + theory/arith/linear/simplex_update.cpp + theory/arith/linear/simplex_update.h + theory/arith/linear/soi_simplex.cpp + theory/arith/linear/soi_simplex.h + theory/arith/linear/tableau.cpp + theory/arith/linear/tableau.h + theory/arith/linear/tableau_sizes.cpp + theory/arith/linear/tableau_sizes.h + theory/arith/linear/theory_arith_private.cpp + theory/arith/linear/theory_arith_private.h theory/arith/nl/coverings_solver.cpp theory/arith/nl/coverings_solver.h theory/arith/nl/coverings/cdcac.cpp @@ -487,12 +503,8 @@ libcvc5_add_sources( theory/arith/nl/transcendental/transcendental_solver.h theory/arith/nl/transcendental/transcendental_state.cpp theory/arith/nl/transcendental/transcendental_state.h - theory/arith/normal_form.cpp - theory/arith/normal_form.h theory/arith/operator_elim.cpp theory/arith/operator_elim.h - theory/arith/partial_model.cpp - theory/arith/partial_model.h theory/arith/pp_rewrite_eq.cpp theory/arith/pp_rewrite_eq.h theory/arith/proof_checker.cpp @@ -506,20 +518,8 @@ libcvc5_add_sources( theory/arith/rewriter/rewrite_atom.h theory/arith/rewrites.cpp theory/arith/rewrites.h - theory/arith/simplex.cpp - theory/arith/simplex.h - theory/arith/simplex_update.cpp - theory/arith/simplex_update.h - theory/arith/soi_simplex.cpp - theory/arith/soi_simplex.h - theory/arith/tableau.cpp - theory/arith/tableau.h - theory/arith/tableau_sizes.cpp - theory/arith/tableau_sizes.h theory/arith/theory_arith.cpp theory/arith/theory_arith.h - theory/arith/theory_arith_private.cpp - theory/arith/theory_arith_private.h theory/arith/theory_arith_type_rules.cpp theory/arith/theory_arith_type_rules.h theory/arith/type_enumerator.h diff --git a/src/preprocessing/passes/pseudo_boolean_processor.cpp b/src/preprocessing/passes/pseudo_boolean_processor.cpp index e680dc4b5..a3ceddeb9 100644 --- a/src/preprocessing/passes/pseudo_boolean_processor.cpp +++ b/src/preprocessing/passes/pseudo_boolean_processor.cpp @@ -22,7 +22,7 @@ #include "preprocessing/assertion_pipeline.h" #include "preprocessing/preprocessing_pass_context.h" #include "theory/arith/arith_utilities.h" -#include "theory/arith/normal_form.h" +#include "theory/arith/linear/normal_form.h" #include "theory/rewriter.h" namespace cvc5::internal { @@ -78,13 +78,13 @@ bool PseudoBooleanProcessor::decomposeAssertion(Node assertion, bool negated) return false; } - if (!Polynomial::isMember(l)) + if (!linear::Polynomial::isMember(l)) { Trace("pbs::rewrites") << "not polynomial" << assertion << std::endl; return false; } - Polynomial p = Polynomial::parsePolynomial(l); + linear::Polynomial p = linear::Polynomial::parsePolynomial(l); clear(); if (negated) { @@ -112,9 +112,9 @@ bool PseudoBooleanProcessor::decomposeAssertion(Node assertion, bool negated) Assert(d_off.value().isIntegral()); int adj = negated ? -1 : 1; - for (Polynomial::iterator i = p.begin(), end = p.end(); i != end; ++i) + for (linear::Polynomial::iterator i = p.begin(), end = p.end(); i != end; ++i) { - Monomial m = *i; + linear::Monomial m = *i; const Rational& coeff = m.getConstant().getValue(); if (!(coeff.isOne() || coeff.isNegativeOne())) { @@ -122,7 +122,7 @@ bool PseudoBooleanProcessor::decomposeAssertion(Node assertion, bool negated) } Assert(coeff.sgn() != 0); - const VarList& vl = m.getVarList(); + const linear::VarList& vl = m.getVarList(); Node v = vl.getNode(); if (!isPseudoBoolean(v)) diff --git a/src/theory/arith/approx_simplex.cpp b/src/theory/arith/approx_simplex.cpp deleted file mode 100644 index 0c8f4506a..000000000 --- a/src/theory/arith/approx_simplex.cpp +++ /dev/null @@ -1,3083 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Aina Niemetz - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ -#include "theory/arith/approx_simplex.h" - -#include - -#include -#include -#include - -#include "base/cvc5config.h" -#include "base/output.h" -#include "proof/eager_proof_generator.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" -#include "theory/arith/cut_log.h" -#include "theory/arith/matrix.h" -#include "theory/arith/normal_form.h" - -#ifdef CVC5_USE_GLPK -#include "theory/arith/partial_model.h" -#endif - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -struct AuxInfo { - TreeLog* tl; - int pivotLimit; - int branchLimit; - int branchDepth; - MipResult term; /* terminatation */ -}; - -enum SlackReplace { SlackUndef=0, SlackLB, SlackUB, SlackVLB, SlackVUB }; - -std::ostream& operator<<(std::ostream& out, MipResult res){ - switch(res){ - case MipUnknown: - out << "MipUnknown"; break; - case MipBingo: - out << "MipBingo"; break; - case MipClosed: - out << "MipClosed"; break; - case BranchesExhausted: - out << "BranchesExhausted"; break; - case PivotsExhauasted: - out << "PivotsExhauasted"; break; - case ExecExhausted: - out << "ExecExhausted"; break; - default: - out << "Unexpected Mip Value!"; break; - } - return out; -} -struct VirtualBound { - // Either x <= d * y or x >= d * y - ArithVar x; // variable being bounded - Kind k; // either LEQ or GEQ - Rational d; // the multiple on y - ArithVar y; // the variable that is the upper bound - ConstraintP c; // the original constraint relating x and y - - VirtualBound() - : x(ARITHVAR_SENTINEL) - , k(kind::UNDEFINED_KIND) - , d() - , y(ARITHVAR_SENTINEL) - , c(NullConstraint) - {} - VirtualBound(ArithVar toBound, Kind rel, const Rational& coeff, ArithVar bounding, ConstraintP orig) - : x(toBound) - , k(rel) - , d(coeff) - , y(bounding) - , c(orig) - { - Assert(k == kind::LEQ || k == kind::GEQ); - } -}; - -struct CutScratchPad { - bool d_failure; // if the construction was unsuccessful - - /* GOMORY CUTS Datastructures */ - ArithVar d_basic; // a variable that is basic in the approximate solver - DenseVector d_tabRow; // a row in the tableau not including d_basic, equal to 0 - DenseMap d_toBound; // each variable in toBound maps each variable in tabRow to either an upper/lower bound - - /* MIR CUTS Datastructures */ - DenseMap d_slacks;// The x'[i] selected for x[i] - DenseMap d_vub; // Virtual upper bounds. - DenseMap d_vlb; // Virtual lower bounds. - DenseMap d_compRanges; - - // a sum of rows in the tableau, with possible replacements for fixed - // sum aggLhs[i] x[i] = aggRhs; - DenseVector d_agg; - // Takes agg and replaces x[i] with a slack variable x'[i] - // Takes agg and replaces x[i] with a slack variable x'[i] - // sum modLhs[i] x'[i] = modRhs; - DenseVector d_mod; - - // Takes mod, and performs c-Mir on it - // sum alpha[i] x'[i] <= beta - DenseVector d_alpha; - - /* The constructed cut */ - // sum cut[i] x[i] <= cutRhs - DenseVector d_cut; - Kind d_cutKind; - - /* The constraints used throughout construction. */ - std::set d_explanation; // use pointer equality - CutScratchPad(){ - clear(); - } - void clear(){ - d_failure = false; - d_basic = ARITHVAR_SENTINEL; - d_tabRow.purge(); - d_toBound.purge(); - - d_slacks.purge(); - d_vub.purge(); - d_vlb.purge(); - d_compRanges.purge(); - - d_agg.purge(); - d_mod.purge(); - d_alpha.purge(); - - d_cut.purge(); - d_cutKind = kind::UNDEFINED_KIND; - d_explanation.clear(); - } -}; - -ApproximateStatistics::ApproximateStatistics() - : d_branchMaxDepth( - smtStatisticsRegistry().registerInt("z::approx::branchMaxDepth")), - d_branchesMaxOnAVar( - smtStatisticsRegistry().registerInt("z::approx::branchesMaxOnAVar")), - d_gaussianElimConstructTime(smtStatisticsRegistry().registerTimer( - "z::approx::gaussianElimConstruct::time")), - d_gaussianElimConstruct(smtStatisticsRegistry().registerInt( - "z::approx::gaussianElimConstruct::calls")), - d_averageGuesses( - smtStatisticsRegistry().registerAverage("z::approx::averageGuesses")) -{ -} - -ApproximateSimplex::ApproximateSimplex(const ArithVariables& v, TreeLog& l, - ApproximateStatistics& s) - : d_vars(v) - , d_log(l) - , d_stats(s) - , d_pivotLimit(std::numeric_limits::max()) - , d_branchLimit(std::numeric_limits::max()) - , d_maxDepth(std::numeric_limits::max()) -{} - -void ApproximateSimplex::setPivotLimit(int pl){ - Assert(pl >= 0); - d_pivotLimit = pl; -} - -void ApproximateSimplex::setBranchingDepth(int bd){ - Assert(bd >= 0); - d_maxDepth = bd; -} - -void ApproximateSimplex::setBranchOnVariableLimit(int bl){ - Assert(bl >= 0); - d_branchLimit = bl; -} - -bool ApproximateSimplex::roughlyEqual(double a, double b){ - if (a == 0){ - return -SMALL_FIXED_DELTA <= b && b <= SMALL_FIXED_DELTA; - }else if (b == 0){ - return -SMALL_FIXED_DELTA <= a && a <= SMALL_FIXED_DELTA; - }else{ - return std::abs(b/a) <= TOLERENCE && std::abs(a/b) <= TOLERENCE; - } -} - -Rational ApproximateSimplex::cfeToRational(const vector& exp){ - if(exp.empty()){ - return Rational(0); - }else{ - Rational result = exp.back(); - vector::const_reverse_iterator exp_iter = exp.rbegin(); - vector::const_reverse_iterator exp_end = exp.rend(); - ++exp_iter; - while(exp_iter != exp_end){ - result = result.inverse(); - const Integer& i = *exp_iter; - result += i; - ++exp_iter; - } - return result; - } -} -std::vector ApproximateSimplex::rationalToCfe(const Rational& q, int depth){ - vector mods; - if(!q.isZero()){ - Rational carry = q; - for(int i = 0; i <= depth; ++i){ - Assert(!carry.isZero()); - mods.push_back(Integer()); - Integer& back = mods.back(); - back = carry.floor(); - Trace("rationalToCfe") << " cfe["<= Integer(1)); - if( r.getDenominator() <= K ){ - return r; - } - - // current numerator and denominator that has not been resolved in the cfe - Integer num = r.getNumerator(), den = r.getDenominator(); - Integer quot,rem; - - unsigned t = 0; - // For a sequence of candidate solutions q_t/p_t - // we keep only 3 time steps: 0[prev], 1[current], 2[next] - // timesteps with a fake timestep 0 (p is 0 and q is 1) - // at timestep 1 - Integer p[3]; // h - Integer q[3]; // k - // load the first 3 time steps manually - p[0] = 0; q[0] = 1; // timestep -2 - p[1] = 1; q[1] = 0; // timestep -1 - - Integer::floorQR(quot, rem, num, den); - num = den; den = rem; - - q[2] = q[0] + quot*q[1]; - p[2] = p[0] + quot*p[1]; - Trace("estimateWithCFE") << " cfe["< ApproximateSimplex::estimateWithCFE(double d, - const Integer& D) -{ - if (std::optional from_double = Rational::fromDouble(d)) - { - return estimateWithCFE(*from_double, D); - } - return std::optional(); -} - -std::optional ApproximateSimplex::estimateWithCFE(double d) -{ - return estimateWithCFE(d, Integer(s_defaultMaxDenom)); -} - -class ApproxNoOp : public ApproximateSimplex { -public: - ApproxNoOp(const ArithVariables& v, TreeLog& l, ApproximateStatistics& s) - : ApproximateSimplex(v,l,s) - {} - ~ApproxNoOp(){} - - LinResult solveRelaxation() override { return LinUnknown; } - Solution extractRelaxation() const override { return Solution(); } - - ArithRatPairVec heuristicOptCoeffs() const override - { - return ArithRatPairVec(); - } - - MipResult solveMIP(bool al) override { return MipUnknown; } - Solution extractMIP() const override { return Solution(); } - - void setOptCoeffs(const ArithRatPairVec& ref) override {} - - void tryCut(int nid, CutInfo& cut) override {} - - std::vector getValidCuts(const NodeLog& node) override - { - return std::vector(); - } - - ArithVar getBranchVar(const NodeLog& nl) const override - { - return ARITHVAR_SENTINEL; - } - - double sumInfeasibilities(bool mip) const override { return 0.0; } -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -/* Begin the declaration of GLPK specific code. */ -#ifdef CVC5_USE_GLPK -extern "C" { -#include -}/* extern "C" */ - -namespace cvc5::internal { -namespace theory { -namespace arith { - -Kind glpk_type_to_kind(int glpk_cut_type){ - switch(glpk_cut_type){ - case GLP_LO: return kind::GEQ; - case GLP_UP: return kind::LEQ; - case GLP_FX: return kind::EQUAL; - case GLP_DB: - case GLP_FR: - default: return kind::UNDEFINED_KIND; - } -} - -class GmiInfo; -class MirInfo; -class BranchCutInfo; - -class ApproxGLPK : public ApproximateSimplex { -private: - glp_prob* d_inputProb; /* a copy of the input prob */ - glp_prob* d_realProb; /* a copy of the real relaxation output */ - glp_prob* d_mipProb; /* a copy of the integer prob */ - - DenseMap d_colIndices; - DenseMap d_rowIndices; - - NodeLog::RowIdMap d_rootRowIds; - //DenseMap d_rowToArithVar; - DenseMap d_colToArithVar; - - bool d_solvedRelaxation; - bool d_solvedMIP; - - CutScratchPad d_pad; - - std::vector d_denomGuesses; - -public: - ApproxGLPK(const ArithVariables& v, TreeLog& l, ApproximateStatistics& s); - ~ApproxGLPK(); - - LinResult solveRelaxation() override; - Solution extractRelaxation() const override { return extractSolution(false); } - - ArithRatPairVec heuristicOptCoeffs() const override; - - MipResult solveMIP(bool al) override; - Solution extractMIP() const override { return extractSolution(true); } - void setOptCoeffs(const ArithRatPairVec& ref) override; - std::vector getValidCuts(const NodeLog& nodes) override; - ArithVar getBranchVar(const NodeLog& con) const override; - - static void printGLPKStatus(int status, std::ostream& out); - - -private: - Solution extractSolution(bool mip) const; - int guessDir(ArithVar v) const; - - // get this stuff out of here - void tryCut(int nid, CutInfo& cut) override; - - ArithVar _getArithVar(int nid, int M, int ind) const; - ArithVar getArithVarFromRow(int nid, int ind) const - { - if (ind >= 0) - { - const NodeLog& nl = d_log.getNode(nid); - return nl.lookupRowId(ind); - } - return ARITHVAR_SENTINEL; - } - - // virtual void mapRowId(int nid, int ind, ArithVar v){ - // NodeLog& nl = d_log.getNode(nid); - // nl.mapRowId(ind, v); - // } - // virtual void applyRowsDeleted(int nid, const RowsDeleted& rd){ - // NodeLog& nl = d_log.getNode(nid); - // nl.applyRowsDeleted(rd); - // } - - ArithVar getArithVarFromStructural(int ind) const{ - if(ind >= 0){ - unsigned u = (unsigned) ind; - if(d_colToArithVar.isKey(u)){ - return d_colToArithVar[u]; - } - } - return ARITHVAR_SENTINEL; - } - - /** - * Attempts to make the row vector vec on the pad. - * If this is not in the row span of the original tableau this - * raises the failure flag. - */ - bool attemptConstructTableRow(int node, int M, const PrimitiveVec& vec); - bool guessCoefficientsConstructTableRow(int node, int M, const PrimitiveVec& vec); - bool guessCoefficientsConstructTableRow(int node, int M, const PrimitiveVec& vec, const Integer& D); - bool gaussianElimConstructTableRow(int node, int M, const PrimitiveVec& vec); - - /* This is a guess of a vector in the row span of the tableau. - * Attempt to cancel out all of the variables. - * returns true if this is constructable. - */ - bool guessIsConstructable(const DenseMap& guess) const; - - /** - * Loads a vector of statuses into a dense map over bounds. - * returns true on failure. - */ - bool loadToBound(int node, int M, int len, int* inds, int* statuses, - DenseMap& toBound) const; - - /** checks the cut on the pad for whether it is sufficiently similar to cut. */ - bool checkCutOnPad(int nid, const CutInfo& cut) const; - - - /** turns the pad into a node and creates an explanation. */ - //std::pair makeCutNodes(int nid, const CutInfo& cut) const; - - // true means failure! - // BRANCH CUTS - bool attemptBranchCut(int nid, const BranchCutInfo& br); - - // GOMORY CUTS - bool attemptGmi(int nid, const GmiInfo& gmi); - /** tries to turn the information on the pad into a cut. */ - bool constructGmiCut(); - - // MIR CUTS - bool attemptMir(int nid, const MirInfo& mir); - bool applyCMIRRule(int nid, const MirInfo& mir); - bool makeRangeForComplemented(int nid, const MirInfo& mir); - bool loadSlacksIntoPad(int nid, const MirInfo& mir); - bool loadVirtualBoundsIntoPad(int nid, const MirInfo& mir); - bool loadRowSumIntoAgg(int nid, int M, const PrimitiveVec& mir); - bool buildModifiedRow(int nid, const MirInfo& mir); - bool constructMixedKnapsack(); - bool replaceSlacksOnCuts(); - bool loadVB(int nid, int M, int j, int ri, bool wantUb, VirtualBound& tmp); - - double sumInfeasibilities(bool mip) const override - { - return sumInfeasibilities(mip? d_mipProb : d_realProb); - } - double sumInfeasibilities(glp_prob* prob, bool mip) const; -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal -#endif /*#ifdef CVC5_USE_GLPK */ -/* End the declaration of GLPK specific code. */ - -/* Begin GPLK/NOGLPK Glue code. */ -namespace cvc5::internal { -namespace theory { -namespace arith { -ApproximateSimplex* ApproximateSimplex::mkApproximateSimplexSolver(const ArithVariables& vars, TreeLog& l, ApproximateStatistics& s){ -#ifdef CVC5_USE_GLPK - return new ApproxGLPK(vars, l, s); -#else - return new ApproxNoOp(vars, l, s); -#endif -} -bool ApproximateSimplex::enabled() { -#ifdef CVC5_USE_GLPK - return true; -#else - return false; -#endif -} -} // namespace arith -} // namespace theory -} // namespace cvc5::internal -/* End GPLK/NOGLPK Glue code. */ - -/* Begin GPLK implementation. */ -#ifdef CVC5_USE_GLPK -namespace cvc5::internal { -namespace theory { -namespace arith { - -#ifdef CVC5_ASSERTIONS -static CutInfoKlass fromGlpkClass(int klass){ - switch(klass){ - case GLP_RF_GMI: return GmiCutKlass; - case GLP_RF_MIR: return MirCutKlass; - case GLP_RF_COV: - case GLP_RF_CLQ: - default: return UnknownKlass; - } -} -#endif - -ApproxGLPK::ApproxGLPK(const ArithVariables& var, - TreeLog& l, - ApproximateStatistics& s) - : ApproximateSimplex(var, l, s), - d_inputProb(nullptr), - d_realProb(nullptr), - d_mipProb(nullptr), - d_solvedRelaxation(false), - d_solvedMIP(false) -{ - - d_denomGuesses.push_back(Integer(1<<22)); - d_denomGuesses.push_back(Integer(ApproximateSimplex::s_defaultMaxDenom)); - d_denomGuesses.push_back(Integer(1ul<<29)); - d_denomGuesses.push_back(Integer(1ul<<31)); - - d_inputProb = glp_create_prob(); - d_realProb = glp_create_prob(); - d_mipProb = glp_create_prob(); - glp_set_obj_dir(d_inputProb, GLP_MAX); - glp_set_prob_name(d_inputProb, "ApproximateSimplex::approximateFindModel"); - - int numRows = 0; - int numCols = 0; - - // Assign each variable to a row and column variable as it appears in the input - for(ArithVariables::var_iterator vi = d_vars.var_begin(), vi_end = d_vars.var_end(); vi != vi_end; ++vi){ - ArithVar v = *vi; - - if(d_vars.isAuxiliary(v)){ - ++numRows; - d_rowIndices.set(v, numRows); - //mapRowId(d_log.getRootId(), numRows, v); - d_rootRowIds.insert(make_pair(numRows, v)); - //d_rowToArithVar.set(numRows, v); - Trace("approx") << "Row vars: " << v << "<->" << numRows << endl; - }else{ - ++numCols; - d_colIndices.set(v, numCols); - d_colToArithVar.set(numCols, v); - Trace("approx") << "Col vars: " << v << "<->" << numCols << endl; - } - } - Assert(numRows > 0); - Assert(numCols > 0); - - glp_add_rows(d_inputProb, numRows); - glp_add_cols(d_inputProb, numCols); - - // Assign the upper/lower bounds and types to each variable - for(ArithVariables::var_iterator vi = d_vars.var_begin(), vi_end = d_vars.var_end(); vi != vi_end; ++vi){ - ArithVar v = *vi; - - int type; - double lb = 0.0; - double ub = 0.0; - if(d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ - if(d_vars.boundsAreEqual(v)){ - type = GLP_FX; - }else{ - type = GLP_DB; - } - lb = d_vars.getLowerBound(v).approx(SMALL_FIXED_DELTA); - ub = d_vars.getUpperBound(v).approx(SMALL_FIXED_DELTA); - }else if(d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ - type = GLP_UP; - ub = d_vars.getUpperBound(v).approx(SMALL_FIXED_DELTA); - }else if(!d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ - type = GLP_LO; - lb = d_vars.getLowerBound(v).approx(SMALL_FIXED_DELTA); - }else{ - type = GLP_FR; - } - - if(d_vars.isAuxiliary(v)){ - int rowIndex = d_rowIndices[v]; - glp_set_row_bnds(d_inputProb, rowIndex, type, lb, ub); - }else{ - int colIndex = d_colIndices[v]; - // is input is correct here - int kind = d_vars.isInteger(v) ? GLP_IV : GLP_CV; - glp_set_col_kind(d_inputProb, colIndex, kind); - glp_set_col_bnds(d_inputProb, colIndex, type, lb, ub); - } - } - - // Count the number of entries - int numEntries = 0; - for(DenseMap::const_iterator i = d_rowIndices.begin(), i_end = d_rowIndices.end(); i != i_end; ++i){ - ArithVar v = *i; - Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); - for (Polynomial::iterator j = p.begin(), end = p.end(); j != end; ++j) - { - ++numEntries; - } - } - - int* ia = new int[numEntries+1]; - int* ja = new int[numEntries+1]; - double* ar = new double[numEntries+1]; - - int entryCounter = 0; - for(DenseMap::const_iterator i = d_rowIndices.begin(), i_end = d_rowIndices.end(); i != i_end; ++i){ - ArithVar v = *i; - int rowIndex = d_rowIndices[v]; - - Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); - - for (Polynomial::iterator j = p.begin(), end = p.end(); j != end; ++j) - { - const Monomial& mono = *j; - const Constant& constant = mono.getConstant(); - const VarList& variable = mono.getVarList(); - - Node n = variable.getNode(); - - Assert(d_vars.hasArithVar(n)); - ArithVar av = d_vars.asArithVar(n); - int colIndex = d_colIndices[av]; - double coeff = constant.getValue().getDouble(); - - ++entryCounter; - ia[entryCounter] = rowIndex; - ja[entryCounter] = colIndex; - ar[entryCounter] = coeff; - } - } - glp_load_matrix(d_inputProb, numEntries, ia, ja, ar); - - delete[] ia; - delete[] ja; - delete[] ar; -} -int ApproxGLPK::guessDir(ArithVar v) const{ - if(d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ - return -1; - }else if(!d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ - return 1; - }else if(!d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ - return 0; - }else{ - int ubSgn = d_vars.getUpperBound(v).sgn(); - int lbSgn = d_vars.getLowerBound(v).sgn(); - - if(ubSgn != 0 && lbSgn == 0){ - return -1; - }else if(ubSgn == 0 && lbSgn != 0){ - return 1; - } - - return 1; - } -} - -ArithRatPairVec ApproxGLPK::heuristicOptCoeffs() const{ - ArithRatPairVec ret; - - // Strategies are guess: - // 1 simple shared "ceiling" variable: danoint, pk1 - // x1 >= c, x1 >= tmp1, x1 >= tmp2, ... - // 1 large row: fixnet, vpm2, pp08a - // (+ .......... ) <= c - // Not yet supported: - // 1 complex shared "ceiling" variable: opt1217 - // x1 >= c, x1 >= (+ ..... ), x1 >= (+ ..... ) - // and all of the ... are the same sign - - - // Candidates: - // 1) Upper and lower bounds are not equal. - // 2) The variable is not integer - // 3a) For columns look for a ceiling variable - // 3B) For rows look for a large row with - - DenseMap d_colCandidates; - DenseMap d_rowCandidates; - - double sumRowLength = 0.0; - uint32_t maxRowLength = 0; - for(ArithVariables::var_iterator vi = d_vars.var_begin(), vi_end = d_vars.var_end(); vi != vi_end; ++vi){ - ArithVar v = *vi; - - int type; - if(d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ - if(d_vars.boundsAreEqual(v)){ - type = GLP_FX; - }else{ - type = GLP_DB; - } - }else if(d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ - type = GLP_UP; - }else if(!d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ - type = GLP_LO; - }else{ - type = GLP_FR; - } - - if(type != GLP_FX && type != GLP_FR){ - - if(d_vars.isAuxiliary(v)){ - Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); - uint32_t len = p.size(); - d_rowCandidates.set(v, len); - sumRowLength += len; - maxRowLength = std::max(maxRowLength, len); - }else if(!d_vars.isInteger(v)){ - d_colCandidates.set(v, BoundCounts()); - } - } - } - - uint32_t maxCount = 0; - for(DenseMap::const_iterator i = d_rowIndices.begin(), i_end = d_rowIndices.end(); i != i_end; ++i){ - ArithVar v = *i; - - bool lbCap = d_vars.hasLowerBound(v) && !d_vars.hasUpperBound(v); - bool ubCap = !d_vars.hasLowerBound(v) && d_vars.hasUpperBound(v); - - if(lbCap || ubCap){ - ConstraintP b = lbCap ? d_vars.getLowerBoundConstraint(v) - : d_vars.getUpperBoundConstraint(v); - - if(!(b->getValue()).noninfinitesimalIsZero()){ continue; } - - Polynomial poly = Polynomial::parsePolynomial(d_vars.asNode(v)); - if(poly.size() != 2) { continue; } - - Polynomial::iterator j = poly.begin(); - Monomial first = *j; - ++j; - Monomial second = *j; - - bool firstIsPos = first.constantIsPositive(); - bool secondIsPos = second.constantIsPositive(); - - if(firstIsPos == secondIsPos){ continue; } - - Monomial pos = firstIsPos == lbCap ? first : second; - Monomial neg = firstIsPos != lbCap ? first : second; - // pos >= neg - VarList p = pos.getVarList(); - VarList n = neg.getVarList(); - if(d_vars.hasArithVar(p.getNode())){ - ArithVar ap = d_vars.asArithVar(p.getNode()); - if( d_colCandidates.isKey(ap)){ - BoundCounts bc = d_colCandidates.get(ap); - bc = BoundCounts(bc.lowerBoundCount(), bc.upperBoundCount()+1); - maxCount = std::max(maxCount, bc.upperBoundCount()); - d_colCandidates.set(ap, bc); - } - } - if(d_vars.hasArithVar(n.getNode())){ - ArithVar an = d_vars.asArithVar(n.getNode()); - if( d_colCandidates.isKey(an)){ - BoundCounts bc = d_colCandidates.get(an); - bc = BoundCounts(bc.lowerBoundCount()+1, bc.upperBoundCount()); - maxCount = std::max(maxCount, bc.lowerBoundCount()); - d_colCandidates.set(an, bc); - } - } - } - } - - // Attempt row - double avgRowLength = d_rowCandidates.size() >= 1 ? - ( sumRowLength / d_rowCandidates.size() ) : 0.0; - - // There is a large row among the candidates - bool guessARowCandidate = maxRowLength >= (10.0 * avgRowLength); - - double rowLengthReq = (maxRowLength * .9); - - if (guessARowCandidate) - { - for (ArithVar r : d_rowCandidates) - { - uint32_t len = d_rowCandidates[r]; - - int dir = guessDir(r); - if(len >= rowLengthReq){ - ret.push_back(ArithRatPair(r, Rational(dir))); - } - } - } - - // Attempt columns - bool guessAColCandidate = maxCount >= 4; - if (guessAColCandidate) - { - for (ArithVar c : d_colCandidates) - { - BoundCounts bc = d_colCandidates[c]; - - int dir = guessDir(c); - double ubScore = double(bc.upperBoundCount()) / maxCount; - double lbScore = double(bc.lowerBoundCount()) / maxCount; - if(ubScore >= .9 || lbScore >= .9){ - ret.push_back(ArithRatPair(c, Rational(c))); - } - } - } - - return ret; -} - -void ApproxGLPK::setOptCoeffs(const ArithRatPairVec& ref){ - DenseMap nbCoeffs; - - for(ArithRatPairVec::const_iterator i = ref.begin(), iend = ref.end(); i != iend; ++i){ - ArithVar v = (*i).first; - const Rational& q = (*i).second; - - if(d_vars.isAuxiliary(v)){ - // replace the variable by its definition and multiply by q - Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); - Polynomial pq = p * q; - - for(Polynomial::iterator j = pq.begin(), jend = pq.end(); j != jend; ++j){ - const Monomial& mono = *j; - const Constant& constant = mono.getConstant(); - const VarList& variable = mono.getVarList(); - - Node n = variable.getNode(); - - Assert(d_vars.hasArithVar(n)); - ArithVar av = d_vars.asArithVar(n); - int colIndex = d_colIndices[av]; - double coeff = constant.getValue().getDouble(); - if(!nbCoeffs.isKey(colIndex)){ - nbCoeffs.set(colIndex, 0.0); - } - nbCoeffs.set(colIndex, nbCoeffs[colIndex]+coeff); - } - }else{ - int colIndex = d_colIndices[v]; - double coeff = q.getDouble(); - if(!nbCoeffs.isKey(colIndex)){ - nbCoeffs.set(colIndex, 0.0); - } - nbCoeffs.set(colIndex, nbCoeffs[colIndex]+coeff); - } - } - for(DenseMap::const_iterator ci =nbCoeffs.begin(), ciend = nbCoeffs.end(); ci != ciend; ++ci){ - Index colIndex = *ci; - double coeff = nbCoeffs[colIndex]; - glp_set_obj_coef(d_inputProb, colIndex, coeff); - } -} - - -/* - * rough strategy: - * real relaxation - * try approximate real optimization of error function - * pivot in its basis - * update to its assignment - * check with FCSimplex - * check integer solution - * try approximate mixed integer problem - * stop at the first feasible point - * pivot in its basis - * update to its assignment - * check with FCSimplex - */ - -void ApproxGLPK::printGLPKStatus(int status, std::ostream& out){ - switch(status){ - case GLP_OPT: - out << "GLP_OPT" << endl; - break; - case GLP_FEAS: - out << "GLP_FEAS" << endl; - break; - case GLP_INFEAS: - out << "GLP_INFEAS" << endl; - break; - case GLP_NOFEAS: - out << "GLP_NOFEAS" << endl; - break; - case GLP_UNBND: - out << "GLP_UNBND" << endl; - break; - case GLP_UNDEF: - out << "GLP_UNDEF" << endl; - break; - default: - out << "Status unknown" << endl; - break; - } -} - -ApproxGLPK::~ApproxGLPK(){ - glp_delete_prob(d_inputProb); - glp_delete_prob(d_realProb); - glp_delete_prob(d_mipProb); - -} - -ApproximateSimplex::Solution ApproxGLPK::extractSolution(bool mip) const -{ - Assert(d_solvedRelaxation); - Assert(!mip || d_solvedMIP); - - ApproximateSimplex::Solution sol; - DenseSet& newBasis = sol.newBasis; - DenseMap& newValues = sol.newValues; - - glp_prob* prob = mip ? d_mipProb : d_realProb; - - for(ArithVariables::var_iterator i = d_vars.var_begin(), i_end = d_vars.var_end(); i != i_end; ++i){ - ArithVar vi = *i; - bool isAux = d_vars.isAuxiliary(vi); - int glpk_index = isAux ? d_rowIndices[vi] : d_colIndices[vi]; - - int status = isAux ? glp_get_row_stat(prob, glpk_index) - : glp_get_col_stat(prob, glpk_index); - - bool useDefaultAssignment = false; - - switch(status){ - case GLP_BS: - newBasis.add(vi); - useDefaultAssignment = true; - break; - case GLP_NL: - case GLP_NS: - if(!mip){ - newValues.set(vi, d_vars.getLowerBound(vi)); - }else{// intentionally fall through otherwise - useDefaultAssignment = true; - } - break; - case GLP_NU: - if(!mip){ - newValues.set(vi, d_vars.getUpperBound(vi)); - }else {// intentionally fall through otherwise - useDefaultAssignment = true; - } - break; - default: - { - useDefaultAssignment = true; - } - break; - } - - if(useDefaultAssignment){ - - double newAssign; - if(mip){ - newAssign = (isAux ? glp_mip_row_val(prob, glpk_index) - : glp_mip_col_val(prob, glpk_index)); - }else{ - newAssign = (isAux ? glp_get_row_prim(prob, glpk_index) - : glp_get_col_prim(prob, glpk_index)); - } - const DeltaRational& oldAssign = d_vars.getAssignment(vi); - - if (d_vars.hasLowerBound(vi) - && roughlyEqual(newAssign, - d_vars.getLowerBound(vi).approx(SMALL_FIXED_DELTA))) - { - - newValues.set(vi, d_vars.getLowerBound(vi)); - } - else if (d_vars.hasUpperBound(vi) - && roughlyEqual( - newAssign, - d_vars.getUpperBound(vi).approx(SMALL_FIXED_DELTA))) - { - newValues.set(vi, d_vars.getUpperBound(vi)); - } - else - { - double rounded = round(newAssign); - if (roughlyEqual(newAssign, rounded)) - { - newAssign = rounded; - } - - DeltaRational proposal; - if (std::optional maybe_new = estimateWithCFE(newAssign)) - { - proposal = *maybe_new; - } - else - { - // failed to estimate the old value. defaulting to the current. - proposal = d_vars.getAssignment(vi); - } - - if (roughlyEqual(newAssign, oldAssign.approx(SMALL_FIXED_DELTA))) - { - proposal = d_vars.getAssignment(vi); - } - - if (d_vars.strictlyLessThanLowerBound(vi, proposal)) - { - proposal = d_vars.getLowerBound(vi); - } - else if (d_vars.strictlyGreaterThanUpperBound(vi, proposal)) - { - proposal = d_vars.getUpperBound(vi); - } - newValues.set(vi, proposal); - } - } - } - return sol; -} - -double ApproxGLPK::sumInfeasibilities(glp_prob* prob, bool mip) const{ - /* compute the sum of dual infeasibilities */ - double infeas = 0.0; - - for(ArithVariables::var_iterator i = d_vars.var_begin(), i_end = d_vars.var_end(); i != i_end; ++i){ - ArithVar vi = *i; - bool isAux = d_vars.isAuxiliary(vi); - int glpk_index = isAux ? d_rowIndices[vi] : d_colIndices[vi]; - - double newAssign; - if(mip){ - newAssign = (isAux ? glp_mip_row_val(prob, glpk_index) - : glp_mip_col_val(prob, glpk_index)); - }else{ - newAssign = (isAux ? glp_get_row_prim(prob, glpk_index) - : glp_get_col_prim(prob, glpk_index)); - } - - - double ub = isAux ? - glp_get_row_ub(prob, glpk_index) : glp_get_col_ub(prob, glpk_index); - - double lb = isAux ? - glp_get_row_lb(prob, glpk_index) : glp_get_col_lb(prob, glpk_index); - - if(ub != +DBL_MAX){ - if(newAssign > ub){ - double ubinf = newAssign - ub; - infeas += ubinf; - Trace("approx::soi") << "ub inf" << vi << " " << ubinf << " " << infeas << endl; - } - } - if(lb != -DBL_MAX){ - if(newAssign < lb){ - double lbinf = lb - newAssign; - infeas += lbinf; - - Trace("approx::soi") << "lb inf" << vi << " " << lbinf << " " << infeas << endl; - } - } - } - return infeas; -} - -LinResult ApproxGLPK::solveRelaxation(){ - Assert(!d_solvedRelaxation); - - glp_smcp parm; - glp_init_smcp(&parm); - parm.presolve = GLP_OFF; - parm.meth = GLP_PRIMAL; - parm.pricing = GLP_PT_PSE; - parm.it_lim = d_pivotLimit; - parm.msg_lev = GLP_MSG_OFF; - - glp_erase_prob(d_realProb); - glp_copy_prob(d_realProb, d_inputProb, GLP_OFF); - - int res = glp_simplex(d_realProb, &parm); - switch(res){ - case 0: - { - int status = glp_get_status(d_realProb); - int iterationcount = glp_get_it_cnt(d_realProb); - switch(status){ - case GLP_OPT: - case GLP_FEAS: - case GLP_UNBND: - d_solvedRelaxation = true; - return LinFeasible; - case GLP_INFEAS: - case GLP_NOFEAS: - d_solvedRelaxation = true; - return LinInfeasible; - default: - { - if(iterationcount >= d_pivotLimit){ - return LinExhausted; - } - return LinUnknown; - } - } - } - default: - return LinUnknown; - } -} - - -struct MirInfo : public CutInfo { - - /** a sum of input rows. */ - PrimitiveVec row_sum; - - /* the delta used */ - double delta; - - /* all of these are length vars == N+M*/ - int nvars; - char* cset; - char* subst; - int* vlbRows; - int* vubRows; - MirInfo(int execOrd, int ord) - : CutInfo(MirCutKlass, execOrd, ord) - , nvars(0) - , cset(NULL) - , subst(NULL) - , vlbRows(NULL) - , vubRows(NULL) - {} - - ~MirInfo(){ - clearSets(); - } - void clearSets(){ - if(cset != NULL){ - delete[] cset; - delete[] subst; - delete[] vlbRows; - delete[] vubRows; - cset = NULL; - nvars = 0; - } - } - void initSet(){ - Assert(d_N >= 0); - Assert(d_mAtCreation >= 0); - clearSets(); - - int vars = 1 + d_N + d_mAtCreation; - - cset = new char[1+vars]; - subst = new char[1+vars]; - vlbRows = new int[1+vars]; - vubRows = new int[1+vars]; - } -}; - -struct GmiInfo : public CutInfo { - int basic; - PrimitiveVec tab_row; - int* tab_statuses; - /* has the length tab_row.length */ - - GmiInfo(int execOrd, int ord) - : CutInfo(GmiCutKlass, execOrd, ord) - , basic(-1) - , tab_row() - , tab_statuses(NULL) - { - Assert(!initialized_tab()); - } - - ~GmiInfo(){ - if(initialized_tab()){ - clear_tab(); - } - } - - bool initialized_tab() const { - return tab_statuses != NULL; - } - - void init_tab(int N){ - if(initialized_tab()){ - clear_tab(); - } - tab_row.setup(N); - tab_statuses = new int[1+N]; - } - - void clear_tab() { - delete[] tab_statuses; - tab_statuses = NULL; - tab_row.clear(); - basic = -1; - } -}; - - - -static void loadCut(glp_tree *tree, CutInfo* cut){ - int ord, cut_len, cut_klass; - int N, M; - int* cut_inds; - double* cut_coeffs; - int glpk_cut_type; - double cut_rhs; - glp_prob* lp; - - lp = glp_ios_get_prob(tree); - ord = cut->poolOrdinal(); - - N = glp_get_num_cols(lp); - M = glp_get_num_rows(lp); - - cut->setDimensions(N, M); - - - - // Get the cut - cut_len = glp_ios_get_cut(tree, ord, NULL, NULL, &cut_klass, NULL, NULL); - Assert(fromGlpkClass(cut_klass) == cut->getKlass()); - - PrimitiveVec& cut_vec = cut->getCutVector(); - cut_vec.setup(cut_len); - cut_inds = cut_vec.inds; - cut_coeffs = cut_vec.coeffs; - - cut_vec.len = glp_ios_get_cut(tree, ord, cut_inds, cut_coeffs, &cut_klass, &glpk_cut_type, &cut_rhs); - Assert(fromGlpkClass(cut_klass) == cut->getKlass()); - Assert(cut_vec.len == cut_len); - - cut->setRhs(cut_rhs); - - cut->setKind( glpk_type_to_kind(glpk_cut_type) ); -} - - -static MirInfo* mirCut(glp_tree *tree, int exec_ord, int cut_ord){ - Trace("approx::mirCut") << "mirCut()" << exec_ord << endl; - - MirInfo* mir; - mir = new MirInfo(exec_ord, cut_ord); - loadCut(tree, mir); - mir->initSet(); - - - int nrows = glp_ios_cut_get_aux_nrows(tree, cut_ord); - - PrimitiveVec& row_sum = mir->row_sum; - row_sum.setup(nrows); - glp_ios_cut_get_aux_rows(tree, cut_ord, row_sum.inds, row_sum.coeffs); - - glp_ios_cut_get_mir_cset(tree, cut_ord, mir->cset); - mir->delta = glp_ios_cut_get_mir_delta(tree, cut_ord); - glp_ios_cut_get_mir_subst(tree, cut_ord, mir->subst); - glp_ios_cut_get_mir_virtual_rows(tree, cut_ord, mir->vlbRows, mir->vubRows); - - if(TraceIsOn("approx::mirCut")){ - Trace("approx::mirCut") << "mir_id: " << exec_ord << endl; - row_sum.print(Trace("approx::mirCut")); - } - - return mir; -} - -static GmiInfo* gmiCut(glp_tree *tree, int exec_ord, int cut_ord){ - Trace("approx::gmiCut") << "gmiCut()" << exec_ord << endl; - - int gmi_var; - int write_pos; - int read_pos; - int stat; - int ind; - int i; - - GmiInfo* gmi; - glp_prob* lp; - - gmi = new GmiInfo(exec_ord, cut_ord); - loadCut(tree, gmi); - - lp = glp_ios_get_prob(tree); - - int N = gmi->getN(); - int M = gmi->getMAtCreation(); - - // Get the tableau row - int nrows CVC5_UNUSED = glp_ios_cut_get_aux_nrows(tree, gmi->poolOrdinal()); - Assert(nrows == 1); - int rows[1+1]; - glp_ios_cut_get_aux_rows(tree, gmi->poolOrdinal(), rows, NULL); - gmi_var = rows[1]; - - gmi->init_tab(N); - gmi->basic = M+gmi_var; - - Trace("approx::gmiCut") - << gmi <<" " << gmi->basic << " " - << cut_ord<<" " << M <<" " << gmi_var << endl; - - PrimitiveVec& tab_row = gmi->tab_row; - Trace("approx::gmiCut") << "Is N sufficient here?" << endl; - tab_row.len = glp_eval_tab_row(lp, gmi->basic, tab_row.inds, tab_row.coeffs); - - Trace("approx::gmiCut") << "gmi_var " << gmi_var << endl; - - Trace("approx::gmiCut") << "tab_pos " << tab_row.len << endl; - write_pos = 1; - for(read_pos = 1; read_pos <= tab_row.len; ++read_pos){ - if (fabs(tab_row.coeffs[read_pos]) < 1e-10){ - }else{ - tab_row.coeffs[write_pos] = tab_row.coeffs[read_pos]; - tab_row.inds[write_pos] = tab_row.inds[read_pos]; - ++write_pos; - } - } - tab_row.len = write_pos-1; - Trace("approx::gmiCut") << "write_pos " << write_pos << endl; - Assert(tab_row.len > 0); - - for(i = 1; i <= tab_row.len; ++i){ - ind = tab_row.inds[i]; - Trace("approx::gmiCut") << "ind " << i << " " << ind << endl; - stat = (ind <= M) ? - glp_get_row_stat(lp, ind) : glp_get_col_stat(lp, ind - M); - - Trace("approx::gmiCut") << "ind " << i << " " << ind << " stat " << stat << endl; - switch (stat){ - case GLP_NL: - case GLP_NU: - case GLP_NS: - gmi->tab_statuses[i] = stat; - break; - case GLP_NF: - default: - Unreachable(); - } - } - - if(TraceIsOn("approx::gmiCut")){ - gmi->print(Trace("approx::gmiCut")); - } - return gmi; -} - -static BranchCutInfo* branchCut(glp_tree *tree, int exec_ord, int br_var, double br_val, bool down_bad){ - //(tree, br_var, br_val, dn < 0); - double rhs; - Kind k; - if(down_bad){ - // down branch is infeasible - // x <= floor(v) is infeasible - // - so x >= ceiling(v) is implied - k = kind::GEQ; - rhs = std::ceil(br_val); - }else{ - // up branch is infeasible - // x >= ceiling(v) is infeasible - // - so x <= floor(v) is implied - k = kind::LEQ; - rhs = std::floor(br_val); - } - BranchCutInfo* br_cut = new BranchCutInfo(exec_ord, br_var, k, rhs); - return br_cut; -} - -static void glpkCallback(glp_tree *tree, void *info){ - AuxInfo* aux = (AuxInfo*)(info); - TreeLog& tl = *(aux->tl); - - int exec = tl.getExecutionOrd(); - int glpk_node_p = -1; - int node_ord = -1; - - if(tl.isActivelyLogging()){ - switch(glp_ios_reason(tree)){ - case GLP_LI_DELROW: - { - glpk_node_p = glp_ios_curr_node(tree); - node_ord = glp_ios_node_ord(tree, glpk_node_p); - - int nrows = glp_ios_rows_deleted(tree, NULL); - int* num = new int[1+nrows]; - glp_ios_rows_deleted(tree, num); - - NodeLog& node = tl.getNode(node_ord); - - RowsDeleted* rd = new RowsDeleted(exec, nrows, num); - - node.addCut(rd); - delete[] num; - } - break; - case GLP_ICUTADDED: - { - int cut_ord = glp_ios_pool_size(tree); - glpk_node_p = glp_ios_curr_node(tree); - node_ord = glp_ios_node_ord(tree, glpk_node_p); - Assert(cut_ord > 0); - Trace("approx") << "curr node " << glpk_node_p - << " cut ordinal " << cut_ord - << " node depth " << glp_ios_node_level(tree, glpk_node_p) - << endl; - int klass; - glp_ios_get_cut(tree, cut_ord, NULL, NULL, &klass, NULL, NULL); - - NodeLog& node = tl.getNode(node_ord); - switch(klass){ - case GLP_RF_GMI: - { - GmiInfo* gmi = gmiCut(tree, exec, cut_ord); - node.addCut(gmi); - } - break; - case GLP_RF_MIR: - { - MirInfo* mir = mirCut(tree, exec, cut_ord); - node.addCut(mir); - } - break; - case GLP_RF_COV: - Trace("approx") << "GLP_RF_COV" << endl; - break; - case GLP_RF_CLQ: - Trace("approx") << "GLP_RF_CLQ" << endl; - break; - default: - break; - } - } - break; - case GLP_ICUTSELECT: - { - glpk_node_p = glp_ios_curr_node(tree); - node_ord = glp_ios_node_ord(tree, glpk_node_p); - int cuts = glp_ios_pool_size(tree); - int* ords = new int[1+cuts]; - int* rows = new int[1+cuts]; - int N = glp_ios_selected_cuts(tree, ords, rows); - - NodeLog& nl = tl.getNode(node_ord); - Trace("approx") << glpk_node_p << " " << node_ord << " " << cuts << " " << N << std::endl; - for(int i = 1; i <= N; ++i){ - Trace("approx") << "adding to " << node_ord <<" @ i= " << i - << " ords[i] = " << ords[i] - << " rows[i] = " << rows[i] << endl; - nl.addSelected(ords[i], rows[i]); - } - delete[] ords; - delete[] rows; - nl.applySelected(); - } - break; - case GLP_LI_BRANCH: - { - // a branch was just made - int br_var; - int p, dn, up; - int p_ord, dn_ord, up_ord; - double br_val; - br_var = glp_ios_branch_log(tree, &br_val, &p, &dn, &up); - p_ord = glp_ios_node_ord(tree, p); - - dn_ord = (dn >= 0) ? glp_ios_node_ord(tree, dn) : -1; - up_ord = (up >= 0) ? glp_ios_node_ord(tree, up) : -1; - - Trace("approx::") << "branch: "<< br_var << " " << br_val << " tree " << p << " " << dn << " " << up << endl; - Trace("approx::") << "\t " << p_ord << " " << dn_ord << " " << up_ord << endl; - if(dn < 0 && up < 0){ - Trace("approx::") << "branch close " << exec << endl; - NodeLog& node = tl.getNode(p_ord); - BranchCutInfo* cut_br = branchCut(tree, exec, br_var, br_val, dn < 0); - node.addCut(cut_br); - tl.close(p_ord); - }else if(dn < 0 || up < 0){ - Trace("approx::") << "branch cut" << exec << endl; - NodeLog& node = tl.getNode(p_ord); - BranchCutInfo* cut_br = branchCut(tree, exec, br_var, br_val, dn < 0); - node.addCut(cut_br); - }else{ - Trace("approx::") << "normal branch" << endl; - tl.branch(p_ord, br_var, br_val, dn_ord, up_ord); - } - } - break; - case GLP_LI_CLOSE: - { - glpk_node_p = glp_ios_curr_node(tree); - node_ord = glp_ios_node_ord(tree, glpk_node_p); - Trace("approx::") << "close " << glpk_node_p << endl; - tl.close(node_ord); - } - break; - default: - break; - } - } - - switch(glp_ios_reason(tree)){ - case GLP_IBINGO: - Trace("approx::") << "bingo" << endl; - aux->term = MipBingo; - glp_ios_terminate(tree); - break; - case GLP_ICUTADDED: - { - tl.addCut(); - } - break; - case GLP_LI_BRANCH: - { - int p, dn, up; - int br_var = glp_ios_branch_log(tree, NULL, &p, &dn, &up); - - if(br_var >= 0){ - unsigned v = br_var; - tl.logBranch(v); - int depth = glp_ios_node_level(tree, p); - unsigned ubl = (aux->branchLimit) >= 0 ? ((unsigned)(aux->branchLimit)) : 0u; - if(tl.numBranches(v) >= ubl || depth >= (aux->branchDepth)){ - aux->term = BranchesExhausted; - glp_ios_terminate(tree); - } - } - } - break; - case GLP_LI_CLOSE: - break; - default: - { - glp_prob* prob = glp_ios_get_prob(tree); - int iterationcount = glp_get_it_cnt(prob); - if(exec > (aux->pivotLimit)){ - aux->term = ExecExhausted; - glp_ios_terminate(tree); - }else if(iterationcount > (aux->pivotLimit)){ - aux->term = PivotsExhauasted; - glp_ios_terminate(tree); - } - } - break; - } -} - -std::vector ApproxGLPK::getValidCuts(const NodeLog& con) -{ - std::vector proven; - int nid = con.getNodeId(); - for(NodeLog::const_iterator j = con.begin(), jend=con.end(); j!=jend; ++j){ - CutInfo* cut = *j; - - if(cut->getKlass() != RowsDeletedKlass){ - if(!cut->reconstructed()){ - Assert(!cut->reconstructed()); - tryCut(nid, *cut); - } - } - - if(cut->proven()){ - proven.push_back(cut); - } - } - return proven; -} - -ArithVar ApproxGLPK::getBranchVar(const NodeLog& con) const{ - int br_var = con.branchVariable(); - return getArithVarFromStructural(br_var); -} - - -MipResult ApproxGLPK::solveMIP(bool activelyLog){ - Assert(d_solvedRelaxation); - // Explicitly disable presolving - // We need the basis thus the presolver must be off! - // This is default, but this is just being cautious. - AuxInfo aux; - aux.pivotLimit = d_pivotLimit; - aux.branchLimit = d_branchLimit; - aux.branchDepth = d_maxDepth; - aux.tl = &d_log; - aux.term = MipUnknown; - - d_log.reset(d_rootRowIds); - if(activelyLog){ - d_log.makeActive(); - }else{ - d_log.makeInactive(); - } - - glp_iocp parm; - glp_init_iocp(&parm); - parm.presolve = GLP_OFF; - parm.pp_tech = GLP_PP_NONE; - parm.fp_heur = GLP_ON; - parm.gmi_cuts = GLP_ON; - parm.mir_cuts = GLP_ON; - parm.cov_cuts = GLP_ON; - parm.cb_func = glpkCallback; - parm.cb_info = &aux; - parm.msg_lev = GLP_MSG_OFF; - - glp_erase_prob(d_mipProb); - glp_copy_prob(d_mipProb, d_realProb, GLP_OFF); - - int res = glp_intopt(d_mipProb, &parm); - - Trace("approx::solveMIP") << "res "<& xs, const DenseMap& cs, bool* anyinf){ - if(anyinf != NULL){ - *anyinf = false; - } - - DeltaRational beta(0); - DenseMap::const_iterator iter, end; - iter = xs.begin(); - end = xs.end(); - - Trace("approx::sumConstraints") << "sumConstraints"; - for(; iter != end; ++iter){ - ArithVar x = *iter; - const Rational& psi = xs[x]; - ConstraintP c = cs[x]; - Assert(c != NullConstraint); - - const DeltaRational& bound = c->getValue(); - beta += bound * psi; - Trace("approx::sumConstraints") << " +("<& v){ - // Remove Slack variables - vector zeroes; - DenseMap::const_iterator i, iend; - for(i = v.begin(), iend = v.end(); i != iend; ++i){ - ArithVar x = *i; - if(v[x].isZero()){ - zeroes.push_back(x); - } - } - - vector::const_iterator j, jend; - for(j = zeroes.begin(), jend = zeroes.end(); j != jend; ++j){ - ArithVar x = *j; - v.remove(x); - } -} -void removeZeroes(DenseVector& v){ - removeZeroes(v.lhs); -} - -void removeAuxillaryVariables(const ArithVariables& vars, DenseMap& vec){ - // Remove auxillary variables - vector aux; - DenseMap::const_iterator vec_iter, vec_end; - vec_iter = vec.begin(), vec_end = vec.end(); - for(; vec_iter != vec_end; ++vec_iter){ - ArithVar x = *vec_iter; - if(vars.isAuxiliary(x)){ - aux.push_back(x); - } - } - - vector::const_iterator aux_iter, aux_end; - aux_iter = aux.begin(), aux_end = aux.end(); - for(; aux_iter != aux_end; ++aux_iter){ - ArithVar s = *aux_iter; - Rational& s_coeff = vec.get(s); - Assert(vars.isAuxiliary(s)); - Assert(vars.hasNode(s)); - Node sAsNode = vars.asNode(s); - Polynomial p = Polynomial::parsePolynomial(sAsNode); - for(Polynomial::iterator j = p.begin(), p_end=p.end(); j != p_end; ++j){ - Monomial m = *j; - const Rational& ns_coeff = m.getConstant().getValue(); - Node vl = m.getVarList().getNode(); - ArithVar ns = vars.asArithVar(vl); - Rational prod = s_coeff * ns_coeff; - if(vec.isKey(ns)){ - vec.get(ns) += prod; - }else{ - vec.set(ns, prod); - } - } - s_coeff = Rational(0); // subtract s_coeff * s from vec - } - removeZeroes(vec); -} - -ArithVar ApproxGLPK::_getArithVar(int nid, int M, int ind) const{ - if(ind <= 0){ - return ARITHVAR_SENTINEL; - }else if(ind <= M){ - return getArithVarFromRow(nid, ind); - }else{ - return getArithVarFromStructural(ind - M); - } -} - - -bool ApproxGLPK::guessIsConstructable(const DenseMap& guess) const { - // basic variable - // sum g[i] * x_i - DenseMap g = guess; - removeAuxillaryVariables(d_vars, g); - - if(TraceIsOn("guessIsConstructable")){ - if(!g.empty()){ - Trace("approx::guessIsConstructable") << "guessIsConstructable failed " << g.size() << endl; - DenseVector::print(Trace("approx::guessIsConstructable"), g); - Trace("approx::guessIsConstructable") << endl; - } - } - return g.empty(); -} - -bool ApproxGLPK::loadToBound(int nid, int M, int len, int* inds, int* statuses, DenseMap& toBound) const{ - for(int i = 1; i <= len; ++i){ - int status = statuses[i]; - int ind = inds[i]; - ArithVar v = _getArithVar(nid, M, ind); - ConstraintP c = NullConstraint; - if(v == ARITHVAR_SENTINEL){ return true; } - - switch(status){ - case GLP_NL: - c = d_vars.getLowerBoundConstraint(v); - break; - case GLP_NU: - case GLP_NS: // upper bound sufficies for fixed variables - c = d_vars.getUpperBoundConstraint(v); - break; - case GLP_NF: - default: - return true; - } - if(c == NullConstraint){ - Trace("approx::") << "couldn't find " << v << " @ " << nid << endl; - return true; - } - Assert(c != NullConstraint); - toBound.set(v, c); - } - return false; -} - -bool ApproxGLPK::checkCutOnPad(int nid, const CutInfo& cut) const{ - - Trace("approx::checkCutOnPad") << "checkCutOnPad(" << nid <<", " << cut.getId() <<")"<& constructedLhs = d_pad.d_cut.lhs; - const Rational& constructedRhs = d_pad.d_cut.rhs; - std::unordered_set visited; - - if(constructedLhs.empty()){ - Trace("approx::checkCutOnPad") << "its empty?" < br_cut_rhs = Rational::fromDouble(br_cut.getRhs()); - if (!br_cut_rhs) - { - return true; - } - - rhs = estimateWithCFE(*br_cut_rhs, Integer(1)); - d_pad.d_failure = !rhs.isIntegral(); - if(d_pad.d_failure) { return true; } - - d_pad.d_failure = checkCutOnPad(nid, br_cut); - if(d_pad.d_failure){ return true; } - - return false; -} - -bool ApproxGLPK::attemptGmi(int nid, const GmiInfo& gmi){ - d_pad.clear(); - - d_pad.d_cutKind = kind::GEQ; - - int M = gmi.getMAtCreation(); - ArithVar b = _getArithVar(nid, M, gmi.basic); - d_pad.d_failure = (b == ARITHVAR_SENTINEL); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = !d_vars.isIntegerInput(b); - if(d_pad.d_failure){ return true; } - - d_pad.d_basic = b; - - - const PrimitiveVec& tab = gmi.tab_row; - d_pad.d_failure = attemptConstructTableRow(nid, M, tab); - if(d_pad.d_failure){ return true; } - - int* statuses = gmi.tab_statuses; - DenseMap& toBound = d_pad.d_toBound; - d_pad.d_failure = loadToBound(nid, M, tab.len, tab.inds, statuses, toBound); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = constructGmiCut(); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = checkCutOnPad(nid, gmi); - if(d_pad.d_failure){ return true; } - - return false; -} - -bool ApproxGLPK::applyCMIRRule(int nid, const MirInfo& mir){ - - const DenseMap& compRanges = d_pad.d_compRanges; - - DenseMap& alpha = d_pad.d_alpha.lhs; - Rational& b = d_pad.d_alpha.rhs; - - std::optional delta = estimateWithCFE(mir.delta); - if (!delta) - { - return true; - } - - d_pad.d_failure = (delta->sgn() <= 0); - if(d_pad.d_failure){ return true; } - - Trace("approx::mir") << "applyCMIRRule() " << delta << " " << mir.delta << endl; - - DenseMap::const_iterator iter, iend; - iter = alpha.begin(), iend = alpha.end(); - for(; iter != iend; ++iter){ - ArithVar v = *iter; - const Rational& curr = alpha[v]; - Rational next = curr / *delta; - if(compRanges.isKey(v)){ - b -= curr * compRanges[v]; - alpha.set(v, - next); - }else{ - alpha.set(v, next); - } - } - b = b / *delta; - - Rational roundB = (b + Rational(1,2)).floor(); - d_pad.d_failure = (b - roundB).abs() < Rational(1,90); - // intensionally more generous than glpk here - if(d_pad.d_failure){ return true; } - - Rational one(1); - Rational fb = b.floor_frac(); - Rational one_sub_fb = one - fb; - Rational gamma = (one / one_sub_fb); - - DenseMap& cut = d_pad.d_cut.lhs; - Rational& beta = d_pad.d_cut.rhs; - - iter = alpha.begin(), iend = alpha.end(); - for(; iter != iend; ++iter){ - ArithVar v = *iter; - const Rational& a_j = alpha[v]; - if(d_vars.isIntegerInput(v)){ - Rational floor_aj = a_j.floor(); - Rational frac_aj = a_j.floor_frac(); - if(frac_aj <= fb){ - cut.set(v, floor_aj); - }else{ - Rational tmp = ((frac_aj - fb) / one_sub_fb); - cut.set(v, floor_aj + tmp); - } - }else{ - cut.set(v, a_j * gamma); - } - } - beta = b.floor(); - - iter = cut.begin(), iend = cut.end(); - for(; iter != iend; ++iter){ - ArithVar v = *iter; - if(compRanges.isKey(v)){ - Rational neg = - cut[v]; - beta += neg * compRanges[v]; - cut.set(v, neg); - } - } - - return false; -} - -bool ApproxGLPK::attemptMir(int nid, const MirInfo& mir){ - d_pad.clear(); - - d_pad.d_cutKind = kind::LEQ; - - // virtual bounds must be done before slacks - d_pad.d_failure = loadVirtualBoundsIntoPad(nid, mir); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = loadSlacksIntoPad(nid, mir); - if(d_pad.d_failure){ return true; } - - - d_pad.d_failure = loadRowSumIntoAgg(nid, mir.getMAtCreation(), mir.row_sum); - if(d_pad.d_failure){ return true; } - - removeFixed(d_vars, d_pad.d_agg, d_pad.d_explanation); - - d_pad.d_failure = buildModifiedRow(nid, mir); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = constructMixedKnapsack(); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = makeRangeForComplemented(nid, mir); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = applyCMIRRule(nid, mir); - if(d_pad.d_failure){ return true; } - - d_pad.d_failure = replaceSlacksOnCuts(); - if(d_pad.d_failure){ return true; } - - removeAuxillaryVariables(d_vars, d_pad.d_cut.lhs); - - d_pad.d_failure = checkCutOnPad(nid, mir); - if(d_pad.d_failure){ return true; } - - return false; - //return makeCutNodes(nid, mir); -} - -/** Returns true on failure. */ -bool ApproxGLPK::loadVB(int nid, int M, int j, int ri, bool wantUb, VirtualBound& tmp){ - if(ri <= 0) { return true; } - - Trace("glpk::loadVB") << "loadVB()" << endl; - - ArithVar rowVar = _getArithVar(nid, M, ri); - ArithVar contVar = _getArithVar(nid, M, j); - if(rowVar == ARITHVAR_SENTINEL){ - Trace("glpk::loadVB") << "loadVB()" - << " rowVar is ARITHVAR_SENTINEL " << rowVar << endl; - return true; - } - if(contVar == ARITHVAR_SENTINEL){ - Trace("glpk::loadVB") << "loadVB()" - << " contVar is ARITHVAR_SENTINEL " << contVar - << endl; - return true; } - - if(!d_vars.isAuxiliary(rowVar)){ - Trace("glpk::loadVB") << "loadVB()" - << " rowVar is not auxilliary " << rowVar << endl; - return true; - } - // is integer is correct here - if(d_vars.isInteger(contVar)){ - Trace("glpk::loadVB") << "loadVB()" - << " contVar is integer " << contVar << endl; - return true; - } - - ConstraintP lb = d_vars.getLowerBoundConstraint(rowVar); - ConstraintP ub = d_vars.getUpperBoundConstraint(rowVar); - - if(lb != NullConstraint && ub != NullConstraint){ - Trace("glpk::loadVB") << "loadVB()" - << " lb and ub are both NULL " << lb << " " << ub - << endl; - return true; - } - - ConstraintP rcon = lb == NullConstraint ? ub : lb; - if(rcon == NullConstraint) { - Trace("glpk::loadVB") << "loadVB()" - << " rcon is NULL " << rcon << endl; - return true; - } - - if(!rcon->getValue().isZero()){ - Trace("glpk::loadVB") << "loadVB()" - << " rcon value is not 0 " << rcon->getValue() - << endl; - return true; - } - - if(!d_vars.hasNode(rowVar)){ - Trace("glpk::loadVB") << "loadVB()" - << " does not have node " << rowVar << endl; - return true; - } - - Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(rowVar)); - if (p.size() != 2) - { - Trace("glpk::loadVB") << "loadVB()" - << " polynomial is not binary: " << p.getNode() - << endl; - return true; - } - - Monomial first = p.getHead(), second = p.getTail().getHead(); - Rational c1 = first.getConstant().getValue(); - Rational c2 = second.getConstant().getValue(); - Node nx1 = first.getVarList().getNode(); - Node nx2 = second.getVarList().getNode(); - - if(!d_vars.hasArithVar(nx1)) { - Trace("glpk::loadVB") << "loadVB()" - << " does not have a variable for nx1: " << nx1 - << endl; - return true; - } - if(!d_vars.hasArithVar(nx2)) { - Trace("glpk::loadVB") << "loadVB()" - << " does not have a variable for nx2 " << nx2 - << endl; - return true; - } - ArithVar x1 = d_vars.asArithVar(nx1), x2 = d_vars.asArithVar(nx2); - - Assert(x1 != x2); - Assert(!c1.isZero()); - Assert(!c2.isZero()); - - Trace("glpk::loadVB") - << " lb " << lb - << " ub " << ub - << " rcon " << rcon - << " x1 " << x1 - << " x2 " << x2 - << " c1 " << c1 - << " c2 " << c2 << endl; - - ArithVar iv = (x1 == contVar) ? x2 : x1; - Rational& cc = (x1 == contVar) ? c1 : c2; - Rational& ic = (x1 == contVar) ? c2 : c1; - - Trace("glpk::loadVB") - << " cv " << contVar - << " cc " << cc - << " iv " << iv - << " c2 " << ic << endl; - - if(!d_vars.isIntegerInput(iv)){ - Trace("glpk::loadVB") << "loadVB()" - << " iv is not an integer input variable " << iv - << endl; - return true; - } - // cc * cv + ic * iv <= 0 or - // cc * cv + ic * iv <= 0 - - if(rcon == ub){ // multiply by -1 - cc = -cc; ic = - ic; - } - Trace("glpk::loadVB") << " cv " << contVar - << " cc " << cc - << " iv " << iv - << " c2 " << ic << endl; - - // cc * cv + ic * iv >= 0 - // cc * cv >= -ic * iv - // if cc < 0: - // cv <= -ic/cc * iv - // elif cc > 0: - // cv >= -ic/cc * iv - Assert(!cc.isZero()); - Rational d = -ic/cc; - Trace("glpk::loadVB") << d << " " << cc.sgn() << endl; - bool nowUb = cc.sgn() < 0; - if(wantUb != nowUb) { - Trace("glpk::loadVB") << "loadVB()" - << " wantUb is not nowUb " << wantUb << " " << nowUb - << endl; - - return true; - } - - Kind rel = wantUb ? kind::LEQ : kind::GEQ; - - tmp = VirtualBound(contVar, rel, d, iv, rcon); - Trace("glpk::loadVB") << "loadVB()" - << " was successful" << endl; - return false; -} - -bool ApproxGLPK::loadVirtualBoundsIntoPad(int nid, const MirInfo& mir){ - Assert(mir.vlbRows != NULL); - Assert(mir.vubRows != NULL); - - int N = mir.getN(); - int M = mir.getMAtCreation(); - - // Load the virtual bounds first - VirtualBound tmp; - for(int j=1; j <= N+M; ++j){ - if(!loadVB(nid, M, j, mir.vlbRows[j], false, tmp)){ - if(d_pad.d_vlb.isKey(tmp.x)){ return true; } - d_pad.d_vlb.set(tmp.x, tmp); - }else if(mir.vlbRows[j] > 0){ - Trace("approx::mir") << "expected vlb to work" << endl; - } - if(!loadVB(nid, M, j, mir.vubRows[j], true, tmp)){ - if(d_pad.d_vub.isKey(tmp.x)){ return true; } - d_pad.d_vub.set(tmp.x, tmp); - }else if(mir.vubRows[j] > 0){ - Trace("approx::mir") << "expected vub to work" << endl; - } - } - return false; -} - -bool ApproxGLPK::loadSlacksIntoPad(int nid, const MirInfo& mir){ - Assert(mir.vlbRows != NULL); - Assert(mir.vubRows != NULL); - - int N = mir.getN(); - int M = mir.getMAtCreation(); - - bool useVB; - // Load the virtual bounds first - SlackReplace rep; - bool lb; - ConstraintP b; - Trace("approx::mir") << "loadSlacksIntoPad(): N="< 0) : (mir.vubRows[j] > 0); - if(useVB){ - if(lb ? d_pad.d_vlb.isKey(v) : d_pad.d_vub.isKey(v)){ - rep = lb ? SlackVLB : SlackVUB; - } - }else{ - b = lb ? d_vars.getLowerBoundConstraint(v) - : d_vars.getUpperBoundConstraint(v); - if(b != NullConstraint){ - if(b->getValue().infinitesimalIsZero()){ - rep = lb ? SlackLB : SlackUB; - } - } - } - - Trace("approx::mir") << " for: " << j << ", " << v; - Trace("approx::mir") << " " << ((rep != SlackUndef) ? "succ" : "fail") << " "; - Trace("approx::mir") << sub << " " << rep << " " << mir.vlbRows[j] << " " << mir.vubRows[j] - << endl; - if(rep != SlackUndef){ - d_pad.d_slacks.set(v,rep); - } - break; - case '?': - continue; - default: - Trace("approx::mir") << " for: " << j << " got subst " << (int)sub << endl; - continue; - } - } - return false; -} - -bool ApproxGLPK::replaceSlacksOnCuts(){ - vector virtualVars; - - DenseMap& cut = d_pad.d_cut.lhs; - Rational& cutRhs = d_pad.d_cut.rhs; - - DenseMap::const_iterator iter, iend; - iter = cut.begin(), iend = cut.end(); - for(; iter != iend; ++iter){ - ArithVar x = *iter; - SlackReplace rep = d_pad.d_slacks[x]; - if(d_vars.isIntegerInput(x)){ - Assert(rep == SlackLB || rep == SlackUB); - Rational& a = cut.get(x); - - const DeltaRational& bound = (rep == SlackLB) ? - d_vars.getLowerBound(x) : d_vars.getUpperBound(x); - Assert(bound.infinitesimalIsZero()); - Rational prod = a * bound.getNoninfinitesimalPart(); - if(rep == SlackLB){ - cutRhs += prod; - }else{ - cutRhs -= prod; - a = -a; - } - }else if(rep == SlackVLB){ - virtualVars.push_back(d_pad.d_vlb[x].y); - }else if(rep == SlackVUB){ - virtualVars.push_back(d_pad.d_vub[x].y); - } - } - - for(size_t i = 0; i < virtualVars.size(); ++i){ - ArithVar x = virtualVars[i]; - if(!cut.isKey(x)){ - cut.set(x, Rational(0)); - } - } - - iter = cut.begin(), iend = cut.end(); - for(; iter != iend; ++iter){ - ArithVar x = *iter; - if(!d_vars.isIntegerInput(x)){ - SlackReplace rep = d_pad.d_slacks[x]; - Rational& a = cut.get(x); - switch(rep){ - case SlackLB: - { - const DeltaRational& bound = d_vars.getLowerBound(x); - Assert(bound.infinitesimalIsZero()); - cutRhs += a * bound.getNoninfinitesimalPart(); - } - break; - case SlackUB: - { - const DeltaRational& bound = d_vars.getUpperBound(x); - Assert(bound.infinitesimalIsZero()); - cutRhs -= a * bound.getNoninfinitesimalPart(); - a = -a; - } - break; - case SlackVLB: - case SlackVUB: - { - bool lb = (rep == SlackVLB); - const VirtualBound& vb = lb ? - d_pad.d_vlb[x] : d_pad.d_vub[x]; - ArithVar y = vb.y; - Assert(vb.x == x); - Assert(cut.isKey(y)); - Rational& ycoeff = cut.get(y); - if(lb){ - ycoeff -= a * vb.d; - }else{ - ycoeff += a * vb.d; - a = -a; - } - } - break; - default: - return true; - } - } - } - removeZeroes(cut); - return false; -} - -bool ApproxGLPK::loadRowSumIntoAgg(int nid, int M, const PrimitiveVec& row_sum){ - DenseMap& lhs = d_pad.d_agg.lhs; - d_pad.d_agg.rhs = Rational(0); - - int len = row_sum.len; - for(int i = 1; i <= len; ++i){ - int aux_ind = row_sum.inds[i]; // auxillary index - double coeff = row_sum.coeffs[i]; - ArithVar x = _getArithVar(nid, M, aux_ind); - if(x == ARITHVAR_SENTINEL){ return true; } - std::optional c = estimateWithCFE(coeff); - if (!c) - { - return true; - } - - if (lhs.isKey(x)) - { - lhs.get(x) -= *c; - } - else - { - lhs.set(x, -(*c)); - } - } - - Trace("approx::mir") << "beg loadRowSumIntoAgg() 1" << endl; - if(TraceIsOn("approx::mir")) { DenseVector::print(Trace("approx::mir"), lhs); } - removeAuxillaryVariables(d_vars, lhs); - Trace("approx::mir") << "end loadRowSumIntoAgg() 1" << endl; - - if(TraceIsOn("approx::mir")){ - Trace("approx::mir") << "loadRowSumIntoAgg() 2" << endl; - DenseVector::print(Trace("approx::mir"), lhs); - Trace("approx::mir") << "end loadRowSumIntoAgg() 2" << endl; - } - - for(int i = 1; i <= len; ++i){ - int aux_ind = row_sum.inds[i]; // auxillary index - double coeff = row_sum.coeffs[i]; - ArithVar x = _getArithVar(nid, M, aux_ind); - Assert(x != ARITHVAR_SENTINEL); - std::optional c = estimateWithCFE(coeff); - if (!c) - { - return true; - } - Assert(!lhs.isKey(x)); - lhs.set(x, *c); - } - - if(TraceIsOn("approx::mir")){ - Trace("approx::mir") << "loadRowSumIntoAgg() 2" << endl; - DenseVector::print(Trace("approx::mir"), lhs); - Trace("approx::mir") << "end loadRowSumIntoAgg() 3" << endl; - } - return false; -} - -bool ApproxGLPK::buildModifiedRow(int nid, const MirInfo& mir){ - const DenseMap& agg = d_pad.d_agg.lhs; - const Rational& aggRhs = d_pad.d_agg.rhs; - DenseMap& mod = d_pad.d_mod.lhs; - Rational& modRhs = d_pad.d_mod.rhs; - - Trace("approx::mir") - << "buildModifiedRow()" - << " |agg|=" << d_pad.d_agg.lhs.size() - << " |mod|=" << d_pad.d_mod.lhs.size() - << " |slacks|=" << d_pad.d_slacks.size() - << " |vlb|=" << d_pad.d_vub.size() - << " |vub|=" << d_pad.d_vlb.size() << endl; - - mod.addAll(agg); - modRhs = aggRhs; - DenseMap::const_iterator iter, iend; - for(iter = agg.begin(), iend = agg.end(); iter != iend; ++iter){ - ArithVar x = *iter; - const Rational& c = mod[x]; - if(!d_pad.d_slacks.isKey(x)){ - Trace("approx::mir") << "missed x: " << x << endl; - return true; - } - SlackReplace rep = d_pad.d_slacks[x]; - switch(rep){ - case SlackLB: // skip for now - case SlackUB: - break; - case SlackVLB: /* x[k] = lb[k] * x[kk] + x'[k] */ - case SlackVUB: /* x[k] = ub[k] * x[kk] - x'[k] */ - { - Assert(!d_vars.isIntegerInput(x)); - bool ub = (rep == SlackVUB); - const VirtualBound& vb = - ub ? d_pad.d_vub[x] : d_pad.d_vlb[x]; - Assert(vb.x == x); - ArithVar y = vb.y; - Rational prod = c * vb.d; - if(mod.isKey(y)){ - mod.get(x) += prod; - }else{ - mod.set(y, prod); - } - if(ub){ - mod.set(x, -c); - } - Assert(vb.c != NullConstraint); - d_pad.d_explanation.insert(vb.c); - } - break; - default: - return true; - } - } - removeZeroes(mod); /* if something cancelled we don't want it in the explanation */ - for(iter = mod.begin(), iend = mod.end(); iter != iend; ++iter){ - ArithVar x = *iter; - if(!d_pad.d_slacks.isKey(x)){ return true; } - - SlackReplace rep = d_pad.d_slacks[x]; - switch(rep){ - case SlackLB: /* x = lb + x' */ - case SlackUB: /* x = ub - x' */ - { - bool ub = (rep == SlackUB); - ConstraintP b = ub ? d_vars.getUpperBoundConstraint(x): - d_vars.getLowerBoundConstraint(x); - - Assert(b != NullConstraint); - Assert(b->getValue().infinitesimalIsZero()); - const Rational& c = mod.get(x); - modRhs -= c * b->getValue().getNoninfinitesimalPart(); - if(ub){ - mod.set(x, -c); - } - d_pad.d_explanation.insert(b); - } - break; - case SlackVLB: /* handled earlier */ - case SlackVUB: - break; - default: - return true; - } - } - removeZeroes(mod); - return false; -} - -bool ApproxGLPK::makeRangeForComplemented(int nid, const MirInfo& mir){ - DenseMap& alpha = d_pad.d_alpha.lhs; - int M = mir.getMAtCreation(); - int N = mir.getN(); - DenseMap& compRanges = d_pad.d_compRanges; - - int complemented = 0; - - for(int j = 1; j <= M + N; ++j){ - if(mir.cset[j] != 0){ - complemented++; - ArithVar x = _getArithVar(nid, M, j); - if(!alpha.isKey(x)){ return true; } - if(!d_vars.isIntegerInput(x)){ return true; } - Assert(d_pad.d_slacks.isKey(x)); - Assert(d_pad.d_slacks[x] == SlackLB || d_pad.d_slacks[x] == SlackUB); - - ConstraintP lb = d_vars.getLowerBoundConstraint(x); - ConstraintP ub = d_vars.getUpperBoundConstraint(x); - - if(lb == NullConstraint) { return true; } - if(ub == NullConstraint) { return true; } - - if(!lb->getValue().infinitesimalIsZero()){ - return true; - } - if(!ub->getValue().infinitesimalIsZero()){ - return true; - } - - const Rational& uval = ub->getValue().getNoninfinitesimalPart(); - const Rational& lval = lb->getValue().getNoninfinitesimalPart(); - - d_pad.d_explanation.insert(lb); - d_pad.d_explanation.insert(ub); - - Rational u = uval - lval; - // u is the same for both rep == LP and rep == UB - if(compRanges.isKey(x)) { return true; } - compRanges.set(x,u); - } - } - - Trace("approx::mir") << "makeRangeForComplemented()" << complemented << endl; - return false; -} - - -bool ApproxGLPK::constructMixedKnapsack(){ - const DenseMap& mod = d_pad.d_mod.lhs; - const Rational& modRhs = d_pad.d_mod.rhs; - DenseMap& alpha = d_pad.d_alpha.lhs; - Rational& beta = d_pad.d_alpha.rhs; - - Assert(alpha.empty()); - beta = modRhs; - - unsigned intVars = 0; - unsigned remain = 0; - unsigned dropped = 0; - DenseMap::const_iterator iter, iend; - for(iter = mod.begin(), iend = mod.end(); iter != iend; ++iter){ - ArithVar v = *iter; - const Rational& c = mod[v]; - Assert(!c.isZero()); - if(d_vars.isIntegerInput(v)){ - intVars++; - alpha.set(v, c); - }else if(c.sgn() < 0){ - remain++; - alpha.set(v, c); - }else{ - dropped++; - } - } - - Trace("approx::mir") - << "constructMixedKnapsack() " - <<" dropped " << dropped - <<" remain " << remain - <<" intVars " << intVars - << endl; - return intVars == 0; // if this is 0 we have failed -} - -bool ApproxGLPK::attemptConstructTableRow(int nid, int M, const PrimitiveVec& vec){ - bool failed = guessCoefficientsConstructTableRow(nid, M, vec); - if(failed){ - failed = gaussianElimConstructTableRow(nid, M, vec); - } - - return failed; -} - -bool ApproxGLPK::gaussianElimConstructTableRow(int nid, int M, const PrimitiveVec& vec){ - TimerStat::CodeTimer codeTimer(d_stats.d_gaussianElimConstructTime); - ++d_stats.d_gaussianElimConstruct; - - ArithVar basic = d_pad.d_basic; - DenseMap& tab = d_pad.d_tabRow.lhs; - tab.purge(); - d_pad.d_tabRow.rhs = Rational(0); - Assert(basic != ARITHVAR_SENTINEL); - Assert(tab.empty()); - Assert(d_pad.d_tabRow.rhs.isZero()); - - if(d_vars.isAuxiliary(basic)) { return true; } - - if(TraceIsOn("gaussianElimConstructTableRow")){ - Trace("gaussianElimConstructTableRow") << "1 gaussianElimConstructTableRow("< onrow; - for(int i = 1; i <= vec.len; ++i){ - int ind = vec.inds[i]; - ArithVar var = _getArithVar(nid, M, ind); - if(var == ARITHVAR_SENTINEL){ - Trace("gaussianElimConstructTableRow") << "couldn't find" << ind << " " << M << " " << nid << endl; - return true; - } - onrow.insert(var); - } - - - Trace("gaussianElimConstructTableRow") << "2 gaussianElimConstructTableRow("< A; - A.increaseSizeTo(d_vars.getNumberOfVariables()); - std::vector< std::pair > rows; - // load the rows for auxiliary variables into A - for (ArithVar v : onrow) - { - if(d_vars.isAuxiliary(v)){ - Assert(d_vars.hasNode(v)); - - vector coeffs; - vector vars; - - coeffs.push_back(Rational(-1)); - vars.push_back(v); - - Node n = d_vars.asNode(v); - Polynomial p = Polynomial::parsePolynomial(n); - Polynomial::iterator j = p.begin(), jend=p.end(); - for(j=p.begin(), jend=p.end(); j!=jend; ++j){ - Monomial m = *j; - if(m.isConstant()) { return true; } - VarList vl = m.getVarList(); - if(!d_vars.hasArithVar(vl.getNode())){ return true; } - ArithVar x = d_vars.asArithVar(vl.getNode()); - const Rational& q = m.getConstant().getValue(); - coeffs.push_back(q); vars.push_back(x); - } - RowIndex rid = A.addRow(coeffs, vars); - rows.push_back(make_pair(rid, ARITHVAR_SENTINEL)); - } - } - Trace("gaussianElimConstructTableRow") << "3 gaussianElimConstructTableRow("<::Entry& e = A.findEntry(rid, other); - if(!e.blank()){ - // r_p : 0 = -1 * other + sum a_i x_i - // rid : 0 = e * other + sum b_i x_i - // rid += e * r_p - // : 0 = 0 * other + ... - Assert(!e.getCoefficient().isZero()); - - Rational cp = e.getCoefficient(); - Trace("gaussianElimConstructTableRow") - << "on " << rid << " subst " << cp << "*" << prevRow << " " << other << endl; - A.rowPlusRowTimesConstant(rid, prevRow, cp); - } - } - if(TraceIsOn("gaussianElimConstructTableRow")){ - A.printMatrix(Trace("gaussianElimConstructTableRow")); - } - - // solve the row for anything other than non-basics - bool solveForBasic = (i + 1 == rows.size()); - Rational q; - ArithVar s = ARITHVAR_SENTINEL; - Matrix::RowIterator k = A.getRow(rid).begin(); - Matrix::RowIterator k_end = A.getRow(rid).end(); - for(; k != k_end; ++k){ - const Matrix::Entry& e = *k; - ArithVar colVar = e.getColVar(); - bool selectColVar = false; - if(colVar == basic){ - selectColVar = solveForBasic; - }else if(onrow.find(colVar) == onrow.end()) { - selectColVar = true; - } - if(selectColVar){ - s = colVar; - q = e.getCoefficient(); - } - } - if(s == ARITHVAR_SENTINEL || q.isZero()){ - Trace("gaussianElimConstructTableRow") << "3 fail gaussianElimConstructTableRow("<::RowIterator k = A.getRow(rid_last).begin(); - Matrix::RowIterator k_end = A.getRow(rid_last).end(); - for(; k != k_end; ++k){ - const Matrix::Entry& e = *k; - tab.set(e.getColVar(), e.getCoefficient()); - } - Trace("gaussianElimConstructTableRow") << "5 gaussianElimConstructTableRow("< cfe = estimateWithCFE(coeff, D); - if (!cfe) - { - return true; - } - tab.set(var, *cfe); - Trace("guessCoefficientsConstructTableRow") << var << " cfe " << cfe << endl; - } - if(!guessIsConstructable(tab)){ - Trace("guessCoefficientsConstructTableRow") << "failed to construct with " << D << endl; - return true; - } - tab.remove(basic); - return false; -} - -/* Maps an ArithVar to either an upper/lower bound */ -bool ApproxGLPK::constructGmiCut(){ - const DenseMap& tabRow = d_pad.d_tabRow.lhs; - const DenseMap& toBound = d_pad.d_toBound; - DenseMap& cut = d_pad.d_cut.lhs; - std::set& explanation = d_pad.d_explanation; - Rational& rhs = d_pad.d_cut.rhs; - - DenseMap::const_iterator iter, end; - Assert(cut.empty()); - - // compute beta for a "fake" assignment - bool anyInf; - DeltaRational dbeta = sumConstraints(tabRow, toBound, &anyInf); - const Rational& beta = dbeta.getNoninfinitesimalPart(); - Trace("approx::gmi") << dbeta << endl; - if(anyInf || beta.isIntegral()){ return true; } - - Rational one = Rational(1); - Rational fbeta = beta.floor_frac(); - rhs = fbeta; - Assert(fbeta.sgn() > 0); - Assert(fbeta < one); - Rational one_sub_fbeta = one - fbeta; - for(iter = tabRow.begin(), end = tabRow.end(); iter != end; ++iter){ - ArithVar x = *iter; - const Rational& psi = tabRow[x]; - ConstraintP c = toBound[x]; - const Rational& bound = c->getValue().getNoninfinitesimalPart(); - if(d_vars.boundsAreEqual(x)){ - // do not add a coefficient - // implictly substitute the variable w/ its constraint - std::pair exp = d_vars.explainEqualBounds(x); - explanation.insert(exp.first); - if(exp.second != NullConstraint){ - explanation.insert(exp.second); - } - }else if(d_vars.isIntegerInput(x) && psi.isIntegral()){ - // do not add a coefficient - // nothing to explain - Trace("approx::gmi") << "skipping " << x << endl; - }else{ - explanation.insert(c); - Rational phi; - Rational alpha = (c->isUpperBound() ? psi : -psi); - - // x - ub <= 0 and lb - x <= 0 - if(d_vars.isIntegerInput(x)){ - Assert(!psi.isIntegral()); - // alpha = slack_sgn * psi - Rational falpha = alpha.floor_frac(); - Assert(falpha.sgn() > 0); - Assert(falpha < one); - phi = (falpha <= fbeta) ? - falpha : ((fbeta / one_sub_fbeta) * (one - falpha)); - }else{ - phi = (alpha >= 0) ? - alpha : ((fbeta / one_sub_fbeta) * (- alpha)); - } - Assert(phi.sgn() != 0); - if(c->isUpperBound()){ - cut.set(x, -phi); - rhs -= phi * bound; - }else{ - cut.set(x, phi); - rhs += phi * bound; - } - } - } - if(TraceIsOn("approx::gmi")){ - Trace("approx::gmi") << "pre removeSlackVariables"; - d_pad.d_cut.print(Trace("approx::gmi")); - Trace("approx::gmi") << endl; - } - removeAuxillaryVariables(d_vars, cut); - - if(TraceIsOn("approx::gmi")){ - Trace("approx::gmi") << "post removeAuxillaryVariables"; - d_pad.d_cut.print(Trace("approx::gmi")); - Trace("approx::gmi") << endl; - } - removeFixed(d_vars, d_pad.d_cut, explanation); - - if(TraceIsOn("approx::gmi")){ - Trace("approx::gmi") << "post removeFixed"; - d_pad.d_cut.print(Trace("approx::gmi")); - Trace("approx::gmi") << endl; - } - return false; -} - -void ApproxGLPK::tryCut(int nid, CutInfo& cut) -{ - Assert(!cut.reconstructed()); - Assert(cut.getKlass() != RowsDeletedKlass); - bool failure = false; - switch(cut.getKlass()){ - case GmiCutKlass: - failure = attemptGmi(nid, static_cast(cut)); - break; - case MirCutKlass: - failure = attemptMir(nid, static_cast(cut)); - break; - case BranchCutKlass: - failure = attemptBranchCut(nid, dynamic_cast(cut)); - break; - default: - break; - } - Assert(failure == d_pad.d_failure); - - if(!failure){ - // move the pad to the cut - cut.setReconstruction(d_pad.d_cut); - - if(cut.getKlass() != BranchCutKlass){ - std::set& exp = d_pad.d_explanation; - ConstraintCPVec asvec(exp.begin(), exp.end()); - cut.swapExplanation(asvec); - } - }else{ - Trace("approx") << "failure " << cut.getKlass() << endl; - } -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal -#endif /*#ifdef CVC5_USE_GLPK */ -/* End GPLK implementation. */ diff --git a/src/theory/arith/approx_simplex.h b/src/theory/arith/approx_simplex.h deleted file mode 100644 index 0b4bc2cd3..000000000 --- a/src/theory/arith/approx_simplex.h +++ /dev/null @@ -1,168 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Morgan Deters - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include -#include - -#include "theory/arith/arithvar.h" -#include "theory/arith/delta_rational.h" -#include "util/dense_map.h" -#include "util/rational.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -enum LinResult { - LinUnknown, /* Unknown error */ - LinFeasible, /* Relaxation is feasible */ - LinInfeasible, /* Relaxation is infeasible/all integer branches closed */ - LinExhausted -}; - -enum MipResult { - MipUnknown, /* Unknown error */ - MipBingo, /* Integer feasible */ - MipClosed, /* All integer branches closed */ - BranchesExhausted, /* Exhausted number of branches */ - PivotsExhauasted, /* Exhausted number of pivots */ - ExecExhausted /* Exhausted total operations */ -}; -std::ostream& operator<<(std::ostream& out, MipResult res); - -class ApproximateStatistics { - public: - ApproximateStatistics(); - - IntStat d_branchMaxDepth; - IntStat d_branchesMaxOnAVar; - - TimerStat d_gaussianElimConstructTime; - IntStat d_gaussianElimConstruct; - AverageStat d_averageGuesses; -}; - - -class NodeLog; -class TreeLog; -class ArithVariables; -class CutInfo; - -class ApproximateSimplex{ - public: - static bool enabled(); - - /** - * If glpk is enabled, return a subclass that can do something. - * If glpk is disabled, return a subclass that does nothing. - */ - static ApproximateSimplex* mkApproximateSimplexSolver(const ArithVariables& vars, TreeLog& l, ApproximateStatistics& s); - ApproximateSimplex(const ArithVariables& v, TreeLog& l, ApproximateStatistics& s); - virtual ~ApproximateSimplex(){} - - /* the maximum pivots allowed in a query. */ - void setPivotLimit(int pl); - - /* maximum branches allowed on a variable */ - void setBranchOnVariableLimit(int bl); - - /* maximum branches allowed on a variable */ - void setBranchingDepth(int bd); - - /** A result is either sat, unsat or unknown.*/ - struct Solution { - DenseSet newBasis; - DenseMap newValues; - Solution() : newBasis(), newValues(){} - }; - - virtual ArithVar getBranchVar(const NodeLog& nl) const = 0; - - /** Sets a maximization criteria for the approximate solver.*/ - virtual void setOptCoeffs(const ArithRatPairVec& ref) = 0; - - virtual ArithRatPairVec heuristicOptCoeffs() const = 0; - - virtual LinResult solveRelaxation() = 0; - virtual Solution extractRelaxation() const = 0; - - virtual MipResult solveMIP(bool activelyLog) = 0; - - virtual Solution extractMIP() const = 0; - - virtual std::vector getValidCuts(const NodeLog& node) = 0; - - virtual void tryCut(int nid, CutInfo& cut) = 0; - - /** UTILITIES FOR DEALING WITH ESTIMATES */ - - static constexpr double SMALL_FIXED_DELTA = .000000001; - static constexpr double TOLERENCE = 1 + .000000001; - - /** Returns true if two doubles are roughly equal based on TOLERENCE and SMALL_FIXED_DELTA.*/ - static bool roughlyEqual(double a, double b); - - /** - * Estimates a double as a Rational using continued fraction expansion that - * cuts off the estimate once the value is approximately zero. - * This is designed for removing rounding artifacts. - */ - static std::optional estimateWithCFE(double d); - static std::optional estimateWithCFE(double d, const Integer& D); - - /** - * Converts a rational to a continued fraction expansion representation - * using a maximum number of expansions equal to depth as long as the expression - * is not roughlyEqual() to 0. - */ - static std::vector rationalToCfe(const Rational& q, int depth); - - /** Converts a continued fraction expansion representation to a rational. */ - static Rational cfeToRational(const std::vector& exp); - - /** Estimates a rational as a continued fraction expansion.*/ - static Rational estimateWithCFE(const Rational& q, const Integer& K); - - virtual double sumInfeasibilities(bool mip) const = 0; - - protected: - const ArithVariables& d_vars; - TreeLog& d_log; - ApproximateStatistics& d_stats; - - /* the maximum pivots allowed in a query. */ - int d_pivotLimit; - - /* maximum branches allowed on a variable */ - int d_branchLimit; - - /* maxmimum branching depth allowed.*/ - int d_maxDepth; - - /* Default denominator for diophatine approximation, 2^{26} .*/ - static constexpr uint64_t s_defaultMaxDenom = (1 << 26); -};/* class ApproximateSimplex */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/arith_ite_utils.cpp b/src/theory/arith/arith_ite_utils.cpp index 6bf79b4ea..ee79b8375 100644 --- a/src/theory/arith/arith_ite_utils.cpp +++ b/src/theory/arith/arith_ite_utils.cpp @@ -26,7 +26,7 @@ #include "preprocessing/util/ite_utilities.h" #include "smt/env.h" #include "theory/arith/arith_utilities.h" -#include "theory/arith/normal_form.h" +#include "theory/arith/linear/normal_form.h" #include "theory/rewriter.h" #include "theory/substitutions.h" #include "theory/theory_model.h" @@ -102,7 +102,7 @@ Node ArithIteUtils::reduceVariablesInItes(Node n){ default: { TypeNode tn = n.getType(); - if (tn.isRealOrInt() && Polynomial::isMember(n)) + if (tn.isRealOrInt() && linear::Polynomial::isMember(n)) { Node newn = Node::null(); if(!d_contains.containsTermITE(n)){ @@ -110,12 +110,12 @@ Node ArithIteUtils::reduceVariablesInItes(Node n){ }else if(n.getNumChildren() > 0){ newn = applyReduceVariablesInItes(n); newn = rewrite(newn); - Assert(Polynomial::isMember(newn)); + Assert(linear::Polynomial::isMember(newn)); }else{ newn = n; } NodeManager* nm = NodeManager::currentNM(); - Polynomial p = Polynomial::parsePolynomial(newn); + linear::Polynomial p = linear::Polynomial::parsePolynomial(newn); if(p.isConstant()){ d_constants[n] = newn; d_varParts[n] = nm->mkConstRealOrInt(tn, Rational(0)); @@ -127,7 +127,7 @@ Node ArithIteUtils::reduceVariablesInItes(Node n){ d_reduceVar[n] = p.getNode(); return p.getNode(); }else{ - Monomial mc = p.getHead(); + linear::Monomial mc = p.getHead(); d_constants[n] = mc.getConstant().getNode(); d_varParts[n] = p.getTail().getNode(); d_reduceVar[n] = newn; @@ -444,12 +444,12 @@ bool ArithIteUtils::solveBinOr(TNode binor){ Node useForCmpL = selectForCmp(otherL); Node useForCmpR = selectForCmp(otherR); - Assert(Polynomial::isMember(sel)); - Assert(Polynomial::isMember(useForCmpL)); - Assert(Polynomial::isMember(useForCmpR)); - Polynomial lside = Polynomial::parsePolynomial( useForCmpL ); - Polynomial rside = Polynomial::parsePolynomial( useForCmpR ); - Polynomial diff = lside-rside; + Assert(linear::Polynomial::isMember(sel)); + Assert(linear::Polynomial::isMember(useForCmpL)); + Assert(linear::Polynomial::isMember(useForCmpR)); + linear::Polynomial lside = linear::Polynomial::parsePolynomial( useForCmpL ); + linear::Polynomial rside = linear::Polynomial::parsePolynomial( useForCmpR ); + linear::Polynomial diff = lside-rside; Trace("arith::ite") << "diff: " << diff.getNode() << endl; if(diff.isConstant()){ diff --git a/src/theory/arith/arith_rewriter.cpp b/src/theory/arith/arith_rewriter.cpp index 8add0b5ec..b32d488a2 100644 --- a/src/theory/arith/arith_rewriter.cpp +++ b/src/theory/arith/arith_rewriter.cpp @@ -28,7 +28,6 @@ #include "smt/logic_exception.h" #include "theory/arith/arith_msum.h" #include "theory/arith/arith_utilities.h" -#include "theory/arith/normal_form.h" #include "theory/arith/operator_elim.h" #include "theory/arith/rewriter/addition.h" #include "theory/arith/rewriter/node_utils.h" diff --git a/src/theory/arith/arith_rewriter.h b/src/theory/arith/arith_rewriter.h index e74790d26..0f2a55f84 100644 --- a/src/theory/arith/arith_rewriter.h +++ b/src/theory/arith/arith_rewriter.h @@ -13,7 +13,6 @@ * Rewriter for the theory of arithmetic. * * This rewrites to the normal form for arithmetic. - * See theory/arith/normal_form.h for more information. */ #include "cvc5_private.h" diff --git a/src/theory/arith/arith_state.cpp b/src/theory/arith/arith_state.cpp index 86088cc06..315551b4e 100644 --- a/src/theory/arith/arith_state.cpp +++ b/src/theory/arith/arith_state.cpp @@ -15,7 +15,7 @@ #include "theory/arith/arith_state.h" -#include "theory/arith/theory_arith_private.h" +#include "theory/arith/linear/theory_arith_private.h" namespace cvc5::internal { namespace theory { @@ -31,7 +31,7 @@ bool ArithState::isInConflict() const return d_parent->anyConflict() || d_conflict; } -void ArithState::setParent(TheoryArithPrivate* p) { d_parent = p; } +void ArithState::setParent(linear::TheoryArithPrivate* p) { d_parent = p; } } // namespace arith } // namespace theory diff --git a/src/theory/arith/arith_state.h b/src/theory/arith/arith_state.h index 3df2d68b0..2fffb8cc5 100644 --- a/src/theory/arith/arith_state.h +++ b/src/theory/arith/arith_state.h @@ -24,7 +24,9 @@ namespace cvc5::internal { namespace theory { namespace arith { +namespace linear { class TheoryArithPrivate; +} /** * The arithmetic state. @@ -44,11 +46,11 @@ class ArithState : public TheoryState /** Are we currently in conflict? */ bool isInConflict() const override; /** Set parent */ - void setParent(TheoryArithPrivate* p); + void setParent(linear::TheoryArithPrivate* p); private: /** reference to parent */ - TheoryArithPrivate* d_parent; + linear::TheoryArithPrivate* d_parent; }; } // namespace arith diff --git a/src/theory/arith/arith_static_learner.cpp b/src/theory/arith/arith_static_learner.cpp deleted file mode 100644 index f45f5977a..000000000 --- a/src/theory/arith/arith_static_learner.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Dejan Jovanovic, Andres Noetzli - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include - -#include "base/output.h" -#include "expr/node_algorithm.h" -#include "options/arith_options.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/arith_static_learner.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/normal_form.h" -#include "theory/rewriter.h" - -using namespace std; -using namespace cvc5::internal::kind; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -ArithStaticLearner::ArithStaticLearner(context::Context* userContext) - : d_minMap(userContext), d_maxMap(userContext), d_statistics() -{ -} - -ArithStaticLearner::~ArithStaticLearner(){ -} - -ArithStaticLearner::Statistics::Statistics() - : d_iteMinMaxApplications(smtStatisticsRegistry().registerInt( - "theory::arith::iteMinMaxApplications")), - d_iteConstantApplications(smtStatisticsRegistry().registerInt( - "theory::arith::iteConstantApplications")) -{ -} - -void ArithStaticLearner::staticLearning(TNode n, NodeBuilder& learned) -{ - vector workList; - workList.push_back(n); - TNodeSet processed; - - //Contains an underapproximation of nodes that must hold. - TNodeSet defTrue; - - defTrue.insert(n); - - while(!workList.empty()) { - n = workList.back(); - - bool unprocessedChildren = false; - for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { - if(processed.find(*i) == processed.end()) { - // unprocessed child - workList.push_back(*i); - unprocessedChildren = true; - } - } - if(n.getKind() == AND && defTrue.find(n) != defTrue.end() ){ - for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { - defTrue.insert(*i); - } - } - - if(unprocessedChildren) { - continue; - } - - workList.pop_back(); - // has node n been processed in the meantime ? - if(processed.find(n) != processed.end()) { - continue; - } - processed.insert(n); - - process(n,learned, defTrue); - - } -} - -void ArithStaticLearner::process(TNode n, - NodeBuilder& learned, - const TNodeSet& defTrue) -{ - Trace("arith::static") << "===================== looking at " << n << endl; - - switch(n.getKind()){ - case ITE: - if (expr::hasBoundVar(n)) - { - // Unsafe with non-ground ITEs; do nothing - Trace("arith::static") - << "(potentially) non-ground ITE, ignoring..." << endl; - break; - } - - if(n[0].getKind() != EQUAL && - isRelationOperator(n[0].getKind()) ){ - iteMinMax(n, learned); - } - - if((d_minMap.find(n[1]) != d_minMap.end() && d_minMap.find(n[2]) != d_minMap.end()) || - (d_maxMap.find(n[1]) != d_maxMap.end() && d_maxMap.find(n[2]) != d_maxMap.end())) { - iteConstant(n, learned); - } - break; - - case CONST_RATIONAL: - case CONST_INTEGER: - // Mark constants as minmax - d_minMap.insert(n, n.getConst()); - d_maxMap.insert(n, n.getConst()); - break; - default: // Do nothing - break; - } -} - -void ArithStaticLearner::iteMinMax(TNode n, NodeBuilder& learned) -{ - Assert(n.getKind() == kind::ITE); - Assert(n[0].getKind() != EQUAL); - Assert(isRelationOperator(n[0].getKind())); - - TNode c = n[0]; - Kind k = oldSimplifiedKind(c); - TNode t = n[1]; - TNode e = n[2]; - TNode cleft = (c.getKind() == NOT) ? c[0][0] : c[0]; - TNode cright = (c.getKind() == NOT) ? c[0][1] : c[1]; - - if((t == cright) && (e == cleft)){ - TNode tmp = t; - t = e; - e = tmp; - k = reverseRelationKind(k); - } - //(ite (< x y) x y) - //(ite (x < y) x y) - //(ite (x - y < 0) x y) - // ---------------- - // (ite (x - y < -c) ) - - if(t == cleft && e == cright){ - // t == cleft && e == cright - Assert(t == cleft); - Assert(e == cright); - switch(k){ - case LT: // (ite (< x y) x y) - case LEQ: { // (ite (<= x y) x y) - Node nLeqX = NodeBuilder(LEQ) << n << t; - Node nLeqY = NodeBuilder(LEQ) << n << e; - Trace("arith::static") << n << "is a min =>" << nLeqX << nLeqY << endl; - learned << nLeqX << nLeqY; - ++(d_statistics.d_iteMinMaxApplications); - break; - } - case GT: // (ite (> x y) x y) - case GEQ: { // (ite (>= x y) x y) - Node nGeqX = NodeBuilder(GEQ) << n << t; - Node nGeqY = NodeBuilder(GEQ) << n << e; - Trace("arith::static") << n << "is a max =>" << nGeqX << nGeqY << endl; - learned << nGeqX << nGeqY; - ++(d_statistics.d_iteMinMaxApplications); - break; - } - default: Unreachable(); - } - } -} - -void ArithStaticLearner::iteConstant(TNode n, NodeBuilder& learned) -{ - Assert(n.getKind() == ITE); - - Trace("arith::static") << "iteConstant(" << n << ")" << endl; - - if (d_minMap.find(n[1]) != d_minMap.end() && d_minMap.find(n[2]) != d_minMap.end()) { - const DeltaRational& first = d_minMap[n[1]]; - const DeltaRational& second = d_minMap[n[2]]; - DeltaRational min = std::min(first, second); - CDNodeToMinMaxMap::const_iterator minFind = d_minMap.find(n); - if (minFind == d_minMap.end() || (*minFind).second < min) { - d_minMap.insert(n, min); - NodeManager* nm = NodeManager::currentNM(); - Node nGeqMin = nm->mkNode( - min.getInfinitesimalPart() == 0 ? kind::GEQ : kind::GT, - n, - nm->mkConstRealOrInt(n.getType(), min.getNoninfinitesimalPart())); - learned << nGeqMin; - Trace("arith::static") << n << " iteConstant" << nGeqMin << endl; - ++(d_statistics.d_iteConstantApplications); - } - } - - if (d_maxMap.find(n[1]) != d_maxMap.end() && d_maxMap.find(n[2]) != d_maxMap.end()) { - const DeltaRational& first = d_maxMap[n[1]]; - const DeltaRational& second = d_maxMap[n[2]]; - DeltaRational max = std::max(first, second); - CDNodeToMinMaxMap::const_iterator maxFind = d_maxMap.find(n); - if (maxFind == d_maxMap.end() || (*maxFind).second > max) { - d_maxMap.insert(n, max); - NodeManager* nm = NodeManager::currentNM(); - Node nLeqMax = nm->mkNode( - max.getInfinitesimalPart() == 0 ? kind::LEQ : kind::LT, - n, - nm->mkConstRealOrInt(n.getType(), max.getNoninfinitesimalPart())); - learned << nLeqMax; - Trace("arith::static") << n << " iteConstant" << nLeqMax << endl; - ++(d_statistics.d_iteConstantApplications); - } - } -} - -std::set listToSet(TNode l){ - std::set ret; - while(l.getKind() == OR){ - Assert(l.getNumChildren() == 2); - ret.insert(l[0]); - l = l[1]; - } - return ret; -} - -void ArithStaticLearner::addBound(TNode n) { - - CDNodeToMinMaxMap::const_iterator minFind = d_minMap.find(n[0]); - CDNodeToMinMaxMap::const_iterator maxFind = d_maxMap.find(n[0]); - - Rational constant = n[1].getConst(); - DeltaRational bound = constant; - - switch(Kind k = n.getKind()) { - case kind::LT: bound = DeltaRational(constant, -1); CVC5_FALLTHROUGH; - case kind::LEQ: - if (maxFind == d_maxMap.end() || (*maxFind).second > bound) - { - d_maxMap.insert(n[0], bound); - Trace("arith::static") << "adding bound " << n << endl; - } - break; - case kind::GT: bound = DeltaRational(constant, 1); CVC5_FALLTHROUGH; - case kind::GEQ: - if (minFind == d_minMap.end() || (*minFind).second < bound) - { - d_minMap.insert(n[0], bound); - Trace("arith::static") << "adding bound " << n << endl; - } - break; - default: Unhandled() << k; break; - } -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/arith_static_learner.h b/src/theory/arith/arith_static_learner.h deleted file mode 100644 index 1ac8d7ce5..000000000 --- a/src/theory/arith/arith_static_learner.h +++ /dev/null @@ -1,80 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andres Noetzli, Dejan Jovanovic - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__ARITH_STATIC_LEARNER_H -#define CVC5__THEORY__ARITH__ARITH_STATIC_LEARNER_H - -#include "context/cdhashmap.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/delta_rational.h" -#include "util/statistics_stats.h" - -namespace cvc5::context { -class Context; -} - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class ArithStaticLearner { -private: - - /** - * Map from a node to it's minimum and maximum. - */ - typedef context::CDHashMap CDNodeToMinMaxMap; - CDNodeToMinMaxMap d_minMap; - CDNodeToMinMaxMap d_maxMap; - -public: - ArithStaticLearner(context::Context* userContext); - ~ArithStaticLearner(); - void staticLearning(TNode n, NodeBuilder& learned); - - void addBound(TNode n); - -private: - void process(TNode n, NodeBuilder& learned, const TNodeSet& defTrue); - - void iteMinMax(TNode n, NodeBuilder& learned); - void iteConstant(TNode n, NodeBuilder& learned); - - /** - * These fields are designed to be accessible to ArithStaticLearner methods. - */ - class Statistics - { - public: - IntStat d_iteMinMaxApplications; - IntStat d_iteConstantApplications; - - Statistics(); - }; - - Statistics d_statistics; - -};/* class ArithStaticLearner */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__ARITH_STATIC_LEARNER_H */ diff --git a/src/theory/arith/arith_utilities.h b/src/theory/arith/arith_utilities.h index 0f85468a2..7f4398b9b 100644 --- a/src/theory/arith/arith_utilities.h +++ b/src/theory/arith/arith_utilities.h @@ -25,7 +25,6 @@ #include "context/cdhashset.h" #include "expr/node.h" #include "expr/subs.h" -#include "theory/arith/arithvar.h" #include "util/dense_map.h" #include "util/integer.h" #include "util/rational.h" @@ -39,10 +38,6 @@ typedef std::unordered_set NodeSet; typedef std::unordered_set TNodeSet; typedef context::CDHashSet CDNodeSet; -//Maps from Nodes -> ArithVars, and vice versa -typedef std::unordered_map NodeToArithVarMap; -typedef DenseMap ArithVarToNodeMap; - inline Node mkRationalNode(const Rational& q){ return NodeManager::currentNM()->mkConst(kind::CONST_RATIONAL, q); } diff --git a/src/theory/arith/arithvar.cpp b/src/theory/arith/arithvar.cpp deleted file mode 100644 index d83fe2b03..000000000 --- a/src/theory/arith/arithvar.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/arithvar.h" -#include -#include - -namespace cvc5::internal { -namespace theory { -namespace arith { - -const ArithVar ARITHVAR_SENTINEL = std::numeric_limits::max(); - -bool debugIsASet(const std::vector& variables){ - std::set asSet(variables.begin(), variables.end()); - return asSet.size() == variables.size(); -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/arithvar.h b/src/theory/arith/arithvar.h deleted file mode 100644 index 33eb19631..000000000 --- a/src/theory/arith/arithvar.h +++ /dev/null @@ -1,45 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * Defines ArithVar which is the internal representation of variables in - * arithmetic - * - * This defines ArithVar which is the internal representation of variables in - * arithmetic. This is a typedef from Index to ArithVar. - * This file also provides utilities for ArithVars. - */ - -#include "cvc5_private.h" - -#pragma once - -#include - -#include "util/index.h" -#include "util/rational.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -typedef Index ArithVar; -extern const ArithVar ARITHVAR_SENTINEL; - -typedef std::vector ArithVarVec; -typedef std::pair ArithRatPair; -typedef std::vector< ArithRatPair > ArithRatPairVec; - -extern bool debugIsASet(const ArithVarVec& variables); - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/arithvar_node_map.h b/src/theory/arith/arithvar_node_map.h deleted file mode 100644 index 85a962758..000000000 --- a/src/theory/arith/arithvar_node_map.h +++ /dev/null @@ -1,95 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Aina Niemetz - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__ARITHVAR_NODE_MAP_H -#define CVC5__THEORY__ARITH__ARITHVAR_NODE_MAP_H - -#include "theory/arith/arithvar.h" -#include "context/context.h" -#include "context/cdlist.h" -#include "context/cdhashmap.h" -#include "context/cdo.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class ArithVarNodeMap { -private: - /** - * Bidirectional map between Nodes and ArithVars. - */ - NodeToArithVarMap d_nodeToArithVarMap; - ArithVarToNodeMap d_arithVarToNodeMap; - -public: - - typedef ArithVarToNodeMap::const_iterator var_iterator; - - ArithVarNodeMap() {} - - inline bool hasArithVar(TNode x) const { - return d_nodeToArithVarMap.find(x) != d_nodeToArithVarMap.end(); - } - - inline bool hasNode(ArithVar a) const { - return d_arithVarToNodeMap.isKey(a); - } - - inline ArithVar asArithVar(TNode x) const{ - Assert(hasArithVar(x)); - Assert((d_nodeToArithVarMap.find(x))->second <= ARITHVAR_SENTINEL); - return (d_nodeToArithVarMap.find(x))->second; - } - - inline Node asNode(ArithVar a) const{ - Assert(hasNode(a)); - return d_arithVarToNodeMap[a]; - } - - inline void setArithVar(TNode x, ArithVar a){ - Assert(!hasArithVar(x)); - Assert(!d_arithVarToNodeMap.isKey(a)); - d_arithVarToNodeMap.set(a, x); - d_nodeToArithVarMap[x] = a; - } - - inline void remove(ArithVar x){ - Assert(hasNode(x)); - Node node = asNode(x); - - d_nodeToArithVarMap.erase(d_nodeToArithVarMap.find(node)); - d_arithVarToNodeMap.remove(x); - } - - var_iterator var_begin() const { - return d_arithVarToNodeMap.begin(); - } - var_iterator var_end() const { - return d_arithVarToNodeMap.end(); - } - -};/* class ArithVarNodeMap */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__ARITHVAR_NODE_MAP_H */ diff --git a/src/theory/arith/attempt_solution_simplex.cpp b/src/theory/arith/attempt_solution_simplex.cpp deleted file mode 100644 index ed35dad74..000000000 --- a/src/theory/arith/attempt_solution_simplex.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ -#include "theory/arith/attempt_solution_simplex.h" - -#include "base/output.h" -#include "options/arith_options.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" -#include "theory/arith/error_set.h" -#include "theory/arith/linear_equality.h" -#include "theory/arith/tableau.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -AttemptSolutionSDP::AttemptSolutionSDP(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc) - : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), - d_statistics() -{ } - -AttemptSolutionSDP::Statistics::Statistics() - : d_searchTime(smtStatisticsRegistry().registerTimer( - "theory::arith::attempt::searchTime")), - d_queueTime(smtStatisticsRegistry().registerTimer( - "theory::arith::attempt::queueTime")), - d_conflicts(smtStatisticsRegistry().registerInt( - "theory::arith::attempt::conflicts")) -{ -} - -bool AttemptSolutionSDP::matchesNewValue(const DenseMap& nv, ArithVar v) const{ - return nv[v] == d_variables.getAssignment(v); -} - -Result::Status AttemptSolutionSDP::attempt( - const ApproximateSimplex::Solution& sol) -{ - const DenseSet& newBasis = sol.newBasis; - const DenseMap& newValues = sol.newValues; - - DenseSet needsToBeAdded; - for(DenseSet::const_iterator i = newBasis.begin(), i_end = newBasis.end(); i != i_end; ++i){ - ArithVar b = *i; - if(!d_tableau.isBasic(b)){ - needsToBeAdded.add(b); - } - } - DenseMap::const_iterator nvi = newValues.begin(), nvi_end = newValues.end(); - for(; nvi != nvi_end; ++nvi){ - ArithVar currentlyNb = *nvi; - if(!d_tableau.isBasic(currentlyNb)){ - if(!matchesNewValue(newValues, currentlyNb)){ - const DeltaRational& newValue = newValues[currentlyNb]; - Trace("arith::updateMany") - << "updateMany:" << currentlyNb << " " - << d_variables.getAssignment(currentlyNb) << " to "<< newValue << endl; - d_linEq.update(currentlyNb, newValue); - Assert(d_variables.assignmentIsConsistent(currentlyNb)); - } - } - } - d_errorSet.reduceToSignals(); - d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); - - if(processSignals()){ - Trace("arith::findModel") << "attemptSolution() early conflict" << endl; - d_conflictVariables.purge(); - return Result::UNSAT; - }else if(d_errorSet.errorEmpty()){ - Trace("arith::findModel") << "attemptSolution() fixed itself" << endl; - return Result::SAT; - } - - while(!needsToBeAdded.empty() && !d_errorSet.errorEmpty()){ - ArithVar toRemove = ARITHVAR_SENTINEL; - ArithVar toAdd = ARITHVAR_SENTINEL; - DenseSet::const_iterator i = needsToBeAdded.begin(), i_end = needsToBeAdded.end(); - for(; toAdd == ARITHVAR_SENTINEL && i != i_end; ++i){ - ArithVar v = *i; - - Tableau::ColIterator colIter = d_tableau.colIterator(v); - for(; !colIter.atEnd(); ++colIter){ - const Tableau::Entry& entry = *colIter; - Assert(entry.getColVar() == v); - ArithVar b = d_tableau.rowIndexToBasic(entry.getRowIndex()); - if(!newBasis.isMember(b)){ - toAdd = v; - - bool favorBOverToRemove = - (toRemove == ARITHVAR_SENTINEL) || - (matchesNewValue(newValues, toRemove) && !matchesNewValue(newValues, b)) || - (d_tableau.basicRowLength(toRemove) > d_tableau.basicRowLength(b)); - - if(favorBOverToRemove){ - toRemove = b; - } - } - } - } - Assert(toRemove != ARITHVAR_SENTINEL); - Assert(toAdd != ARITHVAR_SENTINEL); - - Trace("arith::forceNewBasis") << toRemove << " " << toAdd << endl; - - d_linEq.pivotAndUpdate(toRemove, toAdd, newValues[toRemove]); - - Trace("arith::forceNewBasis") << needsToBeAdded.size() << "to go" << endl; - needsToBeAdded.remove(toAdd); - - bool conflict = processSignals(); - if(conflict){ - d_errorSet.reduceToSignals(); - d_conflictVariables.purge(); - - return Result::UNSAT; - } - } - Assert(d_conflictVariables.empty()); - - if(d_errorSet.errorEmpty()){ - return Result::SAT; - }else{ - d_errorSet.reduceToSignals(); - return Result::UNKNOWN; - } -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/attempt_solution_simplex.h b/src/theory/arith/attempt_solution_simplex.h deleted file mode 100644 index 43c6f718d..000000000 --- a/src/theory/arith/attempt_solution_simplex.h +++ /dev/null @@ -1,100 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - * - * This implements the Simplex module for the Simpelx for DPLL(T) decision - * procedure. - * See the Simplex for DPLL(T) technical report for more background.(citation?) - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This is required to either produce a conflict or satisifying PartialModel. - * Further, we require being told when a basic variable updates its value. - * - * During the Simplex search we maintain a queue of variables. - * The queue is required to contain all of the basic variables that voilate - * their bounds. - * As elimination from the queue is more efficient to be done lazily, - * we do not maintain that the queue of variables needs to be only basic - * variables or only variables that satisfy their bounds. - * - * The simplex procedure roughly follows Alberto's thesis. (citation?) - * There is one round of selecting using a heuristic pivoting rule. - * (See PreferenceFunction Documentation for the available options.) - * The non-basic variable is the one that appears in the fewest pivots. - * (Bruno says that Leonardo invented this first.) - * After this, Bland's pivot rule is invoked. - * - * During this proccess, we periodically inspect the queue of variables to - * 1) remove now extraneous extries, - * 2) detect conflicts that are "waiting" on the queue but may not be detected - * by the current queue heuristics, and - * 3) detect multiple conflicts. - * - * Conflicts are greedily slackened to use the weakest bounds that still - * produce the conflict. - * - * Extra things tracked atm: (Subject to change at Tim's whims) - * - A superset of all of the newly pivoted variables. - * - A queue of additional conflicts that were discovered by Simplex. - * These are theory valid and are currently turned into lemmas - */ - -#include "cvc5_private.h" - -#pragma once - -#include "theory/arith/approx_simplex.h" -#include "theory/arith/simplex.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class AttemptSolutionSDP : public SimplexDecisionProcedure { -public: - AttemptSolutionSDP(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc); - - Result::Status attempt(const ApproximateSimplex::Solution& sol); - - Result::Status findModel(bool exactResult) override { Unreachable(); } - -private: - bool matchesNewValue(const DenseMap& nv, ArithVar v) const; - - bool processSignals() - { - TimerStat& timer = d_statistics.d_queueTime; - IntStat& conflictStat = d_statistics.d_conflicts; - return standardProcessSignals(timer, conflictStat); - } - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - TimerStat d_searchTime; - TimerStat d_queueTime; - IntStat d_conflicts; - - Statistics(); - } d_statistics; -};/* class AttemptSolutionSDP */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/bound_counts.h b/src/theory/arith/bound_counts.h deleted file mode 100644 index bef547d75..000000000 --- a/src/theory/arith/bound_counts.h +++ /dev/null @@ -1,235 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Clark Barrett - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" -#pragma once - -#include "base/check.h" -#include "theory/arith/arithvar.h" -#include "util/dense_map.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class BoundCounts { -private: - uint32_t d_lowerBoundCount; - uint32_t d_upperBoundCount; - -public: - BoundCounts() : d_lowerBoundCount(0), d_upperBoundCount(0) {} - BoundCounts(uint32_t lbs, uint32_t ubs) - : d_lowerBoundCount(lbs), d_upperBoundCount(ubs) {} - - bool operator==(BoundCounts bc) const { - return d_lowerBoundCount == bc.d_lowerBoundCount - && d_upperBoundCount == bc.d_upperBoundCount; - } - bool operator!=(BoundCounts bc) const { - return d_lowerBoundCount != bc.d_lowerBoundCount - || d_upperBoundCount != bc.d_upperBoundCount; - } - /** This is not a total order! */ - bool operator>=(BoundCounts bc) const { - return d_lowerBoundCount >= bc.d_lowerBoundCount && - d_upperBoundCount >= bc.d_upperBoundCount; - } - - inline bool isZero() const{ return d_lowerBoundCount == 0 && d_upperBoundCount == 0; } - inline uint32_t lowerBoundCount() const{ - return d_lowerBoundCount; - } - inline uint32_t upperBoundCount() const{ - return d_upperBoundCount; - } - - inline BoundCounts operator+(BoundCounts bc) const{ - return BoundCounts(d_lowerBoundCount + bc.d_lowerBoundCount, - d_upperBoundCount + bc.d_upperBoundCount); - } - - inline BoundCounts operator-(BoundCounts bc) const { - Assert(*this >= bc); - return BoundCounts(d_lowerBoundCount - bc.d_lowerBoundCount, - d_upperBoundCount - bc.d_upperBoundCount); - } - - - inline BoundCounts& operator+=(BoundCounts bc) { - d_upperBoundCount += bc.d_upperBoundCount; - d_lowerBoundCount += bc.d_lowerBoundCount; - return *this; - } - - inline BoundCounts& operator-=(BoundCounts bc) { - Assert(d_lowerBoundCount >= bc.d_lowerBoundCount); - Assert(d_upperBoundCount >= bc.d_upperBoundCount); - d_upperBoundCount -= bc.d_upperBoundCount; - d_lowerBoundCount -= bc.d_lowerBoundCount; - - return *this; - } - - /** Based on the sign coefficient a variable is multiplied by, - * the effects the bound counts either has no effect (sgn == 0), - * the lower bounds and upper bounds flip (sgn < 0), or nothing (sgn >0). - */ - inline BoundCounts multiplyBySgn(int sgn) const{ - if(sgn > 0){ - return *this; - }else if(sgn == 0){ - return BoundCounts(0,0); - }else{ - return BoundCounts(d_upperBoundCount, d_lowerBoundCount); - } - } - - inline void addInChange(int sgn, BoundCounts before, BoundCounts after){ - if(before == after){ - return; - }else if(sgn < 0){ - Assert(d_upperBoundCount >= before.d_lowerBoundCount); - Assert(d_lowerBoundCount >= before.d_upperBoundCount); - d_upperBoundCount += after.d_lowerBoundCount - before.d_lowerBoundCount; - d_lowerBoundCount += after.d_upperBoundCount - before.d_upperBoundCount; - }else if(sgn > 0){ - Assert(d_upperBoundCount >= before.d_upperBoundCount); - Assert(d_lowerBoundCount >= before.d_lowerBoundCount); - d_upperBoundCount += after.d_upperBoundCount - before.d_upperBoundCount; - d_lowerBoundCount += after.d_lowerBoundCount - before.d_lowerBoundCount; - } - } - - inline void addInSgn(BoundCounts bc, int before, int after){ - Assert(before != after); - Assert(!bc.isZero()); - - if(before < 0){ - d_upperBoundCount -= bc.d_lowerBoundCount; - d_lowerBoundCount -= bc.d_upperBoundCount; - }else if(before > 0){ - d_upperBoundCount -= bc.d_upperBoundCount; - d_lowerBoundCount -= bc.d_lowerBoundCount; - } - if(after < 0){ - d_upperBoundCount += bc.d_lowerBoundCount; - d_lowerBoundCount += bc.d_upperBoundCount; - }else if(after > 0){ - d_upperBoundCount += bc.d_upperBoundCount; - d_lowerBoundCount += bc.d_lowerBoundCount; - } - } -}; - -class BoundsInfo { -private: - - /** - * x = \sum_{a < 0} a_i i + \sum_{b > 0} b_j j - * - * AtUpperBound = {assignment(i) = lb(i)} \cup {assignment(j) = ub(j)} - * AtLowerBound = {assignment(i) = ub(i)} \cup {assignment(j) = lb(j)} - */ - BoundCounts d_atBounds; - - /** This is for counting how many upper and lower bounds a row has. */ - BoundCounts d_hasBounds; - -public: - BoundsInfo() : d_atBounds(), d_hasBounds() {} - BoundsInfo(BoundCounts atBounds, BoundCounts hasBounds) - : d_atBounds(atBounds), d_hasBounds(hasBounds) {} - - BoundCounts atBounds() const { return d_atBounds; } - BoundCounts hasBounds() const { return d_hasBounds; } - - /** This corresponds to adding in another variable to the row. */ - inline BoundsInfo operator+(const BoundsInfo& bc) const{ - return BoundsInfo(d_atBounds + bc.d_atBounds, - d_hasBounds + bc.d_hasBounds); - } - /** This corresponds to removing a variable from the row. */ - inline BoundsInfo operator-(const BoundsInfo& bc) const { - Assert(*this >= bc); - return BoundsInfo(d_atBounds - bc.d_atBounds, - d_hasBounds - bc.d_hasBounds); - } - - inline BoundsInfo& operator+=(const BoundsInfo& bc) { - d_atBounds += bc.d_atBounds; - d_hasBounds += bc.d_hasBounds; - return (*this); - } - - /** Based on the sign coefficient a variable is multiplied by, - * the effects the bound counts either has no effect (sgn == 0), - * the lower bounds and upper bounds flip (sgn < 0), or nothing (sgn >0). - */ - inline BoundsInfo multiplyBySgn(int sgn) const{ - return BoundsInfo(d_atBounds.multiplyBySgn(sgn), d_hasBounds.multiplyBySgn(sgn)); - } - - bool operator==(const BoundsInfo& other) const{ - return d_atBounds == other.d_atBounds && d_hasBounds == other.d_hasBounds; - } - bool operator!=(const BoundsInfo& other) const{ - return !(*this == other); - } - /** This is not a total order! */ - bool operator>=(const BoundsInfo& other) const{ - return d_atBounds >= other.d_atBounds && d_hasBounds >= other.d_hasBounds; - } - void addInChange(int sgn, const BoundsInfo& before, const BoundsInfo& after){ - addInAtBoundChange(sgn, before.d_atBounds, after.d_atBounds); - addInHasBoundChange(sgn, before.d_hasBounds, after.d_hasBounds); - } - void addInAtBoundChange(int sgn, BoundCounts before, BoundCounts after){ - d_atBounds.addInChange(sgn, before, after); - } - void addInHasBoundChange(int sgn, BoundCounts before, BoundCounts after){ - d_hasBounds.addInChange(sgn, before, after); - } - - inline void addInSgn(const BoundsInfo& bc, int before, int after){ - if(!bc.d_atBounds.isZero()){ d_atBounds.addInSgn(bc.d_atBounds, before, after);} - if(!bc.d_hasBounds.isZero()){ d_hasBounds.addInSgn(bc.d_hasBounds, before, after);} - } -}; - -/** This is intended to map each row to its relevant bound information. */ -typedef DenseMap BoundInfoMap; - -inline std::ostream& operator<<(std::ostream& os, const BoundCounts& bc){ - os << "[bc " << bc.lowerBoundCount() << ", " << bc.upperBoundCount() << "]"; - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const BoundsInfo& inf){ - os << "[bi : @ " << inf.atBounds() << ", " << inf.hasBounds() << "]"; - return os; -} -class BoundUpdateCallback { -public: - virtual ~BoundUpdateCallback() {} - virtual void operator()(ArithVar v, const BoundsInfo& up) = 0; -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/bound_inference.cpp b/src/theory/arith/bound_inference.cpp index 68b3a7b2a..3ec622dcf 100644 --- a/src/theory/arith/bound_inference.cpp +++ b/src/theory/arith/bound_inference.cpp @@ -16,7 +16,7 @@ #include "theory/arith/bound_inference.h" #include "smt/env.h" -#include "theory/arith/normal_form.h" +#include "theory/arith/linear/normal_form.h" #include "theory/rewriter.h" using namespace cvc5::internal::kind; @@ -62,7 +62,7 @@ bool BoundInference::add(const Node& n, bool onlyVariables) return false; } // Parse the node as a comparison - auto comp = Comparison::parseNormalForm(tmp); + auto comp = linear::Comparison::parseNormalForm(tmp); auto dec = comp.decompose(true); if (onlyVariables && !std::get<0>(dec).isVariable()) { diff --git a/src/theory/arith/callbacks.cpp b/src/theory/arith/callbacks.cpp deleted file mode 100644 index fb38ac0cd..000000000 --- a/src/theory/arith/callbacks.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/callbacks.h" - -#include "expr/skolem_manager.h" -#include "proof/proof_node.h" -#include "theory/arith/theory_arith_private.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -SetupLiteralCallBack::SetupLiteralCallBack(TheoryArithPrivate& ta) - : d_arith(ta) -{} -void SetupLiteralCallBack::operator()(TNode lit){ - TNode atom = (lit.getKind() == kind::NOT) ? lit[0] : lit; - if(!d_arith.isSetup(atom)){ - d_arith.setupAtom(atom); - } -} - -DeltaComputeCallback::DeltaComputeCallback(const TheoryArithPrivate& ta) - : d_ta(ta) -{} -Rational DeltaComputeCallback::operator()() const{ - return d_ta.deltaValueForTotalOrder(); -} - -TempVarMalloc::TempVarMalloc(TheoryArithPrivate& ta) -: d_ta(ta) -{} -ArithVar TempVarMalloc::request(){ - NodeManager* nm = NodeManager::currentNM(); - SkolemManager* sm = nm->getSkolemManager(); - Node skolem = sm->mkDummySkolem("tmpVar", nm->realType()); - return d_ta.requestArithVar(skolem, false, true); -} -void TempVarMalloc::release(ArithVar v){ - d_ta.releaseArithVar(v); -} - -BasicVarModelUpdateCallBack::BasicVarModelUpdateCallBack(TheoryArithPrivate& ta) - : d_ta(ta) -{} -void BasicVarModelUpdateCallBack::operator()(ArithVar x){ - d_ta.signal(x); -} - -RaiseConflict::RaiseConflict(TheoryArithPrivate& ta) - : d_ta(ta) -{} - -void RaiseConflict::raiseConflict(ConstraintCP c, InferenceId id) const{ - Assert(c->inConflict()); - d_ta.raiseConflict(c, id); -} - -FarkasConflictBuilder::FarkasConflictBuilder(bool produceProofs) - : d_farkas(), - d_constraints(), - d_consequent(NullConstraint), - d_consequentSet(false), - d_produceProofs(produceProofs) -{ - reset(); -} - -bool FarkasConflictBuilder::underConstruction() const{ - return d_consequent != NullConstraint; -} - -bool FarkasConflictBuilder::consequentIsSet() const{ - return d_consequentSet; -} - -void FarkasConflictBuilder::reset(){ - d_consequent = NullConstraint; - d_constraints.clear(); - d_consequentSet = false; - if (d_produceProofs) - { - d_farkas.clear(); - } - Assert(!underConstruction()); -} - -/* Adds a constraint to the constraint under construction. */ -void FarkasConflictBuilder::addConstraint(ConstraintCP c, const Rational& fc){ - Assert( - !d_produceProofs - || (!underConstruction() && d_constraints.empty() && d_farkas.empty()) - || (underConstruction() && d_constraints.size() + 1 == d_farkas.size())); - Assert(d_produceProofs || d_farkas.empty()); - Assert(c->isTrue()); - - if(d_consequent == NullConstraint){ - d_consequent = c; - } else { - d_constraints.push_back(c); - } - if (d_produceProofs) - { - d_farkas.push_back(fc); - } - Assert(!d_produceProofs || d_constraints.size() + 1 == d_farkas.size()); - Assert(d_produceProofs || d_farkas.empty()); -} - -void FarkasConflictBuilder::addConstraint(ConstraintCP c, const Rational& fc, const Rational& mult){ - Assert(!mult.isZero()); - if (d_produceProofs && !mult.isOne()) - { - Rational prod = fc * mult; - addConstraint(c, prod); - } - else - { - addConstraint(c, fc); - } -} - -void FarkasConflictBuilder::makeLastConsequent(){ - Assert(!d_consequentSet); - Assert(underConstruction()); - - if(d_constraints.empty()){ - // no-op - d_consequentSet = true; - } else { - Assert(d_consequent != NullConstraint); - ConstraintCP last = d_constraints.back(); - d_constraints.back() = d_consequent; - d_consequent = last; - if (d_produceProofs) - { - std::swap(d_farkas.front(), d_farkas.back()); - } - d_consequentSet = true; - } - - Assert(!d_consequent->negationHasProof()); - Assert(d_consequentSet); -} - -/* Turns the vector under construction into a conflict */ -ConstraintCP FarkasConflictBuilder::commitConflict(){ - Assert(underConstruction()); - Assert(!d_constraints.empty()); - Assert( - !d_produceProofs - || (!underConstruction() && d_constraints.empty() && d_farkas.empty()) - || (underConstruction() && d_constraints.size() + 1 == d_farkas.size())); - Assert(d_produceProofs || d_farkas.empty()); - Assert(d_consequentSet); - - ConstraintP not_c = d_consequent->getNegation(); - RationalVectorCP coeffs = d_produceProofs ? &d_farkas : nullptr; - not_c->impliedByFarkas(d_constraints, coeffs, true ); - - reset(); - Assert(!underConstruction()); - Assert(not_c->inConflict()); - Assert(!d_consequentSet); - return not_c; -} - -RaiseEqualityEngineConflict::RaiseEqualityEngineConflict(TheoryArithPrivate& ta) - : d_ta(ta) -{} - -/* If you are not an equality engine, don't use this! */ -void RaiseEqualityEngineConflict::raiseEEConflict( - Node n, std::shared_ptr pf) const -{ - d_ta.raiseBlackBoxConflict(n, pf); -} - -BoundCountingLookup::BoundCountingLookup(TheoryArithPrivate& ta) -: d_ta(ta) -{} - -const BoundsInfo& BoundCountingLookup::boundsInfo(ArithVar basic) const{ - return d_ta.boundsInfo(basic); -} - -BoundCounts BoundCountingLookup::atBounds(ArithVar basic) const{ - return boundsInfo(basic).atBounds(); -} -BoundCounts BoundCountingLookup::hasBounds(ArithVar basic) const { - return boundsInfo(basic).hasBounds(); -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/callbacks.h b/src/theory/arith/callbacks.h deleted file mode 100644 index 976cdf6b0..000000000 --- a/src/theory/arith/callbacks.h +++ /dev/null @@ -1,205 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Mathias Preiner, Clark Barrett - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#pragma once - -#include "expr/node.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/bound_counts.h" -#include "theory/arith/constraint_forward.h" -#include "theory/inference_id.h" -#include "util/rational.h" - -namespace cvc5::internal { - -class ProofNode; - -namespace theory { -namespace arith { - -class TheoryArithPrivate; - -/** - * ArithVarCallBack provides a mechanism for agreeing on callbacks while - * breaking mutual recursion inclusion order problems. - */ -class ArithVarCallBack { -public: - virtual ~ArithVarCallBack() {} - virtual void operator()(ArithVar x) = 0; -}; - -/** - * Requests arithmetic variables for internal use, - * and releases arithmetic variables that are no longer being used. - */ -class ArithVarMalloc { -public: - virtual ~ArithVarMalloc() {} - virtual ArithVar request() = 0; - virtual void release(ArithVar v) = 0; -}; - -class TNodeCallBack { -public: - virtual ~TNodeCallBack() {} - virtual void operator()(TNode n) = 0; -}; - -class NodeCallBack { -public: - virtual ~NodeCallBack() {} - virtual void operator()(Node n) = 0; -}; - -class RationalCallBack { -public: - virtual ~RationalCallBack() {} - virtual Rational operator()() const = 0; -}; - -class SetupLiteralCallBack : public TNodeCallBack { -private: - TheoryArithPrivate& d_arith; -public: - SetupLiteralCallBack(TheoryArithPrivate& ta); - void operator()(TNode lit) override; -}; - -class DeltaComputeCallback : public RationalCallBack { -private: - const TheoryArithPrivate& d_ta; -public: - DeltaComputeCallback(const TheoryArithPrivate& ta); - Rational operator()() const override; -}; - -class BasicVarModelUpdateCallBack : public ArithVarCallBack{ -private: - TheoryArithPrivate& d_ta; -public: - BasicVarModelUpdateCallBack(TheoryArithPrivate& ta); - void operator()(ArithVar x) override; -}; - -class TempVarMalloc : public ArithVarMalloc { -private: - TheoryArithPrivate& d_ta; -public: - TempVarMalloc(TheoryArithPrivate& ta); - ArithVar request() override; - void release(ArithVar v) override; -}; - -class RaiseConflict { -private: - TheoryArithPrivate& d_ta; -public: - RaiseConflict(TheoryArithPrivate& ta); - - /** Calls d_ta.raiseConflict(c) */ - void raiseConflict(ConstraintCP c, InferenceId id) const; -}; - -class FarkasConflictBuilder { -private: - RationalVector d_farkas; - ConstraintCPVec d_constraints; - ConstraintCP d_consequent; - bool d_consequentSet; - bool d_produceProofs; - - public: - - /** - * Constructs a new FarkasConflictBuilder. - */ - FarkasConflictBuilder(bool produceProofs); - - /** - * Adds an antecedent constraint to the conflict under construction - * with the farkas coefficient fc * mult. - * - * The value mult is either 1 or -1. - */ - void addConstraint(ConstraintCP c, const Rational& fc, const Rational& mult); - - /** - * Adds an antecedent constraint to the conflict under construction - * with the farkas coefficient fc. - */ - void addConstraint(ConstraintCP c, const Rational& fc); - - /** - * Makes the last constraint added the consequent. - * Can be done exactly once per reset(). - */ - void makeLastConsequent(); - - /** - * Turns the antecendents into a proof of the negation of one of the - * antecedents. - * - * The buffer is no longer underConstruction afterwards. - * - * precondition: - * - At least two constraints have been asserted. - * - makeLastConsequent() has been called. - * - * postcondition: The returned constraint is in conflict. - */ - ConstraintCP commitConflict(); - - /** Returns true if a conflict has been pushed back since the last reset. */ - bool underConstruction() const; - - /** Returns true if the consequent has been set since the last reset. */ - bool consequentIsSet() const; - - /** Resets the state of the buffer. */ - void reset(); -}; - - -class RaiseEqualityEngineConflict { -private: - TheoryArithPrivate& d_ta; - -public: - RaiseEqualityEngineConflict(TheoryArithPrivate& ta); - - /* If you are not an equality engine, don't use this! - * - * The proof should prove that `n` is a conflict. - * */ - void raiseEEConflict(Node n, std::shared_ptr pf) const; -}; - -class BoundCountingLookup { -private: - TheoryArithPrivate& d_ta; -public: - BoundCountingLookup(TheoryArithPrivate& ta); - const BoundsInfo& boundsInfo(ArithVar basic) const; - BoundCounts atBounds(ArithVar basic) const; - BoundCounts hasBounds(ArithVar basic) const; -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/congruence_manager.cpp b/src/theory/arith/congruence_manager.cpp deleted file mode 100644 index a9a096200..000000000 --- a/src/theory/arith/congruence_manager.cpp +++ /dev/null @@ -1,715 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Alex Ozdemir, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/congruence_manager.h" - -#include "base/output.h" -#include "options/arith_options.h" -#include "proof/proof_node.h" -#include "proof/proof_node_manager.h" -#include "smt/env.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/constraint.h" -#include "theory/arith/partial_model.h" -#include "theory/ee_setup_info.h" -#include "theory/rewriter.h" -#include "theory/uf/equality_engine.h" -#include "theory/uf/proof_equality_engine.h" - -using namespace cvc5::internal::kind; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -ArithCongruenceManager::ArithCongruenceManager( - Env& env, - ConstraintDatabase& cd, - SetupLiteralCallBack setup, - const ArithVariables& avars, - RaiseEqualityEngineConflict raiseConflict) - : EnvObj(env), - d_inConflict(context()), - d_raiseConflict(raiseConflict), - d_notify(*this), - d_keepAlive(context()), - d_propagatations(context()), - d_explanationMap(context()), - d_constraintDatabase(cd), - d_setupLiteral(setup), - d_avariables(avars), - d_ee(nullptr), - d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager() - : nullptr), - // Construct d_pfGenEe with the SAT context, since its proof include - // unclosed assumptions of theory literals. - d_pfGenEe(new EagerProofGenerator( - d_pnm, context(), "ArithCongruenceManager::pfGenEe")), - // Construct d_pfGenEe with the USER context, since its proofs are closed. - d_pfGenExplain(new EagerProofGenerator( - d_pnm, userContext(), "ArithCongruenceManager::pfGenExplain")), - d_pfee(nullptr) -{ -} - -ArithCongruenceManager::~ArithCongruenceManager() {} - -bool ArithCongruenceManager::needsEqualityEngine(EeSetupInfo& esi) -{ - Assert(!options().arith.arithEqSolver); - esi.d_notify = &d_notify; - esi.d_name = "arithCong::ee"; - return true; -} - -void ArithCongruenceManager::finishInit(eq::EqualityEngine* ee) -{ - if (options().arith.arithEqSolver) - { - // use our own copy - d_allocEe = std::make_unique( - d_env, context(), d_notify, "arithCong::ee", true); - d_ee = d_allocEe.get(); - if (d_pnm != nullptr) - { - // allocate an internal proof equality engine - d_allocPfee = std::make_unique(d_env, *d_ee); - d_ee->setProofEqualityEngine(d_allocPfee.get()); - } - } - else - { - Assert(ee != nullptr); - // otherwise, we use the official one - d_ee = ee; - } - // set the congruence kinds on the separate equality engine - d_ee->addFunctionKind(kind::NONLINEAR_MULT); - d_ee->addFunctionKind(kind::EXPONENTIAL); - d_ee->addFunctionKind(kind::SINE); - d_ee->addFunctionKind(kind::IAND); - d_ee->addFunctionKind(kind::POW2); - // the proof equality engine is the one from the equality engine - d_pfee = d_ee->getProofEqualityEngine(); - // have proof equality engine only if proofs are enabled - Assert(isProofEnabled() == (d_pfee != nullptr)); -} - -ArithCongruenceManager::Statistics::Statistics() - : d_watchedVariables(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::watchedVariables")), - d_watchedVariableIsZero(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::watchedVariableIsZero")), - d_watchedVariableIsNotZero(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::watchedVariableIsNotZero")), - d_equalsConstantCalls(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::equalsConstantCalls")), - d_propagations(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::propagations")), - d_propagateConstraints(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::propagateConstraints")), - d_conflicts(smtStatisticsRegistry().registerInt( - "theory::arith::congruence::conflicts")) -{ -} - -ArithCongruenceManager::ArithCongruenceNotify::ArithCongruenceNotify(ArithCongruenceManager& acm) - : d_acm(acm) -{} - -bool ArithCongruenceManager::ArithCongruenceNotify::eqNotifyTriggerPredicate( - TNode predicate, bool value) -{ - Assert(predicate.getKind() == kind::EQUAL); - Trace("arith::congruences") - << "ArithCongruenceNotify::eqNotifyTriggerPredicate(" << predicate << ", " - << (value ? "true" : "false") << ")" << std::endl; - if (value) { - return d_acm.propagate(predicate); - } - return d_acm.propagate(predicate.notNode()); -} - -bool ArithCongruenceManager::ArithCongruenceNotify::eqNotifyTriggerTermEquality(TheoryId tag, TNode t1, TNode t2, bool value) { - Trace("arith::congruences") << "ArithCongruenceNotify::eqNotifyTriggerTermEquality(" << t1 << ", " << t2 << ", " << (value ? "true" : "false") << ")" << std::endl; - if (value) { - return d_acm.propagate(t1.eqNode(t2)); - } else { - return d_acm.propagate(t1.eqNode(t2).notNode()); - } -} -void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyConstantTermMerge(TNode t1, TNode t2) { - Trace("arith::congruences") << "ArithCongruenceNotify::eqNotifyConstantTermMerge(" << t1 << ", " << t2 << std::endl; - d_acm.propagate(t1.eqNode(t2)); -} -void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyNewClass(TNode t) { -} -void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyMerge(TNode t1, - TNode t2) -{ -} -void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyDisequal(TNode t1, TNode t2, TNode reason) { -} - -void ArithCongruenceManager::raiseConflict(Node conflict, - std::shared_ptr pf) -{ - Assert(!inConflict()); - Trace("arith::conflict") << "difference manager conflict " << conflict << std::endl; - d_inConflict.raise(); - d_raiseConflict.raiseEEConflict(conflict, pf); -} -bool ArithCongruenceManager::inConflict() const{ - return d_inConflict.isRaised(); -} - -bool ArithCongruenceManager::hasMorePropagations() const { - return !d_propagatations.empty(); -} -const Node ArithCongruenceManager::getNextPropagation() { - Assert(hasMorePropagations()); - Node prop = d_propagatations.front(); - d_propagatations.dequeue(); - return prop; -} - -bool ArithCongruenceManager::canExplain(TNode n) const { - return d_explanationMap.find(n) != d_explanationMap.end(); -} - -Node ArithCongruenceManager::externalToInternal(TNode n) const{ - Assert(canExplain(n)); - ExplainMap::const_iterator iter = d_explanationMap.find(n); - size_t pos = (*iter).second; - return d_propagatations[pos]; -} - -void ArithCongruenceManager::pushBack(TNode n){ - d_explanationMap.insert(n, d_propagatations.size()); - d_propagatations.enqueue(n); - - ++(d_statistics.d_propagations); -} -void ArithCongruenceManager::pushBack(TNode n, TNode r){ - d_explanationMap.insert(r, d_propagatations.size()); - d_explanationMap.insert(n, d_propagatations.size()); - d_propagatations.enqueue(n); - - ++(d_statistics.d_propagations); -} -void ArithCongruenceManager::pushBack(TNode n, TNode r, TNode w){ - d_explanationMap.insert(w, d_propagatations.size()); - d_explanationMap.insert(r, d_propagatations.size()); - d_explanationMap.insert(n, d_propagatations.size()); - d_propagatations.enqueue(n); - - ++(d_statistics.d_propagations); -} - -void ArithCongruenceManager::watchedVariableIsZero(ConstraintCP lb, ConstraintCP ub){ - Assert(lb->isLowerBound()); - Assert(ub->isUpperBound()); - Assert(lb->getVariable() == ub->getVariable()); - Assert(lb->getValue().sgn() == 0); - Assert(ub->getValue().sgn() == 0); - - ++(d_statistics.d_watchedVariableIsZero); - - ArithVar s = lb->getVariable(); - TNode eq = d_watchedEqualities[s]; - ConstraintCP eqC = d_constraintDatabase.getConstraint( - s, ConstraintType::Equality, lb->getValue()); - NodeBuilder reasonBuilder(Kind::AND); - auto pfLb = lb->externalExplainByAssertions(reasonBuilder); - auto pfUb = ub->externalExplainByAssertions(reasonBuilder); - Node reason = mkAndFromBuilder(reasonBuilder); - std::shared_ptr pf{}; - if (isProofEnabled()) - { - pf = d_pnm->mkNode( - PfRule::ARITH_TRICHOTOMY, {pfLb, pfUb}, {eqC->getProofLiteral()}); - pf = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {eq}); - } - - d_keepAlive.push_back(reason); - Trace("arith-ee") << "Asserting an equality on " << s << ", on trichotomy" - << std::endl; - Trace("arith-ee") << " based on " << lb << std::endl; - Trace("arith-ee") << " based on " << ub << std::endl; - assertionToEqualityEngine(true, s, reason, pf); -} - -void ArithCongruenceManager::watchedVariableIsZero(ConstraintCP eq){ - Trace("arith::cong") << "Cong::watchedVariableIsZero: " << *eq << std::endl; - - Assert(eq->isEquality()); - Assert(eq->getValue().sgn() == 0); - - ++(d_statistics.d_watchedVariableIsZero); - - ArithVar s = eq->getVariable(); - - //Explain for conflict is correct as these proofs are generated - //and stored eagerly - //These will be safe for propagation later as well - NodeBuilder nb(Kind::AND); - // An open proof of eq from literals now in reason. - if (TraceIsOn("arith::cong")) - { - eq->printProofTree(Trace("arith::cong")); - } - auto pf = eq->externalExplainByAssertions(nb); - if (isProofEnabled()) - { - pf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {d_watchedEqualities[s]}); - } - Node reason = mkAndFromBuilder(nb); - - d_keepAlive.push_back(reason); - assertionToEqualityEngine(true, s, reason, pf); -} - -void ArithCongruenceManager::watchedVariableCannotBeZero(ConstraintCP c){ - Trace("arith::cong::notzero") - << "Cong::watchedVariableCannotBeZero " << *c << std::endl; - ++(d_statistics.d_watchedVariableIsNotZero); - - ArithVar s = c->getVariable(); - Node disEq = d_watchedEqualities[s].negate(); - - //Explain for conflict is correct as these proofs are generated and stored eagerly - //These will be safe for propagation later as well - NodeBuilder nb(Kind::AND); - // An open proof of eq from literals now in reason. - auto pf = c->externalExplainByAssertions(nb); - if (TraceIsOn("arith::cong::notzero")) - { - Trace("arith::cong::notzero") << " original proof "; - pf->printDebug(Trace("arith::cong::notzero")); - Trace("arith::cong::notzero") << std::endl; - } - Node reason = mkAndFromBuilder(nb); - if (isProofEnabled()) - { - if (c->getType() == ConstraintType::Disequality) - { - Assert(c->getLiteral() == d_watchedEqualities[s].negate()); - // We have to prove equivalence to the watched disequality. - pf = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {disEq}); - } - else - { - Trace("arith::cong::notzero") - << " proof modification needed" << std::endl; - - // Four cases: - // c has form x_i = d, d > 0 => multiply c by -1 in Farkas proof - // c has form x_i = d, d > 0 => multiply c by 1 in Farkas proof - // c has form x_i <= d, d < 0 => multiply c by 1 in Farkas proof - // c has form x_i >= d, d > 0 => multiply c by -1 in Farkas proof - const bool scaleCNegatively = c->getType() == ConstraintType::LowerBound - || (c->getType() == ConstraintType::Equality - && c->getValue().sgn() > 0); - const int cSign = scaleCNegatively ? -1 : 1; - TNode isZero = d_watchedEqualities[s]; - TypeNode type = isZero[0].getType(); - const auto isZeroPf = d_pnm->mkAssume(isZero); - const auto nm = NodeManager::currentNM(); - const auto sumPf = - d_pnm->mkNode(PfRule::MACRO_ARITH_SCALE_SUM_UB, - {isZeroPf, pf}, - // Trick for getting correct, opposing signs. - {nm->mkConstRealOrInt(type, Rational(-1 * cSign)), - nm->mkConstRealOrInt(type, Rational(cSign))}); - const auto botPf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); - std::vector assumption = {isZero}; - pf = d_pnm->mkScope(botPf, assumption, false); - Trace("arith::cong::notzero") << " new proof "; - pf->printDebug(Trace("arith::cong::notzero")); - Trace("arith::cong::notzero") << std::endl; - } - Assert(pf->getResult() == disEq); - } - d_keepAlive.push_back(reason); - assertionToEqualityEngine(false, s, reason, pf); -} - - -bool ArithCongruenceManager::propagate(TNode x){ - Trace("arith::congruenceManager")<< "ArithCongruenceManager::propagate("<()){ - return true; - }else{ - // x rewrites to false. - ++(d_statistics.d_conflicts); - TrustNode trn = explainInternal(x); - Node conf = flattenAnd(trn.getNode()); - Trace("arith::congruenceManager") << "rewritten to false "<getProofFor(trn.getProven()); - auto confPf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {conf.negate()}); - raiseConflict(conf, confPf); - } - else - { - raiseConflict(conf); - } - return false; - } - } - - Assert(rewritten.getKind() != kind::CONST_BOOLEAN); - - ConstraintP c = d_constraintDatabase.lookup(rewritten); - if(c == NullConstraint){ - //using setup as there may not be a corresponding congruence literal yet - d_setupLiteral(rewritten); - c = d_constraintDatabase.lookup(rewritten); - Assert(c != NullConstraint); - } - - Trace("arith::congruenceManager")<< "x is " - << c->hasProof() << " " - << (x == rewritten) << " " - << c->canBePropagated() << " " - << c->negationHasProof() << std::endl; - - if(c->negationHasProof()){ - TrustNode texpC = explainInternal(x); - Node expC = texpC.getNode(); - ConstraintCP negC = c->getNegation(); - Node neg = Constraint::externalExplainByAssertions({negC}); - Node conf = expC.andNode(neg); - Node final = flattenAnd(conf); - - ++(d_statistics.d_conflicts); - raiseConflict(final); - Trace("arith::congruenceManager") << "congruenceManager found a conflict " << final << std::endl; - return false; - } - - // Cases for propagation - // C : c has a proof - // S : x == rewritten - // P : c can be propagated - // - // CSP - // 000 : propagate x, and mark C it as being explained - // 001 : propagate x, and propagate c after marking it as being explained - // 01* : propagate x, mark c but do not propagate c - // 10* : propagate x, do not mark c and do not propagate c - // 11* : drop the constraint, do not propagate x or c - - if(!c->hasProof() && x != rewritten){ - if(c->assertedToTheTheory()){ - pushBack(x, rewritten, c->getWitness()); - }else{ - pushBack(x, rewritten); - } - - c->setEqualityEngineProof(); - if(c->canBePropagated() && !c->assertedToTheTheory()){ - - ++(d_statistics.d_propagateConstraints); - c->propagate(); - } - }else if(!c->hasProof() && x == rewritten){ - if(c->assertedToTheTheory()){ - pushBack(x, c->getWitness()); - }else{ - pushBack(x); - } - c->setEqualityEngineProof(); - }else if(c->hasProof() && x != rewritten){ - if(c->assertedToTheTheory()){ - pushBack(x); - }else{ - pushBack(x); - } - }else{ - Assert(c->hasProof() && x == rewritten); - } - return true; -} - -void ArithCongruenceManager::explain(TNode literal, std::vector& assumptions) { - if (literal.getKind() != kind::NOT) { - d_ee->explainEquality(literal[0], literal[1], true, assumptions); - } else { - d_ee->explainEquality(literal[0][0], literal[0][1], false, assumptions); - } -} - -void ArithCongruenceManager::enqueueIntoNB(const std::set s, - NodeBuilder& nb) -{ - std::set::const_iterator it = s.begin(); - std::set::const_iterator it_end = s.end(); - for(; it != it_end; ++it) { - nb << *it; - } -} - -TrustNode ArithCongruenceManager::explainInternal(TNode internal) -{ - if (isProofEnabled()) - { - return d_pfee->explain(internal); - } - // otherwise, explain without proof generator - Node exp = d_ee->mkExplainLit(internal); - return TrustNode::mkTrustPropExp(internal, exp, nullptr); -} - -TrustNode ArithCongruenceManager::explain(TNode external) -{ - Trace("arith-ee") << "Ask for explanation of " << external << std::endl; - Node internal = externalToInternal(external); - Trace("arith-ee") << "...internal = " << internal << std::endl; - TrustNode trn = explainInternal(internal); - if (isProofEnabled() && trn.getProven()[1] != external) - { - Assert(trn.getKind() == TrustNodeKind::PROP_EXP); - Assert(trn.getProven().getKind() == Kind::IMPLIES); - Assert(trn.getGenerator() != nullptr); - Trace("arith-ee") << "tweaking proof to prove " << external << " not " - << trn.getProven()[1] << std::endl; - std::vector> assumptionPfs; - std::vector assumptions = andComponents(trn.getNode()); - assumptionPfs.push_back(trn.toProofNode()); - for (const auto& a : assumptions) - { - assumptionPfs.push_back( - d_pnm->mkNode(PfRule::TRUE_INTRO, {d_pnm->mkAssume(a)}, {})); - } - auto litPf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {assumptionPfs}, {external}); - auto extPf = d_pnm->mkScope(litPf, assumptions); - return d_pfGenExplain->mkTrustedPropagation(external, trn.getNode(), extPf); - } - return trn; -} - -void ArithCongruenceManager::explain(TNode external, NodeBuilder& out) -{ - Node internal = externalToInternal(external); - - std::vector assumptions; - explain(internal, assumptions); - std::set assumptionSet; - assumptionSet.insert(assumptions.begin(), assumptions.end()); - - enqueueIntoNB(assumptionSet, out); -} - -void ArithCongruenceManager::addWatchedPair(ArithVar s, TNode x, TNode y){ - Assert(!isWatchedVariable(s)); - - Trace("arith::congruenceManager") - << "addWatchedPair(" << s << ", " << x << ", " << y << ")" << std::endl; - - - ++(d_statistics.d_watchedVariables); - - d_watchedVariables.add(s); - - Node eq = x.eqNode(y); - d_watchedEqualities.set(s, eq); -} - -void ArithCongruenceManager::assertLitToEqualityEngine( - Node lit, TNode reason, std::shared_ptr pf) -{ - bool isEquality = lit.getKind() != Kind::NOT; - Node eq = isEquality ? lit : lit[0]; - Assert(eq.getKind() == Kind::EQUAL); - - Trace("arith-ee") << "Assert to Eq " << lit << ", reason " << reason - << std::endl; - if (isProofEnabled()) - { - if (CDProof::isSame(lit, reason)) - { - Trace("arith-pfee") << "Asserting only, b/c implied by symm" << std::endl; - // The equality engine doesn't ref-count for us... - d_keepAlive.push_back(eq); - d_keepAlive.push_back(reason); - d_ee->assertEquality(eq, isEquality, reason); - } - else if (hasProofFor(lit)) - { - Trace("arith-pfee") << "Skipping b/c already done" << std::endl; - } - else - { - setProofFor(lit, pf); - Trace("arith-pfee") << "Actually asserting" << std::endl; - if (TraceIsOn("arith-pfee")) - { - Trace("arith-pfee") << "Proof: "; - pf->printDebug(Trace("arith-pfee")); - Trace("arith-pfee") << std::endl; - } - // The proof equality engine *does* ref-count for us... - d_pfee->assertFact(lit, reason, d_pfGenEe.get()); - } - } - else - { - // The equality engine doesn't ref-count for us... - d_keepAlive.push_back(eq); - d_keepAlive.push_back(reason); - d_ee->assertEquality(eq, isEquality, reason); - } -} - -void ArithCongruenceManager::assertionToEqualityEngine( - bool isEquality, ArithVar s, TNode reason, std::shared_ptr pf) -{ - Assert(isWatchedVariable(s)); - - TNode eq = d_watchedEqualities[s]; - Assert(eq.getKind() == kind::EQUAL); - - Node lit = isEquality ? Node(eq) : eq.notNode(); - Trace("arith-ee") << "Assert to Eq " << eq << ", pol " << isEquality - << ", reason " << reason << std::endl; - assertLitToEqualityEngine(lit, reason, pf); -} - -bool ArithCongruenceManager::hasProofFor(TNode f) const -{ - Assert(isProofEnabled()); - if (d_pfGenEe->hasProofFor(f)) - { - return true; - } - Node sym = CDProof::getSymmFact(f); - Assert(!sym.isNull()); - return d_pfGenEe->hasProofFor(sym); -} - -void ArithCongruenceManager::setProofFor(TNode f, - std::shared_ptr pf) const -{ - Assert(!hasProofFor(f)); - d_pfGenEe->mkTrustNode(f, pf); - Node symF = CDProof::getSymmFact(f); - auto symPf = d_pnm->mkNode(PfRule::SYMM, {pf}, {}); - d_pfGenEe->mkTrustNode(symF, symPf); -} - -void ArithCongruenceManager::equalsConstant(ConstraintCP c){ - Assert(c->isEquality()); - - ++(d_statistics.d_equalsConstantCalls); - Trace("equalsConstant") << "equals constant " << c << std::endl; - - ArithVar x = c->getVariable(); - Node xAsNode = d_avariables.asNode(x); - NodeManager* nm = NodeManager::currentNM(); - Node asRational = nm->mkConstRealOrInt( - xAsNode.getType(), c->getValue().getNoninfinitesimalPart()); - - // No guarentee this is in normal form! - // Note though, that it happens to be in proof normal form! - Node eq = xAsNode.eqNode(asRational); - d_keepAlive.push_back(eq); - - NodeBuilder nb(Kind::AND); - auto pf = c->externalExplainByAssertions(nb); - Node reason = mkAndFromBuilder(nb); - d_keepAlive.push_back(reason); - - Trace("arith-ee") << "Assert equalsConstant " << eq << ", reason " << reason << std::endl; - assertLitToEqualityEngine(eq, reason, pf); -} - -void ArithCongruenceManager::equalsConstant(ConstraintCP lb, ConstraintCP ub){ - Assert(lb->isLowerBound()); - Assert(ub->isUpperBound()); - Assert(lb->getVariable() == ub->getVariable()); - - ++(d_statistics.d_equalsConstantCalls); - Trace("equalsConstant") << "equals constant " << lb << std::endl - << ub << std::endl; - - ArithVar x = lb->getVariable(); - NodeBuilder nb(Kind::AND); - auto pfLb = lb->externalExplainByAssertions(nb); - auto pfUb = ub->externalExplainByAssertions(nb); - Node reason = mkAndFromBuilder(nb); - - Node xAsNode = d_avariables.asNode(x); - NodeManager* nm = NodeManager::currentNM(); - Node asRational = nm->mkConstRealOrInt( - xAsNode.getType(), lb->getValue().getNoninfinitesimalPart()); - - // No guarentee this is in normal form! - // Note though, that it happens to be in proof normal form! - Node eq = xAsNode.eqNode(asRational); - std::shared_ptr pf; - if (isProofEnabled()) - { - pf = d_pnm->mkNode(PfRule::ARITH_TRICHOTOMY, {pfLb, pfUb}, {eq}); - } - d_keepAlive.push_back(eq); - d_keepAlive.push_back(reason); - - Trace("arith-ee") << "Assert equalsConstant2 " << eq << ", reason " << reason << std::endl; - - assertLitToEqualityEngine(eq, reason, pf); -} - -bool ArithCongruenceManager::isProofEnabled() const { return d_pnm != nullptr; } - -std::vector andComponents(TNode an) -{ - auto nm = NodeManager::currentNM(); - if (an == nm->mkConst(true)) - { - return {}; - } - else if (an.getKind() != Kind::AND) - { - return {an}; - } - std::vector a{}; - a.reserve(an.getNumChildren()); - a.insert(a.end(), an.begin(), an.end()); - return a; -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/congruence_manager.h b/src/theory/arith/congruence_manager.h deleted file mode 100644 index 1cacb9431..000000000 --- a/src/theory/arith/congruence_manager.h +++ /dev/null @@ -1,300 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Alex Ozdemir, Tim King, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include "context/cdhashmap.h" -#include "context/cdlist.h" -#include "context/cdmaybe.h" -#include "context/cdtrail_queue.h" -#include "proof/trust_node.h" -#include "smt/env_obj.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/callbacks.h" -#include "theory/arith/constraint_forward.h" -#include "theory/uf/equality_engine_notify.h" -#include "util/dense_map.h" -#include "util/statistics_stats.h" - -namespace cvc5::context { -class Context; -class UserContext; -} // namespace cvc5::context - -namespace cvc5::internal { - -class ProofNodeManager; -class EagerProofGenerator; - -namespace theory { -struct EeSetupInfo; - -namespace eq { -class ProofEqEngine; -class EqualityEngine; -} - -namespace arith { - -class ArithVariables; - -class ArithCongruenceManager : protected EnvObj -{ - private: - context::CDRaised d_inConflict; - RaiseEqualityEngineConflict d_raiseConflict; - - /** - * The set of ArithVars equivalent to a pair of terms. - * If this is 0 or cannot be 0, this can be signalled. - * The pair of terms for the congruence is stored in watched equalities. - */ - DenseSet d_watchedVariables; - /** d_watchedVariables |-> (= x y) */ - ArithVarToNodeMap d_watchedEqualities; - - - class ArithCongruenceNotify : public eq::EqualityEngineNotify { - private: - ArithCongruenceManager& d_acm; - public: - ArithCongruenceNotify(ArithCongruenceManager& acm); - - bool eqNotifyTriggerPredicate(TNode predicate, bool value) override; - - bool eqNotifyTriggerTermEquality(TheoryId tag, - TNode t1, - TNode t2, - bool value) override; - - void eqNotifyConstantTermMerge(TNode t1, TNode t2) override; - void eqNotifyNewClass(TNode t) override; - void eqNotifyMerge(TNode t1, TNode t2) override; - void eqNotifyDisequal(TNode t1, TNode t2, TNode reason) override; - }; - ArithCongruenceNotify d_notify; - - context::CDList d_keepAlive; - - /** Store the propagations. */ - context::CDTrailQueue d_propagatations; - - /* This maps the node a theory engine will request on an explain call to - * to its corresponding PropUnit. - * This is node is potentially both the propagation or - * rewrite(propagation). - */ - typedef context::CDHashMap ExplainMap; - ExplainMap d_explanationMap; - - ConstraintDatabase& d_constraintDatabase; - SetupLiteralCallBack d_setupLiteral; - - const ArithVariables& d_avariables; - - /** The equality engine being used by this class */ - eq::EqualityEngine* d_ee; - /** The equality engine we allocated */ - std::unique_ptr d_allocEe; - /** proof manager */ - ProofNodeManager* d_pnm; - /** A proof generator for storing proofs of facts that are asserted to the EQ - * engine. Note that these proofs **are not closed**; they may contain - * literals from the explanation of their fact as unclosed assumptions. - * This makes these proofs SAT-context depdent. - * - * This is why this generator is separate from the TheoryArithPrivate - * generator, which stores closed proofs. - */ - std::unique_ptr d_pfGenEe; - /** A proof generator for TrustNodes sent to TheoryArithPrivate. - * - * When TheoryArithPrivate requests an explanation from - * ArithCongruenceManager, it can request a TrustNode for that explanation. - * This proof generator is the one used in that TrustNode: it stores the - * (closed) proofs of implications proved by the - * ArithCongruenceManager/EqualityEngine. - * - * It is insufficient to just use the ProofGenerator from the ProofEqEngine, - * since sometimes the ArithCongruenceManager needs to add some - * arith-specific reasoning to extend the proof (e.g. rewriting the result - * into a normal form). - * */ - std::unique_ptr d_pfGenExplain; - - /** Pointer to the proof equality engine of TheoryArith */ - theory::eq::ProofEqEngine* d_pfee; - /** The proof equality engine we allocated */ - std::unique_ptr d_allocPfee; - - /** Raise a conflict node `conflict` to the theory of arithmetic. - * - * When proofs are enabled, a (closed) proof of the conflict should be - * provided. - */ - void raiseConflict(Node conflict, std::shared_ptr pf = nullptr); - /** - * Are proofs enabled? This is true if a non-null proof manager was provided - * to the constructor of this class. - */ - bool isProofEnabled() const; - - public: - bool inConflict() const; - - bool hasMorePropagations() const; - - const Node getNextPropagation(); - - bool canExplain(TNode n) const; - -private: - Node externalToInternal(TNode n) const; - - void pushBack(TNode n); - - void pushBack(TNode n, TNode r); - - void pushBack(TNode n, TNode r, TNode w); - - bool propagate(TNode x); - void explain(TNode literal, std::vector& assumptions); - - /** Assert this literal to the eq engine. Common functionality for - * * assertionToEqualityEngine(..) - * * equalsConstant(c) - * * equalsConstant(lb, ub) - * If proof is off, then just asserts. - */ - void assertLitToEqualityEngine(Node lit, - TNode reason, - std::shared_ptr pf); - /** This sends a shared term to the uninterpreted equality engine. */ - void assertionToEqualityEngine(bool eq, - ArithVar s, - TNode reason, - std::shared_ptr pf); - - /** Check for proof for this or a symmetric fact - * - * The proof submitted to this method are stored in `d_pfGenEe`, and should - * have closure properties consistent with the documentation for that member. - * - * @returns whether this or a symmetric fact has a proof. - */ - bool hasProofFor(TNode f) const; - /** - * Sets the proof for this fact and the symmetric one. - * - * The proof submitted to this method are stored in `d_pfGenEe`, and should - * have closure properties consistent with the documentation for that member. - * */ - void setProofFor(TNode f, std::shared_ptr pf) const; - - /** Dequeues the delay queue and asserts these equalities.*/ - void enableSharedTerms(); - void dequeueLiterals(); - - void enqueueIntoNB(const std::set all, NodeBuilder& nb); - - /** - * Determine an explaination for `internal`. That is a conjunction of theory - * literals which imply `internal`. - * - * The TrustNode here is a trusted propagation. - */ - TrustNode explainInternal(TNode internal); - - public: - ArithCongruenceManager(Env& env, - ConstraintDatabase&, - SetupLiteralCallBack, - const ArithVariables&, - RaiseEqualityEngineConflict raiseConflict); - ~ArithCongruenceManager(); - - //--------------------------------- initialization - /** - * Returns true if we need an equality engine, see - * Theory::needsEqualityEngine. - */ - bool needsEqualityEngine(EeSetupInfo& esi); - /** - * Finish initialize. This class is instructed by TheoryArithPrivate to use - * the equality engine ee. - */ - void finishInit(eq::EqualityEngine* ee); - //--------------------------------- end initialization - - /** - * Return the trust node for the explanation of literal. The returned - * trust node is generated by the proof equality engine of this class. - */ - TrustNode explain(TNode literal); - - void explain(TNode lit, NodeBuilder& out); - - void addWatchedPair(ArithVar s, TNode x, TNode y); - - inline bool isWatchedVariable(ArithVar s) const { - return d_watchedVariables.isMember(s); - } - - /** Assert an equality. */ - void watchedVariableIsZero(ConstraintCP eq); - - /** Assert a conjunction from lb and ub. */ - void watchedVariableIsZero(ConstraintCP lb, ConstraintCP ub); - - /** Assert that the value cannot be zero. */ - void watchedVariableCannotBeZero(ConstraintCP c); - - /** Assert that the value cannot be zero. */ - void watchedVariableCannotBeZero(ConstraintCP c, ConstraintCP d); - - - /** Assert that the value is congruent to a constant. */ - void equalsConstant(ConstraintCP eq); - void equalsConstant(ConstraintCP lb, ConstraintCP ub); - - private: - class Statistics { - public: - IntStat d_watchedVariables; - IntStat d_watchedVariableIsZero; - IntStat d_watchedVariableIsNotZero; - - IntStat d_equalsConstantCalls; - - IntStat d_propagations; - IntStat d_propagateConstraints; - IntStat d_conflicts; - - Statistics(); - } d_statistics; - -}; /* class ArithCongruenceManager */ - -std::vector andComponents(TNode an); - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/constraint.cpp b/src/theory/arith/constraint.cpp deleted file mode 100644 index dc1f430c8..000000000 --- a/src/theory/arith/constraint.cpp +++ /dev/null @@ -1,2463 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Alex Ozdemir, Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ -#include "theory/arith/constraint.h" - -#include -#include -#include - -#include "base/output.h" -#include "options/smt_options.h" -#include "proof/eager_proof_generator.h" -#include "proof/proof_node_manager.h" -#include "smt/env.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/congruence_manager.h" -#include "theory/arith/normal_form.h" -#include "theory/arith/partial_model.h" -#include "theory/builtin/proof_checker.h" -#include "theory/rewriter.h" - -using namespace std; -using namespace cvc5::internal::kind; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -ConstraintRule::ConstraintRule() - : d_constraint(NullConstraint), - d_proofType(NoAP), - d_antecedentEnd(AntecedentIdSentinel) -{ - d_farkasCoefficients = RationalVectorCPSentinel; -} - -ConstraintRule::ConstraintRule(ConstraintP con, ArithProofType pt) - : d_constraint(con), d_proofType(pt), d_antecedentEnd(AntecedentIdSentinel) -{ - d_farkasCoefficients = RationalVectorCPSentinel; -} -ConstraintRule::ConstraintRule(ConstraintP con, - ArithProofType pt, - AntecedentId antecedentEnd) - : d_constraint(con), d_proofType(pt), d_antecedentEnd(antecedentEnd) -{ - d_farkasCoefficients = RationalVectorCPSentinel; -} - -ConstraintRule::ConstraintRule(ConstraintP con, - ArithProofType pt, - AntecedentId antecedentEnd, - RationalVectorCP coeffs) - : d_constraint(con), d_proofType(pt), d_antecedentEnd(antecedentEnd) -{ - Assert(con->isProofProducing() || coeffs == RationalVectorCPSentinel); - d_farkasCoefficients = coeffs; -} - -/** Given a simplifiedKind this returns the corresponding ConstraintType. */ -//ConstraintType constraintTypeOfLiteral(Kind k); -ConstraintType Constraint::constraintTypeOfComparison(const Comparison& cmp){ - Kind k = cmp.comparisonKind(); - switch(k){ - case LT: - case LEQ: - { - Polynomial l = cmp.getLeft(); - if(l.leadingCoefficientIsPositive()){ // (< x c) - return UpperBound; - }else{ - return LowerBound; // (< (-x) c) - } - } - case GT: - case GEQ: - { - Polynomial l = cmp.getLeft(); - if(l.leadingCoefficientIsPositive()){ - return LowerBound; // (> x c) - }else{ - return UpperBound; // (> (-x) c) - } - } - case EQUAL: - return Equality; - case DISTINCT: - return Disequality; - default: Unhandled() << k; - } -} - -Constraint::Constraint(ArithVar x, - ConstraintType t, - const DeltaRational& v, - bool produceProofs) - : d_variable(x), - d_type(t), - d_value(v), - d_database(NULL), - d_literal(Node::null()), - d_negation(NullConstraint), - d_canBePropagated(false), - d_assertionOrder(AssertionOrderSentinel), - d_witness(TNode::null()), - d_crid(ConstraintRuleIdSentinel), - d_split(false), - d_variablePosition(), - d_produceProofs(produceProofs) -{ - Assert(!initialized()); -} - - -std::ostream& operator<<(std::ostream& o, const ArithProofType apt){ - switch(apt){ - case NoAP: o << "NoAP"; break; - case AssumeAP: o << "AssumeAP"; break; - case InternalAssumeAP: o << "InternalAssumeAP"; break; - case FarkasAP: o << "FarkasAP"; break; - case TrichotomyAP: o << "TrichotomyAP"; break; - case EqualityEngineAP: o << "EqualityEngineAP"; break; - case IntTightenAP: o << "IntTightenAP"; break; - case IntHoleAP: o << "IntHoleAP"; break; - default: break; - } - return o; -} - -std::ostream& operator<<(std::ostream& o, const ConstraintCP c){ - if(c == NullConstraint){ - return o << "NullConstraint"; - }else{ - return o << *c; - } -} - -std::ostream& operator<<(std::ostream& o, const ConstraintP c){ - if(c == NullConstraint){ - return o << "NullConstraint"; - }else{ - return o << *c; - } -} - -std::ostream& operator<<(std::ostream& o, const ConstraintType t){ - switch(t){ - case LowerBound: - return o << ">="; - case UpperBound: - return o << "<="; - case Equality: - return o << "="; - case Disequality: - return o << "!="; - default: - Unreachable(); - } -} - -std::ostream& operator<<(std::ostream& o, const Constraint& c){ - o << c.getVariable() << ' ' << c.getType() << ' ' << c.getValue(); - if(c.hasLiteral()){ - o << "(node " << c.getLiteral() << ')'; - } - return o; -} - -std::ostream& operator<<(std::ostream& o, const ValueCollection& vc){ - o << "{"; - bool pending = false; - if(vc.hasEquality()){ - o << "eq: " << vc.getEquality(); - pending = true; - } - if(vc.hasLowerBound()){ - if(pending){ - o << ", "; - } - o << "lb: " << vc.getLowerBound(); - pending = true; - } - if(vc.hasUpperBound()){ - if(pending){ - o << ", "; - } - o << "ub: " << vc.getUpperBound(); - pending = true; - } - if(vc.hasDisequality()){ - if(pending){ - o << ", "; - } - o << "de: " << vc.getDisequality(); - } - return o << "}"; -} - -std::ostream& operator<<(std::ostream& o, const ConstraintCPVec& v){ - o << "[" << v.size() << "x"; - ConstraintCPVec::const_iterator i, end; - for(i=v.begin(), end=v.end(); i != end; ++i){ - ConstraintCP c = *i; - o << ", " << (*c); - } - o << "]"; - return o; -} - -ValueCollection::ValueCollection() - : d_lowerBound(NullConstraint), - d_upperBound(NullConstraint), - d_equality(NullConstraint), - d_disequality(NullConstraint) -{} - -bool ValueCollection::hasLowerBound() const{ - return d_lowerBound != NullConstraint; -} - -bool ValueCollection::hasUpperBound() const{ - return d_upperBound != NullConstraint; -} - -bool ValueCollection::hasEquality() const{ - return d_equality != NullConstraint; -} - -bool ValueCollection::hasDisequality() const { - return d_disequality != NullConstraint; -} - -ConstraintP ValueCollection::getLowerBound() const { - Assert(hasLowerBound()); - return d_lowerBound; -} - -ConstraintP ValueCollection::getUpperBound() const { - Assert(hasUpperBound()); - return d_upperBound; -} - -ConstraintP ValueCollection::getEquality() const { - Assert(hasEquality()); - return d_equality; -} - -ConstraintP ValueCollection::getDisequality() const { - Assert(hasDisequality()); - return d_disequality; -} - - -void ValueCollection::push_into(std::vector& vec) const { - Trace("arith::constraint") << "push_into " << *this << endl; - if(hasEquality()){ - vec.push_back(d_equality); - } - if(hasLowerBound()){ - vec.push_back(d_lowerBound); - } - if(hasUpperBound()){ - vec.push_back(d_upperBound); - } - if(hasDisequality()){ - vec.push_back(d_disequality); - } -} - -ValueCollection ValueCollection::mkFromConstraint(ConstraintP c){ - ValueCollection ret; - Assert(ret.empty()); - switch(c->getType()){ - case LowerBound: - ret.d_lowerBound = c; - break; - case UpperBound: - ret.d_upperBound = c; - break; - case Equality: - ret.d_equality = c; - break; - case Disequality: - ret.d_disequality = c; - break; - default: - Unreachable(); - } - return ret; -} - -bool ValueCollection::hasConstraintOfType(ConstraintType t) const{ - switch(t){ - case LowerBound: - return hasLowerBound(); - case UpperBound: - return hasUpperBound(); - case Equality: - return hasEquality(); - case Disequality: - return hasDisequality(); - default: - Unreachable(); - } -} - -ArithVar ValueCollection::getVariable() const{ - Assert(!empty()); - return nonNull()->getVariable(); -} - -const DeltaRational& ValueCollection::getValue() const{ - Assert(!empty()); - return nonNull()->getValue(); -} - -void ValueCollection::add(ConstraintP c){ - Assert(c != NullConstraint); - - Assert(empty() || getVariable() == c->getVariable()); - Assert(empty() || getValue() == c->getValue()); - - switch(c->getType()){ - case LowerBound: - Assert(!hasLowerBound()); - d_lowerBound = c; - break; - case Equality: - Assert(!hasEquality()); - d_equality = c; - break; - case UpperBound: - Assert(!hasUpperBound()); - d_upperBound = c; - break; - case Disequality: - Assert(!hasDisequality()); - d_disequality = c; - break; - default: - Unreachable(); - } -} - -ConstraintP ValueCollection::getConstraintOfType(ConstraintType t) const{ - switch(t){ - case LowerBound: Assert(hasLowerBound()); return d_lowerBound; - case Equality: Assert(hasEquality()); return d_equality; - case UpperBound: Assert(hasUpperBound()); return d_upperBound; - case Disequality: Assert(hasDisequality()); return d_disequality; - default: Unreachable(); - } -} - -void ValueCollection::remove(ConstraintType t){ - switch(t){ - case LowerBound: - Assert(hasLowerBound()); - d_lowerBound = NullConstraint; - break; - case Equality: - Assert(hasEquality()); - d_equality = NullConstraint; - break; - case UpperBound: - Assert(hasUpperBound()); - d_upperBound = NullConstraint; - break; - case Disequality: - Assert(hasDisequality()); - d_disequality = NullConstraint; - break; - default: - Unreachable(); - } -} - -bool ValueCollection::empty() const{ - return - !(hasLowerBound() || - hasUpperBound() || - hasEquality() || - hasDisequality()); -} - -ConstraintP ValueCollection::nonNull() const{ - //This can be optimized by caching, but this is not necessary yet! - /* "Premature optimization is the root of all evil." */ - if(hasLowerBound()){ - return d_lowerBound; - }else if(hasUpperBound()){ - return d_upperBound; - }else if(hasEquality()){ - return d_equality; - }else if(hasDisequality()){ - return d_disequality; - }else{ - return NullConstraint; - } -} - -bool Constraint::initialized() const { - return d_database != NULL; -} - -const ConstraintDatabase& Constraint::getDatabase() const{ - Assert(initialized()); - return *d_database; -} - -void Constraint::initialize(ConstraintDatabase* db, SortedConstraintMapIterator v, ConstraintP negation){ - Assert(!initialized()); - d_database = db; - d_variablePosition = v; - d_negation = negation; -} - -Constraint::~Constraint() { - // Call this instead of safeToGarbageCollect() - Assert(!contextDependentDataIsSet()); - - if(initialized()){ - ValueCollection& vc = d_variablePosition->second; - Trace("arith::constraint") << "removing" << vc << endl; - - vc.remove(getType()); - - if(vc.empty()){ - Trace("arith::constraint") << "erasing" << vc << endl; - SortedConstraintMap& perVariable = d_database->getVariableSCM(getVariable()); - perVariable.erase(d_variablePosition); - } - - if(hasLiteral()){ - d_database->d_nodetoConstraintMap.erase(getLiteral()); - } - } -} - -const ConstraintRule& Constraint::getConstraintRule() const { - Assert(hasProof()); - return d_database->d_watches->d_constraintProofs[d_crid]; -} - -const ValueCollection& Constraint::getValueCollection() const{ - return d_variablePosition->second; -} - - -ConstraintP Constraint::getCeiling() { - Trace("getCeiling") << "Constraint_::getCeiling on " << *this << endl; - Assert(getValue().getInfinitesimalPart().sgn() > 0); - - const DeltaRational ceiling(getValue().ceiling()); - return d_database->getConstraint(getVariable(), getType(), ceiling); -} - -ConstraintP Constraint::getFloor() { - Assert(getValue().getInfinitesimalPart().sgn() < 0); - - const DeltaRational floor(Rational(getValue().floor())); - return d_database->getConstraint(getVariable(), getType(), floor); -} - -void Constraint::setCanBePropagated() { - Assert(!canBePropagated()); - d_database->pushCanBePropagatedWatch(this); -} - -void Constraint::setAssertedToTheTheory(TNode witness, bool nowInConflict) { - Assert(hasLiteral()); - Assert(!assertedToTheTheory()); - Assert(negationHasProof() == nowInConflict); - d_database->pushAssertionOrderWatch(this, witness); - - if(TraceIsOn("constraint::conflictCommit") && nowInConflict ){ - Trace("constraint::conflictCommit") << "inConflict@setAssertedToTheTheory"; - Trace("constraint::conflictCommit") << "\t" << this << std::endl; - Trace("constraint::conflictCommit") << "\t" << getNegation() << std::endl; - Trace("constraint::conflictCommit") << "\t" << getNegation()->externalExplainByAssertions() << std::endl; - - } -} - -bool Constraint::satisfiedBy(const DeltaRational& dr) const { - switch(getType()){ - case LowerBound: - return getValue() <= dr; - case Equality: - return getValue() == dr; - case UpperBound: - return getValue() >= dr; - case Disequality: - return getValue() != dr; - } - Unreachable(); -} - -bool Constraint::isInternalAssumption() const { - return getProofType() == InternalAssumeAP; -} - -TrustNode Constraint::externalExplainByAssertions() const -{ - NodeBuilder nb(kind::AND); - auto pfFromAssumptions = externalExplain(nb, AssertionOrderSentinel); - Node exp = mkAndFromBuilder(nb); - if (d_database->isProofEnabled()) - { - std::vector assumptions; - if (exp.getKind() == Kind::AND) - { - assumptions.insert(assumptions.end(), exp.begin(), exp.end()); - } - else - { - assumptions.push_back(exp); - } - auto pf = d_database->d_pnm->mkScope(pfFromAssumptions, assumptions); - return d_database->d_pfGen->mkTrustedPropagation( - getLiteral(), NodeManager::currentNM()->mkAnd(assumptions), pf); - } - return TrustNode::mkTrustPropExp(getLiteral(), exp); -} - -bool Constraint::isAssumption() const { - return getProofType() == AssumeAP; -} - -bool Constraint::hasEqualityEngineProof() const { - return getProofType() == EqualityEngineAP; -} - -bool Constraint::hasFarkasProof() const { - return getProofType() == FarkasAP; -} - -bool Constraint::hasSimpleFarkasProof() const -{ - Trace("constraints::hsfp") << "hasSimpleFarkasProof " << this << std::endl; - if (!hasFarkasProof()) - { - Trace("constraints::hsfp") << "There is no simple Farkas proof because " - "there is no farkas proof." - << std::endl; - return false; - } - - // For each antecdent ... - AntecedentId i = getConstraintRule().d_antecedentEnd; - for (ConstraintCP a = d_database->getAntecedent(i); a != NullConstraint; - a = d_database->getAntecedent(--i)) - { - // ... that antecdent must be an assumption OR a tightened assumption ... - if (a->isPossiblyTightenedAssumption()) - { - continue; - } - - // ... otherwise, we do not have a simple Farkas proof. - if (TraceIsOn("constraints::hsfp")) - { - Trace("constraints::hsfp") << "There is no simple Farkas proof b/c there " - "is an antecdent w/ rule "; - a->getConstraintRule().print(Trace("constraints::hsfp"), d_produceProofs); - Trace("constraints::hsfp") << std::endl; - } - - return false; - } - return true; -} - -bool Constraint::isPossiblyTightenedAssumption() const -{ - // ... that antecdent must be an assumption ... - - if (isAssumption()) return true; - if (!hasIntTightenProof()) return false; - if (getConstraintRule().d_antecedentEnd == AntecedentIdSentinel) return false; - return d_database->getAntecedent(getConstraintRule().d_antecedentEnd) - ->isAssumption(); -} - -bool Constraint::hasIntTightenProof() const { - return getProofType() == IntTightenAP; -} - -bool Constraint::hasIntHoleProof() const { - return getProofType() == IntHoleAP; -} - -bool Constraint::hasTrichotomyProof() const { - return getProofType() == TrichotomyAP; -} - -void Constraint::printProofTree(std::ostream& out, size_t depth) const -{ - if (d_produceProofs) - { - const ConstraintRule& rule = getConstraintRule(); - out << std::string(2 * depth, ' ') << "* " << getVariable() << " ["; - out << getProofLiteral(); - if (assertedToTheTheory()) - { - out << " | wit: " << getWitness(); - } - out << "]" << ' ' << getType() << ' ' << getValue() << " (" - << getProofType() << ")"; - if (getProofType() == FarkasAP) - { - out << " ["; - bool first = true; - for (const auto& coeff : *rule.d_farkasCoefficients) - { - if (not first) - { - out << ", "; - } - first = false; - out << coeff; - } - out << "]"; - } - out << endl; - - for (AntecedentId i = rule.d_antecedentEnd; i != AntecedentIdSentinel; --i) - { - ConstraintCP antecdent = d_database->getAntecedent(i); - if (antecdent == NullConstraint) - { - break; - } - antecdent->printProofTree(out, depth + 1); - } - return; - } - out << "Cannot print proof. This is not a proof build." << endl; -} - -bool Constraint::sanityChecking(Node n) const { - Comparison cmp = Comparison::parseNormalForm(n); - Kind k = cmp.comparisonKind(); - Polynomial pleft = cmp.normalizedVariablePart(); - Assert(k == EQUAL || k == DISTINCT || pleft.leadingCoefficientIsPositive()); - Assert(k != EQUAL || Monomial::isMember(n[0])); - Assert(k != DISTINCT || Monomial::isMember(n[0][0])); - - TNode left = pleft.getNode(); - DeltaRational right = cmp.normalizedDeltaRational(); - - const ArithVariables& avariables = d_database->getArithVariables(); - - Trace("Constraint::sanityChecking") << cmp.getNode() << endl; - Trace("Constraint::sanityChecking") << k << endl; - Trace("Constraint::sanityChecking") << pleft.getNode() << endl; - Trace("Constraint::sanityChecking") << left << endl; - Trace("Constraint::sanityChecking") << right << endl; - Trace("Constraint::sanityChecking") << getValue() << endl; - Trace("Constraint::sanityChecking") << avariables.hasArithVar(left) << endl; - Trace("Constraint::sanityChecking") << avariables.asArithVar(left) << endl; - Trace("Constraint::sanityChecking") << getVariable() << endl; - - - if(avariables.hasArithVar(left) && - avariables.asArithVar(left) == getVariable() && - getValue() == right){ - switch(getType()){ - case LowerBound: - case UpperBound: - //Be overapproximate - return k == GT || k == GEQ ||k == LT || k == LEQ; - case Equality: - return k == EQUAL; - case Disequality: - return k == DISTINCT; - default: - Unreachable(); - } - }else{ - return false; - } -} - -ConstraintCP ConstraintDatabase::getAntecedent (AntecedentId p) const { - Assert(p < d_antecedents.size()); - return d_antecedents[p]; -} - -void ConstraintRule::print(std::ostream& out, bool produceProofs) const -{ - RationalVectorCP coeffs = produceProofs ? d_farkasCoefficients : nullptr; - out << "{ConstraintRule, "; - out << d_constraint << std::endl; - out << "d_proofType= " << d_proofType << ", " << std::endl; - out << "d_antecedentEnd= "<< d_antecedentEnd << std::endl; - - if (d_constraint != NullConstraint && d_antecedentEnd != AntecedentIdSentinel) - { - const ConstraintDatabase& database = d_constraint->getDatabase(); - - size_t coeffIterator = (coeffs != RationalVectorCPSentinel) ? coeffs->size()-1 : 0; - AntecedentId p = d_antecedentEnd; - // must have at least one antecedent - ConstraintCP antecedent = database.getAntecedent(p); - while(antecedent != NullConstraint){ - if(coeffs != RationalVectorCPSentinel){ - out << coeffs->at(coeffIterator); - } else { - out << "_"; - } - out << " * (" << *antecedent << ")" << std::endl; - - Assert((coeffs == RationalVectorCPSentinel) || coeffIterator > 0); - --p; - coeffIterator = (coeffs != RationalVectorCPSentinel) ? coeffIterator-1 : 0; - antecedent = database.getAntecedent(p); - } - if(coeffs != RationalVectorCPSentinel){ - out << coeffs->front(); - } else { - out << "_"; - } - out << " * (" << *(d_constraint->getNegation()) << ")"; - out << " [not d_constraint] " << endl; - } - out << "}"; -} - -bool Constraint::wellFormedFarkasProof() const { - Assert(hasProof()); - - const ConstraintRule& cr = getConstraintRule(); - if(cr.d_constraint != this){ return false; } - if(cr.d_proofType != FarkasAP){ return false; } - - AntecedentId p = cr.d_antecedentEnd; - - // must have at least one antecedent - ConstraintCP antecedent = d_database->d_antecedents[p]; - if(antecedent == NullConstraint) { return false; } - - if (!d_produceProofs) - { - return cr.d_farkasCoefficients == RationalVectorCPSentinel; - } - Assert(d_produceProofs); - - if(cr.d_farkasCoefficients == RationalVectorCPSentinel){ return false; } - if(cr.d_farkasCoefficients->size() < 2){ return false; } - - const ArithVariables& vars = d_database->getArithVariables(); - - DeltaRational rhs(0); - Node lhs = Polynomial::mkZero().getNode(); - - RationalVector::const_iterator coeffIterator = cr.d_farkasCoefficients->end()-1; - RationalVector::const_iterator coeffBegin = cr.d_farkasCoefficients->begin(); - - while(antecedent != NullConstraint){ - Assert(lhs.isNull() || Polynomial::isMember(lhs)); - - const Rational& coeff = *coeffIterator; - int coeffSgn = coeff.sgn(); - - rhs += antecedent->getValue() * coeff; - - ArithVar antVar = antecedent->getVariable(); - if(!lhs.isNull() && vars.hasNode(antVar)){ - Node antAsNode = vars.asNode(antVar); - if(Polynomial::isMember(antAsNode)){ - Polynomial lhsPoly = Polynomial::parsePolynomial(lhs); - Polynomial antPoly = Polynomial::parsePolynomial(antAsNode); - Polynomial sum = lhsPoly + (antPoly * coeff); - lhs = sum.getNode(); - }else{ - lhs = Node::null(); - } - } else { - lhs = Node::null(); - } - Trace("constraints::wffp") << "running sum: " << lhs << " <= " << rhs << endl; - - switch( antecedent->getType() ){ - case LowerBound: - // fc[l] < 0, therefore return false if coeffSgn >= 0 - if(coeffSgn >= 0){ return false; } - break; - case UpperBound: - // fc[u] > 0, therefore return false if coeffSgn <= 0 - if(coeffSgn <= 0){ return false; } - break; - case Equality: - if(coeffSgn == 0) { return false; } - break; - case Disequality: - default: - return false; - } - - if(coeffIterator == coeffBegin){ return false; } - --coeffIterator; - --p; - antecedent = d_database->d_antecedents[p]; - } - if(coeffIterator != coeffBegin){ return false; } - - const Rational& firstCoeff = (*coeffBegin); - int firstCoeffSgn = firstCoeff.sgn(); - rhs += (getNegation()->getValue()) * firstCoeff; - if(!lhs.isNull() && vars.hasNode(getVariable())){ - Node firstAsNode = vars.asNode(getVariable()); - if(Polynomial::isMember(firstAsNode)){ - Polynomial lhsPoly = Polynomial::parsePolynomial(lhs); - Polynomial firstPoly = Polynomial::parsePolynomial(firstAsNode); - Polynomial sum = lhsPoly + (firstPoly * firstCoeff); - lhs = sum.getNode(); - }else{ - lhs = Node::null(); - } - }else{ - lhs = Node::null(); - } - - switch( getNegation()->getType() ){ - case LowerBound: - // fc[l] < 0, therefore return false if coeffSgn >= 0 - if(firstCoeffSgn >= 0){ return false; } - break; - case UpperBound: - // fc[u] > 0, therefore return false if coeffSgn <= 0 - if(firstCoeffSgn <= 0){ return false; } - break; - case Equality: - if(firstCoeffSgn == 0) { return false; } - break; - case Disequality: - default: - return false; - } - Trace("constraints::wffp") << "final sum: " << lhs << " <= " << rhs << endl; - // 0 = lhs <= rhs < 0 - return (lhs.isNull() || (Constant::isMember(lhs) && Constant(lhs).isZero())) - && rhs.sgn() < 0; -} - -ConstraintP Constraint::makeNegation(ArithVar v, - ConstraintType t, - const DeltaRational& r, - bool produceProofs) -{ - switch(t){ - case LowerBound: - { - Assert(r.infinitesimalSgn() >= 0); - if(r.infinitesimalSgn() > 0){ - Assert(r.getInfinitesimalPart() == 1); - // make (not (v > r)), which is (v <= r) - DeltaRational dropInf(r.getNoninfinitesimalPart(), 0); - return new Constraint(v, UpperBound, dropInf, produceProofs); - }else{ - Assert(r.infinitesimalSgn() == 0); - // make (not (v >= r)), which is (v < r) - DeltaRational addInf(r.getNoninfinitesimalPart(), -1); - return new Constraint(v, UpperBound, addInf, produceProofs); - } - } - case UpperBound: - { - Assert(r.infinitesimalSgn() <= 0); - if(r.infinitesimalSgn() < 0){ - Assert(r.getInfinitesimalPart() == -1); - // make (not (v < r)), which is (v >= r) - DeltaRational dropInf(r.getNoninfinitesimalPart(), 0); - return new Constraint(v, LowerBound, dropInf, produceProofs); - }else{ - Assert(r.infinitesimalSgn() == 0); - // make (not (v <= r)), which is (v > r) - DeltaRational addInf(r.getNoninfinitesimalPart(), 1); - return new Constraint(v, LowerBound, addInf, produceProofs); - } - } - case Equality: return new Constraint(v, Disequality, r, produceProofs); - case Disequality: return new Constraint(v, Equality, r, produceProofs); - default: Unreachable(); return NullConstraint; - } -} - -ConstraintDatabase::ConstraintDatabase(Env& env, - const ArithVariables& avars, - ArithCongruenceManager& cm, - RaiseConflict raiseConflict, - EagerProofGenerator* pfGen) - : EnvObj(env), - d_varDatabases(), - d_toPropagate(context()), - d_antecedents(context(), false), - d_watches(new Watches(context(), userContext())), - d_avariables(avars), - d_congruenceManager(cm), - d_pfGen(pfGen), - d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager() - : nullptr), - d_raiseConflict(raiseConflict), - d_one(1), - d_negOne(-1) -{ -} - -SortedConstraintMap& ConstraintDatabase::getVariableSCM(ArithVar v) const{ - Assert(variableDatabaseIsSetup(v)); - return d_varDatabases[v]->d_constraints; -} - -void ConstraintDatabase::pushSplitWatch(ConstraintP c){ - Assert(!c->d_split); - c->d_split = true; - d_watches->d_splitWatches.push_back(c); -} - - -void ConstraintDatabase::pushCanBePropagatedWatch(ConstraintP c){ - Assert(!c->d_canBePropagated); - c->d_canBePropagated = true; - d_watches->d_canBePropagatedWatches.push_back(c); -} - -void ConstraintDatabase::pushAssertionOrderWatch(ConstraintP c, TNode witness){ - Assert(!c->assertedToTheTheory()); - c->d_assertionOrder = d_watches->d_assertionOrderWatches.size(); - c->d_witness = witness; - d_watches->d_assertionOrderWatches.push_back(c); -} - - -void ConstraintDatabase::pushConstraintRule(const ConstraintRule& crp){ - ConstraintP c = crp.d_constraint; - Assert(c->d_crid == ConstraintRuleIdSentinel); - Assert(!c->hasProof()); - c->d_crid = d_watches->d_constraintProofs.size(); - d_watches->d_constraintProofs.push_back(crp); -} - -ConstraintP ConstraintDatabase::getConstraint(ArithVar v, ConstraintType t, const DeltaRational& r){ - //This must always return a constraint. - - SortedConstraintMap& scm = getVariableSCM(v); - pair insertAttempt; - insertAttempt = scm.insert(make_pair(r, ValueCollection())); - - SortedConstraintMapIterator pos = insertAttempt.first; - ValueCollection& vc = pos->second; - if(vc.hasConstraintOfType(t)){ - return vc.getConstraintOfType(t); - }else{ - ConstraintP c = new Constraint(v, t, r, options().smt.produceProofs); - ConstraintP negC = - Constraint::makeNegation(v, t, r, options().smt.produceProofs); - - SortedConstraintMapIterator negPos; - if(t == Equality || t == Disequality){ - negPos = pos; - }else{ - pair negInsertAttempt; - negInsertAttempt = scm.insert(make_pair(negC->getValue(), ValueCollection())); - Assert(negInsertAttempt.second - || !negInsertAttempt.first->second.hasConstraintOfType( - negC->getType())); - negPos = negInsertAttempt.first; - } - - c->initialize(this, pos, negC); - negC->initialize(this, negPos, c); - - vc.add(c); - negPos->second.add(negC); - - return c; - } -} - -ConstraintP ConstraintDatabase::ensureConstraint(ValueCollection& vc, ConstraintType t){ - if(vc.hasConstraintOfType(t)){ - return vc.getConstraintOfType(t); - }else{ - return getConstraint(vc.getVariable(), t, vc.getValue()); - } -} - -bool ConstraintDatabase::emptyDatabase(const std::vector& vec){ - std::vector::const_iterator first = vec.begin(); - std::vector::const_iterator last = vec.end(); - return std::find_if(first, last, PerVariableDatabase::IsEmpty) == last; -} - -ConstraintDatabase::~ConstraintDatabase(){ - delete d_watches; - - std::vector constraintList; - - while(!d_varDatabases.empty()){ - PerVariableDatabase* back = d_varDatabases.back(); - - SortedConstraintMap& scm = back->d_constraints; - SortedConstraintMapIterator i = scm.begin(), i_end = scm.end(); - for(; i != i_end; ++i){ - (i->second).push_into(constraintList); - } - while(!constraintList.empty()){ - ConstraintP c = constraintList.back(); - constraintList.pop_back(); - delete c; - } - Assert(scm.empty()); - d_varDatabases.pop_back(); - delete back; - } - - Assert(d_nodetoConstraintMap.empty()); -} - -ConstraintDatabase::Statistics::Statistics() - : d_unatePropagateCalls(smtStatisticsRegistry().registerInt( - "theory::arith::cd::unatePropagateCalls")), - d_unatePropagateImplications(smtStatisticsRegistry().registerInt( - "theory::arith::cd::unatePropagateImplications")) -{ -} - -void ConstraintDatabase::deleteConstraintAndNegation(ConstraintP c){ - Assert(c->safeToGarbageCollect()); - ConstraintP neg = c->getNegation(); - Assert(neg->safeToGarbageCollect()); - delete c; - delete neg; -} - -void ConstraintDatabase::addVariable(ArithVar v){ - if(d_reclaimable.isMember(v)){ - SortedConstraintMap& scm = getVariableSCM(v); - - std::vector constraintList; - - for(SortedConstraintMapIterator i = scm.begin(), end = scm.end(); i != end; ++i){ - (i->second).push_into(constraintList); - } - while(!constraintList.empty()){ - ConstraintP c = constraintList.back(); - constraintList.pop_back(); - Assert(c->safeToGarbageCollect()); - delete c; - } - Assert(scm.empty()); - - d_reclaimable.remove(v); - }else{ - Trace("arith::constraint") << "about to fail" << v << " " << d_varDatabases.size() << endl; - Assert(v == d_varDatabases.size()); - d_varDatabases.push_back(new PerVariableDatabase(v)); - } -} - -void ConstraintDatabase::removeVariable(ArithVar v){ - Assert(!d_reclaimable.isMember(v)); - d_reclaimable.add(v); -} - -bool Constraint::safeToGarbageCollect() const{ - // Do not call during destructor as getNegation() may be Null by this point - Assert(getNegation() != NullConstraint); - return !contextDependentDataIsSet() && ! getNegation()->contextDependentDataIsSet(); -} - -bool Constraint::contextDependentDataIsSet() const{ - return hasProof() || isSplit() || canBePropagated() || assertedToTheTheory(); -} - -TrustNode Constraint::split() -{ - Assert(isEquality() || isDisequality()); - - bool isEq = isEquality(); - - ConstraintP eq = isEq ? this : d_negation; - ConstraintP diseq = isEq ? d_negation : this; - - TNode eqNode = eq->getLiteral(); - Assert(eqNode.getKind() == kind::EQUAL); - TNode lhs = eqNode[0]; - TNode rhs = eqNode[1]; - - Node leqNode = NodeBuilder(kind::LEQ) << lhs << rhs; - Node ltNode = NodeBuilder(kind::LT) << lhs << rhs; - Node gtNode = NodeBuilder(kind::GT) << lhs << rhs; - Node geqNode = NodeBuilder(kind::GEQ) << lhs << rhs; - - Node lemma = NodeBuilder(OR) << leqNode << geqNode; - - TrustNode trustedLemma; - if (d_database->isProofEnabled()) - { - TypeNode type = lhs.getType(); - // Farkas proof that this works. - auto nm = NodeManager::currentNM(); - auto nLeqPf = d_database->d_pnm->mkAssume(leqNode.negate()); - auto gtPf = d_database->d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {nLeqPf}, {gtNode}); - auto nGeqPf = d_database->d_pnm->mkAssume(geqNode.negate()); - auto ltPf = d_database->d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {nGeqPf}, {ltNode}); - auto sumPf = - d_database->d_pnm->mkNode(PfRule::MACRO_ARITH_SCALE_SUM_UB, - {gtPf, ltPf}, - {nm->mkConstRealOrInt(type, Rational(-1)), - nm->mkConstRealOrInt(type, Rational(1))}); - auto botPf = d_database->d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); - std::vector a = {leqNode.negate(), geqNode.negate()}; - auto notAndNotPf = d_database->d_pnm->mkScope(botPf, a); - // No need to ensure that the expected node aggrees with `a` because we are - // not providing an expected node. - auto orNotNotPf = - d_database->d_pnm->mkNode(PfRule::NOT_AND, {notAndNotPf}, {}); - auto orPf = d_database->d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {orNotNotPf}, {lemma}); - trustedLemma = d_database->d_pfGen->mkTrustNode(lemma, orPf); - } - else - { - trustedLemma = TrustNode::mkTrustLemma(lemma); - } - - eq->d_database->pushSplitWatch(eq); - diseq->d_database->pushSplitWatch(diseq); - - return trustedLemma; -} - -bool ConstraintDatabase::hasLiteral(TNode literal) const { - return lookup(literal) != NullConstraint; -} - -ConstraintP ConstraintDatabase::addLiteral(TNode literal){ - Assert(!hasLiteral(literal)); - bool isNot = (literal.getKind() == NOT); - Node atomNode = (isNot ? literal[0] : literal); - Node negationNode = atomNode.notNode(); - - Assert(!hasLiteral(atomNode)); - Assert(!hasLiteral(negationNode)); - Comparison posCmp = Comparison::parseNormalForm(atomNode); - - ConstraintType posType = Constraint::constraintTypeOfComparison(posCmp); - - Polynomial nvp = posCmp.normalizedVariablePart(); - ArithVar v = d_avariables.asArithVar(nvp.getNode()); - - DeltaRational posDR = posCmp.normalizedDeltaRational(); - - ConstraintP posC = - new Constraint(v, posType, posDR, options().smt.produceProofs); - - Trace("arith::constraint") << "addliteral( literal ->" << literal << ")" << endl; - Trace("arith::constraint") << "addliteral( posC ->" << posC << ")" << endl; - - SortedConstraintMap& scm = getVariableSCM(posC->getVariable()); - pair insertAttempt; - insertAttempt = scm.insert(make_pair(posC->getValue(), ValueCollection())); - - SortedConstraintMapIterator posI = insertAttempt.first; - // If the attempt succeeds, i points to a new empty ValueCollection - // If the attempt fails, i points to a pre-existing ValueCollection - - if(posI->second.hasConstraintOfType(posC->getType())){ - //This is the situation where the ConstraintP exists, but - //the literal has not been associated with it. - ConstraintP hit = posI->second.getConstraintOfType(posC->getType()); - Trace("arith::constraint") << "hit " << hit << endl; - Trace("arith::constraint") << "posC " << posC << endl; - - delete posC; - - hit->setLiteral(atomNode); - hit->getNegation()->setLiteral(negationNode); - return isNot ? hit->getNegation(): hit; - }else{ - Comparison negCmp = Comparison::parseNormalForm(negationNode); - - ConstraintType negType = Constraint::constraintTypeOfComparison(negCmp); - DeltaRational negDR = negCmp.normalizedDeltaRational(); - - ConstraintP negC = - new Constraint(v, negType, negDR, options().smt.produceProofs); - - SortedConstraintMapIterator negI; - - if(posC->isEquality()){ - negI = posI; - }else{ - Assert(posC->isLowerBound() || posC->isUpperBound()); - - pair negInsertAttempt; - negInsertAttempt = scm.insert(make_pair(negC->getValue(), ValueCollection())); - - Trace("nf::tmp") << "sdhjfgdhjkldfgljkhdfg" << endl; - Trace("nf::tmp") << negC << endl; - Trace("nf::tmp") << negC->getValue() << endl; - - //This should always succeed as the DeltaRational for the negation is unique! - Assert(negInsertAttempt.second); - - negI = negInsertAttempt.first; - } - - (posI->second).add(posC); - (negI->second).add(negC); - - posC->initialize(this, posI, negC); - negC->initialize(this, negI, posC); - - posC->setLiteral(atomNode); - negC->setLiteral(negationNode); - - return isNot ? negC : posC; - } -} - - -ConstraintP ConstraintDatabase::lookup(TNode literal) const{ - NodetoConstraintMap::const_iterator iter = d_nodetoConstraintMap.find(literal); - if(iter == d_nodetoConstraintMap.end()){ - return NullConstraint; - }else{ - return iter->second; - } -} - -void Constraint::setAssumption(bool nowInConflict){ - Trace("constraints::pf") << "setAssumption(" << this << ")" << std::endl; - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(hasLiteral()); - Assert(assertedToTheTheory()); - - d_database->pushConstraintRule(ConstraintRule(this, AssumeAP)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict@setAssumption " << this << std::endl; - } -} - -void Constraint::tryToPropagate(){ - Assert(hasProof()); - Assert(!isAssumption()); - Assert(!isInternalAssumption()); - - if(canBePropagated() && !assertedToTheTheory() && !isAssumption() && !isInternalAssumption()){ - propagate(); - } -} - -void Constraint::propagate(){ - Assert(hasProof()); - Assert(canBePropagated()); - Assert(!assertedToTheTheory()); - Assert(!isAssumption()); - Assert(!isInternalAssumption()); - - d_database->d_toPropagate.push(this); -} - - -/* - * Example: - * x <= a and a < b - * |= x <= b - * --- - * 1*(x <= a) + (-1)*(x > b) => (0 <= a-b) - */ -void Constraint::impliedByUnate(ConstraintCP imp, bool nowInConflict){ - Trace("constraints::pf") << "impliedByUnate(" << this << ", " << *imp << ")" << std::endl; - Assert(!hasProof()); - Assert(imp->hasProof()); - Assert(negationHasProof() == nowInConflict); - - d_database->d_antecedents.push_back(NullConstraint); - d_database->d_antecedents.push_back(imp); - - AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; - - RationalVectorP coeffs; - if (d_produceProofs) - { - std::pair sgns = unateFarkasSigns(getNegation(), imp); - - Rational first(sgns.first); - Rational second(sgns.second); - - coeffs = new RationalVector(); - coeffs->push_back(first); - coeffs->push_back(second); - } - else - { - coeffs = RationalVectorPSentinel; - } - // no need to delete coeffs the memory is owned by ConstraintRule - d_database->pushConstraintRule(ConstraintRule(this, FarkasAP, antecedentEnd, coeffs)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict@impliedByUnate " << this << std::endl; - } - - if(TraceIsOn("constraints::wffp") && !wellFormedFarkasProof()){ - getConstraintRule().print(Trace("constraints::wffp"), d_produceProofs); - } - Assert(wellFormedFarkasProof()); -} - -void Constraint::impliedByTrichotomy(ConstraintCP a, ConstraintCP b, bool nowInConflict){ - Trace("constraints::pf") << "impliedByTrichotomy(" << this << ", " << *a << ", "; - Trace("constraints::pf") << *b << ")" << std::endl; - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(a->hasProof()); - Assert(b->hasProof()); - - d_database->d_antecedents.push_back(NullConstraint); - d_database->d_antecedents.push_back(a); - d_database->d_antecedents.push_back(b); - - AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; - d_database->pushConstraintRule(ConstraintRule(this, TrichotomyAP, antecedentEnd)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict@impliedByTrichotomy " << this << std::endl; - } -} - - -bool Constraint::allHaveProof(const ConstraintCPVec& b){ - for(ConstraintCPVec::const_iterator i=b.begin(), i_end=b.end(); i != i_end; ++i){ - ConstraintCP cp = *i; - if(! (cp->hasProof())){ return false; } - } - return true; -} - -void Constraint::impliedByIntTighten(ConstraintCP a, bool nowInConflict){ - Trace("constraints::pf") << "impliedByIntTighten(" << this << ", " << *a << ")" << std::endl; - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(a->hasProof()); - Trace("pf::arith") << "impliedByIntTighten(" << this << ", " << a << ")" - << std::endl; - - d_database->d_antecedents.push_back(NullConstraint); - d_database->d_antecedents.push_back(a); - AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; - d_database->pushConstraintRule(ConstraintRule(this, IntTightenAP, antecedentEnd)); - - Assert(inConflict() == nowInConflict); - if(inConflict()){ - Trace("constraint::conflictCommit") << "inConflict impliedByIntTighten" << this << std::endl; - } -} - -void Constraint::impliedByIntHole(ConstraintCP a, bool nowInConflict){ - Trace("constraints::pf") << "impliedByIntHole(" << this << ", " << *a << ")" << std::endl; - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(a->hasProof()); - Trace("pf::arith") << "impliedByIntHole(" << this << ", " << a << ")" - << std::endl; - - d_database->d_antecedents.push_back(NullConstraint); - d_database->d_antecedents.push_back(a); - AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; - d_database->pushConstraintRule(ConstraintRule(this, IntHoleAP, antecedentEnd)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict impliedByIntHole" << this << std::endl; - } -} - -void Constraint::impliedByIntHole(const ConstraintCPVec& b, bool nowInConflict){ - Trace("constraints::pf") << "impliedByIntHole(" << this; - if (TraceIsOn("constraints::pf")) { - for (const ConstraintCP& p : b) - { - Trace("constraints::pf") << ", " << p; - } - } - Trace("constraints::pf") << ")" << std::endl; - - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(allHaveProof(b)); - - CDConstraintList& antecedents = d_database->d_antecedents; - antecedents.push_back(NullConstraint); - for(ConstraintCPVec::const_iterator i=b.begin(), i_end=b.end(); i != i_end; ++i){ - antecedents.push_back(*i); - } - AntecedentId antecedentEnd = antecedents.size() - 1; - - d_database->pushConstraintRule(ConstraintRule(this, IntHoleAP, antecedentEnd)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict@impliedByIntHole[vec] " << this << std::endl; - } -} - -/* - * If proofs are off, coeffs == RationalVectorSentinal. - * If proofs are on, - * coeffs != RationalVectorSentinal, - * coeffs->size() = a.size() + 1, - * for i in [0,a.size) : coeff[i] corresponds to a[i], and - * coeff.back() corresponds to the current constraint. - */ -void Constraint::impliedByFarkas(const ConstraintCPVec& a, RationalVectorCP coeffs, bool nowInConflict){ - Trace("constraints::pf") << "impliedByFarkas(" << this; - if (TraceIsOn("constraints::pf")) { - for (const ConstraintCP& p : a) - { - Trace("constraints::pf") << ", " << p; - } - } - Trace("constraints::pf") << ", "; - Trace("constraints::pf") << ")" << std::endl; - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(allHaveProof(a)); - - Assert(d_produceProofs == (coeffs != RationalVectorCPSentinel)); - Assert(!d_produceProofs || coeffs->size() == a.size() + 1); - - Assert(a.size() >= 1); - - d_database->d_antecedents.push_back(NullConstraint); - for(ConstraintCPVec::const_iterator i = a.begin(), end = a.end(); i != end; ++i){ - ConstraintCP c_i = *i; - Assert(c_i->hasProof()); - d_database->d_antecedents.push_back(c_i); - } - AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; - - RationalVectorCP coeffsCopy; - if (d_produceProofs) - { - Assert(coeffs != RationalVectorCPSentinel); - coeffsCopy = new RationalVector(*coeffs); - } - else - { - coeffsCopy = RationalVectorCPSentinel; - } - d_database->pushConstraintRule(ConstraintRule(this, FarkasAP, antecedentEnd, coeffsCopy)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict@impliedByFarkas " << this << std::endl; - } - if(TraceIsOn("constraints::wffp") && !wellFormedFarkasProof()){ - getConstraintRule().print(Trace("constraints::wffp"), d_produceProofs); - } - Assert(wellFormedFarkasProof()); -} - - -void Constraint::setInternalAssumption(bool nowInConflict){ - Trace("constraints::pf") << "setInternalAssumption(" << this; - Trace("constraints::pf") << ")" << std::endl; - Assert(!hasProof()); - Assert(negationHasProof() == nowInConflict); - Assert(!assertedToTheTheory()); - - d_database->pushConstraintRule(ConstraintRule(this, InternalAssumeAP)); - - Assert(inConflict() == nowInConflict); - if(TraceIsOn("constraint::conflictCommit") && inConflict()){ - Trace("constraint::conflictCommit") << "inConflict@setInternalAssumption " << this << std::endl; - } -} - - -void Constraint::setEqualityEngineProof(){ - Trace("constraints::pf") << "setEqualityEngineProof(" << this; - Trace("constraints::pf") << ")" << std::endl; - Assert(truthIsUnknown()); - Assert(hasLiteral()); - d_database->pushConstraintRule(ConstraintRule(this, EqualityEngineAP)); -} - - -SortedConstraintMap& Constraint::constraintSet() const{ - Assert(d_database->variableDatabaseIsSetup(d_variable)); - return (d_database->d_varDatabases[d_variable])->d_constraints; -} - -bool Constraint::antecentListIsEmpty() const{ - Assert(hasProof()); - return d_database->d_antecedents[getEndAntecedent()] == NullConstraint; -} - -bool Constraint::antecedentListLengthIsOne() const { - Assert(hasProof()); - return !antecentListIsEmpty() && - d_database->d_antecedents[getEndAntecedent()-1] == NullConstraint; -} - -Node Constraint::externalImplication(const ConstraintCPVec& b) const{ - Assert(hasLiteral()); - Node antecedent = externalExplainByAssertions(b); - Node implied = getLiteral(); - return antecedent.impNode(implied); -} - - -Node Constraint::externalExplainByAssertions(const ConstraintCPVec& b){ - return externalExplain(b, AssertionOrderSentinel); -} - -TrustNode Constraint::externalExplainForPropagation(TNode lit) const -{ - Assert(hasProof()); - Assert(!isAssumption()); - Assert(!isInternalAssumption()); - NodeBuilder nb(Kind::AND); - auto pfFromAssumptions = externalExplain(nb, d_assertionOrder); - Node n = mkAndFromBuilder(nb); - if (d_database->isProofEnabled()) - { - std::vector assumptions; - if (n.getKind() == Kind::AND) - { - assumptions.insert(assumptions.end(), n.begin(), n.end()); - } - else - { - assumptions.push_back(n); - } - if (getProofLiteral() != lit) - { - pfFromAssumptions = d_database->d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pfFromAssumptions}, {lit}); - } - auto pf = d_database->d_pnm->mkScope(pfFromAssumptions, assumptions); - return d_database->d_pfGen->mkTrustedPropagation( - lit, NodeManager::currentNM()->mkAnd(assumptions), pf); - } - else - { - return TrustNode::mkTrustPropExp(lit, n); - } -} - -TrustNode Constraint::externalExplainConflict() const -{ - Trace("pf::arith::explain") << this << std::endl; - Assert(inConflict()); - NodeBuilder nb(kind::AND); - auto pf1 = externalExplainByAssertions(nb); - auto not2 = getNegation()->getProofLiteral().negate(); - auto pf2 = getNegation()->externalExplainByAssertions(nb); - Node n = mkAndFromBuilder(nb); - if (d_database->isProofEnabled()) - { - auto pfNot2 = d_database->d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pf1}, {not2}); - std::vector lits; - if (n.getKind() == Kind::AND) - { - lits.insert(lits.end(), n.begin(), n.end()); - } - else - { - lits.push_back(n); - } - if (TraceIsOn("arith::pf::externalExplainConflict")) - { - Trace("arith::pf::externalExplainConflict") << "Lits:" << std::endl; - for (const auto& l : lits) - { - Trace("arith::pf::externalExplainConflict") << " : " << l << std::endl; - } - } - std::vector contraLits = {getProofLiteral(), - getNegation()->getProofLiteral()}; - auto bot = - not2.getKind() == Kind::NOT - ? d_database->d_pnm->mkNode(PfRule::CONTRA, {pf2, pfNot2}, {}) - : d_database->d_pnm->mkNode(PfRule::CONTRA, {pfNot2, pf2}, {}); - if (TraceIsOn("arith::pf::tree")) - { - Trace("arith::pf::tree") << *this << std::endl; - Trace("arith::pf::tree") << *getNegation() << std::endl; - Trace("arith::pf::tree") << "\n\nTree:\n"; - printProofTree(Trace("arith::pf::tree")); - getNegation()->printProofTree(Trace("arith::pf::tree")); - } - auto confPf = d_database->d_pnm->mkScope(bot, lits); - return d_database->d_pfGen->mkTrustNode( - NodeManager::currentNM()->mkAnd(lits), confPf, true); - } - else - { - return TrustNode::mkTrustConflict(n); - } -} - -struct ConstraintCPHash { - /* Todo replace with an id */ - size_t operator()(ConstraintCP c) const{ - Assert(sizeof(ConstraintCP) > 0); - return ((size_t)c)/sizeof(ConstraintCP); - } -}; - -void Constraint::assertionFringe(ConstraintCPVec& v){ - unordered_set visited; - size_t writePos = 0; - - if(!v.empty()){ - const ConstraintDatabase* db = v.back()->d_database; - const CDConstraintList& antecedents = db->d_antecedents; - for(size_t i = 0; i < v.size(); ++i){ - ConstraintCP vi = v[i]; - if(visited.find(vi) == visited.end()){ - Assert(vi->hasProof()); - visited.insert(vi); - if(vi->onFringe()){ - v[writePos] = vi; - writePos++; - }else{ - Assert(vi->hasTrichotomyProof() || vi->hasFarkasProof() - || vi->hasIntHoleProof() || vi->hasIntTightenProof()); - AntecedentId p = vi->getEndAntecedent(); - - ConstraintCP antecedent = antecedents[p]; - while(antecedent != NullConstraint){ - v.push_back(antecedent); - --p; - antecedent = antecedents[p]; - } - } - } - } - v.resize(writePos); - } -} - -void Constraint::assertionFringe(ConstraintCPVec& o, const ConstraintCPVec& i){ - o.insert(o.end(), i.begin(), i.end()); - assertionFringe(o); -} - -Node Constraint::externalExplain(const ConstraintCPVec& v, AssertionOrder order){ - NodeBuilder nb(kind::AND); - ConstraintCPVec::const_iterator i, end; - for(i = v.begin(), end = v.end(); i != end; ++i){ - ConstraintCP v_i = *i; - v_i->externalExplain(nb, order); - } - return mkAndFromBuilder(nb); -} - -std::shared_ptr Constraint::externalExplain( - NodeBuilder& nb, AssertionOrder order) const -{ - if (TraceIsOn("pf::arith::explain")) - { - this->printProofTree(Trace("arith::pf::tree")); - Trace("pf::arith::explain") << "Explaining: " << this << " with rule "; - getConstraintRule().print(Trace("pf::arith::explain"), d_produceProofs); - Trace("pf::arith::explain") << std::endl; - } - Assert(hasProof()); - Assert(!isAssumption() || assertedToTheTheory()); - Assert(!isInternalAssumption()); - std::shared_ptr pf{}; - - ProofNodeManager* pnm = d_database->d_pnm; - - if (assertedBefore(order)) - { - Trace("pf::arith::explain") << " already asserted" << std::endl; - nb << getWitness(); - if (d_database->isProofEnabled()) - { - pf = pnm->mkAssume(getWitness()); - // If the witness and literal differ, prove the difference through a - // rewrite. - if (getWitness() != getProofLiteral()) - { - pf = pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {getProofLiteral()}); - } - } - } - else if (hasEqualityEngineProof()) - { - Trace("pf::arith::explain") << " going to ee:" << std::endl; - TrustNode exp = d_database->eeExplain(this); - if (d_database->isProofEnabled()) - { - Assert(exp.getProven().getKind() == Kind::IMPLIES); - std::vector> hypotheses; - hypotheses.push_back(exp.getGenerator()->getProofFor(exp.getProven())); - if (exp.getNode().getKind() == Kind::AND) - { - for (const auto& h : exp.getNode()) - { - hypotheses.push_back( - pnm->mkNode(PfRule::TRUE_INTRO, {pnm->mkAssume(h)}, {})); - } - } - else - { - hypotheses.push_back(pnm->mkNode( - PfRule::TRUE_INTRO, {pnm->mkAssume(exp.getNode())}, {})); - } - pf = pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {hypotheses}, {getProofLiteral()}); - } - Trace("pf::arith::explain") - << " explanation: " << exp.getNode() << std::endl; - if (exp.getNode().getKind() == Kind::AND) - { - nb.append(exp.getNode().begin(), exp.getNode().end()); - } - else - { - nb << exp.getNode(); - } - } - else - { - Trace("pf::arith::explain") << " recursion!" << std::endl; - Assert(!isAssumption()); - AntecedentId p = getEndAntecedent(); - ConstraintCP antecedent = d_database->d_antecedents[p]; - std::vector> children; - - while (antecedent != NullConstraint) - { - Trace("pf::arith::explain") << "Explain " << antecedent << std::endl; - auto pn = antecedent->externalExplain(nb, order); - if (d_database->isProofEnabled()) - { - children.push_back(pn); - } - --p; - antecedent = d_database->d_antecedents[p]; - } - - if (d_database->isProofEnabled()) - { - switch (getProofType()) - { - case ArithProofType::AssumeAP: - case ArithProofType::EqualityEngineAP: - { - Unreachable() << "These should be handled above"; - break; - } - case ArithProofType::FarkasAP: - { - // Per docs in constraint.h, - // the 0th farkas coefficient is for the negation of the deduced - // constraint the 1st corresponds to the last antecedent the nth - // corresponds to the first antecedent Then, the farkas coefficients - // and the antecedents are in the same order. - - // Enumerate child proofs (negation included) in d_farkasCoefficients - // order - Node plit = getNegation()->getProofLiteral(); - std::vector> farkasChildren; - farkasChildren.push_back(pnm->mkAssume(plit)); - farkasChildren.insert( - farkasChildren.end(), children.rbegin(), children.rend()); - - NodeManager* nm = NodeManager::currentNM(); - - // Enumerate d_farkasCoefficients as nodes. - std::vector farkasCoeffs; - TypeNode type = plit[0].getType(); - for (Rational r : *getFarkasCoefficients()) - { - farkasCoeffs.push_back(nm->mkConstRealOrInt(type, Rational(r))); - } - - // Apply the scaled-sum rule. - std::shared_ptr sumPf = pnm->mkNode( - PfRule::MACRO_ARITH_SCALE_SUM_UB, farkasChildren, farkasCoeffs); - - // Provable rewrite the result - auto botPf = pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); - - // Scope out the negated constraint, yielding a proof of the - // constraint. - std::vector assump{plit}; - auto maybeDoubleNotPf = pnm->mkScope(botPf, assump, false); - - // No need to ensure that the expected node aggrees with `assump` - // because we are not providing an expected node. - // - // Prove that this is the literal (may need to clean a double-not) - pf = pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, - {maybeDoubleNotPf}, - {getProofLiteral()}); - - break; - } - case ArithProofType::IntTightenAP: - { - if (isUpperBound()) - { - pf = pnm->mkNode( - PfRule::INT_TIGHT_UB, children, {}, getProofLiteral()); - } - else if (isLowerBound()) - { - pf = pnm->mkNode( - PfRule::INT_TIGHT_LB, children, {}, getProofLiteral()); - } - else - { - Unreachable(); - } - break; - } - case ArithProofType::IntHoleAP: - { - Node t = - builtin::BuiltinProofRuleChecker::mkTheoryIdNode(THEORY_ARITH); - pf = pnm->mkNode(PfRule::THEORY_INFERENCE, - children, - {getProofLiteral(), t}, - getProofLiteral()); - break; - } - case ArithProofType::TrichotomyAP: - { - pf = pnm->mkNode(PfRule::ARITH_TRICHOTOMY, - children, - {getProofLiteral()}, - getProofLiteral()); - break; - } - case ArithProofType::InternalAssumeAP: - case ArithProofType::NoAP: - default: - { - Unreachable() << getProofType() - << " should not be visible in explanation"; - break; - } - } - } - } - return pf; -} - -Node Constraint::externalExplainByAssertions(ConstraintCP a, ConstraintCP b){ - NodeBuilder nb(kind::AND); - a->externalExplainByAssertions(nb); - b->externalExplainByAssertions(nb); - return nb; -} - -Node Constraint::externalExplainByAssertions(ConstraintCP a, ConstraintCP b, ConstraintCP c){ - NodeBuilder nb(kind::AND); - a->externalExplainByAssertions(nb); - b->externalExplainByAssertions(nb); - c->externalExplainByAssertions(nb); - return nb; -} - -ConstraintP Constraint::getStrictlyWeakerLowerBound(bool hasLiteral, bool asserted) const { - Assert(initialized()); - Assert(!asserted || hasLiteral); - - SortedConstraintMapConstIterator i = d_variablePosition; - const SortedConstraintMap& scm = constraintSet(); - SortedConstraintMapConstIterator i_begin = scm.begin(); - while(i != i_begin){ - --i; - const ValueCollection& vc = i->second; - if(vc.hasLowerBound()){ - ConstraintP weaker = vc.getLowerBound(); - - // asserted -> hasLiteral - // hasLiteral -> weaker->hasLiteral() - // asserted -> weaker->assertedToTheTheory() - if((!hasLiteral || (weaker->hasLiteral())) && - (!asserted || ( weaker->assertedToTheTheory()))){ - return weaker; - } - } - } - return NullConstraint; -} - -ConstraintP Constraint::getStrictlyWeakerUpperBound(bool hasLiteral, bool asserted) const { - SortedConstraintMapConstIterator i = d_variablePosition; - const SortedConstraintMap& scm = constraintSet(); - SortedConstraintMapConstIterator i_end = scm.end(); - - ++i; - for(; i != i_end; ++i){ - const ValueCollection& vc = i->second; - if(vc.hasUpperBound()){ - ConstraintP weaker = vc.getUpperBound(); - if((!hasLiteral || (weaker->hasLiteral())) && - (!asserted || ( weaker->assertedToTheTheory()))){ - return weaker; - } - } - } - - return NullConstraint; -} - -ConstraintP ConstraintDatabase::getBestImpliedBound(ArithVar v, ConstraintType t, const DeltaRational& r) const { - Assert(variableDatabaseIsSetup(v)); - Assert(t == UpperBound || t == LowerBound); - - SortedConstraintMap& scm = getVariableSCM(v); - if(t == UpperBound){ - SortedConstraintMapConstIterator i = scm.lower_bound(r); - SortedConstraintMapConstIterator i_end = scm.end(); - Assert(i == i_end || r <= i->first); - for(; i != i_end; i++){ - Assert(r <= i->first); - const ValueCollection& vc = i->second; - if(vc.hasUpperBound()){ - return vc.getUpperBound(); - } - } - return NullConstraint; - }else{ - Assert(t == LowerBound); - if(scm.empty()){ - return NullConstraint; - }else{ - SortedConstraintMapConstIterator i = scm.lower_bound(r); - SortedConstraintMapConstIterator i_begin = scm.begin(); - SortedConstraintMapConstIterator i_end = scm.end(); - Assert(i == i_end || r <= i->first); - - int fdj = 0; - - if(i == i_end){ - --i; - Trace("getBestImpliedBound") << fdj++ << " " << r << " " << i->first << endl; - }else if( (i->first) > r){ - if(i == i_begin){ - return NullConstraint; - }else{ - --i; - Trace("getBestImpliedBound") << fdj++ << " " << r << " " << i->first << endl; - } - } - - do{ - Trace("getBestImpliedBound") << fdj++ << " " << r << " " << i->first << endl; - Assert(r >= i->first); - const ValueCollection& vc = i->second; - - if(vc.hasLowerBound()){ - return vc.getLowerBound(); - } - - if(i == i_begin){ - break; - }else{ - --i; - } - }while(true); - return NullConstraint; - } - } -} -TrustNode ConstraintDatabase::eeExplain(const Constraint* const c) const -{ - Assert(c->hasLiteral()); - return d_congruenceManager.explain(c->getLiteral()); -} - -void ConstraintDatabase::eeExplain(ConstraintCP c, NodeBuilder& nb) const -{ - Assert(c->hasLiteral()); - // NOTE: this is not a recommended method since it ignores proofs - d_congruenceManager.explain(c->getLiteral(), nb); -} - -bool ConstraintDatabase::variableDatabaseIsSetup(ArithVar v) const { - return v < d_varDatabases.size(); -} - -ConstraintDatabase::Watches::Watches(context::Context* satContext, - context::Context* userContext) - : d_constraintProofs(satContext), - d_canBePropagatedWatches(satContext), - d_assertionOrderWatches(satContext), - d_splitWatches(userContext) -{} - - -void Constraint::setLiteral(Node n) { - Trace("arith::constraint") << "Mapping " << *this << " to " << n << std::endl; - Assert(Comparison::isNormalAtom(n)); - Assert(!hasLiteral()); - Assert(sanityChecking(n)); - d_literal = n; - NodetoConstraintMap& map = d_database->d_nodetoConstraintMap; - Assert(map.find(n) == map.end()); - map.insert(make_pair(d_literal, this)); -} - -Node Constraint::getProofLiteral() const -{ - Assert(d_database != nullptr); - Assert(d_database->d_avariables.hasNode(d_variable)); - Node varPart = d_database->d_avariables.asNode(d_variable); - Kind cmp; - bool neg = false; - switch (d_type) - { - case ConstraintType::UpperBound: - { - if (d_value.infinitesimalIsZero()) - { - cmp = Kind::LEQ; - } - else - { - cmp = Kind::LT; - } - break; - } - case ConstraintType::LowerBound: - { - if (d_value.infinitesimalIsZero()) - { - cmp = Kind::GEQ; - } - else - { - cmp = Kind::GT; - } - break; - } - case ConstraintType::Equality: - { - cmp = Kind::EQUAL; - break; - } - case ConstraintType::Disequality: - { - cmp = Kind::EQUAL; - neg = true; - break; - } - default: Unreachable() << d_type; - } - NodeManager* nm = NodeManager::currentNM(); - Node constPart = nm->mkConstRealOrInt( - varPart.getType(), Rational(d_value.getNoninfinitesimalPart())); - Node posLit = nm->mkNode(cmp, varPart, constPart); - return neg ? posLit.negate() : posLit; -} - -void ConstraintDatabase::proveOr(std::vector& out, - ConstraintP a, - ConstraintP b, - bool negateSecond) const -{ - Node la = a->getLiteral(); - Node lb = b->getLiteral(); - Node orN = (la < lb) ? la.orNode(lb) : lb.orNode(la); - if (isProofEnabled()) - { - Assert(b->getNegation()->getType() != ConstraintType::Disequality); - auto nm = NodeManager::currentNM(); - Node alit = a->getNegation()->getProofLiteral(); - TypeNode type = alit[0].getType(); - auto pf_neg_la = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, - {d_pnm->mkAssume(la.negate())}, - {alit}); - Node blit = b->getNegation()->getProofLiteral(); - auto pf_neg_lb = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, - {d_pnm->mkAssume(lb.negate())}, - {blit}); - int sndSign = negateSecond ? -1 : 1; - auto bot_pf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, - {d_pnm->mkNode(PfRule::MACRO_ARITH_SCALE_SUM_UB, - {pf_neg_la, pf_neg_lb}, - {nm->mkConstRealOrInt(type, Rational(-1 * sndSign)), - nm->mkConstRealOrInt(type, Rational(sndSign))})}, - {nm->mkConst(false)}); - std::vector as; - std::transform(orN.begin(), orN.end(), std::back_inserter(as), [](Node n) { - return n.negate(); - }); - // No need to ensure that the expected node aggrees with `as` because we - // are not providing an expected node. - auto pf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, - {d_pnm->mkNode(PfRule::NOT_AND, {d_pnm->mkScope(bot_pf, as)}, {})}, - {orN}); - out.push_back(d_pfGen->mkTrustNode(orN, pf)); - } - else - { - out.push_back(TrustNode::mkTrustLemma(orN)); - } -} - -void ConstraintDatabase::implies(std::vector& out, - ConstraintP a, - ConstraintP b) const -{ - Node la = a->getLiteral(); - Node lb = b->getLiteral(); - - Node neg_la = (la.getKind() == kind::NOT)? la[0] : la.notNode(); - - Assert(lb != neg_la); - Assert(b->getNegation()->getType() == ConstraintType::LowerBound - || b->getNegation()->getType() == ConstraintType::UpperBound); - proveOr(out, - a->getNegation(), - b, - b->getNegation()->getType() == ConstraintType::LowerBound); -} - -void ConstraintDatabase::mutuallyExclusive(std::vector& out, - ConstraintP a, - ConstraintP b) const -{ - Node la = a->getLiteral(); - Node lb = b->getLiteral(); - - Node neg_la = la.negate(); - Node neg_lb = lb.negate(); - proveOr(out, a->getNegation(), b->getNegation(), true); -} - -void ConstraintDatabase::outputUnateInequalityLemmas( - std::vector& out, ArithVar v) const -{ - SortedConstraintMap& scm = getVariableSCM(v); - SortedConstraintMapConstIterator scm_iter = scm.begin(); - SortedConstraintMapConstIterator scm_end = scm.end(); - ConstraintP prev = NullConstraint; - //get transitive unates - //Only lower bounds or upperbounds should be done. - for(; scm_iter != scm_end; ++scm_iter){ - const ValueCollection& vc = scm_iter->second; - if(vc.hasUpperBound()){ - ConstraintP ub = vc.getUpperBound(); - if(ub->hasLiteral()){ - if(prev != NullConstraint){ - implies(out, prev, ub); - } - prev = ub; - } - } - } -} - -void ConstraintDatabase::outputUnateEqualityLemmas(std::vector& out, - ArithVar v) const -{ - vector equalities; - - SortedConstraintMap& scm = getVariableSCM(v); - SortedConstraintMapConstIterator scm_iter = scm.begin(); - SortedConstraintMapConstIterator scm_end = scm.end(); - - for(; scm_iter != scm_end; ++scm_iter){ - const ValueCollection& vc = scm_iter->second; - if(vc.hasEquality()){ - ConstraintP eq = vc.getEquality(); - if(eq->hasLiteral()){ - equalities.push_back(eq); - } - } - } - - vector::const_iterator i, j, eq_end = equalities.end(); - for(i = equalities.begin(); i != eq_end; ++i){ - ConstraintP at_i = *i; - for(j= i + 1; j != eq_end; ++j){ - ConstraintP at_j = *j; - - mutuallyExclusive(out, at_i, at_j); - } - } - - for(i = equalities.begin(); i != eq_end; ++i){ - ConstraintP eq = *i; - const ValueCollection& vc = eq->getValueCollection(); - Assert(vc.hasEquality() && vc.getEquality()->hasLiteral()); - - bool hasLB = vc.hasLowerBound() && vc.getLowerBound()->hasLiteral(); - bool hasUB = vc.hasUpperBound() && vc.getUpperBound()->hasLiteral(); - - ConstraintP lb = hasLB ? - vc.getLowerBound() : eq->getStrictlyWeakerLowerBound(true, false); - ConstraintP ub = hasUB ? - vc.getUpperBound() : eq->getStrictlyWeakerUpperBound(true, false); - - if(hasUB && hasLB && !eq->isSplit()){ - out.push_back(eq->split()); - } - if(lb != NullConstraint){ - implies(out, eq, lb); - } - if(ub != NullConstraint){ - implies(out, eq, ub); - } - } -} - -void ConstraintDatabase::outputUnateEqualityLemmas( - std::vector& lemmas) const -{ - for(ArithVar v = 0, N = d_varDatabases.size(); v < N; ++v){ - outputUnateEqualityLemmas(lemmas, v); - } -} - -void ConstraintDatabase::outputUnateInequalityLemmas( - std::vector& lemmas) const -{ - for(ArithVar v = 0, N = d_varDatabases.size(); v < N; ++v){ - outputUnateInequalityLemmas(lemmas, v); - } -} - -bool ConstraintDatabase::handleUnateProp(ConstraintP ant, ConstraintP cons){ - if(cons->negationHasProof()){ - Trace("arith::unate") << "handleUnate: " << ant << " implies " << cons << endl; - cons->impliedByUnate(ant, true); - d_raiseConflict.raiseConflict(cons, InferenceId::ARITH_CONF_UNATE_PROP); - return true; - }else if(!cons->isTrue()){ - ++d_statistics.d_unatePropagateImplications; - Trace("arith::unate") << "handleUnate: " << ant << " implies " << cons << endl; - cons->impliedByUnate(ant, false); - cons->tryToPropagate(); - return false; - } else { - return false; - } -} - -void ConstraintDatabase::unatePropLowerBound(ConstraintP curr, ConstraintP prev){ - Trace("arith::unate") << "unatePropLowerBound " << curr << " " << prev << endl; - Assert(curr != prev); - Assert(curr != NullConstraint); - bool hasPrev = ! (prev == NullConstraint); - Assert(!hasPrev || curr->getValue() > prev->getValue()); - - ++d_statistics.d_unatePropagateCalls; - - const SortedConstraintMap& scm = curr->constraintSet(); - const SortedConstraintMapConstIterator scm_begin = scm.begin(); - SortedConstraintMapConstIterator scm_i = curr->d_variablePosition; - - //Ignore the first ValueCollection - // NOPE: (>= p c) then (= p c) NOPE - // NOPE: (>= p c) then (not (= p c)) NOPE - - while(scm_i != scm_begin){ - --scm_i; // move the iterator back - - const ValueCollection& vc = scm_i->second; - - //If it has the previous element, do nothing and stop! - if(hasPrev && - vc.hasConstraintOfType(prev->getType()) - && vc.getConstraintOfType(prev->getType()) == prev){ - break; - } - - //Don't worry about implying the negation of upperbound. - //These should all be handled by propagating the LowerBounds! - if(vc.hasLowerBound()){ - ConstraintP lb = vc.getLowerBound(); - if(handleUnateProp(curr, lb)){ return; } - } - if(vc.hasDisequality()){ - ConstraintP dis = vc.getDisequality(); - if(handleUnateProp(curr, dis)){ return; } - } - } -} - -void ConstraintDatabase::unatePropUpperBound(ConstraintP curr, ConstraintP prev){ - Trace("arith::unate") << "unatePropUpperBound " << curr << " " << prev << endl; - Assert(curr != prev); - Assert(curr != NullConstraint); - bool hasPrev = ! (prev == NullConstraint); - Assert(!hasPrev || curr->getValue() < prev->getValue()); - - ++d_statistics.d_unatePropagateCalls; - - const SortedConstraintMap& scm = curr->constraintSet(); - const SortedConstraintMapConstIterator scm_end = scm.end(); - SortedConstraintMapConstIterator scm_i = curr->d_variablePosition; - ++scm_i; - for(; scm_i != scm_end; ++scm_i){ - const ValueCollection& vc = scm_i->second; - - //If it has the previous element, do nothing and stop! - if(hasPrev && - vc.hasConstraintOfType(prev->getType()) && - vc.getConstraintOfType(prev->getType()) == prev){ - break; - } - //Don't worry about implying the negation of upperbound. - //These should all be handled by propagating the UpperBounds! - if(vc.hasUpperBound()){ - ConstraintP ub = vc.getUpperBound(); - if(handleUnateProp(curr, ub)){ return; } - } - if(vc.hasDisequality()){ - ConstraintP dis = vc.getDisequality(); - if(handleUnateProp(curr, dis)){ return; } - } - } -} - -void ConstraintDatabase::unatePropEquality(ConstraintP curr, ConstraintP prevLB, ConstraintP prevUB){ - Trace("arith::unate") << "unatePropEquality " << curr << " " << prevLB << " " << prevUB << endl; - Assert(curr != prevLB); - Assert(curr != prevUB); - Assert(curr != NullConstraint); - bool hasPrevLB = ! (prevLB == NullConstraint); - bool hasPrevUB = ! (prevUB == NullConstraint); - Assert(!hasPrevLB || curr->getValue() >= prevLB->getValue()); - Assert(!hasPrevUB || curr->getValue() <= prevUB->getValue()); - - ++d_statistics.d_unatePropagateCalls; - - const SortedConstraintMap& scm = curr->constraintSet(); - SortedConstraintMapConstIterator scm_curr = curr->d_variablePosition; - SortedConstraintMapConstIterator scm_last = hasPrevUB ? prevUB->d_variablePosition : scm.end(); - SortedConstraintMapConstIterator scm_i; - if(hasPrevLB){ - scm_i = prevLB->d_variablePosition; - if(scm_i != scm_curr){ // If this does not move this past scm_curr, move it one forward - ++scm_i; - } - }else{ - scm_i = scm.begin(); - } - - for(; scm_i != scm_curr; ++scm_i){ - // between the previous LB and the curr - const ValueCollection& vc = scm_i->second; - - //Don't worry about implying the negation of upperbound. - //These should all be handled by propagating the LowerBounds! - if(vc.hasLowerBound()){ - ConstraintP lb = vc.getLowerBound(); - if(handleUnateProp(curr, lb)){ return; } - } - if(vc.hasDisequality()){ - ConstraintP dis = vc.getDisequality(); - if(handleUnateProp(curr, dis)){ return; } - } - } - Assert(scm_i == scm_curr); - if(!hasPrevUB || scm_i != scm_last){ - ++scm_i; - } // hasPrevUB implies scm_i != scm_last - - for(; scm_i != scm_last; ++scm_i){ - // between the curr and the previous UB imply the upperbounds and disequalities. - const ValueCollection& vc = scm_i->second; - - //Don't worry about implying the negation of upperbound. - //These should all be handled by propagating the UpperBounds! - if(vc.hasUpperBound()){ - ConstraintP ub = vc.getUpperBound(); - if(handleUnateProp(curr, ub)){ return; } - } - if(vc.hasDisequality()){ - ConstraintP dis = vc.getDisequality(); - if(handleUnateProp(curr, dis)){ return; } - } - } -} - -std::pair Constraint::unateFarkasSigns(ConstraintCP ca, ConstraintCP cb){ - ConstraintType a = ca->getType(); - ConstraintType b = cb->getType(); - - Assert(a != Disequality); - Assert(b != Disequality); - - int a_sgn = (a == LowerBound) ? -1 : ((a == UpperBound) ? 1 : 0); - int b_sgn = (b == LowerBound) ? -1 : ((b == UpperBound) ? 1 : 0); - - if(a_sgn == 0 && b_sgn == 0){ - Assert(a == Equality); - Assert(b == Equality); - Assert(ca->getValue() != cb->getValue()); - if(ca->getValue() < cb->getValue()){ - a_sgn = 1; - b_sgn = -1; - }else{ - a_sgn = -1; - b_sgn = 1; - } - }else if(a_sgn == 0){ - Assert(b_sgn != 0); - Assert(a == Equality); - a_sgn = -b_sgn; - }else if(b_sgn == 0){ - Assert(a_sgn != 0); - Assert(b == Equality); - b_sgn = -a_sgn; - } - Assert(a_sgn != 0); - Assert(b_sgn != 0); - - Trace("arith::unateFarkasSigns") << "Constraint::unateFarkasSigns("< " - << "("< -#include - -#include "base/configuration_private.h" -#include "context/cdlist.h" -#include "context/cdqueue.h" -#include "expr/node.h" -#include "proof/trust_node.h" -#include "smt/env_obj.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/callbacks.h" -#include "theory/arith/constraint_forward.h" -#include "theory/arith/delta_rational.h" -#include "util/statistics_stats.h" - -namespace cvc5::context { -class Context; -} -namespace cvc5::internal { - -class ProofNodeManager; -class EagerProofGenerator; - -namespace theory { - -namespace arith { - -class Comparison; -class ArithCongruenceManager; -class ArithVariables; - -/** - * Logs the types of different proofs. - * Current, proof types: - * - NoAP : This constraint is not known to be true. - * - AssumeAP : This is an input assertion. There is no proof. - * : Something can be both asserted and have a proof. - * - InternalAssumeAP : An internal assumption. This has no guarantee of having an external proof. - * : This must be removed by regression. - * - FarkasAP : A proof with Farka's coefficients, i.e. - * : \sum lambda_i ( asNode(x_i) <= c_i ) |= 0 < 0 - * : If proofs are on, coefficients will be logged. - * : If proofs are off, coefficients will not be logged. - * : A unate implication is a FarkasAP. - * - TrichotomyAP : This is any entailment using (x<= a and x >=a) => x = a - * : Equivalently, (x > a or x < a or x = a) - * : There are 3 candidate ways this can propagate: - * : !(x > a) and !(x = a) => x < a - * : !(x < a) and !(x = a) => x > a - * : !(x > a) and !(x < a) => x = a - * - EqualityEngineAP : This is propagated by the equality engine. - * : Consult this for the proof. - * - IntTightenAP : This is indicates that a bound involving integers was tightened. - * : e.g. i < 5.5 became i <= 5, when i is an integer. - * - IntHoleAP : This is currently a catch-all for all integer specific reason. - */ -enum ArithProofType - { NoAP, - AssumeAP, - InternalAssumeAP, - FarkasAP, - TrichotomyAP, - EqualityEngineAP, - IntTightenAP, - IntHoleAP}; - -/** - * The types of constraints. - * The convex constraints are the constraints are LowerBound, Equality, - * and UpperBound. - */ -enum ConstraintType {LowerBound, Equality, UpperBound, Disequality}; - -typedef context::CDList CDConstraintList; - -typedef std::unordered_map NodetoConstraintMap; - -typedef size_t ConstraintRuleID; -static constexpr ConstraintRuleID ConstraintRuleIdSentinel = - std::numeric_limits::max(); - -typedef size_t AntecedentId; -static constexpr AntecedentId AntecedentIdSentinel = - std::numeric_limits::max(); - -typedef size_t AssertionOrder; -static constexpr AssertionOrder AssertionOrderSentinel = - std::numeric_limits::max(); - -/** - * A ValueCollection binds together convex constraints that have the same - * DeltaRational value. - */ -class ValueCollection { -private: - - ConstraintP d_lowerBound; - ConstraintP d_upperBound; - ConstraintP d_equality; - ConstraintP d_disequality; - -public: - ValueCollection(); - - static ValueCollection mkFromConstraint(ConstraintP c); - - bool hasLowerBound() const; - bool hasUpperBound() const; - bool hasEquality() const; - bool hasDisequality() const; - - bool hasConstraintOfType(ConstraintType t) const; - - ConstraintP getLowerBound() const; - ConstraintP getUpperBound() const; - ConstraintP getEquality() const; - ConstraintP getDisequality() const; - - ConstraintP getConstraintOfType(ConstraintType t) const; - - /** Returns true if any of the constraints are non-null. */ - bool empty() const; - - /** - * Remove the constraint of the type t from the collection. - * Returns true if the ValueCollection is now empty. - * If true is returned, d_value is now NULL. - */ - void remove(ConstraintType t); - - /** - * Adds a constraint to the set. - * The collection must not have a constraint of that type already. - */ - void add(ConstraintP c); - - void push_into(std::vector& vec) const; - - ConstraintP nonNull() const; - - ArithVar getVariable() const; - const DeltaRational& getValue() const; -}; - -/** - * A Map of ValueCollections sorted by the associated DeltaRational values. - * - * Discussion: - * While it is more natural to consider this a set, this cannot be a set as in - * sets the type of both iterator and const_iterator in sets are - * "constant iterators". We require iterators that dereference to - * ValueCollection&. - * - * See: - * http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#103 - */ -typedef std::map SortedConstraintMap; -typedef SortedConstraintMap::iterator SortedConstraintMapIterator; -typedef SortedConstraintMap::const_iterator SortedConstraintMapConstIterator; - -/** A Pair associating a variables and a Sorted ConstraintSet. */ -struct PerVariableDatabase{ - ArithVar d_var; - SortedConstraintMap d_constraints; - - // x ? c_1, x ? c_2, x ? c_3, ... - // where ? is a non-empty subset of {lb, ub, eq} - // c_1 < c_2 < c_3 < ... - - PerVariableDatabase(ArithVar v) : d_var(v), d_constraints() {} - - bool empty() const { - return d_constraints.empty(); - } - - static bool IsEmpty(const PerVariableDatabase& p){ - return p.empty(); - } -}; - -/** - * If proofs are on, there is a vector of rationals for farkas coefficients. - * This is the owner of the memory for the vector, and calls delete upon - * cleanup. - * - */ -struct ConstraintRule { - ConstraintP d_constraint; - ArithProofType d_proofType; - AntecedentId d_antecedentEnd; - - /** - * In this comment, we abbreviate ConstraintDatabase::d_antecedents - * and d_farkasCoefficients as ans and fc. - * - * This list is always empty if proofs are not enabled. - * - * If proofs are enabled, the proof of constraint c at p in ans[p] of length n - * is (NullConstraint, ans[p-(n-1)], ... , ans[p-1], ans[p]) - * - * Farkas' proofs show a contradiction with the negation of c, c_not = - * c->getNegation(). - * - * We treat the position for NullConstraint (p-n) as the position for the - * farkas coefficient for so we pretend c_not is ans[p-n]. So this correlation - * for the constraints we are going to use: (c_not, ans[p-n+(1)], ... , - * ans[p-n+(n-1)], ans[p-n+(n)]) With the coefficients at positions: (fc[0], - * fc[1)], ... fc[n]) - * - * The index of the constraints in the proof are {i | i <= 0 <= n] } (with - * c_not being p-n). Partition the indices into L, U, and E, the lower bounds, - * the upper bounds and equalities. - * - * We standardize the proofs to be upper bound oriented following the - * convention: A x <= b with the proof witness of the form (lambda) Ax <= - * (lambda) b and lambda >= 0. - * - * To accomplish this cleanly, the fc coefficients must be negative for lower - * bounds. The signs of equalities can be either positive or negative. - * - * Thus the proof corresponds to (with multiplication over inequalities): - * \sum_{u in U} fc[u] ans[p-n+u] + \sum_{e in E} fc[e] ans[p-n+e] - * + \sum_{l in L} fc[l] ans[p-n+l] - * |= 0 < 0 - * where fc[u] > 0, fc[l] < 0, and fc[e] != 0 (i.e. it can be either +/-). - * - * There is no requirement that the proof is minimal. - * We do however use all of the constraints by requiring non-zero - * coefficients. - */ - RationalVectorCP d_farkasCoefficients; - - ConstraintRule(); - ConstraintRule(ConstraintP con, ArithProofType pt); - ConstraintRule(ConstraintP con, - ArithProofType pt, - AntecedentId antecedentEnd); - ConstraintRule(ConstraintP con, - ArithProofType pt, - AntecedentId antecedentEnd, - RationalVectorCP coeffs); - - void print(std::ostream& out, bool produceProofs) const; -}; /* class ConstraintRule */ - -class Constraint { - - friend class ConstraintDatabase; - - public: - /** - * This begins construction of a minimal constraint. - * - * This should only be called by ConstraintDatabase. - * - * Because of circular dependencies a Constraint is not fully valid until - * initialize has been called on it. - */ - Constraint(ArithVar x, - ConstraintType t, - const DeltaRational& v, - bool produceProofs); - - /** - * Destructor for a constraint. - * This should only be called if safeToGarbageCollect() is true. - */ - ~Constraint(); - - static ConstraintType constraintTypeOfComparison(const Comparison& cmp); - - inline ConstraintType getType() const { - return d_type; - } - - inline ArithVar getVariable() const { - return d_variable; - } - - const DeltaRational& getValue() const { - return d_value; - } - - inline ConstraintP getNegation() const { - return d_negation; - } - - bool isEquality() const{ - return d_type == Equality; - } - bool isDisequality() const{ - return d_type == Disequality; - } - bool isLowerBound() const{ - return d_type == LowerBound; - } - bool isUpperBound() const{ - return d_type == UpperBound; - } - bool isStrictUpperBound() const{ - Assert(isUpperBound()); - return getValue().infinitesimalSgn() < 0; - } - - bool isStrictLowerBound() const{ - Assert(isLowerBound()); - return getValue().infinitesimalSgn() > 0; - } - - bool isSplit() const { - return d_split; - } - - /** - * Splits the node in the user context. - * Returns a lemma that is assumed to be true for the rest of the user context. - * Constraint must be an equality or disequality. - */ - TrustNode split(); - - bool canBePropagated() const { - return d_canBePropagated; - } - void setCanBePropagated(); - - /** - * Light wrapper for calling setCanBePropagated(), - * on this and this->d_negation. - */ - void setPreregistered(){ - setCanBePropagated(); - d_negation->setCanBePropagated(); - } - - bool assertedToTheTheory() const { - Assert((d_assertionOrder < AssertionOrderSentinel) != d_witness.isNull()); - return d_assertionOrder < AssertionOrderSentinel; - } - TNode getWitness() const { - Assert(assertedToTheTheory()); - return d_witness; - } - - bool assertedBefore(AssertionOrder time) const { - return d_assertionOrder < time; - } - - /** - * Sets the witness literal for a node being on the assertion stack. - * - * If the negation of the node is true, inConflict must be true. - * If the negation of the node is false, inConflict must be false. - * Hence, negationHasProof() == inConflict. - * - * This replaces: - * void setAssertedToTheTheory(TNode witness); - * void setAssertedToTheTheoryWithNegationTrue(TNode witness); - */ - void setAssertedToTheTheory(TNode witness, bool inConflict); - - bool hasLiteral() const { - return !d_literal.isNull(); - } - - void setLiteral(Node n); - - Node getLiteral() const { - Assert(hasLiteral()); - return d_literal; - } - - /** Gets a literal in the normal form suitable for proofs. - * That is, (sum of non-const monomials) >< const. - * - * This is a sister method to `getLiteral`, which returns a normal form - * literal, suitable for external solving use. - */ - Node getProofLiteral() const; - - /** - * Set the node as having a proof and being an assumption. - * The node must be assertedToTheTheory(). - * - * Precondition: negationHasProof() == inConflict. - * - * Replaces: - * selfExplaining(). - * selfExplainingWithNegationTrue(). - */ - void setAssumption(bool inConflict); - - /** Returns true if the node is an assumption.*/ - bool isAssumption() const; - - /** Whether we produce proofs */ - bool isProofProducing() const { return d_produceProofs; } - - /** Set the constraint to have an EqualityEngine proof. */ - void setEqualityEngineProof(); - bool hasEqualityEngineProof() const; - - /** Returns true if the node has a Farkas' proof. */ - bool hasFarkasProof() const; - - /** - * @brief Returns whether this constraint is provable using a Farkas - * proof applied to (possibly tightened) input assertions. - * - * An example of a constraint that has a simple Farkas proof: - * x <= 0 proven from x + y <= 0 and x - y <= 0. - * - * An example of another constraint that has a simple Farkas proof: - * x <= 0 proven from x + y <= 0 and x - y <= 0.5 for integers x, y - * (integer bound-tightening is applied first!). - * - * An example of a constraint that might be proven **without** a simple - * Farkas proof: - * x < 0 proven from not(x == 0) and not(x > 0). - * - * This could be proven internally by the arithmetic theory using - * `TrichotomyAP` as the proof type. - * - */ - bool hasSimpleFarkasProof() const; - /** - * Returns whether this constraint is an assumption or a tightened - * assumption. - */ - bool isPossiblyTightenedAssumption() const; - - /** Returns true if the node has a int bound tightening proof. */ - bool hasIntTightenProof() const; - - /** Returns true if the node has a int hole proof. */ - bool hasIntHoleProof() const; - - /** Returns true if the node has a trichotomy proof. */ - bool hasTrichotomyProof() const; - - void printProofTree(std::ostream & out, size_t depth = 0) const; - - /** - * A sets the constraint to be an internal assumption. - * - * This does not need to have a witness or an associated literal. - * This is always itself in the explanation fringe for both conflicts - * and propagation. - * This cannot be converted back into a Node conflict or explanation. - * - * This cannot have a proof or be asserted to the theory! - * - */ - void setInternalAssumption(bool inConflict); - bool isInternalAssumption() const; - - /** - * Returns a explanation of the constraint that is appropriate for conflicts. - * - * This is not appropriate for propagation! - * - * This is the minimum fringe of the implication tree s.t. - * every constraint is assertedToTheTheory() or hasEqualityEngineProof(). - */ - TrustNode externalExplainByAssertions() const; - - /** - * Writes an explanation of a constraint into the node builder. - * Pushes back an explanation that is acceptable to send to the sat solver. - * nb is assumed to be an AND. - * - * This is the minimum fringe of the implication tree s.t. - * every constraint is assertedToTheTheory() or hasEqualityEngineProof(). - * - * This is not appropriate for propagation! - * Use explainForPropagation() instead. - */ - std::shared_ptr externalExplainByAssertions(NodeBuilder& nb) const - { - return externalExplain(nb, AssertionOrderSentinel); - } - - /* Equivalent to calling externalExplainByAssertions on all constraints in b */ - static Node externalExplainByAssertions(const ConstraintCPVec& b); - static Node externalExplainByAssertions(ConstraintCP a, ConstraintCP b); - static Node externalExplainByAssertions(ConstraintCP a, ConstraintCP b, ConstraintCP c); - - /** - * This is the minimum fringe of the implication tree s.t. every constraint is - * - assertedToTheTheory(), - * - isInternalDecision() or - * - hasEqualityEngineProof(). - */ - static void assertionFringe(ConstraintCPVec& v); - static void assertionFringe(ConstraintCPVec& out, const ConstraintCPVec& in); - - /** The fringe of a farkas' proof. */ - bool onFringe() const { - return assertedToTheTheory() || isInternalAssumption() || hasEqualityEngineProof(); - } - - /** - * Returns an explanation of a propagation by the ConstraintDatabase. - * The constraint must have a proof. - * The constraint cannot be an assumption. - * - * This is the minimum fringe of the implication tree (excluding the - * constraint itself) s.t. every constraint is assertedToTheTheory() or - * hasEqualityEngineProof(). - * - * All return conjuncts were asserted before this constraint. - * - * Requires the given node to rewrite to the canonical literal for this - * constraint. - * - * @params n the literal to prove - * n must rewrite to the constraint's canonical literal - * - * @returns a trust node of the form: - * (=> explanation n) - */ - TrustNode externalExplainForPropagation(TNode n) const; - - /** - * Explain the constraint and its negation in terms of assertions. - * The constraint must be in conflict. - */ - TrustNode externalExplainConflict() const; - - /** The constraint is known to be true. */ - inline bool hasProof() const { - return d_crid != ConstraintRuleIdSentinel; - } - - /** The negation of the constraint is known to hold. */ - inline bool negationHasProof() const { - return d_negation->hasProof(); - } - - /** Neither the contraint has a proof nor the negation has a proof.*/ - bool truthIsUnknown() const { - return !hasProof() && !negationHasProof(); - } - - /** This is a synonym for hasProof(). */ - inline bool isTrue() const { - return hasProof(); - } - - /** Both the constraint and its negation are true. */ - inline bool inConflict() const { - return hasProof() && negationHasProof(); - } - - /** - * Returns the constraint that corresponds to taking - * x r ceiling(getValue()) where r is the node's getType(). - * Esstentially this is an up branch. - */ - ConstraintP getCeiling(); - - /** - * Returns the constraint that corresponds to taking - * x r floor(getValue()) where r is the node's getType(). - * Esstentially this is a down branch. - */ - ConstraintP getFloor(); - - static ConstraintP makeNegation(ArithVar v, - ConstraintType t, - const DeltaRational& r, - bool produceProofs); - - const ValueCollection& getValueCollection() const; - - - ConstraintP getStrictlyWeakerUpperBound(bool hasLiteral, bool mustBeAsserted) const; - ConstraintP getStrictlyWeakerLowerBound(bool hasLiteral, bool mustBeAsserted) const; - - /** - * Marks a the constraint c as being entailed by a. - * The Farkas proof 1*(a) + -1 (c) |= 0<0 - * - * After calling impliedByUnate(), the caller should either raise a conflict - * or try call tryToPropagate(). - */ - void impliedByUnate(ConstraintCP a, bool inConflict); - - /** - * Marks a the constraint c as being entailed by a. - * The reason has to do with integer bound tightening. - * - * After calling impliedByIntTighten(), the caller should either raise a conflict - * or try call tryToPropagate(). - */ - void impliedByIntTighten(ConstraintCP a, bool inConflict); - - /** - * Marks a the constraint c as being entailed by a. - * The reason has to do with integer reasoning. - * - * After calling impliedByIntHole(), the caller should either raise a conflict - * or try call tryToPropagate(). - */ - void impliedByIntHole(ConstraintCP a, bool inConflict); - - /** - * Marks a the constraint c as being entailed by a. - * The reason has to do with integer reasoning. - * - * After calling impliedByIntHole(), the caller should either raise a conflict - * or try call tryToPropagate(). - */ - void impliedByIntHole(const ConstraintCPVec& b, bool inConflict); - - /** - * This is a lemma of the form: - * x < d or x = d or x > d - * The current constraint c is one of the above constraints and {a,b} - * are the negation of the other two constraints. - * - * Preconditions: - * - negationHasProof() == inConflict. - * - * After calling impliedByTrichotomy(), the caller should either raise a conflict - * or try call tryToPropagate(). - */ - void impliedByTrichotomy(ConstraintCP a, ConstraintCP b, bool inConflict); - - /** - * Marks the node as having a Farkas proof. - * - * Preconditions: - * - coeffs == NULL if proofs are off. - * - See the comments for ConstraintRule for the form of coeffs when - * proofs are on. - * - negationHasProof() == inConflict. - * - * After calling impliedByFarkas(), the caller should either raise a conflict - * or try call tryToPropagate(). - */ - void impliedByFarkas(const ConstraintCPVec& b, RationalVectorCP coeffs, bool inConflict); - - /** - * Generates an implication node, B => getLiteral(), - * where B is the result of externalExplainByAssertions(b). - * Does not guarantee b is the explanation of the constraint. - */ - Node externalImplication(const ConstraintCPVec& b) const; - - /** - * Returns true if the variable is assigned the value dr, - * the constraint would be satisfied. - */ - bool satisfiedBy(const DeltaRational& dr) const; - - /** - * The node must have a proof already and be eligible for propagation! - * You probably want to call tryToPropagate() instead. - * - * Preconditions: - * - hasProof() - * - canBePropagated() - * - !assertedToTheTheory() - */ - void propagate(); - - /** - * If the constraint - * canBePropagated() and - * !assertedToTheTheory(), - * the constraint is added to the database's propagation queue. - * - * Precondition: - * - hasProof() - */ - void tryToPropagate(); - - /** - * Returns a reference to the containing database. - * Precondition: the constraint must be initialized. - */ - const ConstraintDatabase& getDatabase() const; - - /** Returns the constraint rule at the position. */ - const ConstraintRule& getConstraintRule() const; - - private: - /** Returns true if the constraint has been initialized. */ - bool initialized() const; - - /** - * This initializes the fields that cannot be set in the constructor due to - * circular dependencies. - */ - void initialize(ConstraintDatabase* db, - SortedConstraintMapIterator v, - ConstraintP negation); - - class ConstraintRuleCleanup - { - public: - inline void operator()(ConstraintRule* crp) - { - Assert(crp != NULL); - ConstraintP constraint = crp->d_constraint; - Assert(constraint->d_crid != ConstraintRuleIdSentinel); - constraint->d_crid = ConstraintRuleIdSentinel; - if (constraint->isProofProducing()) - { - if (crp->d_farkasCoefficients != RationalVectorCPSentinel) - { - delete crp->d_farkasCoefficients; - } - } - } - }; - - class CanBePropagatedCleanup - { - public: - inline void operator()(ConstraintP* p) - { - ConstraintP constraint = *p; - Assert(constraint->d_canBePropagated); - constraint->d_canBePropagated = false; - } - }; - - class AssertionOrderCleanup - { - public: - inline void operator()(ConstraintP* p) - { - ConstraintP constraint = *p; - Assert(constraint->assertedToTheTheory()); - constraint->d_assertionOrder = AssertionOrderSentinel; - constraint->d_witness = TNode::null(); - Assert(!constraint->assertedToTheTheory()); - } - }; - - class SplitCleanup - { - public: - inline void operator()(ConstraintP* p) - { - ConstraintP constraint = *p; - Assert(constraint->d_split); - constraint->d_split = false; - } - }; - - /** - * Returns true if the node is safe to garbage collect. - * Both it and its negation must have no context dependent data set. - */ - bool safeToGarbageCollect() const; - - /** - * Returns true if the constraint has no context dependent data set. - */ - bool contextDependentDataIsSet() const; - - /** - * Returns true if the node correctly corresponds to the constraint that is - * being set. - */ - bool sanityChecking(Node n) const; - - /** Returns a reference to the map for d_variable. */ - SortedConstraintMap& constraintSet() const; - - /** Returns coefficients for the proofs for farkas cancellation. */ - static std::pair unateFarkasSigns(ConstraintCP a, ConstraintCP b); - - Node externalExplain(AssertionOrder order) const; - /** - * Returns an explanation of that was assertedBefore(order). - * The constraint must have a proof. - * The constraint cannot be selfExplaining(). - * - * This is the minimum fringe of the implication tree - * s.t. every constraint is assertedBefore(order) or hasEqualityEngineProof(). - */ - std::shared_ptr externalExplain(NodeBuilder& nb, - AssertionOrder order) const; - - static Node externalExplain(const ConstraintCPVec& b, AssertionOrder order); - - inline ArithProofType getProofType() const { - return getConstraintRule().d_proofType; - } - - inline AntecedentId getEndAntecedent() const { - return getConstraintRule().d_antecedentEnd; - } - - inline RationalVectorCP getFarkasCoefficients() const - { - return d_produceProofs ? getConstraintRule().d_farkasCoefficients : nullptr; - } - - /** - * The proof of the node is empty. - * The proof must be a special proof. Either - * isSelfExplaining() or - * hasEqualityEngineProof() - */ - bool antecentListIsEmpty() const; - - bool antecedentListLengthIsOne() const; - - /** Return true if every element in b has a proof. */ - static bool allHaveProof(const ConstraintCPVec& b); - - /** Precondition: hasFarkasProof() - * Computes the combination implied by the farkas coefficients. Sees if it is - * a contradiction. - */ - - bool wellFormedFarkasProof() const; - - /** The ArithVar associated with the constraint. */ - const ArithVar d_variable; - - /** The type of the Constraint. */ - const ConstraintType d_type; - - /** The DeltaRational value with the constraint. */ - const DeltaRational d_value; - - /** A pointer to the associated database for the Constraint. */ - ConstraintDatabase* d_database; - - /** - * The node to be communicated with the TheoryEngine. - * - * This is not context dependent, but may be set once. - * - * This must be set if the constraint canBePropagated(). - * This must be set if the constraint assertedToTheTheory(). - * Otherwise, this may be null(). - */ - Node d_literal; - - /** Pointer to the negation of the Constraint. */ - ConstraintP d_negation; - - /** - * This is true if the associated node can be propagated. - * - * This should be enabled if the node has been preregistered. - * - * Sat Context Dependent. - * This is initially false. - */ - bool d_canBePropagated; - - /** - * This is the order the constraint was asserted to the theory. - * If this has been set, the node can be used in conflicts. - * If this is c.d_assertedOrder < d.d_assertedOrder, then c can be used in the - * explanation of d. - * - * This should be set after the literal is dequeued by Theory::get(). - * - * Sat Context Dependent. - * This is initially AssertionOrderSentinel. - */ - AssertionOrder d_assertionOrder; - - /** - * This is guaranteed to be on the fact queue. - * For example if x + y = x + 1 is on the fact queue, then use this - */ - TNode d_witness; - - /** - * The position of the constraint in the constraint rule id. - * - * Sat Context Dependent. - * This is initially - */ - ConstraintRuleID d_crid; - - /** - * True if the equality has been split. - * Only meaningful if ConstraintType == Equality. - * - * User Context Dependent. - * This is initially false. - */ - bool d_split; - - /** - * Position in sorted constraint set for the variable. - * Unset if d_type is Disequality. - */ - SortedConstraintMapIterator d_variablePosition; - - /** Whether to produce proofs, */ - bool d_produceProofs; - -}; /* class ConstraintValue */ - -std::ostream& operator<<(std::ostream& o, const Constraint& c); -std::ostream& operator<<(std::ostream& o, const ConstraintP c); -std::ostream& operator<<(std::ostream& o, const ConstraintCP c); -std::ostream& operator<<(std::ostream& o, const ConstraintType t); -std::ostream& operator<<(std::ostream& o, const ValueCollection& c); -std::ostream& operator<<(std::ostream& o, const ConstraintCPVec& v); -std::ostream& operator<<(std::ostream& o, const ArithProofType); - -class ConstraintDatabase : protected EnvObj -{ - private: - /** - * The map from ArithVars to their unique databases. - * When the vector changes size, we cannot allow the maps to move so this - * is a vector of pointers. - */ - std::vector d_varDatabases; - - SortedConstraintMap& getVariableSCM(ArithVar v) const; - - /** Maps literals to constraints.*/ - NodetoConstraintMap d_nodetoConstraintMap; - - /** - * A queue of propagated constraints. - * ConstraintCP are pointers. - * The elements of the queue do not require destruction. - */ - context::CDQueue d_toPropagate; - - /** - * Proofs are lists of valid constraints terminated by the first null - * sentinel value in the proof list. - * We abbreviate d_antecedents as ans in the comment. - * - * The proof at p in ans[p] of length n is - * (NullConstraint, ans[p-(n-1)], ... , ans[p-1], ans[p]) - * - * The proof at p corresponds to the conjunction: - * (and x_i) - * - * So the proof of a Constraint c corresponds to the horn clause: - * (implies (and x_i) c) - * where (and x_i) is the proof at c.d_crid d_antecedentEnd. - * - * Constraints are pointers so this list is designed not to require any destruction. - */ - CDConstraintList d_antecedents; - - typedef context::CDList - ConstraintRuleList; - typedef context::CDList - CBPList; - typedef context::CDList - AOList; - typedef context::CDList SplitList; - - /** - * The watch lists are collected together as they need to be garbage collected - * carefully. - */ - struct Watches{ - - /** - * Contains the exact list of constraints that have a proof. - * Upon pop, this unsets d_crid to NoAP. - * - * The index in this list is the proper ordering of the proofs. - */ - ConstraintRuleList d_constraintProofs; - - /** - * Contains the exact list of constraints that can be used for propagation. - */ - CBPList d_canBePropagatedWatches; - - /** - * Contains the exact list of constraints that have been asserted to the theory. - */ - AOList d_assertionOrderWatches; - - - /** - * Contains the exact list of atoms that have been preregistered. - * This is a pointer as it must be destroyed before the elements of - * d_varDatabases. - */ - SplitList d_splitWatches; - Watches(context::Context* satContext, context::Context* userContext); - }; - Watches* d_watches; - - void pushSplitWatch(ConstraintP c); - void pushCanBePropagatedWatch(ConstraintP c); - void pushAssertionOrderWatch(ConstraintP c, TNode witness); - - /** Assumes that antecedents have already been pushed. */ - void pushConstraintRule(const ConstraintRule& crp); - - /** Returns true if all of the entries of the vector are empty. */ - static bool emptyDatabase(const std::vector& vec); - - /** Map from nodes to arithvars. */ - const ArithVariables& d_avariables; - - const ArithVariables& getArithVariables() const{ - return d_avariables; - } - - ArithCongruenceManager& d_congruenceManager; - - /** Owned by the TheoryArithPrivate, used here. */ - EagerProofGenerator* d_pfGen; - /** Owned by the TheoryArithPrivate, used here. */ - ProofNodeManager* d_pnm; - - RaiseConflict d_raiseConflict; - - - const Rational d_one; - const Rational d_negOne; - - friend class Constraint; - - public: - ConstraintDatabase(Env& env, - const ArithVariables& variables, - ArithCongruenceManager& dm, - RaiseConflict conflictCallBack, - EagerProofGenerator* pfGen); - - ~ConstraintDatabase(); - - /** Adds a literal to the database. */ - ConstraintP addLiteral(TNode lit); - - /** - * If hasLiteral() is true, returns the constraint. - * Otherwise, returns NullConstraint. - */ - ConstraintP lookup(TNode literal) const; - - /** - * Returns true if the literal has been added to the database. - * This is a hash table lookup. - * It does not look in the database for an equivalent corresponding constraint. - */ - bool hasLiteral(TNode literal) const; - - bool hasMorePropagations() const{ - return !d_toPropagate.empty(); - } - - ConstraintCP nextPropagation(){ - Assert(hasMorePropagations()); - - ConstraintCP p = d_toPropagate.front(); - d_toPropagate.pop(); - - return p; - } - - void addVariable(ArithVar v); - bool variableDatabaseIsSetup(ArithVar v) const; - void removeVariable(ArithVar v); - - /** Get an explanation and proof for this constraint from the equality engine - */ - TrustNode eeExplain(ConstraintCP c) const; - /** Get an explanation for this constraint from the equality engine */ - void eeExplain(ConstraintCP c, NodeBuilder& nb) const; - - /** - * Returns a constraint with the variable v, the constraint type t, and a value - * dominated by r (explained below) if such a constraint exists in the database. - * If no such constraint exists, NullConstraint is returned. - * - * t must be either UpperBound or LowerBound. - * The returned value v is dominated: - * If t is UpperBound, r <= v - * If t is LowerBound, r >= v - * - * variableDatabaseIsSetup(v) must be true. - */ - ConstraintP getBestImpliedBound(ArithVar v, ConstraintType t, const DeltaRational& r) const; - - /** Returns the constraint, if it exists */ - ConstraintP lookupConstraint(ArithVar v, ConstraintType t, const DeltaRational& r) const; - - /** - * Returns a constraint with the variable v, the constraint type t and the value r. - * If there is such a constraint in the database already, it is returned. - * If there is no such constraint, this constraint is added to the database. - * - */ - ConstraintP getConstraint(ArithVar v, ConstraintType t, const DeltaRational& r); - - /** - * Returns a constraint of the given type for the value and variable - * for the given ValueCollection, vc. - * This is made if there is no such constraint. - */ - ConstraintP ensureConstraint(ValueCollection& vc, ConstraintType t); - - - void deleteConstraintAndNegation(ConstraintP c); - - /** Given constraints `a` and `b` such that `a OR b` by unate reasoning, - * adds a TrustNode to `out` which proves `a OR b` as a lemma. - * - * Example: `x <= 5` OR `5 <= x`. - */ - void proveOr(std::vector& out, - ConstraintP a, - ConstraintP b, - bool negateSecond) const; - /** Given constraints `a` and `b` such that `a` implies `b` by unate - * reasoning, adds a TrustNode to `out` which proves `-a OR b` as a lemma. - * - * Example: `x >= 5` -> `x >= 4`. - */ - void implies(std::vector& out, ConstraintP a, ConstraintP b) const; - /** Given constraints `a` and `b` such that `not(a AND b)` by unate reasoning, - * adds a TrustNode to `out` which proves `-a OR -b` as a lemma. - * - * Example: `x >= 4` -> `x <= 3`. - */ - void mutuallyExclusive(std::vector& out, - ConstraintP a, - ConstraintP b) const; - - /** - * Outputs a minimal set of unate implications onto the vector for the variable. - * This outputs lemmas of the general forms - * (= p c) implies (<= p d) for c < d, or - * (= p c) implies (not (= p d)) for c != d. - */ - void outputUnateEqualityLemmas(std::vector& lemmas) const; - void outputUnateEqualityLemmas(std::vector& lemmas, - ArithVar v) const; - - /** - * Outputs a minimal set of unate implications onto the vector for the variable. - * - * If ineqs is true, this outputs lemmas of the general form - * (<= p c) implies (<= p d) for c < d. - */ - void outputUnateInequalityLemmas(std::vector& lemmas) const; - void outputUnateInequalityLemmas(std::vector& lemmas, - ArithVar v) const; - - void unatePropLowerBound(ConstraintP curr, ConstraintP prev); - void unatePropUpperBound(ConstraintP curr, ConstraintP prev); - void unatePropEquality(ConstraintP curr, ConstraintP prevLB, ConstraintP prevUB); - - /** AntecendentID must be in range. */ - ConstraintCP getAntecedent(AntecedentId p) const; - - bool isProofEnabled() const { return d_pnm != nullptr; } - - private: - /** returns true if cons is now in conflict. */ - bool handleUnateProp(ConstraintP ant, ConstraintP cons); - - DenseSet d_reclaimable; - - class Statistics { - public: - IntStat d_unatePropagateCalls; - IntStat d_unatePropagateImplications; - - Statistics(); - } d_statistics; - -}; /* ConstraintDatabase */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__CONSTRAINT_H */ diff --git a/src/theory/arith/constraint_forward.h b/src/theory/arith/constraint_forward.h deleted file mode 100644 index 5b700ceeb..000000000 --- a/src/theory/arith/constraint_forward.h +++ /dev/null @@ -1,53 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Aina Niemetz - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * Forward declarations of the ConstraintValue and ConstraintDatabase - * classes. - * - * This is the forward declarations of the ConstraintValue and - * ConstraintDatabase and the typedef for Constraint. - * This is used to break circular dependencies and minimize interaction - * between header files. - */ - -#ifndef CVC5__THEORY__ARITH__CONSTRAINT_FORWARD_H -#define CVC5__THEORY__ARITH__CONSTRAINT_FORWARD_H - -#include - -#include "cvc5_private.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class Constraint; -typedef Constraint* ConstraintP; -typedef const Constraint* ConstraintCP; - -static constexpr ConstraintP NullConstraint = nullptr; - -class ConstraintDatabase; - -typedef std::vector ConstraintCPVec; - -typedef std::vector RationalVector; -typedef RationalVector* RationalVectorP; -typedef const RationalVector* RationalVectorCP; -static constexpr RationalVectorCP RationalVectorCPSentinel = nullptr; -static constexpr RationalVectorP RationalVectorPSentinel = nullptr; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__CONSTRAINT_FORWARD_H */ diff --git a/src/theory/arith/cut_log.cpp b/src/theory/arith/cut_log.cpp deleted file mode 100644 index 0fc239e53..000000000 --- a/src/theory/arith/cut_log.cpp +++ /dev/null @@ -1,711 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andrew Reynolds, Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/cut_log.h" - -#include -#include - -#include -#include -#include - -#include "base/cvc5config.h" -#include "base/output.h" -#include "theory/arith/approx_simplex.h" -#include "theory/arith/constraint.h" -#include "theory/arith/normal_form.h" -#include "util/ostream_util.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -NodeLog::const_iterator NodeLog::begin() const { return d_cuts.begin(); } -NodeLog::const_iterator NodeLog::end() const { return d_cuts.end(); } - -NodeLog& TreeLog::getNode(int nid) { - ToNodeMap::iterator i = d_toNode.find(nid); - Assert(i != d_toNode.end()); - return (*i).second; -} - -TreeLog::const_iterator TreeLog::begin() const { return d_toNode.begin(); } -TreeLog::const_iterator TreeLog::end() const { return d_toNode.end(); } - -int TreeLog::getExecutionOrd(){ - int res = next_exec_ord; - ++next_exec_ord; - return res; -} -void TreeLog::makeInactive(){ d_active = false; } -void TreeLog::makeActive(){ d_active = true; } -bool TreeLog::isActivelyLogging() const { return d_active; } - - -PrimitiveVec::PrimitiveVec() - : len(0) - , inds(NULL) - , coeffs(NULL) -{} - -PrimitiveVec::~PrimitiveVec(){ - clear(); -} -bool PrimitiveVec::initialized() const { - return inds != NULL; -} -void PrimitiveVec::clear() { - if(initialized()){ - delete[] inds; - delete[] coeffs; - len = 0; - inds = NULL; - coeffs = NULL; - } -} -void PrimitiveVec::setup(int l){ - Assert(!initialized()); - len = l; - inds = new int[1+len]; - coeffs = new double[1+len]; -} -void PrimitiveVec::print(std::ostream& out) const{ - Assert(initialized()); - StreamFormatScope scope(out); - - out << len << " " << std::setprecision(15); - for(int i = 1; i <= len; ++i){ - out << "["<< inds[i] <<", " << coeffs[i]<<"]"; - } -} -std::ostream& operator<<(std::ostream& os, const PrimitiveVec& pv){ - pv.print(os); - return os; -} - -CutInfo::CutInfo(CutInfoKlass kl, int eid, int o) - : d_klass(kl), - d_execOrd(eid), - d_poolOrd(o), - d_cutType(kind::UNDEFINED_KIND), - d_cutRhs(), - d_cutVec(), - d_mAtCreation(-1), - d_N(-1), - d_rowId(-1), - d_exactPrecision(nullptr), - d_explanation(nullptr) -{} - -CutInfo::~CutInfo(){ -} - -int CutInfo::getId() const { - return d_execOrd; -} - -int CutInfo::getRowId() const{ - return d_rowId; -} - -void CutInfo::setRowId(int rid){ - d_rowId = rid; -} - -void CutInfo::print(ostream& out) const{ - out << "[CutInfo " << d_execOrd << " " << d_poolOrd - << " " << d_klass << " " << d_cutType << " " << d_cutRhs - << " "; - d_cutVec.print(out); - out << "]" << endl; -} - -PrimitiveVec& CutInfo::getCutVector(){ - return d_cutVec; -} - -const PrimitiveVec& CutInfo::getCutVector() const{ - return d_cutVec; -} - -// void CutInfo::init_cut(int l){ -// cut_vec.setup(l); -// } - -Kind CutInfo::getKind() const{ - return d_cutType; -} - -void CutInfo::setKind(Kind k){ - Assert(k == kind::LEQ || k == kind::GEQ); - d_cutType = k; -} - -double CutInfo::getRhs() const{ - return d_cutRhs; -} - -void CutInfo::setRhs(double r){ - d_cutRhs = r; -} - -bool CutInfo::reconstructed() const { return d_exactPrecision != nullptr; } - -CutInfoKlass CutInfo::getKlass() const{ - return d_klass; -} - -int CutInfo::poolOrdinal() const{ - return d_poolOrd; -} - -void CutInfo::setDimensions(int N, int M){ - d_mAtCreation = M; - d_N = N; -} - -int CutInfo::getN() const{ - return d_N; -} - -int CutInfo::getMAtCreation() const{ - return d_mAtCreation; -} - -/* Returns true if the cut has an explanation. */ -bool CutInfo::proven() const { return d_explanation != nullptr; } - -bool CutInfo::operator<(const CutInfo& o) const{ - return d_execOrd < o.d_execOrd; -} - - -void CutInfo::setReconstruction(const DenseVector& ep){ - Assert(!reconstructed()); - d_exactPrecision.reset(new DenseVector(ep)); -} - -void CutInfo::setExplanation(const ConstraintCPVec& ex){ - Assert(reconstructed()); - if (d_explanation == nullptr) - { - d_explanation.reset(new ConstraintCPVec(ex)); - } - else - { - *d_explanation = ex; - } -} - -void CutInfo::swapExplanation(ConstraintCPVec& ex){ - Assert(reconstructed()); - Assert(!proven()); - if (d_explanation == nullptr) - { - d_explanation.reset(new ConstraintCPVec()); - } - d_explanation->swap(ex); -} - -const DenseVector& CutInfo::getReconstruction() const { - Assert(reconstructed()); - return *d_exactPrecision; -} - -void CutInfo::clearReconstruction(){ - if(proven()){ - d_explanation = nullptr; - } - - if(reconstructed()){ - d_exactPrecision = nullptr; - } - - Assert(!reconstructed()); - Assert(!proven()); -} - -const ConstraintCPVec& CutInfo::getExplanation() const { - Assert(proven()); - return *d_explanation; -} - -std::ostream& operator<<(std::ostream& os, const CutInfo& ci){ - ci.print(os); - return os; -} - -std::ostream& operator<<(std::ostream& out, CutInfoKlass kl){ - switch(kl){ - case MirCutKlass: - out << "MirCutKlass"; break; - case GmiCutKlass: - out << "GmiCutKlass"; break; - case BranchCutKlass: - out << "BranchCutKlass"; break; - case RowsDeletedKlass: - out << "RowDeletedKlass"; break; - case UnknownKlass: - out << "UnknownKlass"; break; - default: - out << "unexpected CutInfoKlass"; break; - } - return out; -} -bool NodeLog::isBranch() const{ - return d_brVar >= 0; -} - -NodeLog::NodeLog() - : d_nid(-1) - , d_parent(NULL) - , d_tl(NULL) - , d_cuts() - , d_rowIdsSelected() - , d_stat(Open) - , d_brVar(-1) - , d_brVal(0.0) - , d_downId(-1) - , d_upId(-1) - , d_rowId2ArithVar() -{} - -NodeLog::NodeLog(TreeLog* tl, int node, const RowIdMap& m) - : d_nid(node) - , d_parent(NULL) - , d_tl(tl) - , d_cuts() - , d_rowIdsSelected() - , d_stat(Open) - , d_brVar(-1) - , d_brVal(0.0) - , d_downId(-1) - , d_upId(-1) - , d_rowId2ArithVar(m) -{} - -NodeLog::NodeLog(TreeLog* tl, NodeLog* parent, int node) - : d_nid(node) - , d_parent(parent) - , d_tl(tl) - , d_cuts() - , d_rowIdsSelected() - , d_stat(Open) - , d_brVar(-1) - , d_brVal(0.0) - , d_downId(-1) - , d_upId(-1) - , d_rowId2ArithVar() -{} - -NodeLog::~NodeLog(){ - CutSet::iterator i = d_cuts.begin(), iend = d_cuts.end(); - for(; i != iend; ++i){ - CutInfo* c = *i; - delete c; - } - d_cuts.clear(); - Assert(d_cuts.empty()); -} - -std::ostream& operator<<(std::ostream& os, const NodeLog& nl){ - nl.print(os); - return os; -} - -void NodeLog::copyParentRowIds() { - Assert(d_parent != NULL); - d_rowId2ArithVar = d_parent->d_rowId2ArithVar; -} - -int NodeLog::branchVariable() const { - return d_brVar; -} -double NodeLog::branchValue() const{ - return d_brVal; -} -int NodeLog::getNodeId() const { - return d_nid; -} -int NodeLog::getDownId() const{ - return d_downId; -} -int NodeLog::getUpId() const{ - return d_upId; -} -void NodeLog::addSelected(int ord, int sel){ - Assert(d_rowIdsSelected.find(ord) == d_rowIdsSelected.end()); - d_rowIdsSelected[ord] = sel; - Trace("approx::nodelog") << "addSelected("<< ord << ", "<< sel << ")" << endl; -} -void NodeLog::applySelected() { - CutSet::iterator iter = d_cuts.begin(), iend = d_cuts.end(), todelete; - while(iter != iend){ - CutInfo* curr = *iter; - int poolOrd = curr->poolOrdinal(); - if(curr->getRowId() >= 0 ){ - // selected previously, kip - ++iter; - }else if(curr->getKlass() == RowsDeletedKlass){ - // skip - ++iter; - }else if(curr->getKlass() == BranchCutKlass){ - // skip - ++iter; - }else if(d_rowIdsSelected.find(poolOrd) == d_rowIdsSelected.end()){ - todelete = iter; - ++iter; - d_cuts.erase(todelete); - delete curr; - }else{ - Trace("approx::nodelog") << "applySelected " << curr->getId() << " " << poolOrd << "->" << d_rowIdsSelected[poolOrd] << endl; - curr->setRowId( d_rowIdsSelected[poolOrd] ); - ++iter; - } - } - d_rowIdsSelected.clear(); -} - -void NodeLog::applyRowsDeleted(const RowsDeleted& rd) { - std::map currInOrd; //sorted - - const PrimitiveVec& cv = rd.getCutVector(); - std::vector sortedRemoved (cv.inds+1, cv.inds+cv.len+1); - sortedRemoved.push_back(INT_MAX); - std::sort(sortedRemoved.begin(), sortedRemoved.end()); - - if(TraceIsOn("approx::nodelog")){ - Trace("approx::nodelog") << "Removing #" << sortedRemoved.size()<< "..."; - for(unsigned k = 0; kgetId() < rd.getId()){ - if(d_rowId2ArithVar.find(curr->getRowId()) != d_rowId2ArithVar.end()){ - if(curr->getRowId() >= min){ - currInOrd.insert(make_pair(curr->getRowId(), curr)); - } - } - } - ++iter; - } - - RowIdMap::const_iterator i, end; - i=d_rowId2ArithVar.begin(), end = d_rowId2ArithVar.end(); - for(; i != end; ++i){ - int key = (*i).first; - if(key >= min){ - if(currInOrd.find(key) == currInOrd.end()){ - CutInfo* null = NULL; - currInOrd.insert(make_pair(key, null)); - } - } - } - - - - std::map::iterator j, jend; - - int posInSorted = 0; - for(j = currInOrd.begin(), jend=currInOrd.end(); j!=jend; ++j){ - int origOrd = (*j).first; - ArithVar v = d_rowId2ArithVar[origOrd]; - int headRemovedOrd = sortedRemoved[posInSorted]; - while(headRemovedOrd < origOrd){ - ++posInSorted; - headRemovedOrd = sortedRemoved[posInSorted]; - } - // headRemoveOrd >= origOrd - Assert(headRemovedOrd >= origOrd); - - CutInfo* ci = (*j).second; - if(headRemovedOrd == origOrd){ - - if(ci == NULL){ - Trace("approx::nodelog") << "deleting from above because of " << rd << endl; - Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; - d_rowId2ArithVar.erase(origOrd); - }else{ - Trace("approx::nodelog") << "deleting " << ci << " because of " << rd << endl; - Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; - d_rowId2ArithVar.erase(origOrd); - ci->setRowId(-1); - } - }else{ - Assert(headRemovedOrd > origOrd); - // headRemoveOrd > origOrd - int newOrd = origOrd - posInSorted; - Assert(newOrd > 0); - if(ci == NULL){ - Trace("approx::nodelog") << "shifting above down due to " << rd << endl; - Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; - Trace("approx::nodelog") << "now have " << newOrd << " <-> " << v << endl; - d_rowId2ArithVar.erase(origOrd); - mapRowId(newOrd, v); - }else{ - Trace("approx::nodelog") << "shifting " << ci << " down due to " << rd << endl; - Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; - Trace("approx::nodelog") << "now have " << newOrd << " <-> " << v << endl; - ci->setRowId(newOrd); - d_rowId2ArithVar.erase(origOrd); - mapRowId(newOrd, v); - } - } - } - -} - -// void NodeLog::adjustRowId(CutInfo& ci, const RowsDeleted& rd) { -// int origRowId = ci.getRowId(); -// int newRowId = ci.getRowId(); -// ArithVar v = d_rowId2ArithVar[origRowId]; - -// const PrimitiveVec& cv = rd.getCutVector(); - -// for(int j = 1, N = cv.len; j <= N; j++){ -// int ind = cv.inds[j]; -// if(ind == origRowId){ -// newRowId = -1; -// break; -// }else if(ind < origRowId){ -// newRowId--; -// } -// } - -// if(newRowId < 0){ -// cout << "deleting " << ci << " because of " << rd << endl; -// cout << "had " << origRowId << " <-> " << v << endl; -// d_rowId2ArithVar.erase(origRowId); -// ci.setRowId(-1); -// }else if(newRowId != origRowId){ -// cout << "adjusting " << ci << " because of " << rd << endl; -// cout << "had " << origRowId << " <-> " << v << endl; -// cout << "now have " << newRowId << " <-> " << v << endl; -// d_rowId2ArithVar.erase(origRowId); -// ci.setRowId(newRowId); -// mapRowId(newRowId, v); -// }else{ -// cout << "row id unchanged " << ci << " because of " << rd << endl; -// } -// } - - -ArithVar NodeLog::lookupRowId(int rowId) const{ - RowIdMap::const_iterator i = d_rowId2ArithVar.find(rowId); - if(i == d_rowId2ArithVar.end()){ - return ARITHVAR_SENTINEL; - }else{ - return (*i).second; - } -} - -void NodeLog::mapRowId(int rowId, ArithVar v){ - Assert(lookupRowId(rowId) == ARITHVAR_SENTINEL); - Trace("approx::nodelog") - << "On " << getNodeId() - << " adding row id " << rowId << " <-> " << v << endl; - d_rowId2ArithVar[rowId] = v; -} - - - -void NodeLog::addCut(CutInfo* ci){ - Assert(ci != NULL); - d_cuts.insert(ci); -} - -void NodeLog::print(ostream& o) const{ - o << "[n" << getNodeId(); - for(const_iterator iter = begin(), iend = end(); iter != iend; ++iter ){ - CutInfo* cut = *iter; - o << ", " << cut->poolOrdinal(); - if(cut->getRowId() >= 0){ - o << " " << cut->getRowId(); - } - } - o << "]" << std::endl; -} - -void NodeLog::closeNode(){ - Assert(d_stat == Open); - d_stat = Closed; -} - -void NodeLog::setBranch(int br, double val, int d, int u){ - Assert(d_stat == Open); - d_brVar = br; - d_brVal = val; - d_downId = d; - d_upId = u; - d_stat = Branched; -} - -TreeLog::TreeLog() - : next_exec_ord(0) - , d_toNode() - , d_branches() - , d_numCuts(0) - , d_active(false) -{ - NodeLog::RowIdMap empty; - reset(empty); -} - -int TreeLog::getRootId() const{ - return 1; -} - -NodeLog& TreeLog::getRootNode(){ - return getNode(getRootId()); -} - -void TreeLog::clear(){ - next_exec_ord = 0; - d_toNode.clear(); - d_branches.purge(); - - d_numCuts = 0; - - // add root -} - -void TreeLog::reset(const NodeLog::RowIdMap& m){ - clear(); - d_toNode.insert(make_pair(getRootId(), NodeLog(this, getRootId(), m))); -} - -void TreeLog::addCut(){ d_numCuts++; } -uint32_t TreeLog::cutCount() const { return d_numCuts; } -void TreeLog::logBranch(uint32_t x){ - d_branches.add(x); -} -uint32_t TreeLog::numBranches(uint32_t x){ - return d_branches.count(x); -} - -void TreeLog::branch(int nid, int br, double val, int dn, int up){ - NodeLog& nl = getNode(nid); - nl.setBranch(br, val, dn, up); - - d_toNode.insert(make_pair(dn, NodeLog(this, &nl, dn))); - d_toNode.insert(make_pair(up, NodeLog(this, &nl, up))); -} - -void TreeLog::close(int nid){ - NodeLog& nl = getNode(nid); - nl.closeNode(); -} - - - -// void TreeLog::applySelected() { -// std::map::iterator iter, end; -// for(iter = d_toNode.begin(), end = d_toNode.end(); iter != end; ++iter){ -// NodeLog& onNode = (*iter).second; -// //onNode.applySelected(); -// } -// } - -void TreeLog::print(ostream& o) const{ - o << "TreeLog: " << d_toNode.size() << std::endl; - for(const_iterator iter = begin(), iend = end(); iter != iend; ++iter){ - const NodeLog& onNode = (*iter).second; - onNode.print(o); - } -} - -void TreeLog::applyRowsDeleted(int nid, const RowsDeleted& rd){ - NodeLog& nl = getNode(nid); - nl.applyRowsDeleted(rd); -} - -void TreeLog::mapRowId(int nid, int ind, ArithVar v){ - NodeLog& nl = getNode(nid); - nl.mapRowId(ind, v); -} - -void DenseVector::purge() { - lhs.purge(); - rhs = Rational(0); -} - -RowsDeleted::RowsDeleted(int execOrd, int nrows, const int num[]) - : CutInfo(RowsDeletedKlass, execOrd, 0) -{ - d_cutVec.setup(nrows); - for(int j=1; j <= nrows; j++){ - d_cutVec.coeffs[j] = 0; - d_cutVec.inds[j] = num[j]; - } -} - -BranchCutInfo::BranchCutInfo(int execOrd, int br, Kind dir, double val) - : CutInfo(BranchCutKlass, execOrd, 0) -{ - d_cutVec.setup(1); - d_cutVec.inds[1] = br; - d_cutVec.coeffs[1] = +1.0; - d_cutRhs = val; - d_cutType = dir; -} - -void TreeLog::printBranchInfo(ostream& os) const{ - uint32_t total = 0; - DenseMultiset::const_iterator iter = d_branches.begin(), iend = d_branches.end(); - for(; iter != iend; ++iter){ - uint32_t el = *iter; - total += el; - } - os << "printBranchInfo() : " << total << endl; - iter = d_branches.begin(), iend = d_branches.end(); - for(; iter != iend; ++iter){ - uint32_t el = *iter; - os << "["<& v){ - out << "[DenseVec len " << v.size(); - DenseMap::const_iterator iter, end; - for(iter = v.begin(), end = v.end(); iter != end; ++iter){ - ArithVar x = *iter; - out << ", "<< x << " " << v[x]; - } - out << "]"; -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/cut_log.h b/src/theory/arith/cut_log.h deleted file mode 100644 index 2cf3eac4d..000000000 --- a/src/theory/arith/cut_log.h +++ /dev/null @@ -1,295 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Morgan Deters, Kshitij Bansal - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include -#include -#include -#include - -#include "expr/kind.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/constraint_forward.h" -#include "util/dense_map.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -/** A low level vector of indexed doubles. */ -struct PrimitiveVec { - int len; - int* inds; - double* coeffs; - PrimitiveVec(); - ~PrimitiveVec(); - bool initialized() const; - void clear(); - void setup(int l); - void print(std::ostream& out) const; -}; -std::ostream& operator<<(std::ostream& os, const PrimitiveVec& pv); - -struct DenseVector { - DenseMap lhs; - Rational rhs; - void purge(); - void print(std::ostream& os) const; - - static void print(std::ostream& os, const DenseMap& lhs); -}; - -/** The different kinds of cuts. */ -enum CutInfoKlass{ MirCutKlass, GmiCutKlass, BranchCutKlass, - RowsDeletedKlass, - UnknownKlass}; -std::ostream& operator<<(std::ostream& os, CutInfoKlass kl); - -/** A general class for describing a cut. */ -class CutInfo { -protected: - CutInfoKlass d_klass; - int d_execOrd; - - int d_poolOrd; /* cut's ordinal in the current node pool */ - Kind d_cutType; /* Lowerbound, upperbound or undefined. */ - double d_cutRhs; /* right hand side of the cut */ - PrimitiveVec d_cutVec; /* vector of the cut */ - - /** - * The number of rows at the time the cut was made. - * This is required to descramble indices after the fact! - */ - int d_mAtCreation; - - /** This is the number of structural variables. */ - int d_N; - - /** if selected, make this non-zero */ - int d_rowId; - - /* If the cut has been successfully created, - * the cut is stored in exact precision in d_exactPrecision. - * If the cut has not yet been proven, this is null. - */ - std::unique_ptr d_exactPrecision; - - std::unique_ptr d_explanation; - - public: - CutInfo(CutInfoKlass kl, int cutid, int ordinal); - - virtual ~CutInfo(); - - int getId() const; - - int getRowId() const; - void setRowId(int rid); - - void print(std::ostream& out) const; - //void init_cut(int l); - PrimitiveVec& getCutVector(); - const PrimitiveVec& getCutVector() const; - - Kind getKind() const; - void setKind(Kind k); - - - void setRhs(double r); - double getRhs() const; - - CutInfoKlass getKlass() const; - int poolOrdinal() const; - - void setDimensions(int N, int M); - int getN() const; - int getMAtCreation() const; - - bool operator<(const CutInfo& o) const; - - /* Returns true if the cut was successfully made in exact precision.*/ - bool reconstructed() const; - - /* Returns true if the cut has an explanation. */ - bool proven() const; - - void setReconstruction(const DenseVector& ep); - void setExplanation(const ConstraintCPVec& ex); - void swapExplanation(ConstraintCPVec& ex); - - const DenseVector& getReconstruction() const; - const ConstraintCPVec& getExplanation() const; - - void clearReconstruction(); -}; -std::ostream& operator<<(std::ostream& os, const CutInfo& ci); - -class BranchCutInfo : public CutInfo { -public: - BranchCutInfo(int execOrd, int br, Kind dir, double val); -}; - -class RowsDeleted : public CutInfo { -public: - RowsDeleted(int execOrd, int nrows, const int num[]); -}; - -class TreeLog; - -class NodeLog { -private: - int d_nid; - NodeLog* d_parent; /* If null this is the root */ - TreeLog* d_tl; /* TreeLog containing the node. */ - - struct CmpCutPointer{ - int operator()(const CutInfo* a, const CutInfo* b) const{ - return *a < *b; - } - }; - typedef std::set CutSet; - CutSet d_cuts; - std::map d_rowIdsSelected; - - enum Status {Open, Closed, Branched}; - Status d_stat; - - int d_brVar; // branching variable - double d_brVal; - int d_downId; - int d_upId; - -public: - typedef std::unordered_map RowIdMap; -private: - RowIdMap d_rowId2ArithVar; - -public: - NodeLog(); /* default constructor. */ - NodeLog(TreeLog* tl, int node, const RowIdMap& m); /* makes a root node. */ - NodeLog(TreeLog* tl, NodeLog* parent, int node);/* makes a non-root node. */ - - ~NodeLog(); - - int getNodeId() const; - void addSelected(int ord, int sel); - void applySelected(); - void addCut(CutInfo* ci); - void print(std::ostream& o) const; - - bool isRoot() const; - const NodeLog& getParent() const; - - void copyParentRowIds(); - - bool isBranch() const; - int branchVariable() const; - double branchValue() const; - - typedef CutSet::const_iterator const_iterator; - const_iterator begin() const; - const_iterator end() const; - - void setBranch(int br, double val, int dn, int up); - void closeNode(); - - int getDownId() const; - int getUpId() const; - - /** - * Looks up a row id to the appropriate arith variable. - * Be careful these are deleted in context during replay! - * failure returns ARITHVAR_SENTINEL */ - ArithVar lookupRowId(int rowId) const; - - /** - * Maps a row id to an arithvar. - * Be careful these are deleted in context during replay! - */ - void mapRowId(int rowid, ArithVar v); - void applyRowsDeleted(const RowsDeleted& rd); - -}; -std::ostream& operator<<(std::ostream& os, const NodeLog& nl); - -class TreeLog { -private: - int next_exec_ord; - typedef std::map ToNodeMap; - ToNodeMap d_toNode; - DenseMultiset d_branches; - - uint32_t d_numCuts; - - bool d_active; - -public: - TreeLog(); - - NodeLog& getNode(int nid); - void branch(int nid, int br, double val, int dn, int up); - void close(int nid); - - //void applySelected(); - void print(std::ostream& o) const; - - typedef ToNodeMap::const_iterator const_iterator; - const_iterator begin() const; - const_iterator end() const; - - int getExecutionOrd(); - - void reset(const NodeLog::RowIdMap& m); - - // Applies rd tp to the node with id nid - void applyRowsDeleted(int nid, const RowsDeleted& rd); - - // Synonym for getNode(nid).mapRowId(ind, v) - void mapRowId(int nid, int ind, ArithVar v); - -private: - void clear(); - -public: - void makeInactive(); - void makeActive(); - - bool isActivelyLogging() const; - - void addCut(); - uint32_t cutCount() const; - - void logBranch(uint32_t x); - uint32_t numBranches(uint32_t x); - - int getRootId() const; - - uint32_t numNodes() const{ - return d_toNode.size(); - } - - NodeLog& getRootNode(); - void printBranchInfo(std::ostream& os) const; -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/dio_solver.cpp b/src/theory/arith/dio_solver.cpp deleted file mode 100644 index 8e5f636e0..000000000 --- a/src/theory/arith/dio_solver.cpp +++ /dev/null @@ -1,832 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * Diophantine equation solver - * - * A Diophantine equation solver for the theory of arithmetic. - */ -#include "theory/arith/dio_solver.h" - -#include - -#include "base/output.h" -#include "expr/skolem_manager.h" -#include "options/arith_options.h" -#include "smt/env.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/partial_model.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -inline Node makeIntegerVariable(){ - NodeManager* nm = NodeManager::currentNM(); - SkolemManager* sm = nm->getSkolemManager(); - return sm->mkDummySkolem("intvar", - nm->integerType(), - "is an integer variable created by the dio solver"); -} - -DioSolver::DioSolver(Env& env) - : EnvObj(env), - d_lastUsedProofVariable(context(), 0), - d_inputConstraints(context()), - d_nextInputConstraintToEnqueue(context(), 0), - d_trail(context()), - d_subs(context()), - d_currentF(), - d_savedQueue(context()), - d_savedQueueIndex(context(), 0), - d_conflictIndex(context()), - d_maxInputCoefficientLength(context(), 0), - d_usedDecomposeIndex(context(), false), - d_lastPureSubstitution(context(), 0), - d_pureSubstitionIter(context(), 0), - d_decompositionLemmaQueue(context()) -{ -} - -DioSolver::Statistics::Statistics() - : d_conflictCalls(smtStatisticsRegistry().registerInt( - "theory::arith::dio::conflictCalls")), - d_cutCalls( - smtStatisticsRegistry().registerInt("theory::arith::dio::cutCalls")), - d_cuts(smtStatisticsRegistry().registerInt("theory::arith::dio::cuts")), - d_conflicts( - smtStatisticsRegistry().registerInt("theory::arith::dio::conflicts")), - d_conflictTimer(smtStatisticsRegistry().registerTimer( - "theory::arith::dio::conflictTimer")), - d_cutTimer( - smtStatisticsRegistry().registerTimer("theory::arith::dio::cutTimer")) -{ -} - -bool DioSolver::queueConditions(TrailIndex t){ - Trace("queueConditions") << !inConflict() << std::endl; - Trace("queueConditions") << gcdIsOne(t) << std::endl; - Trace("queueConditions") << !debugAnySubstitionApplies(t) << std::endl; - Trace("queueConditions") << !triviallySat(t) << std::endl; - Trace("queueConditions") << !triviallyUnsat(t) << std::endl; - - return - !inConflict() && - gcdIsOne(t) && - !debugAnySubstitionApplies(t) && - !triviallySat(t) && - !triviallyUnsat(t); -} - -size_t DioSolver::allocateProofVariable() { - Assert(d_lastUsedProofVariable <= d_proofVariablePool.size()); - if(d_lastUsedProofVariable == d_proofVariablePool.size()){ - Assert(d_lastUsedProofVariable == d_proofVariablePool.size()); - Node intVar = makeIntegerVariable(); - d_proofVariablePool.push_back(Variable(intVar)); - } - size_t res = d_lastUsedProofVariable; - d_lastUsedProofVariable = d_lastUsedProofVariable + 1; - return res; -} - - -Node DioSolver::nextPureSubstitution(){ - Assert(hasMorePureSubstitutions()); - SubIndex curr = d_pureSubstitionIter; - d_pureSubstitionIter = d_pureSubstitionIter + 1; - - Assert(d_subs[curr].d_fresh.isNull()); - Variable v = d_subs[curr].d_eliminated; - - SumPair sp = d_trail[d_subs[curr].d_constraint].d_eq; - Polynomial p = sp.getPolynomial(); - Constant c = -sp.getConstant(); - Polynomial cancelV = p + Polynomial::mkPolynomial(v); - Node eq = NodeManager::currentNM()->mkNode(kind::EQUAL, v.getNode(), cancelV.getNode()); - return eq; -} - - -bool DioSolver::debugEqualityInInputEquations(Node eq){ - typedef context::CDList::const_iterator const_iterator; - const_iterator i=d_inputConstraints.begin(), end = d_inputConstraints.end(); - for(; i != end; ++i){ - Node reason_i = (*i).d_reason; - if(eq == reason_i){ - return true; - } - } - return false; -} - - -void DioSolver::pushInputConstraint(const Comparison& eq, Node reason){ - Assert(!debugEqualityInInputEquations(reason)); - Assert(eq.debugIsIntegral()); - Assert(eq.getNode().getKind() == kind::EQUAL); - - SumPair sp = eq.toSumPair(); - if(sp.isNonlinear()){ - return; - } - - - - uint32_t length = sp.maxLength(); - if(length > d_maxInputCoefficientLength){ - d_maxInputCoefficientLength = length; - } - - size_t varIndex = allocateProofVariable(); - Variable proofVariable(d_proofVariablePool[varIndex]); - //Variable proofVariable(makeIntegerVariable()); - - TrailIndex posInTrail = d_trail.size(); - Trace("dio::pushInputConstraint") << "pushInputConstraint @ " << posInTrail - << " " << eq.getNode() - << " " << reason << endl; - d_trail.push_back(Constraint(sp,Polynomial::mkPolynomial(proofVariable))); - - size_t posInConstraintList = d_inputConstraints.size(); - d_inputConstraints.push_back(InputConstraint(reason, posInTrail)); - - d_varToInputConstraintMap[proofVariable.getNode()] = posInConstraintList; -} - - -DioSolver::TrailIndex DioSolver::scaleEqAtIndex(DioSolver::TrailIndex i, const Integer& g){ - Assert(g != 0); - Constant invg = Constant::mkConstant(Rational(Integer(1),g)); - const SumPair& sp = d_trail[i].d_eq; - const Polynomial& proof = d_trail[i].d_proof; - - SumPair newSP = sp * invg; - Polynomial newProof = proof * invg; - - Assert(newSP.isIntegral()); - Assert(newSP.gcd() == 1); - - TrailIndex j = d_trail.size(); - - d_trail.push_back(Constraint(newSP, newProof)); - - Trace("arith::dio") << "scaleEqAtIndex(" << i <<","<= 2 && - length > d_maxInputCoefficientLength + MAX_GROWTH_RATE; - if(TraceIsOn("arith::dio::max") && result){ - - const SumPair& eq = d_trail[j].d_eq; - const Polynomial& proof = d_trail[j].d_proof; - - Trace("arith::dio::max") << "about to drop:" << std::endl; - Trace("arith::dio::max") << "d_trail[" << j << "].d_eq = " << eq.getNode() << std::endl; - Trace("arith::dio::max") << "d_trail[" << j << "].d_proof = " << proof.getNode() << std::endl; - } - return result; -} - -void DioSolver::enqueueInputConstraints(){ - Assert(d_currentF.empty()); - while(d_savedQueueIndex < d_savedQueue.size()){ - d_currentF.push_back(d_savedQueue[d_savedQueueIndex]); - d_savedQueueIndex = d_savedQueueIndex + 1; - } - - while(d_nextInputConstraintToEnqueue < d_inputConstraints.size() && !inConflict()){ - size_t curr = d_nextInputConstraintToEnqueue; - d_nextInputConstraintToEnqueue = d_nextInputConstraintToEnqueue + 1; - - TrailIndex i = d_inputConstraints[curr].d_trailPos; - TrailIndex j = applyAllSubstitutionsToIndex(i); - - if(!triviallySat(j)){ - if(triviallyUnsat(j)){ - raiseConflict(j); - }else{ - TrailIndex k = reduceByGCD(j); - - if(!inConflict()){ - if(triviallyUnsat(k)){ - raiseConflict(k); - }else if(!(triviallySat(k) || anyCoefficientExceedsMaximum(k))){ - pushToQueueBack(k); - } - } - } - } - } -} - - -/*TODO Currently linear in the size of the Queue - *It is not clear if am O(log n) strategy would be better. - *Right before this in the algorithm is a substitution which could potentially - *effect the key values of everything in the queue. - */ -void DioSolver::moveMinimumByAbsToQueueFront(){ - Assert(!queueEmpty()); - - //Select the minimum element. - size_t indexInQueue = 0; - Monomial minMonomial = d_trail[d_currentF[indexInQueue]].d_minimalMonomial; - - size_t N = d_currentF.size(); - for(size_t i=1; i < N; ++i){ - Monomial curr = d_trail[d_currentF[i]].d_minimalMonomial; - if(curr.absCmp(minMonomial) < 0){ - indexInQueue = i; - minMonomial = curr; - } - } - - TrailIndex tmp = d_currentF[indexInQueue]; - d_currentF[indexInQueue] = d_currentF.front(); - d_currentF.front() = tmp; -} - -bool DioSolver::queueEmpty() const{ - return d_currentF.empty(); -} - -Node DioSolver::columnGcdIsOne() const{ - std::unordered_map gcdMap; - - std::deque::const_iterator iter, end; - for(iter = d_currentF.begin(), end = d_currentF.end(); iter != end; ++iter){ - TrailIndex curr = *iter; - Polynomial p = d_trail[curr].d_eq.getPolynomial(); - Polynomial::iterator monoIter = p.begin(), monoEnd = p.end(); - for(; monoIter != monoEnd; ++monoIter){ - Monomial m = *monoIter; - VarList vl = m.getVarList(); - Node vlNode = vl.getNode(); - - Constant c = m.getConstant(); - Integer zc = c.getValue().getNumerator(); - if(gcdMap.find(vlNode) == gcdMap.end()){ - // have not seen vl yet. - // gcd is c - Assert(c.isIntegral()); - Assert(!m.absCoefficientIsOne()); - gcdMap.insert(make_pair(vlNode, zc.abs())); - }else{ - const Integer& currentGcd = gcdMap[vlNode]; - Integer newGcd = currentGcd.gcd(zc); - if(newGcd == 1){ - return vlNode; - }else{ - gcdMap[vlNode] = newGcd; - } - } - } - } - return Node::null(); -} - -void DioSolver::saveQueue(){ - std::deque::const_iterator iter, end; - for(iter = d_currentF.begin(), end = d_currentF.end(); iter != end; ++iter){ - d_savedQueue.push_back(*iter); - } -} - -DioSolver::TrailIndex DioSolver::impliedGcdOfOne(){ - Node canReduce = columnGcdIsOne(); - if(canReduce.isNull()){ - return 0; - }else{ - VarList vl = VarList::parseVarList(canReduce); - - TrailIndex current; - Integer currentCoeff, currentGcd; - - //step 1 find the first equation containing vl - //Set current and currentCoefficient - std::deque::const_iterator iter, end; - for(iter = d_currentF.begin(), end = d_currentF.end(); true; ++iter){ - Assert(iter != end); - current = *iter; - Constant coeff = d_trail[current].d_eq.getPolynomial().getCoefficient(vl); - if(!coeff.isZero()){ - currentCoeff = coeff.getValue().getNumerator(); - currentGcd = currentCoeff.abs(); - - ++iter; - break; - } - } - - //For the rest of the equations keep reducing until the coefficient is one - for(; iter != end; ++iter){ - Trace("arith::dio") << "next round : " << currentCoeff << " " << currentGcd << endl; - TrailIndex inQueue = *iter; - Constant iqc = d_trail[inQueue].d_eq.getPolynomial().getCoefficient(vl); - if(!iqc.isZero()){ - Integer inQueueCoeff = iqc.getValue().getNumerator(); - - //mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, mpz_t a, mpz_t b); - Integer g, s, t; - // g = a*s + b*t - Integer::extendedGcd(g, s, t, currentCoeff, inQueueCoeff); - - Trace("arith::dio") << "extendedReduction : " << endl; - Trace("arith::dio") << g << " = " << s <<"*"<< currentCoeff << " + " << t <<"*"<< inQueueCoeff << endl; - - Assert(g <= currentGcd); - if(g < currentGcd){ - if(s.sgn() == 0){ - Trace("arith::dio") << "extendedReduction drop" << endl; - Assert(inQueueCoeff.divides(currentGcd)); - current = *iter; - currentCoeff = inQueueCoeff; - currentGcd = inQueueCoeff.abs(); - }else{ - - Trace("arith::dio") << "extendedReduction combine" << endl; - TrailIndex next = combineEqAtIndexes(current, s, inQueue, t); - - Assert(d_trail[next] - .d_eq.getPolynomial() - .getCoefficient(vl) - .getValue() - .getNumerator() - == g); - - current = next; - currentCoeff = g; - currentGcd = g; - if(currentGcd == 1){ - return current; - } - } - } - } - } - //This is not reachble as it is assured that the gcd of the column is 1 - Unreachable(); - } -} - -bool DioSolver::processEquations(bool allowDecomposition){ - Assert(!inConflict()); - - enqueueInputConstraints(); - while(! queueEmpty() && !inConflict()){ - moveMinimumByAbsToQueueFront(); - - TrailIndex minimum = d_currentF.front(); - TrailIndex reduceIndex; - - Assert(inRange(minimum)); - Assert(!inConflict()); - - Trace("arith::dio") << "processEquations " << minimum << " : " << d_trail[minimum].d_eq.getNode() << endl; - - Assert(queueConditions(minimum)); - - bool canDirectlySolve = d_trail[minimum].d_minimalMonomial.absCoefficientIsOne(); - - std::pair p; - if(canDirectlySolve){ - d_currentF.pop_front(); - p = solveIndex(minimum); - reduceIndex = minimum; - }else{ - TrailIndex implied = impliedGcdOfOne(); - - if(implied != 0){ - p = solveIndex(implied); - reduceIndex = implied; - }else if(allowDecomposition){ - d_currentF.pop_front(); - p = decomposeIndex(minimum); - reduceIndex = minimum; - }else { - // Cannot make progress without decomposeIndex - saveQueue(); - break; - } - } - - SubIndex subIndex = p.first; - TrailIndex next = p.second; - subAndReduceCurrentFByIndex(subIndex); - - if(next != reduceIndex){ - if(triviallyUnsat(next)){ - raiseConflict(next); - }else if(! triviallySat(next) ){ - pushToQueueBack(next); - } - } - } - - d_currentF.clear(); - return inConflict(); -} - -Node DioSolver::processEquationsForConflict(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_conflictTimer); - ++(d_statistics.d_conflictCalls); - - Assert(!inConflict()); - if(processEquations(true)){ - ++(d_statistics.d_conflicts); - return proveIndex(getConflictIndex()); - }else{ - return Node::null(); - } -} - -SumPair DioSolver::processEquationsForCut(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_cutTimer); - ++(d_statistics.d_cutCalls); - - Assert(!inConflict()); - if(processEquations(true)){ - ++(d_statistics.d_cuts); - return purifyIndex(getConflictIndex()); - }else{ - return SumPair::mkZero(); - } -} - - -SumPair DioSolver::purifyIndex(TrailIndex i){ - // TODO: "This uses the substitution trail to reverse the substitutions from the sum term. Using the proof term should be more efficient." - - SumPair curr = d_trail[i].d_eq; - - Constant negOne = Constant::mkConstant(-1); - - for(uint32_t revIter = d_subs.size(); revIter > 0; --revIter){ - uint32_t i2 = revIter - 1; - Node freshNode = d_subs[i2].d_fresh; - if(freshNode.isNull()){ - continue; - }else{ - Variable var(freshNode); - Polynomial vsum = curr.getPolynomial(); - - Constant a = vsum.getCoefficient(VarList(var)); - if(!a.isZero()){ - const SumPair& sj = d_trail[d_subs[i2].d_constraint].d_eq; - Assert(sj.getPolynomial().getCoefficient(VarList(var)).isOne()); - SumPair newSi = (curr * negOne) + (sj * a); - Assert(newSi.getPolynomial().getCoefficient(VarList(var)).isZero()); - curr = newSi; - } - } - } - return curr; -} - -DioSolver::TrailIndex DioSolver::combineEqAtIndexes(DioSolver::TrailIndex i, const Integer& q, DioSolver::TrailIndex j, const Integer& r){ - Constant cq = Constant::mkConstant(q); - Constant cr = Constant::mkConstant(r); - - const SumPair& si = d_trail[i].d_eq; - const SumPair& sj = d_trail[j].d_eq; - - Trace("arith::dio") << "combineEqAtIndexes(" << i <<","< DioSolver::decomposeIndex(DioSolver::TrailIndex i){ - const SumPair& si = d_trail[i].d_eq; - - d_usedDecomposeIndex = true; - - Trace("arith::dio") << "before decomposeIndex("< 1); - - //It is not sufficient to reduce the case where abs(a) == 1 to abs(a) > 1. - //We need to handle both cases seperately to ensure termination. - Node qr = SumPair::computeQR(si, a.getValue().getNumerator()); - - Assert(qr.getKind() == kind::ADD); - Assert(qr.getNumChildren() == 2); - SumPair q = SumPair::parseSumPair(qr[0]); - SumPair r = SumPair::parseSumPair(qr[1]); - - Assert(q.getPolynomial().getCoefficient(vl) == Constant::mkConstant(1)); - - Assert(!r.isZero()); - Node freshNode = makeIntegerVariable(); - Variable fresh(freshNode); - SumPair fresh_one=SumPair::mkSumPair(fresh); - SumPair fresh_a = fresh_one * a; - - SumPair newSI = SumPair(fresh_one) - q; - // this normalizes the coefficient of var to -1 - - - TrailIndex ci = d_trail.size(); - d_trail.push_back(Constraint(newSI, Polynomial::mkZero())); - // no longer reference av safely! - addTrailElementAsLemma(ci); - - Trace("arith::dio") << "Decompose ci(" << ci <<":" << d_trail[ci].d_eq.getNode() - << ") for " << d_trail[i].d_minimalMonomial.getNode() << endl; - Assert(d_trail[ci].d_eq.getPolynomial().getCoefficient(vl) - == Constant::mkConstant(-1)); - - SumPair newFact = r + fresh_a; - - TrailIndex nextIndex = d_trail.size(); - d_trail.push_back(Constraint(newFact, d_trail[i].d_proof)); - - SubIndex subBy = d_subs.size(); - d_subs.push_back(Substitution(freshNode, var, ci)); - - Trace("arith::dio") << "Decompose nextIndex " << d_trail[nextIndex].d_eq.getNode() << endl; - return make_pair(subBy, nextIndex); -} - - -DioSolver::TrailIndex DioSolver::applySubstitution(DioSolver::SubIndex si, DioSolver::TrailIndex ti){ - Variable var = d_subs[si].d_eliminated; - TrailIndex subIndex = d_subs[si].d_constraint; - - const SumPair& curr = d_trail[ti].d_eq; - Polynomial vsum = curr.getPolynomial(); - - Constant a = vsum.getCoefficient(VarList(var)); - Assert(a.isIntegral()); - if(!a.isZero()){ - Integer one(1); - TrailIndex afterSub = combineEqAtIndexes(ti, one, subIndex, a.getValue().getNumerator()); - Assert(d_trail[afterSub] - .d_eq.getPolynomial() - .getCoefficient(VarList(var)) - .isZero()); - return afterSub; - }else{ - return ti; - } -} - - -DioSolver::TrailIndex DioSolver::reduceByGCD(DioSolver::TrailIndex ti){ - const SumPair& sp = d_trail[ti].d_eq; - Polynomial vsum = sp.getPolynomial(); - Constant c = sp.getConstant(); - - Trace("arith::dio") << "reduceByGCD " << vsum.getNode() << endl; - Assert(!vsum.isConstant()); - Integer g = vsum.gcd(); - Assert(g >= 1); - Trace("arith::dio") << "gcd("<< vsum.getNode() <<")=" << g << " " << c.getValue() << endl; - if(g.divides(c.getValue().getNumerator())){ - if(g > 1){ - return scaleEqAtIndex(ti, g); - }else{ - return ti; - } - }else{ - raiseConflict(ti); - return ti; - } -} - -bool DioSolver::triviallySat(TrailIndex i){ - const SumPair& eq = d_trail[i].d_eq; - if(eq.isConstant()){ - return eq.getConstant().isZero(); - }else{ - return false; - } -} - -bool DioSolver::triviallyUnsat(DioSolver::TrailIndex i){ - const SumPair& eq = d_trail[i].d_eq; - if(eq.isConstant()){ - return !eq.getConstant().isZero(); - }else{ - return false; - } -} - - -bool DioSolver::gcdIsOne(DioSolver::TrailIndex i){ - const SumPair& eq = d_trail[i].d_eq; - return eq.gcd() == Integer(1); -} - -void DioSolver::subAndReduceCurrentFByIndex(DioSolver::SubIndex subIndex){ - size_t N = d_currentF.size(); - - size_t readIter = 0, writeIter = 0; - for(; readIter < N && !inConflict(); ++readIter){ - TrailIndex curr = d_currentF[readIter]; - TrailIndex nextTI = applySubstitution(subIndex, curr); - if(nextTI == curr){ - d_currentF[writeIter] = curr; - ++writeIter; - }else{ - Assert(nextTI != curr); - - if(triviallyUnsat(nextTI)){ - raiseConflict(nextTI); - }else if(!triviallySat(nextTI)){ - TrailIndex nextNextTI = reduceByGCD(nextTI); - - if(!(inConflict() || anyCoefficientExceedsMaximum(nextNextTI))){ - Assert(queueConditions(nextNextTI)); - d_currentF[writeIter] = nextNextTI; - ++writeIter; - } - } - } - } - if(!inConflict() && writeIter < N){ - d_currentF.resize(writeIter); - } -} - -void DioSolver::addTrailElementAsLemma(TrailIndex i) { - if (options().arith.exportDioDecompositions) - { - d_decompositionLemmaQueue.push(i); - } -} - -Node DioSolver::trailIndexToEquality(TrailIndex i) const { - const SumPair& sp = d_trail[i].d_eq; - Node n = sp.getNode(); - Node zero = - NodeManager::currentNM()->mkConstRealOrInt(n.getType(), Rational(0)); - Node eq = n.eqNode(zero); - return eq; -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/dio_solver.h b/src/theory/arith/dio_solver.h deleted file mode 100644 index 1ac2eaadb..000000000 --- a/src/theory/arith/dio_solver.h +++ /dev/null @@ -1,424 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Morgan Deters, Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * A Diophantine equation solver for the theory of arithmetic. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__DIO_SOLVER_H -#define CVC5__THEORY__ARITH__DIO_SOLVER_H - -#include -#include -#include - -#include "context/cdlist.h" -#include "context/cdmaybe.h" -#include "context/cdo.h" -#include "context/cdqueue.h" -#include "smt/env_obj.h" -#include "theory/arith/normal_form.h" -#include "util/rational.h" -#include "util/statistics_stats.h" - -namespace cvc5::context { -class Context; -} -namespace cvc5::internal { -namespace theory { -namespace arith { - -class DioSolver : protected EnvObj -{ - private: - typedef size_t TrailIndex; - typedef size_t InputConstraintIndex; - typedef size_t SubIndex; - - std::vector d_proofVariablePool; - /** Sat context dependent. */ - context::CDO d_lastUsedProofVariable; - - /** - * The set of input constraints is stored in a CDList. - * Each constraint point to an element of the trail. - */ - struct InputConstraint { - Node d_reason; - TrailIndex d_trailPos; - InputConstraint(Node reason, TrailIndex pos) : d_reason(reason), d_trailPos(pos) {} - }; - context::CDList d_inputConstraints; - - /** - * This is the next input constraint to handle. - */ - context::CDO d_nextInputConstraintToEnqueue; - - /** - * We maintain a map from the variables associated with proofs to an input constraint. - * These variables can then be used in polynomial manipulations. - */ - typedef std::unordered_map - NodeToInputConstraintIndexMap; - NodeToInputConstraintIndexMap d_varToInputConstraintMap; - - Node proofVariableToReason(const Variable& v) const{ - Assert(d_varToInputConstraintMap.find(v.getNode()) - != d_varToInputConstraintMap.end()); - InputConstraintIndex pos = (*(d_varToInputConstraintMap.find(v.getNode()))).second; - Assert(pos < d_inputConstraints.size()); - return d_inputConstraints[pos].d_reason; - } - - /** - * The main work horse of the algorithm, the trail of constraints. - * Each constraint is a SumPair that implicitly represents an equality against 0. - * d_trail[i].d_eq = (+ c (+ [(* coeff var)])) representing (+ [(* coeff var)]) = -c - * Each constraint has a proof in terms of a linear combination of the input constraints. - * d_trail[i].d_proof - * - * Each Constraint also a monomial in d_eq.getPolynomial() - * of minimal absolute value by the coefficients. - * d_trail[i].d_minimalMonomial - * - * See Alberto's paper for how linear proofs are maintained for the abstract - * state machine in rules (7), (8) and (9). - */ - struct Constraint { - SumPair d_eq; - Polynomial d_proof; - Monomial d_minimalMonomial; - Constraint(const SumPair& eq, const Polynomial& p) : - d_eq(eq), d_proof(p), d_minimalMonomial(d_eq.getPolynomial().selectAbsMinimum()) - {} - }; - context::CDList d_trail; - - // /** Compare by d_minimal. */ - // struct TrailMinimalCoefficientOrder { - // const context::CDList& d_trail; - // TrailMinimalCoefficientOrder(const context::CDList& - // trail): - // d_trail(trail) - // {} - - // bool operator()(TrailIndex i, TrailIndex j){ - // return d_trail[i].d_minimalMonomial.absLessThan(d_trail[j].d_minimalMonomial); - // } - // }; - - /** - * A substitution is stored as a constraint in the trail together with - * the variable to be eliminated, and a fresh variable if one was introduced. - * The variable d_subs[i].d_eliminated is substituted using the implicit equality in - * d_trail[d_subs[i].d_constraint] - * - d_subs[i].d_eliminated is normalized to have coefficient -1 in - * d_trail[d_subs[i].d_constraint]. - * - d_subs[i].d_fresh is either Node::null() or it is variable it is normalized - * to have coefficient 1 in d_trail[d_subs[i].d_constraint]. - */ - struct Substitution { - Node d_fresh; - Variable d_eliminated; - TrailIndex d_constraint; - Substitution(Node f, const Variable& e, TrailIndex c) : - d_fresh(f), d_eliminated(e), d_constraint(c) - {} - }; - context::CDList d_subs; - - /** - * This is the queue of constraints to be processed in the current context level. - * This is to be empty upon entering solver and cleared upon leaving the solver. - * - * All elements in currentF: - * - are fully substituted according to d_subs. - * - !isConstant(). - * - If the element is (+ constant (+ [(* coeff var)] )), then the gcd(coeff) = 1 - */ - std::deque d_currentF; - context::CDList d_savedQueue; - context::CDO d_savedQueueIndex; - context::CDMaybe d_conflictIndex; - - /** - * Drop derived constraints with a coefficient length larger than - * the maximum input constraints length than 2**MAX_GROWTH_RATE. - */ - context::CDO d_maxInputCoefficientLength; - static constexpr uint32_t MAX_GROWTH_RATE = 3; - - /** Returns true if the element on the trail should be dropped.*/ - bool anyCoefficientExceedsMaximum(TrailIndex j) const; - - /** - * Is true if decomposeIndex has been used in this context. - */ - context::CDO d_usedDecomposeIndex; - - context::CDO d_lastPureSubstitution; - context::CDO d_pureSubstitionIter; - - /** - * Decomposition lemma queue. - */ - context::CDQueue d_decompositionLemmaQueue; - - public: - /** Construct a Diophantine equation solver with the given context. */ - DioSolver(Env& env); - - /** Returns true if the substitutions use no new variables. */ - bool hasMorePureSubstitutions() const - { - return d_pureSubstitionIter < d_lastPureSubstitution; - } - - Node nextPureSubstitution(); - - /** - * Adds an equality to the queue of the DioSolver. - * orig is blamed in a conflict. - * orig can either be of the form (= p c) or (and ub lb). - * where ub is either (leq p c) or (not (> p [- c 1])), and - * where lb is either (geq p c) or (not (< p [+ c 1])) - * - * If eq cannot be used, this constraint is dropped. - */ - void pushInputConstraint(const Comparison& eq, Node reason); - - /** - * Processes the queue looking for any conflict. - * If a conflict is found, this returns conflict. - * Otherwise, it returns null. - * The conflict is guarenteed to be over literals given in addEquality. - */ - Node processEquationsForConflict(); - - /** - * Processes the queue looking for an integer unsatisfiable cutting plane. - * If such a plane is found this returns an entailed plane using no - * fresh variables. - */ - SumPair processEquationsForCut(); - -private: - /** Returns true if the TrailIndex refers to a element in the trail. */ - bool inRange(TrailIndex i) const{ - return i < d_trail.size(); - } - - Node columnGcdIsOne() const; - - - /** - * Returns true if the context dependent flag for conflicts - * has been raised. - */ - bool inConflict() const { return d_conflictIndex.isSet(); } - - /** Raises a conflict at the index ti. */ - void raiseConflict(TrailIndex ti){ - Assert(!inConflict()); - d_conflictIndex.set(ti); - } - - /** Returns the conflict index. */ - TrailIndex getConflictIndex() const{ - Assert(inConflict()); - return d_conflictIndex.get(); - } - - /** - * Allocates a "unique" proof variable. - * This variable is fresh with respect to the context. - * Returns index of the variable in d_variablePool; - */ - size_t allocateProofVariable(); - - - /** Empties the unproccessed input constraints into the queue. */ - void enqueueInputConstraints(); - - /** - * Returns true if an input equality is in the map. - * This is expensive and is only for debug assertions. - */ - bool debugEqualityInInputEquations(Node eq); - - /** Applies the substitution at subIndex to currentF. */ - void subAndReduceCurrentFByIndex(SubIndex d_subIndex); - - /** - * Takes as input a TrailIndex i and an integer that divides d_trail[i].d_eq, and - * returns a TrailIndex j s.t. - * d_trail[j].d_eq = (1/g) d_trail[i].d_eq - * and - * d_trail[j].d_proof = (1/g) d_trail[i].d_proof. - * - * g must be non-zero. - * - * This corresponds to an application of Alberto's rule (7). - */ - TrailIndex scaleEqAtIndex(TrailIndex i, const Integer& g); - - - /** - * Takes as input TrailIndex's i and j and Integer's q and r and a TrailIndex k s.t. - * d_trail[k].d_eq == d_trail[i].d_eq * q + d_trail[j].d_eq * r - * and - * d_trail[k].d_proof == d_trail[i].d_proof * q + d_trail[j].d_proof * r - * - * This corresponds to an application of Alberto's rule (8). - */ - TrailIndex combineEqAtIndexes(TrailIndex i, const Integer& q, TrailIndex j, const Integer& r); - - /** - * Decomposes the equation at index ti of trail by the variable - * with the lowest coefficient. - * This corresponds to an application of Alberto's rule (9). - * - * Returns a pair of a SubIndex and a TrailIndex. - * The SubIndex is the index of a newly introduced substition. - */ - std::pair decomposeIndex(TrailIndex ti); - - /** Solves the index at ti for the value in minimumMonomial. */ - std::pair solveIndex(TrailIndex ti); - - /** Prints the queue for debugging purposes to Trace("arith::dio"). */ - void printQueue(); - - /** - * Exhaustively applies all substitutions discovered to an element of the trail. - * Returns a TrailIndex corresponding to the substitutions being applied. - */ - TrailIndex applyAllSubstitutionsToIndex(TrailIndex i); - - /** - * Applies a substitution to an element in the trail. - */ - TrailIndex applySubstitution(SubIndex s, TrailIndex i); - - /** - * Reduces the trail node at i by the gcd of the variables. - * Returns the new trail element. - * - * This raises the conflict flag if unsat is detected. - */ - TrailIndex reduceByGCD(TrailIndex i); - - /** - * Returns true if i'th element in the trail is trivially true. - * (0 = 0) - */ - bool triviallySat(TrailIndex t); - - /** - * Returns true if i'th element in the trail is trivially unsatisfiable. - * (1 = 0) - */ - bool triviallyUnsat(TrailIndex t); - - /** Returns true if the gcd of the i'th element of the trail is 1.*/ - bool gcdIsOne(TrailIndex t); - - bool debugAnySubstitionApplies(TrailIndex t); - bool debugSubstitutionApplies(SubIndex si, TrailIndex ti); - - - /** Returns true if the queue of nodes to process is empty. */ - bool queueEmpty() const; - - bool queueConditions(TrailIndex t); - - - void pushToQueueBack(TrailIndex t){ - Assert(queueConditions(t)); - d_currentF.push_back(t); - } - - void pushToQueueFront(TrailIndex t){ - Assert(queueConditions(t)); - d_currentF.push_front(t); - } - - /** - * Moves the minimum Constraint by absolute value of the minimum coefficient to - * the front of the queue. - */ - void moveMinimumByAbsToQueueFront(); - - void saveQueue(); - - TrailIndex impliedGcdOfOne(); - - - /** - * Processing the current set of equations. - * - * decomposeIndex() rule is only applied if allowDecomposition is true. - */ - bool processEquations(bool allowDecomposition); - - /** - * Constructs a proof from any d_trail[i] in terms of input literals. - */ - Node proveIndex(TrailIndex i); - - /** - * Returns the SumPair in d_trail[i].d_eq with all of the fresh variables purified out. - */ - SumPair purifyIndex(TrailIndex i); - -public: - bool hasMoreDecompositionLemmas() const{ - return !d_decompositionLemmaQueue.empty(); - } - Node nextDecompositionLemma() { - Assert(hasMoreDecompositionLemmas()); - TrailIndex front = d_decompositionLemmaQueue.front(); - d_decompositionLemmaQueue.pop(); - return trailIndexToEquality(front); - } -private: - Node trailIndexToEquality(TrailIndex i) const; - void addTrailElementAsLemma(TrailIndex i); - -public: - - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - - IntStat d_conflictCalls; - IntStat d_cutCalls; - - IntStat d_cuts; - IntStat d_conflicts; - - TimerStat d_conflictTimer; - TimerStat d_cutTimer; - - Statistics(); - }; - - Statistics d_statistics; -}; /* class DioSolver */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__DIO_SOLVER_H */ diff --git a/src/theory/arith/dual_simplex.cpp b/src/theory/arith/dual_simplex.cpp deleted file mode 100644 index e43119154..000000000 --- a/src/theory/arith/dual_simplex.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - */ -#include "theory/arith/dual_simplex.h" - -#include "base/output.h" -#include "options/arith_options.h" -#include "smt/env.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" -#include "theory/arith/error_set.h" -#include "theory/arith/linear_equality.h" - - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -DualSimplexDecisionProcedure::DualSimplexDecisionProcedure( - Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc) - : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), - d_pivotsInRound(), - d_statistics(d_pivots) -{ } - -DualSimplexDecisionProcedure::Statistics::Statistics(uint32_t& pivots) - : d_statUpdateConflicts(smtStatisticsRegistry().registerInt( - "theory::arith::dual::UpdateConflicts")), - d_processSignalsTime(smtStatisticsRegistry().registerTimer( - "theory::arith::dual::findConflictOnTheQueueTime")), - d_simplexConflicts(smtStatisticsRegistry().registerInt( - "theory::arith::dual::simplexConflicts")), - d_recentViolationCatches(smtStatisticsRegistry().registerInt( - "theory::arith::dual::recentViolationCatches")), - d_searchTime(smtStatisticsRegistry().registerTimer( - "theory::arith::dual::searchTime")), - d_finalCheckPivotCounter( - smtStatisticsRegistry().registerReference( - "theory::arith::dual::lastPivots", pivots)) -{ -} - -Result::Status DualSimplexDecisionProcedure::dualFindModel(bool exactResult) -{ - Assert(d_conflictVariables.empty()); - - d_pivots = 0; - - if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ - Trace("arith::findModel") << "dualFindModel() trivial" << endl; - return Result::SAT; - } - - // We need to reduce this because of - d_errorSet.reduceToSignals(); - d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); - - if(processSignals()){ - d_conflictVariables.purge(); - - Trace("arith::findModel") << "dualFindModel() early conflict" << endl; - return Result::UNSAT; - }else if(d_errorSet.errorEmpty()){ - Trace("arith::findModel") << "dualFindModel() fixed itself" << endl; - Assert(!d_errorSet.moreSignals()); - return Result::SAT; - } - - Trace("arith::findModel") << "dualFindModel() start non-trivial" << endl; - - Result::Status result = Result::UNKNOWN; - - exactResult |= d_varOrderPivotLimit < 0; - - uint32_t checkPeriod = options().arith.arithSimplexCheckPeriod; - if (result == Result::UNKNOWN) - { - uint32_t numDifferencePivots = options().arith.arithHeuristicPivots < 0 - ? d_numVariables + 1 - : options().arith.arithHeuristicPivots; - // The signed to unsigned conversion is safe. - if(numDifferencePivots > 0){ - - d_errorSet.setSelectionRule(d_heuristicRule); - if(searchForFeasibleSolution(numDifferencePivots)){ - result = Result::UNSAT; - } - } - } - Assert(!d_errorSet.moreSignals()); - - if(!d_errorSet.errorEmpty() && result != Result::UNSAT){ - if(exactResult){ - d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); - while(!d_errorSet.errorEmpty() && result != Result::UNSAT){ - Assert(checkPeriod > 0); - if(searchForFeasibleSolution(checkPeriod)){ - result = Result::UNSAT; - } - } - } - else if (d_varOrderPivotLimit > 0) - { - d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); - if (searchForFeasibleSolution(d_varOrderPivotLimit)) - { - result = Result::UNSAT; - } - } - } - - Assert(!d_errorSet.moreSignals()); - if (result == Result::UNKNOWN && d_errorSet.errorEmpty()) - { - result = Result::SAT; - } - - d_pivotsInRound.purge(); - // ensure that the conflict variable is still in the queue. - d_conflictVariables.purge(); - - Trace("arith::findModel") << "end findModel() " << result << endl; - - return result; -} - -//corresponds to Check() in dM06 -//template -bool DualSimplexDecisionProcedure::searchForFeasibleSolution(uint32_t remainingIterations){ - TimerStat::CodeTimer codeTimer(d_statistics.d_searchTime); - - Trace("arith") << "searchForFeasibleSolution" << endl; - Assert(remainingIterations > 0); - - while(remainingIterations > 0 && !d_errorSet.focusEmpty()){ - if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - Assert(d_conflictVariables.empty()); - ArithVar x_i = d_errorSet.topFocusVariable(); - - Trace("arith::update::select") << "selectSmallestInconsistentVar()=" << x_i << endl; - if(x_i == ARITHVAR_SENTINEL){ - Trace("arith::update") << "No inconsistent variables" << endl; - return false; //sat - } - - --remainingIterations; - - bool useVarOrderPivot = - d_pivotsInRound.count(x_i) >= options().arith.arithPivotThreshold; - if(!useVarOrderPivot){ - d_pivotsInRound.add(x_i); - } - - Trace("arith::update") << "pivots in rounds: " << d_pivotsInRound.count(x_i) - << " use " << useVarOrderPivot << " threshold " - << options().arith.arithPivotThreshold << std::endl; - - LinearEqualityModule::VarPreferenceFunction pf = useVarOrderPivot ? - &LinearEqualityModule::minVarOrder : &LinearEqualityModule::minBoundAndColLength; - - //DeltaRational beta_i = d_variables.getAssignment(x_i); - ArithVar x_j = ARITHVAR_SENTINEL; - - int32_t prevErrorSize CVC5_UNUSED = d_errorSet.errorSize(); - - if(d_variables.cmpAssignmentLowerBound(x_i) < 0 ){ - x_j = d_linEq.selectSlackUpperBound(x_i, pf); - if(x_j == ARITHVAR_SENTINEL ){ - Unreachable(); - // ++(d_statistics.d_statUpdateConflicts); - // reportConflict(x_i); - // ++(d_statistics.d_simplexConflicts); - // Node conflict = d_linEq.generateConflictBelowLowerBound(x_i); //unsat - // d_conflictVariable = x_i; - // reportConflict(conflict); - // return true; - }else{ - const DeltaRational& l_i = d_variables.getLowerBound(x_i); - d_linEq.pivotAndUpdate(x_i, x_j, l_i); - } - }else if(d_variables.cmpAssignmentUpperBound(x_i) > 0){ - x_j = d_linEq.selectSlackLowerBound(x_i, pf); - if(x_j == ARITHVAR_SENTINEL ){ - Unreachable(); - // ++(d_statistics.d_statUpdateConflicts); - // reportConflict(x_i); - // ++(d_statistics.d_simplexConflicts); - // Node conflict = d_linEq.generateConflictAboveUpperBound(x_i); //unsat - // d_conflictVariable = x_i; - // reportConflict(conflict); - // return true; - }else{ - const DeltaRational& u_i = d_variables.getUpperBound(x_i); - d_linEq.pivotAndUpdate(x_i, x_j, u_i); - } - } - Assert(x_j != ARITHVAR_SENTINEL); - - bool conflict = processSignals(); - int32_t currErrorSize CVC5_UNUSED = d_errorSet.errorSize(); - d_pivots++; - - if(TraceIsOn("arith::dual")){ - Trace("arith::dual") - << "#" << d_pivots - << " c" << conflict - << " d" << (prevErrorSize - currErrorSize) - << " f" << d_errorSet.inError(x_j) - << " h" << d_conflictVariables.isMember(x_j) - << " " << x_i << "->" << x_j - << endl; - } - - if(conflict){ - return true; - } - } - Assert(!d_errorSet.focusEmpty() || d_errorSet.errorEmpty()); - Assert(remainingIterations == 0 || d_errorSet.focusEmpty()); - Assert(d_errorSet.noSignals()); - - return false; -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/dual_simplex.h b/src/theory/arith/dual_simplex.h deleted file mode 100644 index b4df9b4c5..000000000 --- a/src/theory/arith/dual_simplex.h +++ /dev/null @@ -1,120 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - * - * This implements the Simplex module for the Simpelx for DPLL(T) decision - * procedure. - * See the Simplex for DPLL(T) technical report for more background.(citation?) - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This is required to either produce a conflict or satisifying PartialModel. - * Further, we require being told when a basic variable updates its value. - * - * During the Simplex search we maintain a queue of variables. - * The queue is required to contain all of the basic variables that voilate - * their bounds. - * As elimination from the queue is more efficient to be done lazily, - * we do not maintain that the queue of variables needs to be only basic - * variables or only variables that satisfy their bounds. - * - * The simplex procedure roughly follows Alberto's thesis. (citation?) - * There is one round of selecting using a heuristic pivoting rule. - * (See PreferenceFunction Documentation for the available options.) - * The non-basic variable is the one that appears in the fewest pivots. - * (Bruno says that Leonardo invented this first.) - * After this, Bland's pivot rule is invoked. - * - * During this proccess, we periodically inspect the queue of variables to - * 1) remove now extraneous extries, - * 2) detect conflicts that are "waiting" on the queue but may not be detected - * by the current queue heuristics, and - * 3) detect multiple conflicts. - * - * Conflicts are greedily slackened to use the weakest bounds that still - * produce the conflict. - * - * Extra things tracked atm: (Subject to change at Tim's whims) - * - A superset of all of the newly pivoted variables. - * - A queue of additional conflicts that were discovered by Simplex. - * These are theory valid and are currently turned into lemmas - */ - -#include "cvc5_private.h" - -#pragma once - -#include "theory/arith/simplex.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class DualSimplexDecisionProcedure : public SimplexDecisionProcedure{ -public: - DualSimplexDecisionProcedure(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc); - - Result::Status findModel(bool exactResult) override - { - return dualFindModel(exactResult); - } - -private: - - /** - * Maps a variable to how many times they have been used as a pivot in the - * simplex search. - */ - DenseMultiset d_pivotsInRound; - - Result::Status dualFindModel(bool exactResult); - - /** - * This is the main simplex for DPLL(T) loop. - * It runs for at most maxIterations. - * - * Returns true iff it has found a conflict. - * d_conflictVariable will be set and the conflict for this row is reported. - */ - bool searchForFeasibleSolution(uint32_t maxIterations); - - - bool processSignals(){ - TimerStat &timer = d_statistics.d_processSignalsTime; - IntStat& conflictStat = d_statistics.d_recentViolationCatches; - return standardProcessSignals(timer, conflictStat); - } - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - IntStat d_statUpdateConflicts; - TimerStat d_processSignalsTime; - IntStat d_simplexConflicts; - IntStat d_recentViolationCatches; - TimerStat d_searchTime; - - ReferenceStat d_finalCheckPivotCounter; - - Statistics(uint32_t& pivots); - } d_statistics; -};/* class DualSimplexDecisionProcedure */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/error_set.cpp b/src/theory/arith/error_set.cpp deleted file mode 100644 index 8a904b549..000000000 --- a/src/theory/arith/error_set.cpp +++ /dev/null @@ -1,490 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andres Noetzli, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/error_set.h" - -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -ErrorInformation::ErrorInformation() - : d_variable(ARITHVAR_SENTINEL), - d_violated(NullConstraint), - d_sgn(0), - d_relaxed(false), - d_inFocus(false), - d_handle(), - d_amount(nullptr), - d_metric(0) -{ - Trace("arith::error::mem") - << "def constructor " << d_variable << " " << d_amount.get() << endl; -} - -ErrorInformation::ErrorInformation(ArithVar var, ConstraintP vio, int sgn) - : d_variable(var), - d_violated(vio), - d_sgn(sgn), - d_relaxed(false), - d_inFocus(false), - d_handle(), - d_amount(nullptr), - d_metric(0) -{ - Assert(debugInitialized()); - Trace("arith::error::mem") - << "constructor " << d_variable << " " << d_amount.get() << endl; -} - - -ErrorInformation::~ErrorInformation() { - Assert(d_relaxed != true); - if (d_amount != nullptr) - { - Trace("arith::error::mem") << d_amount.get() << endl; - Trace("arith::error::mem") - << "destroy " << d_variable << " " << d_amount.get() << endl; - d_amount = nullptr; - } -} - -ErrorInformation::ErrorInformation(const ErrorInformation& ei) - : d_variable(ei.d_variable) - , d_violated(ei.d_violated) - , d_sgn(ei.d_sgn) - , d_relaxed(ei.d_relaxed) - , d_inFocus(ei.d_inFocus) - , d_handle(ei.d_handle) - , d_metric(0) -{ - if (ei.d_amount == nullptr) - { - d_amount = nullptr; - } - else - { - d_amount = std::make_unique(*ei.d_amount); - } - Trace("arith::error::mem") - << "copy const " << d_variable << " " << d_amount.get() << endl; -} - -ErrorInformation& ErrorInformation::operator=(const ErrorInformation& ei){ - d_variable = ei.d_variable; - d_violated = ei.d_violated; - d_sgn = ei.d_sgn; - d_relaxed = (ei.d_relaxed); - d_inFocus = (ei.d_inFocus); - d_handle = (ei.d_handle); - d_metric = ei.d_metric; - if (d_amount != nullptr && ei.d_amount != nullptr) - { - Trace("arith::error::mem") - << "assignment assign " << d_variable << " " << d_amount.get() << endl; - *d_amount = *ei.d_amount; - } - else if (ei.d_amount != nullptr) - { - d_amount = std::make_unique(*ei.d_amount); - Trace("arith::error::mem") - << "assignment alloc " << d_variable << " " << d_amount.get() << endl; - } - else if (d_amount != nullptr) - { - Trace("arith::error::mem") - << "assignment release " << d_variable << " " << d_amount.get() << endl; - d_amount = nullptr; - } - else - { - d_amount = nullptr; - } - return *this; -} - -void ErrorInformation::reset(ConstraintP c, int sgn){ - Assert(!isRelaxed()); - Assert(c != NullConstraint); - d_violated = c; - d_sgn = sgn; - - if (d_amount != nullptr) - { - Trace("arith::error::mem") - << "reset " << d_variable << " " << d_amount.get() << endl; - d_amount = nullptr; - } -} - -void ErrorInformation::setAmount(const DeltaRational& am){ - if (d_amount == nullptr) - { - d_amount = std::make_unique(); - Trace("arith::error::mem") - << "setAmount " << d_variable << " " << d_amount.get() << endl; - } - (*d_amount) = am; -} - -ErrorSet::Statistics::Statistics() - : d_enqueues( - smtStatisticsRegistry().registerInt("theory::arith::pqueue::enqueues")), - d_enqueuesCollection(smtStatisticsRegistry().registerInt( - "theory::arith::pqueue::enqueuesCollection")), - d_enqueuesDiffMode(smtStatisticsRegistry().registerInt( - "theory::arith::pqueue::enqueuesDiffMode")), - d_enqueuesVarOrderMode(smtStatisticsRegistry().registerInt( - "theory::arith::pqueue::enqueuesVarOrderMode")), - d_enqueuesCollectionDuplicates(smtStatisticsRegistry().registerInt( - "theory::arith::pqueue::enqueuesCollectionDuplicates")), - d_enqueuesVarOrderModeDuplicates(smtStatisticsRegistry().registerInt( - "theory::arith::pqueue::enqueuesVarOrderModeDuplicates")) -{ -} - -ErrorSet::ErrorSet(ArithVariables& vars, - TableauSizes tabSizes, - BoundCountingLookup lookups) - : d_variables(vars), - d_errInfo(), - d_selectionRule(options::ErrorSelectionRule::VAR_ORDER), - d_focus(ComparatorPivotRule(this, d_selectionRule)), - d_outOfFocus(), - d_signals(), - d_tableauSizes(tabSizes), - d_boundLookup(lookups) -{} - -options::ErrorSelectionRule ErrorSet::getSelectionRule() const -{ - return d_selectionRule; -} - -void ErrorSet::recomputeAmount(ErrorInformation& ei, - options::ErrorSelectionRule rule) -{ - switch(rule){ - case options::ErrorSelectionRule::MINIMUM_AMOUNT: - case options::ErrorSelectionRule::MAXIMUM_AMOUNT: - ei.setAmount(computeDiff(ei.getVariable())); - break; - case options::ErrorSelectionRule::SUM_METRIC: - ei.setMetric(sumMetric(ei.getVariable())); - break; - case options::ErrorSelectionRule::VAR_ORDER: - // do nothing - break; - } -} - -void ErrorSet::setSelectionRule(options::ErrorSelectionRule rule) -{ - if(rule != getSelectionRule()){ - FocusSet into(ComparatorPivotRule(this, rule)); - FocusSet::const_iterator iter = d_focus.begin(); - FocusSet::const_iterator i_end = d_focus.end(); - for(; iter != i_end; ++iter){ - ArithVar v = *iter; - ErrorInformation& ei = d_errInfo.get(v); - if(ei.inFocus()){ - recomputeAmount(ei, rule); - FocusSetHandle handle = into.push(v); - ei.setHandle(handle); - } - } - d_focus.swap(into); - d_selectionRule = rule; - } - Assert(getSelectionRule() == rule); -} - -ComparatorPivotRule::ComparatorPivotRule(const ErrorSet* es, - options::ErrorSelectionRule r) - : d_errorSet(es), d_rule(r) -{} - -bool ComparatorPivotRule::operator()(ArithVar v, ArithVar u) const { - switch(d_rule){ - case options::ErrorSelectionRule::VAR_ORDER: - // This needs to be the reverse of the minVariableOrder - return v > u; - case options::ErrorSelectionRule::SUM_METRIC: - { - uint32_t v_metric = d_errorSet->getMetric(v); - uint32_t u_metric = d_errorSet->getMetric(u); - if(v_metric == u_metric){ - return v > u; - }else{ - return v_metric > u_metric; - } - } - case options::ErrorSelectionRule::MINIMUM_AMOUNT: - { - const DeltaRational& vamt = d_errorSet->getAmount(v); - const DeltaRational& uamt = d_errorSet->getAmount(u); - int cmp = vamt.cmp(uamt); - if(cmp == 0){ - return v > u; - }else{ - return cmp > 0; - } - } - case options::ErrorSelectionRule::MAXIMUM_AMOUNT: - { - const DeltaRational& vamt = d_errorSet->getAmount(v); - const DeltaRational& uamt = d_errorSet->getAmount(u); - int cmp = vamt.cmp(uamt); - if(cmp == 0){ - return v > u; - }else{ - return cmp < 0; - } - } - } - Unreachable(); -} - -void ErrorSet::update(ErrorInformation& ei){ - if(ei.inFocus()){ - - switch(getSelectionRule()){ - case options::ErrorSelectionRule::MINIMUM_AMOUNT: - case options::ErrorSelectionRule::MAXIMUM_AMOUNT: - ei.setAmount(computeDiff(ei.getVariable())); - d_focus.update(ei.getHandle(), ei.getVariable()); - break; - case options::ErrorSelectionRule::SUM_METRIC: - ei.setMetric(sumMetric(ei.getVariable())); - d_focus.update(ei.getHandle(), ei.getVariable()); - break; - case options::ErrorSelectionRule::VAR_ORDER: - // do nothing - break; - } - } -} - -/** A variable becomes satisfied. */ -void ErrorSet::transitionVariableOutOfError(ArithVar v) { - Assert(!inconsistent(v)); - ErrorInformation& ei = d_errInfo.get(v); - Assert(ei.debugInitialized()); - if(ei.isRelaxed()){ - ConstraintP viol = ei.getViolated(); - if(ei.sgn() > 0){ - d_variables.setLowerBoundConstraint(viol); - }else{ - d_variables.setUpperBoundConstraint(viol); - } - Assert(!inconsistent(v)); - ei.setUnrelaxed(); - } - if(ei.inFocus()){ - d_focus.erase(ei.getHandle()); - ei.setInFocus(false); - } - d_errInfo.remove(v); -} - - -void ErrorSet::transitionVariableIntoError(ArithVar v) { - Assert(inconsistent(v)); - bool vilb = d_variables.cmpAssignmentLowerBound(v) < 0; - int sgn = vilb ? 1 : -1; - ConstraintP c = vilb ? - d_variables.getLowerBoundConstraint(v) : d_variables.getUpperBoundConstraint(v); - d_errInfo.set(v, ErrorInformation(v, c, sgn)); - ErrorInformation& ei = d_errInfo.get(v); - - switch(getSelectionRule()){ - case options::ErrorSelectionRule::MINIMUM_AMOUNT: - case options::ErrorSelectionRule::MAXIMUM_AMOUNT: - ei.setAmount(computeDiff(v)); - break; - case options::ErrorSelectionRule::SUM_METRIC: - ei.setMetric(sumMetric(ei.getVariable())); - break; - case options::ErrorSelectionRule::VAR_ORDER: - // do nothing - break; - } - ei.setInFocus(true); - FocusSetHandle handle = d_focus.push(v); - ei.setHandle(handle); -} - -void ErrorSet::dropFromFocus(ArithVar v) { - Assert(inError(v)); - ErrorInformation& ei = d_errInfo.get(v); - Assert(ei.inFocus()); - d_focus.erase(ei.getHandle()); - ei.setInFocus(false); - d_outOfFocus.push_back(v); -} - -void ErrorSet::addBackIntoFocus(ArithVar v) { - Assert(inError(v)); - ErrorInformation& ei = d_errInfo.get(v); - Assert(!ei.inFocus()); - switch(getSelectionRule()){ - case options::ErrorSelectionRule::MINIMUM_AMOUNT: - case options::ErrorSelectionRule::MAXIMUM_AMOUNT: - ei.setAmount(computeDiff(v)); - break; - case options::ErrorSelectionRule::SUM_METRIC: - ei.setMetric(sumMetric(v)); - break; - case options::ErrorSelectionRule::VAR_ORDER: - // do nothing - break; - } - - ei.setInFocus(true); - FocusSetHandle handle = d_focus.push(v); - ei.setHandle(handle); -} - -void ErrorSet::blur(){ - while(!d_outOfFocus.empty()){ - ArithVar v = d_outOfFocus.back(); - d_outOfFocus.pop_back(); - - if(inError(v) && !inFocus(v)){ - addBackIntoFocus(v); - } - } -} - - - -int ErrorSet::popSignal() { - ArithVar back = d_signals.back(); - d_signals.pop_back(); - - if(inError(back)){ - ErrorInformation& ei = d_errInfo.get(back); - int prevSgn = ei.sgn(); - int focusSgn = ei.focusSgn(); - bool vilb = d_variables.cmpAssignmentLowerBound(back) < 0; - bool viub = d_variables.cmpAssignmentUpperBound(back) > 0; - if(vilb || viub){ - Assert(!vilb || !viub); - int currSgn = vilb ? 1 : -1; - if(currSgn != prevSgn){ - ConstraintP curr = vilb ? d_variables.getLowerBoundConstraint(back) - : d_variables.getUpperBoundConstraint(back); - ei.reset(curr, currSgn); - } - update(ei); - }else{ - transitionVariableOutOfError(back); - } - return focusSgn; - }else if(inconsistent(back)){ - transitionVariableIntoError(back); - } - return 0; -} - -void ErrorSet::clear(){ - // Nothing should be relaxed! - d_signals.clear(); - d_errInfo.purge(); - d_focus.clear(); -} - -void ErrorSet::clearFocus(){ - for(ErrorSet::focus_iterator i =focusBegin(), i_end = focusEnd(); i != i_end; ++i){ - ArithVar f = *i; - ErrorInformation& fei = d_errInfo.get(f); - fei.setInFocus(false); - d_outOfFocus.push_back(f); - } - d_focus.clear(); -} - -void ErrorSet::reduceToSignals(){ - for(error_iterator ei=errorBegin(), ei_end=errorEnd(); ei != ei_end; ++ei){ - ArithVar curr = *ei; - signalVariable(curr); - } - - d_errInfo.purge(); - d_focus.clear(); - d_outOfFocus.clear(); -} - -DeltaRational ErrorSet::computeDiff(ArithVar v) const{ - Assert(inconsistent(v)); - const DeltaRational& beta = d_variables.getAssignment(v); - DeltaRational diff = d_variables.cmpAssignmentLowerBound(v) < 0 ? - d_variables.getLowerBound(v) - beta: - beta - d_variables.getUpperBound(v); - - Assert(diff.sgn() > 0); - return diff; -} - -void ErrorSet::debugPrint(std::ostream& out) const { - out << "error set debugprint" << endl; - for(error_iterator i = errorBegin(), i_end = errorEnd(); - i != i_end; ++i){ - ArithVar e = *i; - const ErrorInformation& ei = d_errInfo[e]; - ei.print(out); - out << " "; - d_variables.printModel(e, out); - out << endl; - } - out << "focus "; - for(focus_iterator i = focusBegin(), i_end = focusEnd(); - i != i_end; ++i){ - out << *i << " "; - } - out << ";" << endl; -} - -void ErrorSet::focusDownToJust(ArithVar v) { - clearFocus(); - - ErrorInformation& vei = d_errInfo.get(v); - vei.setInFocus(true); - FocusSetHandle handle = d_focus.push(v); - vei.setHandle(handle); -} - -void ErrorSet::pushErrorInto(ArithVarVec& vec) const{ - for(error_iterator i = errorBegin(), e = errorEnd(); i != e; ++i ){ - vec.push_back(*i); - } -} - -void ErrorSet::pushFocusInto(ArithVarVec& vec) const{ - for(focus_iterator i = focusBegin(), e = focusEnd(); i != e; ++i ){ - vec.push_back(*i); - } -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/error_set.h b/src/theory/arith/error_set.h deleted file mode 100644 index ec61ee0c8..000000000 --- a/src/theory/arith/error_set.h +++ /dev/null @@ -1,421 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Mathias Preiner, Andres Noetzli - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include -#include - -#include "options/arith_options.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/bound_counts.h" -#include "theory/arith/callbacks.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/partial_model.h" -#include "theory/arith/tableau_sizes.h" -#include "util/bin_heap.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - - -/** - * The priority queue has 3 different modes of operation: - * - Collection - * This passively collects arithmetic variables that may be inconsistent. - * This does not maintain any heap structure. - * dequeueInconsistentBasicVariable() does not work in this mode! - * Entering this mode requires the queue to be empty. - * - * - Difference Queue - * This mode uses the difference between a variables and its bound - * to determine which to dequeue first. - * - * - Variable Order Queue - * This mode uses the variable order to determine which ArithVar is dequeued first. - * - * The transitions between the modes of operation are: - * Collection => Difference Queue - * Difference Queue => Variable Order Queue - * Difference Queue => Collection (queue must be empty!) - * Variable Order Queue => Collection (queue must be empty!) - * - * The queue begins in Collection mode. - */ - - -class ErrorSet; - -class ComparatorPivotRule { -private: - const ErrorSet* d_errorSet; - - options::ErrorSelectionRule d_rule; - - public: - ComparatorPivotRule(); - ComparatorPivotRule(const ErrorSet* es, options::ErrorSelectionRule r); - - bool operator()(ArithVar v, ArithVar u) const; - options::ErrorSelectionRule getRule() const { return d_rule; } -}; - -// typedef boost::heap::d_ary_heap< -// ArithVar, -// boost::heap::arity<2>, -// boost::heap::compare, -// boost::heap::mutable_ > FocusSet; -// -// typedef FocusSet::handle_type FocusSetHandle; - -// typedef CVC5_PB_DS_NAMESPACE::priority_queue< -// ArithVar, -// ComparatorPivotRule, -// CVC5_PB_DS_NAMESPACE::pairing_heap_tag> FocusSet; - -// typedef FocusSet::point_iterator FocusSetHandle; - -typedef BinaryHeap FocusSet; -typedef FocusSet::handle FocusSetHandle; - - -class ErrorInformation { -private: - /** The variable that is in error. */ - ArithVar d_variable; - - /** - * The constraint that was violated. - * This needs to be saved in case that the - * violated constraint - */ - ConstraintP d_violated; - - /** - * This is the sgn of the first derivate the variable must move to satisfy - * the bound violated. - * If d_sgn > 0, then d_violated was a lowerbound. - * If d_sgn < 0, then d_violated was an upperbound. - */ - int d_sgn; - - /** - * If this is true, then the bound is no longer set on d_variables. - * This MUST be undone before this is deleted. - */ - bool d_relaxed; - - /** - * If this is true, then the variable is in the focus set and the focus heap. - * d_handle is then a reasonable thing to interpret. - * If this is false, the variable is somewhere in - */ - bool d_inFocus; - FocusSetHandle d_handle; - - /** - * Auxillary information for storing the difference between a variable and its bound. - * Only set on signals. - */ - std::unique_ptr d_amount; - - /** */ - uint32_t d_metric; - -public: - ErrorInformation(); - ErrorInformation(ArithVar var, ConstraintP vio, int sgn); - ~ErrorInformation(); - ErrorInformation(const ErrorInformation& ei); - ErrorInformation& operator=(const ErrorInformation& ei); - - void reset(ConstraintP c, int sgn); - - inline ArithVar getVariable() const { return d_variable; } - - bool isRelaxed() const { return d_relaxed; } - void setRelaxed() - { - Assert(!d_relaxed); - d_relaxed = true; - } - void setUnrelaxed() - { - Assert(d_relaxed); - d_relaxed = false; - } - - inline int sgn() const { return d_sgn; } - - inline bool inFocus() const { return d_inFocus; } - inline int focusSgn() const { - return (d_inFocus) ? sgn() : 0; - } - - inline void setInFocus(bool inFocus) { d_inFocus = inFocus; } - - const DeltaRational& getAmount() const { - Assert(d_amount != nullptr); - return *d_amount; - } - - void setAmount(const DeltaRational& am); - void setMetric(uint32_t m) { d_metric = m; } - uint32_t getMetric() const { return d_metric; } - - inline void setHandle(FocusSetHandle h) { - Assert(d_inFocus); - d_handle = h; - } - inline const FocusSetHandle& getHandle() const{ return d_handle; } - - inline ConstraintP getViolated() const { return d_violated; } - - bool debugInitialized() const { - return - d_variable != ARITHVAR_SENTINEL && - d_violated != NullConstraint && - d_sgn != 0; - } - void print(std::ostream& os) const { - os << "{ErrorInfo: " << d_variable - << ", " << d_violated - << ", " << d_sgn - << ", " << d_relaxed - << ", " << d_inFocus; - if (d_amount == nullptr) - { - os << "nullptr"; - } - else - { - os << (*d_amount); - } - os << "}"; - } -}; - -class ErrorInfoMap : public DenseMap {}; - -class ErrorSet { -private: - /** - * Reference to the arithmetic partial model for checking if a variable - * is consistent with its upper and lower bounds. - */ - ArithVariables& d_variables; - - /** - * The set of all variables that violate exactly one of their bounds. - */ - ErrorInfoMap d_errInfo; - - options::ErrorSelectionRule d_selectionRule; - /** - * The ordered heap for the variables that are in ErrorSet. - */ - FocusSet d_focus; - - - /** - * A strict subset of the error set. - * d_outOfFocus \neq d_errInfo. - * - * Its symbolic complement is Focus. - * d_outOfFocus \intersect Focus == \emptyset - * d_outOfFocus \union Focus == d_errInfo - */ - ArithVarVec d_outOfFocus; - - /** - * Before a variable is added to the error set, it is added to the signals list. - * A variable may appear on the list multiple times. - * This introduces a delay. - */ - ArithVarVec d_signals; - - TableauSizes d_tableauSizes; - - BoundCountingLookup d_boundLookup; - - /** - * Computes the difference between the assignment and its bound for x. - */ -public: - DeltaRational computeDiff(ArithVar x) const; -private: - void recomputeAmount(ErrorInformation& ei, options::ErrorSelectionRule r); - - void update(ErrorInformation& ei); - void transitionVariableOutOfError(ArithVar v); - void transitionVariableIntoError(ArithVar v); - void addBackIntoFocus(ArithVar v); - -public: - - /** The new focus set is the entire error set. */ - void blur(); - void dropFromFocus(ArithVar v); - - void dropFromFocusAll(const ArithVarVec& vec) { - for(ArithVarVec::const_iterator i = vec.begin(), i_end = vec.end(); i != i_end; ++i){ - ArithVar v = *i; - dropFromFocus(v); - } - } - - ErrorSet(ArithVariables& var, TableauSizes tabSizes, BoundCountingLookup boundLookup); - - typedef ErrorInfoMap::const_iterator error_iterator; - error_iterator errorBegin() const { return d_errInfo.begin(); } - error_iterator errorEnd() const { return d_errInfo.end(); } - - bool inError(ArithVar v) const { return d_errInfo.isKey(v); } - bool inFocus(ArithVar v) const { return d_errInfo[v].inFocus(); } - - void pushErrorInto(ArithVarVec& vec) const; - void pushFocusInto(ArithVarVec& vec) const; - - options::ErrorSelectionRule getSelectionRule() const; - void setSelectionRule(options::ErrorSelectionRule rule); - - inline ArithVar topFocusVariable() const{ - Assert(!focusEmpty()); - return d_focus.top(); - } - - inline void signalVariable(ArithVar var){ - d_signals.push_back(var); - } - - inline void signalUnderCnd(ArithVar var, bool b){ - if(b){ signalVariable(var); } - } - - inline bool inconsistent(ArithVar var) const{ - return !d_variables.assignmentIsConsistent(var) ; - } - inline void signalIfInconsistent(ArithVar var){ - signalUnderCnd(var, inconsistent(var)); - } - - inline bool errorEmpty() const{ - return d_errInfo.empty(); - } - inline uint32_t errorSize() const{ - return d_errInfo.size(); - } - - inline bool focusEmpty() const { - return d_focus.empty(); - } - inline uint32_t focusSize() const{ - return d_focus.size(); - } - - inline int getSgn(ArithVar x) const { - Assert(inError(x)); - return d_errInfo[x].sgn(); - } - inline int focusSgn(ArithVar v) const { - if(inError(v)){ - return d_errInfo[v].focusSgn(); - }else{ - return 0; - } - } - - void focusDownToJust(ArithVar v); - - void clearFocus(); - - /** Clears the set. */ - void clear(); - void reduceToSignals(); - - bool noSignals() const { - return d_signals.empty(); - } - bool moreSignals() const { - return !noSignals(); - } - ArithVar topSignal() const { - Assert(moreSignals()); - return d_signals.back(); - } - - /** - * Moves a variable out of the signals. - * This moves it into the error set. - * Return the previous focus sign. - */ - int popSignal(); - - const DeltaRational& getAmount(ArithVar v) const { - return d_errInfo[v].getAmount(); - } - - uint32_t sumMetric(ArithVar a) const{ - Assert(inError(a)); - BoundCounts bcs = d_boundLookup.atBounds(a); - uint32_t count = getSgn(a) > 0 ? bcs.upperBoundCount() : bcs.lowerBoundCount(); - - uint32_t length = d_tableauSizes.getRowLength(a); - - return (length - count); - } - - uint32_t getMetric(ArithVar a) const { - return d_errInfo[a].getMetric(); - } - - ConstraintP getViolated(ArithVar a) const { - return d_errInfo[a].getViolated(); - } - - - typedef FocusSet::const_iterator focus_iterator; - focus_iterator focusBegin() const { return d_focus.begin(); } - focus_iterator focusEnd() const { return d_focus.end(); } - - void debugPrint(std::ostream& out) const; - -private: - class Statistics { - public: - IntStat d_enqueues; - IntStat d_enqueuesCollection; - IntStat d_enqueuesDiffMode; - IntStat d_enqueuesVarOrderMode; - - IntStat d_enqueuesCollectionDuplicates; - IntStat d_enqueuesVarOrderModeDuplicates; - - Statistics(); - }; - - Statistics d_statistics; -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/fc_simplex.cpp b/src/theory/arith/fc_simplex.cpp deleted file mode 100644 index a4bb076d3..000000000 --- a/src/theory/arith/fc_simplex.cpp +++ /dev/null @@ -1,787 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T)decision procedure. - */ -#include "theory/arith/fc_simplex.h" - -#include "base/output.h" -#include "options/arith_options.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" -#include "theory/arith/error_set.h" -#include "util/statistics_stats.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -FCSimplexDecisionProcedure::FCSimplexDecisionProcedure( - Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc) - : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), - d_focusSize(0), - d_focusErrorVar(ARITHVAR_SENTINEL), - d_focusCoefficients(), - d_pivotBudget(0), - d_prevWitnessImprovement(AntiProductive), - d_witnessImprovementInARow(0), - d_sgnDisagreements(), - d_statistics("theory::arith::FC::", d_pivots) -{ } - -FCSimplexDecisionProcedure::Statistics::Statistics(const std::string& name, - uint32_t& pivots) - : d_initialSignalsTime( - smtStatisticsRegistry().registerTimer(name + "initialProcessTime")), - d_initialConflicts( - smtStatisticsRegistry().registerInt(name + "UpdateConflicts")), - d_fcFoundUnsat(smtStatisticsRegistry().registerInt(name + "FoundUnsat")), - d_fcFoundSat(smtStatisticsRegistry().registerInt(name + "FoundSat")), - d_fcMissed(smtStatisticsRegistry().registerInt(name + "Missed")), - d_fcTimer(smtStatisticsRegistry().registerTimer(name + "Timer")), - d_fcFocusConstructionTimer( - smtStatisticsRegistry().registerTimer(name + "Construction")), - d_selectUpdateForDualLike(smtStatisticsRegistry().registerTimer( - name + "selectUpdateForDualLike")), - d_selectUpdateForPrimal(smtStatisticsRegistry().registerTimer( - name + "selectUpdateForPrimal")), - d_finalCheckPivotCounter( - smtStatisticsRegistry().registerReference( - name + "lastPivots", pivots)) -{ -} - -Result::Status FCSimplexDecisionProcedure::findModel(bool exactResult) -{ - Assert(d_conflictVariables.empty()); - Assert(d_sgnDisagreements.empty()); - - d_pivots = 0; - - if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ - Trace("arith::findModel") << "fcFindModel() trivial" << endl; - Assert(d_conflictVariables.empty()); - return Result::SAT; - } - - // We need to reduce this because of - d_errorSet.reduceToSignals(); - - // We must start tracking NOW - d_errorSet.setSelectionRule(options::ErrorSelectionRule::SUM_METRIC); - - if(initialProcessSignals()){ - d_conflictVariables.purge(); - Trace("arith::findModel") << "fcFindModel() early conflict" << endl; - Assert(d_conflictVariables.empty()); - return Result::UNSAT; - }else if(d_errorSet.errorEmpty()){ - Trace("arith::findModel") << "fcFindModel() fixed itself" << endl; - Assert(d_conflictVariables.empty()); - return Result::SAT; - } - - Trace("arith::findModel") << "fcFindModel() start non-trivial" << endl; - - exactResult |= d_varOrderPivotLimit < 0; - - d_prevWitnessImprovement = HeuristicDegenerate; - d_witnessImprovementInARow = 0; - - Result::Status result = Result::UNKNOWN; - - if (result == Result::UNKNOWN) - { - if(exactResult){ - d_pivotBudget = -1; - }else{ - d_pivotBudget = d_varOrderPivotLimit; - } - - result = dualLike(); - - if(result == Result::UNSAT){ - ++(d_statistics.d_fcFoundUnsat); - }else if(d_errorSet.errorEmpty()){ - ++(d_statistics.d_fcFoundSat); - }else{ - ++(d_statistics.d_fcMissed); - } - } - - Assert(!d_errorSet.moreSignals()); - if (result == Result::UNKNOWN && d_errorSet.errorEmpty()) - { - result = Result::SAT; - } - - // ensure that the conflict variable is still in the queue. - d_conflictVariables.purge(); - - Trace("arith::findModel") << "end findModel() " << result << endl; - - Assert(d_conflictVariables.empty()); - return result; -} - - -void FCSimplexDecisionProcedure::logPivot(WitnessImprovement w){ - if(d_pivotBudget > 0) { - --d_pivotBudget; - } - Assert(w != AntiProductive); - - if(w == d_prevWitnessImprovement){ - ++d_witnessImprovementInARow; - // ignore overflow : probably never reached - if(d_witnessImprovementInARow == 0){ - --d_witnessImprovementInARow; - } - }else{ - if(w != BlandsDegenerate){ - d_witnessImprovementInARow = 1; - } - // if w == BlandsDegenerate do not reset the counter - d_prevWitnessImprovement = w; - } - if(strongImprovement(w)){ - d_leavingCountSinceImprovement.purge(); - } - - Trace("logPivot") << "logPivot " << d_prevWitnessImprovement << " " << d_witnessImprovementInARow << endl; - -} - -uint32_t FCSimplexDecisionProcedure::degeneratePivotsInARow() const { - switch(d_prevWitnessImprovement){ - case ConflictFound: - case ErrorDropped: - case FocusImproved: - return 0; - case HeuristicDegenerate: - case BlandsDegenerate: - return d_witnessImprovementInARow; - // Degenerate is unreachable for its own reasons - case Degenerate: - case FocusShrank: - case AntiProductive: - Unreachable(); - return -1; - } - Unreachable(); -} - -void FCSimplexDecisionProcedure::adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges){ - uint32_t newErrorSize = d_errorSet.errorSize(); - uint32_t newFocusSize = d_errorSet.focusSize(); - - //Assert(!d_conflictVariables.empty() || newFocusSize <= d_focusSize); - Assert(!d_conflictVariables.empty() || newErrorSize <= d_errorSize); - - if(newFocusSize == 0 || !d_conflictVariables.empty() ){ - tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); - d_focusErrorVar = ARITHVAR_SENTINEL; - }else if(2*newFocusSize < d_focusSize ){ - tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); - d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); - }else{ - adjustInfeasFunc(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar, focusChanges); - } - - d_errorSize = newErrorSize; - d_focusSize = newFocusSize; -} - -WitnessImprovement FCSimplexDecisionProcedure::adjustFocusShrank(const ArithVarVec& dropped){ - Assert(dropped.size() > 0); - Assert(d_errorSet.focusSize() == d_focusSize); - Assert(d_errorSet.focusSize() > dropped.size()); - - uint32_t newFocusSize = d_focusSize - dropped.size(); - Assert(newFocusSize > 0); - - if(2 * newFocusSize <= d_focusSize){ - d_errorSet.dropFromFocusAll(dropped); - tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); - d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); - }else{ - shrinkInfeasFunc(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar, dropped); - d_errorSet.dropFromFocusAll(dropped); - } - - d_focusSize = newFocusSize; - Assert(d_errorSet.focusSize() == d_focusSize); - return FocusShrank; -} - -WitnessImprovement FCSimplexDecisionProcedure::focusDownToJust(ArithVar v){ - // uint32_t newErrorSize = d_errorSet.errorSize(); - // uint32_t newFocusSize = d_errorSet.focusSize(); - Assert(d_focusSize == d_errorSet.focusSize()); - Assert(d_focusSize > 1); - Assert(d_errorSet.inFocus(v)); - - d_errorSet.focusDownToJust(v); - Assert(d_errorSet.focusSize() == 1); - d_focusSize = 1; - - tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); - d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); - - return FocusShrank; -} - - - -UpdateInfo FCSimplexDecisionProcedure::selectPrimalUpdate(ArithVar basic, LinearEqualityModule::UpdatePreferenceFunction upf, LinearEqualityModule::VarPreferenceFunction bpf) { - UpdateInfo selected; - - Trace("arith::selectPrimalUpdate") - << "selectPrimalUpdate" << endl - << basic << " " << d_tableau.basicRowLength(basic) << " " - << d_linEq.debugBasicAtBoundCount(basic) << endl; - - static constexpr int s_maxCandidatesAfterImprove = 3; - bool isFocus = basic == d_focusErrorVar; - Assert(isFocus || d_errorSet.inError(basic)); - int basicDir = isFocus? 1 : d_errorSet.getSgn(basic); - bool dualLike = !isFocus && d_focusSize > 1; - - if(!isFocus){ - loadFocusSigns(); - } - - decreasePenalties(); - - typedef std::vector CandVector; - CandVector candidates; - - for(Tableau::RowIterator ri = d_tableau.basicRowIterator(basic); !ri.atEnd(); ++ri){ - const Tableau::Entry& e = *ri; - ArithVar curr = e.getColVar(); - if(curr == basic){ continue; } - - int sgn = e.getCoefficient().sgn(); - int curr_movement = basicDir * sgn; - - bool candidate = - (curr_movement > 0 && d_variables.cmpAssignmentUpperBound(curr) < 0) || - (curr_movement < 0 && d_variables.cmpAssignmentLowerBound(curr) > 0); - - Trace("arith::selectPrimalUpdate") - << "storing " << basic - << " " << curr - << " " << candidate - << " " << e.getCoefficient() - << " " << curr_movement - << " " << focusCoefficient(curr) << endl; - - if(!candidate) { continue; } - - if(!isFocus){ - const Rational& focusC = focusCoefficient(curr); - Assert(dualLike || !focusC.isZero()); - if(dualLike && curr_movement != focusC.sgn()){ - Trace("arith::selectPrimalUpdate") << "sgn disagreement " << curr << endl; - d_sgnDisagreements.push_back(curr); - continue; - }else{ - candidates.push_back(Cand(curr, penalty(curr), curr_movement, &focusC)); - } - }else{ - candidates.push_back(Cand(curr, penalty(curr), curr_movement, &e.getCoefficient())); - } - } - - CompPenaltyColLength colCmp(&d_linEq, options().arith.havePenalties); - CandVector::iterator i = candidates.begin(); - CandVector::iterator end = candidates.end(); - std::make_heap(i, end, colCmp); - - bool checkEverything = d_pivots == 0; - - int candidatesAfterFocusImprove = 0; - while(i != end && (checkEverything || candidatesAfterFocusImprove <= s_maxCandidatesAfterImprove)){ - std::pop_heap(i, end, colCmp); - --end; - Cand& cand = (*end); - ArithVar curr = cand.d_nb; - const Rational& coeff = *cand.d_coeff; - - LinearEqualityModule::UpdatePreferenceFunction leavingPrefFunc = selectLeavingFunction(curr); - UpdateInfo currProposal = d_linEq.speculativeUpdate(curr, coeff, leavingPrefFunc); - - Trace("arith::selectPrimalUpdate") - << "selected " << selected << endl - << "currProp " << currProposal << endl - << "coeff " << coeff << endl; - - Assert(!currProposal.uninitialized()); - - if(candidatesAfterFocusImprove > 0){ - candidatesAfterFocusImprove++; - } - - if(selected.uninitialized() || (d_linEq.*upf)(selected, currProposal)){ - - selected = currProposal; - WitnessImprovement w = selected.getWitness(false); - Trace("arith::selectPrimalUpdate") << "selected " << w << endl; - setPenalty(curr, w); - if(improvement(w)){ - bool exitEarly; - switch(w){ - case ConflictFound: exitEarly = true; break; - case ErrorDropped: - if(checkEverything){ - exitEarly = d_errorSize + selected.errorsChange() == 0; - Trace("arith::selectPrimalUpdate") - << "ee " << d_errorSize << " " - << selected.errorsChange() << " " - << d_errorSize + selected.errorsChange() << endl; - }else{ - exitEarly = true; - } - break; - case FocusImproved: - candidatesAfterFocusImprove = 1; - exitEarly = false; - break; - default: - exitEarly = false; break; - } - if(exitEarly){ break; } - } - }else{ - Trace("arith::selectPrimalUpdate") << "dropped "<< endl; - } - - } - - if(!isFocus){ - unloadFocusSigns(); - } - return selected; -} - -bool FCSimplexDecisionProcedure::debugCheckWitness(const UpdateInfo& inf, WitnessImprovement w, bool useBlands){ - if(inf.getWitness(useBlands) == w){ - switch(w){ - case ConflictFound: return inf.foundConflict(); - case ErrorDropped: return inf.errorsChange() < 0; - case FocusImproved: return inf.focusDirection() > 0; - case FocusShrank: return false; // This is not a valid output - case Degenerate: return false; // This is not a valid output - case BlandsDegenerate: return useBlands; - case HeuristicDegenerate: return !useBlands; - case AntiProductive: return false; - } - } - return false; -} - -WitnessImprovement FCSimplexDecisionProcedure::primalImproveError(ArithVar errorVar){ - bool useBlands = degeneratePivotsInARow() >= s_maxDegeneratePivotsBeforeBlandsOnLeaving; - UpdateInfo selected = selectUpdateForPrimal (errorVar, useBlands); - Assert(!selected.uninitialized()); - WitnessImprovement w = selected.getWitness(useBlands); - Assert(debugCheckWitness(selected, w, useBlands)); - - updateAndSignal(selected, w); - logPivot(w); - return w; -} - - -WitnessImprovement FCSimplexDecisionProcedure::focusUsingSignDisagreements(ArithVar basic){ - Assert(!d_sgnDisagreements.empty()); - Assert(d_errorSet.focusSize() >= 2); - - if(TraceIsOn("arith::focus")){ - d_errorSet.debugPrint(Trace("arith::focus")); - } - - ArithVar nb = d_linEq.minBy(d_sgnDisagreements, &LinearEqualityModule::minColLength); - const Tableau::Entry& e_evar_nb = d_tableau.basicFindEntry(basic, nb); - int oppositeSgn = - (e_evar_nb.getCoefficient().sgn()); - Trace("arith::focus") << "focusUsingSignDisagreements " << basic << " " << oppositeSgn << endl; - - ArithVarVec dropped; - - Tableau::ColIterator colIter = d_tableau.colIterator(nb); - for(; !colIter.atEnd(); ++colIter){ - const Tableau::Entry& entry = *colIter; - Assert(entry.getColVar() == nb); - - int sgn = entry.getCoefficient().sgn(); - Trace("arith::focus") - << "on row " - << d_tableau.rowIndexToBasic(entry.getRowIndex()) - << " " - << entry.getCoefficient() << endl; - ArithVar currRow = d_tableau.rowIndexToBasic(entry.getRowIndex()); - if(d_errorSet.inError(currRow) && d_errorSet.inFocus(currRow)){ - int errSgn = d_errorSet.getSgn(currRow); - - if(errSgn * sgn == oppositeSgn){ - dropped.push_back(currRow); - Trace("arith::focus") << "dropping from focus " << currRow << endl; - } - } - } - - d_sgnDisagreements.clear(); - return adjustFocusShrank(dropped); -} - -bool debugSelectedErrorDropped(const UpdateInfo& selected, int32_t prevErrorSize, int32_t currErrorSize){ - int diff = currErrorSize - prevErrorSize; - return selected.foundConflict() || diff == selected.errorsChange(); -} - -void FCSimplexDecisionProcedure::debugPrintSignal(ArithVar updated) const{ - Trace("updateAndSignal") << "updated basic " << updated; - Trace("updateAndSignal") << " length " << d_tableau.basicRowLength(updated); - Trace("updateAndSignal") << " consistent " << d_variables.assignmentIsConsistent(updated); - int dir = !d_variables.assignmentIsConsistent(updated) ? - d_errorSet.getSgn(updated) : 0; - Trace("updateAndSignal") << " dir " << dir; - Trace("updateAndSignal") << " debugBasicAtBoundCount " << d_linEq.debugBasicAtBoundCount(updated) << endl; -} - -bool debugUpdatedBasic(const UpdateInfo& selected, ArithVar updated){ - if(selected.describesPivot() && updated == selected.leaving()){ - return selected.foundConflict(); - }else{ - return true; - } -} - -void FCSimplexDecisionProcedure::updateAndSignal(const UpdateInfo& selected, WitnessImprovement w){ - ArithVar nonbasic = selected.nonbasic(); - - Trace("updateAndSignal") << "updateAndSignal " << selected << endl; - - stringstream ss; - - if(selected.describesPivot()){ - ConstraintP limiting = selected.limiting(); - ArithVar basic = limiting->getVariable(); - Assert(d_linEq.basicIsTracked(basic)); - d_linEq.pivotAndUpdate(basic, nonbasic, limiting->getValue()); - }else{ - Assert(!selected.unbounded() || selected.errorsChange() < 0); - - DeltaRational newAssignment = - d_variables.getAssignment(nonbasic) + selected.nonbasicDelta(); - - d_linEq.updateTracked(nonbasic, newAssignment); - } - d_pivots++; - - increaseLeavingCount(nonbasic); - - vector< pair > focusChanges; - while(d_errorSet.moreSignals()){ - ArithVar updated = d_errorSet.topSignal(); - int prevFocusSgn = d_errorSet.popSignal(); - - if(d_tableau.isBasic(updated)){ - Assert(!d_variables.assignmentIsConsistent(updated) - == d_errorSet.inError(updated)); - if(TraceIsOn("updateAndSignal")){debugPrintSignal(updated);} - if(!d_variables.assignmentIsConsistent(updated)){ - if(checkBasicForConflict(updated)){ - reportConflict(updated); - Assert(debugUpdatedBasic(selected, updated)); - } - } - }else{ - Trace("updateAndSignal") << "updated nonbasic " << updated << endl; - } - int currFocusSgn = d_errorSet.focusSgn(updated); - if(currFocusSgn != prevFocusSgn){ - int change = currFocusSgn - prevFocusSgn; - focusChanges.push_back(make_pair(updated, change)); - } - } - - if(TraceIsOn("error")){ d_errorSet.debugPrint(Trace("error")); } - - Assert( - debugSelectedErrorDropped(selected, d_errorSize, d_errorSet.errorSize())); - - adjustFocusAndError(selected, focusChanges); -} - -WitnessImprovement FCSimplexDecisionProcedure::dualLikeImproveError(ArithVar errorVar){ - Assert(d_sgnDisagreements.empty()); - Assert(d_focusSize > 1); - - UpdateInfo selected = selectUpdateForDualLike(errorVar); - - if(selected.uninitialized()){ - // we found no proposals - // If this is empty, there must be an error on this variable! - // this should not be possible. It Should have been caught as a signal earlier - WitnessImprovement dropped = focusUsingSignDisagreements(errorVar); - Assert(d_sgnDisagreements.empty()); - - return dropped; - }else{ - d_sgnDisagreements.clear(); - } - - Assert(d_sgnDisagreements.empty()); - Assert(!selected.uninitialized()); - - if(selected.focusDirection() == 0 && - d_prevWitnessImprovement == HeuristicDegenerate && - d_witnessImprovementInARow >= s_focusThreshold){ - - Trace("focusDownToJust") << "focusDownToJust " << errorVar << endl; - - return focusDownToJust(errorVar); - }else{ - WitnessImprovement w = selected.getWitness(false); - Assert(debugCheckWitness(selected, w, false)); - updateAndSignal(selected, w); - logPivot(w); - return w; - } -} - -WitnessImprovement FCSimplexDecisionProcedure::focusDownToLastHalf(){ - Assert(d_focusSize >= 2); - - Trace("focusDownToLastHalf") << "focusDownToLastHalf " - << d_errorSet.errorSize() << " " - << d_errorSet.focusSize() << " "; - - uint32_t half = d_focusSize/2; - ArithVarVec buf; - for(ErrorSet::focus_iterator i = d_errorSet.focusBegin(), - i_end = d_errorSet.focusEnd(); i != i_end; ++i){ - if(half > 0){ - --half; - } else{ - buf.push_back(*i); - } - } - WitnessImprovement w = adjustFocusShrank(buf); - Trace("focusDownToLastHalf") << "-> " << d_errorSet.focusSize() << endl; - return w; -} - -WitnessImprovement FCSimplexDecisionProcedure::selectFocusImproving() { - Assert(d_focusErrorVar != ARITHVAR_SENTINEL); - Assert(d_focusSize >= 2); - - LinearEqualityModule::UpdatePreferenceFunction upf = - &LinearEqualityModule::preferWitness; - - LinearEqualityModule::VarPreferenceFunction bpf = - &LinearEqualityModule::minRowLength; - - UpdateInfo selected = selectPrimalUpdate(d_focusErrorVar, upf, bpf); - - if(selected.uninitialized()){ - Trace("selectFocusImproving") << "focus is optimum, but we don't have sat/conflict yet" << endl; - - return focusDownToLastHalf(); - } - Assert(!selected.uninitialized()); - WitnessImprovement w = selected.getWitness(false); - Assert(debugCheckWitness(selected, w, false)); - - if(degenerate(w)){ - Trace("selectFocusImproving") << "only degenerate" << endl; - if(d_prevWitnessImprovement == HeuristicDegenerate && - d_witnessImprovementInARow >= s_focusThreshold){ - Trace("selectFocusImproving") << "focus down been degenerate too long" << endl; - return focusDownToLastHalf(); - }else{ - Trace("selectFocusImproving") << "taking degenerate" << endl; - } - } - Trace("selectFocusImproving") << "selectFocusImproving did this " << selected << endl; - - updateAndSignal(selected, w); - logPivot(w); - return w; -} - -bool FCSimplexDecisionProcedure::debugDualLike(WitnessImprovement w, - ostream& out, - uint32_t prevFocusSize, - uint32_t prevErrorSize) const -{ - out << "DLV() "; - switch(w){ - case ConflictFound: - out << "found conflict" << endl; - return !d_conflictVariables.empty(); - case ErrorDropped: - out << "dropped " << prevErrorSize - d_errorSize << endl; - return d_errorSize < prevErrorSize; - case FocusImproved: - out << "focus improved"<< endl; - return d_errorSize == prevErrorSize; - case FocusShrank: - out << "focus shrank"<< endl; - return d_errorSize == prevErrorSize && prevFocusSize > d_focusSize; - case BlandsDegenerate: - out << "bland degenerate"<< endl; - return true; - case HeuristicDegenerate: - out << "heuristic degenerate"<< endl; - return true; - case AntiProductive: - out << "focus blur" << endl; - return prevFocusSize == 0; - case Degenerate: - return false; - } - return false; -} - -Result::Status FCSimplexDecisionProcedure::dualLike() -{ - TimerStat::CodeTimer codeTimer(d_statistics.d_fcTimer); - - Assert(d_sgnDisagreements.empty()); - Assert(d_pivotBudget != 0); - Assert(d_errorSize == d_errorSet.errorSize()); - Assert(d_errorSize > 0); - Assert(d_focusSize == d_errorSet.focusSize()); - Assert(d_focusSize > 0); - Assert(d_conflictVariables.empty()); - Assert(d_focusErrorVar == ARITHVAR_SENTINEL); - - d_scores.purge(); - d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); - - - while(d_pivotBudget != 0 && d_errorSize > 0 && d_conflictVariables.empty()){ - Trace("dualLike") << "dualLike " << endl; - - Assert(d_errorSet.noSignals()); - - WitnessImprovement w = AntiProductive; - uint32_t prevFocusSize = d_focusSize; - uint32_t prevErrorSize = d_errorSize; - - if(d_focusSize == 0){ - Assert(d_errorSize == d_errorSet.errorSize()); - Assert(d_focusErrorVar == ARITHVAR_SENTINEL); - - d_errorSet.blur(); - - d_focusSize = d_errorSet.focusSize(); - - Assert(d_errorSize == d_focusSize); - Assert(d_errorSize >= 1); - - d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); - - Trace("dualLike") << "blur " << d_focusSize << endl; - }else if(d_focusSize == 1){ - // Possible outcomes: - // - errorSet size shrunk - // -- fixed v - // -- fixed something other than v - // - conflict - // - budget was exhausted - - ArithVar e = d_errorSet.topFocusVariable(); - Trace("dualLike") << "primalImproveError " << e << endl; - w = primalImproveError(e); - }else{ - - // Possible outcomes: - // - errorSet size shrunk - // -- fixed v - // -- fixed something other than v - // - conflict - // - budget was exhausted - // - focus went down - Assert(d_focusSize > 1); - ArithVar e = d_errorSet.topFocusVariable(); - static constexpr unsigned s_sumMetricThreshold = 1; - if(d_errorSet.sumMetric(e) <= s_sumMetricThreshold){ - Trace("dualLike") << "dualLikeImproveError " << e << endl; - w = dualLikeImproveError(e); - }else{ - Trace("dualLike") << "selectFocusImproving " << endl; - w = selectFocusImproving(); - } - } - Trace("dualLike") << "witnessImprovement: " << w << endl; - Assert(d_focusSize == d_errorSet.focusSize()); - Assert(d_errorSize == d_errorSet.errorSize()); - - Assert(debugDualLike(w, Trace("dualLike"), prevFocusSize, prevErrorSize)); - Trace("dualLike") << "Focus size " << d_focusSize << " (was " << prevFocusSize << ")" << endl; - Trace("dualLike") << "Error size " << d_errorSize << " (was " << prevErrorSize << ")" << endl; - } - - - if(d_focusErrorVar != ARITHVAR_SENTINEL){ - tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); - d_focusErrorVar = ARITHVAR_SENTINEL; - } - - Assert(d_focusErrorVar == ARITHVAR_SENTINEL); - if(!d_conflictVariables.empty()){ - return Result::UNSAT; - }else if(d_errorSet.errorEmpty()){ - Assert(d_errorSet.noSignals()); - return Result::SAT; - }else{ - Assert(d_pivotBudget == 0); - return Result::UNKNOWN; - } -} - - -void FCSimplexDecisionProcedure::loadFocusSigns(){ - Assert(d_focusCoefficients.empty()); - Assert(d_focusErrorVar != ARITHVAR_SENTINEL); - for(Tableau::RowIterator ri = d_tableau.basicRowIterator(d_focusErrorVar); !ri.atEnd(); ++ri){ - const Tableau::Entry& e = *ri; - ArithVar curr = e.getColVar(); - d_focusCoefficients.set(curr, &e.getCoefficient()); - } -} - -void FCSimplexDecisionProcedure::unloadFocusSigns(){ - d_focusCoefficients.purge(); -} - -const Rational& FCSimplexDecisionProcedure::focusCoefficient(ArithVar nb) const { - if(d_focusCoefficients.isKey(nb)){ - return *(d_focusCoefficients[nb]); - }else{ - return d_zero; - } -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/fc_simplex.h b/src/theory/arith/fc_simplex.h deleted file mode 100644 index c9460916f..000000000 --- a/src/theory/arith/fc_simplex.h +++ /dev/null @@ -1,259 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Morgan Deters - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T)decision procedure. - * - * This implements the Simplex module for the Simpelx for DPLL(T) decision - * procedure. - * See the Simplex for DPLL(T) technical report for more background.(citation?) - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This is required to either produce a conflict or satisifying PartialModel. - * Further, we require being told when a basic variable updates its value. - * - * During the Simplex search we maintain a queue of variables. - * The queue is required to contain all of the basic variables that voilate - * their bounds. - * As elimination from the queue is more efficient to be done lazily, - * we do not maintain that the queue of variables needs to be only basic - * variables or only variables that satisfy their bounds. - * - * The simplex procedure roughly follows Alberto's thesis. (citation?) - * There is one round of selecting using a heuristic pivoting rule. - * (See PreferenceFunction Documentation for the available options.) - * The non-basic variable is the one that appears in the fewest pivots. - * (Bruno says that Leonardo invented this first.) - * After this, Bland's pivot rule is invoked. - * - * During this proccess, we periodically inspect the queue of variables to - * 1) remove now extraneous extries, - * 2) detect conflicts that are "waiting" on the queue but may not be detected - * by the current queue heuristics, and - * 3) detect multiple conflicts. - * - * Conflicts are greedily slackened to use the weakest bounds that still - * produce the conflict. - * - * Extra things tracked atm: (Subject to change at Tim's whims) - * - A superset of all of the newly pivoted variables. - * - A queue of additional conflicts that were discovered by Simplex. - * These are theory valid and are currently turned into lemmas - */ - -#include "cvc5_private.h" - -#pragma once - -#include "theory/arith/error_set.h" -#include "theory/arith/linear_equality.h" -#include "theory/arith/simplex.h" -#include "theory/arith/simplex_update.h" -#include "util/dense_map.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class FCSimplexDecisionProcedure : public SimplexDecisionProcedure{ -public: - FCSimplexDecisionProcedure(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc); - - Result::Status findModel(bool exactResult) override; - - // other error variables are dropping - WitnessImprovement dualLikeImproveError(ArithVar evar); - WitnessImprovement primalImproveError(ArithVar evar); - - // dual like - // - found conflict - // - satisfied error set - Result::Status dualLike(); - -private: - static constexpr uint32_t PENALTY = 4; - DenseMultiset d_scores; - void decreasePenalties() { d_scores.removeOneOfEverything(); } - uint32_t penalty(ArithVar x) const { return d_scores.count(x); } - void setPenalty(ArithVar x, WitnessImprovement w) - { - if (improvement(w)) - { - if (d_scores.count(x) > 0) - { - d_scores.removeAll(x); - } - } - else - { - d_scores.setCount(x, PENALTY); - } - } - - /** The size of the focus set. */ - uint32_t d_focusSize; - - /** The current error focus variable. */ - ArithVar d_focusErrorVar; - - /** - * The signs of the coefficients in the focus set. - * This is empty until this has been loaded. - */ - DenseMap d_focusCoefficients; - - /** - * Loads the signs of the coefficients of the variables on the row d_focusErrorVar - * into d_focusSgns. - */ - void loadFocusSigns(); - - /** Unloads the information from d_focusSgns. */ - void unloadFocusSigns(); - - /** - * The signs of a variable in the row of d_focusErrorVar. - * d_focusSgns must be loaded. - */ - const Rational& focusCoefficient(ArithVar nb) const; - - int32_t d_pivotBudget; - - WitnessImprovement d_prevWitnessImprovement; - uint32_t d_witnessImprovementInARow; - - uint32_t degeneratePivotsInARow() const; - - static constexpr uint32_t s_focusThreshold = 6; - static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnLeaving = 100; - static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnEntering = 10; - - DenseMap d_leavingCountSinceImprovement; - void increaseLeavingCount(ArithVar x){ - if(!d_leavingCountSinceImprovement.isKey(x)){ - d_leavingCountSinceImprovement.set(x,1); - }else{ - (d_leavingCountSinceImprovement.get(x))++; - } - } - LinearEqualityModule::UpdatePreferenceFunction selectLeavingFunction(ArithVar x){ - bool useBlands = d_leavingCountSinceImprovement.isKey(x) && - d_leavingCountSinceImprovement[x] >= s_maxDegeneratePivotsBeforeBlandsOnEntering; - if(useBlands) { - return &LinearEqualityModule::preferWitness; - } else { - return &LinearEqualityModule::preferWitness; - } - } - - bool debugDualLike(WitnessImprovement w, std::ostream& out, - uint32_t prevFocusSize, uint32_t prevErrorSize) const; - - void debugPrintSignal(ArithVar updated) const; - - ArithVarVec d_sgnDisagreements; - - void logPivot(WitnessImprovement w); - - void updateAndSignal(const UpdateInfo& selected, WitnessImprovement w); - - UpdateInfo selectPrimalUpdate(ArithVar error, - LinearEqualityModule::UpdatePreferenceFunction upf, - LinearEqualityModule::VarPreferenceFunction bpf); - - - UpdateInfo selectUpdateForDualLike(ArithVar basic){ - TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForDualLike); - - LinearEqualityModule::UpdatePreferenceFunction upf = - &LinearEqualityModule::preferWitness; - LinearEqualityModule::VarPreferenceFunction bpf = - &LinearEqualityModule::minVarOrder; - return selectPrimalUpdate(basic, upf, bpf); - } - - UpdateInfo selectUpdateForPrimal(ArithVar basic, bool useBlands){ - TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForPrimal); - - LinearEqualityModule::UpdatePreferenceFunction upf; - if(useBlands) { - upf = &LinearEqualityModule::preferWitness; - } else { - upf = &LinearEqualityModule::preferWitness; - } - - LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? - &LinearEqualityModule::minVarOrder : - &LinearEqualityModule::minRowLength; - - return selectPrimalUpdate(basic, upf, bpf); - } - WitnessImprovement selectFocusImproving() ; - - WitnessImprovement focusUsingSignDisagreements(ArithVar basic); - WitnessImprovement focusDownToLastHalf(); - WitnessImprovement adjustFocusShrank(const ArithVarVec& drop); - WitnessImprovement focusDownToJust(ArithVar v); - - - void adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges); - - /** - * This is the main simplex for DPLL(T) loop. - * It runs for at most maxIterations. - * - * Returns true iff it has found a conflict. - * d_conflictVariable will be set and the conflict for this row is reported. - */ - bool searchForFeasibleSolution(uint32_t maxIterations); - - bool initialProcessSignals(){ - TimerStat &timer = d_statistics.d_initialSignalsTime; - IntStat& conflictStat = d_statistics.d_initialConflicts; - bool res = standardProcessSignals(timer, conflictStat); - d_focusSize = d_errorSet.focusSize(); - return res; - } - - static bool debugCheckWitness(const UpdateInfo& inf, WitnessImprovement w, bool useBlands); - - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - TimerStat d_initialSignalsTime; - IntStat d_initialConflicts; - - IntStat d_fcFoundUnsat; - IntStat d_fcFoundSat; - IntStat d_fcMissed; - - TimerStat d_fcTimer; - TimerStat d_fcFocusConstructionTimer; - - TimerStat d_selectUpdateForDualLike; - TimerStat d_selectUpdateForPrimal; - - ReferenceStat d_finalCheckPivotCounter; - - Statistics(const std::string& name, uint32_t& pivots); - } d_statistics; -};/* class FCSimplexDecisionProcedure */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/infer_bounds.cpp b/src/theory/arith/infer_bounds.cpp deleted file mode 100644 index 30ae5f934..000000000 --- a/src/theory/arith/infer_bounds.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andres Noetzli, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/infer_bounds.h" -#include "theory/rewriter.h" - -using namespace cvc5::internal::kind; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -using namespace inferbounds; - -InferBoundAlgorithm::InferBoundAlgorithm() - : d_alg(None) -{} - -InferBoundAlgorithm::InferBoundAlgorithm(Algorithms a) - : d_alg(a) -{ - Assert(a != Simplex); -} - -InferBoundAlgorithm::InferBoundAlgorithm( - const std::optional& simplexRounds) - : d_alg(Simplex) -{} - -Algorithms InferBoundAlgorithm::getAlgorithm() const{ - return d_alg; -} - -const std::optional& InferBoundAlgorithm::getSimplexRounds() const -{ - Assert(getAlgorithm() == Simplex); - return d_simplexRounds; -} - -InferBoundAlgorithm InferBoundAlgorithm::mkLookup(){ - return InferBoundAlgorithm(Lookup); -} - -InferBoundAlgorithm InferBoundAlgorithm::mkRowSum(){ - return InferBoundAlgorithm(RowSum); -} - -InferBoundAlgorithm InferBoundAlgorithm::mkSimplex( - const std::optional& rounds) -{ - return InferBoundAlgorithm(rounds); -} - -ArithEntailmentCheckParameters::ArithEntailmentCheckParameters() - : d_algorithms() -{} - -ArithEntailmentCheckParameters::~ArithEntailmentCheckParameters() -{} - - -void ArithEntailmentCheckParameters::addLookupRowSumAlgorithms(){ - addAlgorithm(InferBoundAlgorithm::mkLookup()); - addAlgorithm(InferBoundAlgorithm::mkRowSum()); -} - -void ArithEntailmentCheckParameters::addAlgorithm(const inferbounds::InferBoundAlgorithm& alg){ - d_algorithms.push_back(alg); -} - -ArithEntailmentCheckParameters::const_iterator ArithEntailmentCheckParameters::begin() const{ - return d_algorithms.begin(); -} - -ArithEntailmentCheckParameters::const_iterator ArithEntailmentCheckParameters::end() const{ - return d_algorithms.end(); -} - -InferBoundsResult::InferBoundsResult() - : d_foundBound(false) - , d_budgetExhausted(false) - , d_boundIsProvenOpt(false) - , d_inconsistentState(false) - , d_reachedThreshold(false) - , d_value(false) - , d_term(Node::null()) - , d_upperBound(true) - , d_explanation(Node::null()) -{} - -InferBoundsResult::InferBoundsResult(Node term, bool ub) - : d_foundBound(false) - , d_budgetExhausted(false) - , d_boundIsProvenOpt(false) - , d_inconsistentState(false) - , d_reachedThreshold(false) - , d_value(false) - , d_term(term) - , d_upperBound(ub) - , d_explanation(Node::null()) -{} - -bool InferBoundsResult::foundBound() const { - return d_foundBound; -} -bool InferBoundsResult::boundIsOptimal() const { - return d_boundIsProvenOpt; -} -bool InferBoundsResult::inconsistentState() const { - return d_inconsistentState; -} - -bool InferBoundsResult::boundIsInteger() const{ - return foundBound() && d_value.isIntegral(); -} - -bool InferBoundsResult::boundIsRational() const { - return foundBound() && d_value.infinitesimalIsZero(); -} - -Integer InferBoundsResult::valueAsInteger() const{ - Assert(boundIsInteger()); - return getValue().floor(); -} -const Rational& InferBoundsResult::valueAsRational() const{ - Assert(boundIsRational()); - return getValue().getNoninfinitesimalPart(); -} - -const DeltaRational& InferBoundsResult::getValue() const{ - return d_value; -} - -Node InferBoundsResult::getTerm() const { return d_term; } - -Node InferBoundsResult::getLiteral() const{ - const Rational& q = getValue().getNoninfinitesimalPart(); - NodeManager* nm = NodeManager::currentNM(); - Node qnode = nm->mkConst(CONST_RATIONAL, q); - - Kind k; - if(d_upperBound){ - // x <= q + c*delta - Assert(getValue().infinitesimalSgn() <= 0); - k = boundIsRational() ? kind::LEQ : kind::LT; - }else{ - // x >= q + c*delta - Assert(getValue().infinitesimalSgn() >= 0); - k = boundIsRational() ? kind::GEQ : kind::GT; - } - return nm->mkNode(k, getTerm(), qnode); -} - -/* If there is a bound, this is a node that explains the bound. */ -Node InferBoundsResult::getExplanation() const{ - return d_explanation; -} - - -void InferBoundsResult::setBound(const DeltaRational& dr, Node exp){ - d_foundBound = true; - d_value = dr; - d_explanation = exp; -} - -void InferBoundsResult::setBudgetExhausted() { d_budgetExhausted = true; } -void InferBoundsResult::setReachedThreshold() { d_reachedThreshold = true; } -void InferBoundsResult::setIsOptimal() { d_boundIsProvenOpt = true; } -void InferBoundsResult::setInconsistent() { d_inconsistentState = true; } - -bool InferBoundsResult::thresholdWasReached() const{ - return d_reachedThreshold; -} -bool InferBoundsResult::budgetIsExhausted() const{ - return d_budgetExhausted; -} - -std::ostream& operator<<(std::ostream& os, const InferBoundsResult& ibr){ - os << "{InferBoundsResult " << std::endl; - os << "on " << ibr.getTerm() << ", "; - if(ibr.findUpperBound()){ - os << "find upper bound, "; - }else{ - os << "find lower bound, "; - } - if(ibr.foundBound()){ - os << "found a bound: "; - if(ibr.boundIsInteger()){ - os << ibr.valueAsInteger() << "(int), "; - }else if(ibr.boundIsRational()){ - os << ibr.valueAsRational() << "(rat), "; - }else{ - os << ibr.getValue() << "(extended), "; - } - - os << "as term " << ibr.getLiteral() << ", "; - os << "explanation " << ibr.getExplanation() << ", "; - }else { - os << "did not find a bound, "; - } - - if(ibr.boundIsOptimal()){ - os << "(opt), "; - } - - if(ibr.inconsistentState()){ - os << "(inconsistent), "; - } - if(ibr.budgetIsExhausted()){ - os << "(budget exhausted), "; - } - if(ibr.thresholdWasReached()){ - os << "(reached threshold), "; - } - os << "}"; - return os; -} - -ArithEntailmentCheckSideEffects::ArithEntailmentCheckSideEffects() - : d_simplexSideEffects(NULL) -{} - -ArithEntailmentCheckSideEffects::~ArithEntailmentCheckSideEffects(){ - if(d_simplexSideEffects != NULL){ - delete d_simplexSideEffects; - d_simplexSideEffects = NULL; - } -} - -InferBoundsResult& ArithEntailmentCheckSideEffects::getSimplexSideEffects(){ - if(d_simplexSideEffects == NULL){ - d_simplexSideEffects = new InferBoundsResult; - } - return *d_simplexSideEffects; -} - -namespace inferbounds { /* namespace arith */ - -std::ostream& operator<<(std::ostream& os, const Algorithms a){ - switch(a){ - case None: os << "AlgNone"; break; - case Lookup: os << "AlgLookup"; break; - case RowSum: os << "AlgRowSum"; break; - case Simplex: os << "AlgSimplex"; break; - default: - Unhandled(); - } - - return os; -} - -} /* namespace inferbounds */ - -} /* namespace arith */ -} /* namespace theory */ -} // namespace cvc5::internal diff --git a/src/theory/arith/infer_bounds.h b/src/theory/arith/infer_bounds.h deleted file mode 100644 index cf9d71a77..000000000 --- a/src/theory/arith/infer_bounds.h +++ /dev/null @@ -1,164 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andrew Reynolds, Andres Noetzli - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include -#include - -#include "expr/node.h" -#include "theory/arith/delta_rational.h" -#include "util/integer.h" -#include "util/rational.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -namespace inferbounds { - enum Algorithms {None = 0, Lookup, RowSum, Simplex}; - enum SimplexParamKind { Unbounded, NumVars, Direct}; - -class InferBoundAlgorithm { -private: - Algorithms d_alg; - std::optional d_simplexRounds; - InferBoundAlgorithm(Algorithms a); - InferBoundAlgorithm(const std::optional& simplexRounds); - - public: - InferBoundAlgorithm(); - - Algorithms getAlgorithm() const; - const std::optional& getSimplexRounds() const; - - static InferBoundAlgorithm mkLookup(); - static InferBoundAlgorithm mkRowSum(); - static InferBoundAlgorithm mkSimplex(const std::optional& rounds); -}; - -std::ostream& operator<<(std::ostream& os, const Algorithms a); -} /* namespace inferbounds */ - -class ArithEntailmentCheckParameters -{ - private: - typedef std::vector VecInferBoundAlg; - VecInferBoundAlg d_algorithms; - -public: - typedef VecInferBoundAlg::const_iterator const_iterator; - - ArithEntailmentCheckParameters(); - ~ArithEntailmentCheckParameters(); - - void addLookupRowSumAlgorithms(); - void addAlgorithm(const inferbounds::InferBoundAlgorithm& alg); - - const_iterator begin() const; - const_iterator end() const; -}; - - - -class InferBoundsResult { -public: - InferBoundsResult(); - InferBoundsResult(Node term, bool ub); - - void setBound(const DeltaRational& dr, Node exp); - bool foundBound() const; - - void setIsOptimal(); - bool boundIsOptimal() const; - - void setInconsistent(); - bool inconsistentState() const; - - const DeltaRational& getValue() const; - bool boundIsRational() const; - const Rational& valueAsRational() const; - bool boundIsInteger() const; - Integer valueAsInteger() const; - - Node getTerm() const; - Node getLiteral() const; - void setTerm(Node t){ d_term = t; } - - /* If there is a bound, this is a node that explains the bound. */ - Node getExplanation() const; - - bool budgetIsExhausted() const; - void setBudgetExhausted(); - - bool thresholdWasReached() const; - void setReachedThreshold(); - - bool findUpperBound() const { return d_upperBound; } - - void setFindLowerBound() { d_upperBound = false; } - void setFindUpperBound() { d_upperBound = true; } -private: - /* was a bound found */ - bool d_foundBound; - - /* was the budget exhausted */ - bool d_budgetExhausted; - - /* does the bound have to be optimal*/ - bool d_boundIsProvenOpt; - - /* was this started on an inconsistent state. */ - bool d_inconsistentState; - - /* reached the threshold. */ - bool d_reachedThreshold; - - /* the value of the bound */ - DeltaRational d_value; - - /* The input term. */ - Node d_term; - - /* Was the bound found an upper or lower bound.*/ - bool d_upperBound; - - /* Explanation of the bound. */ - Node d_explanation; -}; - -std::ostream& operator<<(std::ostream& os, const InferBoundsResult& ibr); - -class ArithEntailmentCheckSideEffects -{ - public: - ArithEntailmentCheckSideEffects(); - ~ArithEntailmentCheckSideEffects(); - - InferBoundsResult& getSimplexSideEffects(); - -private: - InferBoundsResult* d_simplexSideEffects; -}; - - -} /* namespace arith */ -} /* namespace theory */ -} // namespace cvc5::internal diff --git a/src/theory/arith/linear/approx_simplex.cpp b/src/theory/arith/linear/approx_simplex.cpp new file mode 100644 index 000000000..81c6183f1 --- /dev/null +++ b/src/theory/arith/linear/approx_simplex.cpp @@ -0,0 +1,3083 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ +#include "theory/arith/linear/approx_simplex.h" + +#include + +#include +#include +#include + +#include "base/cvc5config.h" +#include "base/output.h" +#include "proof/eager_proof_generator.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/cut_log.h" +#include "theory/arith/linear/matrix.h" +#include "theory/arith/linear/normal_form.h" + +#ifdef CVC5_USE_GLPK +#include "theory/arith/linear/partial_model.h" +#endif + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +struct AuxInfo { + TreeLog* tl; + int pivotLimit; + int branchLimit; + int branchDepth; + MipResult term; /* terminatation */ +}; + +enum SlackReplace { SlackUndef=0, SlackLB, SlackUB, SlackVLB, SlackVUB }; + +std::ostream& operator<<(std::ostream& out, MipResult res){ + switch(res){ + case MipUnknown: + out << "MipUnknown"; break; + case MipBingo: + out << "MipBingo"; break; + case MipClosed: + out << "MipClosed"; break; + case BranchesExhausted: + out << "BranchesExhausted"; break; + case PivotsExhauasted: + out << "PivotsExhauasted"; break; + case ExecExhausted: + out << "ExecExhausted"; break; + default: + out << "Unexpected Mip Value!"; break; + } + return out; +} +struct VirtualBound { + // Either x <= d * y or x >= d * y + ArithVar x; // variable being bounded + Kind k; // either LEQ or GEQ + Rational d; // the multiple on y + ArithVar y; // the variable that is the upper bound + ConstraintP c; // the original constraint relating x and y + + VirtualBound() + : x(ARITHVAR_SENTINEL) + , k(kind::UNDEFINED_KIND) + , d() + , y(ARITHVAR_SENTINEL) + , c(NullConstraint) + {} + VirtualBound(ArithVar toBound, Kind rel, const Rational& coeff, ArithVar bounding, ConstraintP orig) + : x(toBound) + , k(rel) + , d(coeff) + , y(bounding) + , c(orig) + { + Assert(k == kind::LEQ || k == kind::GEQ); + } +}; + +struct CutScratchPad { + bool d_failure; // if the construction was unsuccessful + + /* GOMORY CUTS Datastructures */ + ArithVar d_basic; // a variable that is basic in the approximate solver + DenseVector d_tabRow; // a row in the tableau not including d_basic, equal to 0 + DenseMap d_toBound; // each variable in toBound maps each variable in tabRow to either an upper/lower bound + + /* MIR CUTS Datastructures */ + DenseMap d_slacks;// The x'[i] selected for x[i] + DenseMap d_vub; // Virtual upper bounds. + DenseMap d_vlb; // Virtual lower bounds. + DenseMap d_compRanges; + + // a sum of rows in the tableau, with possible replacements for fixed + // sum aggLhs[i] x[i] = aggRhs; + DenseVector d_agg; + // Takes agg and replaces x[i] with a slack variable x'[i] + // Takes agg and replaces x[i] with a slack variable x'[i] + // sum modLhs[i] x'[i] = modRhs; + DenseVector d_mod; + + // Takes mod, and performs c-Mir on it + // sum alpha[i] x'[i] <= beta + DenseVector d_alpha; + + /* The constructed cut */ + // sum cut[i] x[i] <= cutRhs + DenseVector d_cut; + Kind d_cutKind; + + /* The constraints used throughout construction. */ + std::set d_explanation; // use pointer equality + CutScratchPad(){ + clear(); + } + void clear(){ + d_failure = false; + d_basic = ARITHVAR_SENTINEL; + d_tabRow.purge(); + d_toBound.purge(); + + d_slacks.purge(); + d_vub.purge(); + d_vlb.purge(); + d_compRanges.purge(); + + d_agg.purge(); + d_mod.purge(); + d_alpha.purge(); + + d_cut.purge(); + d_cutKind = kind::UNDEFINED_KIND; + d_explanation.clear(); + } +}; + +ApproximateStatistics::ApproximateStatistics() + : d_branchMaxDepth( + smtStatisticsRegistry().registerInt("z::approx::branchMaxDepth")), + d_branchesMaxOnAVar( + smtStatisticsRegistry().registerInt("z::approx::branchesMaxOnAVar")), + d_gaussianElimConstructTime(smtStatisticsRegistry().registerTimer( + "z::approx::gaussianElimConstruct::time")), + d_gaussianElimConstruct(smtStatisticsRegistry().registerInt( + "z::approx::gaussianElimConstruct::calls")), + d_averageGuesses( + smtStatisticsRegistry().registerAverage("z::approx::averageGuesses")) +{ +} + +ApproximateSimplex::ApproximateSimplex(const ArithVariables& v, TreeLog& l, + ApproximateStatistics& s) + : d_vars(v) + , d_log(l) + , d_stats(s) + , d_pivotLimit(std::numeric_limits::max()) + , d_branchLimit(std::numeric_limits::max()) + , d_maxDepth(std::numeric_limits::max()) +{} + +void ApproximateSimplex::setPivotLimit(int pl){ + Assert(pl >= 0); + d_pivotLimit = pl; +} + +void ApproximateSimplex::setBranchingDepth(int bd){ + Assert(bd >= 0); + d_maxDepth = bd; +} + +void ApproximateSimplex::setBranchOnVariableLimit(int bl){ + Assert(bl >= 0); + d_branchLimit = bl; +} + +bool ApproximateSimplex::roughlyEqual(double a, double b){ + if (a == 0){ + return -SMALL_FIXED_DELTA <= b && b <= SMALL_FIXED_DELTA; + }else if (b == 0){ + return -SMALL_FIXED_DELTA <= a && a <= SMALL_FIXED_DELTA; + }else{ + return std::abs(b/a) <= TOLERENCE && std::abs(a/b) <= TOLERENCE; + } +} + +Rational ApproximateSimplex::cfeToRational(const vector& exp){ + if(exp.empty()){ + return Rational(0); + }else{ + Rational result = exp.back(); + vector::const_reverse_iterator exp_iter = exp.rbegin(); + vector::const_reverse_iterator exp_end = exp.rend(); + ++exp_iter; + while(exp_iter != exp_end){ + result = result.inverse(); + const Integer& i = *exp_iter; + result += i; + ++exp_iter; + } + return result; + } +} +std::vector ApproximateSimplex::rationalToCfe(const Rational& q, int depth){ + vector mods; + if(!q.isZero()){ + Rational carry = q; + for(int i = 0; i <= depth; ++i){ + Assert(!carry.isZero()); + mods.push_back(Integer()); + Integer& back = mods.back(); + back = carry.floor(); + Trace("rationalToCfe") << " cfe["<= Integer(1)); + if( r.getDenominator() <= K ){ + return r; + } + + // current numerator and denominator that has not been resolved in the cfe + Integer num = r.getNumerator(), den = r.getDenominator(); + Integer quot,rem; + + unsigned t = 0; + // For a sequence of candidate solutions q_t/p_t + // we keep only 3 time steps: 0[prev], 1[current], 2[next] + // timesteps with a fake timestep 0 (p is 0 and q is 1) + // at timestep 1 + Integer p[3]; // h + Integer q[3]; // k + // load the first 3 time steps manually + p[0] = 0; q[0] = 1; // timestep -2 + p[1] = 1; q[1] = 0; // timestep -1 + + Integer::floorQR(quot, rem, num, den); + num = den; den = rem; + + q[2] = q[0] + quot*q[1]; + p[2] = p[0] + quot*p[1]; + Trace("estimateWithCFE") << " cfe["< ApproximateSimplex::estimateWithCFE(double d, + const Integer& D) +{ + if (std::optional from_double = Rational::fromDouble(d)) + { + return estimateWithCFE(*from_double, D); + } + return std::optional(); +} + +std::optional ApproximateSimplex::estimateWithCFE(double d) +{ + return estimateWithCFE(d, Integer(s_defaultMaxDenom)); +} + +class ApproxNoOp : public ApproximateSimplex { +public: + ApproxNoOp(const ArithVariables& v, TreeLog& l, ApproximateStatistics& s) + : ApproximateSimplex(v,l,s) + {} + ~ApproxNoOp(){} + + LinResult solveRelaxation() override { return LinUnknown; } + Solution extractRelaxation() const override { return Solution(); } + + ArithRatPairVec heuristicOptCoeffs() const override + { + return ArithRatPairVec(); + } + + MipResult solveMIP(bool al) override { return MipUnknown; } + Solution extractMIP() const override { return Solution(); } + + void setOptCoeffs(const ArithRatPairVec& ref) override {} + + void tryCut(int nid, CutInfo& cut) override {} + + std::vector getValidCuts(const NodeLog& node) override + { + return std::vector(); + } + + ArithVar getBranchVar(const NodeLog& nl) const override + { + return ARITHVAR_SENTINEL; + } + + double sumInfeasibilities(bool mip) const override { return 0.0; } +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +/* Begin the declaration of GLPK specific code. */ +#ifdef CVC5_USE_GLPK +extern "C" { +#include +}/* extern "C" */ + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +Kind glpk_type_to_kind(int glpk_cut_type){ + switch(glpk_cut_type){ + case GLP_LO: return kind::GEQ; + case GLP_UP: return kind::LEQ; + case GLP_FX: return kind::EQUAL; + case GLP_DB: + case GLP_FR: + default: return kind::UNDEFINED_KIND; + } +} + +class GmiInfo; +class MirInfo; +class BranchCutInfo; + +class ApproxGLPK : public ApproximateSimplex { +private: + glp_prob* d_inputProb; /* a copy of the input prob */ + glp_prob* d_realProb; /* a copy of the real relaxation output */ + glp_prob* d_mipProb; /* a copy of the integer prob */ + + DenseMap d_colIndices; + DenseMap d_rowIndices; + + NodeLog::RowIdMap d_rootRowIds; + //DenseMap d_rowToArithVar; + DenseMap d_colToArithVar; + + bool d_solvedRelaxation; + bool d_solvedMIP; + + CutScratchPad d_pad; + + std::vector d_denomGuesses; + +public: + ApproxGLPK(const ArithVariables& v, TreeLog& l, ApproximateStatistics& s); + ~ApproxGLPK(); + + LinResult solveRelaxation() override; + Solution extractRelaxation() const override { return extractSolution(false); } + + ArithRatPairVec heuristicOptCoeffs() const override; + + MipResult solveMIP(bool al) override; + Solution extractMIP() const override { return extractSolution(true); } + void setOptCoeffs(const ArithRatPairVec& ref) override; + std::vector getValidCuts(const NodeLog& nodes) override; + ArithVar getBranchVar(const NodeLog& con) const override; + + static void printGLPKStatus(int status, std::ostream& out); + + +private: + Solution extractSolution(bool mip) const; + int guessDir(ArithVar v) const; + + // get this stuff out of here + void tryCut(int nid, CutInfo& cut) override; + + ArithVar _getArithVar(int nid, int M, int ind) const; + ArithVar getArithVarFromRow(int nid, int ind) const + { + if (ind >= 0) + { + const NodeLog& nl = d_log.getNode(nid); + return nl.lookupRowId(ind); + } + return ARITHVAR_SENTINEL; + } + + // virtual void mapRowId(int nid, int ind, ArithVar v){ + // NodeLog& nl = d_log.getNode(nid); + // nl.mapRowId(ind, v); + // } + // virtual void applyRowsDeleted(int nid, const RowsDeleted& rd){ + // NodeLog& nl = d_log.getNode(nid); + // nl.applyRowsDeleted(rd); + // } + + ArithVar getArithVarFromStructural(int ind) const{ + if(ind >= 0){ + unsigned u = (unsigned) ind; + if(d_colToArithVar.isKey(u)){ + return d_colToArithVar[u]; + } + } + return ARITHVAR_SENTINEL; + } + + /** + * Attempts to make the row vector vec on the pad. + * If this is not in the row span of the original tableau this + * raises the failure flag. + */ + bool attemptConstructTableRow(int node, int M, const PrimitiveVec& vec); + bool guessCoefficientsConstructTableRow(int node, int M, const PrimitiveVec& vec); + bool guessCoefficientsConstructTableRow(int node, int M, const PrimitiveVec& vec, const Integer& D); + bool gaussianElimConstructTableRow(int node, int M, const PrimitiveVec& vec); + + /* This is a guess of a vector in the row span of the tableau. + * Attempt to cancel out all of the variables. + * returns true if this is constructable. + */ + bool guessIsConstructable(const DenseMap& guess) const; + + /** + * Loads a vector of statuses into a dense map over bounds. + * returns true on failure. + */ + bool loadToBound(int node, int M, int len, int* inds, int* statuses, + DenseMap& toBound) const; + + /** checks the cut on the pad for whether it is sufficiently similar to cut. */ + bool checkCutOnPad(int nid, const CutInfo& cut) const; + + + /** turns the pad into a node and creates an explanation. */ + //std::pair makeCutNodes(int nid, const CutInfo& cut) const; + + // true means failure! + // BRANCH CUTS + bool attemptBranchCut(int nid, const BranchCutInfo& br); + + // GOMORY CUTS + bool attemptGmi(int nid, const GmiInfo& gmi); + /** tries to turn the information on the pad into a cut. */ + bool constructGmiCut(); + + // MIR CUTS + bool attemptMir(int nid, const MirInfo& mir); + bool applyCMIRRule(int nid, const MirInfo& mir); + bool makeRangeForComplemented(int nid, const MirInfo& mir); + bool loadSlacksIntoPad(int nid, const MirInfo& mir); + bool loadVirtualBoundsIntoPad(int nid, const MirInfo& mir); + bool loadRowSumIntoAgg(int nid, int M, const PrimitiveVec& mir); + bool buildModifiedRow(int nid, const MirInfo& mir); + bool constructMixedKnapsack(); + bool replaceSlacksOnCuts(); + bool loadVB(int nid, int M, int j, int ri, bool wantUb, VirtualBound& tmp); + + double sumInfeasibilities(bool mip) const override + { + return sumInfeasibilities(mip? d_mipProb : d_realProb); + } + double sumInfeasibilities(glp_prob* prob, bool mip) const; +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal +#endif /*#ifdef CVC5_USE_GLPK */ +/* End the declaration of GLPK specific code. */ + +/* Begin GPLK/NOGLPK Glue code. */ +namespace cvc5::internal { +namespace theory { +namespace arith::linear { +ApproximateSimplex* ApproximateSimplex::mkApproximateSimplexSolver(const ArithVariables& vars, TreeLog& l, ApproximateStatistics& s){ +#ifdef CVC5_USE_GLPK + return new ApproxGLPK(vars, l, s); +#else + return new ApproxNoOp(vars, l, s); +#endif +} +bool ApproximateSimplex::enabled() { +#ifdef CVC5_USE_GLPK + return true; +#else + return false; +#endif +} +} // namespace arith +} // namespace theory +} // namespace cvc5::internal +/* End GPLK/NOGLPK Glue code. */ + +/* Begin GPLK implementation. */ +#ifdef CVC5_USE_GLPK +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +#ifdef CVC5_ASSERTIONS +static CutInfoKlass fromGlpkClass(int klass){ + switch(klass){ + case GLP_RF_GMI: return GmiCutKlass; + case GLP_RF_MIR: return MirCutKlass; + case GLP_RF_COV: + case GLP_RF_CLQ: + default: return UnknownKlass; + } +} +#endif + +ApproxGLPK::ApproxGLPK(const ArithVariables& var, + TreeLog& l, + ApproximateStatistics& s) + : ApproximateSimplex(var, l, s), + d_inputProb(nullptr), + d_realProb(nullptr), + d_mipProb(nullptr), + d_solvedRelaxation(false), + d_solvedMIP(false) +{ + + d_denomGuesses.push_back(Integer(1<<22)); + d_denomGuesses.push_back(Integer(ApproximateSimplex::s_defaultMaxDenom)); + d_denomGuesses.push_back(Integer(1ul<<29)); + d_denomGuesses.push_back(Integer(1ul<<31)); + + d_inputProb = glp_create_prob(); + d_realProb = glp_create_prob(); + d_mipProb = glp_create_prob(); + glp_set_obj_dir(d_inputProb, GLP_MAX); + glp_set_prob_name(d_inputProb, "ApproximateSimplex::approximateFindModel"); + + int numRows = 0; + int numCols = 0; + + // Assign each variable to a row and column variable as it appears in the input + for(ArithVariables::var_iterator vi = d_vars.var_begin(), vi_end = d_vars.var_end(); vi != vi_end; ++vi){ + ArithVar v = *vi; + + if(d_vars.isAuxiliary(v)){ + ++numRows; + d_rowIndices.set(v, numRows); + //mapRowId(d_log.getRootId(), numRows, v); + d_rootRowIds.insert(make_pair(numRows, v)); + //d_rowToArithVar.set(numRows, v); + Trace("approx") << "Row vars: " << v << "<->" << numRows << endl; + }else{ + ++numCols; + d_colIndices.set(v, numCols); + d_colToArithVar.set(numCols, v); + Trace("approx") << "Col vars: " << v << "<->" << numCols << endl; + } + } + Assert(numRows > 0); + Assert(numCols > 0); + + glp_add_rows(d_inputProb, numRows); + glp_add_cols(d_inputProb, numCols); + + // Assign the upper/lower bounds and types to each variable + for(ArithVariables::var_iterator vi = d_vars.var_begin(), vi_end = d_vars.var_end(); vi != vi_end; ++vi){ + ArithVar v = *vi; + + int type; + double lb = 0.0; + double ub = 0.0; + if(d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ + if(d_vars.boundsAreEqual(v)){ + type = GLP_FX; + }else{ + type = GLP_DB; + } + lb = d_vars.getLowerBound(v).approx(SMALL_FIXED_DELTA); + ub = d_vars.getUpperBound(v).approx(SMALL_FIXED_DELTA); + }else if(d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ + type = GLP_UP; + ub = d_vars.getUpperBound(v).approx(SMALL_FIXED_DELTA); + }else if(!d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ + type = GLP_LO; + lb = d_vars.getLowerBound(v).approx(SMALL_FIXED_DELTA); + }else{ + type = GLP_FR; + } + + if(d_vars.isAuxiliary(v)){ + int rowIndex = d_rowIndices[v]; + glp_set_row_bnds(d_inputProb, rowIndex, type, lb, ub); + }else{ + int colIndex = d_colIndices[v]; + // is input is correct here + int kind = d_vars.isInteger(v) ? GLP_IV : GLP_CV; + glp_set_col_kind(d_inputProb, colIndex, kind); + glp_set_col_bnds(d_inputProb, colIndex, type, lb, ub); + } + } + + // Count the number of entries + int numEntries = 0; + for(DenseMap::const_iterator i = d_rowIndices.begin(), i_end = d_rowIndices.end(); i != i_end; ++i){ + ArithVar v = *i; + Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); + for (Polynomial::iterator j = p.begin(), end = p.end(); j != end; ++j) + { + ++numEntries; + } + } + + int* ia = new int[numEntries+1]; + int* ja = new int[numEntries+1]; + double* ar = new double[numEntries+1]; + + int entryCounter = 0; + for(DenseMap::const_iterator i = d_rowIndices.begin(), i_end = d_rowIndices.end(); i != i_end; ++i){ + ArithVar v = *i; + int rowIndex = d_rowIndices[v]; + + Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); + + for (Polynomial::iterator j = p.begin(), end = p.end(); j != end; ++j) + { + const Monomial& mono = *j; + const Constant& constant = mono.getConstant(); + const VarList& variable = mono.getVarList(); + + Node n = variable.getNode(); + + Assert(d_vars.hasArithVar(n)); + ArithVar av = d_vars.asArithVar(n); + int colIndex = d_colIndices[av]; + double coeff = constant.getValue().getDouble(); + + ++entryCounter; + ia[entryCounter] = rowIndex; + ja[entryCounter] = colIndex; + ar[entryCounter] = coeff; + } + } + glp_load_matrix(d_inputProb, numEntries, ia, ja, ar); + + delete[] ia; + delete[] ja; + delete[] ar; +} +int ApproxGLPK::guessDir(ArithVar v) const{ + if(d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ + return -1; + }else if(!d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ + return 1; + }else if(!d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ + return 0; + }else{ + int ubSgn = d_vars.getUpperBound(v).sgn(); + int lbSgn = d_vars.getLowerBound(v).sgn(); + + if(ubSgn != 0 && lbSgn == 0){ + return -1; + }else if(ubSgn == 0 && lbSgn != 0){ + return 1; + } + + return 1; + } +} + +ArithRatPairVec ApproxGLPK::heuristicOptCoeffs() const{ + ArithRatPairVec ret; + + // Strategies are guess: + // 1 simple shared "ceiling" variable: danoint, pk1 + // x1 >= c, x1 >= tmp1, x1 >= tmp2, ... + // 1 large row: fixnet, vpm2, pp08a + // (+ .......... ) <= c + // Not yet supported: + // 1 complex shared "ceiling" variable: opt1217 + // x1 >= c, x1 >= (+ ..... ), x1 >= (+ ..... ) + // and all of the ... are the same sign + + + // Candidates: + // 1) Upper and lower bounds are not equal. + // 2) The variable is not integer + // 3a) For columns look for a ceiling variable + // 3B) For rows look for a large row with + + DenseMap d_colCandidates; + DenseMap d_rowCandidates; + + double sumRowLength = 0.0; + uint32_t maxRowLength = 0; + for(ArithVariables::var_iterator vi = d_vars.var_begin(), vi_end = d_vars.var_end(); vi != vi_end; ++vi){ + ArithVar v = *vi; + + int type; + if(d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ + if(d_vars.boundsAreEqual(v)){ + type = GLP_FX; + }else{ + type = GLP_DB; + } + }else if(d_vars.hasUpperBound(v) && !d_vars.hasLowerBound(v)){ + type = GLP_UP; + }else if(!d_vars.hasUpperBound(v) && d_vars.hasLowerBound(v)){ + type = GLP_LO; + }else{ + type = GLP_FR; + } + + if(type != GLP_FX && type != GLP_FR){ + + if(d_vars.isAuxiliary(v)){ + Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); + uint32_t len = p.size(); + d_rowCandidates.set(v, len); + sumRowLength += len; + maxRowLength = std::max(maxRowLength, len); + }else if(!d_vars.isInteger(v)){ + d_colCandidates.set(v, BoundCounts()); + } + } + } + + uint32_t maxCount = 0; + for(DenseMap::const_iterator i = d_rowIndices.begin(), i_end = d_rowIndices.end(); i != i_end; ++i){ + ArithVar v = *i; + + bool lbCap = d_vars.hasLowerBound(v) && !d_vars.hasUpperBound(v); + bool ubCap = !d_vars.hasLowerBound(v) && d_vars.hasUpperBound(v); + + if(lbCap || ubCap){ + ConstraintP b = lbCap ? d_vars.getLowerBoundConstraint(v) + : d_vars.getUpperBoundConstraint(v); + + if(!(b->getValue()).noninfinitesimalIsZero()){ continue; } + + Polynomial poly = Polynomial::parsePolynomial(d_vars.asNode(v)); + if(poly.size() != 2) { continue; } + + Polynomial::iterator j = poly.begin(); + Monomial first = *j; + ++j; + Monomial second = *j; + + bool firstIsPos = first.constantIsPositive(); + bool secondIsPos = second.constantIsPositive(); + + if(firstIsPos == secondIsPos){ continue; } + + Monomial pos = firstIsPos == lbCap ? first : second; + Monomial neg = firstIsPos != lbCap ? first : second; + // pos >= neg + VarList p = pos.getVarList(); + VarList n = neg.getVarList(); + if(d_vars.hasArithVar(p.getNode())){ + ArithVar ap = d_vars.asArithVar(p.getNode()); + if( d_colCandidates.isKey(ap)){ + BoundCounts bc = d_colCandidates.get(ap); + bc = BoundCounts(bc.lowerBoundCount(), bc.upperBoundCount()+1); + maxCount = std::max(maxCount, bc.upperBoundCount()); + d_colCandidates.set(ap, bc); + } + } + if(d_vars.hasArithVar(n.getNode())){ + ArithVar an = d_vars.asArithVar(n.getNode()); + if( d_colCandidates.isKey(an)){ + BoundCounts bc = d_colCandidates.get(an); + bc = BoundCounts(bc.lowerBoundCount()+1, bc.upperBoundCount()); + maxCount = std::max(maxCount, bc.lowerBoundCount()); + d_colCandidates.set(an, bc); + } + } + } + } + + // Attempt row + double avgRowLength = d_rowCandidates.size() >= 1 ? + ( sumRowLength / d_rowCandidates.size() ) : 0.0; + + // There is a large row among the candidates + bool guessARowCandidate = maxRowLength >= (10.0 * avgRowLength); + + double rowLengthReq = (maxRowLength * .9); + + if (guessARowCandidate) + { + for (ArithVar r : d_rowCandidates) + { + uint32_t len = d_rowCandidates[r]; + + int dir = guessDir(r); + if(len >= rowLengthReq){ + ret.push_back(ArithRatPair(r, Rational(dir))); + } + } + } + + // Attempt columns + bool guessAColCandidate = maxCount >= 4; + if (guessAColCandidate) + { + for (ArithVar c : d_colCandidates) + { + BoundCounts bc = d_colCandidates[c]; + + int dir = guessDir(c); + double ubScore = double(bc.upperBoundCount()) / maxCount; + double lbScore = double(bc.lowerBoundCount()) / maxCount; + if(ubScore >= .9 || lbScore >= .9){ + ret.push_back(ArithRatPair(c, Rational(c))); + } + } + } + + return ret; +} + +void ApproxGLPK::setOptCoeffs(const ArithRatPairVec& ref){ + DenseMap nbCoeffs; + + for(ArithRatPairVec::const_iterator i = ref.begin(), iend = ref.end(); i != iend; ++i){ + ArithVar v = (*i).first; + const Rational& q = (*i).second; + + if(d_vars.isAuxiliary(v)){ + // replace the variable by its definition and multiply by q + Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(v)); + Polynomial pq = p * q; + + for(Polynomial::iterator j = pq.begin(), jend = pq.end(); j != jend; ++j){ + const Monomial& mono = *j; + const Constant& constant = mono.getConstant(); + const VarList& variable = mono.getVarList(); + + Node n = variable.getNode(); + + Assert(d_vars.hasArithVar(n)); + ArithVar av = d_vars.asArithVar(n); + int colIndex = d_colIndices[av]; + double coeff = constant.getValue().getDouble(); + if(!nbCoeffs.isKey(colIndex)){ + nbCoeffs.set(colIndex, 0.0); + } + nbCoeffs.set(colIndex, nbCoeffs[colIndex]+coeff); + } + }else{ + int colIndex = d_colIndices[v]; + double coeff = q.getDouble(); + if(!nbCoeffs.isKey(colIndex)){ + nbCoeffs.set(colIndex, 0.0); + } + nbCoeffs.set(colIndex, nbCoeffs[colIndex]+coeff); + } + } + for(DenseMap::const_iterator ci =nbCoeffs.begin(), ciend = nbCoeffs.end(); ci != ciend; ++ci){ + Index colIndex = *ci; + double coeff = nbCoeffs[colIndex]; + glp_set_obj_coef(d_inputProb, colIndex, coeff); + } +} + + +/* + * rough strategy: + * real relaxation + * try approximate real optimization of error function + * pivot in its basis + * update to its assignment + * check with FCSimplex + * check integer solution + * try approximate mixed integer problem + * stop at the first feasible point + * pivot in its basis + * update to its assignment + * check with FCSimplex + */ + +void ApproxGLPK::printGLPKStatus(int status, std::ostream& out){ + switch(status){ + case GLP_OPT: + out << "GLP_OPT" << endl; + break; + case GLP_FEAS: + out << "GLP_FEAS" << endl; + break; + case GLP_INFEAS: + out << "GLP_INFEAS" << endl; + break; + case GLP_NOFEAS: + out << "GLP_NOFEAS" << endl; + break; + case GLP_UNBND: + out << "GLP_UNBND" << endl; + break; + case GLP_UNDEF: + out << "GLP_UNDEF" << endl; + break; + default: + out << "Status unknown" << endl; + break; + } +} + +ApproxGLPK::~ApproxGLPK(){ + glp_delete_prob(d_inputProb); + glp_delete_prob(d_realProb); + glp_delete_prob(d_mipProb); + +} + +ApproximateSimplex::Solution ApproxGLPK::extractSolution(bool mip) const +{ + Assert(d_solvedRelaxation); + Assert(!mip || d_solvedMIP); + + ApproximateSimplex::Solution sol; + DenseSet& newBasis = sol.newBasis; + DenseMap& newValues = sol.newValues; + + glp_prob* prob = mip ? d_mipProb : d_realProb; + + for(ArithVariables::var_iterator i = d_vars.var_begin(), i_end = d_vars.var_end(); i != i_end; ++i){ + ArithVar vi = *i; + bool isAux = d_vars.isAuxiliary(vi); + int glpk_index = isAux ? d_rowIndices[vi] : d_colIndices[vi]; + + int status = isAux ? glp_get_row_stat(prob, glpk_index) + : glp_get_col_stat(prob, glpk_index); + + bool useDefaultAssignment = false; + + switch(status){ + case GLP_BS: + newBasis.add(vi); + useDefaultAssignment = true; + break; + case GLP_NL: + case GLP_NS: + if(!mip){ + newValues.set(vi, d_vars.getLowerBound(vi)); + }else{// intentionally fall through otherwise + useDefaultAssignment = true; + } + break; + case GLP_NU: + if(!mip){ + newValues.set(vi, d_vars.getUpperBound(vi)); + }else {// intentionally fall through otherwise + useDefaultAssignment = true; + } + break; + default: + { + useDefaultAssignment = true; + } + break; + } + + if(useDefaultAssignment){ + + double newAssign; + if(mip){ + newAssign = (isAux ? glp_mip_row_val(prob, glpk_index) + : glp_mip_col_val(prob, glpk_index)); + }else{ + newAssign = (isAux ? glp_get_row_prim(prob, glpk_index) + : glp_get_col_prim(prob, glpk_index)); + } + const DeltaRational& oldAssign = d_vars.getAssignment(vi); + + if (d_vars.hasLowerBound(vi) + && roughlyEqual(newAssign, + d_vars.getLowerBound(vi).approx(SMALL_FIXED_DELTA))) + { + + newValues.set(vi, d_vars.getLowerBound(vi)); + } + else if (d_vars.hasUpperBound(vi) + && roughlyEqual( + newAssign, + d_vars.getUpperBound(vi).approx(SMALL_FIXED_DELTA))) + { + newValues.set(vi, d_vars.getUpperBound(vi)); + } + else + { + double rounded = round(newAssign); + if (roughlyEqual(newAssign, rounded)) + { + newAssign = rounded; + } + + DeltaRational proposal; + if (std::optional maybe_new = estimateWithCFE(newAssign)) + { + proposal = *maybe_new; + } + else + { + // failed to estimate the old value. defaulting to the current. + proposal = d_vars.getAssignment(vi); + } + + if (roughlyEqual(newAssign, oldAssign.approx(SMALL_FIXED_DELTA))) + { + proposal = d_vars.getAssignment(vi); + } + + if (d_vars.strictlyLessThanLowerBound(vi, proposal)) + { + proposal = d_vars.getLowerBound(vi); + } + else if (d_vars.strictlyGreaterThanUpperBound(vi, proposal)) + { + proposal = d_vars.getUpperBound(vi); + } + newValues.set(vi, proposal); + } + } + } + return sol; +} + +double ApproxGLPK::sumInfeasibilities(glp_prob* prob, bool mip) const{ + /* compute the sum of dual infeasibilities */ + double infeas = 0.0; + + for(ArithVariables::var_iterator i = d_vars.var_begin(), i_end = d_vars.var_end(); i != i_end; ++i){ + ArithVar vi = *i; + bool isAux = d_vars.isAuxiliary(vi); + int glpk_index = isAux ? d_rowIndices[vi] : d_colIndices[vi]; + + double newAssign; + if(mip){ + newAssign = (isAux ? glp_mip_row_val(prob, glpk_index) + : glp_mip_col_val(prob, glpk_index)); + }else{ + newAssign = (isAux ? glp_get_row_prim(prob, glpk_index) + : glp_get_col_prim(prob, glpk_index)); + } + + + double ub = isAux ? + glp_get_row_ub(prob, glpk_index) : glp_get_col_ub(prob, glpk_index); + + double lb = isAux ? + glp_get_row_lb(prob, glpk_index) : glp_get_col_lb(prob, glpk_index); + + if(ub != +DBL_MAX){ + if(newAssign > ub){ + double ubinf = newAssign - ub; + infeas += ubinf; + Trace("approx::soi") << "ub inf" << vi << " " << ubinf << " " << infeas << endl; + } + } + if(lb != -DBL_MAX){ + if(newAssign < lb){ + double lbinf = lb - newAssign; + infeas += lbinf; + + Trace("approx::soi") << "lb inf" << vi << " " << lbinf << " " << infeas << endl; + } + } + } + return infeas; +} + +LinResult ApproxGLPK::solveRelaxation(){ + Assert(!d_solvedRelaxation); + + glp_smcp parm; + glp_init_smcp(&parm); + parm.presolve = GLP_OFF; + parm.meth = GLP_PRIMAL; + parm.pricing = GLP_PT_PSE; + parm.it_lim = d_pivotLimit; + parm.msg_lev = GLP_MSG_OFF; + + glp_erase_prob(d_realProb); + glp_copy_prob(d_realProb, d_inputProb, GLP_OFF); + + int res = glp_simplex(d_realProb, &parm); + switch(res){ + case 0: + { + int status = glp_get_status(d_realProb); + int iterationcount = glp_get_it_cnt(d_realProb); + switch(status){ + case GLP_OPT: + case GLP_FEAS: + case GLP_UNBND: + d_solvedRelaxation = true; + return LinFeasible; + case GLP_INFEAS: + case GLP_NOFEAS: + d_solvedRelaxation = true; + return LinInfeasible; + default: + { + if(iterationcount >= d_pivotLimit){ + return LinExhausted; + } + return LinUnknown; + } + } + } + default: + return LinUnknown; + } +} + + +struct MirInfo : public CutInfo { + + /** a sum of input rows. */ + PrimitiveVec row_sum; + + /* the delta used */ + double delta; + + /* all of these are length vars == N+M*/ + int nvars; + char* cset; + char* subst; + int* vlbRows; + int* vubRows; + MirInfo(int execOrd, int ord) + : CutInfo(MirCutKlass, execOrd, ord) + , nvars(0) + , cset(NULL) + , subst(NULL) + , vlbRows(NULL) + , vubRows(NULL) + {} + + ~MirInfo(){ + clearSets(); + } + void clearSets(){ + if(cset != NULL){ + delete[] cset; + delete[] subst; + delete[] vlbRows; + delete[] vubRows; + cset = NULL; + nvars = 0; + } + } + void initSet(){ + Assert(d_N >= 0); + Assert(d_mAtCreation >= 0); + clearSets(); + + int vars = 1 + d_N + d_mAtCreation; + + cset = new char[1+vars]; + subst = new char[1+vars]; + vlbRows = new int[1+vars]; + vubRows = new int[1+vars]; + } +}; + +struct GmiInfo : public CutInfo { + int basic; + PrimitiveVec tab_row; + int* tab_statuses; + /* has the length tab_row.length */ + + GmiInfo(int execOrd, int ord) + : CutInfo(GmiCutKlass, execOrd, ord) + , basic(-1) + , tab_row() + , tab_statuses(NULL) + { + Assert(!initialized_tab()); + } + + ~GmiInfo(){ + if(initialized_tab()){ + clear_tab(); + } + } + + bool initialized_tab() const { + return tab_statuses != NULL; + } + + void init_tab(int N){ + if(initialized_tab()){ + clear_tab(); + } + tab_row.setup(N); + tab_statuses = new int[1+N]; + } + + void clear_tab() { + delete[] tab_statuses; + tab_statuses = NULL; + tab_row.clear(); + basic = -1; + } +}; + + + +static void loadCut(glp_tree *tree, CutInfo* cut){ + int ord, cut_len, cut_klass; + int N, M; + int* cut_inds; + double* cut_coeffs; + int glpk_cut_type; + double cut_rhs; + glp_prob* lp; + + lp = glp_ios_get_prob(tree); + ord = cut->poolOrdinal(); + + N = glp_get_num_cols(lp); + M = glp_get_num_rows(lp); + + cut->setDimensions(N, M); + + + + // Get the cut + cut_len = glp_ios_get_cut(tree, ord, NULL, NULL, &cut_klass, NULL, NULL); + Assert(fromGlpkClass(cut_klass) == cut->getKlass()); + + PrimitiveVec& cut_vec = cut->getCutVector(); + cut_vec.setup(cut_len); + cut_inds = cut_vec.inds; + cut_coeffs = cut_vec.coeffs; + + cut_vec.len = glp_ios_get_cut(tree, ord, cut_inds, cut_coeffs, &cut_klass, &glpk_cut_type, &cut_rhs); + Assert(fromGlpkClass(cut_klass) == cut->getKlass()); + Assert(cut_vec.len == cut_len); + + cut->setRhs(cut_rhs); + + cut->setKind( glpk_type_to_kind(glpk_cut_type) ); +} + + +static MirInfo* mirCut(glp_tree *tree, int exec_ord, int cut_ord){ + Trace("approx::mirCut") << "mirCut()" << exec_ord << endl; + + MirInfo* mir; + mir = new MirInfo(exec_ord, cut_ord); + loadCut(tree, mir); + mir->initSet(); + + + int nrows = glp_ios_cut_get_aux_nrows(tree, cut_ord); + + PrimitiveVec& row_sum = mir->row_sum; + row_sum.setup(nrows); + glp_ios_cut_get_aux_rows(tree, cut_ord, row_sum.inds, row_sum.coeffs); + + glp_ios_cut_get_mir_cset(tree, cut_ord, mir->cset); + mir->delta = glp_ios_cut_get_mir_delta(tree, cut_ord); + glp_ios_cut_get_mir_subst(tree, cut_ord, mir->subst); + glp_ios_cut_get_mir_virtual_rows(tree, cut_ord, mir->vlbRows, mir->vubRows); + + if(TraceIsOn("approx::mirCut")){ + Trace("approx::mirCut") << "mir_id: " << exec_ord << endl; + row_sum.print(Trace("approx::mirCut")); + } + + return mir; +} + +static GmiInfo* gmiCut(glp_tree *tree, int exec_ord, int cut_ord){ + Trace("approx::gmiCut") << "gmiCut()" << exec_ord << endl; + + int gmi_var; + int write_pos; + int read_pos; + int stat; + int ind; + int i; + + GmiInfo* gmi; + glp_prob* lp; + + gmi = new GmiInfo(exec_ord, cut_ord); + loadCut(tree, gmi); + + lp = glp_ios_get_prob(tree); + + int N = gmi->getN(); + int M = gmi->getMAtCreation(); + + // Get the tableau row + int nrows CVC5_UNUSED = glp_ios_cut_get_aux_nrows(tree, gmi->poolOrdinal()); + Assert(nrows == 1); + int rows[1+1]; + glp_ios_cut_get_aux_rows(tree, gmi->poolOrdinal(), rows, NULL); + gmi_var = rows[1]; + + gmi->init_tab(N); + gmi->basic = M+gmi_var; + + Trace("approx::gmiCut") + << gmi <<" " << gmi->basic << " " + << cut_ord<<" " << M <<" " << gmi_var << endl; + + PrimitiveVec& tab_row = gmi->tab_row; + Trace("approx::gmiCut") << "Is N sufficient here?" << endl; + tab_row.len = glp_eval_tab_row(lp, gmi->basic, tab_row.inds, tab_row.coeffs); + + Trace("approx::gmiCut") << "gmi_var " << gmi_var << endl; + + Trace("approx::gmiCut") << "tab_pos " << tab_row.len << endl; + write_pos = 1; + for(read_pos = 1; read_pos <= tab_row.len; ++read_pos){ + if (fabs(tab_row.coeffs[read_pos]) < 1e-10){ + }else{ + tab_row.coeffs[write_pos] = tab_row.coeffs[read_pos]; + tab_row.inds[write_pos] = tab_row.inds[read_pos]; + ++write_pos; + } + } + tab_row.len = write_pos-1; + Trace("approx::gmiCut") << "write_pos " << write_pos << endl; + Assert(tab_row.len > 0); + + for(i = 1; i <= tab_row.len; ++i){ + ind = tab_row.inds[i]; + Trace("approx::gmiCut") << "ind " << i << " " << ind << endl; + stat = (ind <= M) ? + glp_get_row_stat(lp, ind) : glp_get_col_stat(lp, ind - M); + + Trace("approx::gmiCut") << "ind " << i << " " << ind << " stat " << stat << endl; + switch (stat){ + case GLP_NL: + case GLP_NU: + case GLP_NS: + gmi->tab_statuses[i] = stat; + break; + case GLP_NF: + default: + Unreachable(); + } + } + + if(TraceIsOn("approx::gmiCut")){ + gmi->print(Trace("approx::gmiCut")); + } + return gmi; +} + +static BranchCutInfo* branchCut(glp_tree *tree, int exec_ord, int br_var, double br_val, bool down_bad){ + //(tree, br_var, br_val, dn < 0); + double rhs; + Kind k; + if(down_bad){ + // down branch is infeasible + // x <= floor(v) is infeasible + // - so x >= ceiling(v) is implied + k = kind::GEQ; + rhs = std::ceil(br_val); + }else{ + // up branch is infeasible + // x >= ceiling(v) is infeasible + // - so x <= floor(v) is implied + k = kind::LEQ; + rhs = std::floor(br_val); + } + BranchCutInfo* br_cut = new BranchCutInfo(exec_ord, br_var, k, rhs); + return br_cut; +} + +static void glpkCallback(glp_tree *tree, void *info){ + AuxInfo* aux = (AuxInfo*)(info); + TreeLog& tl = *(aux->tl); + + int exec = tl.getExecutionOrd(); + int glpk_node_p = -1; + int node_ord = -1; + + if(tl.isActivelyLogging()){ + switch(glp_ios_reason(tree)){ + case GLP_LI_DELROW: + { + glpk_node_p = glp_ios_curr_node(tree); + node_ord = glp_ios_node_ord(tree, glpk_node_p); + + int nrows = glp_ios_rows_deleted(tree, NULL); + int* num = new int[1+nrows]; + glp_ios_rows_deleted(tree, num); + + NodeLog& node = tl.getNode(node_ord); + + RowsDeleted* rd = new RowsDeleted(exec, nrows, num); + + node.addCut(rd); + delete[] num; + } + break; + case GLP_ICUTADDED: + { + int cut_ord = glp_ios_pool_size(tree); + glpk_node_p = glp_ios_curr_node(tree); + node_ord = glp_ios_node_ord(tree, glpk_node_p); + Assert(cut_ord > 0); + Trace("approx") << "curr node " << glpk_node_p + << " cut ordinal " << cut_ord + << " node depth " << glp_ios_node_level(tree, glpk_node_p) + << endl; + int klass; + glp_ios_get_cut(tree, cut_ord, NULL, NULL, &klass, NULL, NULL); + + NodeLog& node = tl.getNode(node_ord); + switch(klass){ + case GLP_RF_GMI: + { + GmiInfo* gmi = gmiCut(tree, exec, cut_ord); + node.addCut(gmi); + } + break; + case GLP_RF_MIR: + { + MirInfo* mir = mirCut(tree, exec, cut_ord); + node.addCut(mir); + } + break; + case GLP_RF_COV: + Trace("approx") << "GLP_RF_COV" << endl; + break; + case GLP_RF_CLQ: + Trace("approx") << "GLP_RF_CLQ" << endl; + break; + default: + break; + } + } + break; + case GLP_ICUTSELECT: + { + glpk_node_p = glp_ios_curr_node(tree); + node_ord = glp_ios_node_ord(tree, glpk_node_p); + int cuts = glp_ios_pool_size(tree); + int* ords = new int[1+cuts]; + int* rows = new int[1+cuts]; + int N = glp_ios_selected_cuts(tree, ords, rows); + + NodeLog& nl = tl.getNode(node_ord); + Trace("approx") << glpk_node_p << " " << node_ord << " " << cuts << " " << N << std::endl; + for(int i = 1; i <= N; ++i){ + Trace("approx") << "adding to " << node_ord <<" @ i= " << i + << " ords[i] = " << ords[i] + << " rows[i] = " << rows[i] << endl; + nl.addSelected(ords[i], rows[i]); + } + delete[] ords; + delete[] rows; + nl.applySelected(); + } + break; + case GLP_LI_BRANCH: + { + // a branch was just made + int br_var; + int p, dn, up; + int p_ord, dn_ord, up_ord; + double br_val; + br_var = glp_ios_branch_log(tree, &br_val, &p, &dn, &up); + p_ord = glp_ios_node_ord(tree, p); + + dn_ord = (dn >= 0) ? glp_ios_node_ord(tree, dn) : -1; + up_ord = (up >= 0) ? glp_ios_node_ord(tree, up) : -1; + + Trace("approx::") << "branch: "<< br_var << " " << br_val << " tree " << p << " " << dn << " " << up << endl; + Trace("approx::") << "\t " << p_ord << " " << dn_ord << " " << up_ord << endl; + if(dn < 0 && up < 0){ + Trace("approx::") << "branch close " << exec << endl; + NodeLog& node = tl.getNode(p_ord); + BranchCutInfo* cut_br = branchCut(tree, exec, br_var, br_val, dn < 0); + node.addCut(cut_br); + tl.close(p_ord); + }else if(dn < 0 || up < 0){ + Trace("approx::") << "branch cut" << exec << endl; + NodeLog& node = tl.getNode(p_ord); + BranchCutInfo* cut_br = branchCut(tree, exec, br_var, br_val, dn < 0); + node.addCut(cut_br); + }else{ + Trace("approx::") << "normal branch" << endl; + tl.branch(p_ord, br_var, br_val, dn_ord, up_ord); + } + } + break; + case GLP_LI_CLOSE: + { + glpk_node_p = glp_ios_curr_node(tree); + node_ord = glp_ios_node_ord(tree, glpk_node_p); + Trace("approx::") << "close " << glpk_node_p << endl; + tl.close(node_ord); + } + break; + default: + break; + } + } + + switch(glp_ios_reason(tree)){ + case GLP_IBINGO: + Trace("approx::") << "bingo" << endl; + aux->term = MipBingo; + glp_ios_terminate(tree); + break; + case GLP_ICUTADDED: + { + tl.addCut(); + } + break; + case GLP_LI_BRANCH: + { + int p, dn, up; + int br_var = glp_ios_branch_log(tree, NULL, &p, &dn, &up); + + if(br_var >= 0){ + unsigned v = br_var; + tl.logBranch(v); + int depth = glp_ios_node_level(tree, p); + unsigned ubl = (aux->branchLimit) >= 0 ? ((unsigned)(aux->branchLimit)) : 0u; + if(tl.numBranches(v) >= ubl || depth >= (aux->branchDepth)){ + aux->term = BranchesExhausted; + glp_ios_terminate(tree); + } + } + } + break; + case GLP_LI_CLOSE: + break; + default: + { + glp_prob* prob = glp_ios_get_prob(tree); + int iterationcount = glp_get_it_cnt(prob); + if(exec > (aux->pivotLimit)){ + aux->term = ExecExhausted; + glp_ios_terminate(tree); + }else if(iterationcount > (aux->pivotLimit)){ + aux->term = PivotsExhauasted; + glp_ios_terminate(tree); + } + } + break; + } +} + +std::vector ApproxGLPK::getValidCuts(const NodeLog& con) +{ + std::vector proven; + int nid = con.getNodeId(); + for(NodeLog::const_iterator j = con.begin(), jend=con.end(); j!=jend; ++j){ + CutInfo* cut = *j; + + if(cut->getKlass() != RowsDeletedKlass){ + if(!cut->reconstructed()){ + Assert(!cut->reconstructed()); + tryCut(nid, *cut); + } + } + + if(cut->proven()){ + proven.push_back(cut); + } + } + return proven; +} + +ArithVar ApproxGLPK::getBranchVar(const NodeLog& con) const{ + int br_var = con.branchVariable(); + return getArithVarFromStructural(br_var); +} + + +MipResult ApproxGLPK::solveMIP(bool activelyLog){ + Assert(d_solvedRelaxation); + // Explicitly disable presolving + // We need the basis thus the presolver must be off! + // This is default, but this is just being cautious. + AuxInfo aux; + aux.pivotLimit = d_pivotLimit; + aux.branchLimit = d_branchLimit; + aux.branchDepth = d_maxDepth; + aux.tl = &d_log; + aux.term = MipUnknown; + + d_log.reset(d_rootRowIds); + if(activelyLog){ + d_log.makeActive(); + }else{ + d_log.makeInactive(); + } + + glp_iocp parm; + glp_init_iocp(&parm); + parm.presolve = GLP_OFF; + parm.pp_tech = GLP_PP_NONE; + parm.fp_heur = GLP_ON; + parm.gmi_cuts = GLP_ON; + parm.mir_cuts = GLP_ON; + parm.cov_cuts = GLP_ON; + parm.cb_func = glpkCallback; + parm.cb_info = &aux; + parm.msg_lev = GLP_MSG_OFF; + + glp_erase_prob(d_mipProb); + glp_copy_prob(d_mipProb, d_realProb, GLP_OFF); + + int res = glp_intopt(d_mipProb, &parm); + + Trace("approx::solveMIP") << "res "<& xs, const DenseMap& cs, bool* anyinf){ + if(anyinf != NULL){ + *anyinf = false; + } + + DeltaRational beta(0); + DenseMap::const_iterator iter, end; + iter = xs.begin(); + end = xs.end(); + + Trace("approx::sumConstraints") << "sumConstraints"; + for(; iter != end; ++iter){ + ArithVar x = *iter; + const Rational& psi = xs[x]; + ConstraintP c = cs[x]; + Assert(c != NullConstraint); + + const DeltaRational& bound = c->getValue(); + beta += bound * psi; + Trace("approx::sumConstraints") << " +("<& v){ + // Remove Slack variables + vector zeroes; + DenseMap::const_iterator i, iend; + for(i = v.begin(), iend = v.end(); i != iend; ++i){ + ArithVar x = *i; + if(v[x].isZero()){ + zeroes.push_back(x); + } + } + + vector::const_iterator j, jend; + for(j = zeroes.begin(), jend = zeroes.end(); j != jend; ++j){ + ArithVar x = *j; + v.remove(x); + } +} +void removeZeroes(DenseVector& v){ + removeZeroes(v.lhs); +} + +void removeAuxillaryVariables(const ArithVariables& vars, DenseMap& vec){ + // Remove auxillary variables + vector aux; + DenseMap::const_iterator vec_iter, vec_end; + vec_iter = vec.begin(), vec_end = vec.end(); + for(; vec_iter != vec_end; ++vec_iter){ + ArithVar x = *vec_iter; + if(vars.isAuxiliary(x)){ + aux.push_back(x); + } + } + + vector::const_iterator aux_iter, aux_end; + aux_iter = aux.begin(), aux_end = aux.end(); + for(; aux_iter != aux_end; ++aux_iter){ + ArithVar s = *aux_iter; + Rational& s_coeff = vec.get(s); + Assert(vars.isAuxiliary(s)); + Assert(vars.hasNode(s)); + Node sAsNode = vars.asNode(s); + Polynomial p = Polynomial::parsePolynomial(sAsNode); + for(Polynomial::iterator j = p.begin(), p_end=p.end(); j != p_end; ++j){ + Monomial m = *j; + const Rational& ns_coeff = m.getConstant().getValue(); + Node vl = m.getVarList().getNode(); + ArithVar ns = vars.asArithVar(vl); + Rational prod = s_coeff * ns_coeff; + if(vec.isKey(ns)){ + vec.get(ns) += prod; + }else{ + vec.set(ns, prod); + } + } + s_coeff = Rational(0); // subtract s_coeff * s from vec + } + removeZeroes(vec); +} + +ArithVar ApproxGLPK::_getArithVar(int nid, int M, int ind) const{ + if(ind <= 0){ + return ARITHVAR_SENTINEL; + }else if(ind <= M){ + return getArithVarFromRow(nid, ind); + }else{ + return getArithVarFromStructural(ind - M); + } +} + + +bool ApproxGLPK::guessIsConstructable(const DenseMap& guess) const { + // basic variable + // sum g[i] * x_i + DenseMap g = guess; + removeAuxillaryVariables(d_vars, g); + + if(TraceIsOn("guessIsConstructable")){ + if(!g.empty()){ + Trace("approx::guessIsConstructable") << "guessIsConstructable failed " << g.size() << endl; + DenseVector::print(Trace("approx::guessIsConstructable"), g); + Trace("approx::guessIsConstructable") << endl; + } + } + return g.empty(); +} + +bool ApproxGLPK::loadToBound(int nid, int M, int len, int* inds, int* statuses, DenseMap& toBound) const{ + for(int i = 1; i <= len; ++i){ + int status = statuses[i]; + int ind = inds[i]; + ArithVar v = _getArithVar(nid, M, ind); + ConstraintP c = NullConstraint; + if(v == ARITHVAR_SENTINEL){ return true; } + + switch(status){ + case GLP_NL: + c = d_vars.getLowerBoundConstraint(v); + break; + case GLP_NU: + case GLP_NS: // upper bound sufficies for fixed variables + c = d_vars.getUpperBoundConstraint(v); + break; + case GLP_NF: + default: + return true; + } + if(c == NullConstraint){ + Trace("approx::") << "couldn't find " << v << " @ " << nid << endl; + return true; + } + Assert(c != NullConstraint); + toBound.set(v, c); + } + return false; +} + +bool ApproxGLPK::checkCutOnPad(int nid, const CutInfo& cut) const{ + + Trace("approx::checkCutOnPad") << "checkCutOnPad(" << nid <<", " << cut.getId() <<")"<& constructedLhs = d_pad.d_cut.lhs; + const Rational& constructedRhs = d_pad.d_cut.rhs; + std::unordered_set visited; + + if(constructedLhs.empty()){ + Trace("approx::checkCutOnPad") << "its empty?" < br_cut_rhs = Rational::fromDouble(br_cut.getRhs()); + if (!br_cut_rhs) + { + return true; + } + + rhs = estimateWithCFE(*br_cut_rhs, Integer(1)); + d_pad.d_failure = !rhs.isIntegral(); + if(d_pad.d_failure) { return true; } + + d_pad.d_failure = checkCutOnPad(nid, br_cut); + if(d_pad.d_failure){ return true; } + + return false; +} + +bool ApproxGLPK::attemptGmi(int nid, const GmiInfo& gmi){ + d_pad.clear(); + + d_pad.d_cutKind = kind::GEQ; + + int M = gmi.getMAtCreation(); + ArithVar b = _getArithVar(nid, M, gmi.basic); + d_pad.d_failure = (b == ARITHVAR_SENTINEL); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = !d_vars.isIntegerInput(b); + if(d_pad.d_failure){ return true; } + + d_pad.d_basic = b; + + + const PrimitiveVec& tab = gmi.tab_row; + d_pad.d_failure = attemptConstructTableRow(nid, M, tab); + if(d_pad.d_failure){ return true; } + + int* statuses = gmi.tab_statuses; + DenseMap& toBound = d_pad.d_toBound; + d_pad.d_failure = loadToBound(nid, M, tab.len, tab.inds, statuses, toBound); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = constructGmiCut(); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = checkCutOnPad(nid, gmi); + if(d_pad.d_failure){ return true; } + + return false; +} + +bool ApproxGLPK::applyCMIRRule(int nid, const MirInfo& mir){ + + const DenseMap& compRanges = d_pad.d_compRanges; + + DenseMap& alpha = d_pad.d_alpha.lhs; + Rational& b = d_pad.d_alpha.rhs; + + std::optional delta = estimateWithCFE(mir.delta); + if (!delta) + { + return true; + } + + d_pad.d_failure = (delta->sgn() <= 0); + if(d_pad.d_failure){ return true; } + + Trace("approx::mir") << "applyCMIRRule() " << delta << " " << mir.delta << endl; + + DenseMap::const_iterator iter, iend; + iter = alpha.begin(), iend = alpha.end(); + for(; iter != iend; ++iter){ + ArithVar v = *iter; + const Rational& curr = alpha[v]; + Rational next = curr / *delta; + if(compRanges.isKey(v)){ + b -= curr * compRanges[v]; + alpha.set(v, - next); + }else{ + alpha.set(v, next); + } + } + b = b / *delta; + + Rational roundB = (b + Rational(1,2)).floor(); + d_pad.d_failure = (b - roundB).abs() < Rational(1,90); + // intensionally more generous than glpk here + if(d_pad.d_failure){ return true; } + + Rational one(1); + Rational fb = b.floor_frac(); + Rational one_sub_fb = one - fb; + Rational gamma = (one / one_sub_fb); + + DenseMap& cut = d_pad.d_cut.lhs; + Rational& beta = d_pad.d_cut.rhs; + + iter = alpha.begin(), iend = alpha.end(); + for(; iter != iend; ++iter){ + ArithVar v = *iter; + const Rational& a_j = alpha[v]; + if(d_vars.isIntegerInput(v)){ + Rational floor_aj = a_j.floor(); + Rational frac_aj = a_j.floor_frac(); + if(frac_aj <= fb){ + cut.set(v, floor_aj); + }else{ + Rational tmp = ((frac_aj - fb) / one_sub_fb); + cut.set(v, floor_aj + tmp); + } + }else{ + cut.set(v, a_j * gamma); + } + } + beta = b.floor(); + + iter = cut.begin(), iend = cut.end(); + for(; iter != iend; ++iter){ + ArithVar v = *iter; + if(compRanges.isKey(v)){ + Rational neg = - cut[v]; + beta += neg * compRanges[v]; + cut.set(v, neg); + } + } + + return false; +} + +bool ApproxGLPK::attemptMir(int nid, const MirInfo& mir){ + d_pad.clear(); + + d_pad.d_cutKind = kind::LEQ; + + // virtual bounds must be done before slacks + d_pad.d_failure = loadVirtualBoundsIntoPad(nid, mir); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = loadSlacksIntoPad(nid, mir); + if(d_pad.d_failure){ return true; } + + + d_pad.d_failure = loadRowSumIntoAgg(nid, mir.getMAtCreation(), mir.row_sum); + if(d_pad.d_failure){ return true; } + + removeFixed(d_vars, d_pad.d_agg, d_pad.d_explanation); + + d_pad.d_failure = buildModifiedRow(nid, mir); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = constructMixedKnapsack(); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = makeRangeForComplemented(nid, mir); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = applyCMIRRule(nid, mir); + if(d_pad.d_failure){ return true; } + + d_pad.d_failure = replaceSlacksOnCuts(); + if(d_pad.d_failure){ return true; } + + removeAuxillaryVariables(d_vars, d_pad.d_cut.lhs); + + d_pad.d_failure = checkCutOnPad(nid, mir); + if(d_pad.d_failure){ return true; } + + return false; + //return makeCutNodes(nid, mir); +} + +/** Returns true on failure. */ +bool ApproxGLPK::loadVB(int nid, int M, int j, int ri, bool wantUb, VirtualBound& tmp){ + if(ri <= 0) { return true; } + + Trace("glpk::loadVB") << "loadVB()" << endl; + + ArithVar rowVar = _getArithVar(nid, M, ri); + ArithVar contVar = _getArithVar(nid, M, j); + if(rowVar == ARITHVAR_SENTINEL){ + Trace("glpk::loadVB") << "loadVB()" + << " rowVar is ARITHVAR_SENTINEL " << rowVar << endl; + return true; + } + if(contVar == ARITHVAR_SENTINEL){ + Trace("glpk::loadVB") << "loadVB()" + << " contVar is ARITHVAR_SENTINEL " << contVar + << endl; + return true; } + + if(!d_vars.isAuxiliary(rowVar)){ + Trace("glpk::loadVB") << "loadVB()" + << " rowVar is not auxilliary " << rowVar << endl; + return true; + } + // is integer is correct here + if(d_vars.isInteger(contVar)){ + Trace("glpk::loadVB") << "loadVB()" + << " contVar is integer " << contVar << endl; + return true; + } + + ConstraintP lb = d_vars.getLowerBoundConstraint(rowVar); + ConstraintP ub = d_vars.getUpperBoundConstraint(rowVar); + + if(lb != NullConstraint && ub != NullConstraint){ + Trace("glpk::loadVB") << "loadVB()" + << " lb and ub are both NULL " << lb << " " << ub + << endl; + return true; + } + + ConstraintP rcon = lb == NullConstraint ? ub : lb; + if(rcon == NullConstraint) { + Trace("glpk::loadVB") << "loadVB()" + << " rcon is NULL " << rcon << endl; + return true; + } + + if(!rcon->getValue().isZero()){ + Trace("glpk::loadVB") << "loadVB()" + << " rcon value is not 0 " << rcon->getValue() + << endl; + return true; + } + + if(!d_vars.hasNode(rowVar)){ + Trace("glpk::loadVB") << "loadVB()" + << " does not have node " << rowVar << endl; + return true; + } + + Polynomial p = Polynomial::parsePolynomial(d_vars.asNode(rowVar)); + if (p.size() != 2) + { + Trace("glpk::loadVB") << "loadVB()" + << " polynomial is not binary: " << p.getNode() + << endl; + return true; + } + + Monomial first = p.getHead(), second = p.getTail().getHead(); + Rational c1 = first.getConstant().getValue(); + Rational c2 = second.getConstant().getValue(); + Node nx1 = first.getVarList().getNode(); + Node nx2 = second.getVarList().getNode(); + + if(!d_vars.hasArithVar(nx1)) { + Trace("glpk::loadVB") << "loadVB()" + << " does not have a variable for nx1: " << nx1 + << endl; + return true; + } + if(!d_vars.hasArithVar(nx2)) { + Trace("glpk::loadVB") << "loadVB()" + << " does not have a variable for nx2 " << nx2 + << endl; + return true; + } + ArithVar x1 = d_vars.asArithVar(nx1), x2 = d_vars.asArithVar(nx2); + + Assert(x1 != x2); + Assert(!c1.isZero()); + Assert(!c2.isZero()); + + Trace("glpk::loadVB") + << " lb " << lb + << " ub " << ub + << " rcon " << rcon + << " x1 " << x1 + << " x2 " << x2 + << " c1 " << c1 + << " c2 " << c2 << endl; + + ArithVar iv = (x1 == contVar) ? x2 : x1; + Rational& cc = (x1 == contVar) ? c1 : c2; + Rational& ic = (x1 == contVar) ? c2 : c1; + + Trace("glpk::loadVB") + << " cv " << contVar + << " cc " << cc + << " iv " << iv + << " c2 " << ic << endl; + + if(!d_vars.isIntegerInput(iv)){ + Trace("glpk::loadVB") << "loadVB()" + << " iv is not an integer input variable " << iv + << endl; + return true; + } + // cc * cv + ic * iv <= 0 or + // cc * cv + ic * iv <= 0 + + if(rcon == ub){ // multiply by -1 + cc = -cc; ic = - ic; + } + Trace("glpk::loadVB") << " cv " << contVar + << " cc " << cc + << " iv " << iv + << " c2 " << ic << endl; + + // cc * cv + ic * iv >= 0 + // cc * cv >= -ic * iv + // if cc < 0: + // cv <= -ic/cc * iv + // elif cc > 0: + // cv >= -ic/cc * iv + Assert(!cc.isZero()); + Rational d = -ic/cc; + Trace("glpk::loadVB") << d << " " << cc.sgn() << endl; + bool nowUb = cc.sgn() < 0; + if(wantUb != nowUb) { + Trace("glpk::loadVB") << "loadVB()" + << " wantUb is not nowUb " << wantUb << " " << nowUb + << endl; + + return true; + } + + Kind rel = wantUb ? kind::LEQ : kind::GEQ; + + tmp = VirtualBound(contVar, rel, d, iv, rcon); + Trace("glpk::loadVB") << "loadVB()" + << " was successful" << endl; + return false; +} + +bool ApproxGLPK::loadVirtualBoundsIntoPad(int nid, const MirInfo& mir){ + Assert(mir.vlbRows != NULL); + Assert(mir.vubRows != NULL); + + int N = mir.getN(); + int M = mir.getMAtCreation(); + + // Load the virtual bounds first + VirtualBound tmp; + for(int j=1; j <= N+M; ++j){ + if(!loadVB(nid, M, j, mir.vlbRows[j], false, tmp)){ + if(d_pad.d_vlb.isKey(tmp.x)){ return true; } + d_pad.d_vlb.set(tmp.x, tmp); + }else if(mir.vlbRows[j] > 0){ + Trace("approx::mir") << "expected vlb to work" << endl; + } + if(!loadVB(nid, M, j, mir.vubRows[j], true, tmp)){ + if(d_pad.d_vub.isKey(tmp.x)){ return true; } + d_pad.d_vub.set(tmp.x, tmp); + }else if(mir.vubRows[j] > 0){ + Trace("approx::mir") << "expected vub to work" << endl; + } + } + return false; +} + +bool ApproxGLPK::loadSlacksIntoPad(int nid, const MirInfo& mir){ + Assert(mir.vlbRows != NULL); + Assert(mir.vubRows != NULL); + + int N = mir.getN(); + int M = mir.getMAtCreation(); + + bool useVB; + // Load the virtual bounds first + SlackReplace rep; + bool lb; + ConstraintP b; + Trace("approx::mir") << "loadSlacksIntoPad(): N="< 0) : (mir.vubRows[j] > 0); + if(useVB){ + if(lb ? d_pad.d_vlb.isKey(v) : d_pad.d_vub.isKey(v)){ + rep = lb ? SlackVLB : SlackVUB; + } + }else{ + b = lb ? d_vars.getLowerBoundConstraint(v) + : d_vars.getUpperBoundConstraint(v); + if(b != NullConstraint){ + if(b->getValue().infinitesimalIsZero()){ + rep = lb ? SlackLB : SlackUB; + } + } + } + + Trace("approx::mir") << " for: " << j << ", " << v; + Trace("approx::mir") << " " << ((rep != SlackUndef) ? "succ" : "fail") << " "; + Trace("approx::mir") << sub << " " << rep << " " << mir.vlbRows[j] << " " << mir.vubRows[j] + << endl; + if(rep != SlackUndef){ + d_pad.d_slacks.set(v,rep); + } + break; + case '?': + continue; + default: + Trace("approx::mir") << " for: " << j << " got subst " << (int)sub << endl; + continue; + } + } + return false; +} + +bool ApproxGLPK::replaceSlacksOnCuts(){ + vector virtualVars; + + DenseMap& cut = d_pad.d_cut.lhs; + Rational& cutRhs = d_pad.d_cut.rhs; + + DenseMap::const_iterator iter, iend; + iter = cut.begin(), iend = cut.end(); + for(; iter != iend; ++iter){ + ArithVar x = *iter; + SlackReplace rep = d_pad.d_slacks[x]; + if(d_vars.isIntegerInput(x)){ + Assert(rep == SlackLB || rep == SlackUB); + Rational& a = cut.get(x); + + const DeltaRational& bound = (rep == SlackLB) ? + d_vars.getLowerBound(x) : d_vars.getUpperBound(x); + Assert(bound.infinitesimalIsZero()); + Rational prod = a * bound.getNoninfinitesimalPart(); + if(rep == SlackLB){ + cutRhs += prod; + }else{ + cutRhs -= prod; + a = -a; + } + }else if(rep == SlackVLB){ + virtualVars.push_back(d_pad.d_vlb[x].y); + }else if(rep == SlackVUB){ + virtualVars.push_back(d_pad.d_vub[x].y); + } + } + + for(size_t i = 0; i < virtualVars.size(); ++i){ + ArithVar x = virtualVars[i]; + if(!cut.isKey(x)){ + cut.set(x, Rational(0)); + } + } + + iter = cut.begin(), iend = cut.end(); + for(; iter != iend; ++iter){ + ArithVar x = *iter; + if(!d_vars.isIntegerInput(x)){ + SlackReplace rep = d_pad.d_slacks[x]; + Rational& a = cut.get(x); + switch(rep){ + case SlackLB: + { + const DeltaRational& bound = d_vars.getLowerBound(x); + Assert(bound.infinitesimalIsZero()); + cutRhs += a * bound.getNoninfinitesimalPart(); + } + break; + case SlackUB: + { + const DeltaRational& bound = d_vars.getUpperBound(x); + Assert(bound.infinitesimalIsZero()); + cutRhs -= a * bound.getNoninfinitesimalPart(); + a = -a; + } + break; + case SlackVLB: + case SlackVUB: + { + bool lb = (rep == SlackVLB); + const VirtualBound& vb = lb ? + d_pad.d_vlb[x] : d_pad.d_vub[x]; + ArithVar y = vb.y; + Assert(vb.x == x); + Assert(cut.isKey(y)); + Rational& ycoeff = cut.get(y); + if(lb){ + ycoeff -= a * vb.d; + }else{ + ycoeff += a * vb.d; + a = -a; + } + } + break; + default: + return true; + } + } + } + removeZeroes(cut); + return false; +} + +bool ApproxGLPK::loadRowSumIntoAgg(int nid, int M, const PrimitiveVec& row_sum){ + DenseMap& lhs = d_pad.d_agg.lhs; + d_pad.d_agg.rhs = Rational(0); + + int len = row_sum.len; + for(int i = 1; i <= len; ++i){ + int aux_ind = row_sum.inds[i]; // auxillary index + double coeff = row_sum.coeffs[i]; + ArithVar x = _getArithVar(nid, M, aux_ind); + if(x == ARITHVAR_SENTINEL){ return true; } + std::optional c = estimateWithCFE(coeff); + if (!c) + { + return true; + } + + if (lhs.isKey(x)) + { + lhs.get(x) -= *c; + } + else + { + lhs.set(x, -(*c)); + } + } + + Trace("approx::mir") << "beg loadRowSumIntoAgg() 1" << endl; + if(TraceIsOn("approx::mir")) { DenseVector::print(Trace("approx::mir"), lhs); } + removeAuxillaryVariables(d_vars, lhs); + Trace("approx::mir") << "end loadRowSumIntoAgg() 1" << endl; + + if(TraceIsOn("approx::mir")){ + Trace("approx::mir") << "loadRowSumIntoAgg() 2" << endl; + DenseVector::print(Trace("approx::mir"), lhs); + Trace("approx::mir") << "end loadRowSumIntoAgg() 2" << endl; + } + + for(int i = 1; i <= len; ++i){ + int aux_ind = row_sum.inds[i]; // auxillary index + double coeff = row_sum.coeffs[i]; + ArithVar x = _getArithVar(nid, M, aux_ind); + Assert(x != ARITHVAR_SENTINEL); + std::optional c = estimateWithCFE(coeff); + if (!c) + { + return true; + } + Assert(!lhs.isKey(x)); + lhs.set(x, *c); + } + + if(TraceIsOn("approx::mir")){ + Trace("approx::mir") << "loadRowSumIntoAgg() 2" << endl; + DenseVector::print(Trace("approx::mir"), lhs); + Trace("approx::mir") << "end loadRowSumIntoAgg() 3" << endl; + } + return false; +} + +bool ApproxGLPK::buildModifiedRow(int nid, const MirInfo& mir){ + const DenseMap& agg = d_pad.d_agg.lhs; + const Rational& aggRhs = d_pad.d_agg.rhs; + DenseMap& mod = d_pad.d_mod.lhs; + Rational& modRhs = d_pad.d_mod.rhs; + + Trace("approx::mir") + << "buildModifiedRow()" + << " |agg|=" << d_pad.d_agg.lhs.size() + << " |mod|=" << d_pad.d_mod.lhs.size() + << " |slacks|=" << d_pad.d_slacks.size() + << " |vlb|=" << d_pad.d_vub.size() + << " |vub|=" << d_pad.d_vlb.size() << endl; + + mod.addAll(agg); + modRhs = aggRhs; + DenseMap::const_iterator iter, iend; + for(iter = agg.begin(), iend = agg.end(); iter != iend; ++iter){ + ArithVar x = *iter; + const Rational& c = mod[x]; + if(!d_pad.d_slacks.isKey(x)){ + Trace("approx::mir") << "missed x: " << x << endl; + return true; + } + SlackReplace rep = d_pad.d_slacks[x]; + switch(rep){ + case SlackLB: // skip for now + case SlackUB: + break; + case SlackVLB: /* x[k] = lb[k] * x[kk] + x'[k] */ + case SlackVUB: /* x[k] = ub[k] * x[kk] - x'[k] */ + { + Assert(!d_vars.isIntegerInput(x)); + bool ub = (rep == SlackVUB); + const VirtualBound& vb = + ub ? d_pad.d_vub[x] : d_pad.d_vlb[x]; + Assert(vb.x == x); + ArithVar y = vb.y; + Rational prod = c * vb.d; + if(mod.isKey(y)){ + mod.get(x) += prod; + }else{ + mod.set(y, prod); + } + if(ub){ + mod.set(x, -c); + } + Assert(vb.c != NullConstraint); + d_pad.d_explanation.insert(vb.c); + } + break; + default: + return true; + } + } + removeZeroes(mod); /* if something cancelled we don't want it in the explanation */ + for(iter = mod.begin(), iend = mod.end(); iter != iend; ++iter){ + ArithVar x = *iter; + if(!d_pad.d_slacks.isKey(x)){ return true; } + + SlackReplace rep = d_pad.d_slacks[x]; + switch(rep){ + case SlackLB: /* x = lb + x' */ + case SlackUB: /* x = ub - x' */ + { + bool ub = (rep == SlackUB); + ConstraintP b = ub ? d_vars.getUpperBoundConstraint(x): + d_vars.getLowerBoundConstraint(x); + + Assert(b != NullConstraint); + Assert(b->getValue().infinitesimalIsZero()); + const Rational& c = mod.get(x); + modRhs -= c * b->getValue().getNoninfinitesimalPart(); + if(ub){ + mod.set(x, -c); + } + d_pad.d_explanation.insert(b); + } + break; + case SlackVLB: /* handled earlier */ + case SlackVUB: + break; + default: + return true; + } + } + removeZeroes(mod); + return false; +} + +bool ApproxGLPK::makeRangeForComplemented(int nid, const MirInfo& mir){ + DenseMap& alpha = d_pad.d_alpha.lhs; + int M = mir.getMAtCreation(); + int N = mir.getN(); + DenseMap& compRanges = d_pad.d_compRanges; + + int complemented = 0; + + for(int j = 1; j <= M + N; ++j){ + if(mir.cset[j] != 0){ + complemented++; + ArithVar x = _getArithVar(nid, M, j); + if(!alpha.isKey(x)){ return true; } + if(!d_vars.isIntegerInput(x)){ return true; } + Assert(d_pad.d_slacks.isKey(x)); + Assert(d_pad.d_slacks[x] == SlackLB || d_pad.d_slacks[x] == SlackUB); + + ConstraintP lb = d_vars.getLowerBoundConstraint(x); + ConstraintP ub = d_vars.getUpperBoundConstraint(x); + + if(lb == NullConstraint) { return true; } + if(ub == NullConstraint) { return true; } + + if(!lb->getValue().infinitesimalIsZero()){ + return true; + } + if(!ub->getValue().infinitesimalIsZero()){ + return true; + } + + const Rational& uval = ub->getValue().getNoninfinitesimalPart(); + const Rational& lval = lb->getValue().getNoninfinitesimalPart(); + + d_pad.d_explanation.insert(lb); + d_pad.d_explanation.insert(ub); + + Rational u = uval - lval; + // u is the same for both rep == LP and rep == UB + if(compRanges.isKey(x)) { return true; } + compRanges.set(x,u); + } + } + + Trace("approx::mir") << "makeRangeForComplemented()" << complemented << endl; + return false; +} + + +bool ApproxGLPK::constructMixedKnapsack(){ + const DenseMap& mod = d_pad.d_mod.lhs; + const Rational& modRhs = d_pad.d_mod.rhs; + DenseMap& alpha = d_pad.d_alpha.lhs; + Rational& beta = d_pad.d_alpha.rhs; + + Assert(alpha.empty()); + beta = modRhs; + + unsigned intVars = 0; + unsigned remain = 0; + unsigned dropped = 0; + DenseMap::const_iterator iter, iend; + for(iter = mod.begin(), iend = mod.end(); iter != iend; ++iter){ + ArithVar v = *iter; + const Rational& c = mod[v]; + Assert(!c.isZero()); + if(d_vars.isIntegerInput(v)){ + intVars++; + alpha.set(v, c); + }else if(c.sgn() < 0){ + remain++; + alpha.set(v, c); + }else{ + dropped++; + } + } + + Trace("approx::mir") + << "constructMixedKnapsack() " + <<" dropped " << dropped + <<" remain " << remain + <<" intVars " << intVars + << endl; + return intVars == 0; // if this is 0 we have failed +} + +bool ApproxGLPK::attemptConstructTableRow(int nid, int M, const PrimitiveVec& vec){ + bool failed = guessCoefficientsConstructTableRow(nid, M, vec); + if(failed){ + failed = gaussianElimConstructTableRow(nid, M, vec); + } + + return failed; +} + +bool ApproxGLPK::gaussianElimConstructTableRow(int nid, int M, const PrimitiveVec& vec){ + TimerStat::CodeTimer codeTimer(d_stats.d_gaussianElimConstructTime); + ++d_stats.d_gaussianElimConstruct; + + ArithVar basic = d_pad.d_basic; + DenseMap& tab = d_pad.d_tabRow.lhs; + tab.purge(); + d_pad.d_tabRow.rhs = Rational(0); + Assert(basic != ARITHVAR_SENTINEL); + Assert(tab.empty()); + Assert(d_pad.d_tabRow.rhs.isZero()); + + if(d_vars.isAuxiliary(basic)) { return true; } + + if(TraceIsOn("gaussianElimConstructTableRow")){ + Trace("gaussianElimConstructTableRow") << "1 gaussianElimConstructTableRow("< onrow; + for(int i = 1; i <= vec.len; ++i){ + int ind = vec.inds[i]; + ArithVar var = _getArithVar(nid, M, ind); + if(var == ARITHVAR_SENTINEL){ + Trace("gaussianElimConstructTableRow") << "couldn't find" << ind << " " << M << " " << nid << endl; + return true; + } + onrow.insert(var); + } + + + Trace("gaussianElimConstructTableRow") << "2 gaussianElimConstructTableRow("< A; + A.increaseSizeTo(d_vars.getNumberOfVariables()); + std::vector< std::pair > rows; + // load the rows for auxiliary variables into A + for (ArithVar v : onrow) + { + if(d_vars.isAuxiliary(v)){ + Assert(d_vars.hasNode(v)); + + vector coeffs; + vector vars; + + coeffs.push_back(Rational(-1)); + vars.push_back(v); + + Node n = d_vars.asNode(v); + Polynomial p = Polynomial::parsePolynomial(n); + Polynomial::iterator j = p.begin(), jend=p.end(); + for(j=p.begin(), jend=p.end(); j!=jend; ++j){ + Monomial m = *j; + if(m.isConstant()) { return true; } + VarList vl = m.getVarList(); + if(!d_vars.hasArithVar(vl.getNode())){ return true; } + ArithVar x = d_vars.asArithVar(vl.getNode()); + const Rational& q = m.getConstant().getValue(); + coeffs.push_back(q); vars.push_back(x); + } + RowIndex rid = A.addRow(coeffs, vars); + rows.push_back(make_pair(rid, ARITHVAR_SENTINEL)); + } + } + Trace("gaussianElimConstructTableRow") << "3 gaussianElimConstructTableRow("<::Entry& e = A.findEntry(rid, other); + if(!e.blank()){ + // r_p : 0 = -1 * other + sum a_i x_i + // rid : 0 = e * other + sum b_i x_i + // rid += e * r_p + // : 0 = 0 * other + ... + Assert(!e.getCoefficient().isZero()); + + Rational cp = e.getCoefficient(); + Trace("gaussianElimConstructTableRow") + << "on " << rid << " subst " << cp << "*" << prevRow << " " << other << endl; + A.rowPlusRowTimesConstant(rid, prevRow, cp); + } + } + if(TraceIsOn("gaussianElimConstructTableRow")){ + A.printMatrix(Trace("gaussianElimConstructTableRow")); + } + + // solve the row for anything other than non-basics + bool solveForBasic = (i + 1 == rows.size()); + Rational q; + ArithVar s = ARITHVAR_SENTINEL; + Matrix::RowIterator k = A.getRow(rid).begin(); + Matrix::RowIterator k_end = A.getRow(rid).end(); + for(; k != k_end; ++k){ + const Matrix::Entry& e = *k; + ArithVar colVar = e.getColVar(); + bool selectColVar = false; + if(colVar == basic){ + selectColVar = solveForBasic; + }else if(onrow.find(colVar) == onrow.end()) { + selectColVar = true; + } + if(selectColVar){ + s = colVar; + q = e.getCoefficient(); + } + } + if(s == ARITHVAR_SENTINEL || q.isZero()){ + Trace("gaussianElimConstructTableRow") << "3 fail gaussianElimConstructTableRow("<::RowIterator k = A.getRow(rid_last).begin(); + Matrix::RowIterator k_end = A.getRow(rid_last).end(); + for(; k != k_end; ++k){ + const Matrix::Entry& e = *k; + tab.set(e.getColVar(), e.getCoefficient()); + } + Trace("gaussianElimConstructTableRow") << "5 gaussianElimConstructTableRow("< cfe = estimateWithCFE(coeff, D); + if (!cfe) + { + return true; + } + tab.set(var, *cfe); + Trace("guessCoefficientsConstructTableRow") << var << " cfe " << cfe << endl; + } + if(!guessIsConstructable(tab)){ + Trace("guessCoefficientsConstructTableRow") << "failed to construct with " << D << endl; + return true; + } + tab.remove(basic); + return false; +} + +/* Maps an ArithVar to either an upper/lower bound */ +bool ApproxGLPK::constructGmiCut(){ + const DenseMap& tabRow = d_pad.d_tabRow.lhs; + const DenseMap& toBound = d_pad.d_toBound; + DenseMap& cut = d_pad.d_cut.lhs; + std::set& explanation = d_pad.d_explanation; + Rational& rhs = d_pad.d_cut.rhs; + + DenseMap::const_iterator iter, end; + Assert(cut.empty()); + + // compute beta for a "fake" assignment + bool anyInf; + DeltaRational dbeta = sumConstraints(tabRow, toBound, &anyInf); + const Rational& beta = dbeta.getNoninfinitesimalPart(); + Trace("approx::gmi") << dbeta << endl; + if(anyInf || beta.isIntegral()){ return true; } + + Rational one = Rational(1); + Rational fbeta = beta.floor_frac(); + rhs = fbeta; + Assert(fbeta.sgn() > 0); + Assert(fbeta < one); + Rational one_sub_fbeta = one - fbeta; + for(iter = tabRow.begin(), end = tabRow.end(); iter != end; ++iter){ + ArithVar x = *iter; + const Rational& psi = tabRow[x]; + ConstraintP c = toBound[x]; + const Rational& bound = c->getValue().getNoninfinitesimalPart(); + if(d_vars.boundsAreEqual(x)){ + // do not add a coefficient + // implictly substitute the variable w/ its constraint + std::pair exp = d_vars.explainEqualBounds(x); + explanation.insert(exp.first); + if(exp.second != NullConstraint){ + explanation.insert(exp.second); + } + }else if(d_vars.isIntegerInput(x) && psi.isIntegral()){ + // do not add a coefficient + // nothing to explain + Trace("approx::gmi") << "skipping " << x << endl; + }else{ + explanation.insert(c); + Rational phi; + Rational alpha = (c->isUpperBound() ? psi : -psi); + + // x - ub <= 0 and lb - x <= 0 + if(d_vars.isIntegerInput(x)){ + Assert(!psi.isIntegral()); + // alpha = slack_sgn * psi + Rational falpha = alpha.floor_frac(); + Assert(falpha.sgn() > 0); + Assert(falpha < one); + phi = (falpha <= fbeta) ? + falpha : ((fbeta / one_sub_fbeta) * (one - falpha)); + }else{ + phi = (alpha >= 0) ? + alpha : ((fbeta / one_sub_fbeta) * (- alpha)); + } + Assert(phi.sgn() != 0); + if(c->isUpperBound()){ + cut.set(x, -phi); + rhs -= phi * bound; + }else{ + cut.set(x, phi); + rhs += phi * bound; + } + } + } + if(TraceIsOn("approx::gmi")){ + Trace("approx::gmi") << "pre removeSlackVariables"; + d_pad.d_cut.print(Trace("approx::gmi")); + Trace("approx::gmi") << endl; + } + removeAuxillaryVariables(d_vars, cut); + + if(TraceIsOn("approx::gmi")){ + Trace("approx::gmi") << "post removeAuxillaryVariables"; + d_pad.d_cut.print(Trace("approx::gmi")); + Trace("approx::gmi") << endl; + } + removeFixed(d_vars, d_pad.d_cut, explanation); + + if(TraceIsOn("approx::gmi")){ + Trace("approx::gmi") << "post removeFixed"; + d_pad.d_cut.print(Trace("approx::gmi")); + Trace("approx::gmi") << endl; + } + return false; +} + +void ApproxGLPK::tryCut(int nid, CutInfo& cut) +{ + Assert(!cut.reconstructed()); + Assert(cut.getKlass() != RowsDeletedKlass); + bool failure = false; + switch(cut.getKlass()){ + case GmiCutKlass: + failure = attemptGmi(nid, static_cast(cut)); + break; + case MirCutKlass: + failure = attemptMir(nid, static_cast(cut)); + break; + case BranchCutKlass: + failure = attemptBranchCut(nid, dynamic_cast(cut)); + break; + default: + break; + } + Assert(failure == d_pad.d_failure); + + if(!failure){ + // move the pad to the cut + cut.setReconstruction(d_pad.d_cut); + + if(cut.getKlass() != BranchCutKlass){ + std::set& exp = d_pad.d_explanation; + ConstraintCPVec asvec(exp.begin(), exp.end()); + cut.swapExplanation(asvec); + } + }else{ + Trace("approx") << "failure " << cut.getKlass() << endl; + } +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal +#endif /*#ifdef CVC5_USE_GLPK */ +/* End GPLK implementation. */ diff --git a/src/theory/arith/linear/approx_simplex.h b/src/theory/arith/linear/approx_simplex.h new file mode 100644 index 000000000..feac8156c --- /dev/null +++ b/src/theory/arith/linear/approx_simplex.h @@ -0,0 +1,168 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Morgan Deters + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include +#include + +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/delta_rational.h" +#include "util/dense_map.h" +#include "util/rational.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +enum LinResult { + LinUnknown, /* Unknown error */ + LinFeasible, /* Relaxation is feasible */ + LinInfeasible, /* Relaxation is infeasible/all integer branches closed */ + LinExhausted +}; + +enum MipResult { + MipUnknown, /* Unknown error */ + MipBingo, /* Integer feasible */ + MipClosed, /* All integer branches closed */ + BranchesExhausted, /* Exhausted number of branches */ + PivotsExhauasted, /* Exhausted number of pivots */ + ExecExhausted /* Exhausted total operations */ +}; +std::ostream& operator<<(std::ostream& out, MipResult res); + +class ApproximateStatistics { + public: + ApproximateStatistics(); + + IntStat d_branchMaxDepth; + IntStat d_branchesMaxOnAVar; + + TimerStat d_gaussianElimConstructTime; + IntStat d_gaussianElimConstruct; + AverageStat d_averageGuesses; +}; + + +class NodeLog; +class TreeLog; +class ArithVariables; +class CutInfo; + +class ApproximateSimplex{ + public: + static bool enabled(); + + /** + * If glpk is enabled, return a subclass that can do something. + * If glpk is disabled, return a subclass that does nothing. + */ + static ApproximateSimplex* mkApproximateSimplexSolver(const ArithVariables& vars, TreeLog& l, ApproximateStatistics& s); + ApproximateSimplex(const ArithVariables& v, TreeLog& l, ApproximateStatistics& s); + virtual ~ApproximateSimplex(){} + + /* the maximum pivots allowed in a query. */ + void setPivotLimit(int pl); + + /* maximum branches allowed on a variable */ + void setBranchOnVariableLimit(int bl); + + /* maximum branches allowed on a variable */ + void setBranchingDepth(int bd); + + /** A result is either sat, unsat or unknown.*/ + struct Solution { + DenseSet newBasis; + DenseMap newValues; + Solution() : newBasis(), newValues(){} + }; + + virtual ArithVar getBranchVar(const NodeLog& nl) const = 0; + + /** Sets a maximization criteria for the approximate solver.*/ + virtual void setOptCoeffs(const ArithRatPairVec& ref) = 0; + + virtual ArithRatPairVec heuristicOptCoeffs() const = 0; + + virtual LinResult solveRelaxation() = 0; + virtual Solution extractRelaxation() const = 0; + + virtual MipResult solveMIP(bool activelyLog) = 0; + + virtual Solution extractMIP() const = 0; + + virtual std::vector getValidCuts(const NodeLog& node) = 0; + + virtual void tryCut(int nid, CutInfo& cut) = 0; + + /** UTILITIES FOR DEALING WITH ESTIMATES */ + + static constexpr double SMALL_FIXED_DELTA = .000000001; + static constexpr double TOLERENCE = 1 + .000000001; + + /** Returns true if two doubles are roughly equal based on TOLERENCE and SMALL_FIXED_DELTA.*/ + static bool roughlyEqual(double a, double b); + + /** + * Estimates a double as a Rational using continued fraction expansion that + * cuts off the estimate once the value is approximately zero. + * This is designed for removing rounding artifacts. + */ + static std::optional estimateWithCFE(double d); + static std::optional estimateWithCFE(double d, const Integer& D); + + /** + * Converts a rational to a continued fraction expansion representation + * using a maximum number of expansions equal to depth as long as the expression + * is not roughlyEqual() to 0. + */ + static std::vector rationalToCfe(const Rational& q, int depth); + + /** Converts a continued fraction expansion representation to a rational. */ + static Rational cfeToRational(const std::vector& exp); + + /** Estimates a rational as a continued fraction expansion.*/ + static Rational estimateWithCFE(const Rational& q, const Integer& K); + + virtual double sumInfeasibilities(bool mip) const = 0; + + protected: + const ArithVariables& d_vars; + TreeLog& d_log; + ApproximateStatistics& d_stats; + + /* the maximum pivots allowed in a query. */ + int d_pivotLimit; + + /* maximum branches allowed on a variable */ + int d_branchLimit; + + /* maxmimum branching depth allowed.*/ + int d_maxDepth; + + /* Default denominator for diophatine approximation, 2^{26} .*/ + static constexpr uint64_t s_defaultMaxDenom = (1 << 26); +};/* class ApproximateSimplex */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/arith_static_learner.cpp b/src/theory/arith/linear/arith_static_learner.cpp new file mode 100644 index 000000000..c07359b51 --- /dev/null +++ b/src/theory/arith/linear/arith_static_learner.cpp @@ -0,0 +1,270 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Dejan Jovanovic, Andres Noetzli + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include + +#include "base/output.h" +#include "expr/node_algorithm.h" +#include "options/arith_options.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/arith_static_learner.h" +#include "theory/arith/arith_utilities.h" + +using namespace std; +using namespace cvc5::internal::kind; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +ArithStaticLearner::ArithStaticLearner(context::Context* userContext) + : d_minMap(userContext), d_maxMap(userContext), d_statistics() +{ +} + +ArithStaticLearner::~ArithStaticLearner(){ +} + +ArithStaticLearner::Statistics::Statistics() + : d_iteMinMaxApplications(smtStatisticsRegistry().registerInt( + "theory::arith::iteMinMaxApplications")), + d_iteConstantApplications(smtStatisticsRegistry().registerInt( + "theory::arith::iteConstantApplications")) +{ +} + +void ArithStaticLearner::staticLearning(TNode n, NodeBuilder& learned) +{ + vector workList; + workList.push_back(n); + TNodeSet processed; + + //Contains an underapproximation of nodes that must hold. + TNodeSet defTrue; + + defTrue.insert(n); + + while(!workList.empty()) { + n = workList.back(); + + bool unprocessedChildren = false; + for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { + if(processed.find(*i) == processed.end()) { + // unprocessed child + workList.push_back(*i); + unprocessedChildren = true; + } + } + if(n.getKind() == AND && defTrue.find(n) != defTrue.end() ){ + for(TNode::iterator i = n.begin(), iend = n.end(); i != iend; ++i) { + defTrue.insert(*i); + } + } + + if(unprocessedChildren) { + continue; + } + + workList.pop_back(); + // has node n been processed in the meantime ? + if(processed.find(n) != processed.end()) { + continue; + } + processed.insert(n); + + process(n,learned, defTrue); + + } +} + +void ArithStaticLearner::process(TNode n, + NodeBuilder& learned, + const TNodeSet& defTrue) +{ + Trace("arith::static") << "===================== looking at " << n << endl; + + switch(n.getKind()){ + case ITE: + if (expr::hasBoundVar(n)) + { + // Unsafe with non-ground ITEs; do nothing + Trace("arith::static") + << "(potentially) non-ground ITE, ignoring..." << endl; + break; + } + + if(n[0].getKind() != EQUAL && + isRelationOperator(n[0].getKind()) ){ + iteMinMax(n, learned); + } + + if((d_minMap.find(n[1]) != d_minMap.end() && d_minMap.find(n[2]) != d_minMap.end()) || + (d_maxMap.find(n[1]) != d_maxMap.end() && d_maxMap.find(n[2]) != d_maxMap.end())) { + iteConstant(n, learned); + } + break; + + case CONST_RATIONAL: + case CONST_INTEGER: + // Mark constants as minmax + d_minMap.insert(n, n.getConst()); + d_maxMap.insert(n, n.getConst()); + break; + default: // Do nothing + break; + } +} + +void ArithStaticLearner::iteMinMax(TNode n, NodeBuilder& learned) +{ + Assert(n.getKind() == kind::ITE); + Assert(n[0].getKind() != EQUAL); + Assert(isRelationOperator(n[0].getKind())); + + TNode c = n[0]; + Kind k = oldSimplifiedKind(c); + TNode t = n[1]; + TNode e = n[2]; + TNode cleft = (c.getKind() == NOT) ? c[0][0] : c[0]; + TNode cright = (c.getKind() == NOT) ? c[0][1] : c[1]; + + if((t == cright) && (e == cleft)){ + TNode tmp = t; + t = e; + e = tmp; + k = reverseRelationKind(k); + } + //(ite (< x y) x y) + //(ite (x < y) x y) + //(ite (x - y < 0) x y) + // ---------------- + // (ite (x - y < -c) ) + + if(t == cleft && e == cright){ + // t == cleft && e == cright + Assert(t == cleft); + Assert(e == cright); + switch(k){ + case LT: // (ite (< x y) x y) + case LEQ: { // (ite (<= x y) x y) + Node nLeqX = NodeBuilder(LEQ) << n << t; + Node nLeqY = NodeBuilder(LEQ) << n << e; + Trace("arith::static") << n << "is a min =>" << nLeqX << nLeqY << endl; + learned << nLeqX << nLeqY; + ++(d_statistics.d_iteMinMaxApplications); + break; + } + case GT: // (ite (> x y) x y) + case GEQ: { // (ite (>= x y) x y) + Node nGeqX = NodeBuilder(GEQ) << n << t; + Node nGeqY = NodeBuilder(GEQ) << n << e; + Trace("arith::static") << n << "is a max =>" << nGeqX << nGeqY << endl; + learned << nGeqX << nGeqY; + ++(d_statistics.d_iteMinMaxApplications); + break; + } + default: Unreachable(); + } + } +} + +void ArithStaticLearner::iteConstant(TNode n, NodeBuilder& learned) +{ + Assert(n.getKind() == ITE); + + Trace("arith::static") << "iteConstant(" << n << ")" << endl; + + if (d_minMap.find(n[1]) != d_minMap.end() && d_minMap.find(n[2]) != d_minMap.end()) { + const DeltaRational& first = d_minMap[n[1]]; + const DeltaRational& second = d_minMap[n[2]]; + DeltaRational min = std::min(first, second); + CDNodeToMinMaxMap::const_iterator minFind = d_minMap.find(n); + if (minFind == d_minMap.end() || (*minFind).second < min) { + d_minMap.insert(n, min); + NodeManager* nm = NodeManager::currentNM(); + Node nGeqMin = nm->mkNode( + min.getInfinitesimalPart() == 0 ? kind::GEQ : kind::GT, + n, + nm->mkConstRealOrInt(n.getType(), min.getNoninfinitesimalPart())); + learned << nGeqMin; + Trace("arith::static") << n << " iteConstant" << nGeqMin << endl; + ++(d_statistics.d_iteConstantApplications); + } + } + + if (d_maxMap.find(n[1]) != d_maxMap.end() && d_maxMap.find(n[2]) != d_maxMap.end()) { + const DeltaRational& first = d_maxMap[n[1]]; + const DeltaRational& second = d_maxMap[n[2]]; + DeltaRational max = std::max(first, second); + CDNodeToMinMaxMap::const_iterator maxFind = d_maxMap.find(n); + if (maxFind == d_maxMap.end() || (*maxFind).second > max) { + d_maxMap.insert(n, max); + NodeManager* nm = NodeManager::currentNM(); + Node nLeqMax = nm->mkNode( + max.getInfinitesimalPart() == 0 ? kind::LEQ : kind::LT, + n, + nm->mkConstRealOrInt(n.getType(), max.getNoninfinitesimalPart())); + learned << nLeqMax; + Trace("arith::static") << n << " iteConstant" << nLeqMax << endl; + ++(d_statistics.d_iteConstantApplications); + } + } +} + +std::set listToSet(TNode l){ + std::set ret; + while(l.getKind() == OR){ + Assert(l.getNumChildren() == 2); + ret.insert(l[0]); + l = l[1]; + } + return ret; +} + +void ArithStaticLearner::addBound(TNode n) { + + CDNodeToMinMaxMap::const_iterator minFind = d_minMap.find(n[0]); + CDNodeToMinMaxMap::const_iterator maxFind = d_maxMap.find(n[0]); + + Rational constant = n[1].getConst(); + DeltaRational bound = constant; + + switch(Kind k = n.getKind()) { + case kind::LT: bound = DeltaRational(constant, -1); CVC5_FALLTHROUGH; + case kind::LEQ: + if (maxFind == d_maxMap.end() || (*maxFind).second > bound) + { + d_maxMap.insert(n[0], bound); + Trace("arith::static") << "adding bound " << n << endl; + } + break; + case kind::GT: bound = DeltaRational(constant, 1); CVC5_FALLTHROUGH; + case kind::GEQ: + if (minFind == d_minMap.end() || (*minFind).second < bound) + { + d_minMap.insert(n[0], bound); + Trace("arith::static") << "adding bound " << n << endl; + } + break; + default: Unhandled() << k; break; + } +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/arith_static_learner.h b/src/theory/arith/linear/arith_static_learner.h new file mode 100644 index 000000000..c07681b45 --- /dev/null +++ b/src/theory/arith/linear/arith_static_learner.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andres Noetzli, Dejan Jovanovic + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__ARITH_STATIC_LEARNER_H +#define CVC5__THEORY__ARITH__ARITH_STATIC_LEARNER_H + +#include "context/cdhashmap.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/delta_rational.h" +#include "util/statistics_stats.h" + +namespace cvc5::context { +class Context; +} + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class ArithStaticLearner { +private: + + /** + * Map from a node to it's minimum and maximum. + */ + typedef context::CDHashMap CDNodeToMinMaxMap; + CDNodeToMinMaxMap d_minMap; + CDNodeToMinMaxMap d_maxMap; + +public: + ArithStaticLearner(context::Context* userContext); + ~ArithStaticLearner(); + void staticLearning(TNode n, NodeBuilder& learned); + + void addBound(TNode n); + +private: + void process(TNode n, NodeBuilder& learned, const TNodeSet& defTrue); + + void iteMinMax(TNode n, NodeBuilder& learned); + void iteConstant(TNode n, NodeBuilder& learned); + + /** + * These fields are designed to be accessible to ArithStaticLearner methods. + */ + class Statistics + { + public: + IntStat d_iteMinMaxApplications; + IntStat d_iteConstantApplications; + + Statistics(); + }; + + Statistics d_statistics; + +};/* class ArithStaticLearner */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__ARITH_STATIC_LEARNER_H */ diff --git a/src/theory/arith/linear/arithvar.cpp b/src/theory/arith/linear/arithvar.cpp new file mode 100644 index 000000000..2cebf540b --- /dev/null +++ b/src/theory/arith/linear/arithvar.cpp @@ -0,0 +1,36 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/arithvar.h" +#include +#include + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +const ArithVar ARITHVAR_SENTINEL = std::numeric_limits::max(); + +bool debugIsASet(const std::vector& variables){ + std::set asSet(variables.begin(), variables.end()); + return asSet.size() == variables.size(); +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/arithvar.h b/src/theory/arith/linear/arithvar.h new file mode 100644 index 000000000..f7679413e --- /dev/null +++ b/src/theory/arith/linear/arithvar.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * Defines ArithVar which is the internal representation of variables in + * arithmetic + * + * This defines ArithVar which is the internal representation of variables in + * arithmetic. This is a typedef from Index to ArithVar. + * This file also provides utilities for ArithVars. + */ + +#include "cvc5_private.h" + +#pragma once + +#include + +#include "util/index.h" +#include "util/rational.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +typedef Index ArithVar; +extern const ArithVar ARITHVAR_SENTINEL; + +typedef std::vector ArithVarVec; +typedef std::pair ArithRatPair; +typedef std::vector< ArithRatPair > ArithRatPairVec; + +extern bool debugIsASet(const ArithVarVec& variables); + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/arithvar_node_map.h b/src/theory/arith/linear/arithvar_node_map.h new file mode 100644 index 000000000..067d990d4 --- /dev/null +++ b/src/theory/arith/linear/arithvar_node_map.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__ARITHVAR_NODE_MAP_H +#define CVC5__THEORY__ARITH__ARITHVAR_NODE_MAP_H + +#include "theory/arith/linear/arithvar.h" +#include "context/context.h" +#include "context/cdlist.h" +#include "context/cdhashmap.h" +#include "context/cdo.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +//Maps from Nodes -> ArithVars, and vice versa +typedef std::unordered_map NodeToArithVarMap; +typedef DenseMap ArithVarToNodeMap; + +class ArithVarNodeMap { +private: + /** + * Bidirectional map between Nodes and ArithVars. + */ + NodeToArithVarMap d_nodeToArithVarMap; + ArithVarToNodeMap d_arithVarToNodeMap; + +public: + + typedef ArithVarToNodeMap::const_iterator var_iterator; + + ArithVarNodeMap() {} + + inline bool hasArithVar(TNode x) const { + return d_nodeToArithVarMap.find(x) != d_nodeToArithVarMap.end(); + } + + inline bool hasNode(ArithVar a) const { + return d_arithVarToNodeMap.isKey(a); + } + + inline ArithVar asArithVar(TNode x) const{ + Assert(hasArithVar(x)); + Assert((d_nodeToArithVarMap.find(x))->second <= ARITHVAR_SENTINEL); + return (d_nodeToArithVarMap.find(x))->second; + } + + inline Node asNode(ArithVar a) const{ + Assert(hasNode(a)); + return d_arithVarToNodeMap[a]; + } + + inline void setArithVar(TNode x, ArithVar a){ + Assert(!hasArithVar(x)); + Assert(!d_arithVarToNodeMap.isKey(a)); + d_arithVarToNodeMap.set(a, x); + d_nodeToArithVarMap[x] = a; + } + + inline void remove(ArithVar x){ + Assert(hasNode(x)); + Node node = asNode(x); + + d_nodeToArithVarMap.erase(d_nodeToArithVarMap.find(node)); + d_arithVarToNodeMap.remove(x); + } + + var_iterator var_begin() const { + return d_arithVarToNodeMap.begin(); + } + var_iterator var_end() const { + return d_arithVarToNodeMap.end(); + } + +};/* class ArithVarNodeMap */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__ARITHVAR_NODE_MAP_H */ diff --git a/src/theory/arith/linear/attempt_solution_simplex.cpp b/src/theory/arith/linear/attempt_solution_simplex.cpp new file mode 100644 index 000000000..7ded87711 --- /dev/null +++ b/src/theory/arith/linear/attempt_solution_simplex.cpp @@ -0,0 +1,152 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ +#include "theory/arith/linear/attempt_solution_simplex.h" + +#include "base/output.h" +#include "options/arith_options.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/error_set.h" +#include "theory/arith/linear/linear_equality.h" +#include "theory/arith/linear/tableau.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +AttemptSolutionSDP::AttemptSolutionSDP(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), + d_statistics() +{ } + +AttemptSolutionSDP::Statistics::Statistics() + : d_searchTime(smtStatisticsRegistry().registerTimer( + "theory::arith::attempt::searchTime")), + d_queueTime(smtStatisticsRegistry().registerTimer( + "theory::arith::attempt::queueTime")), + d_conflicts(smtStatisticsRegistry().registerInt( + "theory::arith::attempt::conflicts")) +{ +} + +bool AttemptSolutionSDP::matchesNewValue(const DenseMap& nv, ArithVar v) const{ + return nv[v] == d_variables.getAssignment(v); +} + +Result::Status AttemptSolutionSDP::attempt( + const ApproximateSimplex::Solution& sol) +{ + const DenseSet& newBasis = sol.newBasis; + const DenseMap& newValues = sol.newValues; + + DenseSet needsToBeAdded; + for(DenseSet::const_iterator i = newBasis.begin(), i_end = newBasis.end(); i != i_end; ++i){ + ArithVar b = *i; + if(!d_tableau.isBasic(b)){ + needsToBeAdded.add(b); + } + } + DenseMap::const_iterator nvi = newValues.begin(), nvi_end = newValues.end(); + for(; nvi != nvi_end; ++nvi){ + ArithVar currentlyNb = *nvi; + if(!d_tableau.isBasic(currentlyNb)){ + if(!matchesNewValue(newValues, currentlyNb)){ + const DeltaRational& newValue = newValues[currentlyNb]; + Trace("arith::updateMany") + << "updateMany:" << currentlyNb << " " + << d_variables.getAssignment(currentlyNb) << " to "<< newValue << endl; + d_linEq.update(currentlyNb, newValue); + Assert(d_variables.assignmentIsConsistent(currentlyNb)); + } + } + } + d_errorSet.reduceToSignals(); + d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); + + if(processSignals()){ + Trace("arith::findModel") << "attemptSolution() early conflict" << endl; + d_conflictVariables.purge(); + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Trace("arith::findModel") << "attemptSolution() fixed itself" << endl; + return Result::SAT; + } + + while(!needsToBeAdded.empty() && !d_errorSet.errorEmpty()){ + ArithVar toRemove = ARITHVAR_SENTINEL; + ArithVar toAdd = ARITHVAR_SENTINEL; + DenseSet::const_iterator i = needsToBeAdded.begin(), i_end = needsToBeAdded.end(); + for(; toAdd == ARITHVAR_SENTINEL && i != i_end; ++i){ + ArithVar v = *i; + + Tableau::ColIterator colIter = d_tableau.colIterator(v); + for(; !colIter.atEnd(); ++colIter){ + const Tableau::Entry& entry = *colIter; + Assert(entry.getColVar() == v); + ArithVar b = d_tableau.rowIndexToBasic(entry.getRowIndex()); + if(!newBasis.isMember(b)){ + toAdd = v; + + bool favorBOverToRemove = + (toRemove == ARITHVAR_SENTINEL) || + (matchesNewValue(newValues, toRemove) && !matchesNewValue(newValues, b)) || + (d_tableau.basicRowLength(toRemove) > d_tableau.basicRowLength(b)); + + if(favorBOverToRemove){ + toRemove = b; + } + } + } + } + Assert(toRemove != ARITHVAR_SENTINEL); + Assert(toAdd != ARITHVAR_SENTINEL); + + Trace("arith::forceNewBasis") << toRemove << " " << toAdd << endl; + + d_linEq.pivotAndUpdate(toRemove, toAdd, newValues[toRemove]); + + Trace("arith::forceNewBasis") << needsToBeAdded.size() << "to go" << endl; + needsToBeAdded.remove(toAdd); + + bool conflict = processSignals(); + if(conflict){ + d_errorSet.reduceToSignals(); + d_conflictVariables.purge(); + + return Result::UNSAT; + } + } + Assert(d_conflictVariables.empty()); + + if(d_errorSet.errorEmpty()){ + return Result::SAT; + }else{ + d_errorSet.reduceToSignals(); + return Result::UNKNOWN; + } +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/attempt_solution_simplex.h b/src/theory/arith/linear/attempt_solution_simplex.h new file mode 100644 index 000000000..1e3682be0 --- /dev/null +++ b/src/theory/arith/linear/attempt_solution_simplex.h @@ -0,0 +1,100 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + * + * This implements the Simplex module for the Simpelx for DPLL(T) decision + * procedure. + * See the Simplex for DPLL(T) technical report for more background.(citation?) + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This is required to either produce a conflict or satisifying PartialModel. + * Further, we require being told when a basic variable updates its value. + * + * During the Simplex search we maintain a queue of variables. + * The queue is required to contain all of the basic variables that voilate + * their bounds. + * As elimination from the queue is more efficient to be done lazily, + * we do not maintain that the queue of variables needs to be only basic + * variables or only variables that satisfy their bounds. + * + * The simplex procedure roughly follows Alberto's thesis. (citation?) + * There is one round of selecting using a heuristic pivoting rule. + * (See PreferenceFunction Documentation for the available options.) + * The non-basic variable is the one that appears in the fewest pivots. + * (Bruno says that Leonardo invented this first.) + * After this, Bland's pivot rule is invoked. + * + * During this proccess, we periodically inspect the queue of variables to + * 1) remove now extraneous extries, + * 2) detect conflicts that are "waiting" on the queue but may not be detected + * by the current queue heuristics, and + * 3) detect multiple conflicts. + * + * Conflicts are greedily slackened to use the weakest bounds that still + * produce the conflict. + * + * Extra things tracked atm: (Subject to change at Tim's whims) + * - A superset of all of the newly pivoted variables. + * - A queue of additional conflicts that were discovered by Simplex. + * These are theory valid and are currently turned into lemmas + */ + +#include "cvc5_private.h" + +#pragma once + +#include "theory/arith/linear/approx_simplex.h" +#include "theory/arith/linear/simplex.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class AttemptSolutionSDP : public SimplexDecisionProcedure { +public: + AttemptSolutionSDP(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc); + + Result::Status attempt(const ApproximateSimplex::Solution& sol); + + Result::Status findModel(bool exactResult) override { Unreachable(); } + +private: + bool matchesNewValue(const DenseMap& nv, ArithVar v) const; + + bool processSignals() + { + TimerStat& timer = d_statistics.d_queueTime; + IntStat& conflictStat = d_statistics.d_conflicts; + return standardProcessSignals(timer, conflictStat); + } + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + TimerStat d_searchTime; + TimerStat d_queueTime; + IntStat d_conflicts; + + Statistics(); + } d_statistics; +};/* class AttemptSolutionSDP */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/bound_counts.h b/src/theory/arith/linear/bound_counts.h new file mode 100644 index 000000000..3524e1cb2 --- /dev/null +++ b/src/theory/arith/linear/bound_counts.h @@ -0,0 +1,235 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Clark Barrett + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" +#pragma once + +#include "base/check.h" +#include "theory/arith/linear/arithvar.h" +#include "util/dense_map.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class BoundCounts { +private: + uint32_t d_lowerBoundCount; + uint32_t d_upperBoundCount; + +public: + BoundCounts() : d_lowerBoundCount(0), d_upperBoundCount(0) {} + BoundCounts(uint32_t lbs, uint32_t ubs) + : d_lowerBoundCount(lbs), d_upperBoundCount(ubs) {} + + bool operator==(BoundCounts bc) const { + return d_lowerBoundCount == bc.d_lowerBoundCount + && d_upperBoundCount == bc.d_upperBoundCount; + } + bool operator!=(BoundCounts bc) const { + return d_lowerBoundCount != bc.d_lowerBoundCount + || d_upperBoundCount != bc.d_upperBoundCount; + } + /** This is not a total order! */ + bool operator>=(BoundCounts bc) const { + return d_lowerBoundCount >= bc.d_lowerBoundCount && + d_upperBoundCount >= bc.d_upperBoundCount; + } + + inline bool isZero() const{ return d_lowerBoundCount == 0 && d_upperBoundCount == 0; } + inline uint32_t lowerBoundCount() const{ + return d_lowerBoundCount; + } + inline uint32_t upperBoundCount() const{ + return d_upperBoundCount; + } + + inline BoundCounts operator+(BoundCounts bc) const{ + return BoundCounts(d_lowerBoundCount + bc.d_lowerBoundCount, + d_upperBoundCount + bc.d_upperBoundCount); + } + + inline BoundCounts operator-(BoundCounts bc) const { + Assert(*this >= bc); + return BoundCounts(d_lowerBoundCount - bc.d_lowerBoundCount, + d_upperBoundCount - bc.d_upperBoundCount); + } + + + inline BoundCounts& operator+=(BoundCounts bc) { + d_upperBoundCount += bc.d_upperBoundCount; + d_lowerBoundCount += bc.d_lowerBoundCount; + return *this; + } + + inline BoundCounts& operator-=(BoundCounts bc) { + Assert(d_lowerBoundCount >= bc.d_lowerBoundCount); + Assert(d_upperBoundCount >= bc.d_upperBoundCount); + d_upperBoundCount -= bc.d_upperBoundCount; + d_lowerBoundCount -= bc.d_lowerBoundCount; + + return *this; + } + + /** Based on the sign coefficient a variable is multiplied by, + * the effects the bound counts either has no effect (sgn == 0), + * the lower bounds and upper bounds flip (sgn < 0), or nothing (sgn >0). + */ + inline BoundCounts multiplyBySgn(int sgn) const{ + if(sgn > 0){ + return *this; + }else if(sgn == 0){ + return BoundCounts(0,0); + }else{ + return BoundCounts(d_upperBoundCount, d_lowerBoundCount); + } + } + + inline void addInChange(int sgn, BoundCounts before, BoundCounts after){ + if(before == after){ + return; + }else if(sgn < 0){ + Assert(d_upperBoundCount >= before.d_lowerBoundCount); + Assert(d_lowerBoundCount >= before.d_upperBoundCount); + d_upperBoundCount += after.d_lowerBoundCount - before.d_lowerBoundCount; + d_lowerBoundCount += after.d_upperBoundCount - before.d_upperBoundCount; + }else if(sgn > 0){ + Assert(d_upperBoundCount >= before.d_upperBoundCount); + Assert(d_lowerBoundCount >= before.d_lowerBoundCount); + d_upperBoundCount += after.d_upperBoundCount - before.d_upperBoundCount; + d_lowerBoundCount += after.d_lowerBoundCount - before.d_lowerBoundCount; + } + } + + inline void addInSgn(BoundCounts bc, int before, int after){ + Assert(before != after); + Assert(!bc.isZero()); + + if(before < 0){ + d_upperBoundCount -= bc.d_lowerBoundCount; + d_lowerBoundCount -= bc.d_upperBoundCount; + }else if(before > 0){ + d_upperBoundCount -= bc.d_upperBoundCount; + d_lowerBoundCount -= bc.d_lowerBoundCount; + } + if(after < 0){ + d_upperBoundCount += bc.d_lowerBoundCount; + d_lowerBoundCount += bc.d_upperBoundCount; + }else if(after > 0){ + d_upperBoundCount += bc.d_upperBoundCount; + d_lowerBoundCount += bc.d_lowerBoundCount; + } + } +}; + +class BoundsInfo { +private: + + /** + * x = \sum_{a < 0} a_i i + \sum_{b > 0} b_j j + * + * AtUpperBound = {assignment(i) = lb(i)} \cup {assignment(j) = ub(j)} + * AtLowerBound = {assignment(i) = ub(i)} \cup {assignment(j) = lb(j)} + */ + BoundCounts d_atBounds; + + /** This is for counting how many upper and lower bounds a row has. */ + BoundCounts d_hasBounds; + +public: + BoundsInfo() : d_atBounds(), d_hasBounds() {} + BoundsInfo(BoundCounts atBounds, BoundCounts hasBounds) + : d_atBounds(atBounds), d_hasBounds(hasBounds) {} + + BoundCounts atBounds() const { return d_atBounds; } + BoundCounts hasBounds() const { return d_hasBounds; } + + /** This corresponds to adding in another variable to the row. */ + inline BoundsInfo operator+(const BoundsInfo& bc) const{ + return BoundsInfo(d_atBounds + bc.d_atBounds, + d_hasBounds + bc.d_hasBounds); + } + /** This corresponds to removing a variable from the row. */ + inline BoundsInfo operator-(const BoundsInfo& bc) const { + Assert(*this >= bc); + return BoundsInfo(d_atBounds - bc.d_atBounds, + d_hasBounds - bc.d_hasBounds); + } + + inline BoundsInfo& operator+=(const BoundsInfo& bc) { + d_atBounds += bc.d_atBounds; + d_hasBounds += bc.d_hasBounds; + return (*this); + } + + /** Based on the sign coefficient a variable is multiplied by, + * the effects the bound counts either has no effect (sgn == 0), + * the lower bounds and upper bounds flip (sgn < 0), or nothing (sgn >0). + */ + inline BoundsInfo multiplyBySgn(int sgn) const{ + return BoundsInfo(d_atBounds.multiplyBySgn(sgn), d_hasBounds.multiplyBySgn(sgn)); + } + + bool operator==(const BoundsInfo& other) const{ + return d_atBounds == other.d_atBounds && d_hasBounds == other.d_hasBounds; + } + bool operator!=(const BoundsInfo& other) const{ + return !(*this == other); + } + /** This is not a total order! */ + bool operator>=(const BoundsInfo& other) const{ + return d_atBounds >= other.d_atBounds && d_hasBounds >= other.d_hasBounds; + } + void addInChange(int sgn, const BoundsInfo& before, const BoundsInfo& after){ + addInAtBoundChange(sgn, before.d_atBounds, after.d_atBounds); + addInHasBoundChange(sgn, before.d_hasBounds, after.d_hasBounds); + } + void addInAtBoundChange(int sgn, BoundCounts before, BoundCounts after){ + d_atBounds.addInChange(sgn, before, after); + } + void addInHasBoundChange(int sgn, BoundCounts before, BoundCounts after){ + d_hasBounds.addInChange(sgn, before, after); + } + + inline void addInSgn(const BoundsInfo& bc, int before, int after){ + if(!bc.d_atBounds.isZero()){ d_atBounds.addInSgn(bc.d_atBounds, before, after);} + if(!bc.d_hasBounds.isZero()){ d_hasBounds.addInSgn(bc.d_hasBounds, before, after);} + } +}; + +/** This is intended to map each row to its relevant bound information. */ +typedef DenseMap BoundInfoMap; + +inline std::ostream& operator<<(std::ostream& os, const BoundCounts& bc){ + os << "[bc " << bc.lowerBoundCount() << ", " << bc.upperBoundCount() << "]"; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const BoundsInfo& inf){ + os << "[bi : @ " << inf.atBounds() << ", " << inf.hasBounds() << "]"; + return os; +} +class BoundUpdateCallback { +public: + virtual ~BoundUpdateCallback() {} + virtual void operator()(ArithVar v, const BoundsInfo& up) = 0; +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/callbacks.cpp b/src/theory/arith/linear/callbacks.cpp new file mode 100644 index 000000000..87b022cf6 --- /dev/null +++ b/src/theory/arith/linear/callbacks.cpp @@ -0,0 +1,212 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/callbacks.h" + +#include "expr/skolem_manager.h" +#include "proof/proof_node.h" +#include "theory/arith/linear/theory_arith_private.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +SetupLiteralCallBack::SetupLiteralCallBack(TheoryArithPrivate& ta) + : d_arith(ta) +{} +void SetupLiteralCallBack::operator()(TNode lit){ + TNode atom = (lit.getKind() == kind::NOT) ? lit[0] : lit; + if(!d_arith.isSetup(atom)){ + d_arith.setupAtom(atom); + } +} + +DeltaComputeCallback::DeltaComputeCallback(const TheoryArithPrivate& ta) + : d_ta(ta) +{} +Rational DeltaComputeCallback::operator()() const{ + return d_ta.deltaValueForTotalOrder(); +} + +TempVarMalloc::TempVarMalloc(TheoryArithPrivate& ta) +: d_ta(ta) +{} +ArithVar TempVarMalloc::request(){ + NodeManager* nm = NodeManager::currentNM(); + SkolemManager* sm = nm->getSkolemManager(); + Node skolem = sm->mkDummySkolem("tmpVar", nm->realType()); + return d_ta.requestArithVar(skolem, false, true); +} +void TempVarMalloc::release(ArithVar v){ + d_ta.releaseArithVar(v); +} + +BasicVarModelUpdateCallBack::BasicVarModelUpdateCallBack(TheoryArithPrivate& ta) + : d_ta(ta) +{} +void BasicVarModelUpdateCallBack::operator()(ArithVar x){ + d_ta.signal(x); +} + +RaiseConflict::RaiseConflict(TheoryArithPrivate& ta) + : d_ta(ta) +{} + +void RaiseConflict::raiseConflict(ConstraintCP c, InferenceId id) const{ + Assert(c->inConflict()); + d_ta.raiseConflict(c, id); +} + +FarkasConflictBuilder::FarkasConflictBuilder(bool produceProofs) + : d_farkas(), + d_constraints(), + d_consequent(NullConstraint), + d_consequentSet(false), + d_produceProofs(produceProofs) +{ + reset(); +} + +bool FarkasConflictBuilder::underConstruction() const{ + return d_consequent != NullConstraint; +} + +bool FarkasConflictBuilder::consequentIsSet() const{ + return d_consequentSet; +} + +void FarkasConflictBuilder::reset(){ + d_consequent = NullConstraint; + d_constraints.clear(); + d_consequentSet = false; + if (d_produceProofs) + { + d_farkas.clear(); + } + Assert(!underConstruction()); +} + +/* Adds a constraint to the constraint under construction. */ +void FarkasConflictBuilder::addConstraint(ConstraintCP c, const Rational& fc){ + Assert( + !d_produceProofs + || (!underConstruction() && d_constraints.empty() && d_farkas.empty()) + || (underConstruction() && d_constraints.size() + 1 == d_farkas.size())); + Assert(d_produceProofs || d_farkas.empty()); + Assert(c->isTrue()); + + if(d_consequent == NullConstraint){ + d_consequent = c; + } else { + d_constraints.push_back(c); + } + if (d_produceProofs) + { + d_farkas.push_back(fc); + } + Assert(!d_produceProofs || d_constraints.size() + 1 == d_farkas.size()); + Assert(d_produceProofs || d_farkas.empty()); +} + +void FarkasConflictBuilder::addConstraint(ConstraintCP c, const Rational& fc, const Rational& mult){ + Assert(!mult.isZero()); + if (d_produceProofs && !mult.isOne()) + { + Rational prod = fc * mult; + addConstraint(c, prod); + } + else + { + addConstraint(c, fc); + } +} + +void FarkasConflictBuilder::makeLastConsequent(){ + Assert(!d_consequentSet); + Assert(underConstruction()); + + if(d_constraints.empty()){ + // no-op + d_consequentSet = true; + } else { + Assert(d_consequent != NullConstraint); + ConstraintCP last = d_constraints.back(); + d_constraints.back() = d_consequent; + d_consequent = last; + if (d_produceProofs) + { + std::swap(d_farkas.front(), d_farkas.back()); + } + d_consequentSet = true; + } + + Assert(!d_consequent->negationHasProof()); + Assert(d_consequentSet); +} + +/* Turns the vector under construction into a conflict */ +ConstraintCP FarkasConflictBuilder::commitConflict(){ + Assert(underConstruction()); + Assert(!d_constraints.empty()); + Assert( + !d_produceProofs + || (!underConstruction() && d_constraints.empty() && d_farkas.empty()) + || (underConstruction() && d_constraints.size() + 1 == d_farkas.size())); + Assert(d_produceProofs || d_farkas.empty()); + Assert(d_consequentSet); + + ConstraintP not_c = d_consequent->getNegation(); + RationalVectorCP coeffs = d_produceProofs ? &d_farkas : nullptr; + not_c->impliedByFarkas(d_constraints, coeffs, true ); + + reset(); + Assert(!underConstruction()); + Assert(not_c->inConflict()); + Assert(!d_consequentSet); + return not_c; +} + +RaiseEqualityEngineConflict::RaiseEqualityEngineConflict(TheoryArithPrivate& ta) + : d_ta(ta) +{} + +/* If you are not an equality engine, don't use this! */ +void RaiseEqualityEngineConflict::raiseEEConflict( + Node n, std::shared_ptr pf) const +{ + d_ta.raiseBlackBoxConflict(n, pf); +} + +BoundCountingLookup::BoundCountingLookup(TheoryArithPrivate& ta) +: d_ta(ta) +{} + +const BoundsInfo& BoundCountingLookup::boundsInfo(ArithVar basic) const{ + return d_ta.boundsInfo(basic); +} + +BoundCounts BoundCountingLookup::atBounds(ArithVar basic) const{ + return boundsInfo(basic).atBounds(); +} +BoundCounts BoundCountingLookup::hasBounds(ArithVar basic) const { + return boundsInfo(basic).hasBounds(); +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/callbacks.h b/src/theory/arith/linear/callbacks.h new file mode 100644 index 000000000..c93db91ca --- /dev/null +++ b/src/theory/arith/linear/callbacks.h @@ -0,0 +1,205 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Mathias Preiner, Clark Barrett + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#pragma once + +#include "expr/node.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/bound_counts.h" +#include "theory/arith/linear/constraint_forward.h" +#include "theory/inference_id.h" +#include "util/rational.h" + +namespace cvc5::internal { + +class ProofNode; + +namespace theory { +namespace arith::linear { + +class TheoryArithPrivate; + +/** + * ArithVarCallBack provides a mechanism for agreeing on callbacks while + * breaking mutual recursion inclusion order problems. + */ +class ArithVarCallBack { +public: + virtual ~ArithVarCallBack() {} + virtual void operator()(ArithVar x) = 0; +}; + +/** + * Requests arithmetic variables for internal use, + * and releases arithmetic variables that are no longer being used. + */ +class ArithVarMalloc { +public: + virtual ~ArithVarMalloc() {} + virtual ArithVar request() = 0; + virtual void release(ArithVar v) = 0; +}; + +class TNodeCallBack { +public: + virtual ~TNodeCallBack() {} + virtual void operator()(TNode n) = 0; +}; + +class NodeCallBack { +public: + virtual ~NodeCallBack() {} + virtual void operator()(Node n) = 0; +}; + +class RationalCallBack { +public: + virtual ~RationalCallBack() {} + virtual Rational operator()() const = 0; +}; + +class SetupLiteralCallBack : public TNodeCallBack { +private: + TheoryArithPrivate& d_arith; +public: + SetupLiteralCallBack(TheoryArithPrivate& ta); + void operator()(TNode lit) override; +}; + +class DeltaComputeCallback : public RationalCallBack { +private: + const TheoryArithPrivate& d_ta; +public: + DeltaComputeCallback(const TheoryArithPrivate& ta); + Rational operator()() const override; +}; + +class BasicVarModelUpdateCallBack : public ArithVarCallBack{ +private: + TheoryArithPrivate& d_ta; +public: + BasicVarModelUpdateCallBack(TheoryArithPrivate& ta); + void operator()(ArithVar x) override; +}; + +class TempVarMalloc : public ArithVarMalloc { +private: + TheoryArithPrivate& d_ta; +public: + TempVarMalloc(TheoryArithPrivate& ta); + ArithVar request() override; + void release(ArithVar v) override; +}; + +class RaiseConflict { +private: + TheoryArithPrivate& d_ta; +public: + RaiseConflict(TheoryArithPrivate& ta); + + /** Calls d_ta.raiseConflict(c) */ + void raiseConflict(ConstraintCP c, InferenceId id) const; +}; + +class FarkasConflictBuilder { +private: + RationalVector d_farkas; + ConstraintCPVec d_constraints; + ConstraintCP d_consequent; + bool d_consequentSet; + bool d_produceProofs; + + public: + + /** + * Constructs a new FarkasConflictBuilder. + */ + FarkasConflictBuilder(bool produceProofs); + + /** + * Adds an antecedent constraint to the conflict under construction + * with the farkas coefficient fc * mult. + * + * The value mult is either 1 or -1. + */ + void addConstraint(ConstraintCP c, const Rational& fc, const Rational& mult); + + /** + * Adds an antecedent constraint to the conflict under construction + * with the farkas coefficient fc. + */ + void addConstraint(ConstraintCP c, const Rational& fc); + + /** + * Makes the last constraint added the consequent. + * Can be done exactly once per reset(). + */ + void makeLastConsequent(); + + /** + * Turns the antecendents into a proof of the negation of one of the + * antecedents. + * + * The buffer is no longer underConstruction afterwards. + * + * precondition: + * - At least two constraints have been asserted. + * - makeLastConsequent() has been called. + * + * postcondition: The returned constraint is in conflict. + */ + ConstraintCP commitConflict(); + + /** Returns true if a conflict has been pushed back since the last reset. */ + bool underConstruction() const; + + /** Returns true if the consequent has been set since the last reset. */ + bool consequentIsSet() const; + + /** Resets the state of the buffer. */ + void reset(); +}; + + +class RaiseEqualityEngineConflict { +private: + TheoryArithPrivate& d_ta; + +public: + RaiseEqualityEngineConflict(TheoryArithPrivate& ta); + + /* If you are not an equality engine, don't use this! + * + * The proof should prove that `n` is a conflict. + * */ + void raiseEEConflict(Node n, std::shared_ptr pf) const; +}; + +class BoundCountingLookup { +private: + TheoryArithPrivate& d_ta; +public: + BoundCountingLookup(TheoryArithPrivate& ta); + const BoundsInfo& boundsInfo(ArithVar basic) const; + BoundCounts atBounds(ArithVar basic) const; + BoundCounts hasBounds(ArithVar basic) const; +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/congruence_manager.cpp b/src/theory/arith/linear/congruence_manager.cpp new file mode 100644 index 000000000..da4d81aa7 --- /dev/null +++ b/src/theory/arith/linear/congruence_manager.cpp @@ -0,0 +1,715 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Alex Ozdemir, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/congruence_manager.h" + +#include "base/output.h" +#include "options/arith_options.h" +#include "proof/proof_node.h" +#include "proof/proof_node_manager.h" +#include "smt/env.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/partial_model.h" +#include "theory/ee_setup_info.h" +#include "theory/rewriter.h" +#include "theory/uf/equality_engine.h" +#include "theory/uf/proof_equality_engine.h" + +using namespace cvc5::internal::kind; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +ArithCongruenceManager::ArithCongruenceManager( + Env& env, + ConstraintDatabase& cd, + SetupLiteralCallBack setup, + const ArithVariables& avars, + RaiseEqualityEngineConflict raiseConflict) + : EnvObj(env), + d_inConflict(context()), + d_raiseConflict(raiseConflict), + d_notify(*this), + d_keepAlive(context()), + d_propagatations(context()), + d_explanationMap(context()), + d_constraintDatabase(cd), + d_setupLiteral(setup), + d_avariables(avars), + d_ee(nullptr), + d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager() + : nullptr), + // Construct d_pfGenEe with the SAT context, since its proof include + // unclosed assumptions of theory literals. + d_pfGenEe(new EagerProofGenerator( + d_pnm, context(), "ArithCongruenceManager::pfGenEe")), + // Construct d_pfGenEe with the USER context, since its proofs are closed. + d_pfGenExplain(new EagerProofGenerator( + d_pnm, userContext(), "ArithCongruenceManager::pfGenExplain")), + d_pfee(nullptr) +{ +} + +ArithCongruenceManager::~ArithCongruenceManager() {} + +bool ArithCongruenceManager::needsEqualityEngine(EeSetupInfo& esi) +{ + Assert(!options().arith.arithEqSolver); + esi.d_notify = &d_notify; + esi.d_name = "arithCong::ee"; + return true; +} + +void ArithCongruenceManager::finishInit(eq::EqualityEngine* ee) +{ + if (options().arith.arithEqSolver) + { + // use our own copy + d_allocEe = std::make_unique( + d_env, context(), d_notify, "arithCong::ee", true); + d_ee = d_allocEe.get(); + if (d_pnm != nullptr) + { + // allocate an internal proof equality engine + d_allocPfee = std::make_unique(d_env, *d_ee); + d_ee->setProofEqualityEngine(d_allocPfee.get()); + } + } + else + { + Assert(ee != nullptr); + // otherwise, we use the official one + d_ee = ee; + } + // set the congruence kinds on the separate equality engine + d_ee->addFunctionKind(kind::NONLINEAR_MULT); + d_ee->addFunctionKind(kind::EXPONENTIAL); + d_ee->addFunctionKind(kind::SINE); + d_ee->addFunctionKind(kind::IAND); + d_ee->addFunctionKind(kind::POW2); + // the proof equality engine is the one from the equality engine + d_pfee = d_ee->getProofEqualityEngine(); + // have proof equality engine only if proofs are enabled + Assert(isProofEnabled() == (d_pfee != nullptr)); +} + +ArithCongruenceManager::Statistics::Statistics() + : d_watchedVariables(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::watchedVariables")), + d_watchedVariableIsZero(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::watchedVariableIsZero")), + d_watchedVariableIsNotZero(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::watchedVariableIsNotZero")), + d_equalsConstantCalls(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::equalsConstantCalls")), + d_propagations(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::propagations")), + d_propagateConstraints(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::propagateConstraints")), + d_conflicts(smtStatisticsRegistry().registerInt( + "theory::arith::congruence::conflicts")) +{ +} + +ArithCongruenceManager::ArithCongruenceNotify::ArithCongruenceNotify(ArithCongruenceManager& acm) + : d_acm(acm) +{} + +bool ArithCongruenceManager::ArithCongruenceNotify::eqNotifyTriggerPredicate( + TNode predicate, bool value) +{ + Assert(predicate.getKind() == kind::EQUAL); + Trace("arith::congruences") + << "ArithCongruenceNotify::eqNotifyTriggerPredicate(" << predicate << ", " + << (value ? "true" : "false") << ")" << std::endl; + if (value) { + return d_acm.propagate(predicate); + } + return d_acm.propagate(predicate.notNode()); +} + +bool ArithCongruenceManager::ArithCongruenceNotify::eqNotifyTriggerTermEquality(TheoryId tag, TNode t1, TNode t2, bool value) { + Trace("arith::congruences") << "ArithCongruenceNotify::eqNotifyTriggerTermEquality(" << t1 << ", " << t2 << ", " << (value ? "true" : "false") << ")" << std::endl; + if (value) { + return d_acm.propagate(t1.eqNode(t2)); + } else { + return d_acm.propagate(t1.eqNode(t2).notNode()); + } +} +void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyConstantTermMerge(TNode t1, TNode t2) { + Trace("arith::congruences") << "ArithCongruenceNotify::eqNotifyConstantTermMerge(" << t1 << ", " << t2 << std::endl; + d_acm.propagate(t1.eqNode(t2)); +} +void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyNewClass(TNode t) { +} +void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyMerge(TNode t1, + TNode t2) +{ +} +void ArithCongruenceManager::ArithCongruenceNotify::eqNotifyDisequal(TNode t1, TNode t2, TNode reason) { +} + +void ArithCongruenceManager::raiseConflict(Node conflict, + std::shared_ptr pf) +{ + Assert(!inConflict()); + Trace("arith::conflict") << "difference manager conflict " << conflict << std::endl; + d_inConflict.raise(); + d_raiseConflict.raiseEEConflict(conflict, pf); +} +bool ArithCongruenceManager::inConflict() const{ + return d_inConflict.isRaised(); +} + +bool ArithCongruenceManager::hasMorePropagations() const { + return !d_propagatations.empty(); +} +const Node ArithCongruenceManager::getNextPropagation() { + Assert(hasMorePropagations()); + Node prop = d_propagatations.front(); + d_propagatations.dequeue(); + return prop; +} + +bool ArithCongruenceManager::canExplain(TNode n) const { + return d_explanationMap.find(n) != d_explanationMap.end(); +} + +Node ArithCongruenceManager::externalToInternal(TNode n) const{ + Assert(canExplain(n)); + ExplainMap::const_iterator iter = d_explanationMap.find(n); + size_t pos = (*iter).second; + return d_propagatations[pos]; +} + +void ArithCongruenceManager::pushBack(TNode n){ + d_explanationMap.insert(n, d_propagatations.size()); + d_propagatations.enqueue(n); + + ++(d_statistics.d_propagations); +} +void ArithCongruenceManager::pushBack(TNode n, TNode r){ + d_explanationMap.insert(r, d_propagatations.size()); + d_explanationMap.insert(n, d_propagatations.size()); + d_propagatations.enqueue(n); + + ++(d_statistics.d_propagations); +} +void ArithCongruenceManager::pushBack(TNode n, TNode r, TNode w){ + d_explanationMap.insert(w, d_propagatations.size()); + d_explanationMap.insert(r, d_propagatations.size()); + d_explanationMap.insert(n, d_propagatations.size()); + d_propagatations.enqueue(n); + + ++(d_statistics.d_propagations); +} + +void ArithCongruenceManager::watchedVariableIsZero(ConstraintCP lb, ConstraintCP ub){ + Assert(lb->isLowerBound()); + Assert(ub->isUpperBound()); + Assert(lb->getVariable() == ub->getVariable()); + Assert(lb->getValue().sgn() == 0); + Assert(ub->getValue().sgn() == 0); + + ++(d_statistics.d_watchedVariableIsZero); + + ArithVar s = lb->getVariable(); + TNode eq = d_watchedEqualities[s]; + ConstraintCP eqC = d_constraintDatabase.getConstraint( + s, ConstraintType::Equality, lb->getValue()); + NodeBuilder reasonBuilder(Kind::AND); + auto pfLb = lb->externalExplainByAssertions(reasonBuilder); + auto pfUb = ub->externalExplainByAssertions(reasonBuilder); + Node reason = mkAndFromBuilder(reasonBuilder); + std::shared_ptr pf{}; + if (isProofEnabled()) + { + pf = d_pnm->mkNode( + PfRule::ARITH_TRICHOTOMY, {pfLb, pfUb}, {eqC->getProofLiteral()}); + pf = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {eq}); + } + + d_keepAlive.push_back(reason); + Trace("arith-ee") << "Asserting an equality on " << s << ", on trichotomy" + << std::endl; + Trace("arith-ee") << " based on " << lb << std::endl; + Trace("arith-ee") << " based on " << ub << std::endl; + assertionToEqualityEngine(true, s, reason, pf); +} + +void ArithCongruenceManager::watchedVariableIsZero(ConstraintCP eq){ + Trace("arith::cong") << "Cong::watchedVariableIsZero: " << *eq << std::endl; + + Assert(eq->isEquality()); + Assert(eq->getValue().sgn() == 0); + + ++(d_statistics.d_watchedVariableIsZero); + + ArithVar s = eq->getVariable(); + + //Explain for conflict is correct as these proofs are generated + //and stored eagerly + //These will be safe for propagation later as well + NodeBuilder nb(Kind::AND); + // An open proof of eq from literals now in reason. + if (TraceIsOn("arith::cong")) + { + eq->printProofTree(Trace("arith::cong")); + } + auto pf = eq->externalExplainByAssertions(nb); + if (isProofEnabled()) + { + pf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {d_watchedEqualities[s]}); + } + Node reason = mkAndFromBuilder(nb); + + d_keepAlive.push_back(reason); + assertionToEqualityEngine(true, s, reason, pf); +} + +void ArithCongruenceManager::watchedVariableCannotBeZero(ConstraintCP c){ + Trace("arith::cong::notzero") + << "Cong::watchedVariableCannotBeZero " << *c << std::endl; + ++(d_statistics.d_watchedVariableIsNotZero); + + ArithVar s = c->getVariable(); + Node disEq = d_watchedEqualities[s].negate(); + + //Explain for conflict is correct as these proofs are generated and stored eagerly + //These will be safe for propagation later as well + NodeBuilder nb(Kind::AND); + // An open proof of eq from literals now in reason. + auto pf = c->externalExplainByAssertions(nb); + if (TraceIsOn("arith::cong::notzero")) + { + Trace("arith::cong::notzero") << " original proof "; + pf->printDebug(Trace("arith::cong::notzero")); + Trace("arith::cong::notzero") << std::endl; + } + Node reason = mkAndFromBuilder(nb); + if (isProofEnabled()) + { + if (c->getType() == ConstraintType::Disequality) + { + Assert(c->getLiteral() == d_watchedEqualities[s].negate()); + // We have to prove equivalence to the watched disequality. + pf = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {disEq}); + } + else + { + Trace("arith::cong::notzero") + << " proof modification needed" << std::endl; + + // Four cases: + // c has form x_i = d, d > 0 => multiply c by -1 in Farkas proof + // c has form x_i = d, d > 0 => multiply c by 1 in Farkas proof + // c has form x_i <= d, d < 0 => multiply c by 1 in Farkas proof + // c has form x_i >= d, d > 0 => multiply c by -1 in Farkas proof + const bool scaleCNegatively = c->getType() == ConstraintType::LowerBound + || (c->getType() == ConstraintType::Equality + && c->getValue().sgn() > 0); + const int cSign = scaleCNegatively ? -1 : 1; + TNode isZero = d_watchedEqualities[s]; + TypeNode type = isZero[0].getType(); + const auto isZeroPf = d_pnm->mkAssume(isZero); + const auto nm = NodeManager::currentNM(); + const auto sumPf = + d_pnm->mkNode(PfRule::MACRO_ARITH_SCALE_SUM_UB, + {isZeroPf, pf}, + // Trick for getting correct, opposing signs. + {nm->mkConstRealOrInt(type, Rational(-1 * cSign)), + nm->mkConstRealOrInt(type, Rational(cSign))}); + const auto botPf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); + std::vector assumption = {isZero}; + pf = d_pnm->mkScope(botPf, assumption, false); + Trace("arith::cong::notzero") << " new proof "; + pf->printDebug(Trace("arith::cong::notzero")); + Trace("arith::cong::notzero") << std::endl; + } + Assert(pf->getResult() == disEq); + } + d_keepAlive.push_back(reason); + assertionToEqualityEngine(false, s, reason, pf); +} + + +bool ArithCongruenceManager::propagate(TNode x){ + Trace("arith::congruenceManager")<< "ArithCongruenceManager::propagate("<()){ + return true; + }else{ + // x rewrites to false. + ++(d_statistics.d_conflicts); + TrustNode trn = explainInternal(x); + Node conf = flattenAnd(trn.getNode()); + Trace("arith::congruenceManager") << "rewritten to false "<getProofFor(trn.getProven()); + auto confPf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {conf.negate()}); + raiseConflict(conf, confPf); + } + else + { + raiseConflict(conf); + } + return false; + } + } + + Assert(rewritten.getKind() != kind::CONST_BOOLEAN); + + ConstraintP c = d_constraintDatabase.lookup(rewritten); + if(c == NullConstraint){ + //using setup as there may not be a corresponding congruence literal yet + d_setupLiteral(rewritten); + c = d_constraintDatabase.lookup(rewritten); + Assert(c != NullConstraint); + } + + Trace("arith::congruenceManager")<< "x is " + << c->hasProof() << " " + << (x == rewritten) << " " + << c->canBePropagated() << " " + << c->negationHasProof() << std::endl; + + if(c->negationHasProof()){ + TrustNode texpC = explainInternal(x); + Node expC = texpC.getNode(); + ConstraintCP negC = c->getNegation(); + Node neg = Constraint::externalExplainByAssertions({negC}); + Node conf = expC.andNode(neg); + Node final = flattenAnd(conf); + + ++(d_statistics.d_conflicts); + raiseConflict(final); + Trace("arith::congruenceManager") << "congruenceManager found a conflict " << final << std::endl; + return false; + } + + // Cases for propagation + // C : c has a proof + // S : x == rewritten + // P : c can be propagated + // + // CSP + // 000 : propagate x, and mark C it as being explained + // 001 : propagate x, and propagate c after marking it as being explained + // 01* : propagate x, mark c but do not propagate c + // 10* : propagate x, do not mark c and do not propagate c + // 11* : drop the constraint, do not propagate x or c + + if(!c->hasProof() && x != rewritten){ + if(c->assertedToTheTheory()){ + pushBack(x, rewritten, c->getWitness()); + }else{ + pushBack(x, rewritten); + } + + c->setEqualityEngineProof(); + if(c->canBePropagated() && !c->assertedToTheTheory()){ + + ++(d_statistics.d_propagateConstraints); + c->propagate(); + } + }else if(!c->hasProof() && x == rewritten){ + if(c->assertedToTheTheory()){ + pushBack(x, c->getWitness()); + }else{ + pushBack(x); + } + c->setEqualityEngineProof(); + }else if(c->hasProof() && x != rewritten){ + if(c->assertedToTheTheory()){ + pushBack(x); + }else{ + pushBack(x); + } + }else{ + Assert(c->hasProof() && x == rewritten); + } + return true; +} + +void ArithCongruenceManager::explain(TNode literal, std::vector& assumptions) { + if (literal.getKind() != kind::NOT) { + d_ee->explainEquality(literal[0], literal[1], true, assumptions); + } else { + d_ee->explainEquality(literal[0][0], literal[0][1], false, assumptions); + } +} + +void ArithCongruenceManager::enqueueIntoNB(const std::set s, + NodeBuilder& nb) +{ + std::set::const_iterator it = s.begin(); + std::set::const_iterator it_end = s.end(); + for(; it != it_end; ++it) { + nb << *it; + } +} + +TrustNode ArithCongruenceManager::explainInternal(TNode internal) +{ + if (isProofEnabled()) + { + return d_pfee->explain(internal); + } + // otherwise, explain without proof generator + Node exp = d_ee->mkExplainLit(internal); + return TrustNode::mkTrustPropExp(internal, exp, nullptr); +} + +TrustNode ArithCongruenceManager::explain(TNode external) +{ + Trace("arith-ee") << "Ask for explanation of " << external << std::endl; + Node internal = externalToInternal(external); + Trace("arith-ee") << "...internal = " << internal << std::endl; + TrustNode trn = explainInternal(internal); + if (isProofEnabled() && trn.getProven()[1] != external) + { + Assert(trn.getKind() == TrustNodeKind::PROP_EXP); + Assert(trn.getProven().getKind() == Kind::IMPLIES); + Assert(trn.getGenerator() != nullptr); + Trace("arith-ee") << "tweaking proof to prove " << external << " not " + << trn.getProven()[1] << std::endl; + std::vector> assumptionPfs; + std::vector assumptions = andComponents(trn.getNode()); + assumptionPfs.push_back(trn.toProofNode()); + for (const auto& a : assumptions) + { + assumptionPfs.push_back( + d_pnm->mkNode(PfRule::TRUE_INTRO, {d_pnm->mkAssume(a)}, {})); + } + auto litPf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {assumptionPfs}, {external}); + auto extPf = d_pnm->mkScope(litPf, assumptions); + return d_pfGenExplain->mkTrustedPropagation(external, trn.getNode(), extPf); + } + return trn; +} + +void ArithCongruenceManager::explain(TNode external, NodeBuilder& out) +{ + Node internal = externalToInternal(external); + + std::vector assumptions; + explain(internal, assumptions); + std::set assumptionSet; + assumptionSet.insert(assumptions.begin(), assumptions.end()); + + enqueueIntoNB(assumptionSet, out); +} + +void ArithCongruenceManager::addWatchedPair(ArithVar s, TNode x, TNode y){ + Assert(!isWatchedVariable(s)); + + Trace("arith::congruenceManager") + << "addWatchedPair(" << s << ", " << x << ", " << y << ")" << std::endl; + + + ++(d_statistics.d_watchedVariables); + + d_watchedVariables.add(s); + + Node eq = x.eqNode(y); + d_watchedEqualities.set(s, eq); +} + +void ArithCongruenceManager::assertLitToEqualityEngine( + Node lit, TNode reason, std::shared_ptr pf) +{ + bool isEquality = lit.getKind() != Kind::NOT; + Node eq = isEquality ? lit : lit[0]; + Assert(eq.getKind() == Kind::EQUAL); + + Trace("arith-ee") << "Assert to Eq " << lit << ", reason " << reason + << std::endl; + if (isProofEnabled()) + { + if (CDProof::isSame(lit, reason)) + { + Trace("arith-pfee") << "Asserting only, b/c implied by symm" << std::endl; + // The equality engine doesn't ref-count for us... + d_keepAlive.push_back(eq); + d_keepAlive.push_back(reason); + d_ee->assertEquality(eq, isEquality, reason); + } + else if (hasProofFor(lit)) + { + Trace("arith-pfee") << "Skipping b/c already done" << std::endl; + } + else + { + setProofFor(lit, pf); + Trace("arith-pfee") << "Actually asserting" << std::endl; + if (TraceIsOn("arith-pfee")) + { + Trace("arith-pfee") << "Proof: "; + pf->printDebug(Trace("arith-pfee")); + Trace("arith-pfee") << std::endl; + } + // The proof equality engine *does* ref-count for us... + d_pfee->assertFact(lit, reason, d_pfGenEe.get()); + } + } + else + { + // The equality engine doesn't ref-count for us... + d_keepAlive.push_back(eq); + d_keepAlive.push_back(reason); + d_ee->assertEquality(eq, isEquality, reason); + } +} + +void ArithCongruenceManager::assertionToEqualityEngine( + bool isEquality, ArithVar s, TNode reason, std::shared_ptr pf) +{ + Assert(isWatchedVariable(s)); + + TNode eq = d_watchedEqualities[s]; + Assert(eq.getKind() == kind::EQUAL); + + Node lit = isEquality ? Node(eq) : eq.notNode(); + Trace("arith-ee") << "Assert to Eq " << eq << ", pol " << isEquality + << ", reason " << reason << std::endl; + assertLitToEqualityEngine(lit, reason, pf); +} + +bool ArithCongruenceManager::hasProofFor(TNode f) const +{ + Assert(isProofEnabled()); + if (d_pfGenEe->hasProofFor(f)) + { + return true; + } + Node sym = CDProof::getSymmFact(f); + Assert(!sym.isNull()); + return d_pfGenEe->hasProofFor(sym); +} + +void ArithCongruenceManager::setProofFor(TNode f, + std::shared_ptr pf) const +{ + Assert(!hasProofFor(f)); + d_pfGenEe->mkTrustNode(f, pf); + Node symF = CDProof::getSymmFact(f); + auto symPf = d_pnm->mkNode(PfRule::SYMM, {pf}, {}); + d_pfGenEe->mkTrustNode(symF, symPf); +} + +void ArithCongruenceManager::equalsConstant(ConstraintCP c){ + Assert(c->isEquality()); + + ++(d_statistics.d_equalsConstantCalls); + Trace("equalsConstant") << "equals constant " << c << std::endl; + + ArithVar x = c->getVariable(); + Node xAsNode = d_avariables.asNode(x); + NodeManager* nm = NodeManager::currentNM(); + Node asRational = nm->mkConstRealOrInt( + xAsNode.getType(), c->getValue().getNoninfinitesimalPart()); + + // No guarentee this is in normal form! + // Note though, that it happens to be in proof normal form! + Node eq = xAsNode.eqNode(asRational); + d_keepAlive.push_back(eq); + + NodeBuilder nb(Kind::AND); + auto pf = c->externalExplainByAssertions(nb); + Node reason = mkAndFromBuilder(nb); + d_keepAlive.push_back(reason); + + Trace("arith-ee") << "Assert equalsConstant " << eq << ", reason " << reason << std::endl; + assertLitToEqualityEngine(eq, reason, pf); +} + +void ArithCongruenceManager::equalsConstant(ConstraintCP lb, ConstraintCP ub){ + Assert(lb->isLowerBound()); + Assert(ub->isUpperBound()); + Assert(lb->getVariable() == ub->getVariable()); + + ++(d_statistics.d_equalsConstantCalls); + Trace("equalsConstant") << "equals constant " << lb << std::endl + << ub << std::endl; + + ArithVar x = lb->getVariable(); + NodeBuilder nb(Kind::AND); + auto pfLb = lb->externalExplainByAssertions(nb); + auto pfUb = ub->externalExplainByAssertions(nb); + Node reason = mkAndFromBuilder(nb); + + Node xAsNode = d_avariables.asNode(x); + NodeManager* nm = NodeManager::currentNM(); + Node asRational = nm->mkConstRealOrInt( + xAsNode.getType(), lb->getValue().getNoninfinitesimalPart()); + + // No guarentee this is in normal form! + // Note though, that it happens to be in proof normal form! + Node eq = xAsNode.eqNode(asRational); + std::shared_ptr pf; + if (isProofEnabled()) + { + pf = d_pnm->mkNode(PfRule::ARITH_TRICHOTOMY, {pfLb, pfUb}, {eq}); + } + d_keepAlive.push_back(eq); + d_keepAlive.push_back(reason); + + Trace("arith-ee") << "Assert equalsConstant2 " << eq << ", reason " << reason << std::endl; + + assertLitToEqualityEngine(eq, reason, pf); +} + +bool ArithCongruenceManager::isProofEnabled() const { return d_pnm != nullptr; } + +std::vector andComponents(TNode an) +{ + auto nm = NodeManager::currentNM(); + if (an == nm->mkConst(true)) + { + return {}; + } + else if (an.getKind() != Kind::AND) + { + return {an}; + } + std::vector a{}; + a.reserve(an.getNumChildren()); + a.insert(a.end(), an.begin(), an.end()); + return a; +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/congruence_manager.h b/src/theory/arith/linear/congruence_manager.h new file mode 100644 index 000000000..c9dfb158d --- /dev/null +++ b/src/theory/arith/linear/congruence_manager.h @@ -0,0 +1,301 @@ +/****************************************************************************** + * Top contributors (to current version): + * Alex Ozdemir, Tim King, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include "context/cdhashmap.h" +#include "context/cdlist.h" +#include "context/cdmaybe.h" +#include "context/cdtrail_queue.h" +#include "proof/trust_node.h" +#include "smt/env_obj.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/arithvar_node_map.h" +#include "theory/arith/linear/callbacks.h" +#include "theory/arith/linear/constraint_forward.h" +#include "theory/uf/equality_engine_notify.h" +#include "util/dense_map.h" +#include "util/statistics_stats.h" + +namespace cvc5::context { +class Context; +class UserContext; +} // namespace cvc5::context + +namespace cvc5::internal { + +class ProofNodeManager; +class EagerProofGenerator; + +namespace theory { +struct EeSetupInfo; + +namespace eq { +class ProofEqEngine; +class EqualityEngine; +} + +namespace arith::linear { + +class ArithVariables; + +class ArithCongruenceManager : protected EnvObj +{ + private: + context::CDRaised d_inConflict; + RaiseEqualityEngineConflict d_raiseConflict; + + /** + * The set of ArithVars equivalent to a pair of terms. + * If this is 0 or cannot be 0, this can be signalled. + * The pair of terms for the congruence is stored in watched equalities. + */ + DenseSet d_watchedVariables; + /** d_watchedVariables |-> (= x y) */ + ArithVarToNodeMap d_watchedEqualities; + + + class ArithCongruenceNotify : public eq::EqualityEngineNotify { + private: + ArithCongruenceManager& d_acm; + public: + ArithCongruenceNotify(ArithCongruenceManager& acm); + + bool eqNotifyTriggerPredicate(TNode predicate, bool value) override; + + bool eqNotifyTriggerTermEquality(TheoryId tag, + TNode t1, + TNode t2, + bool value) override; + + void eqNotifyConstantTermMerge(TNode t1, TNode t2) override; + void eqNotifyNewClass(TNode t) override; + void eqNotifyMerge(TNode t1, TNode t2) override; + void eqNotifyDisequal(TNode t1, TNode t2, TNode reason) override; + }; + ArithCongruenceNotify d_notify; + + context::CDList d_keepAlive; + + /** Store the propagations. */ + context::CDTrailQueue d_propagatations; + + /* This maps the node a theory engine will request on an explain call to + * to its corresponding PropUnit. + * This is node is potentially both the propagation or + * rewrite(propagation). + */ + typedef context::CDHashMap ExplainMap; + ExplainMap d_explanationMap; + + ConstraintDatabase& d_constraintDatabase; + SetupLiteralCallBack d_setupLiteral; + + const ArithVariables& d_avariables; + + /** The equality engine being used by this class */ + eq::EqualityEngine* d_ee; + /** The equality engine we allocated */ + std::unique_ptr d_allocEe; + /** proof manager */ + ProofNodeManager* d_pnm; + /** A proof generator for storing proofs of facts that are asserted to the EQ + * engine. Note that these proofs **are not closed**; they may contain + * literals from the explanation of their fact as unclosed assumptions. + * This makes these proofs SAT-context depdent. + * + * This is why this generator is separate from the TheoryArithPrivate + * generator, which stores closed proofs. + */ + std::unique_ptr d_pfGenEe; + /** A proof generator for TrustNodes sent to TheoryArithPrivate. + * + * When TheoryArithPrivate requests an explanation from + * ArithCongruenceManager, it can request a TrustNode for that explanation. + * This proof generator is the one used in that TrustNode: it stores the + * (closed) proofs of implications proved by the + * ArithCongruenceManager/EqualityEngine. + * + * It is insufficient to just use the ProofGenerator from the ProofEqEngine, + * since sometimes the ArithCongruenceManager needs to add some + * arith-specific reasoning to extend the proof (e.g. rewriting the result + * into a normal form). + * */ + std::unique_ptr d_pfGenExplain; + + /** Pointer to the proof equality engine of TheoryArith */ + theory::eq::ProofEqEngine* d_pfee; + /** The proof equality engine we allocated */ + std::unique_ptr d_allocPfee; + + /** Raise a conflict node `conflict` to the theory of arithmetic. + * + * When proofs are enabled, a (closed) proof of the conflict should be + * provided. + */ + void raiseConflict(Node conflict, std::shared_ptr pf = nullptr); + /** + * Are proofs enabled? This is true if a non-null proof manager was provided + * to the constructor of this class. + */ + bool isProofEnabled() const; + + public: + bool inConflict() const; + + bool hasMorePropagations() const; + + const Node getNextPropagation(); + + bool canExplain(TNode n) const; + +private: + Node externalToInternal(TNode n) const; + + void pushBack(TNode n); + + void pushBack(TNode n, TNode r); + + void pushBack(TNode n, TNode r, TNode w); + + bool propagate(TNode x); + void explain(TNode literal, std::vector& assumptions); + + /** Assert this literal to the eq engine. Common functionality for + * * assertionToEqualityEngine(..) + * * equalsConstant(c) + * * equalsConstant(lb, ub) + * If proof is off, then just asserts. + */ + void assertLitToEqualityEngine(Node lit, + TNode reason, + std::shared_ptr pf); + /** This sends a shared term to the uninterpreted equality engine. */ + void assertionToEqualityEngine(bool eq, + ArithVar s, + TNode reason, + std::shared_ptr pf); + + /** Check for proof for this or a symmetric fact + * + * The proof submitted to this method are stored in `d_pfGenEe`, and should + * have closure properties consistent with the documentation for that member. + * + * @returns whether this or a symmetric fact has a proof. + */ + bool hasProofFor(TNode f) const; + /** + * Sets the proof for this fact and the symmetric one. + * + * The proof submitted to this method are stored in `d_pfGenEe`, and should + * have closure properties consistent with the documentation for that member. + * */ + void setProofFor(TNode f, std::shared_ptr pf) const; + + /** Dequeues the delay queue and asserts these equalities.*/ + void enableSharedTerms(); + void dequeueLiterals(); + + void enqueueIntoNB(const std::set all, NodeBuilder& nb); + + /** + * Determine an explaination for `internal`. That is a conjunction of theory + * literals which imply `internal`. + * + * The TrustNode here is a trusted propagation. + */ + TrustNode explainInternal(TNode internal); + + public: + ArithCongruenceManager(Env& env, + ConstraintDatabase&, + SetupLiteralCallBack, + const ArithVariables&, + RaiseEqualityEngineConflict raiseConflict); + ~ArithCongruenceManager(); + + //--------------------------------- initialization + /** + * Returns true if we need an equality engine, see + * Theory::needsEqualityEngine. + */ + bool needsEqualityEngine(EeSetupInfo& esi); + /** + * Finish initialize. This class is instructed by TheoryArithPrivate to use + * the equality engine ee. + */ + void finishInit(eq::EqualityEngine* ee); + //--------------------------------- end initialization + + /** + * Return the trust node for the explanation of literal. The returned + * trust node is generated by the proof equality engine of this class. + */ + TrustNode explain(TNode literal); + + void explain(TNode lit, NodeBuilder& out); + + void addWatchedPair(ArithVar s, TNode x, TNode y); + + inline bool isWatchedVariable(ArithVar s) const { + return d_watchedVariables.isMember(s); + } + + /** Assert an equality. */ + void watchedVariableIsZero(ConstraintCP eq); + + /** Assert a conjunction from lb and ub. */ + void watchedVariableIsZero(ConstraintCP lb, ConstraintCP ub); + + /** Assert that the value cannot be zero. */ + void watchedVariableCannotBeZero(ConstraintCP c); + + /** Assert that the value cannot be zero. */ + void watchedVariableCannotBeZero(ConstraintCP c, ConstraintCP d); + + + /** Assert that the value is congruent to a constant. */ + void equalsConstant(ConstraintCP eq); + void equalsConstant(ConstraintCP lb, ConstraintCP ub); + + private: + class Statistics { + public: + IntStat d_watchedVariables; + IntStat d_watchedVariableIsZero; + IntStat d_watchedVariableIsNotZero; + + IntStat d_equalsConstantCalls; + + IntStat d_propagations; + IntStat d_propagateConstraints; + IntStat d_conflicts; + + Statistics(); + } d_statistics; + +}; /* class ArithCongruenceManager */ + +std::vector andComponents(TNode an); + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/constraint.cpp b/src/theory/arith/linear/constraint.cpp new file mode 100644 index 000000000..9ed758f98 --- /dev/null +++ b/src/theory/arith/linear/constraint.cpp @@ -0,0 +1,2463 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Alex Ozdemir, Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ +#include "theory/arith/linear/constraint.h" + +#include +#include +#include + +#include "base/output.h" +#include "options/smt_options.h" +#include "proof/eager_proof_generator.h" +#include "proof/proof_node_manager.h" +#include "smt/env.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/linear/congruence_manager.h" +#include "theory/arith/linear/normal_form.h" +#include "theory/arith/linear/partial_model.h" +#include "theory/builtin/proof_checker.h" +#include "theory/rewriter.h" + +using namespace std; +using namespace cvc5::internal::kind; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +ConstraintRule::ConstraintRule() + : d_constraint(NullConstraint), + d_proofType(NoAP), + d_antecedentEnd(AntecedentIdSentinel) +{ + d_farkasCoefficients = RationalVectorCPSentinel; +} + +ConstraintRule::ConstraintRule(ConstraintP con, ArithProofType pt) + : d_constraint(con), d_proofType(pt), d_antecedentEnd(AntecedentIdSentinel) +{ + d_farkasCoefficients = RationalVectorCPSentinel; +} +ConstraintRule::ConstraintRule(ConstraintP con, + ArithProofType pt, + AntecedentId antecedentEnd) + : d_constraint(con), d_proofType(pt), d_antecedentEnd(antecedentEnd) +{ + d_farkasCoefficients = RationalVectorCPSentinel; +} + +ConstraintRule::ConstraintRule(ConstraintP con, + ArithProofType pt, + AntecedentId antecedentEnd, + RationalVectorCP coeffs) + : d_constraint(con), d_proofType(pt), d_antecedentEnd(antecedentEnd) +{ + Assert(con->isProofProducing() || coeffs == RationalVectorCPSentinel); + d_farkasCoefficients = coeffs; +} + +/** Given a simplifiedKind this returns the corresponding ConstraintType. */ +//ConstraintType constraintTypeOfLiteral(Kind k); +ConstraintType Constraint::constraintTypeOfComparison(const Comparison& cmp){ + Kind k = cmp.comparisonKind(); + switch(k){ + case LT: + case LEQ: + { + Polynomial l = cmp.getLeft(); + if(l.leadingCoefficientIsPositive()){ // (< x c) + return UpperBound; + }else{ + return LowerBound; // (< (-x) c) + } + } + case GT: + case GEQ: + { + Polynomial l = cmp.getLeft(); + if(l.leadingCoefficientIsPositive()){ + return LowerBound; // (> x c) + }else{ + return UpperBound; // (> (-x) c) + } + } + case EQUAL: + return Equality; + case DISTINCT: + return Disequality; + default: Unhandled() << k; + } +} + +Constraint::Constraint(ArithVar x, + ConstraintType t, + const DeltaRational& v, + bool produceProofs) + : d_variable(x), + d_type(t), + d_value(v), + d_database(NULL), + d_literal(Node::null()), + d_negation(NullConstraint), + d_canBePropagated(false), + d_assertionOrder(AssertionOrderSentinel), + d_witness(TNode::null()), + d_crid(ConstraintRuleIdSentinel), + d_split(false), + d_variablePosition(), + d_produceProofs(produceProofs) +{ + Assert(!initialized()); +} + + +std::ostream& operator<<(std::ostream& o, const ArithProofType apt){ + switch(apt){ + case NoAP: o << "NoAP"; break; + case AssumeAP: o << "AssumeAP"; break; + case InternalAssumeAP: o << "InternalAssumeAP"; break; + case FarkasAP: o << "FarkasAP"; break; + case TrichotomyAP: o << "TrichotomyAP"; break; + case EqualityEngineAP: o << "EqualityEngineAP"; break; + case IntTightenAP: o << "IntTightenAP"; break; + case IntHoleAP: o << "IntHoleAP"; break; + default: break; + } + return o; +} + +std::ostream& operator<<(std::ostream& o, const ConstraintCP c){ + if(c == NullConstraint){ + return o << "NullConstraint"; + }else{ + return o << *c; + } +} + +std::ostream& operator<<(std::ostream& o, const ConstraintP c){ + if(c == NullConstraint){ + return o << "NullConstraint"; + }else{ + return o << *c; + } +} + +std::ostream& operator<<(std::ostream& o, const ConstraintType t){ + switch(t){ + case LowerBound: + return o << ">="; + case UpperBound: + return o << "<="; + case Equality: + return o << "="; + case Disequality: + return o << "!="; + default: + Unreachable(); + } +} + +std::ostream& operator<<(std::ostream& o, const Constraint& c){ + o << c.getVariable() << ' ' << c.getType() << ' ' << c.getValue(); + if(c.hasLiteral()){ + o << "(node " << c.getLiteral() << ')'; + } + return o; +} + +std::ostream& operator<<(std::ostream& o, const ValueCollection& vc){ + o << "{"; + bool pending = false; + if(vc.hasEquality()){ + o << "eq: " << vc.getEquality(); + pending = true; + } + if(vc.hasLowerBound()){ + if(pending){ + o << ", "; + } + o << "lb: " << vc.getLowerBound(); + pending = true; + } + if(vc.hasUpperBound()){ + if(pending){ + o << ", "; + } + o << "ub: " << vc.getUpperBound(); + pending = true; + } + if(vc.hasDisequality()){ + if(pending){ + o << ", "; + } + o << "de: " << vc.getDisequality(); + } + return o << "}"; +} + +std::ostream& operator<<(std::ostream& o, const ConstraintCPVec& v){ + o << "[" << v.size() << "x"; + ConstraintCPVec::const_iterator i, end; + for(i=v.begin(), end=v.end(); i != end; ++i){ + ConstraintCP c = *i; + o << ", " << (*c); + } + o << "]"; + return o; +} + +ValueCollection::ValueCollection() + : d_lowerBound(NullConstraint), + d_upperBound(NullConstraint), + d_equality(NullConstraint), + d_disequality(NullConstraint) +{} + +bool ValueCollection::hasLowerBound() const{ + return d_lowerBound != NullConstraint; +} + +bool ValueCollection::hasUpperBound() const{ + return d_upperBound != NullConstraint; +} + +bool ValueCollection::hasEquality() const{ + return d_equality != NullConstraint; +} + +bool ValueCollection::hasDisequality() const { + return d_disequality != NullConstraint; +} + +ConstraintP ValueCollection::getLowerBound() const { + Assert(hasLowerBound()); + return d_lowerBound; +} + +ConstraintP ValueCollection::getUpperBound() const { + Assert(hasUpperBound()); + return d_upperBound; +} + +ConstraintP ValueCollection::getEquality() const { + Assert(hasEquality()); + return d_equality; +} + +ConstraintP ValueCollection::getDisequality() const { + Assert(hasDisequality()); + return d_disequality; +} + + +void ValueCollection::push_into(std::vector& vec) const { + Trace("arith::constraint") << "push_into " << *this << endl; + if(hasEquality()){ + vec.push_back(d_equality); + } + if(hasLowerBound()){ + vec.push_back(d_lowerBound); + } + if(hasUpperBound()){ + vec.push_back(d_upperBound); + } + if(hasDisequality()){ + vec.push_back(d_disequality); + } +} + +ValueCollection ValueCollection::mkFromConstraint(ConstraintP c){ + ValueCollection ret; + Assert(ret.empty()); + switch(c->getType()){ + case LowerBound: + ret.d_lowerBound = c; + break; + case UpperBound: + ret.d_upperBound = c; + break; + case Equality: + ret.d_equality = c; + break; + case Disequality: + ret.d_disequality = c; + break; + default: + Unreachable(); + } + return ret; +} + +bool ValueCollection::hasConstraintOfType(ConstraintType t) const{ + switch(t){ + case LowerBound: + return hasLowerBound(); + case UpperBound: + return hasUpperBound(); + case Equality: + return hasEquality(); + case Disequality: + return hasDisequality(); + default: + Unreachable(); + } +} + +ArithVar ValueCollection::getVariable() const{ + Assert(!empty()); + return nonNull()->getVariable(); +} + +const DeltaRational& ValueCollection::getValue() const{ + Assert(!empty()); + return nonNull()->getValue(); +} + +void ValueCollection::add(ConstraintP c){ + Assert(c != NullConstraint); + + Assert(empty() || getVariable() == c->getVariable()); + Assert(empty() || getValue() == c->getValue()); + + switch(c->getType()){ + case LowerBound: + Assert(!hasLowerBound()); + d_lowerBound = c; + break; + case Equality: + Assert(!hasEquality()); + d_equality = c; + break; + case UpperBound: + Assert(!hasUpperBound()); + d_upperBound = c; + break; + case Disequality: + Assert(!hasDisequality()); + d_disequality = c; + break; + default: + Unreachable(); + } +} + +ConstraintP ValueCollection::getConstraintOfType(ConstraintType t) const{ + switch(t){ + case LowerBound: Assert(hasLowerBound()); return d_lowerBound; + case Equality: Assert(hasEquality()); return d_equality; + case UpperBound: Assert(hasUpperBound()); return d_upperBound; + case Disequality: Assert(hasDisequality()); return d_disequality; + default: Unreachable(); + } +} + +void ValueCollection::remove(ConstraintType t){ + switch(t){ + case LowerBound: + Assert(hasLowerBound()); + d_lowerBound = NullConstraint; + break; + case Equality: + Assert(hasEquality()); + d_equality = NullConstraint; + break; + case UpperBound: + Assert(hasUpperBound()); + d_upperBound = NullConstraint; + break; + case Disequality: + Assert(hasDisequality()); + d_disequality = NullConstraint; + break; + default: + Unreachable(); + } +} + +bool ValueCollection::empty() const{ + return + !(hasLowerBound() || + hasUpperBound() || + hasEquality() || + hasDisequality()); +} + +ConstraintP ValueCollection::nonNull() const{ + //This can be optimized by caching, but this is not necessary yet! + /* "Premature optimization is the root of all evil." */ + if(hasLowerBound()){ + return d_lowerBound; + }else if(hasUpperBound()){ + return d_upperBound; + }else if(hasEquality()){ + return d_equality; + }else if(hasDisequality()){ + return d_disequality; + }else{ + return NullConstraint; + } +} + +bool Constraint::initialized() const { + return d_database != NULL; +} + +const ConstraintDatabase& Constraint::getDatabase() const{ + Assert(initialized()); + return *d_database; +} + +void Constraint::initialize(ConstraintDatabase* db, SortedConstraintMapIterator v, ConstraintP negation){ + Assert(!initialized()); + d_database = db; + d_variablePosition = v; + d_negation = negation; +} + +Constraint::~Constraint() { + // Call this instead of safeToGarbageCollect() + Assert(!contextDependentDataIsSet()); + + if(initialized()){ + ValueCollection& vc = d_variablePosition->second; + Trace("arith::constraint") << "removing" << vc << endl; + + vc.remove(getType()); + + if(vc.empty()){ + Trace("arith::constraint") << "erasing" << vc << endl; + SortedConstraintMap& perVariable = d_database->getVariableSCM(getVariable()); + perVariable.erase(d_variablePosition); + } + + if(hasLiteral()){ + d_database->d_nodetoConstraintMap.erase(getLiteral()); + } + } +} + +const ConstraintRule& Constraint::getConstraintRule() const { + Assert(hasProof()); + return d_database->d_watches->d_constraintProofs[d_crid]; +} + +const ValueCollection& Constraint::getValueCollection() const{ + return d_variablePosition->second; +} + + +ConstraintP Constraint::getCeiling() { + Trace("getCeiling") << "Constraint_::getCeiling on " << *this << endl; + Assert(getValue().getInfinitesimalPart().sgn() > 0); + + const DeltaRational ceiling(getValue().ceiling()); + return d_database->getConstraint(getVariable(), getType(), ceiling); +} + +ConstraintP Constraint::getFloor() { + Assert(getValue().getInfinitesimalPart().sgn() < 0); + + const DeltaRational floor(Rational(getValue().floor())); + return d_database->getConstraint(getVariable(), getType(), floor); +} + +void Constraint::setCanBePropagated() { + Assert(!canBePropagated()); + d_database->pushCanBePropagatedWatch(this); +} + +void Constraint::setAssertedToTheTheory(TNode witness, bool nowInConflict) { + Assert(hasLiteral()); + Assert(!assertedToTheTheory()); + Assert(negationHasProof() == nowInConflict); + d_database->pushAssertionOrderWatch(this, witness); + + if(TraceIsOn("constraint::conflictCommit") && nowInConflict ){ + Trace("constraint::conflictCommit") << "inConflict@setAssertedToTheTheory"; + Trace("constraint::conflictCommit") << "\t" << this << std::endl; + Trace("constraint::conflictCommit") << "\t" << getNegation() << std::endl; + Trace("constraint::conflictCommit") << "\t" << getNegation()->externalExplainByAssertions() << std::endl; + + } +} + +bool Constraint::satisfiedBy(const DeltaRational& dr) const { + switch(getType()){ + case LowerBound: + return getValue() <= dr; + case Equality: + return getValue() == dr; + case UpperBound: + return getValue() >= dr; + case Disequality: + return getValue() != dr; + } + Unreachable(); +} + +bool Constraint::isInternalAssumption() const { + return getProofType() == InternalAssumeAP; +} + +TrustNode Constraint::externalExplainByAssertions() const +{ + NodeBuilder nb(kind::AND); + auto pfFromAssumptions = externalExplain(nb, AssertionOrderSentinel); + Node exp = mkAndFromBuilder(nb); + if (d_database->isProofEnabled()) + { + std::vector assumptions; + if (exp.getKind() == Kind::AND) + { + assumptions.insert(assumptions.end(), exp.begin(), exp.end()); + } + else + { + assumptions.push_back(exp); + } + auto pf = d_database->d_pnm->mkScope(pfFromAssumptions, assumptions); + return d_database->d_pfGen->mkTrustedPropagation( + getLiteral(), NodeManager::currentNM()->mkAnd(assumptions), pf); + } + return TrustNode::mkTrustPropExp(getLiteral(), exp); +} + +bool Constraint::isAssumption() const { + return getProofType() == AssumeAP; +} + +bool Constraint::hasEqualityEngineProof() const { + return getProofType() == EqualityEngineAP; +} + +bool Constraint::hasFarkasProof() const { + return getProofType() == FarkasAP; +} + +bool Constraint::hasSimpleFarkasProof() const +{ + Trace("constraints::hsfp") << "hasSimpleFarkasProof " << this << std::endl; + if (!hasFarkasProof()) + { + Trace("constraints::hsfp") << "There is no simple Farkas proof because " + "there is no farkas proof." + << std::endl; + return false; + } + + // For each antecdent ... + AntecedentId i = getConstraintRule().d_antecedentEnd; + for (ConstraintCP a = d_database->getAntecedent(i); a != NullConstraint; + a = d_database->getAntecedent(--i)) + { + // ... that antecdent must be an assumption OR a tightened assumption ... + if (a->isPossiblyTightenedAssumption()) + { + continue; + } + + // ... otherwise, we do not have a simple Farkas proof. + if (TraceIsOn("constraints::hsfp")) + { + Trace("constraints::hsfp") << "There is no simple Farkas proof b/c there " + "is an antecdent w/ rule "; + a->getConstraintRule().print(Trace("constraints::hsfp"), d_produceProofs); + Trace("constraints::hsfp") << std::endl; + } + + return false; + } + return true; +} + +bool Constraint::isPossiblyTightenedAssumption() const +{ + // ... that antecdent must be an assumption ... + + if (isAssumption()) return true; + if (!hasIntTightenProof()) return false; + if (getConstraintRule().d_antecedentEnd == AntecedentIdSentinel) return false; + return d_database->getAntecedent(getConstraintRule().d_antecedentEnd) + ->isAssumption(); +} + +bool Constraint::hasIntTightenProof() const { + return getProofType() == IntTightenAP; +} + +bool Constraint::hasIntHoleProof() const { + return getProofType() == IntHoleAP; +} + +bool Constraint::hasTrichotomyProof() const { + return getProofType() == TrichotomyAP; +} + +void Constraint::printProofTree(std::ostream& out, size_t depth) const +{ + if (d_produceProofs) + { + const ConstraintRule& rule = getConstraintRule(); + out << std::string(2 * depth, ' ') << "* " << getVariable() << " ["; + out << getProofLiteral(); + if (assertedToTheTheory()) + { + out << " | wit: " << getWitness(); + } + out << "]" << ' ' << getType() << ' ' << getValue() << " (" + << getProofType() << ")"; + if (getProofType() == FarkasAP) + { + out << " ["; + bool first = true; + for (const auto& coeff : *rule.d_farkasCoefficients) + { + if (not first) + { + out << ", "; + } + first = false; + out << coeff; + } + out << "]"; + } + out << endl; + + for (AntecedentId i = rule.d_antecedentEnd; i != AntecedentIdSentinel; --i) + { + ConstraintCP antecdent = d_database->getAntecedent(i); + if (antecdent == NullConstraint) + { + break; + } + antecdent->printProofTree(out, depth + 1); + } + return; + } + out << "Cannot print proof. This is not a proof build." << endl; +} + +bool Constraint::sanityChecking(Node n) const { + Comparison cmp = Comparison::parseNormalForm(n); + Kind k = cmp.comparisonKind(); + Polynomial pleft = cmp.normalizedVariablePart(); + Assert(k == EQUAL || k == DISTINCT || pleft.leadingCoefficientIsPositive()); + Assert(k != EQUAL || Monomial::isMember(n[0])); + Assert(k != DISTINCT || Monomial::isMember(n[0][0])); + + TNode left = pleft.getNode(); + DeltaRational right = cmp.normalizedDeltaRational(); + + const ArithVariables& avariables = d_database->getArithVariables(); + + Trace("Constraint::sanityChecking") << cmp.getNode() << endl; + Trace("Constraint::sanityChecking") << k << endl; + Trace("Constraint::sanityChecking") << pleft.getNode() << endl; + Trace("Constraint::sanityChecking") << left << endl; + Trace("Constraint::sanityChecking") << right << endl; + Trace("Constraint::sanityChecking") << getValue() << endl; + Trace("Constraint::sanityChecking") << avariables.hasArithVar(left) << endl; + Trace("Constraint::sanityChecking") << avariables.asArithVar(left) << endl; + Trace("Constraint::sanityChecking") << getVariable() << endl; + + + if(avariables.hasArithVar(left) && + avariables.asArithVar(left) == getVariable() && + getValue() == right){ + switch(getType()){ + case LowerBound: + case UpperBound: + //Be overapproximate + return k == GT || k == GEQ ||k == LT || k == LEQ; + case Equality: + return k == EQUAL; + case Disequality: + return k == DISTINCT; + default: + Unreachable(); + } + }else{ + return false; + } +} + +ConstraintCP ConstraintDatabase::getAntecedent (AntecedentId p) const { + Assert(p < d_antecedents.size()); + return d_antecedents[p]; +} + +void ConstraintRule::print(std::ostream& out, bool produceProofs) const +{ + RationalVectorCP coeffs = produceProofs ? d_farkasCoefficients : nullptr; + out << "{ConstraintRule, "; + out << d_constraint << std::endl; + out << "d_proofType= " << d_proofType << ", " << std::endl; + out << "d_antecedentEnd= "<< d_antecedentEnd << std::endl; + + if (d_constraint != NullConstraint && d_antecedentEnd != AntecedentIdSentinel) + { + const ConstraintDatabase& database = d_constraint->getDatabase(); + + size_t coeffIterator = (coeffs != RationalVectorCPSentinel) ? coeffs->size()-1 : 0; + AntecedentId p = d_antecedentEnd; + // must have at least one antecedent + ConstraintCP antecedent = database.getAntecedent(p); + while(antecedent != NullConstraint){ + if(coeffs != RationalVectorCPSentinel){ + out << coeffs->at(coeffIterator); + } else { + out << "_"; + } + out << " * (" << *antecedent << ")" << std::endl; + + Assert((coeffs == RationalVectorCPSentinel) || coeffIterator > 0); + --p; + coeffIterator = (coeffs != RationalVectorCPSentinel) ? coeffIterator-1 : 0; + antecedent = database.getAntecedent(p); + } + if(coeffs != RationalVectorCPSentinel){ + out << coeffs->front(); + } else { + out << "_"; + } + out << " * (" << *(d_constraint->getNegation()) << ")"; + out << " [not d_constraint] " << endl; + } + out << "}"; +} + +bool Constraint::wellFormedFarkasProof() const { + Assert(hasProof()); + + const ConstraintRule& cr = getConstraintRule(); + if(cr.d_constraint != this){ return false; } + if(cr.d_proofType != FarkasAP){ return false; } + + AntecedentId p = cr.d_antecedentEnd; + + // must have at least one antecedent + ConstraintCP antecedent = d_database->d_antecedents[p]; + if(antecedent == NullConstraint) { return false; } + + if (!d_produceProofs) + { + return cr.d_farkasCoefficients == RationalVectorCPSentinel; + } + Assert(d_produceProofs); + + if(cr.d_farkasCoefficients == RationalVectorCPSentinel){ return false; } + if(cr.d_farkasCoefficients->size() < 2){ return false; } + + const ArithVariables& vars = d_database->getArithVariables(); + + DeltaRational rhs(0); + Node lhs = Polynomial::mkZero().getNode(); + + RationalVector::const_iterator coeffIterator = cr.d_farkasCoefficients->end()-1; + RationalVector::const_iterator coeffBegin = cr.d_farkasCoefficients->begin(); + + while(antecedent != NullConstraint){ + Assert(lhs.isNull() || Polynomial::isMember(lhs)); + + const Rational& coeff = *coeffIterator; + int coeffSgn = coeff.sgn(); + + rhs += antecedent->getValue() * coeff; + + ArithVar antVar = antecedent->getVariable(); + if(!lhs.isNull() && vars.hasNode(antVar)){ + Node antAsNode = vars.asNode(antVar); + if(Polynomial::isMember(antAsNode)){ + Polynomial lhsPoly = Polynomial::parsePolynomial(lhs); + Polynomial antPoly = Polynomial::parsePolynomial(antAsNode); + Polynomial sum = lhsPoly + (antPoly * coeff); + lhs = sum.getNode(); + }else{ + lhs = Node::null(); + } + } else { + lhs = Node::null(); + } + Trace("constraints::wffp") << "running sum: " << lhs << " <= " << rhs << endl; + + switch( antecedent->getType() ){ + case LowerBound: + // fc[l] < 0, therefore return false if coeffSgn >= 0 + if(coeffSgn >= 0){ return false; } + break; + case UpperBound: + // fc[u] > 0, therefore return false if coeffSgn <= 0 + if(coeffSgn <= 0){ return false; } + break; + case Equality: + if(coeffSgn == 0) { return false; } + break; + case Disequality: + default: + return false; + } + + if(coeffIterator == coeffBegin){ return false; } + --coeffIterator; + --p; + antecedent = d_database->d_antecedents[p]; + } + if(coeffIterator != coeffBegin){ return false; } + + const Rational& firstCoeff = (*coeffBegin); + int firstCoeffSgn = firstCoeff.sgn(); + rhs += (getNegation()->getValue()) * firstCoeff; + if(!lhs.isNull() && vars.hasNode(getVariable())){ + Node firstAsNode = vars.asNode(getVariable()); + if(Polynomial::isMember(firstAsNode)){ + Polynomial lhsPoly = Polynomial::parsePolynomial(lhs); + Polynomial firstPoly = Polynomial::parsePolynomial(firstAsNode); + Polynomial sum = lhsPoly + (firstPoly * firstCoeff); + lhs = sum.getNode(); + }else{ + lhs = Node::null(); + } + }else{ + lhs = Node::null(); + } + + switch( getNegation()->getType() ){ + case LowerBound: + // fc[l] < 0, therefore return false if coeffSgn >= 0 + if(firstCoeffSgn >= 0){ return false; } + break; + case UpperBound: + // fc[u] > 0, therefore return false if coeffSgn <= 0 + if(firstCoeffSgn <= 0){ return false; } + break; + case Equality: + if(firstCoeffSgn == 0) { return false; } + break; + case Disequality: + default: + return false; + } + Trace("constraints::wffp") << "final sum: " << lhs << " <= " << rhs << endl; + // 0 = lhs <= rhs < 0 + return (lhs.isNull() || (Constant::isMember(lhs) && Constant(lhs).isZero())) + && rhs.sgn() < 0; +} + +ConstraintP Constraint::makeNegation(ArithVar v, + ConstraintType t, + const DeltaRational& r, + bool produceProofs) +{ + switch(t){ + case LowerBound: + { + Assert(r.infinitesimalSgn() >= 0); + if(r.infinitesimalSgn() > 0){ + Assert(r.getInfinitesimalPart() == 1); + // make (not (v > r)), which is (v <= r) + DeltaRational dropInf(r.getNoninfinitesimalPart(), 0); + return new Constraint(v, UpperBound, dropInf, produceProofs); + }else{ + Assert(r.infinitesimalSgn() == 0); + // make (not (v >= r)), which is (v < r) + DeltaRational addInf(r.getNoninfinitesimalPart(), -1); + return new Constraint(v, UpperBound, addInf, produceProofs); + } + } + case UpperBound: + { + Assert(r.infinitesimalSgn() <= 0); + if(r.infinitesimalSgn() < 0){ + Assert(r.getInfinitesimalPart() == -1); + // make (not (v < r)), which is (v >= r) + DeltaRational dropInf(r.getNoninfinitesimalPart(), 0); + return new Constraint(v, LowerBound, dropInf, produceProofs); + }else{ + Assert(r.infinitesimalSgn() == 0); + // make (not (v <= r)), which is (v > r) + DeltaRational addInf(r.getNoninfinitesimalPart(), 1); + return new Constraint(v, LowerBound, addInf, produceProofs); + } + } + case Equality: return new Constraint(v, Disequality, r, produceProofs); + case Disequality: return new Constraint(v, Equality, r, produceProofs); + default: Unreachable(); return NullConstraint; + } +} + +ConstraintDatabase::ConstraintDatabase(Env& env, + const ArithVariables& avars, + ArithCongruenceManager& cm, + RaiseConflict raiseConflict, + EagerProofGenerator* pfGen) + : EnvObj(env), + d_varDatabases(), + d_toPropagate(context()), + d_antecedents(context(), false), + d_watches(new Watches(context(), userContext())), + d_avariables(avars), + d_congruenceManager(cm), + d_pfGen(pfGen), + d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager() + : nullptr), + d_raiseConflict(raiseConflict), + d_one(1), + d_negOne(-1) +{ +} + +SortedConstraintMap& ConstraintDatabase::getVariableSCM(ArithVar v) const{ + Assert(variableDatabaseIsSetup(v)); + return d_varDatabases[v]->d_constraints; +} + +void ConstraintDatabase::pushSplitWatch(ConstraintP c){ + Assert(!c->d_split); + c->d_split = true; + d_watches->d_splitWatches.push_back(c); +} + + +void ConstraintDatabase::pushCanBePropagatedWatch(ConstraintP c){ + Assert(!c->d_canBePropagated); + c->d_canBePropagated = true; + d_watches->d_canBePropagatedWatches.push_back(c); +} + +void ConstraintDatabase::pushAssertionOrderWatch(ConstraintP c, TNode witness){ + Assert(!c->assertedToTheTheory()); + c->d_assertionOrder = d_watches->d_assertionOrderWatches.size(); + c->d_witness = witness; + d_watches->d_assertionOrderWatches.push_back(c); +} + + +void ConstraintDatabase::pushConstraintRule(const ConstraintRule& crp){ + ConstraintP c = crp.d_constraint; + Assert(c->d_crid == ConstraintRuleIdSentinel); + Assert(!c->hasProof()); + c->d_crid = d_watches->d_constraintProofs.size(); + d_watches->d_constraintProofs.push_back(crp); +} + +ConstraintP ConstraintDatabase::getConstraint(ArithVar v, ConstraintType t, const DeltaRational& r){ + //This must always return a constraint. + + SortedConstraintMap& scm = getVariableSCM(v); + pair insertAttempt; + insertAttempt = scm.insert(make_pair(r, ValueCollection())); + + SortedConstraintMapIterator pos = insertAttempt.first; + ValueCollection& vc = pos->second; + if(vc.hasConstraintOfType(t)){ + return vc.getConstraintOfType(t); + }else{ + ConstraintP c = new Constraint(v, t, r, options().smt.produceProofs); + ConstraintP negC = + Constraint::makeNegation(v, t, r, options().smt.produceProofs); + + SortedConstraintMapIterator negPos; + if(t == Equality || t == Disequality){ + negPos = pos; + }else{ + pair negInsertAttempt; + negInsertAttempt = scm.insert(make_pair(negC->getValue(), ValueCollection())); + Assert(negInsertAttempt.second + || !negInsertAttempt.first->second.hasConstraintOfType( + negC->getType())); + negPos = negInsertAttempt.first; + } + + c->initialize(this, pos, negC); + negC->initialize(this, negPos, c); + + vc.add(c); + negPos->second.add(negC); + + return c; + } +} + +ConstraintP ConstraintDatabase::ensureConstraint(ValueCollection& vc, ConstraintType t){ + if(vc.hasConstraintOfType(t)){ + return vc.getConstraintOfType(t); + }else{ + return getConstraint(vc.getVariable(), t, vc.getValue()); + } +} + +bool ConstraintDatabase::emptyDatabase(const std::vector& vec){ + std::vector::const_iterator first = vec.begin(); + std::vector::const_iterator last = vec.end(); + return std::find_if(first, last, PerVariableDatabase::IsEmpty) == last; +} + +ConstraintDatabase::~ConstraintDatabase(){ + delete d_watches; + + std::vector constraintList; + + while(!d_varDatabases.empty()){ + PerVariableDatabase* back = d_varDatabases.back(); + + SortedConstraintMap& scm = back->d_constraints; + SortedConstraintMapIterator i = scm.begin(), i_end = scm.end(); + for(; i != i_end; ++i){ + (i->second).push_into(constraintList); + } + while(!constraintList.empty()){ + ConstraintP c = constraintList.back(); + constraintList.pop_back(); + delete c; + } + Assert(scm.empty()); + d_varDatabases.pop_back(); + delete back; + } + + Assert(d_nodetoConstraintMap.empty()); +} + +ConstraintDatabase::Statistics::Statistics() + : d_unatePropagateCalls(smtStatisticsRegistry().registerInt( + "theory::arith::cd::unatePropagateCalls")), + d_unatePropagateImplications(smtStatisticsRegistry().registerInt( + "theory::arith::cd::unatePropagateImplications")) +{ +} + +void ConstraintDatabase::deleteConstraintAndNegation(ConstraintP c){ + Assert(c->safeToGarbageCollect()); + ConstraintP neg = c->getNegation(); + Assert(neg->safeToGarbageCollect()); + delete c; + delete neg; +} + +void ConstraintDatabase::addVariable(ArithVar v){ + if(d_reclaimable.isMember(v)){ + SortedConstraintMap& scm = getVariableSCM(v); + + std::vector constraintList; + + for(SortedConstraintMapIterator i = scm.begin(), end = scm.end(); i != end; ++i){ + (i->second).push_into(constraintList); + } + while(!constraintList.empty()){ + ConstraintP c = constraintList.back(); + constraintList.pop_back(); + Assert(c->safeToGarbageCollect()); + delete c; + } + Assert(scm.empty()); + + d_reclaimable.remove(v); + }else{ + Trace("arith::constraint") << "about to fail" << v << " " << d_varDatabases.size() << endl; + Assert(v == d_varDatabases.size()); + d_varDatabases.push_back(new PerVariableDatabase(v)); + } +} + +void ConstraintDatabase::removeVariable(ArithVar v){ + Assert(!d_reclaimable.isMember(v)); + d_reclaimable.add(v); +} + +bool Constraint::safeToGarbageCollect() const{ + // Do not call during destructor as getNegation() may be Null by this point + Assert(getNegation() != NullConstraint); + return !contextDependentDataIsSet() && ! getNegation()->contextDependentDataIsSet(); +} + +bool Constraint::contextDependentDataIsSet() const{ + return hasProof() || isSplit() || canBePropagated() || assertedToTheTheory(); +} + +TrustNode Constraint::split() +{ + Assert(isEquality() || isDisequality()); + + bool isEq = isEquality(); + + ConstraintP eq = isEq ? this : d_negation; + ConstraintP diseq = isEq ? d_negation : this; + + TNode eqNode = eq->getLiteral(); + Assert(eqNode.getKind() == kind::EQUAL); + TNode lhs = eqNode[0]; + TNode rhs = eqNode[1]; + + Node leqNode = NodeBuilder(kind::LEQ) << lhs << rhs; + Node ltNode = NodeBuilder(kind::LT) << lhs << rhs; + Node gtNode = NodeBuilder(kind::GT) << lhs << rhs; + Node geqNode = NodeBuilder(kind::GEQ) << lhs << rhs; + + Node lemma = NodeBuilder(OR) << leqNode << geqNode; + + TrustNode trustedLemma; + if (d_database->isProofEnabled()) + { + TypeNode type = lhs.getType(); + // Farkas proof that this works. + auto nm = NodeManager::currentNM(); + auto nLeqPf = d_database->d_pnm->mkAssume(leqNode.negate()); + auto gtPf = d_database->d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {nLeqPf}, {gtNode}); + auto nGeqPf = d_database->d_pnm->mkAssume(geqNode.negate()); + auto ltPf = d_database->d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {nGeqPf}, {ltNode}); + auto sumPf = + d_database->d_pnm->mkNode(PfRule::MACRO_ARITH_SCALE_SUM_UB, + {gtPf, ltPf}, + {nm->mkConstRealOrInt(type, Rational(-1)), + nm->mkConstRealOrInt(type, Rational(1))}); + auto botPf = d_database->d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); + std::vector a = {leqNode.negate(), geqNode.negate()}; + auto notAndNotPf = d_database->d_pnm->mkScope(botPf, a); + // No need to ensure that the expected node aggrees with `a` because we are + // not providing an expected node. + auto orNotNotPf = + d_database->d_pnm->mkNode(PfRule::NOT_AND, {notAndNotPf}, {}); + auto orPf = d_database->d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {orNotNotPf}, {lemma}); + trustedLemma = d_database->d_pfGen->mkTrustNode(lemma, orPf); + } + else + { + trustedLemma = TrustNode::mkTrustLemma(lemma); + } + + eq->d_database->pushSplitWatch(eq); + diseq->d_database->pushSplitWatch(diseq); + + return trustedLemma; +} + +bool ConstraintDatabase::hasLiteral(TNode literal) const { + return lookup(literal) != NullConstraint; +} + +ConstraintP ConstraintDatabase::addLiteral(TNode literal){ + Assert(!hasLiteral(literal)); + bool isNot = (literal.getKind() == NOT); + Node atomNode = (isNot ? literal[0] : literal); + Node negationNode = atomNode.notNode(); + + Assert(!hasLiteral(atomNode)); + Assert(!hasLiteral(negationNode)); + Comparison posCmp = Comparison::parseNormalForm(atomNode); + + ConstraintType posType = Constraint::constraintTypeOfComparison(posCmp); + + Polynomial nvp = posCmp.normalizedVariablePart(); + ArithVar v = d_avariables.asArithVar(nvp.getNode()); + + DeltaRational posDR = posCmp.normalizedDeltaRational(); + + ConstraintP posC = + new Constraint(v, posType, posDR, options().smt.produceProofs); + + Trace("arith::constraint") << "addliteral( literal ->" << literal << ")" << endl; + Trace("arith::constraint") << "addliteral( posC ->" << posC << ")" << endl; + + SortedConstraintMap& scm = getVariableSCM(posC->getVariable()); + pair insertAttempt; + insertAttempt = scm.insert(make_pair(posC->getValue(), ValueCollection())); + + SortedConstraintMapIterator posI = insertAttempt.first; + // If the attempt succeeds, i points to a new empty ValueCollection + // If the attempt fails, i points to a pre-existing ValueCollection + + if(posI->second.hasConstraintOfType(posC->getType())){ + //This is the situation where the ConstraintP exists, but + //the literal has not been associated with it. + ConstraintP hit = posI->second.getConstraintOfType(posC->getType()); + Trace("arith::constraint") << "hit " << hit << endl; + Trace("arith::constraint") << "posC " << posC << endl; + + delete posC; + + hit->setLiteral(atomNode); + hit->getNegation()->setLiteral(negationNode); + return isNot ? hit->getNegation(): hit; + }else{ + Comparison negCmp = Comparison::parseNormalForm(negationNode); + + ConstraintType negType = Constraint::constraintTypeOfComparison(negCmp); + DeltaRational negDR = negCmp.normalizedDeltaRational(); + + ConstraintP negC = + new Constraint(v, negType, negDR, options().smt.produceProofs); + + SortedConstraintMapIterator negI; + + if(posC->isEquality()){ + negI = posI; + }else{ + Assert(posC->isLowerBound() || posC->isUpperBound()); + + pair negInsertAttempt; + negInsertAttempt = scm.insert(make_pair(negC->getValue(), ValueCollection())); + + Trace("nf::tmp") << "sdhjfgdhjkldfgljkhdfg" << endl; + Trace("nf::tmp") << negC << endl; + Trace("nf::tmp") << negC->getValue() << endl; + + //This should always succeed as the DeltaRational for the negation is unique! + Assert(negInsertAttempt.second); + + negI = negInsertAttempt.first; + } + + (posI->second).add(posC); + (negI->second).add(negC); + + posC->initialize(this, posI, negC); + negC->initialize(this, negI, posC); + + posC->setLiteral(atomNode); + negC->setLiteral(negationNode); + + return isNot ? negC : posC; + } +} + + +ConstraintP ConstraintDatabase::lookup(TNode literal) const{ + NodetoConstraintMap::const_iterator iter = d_nodetoConstraintMap.find(literal); + if(iter == d_nodetoConstraintMap.end()){ + return NullConstraint; + }else{ + return iter->second; + } +} + +void Constraint::setAssumption(bool nowInConflict){ + Trace("constraints::pf") << "setAssumption(" << this << ")" << std::endl; + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(hasLiteral()); + Assert(assertedToTheTheory()); + + d_database->pushConstraintRule(ConstraintRule(this, AssumeAP)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict@setAssumption " << this << std::endl; + } +} + +void Constraint::tryToPropagate(){ + Assert(hasProof()); + Assert(!isAssumption()); + Assert(!isInternalAssumption()); + + if(canBePropagated() && !assertedToTheTheory() && !isAssumption() && !isInternalAssumption()){ + propagate(); + } +} + +void Constraint::propagate(){ + Assert(hasProof()); + Assert(canBePropagated()); + Assert(!assertedToTheTheory()); + Assert(!isAssumption()); + Assert(!isInternalAssumption()); + + d_database->d_toPropagate.push(this); +} + + +/* + * Example: + * x <= a and a < b + * |= x <= b + * --- + * 1*(x <= a) + (-1)*(x > b) => (0 <= a-b) + */ +void Constraint::impliedByUnate(ConstraintCP imp, bool nowInConflict){ + Trace("constraints::pf") << "impliedByUnate(" << this << ", " << *imp << ")" << std::endl; + Assert(!hasProof()); + Assert(imp->hasProof()); + Assert(negationHasProof() == nowInConflict); + + d_database->d_antecedents.push_back(NullConstraint); + d_database->d_antecedents.push_back(imp); + + AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; + + RationalVectorP coeffs; + if (d_produceProofs) + { + std::pair sgns = unateFarkasSigns(getNegation(), imp); + + Rational first(sgns.first); + Rational second(sgns.second); + + coeffs = new RationalVector(); + coeffs->push_back(first); + coeffs->push_back(second); + } + else + { + coeffs = RationalVectorPSentinel; + } + // no need to delete coeffs the memory is owned by ConstraintRule + d_database->pushConstraintRule(ConstraintRule(this, FarkasAP, antecedentEnd, coeffs)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict@impliedByUnate " << this << std::endl; + } + + if(TraceIsOn("constraints::wffp") && !wellFormedFarkasProof()){ + getConstraintRule().print(Trace("constraints::wffp"), d_produceProofs); + } + Assert(wellFormedFarkasProof()); +} + +void Constraint::impliedByTrichotomy(ConstraintCP a, ConstraintCP b, bool nowInConflict){ + Trace("constraints::pf") << "impliedByTrichotomy(" << this << ", " << *a << ", "; + Trace("constraints::pf") << *b << ")" << std::endl; + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(a->hasProof()); + Assert(b->hasProof()); + + d_database->d_antecedents.push_back(NullConstraint); + d_database->d_antecedents.push_back(a); + d_database->d_antecedents.push_back(b); + + AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; + d_database->pushConstraintRule(ConstraintRule(this, TrichotomyAP, antecedentEnd)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict@impliedByTrichotomy " << this << std::endl; + } +} + + +bool Constraint::allHaveProof(const ConstraintCPVec& b){ + for(ConstraintCPVec::const_iterator i=b.begin(), i_end=b.end(); i != i_end; ++i){ + ConstraintCP cp = *i; + if(! (cp->hasProof())){ return false; } + } + return true; +} + +void Constraint::impliedByIntTighten(ConstraintCP a, bool nowInConflict){ + Trace("constraints::pf") << "impliedByIntTighten(" << this << ", " << *a << ")" << std::endl; + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(a->hasProof()); + Trace("pf::arith") << "impliedByIntTighten(" << this << ", " << a << ")" + << std::endl; + + d_database->d_antecedents.push_back(NullConstraint); + d_database->d_antecedents.push_back(a); + AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; + d_database->pushConstraintRule(ConstraintRule(this, IntTightenAP, antecedentEnd)); + + Assert(inConflict() == nowInConflict); + if(inConflict()){ + Trace("constraint::conflictCommit") << "inConflict impliedByIntTighten" << this << std::endl; + } +} + +void Constraint::impliedByIntHole(ConstraintCP a, bool nowInConflict){ + Trace("constraints::pf") << "impliedByIntHole(" << this << ", " << *a << ")" << std::endl; + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(a->hasProof()); + Trace("pf::arith") << "impliedByIntHole(" << this << ", " << a << ")" + << std::endl; + + d_database->d_antecedents.push_back(NullConstraint); + d_database->d_antecedents.push_back(a); + AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; + d_database->pushConstraintRule(ConstraintRule(this, IntHoleAP, antecedentEnd)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict impliedByIntHole" << this << std::endl; + } +} + +void Constraint::impliedByIntHole(const ConstraintCPVec& b, bool nowInConflict){ + Trace("constraints::pf") << "impliedByIntHole(" << this; + if (TraceIsOn("constraints::pf")) { + for (const ConstraintCP& p : b) + { + Trace("constraints::pf") << ", " << p; + } + } + Trace("constraints::pf") << ")" << std::endl; + + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(allHaveProof(b)); + + CDConstraintList& antecedents = d_database->d_antecedents; + antecedents.push_back(NullConstraint); + for(ConstraintCPVec::const_iterator i=b.begin(), i_end=b.end(); i != i_end; ++i){ + antecedents.push_back(*i); + } + AntecedentId antecedentEnd = antecedents.size() - 1; + + d_database->pushConstraintRule(ConstraintRule(this, IntHoleAP, antecedentEnd)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict@impliedByIntHole[vec] " << this << std::endl; + } +} + +/* + * If proofs are off, coeffs == RationalVectorSentinal. + * If proofs are on, + * coeffs != RationalVectorSentinal, + * coeffs->size() = a.size() + 1, + * for i in [0,a.size) : coeff[i] corresponds to a[i], and + * coeff.back() corresponds to the current constraint. + */ +void Constraint::impliedByFarkas(const ConstraintCPVec& a, RationalVectorCP coeffs, bool nowInConflict){ + Trace("constraints::pf") << "impliedByFarkas(" << this; + if (TraceIsOn("constraints::pf")) { + for (const ConstraintCP& p : a) + { + Trace("constraints::pf") << ", " << p; + } + } + Trace("constraints::pf") << ", "; + Trace("constraints::pf") << ")" << std::endl; + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(allHaveProof(a)); + + Assert(d_produceProofs == (coeffs != RationalVectorCPSentinel)); + Assert(!d_produceProofs || coeffs->size() == a.size() + 1); + + Assert(a.size() >= 1); + + d_database->d_antecedents.push_back(NullConstraint); + for(ConstraintCPVec::const_iterator i = a.begin(), end = a.end(); i != end; ++i){ + ConstraintCP c_i = *i; + Assert(c_i->hasProof()); + d_database->d_antecedents.push_back(c_i); + } + AntecedentId antecedentEnd = d_database->d_antecedents.size() - 1; + + RationalVectorCP coeffsCopy; + if (d_produceProofs) + { + Assert(coeffs != RationalVectorCPSentinel); + coeffsCopy = new RationalVector(*coeffs); + } + else + { + coeffsCopy = RationalVectorCPSentinel; + } + d_database->pushConstraintRule(ConstraintRule(this, FarkasAP, antecedentEnd, coeffsCopy)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict@impliedByFarkas " << this << std::endl; + } + if(TraceIsOn("constraints::wffp") && !wellFormedFarkasProof()){ + getConstraintRule().print(Trace("constraints::wffp"), d_produceProofs); + } + Assert(wellFormedFarkasProof()); +} + + +void Constraint::setInternalAssumption(bool nowInConflict){ + Trace("constraints::pf") << "setInternalAssumption(" << this; + Trace("constraints::pf") << ")" << std::endl; + Assert(!hasProof()); + Assert(negationHasProof() == nowInConflict); + Assert(!assertedToTheTheory()); + + d_database->pushConstraintRule(ConstraintRule(this, InternalAssumeAP)); + + Assert(inConflict() == nowInConflict); + if(TraceIsOn("constraint::conflictCommit") && inConflict()){ + Trace("constraint::conflictCommit") << "inConflict@setInternalAssumption " << this << std::endl; + } +} + + +void Constraint::setEqualityEngineProof(){ + Trace("constraints::pf") << "setEqualityEngineProof(" << this; + Trace("constraints::pf") << ")" << std::endl; + Assert(truthIsUnknown()); + Assert(hasLiteral()); + d_database->pushConstraintRule(ConstraintRule(this, EqualityEngineAP)); +} + + +SortedConstraintMap& Constraint::constraintSet() const{ + Assert(d_database->variableDatabaseIsSetup(d_variable)); + return (d_database->d_varDatabases[d_variable])->d_constraints; +} + +bool Constraint::antecentListIsEmpty() const{ + Assert(hasProof()); + return d_database->d_antecedents[getEndAntecedent()] == NullConstraint; +} + +bool Constraint::antecedentListLengthIsOne() const { + Assert(hasProof()); + return !antecentListIsEmpty() && + d_database->d_antecedents[getEndAntecedent()-1] == NullConstraint; +} + +Node Constraint::externalImplication(const ConstraintCPVec& b) const{ + Assert(hasLiteral()); + Node antecedent = externalExplainByAssertions(b); + Node implied = getLiteral(); + return antecedent.impNode(implied); +} + + +Node Constraint::externalExplainByAssertions(const ConstraintCPVec& b){ + return externalExplain(b, AssertionOrderSentinel); +} + +TrustNode Constraint::externalExplainForPropagation(TNode lit) const +{ + Assert(hasProof()); + Assert(!isAssumption()); + Assert(!isInternalAssumption()); + NodeBuilder nb(Kind::AND); + auto pfFromAssumptions = externalExplain(nb, d_assertionOrder); + Node n = mkAndFromBuilder(nb); + if (d_database->isProofEnabled()) + { + std::vector assumptions; + if (n.getKind() == Kind::AND) + { + assumptions.insert(assumptions.end(), n.begin(), n.end()); + } + else + { + assumptions.push_back(n); + } + if (getProofLiteral() != lit) + { + pfFromAssumptions = d_database->d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pfFromAssumptions}, {lit}); + } + auto pf = d_database->d_pnm->mkScope(pfFromAssumptions, assumptions); + return d_database->d_pfGen->mkTrustedPropagation( + lit, NodeManager::currentNM()->mkAnd(assumptions), pf); + } + else + { + return TrustNode::mkTrustPropExp(lit, n); + } +} + +TrustNode Constraint::externalExplainConflict() const +{ + Trace("pf::arith::explain") << this << std::endl; + Assert(inConflict()); + NodeBuilder nb(kind::AND); + auto pf1 = externalExplainByAssertions(nb); + auto not2 = getNegation()->getProofLiteral().negate(); + auto pf2 = getNegation()->externalExplainByAssertions(nb); + Node n = mkAndFromBuilder(nb); + if (d_database->isProofEnabled()) + { + auto pfNot2 = d_database->d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pf1}, {not2}); + std::vector lits; + if (n.getKind() == Kind::AND) + { + lits.insert(lits.end(), n.begin(), n.end()); + } + else + { + lits.push_back(n); + } + if (TraceIsOn("arith::pf::externalExplainConflict")) + { + Trace("arith::pf::externalExplainConflict") << "Lits:" << std::endl; + for (const auto& l : lits) + { + Trace("arith::pf::externalExplainConflict") << " : " << l << std::endl; + } + } + std::vector contraLits = {getProofLiteral(), + getNegation()->getProofLiteral()}; + auto bot = + not2.getKind() == Kind::NOT + ? d_database->d_pnm->mkNode(PfRule::CONTRA, {pf2, pfNot2}, {}) + : d_database->d_pnm->mkNode(PfRule::CONTRA, {pfNot2, pf2}, {}); + if (TraceIsOn("arith::pf::tree")) + { + Trace("arith::pf::tree") << *this << std::endl; + Trace("arith::pf::tree") << *getNegation() << std::endl; + Trace("arith::pf::tree") << "\n\nTree:\n"; + printProofTree(Trace("arith::pf::tree")); + getNegation()->printProofTree(Trace("arith::pf::tree")); + } + auto confPf = d_database->d_pnm->mkScope(bot, lits); + return d_database->d_pfGen->mkTrustNode( + NodeManager::currentNM()->mkAnd(lits), confPf, true); + } + else + { + return TrustNode::mkTrustConflict(n); + } +} + +struct ConstraintCPHash { + /* Todo replace with an id */ + size_t operator()(ConstraintCP c) const{ + Assert(sizeof(ConstraintCP) > 0); + return ((size_t)c)/sizeof(ConstraintCP); + } +}; + +void Constraint::assertionFringe(ConstraintCPVec& v){ + unordered_set visited; + size_t writePos = 0; + + if(!v.empty()){ + const ConstraintDatabase* db = v.back()->d_database; + const CDConstraintList& antecedents = db->d_antecedents; + for(size_t i = 0; i < v.size(); ++i){ + ConstraintCP vi = v[i]; + if(visited.find(vi) == visited.end()){ + Assert(vi->hasProof()); + visited.insert(vi); + if(vi->onFringe()){ + v[writePos] = vi; + writePos++; + }else{ + Assert(vi->hasTrichotomyProof() || vi->hasFarkasProof() + || vi->hasIntHoleProof() || vi->hasIntTightenProof()); + AntecedentId p = vi->getEndAntecedent(); + + ConstraintCP antecedent = antecedents[p]; + while(antecedent != NullConstraint){ + v.push_back(antecedent); + --p; + antecedent = antecedents[p]; + } + } + } + } + v.resize(writePos); + } +} + +void Constraint::assertionFringe(ConstraintCPVec& o, const ConstraintCPVec& i){ + o.insert(o.end(), i.begin(), i.end()); + assertionFringe(o); +} + +Node Constraint::externalExplain(const ConstraintCPVec& v, AssertionOrder order){ + NodeBuilder nb(kind::AND); + ConstraintCPVec::const_iterator i, end; + for(i = v.begin(), end = v.end(); i != end; ++i){ + ConstraintCP v_i = *i; + v_i->externalExplain(nb, order); + } + return mkAndFromBuilder(nb); +} + +std::shared_ptr Constraint::externalExplain( + NodeBuilder& nb, AssertionOrder order) const +{ + if (TraceIsOn("pf::arith::explain")) + { + this->printProofTree(Trace("arith::pf::tree")); + Trace("pf::arith::explain") << "Explaining: " << this << " with rule "; + getConstraintRule().print(Trace("pf::arith::explain"), d_produceProofs); + Trace("pf::arith::explain") << std::endl; + } + Assert(hasProof()); + Assert(!isAssumption() || assertedToTheTheory()); + Assert(!isInternalAssumption()); + std::shared_ptr pf{}; + + ProofNodeManager* pnm = d_database->d_pnm; + + if (assertedBefore(order)) + { + Trace("pf::arith::explain") << " already asserted" << std::endl; + nb << getWitness(); + if (d_database->isProofEnabled()) + { + pf = pnm->mkAssume(getWitness()); + // If the witness and literal differ, prove the difference through a + // rewrite. + if (getWitness() != getProofLiteral()) + { + pf = pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pf}, {getProofLiteral()}); + } + } + } + else if (hasEqualityEngineProof()) + { + Trace("pf::arith::explain") << " going to ee:" << std::endl; + TrustNode exp = d_database->eeExplain(this); + if (d_database->isProofEnabled()) + { + Assert(exp.getProven().getKind() == Kind::IMPLIES); + std::vector> hypotheses; + hypotheses.push_back(exp.getGenerator()->getProofFor(exp.getProven())); + if (exp.getNode().getKind() == Kind::AND) + { + for (const auto& h : exp.getNode()) + { + hypotheses.push_back( + pnm->mkNode(PfRule::TRUE_INTRO, {pnm->mkAssume(h)}, {})); + } + } + else + { + hypotheses.push_back(pnm->mkNode( + PfRule::TRUE_INTRO, {pnm->mkAssume(exp.getNode())}, {})); + } + pf = pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {hypotheses}, {getProofLiteral()}); + } + Trace("pf::arith::explain") + << " explanation: " << exp.getNode() << std::endl; + if (exp.getNode().getKind() == Kind::AND) + { + nb.append(exp.getNode().begin(), exp.getNode().end()); + } + else + { + nb << exp.getNode(); + } + } + else + { + Trace("pf::arith::explain") << " recursion!" << std::endl; + Assert(!isAssumption()); + AntecedentId p = getEndAntecedent(); + ConstraintCP antecedent = d_database->d_antecedents[p]; + std::vector> children; + + while (antecedent != NullConstraint) + { + Trace("pf::arith::explain") << "Explain " << antecedent << std::endl; + auto pn = antecedent->externalExplain(nb, order); + if (d_database->isProofEnabled()) + { + children.push_back(pn); + } + --p; + antecedent = d_database->d_antecedents[p]; + } + + if (d_database->isProofEnabled()) + { + switch (getProofType()) + { + case ArithProofType::AssumeAP: + case ArithProofType::EqualityEngineAP: + { + Unreachable() << "These should be handled above"; + break; + } + case ArithProofType::FarkasAP: + { + // Per docs in constraint.h, + // the 0th farkas coefficient is for the negation of the deduced + // constraint the 1st corresponds to the last antecedent the nth + // corresponds to the first antecedent Then, the farkas coefficients + // and the antecedents are in the same order. + + // Enumerate child proofs (negation included) in d_farkasCoefficients + // order + Node plit = getNegation()->getProofLiteral(); + std::vector> farkasChildren; + farkasChildren.push_back(pnm->mkAssume(plit)); + farkasChildren.insert( + farkasChildren.end(), children.rbegin(), children.rend()); + + NodeManager* nm = NodeManager::currentNM(); + + // Enumerate d_farkasCoefficients as nodes. + std::vector farkasCoeffs; + TypeNode type = plit[0].getType(); + for (Rational r : *getFarkasCoefficients()) + { + farkasCoeffs.push_back(nm->mkConstRealOrInt(type, Rational(r))); + } + + // Apply the scaled-sum rule. + std::shared_ptr sumPf = pnm->mkNode( + PfRule::MACRO_ARITH_SCALE_SUM_UB, farkasChildren, farkasCoeffs); + + // Provable rewrite the result + auto botPf = pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); + + // Scope out the negated constraint, yielding a proof of the + // constraint. + std::vector assump{plit}; + auto maybeDoubleNotPf = pnm->mkScope(botPf, assump, false); + + // No need to ensure that the expected node aggrees with `assump` + // because we are not providing an expected node. + // + // Prove that this is the literal (may need to clean a double-not) + pf = pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, + {maybeDoubleNotPf}, + {getProofLiteral()}); + + break; + } + case ArithProofType::IntTightenAP: + { + if (isUpperBound()) + { + pf = pnm->mkNode( + PfRule::INT_TIGHT_UB, children, {}, getProofLiteral()); + } + else if (isLowerBound()) + { + pf = pnm->mkNode( + PfRule::INT_TIGHT_LB, children, {}, getProofLiteral()); + } + else + { + Unreachable(); + } + break; + } + case ArithProofType::IntHoleAP: + { + Node t = + builtin::BuiltinProofRuleChecker::mkTheoryIdNode(THEORY_ARITH); + pf = pnm->mkNode(PfRule::THEORY_INFERENCE, + children, + {getProofLiteral(), t}, + getProofLiteral()); + break; + } + case ArithProofType::TrichotomyAP: + { + pf = pnm->mkNode(PfRule::ARITH_TRICHOTOMY, + children, + {getProofLiteral()}, + getProofLiteral()); + break; + } + case ArithProofType::InternalAssumeAP: + case ArithProofType::NoAP: + default: + { + Unreachable() << getProofType() + << " should not be visible in explanation"; + break; + } + } + } + } + return pf; +} + +Node Constraint::externalExplainByAssertions(ConstraintCP a, ConstraintCP b){ + NodeBuilder nb(kind::AND); + a->externalExplainByAssertions(nb); + b->externalExplainByAssertions(nb); + return nb; +} + +Node Constraint::externalExplainByAssertions(ConstraintCP a, ConstraintCP b, ConstraintCP c){ + NodeBuilder nb(kind::AND); + a->externalExplainByAssertions(nb); + b->externalExplainByAssertions(nb); + c->externalExplainByAssertions(nb); + return nb; +} + +ConstraintP Constraint::getStrictlyWeakerLowerBound(bool hasLiteral, bool asserted) const { + Assert(initialized()); + Assert(!asserted || hasLiteral); + + SortedConstraintMapConstIterator i = d_variablePosition; + const SortedConstraintMap& scm = constraintSet(); + SortedConstraintMapConstIterator i_begin = scm.begin(); + while(i != i_begin){ + --i; + const ValueCollection& vc = i->second; + if(vc.hasLowerBound()){ + ConstraintP weaker = vc.getLowerBound(); + + // asserted -> hasLiteral + // hasLiteral -> weaker->hasLiteral() + // asserted -> weaker->assertedToTheTheory() + if((!hasLiteral || (weaker->hasLiteral())) && + (!asserted || ( weaker->assertedToTheTheory()))){ + return weaker; + } + } + } + return NullConstraint; +} + +ConstraintP Constraint::getStrictlyWeakerUpperBound(bool hasLiteral, bool asserted) const { + SortedConstraintMapConstIterator i = d_variablePosition; + const SortedConstraintMap& scm = constraintSet(); + SortedConstraintMapConstIterator i_end = scm.end(); + + ++i; + for(; i != i_end; ++i){ + const ValueCollection& vc = i->second; + if(vc.hasUpperBound()){ + ConstraintP weaker = vc.getUpperBound(); + if((!hasLiteral || (weaker->hasLiteral())) && + (!asserted || ( weaker->assertedToTheTheory()))){ + return weaker; + } + } + } + + return NullConstraint; +} + +ConstraintP ConstraintDatabase::getBestImpliedBound(ArithVar v, ConstraintType t, const DeltaRational& r) const { + Assert(variableDatabaseIsSetup(v)); + Assert(t == UpperBound || t == LowerBound); + + SortedConstraintMap& scm = getVariableSCM(v); + if(t == UpperBound){ + SortedConstraintMapConstIterator i = scm.lower_bound(r); + SortedConstraintMapConstIterator i_end = scm.end(); + Assert(i == i_end || r <= i->first); + for(; i != i_end; i++){ + Assert(r <= i->first); + const ValueCollection& vc = i->second; + if(vc.hasUpperBound()){ + return vc.getUpperBound(); + } + } + return NullConstraint; + }else{ + Assert(t == LowerBound); + if(scm.empty()){ + return NullConstraint; + }else{ + SortedConstraintMapConstIterator i = scm.lower_bound(r); + SortedConstraintMapConstIterator i_begin = scm.begin(); + SortedConstraintMapConstIterator i_end = scm.end(); + Assert(i == i_end || r <= i->first); + + int fdj = 0; + + if(i == i_end){ + --i; + Trace("getBestImpliedBound") << fdj++ << " " << r << " " << i->first << endl; + }else if( (i->first) > r){ + if(i == i_begin){ + return NullConstraint; + }else{ + --i; + Trace("getBestImpliedBound") << fdj++ << " " << r << " " << i->first << endl; + } + } + + do{ + Trace("getBestImpliedBound") << fdj++ << " " << r << " " << i->first << endl; + Assert(r >= i->first); + const ValueCollection& vc = i->second; + + if(vc.hasLowerBound()){ + return vc.getLowerBound(); + } + + if(i == i_begin){ + break; + }else{ + --i; + } + }while(true); + return NullConstraint; + } + } +} +TrustNode ConstraintDatabase::eeExplain(const Constraint* const c) const +{ + Assert(c->hasLiteral()); + return d_congruenceManager.explain(c->getLiteral()); +} + +void ConstraintDatabase::eeExplain(ConstraintCP c, NodeBuilder& nb) const +{ + Assert(c->hasLiteral()); + // NOTE: this is not a recommended method since it ignores proofs + d_congruenceManager.explain(c->getLiteral(), nb); +} + +bool ConstraintDatabase::variableDatabaseIsSetup(ArithVar v) const { + return v < d_varDatabases.size(); +} + +ConstraintDatabase::Watches::Watches(context::Context* satContext, + context::Context* userContext) + : d_constraintProofs(satContext), + d_canBePropagatedWatches(satContext), + d_assertionOrderWatches(satContext), + d_splitWatches(userContext) +{} + + +void Constraint::setLiteral(Node n) { + Trace("arith::constraint") << "Mapping " << *this << " to " << n << std::endl; + Assert(Comparison::isNormalAtom(n)); + Assert(!hasLiteral()); + Assert(sanityChecking(n)); + d_literal = n; + NodetoConstraintMap& map = d_database->d_nodetoConstraintMap; + Assert(map.find(n) == map.end()); + map.insert(make_pair(d_literal, this)); +} + +Node Constraint::getProofLiteral() const +{ + Assert(d_database != nullptr); + Assert(d_database->d_avariables.hasNode(d_variable)); + Node varPart = d_database->d_avariables.asNode(d_variable); + Kind cmp; + bool neg = false; + switch (d_type) + { + case ConstraintType::UpperBound: + { + if (d_value.infinitesimalIsZero()) + { + cmp = Kind::LEQ; + } + else + { + cmp = Kind::LT; + } + break; + } + case ConstraintType::LowerBound: + { + if (d_value.infinitesimalIsZero()) + { + cmp = Kind::GEQ; + } + else + { + cmp = Kind::GT; + } + break; + } + case ConstraintType::Equality: + { + cmp = Kind::EQUAL; + break; + } + case ConstraintType::Disequality: + { + cmp = Kind::EQUAL; + neg = true; + break; + } + default: Unreachable() << d_type; + } + NodeManager* nm = NodeManager::currentNM(); + Node constPart = nm->mkConstRealOrInt( + varPart.getType(), Rational(d_value.getNoninfinitesimalPart())); + Node posLit = nm->mkNode(cmp, varPart, constPart); + return neg ? posLit.negate() : posLit; +} + +void ConstraintDatabase::proveOr(std::vector& out, + ConstraintP a, + ConstraintP b, + bool negateSecond) const +{ + Node la = a->getLiteral(); + Node lb = b->getLiteral(); + Node orN = (la < lb) ? la.orNode(lb) : lb.orNode(la); + if (isProofEnabled()) + { + Assert(b->getNegation()->getType() != ConstraintType::Disequality); + auto nm = NodeManager::currentNM(); + Node alit = a->getNegation()->getProofLiteral(); + TypeNode type = alit[0].getType(); + auto pf_neg_la = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, + {d_pnm->mkAssume(la.negate())}, + {alit}); + Node blit = b->getNegation()->getProofLiteral(); + auto pf_neg_lb = d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, + {d_pnm->mkAssume(lb.negate())}, + {blit}); + int sndSign = negateSecond ? -1 : 1; + auto bot_pf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, + {d_pnm->mkNode(PfRule::MACRO_ARITH_SCALE_SUM_UB, + {pf_neg_la, pf_neg_lb}, + {nm->mkConstRealOrInt(type, Rational(-1 * sndSign)), + nm->mkConstRealOrInt(type, Rational(sndSign))})}, + {nm->mkConst(false)}); + std::vector as; + std::transform(orN.begin(), orN.end(), std::back_inserter(as), [](Node n) { + return n.negate(); + }); + // No need to ensure that the expected node aggrees with `as` because we + // are not providing an expected node. + auto pf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, + {d_pnm->mkNode(PfRule::NOT_AND, {d_pnm->mkScope(bot_pf, as)}, {})}, + {orN}); + out.push_back(d_pfGen->mkTrustNode(orN, pf)); + } + else + { + out.push_back(TrustNode::mkTrustLemma(orN)); + } +} + +void ConstraintDatabase::implies(std::vector& out, + ConstraintP a, + ConstraintP b) const +{ + Node la = a->getLiteral(); + Node lb = b->getLiteral(); + + Node neg_la = (la.getKind() == kind::NOT)? la[0] : la.notNode(); + + Assert(lb != neg_la); + Assert(b->getNegation()->getType() == ConstraintType::LowerBound + || b->getNegation()->getType() == ConstraintType::UpperBound); + proveOr(out, + a->getNegation(), + b, + b->getNegation()->getType() == ConstraintType::LowerBound); +} + +void ConstraintDatabase::mutuallyExclusive(std::vector& out, + ConstraintP a, + ConstraintP b) const +{ + Node la = a->getLiteral(); + Node lb = b->getLiteral(); + + Node neg_la = la.negate(); + Node neg_lb = lb.negate(); + proveOr(out, a->getNegation(), b->getNegation(), true); +} + +void ConstraintDatabase::outputUnateInequalityLemmas( + std::vector& out, ArithVar v) const +{ + SortedConstraintMap& scm = getVariableSCM(v); + SortedConstraintMapConstIterator scm_iter = scm.begin(); + SortedConstraintMapConstIterator scm_end = scm.end(); + ConstraintP prev = NullConstraint; + //get transitive unates + //Only lower bounds or upperbounds should be done. + for(; scm_iter != scm_end; ++scm_iter){ + const ValueCollection& vc = scm_iter->second; + if(vc.hasUpperBound()){ + ConstraintP ub = vc.getUpperBound(); + if(ub->hasLiteral()){ + if(prev != NullConstraint){ + implies(out, prev, ub); + } + prev = ub; + } + } + } +} + +void ConstraintDatabase::outputUnateEqualityLemmas(std::vector& out, + ArithVar v) const +{ + vector equalities; + + SortedConstraintMap& scm = getVariableSCM(v); + SortedConstraintMapConstIterator scm_iter = scm.begin(); + SortedConstraintMapConstIterator scm_end = scm.end(); + + for(; scm_iter != scm_end; ++scm_iter){ + const ValueCollection& vc = scm_iter->second; + if(vc.hasEquality()){ + ConstraintP eq = vc.getEquality(); + if(eq->hasLiteral()){ + equalities.push_back(eq); + } + } + } + + vector::const_iterator i, j, eq_end = equalities.end(); + for(i = equalities.begin(); i != eq_end; ++i){ + ConstraintP at_i = *i; + for(j= i + 1; j != eq_end; ++j){ + ConstraintP at_j = *j; + + mutuallyExclusive(out, at_i, at_j); + } + } + + for(i = equalities.begin(); i != eq_end; ++i){ + ConstraintP eq = *i; + const ValueCollection& vc = eq->getValueCollection(); + Assert(vc.hasEquality() && vc.getEquality()->hasLiteral()); + + bool hasLB = vc.hasLowerBound() && vc.getLowerBound()->hasLiteral(); + bool hasUB = vc.hasUpperBound() && vc.getUpperBound()->hasLiteral(); + + ConstraintP lb = hasLB ? + vc.getLowerBound() : eq->getStrictlyWeakerLowerBound(true, false); + ConstraintP ub = hasUB ? + vc.getUpperBound() : eq->getStrictlyWeakerUpperBound(true, false); + + if(hasUB && hasLB && !eq->isSplit()){ + out.push_back(eq->split()); + } + if(lb != NullConstraint){ + implies(out, eq, lb); + } + if(ub != NullConstraint){ + implies(out, eq, ub); + } + } +} + +void ConstraintDatabase::outputUnateEqualityLemmas( + std::vector& lemmas) const +{ + for(ArithVar v = 0, N = d_varDatabases.size(); v < N; ++v){ + outputUnateEqualityLemmas(lemmas, v); + } +} + +void ConstraintDatabase::outputUnateInequalityLemmas( + std::vector& lemmas) const +{ + for(ArithVar v = 0, N = d_varDatabases.size(); v < N; ++v){ + outputUnateInequalityLemmas(lemmas, v); + } +} + +bool ConstraintDatabase::handleUnateProp(ConstraintP ant, ConstraintP cons){ + if(cons->negationHasProof()){ + Trace("arith::unate") << "handleUnate: " << ant << " implies " << cons << endl; + cons->impliedByUnate(ant, true); + d_raiseConflict.raiseConflict(cons, InferenceId::ARITH_CONF_UNATE_PROP); + return true; + }else if(!cons->isTrue()){ + ++d_statistics.d_unatePropagateImplications; + Trace("arith::unate") << "handleUnate: " << ant << " implies " << cons << endl; + cons->impliedByUnate(ant, false); + cons->tryToPropagate(); + return false; + } else { + return false; + } +} + +void ConstraintDatabase::unatePropLowerBound(ConstraintP curr, ConstraintP prev){ + Trace("arith::unate") << "unatePropLowerBound " << curr << " " << prev << endl; + Assert(curr != prev); + Assert(curr != NullConstraint); + bool hasPrev = ! (prev == NullConstraint); + Assert(!hasPrev || curr->getValue() > prev->getValue()); + + ++d_statistics.d_unatePropagateCalls; + + const SortedConstraintMap& scm = curr->constraintSet(); + const SortedConstraintMapConstIterator scm_begin = scm.begin(); + SortedConstraintMapConstIterator scm_i = curr->d_variablePosition; + + //Ignore the first ValueCollection + // NOPE: (>= p c) then (= p c) NOPE + // NOPE: (>= p c) then (not (= p c)) NOPE + + while(scm_i != scm_begin){ + --scm_i; // move the iterator back + + const ValueCollection& vc = scm_i->second; + + //If it has the previous element, do nothing and stop! + if(hasPrev && + vc.hasConstraintOfType(prev->getType()) + && vc.getConstraintOfType(prev->getType()) == prev){ + break; + } + + //Don't worry about implying the negation of upperbound. + //These should all be handled by propagating the LowerBounds! + if(vc.hasLowerBound()){ + ConstraintP lb = vc.getLowerBound(); + if(handleUnateProp(curr, lb)){ return; } + } + if(vc.hasDisequality()){ + ConstraintP dis = vc.getDisequality(); + if(handleUnateProp(curr, dis)){ return; } + } + } +} + +void ConstraintDatabase::unatePropUpperBound(ConstraintP curr, ConstraintP prev){ + Trace("arith::unate") << "unatePropUpperBound " << curr << " " << prev << endl; + Assert(curr != prev); + Assert(curr != NullConstraint); + bool hasPrev = ! (prev == NullConstraint); + Assert(!hasPrev || curr->getValue() < prev->getValue()); + + ++d_statistics.d_unatePropagateCalls; + + const SortedConstraintMap& scm = curr->constraintSet(); + const SortedConstraintMapConstIterator scm_end = scm.end(); + SortedConstraintMapConstIterator scm_i = curr->d_variablePosition; + ++scm_i; + for(; scm_i != scm_end; ++scm_i){ + const ValueCollection& vc = scm_i->second; + + //If it has the previous element, do nothing and stop! + if(hasPrev && + vc.hasConstraintOfType(prev->getType()) && + vc.getConstraintOfType(prev->getType()) == prev){ + break; + } + //Don't worry about implying the negation of upperbound. + //These should all be handled by propagating the UpperBounds! + if(vc.hasUpperBound()){ + ConstraintP ub = vc.getUpperBound(); + if(handleUnateProp(curr, ub)){ return; } + } + if(vc.hasDisequality()){ + ConstraintP dis = vc.getDisequality(); + if(handleUnateProp(curr, dis)){ return; } + } + } +} + +void ConstraintDatabase::unatePropEquality(ConstraintP curr, ConstraintP prevLB, ConstraintP prevUB){ + Trace("arith::unate") << "unatePropEquality " << curr << " " << prevLB << " " << prevUB << endl; + Assert(curr != prevLB); + Assert(curr != prevUB); + Assert(curr != NullConstraint); + bool hasPrevLB = ! (prevLB == NullConstraint); + bool hasPrevUB = ! (prevUB == NullConstraint); + Assert(!hasPrevLB || curr->getValue() >= prevLB->getValue()); + Assert(!hasPrevUB || curr->getValue() <= prevUB->getValue()); + + ++d_statistics.d_unatePropagateCalls; + + const SortedConstraintMap& scm = curr->constraintSet(); + SortedConstraintMapConstIterator scm_curr = curr->d_variablePosition; + SortedConstraintMapConstIterator scm_last = hasPrevUB ? prevUB->d_variablePosition : scm.end(); + SortedConstraintMapConstIterator scm_i; + if(hasPrevLB){ + scm_i = prevLB->d_variablePosition; + if(scm_i != scm_curr){ // If this does not move this past scm_curr, move it one forward + ++scm_i; + } + }else{ + scm_i = scm.begin(); + } + + for(; scm_i != scm_curr; ++scm_i){ + // between the previous LB and the curr + const ValueCollection& vc = scm_i->second; + + //Don't worry about implying the negation of upperbound. + //These should all be handled by propagating the LowerBounds! + if(vc.hasLowerBound()){ + ConstraintP lb = vc.getLowerBound(); + if(handleUnateProp(curr, lb)){ return; } + } + if(vc.hasDisequality()){ + ConstraintP dis = vc.getDisequality(); + if(handleUnateProp(curr, dis)){ return; } + } + } + Assert(scm_i == scm_curr); + if(!hasPrevUB || scm_i != scm_last){ + ++scm_i; + } // hasPrevUB implies scm_i != scm_last + + for(; scm_i != scm_last; ++scm_i){ + // between the curr and the previous UB imply the upperbounds and disequalities. + const ValueCollection& vc = scm_i->second; + + //Don't worry about implying the negation of upperbound. + //These should all be handled by propagating the UpperBounds! + if(vc.hasUpperBound()){ + ConstraintP ub = vc.getUpperBound(); + if(handleUnateProp(curr, ub)){ return; } + } + if(vc.hasDisequality()){ + ConstraintP dis = vc.getDisequality(); + if(handleUnateProp(curr, dis)){ return; } + } + } +} + +std::pair Constraint::unateFarkasSigns(ConstraintCP ca, ConstraintCP cb){ + ConstraintType a = ca->getType(); + ConstraintType b = cb->getType(); + + Assert(a != Disequality); + Assert(b != Disequality); + + int a_sgn = (a == LowerBound) ? -1 : ((a == UpperBound) ? 1 : 0); + int b_sgn = (b == LowerBound) ? -1 : ((b == UpperBound) ? 1 : 0); + + if(a_sgn == 0 && b_sgn == 0){ + Assert(a == Equality); + Assert(b == Equality); + Assert(ca->getValue() != cb->getValue()); + if(ca->getValue() < cb->getValue()){ + a_sgn = 1; + b_sgn = -1; + }else{ + a_sgn = -1; + b_sgn = 1; + } + }else if(a_sgn == 0){ + Assert(b_sgn != 0); + Assert(a == Equality); + a_sgn = -b_sgn; + }else if(b_sgn == 0){ + Assert(a_sgn != 0); + Assert(b == Equality); + b_sgn = -a_sgn; + } + Assert(a_sgn != 0); + Assert(b_sgn != 0); + + Trace("arith::unateFarkasSigns") << "Constraint::unateFarkasSigns("< " + << "("< +#include + +#include "base/configuration_private.h" +#include "context/cdlist.h" +#include "context/cdqueue.h" +#include "expr/node.h" +#include "proof/trust_node.h" +#include "smt/env_obj.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/callbacks.h" +#include "theory/arith/linear/constraint_forward.h" +#include "theory/arith/delta_rational.h" +#include "util/statistics_stats.h" + +namespace cvc5::context { +class Context; +} +namespace cvc5::internal { + +class ProofNodeManager; +class EagerProofGenerator; + +namespace theory { + +namespace arith::linear { + +class Comparison; +class ArithCongruenceManager; +class ArithVariables; + +/** + * Logs the types of different proofs. + * Current, proof types: + * - NoAP : This constraint is not known to be true. + * - AssumeAP : This is an input assertion. There is no proof. + * : Something can be both asserted and have a proof. + * - InternalAssumeAP : An internal assumption. This has no guarantee of having an external proof. + * : This must be removed by regression. + * - FarkasAP : A proof with Farka's coefficients, i.e. + * : \sum lambda_i ( asNode(x_i) <= c_i ) |= 0 < 0 + * : If proofs are on, coefficients will be logged. + * : If proofs are off, coefficients will not be logged. + * : A unate implication is a FarkasAP. + * - TrichotomyAP : This is any entailment using (x<= a and x >=a) => x = a + * : Equivalently, (x > a or x < a or x = a) + * : There are 3 candidate ways this can propagate: + * : !(x > a) and !(x = a) => x < a + * : !(x < a) and !(x = a) => x > a + * : !(x > a) and !(x < a) => x = a + * - EqualityEngineAP : This is propagated by the equality engine. + * : Consult this for the proof. + * - IntTightenAP : This is indicates that a bound involving integers was tightened. + * : e.g. i < 5.5 became i <= 5, when i is an integer. + * - IntHoleAP : This is currently a catch-all for all integer specific reason. + */ +enum ArithProofType + { NoAP, + AssumeAP, + InternalAssumeAP, + FarkasAP, + TrichotomyAP, + EqualityEngineAP, + IntTightenAP, + IntHoleAP}; + +/** + * The types of constraints. + * The convex constraints are the constraints are LowerBound, Equality, + * and UpperBound. + */ +enum ConstraintType {LowerBound, Equality, UpperBound, Disequality}; + +typedef context::CDList CDConstraintList; + +typedef std::unordered_map NodetoConstraintMap; + +typedef size_t ConstraintRuleID; +static constexpr ConstraintRuleID ConstraintRuleIdSentinel = + std::numeric_limits::max(); + +typedef size_t AntecedentId; +static constexpr AntecedentId AntecedentIdSentinel = + std::numeric_limits::max(); + +typedef size_t AssertionOrder; +static constexpr AssertionOrder AssertionOrderSentinel = + std::numeric_limits::max(); + +/** + * A ValueCollection binds together convex constraints that have the same + * DeltaRational value. + */ +class ValueCollection { +private: + + ConstraintP d_lowerBound; + ConstraintP d_upperBound; + ConstraintP d_equality; + ConstraintP d_disequality; + +public: + ValueCollection(); + + static ValueCollection mkFromConstraint(ConstraintP c); + + bool hasLowerBound() const; + bool hasUpperBound() const; + bool hasEquality() const; + bool hasDisequality() const; + + bool hasConstraintOfType(ConstraintType t) const; + + ConstraintP getLowerBound() const; + ConstraintP getUpperBound() const; + ConstraintP getEquality() const; + ConstraintP getDisequality() const; + + ConstraintP getConstraintOfType(ConstraintType t) const; + + /** Returns true if any of the constraints are non-null. */ + bool empty() const; + + /** + * Remove the constraint of the type t from the collection. + * Returns true if the ValueCollection is now empty. + * If true is returned, d_value is now NULL. + */ + void remove(ConstraintType t); + + /** + * Adds a constraint to the set. + * The collection must not have a constraint of that type already. + */ + void add(ConstraintP c); + + void push_into(std::vector& vec) const; + + ConstraintP nonNull() const; + + ArithVar getVariable() const; + const DeltaRational& getValue() const; +}; + +/** + * A Map of ValueCollections sorted by the associated DeltaRational values. + * + * Discussion: + * While it is more natural to consider this a set, this cannot be a set as in + * sets the type of both iterator and const_iterator in sets are + * "constant iterators". We require iterators that dereference to + * ValueCollection&. + * + * See: + * http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#103 + */ +typedef std::map SortedConstraintMap; +typedef SortedConstraintMap::iterator SortedConstraintMapIterator; +typedef SortedConstraintMap::const_iterator SortedConstraintMapConstIterator; + +/** A Pair associating a variables and a Sorted ConstraintSet. */ +struct PerVariableDatabase{ + ArithVar d_var; + SortedConstraintMap d_constraints; + + // x ? c_1, x ? c_2, x ? c_3, ... + // where ? is a non-empty subset of {lb, ub, eq} + // c_1 < c_2 < c_3 < ... + + PerVariableDatabase(ArithVar v) : d_var(v), d_constraints() {} + + bool empty() const { + return d_constraints.empty(); + } + + static bool IsEmpty(const PerVariableDatabase& p){ + return p.empty(); + } +}; + +/** + * If proofs are on, there is a vector of rationals for farkas coefficients. + * This is the owner of the memory for the vector, and calls delete upon + * cleanup. + * + */ +struct ConstraintRule { + ConstraintP d_constraint; + ArithProofType d_proofType; + AntecedentId d_antecedentEnd; + + /** + * In this comment, we abbreviate ConstraintDatabase::d_antecedents + * and d_farkasCoefficients as ans and fc. + * + * This list is always empty if proofs are not enabled. + * + * If proofs are enabled, the proof of constraint c at p in ans[p] of length n + * is (NullConstraint, ans[p-(n-1)], ... , ans[p-1], ans[p]) + * + * Farkas' proofs show a contradiction with the negation of c, c_not = + * c->getNegation(). + * + * We treat the position for NullConstraint (p-n) as the position for the + * farkas coefficient for so we pretend c_not is ans[p-n]. So this correlation + * for the constraints we are going to use: (c_not, ans[p-n+(1)], ... , + * ans[p-n+(n-1)], ans[p-n+(n)]) With the coefficients at positions: (fc[0], + * fc[1)], ... fc[n]) + * + * The index of the constraints in the proof are {i | i <= 0 <= n] } (with + * c_not being p-n). Partition the indices into L, U, and E, the lower bounds, + * the upper bounds and equalities. + * + * We standardize the proofs to be upper bound oriented following the + * convention: A x <= b with the proof witness of the form (lambda) Ax <= + * (lambda) b and lambda >= 0. + * + * To accomplish this cleanly, the fc coefficients must be negative for lower + * bounds. The signs of equalities can be either positive or negative. + * + * Thus the proof corresponds to (with multiplication over inequalities): + * \sum_{u in U} fc[u] ans[p-n+u] + \sum_{e in E} fc[e] ans[p-n+e] + * + \sum_{l in L} fc[l] ans[p-n+l] + * |= 0 < 0 + * where fc[u] > 0, fc[l] < 0, and fc[e] != 0 (i.e. it can be either +/-). + * + * There is no requirement that the proof is minimal. + * We do however use all of the constraints by requiring non-zero + * coefficients. + */ + RationalVectorCP d_farkasCoefficients; + + ConstraintRule(); + ConstraintRule(ConstraintP con, ArithProofType pt); + ConstraintRule(ConstraintP con, + ArithProofType pt, + AntecedentId antecedentEnd); + ConstraintRule(ConstraintP con, + ArithProofType pt, + AntecedentId antecedentEnd, + RationalVectorCP coeffs); + + void print(std::ostream& out, bool produceProofs) const; +}; /* class ConstraintRule */ + +class Constraint { + + friend class ConstraintDatabase; + + public: + /** + * This begins construction of a minimal constraint. + * + * This should only be called by ConstraintDatabase. + * + * Because of circular dependencies a Constraint is not fully valid until + * initialize has been called on it. + */ + Constraint(ArithVar x, + ConstraintType t, + const DeltaRational& v, + bool produceProofs); + + /** + * Destructor for a constraint. + * This should only be called if safeToGarbageCollect() is true. + */ + ~Constraint(); + + static ConstraintType constraintTypeOfComparison(const Comparison& cmp); + + inline ConstraintType getType() const { + return d_type; + } + + inline ArithVar getVariable() const { + return d_variable; + } + + const DeltaRational& getValue() const { + return d_value; + } + + inline ConstraintP getNegation() const { + return d_negation; + } + + bool isEquality() const{ + return d_type == Equality; + } + bool isDisequality() const{ + return d_type == Disequality; + } + bool isLowerBound() const{ + return d_type == LowerBound; + } + bool isUpperBound() const{ + return d_type == UpperBound; + } + bool isStrictUpperBound() const{ + Assert(isUpperBound()); + return getValue().infinitesimalSgn() < 0; + } + + bool isStrictLowerBound() const{ + Assert(isLowerBound()); + return getValue().infinitesimalSgn() > 0; + } + + bool isSplit() const { + return d_split; + } + + /** + * Splits the node in the user context. + * Returns a lemma that is assumed to be true for the rest of the user context. + * Constraint must be an equality or disequality. + */ + TrustNode split(); + + bool canBePropagated() const { + return d_canBePropagated; + } + void setCanBePropagated(); + + /** + * Light wrapper for calling setCanBePropagated(), + * on this and this->d_negation. + */ + void setPreregistered(){ + setCanBePropagated(); + d_negation->setCanBePropagated(); + } + + bool assertedToTheTheory() const { + Assert((d_assertionOrder < AssertionOrderSentinel) != d_witness.isNull()); + return d_assertionOrder < AssertionOrderSentinel; + } + TNode getWitness() const { + Assert(assertedToTheTheory()); + return d_witness; + } + + bool assertedBefore(AssertionOrder time) const { + return d_assertionOrder < time; + } + + /** + * Sets the witness literal for a node being on the assertion stack. + * + * If the negation of the node is true, inConflict must be true. + * If the negation of the node is false, inConflict must be false. + * Hence, negationHasProof() == inConflict. + * + * This replaces: + * void setAssertedToTheTheory(TNode witness); + * void setAssertedToTheTheoryWithNegationTrue(TNode witness); + */ + void setAssertedToTheTheory(TNode witness, bool inConflict); + + bool hasLiteral() const { + return !d_literal.isNull(); + } + + void setLiteral(Node n); + + Node getLiteral() const { + Assert(hasLiteral()); + return d_literal; + } + + /** Gets a literal in the normal form suitable for proofs. + * That is, (sum of non-const monomials) >< const. + * + * This is a sister method to `getLiteral`, which returns a normal form + * literal, suitable for external solving use. + */ + Node getProofLiteral() const; + + /** + * Set the node as having a proof and being an assumption. + * The node must be assertedToTheTheory(). + * + * Precondition: negationHasProof() == inConflict. + * + * Replaces: + * selfExplaining(). + * selfExplainingWithNegationTrue(). + */ + void setAssumption(bool inConflict); + + /** Returns true if the node is an assumption.*/ + bool isAssumption() const; + + /** Whether we produce proofs */ + bool isProofProducing() const { return d_produceProofs; } + + /** Set the constraint to have an EqualityEngine proof. */ + void setEqualityEngineProof(); + bool hasEqualityEngineProof() const; + + /** Returns true if the node has a Farkas' proof. */ + bool hasFarkasProof() const; + + /** + * @brief Returns whether this constraint is provable using a Farkas + * proof applied to (possibly tightened) input assertions. + * + * An example of a constraint that has a simple Farkas proof: + * x <= 0 proven from x + y <= 0 and x - y <= 0. + * + * An example of another constraint that has a simple Farkas proof: + * x <= 0 proven from x + y <= 0 and x - y <= 0.5 for integers x, y + * (integer bound-tightening is applied first!). + * + * An example of a constraint that might be proven **without** a simple + * Farkas proof: + * x < 0 proven from not(x == 0) and not(x > 0). + * + * This could be proven internally by the arithmetic theory using + * `TrichotomyAP` as the proof type. + * + */ + bool hasSimpleFarkasProof() const; + /** + * Returns whether this constraint is an assumption or a tightened + * assumption. + */ + bool isPossiblyTightenedAssumption() const; + + /** Returns true if the node has a int bound tightening proof. */ + bool hasIntTightenProof() const; + + /** Returns true if the node has a int hole proof. */ + bool hasIntHoleProof() const; + + /** Returns true if the node has a trichotomy proof. */ + bool hasTrichotomyProof() const; + + void printProofTree(std::ostream & out, size_t depth = 0) const; + + /** + * A sets the constraint to be an internal assumption. + * + * This does not need to have a witness or an associated literal. + * This is always itself in the explanation fringe for both conflicts + * and propagation. + * This cannot be converted back into a Node conflict or explanation. + * + * This cannot have a proof or be asserted to the theory! + * + */ + void setInternalAssumption(bool inConflict); + bool isInternalAssumption() const; + + /** + * Returns a explanation of the constraint that is appropriate for conflicts. + * + * This is not appropriate for propagation! + * + * This is the minimum fringe of the implication tree s.t. + * every constraint is assertedToTheTheory() or hasEqualityEngineProof(). + */ + TrustNode externalExplainByAssertions() const; + + /** + * Writes an explanation of a constraint into the node builder. + * Pushes back an explanation that is acceptable to send to the sat solver. + * nb is assumed to be an AND. + * + * This is the minimum fringe of the implication tree s.t. + * every constraint is assertedToTheTheory() or hasEqualityEngineProof(). + * + * This is not appropriate for propagation! + * Use explainForPropagation() instead. + */ + std::shared_ptr externalExplainByAssertions(NodeBuilder& nb) const + { + return externalExplain(nb, AssertionOrderSentinel); + } + + /* Equivalent to calling externalExplainByAssertions on all constraints in b */ + static Node externalExplainByAssertions(const ConstraintCPVec& b); + static Node externalExplainByAssertions(ConstraintCP a, ConstraintCP b); + static Node externalExplainByAssertions(ConstraintCP a, ConstraintCP b, ConstraintCP c); + + /** + * This is the minimum fringe of the implication tree s.t. every constraint is + * - assertedToTheTheory(), + * - isInternalDecision() or + * - hasEqualityEngineProof(). + */ + static void assertionFringe(ConstraintCPVec& v); + static void assertionFringe(ConstraintCPVec& out, const ConstraintCPVec& in); + + /** The fringe of a farkas' proof. */ + bool onFringe() const { + return assertedToTheTheory() || isInternalAssumption() || hasEqualityEngineProof(); + } + + /** + * Returns an explanation of a propagation by the ConstraintDatabase. + * The constraint must have a proof. + * The constraint cannot be an assumption. + * + * This is the minimum fringe of the implication tree (excluding the + * constraint itself) s.t. every constraint is assertedToTheTheory() or + * hasEqualityEngineProof(). + * + * All return conjuncts were asserted before this constraint. + * + * Requires the given node to rewrite to the canonical literal for this + * constraint. + * + * @params n the literal to prove + * n must rewrite to the constraint's canonical literal + * + * @returns a trust node of the form: + * (=> explanation n) + */ + TrustNode externalExplainForPropagation(TNode n) const; + + /** + * Explain the constraint and its negation in terms of assertions. + * The constraint must be in conflict. + */ + TrustNode externalExplainConflict() const; + + /** The constraint is known to be true. */ + inline bool hasProof() const { + return d_crid != ConstraintRuleIdSentinel; + } + + /** The negation of the constraint is known to hold. */ + inline bool negationHasProof() const { + return d_negation->hasProof(); + } + + /** Neither the contraint has a proof nor the negation has a proof.*/ + bool truthIsUnknown() const { + return !hasProof() && !negationHasProof(); + } + + /** This is a synonym for hasProof(). */ + inline bool isTrue() const { + return hasProof(); + } + + /** Both the constraint and its negation are true. */ + inline bool inConflict() const { + return hasProof() && negationHasProof(); + } + + /** + * Returns the constraint that corresponds to taking + * x r ceiling(getValue()) where r is the node's getType(). + * Esstentially this is an up branch. + */ + ConstraintP getCeiling(); + + /** + * Returns the constraint that corresponds to taking + * x r floor(getValue()) where r is the node's getType(). + * Esstentially this is a down branch. + */ + ConstraintP getFloor(); + + static ConstraintP makeNegation(ArithVar v, + ConstraintType t, + const DeltaRational& r, + bool produceProofs); + + const ValueCollection& getValueCollection() const; + + + ConstraintP getStrictlyWeakerUpperBound(bool hasLiteral, bool mustBeAsserted) const; + ConstraintP getStrictlyWeakerLowerBound(bool hasLiteral, bool mustBeAsserted) const; + + /** + * Marks a the constraint c as being entailed by a. + * The Farkas proof 1*(a) + -1 (c) |= 0<0 + * + * After calling impliedByUnate(), the caller should either raise a conflict + * or try call tryToPropagate(). + */ + void impliedByUnate(ConstraintCP a, bool inConflict); + + /** + * Marks a the constraint c as being entailed by a. + * The reason has to do with integer bound tightening. + * + * After calling impliedByIntTighten(), the caller should either raise a conflict + * or try call tryToPropagate(). + */ + void impliedByIntTighten(ConstraintCP a, bool inConflict); + + /** + * Marks a the constraint c as being entailed by a. + * The reason has to do with integer reasoning. + * + * After calling impliedByIntHole(), the caller should either raise a conflict + * or try call tryToPropagate(). + */ + void impliedByIntHole(ConstraintCP a, bool inConflict); + + /** + * Marks a the constraint c as being entailed by a. + * The reason has to do with integer reasoning. + * + * After calling impliedByIntHole(), the caller should either raise a conflict + * or try call tryToPropagate(). + */ + void impliedByIntHole(const ConstraintCPVec& b, bool inConflict); + + /** + * This is a lemma of the form: + * x < d or x = d or x > d + * The current constraint c is one of the above constraints and {a,b} + * are the negation of the other two constraints. + * + * Preconditions: + * - negationHasProof() == inConflict. + * + * After calling impliedByTrichotomy(), the caller should either raise a conflict + * or try call tryToPropagate(). + */ + void impliedByTrichotomy(ConstraintCP a, ConstraintCP b, bool inConflict); + + /** + * Marks the node as having a Farkas proof. + * + * Preconditions: + * - coeffs == NULL if proofs are off. + * - See the comments for ConstraintRule for the form of coeffs when + * proofs are on. + * - negationHasProof() == inConflict. + * + * After calling impliedByFarkas(), the caller should either raise a conflict + * or try call tryToPropagate(). + */ + void impliedByFarkas(const ConstraintCPVec& b, RationalVectorCP coeffs, bool inConflict); + + /** + * Generates an implication node, B => getLiteral(), + * where B is the result of externalExplainByAssertions(b). + * Does not guarantee b is the explanation of the constraint. + */ + Node externalImplication(const ConstraintCPVec& b) const; + + /** + * Returns true if the variable is assigned the value dr, + * the constraint would be satisfied. + */ + bool satisfiedBy(const DeltaRational& dr) const; + + /** + * The node must have a proof already and be eligible for propagation! + * You probably want to call tryToPropagate() instead. + * + * Preconditions: + * - hasProof() + * - canBePropagated() + * - !assertedToTheTheory() + */ + void propagate(); + + /** + * If the constraint + * canBePropagated() and + * !assertedToTheTheory(), + * the constraint is added to the database's propagation queue. + * + * Precondition: + * - hasProof() + */ + void tryToPropagate(); + + /** + * Returns a reference to the containing database. + * Precondition: the constraint must be initialized. + */ + const ConstraintDatabase& getDatabase() const; + + /** Returns the constraint rule at the position. */ + const ConstraintRule& getConstraintRule() const; + + private: + /** Returns true if the constraint has been initialized. */ + bool initialized() const; + + /** + * This initializes the fields that cannot be set in the constructor due to + * circular dependencies. + */ + void initialize(ConstraintDatabase* db, + SortedConstraintMapIterator v, + ConstraintP negation); + + class ConstraintRuleCleanup + { + public: + inline void operator()(ConstraintRule* crp) + { + Assert(crp != NULL); + ConstraintP constraint = crp->d_constraint; + Assert(constraint->d_crid != ConstraintRuleIdSentinel); + constraint->d_crid = ConstraintRuleIdSentinel; + if (constraint->isProofProducing()) + { + if (crp->d_farkasCoefficients != RationalVectorCPSentinel) + { + delete crp->d_farkasCoefficients; + } + } + } + }; + + class CanBePropagatedCleanup + { + public: + inline void operator()(ConstraintP* p) + { + ConstraintP constraint = *p; + Assert(constraint->d_canBePropagated); + constraint->d_canBePropagated = false; + } + }; + + class AssertionOrderCleanup + { + public: + inline void operator()(ConstraintP* p) + { + ConstraintP constraint = *p; + Assert(constraint->assertedToTheTheory()); + constraint->d_assertionOrder = AssertionOrderSentinel; + constraint->d_witness = TNode::null(); + Assert(!constraint->assertedToTheTheory()); + } + }; + + class SplitCleanup + { + public: + inline void operator()(ConstraintP* p) + { + ConstraintP constraint = *p; + Assert(constraint->d_split); + constraint->d_split = false; + } + }; + + /** + * Returns true if the node is safe to garbage collect. + * Both it and its negation must have no context dependent data set. + */ + bool safeToGarbageCollect() const; + + /** + * Returns true if the constraint has no context dependent data set. + */ + bool contextDependentDataIsSet() const; + + /** + * Returns true if the node correctly corresponds to the constraint that is + * being set. + */ + bool sanityChecking(Node n) const; + + /** Returns a reference to the map for d_variable. */ + SortedConstraintMap& constraintSet() const; + + /** Returns coefficients for the proofs for farkas cancellation. */ + static std::pair unateFarkasSigns(ConstraintCP a, ConstraintCP b); + + Node externalExplain(AssertionOrder order) const; + /** + * Returns an explanation of that was assertedBefore(order). + * The constraint must have a proof. + * The constraint cannot be selfExplaining(). + * + * This is the minimum fringe of the implication tree + * s.t. every constraint is assertedBefore(order) or hasEqualityEngineProof(). + */ + std::shared_ptr externalExplain(NodeBuilder& nb, + AssertionOrder order) const; + + static Node externalExplain(const ConstraintCPVec& b, AssertionOrder order); + + inline ArithProofType getProofType() const { + return getConstraintRule().d_proofType; + } + + inline AntecedentId getEndAntecedent() const { + return getConstraintRule().d_antecedentEnd; + } + + inline RationalVectorCP getFarkasCoefficients() const + { + return d_produceProofs ? getConstraintRule().d_farkasCoefficients : nullptr; + } + + /** + * The proof of the node is empty. + * The proof must be a special proof. Either + * isSelfExplaining() or + * hasEqualityEngineProof() + */ + bool antecentListIsEmpty() const; + + bool antecedentListLengthIsOne() const; + + /** Return true if every element in b has a proof. */ + static bool allHaveProof(const ConstraintCPVec& b); + + /** Precondition: hasFarkasProof() + * Computes the combination implied by the farkas coefficients. Sees if it is + * a contradiction. + */ + + bool wellFormedFarkasProof() const; + + /** The ArithVar associated with the constraint. */ + const ArithVar d_variable; + + /** The type of the Constraint. */ + const ConstraintType d_type; + + /** The DeltaRational value with the constraint. */ + const DeltaRational d_value; + + /** A pointer to the associated database for the Constraint. */ + ConstraintDatabase* d_database; + + /** + * The node to be communicated with the TheoryEngine. + * + * This is not context dependent, but may be set once. + * + * This must be set if the constraint canBePropagated(). + * This must be set if the constraint assertedToTheTheory(). + * Otherwise, this may be null(). + */ + Node d_literal; + + /** Pointer to the negation of the Constraint. */ + ConstraintP d_negation; + + /** + * This is true if the associated node can be propagated. + * + * This should be enabled if the node has been preregistered. + * + * Sat Context Dependent. + * This is initially false. + */ + bool d_canBePropagated; + + /** + * This is the order the constraint was asserted to the theory. + * If this has been set, the node can be used in conflicts. + * If this is c.d_assertedOrder < d.d_assertedOrder, then c can be used in the + * explanation of d. + * + * This should be set after the literal is dequeued by Theory::get(). + * + * Sat Context Dependent. + * This is initially AssertionOrderSentinel. + */ + AssertionOrder d_assertionOrder; + + /** + * This is guaranteed to be on the fact queue. + * For example if x + y = x + 1 is on the fact queue, then use this + */ + TNode d_witness; + + /** + * The position of the constraint in the constraint rule id. + * + * Sat Context Dependent. + * This is initially + */ + ConstraintRuleID d_crid; + + /** + * True if the equality has been split. + * Only meaningful if ConstraintType == Equality. + * + * User Context Dependent. + * This is initially false. + */ + bool d_split; + + /** + * Position in sorted constraint set for the variable. + * Unset if d_type is Disequality. + */ + SortedConstraintMapIterator d_variablePosition; + + /** Whether to produce proofs, */ + bool d_produceProofs; + +}; /* class ConstraintValue */ + +std::ostream& operator<<(std::ostream& o, const Constraint& c); +std::ostream& operator<<(std::ostream& o, const ConstraintP c); +std::ostream& operator<<(std::ostream& o, const ConstraintCP c); +std::ostream& operator<<(std::ostream& o, const ConstraintType t); +std::ostream& operator<<(std::ostream& o, const ValueCollection& c); +std::ostream& operator<<(std::ostream& o, const ConstraintCPVec& v); +std::ostream& operator<<(std::ostream& o, const ArithProofType); + +class ConstraintDatabase : protected EnvObj +{ + private: + /** + * The map from ArithVars to their unique databases. + * When the vector changes size, we cannot allow the maps to move so this + * is a vector of pointers. + */ + std::vector d_varDatabases; + + SortedConstraintMap& getVariableSCM(ArithVar v) const; + + /** Maps literals to constraints.*/ + NodetoConstraintMap d_nodetoConstraintMap; + + /** + * A queue of propagated constraints. + * ConstraintCP are pointers. + * The elements of the queue do not require destruction. + */ + context::CDQueue d_toPropagate; + + /** + * Proofs are lists of valid constraints terminated by the first null + * sentinel value in the proof list. + * We abbreviate d_antecedents as ans in the comment. + * + * The proof at p in ans[p] of length n is + * (NullConstraint, ans[p-(n-1)], ... , ans[p-1], ans[p]) + * + * The proof at p corresponds to the conjunction: + * (and x_i) + * + * So the proof of a Constraint c corresponds to the horn clause: + * (implies (and x_i) c) + * where (and x_i) is the proof at c.d_crid d_antecedentEnd. + * + * Constraints are pointers so this list is designed not to require any destruction. + */ + CDConstraintList d_antecedents; + + typedef context::CDList + ConstraintRuleList; + typedef context::CDList + CBPList; + typedef context::CDList + AOList; + typedef context::CDList SplitList; + + /** + * The watch lists are collected together as they need to be garbage collected + * carefully. + */ + struct Watches{ + + /** + * Contains the exact list of constraints that have a proof. + * Upon pop, this unsets d_crid to NoAP. + * + * The index in this list is the proper ordering of the proofs. + */ + ConstraintRuleList d_constraintProofs; + + /** + * Contains the exact list of constraints that can be used for propagation. + */ + CBPList d_canBePropagatedWatches; + + /** + * Contains the exact list of constraints that have been asserted to the theory. + */ + AOList d_assertionOrderWatches; + + + /** + * Contains the exact list of atoms that have been preregistered. + * This is a pointer as it must be destroyed before the elements of + * d_varDatabases. + */ + SplitList d_splitWatches; + Watches(context::Context* satContext, context::Context* userContext); + }; + Watches* d_watches; + + void pushSplitWatch(ConstraintP c); + void pushCanBePropagatedWatch(ConstraintP c); + void pushAssertionOrderWatch(ConstraintP c, TNode witness); + + /** Assumes that antecedents have already been pushed. */ + void pushConstraintRule(const ConstraintRule& crp); + + /** Returns true if all of the entries of the vector are empty. */ + static bool emptyDatabase(const std::vector& vec); + + /** Map from nodes to arithvars. */ + const ArithVariables& d_avariables; + + const ArithVariables& getArithVariables() const{ + return d_avariables; + } + + ArithCongruenceManager& d_congruenceManager; + + /** Owned by the TheoryArithPrivate, used here. */ + EagerProofGenerator* d_pfGen; + /** Owned by the TheoryArithPrivate, used here. */ + ProofNodeManager* d_pnm; + + RaiseConflict d_raiseConflict; + + + const Rational d_one; + const Rational d_negOne; + + friend class Constraint; + + public: + ConstraintDatabase(Env& env, + const ArithVariables& variables, + ArithCongruenceManager& dm, + RaiseConflict conflictCallBack, + EagerProofGenerator* pfGen); + + ~ConstraintDatabase(); + + /** Adds a literal to the database. */ + ConstraintP addLiteral(TNode lit); + + /** + * If hasLiteral() is true, returns the constraint. + * Otherwise, returns NullConstraint. + */ + ConstraintP lookup(TNode literal) const; + + /** + * Returns true if the literal has been added to the database. + * This is a hash table lookup. + * It does not look in the database for an equivalent corresponding constraint. + */ + bool hasLiteral(TNode literal) const; + + bool hasMorePropagations() const{ + return !d_toPropagate.empty(); + } + + ConstraintCP nextPropagation(){ + Assert(hasMorePropagations()); + + ConstraintCP p = d_toPropagate.front(); + d_toPropagate.pop(); + + return p; + } + + void addVariable(ArithVar v); + bool variableDatabaseIsSetup(ArithVar v) const; + void removeVariable(ArithVar v); + + /** Get an explanation and proof for this constraint from the equality engine + */ + TrustNode eeExplain(ConstraintCP c) const; + /** Get an explanation for this constraint from the equality engine */ + void eeExplain(ConstraintCP c, NodeBuilder& nb) const; + + /** + * Returns a constraint with the variable v, the constraint type t, and a value + * dominated by r (explained below) if such a constraint exists in the database. + * If no such constraint exists, NullConstraint is returned. + * + * t must be either UpperBound or LowerBound. + * The returned value v is dominated: + * If t is UpperBound, r <= v + * If t is LowerBound, r >= v + * + * variableDatabaseIsSetup(v) must be true. + */ + ConstraintP getBestImpliedBound(ArithVar v, ConstraintType t, const DeltaRational& r) const; + + /** Returns the constraint, if it exists */ + ConstraintP lookupConstraint(ArithVar v, ConstraintType t, const DeltaRational& r) const; + + /** + * Returns a constraint with the variable v, the constraint type t and the value r. + * If there is such a constraint in the database already, it is returned. + * If there is no such constraint, this constraint is added to the database. + * + */ + ConstraintP getConstraint(ArithVar v, ConstraintType t, const DeltaRational& r); + + /** + * Returns a constraint of the given type for the value and variable + * for the given ValueCollection, vc. + * This is made if there is no such constraint. + */ + ConstraintP ensureConstraint(ValueCollection& vc, ConstraintType t); + + + void deleteConstraintAndNegation(ConstraintP c); + + /** Given constraints `a` and `b` such that `a OR b` by unate reasoning, + * adds a TrustNode to `out` which proves `a OR b` as a lemma. + * + * Example: `x <= 5` OR `5 <= x`. + */ + void proveOr(std::vector& out, + ConstraintP a, + ConstraintP b, + bool negateSecond) const; + /** Given constraints `a` and `b` such that `a` implies `b` by unate + * reasoning, adds a TrustNode to `out` which proves `-a OR b` as a lemma. + * + * Example: `x >= 5` -> `x >= 4`. + */ + void implies(std::vector& out, ConstraintP a, ConstraintP b) const; + /** Given constraints `a` and `b` such that `not(a AND b)` by unate reasoning, + * adds a TrustNode to `out` which proves `-a OR -b` as a lemma. + * + * Example: `x >= 4` -> `x <= 3`. + */ + void mutuallyExclusive(std::vector& out, + ConstraintP a, + ConstraintP b) const; + + /** + * Outputs a minimal set of unate implications onto the vector for the variable. + * This outputs lemmas of the general forms + * (= p c) implies (<= p d) for c < d, or + * (= p c) implies (not (= p d)) for c != d. + */ + void outputUnateEqualityLemmas(std::vector& lemmas) const; + void outputUnateEqualityLemmas(std::vector& lemmas, + ArithVar v) const; + + /** + * Outputs a minimal set of unate implications onto the vector for the variable. + * + * If ineqs is true, this outputs lemmas of the general form + * (<= p c) implies (<= p d) for c < d. + */ + void outputUnateInequalityLemmas(std::vector& lemmas) const; + void outputUnateInequalityLemmas(std::vector& lemmas, + ArithVar v) const; + + void unatePropLowerBound(ConstraintP curr, ConstraintP prev); + void unatePropUpperBound(ConstraintP curr, ConstraintP prev); + void unatePropEquality(ConstraintP curr, ConstraintP prevLB, ConstraintP prevUB); + + /** AntecendentID must be in range. */ + ConstraintCP getAntecedent(AntecedentId p) const; + + bool isProofEnabled() const { return d_pnm != nullptr; } + + private: + /** returns true if cons is now in conflict. */ + bool handleUnateProp(ConstraintP ant, ConstraintP cons); + + DenseSet d_reclaimable; + + class Statistics { + public: + IntStat d_unatePropagateCalls; + IntStat d_unatePropagateImplications; + + Statistics(); + } d_statistics; + +}; /* ConstraintDatabase */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__CONSTRAINT_H */ diff --git a/src/theory/arith/linear/constraint_forward.h b/src/theory/arith/linear/constraint_forward.h new file mode 100644 index 000000000..fa7f16c3b --- /dev/null +++ b/src/theory/arith/linear/constraint_forward.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * Forward declarations of the ConstraintValue and ConstraintDatabase + * classes. + * + * This is the forward declarations of the ConstraintValue and + * ConstraintDatabase and the typedef for Constraint. + * This is used to break circular dependencies and minimize interaction + * between header files. + */ + +#ifndef CVC5__THEORY__ARITH__CONSTRAINT_FORWARD_H +#define CVC5__THEORY__ARITH__CONSTRAINT_FORWARD_H + +#include + +#include "cvc5_private.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class Constraint; +typedef Constraint* ConstraintP; +typedef const Constraint* ConstraintCP; + +static constexpr ConstraintP NullConstraint = nullptr; + +class ConstraintDatabase; + +typedef std::vector ConstraintCPVec; + +typedef std::vector RationalVector; +typedef RationalVector* RationalVectorP; +typedef const RationalVector* RationalVectorCP; +static constexpr RationalVectorCP RationalVectorCPSentinel = nullptr; +static constexpr RationalVectorP RationalVectorPSentinel = nullptr; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__CONSTRAINT_FORWARD_H */ diff --git a/src/theory/arith/linear/cut_log.cpp b/src/theory/arith/linear/cut_log.cpp new file mode 100644 index 000000000..9bf3cb118 --- /dev/null +++ b/src/theory/arith/linear/cut_log.cpp @@ -0,0 +1,711 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andrew Reynolds, Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/cut_log.h" + +#include +#include + +#include +#include +#include + +#include "base/cvc5config.h" +#include "base/output.h" +#include "theory/arith/linear/approx_simplex.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/normal_form.h" +#include "util/ostream_util.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +NodeLog::const_iterator NodeLog::begin() const { return d_cuts.begin(); } +NodeLog::const_iterator NodeLog::end() const { return d_cuts.end(); } + +NodeLog& TreeLog::getNode(int nid) { + ToNodeMap::iterator i = d_toNode.find(nid); + Assert(i != d_toNode.end()); + return (*i).second; +} + +TreeLog::const_iterator TreeLog::begin() const { return d_toNode.begin(); } +TreeLog::const_iterator TreeLog::end() const { return d_toNode.end(); } + +int TreeLog::getExecutionOrd(){ + int res = next_exec_ord; + ++next_exec_ord; + return res; +} +void TreeLog::makeInactive(){ d_active = false; } +void TreeLog::makeActive(){ d_active = true; } +bool TreeLog::isActivelyLogging() const { return d_active; } + + +PrimitiveVec::PrimitiveVec() + : len(0) + , inds(NULL) + , coeffs(NULL) +{} + +PrimitiveVec::~PrimitiveVec(){ + clear(); +} +bool PrimitiveVec::initialized() const { + return inds != NULL; +} +void PrimitiveVec::clear() { + if(initialized()){ + delete[] inds; + delete[] coeffs; + len = 0; + inds = NULL; + coeffs = NULL; + } +} +void PrimitiveVec::setup(int l){ + Assert(!initialized()); + len = l; + inds = new int[1+len]; + coeffs = new double[1+len]; +} +void PrimitiveVec::print(std::ostream& out) const{ + Assert(initialized()); + StreamFormatScope scope(out); + + out << len << " " << std::setprecision(15); + for(int i = 1; i <= len; ++i){ + out << "["<< inds[i] <<", " << coeffs[i]<<"]"; + } +} +std::ostream& operator<<(std::ostream& os, const PrimitiveVec& pv){ + pv.print(os); + return os; +} + +CutInfo::CutInfo(CutInfoKlass kl, int eid, int o) + : d_klass(kl), + d_execOrd(eid), + d_poolOrd(o), + d_cutType(kind::UNDEFINED_KIND), + d_cutRhs(), + d_cutVec(), + d_mAtCreation(-1), + d_N(-1), + d_rowId(-1), + d_exactPrecision(nullptr), + d_explanation(nullptr) +{} + +CutInfo::~CutInfo(){ +} + +int CutInfo::getId() const { + return d_execOrd; +} + +int CutInfo::getRowId() const{ + return d_rowId; +} + +void CutInfo::setRowId(int rid){ + d_rowId = rid; +} + +void CutInfo::print(ostream& out) const{ + out << "[CutInfo " << d_execOrd << " " << d_poolOrd + << " " << d_klass << " " << d_cutType << " " << d_cutRhs + << " "; + d_cutVec.print(out); + out << "]" << endl; +} + +PrimitiveVec& CutInfo::getCutVector(){ + return d_cutVec; +} + +const PrimitiveVec& CutInfo::getCutVector() const{ + return d_cutVec; +} + +// void CutInfo::init_cut(int l){ +// cut_vec.setup(l); +// } + +Kind CutInfo::getKind() const{ + return d_cutType; +} + +void CutInfo::setKind(Kind k){ + Assert(k == kind::LEQ || k == kind::GEQ); + d_cutType = k; +} + +double CutInfo::getRhs() const{ + return d_cutRhs; +} + +void CutInfo::setRhs(double r){ + d_cutRhs = r; +} + +bool CutInfo::reconstructed() const { return d_exactPrecision != nullptr; } + +CutInfoKlass CutInfo::getKlass() const{ + return d_klass; +} + +int CutInfo::poolOrdinal() const{ + return d_poolOrd; +} + +void CutInfo::setDimensions(int N, int M){ + d_mAtCreation = M; + d_N = N; +} + +int CutInfo::getN() const{ + return d_N; +} + +int CutInfo::getMAtCreation() const{ + return d_mAtCreation; +} + +/* Returns true if the cut has an explanation. */ +bool CutInfo::proven() const { return d_explanation != nullptr; } + +bool CutInfo::operator<(const CutInfo& o) const{ + return d_execOrd < o.d_execOrd; +} + + +void CutInfo::setReconstruction(const DenseVector& ep){ + Assert(!reconstructed()); + d_exactPrecision.reset(new DenseVector(ep)); +} + +void CutInfo::setExplanation(const ConstraintCPVec& ex){ + Assert(reconstructed()); + if (d_explanation == nullptr) + { + d_explanation.reset(new ConstraintCPVec(ex)); + } + else + { + *d_explanation = ex; + } +} + +void CutInfo::swapExplanation(ConstraintCPVec& ex){ + Assert(reconstructed()); + Assert(!proven()); + if (d_explanation == nullptr) + { + d_explanation.reset(new ConstraintCPVec()); + } + d_explanation->swap(ex); +} + +const DenseVector& CutInfo::getReconstruction() const { + Assert(reconstructed()); + return *d_exactPrecision; +} + +void CutInfo::clearReconstruction(){ + if(proven()){ + d_explanation = nullptr; + } + + if(reconstructed()){ + d_exactPrecision = nullptr; + } + + Assert(!reconstructed()); + Assert(!proven()); +} + +const ConstraintCPVec& CutInfo::getExplanation() const { + Assert(proven()); + return *d_explanation; +} + +std::ostream& operator<<(std::ostream& os, const CutInfo& ci){ + ci.print(os); + return os; +} + +std::ostream& operator<<(std::ostream& out, CutInfoKlass kl){ + switch(kl){ + case MirCutKlass: + out << "MirCutKlass"; break; + case GmiCutKlass: + out << "GmiCutKlass"; break; + case BranchCutKlass: + out << "BranchCutKlass"; break; + case RowsDeletedKlass: + out << "RowDeletedKlass"; break; + case UnknownKlass: + out << "UnknownKlass"; break; + default: + out << "unexpected CutInfoKlass"; break; + } + return out; +} +bool NodeLog::isBranch() const{ + return d_brVar >= 0; +} + +NodeLog::NodeLog() + : d_nid(-1) + , d_parent(NULL) + , d_tl(NULL) + , d_cuts() + , d_rowIdsSelected() + , d_stat(Open) + , d_brVar(-1) + , d_brVal(0.0) + , d_downId(-1) + , d_upId(-1) + , d_rowId2ArithVar() +{} + +NodeLog::NodeLog(TreeLog* tl, int node, const RowIdMap& m) + : d_nid(node) + , d_parent(NULL) + , d_tl(tl) + , d_cuts() + , d_rowIdsSelected() + , d_stat(Open) + , d_brVar(-1) + , d_brVal(0.0) + , d_downId(-1) + , d_upId(-1) + , d_rowId2ArithVar(m) +{} + +NodeLog::NodeLog(TreeLog* tl, NodeLog* parent, int node) + : d_nid(node) + , d_parent(parent) + , d_tl(tl) + , d_cuts() + , d_rowIdsSelected() + , d_stat(Open) + , d_brVar(-1) + , d_brVal(0.0) + , d_downId(-1) + , d_upId(-1) + , d_rowId2ArithVar() +{} + +NodeLog::~NodeLog(){ + CutSet::iterator i = d_cuts.begin(), iend = d_cuts.end(); + for(; i != iend; ++i){ + CutInfo* c = *i; + delete c; + } + d_cuts.clear(); + Assert(d_cuts.empty()); +} + +std::ostream& operator<<(std::ostream& os, const NodeLog& nl){ + nl.print(os); + return os; +} + +void NodeLog::copyParentRowIds() { + Assert(d_parent != NULL); + d_rowId2ArithVar = d_parent->d_rowId2ArithVar; +} + +int NodeLog::branchVariable() const { + return d_brVar; +} +double NodeLog::branchValue() const{ + return d_brVal; +} +int NodeLog::getNodeId() const { + return d_nid; +} +int NodeLog::getDownId() const{ + return d_downId; +} +int NodeLog::getUpId() const{ + return d_upId; +} +void NodeLog::addSelected(int ord, int sel){ + Assert(d_rowIdsSelected.find(ord) == d_rowIdsSelected.end()); + d_rowIdsSelected[ord] = sel; + Trace("approx::nodelog") << "addSelected("<< ord << ", "<< sel << ")" << endl; +} +void NodeLog::applySelected() { + CutSet::iterator iter = d_cuts.begin(), iend = d_cuts.end(), todelete; + while(iter != iend){ + CutInfo* curr = *iter; + int poolOrd = curr->poolOrdinal(); + if(curr->getRowId() >= 0 ){ + // selected previously, kip + ++iter; + }else if(curr->getKlass() == RowsDeletedKlass){ + // skip + ++iter; + }else if(curr->getKlass() == BranchCutKlass){ + // skip + ++iter; + }else if(d_rowIdsSelected.find(poolOrd) == d_rowIdsSelected.end()){ + todelete = iter; + ++iter; + d_cuts.erase(todelete); + delete curr; + }else{ + Trace("approx::nodelog") << "applySelected " << curr->getId() << " " << poolOrd << "->" << d_rowIdsSelected[poolOrd] << endl; + curr->setRowId( d_rowIdsSelected[poolOrd] ); + ++iter; + } + } + d_rowIdsSelected.clear(); +} + +void NodeLog::applyRowsDeleted(const RowsDeleted& rd) { + std::map currInOrd; //sorted + + const PrimitiveVec& cv = rd.getCutVector(); + std::vector sortedRemoved (cv.inds+1, cv.inds+cv.len+1); + sortedRemoved.push_back(INT_MAX); + std::sort(sortedRemoved.begin(), sortedRemoved.end()); + + if(TraceIsOn("approx::nodelog")){ + Trace("approx::nodelog") << "Removing #" << sortedRemoved.size()<< "..."; + for(unsigned k = 0; kgetId() < rd.getId()){ + if(d_rowId2ArithVar.find(curr->getRowId()) != d_rowId2ArithVar.end()){ + if(curr->getRowId() >= min){ + currInOrd.insert(make_pair(curr->getRowId(), curr)); + } + } + } + ++iter; + } + + RowIdMap::const_iterator i, end; + i=d_rowId2ArithVar.begin(), end = d_rowId2ArithVar.end(); + for(; i != end; ++i){ + int key = (*i).first; + if(key >= min){ + if(currInOrd.find(key) == currInOrd.end()){ + CutInfo* null = NULL; + currInOrd.insert(make_pair(key, null)); + } + } + } + + + + std::map::iterator j, jend; + + int posInSorted = 0; + for(j = currInOrd.begin(), jend=currInOrd.end(); j!=jend; ++j){ + int origOrd = (*j).first; + ArithVar v = d_rowId2ArithVar[origOrd]; + int headRemovedOrd = sortedRemoved[posInSorted]; + while(headRemovedOrd < origOrd){ + ++posInSorted; + headRemovedOrd = sortedRemoved[posInSorted]; + } + // headRemoveOrd >= origOrd + Assert(headRemovedOrd >= origOrd); + + CutInfo* ci = (*j).second; + if(headRemovedOrd == origOrd){ + + if(ci == NULL){ + Trace("approx::nodelog") << "deleting from above because of " << rd << endl; + Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; + d_rowId2ArithVar.erase(origOrd); + }else{ + Trace("approx::nodelog") << "deleting " << ci << " because of " << rd << endl; + Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; + d_rowId2ArithVar.erase(origOrd); + ci->setRowId(-1); + } + }else{ + Assert(headRemovedOrd > origOrd); + // headRemoveOrd > origOrd + int newOrd = origOrd - posInSorted; + Assert(newOrd > 0); + if(ci == NULL){ + Trace("approx::nodelog") << "shifting above down due to " << rd << endl; + Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; + Trace("approx::nodelog") << "now have " << newOrd << " <-> " << v << endl; + d_rowId2ArithVar.erase(origOrd); + mapRowId(newOrd, v); + }else{ + Trace("approx::nodelog") << "shifting " << ci << " down due to " << rd << endl; + Trace("approx::nodelog") << "had " << origOrd << " <-> " << v << endl; + Trace("approx::nodelog") << "now have " << newOrd << " <-> " << v << endl; + ci->setRowId(newOrd); + d_rowId2ArithVar.erase(origOrd); + mapRowId(newOrd, v); + } + } + } + +} + +// void NodeLog::adjustRowId(CutInfo& ci, const RowsDeleted& rd) { +// int origRowId = ci.getRowId(); +// int newRowId = ci.getRowId(); +// ArithVar v = d_rowId2ArithVar[origRowId]; + +// const PrimitiveVec& cv = rd.getCutVector(); + +// for(int j = 1, N = cv.len; j <= N; j++){ +// int ind = cv.inds[j]; +// if(ind == origRowId){ +// newRowId = -1; +// break; +// }else if(ind < origRowId){ +// newRowId--; +// } +// } + +// if(newRowId < 0){ +// cout << "deleting " << ci << " because of " << rd << endl; +// cout << "had " << origRowId << " <-> " << v << endl; +// d_rowId2ArithVar.erase(origRowId); +// ci.setRowId(-1); +// }else if(newRowId != origRowId){ +// cout << "adjusting " << ci << " because of " << rd << endl; +// cout << "had " << origRowId << " <-> " << v << endl; +// cout << "now have " << newRowId << " <-> " << v << endl; +// d_rowId2ArithVar.erase(origRowId); +// ci.setRowId(newRowId); +// mapRowId(newRowId, v); +// }else{ +// cout << "row id unchanged " << ci << " because of " << rd << endl; +// } +// } + + +ArithVar NodeLog::lookupRowId(int rowId) const{ + RowIdMap::const_iterator i = d_rowId2ArithVar.find(rowId); + if(i == d_rowId2ArithVar.end()){ + return ARITHVAR_SENTINEL; + }else{ + return (*i).second; + } +} + +void NodeLog::mapRowId(int rowId, ArithVar v){ + Assert(lookupRowId(rowId) == ARITHVAR_SENTINEL); + Trace("approx::nodelog") + << "On " << getNodeId() + << " adding row id " << rowId << " <-> " << v << endl; + d_rowId2ArithVar[rowId] = v; +} + + + +void NodeLog::addCut(CutInfo* ci){ + Assert(ci != NULL); + d_cuts.insert(ci); +} + +void NodeLog::print(ostream& o) const{ + o << "[n" << getNodeId(); + for(const_iterator iter = begin(), iend = end(); iter != iend; ++iter ){ + CutInfo* cut = *iter; + o << ", " << cut->poolOrdinal(); + if(cut->getRowId() >= 0){ + o << " " << cut->getRowId(); + } + } + o << "]" << std::endl; +} + +void NodeLog::closeNode(){ + Assert(d_stat == Open); + d_stat = Closed; +} + +void NodeLog::setBranch(int br, double val, int d, int u){ + Assert(d_stat == Open); + d_brVar = br; + d_brVal = val; + d_downId = d; + d_upId = u; + d_stat = Branched; +} + +TreeLog::TreeLog() + : next_exec_ord(0) + , d_toNode() + , d_branches() + , d_numCuts(0) + , d_active(false) +{ + NodeLog::RowIdMap empty; + reset(empty); +} + +int TreeLog::getRootId() const{ + return 1; +} + +NodeLog& TreeLog::getRootNode(){ + return getNode(getRootId()); +} + +void TreeLog::clear(){ + next_exec_ord = 0; + d_toNode.clear(); + d_branches.purge(); + + d_numCuts = 0; + + // add root +} + +void TreeLog::reset(const NodeLog::RowIdMap& m){ + clear(); + d_toNode.insert(make_pair(getRootId(), NodeLog(this, getRootId(), m))); +} + +void TreeLog::addCut(){ d_numCuts++; } +uint32_t TreeLog::cutCount() const { return d_numCuts; } +void TreeLog::logBranch(uint32_t x){ + d_branches.add(x); +} +uint32_t TreeLog::numBranches(uint32_t x){ + return d_branches.count(x); +} + +void TreeLog::branch(int nid, int br, double val, int dn, int up){ + NodeLog& nl = getNode(nid); + nl.setBranch(br, val, dn, up); + + d_toNode.insert(make_pair(dn, NodeLog(this, &nl, dn))); + d_toNode.insert(make_pair(up, NodeLog(this, &nl, up))); +} + +void TreeLog::close(int nid){ + NodeLog& nl = getNode(nid); + nl.closeNode(); +} + + + +// void TreeLog::applySelected() { +// std::map::iterator iter, end; +// for(iter = d_toNode.begin(), end = d_toNode.end(); iter != end; ++iter){ +// NodeLog& onNode = (*iter).second; +// //onNode.applySelected(); +// } +// } + +void TreeLog::print(ostream& o) const{ + o << "TreeLog: " << d_toNode.size() << std::endl; + for(const_iterator iter = begin(), iend = end(); iter != iend; ++iter){ + const NodeLog& onNode = (*iter).second; + onNode.print(o); + } +} + +void TreeLog::applyRowsDeleted(int nid, const RowsDeleted& rd){ + NodeLog& nl = getNode(nid); + nl.applyRowsDeleted(rd); +} + +void TreeLog::mapRowId(int nid, int ind, ArithVar v){ + NodeLog& nl = getNode(nid); + nl.mapRowId(ind, v); +} + +void DenseVector::purge() { + lhs.purge(); + rhs = Rational(0); +} + +RowsDeleted::RowsDeleted(int execOrd, int nrows, const int num[]) + : CutInfo(RowsDeletedKlass, execOrd, 0) +{ + d_cutVec.setup(nrows); + for(int j=1; j <= nrows; j++){ + d_cutVec.coeffs[j] = 0; + d_cutVec.inds[j] = num[j]; + } +} + +BranchCutInfo::BranchCutInfo(int execOrd, int br, Kind dir, double val) + : CutInfo(BranchCutKlass, execOrd, 0) +{ + d_cutVec.setup(1); + d_cutVec.inds[1] = br; + d_cutVec.coeffs[1] = +1.0; + d_cutRhs = val; + d_cutType = dir; +} + +void TreeLog::printBranchInfo(ostream& os) const{ + uint32_t total = 0; + DenseMultiset::const_iterator iter = d_branches.begin(), iend = d_branches.end(); + for(; iter != iend; ++iter){ + uint32_t el = *iter; + total += el; + } + os << "printBranchInfo() : " << total << endl; + iter = d_branches.begin(), iend = d_branches.end(); + for(; iter != iend; ++iter){ + uint32_t el = *iter; + os << "["<& v){ + out << "[DenseVec len " << v.size(); + DenseMap::const_iterator iter, end; + for(iter = v.begin(), end = v.end(); iter != end; ++iter){ + ArithVar x = *iter; + out << ", "<< x << " " << v[x]; + } + out << "]"; +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/cut_log.h b/src/theory/arith/linear/cut_log.h new file mode 100644 index 000000000..bfcabc32b --- /dev/null +++ b/src/theory/arith/linear/cut_log.h @@ -0,0 +1,295 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Morgan Deters, Kshitij Bansal + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include +#include +#include +#include + +#include "expr/kind.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/constraint_forward.h" +#include "util/dense_map.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +/** A low level vector of indexed doubles. */ +struct PrimitiveVec { + int len; + int* inds; + double* coeffs; + PrimitiveVec(); + ~PrimitiveVec(); + bool initialized() const; + void clear(); + void setup(int l); + void print(std::ostream& out) const; +}; +std::ostream& operator<<(std::ostream& os, const PrimitiveVec& pv); + +struct DenseVector { + DenseMap lhs; + Rational rhs; + void purge(); + void print(std::ostream& os) const; + + static void print(std::ostream& os, const DenseMap& lhs); +}; + +/** The different kinds of cuts. */ +enum CutInfoKlass{ MirCutKlass, GmiCutKlass, BranchCutKlass, + RowsDeletedKlass, + UnknownKlass}; +std::ostream& operator<<(std::ostream& os, CutInfoKlass kl); + +/** A general class for describing a cut. */ +class CutInfo { +protected: + CutInfoKlass d_klass; + int d_execOrd; + + int d_poolOrd; /* cut's ordinal in the current node pool */ + Kind d_cutType; /* Lowerbound, upperbound or undefined. */ + double d_cutRhs; /* right hand side of the cut */ + PrimitiveVec d_cutVec; /* vector of the cut */ + + /** + * The number of rows at the time the cut was made. + * This is required to descramble indices after the fact! + */ + int d_mAtCreation; + + /** This is the number of structural variables. */ + int d_N; + + /** if selected, make this non-zero */ + int d_rowId; + + /* If the cut has been successfully created, + * the cut is stored in exact precision in d_exactPrecision. + * If the cut has not yet been proven, this is null. + */ + std::unique_ptr d_exactPrecision; + + std::unique_ptr d_explanation; + + public: + CutInfo(CutInfoKlass kl, int cutid, int ordinal); + + virtual ~CutInfo(); + + int getId() const; + + int getRowId() const; + void setRowId(int rid); + + void print(std::ostream& out) const; + //void init_cut(int l); + PrimitiveVec& getCutVector(); + const PrimitiveVec& getCutVector() const; + + Kind getKind() const; + void setKind(Kind k); + + + void setRhs(double r); + double getRhs() const; + + CutInfoKlass getKlass() const; + int poolOrdinal() const; + + void setDimensions(int N, int M); + int getN() const; + int getMAtCreation() const; + + bool operator<(const CutInfo& o) const; + + /* Returns true if the cut was successfully made in exact precision.*/ + bool reconstructed() const; + + /* Returns true if the cut has an explanation. */ + bool proven() const; + + void setReconstruction(const DenseVector& ep); + void setExplanation(const ConstraintCPVec& ex); + void swapExplanation(ConstraintCPVec& ex); + + const DenseVector& getReconstruction() const; + const ConstraintCPVec& getExplanation() const; + + void clearReconstruction(); +}; +std::ostream& operator<<(std::ostream& os, const CutInfo& ci); + +class BranchCutInfo : public CutInfo { +public: + BranchCutInfo(int execOrd, int br, Kind dir, double val); +}; + +class RowsDeleted : public CutInfo { +public: + RowsDeleted(int execOrd, int nrows, const int num[]); +}; + +class TreeLog; + +class NodeLog { +private: + int d_nid; + NodeLog* d_parent; /* If null this is the root */ + TreeLog* d_tl; /* TreeLog containing the node. */ + + struct CmpCutPointer{ + int operator()(const CutInfo* a, const CutInfo* b) const{ + return *a < *b; + } + }; + typedef std::set CutSet; + CutSet d_cuts; + std::map d_rowIdsSelected; + + enum Status {Open, Closed, Branched}; + Status d_stat; + + int d_brVar; // branching variable + double d_brVal; + int d_downId; + int d_upId; + +public: + typedef std::unordered_map RowIdMap; +private: + RowIdMap d_rowId2ArithVar; + +public: + NodeLog(); /* default constructor. */ + NodeLog(TreeLog* tl, int node, const RowIdMap& m); /* makes a root node. */ + NodeLog(TreeLog* tl, NodeLog* parent, int node);/* makes a non-root node. */ + + ~NodeLog(); + + int getNodeId() const; + void addSelected(int ord, int sel); + void applySelected(); + void addCut(CutInfo* ci); + void print(std::ostream& o) const; + + bool isRoot() const; + const NodeLog& getParent() const; + + void copyParentRowIds(); + + bool isBranch() const; + int branchVariable() const; + double branchValue() const; + + typedef CutSet::const_iterator const_iterator; + const_iterator begin() const; + const_iterator end() const; + + void setBranch(int br, double val, int dn, int up); + void closeNode(); + + int getDownId() const; + int getUpId() const; + + /** + * Looks up a row id to the appropriate arith variable. + * Be careful these are deleted in context during replay! + * failure returns ARITHVAR_SENTINEL */ + ArithVar lookupRowId(int rowId) const; + + /** + * Maps a row id to an arithvar. + * Be careful these are deleted in context during replay! + */ + void mapRowId(int rowid, ArithVar v); + void applyRowsDeleted(const RowsDeleted& rd); + +}; +std::ostream& operator<<(std::ostream& os, const NodeLog& nl); + +class TreeLog { +private: + int next_exec_ord; + typedef std::map ToNodeMap; + ToNodeMap d_toNode; + DenseMultiset d_branches; + + uint32_t d_numCuts; + + bool d_active; + +public: + TreeLog(); + + NodeLog& getNode(int nid); + void branch(int nid, int br, double val, int dn, int up); + void close(int nid); + + //void applySelected(); + void print(std::ostream& o) const; + + typedef ToNodeMap::const_iterator const_iterator; + const_iterator begin() const; + const_iterator end() const; + + int getExecutionOrd(); + + void reset(const NodeLog::RowIdMap& m); + + // Applies rd tp to the node with id nid + void applyRowsDeleted(int nid, const RowsDeleted& rd); + + // Synonym for getNode(nid).mapRowId(ind, v) + void mapRowId(int nid, int ind, ArithVar v); + +private: + void clear(); + +public: + void makeInactive(); + void makeActive(); + + bool isActivelyLogging() const; + + void addCut(); + uint32_t cutCount() const; + + void logBranch(uint32_t x); + uint32_t numBranches(uint32_t x); + + int getRootId() const; + + uint32_t numNodes() const{ + return d_toNode.size(); + } + + NodeLog& getRootNode(); + void printBranchInfo(std::ostream& os) const; +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/dio_solver.cpp b/src/theory/arith/linear/dio_solver.cpp new file mode 100644 index 000000000..b3eede0dd --- /dev/null +++ b/src/theory/arith/linear/dio_solver.cpp @@ -0,0 +1,832 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * Diophantine equation solver + * + * A Diophantine equation solver for the theory of arithmetic. + */ +#include "theory/arith/linear/dio_solver.h" + +#include + +#include "base/output.h" +#include "expr/skolem_manager.h" +#include "options/arith_options.h" +#include "smt/env.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/partial_model.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +inline Node makeIntegerVariable(){ + NodeManager* nm = NodeManager::currentNM(); + SkolemManager* sm = nm->getSkolemManager(); + return sm->mkDummySkolem("intvar", + nm->integerType(), + "is an integer variable created by the dio solver"); +} + +DioSolver::DioSolver(Env& env) + : EnvObj(env), + d_lastUsedProofVariable(context(), 0), + d_inputConstraints(context()), + d_nextInputConstraintToEnqueue(context(), 0), + d_trail(context()), + d_subs(context()), + d_currentF(), + d_savedQueue(context()), + d_savedQueueIndex(context(), 0), + d_conflictIndex(context()), + d_maxInputCoefficientLength(context(), 0), + d_usedDecomposeIndex(context(), false), + d_lastPureSubstitution(context(), 0), + d_pureSubstitionIter(context(), 0), + d_decompositionLemmaQueue(context()) +{ +} + +DioSolver::Statistics::Statistics() + : d_conflictCalls(smtStatisticsRegistry().registerInt( + "theory::arith::dio::conflictCalls")), + d_cutCalls( + smtStatisticsRegistry().registerInt("theory::arith::dio::cutCalls")), + d_cuts(smtStatisticsRegistry().registerInt("theory::arith::dio::cuts")), + d_conflicts( + smtStatisticsRegistry().registerInt("theory::arith::dio::conflicts")), + d_conflictTimer(smtStatisticsRegistry().registerTimer( + "theory::arith::dio::conflictTimer")), + d_cutTimer( + smtStatisticsRegistry().registerTimer("theory::arith::dio::cutTimer")) +{ +} + +bool DioSolver::queueConditions(TrailIndex t){ + Trace("queueConditions") << !inConflict() << std::endl; + Trace("queueConditions") << gcdIsOne(t) << std::endl; + Trace("queueConditions") << !debugAnySubstitionApplies(t) << std::endl; + Trace("queueConditions") << !triviallySat(t) << std::endl; + Trace("queueConditions") << !triviallyUnsat(t) << std::endl; + + return + !inConflict() && + gcdIsOne(t) && + !debugAnySubstitionApplies(t) && + !triviallySat(t) && + !triviallyUnsat(t); +} + +size_t DioSolver::allocateProofVariable() { + Assert(d_lastUsedProofVariable <= d_proofVariablePool.size()); + if(d_lastUsedProofVariable == d_proofVariablePool.size()){ + Assert(d_lastUsedProofVariable == d_proofVariablePool.size()); + Node intVar = makeIntegerVariable(); + d_proofVariablePool.push_back(Variable(intVar)); + } + size_t res = d_lastUsedProofVariable; + d_lastUsedProofVariable = d_lastUsedProofVariable + 1; + return res; +} + + +Node DioSolver::nextPureSubstitution(){ + Assert(hasMorePureSubstitutions()); + SubIndex curr = d_pureSubstitionIter; + d_pureSubstitionIter = d_pureSubstitionIter + 1; + + Assert(d_subs[curr].d_fresh.isNull()); + Variable v = d_subs[curr].d_eliminated; + + SumPair sp = d_trail[d_subs[curr].d_constraint].d_eq; + Polynomial p = sp.getPolynomial(); + Constant c = -sp.getConstant(); + Polynomial cancelV = p + Polynomial::mkPolynomial(v); + Node eq = NodeManager::currentNM()->mkNode(kind::EQUAL, v.getNode(), cancelV.getNode()); + return eq; +} + + +bool DioSolver::debugEqualityInInputEquations(Node eq){ + typedef context::CDList::const_iterator const_iterator; + const_iterator i=d_inputConstraints.begin(), end = d_inputConstraints.end(); + for(; i != end; ++i){ + Node reason_i = (*i).d_reason; + if(eq == reason_i){ + return true; + } + } + return false; +} + + +void DioSolver::pushInputConstraint(const Comparison& eq, Node reason){ + Assert(!debugEqualityInInputEquations(reason)); + Assert(eq.debugIsIntegral()); + Assert(eq.getNode().getKind() == kind::EQUAL); + + SumPair sp = eq.toSumPair(); + if(sp.isNonlinear()){ + return; + } + + + + uint32_t length = sp.maxLength(); + if(length > d_maxInputCoefficientLength){ + d_maxInputCoefficientLength = length; + } + + size_t varIndex = allocateProofVariable(); + Variable proofVariable(d_proofVariablePool[varIndex]); + //Variable proofVariable(makeIntegerVariable()); + + TrailIndex posInTrail = d_trail.size(); + Trace("dio::pushInputConstraint") << "pushInputConstraint @ " << posInTrail + << " " << eq.getNode() + << " " << reason << endl; + d_trail.push_back(Constraint(sp,Polynomial::mkPolynomial(proofVariable))); + + size_t posInConstraintList = d_inputConstraints.size(); + d_inputConstraints.push_back(InputConstraint(reason, posInTrail)); + + d_varToInputConstraintMap[proofVariable.getNode()] = posInConstraintList; +} + + +DioSolver::TrailIndex DioSolver::scaleEqAtIndex(DioSolver::TrailIndex i, const Integer& g){ + Assert(g != 0); + Constant invg = Constant::mkConstant(Rational(Integer(1),g)); + const SumPair& sp = d_trail[i].d_eq; + const Polynomial& proof = d_trail[i].d_proof; + + SumPair newSP = sp * invg; + Polynomial newProof = proof * invg; + + Assert(newSP.isIntegral()); + Assert(newSP.gcd() == 1); + + TrailIndex j = d_trail.size(); + + d_trail.push_back(Constraint(newSP, newProof)); + + Trace("arith::dio") << "scaleEqAtIndex(" << i <<","<= 2 && + length > d_maxInputCoefficientLength + MAX_GROWTH_RATE; + if(TraceIsOn("arith::dio::max") && result){ + + const SumPair& eq = d_trail[j].d_eq; + const Polynomial& proof = d_trail[j].d_proof; + + Trace("arith::dio::max") << "about to drop:" << std::endl; + Trace("arith::dio::max") << "d_trail[" << j << "].d_eq = " << eq.getNode() << std::endl; + Trace("arith::dio::max") << "d_trail[" << j << "].d_proof = " << proof.getNode() << std::endl; + } + return result; +} + +void DioSolver::enqueueInputConstraints(){ + Assert(d_currentF.empty()); + while(d_savedQueueIndex < d_savedQueue.size()){ + d_currentF.push_back(d_savedQueue[d_savedQueueIndex]); + d_savedQueueIndex = d_savedQueueIndex + 1; + } + + while(d_nextInputConstraintToEnqueue < d_inputConstraints.size() && !inConflict()){ + size_t curr = d_nextInputConstraintToEnqueue; + d_nextInputConstraintToEnqueue = d_nextInputConstraintToEnqueue + 1; + + TrailIndex i = d_inputConstraints[curr].d_trailPos; + TrailIndex j = applyAllSubstitutionsToIndex(i); + + if(!triviallySat(j)){ + if(triviallyUnsat(j)){ + raiseConflict(j); + }else{ + TrailIndex k = reduceByGCD(j); + + if(!inConflict()){ + if(triviallyUnsat(k)){ + raiseConflict(k); + }else if(!(triviallySat(k) || anyCoefficientExceedsMaximum(k))){ + pushToQueueBack(k); + } + } + } + } + } +} + + +/*TODO Currently linear in the size of the Queue + *It is not clear if am O(log n) strategy would be better. + *Right before this in the algorithm is a substitution which could potentially + *effect the key values of everything in the queue. + */ +void DioSolver::moveMinimumByAbsToQueueFront(){ + Assert(!queueEmpty()); + + //Select the minimum element. + size_t indexInQueue = 0; + Monomial minMonomial = d_trail[d_currentF[indexInQueue]].d_minimalMonomial; + + size_t N = d_currentF.size(); + for(size_t i=1; i < N; ++i){ + Monomial curr = d_trail[d_currentF[i]].d_minimalMonomial; + if(curr.absCmp(minMonomial) < 0){ + indexInQueue = i; + minMonomial = curr; + } + } + + TrailIndex tmp = d_currentF[indexInQueue]; + d_currentF[indexInQueue] = d_currentF.front(); + d_currentF.front() = tmp; +} + +bool DioSolver::queueEmpty() const{ + return d_currentF.empty(); +} + +Node DioSolver::columnGcdIsOne() const{ + std::unordered_map gcdMap; + + std::deque::const_iterator iter, end; + for(iter = d_currentF.begin(), end = d_currentF.end(); iter != end; ++iter){ + TrailIndex curr = *iter; + Polynomial p = d_trail[curr].d_eq.getPolynomial(); + Polynomial::iterator monoIter = p.begin(), monoEnd = p.end(); + for(; monoIter != monoEnd; ++monoIter){ + Monomial m = *monoIter; + VarList vl = m.getVarList(); + Node vlNode = vl.getNode(); + + Constant c = m.getConstant(); + Integer zc = c.getValue().getNumerator(); + if(gcdMap.find(vlNode) == gcdMap.end()){ + // have not seen vl yet. + // gcd is c + Assert(c.isIntegral()); + Assert(!m.absCoefficientIsOne()); + gcdMap.insert(make_pair(vlNode, zc.abs())); + }else{ + const Integer& currentGcd = gcdMap[vlNode]; + Integer newGcd = currentGcd.gcd(zc); + if(newGcd == 1){ + return vlNode; + }else{ + gcdMap[vlNode] = newGcd; + } + } + } + } + return Node::null(); +} + +void DioSolver::saveQueue(){ + std::deque::const_iterator iter, end; + for(iter = d_currentF.begin(), end = d_currentF.end(); iter != end; ++iter){ + d_savedQueue.push_back(*iter); + } +} + +DioSolver::TrailIndex DioSolver::impliedGcdOfOne(){ + Node canReduce = columnGcdIsOne(); + if(canReduce.isNull()){ + return 0; + }else{ + VarList vl = VarList::parseVarList(canReduce); + + TrailIndex current; + Integer currentCoeff, currentGcd; + + //step 1 find the first equation containing vl + //Set current and currentCoefficient + std::deque::const_iterator iter, end; + for(iter = d_currentF.begin(), end = d_currentF.end(); true; ++iter){ + Assert(iter != end); + current = *iter; + Constant coeff = d_trail[current].d_eq.getPolynomial().getCoefficient(vl); + if(!coeff.isZero()){ + currentCoeff = coeff.getValue().getNumerator(); + currentGcd = currentCoeff.abs(); + + ++iter; + break; + } + } + + //For the rest of the equations keep reducing until the coefficient is one + for(; iter != end; ++iter){ + Trace("arith::dio") << "next round : " << currentCoeff << " " << currentGcd << endl; + TrailIndex inQueue = *iter; + Constant iqc = d_trail[inQueue].d_eq.getPolynomial().getCoefficient(vl); + if(!iqc.isZero()){ + Integer inQueueCoeff = iqc.getValue().getNumerator(); + + //mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, mpz_t a, mpz_t b); + Integer g, s, t; + // g = a*s + b*t + Integer::extendedGcd(g, s, t, currentCoeff, inQueueCoeff); + + Trace("arith::dio") << "extendedReduction : " << endl; + Trace("arith::dio") << g << " = " << s <<"*"<< currentCoeff << " + " << t <<"*"<< inQueueCoeff << endl; + + Assert(g <= currentGcd); + if(g < currentGcd){ + if(s.sgn() == 0){ + Trace("arith::dio") << "extendedReduction drop" << endl; + Assert(inQueueCoeff.divides(currentGcd)); + current = *iter; + currentCoeff = inQueueCoeff; + currentGcd = inQueueCoeff.abs(); + }else{ + + Trace("arith::dio") << "extendedReduction combine" << endl; + TrailIndex next = combineEqAtIndexes(current, s, inQueue, t); + + Assert(d_trail[next] + .d_eq.getPolynomial() + .getCoefficient(vl) + .getValue() + .getNumerator() + == g); + + current = next; + currentCoeff = g; + currentGcd = g; + if(currentGcd == 1){ + return current; + } + } + } + } + } + //This is not reachble as it is assured that the gcd of the column is 1 + Unreachable(); + } +} + +bool DioSolver::processEquations(bool allowDecomposition){ + Assert(!inConflict()); + + enqueueInputConstraints(); + while(! queueEmpty() && !inConflict()){ + moveMinimumByAbsToQueueFront(); + + TrailIndex minimum = d_currentF.front(); + TrailIndex reduceIndex; + + Assert(inRange(minimum)); + Assert(!inConflict()); + + Trace("arith::dio") << "processEquations " << minimum << " : " << d_trail[minimum].d_eq.getNode() << endl; + + Assert(queueConditions(minimum)); + + bool canDirectlySolve = d_trail[minimum].d_minimalMonomial.absCoefficientIsOne(); + + std::pair p; + if(canDirectlySolve){ + d_currentF.pop_front(); + p = solveIndex(minimum); + reduceIndex = minimum; + }else{ + TrailIndex implied = impliedGcdOfOne(); + + if(implied != 0){ + p = solveIndex(implied); + reduceIndex = implied; + }else if(allowDecomposition){ + d_currentF.pop_front(); + p = decomposeIndex(minimum); + reduceIndex = minimum; + }else { + // Cannot make progress without decomposeIndex + saveQueue(); + break; + } + } + + SubIndex subIndex = p.first; + TrailIndex next = p.second; + subAndReduceCurrentFByIndex(subIndex); + + if(next != reduceIndex){ + if(triviallyUnsat(next)){ + raiseConflict(next); + }else if(! triviallySat(next) ){ + pushToQueueBack(next); + } + } + } + + d_currentF.clear(); + return inConflict(); +} + +Node DioSolver::processEquationsForConflict(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_conflictTimer); + ++(d_statistics.d_conflictCalls); + + Assert(!inConflict()); + if(processEquations(true)){ + ++(d_statistics.d_conflicts); + return proveIndex(getConflictIndex()); + }else{ + return Node::null(); + } +} + +SumPair DioSolver::processEquationsForCut(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_cutTimer); + ++(d_statistics.d_cutCalls); + + Assert(!inConflict()); + if(processEquations(true)){ + ++(d_statistics.d_cuts); + return purifyIndex(getConflictIndex()); + }else{ + return SumPair::mkZero(); + } +} + + +SumPair DioSolver::purifyIndex(TrailIndex i){ + // TODO: "This uses the substitution trail to reverse the substitutions from the sum term. Using the proof term should be more efficient." + + SumPair curr = d_trail[i].d_eq; + + Constant negOne = Constant::mkConstant(-1); + + for(uint32_t revIter = d_subs.size(); revIter > 0; --revIter){ + uint32_t i2 = revIter - 1; + Node freshNode = d_subs[i2].d_fresh; + if(freshNode.isNull()){ + continue; + }else{ + Variable var(freshNode); + Polynomial vsum = curr.getPolynomial(); + + Constant a = vsum.getCoefficient(VarList(var)); + if(!a.isZero()){ + const SumPair& sj = d_trail[d_subs[i2].d_constraint].d_eq; + Assert(sj.getPolynomial().getCoefficient(VarList(var)).isOne()); + SumPair newSi = (curr * negOne) + (sj * a); + Assert(newSi.getPolynomial().getCoefficient(VarList(var)).isZero()); + curr = newSi; + } + } + } + return curr; +} + +DioSolver::TrailIndex DioSolver::combineEqAtIndexes(DioSolver::TrailIndex i, const Integer& q, DioSolver::TrailIndex j, const Integer& r){ + Constant cq = Constant::mkConstant(q); + Constant cr = Constant::mkConstant(r); + + const SumPair& si = d_trail[i].d_eq; + const SumPair& sj = d_trail[j].d_eq; + + Trace("arith::dio") << "combineEqAtIndexes(" << i <<","< DioSolver::decomposeIndex(DioSolver::TrailIndex i){ + const SumPair& si = d_trail[i].d_eq; + + d_usedDecomposeIndex = true; + + Trace("arith::dio") << "before decomposeIndex("< 1); + + //It is not sufficient to reduce the case where abs(a) == 1 to abs(a) > 1. + //We need to handle both cases seperately to ensure termination. + Node qr = SumPair::computeQR(si, a.getValue().getNumerator()); + + Assert(qr.getKind() == kind::ADD); + Assert(qr.getNumChildren() == 2); + SumPair q = SumPair::parseSumPair(qr[0]); + SumPair r = SumPair::parseSumPair(qr[1]); + + Assert(q.getPolynomial().getCoefficient(vl) == Constant::mkConstant(1)); + + Assert(!r.isZero()); + Node freshNode = makeIntegerVariable(); + Variable fresh(freshNode); + SumPair fresh_one=SumPair::mkSumPair(fresh); + SumPair fresh_a = fresh_one * a; + + SumPair newSI = SumPair(fresh_one) - q; + // this normalizes the coefficient of var to -1 + + + TrailIndex ci = d_trail.size(); + d_trail.push_back(Constraint(newSI, Polynomial::mkZero())); + // no longer reference av safely! + addTrailElementAsLemma(ci); + + Trace("arith::dio") << "Decompose ci(" << ci <<":" << d_trail[ci].d_eq.getNode() + << ") for " << d_trail[i].d_minimalMonomial.getNode() << endl; + Assert(d_trail[ci].d_eq.getPolynomial().getCoefficient(vl) + == Constant::mkConstant(-1)); + + SumPair newFact = r + fresh_a; + + TrailIndex nextIndex = d_trail.size(); + d_trail.push_back(Constraint(newFact, d_trail[i].d_proof)); + + SubIndex subBy = d_subs.size(); + d_subs.push_back(Substitution(freshNode, var, ci)); + + Trace("arith::dio") << "Decompose nextIndex " << d_trail[nextIndex].d_eq.getNode() << endl; + return make_pair(subBy, nextIndex); +} + + +DioSolver::TrailIndex DioSolver::applySubstitution(DioSolver::SubIndex si, DioSolver::TrailIndex ti){ + Variable var = d_subs[si].d_eliminated; + TrailIndex subIndex = d_subs[si].d_constraint; + + const SumPair& curr = d_trail[ti].d_eq; + Polynomial vsum = curr.getPolynomial(); + + Constant a = vsum.getCoefficient(VarList(var)); + Assert(a.isIntegral()); + if(!a.isZero()){ + Integer one(1); + TrailIndex afterSub = combineEqAtIndexes(ti, one, subIndex, a.getValue().getNumerator()); + Assert(d_trail[afterSub] + .d_eq.getPolynomial() + .getCoefficient(VarList(var)) + .isZero()); + return afterSub; + }else{ + return ti; + } +} + + +DioSolver::TrailIndex DioSolver::reduceByGCD(DioSolver::TrailIndex ti){ + const SumPair& sp = d_trail[ti].d_eq; + Polynomial vsum = sp.getPolynomial(); + Constant c = sp.getConstant(); + + Trace("arith::dio") << "reduceByGCD " << vsum.getNode() << endl; + Assert(!vsum.isConstant()); + Integer g = vsum.gcd(); + Assert(g >= 1); + Trace("arith::dio") << "gcd("<< vsum.getNode() <<")=" << g << " " << c.getValue() << endl; + if(g.divides(c.getValue().getNumerator())){ + if(g > 1){ + return scaleEqAtIndex(ti, g); + }else{ + return ti; + } + }else{ + raiseConflict(ti); + return ti; + } +} + +bool DioSolver::triviallySat(TrailIndex i){ + const SumPair& eq = d_trail[i].d_eq; + if(eq.isConstant()){ + return eq.getConstant().isZero(); + }else{ + return false; + } +} + +bool DioSolver::triviallyUnsat(DioSolver::TrailIndex i){ + const SumPair& eq = d_trail[i].d_eq; + if(eq.isConstant()){ + return !eq.getConstant().isZero(); + }else{ + return false; + } +} + + +bool DioSolver::gcdIsOne(DioSolver::TrailIndex i){ + const SumPair& eq = d_trail[i].d_eq; + return eq.gcd() == Integer(1); +} + +void DioSolver::subAndReduceCurrentFByIndex(DioSolver::SubIndex subIndex){ + size_t N = d_currentF.size(); + + size_t readIter = 0, writeIter = 0; + for(; readIter < N && !inConflict(); ++readIter){ + TrailIndex curr = d_currentF[readIter]; + TrailIndex nextTI = applySubstitution(subIndex, curr); + if(nextTI == curr){ + d_currentF[writeIter] = curr; + ++writeIter; + }else{ + Assert(nextTI != curr); + + if(triviallyUnsat(nextTI)){ + raiseConflict(nextTI); + }else if(!triviallySat(nextTI)){ + TrailIndex nextNextTI = reduceByGCD(nextTI); + + if(!(inConflict() || anyCoefficientExceedsMaximum(nextNextTI))){ + Assert(queueConditions(nextNextTI)); + d_currentF[writeIter] = nextNextTI; + ++writeIter; + } + } + } + } + if(!inConflict() && writeIter < N){ + d_currentF.resize(writeIter); + } +} + +void DioSolver::addTrailElementAsLemma(TrailIndex i) { + if (options().arith.exportDioDecompositions) + { + d_decompositionLemmaQueue.push(i); + } +} + +Node DioSolver::trailIndexToEquality(TrailIndex i) const { + const SumPair& sp = d_trail[i].d_eq; + Node n = sp.getNode(); + Node zero = + NodeManager::currentNM()->mkConstRealOrInt(n.getType(), Rational(0)); + Node eq = n.eqNode(zero); + return eq; +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/dio_solver.h b/src/theory/arith/linear/dio_solver.h new file mode 100644 index 000000000..9887a2396 --- /dev/null +++ b/src/theory/arith/linear/dio_solver.h @@ -0,0 +1,424 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Morgan Deters, Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * A Diophantine equation solver for the theory of arithmetic. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__DIO_SOLVER_H +#define CVC5__THEORY__ARITH__DIO_SOLVER_H + +#include +#include +#include + +#include "context/cdlist.h" +#include "context/cdmaybe.h" +#include "context/cdo.h" +#include "context/cdqueue.h" +#include "smt/env_obj.h" +#include "theory/arith/linear/normal_form.h" +#include "util/rational.h" +#include "util/statistics_stats.h" + +namespace cvc5::context { +class Context; +} +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class DioSolver : protected EnvObj +{ + private: + typedef size_t TrailIndex; + typedef size_t InputConstraintIndex; + typedef size_t SubIndex; + + std::vector d_proofVariablePool; + /** Sat context dependent. */ + context::CDO d_lastUsedProofVariable; + + /** + * The set of input constraints is stored in a CDList. + * Each constraint point to an element of the trail. + */ + struct InputConstraint { + Node d_reason; + TrailIndex d_trailPos; + InputConstraint(Node reason, TrailIndex pos) : d_reason(reason), d_trailPos(pos) {} + }; + context::CDList d_inputConstraints; + + /** + * This is the next input constraint to handle. + */ + context::CDO d_nextInputConstraintToEnqueue; + + /** + * We maintain a map from the variables associated with proofs to an input constraint. + * These variables can then be used in polynomial manipulations. + */ + typedef std::unordered_map + NodeToInputConstraintIndexMap; + NodeToInputConstraintIndexMap d_varToInputConstraintMap; + + Node proofVariableToReason(const Variable& v) const{ + Assert(d_varToInputConstraintMap.find(v.getNode()) + != d_varToInputConstraintMap.end()); + InputConstraintIndex pos = (*(d_varToInputConstraintMap.find(v.getNode()))).second; + Assert(pos < d_inputConstraints.size()); + return d_inputConstraints[pos].d_reason; + } + + /** + * The main work horse of the algorithm, the trail of constraints. + * Each constraint is a SumPair that implicitly represents an equality against 0. + * d_trail[i].d_eq = (+ c (+ [(* coeff var)])) representing (+ [(* coeff var)]) = -c + * Each constraint has a proof in terms of a linear combination of the input constraints. + * d_trail[i].d_proof + * + * Each Constraint also a monomial in d_eq.getPolynomial() + * of minimal absolute value by the coefficients. + * d_trail[i].d_minimalMonomial + * + * See Alberto's paper for how linear proofs are maintained for the abstract + * state machine in rules (7), (8) and (9). + */ + struct Constraint { + SumPair d_eq; + Polynomial d_proof; + Monomial d_minimalMonomial; + Constraint(const SumPair& eq, const Polynomial& p) : + d_eq(eq), d_proof(p), d_minimalMonomial(d_eq.getPolynomial().selectAbsMinimum()) + {} + }; + context::CDList d_trail; + + // /** Compare by d_minimal. */ + // struct TrailMinimalCoefficientOrder { + // const context::CDList& d_trail; + // TrailMinimalCoefficientOrder(const context::CDList& + // trail): + // d_trail(trail) + // {} + + // bool operator()(TrailIndex i, TrailIndex j){ + // return d_trail[i].d_minimalMonomial.absLessThan(d_trail[j].d_minimalMonomial); + // } + // }; + + /** + * A substitution is stored as a constraint in the trail together with + * the variable to be eliminated, and a fresh variable if one was introduced. + * The variable d_subs[i].d_eliminated is substituted using the implicit equality in + * d_trail[d_subs[i].d_constraint] + * - d_subs[i].d_eliminated is normalized to have coefficient -1 in + * d_trail[d_subs[i].d_constraint]. + * - d_subs[i].d_fresh is either Node::null() or it is variable it is normalized + * to have coefficient 1 in d_trail[d_subs[i].d_constraint]. + */ + struct Substitution { + Node d_fresh; + Variable d_eliminated; + TrailIndex d_constraint; + Substitution(Node f, const Variable& e, TrailIndex c) : + d_fresh(f), d_eliminated(e), d_constraint(c) + {} + }; + context::CDList d_subs; + + /** + * This is the queue of constraints to be processed in the current context level. + * This is to be empty upon entering solver and cleared upon leaving the solver. + * + * All elements in currentF: + * - are fully substituted according to d_subs. + * - !isConstant(). + * - If the element is (+ constant (+ [(* coeff var)] )), then the gcd(coeff) = 1 + */ + std::deque d_currentF; + context::CDList d_savedQueue; + context::CDO d_savedQueueIndex; + context::CDMaybe d_conflictIndex; + + /** + * Drop derived constraints with a coefficient length larger than + * the maximum input constraints length than 2**MAX_GROWTH_RATE. + */ + context::CDO d_maxInputCoefficientLength; + static constexpr uint32_t MAX_GROWTH_RATE = 3; + + /** Returns true if the element on the trail should be dropped.*/ + bool anyCoefficientExceedsMaximum(TrailIndex j) const; + + /** + * Is true if decomposeIndex has been used in this context. + */ + context::CDO d_usedDecomposeIndex; + + context::CDO d_lastPureSubstitution; + context::CDO d_pureSubstitionIter; + + /** + * Decomposition lemma queue. + */ + context::CDQueue d_decompositionLemmaQueue; + + public: + /** Construct a Diophantine equation solver with the given context. */ + DioSolver(Env& env); + + /** Returns true if the substitutions use no new variables. */ + bool hasMorePureSubstitutions() const + { + return d_pureSubstitionIter < d_lastPureSubstitution; + } + + Node nextPureSubstitution(); + + /** + * Adds an equality to the queue of the DioSolver. + * orig is blamed in a conflict. + * orig can either be of the form (= p c) or (and ub lb). + * where ub is either (leq p c) or (not (> p [- c 1])), and + * where lb is either (geq p c) or (not (< p [+ c 1])) + * + * If eq cannot be used, this constraint is dropped. + */ + void pushInputConstraint(const Comparison& eq, Node reason); + + /** + * Processes the queue looking for any conflict. + * If a conflict is found, this returns conflict. + * Otherwise, it returns null. + * The conflict is guarenteed to be over literals given in addEquality. + */ + Node processEquationsForConflict(); + + /** + * Processes the queue looking for an integer unsatisfiable cutting plane. + * If such a plane is found this returns an entailed plane using no + * fresh variables. + */ + SumPair processEquationsForCut(); + +private: + /** Returns true if the TrailIndex refers to a element in the trail. */ + bool inRange(TrailIndex i) const{ + return i < d_trail.size(); + } + + Node columnGcdIsOne() const; + + + /** + * Returns true if the context dependent flag for conflicts + * has been raised. + */ + bool inConflict() const { return d_conflictIndex.isSet(); } + + /** Raises a conflict at the index ti. */ + void raiseConflict(TrailIndex ti){ + Assert(!inConflict()); + d_conflictIndex.set(ti); + } + + /** Returns the conflict index. */ + TrailIndex getConflictIndex() const{ + Assert(inConflict()); + return d_conflictIndex.get(); + } + + /** + * Allocates a "unique" proof variable. + * This variable is fresh with respect to the context. + * Returns index of the variable in d_variablePool; + */ + size_t allocateProofVariable(); + + + /** Empties the unproccessed input constraints into the queue. */ + void enqueueInputConstraints(); + + /** + * Returns true if an input equality is in the map. + * This is expensive and is only for debug assertions. + */ + bool debugEqualityInInputEquations(Node eq); + + /** Applies the substitution at subIndex to currentF. */ + void subAndReduceCurrentFByIndex(SubIndex d_subIndex); + + /** + * Takes as input a TrailIndex i and an integer that divides d_trail[i].d_eq, and + * returns a TrailIndex j s.t. + * d_trail[j].d_eq = (1/g) d_trail[i].d_eq + * and + * d_trail[j].d_proof = (1/g) d_trail[i].d_proof. + * + * g must be non-zero. + * + * This corresponds to an application of Alberto's rule (7). + */ + TrailIndex scaleEqAtIndex(TrailIndex i, const Integer& g); + + + /** + * Takes as input TrailIndex's i and j and Integer's q and r and a TrailIndex k s.t. + * d_trail[k].d_eq == d_trail[i].d_eq * q + d_trail[j].d_eq * r + * and + * d_trail[k].d_proof == d_trail[i].d_proof * q + d_trail[j].d_proof * r + * + * This corresponds to an application of Alberto's rule (8). + */ + TrailIndex combineEqAtIndexes(TrailIndex i, const Integer& q, TrailIndex j, const Integer& r); + + /** + * Decomposes the equation at index ti of trail by the variable + * with the lowest coefficient. + * This corresponds to an application of Alberto's rule (9). + * + * Returns a pair of a SubIndex and a TrailIndex. + * The SubIndex is the index of a newly introduced substition. + */ + std::pair decomposeIndex(TrailIndex ti); + + /** Solves the index at ti for the value in minimumMonomial. */ + std::pair solveIndex(TrailIndex ti); + + /** Prints the queue for debugging purposes to Trace("arith::dio"). */ + void printQueue(); + + /** + * Exhaustively applies all substitutions discovered to an element of the trail. + * Returns a TrailIndex corresponding to the substitutions being applied. + */ + TrailIndex applyAllSubstitutionsToIndex(TrailIndex i); + + /** + * Applies a substitution to an element in the trail. + */ + TrailIndex applySubstitution(SubIndex s, TrailIndex i); + + /** + * Reduces the trail node at i by the gcd of the variables. + * Returns the new trail element. + * + * This raises the conflict flag if unsat is detected. + */ + TrailIndex reduceByGCD(TrailIndex i); + + /** + * Returns true if i'th element in the trail is trivially true. + * (0 = 0) + */ + bool triviallySat(TrailIndex t); + + /** + * Returns true if i'th element in the trail is trivially unsatisfiable. + * (1 = 0) + */ + bool triviallyUnsat(TrailIndex t); + + /** Returns true if the gcd of the i'th element of the trail is 1.*/ + bool gcdIsOne(TrailIndex t); + + bool debugAnySubstitionApplies(TrailIndex t); + bool debugSubstitutionApplies(SubIndex si, TrailIndex ti); + + + /** Returns true if the queue of nodes to process is empty. */ + bool queueEmpty() const; + + bool queueConditions(TrailIndex t); + + + void pushToQueueBack(TrailIndex t){ + Assert(queueConditions(t)); + d_currentF.push_back(t); + } + + void pushToQueueFront(TrailIndex t){ + Assert(queueConditions(t)); + d_currentF.push_front(t); + } + + /** + * Moves the minimum Constraint by absolute value of the minimum coefficient to + * the front of the queue. + */ + void moveMinimumByAbsToQueueFront(); + + void saveQueue(); + + TrailIndex impliedGcdOfOne(); + + + /** + * Processing the current set of equations. + * + * decomposeIndex() rule is only applied if allowDecomposition is true. + */ + bool processEquations(bool allowDecomposition); + + /** + * Constructs a proof from any d_trail[i] in terms of input literals. + */ + Node proveIndex(TrailIndex i); + + /** + * Returns the SumPair in d_trail[i].d_eq with all of the fresh variables purified out. + */ + SumPair purifyIndex(TrailIndex i); + +public: + bool hasMoreDecompositionLemmas() const{ + return !d_decompositionLemmaQueue.empty(); + } + Node nextDecompositionLemma() { + Assert(hasMoreDecompositionLemmas()); + TrailIndex front = d_decompositionLemmaQueue.front(); + d_decompositionLemmaQueue.pop(); + return trailIndexToEquality(front); + } +private: + Node trailIndexToEquality(TrailIndex i) const; + void addTrailElementAsLemma(TrailIndex i); + +public: + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + + IntStat d_conflictCalls; + IntStat d_cutCalls; + + IntStat d_cuts; + IntStat d_conflicts; + + TimerStat d_conflictTimer; + TimerStat d_cutTimer; + + Statistics(); + }; + + Statistics d_statistics; +}; /* class DioSolver */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__DIO_SOLVER_H */ diff --git a/src/theory/arith/linear/dual_simplex.cpp b/src/theory/arith/linear/dual_simplex.cpp new file mode 100644 index 000000000..3ef92bc6b --- /dev/null +++ b/src/theory/arith/linear/dual_simplex.cpp @@ -0,0 +1,245 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + */ +#include "theory/arith/linear/dual_simplex.h" + +#include "base/output.h" +#include "options/arith_options.h" +#include "smt/env.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/error_set.h" +#include "theory/arith/linear/linear_equality.h" + + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +DualSimplexDecisionProcedure::DualSimplexDecisionProcedure( + Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), + d_pivotsInRound(), + d_statistics(d_pivots) +{ } + +DualSimplexDecisionProcedure::Statistics::Statistics(uint32_t& pivots) + : d_statUpdateConflicts(smtStatisticsRegistry().registerInt( + "theory::arith::dual::UpdateConflicts")), + d_processSignalsTime(smtStatisticsRegistry().registerTimer( + "theory::arith::dual::findConflictOnTheQueueTime")), + d_simplexConflicts(smtStatisticsRegistry().registerInt( + "theory::arith::dual::simplexConflicts")), + d_recentViolationCatches(smtStatisticsRegistry().registerInt( + "theory::arith::dual::recentViolationCatches")), + d_searchTime(smtStatisticsRegistry().registerTimer( + "theory::arith::dual::searchTime")), + d_finalCheckPivotCounter( + smtStatisticsRegistry().registerReference( + "theory::arith::dual::lastPivots", pivots)) +{ +} + +Result::Status DualSimplexDecisionProcedure::dualFindModel(bool exactResult) +{ + Assert(d_conflictVariables.empty()); + + d_pivots = 0; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Trace("arith::findModel") << "dualFindModel() trivial" << endl; + return Result::SAT; + } + + // We need to reduce this because of + d_errorSet.reduceToSignals(); + d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); + + if(processSignals()){ + d_conflictVariables.purge(); + + Trace("arith::findModel") << "dualFindModel() early conflict" << endl; + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Trace("arith::findModel") << "dualFindModel() fixed itself" << endl; + Assert(!d_errorSet.moreSignals()); + return Result::SAT; + } + + Trace("arith::findModel") << "dualFindModel() start non-trivial" << endl; + + Result::Status result = Result::UNKNOWN; + + exactResult |= d_varOrderPivotLimit < 0; + + uint32_t checkPeriod = options().arith.arithSimplexCheckPeriod; + if (result == Result::UNKNOWN) + { + uint32_t numDifferencePivots = options().arith.arithHeuristicPivots < 0 + ? d_numVariables + 1 + : options().arith.arithHeuristicPivots; + // The signed to unsigned conversion is safe. + if(numDifferencePivots > 0){ + + d_errorSet.setSelectionRule(d_heuristicRule); + if(searchForFeasibleSolution(numDifferencePivots)){ + result = Result::UNSAT; + } + } + } + Assert(!d_errorSet.moreSignals()); + + if(!d_errorSet.errorEmpty() && result != Result::UNSAT){ + if(exactResult){ + d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); + while(!d_errorSet.errorEmpty() && result != Result::UNSAT){ + Assert(checkPeriod > 0); + if(searchForFeasibleSolution(checkPeriod)){ + result = Result::UNSAT; + } + } + } + else if (d_varOrderPivotLimit > 0) + { + d_errorSet.setSelectionRule(options::ErrorSelectionRule::VAR_ORDER); + if (searchForFeasibleSolution(d_varOrderPivotLimit)) + { + result = Result::UNSAT; + } + } + } + + Assert(!d_errorSet.moreSignals()); + if (result == Result::UNKNOWN && d_errorSet.errorEmpty()) + { + result = Result::SAT; + } + + d_pivotsInRound.purge(); + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Trace("arith::findModel") << "end findModel() " << result << endl; + + return result; +} + +//corresponds to Check() in dM06 +//template +bool DualSimplexDecisionProcedure::searchForFeasibleSolution(uint32_t remainingIterations){ + TimerStat::CodeTimer codeTimer(d_statistics.d_searchTime); + + Trace("arith") << "searchForFeasibleSolution" << endl; + Assert(remainingIterations > 0); + + while(remainingIterations > 0 && !d_errorSet.focusEmpty()){ + if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + Assert(d_conflictVariables.empty()); + ArithVar x_i = d_errorSet.topFocusVariable(); + + Trace("arith::update::select") << "selectSmallestInconsistentVar()=" << x_i << endl; + if(x_i == ARITHVAR_SENTINEL){ + Trace("arith::update") << "No inconsistent variables" << endl; + return false; //sat + } + + --remainingIterations; + + bool useVarOrderPivot = + d_pivotsInRound.count(x_i) >= options().arith.arithPivotThreshold; + if(!useVarOrderPivot){ + d_pivotsInRound.add(x_i); + } + + Trace("arith::update") << "pivots in rounds: " << d_pivotsInRound.count(x_i) + << " use " << useVarOrderPivot << " threshold " + << options().arith.arithPivotThreshold << std::endl; + + LinearEqualityModule::VarPreferenceFunction pf = useVarOrderPivot ? + &LinearEqualityModule::minVarOrder : &LinearEqualityModule::minBoundAndColLength; + + //DeltaRational beta_i = d_variables.getAssignment(x_i); + ArithVar x_j = ARITHVAR_SENTINEL; + + int32_t prevErrorSize CVC5_UNUSED = d_errorSet.errorSize(); + + if(d_variables.cmpAssignmentLowerBound(x_i) < 0 ){ + x_j = d_linEq.selectSlackUpperBound(x_i, pf); + if(x_j == ARITHVAR_SENTINEL ){ + Unreachable(); + // ++(d_statistics.d_statUpdateConflicts); + // reportConflict(x_i); + // ++(d_statistics.d_simplexConflicts); + // Node conflict = d_linEq.generateConflictBelowLowerBound(x_i); //unsat + // d_conflictVariable = x_i; + // reportConflict(conflict); + // return true; + }else{ + const DeltaRational& l_i = d_variables.getLowerBound(x_i); + d_linEq.pivotAndUpdate(x_i, x_j, l_i); + } + }else if(d_variables.cmpAssignmentUpperBound(x_i) > 0){ + x_j = d_linEq.selectSlackLowerBound(x_i, pf); + if(x_j == ARITHVAR_SENTINEL ){ + Unreachable(); + // ++(d_statistics.d_statUpdateConflicts); + // reportConflict(x_i); + // ++(d_statistics.d_simplexConflicts); + // Node conflict = d_linEq.generateConflictAboveUpperBound(x_i); //unsat + // d_conflictVariable = x_i; + // reportConflict(conflict); + // return true; + }else{ + const DeltaRational& u_i = d_variables.getUpperBound(x_i); + d_linEq.pivotAndUpdate(x_i, x_j, u_i); + } + } + Assert(x_j != ARITHVAR_SENTINEL); + + bool conflict = processSignals(); + int32_t currErrorSize CVC5_UNUSED = d_errorSet.errorSize(); + d_pivots++; + + if(TraceIsOn("arith::dual")){ + Trace("arith::dual") + << "#" << d_pivots + << " c" << conflict + << " d" << (prevErrorSize - currErrorSize) + << " f" << d_errorSet.inError(x_j) + << " h" << d_conflictVariables.isMember(x_j) + << " " << x_i << "->" << x_j + << endl; + } + + if(conflict){ + return true; + } + } + Assert(!d_errorSet.focusEmpty() || d_errorSet.errorEmpty()); + Assert(remainingIterations == 0 || d_errorSet.focusEmpty()); + Assert(d_errorSet.noSignals()); + + return false; +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/dual_simplex.h b/src/theory/arith/linear/dual_simplex.h new file mode 100644 index 000000000..1bb40e871 --- /dev/null +++ b/src/theory/arith/linear/dual_simplex.h @@ -0,0 +1,120 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + * + * This implements the Simplex module for the Simpelx for DPLL(T) decision + * procedure. + * See the Simplex for DPLL(T) technical report for more background.(citation?) + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This is required to either produce a conflict or satisifying PartialModel. + * Further, we require being told when a basic variable updates its value. + * + * During the Simplex search we maintain a queue of variables. + * The queue is required to contain all of the basic variables that voilate + * their bounds. + * As elimination from the queue is more efficient to be done lazily, + * we do not maintain that the queue of variables needs to be only basic + * variables or only variables that satisfy their bounds. + * + * The simplex procedure roughly follows Alberto's thesis. (citation?) + * There is one round of selecting using a heuristic pivoting rule. + * (See PreferenceFunction Documentation for the available options.) + * The non-basic variable is the one that appears in the fewest pivots. + * (Bruno says that Leonardo invented this first.) + * After this, Bland's pivot rule is invoked. + * + * During this proccess, we periodically inspect the queue of variables to + * 1) remove now extraneous extries, + * 2) detect conflicts that are "waiting" on the queue but may not be detected + * by the current queue heuristics, and + * 3) detect multiple conflicts. + * + * Conflicts are greedily slackened to use the weakest bounds that still + * produce the conflict. + * + * Extra things tracked atm: (Subject to change at Tim's whims) + * - A superset of all of the newly pivoted variables. + * - A queue of additional conflicts that were discovered by Simplex. + * These are theory valid and are currently turned into lemmas + */ + +#include "cvc5_private.h" + +#pragma once + +#include "theory/arith/linear/simplex.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class DualSimplexDecisionProcedure : public SimplexDecisionProcedure{ +public: + DualSimplexDecisionProcedure(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc); + + Result::Status findModel(bool exactResult) override + { + return dualFindModel(exactResult); + } + +private: + + /** + * Maps a variable to how many times they have been used as a pivot in the + * simplex search. + */ + DenseMultiset d_pivotsInRound; + + Result::Status dualFindModel(bool exactResult); + + /** + * This is the main simplex for DPLL(T) loop. + * It runs for at most maxIterations. + * + * Returns true iff it has found a conflict. + * d_conflictVariable will be set and the conflict for this row is reported. + */ + bool searchForFeasibleSolution(uint32_t maxIterations); + + + bool processSignals(){ + TimerStat &timer = d_statistics.d_processSignalsTime; + IntStat& conflictStat = d_statistics.d_recentViolationCatches; + return standardProcessSignals(timer, conflictStat); + } + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + IntStat d_statUpdateConflicts; + TimerStat d_processSignalsTime; + IntStat d_simplexConflicts; + IntStat d_recentViolationCatches; + TimerStat d_searchTime; + + ReferenceStat d_finalCheckPivotCounter; + + Statistics(uint32_t& pivots); + } d_statistics; +};/* class DualSimplexDecisionProcedure */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/error_set.cpp b/src/theory/arith/linear/error_set.cpp new file mode 100644 index 000000000..025af4136 --- /dev/null +++ b/src/theory/arith/linear/error_set.cpp @@ -0,0 +1,490 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andres Noetzli, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/error_set.h" + +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +ErrorInformation::ErrorInformation() + : d_variable(ARITHVAR_SENTINEL), + d_violated(NullConstraint), + d_sgn(0), + d_relaxed(false), + d_inFocus(false), + d_handle(), + d_amount(nullptr), + d_metric(0) +{ + Trace("arith::error::mem") + << "def constructor " << d_variable << " " << d_amount.get() << endl; +} + +ErrorInformation::ErrorInformation(ArithVar var, ConstraintP vio, int sgn) + : d_variable(var), + d_violated(vio), + d_sgn(sgn), + d_relaxed(false), + d_inFocus(false), + d_handle(), + d_amount(nullptr), + d_metric(0) +{ + Assert(debugInitialized()); + Trace("arith::error::mem") + << "constructor " << d_variable << " " << d_amount.get() << endl; +} + + +ErrorInformation::~ErrorInformation() { + Assert(d_relaxed != true); + if (d_amount != nullptr) + { + Trace("arith::error::mem") << d_amount.get() << endl; + Trace("arith::error::mem") + << "destroy " << d_variable << " " << d_amount.get() << endl; + d_amount = nullptr; + } +} + +ErrorInformation::ErrorInformation(const ErrorInformation& ei) + : d_variable(ei.d_variable) + , d_violated(ei.d_violated) + , d_sgn(ei.d_sgn) + , d_relaxed(ei.d_relaxed) + , d_inFocus(ei.d_inFocus) + , d_handle(ei.d_handle) + , d_metric(0) +{ + if (ei.d_amount == nullptr) + { + d_amount = nullptr; + } + else + { + d_amount = std::make_unique(*ei.d_amount); + } + Trace("arith::error::mem") + << "copy const " << d_variable << " " << d_amount.get() << endl; +} + +ErrorInformation& ErrorInformation::operator=(const ErrorInformation& ei){ + d_variable = ei.d_variable; + d_violated = ei.d_violated; + d_sgn = ei.d_sgn; + d_relaxed = (ei.d_relaxed); + d_inFocus = (ei.d_inFocus); + d_handle = (ei.d_handle); + d_metric = ei.d_metric; + if (d_amount != nullptr && ei.d_amount != nullptr) + { + Trace("arith::error::mem") + << "assignment assign " << d_variable << " " << d_amount.get() << endl; + *d_amount = *ei.d_amount; + } + else if (ei.d_amount != nullptr) + { + d_amount = std::make_unique(*ei.d_amount); + Trace("arith::error::mem") + << "assignment alloc " << d_variable << " " << d_amount.get() << endl; + } + else if (d_amount != nullptr) + { + Trace("arith::error::mem") + << "assignment release " << d_variable << " " << d_amount.get() << endl; + d_amount = nullptr; + } + else + { + d_amount = nullptr; + } + return *this; +} + +void ErrorInformation::reset(ConstraintP c, int sgn){ + Assert(!isRelaxed()); + Assert(c != NullConstraint); + d_violated = c; + d_sgn = sgn; + + if (d_amount != nullptr) + { + Trace("arith::error::mem") + << "reset " << d_variable << " " << d_amount.get() << endl; + d_amount = nullptr; + } +} + +void ErrorInformation::setAmount(const DeltaRational& am){ + if (d_amount == nullptr) + { + d_amount = std::make_unique(); + Trace("arith::error::mem") + << "setAmount " << d_variable << " " << d_amount.get() << endl; + } + (*d_amount) = am; +} + +ErrorSet::Statistics::Statistics() + : d_enqueues( + smtStatisticsRegistry().registerInt("theory::arith::pqueue::enqueues")), + d_enqueuesCollection(smtStatisticsRegistry().registerInt( + "theory::arith::pqueue::enqueuesCollection")), + d_enqueuesDiffMode(smtStatisticsRegistry().registerInt( + "theory::arith::pqueue::enqueuesDiffMode")), + d_enqueuesVarOrderMode(smtStatisticsRegistry().registerInt( + "theory::arith::pqueue::enqueuesVarOrderMode")), + d_enqueuesCollectionDuplicates(smtStatisticsRegistry().registerInt( + "theory::arith::pqueue::enqueuesCollectionDuplicates")), + d_enqueuesVarOrderModeDuplicates(smtStatisticsRegistry().registerInt( + "theory::arith::pqueue::enqueuesVarOrderModeDuplicates")) +{ +} + +ErrorSet::ErrorSet(ArithVariables& vars, + TableauSizes tabSizes, + BoundCountingLookup lookups) + : d_variables(vars), + d_errInfo(), + d_selectionRule(options::ErrorSelectionRule::VAR_ORDER), + d_focus(ComparatorPivotRule(this, d_selectionRule)), + d_outOfFocus(), + d_signals(), + d_tableauSizes(tabSizes), + d_boundLookup(lookups) +{} + +options::ErrorSelectionRule ErrorSet::getSelectionRule() const +{ + return d_selectionRule; +} + +void ErrorSet::recomputeAmount(ErrorInformation& ei, + options::ErrorSelectionRule rule) +{ + switch(rule){ + case options::ErrorSelectionRule::MINIMUM_AMOUNT: + case options::ErrorSelectionRule::MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(ei.getVariable())); + break; + case options::ErrorSelectionRule::SUM_METRIC: + ei.setMetric(sumMetric(ei.getVariable())); + break; + case options::ErrorSelectionRule::VAR_ORDER: + // do nothing + break; + } +} + +void ErrorSet::setSelectionRule(options::ErrorSelectionRule rule) +{ + if(rule != getSelectionRule()){ + FocusSet into(ComparatorPivotRule(this, rule)); + FocusSet::const_iterator iter = d_focus.begin(); + FocusSet::const_iterator i_end = d_focus.end(); + for(; iter != i_end; ++iter){ + ArithVar v = *iter; + ErrorInformation& ei = d_errInfo.get(v); + if(ei.inFocus()){ + recomputeAmount(ei, rule); + FocusSetHandle handle = into.push(v); + ei.setHandle(handle); + } + } + d_focus.swap(into); + d_selectionRule = rule; + } + Assert(getSelectionRule() == rule); +} + +ComparatorPivotRule::ComparatorPivotRule(const ErrorSet* es, + options::ErrorSelectionRule r) + : d_errorSet(es), d_rule(r) +{} + +bool ComparatorPivotRule::operator()(ArithVar v, ArithVar u) const { + switch(d_rule){ + case options::ErrorSelectionRule::VAR_ORDER: + // This needs to be the reverse of the minVariableOrder + return v > u; + case options::ErrorSelectionRule::SUM_METRIC: + { + uint32_t v_metric = d_errorSet->getMetric(v); + uint32_t u_metric = d_errorSet->getMetric(u); + if(v_metric == u_metric){ + return v > u; + }else{ + return v_metric > u_metric; + } + } + case options::ErrorSelectionRule::MINIMUM_AMOUNT: + { + const DeltaRational& vamt = d_errorSet->getAmount(v); + const DeltaRational& uamt = d_errorSet->getAmount(u); + int cmp = vamt.cmp(uamt); + if(cmp == 0){ + return v > u; + }else{ + return cmp > 0; + } + } + case options::ErrorSelectionRule::MAXIMUM_AMOUNT: + { + const DeltaRational& vamt = d_errorSet->getAmount(v); + const DeltaRational& uamt = d_errorSet->getAmount(u); + int cmp = vamt.cmp(uamt); + if(cmp == 0){ + return v > u; + }else{ + return cmp < 0; + } + } + } + Unreachable(); +} + +void ErrorSet::update(ErrorInformation& ei){ + if(ei.inFocus()){ + + switch(getSelectionRule()){ + case options::ErrorSelectionRule::MINIMUM_AMOUNT: + case options::ErrorSelectionRule::MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(ei.getVariable())); + d_focus.update(ei.getHandle(), ei.getVariable()); + break; + case options::ErrorSelectionRule::SUM_METRIC: + ei.setMetric(sumMetric(ei.getVariable())); + d_focus.update(ei.getHandle(), ei.getVariable()); + break; + case options::ErrorSelectionRule::VAR_ORDER: + // do nothing + break; + } + } +} + +/** A variable becomes satisfied. */ +void ErrorSet::transitionVariableOutOfError(ArithVar v) { + Assert(!inconsistent(v)); + ErrorInformation& ei = d_errInfo.get(v); + Assert(ei.debugInitialized()); + if(ei.isRelaxed()){ + ConstraintP viol = ei.getViolated(); + if(ei.sgn() > 0){ + d_variables.setLowerBoundConstraint(viol); + }else{ + d_variables.setUpperBoundConstraint(viol); + } + Assert(!inconsistent(v)); + ei.setUnrelaxed(); + } + if(ei.inFocus()){ + d_focus.erase(ei.getHandle()); + ei.setInFocus(false); + } + d_errInfo.remove(v); +} + + +void ErrorSet::transitionVariableIntoError(ArithVar v) { + Assert(inconsistent(v)); + bool vilb = d_variables.cmpAssignmentLowerBound(v) < 0; + int sgn = vilb ? 1 : -1; + ConstraintP c = vilb ? + d_variables.getLowerBoundConstraint(v) : d_variables.getUpperBoundConstraint(v); + d_errInfo.set(v, ErrorInformation(v, c, sgn)); + ErrorInformation& ei = d_errInfo.get(v); + + switch(getSelectionRule()){ + case options::ErrorSelectionRule::MINIMUM_AMOUNT: + case options::ErrorSelectionRule::MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(v)); + break; + case options::ErrorSelectionRule::SUM_METRIC: + ei.setMetric(sumMetric(ei.getVariable())); + break; + case options::ErrorSelectionRule::VAR_ORDER: + // do nothing + break; + } + ei.setInFocus(true); + FocusSetHandle handle = d_focus.push(v); + ei.setHandle(handle); +} + +void ErrorSet::dropFromFocus(ArithVar v) { + Assert(inError(v)); + ErrorInformation& ei = d_errInfo.get(v); + Assert(ei.inFocus()); + d_focus.erase(ei.getHandle()); + ei.setInFocus(false); + d_outOfFocus.push_back(v); +} + +void ErrorSet::addBackIntoFocus(ArithVar v) { + Assert(inError(v)); + ErrorInformation& ei = d_errInfo.get(v); + Assert(!ei.inFocus()); + switch(getSelectionRule()){ + case options::ErrorSelectionRule::MINIMUM_AMOUNT: + case options::ErrorSelectionRule::MAXIMUM_AMOUNT: + ei.setAmount(computeDiff(v)); + break; + case options::ErrorSelectionRule::SUM_METRIC: + ei.setMetric(sumMetric(v)); + break; + case options::ErrorSelectionRule::VAR_ORDER: + // do nothing + break; + } + + ei.setInFocus(true); + FocusSetHandle handle = d_focus.push(v); + ei.setHandle(handle); +} + +void ErrorSet::blur(){ + while(!d_outOfFocus.empty()){ + ArithVar v = d_outOfFocus.back(); + d_outOfFocus.pop_back(); + + if(inError(v) && !inFocus(v)){ + addBackIntoFocus(v); + } + } +} + + + +int ErrorSet::popSignal() { + ArithVar back = d_signals.back(); + d_signals.pop_back(); + + if(inError(back)){ + ErrorInformation& ei = d_errInfo.get(back); + int prevSgn = ei.sgn(); + int focusSgn = ei.focusSgn(); + bool vilb = d_variables.cmpAssignmentLowerBound(back) < 0; + bool viub = d_variables.cmpAssignmentUpperBound(back) > 0; + if(vilb || viub){ + Assert(!vilb || !viub); + int currSgn = vilb ? 1 : -1; + if(currSgn != prevSgn){ + ConstraintP curr = vilb ? d_variables.getLowerBoundConstraint(back) + : d_variables.getUpperBoundConstraint(back); + ei.reset(curr, currSgn); + } + update(ei); + }else{ + transitionVariableOutOfError(back); + } + return focusSgn; + }else if(inconsistent(back)){ + transitionVariableIntoError(back); + } + return 0; +} + +void ErrorSet::clear(){ + // Nothing should be relaxed! + d_signals.clear(); + d_errInfo.purge(); + d_focus.clear(); +} + +void ErrorSet::clearFocus(){ + for(ErrorSet::focus_iterator i =focusBegin(), i_end = focusEnd(); i != i_end; ++i){ + ArithVar f = *i; + ErrorInformation& fei = d_errInfo.get(f); + fei.setInFocus(false); + d_outOfFocus.push_back(f); + } + d_focus.clear(); +} + +void ErrorSet::reduceToSignals(){ + for(error_iterator ei=errorBegin(), ei_end=errorEnd(); ei != ei_end; ++ei){ + ArithVar curr = *ei; + signalVariable(curr); + } + + d_errInfo.purge(); + d_focus.clear(); + d_outOfFocus.clear(); +} + +DeltaRational ErrorSet::computeDiff(ArithVar v) const{ + Assert(inconsistent(v)); + const DeltaRational& beta = d_variables.getAssignment(v); + DeltaRational diff = d_variables.cmpAssignmentLowerBound(v) < 0 ? + d_variables.getLowerBound(v) - beta: + beta - d_variables.getUpperBound(v); + + Assert(diff.sgn() > 0); + return diff; +} + +void ErrorSet::debugPrint(std::ostream& out) const { + out << "error set debugprint" << endl; + for(error_iterator i = errorBegin(), i_end = errorEnd(); + i != i_end; ++i){ + ArithVar e = *i; + const ErrorInformation& ei = d_errInfo[e]; + ei.print(out); + out << " "; + d_variables.printModel(e, out); + out << endl; + } + out << "focus "; + for(focus_iterator i = focusBegin(), i_end = focusEnd(); + i != i_end; ++i){ + out << *i << " "; + } + out << ";" << endl; +} + +void ErrorSet::focusDownToJust(ArithVar v) { + clearFocus(); + + ErrorInformation& vei = d_errInfo.get(v); + vei.setInFocus(true); + FocusSetHandle handle = d_focus.push(v); + vei.setHandle(handle); +} + +void ErrorSet::pushErrorInto(ArithVarVec& vec) const{ + for(error_iterator i = errorBegin(), e = errorEnd(); i != e; ++i ){ + vec.push_back(*i); + } +} + +void ErrorSet::pushFocusInto(ArithVarVec& vec) const{ + for(focus_iterator i = focusBegin(), e = focusEnd(); i != e; ++i ){ + vec.push_back(*i); + } +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/error_set.h b/src/theory/arith/linear/error_set.h new file mode 100644 index 000000000..448601b6c --- /dev/null +++ b/src/theory/arith/linear/error_set.h @@ -0,0 +1,421 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Mathias Preiner, Andres Noetzli + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include +#include + +#include "options/arith_options.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/bound_counts.h" +#include "theory/arith/linear/callbacks.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/linear/partial_model.h" +#include "theory/arith/linear/tableau_sizes.h" +#include "util/bin_heap.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + + +/** + * The priority queue has 3 different modes of operation: + * - Collection + * This passively collects arithmetic variables that may be inconsistent. + * This does not maintain any heap structure. + * dequeueInconsistentBasicVariable() does not work in this mode! + * Entering this mode requires the queue to be empty. + * + * - Difference Queue + * This mode uses the difference between a variables and its bound + * to determine which to dequeue first. + * + * - Variable Order Queue + * This mode uses the variable order to determine which ArithVar is dequeued first. + * + * The transitions between the modes of operation are: + * Collection => Difference Queue + * Difference Queue => Variable Order Queue + * Difference Queue => Collection (queue must be empty!) + * Variable Order Queue => Collection (queue must be empty!) + * + * The queue begins in Collection mode. + */ + + +class ErrorSet; + +class ComparatorPivotRule { +private: + const ErrorSet* d_errorSet; + + options::ErrorSelectionRule d_rule; + + public: + ComparatorPivotRule(); + ComparatorPivotRule(const ErrorSet* es, options::ErrorSelectionRule r); + + bool operator()(ArithVar v, ArithVar u) const; + options::ErrorSelectionRule getRule() const { return d_rule; } +}; + +// typedef boost::heap::d_ary_heap< +// ArithVar, +// boost::heap::arity<2>, +// boost::heap::compare, +// boost::heap::mutable_ > FocusSet; +// +// typedef FocusSet::handle_type FocusSetHandle; + +// typedef CVC5_PB_DS_NAMESPACE::priority_queue< +// ArithVar, +// ComparatorPivotRule, +// CVC5_PB_DS_NAMESPACE::pairing_heap_tag> FocusSet; + +// typedef FocusSet::point_iterator FocusSetHandle; + +typedef BinaryHeap FocusSet; +typedef FocusSet::handle FocusSetHandle; + + +class ErrorInformation { +private: + /** The variable that is in error. */ + ArithVar d_variable; + + /** + * The constraint that was violated. + * This needs to be saved in case that the + * violated constraint + */ + ConstraintP d_violated; + + /** + * This is the sgn of the first derivate the variable must move to satisfy + * the bound violated. + * If d_sgn > 0, then d_violated was a lowerbound. + * If d_sgn < 0, then d_violated was an upperbound. + */ + int d_sgn; + + /** + * If this is true, then the bound is no longer set on d_variables. + * This MUST be undone before this is deleted. + */ + bool d_relaxed; + + /** + * If this is true, then the variable is in the focus set and the focus heap. + * d_handle is then a reasonable thing to interpret. + * If this is false, the variable is somewhere in + */ + bool d_inFocus; + FocusSetHandle d_handle; + + /** + * Auxillary information for storing the difference between a variable and its bound. + * Only set on signals. + */ + std::unique_ptr d_amount; + + /** */ + uint32_t d_metric; + +public: + ErrorInformation(); + ErrorInformation(ArithVar var, ConstraintP vio, int sgn); + ~ErrorInformation(); + ErrorInformation(const ErrorInformation& ei); + ErrorInformation& operator=(const ErrorInformation& ei); + + void reset(ConstraintP c, int sgn); + + inline ArithVar getVariable() const { return d_variable; } + + bool isRelaxed() const { return d_relaxed; } + void setRelaxed() + { + Assert(!d_relaxed); + d_relaxed = true; + } + void setUnrelaxed() + { + Assert(d_relaxed); + d_relaxed = false; + } + + inline int sgn() const { return d_sgn; } + + inline bool inFocus() const { return d_inFocus; } + inline int focusSgn() const { + return (d_inFocus) ? sgn() : 0; + } + + inline void setInFocus(bool inFocus) { d_inFocus = inFocus; } + + const DeltaRational& getAmount() const { + Assert(d_amount != nullptr); + return *d_amount; + } + + void setAmount(const DeltaRational& am); + void setMetric(uint32_t m) { d_metric = m; } + uint32_t getMetric() const { return d_metric; } + + inline void setHandle(FocusSetHandle h) { + Assert(d_inFocus); + d_handle = h; + } + inline const FocusSetHandle& getHandle() const{ return d_handle; } + + inline ConstraintP getViolated() const { return d_violated; } + + bool debugInitialized() const { + return + d_variable != ARITHVAR_SENTINEL && + d_violated != NullConstraint && + d_sgn != 0; + } + void print(std::ostream& os) const { + os << "{ErrorInfo: " << d_variable + << ", " << d_violated + << ", " << d_sgn + << ", " << d_relaxed + << ", " << d_inFocus; + if (d_amount == nullptr) + { + os << "nullptr"; + } + else + { + os << (*d_amount); + } + os << "}"; + } +}; + +class ErrorInfoMap : public DenseMap {}; + +class ErrorSet { +private: + /** + * Reference to the arithmetic partial model for checking if a variable + * is consistent with its upper and lower bounds. + */ + ArithVariables& d_variables; + + /** + * The set of all variables that violate exactly one of their bounds. + */ + ErrorInfoMap d_errInfo; + + options::ErrorSelectionRule d_selectionRule; + /** + * The ordered heap for the variables that are in ErrorSet. + */ + FocusSet d_focus; + + + /** + * A strict subset of the error set. + * d_outOfFocus \neq d_errInfo. + * + * Its symbolic complement is Focus. + * d_outOfFocus \intersect Focus == \emptyset + * d_outOfFocus \union Focus == d_errInfo + */ + ArithVarVec d_outOfFocus; + + /** + * Before a variable is added to the error set, it is added to the signals list. + * A variable may appear on the list multiple times. + * This introduces a delay. + */ + ArithVarVec d_signals; + + TableauSizes d_tableauSizes; + + BoundCountingLookup d_boundLookup; + + /** + * Computes the difference between the assignment and its bound for x. + */ +public: + DeltaRational computeDiff(ArithVar x) const; +private: + void recomputeAmount(ErrorInformation& ei, options::ErrorSelectionRule r); + + void update(ErrorInformation& ei); + void transitionVariableOutOfError(ArithVar v); + void transitionVariableIntoError(ArithVar v); + void addBackIntoFocus(ArithVar v); + +public: + + /** The new focus set is the entire error set. */ + void blur(); + void dropFromFocus(ArithVar v); + + void dropFromFocusAll(const ArithVarVec& vec) { + for(ArithVarVec::const_iterator i = vec.begin(), i_end = vec.end(); i != i_end; ++i){ + ArithVar v = *i; + dropFromFocus(v); + } + } + + ErrorSet(ArithVariables& var, TableauSizes tabSizes, BoundCountingLookup boundLookup); + + typedef ErrorInfoMap::const_iterator error_iterator; + error_iterator errorBegin() const { return d_errInfo.begin(); } + error_iterator errorEnd() const { return d_errInfo.end(); } + + bool inError(ArithVar v) const { return d_errInfo.isKey(v); } + bool inFocus(ArithVar v) const { return d_errInfo[v].inFocus(); } + + void pushErrorInto(ArithVarVec& vec) const; + void pushFocusInto(ArithVarVec& vec) const; + + options::ErrorSelectionRule getSelectionRule() const; + void setSelectionRule(options::ErrorSelectionRule rule); + + inline ArithVar topFocusVariable() const{ + Assert(!focusEmpty()); + return d_focus.top(); + } + + inline void signalVariable(ArithVar var){ + d_signals.push_back(var); + } + + inline void signalUnderCnd(ArithVar var, bool b){ + if(b){ signalVariable(var); } + } + + inline bool inconsistent(ArithVar var) const{ + return !d_variables.assignmentIsConsistent(var) ; + } + inline void signalIfInconsistent(ArithVar var){ + signalUnderCnd(var, inconsistent(var)); + } + + inline bool errorEmpty() const{ + return d_errInfo.empty(); + } + inline uint32_t errorSize() const{ + return d_errInfo.size(); + } + + inline bool focusEmpty() const { + return d_focus.empty(); + } + inline uint32_t focusSize() const{ + return d_focus.size(); + } + + inline int getSgn(ArithVar x) const { + Assert(inError(x)); + return d_errInfo[x].sgn(); + } + inline int focusSgn(ArithVar v) const { + if(inError(v)){ + return d_errInfo[v].focusSgn(); + }else{ + return 0; + } + } + + void focusDownToJust(ArithVar v); + + void clearFocus(); + + /** Clears the set. */ + void clear(); + void reduceToSignals(); + + bool noSignals() const { + return d_signals.empty(); + } + bool moreSignals() const { + return !noSignals(); + } + ArithVar topSignal() const { + Assert(moreSignals()); + return d_signals.back(); + } + + /** + * Moves a variable out of the signals. + * This moves it into the error set. + * Return the previous focus sign. + */ + int popSignal(); + + const DeltaRational& getAmount(ArithVar v) const { + return d_errInfo[v].getAmount(); + } + + uint32_t sumMetric(ArithVar a) const{ + Assert(inError(a)); + BoundCounts bcs = d_boundLookup.atBounds(a); + uint32_t count = getSgn(a) > 0 ? bcs.upperBoundCount() : bcs.lowerBoundCount(); + + uint32_t length = d_tableauSizes.getRowLength(a); + + return (length - count); + } + + uint32_t getMetric(ArithVar a) const { + return d_errInfo[a].getMetric(); + } + + ConstraintP getViolated(ArithVar a) const { + return d_errInfo[a].getViolated(); + } + + + typedef FocusSet::const_iterator focus_iterator; + focus_iterator focusBegin() const { return d_focus.begin(); } + focus_iterator focusEnd() const { return d_focus.end(); } + + void debugPrint(std::ostream& out) const; + +private: + class Statistics { + public: + IntStat d_enqueues; + IntStat d_enqueuesCollection; + IntStat d_enqueuesDiffMode; + IntStat d_enqueuesVarOrderMode; + + IntStat d_enqueuesCollectionDuplicates; + IntStat d_enqueuesVarOrderModeDuplicates; + + Statistics(); + }; + + Statistics d_statistics; +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/fc_simplex.cpp b/src/theory/arith/linear/fc_simplex.cpp new file mode 100644 index 000000000..9e2554109 --- /dev/null +++ b/src/theory/arith/linear/fc_simplex.cpp @@ -0,0 +1,787 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T)decision procedure. + */ +#include "theory/arith/linear/fc_simplex.h" + +#include "base/output.h" +#include "options/arith_options.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/error_set.h" +#include "util/statistics_stats.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +FCSimplexDecisionProcedure::FCSimplexDecisionProcedure( + Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), + d_focusSize(0), + d_focusErrorVar(ARITHVAR_SENTINEL), + d_focusCoefficients(), + d_pivotBudget(0), + d_prevWitnessImprovement(AntiProductive), + d_witnessImprovementInARow(0), + d_sgnDisagreements(), + d_statistics("theory::arith::FC::", d_pivots) +{ } + +FCSimplexDecisionProcedure::Statistics::Statistics(const std::string& name, + uint32_t& pivots) + : d_initialSignalsTime( + smtStatisticsRegistry().registerTimer(name + "initialProcessTime")), + d_initialConflicts( + smtStatisticsRegistry().registerInt(name + "UpdateConflicts")), + d_fcFoundUnsat(smtStatisticsRegistry().registerInt(name + "FoundUnsat")), + d_fcFoundSat(smtStatisticsRegistry().registerInt(name + "FoundSat")), + d_fcMissed(smtStatisticsRegistry().registerInt(name + "Missed")), + d_fcTimer(smtStatisticsRegistry().registerTimer(name + "Timer")), + d_fcFocusConstructionTimer( + smtStatisticsRegistry().registerTimer(name + "Construction")), + d_selectUpdateForDualLike(smtStatisticsRegistry().registerTimer( + name + "selectUpdateForDualLike")), + d_selectUpdateForPrimal(smtStatisticsRegistry().registerTimer( + name + "selectUpdateForPrimal")), + d_finalCheckPivotCounter( + smtStatisticsRegistry().registerReference( + name + "lastPivots", pivots)) +{ +} + +Result::Status FCSimplexDecisionProcedure::findModel(bool exactResult) +{ + Assert(d_conflictVariables.empty()); + Assert(d_sgnDisagreements.empty()); + + d_pivots = 0; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Trace("arith::findModel") << "fcFindModel() trivial" << endl; + Assert(d_conflictVariables.empty()); + return Result::SAT; + } + + // We need to reduce this because of + d_errorSet.reduceToSignals(); + + // We must start tracking NOW + d_errorSet.setSelectionRule(options::ErrorSelectionRule::SUM_METRIC); + + if(initialProcessSignals()){ + d_conflictVariables.purge(); + Trace("arith::findModel") << "fcFindModel() early conflict" << endl; + Assert(d_conflictVariables.empty()); + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Trace("arith::findModel") << "fcFindModel() fixed itself" << endl; + Assert(d_conflictVariables.empty()); + return Result::SAT; + } + + Trace("arith::findModel") << "fcFindModel() start non-trivial" << endl; + + exactResult |= d_varOrderPivotLimit < 0; + + d_prevWitnessImprovement = HeuristicDegenerate; + d_witnessImprovementInARow = 0; + + Result::Status result = Result::UNKNOWN; + + if (result == Result::UNKNOWN) + { + if(exactResult){ + d_pivotBudget = -1; + }else{ + d_pivotBudget = d_varOrderPivotLimit; + } + + result = dualLike(); + + if(result == Result::UNSAT){ + ++(d_statistics.d_fcFoundUnsat); + }else if(d_errorSet.errorEmpty()){ + ++(d_statistics.d_fcFoundSat); + }else{ + ++(d_statistics.d_fcMissed); + } + } + + Assert(!d_errorSet.moreSignals()); + if (result == Result::UNKNOWN && d_errorSet.errorEmpty()) + { + result = Result::SAT; + } + + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Trace("arith::findModel") << "end findModel() " << result << endl; + + Assert(d_conflictVariables.empty()); + return result; +} + + +void FCSimplexDecisionProcedure::logPivot(WitnessImprovement w){ + if(d_pivotBudget > 0) { + --d_pivotBudget; + } + Assert(w != AntiProductive); + + if(w == d_prevWitnessImprovement){ + ++d_witnessImprovementInARow; + // ignore overflow : probably never reached + if(d_witnessImprovementInARow == 0){ + --d_witnessImprovementInARow; + } + }else{ + if(w != BlandsDegenerate){ + d_witnessImprovementInARow = 1; + } + // if w == BlandsDegenerate do not reset the counter + d_prevWitnessImprovement = w; + } + if(strongImprovement(w)){ + d_leavingCountSinceImprovement.purge(); + } + + Trace("logPivot") << "logPivot " << d_prevWitnessImprovement << " " << d_witnessImprovementInARow << endl; + +} + +uint32_t FCSimplexDecisionProcedure::degeneratePivotsInARow() const { + switch(d_prevWitnessImprovement){ + case ConflictFound: + case ErrorDropped: + case FocusImproved: + return 0; + case HeuristicDegenerate: + case BlandsDegenerate: + return d_witnessImprovementInARow; + // Degenerate is unreachable for its own reasons + case Degenerate: + case FocusShrank: + case AntiProductive: + Unreachable(); + return -1; + } + Unreachable(); +} + +void FCSimplexDecisionProcedure::adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges){ + uint32_t newErrorSize = d_errorSet.errorSize(); + uint32_t newFocusSize = d_errorSet.focusSize(); + + //Assert(!d_conflictVariables.empty() || newFocusSize <= d_focusSize); + Assert(!d_conflictVariables.empty() || newErrorSize <= d_errorSize); + + if(newFocusSize == 0 || !d_conflictVariables.empty() ){ + tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); + d_focusErrorVar = ARITHVAR_SENTINEL; + }else if(2*newFocusSize < d_focusSize ){ + tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); + d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); + }else{ + adjustInfeasFunc(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar, focusChanges); + } + + d_errorSize = newErrorSize; + d_focusSize = newFocusSize; +} + +WitnessImprovement FCSimplexDecisionProcedure::adjustFocusShrank(const ArithVarVec& dropped){ + Assert(dropped.size() > 0); + Assert(d_errorSet.focusSize() == d_focusSize); + Assert(d_errorSet.focusSize() > dropped.size()); + + uint32_t newFocusSize = d_focusSize - dropped.size(); + Assert(newFocusSize > 0); + + if(2 * newFocusSize <= d_focusSize){ + d_errorSet.dropFromFocusAll(dropped); + tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); + d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); + }else{ + shrinkInfeasFunc(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar, dropped); + d_errorSet.dropFromFocusAll(dropped); + } + + d_focusSize = newFocusSize; + Assert(d_errorSet.focusSize() == d_focusSize); + return FocusShrank; +} + +WitnessImprovement FCSimplexDecisionProcedure::focusDownToJust(ArithVar v){ + // uint32_t newErrorSize = d_errorSet.errorSize(); + // uint32_t newFocusSize = d_errorSet.focusSize(); + Assert(d_focusSize == d_errorSet.focusSize()); + Assert(d_focusSize > 1); + Assert(d_errorSet.inFocus(v)); + + d_errorSet.focusDownToJust(v); + Assert(d_errorSet.focusSize() == 1); + d_focusSize = 1; + + tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); + d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); + + return FocusShrank; +} + + + +UpdateInfo FCSimplexDecisionProcedure::selectPrimalUpdate(ArithVar basic, LinearEqualityModule::UpdatePreferenceFunction upf, LinearEqualityModule::VarPreferenceFunction bpf) { + UpdateInfo selected; + + Trace("arith::selectPrimalUpdate") + << "selectPrimalUpdate" << endl + << basic << " " << d_tableau.basicRowLength(basic) << " " + << d_linEq.debugBasicAtBoundCount(basic) << endl; + + static constexpr int s_maxCandidatesAfterImprove = 3; + bool isFocus = basic == d_focusErrorVar; + Assert(isFocus || d_errorSet.inError(basic)); + int basicDir = isFocus? 1 : d_errorSet.getSgn(basic); + bool dualLike = !isFocus && d_focusSize > 1; + + if(!isFocus){ + loadFocusSigns(); + } + + decreasePenalties(); + + typedef std::vector CandVector; + CandVector candidates; + + for(Tableau::RowIterator ri = d_tableau.basicRowIterator(basic); !ri.atEnd(); ++ri){ + const Tableau::Entry& e = *ri; + ArithVar curr = e.getColVar(); + if(curr == basic){ continue; } + + int sgn = e.getCoefficient().sgn(); + int curr_movement = basicDir * sgn; + + bool candidate = + (curr_movement > 0 && d_variables.cmpAssignmentUpperBound(curr) < 0) || + (curr_movement < 0 && d_variables.cmpAssignmentLowerBound(curr) > 0); + + Trace("arith::selectPrimalUpdate") + << "storing " << basic + << " " << curr + << " " << candidate + << " " << e.getCoefficient() + << " " << curr_movement + << " " << focusCoefficient(curr) << endl; + + if(!candidate) { continue; } + + if(!isFocus){ + const Rational& focusC = focusCoefficient(curr); + Assert(dualLike || !focusC.isZero()); + if(dualLike && curr_movement != focusC.sgn()){ + Trace("arith::selectPrimalUpdate") << "sgn disagreement " << curr << endl; + d_sgnDisagreements.push_back(curr); + continue; + }else{ + candidates.push_back(Cand(curr, penalty(curr), curr_movement, &focusC)); + } + }else{ + candidates.push_back(Cand(curr, penalty(curr), curr_movement, &e.getCoefficient())); + } + } + + CompPenaltyColLength colCmp(&d_linEq, options().arith.havePenalties); + CandVector::iterator i = candidates.begin(); + CandVector::iterator end = candidates.end(); + std::make_heap(i, end, colCmp); + + bool checkEverything = d_pivots == 0; + + int candidatesAfterFocusImprove = 0; + while(i != end && (checkEverything || candidatesAfterFocusImprove <= s_maxCandidatesAfterImprove)){ + std::pop_heap(i, end, colCmp); + --end; + Cand& cand = (*end); + ArithVar curr = cand.d_nb; + const Rational& coeff = *cand.d_coeff; + + LinearEqualityModule::UpdatePreferenceFunction leavingPrefFunc = selectLeavingFunction(curr); + UpdateInfo currProposal = d_linEq.speculativeUpdate(curr, coeff, leavingPrefFunc); + + Trace("arith::selectPrimalUpdate") + << "selected " << selected << endl + << "currProp " << currProposal << endl + << "coeff " << coeff << endl; + + Assert(!currProposal.uninitialized()); + + if(candidatesAfterFocusImprove > 0){ + candidatesAfterFocusImprove++; + } + + if(selected.uninitialized() || (d_linEq.*upf)(selected, currProposal)){ + + selected = currProposal; + WitnessImprovement w = selected.getWitness(false); + Trace("arith::selectPrimalUpdate") << "selected " << w << endl; + setPenalty(curr, w); + if(improvement(w)){ + bool exitEarly; + switch(w){ + case ConflictFound: exitEarly = true; break; + case ErrorDropped: + if(checkEverything){ + exitEarly = d_errorSize + selected.errorsChange() == 0; + Trace("arith::selectPrimalUpdate") + << "ee " << d_errorSize << " " + << selected.errorsChange() << " " + << d_errorSize + selected.errorsChange() << endl; + }else{ + exitEarly = true; + } + break; + case FocusImproved: + candidatesAfterFocusImprove = 1; + exitEarly = false; + break; + default: + exitEarly = false; break; + } + if(exitEarly){ break; } + } + }else{ + Trace("arith::selectPrimalUpdate") << "dropped "<< endl; + } + + } + + if(!isFocus){ + unloadFocusSigns(); + } + return selected; +} + +bool FCSimplexDecisionProcedure::debugCheckWitness(const UpdateInfo& inf, WitnessImprovement w, bool useBlands){ + if(inf.getWitness(useBlands) == w){ + switch(w){ + case ConflictFound: return inf.foundConflict(); + case ErrorDropped: return inf.errorsChange() < 0; + case FocusImproved: return inf.focusDirection() > 0; + case FocusShrank: return false; // This is not a valid output + case Degenerate: return false; // This is not a valid output + case BlandsDegenerate: return useBlands; + case HeuristicDegenerate: return !useBlands; + case AntiProductive: return false; + } + } + return false; +} + +WitnessImprovement FCSimplexDecisionProcedure::primalImproveError(ArithVar errorVar){ + bool useBlands = degeneratePivotsInARow() >= s_maxDegeneratePivotsBeforeBlandsOnLeaving; + UpdateInfo selected = selectUpdateForPrimal (errorVar, useBlands); + Assert(!selected.uninitialized()); + WitnessImprovement w = selected.getWitness(useBlands); + Assert(debugCheckWitness(selected, w, useBlands)); + + updateAndSignal(selected, w); + logPivot(w); + return w; +} + + +WitnessImprovement FCSimplexDecisionProcedure::focusUsingSignDisagreements(ArithVar basic){ + Assert(!d_sgnDisagreements.empty()); + Assert(d_errorSet.focusSize() >= 2); + + if(TraceIsOn("arith::focus")){ + d_errorSet.debugPrint(Trace("arith::focus")); + } + + ArithVar nb = d_linEq.minBy(d_sgnDisagreements, &LinearEqualityModule::minColLength); + const Tableau::Entry& e_evar_nb = d_tableau.basicFindEntry(basic, nb); + int oppositeSgn = - (e_evar_nb.getCoefficient().sgn()); + Trace("arith::focus") << "focusUsingSignDisagreements " << basic << " " << oppositeSgn << endl; + + ArithVarVec dropped; + + Tableau::ColIterator colIter = d_tableau.colIterator(nb); + for(; !colIter.atEnd(); ++colIter){ + const Tableau::Entry& entry = *colIter; + Assert(entry.getColVar() == nb); + + int sgn = entry.getCoefficient().sgn(); + Trace("arith::focus") + << "on row " + << d_tableau.rowIndexToBasic(entry.getRowIndex()) + << " " + << entry.getCoefficient() << endl; + ArithVar currRow = d_tableau.rowIndexToBasic(entry.getRowIndex()); + if(d_errorSet.inError(currRow) && d_errorSet.inFocus(currRow)){ + int errSgn = d_errorSet.getSgn(currRow); + + if(errSgn * sgn == oppositeSgn){ + dropped.push_back(currRow); + Trace("arith::focus") << "dropping from focus " << currRow << endl; + } + } + } + + d_sgnDisagreements.clear(); + return adjustFocusShrank(dropped); +} + +bool debugSelectedErrorDropped(const UpdateInfo& selected, int32_t prevErrorSize, int32_t currErrorSize){ + int diff = currErrorSize - prevErrorSize; + return selected.foundConflict() || diff == selected.errorsChange(); +} + +void FCSimplexDecisionProcedure::debugPrintSignal(ArithVar updated) const{ + Trace("updateAndSignal") << "updated basic " << updated; + Trace("updateAndSignal") << " length " << d_tableau.basicRowLength(updated); + Trace("updateAndSignal") << " consistent " << d_variables.assignmentIsConsistent(updated); + int dir = !d_variables.assignmentIsConsistent(updated) ? + d_errorSet.getSgn(updated) : 0; + Trace("updateAndSignal") << " dir " << dir; + Trace("updateAndSignal") << " debugBasicAtBoundCount " << d_linEq.debugBasicAtBoundCount(updated) << endl; +} + +bool debugUpdatedBasic(const UpdateInfo& selected, ArithVar updated){ + if(selected.describesPivot() && updated == selected.leaving()){ + return selected.foundConflict(); + }else{ + return true; + } +} + +void FCSimplexDecisionProcedure::updateAndSignal(const UpdateInfo& selected, WitnessImprovement w){ + ArithVar nonbasic = selected.nonbasic(); + + Trace("updateAndSignal") << "updateAndSignal " << selected << endl; + + stringstream ss; + + if(selected.describesPivot()){ + ConstraintP limiting = selected.limiting(); + ArithVar basic = limiting->getVariable(); + Assert(d_linEq.basicIsTracked(basic)); + d_linEq.pivotAndUpdate(basic, nonbasic, limiting->getValue()); + }else{ + Assert(!selected.unbounded() || selected.errorsChange() < 0); + + DeltaRational newAssignment = + d_variables.getAssignment(nonbasic) + selected.nonbasicDelta(); + + d_linEq.updateTracked(nonbasic, newAssignment); + } + d_pivots++; + + increaseLeavingCount(nonbasic); + + vector< pair > focusChanges; + while(d_errorSet.moreSignals()){ + ArithVar updated = d_errorSet.topSignal(); + int prevFocusSgn = d_errorSet.popSignal(); + + if(d_tableau.isBasic(updated)){ + Assert(!d_variables.assignmentIsConsistent(updated) + == d_errorSet.inError(updated)); + if(TraceIsOn("updateAndSignal")){debugPrintSignal(updated);} + if(!d_variables.assignmentIsConsistent(updated)){ + if(checkBasicForConflict(updated)){ + reportConflict(updated); + Assert(debugUpdatedBasic(selected, updated)); + } + } + }else{ + Trace("updateAndSignal") << "updated nonbasic " << updated << endl; + } + int currFocusSgn = d_errorSet.focusSgn(updated); + if(currFocusSgn != prevFocusSgn){ + int change = currFocusSgn - prevFocusSgn; + focusChanges.push_back(make_pair(updated, change)); + } + } + + if(TraceIsOn("error")){ d_errorSet.debugPrint(Trace("error")); } + + Assert( + debugSelectedErrorDropped(selected, d_errorSize, d_errorSet.errorSize())); + + adjustFocusAndError(selected, focusChanges); +} + +WitnessImprovement FCSimplexDecisionProcedure::dualLikeImproveError(ArithVar errorVar){ + Assert(d_sgnDisagreements.empty()); + Assert(d_focusSize > 1); + + UpdateInfo selected = selectUpdateForDualLike(errorVar); + + if(selected.uninitialized()){ + // we found no proposals + // If this is empty, there must be an error on this variable! + // this should not be possible. It Should have been caught as a signal earlier + WitnessImprovement dropped = focusUsingSignDisagreements(errorVar); + Assert(d_sgnDisagreements.empty()); + + return dropped; + }else{ + d_sgnDisagreements.clear(); + } + + Assert(d_sgnDisagreements.empty()); + Assert(!selected.uninitialized()); + + if(selected.focusDirection() == 0 && + d_prevWitnessImprovement == HeuristicDegenerate && + d_witnessImprovementInARow >= s_focusThreshold){ + + Trace("focusDownToJust") << "focusDownToJust " << errorVar << endl; + + return focusDownToJust(errorVar); + }else{ + WitnessImprovement w = selected.getWitness(false); + Assert(debugCheckWitness(selected, w, false)); + updateAndSignal(selected, w); + logPivot(w); + return w; + } +} + +WitnessImprovement FCSimplexDecisionProcedure::focusDownToLastHalf(){ + Assert(d_focusSize >= 2); + + Trace("focusDownToLastHalf") << "focusDownToLastHalf " + << d_errorSet.errorSize() << " " + << d_errorSet.focusSize() << " "; + + uint32_t half = d_focusSize/2; + ArithVarVec buf; + for(ErrorSet::focus_iterator i = d_errorSet.focusBegin(), + i_end = d_errorSet.focusEnd(); i != i_end; ++i){ + if(half > 0){ + --half; + } else{ + buf.push_back(*i); + } + } + WitnessImprovement w = adjustFocusShrank(buf); + Trace("focusDownToLastHalf") << "-> " << d_errorSet.focusSize() << endl; + return w; +} + +WitnessImprovement FCSimplexDecisionProcedure::selectFocusImproving() { + Assert(d_focusErrorVar != ARITHVAR_SENTINEL); + Assert(d_focusSize >= 2); + + LinearEqualityModule::UpdatePreferenceFunction upf = + &LinearEqualityModule::preferWitness; + + LinearEqualityModule::VarPreferenceFunction bpf = + &LinearEqualityModule::minRowLength; + + UpdateInfo selected = selectPrimalUpdate(d_focusErrorVar, upf, bpf); + + if(selected.uninitialized()){ + Trace("selectFocusImproving") << "focus is optimum, but we don't have sat/conflict yet" << endl; + + return focusDownToLastHalf(); + } + Assert(!selected.uninitialized()); + WitnessImprovement w = selected.getWitness(false); + Assert(debugCheckWitness(selected, w, false)); + + if(degenerate(w)){ + Trace("selectFocusImproving") << "only degenerate" << endl; + if(d_prevWitnessImprovement == HeuristicDegenerate && + d_witnessImprovementInARow >= s_focusThreshold){ + Trace("selectFocusImproving") << "focus down been degenerate too long" << endl; + return focusDownToLastHalf(); + }else{ + Trace("selectFocusImproving") << "taking degenerate" << endl; + } + } + Trace("selectFocusImproving") << "selectFocusImproving did this " << selected << endl; + + updateAndSignal(selected, w); + logPivot(w); + return w; +} + +bool FCSimplexDecisionProcedure::debugDualLike(WitnessImprovement w, + ostream& out, + uint32_t prevFocusSize, + uint32_t prevErrorSize) const +{ + out << "DLV() "; + switch(w){ + case ConflictFound: + out << "found conflict" << endl; + return !d_conflictVariables.empty(); + case ErrorDropped: + out << "dropped " << prevErrorSize - d_errorSize << endl; + return d_errorSize < prevErrorSize; + case FocusImproved: + out << "focus improved"<< endl; + return d_errorSize == prevErrorSize; + case FocusShrank: + out << "focus shrank"<< endl; + return d_errorSize == prevErrorSize && prevFocusSize > d_focusSize; + case BlandsDegenerate: + out << "bland degenerate"<< endl; + return true; + case HeuristicDegenerate: + out << "heuristic degenerate"<< endl; + return true; + case AntiProductive: + out << "focus blur" << endl; + return prevFocusSize == 0; + case Degenerate: + return false; + } + return false; +} + +Result::Status FCSimplexDecisionProcedure::dualLike() +{ + TimerStat::CodeTimer codeTimer(d_statistics.d_fcTimer); + + Assert(d_sgnDisagreements.empty()); + Assert(d_pivotBudget != 0); + Assert(d_errorSize == d_errorSet.errorSize()); + Assert(d_errorSize > 0); + Assert(d_focusSize == d_errorSet.focusSize()); + Assert(d_focusSize > 0); + Assert(d_conflictVariables.empty()); + Assert(d_focusErrorVar == ARITHVAR_SENTINEL); + + d_scores.purge(); + d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); + + + while(d_pivotBudget != 0 && d_errorSize > 0 && d_conflictVariables.empty()){ + Trace("dualLike") << "dualLike " << endl; + + Assert(d_errorSet.noSignals()); + + WitnessImprovement w = AntiProductive; + uint32_t prevFocusSize = d_focusSize; + uint32_t prevErrorSize = d_errorSize; + + if(d_focusSize == 0){ + Assert(d_errorSize == d_errorSet.errorSize()); + Assert(d_focusErrorVar == ARITHVAR_SENTINEL); + + d_errorSet.blur(); + + d_focusSize = d_errorSet.focusSize(); + + Assert(d_errorSize == d_focusSize); + Assert(d_errorSize >= 1); + + d_focusErrorVar = constructInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer); + + Trace("dualLike") << "blur " << d_focusSize << endl; + }else if(d_focusSize == 1){ + // Possible outcomes: + // - errorSet size shrunk + // -- fixed v + // -- fixed something other than v + // - conflict + // - budget was exhausted + + ArithVar e = d_errorSet.topFocusVariable(); + Trace("dualLike") << "primalImproveError " << e << endl; + w = primalImproveError(e); + }else{ + + // Possible outcomes: + // - errorSet size shrunk + // -- fixed v + // -- fixed something other than v + // - conflict + // - budget was exhausted + // - focus went down + Assert(d_focusSize > 1); + ArithVar e = d_errorSet.topFocusVariable(); + static constexpr unsigned s_sumMetricThreshold = 1; + if(d_errorSet.sumMetric(e) <= s_sumMetricThreshold){ + Trace("dualLike") << "dualLikeImproveError " << e << endl; + w = dualLikeImproveError(e); + }else{ + Trace("dualLike") << "selectFocusImproving " << endl; + w = selectFocusImproving(); + } + } + Trace("dualLike") << "witnessImprovement: " << w << endl; + Assert(d_focusSize == d_errorSet.focusSize()); + Assert(d_errorSize == d_errorSet.errorSize()); + + Assert(debugDualLike(w, Trace("dualLike"), prevFocusSize, prevErrorSize)); + Trace("dualLike") << "Focus size " << d_focusSize << " (was " << prevFocusSize << ")" << endl; + Trace("dualLike") << "Error size " << d_errorSize << " (was " << prevErrorSize << ")" << endl; + } + + + if(d_focusErrorVar != ARITHVAR_SENTINEL){ + tearDownInfeasiblityFunction(d_statistics.d_fcFocusConstructionTimer, d_focusErrorVar); + d_focusErrorVar = ARITHVAR_SENTINEL; + } + + Assert(d_focusErrorVar == ARITHVAR_SENTINEL); + if(!d_conflictVariables.empty()){ + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Assert(d_errorSet.noSignals()); + return Result::SAT; + }else{ + Assert(d_pivotBudget == 0); + return Result::UNKNOWN; + } +} + + +void FCSimplexDecisionProcedure::loadFocusSigns(){ + Assert(d_focusCoefficients.empty()); + Assert(d_focusErrorVar != ARITHVAR_SENTINEL); + for(Tableau::RowIterator ri = d_tableau.basicRowIterator(d_focusErrorVar); !ri.atEnd(); ++ri){ + const Tableau::Entry& e = *ri; + ArithVar curr = e.getColVar(); + d_focusCoefficients.set(curr, &e.getCoefficient()); + } +} + +void FCSimplexDecisionProcedure::unloadFocusSigns(){ + d_focusCoefficients.purge(); +} + +const Rational& FCSimplexDecisionProcedure::focusCoefficient(ArithVar nb) const { + if(d_focusCoefficients.isKey(nb)){ + return *(d_focusCoefficients[nb]); + }else{ + return d_zero; + } +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/fc_simplex.h b/src/theory/arith/linear/fc_simplex.h new file mode 100644 index 000000000..1d07df95a --- /dev/null +++ b/src/theory/arith/linear/fc_simplex.h @@ -0,0 +1,259 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Morgan Deters + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T)decision procedure. + * + * This implements the Simplex module for the Simpelx for DPLL(T) decision + * procedure. + * See the Simplex for DPLL(T) technical report for more background.(citation?) + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This is required to either produce a conflict or satisifying PartialModel. + * Further, we require being told when a basic variable updates its value. + * + * During the Simplex search we maintain a queue of variables. + * The queue is required to contain all of the basic variables that voilate + * their bounds. + * As elimination from the queue is more efficient to be done lazily, + * we do not maintain that the queue of variables needs to be only basic + * variables or only variables that satisfy their bounds. + * + * The simplex procedure roughly follows Alberto's thesis. (citation?) + * There is one round of selecting using a heuristic pivoting rule. + * (See PreferenceFunction Documentation for the available options.) + * The non-basic variable is the one that appears in the fewest pivots. + * (Bruno says that Leonardo invented this first.) + * After this, Bland's pivot rule is invoked. + * + * During this proccess, we periodically inspect the queue of variables to + * 1) remove now extraneous extries, + * 2) detect conflicts that are "waiting" on the queue but may not be detected + * by the current queue heuristics, and + * 3) detect multiple conflicts. + * + * Conflicts are greedily slackened to use the weakest bounds that still + * produce the conflict. + * + * Extra things tracked atm: (Subject to change at Tim's whims) + * - A superset of all of the newly pivoted variables. + * - A queue of additional conflicts that were discovered by Simplex. + * These are theory valid and are currently turned into lemmas + */ + +#include "cvc5_private.h" + +#pragma once + +#include "theory/arith/linear/error_set.h" +#include "theory/arith/linear/linear_equality.h" +#include "theory/arith/linear/simplex.h" +#include "theory/arith/linear/simplex_update.h" +#include "util/dense_map.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class FCSimplexDecisionProcedure : public SimplexDecisionProcedure{ +public: + FCSimplexDecisionProcedure(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc); + + Result::Status findModel(bool exactResult) override; + + // other error variables are dropping + WitnessImprovement dualLikeImproveError(ArithVar evar); + WitnessImprovement primalImproveError(ArithVar evar); + + // dual like + // - found conflict + // - satisfied error set + Result::Status dualLike(); + +private: + static constexpr uint32_t PENALTY = 4; + DenseMultiset d_scores; + void decreasePenalties() { d_scores.removeOneOfEverything(); } + uint32_t penalty(ArithVar x) const { return d_scores.count(x); } + void setPenalty(ArithVar x, WitnessImprovement w) + { + if (improvement(w)) + { + if (d_scores.count(x) > 0) + { + d_scores.removeAll(x); + } + } + else + { + d_scores.setCount(x, PENALTY); + } + } + + /** The size of the focus set. */ + uint32_t d_focusSize; + + /** The current error focus variable. */ + ArithVar d_focusErrorVar; + + /** + * The signs of the coefficients in the focus set. + * This is empty until this has been loaded. + */ + DenseMap d_focusCoefficients; + + /** + * Loads the signs of the coefficients of the variables on the row d_focusErrorVar + * into d_focusSgns. + */ + void loadFocusSigns(); + + /** Unloads the information from d_focusSgns. */ + void unloadFocusSigns(); + + /** + * The signs of a variable in the row of d_focusErrorVar. + * d_focusSgns must be loaded. + */ + const Rational& focusCoefficient(ArithVar nb) const; + + int32_t d_pivotBudget; + + WitnessImprovement d_prevWitnessImprovement; + uint32_t d_witnessImprovementInARow; + + uint32_t degeneratePivotsInARow() const; + + static constexpr uint32_t s_focusThreshold = 6; + static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnLeaving = 100; + static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnEntering = 10; + + DenseMap d_leavingCountSinceImprovement; + void increaseLeavingCount(ArithVar x){ + if(!d_leavingCountSinceImprovement.isKey(x)){ + d_leavingCountSinceImprovement.set(x,1); + }else{ + (d_leavingCountSinceImprovement.get(x))++; + } + } + LinearEqualityModule::UpdatePreferenceFunction selectLeavingFunction(ArithVar x){ + bool useBlands = d_leavingCountSinceImprovement.isKey(x) && + d_leavingCountSinceImprovement[x] >= s_maxDegeneratePivotsBeforeBlandsOnEntering; + if(useBlands) { + return &LinearEqualityModule::preferWitness; + } else { + return &LinearEqualityModule::preferWitness; + } + } + + bool debugDualLike(WitnessImprovement w, std::ostream& out, + uint32_t prevFocusSize, uint32_t prevErrorSize) const; + + void debugPrintSignal(ArithVar updated) const; + + ArithVarVec d_sgnDisagreements; + + void logPivot(WitnessImprovement w); + + void updateAndSignal(const UpdateInfo& selected, WitnessImprovement w); + + UpdateInfo selectPrimalUpdate(ArithVar error, + LinearEqualityModule::UpdatePreferenceFunction upf, + LinearEqualityModule::VarPreferenceFunction bpf); + + + UpdateInfo selectUpdateForDualLike(ArithVar basic){ + TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForDualLike); + + LinearEqualityModule::UpdatePreferenceFunction upf = + &LinearEqualityModule::preferWitness; + LinearEqualityModule::VarPreferenceFunction bpf = + &LinearEqualityModule::minVarOrder; + return selectPrimalUpdate(basic, upf, bpf); + } + + UpdateInfo selectUpdateForPrimal(ArithVar basic, bool useBlands){ + TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForPrimal); + + LinearEqualityModule::UpdatePreferenceFunction upf; + if(useBlands) { + upf = &LinearEqualityModule::preferWitness; + } else { + upf = &LinearEqualityModule::preferWitness; + } + + LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? + &LinearEqualityModule::minVarOrder : + &LinearEqualityModule::minRowLength; + + return selectPrimalUpdate(basic, upf, bpf); + } + WitnessImprovement selectFocusImproving() ; + + WitnessImprovement focusUsingSignDisagreements(ArithVar basic); + WitnessImprovement focusDownToLastHalf(); + WitnessImprovement adjustFocusShrank(const ArithVarVec& drop); + WitnessImprovement focusDownToJust(ArithVar v); + + + void adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges); + + /** + * This is the main simplex for DPLL(T) loop. + * It runs for at most maxIterations. + * + * Returns true iff it has found a conflict. + * d_conflictVariable will be set and the conflict for this row is reported. + */ + bool searchForFeasibleSolution(uint32_t maxIterations); + + bool initialProcessSignals(){ + TimerStat &timer = d_statistics.d_initialSignalsTime; + IntStat& conflictStat = d_statistics.d_initialConflicts; + bool res = standardProcessSignals(timer, conflictStat); + d_focusSize = d_errorSet.focusSize(); + return res; + } + + static bool debugCheckWitness(const UpdateInfo& inf, WitnessImprovement w, bool useBlands); + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + TimerStat d_initialSignalsTime; + IntStat d_initialConflicts; + + IntStat d_fcFoundUnsat; + IntStat d_fcFoundSat; + IntStat d_fcMissed; + + TimerStat d_fcTimer; + TimerStat d_fcFocusConstructionTimer; + + TimerStat d_selectUpdateForDualLike; + TimerStat d_selectUpdateForPrimal; + + ReferenceStat d_finalCheckPivotCounter; + + Statistics(const std::string& name, uint32_t& pivots); + } d_statistics; +};/* class FCSimplexDecisionProcedure */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/infer_bounds.cpp b/src/theory/arith/linear/infer_bounds.cpp new file mode 100644 index 000000000..ec2843aa2 --- /dev/null +++ b/src/theory/arith/linear/infer_bounds.cpp @@ -0,0 +1,271 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andres Noetzli, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/infer_bounds.h" +#include "theory/rewriter.h" + +using namespace cvc5::internal::kind; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +using namespace inferbounds; + +InferBoundAlgorithm::InferBoundAlgorithm() + : d_alg(None) +{} + +InferBoundAlgorithm::InferBoundAlgorithm(Algorithms a) + : d_alg(a) +{ + Assert(a != Simplex); +} + +InferBoundAlgorithm::InferBoundAlgorithm( + const std::optional& simplexRounds) + : d_alg(Simplex) +{} + +Algorithms InferBoundAlgorithm::getAlgorithm() const{ + return d_alg; +} + +const std::optional& InferBoundAlgorithm::getSimplexRounds() const +{ + Assert(getAlgorithm() == Simplex); + return d_simplexRounds; +} + +InferBoundAlgorithm InferBoundAlgorithm::mkLookup(){ + return InferBoundAlgorithm(Lookup); +} + +InferBoundAlgorithm InferBoundAlgorithm::mkRowSum(){ + return InferBoundAlgorithm(RowSum); +} + +InferBoundAlgorithm InferBoundAlgorithm::mkSimplex( + const std::optional& rounds) +{ + return InferBoundAlgorithm(rounds); +} + +ArithEntailmentCheckParameters::ArithEntailmentCheckParameters() + : d_algorithms() +{} + +ArithEntailmentCheckParameters::~ArithEntailmentCheckParameters() +{} + + +void ArithEntailmentCheckParameters::addLookupRowSumAlgorithms(){ + addAlgorithm(InferBoundAlgorithm::mkLookup()); + addAlgorithm(InferBoundAlgorithm::mkRowSum()); +} + +void ArithEntailmentCheckParameters::addAlgorithm(const inferbounds::InferBoundAlgorithm& alg){ + d_algorithms.push_back(alg); +} + +ArithEntailmentCheckParameters::const_iterator ArithEntailmentCheckParameters::begin() const{ + return d_algorithms.begin(); +} + +ArithEntailmentCheckParameters::const_iterator ArithEntailmentCheckParameters::end() const{ + return d_algorithms.end(); +} + +InferBoundsResult::InferBoundsResult() + : d_foundBound(false) + , d_budgetExhausted(false) + , d_boundIsProvenOpt(false) + , d_inconsistentState(false) + , d_reachedThreshold(false) + , d_value(false) + , d_term(Node::null()) + , d_upperBound(true) + , d_explanation(Node::null()) +{} + +InferBoundsResult::InferBoundsResult(Node term, bool ub) + : d_foundBound(false) + , d_budgetExhausted(false) + , d_boundIsProvenOpt(false) + , d_inconsistentState(false) + , d_reachedThreshold(false) + , d_value(false) + , d_term(term) + , d_upperBound(ub) + , d_explanation(Node::null()) +{} + +bool InferBoundsResult::foundBound() const { + return d_foundBound; +} +bool InferBoundsResult::boundIsOptimal() const { + return d_boundIsProvenOpt; +} +bool InferBoundsResult::inconsistentState() const { + return d_inconsistentState; +} + +bool InferBoundsResult::boundIsInteger() const{ + return foundBound() && d_value.isIntegral(); +} + +bool InferBoundsResult::boundIsRational() const { + return foundBound() && d_value.infinitesimalIsZero(); +} + +Integer InferBoundsResult::valueAsInteger() const{ + Assert(boundIsInteger()); + return getValue().floor(); +} +const Rational& InferBoundsResult::valueAsRational() const{ + Assert(boundIsRational()); + return getValue().getNoninfinitesimalPart(); +} + +const DeltaRational& InferBoundsResult::getValue() const{ + return d_value; +} + +Node InferBoundsResult::getTerm() const { return d_term; } + +Node InferBoundsResult::getLiteral() const{ + const Rational& q = getValue().getNoninfinitesimalPart(); + NodeManager* nm = NodeManager::currentNM(); + Node qnode = nm->mkConst(CONST_RATIONAL, q); + + Kind k; + if(d_upperBound){ + // x <= q + c*delta + Assert(getValue().infinitesimalSgn() <= 0); + k = boundIsRational() ? kind::LEQ : kind::LT; + }else{ + // x >= q + c*delta + Assert(getValue().infinitesimalSgn() >= 0); + k = boundIsRational() ? kind::GEQ : kind::GT; + } + return nm->mkNode(k, getTerm(), qnode); +} + +/* If there is a bound, this is a node that explains the bound. */ +Node InferBoundsResult::getExplanation() const{ + return d_explanation; +} + + +void InferBoundsResult::setBound(const DeltaRational& dr, Node exp){ + d_foundBound = true; + d_value = dr; + d_explanation = exp; +} + +void InferBoundsResult::setBudgetExhausted() { d_budgetExhausted = true; } +void InferBoundsResult::setReachedThreshold() { d_reachedThreshold = true; } +void InferBoundsResult::setIsOptimal() { d_boundIsProvenOpt = true; } +void InferBoundsResult::setInconsistent() { d_inconsistentState = true; } + +bool InferBoundsResult::thresholdWasReached() const{ + return d_reachedThreshold; +} +bool InferBoundsResult::budgetIsExhausted() const{ + return d_budgetExhausted; +} + +std::ostream& operator<<(std::ostream& os, const InferBoundsResult& ibr){ + os << "{InferBoundsResult " << std::endl; + os << "on " << ibr.getTerm() << ", "; + if(ibr.findUpperBound()){ + os << "find upper bound, "; + }else{ + os << "find lower bound, "; + } + if(ibr.foundBound()){ + os << "found a bound: "; + if(ibr.boundIsInteger()){ + os << ibr.valueAsInteger() << "(int), "; + }else if(ibr.boundIsRational()){ + os << ibr.valueAsRational() << "(rat), "; + }else{ + os << ibr.getValue() << "(extended), "; + } + + os << "as term " << ibr.getLiteral() << ", "; + os << "explanation " << ibr.getExplanation() << ", "; + }else { + os << "did not find a bound, "; + } + + if(ibr.boundIsOptimal()){ + os << "(opt), "; + } + + if(ibr.inconsistentState()){ + os << "(inconsistent), "; + } + if(ibr.budgetIsExhausted()){ + os << "(budget exhausted), "; + } + if(ibr.thresholdWasReached()){ + os << "(reached threshold), "; + } + os << "}"; + return os; +} + +ArithEntailmentCheckSideEffects::ArithEntailmentCheckSideEffects() + : d_simplexSideEffects(NULL) +{} + +ArithEntailmentCheckSideEffects::~ArithEntailmentCheckSideEffects(){ + if(d_simplexSideEffects != NULL){ + delete d_simplexSideEffects; + d_simplexSideEffects = NULL; + } +} + +InferBoundsResult& ArithEntailmentCheckSideEffects::getSimplexSideEffects(){ + if(d_simplexSideEffects == NULL){ + d_simplexSideEffects = new InferBoundsResult; + } + return *d_simplexSideEffects; +} + +namespace inferbounds { /* namespace arith */ + +std::ostream& operator<<(std::ostream& os, const Algorithms a){ + switch(a){ + case None: os << "AlgNone"; break; + case Lookup: os << "AlgLookup"; break; + case RowSum: os << "AlgRowSum"; break; + case Simplex: os << "AlgSimplex"; break; + default: + Unhandled(); + } + + return os; +} + +} /* namespace inferbounds */ + +} /* namespace arith */ +} /* namespace theory */ +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/infer_bounds.h b/src/theory/arith/linear/infer_bounds.h new file mode 100644 index 000000000..de6b50e38 --- /dev/null +++ b/src/theory/arith/linear/infer_bounds.h @@ -0,0 +1,164 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andrew Reynolds, Andres Noetzli + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include +#include + +#include "expr/node.h" +#include "theory/arith/delta_rational.h" +#include "util/integer.h" +#include "util/rational.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +namespace inferbounds { + enum Algorithms {None = 0, Lookup, RowSum, Simplex}; + enum SimplexParamKind { Unbounded, NumVars, Direct}; + +class InferBoundAlgorithm { +private: + Algorithms d_alg; + std::optional d_simplexRounds; + InferBoundAlgorithm(Algorithms a); + InferBoundAlgorithm(const std::optional& simplexRounds); + + public: + InferBoundAlgorithm(); + + Algorithms getAlgorithm() const; + const std::optional& getSimplexRounds() const; + + static InferBoundAlgorithm mkLookup(); + static InferBoundAlgorithm mkRowSum(); + static InferBoundAlgorithm mkSimplex(const std::optional& rounds); +}; + +std::ostream& operator<<(std::ostream& os, const Algorithms a); +} /* namespace inferbounds */ + +class ArithEntailmentCheckParameters +{ + private: + typedef std::vector VecInferBoundAlg; + VecInferBoundAlg d_algorithms; + +public: + typedef VecInferBoundAlg::const_iterator const_iterator; + + ArithEntailmentCheckParameters(); + ~ArithEntailmentCheckParameters(); + + void addLookupRowSumAlgorithms(); + void addAlgorithm(const inferbounds::InferBoundAlgorithm& alg); + + const_iterator begin() const; + const_iterator end() const; +}; + + + +class InferBoundsResult { +public: + InferBoundsResult(); + InferBoundsResult(Node term, bool ub); + + void setBound(const DeltaRational& dr, Node exp); + bool foundBound() const; + + void setIsOptimal(); + bool boundIsOptimal() const; + + void setInconsistent(); + bool inconsistentState() const; + + const DeltaRational& getValue() const; + bool boundIsRational() const; + const Rational& valueAsRational() const; + bool boundIsInteger() const; + Integer valueAsInteger() const; + + Node getTerm() const; + Node getLiteral() const; + void setTerm(Node t){ d_term = t; } + + /* If there is a bound, this is a node that explains the bound. */ + Node getExplanation() const; + + bool budgetIsExhausted() const; + void setBudgetExhausted(); + + bool thresholdWasReached() const; + void setReachedThreshold(); + + bool findUpperBound() const { return d_upperBound; } + + void setFindLowerBound() { d_upperBound = false; } + void setFindUpperBound() { d_upperBound = true; } +private: + /* was a bound found */ + bool d_foundBound; + + /* was the budget exhausted */ + bool d_budgetExhausted; + + /* does the bound have to be optimal*/ + bool d_boundIsProvenOpt; + + /* was this started on an inconsistent state. */ + bool d_inconsistentState; + + /* reached the threshold. */ + bool d_reachedThreshold; + + /* the value of the bound */ + DeltaRational d_value; + + /* The input term. */ + Node d_term; + + /* Was the bound found an upper or lower bound.*/ + bool d_upperBound; + + /* Explanation of the bound. */ + Node d_explanation; +}; + +std::ostream& operator<<(std::ostream& os, const InferBoundsResult& ibr); + +class ArithEntailmentCheckSideEffects +{ + public: + ArithEntailmentCheckSideEffects(); + ~ArithEntailmentCheckSideEffects(); + + InferBoundsResult& getSimplexSideEffects(); + +private: + InferBoundsResult* d_simplexSideEffects; +}; + + +} /* namespace arith */ +} /* namespace theory */ +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/linear_equality.cpp b/src/theory/arith/linear/linear_equality.cpp new file mode 100644 index 000000000..6fcabefab --- /dev/null +++ b/src/theory/arith/linear/linear_equality.cpp @@ -0,0 +1,1375 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andres Noetzli + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This implements the LinearEqualityModule. + */ +#include "theory/arith/linear/linear_equality.h" + +#include "base/output.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" + + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +/* Explicitly instatiate these functions. */ + +template ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; +template ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; + +template bool LinearEqualityModule::preferWitness(const UpdateInfo& a, const UpdateInfo& b) const; +template bool LinearEqualityModule::preferWitness(const UpdateInfo& a, const UpdateInfo& b) const; + + +void Border::output(std::ostream& out) const{ + out << "{Border" + << ", " << d_bound->getVariable() + << ", " << d_bound->getValue() + << ", " << d_diff + << ", " << d_areFixing + << ", " << d_upperbound; + if(ownBorder()){ + out << ", ownBorder"; + }else{ + out << ", " << d_entry->getCoefficient(); + } + out << ", " << d_bound + << "}"; +} + +LinearEqualityModule::LinearEqualityModule(ArithVariables& vars, Tableau& t, BoundInfoMap& boundsTracking, BasicVarModelUpdateCallBack f): + d_variables(vars), + d_tableau(t), + d_basicVariableUpdates(f), + d_increasing(1), + d_decreasing(-1), + d_upperBoundDifference(), + d_lowerBoundDifference(), + d_one(1), + d_negOne(-1), + d_btracking(boundsTracking), + d_areTracking(false), + d_trackCallback(this) +{} + +LinearEqualityModule::Statistics::Statistics() + : d_statPivots( + smtStatisticsRegistry().registerInt("theory::arith::pivots")), + d_statUpdates( + smtStatisticsRegistry().registerInt("theory::arith::updates")), + d_pivotTime( + smtStatisticsRegistry().registerTimer("theory::arith::pivotTime")), + d_adjTime( + smtStatisticsRegistry().registerTimer("theory::arith::adjTime")), + d_weakeningAttempts(smtStatisticsRegistry().registerInt( + "theory::arith::weakening::attempts")), + d_weakeningSuccesses(smtStatisticsRegistry().registerInt( + "theory::arith::weakening::success")), + d_weakenings(smtStatisticsRegistry().registerInt( + "theory::arith::weakening::total")), + d_weakenTime(smtStatisticsRegistry().registerTimer( + "theory::arith::weakening::time")), + d_forceTime( + smtStatisticsRegistry().registerTimer("theory::arith::forcing::time")) +{ +} + +void LinearEqualityModule::includeBoundUpdate(ArithVar v, const BoundsInfo& prev){ + Assert(!d_areTracking); + + BoundsInfo curr = d_variables.boundsInfo(v); + + Assert(prev != curr); + Tableau::ColIterator basicIter = d_tableau.colIterator(v); + for(; !basicIter.atEnd(); ++basicIter){ + const Tableau::Entry& entry = *basicIter; + Assert(entry.getColVar() == v); + int a_ijSgn = entry.getCoefficient().sgn(); + + RowIndex ridx = entry.getRowIndex(); + BoundsInfo& counts = d_btracking.get(ridx); + Trace("includeBoundUpdate") << d_tableau.rowIndexToBasic(ridx) << " " << counts << " to " ; + counts.addInChange(a_ijSgn, prev, curr); + Trace("includeBoundUpdate") << counts << " " << a_ijSgn << std::endl; + } +} + +void LinearEqualityModule::updateMany(const DenseMap& many){ + for(DenseMap::const_iterator i = many.begin(), i_end = many.end(); i != i_end; ++i){ + ArithVar nb = *i; + if(!d_tableau.isBasic(nb)){ + Assert(!d_tableau.isBasic(nb)); + const DeltaRational& newValue = many[nb]; + if(newValue != d_variables.getAssignment(nb)){ + Trace("arith::updateMany") + << "updateMany:" << nb << " " + << d_variables.getAssignment(nb) << " to "<< newValue << endl; + update(nb, newValue); + } + } + } +} + + + + +void LinearEqualityModule::applySolution(const DenseSet& newBasis, const DenseMap& newValues){ + forceNewBasis(newBasis); + updateMany(newValues); +} + +void LinearEqualityModule::forceNewBasis(const DenseSet& newBasis){ + TimerStat::CodeTimer codeTimer(d_statistics.d_forceTime); + cout << "force begin" << endl; + DenseSet needsToBeAdded; + for(DenseSet::const_iterator i = newBasis.begin(), i_end = newBasis.end(); i != i_end; ++i){ + ArithVar b = *i; + if(!d_tableau.isBasic(b)){ + needsToBeAdded.add(b); + } + } + + while(!needsToBeAdded.empty()){ + ArithVar toRemove = ARITHVAR_SENTINEL; + ArithVar toAdd = ARITHVAR_SENTINEL; + DenseSet::const_iterator i = needsToBeAdded.begin(), i_end = needsToBeAdded.end(); + for(; toAdd == ARITHVAR_SENTINEL && i != i_end; ++i){ + ArithVar v = *i; + + Tableau::ColIterator colIter = d_tableau.colIterator(v); + for(; !colIter.atEnd(); ++colIter){ + const Tableau::Entry& entry = *colIter; + Assert(entry.getColVar() == v); + ArithVar b = d_tableau.rowIndexToBasic(entry.getRowIndex()); + if(!newBasis.isMember(b)){ + toAdd = v; + if(toRemove == ARITHVAR_SENTINEL || + d_tableau.basicRowLength(toRemove) > d_tableau.basicRowLength(b)){ + toRemove = b; + } + } + } + } + Assert(toRemove != ARITHVAR_SENTINEL); + Assert(toAdd != ARITHVAR_SENTINEL); + + Trace("arith::forceNewBasis") << toRemove << " " << toAdd << endl; + d_tableau.pivot(toRemove, toAdd, d_trackCallback); + d_basicVariableUpdates(toAdd); + + Trace("arith::forceNewBasis") << needsToBeAdded.size() << "to go" << endl; + needsToBeAdded.remove(toAdd); + } +} + +void LinearEqualityModule::updateUntracked(ArithVar x_i, const DeltaRational& v){ + Assert(!d_tableau.isBasic(x_i)); + Assert(!d_areTracking); + const DeltaRational& assignment_x_i = d_variables.getAssignment(x_i); + ++(d_statistics.d_statUpdates); + + + Trace("arith") <<"update " << x_i << ": " + << assignment_x_i << "|-> " << v << endl; + DeltaRational diff = v - assignment_x_i; + + Tableau::ColIterator colIter = d_tableau.colIterator(x_i); + for(; !colIter.atEnd(); ++colIter){ + const Tableau::Entry& entry = *colIter; + Assert(entry.getColVar() == x_i); + + ArithVar x_j = d_tableau.rowIndexToBasic(entry.getRowIndex()); + const Rational& a_ji = entry.getCoefficient(); + + const DeltaRational& assignment = d_variables.getAssignment(x_j); + DeltaRational nAssignment = assignment+(diff * a_ji); + d_variables.setAssignment(x_j, nAssignment); + + d_basicVariableUpdates(x_j); + } + + d_variables.setAssignment(x_i, v); + + if(TraceIsOn("paranoid:check_tableau")){ debugCheckTableau(); } +} + +void LinearEqualityModule::updateTracked(ArithVar x_i, const DeltaRational& v){ + TimerStat::CodeTimer codeTimer(d_statistics.d_adjTime); + + Assert(!d_tableau.isBasic(x_i)); + Assert(d_areTracking); + + ++(d_statistics.d_statUpdates); + + DeltaRational diff = v - d_variables.getAssignment(x_i); + Trace("arith") <<"update " << x_i << ": " + << d_variables.getAssignment(x_i) << "|-> " << v << endl; + + + BoundCounts before = d_variables.atBoundCounts(x_i); + d_variables.setAssignment(x_i, v); + BoundCounts after = d_variables.atBoundCounts(x_i); + + bool anyChange = before != after; + + Tableau::ColIterator colIter = d_tableau.colIterator(x_i); + for(; !colIter.atEnd(); ++colIter){ + const Tableau::Entry& entry = *colIter; + Assert(entry.getColVar() == x_i); + + RowIndex ridx = entry.getRowIndex(); + ArithVar x_j = d_tableau.rowIndexToBasic(ridx); + const Rational& a_ji = entry.getCoefficient(); + + const DeltaRational& assignment = d_variables.getAssignment(x_j); + DeltaRational nAssignment = assignment+(diff * a_ji); + Trace("update") << x_j << " " << a_ji << assignment << " -> " << nAssignment << endl; + BoundCounts xjBefore = d_variables.atBoundCounts(x_j); + d_variables.setAssignment(x_j, nAssignment); + BoundCounts xjAfter = d_variables.atBoundCounts(x_j); + + Assert(rowIndexIsTracked(ridx)); + BoundsInfo& next_bc_k = d_btracking.get(ridx); + if(anyChange){ + next_bc_k.addInAtBoundChange(a_ji.sgn(), before, after); + } + if(xjBefore != xjAfter){ + next_bc_k.addInAtBoundChange(-1, xjBefore, xjAfter); + } + + d_basicVariableUpdates(x_j); + } + + if(TraceIsOn("paranoid:check_tableau")){ debugCheckTableau(); } +} + +void LinearEqualityModule::pivotAndUpdate(ArithVar x_i, ArithVar x_j, const DeltaRational& x_i_value){ + Assert(x_i != x_j); + + TimerStat::CodeTimer codeTimer(d_statistics.d_pivotTime); + + if(TraceIsOn("arith::tracking::pre")){ + Trace("arith::tracking") << "pre update" << endl; + debugCheckTracking(); + } + + if(TraceIsOn("arith::simplex:row")){ debugPivot(x_i, x_j); } + + RowIndex ridx = d_tableau.basicToRowIndex(x_i); + const Tableau::Entry& entry_ij = d_tableau.findEntry(ridx, x_j); + Assert(!entry_ij.blank()); + + const Rational& a_ij = entry_ij.getCoefficient(); + const DeltaRational& betaX_i = d_variables.getAssignment(x_i); + DeltaRational theta = (x_i_value - betaX_i)/a_ij; + DeltaRational x_j_value = d_variables.getAssignment(x_j) + theta; + + updateTracked(x_j, x_j_value); + + if(TraceIsOn("arith::tracking::mid")){ + Trace("arith::tracking") << "postupdate prepivot" << endl; + debugCheckTracking(); + } + + // Pivots + ++(d_statistics.d_statPivots); + + d_tableau.pivot(x_i, x_j, d_trackCallback); + + if(TraceIsOn("arith::tracking::post")){ + Trace("arith::tracking") << "postpivot" << endl; + debugCheckTracking(); + } + + d_basicVariableUpdates(x_j); + + if(TraceIsOn("matrix")){ + d_tableau.printMatrix(); + } +} + +uint32_t LinearEqualityModule::updateProduct(const UpdateInfo& inf) const { + uint32_t colLen = d_tableau.getColLength(inf.nonbasic()); + if(inf.describesPivot()){ + Assert(inf.leaving() != inf.nonbasic()); + return colLen + d_tableau.basicRowLength(inf.leaving()); + }else{ + return colLen; + } +} + +void LinearEqualityModule::debugCheckTracking(){ + Tableau::BasicIterator basicIter = d_tableau.beginBasic(), + endIter = d_tableau.endBasic(); + for(; basicIter != endIter; ++basicIter){ + ArithVar basic = *basicIter; + Trace("arith::tracking") << "arith::tracking row basic: " << basic << endl; + + for(Tableau::RowIterator iter = d_tableau.basicRowIterator(basic); !iter.atEnd() && TraceIsOn("arith::tracking"); ++iter){ + const Tableau::Entry& entry = *iter; + + ArithVar var = entry.getColVar(); + const Rational& coeff = entry.getCoefficient(); + DeltaRational beta = d_variables.getAssignment(var); + Trace("arith::tracking") << var << " " << d_variables.boundsInfo(var) + << " " << beta << coeff; + if(d_variables.hasLowerBound(var)){ + Trace("arith::tracking") << "(lb " << d_variables.getLowerBound(var) << ")"; + } + if(d_variables.hasUpperBound(var)){ + Trace("arith::tracking") << "(up " << d_variables.getUpperBound(var) << ")"; + } + Trace("arith::tracking") << endl; + } + Trace("arith::tracking") << "end row"<< endl; + + if(basicIsTracked(basic)){ + RowIndex ridx = d_tableau.basicToRowIndex(basic); + BoundsInfo computed = computeRowBoundInfo(ridx, false); + Trace("arith::tracking") + << "computed " << computed + << " tracking " << d_btracking[ridx] << endl; + Assert(computed == d_btracking[ridx]); + } + } +} + +void LinearEqualityModule::debugPivot(ArithVar x_i, ArithVar x_j){ + Trace("arith::pivot") << "debugPivot("<< x_i <<"|->"<< x_j << ")" << endl; + + for(Tableau::RowIterator iter = d_tableau.basicRowIterator(x_i); !iter.atEnd(); ++iter){ + const Tableau::Entry& entry = *iter; + + ArithVar var = entry.getColVar(); + const Rational& coeff = entry.getCoefficient(); + DeltaRational beta = d_variables.getAssignment(var); + Trace("arith::pivot") << var << beta << coeff; + if(d_variables.hasLowerBound(var)){ + Trace("arith::pivot") << "(lb " << d_variables.getLowerBound(var) << ")"; + } + if(d_variables.hasUpperBound(var)){ + Trace("arith::pivot") << "(up " << d_variables.getUpperBound(var) << ")"; + } + Trace("arith::pivot") << endl; + } + Trace("arith::pivot") << "end row"<< endl; +} + +/** + * This check is quite expensive. + * It should be wrapped in a TraceIsOn() guard. + * if(TraceIsOn("paranoid:check_tableau")){ + * checkTableau(); + * } + */ +void LinearEqualityModule::debugCheckTableau(){ + Tableau::BasicIterator basicIter = d_tableau.beginBasic(), + endIter = d_tableau.endBasic(); + for(; basicIter != endIter; ++basicIter){ + ArithVar basic = *basicIter; + DeltaRational sum; + Trace("paranoid:check_tableau") << "starting row" << basic << endl; + Tableau::RowIterator nonbasicIter = d_tableau.basicRowIterator(basic); + for(; !nonbasicIter.atEnd(); ++nonbasicIter){ + const Tableau::Entry& entry = *nonbasicIter; + ArithVar nonbasic = entry.getColVar(); + if(basic == nonbasic) continue; + + const Rational& coeff = entry.getCoefficient(); + DeltaRational beta = d_variables.getAssignment(nonbasic); + Trace("paranoid:check_tableau") << nonbasic << beta << coeff< 0)); + + const DeltaRational& bound = vUb ? + d_variables.getUpperBound(v): + d_variables.getLowerBound(v); + + DeltaRational diff = bound * coeff; + sum = sum + diff; + } + return sum; +} + +/** + * Computes the value of a basic variable using the current assignment. + */ +DeltaRational LinearEqualityModule::computeRowValue(ArithVar x, bool useSafe) const{ + Assert(d_tableau.isBasic(x)); + DeltaRational sum(0); + + for(Tableau::RowIterator i = d_tableau.basicRowIterator(x); !i.atEnd(); ++i){ + const Tableau::Entry& entry = (*i); + ArithVar nonbasic = entry.getColVar(); + if(nonbasic == x) continue; + const Rational& coeff = entry.getCoefficient(); + + const DeltaRational& assignment = d_variables.getAssignment(nonbasic, useSafe); + sum = sum + (assignment * coeff); + } + return sum; +} + +const Tableau::Entry* LinearEqualityModule::rowLacksBound(RowIndex ridx, bool rowUb, ArithVar skip){ + Tableau::RowIterator iter = d_tableau.ridRowIterator(ridx); + for(; !iter.atEnd(); ++iter){ + const Tableau::Entry& entry = *iter; + + ArithVar var = entry.getColVar(); + if(var == skip) { continue; } + + int sgn = entry.getCoefficient().sgn(); + bool selectUb = (rowUb == (sgn > 0)); + bool hasBound = selectUb ? + d_variables.hasUpperBound(var): + d_variables.hasLowerBound(var); + if(!hasBound){ + return &entry; + } + } + return NULL; +} + +void LinearEqualityModule::propagateBasicFromRow(ConstraintP c, + bool produceProofs) +{ + Assert(c != NullConstraint); + Assert(c->isUpperBound() || c->isLowerBound()); + Assert(!c->assertedToTheTheory()); + Assert(!c->hasProof()); + + bool upperBound = c->isUpperBound(); + ArithVar basic = c->getVariable(); + RowIndex ridx = d_tableau.basicToRowIndex(basic); + + ConstraintCPVec bounds; + RationalVectorP coeffs = produceProofs ? new RationalVector() : nullptr; + propagateRow(bounds, ridx, upperBound, c, coeffs); + c->impliedByFarkas(bounds, coeffs, false); + c->tryToPropagate(); + + if(coeffs != RationalVectorPSentinel) { delete coeffs; } +} + +/* An explanation of the farkas coefficients. + * + * We are proving c using the other variables on the row. + * The proof is in terms of the other constraints and the negation of c, ~c. + * + * A row has the form: + * sum a_i * x_i = 0 + * or + * sx + sum r y + sum q z = 0 + * where r > 0 and q < 0. + * + * If rowUp, we are proving c + * g = sum r u_y + sum q l_z + * and c is entailed by -sx <= g + * If !rowUp, we are proving c + * g = sum r l_y + sum q u_z + * and c is entailed by -sx >= g + * + * | s | c | ~c | u_i | l_i + * if rowUp | s > 0 | x >= -g/s | x < -g/s | a_i > 0 | a_i < 0 + * if rowUp | s < 0 | x <= -g/s | x > -g/s | a_i > 0 | a_i < 0 + * if !rowUp | s > 0 | x <= -g/s | x > -g/s | a_i < 0 | a_i > 0 + * if !rowUp | s < 0 | x >= -g/s | x < -g/s | a_i < 0 | a_i > 0 + * + * + * Thus we treat !rowUp as multiplying the row by -1 and rowUp as 1 + * for the entire row. + */ +void LinearEqualityModule::propagateRow(ConstraintCPVec& into, RowIndex ridx, bool rowUp, ConstraintP c, RationalVectorP farkas){ + Assert(!c->assertedToTheTheory()); + Assert(c->canBePropagated()); + Assert(!c->hasProof()); + + if(farkas != RationalVectorPSentinel){ + Assert(farkas->empty()); + farkas->push_back(Rational(0)); + } + + ArithVar v = c->getVariable(); + Trace("arith::propagateRow") << "LinearEqualityModule::propagateRow(" + << ridx << ", " << rowUp << ", " << v << ") start" << endl; + + const Rational& multiple = rowUp ? d_one : d_negOne; + + Trace("arith::propagateRow") << "multiple: " << multiple << endl; + + Tableau::RowIterator iter = d_tableau.ridRowIterator(ridx); + for(; !iter.atEnd(); ++iter){ + const Tableau::Entry& entry = *iter; + ArithVar nonbasic = entry.getColVar(); + const Rational& a_ij = entry.getCoefficient(); + int sgn = a_ij.sgn(); + Assert(sgn != 0); + bool selectUb = rowUp ? (sgn > 0) : (sgn < 0); + + Assert(nonbasic != v || (rowUp && a_ij.sgn() > 0 && c->isLowerBound()) + || (rowUp && a_ij.sgn() < 0 && c->isUpperBound()) + || (!rowUp && a_ij.sgn() > 0 && c->isUpperBound()) + || (!rowUp && a_ij.sgn() < 0 && c->isLowerBound())); + + if(TraceIsOn("arith::propagateRow")){ + if(nonbasic == v){ + Trace("arith::propagateRow") << "(target) " + << rowUp << " " + << a_ij.sgn() << " " + << c->isLowerBound() << " " + << c->isUpperBound() << endl; + + Trace("arith::propagateRow") << "(target) "; + } + Trace("arith::propagateRow") << "propagateRow " << a_ij << " * " << nonbasic ; + } + + if(nonbasic == v){ + if(farkas != RationalVectorPSentinel){ + Assert(farkas->front().isZero()); + Rational multAij = multiple * a_ij; + Trace("arith::propagateRow") << "(" << multAij << ") "; + farkas->front() = multAij; + } + + Trace("arith::propagateRow") << c << endl; + }else{ + + ConstraintCP bound = selectUb + ? d_variables.getUpperBoundConstraint(nonbasic) + : d_variables.getLowerBoundConstraint(nonbasic); + + if(farkas != RationalVectorPSentinel){ + Rational multAij = multiple * a_ij; + Trace("arith::propagateRow") << "(" << multAij << ") "; + farkas->push_back(multAij); + } + Assert(bound != NullConstraint); + Trace("arith::propagateRow") << bound << endl; + into.push_back(bound); + } + } + Trace("arith::propagateRow") << "LinearEqualityModule::propagateRow(" + << ridx << ", " << rowUp << ", " << v << ") done" << endl; + +} + +ConstraintP LinearEqualityModule::weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, const Rational& coeff, bool& anyWeakening, ArithVar basic) const { + + int sgn = coeff.sgn(); + bool ub = aboveUpper?(sgn < 0) : (sgn > 0); + + ConstraintP c = ub ? + d_variables.getUpperBoundConstraint(v) : + d_variables.getLowerBoundConstraint(v); + + bool weakened; + do{ + const DeltaRational& bound = c->getValue(); + + weakened = false; + + ConstraintP weaker = ub? + c->getStrictlyWeakerUpperBound(true, true): + c->getStrictlyWeakerLowerBound(true, true); + + if(weaker != NullConstraint){ + const DeltaRational& weakerBound = weaker->getValue(); + + DeltaRational diff = aboveUpper ? bound - weakerBound : weakerBound - bound; + //if var == basic, + // if aboveUpper, weakerBound > bound, multiply by -1 + // if !aboveUpper, weakerBound < bound, multiply by -1 + diff = diff * coeff; + if(surplus > diff){ + ++d_statistics.d_weakenings; + weakened = true; + anyWeakening = true; + surplus = surplus - diff; + + Trace("arith::weak") << "found:" << endl; + if(v == basic){ + Trace("arith::weak") << " basic: "; + } + Trace("arith::weak") << " " << surplus << " "<< diff << endl + << " " << bound << c << endl + << " " << weakerBound << weaker << endl; + + Assert(diff.sgn() > 0); + c = weaker; + } + } + }while(weakened); + + return c; +} + +/* An explanation of the farkas coefficients. + * + * We are proving a conflict on the basic variable x_b. + * If aboveUpper, then the conflict is with the constraint c : x_b <= u_b. + * If !aboveUpper, then the conflict is with the constraint c : x_b >= l_b. + * + * A row has the form: + * -x_b sum a_i * x_i = 0 + * or + * -x_b + sum r y + sum q z = 0, + * x_b = sum r y + sum q z + * where r > 0 and q < 0. + * + * + * If !aboveUp, we are proving ~c: x_b < l_b + * g = sum r u_y + sum q l_z + * x_b <= g < l_b + * and ~c is entailed by x_b <= g + * + * If aboveUp, we are proving ~c : x_b > u_b + * g = sum r l_y + sum q u_z + * x_b >= g > u_b + * and ~c is entailed by x_b >= g + * + * + * | s | c | ~c | u_i | l_i + * if !aboveUp | s > 0 | x >= -g/s | x < -g/s | a_i > 0 | a_i < 0 + * if !aboveUp | s < 0 | x <= -g/s | x > -g/s | a_i > 0 | a_i < 0 + * if aboveUp | s > 0 | x <= -g/s | x > -g/s | a_i < 0 | a_i > 0 + * if aboveUp | s < 0 | x >= -g/s | x < -g/s | a_i < 0 | a_i > 0 + * + * Thus we treat aboveUp as multiplying the row by -1 and !aboveUp as 1 + * for the entire row. + */ +ConstraintCP LinearEqualityModule::minimallyWeakConflict(bool aboveUpper, ArithVar basicVar, FarkasConflictBuilder& fcs) const { + Assert(!fcs.underConstruction()); + TimerStat::CodeTimer codeTimer(d_statistics.d_weakenTime); + + Trace("arith::weak") << "LinearEqualityModule::minimallyWeakConflict(" + << aboveUpper <<", "<< basicVar << ", ...) start" << endl; + + const Rational& adjustSgn = aboveUpper ? d_negOne : d_one; + const DeltaRational& assignment = d_variables.getAssignment(basicVar); + DeltaRational surplus; + if(aboveUpper){ + Assert(d_variables.hasUpperBound(basicVar)); + Assert(assignment > d_variables.getUpperBound(basicVar)); + surplus = assignment - d_variables.getUpperBound(basicVar); + }else{ + Assert(d_variables.hasLowerBound(basicVar)); + Assert(assignment < d_variables.getLowerBound(basicVar)); + surplus = d_variables.getLowerBound(basicVar) - assignment; + } + + bool anyWeakenings = false; + for(Tableau::RowIterator i = d_tableau.basicRowIterator(basicVar); !i.atEnd(); ++i){ + const Tableau::Entry& entry = *i; + ArithVar v = entry.getColVar(); + const Rational& coeff = entry.getCoefficient(); + bool weakening = false; + ConstraintP c = weakestExplanation(aboveUpper, surplus, v, coeff, weakening, basicVar); + Trace("arith::weak") << "weak : " << weakening << " " + << c->assertedToTheTheory() << " " + << d_variables.getAssignment(v) << " " + << c << endl; + anyWeakenings = anyWeakenings || weakening; + + fcs.addConstraint(c, coeff, adjustSgn); + if(basicVar == v){ + Assert(!c->negationHasProof()); + fcs.makeLastConsequent(); + } + } + Assert(fcs.consequentIsSet()); + + ConstraintCP conflicted = fcs.commitConflict(); + + ++d_statistics.d_weakeningAttempts; + if(anyWeakenings){ + ++d_statistics.d_weakeningSuccesses; + } + Trace("arith::weak") << "LinearEqualityModule::minimallyWeakConflict(" + << aboveUpper <<", "<< basicVar << ", ...) done" << endl; + return conflicted; +} + +ArithVar LinearEqualityModule::minVarOrder(ArithVar x, ArithVar y) const { + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + if(x <= y){ + return x; + } else { + return y; + } +} + +ArithVar LinearEqualityModule::minColLength(ArithVar x, ArithVar y) const { + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + Assert(!d_tableau.isBasic(x)); + Assert(!d_tableau.isBasic(y)); + uint32_t xLen = d_tableau.getColLength(x); + uint32_t yLen = d_tableau.getColLength(y); + if( xLen > yLen){ + return y; + } else if( xLen== yLen ){ + return minVarOrder(x,y); + }else{ + return x; + } +} + +ArithVar LinearEqualityModule::minRowLength(ArithVar x, ArithVar y) const { + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + Assert(d_tableau.isBasic(x)); + Assert(d_tableau.isBasic(y)); + uint32_t xLen = d_tableau.basicRowLength(x); + uint32_t yLen = d_tableau.basicRowLength(y); + if( xLen > yLen){ + return y; + } else if( xLen== yLen ){ + return minVarOrder(x,y); + }else{ + return x; + } +} + +ArithVar LinearEqualityModule::minBoundAndColLength(ArithVar x, ArithVar y) const{ + Assert(x != ARITHVAR_SENTINEL); + Assert(y != ARITHVAR_SENTINEL); + Assert(!d_tableau.isBasic(x)); + Assert(!d_tableau.isBasic(y)); + if(d_variables.hasEitherBound(x) && !d_variables.hasEitherBound(y)){ + return y; + }else if(!d_variables.hasEitherBound(x) && d_variables.hasEitherBound(y)){ + return x; + }else { + return minColLength(x, y); + } +} + +template +ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pref) const{ + ArithVar slack = ARITHVAR_SENTINEL; + + for(Tableau::RowIterator iter = d_tableau.basicRowIterator(x_i); !iter.atEnd(); ++iter){ + const Tableau::Entry& entry = *iter; + ArithVar nonbasic = entry.getColVar(); + if(nonbasic == x_i) continue; + + const Rational& a_ij = entry.getCoefficient(); + int sgn = a_ij.sgn(); + if(isAcceptableSlack(sgn, nonbasic)){ + //If one of the above conditions is met, we have found an acceptable + //nonbasic variable to pivot x_i with. We can now choose which one we + //prefer the most. + slack = (slack == ARITHVAR_SENTINEL) ? nonbasic : (this->*pref)(slack, nonbasic); + } + } + + return slack; +} + +const Tableau::Entry* LinearEqualityModule::selectSlackEntry(ArithVar x_i, bool above) const{ + for(Tableau::RowIterator iter = d_tableau.basicRowIterator(x_i); !iter.atEnd(); ++iter){ + const Tableau::Entry& entry = *iter; + ArithVar nonbasic = entry.getColVar(); + if(nonbasic == x_i) continue; + + const Rational& a_ij = entry.getCoefficient(); + int sgn = a_ij.sgn(); + if(above && isAcceptableSlack(sgn, nonbasic)){ + //If one of the above conditions is met, we have found an acceptable + //nonbasic variable to pivot x_i with. We can now choose which one we + //prefer the most. + return &entry; + }else if(!above && isAcceptableSlack(sgn, nonbasic)){ + return &entry; + } + } + + return NULL; +} + +void LinearEqualityModule::startTrackingBoundCounts(){ + Assert(!d_areTracking); + d_areTracking = true; + if(TraceIsOn("arith::tracking")){ + debugCheckTracking(); + } + Assert(d_areTracking); +} + +void LinearEqualityModule::stopTrackingBoundCounts(){ + Assert(d_areTracking); + d_areTracking = false; + if(TraceIsOn("arith::tracking")){ + debugCheckTracking(); + } + Assert(!d_areTracking); +} + + +void LinearEqualityModule::trackRowIndex(RowIndex ridx){ + Assert(!rowIndexIsTracked(ridx)); + BoundsInfo bi = computeRowBoundInfo(ridx, true); + d_btracking.set(ridx, bi); +} + +BoundsInfo LinearEqualityModule::computeRowBoundInfo(RowIndex ridx, bool inQueue) const{ + BoundsInfo bi; + + Tableau::RowIterator iter = d_tableau.ridRowIterator(ridx); + for(; !iter.atEnd(); ++iter){ + const Tableau::Entry& entry = *iter; + ArithVar v = entry.getColVar(); + const Rational& a_ij = entry.getCoefficient(); + bi += (d_variables.selectBoundsInfo(v, inQueue)).multiplyBySgn(a_ij.sgn()); + } + return bi; +} + +BoundCounts LinearEqualityModule::debugBasicAtBoundCount(ArithVar x_i) const { + return d_btracking[d_tableau.basicToRowIndex(x_i)].atBounds(); +} + +/** + * If the pivot described in u were performed, + * then the row would qualify as being either at the minimum/maximum + * to the non-basics being at their bounds. + * The minimum/maximum is determined by the direction the non-basic is changing. + */ +bool LinearEqualityModule::basicsAtBounds(const UpdateInfo& u) const { + Assert(u.describesPivot()); + + ArithVar nonbasic = u.nonbasic(); + ArithVar basic = u.leaving(); + Assert(basicIsTracked(basic)); + int coeffSgn = u.getCoefficient().sgn(); + int nbdir = u.nonbasicDirection(); + + ConstraintP c = u.limiting(); + int toUB = (c->getType() == UpperBound || + c->getType() == Equality) ? 1 : 0; + int toLB = (c->getType() == LowerBound || + c->getType() == Equality) ? 1 : 0; + + RowIndex ridx = d_tableau.basicToRowIndex(basic); + + BoundCounts bcs = d_btracking[ridx].atBounds(); + // x = c*n + \sum d*m + // 0 = -x + c*n + \sum d*m + // n = 1/c * x + -1/c * (\sum d*m) + BoundCounts nonb = bcs - d_variables.atBoundCounts(nonbasic).multiplyBySgn(coeffSgn); + nonb.addInChange(-1, d_variables.atBoundCounts(basic), BoundCounts(toLB, toUB)); + nonb = nonb.multiplyBySgn(-coeffSgn); + + uint32_t length = d_tableau.basicRowLength(basic); + Trace("basicsAtBounds") + << "bcs " << bcs + << "nonb " << nonb + << "length " << length << endl; + // nonb has nb excluded. + if(nbdir < 0){ + return nonb.lowerBoundCount() + 1 == length; + }else{ + Assert(nbdir > 0); + return nonb.upperBoundCount() + 1 == length; + } +} + +bool LinearEqualityModule::nonbasicsAtLowerBounds(ArithVar basic) const { + Assert(basicIsTracked(basic)); + RowIndex ridx = d_tableau.basicToRowIndex(basic); + + BoundCounts bcs = d_btracking[ridx].atBounds(); + uint32_t length = d_tableau.basicRowLength(basic); + + // return true if excluding the basic is every element is at its "lowerbound" + // The psuedo code is: + // bcs -= basic.count(basic, basic's sgn) + // return bcs.lowerBoundCount() + 1 == length + // As basic's sign is always -1, we can pull out the pieces of the count: + // bcs.lowerBoundCount() - basic.atUpperBoundInd() + 1 == length + // basic.atUpperBoundInd() is either 0 or 1 + uint32_t lbc = bcs.lowerBoundCount(); + return (lbc == length) || + (lbc + 1 == length && d_variables.cmpAssignmentUpperBound(basic) != 0); +} + +bool LinearEqualityModule::nonbasicsAtUpperBounds(ArithVar basic) const { + Assert(basicIsTracked(basic)); + RowIndex ridx = d_tableau.basicToRowIndex(basic); + BoundCounts bcs = d_btracking[ridx].atBounds(); + uint32_t length = d_tableau.basicRowLength(basic); + uint32_t ubc = bcs.upperBoundCount(); + // See the comment for nonbasicsAtLowerBounds() + + return (ubc == length) || + (ubc + 1 == length && d_variables.cmpAssignmentLowerBound(basic) != 0); +} + +void LinearEqualityModule::trackingMultiplyRow(RowIndex ridx, int sgn) { + Assert(rowIndexIsTracked(ridx)); + Assert(sgn != 0); + if(sgn < 0){ + BoundsInfo& bi = d_btracking.get(ridx); + bi = bi.multiplyBySgn(sgn); + } +} + +void LinearEqualityModule::trackingCoefficientChange(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn){ + Assert(oldSgn != currSgn); + BoundsInfo nb_inf = d_variables.boundsInfo(nb); + + Assert(rowIndexIsTracked(ridx)); + + BoundsInfo& row_bi = d_btracking.get(ridx); + row_bi.addInSgn(nb_inf, oldSgn, currSgn); +} + +ArithVar LinearEqualityModule::minBy(const ArithVarVec& vec, VarPreferenceFunction pf) const{ + if(vec.empty()) { + return ARITHVAR_SENTINEL; + }else { + ArithVar sel = vec.front(); + ArithVarVec::const_iterator i = vec.begin() + 1; + ArithVarVec::const_iterator i_end = vec.end(); + for(; i != i_end; ++i){ + sel = (this->*pf)(sel, *i); + } + return sel; + } +} + +bool LinearEqualityModule::accumulateBorder(const Tableau::Entry& entry, bool ub){ + ArithVar currBasic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + + Assert(basicIsTracked(currBasic)); + + ConstraintP bound = ub ? + d_variables.getUpperBoundConstraint(currBasic): + d_variables.getLowerBoundConstraint(currBasic); + + if(bound == NullConstraint){ return false; } + Assert(bound != NullConstraint); + + const Rational& coeff = entry.getCoefficient(); + + const DeltaRational& assignment = d_variables.getAssignment(currBasic); + DeltaRational toBound = bound->getValue() - assignment; + DeltaRational nbDiff = toBound/coeff; + + // if ub + // if toUB >= 0 + // then ub >= currBasic + // if sgn > 0, + // then diff >= 0, so nb must increase for G + // else diff <= 0, so nb must decrease for G + // else ub < currBasic + // if sgn > 0, + // then diff < 0, so nb must decrease for G + // else diff > 0, so nb must increase for G + + int diffSgn = nbDiff.sgn(); + + if(diffSgn != 0 && willBeInConflictAfterPivot(entry, nbDiff, ub)){ + return true; + }else{ + bool areFixing = ub ? (toBound.sgn() < 0 ) : (toBound.sgn() > 0); + Border border(bound, nbDiff, areFixing, &entry, ub); + bool increasing = + (diffSgn > 0) || + (diffSgn == 0 && ((coeff.sgn() > 0) == ub)); + + // assume diffSgn == 0 + // if coeff > 0, + // if ub, inc + // else, dec + // else coeff < 0 + // if ub, dec + // else, inc + + if(increasing){ + Trace("handleBorders") << "push back increasing " << border << endl; + d_increasing.push_back(border); + }else{ + Trace("handleBorders") << "push back decreasing " << border << endl; + d_decreasing.push_back(border); + } + return false; + } +} + +bool LinearEqualityModule::willBeInConflictAfterPivot(const Tableau::Entry& entry, const DeltaRational& nbDiff, bool bToUB) const{ + int nbSgn = nbDiff.sgn(); + Assert(nbSgn != 0); + + if(nbSgn > 0){ + if (!d_upperBoundDifference || nbDiff <= *d_upperBoundDifference) + { + return false; + } + }else{ + if (!d_lowerBoundDifference || nbDiff >= *d_lowerBoundDifference) + { + return false; + } + } + + // Assume past this point, nb will be in error if this pivot is done + ArithVar nb = entry.getColVar(); + RowIndex ridx = entry.getRowIndex(); + ArithVar basic = d_tableau.rowIndexToBasic(ridx); + Assert(rowIndexIsTracked(ridx)); + int coeffSgn = entry.getCoefficient().sgn(); + + + // if bToUB, then basic is going to be set to its upperbound + // if not bToUB, then basic is going to be set to its lowerbound + + // Different steps of solving for this: + // 1) y = a * x + \sum b * z + // 2) -a * x = -y + \sum b * z + // 3) x = (-1/a) * ( -y + \sum b * z) + + BoundCounts bc = d_btracking[ridx].atBounds(); + + // 1) y = a * x + \sum b * z + // Get bc(\sum b * z) + BoundCounts sumOnly = bc - d_variables.atBoundCounts(nb).multiplyBySgn(coeffSgn); + + // y's bounds in the proposed model + int yWillBeAtUb = (bToUB || d_variables.boundsAreEqual(basic)) ? 1 : 0; + int yWillBeAtLb = (!bToUB || d_variables.boundsAreEqual(basic)) ? 1 : 0; + BoundCounts ysBounds(yWillBeAtLb, yWillBeAtUb); + + // 2) -a * x = -y + \sum b * z + // Get bc(-y + \sum b * z) + sumOnly.addInChange(-1, d_variables.atBoundCounts(basic), ysBounds); + + // 3) x = (-1/a) * ( -y + \sum b * z) + // Get bc((-1/a) * ( -y + \sum b * z)) + BoundCounts xsBoundsAfterPivot = sumOnly.multiplyBySgn(-coeffSgn); + + uint32_t length = d_tableau.basicRowLength(basic); + if(nbSgn > 0){ + // Only check for the upper bound being violated + return xsBoundsAfterPivot.lowerBoundCount() + 1 == length; + }else{ + // Only check for the lower bound being violated + return xsBoundsAfterPivot.upperBoundCount() + 1 == length; + } +} + +UpdateInfo LinearEqualityModule::mkConflictUpdate(const Tableau::Entry& entry, bool ub) const{ + ArithVar currBasic = d_tableau.rowIndexToBasic(entry.getRowIndex()); + ArithVar nb = entry.getColVar(); + + ConstraintP bound = ub ? + d_variables.getUpperBoundConstraint(currBasic): + d_variables.getLowerBoundConstraint(currBasic); + + + const Rational& coeff = entry.getCoefficient(); + const DeltaRational& assignment = d_variables.getAssignment(currBasic); + DeltaRational toBound = bound->getValue() - assignment; + DeltaRational nbDiff = toBound/coeff; + + return UpdateInfo::conflict(nb, nbDiff, coeff, bound); +} + +UpdateInfo LinearEqualityModule::speculativeUpdate(ArithVar nb, const Rational& focusCoeff, UpdatePreferenceFunction pref){ + Assert(d_increasing.empty()); + Assert(d_decreasing.empty()); + Assert(!d_lowerBoundDifference); + Assert(!d_upperBoundDifference); + + int focusCoeffSgn = focusCoeff.sgn(); + + Trace("speculativeUpdate") << "speculativeUpdate" << endl; + Trace("speculativeUpdate") << "nb " << nb << endl; + Trace("speculativeUpdate") << "focusCoeff " << focusCoeff << endl; + + if(d_variables.hasUpperBound(nb)){ + ConstraintP ub = d_variables.getUpperBoundConstraint(nb); + d_upperBoundDifference = ub->getValue() - d_variables.getAssignment(nb); + Border border(ub, *d_upperBoundDifference, false, NULL, true); + Trace("handleBorders") << "push back increasing " << border << endl; + d_increasing.push_back(border); + } + if(d_variables.hasLowerBound(nb)){ + ConstraintP lb = d_variables.getLowerBoundConstraint(nb); + d_lowerBoundDifference = lb->getValue() - d_variables.getAssignment(nb); + Border border(lb, *d_lowerBoundDifference, false, NULL, false); + Trace("handleBorders") << "push back decreasing " << border << endl; + d_decreasing.push_back(border); + } + + Tableau::ColIterator colIter = d_tableau.colIterator(nb); + for(; !colIter.atEnd(); ++colIter){ + const Tableau::Entry& entry = *colIter; + Assert(entry.getColVar() == nb); + + if(accumulateBorder(entry, true)){ + clearSpeculative(); + return mkConflictUpdate(entry, true); + } + if(accumulateBorder(entry, false)){ + clearSpeculative(); + return mkConflictUpdate(entry, false); + } + } + + UpdateInfo selected; + BorderHeap& withSgn = focusCoeffSgn > 0 ? d_increasing : d_decreasing; + BorderHeap& againstSgn = focusCoeffSgn > 0 ? d_decreasing : d_increasing; + + handleBorders(selected, nb, focusCoeff, withSgn, 0, pref); + int m = 1 - selected.errorsChangeSafe(0); + handleBorders(selected, nb, focusCoeff, againstSgn, m, pref); + + clearSpeculative(); + return selected; +} + +void LinearEqualityModule::clearSpeculative(){ + // clear everything away + d_increasing.clear(); + d_decreasing.clear(); + d_lowerBoundDifference.reset(); + d_upperBoundDifference.reset(); +} + +void LinearEqualityModule::handleBorders(UpdateInfo& selected, ArithVar nb, const Rational& focusCoeff, BorderHeap& heap, int minimumFixes, UpdatePreferenceFunction pref){ + Assert(minimumFixes >= 0); + + // The values popped off of the heap + // should be popped with the values closest to 0 + // being first and larger in absolute value last + + + int fixesRemaining = heap.possibleFixes(); + + Trace("handleBorders") + << "handleBorders " + << "nb " << nb + << "fc " << focusCoeff + << "h.e " << heap.empty() + << "h.dir " << heap.direction() + << "h.rem " << fixesRemaining + << "h.0s " << heap.numZeroes() + << "min " << minimumFixes + << endl; + + if(heap.empty()){ + // if the heap is empty, return + return; + } + + bool zeroesWillDominate = fixesRemaining - heap.numZeroes() < minimumFixes; + + // can the number of fixes ever exceed the minimum? + // no more than the number of possible fixes can be fixed in total + // nothing can be fixed before the zeroes are taken care of + if(minimumFixes > 0 && zeroesWillDominate){ + return; + } + + + int negErrorChange = 0; + int nbDir = heap.direction(); + + // points at the beginning of the heap + if(zeroesWillDominate){ + heap.dropNonZeroes(); + } + heap.make_heap(); + + + // pretend like the previous block had a value of zero. + // The block that actually has a value of 0 must handle this. + const DeltaRational zero(0); + const DeltaRational* prevBlockValue = &zero; + + /** The coefficient changes as the value crosses border. */ + Rational effectiveCoefficient = focusCoeff; + + /* Keeps track of the change to the value of the focus function.*/ + DeltaRational totalFocusChange(0); + + const int focusCoeffSgn = focusCoeff.sgn(); + + while(heap.more() && + (fixesRemaining + negErrorChange > minimumFixes || + (fixesRemaining + negErrorChange == minimumFixes && + effectiveCoefficient.sgn() == focusCoeffSgn))){ + // There are more elements && + // we can either fix at least 1 more variable in the error function + // or we can improve the error function + + + int brokenInBlock = 0; + BorderVec::const_iterator endBlock = heap.end(); + + pop_block(heap, brokenInBlock, fixesRemaining, negErrorChange); + + // if endVec == beginVec, block starts there + // other wise, block starts at endVec + BorderVec::const_iterator startBlock + = heap.more() ? heap.end() : heap.begin(); + + const DeltaRational& blockValue = (*startBlock).d_diff; + + // if decreasing + // blockValue < prevBlockValue + // diff.sgn() = -1 + DeltaRational diff = blockValue - (*prevBlockValue); + DeltaRational blockChangeToFocus = diff * effectiveCoefficient; + totalFocusChange += blockChangeToFocus; + + Trace("handleBorders") + << "blockValue " << (blockValue) + << "diff " << diff + << "blockChangeToFocus " << totalFocusChange + << "blockChangeToFocus " << totalFocusChange + << "negErrorChange " << negErrorChange + << "brokenInBlock " << brokenInBlock + << "fixesRemaining " << fixesRemaining + << endl; + + int currFocusChangeSgn = totalFocusChange.sgn(); + for(BorderVec::const_iterator i = startBlock; i != endBlock; ++i){ + const Border& b = *i; + + Trace("handleBorders") << b << endl; + + bool makesImprovement = negErrorChange > 0 || + (negErrorChange == 0 && currFocusChangeSgn > 0); + + if(!makesImprovement){ + if(b.ownBorder() || minimumFixes > 0){ + continue; + } + } + + UpdateInfo proposal(nb, nbDir); + if(b.ownBorder()){ + proposal.witnessedUpdate(b.d_diff, b.d_bound, -negErrorChange, currFocusChangeSgn); + }else{ + proposal.update(b.d_diff, b.getCoefficient(), b.d_bound, -negErrorChange, currFocusChangeSgn); + } + + if(selected.unbounded() || (this->*pref)(selected, proposal)){ + selected = proposal; + } + } + + effectiveCoefficient += updateCoefficient(startBlock, endBlock); + prevBlockValue = &blockValue; + negErrorChange -= brokenInBlock; + } +} + +Rational LinearEqualityModule::updateCoefficient(BorderVec::const_iterator startBlock, BorderVec::const_iterator endBlock){ + //update coefficient + Rational changeToCoefficient(0); + for(BorderVec::const_iterator i = startBlock; i != endBlock; ++i){ + const Border& curr = *i; + if(curr.ownBorder()){// breaking its own bound + if(curr.d_upperbound){ + changeToCoefficient -= 1; + }else{ + changeToCoefficient += 1; + } + }else{ + const Rational& coeff = curr.d_entry->getCoefficient(); + if(curr.d_areFixing){ + if(curr.d_upperbound){// fixing an upper bound + changeToCoefficient += coeff; + }else{// fixing a lower bound + changeToCoefficient -= coeff; + } + }else{ + if(curr.d_upperbound){// breaking an upper bound + changeToCoefficient -= coeff; + }else{ + // breaking a lower bound + changeToCoefficient += coeff; + } + } + } + } + return changeToCoefficient; +} + +void LinearEqualityModule::pop_block(BorderHeap& heap, int& brokenInBlock, int& fixesRemaining, int& negErrorChange){ + Assert(heap.more()); + + if(heap.top().d_areFixing){ + fixesRemaining--; + negErrorChange++; + }else{ + brokenInBlock++; + } + heap.pop_heap(); + const DeltaRational& blockValue = (*heap.end()).d_diff; + + while(heap.more()){ + const Border& top = heap.top(); + if(blockValue == top.d_diff){ + // belongs to the block + if(top.d_areFixing){ + fixesRemaining--; + negErrorChange++; + }else{ + brokenInBlock++; + } + heap.pop_heap(); + }else{ + // does not belong to the block + Assert((heap.direction() > 0) ? (blockValue < top.d_diff) + : (blockValue > top.d_diff)); + break; + } + } +} + +void LinearEqualityModule::substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult){ + d_tableau.substitutePlusTimesConstant(to, from, mult, d_trackCallback); +} +void LinearEqualityModule::directlyAddToCoefficient(ArithVar row, ArithVar col, const Rational& mult){ + d_tableau.directlyAddToCoefficient(row, col, mult, d_trackCallback); +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/linear_equality.h b/src/theory/arith/linear/linear_equality.h new file mode 100644 index 000000000..30b137dd8 --- /dev/null +++ b/src/theory/arith/linear/linear_equality.h @@ -0,0 +1,762 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This module maintains the relationship between a Tableau and + * PartialModel. + * + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This maintains the relationship needed by the SimplexDecisionProcedure. + * + * In the language of Simplex for DPLL(T), this provides: + * - update() + * - pivotAndUpdate() + * + * This class also provides utility functions that require + * using both the Tableau and PartialModel. + */ + +#include "cvc5_private.h" + +#pragma once + +#include "options/arith_options.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/constraint_forward.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/linear/partial_model.h" +#include "theory/arith/linear/simplex_update.h" +#include "theory/arith/linear/tableau.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +struct Border{ + // The constraint for the border + ConstraintP d_bound; + + // The change to the nonbasic to reach the border + DeltaRational d_diff; + + // Is reach this value fixing the constraint + // or is going past this value hurting the constraint + bool d_areFixing; + + // Entry into the tableau + const Tableau::Entry* d_entry; + + // Was this an upper bound or a lower bound? + bool d_upperbound; + + Border(): + d_bound(NullConstraint) // ignore the other values + {} + + Border(ConstraintP l, const DeltaRational& diff, bool areFixing, const Tableau::Entry* en, bool ub): + d_bound(l), d_diff(diff), d_areFixing(areFixing), d_entry(en), d_upperbound(ub) + {} + + Border(ConstraintP l, const DeltaRational& diff, bool areFixing, bool ub): + d_bound(l), d_diff(diff), d_areFixing(areFixing), d_entry(NULL), d_upperbound(ub) + {} + bool operator<(const Border& other) const{ + return d_diff < other.d_diff; + } + + /** d_lim is the nonbasic variable's own bound. */ + bool ownBorder() const { return d_entry == NULL; } + + bool isZero() const { return d_diff.sgn() == 0; } + static bool nonZero(const Border& b) { return !b.isZero(); } + + const Rational& getCoefficient() const { + Assert(!ownBorder()); + return d_entry->getCoefficient(); + } + void output(std::ostream& out) const; +}; + +inline std::ostream& operator<<(std::ostream& out, const Border& b){ + b.output(out); + return out; +} + +typedef std::vector BorderVec; + +class BorderHeap { + const int d_dir; + + class BorderHeapCmp { + private: + int d_nbDirection; + public: + BorderHeapCmp(int dir): d_nbDirection(dir){} + bool operator()(const Border& a, const Border& b) const{ + if(d_nbDirection > 0){ + // if nb is increasing, + // this needs to act like a max + // in order to have a min heap + return b < a; + }else{ + // if nb is decreasing, + // this needs to act like a min + // in order to have a max heap + return a < b; + } + } + }; + const BorderHeapCmp d_cmp; + + BorderVec d_vec; + + BorderVec::iterator d_begin; + + /** + * Once this is initialized the top of the heap will always + * be at d_end - 1 + */ + BorderVec::iterator d_end; + + int d_possibleFixes; + int d_numZeroes; + +public: + BorderHeap(int dir) + : d_dir(dir), d_cmp(dir), d_possibleFixes(0), d_numZeroes(0) + {} + + void push_back(const Border& b){ + d_vec.push_back(b); + if(b.d_areFixing){ + d_possibleFixes++; + } + if(b.d_diff.sgn() == 0){ + d_numZeroes++; + } + } + + int numZeroes() const { return d_numZeroes; } + int possibleFixes() const { return d_possibleFixes; } + int direction() const { return d_dir; } + + void make_heap(){ + d_begin = d_vec.begin(); + d_end = d_vec.end(); + std::make_heap(d_begin, d_end, d_cmp); + } + + void dropNonZeroes(){ + d_vec.erase(std::remove_if(d_vec.begin(), d_vec.end(), &Border::nonZero), + d_vec.end()); + } + + const Border& top() const { + Assert(more()); + return *d_begin; + } + void pop_heap(){ + Assert(more()); + + std::pop_heap(d_begin, d_end, d_cmp); + --d_end; + } + + BorderVec::const_iterator end() const{ + return BorderVec::const_iterator(d_end); + } + BorderVec::const_iterator begin() const{ + return BorderVec::const_iterator(d_begin); + } + + inline bool more() const{ return d_begin != d_end; } + + inline bool empty() const{ return d_vec.empty(); } + + void clear(){ + d_possibleFixes = 0; + d_numZeroes = 0; + d_vec.clear(); + } +}; + + +class LinearEqualityModule { +public: + typedef ArithVar (LinearEqualityModule::*VarPreferenceFunction)(ArithVar, ArithVar) const; + + + typedef bool (LinearEqualityModule::*UpdatePreferenceFunction)(const UpdateInfo&, const UpdateInfo&) const; + + +private: + /** + * Manages information about the assignment and upper and lower bounds on the + * variables. + */ + ArithVariables& d_variables; + + /** Reference to the Tableau to operate upon. */ + Tableau& d_tableau; + + /** Called whenever the value of a basic variable is updated. */ + BasicVarModelUpdateCallBack d_basicVariableUpdates; + + BorderHeap d_increasing; + BorderHeap d_decreasing; + std::optional d_upperBoundDifference; + std::optional d_lowerBoundDifference; + + Rational d_one; + Rational d_negOne; +public: + + /** + * Initializes a LinearEqualityModule with a partial model, a tableau, + * and a callback function for when basic variables update their values. + */ + LinearEqualityModule(ArithVariables& vars, Tableau& t, BoundInfoMap& boundTracking, BasicVarModelUpdateCallBack f); + + /** + * Updates the assignment of a nonbasic variable x_i to v. + * Also updates the assignment of basic variables accordingly. + */ + void update(ArithVar x_i, const DeltaRational& v){ + if(d_areTracking){ + updateTracked(x_i,v); + }else{ + updateUntracked(x_i,v); + } + } + + /** Specialization of update if the module is not tracking yet (for Assert*). */ + void updateUntracked(ArithVar x_i, const DeltaRational& v); + + /** Specialization of update if the module is not tracking yet (for Simplex). */ + void updateTracked(ArithVar x_i, const DeltaRational& v); + + + /** + * Updates the value of a basic variable x_i to v, + * and then pivots x_i with the nonbasic variable in its row x_j. + * Updates the assignment of the other basic variables accordingly. + */ + void pivotAndUpdate(ArithVar x_i, ArithVar x_j, const DeltaRational& v); + + ArithVariables& getVariables() const{ return d_variables; } + Tableau& getTableau() const{ return d_tableau; } + + /** + * Updates every non-basic to reflect the assignment in many. + * For use with ApproximateSimplex. + */ + void updateMany(const DenseMap& many); + void forceNewBasis(const DenseSet& newBasis); + void applySolution(const DenseSet& newBasis, const DenseMap& newValues); + + + /** + * Returns a pointer to the first Tableau entry on the row ridx that does not + * have an either a lower bound/upper bound for proving a bound on skip. + * The variable skip is always excluded. Returns NULL if there is no such element. + * + * If skip == ARITHVAR_SENTINEL, this is equivalent to considering the whole row. + */ + const Tableau::Entry* rowLacksBound(RowIndex ridx, bool upperBound, ArithVar skip); + + + void startTrackingBoundCounts(); + void stopTrackingBoundCounts(); + + + void includeBoundUpdate(ArithVar nb, const BoundsInfo& prev); + + + uint32_t updateProduct(const UpdateInfo& inf) const; + + inline bool minNonBasicVarOrder(const UpdateInfo& a, const UpdateInfo& b) const{ + return a.nonbasic() >= b.nonbasic(); + } + + /** + * Prefer the update that touch the fewest entries in the matrix. + * + * The intuition is that this operation will be cheaper. + * This strongly biases the system towards updates instead of pivots. + */ + inline bool minProduct(const UpdateInfo& a, const UpdateInfo& b) const{ + uint32_t aprod = updateProduct(a); + uint32_t bprod = updateProduct(b); + + if(aprod == bprod){ + return minNonBasicVarOrder(a,b); + }else{ + return aprod > bprod; + } + } + inline bool constrainedMin(const UpdateInfo& a, const UpdateInfo& b) const{ + if(a.describesPivot() && b.describesPivot()){ + bool aAtBounds = basicsAtBounds(a); + bool bAtBounds = basicsAtBounds(b); + if(aAtBounds != bAtBounds){ + return bAtBounds; + } + } + return minProduct(a,b); + } + + /** + * If both a and b are pivots, prefer the pivot with the leaving variables that has equal bounds. + * The intuition is that such variables will be less likely to lead to future problems. + */ + inline bool preferFrozen(const UpdateInfo& a, const UpdateInfo& b) const { + if(a.describesPivot() && b.describesPivot()){ + bool aFrozen = d_variables.boundsAreEqual(a.leaving()); + bool bFrozen = d_variables.boundsAreEqual(b.leaving()); + + if(aFrozen != bFrozen){ + return bFrozen; + } + } + return constrainedMin(a,b); + } + + /** + * Prefer pivots with entering variables that do not have bounds. + * The intuition is that such variables will be less likely to lead to future problems. + */ + bool preferNeitherBound(const UpdateInfo& a, const UpdateInfo& b) const { + if(d_variables.hasEitherBound(a.nonbasic()) == d_variables.hasEitherBound(b.nonbasic())){ + return preferFrozen(a,b); + }else{ + return d_variables.hasEitherBound(a.nonbasic()); + } + } + + bool modifiedBlands(const UpdateInfo& a, const UpdateInfo& b) const { + Assert(a.focusDirection() == 0 && b.focusDirection() == 0); + Assert(a.describesPivot()); + Assert(b.describesPivot()); + if(a.nonbasic() == b.nonbasic()){ + bool aIsZero = a.nonbasicDelta().sgn() == 0; + bool bIsZero = b.nonbasicDelta().sgn() == 0; + + if((aIsZero || bIsZero) && (!aIsZero || !bIsZero)){ + return bIsZero; + }else{ + return a.leaving() >= b.leaving(); + } + }else{ + return a.nonbasic() > b.nonbasic(); + } + } + + template + bool preferWitness(const UpdateInfo& a, const UpdateInfo& b) const{ + WitnessImprovement aImp = a.getWitness(!heuristic); + WitnessImprovement bImp = b.getWitness(!heuristic); + + if(aImp == bImp){ + switch(aImp){ + case ConflictFound: + return preferNeitherBound(a,b); + case ErrorDropped: + if(a.errorsChange() == b.errorsChange()){ + return preferNeitherBound(a,b); + }else{ + return a.errorsChange() > b.errorsChange(); + } + case FocusImproved: + return preferNeitherBound(a,b); + case BlandsDegenerate: + Assert(a.describesPivot()); + Assert(b.describesPivot()); + Assert(a.focusDirection() == 0 && b.focusDirection() == 0); + return modifiedBlands(a,b); + case HeuristicDegenerate: + Assert(a.describesPivot()); + Assert(b.describesPivot()); + Assert(a.focusDirection() == 0 && b.focusDirection() == 0); + return preferNeitherBound(a,b); + case AntiProductive: + return minNonBasicVarOrder(a, b); + // Not valid responses + case Degenerate: + case FocusShrank: + Unreachable(); + } + Unreachable(); + }else{ + return aImp > bImp; + } + } + +private: + + /** + * This maps each row index to its relevant bounds info. + * This tracks the count for how many variables on a row have bounds + * and how many are assigned at their bounds. + */ + BoundInfoMap& d_btracking; + bool d_areTracking; + +public: + /** + * The constraint on a basic variable b is implied by the constraints + * on its row. This is a wrapper for propagateRow(). + */ + void propagateBasicFromRow(ConstraintP c, bool produceProofs); + + /** + * Let v be the variable for the constraint c. + * Exports either the explanation of an upperbound or a lower bound + * of v using the other variables in the row. + * + * If farkas != RationalVectorPSentinel, this function additionally + * stores the farkas coefficients of the constraints stored in into. + * Position 0 is the coefficient of v. + * Position i > 0, corresponds to the order of the other constraints. + */ + void propagateRow(ConstraintCPVec& into, + RowIndex ridx, + bool rowUp, + ConstraintP c, + RationalVectorP farkas); + + /** + * Computes the value of a basic variable using the assignments + * of the values of the variables in the basic variable's row tableau. + * This can compute the value using either: + * - the the current assignment (useSafe=false) or + * - the safe assignment (useSafe = true). + */ + DeltaRational computeRowValue(ArithVar x, bool useSafe) const; + + /** + * A PreferenceFunction takes a const ref to the SimplexDecisionProcedure, + * and 2 ArithVar variables and returns one of the ArithVar variables + * potentially using the internals of the SimplexDecisionProcedure. + */ + + ArithVar noPreference(ArithVar x, ArithVar y) const { return x; } + + /** + * minVarOrder is a PreferenceFunction for selecting the smaller of the 2 + * ArithVars. This PreferenceFunction is used during the VarOrder stage of + * findModel. + */ + ArithVar minVarOrder(ArithVar x, ArithVar y) const; + + /** + * minColLength is a PreferenceFunction for selecting the variable with the + * smaller row count in the tableau. + * + * This is a heuristic rule and should not be used during the VarOrder + * stage of findModel. + */ + ArithVar minColLength(ArithVar x, ArithVar y) const; + + /** + * minRowLength is a PreferenceFunction for selecting the variable with the + * smaller row count in the tableau. + * + * This is a heuristic rule and should not be used during the VarOrder + * stage of findModel. + */ + ArithVar minRowLength(ArithVar x, ArithVar y) const; + + /** + * minBoundAndRowCount is a PreferenceFunction for preferring a variable + * without an asserted bound over variables with an asserted bound. + * If both have bounds or both do not have bounds, + * the rule falls back to minRowCount(...). + * + * This is a heuristic rule and should not be used during the VarOrder + * stage of findModel. + */ + ArithVar minBoundAndColLength(ArithVar x, ArithVar y) const; + + template + inline bool isAcceptableSlack(int sgn, ArithVar nonbasic) const + { + return (above && sgn < 0 && d_variables.strictlyBelowUpperBound(nonbasic)) + || (above && sgn > 0 && d_variables.strictlyAboveLowerBound(nonbasic)) + || (!above && sgn > 0 + && d_variables.strictlyBelowUpperBound(nonbasic)) + || (!above && sgn < 0 + && d_variables.strictlyAboveLowerBound(nonbasic)); + } + + /** + * Given the basic variable x_i, + * this function finds the smallest nonbasic variable x_j in the row of x_i + * in the tableau that can "take up the slack" to let x_i satisfy its bounds. + * This returns ARITHVAR_SENTINEL if none exists. + * + * More formally one of the following conditions must be satisfied: + * - lowerBound && a_ij < 0 && assignment(x_j) < upperbound(x_j) + * - lowerBound && a_ij > 0 && assignment(x_j) > lowerbound(x_j) + * - !lowerBound && a_ij > 0 && assignment(x_j) < upperbound(x_j) + * - !lowerBound && a_ij < 0 && assignment(x_j) > lowerbound(x_j) + * + */ + template ArithVar selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; + ArithVar selectSlackLowerBound(ArithVar x_i, VarPreferenceFunction pf) const { + return selectSlack(x_i, pf); + } + ArithVar selectSlackUpperBound(ArithVar x_i, VarPreferenceFunction pf) const { + return selectSlack(x_i, pf); + } + + const Tableau::Entry* selectSlackEntry(ArithVar x_i, bool above) const; + + inline bool rowIndexIsTracked(RowIndex ridx) const { + return d_btracking.isKey(ridx); + } + inline bool basicIsTracked(ArithVar v) const { + return rowIndexIsTracked(d_tableau.basicToRowIndex(v)); + } + void trackRowIndex(RowIndex ridx); + void stopTrackingRowIndex(RowIndex ridx){ + Assert(rowIndexIsTracked(ridx)); + d_btracking.remove(ridx); + } + + /** + * If the pivot described in u were performed, + * then the row would qualify as being either at the minimum/maximum + * to the non-basics being at their bounds. + * The minimum/maximum is determined by the direction the non-basic is changing. + */ + bool basicsAtBounds(const UpdateInfo& u) const; + +private: + + /** + * Recomputes the bound info for a row using either the information + * in the bounds queue or the current information. + * O(row length of ridx) + */ + BoundsInfo computeRowBoundInfo(RowIndex ridx, bool inQueue) const; + +public: + /** Debug only routine. */ + BoundCounts debugBasicAtBoundCount(ArithVar x_i) const; + + /** Track the effect of the change of coefficient for bound counting. */ + void trackingCoefficientChange(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn); + + /** Track the effect of multiplying a row by a sign for bound counting. */ + void trackingMultiplyRow(RowIndex ridx, int sgn); + + /** Count for how many on a row have *an* upper/lower bounds. */ + BoundCounts hasBoundCount(RowIndex ri) const { + Assert(d_variables.boundsQueueEmpty()); + return d_btracking[ri].hasBounds(); + } + + /** + * Are there any non-basics on x_i's row that are not at + * their respective lower bounds (mod sgns). + * O(1) time due to the atBound() count. + */ + bool nonbasicsAtLowerBounds(ArithVar x_i) const; + + /** + * Are there any non-basics on x_i's row that are not at + * their respective upper bounds (mod sgns). + * O(1) time due to the atBound() count. + */ + bool nonbasicsAtUpperBounds(ArithVar x_i) const; + +private: + class TrackingCallback : public CoefficientChangeCallback { + private: + LinearEqualityModule* d_linEq; + public: + TrackingCallback(LinearEqualityModule* le) : d_linEq(le) {} + void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) override + { + d_linEq->trackingCoefficientChange(ridx, nb, oldSgn, currSgn); + } + void multiplyRow(RowIndex ridx, int sgn) override + { + d_linEq->trackingMultiplyRow(ridx, sgn); + } + bool canUseRow(RowIndex ridx) const override + { + ArithVar basic = d_linEq->getTableau().rowIndexToBasic(ridx); + return d_linEq->basicIsTracked(basic); + } + } d_trackCallback; + + /** + * Selects the constraint for the variable v on the row for basic + * with the weakest possible constraint that is consistent with the surplus + * surplus. + */ + ConstraintP weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, + const Rational& coeff, bool& anyWeakening, ArithVar basic) const; + +public: + /** + * Constructs a minimally weak conflict for the basic variable basicVar. + * + * Returns a constraint that is now in conflict. + */ + ConstraintCP minimallyWeakConflict(bool aboveUpper, ArithVar basicVar, FarkasConflictBuilder& rc) const; + + /** + * Given a basic variable that is know to have a conflict on it, + * construct and return a conflict. + * Follows section 4.2 in the CAV06 paper. + */ + inline ConstraintCP generateConflictAboveUpperBound(ArithVar conflictVar, FarkasConflictBuilder& rc) const { + return minimallyWeakConflict(true, conflictVar, rc); + } + + inline ConstraintCP generateConflictBelowLowerBound(ArithVar conflictVar, FarkasConflictBuilder& rc) const { + return minimallyWeakConflict(false, conflictVar, rc); + } + + /** + * Computes the sum of the upper/lower bound of row. + * The variable skip is not included in the sum. + */ + DeltaRational computeRowBound(RowIndex ridx, bool rowUb, ArithVar skip) const; + +public: + void substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult); + void directlyAddToCoefficient(ArithVar row, ArithVar col, const Rational& mult); + + + /** + * Checks to make sure the assignment is consistent with the tableau. + * This code is for debugging. + */ + void debugCheckTableau(); + + void debugCheckTracking(); + + /** Debugging information for a pivot. */ + void debugPivot(ArithVar x_i, ArithVar x_j); + + ArithVar minBy(const ArithVarVec& vec, VarPreferenceFunction pf) const; + + /** + * Returns true if there would be a conflict on this row after a pivot + * and update using its basic variable and one of the non-basic variables on + * the row. + */ + bool willBeInConflictAfterPivot(const Tableau::Entry& entry, const DeltaRational& nbDiff, bool bToUB) const; + UpdateInfo mkConflictUpdate(const Tableau::Entry& entry, bool ub) const; + + /** + * Looks more an update for fcSimplex on the nonbasic variable nb with the focus coefficient. + */ + UpdateInfo speculativeUpdate(ArithVar nb, const Rational& focusCoeff, UpdatePreferenceFunction pref); + +private: + + /** + * Examines the effects of pivoting the entries column variable + * with the row's basic variable and setting the variable s.t. + * the basic variable is equal to one of its bounds. + * + * If ub, then the basic variable will be equal its upper bound. + * If not ub,then the basic variable will be equal its lower bound. + * + * Returns iff this row will be in conflict after the pivot. + * + * If this is false, add the bound to the relevant heap. + * If the bound is +/-infinity, this is ignored. + + * + * Returns true if this would be a conflict. + * If it returns false, this + */ + bool accumulateBorder(const Tableau::Entry& entry, bool ub); + + void handleBorders(UpdateInfo& selected, ArithVar nb, const Rational& focusCoeff, BorderHeap& heap, int minimumFixes, UpdatePreferenceFunction pref); + void pop_block(BorderHeap& heap, int& brokenInBlock, int& fixesRemaining, int& negErrorChange); + void clearSpeculative(); + Rational updateCoefficient(BorderVec::const_iterator startBlock, BorderVec::const_iterator endBlock); + +private: + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + IntStat d_statPivots, d_statUpdates; + TimerStat d_pivotTime; + TimerStat d_adjTime; + + IntStat d_weakeningAttempts, d_weakeningSuccesses, d_weakenings; + TimerStat d_weakenTime; + TimerStat d_forceTime; + + Statistics(); + }; + mutable Statistics d_statistics; + +};/* class LinearEqualityModule */ + +struct Cand { + ArithVar d_nb; + uint32_t d_penalty; + int d_sgn; + const Rational* d_coeff; + + Cand(ArithVar nb, uint32_t penalty, int s, const Rational* c) : + d_nb(nb), d_penalty(penalty), d_sgn(s), d_coeff(c){} +}; + + +class CompPenaltyColLength { +private: + LinearEqualityModule* d_mod; + const bool d_havePenalties; + + public: + CompPenaltyColLength(LinearEqualityModule* mod, bool havePenalties) + : d_mod(mod), d_havePenalties(havePenalties) + { + } + + bool operator()(const Cand& x, const Cand& y) const { + if (x.d_penalty == y.d_penalty || !d_havePenalties) + { + return x.d_nb == d_mod->minBoundAndColLength(x.d_nb,y.d_nb); + } + else + { + return x.d_penalty < y.d_penalty; + } + } +}; + +class UpdateTrackingCallback : public BoundUpdateCallback { +private: + LinearEqualityModule* d_mod; +public: + UpdateTrackingCallback(LinearEqualityModule* mod): d_mod(mod){} + void operator()(ArithVar v, const BoundsInfo& bi) override + { + d_mod->includeBoundUpdate(v, bi); + } +}; + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/matrix.cpp b/src/theory/arith/linear/matrix.cpp new file mode 100644 index 000000000..3a8b3b9fb --- /dev/null +++ b/src/theory/arith/linear/matrix.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * Sparse matrix implementations for different types. + */ + +#include "theory/arith/linear/matrix.h" + +using namespace std; +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +void NoEffectCCCB::update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) {} +void NoEffectCCCB::multiplyRow(RowIndex ridx, int sgn){} +bool NoEffectCCCB::canUseRow(RowIndex ridx) const { return false; } + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/matrix.h b/src/theory/arith/linear/matrix.h new file mode 100644 index 000000000..bed8aa9df --- /dev/null +++ b/src/theory/arith/linear/matrix.h @@ -0,0 +1,1002 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * Sparse matrix implementations for different types. + * + * Sparse matrix implementations for different types. + * This defines Matrix, IntegerEqualityTables and Tableau. + */ + +#include "cvc5_private.h" + +#pragma once + +#include +#include +#include + +#include "base/output.h" +#include "theory/arith/linear/arithvar.h" +#include "util/dense_map.h" +#include "util/index.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +typedef Index EntryID; +const EntryID ENTRYID_SENTINEL = std::numeric_limits::max(); + +typedef Index RowIndex; +const RowIndex ROW_INDEX_SENTINEL = std::numeric_limits::max(); + +class CoefficientChangeCallback { +public: + virtual ~CoefficientChangeCallback() {} + virtual void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) = 0; + virtual void multiplyRow(RowIndex ridx, int Sgn) = 0; + virtual bool canUseRow(RowIndex ridx) const = 0; +}; + +class NoEffectCCCB : public CoefficientChangeCallback { +public: + void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) override; + void multiplyRow(RowIndex ridx, int Sgn) override; + bool canUseRow(RowIndex ridx) const override; +}; + +template +class MatrixEntry { +private: + RowIndex d_rowIndex; + ArithVar d_colVar; + + EntryID d_nextRow; + EntryID d_nextCol; + + EntryID d_prevRow; + EntryID d_prevCol; + + T d_coefficient; + +public: + MatrixEntry(): + d_rowIndex(ROW_INDEX_SENTINEL), + d_colVar(ARITHVAR_SENTINEL), + d_nextRow(ENTRYID_SENTINEL), + d_nextCol(ENTRYID_SENTINEL), + d_prevRow(ENTRYID_SENTINEL), + d_prevCol(ENTRYID_SENTINEL), + d_coefficient() + {} + + MatrixEntry(RowIndex row, ArithVar col, const T& coeff): + d_rowIndex(row), + d_colVar(col), + d_nextRow(ENTRYID_SENTINEL), + d_nextCol(ENTRYID_SENTINEL), + d_prevRow(ENTRYID_SENTINEL), + d_prevCol(ENTRYID_SENTINEL), + d_coefficient(coeff) + {} + +private: + bool unusedConsistent() const { + return + (d_rowIndex == ROW_INDEX_SENTINEL && d_colVar == ARITHVAR_SENTINEL) || + (d_rowIndex != ROW_INDEX_SENTINEL && d_colVar != ARITHVAR_SENTINEL); + } + +public: + + EntryID getNextRowEntryID() const { + return d_nextRow; + } + + EntryID getNextColEntryID() const { + return d_nextCol; + } + EntryID getPrevRowEntryID() const { + return d_prevRow; + } + + EntryID getPrevColEntryID() const { + return d_prevCol; + } + + void setNextRowEntryID(EntryID id) { + d_nextRow = id; + } + void setNextColEntryID(EntryID id) { + d_nextCol = id; + } + void setPrevRowEntryID(EntryID id) { + d_prevRow = id; + } + void setPrevColEntryID(EntryID id) { + d_prevCol = id; + } + + RowIndex getRowIndex() const{ + return d_rowIndex; + } + + ArithVar getColVar() const{ + return d_colVar; + } + + const T& getCoefficient() const { + return d_coefficient; + } + + T& getCoefficient(){ + return d_coefficient; + } + + void setCoefficient(const T& t){ + d_coefficient = t; + } + + void markBlank() { + d_rowIndex = ROW_INDEX_SENTINEL; + d_colVar = ARITHVAR_SENTINEL; + } + + bool blank() const{ + Assert(unusedConsistent()); + + return d_rowIndex == ROW_INDEX_SENTINEL; + } +}; /* class MatrixEntry */ + +template +class MatrixEntryVector { +private: + typedef MatrixEntry EntryType; + typedef std::vector EntryArray; + + EntryArray d_entries; + std::queue d_freedEntries; + + uint32_t d_size; + +public: + MatrixEntryVector(): + d_entries(), d_freedEntries(), d_size(0) + {} + + const EntryType& operator[](EntryID id) const{ + Assert(inBounds(id)); + return d_entries[id]; + } + + EntryType& get(EntryID id){ + Assert(inBounds(id)); + return d_entries[id]; + } + + void freeEntry(EntryID id){ + Assert(get(id).blank()); + Assert(d_size > 0); + + d_freedEntries.push(id); + --d_size; + } + + EntryID newEntry(){ + EntryID newId; + if(d_freedEntries.empty()){ + newId = d_entries.size(); + d_entries.push_back(MatrixEntry()); + }else{ + newId = d_freedEntries.front(); + d_freedEntries.pop(); + } + ++d_size; + return newId; + } + + uint32_t size() const{ return d_size; } + uint32_t capacity() const{ return d_entries.capacity(); } + + +private: + bool inBounds(EntryID id) const{ + return id < d_entries.size(); + } +}; /* class MatrixEntryVector */ + +template +class MatrixVector { +private: + EntryID d_head; + uint32_t d_size; + + MatrixEntryVector* d_entries; + + class Iterator { + private: + EntryID d_curr; + const MatrixEntryVector* d_entries; + + public: + Iterator(EntryID start, const MatrixEntryVector* entries) : + d_curr(start), d_entries(entries) + {} + + public: + + EntryID getID() const { + return d_curr; + } + + const MatrixEntry& operator*() const{ + Assert(!atEnd()); + return (*d_entries)[d_curr]; + } + + Iterator& operator++(){ + Assert(!atEnd()); + const MatrixEntry& entry = (*d_entries)[d_curr]; + d_curr = isRow ? entry.getNextRowEntryID() : entry.getNextColEntryID(); + return *this; + } + + bool atEnd() const { + return d_curr == ENTRYID_SENTINEL; + } + + bool operator==(const Iterator& i) const{ + return d_curr == i.d_curr && d_entries == i.d_entries; + } + + bool operator!=(const Iterator& i) const{ + return !(d_curr == i.d_curr && d_entries == i.d_entries); + } + }; /* class MatrixVector::Iterator */ + +public: + MatrixVector(MatrixEntryVector* mev) + : d_head(ENTRYID_SENTINEL), d_size(0), d_entries(mev) + {} + + MatrixVector(EntryID head, uint32_t size, MatrixEntryVector* mev) + : d_head(head), d_size(size), d_entries(mev) + {} + + typedef Iterator const_iterator; + const_iterator begin() const { + return Iterator(d_head, d_entries); + } + const_iterator end() const { + return Iterator(ENTRYID_SENTINEL, d_entries); + } + + EntryID getHead() const { return d_head; } + + uint32_t getSize() const { return d_size; } + + void insert(EntryID newId){ + if(isRow){ + d_entries->get(newId).setNextRowEntryID(d_head); + + if(d_head != ENTRYID_SENTINEL){ + d_entries->get(d_head).setPrevRowEntryID(newId); + } + }else{ + d_entries->get(newId).setNextColEntryID(d_head); + + if(d_head != ENTRYID_SENTINEL){ + d_entries->get(d_head).setPrevColEntryID(newId); + } + } + + d_head = newId; + ++d_size; + } + void remove(EntryID id){ + Assert(d_size > 0); + --d_size; + if(isRow){ + EntryID prevRow = d_entries->get(id).getPrevRowEntryID(); + EntryID nextRow = d_entries->get(id).getNextRowEntryID(); + + if(d_head == id){ + d_head = nextRow; + } + if(prevRow != ENTRYID_SENTINEL){ + d_entries->get(prevRow).setNextRowEntryID(nextRow); + } + if(nextRow != ENTRYID_SENTINEL){ + d_entries->get(nextRow).setPrevRowEntryID(prevRow); + } + }else{ + EntryID prevCol = d_entries->get(id).getPrevColEntryID(); + EntryID nextCol = d_entries->get(id).getNextColEntryID(); + + if(d_head == id){ + d_head = nextCol; + } + + if(prevCol != ENTRYID_SENTINEL){ + d_entries->get(prevCol).setNextColEntryID(nextCol); + } + if(nextCol != ENTRYID_SENTINEL){ + d_entries->get(nextCol).setPrevColEntryID(prevCol); + } + } + } +}; /* class MatrixVector */ + +template + class RowVector : public MatrixVector +{ +private: + typedef MatrixVector SuperT; +public: + typedef typename SuperT::const_iterator const_iterator; + + RowVector(MatrixEntryVector* mev) : SuperT(mev){} + RowVector(EntryID head, uint32_t size, MatrixEntryVector* mev) + : SuperT(head, size, mev){} +};/* class RowVector */ + +template + class ColumnVector : public MatrixVector +{ +private: + typedef MatrixVector SuperT; +public: + typedef typename SuperT::const_iterator const_iterator; + + ColumnVector(MatrixEntryVector* mev) : SuperT(mev){} + ColumnVector(EntryID head, uint32_t size, MatrixEntryVector* mev) + : SuperT(head, size, mev){} +};/* class ColumnVector */ + +template +class Matrix { +public: + typedef MatrixEntry Entry; + +protected: + typedef RowVector RowVectorT; + typedef ColumnVector ColumnVectorT; + +public: + typedef typename RowVectorT::const_iterator RowIterator; + typedef typename ColumnVectorT::const_iterator ColIterator; + +protected: + // RowTable : RowID |-> RowVector + typedef std::vector< RowVectorT > RowTable; + RowTable d_rows; + + // ColumnTable : ArithVar |-> ColumnVector + typedef std::vector< ColumnVectorT > ColumnTable; + ColumnTable d_columns; + + /* The merge buffer is used to store a row in order to optimize row addition. */ + typedef std::pair PosUsedPair; + typedef DenseMap< PosUsedPair > RowToPosUsedPairMap; + RowToPosUsedPairMap d_mergeBuffer; + + /* The row that is in the merge buffer. */ + RowIndex d_rowInMergeBuffer; + + uint32_t d_entriesInUse; + MatrixEntryVector d_entries; + + std::vector d_pool; + + T d_zero; + +public: + /** + * Constructs an empty Matrix. + */ + Matrix() + : d_rows(), + d_columns(), + d_mergeBuffer(), + d_rowInMergeBuffer(ROW_INDEX_SENTINEL), + d_entriesInUse(0), + d_entries(), + d_zero(0) + {} + + Matrix(const T& zero) + : d_rows(), + d_columns(), + d_mergeBuffer(), + d_rowInMergeBuffer(ROW_INDEX_SENTINEL), + d_entriesInUse(0), + d_entries(), + d_zero(zero) + {} + + Matrix(const Matrix& m) + : d_rows(), + d_columns(), + d_mergeBuffer(m.d_mergeBuffer), + d_rowInMergeBuffer(m.d_rowInMergeBuffer), + d_entriesInUse(m.d_entriesInUse), + d_entries(m.d_entries), + d_zero(m.d_zero) + { + d_columns.clear(); + for(typename ColumnTable::const_iterator c=m.d_columns.begin(), cend = m.d_columns.end(); c!=cend; ++c){ + const ColumnVectorT& col = *c; + d_columns.push_back(ColumnVectorT(col.getHead(),col.getSize(),&d_entries)); + } + d_rows.clear(); + for(typename RowTable::const_iterator r=m.d_rows.begin(), rend = m.d_rows.end(); r!=rend; ++r){ + const RowVectorT& row = *r; + d_rows.push_back(RowVectorT(row.getHead(),row.getSize(),&d_entries)); + } + } + + Matrix& operator=(const Matrix& m){ + d_mergeBuffer = (m.d_mergeBuffer); + d_rowInMergeBuffer = (m.d_rowInMergeBuffer); + d_entriesInUse = (m.d_entriesInUse); + d_entries = (m.d_entries); + d_zero = (m.d_zero); + d_columns.clear(); + for(typename ColumnTable::const_iterator c=m.d_columns.begin(), cend = m.d_columns.end(); c!=cend; ++c){ + const ColumnVector& col = *c; + d_columns.push_back(ColumnVector(col.getHead(), col.getSize(), &d_entries)); + } + d_rows.clear(); + for(typename RowTable::const_iterator r=m.d_rows.begin(), rend = m.d_rows.end(); r!=rend; ++r){ + const RowVector& row = *r; + d_rows.push_back(RowVector(row.getHead(), row.getSize(), &d_entries)); + } + return *this; + } + +protected: + + void addEntry(RowIndex row, ArithVar col, const T& coeff){ + Trace("tableau") << "addEntry(" << row << "," << col <<"," << coeff << ")" << std::endl; + + Assert(coeff != 0); + Assert(row < d_rows.size()); + Assert(col < d_columns.size()); + + EntryID newId = d_entries.newEntry(); + Entry& newEntry = d_entries.get(newId); + newEntry = Entry(row, col, coeff); + + Assert(newEntry.getCoefficient() != 0); + + ++d_entriesInUse; + + d_rows[row].insert(newId); + d_columns[col].insert(newId); + } + + void removeEntry(EntryID id){ + Assert(d_entriesInUse > 0); + --d_entriesInUse; + + Entry& entry = d_entries.get(id); + + RowIndex ridx = entry.getRowIndex(); + ArithVar col = entry.getColVar(); + + Assert(d_rows[ridx].getSize() > 0); + Assert(d_columns[col].getSize() > 0); + + d_rows[ridx].remove(id); + d_columns[col].remove(id); + + entry.markBlank(); + + d_entries.freeEntry(id); + } + + private: + RowIndex requestRowIndex(){ + if(d_pool.empty()){ + RowIndex ridx = d_rows.size(); + d_rows.push_back(RowVectorT(&d_entries)); + return ridx; + }else{ + RowIndex rid = d_pool.back(); + d_pool.pop_back(); + return rid; + } + } + + void releaseRowIndex(RowIndex rid){ + d_pool.push_back(rid); + } + +public: + + size_t getNumRows() const { + return d_rows.size(); + } + + size_t getNumColumns() const { + return d_columns.size(); + } + + void increaseSize(){ + d_columns.push_back(ColumnVector(&d_entries)); + } + + void increaseSizeTo(size_t s){ + while(getNumColumns() < s){ + increaseSize(); + } + } + + const RowVector& getRow(RowIndex r) const { + Assert(r < d_rows.size()); + return d_rows[r]; + } + + const ColumnVector& getColumn(ArithVar v) const { + Assert(v < d_columns.size()); + return d_columns[v]; + } + + uint32_t getRowLength(RowIndex r) const{ + return getRow(r).getSize(); + } + + uint32_t getColLength(ArithVar x) const{ + return getColumn(x).getSize(); + } + + /** + * Adds a row to the matrix. + * The new row is equivalent to: + * \f$\sum_i\f$ coeffs[i] * variables[i] + */ + RowIndex addRow(const std::vector& coeffs, + const std::vector& variables){ + + RowIndex ridx = requestRowIndex(); + + //RowIndex ridx = d_rows.size(); + //d_rows.push_back(RowVectorT(&d_entries)); + + typename std::vector::const_iterator coeffIter = coeffs.begin(); + std::vector::const_iterator varsIter = variables.begin(); + std::vector::const_iterator varsEnd = variables.end(); + + for(; varsIter != varsEnd; ++coeffIter, ++varsIter){ + const T& coeff = *coeffIter; + ArithVar var_i = *varsIter; + Assert(var_i < getNumColumns()); + addEntry(ridx, var_i, coeff); + } + + return ridx; + } + + + void loadRowIntoBuffer(RowIndex rid){ + Assert(d_mergeBuffer.empty()); + Assert(d_rowInMergeBuffer == ROW_INDEX_SENTINEL); + + RowIterator i = getRow(rid).begin(), i_end = getRow(rid).end(); + for(; i != i_end; ++i){ + EntryID id = i.getID(); + const MatrixEntry& entry = *i; + ArithVar colVar = entry.getColVar(); + d_mergeBuffer.set(colVar, std::make_pair(id, false)); + } + + d_rowInMergeBuffer = rid; + } + + void clearBuffer() { + Assert(d_rowInMergeBuffer != ROW_INDEX_SENTINEL); + + d_rowInMergeBuffer = ROW_INDEX_SENTINEL; + d_mergeBuffer.purge(); + } + + /* to *= mult */ + void multiplyRowByConstant(RowIndex to, const T& mult){ + RowIterator i = getRow(to).begin(); + RowIterator i_end = getRow(to).end(); + for( ; i != i_end; ++i){ + EntryID id = i.getID(); + Entry& entry = d_entries.get(id); + T& coeff = entry.getCoefficient(); + coeff *= mult; + } + } + + /** to += mult * from. + * Use the more efficient rowPlusBufferTimesConstant() for + * repeated use. + */ + void rowPlusRowTimesConstant(RowIndex to, RowIndex from, const T& mult){ + Assert(to != from); + loadRowIntoBuffer(from); + rowPlusBufferTimesConstant(to, mult); + clearBuffer(); + } + + /** to += mult * buffer. + * Invalidates coefficients on the row. + * (mult should never be a direct copy of a coefficient!) + */ + void rowPlusBufferTimesConstant(RowIndex to, const T& mult){ + Assert(d_rowInMergeBuffer != ROW_INDEX_SENTINEL); + Assert(to != ROW_INDEX_SENTINEL); + + Trace("tableau") << "rowPlusRowTimesConstant(" + << to << "," << mult << "," << d_rowInMergeBuffer << ")" + << std::endl; + + Assert(debugNoZeroCoefficients(to)); + Assert(debugNoZeroCoefficients(d_rowInMergeBuffer)); + + Assert(mult != 0); + + RowIterator i = getRow(to).begin(); + RowIterator i_end = getRow(to).end(); + while(i != i_end){ + EntryID id = i.getID(); + Entry& entry = d_entries.get(id); + ArithVar colVar = entry.getColVar(); + + ++i; + + if(d_mergeBuffer.isKey(colVar)){ + EntryID bufferEntry = d_mergeBuffer[colVar].first; + Assert(!d_mergeBuffer[colVar].second); + d_mergeBuffer.get(colVar).second = true; + + const Entry& other = d_entries.get(bufferEntry); + T& coeff = entry.getCoefficient(); + coeff += mult * other.getCoefficient(); + + if(coeff.sgn() == 0){ + removeEntry(id); + } + } + } + + i = getRow(d_rowInMergeBuffer).begin(); + i_end = getRow(d_rowInMergeBuffer).end(); + + for(; i != i_end; ++i){ + const Entry& entry = *i; + ArithVar colVar = entry.getColVar(); + + if(d_mergeBuffer[colVar].second){ + d_mergeBuffer.get(colVar).second = false; + }else{ + Assert(!(d_mergeBuffer[colVar]).second); + T newCoeff = mult * entry.getCoefficient(); + addEntry(to, colVar, newCoeff); + } + } + + Assert(mergeBufferIsClear()); + + if(TraceIsOn("matrix")) { printMatrix(); } + } + + /** to += mult * buffer. */ + void rowPlusBufferTimesConstant(RowIndex to, const T& mult, CoefficientChangeCallback& cb){ + Assert(d_rowInMergeBuffer != ROW_INDEX_SENTINEL); + Assert(to != ROW_INDEX_SENTINEL); + + Trace("tableau") << "rowPlusRowTimesConstant(" + << to << "," << mult << "," << d_rowInMergeBuffer << ")" + << std::endl; + + Assert(debugNoZeroCoefficients(to)); + Assert(debugNoZeroCoefficients(d_rowInMergeBuffer)); + + Assert(mult != 0); + + RowIterator i = getRow(to).begin(); + RowIterator i_end = getRow(to).end(); + while(i != i_end){ + EntryID id = i.getID(); + Entry& entry = d_entries.get(id); + ArithVar colVar = entry.getColVar(); + + ++i; + + if(d_mergeBuffer.isKey(colVar)){ + EntryID bufferEntry = d_mergeBuffer[colVar].first; + Assert(!d_mergeBuffer[colVar].second); + d_mergeBuffer.get(colVar).second = true; + + const Entry& other = d_entries.get(bufferEntry); + T& coeff = entry.getCoefficient(); + int coeffOldSgn = coeff.sgn(); + coeff += mult * other.getCoefficient(); + int coeffNewSgn = coeff.sgn(); + + if(coeffOldSgn != coeffNewSgn){ + cb.update(to, colVar, coeffOldSgn, coeffNewSgn); + + if(coeffNewSgn == 0){ + removeEntry(id); + } + } + } + } + + i = getRow(d_rowInMergeBuffer).begin(); + i_end = getRow(d_rowInMergeBuffer).end(); + + for(; i != i_end; ++i){ + const Entry& entry = *i; + ArithVar colVar = entry.getColVar(); + + if(d_mergeBuffer[colVar].second){ + d_mergeBuffer.get(colVar).second = false; + }else{ + Assert(!(d_mergeBuffer[colVar]).second); + T newCoeff = mult * entry.getCoefficient(); + addEntry(to, colVar, newCoeff); + + cb.update(to, colVar, 0, newCoeff.sgn()); + } + } + + Assert(mergeBufferIsClear()); + + if(TraceIsOn("matrix")) { printMatrix(); } + } + + bool mergeBufferIsClear() const{ + RowToPosUsedPairMap::const_iterator i = d_mergeBuffer.begin(); + RowToPosUsedPairMap::const_iterator i_end = d_mergeBuffer.end(); + for(; i != i_end; ++i){ + RowIndex rid = *i; + if(d_mergeBuffer[rid].second){ + return false; + } + } + return true; + } + +protected: + + EntryID findOnRow(RowIndex rid, ArithVar column) const { + RowIterator i = d_rows[rid].begin(), i_end = d_rows[rid].end(); + for(; i != i_end; ++i){ + EntryID id = i.getID(); + const MatrixEntry& entry = *i; + ArithVar colVar = entry.getColVar(); + + if(colVar == column){ + return id; + } + } + return ENTRYID_SENTINEL; + } + + EntryID findOnCol(RowIndex rid, ArithVar column) const{ + ColIterator i = d_columns[column].begin(), i_end = d_columns[column].end(); + for(; i != i_end; ++i){ + EntryID id = i.getID(); + const MatrixEntry& entry = *i; + RowIndex currRow = entry.getRowIndex(); + + if(currRow == rid){ + return id; + } + } + return ENTRYID_SENTINEL; + } + + EntryID findEntryID(RowIndex rid, ArithVar col) const{ + bool colIsShorter = getColLength(col) < getRowLength(rid); + EntryID id = colIsShorter ? findOnCol(rid, col) : findOnRow(rid,col); + return id; + } + MatrixEntry d_failedFind; +public: + + /** If the find fails, isUnused is true on the entry. */ + const MatrixEntry& findEntry(RowIndex rid, ArithVar col) const{ + EntryID id = findEntryID(rid, col); + if(id == ENTRYID_SENTINEL){ + return d_failedFind; + }else{ + return d_entries[id]; + } + } + + /** + * Prints the contents of the Matrix to Trace("matrix") + */ + void printMatrix(std::ostream& out) const { + out << "Matrix::printMatrix" << std::endl; + + for(RowIndex i = 0, N = d_rows.size(); i < N; ++i){ + printRow(i, out); + } + } + void printMatrix() const { + printMatrix(Trace("matrix")); + } + + void printRow(RowIndex rid, std::ostream& out) const { + out << "{" << rid << ":"; + const RowVector& row = getRow(rid); + RowIterator i = row.begin(); + RowIterator i_end = row.end(); + for(; i != i_end; ++i){ + printEntry(*i, out); + out << ","; + } + out << "}" << std::endl; + } + void printRow(RowIndex rid) const { + printRow(rid, Trace("matrix")); + } + + void printEntry(const MatrixEntry& entry, std::ostream& out) const { + out << entry.getColVar() << "*" << entry.getCoefficient(); + } + void printEntry(const MatrixEntry& entry) const { + printEntry(entry, Trace("matrix")); + } +public: + uint32_t size() const { + return d_entriesInUse; + } + uint32_t getNumEntriesInTableau() const { + return d_entries.size(); + } + uint32_t getEntryCapacity() const { + return d_entries.capacity(); + } + + void manipulateRowEntry(RowIndex row, ArithVar col, const T& c, CoefficientChangeCallback& cb){ + int coeffOldSgn; + int coeffNewSgn; + + EntryID id = findEntryID(row, col); + if(id == ENTRYID_SENTINEL){ + coeffOldSgn = 0; + addEntry(row, col, c); + coeffNewSgn = c.sgn(); + }else{ + Entry& e = d_entries.get(id); + T& t = e.getCoefficient(); + coeffOldSgn = t.sgn(); + t += c; + coeffNewSgn = t.sgn(); + } + + if(coeffOldSgn != coeffNewSgn){ + cb.update(row, col, coeffOldSgn, coeffNewSgn); + } + if(coeffNewSgn == 0){ + removeEntry(id); + } + } + + void removeRow(RowIndex rid){ + RowIterator i = getRow(rid).begin(); + RowIterator i_end = getRow(rid).end(); + for(; i != i_end; ++i){ + EntryID id = i.getID(); + removeEntry(id); + } + releaseRowIndex(rid); + } + + double densityMeasure() const{ + Assert(numNonZeroEntriesByRow() == numNonZeroEntries()); + Assert(numNonZeroEntriesByCol() == numNonZeroEntries()); + + uint32_t n = getNumRows(); + if(n == 0){ + return 1.0; + }else { + uint32_t s = numNonZeroEntries(); + uint32_t m = d_columns.size(); + uint32_t divisor = (n *(m - n + 1)); + + Assert(n >= 1); + Assert(m >= n); + Assert(divisor > 0); + Assert(divisor >= s); + + return (double(s)) / divisor; + } + } + + void loadSignQueries(RowIndex rid, DenseMap& target) const{ + + RowIterator i = getRow(rid).begin(), i_end = getRow(rid).end(); + for(; i != i_end; ++i){ + const MatrixEntry& entry = *i; + target.set(entry.getColVar(), entry.getCoefficient().sgn()); + } + } + +protected: + uint32_t numNonZeroEntries() const { return size(); } + + uint32_t numNonZeroEntriesByRow() const { + uint32_t rowSum = 0; + for(RowIndex rid = 0, N = d_rows.size(); rid < N; ++rid){ + rowSum += getRowLength(rid); + } + return rowSum; + } + + uint32_t numNonZeroEntriesByCol() const { + uint32_t colSum = 0; + for(ArithVar v = 0, N = d_columns.size(); v < N; ++v){ + colSum += getColLength(v); + } + return colSum; + } + + + bool debugNoZeroCoefficients(RowIndex ridx){ + for(RowIterator i=getRow(ridx).begin(); !i.atEnd(); ++i){ + const Entry& entry = *i; + if(entry.getCoefficient() == 0){ + return false; + } + } + return true; + } + bool debugMatchingCountsForRow(RowIndex ridx){ + for(RowIterator i=getRow(ridx).begin(); !i.atEnd(); ++i){ + const Entry& entry = *i; + ArithVar colVar = entry.getColVar(); + uint32_t count = debugCountColLength(colVar); + Trace("tableau") << "debugMatchingCountsForRow " + << ridx << ":" << colVar << " " << count + <<" "<< getColLength(colVar) << std::endl; + if( count != getColLength(colVar) ){ + return false; + } + } + return true; + } + + uint32_t debugCountColLength(ArithVar var){ + Trace("tableau") << var << " "; + uint32_t count = 0; + for(ColIterator i=getColumn(var).begin(); !i.atEnd(); ++i){ + const Entry& entry = *i; + Trace("tableau") << "(" << entry.getRowIndex() << ", " << i.getID() << ") "; + ++count; + } + Trace("tableau") << std::endl; + return count; + } + uint32_t debugCountRowLength(RowIndex ridx){ + uint32_t count = 0; + for(RowIterator i=getRow(ridx).begin(); !i.atEnd(); ++i){ + ++count; + } + return count; + } + +};/* class Matrix */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/normal_form.cpp b/src/theory/arith/linear/normal_form.cpp new file mode 100644 index 000000000..81bb23833 --- /dev/null +++ b/src/theory/arith/linear/normal_form.cpp @@ -0,0 +1,1427 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ +#include "theory/arith/linear/normal_form.h" + +#include + +#include "base/output.h" +#include "theory/arith/arith_utilities.h" +#include "theory/theory.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +Constant Constant::mkConstant(const Rational& rat) { + return Constant(mkRationalNode(rat)); +} + +size_t Variable::getComplexity() const{ + return 1u; +} + +size_t VarList::getComplexity() const{ + if(empty()){ + return 1; + }else if(singleton()){ + return 1; + }else{ + return size() + 1; + } +} + +size_t Monomial::getComplexity() const{ + return getConstant().getComplexity() + getVarList().getComplexity(); +} + +size_t Polynomial::getComplexity() const{ + size_t cmp = 0; + iterator i = begin(), e = end(); + for(; i != e; ++i){ + Monomial m = *i; + cmp += m.getComplexity(); + } + return cmp; +} + +size_t Constant::getComplexity() const{ + return getValue().complexity(); +} + +bool Variable::isLeafMember(Node n){ + return (!isRelationOperator(n.getKind())) && + (Theory::isLeafOf(n, theory::THEORY_ARITH)); +} + +VarList::VarList(Node n) : NodeWrapper(n) { Assert(isSorted(begin(), end())); } + +bool Variable::isIAndMember(Node n) +{ + return n.getKind() == kind::IAND && Polynomial::isMember(n[0]) + && Polynomial::isMember(n[1]); +} + +bool Variable::isPow2Member(Node n) +{ + return n.getKind() == kind::POW2 && Polynomial::isMember(n[0]); +} + +bool Variable::isDivMember(Node n){ + switch(n.getKind()){ + case kind::DIVISION: + case kind::INTS_DIVISION: + case kind::INTS_MODULUS: + case kind::DIVISION_TOTAL: + case kind::INTS_DIVISION_TOTAL: + case kind::INTS_MODULUS_TOTAL: + return Polynomial::isMember(n[0]) && Polynomial::isMember(n[1]); + default: + return false; + } +} + +bool Variable::isTranscendentalMember(Node n) { + switch(n.getKind()){ + case kind::EXPONENTIAL: + case kind::SINE: + case kind::COSINE: + case kind::TANGENT: + case kind::COSECANT: + case kind::SECANT: + case kind::COTANGENT: + case kind::ARCSINE: + case kind::ARCCOSINE: + case kind::ARCTANGENT: + case kind::ARCCOSECANT: + case kind::ARCSECANT: + case kind::ARCCOTANGENT: + case kind::SQRT: return Polynomial::isMember(n[0]); + case kind::PI: + return true; + default: + return false; + } +} + + +bool VarList::isSorted(iterator start, iterator end) { + return std::is_sorted(start, end); +} + +bool VarList::isMember(Node n) { + if(Variable::isMember(n)) { + return true; + } + if(n.getKind() == kind::NONLINEAR_MULT) { + Node::iterator curr = n.begin(), end = n.end(); + Node prev = *curr; + if(!Variable::isMember(prev)) return false; + + Variable::VariableNodeCmp cmp; + + while( (++curr) != end) { + if(!Variable::isMember(*curr)) return false; + // prev <= curr : accept + // !(prev <= curr) : reject + // !(!(prev > curr)) : reject + // curr < prev : reject + if((cmp(*curr, prev))) return false; + prev = *curr; + } + return true; + } else { + return false; + } +} + +int VarList::cmp(const VarList& vl) const { + int dif = this->size() - vl.size(); + if (dif == 0) { + if(this->getNode() == vl.getNode()) { + return 0; + } + + Assert(!empty()); + Assert(!vl.empty()); + if(this->size() == 1){ + return Variable::VariableNodeCmp::cmp(this->getNode(), vl.getNode()); + } + + + internal_iterator ii=this->internalBegin(), ie=this->internalEnd(); + internal_iterator ci=vl.internalBegin(), ce=vl.internalEnd(); + for(; ii != ie; ++ii, ++ci){ + Node vi = *ii; + Node vc = *ci; + int tmp = Variable::VariableNodeCmp::cmp(vi, vc); + if(tmp != 0){ + return tmp; + } + } + Unreachable(); + } else if(dif < 0) { + return -1; + } else { + return 1; + } +} + +VarList VarList::parseVarList(Node n) { + return VarList(n); + // if(Variable::isMember(n)) { + // return VarList(Variable(n)); + // } else { + // Assert(n.getKind() == kind::MULT); + // for(Node::iterator i=n.begin(), end = n.end(); i!=end; ++i) { + // Assert(Variable::isMember(*i)); + // } + // return VarList(n); + // } +} + +VarList VarList::operator*(const VarList& other) const { + if(this->empty()) { + return other; + } else if(other.empty()) { + return *this; + } else { + vector result; + + internal_iterator + thisBegin = this->internalBegin(), + thisEnd = this->internalEnd(), + otherBegin = other.internalBegin(), + otherEnd = other.internalEnd(); + + Variable::VariableNodeCmp cmp; + std::merge(thisBegin, thisEnd, otherBegin, otherEnd, std::back_inserter(result), cmp); + + Assert(result.size() >= 2); + Node mult = NodeManager::currentNM()->mkNode(kind::NONLINEAR_MULT, result); + return VarList::parseVarList(mult); + } +} + +bool Monomial::isMember(TNode n){ + if(n.getKind() == kind::CONST_RATIONAL) { + return true; + } else if(multStructured(n)) { + return VarList::isMember(n[1]); + } else { + return VarList::isMember(n); + } +} + +Monomial Monomial::mkMonomial(const Constant& c, const VarList& vl) { + if(c.isZero() || vl.empty() ) { + return Monomial(c); + } else if(c.isOne()) { + return Monomial(vl); + } else { + return Monomial(c, vl); + } +} + +Monomial Monomial::mkMonomial(const VarList& vl) { + // acts like Monomial::mkMonomial( 1, vl) + if( vl.empty() ) { + return Monomial::mkOne(); + } else if(true){ + return Monomial(vl); + } +} + +Monomial Monomial::parseMonomial(Node n) { + if(n.getKind() == kind::CONST_RATIONAL) { + return Monomial(Constant(n)); + } else if(multStructured(n)) { + return Monomial::mkMonomial(Constant(n[0]),VarList::parseVarList(n[1])); + } else { + return Monomial(VarList::parseVarList(n)); + } +} +Monomial Monomial::operator*(const Rational& q) const { + if(q.isZero()){ + return mkZero(); + }else{ + Constant newConstant = this->getConstant() * q; + return Monomial::mkMonomial(newConstant, getVarList()); + } +} + +Monomial Monomial::operator*(const Constant& c) const { + return (*this) * c.getValue(); + // if(c.isZero()){ + // return mkZero(); + // }else{ + // Constant newConstant = this->getConstant() * c; + // return Monomial::mkMonomial(newConstant, getVarList()); + // } +} + +Monomial Monomial::operator*(const Monomial& mono) const { + Constant newConstant = this->getConstant() * mono.getConstant(); + VarList newVL = this->getVarList() * mono.getVarList(); + + return Monomial::mkMonomial(newConstant, newVL); +} + +// vector Monomial::sumLikeTerms(const std::vector & monos) +// { +// Assert(isSorted(monos)); +// vector outMonomials; +// typedef vector::const_iterator iterator; +// for(iterator rangeIter = monos.begin(), end=monos.end(); rangeIter != end;) +// { +// Rational constant = (*rangeIter).getConstant().getValue(); +// VarList varList = (*rangeIter).getVarList(); +// ++rangeIter; +// while(rangeIter != end && varList == (*rangeIter).getVarList()) { +// constant += (*rangeIter).getConstant().getValue(); +// ++rangeIter; +// } +// if(constant != 0) { +// Constant asConstant = Constant::mkConstant(constant); +// Monomial nonZero = Monomial::mkMonomial(asConstant, varList); +// outMonomials.push_back(nonZero); +// } +// } + +// Assert(isStrictlySorted(outMonomials)); +// return outMonomials; +// } + +void Monomial::sort(std::vector& m){ + if(!isSorted(m)){ + std::sort(m.begin(), m.end()); + } +} + +void Monomial::combineAdjacentMonomials(std::vector& monos) { + Assert(isSorted(monos)); + size_t writePos, readPos, N; + for(writePos = 0, readPos = 0, N = monos.size(); readPos < N;){ + Monomial& atRead = monos[readPos]; + const VarList& varList = atRead.getVarList(); + + size_t rangeEnd = readPos+1; + for(; rangeEnd < N; rangeEnd++){ + if(!(varList == monos[rangeEnd].getVarList())){ break; } + } + // monos[i] for i in [readPos, rangeEnd) has the same var list + if(readPos+1 == rangeEnd){ // no addition needed + if(!atRead.getConstant().isZero()){ + Monomial cpy = atRead; // being paranoid here + monos[writePos] = cpy; + writePos++; + } + }else{ + Rational constant(monos[readPos].getConstant().getValue()); + for(size_t i=readPos+1; i < rangeEnd; ++i){ + constant += monos[i].getConstant().getValue(); + } + if(!constant.isZero()){ + Constant asConstant = Constant::mkConstant(constant); + Monomial nonZero = Monomial::mkMonomial(asConstant, varList); + monos[writePos] = nonZero; + writePos++; + } + } + Assert(rangeEnd > readPos); + readPos = rangeEnd; + } + if(writePos > 0 ){ + Monomial cp = monos[0]; + Assert(writePos <= N); + monos.resize(writePos, cp); + }else{ + monos.clear(); + } + Assert(isStrictlySorted(monos)); +} + +void Monomial::print() const { + Trace("normal-form") << getNode() << std::endl; +} + +void Monomial::printList(const std::vector& list) { + for(vector::const_iterator i = list.begin(), end = list.end(); i != end; ++i) { + const Monomial& m =*i; + m.print(); + } +} +Polynomial Polynomial::operator+(const Polynomial& vl) const { + + std::vector sortedMonos; + std::merge(begin(), end(), vl.begin(), vl.end(), std::back_inserter(sortedMonos)); + + Monomial::combineAdjacentMonomials(sortedMonos); + //std::vector combined = Monomial::sumLikeTerms(sortedMonos); + + Polynomial result = mkPolynomial(sortedMonos); + return result; +} + +Polynomial Polynomial::exactDivide(const Integer& z) const { + Assert(isIntegral()); + if(z.isOne()){ + return (*this); + }else { + Constant invz = Constant::mkConstant(Rational(1,z)); + Polynomial prod = (*this) * Monomial::mkMonomial(invz); + Assert(prod.isIntegral()); + return prod; + } +} + +Polynomial Polynomial::sumPolynomials(const std::vector& ps){ + if(ps.empty()){ + return mkZero(); + }else if(ps.size() <= 4){ + // if there are few enough polynomials just add them + Polynomial p = ps[0]; + for(size_t i = 1; i < ps.size(); ++i){ + p = p + ps[i]; + } + return p; + }else{ + // general case + std::map coeffs; + for(size_t i = 0, N = ps.size(); i monos; + std::map::const_iterator ci = coeffs.begin(), cend = coeffs.end(); + for(; ci != cend; ++ci){ + if(!(*ci).second.isZero()){ + Constant c = Constant::mkConstant((*ci).second); + Node n = (*ci).first; + VarList vl = VarList::parseVarList(n); + monos.push_back(Monomial::mkMonomial(c, vl)); + } + } + Monomial::sort(monos); + Monomial::combineAdjacentMonomials(monos); + + Polynomial result = mkPolynomial(monos); + return result; + } +} + +Polynomial Polynomial::operator-(const Polynomial& vl) const { + Constant negOne = Constant::mkConstant(Rational(-1)); + + return *this + (vl*negOne); +} + +Polynomial Polynomial::operator*(const Rational& q) const{ + if(q.isZero()){ + return Polynomial::mkZero(); + }else if(q.isOne()){ + return *this; + }else{ + std::vector newMonos; + for(iterator i = this->begin(), end = this->end(); i != end; ++i) { + newMonos.push_back((*i)*q); + } + + Assert(Monomial::isStrictlySorted(newMonos)); + return Polynomial::mkPolynomial(newMonos); + } +} + +Polynomial Polynomial::operator*(const Constant& c) const{ + return (*this) * c.getValue(); + // if(c.isZero()){ + // return Polynomial::mkZero(); + // }else if(c.isOne()){ + // return *this; + // }else{ + // std::vector newMonos; + // for(iterator i = this->begin(), end = this->end(); i != end; ++i) { + // newMonos.push_back((*i)*c); + // } + + // Assert(Monomial::isStrictlySorted(newMonos)); + // return Polynomial::mkPolynomial(newMonos); + // } +} + +Polynomial Polynomial::operator*(const Monomial& mono) const { + if(mono.isZero()) { + return Polynomial(mono); //Don't multiply by zero + } else { + std::vector newMonos; + for(iterator i = this->begin(), end = this->end(); i != end; ++i) { + newMonos.push_back(mono * (*i)); + } + + // We may need to sort newMonos. + // Suppose this = (+ x y), mono = x, (* x y).getId() < (* x x).getId() + // newMonos = <(* x x), (* x y)> after this loop. + // This is not sorted according to the current VarList order. + Monomial::sort(newMonos); + return Polynomial::mkPolynomial(newMonos); + } +} + +Polynomial Polynomial::operator*(const Polynomial& poly) const { + Polynomial res = Polynomial::mkZero(); + for(iterator i = this->begin(), end = this->end(); i != end; ++i) { + Monomial curr = *i; + Polynomial prod = poly * curr; + Polynomial sum = res + prod; + res = sum; + } + return res; +} + +Monomial Polynomial::selectAbsMinimum() const { + iterator iter = begin(), myend = end(); + Assert(iter != myend); + + Monomial min = *iter; + ++iter; + for(; iter != end(); ++iter){ + Monomial curr = *iter; + if(curr.absCmp(min) < 0){ + min = curr; + } + } + return min; +} + +bool Polynomial::leadingCoefficientIsAbsOne() const { + return getHead().absCoefficientIsOne(); +} +bool Polynomial::leadingCoefficientIsPositive() const { + return getHead().getConstant().isPositive(); +} + +bool Polynomial::denominatorLCMIsOne() const { + return denominatorLCM().isOne(); +} + +bool Polynomial::numeratorGCDIsOne() const { + return gcd().isOne(); +} + +Integer Polynomial::gcd() const { + Assert(isIntegral()); + return numeratorGCD(); +} + +Integer Polynomial::numeratorGCD() const { + //We'll use the standardization that gcd(0, 0) = 0 + //So that the gcd of the zero polynomial is gcd{0} = 0 + iterator i=begin(), e=end(); + Assert(i != e); + + Integer d = (*i).getConstant().getValue().getNumerator().abs(); + if(d.isOne()){ + return d; + } + ++i; + for(; i!=e; ++i){ + Integer c = (*i).getConstant().getValue().getNumerator(); + d = d.gcd(c); + if(d.isOne()){ + return d; + } + } + return d; +} + +Integer Polynomial::denominatorLCM() const { + Integer tmp(1); + for (iterator i = begin(), e = end(); i != e; ++i) { + const Integer denominator = (*i).getConstant().getValue().getDenominator(); + tmp = tmp.lcm(denominator); + } + return tmp; +} + +Constant Polynomial::getCoefficient(const VarList& vl) const{ + //TODO improve to binary search... + for(iterator iter=begin(), myend=end(); iter != myend; ++iter){ + Monomial m = *iter; + VarList curr = m.getVarList(); + if(curr == vl){ + return m.getConstant(); + } + } + return Constant::mkConstant(0); +} + +Node Polynomial::computeQR(const Polynomial& p, const Integer& div){ + Assert(p.isIntegral()); + std::vector q_vec, r_vec; + Integer tmp_q, tmp_r; + for(iterator iter = p.begin(), pend = p.end(); iter != pend; ++iter){ + Monomial curr = *iter; + VarList vl = curr.getVarList(); + Constant c = curr.getConstant(); + + const Integer& a = c.getValue().getNumerator(); + Integer::floorQR(tmp_q, tmp_r, a, div); + Constant q=Constant::mkConstant(tmp_q); + Constant r=Constant::mkConstant(tmp_r); + if(!q.isZero()){ + q_vec.push_back(Monomial::mkMonomial(q, vl)); + } + if(!r.isZero()){ + r_vec.push_back(Monomial::mkMonomial(r, vl)); + } + } + + Polynomial p_q = Polynomial::mkPolynomial(q_vec); + Polynomial p_r = Polynomial::mkPolynomial(r_vec); + + return NodeManager::currentNM()->mkNode( + kind::ADD, p_q.getNode(), p_r.getNode()); +} + + +Monomial Polynomial::minimumVariableMonomial() const{ + Assert(!isConstant()); + if(singleton()){ + return getHead(); + }else{ + iterator i = begin(); + Monomial first = *i; + if( first.isConstant() ){ + ++i; + Assert(i != end()); + return *i; + }else{ + return first; + } + } +} + +bool Polynomial::variableMonomialAreStrictlyGreater(const Monomial& m) const{ + if(isConstant()){ + return true; + }else{ + Monomial minimum = minimumVariableMonomial(); + Trace("nf::tmp") << "minimum " << minimum.getNode() << endl; + Trace("nf::tmp") << "m " << m.getNode() << endl; + return m < minimum; + } +} + +bool Polynomial::isMember(TNode n) { + if(Monomial::isMember(n)){ + return true; + } + else if (n.getKind() == kind::ADD) + { + Assert(n.getNumChildren() >= 2); + Node::iterator currIter = n.begin(), end = n.end(); + Node prev = *currIter; + if(!Monomial::isMember(prev)){ + return false; + } + + Monomial mprev = Monomial::parseMonomial(prev); + ++currIter; + for(; currIter != end; ++currIter){ + Node curr = *currIter; + if(!Monomial::isMember(curr)){ + return false; + } + Monomial mcurr = Monomial::parseMonomial(curr); + if(!(mprev < mcurr)){ + return false; + } + mprev = mcurr; + } + return true; + } + else + { + return false; + } +} + +Node SumPair::computeQR(const SumPair& sp, const Integer& div){ + Assert(sp.isIntegral()); + + const Integer& constant = sp.getConstant().getValue().getNumerator(); + + Integer constant_q, constant_r; + Integer::floorQR(constant_q, constant_r, constant, div); + + Node p_qr = Polynomial::computeQR(sp.getPolynomial(), div); + Assert(p_qr.getKind() == kind::ADD); + Assert(p_qr.getNumChildren() == 2); + + Polynomial p_q = Polynomial::parsePolynomial(p_qr[0]); + Polynomial p_r = Polynomial::parsePolynomial(p_qr[1]); + + SumPair sp_q(p_q, Constant::mkConstant(constant_q)); + SumPair sp_r(p_r, Constant::mkConstant(constant_r)); + + return NodeManager::currentNM()->mkNode( + kind::ADD, sp_q.getNode(), sp_r.getNode()); +} + +SumPair SumPair::mkSumPair(const Polynomial& p){ + if(p.isConstant()){ + Constant leadingConstant = p.getHead().getConstant(); + return SumPair(Polynomial::mkZero(), leadingConstant); + }else if(p.containsConstant()){ + Assert(!p.singleton()); + return SumPair(p.getTail(), p.getHead().getConstant()); + }else{ + return SumPair(p, Constant::mkZero()); + } +} + +Comparison::Comparison(TNode n) : NodeWrapper(n) { Assert(isNormalForm()); } + +SumPair Comparison::toSumPair() const { + Kind cmpKind = comparisonKind(); + switch(cmpKind){ + case kind::LT: + case kind::LEQ: + case kind::GT: + case kind::GEQ: + { + TNode lit = getNode(); + TNode atom = (cmpKind == kind::LT || cmpKind == kind::LEQ) ? lit[0] : lit; + Polynomial p = Polynomial::parsePolynomial(atom[0]); + Constant c = Constant::mkConstant(atom[1]); + if(p.leadingCoefficientIsPositive()){ + return SumPair(p, -c); + }else{ + return SumPair(-p, c); + } + } + case kind::EQUAL: + case kind::DISTINCT: + { + Polynomial left = getLeft(); + Polynomial right = getRight(); + Trace("nf::tmp") << "left: " << left.getNode() << endl; + Trace("nf::tmp") << "right: " << right.getNode() << endl; + if(right.isConstant()){ + return SumPair(left, -right.getHead().getConstant()); + }else if(right.containsConstant()){ + Assert(!right.singleton()); + + Polynomial noConstant = right.getTail(); + return SumPair(left - noConstant, -right.getHead().getConstant()); + }else{ + return SumPair(left - right, Constant::mkZero()); + } + } + default: Unhandled() << cmpKind; + } +} + +Polynomial Comparison::normalizedVariablePart() const { + Kind cmpKind = comparisonKind(); + switch(cmpKind){ + case kind::LT: + case kind::LEQ: + case kind::GT: + case kind::GEQ: + { + TNode lit = getNode(); + TNode atom = (cmpKind == kind::LT || cmpKind == kind::LEQ) ? lit[0] : lit; + Polynomial p = Polynomial::parsePolynomial(atom[0]); + if(p.leadingCoefficientIsPositive()){ + return p; + }else{ + return -p; + } + } + case kind::EQUAL: + case kind::DISTINCT: + { + Polynomial left = getLeft(); + Polynomial right = getRight(); + if(right.isConstant()){ + return left; + }else{ + Polynomial noConstant = right.containsConstant() ? right.getTail() : right; + Polynomial diff = left - noConstant; + if(diff.leadingCoefficientIsPositive()){ + return diff; + }else{ + return -diff; + } + } + } + default: Unhandled() << cmpKind; + } +} + +DeltaRational Comparison::normalizedDeltaRational() const { + Kind cmpKind = comparisonKind(); + int delta = deltaCoeff(cmpKind); + switch(cmpKind){ + case kind::LT: + case kind::LEQ: + case kind::GT: + case kind::GEQ: + { + Node lit = getNode(); + Node atom = (cmpKind == kind::LT || cmpKind == kind::LEQ) ? lit[0] : lit; + Polynomial left = Polynomial::parsePolynomial(atom[0]); + const Rational& q = atom[1].getConst(); + if(left.leadingCoefficientIsPositive()){ + return DeltaRational(q, delta); + }else{ + return DeltaRational(-q, -delta); + } + } + case kind::EQUAL: + case kind::DISTINCT: + { + Polynomial right = getRight(); + Monomial firstRight = right.getHead(); + if(firstRight.isConstant()){ + DeltaRational c = DeltaRational(firstRight.getConstant().getValue(), 0); + Polynomial left = getLeft(); + if(!left.allIntegralVariables()){ + return c; + //this is a qpolynomial and the sign of the leading + //coefficient will not change after the diff below + } else{ + // the polynomial may be a z polynomial in which case + // taking the diff is the simplest and obviously correct means + Polynomial diff = right.singleton() ? left : left - right.getTail(); + if(diff.leadingCoefficientIsPositive()){ + return c; + }else{ + return -c; + } + } + }else{ // The constant is 0 sign cannot change + return DeltaRational(0, 0); + } + } + default: Unhandled() << cmpKind; + } +} + +std::tuple Comparison::decompose( + bool split_constant) const +{ + Kind rel = getNode().getKind(); + if (rel == Kind::NOT) + { + switch (getNode()[0].getKind()) + { + case kind::LEQ: rel = Kind::GT; break; + case kind::LT: rel = Kind::GEQ; break; + case kind::EQUAL: rel = Kind::DISTINCT; break; + case kind::DISTINCT: rel = Kind::EQUAL; break; + case kind::GEQ: rel = Kind::LT; break; + case kind::GT: rel = Kind::LEQ; break; + default: + Assert(false) << "Unsupported relation: " << getNode()[0].getKind(); + } + } + + Polynomial poly = getLeft() - getRight(); + + if (!split_constant) + { + return std::tuple{ + poly, rel, Constant::mkZero()}; + } + + Constant right = Constant::mkZero(); + if (poly.containsConstant()) + { + right = -poly.getHead().getConstant(); + poly = poly + Polynomial::mkPolynomial(right); + } + + Constant lcoeff = poly.getHead().getConstant(); + if (!lcoeff.isOne()) + { + Constant invlcoeff = lcoeff.inverse(); + if (lcoeff.isNegative()) + { + switch (rel) + { + case kind::LEQ: rel = Kind::GEQ; break; + case kind::LT: rel = Kind::GT; break; + case kind::EQUAL: break; + case kind::DISTINCT: break; + case kind::GEQ: rel = Kind::LEQ; break; + case kind::GT: rel = Kind::LT; break; + default: Assert(false) << "Unsupported relation: " << rel; + } + } + poly = poly * invlcoeff; + right = right * invlcoeff; + } + + return std::tuple{poly, rel, right}; +} + +Comparison Comparison::parseNormalForm(TNode n) { + Trace("polynomial") << "Comparison::parseNormalForm(" << n << ")"; + Comparison result(n); + Assert(result.isNormalForm()); + return result; +} + +Node Comparison::toNode(Kind k, const Polynomial& l, const Constant& r) { + Assert(isRelationOperator(k)); + switch(k) { + case kind::GEQ: + case kind::GT: + return NodeManager::currentNM()->mkNode(k, l.getNode(), r.getNode()); + default: Unhandled() << k; + } +} + +Node Comparison::toNode(Kind k, const Polynomial& l, const Polynomial& r) { + Assert(isRelationOperator(k)); + switch(k) { + case kind::GEQ: + case kind::EQUAL: + case kind::GT: + return NodeManager::currentNM()->mkNode(k, l.getNode(), r.getNode()); + case kind::LEQ: + return toNode(kind::GEQ, r, l).notNode(); + case kind::LT: + return toNode(kind::GT, r, l).notNode(); + case kind::DISTINCT: + return toNode(kind::EQUAL, r, l).notNode(); + default: + Unreachable(); + } +} + +bool Comparison::rightIsConstant() const { + if(getNode().getKind() == kind::NOT){ + return getNode()[0][1].getKind() == kind::CONST_RATIONAL; + }else{ + return getNode()[1].getKind() == kind::CONST_RATIONAL; + } +} + +size_t Comparison::getComplexity() const{ + switch(comparisonKind()){ + case kind::CONST_BOOLEAN: return 1; + case kind::LT: + case kind::LEQ: + case kind::DISTINCT: + case kind::EQUAL: + case kind::GT: + case kind::GEQ: + return getLeft().getComplexity() + getRight().getComplexity(); + default: Unhandled() << comparisonKind(); return -1; + } +} + +Polynomial Comparison::getLeft() const { + TNode left; + Kind k = comparisonKind(); + switch(k){ + case kind::LT: + case kind::LEQ: + case kind::DISTINCT: + left = getNode()[0][0]; + break; + case kind::EQUAL: + case kind::GT: + case kind::GEQ: + left = getNode()[0]; + break; + default: Unhandled() << k; + } + return Polynomial::parsePolynomial(left); +} + +Polynomial Comparison::getRight() const { + TNode right; + Kind k = comparisonKind(); + switch(k){ + case kind::LT: + case kind::LEQ: + case kind::DISTINCT: + right = getNode()[0][1]; + break; + case kind::EQUAL: + case kind::GT: + case kind::GEQ: + right = getNode()[1]; + break; + default: Unhandled() << k; + } + return Polynomial::parsePolynomial(right); +} + +// Polynomial Comparison::getLeft() const { +// Node n = getNode(); +// Node left = (n.getKind() == kind::NOT ? n[0]: n)[0]; +// return Polynomial::parsePolynomial(left); +// } + +// Polynomial Comparison::getRight() const { +// Node n = getNode(); +// Node right = (n.getKind() == kind::NOT ? n[0]: n)[1]; +// return Polynomial::parsePolynomial(right); +// } + +bool Comparison::isNormalForm() const { + Node n = getNode(); + Kind cmpKind = comparisonKind(n); + Trace("nf::tmp") << "isNormalForm " << n << " " << cmpKind << endl; + switch(cmpKind){ + case kind::CONST_BOOLEAN: + return true; + case kind::GT: + return isNormalGT(); + case kind::GEQ: + return isNormalGEQ(); + case kind::EQUAL: + return isNormalEquality(); + case kind::LT: + return isNormalLT(); + case kind::LEQ: + return isNormalLEQ(); + case kind::DISTINCT: + return isNormalDistinct(); + default: + return false; + } +} + +/** This must be (> qpolynomial constant) */ +bool Comparison::isNormalGT() const { + Node n = getNode(); + Assert(n.getKind() == kind::GT); + if(!rightIsConstant()){ + return false; + }else{ + Polynomial left = getLeft(); + if(left.containsConstant()){ + return false; + }else if(!left.leadingCoefficientIsAbsOne()){ + return false; + }else{ + return !left.isIntegral(); + } + } +} + +/** This must be (not (> qpolynomial constant)) */ +bool Comparison::isNormalLEQ() const { + Node n = getNode(); + Trace("nf::tmp") << "isNormalLEQ " << n << endl; + Assert(n.getKind() == kind::NOT); + Assert(n[0].getKind() == kind::GT); + if(!rightIsConstant()){ + return false; + }else{ + Polynomial left = getLeft(); + if(left.containsConstant()){ + return false; + }else if(!left.leadingCoefficientIsAbsOne()){ + return false; + }else{ + return !left.isIntegral(); + } + } +} + + +/** This must be (>= qpolynomial constant) or (>= zpolynomial constant) */ +bool Comparison::isNormalGEQ() const { + Node n = getNode(); + Assert(n.getKind() == kind::GEQ); + + Trace("nf::tmp") << "isNormalGEQ " << n << " " << rightIsConstant() << endl; + + if(!rightIsConstant()){ + return false; + }else{ + Polynomial left = getLeft(); + if(left.containsConstant()){ + return false; + }else{ + if(left.isIntegral()){ + return left.signNormalizedReducedSum(); + }else{ + return left.leadingCoefficientIsAbsOne(); + } + } + } +} + +/** This must be (not (>= qpolynomial constant)) or (not (>= zpolynomial constant)) */ +bool Comparison::isNormalLT() const { + Node n = getNode(); + Assert(n.getKind() == kind::NOT); + Assert(n[0].getKind() == kind::GEQ); + + if(!rightIsConstant()){ + return false; + }else{ + Polynomial left = getLeft(); + if(left.containsConstant()){ + return false; + }else{ + if(left.isIntegral()){ + return left.signNormalizedReducedSum(); + }else{ + return left.leadingCoefficientIsAbsOne(); + } + } + } +} + + +bool Comparison::isNormalEqualityOrDisequality() const { + Polynomial pleft = getLeft(); + + if(pleft.numMonomials() == 1){ + Monomial mleft = pleft.getHead(); + if(mleft.isConstant()){ + return false; + }else{ + Polynomial pright = getRight(); + if(allIntegralVariables()){ + const Rational& lcoeff = mleft.getConstant().getValue(); + if(pright.isConstant()){ + return pright.isIntegral() && lcoeff.isOne(); + } + Polynomial varRight = pright.containsConstant() ? pright.getTail() : pright; + if(lcoeff.sgn() <= 0){ + return false; + }else{ + Integer lcm = lcoeff.getDenominator().lcm(varRight.denominatorLCM()); + Integer g = lcoeff.getNumerator().gcd(varRight.numeratorGCD()); + Trace("nf::tmp") << lcm << " " << g << endl; + if(!lcm.isOne()){ + return false; + }else if(!g.isOne()){ + return false; + }else{ + Monomial absMinRight = varRight.selectAbsMinimum(); + Trace("nf::tmp") << mleft.getNode() << " " << absMinRight.getNode() << endl; + if( mleft.absCmp(absMinRight) < 0){ + return true; + }else{ + return (!(absMinRight.absCmp(mleft)< 0)) && mleft < absMinRight; + } + } + } + }else{ + if(mleft.coefficientIsOne()){ + Trace("nf::tmp") + << "dfklj " << mleft.getNode() << endl + << pright.getNode() << endl + << pright.variableMonomialAreStrictlyGreater(mleft) + << endl; + return pright.variableMonomialAreStrictlyGreater(mleft); + }else{ + return false; + } + } + } + }else{ + return false; + } +} + +/** This must be (= qvarlist qpolynomial) or (= zmonomial zpolynomial)*/ +bool Comparison::isNormalEquality() const { + Assert(getNode().getKind() == kind::EQUAL); + return Theory::theoryOf(getNode()[0].getType()) == THEORY_ARITH && + isNormalEqualityOrDisequality(); +} + +/** + * This must be (not (= qvarlist qpolynomial)) or + * (not (= zmonomial zpolynomial)). + */ +bool Comparison::isNormalDistinct() const { + Assert(getNode().getKind() == kind::NOT); + Assert(getNode()[0].getKind() == kind::EQUAL); + + return Theory::theoryOf(getNode()[0][0].getType()) == THEORY_ARITH && + isNormalEqualityOrDisequality(); +} + +Node Comparison::mkRatEquality(const Polynomial& p){ + Assert(!p.isConstant()); + Assert(!p.allIntegralVariables()); + + Monomial minimalVList = p.minimumVariableMonomial(); + Constant coeffInv = -(minimalVList.getConstant().inverse()); + + Polynomial newRight = (p - minimalVList) * coeffInv; + Polynomial newLeft(Monomial::mkMonomial(minimalVList.getVarList())); + + return toNode(kind::EQUAL, newLeft, newRight); +} + +Node Comparison::mkRatInequality(Kind k, const Polynomial& p){ + Assert(k == kind::GEQ || k == kind::GT); + Assert(!p.isConstant()); + Assert(!p.allIntegralVariables()); + + SumPair sp = SumPair::mkSumPair(p); + Polynomial left = sp.getPolynomial(); + Constant right = - sp.getConstant(); + + Monomial minimalVList = left.getHead(); + Assert(!minimalVList.isConstant()); + + Constant coeffInv = minimalVList.getConstant().inverse().abs(); + Polynomial newLeft = left * coeffInv; + Constant newRight = right * (coeffInv); + + return toNode(k, newLeft, newRight); +} + +Node Comparison::mkIntInequality(Kind k, const Polynomial& p){ + Assert(kind::GT == k || kind::GEQ == k); + Assert(!p.isConstant()); + Assert(p.allIntegralVariables()); + + SumPair sp = SumPair::mkSumPair(p); + Polynomial left = sp.getPolynomial(); + Rational right = - (sp.getConstant().getValue()); + + + Monomial m = left.getHead(); + Assert(!m.isConstant()); + + Integer lcm = left.denominatorLCM(); + Integer g = left.numeratorGCD(); + Rational mult(lcm,g); + + Polynomial newLeft = left * mult; + Rational rightMult = right * mult; + + bool negateResult = false; + if(!newLeft.leadingCoefficientIsPositive()){ + // multiply by -1 + // a: left >= right or b: left > right + // becomes + // a: -left <= -right or b: -left < -right + // a: not (-left > -right) or b: (not -left >= -right) + newLeft = -newLeft; + rightMult = -rightMult; + k = (kind::GT == k) ? kind::GEQ : kind::GT; + negateResult = true; + // the later stages handle: + // a: not (-left >= -right + 1) or b: (not -left >= -right) + } + + Node result = Node::null(); + if(rightMult.isIntegral()){ + if(k == kind::GT){ + // (> p z) + // (>= p (+ z 1)) + Constant rightMultPlusOne = Constant::mkConstant(rightMult + 1); + result = toNode(kind::GEQ, newLeft, rightMultPlusOne); + }else{ + Constant newRight = Constant::mkConstant(rightMult); + result = toNode(kind::GEQ, newLeft, newRight); + } + }else{ + //(>= l (/ n d)) + //(>= l (ceil (/ n d))) + //This also hold for GT as (ceil (/ n d)) > (/ n d) + Integer ceilr = rightMult.ceiling(); + Constant ceilRight = Constant::mkConstant(ceilr); + result = toNode(kind::GEQ, newLeft, ceilRight); + } + Assert(!result.isNull()); + if(negateResult){ + return result.notNode(); + }else{ + return result; + } +} + +Node Comparison::mkIntEquality(const Polynomial& p){ + Assert(!p.isConstant()); + Assert(p.allIntegralVariables()); + + SumPair sp = SumPair::mkSumPair(p); + Polynomial varPart = sp.getPolynomial(); + Constant constPart = sp.getConstant(); + + Integer lcm = varPart.denominatorLCM(); + Integer g = varPart.numeratorGCD(); + Constant mult = Constant::mkConstant(Rational(lcm,g)); + + Constant constMult = constPart * mult; + + if(constMult.isIntegral()){ + Polynomial varPartMult = varPart * mult; + + Monomial m = varPartMult.selectAbsMinimum(); + bool mIsPositive = m.getConstant().isPositive(); + + Polynomial noM = (varPartMult + (- m)) + Polynomial::mkPolynomial(constMult); + + // m + noM = 0 + Polynomial newRight = mIsPositive ? -noM : noM; + Polynomial newLeft = mIsPositive ? m : -m; + + Assert(newRight.isIntegral()); + return toNode(kind::EQUAL, newLeft, newRight); + }else{ + return mkBoolNode(false); + } +} + +Comparison Comparison::mkComparison(Kind k, const Polynomial& l, const Polynomial& r){ + + //Make this special case fast for sharing! + if((k == kind::EQUAL || k == kind::DISTINCT) && l.isVarList() && r.isVarList()){ + VarList vLeft = l.asVarList(); + VarList vRight = r.asVarList(); + + if(vLeft == vRight){ + // return true for equalities and false for disequalities + return Comparison(k == kind::EQUAL); + }else{ + Node eqNode = vLeft < vRight ? toNode( kind::EQUAL, l, r) : toNode( kind::EQUAL, r, l); + Node forK = (k == kind::DISTINCT) ? eqNode.notNode() : eqNode; + return Comparison(forK); + } + } + + //General case + Polynomial diff = l - r; + if(diff.isConstant()){ + bool res = evaluateConstantPredicate(k, diff.asConstant(), Rational(0)); + return Comparison(res); + }else{ + Node result = Node::null(); + bool isInteger = diff.allIntegralVariables(); + switch(k){ + case kind::EQUAL: + result = isInteger ? mkIntEquality(diff) : mkRatEquality(diff); + break; + case kind::DISTINCT: + { + Node eq = isInteger ? mkIntEquality(diff) : mkRatEquality(diff); + result = eq.notNode(); + } + break; + case kind::LEQ: + case kind::LT: + { + Polynomial neg = - diff; + Kind negKind = (k == kind::LEQ ? kind::GEQ : kind::GT); + result = isInteger ? + mkIntInequality(negKind, neg) : mkRatInequality(negKind, neg); + } + break; + case kind::GEQ: + case kind::GT: + result = isInteger ? + mkIntInequality(k, diff) : mkRatInequality(k, diff); + break; + default: Unhandled() << k; + } + Assert(!result.isNull()); + if(result.getKind() == kind::NOT && result[0].getKind() == kind::CONST_BOOLEAN){ + return Comparison(!(result[0].getConst())); + }else{ + Comparison cmp(result); + Assert(cmp.isNormalForm()); + return cmp; + } + } +} + +bool Comparison::isBoolean() const { + return getNode().getKind() == kind::CONST_BOOLEAN; +} + + +bool Comparison::debugIsIntegral() const{ + return getLeft().isIntegral() && getRight().isIntegral(); +} + +Kind Comparison::comparisonKind(TNode literal){ + switch(literal.getKind()){ + case kind::CONST_BOOLEAN: + case kind::GT: + case kind::GEQ: + case kind::EQUAL: + return literal.getKind(); + case kind::NOT: + { + TNode negatedAtom = literal[0]; + switch(negatedAtom.getKind()){ + case kind::GT: //(not (GT x c)) <=> (LEQ x c) + return kind::LEQ; + case kind::GEQ: //(not (GEQ x c)) <=> (LT x c) + return kind::LT; + case kind::EQUAL: + return kind::DISTINCT; + default: + return kind::UNDEFINED_KIND; + } + } + default: + return kind::UNDEFINED_KIND; + } +} + + +Node Polynomial::makeAbsCondition(Variable v, Polynomial p){ + Polynomial zerop = Polynomial::mkZero(); + + Polynomial varp = Polynomial::mkPolynomial(v); + Comparison pLeq0 = Comparison::mkComparison(kind::LEQ, p, zerop); + Comparison negP = Comparison::mkComparison(kind::EQUAL, varp, -p); + Comparison posP = Comparison::mkComparison(kind::EQUAL, varp, p); + + Node absCnd = (pLeq0.getNode()).iteNode(negP.getNode(), posP.getNode()); + return absCnd; +} + +bool Polynomial::isNonlinear() const { + + for(iterator i=begin(), iend =end(); i != iend; ++i){ + Monomial m = *i; + if(m.isNonlinear()){ + return true; + } + } + return false; +} + +} //namespace arith +} //namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/normal_form.h b/src/theory/arith/linear/normal_form.h new file mode 100644 index 000000000..9656e2876 --- /dev/null +++ b/src/theory/arith/linear/normal_form.h @@ -0,0 +1,1466 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Morgan Deters + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NORMAL_FORM_H +#define CVC5__THEORY__ARITH__NORMAL_FORM_H + +#include + +#include "base/output.h" +#include "expr/node.h" +#include "expr/node_self_iterator.h" +#include "theory/arith/delta_rational.h" +#include "util/rational.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +/***********************************************/ +/***************** Normal Form *****************/ +/***********************************************/ +/***********************************************/ + +/** + * Section 1: Languages + * The normal form for arithmetic nodes is defined by the language + * accepted by the following BNFs with some guard conditions. + * (The guard conditions are in Section 3 for completeness.) + * + * variable := n + * where + * n.isVar() or is foreign + * n.getType() \in {Integer, Real} + * + * constant := n + * where + * n.getKind() == kind::CONST_RATIONAL + * + * var_list := variable | (* [variable]) + * where + * len [variable] >= 2 + * isSorted varOrder [variable] + * + * monomial := constant | var_list | (* constant' var_list') + * where + * \f$ constant' \not\in {0,1} \f$ + * + * polynomial := monomial' | (+ [monomial]) + * where + * len [monomial] >= 2 + * isStrictlySorted monoOrder [monomial] + * forall (\x -> x != 0) [monomial] + * + * rational_cmp := (|><| qpolynomial constant) + * where + * |><| is GEQ, or GT + * not (exists constantMonomial (monomialList qpolynomial)) + * (exists realMonomial (monomialList qpolynomial)) + * abs(monomialCoefficient (head (monomialList qpolynomial))) == 1 + * + * integer_cmp := (>= zpolynomial constant) + * where + * not (exists constantMonomial (monomialList zpolynomial)) + * (forall integerMonomial (monomialList zpolynomial)) + * the gcd of all numerators of coefficients is 1 + * the denominator of all coefficients and the constant is 1 + * the leading coefficient is positive + * + * rational_eq := (= qvarlist qpolynomial) + * where + * let allMonomials = (cons qvarlist (monomialList zpolynomial)) + * let variableMonomials = (drop constantMonomial allMonomials) + * isStrictlySorted variableMonomials + * exists realMonomial variableMonomials + * is not empty qvarlist + * + * integer_eq := (= zmonomial zpolynomial) + * where + * let allMonomials = (cons zmonomial (monomialList zpolynomial)) + * let variableMonomials = (drop constantMonomial allMonomials) + * not (constantMonomial zmonomial) + * (forall integerMonomial allMonomials) + * isStrictlySorted variableMonomials + * the gcd of all numerators of coefficients is 1 + * the denominator of all coefficients and the constant is 1 + * the coefficient of monomial is positive + * the value of the coefficient of monomial is minimal in variableMonomials + * + * comparison := TRUE | FALSE + * | rational_cmp | (not rational_cmp) + * | rational_eq | (not rational_eq) + * | integer_cmp | (not integer_cmp) + * | integer_eq | (not integer_eq) + * + * Normal Form for terms := polynomial + * Normal Form for atoms := comparison + */ + +/** + * Section 2: Helper Classes + * The langauges accepted by each of these defintions + * roughly corresponds to one of the following helper classes: + * Variable + * Constant + * VarList + * Monomial + * Polynomial + * Comparison + * + * Each of the classes obeys the following contracts/design decisions: + * -Calling isMember(Node node) on a node returns true iff that node is a + * a member of the language. Note: isMember is O(n). + * -Calling isNormalForm() on a helper class object returns true iff that + * helper class currently represents a normal form object. + * -If isNormalForm() is false, then this object must have been made + * using a mk*() factory function. + * -If isNormalForm() is true, calling getNode() on all of these classes + * returns a node that would be accepted by the corresponding language. + * And if isNormalForm() is false, returns Node::null(). + * -Each of the classes is immutable. + * -Public facing constuctors have a 1-to-1 correspondence with one of + * production rules in the above grammar. + * -Public facing constuctors are required to fail in debug mode when the + * guards of the production rule are not strictly met. + * For example: Monomial(Constant(1),VarList(Variable(x))) must fail. + * -When a class has a Class parseClass(Node node) function, + * if isMember(node) is true, the function is required to return an instance + * of the helper class, instance, s.t. instance.getNode() == node. + * And if isMember(node) is false, this throws an assertion failure in debug + * mode and has undefined behaviour if not in debug mode. + * -Only public facing constructors, parseClass(node), and mk*() functions are + * considered privileged functions for the helper class. + * -Only privileged functions may use private constructors, and access + * private data members. + * -All non-privileged functions are considered utility functions and + * must use a privileged function in order to create an instance of the class. + */ + +/** + * Section 3: Guard Conditions Misc. + * + * + * variable_order x y = + * if (meta_kind_variable x) and (meta_kind_variable y) + * then node_order x y + * else if (meta_kind_variable x) + * then false + * else if (meta_kind_variable y) + * then true + * else node_order x y + * + * var_list_len vl = + * match vl with + * variable -> 1 + * | (* [variable]) -> len [variable] + * + * order res = + * match res with + * Empty -> (0,Node::null()) + * | NonEmpty(vl) -> (var_list_len vl, vl) + * + * var_listOrder a b = tuple_cmp (order a) (order b) + * + * monomialVarList monomial = + * match monomial with + * constant -> Empty + * | var_list -> NonEmpty(var_list) + * | (* constant' var_list') -> NonEmpty(var_list') + * + * monoOrder m0 m1 = var_listOrder (monomialVarList m0) (monomialVarList m1) + * + * integerMonomial mono = + * forall varHasTypeInteger (monomialVarList mono) + * + * realMonomial mono = not (integerMonomial mono) + * + * constantMonomial monomial = + * match monomial with + * constant -> true + * | var_list -> false + * | (* constant' var_list') -> false + * + * monomialCoefficient monomial = + * match monomial with + * constant -> constant + * | var_list -> Constant(1) + * | (* constant' var_list') -> constant' + * + * monomialList polynomial = + * match polynomial with + * monomial -> monomial::[] + * | (+ [monomial]) -> [monomial] + */ + +/** + * A NodeWrapper is a class that is a thinly veiled container of a Node object. + */ +class NodeWrapper { +private: + Node node; +public: + NodeWrapper(Node n) : node(n) {} + const Node& getNode() const { return node; } +};/* class NodeWrapper */ + + +class Variable : public NodeWrapper { +public: + Variable(Node n) : NodeWrapper(n) { Assert(isMember(getNode())); } + + // TODO: check if it's a theory leaf also + static bool isMember(Node n) + { + Kind k = n.getKind(); + switch (k) + { + case kind::CONST_RATIONAL: return false; + case kind::INTS_DIVISION: + case kind::INTS_MODULUS: + case kind::DIVISION: + case kind::INTS_DIVISION_TOTAL: + case kind::INTS_MODULUS_TOTAL: + case kind::DIVISION_TOTAL: return isDivMember(n); + case kind::IAND: return isIAndMember(n); + case kind::POW2: return isPow2Member(n); + case kind::EXPONENTIAL: + case kind::SINE: + case kind::COSINE: + case kind::TANGENT: + case kind::COSECANT: + case kind::SECANT: + case kind::COTANGENT: + case kind::ARCSINE: + case kind::ARCCOSINE: + case kind::ARCTANGENT: + case kind::ARCCOSECANT: + case kind::ARCSECANT: + case kind::ARCCOTANGENT: + case kind::SQRT: + case kind::PI: return isTranscendentalMember(n); + case kind::ABS: + case kind::TO_INTEGER: + // Treat to_int as a variable; it is replaced in early preprocessing + // by a variable. + return true; + default: return isLeafMember(n); + } + } + + static bool isLeafMember(Node n); + static bool isIAndMember(Node n); + static bool isPow2Member(Node n); + static bool isDivMember(Node n); + bool isDivLike() const{ + return isDivMember(getNode()); + } + static bool isTranscendentalMember(Node n); + + bool isNormalForm() { return isMember(getNode()); } + + bool isIntegral() const { + return getNode().getType().isInteger(); + } + + bool isMetaKindVariable() const { + return getNode().isVar(); + } + + bool operator<(const Variable& v) const { + VariableNodeCmp cmp; + return cmp(this->getNode(), v.getNode()); + } + + struct VariableNodeCmp { + static inline int cmp(const Node& n, const Node& m) { + if ( n == m ) { return 0; } + + // RAN < real var < int var < non-variable + + bool nIsRAN = n.getKind() == Kind::REAL_ALGEBRAIC_NUMBER; + bool mIsRAN = m.getKind() == Kind::REAL_ALGEBRAIC_NUMBER; + + if (mIsRAN != nIsRAN) + { + return nIsRAN ? -1 : 1; + } + + bool nIsInteger = n.getType().isInteger(); + bool mIsInteger = m.getType().isInteger(); + + if(nIsInteger == mIsInteger){ + bool nIsVariable = n.isVar(); + bool mIsVariable = m.isVar(); + + if(nIsVariable == mIsVariable){ + if(n < m){ + return -1; + }else{ + Assert(n != m); + return 1; + } + }else{ + if(nIsVariable){ + return -1; // nIsVariable => !mIsVariable + }else{ + return 1; // !nIsVariable => mIsVariable + } + } + }else{ + Assert(nIsInteger != mIsInteger); + if(nIsInteger){ + return 1; // nIsInteger => !mIsInteger + }else{ + return -1; // !nIsInteger => mIsInteger + } + } + } + + bool operator()(const Node& n, const Node& m) const { + return VariableNodeCmp::cmp(n,m) < 0; + } + }; + + bool operator==(const Variable& v) const { return getNode() == v.getNode();} + + size_t getComplexity() const; +};/* class Variable */ + +class Constant : public NodeWrapper { +public: + Constant(Node n) : NodeWrapper(n) { Assert(isMember(getNode())); } + + static bool isMember(Node n) { return n.getKind() == kind::CONST_RATIONAL; } + + bool isNormalForm() { return isMember(getNode()); } + + static Constant mkConstant(Node n) + { + Assert(n.getKind() == kind::CONST_RATIONAL); + return Constant(n); + } + + static Constant mkConstant(const Rational& rat); + + static Constant mkZero() { + return mkConstant(Rational(0)); + } + + static Constant mkOne() { + return mkConstant(Rational(1)); + } + + const Rational& getValue() const { + return getNode().getConst(); + } + + static int absCmp(const Constant& a, const Constant& b); + bool isIntegral() const { return getValue().isIntegral(); } + + int sgn() const { return getValue().sgn(); } + + bool isZero() const { return sgn() == 0; } + bool isNegative() const { return sgn() < 0; } + bool isPositive() const { return sgn() > 0; } + + bool isOne() const { return getValue() == 1; } + + Constant operator*(const Rational& other) const { + return mkConstant(getValue() * other); + } + + Constant operator*(const Constant& other) const { + return mkConstant(getValue() * other.getValue()); + } + Constant operator+(const Constant& other) const { + return mkConstant(getValue() + other.getValue()); + } + Constant operator-() const { + return mkConstant(-getValue()); + } + + Constant inverse() const{ + Assert(!isZero()); + return mkConstant(getValue().inverse()); + } + + bool operator<(const Constant& other) const { + return getValue() < other.getValue(); + } + + bool operator==(const Constant& other) const { + //Rely on node uniqueness. + return getNode() == other.getNode(); + } + + Constant abs() const { + if(isNegative()){ + return -(*this); + }else{ + return (*this); + } + } + + uint32_t length() const{ + Assert(isIntegral()); + return getValue().getNumerator().length(); + } + + size_t getComplexity() const; + +};/* class Constant */ + + +template +inline Node makeNode(Kind k, GetNodeIterator start, GetNodeIterator end) { + NodeBuilder nb(k); + + while(start != end) { + nb << (*start).getNode(); + ++start; + } + + return Node(nb); +}/* makeNode(Kind, iterator, iterator) */ + +/** + * A VarList is a sorted list of variables representing a product. + * If the VarList is empty, it represents an empty product or 1. + * If the VarList has size 1, it represents a single variable. + * + * A non-sorted VarList can never be successfully made in debug mode. + */ +class VarList : public NodeWrapper { +private: + + static Node multList(const std::vector& list) { + Assert(list.size() >= 2); + + return makeNode(kind::NONLINEAR_MULT, list.begin(), list.end()); + } + + VarList() : NodeWrapper(Node::null()) {} + + VarList(Node n); + + typedef expr::NodeSelfIterator internal_iterator; + + internal_iterator internalBegin() const { + if(singleton()){ + return expr::NodeSelfIterator::self(getNode()); + }else{ + return getNode().begin(); + } + } + + internal_iterator internalEnd() const { + if(singleton()){ + return expr::NodeSelfIterator::selfEnd(getNode()); + }else{ + return getNode().end(); + } + } + +public: + + class iterator { + private: + internal_iterator d_iter; + + public: + /* The following types are required by trait std::iterator_traits */ + + /** Iterator tag */ + using iterator_category = std::forward_iterator_tag; + + /** The type of the item */ + using value_type = Variable; + + /** The pointer type of the item */ + using pointer = Variable*; + + /** The reference type of the item */ + using reference = Variable&; + + /** The type returned when two iterators are subtracted */ + using difference_type = std::ptrdiff_t; + + /* End of std::iterator_traits required types */ + + explicit iterator(internal_iterator i) : d_iter(i) {} + + inline Variable operator*() { + return Variable(*d_iter); + } + + bool operator==(const iterator& i) { + return d_iter == i.d_iter; + } + + bool operator!=(const iterator& i) { + return d_iter != i.d_iter; + } + + iterator operator++() { + ++d_iter; + return *this; + } + + iterator operator++(int) { + return iterator(d_iter++); + } + }; + + iterator begin() const { + return iterator(internalBegin()); + } + + iterator end() const { + return iterator(internalEnd()); + } + + Variable getHead() const { + Assert(!empty()); + return *(begin()); + } + + VarList(Variable v) : NodeWrapper(v.getNode()) { + Assert(isSorted(begin(), end())); + } + + VarList(const std::vector& l) : NodeWrapper(multList(l)) { + Assert(l.size() >= 2); + Assert(isSorted(begin(), end())); + } + + static bool isMember(Node n); + + bool isNormalForm() const { + return !empty(); + } + + static VarList mkEmptyVarList() { + return VarList(); + } + + + /** There are no restrictions on the size of l */ + static VarList mkVarList(const std::vector& l) { + if(l.size() == 0) { + return mkEmptyVarList(); + } else if(l.size() == 1) { + return VarList((*l.begin()).getNode()); + } else { + return VarList(l); + } + } + + bool empty() const { return getNode().isNull(); } + bool singleton() const { + return !empty() && getNode().getKind() != kind::NONLINEAR_MULT; + } + + int size() const { + if(singleton()) + return 1; + else + return getNode().getNumChildren(); + } + + static VarList parseVarList(Node n); + + VarList operator*(const VarList& vl) const; + + int cmp(const VarList& vl) const; + + bool operator<(const VarList& vl) const { return cmp(vl) < 0; } + + bool operator==(const VarList& vl) const { return cmp(vl) == 0; } + + bool isIntegral() const { + for(iterator i = begin(), e=end(); i != e; ++i ){ + Variable var = *i; + if(!var.isIntegral()){ + return false; + } + } + return true; + } + size_t getComplexity() const; + +private: + bool isSorted(iterator start, iterator end); + +};/* class VarList */ + + +/** Constructors have side conditions. Use the static mkMonomial functions instead. */ +class Monomial : public NodeWrapper { +private: + Constant constant; + VarList varList; + Monomial(Node n, const Constant& c, const VarList& vl): + NodeWrapper(n), constant(c), varList(vl) + { + Assert(!c.isZero() || vl.empty()); + Assert(c.isZero() || !vl.empty()); + + Assert(!c.isOne() || !multStructured(n)); + } + + static Node makeMultNode(const Constant& c, const VarList& vl) { + Assert(!c.isZero()); + Assert(!c.isOne()); + Assert(!vl.empty()); + return NodeManager::currentNM()->mkNode(kind::MULT, c.getNode(), vl.getNode()); + } + + static bool multStructured(Node n) { + return n.getKind() == kind::MULT && + n[0].getKind() == kind::CONST_RATIONAL && + n.getNumChildren() == 2; + } + + Monomial(const Constant& c): + NodeWrapper(c.getNode()), constant(c), varList(VarList::mkEmptyVarList()) + { } + + Monomial(const VarList& vl): + NodeWrapper(vl.getNode()), constant(Constant::mkConstant(1)), varList(vl) + { + Assert(!varList.empty()); + } + + Monomial(const Constant& c, const VarList& vl): + NodeWrapper(makeMultNode(c,vl)), constant(c), varList(vl) + { + Assert(!c.isZero()); + Assert(!c.isOne()); + Assert(!varList.empty()); + + Assert(multStructured(getNode())); + } +public: + static bool isMember(TNode n); + + /** Makes a monomial with no restrictions on c and vl. */ + static Monomial mkMonomial(const Constant& c, const VarList& vl); + + /** If vl is empty, this make one. */ + static Monomial mkMonomial(const VarList& vl); + + static Monomial mkMonomial(const Constant& c){ + return Monomial(c); + } + + static Monomial mkMonomial(const Variable& v){ + return Monomial(VarList(v)); + } + + static Monomial parseMonomial(Node n); + + static Monomial mkZero() { + return Monomial(Constant::mkConstant(0)); + } + static Monomial mkOne() { + return Monomial(Constant::mkConstant(1)); + } + const Constant& getConstant() const { return constant; } + const VarList& getVarList() const { return varList; } + + bool isConstant() const { + return varList.empty(); + } + + bool isZero() const { + return constant.isZero(); + } + + bool coefficientIsOne() const { + return constant.isOne(); + } + + bool absCoefficientIsOne() const { + return coefficientIsOne() || constant.getValue() == -1; + } + + bool constantIsPositive() const { + return getConstant().isPositive(); + } + + Monomial operator*(const Rational& q) const; + Monomial operator*(const Constant& c) const; + Monomial operator*(const Monomial& mono) const; + + Monomial operator-() const{ + return (*this) * Rational(-1); + } + + + int cmp(const Monomial& mono) const { + return getVarList().cmp(mono.getVarList()); + } + + bool operator<(const Monomial& vl) const { + return cmp(vl) < 0; + } + + bool operator==(const Monomial& vl) const { + return cmp(vl) == 0; + } + + static bool isSorted(const std::vector& m) { + return std::is_sorted(m.begin(), m.end()); + } + + static bool isStrictlySorted(const std::vector& m) { + return isSorted(m) && std::adjacent_find(m.begin(),m.end()) == m.end(); + } + + static void sort(std::vector& m); + static void combineAdjacentMonomials(std::vector& m); + + /** + * The variable product + */ + bool integralVariables() const { + return getVarList().isIntegral(); + } + + /** + * The coefficient of the monomial is integral. + */ + bool integralCoefficient() const { + return getConstant().isIntegral(); + } + + /** + * A Monomial is an "integral" monomial if the constant is integral. + */ + bool isIntegral() const { + return integralCoefficient() && integralVariables(); + } + + /** Returns true if the VarList is a product of at least 2 Variables.*/ + bool isNonlinear() const { + return getVarList().size() >= 2; + } + + /** + * Given a sorted list of monomials, this function transforms this + * into a strictly sorted list of monomials that does not contain zero. + */ + //static std::vector sumLikeTerms(const std::vector& monos); + + int absCmp(const Monomial& other) const{ + return getConstant().getValue().absCmp(other.getConstant().getValue()); + } + // bool absLessThan(const Monomial& other) const{ + // return getConstant().abs() < other.getConstant().abs(); + // } + + uint32_t coefficientLength() const{ + return getConstant().length(); + } + + void print() const; + static void printList(const std::vector& list); + + size_t getComplexity() const; +};/* class Monomial */ + +class SumPair; +class Comparison;; + +class Polynomial : public NodeWrapper { +private: + bool d_singleton; + + Polynomial(TNode n) : NodeWrapper(n), d_singleton(Monomial::isMember(n)) { + Assert(isMember(getNode())); + } + + static Node makePlusNode(const std::vector& m) { + Assert(m.size() >= 2); + + return makeNode(kind::ADD, m.begin(), m.end()); + } + + typedef expr::NodeSelfIterator internal_iterator; + + internal_iterator internalBegin() const { + if(singleton()){ + return expr::NodeSelfIterator::self(getNode()); + }else{ + return getNode().begin(); + } + } + + internal_iterator internalEnd() const { + if(singleton()){ + return expr::NodeSelfIterator::selfEnd(getNode()); + }else{ + return getNode().end(); + } + } + + bool singleton() const { return d_singleton; } + +public: + static bool isMember(TNode n); + + class iterator { + private: + internal_iterator d_iter; + + public: + /* The following types are required by trait std::iterator_traits */ + + /** Iterator tag */ + using iterator_category = std::forward_iterator_tag; + + /** The type of the item */ + using value_type = Monomial; + + /** The pointer type of the item */ + using pointer = Monomial*; + + /** The reference type of the item */ + using reference = Monomial&; + + /** The type returned when two iterators are subtracted */ + using difference_type = std::ptrdiff_t; + + /* End of std::iterator_traits required types */ + + explicit iterator(internal_iterator i) : d_iter(i) {} + + inline Monomial operator*() { + return Monomial::parseMonomial(*d_iter); + } + + bool operator==(const iterator& i) { + return d_iter == i.d_iter; + } + + bool operator!=(const iterator& i) { + return d_iter != i.d_iter; + } + + iterator operator++() { + ++d_iter; + return *this; + } + + iterator operator++(int) { + return iterator(d_iter++); + } + }; + + iterator begin() const { return iterator(internalBegin()); } + iterator end() const { return iterator(internalEnd()); } + + Polynomial(const Monomial& m): + NodeWrapper(m.getNode()), d_singleton(true) + {} + + Polynomial(const std::vector& m): + NodeWrapper(makePlusNode(m)), d_singleton(false) + { + Assert(m.size() >= 2); + Assert(Monomial::isStrictlySorted(m)); + } + + static Polynomial mkPolynomial(const Constant& c){ + return Polynomial(Monomial::mkMonomial(c)); + } + + static Polynomial mkPolynomial(const Variable& v){ + return Polynomial(Monomial::mkMonomial(v)); + } + + static Polynomial mkPolynomial(const std::vector& m) { + if(m.size() == 0) { + return Polynomial(Monomial::mkZero()); + } else if(m.size() == 1) { + return Polynomial((*m.begin())); + } else { + return Polynomial(m); + } + } + + static Polynomial parsePolynomial(Node n) { + return Polynomial(n); + } + + static Polynomial mkZero() { + return Polynomial(Monomial::mkZero()); + } + static Polynomial mkOne() { + return Polynomial(Monomial::mkOne()); + } + bool isZero() const { + return singleton() && (getHead().isZero()); + } + + bool isConstant() const { + return singleton() && (getHead().isConstant()); + } + + bool containsConstant() const { + return getHead().isConstant(); + } + + uint32_t size() const{ + if(singleton()){ + return 1; + }else{ + Assert(getNode().getKind() == kind::ADD); + return getNode().getNumChildren(); + } + } + + Monomial getHead() const { + return *(begin()); + } + + Polynomial getTail() const { + Assert(!singleton()); + + iterator tailStart = begin(); + ++tailStart; + std::vector subrange; + std::copy(tailStart, end(), std::back_inserter(subrange)); + return mkPolynomial(subrange); + } + + Monomial minimumVariableMonomial() const; + bool variableMonomialAreStrictlyGreater(const Monomial& m) const; + + void printList() const { + if(TraceIsOn("normal-form")){ + Trace("normal-form") << "start list" << std::endl; + for(iterator i = begin(), oend = end(); i != oend; ++i) { + const Monomial& m =*i; + m.print(); + } + Trace("normal-form") << "end list" << std::endl; + } + } + + /** A Polynomial is an "integral" polynomial if all of the monomials are integral. */ + bool allIntegralVariables() const { + for(iterator i = begin(), e=end(); i!=e; ++i){ + if(!(*i).integralVariables()){ + return false; + } + } + return true; + } + + /** + * A Polynomial is an "integral" polynomial if all of the monomials are integral + * and all of the coefficients are Integral. */ + bool isIntegral() const { + for(iterator i = begin(), e=end(); i!=e; ++i){ + if(!(*i).isIntegral()){ + return false; + } + } + return true; + } + + static Polynomial sumPolynomials(const std::vector& polynomials); + + /** Returns true if the polynomial contains a non-linear monomial.*/ + bool isNonlinear() const; + + /** Check whether this polynomial is only a single variable. */ + bool isVariable() const + { + return singleton() && getHead().getVarList().singleton() + && getHead().coefficientIsOne(); + } + /** Return the variable, given that isVariable() holds. */ + Variable getVariable() const + { + Assert(isVariable()); + return getHead().getVarList().getHead(); + } + + /** + * Selects a minimal monomial in the polynomial by the absolute value of + * the coefficient. + */ + Monomial selectAbsMinimum() const; + + /** Returns true if the absolute value of the head coefficient is one. */ + bool leadingCoefficientIsAbsOne() const; + bool leadingCoefficientIsPositive() const; + bool denominatorLCMIsOne() const; + bool numeratorGCDIsOne() const; + + bool signNormalizedReducedSum() const { + return leadingCoefficientIsPositive() && denominatorLCMIsOne() && numeratorGCDIsOne(); + } + + /** + * Returns the Least Common Multiple of the denominators of the coefficients + * of the monomials. + */ + Integer denominatorLCM() const; + + /** + * Returns the GCD of the numerators of the monomials. + * Requires this to be an isIntegral() polynomial. + */ + Integer numeratorGCD() const; + + /** + * Returns the GCD of the coefficients of the monomials. + * Requires this to be an isIntegral() polynomial. + */ + Integer gcd() const; + + /** z must divide all of the coefficients of the polynomial. */ + Polynomial exactDivide(const Integer& z) const; + + Polynomial operator+(const Polynomial& vl) const; + Polynomial operator-(const Polynomial& vl) const; + Polynomial operator-() const{ + return (*this) * Rational(-1); + } + + Polynomial operator*(const Rational& q) const; + Polynomial operator*(const Constant& c) const; + Polynomial operator*(const Monomial& mono) const; + + Polynomial operator*(const Polynomial& poly) const; + + /** + * Viewing the integer polynomial as a list [(* coeff_i mono_i)] + * The quotient and remainder of p divided by the non-zero integer z is: + * q := [(* floor(coeff_i/z) mono_i )] + * r := [(* rem(coeff_i/z) mono_i)] + * computeQR(p,z) returns the node (+ q r). + * + * q and r are members of the Polynomial class. + * For example: + * computeQR( p = (+ 5 (* 3 x) (* 8 y)) , z = 2) returns + * (+ (+ 2 x (* 4 y)) (+ 1 x)) + */ + static Node computeQR(const Polynomial& p, const Integer& z); + + /** Returns the coefficient associated with the VarList in the polynomial. */ + Constant getCoefficient(const VarList& vl) const; + + uint32_t maxLength() const{ + iterator i = begin(), e=end(); + if( i == e){ + return 1; + }else{ + uint32_t max = (*i).coefficientLength(); + ++i; + for(; i!=e; ++i){ + uint32_t curr = (*i).coefficientLength(); + if(curr > max){ + max = curr; + } + } + return max; + } + } + + uint32_t numMonomials() const { + if (getNode().getKind() == kind::ADD) + { + return getNode().getNumChildren(); + } + else if (isZero()) + { + return 0; + } + else + { + return 1; + } + } + + const Rational& asConstant() const{ + Assert(isConstant()); + return getNode().getConst(); + //return getHead().getConstant().getValue(); + } + + bool isVarList() const { + if(singleton()){ + return VarList::isMember(getNode()); + }else{ + return false; + } + } + + VarList asVarList() const { + Assert(isVarList()); + return getHead().getVarList(); + } + + size_t getComplexity() const; + + friend class SumPair; + friend class Comparison; + + /** Returns a node that if asserted ensures v is the abs of this polynomial.*/ + Node makeAbsCondition(Variable v){ + return makeAbsCondition(v, *this); + } + + /** Returns a node that if asserted ensures v is the abs of p.*/ + static Node makeAbsCondition(Variable v, Polynomial p); + +};/* class Polynomial */ + + +/** + * SumPair is a utility class that extends polynomials for use in computations. + * A SumPair is always a combination of (+ p c) where + * c is a constant and p is a polynomial such that p = 0 or !p.containsConstant(). + * + * These are a useful utility for representing the equation p = c as (+ p -c) where the pair + * is known to implicitly be equal to 0. + * + * SumPairs do not have unique representations due to the potential for p = 0. + * This makes them inappropriate for normal forms. + */ +class SumPair : public NodeWrapper { +private: + static Node toNode(const Polynomial& p, const Constant& c){ + return NodeManager::currentNM()->mkNode( + kind::ADD, p.getNode(), c.getNode()); + } + + SumPair(TNode n) : NodeWrapper(n) { Assert(isNormalForm()); } + + public: + SumPair(const Polynomial& p): + NodeWrapper(toNode(p, Constant::mkConstant(0))) + { + Assert(isNormalForm()); + } + + SumPair(const Polynomial& p, const Constant& c): + NodeWrapper(toNode(p, c)) + { + Assert(isNormalForm()); + } + + static bool isMember(TNode n) { + if (n.getKind() == kind::ADD && n.getNumChildren() == 2) + { + if(Constant::isMember(n[1])){ + if(Polynomial::isMember(n[0])){ + Polynomial p = Polynomial::parsePolynomial(n[0]); + return p.isZero() || (!p.containsConstant()); + }else{ + return false; + } + }else{ + return false; + } + } + else + { + return false; + } + } + + bool isNormalForm() const { + return isMember(getNode()); + } + + Polynomial getPolynomial() const { + return Polynomial::parsePolynomial(getNode()[0]); + } + + Constant getConstant() const { + return Constant::mkConstant((getNode())[1]); + } + + SumPair operator+(const SumPair& other) const { + return SumPair(getPolynomial() + other.getPolynomial(), + getConstant() + other.getConstant()); + } + + SumPair operator*(const Constant& c) const { + return SumPair(getPolynomial() * c, getConstant() * c); + } + + SumPair operator-(const SumPair& other) const { + return (*this) + (other * Constant::mkConstant(-1)); + } + + static SumPair mkSumPair(const Polynomial& p); + + static SumPair mkSumPair(const Variable& var){ + return SumPair(Polynomial::mkPolynomial(var)); + } + + static SumPair parseSumPair(TNode n){ + return SumPair(n); + } + + bool isIntegral() const{ + return getConstant().isIntegral() && getPolynomial().isIntegral(); + } + + bool isConstant() const { + return getPolynomial().isZero(); + } + + bool isZero() const { + return getConstant().isZero() && isConstant(); + } + + uint32_t size() const{ + return getPolynomial().size(); + } + + bool isNonlinear() const{ + return getPolynomial().isNonlinear(); + } + + /** + * Returns the greatest common divisor of gcd(getPolynomial()) and getConstant(). + * The SumPair must be integral. + */ + Integer gcd() const { + Assert(isIntegral()); + return (getPolynomial().gcd()).gcd(getConstant().getValue().getNumerator()); + } + + uint32_t maxLength() const { + Assert(isIntegral()); + return std::max(getPolynomial().maxLength(), getConstant().length()); + } + + static SumPair mkZero() { + return SumPair(Polynomial::mkZero(), Constant::mkConstant(0)); + } + + static Node computeQR(const SumPair& sp, const Integer& div); + +};/* class SumPair */ + +/* class OrderedPolynomialPair { */ +/* private: */ +/* Polynomial d_first; */ +/* Polynomial d_second; */ +/* public: */ +/* OrderedPolynomialPair(const Polynomial& f, const Polynomial& s) */ +/* : d_first(f), */ +/* d_second(s) */ +/* {} */ + +/* /\** Returns the first part of the pair. *\/ */ +/* const Polynomial& getFirst() const { */ +/* return d_first; */ +/* } */ + +/* /\** Returns the second part of the pair. *\/ */ +/* const Polynomial& getSecond() const { */ +/* return d_second; */ +/* } */ + +/* OrderedPolynomialPair operator*(const Constant& c) const; */ +/* OrderedPolynomialPair operator+(const Polynomial& p) const; */ + +/* /\** Returns true if both of the polynomials are constant. *\/ */ +/* bool isConstant() const; */ + +/* /\** */ +/* * Evaluates an isConstant() ordered pair as if */ +/* * (k getFirst() getRight()) */ +/* *\/ */ +/* bool evaluateConstant(Kind k) const; */ + +/* /\** */ +/* * Returns the Least Common Multiple of the monomials */ +/* * on the lefthand side and the constant on the right. */ +/* *\/ */ +/* Integer denominatorLCM() const; */ + +/* /\** Constructs a SumPair. *\/ */ +/* SumPair toSumPair() const; */ + + +/* OrderedPolynomialPair divideByGCD() const; */ +/* OrderedPolynomialPair multiplyConstant(const Constant& c) const; */ + +/* /\** */ +/* * Returns true if all of the variables are integers, */ +/* * and the coefficients are integers. */ +/* *\/ */ +/* bool isIntegral() const; */ + +/* /\** Returns true if all of the variables are integers. *\/ */ +/* bool allIntegralVariables() const { */ +/* return getFirst().allIntegralVariables() && getSecond().allIntegralVariables(); */ +/* } */ +/* }; */ + +class Comparison : public NodeWrapper { +private: + + static Node toNode(Kind k, const Polynomial& l, const Constant& c); + static Node toNode(Kind k, const Polynomial& l, const Polynomial& r); + + Comparison(TNode n); + + /** + * Creates a node in normal form equivalent to (= l 0). + * All variables in l are integral. + */ + static Node mkIntEquality(const Polynomial& l); + + /** + * Creates a comparison equivalent to (k l 0). + * k is either GT or GEQ. + * All variables in l are integral. + */ + static Node mkIntInequality(Kind k, const Polynomial& l); + + /** + * Creates a node equivalent to (= l 0). + * It is not the case that all variables in l are integral. + */ + static Node mkRatEquality(const Polynomial& l); + + /** + * Creates a comparison equivalent to (k l 0). + * k is either GT or GEQ. + * It is not the case that all variables in l are integral. + */ + static Node mkRatInequality(Kind k, const Polynomial& l); + +public: + + Comparison(bool val) : + NodeWrapper(NodeManager::currentNM()->mkConst(val)) + { } + + /** + * Given a literal to TheoryArith return a single kind to + * to indicate its underlying structure. + * The function returns the following in each case: + * - (K left right) -> K where is either EQUAL, GT, or GEQ + * - (CONST_BOOLEAN b) -> CONST_BOOLEAN + * - (NOT (EQUAL left right)) -> DISTINCT + * - (NOT (GT left right)) -> LEQ + * - (NOT (GEQ left right)) -> LT + * If none of these match, it returns UNDEFINED_KIND. + */ + static Kind comparisonKind(TNode literal); + + Kind comparisonKind() const { return comparisonKind(getNode()); } + + static Comparison mkComparison(Kind k, const Polynomial& l, const Polynomial& r); + + /** Returns true if the comparison is a boolean constant. */ + bool isBoolean() const; + + /** + * Returns true if the comparison is either a boolean term, + * in integer normal form or mixed normal form. + */ + bool isNormalForm() const; + +private: + bool isNormalGT() const; + bool isNormalGEQ() const; + + bool isNormalLT() const; + bool isNormalLEQ() const; + + bool isNormalEquality() const; + bool isNormalDistinct() const; + bool isNormalEqualityOrDisequality() const; + + bool allIntegralVariables() const { + return getLeft().allIntegralVariables() && getRight().allIntegralVariables(); + } + bool rightIsConstant() const; + +public: + Polynomial getLeft() const; + Polynomial getRight() const; + + /* /\** Normal form check if at least one variable is real. *\/ */ + /* bool isMixedCompareNormalForm() const; */ + + /* /\** Normal form check if at least one variable is real. *\/ */ + /* bool isMixedEqualsNormalForm() const; */ + + /* /\** Normal form check is all variables are integer.*\/ */ + /* bool isIntegerCompareNormalForm() const; */ + + /* /\** Normal form check is all variables are integer.*\/ */ + /* bool isIntegerEqualsNormalForm() const; */ + + + /** + * Returns true if all of the variables are integers, the coefficients are integers, + * and the right hand coefficient is an integer. + */ + bool debugIsIntegral() const; + + static Comparison parseNormalForm(TNode n); + + inline static bool isNormalAtom(TNode n){ + Comparison parse = Comparison::parseNormalForm(n); + return parse.isNormalForm(); + } + + size_t getComplexity() const; + + SumPair toSumPair() const; + + Polynomial normalizedVariablePart() const; + DeltaRational normalizedDeltaRational() const; + + /** + * Transforms a Comparison object into a stronger normal form: + * Polynomial ~Kind~ Constant + * + * From the comparison, this method resolved a negation (if present) and + * moves everything to the left side. + * If split_constant is false, the constant is always zero. + * If split_constant is true, the polynomial has no constant term and is + * normalized to have leading coefficient one. + */ + std::tuple decompose( + bool split_constant = false) const; + +};/* class Comparison */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__NORMAL_FORM_H */ diff --git a/src/theory/arith/linear/partial_model.cpp b/src/theory/arith/linear/partial_model.cpp new file mode 100644 index 000000000..58beca87f --- /dev/null +++ b/src/theory/arith/linear/partial_model.cpp @@ -0,0 +1,691 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "base/output.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/normal_form.h" +#include "theory/arith/linear/partial_model.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +ArithVariables::ArithVariables(context::Context* c, + DeltaComputeCallback deltaComputingFunc) + : d_vars(), + d_safeAssignment(), + d_numberOfVariables(0), + d_pool(), + d_released(), + d_nodeToArithVarMap(), + d_boundsQueue(), + d_enqueueingBoundCounts(true), + d_lbRevertHistory(c, true, LowerBoundCleanUp(this)), + d_ubRevertHistory(c, true, UpperBoundCleanUp(this)), + d_deltaIsSafe(false), + d_delta(-1, 1), + d_deltaComputingFunc(deltaComputingFunc) +{ } + +ArithVar ArithVariables::getNumberOfVariables() const { + return d_numberOfVariables; +} + + +bool ArithVariables::hasArithVar(TNode x) const { + return d_nodeToArithVarMap.find(x) != d_nodeToArithVarMap.end(); +} + +bool ArithVariables::hasNode(ArithVar a) const { + return d_vars.isKey(a); +} + +ArithVar ArithVariables::asArithVar(TNode x) const{ + Assert(hasArithVar(x)); + Assert((d_nodeToArithVarMap.find(x))->second <= ARITHVAR_SENTINEL); + return (d_nodeToArithVarMap.find(x))->second; +} + +Node ArithVariables::asNode(ArithVar a) const{ + Assert(hasNode(a)); + return d_vars[a].d_node; +} + +ArithVariables::var_iterator::var_iterator() + : d_vars(NULL) + , d_wrapped() +{} + +ArithVariables::var_iterator::var_iterator(const VarInfoVec* vars, VarInfoVec::const_iterator ci) + : d_vars(vars), d_wrapped(ci) +{ + nextInitialized(); +} + +ArithVariables::var_iterator& ArithVariables::var_iterator::operator++(){ + ++d_wrapped; + nextInitialized(); + return *this; +} +bool ArithVariables::var_iterator::operator==(const ArithVariables::var_iterator& other) const{ + return d_wrapped == other.d_wrapped; +} +bool ArithVariables::var_iterator::operator!=(const ArithVariables::var_iterator& other) const{ + return d_wrapped != other.d_wrapped; +} +ArithVar ArithVariables::var_iterator::operator*() const{ + return *d_wrapped; +} + +void ArithVariables::var_iterator::nextInitialized(){ + VarInfoVec::const_iterator end = d_vars->end(); + while(d_wrapped != end && + !((*d_vars)[*d_wrapped].initialized())){ + ++d_wrapped; + } +} + +ArithVariables::var_iterator ArithVariables::var_begin() const { + return var_iterator(&d_vars, d_vars.begin()); +} + +ArithVariables::var_iterator ArithVariables::var_end() const { + return var_iterator(&d_vars, d_vars.end()); +} +bool ArithVariables::isInteger(ArithVar x) const { + return d_vars[x].d_type >= ArithType::Integer; +} + +/** Is the assignment to x integral? */ +bool ArithVariables::integralAssignment(ArithVar x) const { + return getAssignment(x).isIntegral(); +} +bool ArithVariables::isAuxiliary(ArithVar x) const { + return d_vars[x].d_auxiliary; +} + +bool ArithVariables::isIntegerInput(ArithVar x) const { + return isInteger(x) && !isAuxiliary(x); +} + +ArithVariables::VarInfo::VarInfo() + : d_var(ARITHVAR_SENTINEL), + d_assignment(0), + d_lb(NullConstraint), + d_ub(NullConstraint), + d_cmpAssignmentLB(1), + d_cmpAssignmentUB(-1), + d_pushCount(0), + d_type(ArithType::Unset), + d_node(Node::null()), + d_auxiliary(false) {} + +bool ArithVariables::VarInfo::initialized() const { + return d_var != ARITHVAR_SENTINEL; +} + +void ArithVariables::VarInfo::initialize(ArithVar v, Node n, bool aux){ + Assert(!initialized()); + Assert(d_lb == NullConstraint); + Assert(d_ub == NullConstraint); + Assert(d_cmpAssignmentLB > 0); + Assert(d_cmpAssignmentUB < 0); + d_var = v; + d_node = n; + d_auxiliary = aux; + + if(d_auxiliary){ + //The type computation is not quite accurate for Rationals that are + //integral. + //We'll use the isIntegral check from the polynomial package instead. + Polynomial p = Polynomial::parsePolynomial(n); + d_type = p.isIntegral() ? ArithType::Integer : ArithType::Real; + }else{ + d_type = n.getType().isInteger() ? ArithType::Integer : ArithType::Real; + } + + Assert(initialized()); +} + +void ArithVariables::VarInfo::uninitialize(){ + d_var = ARITHVAR_SENTINEL; + d_node = Node::null(); +} + +bool ArithVariables::VarInfo::setAssignment(const DeltaRational& a, BoundsInfo& prev){ + Assert(initialized()); + d_assignment = a; + int cmpUB = (d_ub == NullConstraint) ? -1 : + d_assignment.cmp(d_ub->getValue()); + + int cmpLB = (d_lb == NullConstraint) ? 1 : + d_assignment.cmp(d_lb->getValue()); + + bool lbChanged = cmpLB != d_cmpAssignmentLB && + (cmpLB == 0 || d_cmpAssignmentLB == 0); + bool ubChanged = cmpUB != d_cmpAssignmentUB && + (cmpUB == 0 || d_cmpAssignmentUB == 0); + + if(lbChanged || ubChanged){ + prev = boundsInfo(); + } + + d_cmpAssignmentUB = cmpUB; + d_cmpAssignmentLB = cmpLB; + return lbChanged || ubChanged; +} + +void ArithVariables::releaseArithVar(ArithVar v){ + VarInfo& vi = d_vars.get(v); + + size_t removed CVC5_UNUSED = d_nodeToArithVarMap.erase(vi.d_node); + Assert(removed == 1); + + vi.uninitialize(); + + if(d_safeAssignment.isKey(v)){ + d_safeAssignment.remove(v); + } + if(vi.canBeReclaimed()){ + d_pool.push_back(v); + }else{ + d_released.push_back(v); + } +} + +bool ArithVariables::VarInfo::setUpperBound(ConstraintP ub, BoundsInfo& prev){ + Assert(initialized()); + bool wasNull = d_ub == NullConstraint; + bool isNull = ub == NullConstraint; + + int cmpUB = isNull ? -1 : d_assignment.cmp(ub->getValue()); + bool ubChanged = (wasNull != isNull) || + (cmpUB != d_cmpAssignmentUB && (cmpUB == 0 || d_cmpAssignmentUB == 0)); + if(ubChanged){ + prev = boundsInfo(); + } + d_ub = ub; + d_cmpAssignmentUB = cmpUB; + return ubChanged; +} + +bool ArithVariables::VarInfo::setLowerBound(ConstraintP lb, BoundsInfo& prev){ + Assert(initialized()); + bool wasNull = d_lb == NullConstraint; + bool isNull = lb == NullConstraint; + + int cmpLB = isNull ? 1 : d_assignment.cmp(lb->getValue()); + + bool lbChanged = (wasNull != isNull) || + (cmpLB != d_cmpAssignmentLB && (cmpLB == 0 || d_cmpAssignmentLB == 0)); + if(lbChanged){ + prev = boundsInfo(); + } + d_lb = lb; + d_cmpAssignmentLB = cmpLB; + return lbChanged; +} + +BoundCounts ArithVariables::VarInfo::atBoundCounts() const { + uint32_t lbIndc = (d_cmpAssignmentLB == 0) ? 1 : 0; + uint32_t ubIndc = (d_cmpAssignmentUB == 0) ? 1 : 0; + return BoundCounts(lbIndc, ubIndc); +} + +BoundCounts ArithVariables::VarInfo::hasBoundCounts() const { + uint32_t lbIndc = (d_lb != NullConstraint) ? 1 : 0; + uint32_t ubIndc = (d_ub != NullConstraint) ? 1 : 0; + return BoundCounts(lbIndc, ubIndc); +} + +BoundsInfo ArithVariables::VarInfo::boundsInfo() const{ + return BoundsInfo(atBoundCounts(), hasBoundCounts()); +} + +bool ArithVariables::VarInfo::canBeReclaimed() const{ + return d_pushCount == 0; +} + +bool ArithVariables::canBeReleased(ArithVar v) const{ + return d_vars[v].canBeReclaimed(); +} + +void ArithVariables::attemptToReclaimReleased(){ + size_t readPos = 0, writePos = 0, N = d_released.size(); + for(; readPos < N; ++readPos){ + ArithVar v = d_released[readPos]; + if(canBeReleased(v)){ + d_pool.push_back(v); + }else{ + d_released[writePos] = v; + writePos++; + } + } + d_released.resize(writePos); +} + +ArithVar ArithVariables::allocateVariable(){ + if(d_pool.empty()){ + attemptToReclaimReleased(); + } + bool reclaim = !d_pool.empty(); + + ArithVar varX; + if(reclaim){ + varX = d_pool.back(); + d_pool.pop_back(); + }else{ + varX = d_numberOfVariables; + ++d_numberOfVariables; + } + d_vars.set(varX, VarInfo()); + return varX; +} + + +const Rational& ArithVariables::getDelta(){ + if(!d_deltaIsSafe){ + Rational nextDelta = d_deltaComputingFunc(); + setDelta(nextDelta); + } + Assert(d_deltaIsSafe); + return d_delta; +} + +bool ArithVariables::boundsAreEqual(ArithVar x) const{ + if(hasLowerBound(x) && hasUpperBound(x)){ + return getUpperBound(x) == getLowerBound(x); + }else{ + return false; + } +} + + +std::pair ArithVariables::explainEqualBounds(ArithVar x) const{ + Assert(boundsAreEqual(x)); + + ConstraintP lb = getLowerBoundConstraint(x); + ConstraintP ub = getUpperBoundConstraint(x); + if(lb->isEquality()){ + return make_pair(lb, NullConstraint); + }else if(ub->isEquality()){ + return make_pair(ub, NullConstraint); + }else{ + return make_pair(lb, ub); + } +} + +void ArithVariables::setAssignment(ArithVar x, const DeltaRational& r){ + Trace("partial_model") << "pm: updating the assignment to" << x + << " now " << r <getValue(); +} + +const DeltaRational& ArithVariables::getLowerBound(ArithVar x) const { + Assert(inMaps(x)); + Assert(hasLowerBound(x)); + + return getLowerBoundConstraint(x)->getValue(); +} + +const DeltaRational& ArithVariables::getSafeAssignment(ArithVar x) const{ + Assert(inMaps(x)); + if(d_safeAssignment.isKey(x)){ + return d_safeAssignment[x]; + }else{ + return d_vars[x].d_assignment; + } +} + +const DeltaRational& ArithVariables::getAssignment(ArithVar x, bool safe) const{ + Assert(inMaps(x)); + if(safe && d_safeAssignment.isKey(x)){ + return d_safeAssignment[x]; + }else{ + return d_vars[x].d_assignment; + } +} + +const DeltaRational& ArithVariables::getAssignment(ArithVar x) const{ + Assert(inMaps(x)); + return d_vars[x].d_assignment; +} + + +void ArithVariables::setLowerBoundConstraint(ConstraintP c){ + AssertArgument(c != NullConstraint, "Cannot set a lower bound to NullConstraint."); + AssertArgument(c->isEquality() || c->isLowerBound(), + "Constraint type must be set to an equality or UpperBound."); + ArithVar x = c->getVariable(); + Trace("partial_model") << "setLowerBoundConstraint(" << x << ":" << c << ")" << endl; + Assert(inMaps(x)); + Assert(greaterThanLowerBound(x, c->getValue())); + + invalidateDelta(); + VarInfo& vi = d_vars.get(x); + pushLowerBound(vi); + BoundsInfo prev; + if(vi.setLowerBound(c, prev)){ + addToBoundQueue(x, prev); + } +} + +void ArithVariables::setUpperBoundConstraint(ConstraintP c){ + AssertArgument(c != NullConstraint, "Cannot set a upper bound to NullConstraint."); + AssertArgument(c->isEquality() || c->isUpperBound(), + "Constraint type must be set to an equality or UpperBound."); + + ArithVar x = c->getVariable(); + Trace("partial_model") << "setUpperBoundConstraint(" << x << ":" << c << ")" << endl; + Assert(inMaps(x)); + Assert(lessThanUpperBound(x, c->getValue())); + + invalidateDelta(); + VarInfo& vi = d_vars.get(x); + pushUpperBound(vi); + BoundsInfo prev; + if(vi.setUpperBound(c, prev)){ + addToBoundQueue(x, prev); + } +} + +int ArithVariables::cmpToLowerBound(ArithVar x, const DeltaRational& c) const{ + if(!hasLowerBound(x)){ + // l = -\intfy + // ? c < -\infty |- _|_ + return 1; + }else{ + return c.cmp(getLowerBound(x)); + } +} + +int ArithVariables::cmpToUpperBound(ArithVar x, const DeltaRational& c) const{ + if(!hasUpperBound(x)){ + //u = \intfy + // ? c > \infty |- _|_ + return -1; + }else{ + return c.cmp(getUpperBound(x)); + } +} + +bool ArithVariables::equalsLowerBound(ArithVar x, const DeltaRational& c){ + if(!hasLowerBound(x)){ + return false; + }else{ + return c == getLowerBound(x); + } +} +bool ArithVariables::equalsUpperBound(ArithVar x, const DeltaRational& c){ + if(!hasUpperBound(x)){ + return false; + }else{ + return c == getUpperBound(x); + } +} + +bool ArithVariables::hasEitherBound(ArithVar x) const{ + return hasLowerBound(x) || hasUpperBound(x); +} + +bool ArithVariables::strictlyBelowUpperBound(ArithVar x) const{ + return d_vars[x].d_cmpAssignmentUB < 0; +} + +bool ArithVariables::strictlyAboveLowerBound(ArithVar x) const{ + return d_vars[x].d_cmpAssignmentLB > 0; +} + +bool ArithVariables::assignmentIsConsistent(ArithVar x) const{ + return + d_vars[x].d_cmpAssignmentLB >= 0 && + d_vars[x].d_cmpAssignmentUB <= 0; +} + + +void ArithVariables::clearSafeAssignments(bool revert){ + + if(revert && !d_safeAssignment.empty()){ + invalidateDelta(); + } + + while(!d_safeAssignment.empty()){ + ArithVar atBack = d_safeAssignment.back(); + if(revert){ + VarInfo& vi = d_vars.get(atBack); + BoundsInfo prev; + if(vi.setAssignment(d_safeAssignment[atBack], prev)){ + addToBoundQueue(atBack, prev); + } + } + d_safeAssignment.pop_back(); + } +} + +void ArithVariables::revertAssignmentChanges(){ + clearSafeAssignments(true); +} +void ArithVariables::commitAssignmentChanges(){ + clearSafeAssignments(false); +} + +bool ArithVariables::lowerBoundIsZero(ArithVar x){ + return hasLowerBound(x) && getLowerBound(x).sgn() == 0; +} + +bool ArithVariables::upperBoundIsZero(ArithVar x){ + return hasUpperBound(x) && getUpperBound(x).sgn() == 0; +} + +void ArithVariables::printEntireModel(std::ostream& out) const{ + out << "---Printing Model ---" << std::endl; + for(var_iterator i = var_begin(), iend = var_end(); i != iend; ++i){ + printModel(*i, out); + } + out << "---Done Model ---" << std::endl; +} + +void ArithVariables::printModel(ArithVar x, std::ostream& out) const{ + out << "model" << x << ": " + << asNode(x) << " " + << getAssignment(x) << " "; + if(!hasLowerBound(x)){ + out << "no lb "; + }else{ + out << getLowerBound(x) << " "; + out << getLowerBoundConstraint(x) << " "; + } + if(!hasUpperBound(x)){ + out << "no ub "; + }else{ + out << getUpperBound(x) << " "; + out << getUpperBoundConstraint(x) << " "; + } + + if(isInteger(x) && !integralAssignment(x)){ + out << "(not an integer)" << endl; + } + out << endl; +} + +void ArithVariables::printModel(ArithVar x) const{ + printModel(x, Trace("model")); +} + +void ArithVariables::pushUpperBound(VarInfo& vi){ + ++vi.d_pushCount; + d_ubRevertHistory.push_back(make_pair(vi.d_var, vi.d_ub)); +} +void ArithVariables::pushLowerBound(VarInfo& vi){ + ++vi.d_pushCount; + d_lbRevertHistory.push_back(make_pair(vi.d_var, vi.d_lb)); +} + +void ArithVariables::popUpperBound(AVCPair* c){ + ArithVar x = c->first; + VarInfo& vi = d_vars.get(x); + BoundsInfo prev; + if(vi.setUpperBound(c->second, prev)){ + addToBoundQueue(x, prev); + } + --vi.d_pushCount; +} + +void ArithVariables::popLowerBound(AVCPair* c){ + ArithVar x = c->first; + VarInfo& vi = d_vars.get(x); + BoundsInfo prev; + if(vi.setLowerBound(c->second, prev)){ + addToBoundQueue(x, prev); + } + --vi.d_pushCount; +} + +void ArithVariables::addToBoundQueue(ArithVar v, const BoundsInfo& prev){ + if(d_enqueueingBoundCounts && !d_boundsQueue.isKey(v)){ + d_boundsQueue.set(v, prev); + } +} + +BoundsInfo ArithVariables::selectBoundsInfo(ArithVar v, bool old) const { + if(old && d_boundsQueue.isKey(v)){ + return d_boundsQueue[v]; + }else{ + return boundsInfo(v); + } +} + +bool ArithVariables::boundsQueueEmpty() const { + return d_boundsQueue.empty(); +} + +void ArithVariables::processBoundsQueue(BoundUpdateCallback& changed){ + while(!boundsQueueEmpty()){ + ArithVar v = d_boundsQueue.back(); + BoundsInfo prev = d_boundsQueue[v]; + d_boundsQueue.pop_back(); + BoundsInfo curr = boundsInfo(v); + if(prev != curr){ + changed(v, prev); + } + } +} + +void ArithVariables::invalidateDelta() { + d_deltaIsSafe = false; +} + +void ArithVariables::setDelta(const Rational& d){ + d_delta = d; + d_deltaIsSafe = true; +} + +void ArithVariables::startQueueingBoundCounts(){ + d_enqueueingBoundCounts = true; +} +void ArithVariables::stopQueueingBoundCounts(){ + d_enqueueingBoundCounts = false; +} + +bool ArithVariables::inMaps(ArithVar x) const{ + return x < getNumberOfVariables(); +} + +ArithVariables::LowerBoundCleanUp::LowerBoundCleanUp(ArithVariables* pm) + : d_pm(pm) +{} +void ArithVariables::LowerBoundCleanUp::operator()(AVCPair* p){ + d_pm->popLowerBound(p); +} + +ArithVariables::UpperBoundCleanUp::UpperBoundCleanUp(ArithVariables* pm) + : d_pm(pm) +{} +void ArithVariables::UpperBoundCleanUp::operator()(AVCPair* p){ + d_pm->popUpperBound(p); +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/partial_model.h b/src/theory/arith/linear/partial_model.h new file mode 100644 index 000000000..349478b69 --- /dev/null +++ b/src/theory/arith/linear/partial_model.h @@ -0,0 +1,421 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Morgan Deters, Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * Datastructures that track variable by variable information. + * + * This is a datastructure that tracks variable specific information. + * This is partially context dependent to back track upper/lower bounds + * and information derived from these. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__PARTIAL_MODEL_H +#define CVC5__THEORY__ARITH__PARTIAL_MODEL_H + +#include + +#include "context/cdlist.h" +#include "expr/node.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/arithvar_node_map.h" +#include "theory/arith/linear/bound_counts.h" +#include "theory/arith/linear/callbacks.h" +#include "theory/arith/linear/constraint_forward.h" +#include "theory/arith/delta_rational.h" + +namespace cvc5::context { +class Context; +} +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +/** + * (For the moment) the type hierarchy goes as: + * Integer <: Real + * The type number of a variable is an integer representing the most specific + * type of the variable. The possible values of type number are: + */ +enum class ArithType { + Unset, + Real, + Integer, +}; + +class ArithVariables { +private: + + class VarInfo { + friend class ArithVariables; + ArithVar d_var; + + DeltaRational d_assignment; + ConstraintP d_lb; + ConstraintP d_ub; + int d_cmpAssignmentLB; + int d_cmpAssignmentUB; + + unsigned d_pushCount; + ArithType d_type; + Node d_node; + bool d_auxiliary; + + public: + VarInfo(); + + bool setAssignment(const DeltaRational& r, BoundsInfo& prev); + bool setLowerBound(ConstraintP c, BoundsInfo& prev); + bool setUpperBound(ConstraintP c, BoundsInfo& prev); + + /** Returns true if this VarInfo has been initialized. */ + bool initialized() const; + + /** + * Initializes the VarInfo with the ArithVar index it is associated with, + * the node that the variable represents, and whether it is an auxillary + * variable. + */ + void initialize(ArithVar v, Node n, bool aux); + + /** Uninitializes the VarInfo. */ + void uninitialize(); + + bool canBeReclaimed() const; + + /** Indicator variables for if the assignment is equal to the upper + * and lower bounds. */ + BoundCounts atBoundCounts() const; + + /** Combination of indicator variables for whether it has upper and + * lower bounds. */ + BoundCounts hasBoundCounts() const; + + /** Stores both atBoundCounts() and hasBoundCounts(). */ + BoundsInfo boundsInfo() const; + }; + + /**Maps from ArithVar -> VarInfo */ + typedef DenseMap VarInfoVec; + + /** This maps an ArithVar to its Variable information.*/ + VarInfoVec d_vars; + + /** Partial Map from Arithvar -> PreviousAssignment */ + DenseMap d_safeAssignment; + + /** if d_vars.isKey(x), then x < d_numberOfVariables */ + ArithVar d_numberOfVariables; + + /** [0, d_numberOfVariables) \intersect d_vars.keys == d_pool */ + // Everything in the pool is fair game. + // There must be NO outstanding assertions + std::vector d_pool; + std::vector d_released; + //std::list::iterator d_releasedIterator; + + // Reverse Map from Node to ArithVar + // Inverse of d_vars[x].d_node + NodeToArithVarMap d_nodeToArithVarMap; + + + /** The queue of constraints where the assignment is at the bound.*/ + DenseMap d_boundsQueue; + + /** + * If this is true, record the incoming changes to the bound information. + * If this is false, the responsibility of recording the changes is + * LinearEqualities's. + */ + bool d_enqueueingBoundCounts; + + public: + + /** Returns the number of variables. */ + ArithVar getNumberOfVariables() const; + + /** Returns true if the node has an associated variables. */ + bool hasArithVar(TNode x) const; + + /** Returns true if the variable has a defining node. */ + bool hasNode(ArithVar a) const; + + /** Returns the ArithVar associated with a node. */ + ArithVar asArithVar(TNode x) const; + + /** Returns the node associated with an ArithVar. */ + Node asNode(ArithVar a) const; + + /** Allocates a freshly allocated variables. */ + ArithVar allocateVariable(); + + class var_iterator { + private: + const VarInfoVec* d_vars; + VarInfoVec::const_iterator d_wrapped; + public: + var_iterator(); + var_iterator(const VarInfoVec* vars, VarInfoVec::const_iterator ci); + var_iterator& operator++(); + + bool operator==(const var_iterator& other) const; + bool operator!=(const var_iterator& other) const; + ArithVar operator*() const; + + private: + void nextInitialized(); + }; + + var_iterator var_begin() const; + var_iterator var_end() const; + + + bool canBeReleased(ArithVar v) const; + void releaseArithVar(ArithVar v); + void attemptToReclaimReleased(); + + /** Is this variable guaranteed to have an integer assignment? + * (Should agree with the type system.) */ + bool isInteger(ArithVar x) const; + + /** Is the assignment to x integral? */ + bool integralAssignment(ArithVar x) const; + + /* Is this variable defined as a linear sum of other variables? */ + bool isAuxiliary(ArithVar x) const; + + /* Is the variable both input and not auxiliary? */ + bool isIntegerInput(ArithVar x) const; + + private: + + typedef std::pair AVCPair; + class LowerBoundCleanUp { + private: + ArithVariables* d_pm; + public: + LowerBoundCleanUp(ArithVariables* pm); + void operator()(AVCPair* restore); + }; + + class UpperBoundCleanUp { + private: + ArithVariables* d_pm; + public: + UpperBoundCleanUp(ArithVariables* pm); + void operator()(AVCPair* restore); + }; + + typedef context::CDList LBReverts; + LBReverts d_lbRevertHistory; + + typedef context::CDList UBReverts; + UBReverts d_ubRevertHistory; + + void pushUpperBound(VarInfo&); + void popUpperBound(AVCPair*); + void pushLowerBound(VarInfo&); + void popLowerBound(AVCPair*); + + // This is true when setDelta() is called, until invalidateDelta is called + bool d_deltaIsSafe; + // Cache of a value of delta to ensure a total order. + Rational d_delta; + // Function to call if the value of delta needs to be recomputed. + DeltaComputeCallback d_deltaComputingFunc; + + +public: + ArithVariables(context::Context* c, DeltaComputeCallback deltaComputation); + + /** + * This sets the lower bound for a variable in the current context. + * This must be stronger the previous constraint. + */ + void setLowerBoundConstraint(ConstraintP lb); + + /** + * This sets the upper bound for a variable in the current context. + * This must be stronger the previous constraint. + */ + void setUpperBoundConstraint(ConstraintP ub); + + /** Returns the constraint for the upper bound of a variable. */ + inline ConstraintP getUpperBoundConstraint(ArithVar x) const + { + return d_vars[x].d_ub; + } + /** Returns the constraint for the lower bound of a variable. */ + inline ConstraintP getLowerBoundConstraint(ArithVar x) const{ + return d_vars[x].d_lb; + } + + /* Initializes a variable to a safe value.*/ + void initialize(ArithVar x, Node n, bool aux); + + ArithVar allocate(Node n, bool aux = false); + + /* Gets the last assignment to a variable that is known to be consistent. */ + const DeltaRational& getSafeAssignment(ArithVar x) const; + const DeltaRational& getAssignment(ArithVar x, bool safe) const; + + /* Reverts all variable assignments to their safe values. */ + void revertAssignmentChanges(); + + /* Commits all variables assignments as safe.*/ + void commitAssignmentChanges(); + + + bool lowerBoundIsZero(ArithVar x); + bool upperBoundIsZero(ArithVar x); + + bool boundsAreEqual(ArithVar x) const; + + /* Sets an unsafe variable assignment */ + void setAssignment(ArithVar x, const DeltaRational& r); + void setAssignment(ArithVar x, const DeltaRational& safe, const DeltaRational& r); + + + /** Must know that the bound exists before calling this! */ + const DeltaRational& getUpperBound(ArithVar x) const; + const DeltaRational& getLowerBound(ArithVar x) const; + const DeltaRational& getAssignment(ArithVar x) const; + + + bool equalsLowerBound(ArithVar x, const DeltaRational& c); + bool equalsUpperBound(ArithVar x, const DeltaRational& c); + + /** + * If lowerbound > - \infty: + * return getAssignment(x).cmp(getLowerBound(x)) + * If lowerbound = - \infty: + * return 1 + */ + int cmpToLowerBound(ArithVar x, const DeltaRational& c) const; + + inline bool strictlyLessThanLowerBound(ArithVar x, const DeltaRational& c) const{ + return cmpToLowerBound(x, c) < 0; + } + inline bool lessThanLowerBound(ArithVar x, const DeltaRational& c) const{ + return cmpToLowerBound(x, c) <= 0; + } + + inline bool strictlyGreaterThanLowerBound(ArithVar x, const DeltaRational& c) const{ + return cmpToLowerBound(x, c) > 0; + } + + inline bool greaterThanLowerBound(ArithVar x, const DeltaRational& c) const{ + return cmpToLowerBound(x, c) >= 0; + } + /** + * If upperbound < \infty: + * return getAssignment(x).cmp(getUpperBound(x)) + * If upperbound = \infty: + * return -1 + */ + int cmpToUpperBound(ArithVar x, const DeltaRational& c) const; + + inline bool strictlyLessThanUpperBound(ArithVar x, const DeltaRational& c) const{ + return cmpToUpperBound(x, c) < 0; + } + + inline bool lessThanUpperBound(ArithVar x, const DeltaRational& c) const{ + return cmpToUpperBound(x, c) <= 0; + } + + inline bool strictlyGreaterThanUpperBound(ArithVar x, const DeltaRational& c) const{ + return cmpToUpperBound(x, c) > 0; + } + + inline bool greaterThanUpperBound(ArithVar x, const DeltaRational& c) const{ + return cmpToUpperBound(x, c) >= 0; + } + + inline int cmpAssignmentLowerBound(ArithVar x) const{ + return d_vars[x].d_cmpAssignmentLB; + } + inline int cmpAssignmentUpperBound(ArithVar x) const{ + return d_vars[x].d_cmpAssignmentUB; + } + + inline BoundCounts atBoundCounts(ArithVar x) const { + return d_vars[x].atBoundCounts(); + } + inline BoundCounts hasBoundCounts(ArithVar x) const { + return d_vars[x].hasBoundCounts(); + } + inline BoundsInfo boundsInfo(ArithVar x) const{ + return d_vars[x].boundsInfo(); + } + + bool strictlyBelowUpperBound(ArithVar x) const; + bool strictlyAboveLowerBound(ArithVar x) const; + bool assignmentIsConsistent(ArithVar x) const; + + void printModel(ArithVar x, std::ostream& out) const; + void printModel(ArithVar x) const; + + /** returns true iff x has both a lower and upper bound. */ + bool hasEitherBound(ArithVar x) const; + inline bool hasLowerBound(ArithVar x) const{ + return d_vars[x].d_lb != NullConstraint; + } + inline bool hasUpperBound(ArithVar x) const{ + return d_vars[x].d_ub != NullConstraint; + } + + const Rational& getDelta(); + + void invalidateDelta(); + + void setDelta(const Rational& d); + + void startQueueingBoundCounts(); + void stopQueueingBoundCounts(); + void addToBoundQueue(ArithVar v, const BoundsInfo& prev); + + BoundsInfo selectBoundsInfo(ArithVar v, bool old) const; + + bool boundsQueueEmpty() const; + void processBoundsQueue(BoundUpdateCallback& changed); + + void printEntireModel(std::ostream& out) const; + + + /** + * Precondition: assumes boundsAreEqual(x). + * If the either the lower/ upper bound is an equality, eq, + * this returns make_pair(eq, NullConstraint). + * Otherwise, this returns make_pair(lb, ub). + */ + std::pair explainEqualBounds(ArithVar x) const; + +private: + + /** + * This function implements the mostly identical: + * revertAssignmentChanges() and commitAssignmentChanges(). + */ + void clearSafeAssignments(bool revert); + + bool debugEqualSizes(); + + bool inMaps(ArithVar x) const; + +};/* class ArithVariables */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__ARITH__PARTIAL_MODEL_H */ diff --git a/src/theory/arith/linear/simplex.cpp b/src/theory/arith/linear/simplex.cpp new file mode 100644 index 000000000..6e20cccd0 --- /dev/null +++ b/src/theory/arith/linear/simplex.cpp @@ -0,0 +1,290 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + */ + +#include "theory/arith/linear/simplex.h" + +#include "base/output.h" +#include "options/arith_options.h" +#include "options/smt_options.h" +#include "smt/env.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/error_set.h" +#include "theory/arith/linear/linear_equality.h" +#include "theory/arith/linear/tableau.h" +#include "util/statistics_value.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +SimplexDecisionProcedure::SimplexDecisionProcedure( + Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc) + : EnvObj(env), + d_pivots(0), + d_conflictVariables(), + d_linEq(linEq), + d_variables(d_linEq.getVariables()), + d_tableau(d_linEq.getTableau()), + d_errorSet(errors), + d_numVariables(0), + d_conflictChannel(conflictChannel), + d_conflictBuilder(NULL), + d_arithVarMalloc(tvmalloc), + d_errorSize(0), + d_zero(0), + d_posOne(1), + d_negOne(-1) +{ + d_heuristicRule = options().arith.arithErrorSelectionRule; + d_errorSet.setSelectionRule(d_heuristicRule); + d_conflictBuilder = new FarkasConflictBuilder(options().smt.produceProofs); +} + +SimplexDecisionProcedure::~SimplexDecisionProcedure(){ + delete d_conflictBuilder; +} + + +bool SimplexDecisionProcedure::standardProcessSignals(TimerStat &timer, IntStat& conflicts) { + TimerStat::CodeTimer codeTimer(timer); + Assert(d_conflictVariables.empty()); + + while(d_errorSet.moreSignals()){ + ArithVar curr = d_errorSet.topSignal(); + if(d_tableau.isBasic(curr) && !d_variables.assignmentIsConsistent(curr)){ + Assert(d_linEq.basicIsTracked(curr)); + + if(!d_conflictVariables.isMember(curr) && checkBasicForConflict(curr)){ + + Trace("recentlyViolated") + << "It worked? " + << conflicts.get() + << " " << curr + << " " << checkBasicForConflict(curr) << endl; + reportConflict(curr); + ++conflicts; + } + } + // Pop signal afterwards in case d_linEq.trackVariable(curr); + // is needed for for the ErrorSet + d_errorSet.popSignal(); + } + d_errorSize = d_errorSet.errorSize(); + + Assert(d_errorSet.noSignals()); + return !d_conflictVariables.empty(); +} + +/** Reports a conflict to on the output channel. */ +void SimplexDecisionProcedure::reportConflict(ArithVar basic){ + Assert(!d_conflictVariables.isMember(basic)); + Assert(checkBasicForConflict(basic)); + + ConstraintCP conflicted = generateConflictForBasic(basic); + Assert(conflicted != NullConstraint); + d_conflictChannel.raiseConflict(conflicted, InferenceId::ARITH_CONF_SIMPLEX); + + d_conflictVariables.add(basic); +} + +ConstraintCP SimplexDecisionProcedure::generateConflictForBasic(ArithVar basic) const { + Assert(d_tableau.isBasic(basic)); + Assert(checkBasicForConflict(basic)); + + if(d_variables.cmpAssignmentLowerBound(basic) < 0){ + Assert(d_linEq.nonbasicsAtUpperBounds(basic)); + return d_linEq.generateConflictBelowLowerBound(basic, *d_conflictBuilder); + }else if(d_variables.cmpAssignmentUpperBound(basic) > 0){ + Assert(d_linEq.nonbasicsAtLowerBounds(basic)); + return d_linEq.generateConflictAboveUpperBound(basic, *d_conflictBuilder); + }else{ + Unreachable(); + return NullConstraint; + } +} +bool SimplexDecisionProcedure::maybeGenerateConflictForBasic(ArithVar basic) const { + if(checkBasicForConflict(basic)){ + ConstraintCP conflicted = generateConflictForBasic(basic); + d_conflictChannel.raiseConflict(conflicted, InferenceId::UNKNOWN); + return true; + }else{ + return false; + } +} + +bool SimplexDecisionProcedure::checkBasicForConflict(ArithVar basic) const { + Assert(d_tableau.isBasic(basic)); + Assert(d_linEq.basicIsTracked(basic)); + + if(d_variables.cmpAssignmentLowerBound(basic) < 0){ + if(d_linEq.nonbasicsAtUpperBounds(basic)){ + return true; + } + }else if(d_variables.cmpAssignmentUpperBound(basic) > 0){ + if(d_linEq.nonbasicsAtLowerBounds(basic)){ + return true; + } + } + return false; +} + +void SimplexDecisionProcedure::tearDownInfeasiblityFunction(TimerStat& timer, ArithVar tmp){ + TimerStat::CodeTimer codeTimer(timer); + Assert(tmp != ARITHVAR_SENTINEL); + Assert(d_tableau.isBasic(tmp)); + + RowIndex ri = d_tableau.basicToRowIndex(tmp); + d_linEq.stopTrackingRowIndex(ri); + d_tableau.removeBasicRow(tmp); + releaseVariable(tmp); +} + +void SimplexDecisionProcedure::shrinkInfeasFunc(TimerStat& timer, ArithVar inf, const ArithVarVec& dropped){ + TimerStat::CodeTimer codeTimer(timer); + for(ArithVarVec::const_iterator i=dropped.begin(), i_end = dropped.end(); i != i_end; ++i){ + ArithVar back = *i; + + int focusSgn = d_errorSet.focusSgn(back); + Rational chg(-focusSgn); + + d_linEq.substitutePlusTimesConstant(inf, back, chg); + } +} + +void SimplexDecisionProcedure::adjustInfeasFunc(TimerStat& timer, ArithVar inf, const AVIntPairVec& focusChanges){ + TimerStat::CodeTimer codeTimer(timer); + for(AVIntPairVec::const_iterator i=focusChanges.begin(), i_end = focusChanges.end(); i != i_end; ++i){ + ArithVar v = (*i).first; + int focusChange = (*i).second; + + Rational chg(focusChange); + if(d_tableau.isBasic(v)){ + d_linEq.substitutePlusTimesConstant(inf, v, chg); + }else{ + d_linEq.directlyAddToCoefficient(inf, v, chg); + } + } +} + +void SimplexDecisionProcedure::addToInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e){ + AVIntPairVec justE; + int sgn = d_errorSet.getSgn(e); + justE.push_back(make_pair(e, sgn)); + adjustInfeasFunc(timer, inf, justE); +} + +void SimplexDecisionProcedure::removeFromInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e){ + AVIntPairVec justE; + int opSgn = -d_errorSet.getSgn(e); + justE.push_back(make_pair(e, opSgn)); + adjustInfeasFunc(timer, inf, justE); +} + +ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer, const ArithVarVec& set){ + Trace("constructInfeasiblityFunction") << "constructInfeasiblityFunction start" << endl; + + TimerStat::CodeTimer codeTimer(timer); + Assert(!d_errorSet.focusEmpty()); + Assert(debugIsASet(set)); + + ArithVar inf = requestVariable(); + Assert(inf != ARITHVAR_SENTINEL); + + std::vector coeffs; + std::vector variables; + + for(ArithVarVec::const_iterator iter = set.begin(), iend = set.end(); iter != iend; ++iter){ + ArithVar e = *iter; + + Assert(d_tableau.isBasic(e)); + Assert(!d_variables.assignmentIsConsistent(e)); + + int sgn = d_errorSet.getSgn(e); + Assert(sgn == -1 || sgn == 1); + const Rational& violatedCoeff = sgn < 0 ? d_negOne : d_posOne; + coeffs.push_back(violatedCoeff); + variables.push_back(e); + + Trace("constructInfeasiblityFunction") << violatedCoeff << " " << e << endl; + + } + d_tableau.addRow(inf, coeffs, variables); + DeltaRational newAssignment = d_linEq.computeRowValue(inf, false); + d_variables.setAssignment(inf, newAssignment); + + //d_linEq.trackVariable(inf); + d_linEq.trackRowIndex(d_tableau.basicToRowIndex(inf)); + + Trace("constructInfeasiblityFunction") << inf << " " << newAssignment << endl; + Trace("constructInfeasiblityFunction") << "constructInfeasiblityFunction done" << endl; + return inf; +} + +ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer){ + ArithVarVec inError; + d_errorSet.pushFocusInto(inError); + return constructInfeasiblityFunction(timer, inError); +} + +ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer, ArithVar e){ + ArithVarVec justE; + justE.push_back(e); + return constructInfeasiblityFunction(timer, justE); +} + +void SimplexDecisionProcedure::addSgn(sgn_table& sgns, ArithVar col, int sgn, ArithVar basic){ + pair p = make_pair(col, determinizeSgn(sgn)); + sgns[p].push_back(basic); +} + +void SimplexDecisionProcedure::addRowSgns(sgn_table& sgns, ArithVar basic, int norm){ + for(Tableau::RowIterator i = d_tableau.basicRowIterator(basic); !i.atEnd(); ++i){ + const Tableau::Entry& entry = *i; + ArithVar v = entry.getColVar(); + int sgn = (entry.getCoefficient().sgn()); + addSgn(sgns, v, norm * sgn, basic); + } +} + +ArithVar SimplexDecisionProcedure::find_basic_in_sgns(const sgn_table& sgns, ArithVar col, int sgn, const DenseSet& m, bool inside){ + pair p = make_pair(col, determinizeSgn(sgn)); + sgn_table::const_iterator i = sgns.find(p); + + if(i != sgns.end()){ + const ArithVarVec& vec = (*i).second; + for(ArithVarVec::const_iterator viter = vec.begin(), vend = vec.end(); viter != vend; ++viter){ + ArithVar curr = *viter; + if(inside == m.isMember(curr)){ + return curr; + } + } + } + return ARITHVAR_SENTINEL; +} + +SimplexDecisionProcedure::sgn_table::const_iterator SimplexDecisionProcedure::find_sgns(const sgn_table& sgns, ArithVar col, int sgn){ + pair p = make_pair(col, determinizeSgn(sgn)); + return sgns.find(p); +} +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/simplex.h b/src/theory/arith/linear/simplex.h new file mode 100644 index 000000000..e739ac837 --- /dev/null +++ b/src/theory/arith/linear/simplex.h @@ -0,0 +1,235 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + * + * This implements the Simplex module for the Simpelx for DPLL(T) decision + * procedure. + * See the Simplex for DPLL(T) technical report for more background.(citation?) + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This is required to either produce a conflict or satisifying PartialModel. + * Further, we require being told when a basic variable updates its value. + * + * During the Simplex search we maintain a queue of variables. + * The queue is required to contain all of the basic variables that voilate + * their bounds. + * As elimination from the queue is more efficient to be done lazily, + * we do not maintain that the queue of variables needs to be only basic + * variables or only variables that satisfy their bounds. + * + * The simplex procedure roughly follows Alberto's thesis. (citation?) + * There is one round of selecting using a heuristic pivoting rule. + * (See PreferenceFunction Documentation for the available options.) + * The non-basic variable is the one that appears in the fewest pivots. + * (Bruno says that Leonardo invented this first.) + * After this, Bland's pivot rule is invoked. + * + * During this proccess, we periodically inspect the queue of variables to + * 1) remove now extraneous extries, + * 2) detect conflicts that are "waiting" on the queue but may not be detected + * by the current queue heuristics, and + * 3) detect multiple conflicts. + * + * Conflicts are greedily slackened to use the weakest bounds that still + * produce the conflict. + * + * Extra things tracked atm: (Subject to change at Tim's whims) + * - A superset of all of the newly pivoted variables. + * - A queue of additional conflicts that were discovered by Simplex. + * These are theory valid and are currently turned into lemmas + */ + +#include "cvc5_private.h" + +#pragma once + +#include + +#include "options/arith_options.h" +#include "smt/env_obj.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/partial_model.h" +#include "util/dense_map.h" +#include "util/result.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class ErrorSet; +class LinearEqualityModule; +class Tableau; + +class SimplexDecisionProcedure : protected EnvObj +{ + protected: + typedef std::vector< std::pair > AVIntPairVec; + + /** Pivot count of the current round of pivoting. */ + uint32_t d_pivots; + + /** The set of variables that are in conflict in this round. */ + DenseSet d_conflictVariables; + + /** The rule to use for heuristic selection mode. */ + options::ErrorSelectionRule d_heuristicRule; + + /** Linear equality module. */ + LinearEqualityModule& d_linEq; + + /** + * Manages information about the assignment and upper and lower bounds on + * variables. + * Partial model matches that in LinearEqualityModule. + */ + ArithVariables& d_variables; + + /** + * Stores the linear equalities used by Simplex. + * Tableau from the LinearEquality module. + */ + Tableau& d_tableau; + + /** Contains a superset of the basic variables in violation of their bounds. */ + ErrorSet& d_errorSet; + + /** Number of variables in the system. This is used for tuning heuristics. */ + ArithVar d_numVariables; + + /** This is the call back channel for Simplex to report conflicts. */ + RaiseConflict d_conflictChannel; + + /** This is the call back channel for Simplex to report conflicts. */ + FarkasConflictBuilder* d_conflictBuilder; + + /** Used for requesting d_opt, bound and error variables for primal.*/ + TempVarMalloc d_arithVarMalloc; + + /** The size of the error set. */ + uint32_t d_errorSize; + + /** A local copy of 0. */ + const Rational d_zero; + + /** A local copy of 1. */ + const Rational d_posOne; + + /** A local copy of -1. */ + const Rational d_negOne; + + /** + * Locally cached value of arithStandardCheckVarOrderPivots option. It is + * cached here to allow for single runs with a different (lower) limit. + */ + int64_t d_varOrderPivotLimit = -1; + + ArithVar constructInfeasiblityFunction(TimerStat& timer); + ArithVar constructInfeasiblityFunction(TimerStat& timer, ArithVar e); + ArithVar constructInfeasiblityFunction(TimerStat& timer, const ArithVarVec& set); + + void tearDownInfeasiblityFunction(TimerStat& timer, ArithVar inf); + void adjustInfeasFunc(TimerStat& timer, ArithVar inf, const AVIntPairVec& focusChanges); + void addToInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e); + void removeFromInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e); + void shrinkInfeasFunc(TimerStat& timer, ArithVar inf, const ArithVarVec& dropped); + +public: + SimplexDecisionProcedure(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc); + virtual ~SimplexDecisionProcedure(); + + /** + * Tries to update the assignments of variables such that all of the + * assignments are consistent with their bounds. + * This is done by a simplex search through the possible bases of the tableau. + * + * If all of the variables can be made consistent with their bounds + * SAT is returned. Otherwise UNSAT is returned, and at least 1 conflict + * was reported on the conflictCallback passed to the Module. + * + * Tableau pivoting is performed so variables may switch from being basic to + * nonbasic and vice versa. + * + * Corresponds to the "check()" procedure in [Cav06]. + */ + virtual Result::Status findModel(bool exactResult) = 0; + + void increaseMax() { d_numVariables++; } + + uint32_t getPivots() const { return d_pivots; } + + /** Set the variable ordering pivot limit */ + void setVarOrderPivotLimit(int64_t value) { d_varOrderPivotLimit = value; } + +protected: + /** Reports a conflict to on the output channel. */ + void reportConflict(ArithVar basic); + + /** + * Checks a basic variable, b, to see if it is in conflict. + * If a conflict is discovered a node summarizing the conflict is returned. + * Otherwise, Node::null() is returned. + */ + bool maybeGenerateConflictForBasic(ArithVar basic) const; + + /** Returns true if a tracked basic variable has a conflict on it. */ + bool checkBasicForConflict(ArithVar b) const; + + /** + * If a basic variable has a conflict on its row, + * this produces a minimized row on the conflict channel. + */ + ConstraintCP generateConflictForBasic(ArithVar basic) const; + + /** Gets a fresh variable from TheoryArith. */ + ArithVar requestVariable() { return d_arithVarMalloc.request(); } + + /** Releases a requested variable from TheoryArith.*/ + void releaseVariable(ArithVar v) { d_arithVarMalloc.release(v); } + + /** Post condition: !d_queue.moreSignals() */ + bool standardProcessSignals(TimerStat& timer, IntStat& conflictStat); + + struct ArithVarIntPairHashFunc + { + size_t operator()(const std::pair& p) const + { + size_t h1 = std::hash()(p.first); + size_t h2 = std::hash()(p.second); + return h1 + 3389 * h2; + } + }; + + typedef std::unordered_map< std::pair, ArithVarVec, ArithVarIntPairHashFunc> sgn_table; + + static inline int determinizeSgn(int sgn){ + return sgn < 0 ? -1 : (sgn == 0 ? 0 : 1); + } + + void addSgn(sgn_table& sgns, ArithVar col, int sgn, ArithVar basic); + void addRowSgns(sgn_table& sgns, ArithVar basic, int norm); + ArithVar find_basic_in_sgns(const sgn_table& sgns, ArithVar col, int sgn, const DenseSet& m, bool inside); + + sgn_table::const_iterator find_sgns(const sgn_table& sgns, ArithVar col, int sgn); + +}; /* class SimplexDecisionProcedure */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/simplex_update.cpp b/src/theory/arith/linear/simplex_update.cpp new file mode 100644 index 000000000..9e7a55108 --- /dev/null +++ b/src/theory/arith/linear/simplex_update.cpp @@ -0,0 +1,207 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andres Noetzli, Mathias Preiner + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This implements the UpdateInfo. + */ + +#include "theory/arith/linear/simplex_update.h" + +#include "theory/arith/linear/constraint.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +/* + * Generates a string representation of std::optional and inserts it into a + * stream. + * + * Note: We define this function here in the cvc5::internal::theory::arith namespace, + * because it would otherwise not be found for std::optional. This is due + * to the argument-dependent lookup rules. + * + * @param out The stream + * @param m The value + * @return The stream + */ +std::ostream& operator<<(std::ostream& out, const std::optional& m) +{ + return cvc5::internal::operator<<(out, m); +} + +UpdateInfo::UpdateInfo(): + d_nonbasic(ARITHVAR_SENTINEL), + d_nonbasicDirection(0), + d_nonbasicDelta(), + d_foundConflict(false), + d_errorsChange(), + d_focusDirection(), + d_tableauCoefficient(), + d_limiting(NullConstraint), + d_witness(AntiProductive) +{} + +UpdateInfo::UpdateInfo(ArithVar nb, int dir): + d_nonbasic(nb), + d_nonbasicDirection(dir), + d_nonbasicDelta(), + d_foundConflict(false), + d_errorsChange(), + d_focusDirection(), + d_tableauCoefficient(), + d_limiting(NullConstraint), + d_witness(AntiProductive) +{ + Assert(dir == 1 || dir == -1); +} + +UpdateInfo::UpdateInfo(bool conflict, ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP c): + d_nonbasic(nb), + d_nonbasicDirection(delta.sgn()), + d_nonbasicDelta(delta), + d_foundConflict(true), + d_errorsChange(), + d_focusDirection(), + d_tableauCoefficient(&r), + d_limiting(c), + d_witness(ConflictFound) +{ + Assert(conflict); +} + +UpdateInfo UpdateInfo::conflict(ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP lim){ + return UpdateInfo(true, nb, delta, r, lim); +} + +void UpdateInfo::updateUnbounded(const DeltaRational& delta, int ec, int f){ + d_limiting = NullConstraint; + d_nonbasicDelta = delta; + d_errorsChange = ec; + d_focusDirection = f; + d_tableauCoefficient.reset(); + updateWitness(); + Assert(unbounded()); + Assert(improvement(d_witness)); + Assert(!describesPivot()); + Assert(debugSgnAgreement()); +} +void UpdateInfo::updatePureFocus(const DeltaRational& delta, ConstraintP c){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange.reset(); + d_focusDirection = 1; + d_tableauCoefficient.reset(); + updateWitness(); + Assert(!describesPivot()); + Assert(improvement(d_witness)); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::updatePivot(const DeltaRational& delta, const Rational& r, ConstraintP c){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange.reset(); + d_focusDirection.reset(); + updateWitness(); + Assert(describesPivot()); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::updatePivot(const DeltaRational& delta, const Rational& r, ConstraintP c, int ec){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange = ec; + d_focusDirection.reset(); + d_tableauCoefficient = &r; + updateWitness(); + Assert(describesPivot()); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::witnessedUpdate(const DeltaRational& delta, ConstraintP c, int ec, int fd){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange = ec; + d_focusDirection = fd; + d_tableauCoefficient.reset(); + updateWitness(); + Assert(describesPivot() || improvement(d_witness)); + Assert(debugSgnAgreement()); +} + +void UpdateInfo::update(const DeltaRational& delta, const Rational& r, ConstraintP c, int ec, int fd){ + d_limiting = c; + d_nonbasicDelta = delta; + d_errorsChange = ec; + d_focusDirection = fd; + d_tableauCoefficient = &r; + updateWitness(); + Assert(describesPivot() || improvement(d_witness)); + Assert(debugSgnAgreement()); +} + +bool UpdateInfo::describesPivot() const { + return !unbounded() && d_nonbasic != d_limiting->getVariable(); +} + +void UpdateInfo::output(std::ostream& out) const{ + out << "{UpdateInfo" + << ", nb = " << d_nonbasic + << ", dir = " << d_nonbasicDirection + << ", delta = " << d_nonbasicDelta + << ", conflict = " << d_foundConflict + << ", errorChange = " << d_errorsChange + << ", focusDir = " << d_focusDirection + << ", witness = " << d_witness + << ", limiting = " << d_limiting + << "}"; +} + +ArithVar UpdateInfo::leaving() const{ + Assert(describesPivot()); + + return d_limiting->getVariable(); +} + +std::ostream& operator<<(std::ostream& out, const UpdateInfo& up){ + up.output(out); + return out; +} + + +std::ostream& operator<<(std::ostream& out, WitnessImprovement w){ + switch(w){ + case ConflictFound: + out << "ConflictFound"; break; + case ErrorDropped: + out << "ErrorDropped"; break; + case FocusImproved: + out << "FocusImproved"; break; + case FocusShrank: + out << "FocusShrank"; break; + case Degenerate: + out << "Degenerate"; break; + case BlandsDegenerate: + out << "BlandsDegenerate"; break; + case HeuristicDegenerate: + out << "HeuristicDegenerate"; break; + case AntiProductive: + out << "AntiProductive"; break; + } + return out; +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/simplex_update.h b/src/theory/arith/linear/simplex_update.h new file mode 100644 index 000000000..0a4c90ecc --- /dev/null +++ b/src/theory/arith/linear/simplex_update.h @@ -0,0 +1,360 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andres Noetzli, Morgan Deters + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This provides a class for summarizing pivot proposals. + * + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This maintains the relationship needed by the SimplexDecisionProcedure. + * + * In the language of Simplex for DPLL(T), this provides: + * - update() + * - pivotAndUpdate() + * + * This class also provides utility functions that require + * using both the Tableau and PartialModel. + */ + +#include "cvc5_private.h" + +#pragma once + +#include + +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/constraint_forward.h" +#include "theory/arith/delta_rational.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +enum WitnessImprovement { + ConflictFound = 0, + ErrorDropped = 1, + FocusImproved = 2, + FocusShrank = 3, + Degenerate = 4, + BlandsDegenerate = 5, + HeuristicDegenerate = 6, + AntiProductive = 7 +}; + +inline bool strongImprovement(WitnessImprovement w){ + return w <= FocusImproved; +} + +inline bool improvement(WitnessImprovement w){ + return w <= FocusShrank; +} + +inline bool degenerate(WitnessImprovement w){ + switch(w){ + case Degenerate: + case BlandsDegenerate: + case HeuristicDegenerate: + return true; + default: + return false; + } +} + +std::ostream& operator<<(std::ostream& out, WitnessImprovement w); + +/** + * This class summarizes both potential: + * - pivot-and-update operations or + * - a pure update operation. + * This stores enough information for the various algorithms hat consider these operations. + * These require slightly different pieces of information at different points + * so they are a bit verbose and paranoid. + */ +class UpdateInfo { +private: + + /** + * The nonbasic variables under consideration. + * This is either the entering variable on a pivot and update + * or the variable being updated. + * This can only be set in the constructor or assignment. + * + * If this uninitialized, then this is ARITHVAR_SENTINEL. + */ + ArithVar d_nonbasic; + + /** + * The sgn of the "intended" derivative (delta) of the update to d_nonbasic. + * This is either 1, -1, or 0. + * It is "intended" as the delta is always allowed to be 0. + * (See debugSgnAgreement().) + * + * If this uninitialized, then this is 0. + * If this is initialized, then it is -1 or 1. + * + * This can only be set in the constructor or assignment. + */ + int d_nonbasicDirection; + + /** + * The change in the assignment of d_nonbasic. + * This is changed via the updateProposal(...) methods. + * The value needs to satisfy debugSgnAgreement() or it is in conflict. + */ + std::optional d_nonbasicDelta; + + /** + * This is true if the pivot-and-update is *known* to cause a conflict. + * This can only be true if it was constructed through the static conflict(...) method. + */ + bool d_foundConflict; + + /** This is the change in the size of the error set. */ + std::optional d_errorsChange; + + /** This is the sgn of the change in the value of the focus set.*/ + std::optional d_focusDirection; + + /** This is the sgn of the change in the value of the focus set.*/ + std::optional d_focusChange; + + /** This is the coefficient in the tableau for the entry.*/ + std::optional d_tableauCoefficient; + + /** + * This is the constraint that nonbasic is basic is updating s.t. its variable is against it. + * This has 3 different possibilities: + * - Unbounded : then this is NullConstraint and unbounded() is true. + * - Pivot-And-Update: then this is not NullConstraint and the variable is not d_nonbasic. + * - Update: then this is not NullConstraint and the variable is d_nonbasic. + */ + ConstraintP d_limiting; + + WitnessImprovement d_witness; + + /** + * This returns true if + * d_nonbasicDelta is zero() or its sgn() must agree with d_nonbasicDirection. + */ + bool debugSgnAgreement() const { + int deltaSgn = d_nonbasicDelta.value().sgn(); + return deltaSgn == 0 || deltaSgn == d_nonbasicDirection; + } + + /** This private constructor allows for setting conflict to true. */ + UpdateInfo(bool conflict, ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP lim); + +public: + + /** This constructs an uninitialized UpdateInfo. */ + UpdateInfo(); + + /** + * This constructs an initialized UpdateInfo. + * dir must be 1 or -1. + */ + UpdateInfo(ArithVar nb, int dir); + + /** + * This updates the nonBasicDelta to d and limiting to NullConstraint. + * This describes an unbounded() update. + */ + void updateUnbounded(const DeltaRational& d, int ec, int f); + + + void updatePureFocus(const DeltaRational& d, ConstraintP c); + //void updatePureError(const DeltaRational& d, Constraint c, int e); + //void updatePure(const DeltaRational& d, Constraint c, int e, int f); + + /** + * This updates the nonBasicDelta to d and limiting to c. + * This clears errorChange() and focusDir(). + */ + void updatePivot(const DeltaRational& d, const Rational& r, ConstraintP c); + + /** + * This updates the nonBasicDelta to d, limiting to c, and errorChange to e. + * This clears focusDir(). + */ + void updatePivot(const DeltaRational& d, const Rational& r, ConstraintP c, int e); + + /** + * This updates the nonBasicDelta to d, limiting to c, errorChange to e and + * focusDir to f. + */ + void witnessedUpdate(const DeltaRational& d, ConstraintP c, int e, int f); + void update(const DeltaRational& d, const Rational& r, ConstraintP c, int e, int f); + + + static UpdateInfo conflict(ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP lim); + + inline ArithVar nonbasic() const { return d_nonbasic; } + inline bool uninitialized() const { + return d_nonbasic == ARITHVAR_SENTINEL; + } + + /** + * There is no limiting value to the improvement of the focus. + * If this is true, this never describes an update. + */ + inline bool unbounded() const { + return d_limiting == NullConstraint; + } + + /** + * The update either describes a pivotAndUpdate operation + * or it describes just an update. + */ + bool describesPivot() const; + + /** Returns the . describesPivot() must be true. */ + ArithVar leaving() const; + + /** + * Returns true if this is *known* to find a conflict. + * If true, this must have been made through the static conflict(...) function. + */ + bool foundConflict() const { return d_foundConflict; } + + /** Returns the direction nonbasic is supposed to move. */ + inline int nonbasicDirection() const{ return d_nonbasicDirection; } + + /** Requires errorsChange to be set through setErrorsChange or updateProposal. */ + inline int errorsChange() const { return d_errorsChange.value(); } + + /** + * If errorsChange has been set, return errorsChange(). + * Otherwise, return def. + */ + inline int errorsChangeSafe(int def) const { + if (d_errorsChange) + { + return d_errorsChange.value(); + } + else + { + return def; + } + } + + /** Sets the errorChange. */ + void setErrorsChange(int ec){ + d_errorsChange = ec; + updateWitness(); + } + + + /** Requires errorsChange to be set through setErrorsChange or updateProposal. */ + inline int focusDirection() const { return d_focusDirection.value(); } + + /** Sets the focusDirection. */ + void setFocusDirection(int fd){ + Assert(-1 <= fd && fd <= 1); + d_focusDirection = fd; + updateWitness(); + } + + /** + * nonbasicDirection must be the same as the sign for the focus function's + * coefficient for this to be safe. + * The burden for this being safe is on the user! + */ + void determineFocusDirection(){ + const int deltaSgn = d_nonbasicDelta.value().sgn(); + setFocusDirection(deltaSgn * d_nonbasicDirection); + } + + /** Requires nonbasicDelta to be set through updateProposal(...). */ + const DeltaRational& nonbasicDelta() const { return d_nonbasicDelta.value(); } + const Rational& getCoefficient() const { + Assert(describesPivot()); + Assert(d_tableauCoefficient.value() != NULL); + return *(d_tableauCoefficient.value()); + } + int basicDirection() const { + return nonbasicDirection() * (getCoefficient().sgn()); + } + + /** Returns the limiting constraint. */ + inline ConstraintP limiting() const { + return d_limiting; + } + + WitnessImprovement getWitness(bool useBlands = false) const{ + Assert(d_witness == computeWitness()); + + if(d_witness == Degenerate){ + if(useBlands){ + return BlandsDegenerate; + }else{ + return HeuristicDegenerate; + } + }else{ + return d_witness; + } + } + + const DeltaRational& focusChange() const { return d_focusChange.value(); } + void setFocusChange(const DeltaRational& fc) { + d_focusChange = fc; + } + + /** Outputs the UpdateInfo into out. */ + void output(std::ostream& out) const; + +private: + void updateWitness() { + d_witness = computeWitness(); + Assert(describesPivot() || improvement(d_witness)); + } + + /** + * Determines the appropriate WitnessImprovement for the update. + * useBlands breaks ties for degenerate pivots. + * + * This is safe if: + * - d_foundConflict is true, or + * - d_foundConflict is false and d_errorsChange has been set and d_errorsChange < 0, or + * - d_foundConflict is false and d_errorsChange has been set and d_errorsChange >= 0 and d_focusDirection has been set. + */ + WitnessImprovement computeWitness() const { + if(d_foundConflict){ + return ConflictFound; + } + else if (d_errorsChange && d_errorsChange.value() < 0) + { + return ErrorDropped; + } + else if (d_errorsChange.value_or(0) == 0) + { + if (d_focusDirection) + { + if (*d_focusDirection > 0) + { + return FocusImproved; + } + else if (*d_focusDirection == 0) + { + return Degenerate; + } + } + } + return AntiProductive; + } + +}; + +std::ostream& operator<<(std::ostream& out, const UpdateInfo& up); + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/soi_simplex.cpp b/src/theory/arith/linear/soi_simplex.cpp new file mode 100644 index 000000000..0ba3e3495 --- /dev/null +++ b/src/theory/arith/linear/soi_simplex.cpp @@ -0,0 +1,912 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + */ +#include "theory/arith/linear/soi_simplex.h" + +#include + +#include "base/output.h" +#include "options/arith_options.h" +#include "smt/smt_statistics_registry.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/error_set.h" +#include "theory/arith/linear/tableau.h" +#include "util/statistics_stats.h" + +using namespace std; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +SumOfInfeasibilitiesSPD::SumOfInfeasibilitiesSPD(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc) + : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), + d_soiVar(ARITHVAR_SENTINEL), + d_pivotBudget(0), + d_prevWitnessImprovement(AntiProductive), + d_witnessImprovementInARow(0), + d_sgnDisagreements(), + d_statistics("theory::arith::SOI", d_pivots) +{ } + +SumOfInfeasibilitiesSPD::Statistics::Statistics(const std::string& name, + uint32_t& pivots) + : d_initialSignalsTime( + smtStatisticsRegistry().registerTimer(name + "initialProcessTime")), + d_initialConflicts( + smtStatisticsRegistry().registerInt(name + "UpdateConflicts")), + d_soiFoundUnsat(smtStatisticsRegistry().registerInt(name + "FoundUnsat")), + d_soiFoundSat(smtStatisticsRegistry().registerInt(name + "FoundSat")), + d_soiMissed(smtStatisticsRegistry().registerInt(name + "Missed")), + d_soiConflicts( + smtStatisticsRegistry().registerInt(name + "ConfMin::num")), + d_hasToBeMinimal( + smtStatisticsRegistry().registerInt(name + "HasToBeMin")), + d_maybeNotMinimal( + smtStatisticsRegistry().registerInt(name + "MaybeNotMin")), + d_soiTimer(smtStatisticsRegistry().registerTimer(name + "Time")), + d_soiFocusConstructionTimer( + smtStatisticsRegistry().registerTimer(name + "Construction")), + d_soiConflictMinimization(smtStatisticsRegistry().registerTimer( + name + "Conflict::Minimization")), + d_selectUpdateForSOI( + smtStatisticsRegistry().registerTimer(name + "selectSOI")), + d_finalCheckPivotCounter( + smtStatisticsRegistry().registerReference( + name + "lastPivots", pivots)) +{ +} + +Result::Status SumOfInfeasibilitiesSPD::findModel(bool exactResult) +{ + Assert(d_conflictVariables.empty()); + Assert(d_sgnDisagreements.empty()); + + d_pivots = 0; + + if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ + Trace("soi::findModel") << "soiFindModel() trivial" << endl; + Assert(d_conflictVariables.empty()); + return Result::SAT; + } + + // We need to reduce this because of + d_errorSet.reduceToSignals(); + + // We must start tracking NOW + d_errorSet.setSelectionRule(options::ErrorSelectionRule::SUM_METRIC); + + if(initialProcessSignals()){ + d_conflictVariables.purge(); + Trace("soi::findModel") << "fcFindModel() early conflict" << endl; + Assert(d_conflictVariables.empty()); + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Trace("soi::findModel") << "fcFindModel() fixed itself" << endl; + Assert(!d_errorSet.moreSignals()); + Assert(d_conflictVariables.empty()); + return Result::SAT; + } + + Trace("soi::findModel") << "fcFindModel() start non-trivial" << endl; + + exactResult |= d_varOrderPivotLimit < 0; + + d_prevWitnessImprovement = HeuristicDegenerate; + d_witnessImprovementInARow = 0; + + Result::Status result = Result::UNKNOWN; + + if (result == Result::UNKNOWN) + { + if(exactResult){ + d_pivotBudget = -1; + }else{ + d_pivotBudget = d_varOrderPivotLimit; + } + + result = sumOfInfeasibilities(); + + if(result == Result::UNSAT){ + ++(d_statistics.d_soiFoundUnsat); + }else if(d_errorSet.errorEmpty()){ + ++(d_statistics.d_soiFoundSat); + }else{ + ++(d_statistics.d_soiMissed); + } + } + + Assert(!d_errorSet.moreSignals()); + if (result == Result::UNKNOWN && d_errorSet.errorEmpty()) + { + result = Result::SAT; + } + + // ensure that the conflict variable is still in the queue. + d_conflictVariables.purge(); + + Trace("soi::findModel") << "end findModel() " << result << endl; + + Assert(d_conflictVariables.empty()); + return result; +} + + +void SumOfInfeasibilitiesSPD::logPivot(WitnessImprovement w){ + if(d_pivotBudget > 0) { + --d_pivotBudget; + } + Assert(w != AntiProductive); + + if(w == d_prevWitnessImprovement){ + ++d_witnessImprovementInARow; + if(d_witnessImprovementInARow == 0){ + --d_witnessImprovementInARow; + } + }else{ + if(w != BlandsDegenerate){ + d_witnessImprovementInARow = 1; + } + d_prevWitnessImprovement = w; + } + if(strongImprovement(w)){ + d_leavingCountSinceImprovement.purge(); + } + + Trace("logPivot") << "logPivot " << d_prevWitnessImprovement << " " << d_witnessImprovementInARow << endl; +} + +uint32_t SumOfInfeasibilitiesSPD::degeneratePivotsInARow() const { + switch(d_prevWitnessImprovement){ + case ConflictFound: + case ErrorDropped: + case FocusImproved: + return 0; + case HeuristicDegenerate: + case BlandsDegenerate: + return d_witnessImprovementInARow; + // Degenerate is unreachable for its own reasons + case Degenerate: + case FocusShrank: + case AntiProductive: + Unreachable(); + return -1; + } + Unreachable(); +} + +void SumOfInfeasibilitiesSPD::adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges){ + uint32_t newErrorSize = d_errorSet.errorSize(); + adjustInfeasFunc(d_statistics.d_soiFocusConstructionTimer, d_soiVar, focusChanges); + d_errorSize = newErrorSize; +} + + +UpdateInfo SumOfInfeasibilitiesSPD::selectUpdate(LinearEqualityModule::UpdatePreferenceFunction upf, LinearEqualityModule::VarPreferenceFunction bpf) { + UpdateInfo selected; + + Trace("soi::selectPrimalUpdate") + << "selectPrimalUpdate " << endl + << d_soiVar << " " << d_tableau.basicRowLength(d_soiVar) << " " + << d_linEq.debugBasicAtBoundCount(d_soiVar) << endl; + + typedef std::vector CandVector; + CandVector candidates; + + for(Tableau::RowIterator ri = d_tableau.basicRowIterator(d_soiVar); !ri.atEnd(); ++ri){ + const Tableau::Entry& e = *ri; + ArithVar curr = e.getColVar(); + if(curr == d_soiVar){ continue; } + + int sgn = e.getCoefficient().sgn(); + bool candidate = + (sgn > 0 && d_variables.cmpAssignmentUpperBound(curr) < 0) || + (sgn < 0 && d_variables.cmpAssignmentLowerBound(curr) > 0); + + Trace("soi::selectPrimalUpdate") + << "storing " << d_soiVar + << " " << curr + << " " << candidate + << " " << e.getCoefficient() + << " " << sgn << endl; + + if(candidate) { + candidates.push_back(Cand(curr, 0, sgn, &e.getCoefficient())); + } + } + + CompPenaltyColLength colCmp(&d_linEq, options().arith.havePenalties); + CandVector::iterator i = candidates.begin(); + CandVector::iterator end = candidates.end(); + std::make_heap(i, end, colCmp); + + // For the first 3 pivots take the best + // After that, once an improvement is found on look at a + // small number of pivots after finding an improvement + // the longer the search to more willing we are to look at more candidates + int maxCandidatesAfterImprove = + (d_pivots <= 2) ? std::numeric_limits::max() : d_pivots/5; + + int candidatesAfterFocusImprove = 0; + while(i != end && candidatesAfterFocusImprove <= maxCandidatesAfterImprove){ + std::pop_heap(i, end, colCmp); + --end; + Cand& cand = (*end); + ArithVar curr = cand.d_nb; + const Rational& coeff = *cand.d_coeff; + + LinearEqualityModule::UpdatePreferenceFunction leavingPrefFunc = selectLeavingFunction(curr); + UpdateInfo currProposal = d_linEq.speculativeUpdate(curr, coeff, leavingPrefFunc); + + Trace("soi::selectPrimalUpdate") + << "selected " << selected << endl + << "currProp " << currProposal << endl + << "coeff " << coeff << endl; + + Assert(!currProposal.uninitialized()); + + if(candidatesAfterFocusImprove > 0){ + candidatesAfterFocusImprove++; + } + + if(selected.uninitialized() || (d_linEq.*upf)(selected, currProposal)){ + selected = currProposal; + WitnessImprovement w = selected.getWitness(false); + Trace("soi::selectPrimalUpdate") << "selected " << w << endl; + //setPenalty(curr, w); + if(improvement(w)){ + bool exitEarly; + switch(w){ + case ConflictFound: exitEarly = true; break; + case FocusImproved: + candidatesAfterFocusImprove = 1; + exitEarly = false; + break; + default: + exitEarly = false; break; + } + if(exitEarly){ break; } + } + }else{ + Trace("soi::selectPrimalUpdate") << "dropped "<< endl; + } + + } + return selected; +} + +bool debugCheckWitness(const UpdateInfo& inf, WitnessImprovement w, bool useBlands){ + if(inf.getWitness(useBlands) == w){ + switch(w){ + case ConflictFound: return inf.foundConflict(); + case ErrorDropped: return inf.errorsChange() < 0; + case FocusImproved: return inf.focusDirection() > 0; + case FocusShrank: return false; // This is not a valid output + case Degenerate: return false; // This is not a valid output + case BlandsDegenerate: return useBlands; + case HeuristicDegenerate: return !useBlands; + case AntiProductive: return false; + } + } + return false; +} + + +void SumOfInfeasibilitiesSPD::debugPrintSignal(ArithVar updated) const{ + Trace("updateAndSignal") << "updated basic " << updated; + Trace("updateAndSignal") << " length " << d_tableau.basicRowLength(updated); + Trace("updateAndSignal") << " consistent " << d_variables.assignmentIsConsistent(updated); + int dir = !d_variables.assignmentIsConsistent(updated) ? + d_errorSet.getSgn(updated) : 0; + Trace("updateAndSignal") << " dir " << dir; + Trace("updateAndSignal") << " debugBasicAtBoundCount " << d_linEq.debugBasicAtBoundCount(updated) << endl; +} + + +void SumOfInfeasibilitiesSPD::updateAndSignal(const UpdateInfo& selected, WitnessImprovement w){ + ArithVar nonbasic = selected.nonbasic(); + + Trace("updateAndSignal") << "updateAndSignal " << selected << endl; + + if(selected.describesPivot()){ + ConstraintP limiting = selected.limiting(); + ArithVar basic = limiting->getVariable(); + Assert(d_linEq.basicIsTracked(basic)); + d_linEq.pivotAndUpdate(basic, nonbasic, limiting->getValue()); + }else{ + Assert(!selected.unbounded() || selected.errorsChange() < 0); + + DeltaRational newAssignment = + d_variables.getAssignment(nonbasic) + selected.nonbasicDelta(); + + d_linEq.updateTracked(nonbasic, newAssignment); + } + d_pivots++; + + increaseLeavingCount(nonbasic); + + vector< pair > focusChanges; + while(d_errorSet.moreSignals()){ + ArithVar updated = d_errorSet.topSignal(); + int prevFocusSgn = d_errorSet.popSignal(); + + if(d_tableau.isBasic(updated)){ + Assert(!d_variables.assignmentIsConsistent(updated) + == d_errorSet.inError(updated)); + if(TraceIsOn("updateAndSignal")){debugPrintSignal(updated);} + if(!d_variables.assignmentIsConsistent(updated)){ + if(checkBasicForConflict(updated)){ + reportConflict(updated); + //Assert(debugUpdatedBasic(selected, updated)); + } + } + }else{ + Trace("updateAndSignal") << "updated nonbasic " << updated << endl; + } + int currFocusSgn = d_errorSet.focusSgn(updated); + if(currFocusSgn != prevFocusSgn){ + int change = currFocusSgn - prevFocusSgn; + focusChanges.push_back(make_pair(updated, change)); + } + } + + if(TraceIsOn("error")){ d_errorSet.debugPrint(Trace("error")); } + + //Assert(debugSelectedErrorDropped(selected, d_errorSize, d_errorSet.errorSize())); + + adjustFocusAndError(selected, focusChanges); +} + +void SumOfInfeasibilitiesSPD::qeAddRange(uint32_t begin, uint32_t end){ + Assert(!d_qeInSoi.empty()); + for(uint32_t i = begin; i != end; ++i){ + ArithVar v = d_qeConflict[i]; + addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, v); + d_qeInSoi.add(v); + } +} + +void SumOfInfeasibilitiesSPD::qeRemoveRange(uint32_t begin, uint32_t end){ + for(uint32_t i = begin; i != end; ++i){ + ArithVar v = d_qeConflict[i]; + removeFromInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, v); + d_qeInSoi.remove(v); + } + Assert(!d_qeInSoi.empty()); +} + +void SumOfInfeasibilitiesSPD::qeSwapRange(uint32_t N, uint32_t r, uint32_t s){ + for(uint32_t i = 0; i < N; ++i){ + std::swap(d_qeConflict[r+i], d_qeConflict[s+i]); + } +} + +/** + * Region notation: + * A region is either + * - A single element X@i with the name X at the position i + * - A sequence of indices X@[i,j) with the name X and the elements between i [inclusive] and j exclusive + * - A concatenation of regions R1 and R2, R1;R2 + * + * Given the fixed assumptions C @ [0,cEnd) and a set of candidate minimizations U@[cEnd, uEnd) + * s.t. C \cup U is known to be in conflict ([0,uEnd) has a conflict), find a minimal + * subset of U, Delta, s.t. C \cup Delta is in conflict. + * + * Pre: + * [0, uEnd) is a set and is in conflict. + * uEnd <= assumptions.size() + * [0, cEnd) is in d_inSoi. + * + * Invariants: [0,cEnd) is never modified + * + * Post: + * [0, cEnd); [cEnd, deltaEnd) is in conflict + * [0, deltaEnd) is a set + * [0, deltaEnd) is in d_inSoi + */ +uint32_t SumOfInfeasibilitiesSPD::quickExplainRec(uint32_t cEnd, uint32_t uEnd){ + Assert(cEnd <= uEnd); + Assert(d_qeInUAndNotInSoi.empty()); + Assert(d_qeGreedyOrder.empty()); + + const Tableau::Entry* spoiler = NULL; + + if(d_soiVar != ARITHVAR_SENTINEL && d_linEq.selectSlackEntry(d_soiVar, false) == NULL){ + // already in conflict + return cEnd; + } + + Assert(cEnd < uEnd); + + // Phase 1 : Construct the conflict greedily + + for(uint32_t i = cEnd; i < uEnd; ++i){ + d_qeInUAndNotInSoi.add(d_qeConflict[i]); + } + if(d_soiVar == ARITHVAR_SENTINEL){ // special case for d_soiVar being empty + ArithVar first = d_qeConflict[cEnd]; + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, first); + d_qeInSoi.add(first); + d_qeInUAndNotInSoi.remove(first); + d_qeGreedyOrder.push_back(first); + } + while((spoiler = d_linEq.selectSlackEntry(d_soiVar, false)) != NULL){ + Assert(!d_qeInUAndNotInSoi.empty()); + + ArithVar nb = spoiler->getColVar(); + int oppositeSgn = -(spoiler->getCoefficient().sgn()); + Assert(oppositeSgn != 0); + + ArithVar basicWithOp = find_basic_in_sgns(d_qeSgns, nb, oppositeSgn, d_qeInUAndNotInSoi, true); + Assert(basicWithOp != ARITHVAR_SENTINEL); + + addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, basicWithOp); + d_qeInSoi.add(basicWithOp); + d_qeInUAndNotInSoi.remove(basicWithOp); + d_qeGreedyOrder.push_back(basicWithOp); + } + Assert(spoiler == NULL); + + // Compact the set u + uint32_t newEnd = cEnd + d_qeGreedyOrder.size(); + std::copy(d_qeGreedyOrder.begin(), d_qeGreedyOrder.end(), d_qeConflict.begin()+cEnd); + + d_qeInUAndNotInSoi.purge(); + d_qeGreedyOrder.clear(); + + // Phase 2 : Recursively determine the minimal set of rows + + uint32_t xPos = cEnd; + std::swap(d_qeGreedyOrder[xPos], d_qeGreedyOrder[newEnd - 1]); + uint32_t uBegin = xPos + 1; + uint32_t split = (newEnd - uBegin)/2 + uBegin; + + //assumptions : C @ [0, cEnd); X @ xPos; U1 @ [u1Begin, split); U2 @ [split, newEnd) + // [0, newEnd) == d_inSoi + + uint32_t compactU2; + if(split == newEnd){ // U2 is empty + compactU2 = newEnd; + }else{ + // Remove U2 from Soi + qeRemoveRange(split, newEnd); + // [0, split) == d_inSoi + + // pre assumptions: C + X + U1 @ [0,split); U2 [split, newEnd) + compactU2 = quickExplainRec(split, newEnd); + // post: + // assumptions: C + X + U1 @ [0, split); delta2 @ [split - compactU2) + // d_inSoi = [0, compactU2) + } + uint32_t deltaSize = compactU2 - split; + qeSwapRange(deltaSize, uBegin, split); + uint32_t d2End = uBegin+deltaSize; + // assumptions : C @ [0, cEnd); X @ xPos; delta2 @ [uBegin, d2End); U1 @ [d2End, compactU2) + // d_inSoi == [0, compactU2) + + uint32_t d1End; + if(d2End == compactU2){ // U1 is empty + d1End = d2End; + }else{ + qeRemoveRange(d2End, compactU2); + + //pre assumptions : C + X + delta2 @ [0, d2End); U1 @ [d2End, compactU2); + d1End = quickExplainRec(d2End, compactU2); + //post: + // assumptions : C + X + delta2 @ [0, d2End); delta1 @ [d2End, d1End); + // d_inSoi = [0, d1End) + } + //After both: + // d_inSoi == [0, d1End), C @ [0, cEnd); X + delta2 + delta 1 @ [xPos, d1End); + + Assert(d_qeInUAndNotInSoi.empty()); + Assert(d_qeGreedyOrder.empty()); + return d1End; +} + +void SumOfInfeasibilitiesSPD::quickExplain(){ + Assert(d_qeInSoi.empty()); + Assert(d_qeInUAndNotInSoi.empty()); + Assert(d_qeGreedyOrder.empty()); + Assert(d_soiVar == ARITHVAR_SENTINEL); + Assert(d_qeSgns.empty()); + + d_qeConflict.clear(); + d_errorSet.pushFocusInto(d_qeConflict); + + //cout << d_qeConflict.size() << " "; + uint32_t size = d_qeConflict.size(); + + if(size > 2){ + for(ErrorSet::focus_iterator iter = d_errorSet.focusBegin(), end = d_errorSet.focusEnd(); iter != end; ++iter){ + ArithVar e = *iter; + addRowSgns(d_qeSgns, e, d_errorSet.getSgn(e)); + } + uint32_t end = quickExplainRec(0u, size); + Assert(end <= d_qeConflict.size()); + Assert(d_soiVar != ARITHVAR_SENTINEL); + Assert(!d_qeInSoi.empty()); + + d_qeConflict.resize(end); + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + d_qeInSoi.purge(); + d_qeSgns.clear(); + } + + //cout << d_qeConflict.size() << endl; + + Assert(d_qeInSoi.empty()); + Assert(d_qeInUAndNotInSoi.empty()); + Assert(d_qeGreedyOrder.empty()); + Assert(d_soiVar == ARITHVAR_SENTINEL); + Assert(d_qeSgns.empty()); +} + +unsigned SumOfInfeasibilitiesSPD::trySet(const ArithVarVec& set){ + Assert(d_soiVar == ARITHVAR_SENTINEL); + bool success = false; + if(set.size() >= 2){ + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, set); + success = d_linEq.selectSlackEntry(d_soiVar, false) == NULL; + + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + } + return success ? set.size() : std::numeric_limits::max(); +} + +std::vector< ArithVarVec > SumOfInfeasibilitiesSPD::greedyConflictSubsets(){ + Trace("arith::greedyConflictSubsets") << "greedyConflictSubsets start" << endl; + + std::vector< ArithVarVec > subsets; + Assert(d_soiVar == ARITHVAR_SENTINEL); + + if(d_errorSize <= 2){ + ArithVarVec inError; + d_errorSet.pushFocusInto(inError); + + Assert(debugIsASet(inError)); + subsets.push_back(inError); + return subsets; + } + Assert(d_errorSize > 2); + + //sgns_table< , [basics] >; + // Phase 0: Construct the sgns table + sgn_table sgns; + DenseSet hasParticipated; //Has participated in a conflict + for(ErrorSet::focus_iterator iter = d_errorSet.focusBegin(), end = d_errorSet.focusEnd(); iter != end; ++iter){ + ArithVar e = *iter; + addRowSgns(sgns, e, d_errorSet.getSgn(e)); + + Trace("arith::greedyConflictSubsets") << "basic error var: " << e << endl; + if(TraceIsOn("arith::greedyConflictSubsets")){ + d_tableau.debugPrintIsBasic(e); + d_tableau.printBasicRow(e, Trace("arith::greedyConflictSubsets")); + } + } + + // Phase 1: Try to find at least 1 pair for every element + ArithVarVec tmp; + tmp.push_back(0); + tmp.push_back(0); + for(ErrorSet::focus_iterator iter = d_errorSet.focusBegin(), end = d_errorSet.focusEnd(); iter != end; ++iter){ + ArithVar e = *iter; + tmp[0] = e; + + int errSgn = d_errorSet.getSgn(e); + bool decreasing = errSgn < 0; + const Tableau::Entry* spoiler = d_linEq.selectSlackEntry(e, decreasing); + Assert(spoiler != NULL); + ArithVar nb = spoiler->getColVar(); + int oppositeSgn = -(errSgn * (spoiler->getCoefficient().sgn())); + + sgn_table::const_iterator opposites = find_sgns(sgns, nb, oppositeSgn); + Assert(opposites != sgns.end()); + + const ArithVarVec& choices = (*opposites).second; + for(ArithVarVec::const_iterator j = choices.begin(), jend = choices.end(); j != jend; ++j){ + ArithVar b = *j; + if(b < e){ continue; } + tmp[0] = e; + tmp[1] = b; + if(trySet(tmp) == 2){ + Trace("arith::greedyConflictSubsets") << "found a pair " << b << " " << e << endl; + hasParticipated.softAdd(b); + hasParticipated.softAdd(e); + Assert(debugIsASet(tmp)); + subsets.push_back(tmp); + ++(d_statistics.d_soiConflicts); + ++(d_statistics.d_hasToBeMinimal); + } + } + } + + + // Phase 2: If there is a variable that has not participated attempt to start a conflict + ArithVarVec possibleStarts; //List of elements that can be tried for starts. + d_errorSet.pushFocusInto(possibleStarts); + while(!possibleStarts.empty()){ + Assert(d_soiVar == ARITHVAR_SENTINEL); + + ArithVar v = possibleStarts.back(); + possibleStarts.pop_back(); + if(hasParticipated.isMember(v)){ continue; } + + hasParticipated.add(v); + + Assert(d_soiVar == ARITHVAR_SENTINEL); + //d_soiVar's row = \sumofinfeasibilites underConstruction + ArithVarVec underConstruction; + underConstruction.push_back(v); + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, v); + + Trace("arith::greedyConflictSubsets") << "trying " << v << endl; + + const Tableau::Entry* spoiler = NULL; + while( (spoiler = d_linEq.selectSlackEntry(d_soiVar, false)) != NULL){ + ArithVar nb = spoiler->getColVar(); + int oppositeSgn = -(spoiler->getCoefficient().sgn()); + Assert(oppositeSgn != 0); + + Trace("arith::greedyConflictSubsets") << "looking for " << nb << " " << oppositeSgn << endl; + + ArithVar basicWithOp = find_basic_in_sgns(sgns, nb, oppositeSgn, hasParticipated, false); + + if(basicWithOp == ARITHVAR_SENTINEL){ + Trace("arith::greedyConflictSubsets") << "search did not work for " << nb << endl; + // greedy construction has failed + break; + }else{ + Trace("arith::greedyConflictSubsets") << "found " << basicWithOp << endl; + + addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, basicWithOp); + hasParticipated.softAdd(basicWithOp); + underConstruction.push_back(basicWithOp); + } + } + if(spoiler == NULL){ + Trace("arith::greedyConflictSubsets") << "success" << endl; + //then underConstruction contains a conflicting subset + Assert(debugIsASet(underConstruction)); + subsets.push_back(underConstruction); + ++d_statistics.d_soiConflicts; + if(underConstruction.size() == 3){ + ++d_statistics.d_hasToBeMinimal; + }else{ + ++d_statistics.d_maybeNotMinimal; + } + }else{ + Trace("arith::greedyConflictSubsets") << "failure" << endl; + } + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + // if(false && spoiler == NULL){ + // ArithVarVec tmp; + // int smallest = tryAllSubsets(underConstruction, 0, tmp); + // cout << underConstruction.size() << " " << smallest << endl; + // Assert(smallest >= underConstruction.size()); + // if(smallest < underConstruction.size()){ + // exit(-1); + // } + // } + } + + Assert(d_soiVar == ARITHVAR_SENTINEL); + Trace("arith::greedyConflictSubsets") << "greedyConflictSubsets done" << endl; + return subsets; +} + +bool SumOfInfeasibilitiesSPD::generateSOIConflict(const ArithVarVec& subset){ + Assert(d_soiVar == ARITHVAR_SENTINEL); + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, subset); + Assert(!subset.empty()); + Assert(!d_conflictBuilder->underConstruction()); + + Trace("arith::generateSOIConflict") << "SumOfInfeasibilitiesSPD::generateSOIConflict(...) start" << endl; + + bool success = false; + + for(ArithVarVec::const_iterator iter = subset.begin(), end = subset.end(); iter != end; ++iter){ + ArithVar e = *iter; + ConstraintP violated = d_errorSet.getViolated(e); + Assert(violated != NullConstraint); + + int sgn = d_errorSet.getSgn(e); + const Rational& violatedCoeff = sgn > 0 ? d_negOne : d_posOne; + Trace("arith::generateSOIConflict") << "basic error var: " + << "(" << violatedCoeff << ")" + << " " << violated + << endl; + + + d_conflictBuilder->addConstraint(violated, violatedCoeff); + Assert(violated->hasProof()); + if(!success && !violated->negationHasProof()){ + success = true; + d_conflictBuilder->makeLastConsequent(); + } + } + + if(!success){ + // failure + d_conflictBuilder->reset(); + } else { + // pick a violated constraint arbitrarily. any of them may be selected for the conflict + Assert(d_conflictBuilder->underConstruction()); + Assert(d_conflictBuilder->consequentIsSet()); + + for(Tableau::RowIterator i = d_tableau.basicRowIterator(d_soiVar); !i.atEnd(); ++i){ + const Tableau::Entry& entry = *i; + ArithVar v = entry.getColVar(); + if(v == d_soiVar){ continue; } + const Rational& coeff = entry.getCoefficient(); + + ConstraintP c = (coeff.sgn() > 0) ? + d_variables.getUpperBoundConstraint(v) : + d_variables.getLowerBoundConstraint(v); + + Trace("arith::generateSOIConflict") << "non-basic var: " + << "(" << coeff << ")" + << " " << c + << endl; + d_conflictBuilder->addConstraint(c, coeff); + } + ConstraintCP conflicted = d_conflictBuilder->commitConflict(); + d_conflictChannel.raiseConflict(conflicted, + InferenceId::ARITH_CONF_SOI_SIMPLEX); + } + + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + Trace("arith::generateSOIConflict") << "SumOfInfeasibilitiesSPD::generateSOIConflict(...) done" << endl; + Assert(d_soiVar == ARITHVAR_SENTINEL); + Assert(!d_conflictBuilder->underConstruction()); + return success; +} + + +WitnessImprovement SumOfInfeasibilitiesSPD::SOIConflict(){ + Trace("arith::SOIConflict") << "SumOfInfeasibilitiesSPD::SOIConflict() start " + << ": |E| = " << d_errorSize << endl; + if(TraceIsOn("arith::SOIConflict")){ + d_errorSet.debugPrint(cout); + } + Trace("arith::SOIConflict") << endl; + + tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + + if (options().arith.soiQuickExplain) + { + quickExplain(); + generateSOIConflict(d_qeConflict); + } + else + { + vector subsets = greedyConflictSubsets(); + Assert(d_soiVar == ARITHVAR_SENTINEL); + bool anySuccess = false; + Assert(!subsets.empty()); + for(vector::const_iterator i = subsets.begin(), end = subsets.end(); i != end; ++i){ + const ArithVarVec& subset = *i; + Assert(debugIsASet(subset)); + anySuccess = generateSOIConflict(subset) || anySuccess; + //Node conflict = generateSOIConflict(subset); + //cout << conflict << endl; + + //reportConflict(conf); do not do this. We need a custom explanations! + //d_conflictChannel(conflict); + } + Assert(anySuccess); + } + Assert(d_soiVar == ARITHVAR_SENTINEL); + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization); + + //reportConflict(conf); do not do this. We need a custom explanations! + d_conflictVariables.add(d_soiVar); + + Trace("arith::SOIConflict") + << "SumOfInfeasibilitiesSPD::SOIConflict() end" << endl; + return ConflictFound; +} + +WitnessImprovement SumOfInfeasibilitiesSPD::soiRound() { + Assert(d_soiVar != ARITHVAR_SENTINEL); + + bool useBlands = degeneratePivotsInARow() >= s_maxDegeneratePivotsBeforeBlandsOnLeaving; + LinearEqualityModule::UpdatePreferenceFunction upf; + if(useBlands) { + upf = &LinearEqualityModule::preferWitness; + } else { + upf = &LinearEqualityModule::preferWitness; + } + + LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? + &LinearEqualityModule::minVarOrder : + &LinearEqualityModule::minRowLength; + bpf = &LinearEqualityModule::minVarOrder; + + UpdateInfo selected = selectUpdate(upf, bpf); + + if(selected.uninitialized()){ + Trace("selectFocusImproving") << "SOI is optimum, but we don't have sat/conflict yet" << endl; + return SOIConflict(); + }else{ + Assert(!selected.uninitialized()); + WitnessImprovement w = selected.getWitness(false); + Assert(debugCheckWitness(selected, w, false)); + + updateAndSignal(selected, w); + logPivot(w); + return w; + } +} + +Result::Status SumOfInfeasibilitiesSPD::sumOfInfeasibilities() +{ + TimerStat::CodeTimer codeTimer(d_statistics.d_soiTimer); + + Assert(d_sgnDisagreements.empty()); + Assert(d_pivotBudget != 0); + Assert(d_errorSize == d_errorSet.errorSize()); + Assert(d_errorSize > 0); + Assert(d_conflictVariables.empty()); + Assert(d_soiVar == ARITHVAR_SENTINEL); + + //d_scores.purge(); + d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiFocusConstructionTimer); + + + while(d_pivotBudget != 0 && d_errorSize > 0 && d_conflictVariables.empty()){ + Trace("dualLike") << "dualLike" << endl; + + Assert(d_errorSet.noSignals()); + // Possible outcomes: + // - conflict + // - budget was exhausted + // - focus went down + WitnessImprovement w = soiRound(); + Trace("dualLike") << "selectFocusImproving -> " << w << endl; + + Assert(d_errorSize == d_errorSet.errorSize()); + } + + + if(d_soiVar != ARITHVAR_SENTINEL){ + tearDownInfeasiblityFunction(d_statistics.d_soiFocusConstructionTimer, d_soiVar); + d_soiVar = ARITHVAR_SENTINEL; + } + + Assert(d_soiVar == ARITHVAR_SENTINEL); + if(!d_conflictVariables.empty()){ + return Result::UNSAT; + }else if(d_errorSet.errorEmpty()){ + Assert(d_errorSet.noSignals()); + return Result::SAT; + }else{ + Assert(d_pivotBudget == 0); + return Result::UNKNOWN; + } +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/soi_simplex.h b/src/theory/arith/linear/soi_simplex.h new file mode 100644 index 000000000..c8cfc649a --- /dev/null +++ b/src/theory/arith/linear/soi_simplex.h @@ -0,0 +1,227 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Morgan Deters + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * This is an implementation of the Simplex Module for the Simplex for + * DPLL(T) decision procedure. + * + * This implements the Simplex module for the Simpelx for DPLL(T) decision + * procedure. + * See the Simplex for DPLL(T) technical report for more background.(citation?) + * This shares with the theory a Tableau, and a PartialModel that: + * - satisfies the equalities in the Tableau, and + * - the assignment for the non-basic variables satisfies their bounds. + * This is required to either produce a conflict or satisifying PartialModel. + * Further, we require being told when a basic variable updates its value. + * + * During the Simplex search we maintain a queue of variables. + * The queue is required to contain all of the basic variables that voilate + * their bounds. + * As elimination from the queue is more efficient to be done lazily, + * we do not maintain that the queue of variables needs to be only basic + * variables or only variables that satisfy their bounds. + * + * The simplex procedure roughly follows Alberto's thesis. (citation?) + * There is one round of selecting using a heuristic pivoting rule. + * (See PreferenceFunction Documentation for the available options.) + * The non-basic variable is the one that appears in the fewest pivots. + * (Bruno says that Leonardo invented this first.) + * After this, Bland's pivot rule is invoked. + * + * During this proccess, we periodically inspect the queue of variables to + * 1) remove now extraneous extries, + * 2) detect conflicts that are "waiting" on the queue but may not be detected + * by the current queue heuristics, and + * 3) detect multiple conflicts. + * + * Conflicts are greedily slackened to use the weakest bounds that still + * produce the conflict. + * + * Extra things tracked atm: (Subject to change at Tim's whims) + * - A superset of all of the newly pivoted variables. + * - A queue of additional conflicts that were discovered by Simplex. + * These are theory valid and are currently turned into lemmas + */ + +#include "cvc5_private.h" + +#pragma once + +#include "theory/arith/linear/linear_equality.h" +#include "theory/arith/linear/simplex.h" +#include "theory/arith/linear/simplex_update.h" +#include "util/dense_map.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class SumOfInfeasibilitiesSPD : public SimplexDecisionProcedure { +public: + SumOfInfeasibilitiesSPD(Env& env, + LinearEqualityModule& linEq, + ErrorSet& errors, + RaiseConflict conflictChannel, + TempVarMalloc tvmalloc); + + Result::Status findModel(bool exactResult) override; + + // other error variables are dropping + WitnessImprovement dualLikeImproveError(ArithVar evar); + WitnessImprovement primalImproveError(ArithVar evar); + +private: + /** The current sum of infeasibilities variable. */ + ArithVar d_soiVar; + + // dual like + // - found conflict + // - satisfied error set + Result::Status sumOfInfeasibilities(); + + int32_t d_pivotBudget; + + WitnessImprovement d_prevWitnessImprovement; + uint32_t d_witnessImprovementInARow; + + uint32_t degeneratePivotsInARow() const; + + static constexpr uint32_t s_focusThreshold = 6; + static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnLeaving = 100; + static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnEntering = 10; + + DenseMap d_leavingCountSinceImprovement; + void increaseLeavingCount(ArithVar x){ + if(!d_leavingCountSinceImprovement.isKey(x)){ + d_leavingCountSinceImprovement.set(x,1); + }else{ + (d_leavingCountSinceImprovement.get(x))++; + } + } + LinearEqualityModule::UpdatePreferenceFunction selectLeavingFunction(ArithVar x){ + bool useBlands = d_leavingCountSinceImprovement.isKey(x) && + d_leavingCountSinceImprovement[x] >= s_maxDegeneratePivotsBeforeBlandsOnEntering; + if(useBlands) { + return &LinearEqualityModule::preferWitness; + } else { + return &LinearEqualityModule::preferWitness; + } + } + + void debugPrintSignal(ArithVar updated) const; + + ArithVarVec d_sgnDisagreements; + + void logPivot(WitnessImprovement w); + + void updateAndSignal(const UpdateInfo& selected, WitnessImprovement w); + + UpdateInfo selectUpdate(LinearEqualityModule::UpdatePreferenceFunction upf, + LinearEqualityModule::VarPreferenceFunction bpf); + + + // UpdateInfo selectUpdateForDualLike(ArithVar basic){ + // TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForDualLike); + + // LinearEqualityModule::UpdatePreferenceFunction upf = + // &LinearEqualityModule::preferWitness; + // LinearEqualityModule::VarPreferenceFunction bpf = + // &LinearEqualityModule::minVarOrder; + // return selectPrimalUpdate(basic, upf, bpf); + // } + + // UpdateInfo selectUpdateForPrimal(ArithVar basic, bool useBlands){ + // TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForPrimal); + + // LinearEqualityModule::UpdatePreferenceFunction upf = useBlands ? + // &LinearEqualityModule::preferWitness: + // &LinearEqualityModule::preferWitness; + + // LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? + // &LinearEqualityModule::minVarOrder : + // &LinearEqualityModule::minRowLength; + // bpf = &LinearEqualityModule::minVarOrder; + + // return selectPrimalUpdate(basic, upf, bpf); + // } + // WitnessImprovement selectFocusImproving() ; + WitnessImprovement soiRound(); + WitnessImprovement SOIConflict(); + std::vector< ArithVarVec > greedyConflictSubsets(); + bool generateSOIConflict(const ArithVarVec& subset); + + // WitnessImprovement focusUsingSignDisagreements(ArithVar basic); + // WitnessImprovement focusDownToLastHalf(); + // WitnessImprovement adjustFocusShrank(const ArithVarVec& drop); + // WitnessImprovement focusDownToJust(ArithVar v); + + + void adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges); + + /** + * This is the main simplex for DPLL(T) loop. + * It runs for at most maxIterations. + * + * Returns true iff it has found a conflict. + * d_conflictVariable will be set and the conflict for this row is reported. + */ + bool searchForFeasibleSolution(uint32_t maxIterations); + + bool initialProcessSignals(){ + TimerStat &timer = d_statistics.d_initialSignalsTime; + IntStat& conflictStat = d_statistics.d_initialConflicts; + return standardProcessSignals(timer, conflictStat); + } + + void quickExplain(); + DenseSet d_qeInSoi; + DenseSet d_qeInUAndNotInSoi; + ArithVarVec d_qeConflict; + ArithVarVec d_qeGreedyOrder; + sgn_table d_qeSgns; + + uint32_t quickExplainRec(uint32_t cEnd, uint32_t uEnd); + void qeAddRange(uint32_t begin, uint32_t end); + void qeRemoveRange(uint32_t begin, uint32_t end); + void qeSwapRange(uint32_t N, uint32_t r, uint32_t s); + + unsigned trySet(const ArithVarVec& set); + unsigned tryAllSubsets(const ArithVarVec& set, unsigned depth, ArithVarVec& tmp); + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + TimerStat d_initialSignalsTime; + IntStat d_initialConflicts; + + IntStat d_soiFoundUnsat; + IntStat d_soiFoundSat; + IntStat d_soiMissed; + + IntStat d_soiConflicts; + IntStat d_hasToBeMinimal; + IntStat d_maybeNotMinimal; + + TimerStat d_soiTimer; + TimerStat d_soiFocusConstructionTimer; + TimerStat d_soiConflictMinimization; + TimerStat d_selectUpdateForSOI; + + ReferenceStat d_finalCheckPivotCounter; + + Statistics(const std::string& name, uint32_t& pivots); + } d_statistics; +};/* class FCSimplexDecisionProcedure */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/tableau.cpp b/src/theory/arith/linear/tableau.cpp new file mode 100644 index 000000000..324f5df87 --- /dev/null +++ b/src/theory/arith/linear/tableau.cpp @@ -0,0 +1,196 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "base/output.h" +#include "theory/arith/linear/tableau.h" + +using namespace std; +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + + +void Tableau::pivot(ArithVar oldBasic, ArithVar newBasic, CoefficientChangeCallback& cb){ + Assert(isBasic(oldBasic)); + Assert(!isBasic(newBasic)); + Assert(d_mergeBuffer.empty()); + + Trace("tableau") << "Tableau::pivot(" << oldBasic <<", " << newBasic <<")" << endl; + + RowIndex ridx = basicToRowIndex(oldBasic); + + rowPivot(oldBasic, newBasic, cb); + Assert(ridx == basicToRowIndex(newBasic)); + + loadRowIntoBuffer(ridx); + + ColIterator colIter = colIterator(newBasic); + while(!colIter.atEnd()){ + EntryID id = colIter.getID(); + Entry& entry = d_entries.get(id); + + ++colIter; //needs to be incremented before the variable is removed + if(entry.getRowIndex() == ridx){ continue; } + + RowIndex to = entry.getRowIndex(); + Rational coeff = entry.getCoefficient(); + if(cb.canUseRow(to)){ + rowPlusBufferTimesConstant(to, coeff, cb); + }else{ + rowPlusBufferTimesConstant(to, coeff); + } + } + clearBuffer(); + + //Clear the column for used for this variable + + Assert(d_mergeBuffer.empty()); + Assert(!isBasic(oldBasic)); + Assert(isBasic(newBasic)); + Assert(getColLength(newBasic) == 1); +} + +/** + * Changes basic to newbasic (a variable on the row). + */ +void Tableau::rowPivot(ArithVar basicOld, ArithVar basicNew, CoefficientChangeCallback& cb){ + Assert(isBasic(basicOld)); + Assert(!isBasic(basicNew)); + + RowIndex rid = basicToRowIndex(basicOld); + + EntryID newBasicID = findOnRow(rid, basicNew); + + Assert(newBasicID != ENTRYID_SENTINEL); + + Tableau::Entry& newBasicEntry = d_entries.get(newBasicID); + const Rational& a_rs = newBasicEntry.getCoefficient(); + int a_rs_sgn = a_rs.sgn(); + Rational negInverseA_rs = -(a_rs.inverse()); + + for(RowIterator i = basicRowIterator(basicOld); !i.atEnd(); ++i){ + EntryID id = i.getID(); + Tableau::Entry& entry = d_entries.get(id); + + entry.getCoefficient() *= negInverseA_rs; + } + + d_basic2RowIndex.remove(basicOld); + d_basic2RowIndex.set(basicNew, rid); + d_rowIndex2basic.set(rid, basicNew); + + cb.multiplyRow(rid, -a_rs_sgn); +} + +void Tableau::addRow(ArithVar basic, + const std::vector& coefficients, + const std::vector& variables) +{ + Assert(basic < getNumColumns()); + Assert(debugIsASet(variables)); + Assert(coefficients.size() == variables.size()); + Assert(!isBasic(basic)); + + RowIndex newRow = Matrix::addRow(coefficients, variables); + addEntry(newRow, basic, Rational(-1)); + + Assert(!d_basic2RowIndex.isKey(basic)); + Assert(!d_rowIndex2basic.isKey(newRow)); + + d_basic2RowIndex.set(basic, newRow); + d_rowIndex2basic.set(newRow, basic); + + + if(TraceIsOn("matrix")){ printMatrix(); } + + NoEffectCCCB noeffect; + NoEffectCCCB* nep = &noeffect; + CoefficientChangeCallback* cccb = static_cast(nep); + + vector::const_iterator coeffIter = coefficients.begin(); + vector::const_iterator varsIter = variables.begin(); + vector::const_iterator varsEnd = variables.end(); + for(; varsIter != varsEnd; ++coeffIter, ++varsIter){ + ArithVar var = *varsIter; + + if(isBasic(var)){ + Rational coeff = *coeffIter; + + RowIndex ri = basicToRowIndex(var); + + loadRowIntoBuffer(ri); + rowPlusBufferTimesConstant(newRow, coeff, *cccb); + clearBuffer(); + } + } + + if(TraceIsOn("matrix")) { printMatrix(); } + + Assert(debugNoZeroCoefficients(newRow)); + Assert(debugMatchingCountsForRow(newRow)); + Assert(getColLength(basic) == 1); +} + +void Tableau::removeBasicRow(ArithVar basic){ + RowIndex rid = basicToRowIndex(basic); + + removeRow(rid); + d_basic2RowIndex.remove(basic); + d_rowIndex2basic.remove(rid); +} + +void Tableau::substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult, CoefficientChangeCallback& cb){ + if(!mult.isZero()){ + RowIndex to_idx = basicToRowIndex(to); + addEntry(to_idx, from, mult); // Add an entry to be cancelled out + RowIndex from_idx = basicToRowIndex(from); + + cb.update(to_idx, from, 0, mult.sgn()); + + loadRowIntoBuffer(from_idx); + rowPlusBufferTimesConstant(to_idx, mult, cb); + clearBuffer(); + } +} + +uint32_t Tableau::rowComplexity(ArithVar basic) const{ + uint32_t complexity = 0; + for(RowIterator i = basicRowIterator(basic); !i.atEnd(); ++i){ + const Entry& e = *i; + complexity += e.getCoefficient().complexity(); + } + return complexity; +} + +double Tableau::avgRowComplexity() const{ + double sum = 0; + uint32_t rows = 0; + for(BasicIterator i = beginBasic(), i_end = endBasic(); i != i_end; ++i){ + sum += rowComplexity(*i); + rows++; + } + return (rows == 0) ? 0 : (sum/rows); +} + +void Tableau::printBasicRow(ArithVar basic, std::ostream& out){ + printRow(basicToRowIndex(basic), out); +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/tableau.h b/src/theory/arith/linear/tableau.h new file mode 100644 index 000000000..7bbc0d6ed --- /dev/null +++ b/src/theory/arith/linear/tableau.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Morgan Deters + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include + +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/matrix.h" +#include "util/dense_map.h" +#include "util/rational.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +/** + * A Tableau is a Rational matrix that keeps its rows in solved form. + * Each row has a basic variable with coefficient -1 that is solved. + * Tableau is optimized for pivoting. + * The tableau should only be updated via pivot calls. + */ +class Tableau : public Matrix { +public: +private: + typedef DenseMap BasicToRowMap; + // Set of all of the basic variables in the tableau. + // ArithVarMap : ArithVar |-> RowIndex + BasicToRowMap d_basic2RowIndex; + + // RowIndex |-> Basic Variable + typedef DenseMap RowIndexToBasicMap; + RowIndexToBasicMap d_rowIndex2basic; + +public: + + Tableau() : Matrix(Rational(0)) {} + + typedef Matrix::ColIterator ColIterator; + typedef Matrix::RowIterator RowIterator; + typedef BasicToRowMap::const_iterator BasicIterator; + + typedef MatrixEntry Entry; + + bool isBasic(ArithVar v) const{ + return d_basic2RowIndex.isKey(v); + } + + void debugPrintIsBasic(ArithVar v) const { + if(isBasic(v)){ + Trace("model") << v << " is basic." << std::endl; + }else{ + Trace("model") << v << " is non-basic." << std::endl; + } + } + + BasicIterator beginBasic() const { + return d_basic2RowIndex.begin(); + } + BasicIterator endBasic() const { + return d_basic2RowIndex.end(); + } + + RowIndex basicToRowIndex(ArithVar x) const { + return d_basic2RowIndex[x]; + } + + ArithVar rowIndexToBasic(RowIndex rid) const { + Assert(d_rowIndex2basic.isKey(rid)); + return d_rowIndex2basic[rid]; + } + + ColIterator colIterator(ArithVar x) const { + return getColumn(x).begin(); + } + + RowIterator ridRowIterator(RowIndex rid) const { + return getRow(rid).begin(); + } + + RowIterator basicRowIterator(ArithVar basic) const { + return ridRowIterator(basicToRowIndex(basic)); + } + + const Entry& basicFindEntry(ArithVar basic, ArithVar col) const { + return findEntry(basicToRowIndex(basic), col); + } + + /** + * Adds a row to the tableau. + * The new row is equivalent to: + * basicVar = \f$\sum_i\f$ coeffs[i] * variables[i] + * preconditions: + * basicVar is already declared to be basic + * basicVar does not have a row associated with it in the tableau. + * + * Note: each variables[i] does not have to be non-basic. + * Pivoting will be mimicked if it is basic. + */ + void addRow(ArithVar basicVar, + const std::vector& coeffs, + const std::vector& variables); + + /** + * preconditions: + * x_r is basic, + * x_s is non-basic, and + * a_rs != 0. + */ + void pivot(ArithVar basicOld, ArithVar basicNew, CoefficientChangeCallback& cb); + + void removeBasicRow(ArithVar basic); + + uint32_t basicRowLength(ArithVar basic) const{ + RowIndex ridx = basicToRowIndex(basic); + return getRowLength(ridx); + } + + /** + * to += mult * from + * replacing from with its row. + */ + void substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult, CoefficientChangeCallback& cb); + + void directlyAddToCoefficient(ArithVar rowVar, ArithVar col, const Rational& mult, CoefficientChangeCallback& cb){ + RowIndex ridx = basicToRowIndex(rowVar); + manipulateRowEntry(ridx, col, mult, cb); + } + + /* Returns the complexity of a row in the tableau. */ + uint32_t rowComplexity(ArithVar basic) const; + + /* Returns the average complexity of the rows in the tableau. */ + double avgRowComplexity() const; + + void printBasicRow(ArithVar basic, std::ostream& out); + +private: + /* Changes the basic variable on the row for basicOld to basicNew. */ + void rowPivot(ArithVar basicOld, ArithVar basicNew, CoefficientChangeCallback& cb); + +};/* class Tableau */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/tableau_sizes.cpp b/src/theory/arith/linear/tableau_sizes.cpp new file mode 100644 index 000000000..a7c8f9e88 --- /dev/null +++ b/src/theory/arith/linear/tableau_sizes.cpp @@ -0,0 +1,37 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "base/output.h" +#include "theory/arith/linear/tableau_sizes.h" +#include "theory/arith/linear/tableau.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +uint32_t TableauSizes::getRowLength(ArithVar b) const { + return d_tab->basicRowLength(b); +} + +uint32_t TableauSizes::getColumnLength(ArithVar x) const { + return d_tab->getColLength(x); +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/tableau_sizes.h b/src/theory/arith/linear/tableau_sizes.h new file mode 100644 index 000000000..3fbd0c14f --- /dev/null +++ b/src/theory/arith/linear/tableau_sizes.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "cvc5_private.h" + +#pragma once + +#include "theory/arith/linear/arithvar.h" + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +class Tableau; + +class TableauSizes { +private: + const Tableau* d_tab; +public: + TableauSizes(const Tableau* tab): d_tab(tab){} + + uint32_t getRowLength(ArithVar b) const; + uint32_t getColumnLength(ArithVar x) const; +}; /* TableauSizes */ + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/theory_arith_private.cpp b/src/theory/arith/linear/theory_arith_private.cpp new file mode 100644 index 000000000..04ebdeffb --- /dev/null +++ b/src/theory/arith/linear/theory_arith_private.cpp @@ -0,0 +1,4999 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Gereon Kremer, Alex Ozdemir + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#include "theory/arith/linear/theory_arith_private.h" + +#include +#include +#include +#include + +#include "base/output.h" +#include "context/cdhashset.h" +#include "context/cdinsert_hashmap.h" +#include "context/cdlist.h" +#include "context/cdqueue.h" +#include "context/context.h" +#include "expr/kind.h" +#include "expr/metakind.h" +#include "expr/node.h" +#include "expr/node_algorithm.h" +#include "expr/node_builder.h" +#include "expr/skolem_manager.h" +#include "options/arith_options.h" +#include "options/base_options.h" +#include "options/smt_options.h" +#include "preprocessing/util/ite_utilities.h" +#include "proof/proof_generator.h" +#include "proof/proof_node_manager.h" +#include "proof/proof_rule.h" +#include "smt/logic_exception.h" +#include "smt/smt_statistics_registry.h" +#include "smt_util/boolean_simplification.h" +#include "theory/arith/linear/approx_simplex.h" +#include "theory/arith/arith_rewriter.h" +#include "theory/arith/linear/arith_static_learner.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/congruence_manager.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/linear/cut_log.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/linear/dio_solver.h" +#include "theory/arith/linear/linear_equality.h" +#include "theory/arith/linear/matrix.h" +#include "theory/arith/nl/nonlinear_extension.h" +#include "theory/arith/linear/normal_form.h" +#include "theory/arith/linear/partial_model.h" +#include "theory/arith/linear/simplex.h" +#include "theory/arith/theory_arith.h" +#include "theory/ext_theory.h" +#include "theory/quantifiers/fmf/bounded_integers.h" +#include "theory/rewriter.h" +#include "theory/theory_model.h" +#include "theory/trust_substitutions.h" +#include "theory/valuation.h" +#include "util/dense_map.h" +#include "util/integer.h" +#include "util/random.h" +#include "util/rational.h" +#include "util/result.h" +#include "util/statistics_stats.h" + +using namespace std; +using namespace cvc5::internal::kind; + +namespace cvc5::internal { +namespace theory { +namespace arith::linear { + +static Node toSumNode(const ArithVariables& vars, const DenseMap& sum); +static bool complexityBelow(const DenseMap& row, uint32_t cap); + +TheoryArithPrivate::TheoryArithPrivate(TheoryArith& containing, + Env& env, + BranchAndBound& bab) + : EnvObj(env), + d_containing(containing), + d_foundNl(false), + d_rowTracking(), + d_bab(bab), + d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager() + : nullptr), + d_checker(), + d_pfGen(new EagerProofGenerator(d_pnm, userContext())), + d_constraintDatabase(d_env, + d_partialModel, + d_congruenceManager, + RaiseConflict(*this), + d_pfGen.get()), + d_qflraStatus(Result::UNKNOWN), + d_unknownsInARow(0), + d_hasDoneWorkSinceCut(false), + d_learner(userContext()), + d_assertionsThatDoNotMatchTheirLiterals(context()), + d_nextIntegerCheckVar(0), + d_constantIntegerVariables(context()), + d_diseqQueue(context(), false), + d_currentPropagationList(), + d_learnedBounds(context()), + d_preregisteredNodes(context()), + d_partialModel(context(), DeltaComputeCallback(*this)), + d_errorSet( + d_partialModel, TableauSizes(&d_tableau), BoundCountingLookup(*this)), + d_tableau(), + d_linEq(d_partialModel, + d_tableau, + d_rowTracking, + BasicVarModelUpdateCallBack(*this)), + d_diosolver(env), + d_restartsCounter(0), + d_tableauSizeHasBeenModified(false), + d_tableauResetDensity(1.6), + d_tableauResetPeriod(10), + d_conflicts(context()), + d_blackBoxConflict(context(), Node::null()), + d_blackBoxConflictPf(context(), std::shared_ptr(nullptr)), + d_congruenceManager(d_env, + d_constraintDatabase, + SetupLiteralCallBack(*this), + d_partialModel, + RaiseEqualityEngineConflict(*this)), + d_cmEnabled(context(), options().arith.arithCongMan), + + d_dualSimplex( + env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_fcSimplex( + env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_soiSimplex( + env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_attemptSolSimplex( + env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), + d_pass1SDP(NULL), + d_otherSDP(NULL), + d_lastContextIntegerAttempted(context(), -1), + + d_DELTA_ZERO(0), + d_approxCuts(context()), + d_fullCheckCounter(0), + d_cutCount(context(), 0), + d_cutInContext(context()), + d_likelyIntegerInfeasible(context(), false), + d_guessedCoeffSet(context(), false), + d_guessedCoeffs(), + d_treeLog(NULL), + d_replayVariables(), + d_replayConstraints(), + d_lhsTmp(), + d_approxStats(NULL), + d_attemptSolveIntTurnedOff(userContext(), 0), + d_dioSolveResources(0), + d_solveIntMaybeHelp(0u), + d_solveIntAttempts(0u), + d_newFacts(false), + d_previousStatus(Result::UNKNOWN), + d_statistics(statisticsRegistry(), "theory::arith::") +{ +} + +TheoryArithPrivate::~TheoryArithPrivate(){ + if(d_treeLog != NULL){ delete d_treeLog; } + if(d_approxStats != NULL) { delete d_approxStats; } +} + +bool TheoryArithPrivate::needsEqualityEngine(EeSetupInfo& esi) +{ + if (!d_cmEnabled) + { + return false; + } + return d_congruenceManager.needsEqualityEngine(esi); +} +void TheoryArithPrivate::finishInit() +{ + if (d_cmEnabled) + { + eq::EqualityEngine* ee = d_containing.getEqualityEngine(); + Assert(ee != nullptr); + d_congruenceManager.finishInit(ee); + } +} + +static bool contains(const ConstraintCPVec& v, ConstraintP con){ + for(unsigned i = 0, N = v.size(); i < N; ++i){ + if(v[i] == con){ + return true; + } + } + return false; +} +static void drop( ConstraintCPVec& v, ConstraintP con){ + size_t readPos, writePos, N; + for(readPos = 0, writePos = 0, N = v.size(); readPos < N; ++readPos){ + ConstraintCP curr = v[readPos]; + if(curr != con){ + v[writePos] = curr; + writePos++; + } + } + v.resize(writePos); +} + + +static void resolve(ConstraintCPVec& buf, ConstraintP c, const ConstraintCPVec& pos, const ConstraintCPVec& neg){ + unsigned posPos CVC5_UNUSED = pos.size(); + for(unsigned i = 0, N = pos.size(); i < N; ++i){ + if(pos[i] == c){ + posPos = i; + }else{ + buf.push_back(pos[i]); + } + } + Assert(posPos < pos.size()); + ConstraintP negc = c->getNegation(); + unsigned negPos CVC5_UNUSED = neg.size(); + for(unsigned i = 0, N = neg.size(); i < N; ++i){ + if(neg[i] == negc){ + negPos = i; + }else{ + buf.push_back(neg[i]); + } + } + Assert(negPos < neg.size()); +} + +TheoryArithPrivate::ModelException::ModelException(TNode n, const char* msg) +{ + stringstream ss; + ss << "Cannot construct a model for " << n << " as " << endl << msg; + setMessage(ss.str()); +} +TheoryArithPrivate::ModelException::~ModelException() {} + +TheoryArithPrivate::Statistics::Statistics(StatisticsRegistry& reg, + const std::string& name) + : d_statAssertUpperConflicts( + reg.registerInt(name + "AssertUpperConflicts")), + d_statAssertLowerConflicts( + reg.registerInt(name + "AssertLowerConflicts")), + d_statUserVariables(reg.registerInt(name + "UserVariables")), + d_statAuxiliaryVariables(reg.registerInt(name + "AuxiliaryVariables")), + d_statDisequalitySplits(reg.registerInt(name + "DisequalitySplits")), + d_statDisequalityConflicts( + reg.registerInt(name + "DisequalityConflicts")), + d_simplifyTimer(reg.registerTimer(name + "simplifyTimer")), + d_staticLearningTimer(reg.registerTimer(name + "staticLearningTimer")), + d_presolveTime(reg.registerTimer(name + "presolveTime")), + d_newPropTime(reg.registerTimer(name + "newPropTimer")), + d_externalBranchAndBounds( + reg.registerInt(name + "externalBranchAndBounds")), + d_initialTableauSize(reg.registerInt(name + "initialTableauSize")), + d_currSetToSmaller(reg.registerInt(name + "currSetToSmaller")), + d_smallerSetToCurr(reg.registerInt(name + "smallerSetToCurr")), + d_restartTimer(reg.registerTimer(name + "restartTimer")), + d_boundComputationTime(reg.registerTimer(name + "bound::time")), + d_boundComputations(reg.registerInt(name + "bound::boundComputations")), + d_boundPropagations(reg.registerInt(name + "bound::boundPropagations")), + d_unknownChecks(reg.registerInt(name + "status::unknowns")), + d_maxUnknownsInARow(reg.registerInt(name + "status::maxUnknownsInARow")), + d_avgUnknownsInARow( + reg.registerAverage(name + "status::avgUnknownsInARow")), + d_revertsOnConflicts( + reg.registerInt(name + "status::revertsOnConflicts")), + d_commitsOnConflicts( + reg.registerInt(name + "status::commitsOnConflicts")), + d_nontrivialSatChecks( + reg.registerInt(name + "status::nontrivialSatChecks")), + d_replayLogRecCount(reg.registerInt(name + "z::approx::replay::rec")), + d_replayLogRecConflictEscalation( + reg.registerInt(name + "z::approx::replay::rec::escalation")), + d_replayLogRecEarlyExit( + reg.registerInt(name + "z::approx::replay::rec::earlyexit")), + d_replayBranchCloseFailures(reg.registerInt( + name + "z::approx::replay::rec::branch::closefailures")), + d_replayLeafCloseFailures(reg.registerInt( + name + "z::approx::replay::rec::leaf::closefailures")), + d_replayBranchSkips( + reg.registerInt(name + "z::approx::replay::rec::branch::skips")), + d_mirCutsAttempted( + reg.registerInt(name + "z::approx::cuts::mir::attempted")), + d_gmiCutsAttempted( + reg.registerInt(name + "z::approx::cuts::gmi::attempted")), + d_branchCutsAttempted( + reg.registerInt(name + "z::approx::cuts::branch::attempted")), + d_cutsReconstructed( + reg.registerInt(name + "z::approx::cuts::reconstructed")), + d_cutsReconstructionFailed( + reg.registerInt(name + "z::approx::cuts::reconstructed::failed")), + d_cutsProven(reg.registerInt(name + "z::approx::cuts::proofs")), + d_cutsProofFailed( + reg.registerInt(name + "z::approx::cuts::proofs::failed")), + d_mipReplayLemmaCalls( + reg.registerInt(name + "z::approx::external::calls")), + d_mipExternalCuts(reg.registerInt(name + "z::approx::external::cuts")), + d_mipExternalBranch( + reg.registerInt(name + "z::approx::external::branches")), + d_inSolveInteger(reg.registerInt(name + "z::approx::inSolverInteger")), + d_branchesExhausted( + reg.registerInt(name + "z::approx::exhausted::branches")), + d_execExhausted(reg.registerInt(name + "z::approx::exhausted::exec")), + d_pivotsExhausted(reg.registerInt(name + "z::approx::exhausted::pivots")), + d_panicBranches(reg.registerInt(name + "z::arith::paniclemmas")), + d_relaxCalls(reg.registerInt(name + "z::arith::relax::calls")), + d_relaxLinFeas(reg.registerInt(name + "z::arith::relax::feasible::res")), + d_relaxLinFeasFailures( + reg.registerInt(name + "z::arith::relax::feasible::failures")), + d_relaxLinInfeas(reg.registerInt(name + "z::arith::relax::infeasible")), + d_relaxLinInfeasFailures( + reg.registerInt(name + "z::arith::relax::infeasible::failures")), + d_relaxLinExhausted(reg.registerInt(name + "z::arith::relax::exhausted")), + d_relaxOthers(reg.registerInt(name + "z::arith::relax::other")), + d_applyRowsDeleted( + reg.registerInt(name + "z::arith::cuts::applyRowsDeleted")), + d_replaySimplexTimer( + reg.registerTimer(name + "z::approx::replay::simplex::timer")), + d_replayLogTimer( + reg.registerTimer(name + "z::approx::replay::log::timer")), + d_solveIntTimer(reg.registerTimer(name + "z::solveInt::timer")), + d_solveRealRelaxTimer( + reg.registerTimer(name + "z::solveRealRelax::timer")), + d_solveIntCalls(reg.registerInt(name + "z::solveInt::calls")), + d_solveStandardEffort( + reg.registerInt(name + "z::solveInt::calls::standardEffort")), + d_approxDisabled(reg.registerInt(name + "z::approxDisabled")), + d_replayAttemptFailed(reg.registerInt(name + "z::replayAttemptFailed")), + d_cutsRejectedDuringReplay( + reg.registerInt(name + "z::approx::replay::cuts::rejected")), + d_cutsRejectedDuringLemmas( + reg.registerInt(name + "z::approx::external::cuts::rejected")), + d_satPivots(reg.registerHistogram(name + "pivots::sat")), + d_unsatPivots(reg.registerHistogram(name + "pivots::unsat")), + d_unknownPivots( + reg.registerHistogram(name + "pivots::unknown")), + d_solveIntModelsAttempts( + reg.registerInt(name + "z::solveInt::models::attempts")), + d_solveIntModelsSuccessful( + reg.registerInt(name + "zzz::solveInt::models::successful")), + d_mipTimer(reg.registerTimer(name + "z::approx::mip::timer")), + d_lpTimer(reg.registerTimer(name + "z::approx::lp::timer")), + d_mipProofsAttempted(reg.registerInt(name + "z::mip::proofs::attempted")), + d_mipProofsSuccessful( + reg.registerInt(name + "z::mip::proofs::successful")), + d_numBranchesFailed( + reg.registerInt(name + "z::mip::branch::proof::failed")) +{ +} + +bool complexityBelow(const DenseMap& row, uint32_t cap){ + DenseMap::const_iterator riter, rend; + for(riter=row.begin(), rend=row.end(); riter != rend; ++riter){ + ArithVar v = *riter; + const Rational& q = row[v]; + if(q.complexity() > cap){ + return false; + } + } + return true; +} + +bool TheoryArithPrivate::isProofEnabled() const +{ + return d_pnm != nullptr; +} + +void TheoryArithPrivate::raiseConflict(ConstraintCP a, InferenceId id){ + Assert(a->inConflict()); + Assert(id != InferenceId::UNKNOWN) + << "Must provide an inference id in TheoryArithPrivate::raiseConflict"; + d_conflicts.push_back(std::make_pair(a, id)); +} + +void TheoryArithPrivate::raiseBlackBoxConflict(Node bb, + std::shared_ptr pf) +{ + Trace("arith::bb") << "raiseBlackBoxConflict: " << bb << std::endl; + if (d_blackBoxConflict.get().isNull()) + { + if (isProofEnabled()) + { + Trace("arith::bb") << " with proof " << pf << std::endl; + d_blackBoxConflictPf.set(pf); + } + d_blackBoxConflict = bb; + } +} + +bool TheoryArithPrivate::anyConflict() const +{ + return !conflictQueueEmpty() || !d_blackBoxConflict.get().isNull(); +} + +void TheoryArithPrivate::revertOutOfConflict(){ + d_partialModel.revertAssignmentChanges(); + clearUpdates(); + d_currentPropagationList.clear(); +} + +void TheoryArithPrivate::clearUpdates(){ + d_updatedBounds.purge(); +} + +void TheoryArithPrivate::zeroDifferenceDetected(ArithVar x){ + if(d_cmEnabled){ + Assert(d_congruenceManager.isWatchedVariable(x)); + Assert(d_partialModel.upperBoundIsZero(x)); + Assert(d_partialModel.lowerBoundIsZero(x)); + + ConstraintP lb = d_partialModel.getLowerBoundConstraint(x); + ConstraintP ub = d_partialModel.getUpperBoundConstraint(x); + + if(lb->isEquality()){ + d_congruenceManager.watchedVariableIsZero(lb); + }else if(ub->isEquality()){ + d_congruenceManager.watchedVariableIsZero(ub); + }else{ + d_congruenceManager.watchedVariableIsZero(lb, ub); + } + } +} + +bool TheoryArithPrivate::getSolveIntegerResource(){ + if(d_attemptSolveIntTurnedOff > 0){ + d_attemptSolveIntTurnedOff = d_attemptSolveIntTurnedOff - 1; + return false; + }else{ + return true; + } +} + +bool TheoryArithPrivate::getDioCuttingResource(){ + if(d_dioSolveResources > 0){ + d_dioSolveResources--; + if(d_dioSolveResources == 0){ + d_dioSolveResources = -options().arith.rrTurns; + } + return true; + }else{ + d_dioSolveResources++; + if(d_dioSolveResources >= 0){ + d_dioSolveResources = options().arith.dioSolverTurns; + } + return false; + } +} + +/* procedure AssertLower( x_i >= c_i ) */ +bool TheoryArithPrivate::AssertLower(ConstraintP constraint){ + Assert(constraint != NullConstraint); + Assert(constraint->isLowerBound()); + Assert(constraint->isTrue()); + Assert(!constraint->negationHasProof()); + + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Trace("arith") << "AssertLower(" << x_i << " " << c_i << ")"<< std::endl; + + Assert(!isInteger(x_i) || c_i.isIntegral()); + + //TODO Relax to less than? + if(d_partialModel.lessThanLowerBound(x_i, c_i)){ + return false; //sat + } + + int cmpToUB = d_partialModel.cmpToUpperBound(x_i, c_i); + if(cmpToUB > 0){ // c_i < \lowerbound(x_i) + ConstraintP ubc = d_partialModel.getUpperBoundConstraint(x_i); + ConstraintP negation = constraint->getNegation(); + negation->impliedByUnate(ubc, true); + + raiseConflict(constraint, InferenceId::ARITH_CONF_LOWER); + + ++(d_statistics.d_statAssertLowerConflicts); + return true; + }else if(cmpToUB == 0){ + if(isInteger(x_i)){ + d_constantIntegerVariables.push_back(x_i); + Trace("dio::push") << "dio::push " << x_i << endl; + } + ConstraintP ub = d_partialModel.getUpperBoundConstraint(x_i); + + if(d_cmEnabled){ + if(!d_congruenceManager.isWatchedVariable(x_i) || c_i.sgn() != 0){ + // if it is not a watched variable report it + // if it is is a watched variable and c_i == 0, + // let zeroDifferenceDetected(x_i) catch this + d_congruenceManager.equalsConstant(constraint, ub); + } + } + + const ValueCollection& vc = constraint->getValueCollection(); + if(vc.hasEquality()){ + Assert(vc.hasDisequality()); + ConstraintP eq = vc.getEquality(); + ConstraintP diseq = vc.getDisequality(); + // x <= b, x >= b |= x = b + // (x > b or x < b or x = b) + Trace("arith::eq") << "lb == ub, propagate eq" << eq << endl; + bool triConflict = diseq->isTrue(); + + if(!eq->isTrue()){ + eq->impliedByTrichotomy(constraint, ub, triConflict); + eq->tryToPropagate(); + } + if(triConflict){ + ++(d_statistics.d_statDisequalityConflicts); + raiseConflict(eq, InferenceId::ARITH_CONF_TRICHOTOMY); + return true; + } + } + }else{ + // l <= x <= u and l < u + Assert(cmpToUB < 0); + const ValueCollection& vc = constraint->getValueCollection(); + + if(vc.hasDisequality()){ + const ConstraintP diseq = vc.getDisequality(); + if(diseq->isTrue()){ + const ConstraintP ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); + ConstraintP negUb = ub->getNegation(); + + // l <= x, l != x |= l < x + // |= not (l >= x) + bool ubInConflict = ub->hasProof(); + bool learnNegUb = !(negUb->hasProof()); + if(learnNegUb){ + negUb->impliedByTrichotomy(constraint, diseq, ubInConflict); + negUb->tryToPropagate(); + } + if(ubInConflict){ + raiseConflict(ub, InferenceId::ARITH_CONF_TRICHOTOMY); + return true; + }else if(learnNegUb){ + d_learnedBounds.push_back(negUb); + } + } + } + } + + d_currentPropagationList.push_back(constraint); + d_currentPropagationList.push_back(d_partialModel.getLowerBoundConstraint(x_i)); + + d_partialModel.setLowerBoundConstraint(constraint); + + if(d_cmEnabled){ + if(d_congruenceManager.isWatchedVariable(x_i)){ + int sgn = c_i.sgn(); + if(sgn > 0){ + d_congruenceManager.watchedVariableCannotBeZero(constraint); + }else if(sgn == 0 && d_partialModel.upperBoundIsZero(x_i)){ + zeroDifferenceDetected(x_i); + } + } + } + + d_updatedBounds.softAdd(x_i); + + if(TraceIsOn("model")) { + Trace("model") << "before" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + if(!d_tableau.isBasic(x_i)){ + if(d_partialModel.getAssignment(x_i) < c_i){ + d_linEq.update(x_i, c_i); + } + }else{ + d_errorSet.signalVariable(x_i); + } + + if(TraceIsOn("model")) { + Trace("model") << "after" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + return false; //sat +} + +/* procedure AssertUpper( x_i <= c_i) */ +bool TheoryArithPrivate::AssertUpper(ConstraintP constraint){ + Assert(constraint != NullConstraint); + Assert(constraint->isUpperBound()); + Assert(constraint->isTrue()); + Assert(!constraint->negationHasProof()); + + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Trace("arith") << "AssertUpper(" << x_i << " " << c_i << ")"<< std::endl; + + + //Too strong because of rounding with integers + //Assert(!constraint->hasLiteral() || original == constraint->getLiteral()); + Assert(!isInteger(x_i) || c_i.isIntegral()); + + Trace("arith") << "AssertUpper(" << x_i << " " << c_i << ")"<< std::endl; + + if(d_partialModel.greaterThanUpperBound(x_i, c_i) ){ // \upperbound(x_i) <= c_i + return false; //sat + } + + // cmpToLb = \lowerbound(x_i).cmp(c_i) + int cmpToLB = d_partialModel.cmpToLowerBound(x_i, c_i); + if( cmpToLB < 0 ){ // \upperbound(x_i) < \lowerbound(x_i) + // l_i <= x_i and c_i < l_i |= c_i < x_i + // or ... |= not (x_i <= c_i) + ConstraintP lbc = d_partialModel.getLowerBoundConstraint(x_i); + ConstraintP negConstraint = constraint->getNegation(); + negConstraint->impliedByUnate(lbc, true); + raiseConflict(constraint, InferenceId::ARITH_CONF_UPPER); + ++(d_statistics.d_statAssertUpperConflicts); + return true; + }else if(cmpToLB == 0){ // \lowerBound(x_i) == \upperbound(x_i) + if(isInteger(x_i)){ + d_constantIntegerVariables.push_back(x_i); + Trace("dio::push") << "dio::push " << x_i << endl; + } + + const ValueCollection& vc = constraint->getValueCollection(); + ConstraintP lb = d_partialModel.getLowerBoundConstraint(x_i); + if(d_cmEnabled){ + if(!d_congruenceManager.isWatchedVariable(x_i) || c_i.sgn() != 0){ + // if it is not a watched variable report it + // if it is is a watched variable and c_i == 0, + // let zeroDifferenceDetected(x_i) catch this + d_congruenceManager.equalsConstant(lb, constraint); + } + } + + if(vc.hasDisequality()){ + Assert(vc.hasDisequality()); + ConstraintP eq = vc.getEquality(); + ConstraintP diseq = vc.getDisequality(); + // x <= b, x >= b |= x = b + // (x > b or x < b or x = b) + Trace("arith::eq") << "lb == ub, propagate eq" << eq << endl; + bool triConflict = diseq->isTrue(); + if(!eq->isTrue()){ + eq->impliedByTrichotomy(constraint, lb, triConflict); + eq->tryToPropagate(); + } + if(triConflict){ + ++(d_statistics.d_statDisequalityConflicts); + raiseConflict(eq, InferenceId::ARITH_CONF_TRICHOTOMY); + return true; + } + } + }else if(cmpToLB > 0){ + // l <= x <= u and l < u + Assert(cmpToLB > 0); + const ValueCollection& vc = constraint->getValueCollection(); + + if(vc.hasDisequality()){ + const ConstraintP diseq = vc.getDisequality(); + if(diseq->isTrue()){ + const ConstraintP lb = d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); + ConstraintP negLb = lb->getNegation(); + + // x <= u, u != x |= u < x + // |= not (u >= x) + bool lbInConflict = lb->hasProof(); + bool learnNegLb = !(negLb->hasProof()); + if(learnNegLb){ + negLb->impliedByTrichotomy(constraint, diseq, lbInConflict); + negLb->tryToPropagate(); + } + if(lbInConflict){ + raiseConflict(lb, InferenceId::ARITH_CONF_TRICHOTOMY); + return true; + }else if(learnNegLb){ + d_learnedBounds.push_back(negLb); + } + } + } + } + + d_currentPropagationList.push_back(constraint); + d_currentPropagationList.push_back(d_partialModel.getUpperBoundConstraint(x_i)); + //It is fine if this is NullConstraint + + d_partialModel.setUpperBoundConstraint(constraint); + + if(d_cmEnabled){ + if(d_congruenceManager.isWatchedVariable(x_i)){ + int sgn = c_i.sgn(); + if(sgn < 0){ + d_congruenceManager.watchedVariableCannotBeZero(constraint); + }else if(sgn == 0 && d_partialModel.lowerBoundIsZero(x_i)){ + zeroDifferenceDetected(x_i); + } + } + } + + d_updatedBounds.softAdd(x_i); + + if(TraceIsOn("model")) { + Trace("model") << "before" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + if(!d_tableau.isBasic(x_i)){ + if(d_partialModel.getAssignment(x_i) > c_i){ + d_linEq.update(x_i, c_i); + } + }else{ + d_errorSet.signalVariable(x_i); + } + + if(TraceIsOn("model")) { + Trace("model") << "after" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + return false; //sat +} + + +/* procedure AssertEquality( x_i == c_i ) */ +bool TheoryArithPrivate::AssertEquality(ConstraintP constraint){ + Assert(constraint != NullConstraint); + Assert(constraint->isEquality()); + Assert(constraint->isTrue()); + Assert(!constraint->negationHasProof()); + + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + + Trace("arith") << "AssertEquality(" << x_i << " " << c_i << ")"<< std::endl; + + //Should be fine in integers + Assert(!isInteger(x_i) || c_i.isIntegral()); + + int cmpToLB = d_partialModel.cmpToLowerBound(x_i, c_i); + int cmpToUB = d_partialModel.cmpToUpperBound(x_i, c_i); + + // u_i <= c_i <= l_i + // This can happen if both c_i <= x_i and x_i <= c_i are in the system. + if(cmpToUB >= 0 && cmpToLB <= 0){ + return false; //sat + } + + if(cmpToUB > 0 || cmpToLB < 0){ + ConstraintP cb = (cmpToUB > 0) ? d_partialModel.getUpperBoundConstraint(x_i) : + d_partialModel.getLowerBoundConstraint(x_i); + ConstraintP diseq = constraint->getNegation(); + Assert(!diseq->isTrue()); + diseq->impliedByUnate(cb, true); + raiseConflict(constraint, InferenceId::ARITH_CONF_EQ); + return true; + } + + Assert(cmpToUB <= 0); + Assert(cmpToLB >= 0); + Assert(cmpToUB < 0 || cmpToLB > 0); + + if(isInteger(x_i)){ + d_constantIntegerVariables.push_back(x_i); + Trace("dio::push") << "dio::push " << x_i << endl; + } + + // Don't bother to check whether x_i != c_i is in d_diseq + // The a and (not a) should never be on the fact queue + d_currentPropagationList.push_back(constraint); + d_currentPropagationList.push_back(d_partialModel.getLowerBoundConstraint(x_i)); + d_currentPropagationList.push_back(d_partialModel.getUpperBoundConstraint(x_i)); + + d_partialModel.setUpperBoundConstraint(constraint); + d_partialModel.setLowerBoundConstraint(constraint); + + if(d_cmEnabled){ + if(d_congruenceManager.isWatchedVariable(x_i)){ + int sgn = c_i.sgn(); + if(sgn == 0){ + zeroDifferenceDetected(x_i); + }else{ + d_congruenceManager.watchedVariableCannotBeZero(constraint); + d_congruenceManager.equalsConstant(constraint); + } + }else{ + d_congruenceManager.equalsConstant(constraint); + } + } + + d_updatedBounds.softAdd(x_i); + + if(TraceIsOn("model")) { + Trace("model") << "before" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + if(!d_tableau.isBasic(x_i)){ + if(!(d_partialModel.getAssignment(x_i) == c_i)){ + d_linEq.update(x_i, c_i); + } + }else{ + d_errorSet.signalVariable(x_i); + } + + if(TraceIsOn("model")) { + Trace("model") << "after" << endl; + d_partialModel.printModel(x_i); + d_tableau.debugPrintIsBasic(x_i); + } + + return false; +} + + +/* procedure AssertDisequality( x_i != c_i ) */ +bool TheoryArithPrivate::AssertDisequality(ConstraintP constraint){ + Assert(constraint != NullConstraint); + Assert(constraint->isDisequality()); + Assert(constraint->isTrue()); + Assert(!constraint->negationHasProof()); + + ArithVar x_i = constraint->getVariable(); + const DeltaRational& c_i = constraint->getValue(); + Trace("arith") << "AssertDisequality(" << x_i << " " << c_i << ")"<< std::endl; + + //Should be fine in integers + Assert(!isInteger(x_i) || c_i.isIntegral()); + + if(d_cmEnabled){ + if(d_congruenceManager.isWatchedVariable(x_i)){ + int sgn = c_i.sgn(); + if(sgn == 0){ + d_congruenceManager.watchedVariableCannotBeZero(constraint); + } + } + } + + const ValueCollection& vc = constraint->getValueCollection(); + if(vc.hasLowerBound() && vc.hasUpperBound()){ + const ConstraintP lb = vc.getLowerBound(); + const ConstraintP ub = vc.getUpperBound(); + if(lb->isTrue() && ub->isTrue()){ + ConstraintP eq = constraint->getNegation(); + eq->impliedByTrichotomy(lb, ub, true); + raiseConflict(constraint, InferenceId::ARITH_CONF_TRICHOTOMY); + //in conflict + ++(d_statistics.d_statDisequalityConflicts); + return true; + } + } + if(vc.hasLowerBound() ){ + const ConstraintP lb = vc.getLowerBound(); + if(lb->isTrue()){ + const ConstraintP ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); + Assert(!ub->isTrue()); + Trace("arith::eq") << "propagate UpperBound " << constraint << lb << ub << endl; + const ConstraintP negUb = ub->getNegation(); + if(!negUb->isTrue()){ + negUb->impliedByTrichotomy(constraint, lb, false); + negUb->tryToPropagate(); + d_learnedBounds.push_back(negUb); + } + } + } + if(vc.hasUpperBound()){ + const ConstraintP ub = vc.getUpperBound(); + if(ub->isTrue()){ + const ConstraintP lb = d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); + Assert(!lb->isTrue()); + + Trace("arith::eq") << "propagate LowerBound " << constraint << lb << ub << endl; + const ConstraintP negLb = lb->getNegation(); + if(!negLb->isTrue()){ + negLb->impliedByTrichotomy(constraint, ub, false); + negLb->tryToPropagate(); + d_learnedBounds.push_back(negLb); + } + } + } + + bool split = constraint->isSplit(); + + if(!split && c_i == d_partialModel.getAssignment(x_i)){ + Trace("arith::eq") << "lemma now! " << constraint << endl; + outputTrustedLemma(constraint->split(), InferenceId::ARITH_SPLIT_DEQ); + return false; + }else if(d_partialModel.strictlyLessThanLowerBound(x_i, c_i)){ + Trace("arith::eq") << "can drop as less than lb" << constraint << endl; + }else if(d_partialModel.strictlyGreaterThanUpperBound(x_i, c_i)){ + Trace("arith::eq") << "can drop as less than ub" << constraint << endl; + }else if(!split){ + Trace("arith::eq") << "push back" << constraint << endl; + d_diseqQueue.push(constraint); + d_partialModel.invalidateDelta(); + }else{ + Trace("arith::eq") << "skipping already split " << constraint << endl; + } + return false; +} + +void TheoryArithPrivate::notifySharedTerm(TNode n) +{ + Trace("arith::notifySharedTerm") << "notifySharedTerm: " << n << endl; + if(n.isConst()){ + d_partialModel.invalidateDelta(); + } + if(!n.isConst() && !isSetup(n)){ + Polynomial poly = Polynomial::parsePolynomial(n); + Polynomial::iterator it = poly.begin(); + Polynomial::iterator it_end = poly.end(); + for (; it != it_end; ++ it) { + Monomial m = *it; + if (!m.isConstant() && !isSetup(m.getVarList().getNode())) { + setupVariableList(m.getVarList()); + } + } + } +} + +Node TheoryArithPrivate::getModelValue(TNode term) { + try{ + const DeltaRational drv = getDeltaValue(term); + const Rational& delta = d_partialModel.getDelta(); + const Rational qmodel = drv.substituteDelta( delta ); + return NodeManager::currentNM()->mkConstRealOrInt(term.getType(), qmodel); + } catch (DeltaRationalException& dr) { + return Node::null(); + } catch (ModelException& me) { + return Node::null(); + } +} + +Theory::PPAssertStatus TheoryArithPrivate::ppAssert( + TrustNode tin, TrustSubstitutionMap& outSubstitutions) +{ + TimerStat::CodeTimer codeTimer(d_statistics.d_simplifyTimer); + TNode in = tin.getNode(); + Trace("simplify") << "TheoryArithPrivate::solve(" << in << ")" << endl; + + + // Solve equalities + Rational minConstant = 0; + Node minMonomial; + Node minVar; + if (in.getKind() == kind::EQUAL && + Theory::theoryOf(in[0].getType()) == THEORY_ARITH) { + Comparison cmp = Comparison::parseNormalForm(in); + + Polynomial left = cmp.getLeft(); + + Monomial m = left.getHead(); + if (m.getVarList().singleton()){ + VarList vl = m.getVarList(); + Node var = vl.getNode(); + if (var.isVar()) + { + // if vl.isIntegral then m.getConstant().isOne() + if(!vl.isIntegral() || m.getConstant().isOne()){ + minVar = var; + } + } + } + + // Solve for variable + if (!minVar.isNull()) { + Polynomial right = cmp.getRight(); + Node elim = right.getNode(); + // ax + p = c -> (ax + p) -ax - c = -ax + // x = (p - ax - c) * -1/a + // Add the substitution if not recursive + Assert(elim == rewrite(elim)); + + if (right.size() > options().arith.ppAssertMaxSubSize) + { + Trace("simplify") + << "TheoryArithPrivate::solve(): did not substitute due to the " + "right hand side containing too many terms: " + << minVar << ":" << elim << endl; + Trace("simplify") << right.size() << endl; + } + else if (d_containing.isLegalElimination(minVar, elim)) + { + // cannot eliminate integers here unless we know the resulting + // substitution is integral + Trace("simplify") << "TheoryArithPrivate::solve(): substitution " + << minVar << " |-> " << elim << endl; + + outSubstitutions.addSubstitutionSolved(minVar, elim, tin); + return Theory::PP_ASSERT_STATUS_SOLVED; + } + else + { + Trace("simplify") << "TheoryArithPrivate::solve(): can't substitute " + << minVar << ":" << minVar.getType() << " |-> " + << elim << ":" << elim.getType() << endl; + } + } + } + + // If a relation, remember the bound + switch(in.getKind()) { + case kind::LEQ: + case kind::LT: + case kind::GEQ: + case kind::GT: + if (in[0].isVar()) { + d_learner.addBound(in); + } + break; + default: + // Do nothing + break; + } + + return Theory::PP_ASSERT_STATUS_UNSOLVED; +} + +void TheoryArithPrivate::ppStaticLearn(TNode n, NodeBuilder& learned) +{ + TimerStat::CodeTimer codeTimer(d_statistics.d_staticLearningTimer); + + d_learner.staticLearning(n, learned); +} + +ArithVar TheoryArithPrivate::findShortestBasicRow(ArithVar variable){ + ArithVar bestBasic = ARITHVAR_SENTINEL; + uint64_t bestRowLength = std::numeric_limits::max(); + + Tableau::ColIterator basicIter = d_tableau.colIterator(variable); + for(; !basicIter.atEnd(); ++basicIter){ + const Tableau::Entry& entry = *basicIter; + Assert(entry.getColVar() == variable); + RowIndex ridx = entry.getRowIndex(); + ArithVar basic = d_tableau.rowIndexToBasic(ridx); + uint32_t rowLength = d_tableau.getRowLength(ridx); + if((rowLength < bestRowLength) || + (rowLength == bestRowLength && basic < bestBasic)){ + bestBasic = basic; + bestRowLength = rowLength; + } + } + Assert(bestBasic == ARITHVAR_SENTINEL + || bestRowLength < std::numeric_limits::max()); + return bestBasic; +} + +void TheoryArithPrivate::setupVariable(const Variable& x){ + Node n = x.getNode(); + + Assert(!isSetup(n)); + + ++(d_statistics.d_statUserVariables); + requestArithVar(n, false, false); + //ArithVar varN = requestArithVar(n,false); + //setupInitialValue(varN); + + markSetup(n); +} + +void TheoryArithPrivate::setupVariableList(const VarList& vl){ + Assert(!vl.empty()); + + TNode vlNode = vl.getNode(); + Assert(!isSetup(vlNode)); + Assert(!d_partialModel.hasArithVar(vlNode)); + + for(VarList::iterator i = vl.begin(), end = vl.end(); i != end; ++i){ + Variable var = *i; + + if(!isSetup(var.getNode())){ + setupVariable(var); + } + } + + if(!vl.singleton()){ + // vl is the product of at least 2 variables + // vl : (* v1 v2 ...) + if (logicInfo().isLinear()) + { + throw LogicException("A non-linear fact was asserted to arithmetic in a linear logic."); + } + d_foundNl = true; + + ++(d_statistics.d_statUserVariables); + requestArithVar(vlNode, false, false); + //ArithVar av = requestArithVar(vlNode, false); + //setupInitialValue(av); + + markSetup(vlNode); + } + else if (vlNode.getKind() == kind::EXPONENTIAL + || vlNode.getKind() == kind::SINE || vlNode.getKind() == kind::COSINE + || vlNode.getKind() == kind::TANGENT) + { + d_foundNl = true; + } + + /* Note: + * Only call markSetup if the VarList is not a singleton. + * See the comment in setupPolynomail for more. + */ +} + +void TheoryArithPrivate::cautiousSetupPolynomial(const Polynomial& p){ + if(p.containsConstant()){ + if(!p.isConstant()){ + Polynomial noConstant = p.getTail(); + if(!isSetup(noConstant.getNode())){ + setupPolynomial(noConstant); + } + } + }else if(!isSetup(p.getNode())){ + setupPolynomial(p); + } +} + + +void TheoryArithPrivate::setupPolynomial(const Polynomial& poly) { + Assert(!poly.containsConstant()); + TNode polyNode = poly.getNode(); + Assert(!isSetup(polyNode)); + Assert(!d_partialModel.hasArithVar(polyNode)); + + for(Polynomial::iterator i = poly.begin(), end = poly.end(); i != end; ++i){ + Monomial mono = *i; + const VarList& vl = mono.getVarList(); + if(!isSetup(vl.getNode())){ + setupVariableList(vl); + } + } + + if (polyNode.getKind() == ADD) + { + d_tableauSizeHasBeenModified = true; + + vector variables; + vector coefficients; + asVectors(poly, coefficients, variables); + + ArithVar varSlack = requestArithVar(polyNode, true, false); + d_tableau.addRow(varSlack, coefficients, variables); + setupBasicValue(varSlack); + d_linEq.trackRowIndex(d_tableau.basicToRowIndex(varSlack)); + + //Add differences to the difference manager + Polynomial::iterator i = poly.begin(), end = poly.end(); + if(i != end){ + Monomial first = *i; + ++i; + if(i != end){ + Monomial second = *i; + ++i; + if(i == end){ + if(first.getConstant().isOne() && second.getConstant().getValue() == -1){ + VarList vl0 = first.getVarList(); + VarList vl1 = second.getVarList(); + if(vl0.singleton() && vl1.singleton()){ + d_congruenceManager.addWatchedPair(varSlack, vl0.getNode(), vl1.getNode()); + } + } + } + } + } + + ++(d_statistics.d_statAuxiliaryVariables); + markSetup(polyNode); + } + + /* Note: + * It is worth documenting that polyNode should only be marked as + * being setup by this function if it has kind ADD. + * Other kinds will be marked as being setup by lower levels of setup + * specifically setupVariableList. + */ +} + +void TheoryArithPrivate::setupAtom(TNode atom) { + Assert(isRelationOperator(atom.getKind())) << atom; + Assert(Comparison::isNormalAtom(atom)); + Assert(!isSetup(atom)); + Assert(!d_constraintDatabase.hasLiteral(atom)); + + Comparison cmp = Comparison::parseNormalForm(atom); + Polynomial nvp = cmp.normalizedVariablePart(); + Assert(!nvp.isZero()); + + if(!isSetup(nvp.getNode())){ + setupPolynomial(nvp); + } + + d_constraintDatabase.addLiteral(atom); + + markSetup(atom); +} + +void TheoryArithPrivate::preRegisterTerm(TNode n) { + Trace("arith::preregister") <<"begin arith::preRegisterTerm("<< n <<")"<< endl; + + d_preregisteredNodes.insert(n); + + try { + if(isRelationOperator(n.getKind())){ + if(!isSetup(n)){ + setupAtom(n); + } + ConstraintP c = d_constraintDatabase.lookup(n); + Assert(c != NullConstraint); + + Trace("arith::preregister") << "setup constraint" << c << endl; + Assert(!c->canBePropagated()); + c->setPreregistered(); + } + } catch(LogicException& le) { + std::stringstream ss; + ss << le.getMessage() << endl << "The fact in question: " << n << endl; + throw LogicException(ss.str()); + } + + Trace("arith::preregister") << "end arith::preRegisterTerm("<< n <<")" << endl; +} + +void TheoryArithPrivate::releaseArithVar(ArithVar v){ + //Assert(d_partialModel.hasNode(v)); + + d_constraintDatabase.removeVariable(v); + d_partialModel.releaseArithVar(v); +} + +ArithVar TheoryArithPrivate::requestArithVar(TNode x, bool aux, bool internal){ + //TODO : The VarList trick is good enough? + Kind xk = x.getKind(); + Assert(isLeaf(x) || VarList::isMember(x) || xk == ADD || internal); + if (logicInfo().isLinear() + && (Variable::isDivMember(x) || xk == IAND || isTranscendentalKind(xk))) + { + stringstream ss; + ss << "A non-linear fact was asserted to " + "arithmetic in a linear logic: " + << x << std::endl; + throw LogicException(ss.str()); + } + Assert(!d_partialModel.hasArithVar(x)); + Assert(x.getType().isRealOrInt()); // real or integer + + ArithVar max = d_partialModel.getNumberOfVariables(); + ArithVar varX = d_partialModel.allocate(x, aux); + + bool reclaim = max >= d_partialModel.getNumberOfVariables();; + + if(!reclaim){ + d_dualSimplex.increaseMax(); + + d_tableau.increaseSize(); + d_tableauSizeHasBeenModified = true; + } + d_constraintDatabase.addVariable(varX); + + Trace("arith::arithvar") << "@" << context()->getLevel() << " " << x + << " |-> " << varX << "(relaiming " << reclaim << ")" + << endl; + + Assert(!d_partialModel.hasUpperBound(varX)); + Assert(!d_partialModel.hasLowerBound(varX)); + + return varX; +} + +void TheoryArithPrivate::asVectors(const Polynomial& p, std::vector& coeffs, std::vector& variables) { + for(Polynomial::iterator i = p.begin(), end = p.end(); i != end; ++i){ + const Monomial& mono = *i; + const Constant& constant = mono.getConstant(); + const VarList& variable = mono.getVarList(); + + Node n = variable.getNode(); + + Trace("arith::asVectors") << "should be var: " << n << endl; + + // TODO: This VarList::isMember(n) can be stronger + Assert(isLeaf(n) || VarList::isMember(n)); + Assert(theoryOf(n) != THEORY_ARITH || d_partialModel.hasArithVar(n)); + + Assert(d_partialModel.hasArithVar(n)); + ArithVar av = d_partialModel.asArithVar(n); + + coeffs.push_back(constant.getValue()); + variables.push_back(av); + } +} + +/* Requirements: + * For basic variables the row must have been added to the tableau. + */ +void TheoryArithPrivate::setupBasicValue(ArithVar x){ + Assert(d_tableau.isBasic(x)); + //If the variable is basic, assertions may have already happened and updates + //may have occured before setting this variable up. + + //This can go away if the tableau creation is done at preregister + //time instead of register + DeltaRational safeAssignment = d_linEq.computeRowValue(x, true); + DeltaRational assignment = d_linEq.computeRowValue(x, false); + d_partialModel.setAssignment(x,safeAssignment,assignment); + + Trace("arith") << "setupVariable("< 1); + Assert(!gcd.divides(c.asConstant().getNumerator())); + Comparison leq = Comparison::mkComparison(LEQ, p, c); + Comparison geq = Comparison::mkComparison(GEQ, p, c); + Node lemma = NodeManager::currentNM()->mkNode(OR, leq.getNode(), geq.getNode()); + Node rewrittenLemma = rewrite(lemma); + Trace("arith::dio::ex") << "dioCutting found the plane: " << plane.getNode() << endl; + Trace("arith::dio::ex") << "resulting in the cut: " << lemma << endl; + Trace("arith::dio::ex") << "rewritten " << rewrittenLemma << endl; + Trace("arith::dio") << "dioCutting found the plane: " << plane.getNode() << endl; + Trace("arith::dio") << "resulting in the cut: " << lemma << endl; + Trace("arith::dio") << "rewritten " << rewrittenLemma << endl; + if (proofsEnabled()) + { + NodeManager* nm = NodeManager::currentNM(); + Node gt = nm->mkNode(kind::GT, p.getNode(), c.getNode()); + Node lt = nm->mkNode(kind::LT, p.getNode(), c.getNode()); + TypeNode type = gt[0].getType(); + + Pf pfNotLeq = d_pnm->mkAssume(leq.getNode().negate()); + Pf pfGt = + d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pfNotLeq}, {gt}); + Pf pfNotGeq = d_pnm->mkAssume(geq.getNode().negate()); + Pf pfLt = + d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pfNotGeq}, {lt}); + Pf pfSum = d_pnm->mkNode( + PfRule::MACRO_ARITH_SCALE_SUM_UB, + {pfGt, pfLt}, + {nm->mkConstRealOrInt(type, -1), nm->mkConstRealOrInt(type, 1)}); + Pf pfBot = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pfSum}, {nm->mkConst(false)}); + std::vector assumptions = {leq.getNode().negate(), + geq.getNode().negate()}; + Pf pfNotAndNot = d_pnm->mkScope(pfBot, assumptions); + Pf pfOr = d_pnm->mkNode(PfRule::NOT_AND, {pfNotAndNot}, {}); + Pf pfRewritten = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pfOr}, {rewrittenLemma}); + return d_pfGen->mkTrustNode(rewrittenLemma, pfRewritten); + } + else + { + return TrustNode::mkTrustLemma(rewrittenLemma, nullptr); + } + } +} + +Node TheoryArithPrivate::callDioSolver(){ + while(!d_constantIntegerVariables.empty()){ + ArithVar v = d_constantIntegerVariables.front(); + d_constantIntegerVariables.pop(); + + Trace("arith::dio") << "callDioSolver " << v << endl; + + Assert(isInteger(v)); + Assert(d_partialModel.boundsAreEqual(v)); + + ConstraintP lb = d_partialModel.getLowerBoundConstraint(v); + ConstraintP ub = d_partialModel.getUpperBoundConstraint(v); + + Node orig = Node::null(); + if(lb->isEquality()){ + orig = Constraint::externalExplainByAssertions({lb}); + }else if(ub->isEquality()){ + orig = Constraint::externalExplainByAssertions({ub}); + }else { + orig = Constraint::externalExplainByAssertions(ub, lb); + } + + Assert(d_partialModel.assignmentIsConsistent(v)); + + Comparison eq = mkIntegerEqualityFromAssignment(v); + + if(eq.isBoolean()){ + //This can only be a conflict + Assert(!eq.getNode().getConst()); + + //This should be handled by the normal form earlier in the case of equality + Assert(orig.getKind() != EQUAL); + return orig; + }else{ + Trace("dio::push") << "dio::push " << v << " " << eq.getNode() << " with reason " << orig << endl; + d_diosolver.pushInputConstraint(eq, orig); + } + } + + return d_diosolver.processEquationsForConflict(); +} + +ConstraintP TheoryArithPrivate::constraintFromFactQueue(TNode assertion) +{ + Kind simpleKind = Comparison::comparisonKind(assertion); + ConstraintP constraint = d_constraintDatabase.lookup(assertion); + if(constraint == NullConstraint){ + Assert(simpleKind == EQUAL || simpleKind == DISTINCT); + bool isDistinct = simpleKind == DISTINCT; + Node eq = (simpleKind == DISTINCT) ? assertion[0] : assertion; + Assert(!isSetup(eq)); + Node reEq = rewrite(eq); + Trace("arith::distinct::const") << "Assertion: " << assertion << std::endl; + Trace("arith::distinct::const") << "Eq : " << eq << std::endl; + Trace("arith::distinct::const") << "reEq : " << reEq << std::endl; + if(reEq.getKind() == CONST_BOOLEAN){ + if(reEq.getConst() == isDistinct){ + // if is (not true), or false + Assert((reEq.getConst() && isDistinct) + || (!reEq.getConst() && !isDistinct)); + if (proofsEnabled()) + { + Pf assume = d_pnm->mkAssume(assertion); + std::vector assumptions = {assertion}; + Pf pf = d_pnm->mkScope(d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, + {d_pnm->mkAssume(assertion)}, + {}), + assumptions); + raiseBlackBoxConflict(assertion, pf); + } + else + { + raiseBlackBoxConflict(assertion); + } + } + return NullConstraint; + } + Assert(reEq.getKind() != CONST_BOOLEAN); + if(!isSetup(reEq)){ + setupAtom(reEq); + } + Node reAssertion = isDistinct ? reEq.notNode() : reEq; + constraint = d_constraintDatabase.lookup(reAssertion); + + if(assertion != reAssertion){ + Trace("arith::nf") << "getting non-nf assertion " << assertion << " |-> " << reAssertion << endl; + Assert(constraint != NullConstraint); + d_assertionsThatDoNotMatchTheirLiterals.insert(assertion, constraint); + } + } + + Assert(constraint != NullConstraint); + + if(constraint->assertedToTheTheory()){ + //Do nothing + return NullConstraint; + } + Assert(!constraint->assertedToTheTheory()); + bool inConflict = constraint->negationHasProof(); + constraint->setAssertedToTheTheory(assertion, inConflict); + + if(!constraint->hasProof()){ + Trace("arith::constraint") << "marking as constraint as self explaining " << endl; + constraint->setAssumption(inConflict); + } else { + Trace("arith::constraint") + << "already has proof: " + << Constraint::externalExplainByAssertions({constraint}); + } + + if(TraceIsOn("arith::negatedassumption") && inConflict){ + ConstraintP negation = constraint->getNegation(); + if(TraceIsOn("arith::negatedassumption") && negation->isAssumption()){ + debugPrintFacts(); + } + Trace("arith::eq") << "negation has proof" << endl; + Trace("arith::eq") << constraint << endl; + Trace("arith::eq") << negation << endl; + } + + if(inConflict){ + ConstraintP negation = constraint->getNegation(); + if(TraceIsOn("arith::negatedassumption") && negation->isAssumption()){ + debugPrintFacts(); + } + Trace("arith::eq") << "negation has proof" << endl; + Trace("arith::eq") << constraint << endl; + Trace("arith::eq") << negation << endl; + raiseConflict(negation, InferenceId::ARITH_CONF_FACT_QUEUE); + return NullConstraint; + }else{ + return constraint; + } +} + +bool TheoryArithPrivate::assertionCases(ConstraintP constraint){ + Assert(constraint->hasProof()); + Assert(!constraint->negationHasProof()); + + ArithVar x_i = constraint->getVariable(); + + switch(constraint->getType()){ + case UpperBound: + if(isInteger(x_i) && constraint->isStrictUpperBound()){ + ConstraintP floorConstraint = constraint->getFloor(); + if(!floorConstraint->isTrue()){ + bool inConflict = floorConstraint->negationHasProof(); + if (TraceIsOn("arith::intbound")) { + Trace("arith::intbound") << "literal, before: " << constraint->getLiteral() << std::endl; + Trace("arith::intbound") << "constraint, after: " << floorConstraint << std::endl; + } + floorConstraint->impliedByIntTighten(constraint, inConflict); + floorConstraint->tryToPropagate(); + if(inConflict){ + raiseConflict(floorConstraint, InferenceId::ARITH_TIGHTEN_FLOOR); + return true; + } + } + return AssertUpper(floorConstraint); + }else{ + return AssertUpper(constraint); + } + case LowerBound: + if(isInteger(x_i) && constraint->isStrictLowerBound()){ + ConstraintP ceilingConstraint = constraint->getCeiling(); + if(!ceilingConstraint->isTrue()){ + bool inConflict = ceilingConstraint->negationHasProof(); + if (TraceIsOn("arith::intbound")) { + Trace("arith::intbound") << "literal, before: " << constraint->getLiteral() << std::endl; + Trace("arith::intbound") << "constraint, after: " << ceilingConstraint << std::endl; + } + ceilingConstraint->impliedByIntTighten(constraint, inConflict); + ceilingConstraint->tryToPropagate(); + if(inConflict){ + raiseConflict(ceilingConstraint, InferenceId::ARITH_TIGHTEN_CEIL); + return true; + } + } + return AssertLower(ceilingConstraint); + }else{ + return AssertLower(constraint); + } + case Equality: + return AssertEquality(constraint); + case Disequality: + return AssertDisequality(constraint); + default: + Unreachable(); + return false; + } +} +/** + * Looks for through the variables starting at d_nextIntegerCheckVar + * for the first integer variable that is between its upper and lower bounds + * that has a non-integer assignment. + * + * If assumeBounds is true, skip the check that the variable is in bounds. + * + * If there is no such variable, returns ARITHVAR_SENTINEL; + */ +ArithVar TheoryArithPrivate::nextIntegerViolation(bool assumeBounds) const +{ + ArithVar numVars = d_partialModel.getNumberOfVariables(); + ArithVar v = d_nextIntegerCheckVar; + if (numVars > 0) + { + const ArithVar rrEnd = d_nextIntegerCheckVar; + do + { + if (isIntegerInput(v)) + { + if (!d_partialModel.integralAssignment(v)) + { + if (assumeBounds || d_partialModel.assignmentIsConsistent(v)) + { + return v; + } + } + } + v = (1 + v == numVars) ? 0 : (1 + v); + } while (v != rrEnd); + } + return ARITHVAR_SENTINEL; +} + +/** + * Checks the set of integer variables I to see if each variable + * in I has an integer assignment. + */ +bool TheoryArithPrivate::hasIntegerModel() +{ + ArithVar next = nextIntegerViolation(true); + if (next != ARITHVAR_SENTINEL) + { + d_nextIntegerCheckVar = next; + if (TraceIsOn("arith::hasIntegerModel")) + { + Trace("arith::hasIntegerModel") << "has int model? " << next << endl; + d_partialModel.printModel(next, Trace("arith::hasIntegerModel")); + } + return false; + } + else + { + return true; + } +} + +Node flattenAndSort(Node n){ + Kind k = n.getKind(); + switch(k){ + case kind::OR: + case kind::AND: + case kind::ADD: + case kind::MULT: + break; + default: + return n; + } + + std::vector out; + std::vector process; + process.push_back(n); + while(!process.empty()){ + Node b = process.back(); + process.pop_back(); + if(b.getKind() == k){ + for(Node::iterator i=b.begin(), end=b.end(); i!=end; ++i){ + process.push_back(*i); + } + } else { + out.push_back(b); + } + } + Assert(out.size() >= 2); + std::sort(out.begin(), out.end()); + return NodeManager::currentNM()->mkNode(k, out); +} + + + +/** Outputs conflicts to the output channel. */ +void TheoryArithPrivate::outputConflicts(){ + Trace("arith::conflict") << "outputting conflicts" << std::endl; + Assert(anyConflict()); + + if(!conflictQueueEmpty()){ + Assert(!d_conflicts.empty()); + for(size_t i = 0, i_end = d_conflicts.size(); i < i_end; ++i){ + const std::pair& conf = d_conflicts[i]; + const ConstraintCP& confConstraint = conf.first; + bool hasProof = confConstraint->hasProof(); + Assert(confConstraint->inConflict()); + const ConstraintRule& pf = confConstraint->getConstraintRule(); + if (TraceIsOn("arith::conflict")) + { + pf.print(std::cout, options().smt.produceProofs); + std::cout << std::endl; + } + if (TraceIsOn("arith::pf::tree")) + { + Trace("arith::pf::tree") << "\n\nTree:\n"; + confConstraint->printProofTree(Trace("arith::pf::tree")); + confConstraint->getNegation()->printProofTree(Trace("arith::pf::tree")); + } + + TrustNode trustedConflict = confConstraint->externalExplainConflict(); + Node conflict = trustedConflict.getNode(); + + Trace("arith::conflict") + << "d_conflicts[" << i << "] " << conflict + << " has proof: " << hasProof << ", id = " << conf.second << endl; + if(TraceIsOn("arith::normalize::external")){ + conflict = flattenAndSort(conflict); + Trace("arith::conflict") << "(normalized to) " << conflict << endl; + } + + if (isProofEnabled()) + { + outputTrustedConflict(trustedConflict, conf.second); + } + else + { + outputConflict(conflict, conf.second); + } + } + } + if(!d_blackBoxConflict.get().isNull()){ + Node bb = d_blackBoxConflict.get(); + Trace("arith::conflict") << "black box conflict" << bb + << endl; + if(TraceIsOn("arith::normalize::external")){ + bb = flattenAndSort(bb); + Trace("arith::conflict") << "(normalized to) " << bb << endl; + } + if (isProofEnabled() && d_blackBoxConflictPf.get()) + { + auto confPf = d_blackBoxConflictPf.get(); + outputTrustedConflict(d_pfGen->mkTrustNode(bb, confPf, true), InferenceId::ARITH_BLACK_BOX); + } + else + { + outputConflict(bb, InferenceId::ARITH_BLACK_BOX); + } + } +} + +bool TheoryArithPrivate::outputTrustedLemma(TrustNode lemma, InferenceId id) +{ + Trace("arith::channel") << "Arith trusted lemma: " << lemma << std::endl; + return d_containing.d_im.trustedLemma(lemma, id); +} + +bool TheoryArithPrivate::outputLemma(TNode lem, InferenceId id) { + Trace("arith::channel") << "Arith lemma: " << lem << std::endl; + return d_containing.d_im.lemma(lem, id); +} + +void TheoryArithPrivate::outputTrustedConflict(TrustNode conf, InferenceId id) +{ + Trace("arith::channel") << "Arith trusted conflict: " << conf << std::endl; + d_containing.d_im.trustedConflict(conf, id); +} + +void TheoryArithPrivate::outputConflict(TNode lit, InferenceId id) { + Trace("arith::channel") << "Arith conflict: " << lit << std::endl; + d_containing.d_im.conflict(lit, id); +} + +void TheoryArithPrivate::outputPropagate(TNode lit) { + Trace("arith::channel") << "Arith propagation: " << lit << std::endl; + // call the propagate lit method of the + d_containing.d_im.propagateLit(lit); +} + +void TheoryArithPrivate::outputRestart() { + Trace("arith::channel") << "Arith restart!" << std::endl; + (d_containing.d_out)->demandRestart(); +} + +bool TheoryArithPrivate::attemptSolveInteger(Theory::Effort effortLevel, bool emmmittedLemmaOrSplit){ + int level = context()->getLevel(); + Trace("approx") + << "attemptSolveInteger " << d_qflraStatus + << " " << emmmittedLemmaOrSplit + << " " << effortLevel + << " " << d_lastContextIntegerAttempted + << " " << level + << endl; + + if(d_qflraStatus == Result::UNSAT){ return false; } + if(emmmittedLemmaOrSplit){ return false; } + if (!options().arith.useApprox) + { + return false; + } + if(!ApproximateSimplex::enabled()){ return false; } + + if(Theory::fullEffort(effortLevel)){ + if(hasIntegerModel()){ + return false; + }else{ + return getSolveIntegerResource(); + } + } + + if(d_lastContextIntegerAttempted <= 0){ + if(hasIntegerModel()){ + d_lastContextIntegerAttempted = context()->getLevel(); + return false; + }else{ + return getSolveIntegerResource(); + } + } + + if (!options().arith.trySolveIntStandardEffort) + { + return false; + } + + if (d_lastContextIntegerAttempted <= (level >> 2)) + { + double d = (double)(d_solveIntMaybeHelp + 1) + / (d_solveIntAttempts + 1 + level * level); + if (Random::getRandom().pickWithProb(d)) + { + return getSolveIntegerResource(); + } + } + return false; +} + +bool TheoryArithPrivate::replayLog(ApproximateSimplex* approx){ + TimerStat::CodeTimer codeTimer(d_statistics.d_replayLogTimer); + + ++d_statistics.d_mipProofsAttempted; + + Assert(d_replayVariables.empty()); + Assert(d_replayConstraints.empty()); + + size_t enteringPropN = d_currentPropagationList.size(); + Assert(conflictQueueEmpty()); + TreeLog& tl = getTreeLog(); + //tl.applySelected(); /* set row ids */ + + d_replayedLemmas = false; + + /* use the try block for the purpose of pushing the sat context */ + context::Context::ScopedPush speculativePush(context()); + d_cmEnabled = false; + std::vector res = + replayLogRec(approx, tl.getRootId(), NullConstraint, 1); + + if(res.empty()){ + ++d_statistics.d_replayAttemptFailed; + }else{ + unsigned successes = 0; + for(size_t i =0, N = res.size(); i < N; ++i){ + ConstraintCPVec& vec = res[i]; + Assert(vec.size() >= 2); + for(size_t j=0, M = vec.size(); j < M; ++j){ + ConstraintCP at_j = vec[j]; + Assert(at_j->isTrue()); + if(!at_j->negationHasProof()){ + successes++; + vec[j] = vec.back(); + vec.pop_back(); + ConstraintP neg_at_j = at_j->getNegation(); + + Trace("approx::replayLog") << "Setting the proof for the replayLog conflict on:" << endl + << " (" << neg_at_j->isTrue() <<") " << neg_at_j << endl + << " (" << at_j->isTrue() <<") " << at_j << endl; + neg_at_j->impliedByIntHole(vec, true); + raiseConflict(at_j, InferenceId::ARITH_CONF_REPLAY_LOG); + break; + } + } + } + if(successes > 0){ + ++d_statistics.d_mipProofsSuccessful; + } + } + + if(d_currentPropagationList.size() > enteringPropN){ + d_currentPropagationList.resize(enteringPropN); + } + + /* It is not clear what the d_qflraStatus is at this point */ + d_qflraStatus = Result::UNKNOWN; + + Assert(d_replayVariables.empty()); + Assert(d_replayConstraints.empty()); + + return !conflictQueueEmpty(); +} + +std::pair TheoryArithPrivate::replayGetConstraint(const DenseMap& lhs, Kind k, const Rational& rhs, bool branch) +{ + ArithVar added = ARITHVAR_SENTINEL; + Node sum = toSumNode(d_partialModel, lhs); + if(sum.isNull()){ return make_pair(NullConstraint, added); } + + Trace("approx::constraint") << "replayGetConstraint " << sum + << " " << k + << " " << rhs + << endl; + + Assert(k == kind::LEQ || k == kind::GEQ); + + NodeManager* nm = NodeManager::currentNM(); + Node comparison = + nm->mkNode(k, sum, nm->mkConstRealOrInt(sum.getType(), rhs)); + Node rewritten = rewrite(comparison); + if(!(Comparison::isNormalAtom(rewritten))){ + return make_pair(NullConstraint, added); + } + + Comparison cmp = Comparison::parseNormalForm(rewritten); + if(cmp.isBoolean()){ return make_pair(NullConstraint, added); } + + Polynomial nvp = cmp.normalizedVariablePart(); + if(nvp.isZero()){ return make_pair(NullConstraint, added); } + + Node norm = nvp.getNode(); + + ConstraintType t = Constraint::constraintTypeOfComparison(cmp); + DeltaRational dr = cmp.normalizedDeltaRational(); + + Trace("approx::constraint") << "rewriting " << rewritten << endl + << " |-> " << norm << " " << t << " " << dr << endl; + + Assert(!branch || d_partialModel.hasArithVar(norm)); + ArithVar v = ARITHVAR_SENTINEL; + if(d_partialModel.hasArithVar(norm)){ + + v = d_partialModel.asArithVar(norm); + Trace("approx::constraint") + << "replayGetConstraint found " << norm << " |-> " << v << " @ " + << context()->getLevel() << endl; + Assert(!branch || d_partialModel.isIntegerInput(v)); + }else{ + v = requestArithVar(norm, true, true); + d_replayVariables.push_back(v); + + added = v; + + Trace("approx::constraint") + << "replayGetConstraint adding " << norm << " |-> " << v << " @ " + << context()->getLevel() << endl; + + Polynomial poly = Polynomial::parsePolynomial(norm); + vector variables; + vector coefficients; + asVectors(poly, coefficients, variables); + d_tableau.addRow(v, coefficients, variables); + setupBasicValue(v); + d_linEq.trackRowIndex(d_tableau.basicToRowIndex(v)); + } + Assert(d_partialModel.hasArithVar(norm)); + Assert(d_partialModel.asArithVar(norm) == v); + Assert(d_constraintDatabase.variableDatabaseIsSetup(v)); + + ConstraintP imp = d_constraintDatabase.getBestImpliedBound(v, t, dr); + if(imp != NullConstraint){ + if(imp->getValue() == dr){ + Assert(added == ARITHVAR_SENTINEL); + return make_pair(imp, added); + } + } + + ConstraintP newc = d_constraintDatabase.getConstraint(v, t, dr); + d_replayConstraints.push_back(newc); + return make_pair(newc, added); +} + +std::pair TheoryArithPrivate::replayGetConstraint( + ApproximateSimplex* approx, const NodeLog& nl) +{ + Assert(nl.isBranch()); + Assert(d_lhsTmp.empty()); + + ArithVar v = approx->getBranchVar(nl); + if(v != ARITHVAR_SENTINEL && d_partialModel.isIntegerInput(v)){ + if(d_partialModel.hasNode(v)){ + d_lhsTmp.set(v, Rational(1)); + double dval = nl.branchValue(); + std::optional maybe_value = + ApproximateSimplex::estimateWithCFE(dval); + if (!maybe_value) + { + return make_pair(NullConstraint, ARITHVAR_SENTINEL); + } + Rational fl(maybe_value.value().floor()); + pair p; + p = replayGetConstraint(d_lhsTmp, kind::LEQ, fl, true); + d_lhsTmp.purge(); + return p; + } + } + return make_pair(NullConstraint, ARITHVAR_SENTINEL); +} + +std::pair TheoryArithPrivate::replayGetConstraint(const CutInfo& ci) { + Assert(ci.reconstructed()); + const DenseMap& lhs = ci.getReconstruction().lhs; + const Rational& rhs = ci.getReconstruction().rhs; + Kind k = ci.getKind(); + + return replayGetConstraint(lhs, k, rhs, ci.getKlass() == BranchCutKlass); +} + +Node toSumNode(const ArithVariables& vars, const DenseMap& sum){ + Trace("arith::toSumNode") << "toSumNode() begin" << endl; + NodeManager* nm = NodeManager::currentNM(); + DenseMap::const_iterator iter, end; + iter = sum.begin(), end = sum.end(); + std::vector children; + for(; iter != end; ++iter){ + ArithVar x = *iter; + if(!vars.hasNode(x)){ return Node::null(); } + Node xNode = vars.asNode(x); + const Rational& q = sum[x]; + Node mult = nm->mkNode(kind::MULT, mkRationalNode(q), xNode); + Trace("arith::toSumNode") << "toSumNode() " << x << " " << mult << endl; + children.push_back(mult); + } + Trace("arith::toSumNode") << "toSumNode() end" << endl; + if (children.empty()) + { + // NOTE: real type assumed here + return nm->mkConstReal(Rational(0)); + } + else if (children.size() == 1) + { + return children[0]; + } + return nm->mkNode(kind::ADD, children); +} + +ConstraintCP TheoryArithPrivate::vectorToIntHoleConflict(const ConstraintCPVec& conflict){ + Assert(conflict.size() >= 2); + ConstraintCPVec exp(conflict.begin(), conflict.end()-1); + ConstraintCP back = conflict.back(); + Assert(back->hasProof()); + ConstraintP negBack = back->getNegation(); + // This can select negBack multiple times so we need to test if negBack has a proof. + if(negBack->hasProof()){ + // back is in conflict already + } else { + negBack->impliedByIntHole(exp, true); + } + + return back; +} + +void TheoryArithPrivate::intHoleConflictToVector(ConstraintCP conflicting, ConstraintCPVec& conflict){ + ConstraintCP negConflicting = conflicting->getNegation(); + Assert(conflicting->hasProof()); + Assert(negConflicting->hasProof()); + + conflict.push_back(conflicting); + conflict.push_back(negConflicting); + + Constraint::assertionFringe(conflict); +} + +void TheoryArithPrivate::tryBranchCut(ApproximateSimplex* approx, int nid, BranchCutInfo& bci){ + Assert(conflictQueueEmpty()); + std::vector< ConstraintCPVec > conflicts; + + approx->tryCut(nid, bci); + Trace("approx::branch") << "tryBranchCut" << bci << endl; + Assert(bci.reconstructed()); + Assert(!bci.proven()); + pair p = replayGetConstraint(bci); + Assert(p.second == ARITHVAR_SENTINEL); + ConstraintP bc = p.first; + Assert(bc != NullConstraint); + if(bc->hasProof()){ + return; + } + + ConstraintP bcneg = bc->getNegation(); + { + context::Context::ScopedPush speculativePush(context()); + replayAssert(bcneg); + if(conflictQueueEmpty()){ + TimerStat::CodeTimer codeTimer(d_statistics.d_replaySimplexTimer); + + //test for linear feasibility + d_partialModel.stopQueueingBoundCounts(); + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processBoundsQueue(utcb); + d_linEq.startTrackingBoundCounts(); + + SimplexDecisionProcedure& simplex = selectSimplex(true); + simplex.findModel(false); + // Can change d_qflraStatus + + d_linEq.stopTrackingBoundCounts(); + d_partialModel.startQueueingBoundCounts(); + } + for(size_t i = 0, N = d_conflicts.size(); i < N; ++i){ + + conflicts.push_back(ConstraintCPVec()); + intHoleConflictToVector(d_conflicts[i].first, conflicts.back()); + Constraint::assertionFringe(conflicts.back()); + + // ConstraintCP conflicting = d_conflicts[i]; + // ConstraintCP negConflicting = conflicting->getNegation(); + // Assert(conflicting->hasProof()); + // Assert(negConflicting->hasProof()); + + // conflicts.push_back(ConstraintCPVec()); + // ConstraintCPVec& back = conflicts.back(); + // back.push_back(conflicting); + // back.push_back(negConflicting); + + // // remove the floor/ceiling contraint implied by bcneg + // Constraint::assertionFringe(back); + } + + if(TraceIsOn("approx::branch")){ + if(d_conflicts.empty()){ + entireStateIsConsistent("branchfailure"); + } + } + } + + Trace("approx::branch") << "branch constraint " << bc << endl; + for(size_t i = 0, N = conflicts.size(); i < N; ++i){ + ConstraintCPVec& conf = conflicts[i]; + + // make sure to be working on the assertion fringe! + if(!contains(conf, bcneg)){ + Trace("approx::branch") << "reraise " << conf << endl; + ConstraintCP conflicting = vectorToIntHoleConflict(conf); + raiseConflict(conflicting, InferenceId::ARITH_CONF_BRANCH_CUT); + }else if(!bci.proven()){ + drop(conf, bcneg); + bci.setExplanation(conf); + Trace("approx::branch") << "dropped " << bci << endl; + } + } +} + +void TheoryArithPrivate::replayAssert(ConstraintP c) { + if(!c->assertedToTheTheory()){ + bool inConflict = c->negationHasProof(); + if(!c->hasProof()){ + c->setInternalAssumption(inConflict); + Trace("approx::replayAssert") << "replayAssert " << c << " set internal" << endl; + }else{ + Trace("approx::replayAssert") << "replayAssert " << c << " has explanation" << endl; + } + Trace("approx::replayAssert") << "replayAssertion " << c << endl; + if(inConflict){ + raiseConflict(c, InferenceId::ARITH_CONF_REPLAY_ASSERT); + }else{ + assertionCases(c); + } + }else{ + Trace("approx::replayAssert") + << "replayAssert " << c << " already asserted" << endl; + } +} + + +void TheoryArithPrivate::resolveOutPropagated(std::vector& confs, const std::set& propagated) const { + Trace("arith::resolveOutPropagated") + << "starting resolveOutPropagated() " << confs.size() << endl; + for(size_t i =0, N = confs.size(); i < N; ++i){ + ConstraintCPVec& conf = confs[i]; + size_t orig = conf.size(); + Constraint::assertionFringe(conf); + Trace("arith::resolveOutPropagated") + << " conf["< &confs) const { + int checks CVC5_UNUSED = 0; + int subsumed CVC5_UNUSED = 0; + + for (size_t i = 0, N = confs.size(); i < N; ++i) { + ConstraintCPVec &conf = confs[i]; + std::sort(conf.begin(), conf.end()); + } + + std::sort(confs.begin(), confs.end(), SizeOrd()); + for (size_t i = 0; i < confs.size(); i++) { + // i is not subsumed + for (size_t j = i + 1; j < confs.size();) { + ConstraintCPVec& a = confs[i]; + ConstraintCPVec& b = confs[j]; + checks++; + bool subsumes = std::includes(a.begin(), a.end(), b.begin(), b.end()); + if (subsumes) { + ConstraintCPVec& back = confs.back(); + b.swap(back); + confs.pop_back(); + subsumed++; + } else { + j++; + } + } + } + Trace("arith::subsumption") << "subsumed " << subsumed << "/" << checks + << endl; +} + +std::vector TheoryArithPrivate::replayLogRec(ApproximateSimplex* approx, int nid, ConstraintP bc, int depth){ + ++(d_statistics.d_replayLogRecCount); + Trace("approx::replayLogRec") << "replayLogRec()" << std::endl; + + size_t rpvars_size = d_replayVariables.size(); + size_t rpcons_size = d_replayConstraints.size(); + std::vector res; + + { /* create a block for the purpose of pushing the sat context */ + context::Context::ScopedPush speculativePush(context()); + Assert(!anyConflict()); + Assert(conflictQueueEmpty()); + set propagated; + + TreeLog& tl = getTreeLog(); + + if(bc != NullConstraint){ + replayAssert(bc); + } + + const NodeLog& nl = tl.getNode(nid); + NodeLog::const_iterator iter = nl.begin(), end = nl.end(); + for(; conflictQueueEmpty() && iter != end; ++iter){ + CutInfo* ci = *iter; + bool reject = false; + //cout << " trying " << *ci << endl; + if(ci->getKlass() == RowsDeletedKlass){ + RowsDeleted* rd = dynamic_cast(ci); + + tl.applyRowsDeleted(nid, *rd); + // The previous line modifies nl + + ++d_statistics.d_applyRowsDeleted; + }else if(ci->getKlass() == BranchCutKlass){ + BranchCutInfo* bci = dynamic_cast(ci); + Assert(bci != NULL); + tryBranchCut(approx, nid, *bci); + + ++d_statistics.d_branchCutsAttempted; + if(!(conflictQueueEmpty() || ci->reconstructed())){ + ++d_statistics.d_numBranchesFailed; + } + }else{ + approx->tryCut(nid, *ci); + if(ci->getKlass() == GmiCutKlass){ + ++d_statistics.d_gmiCutsAttempted; + }else if(ci->getKlass() == MirCutKlass){ + ++d_statistics.d_mirCutsAttempted; + } + + if(ci->reconstructed() && ci->proven()){ + const DenseMap& row = ci->getReconstruction().lhs; + reject = !complexityBelow(row, options().arith.replayRejectCutSize); + } + } + if(conflictQueueEmpty()){ + if(reject){ + ++d_statistics.d_cutsRejectedDuringReplay; + }else if(ci->reconstructed()){ + // success + ++d_statistics.d_cutsReconstructed; + + pair p = replayGetConstraint(*ci); + if(p.second != ARITHVAR_SENTINEL){ + Assert(ci->getRowId() >= 1); + tl.mapRowId(nl.getNodeId(), ci->getRowId(), p.second); + } + ConstraintP con = p.first; + if(TraceIsOn("approx::replayLogRec")){ + Trace("approx::replayLogRec") << "cut was remade " << con << " " << *ci << endl; + } + + if(ci->proven()){ + ++d_statistics.d_cutsProven; + + const ConstraintCPVec& exp = ci->getExplanation(); + // success + if(con->isTrue()){ + Trace("approx::replayLogRec") << "not asserted?" << endl; + }else if(!con->negationHasProof()){ + con->impliedByIntHole(exp, false); + replayAssert(con); + Trace("approx::replayLogRec") << "cut prop" << endl; + }else { + con->impliedByIntHole(exp, true); + Trace("approx::replayLogRec") << "cut into conflict " << con << endl; + raiseConflict(con, InferenceId::ARITH_CONF_REPLAY_LOG_REC); + } + }else{ + ++d_statistics.d_cutsProofFailed; + Trace("approx::replayLogRec") << "failed to get proof " << *ci << endl; + } + }else if(ci->getKlass() != RowsDeletedKlass){ + ++d_statistics.d_cutsReconstructionFailed; + } + } + } + + /* check if the system is feasible under with the cuts */ + if(conflictQueueEmpty()){ + Assert(options().arith.replayEarlyCloseDepths >= 1); + if (!nl.isBranch() || depth % options().arith.replayEarlyCloseDepths == 0) + { + TimerStat::CodeTimer codeTimer(d_statistics.d_replaySimplexTimer); + //test for linear feasibility + d_partialModel.stopQueueingBoundCounts(); + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processBoundsQueue(utcb); + d_linEq.startTrackingBoundCounts(); + + SimplexDecisionProcedure& simplex = selectSimplex(true); + simplex.findModel(false); + // can change d_qflraStatus + + d_linEq.stopTrackingBoundCounts(); + d_partialModel.startQueueingBoundCounts(); + } + }else{ + ++d_statistics.d_replayLogRecConflictEscalation; + } + + if(!conflictQueueEmpty()){ + /* if a conflict has been found stop */ + for(size_t i = 0, N = d_conflicts.size(); i < N; ++i){ + res.push_back(ConstraintCPVec()); + intHoleConflictToVector(d_conflicts[i].first, res.back()); + } + ++d_statistics.d_replayLogRecEarlyExit; + }else if(nl.isBranch()){ + /* if it is a branch try the branch */ + pair p = replayGetConstraint(approx, nl); + Assert(p.second == ARITHVAR_SENTINEL); + ConstraintP dnc = p.first; + if(dnc != NullConstraint){ + ConstraintP upc = dnc->getNegation(); + + int dnid = nl.getDownId(); + int upid = nl.getUpId(); + + NodeLog& dnlog = tl.getNode(dnid); + NodeLog& uplog = tl.getNode(upid); + dnlog.copyParentRowIds(); + uplog.copyParentRowIds(); + + std::vector dnres; + std::vector upres; + std::vector containsdn; + std::vector containsup; + if(res.empty()){ + dnres = replayLogRec(approx, dnid, dnc, depth+1); + for(size_t i = 0, N = dnres.size(); i < N; ++i){ + ConstraintCPVec& conf = dnres[i]; + if(contains(conf, dnc)){ + containsdn.push_back(i); + }else{ + res.push_back(conf); + } + } + }else{ + Trace("approx::replayLogRec") << "replayLogRec() skipping" << dnlog << std::endl; + ++d_statistics.d_replayBranchSkips; + } + + if(res.empty()){ + upres = replayLogRec(approx, upid, upc, depth+1); + + for(size_t i = 0, N = upres.size(); i < N; ++i){ + ConstraintCPVec& conf = upres[i]; + if(contains(conf, upc)){ + containsup.push_back(i); + }else{ + res.push_back(conf); + } + } + }else{ + Trace("approx::replayLogRec") << "replayLogRec() skipping" << uplog << std::endl; + ++d_statistics.d_replayBranchSkips; + } + + if(res.empty()){ + for(size_t i = 0, N = containsdn.size(); i < N; ++i){ + ConstraintCPVec& dnconf = dnres[containsdn[i]]; + for(size_t j = 0, M = containsup.size(); j < M; ++j){ + ConstraintCPVec& upconf = upres[containsup[j]]; + + res.push_back(ConstraintCPVec()); + ConstraintCPVec& back = res.back(); + resolve(back, dnc, dnconf, upconf); + } + } + if(res.size() >= 2u){ + subsumption(res); + + if(res.size() > 100u){ + res.resize(100u); + } + } + }else{ + Trace("approx::replayLogRec") << "replayLogRec() skipping resolving" << nl << std::endl; + } + Trace("approx::replayLogRec") << "found #"< rpcons_size){ + ConstraintP c = d_replayConstraints.back(); + d_replayConstraints.pop_back(); + d_constraintDatabase.deleteConstraintAndNegation(c); + } + + /* Garbage collect the ArithVars made by this call */ + if(d_replayVariables.size() > rpvars_size){ + d_partialModel.stopQueueingBoundCounts(); + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processBoundsQueue(utcb); + d_linEq.startTrackingBoundCounts(); + while(d_replayVariables.size() > rpvars_size){ + ArithVar v = d_replayVariables.back(); + d_replayVariables.pop_back(); + Assert(d_partialModel.canBeReleased(v)); + if(!d_tableau.isBasic(v)){ + /* if it is not basic make it basic. */ + auto ci = d_tableau.colIterator(v); + Assert(!ci.atEnd()); + ArithVar b = d_tableau.rowIndexToBasic((*ci).getRowIndex()); + Assert(b != ARITHVAR_SENTINEL); + DeltaRational cp = d_partialModel.getAssignment(b); + if(d_partialModel.cmpAssignmentLowerBound(b) < 0){ + cp = d_partialModel.getLowerBound(b); + }else if(d_partialModel.cmpAssignmentUpperBound(b) > 0){ + cp = d_partialModel.getUpperBound(b); + } + d_linEq.pivotAndUpdate(b, v, cp); + } + Assert(d_tableau.isBasic(v)); + d_linEq.stopTrackingRowIndex(d_tableau.basicToRowIndex(v)); + d_tableau.removeBasicRow(v); + + releaseArithVar(v); + Trace("approx::vars") << "releasing " << v << endl; + } + d_linEq.stopTrackingBoundCounts(); + d_partialModel.startQueueingBoundCounts(); + d_partialModel.attemptToReclaimReleased(); + } + return res; +} + +TreeLog& TheoryArithPrivate::getTreeLog(){ + if(d_treeLog == NULL){ + d_treeLog = new TreeLog(); + } + return *d_treeLog; +} + +ApproximateStatistics& TheoryArithPrivate::getApproxStats(){ + if(d_approxStats == NULL){ + d_approxStats = new ApproximateStatistics(); + } + return *d_approxStats; +} + +Node TheoryArithPrivate::branchToNode(ApproximateSimplex* approx, + const NodeLog& bn) const +{ + Assert(bn.isBranch()); + ArithVar v = approx->getBranchVar(bn); + if(v != ARITHVAR_SENTINEL && d_partialModel.isIntegerInput(v)){ + if(d_partialModel.hasNode(v)){ + Node n = d_partialModel.asNode(v); + double dval = bn.branchValue(); + std::optional maybe_value = + ApproximateSimplex::estimateWithCFE(dval); + if (!maybe_value) + { + return Node::null(); + } + Rational fl(maybe_value.value().floor()); + NodeManager* nm = NodeManager::currentNM(); + Node leq = + nm->mkNode(kind::LEQ, n, nm->mkConstRealOrInt(n.getType(), fl)); + Node norm = rewrite(leq); + return norm; + } + } + return Node::null(); +} + +Node TheoryArithPrivate::cutToLiteral(ApproximateSimplex* approx, const CutInfo& ci) const{ + Assert(ci.reconstructed()); + + const DenseMap& lhs = ci.getReconstruction().lhs; + Node sum = toSumNode(d_partialModel, lhs); + if(!sum.isNull()){ + NodeManager* nm = NodeManager::currentNM(); + Kind k = ci.getKind(); + Assert(k == kind::LEQ || k == kind::GEQ); + Node rhs = nm->mkConstRealOrInt(sum.getType(), ci.getReconstruction().rhs); + Node ineq = nm->mkNode(k, sum, rhs); + return rewrite(ineq); + } + return Node::null(); +} + +bool TheoryArithPrivate::replayLemmas(ApproximateSimplex* approx){ + ++(d_statistics.d_mipReplayLemmaCalls); + bool anythingnew = false; + + TreeLog& tl = getTreeLog(); + NodeLog& root = tl.getRootNode(); + root.applySelected(); /* set row ids */ + + vector cuts = approx->getValidCuts(root); + for(size_t i =0, N =cuts.size(); i < N; ++i){ + const CutInfo* cut = cuts[i]; + Assert(cut->reconstructed()); + Assert(cut->proven()); + + const DenseMap& row = cut->getReconstruction().lhs; + if (!complexityBelow(row, options().arith.lemmaRejectCutSize)) + { + ++(d_statistics.d_cutsRejectedDuringLemmas); + continue; + } + + Node cutConstraint = cutToLiteral(approx, *cut); + if(!cutConstraint.isNull()){ + const ConstraintCPVec& exp = cut->getExplanation(); + Node asLemma = Constraint::externalExplainByAssertions(exp); + + Node implied = rewrite(cutConstraint); + anythingnew = anythingnew || !isSatLiteral(implied); + + Node implication = asLemma.impNode(implied); + // DO NOT CALL OUTPUT LEMMA! + // TODO (project #37): justify + d_approxCuts.push_back(TrustNode::mkTrustLemma(implication, nullptr)); + Trace("approx::lemmas") << "cut["<mkTrustNode(branch, PfRule::SPLIT, {}, {lit}); + } + else + { + d_approxCuts.push_back(TrustNode::mkTrustLemma(branch, nullptr)); + } + ++(d_statistics.d_mipExternalBranch); + Trace("approx::lemmas") << "branching "<< root <<" as " << branch << endl; + } + } + return anythingnew; +} + +void TheoryArithPrivate::turnOffApproxFor(int32_t rounds){ + d_attemptSolveIntTurnedOff = d_attemptSolveIntTurnedOff + rounds; + ++(d_statistics.d_approxDisabled); +} + +bool TheoryArithPrivate::safeToCallApprox() const{ + unsigned numRows = 0; + unsigned numCols = 0; + var_iterator vi = var_begin(), vi_end = var_end(); + // Assign each variable to a row and column variable as it appears in the input + for(; vi != vi_end && !(numRows > 0 && numCols > 0); ++vi){ + ArithVar v = *vi; + + if(d_partialModel.isAuxiliary(v)){ + ++numRows; + }else{ + ++numCols; + } + } + return (numRows > 0 && numCols > 0); +} + +// solve() +// res = solveRealRelaxation(effortLevel); +// switch(res){ +// case LinFeas: +// case LinInfeas: +// return replay() +// case Unknown: +// case Error +// if() +void TheoryArithPrivate::solveInteger(Theory::Effort effortLevel){ + if(!safeToCallApprox()) { return; } + + Assert(safeToCallApprox()); + TimerStat::CodeTimer codeTimer0(d_statistics.d_solveIntTimer); + + ++(d_statistics.d_solveIntCalls); + d_statistics.d_inSolveInteger = 1; + + if(!Theory::fullEffort(effortLevel)){ + d_solveIntAttempts++; + ++(d_statistics.d_solveStandardEffort); + } + + // if integers are attempted, + Assert(options().arith.useApprox); + Assert(ApproximateSimplex::enabled()); + + int level = context()->getLevel(); + d_lastContextIntegerAttempted = level; + + static constexpr int32_t mipLimit = 200000; + + TreeLog& tl = getTreeLog(); + ApproximateStatistics& stats = getApproxStats(); + ApproximateSimplex* approx = + ApproximateSimplex::mkApproximateSimplexSolver(d_partialModel, tl, stats); + + approx->setPivotLimit(mipLimit); + if(!d_guessedCoeffSet){ + d_guessedCoeffs = approx->heuristicOptCoeffs(); + d_guessedCoeffSet = true; + } + if(!d_guessedCoeffs.empty()){ + approx->setOptCoeffs(d_guessedCoeffs); + } + static constexpr int32_t depthForLikelyInfeasible = 10; + int maxDepthPass1 = d_likelyIntegerInfeasible + ? depthForLikelyInfeasible + : options().arith.maxApproxDepth; + approx->setBranchingDepth(maxDepthPass1); + approx->setBranchOnVariableLimit(100); + LinResult relaxRes = approx->solveRelaxation(); + if( relaxRes == LinFeasible ){ + MipResult mipRes = MipUnknown; + { + TimerStat::CodeTimer codeTimer1(d_statistics.d_mipTimer); + mipRes = approx->solveMIP(false); + } + + Trace("arith::solveInteger") << "mipRes " << mipRes << endl; + switch(mipRes) { + case MipBingo: + // attempt the solution + { + ++(d_statistics.d_solveIntModelsAttempts); + + d_partialModel.stopQueueingBoundCounts(); + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processBoundsQueue(utcb); + d_linEq.startTrackingBoundCounts(); + + ApproximateSimplex::Solution mipSolution; + mipSolution = approx->extractMIP(); + importSolution(mipSolution); + solveRelaxationOrPanic(effortLevel); + + if (d_qflraStatus == Result::SAT) + { + if (!anyConflict()) + { + if (ARITHVAR_SENTINEL == nextIntegerViolation(false)) + { + ++(d_statistics.d_solveIntModelsSuccessful); + } + } + } + + // shutdown simplex + d_linEq.stopTrackingBoundCounts(); + d_partialModel.startQueueingBoundCounts(); + } + break; + case MipClosed: + /* All integer branches closed */ + approx->setPivotLimit(2*mipLimit); + { + TimerStat::CodeTimer codeTimer2(d_statistics.d_mipTimer); + mipRes = approx->solveMIP(true); + } + + if(mipRes == MipClosed){ + d_likelyIntegerInfeasible = true; + replayLog(approx); + AlwaysAssert(anyConflict() || d_qflraStatus != Result::SAT); + + if (!anyConflict()) + { + solveRealRelaxation(effortLevel); + } + } + if(!(anyConflict() || !d_approxCuts.empty())){ + turnOffApproxFor(options().arith.replayNumericFailurePenalty); + } + break; + case BranchesExhausted: + case ExecExhausted: + case PivotsExhauasted: + if(mipRes == BranchesExhausted){ + ++d_statistics.d_branchesExhausted; + }else if(mipRes == ExecExhausted){ + ++d_statistics.d_execExhausted; + }else if(mipRes == PivotsExhauasted){ + ++d_statistics.d_pivotsExhausted; + } + + approx->setPivotLimit(2*mipLimit); + approx->setBranchingDepth(2); + { + TimerStat::CodeTimer codeTimer3(d_statistics.d_mipTimer); + mipRes = approx->solveMIP(true); + } + replayLemmas(approx); + break; + case MipUnknown: + break; + } + } + delete approx; + + if(!Theory::fullEffort(effortLevel)){ + if(anyConflict() || !d_approxCuts.empty()){ + d_solveIntMaybeHelp++; + } + } + + d_statistics.d_inSolveInteger = 0; +} + +SimplexDecisionProcedure& TheoryArithPrivate::selectSimplex(bool pass1){ + if(pass1){ + if(d_pass1SDP == NULL){ + if (options().arith.useFC) + { + d_pass1SDP = (SimplexDecisionProcedure*)(&d_fcSimplex); + } + else if (options().arith.useSOI) + { + d_pass1SDP = (SimplexDecisionProcedure*)(&d_soiSimplex); + } + else + { + d_pass1SDP = (SimplexDecisionProcedure*)(&d_dualSimplex); + } + } + Assert(d_pass1SDP != NULL); + return *d_pass1SDP; + }else{ + if(d_otherSDP == NULL){ + if (options().arith.useFC) + { + d_otherSDP = (SimplexDecisionProcedure*)(&d_fcSimplex); + } + else if (options().arith.useSOI) + { + d_otherSDP = (SimplexDecisionProcedure*)(&d_soiSimplex); + } + else + { + d_otherSDP = (SimplexDecisionProcedure*)(&d_soiSimplex); + } + } + Assert(d_otherSDP != NULL); + return *d_otherSDP; + } +} + +void TheoryArithPrivate::importSolution(const ApproximateSimplex::Solution& solution){ + if(TraceIsOn("arith::importSolution")){ + Trace("arith::importSolution") << "importSolution before " << d_qflraStatus << endl; + d_partialModel.printEntireModel(Trace("arith::importSolution")); + } + + d_qflraStatus = d_attemptSolSimplex.attempt(solution); + + if(TraceIsOn("arith::importSolution")){ + Trace("arith::importSolution") << "importSolution intermediate " << d_qflraStatus << endl; + d_partialModel.printEntireModel(Trace("arith::importSolution")); + } + + if(d_qflraStatus != Result::UNSAT){ + static constexpr int64_t pass2Limit = 20; + SimplexDecisionProcedure& simplex = selectSimplex(false); + simplex.setVarOrderPivotLimit(pass2Limit); + d_qflraStatus = simplex.findModel(false); + } + + if(TraceIsOn("arith::importSolution")){ + Trace("arith::importSolution") << "importSolution after " << d_qflraStatus << endl; + d_partialModel.printEntireModel(Trace("arith::importSolution")); + } +} + +bool TheoryArithPrivate::solveRelaxationOrPanic(Theory::Effort effortLevel) +{ + // if at this point the linear relaxation is still unknown, + // attempt to branch an integer variable as a last ditch effort on full check + if (d_qflraStatus == Result::UNKNOWN) + { + d_qflraStatus = selectSimplex(true).findModel(false); + } + + if (Theory::fullEffort(effortLevel) && d_qflraStatus == Result::UNKNOWN) + { + ArithVar canBranch = nextIntegerViolation(false); + if (canBranch != ARITHVAR_SENTINEL) + { + ++d_statistics.d_panicBranches; + TrustNode branch = branchIntegerVariable(canBranch); + Assert(branch.getNode().getKind() == kind::OR); + Node rwbranch = rewrite(branch.getNode()[0]); + if (!isSatLiteral(rwbranch)) + { + d_approxCuts.push_back(branch); + return true; + } + } + d_qflraStatus = selectSimplex(false).findModel(true); + } + return false; +} + +bool TheoryArithPrivate::solveRealRelaxation(Theory::Effort effortLevel){ + TimerStat::CodeTimer codeTimer0(d_statistics.d_solveRealRelaxTimer); + Assert(d_qflraStatus != Result::SAT); + + d_partialModel.stopQueueingBoundCounts(); + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processBoundsQueue(utcb); + d_linEq.startTrackingBoundCounts(); + + bool noPivotLimit = + Theory::fullEffort(effortLevel) || !options().arith.restrictedPivots; + + SimplexDecisionProcedure& simplex = selectSimplex(true); + + bool useApprox = options().arith.useApprox && ApproximateSimplex::enabled() + && getSolveIntegerResource(); + + Trace("TheoryArithPrivate::solveRealRelaxation") + << "solveRealRelaxation() approx" + << " " << options().arith.useApprox << " " + << ApproximateSimplex::enabled() << " " << useApprox << " " + << safeToCallApprox() << endl; + + bool noPivotLimitPass1 = noPivotLimit && !useApprox; + d_qflraStatus = simplex.findModel(noPivotLimitPass1); + + Trace("TheoryArithPrivate::solveRealRelaxation") + << "solveRealRelaxation()" << " pass1 " << d_qflraStatus << endl; + + if (d_qflraStatus == Result::UNKNOWN && useApprox && safeToCallApprox()) + { + // pass2: fancy-final + static constexpr int32_t relaxationLimit = 10000; + Assert(ApproximateSimplex::enabled()); + + TreeLog& tl = getTreeLog(); + ApproximateStatistics& stats = getApproxStats(); + ApproximateSimplex* approxSolver = + ApproximateSimplex::mkApproximateSimplexSolver(d_partialModel, tl, stats); + + approxSolver->setPivotLimit(relaxationLimit); + + if(!d_guessedCoeffSet){ + d_guessedCoeffs = approxSolver->heuristicOptCoeffs(); + d_guessedCoeffSet = true; + } + if(!d_guessedCoeffs.empty()){ + approxSolver->setOptCoeffs(d_guessedCoeffs); + } + + ++d_statistics.d_relaxCalls; + + ApproximateSimplex::Solution relaxSolution; + LinResult relaxRes = LinUnknown; + { + TimerStat::CodeTimer codeTimer1(d_statistics.d_lpTimer); + relaxRes = approxSolver->solveRelaxation(); + } + Trace("solveRealRelaxation") << "solve relaxation? " << endl; + switch(relaxRes){ + case LinFeasible: + Trace("solveRealRelaxation") << "lin feasible? " << endl; + ++d_statistics.d_relaxLinFeas; + relaxSolution = approxSolver->extractRelaxation(); + importSolution(relaxSolution); + if(d_qflraStatus != Result::SAT){ + ++d_statistics.d_relaxLinFeasFailures; + } + break; + case LinInfeasible: + // todo attempt to recreate approximate conflict + ++d_statistics.d_relaxLinInfeas; + Trace("solveRealRelaxation") << "lin infeasible " << endl; + relaxSolution = approxSolver->extractRelaxation(); + importSolution(relaxSolution); + if(d_qflraStatus != Result::UNSAT){ + ++d_statistics.d_relaxLinInfeasFailures; + } + break; + case LinExhausted: + ++d_statistics.d_relaxLinExhausted; + Trace("solveRealRelaxation") << "exhuasted " << endl; + break; + case LinUnknown: + default: + ++d_statistics.d_relaxOthers; + break; + } + delete approxSolver; + + } + + bool emmittedConflictOrSplit = solveRelaxationOrPanic(effortLevel); + + // TODO Save zeroes with no conflicts + d_linEq.stopTrackingBoundCounts(); + d_partialModel.startQueueingBoundCounts(); + + return emmittedConflictOrSplit; +} + +bool TheoryArithPrivate::hasFreshArithLiteral(Node n) const{ + switch(n.getKind()){ + case kind::LEQ: + case kind::GEQ: + case kind::GT: + case kind::LT: + return !isSatLiteral(n); + case kind::EQUAL: + if (n[0].getType().isRealOrInt()) + { + return !isSatLiteral(n); + } + else if (n[0].getType().isBoolean()) + { + return hasFreshArithLiteral(n[0]) || + hasFreshArithLiteral(n[1]); + } + else + { + return false; + } + case kind::IMPLIES: + // try the rhs first + return hasFreshArithLiteral(n[1]) || + hasFreshArithLiteral(n[0]); + default: + if(n.getType().isBoolean()){ + for(Node::iterator ni=n.begin(), nend=n.end(); ni!=nend; ++ni){ + Node child = *ni; + if(hasFreshArithLiteral(child)){ + return true; + } + } + } + return false; + } +} + +bool TheoryArithPrivate::preCheck(Theory::Effort level) +{ + Assert(d_currentPropagationList.empty()); + if(TraceIsOn("arith::consistency")){ + Assert(unenqueuedVariablesAreConsistent()); + } + + d_newFacts = !done(); + // If d_previousStatus == SAT, then reverts on conflicts are safe + // Otherwise, they are not and must be committed. + d_previousStatus = d_qflraStatus; + if (d_newFacts) + { + d_qflraStatus = Result::UNKNOWN; + d_hasDoneWorkSinceCut = true; + } + return false; +} + +void TheoryArithPrivate::preNotifyFact(TNode atom, bool pol, TNode fact) +{ + ConstraintP curr = constraintFromFactQueue(fact); + if (curr != NullConstraint) + { + bool res CVC5_UNUSED = assertionCases(curr); + Assert(!res || anyConflict()); + } +} + +bool TheoryArithPrivate::postCheck(Theory::Effort effortLevel) +{ + if(!anyConflict()){ + while(!d_learnedBounds.empty()){ + // we may attempt some constraints twice. this is okay! + ConstraintP curr = d_learnedBounds.front(); + d_learnedBounds.pop(); + Trace("arith::learned") << curr << endl; + + bool res CVC5_UNUSED = assertionCases(curr); + Assert(!res || anyConflict()); + + if(anyConflict()){ break; } + } + } + + if(anyConflict()){ + d_qflraStatus = Result::UNSAT; + if (options().arith.revertArithModels && d_previousStatus == Result::SAT) + { + ++d_statistics.d_revertsOnConflicts; + Trace("arith::bt") << "clearing here " + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + revertOutOfConflict(); + d_errorSet.clear(); + }else{ + ++d_statistics.d_commitsOnConflicts; + Trace("arith::bt") << "committing here " + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + revertOutOfConflict(); + } + outputConflicts(); + //cout << "unate conflict 1 " << effortLevel << std::endl; + return true; + } + + + if(TraceIsOn("arith::print_assertions")) { + debugPrintAssertions(Trace("arith::print_assertions")); + } + + bool emmittedConflictOrSplit = false; + Assert(d_conflicts.empty()); + + bool useSimplex = d_qflraStatus != Result::SAT; + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "pre realRelax" << endl; + + if(useSimplex){ + emmittedConflictOrSplit = solveRealRelaxation(effortLevel); + } + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "post realRelax" << endl; + + + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "pre solveInteger" << endl; + + if(attemptSolveInteger(effortLevel, emmittedConflictOrSplit)){ + solveInteger(effortLevel); + if(anyConflict()){ + ++d_statistics.d_commitsOnConflicts; + Trace("arith::bt") << "committing here " + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + revertOutOfConflict(); + d_errorSet.clear(); + outputConflicts(); + return true; + } + } + + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "post solveInteger" << endl; + + switch(d_qflraStatus){ + case Result::SAT: + if (d_newFacts) + { + ++d_statistics.d_nontrivialSatChecks; + } + + Trace("arith::bt") << "committing sap inConflit" + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + d_unknownsInARow = 0; + if(TraceIsOn("arith::consistency")){ + Assert(entireStateIsConsistent("sat comit")); + } + if (useSimplex && options().arith.collectPivots) + { + if (options().arith.useFC) + { + d_statistics.d_satPivots << d_fcSimplex.getPivots(); + } + else + { + d_statistics.d_satPivots << d_dualSimplex.getPivots(); + } + } + break; + case Result::UNKNOWN: + ++d_unknownsInARow; + ++(d_statistics.d_unknownChecks); + Assert(!Theory::fullEffort(effortLevel)); + Trace("arith::bt") << "committing unknown" + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + d_statistics.d_maxUnknownsInARow.maxAssign(d_unknownsInARow); + + if (useSimplex && options().arith.collectPivots) + { + if (options().arith.useFC) + { + d_statistics.d_unknownPivots << d_fcSimplex.getPivots(); + } + else + { + d_statistics.d_unknownPivots << d_dualSimplex.getPivots(); + } + } + break; + case Result::UNSAT: + d_unknownsInARow = 0; + + ++d_statistics.d_commitsOnConflicts; + + Trace("arith::bt") << "committing on conflict" + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + d_partialModel.commitAssignmentChanges(); + revertOutOfConflict(); + + if(TraceIsOn("arith::consistency::comitonconflict")){ + entireStateIsConsistent("commit on conflict"); + } + outputConflicts(); + emmittedConflictOrSplit = true; + Trace("arith::conflict") << "simplex conflict" << endl; + + if (useSimplex && options().arith.collectPivots) + { + if (options().arith.useFC) + { + d_statistics.d_unsatPivots << d_fcSimplex.getPivots(); + } + else + { + d_statistics.d_unsatPivots << d_dualSimplex.getPivots(); + } + } + break; + default: + Unimplemented(); + } + d_statistics.d_avgUnknownsInARow << d_unknownsInARow; + + size_t nPivots = options().arith.useFC ? d_fcSimplex.getPivots() + : d_dualSimplex.getPivots(); + for (std::size_t i = 0; i < nPivots; ++i) + { + d_containing.d_out->spendResource( + Resource::ArithPivotStep); + } + + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "pre approx cuts" << endl; + if(!d_approxCuts.empty()){ + bool anyFresh = false; + while(!d_approxCuts.empty()){ + TrustNode lem = d_approxCuts.front(); + d_approxCuts.pop(); + Trace("arith::approx::cuts") << "approximate cut:" << lem << endl; + anyFresh = anyFresh || hasFreshArithLiteral(lem.getNode()); + Trace("arith::lemma") << "approximate cut:" << lem << endl; + outputTrustedLemma(lem, InferenceId::ARITH_APPROX_CUT); + } + if(anyFresh){ + emmittedConflictOrSplit = true; + } + } + + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "post approx cuts" << endl; + + // This should be fine if sat or unknown + if (!emmittedConflictOrSplit + && (options().arith.arithPropagationMode + == options::ArithPropagationMode::UNATE_PROP + || options().arith.arithPropagationMode + == options::ArithPropagationMode::BOTH_PROP)) + { + TimerStat::CodeTimer codeTimer0(d_statistics.d_newPropTime); + Assert(d_qflraStatus != Result::UNSAT); + + while(!d_currentPropagationList.empty() && !anyConflict()){ + ConstraintP curr = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + + ConstraintType t = curr->getType(); + Assert(t != Disequality) + << "Disequalities are not allowed in d_currentPropagation"; + + switch(t){ + case LowerBound: + { + ConstraintP prev = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + d_constraintDatabase.unatePropLowerBound(curr, prev); + break; + } + case UpperBound: + { + ConstraintP prev = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + d_constraintDatabase.unatePropUpperBound(curr, prev); + break; + } + case Equality: + { + ConstraintP prevLB = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + ConstraintP prevUB = d_currentPropagationList.front(); + d_currentPropagationList.pop_front(); + d_constraintDatabase.unatePropEquality(curr, prevLB, prevUB); + break; + } + default: Unhandled() << curr->getType(); + } + } + + if(anyConflict()){ + Trace("arith::unate") << "unate conflict" << endl; + revertOutOfConflict(); + d_qflraStatus = Result::UNSAT; + outputConflicts(); + emmittedConflictOrSplit = true; + //cout << "unate conflict " << endl; + Trace("arith::bt") << "committing on unate conflict" + << " " << d_newFacts << " " << d_previousStatus << " " + << d_qflraStatus << endl; + + Trace("arith::conflict") << "unate arith conflict" << endl; + } + } + else + { + TimerStat::CodeTimer codeTimer1(d_statistics.d_newPropTime); + d_currentPropagationList.clear(); + } + Assert(d_currentPropagationList.empty()); + + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "post unate" << endl; + + if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel)){ + ++d_fullCheckCounter; + } + if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel)){ + emmittedConflictOrSplit = splitDisequalities(); + } + Trace("arith::ems") << "ems: " << emmittedConflictOrSplit + << "pos splitting" << endl; + + + Trace("arith") << "integer? " + << " conf/split " << emmittedConflictOrSplit + << " fulleffort " << Theory::fullEffort(effortLevel) << endl; + + if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel) && !hasIntegerModel()){ + Node possibleConflict = Node::null(); + if (!emmittedConflictOrSplit && options().arith.arithDioSolver) + { + possibleConflict = callDioSolver(); + if(possibleConflict != Node::null()){ + revertOutOfConflict(); + Trace("arith::conflict") << "dio conflict " << possibleConflict << endl; + // TODO (project #37): justify (proofs in the DIO solver) + raiseBlackBoxConflict(possibleConflict); + outputConflicts(); + emmittedConflictOrSplit = true; + } + } + + if (!emmittedConflictOrSplit && d_hasDoneWorkSinceCut + && options().arith.arithDioSolver) + { + if(getDioCuttingResource()){ + TrustNode possibleLemma = dioCutting(); + if(!possibleLemma.isNull()){ + d_hasDoneWorkSinceCut = false; + d_cutCount = d_cutCount + 1; + Trace("arith::lemma") << "dio cut " << possibleLemma << endl; + if (outputTrustedLemma(possibleLemma, InferenceId::ARITH_DIO_CUT)) + { + emmittedConflictOrSplit = true; + } + } + } + } + + if(!emmittedConflictOrSplit) { + TrustNode possibleLemma = roundRobinBranch(); + if (!possibleLemma.getNode().isNull()) + { + ++(d_statistics.d_externalBranchAndBounds); + d_cutCount = d_cutCount + 1; + Trace("arith::lemma") << "rrbranch lemma" + << possibleLemma << endl; + if (outputTrustedLemma(possibleLemma, InferenceId::ARITH_BB_LEMMA)) + { + emmittedConflictOrSplit = true; + } + } + } + + if (options().arith.maxCutsInContext <= d_cutCount) + { + if(d_diosolver.hasMoreDecompositionLemmas()){ + while(d_diosolver.hasMoreDecompositionLemmas()){ + Node decompositionLemma = d_diosolver.nextDecompositionLemma(); + Trace("arith::lemma") << "dio decomposition lemma " + << decompositionLemma << endl; + outputLemma(decompositionLemma, InferenceId::ARITH_DIO_DECOMPOSITION); + } + }else{ + Trace("arith::restart") << "arith restart!" << endl; + outputRestart(); + } + } + }//if !emmittedConflictOrSplit && fullEffort(effortLevel) && !hasIntegerModel() + + if(Theory::fullEffort(effortLevel)){ + if(TraceIsOn("arith::consistency::final")){ + entireStateIsConsistent("arith::consistency::final"); + } + } + + if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + if(TraceIsOn("arith::print_model")) { + debugPrintModel(Trace("arith::print_model")); + } + Trace("arith") << "TheoryArithPrivate::check end" << std::endl; + return emmittedConflictOrSplit; +} + +bool TheoryArithPrivate::foundNonlinear() const { return d_foundNl; } + +TrustNode TheoryArithPrivate::branchIntegerVariable(ArithVar x) const +{ + const DeltaRational& d = d_partialModel.getAssignment(x); + Assert(!d.isIntegral()); + const Rational& r = d.getNoninfinitesimalPart(); + const Rational& i = d.getInfinitesimalPart(); + Trace("integers") << "integers: assignment to [[" << d_partialModel.asNode(x) << "]] is " << r << "[" << i << "]" << endl; + Assert(!(r.getDenominator() == 1 && i.getNumerator() == 0)); + TNode var = d_partialModel.asNode(x); + TrustNode lem = d_bab.branchIntegerVariable(var, r); + if (TraceIsOn("integers")) + { + Node l = lem.getNode(); + if (isSatLiteral(l[0])) + { + Trace("integers") << " " << l[0] << " == " << getSatValue(l[0]) + << endl; + } + else + { + Trace("integers") << " " << l[0] << " is not assigned a SAT literal" + << endl; + } + if (isSatLiteral(l[1])) + { + Trace("integers") << " " << l[1] << " == " << getSatValue(l[1]) + << endl; + } + else + { + Trace("integers") << " " << l[1] << " is not assigned a SAT literal" + << endl; + } + } + return lem; +} + +std::vector TheoryArithPrivate::cutAllBounded() const{ + vector lemmas; + ArithVar max = d_partialModel.getNumberOfVariables(); + + if (options().arith.doCutAllBounded && max > 0) + { + for(ArithVar iter = 0; iter != max; ++iter){ + //Do not include slack variables + const DeltaRational& d = d_partialModel.getAssignment(iter); + if(isIntegerInput(iter) && + !d_cutInContext.contains(iter) && + d_partialModel.hasUpperBound(iter) && + d_partialModel.hasLowerBound(iter) && + !d.isIntegral()){ + lemmas.push_back(iter); + } + } + } + return lemmas; +} + +/** Returns true if the roundRobinBranching() issues a lemma. */ +TrustNode TheoryArithPrivate::roundRobinBranch() +{ + if(hasIntegerModel()){ + return TrustNode::null(); + }else{ + ArithVar v = d_nextIntegerCheckVar; + + Assert(isInteger(v)); + Assert(!isAuxiliaryVariable(v)); + return branchIntegerVariable(v); + } +} + +bool TheoryArithPrivate::splitDisequalities(){ + bool splitSomething = false; + + vector save; + + while(!d_diseqQueue.empty()){ + ConstraintP front = d_diseqQueue.front(); + d_diseqQueue.pop(); + + if(front->isSplit()){ + Trace("arith::eq") << "split already" << endl; + }else{ + Trace("arith::eq") << "not split already" << endl; + + ArithVar lhsVar = front->getVariable(); + + const DeltaRational& lhsValue = d_partialModel.getAssignment(lhsVar); + const DeltaRational& rhsValue = front->getValue(); + if(lhsValue == rhsValue){ + Trace("arith::lemma") << "Splitting on " << front << endl; + Trace("arith::lemma") << "LHS value = " << lhsValue << endl; + Trace("arith::lemma") << "RHS value = " << rhsValue << endl; + TrustNode lemma = front->split(); + ++(d_statistics.d_statDisequalitySplits); + + Trace("arith::lemma") << "Now " << lemma.getNode() << endl; + outputTrustedLemma(lemma, InferenceId::ARITH_SPLIT_DEQ); + splitSomething = true; + }else if(d_partialModel.strictlyLessThanLowerBound(lhsVar, rhsValue)){ + Trace("arith::eq") << "can drop as less than lb" << front << endl; + }else if(d_partialModel.strictlyGreaterThanUpperBound(lhsVar, rhsValue)){ + Trace("arith::eq") << "can drop as greater than ub" << front << endl; + }else{ + Trace("arith::eq") << "save" << front << ": " <::const_iterator it = d_diseqQueue.begin(); + context::CDQueue::const_iterator it_end = d_diseqQueue.end(); + for(; it != it_end; ++ it) { + out << *it << endl; + } +} + +void TheoryArithPrivate::debugPrintModel(std::ostream& out) const{ + out << "Model:" << endl; + for (var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar i = *vi; + if(d_partialModel.hasNode(i)){ + out << d_partialModel.asNode(i) << " : " << + d_partialModel.getAssignment(i); + if(d_tableau.isBasic(i)){ + out << " (basic)"; + } + out << endl; + } + } +} + +TrustNode TheoryArithPrivate::explain(TNode n) +{ + Trace("arith::explain") << "explain @" << context()->getLevel() << ": " << n + << endl; + + ConstraintP c = d_constraintDatabase.lookup(n); + TrustNode exp; + if(c != NullConstraint){ + Assert(!c->isAssumption()); + exp = c->externalExplainForPropagation(n); + Trace("arith::explain") << "constraint explanation" << n << ":" << exp << endl; + }else if(d_assertionsThatDoNotMatchTheirLiterals.find(n) != d_assertionsThatDoNotMatchTheirLiterals.end()){ + c = d_assertionsThatDoNotMatchTheirLiterals[n]; + if(!c->isAssumption()){ + exp = c->externalExplainForPropagation(n); + Trace("arith::explain") << "assertions explanation" << n << ":" << exp << endl; + }else{ + Trace("arith::explain") << "this is a strange mismatch" << n << endl; + Assert(d_congruenceManager.canExplain(n)); + exp = d_congruenceManager.explain(n); + } + }else{ + Assert(d_congruenceManager.canExplain(n)); + Trace("arith::explain") << "dm explanation" << n << endl; + exp = d_congruenceManager.explain(n); + } + return exp; +} + +void TheoryArithPrivate::propagate(Theory::Effort e) { + // This uses model values for safety. Disable for now. + if (d_qflraStatus == Result::SAT + && (options().arith.arithPropagationMode + == options::ArithPropagationMode::BOUND_INFERENCE_PROP + || options().arith.arithPropagationMode + == options::ArithPropagationMode::BOTH_PROP) + && hasAnyUpdates()) + { + if (options().arith.newProp) + { + propagateCandidatesNew(); + } + else + { + propagateCandidates(); + } + } + else + { + clearUpdates(); + } + + while(d_constraintDatabase.hasMorePropagations()){ + ConstraintCP c = d_constraintDatabase.nextPropagation(); + Trace("arith::prop") << "next prop" << context()->getLevel() << ": " << c + << endl; + + if(c->negationHasProof()){ + Trace("arith::prop") << "negation has proof " << c->getNegation() << endl; + Trace("arith::prop") << c->getNegation()->externalExplainByAssertions() + << endl; + } + Assert(!c->negationHasProof()) + << "A constraint has been propagated on the constraint propagation " + "queue, but the negation has been set to true. Contact Tim now!"; + + if(!c->assertedToTheTheory()){ + Node literal = c->getLiteral(); + Trace("arith::prop") << "propagating @" << context()->getLevel() << " " + << literal << endl; + + outputPropagate(literal); + }else{ + Trace("arith::prop") << "already asserted to the theory " << c->getLiteral() << endl; + } + } + + NodeManager* nm = NodeManager::currentNM(); + while(d_congruenceManager.hasMorePropagations()){ + TNode toProp = d_congruenceManager.getNextPropagation(); + + //Currently if the flag is set this came from an equality detected by the + //equality engine in the the difference manager. + Node normalized = rewrite(toProp); + + ConstraintP constraint = d_constraintDatabase.lookup(normalized); + if(constraint == NullConstraint){ + Trace("arith::prop") << "propagating on non-constraint? " << toProp << endl; + + outputPropagate(toProp); + }else if(constraint->negationHasProof()){ + // The congruence manager can prove: antecedents => toProp, + // ergo. antecedents ^ ~toProp is a conflict. + TrustNode exp = d_congruenceManager.explain(toProp); + Node notNormalized = normalized.negate(); + std::vector ants(exp.getNode().begin(), exp.getNode().end()); + ants.push_back(notNormalized); + Node lp = nm->mkAnd(ants); + Trace("arith::prop") << "propagate conflict" << lp << endl; + if (proofsEnabled()) + { + // Assume all of antecedents and ~toProp (rewritten) + std::vector pfAntList; + for (size_t i = 0; i < ants.size(); ++i) + { + pfAntList.push_back(d_pnm->mkAssume(ants[i])); + } + Pf pfAnt = pfAntList.size() > 1 + ? d_pnm->mkNode(PfRule::AND_INTRO, pfAntList, {}) + : pfAntList[0]; + // Use modus ponens to get toProp (un rewritten) + Pf pfConc = d_pnm->mkNode( + PfRule::MODUS_PONENS, + {pfAnt, exp.getGenerator()->getProofFor(exp.getProven())}, + {}); + // prove toProp (rewritten) + Pf pfConcRewritten = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {pfConc}, {normalized}); + Pf pfNotNormalized = d_pnm->mkAssume(notNormalized); + // prove bottom from toProp and ~toProp + Pf pfBot; + if (normalized.getKind() == kind::NOT) + { + pfBot = d_pnm->mkNode( + PfRule::CONTRA, {pfNotNormalized, pfConcRewritten}, {}); + } + else + { + pfBot = d_pnm->mkNode( + PfRule::CONTRA, {pfConcRewritten, pfNotNormalized}, {}); + } + // close scope + Pf pfNotAnd = d_pnm->mkScope(pfBot, ants); + raiseBlackBoxConflict(lp, pfNotAnd); + } + else + { + raiseBlackBoxConflict(lp); + } + outputConflicts(); + return; + }else{ + Trace("arith::prop") << "propagating still?" << toProp << endl; + outputPropagate(toProp); + } + } +} + +DeltaRational TheoryArithPrivate::getDeltaValue(TNode term) const +{ + AlwaysAssert(d_qflraStatus != Result::UNKNOWN); + Trace("arith::value") << term << std::endl; + + if (d_partialModel.hasArithVar(term)) { + ArithVar var = d_partialModel.asArithVar(term); + return d_partialModel.getAssignment(var); + } + + switch (Kind kind = term.getKind()) { + case kind::CONST_RATIONAL: + case kind::CONST_INTEGER: return term.getConst(); + + case kind::ADD: + { // 2+ args + DeltaRational value(0); + for (TNode::iterator i = term.begin(), iend = term.end(); i != iend; + ++i) { + value = value + getDeltaValue(*i); + } + return value; + } + + case kind::NONLINEAR_MULT: + case kind::MULT: { // 2+ args + Assert(!isSetup(term)); + DeltaRational value(1); + for (TNode::iterator i = term.begin(), iend = term.end(); i != iend; + ++i) { + value = value * getDeltaValue(*i); + } + return value; + } + case kind::SUB: + { // 2 args + return getDeltaValue(term[0]) - getDeltaValue(term[1]); + } + case kind::NEG: + { // 1 arg + return (-getDeltaValue(term[0])); + } + + case kind::DIVISION: { // 2 args + Assert(!isSetup(term)); + return getDeltaValue(term[0]) / getDeltaValue(term[1]); + } + case kind::DIVISION_TOTAL: + case kind::INTS_DIVISION_TOTAL: + case kind::INTS_MODULUS_TOTAL: { // 2 args + Assert(!isSetup(term)); + DeltaRational denominator = getDeltaValue(term[1]); + if (denominator.isZero()) { + return DeltaRational(0, 0); + } + DeltaRational numerator = getDeltaValue(term[0]); + if (kind == kind::DIVISION_TOTAL) { + return numerator / denominator; + } else if (kind == kind::INTS_DIVISION_TOTAL) { + return Rational(numerator.euclidianDivideQuotient(denominator)); + } else { + Assert(kind == kind::INTS_MODULUS_TOTAL); + return Rational(numerator.euclidianDivideRemainder(denominator)); + } + } + + default: + throw ModelException(term, "No model assignment."); + } +} + +Rational TheoryArithPrivate::deltaValueForTotalOrder() const{ + Rational min(2); + std::set relevantDeltaValues; + context::CDQueue::const_iterator qiter = d_diseqQueue.begin(); + context::CDQueue::const_iterator qiter_end = d_diseqQueue.end(); + + for(; qiter != qiter_end; ++qiter){ + ConstraintP curr = *qiter; + + const DeltaRational& rhsValue = curr->getValue(); + relevantDeltaValues.insert(rhsValue); + } + + Theory::shared_terms_iterator shared_iter = d_containing.shared_terms_begin(); + Theory::shared_terms_iterator shared_end = d_containing.shared_terms_end(); + for(; shared_iter != shared_end; ++shared_iter){ + Node sharedCurr = *shared_iter; + + // ModelException is fatal as this point. Don't catch! + // DeltaRationalException is fatal as this point. Don't catch! + DeltaRational val = getDeltaValue(sharedCurr); + relevantDeltaValues.insert(val); + } + + for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar v = *vi; + const DeltaRational& value = d_partialModel.getAssignment(v); + relevantDeltaValues.insert(value); + if( d_partialModel.hasLowerBound(v)){ + const DeltaRational& lb = d_partialModel.getLowerBound(v); + relevantDeltaValues.insert(lb); + } + if( d_partialModel.hasUpperBound(v)){ + const DeltaRational& ub = d_partialModel.getUpperBound(v); + relevantDeltaValues.insert(ub); + } + } + + if(relevantDeltaValues.size() >= 2){ + std::set::const_iterator iter = relevantDeltaValues.begin(); + std::set::const_iterator iter_end = relevantDeltaValues.end(); + DeltaRational prev = *iter; + ++iter; + for(; iter != iter_end; ++iter){ + const DeltaRational& curr = *iter; + + Assert(prev < curr); + + DeltaRational::seperatingDelta(min, prev, curr); + prev = curr; + } + } + + Assert(min.sgn() > 0); + Rational belowMin = min/Rational(2); + return belowMin; +} + +void TheoryArithPrivate::collectModelValues(const std::set& termSet, + std::map& arithModel) +{ + AlwaysAssert(d_qflraStatus == Result::SAT); + + if(TraceIsOn("arith::collectModelInfo")){ + debugPrintFacts(); + } + + Trace("arith::collectModelInfo") << "collectModelInfo() begin " << endl; + + // Delta lasts at least the duration of the function call + const Rational& delta = d_partialModel.getDelta(); + std::unordered_set shared = d_containing.currentlySharedTerms(); + + // TODO: + // This is not very good for user push/pop.... + // Revisit when implementing push/pop + NodeManager* nm = NodeManager::currentNM(); + for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar v = *vi; + + if(!isAuxiliaryVariable(v)){ + Node term = d_partialModel.asNode(v); + + if((theoryOf(term) == THEORY_ARITH || shared.find(term) != shared.end()) + && termSet.find(term) != termSet.end()){ + + const DeltaRational& mod = d_partialModel.getAssignment(v); + Rational qmodel = mod.substituteDelta(delta); + + Node qNode = nm->mkConstRealOrInt(term.getType(), qmodel); + Trace("arith::collectModelInfo") << "m->assertEquality(" << term << ", " << qmodel << ", true)" << endl; + // Add to the map + arithModel[term] = qNode; + }else{ + Trace("arith::collectModelInfo") << "Skipping m->assertEquality(" << term << ", true)" << endl; + + } + } + } + + // Iterate over equivalence classes in LinearEqualityModule + // const eq::EqualityEngine& ee = d_congruenceManager.getEqualityEngine(); + // m->assertEqualityEngine(&ee); + + Trace("arith::collectModelInfo") << "collectModelInfo() end " << endl; +} + +bool TheoryArithPrivate::safeToReset() const { + Assert(!d_tableauSizeHasBeenModified); + Assert(d_errorSet.noSignals()); + + ErrorSet::error_iterator error_iter = d_errorSet.errorBegin(); + ErrorSet::error_iterator error_end = d_errorSet.errorEnd(); + for(; error_iter != error_end; ++error_iter){ + ArithVar basic = *error_iter; + if(!d_smallTableauCopy.isBasic(basic)){ + return false; + } + } + + return true; +} + +void TheoryArithPrivate::notifyRestart(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_restartTimer); + + if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + + ++d_restartsCounter; + d_solveIntMaybeHelp = 0; + d_solveIntAttempts = 0; +} + +bool TheoryArithPrivate::entireStateIsConsistent(const string& s){ + bool result = true; + for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar var = *vi; + //ArithVar var = d_partialModel.asArithVar(*i); + if(!d_partialModel.assignmentIsConsistent(var)){ + d_partialModel.printModel(var); + warning() << s << ":" << "Assignment is not consistent for " << var << d_partialModel.asNode(var); + if(d_tableau.isBasic(var)){ + warning() << " (basic)"; + } + warning() << std::endl; + result = false; + }else if(d_partialModel.isInteger(var) && !d_partialModel.integralAssignment(var)){ + d_partialModel.printModel(var); + warning() << s << ":" << "Assignment is not integer for integer variable " << var << d_partialModel.asNode(var); + if(d_tableau.isBasic(var)){ + warning() << " (basic)"; + } + warning() << std::endl; + result = false; + } + } + return result; +} + +bool TheoryArithPrivate::unenqueuedVariablesAreConsistent(){ + bool result = true; + for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ + ArithVar var = *vi; + if(!d_partialModel.assignmentIsConsistent(var)){ + if(!d_errorSet.inError(var)){ + + d_partialModel.printModel(var); + warning() << "Unenqueued var is not consistent for " << var << d_partialModel.asNode(var); + if(d_tableau.isBasic(var)){ + warning() << " (basic)"; + } + warning() << std::endl; + result = false; + } else if(TraceIsOn("arith::consistency::initial")){ + d_partialModel.printModel(var); + warning() << "Initial var is not consistent for " << var << d_partialModel.asNode(var); + if(d_tableau.isBasic(var)){ + warning() << " (basic)"; + } + warning() << std::endl; + } + } + } + return result; +} + +void TheoryArithPrivate::presolve(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_presolveTime); + + d_statistics.d_initialTableauSize = d_tableau.size(); + + if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } + + if(TraceIsOn("arith::presolve")) { + Trace("arith::presolve") << "TheoryArithPrivate::presolve" << endl; + } + + vector lemmas; + if (!options().base.incrementalSolving) + { + switch (options().arith.arithUnateLemmaMode) + { + case options::ArithUnateLemmaMode::NO: break; + case options::ArithUnateLemmaMode::INEQUALITY: + d_constraintDatabase.outputUnateInequalityLemmas(lemmas); + break; + case options::ArithUnateLemmaMode::EQUALITY: + d_constraintDatabase.outputUnateEqualityLemmas(lemmas); + break; + case options::ArithUnateLemmaMode::ALL: + d_constraintDatabase.outputUnateInequalityLemmas(lemmas); + d_constraintDatabase.outputUnateEqualityLemmas(lemmas); + break; + default: Unhandled() << options().arith.arithUnateLemmaMode; + } + } + + vector::const_iterator i = lemmas.begin(), i_end = lemmas.end(); + for(; i != i_end; ++i){ + TrustNode lem = *i; + Trace("arith::oldprop") << " lemma lemma duck " <getValue()); + Assert( + !upperBound + || d_partialModel.lessThanUpperBound(basic, bestImplied->getValue())); + + Assert(upperBound || bound >= bestImplied->getValue()); + Assert(upperBound + || d_partialModel.greaterThanLowerBound(basic, + bestImplied->getValue())); + //slightly changed + + // ConstraintP c = d_constraintDatabase.lookup(bestImplied); + // Assert(c != NullConstraint); + + bool assertedToTheTheory = bestImplied->assertedToTheTheory(); + bool canBePropagated = bestImplied->canBePropagated(); + bool hasProof = bestImplied->hasProof(); + + Trace("arith::prop") << "arith::prop" << basic + << " " << assertedToTheTheory + << " " << canBePropagated + << " " << hasProof + << endl; + + if(bestImplied->negationHasProof()){ + warning() << "the negation of " << bestImplied << " : " << std::endl + << "has proof " << bestImplied->getNegation() << std::endl + << bestImplied->getNegation()->externalExplainByAssertions() + << std::endl; + } + + if(!assertedToTheTheory && canBePropagated && !hasProof ){ + d_linEq.propagateBasicFromRow(bestImplied, options().smt.produceProofs); + // I think this can be skipped if canBePropagated is true + //d_learnedBounds.push(bestImplied); + if(TraceIsOn("arith::prop")){ + Trace("arith::prop") << "success " << bestImplied << endl; + d_partialModel.printModel(basic, Trace("arith::prop")); + } + return true; + } + if(TraceIsOn("arith::prop")){ + Trace("arith::prop") << "failed " << basic + << " " << bound + << " " << assertedToTheTheory + << " " << canBePropagated + << " " << hasProof << endl; + d_partialModel.printModel(basic, Trace("arith::prop")); + } + } + }else if(TraceIsOn("arith::prop")){ + Trace("arith::prop") << "false " << bound << " "; + d_partialModel.printModel(basic, Trace("arith::prop")); + } + return false; +} + +void TheoryArithPrivate::propagateCandidate(ArithVar basic){ + bool success = false; + RowIndex ridx = d_tableau.basicToRowIndex(basic); + + bool tryLowerBound = + d_partialModel.strictlyAboveLowerBound(basic) && + d_linEq.rowLacksBound(ridx, false, basic) == NULL; + + bool tryUpperBound = + d_partialModel.strictlyBelowUpperBound(basic) && + d_linEq.rowLacksBound(ridx, true, basic) == NULL; + + if(tryLowerBound){ + success |= propagateCandidateLowerBound(basic); + } + if(tryUpperBound){ + success |= propagateCandidateUpperBound(basic); + } + if(success){ + ++d_statistics.d_boundPropagations; + } +} + +void TheoryArithPrivate::propagateCandidates(){ + TimerStat::CodeTimer codeTimer(d_statistics.d_boundComputationTime); + + Trace("arith::prop") << "propagateCandidates begin" << endl; + + Assert(d_candidateBasics.empty()); + + if(d_updatedBounds.empty()){ return; } + + DenseSet::const_iterator i = d_updatedBounds.begin(); + DenseSet::const_iterator end = d_updatedBounds.end(); + for(; i != end; ++i){ + ArithVar var = *i; + if (d_tableau.isBasic(var) + && d_tableau.basicRowLength(var) + <= options().arith.arithPropagateMaxLength) + { + d_candidateBasics.softAdd(var); + } + else + { + Tableau::ColIterator basicIter = d_tableau.colIterator(var); + for(; !basicIter.atEnd(); ++basicIter){ + const Tableau::Entry& entry = *basicIter; + RowIndex ridx = entry.getRowIndex(); + ArithVar rowVar = d_tableau.rowIndexToBasic(ridx); + Assert(entry.getColVar() == var); + Assert(d_tableau.isBasic(rowVar)); + if (d_tableau.getRowLength(ridx) + <= options().arith.arithPropagateMaxLength) + { + d_candidateBasics.softAdd(rowVar); + } + } + } + } + d_updatedBounds.purge(); + + while(!d_candidateBasics.empty()){ + ArithVar candidate = d_candidateBasics.back(); + d_candidateBasics.pop_back(); + Assert(d_tableau.isBasic(candidate)); + propagateCandidate(candidate); + } + Trace("arith::prop") << "propagateCandidates end" << endl << endl << endl; +} + +void TheoryArithPrivate::propagateCandidatesNew(){ + /* Four criteria must be met for progagation on a variable to happen using a row: + * 0: A new bound has to have been added to the row. + * 1: The hasBoundsCount for the row must be "full" or be full minus one variable + * (This is O(1) to check, but requires book keeping.) + * 2: The current assignment must be strictly smaller/greater than the current bound. + * assign(x) < upper(x) + * (This is O(1) to compute.) + * 3: There is a bound that is strictly smaller/greater than the current assignment. + * assign(x) < c for some x <= c literal + * (This is O(log n) to compute.) + * 4: The implied bound on x is strictly smaller/greater than the current bound. + * (This is O(n) to compute.) + */ + + TimerStat::CodeTimer codeTimer(d_statistics.d_boundComputationTime); + Trace("arith::prop") << "propagateCandidatesNew begin" << endl; + + Assert(d_qflraStatus == Result::SAT); + if(d_updatedBounds.empty()){ return; } + dumpUpdatedBoundsToRows(); + Assert(d_updatedBounds.empty()); + + if(!d_candidateRows.empty()){ + UpdateTrackingCallback utcb(&d_linEq); + d_partialModel.processBoundsQueue(utcb); + } + + while(!d_candidateRows.empty()){ + RowIndex candidate = d_candidateRows.back(); + d_candidateRows.pop_back(); + propagateCandidateRow(candidate); + } + Trace("arith::prop") << "propagateCandidatesNew end" << endl << endl << endl; +} + +bool TheoryArithPrivate::propagateMightSucceed(ArithVar v, bool ub) const{ + int cmp = ub ? d_partialModel.cmpAssignmentUpperBound(v) + : d_partialModel.cmpAssignmentLowerBound(v); + bool hasSlack = ub ? cmp < 0 : cmp > 0; + if(hasSlack){ + ConstraintType t = ub ? UpperBound : LowerBound; + const DeltaRational& a = d_partialModel.getAssignment(v); + + if(isInteger(v) && !a.isIntegral()){ + return true; + } + + ConstraintP strongestPossible = d_constraintDatabase.getBestImpliedBound(v, t, a); + if(strongestPossible == NullConstraint){ + return false; + }else{ + bool assertedToTheTheory = strongestPossible->assertedToTheTheory(); + bool canBePropagated = strongestPossible->canBePropagated(); + bool hasProof = strongestPossible->hasProof(); + + return !assertedToTheTheory && canBePropagated && !hasProof; + } + }else{ + return false; + } +} + +bool TheoryArithPrivate::attemptSingleton(RowIndex ridx, bool rowUp){ + Trace("arith::prop") << " attemptSingleton" << ridx; + + const Tableau::Entry* ep; + ep = d_linEq.rowLacksBound(ridx, rowUp, ARITHVAR_SENTINEL); + Assert(ep != NULL); + + ArithVar v = ep->getColVar(); + const Rational& coeff = ep->getCoefficient(); + + // 0 = c * v + \sum rest + // Suppose rowUp + // - c * v = \sum rest \leq D + // if c > 0, v \geq -D/c so !vUp + // if c < 0, v \leq -D/c so vUp + // Suppose not rowUp + // - c * v = \sum rest \geq D + // if c > 0, v \leq -D/c so vUp + // if c < 0, v \geq -D/c so !vUp + bool vUp = (rowUp == ( coeff.sgn() < 0)); + + Trace("arith::prop") << " " << rowUp << " " << v << " " << coeff << " " << vUp << endl; + Trace("arith::prop") << " " << propagateMightSucceed(v, vUp) << endl; + + if(propagateMightSucceed(v, vUp)){ + DeltaRational dr = d_linEq.computeRowBound(ridx, rowUp, v); + DeltaRational bound = dr / (- coeff); + return tryToPropagate(ridx, rowUp, v, vUp, bound); + } + return false; +} + +bool TheoryArithPrivate::attemptFull(RowIndex ridx, bool rowUp){ + Trace("arith::prop") << " attemptFull" << ridx << endl; + + vector candidates; + + for(Tableau::RowIterator i = d_tableau.ridRowIterator(ridx); !i.atEnd(); ++i){ + const Tableau::Entry& e =*i; + const Rational& c = e.getCoefficient(); + ArithVar v = e.getColVar(); + bool vUp = (rowUp == (c.sgn() < 0)); + if(propagateMightSucceed(v, vUp)){ + candidates.push_back(&e); + } + } + if(candidates.empty()){ return false; } + + const DeltaRational slack = + d_linEq.computeRowBound(ridx, rowUp, ARITHVAR_SENTINEL); + bool any = false; + vector::const_iterator i, iend; + for(i = candidates.begin(), iend = candidates.end(); i != iend; ++i){ + const Tableau::Entry* ep = *i; + const Rational& c = ep->getCoefficient(); + ArithVar v = ep->getColVar(); + + // See the comment for attemptSingleton() + bool activeUp = (rowUp == (c.sgn() > 0)); + bool vUb = (rowUp == (c.sgn() < 0)); + + const DeltaRational& activeBound = activeUp ? + d_partialModel.getUpperBound(v): + d_partialModel.getLowerBound(v); + + DeltaRational contribution = activeBound * c; + DeltaRational impliedBound = (slack - contribution)/(-c); + + bool success = tryToPropagate(ridx, rowUp, v, vUb, impliedBound); + any |= success; + } + return any; +} + +bool TheoryArithPrivate::tryToPropagate(RowIndex ridx, bool rowUp, ArithVar v, bool vUb, const DeltaRational& bound){ + + bool weaker = vUb ? d_partialModel.strictlyLessThanUpperBound(v, bound): + d_partialModel.strictlyGreaterThanLowerBound(v, bound); + if(weaker){ + ConstraintType t = vUb ? UpperBound : LowerBound; + + ConstraintP implied = d_constraintDatabase.getBestImpliedBound(v, t, bound); + if(implied != NullConstraint){ + return rowImplicationCanBeApplied(ridx, rowUp, implied); + } + } + return false; +} + +Node flattenImplication(Node imp){ + NodeBuilder nb(kind::OR); + std::unordered_set included; + Node left = imp[0]; + Node right = imp[1]; + + if(left.getKind() == kind::AND){ + for(Node::iterator i = left.begin(), iend = left.end(); i != iend; ++i) { + if (!included.count((*i).negate())) + { + nb << (*i).negate(); + included.insert((*i).negate()); + } + } + }else{ + if (!included.count(left.negate())) + { + nb << left.negate(); + included.insert(left.negate()); + } + } + + if(right.getKind() == kind::OR){ + for(Node::iterator i = right.begin(), iend = right.end(); i != iend; ++i) { + if (!included.count(*i)) + { + nb << *i; + included.insert(*i); + } + } + }else{ + if (!included.count(right)) + { + nb << right; + included.insert(right); + } + } + + return nb; +} + +bool TheoryArithPrivate::rowImplicationCanBeApplied(RowIndex ridx, bool rowUp, ConstraintP implied){ + Assert(implied != NullConstraint); + ArithVar v = implied->getVariable(); + + bool assertedToTheTheory = implied->assertedToTheTheory(); + bool canBePropagated = implied->canBePropagated(); + bool hasProof = implied->hasProof(); + + Trace("arith::prop") << "arith::prop" << v + << " " << assertedToTheTheory + << " " << canBePropagated + << " " << hasProof + << endl; + + + if( !assertedToTheTheory && canBePropagated && !hasProof ){ + ConstraintCPVec explain; + if (options().smt.produceProofs) + { + d_farkasBuffer.clear(); + } + RationalVectorP coeffs = + options().smt.produceProofs ? &d_farkasBuffer : nullptr; + + // After invoking `propegateRow`: + // * coeffs[0] is for implied + // * coeffs[i+1] is for explain[i] + d_linEq.propagateRow(explain, ridx, rowUp, implied, coeffs); + if (d_tableau.getRowLength(ridx) <= options().arith.arithPropAsLemmaLength) + { + if (TraceIsOn("arith::prop::pf")) { + for (const auto & constraint : explain) { + Assert(constraint->hasProof()); + constraint->printProofTree(Trace("arith::prop::pf")); + } + } + Node implication = implied->externalImplication(explain); + Node clause = flattenImplication(implication); + std::shared_ptr clausePf{nullptr}; + + if (isProofEnabled()) + { + // We can prove this lemma from Farkas... + std::vector> conflictPfs; + Node pfLit = implied->getNegation()->getProofLiteral(); + TypeNode type = pfLit[0].getType(); + // Assume the negated getLiteral version of the implied constaint + // then rewrite it into proof normal form. + conflictPfs.push_back( + d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, + {d_pnm->mkAssume(implied->getLiteral().negate())}, + {pfLit})); + // Add the explaination proofs. + for (const auto constraint : explain) + { + NodeBuilder nb; + conflictPfs.push_back(constraint->externalExplainByAssertions(nb)); + } + // Collect the farkas coefficients, as nodes. + std::vector farkasCoefficients; + farkasCoefficients.reserve(coeffs->size()); + auto nm = NodeManager::currentNM(); + std::transform(coeffs->begin(), + coeffs->end(), + std::back_inserter(farkasCoefficients), + [nm, type](const Rational& r) { + return nm->mkConstRealOrInt(type, r); + }); + + // Prove bottom. + auto sumPf = d_pnm->mkNode( + PfRule::MACRO_ARITH_SCALE_SUM_UB, conflictPfs, farkasCoefficients); + auto botPf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); + + // Prove the conflict + std::vector assumptions; + assumptions.reserve(clause.getNumChildren()); + std::transform(clause.begin(), + clause.end(), + std::back_inserter(assumptions), + [](TNode r) { return r.negate(); }); + auto notAndNotPf = d_pnm->mkScope(botPf, assumptions); + + // Convert it to a clause + auto orNotNotPf = d_pnm->mkNode(PfRule::NOT_AND, {notAndNotPf}, {}); + clausePf = d_pnm->mkNode( + PfRule::MACRO_SR_PRED_TRANSFORM, {orNotNotPf}, {clause}); + + // Output it + TrustNode trustedClause = d_pfGen->mkTrustNode(clause, clausePf); + outputTrustedLemma(trustedClause, InferenceId::ARITH_ROW_IMPL); + } + else + { + outputLemma(clause, InferenceId::ARITH_ROW_IMPL); + } + } + else + { + Assert(!implied->negationHasProof()); + implied->impliedByFarkas(explain, coeffs, false); + implied->tryToPropagate(); + } + return true; + } + + if(TraceIsOn("arith::prop")){ + Trace("arith::prop") + << "failed " << v << " " << assertedToTheTheory << " " + << canBePropagated << " " << hasProof << " " << implied << endl; + d_partialModel.printModel(v, Trace("arith::prop")); + } + return false; +} + +bool TheoryArithPrivate::propagateCandidateRow(RowIndex ridx){ + BoundCounts hasCount = d_linEq.hasBoundCount(ridx); + uint32_t rowLength = d_tableau.getRowLength(ridx); + + bool success = false; + + Trace("arith::prop") << "propagateCandidateRow attempt " << rowLength << " " + << hasCount << endl; + + if (rowLength >= options().arith.arithPropagateMaxLength + && Random::getRandom().pickWithProb( + 1.0 - double(options().arith.arithPropagateMaxLength) / rowLength)) + { + return false; + } + + if(hasCount.lowerBoundCount() == rowLength){ + success |= attemptFull(ridx, false); + }else if(hasCount.lowerBoundCount() + 1 == rowLength){ + success |= attemptSingleton(ridx, false); + } + + if(hasCount.upperBoundCount() == rowLength){ + success |= attemptFull(ridx, true); + }else if(hasCount.upperBoundCount() + 1 == rowLength){ + success |= attemptSingleton(ridx, true); + } + return success; +} + +void TheoryArithPrivate::dumpUpdatedBoundsToRows(){ + Assert(d_candidateRows.empty()); + DenseSet::const_iterator i = d_updatedBounds.begin(); + DenseSet::const_iterator end = d_updatedBounds.end(); + for(; i != end; ++i){ + ArithVar var = *i; + if(d_tableau.isBasic(var)){ + RowIndex ridx = d_tableau.basicToRowIndex(var); + d_candidateRows.softAdd(ridx); + }else{ + Tableau::ColIterator basicIter = d_tableau.colIterator(var); + for(; !basicIter.atEnd(); ++basicIter){ + const Tableau::Entry& entry = *basicIter; + RowIndex ridx = entry.getRowIndex(); + d_candidateRows.softAdd(ridx); + } + } + } + d_updatedBounds.purge(); +} + +const BoundsInfo& TheoryArithPrivate::boundsInfo(ArithVar basic) const{ + RowIndex ridx = d_tableau.basicToRowIndex(basic); + return d_rowTracking[ridx]; +} + +std::pair TheoryArithPrivate::entailmentCheck(TNode lit) +{ + ArithEntailmentCheckParameters params; + params.addLookupRowSumAlgorithms(); + ArithEntailmentCheckSideEffects out; + + using namespace inferbounds; + + // l k r + // diff : (l - r) k 0 + Trace("arith::entailCheck") << "TheoryArithPrivate::entailmentCheck(" << lit << ")"<< endl; + Kind k; + int primDir; + Rational lm, rm, dm; + Node lp, rp, dp; + DeltaRational sep; + bool successful = decomposeLiteral(lit, k, primDir, lm, lp, rm, rp, dm, dp, sep); + if(!successful) { return make_pair(false, Node::null()); } + + if (dp.isConst()) + { + Node eval = rewrite(lit); + Assert(eval.getKind() == kind::CONST_BOOLEAN); + // if true, true is an acceptable explaination + // if false, the node is uninterpreted and eval can be forgotten + return make_pair(eval.getConst(), eval); + } + Assert(dm != Rational(0)); + Assert(primDir == 1 || primDir == -1); + + int negPrim = -primDir; + + int secDir = (k == EQUAL || k == DISTINCT) ? negPrim: 0; + int negSecDir = (k == EQUAL || k == DISTINCT) ? primDir: 0; + + // primDir*[lm*( lp )] k primDir*[ [rm*( rp )] + sep ] + // primDir*[lm*( lp ) - rm*( rp ) ] k primDir*sep + // primDir*[dm * dp] k primDir*sep + + std::pair bestPrimLeft, bestNegPrimRight, bestPrimDiff, tmp; + std::pair bestSecLeft, bestNegSecRight, bestSecDiff; + bestPrimLeft.first = Node::null(); bestNegPrimRight.first = Node::null(); bestPrimDiff.first = Node::null(); + bestSecLeft.first = Node::null(); bestNegSecRight.first = Node::null(); bestSecDiff.first = Node::null(); + + + + ArithEntailmentCheckParameters::const_iterator alg, alg_end; + for( alg = params.begin(), alg_end = params.end(); alg != alg_end; ++alg ){ + const inferbounds::InferBoundAlgorithm& ibalg = *alg; + + Trace("arith::entailCheck") << "entailmentCheck trying " << (inferbounds::Algorithms) ibalg.getAlgorithm() << endl; + switch(ibalg.getAlgorithm()){ + case inferbounds::None: + break; + case inferbounds::Lookup: + case inferbounds::RowSum: + { + typedef void (TheoryArithPrivate::*EntailmentCheckFunc)(std::pair&, int, TNode) const; + + EntailmentCheckFunc ecfunc = + (ibalg.getAlgorithm() == inferbounds::Lookup) + ? (&TheoryArithPrivate::entailmentCheckBoundLookup) + : (&TheoryArithPrivate::entailmentCheckRowSum); + + (*this.*ecfunc)(tmp, primDir * lm.sgn(), lp); + setToMin(primDir * lm.sgn(), bestPrimLeft, tmp); + + (*this.*ecfunc)(tmp, negPrim * rm.sgn(), rp); + setToMin(negPrim * rm.sgn(), bestNegPrimRight, tmp); + + (*this.*ecfunc)(tmp, secDir * lm.sgn(), lp); + setToMin(secDir * lm.sgn(), bestSecLeft, tmp); + + (*this.*ecfunc)(tmp, negSecDir * rm.sgn(), rp); + setToMin(negSecDir * rm.sgn(), bestNegSecRight, tmp); + + (*this.*ecfunc)(tmp, primDir * dm.sgn(), dp); + setToMin(primDir * dm.sgn(), bestPrimDiff, tmp); + + (*this.*ecfunc)(tmp, secDir * dm.sgn(), dp); + setToMin(secDir * dm.sgn(), bestSecDiff, tmp); + } + break; + default: + Unhandled(); + } + + // turn bounds on prim * left and -prim * right into bounds on prim * diff + if(!bestPrimLeft.first.isNull() && !bestNegPrimRight.first.isNull()){ + // primDir*lm* lp <= primDir*lm*L + // -primDir*rm* rp <= -primDir*rm*R + // primDir*lm* lp -primDir*rm* rp <= primDir*lm*L - primDir*rm*R + // primDir [lm* lp -rm* rp] <= primDir[lm*L - *rm*R] + // primDir [dm * dp] <= primDir[lm*L - *rm*R] + // primDir [dm * dp] <= primDir * dm * ([lm*L - *rm*R]/dm) + tmp.second = ((bestPrimLeft.second * lm) - (bestNegPrimRight.second * rm)) / dm; + tmp.first = (bestPrimLeft.first).andNode(bestNegPrimRight.first); + setToMin(primDir, bestPrimDiff, tmp); + } + + // turn bounds on sec * left and sec * right into bounds on sec * diff + if(secDir != 0 && !bestSecLeft.first.isNull() && !bestNegSecRight.first.isNull()){ + // secDir*lm* lp <= secDir*lm*L + // -secDir*rm* rp <= -secDir*rm*R + // secDir*lm* lp -secDir*rm* rp <= secDir*lm*L - secDir*rm*R + // secDir [lm* lp -rm* rp] <= secDir[lm*L - *rm*R] + // secDir [dm * dp] <= secDir[lm*L - *rm*R] + // secDir [dm * dp] <= secDir * dm * ([lm*L - *rm*R]/dm) + tmp.second = ((bestSecLeft.second * lm) - (bestNegSecRight.second * rm)) / dm; + tmp.first = (bestSecLeft.first).andNode(bestNegSecRight.first); + setToMin(secDir, bestSecDiff, tmp); + } + + switch(k){ + case LEQ: + if(!bestPrimDiff.first.isNull()){ + DeltaRational d = (bestPrimDiff.second * dm); + if((primDir > 0 && d <= sep) || (primDir < 0 && d >= sep) ){ + Trace("arith::entailCheck") << "entailmentCheck found " + << primDir << "*" << dm << "*(" << dp<<")" + << " <= " << primDir << "*" << dm << "*" << bestPrimDiff.second + << " <= " << primDir << "*" << sep << endl + << " by " << bestPrimDiff.first << endl; + Assert(bestPrimDiff.second * (Rational(primDir) * dm) + <= (sep * Rational(primDir))); + return make_pair(true, bestPrimDiff.first); + } + } + break; + case EQUAL: + if(!bestPrimDiff.first.isNull() && !bestSecDiff.first.isNull()){ + // Is primDir [dm * dp] == primDir * sep entailed? + // Iff [dm * dp] == sep entailed? + // Iff dp == sep / dm entailed? + // Iff dp <= sep / dm and dp >= sep / dm entailed? + + // primDir [dm * dp] <= primDir * dm * U + // secDir [dm * dp] <= secDir * dm * L + + // Suppose primDir * dm > 0 + // then secDir * dm < 0 + // dp >= (secDir * L) / secDir * dm + // dp >= (primDir * L) / primDir * dm + // + // dp <= U / dm + // dp >= L / dm + // dp == sep / dm entailed iff U == L == sep + // Suppose primDir * dm < 0 + // then secDir * dm > 0 + // dp >= U / dm + // dp <= L / dm + // dp == sep / dm entailed iff U == L == sep + if(bestPrimDiff.second == bestSecDiff.second){ + if(bestPrimDiff.second == sep){ + return make_pair(true, (bestPrimDiff.first).andNode(bestSecDiff.first)); + } + } + } + // intentionally fall through to DISTINCT case! + // entailments of negations are eager exit cases for EQUAL + CVC5_FALLTHROUGH; + case DISTINCT: + if(!bestPrimDiff.first.isNull()){ + // primDir [dm * dp] <= primDir * dm * U < primDir * sep + if((primDir > 0 && (bestPrimDiff.second * dm < sep)) || + (primDir < 0 && (bestPrimDiff.second * dm > sep))){ + // entailment of negation + if(k == DISTINCT){ + return make_pair(true, bestPrimDiff.first); + }else{ + Assert(k == EQUAL); + return make_pair(false, Node::null()); + } + } + } + if(!bestSecDiff.first.isNull()){ + // If primDir [dm * dp] > primDir * sep, then this is not entailed. + // If primDir [dm * dp] >= primDir * dm * L > primDir * sep + // -primDir * dm * L < -primDir * sep + // secDir * dm * L < secDir * sep + if((secDir > 0 && (bestSecDiff.second * dm < sep)) || + (secDir < 0 && (bestSecDiff.second * dm > sep))){ + if(k == DISTINCT){ + return make_pair(true, bestSecDiff.first); + }else{ + Assert(k == EQUAL); + return make_pair(false, Node::null()); + } + } + } + + break; + default: + Unreachable(); + break; + } + } + return make_pair(false, Node::null()); +} + +bool TheoryArithPrivate::decomposeTerm(Node t, + Rational& m, + Node& p, + Rational& c) +{ + if(!Polynomial::isMember(t)){ + return false; + } + + // TODO Speed up + preprocessing::util::ContainsTermITEVisitor ctv; + if(ctv.containsTermITE(t)){ + return false; + } + + Polynomial poly = Polynomial::parsePolynomial(t); + if(poly.isConstant()){ + c = poly.getHead().getConstant().getValue(); + p = mkRationalNode(Rational(0)); + m = Rational(1); + return true; + }else if(poly.containsConstant()){ + c = poly.getHead().getConstant().getValue(); + poly = poly.getTail(); + }else{ + c = Rational(0); + } + Assert(!poly.isConstant()); + Assert(!poly.containsConstant()); + + const bool intVars = poly.allIntegralVariables(); + + if(intVars){ + m = Rational(1); + if(!poly.isIntegral()){ + Integer denom = poly.denominatorLCM(); + m /= denom; + poly = poly * denom; + } + Integer g = poly.gcd(); + m *= g; + poly = poly * Rational(1,g); + Assert(poly.isIntegral()); + }else{ + Assert(!intVars); + m = poly.getHead().getConstant().getValue(); + poly = poly * m.inverse(); + Assert(poly.leadingCoefficientIsAbsOne()); + } + p = poly.getNode(); + return true; +} + +void TheoryArithPrivate::setToMin(int sgn, std::pair& min, const std::pair& e){ + if(sgn != 0){ + if(min.first.isNull() && !e.first.isNull()){ + min = e; + }else if(!min.first.isNull() && !e.first.isNull()){ + if(sgn > 0 && min.second > e.second){ + min = e; + }else if(sgn < 0 && min.second < e.second){ + min = e; + } + } + } +} + +// std::pair TheoryArithPrivate::entailmentUpperCheck(const Rational& lm, Node lp, const Rational& rm, Node rp, const DeltaRational& sep, const ArithEntailmentCheckParameters& params, ArithEntailmentCheckSideEffects& out){ + +// Rational negRM = -rm; +// Node diff = NodeManager::currentNM()->mkNode(MULT, mkRationalConstan(lm), lp) + (negRM * rp); + +// Rational diffm; +// Node diffp; +// decompose(diff, diffm, diffNode); + + +// std::pair bestUbLeft, bestLbRight, bestUbDiff, tmp; +// bestUbLeft = bestLbRight = bestUbDiff = make_pair(Node::Null(), DeltaRational()); + +// return make_pair(false, Node::null()); +// } + +/** + * Decomposes a literal into the form: + * dir*[lm*( lp )] k dir*[ [rm*( rp )] + sep ] + * dir*[dm* dp] k dir *sep + * dir is either 1 or -1 + */ +bool TheoryArithPrivate::decomposeLiteral(Node lit, Kind& k, int& dir, Rational& lm, Node& lp, Rational& rm, Node& rp, Rational& dm, Node& dp, DeltaRational& sep){ + bool negated = (lit.getKind() == kind::NOT); + TNode atom = negated ? lit[0] : lit; + + TNode left = atom[0]; + TNode right = atom[1]; + + // left : lm*( lp ) + lc + // right: rm*( rp ) + rc + Rational lc, rc; + bool success = decomposeTerm(rewrite(left), lm, lp, lc); + if(!success){ return false; } + success = decomposeTerm(rewrite(right), rm, rp, rc); + if(!success){ return false; } + + Node diff = rewrite(NodeManager::currentNM()->mkNode(kind::SUB, left, right)); + Rational dc; + success = decomposeTerm(diff, dm, dp, dc); + Assert(success); + + // reduce the kind of the to not include literals + // GT, NOT LEQ + // GEQ, NOT LT + // LT, NOT GEQ + // LEQ, NOT LT + Kind atomKind = atom.getKind(); + Kind normKind = negated ? negateKind(atomKind) : atomKind; + + if(normKind == GEQ || normKind == GT){ + dir = -1; + normKind = (normKind == GEQ) ? LEQ : LT; + }else{ + dir = 1; + } + + Trace("arith::decomp") << "arith::decomp " + << lit << "(" << normKind << "*" << dir << ")"<< endl + << " left:" << lc << " + " << lm << "*(" << lp << ") : " <& tmp, int sgn, TNode tp) const { + tmp.first = Node::null(); + if(sgn == 0){ return; } + + Assert(Polynomial::isMember(tp)); + if (tp.isConst()) + { + tmp.first = mkBoolNode(true); + tmp.second = DeltaRational(tp.getConst()); + } + else if (d_partialModel.hasArithVar(tp)) + { + Assert(!tp.isConst()); + ArithVar v = d_partialModel.asArithVar(tp); + Assert(v != ARITHVAR_SENTINEL); + ConstraintP c = (sgn > 0) + ? d_partialModel.getUpperBoundConstraint(v) + : d_partialModel.getLowerBoundConstraint(v); + if(c != NullConstraint){ + tmp.first = Constraint::externalExplainByAssertions({c}); + tmp.second = c->getValue(); + } + } +} + +void TheoryArithPrivate::entailmentCheckRowSum(std::pair& tmp, int sgn, TNode tp) const { + tmp.first = Node::null(); + if(sgn == 0){ return; } + if (tp.getKind() != ADD) + { + return; + } + Assert(Polynomial::isMember(tp)); + + tmp.second = DeltaRational(0); + NodeBuilder nb(kind::AND); + + Polynomial p = Polynomial::parsePolynomial(tp); + for(Polynomial::iterator i = p.begin(), iend = p.end(); i != iend; ++i) { + Monomial m = *i; + Node x = m.getVarList().getNode(); + if(d_partialModel.hasArithVar(x)){ + ArithVar v = d_partialModel.asArithVar(x); + const Rational& coeff = m.getConstant().getValue(); + int dir = sgn * coeff.sgn(); + ConstraintP c = (dir > 0) + ? d_partialModel.getUpperBoundConstraint(v) + : d_partialModel.getLowerBoundConstraint(v); + if(c != NullConstraint){ + tmp.second += c->getValue() * coeff; + c->externalExplainByAssertions(nb); + }else{ + //failed + return; + } + }else{ + // failed + return; + } + } + // success + tmp.first = nb; +} + +ArithProofRuleChecker* TheoryArithPrivate::getProofChecker() +{ + return &d_checker; +} + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear/theory_arith_private.h b/src/theory/arith/linear/theory_arith_private.h new file mode 100644 index 000000000..d8a361315 --- /dev/null +++ b/src/theory/arith/linear/theory_arith_private.h @@ -0,0 +1,881 @@ +/****************************************************************************** + * Top contributors (to current version): + * Tim King, Andrew Reynolds, Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2022 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. + * **************************************************************************** + * + * [[ Add one-line brief description here ]] + * + * [[ Add lengthier description here ]] + * \todo document this file + */ + +#pragma once + +#include +#include + +#include "context/cdhashset.h" +#include "context/cdinsert_hashmap.h" +#include "context/cdlist.h" +#include "context/cdqueue.h" +#include "expr/kind.h" +#include "expr/node.h" +#include "expr/node_builder.h" +#include "proof/trust_node.h" +#include "theory/arith/linear/arith_static_learner.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/linear/arithvar.h" +#include "theory/arith/linear/attempt_solution_simplex.h" +#include "theory/arith/branch_and_bound.h" +#include "theory/arith/linear/congruence_manager.h" +#include "theory/arith/linear/constraint.h" +#include "theory/arith/delta_rational.h" +#include "theory/arith/linear/dio_solver.h" +#include "theory/arith/linear/dual_simplex.h" +#include "theory/arith/linear/error_set.h" +#include "theory/arith/linear/fc_simplex.h" +#include "theory/arith/linear/infer_bounds.h" +#include "theory/arith/linear/linear_equality.h" +#include "theory/arith/linear/matrix.h" +#include "theory/arith/linear/normal_form.h" +#include "theory/arith/linear/partial_model.h" +#include "theory/arith/proof_checker.h" +#include "theory/arith/linear/soi_simplex.h" +#include "theory/arith/theory_arith.h" +#include "theory/valuation.h" +#include "util/dense_map.h" +#include "util/integer.h" +#include "util/rational.h" +#include "util/result.h" +#include "util/statistics_stats.h" + +namespace cvc5::internal { + +class EagerProofGenerator; + +namespace theory { + +class TheoryModel; + +namespace arith::linear { + +class BranchCutInfo; +class TreeLog; +class ApproximateStatistics; + +class ArithEntailmentCheckParameters; +class ArithEntailmentCheckSideEffects; +namespace inferbounds { + class InferBoundAlgorithm; +} +class InferBoundsResult; + +/** + * Implementation of QF_LRA. + * Based upon: + * http://research.microsoft.com/en-us/um/people/leonardo/cav06.pdf + */ +class TheoryArithPrivate : protected EnvObj +{ + private: + static constexpr uint32_t RESET_START = 2; + + TheoryArith& d_containing; + + /** + * Whether we encountered non-linear arithmetic at any time during solving. + */ + bool d_foundNl; + + BoundInfoMap d_rowTracking; + /** Branch and bound utility */ + BranchAndBound& d_bab; + // For proofs + /** Manages the proof nodes of this theory. */ + ProofNodeManager* d_pnm; + /** Checks the proof rules of this theory. */ + ArithProofRuleChecker d_checker; + /** Stores proposition(node)/proof pairs. */ + std::unique_ptr d_pfGen; + + /** + * The constraint database associated with the theory. + * This must be declared before ArithPartialModel. + */ + ConstraintDatabase d_constraintDatabase; + + enum Result::Status d_qflraStatus; + // check() + // !done() -> d_qflraStatus = Unknown + // fullEffort(e) -> simplex returns either sat or unsat + // !fullEffort(e) -> simplex returns either sat, unsat or unknown + // if unknown, save the assignment + // if unknown, the simplex priority queue cannot be emptied + int d_unknownsInARow; + + bool d_replayedLemmas; + + /** + * This counter is false if nothing has been done since the last cut. + * This is used to break an infinite loop. + */ + bool d_hasDoneWorkSinceCut; + + /** Static learner. */ + ArithStaticLearner d_learner; + + //std::vector d_pool; +public: + void releaseArithVar(ArithVar v); + void signal(ArithVar v){ d_errorSet.signalVariable(v); } + + +private: + // t does not contain constants + void entailmentCheckBoundLookup(std::pair& tmp, int sgn, TNode tp) const; + void entailmentCheckRowSum(std::pair& tmp, int sgn, TNode tp) const; + + /** + * Infers either a new upper/lower bound on term in the real relaxation. + * Either: + * - term is malformed (see below) + * - a maximum/minimum is found with the result being a pair + * -- where + * -- term dr is implies by exp + * -- is <= if inferring an upper bound, >= otherwise + * -- exp is in terms of the assertions to the theory. + * - No upper or lower bound is inferrable in the real relaxation. + * -- Returns <0, Null()> + * - the maximum number of rounds was exhausted: + * -- Returns where v is the current feasible value of term + * - Threshold reached: + * -- If theshold != NULL, and a feasible value is found to exceed threshold + * -- Simplex stops and returns + */ + //std::pair inferBound(TNode term, bool lb, int maxRounds = -1, const DeltaRational* threshold = NULL); + +private: + static bool decomposeTerm(Node t, Rational& m, Node& p, Rational& c); + bool decomposeLiteral(Node lit, + Kind& k, + int& dir, + Rational& lm, + Node& lp, + Rational& rm, + Node& rp, + Rational& dm, + Node& dp, + DeltaRational& sep); + static void setToMin(int sgn, + std::pair& min, + const std::pair& e); + + typedef ArithVariables::var_iterator var_iterator; + var_iterator var_begin() const { return d_partialModel.var_begin(); } + var_iterator var_end() const { return d_partialModel.var_end(); } + + NodeSet d_setupNodes; +public: + bool isSetup(Node n) const { + return d_setupNodes.find(n) != d_setupNodes.end(); + } + void markSetup(Node n){ + Assert(!isSetup(n)); + d_setupNodes.insert(n); + } +private: + void setupVariable(const Variable& x); + void setupVariableList(const VarList& vl); + void setupPolynomial(const Polynomial& poly); +public: + void setupAtom(TNode atom); +private: + void cautiousSetupPolynomial(const Polynomial& p); + + /** + * A superset of all of the assertions that currently are not the literal for + * their constraint do not match constraint literals. Not just the witnesses. + */ + context::CDInsertHashMap + d_assertionsThatDoNotMatchTheirLiterals; + + /** Returns true if x is of type Integer. */ + inline bool isInteger(ArithVar x) const { + return d_partialModel.isInteger(x); + } + + + /** Returns true if the variable was initially introduced as an auxiliary variable. */ + inline bool isAuxiliaryVariable(ArithVar x) const{ + return d_partialModel.isAuxiliary(x); + } + + inline bool isIntegerInput(ArithVar x) const + { + return d_partialModel.isIntegerInput(x) + && d_preregisteredNodes.contains(d_partialModel.asNode(x)); + } + + /** + * On full effort checks (after determining LA(Q) satisfiability), we + * consider integer vars, but we make sure to do so fairly to avoid + * nontermination (although this isn't a guarantee). To do it fairly, + * we consider variables in round-robin fashion. This is the + * round-robin index. + */ + ArithVar d_nextIntegerCheckVar; + + /** + * Queue of Integer variables that are known to be equal to a constant. + */ + context::CDQueue d_constantIntegerVariables; + + Node callDioSolver(); + /** + * Produces lemmas of the form (or (>= f 0) (<= f 0)), + * where f is a plane that the diophantine solver is interested in. + * + * More precisely, produces lemmas of the form (or (>= lc -c) (<= lc -c)) + * where lc is a linear combination of variables, c is a constant, and lc + c + * is the plane. + */ + TrustNode dioCutting(); + + Comparison mkIntegerEqualityFromAssignment(ArithVar v); + + /** + * List of all of the disequalities asserted in the current context that are not known + * to be satisfied. + */ + context::CDQueue d_diseqQueue; + + /** + * Constraints that have yet to be processed by proagation work list. + * All of the elements have type of LowerBound, UpperBound, or + * Equality. + * + * This is empty at the beginning of every check call. + * + * If head()->getType() == LowerBound or UpperBound, + * then d_cPL[1] is the previous constraint in d_partialModel for the + * corresponding bound. + * If head()->getType() == Equality, + * then d_cPL[1] is the previous lowerBound in d_partialModel, + * and d_cPL[2] is the previous upperBound in d_partialModel. + */ + std::deque d_currentPropagationList; + + context::CDQueue d_learnedBounds; + + /** + * Contains all nodes that have been preregistered + */ + context::CDHashSet d_preregisteredNodes; + + /** + * Manages information about the assignment and upper and lower bounds on + * variables. + */ + ArithVariables d_partialModel; + + /** The set of variables in error in the partial model. */ + ErrorSet d_errorSet; + + /** + * The tableau for all of the constraints seen thus far in the system. + */ + Tableau d_tableau; + + /** + * Maintains the relationship between the PartialModel and the Tableau. + */ + LinearEqualityModule d_linEq; + + /** + * A Diophantine equation solver. Accesses the tableau and partial + * model (each in a read-only fashion). + */ + DioSolver d_diosolver; + + /** Counts the number of notifyRestart() calls to the theory. */ + uint32_t d_restartsCounter; + + /** + * Every number of restarts equal to s_TABLEAU_RESET_PERIOD, + * the density of the tableau, d, is computed. + * If d >= s_TABLEAU_RESET_DENSITY * d_initialDensity, the tableau + * is set to d_initialTableau. + */ + bool d_tableauSizeHasBeenModified; + double d_tableauResetDensity; + uint32_t d_tableauResetPeriod; + static constexpr uint32_t s_TABLEAU_RESET_INCREMENT = 5; + + /** This is only used by simplex at the moment. */ + context::CDList> d_conflicts; + + /** This is only used by simplex at the moment. */ + context::CDO d_blackBoxConflict; + /** For holding the proof of the above conflict node. */ + context::CDO> d_blackBoxConflictPf; + + bool isProofEnabled() const; + + public: + /** + * This adds the constraint a to the queue of conflicts in d_conflicts. + * Both a and ~a must have a proof. + */ + void raiseConflict(ConstraintCP a, InferenceId id); + + // inline void raiseConflict(const ConstraintCPVec& cv){ + // d_conflicts.push_back(cv); + // } + + // void raiseConflict(ConstraintCP a, ConstraintCP b); + // void raiseConflict(ConstraintCP a, ConstraintCP b, ConstraintCP c); + + /** This is a conflict that is magically known to hold. */ + void raiseBlackBoxConflict(Node bb, std::shared_ptr pf = nullptr); + /** + * Returns true iff a conflict has been raised. This method is public since + * it is needed by the ArithState class to know whether we are in conflict. + */ + bool anyConflict() const; + + private: + inline bool conflictQueueEmpty() const { + return d_conflicts.empty(); + } + + /** + * Outputs the contents of d_conflicts onto d_out. + * The conditions of anyConflict() must hold. + */ + void outputConflicts(); + + /** + * A copy of the tableau. + * This is equivalent to the original tableau if d_tableauSizeHasBeenModified + * is false. + * The set of basic and non-basic variables may differ from d_tableau. + */ + Tableau d_smallTableauCopy; + + /** + * Returns true if all of the basic variables in the simplex queue of + * basic variables that violate their bounds in the current tableau + * are basic in d_smallTableauCopy. + * + * d_tableauSizeHasBeenModified must be false when calling this. + * Simplex's priority queue must be in collection mode. + */ + bool safeToReset() const; + + /** This keeps track of difference equalities. Mostly for sharing. */ + ArithCongruenceManager d_congruenceManager; + context::CDO d_cmEnabled; + + /** This implements the Simplex decision procedure. */ + DualSimplexDecisionProcedure d_dualSimplex; + FCSimplexDecisionProcedure d_fcSimplex; + SumOfInfeasibilitiesSPD d_soiSimplex; + AttemptSolutionSDP d_attemptSolSimplex; + + bool solveRealRelaxation(Theory::Effort effortLevel); + + /* Returns true if this is heuristically a good time to try + * to solve the integers. + */ + bool attemptSolveInteger(Theory::Effort effortLevel, bool emmmittedLemmaOrSplit); + bool replayLemmas(ApproximateSimplex* approx); + void solveInteger(Theory::Effort effortLevel); + bool safeToCallApprox() const; + SimplexDecisionProcedure& selectSimplex(bool pass1); + SimplexDecisionProcedure* d_pass1SDP; + SimplexDecisionProcedure* d_otherSDP; + /* Sets d_qflraStatus */ + void importSolution(const ApproximateSimplex::Solution& solution); + bool solveRelaxationOrPanic(Theory::Effort effortLevel); + context::CDO d_lastContextIntegerAttempted; + bool replayLog(ApproximateSimplex* approx); + + class ModelException : public Exception { + public: + ModelException(TNode n, const char* msg); + ~ModelException() override; + }; + + /** + * Computes the delta rational value of a term from the current partial + * model. This returns the delta value assignment to the term if it is in the + * partial model. Otherwise, this is computed recursively for arithmetic terms + * from each subterm. + * + * This throws a DeltaRationalException if the value cannot be represented as + * a DeltaRational. This throws a ModelException if there is a term is not in + * the partial model and is not a theory of arithmetic term. + * + * precondition: The linear abstraction of the nodes must be satisfiable. + */ + DeltaRational getDeltaValue(TNode term) const + /* throw(DeltaRationalException, ModelException) */; + public: + TheoryArithPrivate(TheoryArith& containing, Env& env, BranchAndBound& bab); + ~TheoryArithPrivate(); + + //--------------------------------- initialization + /** + * Returns true if we need an equality engine, see + * Theory::needsEqualityEngine. + */ + bool needsEqualityEngine(EeSetupInfo& esi); + /** finish initialize */ + void finishInit(); + //--------------------------------- end initialization + + /** + * Does non-context dependent setup for a node connected to a theory. + */ + void preRegisterTerm(TNode n); + + void propagate(Theory::Effort e); + TrustNode explain(TNode n); + + Rational deltaValueForTotalOrder() const; + + bool collectModelInfo(TheoryModel* m); + /** + * Collect model values. This is the main method for extracting information + * about how to construct the model. This method relies on the caller for + * processing the map, which is done so that other modules (e.g. the + * non-linear extension) can modify arithModel before it is sent to the model. + * + * @param termSet The set of relevant terms + * @param arithModel Mapping from terms (of real type) to their values. The + * caller should assert equalities to the model for each entry in this map. + */ + void collectModelValues(const std::set& termSet, + std::map& arithModel); + + void shutdown(){ } + + void presolve(); + void notifyRestart(); + Theory::PPAssertStatus ppAssert(TrustNode tin, + TrustSubstitutionMap& outSubstitutions); + void ppStaticLearn(TNode in, NodeBuilder& learned); + + std::string identify() const { return std::string("TheoryArith"); } + + EqualityStatus getEqualityStatus(TNode a, TNode b); + + /** Called when n is notified as being a shared term with TheoryArith. */ + void notifySharedTerm(TNode n); + + Node getModelValue(TNode var); + + + std::pair entailmentCheck(TNode lit); + + //--------------------------------- standard check + /** Pre-check, called before the fact queue of the theory is processed. */ + bool preCheck(Theory::Effort level); + /** Pre-notify fact. */ + void preNotifyFact(TNode atom, bool pol, TNode fact); + /** + * Post-check, called after the fact queue of the theory is processed. Returns + * true if a conflict or lemma was emitted. + */ + bool postCheck(Theory::Effort level); + //--------------------------------- end standard check + /** + * Found non-linear? This returns true if this solver ever encountered + * any non-linear terms that were unhandled. Note that this class is not + * responsible for handling non-linear arithmetic. If the owner of this + * class does not handle non-linear arithmetic in another way, then + * setIncomplete should be called on the output channel of TheoryArith. + */ + bool foundNonlinear() const; + + /** get the proof checker of this theory */ + ArithProofRuleChecker* getProofChecker(); + + private: + /** The constant zero. */ + DeltaRational d_DELTA_ZERO; + + /** propagates an arithvar */ + void propagateArithVar(bool upperbound, ArithVar var ); + + /** + * Using the simpleKind return the ArithVar associated with the assertion. + */ + ArithVar determineArithVar(const Polynomial& p) const; + ArithVar determineArithVar(TNode assertion) const; + + /** + * Splits the disequalities in d_diseq that are violated using lemmas on demand. + * returns true if any lemmas were issued. + * returns false if all disequalities are satisfied in the current model. + */ + bool splitDisequalities(); + + /** A Difference variable is known to be 0.*/ + void zeroDifferenceDetected(ArithVar x); + + + /** + * Looks for the next integer variable without an integer assignment in a + * round-robin fashion. Changes the value of d_nextIntegerCheckVar. + * + * This returns true if all integer variables have integer assignments. + * If this returns false, d_nextIntegerCheckVar does not have an integer + * assignment. + */ + bool hasIntegerModel(); + + /** + * Looks for through the variables starting at d_nextIntegerCheckVar + * for the first integer variable that is between its upper and lower bounds + * that has a non-integer assignment. + * + * If assumeBounds is true, skip the check that the variable is in bounds. + * + * If there is no such variable, returns ARITHVAR_SENTINEL; + */ + ArithVar nextIntegerViolation(bool assumeBounds) const; + + /** + * Issues branches for non-auxiliary integer variables with non-integer assignments. + * Returns a cut for a lemma. + * If there is an integer model, this returns Node::null(). + */ + TrustNode roundRobinBranch(); + + bool proofsEnabled() const { return d_pnm; } + + public: + /** + * This requests a new unique ArithVar value for x. + * This also does initial (not context dependent) set up for a variable, + * except for setting up the initial. + * + * If aux is true, this is an auxiliary variable. + * If internal is true, x might not be unique up to a constant multiple. + */ + ArithVar requestArithVar(TNode x, bool aux, bool internal); + +public: + const BoundsInfo& boundsInfo(ArithVar basic) const; + + +private: + /** Initial (not context dependent) sets up for a variable.*/ + void setupBasicValue(ArithVar x); + + /** Initial (not context dependent) sets up for a new auxiliary variable.*/ + void setupAuxiliary(TNode left); + + + /** + * Assert*(n, orig) takes an bound n that is implied by orig. + * and asserts that as a new bound if it is tighter than the current bound + * and updates the value of a basic variable if needed. + * + * orig must be a literal in the SAT solver so that it can be used for + * conflict analysis. + * + * x is the variable getting the new bound, + * c is the value of the new bound. + * + * If this new bound is in conflict with the other bound, + * a node describing this conflict is returned. + * If this new bound is not in conflict, Node::null() is returned. + */ + bool AssertLower(ConstraintP constraint); + bool AssertUpper(ConstraintP constraint); + bool AssertEquality(ConstraintP constraint); + bool AssertDisequality(ConstraintP constraint); + + /** Tracks the bounds that were updated in the current round. */ + DenseSet d_updatedBounds; + + /** Tracks the basic variables where propagation might be possible. */ + DenseSet d_candidateBasics; + DenseSet d_candidateRows; + + bool hasAnyUpdates() { return !d_updatedBounds.empty(); } + void clearUpdates(); + + void revertOutOfConflict(); + + void propagateCandidatesNew(); + void dumpUpdatedBoundsToRows(); + bool propagateCandidateRow(RowIndex rid); + bool propagateMightSucceed(ArithVar v, bool ub) const; + /** Attempt to perform a row propagation where there is at most 1 possible variable.*/ + bool attemptSingleton(RowIndex ridx, bool rowUp); + /** Attempt to perform a row propagation where every variable is a potential candidate.*/ + bool attemptFull(RowIndex ridx, bool rowUp); + bool tryToPropagate(RowIndex ridx, bool rowUp, ArithVar v, bool vUp, const DeltaRational& bound); + bool rowImplicationCanBeApplied(RowIndex ridx, bool rowUp, ConstraintP bestImplied); + //void enqueueConstraints(std::vector& out, Node n) const; + //ConstraintCPVec resolveOutPropagated(const ConstraintCPVec& v, const std::set& propagated) const; + void resolveOutPropagated(std::vector& confs, const std::set& propagated) const; + void subsumption(std::vector& confs) const; + + Node cutToLiteral(ApproximateSimplex* approx, const CutInfo& cut) const; + Node branchToNode(ApproximateSimplex* approx, const NodeLog& cut) const; + + void propagateCandidates(); + void propagateCandidate(ArithVar basic); + bool propagateCandidateBound(ArithVar basic, bool upperBound); + + inline bool propagateCandidateLowerBound(ArithVar basic){ + return propagateCandidateBound(basic, false); + } + inline bool propagateCandidateUpperBound(ArithVar basic){ + return propagateCandidateBound(basic, true); + } + + /** + * Performs a check to see if it is definitely true that setup can be avoided. + */ + bool canSafelyAvoidEqualitySetup(TNode equality); + + /** + * Handles the case splitting for check() for a new assertion. + * Returns a conflict if one was found. + * Returns Node::null if no conflict was found. + * + * @param assertion The assertion that was just popped from the fact queue + * of TheoryArith and given to this class via preNotifyFact. + */ + ConstraintP constraintFromFactQueue(TNode assertion); + bool assertionCases(ConstraintP c); + + /** + * Returns the basic variable with the shorted row containing a non-basic variable. + * If no such row exists, return ARITHVAR_SENTINEL. + */ + ArithVar findShortestBasicRow(ArithVar variable); + + /** + * Debugging only routine! + * Returns true iff every variable is consistent in the partial model. + */ + bool entireStateIsConsistent(const std::string& locationHint); + bool unenqueuedVariablesAreConsistent(); + + bool isImpliedUpperBound(ArithVar var, Node exp); + bool isImpliedLowerBound(ArithVar var, Node exp); + + void internalExplain(TNode n, NodeBuilder& explainBuilder); + + void asVectors(const Polynomial& p, + std::vector& coeffs, + std::vector& variables); + + /** Routine for debugging. Print the assertions the theory is aware of. */ + void debugPrintAssertions(std::ostream& out) const; + /** Debugging only routine. Prints the model. */ + void debugPrintModel(std::ostream& out) const; + + bool done() const { return d_containing.done(); } + bool isLeaf(TNode x) const { return d_containing.isLeaf(x); } + TheoryId theoryOf(TNode x) const { return d_containing.theoryOf(x); } + void debugPrintFacts() const { d_containing.debugPrintFacts(); } + bool outputTrustedLemma(TrustNode lem, InferenceId id); + bool outputLemma(TNode lem, InferenceId id); + void outputTrustedConflict(TrustNode conf, InferenceId id); + void outputConflict(TNode lit, InferenceId id); + void outputPropagate(TNode lit); + void outputRestart(); + + inline bool isSatLiteral(TNode l) const { + return (d_containing.d_valuation).isSatLiteral(l); + } + inline Node getSatValue(TNode n) const { + return (d_containing.d_valuation).getSatValue(n); + } + + /** Used for replaying approximate simplex */ + context::CDQueue d_approxCuts; + /** Also used for replaying approximate simplex. "approximate cuts temporary storage" */ + std::vector d_acTmp; + + /** Counts the number of fullCheck calls to arithmetic. */ + uint32_t d_fullCheckCounter; + std::vector cutAllBounded() const; + TrustNode branchIntegerVariable(ArithVar x) const; + void branchVector(const std::vector& lemmas); + + context::CDO d_cutCount; + context::CDHashSet> d_cutInContext; + + context::CDO d_likelyIntegerInfeasible; + + context::CDO d_guessedCoeffSet; + ArithRatPairVec d_guessedCoeffs; + + + TreeLog* d_treeLog; + TreeLog& getTreeLog(); + + + ArithVarVec d_replayVariables; + std::vector d_replayConstraints; + DenseMap d_lhsTmp; + + /* Approximate simpplex solvers are given a copy of their stats */ + ApproximateStatistics* d_approxStats; + ApproximateStatistics& getApproxStats(); + context::CDO d_attemptSolveIntTurnedOff; + void turnOffApproxFor(int32_t rounds); + bool getSolveIntegerResource(); + + void tryBranchCut(ApproximateSimplex* approx, int nid, BranchCutInfo& bl); + std::vector replayLogRec(ApproximateSimplex* approx, int nid, ConstraintP bc, int depth); + + std::pair replayGetConstraint(const CutInfo& info); + std::pair replayGetConstraint( + ApproximateSimplex* approx, const NodeLog& nl); + std::pair replayGetConstraint(const DenseMap& lhs, Kind k, const Rational& rhs, bool branch); + + void replayAssert(ConstraintP c); + + static ConstraintCP vectorToIntHoleConflict(const ConstraintCPVec& conflict); + static void intHoleConflictToVector(ConstraintCP conflicting, ConstraintCPVec& conflict); + + // Returns true if the node contains a literal + // that is an arithmetic literal and is not a sat literal + // No caching is done so this should likely only + // be called carefully! + bool hasFreshArithLiteral(Node n) const; + + int32_t d_dioSolveResources; + bool getDioCuttingResource(); + + uint32_t d_solveIntMaybeHelp, d_solveIntAttempts; + + RationalVector d_farkasBuffer; + + //---------------- during check + /** Whether there were new facts during preCheck */ + bool d_newFacts; + /** The previous status, computed during preCheck */ + Result::Status d_previousStatus; + //---------------- end during check + + /** These fields are designed to be accessible to TheoryArith methods. */ + class Statistics { + public: + IntStat d_statAssertUpperConflicts, d_statAssertLowerConflicts; + + IntStat d_statUserVariables, d_statAuxiliaryVariables; + IntStat d_statDisequalitySplits; + IntStat d_statDisequalityConflicts; + TimerStat d_simplifyTimer; + TimerStat d_staticLearningTimer; + + TimerStat d_presolveTime; + + TimerStat d_newPropTime; + + IntStat d_externalBranchAndBounds; + + IntStat d_initialTableauSize; + IntStat d_currSetToSmaller; + IntStat d_smallerSetToCurr; + TimerStat d_restartTimer; + + TimerStat d_boundComputationTime; + IntStat d_boundComputations, d_boundPropagations; + + IntStat d_unknownChecks; + IntStat d_maxUnknownsInARow; + AverageStat d_avgUnknownsInARow; + + IntStat d_revertsOnConflicts; + IntStat d_commitsOnConflicts; + IntStat d_nontrivialSatChecks; + + IntStat d_replayLogRecCount, + d_replayLogRecConflictEscalation, + d_replayLogRecEarlyExit, + d_replayBranchCloseFailures, + d_replayLeafCloseFailures, + d_replayBranchSkips, + d_mirCutsAttempted, + d_gmiCutsAttempted, + d_branchCutsAttempted, + d_cutsReconstructed, + d_cutsReconstructionFailed, + d_cutsProven, + d_cutsProofFailed, + d_mipReplayLemmaCalls, + d_mipExternalCuts, + d_mipExternalBranch; + + IntStat d_inSolveInteger, + d_branchesExhausted, + d_execExhausted, + d_pivotsExhausted, + d_panicBranches, + d_relaxCalls, + d_relaxLinFeas, + d_relaxLinFeasFailures, + d_relaxLinInfeas, + d_relaxLinInfeasFailures, + d_relaxLinExhausted, + d_relaxOthers; + + IntStat d_applyRowsDeleted; + TimerStat d_replaySimplexTimer; + + TimerStat d_replayLogTimer, + d_solveIntTimer, + d_solveRealRelaxTimer; + + IntStat d_solveIntCalls, + d_solveStandardEffort; + + IntStat d_approxDisabled; + IntStat d_replayAttemptFailed; + + IntStat d_cutsRejectedDuringReplay; + IntStat d_cutsRejectedDuringLemmas; + + HistogramStat d_satPivots; + HistogramStat d_unsatPivots; + HistogramStat d_unknownPivots; + + IntStat d_solveIntModelsAttempts; + IntStat d_solveIntModelsSuccessful; + TimerStat d_mipTimer; + TimerStat d_lpTimer; + + IntStat d_mipProofsAttempted; + IntStat d_mipProofsSuccessful; + + IntStat d_numBranchesFailed; + + Statistics(StatisticsRegistry& reg, const std::string& name); + }; + + + Statistics d_statistics; +}; /* class TheoryArithPrivate */ + + +} // namespace arith +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/arith/linear_equality.cpp b/src/theory/arith/linear_equality.cpp deleted file mode 100644 index 337f24491..000000000 --- a/src/theory/arith/linear_equality.cpp +++ /dev/null @@ -1,1375 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andres Noetzli - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This implements the LinearEqualityModule. - */ -#include "theory/arith/linear_equality.h" - -#include "base/output.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" - - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -/* Explicitly instatiate these functions. */ - -template ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; -template ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; - -template bool LinearEqualityModule::preferWitness(const UpdateInfo& a, const UpdateInfo& b) const; -template bool LinearEqualityModule::preferWitness(const UpdateInfo& a, const UpdateInfo& b) const; - - -void Border::output(std::ostream& out) const{ - out << "{Border" - << ", " << d_bound->getVariable() - << ", " << d_bound->getValue() - << ", " << d_diff - << ", " << d_areFixing - << ", " << d_upperbound; - if(ownBorder()){ - out << ", ownBorder"; - }else{ - out << ", " << d_entry->getCoefficient(); - } - out << ", " << d_bound - << "}"; -} - -LinearEqualityModule::LinearEqualityModule(ArithVariables& vars, Tableau& t, BoundInfoMap& boundsTracking, BasicVarModelUpdateCallBack f): - d_variables(vars), - d_tableau(t), - d_basicVariableUpdates(f), - d_increasing(1), - d_decreasing(-1), - d_upperBoundDifference(), - d_lowerBoundDifference(), - d_one(1), - d_negOne(-1), - d_btracking(boundsTracking), - d_areTracking(false), - d_trackCallback(this) -{} - -LinearEqualityModule::Statistics::Statistics() - : d_statPivots( - smtStatisticsRegistry().registerInt("theory::arith::pivots")), - d_statUpdates( - smtStatisticsRegistry().registerInt("theory::arith::updates")), - d_pivotTime( - smtStatisticsRegistry().registerTimer("theory::arith::pivotTime")), - d_adjTime( - smtStatisticsRegistry().registerTimer("theory::arith::adjTime")), - d_weakeningAttempts(smtStatisticsRegistry().registerInt( - "theory::arith::weakening::attempts")), - d_weakeningSuccesses(smtStatisticsRegistry().registerInt( - "theory::arith::weakening::success")), - d_weakenings(smtStatisticsRegistry().registerInt( - "theory::arith::weakening::total")), - d_weakenTime(smtStatisticsRegistry().registerTimer( - "theory::arith::weakening::time")), - d_forceTime( - smtStatisticsRegistry().registerTimer("theory::arith::forcing::time")) -{ -} - -void LinearEqualityModule::includeBoundUpdate(ArithVar v, const BoundsInfo& prev){ - Assert(!d_areTracking); - - BoundsInfo curr = d_variables.boundsInfo(v); - - Assert(prev != curr); - Tableau::ColIterator basicIter = d_tableau.colIterator(v); - for(; !basicIter.atEnd(); ++basicIter){ - const Tableau::Entry& entry = *basicIter; - Assert(entry.getColVar() == v); - int a_ijSgn = entry.getCoefficient().sgn(); - - RowIndex ridx = entry.getRowIndex(); - BoundsInfo& counts = d_btracking.get(ridx); - Trace("includeBoundUpdate") << d_tableau.rowIndexToBasic(ridx) << " " << counts << " to " ; - counts.addInChange(a_ijSgn, prev, curr); - Trace("includeBoundUpdate") << counts << " " << a_ijSgn << std::endl; - } -} - -void LinearEqualityModule::updateMany(const DenseMap& many){ - for(DenseMap::const_iterator i = many.begin(), i_end = many.end(); i != i_end; ++i){ - ArithVar nb = *i; - if(!d_tableau.isBasic(nb)){ - Assert(!d_tableau.isBasic(nb)); - const DeltaRational& newValue = many[nb]; - if(newValue != d_variables.getAssignment(nb)){ - Trace("arith::updateMany") - << "updateMany:" << nb << " " - << d_variables.getAssignment(nb) << " to "<< newValue << endl; - update(nb, newValue); - } - } - } -} - - - - -void LinearEqualityModule::applySolution(const DenseSet& newBasis, const DenseMap& newValues){ - forceNewBasis(newBasis); - updateMany(newValues); -} - -void LinearEqualityModule::forceNewBasis(const DenseSet& newBasis){ - TimerStat::CodeTimer codeTimer(d_statistics.d_forceTime); - cout << "force begin" << endl; - DenseSet needsToBeAdded; - for(DenseSet::const_iterator i = newBasis.begin(), i_end = newBasis.end(); i != i_end; ++i){ - ArithVar b = *i; - if(!d_tableau.isBasic(b)){ - needsToBeAdded.add(b); - } - } - - while(!needsToBeAdded.empty()){ - ArithVar toRemove = ARITHVAR_SENTINEL; - ArithVar toAdd = ARITHVAR_SENTINEL; - DenseSet::const_iterator i = needsToBeAdded.begin(), i_end = needsToBeAdded.end(); - for(; toAdd == ARITHVAR_SENTINEL && i != i_end; ++i){ - ArithVar v = *i; - - Tableau::ColIterator colIter = d_tableau.colIterator(v); - for(; !colIter.atEnd(); ++colIter){ - const Tableau::Entry& entry = *colIter; - Assert(entry.getColVar() == v); - ArithVar b = d_tableau.rowIndexToBasic(entry.getRowIndex()); - if(!newBasis.isMember(b)){ - toAdd = v; - if(toRemove == ARITHVAR_SENTINEL || - d_tableau.basicRowLength(toRemove) > d_tableau.basicRowLength(b)){ - toRemove = b; - } - } - } - } - Assert(toRemove != ARITHVAR_SENTINEL); - Assert(toAdd != ARITHVAR_SENTINEL); - - Trace("arith::forceNewBasis") << toRemove << " " << toAdd << endl; - d_tableau.pivot(toRemove, toAdd, d_trackCallback); - d_basicVariableUpdates(toAdd); - - Trace("arith::forceNewBasis") << needsToBeAdded.size() << "to go" << endl; - needsToBeAdded.remove(toAdd); - } -} - -void LinearEqualityModule::updateUntracked(ArithVar x_i, const DeltaRational& v){ - Assert(!d_tableau.isBasic(x_i)); - Assert(!d_areTracking); - const DeltaRational& assignment_x_i = d_variables.getAssignment(x_i); - ++(d_statistics.d_statUpdates); - - - Trace("arith") <<"update " << x_i << ": " - << assignment_x_i << "|-> " << v << endl; - DeltaRational diff = v - assignment_x_i; - - Tableau::ColIterator colIter = d_tableau.colIterator(x_i); - for(; !colIter.atEnd(); ++colIter){ - const Tableau::Entry& entry = *colIter; - Assert(entry.getColVar() == x_i); - - ArithVar x_j = d_tableau.rowIndexToBasic(entry.getRowIndex()); - const Rational& a_ji = entry.getCoefficient(); - - const DeltaRational& assignment = d_variables.getAssignment(x_j); - DeltaRational nAssignment = assignment+(diff * a_ji); - d_variables.setAssignment(x_j, nAssignment); - - d_basicVariableUpdates(x_j); - } - - d_variables.setAssignment(x_i, v); - - if(TraceIsOn("paranoid:check_tableau")){ debugCheckTableau(); } -} - -void LinearEqualityModule::updateTracked(ArithVar x_i, const DeltaRational& v){ - TimerStat::CodeTimer codeTimer(d_statistics.d_adjTime); - - Assert(!d_tableau.isBasic(x_i)); - Assert(d_areTracking); - - ++(d_statistics.d_statUpdates); - - DeltaRational diff = v - d_variables.getAssignment(x_i); - Trace("arith") <<"update " << x_i << ": " - << d_variables.getAssignment(x_i) << "|-> " << v << endl; - - - BoundCounts before = d_variables.atBoundCounts(x_i); - d_variables.setAssignment(x_i, v); - BoundCounts after = d_variables.atBoundCounts(x_i); - - bool anyChange = before != after; - - Tableau::ColIterator colIter = d_tableau.colIterator(x_i); - for(; !colIter.atEnd(); ++colIter){ - const Tableau::Entry& entry = *colIter; - Assert(entry.getColVar() == x_i); - - RowIndex ridx = entry.getRowIndex(); - ArithVar x_j = d_tableau.rowIndexToBasic(ridx); - const Rational& a_ji = entry.getCoefficient(); - - const DeltaRational& assignment = d_variables.getAssignment(x_j); - DeltaRational nAssignment = assignment+(diff * a_ji); - Trace("update") << x_j << " " << a_ji << assignment << " -> " << nAssignment << endl; - BoundCounts xjBefore = d_variables.atBoundCounts(x_j); - d_variables.setAssignment(x_j, nAssignment); - BoundCounts xjAfter = d_variables.atBoundCounts(x_j); - - Assert(rowIndexIsTracked(ridx)); - BoundsInfo& next_bc_k = d_btracking.get(ridx); - if(anyChange){ - next_bc_k.addInAtBoundChange(a_ji.sgn(), before, after); - } - if(xjBefore != xjAfter){ - next_bc_k.addInAtBoundChange(-1, xjBefore, xjAfter); - } - - d_basicVariableUpdates(x_j); - } - - if(TraceIsOn("paranoid:check_tableau")){ debugCheckTableau(); } -} - -void LinearEqualityModule::pivotAndUpdate(ArithVar x_i, ArithVar x_j, const DeltaRational& x_i_value){ - Assert(x_i != x_j); - - TimerStat::CodeTimer codeTimer(d_statistics.d_pivotTime); - - if(TraceIsOn("arith::tracking::pre")){ - Trace("arith::tracking") << "pre update" << endl; - debugCheckTracking(); - } - - if(TraceIsOn("arith::simplex:row")){ debugPivot(x_i, x_j); } - - RowIndex ridx = d_tableau.basicToRowIndex(x_i); - const Tableau::Entry& entry_ij = d_tableau.findEntry(ridx, x_j); - Assert(!entry_ij.blank()); - - const Rational& a_ij = entry_ij.getCoefficient(); - const DeltaRational& betaX_i = d_variables.getAssignment(x_i); - DeltaRational theta = (x_i_value - betaX_i)/a_ij; - DeltaRational x_j_value = d_variables.getAssignment(x_j) + theta; - - updateTracked(x_j, x_j_value); - - if(TraceIsOn("arith::tracking::mid")){ - Trace("arith::tracking") << "postupdate prepivot" << endl; - debugCheckTracking(); - } - - // Pivots - ++(d_statistics.d_statPivots); - - d_tableau.pivot(x_i, x_j, d_trackCallback); - - if(TraceIsOn("arith::tracking::post")){ - Trace("arith::tracking") << "postpivot" << endl; - debugCheckTracking(); - } - - d_basicVariableUpdates(x_j); - - if(TraceIsOn("matrix")){ - d_tableau.printMatrix(); - } -} - -uint32_t LinearEqualityModule::updateProduct(const UpdateInfo& inf) const { - uint32_t colLen = d_tableau.getColLength(inf.nonbasic()); - if(inf.describesPivot()){ - Assert(inf.leaving() != inf.nonbasic()); - return colLen + d_tableau.basicRowLength(inf.leaving()); - }else{ - return colLen; - } -} - -void LinearEqualityModule::debugCheckTracking(){ - Tableau::BasicIterator basicIter = d_tableau.beginBasic(), - endIter = d_tableau.endBasic(); - for(; basicIter != endIter; ++basicIter){ - ArithVar basic = *basicIter; - Trace("arith::tracking") << "arith::tracking row basic: " << basic << endl; - - for(Tableau::RowIterator iter = d_tableau.basicRowIterator(basic); !iter.atEnd() && TraceIsOn("arith::tracking"); ++iter){ - const Tableau::Entry& entry = *iter; - - ArithVar var = entry.getColVar(); - const Rational& coeff = entry.getCoefficient(); - DeltaRational beta = d_variables.getAssignment(var); - Trace("arith::tracking") << var << " " << d_variables.boundsInfo(var) - << " " << beta << coeff; - if(d_variables.hasLowerBound(var)){ - Trace("arith::tracking") << "(lb " << d_variables.getLowerBound(var) << ")"; - } - if(d_variables.hasUpperBound(var)){ - Trace("arith::tracking") << "(up " << d_variables.getUpperBound(var) << ")"; - } - Trace("arith::tracking") << endl; - } - Trace("arith::tracking") << "end row"<< endl; - - if(basicIsTracked(basic)){ - RowIndex ridx = d_tableau.basicToRowIndex(basic); - BoundsInfo computed = computeRowBoundInfo(ridx, false); - Trace("arith::tracking") - << "computed " << computed - << " tracking " << d_btracking[ridx] << endl; - Assert(computed == d_btracking[ridx]); - } - } -} - -void LinearEqualityModule::debugPivot(ArithVar x_i, ArithVar x_j){ - Trace("arith::pivot") << "debugPivot("<< x_i <<"|->"<< x_j << ")" << endl; - - for(Tableau::RowIterator iter = d_tableau.basicRowIterator(x_i); !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - - ArithVar var = entry.getColVar(); - const Rational& coeff = entry.getCoefficient(); - DeltaRational beta = d_variables.getAssignment(var); - Trace("arith::pivot") << var << beta << coeff; - if(d_variables.hasLowerBound(var)){ - Trace("arith::pivot") << "(lb " << d_variables.getLowerBound(var) << ")"; - } - if(d_variables.hasUpperBound(var)){ - Trace("arith::pivot") << "(up " << d_variables.getUpperBound(var) << ")"; - } - Trace("arith::pivot") << endl; - } - Trace("arith::pivot") << "end row"<< endl; -} - -/** - * This check is quite expensive. - * It should be wrapped in a TraceIsOn() guard. - * if(TraceIsOn("paranoid:check_tableau")){ - * checkTableau(); - * } - */ -void LinearEqualityModule::debugCheckTableau(){ - Tableau::BasicIterator basicIter = d_tableau.beginBasic(), - endIter = d_tableau.endBasic(); - for(; basicIter != endIter; ++basicIter){ - ArithVar basic = *basicIter; - DeltaRational sum; - Trace("paranoid:check_tableau") << "starting row" << basic << endl; - Tableau::RowIterator nonbasicIter = d_tableau.basicRowIterator(basic); - for(; !nonbasicIter.atEnd(); ++nonbasicIter){ - const Tableau::Entry& entry = *nonbasicIter; - ArithVar nonbasic = entry.getColVar(); - if(basic == nonbasic) continue; - - const Rational& coeff = entry.getCoefficient(); - DeltaRational beta = d_variables.getAssignment(nonbasic); - Trace("paranoid:check_tableau") << nonbasic << beta << coeff< 0)); - - const DeltaRational& bound = vUb ? - d_variables.getUpperBound(v): - d_variables.getLowerBound(v); - - DeltaRational diff = bound * coeff; - sum = sum + diff; - } - return sum; -} - -/** - * Computes the value of a basic variable using the current assignment. - */ -DeltaRational LinearEqualityModule::computeRowValue(ArithVar x, bool useSafe) const{ - Assert(d_tableau.isBasic(x)); - DeltaRational sum(0); - - for(Tableau::RowIterator i = d_tableau.basicRowIterator(x); !i.atEnd(); ++i){ - const Tableau::Entry& entry = (*i); - ArithVar nonbasic = entry.getColVar(); - if(nonbasic == x) continue; - const Rational& coeff = entry.getCoefficient(); - - const DeltaRational& assignment = d_variables.getAssignment(nonbasic, useSafe); - sum = sum + (assignment * coeff); - } - return sum; -} - -const Tableau::Entry* LinearEqualityModule::rowLacksBound(RowIndex ridx, bool rowUb, ArithVar skip){ - Tableau::RowIterator iter = d_tableau.ridRowIterator(ridx); - for(; !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - - ArithVar var = entry.getColVar(); - if(var == skip) { continue; } - - int sgn = entry.getCoefficient().sgn(); - bool selectUb = (rowUb == (sgn > 0)); - bool hasBound = selectUb ? - d_variables.hasUpperBound(var): - d_variables.hasLowerBound(var); - if(!hasBound){ - return &entry; - } - } - return NULL; -} - -void LinearEqualityModule::propagateBasicFromRow(ConstraintP c, - bool produceProofs) -{ - Assert(c != NullConstraint); - Assert(c->isUpperBound() || c->isLowerBound()); - Assert(!c->assertedToTheTheory()); - Assert(!c->hasProof()); - - bool upperBound = c->isUpperBound(); - ArithVar basic = c->getVariable(); - RowIndex ridx = d_tableau.basicToRowIndex(basic); - - ConstraintCPVec bounds; - RationalVectorP coeffs = produceProofs ? new RationalVector() : nullptr; - propagateRow(bounds, ridx, upperBound, c, coeffs); - c->impliedByFarkas(bounds, coeffs, false); - c->tryToPropagate(); - - if(coeffs != RationalVectorPSentinel) { delete coeffs; } -} - -/* An explanation of the farkas coefficients. - * - * We are proving c using the other variables on the row. - * The proof is in terms of the other constraints and the negation of c, ~c. - * - * A row has the form: - * sum a_i * x_i = 0 - * or - * sx + sum r y + sum q z = 0 - * where r > 0 and q < 0. - * - * If rowUp, we are proving c - * g = sum r u_y + sum q l_z - * and c is entailed by -sx <= g - * If !rowUp, we are proving c - * g = sum r l_y + sum q u_z - * and c is entailed by -sx >= g - * - * | s | c | ~c | u_i | l_i - * if rowUp | s > 0 | x >= -g/s | x < -g/s | a_i > 0 | a_i < 0 - * if rowUp | s < 0 | x <= -g/s | x > -g/s | a_i > 0 | a_i < 0 - * if !rowUp | s > 0 | x <= -g/s | x > -g/s | a_i < 0 | a_i > 0 - * if !rowUp | s < 0 | x >= -g/s | x < -g/s | a_i < 0 | a_i > 0 - * - * - * Thus we treat !rowUp as multiplying the row by -1 and rowUp as 1 - * for the entire row. - */ -void LinearEqualityModule::propagateRow(ConstraintCPVec& into, RowIndex ridx, bool rowUp, ConstraintP c, RationalVectorP farkas){ - Assert(!c->assertedToTheTheory()); - Assert(c->canBePropagated()); - Assert(!c->hasProof()); - - if(farkas != RationalVectorPSentinel){ - Assert(farkas->empty()); - farkas->push_back(Rational(0)); - } - - ArithVar v = c->getVariable(); - Trace("arith::propagateRow") << "LinearEqualityModule::propagateRow(" - << ridx << ", " << rowUp << ", " << v << ") start" << endl; - - const Rational& multiple = rowUp ? d_one : d_negOne; - - Trace("arith::propagateRow") << "multiple: " << multiple << endl; - - Tableau::RowIterator iter = d_tableau.ridRowIterator(ridx); - for(; !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - ArithVar nonbasic = entry.getColVar(); - const Rational& a_ij = entry.getCoefficient(); - int sgn = a_ij.sgn(); - Assert(sgn != 0); - bool selectUb = rowUp ? (sgn > 0) : (sgn < 0); - - Assert(nonbasic != v || (rowUp && a_ij.sgn() > 0 && c->isLowerBound()) - || (rowUp && a_ij.sgn() < 0 && c->isUpperBound()) - || (!rowUp && a_ij.sgn() > 0 && c->isUpperBound()) - || (!rowUp && a_ij.sgn() < 0 && c->isLowerBound())); - - if(TraceIsOn("arith::propagateRow")){ - if(nonbasic == v){ - Trace("arith::propagateRow") << "(target) " - << rowUp << " " - << a_ij.sgn() << " " - << c->isLowerBound() << " " - << c->isUpperBound() << endl; - - Trace("arith::propagateRow") << "(target) "; - } - Trace("arith::propagateRow") << "propagateRow " << a_ij << " * " << nonbasic ; - } - - if(nonbasic == v){ - if(farkas != RationalVectorPSentinel){ - Assert(farkas->front().isZero()); - Rational multAij = multiple * a_ij; - Trace("arith::propagateRow") << "(" << multAij << ") "; - farkas->front() = multAij; - } - - Trace("arith::propagateRow") << c << endl; - }else{ - - ConstraintCP bound = selectUb - ? d_variables.getUpperBoundConstraint(nonbasic) - : d_variables.getLowerBoundConstraint(nonbasic); - - if(farkas != RationalVectorPSentinel){ - Rational multAij = multiple * a_ij; - Trace("arith::propagateRow") << "(" << multAij << ") "; - farkas->push_back(multAij); - } - Assert(bound != NullConstraint); - Trace("arith::propagateRow") << bound << endl; - into.push_back(bound); - } - } - Trace("arith::propagateRow") << "LinearEqualityModule::propagateRow(" - << ridx << ", " << rowUp << ", " << v << ") done" << endl; - -} - -ConstraintP LinearEqualityModule::weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, const Rational& coeff, bool& anyWeakening, ArithVar basic) const { - - int sgn = coeff.sgn(); - bool ub = aboveUpper?(sgn < 0) : (sgn > 0); - - ConstraintP c = ub ? - d_variables.getUpperBoundConstraint(v) : - d_variables.getLowerBoundConstraint(v); - - bool weakened; - do{ - const DeltaRational& bound = c->getValue(); - - weakened = false; - - ConstraintP weaker = ub? - c->getStrictlyWeakerUpperBound(true, true): - c->getStrictlyWeakerLowerBound(true, true); - - if(weaker != NullConstraint){ - const DeltaRational& weakerBound = weaker->getValue(); - - DeltaRational diff = aboveUpper ? bound - weakerBound : weakerBound - bound; - //if var == basic, - // if aboveUpper, weakerBound > bound, multiply by -1 - // if !aboveUpper, weakerBound < bound, multiply by -1 - diff = diff * coeff; - if(surplus > diff){ - ++d_statistics.d_weakenings; - weakened = true; - anyWeakening = true; - surplus = surplus - diff; - - Trace("arith::weak") << "found:" << endl; - if(v == basic){ - Trace("arith::weak") << " basic: "; - } - Trace("arith::weak") << " " << surplus << " "<< diff << endl - << " " << bound << c << endl - << " " << weakerBound << weaker << endl; - - Assert(diff.sgn() > 0); - c = weaker; - } - } - }while(weakened); - - return c; -} - -/* An explanation of the farkas coefficients. - * - * We are proving a conflict on the basic variable x_b. - * If aboveUpper, then the conflict is with the constraint c : x_b <= u_b. - * If !aboveUpper, then the conflict is with the constraint c : x_b >= l_b. - * - * A row has the form: - * -x_b sum a_i * x_i = 0 - * or - * -x_b + sum r y + sum q z = 0, - * x_b = sum r y + sum q z - * where r > 0 and q < 0. - * - * - * If !aboveUp, we are proving ~c: x_b < l_b - * g = sum r u_y + sum q l_z - * x_b <= g < l_b - * and ~c is entailed by x_b <= g - * - * If aboveUp, we are proving ~c : x_b > u_b - * g = sum r l_y + sum q u_z - * x_b >= g > u_b - * and ~c is entailed by x_b >= g - * - * - * | s | c | ~c | u_i | l_i - * if !aboveUp | s > 0 | x >= -g/s | x < -g/s | a_i > 0 | a_i < 0 - * if !aboveUp | s < 0 | x <= -g/s | x > -g/s | a_i > 0 | a_i < 0 - * if aboveUp | s > 0 | x <= -g/s | x > -g/s | a_i < 0 | a_i > 0 - * if aboveUp | s < 0 | x >= -g/s | x < -g/s | a_i < 0 | a_i > 0 - * - * Thus we treat aboveUp as multiplying the row by -1 and !aboveUp as 1 - * for the entire row. - */ -ConstraintCP LinearEqualityModule::minimallyWeakConflict(bool aboveUpper, ArithVar basicVar, FarkasConflictBuilder& fcs) const { - Assert(!fcs.underConstruction()); - TimerStat::CodeTimer codeTimer(d_statistics.d_weakenTime); - - Trace("arith::weak") << "LinearEqualityModule::minimallyWeakConflict(" - << aboveUpper <<", "<< basicVar << ", ...) start" << endl; - - const Rational& adjustSgn = aboveUpper ? d_negOne : d_one; - const DeltaRational& assignment = d_variables.getAssignment(basicVar); - DeltaRational surplus; - if(aboveUpper){ - Assert(d_variables.hasUpperBound(basicVar)); - Assert(assignment > d_variables.getUpperBound(basicVar)); - surplus = assignment - d_variables.getUpperBound(basicVar); - }else{ - Assert(d_variables.hasLowerBound(basicVar)); - Assert(assignment < d_variables.getLowerBound(basicVar)); - surplus = d_variables.getLowerBound(basicVar) - assignment; - } - - bool anyWeakenings = false; - for(Tableau::RowIterator i = d_tableau.basicRowIterator(basicVar); !i.atEnd(); ++i){ - const Tableau::Entry& entry = *i; - ArithVar v = entry.getColVar(); - const Rational& coeff = entry.getCoefficient(); - bool weakening = false; - ConstraintP c = weakestExplanation(aboveUpper, surplus, v, coeff, weakening, basicVar); - Trace("arith::weak") << "weak : " << weakening << " " - << c->assertedToTheTheory() << " " - << d_variables.getAssignment(v) << " " - << c << endl; - anyWeakenings = anyWeakenings || weakening; - - fcs.addConstraint(c, coeff, adjustSgn); - if(basicVar == v){ - Assert(!c->negationHasProof()); - fcs.makeLastConsequent(); - } - } - Assert(fcs.consequentIsSet()); - - ConstraintCP conflicted = fcs.commitConflict(); - - ++d_statistics.d_weakeningAttempts; - if(anyWeakenings){ - ++d_statistics.d_weakeningSuccesses; - } - Trace("arith::weak") << "LinearEqualityModule::minimallyWeakConflict(" - << aboveUpper <<", "<< basicVar << ", ...) done" << endl; - return conflicted; -} - -ArithVar LinearEqualityModule::minVarOrder(ArithVar x, ArithVar y) const { - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - if(x <= y){ - return x; - } else { - return y; - } -} - -ArithVar LinearEqualityModule::minColLength(ArithVar x, ArithVar y) const { - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - Assert(!d_tableau.isBasic(x)); - Assert(!d_tableau.isBasic(y)); - uint32_t xLen = d_tableau.getColLength(x); - uint32_t yLen = d_tableau.getColLength(y); - if( xLen > yLen){ - return y; - } else if( xLen== yLen ){ - return minVarOrder(x,y); - }else{ - return x; - } -} - -ArithVar LinearEqualityModule::minRowLength(ArithVar x, ArithVar y) const { - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - Assert(d_tableau.isBasic(x)); - Assert(d_tableau.isBasic(y)); - uint32_t xLen = d_tableau.basicRowLength(x); - uint32_t yLen = d_tableau.basicRowLength(y); - if( xLen > yLen){ - return y; - } else if( xLen== yLen ){ - return minVarOrder(x,y); - }else{ - return x; - } -} - -ArithVar LinearEqualityModule::minBoundAndColLength(ArithVar x, ArithVar y) const{ - Assert(x != ARITHVAR_SENTINEL); - Assert(y != ARITHVAR_SENTINEL); - Assert(!d_tableau.isBasic(x)); - Assert(!d_tableau.isBasic(y)); - if(d_variables.hasEitherBound(x) && !d_variables.hasEitherBound(y)){ - return y; - }else if(!d_variables.hasEitherBound(x) && d_variables.hasEitherBound(y)){ - return x; - }else { - return minColLength(x, y); - } -} - -template -ArithVar LinearEqualityModule::selectSlack(ArithVar x_i, VarPreferenceFunction pref) const{ - ArithVar slack = ARITHVAR_SENTINEL; - - for(Tableau::RowIterator iter = d_tableau.basicRowIterator(x_i); !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - ArithVar nonbasic = entry.getColVar(); - if(nonbasic == x_i) continue; - - const Rational& a_ij = entry.getCoefficient(); - int sgn = a_ij.sgn(); - if(isAcceptableSlack(sgn, nonbasic)){ - //If one of the above conditions is met, we have found an acceptable - //nonbasic variable to pivot x_i with. We can now choose which one we - //prefer the most. - slack = (slack == ARITHVAR_SENTINEL) ? nonbasic : (this->*pref)(slack, nonbasic); - } - } - - return slack; -} - -const Tableau::Entry* LinearEqualityModule::selectSlackEntry(ArithVar x_i, bool above) const{ - for(Tableau::RowIterator iter = d_tableau.basicRowIterator(x_i); !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - ArithVar nonbasic = entry.getColVar(); - if(nonbasic == x_i) continue; - - const Rational& a_ij = entry.getCoefficient(); - int sgn = a_ij.sgn(); - if(above && isAcceptableSlack(sgn, nonbasic)){ - //If one of the above conditions is met, we have found an acceptable - //nonbasic variable to pivot x_i with. We can now choose which one we - //prefer the most. - return &entry; - }else if(!above && isAcceptableSlack(sgn, nonbasic)){ - return &entry; - } - } - - return NULL; -} - -void LinearEqualityModule::startTrackingBoundCounts(){ - Assert(!d_areTracking); - d_areTracking = true; - if(TraceIsOn("arith::tracking")){ - debugCheckTracking(); - } - Assert(d_areTracking); -} - -void LinearEqualityModule::stopTrackingBoundCounts(){ - Assert(d_areTracking); - d_areTracking = false; - if(TraceIsOn("arith::tracking")){ - debugCheckTracking(); - } - Assert(!d_areTracking); -} - - -void LinearEqualityModule::trackRowIndex(RowIndex ridx){ - Assert(!rowIndexIsTracked(ridx)); - BoundsInfo bi = computeRowBoundInfo(ridx, true); - d_btracking.set(ridx, bi); -} - -BoundsInfo LinearEqualityModule::computeRowBoundInfo(RowIndex ridx, bool inQueue) const{ - BoundsInfo bi; - - Tableau::RowIterator iter = d_tableau.ridRowIterator(ridx); - for(; !iter.atEnd(); ++iter){ - const Tableau::Entry& entry = *iter; - ArithVar v = entry.getColVar(); - const Rational& a_ij = entry.getCoefficient(); - bi += (d_variables.selectBoundsInfo(v, inQueue)).multiplyBySgn(a_ij.sgn()); - } - return bi; -} - -BoundCounts LinearEqualityModule::debugBasicAtBoundCount(ArithVar x_i) const { - return d_btracking[d_tableau.basicToRowIndex(x_i)].atBounds(); -} - -/** - * If the pivot described in u were performed, - * then the row would qualify as being either at the minimum/maximum - * to the non-basics being at their bounds. - * The minimum/maximum is determined by the direction the non-basic is changing. - */ -bool LinearEqualityModule::basicsAtBounds(const UpdateInfo& u) const { - Assert(u.describesPivot()); - - ArithVar nonbasic = u.nonbasic(); - ArithVar basic = u.leaving(); - Assert(basicIsTracked(basic)); - int coeffSgn = u.getCoefficient().sgn(); - int nbdir = u.nonbasicDirection(); - - ConstraintP c = u.limiting(); - int toUB = (c->getType() == UpperBound || - c->getType() == Equality) ? 1 : 0; - int toLB = (c->getType() == LowerBound || - c->getType() == Equality) ? 1 : 0; - - RowIndex ridx = d_tableau.basicToRowIndex(basic); - - BoundCounts bcs = d_btracking[ridx].atBounds(); - // x = c*n + \sum d*m - // 0 = -x + c*n + \sum d*m - // n = 1/c * x + -1/c * (\sum d*m) - BoundCounts nonb = bcs - d_variables.atBoundCounts(nonbasic).multiplyBySgn(coeffSgn); - nonb.addInChange(-1, d_variables.atBoundCounts(basic), BoundCounts(toLB, toUB)); - nonb = nonb.multiplyBySgn(-coeffSgn); - - uint32_t length = d_tableau.basicRowLength(basic); - Trace("basicsAtBounds") - << "bcs " << bcs - << "nonb " << nonb - << "length " << length << endl; - // nonb has nb excluded. - if(nbdir < 0){ - return nonb.lowerBoundCount() + 1 == length; - }else{ - Assert(nbdir > 0); - return nonb.upperBoundCount() + 1 == length; - } -} - -bool LinearEqualityModule::nonbasicsAtLowerBounds(ArithVar basic) const { - Assert(basicIsTracked(basic)); - RowIndex ridx = d_tableau.basicToRowIndex(basic); - - BoundCounts bcs = d_btracking[ridx].atBounds(); - uint32_t length = d_tableau.basicRowLength(basic); - - // return true if excluding the basic is every element is at its "lowerbound" - // The psuedo code is: - // bcs -= basic.count(basic, basic's sgn) - // return bcs.lowerBoundCount() + 1 == length - // As basic's sign is always -1, we can pull out the pieces of the count: - // bcs.lowerBoundCount() - basic.atUpperBoundInd() + 1 == length - // basic.atUpperBoundInd() is either 0 or 1 - uint32_t lbc = bcs.lowerBoundCount(); - return (lbc == length) || - (lbc + 1 == length && d_variables.cmpAssignmentUpperBound(basic) != 0); -} - -bool LinearEqualityModule::nonbasicsAtUpperBounds(ArithVar basic) const { - Assert(basicIsTracked(basic)); - RowIndex ridx = d_tableau.basicToRowIndex(basic); - BoundCounts bcs = d_btracking[ridx].atBounds(); - uint32_t length = d_tableau.basicRowLength(basic); - uint32_t ubc = bcs.upperBoundCount(); - // See the comment for nonbasicsAtLowerBounds() - - return (ubc == length) || - (ubc + 1 == length && d_variables.cmpAssignmentLowerBound(basic) != 0); -} - -void LinearEqualityModule::trackingMultiplyRow(RowIndex ridx, int sgn) { - Assert(rowIndexIsTracked(ridx)); - Assert(sgn != 0); - if(sgn < 0){ - BoundsInfo& bi = d_btracking.get(ridx); - bi = bi.multiplyBySgn(sgn); - } -} - -void LinearEqualityModule::trackingCoefficientChange(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn){ - Assert(oldSgn != currSgn); - BoundsInfo nb_inf = d_variables.boundsInfo(nb); - - Assert(rowIndexIsTracked(ridx)); - - BoundsInfo& row_bi = d_btracking.get(ridx); - row_bi.addInSgn(nb_inf, oldSgn, currSgn); -} - -ArithVar LinearEqualityModule::minBy(const ArithVarVec& vec, VarPreferenceFunction pf) const{ - if(vec.empty()) { - return ARITHVAR_SENTINEL; - }else { - ArithVar sel = vec.front(); - ArithVarVec::const_iterator i = vec.begin() + 1; - ArithVarVec::const_iterator i_end = vec.end(); - for(; i != i_end; ++i){ - sel = (this->*pf)(sel, *i); - } - return sel; - } -} - -bool LinearEqualityModule::accumulateBorder(const Tableau::Entry& entry, bool ub){ - ArithVar currBasic = d_tableau.rowIndexToBasic(entry.getRowIndex()); - - Assert(basicIsTracked(currBasic)); - - ConstraintP bound = ub ? - d_variables.getUpperBoundConstraint(currBasic): - d_variables.getLowerBoundConstraint(currBasic); - - if(bound == NullConstraint){ return false; } - Assert(bound != NullConstraint); - - const Rational& coeff = entry.getCoefficient(); - - const DeltaRational& assignment = d_variables.getAssignment(currBasic); - DeltaRational toBound = bound->getValue() - assignment; - DeltaRational nbDiff = toBound/coeff; - - // if ub - // if toUB >= 0 - // then ub >= currBasic - // if sgn > 0, - // then diff >= 0, so nb must increase for G - // else diff <= 0, so nb must decrease for G - // else ub < currBasic - // if sgn > 0, - // then diff < 0, so nb must decrease for G - // else diff > 0, so nb must increase for G - - int diffSgn = nbDiff.sgn(); - - if(diffSgn != 0 && willBeInConflictAfterPivot(entry, nbDiff, ub)){ - return true; - }else{ - bool areFixing = ub ? (toBound.sgn() < 0 ) : (toBound.sgn() > 0); - Border border(bound, nbDiff, areFixing, &entry, ub); - bool increasing = - (diffSgn > 0) || - (diffSgn == 0 && ((coeff.sgn() > 0) == ub)); - - // assume diffSgn == 0 - // if coeff > 0, - // if ub, inc - // else, dec - // else coeff < 0 - // if ub, dec - // else, inc - - if(increasing){ - Trace("handleBorders") << "push back increasing " << border << endl; - d_increasing.push_back(border); - }else{ - Trace("handleBorders") << "push back decreasing " << border << endl; - d_decreasing.push_back(border); - } - return false; - } -} - -bool LinearEqualityModule::willBeInConflictAfterPivot(const Tableau::Entry& entry, const DeltaRational& nbDiff, bool bToUB) const{ - int nbSgn = nbDiff.sgn(); - Assert(nbSgn != 0); - - if(nbSgn > 0){ - if (!d_upperBoundDifference || nbDiff <= *d_upperBoundDifference) - { - return false; - } - }else{ - if (!d_lowerBoundDifference || nbDiff >= *d_lowerBoundDifference) - { - return false; - } - } - - // Assume past this point, nb will be in error if this pivot is done - ArithVar nb = entry.getColVar(); - RowIndex ridx = entry.getRowIndex(); - ArithVar basic = d_tableau.rowIndexToBasic(ridx); - Assert(rowIndexIsTracked(ridx)); - int coeffSgn = entry.getCoefficient().sgn(); - - - // if bToUB, then basic is going to be set to its upperbound - // if not bToUB, then basic is going to be set to its lowerbound - - // Different steps of solving for this: - // 1) y = a * x + \sum b * z - // 2) -a * x = -y + \sum b * z - // 3) x = (-1/a) * ( -y + \sum b * z) - - BoundCounts bc = d_btracking[ridx].atBounds(); - - // 1) y = a * x + \sum b * z - // Get bc(\sum b * z) - BoundCounts sumOnly = bc - d_variables.atBoundCounts(nb).multiplyBySgn(coeffSgn); - - // y's bounds in the proposed model - int yWillBeAtUb = (bToUB || d_variables.boundsAreEqual(basic)) ? 1 : 0; - int yWillBeAtLb = (!bToUB || d_variables.boundsAreEqual(basic)) ? 1 : 0; - BoundCounts ysBounds(yWillBeAtLb, yWillBeAtUb); - - // 2) -a * x = -y + \sum b * z - // Get bc(-y + \sum b * z) - sumOnly.addInChange(-1, d_variables.atBoundCounts(basic), ysBounds); - - // 3) x = (-1/a) * ( -y + \sum b * z) - // Get bc((-1/a) * ( -y + \sum b * z)) - BoundCounts xsBoundsAfterPivot = sumOnly.multiplyBySgn(-coeffSgn); - - uint32_t length = d_tableau.basicRowLength(basic); - if(nbSgn > 0){ - // Only check for the upper bound being violated - return xsBoundsAfterPivot.lowerBoundCount() + 1 == length; - }else{ - // Only check for the lower bound being violated - return xsBoundsAfterPivot.upperBoundCount() + 1 == length; - } -} - -UpdateInfo LinearEqualityModule::mkConflictUpdate(const Tableau::Entry& entry, bool ub) const{ - ArithVar currBasic = d_tableau.rowIndexToBasic(entry.getRowIndex()); - ArithVar nb = entry.getColVar(); - - ConstraintP bound = ub ? - d_variables.getUpperBoundConstraint(currBasic): - d_variables.getLowerBoundConstraint(currBasic); - - - const Rational& coeff = entry.getCoefficient(); - const DeltaRational& assignment = d_variables.getAssignment(currBasic); - DeltaRational toBound = bound->getValue() - assignment; - DeltaRational nbDiff = toBound/coeff; - - return UpdateInfo::conflict(nb, nbDiff, coeff, bound); -} - -UpdateInfo LinearEqualityModule::speculativeUpdate(ArithVar nb, const Rational& focusCoeff, UpdatePreferenceFunction pref){ - Assert(d_increasing.empty()); - Assert(d_decreasing.empty()); - Assert(!d_lowerBoundDifference); - Assert(!d_upperBoundDifference); - - int focusCoeffSgn = focusCoeff.sgn(); - - Trace("speculativeUpdate") << "speculativeUpdate" << endl; - Trace("speculativeUpdate") << "nb " << nb << endl; - Trace("speculativeUpdate") << "focusCoeff " << focusCoeff << endl; - - if(d_variables.hasUpperBound(nb)){ - ConstraintP ub = d_variables.getUpperBoundConstraint(nb); - d_upperBoundDifference = ub->getValue() - d_variables.getAssignment(nb); - Border border(ub, *d_upperBoundDifference, false, NULL, true); - Trace("handleBorders") << "push back increasing " << border << endl; - d_increasing.push_back(border); - } - if(d_variables.hasLowerBound(nb)){ - ConstraintP lb = d_variables.getLowerBoundConstraint(nb); - d_lowerBoundDifference = lb->getValue() - d_variables.getAssignment(nb); - Border border(lb, *d_lowerBoundDifference, false, NULL, false); - Trace("handleBorders") << "push back decreasing " << border << endl; - d_decreasing.push_back(border); - } - - Tableau::ColIterator colIter = d_tableau.colIterator(nb); - for(; !colIter.atEnd(); ++colIter){ - const Tableau::Entry& entry = *colIter; - Assert(entry.getColVar() == nb); - - if(accumulateBorder(entry, true)){ - clearSpeculative(); - return mkConflictUpdate(entry, true); - } - if(accumulateBorder(entry, false)){ - clearSpeculative(); - return mkConflictUpdate(entry, false); - } - } - - UpdateInfo selected; - BorderHeap& withSgn = focusCoeffSgn > 0 ? d_increasing : d_decreasing; - BorderHeap& againstSgn = focusCoeffSgn > 0 ? d_decreasing : d_increasing; - - handleBorders(selected, nb, focusCoeff, withSgn, 0, pref); - int m = 1 - selected.errorsChangeSafe(0); - handleBorders(selected, nb, focusCoeff, againstSgn, m, pref); - - clearSpeculative(); - return selected; -} - -void LinearEqualityModule::clearSpeculative(){ - // clear everything away - d_increasing.clear(); - d_decreasing.clear(); - d_lowerBoundDifference.reset(); - d_upperBoundDifference.reset(); -} - -void LinearEqualityModule::handleBorders(UpdateInfo& selected, ArithVar nb, const Rational& focusCoeff, BorderHeap& heap, int minimumFixes, UpdatePreferenceFunction pref){ - Assert(minimumFixes >= 0); - - // The values popped off of the heap - // should be popped with the values closest to 0 - // being first and larger in absolute value last - - - int fixesRemaining = heap.possibleFixes(); - - Trace("handleBorders") - << "handleBorders " - << "nb " << nb - << "fc " << focusCoeff - << "h.e " << heap.empty() - << "h.dir " << heap.direction() - << "h.rem " << fixesRemaining - << "h.0s " << heap.numZeroes() - << "min " << minimumFixes - << endl; - - if(heap.empty()){ - // if the heap is empty, return - return; - } - - bool zeroesWillDominate = fixesRemaining - heap.numZeroes() < minimumFixes; - - // can the number of fixes ever exceed the minimum? - // no more than the number of possible fixes can be fixed in total - // nothing can be fixed before the zeroes are taken care of - if(minimumFixes > 0 && zeroesWillDominate){ - return; - } - - - int negErrorChange = 0; - int nbDir = heap.direction(); - - // points at the beginning of the heap - if(zeroesWillDominate){ - heap.dropNonZeroes(); - } - heap.make_heap(); - - - // pretend like the previous block had a value of zero. - // The block that actually has a value of 0 must handle this. - const DeltaRational zero(0); - const DeltaRational* prevBlockValue = &zero; - - /** The coefficient changes as the value crosses border. */ - Rational effectiveCoefficient = focusCoeff; - - /* Keeps track of the change to the value of the focus function.*/ - DeltaRational totalFocusChange(0); - - const int focusCoeffSgn = focusCoeff.sgn(); - - while(heap.more() && - (fixesRemaining + negErrorChange > minimumFixes || - (fixesRemaining + negErrorChange == minimumFixes && - effectiveCoefficient.sgn() == focusCoeffSgn))){ - // There are more elements && - // we can either fix at least 1 more variable in the error function - // or we can improve the error function - - - int brokenInBlock = 0; - BorderVec::const_iterator endBlock = heap.end(); - - pop_block(heap, brokenInBlock, fixesRemaining, negErrorChange); - - // if endVec == beginVec, block starts there - // other wise, block starts at endVec - BorderVec::const_iterator startBlock - = heap.more() ? heap.end() : heap.begin(); - - const DeltaRational& blockValue = (*startBlock).d_diff; - - // if decreasing - // blockValue < prevBlockValue - // diff.sgn() = -1 - DeltaRational diff = blockValue - (*prevBlockValue); - DeltaRational blockChangeToFocus = diff * effectiveCoefficient; - totalFocusChange += blockChangeToFocus; - - Trace("handleBorders") - << "blockValue " << (blockValue) - << "diff " << diff - << "blockChangeToFocus " << totalFocusChange - << "blockChangeToFocus " << totalFocusChange - << "negErrorChange " << negErrorChange - << "brokenInBlock " << brokenInBlock - << "fixesRemaining " << fixesRemaining - << endl; - - int currFocusChangeSgn = totalFocusChange.sgn(); - for(BorderVec::const_iterator i = startBlock; i != endBlock; ++i){ - const Border& b = *i; - - Trace("handleBorders") << b << endl; - - bool makesImprovement = negErrorChange > 0 || - (negErrorChange == 0 && currFocusChangeSgn > 0); - - if(!makesImprovement){ - if(b.ownBorder() || minimumFixes > 0){ - continue; - } - } - - UpdateInfo proposal(nb, nbDir); - if(b.ownBorder()){ - proposal.witnessedUpdate(b.d_diff, b.d_bound, -negErrorChange, currFocusChangeSgn); - }else{ - proposal.update(b.d_diff, b.getCoefficient(), b.d_bound, -negErrorChange, currFocusChangeSgn); - } - - if(selected.unbounded() || (this->*pref)(selected, proposal)){ - selected = proposal; - } - } - - effectiveCoefficient += updateCoefficient(startBlock, endBlock); - prevBlockValue = &blockValue; - negErrorChange -= brokenInBlock; - } -} - -Rational LinearEqualityModule::updateCoefficient(BorderVec::const_iterator startBlock, BorderVec::const_iterator endBlock){ - //update coefficient - Rational changeToCoefficient(0); - for(BorderVec::const_iterator i = startBlock; i != endBlock; ++i){ - const Border& curr = *i; - if(curr.ownBorder()){// breaking its own bound - if(curr.d_upperbound){ - changeToCoefficient -= 1; - }else{ - changeToCoefficient += 1; - } - }else{ - const Rational& coeff = curr.d_entry->getCoefficient(); - if(curr.d_areFixing){ - if(curr.d_upperbound){// fixing an upper bound - changeToCoefficient += coeff; - }else{// fixing a lower bound - changeToCoefficient -= coeff; - } - }else{ - if(curr.d_upperbound){// breaking an upper bound - changeToCoefficient -= coeff; - }else{ - // breaking a lower bound - changeToCoefficient += coeff; - } - } - } - } - return changeToCoefficient; -} - -void LinearEqualityModule::pop_block(BorderHeap& heap, int& brokenInBlock, int& fixesRemaining, int& negErrorChange){ - Assert(heap.more()); - - if(heap.top().d_areFixing){ - fixesRemaining--; - negErrorChange++; - }else{ - brokenInBlock++; - } - heap.pop_heap(); - const DeltaRational& blockValue = (*heap.end()).d_diff; - - while(heap.more()){ - const Border& top = heap.top(); - if(blockValue == top.d_diff){ - // belongs to the block - if(top.d_areFixing){ - fixesRemaining--; - negErrorChange++; - }else{ - brokenInBlock++; - } - heap.pop_heap(); - }else{ - // does not belong to the block - Assert((heap.direction() > 0) ? (blockValue < top.d_diff) - : (blockValue > top.d_diff)); - break; - } - } -} - -void LinearEqualityModule::substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult){ - d_tableau.substitutePlusTimesConstant(to, from, mult, d_trackCallback); -} -void LinearEqualityModule::directlyAddToCoefficient(ArithVar row, ArithVar col, const Rational& mult){ - d_tableau.directlyAddToCoefficient(row, col, mult, d_trackCallback); -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/linear_equality.h b/src/theory/arith/linear_equality.h deleted file mode 100644 index ac6b8deae..000000000 --- a/src/theory/arith/linear_equality.h +++ /dev/null @@ -1,762 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This module maintains the relationship between a Tableau and - * PartialModel. - * - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This maintains the relationship needed by the SimplexDecisionProcedure. - * - * In the language of Simplex for DPLL(T), this provides: - * - update() - * - pivotAndUpdate() - * - * This class also provides utility functions that require - * using both the Tableau and PartialModel. - */ - -#include "cvc5_private.h" - -#pragma once - -#include "options/arith_options.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/constraint_forward.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/partial_model.h" -#include "theory/arith/simplex_update.h" -#include "theory/arith/tableau.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -struct Border{ - // The constraint for the border - ConstraintP d_bound; - - // The change to the nonbasic to reach the border - DeltaRational d_diff; - - // Is reach this value fixing the constraint - // or is going past this value hurting the constraint - bool d_areFixing; - - // Entry into the tableau - const Tableau::Entry* d_entry; - - // Was this an upper bound or a lower bound? - bool d_upperbound; - - Border(): - d_bound(NullConstraint) // ignore the other values - {} - - Border(ConstraintP l, const DeltaRational& diff, bool areFixing, const Tableau::Entry* en, bool ub): - d_bound(l), d_diff(diff), d_areFixing(areFixing), d_entry(en), d_upperbound(ub) - {} - - Border(ConstraintP l, const DeltaRational& diff, bool areFixing, bool ub): - d_bound(l), d_diff(diff), d_areFixing(areFixing), d_entry(NULL), d_upperbound(ub) - {} - bool operator<(const Border& other) const{ - return d_diff < other.d_diff; - } - - /** d_lim is the nonbasic variable's own bound. */ - bool ownBorder() const { return d_entry == NULL; } - - bool isZero() const { return d_diff.sgn() == 0; } - static bool nonZero(const Border& b) { return !b.isZero(); } - - const Rational& getCoefficient() const { - Assert(!ownBorder()); - return d_entry->getCoefficient(); - } - void output(std::ostream& out) const; -}; - -inline std::ostream& operator<<(std::ostream& out, const Border& b){ - b.output(out); - return out; -} - -typedef std::vector BorderVec; - -class BorderHeap { - const int d_dir; - - class BorderHeapCmp { - private: - int d_nbDirection; - public: - BorderHeapCmp(int dir): d_nbDirection(dir){} - bool operator()(const Border& a, const Border& b) const{ - if(d_nbDirection > 0){ - // if nb is increasing, - // this needs to act like a max - // in order to have a min heap - return b < a; - }else{ - // if nb is decreasing, - // this needs to act like a min - // in order to have a max heap - return a < b; - } - } - }; - const BorderHeapCmp d_cmp; - - BorderVec d_vec; - - BorderVec::iterator d_begin; - - /** - * Once this is initialized the top of the heap will always - * be at d_end - 1 - */ - BorderVec::iterator d_end; - - int d_possibleFixes; - int d_numZeroes; - -public: - BorderHeap(int dir) - : d_dir(dir), d_cmp(dir), d_possibleFixes(0), d_numZeroes(0) - {} - - void push_back(const Border& b){ - d_vec.push_back(b); - if(b.d_areFixing){ - d_possibleFixes++; - } - if(b.d_diff.sgn() == 0){ - d_numZeroes++; - } - } - - int numZeroes() const { return d_numZeroes; } - int possibleFixes() const { return d_possibleFixes; } - int direction() const { return d_dir; } - - void make_heap(){ - d_begin = d_vec.begin(); - d_end = d_vec.end(); - std::make_heap(d_begin, d_end, d_cmp); - } - - void dropNonZeroes(){ - d_vec.erase(std::remove_if(d_vec.begin(), d_vec.end(), &Border::nonZero), - d_vec.end()); - } - - const Border& top() const { - Assert(more()); - return *d_begin; - } - void pop_heap(){ - Assert(more()); - - std::pop_heap(d_begin, d_end, d_cmp); - --d_end; - } - - BorderVec::const_iterator end() const{ - return BorderVec::const_iterator(d_end); - } - BorderVec::const_iterator begin() const{ - return BorderVec::const_iterator(d_begin); - } - - inline bool more() const{ return d_begin != d_end; } - - inline bool empty() const{ return d_vec.empty(); } - - void clear(){ - d_possibleFixes = 0; - d_numZeroes = 0; - d_vec.clear(); - } -}; - - -class LinearEqualityModule { -public: - typedef ArithVar (LinearEqualityModule::*VarPreferenceFunction)(ArithVar, ArithVar) const; - - - typedef bool (LinearEqualityModule::*UpdatePreferenceFunction)(const UpdateInfo&, const UpdateInfo&) const; - - -private: - /** - * Manages information about the assignment and upper and lower bounds on the - * variables. - */ - ArithVariables& d_variables; - - /** Reference to the Tableau to operate upon. */ - Tableau& d_tableau; - - /** Called whenever the value of a basic variable is updated. */ - BasicVarModelUpdateCallBack d_basicVariableUpdates; - - BorderHeap d_increasing; - BorderHeap d_decreasing; - std::optional d_upperBoundDifference; - std::optional d_lowerBoundDifference; - - Rational d_one; - Rational d_negOne; -public: - - /** - * Initializes a LinearEqualityModule with a partial model, a tableau, - * and a callback function for when basic variables update their values. - */ - LinearEqualityModule(ArithVariables& vars, Tableau& t, BoundInfoMap& boundTracking, BasicVarModelUpdateCallBack f); - - /** - * Updates the assignment of a nonbasic variable x_i to v. - * Also updates the assignment of basic variables accordingly. - */ - void update(ArithVar x_i, const DeltaRational& v){ - if(d_areTracking){ - updateTracked(x_i,v); - }else{ - updateUntracked(x_i,v); - } - } - - /** Specialization of update if the module is not tracking yet (for Assert*). */ - void updateUntracked(ArithVar x_i, const DeltaRational& v); - - /** Specialization of update if the module is not tracking yet (for Simplex). */ - void updateTracked(ArithVar x_i, const DeltaRational& v); - - - /** - * Updates the value of a basic variable x_i to v, - * and then pivots x_i with the nonbasic variable in its row x_j. - * Updates the assignment of the other basic variables accordingly. - */ - void pivotAndUpdate(ArithVar x_i, ArithVar x_j, const DeltaRational& v); - - ArithVariables& getVariables() const{ return d_variables; } - Tableau& getTableau() const{ return d_tableau; } - - /** - * Updates every non-basic to reflect the assignment in many. - * For use with ApproximateSimplex. - */ - void updateMany(const DenseMap& many); - void forceNewBasis(const DenseSet& newBasis); - void applySolution(const DenseSet& newBasis, const DenseMap& newValues); - - - /** - * Returns a pointer to the first Tableau entry on the row ridx that does not - * have an either a lower bound/upper bound for proving a bound on skip. - * The variable skip is always excluded. Returns NULL if there is no such element. - * - * If skip == ARITHVAR_SENTINEL, this is equivalent to considering the whole row. - */ - const Tableau::Entry* rowLacksBound(RowIndex ridx, bool upperBound, ArithVar skip); - - - void startTrackingBoundCounts(); - void stopTrackingBoundCounts(); - - - void includeBoundUpdate(ArithVar nb, const BoundsInfo& prev); - - - uint32_t updateProduct(const UpdateInfo& inf) const; - - inline bool minNonBasicVarOrder(const UpdateInfo& a, const UpdateInfo& b) const{ - return a.nonbasic() >= b.nonbasic(); - } - - /** - * Prefer the update that touch the fewest entries in the matrix. - * - * The intuition is that this operation will be cheaper. - * This strongly biases the system towards updates instead of pivots. - */ - inline bool minProduct(const UpdateInfo& a, const UpdateInfo& b) const{ - uint32_t aprod = updateProduct(a); - uint32_t bprod = updateProduct(b); - - if(aprod == bprod){ - return minNonBasicVarOrder(a,b); - }else{ - return aprod > bprod; - } - } - inline bool constrainedMin(const UpdateInfo& a, const UpdateInfo& b) const{ - if(a.describesPivot() && b.describesPivot()){ - bool aAtBounds = basicsAtBounds(a); - bool bAtBounds = basicsAtBounds(b); - if(aAtBounds != bAtBounds){ - return bAtBounds; - } - } - return minProduct(a,b); - } - - /** - * If both a and b are pivots, prefer the pivot with the leaving variables that has equal bounds. - * The intuition is that such variables will be less likely to lead to future problems. - */ - inline bool preferFrozen(const UpdateInfo& a, const UpdateInfo& b) const { - if(a.describesPivot() && b.describesPivot()){ - bool aFrozen = d_variables.boundsAreEqual(a.leaving()); - bool bFrozen = d_variables.boundsAreEqual(b.leaving()); - - if(aFrozen != bFrozen){ - return bFrozen; - } - } - return constrainedMin(a,b); - } - - /** - * Prefer pivots with entering variables that do not have bounds. - * The intuition is that such variables will be less likely to lead to future problems. - */ - bool preferNeitherBound(const UpdateInfo& a, const UpdateInfo& b) const { - if(d_variables.hasEitherBound(a.nonbasic()) == d_variables.hasEitherBound(b.nonbasic())){ - return preferFrozen(a,b); - }else{ - return d_variables.hasEitherBound(a.nonbasic()); - } - } - - bool modifiedBlands(const UpdateInfo& a, const UpdateInfo& b) const { - Assert(a.focusDirection() == 0 && b.focusDirection() == 0); - Assert(a.describesPivot()); - Assert(b.describesPivot()); - if(a.nonbasic() == b.nonbasic()){ - bool aIsZero = a.nonbasicDelta().sgn() == 0; - bool bIsZero = b.nonbasicDelta().sgn() == 0; - - if((aIsZero || bIsZero) && (!aIsZero || !bIsZero)){ - return bIsZero; - }else{ - return a.leaving() >= b.leaving(); - } - }else{ - return a.nonbasic() > b.nonbasic(); - } - } - - template - bool preferWitness(const UpdateInfo& a, const UpdateInfo& b) const{ - WitnessImprovement aImp = a.getWitness(!heuristic); - WitnessImprovement bImp = b.getWitness(!heuristic); - - if(aImp == bImp){ - switch(aImp){ - case ConflictFound: - return preferNeitherBound(a,b); - case ErrorDropped: - if(a.errorsChange() == b.errorsChange()){ - return preferNeitherBound(a,b); - }else{ - return a.errorsChange() > b.errorsChange(); - } - case FocusImproved: - return preferNeitherBound(a,b); - case BlandsDegenerate: - Assert(a.describesPivot()); - Assert(b.describesPivot()); - Assert(a.focusDirection() == 0 && b.focusDirection() == 0); - return modifiedBlands(a,b); - case HeuristicDegenerate: - Assert(a.describesPivot()); - Assert(b.describesPivot()); - Assert(a.focusDirection() == 0 && b.focusDirection() == 0); - return preferNeitherBound(a,b); - case AntiProductive: - return minNonBasicVarOrder(a, b); - // Not valid responses - case Degenerate: - case FocusShrank: - Unreachable(); - } - Unreachable(); - }else{ - return aImp > bImp; - } - } - -private: - - /** - * This maps each row index to its relevant bounds info. - * This tracks the count for how many variables on a row have bounds - * and how many are assigned at their bounds. - */ - BoundInfoMap& d_btracking; - bool d_areTracking; - -public: - /** - * The constraint on a basic variable b is implied by the constraints - * on its row. This is a wrapper for propagateRow(). - */ - void propagateBasicFromRow(ConstraintP c, bool produceProofs); - - /** - * Let v be the variable for the constraint c. - * Exports either the explanation of an upperbound or a lower bound - * of v using the other variables in the row. - * - * If farkas != RationalVectorPSentinel, this function additionally - * stores the farkas coefficients of the constraints stored in into. - * Position 0 is the coefficient of v. - * Position i > 0, corresponds to the order of the other constraints. - */ - void propagateRow(ConstraintCPVec& into, - RowIndex ridx, - bool rowUp, - ConstraintP c, - RationalVectorP farkas); - - /** - * Computes the value of a basic variable using the assignments - * of the values of the variables in the basic variable's row tableau. - * This can compute the value using either: - * - the the current assignment (useSafe=false) or - * - the safe assignment (useSafe = true). - */ - DeltaRational computeRowValue(ArithVar x, bool useSafe) const; - - /** - * A PreferenceFunction takes a const ref to the SimplexDecisionProcedure, - * and 2 ArithVar variables and returns one of the ArithVar variables - * potentially using the internals of the SimplexDecisionProcedure. - */ - - ArithVar noPreference(ArithVar x, ArithVar y) const { return x; } - - /** - * minVarOrder is a PreferenceFunction for selecting the smaller of the 2 - * ArithVars. This PreferenceFunction is used during the VarOrder stage of - * findModel. - */ - ArithVar minVarOrder(ArithVar x, ArithVar y) const; - - /** - * minColLength is a PreferenceFunction for selecting the variable with the - * smaller row count in the tableau. - * - * This is a heuristic rule and should not be used during the VarOrder - * stage of findModel. - */ - ArithVar minColLength(ArithVar x, ArithVar y) const; - - /** - * minRowLength is a PreferenceFunction for selecting the variable with the - * smaller row count in the tableau. - * - * This is a heuristic rule and should not be used during the VarOrder - * stage of findModel. - */ - ArithVar minRowLength(ArithVar x, ArithVar y) const; - - /** - * minBoundAndRowCount is a PreferenceFunction for preferring a variable - * without an asserted bound over variables with an asserted bound. - * If both have bounds or both do not have bounds, - * the rule falls back to minRowCount(...). - * - * This is a heuristic rule and should not be used during the VarOrder - * stage of findModel. - */ - ArithVar minBoundAndColLength(ArithVar x, ArithVar y) const; - - template - inline bool isAcceptableSlack(int sgn, ArithVar nonbasic) const - { - return (above && sgn < 0 && d_variables.strictlyBelowUpperBound(nonbasic)) - || (above && sgn > 0 && d_variables.strictlyAboveLowerBound(nonbasic)) - || (!above && sgn > 0 - && d_variables.strictlyBelowUpperBound(nonbasic)) - || (!above && sgn < 0 - && d_variables.strictlyAboveLowerBound(nonbasic)); - } - - /** - * Given the basic variable x_i, - * this function finds the smallest nonbasic variable x_j in the row of x_i - * in the tableau that can "take up the slack" to let x_i satisfy its bounds. - * This returns ARITHVAR_SENTINEL if none exists. - * - * More formally one of the following conditions must be satisfied: - * - lowerBound && a_ij < 0 && assignment(x_j) < upperbound(x_j) - * - lowerBound && a_ij > 0 && assignment(x_j) > lowerbound(x_j) - * - !lowerBound && a_ij > 0 && assignment(x_j) < upperbound(x_j) - * - !lowerBound && a_ij < 0 && assignment(x_j) > lowerbound(x_j) - * - */ - template ArithVar selectSlack(ArithVar x_i, VarPreferenceFunction pf) const; - ArithVar selectSlackLowerBound(ArithVar x_i, VarPreferenceFunction pf) const { - return selectSlack(x_i, pf); - } - ArithVar selectSlackUpperBound(ArithVar x_i, VarPreferenceFunction pf) const { - return selectSlack(x_i, pf); - } - - const Tableau::Entry* selectSlackEntry(ArithVar x_i, bool above) const; - - inline bool rowIndexIsTracked(RowIndex ridx) const { - return d_btracking.isKey(ridx); - } - inline bool basicIsTracked(ArithVar v) const { - return rowIndexIsTracked(d_tableau.basicToRowIndex(v)); - } - void trackRowIndex(RowIndex ridx); - void stopTrackingRowIndex(RowIndex ridx){ - Assert(rowIndexIsTracked(ridx)); - d_btracking.remove(ridx); - } - - /** - * If the pivot described in u were performed, - * then the row would qualify as being either at the minimum/maximum - * to the non-basics being at their bounds. - * The minimum/maximum is determined by the direction the non-basic is changing. - */ - bool basicsAtBounds(const UpdateInfo& u) const; - -private: - - /** - * Recomputes the bound info for a row using either the information - * in the bounds queue or the current information. - * O(row length of ridx) - */ - BoundsInfo computeRowBoundInfo(RowIndex ridx, bool inQueue) const; - -public: - /** Debug only routine. */ - BoundCounts debugBasicAtBoundCount(ArithVar x_i) const; - - /** Track the effect of the change of coefficient for bound counting. */ - void trackingCoefficientChange(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn); - - /** Track the effect of multiplying a row by a sign for bound counting. */ - void trackingMultiplyRow(RowIndex ridx, int sgn); - - /** Count for how many on a row have *an* upper/lower bounds. */ - BoundCounts hasBoundCount(RowIndex ri) const { - Assert(d_variables.boundsQueueEmpty()); - return d_btracking[ri].hasBounds(); - } - - /** - * Are there any non-basics on x_i's row that are not at - * their respective lower bounds (mod sgns). - * O(1) time due to the atBound() count. - */ - bool nonbasicsAtLowerBounds(ArithVar x_i) const; - - /** - * Are there any non-basics on x_i's row that are not at - * their respective upper bounds (mod sgns). - * O(1) time due to the atBound() count. - */ - bool nonbasicsAtUpperBounds(ArithVar x_i) const; - -private: - class TrackingCallback : public CoefficientChangeCallback { - private: - LinearEqualityModule* d_linEq; - public: - TrackingCallback(LinearEqualityModule* le) : d_linEq(le) {} - void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) override - { - d_linEq->trackingCoefficientChange(ridx, nb, oldSgn, currSgn); - } - void multiplyRow(RowIndex ridx, int sgn) override - { - d_linEq->trackingMultiplyRow(ridx, sgn); - } - bool canUseRow(RowIndex ridx) const override - { - ArithVar basic = d_linEq->getTableau().rowIndexToBasic(ridx); - return d_linEq->basicIsTracked(basic); - } - } d_trackCallback; - - /** - * Selects the constraint for the variable v on the row for basic - * with the weakest possible constraint that is consistent with the surplus - * surplus. - */ - ConstraintP weakestExplanation(bool aboveUpper, DeltaRational& surplus, ArithVar v, - const Rational& coeff, bool& anyWeakening, ArithVar basic) const; - -public: - /** - * Constructs a minimally weak conflict for the basic variable basicVar. - * - * Returns a constraint that is now in conflict. - */ - ConstraintCP minimallyWeakConflict(bool aboveUpper, ArithVar basicVar, FarkasConflictBuilder& rc) const; - - /** - * Given a basic variable that is know to have a conflict on it, - * construct and return a conflict. - * Follows section 4.2 in the CAV06 paper. - */ - inline ConstraintCP generateConflictAboveUpperBound(ArithVar conflictVar, FarkasConflictBuilder& rc) const { - return minimallyWeakConflict(true, conflictVar, rc); - } - - inline ConstraintCP generateConflictBelowLowerBound(ArithVar conflictVar, FarkasConflictBuilder& rc) const { - return minimallyWeakConflict(false, conflictVar, rc); - } - - /** - * Computes the sum of the upper/lower bound of row. - * The variable skip is not included in the sum. - */ - DeltaRational computeRowBound(RowIndex ridx, bool rowUb, ArithVar skip) const; - -public: - void substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult); - void directlyAddToCoefficient(ArithVar row, ArithVar col, const Rational& mult); - - - /** - * Checks to make sure the assignment is consistent with the tableau. - * This code is for debugging. - */ - void debugCheckTableau(); - - void debugCheckTracking(); - - /** Debugging information for a pivot. */ - void debugPivot(ArithVar x_i, ArithVar x_j); - - ArithVar minBy(const ArithVarVec& vec, VarPreferenceFunction pf) const; - - /** - * Returns true if there would be a conflict on this row after a pivot - * and update using its basic variable and one of the non-basic variables on - * the row. - */ - bool willBeInConflictAfterPivot(const Tableau::Entry& entry, const DeltaRational& nbDiff, bool bToUB) const; - UpdateInfo mkConflictUpdate(const Tableau::Entry& entry, bool ub) const; - - /** - * Looks more an update for fcSimplex on the nonbasic variable nb with the focus coefficient. - */ - UpdateInfo speculativeUpdate(ArithVar nb, const Rational& focusCoeff, UpdatePreferenceFunction pref); - -private: - - /** - * Examines the effects of pivoting the entries column variable - * with the row's basic variable and setting the variable s.t. - * the basic variable is equal to one of its bounds. - * - * If ub, then the basic variable will be equal its upper bound. - * If not ub,then the basic variable will be equal its lower bound. - * - * Returns iff this row will be in conflict after the pivot. - * - * If this is false, add the bound to the relevant heap. - * If the bound is +/-infinity, this is ignored. - - * - * Returns true if this would be a conflict. - * If it returns false, this - */ - bool accumulateBorder(const Tableau::Entry& entry, bool ub); - - void handleBorders(UpdateInfo& selected, ArithVar nb, const Rational& focusCoeff, BorderHeap& heap, int minimumFixes, UpdatePreferenceFunction pref); - void pop_block(BorderHeap& heap, int& brokenInBlock, int& fixesRemaining, int& negErrorChange); - void clearSpeculative(); - Rational updateCoefficient(BorderVec::const_iterator startBlock, BorderVec::const_iterator endBlock); - -private: - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - IntStat d_statPivots, d_statUpdates; - TimerStat d_pivotTime; - TimerStat d_adjTime; - - IntStat d_weakeningAttempts, d_weakeningSuccesses, d_weakenings; - TimerStat d_weakenTime; - TimerStat d_forceTime; - - Statistics(); - }; - mutable Statistics d_statistics; - -};/* class LinearEqualityModule */ - -struct Cand { - ArithVar d_nb; - uint32_t d_penalty; - int d_sgn; - const Rational* d_coeff; - - Cand(ArithVar nb, uint32_t penalty, int s, const Rational* c) : - d_nb(nb), d_penalty(penalty), d_sgn(s), d_coeff(c){} -}; - - -class CompPenaltyColLength { -private: - LinearEqualityModule* d_mod; - const bool d_havePenalties; - - public: - CompPenaltyColLength(LinearEqualityModule* mod, bool havePenalties) - : d_mod(mod), d_havePenalties(havePenalties) - { - } - - bool operator()(const Cand& x, const Cand& y) const { - if (x.d_penalty == y.d_penalty || !d_havePenalties) - { - return x.d_nb == d_mod->minBoundAndColLength(x.d_nb,y.d_nb); - } - else - { - return x.d_penalty < y.d_penalty; - } - } -}; - -class UpdateTrackingCallback : public BoundUpdateCallback { -private: - LinearEqualityModule* d_mod; -public: - UpdateTrackingCallback(LinearEqualityModule* mod): d_mod(mod){} - void operator()(ArithVar v, const BoundsInfo& bi) override - { - d_mod->includeBoundUpdate(v, bi); - } -}; - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/matrix.cpp b/src/theory/arith/matrix.cpp deleted file mode 100644 index bcd4eb880..000000000 --- a/src/theory/arith/matrix.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * Sparse matrix implementations for different types. - */ - -#include "theory/arith/matrix.h" - -using namespace std; -namespace cvc5::internal { -namespace theory { -namespace arith { - -void NoEffectCCCB::update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) {} -void NoEffectCCCB::multiplyRow(RowIndex ridx, int sgn){} -bool NoEffectCCCB::canUseRow(RowIndex ridx) const { return false; } - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/matrix.h b/src/theory/arith/matrix.h deleted file mode 100644 index 95ce0225f..000000000 --- a/src/theory/arith/matrix.h +++ /dev/null @@ -1,1002 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * Sparse matrix implementations for different types. - * - * Sparse matrix implementations for different types. - * This defines Matrix, IntegerEqualityTables and Tableau. - */ - -#include "cvc5_private.h" - -#pragma once - -#include -#include -#include - -#include "base/output.h" -#include "theory/arith/arithvar.h" -#include "util/dense_map.h" -#include "util/index.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -typedef Index EntryID; -const EntryID ENTRYID_SENTINEL = std::numeric_limits::max(); - -typedef Index RowIndex; -const RowIndex ROW_INDEX_SENTINEL = std::numeric_limits::max(); - -class CoefficientChangeCallback { -public: - virtual ~CoefficientChangeCallback() {} - virtual void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) = 0; - virtual void multiplyRow(RowIndex ridx, int Sgn) = 0; - virtual bool canUseRow(RowIndex ridx) const = 0; -}; - -class NoEffectCCCB : public CoefficientChangeCallback { -public: - void update(RowIndex ridx, ArithVar nb, int oldSgn, int currSgn) override; - void multiplyRow(RowIndex ridx, int Sgn) override; - bool canUseRow(RowIndex ridx) const override; -}; - -template -class MatrixEntry { -private: - RowIndex d_rowIndex; - ArithVar d_colVar; - - EntryID d_nextRow; - EntryID d_nextCol; - - EntryID d_prevRow; - EntryID d_prevCol; - - T d_coefficient; - -public: - MatrixEntry(): - d_rowIndex(ROW_INDEX_SENTINEL), - d_colVar(ARITHVAR_SENTINEL), - d_nextRow(ENTRYID_SENTINEL), - d_nextCol(ENTRYID_SENTINEL), - d_prevRow(ENTRYID_SENTINEL), - d_prevCol(ENTRYID_SENTINEL), - d_coefficient() - {} - - MatrixEntry(RowIndex row, ArithVar col, const T& coeff): - d_rowIndex(row), - d_colVar(col), - d_nextRow(ENTRYID_SENTINEL), - d_nextCol(ENTRYID_SENTINEL), - d_prevRow(ENTRYID_SENTINEL), - d_prevCol(ENTRYID_SENTINEL), - d_coefficient(coeff) - {} - -private: - bool unusedConsistent() const { - return - (d_rowIndex == ROW_INDEX_SENTINEL && d_colVar == ARITHVAR_SENTINEL) || - (d_rowIndex != ROW_INDEX_SENTINEL && d_colVar != ARITHVAR_SENTINEL); - } - -public: - - EntryID getNextRowEntryID() const { - return d_nextRow; - } - - EntryID getNextColEntryID() const { - return d_nextCol; - } - EntryID getPrevRowEntryID() const { - return d_prevRow; - } - - EntryID getPrevColEntryID() const { - return d_prevCol; - } - - void setNextRowEntryID(EntryID id) { - d_nextRow = id; - } - void setNextColEntryID(EntryID id) { - d_nextCol = id; - } - void setPrevRowEntryID(EntryID id) { - d_prevRow = id; - } - void setPrevColEntryID(EntryID id) { - d_prevCol = id; - } - - RowIndex getRowIndex() const{ - return d_rowIndex; - } - - ArithVar getColVar() const{ - return d_colVar; - } - - const T& getCoefficient() const { - return d_coefficient; - } - - T& getCoefficient(){ - return d_coefficient; - } - - void setCoefficient(const T& t){ - d_coefficient = t; - } - - void markBlank() { - d_rowIndex = ROW_INDEX_SENTINEL; - d_colVar = ARITHVAR_SENTINEL; - } - - bool blank() const{ - Assert(unusedConsistent()); - - return d_rowIndex == ROW_INDEX_SENTINEL; - } -}; /* class MatrixEntry */ - -template -class MatrixEntryVector { -private: - typedef MatrixEntry EntryType; - typedef std::vector EntryArray; - - EntryArray d_entries; - std::queue d_freedEntries; - - uint32_t d_size; - -public: - MatrixEntryVector(): - d_entries(), d_freedEntries(), d_size(0) - {} - - const EntryType& operator[](EntryID id) const{ - Assert(inBounds(id)); - return d_entries[id]; - } - - EntryType& get(EntryID id){ - Assert(inBounds(id)); - return d_entries[id]; - } - - void freeEntry(EntryID id){ - Assert(get(id).blank()); - Assert(d_size > 0); - - d_freedEntries.push(id); - --d_size; - } - - EntryID newEntry(){ - EntryID newId; - if(d_freedEntries.empty()){ - newId = d_entries.size(); - d_entries.push_back(MatrixEntry()); - }else{ - newId = d_freedEntries.front(); - d_freedEntries.pop(); - } - ++d_size; - return newId; - } - - uint32_t size() const{ return d_size; } - uint32_t capacity() const{ return d_entries.capacity(); } - - -private: - bool inBounds(EntryID id) const{ - return id < d_entries.size(); - } -}; /* class MatrixEntryVector */ - -template -class MatrixVector { -private: - EntryID d_head; - uint32_t d_size; - - MatrixEntryVector* d_entries; - - class Iterator { - private: - EntryID d_curr; - const MatrixEntryVector* d_entries; - - public: - Iterator(EntryID start, const MatrixEntryVector* entries) : - d_curr(start), d_entries(entries) - {} - - public: - - EntryID getID() const { - return d_curr; - } - - const MatrixEntry& operator*() const{ - Assert(!atEnd()); - return (*d_entries)[d_curr]; - } - - Iterator& operator++(){ - Assert(!atEnd()); - const MatrixEntry& entry = (*d_entries)[d_curr]; - d_curr = isRow ? entry.getNextRowEntryID() : entry.getNextColEntryID(); - return *this; - } - - bool atEnd() const { - return d_curr == ENTRYID_SENTINEL; - } - - bool operator==(const Iterator& i) const{ - return d_curr == i.d_curr && d_entries == i.d_entries; - } - - bool operator!=(const Iterator& i) const{ - return !(d_curr == i.d_curr && d_entries == i.d_entries); - } - }; /* class MatrixVector::Iterator */ - -public: - MatrixVector(MatrixEntryVector* mev) - : d_head(ENTRYID_SENTINEL), d_size(0), d_entries(mev) - {} - - MatrixVector(EntryID head, uint32_t size, MatrixEntryVector* mev) - : d_head(head), d_size(size), d_entries(mev) - {} - - typedef Iterator const_iterator; - const_iterator begin() const { - return Iterator(d_head, d_entries); - } - const_iterator end() const { - return Iterator(ENTRYID_SENTINEL, d_entries); - } - - EntryID getHead() const { return d_head; } - - uint32_t getSize() const { return d_size; } - - void insert(EntryID newId){ - if(isRow){ - d_entries->get(newId).setNextRowEntryID(d_head); - - if(d_head != ENTRYID_SENTINEL){ - d_entries->get(d_head).setPrevRowEntryID(newId); - } - }else{ - d_entries->get(newId).setNextColEntryID(d_head); - - if(d_head != ENTRYID_SENTINEL){ - d_entries->get(d_head).setPrevColEntryID(newId); - } - } - - d_head = newId; - ++d_size; - } - void remove(EntryID id){ - Assert(d_size > 0); - --d_size; - if(isRow){ - EntryID prevRow = d_entries->get(id).getPrevRowEntryID(); - EntryID nextRow = d_entries->get(id).getNextRowEntryID(); - - if(d_head == id){ - d_head = nextRow; - } - if(prevRow != ENTRYID_SENTINEL){ - d_entries->get(prevRow).setNextRowEntryID(nextRow); - } - if(nextRow != ENTRYID_SENTINEL){ - d_entries->get(nextRow).setPrevRowEntryID(prevRow); - } - }else{ - EntryID prevCol = d_entries->get(id).getPrevColEntryID(); - EntryID nextCol = d_entries->get(id).getNextColEntryID(); - - if(d_head == id){ - d_head = nextCol; - } - - if(prevCol != ENTRYID_SENTINEL){ - d_entries->get(prevCol).setNextColEntryID(nextCol); - } - if(nextCol != ENTRYID_SENTINEL){ - d_entries->get(nextCol).setPrevColEntryID(prevCol); - } - } - } -}; /* class MatrixVector */ - -template - class RowVector : public MatrixVector -{ -private: - typedef MatrixVector SuperT; -public: - typedef typename SuperT::const_iterator const_iterator; - - RowVector(MatrixEntryVector* mev) : SuperT(mev){} - RowVector(EntryID head, uint32_t size, MatrixEntryVector* mev) - : SuperT(head, size, mev){} -};/* class RowVector */ - -template - class ColumnVector : public MatrixVector -{ -private: - typedef MatrixVector SuperT; -public: - typedef typename SuperT::const_iterator const_iterator; - - ColumnVector(MatrixEntryVector* mev) : SuperT(mev){} - ColumnVector(EntryID head, uint32_t size, MatrixEntryVector* mev) - : SuperT(head, size, mev){} -};/* class ColumnVector */ - -template -class Matrix { -public: - typedef MatrixEntry Entry; - -protected: - typedef cvc5::internal::theory::arith::RowVector RowVectorT; - typedef cvc5::internal::theory::arith::ColumnVector ColumnVectorT; - -public: - typedef typename RowVectorT::const_iterator RowIterator; - typedef typename ColumnVectorT::const_iterator ColIterator; - -protected: - // RowTable : RowID |-> RowVector - typedef std::vector< RowVectorT > RowTable; - RowTable d_rows; - - // ColumnTable : ArithVar |-> ColumnVector - typedef std::vector< ColumnVectorT > ColumnTable; - ColumnTable d_columns; - - /* The merge buffer is used to store a row in order to optimize row addition. */ - typedef std::pair PosUsedPair; - typedef DenseMap< PosUsedPair > RowToPosUsedPairMap; - RowToPosUsedPairMap d_mergeBuffer; - - /* The row that is in the merge buffer. */ - RowIndex d_rowInMergeBuffer; - - uint32_t d_entriesInUse; - MatrixEntryVector d_entries; - - std::vector d_pool; - - T d_zero; - -public: - /** - * Constructs an empty Matrix. - */ - Matrix() - : d_rows(), - d_columns(), - d_mergeBuffer(), - d_rowInMergeBuffer(ROW_INDEX_SENTINEL), - d_entriesInUse(0), - d_entries(), - d_zero(0) - {} - - Matrix(const T& zero) - : d_rows(), - d_columns(), - d_mergeBuffer(), - d_rowInMergeBuffer(ROW_INDEX_SENTINEL), - d_entriesInUse(0), - d_entries(), - d_zero(zero) - {} - - Matrix(const Matrix& m) - : d_rows(), - d_columns(), - d_mergeBuffer(m.d_mergeBuffer), - d_rowInMergeBuffer(m.d_rowInMergeBuffer), - d_entriesInUse(m.d_entriesInUse), - d_entries(m.d_entries), - d_zero(m.d_zero) - { - d_columns.clear(); - for(typename ColumnTable::const_iterator c=m.d_columns.begin(), cend = m.d_columns.end(); c!=cend; ++c){ - const ColumnVectorT& col = *c; - d_columns.push_back(ColumnVectorT(col.getHead(),col.getSize(),&d_entries)); - } - d_rows.clear(); - for(typename RowTable::const_iterator r=m.d_rows.begin(), rend = m.d_rows.end(); r!=rend; ++r){ - const RowVectorT& row = *r; - d_rows.push_back(RowVectorT(row.getHead(),row.getSize(),&d_entries)); - } - } - - Matrix& operator=(const Matrix& m){ - d_mergeBuffer = (m.d_mergeBuffer); - d_rowInMergeBuffer = (m.d_rowInMergeBuffer); - d_entriesInUse = (m.d_entriesInUse); - d_entries = (m.d_entries); - d_zero = (m.d_zero); - d_columns.clear(); - for(typename ColumnTable::const_iterator c=m.d_columns.begin(), cend = m.d_columns.end(); c!=cend; ++c){ - const ColumnVector& col = *c; - d_columns.push_back(ColumnVector(col.getHead(), col.getSize(), &d_entries)); - } - d_rows.clear(); - for(typename RowTable::const_iterator r=m.d_rows.begin(), rend = m.d_rows.end(); r!=rend; ++r){ - const RowVector& row = *r; - d_rows.push_back(RowVector(row.getHead(), row.getSize(), &d_entries)); - } - return *this; - } - -protected: - - void addEntry(RowIndex row, ArithVar col, const T& coeff){ - Trace("tableau") << "addEntry(" << row << "," << col <<"," << coeff << ")" << std::endl; - - Assert(coeff != 0); - Assert(row < d_rows.size()); - Assert(col < d_columns.size()); - - EntryID newId = d_entries.newEntry(); - Entry& newEntry = d_entries.get(newId); - newEntry = Entry(row, col, coeff); - - Assert(newEntry.getCoefficient() != 0); - - ++d_entriesInUse; - - d_rows[row].insert(newId); - d_columns[col].insert(newId); - } - - void removeEntry(EntryID id){ - Assert(d_entriesInUse > 0); - --d_entriesInUse; - - Entry& entry = d_entries.get(id); - - RowIndex ridx = entry.getRowIndex(); - ArithVar col = entry.getColVar(); - - Assert(d_rows[ridx].getSize() > 0); - Assert(d_columns[col].getSize() > 0); - - d_rows[ridx].remove(id); - d_columns[col].remove(id); - - entry.markBlank(); - - d_entries.freeEntry(id); - } - - private: - RowIndex requestRowIndex(){ - if(d_pool.empty()){ - RowIndex ridx = d_rows.size(); - d_rows.push_back(RowVectorT(&d_entries)); - return ridx; - }else{ - RowIndex rid = d_pool.back(); - d_pool.pop_back(); - return rid; - } - } - - void releaseRowIndex(RowIndex rid){ - d_pool.push_back(rid); - } - -public: - - size_t getNumRows() const { - return d_rows.size(); - } - - size_t getNumColumns() const { - return d_columns.size(); - } - - void increaseSize(){ - d_columns.push_back(ColumnVector(&d_entries)); - } - - void increaseSizeTo(size_t s){ - while(getNumColumns() < s){ - increaseSize(); - } - } - - const RowVector& getRow(RowIndex r) const { - Assert(r < d_rows.size()); - return d_rows[r]; - } - - const ColumnVector& getColumn(ArithVar v) const { - Assert(v < d_columns.size()); - return d_columns[v]; - } - - uint32_t getRowLength(RowIndex r) const{ - return getRow(r).getSize(); - } - - uint32_t getColLength(ArithVar x) const{ - return getColumn(x).getSize(); - } - - /** - * Adds a row to the matrix. - * The new row is equivalent to: - * \f$\sum_i\f$ coeffs[i] * variables[i] - */ - RowIndex addRow(const std::vector& coeffs, - const std::vector& variables){ - - RowIndex ridx = requestRowIndex(); - - //RowIndex ridx = d_rows.size(); - //d_rows.push_back(RowVectorT(&d_entries)); - - typename std::vector::const_iterator coeffIter = coeffs.begin(); - std::vector::const_iterator varsIter = variables.begin(); - std::vector::const_iterator varsEnd = variables.end(); - - for(; varsIter != varsEnd; ++coeffIter, ++varsIter){ - const T& coeff = *coeffIter; - ArithVar var_i = *varsIter; - Assert(var_i < getNumColumns()); - addEntry(ridx, var_i, coeff); - } - - return ridx; - } - - - void loadRowIntoBuffer(RowIndex rid){ - Assert(d_mergeBuffer.empty()); - Assert(d_rowInMergeBuffer == ROW_INDEX_SENTINEL); - - RowIterator i = getRow(rid).begin(), i_end = getRow(rid).end(); - for(; i != i_end; ++i){ - EntryID id = i.getID(); - const MatrixEntry& entry = *i; - ArithVar colVar = entry.getColVar(); - d_mergeBuffer.set(colVar, std::make_pair(id, false)); - } - - d_rowInMergeBuffer = rid; - } - - void clearBuffer() { - Assert(d_rowInMergeBuffer != ROW_INDEX_SENTINEL); - - d_rowInMergeBuffer = ROW_INDEX_SENTINEL; - d_mergeBuffer.purge(); - } - - /* to *= mult */ - void multiplyRowByConstant(RowIndex to, const T& mult){ - RowIterator i = getRow(to).begin(); - RowIterator i_end = getRow(to).end(); - for( ; i != i_end; ++i){ - EntryID id = i.getID(); - Entry& entry = d_entries.get(id); - T& coeff = entry.getCoefficient(); - coeff *= mult; - } - } - - /** to += mult * from. - * Use the more efficient rowPlusBufferTimesConstant() for - * repeated use. - */ - void rowPlusRowTimesConstant(RowIndex to, RowIndex from, const T& mult){ - Assert(to != from); - loadRowIntoBuffer(from); - rowPlusBufferTimesConstant(to, mult); - clearBuffer(); - } - - /** to += mult * buffer. - * Invalidates coefficients on the row. - * (mult should never be a direct copy of a coefficient!) - */ - void rowPlusBufferTimesConstant(RowIndex to, const T& mult){ - Assert(d_rowInMergeBuffer != ROW_INDEX_SENTINEL); - Assert(to != ROW_INDEX_SENTINEL); - - Trace("tableau") << "rowPlusRowTimesConstant(" - << to << "," << mult << "," << d_rowInMergeBuffer << ")" - << std::endl; - - Assert(debugNoZeroCoefficients(to)); - Assert(debugNoZeroCoefficients(d_rowInMergeBuffer)); - - Assert(mult != 0); - - RowIterator i = getRow(to).begin(); - RowIterator i_end = getRow(to).end(); - while(i != i_end){ - EntryID id = i.getID(); - Entry& entry = d_entries.get(id); - ArithVar colVar = entry.getColVar(); - - ++i; - - if(d_mergeBuffer.isKey(colVar)){ - EntryID bufferEntry = d_mergeBuffer[colVar].first; - Assert(!d_mergeBuffer[colVar].second); - d_mergeBuffer.get(colVar).second = true; - - const Entry& other = d_entries.get(bufferEntry); - T& coeff = entry.getCoefficient(); - coeff += mult * other.getCoefficient(); - - if(coeff.sgn() == 0){ - removeEntry(id); - } - } - } - - i = getRow(d_rowInMergeBuffer).begin(); - i_end = getRow(d_rowInMergeBuffer).end(); - - for(; i != i_end; ++i){ - const Entry& entry = *i; - ArithVar colVar = entry.getColVar(); - - if(d_mergeBuffer[colVar].second){ - d_mergeBuffer.get(colVar).second = false; - }else{ - Assert(!(d_mergeBuffer[colVar]).second); - T newCoeff = mult * entry.getCoefficient(); - addEntry(to, colVar, newCoeff); - } - } - - Assert(mergeBufferIsClear()); - - if(TraceIsOn("matrix")) { printMatrix(); } - } - - /** to += mult * buffer. */ - void rowPlusBufferTimesConstant(RowIndex to, const T& mult, CoefficientChangeCallback& cb){ - Assert(d_rowInMergeBuffer != ROW_INDEX_SENTINEL); - Assert(to != ROW_INDEX_SENTINEL); - - Trace("tableau") << "rowPlusRowTimesConstant(" - << to << "," << mult << "," << d_rowInMergeBuffer << ")" - << std::endl; - - Assert(debugNoZeroCoefficients(to)); - Assert(debugNoZeroCoefficients(d_rowInMergeBuffer)); - - Assert(mult != 0); - - RowIterator i = getRow(to).begin(); - RowIterator i_end = getRow(to).end(); - while(i != i_end){ - EntryID id = i.getID(); - Entry& entry = d_entries.get(id); - ArithVar colVar = entry.getColVar(); - - ++i; - - if(d_mergeBuffer.isKey(colVar)){ - EntryID bufferEntry = d_mergeBuffer[colVar].first; - Assert(!d_mergeBuffer[colVar].second); - d_mergeBuffer.get(colVar).second = true; - - const Entry& other = d_entries.get(bufferEntry); - T& coeff = entry.getCoefficient(); - int coeffOldSgn = coeff.sgn(); - coeff += mult * other.getCoefficient(); - int coeffNewSgn = coeff.sgn(); - - if(coeffOldSgn != coeffNewSgn){ - cb.update(to, colVar, coeffOldSgn, coeffNewSgn); - - if(coeffNewSgn == 0){ - removeEntry(id); - } - } - } - } - - i = getRow(d_rowInMergeBuffer).begin(); - i_end = getRow(d_rowInMergeBuffer).end(); - - for(; i != i_end; ++i){ - const Entry& entry = *i; - ArithVar colVar = entry.getColVar(); - - if(d_mergeBuffer[colVar].second){ - d_mergeBuffer.get(colVar).second = false; - }else{ - Assert(!(d_mergeBuffer[colVar]).second); - T newCoeff = mult * entry.getCoefficient(); - addEntry(to, colVar, newCoeff); - - cb.update(to, colVar, 0, newCoeff.sgn()); - } - } - - Assert(mergeBufferIsClear()); - - if(TraceIsOn("matrix")) { printMatrix(); } - } - - bool mergeBufferIsClear() const{ - RowToPosUsedPairMap::const_iterator i = d_mergeBuffer.begin(); - RowToPosUsedPairMap::const_iterator i_end = d_mergeBuffer.end(); - for(; i != i_end; ++i){ - RowIndex rid = *i; - if(d_mergeBuffer[rid].second){ - return false; - } - } - return true; - } - -protected: - - EntryID findOnRow(RowIndex rid, ArithVar column) const { - RowIterator i = d_rows[rid].begin(), i_end = d_rows[rid].end(); - for(; i != i_end; ++i){ - EntryID id = i.getID(); - const MatrixEntry& entry = *i; - ArithVar colVar = entry.getColVar(); - - if(colVar == column){ - return id; - } - } - return ENTRYID_SENTINEL; - } - - EntryID findOnCol(RowIndex rid, ArithVar column) const{ - ColIterator i = d_columns[column].begin(), i_end = d_columns[column].end(); - for(; i != i_end; ++i){ - EntryID id = i.getID(); - const MatrixEntry& entry = *i; - RowIndex currRow = entry.getRowIndex(); - - if(currRow == rid){ - return id; - } - } - return ENTRYID_SENTINEL; - } - - EntryID findEntryID(RowIndex rid, ArithVar col) const{ - bool colIsShorter = getColLength(col) < getRowLength(rid); - EntryID id = colIsShorter ? findOnCol(rid, col) : findOnRow(rid,col); - return id; - } - MatrixEntry d_failedFind; -public: - - /** If the find fails, isUnused is true on the entry. */ - const MatrixEntry& findEntry(RowIndex rid, ArithVar col) const{ - EntryID id = findEntryID(rid, col); - if(id == ENTRYID_SENTINEL){ - return d_failedFind; - }else{ - return d_entries[id]; - } - } - - /** - * Prints the contents of the Matrix to Trace("matrix") - */ - void printMatrix(std::ostream& out) const { - out << "Matrix::printMatrix" << std::endl; - - for(RowIndex i = 0, N = d_rows.size(); i < N; ++i){ - printRow(i, out); - } - } - void printMatrix() const { - printMatrix(Trace("matrix")); - } - - void printRow(RowIndex rid, std::ostream& out) const { - out << "{" << rid << ":"; - const RowVector& row = getRow(rid); - RowIterator i = row.begin(); - RowIterator i_end = row.end(); - for(; i != i_end; ++i){ - printEntry(*i, out); - out << ","; - } - out << "}" << std::endl; - } - void printRow(RowIndex rid) const { - printRow(rid, Trace("matrix")); - } - - void printEntry(const MatrixEntry& entry, std::ostream& out) const { - out << entry.getColVar() << "*" << entry.getCoefficient(); - } - void printEntry(const MatrixEntry& entry) const { - printEntry(entry, Trace("matrix")); - } -public: - uint32_t size() const { - return d_entriesInUse; - } - uint32_t getNumEntriesInTableau() const { - return d_entries.size(); - } - uint32_t getEntryCapacity() const { - return d_entries.capacity(); - } - - void manipulateRowEntry(RowIndex row, ArithVar col, const T& c, CoefficientChangeCallback& cb){ - int coeffOldSgn; - int coeffNewSgn; - - EntryID id = findEntryID(row, col); - if(id == ENTRYID_SENTINEL){ - coeffOldSgn = 0; - addEntry(row, col, c); - coeffNewSgn = c.sgn(); - }else{ - Entry& e = d_entries.get(id); - T& t = e.getCoefficient(); - coeffOldSgn = t.sgn(); - t += c; - coeffNewSgn = t.sgn(); - } - - if(coeffOldSgn != coeffNewSgn){ - cb.update(row, col, coeffOldSgn, coeffNewSgn); - } - if(coeffNewSgn == 0){ - removeEntry(id); - } - } - - void removeRow(RowIndex rid){ - RowIterator i = getRow(rid).begin(); - RowIterator i_end = getRow(rid).end(); - for(; i != i_end; ++i){ - EntryID id = i.getID(); - removeEntry(id); - } - releaseRowIndex(rid); - } - - double densityMeasure() const{ - Assert(numNonZeroEntriesByRow() == numNonZeroEntries()); - Assert(numNonZeroEntriesByCol() == numNonZeroEntries()); - - uint32_t n = getNumRows(); - if(n == 0){ - return 1.0; - }else { - uint32_t s = numNonZeroEntries(); - uint32_t m = d_columns.size(); - uint32_t divisor = (n *(m - n + 1)); - - Assert(n >= 1); - Assert(m >= n); - Assert(divisor > 0); - Assert(divisor >= s); - - return (double(s)) / divisor; - } - } - - void loadSignQueries(RowIndex rid, DenseMap& target) const{ - - RowIterator i = getRow(rid).begin(), i_end = getRow(rid).end(); - for(; i != i_end; ++i){ - const MatrixEntry& entry = *i; - target.set(entry.getColVar(), entry.getCoefficient().sgn()); - } - } - -protected: - uint32_t numNonZeroEntries() const { return size(); } - - uint32_t numNonZeroEntriesByRow() const { - uint32_t rowSum = 0; - for(RowIndex rid = 0, N = d_rows.size(); rid < N; ++rid){ - rowSum += getRowLength(rid); - } - return rowSum; - } - - uint32_t numNonZeroEntriesByCol() const { - uint32_t colSum = 0; - for(ArithVar v = 0, N = d_columns.size(); v < N; ++v){ - colSum += getColLength(v); - } - return colSum; - } - - - bool debugNoZeroCoefficients(RowIndex ridx){ - for(RowIterator i=getRow(ridx).begin(); !i.atEnd(); ++i){ - const Entry& entry = *i; - if(entry.getCoefficient() == 0){ - return false; - } - } - return true; - } - bool debugMatchingCountsForRow(RowIndex ridx){ - for(RowIterator i=getRow(ridx).begin(); !i.atEnd(); ++i){ - const Entry& entry = *i; - ArithVar colVar = entry.getColVar(); - uint32_t count = debugCountColLength(colVar); - Trace("tableau") << "debugMatchingCountsForRow " - << ridx << ":" << colVar << " " << count - <<" "<< getColLength(colVar) << std::endl; - if( count != getColLength(colVar) ){ - return false; - } - } - return true; - } - - uint32_t debugCountColLength(ArithVar var){ - Trace("tableau") << var << " "; - uint32_t count = 0; - for(ColIterator i=getColumn(var).begin(); !i.atEnd(); ++i){ - const Entry& entry = *i; - Trace("tableau") << "(" << entry.getRowIndex() << ", " << i.getID() << ") "; - ++count; - } - Trace("tableau") << std::endl; - return count; - } - uint32_t debugCountRowLength(RowIndex ridx){ - uint32_t count = 0; - for(RowIterator i=getRow(ridx).begin(); !i.atEnd(); ++i){ - ++count; - } - return count; - } - -};/* class Matrix */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/nl/icp/icp_solver.cpp b/src/theory/arith/nl/icp/icp_solver.cpp index 8b6e0c303..1a19216b5 100644 --- a/src/theory/arith/nl/icp/icp_solver.cpp +++ b/src/theory/arith/nl/icp/icp_solver.cpp @@ -23,7 +23,7 @@ #include "theory/arith/arith_msum.h" #include "theory/arith/inference_manager.h" #include "theory/arith/nl/poly_conversion.h" -#include "theory/arith/normal_form.h" +#include "theory/arith/linear/normal_form.h" #include "theory/rewriter.h" #include "util/poly_util.h" @@ -89,7 +89,7 @@ std::vector ICPSolver::constructCandidates(const Node& n) { return {}; } - auto comp = Comparison::parseNormalForm(tmp).decompose(false); + auto comp = linear::Comparison::parseNormalForm(tmp).decompose(false); Kind k = std::get<1>(comp); if (k == Kind::DISTINCT) { diff --git a/src/theory/arith/normal_form.cpp b/src/theory/arith/normal_form.cpp deleted file mode 100644 index b7b10036c..000000000 --- a/src/theory/arith/normal_form.cpp +++ /dev/null @@ -1,1427 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ -#include "theory/arith/normal_form.h" - -#include - -#include "base/output.h" -#include "theory/arith/arith_utilities.h" -#include "theory/theory.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -Constant Constant::mkConstant(const Rational& rat) { - return Constant(mkRationalNode(rat)); -} - -size_t Variable::getComplexity() const{ - return 1u; -} - -size_t VarList::getComplexity() const{ - if(empty()){ - return 1; - }else if(singleton()){ - return 1; - }else{ - return size() + 1; - } -} - -size_t Monomial::getComplexity() const{ - return getConstant().getComplexity() + getVarList().getComplexity(); -} - -size_t Polynomial::getComplexity() const{ - size_t cmp = 0; - iterator i = begin(), e = end(); - for(; i != e; ++i){ - Monomial m = *i; - cmp += m.getComplexity(); - } - return cmp; -} - -size_t Constant::getComplexity() const{ - return getValue().complexity(); -} - -bool Variable::isLeafMember(Node n){ - return (!isRelationOperator(n.getKind())) && - (Theory::isLeafOf(n, theory::THEORY_ARITH)); -} - -VarList::VarList(Node n) : NodeWrapper(n) { Assert(isSorted(begin(), end())); } - -bool Variable::isIAndMember(Node n) -{ - return n.getKind() == kind::IAND && Polynomial::isMember(n[0]) - && Polynomial::isMember(n[1]); -} - -bool Variable::isPow2Member(Node n) -{ - return n.getKind() == kind::POW2 && Polynomial::isMember(n[0]); -} - -bool Variable::isDivMember(Node n){ - switch(n.getKind()){ - case kind::DIVISION: - case kind::INTS_DIVISION: - case kind::INTS_MODULUS: - case kind::DIVISION_TOTAL: - case kind::INTS_DIVISION_TOTAL: - case kind::INTS_MODULUS_TOTAL: - return Polynomial::isMember(n[0]) && Polynomial::isMember(n[1]); - default: - return false; - } -} - -bool Variable::isTranscendentalMember(Node n) { - switch(n.getKind()){ - case kind::EXPONENTIAL: - case kind::SINE: - case kind::COSINE: - case kind::TANGENT: - case kind::COSECANT: - case kind::SECANT: - case kind::COTANGENT: - case kind::ARCSINE: - case kind::ARCCOSINE: - case kind::ARCTANGENT: - case kind::ARCCOSECANT: - case kind::ARCSECANT: - case kind::ARCCOTANGENT: - case kind::SQRT: return Polynomial::isMember(n[0]); - case kind::PI: - return true; - default: - return false; - } -} - - -bool VarList::isSorted(iterator start, iterator end) { - return std::is_sorted(start, end); -} - -bool VarList::isMember(Node n) { - if(Variable::isMember(n)) { - return true; - } - if(n.getKind() == kind::NONLINEAR_MULT) { - Node::iterator curr = n.begin(), end = n.end(); - Node prev = *curr; - if(!Variable::isMember(prev)) return false; - - Variable::VariableNodeCmp cmp; - - while( (++curr) != end) { - if(!Variable::isMember(*curr)) return false; - // prev <= curr : accept - // !(prev <= curr) : reject - // !(!(prev > curr)) : reject - // curr < prev : reject - if((cmp(*curr, prev))) return false; - prev = *curr; - } - return true; - } else { - return false; - } -} - -int VarList::cmp(const VarList& vl) const { - int dif = this->size() - vl.size(); - if (dif == 0) { - if(this->getNode() == vl.getNode()) { - return 0; - } - - Assert(!empty()); - Assert(!vl.empty()); - if(this->size() == 1){ - return Variable::VariableNodeCmp::cmp(this->getNode(), vl.getNode()); - } - - - internal_iterator ii=this->internalBegin(), ie=this->internalEnd(); - internal_iterator ci=vl.internalBegin(), ce=vl.internalEnd(); - for(; ii != ie; ++ii, ++ci){ - Node vi = *ii; - Node vc = *ci; - int tmp = Variable::VariableNodeCmp::cmp(vi, vc); - if(tmp != 0){ - return tmp; - } - } - Unreachable(); - } else if(dif < 0) { - return -1; - } else { - return 1; - } -} - -VarList VarList::parseVarList(Node n) { - return VarList(n); - // if(Variable::isMember(n)) { - // return VarList(Variable(n)); - // } else { - // Assert(n.getKind() == kind::MULT); - // for(Node::iterator i=n.begin(), end = n.end(); i!=end; ++i) { - // Assert(Variable::isMember(*i)); - // } - // return VarList(n); - // } -} - -VarList VarList::operator*(const VarList& other) const { - if(this->empty()) { - return other; - } else if(other.empty()) { - return *this; - } else { - vector result; - - internal_iterator - thisBegin = this->internalBegin(), - thisEnd = this->internalEnd(), - otherBegin = other.internalBegin(), - otherEnd = other.internalEnd(); - - Variable::VariableNodeCmp cmp; - std::merge(thisBegin, thisEnd, otherBegin, otherEnd, std::back_inserter(result), cmp); - - Assert(result.size() >= 2); - Node mult = NodeManager::currentNM()->mkNode(kind::NONLINEAR_MULT, result); - return VarList::parseVarList(mult); - } -} - -bool Monomial::isMember(TNode n){ - if(n.getKind() == kind::CONST_RATIONAL) { - return true; - } else if(multStructured(n)) { - return VarList::isMember(n[1]); - } else { - return VarList::isMember(n); - } -} - -Monomial Monomial::mkMonomial(const Constant& c, const VarList& vl) { - if(c.isZero() || vl.empty() ) { - return Monomial(c); - } else if(c.isOne()) { - return Monomial(vl); - } else { - return Monomial(c, vl); - } -} - -Monomial Monomial::mkMonomial(const VarList& vl) { - // acts like Monomial::mkMonomial( 1, vl) - if( vl.empty() ) { - return Monomial::mkOne(); - } else if(true){ - return Monomial(vl); - } -} - -Monomial Monomial::parseMonomial(Node n) { - if(n.getKind() == kind::CONST_RATIONAL) { - return Monomial(Constant(n)); - } else if(multStructured(n)) { - return Monomial::mkMonomial(Constant(n[0]),VarList::parseVarList(n[1])); - } else { - return Monomial(VarList::parseVarList(n)); - } -} -Monomial Monomial::operator*(const Rational& q) const { - if(q.isZero()){ - return mkZero(); - }else{ - Constant newConstant = this->getConstant() * q; - return Monomial::mkMonomial(newConstant, getVarList()); - } -} - -Monomial Monomial::operator*(const Constant& c) const { - return (*this) * c.getValue(); - // if(c.isZero()){ - // return mkZero(); - // }else{ - // Constant newConstant = this->getConstant() * c; - // return Monomial::mkMonomial(newConstant, getVarList()); - // } -} - -Monomial Monomial::operator*(const Monomial& mono) const { - Constant newConstant = this->getConstant() * mono.getConstant(); - VarList newVL = this->getVarList() * mono.getVarList(); - - return Monomial::mkMonomial(newConstant, newVL); -} - -// vector Monomial::sumLikeTerms(const std::vector & monos) -// { -// Assert(isSorted(monos)); -// vector outMonomials; -// typedef vector::const_iterator iterator; -// for(iterator rangeIter = monos.begin(), end=monos.end(); rangeIter != end;) -// { -// Rational constant = (*rangeIter).getConstant().getValue(); -// VarList varList = (*rangeIter).getVarList(); -// ++rangeIter; -// while(rangeIter != end && varList == (*rangeIter).getVarList()) { -// constant += (*rangeIter).getConstant().getValue(); -// ++rangeIter; -// } -// if(constant != 0) { -// Constant asConstant = Constant::mkConstant(constant); -// Monomial nonZero = Monomial::mkMonomial(asConstant, varList); -// outMonomials.push_back(nonZero); -// } -// } - -// Assert(isStrictlySorted(outMonomials)); -// return outMonomials; -// } - -void Monomial::sort(std::vector& m){ - if(!isSorted(m)){ - std::sort(m.begin(), m.end()); - } -} - -void Monomial::combineAdjacentMonomials(std::vector& monos) { - Assert(isSorted(monos)); - size_t writePos, readPos, N; - for(writePos = 0, readPos = 0, N = monos.size(); readPos < N;){ - Monomial& atRead = monos[readPos]; - const VarList& varList = atRead.getVarList(); - - size_t rangeEnd = readPos+1; - for(; rangeEnd < N; rangeEnd++){ - if(!(varList == monos[rangeEnd].getVarList())){ break; } - } - // monos[i] for i in [readPos, rangeEnd) has the same var list - if(readPos+1 == rangeEnd){ // no addition needed - if(!atRead.getConstant().isZero()){ - Monomial cpy = atRead; // being paranoid here - monos[writePos] = cpy; - writePos++; - } - }else{ - Rational constant(monos[readPos].getConstant().getValue()); - for(size_t i=readPos+1; i < rangeEnd; ++i){ - constant += monos[i].getConstant().getValue(); - } - if(!constant.isZero()){ - Constant asConstant = Constant::mkConstant(constant); - Monomial nonZero = Monomial::mkMonomial(asConstant, varList); - monos[writePos] = nonZero; - writePos++; - } - } - Assert(rangeEnd > readPos); - readPos = rangeEnd; - } - if(writePos > 0 ){ - Monomial cp = monos[0]; - Assert(writePos <= N); - monos.resize(writePos, cp); - }else{ - monos.clear(); - } - Assert(isStrictlySorted(monos)); -} - -void Monomial::print() const { - Trace("normal-form") << getNode() << std::endl; -} - -void Monomial::printList(const std::vector& list) { - for(vector::const_iterator i = list.begin(), end = list.end(); i != end; ++i) { - const Monomial& m =*i; - m.print(); - } -} -Polynomial Polynomial::operator+(const Polynomial& vl) const { - - std::vector sortedMonos; - std::merge(begin(), end(), vl.begin(), vl.end(), std::back_inserter(sortedMonos)); - - Monomial::combineAdjacentMonomials(sortedMonos); - //std::vector combined = Monomial::sumLikeTerms(sortedMonos); - - Polynomial result = mkPolynomial(sortedMonos); - return result; -} - -Polynomial Polynomial::exactDivide(const Integer& z) const { - Assert(isIntegral()); - if(z.isOne()){ - return (*this); - }else { - Constant invz = Constant::mkConstant(Rational(1,z)); - Polynomial prod = (*this) * Monomial::mkMonomial(invz); - Assert(prod.isIntegral()); - return prod; - } -} - -Polynomial Polynomial::sumPolynomials(const std::vector& ps){ - if(ps.empty()){ - return mkZero(); - }else if(ps.size() <= 4){ - // if there are few enough polynomials just add them - Polynomial p = ps[0]; - for(size_t i = 1; i < ps.size(); ++i){ - p = p + ps[i]; - } - return p; - }else{ - // general case - std::map coeffs; - for(size_t i = 0, N = ps.size(); i monos; - std::map::const_iterator ci = coeffs.begin(), cend = coeffs.end(); - for(; ci != cend; ++ci){ - if(!(*ci).second.isZero()){ - Constant c = Constant::mkConstant((*ci).second); - Node n = (*ci).first; - VarList vl = VarList::parseVarList(n); - monos.push_back(Monomial::mkMonomial(c, vl)); - } - } - Monomial::sort(monos); - Monomial::combineAdjacentMonomials(monos); - - Polynomial result = mkPolynomial(monos); - return result; - } -} - -Polynomial Polynomial::operator-(const Polynomial& vl) const { - Constant negOne = Constant::mkConstant(Rational(-1)); - - return *this + (vl*negOne); -} - -Polynomial Polynomial::operator*(const Rational& q) const{ - if(q.isZero()){ - return Polynomial::mkZero(); - }else if(q.isOne()){ - return *this; - }else{ - std::vector newMonos; - for(iterator i = this->begin(), end = this->end(); i != end; ++i) { - newMonos.push_back((*i)*q); - } - - Assert(Monomial::isStrictlySorted(newMonos)); - return Polynomial::mkPolynomial(newMonos); - } -} - -Polynomial Polynomial::operator*(const Constant& c) const{ - return (*this) * c.getValue(); - // if(c.isZero()){ - // return Polynomial::mkZero(); - // }else if(c.isOne()){ - // return *this; - // }else{ - // std::vector newMonos; - // for(iterator i = this->begin(), end = this->end(); i != end; ++i) { - // newMonos.push_back((*i)*c); - // } - - // Assert(Monomial::isStrictlySorted(newMonos)); - // return Polynomial::mkPolynomial(newMonos); - // } -} - -Polynomial Polynomial::operator*(const Monomial& mono) const { - if(mono.isZero()) { - return Polynomial(mono); //Don't multiply by zero - } else { - std::vector newMonos; - for(iterator i = this->begin(), end = this->end(); i != end; ++i) { - newMonos.push_back(mono * (*i)); - } - - // We may need to sort newMonos. - // Suppose this = (+ x y), mono = x, (* x y).getId() < (* x x).getId() - // newMonos = <(* x x), (* x y)> after this loop. - // This is not sorted according to the current VarList order. - Monomial::sort(newMonos); - return Polynomial::mkPolynomial(newMonos); - } -} - -Polynomial Polynomial::operator*(const Polynomial& poly) const { - Polynomial res = Polynomial::mkZero(); - for(iterator i = this->begin(), end = this->end(); i != end; ++i) { - Monomial curr = *i; - Polynomial prod = poly * curr; - Polynomial sum = res + prod; - res = sum; - } - return res; -} - -Monomial Polynomial::selectAbsMinimum() const { - iterator iter = begin(), myend = end(); - Assert(iter != myend); - - Monomial min = *iter; - ++iter; - for(; iter != end(); ++iter){ - Monomial curr = *iter; - if(curr.absCmp(min) < 0){ - min = curr; - } - } - return min; -} - -bool Polynomial::leadingCoefficientIsAbsOne() const { - return getHead().absCoefficientIsOne(); -} -bool Polynomial::leadingCoefficientIsPositive() const { - return getHead().getConstant().isPositive(); -} - -bool Polynomial::denominatorLCMIsOne() const { - return denominatorLCM().isOne(); -} - -bool Polynomial::numeratorGCDIsOne() const { - return gcd().isOne(); -} - -Integer Polynomial::gcd() const { - Assert(isIntegral()); - return numeratorGCD(); -} - -Integer Polynomial::numeratorGCD() const { - //We'll use the standardization that gcd(0, 0) = 0 - //So that the gcd of the zero polynomial is gcd{0} = 0 - iterator i=begin(), e=end(); - Assert(i != e); - - Integer d = (*i).getConstant().getValue().getNumerator().abs(); - if(d.isOne()){ - return d; - } - ++i; - for(; i!=e; ++i){ - Integer c = (*i).getConstant().getValue().getNumerator(); - d = d.gcd(c); - if(d.isOne()){ - return d; - } - } - return d; -} - -Integer Polynomial::denominatorLCM() const { - Integer tmp(1); - for (iterator i = begin(), e = end(); i != e; ++i) { - const Integer denominator = (*i).getConstant().getValue().getDenominator(); - tmp = tmp.lcm(denominator); - } - return tmp; -} - -Constant Polynomial::getCoefficient(const VarList& vl) const{ - //TODO improve to binary search... - for(iterator iter=begin(), myend=end(); iter != myend; ++iter){ - Monomial m = *iter; - VarList curr = m.getVarList(); - if(curr == vl){ - return m.getConstant(); - } - } - return Constant::mkConstant(0); -} - -Node Polynomial::computeQR(const Polynomial& p, const Integer& div){ - Assert(p.isIntegral()); - std::vector q_vec, r_vec; - Integer tmp_q, tmp_r; - for(iterator iter = p.begin(), pend = p.end(); iter != pend; ++iter){ - Monomial curr = *iter; - VarList vl = curr.getVarList(); - Constant c = curr.getConstant(); - - const Integer& a = c.getValue().getNumerator(); - Integer::floorQR(tmp_q, tmp_r, a, div); - Constant q=Constant::mkConstant(tmp_q); - Constant r=Constant::mkConstant(tmp_r); - if(!q.isZero()){ - q_vec.push_back(Monomial::mkMonomial(q, vl)); - } - if(!r.isZero()){ - r_vec.push_back(Monomial::mkMonomial(r, vl)); - } - } - - Polynomial p_q = Polynomial::mkPolynomial(q_vec); - Polynomial p_r = Polynomial::mkPolynomial(r_vec); - - return NodeManager::currentNM()->mkNode( - kind::ADD, p_q.getNode(), p_r.getNode()); -} - - -Monomial Polynomial::minimumVariableMonomial() const{ - Assert(!isConstant()); - if(singleton()){ - return getHead(); - }else{ - iterator i = begin(); - Monomial first = *i; - if( first.isConstant() ){ - ++i; - Assert(i != end()); - return *i; - }else{ - return first; - } - } -} - -bool Polynomial::variableMonomialAreStrictlyGreater(const Monomial& m) const{ - if(isConstant()){ - return true; - }else{ - Monomial minimum = minimumVariableMonomial(); - Trace("nf::tmp") << "minimum " << minimum.getNode() << endl; - Trace("nf::tmp") << "m " << m.getNode() << endl; - return m < minimum; - } -} - -bool Polynomial::isMember(TNode n) { - if(Monomial::isMember(n)){ - return true; - } - else if (n.getKind() == kind::ADD) - { - Assert(n.getNumChildren() >= 2); - Node::iterator currIter = n.begin(), end = n.end(); - Node prev = *currIter; - if(!Monomial::isMember(prev)){ - return false; - } - - Monomial mprev = Monomial::parseMonomial(prev); - ++currIter; - for(; currIter != end; ++currIter){ - Node curr = *currIter; - if(!Monomial::isMember(curr)){ - return false; - } - Monomial mcurr = Monomial::parseMonomial(curr); - if(!(mprev < mcurr)){ - return false; - } - mprev = mcurr; - } - return true; - } - else - { - return false; - } -} - -Node SumPair::computeQR(const SumPair& sp, const Integer& div){ - Assert(sp.isIntegral()); - - const Integer& constant = sp.getConstant().getValue().getNumerator(); - - Integer constant_q, constant_r; - Integer::floorQR(constant_q, constant_r, constant, div); - - Node p_qr = Polynomial::computeQR(sp.getPolynomial(), div); - Assert(p_qr.getKind() == kind::ADD); - Assert(p_qr.getNumChildren() == 2); - - Polynomial p_q = Polynomial::parsePolynomial(p_qr[0]); - Polynomial p_r = Polynomial::parsePolynomial(p_qr[1]); - - SumPair sp_q(p_q, Constant::mkConstant(constant_q)); - SumPair sp_r(p_r, Constant::mkConstant(constant_r)); - - return NodeManager::currentNM()->mkNode( - kind::ADD, sp_q.getNode(), sp_r.getNode()); -} - -SumPair SumPair::mkSumPair(const Polynomial& p){ - if(p.isConstant()){ - Constant leadingConstant = p.getHead().getConstant(); - return SumPair(Polynomial::mkZero(), leadingConstant); - }else if(p.containsConstant()){ - Assert(!p.singleton()); - return SumPair(p.getTail(), p.getHead().getConstant()); - }else{ - return SumPair(p, Constant::mkZero()); - } -} - -Comparison::Comparison(TNode n) : NodeWrapper(n) { Assert(isNormalForm()); } - -SumPair Comparison::toSumPair() const { - Kind cmpKind = comparisonKind(); - switch(cmpKind){ - case kind::LT: - case kind::LEQ: - case kind::GT: - case kind::GEQ: - { - TNode lit = getNode(); - TNode atom = (cmpKind == kind::LT || cmpKind == kind::LEQ) ? lit[0] : lit; - Polynomial p = Polynomial::parsePolynomial(atom[0]); - Constant c = Constant::mkConstant(atom[1]); - if(p.leadingCoefficientIsPositive()){ - return SumPair(p, -c); - }else{ - return SumPair(-p, c); - } - } - case kind::EQUAL: - case kind::DISTINCT: - { - Polynomial left = getLeft(); - Polynomial right = getRight(); - Trace("nf::tmp") << "left: " << left.getNode() << endl; - Trace("nf::tmp") << "right: " << right.getNode() << endl; - if(right.isConstant()){ - return SumPair(left, -right.getHead().getConstant()); - }else if(right.containsConstant()){ - Assert(!right.singleton()); - - Polynomial noConstant = right.getTail(); - return SumPair(left - noConstant, -right.getHead().getConstant()); - }else{ - return SumPair(left - right, Constant::mkZero()); - } - } - default: Unhandled() << cmpKind; - } -} - -Polynomial Comparison::normalizedVariablePart() const { - Kind cmpKind = comparisonKind(); - switch(cmpKind){ - case kind::LT: - case kind::LEQ: - case kind::GT: - case kind::GEQ: - { - TNode lit = getNode(); - TNode atom = (cmpKind == kind::LT || cmpKind == kind::LEQ) ? lit[0] : lit; - Polynomial p = Polynomial::parsePolynomial(atom[0]); - if(p.leadingCoefficientIsPositive()){ - return p; - }else{ - return -p; - } - } - case kind::EQUAL: - case kind::DISTINCT: - { - Polynomial left = getLeft(); - Polynomial right = getRight(); - if(right.isConstant()){ - return left; - }else{ - Polynomial noConstant = right.containsConstant() ? right.getTail() : right; - Polynomial diff = left - noConstant; - if(diff.leadingCoefficientIsPositive()){ - return diff; - }else{ - return -diff; - } - } - } - default: Unhandled() << cmpKind; - } -} - -DeltaRational Comparison::normalizedDeltaRational() const { - Kind cmpKind = comparisonKind(); - int delta = deltaCoeff(cmpKind); - switch(cmpKind){ - case kind::LT: - case kind::LEQ: - case kind::GT: - case kind::GEQ: - { - Node lit = getNode(); - Node atom = (cmpKind == kind::LT || cmpKind == kind::LEQ) ? lit[0] : lit; - Polynomial left = Polynomial::parsePolynomial(atom[0]); - const Rational& q = atom[1].getConst(); - if(left.leadingCoefficientIsPositive()){ - return DeltaRational(q, delta); - }else{ - return DeltaRational(-q, -delta); - } - } - case kind::EQUAL: - case kind::DISTINCT: - { - Polynomial right = getRight(); - Monomial firstRight = right.getHead(); - if(firstRight.isConstant()){ - DeltaRational c = DeltaRational(firstRight.getConstant().getValue(), 0); - Polynomial left = getLeft(); - if(!left.allIntegralVariables()){ - return c; - //this is a qpolynomial and the sign of the leading - //coefficient will not change after the diff below - } else{ - // the polynomial may be a z polynomial in which case - // taking the diff is the simplest and obviously correct means - Polynomial diff = right.singleton() ? left : left - right.getTail(); - if(diff.leadingCoefficientIsPositive()){ - return c; - }else{ - return -c; - } - } - }else{ // The constant is 0 sign cannot change - return DeltaRational(0, 0); - } - } - default: Unhandled() << cmpKind; - } -} - -std::tuple Comparison::decompose( - bool split_constant) const -{ - Kind rel = getNode().getKind(); - if (rel == Kind::NOT) - { - switch (getNode()[0].getKind()) - { - case kind::LEQ: rel = Kind::GT; break; - case kind::LT: rel = Kind::GEQ; break; - case kind::EQUAL: rel = Kind::DISTINCT; break; - case kind::DISTINCT: rel = Kind::EQUAL; break; - case kind::GEQ: rel = Kind::LT; break; - case kind::GT: rel = Kind::LEQ; break; - default: - Assert(false) << "Unsupported relation: " << getNode()[0].getKind(); - } - } - - Polynomial poly = getLeft() - getRight(); - - if (!split_constant) - { - return std::tuple{ - poly, rel, Constant::mkZero()}; - } - - Constant right = Constant::mkZero(); - if (poly.containsConstant()) - { - right = -poly.getHead().getConstant(); - poly = poly + Polynomial::mkPolynomial(right); - } - - Constant lcoeff = poly.getHead().getConstant(); - if (!lcoeff.isOne()) - { - Constant invlcoeff = lcoeff.inverse(); - if (lcoeff.isNegative()) - { - switch (rel) - { - case kind::LEQ: rel = Kind::GEQ; break; - case kind::LT: rel = Kind::GT; break; - case kind::EQUAL: break; - case kind::DISTINCT: break; - case kind::GEQ: rel = Kind::LEQ; break; - case kind::GT: rel = Kind::LT; break; - default: Assert(false) << "Unsupported relation: " << rel; - } - } - poly = poly * invlcoeff; - right = right * invlcoeff; - } - - return std::tuple{poly, rel, right}; -} - -Comparison Comparison::parseNormalForm(TNode n) { - Trace("polynomial") << "Comparison::parseNormalForm(" << n << ")"; - Comparison result(n); - Assert(result.isNormalForm()); - return result; -} - -Node Comparison::toNode(Kind k, const Polynomial& l, const Constant& r) { - Assert(isRelationOperator(k)); - switch(k) { - case kind::GEQ: - case kind::GT: - return NodeManager::currentNM()->mkNode(k, l.getNode(), r.getNode()); - default: Unhandled() << k; - } -} - -Node Comparison::toNode(Kind k, const Polynomial& l, const Polynomial& r) { - Assert(isRelationOperator(k)); - switch(k) { - case kind::GEQ: - case kind::EQUAL: - case kind::GT: - return NodeManager::currentNM()->mkNode(k, l.getNode(), r.getNode()); - case kind::LEQ: - return toNode(kind::GEQ, r, l).notNode(); - case kind::LT: - return toNode(kind::GT, r, l).notNode(); - case kind::DISTINCT: - return toNode(kind::EQUAL, r, l).notNode(); - default: - Unreachable(); - } -} - -bool Comparison::rightIsConstant() const { - if(getNode().getKind() == kind::NOT){ - return getNode()[0][1].getKind() == kind::CONST_RATIONAL; - }else{ - return getNode()[1].getKind() == kind::CONST_RATIONAL; - } -} - -size_t Comparison::getComplexity() const{ - switch(comparisonKind()){ - case kind::CONST_BOOLEAN: return 1; - case kind::LT: - case kind::LEQ: - case kind::DISTINCT: - case kind::EQUAL: - case kind::GT: - case kind::GEQ: - return getLeft().getComplexity() + getRight().getComplexity(); - default: Unhandled() << comparisonKind(); return -1; - } -} - -Polynomial Comparison::getLeft() const { - TNode left; - Kind k = comparisonKind(); - switch(k){ - case kind::LT: - case kind::LEQ: - case kind::DISTINCT: - left = getNode()[0][0]; - break; - case kind::EQUAL: - case kind::GT: - case kind::GEQ: - left = getNode()[0]; - break; - default: Unhandled() << k; - } - return Polynomial::parsePolynomial(left); -} - -Polynomial Comparison::getRight() const { - TNode right; - Kind k = comparisonKind(); - switch(k){ - case kind::LT: - case kind::LEQ: - case kind::DISTINCT: - right = getNode()[0][1]; - break; - case kind::EQUAL: - case kind::GT: - case kind::GEQ: - right = getNode()[1]; - break; - default: Unhandled() << k; - } - return Polynomial::parsePolynomial(right); -} - -// Polynomial Comparison::getLeft() const { -// Node n = getNode(); -// Node left = (n.getKind() == kind::NOT ? n[0]: n)[0]; -// return Polynomial::parsePolynomial(left); -// } - -// Polynomial Comparison::getRight() const { -// Node n = getNode(); -// Node right = (n.getKind() == kind::NOT ? n[0]: n)[1]; -// return Polynomial::parsePolynomial(right); -// } - -bool Comparison::isNormalForm() const { - Node n = getNode(); - Kind cmpKind = comparisonKind(n); - Trace("nf::tmp") << "isNormalForm " << n << " " << cmpKind << endl; - switch(cmpKind){ - case kind::CONST_BOOLEAN: - return true; - case kind::GT: - return isNormalGT(); - case kind::GEQ: - return isNormalGEQ(); - case kind::EQUAL: - return isNormalEquality(); - case kind::LT: - return isNormalLT(); - case kind::LEQ: - return isNormalLEQ(); - case kind::DISTINCT: - return isNormalDistinct(); - default: - return false; - } -} - -/** This must be (> qpolynomial constant) */ -bool Comparison::isNormalGT() const { - Node n = getNode(); - Assert(n.getKind() == kind::GT); - if(!rightIsConstant()){ - return false; - }else{ - Polynomial left = getLeft(); - if(left.containsConstant()){ - return false; - }else if(!left.leadingCoefficientIsAbsOne()){ - return false; - }else{ - return !left.isIntegral(); - } - } -} - -/** This must be (not (> qpolynomial constant)) */ -bool Comparison::isNormalLEQ() const { - Node n = getNode(); - Trace("nf::tmp") << "isNormalLEQ " << n << endl; - Assert(n.getKind() == kind::NOT); - Assert(n[0].getKind() == kind::GT); - if(!rightIsConstant()){ - return false; - }else{ - Polynomial left = getLeft(); - if(left.containsConstant()){ - return false; - }else if(!left.leadingCoefficientIsAbsOne()){ - return false; - }else{ - return !left.isIntegral(); - } - } -} - - -/** This must be (>= qpolynomial constant) or (>= zpolynomial constant) */ -bool Comparison::isNormalGEQ() const { - Node n = getNode(); - Assert(n.getKind() == kind::GEQ); - - Trace("nf::tmp") << "isNormalGEQ " << n << " " << rightIsConstant() << endl; - - if(!rightIsConstant()){ - return false; - }else{ - Polynomial left = getLeft(); - if(left.containsConstant()){ - return false; - }else{ - if(left.isIntegral()){ - return left.signNormalizedReducedSum(); - }else{ - return left.leadingCoefficientIsAbsOne(); - } - } - } -} - -/** This must be (not (>= qpolynomial constant)) or (not (>= zpolynomial constant)) */ -bool Comparison::isNormalLT() const { - Node n = getNode(); - Assert(n.getKind() == kind::NOT); - Assert(n[0].getKind() == kind::GEQ); - - if(!rightIsConstant()){ - return false; - }else{ - Polynomial left = getLeft(); - if(left.containsConstant()){ - return false; - }else{ - if(left.isIntegral()){ - return left.signNormalizedReducedSum(); - }else{ - return left.leadingCoefficientIsAbsOne(); - } - } - } -} - - -bool Comparison::isNormalEqualityOrDisequality() const { - Polynomial pleft = getLeft(); - - if(pleft.numMonomials() == 1){ - Monomial mleft = pleft.getHead(); - if(mleft.isConstant()){ - return false; - }else{ - Polynomial pright = getRight(); - if(allIntegralVariables()){ - const Rational& lcoeff = mleft.getConstant().getValue(); - if(pright.isConstant()){ - return pright.isIntegral() && lcoeff.isOne(); - } - Polynomial varRight = pright.containsConstant() ? pright.getTail() : pright; - if(lcoeff.sgn() <= 0){ - return false; - }else{ - Integer lcm = lcoeff.getDenominator().lcm(varRight.denominatorLCM()); - Integer g = lcoeff.getNumerator().gcd(varRight.numeratorGCD()); - Trace("nf::tmp") << lcm << " " << g << endl; - if(!lcm.isOne()){ - return false; - }else if(!g.isOne()){ - return false; - }else{ - Monomial absMinRight = varRight.selectAbsMinimum(); - Trace("nf::tmp") << mleft.getNode() << " " << absMinRight.getNode() << endl; - if( mleft.absCmp(absMinRight) < 0){ - return true; - }else{ - return (!(absMinRight.absCmp(mleft)< 0)) && mleft < absMinRight; - } - } - } - }else{ - if(mleft.coefficientIsOne()){ - Trace("nf::tmp") - << "dfklj " << mleft.getNode() << endl - << pright.getNode() << endl - << pright.variableMonomialAreStrictlyGreater(mleft) - << endl; - return pright.variableMonomialAreStrictlyGreater(mleft); - }else{ - return false; - } - } - } - }else{ - return false; - } -} - -/** This must be (= qvarlist qpolynomial) or (= zmonomial zpolynomial)*/ -bool Comparison::isNormalEquality() const { - Assert(getNode().getKind() == kind::EQUAL); - return Theory::theoryOf(getNode()[0].getType()) == THEORY_ARITH && - isNormalEqualityOrDisequality(); -} - -/** - * This must be (not (= qvarlist qpolynomial)) or - * (not (= zmonomial zpolynomial)). - */ -bool Comparison::isNormalDistinct() const { - Assert(getNode().getKind() == kind::NOT); - Assert(getNode()[0].getKind() == kind::EQUAL); - - return Theory::theoryOf(getNode()[0][0].getType()) == THEORY_ARITH && - isNormalEqualityOrDisequality(); -} - -Node Comparison::mkRatEquality(const Polynomial& p){ - Assert(!p.isConstant()); - Assert(!p.allIntegralVariables()); - - Monomial minimalVList = p.minimumVariableMonomial(); - Constant coeffInv = -(minimalVList.getConstant().inverse()); - - Polynomial newRight = (p - minimalVList) * coeffInv; - Polynomial newLeft(Monomial::mkMonomial(minimalVList.getVarList())); - - return toNode(kind::EQUAL, newLeft, newRight); -} - -Node Comparison::mkRatInequality(Kind k, const Polynomial& p){ - Assert(k == kind::GEQ || k == kind::GT); - Assert(!p.isConstant()); - Assert(!p.allIntegralVariables()); - - SumPair sp = SumPair::mkSumPair(p); - Polynomial left = sp.getPolynomial(); - Constant right = - sp.getConstant(); - - Monomial minimalVList = left.getHead(); - Assert(!minimalVList.isConstant()); - - Constant coeffInv = minimalVList.getConstant().inverse().abs(); - Polynomial newLeft = left * coeffInv; - Constant newRight = right * (coeffInv); - - return toNode(k, newLeft, newRight); -} - -Node Comparison::mkIntInequality(Kind k, const Polynomial& p){ - Assert(kind::GT == k || kind::GEQ == k); - Assert(!p.isConstant()); - Assert(p.allIntegralVariables()); - - SumPair sp = SumPair::mkSumPair(p); - Polynomial left = sp.getPolynomial(); - Rational right = - (sp.getConstant().getValue()); - - - Monomial m = left.getHead(); - Assert(!m.isConstant()); - - Integer lcm = left.denominatorLCM(); - Integer g = left.numeratorGCD(); - Rational mult(lcm,g); - - Polynomial newLeft = left * mult; - Rational rightMult = right * mult; - - bool negateResult = false; - if(!newLeft.leadingCoefficientIsPositive()){ - // multiply by -1 - // a: left >= right or b: left > right - // becomes - // a: -left <= -right or b: -left < -right - // a: not (-left > -right) or b: (not -left >= -right) - newLeft = -newLeft; - rightMult = -rightMult; - k = (kind::GT == k) ? kind::GEQ : kind::GT; - negateResult = true; - // the later stages handle: - // a: not (-left >= -right + 1) or b: (not -left >= -right) - } - - Node result = Node::null(); - if(rightMult.isIntegral()){ - if(k == kind::GT){ - // (> p z) - // (>= p (+ z 1)) - Constant rightMultPlusOne = Constant::mkConstant(rightMult + 1); - result = toNode(kind::GEQ, newLeft, rightMultPlusOne); - }else{ - Constant newRight = Constant::mkConstant(rightMult); - result = toNode(kind::GEQ, newLeft, newRight); - } - }else{ - //(>= l (/ n d)) - //(>= l (ceil (/ n d))) - //This also hold for GT as (ceil (/ n d)) > (/ n d) - Integer ceilr = rightMult.ceiling(); - Constant ceilRight = Constant::mkConstant(ceilr); - result = toNode(kind::GEQ, newLeft, ceilRight); - } - Assert(!result.isNull()); - if(negateResult){ - return result.notNode(); - }else{ - return result; - } -} - -Node Comparison::mkIntEquality(const Polynomial& p){ - Assert(!p.isConstant()); - Assert(p.allIntegralVariables()); - - SumPair sp = SumPair::mkSumPair(p); - Polynomial varPart = sp.getPolynomial(); - Constant constPart = sp.getConstant(); - - Integer lcm = varPart.denominatorLCM(); - Integer g = varPart.numeratorGCD(); - Constant mult = Constant::mkConstant(Rational(lcm,g)); - - Constant constMult = constPart * mult; - - if(constMult.isIntegral()){ - Polynomial varPartMult = varPart * mult; - - Monomial m = varPartMult.selectAbsMinimum(); - bool mIsPositive = m.getConstant().isPositive(); - - Polynomial noM = (varPartMult + (- m)) + Polynomial::mkPolynomial(constMult); - - // m + noM = 0 - Polynomial newRight = mIsPositive ? -noM : noM; - Polynomial newLeft = mIsPositive ? m : -m; - - Assert(newRight.isIntegral()); - return toNode(kind::EQUAL, newLeft, newRight); - }else{ - return mkBoolNode(false); - } -} - -Comparison Comparison::mkComparison(Kind k, const Polynomial& l, const Polynomial& r){ - - //Make this special case fast for sharing! - if((k == kind::EQUAL || k == kind::DISTINCT) && l.isVarList() && r.isVarList()){ - VarList vLeft = l.asVarList(); - VarList vRight = r.asVarList(); - - if(vLeft == vRight){ - // return true for equalities and false for disequalities - return Comparison(k == kind::EQUAL); - }else{ - Node eqNode = vLeft < vRight ? toNode( kind::EQUAL, l, r) : toNode( kind::EQUAL, r, l); - Node forK = (k == kind::DISTINCT) ? eqNode.notNode() : eqNode; - return Comparison(forK); - } - } - - //General case - Polynomial diff = l - r; - if(diff.isConstant()){ - bool res = evaluateConstantPredicate(k, diff.asConstant(), Rational(0)); - return Comparison(res); - }else{ - Node result = Node::null(); - bool isInteger = diff.allIntegralVariables(); - switch(k){ - case kind::EQUAL: - result = isInteger ? mkIntEquality(diff) : mkRatEquality(diff); - break; - case kind::DISTINCT: - { - Node eq = isInteger ? mkIntEquality(diff) : mkRatEquality(diff); - result = eq.notNode(); - } - break; - case kind::LEQ: - case kind::LT: - { - Polynomial neg = - diff; - Kind negKind = (k == kind::LEQ ? kind::GEQ : kind::GT); - result = isInteger ? - mkIntInequality(negKind, neg) : mkRatInequality(negKind, neg); - } - break; - case kind::GEQ: - case kind::GT: - result = isInteger ? - mkIntInequality(k, diff) : mkRatInequality(k, diff); - break; - default: Unhandled() << k; - } - Assert(!result.isNull()); - if(result.getKind() == kind::NOT && result[0].getKind() == kind::CONST_BOOLEAN){ - return Comparison(!(result[0].getConst())); - }else{ - Comparison cmp(result); - Assert(cmp.isNormalForm()); - return cmp; - } - } -} - -bool Comparison::isBoolean() const { - return getNode().getKind() == kind::CONST_BOOLEAN; -} - - -bool Comparison::debugIsIntegral() const{ - return getLeft().isIntegral() && getRight().isIntegral(); -} - -Kind Comparison::comparisonKind(TNode literal){ - switch(literal.getKind()){ - case kind::CONST_BOOLEAN: - case kind::GT: - case kind::GEQ: - case kind::EQUAL: - return literal.getKind(); - case kind::NOT: - { - TNode negatedAtom = literal[0]; - switch(negatedAtom.getKind()){ - case kind::GT: //(not (GT x c)) <=> (LEQ x c) - return kind::LEQ; - case kind::GEQ: //(not (GEQ x c)) <=> (LT x c) - return kind::LT; - case kind::EQUAL: - return kind::DISTINCT; - default: - return kind::UNDEFINED_KIND; - } - } - default: - return kind::UNDEFINED_KIND; - } -} - - -Node Polynomial::makeAbsCondition(Variable v, Polynomial p){ - Polynomial zerop = Polynomial::mkZero(); - - Polynomial varp = Polynomial::mkPolynomial(v); - Comparison pLeq0 = Comparison::mkComparison(kind::LEQ, p, zerop); - Comparison negP = Comparison::mkComparison(kind::EQUAL, varp, -p); - Comparison posP = Comparison::mkComparison(kind::EQUAL, varp, p); - - Node absCnd = (pLeq0.getNode()).iteNode(negP.getNode(), posP.getNode()); - return absCnd; -} - -bool Polynomial::isNonlinear() const { - - for(iterator i=begin(), iend =end(); i != iend; ++i){ - Monomial m = *i; - if(m.isNonlinear()){ - return true; - } - } - return false; -} - -} //namespace arith -} //namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/normal_form.h b/src/theory/arith/normal_form.h deleted file mode 100644 index 5e79d8a54..000000000 --- a/src/theory/arith/normal_form.h +++ /dev/null @@ -1,1466 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Morgan Deters - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NORMAL_FORM_H -#define CVC5__THEORY__ARITH__NORMAL_FORM_H - -#include - -#include "base/output.h" -#include "expr/node.h" -#include "expr/node_self_iterator.h" -#include "theory/arith/delta_rational.h" -#include "util/rational.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -/***********************************************/ -/***************** Normal Form *****************/ -/***********************************************/ -/***********************************************/ - -/** - * Section 1: Languages - * The normal form for arithmetic nodes is defined by the language - * accepted by the following BNFs with some guard conditions. - * (The guard conditions are in Section 3 for completeness.) - * - * variable := n - * where - * n.isVar() or is foreign - * n.getType() \in {Integer, Real} - * - * constant := n - * where - * n.getKind() == kind::CONST_RATIONAL - * - * var_list := variable | (* [variable]) - * where - * len [variable] >= 2 - * isSorted varOrder [variable] - * - * monomial := constant | var_list | (* constant' var_list') - * where - * \f$ constant' \not\in {0,1} \f$ - * - * polynomial := monomial' | (+ [monomial]) - * where - * len [monomial] >= 2 - * isStrictlySorted monoOrder [monomial] - * forall (\x -> x != 0) [monomial] - * - * rational_cmp := (|><| qpolynomial constant) - * where - * |><| is GEQ, or GT - * not (exists constantMonomial (monomialList qpolynomial)) - * (exists realMonomial (monomialList qpolynomial)) - * abs(monomialCoefficient (head (monomialList qpolynomial))) == 1 - * - * integer_cmp := (>= zpolynomial constant) - * where - * not (exists constantMonomial (monomialList zpolynomial)) - * (forall integerMonomial (monomialList zpolynomial)) - * the gcd of all numerators of coefficients is 1 - * the denominator of all coefficients and the constant is 1 - * the leading coefficient is positive - * - * rational_eq := (= qvarlist qpolynomial) - * where - * let allMonomials = (cons qvarlist (monomialList zpolynomial)) - * let variableMonomials = (drop constantMonomial allMonomials) - * isStrictlySorted variableMonomials - * exists realMonomial variableMonomials - * is not empty qvarlist - * - * integer_eq := (= zmonomial zpolynomial) - * where - * let allMonomials = (cons zmonomial (monomialList zpolynomial)) - * let variableMonomials = (drop constantMonomial allMonomials) - * not (constantMonomial zmonomial) - * (forall integerMonomial allMonomials) - * isStrictlySorted variableMonomials - * the gcd of all numerators of coefficients is 1 - * the denominator of all coefficients and the constant is 1 - * the coefficient of monomial is positive - * the value of the coefficient of monomial is minimal in variableMonomials - * - * comparison := TRUE | FALSE - * | rational_cmp | (not rational_cmp) - * | rational_eq | (not rational_eq) - * | integer_cmp | (not integer_cmp) - * | integer_eq | (not integer_eq) - * - * Normal Form for terms := polynomial - * Normal Form for atoms := comparison - */ - -/** - * Section 2: Helper Classes - * The langauges accepted by each of these defintions - * roughly corresponds to one of the following helper classes: - * Variable - * Constant - * VarList - * Monomial - * Polynomial - * Comparison - * - * Each of the classes obeys the following contracts/design decisions: - * -Calling isMember(Node node) on a node returns true iff that node is a - * a member of the language. Note: isMember is O(n). - * -Calling isNormalForm() on a helper class object returns true iff that - * helper class currently represents a normal form object. - * -If isNormalForm() is false, then this object must have been made - * using a mk*() factory function. - * -If isNormalForm() is true, calling getNode() on all of these classes - * returns a node that would be accepted by the corresponding language. - * And if isNormalForm() is false, returns Node::null(). - * -Each of the classes is immutable. - * -Public facing constuctors have a 1-to-1 correspondence with one of - * production rules in the above grammar. - * -Public facing constuctors are required to fail in debug mode when the - * guards of the production rule are not strictly met. - * For example: Monomial(Constant(1),VarList(Variable(x))) must fail. - * -When a class has a Class parseClass(Node node) function, - * if isMember(node) is true, the function is required to return an instance - * of the helper class, instance, s.t. instance.getNode() == node. - * And if isMember(node) is false, this throws an assertion failure in debug - * mode and has undefined behaviour if not in debug mode. - * -Only public facing constructors, parseClass(node), and mk*() functions are - * considered privileged functions for the helper class. - * -Only privileged functions may use private constructors, and access - * private data members. - * -All non-privileged functions are considered utility functions and - * must use a privileged function in order to create an instance of the class. - */ - -/** - * Section 3: Guard Conditions Misc. - * - * - * variable_order x y = - * if (meta_kind_variable x) and (meta_kind_variable y) - * then node_order x y - * else if (meta_kind_variable x) - * then false - * else if (meta_kind_variable y) - * then true - * else node_order x y - * - * var_list_len vl = - * match vl with - * variable -> 1 - * | (* [variable]) -> len [variable] - * - * order res = - * match res with - * Empty -> (0,Node::null()) - * | NonEmpty(vl) -> (var_list_len vl, vl) - * - * var_listOrder a b = tuple_cmp (order a) (order b) - * - * monomialVarList monomial = - * match monomial with - * constant -> Empty - * | var_list -> NonEmpty(var_list) - * | (* constant' var_list') -> NonEmpty(var_list') - * - * monoOrder m0 m1 = var_listOrder (monomialVarList m0) (monomialVarList m1) - * - * integerMonomial mono = - * forall varHasTypeInteger (monomialVarList mono) - * - * realMonomial mono = not (integerMonomial mono) - * - * constantMonomial monomial = - * match monomial with - * constant -> true - * | var_list -> false - * | (* constant' var_list') -> false - * - * monomialCoefficient monomial = - * match monomial with - * constant -> constant - * | var_list -> Constant(1) - * | (* constant' var_list') -> constant' - * - * monomialList polynomial = - * match polynomial with - * monomial -> monomial::[] - * | (+ [monomial]) -> [monomial] - */ - -/** - * A NodeWrapper is a class that is a thinly veiled container of a Node object. - */ -class NodeWrapper { -private: - Node node; -public: - NodeWrapper(Node n) : node(n) {} - const Node& getNode() const { return node; } -};/* class NodeWrapper */ - - -class Variable : public NodeWrapper { -public: - Variable(Node n) : NodeWrapper(n) { Assert(isMember(getNode())); } - - // TODO: check if it's a theory leaf also - static bool isMember(Node n) - { - Kind k = n.getKind(); - switch (k) - { - case kind::CONST_RATIONAL: return false; - case kind::INTS_DIVISION: - case kind::INTS_MODULUS: - case kind::DIVISION: - case kind::INTS_DIVISION_TOTAL: - case kind::INTS_MODULUS_TOTAL: - case kind::DIVISION_TOTAL: return isDivMember(n); - case kind::IAND: return isIAndMember(n); - case kind::POW2: return isPow2Member(n); - case kind::EXPONENTIAL: - case kind::SINE: - case kind::COSINE: - case kind::TANGENT: - case kind::COSECANT: - case kind::SECANT: - case kind::COTANGENT: - case kind::ARCSINE: - case kind::ARCCOSINE: - case kind::ARCTANGENT: - case kind::ARCCOSECANT: - case kind::ARCSECANT: - case kind::ARCCOTANGENT: - case kind::SQRT: - case kind::PI: return isTranscendentalMember(n); - case kind::ABS: - case kind::TO_INTEGER: - // Treat to_int as a variable; it is replaced in early preprocessing - // by a variable. - return true; - default: return isLeafMember(n); - } - } - - static bool isLeafMember(Node n); - static bool isIAndMember(Node n); - static bool isPow2Member(Node n); - static bool isDivMember(Node n); - bool isDivLike() const{ - return isDivMember(getNode()); - } - static bool isTranscendentalMember(Node n); - - bool isNormalForm() { return isMember(getNode()); } - - bool isIntegral() const { - return getNode().getType().isInteger(); - } - - bool isMetaKindVariable() const { - return getNode().isVar(); - } - - bool operator<(const Variable& v) const { - VariableNodeCmp cmp; - return cmp(this->getNode(), v.getNode()); - } - - struct VariableNodeCmp { - static inline int cmp(const Node& n, const Node& m) { - if ( n == m ) { return 0; } - - // RAN < real var < int var < non-variable - - bool nIsRAN = n.getKind() == Kind::REAL_ALGEBRAIC_NUMBER; - bool mIsRAN = m.getKind() == Kind::REAL_ALGEBRAIC_NUMBER; - - if (mIsRAN != nIsRAN) - { - return nIsRAN ? -1 : 1; - } - - bool nIsInteger = n.getType().isInteger(); - bool mIsInteger = m.getType().isInteger(); - - if(nIsInteger == mIsInteger){ - bool nIsVariable = n.isVar(); - bool mIsVariable = m.isVar(); - - if(nIsVariable == mIsVariable){ - if(n < m){ - return -1; - }else{ - Assert(n != m); - return 1; - } - }else{ - if(nIsVariable){ - return -1; // nIsVariable => !mIsVariable - }else{ - return 1; // !nIsVariable => mIsVariable - } - } - }else{ - Assert(nIsInteger != mIsInteger); - if(nIsInteger){ - return 1; // nIsInteger => !mIsInteger - }else{ - return -1; // !nIsInteger => mIsInteger - } - } - } - - bool operator()(const Node& n, const Node& m) const { - return VariableNodeCmp::cmp(n,m) < 0; - } - }; - - bool operator==(const Variable& v) const { return getNode() == v.getNode();} - - size_t getComplexity() const; -};/* class Variable */ - -class Constant : public NodeWrapper { -public: - Constant(Node n) : NodeWrapper(n) { Assert(isMember(getNode())); } - - static bool isMember(Node n) { return n.getKind() == kind::CONST_RATIONAL; } - - bool isNormalForm() { return isMember(getNode()); } - - static Constant mkConstant(Node n) - { - Assert(n.getKind() == kind::CONST_RATIONAL); - return Constant(n); - } - - static Constant mkConstant(const Rational& rat); - - static Constant mkZero() { - return mkConstant(Rational(0)); - } - - static Constant mkOne() { - return mkConstant(Rational(1)); - } - - const Rational& getValue() const { - return getNode().getConst(); - } - - static int absCmp(const Constant& a, const Constant& b); - bool isIntegral() const { return getValue().isIntegral(); } - - int sgn() const { return getValue().sgn(); } - - bool isZero() const { return sgn() == 0; } - bool isNegative() const { return sgn() < 0; } - bool isPositive() const { return sgn() > 0; } - - bool isOne() const { return getValue() == 1; } - - Constant operator*(const Rational& other) const { - return mkConstant(getValue() * other); - } - - Constant operator*(const Constant& other) const { - return mkConstant(getValue() * other.getValue()); - } - Constant operator+(const Constant& other) const { - return mkConstant(getValue() + other.getValue()); - } - Constant operator-() const { - return mkConstant(-getValue()); - } - - Constant inverse() const{ - Assert(!isZero()); - return mkConstant(getValue().inverse()); - } - - bool operator<(const Constant& other) const { - return getValue() < other.getValue(); - } - - bool operator==(const Constant& other) const { - //Rely on node uniqueness. - return getNode() == other.getNode(); - } - - Constant abs() const { - if(isNegative()){ - return -(*this); - }else{ - return (*this); - } - } - - uint32_t length() const{ - Assert(isIntegral()); - return getValue().getNumerator().length(); - } - - size_t getComplexity() const; - -};/* class Constant */ - - -template -inline Node makeNode(Kind k, GetNodeIterator start, GetNodeIterator end) { - NodeBuilder nb(k); - - while(start != end) { - nb << (*start).getNode(); - ++start; - } - - return Node(nb); -}/* makeNode(Kind, iterator, iterator) */ - -/** - * A VarList is a sorted list of variables representing a product. - * If the VarList is empty, it represents an empty product or 1. - * If the VarList has size 1, it represents a single variable. - * - * A non-sorted VarList can never be successfully made in debug mode. - */ -class VarList : public NodeWrapper { -private: - - static Node multList(const std::vector& list) { - Assert(list.size() >= 2); - - return makeNode(kind::NONLINEAR_MULT, list.begin(), list.end()); - } - - VarList() : NodeWrapper(Node::null()) {} - - VarList(Node n); - - typedef expr::NodeSelfIterator internal_iterator; - - internal_iterator internalBegin() const { - if(singleton()){ - return expr::NodeSelfIterator::self(getNode()); - }else{ - return getNode().begin(); - } - } - - internal_iterator internalEnd() const { - if(singleton()){ - return expr::NodeSelfIterator::selfEnd(getNode()); - }else{ - return getNode().end(); - } - } - -public: - - class iterator { - private: - internal_iterator d_iter; - - public: - /* The following types are required by trait std::iterator_traits */ - - /** Iterator tag */ - using iterator_category = std::forward_iterator_tag; - - /** The type of the item */ - using value_type = Variable; - - /** The pointer type of the item */ - using pointer = Variable*; - - /** The reference type of the item */ - using reference = Variable&; - - /** The type returned when two iterators are subtracted */ - using difference_type = std::ptrdiff_t; - - /* End of std::iterator_traits required types */ - - explicit iterator(internal_iterator i) : d_iter(i) {} - - inline Variable operator*() { - return Variable(*d_iter); - } - - bool operator==(const iterator& i) { - return d_iter == i.d_iter; - } - - bool operator!=(const iterator& i) { - return d_iter != i.d_iter; - } - - iterator operator++() { - ++d_iter; - return *this; - } - - iterator operator++(int) { - return iterator(d_iter++); - } - }; - - iterator begin() const { - return iterator(internalBegin()); - } - - iterator end() const { - return iterator(internalEnd()); - } - - Variable getHead() const { - Assert(!empty()); - return *(begin()); - } - - VarList(Variable v) : NodeWrapper(v.getNode()) { - Assert(isSorted(begin(), end())); - } - - VarList(const std::vector& l) : NodeWrapper(multList(l)) { - Assert(l.size() >= 2); - Assert(isSorted(begin(), end())); - } - - static bool isMember(Node n); - - bool isNormalForm() const { - return !empty(); - } - - static VarList mkEmptyVarList() { - return VarList(); - } - - - /** There are no restrictions on the size of l */ - static VarList mkVarList(const std::vector& l) { - if(l.size() == 0) { - return mkEmptyVarList(); - } else if(l.size() == 1) { - return VarList((*l.begin()).getNode()); - } else { - return VarList(l); - } - } - - bool empty() const { return getNode().isNull(); } - bool singleton() const { - return !empty() && getNode().getKind() != kind::NONLINEAR_MULT; - } - - int size() const { - if(singleton()) - return 1; - else - return getNode().getNumChildren(); - } - - static VarList parseVarList(Node n); - - VarList operator*(const VarList& vl) const; - - int cmp(const VarList& vl) const; - - bool operator<(const VarList& vl) const { return cmp(vl) < 0; } - - bool operator==(const VarList& vl) const { return cmp(vl) == 0; } - - bool isIntegral() const { - for(iterator i = begin(), e=end(); i != e; ++i ){ - Variable var = *i; - if(!var.isIntegral()){ - return false; - } - } - return true; - } - size_t getComplexity() const; - -private: - bool isSorted(iterator start, iterator end); - -};/* class VarList */ - - -/** Constructors have side conditions. Use the static mkMonomial functions instead. */ -class Monomial : public NodeWrapper { -private: - Constant constant; - VarList varList; - Monomial(Node n, const Constant& c, const VarList& vl): - NodeWrapper(n), constant(c), varList(vl) - { - Assert(!c.isZero() || vl.empty()); - Assert(c.isZero() || !vl.empty()); - - Assert(!c.isOne() || !multStructured(n)); - } - - static Node makeMultNode(const Constant& c, const VarList& vl) { - Assert(!c.isZero()); - Assert(!c.isOne()); - Assert(!vl.empty()); - return NodeManager::currentNM()->mkNode(kind::MULT, c.getNode(), vl.getNode()); - } - - static bool multStructured(Node n) { - return n.getKind() == kind::MULT && - n[0].getKind() == kind::CONST_RATIONAL && - n.getNumChildren() == 2; - } - - Monomial(const Constant& c): - NodeWrapper(c.getNode()), constant(c), varList(VarList::mkEmptyVarList()) - { } - - Monomial(const VarList& vl): - NodeWrapper(vl.getNode()), constant(Constant::mkConstant(1)), varList(vl) - { - Assert(!varList.empty()); - } - - Monomial(const Constant& c, const VarList& vl): - NodeWrapper(makeMultNode(c,vl)), constant(c), varList(vl) - { - Assert(!c.isZero()); - Assert(!c.isOne()); - Assert(!varList.empty()); - - Assert(multStructured(getNode())); - } -public: - static bool isMember(TNode n); - - /** Makes a monomial with no restrictions on c and vl. */ - static Monomial mkMonomial(const Constant& c, const VarList& vl); - - /** If vl is empty, this make one. */ - static Monomial mkMonomial(const VarList& vl); - - static Monomial mkMonomial(const Constant& c){ - return Monomial(c); - } - - static Monomial mkMonomial(const Variable& v){ - return Monomial(VarList(v)); - } - - static Monomial parseMonomial(Node n); - - static Monomial mkZero() { - return Monomial(Constant::mkConstant(0)); - } - static Monomial mkOne() { - return Monomial(Constant::mkConstant(1)); - } - const Constant& getConstant() const { return constant; } - const VarList& getVarList() const { return varList; } - - bool isConstant() const { - return varList.empty(); - } - - bool isZero() const { - return constant.isZero(); - } - - bool coefficientIsOne() const { - return constant.isOne(); - } - - bool absCoefficientIsOne() const { - return coefficientIsOne() || constant.getValue() == -1; - } - - bool constantIsPositive() const { - return getConstant().isPositive(); - } - - Monomial operator*(const Rational& q) const; - Monomial operator*(const Constant& c) const; - Monomial operator*(const Monomial& mono) const; - - Monomial operator-() const{ - return (*this) * Rational(-1); - } - - - int cmp(const Monomial& mono) const { - return getVarList().cmp(mono.getVarList()); - } - - bool operator<(const Monomial& vl) const { - return cmp(vl) < 0; - } - - bool operator==(const Monomial& vl) const { - return cmp(vl) == 0; - } - - static bool isSorted(const std::vector& m) { - return std::is_sorted(m.begin(), m.end()); - } - - static bool isStrictlySorted(const std::vector& m) { - return isSorted(m) && std::adjacent_find(m.begin(),m.end()) == m.end(); - } - - static void sort(std::vector& m); - static void combineAdjacentMonomials(std::vector& m); - - /** - * The variable product - */ - bool integralVariables() const { - return getVarList().isIntegral(); - } - - /** - * The coefficient of the monomial is integral. - */ - bool integralCoefficient() const { - return getConstant().isIntegral(); - } - - /** - * A Monomial is an "integral" monomial if the constant is integral. - */ - bool isIntegral() const { - return integralCoefficient() && integralVariables(); - } - - /** Returns true if the VarList is a product of at least 2 Variables.*/ - bool isNonlinear() const { - return getVarList().size() >= 2; - } - - /** - * Given a sorted list of monomials, this function transforms this - * into a strictly sorted list of monomials that does not contain zero. - */ - //static std::vector sumLikeTerms(const std::vector& monos); - - int absCmp(const Monomial& other) const{ - return getConstant().getValue().absCmp(other.getConstant().getValue()); - } - // bool absLessThan(const Monomial& other) const{ - // return getConstant().abs() < other.getConstant().abs(); - // } - - uint32_t coefficientLength() const{ - return getConstant().length(); - } - - void print() const; - static void printList(const std::vector& list); - - size_t getComplexity() const; -};/* class Monomial */ - -class SumPair; -class Comparison;; - -class Polynomial : public NodeWrapper { -private: - bool d_singleton; - - Polynomial(TNode n) : NodeWrapper(n), d_singleton(Monomial::isMember(n)) { - Assert(isMember(getNode())); - } - - static Node makePlusNode(const std::vector& m) { - Assert(m.size() >= 2); - - return makeNode(kind::ADD, m.begin(), m.end()); - } - - typedef expr::NodeSelfIterator internal_iterator; - - internal_iterator internalBegin() const { - if(singleton()){ - return expr::NodeSelfIterator::self(getNode()); - }else{ - return getNode().begin(); - } - } - - internal_iterator internalEnd() const { - if(singleton()){ - return expr::NodeSelfIterator::selfEnd(getNode()); - }else{ - return getNode().end(); - } - } - - bool singleton() const { return d_singleton; } - -public: - static bool isMember(TNode n); - - class iterator { - private: - internal_iterator d_iter; - - public: - /* The following types are required by trait std::iterator_traits */ - - /** Iterator tag */ - using iterator_category = std::forward_iterator_tag; - - /** The type of the item */ - using value_type = Monomial; - - /** The pointer type of the item */ - using pointer = Monomial*; - - /** The reference type of the item */ - using reference = Monomial&; - - /** The type returned when two iterators are subtracted */ - using difference_type = std::ptrdiff_t; - - /* End of std::iterator_traits required types */ - - explicit iterator(internal_iterator i) : d_iter(i) {} - - inline Monomial operator*() { - return Monomial::parseMonomial(*d_iter); - } - - bool operator==(const iterator& i) { - return d_iter == i.d_iter; - } - - bool operator!=(const iterator& i) { - return d_iter != i.d_iter; - } - - iterator operator++() { - ++d_iter; - return *this; - } - - iterator operator++(int) { - return iterator(d_iter++); - } - }; - - iterator begin() const { return iterator(internalBegin()); } - iterator end() const { return iterator(internalEnd()); } - - Polynomial(const Monomial& m): - NodeWrapper(m.getNode()), d_singleton(true) - {} - - Polynomial(const std::vector& m): - NodeWrapper(makePlusNode(m)), d_singleton(false) - { - Assert(m.size() >= 2); - Assert(Monomial::isStrictlySorted(m)); - } - - static Polynomial mkPolynomial(const Constant& c){ - return Polynomial(Monomial::mkMonomial(c)); - } - - static Polynomial mkPolynomial(const Variable& v){ - return Polynomial(Monomial::mkMonomial(v)); - } - - static Polynomial mkPolynomial(const std::vector& m) { - if(m.size() == 0) { - return Polynomial(Monomial::mkZero()); - } else if(m.size() == 1) { - return Polynomial((*m.begin())); - } else { - return Polynomial(m); - } - } - - static Polynomial parsePolynomial(Node n) { - return Polynomial(n); - } - - static Polynomial mkZero() { - return Polynomial(Monomial::mkZero()); - } - static Polynomial mkOne() { - return Polynomial(Monomial::mkOne()); - } - bool isZero() const { - return singleton() && (getHead().isZero()); - } - - bool isConstant() const { - return singleton() && (getHead().isConstant()); - } - - bool containsConstant() const { - return getHead().isConstant(); - } - - uint32_t size() const{ - if(singleton()){ - return 1; - }else{ - Assert(getNode().getKind() == kind::ADD); - return getNode().getNumChildren(); - } - } - - Monomial getHead() const { - return *(begin()); - } - - Polynomial getTail() const { - Assert(!singleton()); - - iterator tailStart = begin(); - ++tailStart; - std::vector subrange; - std::copy(tailStart, end(), std::back_inserter(subrange)); - return mkPolynomial(subrange); - } - - Monomial minimumVariableMonomial() const; - bool variableMonomialAreStrictlyGreater(const Monomial& m) const; - - void printList() const { - if(TraceIsOn("normal-form")){ - Trace("normal-form") << "start list" << std::endl; - for(iterator i = begin(), oend = end(); i != oend; ++i) { - const Monomial& m =*i; - m.print(); - } - Trace("normal-form") << "end list" << std::endl; - } - } - - /** A Polynomial is an "integral" polynomial if all of the monomials are integral. */ - bool allIntegralVariables() const { - for(iterator i = begin(), e=end(); i!=e; ++i){ - if(!(*i).integralVariables()){ - return false; - } - } - return true; - } - - /** - * A Polynomial is an "integral" polynomial if all of the monomials are integral - * and all of the coefficients are Integral. */ - bool isIntegral() const { - for(iterator i = begin(), e=end(); i!=e; ++i){ - if(!(*i).isIntegral()){ - return false; - } - } - return true; - } - - static Polynomial sumPolynomials(const std::vector& polynomials); - - /** Returns true if the polynomial contains a non-linear monomial.*/ - bool isNonlinear() const; - - /** Check whether this polynomial is only a single variable. */ - bool isVariable() const - { - return singleton() && getHead().getVarList().singleton() - && getHead().coefficientIsOne(); - } - /** Return the variable, given that isVariable() holds. */ - Variable getVariable() const - { - Assert(isVariable()); - return getHead().getVarList().getHead(); - } - - /** - * Selects a minimal monomial in the polynomial by the absolute value of - * the coefficient. - */ - Monomial selectAbsMinimum() const; - - /** Returns true if the absolute value of the head coefficient is one. */ - bool leadingCoefficientIsAbsOne() const; - bool leadingCoefficientIsPositive() const; - bool denominatorLCMIsOne() const; - bool numeratorGCDIsOne() const; - - bool signNormalizedReducedSum() const { - return leadingCoefficientIsPositive() && denominatorLCMIsOne() && numeratorGCDIsOne(); - } - - /** - * Returns the Least Common Multiple of the denominators of the coefficients - * of the monomials. - */ - Integer denominatorLCM() const; - - /** - * Returns the GCD of the numerators of the monomials. - * Requires this to be an isIntegral() polynomial. - */ - Integer numeratorGCD() const; - - /** - * Returns the GCD of the coefficients of the monomials. - * Requires this to be an isIntegral() polynomial. - */ - Integer gcd() const; - - /** z must divide all of the coefficients of the polynomial. */ - Polynomial exactDivide(const Integer& z) const; - - Polynomial operator+(const Polynomial& vl) const; - Polynomial operator-(const Polynomial& vl) const; - Polynomial operator-() const{ - return (*this) * Rational(-1); - } - - Polynomial operator*(const Rational& q) const; - Polynomial operator*(const Constant& c) const; - Polynomial operator*(const Monomial& mono) const; - - Polynomial operator*(const Polynomial& poly) const; - - /** - * Viewing the integer polynomial as a list [(* coeff_i mono_i)] - * The quotient and remainder of p divided by the non-zero integer z is: - * q := [(* floor(coeff_i/z) mono_i )] - * r := [(* rem(coeff_i/z) mono_i)] - * computeQR(p,z) returns the node (+ q r). - * - * q and r are members of the Polynomial class. - * For example: - * computeQR( p = (+ 5 (* 3 x) (* 8 y)) , z = 2) returns - * (+ (+ 2 x (* 4 y)) (+ 1 x)) - */ - static Node computeQR(const Polynomial& p, const Integer& z); - - /** Returns the coefficient associated with the VarList in the polynomial. */ - Constant getCoefficient(const VarList& vl) const; - - uint32_t maxLength() const{ - iterator i = begin(), e=end(); - if( i == e){ - return 1; - }else{ - uint32_t max = (*i).coefficientLength(); - ++i; - for(; i!=e; ++i){ - uint32_t curr = (*i).coefficientLength(); - if(curr > max){ - max = curr; - } - } - return max; - } - } - - uint32_t numMonomials() const { - if (getNode().getKind() == kind::ADD) - { - return getNode().getNumChildren(); - } - else if (isZero()) - { - return 0; - } - else - { - return 1; - } - } - - const Rational& asConstant() const{ - Assert(isConstant()); - return getNode().getConst(); - //return getHead().getConstant().getValue(); - } - - bool isVarList() const { - if(singleton()){ - return VarList::isMember(getNode()); - }else{ - return false; - } - } - - VarList asVarList() const { - Assert(isVarList()); - return getHead().getVarList(); - } - - size_t getComplexity() const; - - friend class SumPair; - friend class Comparison; - - /** Returns a node that if asserted ensures v is the abs of this polynomial.*/ - Node makeAbsCondition(Variable v){ - return makeAbsCondition(v, *this); - } - - /** Returns a node that if asserted ensures v is the abs of p.*/ - static Node makeAbsCondition(Variable v, Polynomial p); - -};/* class Polynomial */ - - -/** - * SumPair is a utility class that extends polynomials for use in computations. - * A SumPair is always a combination of (+ p c) where - * c is a constant and p is a polynomial such that p = 0 or !p.containsConstant(). - * - * These are a useful utility for representing the equation p = c as (+ p -c) where the pair - * is known to implicitly be equal to 0. - * - * SumPairs do not have unique representations due to the potential for p = 0. - * This makes them inappropriate for normal forms. - */ -class SumPair : public NodeWrapper { -private: - static Node toNode(const Polynomial& p, const Constant& c){ - return NodeManager::currentNM()->mkNode( - kind::ADD, p.getNode(), c.getNode()); - } - - SumPair(TNode n) : NodeWrapper(n) { Assert(isNormalForm()); } - - public: - SumPair(const Polynomial& p): - NodeWrapper(toNode(p, Constant::mkConstant(0))) - { - Assert(isNormalForm()); - } - - SumPair(const Polynomial& p, const Constant& c): - NodeWrapper(toNode(p, c)) - { - Assert(isNormalForm()); - } - - static bool isMember(TNode n) { - if (n.getKind() == kind::ADD && n.getNumChildren() == 2) - { - if(Constant::isMember(n[1])){ - if(Polynomial::isMember(n[0])){ - Polynomial p = Polynomial::parsePolynomial(n[0]); - return p.isZero() || (!p.containsConstant()); - }else{ - return false; - } - }else{ - return false; - } - } - else - { - return false; - } - } - - bool isNormalForm() const { - return isMember(getNode()); - } - - Polynomial getPolynomial() const { - return Polynomial::parsePolynomial(getNode()[0]); - } - - Constant getConstant() const { - return Constant::mkConstant((getNode())[1]); - } - - SumPair operator+(const SumPair& other) const { - return SumPair(getPolynomial() + other.getPolynomial(), - getConstant() + other.getConstant()); - } - - SumPair operator*(const Constant& c) const { - return SumPair(getPolynomial() * c, getConstant() * c); - } - - SumPair operator-(const SumPair& other) const { - return (*this) + (other * Constant::mkConstant(-1)); - } - - static SumPair mkSumPair(const Polynomial& p); - - static SumPair mkSumPair(const Variable& var){ - return SumPair(Polynomial::mkPolynomial(var)); - } - - static SumPair parseSumPair(TNode n){ - return SumPair(n); - } - - bool isIntegral() const{ - return getConstant().isIntegral() && getPolynomial().isIntegral(); - } - - bool isConstant() const { - return getPolynomial().isZero(); - } - - bool isZero() const { - return getConstant().isZero() && isConstant(); - } - - uint32_t size() const{ - return getPolynomial().size(); - } - - bool isNonlinear() const{ - return getPolynomial().isNonlinear(); - } - - /** - * Returns the greatest common divisor of gcd(getPolynomial()) and getConstant(). - * The SumPair must be integral. - */ - Integer gcd() const { - Assert(isIntegral()); - return (getPolynomial().gcd()).gcd(getConstant().getValue().getNumerator()); - } - - uint32_t maxLength() const { - Assert(isIntegral()); - return std::max(getPolynomial().maxLength(), getConstant().length()); - } - - static SumPair mkZero() { - return SumPair(Polynomial::mkZero(), Constant::mkConstant(0)); - } - - static Node computeQR(const SumPair& sp, const Integer& div); - -};/* class SumPair */ - -/* class OrderedPolynomialPair { */ -/* private: */ -/* Polynomial d_first; */ -/* Polynomial d_second; */ -/* public: */ -/* OrderedPolynomialPair(const Polynomial& f, const Polynomial& s) */ -/* : d_first(f), */ -/* d_second(s) */ -/* {} */ - -/* /\** Returns the first part of the pair. *\/ */ -/* const Polynomial& getFirst() const { */ -/* return d_first; */ -/* } */ - -/* /\** Returns the second part of the pair. *\/ */ -/* const Polynomial& getSecond() const { */ -/* return d_second; */ -/* } */ - -/* OrderedPolynomialPair operator*(const Constant& c) const; */ -/* OrderedPolynomialPair operator+(const Polynomial& p) const; */ - -/* /\** Returns true if both of the polynomials are constant. *\/ */ -/* bool isConstant() const; */ - -/* /\** */ -/* * Evaluates an isConstant() ordered pair as if */ -/* * (k getFirst() getRight()) */ -/* *\/ */ -/* bool evaluateConstant(Kind k) const; */ - -/* /\** */ -/* * Returns the Least Common Multiple of the monomials */ -/* * on the lefthand side and the constant on the right. */ -/* *\/ */ -/* Integer denominatorLCM() const; */ - -/* /\** Constructs a SumPair. *\/ */ -/* SumPair toSumPair() const; */ - - -/* OrderedPolynomialPair divideByGCD() const; */ -/* OrderedPolynomialPair multiplyConstant(const Constant& c) const; */ - -/* /\** */ -/* * Returns true if all of the variables are integers, */ -/* * and the coefficients are integers. */ -/* *\/ */ -/* bool isIntegral() const; */ - -/* /\** Returns true if all of the variables are integers. *\/ */ -/* bool allIntegralVariables() const { */ -/* return getFirst().allIntegralVariables() && getSecond().allIntegralVariables(); */ -/* } */ -/* }; */ - -class Comparison : public NodeWrapper { -private: - - static Node toNode(Kind k, const Polynomial& l, const Constant& c); - static Node toNode(Kind k, const Polynomial& l, const Polynomial& r); - - Comparison(TNode n); - - /** - * Creates a node in normal form equivalent to (= l 0). - * All variables in l are integral. - */ - static Node mkIntEquality(const Polynomial& l); - - /** - * Creates a comparison equivalent to (k l 0). - * k is either GT or GEQ. - * All variables in l are integral. - */ - static Node mkIntInequality(Kind k, const Polynomial& l); - - /** - * Creates a node equivalent to (= l 0). - * It is not the case that all variables in l are integral. - */ - static Node mkRatEquality(const Polynomial& l); - - /** - * Creates a comparison equivalent to (k l 0). - * k is either GT or GEQ. - * It is not the case that all variables in l are integral. - */ - static Node mkRatInequality(Kind k, const Polynomial& l); - -public: - - Comparison(bool val) : - NodeWrapper(NodeManager::currentNM()->mkConst(val)) - { } - - /** - * Given a literal to TheoryArith return a single kind to - * to indicate its underlying structure. - * The function returns the following in each case: - * - (K left right) -> K where is either EQUAL, GT, or GEQ - * - (CONST_BOOLEAN b) -> CONST_BOOLEAN - * - (NOT (EQUAL left right)) -> DISTINCT - * - (NOT (GT left right)) -> LEQ - * - (NOT (GEQ left right)) -> LT - * If none of these match, it returns UNDEFINED_KIND. - */ - static Kind comparisonKind(TNode literal); - - Kind comparisonKind() const { return comparisonKind(getNode()); } - - static Comparison mkComparison(Kind k, const Polynomial& l, const Polynomial& r); - - /** Returns true if the comparison is a boolean constant. */ - bool isBoolean() const; - - /** - * Returns true if the comparison is either a boolean term, - * in integer normal form or mixed normal form. - */ - bool isNormalForm() const; - -private: - bool isNormalGT() const; - bool isNormalGEQ() const; - - bool isNormalLT() const; - bool isNormalLEQ() const; - - bool isNormalEquality() const; - bool isNormalDistinct() const; - bool isNormalEqualityOrDisequality() const; - - bool allIntegralVariables() const { - return getLeft().allIntegralVariables() && getRight().allIntegralVariables(); - } - bool rightIsConstant() const; - -public: - Polynomial getLeft() const; - Polynomial getRight() const; - - /* /\** Normal form check if at least one variable is real. *\/ */ - /* bool isMixedCompareNormalForm() const; */ - - /* /\** Normal form check if at least one variable is real. *\/ */ - /* bool isMixedEqualsNormalForm() const; */ - - /* /\** Normal form check is all variables are integer.*\/ */ - /* bool isIntegerCompareNormalForm() const; */ - - /* /\** Normal form check is all variables are integer.*\/ */ - /* bool isIntegerEqualsNormalForm() const; */ - - - /** - * Returns true if all of the variables are integers, the coefficients are integers, - * and the right hand coefficient is an integer. - */ - bool debugIsIntegral() const; - - static Comparison parseNormalForm(TNode n); - - inline static bool isNormalAtom(TNode n){ - Comparison parse = Comparison::parseNormalForm(n); - return parse.isNormalForm(); - } - - size_t getComplexity() const; - - SumPair toSumPair() const; - - Polynomial normalizedVariablePart() const; - DeltaRational normalizedDeltaRational() const; - - /** - * Transforms a Comparison object into a stronger normal form: - * Polynomial ~Kind~ Constant - * - * From the comparison, this method resolved a negation (if present) and - * moves everything to the left side. - * If split_constant is false, the constant is always zero. - * If split_constant is true, the polynomial has no constant term and is - * normalized to have leading coefficient one. - */ - std::tuple decompose( - bool split_constant = false) const; - -};/* class Comparison */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__NORMAL_FORM_H */ diff --git a/src/theory/arith/partial_model.cpp b/src/theory/arith/partial_model.cpp deleted file mode 100644 index 65330381c..000000000 --- a/src/theory/arith/partial_model.cpp +++ /dev/null @@ -1,691 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "base/output.h" -#include "theory/arith/constraint.h" -#include "theory/arith/normal_form.h" -#include "theory/arith/partial_model.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -ArithVariables::ArithVariables(context::Context* c, - DeltaComputeCallback deltaComputingFunc) - : d_vars(), - d_safeAssignment(), - d_numberOfVariables(0), - d_pool(), - d_released(), - d_nodeToArithVarMap(), - d_boundsQueue(), - d_enqueueingBoundCounts(true), - d_lbRevertHistory(c, true, LowerBoundCleanUp(this)), - d_ubRevertHistory(c, true, UpperBoundCleanUp(this)), - d_deltaIsSafe(false), - d_delta(-1, 1), - d_deltaComputingFunc(deltaComputingFunc) -{ } - -ArithVar ArithVariables::getNumberOfVariables() const { - return d_numberOfVariables; -} - - -bool ArithVariables::hasArithVar(TNode x) const { - return d_nodeToArithVarMap.find(x) != d_nodeToArithVarMap.end(); -} - -bool ArithVariables::hasNode(ArithVar a) const { - return d_vars.isKey(a); -} - -ArithVar ArithVariables::asArithVar(TNode x) const{ - Assert(hasArithVar(x)); - Assert((d_nodeToArithVarMap.find(x))->second <= ARITHVAR_SENTINEL); - return (d_nodeToArithVarMap.find(x))->second; -} - -Node ArithVariables::asNode(ArithVar a) const{ - Assert(hasNode(a)); - return d_vars[a].d_node; -} - -ArithVariables::var_iterator::var_iterator() - : d_vars(NULL) - , d_wrapped() -{} - -ArithVariables::var_iterator::var_iterator(const VarInfoVec* vars, VarInfoVec::const_iterator ci) - : d_vars(vars), d_wrapped(ci) -{ - nextInitialized(); -} - -ArithVariables::var_iterator& ArithVariables::var_iterator::operator++(){ - ++d_wrapped; - nextInitialized(); - return *this; -} -bool ArithVariables::var_iterator::operator==(const ArithVariables::var_iterator& other) const{ - return d_wrapped == other.d_wrapped; -} -bool ArithVariables::var_iterator::operator!=(const ArithVariables::var_iterator& other) const{ - return d_wrapped != other.d_wrapped; -} -ArithVar ArithVariables::var_iterator::operator*() const{ - return *d_wrapped; -} - -void ArithVariables::var_iterator::nextInitialized(){ - VarInfoVec::const_iterator end = d_vars->end(); - while(d_wrapped != end && - !((*d_vars)[*d_wrapped].initialized())){ - ++d_wrapped; - } -} - -ArithVariables::var_iterator ArithVariables::var_begin() const { - return var_iterator(&d_vars, d_vars.begin()); -} - -ArithVariables::var_iterator ArithVariables::var_end() const { - return var_iterator(&d_vars, d_vars.end()); -} -bool ArithVariables::isInteger(ArithVar x) const { - return d_vars[x].d_type >= ArithType::Integer; -} - -/** Is the assignment to x integral? */ -bool ArithVariables::integralAssignment(ArithVar x) const { - return getAssignment(x).isIntegral(); -} -bool ArithVariables::isAuxiliary(ArithVar x) const { - return d_vars[x].d_auxiliary; -} - -bool ArithVariables::isIntegerInput(ArithVar x) const { - return isInteger(x) && !isAuxiliary(x); -} - -ArithVariables::VarInfo::VarInfo() - : d_var(ARITHVAR_SENTINEL), - d_assignment(0), - d_lb(NullConstraint), - d_ub(NullConstraint), - d_cmpAssignmentLB(1), - d_cmpAssignmentUB(-1), - d_pushCount(0), - d_type(ArithType::Unset), - d_node(Node::null()), - d_auxiliary(false) {} - -bool ArithVariables::VarInfo::initialized() const { - return d_var != ARITHVAR_SENTINEL; -} - -void ArithVariables::VarInfo::initialize(ArithVar v, Node n, bool aux){ - Assert(!initialized()); - Assert(d_lb == NullConstraint); - Assert(d_ub == NullConstraint); - Assert(d_cmpAssignmentLB > 0); - Assert(d_cmpAssignmentUB < 0); - d_var = v; - d_node = n; - d_auxiliary = aux; - - if(d_auxiliary){ - //The type computation is not quite accurate for Rationals that are - //integral. - //We'll use the isIntegral check from the polynomial package instead. - Polynomial p = Polynomial::parsePolynomial(n); - d_type = p.isIntegral() ? ArithType::Integer : ArithType::Real; - }else{ - d_type = n.getType().isInteger() ? ArithType::Integer : ArithType::Real; - } - - Assert(initialized()); -} - -void ArithVariables::VarInfo::uninitialize(){ - d_var = ARITHVAR_SENTINEL; - d_node = Node::null(); -} - -bool ArithVariables::VarInfo::setAssignment(const DeltaRational& a, BoundsInfo& prev){ - Assert(initialized()); - d_assignment = a; - int cmpUB = (d_ub == NullConstraint) ? -1 : - d_assignment.cmp(d_ub->getValue()); - - int cmpLB = (d_lb == NullConstraint) ? 1 : - d_assignment.cmp(d_lb->getValue()); - - bool lbChanged = cmpLB != d_cmpAssignmentLB && - (cmpLB == 0 || d_cmpAssignmentLB == 0); - bool ubChanged = cmpUB != d_cmpAssignmentUB && - (cmpUB == 0 || d_cmpAssignmentUB == 0); - - if(lbChanged || ubChanged){ - prev = boundsInfo(); - } - - d_cmpAssignmentUB = cmpUB; - d_cmpAssignmentLB = cmpLB; - return lbChanged || ubChanged; -} - -void ArithVariables::releaseArithVar(ArithVar v){ - VarInfo& vi = d_vars.get(v); - - size_t removed CVC5_UNUSED = d_nodeToArithVarMap.erase(vi.d_node); - Assert(removed == 1); - - vi.uninitialize(); - - if(d_safeAssignment.isKey(v)){ - d_safeAssignment.remove(v); - } - if(vi.canBeReclaimed()){ - d_pool.push_back(v); - }else{ - d_released.push_back(v); - } -} - -bool ArithVariables::VarInfo::setUpperBound(ConstraintP ub, BoundsInfo& prev){ - Assert(initialized()); - bool wasNull = d_ub == NullConstraint; - bool isNull = ub == NullConstraint; - - int cmpUB = isNull ? -1 : d_assignment.cmp(ub->getValue()); - bool ubChanged = (wasNull != isNull) || - (cmpUB != d_cmpAssignmentUB && (cmpUB == 0 || d_cmpAssignmentUB == 0)); - if(ubChanged){ - prev = boundsInfo(); - } - d_ub = ub; - d_cmpAssignmentUB = cmpUB; - return ubChanged; -} - -bool ArithVariables::VarInfo::setLowerBound(ConstraintP lb, BoundsInfo& prev){ - Assert(initialized()); - bool wasNull = d_lb == NullConstraint; - bool isNull = lb == NullConstraint; - - int cmpLB = isNull ? 1 : d_assignment.cmp(lb->getValue()); - - bool lbChanged = (wasNull != isNull) || - (cmpLB != d_cmpAssignmentLB && (cmpLB == 0 || d_cmpAssignmentLB == 0)); - if(lbChanged){ - prev = boundsInfo(); - } - d_lb = lb; - d_cmpAssignmentLB = cmpLB; - return lbChanged; -} - -BoundCounts ArithVariables::VarInfo::atBoundCounts() const { - uint32_t lbIndc = (d_cmpAssignmentLB == 0) ? 1 : 0; - uint32_t ubIndc = (d_cmpAssignmentUB == 0) ? 1 : 0; - return BoundCounts(lbIndc, ubIndc); -} - -BoundCounts ArithVariables::VarInfo::hasBoundCounts() const { - uint32_t lbIndc = (d_lb != NullConstraint) ? 1 : 0; - uint32_t ubIndc = (d_ub != NullConstraint) ? 1 : 0; - return BoundCounts(lbIndc, ubIndc); -} - -BoundsInfo ArithVariables::VarInfo::boundsInfo() const{ - return BoundsInfo(atBoundCounts(), hasBoundCounts()); -} - -bool ArithVariables::VarInfo::canBeReclaimed() const{ - return d_pushCount == 0; -} - -bool ArithVariables::canBeReleased(ArithVar v) const{ - return d_vars[v].canBeReclaimed(); -} - -void ArithVariables::attemptToReclaimReleased(){ - size_t readPos = 0, writePos = 0, N = d_released.size(); - for(; readPos < N; ++readPos){ - ArithVar v = d_released[readPos]; - if(canBeReleased(v)){ - d_pool.push_back(v); - }else{ - d_released[writePos] = v; - writePos++; - } - } - d_released.resize(writePos); -} - -ArithVar ArithVariables::allocateVariable(){ - if(d_pool.empty()){ - attemptToReclaimReleased(); - } - bool reclaim = !d_pool.empty(); - - ArithVar varX; - if(reclaim){ - varX = d_pool.back(); - d_pool.pop_back(); - }else{ - varX = d_numberOfVariables; - ++d_numberOfVariables; - } - d_vars.set(varX, VarInfo()); - return varX; -} - - -const Rational& ArithVariables::getDelta(){ - if(!d_deltaIsSafe){ - Rational nextDelta = d_deltaComputingFunc(); - setDelta(nextDelta); - } - Assert(d_deltaIsSafe); - return d_delta; -} - -bool ArithVariables::boundsAreEqual(ArithVar x) const{ - if(hasLowerBound(x) && hasUpperBound(x)){ - return getUpperBound(x) == getLowerBound(x); - }else{ - return false; - } -} - - -std::pair ArithVariables::explainEqualBounds(ArithVar x) const{ - Assert(boundsAreEqual(x)); - - ConstraintP lb = getLowerBoundConstraint(x); - ConstraintP ub = getUpperBoundConstraint(x); - if(lb->isEquality()){ - return make_pair(lb, NullConstraint); - }else if(ub->isEquality()){ - return make_pair(ub, NullConstraint); - }else{ - return make_pair(lb, ub); - } -} - -void ArithVariables::setAssignment(ArithVar x, const DeltaRational& r){ - Trace("partial_model") << "pm: updating the assignment to" << x - << " now " << r <getValue(); -} - -const DeltaRational& ArithVariables::getLowerBound(ArithVar x) const { - Assert(inMaps(x)); - Assert(hasLowerBound(x)); - - return getLowerBoundConstraint(x)->getValue(); -} - -const DeltaRational& ArithVariables::getSafeAssignment(ArithVar x) const{ - Assert(inMaps(x)); - if(d_safeAssignment.isKey(x)){ - return d_safeAssignment[x]; - }else{ - return d_vars[x].d_assignment; - } -} - -const DeltaRational& ArithVariables::getAssignment(ArithVar x, bool safe) const{ - Assert(inMaps(x)); - if(safe && d_safeAssignment.isKey(x)){ - return d_safeAssignment[x]; - }else{ - return d_vars[x].d_assignment; - } -} - -const DeltaRational& ArithVariables::getAssignment(ArithVar x) const{ - Assert(inMaps(x)); - return d_vars[x].d_assignment; -} - - -void ArithVariables::setLowerBoundConstraint(ConstraintP c){ - AssertArgument(c != NullConstraint, "Cannot set a lower bound to NullConstraint."); - AssertArgument(c->isEquality() || c->isLowerBound(), - "Constraint type must be set to an equality or UpperBound."); - ArithVar x = c->getVariable(); - Trace("partial_model") << "setLowerBoundConstraint(" << x << ":" << c << ")" << endl; - Assert(inMaps(x)); - Assert(greaterThanLowerBound(x, c->getValue())); - - invalidateDelta(); - VarInfo& vi = d_vars.get(x); - pushLowerBound(vi); - BoundsInfo prev; - if(vi.setLowerBound(c, prev)){ - addToBoundQueue(x, prev); - } -} - -void ArithVariables::setUpperBoundConstraint(ConstraintP c){ - AssertArgument(c != NullConstraint, "Cannot set a upper bound to NullConstraint."); - AssertArgument(c->isEquality() || c->isUpperBound(), - "Constraint type must be set to an equality or UpperBound."); - - ArithVar x = c->getVariable(); - Trace("partial_model") << "setUpperBoundConstraint(" << x << ":" << c << ")" << endl; - Assert(inMaps(x)); - Assert(lessThanUpperBound(x, c->getValue())); - - invalidateDelta(); - VarInfo& vi = d_vars.get(x); - pushUpperBound(vi); - BoundsInfo prev; - if(vi.setUpperBound(c, prev)){ - addToBoundQueue(x, prev); - } -} - -int ArithVariables::cmpToLowerBound(ArithVar x, const DeltaRational& c) const{ - if(!hasLowerBound(x)){ - // l = -\intfy - // ? c < -\infty |- _|_ - return 1; - }else{ - return c.cmp(getLowerBound(x)); - } -} - -int ArithVariables::cmpToUpperBound(ArithVar x, const DeltaRational& c) const{ - if(!hasUpperBound(x)){ - //u = \intfy - // ? c > \infty |- _|_ - return -1; - }else{ - return c.cmp(getUpperBound(x)); - } -} - -bool ArithVariables::equalsLowerBound(ArithVar x, const DeltaRational& c){ - if(!hasLowerBound(x)){ - return false; - }else{ - return c == getLowerBound(x); - } -} -bool ArithVariables::equalsUpperBound(ArithVar x, const DeltaRational& c){ - if(!hasUpperBound(x)){ - return false; - }else{ - return c == getUpperBound(x); - } -} - -bool ArithVariables::hasEitherBound(ArithVar x) const{ - return hasLowerBound(x) || hasUpperBound(x); -} - -bool ArithVariables::strictlyBelowUpperBound(ArithVar x) const{ - return d_vars[x].d_cmpAssignmentUB < 0; -} - -bool ArithVariables::strictlyAboveLowerBound(ArithVar x) const{ - return d_vars[x].d_cmpAssignmentLB > 0; -} - -bool ArithVariables::assignmentIsConsistent(ArithVar x) const{ - return - d_vars[x].d_cmpAssignmentLB >= 0 && - d_vars[x].d_cmpAssignmentUB <= 0; -} - - -void ArithVariables::clearSafeAssignments(bool revert){ - - if(revert && !d_safeAssignment.empty()){ - invalidateDelta(); - } - - while(!d_safeAssignment.empty()){ - ArithVar atBack = d_safeAssignment.back(); - if(revert){ - VarInfo& vi = d_vars.get(atBack); - BoundsInfo prev; - if(vi.setAssignment(d_safeAssignment[atBack], prev)){ - addToBoundQueue(atBack, prev); - } - } - d_safeAssignment.pop_back(); - } -} - -void ArithVariables::revertAssignmentChanges(){ - clearSafeAssignments(true); -} -void ArithVariables::commitAssignmentChanges(){ - clearSafeAssignments(false); -} - -bool ArithVariables::lowerBoundIsZero(ArithVar x){ - return hasLowerBound(x) && getLowerBound(x).sgn() == 0; -} - -bool ArithVariables::upperBoundIsZero(ArithVar x){ - return hasUpperBound(x) && getUpperBound(x).sgn() == 0; -} - -void ArithVariables::printEntireModel(std::ostream& out) const{ - out << "---Printing Model ---" << std::endl; - for(var_iterator i = var_begin(), iend = var_end(); i != iend; ++i){ - printModel(*i, out); - } - out << "---Done Model ---" << std::endl; -} - -void ArithVariables::printModel(ArithVar x, std::ostream& out) const{ - out << "model" << x << ": " - << asNode(x) << " " - << getAssignment(x) << " "; - if(!hasLowerBound(x)){ - out << "no lb "; - }else{ - out << getLowerBound(x) << " "; - out << getLowerBoundConstraint(x) << " "; - } - if(!hasUpperBound(x)){ - out << "no ub "; - }else{ - out << getUpperBound(x) << " "; - out << getUpperBoundConstraint(x) << " "; - } - - if(isInteger(x) && !integralAssignment(x)){ - out << "(not an integer)" << endl; - } - out << endl; -} - -void ArithVariables::printModel(ArithVar x) const{ - printModel(x, Trace("model")); -} - -void ArithVariables::pushUpperBound(VarInfo& vi){ - ++vi.d_pushCount; - d_ubRevertHistory.push_back(make_pair(vi.d_var, vi.d_ub)); -} -void ArithVariables::pushLowerBound(VarInfo& vi){ - ++vi.d_pushCount; - d_lbRevertHistory.push_back(make_pair(vi.d_var, vi.d_lb)); -} - -void ArithVariables::popUpperBound(AVCPair* c){ - ArithVar x = c->first; - VarInfo& vi = d_vars.get(x); - BoundsInfo prev; - if(vi.setUpperBound(c->second, prev)){ - addToBoundQueue(x, prev); - } - --vi.d_pushCount; -} - -void ArithVariables::popLowerBound(AVCPair* c){ - ArithVar x = c->first; - VarInfo& vi = d_vars.get(x); - BoundsInfo prev; - if(vi.setLowerBound(c->second, prev)){ - addToBoundQueue(x, prev); - } - --vi.d_pushCount; -} - -void ArithVariables::addToBoundQueue(ArithVar v, const BoundsInfo& prev){ - if(d_enqueueingBoundCounts && !d_boundsQueue.isKey(v)){ - d_boundsQueue.set(v, prev); - } -} - -BoundsInfo ArithVariables::selectBoundsInfo(ArithVar v, bool old) const { - if(old && d_boundsQueue.isKey(v)){ - return d_boundsQueue[v]; - }else{ - return boundsInfo(v); - } -} - -bool ArithVariables::boundsQueueEmpty() const { - return d_boundsQueue.empty(); -} - -void ArithVariables::processBoundsQueue(BoundUpdateCallback& changed){ - while(!boundsQueueEmpty()){ - ArithVar v = d_boundsQueue.back(); - BoundsInfo prev = d_boundsQueue[v]; - d_boundsQueue.pop_back(); - BoundsInfo curr = boundsInfo(v); - if(prev != curr){ - changed(v, prev); - } - } -} - -void ArithVariables::invalidateDelta() { - d_deltaIsSafe = false; -} - -void ArithVariables::setDelta(const Rational& d){ - d_delta = d; - d_deltaIsSafe = true; -} - -void ArithVariables::startQueueingBoundCounts(){ - d_enqueueingBoundCounts = true; -} -void ArithVariables::stopQueueingBoundCounts(){ - d_enqueueingBoundCounts = false; -} - -bool ArithVariables::inMaps(ArithVar x) const{ - return x < getNumberOfVariables(); -} - -ArithVariables::LowerBoundCleanUp::LowerBoundCleanUp(ArithVariables* pm) - : d_pm(pm) -{} -void ArithVariables::LowerBoundCleanUp::operator()(AVCPair* p){ - d_pm->popLowerBound(p); -} - -ArithVariables::UpperBoundCleanUp::UpperBoundCleanUp(ArithVariables* pm) - : d_pm(pm) -{} -void ArithVariables::UpperBoundCleanUp::operator()(AVCPair* p){ - d_pm->popUpperBound(p); -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/partial_model.h b/src/theory/arith/partial_model.h deleted file mode 100644 index 3f027e6b8..000000000 --- a/src/theory/arith/partial_model.h +++ /dev/null @@ -1,420 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Morgan Deters, Aina Niemetz - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * Datastructures that track variable by variable information. - * - * This is a datastructure that tracks variable specific information. - * This is partially context dependent to back track upper/lower bounds - * and information derived from these. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__PARTIAL_MODEL_H -#define CVC5__THEORY__ARITH__PARTIAL_MODEL_H - -#include - -#include "context/cdlist.h" -#include "expr/node.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/bound_counts.h" -#include "theory/arith/callbacks.h" -#include "theory/arith/constraint_forward.h" -#include "theory/arith/delta_rational.h" - -namespace cvc5::context { -class Context; -} -namespace cvc5::internal { -namespace theory { -namespace arith { - -/** - * (For the moment) the type hierarchy goes as: - * Integer <: Real - * The type number of a variable is an integer representing the most specific - * type of the variable. The possible values of type number are: - */ -enum class ArithType { - Unset, - Real, - Integer, -}; - -class ArithVariables { -private: - - class VarInfo { - friend class ArithVariables; - ArithVar d_var; - - DeltaRational d_assignment; - ConstraintP d_lb; - ConstraintP d_ub; - int d_cmpAssignmentLB; - int d_cmpAssignmentUB; - - unsigned d_pushCount; - ArithType d_type; - Node d_node; - bool d_auxiliary; - - public: - VarInfo(); - - bool setAssignment(const DeltaRational& r, BoundsInfo& prev); - bool setLowerBound(ConstraintP c, BoundsInfo& prev); - bool setUpperBound(ConstraintP c, BoundsInfo& prev); - - /** Returns true if this VarInfo has been initialized. */ - bool initialized() const; - - /** - * Initializes the VarInfo with the ArithVar index it is associated with, - * the node that the variable represents, and whether it is an auxillary - * variable. - */ - void initialize(ArithVar v, Node n, bool aux); - - /** Uninitializes the VarInfo. */ - void uninitialize(); - - bool canBeReclaimed() const; - - /** Indicator variables for if the assignment is equal to the upper - * and lower bounds. */ - BoundCounts atBoundCounts() const; - - /** Combination of indicator variables for whether it has upper and - * lower bounds. */ - BoundCounts hasBoundCounts() const; - - /** Stores both atBoundCounts() and hasBoundCounts(). */ - BoundsInfo boundsInfo() const; - }; - - /**Maps from ArithVar -> VarInfo */ - typedef DenseMap VarInfoVec; - - /** This maps an ArithVar to its Variable information.*/ - VarInfoVec d_vars; - - /** Partial Map from Arithvar -> PreviousAssignment */ - DenseMap d_safeAssignment; - - /** if d_vars.isKey(x), then x < d_numberOfVariables */ - ArithVar d_numberOfVariables; - - /** [0, d_numberOfVariables) \intersect d_vars.keys == d_pool */ - // Everything in the pool is fair game. - // There must be NO outstanding assertions - std::vector d_pool; - std::vector d_released; - //std::list::iterator d_releasedIterator; - - // Reverse Map from Node to ArithVar - // Inverse of d_vars[x].d_node - NodeToArithVarMap d_nodeToArithVarMap; - - - /** The queue of constraints where the assignment is at the bound.*/ - DenseMap d_boundsQueue; - - /** - * If this is true, record the incoming changes to the bound information. - * If this is false, the responsibility of recording the changes is - * LinearEqualities's. - */ - bool d_enqueueingBoundCounts; - - public: - - /** Returns the number of variables. */ - ArithVar getNumberOfVariables() const; - - /** Returns true if the node has an associated variables. */ - bool hasArithVar(TNode x) const; - - /** Returns true if the variable has a defining node. */ - bool hasNode(ArithVar a) const; - - /** Returns the ArithVar associated with a node. */ - ArithVar asArithVar(TNode x) const; - - /** Returns the node associated with an ArithVar. */ - Node asNode(ArithVar a) const; - - /** Allocates a freshly allocated variables. */ - ArithVar allocateVariable(); - - class var_iterator { - private: - const VarInfoVec* d_vars; - VarInfoVec::const_iterator d_wrapped; - public: - var_iterator(); - var_iterator(const VarInfoVec* vars, VarInfoVec::const_iterator ci); - var_iterator& operator++(); - - bool operator==(const var_iterator& other) const; - bool operator!=(const var_iterator& other) const; - ArithVar operator*() const; - - private: - void nextInitialized(); - }; - - var_iterator var_begin() const; - var_iterator var_end() const; - - - bool canBeReleased(ArithVar v) const; - void releaseArithVar(ArithVar v); - void attemptToReclaimReleased(); - - /** Is this variable guaranteed to have an integer assignment? - * (Should agree with the type system.) */ - bool isInteger(ArithVar x) const; - - /** Is the assignment to x integral? */ - bool integralAssignment(ArithVar x) const; - - /* Is this variable defined as a linear sum of other variables? */ - bool isAuxiliary(ArithVar x) const; - - /* Is the variable both input and not auxiliary? */ - bool isIntegerInput(ArithVar x) const; - - private: - - typedef std::pair AVCPair; - class LowerBoundCleanUp { - private: - ArithVariables* d_pm; - public: - LowerBoundCleanUp(ArithVariables* pm); - void operator()(AVCPair* restore); - }; - - class UpperBoundCleanUp { - private: - ArithVariables* d_pm; - public: - UpperBoundCleanUp(ArithVariables* pm); - void operator()(AVCPair* restore); - }; - - typedef context::CDList LBReverts; - LBReverts d_lbRevertHistory; - - typedef context::CDList UBReverts; - UBReverts d_ubRevertHistory; - - void pushUpperBound(VarInfo&); - void popUpperBound(AVCPair*); - void pushLowerBound(VarInfo&); - void popLowerBound(AVCPair*); - - // This is true when setDelta() is called, until invalidateDelta is called - bool d_deltaIsSafe; - // Cache of a value of delta to ensure a total order. - Rational d_delta; - // Function to call if the value of delta needs to be recomputed. - DeltaComputeCallback d_deltaComputingFunc; - - -public: - ArithVariables(context::Context* c, DeltaComputeCallback deltaComputation); - - /** - * This sets the lower bound for a variable in the current context. - * This must be stronger the previous constraint. - */ - void setLowerBoundConstraint(ConstraintP lb); - - /** - * This sets the upper bound for a variable in the current context. - * This must be stronger the previous constraint. - */ - void setUpperBoundConstraint(ConstraintP ub); - - /** Returns the constraint for the upper bound of a variable. */ - inline ConstraintP getUpperBoundConstraint(ArithVar x) const - { - return d_vars[x].d_ub; - } - /** Returns the constraint for the lower bound of a variable. */ - inline ConstraintP getLowerBoundConstraint(ArithVar x) const{ - return d_vars[x].d_lb; - } - - /* Initializes a variable to a safe value.*/ - void initialize(ArithVar x, Node n, bool aux); - - ArithVar allocate(Node n, bool aux = false); - - /* Gets the last assignment to a variable that is known to be consistent. */ - const DeltaRational& getSafeAssignment(ArithVar x) const; - const DeltaRational& getAssignment(ArithVar x, bool safe) const; - - /* Reverts all variable assignments to their safe values. */ - void revertAssignmentChanges(); - - /* Commits all variables assignments as safe.*/ - void commitAssignmentChanges(); - - - bool lowerBoundIsZero(ArithVar x); - bool upperBoundIsZero(ArithVar x); - - bool boundsAreEqual(ArithVar x) const; - - /* Sets an unsafe variable assignment */ - void setAssignment(ArithVar x, const DeltaRational& r); - void setAssignment(ArithVar x, const DeltaRational& safe, const DeltaRational& r); - - - /** Must know that the bound exists before calling this! */ - const DeltaRational& getUpperBound(ArithVar x) const; - const DeltaRational& getLowerBound(ArithVar x) const; - const DeltaRational& getAssignment(ArithVar x) const; - - - bool equalsLowerBound(ArithVar x, const DeltaRational& c); - bool equalsUpperBound(ArithVar x, const DeltaRational& c); - - /** - * If lowerbound > - \infty: - * return getAssignment(x).cmp(getLowerBound(x)) - * If lowerbound = - \infty: - * return 1 - */ - int cmpToLowerBound(ArithVar x, const DeltaRational& c) const; - - inline bool strictlyLessThanLowerBound(ArithVar x, const DeltaRational& c) const{ - return cmpToLowerBound(x, c) < 0; - } - inline bool lessThanLowerBound(ArithVar x, const DeltaRational& c) const{ - return cmpToLowerBound(x, c) <= 0; - } - - inline bool strictlyGreaterThanLowerBound(ArithVar x, const DeltaRational& c) const{ - return cmpToLowerBound(x, c) > 0; - } - - inline bool greaterThanLowerBound(ArithVar x, const DeltaRational& c) const{ - return cmpToLowerBound(x, c) >= 0; - } - /** - * If upperbound < \infty: - * return getAssignment(x).cmp(getUpperBound(x)) - * If upperbound = \infty: - * return -1 - */ - int cmpToUpperBound(ArithVar x, const DeltaRational& c) const; - - inline bool strictlyLessThanUpperBound(ArithVar x, const DeltaRational& c) const{ - return cmpToUpperBound(x, c) < 0; - } - - inline bool lessThanUpperBound(ArithVar x, const DeltaRational& c) const{ - return cmpToUpperBound(x, c) <= 0; - } - - inline bool strictlyGreaterThanUpperBound(ArithVar x, const DeltaRational& c) const{ - return cmpToUpperBound(x, c) > 0; - } - - inline bool greaterThanUpperBound(ArithVar x, const DeltaRational& c) const{ - return cmpToUpperBound(x, c) >= 0; - } - - inline int cmpAssignmentLowerBound(ArithVar x) const{ - return d_vars[x].d_cmpAssignmentLB; - } - inline int cmpAssignmentUpperBound(ArithVar x) const{ - return d_vars[x].d_cmpAssignmentUB; - } - - inline BoundCounts atBoundCounts(ArithVar x) const { - return d_vars[x].atBoundCounts(); - } - inline BoundCounts hasBoundCounts(ArithVar x) const { - return d_vars[x].hasBoundCounts(); - } - inline BoundsInfo boundsInfo(ArithVar x) const{ - return d_vars[x].boundsInfo(); - } - - bool strictlyBelowUpperBound(ArithVar x) const; - bool strictlyAboveLowerBound(ArithVar x) const; - bool assignmentIsConsistent(ArithVar x) const; - - void printModel(ArithVar x, std::ostream& out) const; - void printModel(ArithVar x) const; - - /** returns true iff x has both a lower and upper bound. */ - bool hasEitherBound(ArithVar x) const; - inline bool hasLowerBound(ArithVar x) const{ - return d_vars[x].d_lb != NullConstraint; - } - inline bool hasUpperBound(ArithVar x) const{ - return d_vars[x].d_ub != NullConstraint; - } - - const Rational& getDelta(); - - void invalidateDelta(); - - void setDelta(const Rational& d); - - void startQueueingBoundCounts(); - void stopQueueingBoundCounts(); - void addToBoundQueue(ArithVar v, const BoundsInfo& prev); - - BoundsInfo selectBoundsInfo(ArithVar v, bool old) const; - - bool boundsQueueEmpty() const; - void processBoundsQueue(BoundUpdateCallback& changed); - - void printEntireModel(std::ostream& out) const; - - - /** - * Precondition: assumes boundsAreEqual(x). - * If the either the lower/ upper bound is an equality, eq, - * this returns make_pair(eq, NullConstraint). - * Otherwise, this returns make_pair(lb, ub). - */ - std::pair explainEqualBounds(ArithVar x) const; - -private: - - /** - * This function implements the mostly identical: - * revertAssignmentChanges() and commitAssignmentChanges(). - */ - void clearSafeAssignments(bool revert); - - bool debugEqualSizes(); - - bool inMaps(ArithVar x) const; - -};/* class ArithVariables */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal - -#endif /* CVC5__THEORY__ARITH__PARTIAL_MODEL_H */ diff --git a/src/theory/arith/proof_checker.cpp b/src/theory/arith/proof_checker.cpp index 93ffb542d..f05bd2185 100644 --- a/src/theory/arith/proof_checker.cpp +++ b/src/theory/arith/proof_checker.cpp @@ -21,8 +21,7 @@ #include "expr/skolem_manager.h" #include "theory/arith/arith_poly_norm.h" #include "theory/arith/arith_utilities.h" -#include "theory/arith/constraint.h" -#include "theory/arith/normal_form.h" +#include "theory/arith/linear/constraint.h" #include "theory/arith/operator_elim.h" using namespace cvc5::internal::kind; diff --git a/src/theory/arith/simplex.cpp b/src/theory/arith/simplex.cpp deleted file mode 100644 index 66aba1042..000000000 --- a/src/theory/arith/simplex.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - */ - -#include "theory/arith/simplex.h" - -#include "base/output.h" -#include "options/arith_options.h" -#include "options/smt_options.h" -#include "smt/env.h" -#include "theory/arith/constraint.h" -#include "theory/arith/error_set.h" -#include "theory/arith/linear_equality.h" -#include "theory/arith/tableau.h" -#include "util/statistics_value.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -SimplexDecisionProcedure::SimplexDecisionProcedure( - Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc) - : EnvObj(env), - d_pivots(0), - d_conflictVariables(), - d_linEq(linEq), - d_variables(d_linEq.getVariables()), - d_tableau(d_linEq.getTableau()), - d_errorSet(errors), - d_numVariables(0), - d_conflictChannel(conflictChannel), - d_conflictBuilder(NULL), - d_arithVarMalloc(tvmalloc), - d_errorSize(0), - d_zero(0), - d_posOne(1), - d_negOne(-1) -{ - d_heuristicRule = options().arith.arithErrorSelectionRule; - d_errorSet.setSelectionRule(d_heuristicRule); - d_conflictBuilder = new FarkasConflictBuilder(options().smt.produceProofs); -} - -SimplexDecisionProcedure::~SimplexDecisionProcedure(){ - delete d_conflictBuilder; -} - - -bool SimplexDecisionProcedure::standardProcessSignals(TimerStat &timer, IntStat& conflicts) { - TimerStat::CodeTimer codeTimer(timer); - Assert(d_conflictVariables.empty()); - - while(d_errorSet.moreSignals()){ - ArithVar curr = d_errorSet.topSignal(); - if(d_tableau.isBasic(curr) && !d_variables.assignmentIsConsistent(curr)){ - Assert(d_linEq.basicIsTracked(curr)); - - if(!d_conflictVariables.isMember(curr) && checkBasicForConflict(curr)){ - - Trace("recentlyViolated") - << "It worked? " - << conflicts.get() - << " " << curr - << " " << checkBasicForConflict(curr) << endl; - reportConflict(curr); - ++conflicts; - } - } - // Pop signal afterwards in case d_linEq.trackVariable(curr); - // is needed for for the ErrorSet - d_errorSet.popSignal(); - } - d_errorSize = d_errorSet.errorSize(); - - Assert(d_errorSet.noSignals()); - return !d_conflictVariables.empty(); -} - -/** Reports a conflict to on the output channel. */ -void SimplexDecisionProcedure::reportConflict(ArithVar basic){ - Assert(!d_conflictVariables.isMember(basic)); - Assert(checkBasicForConflict(basic)); - - ConstraintCP conflicted = generateConflictForBasic(basic); - Assert(conflicted != NullConstraint); - d_conflictChannel.raiseConflict(conflicted, InferenceId::ARITH_CONF_SIMPLEX); - - d_conflictVariables.add(basic); -} - -ConstraintCP SimplexDecisionProcedure::generateConflictForBasic(ArithVar basic) const { - Assert(d_tableau.isBasic(basic)); - Assert(checkBasicForConflict(basic)); - - if(d_variables.cmpAssignmentLowerBound(basic) < 0){ - Assert(d_linEq.nonbasicsAtUpperBounds(basic)); - return d_linEq.generateConflictBelowLowerBound(basic, *d_conflictBuilder); - }else if(d_variables.cmpAssignmentUpperBound(basic) > 0){ - Assert(d_linEq.nonbasicsAtLowerBounds(basic)); - return d_linEq.generateConflictAboveUpperBound(basic, *d_conflictBuilder); - }else{ - Unreachable(); - return NullConstraint; - } -} -bool SimplexDecisionProcedure::maybeGenerateConflictForBasic(ArithVar basic) const { - if(checkBasicForConflict(basic)){ - ConstraintCP conflicted = generateConflictForBasic(basic); - d_conflictChannel.raiseConflict(conflicted, InferenceId::UNKNOWN); - return true; - }else{ - return false; - } -} - -bool SimplexDecisionProcedure::checkBasicForConflict(ArithVar basic) const { - Assert(d_tableau.isBasic(basic)); - Assert(d_linEq.basicIsTracked(basic)); - - if(d_variables.cmpAssignmentLowerBound(basic) < 0){ - if(d_linEq.nonbasicsAtUpperBounds(basic)){ - return true; - } - }else if(d_variables.cmpAssignmentUpperBound(basic) > 0){ - if(d_linEq.nonbasicsAtLowerBounds(basic)){ - return true; - } - } - return false; -} - -void SimplexDecisionProcedure::tearDownInfeasiblityFunction(TimerStat& timer, ArithVar tmp){ - TimerStat::CodeTimer codeTimer(timer); - Assert(tmp != ARITHVAR_SENTINEL); - Assert(d_tableau.isBasic(tmp)); - - RowIndex ri = d_tableau.basicToRowIndex(tmp); - d_linEq.stopTrackingRowIndex(ri); - d_tableau.removeBasicRow(tmp); - releaseVariable(tmp); -} - -void SimplexDecisionProcedure::shrinkInfeasFunc(TimerStat& timer, ArithVar inf, const ArithVarVec& dropped){ - TimerStat::CodeTimer codeTimer(timer); - for(ArithVarVec::const_iterator i=dropped.begin(), i_end = dropped.end(); i != i_end; ++i){ - ArithVar back = *i; - - int focusSgn = d_errorSet.focusSgn(back); - Rational chg(-focusSgn); - - d_linEq.substitutePlusTimesConstant(inf, back, chg); - } -} - -void SimplexDecisionProcedure::adjustInfeasFunc(TimerStat& timer, ArithVar inf, const AVIntPairVec& focusChanges){ - TimerStat::CodeTimer codeTimer(timer); - for(AVIntPairVec::const_iterator i=focusChanges.begin(), i_end = focusChanges.end(); i != i_end; ++i){ - ArithVar v = (*i).first; - int focusChange = (*i).second; - - Rational chg(focusChange); - if(d_tableau.isBasic(v)){ - d_linEq.substitutePlusTimesConstant(inf, v, chg); - }else{ - d_linEq.directlyAddToCoefficient(inf, v, chg); - } - } -} - -void SimplexDecisionProcedure::addToInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e){ - AVIntPairVec justE; - int sgn = d_errorSet.getSgn(e); - justE.push_back(make_pair(e, sgn)); - adjustInfeasFunc(timer, inf, justE); -} - -void SimplexDecisionProcedure::removeFromInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e){ - AVIntPairVec justE; - int opSgn = -d_errorSet.getSgn(e); - justE.push_back(make_pair(e, opSgn)); - adjustInfeasFunc(timer, inf, justE); -} - -ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer, const ArithVarVec& set){ - Trace("constructInfeasiblityFunction") << "constructInfeasiblityFunction start" << endl; - - TimerStat::CodeTimer codeTimer(timer); - Assert(!d_errorSet.focusEmpty()); - Assert(debugIsASet(set)); - - ArithVar inf = requestVariable(); - Assert(inf != ARITHVAR_SENTINEL); - - std::vector coeffs; - std::vector variables; - - for(ArithVarVec::const_iterator iter = set.begin(), iend = set.end(); iter != iend; ++iter){ - ArithVar e = *iter; - - Assert(d_tableau.isBasic(e)); - Assert(!d_variables.assignmentIsConsistent(e)); - - int sgn = d_errorSet.getSgn(e); - Assert(sgn == -1 || sgn == 1); - const Rational& violatedCoeff = sgn < 0 ? d_negOne : d_posOne; - coeffs.push_back(violatedCoeff); - variables.push_back(e); - - Trace("constructInfeasiblityFunction") << violatedCoeff << " " << e << endl; - - } - d_tableau.addRow(inf, coeffs, variables); - DeltaRational newAssignment = d_linEq.computeRowValue(inf, false); - d_variables.setAssignment(inf, newAssignment); - - //d_linEq.trackVariable(inf); - d_linEq.trackRowIndex(d_tableau.basicToRowIndex(inf)); - - Trace("constructInfeasiblityFunction") << inf << " " << newAssignment << endl; - Trace("constructInfeasiblityFunction") << "constructInfeasiblityFunction done" << endl; - return inf; -} - -ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer){ - ArithVarVec inError; - d_errorSet.pushFocusInto(inError); - return constructInfeasiblityFunction(timer, inError); -} - -ArithVar SimplexDecisionProcedure::constructInfeasiblityFunction(TimerStat& timer, ArithVar e){ - ArithVarVec justE; - justE.push_back(e); - return constructInfeasiblityFunction(timer, justE); -} - -void SimplexDecisionProcedure::addSgn(sgn_table& sgns, ArithVar col, int sgn, ArithVar basic){ - pair p = make_pair(col, determinizeSgn(sgn)); - sgns[p].push_back(basic); -} - -void SimplexDecisionProcedure::addRowSgns(sgn_table& sgns, ArithVar basic, int norm){ - for(Tableau::RowIterator i = d_tableau.basicRowIterator(basic); !i.atEnd(); ++i){ - const Tableau::Entry& entry = *i; - ArithVar v = entry.getColVar(); - int sgn = (entry.getCoefficient().sgn()); - addSgn(sgns, v, norm * sgn, basic); - } -} - -ArithVar SimplexDecisionProcedure::find_basic_in_sgns(const sgn_table& sgns, ArithVar col, int sgn, const DenseSet& m, bool inside){ - pair p = make_pair(col, determinizeSgn(sgn)); - sgn_table::const_iterator i = sgns.find(p); - - if(i != sgns.end()){ - const ArithVarVec& vec = (*i).second; - for(ArithVarVec::const_iterator viter = vec.begin(), vend = vec.end(); viter != vend; ++viter){ - ArithVar curr = *viter; - if(inside == m.isMember(curr)){ - return curr; - } - } - } - return ARITHVAR_SENTINEL; -} - -SimplexDecisionProcedure::sgn_table::const_iterator SimplexDecisionProcedure::find_sgns(const sgn_table& sgns, ArithVar col, int sgn){ - pair p = make_pair(col, determinizeSgn(sgn)); - return sgns.find(p); -} -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/simplex.h b/src/theory/arith/simplex.h deleted file mode 100644 index fd50a8594..000000000 --- a/src/theory/arith/simplex.h +++ /dev/null @@ -1,235 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - * - * This implements the Simplex module for the Simpelx for DPLL(T) decision - * procedure. - * See the Simplex for DPLL(T) technical report for more background.(citation?) - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This is required to either produce a conflict or satisifying PartialModel. - * Further, we require being told when a basic variable updates its value. - * - * During the Simplex search we maintain a queue of variables. - * The queue is required to contain all of the basic variables that voilate - * their bounds. - * As elimination from the queue is more efficient to be done lazily, - * we do not maintain that the queue of variables needs to be only basic - * variables or only variables that satisfy their bounds. - * - * The simplex procedure roughly follows Alberto's thesis. (citation?) - * There is one round of selecting using a heuristic pivoting rule. - * (See PreferenceFunction Documentation for the available options.) - * The non-basic variable is the one that appears in the fewest pivots. - * (Bruno says that Leonardo invented this first.) - * After this, Bland's pivot rule is invoked. - * - * During this proccess, we periodically inspect the queue of variables to - * 1) remove now extraneous extries, - * 2) detect conflicts that are "waiting" on the queue but may not be detected - * by the current queue heuristics, and - * 3) detect multiple conflicts. - * - * Conflicts are greedily slackened to use the weakest bounds that still - * produce the conflict. - * - * Extra things tracked atm: (Subject to change at Tim's whims) - * - A superset of all of the newly pivoted variables. - * - A queue of additional conflicts that were discovered by Simplex. - * These are theory valid and are currently turned into lemmas - */ - -#include "cvc5_private.h" - -#pragma once - -#include - -#include "options/arith_options.h" -#include "smt/env_obj.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/partial_model.h" -#include "util/dense_map.h" -#include "util/result.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class ErrorSet; -class LinearEqualityModule; -class Tableau; - -class SimplexDecisionProcedure : protected EnvObj -{ - protected: - typedef std::vector< std::pair > AVIntPairVec; - - /** Pivot count of the current round of pivoting. */ - uint32_t d_pivots; - - /** The set of variables that are in conflict in this round. */ - DenseSet d_conflictVariables; - - /** The rule to use for heuristic selection mode. */ - options::ErrorSelectionRule d_heuristicRule; - - /** Linear equality module. */ - LinearEqualityModule& d_linEq; - - /** - * Manages information about the assignment and upper and lower bounds on - * variables. - * Partial model matches that in LinearEqualityModule. - */ - ArithVariables& d_variables; - - /** - * Stores the linear equalities used by Simplex. - * Tableau from the LinearEquality module. - */ - Tableau& d_tableau; - - /** Contains a superset of the basic variables in violation of their bounds. */ - ErrorSet& d_errorSet; - - /** Number of variables in the system. This is used for tuning heuristics. */ - ArithVar d_numVariables; - - /** This is the call back channel for Simplex to report conflicts. */ - RaiseConflict d_conflictChannel; - - /** This is the call back channel for Simplex to report conflicts. */ - FarkasConflictBuilder* d_conflictBuilder; - - /** Used for requesting d_opt, bound and error variables for primal.*/ - TempVarMalloc d_arithVarMalloc; - - /** The size of the error set. */ - uint32_t d_errorSize; - - /** A local copy of 0. */ - const Rational d_zero; - - /** A local copy of 1. */ - const Rational d_posOne; - - /** A local copy of -1. */ - const Rational d_negOne; - - /** - * Locally cached value of arithStandardCheckVarOrderPivots option. It is - * cached here to allow for single runs with a different (lower) limit. - */ - int64_t d_varOrderPivotLimit = -1; - - ArithVar constructInfeasiblityFunction(TimerStat& timer); - ArithVar constructInfeasiblityFunction(TimerStat& timer, ArithVar e); - ArithVar constructInfeasiblityFunction(TimerStat& timer, const ArithVarVec& set); - - void tearDownInfeasiblityFunction(TimerStat& timer, ArithVar inf); - void adjustInfeasFunc(TimerStat& timer, ArithVar inf, const AVIntPairVec& focusChanges); - void addToInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e); - void removeFromInfeasFunc(TimerStat& timer, ArithVar inf, ArithVar e); - void shrinkInfeasFunc(TimerStat& timer, ArithVar inf, const ArithVarVec& dropped); - -public: - SimplexDecisionProcedure(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc); - virtual ~SimplexDecisionProcedure(); - - /** - * Tries to update the assignments of variables such that all of the - * assignments are consistent with their bounds. - * This is done by a simplex search through the possible bases of the tableau. - * - * If all of the variables can be made consistent with their bounds - * SAT is returned. Otherwise UNSAT is returned, and at least 1 conflict - * was reported on the conflictCallback passed to the Module. - * - * Tableau pivoting is performed so variables may switch from being basic to - * nonbasic and vice versa. - * - * Corresponds to the "check()" procedure in [Cav06]. - */ - virtual Result::Status findModel(bool exactResult) = 0; - - void increaseMax() { d_numVariables++; } - - uint32_t getPivots() const { return d_pivots; } - - /** Set the variable ordering pivot limit */ - void setVarOrderPivotLimit(int64_t value) { d_varOrderPivotLimit = value; } - -protected: - /** Reports a conflict to on the output channel. */ - void reportConflict(ArithVar basic); - - /** - * Checks a basic variable, b, to see if it is in conflict. - * If a conflict is discovered a node summarizing the conflict is returned. - * Otherwise, Node::null() is returned. - */ - bool maybeGenerateConflictForBasic(ArithVar basic) const; - - /** Returns true if a tracked basic variable has a conflict on it. */ - bool checkBasicForConflict(ArithVar b) const; - - /** - * If a basic variable has a conflict on its row, - * this produces a minimized row on the conflict channel. - */ - ConstraintCP generateConflictForBasic(ArithVar basic) const; - - /** Gets a fresh variable from TheoryArith. */ - ArithVar requestVariable() { return d_arithVarMalloc.request(); } - - /** Releases a requested variable from TheoryArith.*/ - void releaseVariable(ArithVar v) { d_arithVarMalloc.release(v); } - - /** Post condition: !d_queue.moreSignals() */ - bool standardProcessSignals(TimerStat& timer, IntStat& conflictStat); - - struct ArithVarIntPairHashFunc - { - size_t operator()(const std::pair& p) const - { - size_t h1 = std::hash()(p.first); - size_t h2 = std::hash()(p.second); - return h1 + 3389 * h2; - } - }; - - typedef std::unordered_map< std::pair, ArithVarVec, ArithVarIntPairHashFunc> sgn_table; - - static inline int determinizeSgn(int sgn){ - return sgn < 0 ? -1 : (sgn == 0 ? 0 : 1); - } - - void addSgn(sgn_table& sgns, ArithVar col, int sgn, ArithVar basic); - void addRowSgns(sgn_table& sgns, ArithVar basic, int norm); - ArithVar find_basic_in_sgns(const sgn_table& sgns, ArithVar col, int sgn, const DenseSet& m, bool inside); - - sgn_table::const_iterator find_sgns(const sgn_table& sgns, ArithVar col, int sgn); - -}; /* class SimplexDecisionProcedure */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/simplex_update.cpp b/src/theory/arith/simplex_update.cpp deleted file mode 100644 index 19d5fcc19..000000000 --- a/src/theory/arith/simplex_update.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andres Noetzli, Mathias Preiner - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This implements the UpdateInfo. - */ - -#include "theory/arith/simplex_update.h" - -#include "theory/arith/constraint.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -/* - * Generates a string representation of std::optional and inserts it into a - * stream. - * - * Note: We define this function here in the cvc5::internal::theory::arith namespace, - * because it would otherwise not be found for std::optional. This is due - * to the argument-dependent lookup rules. - * - * @param out The stream - * @param m The value - * @return The stream - */ -std::ostream& operator<<(std::ostream& out, const std::optional& m) -{ - return cvc5::internal::operator<<(out, m); -} - -UpdateInfo::UpdateInfo(): - d_nonbasic(ARITHVAR_SENTINEL), - d_nonbasicDirection(0), - d_nonbasicDelta(), - d_foundConflict(false), - d_errorsChange(), - d_focusDirection(), - d_tableauCoefficient(), - d_limiting(NullConstraint), - d_witness(AntiProductive) -{} - -UpdateInfo::UpdateInfo(ArithVar nb, int dir): - d_nonbasic(nb), - d_nonbasicDirection(dir), - d_nonbasicDelta(), - d_foundConflict(false), - d_errorsChange(), - d_focusDirection(), - d_tableauCoefficient(), - d_limiting(NullConstraint), - d_witness(AntiProductive) -{ - Assert(dir == 1 || dir == -1); -} - -UpdateInfo::UpdateInfo(bool conflict, ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP c): - d_nonbasic(nb), - d_nonbasicDirection(delta.sgn()), - d_nonbasicDelta(delta), - d_foundConflict(true), - d_errorsChange(), - d_focusDirection(), - d_tableauCoefficient(&r), - d_limiting(c), - d_witness(ConflictFound) -{ - Assert(conflict); -} - -UpdateInfo UpdateInfo::conflict(ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP lim){ - return UpdateInfo(true, nb, delta, r, lim); -} - -void UpdateInfo::updateUnbounded(const DeltaRational& delta, int ec, int f){ - d_limiting = NullConstraint; - d_nonbasicDelta = delta; - d_errorsChange = ec; - d_focusDirection = f; - d_tableauCoefficient.reset(); - updateWitness(); - Assert(unbounded()); - Assert(improvement(d_witness)); - Assert(!describesPivot()); - Assert(debugSgnAgreement()); -} -void UpdateInfo::updatePureFocus(const DeltaRational& delta, ConstraintP c){ - d_limiting = c; - d_nonbasicDelta = delta; - d_errorsChange.reset(); - d_focusDirection = 1; - d_tableauCoefficient.reset(); - updateWitness(); - Assert(!describesPivot()); - Assert(improvement(d_witness)); - Assert(debugSgnAgreement()); -} - -void UpdateInfo::updatePivot(const DeltaRational& delta, const Rational& r, ConstraintP c){ - d_limiting = c; - d_nonbasicDelta = delta; - d_errorsChange.reset(); - d_focusDirection.reset(); - updateWitness(); - Assert(describesPivot()); - Assert(debugSgnAgreement()); -} - -void UpdateInfo::updatePivot(const DeltaRational& delta, const Rational& r, ConstraintP c, int ec){ - d_limiting = c; - d_nonbasicDelta = delta; - d_errorsChange = ec; - d_focusDirection.reset(); - d_tableauCoefficient = &r; - updateWitness(); - Assert(describesPivot()); - Assert(debugSgnAgreement()); -} - -void UpdateInfo::witnessedUpdate(const DeltaRational& delta, ConstraintP c, int ec, int fd){ - d_limiting = c; - d_nonbasicDelta = delta; - d_errorsChange = ec; - d_focusDirection = fd; - d_tableauCoefficient.reset(); - updateWitness(); - Assert(describesPivot() || improvement(d_witness)); - Assert(debugSgnAgreement()); -} - -void UpdateInfo::update(const DeltaRational& delta, const Rational& r, ConstraintP c, int ec, int fd){ - d_limiting = c; - d_nonbasicDelta = delta; - d_errorsChange = ec; - d_focusDirection = fd; - d_tableauCoefficient = &r; - updateWitness(); - Assert(describesPivot() || improvement(d_witness)); - Assert(debugSgnAgreement()); -} - -bool UpdateInfo::describesPivot() const { - return !unbounded() && d_nonbasic != d_limiting->getVariable(); -} - -void UpdateInfo::output(std::ostream& out) const{ - out << "{UpdateInfo" - << ", nb = " << d_nonbasic - << ", dir = " << d_nonbasicDirection - << ", delta = " << d_nonbasicDelta - << ", conflict = " << d_foundConflict - << ", errorChange = " << d_errorsChange - << ", focusDir = " << d_focusDirection - << ", witness = " << d_witness - << ", limiting = " << d_limiting - << "}"; -} - -ArithVar UpdateInfo::leaving() const{ - Assert(describesPivot()); - - return d_limiting->getVariable(); -} - -std::ostream& operator<<(std::ostream& out, const UpdateInfo& up){ - up.output(out); - return out; -} - - -std::ostream& operator<<(std::ostream& out, WitnessImprovement w){ - switch(w){ - case ConflictFound: - out << "ConflictFound"; break; - case ErrorDropped: - out << "ErrorDropped"; break; - case FocusImproved: - out << "FocusImproved"; break; - case FocusShrank: - out << "FocusShrank"; break; - case Degenerate: - out << "Degenerate"; break; - case BlandsDegenerate: - out << "BlandsDegenerate"; break; - case HeuristicDegenerate: - out << "HeuristicDegenerate"; break; - case AntiProductive: - out << "AntiProductive"; break; - } - return out; -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/simplex_update.h b/src/theory/arith/simplex_update.h deleted file mode 100644 index 4bcadbb98..000000000 --- a/src/theory/arith/simplex_update.h +++ /dev/null @@ -1,360 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andres Noetzli, Morgan Deters - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This provides a class for summarizing pivot proposals. - * - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This maintains the relationship needed by the SimplexDecisionProcedure. - * - * In the language of Simplex for DPLL(T), this provides: - * - update() - * - pivotAndUpdate() - * - * This class also provides utility functions that require - * using both the Tableau and PartialModel. - */ - -#include "cvc5_private.h" - -#pragma once - -#include - -#include "theory/arith/arithvar.h" -#include "theory/arith/constraint_forward.h" -#include "theory/arith/delta_rational.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -enum WitnessImprovement { - ConflictFound = 0, - ErrorDropped = 1, - FocusImproved = 2, - FocusShrank = 3, - Degenerate = 4, - BlandsDegenerate = 5, - HeuristicDegenerate = 6, - AntiProductive = 7 -}; - -inline bool strongImprovement(WitnessImprovement w){ - return w <= FocusImproved; -} - -inline bool improvement(WitnessImprovement w){ - return w <= FocusShrank; -} - -inline bool degenerate(WitnessImprovement w){ - switch(w){ - case Degenerate: - case BlandsDegenerate: - case HeuristicDegenerate: - return true; - default: - return false; - } -} - -std::ostream& operator<<(std::ostream& out, WitnessImprovement w); - -/** - * This class summarizes both potential: - * - pivot-and-update operations or - * - a pure update operation. - * This stores enough information for the various algorithms hat consider these operations. - * These require slightly different pieces of information at different points - * so they are a bit verbose and paranoid. - */ -class UpdateInfo { -private: - - /** - * The nonbasic variables under consideration. - * This is either the entering variable on a pivot and update - * or the variable being updated. - * This can only be set in the constructor or assignment. - * - * If this uninitialized, then this is ARITHVAR_SENTINEL. - */ - ArithVar d_nonbasic; - - /** - * The sgn of the "intended" derivative (delta) of the update to d_nonbasic. - * This is either 1, -1, or 0. - * It is "intended" as the delta is always allowed to be 0. - * (See debugSgnAgreement().) - * - * If this uninitialized, then this is 0. - * If this is initialized, then it is -1 or 1. - * - * This can only be set in the constructor or assignment. - */ - int d_nonbasicDirection; - - /** - * The change in the assignment of d_nonbasic. - * This is changed via the updateProposal(...) methods. - * The value needs to satisfy debugSgnAgreement() or it is in conflict. - */ - std::optional d_nonbasicDelta; - - /** - * This is true if the pivot-and-update is *known* to cause a conflict. - * This can only be true if it was constructed through the static conflict(...) method. - */ - bool d_foundConflict; - - /** This is the change in the size of the error set. */ - std::optional d_errorsChange; - - /** This is the sgn of the change in the value of the focus set.*/ - std::optional d_focusDirection; - - /** This is the sgn of the change in the value of the focus set.*/ - std::optional d_focusChange; - - /** This is the coefficient in the tableau for the entry.*/ - std::optional d_tableauCoefficient; - - /** - * This is the constraint that nonbasic is basic is updating s.t. its variable is against it. - * This has 3 different possibilities: - * - Unbounded : then this is NullConstraint and unbounded() is true. - * - Pivot-And-Update: then this is not NullConstraint and the variable is not d_nonbasic. - * - Update: then this is not NullConstraint and the variable is d_nonbasic. - */ - ConstraintP d_limiting; - - WitnessImprovement d_witness; - - /** - * This returns true if - * d_nonbasicDelta is zero() or its sgn() must agree with d_nonbasicDirection. - */ - bool debugSgnAgreement() const { - int deltaSgn = d_nonbasicDelta.value().sgn(); - return deltaSgn == 0 || deltaSgn == d_nonbasicDirection; - } - - /** This private constructor allows for setting conflict to true. */ - UpdateInfo(bool conflict, ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP lim); - -public: - - /** This constructs an uninitialized UpdateInfo. */ - UpdateInfo(); - - /** - * This constructs an initialized UpdateInfo. - * dir must be 1 or -1. - */ - UpdateInfo(ArithVar nb, int dir); - - /** - * This updates the nonBasicDelta to d and limiting to NullConstraint. - * This describes an unbounded() update. - */ - void updateUnbounded(const DeltaRational& d, int ec, int f); - - - void updatePureFocus(const DeltaRational& d, ConstraintP c); - //void updatePureError(const DeltaRational& d, Constraint c, int e); - //void updatePure(const DeltaRational& d, Constraint c, int e, int f); - - /** - * This updates the nonBasicDelta to d and limiting to c. - * This clears errorChange() and focusDir(). - */ - void updatePivot(const DeltaRational& d, const Rational& r, ConstraintP c); - - /** - * This updates the nonBasicDelta to d, limiting to c, and errorChange to e. - * This clears focusDir(). - */ - void updatePivot(const DeltaRational& d, const Rational& r, ConstraintP c, int e); - - /** - * This updates the nonBasicDelta to d, limiting to c, errorChange to e and - * focusDir to f. - */ - void witnessedUpdate(const DeltaRational& d, ConstraintP c, int e, int f); - void update(const DeltaRational& d, const Rational& r, ConstraintP c, int e, int f); - - - static UpdateInfo conflict(ArithVar nb, const DeltaRational& delta, const Rational& r, ConstraintP lim); - - inline ArithVar nonbasic() const { return d_nonbasic; } - inline bool uninitialized() const { - return d_nonbasic == ARITHVAR_SENTINEL; - } - - /** - * There is no limiting value to the improvement of the focus. - * If this is true, this never describes an update. - */ - inline bool unbounded() const { - return d_limiting == NullConstraint; - } - - /** - * The update either describes a pivotAndUpdate operation - * or it describes just an update. - */ - bool describesPivot() const; - - /** Returns the . describesPivot() must be true. */ - ArithVar leaving() const; - - /** - * Returns true if this is *known* to find a conflict. - * If true, this must have been made through the static conflict(...) function. - */ - bool foundConflict() const { return d_foundConflict; } - - /** Returns the direction nonbasic is supposed to move. */ - inline int nonbasicDirection() const{ return d_nonbasicDirection; } - - /** Requires errorsChange to be set through setErrorsChange or updateProposal. */ - inline int errorsChange() const { return d_errorsChange.value(); } - - /** - * If errorsChange has been set, return errorsChange(). - * Otherwise, return def. - */ - inline int errorsChangeSafe(int def) const { - if (d_errorsChange) - { - return d_errorsChange.value(); - } - else - { - return def; - } - } - - /** Sets the errorChange. */ - void setErrorsChange(int ec){ - d_errorsChange = ec; - updateWitness(); - } - - - /** Requires errorsChange to be set through setErrorsChange or updateProposal. */ - inline int focusDirection() const { return d_focusDirection.value(); } - - /** Sets the focusDirection. */ - void setFocusDirection(int fd){ - Assert(-1 <= fd && fd <= 1); - d_focusDirection = fd; - updateWitness(); - } - - /** - * nonbasicDirection must be the same as the sign for the focus function's - * coefficient for this to be safe. - * The burden for this being safe is on the user! - */ - void determineFocusDirection(){ - const int deltaSgn = d_nonbasicDelta.value().sgn(); - setFocusDirection(deltaSgn * d_nonbasicDirection); - } - - /** Requires nonbasicDelta to be set through updateProposal(...). */ - const DeltaRational& nonbasicDelta() const { return d_nonbasicDelta.value(); } - const Rational& getCoefficient() const { - Assert(describesPivot()); - Assert(d_tableauCoefficient.value() != NULL); - return *(d_tableauCoefficient.value()); - } - int basicDirection() const { - return nonbasicDirection() * (getCoefficient().sgn()); - } - - /** Returns the limiting constraint. */ - inline ConstraintP limiting() const { - return d_limiting; - } - - WitnessImprovement getWitness(bool useBlands = false) const{ - Assert(d_witness == computeWitness()); - - if(d_witness == Degenerate){ - if(useBlands){ - return BlandsDegenerate; - }else{ - return HeuristicDegenerate; - } - }else{ - return d_witness; - } - } - - const DeltaRational& focusChange() const { return d_focusChange.value(); } - void setFocusChange(const DeltaRational& fc) { - d_focusChange = fc; - } - - /** Outputs the UpdateInfo into out. */ - void output(std::ostream& out) const; - -private: - void updateWitness() { - d_witness = computeWitness(); - Assert(describesPivot() || improvement(d_witness)); - } - - /** - * Determines the appropriate WitnessImprovement for the update. - * useBlands breaks ties for degenerate pivots. - * - * This is safe if: - * - d_foundConflict is true, or - * - d_foundConflict is false and d_errorsChange has been set and d_errorsChange < 0, or - * - d_foundConflict is false and d_errorsChange has been set and d_errorsChange >= 0 and d_focusDirection has been set. - */ - WitnessImprovement computeWitness() const { - if(d_foundConflict){ - return ConflictFound; - } - else if (d_errorsChange && d_errorsChange.value() < 0) - { - return ErrorDropped; - } - else if (d_errorsChange.value_or(0) == 0) - { - if (d_focusDirection) - { - if (*d_focusDirection > 0) - { - return FocusImproved; - } - else if (*d_focusDirection == 0) - { - return Degenerate; - } - } - } - return AntiProductive; - } - -}; - -std::ostream& operator<<(std::ostream& out, const UpdateInfo& up); - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/soi_simplex.cpp b/src/theory/arith/soi_simplex.cpp deleted file mode 100644 index ad5a71d04..000000000 --- a/src/theory/arith/soi_simplex.cpp +++ /dev/null @@ -1,912 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Andrew Reynolds - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - */ -#include "theory/arith/soi_simplex.h" - -#include - -#include "base/output.h" -#include "options/arith_options.h" -#include "smt/smt_statistics_registry.h" -#include "theory/arith/constraint.h" -#include "theory/arith/error_set.h" -#include "theory/arith/tableau.h" -#include "util/statistics_stats.h" - -using namespace std; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -SumOfInfeasibilitiesSPD::SumOfInfeasibilitiesSPD(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc) - : SimplexDecisionProcedure(env, linEq, errors, conflictChannel, tvmalloc), - d_soiVar(ARITHVAR_SENTINEL), - d_pivotBudget(0), - d_prevWitnessImprovement(AntiProductive), - d_witnessImprovementInARow(0), - d_sgnDisagreements(), - d_statistics("theory::arith::SOI", d_pivots) -{ } - -SumOfInfeasibilitiesSPD::Statistics::Statistics(const std::string& name, - uint32_t& pivots) - : d_initialSignalsTime( - smtStatisticsRegistry().registerTimer(name + "initialProcessTime")), - d_initialConflicts( - smtStatisticsRegistry().registerInt(name + "UpdateConflicts")), - d_soiFoundUnsat(smtStatisticsRegistry().registerInt(name + "FoundUnsat")), - d_soiFoundSat(smtStatisticsRegistry().registerInt(name + "FoundSat")), - d_soiMissed(smtStatisticsRegistry().registerInt(name + "Missed")), - d_soiConflicts( - smtStatisticsRegistry().registerInt(name + "ConfMin::num")), - d_hasToBeMinimal( - smtStatisticsRegistry().registerInt(name + "HasToBeMin")), - d_maybeNotMinimal( - smtStatisticsRegistry().registerInt(name + "MaybeNotMin")), - d_soiTimer(smtStatisticsRegistry().registerTimer(name + "Time")), - d_soiFocusConstructionTimer( - smtStatisticsRegistry().registerTimer(name + "Construction")), - d_soiConflictMinimization(smtStatisticsRegistry().registerTimer( - name + "Conflict::Minimization")), - d_selectUpdateForSOI( - smtStatisticsRegistry().registerTimer(name + "selectSOI")), - d_finalCheckPivotCounter( - smtStatisticsRegistry().registerReference( - name + "lastPivots", pivots)) -{ -} - -Result::Status SumOfInfeasibilitiesSPD::findModel(bool exactResult) -{ - Assert(d_conflictVariables.empty()); - Assert(d_sgnDisagreements.empty()); - - d_pivots = 0; - - if(d_errorSet.errorEmpty() && !d_errorSet.moreSignals()){ - Trace("soi::findModel") << "soiFindModel() trivial" << endl; - Assert(d_conflictVariables.empty()); - return Result::SAT; - } - - // We need to reduce this because of - d_errorSet.reduceToSignals(); - - // We must start tracking NOW - d_errorSet.setSelectionRule(options::ErrorSelectionRule::SUM_METRIC); - - if(initialProcessSignals()){ - d_conflictVariables.purge(); - Trace("soi::findModel") << "fcFindModel() early conflict" << endl; - Assert(d_conflictVariables.empty()); - return Result::UNSAT; - }else if(d_errorSet.errorEmpty()){ - Trace("soi::findModel") << "fcFindModel() fixed itself" << endl; - Assert(!d_errorSet.moreSignals()); - Assert(d_conflictVariables.empty()); - return Result::SAT; - } - - Trace("soi::findModel") << "fcFindModel() start non-trivial" << endl; - - exactResult |= d_varOrderPivotLimit < 0; - - d_prevWitnessImprovement = HeuristicDegenerate; - d_witnessImprovementInARow = 0; - - Result::Status result = Result::UNKNOWN; - - if (result == Result::UNKNOWN) - { - if(exactResult){ - d_pivotBudget = -1; - }else{ - d_pivotBudget = d_varOrderPivotLimit; - } - - result = sumOfInfeasibilities(); - - if(result == Result::UNSAT){ - ++(d_statistics.d_soiFoundUnsat); - }else if(d_errorSet.errorEmpty()){ - ++(d_statistics.d_soiFoundSat); - }else{ - ++(d_statistics.d_soiMissed); - } - } - - Assert(!d_errorSet.moreSignals()); - if (result == Result::UNKNOWN && d_errorSet.errorEmpty()) - { - result = Result::SAT; - } - - // ensure that the conflict variable is still in the queue. - d_conflictVariables.purge(); - - Trace("soi::findModel") << "end findModel() " << result << endl; - - Assert(d_conflictVariables.empty()); - return result; -} - - -void SumOfInfeasibilitiesSPD::logPivot(WitnessImprovement w){ - if(d_pivotBudget > 0) { - --d_pivotBudget; - } - Assert(w != AntiProductive); - - if(w == d_prevWitnessImprovement){ - ++d_witnessImprovementInARow; - if(d_witnessImprovementInARow == 0){ - --d_witnessImprovementInARow; - } - }else{ - if(w != BlandsDegenerate){ - d_witnessImprovementInARow = 1; - } - d_prevWitnessImprovement = w; - } - if(strongImprovement(w)){ - d_leavingCountSinceImprovement.purge(); - } - - Trace("logPivot") << "logPivot " << d_prevWitnessImprovement << " " << d_witnessImprovementInARow << endl; -} - -uint32_t SumOfInfeasibilitiesSPD::degeneratePivotsInARow() const { - switch(d_prevWitnessImprovement){ - case ConflictFound: - case ErrorDropped: - case FocusImproved: - return 0; - case HeuristicDegenerate: - case BlandsDegenerate: - return d_witnessImprovementInARow; - // Degenerate is unreachable for its own reasons - case Degenerate: - case FocusShrank: - case AntiProductive: - Unreachable(); - return -1; - } - Unreachable(); -} - -void SumOfInfeasibilitiesSPD::adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges){ - uint32_t newErrorSize = d_errorSet.errorSize(); - adjustInfeasFunc(d_statistics.d_soiFocusConstructionTimer, d_soiVar, focusChanges); - d_errorSize = newErrorSize; -} - - -UpdateInfo SumOfInfeasibilitiesSPD::selectUpdate(LinearEqualityModule::UpdatePreferenceFunction upf, LinearEqualityModule::VarPreferenceFunction bpf) { - UpdateInfo selected; - - Trace("soi::selectPrimalUpdate") - << "selectPrimalUpdate " << endl - << d_soiVar << " " << d_tableau.basicRowLength(d_soiVar) << " " - << d_linEq.debugBasicAtBoundCount(d_soiVar) << endl; - - typedef std::vector CandVector; - CandVector candidates; - - for(Tableau::RowIterator ri = d_tableau.basicRowIterator(d_soiVar); !ri.atEnd(); ++ri){ - const Tableau::Entry& e = *ri; - ArithVar curr = e.getColVar(); - if(curr == d_soiVar){ continue; } - - int sgn = e.getCoefficient().sgn(); - bool candidate = - (sgn > 0 && d_variables.cmpAssignmentUpperBound(curr) < 0) || - (sgn < 0 && d_variables.cmpAssignmentLowerBound(curr) > 0); - - Trace("soi::selectPrimalUpdate") - << "storing " << d_soiVar - << " " << curr - << " " << candidate - << " " << e.getCoefficient() - << " " << sgn << endl; - - if(candidate) { - candidates.push_back(Cand(curr, 0, sgn, &e.getCoefficient())); - } - } - - CompPenaltyColLength colCmp(&d_linEq, options().arith.havePenalties); - CandVector::iterator i = candidates.begin(); - CandVector::iterator end = candidates.end(); - std::make_heap(i, end, colCmp); - - // For the first 3 pivots take the best - // After that, once an improvement is found on look at a - // small number of pivots after finding an improvement - // the longer the search to more willing we are to look at more candidates - int maxCandidatesAfterImprove = - (d_pivots <= 2) ? std::numeric_limits::max() : d_pivots/5; - - int candidatesAfterFocusImprove = 0; - while(i != end && candidatesAfterFocusImprove <= maxCandidatesAfterImprove){ - std::pop_heap(i, end, colCmp); - --end; - Cand& cand = (*end); - ArithVar curr = cand.d_nb; - const Rational& coeff = *cand.d_coeff; - - LinearEqualityModule::UpdatePreferenceFunction leavingPrefFunc = selectLeavingFunction(curr); - UpdateInfo currProposal = d_linEq.speculativeUpdate(curr, coeff, leavingPrefFunc); - - Trace("soi::selectPrimalUpdate") - << "selected " << selected << endl - << "currProp " << currProposal << endl - << "coeff " << coeff << endl; - - Assert(!currProposal.uninitialized()); - - if(candidatesAfterFocusImprove > 0){ - candidatesAfterFocusImprove++; - } - - if(selected.uninitialized() || (d_linEq.*upf)(selected, currProposal)){ - selected = currProposal; - WitnessImprovement w = selected.getWitness(false); - Trace("soi::selectPrimalUpdate") << "selected " << w << endl; - //setPenalty(curr, w); - if(improvement(w)){ - bool exitEarly; - switch(w){ - case ConflictFound: exitEarly = true; break; - case FocusImproved: - candidatesAfterFocusImprove = 1; - exitEarly = false; - break; - default: - exitEarly = false; break; - } - if(exitEarly){ break; } - } - }else{ - Trace("soi::selectPrimalUpdate") << "dropped "<< endl; - } - - } - return selected; -} - -bool debugCheckWitness(const UpdateInfo& inf, WitnessImprovement w, bool useBlands){ - if(inf.getWitness(useBlands) == w){ - switch(w){ - case ConflictFound: return inf.foundConflict(); - case ErrorDropped: return inf.errorsChange() < 0; - case FocusImproved: return inf.focusDirection() > 0; - case FocusShrank: return false; // This is not a valid output - case Degenerate: return false; // This is not a valid output - case BlandsDegenerate: return useBlands; - case HeuristicDegenerate: return !useBlands; - case AntiProductive: return false; - } - } - return false; -} - - -void SumOfInfeasibilitiesSPD::debugPrintSignal(ArithVar updated) const{ - Trace("updateAndSignal") << "updated basic " << updated; - Trace("updateAndSignal") << " length " << d_tableau.basicRowLength(updated); - Trace("updateAndSignal") << " consistent " << d_variables.assignmentIsConsistent(updated); - int dir = !d_variables.assignmentIsConsistent(updated) ? - d_errorSet.getSgn(updated) : 0; - Trace("updateAndSignal") << " dir " << dir; - Trace("updateAndSignal") << " debugBasicAtBoundCount " << d_linEq.debugBasicAtBoundCount(updated) << endl; -} - - -void SumOfInfeasibilitiesSPD::updateAndSignal(const UpdateInfo& selected, WitnessImprovement w){ - ArithVar nonbasic = selected.nonbasic(); - - Trace("updateAndSignal") << "updateAndSignal " << selected << endl; - - if(selected.describesPivot()){ - ConstraintP limiting = selected.limiting(); - ArithVar basic = limiting->getVariable(); - Assert(d_linEq.basicIsTracked(basic)); - d_linEq.pivotAndUpdate(basic, nonbasic, limiting->getValue()); - }else{ - Assert(!selected.unbounded() || selected.errorsChange() < 0); - - DeltaRational newAssignment = - d_variables.getAssignment(nonbasic) + selected.nonbasicDelta(); - - d_linEq.updateTracked(nonbasic, newAssignment); - } - d_pivots++; - - increaseLeavingCount(nonbasic); - - vector< pair > focusChanges; - while(d_errorSet.moreSignals()){ - ArithVar updated = d_errorSet.topSignal(); - int prevFocusSgn = d_errorSet.popSignal(); - - if(d_tableau.isBasic(updated)){ - Assert(!d_variables.assignmentIsConsistent(updated) - == d_errorSet.inError(updated)); - if(TraceIsOn("updateAndSignal")){debugPrintSignal(updated);} - if(!d_variables.assignmentIsConsistent(updated)){ - if(checkBasicForConflict(updated)){ - reportConflict(updated); - //Assert(debugUpdatedBasic(selected, updated)); - } - } - }else{ - Trace("updateAndSignal") << "updated nonbasic " << updated << endl; - } - int currFocusSgn = d_errorSet.focusSgn(updated); - if(currFocusSgn != prevFocusSgn){ - int change = currFocusSgn - prevFocusSgn; - focusChanges.push_back(make_pair(updated, change)); - } - } - - if(TraceIsOn("error")){ d_errorSet.debugPrint(Trace("error")); } - - //Assert(debugSelectedErrorDropped(selected, d_errorSize, d_errorSet.errorSize())); - - adjustFocusAndError(selected, focusChanges); -} - -void SumOfInfeasibilitiesSPD::qeAddRange(uint32_t begin, uint32_t end){ - Assert(!d_qeInSoi.empty()); - for(uint32_t i = begin; i != end; ++i){ - ArithVar v = d_qeConflict[i]; - addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, v); - d_qeInSoi.add(v); - } -} - -void SumOfInfeasibilitiesSPD::qeRemoveRange(uint32_t begin, uint32_t end){ - for(uint32_t i = begin; i != end; ++i){ - ArithVar v = d_qeConflict[i]; - removeFromInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, v); - d_qeInSoi.remove(v); - } - Assert(!d_qeInSoi.empty()); -} - -void SumOfInfeasibilitiesSPD::qeSwapRange(uint32_t N, uint32_t r, uint32_t s){ - for(uint32_t i = 0; i < N; ++i){ - std::swap(d_qeConflict[r+i], d_qeConflict[s+i]); - } -} - -/** - * Region notation: - * A region is either - * - A single element X@i with the name X at the position i - * - A sequence of indices X@[i,j) with the name X and the elements between i [inclusive] and j exclusive - * - A concatenation of regions R1 and R2, R1;R2 - * - * Given the fixed assumptions C @ [0,cEnd) and a set of candidate minimizations U@[cEnd, uEnd) - * s.t. C \cup U is known to be in conflict ([0,uEnd) has a conflict), find a minimal - * subset of U, Delta, s.t. C \cup Delta is in conflict. - * - * Pre: - * [0, uEnd) is a set and is in conflict. - * uEnd <= assumptions.size() - * [0, cEnd) is in d_inSoi. - * - * Invariants: [0,cEnd) is never modified - * - * Post: - * [0, cEnd); [cEnd, deltaEnd) is in conflict - * [0, deltaEnd) is a set - * [0, deltaEnd) is in d_inSoi - */ -uint32_t SumOfInfeasibilitiesSPD::quickExplainRec(uint32_t cEnd, uint32_t uEnd){ - Assert(cEnd <= uEnd); - Assert(d_qeInUAndNotInSoi.empty()); - Assert(d_qeGreedyOrder.empty()); - - const Tableau::Entry* spoiler = NULL; - - if(d_soiVar != ARITHVAR_SENTINEL && d_linEq.selectSlackEntry(d_soiVar, false) == NULL){ - // already in conflict - return cEnd; - } - - Assert(cEnd < uEnd); - - // Phase 1 : Construct the conflict greedily - - for(uint32_t i = cEnd; i < uEnd; ++i){ - d_qeInUAndNotInSoi.add(d_qeConflict[i]); - } - if(d_soiVar == ARITHVAR_SENTINEL){ // special case for d_soiVar being empty - ArithVar first = d_qeConflict[cEnd]; - d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, first); - d_qeInSoi.add(first); - d_qeInUAndNotInSoi.remove(first); - d_qeGreedyOrder.push_back(first); - } - while((spoiler = d_linEq.selectSlackEntry(d_soiVar, false)) != NULL){ - Assert(!d_qeInUAndNotInSoi.empty()); - - ArithVar nb = spoiler->getColVar(); - int oppositeSgn = -(spoiler->getCoefficient().sgn()); - Assert(oppositeSgn != 0); - - ArithVar basicWithOp = find_basic_in_sgns(d_qeSgns, nb, oppositeSgn, d_qeInUAndNotInSoi, true); - Assert(basicWithOp != ARITHVAR_SENTINEL); - - addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, basicWithOp); - d_qeInSoi.add(basicWithOp); - d_qeInUAndNotInSoi.remove(basicWithOp); - d_qeGreedyOrder.push_back(basicWithOp); - } - Assert(spoiler == NULL); - - // Compact the set u - uint32_t newEnd = cEnd + d_qeGreedyOrder.size(); - std::copy(d_qeGreedyOrder.begin(), d_qeGreedyOrder.end(), d_qeConflict.begin()+cEnd); - - d_qeInUAndNotInSoi.purge(); - d_qeGreedyOrder.clear(); - - // Phase 2 : Recursively determine the minimal set of rows - - uint32_t xPos = cEnd; - std::swap(d_qeGreedyOrder[xPos], d_qeGreedyOrder[newEnd - 1]); - uint32_t uBegin = xPos + 1; - uint32_t split = (newEnd - uBegin)/2 + uBegin; - - //assumptions : C @ [0, cEnd); X @ xPos; U1 @ [u1Begin, split); U2 @ [split, newEnd) - // [0, newEnd) == d_inSoi - - uint32_t compactU2; - if(split == newEnd){ // U2 is empty - compactU2 = newEnd; - }else{ - // Remove U2 from Soi - qeRemoveRange(split, newEnd); - // [0, split) == d_inSoi - - // pre assumptions: C + X + U1 @ [0,split); U2 [split, newEnd) - compactU2 = quickExplainRec(split, newEnd); - // post: - // assumptions: C + X + U1 @ [0, split); delta2 @ [split - compactU2) - // d_inSoi = [0, compactU2) - } - uint32_t deltaSize = compactU2 - split; - qeSwapRange(deltaSize, uBegin, split); - uint32_t d2End = uBegin+deltaSize; - // assumptions : C @ [0, cEnd); X @ xPos; delta2 @ [uBegin, d2End); U1 @ [d2End, compactU2) - // d_inSoi == [0, compactU2) - - uint32_t d1End; - if(d2End == compactU2){ // U1 is empty - d1End = d2End; - }else{ - qeRemoveRange(d2End, compactU2); - - //pre assumptions : C + X + delta2 @ [0, d2End); U1 @ [d2End, compactU2); - d1End = quickExplainRec(d2End, compactU2); - //post: - // assumptions : C + X + delta2 @ [0, d2End); delta1 @ [d2End, d1End); - // d_inSoi = [0, d1End) - } - //After both: - // d_inSoi == [0, d1End), C @ [0, cEnd); X + delta2 + delta 1 @ [xPos, d1End); - - Assert(d_qeInUAndNotInSoi.empty()); - Assert(d_qeGreedyOrder.empty()); - return d1End; -} - -void SumOfInfeasibilitiesSPD::quickExplain(){ - Assert(d_qeInSoi.empty()); - Assert(d_qeInUAndNotInSoi.empty()); - Assert(d_qeGreedyOrder.empty()); - Assert(d_soiVar == ARITHVAR_SENTINEL); - Assert(d_qeSgns.empty()); - - d_qeConflict.clear(); - d_errorSet.pushFocusInto(d_qeConflict); - - //cout << d_qeConflict.size() << " "; - uint32_t size = d_qeConflict.size(); - - if(size > 2){ - for(ErrorSet::focus_iterator iter = d_errorSet.focusBegin(), end = d_errorSet.focusEnd(); iter != end; ++iter){ - ArithVar e = *iter; - addRowSgns(d_qeSgns, e, d_errorSet.getSgn(e)); - } - uint32_t end = quickExplainRec(0u, size); - Assert(end <= d_qeConflict.size()); - Assert(d_soiVar != ARITHVAR_SENTINEL); - Assert(!d_qeInSoi.empty()); - - d_qeConflict.resize(end); - tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); - d_soiVar = ARITHVAR_SENTINEL; - d_qeInSoi.purge(); - d_qeSgns.clear(); - } - - //cout << d_qeConflict.size() << endl; - - Assert(d_qeInSoi.empty()); - Assert(d_qeInUAndNotInSoi.empty()); - Assert(d_qeGreedyOrder.empty()); - Assert(d_soiVar == ARITHVAR_SENTINEL); - Assert(d_qeSgns.empty()); -} - -unsigned SumOfInfeasibilitiesSPD::trySet(const ArithVarVec& set){ - Assert(d_soiVar == ARITHVAR_SENTINEL); - bool success = false; - if(set.size() >= 2){ - d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, set); - success = d_linEq.selectSlackEntry(d_soiVar, false) == NULL; - - tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); - d_soiVar = ARITHVAR_SENTINEL; - } - return success ? set.size() : std::numeric_limits::max(); -} - -std::vector< ArithVarVec > SumOfInfeasibilitiesSPD::greedyConflictSubsets(){ - Trace("arith::greedyConflictSubsets") << "greedyConflictSubsets start" << endl; - - std::vector< ArithVarVec > subsets; - Assert(d_soiVar == ARITHVAR_SENTINEL); - - if(d_errorSize <= 2){ - ArithVarVec inError; - d_errorSet.pushFocusInto(inError); - - Assert(debugIsASet(inError)); - subsets.push_back(inError); - return subsets; - } - Assert(d_errorSize > 2); - - //sgns_table< , [basics] >; - // Phase 0: Construct the sgns table - sgn_table sgns; - DenseSet hasParticipated; //Has participated in a conflict - for(ErrorSet::focus_iterator iter = d_errorSet.focusBegin(), end = d_errorSet.focusEnd(); iter != end; ++iter){ - ArithVar e = *iter; - addRowSgns(sgns, e, d_errorSet.getSgn(e)); - - Trace("arith::greedyConflictSubsets") << "basic error var: " << e << endl; - if(TraceIsOn("arith::greedyConflictSubsets")){ - d_tableau.debugPrintIsBasic(e); - d_tableau.printBasicRow(e, Trace("arith::greedyConflictSubsets")); - } - } - - // Phase 1: Try to find at least 1 pair for every element - ArithVarVec tmp; - tmp.push_back(0); - tmp.push_back(0); - for(ErrorSet::focus_iterator iter = d_errorSet.focusBegin(), end = d_errorSet.focusEnd(); iter != end; ++iter){ - ArithVar e = *iter; - tmp[0] = e; - - int errSgn = d_errorSet.getSgn(e); - bool decreasing = errSgn < 0; - const Tableau::Entry* spoiler = d_linEq.selectSlackEntry(e, decreasing); - Assert(spoiler != NULL); - ArithVar nb = spoiler->getColVar(); - int oppositeSgn = -(errSgn * (spoiler->getCoefficient().sgn())); - - sgn_table::const_iterator opposites = find_sgns(sgns, nb, oppositeSgn); - Assert(opposites != sgns.end()); - - const ArithVarVec& choices = (*opposites).second; - for(ArithVarVec::const_iterator j = choices.begin(), jend = choices.end(); j != jend; ++j){ - ArithVar b = *j; - if(b < e){ continue; } - tmp[0] = e; - tmp[1] = b; - if(trySet(tmp) == 2){ - Trace("arith::greedyConflictSubsets") << "found a pair " << b << " " << e << endl; - hasParticipated.softAdd(b); - hasParticipated.softAdd(e); - Assert(debugIsASet(tmp)); - subsets.push_back(tmp); - ++(d_statistics.d_soiConflicts); - ++(d_statistics.d_hasToBeMinimal); - } - } - } - - - // Phase 2: If there is a variable that has not participated attempt to start a conflict - ArithVarVec possibleStarts; //List of elements that can be tried for starts. - d_errorSet.pushFocusInto(possibleStarts); - while(!possibleStarts.empty()){ - Assert(d_soiVar == ARITHVAR_SENTINEL); - - ArithVar v = possibleStarts.back(); - possibleStarts.pop_back(); - if(hasParticipated.isMember(v)){ continue; } - - hasParticipated.add(v); - - Assert(d_soiVar == ARITHVAR_SENTINEL); - //d_soiVar's row = \sumofinfeasibilites underConstruction - ArithVarVec underConstruction; - underConstruction.push_back(v); - d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, v); - - Trace("arith::greedyConflictSubsets") << "trying " << v << endl; - - const Tableau::Entry* spoiler = NULL; - while( (spoiler = d_linEq.selectSlackEntry(d_soiVar, false)) != NULL){ - ArithVar nb = spoiler->getColVar(); - int oppositeSgn = -(spoiler->getCoefficient().sgn()); - Assert(oppositeSgn != 0); - - Trace("arith::greedyConflictSubsets") << "looking for " << nb << " " << oppositeSgn << endl; - - ArithVar basicWithOp = find_basic_in_sgns(sgns, nb, oppositeSgn, hasParticipated, false); - - if(basicWithOp == ARITHVAR_SENTINEL){ - Trace("arith::greedyConflictSubsets") << "search did not work for " << nb << endl; - // greedy construction has failed - break; - }else{ - Trace("arith::greedyConflictSubsets") << "found " << basicWithOp << endl; - - addToInfeasFunc(d_statistics.d_soiConflictMinimization, d_soiVar, basicWithOp); - hasParticipated.softAdd(basicWithOp); - underConstruction.push_back(basicWithOp); - } - } - if(spoiler == NULL){ - Trace("arith::greedyConflictSubsets") << "success" << endl; - //then underConstruction contains a conflicting subset - Assert(debugIsASet(underConstruction)); - subsets.push_back(underConstruction); - ++d_statistics.d_soiConflicts; - if(underConstruction.size() == 3){ - ++d_statistics.d_hasToBeMinimal; - }else{ - ++d_statistics.d_maybeNotMinimal; - } - }else{ - Trace("arith::greedyConflictSubsets") << "failure" << endl; - } - tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); - d_soiVar = ARITHVAR_SENTINEL; - // if(false && spoiler == NULL){ - // ArithVarVec tmp; - // int smallest = tryAllSubsets(underConstruction, 0, tmp); - // cout << underConstruction.size() << " " << smallest << endl; - // Assert(smallest >= underConstruction.size()); - // if(smallest < underConstruction.size()){ - // exit(-1); - // } - // } - } - - Assert(d_soiVar == ARITHVAR_SENTINEL); - Trace("arith::greedyConflictSubsets") << "greedyConflictSubsets done" << endl; - return subsets; -} - -bool SumOfInfeasibilitiesSPD::generateSOIConflict(const ArithVarVec& subset){ - Assert(d_soiVar == ARITHVAR_SENTINEL); - d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization, subset); - Assert(!subset.empty()); - Assert(!d_conflictBuilder->underConstruction()); - - Trace("arith::generateSOIConflict") << "SumOfInfeasibilitiesSPD::generateSOIConflict(...) start" << endl; - - bool success = false; - - for(ArithVarVec::const_iterator iter = subset.begin(), end = subset.end(); iter != end; ++iter){ - ArithVar e = *iter; - ConstraintP violated = d_errorSet.getViolated(e); - Assert(violated != NullConstraint); - - int sgn = d_errorSet.getSgn(e); - const Rational& violatedCoeff = sgn > 0 ? d_negOne : d_posOne; - Trace("arith::generateSOIConflict") << "basic error var: " - << "(" << violatedCoeff << ")" - << " " << violated - << endl; - - - d_conflictBuilder->addConstraint(violated, violatedCoeff); - Assert(violated->hasProof()); - if(!success && !violated->negationHasProof()){ - success = true; - d_conflictBuilder->makeLastConsequent(); - } - } - - if(!success){ - // failure - d_conflictBuilder->reset(); - } else { - // pick a violated constraint arbitrarily. any of them may be selected for the conflict - Assert(d_conflictBuilder->underConstruction()); - Assert(d_conflictBuilder->consequentIsSet()); - - for(Tableau::RowIterator i = d_tableau.basicRowIterator(d_soiVar); !i.atEnd(); ++i){ - const Tableau::Entry& entry = *i; - ArithVar v = entry.getColVar(); - if(v == d_soiVar){ continue; } - const Rational& coeff = entry.getCoefficient(); - - ConstraintP c = (coeff.sgn() > 0) ? - d_variables.getUpperBoundConstraint(v) : - d_variables.getLowerBoundConstraint(v); - - Trace("arith::generateSOIConflict") << "non-basic var: " - << "(" << coeff << ")" - << " " << c - << endl; - d_conflictBuilder->addConstraint(c, coeff); - } - ConstraintCP conflicted = d_conflictBuilder->commitConflict(); - d_conflictChannel.raiseConflict(conflicted, - InferenceId::ARITH_CONF_SOI_SIMPLEX); - } - - tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); - d_soiVar = ARITHVAR_SENTINEL; - Trace("arith::generateSOIConflict") << "SumOfInfeasibilitiesSPD::generateSOIConflict(...) done" << endl; - Assert(d_soiVar == ARITHVAR_SENTINEL); - Assert(!d_conflictBuilder->underConstruction()); - return success; -} - - -WitnessImprovement SumOfInfeasibilitiesSPD::SOIConflict(){ - Trace("arith::SOIConflict") << "SumOfInfeasibilitiesSPD::SOIConflict() start " - << ": |E| = " << d_errorSize << endl; - if(TraceIsOn("arith::SOIConflict")){ - d_errorSet.debugPrint(cout); - } - Trace("arith::SOIConflict") << endl; - - tearDownInfeasiblityFunction(d_statistics.d_soiConflictMinimization, d_soiVar); - d_soiVar = ARITHVAR_SENTINEL; - - if (options().arith.soiQuickExplain) - { - quickExplain(); - generateSOIConflict(d_qeConflict); - } - else - { - vector subsets = greedyConflictSubsets(); - Assert(d_soiVar == ARITHVAR_SENTINEL); - bool anySuccess = false; - Assert(!subsets.empty()); - for(vector::const_iterator i = subsets.begin(), end = subsets.end(); i != end; ++i){ - const ArithVarVec& subset = *i; - Assert(debugIsASet(subset)); - anySuccess = generateSOIConflict(subset) || anySuccess; - //Node conflict = generateSOIConflict(subset); - //cout << conflict << endl; - - //reportConflict(conf); do not do this. We need a custom explanations! - //d_conflictChannel(conflict); - } - Assert(anySuccess); - } - Assert(d_soiVar == ARITHVAR_SENTINEL); - d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiConflictMinimization); - - //reportConflict(conf); do not do this. We need a custom explanations! - d_conflictVariables.add(d_soiVar); - - Trace("arith::SOIConflict") - << "SumOfInfeasibilitiesSPD::SOIConflict() end" << endl; - return ConflictFound; -} - -WitnessImprovement SumOfInfeasibilitiesSPD::soiRound() { - Assert(d_soiVar != ARITHVAR_SENTINEL); - - bool useBlands = degeneratePivotsInARow() >= s_maxDegeneratePivotsBeforeBlandsOnLeaving; - LinearEqualityModule::UpdatePreferenceFunction upf; - if(useBlands) { - upf = &LinearEqualityModule::preferWitness; - } else { - upf = &LinearEqualityModule::preferWitness; - } - - LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? - &LinearEqualityModule::minVarOrder : - &LinearEqualityModule::minRowLength; - bpf = &LinearEqualityModule::minVarOrder; - - UpdateInfo selected = selectUpdate(upf, bpf); - - if(selected.uninitialized()){ - Trace("selectFocusImproving") << "SOI is optimum, but we don't have sat/conflict yet" << endl; - return SOIConflict(); - }else{ - Assert(!selected.uninitialized()); - WitnessImprovement w = selected.getWitness(false); - Assert(debugCheckWitness(selected, w, false)); - - updateAndSignal(selected, w); - logPivot(w); - return w; - } -} - -Result::Status SumOfInfeasibilitiesSPD::sumOfInfeasibilities() -{ - TimerStat::CodeTimer codeTimer(d_statistics.d_soiTimer); - - Assert(d_sgnDisagreements.empty()); - Assert(d_pivotBudget != 0); - Assert(d_errorSize == d_errorSet.errorSize()); - Assert(d_errorSize > 0); - Assert(d_conflictVariables.empty()); - Assert(d_soiVar == ARITHVAR_SENTINEL); - - //d_scores.purge(); - d_soiVar = constructInfeasiblityFunction(d_statistics.d_soiFocusConstructionTimer); - - - while(d_pivotBudget != 0 && d_errorSize > 0 && d_conflictVariables.empty()){ - Trace("dualLike") << "dualLike" << endl; - - Assert(d_errorSet.noSignals()); - // Possible outcomes: - // - conflict - // - budget was exhausted - // - focus went down - WitnessImprovement w = soiRound(); - Trace("dualLike") << "selectFocusImproving -> " << w << endl; - - Assert(d_errorSize == d_errorSet.errorSize()); - } - - - if(d_soiVar != ARITHVAR_SENTINEL){ - tearDownInfeasiblityFunction(d_statistics.d_soiFocusConstructionTimer, d_soiVar); - d_soiVar = ARITHVAR_SENTINEL; - } - - Assert(d_soiVar == ARITHVAR_SENTINEL); - if(!d_conflictVariables.empty()){ - return Result::UNSAT; - }else if(d_errorSet.errorEmpty()){ - Assert(d_errorSet.noSignals()); - return Result::SAT; - }else{ - Assert(d_pivotBudget == 0); - return Result::UNKNOWN; - } -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/soi_simplex.h b/src/theory/arith/soi_simplex.h deleted file mode 100644 index 56483ee62..000000000 --- a/src/theory/arith/soi_simplex.h +++ /dev/null @@ -1,227 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Morgan Deters - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * This is an implementation of the Simplex Module for the Simplex for - * DPLL(T) decision procedure. - * - * This implements the Simplex module for the Simpelx for DPLL(T) decision - * procedure. - * See the Simplex for DPLL(T) technical report for more background.(citation?) - * This shares with the theory a Tableau, and a PartialModel that: - * - satisfies the equalities in the Tableau, and - * - the assignment for the non-basic variables satisfies their bounds. - * This is required to either produce a conflict or satisifying PartialModel. - * Further, we require being told when a basic variable updates its value. - * - * During the Simplex search we maintain a queue of variables. - * The queue is required to contain all of the basic variables that voilate - * their bounds. - * As elimination from the queue is more efficient to be done lazily, - * we do not maintain that the queue of variables needs to be only basic - * variables or only variables that satisfy their bounds. - * - * The simplex procedure roughly follows Alberto's thesis. (citation?) - * There is one round of selecting using a heuristic pivoting rule. - * (See PreferenceFunction Documentation for the available options.) - * The non-basic variable is the one that appears in the fewest pivots. - * (Bruno says that Leonardo invented this first.) - * After this, Bland's pivot rule is invoked. - * - * During this proccess, we periodically inspect the queue of variables to - * 1) remove now extraneous extries, - * 2) detect conflicts that are "waiting" on the queue but may not be detected - * by the current queue heuristics, and - * 3) detect multiple conflicts. - * - * Conflicts are greedily slackened to use the weakest bounds that still - * produce the conflict. - * - * Extra things tracked atm: (Subject to change at Tim's whims) - * - A superset of all of the newly pivoted variables. - * - A queue of additional conflicts that were discovered by Simplex. - * These are theory valid and are currently turned into lemmas - */ - -#include "cvc5_private.h" - -#pragma once - -#include "theory/arith/linear_equality.h" -#include "theory/arith/simplex.h" -#include "theory/arith/simplex_update.h" -#include "util/dense_map.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class SumOfInfeasibilitiesSPD : public SimplexDecisionProcedure { -public: - SumOfInfeasibilitiesSPD(Env& env, - LinearEqualityModule& linEq, - ErrorSet& errors, - RaiseConflict conflictChannel, - TempVarMalloc tvmalloc); - - Result::Status findModel(bool exactResult) override; - - // other error variables are dropping - WitnessImprovement dualLikeImproveError(ArithVar evar); - WitnessImprovement primalImproveError(ArithVar evar); - -private: - /** The current sum of infeasibilities variable. */ - ArithVar d_soiVar; - - // dual like - // - found conflict - // - satisfied error set - Result::Status sumOfInfeasibilities(); - - int32_t d_pivotBudget; - - WitnessImprovement d_prevWitnessImprovement; - uint32_t d_witnessImprovementInARow; - - uint32_t degeneratePivotsInARow() const; - - static constexpr uint32_t s_focusThreshold = 6; - static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnLeaving = 100; - static constexpr uint32_t s_maxDegeneratePivotsBeforeBlandsOnEntering = 10; - - DenseMap d_leavingCountSinceImprovement; - void increaseLeavingCount(ArithVar x){ - if(!d_leavingCountSinceImprovement.isKey(x)){ - d_leavingCountSinceImprovement.set(x,1); - }else{ - (d_leavingCountSinceImprovement.get(x))++; - } - } - LinearEqualityModule::UpdatePreferenceFunction selectLeavingFunction(ArithVar x){ - bool useBlands = d_leavingCountSinceImprovement.isKey(x) && - d_leavingCountSinceImprovement[x] >= s_maxDegeneratePivotsBeforeBlandsOnEntering; - if(useBlands) { - return &LinearEqualityModule::preferWitness; - } else { - return &LinearEqualityModule::preferWitness; - } - } - - void debugPrintSignal(ArithVar updated) const; - - ArithVarVec d_sgnDisagreements; - - void logPivot(WitnessImprovement w); - - void updateAndSignal(const UpdateInfo& selected, WitnessImprovement w); - - UpdateInfo selectUpdate(LinearEqualityModule::UpdatePreferenceFunction upf, - LinearEqualityModule::VarPreferenceFunction bpf); - - - // UpdateInfo selectUpdateForDualLike(ArithVar basic){ - // TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForDualLike); - - // LinearEqualityModule::UpdatePreferenceFunction upf = - // &LinearEqualityModule::preferWitness; - // LinearEqualityModule::VarPreferenceFunction bpf = - // &LinearEqualityModule::minVarOrder; - // return selectPrimalUpdate(basic, upf, bpf); - // } - - // UpdateInfo selectUpdateForPrimal(ArithVar basic, bool useBlands){ - // TimerStat::CodeTimer codeTimer(d_statistics.d_selectUpdateForPrimal); - - // LinearEqualityModule::UpdatePreferenceFunction upf = useBlands ? - // &LinearEqualityModule::preferWitness: - // &LinearEqualityModule::preferWitness; - - // LinearEqualityModule::VarPreferenceFunction bpf = useBlands ? - // &LinearEqualityModule::minVarOrder : - // &LinearEqualityModule::minRowLength; - // bpf = &LinearEqualityModule::minVarOrder; - - // return selectPrimalUpdate(basic, upf, bpf); - // } - // WitnessImprovement selectFocusImproving() ; - WitnessImprovement soiRound(); - WitnessImprovement SOIConflict(); - std::vector< ArithVarVec > greedyConflictSubsets(); - bool generateSOIConflict(const ArithVarVec& subset); - - // WitnessImprovement focusUsingSignDisagreements(ArithVar basic); - // WitnessImprovement focusDownToLastHalf(); - // WitnessImprovement adjustFocusShrank(const ArithVarVec& drop); - // WitnessImprovement focusDownToJust(ArithVar v); - - - void adjustFocusAndError(const UpdateInfo& up, const AVIntPairVec& focusChanges); - - /** - * This is the main simplex for DPLL(T) loop. - * It runs for at most maxIterations. - * - * Returns true iff it has found a conflict. - * d_conflictVariable will be set and the conflict for this row is reported. - */ - bool searchForFeasibleSolution(uint32_t maxIterations); - - bool initialProcessSignals(){ - TimerStat &timer = d_statistics.d_initialSignalsTime; - IntStat& conflictStat = d_statistics.d_initialConflicts; - return standardProcessSignals(timer, conflictStat); - } - - void quickExplain(); - DenseSet d_qeInSoi; - DenseSet d_qeInUAndNotInSoi; - ArithVarVec d_qeConflict; - ArithVarVec d_qeGreedyOrder; - sgn_table d_qeSgns; - - uint32_t quickExplainRec(uint32_t cEnd, uint32_t uEnd); - void qeAddRange(uint32_t begin, uint32_t end); - void qeRemoveRange(uint32_t begin, uint32_t end); - void qeSwapRange(uint32_t N, uint32_t r, uint32_t s); - - unsigned trySet(const ArithVarVec& set); - unsigned tryAllSubsets(const ArithVarVec& set, unsigned depth, ArithVarVec& tmp); - - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - TimerStat d_initialSignalsTime; - IntStat d_initialConflicts; - - IntStat d_soiFoundUnsat; - IntStat d_soiFoundSat; - IntStat d_soiMissed; - - IntStat d_soiConflicts; - IntStat d_hasToBeMinimal; - IntStat d_maybeNotMinimal; - - TimerStat d_soiTimer; - TimerStat d_soiFocusConstructionTimer; - TimerStat d_soiConflictMinimization; - TimerStat d_selectUpdateForSOI; - - ReferenceStat d_finalCheckPivotCounter; - - Statistics(const std::string& name, uint32_t& pivots); - } d_statistics; -};/* class FCSimplexDecisionProcedure */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/tableau.cpp b/src/theory/arith/tableau.cpp deleted file mode 100644 index 9a96ca944..000000000 --- a/src/theory/arith/tableau.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "base/output.h" -#include "theory/arith/tableau.h" - -using namespace std; -namespace cvc5::internal { -namespace theory { -namespace arith { - - -void Tableau::pivot(ArithVar oldBasic, ArithVar newBasic, CoefficientChangeCallback& cb){ - Assert(isBasic(oldBasic)); - Assert(!isBasic(newBasic)); - Assert(d_mergeBuffer.empty()); - - Trace("tableau") << "Tableau::pivot(" << oldBasic <<", " << newBasic <<")" << endl; - - RowIndex ridx = basicToRowIndex(oldBasic); - - rowPivot(oldBasic, newBasic, cb); - Assert(ridx == basicToRowIndex(newBasic)); - - loadRowIntoBuffer(ridx); - - ColIterator colIter = colIterator(newBasic); - while(!colIter.atEnd()){ - EntryID id = colIter.getID(); - Entry& entry = d_entries.get(id); - - ++colIter; //needs to be incremented before the variable is removed - if(entry.getRowIndex() == ridx){ continue; } - - RowIndex to = entry.getRowIndex(); - Rational coeff = entry.getCoefficient(); - if(cb.canUseRow(to)){ - rowPlusBufferTimesConstant(to, coeff, cb); - }else{ - rowPlusBufferTimesConstant(to, coeff); - } - } - clearBuffer(); - - //Clear the column for used for this variable - - Assert(d_mergeBuffer.empty()); - Assert(!isBasic(oldBasic)); - Assert(isBasic(newBasic)); - Assert(getColLength(newBasic) == 1); -} - -/** - * Changes basic to newbasic (a variable on the row). - */ -void Tableau::rowPivot(ArithVar basicOld, ArithVar basicNew, CoefficientChangeCallback& cb){ - Assert(isBasic(basicOld)); - Assert(!isBasic(basicNew)); - - RowIndex rid = basicToRowIndex(basicOld); - - EntryID newBasicID = findOnRow(rid, basicNew); - - Assert(newBasicID != ENTRYID_SENTINEL); - - Tableau::Entry& newBasicEntry = d_entries.get(newBasicID); - const Rational& a_rs = newBasicEntry.getCoefficient(); - int a_rs_sgn = a_rs.sgn(); - Rational negInverseA_rs = -(a_rs.inverse()); - - for(RowIterator i = basicRowIterator(basicOld); !i.atEnd(); ++i){ - EntryID id = i.getID(); - Tableau::Entry& entry = d_entries.get(id); - - entry.getCoefficient() *= negInverseA_rs; - } - - d_basic2RowIndex.remove(basicOld); - d_basic2RowIndex.set(basicNew, rid); - d_rowIndex2basic.set(rid, basicNew); - - cb.multiplyRow(rid, -a_rs_sgn); -} - -void Tableau::addRow(ArithVar basic, - const std::vector& coefficients, - const std::vector& variables) -{ - Assert(basic < getNumColumns()); - Assert(debugIsASet(variables)); - Assert(coefficients.size() == variables.size()); - Assert(!isBasic(basic)); - - RowIndex newRow = Matrix::addRow(coefficients, variables); - addEntry(newRow, basic, Rational(-1)); - - Assert(!d_basic2RowIndex.isKey(basic)); - Assert(!d_rowIndex2basic.isKey(newRow)); - - d_basic2RowIndex.set(basic, newRow); - d_rowIndex2basic.set(newRow, basic); - - - if(TraceIsOn("matrix")){ printMatrix(); } - - NoEffectCCCB noeffect; - NoEffectCCCB* nep = &noeffect; - CoefficientChangeCallback* cccb = static_cast(nep); - - vector::const_iterator coeffIter = coefficients.begin(); - vector::const_iterator varsIter = variables.begin(); - vector::const_iterator varsEnd = variables.end(); - for(; varsIter != varsEnd; ++coeffIter, ++varsIter){ - ArithVar var = *varsIter; - - if(isBasic(var)){ - Rational coeff = *coeffIter; - - RowIndex ri = basicToRowIndex(var); - - loadRowIntoBuffer(ri); - rowPlusBufferTimesConstant(newRow, coeff, *cccb); - clearBuffer(); - } - } - - if(TraceIsOn("matrix")) { printMatrix(); } - - Assert(debugNoZeroCoefficients(newRow)); - Assert(debugMatchingCountsForRow(newRow)); - Assert(getColLength(basic) == 1); -} - -void Tableau::removeBasicRow(ArithVar basic){ - RowIndex rid = basicToRowIndex(basic); - - removeRow(rid); - d_basic2RowIndex.remove(basic); - d_rowIndex2basic.remove(rid); -} - -void Tableau::substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult, CoefficientChangeCallback& cb){ - if(!mult.isZero()){ - RowIndex to_idx = basicToRowIndex(to); - addEntry(to_idx, from, mult); // Add an entry to be cancelled out - RowIndex from_idx = basicToRowIndex(from); - - cb.update(to_idx, from, 0, mult.sgn()); - - loadRowIntoBuffer(from_idx); - rowPlusBufferTimesConstant(to_idx, mult, cb); - clearBuffer(); - } -} - -uint32_t Tableau::rowComplexity(ArithVar basic) const{ - uint32_t complexity = 0; - for(RowIterator i = basicRowIterator(basic); !i.atEnd(); ++i){ - const Entry& e = *i; - complexity += e.getCoefficient().complexity(); - } - return complexity; -} - -double Tableau::avgRowComplexity() const{ - double sum = 0; - uint32_t rows = 0; - for(BasicIterator i = beginBasic(), i_end = endBasic(); i != i_end; ++i){ - sum += rowComplexity(*i); - rows++; - } - return (rows == 0) ? 0 : (sum/rows); -} - -void Tableau::printBasicRow(ArithVar basic, std::ostream& out){ - printRow(basicToRowIndex(basic), out); -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/tableau.h b/src/theory/arith/tableau.h deleted file mode 100644 index 2c5fc3ccb..000000000 --- a/src/theory/arith/tableau.h +++ /dev/null @@ -1,163 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Morgan Deters - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include - -#include "theory/arith/arithvar.h" -#include "theory/arith/matrix.h" -#include "util/dense_map.h" -#include "util/rational.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -/** - * A Tableau is a Rational matrix that keeps its rows in solved form. - * Each row has a basic variable with coefficient -1 that is solved. - * Tableau is optimized for pivoting. - * The tableau should only be updated via pivot calls. - */ -class Tableau : public Matrix { -public: -private: - typedef DenseMap BasicToRowMap; - // Set of all of the basic variables in the tableau. - // ArithVarMap : ArithVar |-> RowIndex - BasicToRowMap d_basic2RowIndex; - - // RowIndex |-> Basic Variable - typedef DenseMap RowIndexToBasicMap; - RowIndexToBasicMap d_rowIndex2basic; - -public: - - Tableau() : Matrix(Rational(0)) {} - - typedef Matrix::ColIterator ColIterator; - typedef Matrix::RowIterator RowIterator; - typedef BasicToRowMap::const_iterator BasicIterator; - - typedef MatrixEntry Entry; - - bool isBasic(ArithVar v) const{ - return d_basic2RowIndex.isKey(v); - } - - void debugPrintIsBasic(ArithVar v) const { - if(isBasic(v)){ - Trace("model") << v << " is basic." << std::endl; - }else{ - Trace("model") << v << " is non-basic." << std::endl; - } - } - - BasicIterator beginBasic() const { - return d_basic2RowIndex.begin(); - } - BasicIterator endBasic() const { - return d_basic2RowIndex.end(); - } - - RowIndex basicToRowIndex(ArithVar x) const { - return d_basic2RowIndex[x]; - } - - ArithVar rowIndexToBasic(RowIndex rid) const { - Assert(d_rowIndex2basic.isKey(rid)); - return d_rowIndex2basic[rid]; - } - - ColIterator colIterator(ArithVar x) const { - return getColumn(x).begin(); - } - - RowIterator ridRowIterator(RowIndex rid) const { - return getRow(rid).begin(); - } - - RowIterator basicRowIterator(ArithVar basic) const { - return ridRowIterator(basicToRowIndex(basic)); - } - - const Entry& basicFindEntry(ArithVar basic, ArithVar col) const { - return findEntry(basicToRowIndex(basic), col); - } - - /** - * Adds a row to the tableau. - * The new row is equivalent to: - * basicVar = \f$\sum_i\f$ coeffs[i] * variables[i] - * preconditions: - * basicVar is already declared to be basic - * basicVar does not have a row associated with it in the tableau. - * - * Note: each variables[i] does not have to be non-basic. - * Pivoting will be mimicked if it is basic. - */ - void addRow(ArithVar basicVar, - const std::vector& coeffs, - const std::vector& variables); - - /** - * preconditions: - * x_r is basic, - * x_s is non-basic, and - * a_rs != 0. - */ - void pivot(ArithVar basicOld, ArithVar basicNew, CoefficientChangeCallback& cb); - - void removeBasicRow(ArithVar basic); - - uint32_t basicRowLength(ArithVar basic) const{ - RowIndex ridx = basicToRowIndex(basic); - return getRowLength(ridx); - } - - /** - * to += mult * from - * replacing from with its row. - */ - void substitutePlusTimesConstant(ArithVar to, ArithVar from, const Rational& mult, CoefficientChangeCallback& cb); - - void directlyAddToCoefficient(ArithVar rowVar, ArithVar col, const Rational& mult, CoefficientChangeCallback& cb){ - RowIndex ridx = basicToRowIndex(rowVar); - manipulateRowEntry(ridx, col, mult, cb); - } - - /* Returns the complexity of a row in the tableau. */ - uint32_t rowComplexity(ArithVar basic) const; - - /* Returns the average complexity of the rows in the tableau. */ - double avgRowComplexity() const; - - void printBasicRow(ArithVar basic, std::ostream& out); - -private: - /* Changes the basic variable on the row for basicOld to basicNew. */ - void rowPivot(ArithVar basicOld, ArithVar basicNew, CoefficientChangeCallback& cb); - -};/* class Tableau */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/tableau_sizes.cpp b/src/theory/arith/tableau_sizes.cpp deleted file mode 100644 index b353812ff..000000000 --- a/src/theory/arith/tableau_sizes.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "base/output.h" -#include "theory/arith/tableau_sizes.h" -#include "theory/arith/tableau.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -uint32_t TableauSizes::getRowLength(ArithVar b) const { - return d_tab->basicRowLength(b); -} - -uint32_t TableauSizes::getColumnLength(ArithVar x) const { - return d_tab->getColLength(x); -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/tableau_sizes.h b/src/theory/arith/tableau_sizes.h deleted file mode 100644 index cf189a146..000000000 --- a/src/theory/arith/tableau_sizes.h +++ /dev/null @@ -1,43 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "cvc5_private.h" - -#pragma once - -#include "theory/arith/arithvar.h" - -namespace cvc5::internal { -namespace theory { -namespace arith { - -class Tableau; - -class TableauSizes { -private: - const Tableau* d_tab; -public: - TableauSizes(const Tableau* tab): d_tab(tab){} - - uint32_t getRowLength(ArithVar b) const; - uint32_t getColumnLength(ArithVar x) const; -}; /* TableauSizes */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/theory_arith.cpp b/src/theory/arith/theory_arith.cpp index f6496166d..fe04a9c26 100644 --- a/src/theory/arith/theory_arith.cpp +++ b/src/theory/arith/theory_arith.cpp @@ -22,9 +22,8 @@ #include "theory/arith/arith_evaluator.h" #include "theory/arith/arith_rewriter.h" #include "theory/arith/equality_solver.h" -#include "theory/arith/infer_bounds.h" #include "theory/arith/nl/nonlinear_extension.h" -#include "theory/arith/theory_arith_private.h" +#include "theory/arith/linear/theory_arith_private.h" #include "theory/ext_theory.h" #include "theory/rewriter.h" #include "theory/theory_model.h" @@ -45,7 +44,7 @@ TheoryArith::TheoryArith(Env& env, OutputChannel& out, Valuation valuation) d_ppre(d_env), d_bab(env, d_astate, d_im, d_ppre, d_pnm), d_eqSolver(nullptr), - d_internal(new TheoryArithPrivate(*this, env, d_bab)), + d_internal(new linear::TheoryArithPrivate(*this, env, d_bab)), d_nonlinearExtension(nullptr), d_opElim(d_env), d_arithPreproc(env, d_astate, d_im, d_pnm, d_opElim), @@ -363,12 +362,9 @@ Node TheoryArith::getModelValue(TNode var) { std::pair TheoryArith::entailmentCheck(TNode lit) { - ArithEntailmentCheckParameters def; - def.addLookupRowSumAlgorithms(); - ArithEntailmentCheckSideEffects ase; - std::pair res = d_internal->entailmentCheck(lit, def, ase); - return res; + return d_internal->entailmentCheck(lit); } + eq::ProofEqEngine* TheoryArith::getProofEqEngine() { return d_im.getProofEqEngine(); diff --git a/src/theory/arith/theory_arith.h b/src/theory/arith/theory_arith.h index 2682b4d71..c6ced5781 100644 --- a/src/theory/arith/theory_arith.h +++ b/src/theory/arith/theory_arith.h @@ -34,7 +34,10 @@ class NonlinearExtension; } class EqualitySolver; + +namespace linear { class TheoryArithPrivate; +} /** * Implementation of linear and non-linear integer and real arithmetic. @@ -42,7 +45,7 @@ class TheoryArithPrivate; * http://research.microsoft.com/en-us/um/people/leonardo/cav06.pdf */ class TheoryArith : public Theory { - friend class TheoryArithPrivate; + friend class linear::TheoryArithPrivate; public: TheoryArith(Env& env, OutputChannel& out, Valuation valuation); virtual ~TheoryArith(); @@ -157,7 +160,7 @@ class TheoryArith : public Theory { /** The equality solver */ std::unique_ptr d_eqSolver; /** The (old) linear arithmetic solver */ - TheoryArithPrivate* d_internal; + linear::TheoryArithPrivate* d_internal; /** * The non-linear extension, responsible for all approaches for non-linear diff --git a/src/theory/arith/theory_arith_private.cpp b/src/theory/arith/theory_arith_private.cpp deleted file mode 100644 index 58ec5ee19..000000000 --- a/src/theory/arith/theory_arith_private.cpp +++ /dev/null @@ -1,4995 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Gereon Kremer, Alex Ozdemir - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#include "theory/arith/theory_arith_private.h" - -#include -#include -#include -#include - -#include "base/output.h" -#include "context/cdhashset.h" -#include "context/cdinsert_hashmap.h" -#include "context/cdlist.h" -#include "context/cdqueue.h" -#include "context/context.h" -#include "expr/kind.h" -#include "expr/metakind.h" -#include "expr/node.h" -#include "expr/node_algorithm.h" -#include "expr/node_builder.h" -#include "expr/skolem_manager.h" -#include "options/arith_options.h" -#include "options/base_options.h" -#include "options/smt_options.h" -#include "preprocessing/util/ite_utilities.h" -#include "proof/proof_generator.h" -#include "proof/proof_node_manager.h" -#include "proof/proof_rule.h" -#include "smt/logic_exception.h" -#include "smt/smt_statistics_registry.h" -#include "smt_util/boolean_simplification.h" -#include "theory/arith/approx_simplex.h" -#include "theory/arith/arith_rewriter.h" -#include "theory/arith/arith_static_learner.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/congruence_manager.h" -#include "theory/arith/constraint.h" -#include "theory/arith/cut_log.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/dio_solver.h" -#include "theory/arith/linear_equality.h" -#include "theory/arith/matrix.h" -#include "theory/arith/nl/nonlinear_extension.h" -#include "theory/arith/normal_form.h" -#include "theory/arith/partial_model.h" -#include "theory/arith/simplex.h" -#include "theory/arith/theory_arith.h" -#include "theory/ext_theory.h" -#include "theory/quantifiers/fmf/bounded_integers.h" -#include "theory/rewriter.h" -#include "theory/theory_model.h" -#include "theory/trust_substitutions.h" -#include "theory/valuation.h" -#include "util/dense_map.h" -#include "util/integer.h" -#include "util/random.h" -#include "util/rational.h" -#include "util/result.h" -#include "util/statistics_stats.h" - -using namespace std; -using namespace cvc5::internal::kind; - -namespace cvc5::internal { -namespace theory { -namespace arith { - -static Node toSumNode(const ArithVariables& vars, const DenseMap& sum); -static bool complexityBelow(const DenseMap& row, uint32_t cap); - -TheoryArithPrivate::TheoryArithPrivate(TheoryArith& containing, - Env& env, - BranchAndBound& bab) - : EnvObj(env), - d_containing(containing), - d_foundNl(false), - d_rowTracking(), - d_bab(bab), - d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager() - : nullptr), - d_checker(), - d_pfGen(new EagerProofGenerator(d_pnm, userContext())), - d_constraintDatabase(d_env, - d_partialModel, - d_congruenceManager, - RaiseConflict(*this), - d_pfGen.get()), - d_qflraStatus(Result::UNKNOWN), - d_unknownsInARow(0), - d_hasDoneWorkSinceCut(false), - d_learner(userContext()), - d_assertionsThatDoNotMatchTheirLiterals(context()), - d_nextIntegerCheckVar(0), - d_constantIntegerVariables(context()), - d_diseqQueue(context(), false), - d_currentPropagationList(), - d_learnedBounds(context()), - d_preregisteredNodes(context()), - d_partialModel(context(), DeltaComputeCallback(*this)), - d_errorSet( - d_partialModel, TableauSizes(&d_tableau), BoundCountingLookup(*this)), - d_tableau(), - d_linEq(d_partialModel, - d_tableau, - d_rowTracking, - BasicVarModelUpdateCallBack(*this)), - d_diosolver(env), - d_restartsCounter(0), - d_tableauSizeHasBeenModified(false), - d_tableauResetDensity(1.6), - d_tableauResetPeriod(10), - d_conflicts(context()), - d_blackBoxConflict(context(), Node::null()), - d_blackBoxConflictPf(context(), std::shared_ptr(nullptr)), - d_congruenceManager(d_env, - d_constraintDatabase, - SetupLiteralCallBack(*this), - d_partialModel, - RaiseEqualityEngineConflict(*this)), - d_cmEnabled(context(), options().arith.arithCongMan), - - d_dualSimplex( - env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), - d_fcSimplex( - env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), - d_soiSimplex( - env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), - d_attemptSolSimplex( - env, d_linEq, d_errorSet, RaiseConflict(*this), TempVarMalloc(*this)), - d_pass1SDP(NULL), - d_otherSDP(NULL), - d_lastContextIntegerAttempted(context(), -1), - - d_DELTA_ZERO(0), - d_approxCuts(context()), - d_fullCheckCounter(0), - d_cutCount(context(), 0), - d_cutInContext(context()), - d_likelyIntegerInfeasible(context(), false), - d_guessedCoeffSet(context(), false), - d_guessedCoeffs(), - d_treeLog(NULL), - d_replayVariables(), - d_replayConstraints(), - d_lhsTmp(), - d_approxStats(NULL), - d_attemptSolveIntTurnedOff(userContext(), 0), - d_dioSolveResources(0), - d_solveIntMaybeHelp(0u), - d_solveIntAttempts(0u), - d_newFacts(false), - d_previousStatus(Result::UNKNOWN), - d_statistics(statisticsRegistry(), "theory::arith::") -{ -} - -TheoryArithPrivate::~TheoryArithPrivate(){ - if(d_treeLog != NULL){ delete d_treeLog; } - if(d_approxStats != NULL) { delete d_approxStats; } -} - -bool TheoryArithPrivate::needsEqualityEngine(EeSetupInfo& esi) -{ - if (!d_cmEnabled) - { - return false; - } - return d_congruenceManager.needsEqualityEngine(esi); -} -void TheoryArithPrivate::finishInit() -{ - if (d_cmEnabled) - { - eq::EqualityEngine* ee = d_containing.getEqualityEngine(); - Assert(ee != nullptr); - d_congruenceManager.finishInit(ee); - } -} - -static bool contains(const ConstraintCPVec& v, ConstraintP con){ - for(unsigned i = 0, N = v.size(); i < N; ++i){ - if(v[i] == con){ - return true; - } - } - return false; -} -static void drop( ConstraintCPVec& v, ConstraintP con){ - size_t readPos, writePos, N; - for(readPos = 0, writePos = 0, N = v.size(); readPos < N; ++readPos){ - ConstraintCP curr = v[readPos]; - if(curr != con){ - v[writePos] = curr; - writePos++; - } - } - v.resize(writePos); -} - - -static void resolve(ConstraintCPVec& buf, ConstraintP c, const ConstraintCPVec& pos, const ConstraintCPVec& neg){ - unsigned posPos CVC5_UNUSED = pos.size(); - for(unsigned i = 0, N = pos.size(); i < N; ++i){ - if(pos[i] == c){ - posPos = i; - }else{ - buf.push_back(pos[i]); - } - } - Assert(posPos < pos.size()); - ConstraintP negc = c->getNegation(); - unsigned negPos CVC5_UNUSED = neg.size(); - for(unsigned i = 0, N = neg.size(); i < N; ++i){ - if(neg[i] == negc){ - negPos = i; - }else{ - buf.push_back(neg[i]); - } - } - Assert(negPos < neg.size()); -} - -TheoryArithPrivate::ModelException::ModelException(TNode n, const char* msg) -{ - stringstream ss; - ss << "Cannot construct a model for " << n << " as " << endl << msg; - setMessage(ss.str()); -} -TheoryArithPrivate::ModelException::~ModelException() {} - -TheoryArithPrivate::Statistics::Statistics(StatisticsRegistry& reg, - const std::string& name) - : d_statAssertUpperConflicts( - reg.registerInt(name + "AssertUpperConflicts")), - d_statAssertLowerConflicts( - reg.registerInt(name + "AssertLowerConflicts")), - d_statUserVariables(reg.registerInt(name + "UserVariables")), - d_statAuxiliaryVariables(reg.registerInt(name + "AuxiliaryVariables")), - d_statDisequalitySplits(reg.registerInt(name + "DisequalitySplits")), - d_statDisequalityConflicts( - reg.registerInt(name + "DisequalityConflicts")), - d_simplifyTimer(reg.registerTimer(name + "simplifyTimer")), - d_staticLearningTimer(reg.registerTimer(name + "staticLearningTimer")), - d_presolveTime(reg.registerTimer(name + "presolveTime")), - d_newPropTime(reg.registerTimer(name + "newPropTimer")), - d_externalBranchAndBounds( - reg.registerInt(name + "externalBranchAndBounds")), - d_initialTableauSize(reg.registerInt(name + "initialTableauSize")), - d_currSetToSmaller(reg.registerInt(name + "currSetToSmaller")), - d_smallerSetToCurr(reg.registerInt(name + "smallerSetToCurr")), - d_restartTimer(reg.registerTimer(name + "restartTimer")), - d_boundComputationTime(reg.registerTimer(name + "bound::time")), - d_boundComputations(reg.registerInt(name + "bound::boundComputations")), - d_boundPropagations(reg.registerInt(name + "bound::boundPropagations")), - d_unknownChecks(reg.registerInt(name + "status::unknowns")), - d_maxUnknownsInARow(reg.registerInt(name + "status::maxUnknownsInARow")), - d_avgUnknownsInARow( - reg.registerAverage(name + "status::avgUnknownsInARow")), - d_revertsOnConflicts( - reg.registerInt(name + "status::revertsOnConflicts")), - d_commitsOnConflicts( - reg.registerInt(name + "status::commitsOnConflicts")), - d_nontrivialSatChecks( - reg.registerInt(name + "status::nontrivialSatChecks")), - d_replayLogRecCount(reg.registerInt(name + "z::approx::replay::rec")), - d_replayLogRecConflictEscalation( - reg.registerInt(name + "z::approx::replay::rec::escalation")), - d_replayLogRecEarlyExit( - reg.registerInt(name + "z::approx::replay::rec::earlyexit")), - d_replayBranchCloseFailures(reg.registerInt( - name + "z::approx::replay::rec::branch::closefailures")), - d_replayLeafCloseFailures(reg.registerInt( - name + "z::approx::replay::rec::leaf::closefailures")), - d_replayBranchSkips( - reg.registerInt(name + "z::approx::replay::rec::branch::skips")), - d_mirCutsAttempted( - reg.registerInt(name + "z::approx::cuts::mir::attempted")), - d_gmiCutsAttempted( - reg.registerInt(name + "z::approx::cuts::gmi::attempted")), - d_branchCutsAttempted( - reg.registerInt(name + "z::approx::cuts::branch::attempted")), - d_cutsReconstructed( - reg.registerInt(name + "z::approx::cuts::reconstructed")), - d_cutsReconstructionFailed( - reg.registerInt(name + "z::approx::cuts::reconstructed::failed")), - d_cutsProven(reg.registerInt(name + "z::approx::cuts::proofs")), - d_cutsProofFailed( - reg.registerInt(name + "z::approx::cuts::proofs::failed")), - d_mipReplayLemmaCalls( - reg.registerInt(name + "z::approx::external::calls")), - d_mipExternalCuts(reg.registerInt(name + "z::approx::external::cuts")), - d_mipExternalBranch( - reg.registerInt(name + "z::approx::external::branches")), - d_inSolveInteger(reg.registerInt(name + "z::approx::inSolverInteger")), - d_branchesExhausted( - reg.registerInt(name + "z::approx::exhausted::branches")), - d_execExhausted(reg.registerInt(name + "z::approx::exhausted::exec")), - d_pivotsExhausted(reg.registerInt(name + "z::approx::exhausted::pivots")), - d_panicBranches(reg.registerInt(name + "z::arith::paniclemmas")), - d_relaxCalls(reg.registerInt(name + "z::arith::relax::calls")), - d_relaxLinFeas(reg.registerInt(name + "z::arith::relax::feasible::res")), - d_relaxLinFeasFailures( - reg.registerInt(name + "z::arith::relax::feasible::failures")), - d_relaxLinInfeas(reg.registerInt(name + "z::arith::relax::infeasible")), - d_relaxLinInfeasFailures( - reg.registerInt(name + "z::arith::relax::infeasible::failures")), - d_relaxLinExhausted(reg.registerInt(name + "z::arith::relax::exhausted")), - d_relaxOthers(reg.registerInt(name + "z::arith::relax::other")), - d_applyRowsDeleted( - reg.registerInt(name + "z::arith::cuts::applyRowsDeleted")), - d_replaySimplexTimer( - reg.registerTimer(name + "z::approx::replay::simplex::timer")), - d_replayLogTimer( - reg.registerTimer(name + "z::approx::replay::log::timer")), - d_solveIntTimer(reg.registerTimer(name + "z::solveInt::timer")), - d_solveRealRelaxTimer( - reg.registerTimer(name + "z::solveRealRelax::timer")), - d_solveIntCalls(reg.registerInt(name + "z::solveInt::calls")), - d_solveStandardEffort( - reg.registerInt(name + "z::solveInt::calls::standardEffort")), - d_approxDisabled(reg.registerInt(name + "z::approxDisabled")), - d_replayAttemptFailed(reg.registerInt(name + "z::replayAttemptFailed")), - d_cutsRejectedDuringReplay( - reg.registerInt(name + "z::approx::replay::cuts::rejected")), - d_cutsRejectedDuringLemmas( - reg.registerInt(name + "z::approx::external::cuts::rejected")), - d_satPivots(reg.registerHistogram(name + "pivots::sat")), - d_unsatPivots(reg.registerHistogram(name + "pivots::unsat")), - d_unknownPivots( - reg.registerHistogram(name + "pivots::unknown")), - d_solveIntModelsAttempts( - reg.registerInt(name + "z::solveInt::models::attempts")), - d_solveIntModelsSuccessful( - reg.registerInt(name + "zzz::solveInt::models::successful")), - d_mipTimer(reg.registerTimer(name + "z::approx::mip::timer")), - d_lpTimer(reg.registerTimer(name + "z::approx::lp::timer")), - d_mipProofsAttempted(reg.registerInt(name + "z::mip::proofs::attempted")), - d_mipProofsSuccessful( - reg.registerInt(name + "z::mip::proofs::successful")), - d_numBranchesFailed( - reg.registerInt(name + "z::mip::branch::proof::failed")) -{ -} - -bool complexityBelow(const DenseMap& row, uint32_t cap){ - DenseMap::const_iterator riter, rend; - for(riter=row.begin(), rend=row.end(); riter != rend; ++riter){ - ArithVar v = *riter; - const Rational& q = row[v]; - if(q.complexity() > cap){ - return false; - } - } - return true; -} - -bool TheoryArithPrivate::isProofEnabled() const -{ - return d_pnm != nullptr; -} - -void TheoryArithPrivate::raiseConflict(ConstraintCP a, InferenceId id){ - Assert(a->inConflict()); - Assert(id != InferenceId::UNKNOWN) - << "Must provide an inference id in TheoryArithPrivate::raiseConflict"; - d_conflicts.push_back(std::make_pair(a, id)); -} - -void TheoryArithPrivate::raiseBlackBoxConflict(Node bb, - std::shared_ptr pf) -{ - Trace("arith::bb") << "raiseBlackBoxConflict: " << bb << std::endl; - if (d_blackBoxConflict.get().isNull()) - { - if (isProofEnabled()) - { - Trace("arith::bb") << " with proof " << pf << std::endl; - d_blackBoxConflictPf.set(pf); - } - d_blackBoxConflict = bb; - } -} - -bool TheoryArithPrivate::anyConflict() const -{ - return !conflictQueueEmpty() || !d_blackBoxConflict.get().isNull(); -} - -void TheoryArithPrivate::revertOutOfConflict(){ - d_partialModel.revertAssignmentChanges(); - clearUpdates(); - d_currentPropagationList.clear(); -} - -void TheoryArithPrivate::clearUpdates(){ - d_updatedBounds.purge(); -} - -void TheoryArithPrivate::zeroDifferenceDetected(ArithVar x){ - if(d_cmEnabled){ - Assert(d_congruenceManager.isWatchedVariable(x)); - Assert(d_partialModel.upperBoundIsZero(x)); - Assert(d_partialModel.lowerBoundIsZero(x)); - - ConstraintP lb = d_partialModel.getLowerBoundConstraint(x); - ConstraintP ub = d_partialModel.getUpperBoundConstraint(x); - - if(lb->isEquality()){ - d_congruenceManager.watchedVariableIsZero(lb); - }else if(ub->isEquality()){ - d_congruenceManager.watchedVariableIsZero(ub); - }else{ - d_congruenceManager.watchedVariableIsZero(lb, ub); - } - } -} - -bool TheoryArithPrivate::getSolveIntegerResource(){ - if(d_attemptSolveIntTurnedOff > 0){ - d_attemptSolveIntTurnedOff = d_attemptSolveIntTurnedOff - 1; - return false; - }else{ - return true; - } -} - -bool TheoryArithPrivate::getDioCuttingResource(){ - if(d_dioSolveResources > 0){ - d_dioSolveResources--; - if(d_dioSolveResources == 0){ - d_dioSolveResources = -options().arith.rrTurns; - } - return true; - }else{ - d_dioSolveResources++; - if(d_dioSolveResources >= 0){ - d_dioSolveResources = options().arith.dioSolverTurns; - } - return false; - } -} - -/* procedure AssertLower( x_i >= c_i ) */ -bool TheoryArithPrivate::AssertLower(ConstraintP constraint){ - Assert(constraint != NullConstraint); - Assert(constraint->isLowerBound()); - Assert(constraint->isTrue()); - Assert(!constraint->negationHasProof()); - - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Trace("arith") << "AssertLower(" << x_i << " " << c_i << ")"<< std::endl; - - Assert(!isInteger(x_i) || c_i.isIntegral()); - - //TODO Relax to less than? - if(d_partialModel.lessThanLowerBound(x_i, c_i)){ - return false; //sat - } - - int cmpToUB = d_partialModel.cmpToUpperBound(x_i, c_i); - if(cmpToUB > 0){ // c_i < \lowerbound(x_i) - ConstraintP ubc = d_partialModel.getUpperBoundConstraint(x_i); - ConstraintP negation = constraint->getNegation(); - negation->impliedByUnate(ubc, true); - - raiseConflict(constraint, InferenceId::ARITH_CONF_LOWER); - - ++(d_statistics.d_statAssertLowerConflicts); - return true; - }else if(cmpToUB == 0){ - if(isInteger(x_i)){ - d_constantIntegerVariables.push_back(x_i); - Trace("dio::push") << "dio::push " << x_i << endl; - } - ConstraintP ub = d_partialModel.getUpperBoundConstraint(x_i); - - if(d_cmEnabled){ - if(!d_congruenceManager.isWatchedVariable(x_i) || c_i.sgn() != 0){ - // if it is not a watched variable report it - // if it is is a watched variable and c_i == 0, - // let zeroDifferenceDetected(x_i) catch this - d_congruenceManager.equalsConstant(constraint, ub); - } - } - - const ValueCollection& vc = constraint->getValueCollection(); - if(vc.hasEquality()){ - Assert(vc.hasDisequality()); - ConstraintP eq = vc.getEquality(); - ConstraintP diseq = vc.getDisequality(); - // x <= b, x >= b |= x = b - // (x > b or x < b or x = b) - Trace("arith::eq") << "lb == ub, propagate eq" << eq << endl; - bool triConflict = diseq->isTrue(); - - if(!eq->isTrue()){ - eq->impliedByTrichotomy(constraint, ub, triConflict); - eq->tryToPropagate(); - } - if(triConflict){ - ++(d_statistics.d_statDisequalityConflicts); - raiseConflict(eq, InferenceId::ARITH_CONF_TRICHOTOMY); - return true; - } - } - }else{ - // l <= x <= u and l < u - Assert(cmpToUB < 0); - const ValueCollection& vc = constraint->getValueCollection(); - - if(vc.hasDisequality()){ - const ConstraintP diseq = vc.getDisequality(); - if(diseq->isTrue()){ - const ConstraintP ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); - ConstraintP negUb = ub->getNegation(); - - // l <= x, l != x |= l < x - // |= not (l >= x) - bool ubInConflict = ub->hasProof(); - bool learnNegUb = !(negUb->hasProof()); - if(learnNegUb){ - negUb->impliedByTrichotomy(constraint, diseq, ubInConflict); - negUb->tryToPropagate(); - } - if(ubInConflict){ - raiseConflict(ub, InferenceId::ARITH_CONF_TRICHOTOMY); - return true; - }else if(learnNegUb){ - d_learnedBounds.push_back(negUb); - } - } - } - } - - d_currentPropagationList.push_back(constraint); - d_currentPropagationList.push_back(d_partialModel.getLowerBoundConstraint(x_i)); - - d_partialModel.setLowerBoundConstraint(constraint); - - if(d_cmEnabled){ - if(d_congruenceManager.isWatchedVariable(x_i)){ - int sgn = c_i.sgn(); - if(sgn > 0){ - d_congruenceManager.watchedVariableCannotBeZero(constraint); - }else if(sgn == 0 && d_partialModel.upperBoundIsZero(x_i)){ - zeroDifferenceDetected(x_i); - } - } - } - - d_updatedBounds.softAdd(x_i); - - if(TraceIsOn("model")) { - Trace("model") << "before" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - if(!d_tableau.isBasic(x_i)){ - if(d_partialModel.getAssignment(x_i) < c_i){ - d_linEq.update(x_i, c_i); - } - }else{ - d_errorSet.signalVariable(x_i); - } - - if(TraceIsOn("model")) { - Trace("model") << "after" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - return false; //sat -} - -/* procedure AssertUpper( x_i <= c_i) */ -bool TheoryArithPrivate::AssertUpper(ConstraintP constraint){ - Assert(constraint != NullConstraint); - Assert(constraint->isUpperBound()); - Assert(constraint->isTrue()); - Assert(!constraint->negationHasProof()); - - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Trace("arith") << "AssertUpper(" << x_i << " " << c_i << ")"<< std::endl; - - - //Too strong because of rounding with integers - //Assert(!constraint->hasLiteral() || original == constraint->getLiteral()); - Assert(!isInteger(x_i) || c_i.isIntegral()); - - Trace("arith") << "AssertUpper(" << x_i << " " << c_i << ")"<< std::endl; - - if(d_partialModel.greaterThanUpperBound(x_i, c_i) ){ // \upperbound(x_i) <= c_i - return false; //sat - } - - // cmpToLb = \lowerbound(x_i).cmp(c_i) - int cmpToLB = d_partialModel.cmpToLowerBound(x_i, c_i); - if( cmpToLB < 0 ){ // \upperbound(x_i) < \lowerbound(x_i) - // l_i <= x_i and c_i < l_i |= c_i < x_i - // or ... |= not (x_i <= c_i) - ConstraintP lbc = d_partialModel.getLowerBoundConstraint(x_i); - ConstraintP negConstraint = constraint->getNegation(); - negConstraint->impliedByUnate(lbc, true); - raiseConflict(constraint, InferenceId::ARITH_CONF_UPPER); - ++(d_statistics.d_statAssertUpperConflicts); - return true; - }else if(cmpToLB == 0){ // \lowerBound(x_i) == \upperbound(x_i) - if(isInteger(x_i)){ - d_constantIntegerVariables.push_back(x_i); - Trace("dio::push") << "dio::push " << x_i << endl; - } - - const ValueCollection& vc = constraint->getValueCollection(); - ConstraintP lb = d_partialModel.getLowerBoundConstraint(x_i); - if(d_cmEnabled){ - if(!d_congruenceManager.isWatchedVariable(x_i) || c_i.sgn() != 0){ - // if it is not a watched variable report it - // if it is is a watched variable and c_i == 0, - // let zeroDifferenceDetected(x_i) catch this - d_congruenceManager.equalsConstant(lb, constraint); - } - } - - if(vc.hasDisequality()){ - Assert(vc.hasDisequality()); - ConstraintP eq = vc.getEquality(); - ConstraintP diseq = vc.getDisequality(); - // x <= b, x >= b |= x = b - // (x > b or x < b or x = b) - Trace("arith::eq") << "lb == ub, propagate eq" << eq << endl; - bool triConflict = diseq->isTrue(); - if(!eq->isTrue()){ - eq->impliedByTrichotomy(constraint, lb, triConflict); - eq->tryToPropagate(); - } - if(triConflict){ - ++(d_statistics.d_statDisequalityConflicts); - raiseConflict(eq, InferenceId::ARITH_CONF_TRICHOTOMY); - return true; - } - } - }else if(cmpToLB > 0){ - // l <= x <= u and l < u - Assert(cmpToLB > 0); - const ValueCollection& vc = constraint->getValueCollection(); - - if(vc.hasDisequality()){ - const ConstraintP diseq = vc.getDisequality(); - if(diseq->isTrue()){ - const ConstraintP lb = d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); - ConstraintP negLb = lb->getNegation(); - - // x <= u, u != x |= u < x - // |= not (u >= x) - bool lbInConflict = lb->hasProof(); - bool learnNegLb = !(negLb->hasProof()); - if(learnNegLb){ - negLb->impliedByTrichotomy(constraint, diseq, lbInConflict); - negLb->tryToPropagate(); - } - if(lbInConflict){ - raiseConflict(lb, InferenceId::ARITH_CONF_TRICHOTOMY); - return true; - }else if(learnNegLb){ - d_learnedBounds.push_back(negLb); - } - } - } - } - - d_currentPropagationList.push_back(constraint); - d_currentPropagationList.push_back(d_partialModel.getUpperBoundConstraint(x_i)); - //It is fine if this is NullConstraint - - d_partialModel.setUpperBoundConstraint(constraint); - - if(d_cmEnabled){ - if(d_congruenceManager.isWatchedVariable(x_i)){ - int sgn = c_i.sgn(); - if(sgn < 0){ - d_congruenceManager.watchedVariableCannotBeZero(constraint); - }else if(sgn == 0 && d_partialModel.lowerBoundIsZero(x_i)){ - zeroDifferenceDetected(x_i); - } - } - } - - d_updatedBounds.softAdd(x_i); - - if(TraceIsOn("model")) { - Trace("model") << "before" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - if(!d_tableau.isBasic(x_i)){ - if(d_partialModel.getAssignment(x_i) > c_i){ - d_linEq.update(x_i, c_i); - } - }else{ - d_errorSet.signalVariable(x_i); - } - - if(TraceIsOn("model")) { - Trace("model") << "after" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - return false; //sat -} - - -/* procedure AssertEquality( x_i == c_i ) */ -bool TheoryArithPrivate::AssertEquality(ConstraintP constraint){ - Assert(constraint != NullConstraint); - Assert(constraint->isEquality()); - Assert(constraint->isTrue()); - Assert(!constraint->negationHasProof()); - - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - - Trace("arith") << "AssertEquality(" << x_i << " " << c_i << ")"<< std::endl; - - //Should be fine in integers - Assert(!isInteger(x_i) || c_i.isIntegral()); - - int cmpToLB = d_partialModel.cmpToLowerBound(x_i, c_i); - int cmpToUB = d_partialModel.cmpToUpperBound(x_i, c_i); - - // u_i <= c_i <= l_i - // This can happen if both c_i <= x_i and x_i <= c_i are in the system. - if(cmpToUB >= 0 && cmpToLB <= 0){ - return false; //sat - } - - if(cmpToUB > 0 || cmpToLB < 0){ - ConstraintP cb = (cmpToUB > 0) ? d_partialModel.getUpperBoundConstraint(x_i) : - d_partialModel.getLowerBoundConstraint(x_i); - ConstraintP diseq = constraint->getNegation(); - Assert(!diseq->isTrue()); - diseq->impliedByUnate(cb, true); - raiseConflict(constraint, InferenceId::ARITH_CONF_EQ); - return true; - } - - Assert(cmpToUB <= 0); - Assert(cmpToLB >= 0); - Assert(cmpToUB < 0 || cmpToLB > 0); - - if(isInteger(x_i)){ - d_constantIntegerVariables.push_back(x_i); - Trace("dio::push") << "dio::push " << x_i << endl; - } - - // Don't bother to check whether x_i != c_i is in d_diseq - // The a and (not a) should never be on the fact queue - d_currentPropagationList.push_back(constraint); - d_currentPropagationList.push_back(d_partialModel.getLowerBoundConstraint(x_i)); - d_currentPropagationList.push_back(d_partialModel.getUpperBoundConstraint(x_i)); - - d_partialModel.setUpperBoundConstraint(constraint); - d_partialModel.setLowerBoundConstraint(constraint); - - if(d_cmEnabled){ - if(d_congruenceManager.isWatchedVariable(x_i)){ - int sgn = c_i.sgn(); - if(sgn == 0){ - zeroDifferenceDetected(x_i); - }else{ - d_congruenceManager.watchedVariableCannotBeZero(constraint); - d_congruenceManager.equalsConstant(constraint); - } - }else{ - d_congruenceManager.equalsConstant(constraint); - } - } - - d_updatedBounds.softAdd(x_i); - - if(TraceIsOn("model")) { - Trace("model") << "before" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - if(!d_tableau.isBasic(x_i)){ - if(!(d_partialModel.getAssignment(x_i) == c_i)){ - d_linEq.update(x_i, c_i); - } - }else{ - d_errorSet.signalVariable(x_i); - } - - if(TraceIsOn("model")) { - Trace("model") << "after" << endl; - d_partialModel.printModel(x_i); - d_tableau.debugPrintIsBasic(x_i); - } - - return false; -} - - -/* procedure AssertDisequality( x_i != c_i ) */ -bool TheoryArithPrivate::AssertDisequality(ConstraintP constraint){ - Assert(constraint != NullConstraint); - Assert(constraint->isDisequality()); - Assert(constraint->isTrue()); - Assert(!constraint->negationHasProof()); - - ArithVar x_i = constraint->getVariable(); - const DeltaRational& c_i = constraint->getValue(); - Trace("arith") << "AssertDisequality(" << x_i << " " << c_i << ")"<< std::endl; - - //Should be fine in integers - Assert(!isInteger(x_i) || c_i.isIntegral()); - - if(d_cmEnabled){ - if(d_congruenceManager.isWatchedVariable(x_i)){ - int sgn = c_i.sgn(); - if(sgn == 0){ - d_congruenceManager.watchedVariableCannotBeZero(constraint); - } - } - } - - const ValueCollection& vc = constraint->getValueCollection(); - if(vc.hasLowerBound() && vc.hasUpperBound()){ - const ConstraintP lb = vc.getLowerBound(); - const ConstraintP ub = vc.getUpperBound(); - if(lb->isTrue() && ub->isTrue()){ - ConstraintP eq = constraint->getNegation(); - eq->impliedByTrichotomy(lb, ub, true); - raiseConflict(constraint, InferenceId::ARITH_CONF_TRICHOTOMY); - //in conflict - ++(d_statistics.d_statDisequalityConflicts); - return true; - } - } - if(vc.hasLowerBound() ){ - const ConstraintP lb = vc.getLowerBound(); - if(lb->isTrue()){ - const ConstraintP ub = d_constraintDatabase.ensureConstraint(const_cast(vc), UpperBound); - Assert(!ub->isTrue()); - Trace("arith::eq") << "propagate UpperBound " << constraint << lb << ub << endl; - const ConstraintP negUb = ub->getNegation(); - if(!negUb->isTrue()){ - negUb->impliedByTrichotomy(constraint, lb, false); - negUb->tryToPropagate(); - d_learnedBounds.push_back(negUb); - } - } - } - if(vc.hasUpperBound()){ - const ConstraintP ub = vc.getUpperBound(); - if(ub->isTrue()){ - const ConstraintP lb = d_constraintDatabase.ensureConstraint(const_cast(vc), LowerBound); - Assert(!lb->isTrue()); - - Trace("arith::eq") << "propagate LowerBound " << constraint << lb << ub << endl; - const ConstraintP negLb = lb->getNegation(); - if(!negLb->isTrue()){ - negLb->impliedByTrichotomy(constraint, ub, false); - negLb->tryToPropagate(); - d_learnedBounds.push_back(negLb); - } - } - } - - bool split = constraint->isSplit(); - - if(!split && c_i == d_partialModel.getAssignment(x_i)){ - Trace("arith::eq") << "lemma now! " << constraint << endl; - outputTrustedLemma(constraint->split(), InferenceId::ARITH_SPLIT_DEQ); - return false; - }else if(d_partialModel.strictlyLessThanLowerBound(x_i, c_i)){ - Trace("arith::eq") << "can drop as less than lb" << constraint << endl; - }else if(d_partialModel.strictlyGreaterThanUpperBound(x_i, c_i)){ - Trace("arith::eq") << "can drop as less than ub" << constraint << endl; - }else if(!split){ - Trace("arith::eq") << "push back" << constraint << endl; - d_diseqQueue.push(constraint); - d_partialModel.invalidateDelta(); - }else{ - Trace("arith::eq") << "skipping already split " << constraint << endl; - } - return false; -} - -void TheoryArithPrivate::notifySharedTerm(TNode n) -{ - Trace("arith::notifySharedTerm") << "notifySharedTerm: " << n << endl; - if(n.isConst()){ - d_partialModel.invalidateDelta(); - } - if(!n.isConst() && !isSetup(n)){ - Polynomial poly = Polynomial::parsePolynomial(n); - Polynomial::iterator it = poly.begin(); - Polynomial::iterator it_end = poly.end(); - for (; it != it_end; ++ it) { - Monomial m = *it; - if (!m.isConstant() && !isSetup(m.getVarList().getNode())) { - setupVariableList(m.getVarList()); - } - } - } -} - -Node TheoryArithPrivate::getModelValue(TNode term) { - try{ - const DeltaRational drv = getDeltaValue(term); - const Rational& delta = d_partialModel.getDelta(); - const Rational qmodel = drv.substituteDelta( delta ); - return NodeManager::currentNM()->mkConstRealOrInt(term.getType(), qmodel); - } catch (DeltaRationalException& dr) { - return Node::null(); - } catch (ModelException& me) { - return Node::null(); - } -} - -Theory::PPAssertStatus TheoryArithPrivate::ppAssert( - TrustNode tin, TrustSubstitutionMap& outSubstitutions) -{ - TimerStat::CodeTimer codeTimer(d_statistics.d_simplifyTimer); - TNode in = tin.getNode(); - Trace("simplify") << "TheoryArithPrivate::solve(" << in << ")" << endl; - - - // Solve equalities - Rational minConstant = 0; - Node minMonomial; - Node minVar; - if (in.getKind() == kind::EQUAL && - Theory::theoryOf(in[0].getType()) == THEORY_ARITH) { - Comparison cmp = Comparison::parseNormalForm(in); - - Polynomial left = cmp.getLeft(); - - Monomial m = left.getHead(); - if (m.getVarList().singleton()){ - VarList vl = m.getVarList(); - Node var = vl.getNode(); - if (var.isVar()) - { - // if vl.isIntegral then m.getConstant().isOne() - if(!vl.isIntegral() || m.getConstant().isOne()){ - minVar = var; - } - } - } - - // Solve for variable - if (!minVar.isNull()) { - Polynomial right = cmp.getRight(); - Node elim = right.getNode(); - // ax + p = c -> (ax + p) -ax - c = -ax - // x = (p - ax - c) * -1/a - // Add the substitution if not recursive - Assert(elim == rewrite(elim)); - - if (right.size() > options().arith.ppAssertMaxSubSize) - { - Trace("simplify") - << "TheoryArithPrivate::solve(): did not substitute due to the " - "right hand side containing too many terms: " - << minVar << ":" << elim << endl; - Trace("simplify") << right.size() << endl; - } - else if (d_containing.isLegalElimination(minVar, elim)) - { - // cannot eliminate integers here unless we know the resulting - // substitution is integral - Trace("simplify") << "TheoryArithPrivate::solve(): substitution " - << minVar << " |-> " << elim << endl; - - outSubstitutions.addSubstitutionSolved(minVar, elim, tin); - return Theory::PP_ASSERT_STATUS_SOLVED; - } - else - { - Trace("simplify") << "TheoryArithPrivate::solve(): can't substitute " - << minVar << ":" << minVar.getType() << " |-> " - << elim << ":" << elim.getType() << endl; - } - } - } - - // If a relation, remember the bound - switch(in.getKind()) { - case kind::LEQ: - case kind::LT: - case kind::GEQ: - case kind::GT: - if (in[0].isVar()) { - d_learner.addBound(in); - } - break; - default: - // Do nothing - break; - } - - return Theory::PP_ASSERT_STATUS_UNSOLVED; -} - -void TheoryArithPrivate::ppStaticLearn(TNode n, NodeBuilder& learned) -{ - TimerStat::CodeTimer codeTimer(d_statistics.d_staticLearningTimer); - - d_learner.staticLearning(n, learned); -} - -ArithVar TheoryArithPrivate::findShortestBasicRow(ArithVar variable){ - ArithVar bestBasic = ARITHVAR_SENTINEL; - uint64_t bestRowLength = std::numeric_limits::max(); - - Tableau::ColIterator basicIter = d_tableau.colIterator(variable); - for(; !basicIter.atEnd(); ++basicIter){ - const Tableau::Entry& entry = *basicIter; - Assert(entry.getColVar() == variable); - RowIndex ridx = entry.getRowIndex(); - ArithVar basic = d_tableau.rowIndexToBasic(ridx); - uint32_t rowLength = d_tableau.getRowLength(ridx); - if((rowLength < bestRowLength) || - (rowLength == bestRowLength && basic < bestBasic)){ - bestBasic = basic; - bestRowLength = rowLength; - } - } - Assert(bestBasic == ARITHVAR_SENTINEL - || bestRowLength < std::numeric_limits::max()); - return bestBasic; -} - -void TheoryArithPrivate::setupVariable(const Variable& x){ - Node n = x.getNode(); - - Assert(!isSetup(n)); - - ++(d_statistics.d_statUserVariables); - requestArithVar(n, false, false); - //ArithVar varN = requestArithVar(n,false); - //setupInitialValue(varN); - - markSetup(n); -} - -void TheoryArithPrivate::setupVariableList(const VarList& vl){ - Assert(!vl.empty()); - - TNode vlNode = vl.getNode(); - Assert(!isSetup(vlNode)); - Assert(!d_partialModel.hasArithVar(vlNode)); - - for(VarList::iterator i = vl.begin(), end = vl.end(); i != end; ++i){ - Variable var = *i; - - if(!isSetup(var.getNode())){ - setupVariable(var); - } - } - - if(!vl.singleton()){ - // vl is the product of at least 2 variables - // vl : (* v1 v2 ...) - if (logicInfo().isLinear()) - { - throw LogicException("A non-linear fact was asserted to arithmetic in a linear logic."); - } - d_foundNl = true; - - ++(d_statistics.d_statUserVariables); - requestArithVar(vlNode, false, false); - //ArithVar av = requestArithVar(vlNode, false); - //setupInitialValue(av); - - markSetup(vlNode); - } - else if (vlNode.getKind() == kind::EXPONENTIAL - || vlNode.getKind() == kind::SINE || vlNode.getKind() == kind::COSINE - || vlNode.getKind() == kind::TANGENT) - { - d_foundNl = true; - } - - /* Note: - * Only call markSetup if the VarList is not a singleton. - * See the comment in setupPolynomail for more. - */ -} - -void TheoryArithPrivate::cautiousSetupPolynomial(const Polynomial& p){ - if(p.containsConstant()){ - if(!p.isConstant()){ - Polynomial noConstant = p.getTail(); - if(!isSetup(noConstant.getNode())){ - setupPolynomial(noConstant); - } - } - }else if(!isSetup(p.getNode())){ - setupPolynomial(p); - } -} - - -void TheoryArithPrivate::setupPolynomial(const Polynomial& poly) { - Assert(!poly.containsConstant()); - TNode polyNode = poly.getNode(); - Assert(!isSetup(polyNode)); - Assert(!d_partialModel.hasArithVar(polyNode)); - - for(Polynomial::iterator i = poly.begin(), end = poly.end(); i != end; ++i){ - Monomial mono = *i; - const VarList& vl = mono.getVarList(); - if(!isSetup(vl.getNode())){ - setupVariableList(vl); - } - } - - if (polyNode.getKind() == ADD) - { - d_tableauSizeHasBeenModified = true; - - vector variables; - vector coefficients; - asVectors(poly, coefficients, variables); - - ArithVar varSlack = requestArithVar(polyNode, true, false); - d_tableau.addRow(varSlack, coefficients, variables); - setupBasicValue(varSlack); - d_linEq.trackRowIndex(d_tableau.basicToRowIndex(varSlack)); - - //Add differences to the difference manager - Polynomial::iterator i = poly.begin(), end = poly.end(); - if(i != end){ - Monomial first = *i; - ++i; - if(i != end){ - Monomial second = *i; - ++i; - if(i == end){ - if(first.getConstant().isOne() && second.getConstant().getValue() == -1){ - VarList vl0 = first.getVarList(); - VarList vl1 = second.getVarList(); - if(vl0.singleton() && vl1.singleton()){ - d_congruenceManager.addWatchedPair(varSlack, vl0.getNode(), vl1.getNode()); - } - } - } - } - } - - ++(d_statistics.d_statAuxiliaryVariables); - markSetup(polyNode); - } - - /* Note: - * It is worth documenting that polyNode should only be marked as - * being setup by this function if it has kind ADD. - * Other kinds will be marked as being setup by lower levels of setup - * specifically setupVariableList. - */ -} - -void TheoryArithPrivate::setupAtom(TNode atom) { - Assert(isRelationOperator(atom.getKind())) << atom; - Assert(Comparison::isNormalAtom(atom)); - Assert(!isSetup(atom)); - Assert(!d_constraintDatabase.hasLiteral(atom)); - - Comparison cmp = Comparison::parseNormalForm(atom); - Polynomial nvp = cmp.normalizedVariablePart(); - Assert(!nvp.isZero()); - - if(!isSetup(nvp.getNode())){ - setupPolynomial(nvp); - } - - d_constraintDatabase.addLiteral(atom); - - markSetup(atom); -} - -void TheoryArithPrivate::preRegisterTerm(TNode n) { - Trace("arith::preregister") <<"begin arith::preRegisterTerm("<< n <<")"<< endl; - - d_preregisteredNodes.insert(n); - - try { - if(isRelationOperator(n.getKind())){ - if(!isSetup(n)){ - setupAtom(n); - } - ConstraintP c = d_constraintDatabase.lookup(n); - Assert(c != NullConstraint); - - Trace("arith::preregister") << "setup constraint" << c << endl; - Assert(!c->canBePropagated()); - c->setPreregistered(); - } - } catch(LogicException& le) { - std::stringstream ss; - ss << le.getMessage() << endl << "The fact in question: " << n << endl; - throw LogicException(ss.str()); - } - - Trace("arith::preregister") << "end arith::preRegisterTerm("<< n <<")" << endl; -} - -void TheoryArithPrivate::releaseArithVar(ArithVar v){ - //Assert(d_partialModel.hasNode(v)); - - d_constraintDatabase.removeVariable(v); - d_partialModel.releaseArithVar(v); -} - -ArithVar TheoryArithPrivate::requestArithVar(TNode x, bool aux, bool internal){ - //TODO : The VarList trick is good enough? - Kind xk = x.getKind(); - Assert(isLeaf(x) || VarList::isMember(x) || xk == ADD || internal); - if (logicInfo().isLinear() - && (Variable::isDivMember(x) || xk == IAND || isTranscendentalKind(xk))) - { - stringstream ss; - ss << "A non-linear fact was asserted to " - "arithmetic in a linear logic: " - << x << std::endl; - throw LogicException(ss.str()); - } - Assert(!d_partialModel.hasArithVar(x)); - Assert(x.getType().isRealOrInt()); // real or integer - - ArithVar max = d_partialModel.getNumberOfVariables(); - ArithVar varX = d_partialModel.allocate(x, aux); - - bool reclaim = max >= d_partialModel.getNumberOfVariables();; - - if(!reclaim){ - d_dualSimplex.increaseMax(); - - d_tableau.increaseSize(); - d_tableauSizeHasBeenModified = true; - } - d_constraintDatabase.addVariable(varX); - - Trace("arith::arithvar") << "@" << context()->getLevel() << " " << x - << " |-> " << varX << "(relaiming " << reclaim << ")" - << endl; - - Assert(!d_partialModel.hasUpperBound(varX)); - Assert(!d_partialModel.hasLowerBound(varX)); - - return varX; -} - -void TheoryArithPrivate::asVectors(const Polynomial& p, std::vector& coeffs, std::vector& variables) { - for(Polynomial::iterator i = p.begin(), end = p.end(); i != end; ++i){ - const Monomial& mono = *i; - const Constant& constant = mono.getConstant(); - const VarList& variable = mono.getVarList(); - - Node n = variable.getNode(); - - Trace("arith::asVectors") << "should be var: " << n << endl; - - // TODO: This VarList::isMember(n) can be stronger - Assert(isLeaf(n) || VarList::isMember(n)); - Assert(theoryOf(n) != THEORY_ARITH || d_partialModel.hasArithVar(n)); - - Assert(d_partialModel.hasArithVar(n)); - ArithVar av = d_partialModel.asArithVar(n); - - coeffs.push_back(constant.getValue()); - variables.push_back(av); - } -} - -/* Requirements: - * For basic variables the row must have been added to the tableau. - */ -void TheoryArithPrivate::setupBasicValue(ArithVar x){ - Assert(d_tableau.isBasic(x)); - //If the variable is basic, assertions may have already happened and updates - //may have occured before setting this variable up. - - //This can go away if the tableau creation is done at preregister - //time instead of register - DeltaRational safeAssignment = d_linEq.computeRowValue(x, true); - DeltaRational assignment = d_linEq.computeRowValue(x, false); - d_partialModel.setAssignment(x,safeAssignment,assignment); - - Trace("arith") << "setupVariable("< 1); - Assert(!gcd.divides(c.asConstant().getNumerator())); - Comparison leq = Comparison::mkComparison(LEQ, p, c); - Comparison geq = Comparison::mkComparison(GEQ, p, c); - Node lemma = NodeManager::currentNM()->mkNode(OR, leq.getNode(), geq.getNode()); - Node rewrittenLemma = rewrite(lemma); - Trace("arith::dio::ex") << "dioCutting found the plane: " << plane.getNode() << endl; - Trace("arith::dio::ex") << "resulting in the cut: " << lemma << endl; - Trace("arith::dio::ex") << "rewritten " << rewrittenLemma << endl; - Trace("arith::dio") << "dioCutting found the plane: " << plane.getNode() << endl; - Trace("arith::dio") << "resulting in the cut: " << lemma << endl; - Trace("arith::dio") << "rewritten " << rewrittenLemma << endl; - if (proofsEnabled()) - { - NodeManager* nm = NodeManager::currentNM(); - Node gt = nm->mkNode(kind::GT, p.getNode(), c.getNode()); - Node lt = nm->mkNode(kind::LT, p.getNode(), c.getNode()); - TypeNode type = gt[0].getType(); - - Pf pfNotLeq = d_pnm->mkAssume(leq.getNode().negate()); - Pf pfGt = - d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pfNotLeq}, {gt}); - Pf pfNotGeq = d_pnm->mkAssume(geq.getNode().negate()); - Pf pfLt = - d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, {pfNotGeq}, {lt}); - Pf pfSum = d_pnm->mkNode( - PfRule::MACRO_ARITH_SCALE_SUM_UB, - {pfGt, pfLt}, - {nm->mkConstRealOrInt(type, -1), nm->mkConstRealOrInt(type, 1)}); - Pf pfBot = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pfSum}, {nm->mkConst(false)}); - std::vector assumptions = {leq.getNode().negate(), - geq.getNode().negate()}; - Pf pfNotAndNot = d_pnm->mkScope(pfBot, assumptions); - Pf pfOr = d_pnm->mkNode(PfRule::NOT_AND, {pfNotAndNot}, {}); - Pf pfRewritten = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pfOr}, {rewrittenLemma}); - return d_pfGen->mkTrustNode(rewrittenLemma, pfRewritten); - } - else - { - return TrustNode::mkTrustLemma(rewrittenLemma, nullptr); - } - } -} - -Node TheoryArithPrivate::callDioSolver(){ - while(!d_constantIntegerVariables.empty()){ - ArithVar v = d_constantIntegerVariables.front(); - d_constantIntegerVariables.pop(); - - Trace("arith::dio") << "callDioSolver " << v << endl; - - Assert(isInteger(v)); - Assert(d_partialModel.boundsAreEqual(v)); - - ConstraintP lb = d_partialModel.getLowerBoundConstraint(v); - ConstraintP ub = d_partialModel.getUpperBoundConstraint(v); - - Node orig = Node::null(); - if(lb->isEquality()){ - orig = Constraint::externalExplainByAssertions({lb}); - }else if(ub->isEquality()){ - orig = Constraint::externalExplainByAssertions({ub}); - }else { - orig = Constraint::externalExplainByAssertions(ub, lb); - } - - Assert(d_partialModel.assignmentIsConsistent(v)); - - Comparison eq = mkIntegerEqualityFromAssignment(v); - - if(eq.isBoolean()){ - //This can only be a conflict - Assert(!eq.getNode().getConst()); - - //This should be handled by the normal form earlier in the case of equality - Assert(orig.getKind() != EQUAL); - return orig; - }else{ - Trace("dio::push") << "dio::push " << v << " " << eq.getNode() << " with reason " << orig << endl; - d_diosolver.pushInputConstraint(eq, orig); - } - } - - return d_diosolver.processEquationsForConflict(); -} - -ConstraintP TheoryArithPrivate::constraintFromFactQueue(TNode assertion) -{ - Kind simpleKind = Comparison::comparisonKind(assertion); - ConstraintP constraint = d_constraintDatabase.lookup(assertion); - if(constraint == NullConstraint){ - Assert(simpleKind == EQUAL || simpleKind == DISTINCT); - bool isDistinct = simpleKind == DISTINCT; - Node eq = (simpleKind == DISTINCT) ? assertion[0] : assertion; - Assert(!isSetup(eq)); - Node reEq = rewrite(eq); - Trace("arith::distinct::const") << "Assertion: " << assertion << std::endl; - Trace("arith::distinct::const") << "Eq : " << eq << std::endl; - Trace("arith::distinct::const") << "reEq : " << reEq << std::endl; - if(reEq.getKind() == CONST_BOOLEAN){ - if(reEq.getConst() == isDistinct){ - // if is (not true), or false - Assert((reEq.getConst() && isDistinct) - || (!reEq.getConst() && !isDistinct)); - if (proofsEnabled()) - { - Pf assume = d_pnm->mkAssume(assertion); - std::vector assumptions = {assertion}; - Pf pf = d_pnm->mkScope(d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, - {d_pnm->mkAssume(assertion)}, - {}), - assumptions); - raiseBlackBoxConflict(assertion, pf); - } - else - { - raiseBlackBoxConflict(assertion); - } - } - return NullConstraint; - } - Assert(reEq.getKind() != CONST_BOOLEAN); - if(!isSetup(reEq)){ - setupAtom(reEq); - } - Node reAssertion = isDistinct ? reEq.notNode() : reEq; - constraint = d_constraintDatabase.lookup(reAssertion); - - if(assertion != reAssertion){ - Trace("arith::nf") << "getting non-nf assertion " << assertion << " |-> " << reAssertion << endl; - Assert(constraint != NullConstraint); - d_assertionsThatDoNotMatchTheirLiterals.insert(assertion, constraint); - } - } - - Assert(constraint != NullConstraint); - - if(constraint->assertedToTheTheory()){ - //Do nothing - return NullConstraint; - } - Assert(!constraint->assertedToTheTheory()); - bool inConflict = constraint->negationHasProof(); - constraint->setAssertedToTheTheory(assertion, inConflict); - - if(!constraint->hasProof()){ - Trace("arith::constraint") << "marking as constraint as self explaining " << endl; - constraint->setAssumption(inConflict); - } else { - Trace("arith::constraint") - << "already has proof: " - << Constraint::externalExplainByAssertions({constraint}); - } - - if(TraceIsOn("arith::negatedassumption") && inConflict){ - ConstraintP negation = constraint->getNegation(); - if(TraceIsOn("arith::negatedassumption") && negation->isAssumption()){ - debugPrintFacts(); - } - Trace("arith::eq") << "negation has proof" << endl; - Trace("arith::eq") << constraint << endl; - Trace("arith::eq") << negation << endl; - } - - if(inConflict){ - ConstraintP negation = constraint->getNegation(); - if(TraceIsOn("arith::negatedassumption") && negation->isAssumption()){ - debugPrintFacts(); - } - Trace("arith::eq") << "negation has proof" << endl; - Trace("arith::eq") << constraint << endl; - Trace("arith::eq") << negation << endl; - raiseConflict(negation, InferenceId::ARITH_CONF_FACT_QUEUE); - return NullConstraint; - }else{ - return constraint; - } -} - -bool TheoryArithPrivate::assertionCases(ConstraintP constraint){ - Assert(constraint->hasProof()); - Assert(!constraint->negationHasProof()); - - ArithVar x_i = constraint->getVariable(); - - switch(constraint->getType()){ - case UpperBound: - if(isInteger(x_i) && constraint->isStrictUpperBound()){ - ConstraintP floorConstraint = constraint->getFloor(); - if(!floorConstraint->isTrue()){ - bool inConflict = floorConstraint->negationHasProof(); - if (TraceIsOn("arith::intbound")) { - Trace("arith::intbound") << "literal, before: " << constraint->getLiteral() << std::endl; - Trace("arith::intbound") << "constraint, after: " << floorConstraint << std::endl; - } - floorConstraint->impliedByIntTighten(constraint, inConflict); - floorConstraint->tryToPropagate(); - if(inConflict){ - raiseConflict(floorConstraint, InferenceId::ARITH_TIGHTEN_FLOOR); - return true; - } - } - return AssertUpper(floorConstraint); - }else{ - return AssertUpper(constraint); - } - case LowerBound: - if(isInteger(x_i) && constraint->isStrictLowerBound()){ - ConstraintP ceilingConstraint = constraint->getCeiling(); - if(!ceilingConstraint->isTrue()){ - bool inConflict = ceilingConstraint->negationHasProof(); - if (TraceIsOn("arith::intbound")) { - Trace("arith::intbound") << "literal, before: " << constraint->getLiteral() << std::endl; - Trace("arith::intbound") << "constraint, after: " << ceilingConstraint << std::endl; - } - ceilingConstraint->impliedByIntTighten(constraint, inConflict); - ceilingConstraint->tryToPropagate(); - if(inConflict){ - raiseConflict(ceilingConstraint, InferenceId::ARITH_TIGHTEN_CEIL); - return true; - } - } - return AssertLower(ceilingConstraint); - }else{ - return AssertLower(constraint); - } - case Equality: - return AssertEquality(constraint); - case Disequality: - return AssertDisequality(constraint); - default: - Unreachable(); - return false; - } -} -/** - * Looks for through the variables starting at d_nextIntegerCheckVar - * for the first integer variable that is between its upper and lower bounds - * that has a non-integer assignment. - * - * If assumeBounds is true, skip the check that the variable is in bounds. - * - * If there is no such variable, returns ARITHVAR_SENTINEL; - */ -ArithVar TheoryArithPrivate::nextIntegerViolation(bool assumeBounds) const -{ - ArithVar numVars = d_partialModel.getNumberOfVariables(); - ArithVar v = d_nextIntegerCheckVar; - if (numVars > 0) - { - const ArithVar rrEnd = d_nextIntegerCheckVar; - do - { - if (isIntegerInput(v)) - { - if (!d_partialModel.integralAssignment(v)) - { - if (assumeBounds || d_partialModel.assignmentIsConsistent(v)) - { - return v; - } - } - } - v = (1 + v == numVars) ? 0 : (1 + v); - } while (v != rrEnd); - } - return ARITHVAR_SENTINEL; -} - -/** - * Checks the set of integer variables I to see if each variable - * in I has an integer assignment. - */ -bool TheoryArithPrivate::hasIntegerModel() -{ - ArithVar next = nextIntegerViolation(true); - if (next != ARITHVAR_SENTINEL) - { - d_nextIntegerCheckVar = next; - if (TraceIsOn("arith::hasIntegerModel")) - { - Trace("arith::hasIntegerModel") << "has int model? " << next << endl; - d_partialModel.printModel(next, Trace("arith::hasIntegerModel")); - } - return false; - } - else - { - return true; - } -} - -Node flattenAndSort(Node n){ - Kind k = n.getKind(); - switch(k){ - case kind::OR: - case kind::AND: - case kind::ADD: - case kind::MULT: - break; - default: - return n; - } - - std::vector out; - std::vector process; - process.push_back(n); - while(!process.empty()){ - Node b = process.back(); - process.pop_back(); - if(b.getKind() == k){ - for(Node::iterator i=b.begin(), end=b.end(); i!=end; ++i){ - process.push_back(*i); - } - } else { - out.push_back(b); - } - } - Assert(out.size() >= 2); - std::sort(out.begin(), out.end()); - return NodeManager::currentNM()->mkNode(k, out); -} - - - -/** Outputs conflicts to the output channel. */ -void TheoryArithPrivate::outputConflicts(){ - Trace("arith::conflict") << "outputting conflicts" << std::endl; - Assert(anyConflict()); - - if(!conflictQueueEmpty()){ - Assert(!d_conflicts.empty()); - for(size_t i = 0, i_end = d_conflicts.size(); i < i_end; ++i){ - const std::pair& conf = d_conflicts[i]; - const ConstraintCP& confConstraint = conf.first; - bool hasProof = confConstraint->hasProof(); - Assert(confConstraint->inConflict()); - const ConstraintRule& pf = confConstraint->getConstraintRule(); - if (TraceIsOn("arith::conflict")) - { - pf.print(std::cout, options().smt.produceProofs); - std::cout << std::endl; - } - if (TraceIsOn("arith::pf::tree")) - { - Trace("arith::pf::tree") << "\n\nTree:\n"; - confConstraint->printProofTree(Trace("arith::pf::tree")); - confConstraint->getNegation()->printProofTree(Trace("arith::pf::tree")); - } - - TrustNode trustedConflict = confConstraint->externalExplainConflict(); - Node conflict = trustedConflict.getNode(); - - Trace("arith::conflict") - << "d_conflicts[" << i << "] " << conflict - << " has proof: " << hasProof << ", id = " << conf.second << endl; - if(TraceIsOn("arith::normalize::external")){ - conflict = flattenAndSort(conflict); - Trace("arith::conflict") << "(normalized to) " << conflict << endl; - } - - if (isProofEnabled()) - { - outputTrustedConflict(trustedConflict, conf.second); - } - else - { - outputConflict(conflict, conf.second); - } - } - } - if(!d_blackBoxConflict.get().isNull()){ - Node bb = d_blackBoxConflict.get(); - Trace("arith::conflict") << "black box conflict" << bb - << endl; - if(TraceIsOn("arith::normalize::external")){ - bb = flattenAndSort(bb); - Trace("arith::conflict") << "(normalized to) " << bb << endl; - } - if (isProofEnabled() && d_blackBoxConflictPf.get()) - { - auto confPf = d_blackBoxConflictPf.get(); - outputTrustedConflict(d_pfGen->mkTrustNode(bb, confPf, true), InferenceId::ARITH_BLACK_BOX); - } - else - { - outputConflict(bb, InferenceId::ARITH_BLACK_BOX); - } - } -} - -bool TheoryArithPrivate::outputTrustedLemma(TrustNode lemma, InferenceId id) -{ - Trace("arith::channel") << "Arith trusted lemma: " << lemma << std::endl; - return d_containing.d_im.trustedLemma(lemma, id); -} - -bool TheoryArithPrivate::outputLemma(TNode lem, InferenceId id) { - Trace("arith::channel") << "Arith lemma: " << lem << std::endl; - return d_containing.d_im.lemma(lem, id); -} - -void TheoryArithPrivate::outputTrustedConflict(TrustNode conf, InferenceId id) -{ - Trace("arith::channel") << "Arith trusted conflict: " << conf << std::endl; - d_containing.d_im.trustedConflict(conf, id); -} - -void TheoryArithPrivate::outputConflict(TNode lit, InferenceId id) { - Trace("arith::channel") << "Arith conflict: " << lit << std::endl; - d_containing.d_im.conflict(lit, id); -} - -void TheoryArithPrivate::outputPropagate(TNode lit) { - Trace("arith::channel") << "Arith propagation: " << lit << std::endl; - // call the propagate lit method of the - d_containing.d_im.propagateLit(lit); -} - -void TheoryArithPrivate::outputRestart() { - Trace("arith::channel") << "Arith restart!" << std::endl; - (d_containing.d_out)->demandRestart(); -} - -bool TheoryArithPrivate::attemptSolveInteger(Theory::Effort effortLevel, bool emmmittedLemmaOrSplit){ - int level = context()->getLevel(); - Trace("approx") - << "attemptSolveInteger " << d_qflraStatus - << " " << emmmittedLemmaOrSplit - << " " << effortLevel - << " " << d_lastContextIntegerAttempted - << " " << level - << endl; - - if(d_qflraStatus == Result::UNSAT){ return false; } - if(emmmittedLemmaOrSplit){ return false; } - if (!options().arith.useApprox) - { - return false; - } - if(!ApproximateSimplex::enabled()){ return false; } - - if(Theory::fullEffort(effortLevel)){ - if(hasIntegerModel()){ - return false; - }else{ - return getSolveIntegerResource(); - } - } - - if(d_lastContextIntegerAttempted <= 0){ - if(hasIntegerModel()){ - d_lastContextIntegerAttempted = context()->getLevel(); - return false; - }else{ - return getSolveIntegerResource(); - } - } - - if (!options().arith.trySolveIntStandardEffort) - { - return false; - } - - if (d_lastContextIntegerAttempted <= (level >> 2)) - { - double d = (double)(d_solveIntMaybeHelp + 1) - / (d_solveIntAttempts + 1 + level * level); - if (Random::getRandom().pickWithProb(d)) - { - return getSolveIntegerResource(); - } - } - return false; -} - -bool TheoryArithPrivate::replayLog(ApproximateSimplex* approx){ - TimerStat::CodeTimer codeTimer(d_statistics.d_replayLogTimer); - - ++d_statistics.d_mipProofsAttempted; - - Assert(d_replayVariables.empty()); - Assert(d_replayConstraints.empty()); - - size_t enteringPropN = d_currentPropagationList.size(); - Assert(conflictQueueEmpty()); - TreeLog& tl = getTreeLog(); - //tl.applySelected(); /* set row ids */ - - d_replayedLemmas = false; - - /* use the try block for the purpose of pushing the sat context */ - context::Context::ScopedPush speculativePush(context()); - d_cmEnabled = false; - std::vector res = - replayLogRec(approx, tl.getRootId(), NullConstraint, 1); - - if(res.empty()){ - ++d_statistics.d_replayAttemptFailed; - }else{ - unsigned successes = 0; - for(size_t i =0, N = res.size(); i < N; ++i){ - ConstraintCPVec& vec = res[i]; - Assert(vec.size() >= 2); - for(size_t j=0, M = vec.size(); j < M; ++j){ - ConstraintCP at_j = vec[j]; - Assert(at_j->isTrue()); - if(!at_j->negationHasProof()){ - successes++; - vec[j] = vec.back(); - vec.pop_back(); - ConstraintP neg_at_j = at_j->getNegation(); - - Trace("approx::replayLog") << "Setting the proof for the replayLog conflict on:" << endl - << " (" << neg_at_j->isTrue() <<") " << neg_at_j << endl - << " (" << at_j->isTrue() <<") " << at_j << endl; - neg_at_j->impliedByIntHole(vec, true); - raiseConflict(at_j, InferenceId::ARITH_CONF_REPLAY_LOG); - break; - } - } - } - if(successes > 0){ - ++d_statistics.d_mipProofsSuccessful; - } - } - - if(d_currentPropagationList.size() > enteringPropN){ - d_currentPropagationList.resize(enteringPropN); - } - - /* It is not clear what the d_qflraStatus is at this point */ - d_qflraStatus = Result::UNKNOWN; - - Assert(d_replayVariables.empty()); - Assert(d_replayConstraints.empty()); - - return !conflictQueueEmpty(); -} - -std::pair TheoryArithPrivate::replayGetConstraint(const DenseMap& lhs, Kind k, const Rational& rhs, bool branch) -{ - ArithVar added = ARITHVAR_SENTINEL; - Node sum = toSumNode(d_partialModel, lhs); - if(sum.isNull()){ return make_pair(NullConstraint, added); } - - Trace("approx::constraint") << "replayGetConstraint " << sum - << " " << k - << " " << rhs - << endl; - - Assert(k == kind::LEQ || k == kind::GEQ); - - NodeManager* nm = NodeManager::currentNM(); - Node comparison = - nm->mkNode(k, sum, nm->mkConstRealOrInt(sum.getType(), rhs)); - Node rewritten = rewrite(comparison); - if(!(Comparison::isNormalAtom(rewritten))){ - return make_pair(NullConstraint, added); - } - - Comparison cmp = Comparison::parseNormalForm(rewritten); - if(cmp.isBoolean()){ return make_pair(NullConstraint, added); } - - Polynomial nvp = cmp.normalizedVariablePart(); - if(nvp.isZero()){ return make_pair(NullConstraint, added); } - - Node norm = nvp.getNode(); - - ConstraintType t = Constraint::constraintTypeOfComparison(cmp); - DeltaRational dr = cmp.normalizedDeltaRational(); - - Trace("approx::constraint") << "rewriting " << rewritten << endl - << " |-> " << norm << " " << t << " " << dr << endl; - - Assert(!branch || d_partialModel.hasArithVar(norm)); - ArithVar v = ARITHVAR_SENTINEL; - if(d_partialModel.hasArithVar(norm)){ - - v = d_partialModel.asArithVar(norm); - Trace("approx::constraint") - << "replayGetConstraint found " << norm << " |-> " << v << " @ " - << context()->getLevel() << endl; - Assert(!branch || d_partialModel.isIntegerInput(v)); - }else{ - v = requestArithVar(norm, true, true); - d_replayVariables.push_back(v); - - added = v; - - Trace("approx::constraint") - << "replayGetConstraint adding " << norm << " |-> " << v << " @ " - << context()->getLevel() << endl; - - Polynomial poly = Polynomial::parsePolynomial(norm); - vector variables; - vector coefficients; - asVectors(poly, coefficients, variables); - d_tableau.addRow(v, coefficients, variables); - setupBasicValue(v); - d_linEq.trackRowIndex(d_tableau.basicToRowIndex(v)); - } - Assert(d_partialModel.hasArithVar(norm)); - Assert(d_partialModel.asArithVar(norm) == v); - Assert(d_constraintDatabase.variableDatabaseIsSetup(v)); - - ConstraintP imp = d_constraintDatabase.getBestImpliedBound(v, t, dr); - if(imp != NullConstraint){ - if(imp->getValue() == dr){ - Assert(added == ARITHVAR_SENTINEL); - return make_pair(imp, added); - } - } - - ConstraintP newc = d_constraintDatabase.getConstraint(v, t, dr); - d_replayConstraints.push_back(newc); - return make_pair(newc, added); -} - -std::pair TheoryArithPrivate::replayGetConstraint( - ApproximateSimplex* approx, const NodeLog& nl) -{ - Assert(nl.isBranch()); - Assert(d_lhsTmp.empty()); - - ArithVar v = approx->getBranchVar(nl); - if(v != ARITHVAR_SENTINEL && d_partialModel.isIntegerInput(v)){ - if(d_partialModel.hasNode(v)){ - d_lhsTmp.set(v, Rational(1)); - double dval = nl.branchValue(); - std::optional maybe_value = - ApproximateSimplex::estimateWithCFE(dval); - if (!maybe_value) - { - return make_pair(NullConstraint, ARITHVAR_SENTINEL); - } - Rational fl(maybe_value.value().floor()); - pair p; - p = replayGetConstraint(d_lhsTmp, kind::LEQ, fl, true); - d_lhsTmp.purge(); - return p; - } - } - return make_pair(NullConstraint, ARITHVAR_SENTINEL); -} - -std::pair TheoryArithPrivate::replayGetConstraint(const CutInfo& ci) { - Assert(ci.reconstructed()); - const DenseMap& lhs = ci.getReconstruction().lhs; - const Rational& rhs = ci.getReconstruction().rhs; - Kind k = ci.getKind(); - - return replayGetConstraint(lhs, k, rhs, ci.getKlass() == BranchCutKlass); -} - -Node toSumNode(const ArithVariables& vars, const DenseMap& sum){ - Trace("arith::toSumNode") << "toSumNode() begin" << endl; - NodeManager* nm = NodeManager::currentNM(); - DenseMap::const_iterator iter, end; - iter = sum.begin(), end = sum.end(); - std::vector children; - for(; iter != end; ++iter){ - ArithVar x = *iter; - if(!vars.hasNode(x)){ return Node::null(); } - Node xNode = vars.asNode(x); - const Rational& q = sum[x]; - Node mult = nm->mkNode(kind::MULT, mkRationalNode(q), xNode); - Trace("arith::toSumNode") << "toSumNode() " << x << " " << mult << endl; - children.push_back(mult); - } - Trace("arith::toSumNode") << "toSumNode() end" << endl; - if (children.empty()) - { - // NOTE: real type assumed here - return nm->mkConstReal(Rational(0)); - } - else if (children.size() == 1) - { - return children[0]; - } - return nm->mkNode(kind::ADD, children); -} - -ConstraintCP TheoryArithPrivate::vectorToIntHoleConflict(const ConstraintCPVec& conflict){ - Assert(conflict.size() >= 2); - ConstraintCPVec exp(conflict.begin(), conflict.end()-1); - ConstraintCP back = conflict.back(); - Assert(back->hasProof()); - ConstraintP negBack = back->getNegation(); - // This can select negBack multiple times so we need to test if negBack has a proof. - if(negBack->hasProof()){ - // back is in conflict already - } else { - negBack->impliedByIntHole(exp, true); - } - - return back; -} - -void TheoryArithPrivate::intHoleConflictToVector(ConstraintCP conflicting, ConstraintCPVec& conflict){ - ConstraintCP negConflicting = conflicting->getNegation(); - Assert(conflicting->hasProof()); - Assert(negConflicting->hasProof()); - - conflict.push_back(conflicting); - conflict.push_back(negConflicting); - - Constraint::assertionFringe(conflict); -} - -void TheoryArithPrivate::tryBranchCut(ApproximateSimplex* approx, int nid, BranchCutInfo& bci){ - Assert(conflictQueueEmpty()); - std::vector< ConstraintCPVec > conflicts; - - approx->tryCut(nid, bci); - Trace("approx::branch") << "tryBranchCut" << bci << endl; - Assert(bci.reconstructed()); - Assert(!bci.proven()); - pair p = replayGetConstraint(bci); - Assert(p.second == ARITHVAR_SENTINEL); - ConstraintP bc = p.first; - Assert(bc != NullConstraint); - if(bc->hasProof()){ - return; - } - - ConstraintP bcneg = bc->getNegation(); - { - context::Context::ScopedPush speculativePush(context()); - replayAssert(bcneg); - if(conflictQueueEmpty()){ - TimerStat::CodeTimer codeTimer(d_statistics.d_replaySimplexTimer); - - //test for linear feasibility - d_partialModel.stopQueueingBoundCounts(); - UpdateTrackingCallback utcb(&d_linEq); - d_partialModel.processBoundsQueue(utcb); - d_linEq.startTrackingBoundCounts(); - - SimplexDecisionProcedure& simplex = selectSimplex(true); - simplex.findModel(false); - // Can change d_qflraStatus - - d_linEq.stopTrackingBoundCounts(); - d_partialModel.startQueueingBoundCounts(); - } - for(size_t i = 0, N = d_conflicts.size(); i < N; ++i){ - - conflicts.push_back(ConstraintCPVec()); - intHoleConflictToVector(d_conflicts[i].first, conflicts.back()); - Constraint::assertionFringe(conflicts.back()); - - // ConstraintCP conflicting = d_conflicts[i]; - // ConstraintCP negConflicting = conflicting->getNegation(); - // Assert(conflicting->hasProof()); - // Assert(negConflicting->hasProof()); - - // conflicts.push_back(ConstraintCPVec()); - // ConstraintCPVec& back = conflicts.back(); - // back.push_back(conflicting); - // back.push_back(negConflicting); - - // // remove the floor/ceiling contraint implied by bcneg - // Constraint::assertionFringe(back); - } - - if(TraceIsOn("approx::branch")){ - if(d_conflicts.empty()){ - entireStateIsConsistent("branchfailure"); - } - } - } - - Trace("approx::branch") << "branch constraint " << bc << endl; - for(size_t i = 0, N = conflicts.size(); i < N; ++i){ - ConstraintCPVec& conf = conflicts[i]; - - // make sure to be working on the assertion fringe! - if(!contains(conf, bcneg)){ - Trace("approx::branch") << "reraise " << conf << endl; - ConstraintCP conflicting = vectorToIntHoleConflict(conf); - raiseConflict(conflicting, InferenceId::ARITH_CONF_BRANCH_CUT); - }else if(!bci.proven()){ - drop(conf, bcneg); - bci.setExplanation(conf); - Trace("approx::branch") << "dropped " << bci << endl; - } - } -} - -void TheoryArithPrivate::replayAssert(ConstraintP c) { - if(!c->assertedToTheTheory()){ - bool inConflict = c->negationHasProof(); - if(!c->hasProof()){ - c->setInternalAssumption(inConflict); - Trace("approx::replayAssert") << "replayAssert " << c << " set internal" << endl; - }else{ - Trace("approx::replayAssert") << "replayAssert " << c << " has explanation" << endl; - } - Trace("approx::replayAssert") << "replayAssertion " << c << endl; - if(inConflict){ - raiseConflict(c, InferenceId::ARITH_CONF_REPLAY_ASSERT); - }else{ - assertionCases(c); - } - }else{ - Trace("approx::replayAssert") - << "replayAssert " << c << " already asserted" << endl; - } -} - - -void TheoryArithPrivate::resolveOutPropagated(std::vector& confs, const std::set& propagated) const { - Trace("arith::resolveOutPropagated") - << "starting resolveOutPropagated() " << confs.size() << endl; - for(size_t i =0, N = confs.size(); i < N; ++i){ - ConstraintCPVec& conf = confs[i]; - size_t orig = conf.size(); - Constraint::assertionFringe(conf); - Trace("arith::resolveOutPropagated") - << " conf["< &confs) const { - int checks CVC5_UNUSED = 0; - int subsumed CVC5_UNUSED = 0; - - for (size_t i = 0, N = confs.size(); i < N; ++i) { - ConstraintCPVec &conf = confs[i]; - std::sort(conf.begin(), conf.end()); - } - - std::sort(confs.begin(), confs.end(), SizeOrd()); - for (size_t i = 0; i < confs.size(); i++) { - // i is not subsumed - for (size_t j = i + 1; j < confs.size();) { - ConstraintCPVec& a = confs[i]; - ConstraintCPVec& b = confs[j]; - checks++; - bool subsumes = std::includes(a.begin(), a.end(), b.begin(), b.end()); - if (subsumes) { - ConstraintCPVec& back = confs.back(); - b.swap(back); - confs.pop_back(); - subsumed++; - } else { - j++; - } - } - } - Trace("arith::subsumption") << "subsumed " << subsumed << "/" << checks - << endl; -} - -std::vector TheoryArithPrivate::replayLogRec(ApproximateSimplex* approx, int nid, ConstraintP bc, int depth){ - ++(d_statistics.d_replayLogRecCount); - Trace("approx::replayLogRec") << "replayLogRec()" << std::endl; - - size_t rpvars_size = d_replayVariables.size(); - size_t rpcons_size = d_replayConstraints.size(); - std::vector res; - - { /* create a block for the purpose of pushing the sat context */ - context::Context::ScopedPush speculativePush(context()); - Assert(!anyConflict()); - Assert(conflictQueueEmpty()); - set propagated; - - TreeLog& tl = getTreeLog(); - - if(bc != NullConstraint){ - replayAssert(bc); - } - - const NodeLog& nl = tl.getNode(nid); - NodeLog::const_iterator iter = nl.begin(), end = nl.end(); - for(; conflictQueueEmpty() && iter != end; ++iter){ - CutInfo* ci = *iter; - bool reject = false; - //cout << " trying " << *ci << endl; - if(ci->getKlass() == RowsDeletedKlass){ - RowsDeleted* rd = dynamic_cast(ci); - - tl.applyRowsDeleted(nid, *rd); - // The previous line modifies nl - - ++d_statistics.d_applyRowsDeleted; - }else if(ci->getKlass() == BranchCutKlass){ - BranchCutInfo* bci = dynamic_cast(ci); - Assert(bci != NULL); - tryBranchCut(approx, nid, *bci); - - ++d_statistics.d_branchCutsAttempted; - if(!(conflictQueueEmpty() || ci->reconstructed())){ - ++d_statistics.d_numBranchesFailed; - } - }else{ - approx->tryCut(nid, *ci); - if(ci->getKlass() == GmiCutKlass){ - ++d_statistics.d_gmiCutsAttempted; - }else if(ci->getKlass() == MirCutKlass){ - ++d_statistics.d_mirCutsAttempted; - } - - if(ci->reconstructed() && ci->proven()){ - const DenseMap& row = ci->getReconstruction().lhs; - reject = !complexityBelow(row, options().arith.replayRejectCutSize); - } - } - if(conflictQueueEmpty()){ - if(reject){ - ++d_statistics.d_cutsRejectedDuringReplay; - }else if(ci->reconstructed()){ - // success - ++d_statistics.d_cutsReconstructed; - - pair p = replayGetConstraint(*ci); - if(p.second != ARITHVAR_SENTINEL){ - Assert(ci->getRowId() >= 1); - tl.mapRowId(nl.getNodeId(), ci->getRowId(), p.second); - } - ConstraintP con = p.first; - if(TraceIsOn("approx::replayLogRec")){ - Trace("approx::replayLogRec") << "cut was remade " << con << " " << *ci << endl; - } - - if(ci->proven()){ - ++d_statistics.d_cutsProven; - - const ConstraintCPVec& exp = ci->getExplanation(); - // success - if(con->isTrue()){ - Trace("approx::replayLogRec") << "not asserted?" << endl; - }else if(!con->negationHasProof()){ - con->impliedByIntHole(exp, false); - replayAssert(con); - Trace("approx::replayLogRec") << "cut prop" << endl; - }else { - con->impliedByIntHole(exp, true); - Trace("approx::replayLogRec") << "cut into conflict " << con << endl; - raiseConflict(con, InferenceId::ARITH_CONF_REPLAY_LOG_REC); - } - }else{ - ++d_statistics.d_cutsProofFailed; - Trace("approx::replayLogRec") << "failed to get proof " << *ci << endl; - } - }else if(ci->getKlass() != RowsDeletedKlass){ - ++d_statistics.d_cutsReconstructionFailed; - } - } - } - - /* check if the system is feasible under with the cuts */ - if(conflictQueueEmpty()){ - Assert(options().arith.replayEarlyCloseDepths >= 1); - if (!nl.isBranch() || depth % options().arith.replayEarlyCloseDepths == 0) - { - TimerStat::CodeTimer codeTimer(d_statistics.d_replaySimplexTimer); - //test for linear feasibility - d_partialModel.stopQueueingBoundCounts(); - UpdateTrackingCallback utcb(&d_linEq); - d_partialModel.processBoundsQueue(utcb); - d_linEq.startTrackingBoundCounts(); - - SimplexDecisionProcedure& simplex = selectSimplex(true); - simplex.findModel(false); - // can change d_qflraStatus - - d_linEq.stopTrackingBoundCounts(); - d_partialModel.startQueueingBoundCounts(); - } - }else{ - ++d_statistics.d_replayLogRecConflictEscalation; - } - - if(!conflictQueueEmpty()){ - /* if a conflict has been found stop */ - for(size_t i = 0, N = d_conflicts.size(); i < N; ++i){ - res.push_back(ConstraintCPVec()); - intHoleConflictToVector(d_conflicts[i].first, res.back()); - } - ++d_statistics.d_replayLogRecEarlyExit; - }else if(nl.isBranch()){ - /* if it is a branch try the branch */ - pair p = replayGetConstraint(approx, nl); - Assert(p.second == ARITHVAR_SENTINEL); - ConstraintP dnc = p.first; - if(dnc != NullConstraint){ - ConstraintP upc = dnc->getNegation(); - - int dnid = nl.getDownId(); - int upid = nl.getUpId(); - - NodeLog& dnlog = tl.getNode(dnid); - NodeLog& uplog = tl.getNode(upid); - dnlog.copyParentRowIds(); - uplog.copyParentRowIds(); - - std::vector dnres; - std::vector upres; - std::vector containsdn; - std::vector containsup; - if(res.empty()){ - dnres = replayLogRec(approx, dnid, dnc, depth+1); - for(size_t i = 0, N = dnres.size(); i < N; ++i){ - ConstraintCPVec& conf = dnres[i]; - if(contains(conf, dnc)){ - containsdn.push_back(i); - }else{ - res.push_back(conf); - } - } - }else{ - Trace("approx::replayLogRec") << "replayLogRec() skipping" << dnlog << std::endl; - ++d_statistics.d_replayBranchSkips; - } - - if(res.empty()){ - upres = replayLogRec(approx, upid, upc, depth+1); - - for(size_t i = 0, N = upres.size(); i < N; ++i){ - ConstraintCPVec& conf = upres[i]; - if(contains(conf, upc)){ - containsup.push_back(i); - }else{ - res.push_back(conf); - } - } - }else{ - Trace("approx::replayLogRec") << "replayLogRec() skipping" << uplog << std::endl; - ++d_statistics.d_replayBranchSkips; - } - - if(res.empty()){ - for(size_t i = 0, N = containsdn.size(); i < N; ++i){ - ConstraintCPVec& dnconf = dnres[containsdn[i]]; - for(size_t j = 0, M = containsup.size(); j < M; ++j){ - ConstraintCPVec& upconf = upres[containsup[j]]; - - res.push_back(ConstraintCPVec()); - ConstraintCPVec& back = res.back(); - resolve(back, dnc, dnconf, upconf); - } - } - if(res.size() >= 2u){ - subsumption(res); - - if(res.size() > 100u){ - res.resize(100u); - } - } - }else{ - Trace("approx::replayLogRec") << "replayLogRec() skipping resolving" << nl << std::endl; - } - Trace("approx::replayLogRec") << "found #"< rpcons_size){ - ConstraintP c = d_replayConstraints.back(); - d_replayConstraints.pop_back(); - d_constraintDatabase.deleteConstraintAndNegation(c); - } - - /* Garbage collect the ArithVars made by this call */ - if(d_replayVariables.size() > rpvars_size){ - d_partialModel.stopQueueingBoundCounts(); - UpdateTrackingCallback utcb(&d_linEq); - d_partialModel.processBoundsQueue(utcb); - d_linEq.startTrackingBoundCounts(); - while(d_replayVariables.size() > rpvars_size){ - ArithVar v = d_replayVariables.back(); - d_replayVariables.pop_back(); - Assert(d_partialModel.canBeReleased(v)); - if(!d_tableau.isBasic(v)){ - /* if it is not basic make it basic. */ - auto ci = d_tableau.colIterator(v); - Assert(!ci.atEnd()); - ArithVar b = d_tableau.rowIndexToBasic((*ci).getRowIndex()); - Assert(b != ARITHVAR_SENTINEL); - DeltaRational cp = d_partialModel.getAssignment(b); - if(d_partialModel.cmpAssignmentLowerBound(b) < 0){ - cp = d_partialModel.getLowerBound(b); - }else if(d_partialModel.cmpAssignmentUpperBound(b) > 0){ - cp = d_partialModel.getUpperBound(b); - } - d_linEq.pivotAndUpdate(b, v, cp); - } - Assert(d_tableau.isBasic(v)); - d_linEq.stopTrackingRowIndex(d_tableau.basicToRowIndex(v)); - d_tableau.removeBasicRow(v); - - releaseArithVar(v); - Trace("approx::vars") << "releasing " << v << endl; - } - d_linEq.stopTrackingBoundCounts(); - d_partialModel.startQueueingBoundCounts(); - d_partialModel.attemptToReclaimReleased(); - } - return res; -} - -TreeLog& TheoryArithPrivate::getTreeLog(){ - if(d_treeLog == NULL){ - d_treeLog = new TreeLog(); - } - return *d_treeLog; -} - -ApproximateStatistics& TheoryArithPrivate::getApproxStats(){ - if(d_approxStats == NULL){ - d_approxStats = new ApproximateStatistics(); - } - return *d_approxStats; -} - -Node TheoryArithPrivate::branchToNode(ApproximateSimplex* approx, - const NodeLog& bn) const -{ - Assert(bn.isBranch()); - ArithVar v = approx->getBranchVar(bn); - if(v != ARITHVAR_SENTINEL && d_partialModel.isIntegerInput(v)){ - if(d_partialModel.hasNode(v)){ - Node n = d_partialModel.asNode(v); - double dval = bn.branchValue(); - std::optional maybe_value = - ApproximateSimplex::estimateWithCFE(dval); - if (!maybe_value) - { - return Node::null(); - } - Rational fl(maybe_value.value().floor()); - NodeManager* nm = NodeManager::currentNM(); - Node leq = - nm->mkNode(kind::LEQ, n, nm->mkConstRealOrInt(n.getType(), fl)); - Node norm = rewrite(leq); - return norm; - } - } - return Node::null(); -} - -Node TheoryArithPrivate::cutToLiteral(ApproximateSimplex* approx, const CutInfo& ci) const{ - Assert(ci.reconstructed()); - - const DenseMap& lhs = ci.getReconstruction().lhs; - Node sum = toSumNode(d_partialModel, lhs); - if(!sum.isNull()){ - NodeManager* nm = NodeManager::currentNM(); - Kind k = ci.getKind(); - Assert(k == kind::LEQ || k == kind::GEQ); - Node rhs = nm->mkConstRealOrInt(sum.getType(), ci.getReconstruction().rhs); - Node ineq = nm->mkNode(k, sum, rhs); - return rewrite(ineq); - } - return Node::null(); -} - -bool TheoryArithPrivate::replayLemmas(ApproximateSimplex* approx){ - ++(d_statistics.d_mipReplayLemmaCalls); - bool anythingnew = false; - - TreeLog& tl = getTreeLog(); - NodeLog& root = tl.getRootNode(); - root.applySelected(); /* set row ids */ - - vector cuts = approx->getValidCuts(root); - for(size_t i =0, N =cuts.size(); i < N; ++i){ - const CutInfo* cut = cuts[i]; - Assert(cut->reconstructed()); - Assert(cut->proven()); - - const DenseMap& row = cut->getReconstruction().lhs; - if (!complexityBelow(row, options().arith.lemmaRejectCutSize)) - { - ++(d_statistics.d_cutsRejectedDuringLemmas); - continue; - } - - Node cutConstraint = cutToLiteral(approx, *cut); - if(!cutConstraint.isNull()){ - const ConstraintCPVec& exp = cut->getExplanation(); - Node asLemma = Constraint::externalExplainByAssertions(exp); - - Node implied = rewrite(cutConstraint); - anythingnew = anythingnew || !isSatLiteral(implied); - - Node implication = asLemma.impNode(implied); - // DO NOT CALL OUTPUT LEMMA! - // TODO (project #37): justify - d_approxCuts.push_back(TrustNode::mkTrustLemma(implication, nullptr)); - Trace("approx::lemmas") << "cut["<mkTrustNode(branch, PfRule::SPLIT, {}, {lit}); - } - else - { - d_approxCuts.push_back(TrustNode::mkTrustLemma(branch, nullptr)); - } - ++(d_statistics.d_mipExternalBranch); - Trace("approx::lemmas") << "branching "<< root <<" as " << branch << endl; - } - } - return anythingnew; -} - -void TheoryArithPrivate::turnOffApproxFor(int32_t rounds){ - d_attemptSolveIntTurnedOff = d_attemptSolveIntTurnedOff + rounds; - ++(d_statistics.d_approxDisabled); -} - -bool TheoryArithPrivate::safeToCallApprox() const{ - unsigned numRows = 0; - unsigned numCols = 0; - var_iterator vi = var_begin(), vi_end = var_end(); - // Assign each variable to a row and column variable as it appears in the input - for(; vi != vi_end && !(numRows > 0 && numCols > 0); ++vi){ - ArithVar v = *vi; - - if(d_partialModel.isAuxiliary(v)){ - ++numRows; - }else{ - ++numCols; - } - } - return (numRows > 0 && numCols > 0); -} - -// solve() -// res = solveRealRelaxation(effortLevel); -// switch(res){ -// case LinFeas: -// case LinInfeas: -// return replay() -// case Unknown: -// case Error -// if() -void TheoryArithPrivate::solveInteger(Theory::Effort effortLevel){ - if(!safeToCallApprox()) { return; } - - Assert(safeToCallApprox()); - TimerStat::CodeTimer codeTimer0(d_statistics.d_solveIntTimer); - - ++(d_statistics.d_solveIntCalls); - d_statistics.d_inSolveInteger = 1; - - if(!Theory::fullEffort(effortLevel)){ - d_solveIntAttempts++; - ++(d_statistics.d_solveStandardEffort); - } - - // if integers are attempted, - Assert(options().arith.useApprox); - Assert(ApproximateSimplex::enabled()); - - int level = context()->getLevel(); - d_lastContextIntegerAttempted = level; - - static constexpr int32_t mipLimit = 200000; - - TreeLog& tl = getTreeLog(); - ApproximateStatistics& stats = getApproxStats(); - ApproximateSimplex* approx = - ApproximateSimplex::mkApproximateSimplexSolver(d_partialModel, tl, stats); - - approx->setPivotLimit(mipLimit); - if(!d_guessedCoeffSet){ - d_guessedCoeffs = approx->heuristicOptCoeffs(); - d_guessedCoeffSet = true; - } - if(!d_guessedCoeffs.empty()){ - approx->setOptCoeffs(d_guessedCoeffs); - } - static constexpr int32_t depthForLikelyInfeasible = 10; - int maxDepthPass1 = d_likelyIntegerInfeasible - ? depthForLikelyInfeasible - : options().arith.maxApproxDepth; - approx->setBranchingDepth(maxDepthPass1); - approx->setBranchOnVariableLimit(100); - LinResult relaxRes = approx->solveRelaxation(); - if( relaxRes == LinFeasible ){ - MipResult mipRes = MipUnknown; - { - TimerStat::CodeTimer codeTimer1(d_statistics.d_mipTimer); - mipRes = approx->solveMIP(false); - } - - Trace("arith::solveInteger") << "mipRes " << mipRes << endl; - switch(mipRes) { - case MipBingo: - // attempt the solution - { - ++(d_statistics.d_solveIntModelsAttempts); - - d_partialModel.stopQueueingBoundCounts(); - UpdateTrackingCallback utcb(&d_linEq); - d_partialModel.processBoundsQueue(utcb); - d_linEq.startTrackingBoundCounts(); - - ApproximateSimplex::Solution mipSolution; - mipSolution = approx->extractMIP(); - importSolution(mipSolution); - solveRelaxationOrPanic(effortLevel); - - if (d_qflraStatus == Result::SAT) - { - if (!anyConflict()) - { - if (ARITHVAR_SENTINEL == nextIntegerViolation(false)) - { - ++(d_statistics.d_solveIntModelsSuccessful); - } - } - } - - // shutdown simplex - d_linEq.stopTrackingBoundCounts(); - d_partialModel.startQueueingBoundCounts(); - } - break; - case MipClosed: - /* All integer branches closed */ - approx->setPivotLimit(2*mipLimit); - { - TimerStat::CodeTimer codeTimer2(d_statistics.d_mipTimer); - mipRes = approx->solveMIP(true); - } - - if(mipRes == MipClosed){ - d_likelyIntegerInfeasible = true; - replayLog(approx); - AlwaysAssert(anyConflict() || d_qflraStatus != Result::SAT); - - if (!anyConflict()) - { - solveRealRelaxation(effortLevel); - } - } - if(!(anyConflict() || !d_approxCuts.empty())){ - turnOffApproxFor(options().arith.replayNumericFailurePenalty); - } - break; - case BranchesExhausted: - case ExecExhausted: - case PivotsExhauasted: - if(mipRes == BranchesExhausted){ - ++d_statistics.d_branchesExhausted; - }else if(mipRes == ExecExhausted){ - ++d_statistics.d_execExhausted; - }else if(mipRes == PivotsExhauasted){ - ++d_statistics.d_pivotsExhausted; - } - - approx->setPivotLimit(2*mipLimit); - approx->setBranchingDepth(2); - { - TimerStat::CodeTimer codeTimer3(d_statistics.d_mipTimer); - mipRes = approx->solveMIP(true); - } - replayLemmas(approx); - break; - case MipUnknown: - break; - } - } - delete approx; - - if(!Theory::fullEffort(effortLevel)){ - if(anyConflict() || !d_approxCuts.empty()){ - d_solveIntMaybeHelp++; - } - } - - d_statistics.d_inSolveInteger = 0; -} - -SimplexDecisionProcedure& TheoryArithPrivate::selectSimplex(bool pass1){ - if(pass1){ - if(d_pass1SDP == NULL){ - if (options().arith.useFC) - { - d_pass1SDP = (SimplexDecisionProcedure*)(&d_fcSimplex); - } - else if (options().arith.useSOI) - { - d_pass1SDP = (SimplexDecisionProcedure*)(&d_soiSimplex); - } - else - { - d_pass1SDP = (SimplexDecisionProcedure*)(&d_dualSimplex); - } - } - Assert(d_pass1SDP != NULL); - return *d_pass1SDP; - }else{ - if(d_otherSDP == NULL){ - if (options().arith.useFC) - { - d_otherSDP = (SimplexDecisionProcedure*)(&d_fcSimplex); - } - else if (options().arith.useSOI) - { - d_otherSDP = (SimplexDecisionProcedure*)(&d_soiSimplex); - } - else - { - d_otherSDP = (SimplexDecisionProcedure*)(&d_soiSimplex); - } - } - Assert(d_otherSDP != NULL); - return *d_otherSDP; - } -} - -void TheoryArithPrivate::importSolution(const ApproximateSimplex::Solution& solution){ - if(TraceIsOn("arith::importSolution")){ - Trace("arith::importSolution") << "importSolution before " << d_qflraStatus << endl; - d_partialModel.printEntireModel(Trace("arith::importSolution")); - } - - d_qflraStatus = d_attemptSolSimplex.attempt(solution); - - if(TraceIsOn("arith::importSolution")){ - Trace("arith::importSolution") << "importSolution intermediate " << d_qflraStatus << endl; - d_partialModel.printEntireModel(Trace("arith::importSolution")); - } - - if(d_qflraStatus != Result::UNSAT){ - static constexpr int64_t pass2Limit = 20; - SimplexDecisionProcedure& simplex = selectSimplex(false); - simplex.setVarOrderPivotLimit(pass2Limit); - d_qflraStatus = simplex.findModel(false); - } - - if(TraceIsOn("arith::importSolution")){ - Trace("arith::importSolution") << "importSolution after " << d_qflraStatus << endl; - d_partialModel.printEntireModel(Trace("arith::importSolution")); - } -} - -bool TheoryArithPrivate::solveRelaxationOrPanic(Theory::Effort effortLevel) -{ - // if at this point the linear relaxation is still unknown, - // attempt to branch an integer variable as a last ditch effort on full check - if (d_qflraStatus == Result::UNKNOWN) - { - d_qflraStatus = selectSimplex(true).findModel(false); - } - - if (Theory::fullEffort(effortLevel) && d_qflraStatus == Result::UNKNOWN) - { - ArithVar canBranch = nextIntegerViolation(false); - if (canBranch != ARITHVAR_SENTINEL) - { - ++d_statistics.d_panicBranches; - TrustNode branch = branchIntegerVariable(canBranch); - Assert(branch.getNode().getKind() == kind::OR); - Node rwbranch = rewrite(branch.getNode()[0]); - if (!isSatLiteral(rwbranch)) - { - d_approxCuts.push_back(branch); - return true; - } - } - d_qflraStatus = selectSimplex(false).findModel(true); - } - return false; -} - -bool TheoryArithPrivate::solveRealRelaxation(Theory::Effort effortLevel){ - TimerStat::CodeTimer codeTimer0(d_statistics.d_solveRealRelaxTimer); - Assert(d_qflraStatus != Result::SAT); - - d_partialModel.stopQueueingBoundCounts(); - UpdateTrackingCallback utcb(&d_linEq); - d_partialModel.processBoundsQueue(utcb); - d_linEq.startTrackingBoundCounts(); - - bool noPivotLimit = - Theory::fullEffort(effortLevel) || !options().arith.restrictedPivots; - - SimplexDecisionProcedure& simplex = selectSimplex(true); - - bool useApprox = options().arith.useApprox && ApproximateSimplex::enabled() - && getSolveIntegerResource(); - - Trace("TheoryArithPrivate::solveRealRelaxation") - << "solveRealRelaxation() approx" - << " " << options().arith.useApprox << " " - << ApproximateSimplex::enabled() << " " << useApprox << " " - << safeToCallApprox() << endl; - - bool noPivotLimitPass1 = noPivotLimit && !useApprox; - d_qflraStatus = simplex.findModel(noPivotLimitPass1); - - Trace("TheoryArithPrivate::solveRealRelaxation") - << "solveRealRelaxation()" << " pass1 " << d_qflraStatus << endl; - - if (d_qflraStatus == Result::UNKNOWN && useApprox && safeToCallApprox()) - { - // pass2: fancy-final - static constexpr int32_t relaxationLimit = 10000; - Assert(ApproximateSimplex::enabled()); - - TreeLog& tl = getTreeLog(); - ApproximateStatistics& stats = getApproxStats(); - ApproximateSimplex* approxSolver = - ApproximateSimplex::mkApproximateSimplexSolver(d_partialModel, tl, stats); - - approxSolver->setPivotLimit(relaxationLimit); - - if(!d_guessedCoeffSet){ - d_guessedCoeffs = approxSolver->heuristicOptCoeffs(); - d_guessedCoeffSet = true; - } - if(!d_guessedCoeffs.empty()){ - approxSolver->setOptCoeffs(d_guessedCoeffs); - } - - ++d_statistics.d_relaxCalls; - - ApproximateSimplex::Solution relaxSolution; - LinResult relaxRes = LinUnknown; - { - TimerStat::CodeTimer codeTimer1(d_statistics.d_lpTimer); - relaxRes = approxSolver->solveRelaxation(); - } - Trace("solveRealRelaxation") << "solve relaxation? " << endl; - switch(relaxRes){ - case LinFeasible: - Trace("solveRealRelaxation") << "lin feasible? " << endl; - ++d_statistics.d_relaxLinFeas; - relaxSolution = approxSolver->extractRelaxation(); - importSolution(relaxSolution); - if(d_qflraStatus != Result::SAT){ - ++d_statistics.d_relaxLinFeasFailures; - } - break; - case LinInfeasible: - // todo attempt to recreate approximate conflict - ++d_statistics.d_relaxLinInfeas; - Trace("solveRealRelaxation") << "lin infeasible " << endl; - relaxSolution = approxSolver->extractRelaxation(); - importSolution(relaxSolution); - if(d_qflraStatus != Result::UNSAT){ - ++d_statistics.d_relaxLinInfeasFailures; - } - break; - case LinExhausted: - ++d_statistics.d_relaxLinExhausted; - Trace("solveRealRelaxation") << "exhuasted " << endl; - break; - case LinUnknown: - default: - ++d_statistics.d_relaxOthers; - break; - } - delete approxSolver; - - } - - bool emmittedConflictOrSplit = solveRelaxationOrPanic(effortLevel); - - // TODO Save zeroes with no conflicts - d_linEq.stopTrackingBoundCounts(); - d_partialModel.startQueueingBoundCounts(); - - return emmittedConflictOrSplit; -} - -bool TheoryArithPrivate::hasFreshArithLiteral(Node n) const{ - switch(n.getKind()){ - case kind::LEQ: - case kind::GEQ: - case kind::GT: - case kind::LT: - return !isSatLiteral(n); - case kind::EQUAL: - if (n[0].getType().isRealOrInt()) - { - return !isSatLiteral(n); - } - else if (n[0].getType().isBoolean()) - { - return hasFreshArithLiteral(n[0]) || - hasFreshArithLiteral(n[1]); - } - else - { - return false; - } - case kind::IMPLIES: - // try the rhs first - return hasFreshArithLiteral(n[1]) || - hasFreshArithLiteral(n[0]); - default: - if(n.getType().isBoolean()){ - for(Node::iterator ni=n.begin(), nend=n.end(); ni!=nend; ++ni){ - Node child = *ni; - if(hasFreshArithLiteral(child)){ - return true; - } - } - } - return false; - } -} - -bool TheoryArithPrivate::preCheck(Theory::Effort level) -{ - Assert(d_currentPropagationList.empty()); - if(TraceIsOn("arith::consistency")){ - Assert(unenqueuedVariablesAreConsistent()); - } - - d_newFacts = !done(); - // If d_previousStatus == SAT, then reverts on conflicts are safe - // Otherwise, they are not and must be committed. - d_previousStatus = d_qflraStatus; - if (d_newFacts) - { - d_qflraStatus = Result::UNKNOWN; - d_hasDoneWorkSinceCut = true; - } - return false; -} - -void TheoryArithPrivate::preNotifyFact(TNode atom, bool pol, TNode fact) -{ - ConstraintP curr = constraintFromFactQueue(fact); - if (curr != NullConstraint) - { - bool res CVC5_UNUSED = assertionCases(curr); - Assert(!res || anyConflict()); - } -} - -bool TheoryArithPrivate::postCheck(Theory::Effort effortLevel) -{ - if(!anyConflict()){ - while(!d_learnedBounds.empty()){ - // we may attempt some constraints twice. this is okay! - ConstraintP curr = d_learnedBounds.front(); - d_learnedBounds.pop(); - Trace("arith::learned") << curr << endl; - - bool res CVC5_UNUSED = assertionCases(curr); - Assert(!res || anyConflict()); - - if(anyConflict()){ break; } - } - } - - if(anyConflict()){ - d_qflraStatus = Result::UNSAT; - if (options().arith.revertArithModels && d_previousStatus == Result::SAT) - { - ++d_statistics.d_revertsOnConflicts; - Trace("arith::bt") << "clearing here " - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - revertOutOfConflict(); - d_errorSet.clear(); - }else{ - ++d_statistics.d_commitsOnConflicts; - Trace("arith::bt") << "committing here " - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - revertOutOfConflict(); - } - outputConflicts(); - //cout << "unate conflict 1 " << effortLevel << std::endl; - return true; - } - - - if(TraceIsOn("arith::print_assertions")) { - debugPrintAssertions(Trace("arith::print_assertions")); - } - - bool emmittedConflictOrSplit = false; - Assert(d_conflicts.empty()); - - bool useSimplex = d_qflraStatus != Result::SAT; - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "pre realRelax" << endl; - - if(useSimplex){ - emmittedConflictOrSplit = solveRealRelaxation(effortLevel); - } - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "post realRelax" << endl; - - - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "pre solveInteger" << endl; - - if(attemptSolveInteger(effortLevel, emmittedConflictOrSplit)){ - solveInteger(effortLevel); - if(anyConflict()){ - ++d_statistics.d_commitsOnConflicts; - Trace("arith::bt") << "committing here " - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - revertOutOfConflict(); - d_errorSet.clear(); - outputConflicts(); - return true; - } - } - - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "post solveInteger" << endl; - - switch(d_qflraStatus){ - case Result::SAT: - if (d_newFacts) - { - ++d_statistics.d_nontrivialSatChecks; - } - - Trace("arith::bt") << "committing sap inConflit" - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - d_unknownsInARow = 0; - if(TraceIsOn("arith::consistency")){ - Assert(entireStateIsConsistent("sat comit")); - } - if (useSimplex && options().arith.collectPivots) - { - if (options().arith.useFC) - { - d_statistics.d_satPivots << d_fcSimplex.getPivots(); - } - else - { - d_statistics.d_satPivots << d_dualSimplex.getPivots(); - } - } - break; - case Result::UNKNOWN: - ++d_unknownsInARow; - ++(d_statistics.d_unknownChecks); - Assert(!Theory::fullEffort(effortLevel)); - Trace("arith::bt") << "committing unknown" - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - d_statistics.d_maxUnknownsInARow.maxAssign(d_unknownsInARow); - - if (useSimplex && options().arith.collectPivots) - { - if (options().arith.useFC) - { - d_statistics.d_unknownPivots << d_fcSimplex.getPivots(); - } - else - { - d_statistics.d_unknownPivots << d_dualSimplex.getPivots(); - } - } - break; - case Result::UNSAT: - d_unknownsInARow = 0; - - ++d_statistics.d_commitsOnConflicts; - - Trace("arith::bt") << "committing on conflict" - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - d_partialModel.commitAssignmentChanges(); - revertOutOfConflict(); - - if(TraceIsOn("arith::consistency::comitonconflict")){ - entireStateIsConsistent("commit on conflict"); - } - outputConflicts(); - emmittedConflictOrSplit = true; - Trace("arith::conflict") << "simplex conflict" << endl; - - if (useSimplex && options().arith.collectPivots) - { - if (options().arith.useFC) - { - d_statistics.d_unsatPivots << d_fcSimplex.getPivots(); - } - else - { - d_statistics.d_unsatPivots << d_dualSimplex.getPivots(); - } - } - break; - default: - Unimplemented(); - } - d_statistics.d_avgUnknownsInARow << d_unknownsInARow; - - size_t nPivots = options().arith.useFC ? d_fcSimplex.getPivots() - : d_dualSimplex.getPivots(); - for (std::size_t i = 0; i < nPivots; ++i) - { - d_containing.d_out->spendResource( - Resource::ArithPivotStep); - } - - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "pre approx cuts" << endl; - if(!d_approxCuts.empty()){ - bool anyFresh = false; - while(!d_approxCuts.empty()){ - TrustNode lem = d_approxCuts.front(); - d_approxCuts.pop(); - Trace("arith::approx::cuts") << "approximate cut:" << lem << endl; - anyFresh = anyFresh || hasFreshArithLiteral(lem.getNode()); - Trace("arith::lemma") << "approximate cut:" << lem << endl; - outputTrustedLemma(lem, InferenceId::ARITH_APPROX_CUT); - } - if(anyFresh){ - emmittedConflictOrSplit = true; - } - } - - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "post approx cuts" << endl; - - // This should be fine if sat or unknown - if (!emmittedConflictOrSplit - && (options().arith.arithPropagationMode - == options::ArithPropagationMode::UNATE_PROP - || options().arith.arithPropagationMode - == options::ArithPropagationMode::BOTH_PROP)) - { - TimerStat::CodeTimer codeTimer0(d_statistics.d_newPropTime); - Assert(d_qflraStatus != Result::UNSAT); - - while(!d_currentPropagationList.empty() && !anyConflict()){ - ConstraintP curr = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - - ConstraintType t = curr->getType(); - Assert(t != Disequality) - << "Disequalities are not allowed in d_currentPropagation"; - - switch(t){ - case LowerBound: - { - ConstraintP prev = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - d_constraintDatabase.unatePropLowerBound(curr, prev); - break; - } - case UpperBound: - { - ConstraintP prev = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - d_constraintDatabase.unatePropUpperBound(curr, prev); - break; - } - case Equality: - { - ConstraintP prevLB = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - ConstraintP prevUB = d_currentPropagationList.front(); - d_currentPropagationList.pop_front(); - d_constraintDatabase.unatePropEquality(curr, prevLB, prevUB); - break; - } - default: Unhandled() << curr->getType(); - } - } - - if(anyConflict()){ - Trace("arith::unate") << "unate conflict" << endl; - revertOutOfConflict(); - d_qflraStatus = Result::UNSAT; - outputConflicts(); - emmittedConflictOrSplit = true; - //cout << "unate conflict " << endl; - Trace("arith::bt") << "committing on unate conflict" - << " " << d_newFacts << " " << d_previousStatus << " " - << d_qflraStatus << endl; - - Trace("arith::conflict") << "unate arith conflict" << endl; - } - } - else - { - TimerStat::CodeTimer codeTimer1(d_statistics.d_newPropTime); - d_currentPropagationList.clear(); - } - Assert(d_currentPropagationList.empty()); - - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "post unate" << endl; - - if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel)){ - ++d_fullCheckCounter; - } - if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel)){ - emmittedConflictOrSplit = splitDisequalities(); - } - Trace("arith::ems") << "ems: " << emmittedConflictOrSplit - << "pos splitting" << endl; - - - Trace("arith") << "integer? " - << " conf/split " << emmittedConflictOrSplit - << " fulleffort " << Theory::fullEffort(effortLevel) << endl; - - if(!emmittedConflictOrSplit && Theory::fullEffort(effortLevel) && !hasIntegerModel()){ - Node possibleConflict = Node::null(); - if (!emmittedConflictOrSplit && options().arith.arithDioSolver) - { - possibleConflict = callDioSolver(); - if(possibleConflict != Node::null()){ - revertOutOfConflict(); - Trace("arith::conflict") << "dio conflict " << possibleConflict << endl; - // TODO (project #37): justify (proofs in the DIO solver) - raiseBlackBoxConflict(possibleConflict); - outputConflicts(); - emmittedConflictOrSplit = true; - } - } - - if (!emmittedConflictOrSplit && d_hasDoneWorkSinceCut - && options().arith.arithDioSolver) - { - if(getDioCuttingResource()){ - TrustNode possibleLemma = dioCutting(); - if(!possibleLemma.isNull()){ - d_hasDoneWorkSinceCut = false; - d_cutCount = d_cutCount + 1; - Trace("arith::lemma") << "dio cut " << possibleLemma << endl; - if (outputTrustedLemma(possibleLemma, InferenceId::ARITH_DIO_CUT)) - { - emmittedConflictOrSplit = true; - } - } - } - } - - if(!emmittedConflictOrSplit) { - TrustNode possibleLemma = roundRobinBranch(); - if (!possibleLemma.getNode().isNull()) - { - ++(d_statistics.d_externalBranchAndBounds); - d_cutCount = d_cutCount + 1; - Trace("arith::lemma") << "rrbranch lemma" - << possibleLemma << endl; - if (outputTrustedLemma(possibleLemma, InferenceId::ARITH_BB_LEMMA)) - { - emmittedConflictOrSplit = true; - } - } - } - - if (options().arith.maxCutsInContext <= d_cutCount) - { - if(d_diosolver.hasMoreDecompositionLemmas()){ - while(d_diosolver.hasMoreDecompositionLemmas()){ - Node decompositionLemma = d_diosolver.nextDecompositionLemma(); - Trace("arith::lemma") << "dio decomposition lemma " - << decompositionLemma << endl; - outputLemma(decompositionLemma, InferenceId::ARITH_DIO_DECOMPOSITION); - } - }else{ - Trace("arith::restart") << "arith restart!" << endl; - outputRestart(); - } - } - }//if !emmittedConflictOrSplit && fullEffort(effortLevel) && !hasIntegerModel() - - if(Theory::fullEffort(effortLevel)){ - if(TraceIsOn("arith::consistency::final")){ - entireStateIsConsistent("arith::consistency::final"); - } - } - - if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - if(TraceIsOn("arith::print_model")) { - debugPrintModel(Trace("arith::print_model")); - } - Trace("arith") << "TheoryArithPrivate::check end" << std::endl; - return emmittedConflictOrSplit; -} - -bool TheoryArithPrivate::foundNonlinear() const { return d_foundNl; } - -TrustNode TheoryArithPrivate::branchIntegerVariable(ArithVar x) const -{ - const DeltaRational& d = d_partialModel.getAssignment(x); - Assert(!d.isIntegral()); - const Rational& r = d.getNoninfinitesimalPart(); - const Rational& i = d.getInfinitesimalPart(); - Trace("integers") << "integers: assignment to [[" << d_partialModel.asNode(x) << "]] is " << r << "[" << i << "]" << endl; - Assert(!(r.getDenominator() == 1 && i.getNumerator() == 0)); - TNode var = d_partialModel.asNode(x); - TrustNode lem = d_bab.branchIntegerVariable(var, r); - if (TraceIsOn("integers")) - { - Node l = lem.getNode(); - if (isSatLiteral(l[0])) - { - Trace("integers") << " " << l[0] << " == " << getSatValue(l[0]) - << endl; - } - else - { - Trace("integers") << " " << l[0] << " is not assigned a SAT literal" - << endl; - } - if (isSatLiteral(l[1])) - { - Trace("integers") << " " << l[1] << " == " << getSatValue(l[1]) - << endl; - } - else - { - Trace("integers") << " " << l[1] << " is not assigned a SAT literal" - << endl; - } - } - return lem; -} - -std::vector TheoryArithPrivate::cutAllBounded() const{ - vector lemmas; - ArithVar max = d_partialModel.getNumberOfVariables(); - - if (options().arith.doCutAllBounded && max > 0) - { - for(ArithVar iter = 0; iter != max; ++iter){ - //Do not include slack variables - const DeltaRational& d = d_partialModel.getAssignment(iter); - if(isIntegerInput(iter) && - !d_cutInContext.contains(iter) && - d_partialModel.hasUpperBound(iter) && - d_partialModel.hasLowerBound(iter) && - !d.isIntegral()){ - lemmas.push_back(iter); - } - } - } - return lemmas; -} - -/** Returns true if the roundRobinBranching() issues a lemma. */ -TrustNode TheoryArithPrivate::roundRobinBranch() -{ - if(hasIntegerModel()){ - return TrustNode::null(); - }else{ - ArithVar v = d_nextIntegerCheckVar; - - Assert(isInteger(v)); - Assert(!isAuxiliaryVariable(v)); - return branchIntegerVariable(v); - } -} - -bool TheoryArithPrivate::splitDisequalities(){ - bool splitSomething = false; - - vector save; - - while(!d_diseqQueue.empty()){ - ConstraintP front = d_diseqQueue.front(); - d_diseqQueue.pop(); - - if(front->isSplit()){ - Trace("arith::eq") << "split already" << endl; - }else{ - Trace("arith::eq") << "not split already" << endl; - - ArithVar lhsVar = front->getVariable(); - - const DeltaRational& lhsValue = d_partialModel.getAssignment(lhsVar); - const DeltaRational& rhsValue = front->getValue(); - if(lhsValue == rhsValue){ - Trace("arith::lemma") << "Splitting on " << front << endl; - Trace("arith::lemma") << "LHS value = " << lhsValue << endl; - Trace("arith::lemma") << "RHS value = " << rhsValue << endl; - TrustNode lemma = front->split(); - ++(d_statistics.d_statDisequalitySplits); - - Trace("arith::lemma") << "Now " << rewrite(lemma.getNode()) << endl; - outputTrustedLemma(lemma, InferenceId::ARITH_SPLIT_DEQ); - // cout << "Now " << rewrite(lemma) << endl; - splitSomething = true; - }else if(d_partialModel.strictlyLessThanLowerBound(lhsVar, rhsValue)){ - Trace("arith::eq") << "can drop as less than lb" << front << endl; - }else if(d_partialModel.strictlyGreaterThanUpperBound(lhsVar, rhsValue)){ - Trace("arith::eq") << "can drop as greater than ub" << front << endl; - }else{ - Trace("arith::eq") << "save" << front << ": " <::const_iterator it = d_diseqQueue.begin(); - context::CDQueue::const_iterator it_end = d_diseqQueue.end(); - for(; it != it_end; ++ it) { - out << *it << endl; - } -} - -void TheoryArithPrivate::debugPrintModel(std::ostream& out) const{ - out << "Model:" << endl; - for (var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar i = *vi; - if(d_partialModel.hasNode(i)){ - out << d_partialModel.asNode(i) << " : " << - d_partialModel.getAssignment(i); - if(d_tableau.isBasic(i)){ - out << " (basic)"; - } - out << endl; - } - } -} - -TrustNode TheoryArithPrivate::explain(TNode n) -{ - Trace("arith::explain") << "explain @" << context()->getLevel() << ": " << n - << endl; - - ConstraintP c = d_constraintDatabase.lookup(n); - TrustNode exp; - if(c != NullConstraint){ - Assert(!c->isAssumption()); - exp = c->externalExplainForPropagation(n); - Trace("arith::explain") << "constraint explanation" << n << ":" << exp << endl; - }else if(d_assertionsThatDoNotMatchTheirLiterals.find(n) != d_assertionsThatDoNotMatchTheirLiterals.end()){ - c = d_assertionsThatDoNotMatchTheirLiterals[n]; - if(!c->isAssumption()){ - exp = c->externalExplainForPropagation(n); - Trace("arith::explain") << "assertions explanation" << n << ":" << exp << endl; - }else{ - Trace("arith::explain") << "this is a strange mismatch" << n << endl; - Assert(d_congruenceManager.canExplain(n)); - exp = d_congruenceManager.explain(n); - } - }else{ - Assert(d_congruenceManager.canExplain(n)); - Trace("arith::explain") << "dm explanation" << n << endl; - exp = d_congruenceManager.explain(n); - } - return exp; -} - -void TheoryArithPrivate::propagate(Theory::Effort e) { - // This uses model values for safety. Disable for now. - if (d_qflraStatus == Result::SAT - && (options().arith.arithPropagationMode - == options::ArithPropagationMode::BOUND_INFERENCE_PROP - || options().arith.arithPropagationMode - == options::ArithPropagationMode::BOTH_PROP) - && hasAnyUpdates()) - { - if (options().arith.newProp) - { - propagateCandidatesNew(); - } - else - { - propagateCandidates(); - } - } - else - { - clearUpdates(); - } - - while(d_constraintDatabase.hasMorePropagations()){ - ConstraintCP c = d_constraintDatabase.nextPropagation(); - Trace("arith::prop") << "next prop" << context()->getLevel() << ": " << c - << endl; - - if(c->negationHasProof()){ - Trace("arith::prop") << "negation has proof " << c->getNegation() << endl; - Trace("arith::prop") << c->getNegation()->externalExplainByAssertions() - << endl; - } - Assert(!c->negationHasProof()) - << "A constraint has been propagated on the constraint propagation " - "queue, but the negation has been set to true. Contact Tim now!"; - - if(!c->assertedToTheTheory()){ - Node literal = c->getLiteral(); - Trace("arith::prop") << "propagating @" << context()->getLevel() << " " - << literal << endl; - - outputPropagate(literal); - }else{ - Trace("arith::prop") << "already asserted to the theory " << c->getLiteral() << endl; - } - } - - NodeManager* nm = NodeManager::currentNM(); - while(d_congruenceManager.hasMorePropagations()){ - TNode toProp = d_congruenceManager.getNextPropagation(); - - //Currently if the flag is set this came from an equality detected by the - //equality engine in the the difference manager. - Node normalized = rewrite(toProp); - - ConstraintP constraint = d_constraintDatabase.lookup(normalized); - if(constraint == NullConstraint){ - Trace("arith::prop") << "propagating on non-constraint? " << toProp << endl; - - outputPropagate(toProp); - }else if(constraint->negationHasProof()){ - // The congruence manager can prove: antecedents => toProp, - // ergo. antecedents ^ ~toProp is a conflict. - TrustNode exp = d_congruenceManager.explain(toProp); - Node notNormalized = normalized.negate(); - std::vector ants(exp.getNode().begin(), exp.getNode().end()); - ants.push_back(notNormalized); - Node lp = nm->mkAnd(ants); - Trace("arith::prop") << "propagate conflict" << lp << endl; - if (proofsEnabled()) - { - // Assume all of antecedents and ~toProp (rewritten) - std::vector pfAntList; - for (size_t i = 0; i < ants.size(); ++i) - { - pfAntList.push_back(d_pnm->mkAssume(ants[i])); - } - Pf pfAnt = pfAntList.size() > 1 - ? d_pnm->mkNode(PfRule::AND_INTRO, pfAntList, {}) - : pfAntList[0]; - // Use modus ponens to get toProp (un rewritten) - Pf pfConc = d_pnm->mkNode( - PfRule::MODUS_PONENS, - {pfAnt, exp.getGenerator()->getProofFor(exp.getProven())}, - {}); - // prove toProp (rewritten) - Pf pfConcRewritten = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {pfConc}, {normalized}); - Pf pfNotNormalized = d_pnm->mkAssume(notNormalized); - // prove bottom from toProp and ~toProp - Pf pfBot; - if (normalized.getKind() == kind::NOT) - { - pfBot = d_pnm->mkNode( - PfRule::CONTRA, {pfNotNormalized, pfConcRewritten}, {}); - } - else - { - pfBot = d_pnm->mkNode( - PfRule::CONTRA, {pfConcRewritten, pfNotNormalized}, {}); - } - // close scope - Pf pfNotAnd = d_pnm->mkScope(pfBot, ants); - raiseBlackBoxConflict(lp, pfNotAnd); - } - else - { - raiseBlackBoxConflict(lp); - } - outputConflicts(); - return; - }else{ - Trace("arith::prop") << "propagating still?" << toProp << endl; - outputPropagate(toProp); - } - } -} - -DeltaRational TheoryArithPrivate::getDeltaValue(TNode term) const -{ - AlwaysAssert(d_qflraStatus != Result::UNKNOWN); - Trace("arith::value") << term << std::endl; - - if (d_partialModel.hasArithVar(term)) { - ArithVar var = d_partialModel.asArithVar(term); - return d_partialModel.getAssignment(var); - } - - switch (Kind kind = term.getKind()) { - case kind::CONST_RATIONAL: - case kind::CONST_INTEGER: return term.getConst(); - - case kind::ADD: - { // 2+ args - DeltaRational value(0); - for (TNode::iterator i = term.begin(), iend = term.end(); i != iend; - ++i) { - value = value + getDeltaValue(*i); - } - return value; - } - - case kind::NONLINEAR_MULT: - case kind::MULT: { // 2+ args - Assert(!isSetup(term)); - DeltaRational value(1); - for (TNode::iterator i = term.begin(), iend = term.end(); i != iend; - ++i) { - value = value * getDeltaValue(*i); - } - return value; - } - case kind::SUB: - { // 2 args - return getDeltaValue(term[0]) - getDeltaValue(term[1]); - } - case kind::NEG: - { // 1 arg - return (-getDeltaValue(term[0])); - } - - case kind::DIVISION: { // 2 args - Assert(!isSetup(term)); - return getDeltaValue(term[0]) / getDeltaValue(term[1]); - } - case kind::DIVISION_TOTAL: - case kind::INTS_DIVISION_TOTAL: - case kind::INTS_MODULUS_TOTAL: { // 2 args - Assert(!isSetup(term)); - DeltaRational denominator = getDeltaValue(term[1]); - if (denominator.isZero()) { - return DeltaRational(0, 0); - } - DeltaRational numerator = getDeltaValue(term[0]); - if (kind == kind::DIVISION_TOTAL) { - return numerator / denominator; - } else if (kind == kind::INTS_DIVISION_TOTAL) { - return Rational(numerator.euclidianDivideQuotient(denominator)); - } else { - Assert(kind == kind::INTS_MODULUS_TOTAL); - return Rational(numerator.euclidianDivideRemainder(denominator)); - } - } - - default: - throw ModelException(term, "No model assignment."); - } -} - -Rational TheoryArithPrivate::deltaValueForTotalOrder() const{ - Rational min(2); - std::set relevantDeltaValues; - context::CDQueue::const_iterator qiter = d_diseqQueue.begin(); - context::CDQueue::const_iterator qiter_end = d_diseqQueue.end(); - - for(; qiter != qiter_end; ++qiter){ - ConstraintP curr = *qiter; - - const DeltaRational& rhsValue = curr->getValue(); - relevantDeltaValues.insert(rhsValue); - } - - Theory::shared_terms_iterator shared_iter = d_containing.shared_terms_begin(); - Theory::shared_terms_iterator shared_end = d_containing.shared_terms_end(); - for(; shared_iter != shared_end; ++shared_iter){ - Node sharedCurr = *shared_iter; - - // ModelException is fatal as this point. Don't catch! - // DeltaRationalException is fatal as this point. Don't catch! - DeltaRational val = getDeltaValue(sharedCurr); - relevantDeltaValues.insert(val); - } - - for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar v = *vi; - const DeltaRational& value = d_partialModel.getAssignment(v); - relevantDeltaValues.insert(value); - if( d_partialModel.hasLowerBound(v)){ - const DeltaRational& lb = d_partialModel.getLowerBound(v); - relevantDeltaValues.insert(lb); - } - if( d_partialModel.hasUpperBound(v)){ - const DeltaRational& ub = d_partialModel.getUpperBound(v); - relevantDeltaValues.insert(ub); - } - } - - if(relevantDeltaValues.size() >= 2){ - std::set::const_iterator iter = relevantDeltaValues.begin(); - std::set::const_iterator iter_end = relevantDeltaValues.end(); - DeltaRational prev = *iter; - ++iter; - for(; iter != iter_end; ++iter){ - const DeltaRational& curr = *iter; - - Assert(prev < curr); - - DeltaRational::seperatingDelta(min, prev, curr); - prev = curr; - } - } - - Assert(min.sgn() > 0); - Rational belowMin = min/Rational(2); - return belowMin; -} - -void TheoryArithPrivate::collectModelValues(const std::set& termSet, - std::map& arithModel) -{ - AlwaysAssert(d_qflraStatus == Result::SAT); - - if(TraceIsOn("arith::collectModelInfo")){ - debugPrintFacts(); - } - - Trace("arith::collectModelInfo") << "collectModelInfo() begin " << endl; - - // Delta lasts at least the duration of the function call - const Rational& delta = d_partialModel.getDelta(); - std::unordered_set shared = d_containing.currentlySharedTerms(); - - // TODO: - // This is not very good for user push/pop.... - // Revisit when implementing push/pop - NodeManager* nm = NodeManager::currentNM(); - for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar v = *vi; - - if(!isAuxiliaryVariable(v)){ - Node term = d_partialModel.asNode(v); - - if((theoryOf(term) == THEORY_ARITH || shared.find(term) != shared.end()) - && termSet.find(term) != termSet.end()){ - - const DeltaRational& mod = d_partialModel.getAssignment(v); - Rational qmodel = mod.substituteDelta(delta); - - Node qNode = nm->mkConstRealOrInt(term.getType(), qmodel); - Trace("arith::collectModelInfo") << "m->assertEquality(" << term << ", " << qmodel << ", true)" << endl; - // Add to the map - arithModel[term] = qNode; - }else{ - Trace("arith::collectModelInfo") << "Skipping m->assertEquality(" << term << ", true)" << endl; - - } - } - } - - // Iterate over equivalence classes in LinearEqualityModule - // const eq::EqualityEngine& ee = d_congruenceManager.getEqualityEngine(); - // m->assertEqualityEngine(&ee); - - Trace("arith::collectModelInfo") << "collectModelInfo() end " << endl; -} - -bool TheoryArithPrivate::safeToReset() const { - Assert(!d_tableauSizeHasBeenModified); - Assert(d_errorSet.noSignals()); - - ErrorSet::error_iterator error_iter = d_errorSet.errorBegin(); - ErrorSet::error_iterator error_end = d_errorSet.errorEnd(); - for(; error_iter != error_end; ++error_iter){ - ArithVar basic = *error_iter; - if(!d_smallTableauCopy.isBasic(basic)){ - return false; - } - } - - return true; -} - -void TheoryArithPrivate::notifyRestart(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_restartTimer); - - if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - - ++d_restartsCounter; - d_solveIntMaybeHelp = 0; - d_solveIntAttempts = 0; -} - -bool TheoryArithPrivate::entireStateIsConsistent(const string& s){ - bool result = true; - for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar var = *vi; - //ArithVar var = d_partialModel.asArithVar(*i); - if(!d_partialModel.assignmentIsConsistent(var)){ - d_partialModel.printModel(var); - warning() << s << ":" << "Assignment is not consistent for " << var << d_partialModel.asNode(var); - if(d_tableau.isBasic(var)){ - warning() << " (basic)"; - } - warning() << std::endl; - result = false; - }else if(d_partialModel.isInteger(var) && !d_partialModel.integralAssignment(var)){ - d_partialModel.printModel(var); - warning() << s << ":" << "Assignment is not integer for integer variable " << var << d_partialModel.asNode(var); - if(d_tableau.isBasic(var)){ - warning() << " (basic)"; - } - warning() << std::endl; - result = false; - } - } - return result; -} - -bool TheoryArithPrivate::unenqueuedVariablesAreConsistent(){ - bool result = true; - for(var_iterator vi = var_begin(), vend = var_end(); vi != vend; ++vi){ - ArithVar var = *vi; - if(!d_partialModel.assignmentIsConsistent(var)){ - if(!d_errorSet.inError(var)){ - - d_partialModel.printModel(var); - warning() << "Unenqueued var is not consistent for " << var << d_partialModel.asNode(var); - if(d_tableau.isBasic(var)){ - warning() << " (basic)"; - } - warning() << std::endl; - result = false; - } else if(TraceIsOn("arith::consistency::initial")){ - d_partialModel.printModel(var); - warning() << "Initial var is not consistent for " << var << d_partialModel.asNode(var); - if(d_tableau.isBasic(var)){ - warning() << " (basic)"; - } - warning() << std::endl; - } - } - } - return result; -} - -void TheoryArithPrivate::presolve(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_presolveTime); - - d_statistics.d_initialTableauSize = d_tableau.size(); - - if(TraceIsOn("paranoid:check_tableau")){ d_linEq.debugCheckTableau(); } - - if(TraceIsOn("arith::presolve")) { - Trace("arith::presolve") << "TheoryArithPrivate::presolve" << endl; - } - - vector lemmas; - if (!options().base.incrementalSolving) - { - switch (options().arith.arithUnateLemmaMode) - { - case options::ArithUnateLemmaMode::NO: break; - case options::ArithUnateLemmaMode::INEQUALITY: - d_constraintDatabase.outputUnateInequalityLemmas(lemmas); - break; - case options::ArithUnateLemmaMode::EQUALITY: - d_constraintDatabase.outputUnateEqualityLemmas(lemmas); - break; - case options::ArithUnateLemmaMode::ALL: - d_constraintDatabase.outputUnateInequalityLemmas(lemmas); - d_constraintDatabase.outputUnateEqualityLemmas(lemmas); - break; - default: Unhandled() << options().arith.arithUnateLemmaMode; - } - } - - vector::const_iterator i = lemmas.begin(), i_end = lemmas.end(); - for(; i != i_end; ++i){ - TrustNode lem = *i; - Trace("arith::oldprop") << " lemma lemma duck " <getValue()); - Assert( - !upperBound - || d_partialModel.lessThanUpperBound(basic, bestImplied->getValue())); - - Assert(upperBound || bound >= bestImplied->getValue()); - Assert(upperBound - || d_partialModel.greaterThanLowerBound(basic, - bestImplied->getValue())); - //slightly changed - - // ConstraintP c = d_constraintDatabase.lookup(bestImplied); - // Assert(c != NullConstraint); - - bool assertedToTheTheory = bestImplied->assertedToTheTheory(); - bool canBePropagated = bestImplied->canBePropagated(); - bool hasProof = bestImplied->hasProof(); - - Trace("arith::prop") << "arith::prop" << basic - << " " << assertedToTheTheory - << " " << canBePropagated - << " " << hasProof - << endl; - - if(bestImplied->negationHasProof()){ - warning() << "the negation of " << bestImplied << " : " << std::endl - << "has proof " << bestImplied->getNegation() << std::endl - << bestImplied->getNegation()->externalExplainByAssertions() - << std::endl; - } - - if(!assertedToTheTheory && canBePropagated && !hasProof ){ - d_linEq.propagateBasicFromRow(bestImplied, options().smt.produceProofs); - // I think this can be skipped if canBePropagated is true - //d_learnedBounds.push(bestImplied); - if(TraceIsOn("arith::prop")){ - Trace("arith::prop") << "success " << bestImplied << endl; - d_partialModel.printModel(basic, Trace("arith::prop")); - } - return true; - } - if(TraceIsOn("arith::prop")){ - Trace("arith::prop") << "failed " << basic - << " " << bound - << " " << assertedToTheTheory - << " " << canBePropagated - << " " << hasProof << endl; - d_partialModel.printModel(basic, Trace("arith::prop")); - } - } - }else if(TraceIsOn("arith::prop")){ - Trace("arith::prop") << "false " << bound << " "; - d_partialModel.printModel(basic, Trace("arith::prop")); - } - return false; -} - -void TheoryArithPrivate::propagateCandidate(ArithVar basic){ - bool success = false; - RowIndex ridx = d_tableau.basicToRowIndex(basic); - - bool tryLowerBound = - d_partialModel.strictlyAboveLowerBound(basic) && - d_linEq.rowLacksBound(ridx, false, basic) == NULL; - - bool tryUpperBound = - d_partialModel.strictlyBelowUpperBound(basic) && - d_linEq.rowLacksBound(ridx, true, basic) == NULL; - - if(tryLowerBound){ - success |= propagateCandidateLowerBound(basic); - } - if(tryUpperBound){ - success |= propagateCandidateUpperBound(basic); - } - if(success){ - ++d_statistics.d_boundPropagations; - } -} - -void TheoryArithPrivate::propagateCandidates(){ - TimerStat::CodeTimer codeTimer(d_statistics.d_boundComputationTime); - - Trace("arith::prop") << "propagateCandidates begin" << endl; - - Assert(d_candidateBasics.empty()); - - if(d_updatedBounds.empty()){ return; } - - DenseSet::const_iterator i = d_updatedBounds.begin(); - DenseSet::const_iterator end = d_updatedBounds.end(); - for(; i != end; ++i){ - ArithVar var = *i; - if (d_tableau.isBasic(var) - && d_tableau.basicRowLength(var) - <= options().arith.arithPropagateMaxLength) - { - d_candidateBasics.softAdd(var); - } - else - { - Tableau::ColIterator basicIter = d_tableau.colIterator(var); - for(; !basicIter.atEnd(); ++basicIter){ - const Tableau::Entry& entry = *basicIter; - RowIndex ridx = entry.getRowIndex(); - ArithVar rowVar = d_tableau.rowIndexToBasic(ridx); - Assert(entry.getColVar() == var); - Assert(d_tableau.isBasic(rowVar)); - if (d_tableau.getRowLength(ridx) - <= options().arith.arithPropagateMaxLength) - { - d_candidateBasics.softAdd(rowVar); - } - } - } - } - d_updatedBounds.purge(); - - while(!d_candidateBasics.empty()){ - ArithVar candidate = d_candidateBasics.back(); - d_candidateBasics.pop_back(); - Assert(d_tableau.isBasic(candidate)); - propagateCandidate(candidate); - } - Trace("arith::prop") << "propagateCandidates end" << endl << endl << endl; -} - -void TheoryArithPrivate::propagateCandidatesNew(){ - /* Four criteria must be met for progagation on a variable to happen using a row: - * 0: A new bound has to have been added to the row. - * 1: The hasBoundsCount for the row must be "full" or be full minus one variable - * (This is O(1) to check, but requires book keeping.) - * 2: The current assignment must be strictly smaller/greater than the current bound. - * assign(x) < upper(x) - * (This is O(1) to compute.) - * 3: There is a bound that is strictly smaller/greater than the current assignment. - * assign(x) < c for some x <= c literal - * (This is O(log n) to compute.) - * 4: The implied bound on x is strictly smaller/greater than the current bound. - * (This is O(n) to compute.) - */ - - TimerStat::CodeTimer codeTimer(d_statistics.d_boundComputationTime); - Trace("arith::prop") << "propagateCandidatesNew begin" << endl; - - Assert(d_qflraStatus == Result::SAT); - if(d_updatedBounds.empty()){ return; } - dumpUpdatedBoundsToRows(); - Assert(d_updatedBounds.empty()); - - if(!d_candidateRows.empty()){ - UpdateTrackingCallback utcb(&d_linEq); - d_partialModel.processBoundsQueue(utcb); - } - - while(!d_candidateRows.empty()){ - RowIndex candidate = d_candidateRows.back(); - d_candidateRows.pop_back(); - propagateCandidateRow(candidate); - } - Trace("arith::prop") << "propagateCandidatesNew end" << endl << endl << endl; -} - -bool TheoryArithPrivate::propagateMightSucceed(ArithVar v, bool ub) const{ - int cmp = ub ? d_partialModel.cmpAssignmentUpperBound(v) - : d_partialModel.cmpAssignmentLowerBound(v); - bool hasSlack = ub ? cmp < 0 : cmp > 0; - if(hasSlack){ - ConstraintType t = ub ? UpperBound : LowerBound; - const DeltaRational& a = d_partialModel.getAssignment(v); - - if(isInteger(v) && !a.isIntegral()){ - return true; - } - - ConstraintP strongestPossible = d_constraintDatabase.getBestImpliedBound(v, t, a); - if(strongestPossible == NullConstraint){ - return false; - }else{ - bool assertedToTheTheory = strongestPossible->assertedToTheTheory(); - bool canBePropagated = strongestPossible->canBePropagated(); - bool hasProof = strongestPossible->hasProof(); - - return !assertedToTheTheory && canBePropagated && !hasProof; - } - }else{ - return false; - } -} - -bool TheoryArithPrivate::attemptSingleton(RowIndex ridx, bool rowUp){ - Trace("arith::prop") << " attemptSingleton" << ridx; - - const Tableau::Entry* ep; - ep = d_linEq.rowLacksBound(ridx, rowUp, ARITHVAR_SENTINEL); - Assert(ep != NULL); - - ArithVar v = ep->getColVar(); - const Rational& coeff = ep->getCoefficient(); - - // 0 = c * v + \sum rest - // Suppose rowUp - // - c * v = \sum rest \leq D - // if c > 0, v \geq -D/c so !vUp - // if c < 0, v \leq -D/c so vUp - // Suppose not rowUp - // - c * v = \sum rest \geq D - // if c > 0, v \leq -D/c so vUp - // if c < 0, v \geq -D/c so !vUp - bool vUp = (rowUp == ( coeff.sgn() < 0)); - - Trace("arith::prop") << " " << rowUp << " " << v << " " << coeff << " " << vUp << endl; - Trace("arith::prop") << " " << propagateMightSucceed(v, vUp) << endl; - - if(propagateMightSucceed(v, vUp)){ - DeltaRational dr = d_linEq.computeRowBound(ridx, rowUp, v); - DeltaRational bound = dr / (- coeff); - return tryToPropagate(ridx, rowUp, v, vUp, bound); - } - return false; -} - -bool TheoryArithPrivate::attemptFull(RowIndex ridx, bool rowUp){ - Trace("arith::prop") << " attemptFull" << ridx << endl; - - vector candidates; - - for(Tableau::RowIterator i = d_tableau.ridRowIterator(ridx); !i.atEnd(); ++i){ - const Tableau::Entry& e =*i; - const Rational& c = e.getCoefficient(); - ArithVar v = e.getColVar(); - bool vUp = (rowUp == (c.sgn() < 0)); - if(propagateMightSucceed(v, vUp)){ - candidates.push_back(&e); - } - } - if(candidates.empty()){ return false; } - - const DeltaRational slack = - d_linEq.computeRowBound(ridx, rowUp, ARITHVAR_SENTINEL); - bool any = false; - vector::const_iterator i, iend; - for(i = candidates.begin(), iend = candidates.end(); i != iend; ++i){ - const Tableau::Entry* ep = *i; - const Rational& c = ep->getCoefficient(); - ArithVar v = ep->getColVar(); - - // See the comment for attemptSingleton() - bool activeUp = (rowUp == (c.sgn() > 0)); - bool vUb = (rowUp == (c.sgn() < 0)); - - const DeltaRational& activeBound = activeUp ? - d_partialModel.getUpperBound(v): - d_partialModel.getLowerBound(v); - - DeltaRational contribution = activeBound * c; - DeltaRational impliedBound = (slack - contribution)/(-c); - - bool success = tryToPropagate(ridx, rowUp, v, vUb, impliedBound); - any |= success; - } - return any; -} - -bool TheoryArithPrivate::tryToPropagate(RowIndex ridx, bool rowUp, ArithVar v, bool vUb, const DeltaRational& bound){ - - bool weaker = vUb ? d_partialModel.strictlyLessThanUpperBound(v, bound): - d_partialModel.strictlyGreaterThanLowerBound(v, bound); - if(weaker){ - ConstraintType t = vUb ? UpperBound : LowerBound; - - ConstraintP implied = d_constraintDatabase.getBestImpliedBound(v, t, bound); - if(implied != NullConstraint){ - return rowImplicationCanBeApplied(ridx, rowUp, implied); - } - } - return false; -} - -Node flattenImplication(Node imp){ - NodeBuilder nb(kind::OR); - std::unordered_set included; - Node left = imp[0]; - Node right = imp[1]; - - if(left.getKind() == kind::AND){ - for(Node::iterator i = left.begin(), iend = left.end(); i != iend; ++i) { - if (!included.count((*i).negate())) - { - nb << (*i).negate(); - included.insert((*i).negate()); - } - } - }else{ - if (!included.count(left.negate())) - { - nb << left.negate(); - included.insert(left.negate()); - } - } - - if(right.getKind() == kind::OR){ - for(Node::iterator i = right.begin(), iend = right.end(); i != iend; ++i) { - if (!included.count(*i)) - { - nb << *i; - included.insert(*i); - } - } - }else{ - if (!included.count(right)) - { - nb << right; - included.insert(right); - } - } - - return nb; -} - -bool TheoryArithPrivate::rowImplicationCanBeApplied(RowIndex ridx, bool rowUp, ConstraintP implied){ - Assert(implied != NullConstraint); - ArithVar v = implied->getVariable(); - - bool assertedToTheTheory = implied->assertedToTheTheory(); - bool canBePropagated = implied->canBePropagated(); - bool hasProof = implied->hasProof(); - - Trace("arith::prop") << "arith::prop" << v - << " " << assertedToTheTheory - << " " << canBePropagated - << " " << hasProof - << endl; - - - if( !assertedToTheTheory && canBePropagated && !hasProof ){ - ConstraintCPVec explain; - if (options().smt.produceProofs) - { - d_farkasBuffer.clear(); - } - RationalVectorP coeffs = - options().smt.produceProofs ? &d_farkasBuffer : nullptr; - - // After invoking `propegateRow`: - // * coeffs[0] is for implied - // * coeffs[i+1] is for explain[i] - d_linEq.propagateRow(explain, ridx, rowUp, implied, coeffs); - if (d_tableau.getRowLength(ridx) <= options().arith.arithPropAsLemmaLength) - { - if (TraceIsOn("arith::prop::pf")) { - for (const auto & constraint : explain) { - Assert(constraint->hasProof()); - constraint->printProofTree(Trace("arith::prop::pf")); - } - } - Node implication = implied->externalImplication(explain); - Node clause = flattenImplication(implication); - std::shared_ptr clausePf{nullptr}; - - if (isProofEnabled()) - { - // We can prove this lemma from Farkas... - std::vector> conflictPfs; - Node pfLit = implied->getNegation()->getProofLiteral(); - TypeNode type = pfLit[0].getType(); - // Assume the negated getLiteral version of the implied constaint - // then rewrite it into proof normal form. - conflictPfs.push_back( - d_pnm->mkNode(PfRule::MACRO_SR_PRED_TRANSFORM, - {d_pnm->mkAssume(implied->getLiteral().negate())}, - {pfLit})); - // Add the explaination proofs. - for (const auto constraint : explain) - { - NodeBuilder nb; - conflictPfs.push_back(constraint->externalExplainByAssertions(nb)); - } - // Collect the farkas coefficients, as nodes. - std::vector farkasCoefficients; - farkasCoefficients.reserve(coeffs->size()); - auto nm = NodeManager::currentNM(); - std::transform(coeffs->begin(), - coeffs->end(), - std::back_inserter(farkasCoefficients), - [nm, type](const Rational& r) { - return nm->mkConstRealOrInt(type, r); - }); - - // Prove bottom. - auto sumPf = d_pnm->mkNode( - PfRule::MACRO_ARITH_SCALE_SUM_UB, conflictPfs, farkasCoefficients); - auto botPf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {sumPf}, {nm->mkConst(false)}); - - // Prove the conflict - std::vector assumptions; - assumptions.reserve(clause.getNumChildren()); - std::transform(clause.begin(), - clause.end(), - std::back_inserter(assumptions), - [](TNode r) { return r.negate(); }); - auto notAndNotPf = d_pnm->mkScope(botPf, assumptions); - - // Convert it to a clause - auto orNotNotPf = d_pnm->mkNode(PfRule::NOT_AND, {notAndNotPf}, {}); - clausePf = d_pnm->mkNode( - PfRule::MACRO_SR_PRED_TRANSFORM, {orNotNotPf}, {clause}); - - // Output it - TrustNode trustedClause = d_pfGen->mkTrustNode(clause, clausePf); - outputTrustedLemma(trustedClause, InferenceId::ARITH_ROW_IMPL); - } - else - { - outputLemma(clause, InferenceId::ARITH_ROW_IMPL); - } - } - else - { - Assert(!implied->negationHasProof()); - implied->impliedByFarkas(explain, coeffs, false); - implied->tryToPropagate(); - } - return true; - } - - if(TraceIsOn("arith::prop")){ - Trace("arith::prop") - << "failed " << v << " " << assertedToTheTheory << " " - << canBePropagated << " " << hasProof << " " << implied << endl; - d_partialModel.printModel(v, Trace("arith::prop")); - } - return false; -} - -bool TheoryArithPrivate::propagateCandidateRow(RowIndex ridx){ - BoundCounts hasCount = d_linEq.hasBoundCount(ridx); - uint32_t rowLength = d_tableau.getRowLength(ridx); - - bool success = false; - - Trace("arith::prop") << "propagateCandidateRow attempt " << rowLength << " " - << hasCount << endl; - - if (rowLength >= options().arith.arithPropagateMaxLength - && Random::getRandom().pickWithProb( - 1.0 - double(options().arith.arithPropagateMaxLength) / rowLength)) - { - return false; - } - - if(hasCount.lowerBoundCount() == rowLength){ - success |= attemptFull(ridx, false); - }else if(hasCount.lowerBoundCount() + 1 == rowLength){ - success |= attemptSingleton(ridx, false); - } - - if(hasCount.upperBoundCount() == rowLength){ - success |= attemptFull(ridx, true); - }else if(hasCount.upperBoundCount() + 1 == rowLength){ - success |= attemptSingleton(ridx, true); - } - return success; -} - -void TheoryArithPrivate::dumpUpdatedBoundsToRows(){ - Assert(d_candidateRows.empty()); - DenseSet::const_iterator i = d_updatedBounds.begin(); - DenseSet::const_iterator end = d_updatedBounds.end(); - for(; i != end; ++i){ - ArithVar var = *i; - if(d_tableau.isBasic(var)){ - RowIndex ridx = d_tableau.basicToRowIndex(var); - d_candidateRows.softAdd(ridx); - }else{ - Tableau::ColIterator basicIter = d_tableau.colIterator(var); - for(; !basicIter.atEnd(); ++basicIter){ - const Tableau::Entry& entry = *basicIter; - RowIndex ridx = entry.getRowIndex(); - d_candidateRows.softAdd(ridx); - } - } - } - d_updatedBounds.purge(); -} - -const BoundsInfo& TheoryArithPrivate::boundsInfo(ArithVar basic) const{ - RowIndex ridx = d_tableau.basicToRowIndex(basic); - return d_rowTracking[ridx]; -} - -std::pair TheoryArithPrivate::entailmentCheck(TNode lit, const ArithEntailmentCheckParameters& params, ArithEntailmentCheckSideEffects& out){ - using namespace inferbounds; - - // l k r - // diff : (l - r) k 0 - Trace("arith::entailCheck") << "TheoryArithPrivate::entailmentCheck(" << lit << ")"<< endl; - Kind k; - int primDir; - Rational lm, rm, dm; - Node lp, rp, dp; - DeltaRational sep; - bool successful = decomposeLiteral(lit, k, primDir, lm, lp, rm, rp, dm, dp, sep); - if(!successful) { return make_pair(false, Node::null()); } - - if (dp.isConst()) - { - Node eval = rewrite(lit); - Assert(eval.getKind() == kind::CONST_BOOLEAN); - // if true, true is an acceptable explaination - // if false, the node is uninterpreted and eval can be forgotten - return make_pair(eval.getConst(), eval); - } - Assert(dm != Rational(0)); - Assert(primDir == 1 || primDir == -1); - - int negPrim = -primDir; - - int secDir = (k == EQUAL || k == DISTINCT) ? negPrim: 0; - int negSecDir = (k == EQUAL || k == DISTINCT) ? primDir: 0; - - // primDir*[lm*( lp )] k primDir*[ [rm*( rp )] + sep ] - // primDir*[lm*( lp ) - rm*( rp ) ] k primDir*sep - // primDir*[dm * dp] k primDir*sep - - std::pair bestPrimLeft, bestNegPrimRight, bestPrimDiff, tmp; - std::pair bestSecLeft, bestNegSecRight, bestSecDiff; - bestPrimLeft.first = Node::null(); bestNegPrimRight.first = Node::null(); bestPrimDiff.first = Node::null(); - bestSecLeft.first = Node::null(); bestNegSecRight.first = Node::null(); bestSecDiff.first = Node::null(); - - - - ArithEntailmentCheckParameters::const_iterator alg, alg_end; - for( alg = params.begin(), alg_end = params.end(); alg != alg_end; ++alg ){ - const inferbounds::InferBoundAlgorithm& ibalg = *alg; - - Trace("arith::entailCheck") << "entailmentCheck trying " << (inferbounds::Algorithms) ibalg.getAlgorithm() << endl; - switch(ibalg.getAlgorithm()){ - case inferbounds::None: - break; - case inferbounds::Lookup: - case inferbounds::RowSum: - { - typedef void (TheoryArithPrivate::*EntailmentCheckFunc)(std::pair&, int, TNode) const; - - EntailmentCheckFunc ecfunc = - (ibalg.getAlgorithm() == inferbounds::Lookup) - ? (&TheoryArithPrivate::entailmentCheckBoundLookup) - : (&TheoryArithPrivate::entailmentCheckRowSum); - - (*this.*ecfunc)(tmp, primDir * lm.sgn(), lp); - setToMin(primDir * lm.sgn(), bestPrimLeft, tmp); - - (*this.*ecfunc)(tmp, negPrim * rm.sgn(), rp); - setToMin(negPrim * rm.sgn(), bestNegPrimRight, tmp); - - (*this.*ecfunc)(tmp, secDir * lm.sgn(), lp); - setToMin(secDir * lm.sgn(), bestSecLeft, tmp); - - (*this.*ecfunc)(tmp, negSecDir * rm.sgn(), rp); - setToMin(negSecDir * rm.sgn(), bestNegSecRight, tmp); - - (*this.*ecfunc)(tmp, primDir * dm.sgn(), dp); - setToMin(primDir * dm.sgn(), bestPrimDiff, tmp); - - (*this.*ecfunc)(tmp, secDir * dm.sgn(), dp); - setToMin(secDir * dm.sgn(), bestSecDiff, tmp); - } - break; - default: - Unhandled(); - } - - // turn bounds on prim * left and -prim * right into bounds on prim * diff - if(!bestPrimLeft.first.isNull() && !bestNegPrimRight.first.isNull()){ - // primDir*lm* lp <= primDir*lm*L - // -primDir*rm* rp <= -primDir*rm*R - // primDir*lm* lp -primDir*rm* rp <= primDir*lm*L - primDir*rm*R - // primDir [lm* lp -rm* rp] <= primDir[lm*L - *rm*R] - // primDir [dm * dp] <= primDir[lm*L - *rm*R] - // primDir [dm * dp] <= primDir * dm * ([lm*L - *rm*R]/dm) - tmp.second = ((bestPrimLeft.second * lm) - (bestNegPrimRight.second * rm)) / dm; - tmp.first = (bestPrimLeft.first).andNode(bestNegPrimRight.first); - setToMin(primDir, bestPrimDiff, tmp); - } - - // turn bounds on sec * left and sec * right into bounds on sec * diff - if(secDir != 0 && !bestSecLeft.first.isNull() && !bestNegSecRight.first.isNull()){ - // secDir*lm* lp <= secDir*lm*L - // -secDir*rm* rp <= -secDir*rm*R - // secDir*lm* lp -secDir*rm* rp <= secDir*lm*L - secDir*rm*R - // secDir [lm* lp -rm* rp] <= secDir[lm*L - *rm*R] - // secDir [dm * dp] <= secDir[lm*L - *rm*R] - // secDir [dm * dp] <= secDir * dm * ([lm*L - *rm*R]/dm) - tmp.second = ((bestSecLeft.second * lm) - (bestNegSecRight.second * rm)) / dm; - tmp.first = (bestSecLeft.first).andNode(bestNegSecRight.first); - setToMin(secDir, bestSecDiff, tmp); - } - - switch(k){ - case LEQ: - if(!bestPrimDiff.first.isNull()){ - DeltaRational d = (bestPrimDiff.second * dm); - if((primDir > 0 && d <= sep) || (primDir < 0 && d >= sep) ){ - Trace("arith::entailCheck") << "entailmentCheck found " - << primDir << "*" << dm << "*(" << dp<<")" - << " <= " << primDir << "*" << dm << "*" << bestPrimDiff.second - << " <= " << primDir << "*" << sep << endl - << " by " << bestPrimDiff.first << endl; - Assert(bestPrimDiff.second * (Rational(primDir) * dm) - <= (sep * Rational(primDir))); - return make_pair(true, bestPrimDiff.first); - } - } - break; - case EQUAL: - if(!bestPrimDiff.first.isNull() && !bestSecDiff.first.isNull()){ - // Is primDir [dm * dp] == primDir * sep entailed? - // Iff [dm * dp] == sep entailed? - // Iff dp == sep / dm entailed? - // Iff dp <= sep / dm and dp >= sep / dm entailed? - - // primDir [dm * dp] <= primDir * dm * U - // secDir [dm * dp] <= secDir * dm * L - - // Suppose primDir * dm > 0 - // then secDir * dm < 0 - // dp >= (secDir * L) / secDir * dm - // dp >= (primDir * L) / primDir * dm - // - // dp <= U / dm - // dp >= L / dm - // dp == sep / dm entailed iff U == L == sep - // Suppose primDir * dm < 0 - // then secDir * dm > 0 - // dp >= U / dm - // dp <= L / dm - // dp == sep / dm entailed iff U == L == sep - if(bestPrimDiff.second == bestSecDiff.second){ - if(bestPrimDiff.second == sep){ - return make_pair(true, (bestPrimDiff.first).andNode(bestSecDiff.first)); - } - } - } - // intentionally fall through to DISTINCT case! - // entailments of negations are eager exit cases for EQUAL - CVC5_FALLTHROUGH; - case DISTINCT: - if(!bestPrimDiff.first.isNull()){ - // primDir [dm * dp] <= primDir * dm * U < primDir * sep - if((primDir > 0 && (bestPrimDiff.second * dm < sep)) || - (primDir < 0 && (bestPrimDiff.second * dm > sep))){ - // entailment of negation - if(k == DISTINCT){ - return make_pair(true, bestPrimDiff.first); - }else{ - Assert(k == EQUAL); - return make_pair(false, Node::null()); - } - } - } - if(!bestSecDiff.first.isNull()){ - // If primDir [dm * dp] > primDir * sep, then this is not entailed. - // If primDir [dm * dp] >= primDir * dm * L > primDir * sep - // -primDir * dm * L < -primDir * sep - // secDir * dm * L < secDir * sep - if((secDir > 0 && (bestSecDiff.second * dm < sep)) || - (secDir < 0 && (bestSecDiff.second * dm > sep))){ - if(k == DISTINCT){ - return make_pair(true, bestSecDiff.first); - }else{ - Assert(k == EQUAL); - return make_pair(false, Node::null()); - } - } - } - - break; - default: - Unreachable(); - break; - } - } - return make_pair(false, Node::null()); -} - -bool TheoryArithPrivate::decomposeTerm(Node t, - Rational& m, - Node& p, - Rational& c) -{ - if(!Polynomial::isMember(t)){ - return false; - } - - // TODO Speed up - preprocessing::util::ContainsTermITEVisitor ctv; - if(ctv.containsTermITE(t)){ - return false; - } - - Polynomial poly = Polynomial::parsePolynomial(t); - if(poly.isConstant()){ - c = poly.getHead().getConstant().getValue(); - p = mkRationalNode(Rational(0)); - m = Rational(1); - return true; - }else if(poly.containsConstant()){ - c = poly.getHead().getConstant().getValue(); - poly = poly.getTail(); - }else{ - c = Rational(0); - } - Assert(!poly.isConstant()); - Assert(!poly.containsConstant()); - - const bool intVars = poly.allIntegralVariables(); - - if(intVars){ - m = Rational(1); - if(!poly.isIntegral()){ - Integer denom = poly.denominatorLCM(); - m /= denom; - poly = poly * denom; - } - Integer g = poly.gcd(); - m *= g; - poly = poly * Rational(1,g); - Assert(poly.isIntegral()); - }else{ - Assert(!intVars); - m = poly.getHead().getConstant().getValue(); - poly = poly * m.inverse(); - Assert(poly.leadingCoefficientIsAbsOne()); - } - p = poly.getNode(); - return true; -} - -void TheoryArithPrivate::setToMin(int sgn, std::pair& min, const std::pair& e){ - if(sgn != 0){ - if(min.first.isNull() && !e.first.isNull()){ - min = e; - }else if(!min.first.isNull() && !e.first.isNull()){ - if(sgn > 0 && min.second > e.second){ - min = e; - }else if(sgn < 0 && min.second < e.second){ - min = e; - } - } - } -} - -// std::pair TheoryArithPrivate::entailmentUpperCheck(const Rational& lm, Node lp, const Rational& rm, Node rp, const DeltaRational& sep, const ArithEntailmentCheckParameters& params, ArithEntailmentCheckSideEffects& out){ - -// Rational negRM = -rm; -// Node diff = NodeManager::currentNM()->mkNode(MULT, mkRationalConstan(lm), lp) + (negRM * rp); - -// Rational diffm; -// Node diffp; -// decompose(diff, diffm, diffNode); - - -// std::pair bestUbLeft, bestLbRight, bestUbDiff, tmp; -// bestUbLeft = bestLbRight = bestUbDiff = make_pair(Node::Null(), DeltaRational()); - -// return make_pair(false, Node::null()); -// } - -/** - * Decomposes a literal into the form: - * dir*[lm*( lp )] k dir*[ [rm*( rp )] + sep ] - * dir*[dm* dp] k dir *sep - * dir is either 1 or -1 - */ -bool TheoryArithPrivate::decomposeLiteral(Node lit, Kind& k, int& dir, Rational& lm, Node& lp, Rational& rm, Node& rp, Rational& dm, Node& dp, DeltaRational& sep){ - bool negated = (lit.getKind() == kind::NOT); - TNode atom = negated ? lit[0] : lit; - - TNode left = atom[0]; - TNode right = atom[1]; - - // left : lm*( lp ) + lc - // right: rm*( rp ) + rc - Rational lc, rc; - bool success = decomposeTerm(rewrite(left), lm, lp, lc); - if(!success){ return false; } - success = decomposeTerm(rewrite(right), rm, rp, rc); - if(!success){ return false; } - - Node diff = rewrite(NodeManager::currentNM()->mkNode(kind::SUB, left, right)); - Rational dc; - success = decomposeTerm(diff, dm, dp, dc); - Assert(success); - - // reduce the kind of the to not include literals - // GT, NOT LEQ - // GEQ, NOT LT - // LT, NOT GEQ - // LEQ, NOT LT - Kind atomKind = atom.getKind(); - Kind normKind = negated ? negateKind(atomKind) : atomKind; - - if(normKind == GEQ || normKind == GT){ - dir = -1; - normKind = (normKind == GEQ) ? LEQ : LT; - }else{ - dir = 1; - } - - Trace("arith::decomp") << "arith::decomp " - << lit << "(" << normKind << "*" << dir << ")"<< endl - << " left:" << lc << " + " << lm << "*(" << lp << ") : " <& tmp, int sgn, TNode tp) const { - tmp.first = Node::null(); - if(sgn == 0){ return; } - - Assert(Polynomial::isMember(tp)); - if (tp.isConst()) - { - tmp.first = mkBoolNode(true); - tmp.second = DeltaRational(tp.getConst()); - } - else if (d_partialModel.hasArithVar(tp)) - { - Assert(!tp.isConst()); - ArithVar v = d_partialModel.asArithVar(tp); - Assert(v != ARITHVAR_SENTINEL); - ConstraintP c = (sgn > 0) - ? d_partialModel.getUpperBoundConstraint(v) - : d_partialModel.getLowerBoundConstraint(v); - if(c != NullConstraint){ - tmp.first = Constraint::externalExplainByAssertions({c}); - tmp.second = c->getValue(); - } - } -} - -void TheoryArithPrivate::entailmentCheckRowSum(std::pair& tmp, int sgn, TNode tp) const { - tmp.first = Node::null(); - if(sgn == 0){ return; } - if (tp.getKind() != ADD) - { - return; - } - Assert(Polynomial::isMember(tp)); - - tmp.second = DeltaRational(0); - NodeBuilder nb(kind::AND); - - Polynomial p = Polynomial::parsePolynomial(tp); - for(Polynomial::iterator i = p.begin(), iend = p.end(); i != iend; ++i) { - Monomial m = *i; - Node x = m.getVarList().getNode(); - if(d_partialModel.hasArithVar(x)){ - ArithVar v = d_partialModel.asArithVar(x); - const Rational& coeff = m.getConstant().getValue(); - int dir = sgn * coeff.sgn(); - ConstraintP c = (dir > 0) - ? d_partialModel.getUpperBoundConstraint(v) - : d_partialModel.getLowerBoundConstraint(v); - if(c != NullConstraint){ - tmp.second += c->getValue() * coeff; - c->externalExplainByAssertions(nb); - }else{ - //failed - return; - } - }else{ - // failed - return; - } - } - // success - tmp.first = nb; -} - -ArithProofRuleChecker* TheoryArithPrivate::getProofChecker() -{ - return &d_checker; -} - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/arith/theory_arith_private.h b/src/theory/arith/theory_arith_private.h deleted file mode 100644 index eab5669ee..000000000 --- a/src/theory/arith/theory_arith_private.h +++ /dev/null @@ -1,880 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Tim King, Andrew Reynolds, Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2022 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. - * **************************************************************************** - * - * [[ Add one-line brief description here ]] - * - * [[ Add lengthier description here ]] - * \todo document this file - */ - -#pragma once - -#include -#include - -#include "context/cdhashset.h" -#include "context/cdinsert_hashmap.h" -#include "context/cdlist.h" -#include "context/cdqueue.h" -#include "expr/kind.h" -#include "expr/node.h" -#include "expr/node_builder.h" -#include "proof/trust_node.h" -#include "theory/arith/arith_static_learner.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/arithvar.h" -#include "theory/arith/attempt_solution_simplex.h" -#include "theory/arith/branch_and_bound.h" -#include "theory/arith/congruence_manager.h" -#include "theory/arith/constraint.h" -#include "theory/arith/delta_rational.h" -#include "theory/arith/dio_solver.h" -#include "theory/arith/dual_simplex.h" -#include "theory/arith/error_set.h" -#include "theory/arith/fc_simplex.h" -#include "theory/arith/infer_bounds.h" -#include "theory/arith/linear_equality.h" -#include "theory/arith/matrix.h" -#include "theory/arith/normal_form.h" -#include "theory/arith/partial_model.h" -#include "theory/arith/proof_checker.h" -#include "theory/arith/soi_simplex.h" -#include "theory/arith/theory_arith.h" -#include "theory/valuation.h" -#include "util/dense_map.h" -#include "util/integer.h" -#include "util/rational.h" -#include "util/result.h" -#include "util/statistics_stats.h" - -namespace cvc5::internal { - -class EagerProofGenerator; - -namespace theory { - -class TheoryModel; - -namespace arith { - -class BranchCutInfo; -class TreeLog; -class ApproximateStatistics; - -class ArithEntailmentCheckParameters; -class ArithEntailmentCheckSideEffects; -namespace inferbounds { - class InferBoundAlgorithm; -} -class InferBoundsResult; - -/** - * Implementation of QF_LRA. - * Based upon: - * http://research.microsoft.com/en-us/um/people/leonardo/cav06.pdf - */ -class TheoryArithPrivate : protected EnvObj -{ - private: - static constexpr uint32_t RESET_START = 2; - - TheoryArith& d_containing; - - /** - * Whether we encountered non-linear arithmetic at any time during solving. - */ - bool d_foundNl; - - BoundInfoMap d_rowTracking; - /** Branch and bound utility */ - BranchAndBound& d_bab; - // For proofs - /** Manages the proof nodes of this theory. */ - ProofNodeManager* d_pnm; - /** Checks the proof rules of this theory. */ - ArithProofRuleChecker d_checker; - /** Stores proposition(node)/proof pairs. */ - std::unique_ptr d_pfGen; - - /** - * The constraint database associated with the theory. - * This must be declared before ArithPartialModel. - */ - ConstraintDatabase d_constraintDatabase; - - enum Result::Status d_qflraStatus; - // check() - // !done() -> d_qflraStatus = Unknown - // fullEffort(e) -> simplex returns either sat or unsat - // !fullEffort(e) -> simplex returns either sat, unsat or unknown - // if unknown, save the assignment - // if unknown, the simplex priority queue cannot be emptied - int d_unknownsInARow; - - bool d_replayedLemmas; - - /** - * This counter is false if nothing has been done since the last cut. - * This is used to break an infinite loop. - */ - bool d_hasDoneWorkSinceCut; - - /** Static learner. */ - ArithStaticLearner d_learner; - - //std::vector d_pool; -public: - void releaseArithVar(ArithVar v); - void signal(ArithVar v){ d_errorSet.signalVariable(v); } - - -private: - // t does not contain constants - void entailmentCheckBoundLookup(std::pair& tmp, int sgn, TNode tp) const; - void entailmentCheckRowSum(std::pair& tmp, int sgn, TNode tp) const; - - /** - * Infers either a new upper/lower bound on term in the real relaxation. - * Either: - * - term is malformed (see below) - * - a maximum/minimum is found with the result being a pair - * -- where - * -- term dr is implies by exp - * -- is <= if inferring an upper bound, >= otherwise - * -- exp is in terms of the assertions to the theory. - * - No upper or lower bound is inferrable in the real relaxation. - * -- Returns <0, Null()> - * - the maximum number of rounds was exhausted: - * -- Returns where v is the current feasible value of term - * - Threshold reached: - * -- If theshold != NULL, and a feasible value is found to exceed threshold - * -- Simplex stops and returns - */ - //std::pair inferBound(TNode term, bool lb, int maxRounds = -1, const DeltaRational* threshold = NULL); - -private: - static bool decomposeTerm(Node t, Rational& m, Node& p, Rational& c); - bool decomposeLiteral(Node lit, - Kind& k, - int& dir, - Rational& lm, - Node& lp, - Rational& rm, - Node& rp, - Rational& dm, - Node& dp, - DeltaRational& sep); - static void setToMin(int sgn, - std::pair& min, - const std::pair& e); - - typedef ArithVariables::var_iterator var_iterator; - var_iterator var_begin() const { return d_partialModel.var_begin(); } - var_iterator var_end() const { return d_partialModel.var_end(); } - - NodeSet d_setupNodes; -public: - bool isSetup(Node n) const { - return d_setupNodes.find(n) != d_setupNodes.end(); - } - void markSetup(Node n){ - Assert(!isSetup(n)); - d_setupNodes.insert(n); - } -private: - void setupVariable(const Variable& x); - void setupVariableList(const VarList& vl); - void setupPolynomial(const Polynomial& poly); -public: - void setupAtom(TNode atom); -private: - void cautiousSetupPolynomial(const Polynomial& p); - - /** - * A superset of all of the assertions that currently are not the literal for - * their constraint do not match constraint literals. Not just the witnesses. - */ - context::CDInsertHashMap - d_assertionsThatDoNotMatchTheirLiterals; - - /** Returns true if x is of type Integer. */ - inline bool isInteger(ArithVar x) const { - return d_partialModel.isInteger(x); - } - - - /** Returns true if the variable was initially introduced as an auxiliary variable. */ - inline bool isAuxiliaryVariable(ArithVar x) const{ - return d_partialModel.isAuxiliary(x); - } - - inline bool isIntegerInput(ArithVar x) const - { - return d_partialModel.isIntegerInput(x) - && d_preregisteredNodes.contains(d_partialModel.asNode(x)); - } - - /** - * On full effort checks (after determining LA(Q) satisfiability), we - * consider integer vars, but we make sure to do so fairly to avoid - * nontermination (although this isn't a guarantee). To do it fairly, - * we consider variables in round-robin fashion. This is the - * round-robin index. - */ - ArithVar d_nextIntegerCheckVar; - - /** - * Queue of Integer variables that are known to be equal to a constant. - */ - context::CDQueue d_constantIntegerVariables; - - Node callDioSolver(); - /** - * Produces lemmas of the form (or (>= f 0) (<= f 0)), - * where f is a plane that the diophantine solver is interested in. - * - * More precisely, produces lemmas of the form (or (>= lc -c) (<= lc -c)) - * where lc is a linear combination of variables, c is a constant, and lc + c - * is the plane. - */ - TrustNode dioCutting(); - - Comparison mkIntegerEqualityFromAssignment(ArithVar v); - - /** - * List of all of the disequalities asserted in the current context that are not known - * to be satisfied. - */ - context::CDQueue d_diseqQueue; - - /** - * Constraints that have yet to be processed by proagation work list. - * All of the elements have type of LowerBound, UpperBound, or - * Equality. - * - * This is empty at the beginning of every check call. - * - * If head()->getType() == LowerBound or UpperBound, - * then d_cPL[1] is the previous constraint in d_partialModel for the - * corresponding bound. - * If head()->getType() == Equality, - * then d_cPL[1] is the previous lowerBound in d_partialModel, - * and d_cPL[2] is the previous upperBound in d_partialModel. - */ - std::deque d_currentPropagationList; - - context::CDQueue d_learnedBounds; - - /** - * Contains all nodes that have been preregistered - */ - context::CDHashSet d_preregisteredNodes; - - /** - * Manages information about the assignment and upper and lower bounds on - * variables. - */ - ArithVariables d_partialModel; - - /** The set of variables in error in the partial model. */ - ErrorSet d_errorSet; - - /** - * The tableau for all of the constraints seen thus far in the system. - */ - Tableau d_tableau; - - /** - * Maintains the relationship between the PartialModel and the Tableau. - */ - LinearEqualityModule d_linEq; - - /** - * A Diophantine equation solver. Accesses the tableau and partial - * model (each in a read-only fashion). - */ - DioSolver d_diosolver; - - /** Counts the number of notifyRestart() calls to the theory. */ - uint32_t d_restartsCounter; - - /** - * Every number of restarts equal to s_TABLEAU_RESET_PERIOD, - * the density of the tableau, d, is computed. - * If d >= s_TABLEAU_RESET_DENSITY * d_initialDensity, the tableau - * is set to d_initialTableau. - */ - bool d_tableauSizeHasBeenModified; - double d_tableauResetDensity; - uint32_t d_tableauResetPeriod; - static constexpr uint32_t s_TABLEAU_RESET_INCREMENT = 5; - - /** This is only used by simplex at the moment. */ - context::CDList> d_conflicts; - - /** This is only used by simplex at the moment. */ - context::CDO d_blackBoxConflict; - /** For holding the proof of the above conflict node. */ - context::CDO> d_blackBoxConflictPf; - - bool isProofEnabled() const; - - public: - /** - * This adds the constraint a to the queue of conflicts in d_conflicts. - * Both a and ~a must have a proof. - */ - void raiseConflict(ConstraintCP a, InferenceId id); - - // inline void raiseConflict(const ConstraintCPVec& cv){ - // d_conflicts.push_back(cv); - // } - - // void raiseConflict(ConstraintCP a, ConstraintCP b); - // void raiseConflict(ConstraintCP a, ConstraintCP b, ConstraintCP c); - - /** This is a conflict that is magically known to hold. */ - void raiseBlackBoxConflict(Node bb, std::shared_ptr pf = nullptr); - /** - * Returns true iff a conflict has been raised. This method is public since - * it is needed by the ArithState class to know whether we are in conflict. - */ - bool anyConflict() const; - - private: - inline bool conflictQueueEmpty() const { - return d_conflicts.empty(); - } - - /** - * Outputs the contents of d_conflicts onto d_out. - * The conditions of anyConflict() must hold. - */ - void outputConflicts(); - - /** - * A copy of the tableau. - * This is equivalent to the original tableau if d_tableauSizeHasBeenModified - * is false. - * The set of basic and non-basic variables may differ from d_tableau. - */ - Tableau d_smallTableauCopy; - - /** - * Returns true if all of the basic variables in the simplex queue of - * basic variables that violate their bounds in the current tableau - * are basic in d_smallTableauCopy. - * - * d_tableauSizeHasBeenModified must be false when calling this. - * Simplex's priority queue must be in collection mode. - */ - bool safeToReset() const; - - /** This keeps track of difference equalities. Mostly for sharing. */ - ArithCongruenceManager d_congruenceManager; - context::CDO d_cmEnabled; - - /** This implements the Simplex decision procedure. */ - DualSimplexDecisionProcedure d_dualSimplex; - FCSimplexDecisionProcedure d_fcSimplex; - SumOfInfeasibilitiesSPD d_soiSimplex; - AttemptSolutionSDP d_attemptSolSimplex; - - bool solveRealRelaxation(Theory::Effort effortLevel); - - /* Returns true if this is heuristically a good time to try - * to solve the integers. - */ - bool attemptSolveInteger(Theory::Effort effortLevel, bool emmmittedLemmaOrSplit); - bool replayLemmas(ApproximateSimplex* approx); - void solveInteger(Theory::Effort effortLevel); - bool safeToCallApprox() const; - SimplexDecisionProcedure& selectSimplex(bool pass1); - SimplexDecisionProcedure* d_pass1SDP; - SimplexDecisionProcedure* d_otherSDP; - /* Sets d_qflraStatus */ - void importSolution(const ApproximateSimplex::Solution& solution); - bool solveRelaxationOrPanic(Theory::Effort effortLevel); - context::CDO d_lastContextIntegerAttempted; - bool replayLog(ApproximateSimplex* approx); - - class ModelException : public Exception { - public: - ModelException(TNode n, const char* msg); - ~ModelException() override; - }; - - /** - * Computes the delta rational value of a term from the current partial - * model. This returns the delta value assignment to the term if it is in the - * partial model. Otherwise, this is computed recursively for arithmetic terms - * from each subterm. - * - * This throws a DeltaRationalException if the value cannot be represented as - * a DeltaRational. This throws a ModelException if there is a term is not in - * the partial model and is not a theory of arithmetic term. - * - * precondition: The linear abstraction of the nodes must be satisfiable. - */ - DeltaRational getDeltaValue(TNode term) const - /* throw(DeltaRationalException, ModelException) */; - public: - TheoryArithPrivate(TheoryArith& containing, Env& env, BranchAndBound& bab); - ~TheoryArithPrivate(); - - //--------------------------------- initialization - /** - * Returns true if we need an equality engine, see - * Theory::needsEqualityEngine. - */ - bool needsEqualityEngine(EeSetupInfo& esi); - /** finish initialize */ - void finishInit(); - //--------------------------------- end initialization - - /** - * Does non-context dependent setup for a node connected to a theory. - */ - void preRegisterTerm(TNode n); - - void propagate(Theory::Effort e); - TrustNode explain(TNode n); - - Rational deltaValueForTotalOrder() const; - - bool collectModelInfo(TheoryModel* m); - /** - * Collect model values. This is the main method for extracting information - * about how to construct the model. This method relies on the caller for - * processing the map, which is done so that other modules (e.g. the - * non-linear extension) can modify arithModel before it is sent to the model. - * - * @param termSet The set of relevant terms - * @param arithModel Mapping from terms (of real type) to their values. The - * caller should assert equalities to the model for each entry in this map. - */ - void collectModelValues(const std::set& termSet, - std::map& arithModel); - - void shutdown(){ } - - void presolve(); - void notifyRestart(); - Theory::PPAssertStatus ppAssert(TrustNode tin, - TrustSubstitutionMap& outSubstitutions); - void ppStaticLearn(TNode in, NodeBuilder& learned); - - std::string identify() const { return std::string("TheoryArith"); } - - EqualityStatus getEqualityStatus(TNode a, TNode b); - - /** Called when n is notified as being a shared term with TheoryArith. */ - void notifySharedTerm(TNode n); - - Node getModelValue(TNode var); - - - std::pair entailmentCheck(TNode lit, const ArithEntailmentCheckParameters& params, ArithEntailmentCheckSideEffects& out); - - //--------------------------------- standard check - /** Pre-check, called before the fact queue of the theory is processed. */ - bool preCheck(Theory::Effort level); - /** Pre-notify fact. */ - void preNotifyFact(TNode atom, bool pol, TNode fact); - /** - * Post-check, called after the fact queue of the theory is processed. Returns - * true if a conflict or lemma was emitted. - */ - bool postCheck(Theory::Effort level); - //--------------------------------- end standard check - /** - * Found non-linear? This returns true if this solver ever encountered - * any non-linear terms that were unhandled. Note that this class is not - * responsible for handling non-linear arithmetic. If the owner of this - * class does not handle non-linear arithmetic in another way, then - * setIncomplete should be called on the output channel of TheoryArith. - */ - bool foundNonlinear() const; - - /** get the proof checker of this theory */ - ArithProofRuleChecker* getProofChecker(); - - private: - /** The constant zero. */ - DeltaRational d_DELTA_ZERO; - - /** propagates an arithvar */ - void propagateArithVar(bool upperbound, ArithVar var ); - - /** - * Using the simpleKind return the ArithVar associated with the assertion. - */ - ArithVar determineArithVar(const Polynomial& p) const; - ArithVar determineArithVar(TNode assertion) const; - - /** - * Splits the disequalities in d_diseq that are violated using lemmas on demand. - * returns true if any lemmas were issued. - * returns false if all disequalities are satisfied in the current model. - */ - bool splitDisequalities(); - - /** A Difference variable is known to be 0.*/ - void zeroDifferenceDetected(ArithVar x); - - - /** - * Looks for the next integer variable without an integer assignment in a - * round-robin fashion. Changes the value of d_nextIntegerCheckVar. - * - * This returns true if all integer variables have integer assignments. - * If this returns false, d_nextIntegerCheckVar does not have an integer - * assignment. - */ - bool hasIntegerModel(); - - /** - * Looks for through the variables starting at d_nextIntegerCheckVar - * for the first integer variable that is between its upper and lower bounds - * that has a non-integer assignment. - * - * If assumeBounds is true, skip the check that the variable is in bounds. - * - * If there is no such variable, returns ARITHVAR_SENTINEL; - */ - ArithVar nextIntegerViolation(bool assumeBounds) const; - - /** - * Issues branches for non-auxiliary integer variables with non-integer assignments. - * Returns a cut for a lemma. - * If there is an integer model, this returns Node::null(). - */ - TrustNode roundRobinBranch(); - - bool proofsEnabled() const { return d_pnm; } - - public: - /** - * This requests a new unique ArithVar value for x. - * This also does initial (not context dependent) set up for a variable, - * except for setting up the initial. - * - * If aux is true, this is an auxiliary variable. - * If internal is true, x might not be unique up to a constant multiple. - */ - ArithVar requestArithVar(TNode x, bool aux, bool internal); - -public: - const BoundsInfo& boundsInfo(ArithVar basic) const; - - -private: - /** Initial (not context dependent) sets up for a variable.*/ - void setupBasicValue(ArithVar x); - - /** Initial (not context dependent) sets up for a new auxiliary variable.*/ - void setupAuxiliary(TNode left); - - - /** - * Assert*(n, orig) takes an bound n that is implied by orig. - * and asserts that as a new bound if it is tighter than the current bound - * and updates the value of a basic variable if needed. - * - * orig must be a literal in the SAT solver so that it can be used for - * conflict analysis. - * - * x is the variable getting the new bound, - * c is the value of the new bound. - * - * If this new bound is in conflict with the other bound, - * a node describing this conflict is returned. - * If this new bound is not in conflict, Node::null() is returned. - */ - bool AssertLower(ConstraintP constraint); - bool AssertUpper(ConstraintP constraint); - bool AssertEquality(ConstraintP constraint); - bool AssertDisequality(ConstraintP constraint); - - /** Tracks the bounds that were updated in the current round. */ - DenseSet d_updatedBounds; - - /** Tracks the basic variables where propagation might be possible. */ - DenseSet d_candidateBasics; - DenseSet d_candidateRows; - - bool hasAnyUpdates() { return !d_updatedBounds.empty(); } - void clearUpdates(); - - void revertOutOfConflict(); - - void propagateCandidatesNew(); - void dumpUpdatedBoundsToRows(); - bool propagateCandidateRow(RowIndex rid); - bool propagateMightSucceed(ArithVar v, bool ub) const; - /** Attempt to perform a row propagation where there is at most 1 possible variable.*/ - bool attemptSingleton(RowIndex ridx, bool rowUp); - /** Attempt to perform a row propagation where every variable is a potential candidate.*/ - bool attemptFull(RowIndex ridx, bool rowUp); - bool tryToPropagate(RowIndex ridx, bool rowUp, ArithVar v, bool vUp, const DeltaRational& bound); - bool rowImplicationCanBeApplied(RowIndex ridx, bool rowUp, ConstraintP bestImplied); - //void enqueueConstraints(std::vector& out, Node n) const; - //ConstraintCPVec resolveOutPropagated(const ConstraintCPVec& v, const std::set& propagated) const; - void resolveOutPropagated(std::vector& confs, const std::set& propagated) const; - void subsumption(std::vector& confs) const; - - Node cutToLiteral(ApproximateSimplex* approx, const CutInfo& cut) const; - Node branchToNode(ApproximateSimplex* approx, const NodeLog& cut) const; - - void propagateCandidates(); - void propagateCandidate(ArithVar basic); - bool propagateCandidateBound(ArithVar basic, bool upperBound); - - inline bool propagateCandidateLowerBound(ArithVar basic){ - return propagateCandidateBound(basic, false); - } - inline bool propagateCandidateUpperBound(ArithVar basic){ - return propagateCandidateBound(basic, true); - } - - /** - * Performs a check to see if it is definitely true that setup can be avoided. - */ - bool canSafelyAvoidEqualitySetup(TNode equality); - - /** - * Handles the case splitting for check() for a new assertion. - * Returns a conflict if one was found. - * Returns Node::null if no conflict was found. - * - * @param assertion The assertion that was just popped from the fact queue - * of TheoryArith and given to this class via preNotifyFact. - */ - ConstraintP constraintFromFactQueue(TNode assertion); - bool assertionCases(ConstraintP c); - - /** - * Returns the basic variable with the shorted row containing a non-basic variable. - * If no such row exists, return ARITHVAR_SENTINEL. - */ - ArithVar findShortestBasicRow(ArithVar variable); - - /** - * Debugging only routine! - * Returns true iff every variable is consistent in the partial model. - */ - bool entireStateIsConsistent(const std::string& locationHint); - bool unenqueuedVariablesAreConsistent(); - - bool isImpliedUpperBound(ArithVar var, Node exp); - bool isImpliedLowerBound(ArithVar var, Node exp); - - void internalExplain(TNode n, NodeBuilder& explainBuilder); - - void asVectors(const Polynomial& p, - std::vector& coeffs, - std::vector& variables); - - /** Routine for debugging. Print the assertions the theory is aware of. */ - void debugPrintAssertions(std::ostream& out) const; - /** Debugging only routine. Prints the model. */ - void debugPrintModel(std::ostream& out) const; - - bool done() const { return d_containing.done(); } - bool isLeaf(TNode x) const { return d_containing.isLeaf(x); } - TheoryId theoryOf(TNode x) const { return d_containing.theoryOf(x); } - void debugPrintFacts() const { d_containing.debugPrintFacts(); } - bool outputTrustedLemma(TrustNode lem, InferenceId id); - bool outputLemma(TNode lem, InferenceId id); - void outputTrustedConflict(TrustNode conf, InferenceId id); - void outputConflict(TNode lit, InferenceId id); - void outputPropagate(TNode lit); - void outputRestart(); - - inline bool isSatLiteral(TNode l) const { - return (d_containing.d_valuation).isSatLiteral(l); - } - inline Node getSatValue(TNode n) const { - return (d_containing.d_valuation).getSatValue(n); - } - - /** Used for replaying approximate simplex */ - context::CDQueue d_approxCuts; - /** Also used for replaying approximate simplex. "approximate cuts temporary storage" */ - std::vector d_acTmp; - - /** Counts the number of fullCheck calls to arithmetic. */ - uint32_t d_fullCheckCounter; - std::vector cutAllBounded() const; - TrustNode branchIntegerVariable(ArithVar x) const; - void branchVector(const std::vector& lemmas); - - context::CDO d_cutCount; - context::CDHashSet> d_cutInContext; - - context::CDO d_likelyIntegerInfeasible; - - context::CDO d_guessedCoeffSet; - ArithRatPairVec d_guessedCoeffs; - - - TreeLog* d_treeLog; - TreeLog& getTreeLog(); - - - ArithVarVec d_replayVariables; - std::vector d_replayConstraints; - DenseMap d_lhsTmp; - - /* Approximate simpplex solvers are given a copy of their stats */ - ApproximateStatistics* d_approxStats; - ApproximateStatistics& getApproxStats(); - context::CDO d_attemptSolveIntTurnedOff; - void turnOffApproxFor(int32_t rounds); - bool getSolveIntegerResource(); - - void tryBranchCut(ApproximateSimplex* approx, int nid, BranchCutInfo& bl); - std::vector replayLogRec(ApproximateSimplex* approx, int nid, ConstraintP bc, int depth); - - std::pair replayGetConstraint(const CutInfo& info); - std::pair replayGetConstraint( - ApproximateSimplex* approx, const NodeLog& nl); - std::pair replayGetConstraint(const DenseMap& lhs, Kind k, const Rational& rhs, bool branch); - - void replayAssert(ConstraintP c); - - static ConstraintCP vectorToIntHoleConflict(const ConstraintCPVec& conflict); - static void intHoleConflictToVector(ConstraintCP conflicting, ConstraintCPVec& conflict); - - // Returns true if the node contains a literal - // that is an arithmetic literal and is not a sat literal - // No caching is done so this should likely only - // be called carefully! - bool hasFreshArithLiteral(Node n) const; - - int32_t d_dioSolveResources; - bool getDioCuttingResource(); - - uint32_t d_solveIntMaybeHelp, d_solveIntAttempts; - - RationalVector d_farkasBuffer; - - //---------------- during check - /** Whether there were new facts during preCheck */ - bool d_newFacts; - /** The previous status, computed during preCheck */ - Result::Status d_previousStatus; - //---------------- end during check - - /** These fields are designed to be accessible to TheoryArith methods. */ - class Statistics { - public: - IntStat d_statAssertUpperConflicts, d_statAssertLowerConflicts; - - IntStat d_statUserVariables, d_statAuxiliaryVariables; - IntStat d_statDisequalitySplits; - IntStat d_statDisequalityConflicts; - TimerStat d_simplifyTimer; - TimerStat d_staticLearningTimer; - - TimerStat d_presolveTime; - - TimerStat d_newPropTime; - - IntStat d_externalBranchAndBounds; - - IntStat d_initialTableauSize; - IntStat d_currSetToSmaller; - IntStat d_smallerSetToCurr; - TimerStat d_restartTimer; - - TimerStat d_boundComputationTime; - IntStat d_boundComputations, d_boundPropagations; - - IntStat d_unknownChecks; - IntStat d_maxUnknownsInARow; - AverageStat d_avgUnknownsInARow; - - IntStat d_revertsOnConflicts; - IntStat d_commitsOnConflicts; - IntStat d_nontrivialSatChecks; - - IntStat d_replayLogRecCount, - d_replayLogRecConflictEscalation, - d_replayLogRecEarlyExit, - d_replayBranchCloseFailures, - d_replayLeafCloseFailures, - d_replayBranchSkips, - d_mirCutsAttempted, - d_gmiCutsAttempted, - d_branchCutsAttempted, - d_cutsReconstructed, - d_cutsReconstructionFailed, - d_cutsProven, - d_cutsProofFailed, - d_mipReplayLemmaCalls, - d_mipExternalCuts, - d_mipExternalBranch; - - IntStat d_inSolveInteger, - d_branchesExhausted, - d_execExhausted, - d_pivotsExhausted, - d_panicBranches, - d_relaxCalls, - d_relaxLinFeas, - d_relaxLinFeasFailures, - d_relaxLinInfeas, - d_relaxLinInfeasFailures, - d_relaxLinExhausted, - d_relaxOthers; - - IntStat d_applyRowsDeleted; - TimerStat d_replaySimplexTimer; - - TimerStat d_replayLogTimer, - d_solveIntTimer, - d_solveRealRelaxTimer; - - IntStat d_solveIntCalls, - d_solveStandardEffort; - - IntStat d_approxDisabled; - IntStat d_replayAttemptFailed; - - IntStat d_cutsRejectedDuringReplay; - IntStat d_cutsRejectedDuringLemmas; - - HistogramStat d_satPivots; - HistogramStat d_unsatPivots; - HistogramStat d_unknownPivots; - - IntStat d_solveIntModelsAttempts; - IntStat d_solveIntModelsSuccessful; - TimerStat d_mipTimer; - TimerStat d_lpTimer; - - IntStat d_mipProofsAttempted; - IntStat d_mipProofsSuccessful; - - IntStat d_numBranchesFailed; - - Statistics(StatisticsRegistry& reg, const std::string& name); - }; - - - Statistics d_statistics; -}; /* class TheoryArithPrivate */ - -} // namespace arith -} // namespace theory -} // namespace cvc5::internal diff --git a/src/theory/quantifiers/cegqi/ceg_arith_instantiator.cpp b/src/theory/quantifiers/cegqi/ceg_arith_instantiator.cpp index 33b5f08f2..e468ae001 100644 --- a/src/theory/quantifiers/cegqi/ceg_arith_instantiator.cpp +++ b/src/theory/quantifiers/cegqi/ceg_arith_instantiator.cpp @@ -18,9 +18,9 @@ #include "expr/node_algorithm.h" #include "options/quantifiers_options.h" #include "theory/arith/arith_msum.h" -#include "theory/arith/partial_model.h" +#include "theory/arith/linear/partial_model.h" #include "theory/arith/theory_arith.h" -#include "theory/arith/theory_arith_private.h" +#include "theory/arith/linear/theory_arith_private.h" #include "theory/quantifiers/term_util.h" #include "theory/rewriter.h" #include "util/random.h"